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'; // IPC Packet Types class IpcTypes { static const int handshake = 0; static const int frame = 1; static const int close = 2; static const int ping = 3; static const int pong = 4; } // IPC Close Codes class IpcCloseCodes { static const int closeNormal = 1000; static const int closeUnsupported = 1003; static const int closeAbnormal = 1006; } // IPC Error Codes class IpcErrorCodes { static const int invalidClientId = 4000; static const int invalidOrigin = 4001; static const int rateLimited = 4002; static const int tokenRevoked = 4003; static const int invalidVersion = 4004; static const int invalidEncoding = 4005; } // IPC Packet structure class IpcPacket { final int type; final Map data; IpcPacket(this.type, this.data); } // Abstract base class for IPC server abstract class IpcServer { final List _sockets = []; // Encode IPC packet static Uint8List encodeIpcPacket(int type, Map data) { final jsonData = jsonEncode(data); final dataBytes = utf8.encode(jsonData); final dataSize = dataBytes.length; final buffer = ByteData(8 + dataSize); buffer.setInt32(0, type, Endian.little); buffer.setInt32(4, dataSize, Endian.little); buffer.buffer.asUint8List().setRange(8, 8 + dataSize, dataBytes); return buffer.buffer.asUint8List(); } Future start(); Future stop(); void addSocket(IpcSocketWrapper socket) { _sockets.add(socket); } void removeSocket(IpcSocketWrapper socket) { _sockets.remove(socket); } List get sockets => _sockets; void Function( IpcSocketWrapper socket, IpcPacket packet, Map handlers, )? handlePacket; } // Abstract base class for IPC socket wrapper abstract class IpcSocketWrapper { String clientId = ''; bool handshook = false; final List _buffer = []; void addData(List data) { _buffer.addAll(data); } void send(Map msg); void sendPong(dynamic data); void close(); void closeWithCode(int code, [String message = '']); List readPackets() { final packets = []; while (_buffer.length >= 8) { final buffer = Uint8List.fromList(_buffer); final byteData = ByteData.view(buffer.buffer); final type = byteData.getInt32(0, Endian.little); final dataSize = byteData.getInt32(4, Endian.little); if (_buffer.length < 8 + dataSize) break; final dataBytes = _buffer.sublist(8, 8 + dataSize); final jsonStr = utf8.decode(dataBytes); final jsonData = jsonDecode(jsonStr); packets.add(IpcPacket(type, jsonData)); _buffer.removeRange(0, 8 + dataSize); } return packets; } } // Multiplatform IPC Server implementation using dart_ipc class MultiPlatformIpcServer extends IpcServer { StreamSubscription? _serverSubscription; @override Future 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 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 _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 _findAvailableUnixIpcPath() async { // Build list of directories to try, with macOS-specific handling final baseDirs = []; 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(), ); 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 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(); } }