UNPKG

insomnia-plugin-valorant

Version:
169 lines 9.78 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.XMPPManager = void 0; const ws_1 = require("ws"); const get_riot_client_config_1 = require("../util/api/get-riot-client-config"); const node_tls_1 = __importDefault(require("node:tls")); const async_socket_1 = require("../util/async-socket"); const fast_xml_parser_1 = require("fast-xml-parser"); const recognizedModes = ['raw', 'json', 'raw-buffered']; const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false }); const builder = new fast_xml_parser_1.XMLBuilder({ ignoreAttributes: false }); let configCache = undefined; // I don't expect the config data I'm using to change with different token/entitlement combos which is why I'm caching it across XMPP connections async function getOrLoadRiotConfig(token, entitlement) { if (configCache !== undefined) return configCache; configCache = await (0, get_riot_client_config_1.getRiotClientConfig)(token, entitlement); return configCache; } function wsLog(ws, message) { ws.send(`[insomnia-plugin-valorant] ${message}`); } class XMPPManager { _wss = undefined; _getWebsocketURL() { if (this._wss === undefined) throw new Error('Websocket server not initialized!'); const address = this._wss.address(); return `ws://${address.address}:${address.port}`; } async getWebsocketURL() { if (this._wss !== undefined) return this._getWebsocketURL(); return new Promise((resolve, reject) => { this._wss = new ws_1.WebSocketServer({ host: '127.0.0.1', port: 0 }); this._wss.once('listening', () => { resolve(this._getWebsocketURL()); }); this._wss.on('connection', async (ws, request) => { try { const authorization = request.headers['authorization']; const entitlement = request.headers['x-riot-entitlements-jwt']; const pasToken = request.headers['x-riot-pas-jwt']; if (authorization === undefined || Array.isArray(authorization) || !authorization.startsWith('Bearer ')) throw new Error('Invalid authorization header'); if (entitlement === undefined || Array.isArray(entitlement)) throw new Error('Invalid "x-riot-entitlements-jwt" header'); if (pasToken === undefined || Array.isArray(pasToken)) throw new Error('Invalid "x-riot-pas-jwt" header'); const token = authorization.slice('Bearer '.length); const wsUrlSuffix = request.url.split('/').pop(); const mode = recognizedModes.includes(wsUrlSuffix) ? wsUrlSuffix : 'raw'; // Get affinity from PAS token const pasParts = pasToken.split('.'); if (pasParts.length !== 3) throw new Error('Invalid PAS token'); const pasData = JSON.parse(Buffer.from(pasParts[1], 'base64').toString('utf-8')); const affinity = pasData['affinity']; if (affinity === undefined) throw new Error('Invalid PAS token, missing affinity'); wsLog(ws, `Setting up XMPP connection using mode "${mode}"...`); // Get affinity host and domain from riot config const riotConfig = await getOrLoadRiotConfig(token, entitlement); if (!riotConfig['chat.affinities'].hasOwnProperty(affinity)) throw new Error('PAS token affinity not found in riot config affinities'); if (!riotConfig['chat.affinity_domains'].hasOwnProperty(affinity)) throw new Error('PAS token affinity not found in riot config affinity_domains'); const affinityHost = riotConfig['chat.affinities'][affinity]; const affinityDomain = riotConfig['chat.affinity_domains'][affinity]; wsLog(ws, `Connecting to XMPP server "${affinityHost}:5223"...`); // Open the XMPP connection const socket = node_tls_1.default.connect({ host: affinityHost, port: 5223 }); socket.on('close', () => ws.close()); await (0, async_socket_1.waitForConnect)(socket); wsLog(ws, 'Connected to XMPP server, authenticating...'); // Stage 1 await (0, async_socket_1.asyncSocketWrite)(socket, `<?xml version="1.0"?><stream:stream to="${affinityDomain}.pvp.net" version="1.0" xmlns:stream="http://etherx.jabber.org/streams">`); let incomingData = ''; do { incomingData = (await (0, async_socket_1.asyncSocketRead)(socket)).toString(); } while (!incomingData.includes('X-Riot-RSO-PAS')); // Stage 2 await (0, async_socket_1.asyncSocketWrite)(socket, `<auth mechanism="X-Riot-RSO-PAS" xmlns="urn:ietf:params:xml:ns:xmpp-sasl"><rso_token>${token}</rso_token><pas_token>${pasToken}</pas_token></auth>`); await (0, async_socket_1.asyncSocketRead)(socket); // Stage 3 await (0, async_socket_1.asyncSocketWrite)(socket, `<?xml version="1.0"?><stream:stream to="${affinityDomain}.pvp.net" version="1.0" xmlns:stream="http://etherx.jabber.org/streams">`); do { incomingData = (await (0, async_socket_1.asyncSocketRead)(socket)).toString(); } while (!incomingData.includes('stream:features')); // Stage 4 await (0, async_socket_1.asyncSocketWrite)(socket, '<iq id="_xmpp_bind1" type="set"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"></bind></iq>'); await (0, async_socket_1.asyncSocketRead)(socket); // Stage 5 await (0, async_socket_1.asyncSocketWrite)(socket, '<iq id="_xmpp_session1" type="set"><session xmlns="urn:ietf:params:xml:ns:xmpp-session"/></iq>'); await (0, async_socket_1.asyncSocketRead)(socket); // Stage 6 await (0, async_socket_1.asyncSocketWrite)(socket, `<iq id="xmpp_entitlements_0" type="set"><entitlements xmlns="urn:riotgames:entitlements"><token xmlns="">${entitlement}</token></entitlements></iq>`); await (0, async_socket_1.asyncSocketRead)(socket); wsLog(ws, 'Connected and authenticated, now proxying data!'); // Send an empty gap to make the log look nicer ws.send(''); // Ping to keep the connection alive const pingInterval = setInterval(() => { socket.write(' '); }, 150_000); socket.on('close', () => clearInterval(pingInterval)); // Sending modes if (mode === 'raw' || mode === 'raw-buffered') { ws.on('message', data => { if (Array.isArray(data)) { data.forEach(b => socket.write(b)); } else if (data instanceof ArrayBuffer) { socket.write(Buffer.from(data)); } else { socket.write(data); } }); } else if (mode === 'json') { ws.on('message', data => { try { const parsed = JSON.parse(data.toString()); const xml = builder.build(parsed); socket.write(xml); } catch (e) { wsLog(ws, 'Invalid JSON: ' + e.toString()); } }); } // Receiving modes if (mode === 'raw') socket.on('data', data => ws.send(data.toString())); else if (mode === 'raw-buffered' || mode === 'json') { let buffer = ''; socket.on('data', (data) => { buffer += data.toString(); if (fast_xml_parser_1.XMLValidator.validate(`<a>${buffer}</a>`) === true) { if (mode === 'json') { const parsed = parser.parse(buffer); ws.send(JSON.stringify(parsed)); } else { ws.send(buffer); } buffer = ''; } }); } ws.on('close', () => socket.destroy()); } catch (e) { wsLog(ws, 'Error: ' + e.toString()); ws.close(4000); } }); }); } } exports.XMPPManager = XMPPManager; //# sourceMappingURL=XMPPManager.js.map