UNPKG

@fnlb-project/fnbr

Version:

A library to interact with Epic Games' Fortnite HTTP and XMPP services

322 lines 11.4 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getUUID = exports.signCrossPlatformMessage = exports.generateCrossPlatformKeyPair = exports.decodeSTOMPMessageBody = exports.chunk = exports.resolveAuthObject = exports.resolveAuthString = exports.parseStatKey = exports.createDefaultInputTypeStats = exports.parseM3U8File = exports.parseBlurlStream = exports.createPartyInvitation = exports.getRandomDefaultCharacter = exports.makeSnakeCase = exports.makeCamelCase = void 0; const tslib_1 = require("tslib"); /* eslint-disable no-restricted-syntax */ const zlib_1 = tslib_1.__importDefault(require("zlib")); const fs_1 = require("fs"); const crypto_1 = tslib_1.__importDefault(require("crypto")); const defaultCharacters = [ 'CID_A_272_Athena_Commando_F_Prime', 'CID_A_273_Athena_Commando_F_Prime_B', 'CID_A_274_Athena_Commando_F_Prime_C', 'CID_A_275_Athena_Commando_F_Prime_D', 'CID_A_276_Athena_Commando_F_Prime_E', 'CID_A_277_Athena_Commando_F_Prime_F', 'CID_A_278_Athena_Commando_F_Prime_G', 'CID_A_279_Athena_Commando_M_Prime', 'CID_A_280_Athena_Commando_M_Prime_B', 'CID_A_281_Athena_Commando_M_Prime_C', 'CID_A_282_Athena_Commando_M_Prime_D', 'CID_A_283_Athena_Commando_M_Prime_E', 'CID_A_284_Athena_Commando_M_Prime_F', 'CID_A_285_Athena_Commando_M_Prime_G', ]; const makeCamelCase = (obj) => { const returnObj = {}; Object.keys(obj).forEach((k) => { returnObj[k.split('_').map((s, i) => (i > 0 ? `${s.charAt(0).toUpperCase()}${s.slice(1)}` : s)).join('')] = obj[k]; }); return returnObj; }; exports.makeCamelCase = makeCamelCase; const makeSnakeCase = (obj) => { const returnObj = {}; Object.keys(obj).forEach((k) => { returnObj[k.replace(/[A-Z]/g, (l) => `_${l.toLowerCase()}`)] = obj[k]; }); return returnObj; }; exports.makeSnakeCase = makeSnakeCase; const getRandomDefaultCharacter = () => defaultCharacters[Math.floor(Math.random() * defaultCharacters.length)]; exports.getRandomDefaultCharacter = getRandomDefaultCharacter; const createPartyInvitation = (clientUserId, pingerId, data) => { const member = data.members.find((m) => m.account_id === pingerId); const partyMeta = data.meta; const memberMeta = member.meta; const meta = { 'urn:epic:conn:type_s': 'game', 'urn:epic:cfg:build-id_s': partyMeta['urn:epic:cfg:build-id_s'], 'urn:epic:invite:platformdata_s': '', }; if (memberMeta.Platform_j) { meta['Platform_j'] = JSON.parse(memberMeta.Platform_j).Platform.platformStr; } if (memberMeta['urn:epic:member:dn_s']) meta['urn:epic:member:dn_s'] = memberMeta['urn:epic:member:dn_s']; return { party_id: data.id, sent_by: pingerId, sent_to: clientUserId, sent_at: data.sent, updated_at: data.sent, expires_at: data.expies_at, status: 'SENT', meta, }; }; exports.createPartyInvitation = createPartyInvitation; const parseBlurlStream = (stream) => new Promise((res) => { zlib_1.default.inflate(stream.slice(8), (_err, buffer) => { const data = JSON.parse(buffer.toString()); res(data); }); }); exports.parseBlurlStream = parseBlurlStream; const parseM3U8FileLine = (line) => { const [key, value] = line.replace(/^#EXT-X-/, '').split(/:(.+)/); let output; if (value.includes(',')) { output = {}; let store = ''; let isString = false; for (const char of value.split('')) { if (char === '"') { isString = !isString; } else if (char === ',' && !isString) { const [vK, vV] = store.split(/=(.+)/); output[vK] = vV.replace(/(^"|"$)/g, ''); store = ''; } else { store += char; } } } else { output = value; } return [key, output]; }; const parseM3U8File = (data) => { const output = { streams: [], }; let streamInf; for (const line of data.split(/\n/).slice(1)) { if (line.startsWith('#EXT-X-STREAM-INF:')) { [, streamInf] = parseM3U8FileLine(line); } else if (line.startsWith('#EXT-X-')) { const [key, value] = parseM3U8FileLine(line); output[key] = value; } else if (!line.startsWith('#') && streamInf && line.length > 0) { output.streams.push({ data: streamInf, url: line, }); streamInf = undefined; } } return output; }; exports.parseM3U8File = parseM3U8File; const defaultStats = { score: 0, scorePerMin: 0, scorePerMatch: 0, wins: 0, top3: 0, top5: 0, top6: 0, top10: 0, top12: 0, top25: 0, kills: 0, killsPerMin: 0, killsPerMatch: 0, deaths: 0, kd: 0, matches: 0, winRate: 0, minutesPlayed: 0, playersOutlived: 0, lastModified: undefined, }; const createDefaultInputTypeStats = () => ({ overall: { ...defaultStats }, solo: { ...defaultStats }, duo: { ...defaultStats }, squad: { ...defaultStats }, ltm: { ...defaultStats }, }); exports.createDefaultInputTypeStats = createDefaultInputTypeStats; const parseStatKey = (key, value) => { switch (key) { case 'lastmodified': return ['lastModified', new Date(value * 1000)]; case 'placetop25': return ['top25', value]; case 'placetop12': return ['top12', value]; case 'placetop10': return ['top10', value]; case 'placetop6': return ['top6', value]; case 'placetop5': return ['top5', value]; case 'placetop3': return ['top3', value]; case 'placetop1': return ['wins', value]; case 'playersoutlived': return ['playersOutlived', value]; case 'minutesplayed': return ['minutesPlayed', value]; case 'matchesplayed': return ['matches', value]; default: return [key, value]; } }; exports.parseStatKey = parseStatKey; const resolveAuthString = async (str) => { switch (typeof str) { case 'function': return str(); case 'string': if (str.length === 32 || str.startsWith('eg1')) { return str; } return fs_1.promises.readFile(str, 'utf8'); default: throw new TypeError(`The type "${typeof str}" does not resolve to a valid auth string`); } }; exports.resolveAuthString = resolveAuthString; const resolveAuthObject = async (obj) => { switch (typeof obj) { case 'function': return obj(); case 'string': return JSON.parse(await fs_1.promises.readFile(obj, 'utf8')); case 'object': return obj; default: throw new TypeError(`The type "${typeof obj}" does not resolve to a valid auth object`); } }; exports.resolveAuthObject = resolveAuthObject; const chunk = (array, maxSize) => { const chunkedArray = []; for (let i = 0; i < array.length; i += maxSize) { chunkedArray.push(array.slice(i, i + maxSize)); } return chunkedArray; }; exports.chunk = chunk; const decodeSTOMPMessageBody = (body) => { var _a; if (!body) return ''; try { const decoded = Buffer.from(body, 'base64') .toString('utf-8') .replace(/\0+$/, ''); try { return (_a = JSON.parse(decoded).msg) !== null && _a !== void 0 ? _a : decoded; } catch { return decoded; } } catch { return ''; } }; exports.decodeSTOMPMessageBody = decodeSTOMPMessageBody; const getSubtleCrypto = () => { if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.subtle) { return globalThis.crypto.subtle; } return undefined; }; const arrayBufferToBase64 = (buffer) => { const bytes = new Uint8Array(buffer); if (typeof btoa === 'function') { let binary = ''; const len = bytes.byteLength; for (let i = 0; i < len; i++) { binary += String.fromCharCode(bytes[i]); } return btoa(binary); } if (typeof Buffer !== 'undefined') { return Buffer.from(bytes).toString('base64'); } throw new Error('Base64 encoding not supported in this environment'); }; const generateCrossPlatformKeyPair = async () => { if (crypto_1.default && typeof crypto_1.default.generateKeyPairSync === 'function') { const { privateKey, publicKey } = crypto_1.default.generateKeyPairSync('ed25519'); const spkiDer = publicKey.export({ type: 'spki', format: 'der' }); const publicKeyB64 = spkiDer.subarray(spkiDer.length - 32).toString('base64'); return { privateKey, publicKeyB64, }; } const subtle = getSubtleCrypto(); if (subtle) { const keyPair = (await subtle.generateKey({ name: 'Ed25519' }, true, ['sign', 'verify'])); const rawKeyBuffer = await subtle.exportKey('spki', keyPair.publicKey); const publicKeyB64 = arrayBufferToBase64(rawKeyBuffer); return { privateKey: keyPair.privateKey, publicKeyB64, }; } throw new Error('No cryptographic key generation API is available in this environment.'); }; exports.generateCrossPlatformKeyPair = generateCrossPlatformKeyPair; const signCrossPlatformMessage = async (privateKey, data) => { if (typeof CryptoKey !== 'undefined' && privateKey instanceof CryptoKey) { const subtle = getSubtleCrypto(); if (!subtle) { throw new Error('Web Crypto subtle API not found'); } const signBuffer = new Uint8Array(data); const signatureBuffer = await subtle.sign({ name: 'Ed25519' }, privateKey, signBuffer); return arrayBufferToBase64(signatureBuffer); } if (crypto_1.default && typeof crypto_1.default.sign === 'function') { const signature = crypto_1.default.sign(null, data, privateKey); return signature.toString('base64'); } if (privateKey && typeof privateKey === 'object' && privateKey.type === 'private' && privateKey.algorithm) { const subtle = getSubtleCrypto(); if (subtle) { const signatureBuffer = await subtle.sign({ name: 'Ed25519' }, privateKey, data); return arrayBufferToBase64(signatureBuffer); } } throw new Error('No cryptographic signing API is available or private key type is unsupported.'); }; exports.signCrossPlatformMessage = signCrossPlatformMessage; const getUUID = () => { if (typeof crypto_1.default !== 'undefined' && typeof crypto_1.default.randomUUID === 'function') { return crypto_1.default.randomUUID(); } if (typeof globalThis !== 'undefined' && globalThis.crypto && typeof globalThis.crypto.randomUUID === 'function') { return globalThis.crypto.randomUUID(); } return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; const v = c === 'x' ? r : (r & 0x3 | 0x8); return v.toString(16); }); }; exports.getUUID = getUUID; //# sourceMappingURL=Util.js.map