♻️ Refactor ICP server to make it available across platform
This commit is contained in:
		| @@ -12,8 +12,6 @@ import 'package:web_socket_channel/web_socket_channel.dart'; | |||||||
|  |  | ||||||
| // Conditional imports for IPC server - use web stubs on web platform | // Conditional imports for IPC server - use web stubs on web platform | ||||||
| import 'ipc_server.dart' if (dart.library.html) 'ipc_server.web.dart'; | import 'ipc_server.dart' if (dart.library.html) 'ipc_server.web.dart'; | ||||||
| import 'ipc_server.windows.dart' if (dart.library.html) 'ipc_server.web.dart'; |  | ||||||
| import 'ipc_server.unix.dart' if (dart.library.html) 'ipc_server.web.dart'; |  | ||||||
|  |  | ||||||
| const String kRpcLogPrefix = 'arRPC.websocket'; | const String kRpcLogPrefix = 'arRPC.websocket'; | ||||||
| const String kRpcIpcLogPrefix = 'arRPC.ipc'; | const String kRpcIpcLogPrefix = 'arRPC.ipc'; | ||||||
| @@ -112,11 +110,7 @@ class ActivityRpcServer { | |||||||
|     final shouldStartIpc = !Platform.isMacOS && !kIsWeb; |     final shouldStartIpc = !Platform.isMacOS && !kIsWeb; | ||||||
|     if (shouldStartIpc) { |     if (shouldStartIpc) { | ||||||
|       try { |       try { | ||||||
|         if (Platform.isWindows) { |         _ipcServer = MultiPlatformIpcServer(); | ||||||
|           _ipcServer = WindowsIpcServer(); |  | ||||||
|         } else { |  | ||||||
|           _ipcServer = UnixIpcServer(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         // Set up IPC handlers |         // Set up IPC handlers | ||||||
|         _ipcServer!.handlePacket = (socket, packet, _) { |         _ipcServer!.handlePacket = (socket, packet, _) { | ||||||
|   | |||||||
| @@ -1,6 +1,10 @@ | |||||||
| import 'dart:async'; | import 'dart:async'; | ||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
|  | import 'dart:developer' as developer; | ||||||
|  | import 'dart:io'; | ||||||
| import 'dart:typed_data'; | import 'dart:typed_data'; | ||||||
|  | import 'package:dart_ipc/dart_ipc.dart'; | ||||||
|  | import 'package:path/path.dart' as path; | ||||||
|  |  | ||||||
| const String kRpcIpcLogPrefix = 'arRPC.ipc'; | const String kRpcIpcLogPrefix = 'arRPC.ipc'; | ||||||
|  |  | ||||||
| @@ -116,3 +120,178 @@ abstract class IpcSocketWrapper { | |||||||
|     return packets; |     return packets; | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Multiplatform IPC Server implementation using dart_ipc | ||||||
|  | class MultiPlatformIpcServer extends IpcServer { | ||||||
|  |   StreamSubscription? _serverSubscription; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<void> start() async { | ||||||
|  |     try { | ||||||
|  |       final ipcPath = Platform.isWindows | ||||||
|  |           ? r'\\.\pipe\discord-ipc-0' | ||||||
|  |           : await _findAvailableUnixIpcPath(); | ||||||
|  |  | ||||||
|  |       final serverSocket = await bind(ipcPath); | ||||||
|  |       developer.log( | ||||||
|  |         'IPC listening at $ipcPath', | ||||||
|  |         name: kRpcIpcLogPrefix, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       _serverSubscription = serverSocket.listen((socket) { | ||||||
|  |         final socketWrapper = MultiPlatformIpcSocketWrapper(socket); | ||||||
|  |         addSocket(socketWrapper); | ||||||
|  |         developer.log( | ||||||
|  |           'New IPC connection!', | ||||||
|  |           name: kRpcIpcLogPrefix, | ||||||
|  |         ); | ||||||
|  |         _handleIpcData(socketWrapper); | ||||||
|  |       }); | ||||||
|  |     } catch (e) { | ||||||
|  |       throw Exception('Failed to start IPC server: $e'); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<void> stop() async { | ||||||
|  |     for (var socket in sockets) { | ||||||
|  |       try { | ||||||
|  |         socket.close(); | ||||||
|  |       } catch (e) { | ||||||
|  |         developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     sockets.clear(); | ||||||
|  |     _serverSubscription?.cancel(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Handle incoming IPC data | ||||||
|  |   void _handleIpcData(MultiPlatformIpcSocketWrapper socket) { | ||||||
|  |     final startTime = DateTime.now(); | ||||||
|  |     socket.socket.listen((data) { | ||||||
|  |       final readStart = DateTime.now(); | ||||||
|  |       socket.addData(data); | ||||||
|  |       final readDuration = DateTime.now().difference(readStart).inMicroseconds; | ||||||
|  |       developer.log( | ||||||
|  |         'Read data took $readDuration microseconds', | ||||||
|  |         name: kRpcIpcLogPrefix, | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       final packets = socket.readPackets(); | ||||||
|  |       for (final packet in packets) { | ||||||
|  |         handlePacket?.call(socket, packet, {}); | ||||||
|  |       } | ||||||
|  |     }, onDone: () { | ||||||
|  |       developer.log('IPC connection closed', name: kRpcIpcLogPrefix); | ||||||
|  |       socket.close(); | ||||||
|  |     }, onError: (e) { | ||||||
|  |       developer.log('IPC data error: $e', name: kRpcIpcLogPrefix); | ||||||
|  |       socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString()); | ||||||
|  |     }); | ||||||
|  |     final totalDuration = DateTime.now().difference(startTime).inMicroseconds; | ||||||
|  |     developer.log( | ||||||
|  |       '_handleIpcData took $totalDuration microseconds', | ||||||
|  |       name: kRpcIpcLogPrefix, | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   Future<String> _getMacOsSystemTmpDir() async { | ||||||
|  |     final result = await Process.run('getconf', ['DARWIN_USER_TEMP_DIR']); | ||||||
|  |     return (result.stdout as String).trim(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // Find available IPC socket path for Unix-like systems | ||||||
|  |   Future<String> _findAvailableUnixIpcPath() async { | ||||||
|  |     // Build list of directories to try, with macOS-specific handling | ||||||
|  |     final baseDirs = <String>[]; | ||||||
|  |  | ||||||
|  |     if (Platform.isMacOS) { | ||||||
|  |       try { | ||||||
|  |         final macTempDir = await _getMacOsSystemTmpDir(); | ||||||
|  |         if (macTempDir.isNotEmpty) { | ||||||
|  |           baseDirs.add(macTempDir); | ||||||
|  |         } | ||||||
|  |       } catch (e) { | ||||||
|  |         developer.log( | ||||||
|  |           'Failed to get macOS system temp dir: $e', | ||||||
|  |           name: kRpcIpcLogPrefix, | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Add other standard directories | ||||||
|  |     final otherDirs = [ | ||||||
|  |       Platform.environment['XDG_RUNTIME_DIR'], | ||||||
|  |       Platform.environment['TMPDIR'], | ||||||
|  |       Platform.environment['TMP'], | ||||||
|  |       Platform.environment['TEMP'], | ||||||
|  |       '/tmp', | ||||||
|  |     ]; | ||||||
|  |  | ||||||
|  |     baseDirs.addAll( | ||||||
|  |       otherDirs.where((dir) => dir != null && dir.isNotEmpty).cast<String>(), | ||||||
|  |     ); | ||||||
|  |  | ||||||
|  |     for (final baseDir in baseDirs) { | ||||||
|  |       for (int i = 0; i < 10; i++) { | ||||||
|  |         final socketPath = path.join(baseDir, 'discord-ipc-$i'); | ||||||
|  |         try { | ||||||
|  |           final socket = await bind(socketPath); | ||||||
|  |           socket.close(); | ||||||
|  |           try { | ||||||
|  |             await File(socketPath).delete(); | ||||||
|  |           } catch (_) {} | ||||||
|  |           developer.log( | ||||||
|  |             'IPC socket will be created at: $socketPath', | ||||||
|  |             name: kRpcIpcLogPrefix, | ||||||
|  |           ); | ||||||
|  |           return socketPath; | ||||||
|  |         } catch (e) { | ||||||
|  |           if (i == 0) { | ||||||
|  |             developer.log( | ||||||
|  |               'IPC path $socketPath not available: $e', | ||||||
|  |               name: kRpcIpcLogPrefix, | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     throw Exception( | ||||||
|  |       'No available IPC socket paths found in any temp directory', | ||||||
|  |     ); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Multiplatform IPC Socket Wrapper | ||||||
|  | class MultiPlatformIpcSocketWrapper extends IpcSocketWrapper { | ||||||
|  |   final dynamic socket; | ||||||
|  |  | ||||||
|  |   MultiPlatformIpcSocketWrapper(this.socket); | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void send(Map<String, dynamic> msg) { | ||||||
|  |     developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix); | ||||||
|  |     final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg); | ||||||
|  |     socket.add(packet); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void sendPong(dynamic data) { | ||||||
|  |     final packet = IpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {}); | ||||||
|  |     socket.add(packet); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void close() { | ||||||
|  |     socket.close(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void closeWithCode(int code, [String message = '']) { | ||||||
|  |     final closeData = {'code': code, 'message': message}; | ||||||
|  |     final packet = IpcServer.encodeIpcPacket(IpcTypes.close, closeData); | ||||||
|  |     socket.add(packet); | ||||||
|  |     socket.close(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,172 +0,0 @@ | |||||||
| import 'dart:async'; |  | ||||||
| import 'dart:developer' as developer; |  | ||||||
| import 'dart:io'; |  | ||||||
| import 'package:path/path.dart' as path; |  | ||||||
| import 'ipc_server.dart'; |  | ||||||
|  |  | ||||||
| class UnixIpcServer extends IpcServer { |  | ||||||
|   ServerSocket? _ipcServer; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Future<void> start() async { |  | ||||||
|     final ipcPath = await _findAvailableIpcPath(); |  | ||||||
|     _ipcServer = await ServerSocket.bind( |  | ||||||
|       InternetAddress(ipcPath, type: InternetAddressType.unix), |  | ||||||
|       0, |  | ||||||
|     ); |  | ||||||
|     developer.log('IPC listening at $ipcPath', name: kRpcIpcLogPrefix); |  | ||||||
|  |  | ||||||
|     _ipcServer!.listen((Socket socket) { |  | ||||||
|       _onIpcConnection(socket); |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Future<void> stop() async { |  | ||||||
|     for (var socket in sockets) { |  | ||||||
|       try { |  | ||||||
|         socket.close(); |  | ||||||
|       } catch (e) { |  | ||||||
|         developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     sockets.clear(); |  | ||||||
|     await _ipcServer?.close(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Handle new IPC connection |  | ||||||
|   void _onIpcConnection(Socket socket) { |  | ||||||
|     developer.log('New IPC connection!', name: kRpcIpcLogPrefix); |  | ||||||
|  |  | ||||||
|     final socketWrapper = UnixIpcSocketWrapper(socket); |  | ||||||
|     addSocket(socketWrapper); |  | ||||||
|  |  | ||||||
|     socket.listen( |  | ||||||
|       (data) => _onIpcData(socketWrapper, data), |  | ||||||
|       onError: (e) { |  | ||||||
|         developer.log('IPC socket error: $e', name: kRpcIpcLogPrefix); |  | ||||||
|         socket.close(); |  | ||||||
|       }, |  | ||||||
|       onDone: () { |  | ||||||
|         developer.log('IPC socket closed', name: kRpcIpcLogPrefix); |  | ||||||
|         removeSocket(socketWrapper); |  | ||||||
|       }, |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Handle incoming IPC data |  | ||||||
|   void _onIpcData(UnixIpcSocketWrapper socket, List<int> data) { |  | ||||||
|     try { |  | ||||||
|       socket.addData(data); |  | ||||||
|       final packets = socket.readPackets(); |  | ||||||
|       for (final packet in packets) { |  | ||||||
|         handlePacket?.call(socket, packet, {}); |  | ||||||
|       } |  | ||||||
|     } catch (e) { |  | ||||||
|       developer.log('IPC data error: $e', name: kRpcIpcLogPrefix); |  | ||||||
|       socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString()); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<String> _getMacOsSystemTmpDir() async { |  | ||||||
|     final result = await Process.run('getconf', ['DARWIN_USER_TEMP_DIR']); |  | ||||||
|     return (result.stdout as String).trim(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Find available IPC socket path |  | ||||||
|   Future<String> _findAvailableIpcPath() async { |  | ||||||
|     // Build list of directories to try, with macOS-specific handling |  | ||||||
|     final baseDirs = <String>[]; |  | ||||||
|  |  | ||||||
|     if (Platform.isMacOS) { |  | ||||||
|       try { |  | ||||||
|         final macTempDir = await _getMacOsSystemTmpDir(); |  | ||||||
|         if (macTempDir.isNotEmpty) { |  | ||||||
|           baseDirs.add(macTempDir); |  | ||||||
|         } |  | ||||||
|       } catch (e) { |  | ||||||
|         developer.log( |  | ||||||
|           'Failed to get macOS system temp dir: $e', |  | ||||||
|           name: kRpcIpcLogPrefix, |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // Add other standard directories |  | ||||||
|     final otherDirs = [ |  | ||||||
|       Platform.environment['XDG_RUNTIME_DIR'], |  | ||||||
|       Platform.environment['TMPDIR'], |  | ||||||
|       Platform.environment['TMP'], |  | ||||||
|       Platform.environment['TEMP'], |  | ||||||
|       '/tmp', |  | ||||||
|     ]; |  | ||||||
|  |  | ||||||
|     baseDirs.addAll( |  | ||||||
|       otherDirs.where((dir) => dir != null && dir.isNotEmpty).cast<String>(), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     for (final baseDir in baseDirs) { |  | ||||||
|       for (int i = 0; i < 10; i++) { |  | ||||||
|         final socketPath = path.join(baseDir, 'discord-ipc-$i'); |  | ||||||
|         try { |  | ||||||
|           final socket = await ServerSocket.bind( |  | ||||||
|             InternetAddress(socketPath, type: InternetAddressType.unix), |  | ||||||
|             0, |  | ||||||
|           ); |  | ||||||
|           socket.close(); |  | ||||||
|           try { |  | ||||||
|             await File(socketPath).delete(); |  | ||||||
|           } catch (_) {} |  | ||||||
|           developer.log( |  | ||||||
|             'IPC socket will be created at: $socketPath', |  | ||||||
|             name: kRpcIpcLogPrefix, |  | ||||||
|           ); |  | ||||||
|           return socketPath; |  | ||||||
|         } catch (e) { |  | ||||||
|           if (i == 0) { |  | ||||||
|             developer.log( |  | ||||||
|               'IPC path $socketPath not available: $e', |  | ||||||
|               name: kRpcIpcLogPrefix, |  | ||||||
|             ); |  | ||||||
|           } |  | ||||||
|           continue; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     throw Exception( |  | ||||||
|       'No available IPC socket paths found in any temp directory', |  | ||||||
|     ); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class UnixIpcSocketWrapper extends IpcSocketWrapper { |  | ||||||
|   final Socket socket; |  | ||||||
|  |  | ||||||
|   UnixIpcSocketWrapper(this.socket); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void send(Map<String, dynamic> msg) { |  | ||||||
|     developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix); |  | ||||||
|     final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg); |  | ||||||
|     socket.add(packet); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void sendPong(dynamic data) { |  | ||||||
|     final packet = IpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {}); |  | ||||||
|     socket.add(packet); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void close() { |  | ||||||
|     socket.close(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void closeWithCode(int code, [String message = '']) { |  | ||||||
|     final closeData = {'code': code, 'message': message}; |  | ||||||
|     final packet = IpcServer.encodeIpcPacket(IpcTypes.close, closeData); |  | ||||||
|     socket.add(packet); |  | ||||||
|     socket.close(); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -56,8 +56,6 @@ class IpcSocketWrapper { | |||||||
|   List<dynamic> readPackets() => []; |   List<dynamic> readPackets() => []; | ||||||
| } | } | ||||||
|  |  | ||||||
| class WindowsIpcServer extends IpcServer {} | class MultiPlatformIpcServer extends IpcServer {} | ||||||
|  |  | ||||||
| class UnixIpcServer extends IpcServer {} | class MultiPlatformIpcSocketWrapper extends IpcSocketWrapper {} | ||||||
|  |  | ||||||
| class WindowsIpcSocketWrapper extends IpcSocketWrapper {} |  | ||||||
|   | |||||||
| @@ -1,243 +0,0 @@ | |||||||
| import 'dart:async'; |  | ||||||
| import 'dart:developer' as developer; |  | ||||||
| import 'dart:ffi'; |  | ||||||
| import 'dart:io'; |  | ||||||
| import 'dart:isolate'; |  | ||||||
| import 'package:ffi/ffi.dart'; |  | ||||||
| import 'package:win32/win32.dart'; |  | ||||||
| import 'ipc_server.dart'; |  | ||||||
|  |  | ||||||
| class WindowsIpcServer extends IpcServer { |  | ||||||
|   int? _pipeHandle; |  | ||||||
|   Timer? _ipcTimer; |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Future<void> start() async { |  | ||||||
|     final pipeName = r'\\.\pipe\discord-ipc-0'.toNativeUtf16(); |  | ||||||
|     try { |  | ||||||
|       _pipeHandle = CreateNamedPipe( |  | ||||||
|         pipeName, |  | ||||||
|         PIPE_ACCESS_DUPLEX, |  | ||||||
|         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, |  | ||||||
|         PIPE_UNLIMITED_INSTANCES, |  | ||||||
|         4096, // Output buffer size |  | ||||||
|         4096, // Input buffer size |  | ||||||
|         0, // Default timeout |  | ||||||
|         nullptr, // Security attributes |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       if (_pipeHandle == INVALID_HANDLE_VALUE) { |  | ||||||
|         final error = GetLastError(); |  | ||||||
|         throw Exception('Failed to create named pipe: error code $error'); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       developer.log( |  | ||||||
|         r'IPC named pipe created at \\.\pipe\discord-ipc-0', |  | ||||||
|         name: kRpcIpcLogPrefix, |  | ||||||
|       ); |  | ||||||
|  |  | ||||||
|       // Start listening for connections in a separate isolate |  | ||||||
|       _listenWindowsIpc(); |  | ||||||
|     } finally { |  | ||||||
|       free(pipeName); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   Future<void> stop() async { |  | ||||||
|     for (var socket in sockets) { |  | ||||||
|       try { |  | ||||||
|         socket.close(); |  | ||||||
|       } catch (e) { |  | ||||||
|         developer.log('Error closing IPC socket: $e', name: kRpcIpcLogPrefix); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     sockets.clear(); |  | ||||||
|  |  | ||||||
|     if (_pipeHandle != null) { |  | ||||||
|       try { |  | ||||||
|         CloseHandle(_pipeHandle!); |  | ||||||
|       } catch (e) { |  | ||||||
|         developer.log('Error closing named pipe: $e', name: kRpcIpcLogPrefix); |  | ||||||
|       } |  | ||||||
|       _pipeHandle = null; |  | ||||||
|     } |  | ||||||
|     _ipcTimer?.cancel(); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Listen for Windows IPC connections in an isolate |  | ||||||
|   void _listenWindowsIpc() async { |  | ||||||
|     final receivePort = ReceivePort(); |  | ||||||
|     await Isolate.spawn(_windowsIpcIsolate, receivePort.sendPort); |  | ||||||
|  |  | ||||||
|     receivePort.listen((message) { |  | ||||||
|       developer.log(message.toString(), name: kRpcIpcLogPrefix); |  | ||||||
|       if (message is int) { |  | ||||||
|         final socketWrapper = WindowsIpcSocketWrapper(message); |  | ||||||
|         addSocket(socketWrapper); |  | ||||||
|         developer.log( |  | ||||||
|           'New IPC connection on named pipe', |  | ||||||
|           name: kRpcIpcLogPrefix, |  | ||||||
|         ); |  | ||||||
|         _handleWindowsIpcData(socketWrapper); |  | ||||||
|         start(); // Create new pipe for next connection |  | ||||||
|       } |  | ||||||
|     }); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   static void _windowsIpcIsolate(SendPort sendPort) { |  | ||||||
|     while (true) { |  | ||||||
|       final pipeHandle = CreateNamedPipe( |  | ||||||
|         r'\\.\pipe\discord-ipc-0'.toNativeUtf16(), |  | ||||||
|         PIPE_ACCESS_DUPLEX, |  | ||||||
|         PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, |  | ||||||
|         PIPE_UNLIMITED_INSTANCES, |  | ||||||
|         4096, |  | ||||||
|         4096, |  | ||||||
|         0, |  | ||||||
|         nullptr, |  | ||||||
|       ); |  | ||||||
|       if (pipeHandle == INVALID_HANDLE_VALUE) { |  | ||||||
|         developer.log( |  | ||||||
|           'Failed to create named pipe: ${GetLastError()}', |  | ||||||
|           name: kRpcIpcLogPrefix, |  | ||||||
|         ); |  | ||||||
|         break; |  | ||||||
|       } |  | ||||||
|       final connected = ConnectNamedPipe(pipeHandle, nullptr); |  | ||||||
|       if (connected != 0 || GetLastError() == ERROR_PIPE_CONNECTED) { |  | ||||||
|         sendPort.send(pipeHandle); |  | ||||||
|       } |  | ||||||
|       // Avoid tight loop |  | ||||||
|       sleep(Duration(milliseconds: 500)); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   // Handle Windows IPC data |  | ||||||
|   void _handleWindowsIpcData(WindowsIpcSocketWrapper socket) async { |  | ||||||
|     final startTime = DateTime.now(); |  | ||||||
|     final buffer = malloc.allocate<BYTE>(4096); |  | ||||||
|     final bytesRead = malloc.allocate<DWORD>(4); |  | ||||||
|     try { |  | ||||||
|       while (socket.pipeHandle != null) { |  | ||||||
|         final readStart = DateTime.now(); |  | ||||||
|         final success = ReadFile( |  | ||||||
|           socket.pipeHandle!, |  | ||||||
|           buffer.cast(), |  | ||||||
|           4096, |  | ||||||
|           bytesRead, |  | ||||||
|           nullptr, |  | ||||||
|         ); |  | ||||||
|         final readDuration = |  | ||||||
|             DateTime.now().difference(readStart).inMicroseconds; |  | ||||||
|         developer.log( |  | ||||||
|           'ReadFile took $readDuration microseconds', |  | ||||||
|           name: kRpcIpcLogPrefix, |  | ||||||
|         ); |  | ||||||
|  |  | ||||||
|         if (success == FALSE && GetLastError() != ERROR_MORE_DATA) { |  | ||||||
|           developer.log( |  | ||||||
|             'IPC read error: ${GetLastError()}', |  | ||||||
|             name: kRpcIpcLogPrefix, |  | ||||||
|           ); |  | ||||||
|           socket.close(); |  | ||||||
|           break; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         final data = buffer.asTypedList(0); |  | ||||||
|         socket.addData(data); |  | ||||||
|         final packets = socket.readPackets(); |  | ||||||
|         for (final packet in packets) { |  | ||||||
|           handlePacket?.call(socket, packet, {}); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|     } catch (e) { |  | ||||||
|       developer.log('IPC data error: $e', name: kRpcIpcLogPrefix); |  | ||||||
|       socket.closeWithCode(IpcCloseCodes.closeUnsupported, e.toString()); |  | ||||||
|     } finally { |  | ||||||
|       malloc.free(buffer); |  | ||||||
|       malloc.free(bytesRead); |  | ||||||
|       final totalDuration = DateTime.now().difference(startTime).inMicroseconds; |  | ||||||
|       developer.log( |  | ||||||
|         'handleWindowsIpcData took $totalDuration microseconds', |  | ||||||
|         name: kRpcIpcLogPrefix, |  | ||||||
|       ); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class WindowsIpcSocketWrapper extends IpcSocketWrapper { |  | ||||||
|   final int? pipeHandle; |  | ||||||
|  |  | ||||||
|   WindowsIpcSocketWrapper(this.pipeHandle); |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void send(Map<String, dynamic> msg) { |  | ||||||
|     developer.log('IPC sending: $msg', name: kRpcIpcLogPrefix); |  | ||||||
|     final packet = IpcServer.encodeIpcPacket(IpcTypes.frame, msg); |  | ||||||
|     final buffer = malloc.allocate<BYTE>(packet.length); |  | ||||||
|     buffer.asTypedList(packet.length).setAll(0, packet); |  | ||||||
|     final bytesWritten = malloc.allocate<DWORD>(4); // DWORD is 4 bytes |  | ||||||
|     try { |  | ||||||
|       WriteFile( |  | ||||||
|         pipeHandle!, |  | ||||||
|         buffer.cast(), |  | ||||||
|         packet.length, |  | ||||||
|         bytesWritten, |  | ||||||
|         nullptr, |  | ||||||
|       ); |  | ||||||
|     } finally { |  | ||||||
|       malloc.free(buffer); |  | ||||||
|       malloc.free(bytesWritten); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void sendPong(dynamic data) { |  | ||||||
|     final packet = IpcServer.encodeIpcPacket(IpcTypes.pong, data ?? {}); |  | ||||||
|     final buffer = malloc.allocate<BYTE>(packet.length); |  | ||||||
|     buffer.asTypedList(packet.length).setAll(0, packet); |  | ||||||
|     final bytesWritten = malloc.allocate<DWORD>(4); // DWORD is 4 bytes |  | ||||||
|     try { |  | ||||||
|       WriteFile( |  | ||||||
|         pipeHandle!, |  | ||||||
|         buffer.cast(), |  | ||||||
|         packet.length, |  | ||||||
|         bytesWritten, |  | ||||||
|         nullptr, |  | ||||||
|       ); |  | ||||||
|     } finally { |  | ||||||
|       malloc.free(buffer); |  | ||||||
|       malloc.free(bytesWritten); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void close() { |  | ||||||
|     if (pipeHandle != null) { |  | ||||||
|       CloseHandle(pipeHandle!); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   @override |  | ||||||
|   void closeWithCode(int code, [String message = '']) { |  | ||||||
|     final closeData = {'code': code, 'message': message}; |  | ||||||
|     final packet = IpcServer.encodeIpcPacket(IpcTypes.close, closeData); |  | ||||||
|     final buffer = malloc.allocate<BYTE>(packet.length); |  | ||||||
|     buffer.asTypedList(packet.length).setAll(0, packet); |  | ||||||
|     final bytesWritten = malloc.allocate<DWORD>(4); // DWORD is 4 bytes |  | ||||||
|     try { |  | ||||||
|       WriteFile( |  | ||||||
|         pipeHandle!, |  | ||||||
|         buffer.cast(), |  | ||||||
|         packet.length, |  | ||||||
|         bytesWritten, |  | ||||||
|         nullptr, |  | ||||||
|       ); |  | ||||||
|     } finally { |  | ||||||
|       malloc.free(buffer); |  | ||||||
|       malloc.free(bytesWritten); |  | ||||||
|     } |  | ||||||
|     CloseHandle(pipeHandle!); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| @@ -30,7 +30,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|       final user = SnAccount.fromJson(response.data); |       final user = SnAccount.fromJson(response.data); | ||||||
|       state = AsyncValue.data(user); |       state = AsyncValue.data(user); | ||||||
|  |  | ||||||
|       if (kIsWeb || !Platform.isLinux) { |       if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) { | ||||||
|         FirebaseAnalytics.instance.setUserId(id: user.id); |         FirebaseAnalytics.instance.setUserId(id: user.id); | ||||||
|       } |       } | ||||||
|     } catch (error, stackTrace) { |     } catch (error, stackTrace) { | ||||||
| @@ -44,7 +44,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|                       : 'failedToLoadUserInfoNetwork') |                       : 'failedToLoadUserInfoNetwork') | ||||||
|                   .tr() |                   .tr() | ||||||
|                   .trim(), |                   .trim(), | ||||||
|               '${error.response!.statusCode}\n${error.response?.headers}', |               '${error.response?.statusCode ?? 'Network Error'}\n${error.response?.headers}', | ||||||
|               jsonEncode(error.response?.data), |               jsonEncode(error.response?.data), | ||||||
|             ].join('\n\n'), |             ].join('\n\n'), | ||||||
|             iconStyle: IconStyle.error, |             iconStyle: IconStyle.error, | ||||||
| @@ -87,7 +87,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | |||||||
|     final prefs = _ref.read(sharedPreferencesProvider); |     final prefs = _ref.read(sharedPreferencesProvider); | ||||||
|     await prefs.remove(kTokenPairStoreKey); |     await prefs.remove(kTokenPairStoreKey); | ||||||
|     _ref.invalidate(tokenProvider); |     _ref.invalidate(tokenProvider); | ||||||
|     if (kIsWeb || !Platform.isLinux) { |     if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) { | ||||||
|       FirebaseAnalytics.instance.setUserId(id: null); |       FirebaseAnalytics.instance.setUserId(id: null); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -401,6 +401,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.0.0+7.7.0" |     version: "1.0.0+7.7.0" | ||||||
|  |   dart_ipc: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: dart_ipc | ||||||
|  |       sha256: "6cad558cda5304017c1f581df4c96fd4f8e4ee212aae7bfa4357716236faa9ba" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.0.1" | ||||||
|   dart_style: |   dart_style: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -150,6 +150,7 @@ dependencies: | |||||||
|   windows_notification: ^1.3.0 |   windows_notification: ^1.3.0 | ||||||
|   win32: ^5.14.0 |   win32: ^5.14.0 | ||||||
|   ffi: ^2.1.4 |   ffi: ^2.1.4 | ||||||
|  |   dart_ipc: ^1.0.0 | ||||||
|  |  | ||||||
| dev_dependencies: | dev_dependencies: | ||||||
|   flutter_test: |   flutter_test: | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ | |||||||
|  |  | ||||||
| #include <bitsdojo_window_windows/bitsdojo_window_plugin.h> | #include <bitsdojo_window_windows/bitsdojo_window_plugin.h> | ||||||
| #include <connectivity_plus/connectivity_plus_windows_plugin.h> | #include <connectivity_plus/connectivity_plus_windows_plugin.h> | ||||||
|  | #include <dart_ipc/dart_ipc_plugin_c_api.h> | ||||||
| #include <file_saver/file_saver_plugin.h> | #include <file_saver/file_saver_plugin.h> | ||||||
| #include <file_selector_windows/file_selector_windows.h> | #include <file_selector_windows/file_selector_windows.h> | ||||||
| #include <firebase_core/firebase_core_plugin_c_api.h> | #include <firebase_core/firebase_core_plugin_c_api.h> | ||||||
| @@ -38,6 +39,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { | |||||||
|       registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); |       registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); | ||||||
|   ConnectivityPlusWindowsPluginRegisterWithRegistrar( |   ConnectivityPlusWindowsPluginRegisterWithRegistrar( | ||||||
|       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); |       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); | ||||||
|  |   DartIpcPluginCApiRegisterWithRegistrar( | ||||||
|  |       registry->GetRegistrarForPlugin("DartIpcPluginCApi")); | ||||||
|   FileSaverPluginRegisterWithRegistrar( |   FileSaverPluginRegisterWithRegistrar( | ||||||
|       registry->GetRegistrarForPlugin("FileSaverPlugin")); |       registry->GetRegistrarForPlugin("FileSaverPlugin")); | ||||||
|   FileSelectorWindowsRegisterWithRegistrar( |   FileSelectorWindowsRegisterWithRegistrar( | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ | |||||||
| list(APPEND FLUTTER_PLUGIN_LIST | list(APPEND FLUTTER_PLUGIN_LIST | ||||||
|   bitsdojo_window_windows |   bitsdojo_window_windows | ||||||
|   connectivity_plus |   connectivity_plus | ||||||
|  |   dart_ipc | ||||||
|   file_saver |   file_saver | ||||||
|   file_selector_windows |   file_selector_windows | ||||||
|   firebase_core |   firebase_core | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user