♻️ 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 | ||||
| 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 kRpcIpcLogPrefix = 'arRPC.ipc'; | ||||
| @@ -112,11 +110,7 @@ class ActivityRpcServer { | ||||
|     final shouldStartIpc = !Platform.isMacOS && !kIsWeb; | ||||
|     if (shouldStartIpc) { | ||||
|       try { | ||||
|         if (Platform.isWindows) { | ||||
|           _ipcServer = WindowsIpcServer(); | ||||
|         } else { | ||||
|           _ipcServer = UnixIpcServer(); | ||||
|         } | ||||
|         _ipcServer = MultiPlatformIpcServer(); | ||||
|  | ||||
|         // Set up IPC handlers | ||||
|         _ipcServer!.handlePacket = (socket, packet, _) { | ||||
|   | ||||
| @@ -1,6 +1,10 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:developer' as developer; | ||||
| import 'dart:io'; | ||||
| import 'dart:typed_data'; | ||||
| import 'package:dart_ipc/dart_ipc.dart'; | ||||
| import 'package:path/path.dart' as path; | ||||
|  | ||||
| const String kRpcIpcLogPrefix = 'arRPC.ipc'; | ||||
|  | ||||
| @@ -116,3 +120,178 @@ abstract class IpcSocketWrapper { | ||||
|     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() => []; | ||||
| } | ||||
|  | ||||
| class WindowsIpcServer extends IpcServer {} | ||||
| class MultiPlatformIpcServer extends IpcServer {} | ||||
|  | ||||
| class UnixIpcServer extends IpcServer {} | ||||
|  | ||||
| class WindowsIpcSocketWrapper extends IpcSocketWrapper {} | ||||
| class MultiPlatformIpcSocketWrapper 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); | ||||
|       state = AsyncValue.data(user); | ||||
|  | ||||
|       if (kIsWeb || !Platform.isLinux) { | ||||
|       if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) { | ||||
|         FirebaseAnalytics.instance.setUserId(id: user.id); | ||||
|       } | ||||
|     } catch (error, stackTrace) { | ||||
| @@ -44,7 +44,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | ||||
|                       : 'failedToLoadUserInfoNetwork') | ||||
|                   .tr() | ||||
|                   .trim(), | ||||
|               '${error.response!.statusCode}\n${error.response?.headers}', | ||||
|               '${error.response?.statusCode ?? 'Network Error'}\n${error.response?.headers}', | ||||
|               jsonEncode(error.response?.data), | ||||
|             ].join('\n\n'), | ||||
|             iconStyle: IconStyle.error, | ||||
| @@ -87,7 +87,7 @@ class UserInfoNotifier extends StateNotifier<AsyncValue<SnAccount?>> { | ||||
|     final prefs = _ref.read(sharedPreferencesProvider); | ||||
|     await prefs.remove(kTokenPairStoreKey); | ||||
|     _ref.invalidate(tokenProvider); | ||||
|     if (kIsWeb || !Platform.isLinux) { | ||||
|     if (kIsWeb || !(Platform.isLinux || Platform.isWindows)) { | ||||
|       FirebaseAnalytics.instance.setUserId(id: null); | ||||
|     } | ||||
|   } | ||||
|   | ||||
| @@ -401,6 +401,14 @@ packages: | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     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: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -150,6 +150,7 @@ dependencies: | ||||
|   windows_notification: ^1.3.0 | ||||
|   win32: ^5.14.0 | ||||
|   ffi: ^2.1.4 | ||||
|   dart_ipc: ^1.0.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
| @@ -8,6 +8,7 @@ | ||||
|  | ||||
| #include <bitsdojo_window_windows/bitsdojo_window_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_selector_windows/file_selector_windows.h> | ||||
| #include <firebase_core/firebase_core_plugin_c_api.h> | ||||
| @@ -38,6 +39,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { | ||||
|       registry->GetRegistrarForPlugin("BitsdojoWindowPlugin")); | ||||
|   ConnectivityPlusWindowsPluginRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); | ||||
|   DartIpcPluginCApiRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("DartIpcPluginCApi")); | ||||
|   FileSaverPluginRegisterWithRegistrar( | ||||
|       registry->GetRegistrarForPlugin("FileSaverPlugin")); | ||||
|   FileSelectorWindowsRegisterWithRegistrar( | ||||
|   | ||||
| @@ -5,6 +5,7 @@ | ||||
| list(APPEND FLUTTER_PLUGIN_LIST | ||||
|   bitsdojo_window_windows | ||||
|   connectivity_plus | ||||
|   dart_ipc | ||||
|   file_saver | ||||
|   file_selector_windows | ||||
|   firebase_core | ||||
|   | ||||
		Reference in New Issue
	
	Block a user