diff --git a/test-rpc-server/rpc-message.js b/test-rpc-server/rpc-message.js index 6b9b21e..051ef02 100644 --- a/test-rpc-server/rpc-message.js +++ b/test-rpc-server/rpc-message.js @@ -1,22 +1,61 @@ +const path = require('path'); -module.exports = class RpcMessage { - static serialize(obj) { +const VERSION = 1; + +const OPCODES = { + HANDSHAKE: 0, + FRAME: 1, + CLOSE: 2, +}; + +let PipePath; +if (process.platform == 'win32') { + PipePath = '\\\\?\\pipe\\discord-ipc'; +} +else { + const temp = process.env.XDG_RUNTIME_DIR || process.env.TMPDIR || process.env.TMP || process.env.TEMP || '/tmp'; + PipePath = path.join(temp, 'discord-ipc'); +} + +class RpcMessage { + + static serialize(opcode, obj) { const serializedJson = JSON.stringify(obj); - const msgLen = 4 + serializedJson.length; - let buff = Buffer.alloc(msgLen); - buff.writeInt32LE(msgLen, 0); - buff.write(serializedJson, 4, serializedJson.length, 'utf-8'); + const msgLen = serializedJson.length; + let buff = Buffer.alloc(8 + msgLen); + buff.writeInt32LE(opcode, 0); + buff.writeInt32LE(msgLen, 4); + buff.write(serializedJson, 8, serializedJson.length, 'utf-8'); return buff; } + static handshake(id) { + const opcode = OPCODES.HANDSHAKE; + return RpcMessage.serialize(opcode, { + client_id: id, + v: VERSION + }); + } + + static send(obj) { + const opcode = OPCODES.FRAME; + return RpcMessage.serialize(opcode, obj); + } + + static sendClose(code, message) { + const opcode = OPCODES.CLOSE; + return RpcMessage.serialize(opcode, {code, message}); + } + static deserialize(buff) { - const msgLen = buff.readInt32LE(0); - if (buff.length < msgLen) { + const opcode = buff.readInt32LE(0); + const msgLen = buff.readInt32LE(4); + if (buff.length < (msgLen + 8)) { return null; } - const msg = buff.toString('utf-8', 4, msgLen); + const msg = buff.toString('utf-8', 8, msgLen + 8); try { - return JSON.parse(msg); + return {opcode, data: JSON.parse(msg)}; } catch(e) { console.log(`failed to parse "${msg}"`); console.error(e); @@ -24,3 +63,5 @@ module.exports = class RpcMessage { } } }; + +module.exports = {OPCODES, PipePath, RpcMessage}; diff --git a/test-rpc-server/rpc-server.js b/test-rpc-server/rpc-server.js index 7f1e72e..c10ef43 100644 --- a/test-rpc-server/rpc-server.js +++ b/test-rpc-server/rpc-server.js @@ -1,48 +1,59 @@ const net = require('net'); -const RpcMessage = require('./rpc-message'); +const repl = require('repl'); +const {PipePath, RpcMessage} = require('./rpc-message'); -let PipePrefix; -let PipePostfix; -if (process.platform == 'win32') { - PipePrefix = '\\\\.\\pipe\\'; - PipePostfix = ''; -} -else { - PipePrefix = "/tmp"; - PipePostfix = '.pipe'; -} -const PipePath = PipePrefix + 'DiscordRpcServer' + PipePostfix; -let connections = 0; +let connectionNonce = 0; +global.connections = {}; const server = net.createServer(function(sock) { - connections += 1; - console.log('Server: on connection', connections); - let myConnection = connections; + connectionNonce += 1; + console.log('Server: on connection', connectionNonce); + let myConnection = connectionNonce; + let messages = 0; + + global.connections[myConnection] = sock; sock.on('data', function(data) { + messages++; const msgObj = RpcMessage.deserialize(data); if (msgObj != null) { - console.log('Server: on data:', myConnection, msgObj); + const {opcode, data} = msgObj; + console.log(`\nServer (${myConnection}): got opcode: ${opcode}, data: ${JSON.stringify(data)}`); } else { - console.log('Server: got some data', data.toString()); + console.log('\nServer: got some data', data.toString()); } }); sock.on('end', function() { - connections -= 1; - console.log('Server: on end', connections); + delete global.connections[myConnection]; + console.log('\nServer: on end', myConnection); }); }); server.on('close', function(){ - console.log('Server: on close'); -}) + console.log('\nServer: on close'); +}); try { server.listen(PipePath, function(){ - console.log('Server: on listening'); + console.log('\nServer: on listening'); }); } catch(e) { - console.error('could not start server:', e); + console.error('\nServer: could not start:', e); } + +const replServer = repl.start({prompt: '> ', useGlobal: true, breakEvalOnSigint: true}); +replServer.defineCommand('kill', { + help: 'Kill a client', + action(who) { + this.bufferedCommand = ''; + who = parseInt(who, 10); + const sock = global.connections[who]; + if (sock) { + sock.write(RpcMessage.sendClose(123, 'killed')); + sock.end(); + } + this.displayPrompt(); + } +}); diff --git a/test-rpc-server/test-client.js b/test-rpc-server/test-client.js index 7600404..162a914 100644 --- a/test-rpc-server/test-client.js +++ b/test-rpc-server/test-client.js @@ -1,32 +1,27 @@ const net = require('net'); -const RpcMessage = require('./rpc-message'); +const {OPCODES, PipePath, RpcMessage} = require('./rpc-message'); -let PipePrefix; -let PipePostfix; -if (process.platform == 'win32') { - PipePrefix = '\\\\.\\pipe\\'; - PipePostfix = ''; -} -else { - PipePrefix = "/tmp"; - PipePostfix = '.pipe'; -} - -const PipePath = PipePrefix + "DiscordRpcServer" + PipePostfix; +const APP_ID = '12345678910'; +global.isConnected = false; +global.timeoutId = null; function sendMesg(testUpdatesToSend, stream) { const msgObj = { - name: 'My Awesome Game', - state: (testUpdatesToSend % 2 == 0) ? 'In a match' : 'In Lobby' + state: (testUpdatesToSend % 2 == 0) ? 'In a match' : 'In Lobby', + details: 'Excited' }; console.log('Client: send update:', msgObj); - stream.write(RpcMessage.serialize(msgObj)); + stream.write(RpcMessage.send(msgObj)); } function sendMessageLoop(testUpdatesToSend, interval, stream) { + global.timeoutId = null; + if (!global.isConnected) { + return; + } sendMesg(testUpdatesToSend, stream); if (testUpdatesToSend > 1) { - setTimeout(() => {sendMessageLoop(testUpdatesToSend - 1, interval, stream)}, interval); + global.timeoutId = setTimeout(() => {sendMessageLoop(testUpdatesToSend - 1, interval, stream)}, interval); } else { shutdown(); } @@ -34,23 +29,35 @@ function sendMessageLoop(testUpdatesToSend, interval, stream) { const client = net.connect(PipePath, function(stream) { console.log('Client: on connection'); - - sendMessageLoop(5, 3000, client); + global.isConnected = true; + client.write(RpcMessage.handshake(APP_ID)); + sendMessageLoop(10, 3000, client); }); client.on('data', function(data) { const msgObj = RpcMessage.deserialize(data); if (msgObj != null) { - console.log('Client: got data:', msgObj); + const {opcode, data} = msgObj; + console.log(`Client: got opcode: ${opcode}, data: ${JSON.stringify(data)}`); + + if (opcode == OPCODES.CLOSE) { + shutdown(); + } + } else { - console.log('Client: got some data'); + console.log('Client: got some data', data); } }); client.on('end', function() { + global.isConnected = false; console.log('Client: on end'); }); function shutdown() { + if (global.timeoutId !== null) { + clearTimeout(global.timeoutId); + global.timeoutId = null; + } client.end(); } \ No newline at end of file