diff --git a/rage-rpc/README.md b/rage-rpc/README.md new file mode 100644 index 0000000..2af1ed4 --- /dev/null +++ b/rage-rpc/README.md @@ -0,0 +1 @@ +Currently not maintained. \ No newline at end of file diff --git a/rage-rpc/package.json b/rage-rpc/package.json new file mode 100644 index 0000000..a8074f6 --- /dev/null +++ b/rage-rpc/package.json @@ -0,0 +1,24 @@ +{ + "name": "rage-fw-rpc", + "version": "0.0.23-alpha.0", + "main": "dist/index.js", + "types": "dist/src/index.d.ts", + "files": [ + "dist/**/*" + ], + "scripts": { + "build": "tsup" + }, + "dependencies": { + "rage-rpc": "^0.4.0" + }, + "peerDependencies": { + "@ragempcommunity/types-client": "^2.1.8", + "rage-fw-shared-types": "workspace:^" + }, + "keywords": [], + "author": "SashaGoncharov19", + "license": "MIT", + "description": "Client side of rage-fw", + "gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0" +} diff --git a/rage-rpc/src/defs.d.ts b/rage-rpc/src/defs.d.ts new file mode 100644 index 0000000..00cf5bf --- /dev/null +++ b/rage-rpc/src/defs.d.ts @@ -0,0 +1,42 @@ +declare var mp: any; +declare var global: any; +declare var window: any; + +declare type ProcedureListener = (args: any, info: ProcedureListenerInfo) => any; + +declare interface Player { + call: (eventName: string, args?: any[]) => void; + [property: string]: any; +} + +declare interface Browser { + url: string; + execute: (code: string) => void; + [property: string]: any; +} + +declare interface ProcedureListenerInfo { + environment: string; + id?: string; + player?: Player; + browser?: Browser; +} + +declare interface CallOptions { + timeout?: number; + noRet?: boolean; +} + +declare interface Event { + req?: number; + ret?: number; + b?: string; + id: string; + name?: string; + args?: any; + env: string; + fenv?: string; + res?: any; + err?: any; + noRet?: number; +} \ No newline at end of file diff --git a/rage-rpc/src/index.ts b/rage-rpc/src/index.ts new file mode 100644 index 0000000..06b7cd2 --- /dev/null +++ b/rage-rpc/src/index.ts @@ -0,0 +1,568 @@ +import * as util from './util'; + +const environment = util.getEnvironment(); +if(!environment) throw 'Unknown RAGE environment'; + +const ERR_NOT_FOUND = 'PROCEDURE_NOT_FOUND'; + +const IDENTIFIER = '__rpc:id'; +const PROCESS_EVENT = '__rpc:process'; +const BROWSER_REGISTER = '__rpc:browserRegister'; +const BROWSER_UNREGISTER = '__rpc:browserUnregister'; +const TRIGGER_EVENT = '__rpc:triggerEvent'; +const TRIGGER_EVENT_BROWSERS = '__rpc:triggerEventBrowsers'; + +const glob = environment === 'cef' ? window : global; + +if(!glob[PROCESS_EVENT]){ + glob.__rpcListeners = {}; + glob.__rpcPending = {}; + glob.__rpcEvListeners = {}; + + glob[PROCESS_EVENT] = (player: Player | string, rawData?: string) => { + if(environment !== "server") rawData = player as string; + const data: Event = util.parseData(rawData); + + if(data.req){ // someone is trying to remotely call a procedure + const info: ProcedureListenerInfo = { + id: data.id, + environment: data.fenv || data.env + }; + if(environment === "server") info.player = player as Player; + const part = { + ret: 1, + id: data.id, + env: environment + }; + let ret: (ev: Event) => void; + switch(environment){ + case "server": + ret = ev => info.player.call(PROCESS_EVENT, [util.stringifyData(ev)]); + break; + case "client": { + if(data.env === "server"){ + ret = ev => mp.events.callRemote(PROCESS_EVENT, util.stringifyData(ev)); + }else if(data.env === "cef"){ + const browser = data.b && glob.__rpcBrowsers[data.b]; + info.browser = browser; + ret = ev => browser && util.isBrowserValid(browser) && passEventToBrowser(browser, ev, true); + } + break; + } + case "cef": { + ret = ev => mp.trigger(PROCESS_EVENT, util.stringifyData(ev)); + } + } + if(ret){ + const promise = callProcedure(data.name, data.args, info); + if(!data.noRet) promise.then(res => ret({ ...part, res })).catch(err => ret({ ...part, err: err ? err : null })); + } + }else if(data.ret){ // a previously called remote procedure has returned + const info = glob.__rpcPending[data.id]; + if(environment === "server" && info.player !== player) return; + if(info){ + info.resolve(data.hasOwnProperty('err') ? util.promiseReject(data.err) : util.promiseResolve(data.res)); + delete glob.__rpcPending[data.id]; + } + } + }; + + if(environment !== "cef"){ + mp.events.add(PROCESS_EVENT, glob[PROCESS_EVENT]); + + if(environment === "client"){ + // set up internal pass-through events + register('__rpc:callServer', ([name, args, noRet], info) => _callServer(name, args, { fenv: info.environment, noRet })); + register('__rpc:callBrowsers', ([name, args, noRet], info) => _callBrowsers(null, name, args, { fenv: info.environment, noRet })); + + // set up browser identifiers + glob.__rpcBrowsers = {}; + const initBrowser = (browser: Browser): void => { + const id = util.uid(); + Object.keys(glob.__rpcBrowsers).forEach(key => { + const b = glob.__rpcBrowsers[key]; + if(!b || !util.isBrowserValid(b) || b === browser) delete glob.__rpcBrowsers[key]; + }); + glob.__rpcBrowsers[id] = browser; + browser.execute(` + window.name = '${id}'; + if(typeof window['${IDENTIFIER}'] === 'undefined'){ + window['${IDENTIFIER}'] = Promise.resolve(window.name); + }else{ + window['${IDENTIFIER}:resolve'](window.name); + } + `); + }; + mp.browsers.forEach(initBrowser); + mp.events.add('browserCreated', initBrowser); + + // set up browser registration map + glob.__rpcBrowserProcedures = {}; + mp.events.add(BROWSER_REGISTER, (data: string) => { + const [browserId, name] = JSON.parse(data); + glob.__rpcBrowserProcedures[name] = browserId; + }); + mp.events.add(BROWSER_UNREGISTER, (data: string) => { + const [browserId, name] = JSON.parse(data); + if(glob.__rpcBrowserProcedures[name] === browserId) delete glob.__rpcBrowserProcedures[name]; + }); + + register(TRIGGER_EVENT_BROWSERS, ([name, args], info) => { + Object.values(glob.__rpcBrowsers).forEach(browser => { + _callBrowser(browser, TRIGGER_EVENT, [name, args], { fenv: info.environment, noRet: 1 }); + }); + }); + } + }else{ + if(typeof glob[IDENTIFIER] === 'undefined'){ + glob[IDENTIFIER] = new Promise(resolve => { + if (window.name) { + resolve(window.name); + }else{ + glob[IDENTIFIER+':resolve'] = resolve; + } + }); + } + } + + register(TRIGGER_EVENT, ([name, args], info) => callEvent(name, args, info)); +} + +function passEventToBrowser(browser: Browser, data: Event, ignoreNotFound: boolean): void { + const raw = util.stringifyData(data); + browser.execute(`var process = window["${PROCESS_EVENT}"]; if(process){ process(${JSON.stringify(raw)}); }else{ ${ignoreNotFound ? '' : `mp.trigger("${PROCESS_EVENT}", '{"ret":1,"id":"${data.id}","err":"${ERR_NOT_FOUND}","env":"cef"}');`} }`); +} + +function callProcedure(name: string, args: any, info: ProcedureListenerInfo): Promise { + const listener = glob.__rpcListeners[name]; + if(!listener) return util.promiseReject(ERR_NOT_FOUND); + return util.promiseResolve(listener(args, info)); +} + +/** + * Register a procedure. + * @param {string} name - The name of the procedure. + * @param {function} cb - The procedure's callback. The return value will be sent back to the caller. + * @returns {Function} The function, which unregister the event. + */ +export function register(name: string, cb: ProcedureListener): Function { + if(arguments.length !== 2) throw 'register expects 2 arguments: "name" and "cb"'; + if(environment === "cef") glob[IDENTIFIER].then((id: string) => mp.trigger(BROWSER_REGISTER, JSON.stringify([id, name]))); + glob.__rpcListeners[name] = cb; + + return () => unregister(name); +} + +/** + * Unregister a procedure. + * @param {string} name - The name of the procedure. + */ +export function unregister(name: string): void { + if(arguments.length !== 1) throw 'unregister expects 1 argument: "name"'; + if(environment === "cef") glob[IDENTIFIER].then((id: string) => mp.trigger(BROWSER_UNREGISTER, JSON.stringify([id, name]))); + glob.__rpcListeners[name] = undefined; +} + +/** + * Calls a local procedure. Only procedures registered in the same context will be resolved. + * + * Can be called from any environment. + * + * @param name - The name of the locally registered procedure. + * @param args - Any parameters for the procedure. + * @param options - Any options. + * @returns The result from the procedure. + */ +export function call(name: string, args?: any, options: CallOptions = {}): Promise { + if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('call expects 1 to 3 arguments: "name", optional "args", and optional "options"'); + return util.promiseTimeout(callProcedure(name, args, { environment }), options.timeout); +} + +function _callServer(name: string, args?: any, extraData: any = {}): Promise { + switch(environment){ + case "server": { + return call(name, args); + } + case "client": { + const id = util.uid(); + return new Promise(resolve => { + if(!extraData.noRet){ + glob.__rpcPending[id] = { + resolve + }; + } + const event: Event = { + req: 1, + id, + name, + env: environment, + args, + ...extraData + }; + mp.events.callRemote(PROCESS_EVENT, util.stringifyData(event)); + }); + } + case "cef": { + return callClient('__rpc:callServer', [name, args, +extraData.noRet]); + } + } +} + +/** + * Calls a remote procedure registered on the server. + * + * Can be called from any environment. + * + * @param name - The name of the registered procedure. + * @param args - Any parameters for the procedure. + * @param options - Any options. + * @returns The result from the procedure. + */ +export function callServer(name: string, args?: any, options: CallOptions = {}): Promise { + if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('callServer expects 1 to 3 arguments: "name", optional "args", and optional "options"'); + + let extraData: any = {}; + if(options.noRet) extraData.noRet = 1; + + return util.promiseTimeout(_callServer(name, args, extraData), options.timeout); +} + +function _callClient(player: Player, name: string, args?: any, extraData: any = {}): Promise { + switch(environment){ + case 'client': { + return call(name, args); + } + case 'server': { + const id = util.uid(); + return new Promise(resolve => { + if(!extraData.noRet){ + glob.__rpcPending[id] = { + resolve, + player + }; + } + const event: Event = { + req: 1, + id, + name, + env: environment, + args, + ...extraData + }; + player.call(PROCESS_EVENT, [util.stringifyData(event)]); + }); + } + case 'cef': { + const id = util.uid(); + return glob[IDENTIFIER].then((browserId: string) => { + return new Promise(resolve => { + if(!extraData.noRet){ + glob.__rpcPending[id] = { + resolve + }; + } + const event: Event = { + b: browserId, + req: 1, + id, + name, + env: environment, + args, + ...extraData + }; + mp.trigger(PROCESS_EVENT, util.stringifyData(event)); + }); + }); + } + } +} + +/** + * Calls a remote procedure registered on the client. + * + * Can be called from any environment. + * + * @param player - The player to call the procedure on. + * @param name - The name of the registered procedure. + * @param args - Any parameters for the procedure. + * @param options - Any options. + * @returns The result from the procedure. + */ +export function callClient(player: Player | string, name?: string | any, args?: any, options: CallOptions = {}): Promise { + switch(environment){ + case 'client': { + options = args || {}; + args = name; + name = player; + player = null; + if((arguments.length < 1 || arguments.length > 3) || typeof name !== 'string') return util.promiseReject('callClient from the client expects 1 to 3 arguments: "name", optional "args", and optional "options"'); + break; + } + case 'server': { + if((arguments.length < 2 || arguments.length > 4) || typeof player !== 'object') return util.promiseReject('callClient from the server expects 2 to 4 arguments: "player", "name", optional "args", and optional "options"'); + break; + } + case 'cef': { + options = args || {}; + args = name; + name = player; + player = null; + if((arguments.length < 1 || arguments.length > 3) || typeof name !== 'string') return util.promiseReject('callClient from the browser expects 1 to 3 arguments: "name", optional "args", and optional "options"'); + break; + } + } + + let extraData: any = {}; + if(options.noRet) extraData.noRet = 1; + + return util.promiseTimeout(_callClient(player as Player, name, args, extraData), options.timeout); +} + +function _callBrowser(browser: Browser, name: string, args?: any, extraData: any = {}): Promise { + return new Promise(resolve => { + const id = util.uid(); + if(!extraData.noRet){ + glob.__rpcPending[id] = { + resolve + }; + } + passEventToBrowser(browser, { + req: 1, + id, + name, + env: environment, + args, + ...extraData + }, false); + }); +} + +function _callBrowsers(player: Player, name: string, args?: any, extraData: any = {}): Promise { + switch(environment){ + case 'client': + const browserId = glob.__rpcBrowserProcedures[name]; + if(!browserId) return util.promiseReject(ERR_NOT_FOUND); + const browser = glob.__rpcBrowsers[browserId]; + if(!browser || !util.isBrowserValid(browser)) return util.promiseReject(ERR_NOT_FOUND); + return _callBrowser(browser, name, args, extraData); + case 'server': + return _callClient(player, '__rpc:callBrowsers', [name, args, +extraData.noRet], extraData); + case 'cef': + return _callClient(null, '__rpc:callBrowsers', [name, args, +extraData.noRet], extraData); + } +} + +/** + * Calls a remote procedure registered in any browser context. + * + * Can be called from any environment. + * + * @param player - The player to call the procedure on. + * @param name - The name of the registered procedure. + * @param args - Any parameters for the procedure. + * @param options - Any options. + * @returns The result from the procedure. + */ +export function callBrowsers(player: Player | string, name?: string | any, args?: any, options: CallOptions = {}): Promise { + let promise; + let extraData: any = {}; + + switch(environment){ + case 'client': + case 'cef': + options = args || {}; + args = name; + name = player; + if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('callBrowsers from the client or browser expects 1 to 3 arguments: "name", optional "args", and optional "options"'); + if(options.noRet) extraData.noRet = 1; + promise = _callBrowsers(null, name, args, extraData); + break; + case 'server': + if(arguments.length < 2 || arguments.length > 4) return util.promiseReject('callBrowsers from the server expects 2 to 4 arguments: "player", "name", optional "args", and optional "options"'); + if(options.noRet) extraData.noRet = 1; + promise = _callBrowsers(player as Player, name, args, extraData); + break; + } + + if(promise){ + return util.promiseTimeout(promise, options.timeout); + } +} + +/** + * Calls a remote procedure registered in a specific browser instance. + * + * Client-side environment only. + * + * @param browser - The browser instance. + * @param name - The name of the registered procedure. + * @param args - Any parameters for the procedure. + * @param options - Any options. + * @returns The result from the procedure. + */ +export function callBrowser(browser: Browser, name: string, args?: any, options: CallOptions = {}): Promise { + if(environment !== 'client') return util.promiseReject('callBrowser can only be used in the client environment'); + if(arguments.length < 2 || arguments.length > 4) return util.promiseReject('callBrowser expects 2 to 4 arguments: "browser", "name", optional "args", and optional "options"'); + + let extraData: any = {}; + if(options.noRet) extraData.noRet = 1; + + return util.promiseTimeout(_callBrowser(browser, name, args, extraData), options.timeout); +} + +function callEvent(name: string, args: any, info: ProcedureListenerInfo){ + const listeners = glob.__rpcEvListeners[name]; + if(listeners){ + listeners.forEach(listener => listener(args, info)); + } +} + +/** + * Register an event handler. + * @param {string} name - The name of the event. + * @param cb - The callback for the event. + * @returns {Function} The function, which off the event. + */ +export function on(name: string, cb: ProcedureListener): Function { + if(arguments.length !== 2) throw 'on expects 2 arguments: "name" and "cb"'; + + const listeners = glob.__rpcEvListeners[name] || new Set(); + listeners.add(cb); + glob.__rpcEvListeners[name] = listeners; + + return () => off(name, cb); +} + +/** + * Unregister an event handler. + * @param {string} name - The name of the event. + * @param cb - The callback for the event. + */ +export function off(name: string, cb: ProcedureListener){ + if(arguments.length !== 2) throw 'off expects 2 arguments: "name" and "cb"'; + + const listeners = glob.__rpcEvListeners[name]; + if(listeners){ + listeners.delete(cb); + } +} + +/** + * Triggers a local event. Only events registered in the same context will be triggered. + * + * Can be called from any environment. + * + * @param name - The name of the locally registered event. + * @param args - Any parameters for the event. + */ +export function trigger(name: string, args?: any){ + if(arguments.length < 1 || arguments.length > 2) throw 'trigger expects 1 or 2 arguments: "name", and optional "args"'; + callEvent(name, args, { environment }); +} + +/** + * Triggers an event registered on the client. + * + * Can be called from any environment. + * + * @param player - The player to call the procedure on. + * @param name - The name of the event. + * @param args - Any parameters for the event. + */ +export function triggerClient(player: Player | string, name?: string | any, args?: any){ + switch(environment){ + case 'client': { + args = name; + name = player; + player = null; + if((arguments.length < 1 || arguments.length > 2) || typeof name !== 'string') throw 'triggerClient from the client expects 1 or 2 arguments: "name", and optional "args"'; + break; + } + case 'server': { + if((arguments.length < 2 || arguments.length > 3) || typeof player !== 'object') throw 'triggerClient from the server expects 2 or 3 arguments: "player", "name", and optional "args"'; + break; + } + case 'cef': { + args = name; + name = player; + player = null; + if((arguments.length < 1 || arguments.length > 2) || typeof name !== 'string') throw 'triggerClient from the browser expects 1 or 2 arguments: "name", and optional "args"'; + break; + } + } + + _callClient(player as Player, TRIGGER_EVENT, [name, args], { noRet: 1 }); +} + +/** + * Triggers an event registered on the server. + * + * Can be called from any environment. + * + * @param name - The name of the event. + * @param args - Any parameters for the event. + */ +export function triggerServer(name: string, args?: any){ + if(arguments.length < 1 || arguments.length > 2) throw 'triggerServer expects 1 or 2 arguments: "name", and optional "args"'; + + _callServer(TRIGGER_EVENT, [name, args], { noRet: 1 }); +} + +/** + * Triggers an event registered in any browser context. + * + * Can be called from any environment. + * + * @param player - The player to call the procedure on. + * @param name - The name of the event. + * @param args - Any parameters for the event. + */ +export function triggerBrowsers(player: Player | string, name?: string | any, args?: any){ + switch(environment){ + case 'client': + case 'cef': + args = name; + name = player; + player = null; + if(arguments.length < 1 || arguments.length > 2) throw 'triggerBrowsers from the client or browser expects 1 or 2 arguments: "name", and optional "args"'; + break; + case 'server': + if(arguments.length < 2 || arguments.length > 3) throw 'triggerBrowsers from the server expects 2 or 3 arguments: "player", "name", and optional "args"'; + break; + } + + _callClient(player as Player, TRIGGER_EVENT_BROWSERS, [name, args], { noRet: 1 }); +} + +/** + * Triggers an event registered in a specific browser instance. + * + * Client-side environment only. + * + * @param browser - The browser instance. + * @param name - The name of the event. + * @param args - Any parameters for the event. + */ +export function triggerBrowser(browser: Browser, name: string, args?: any){ + if(environment !== 'client') throw 'callBrowser can only be used in the client environment'; + if(arguments.length < 2 || arguments.length > 4) throw 'callBrowser expects 2 or 3 arguments: "browser", "name", and optional "args"'; + + _callBrowser(browser, TRIGGER_EVENT, [name, args], { noRet: 1}); +} + +export default { + register, + unregister, + call, + callServer, + callClient, + callBrowsers, + callBrowser, + on, + off, + trigger, + triggerServer, + triggerClient, + triggerBrowsers, + triggerBrowser +}; \ No newline at end of file diff --git a/rage-rpc/src/util.ts b/rage-rpc/src/util.ts new file mode 100644 index 0000000..10a9c05 --- /dev/null +++ b/rage-rpc/src/util.ts @@ -0,0 +1,122 @@ +enum MpTypes { + Blip = 'b', + Checkpoint = 'cp', + Colshape = 'c', + Label = 'l', + Marker = 'm', + Object = 'o', + Pickup = 'p', + Player = 'pl', + Vehicle = 'v' +} + +function isObjectMpType(obj: any, type: MpTypes){ + const client = getEnvironment() === 'client'; + if(obj && typeof obj === 'object' && typeof obj.id !== 'undefined'){ + const test = (type, collection, mpType) => client ? obj.type === type && collection.at(obj.id) === obj : obj instanceof mpType; + switch(type){ + case MpTypes.Blip: return test('blip', mp.blips, mp.Blip); + case MpTypes.Checkpoint: return test('checkpoint', mp.checkpoints, mp.Checkpoint); + case MpTypes.Colshape: return test('colshape', mp.colshapes, mp.Colshape); + case MpTypes.Label: return test('textlabel', mp.labels, mp.TextLabel); + case MpTypes.Marker: return test('marker', mp.markers, mp.Marker); + case MpTypes.Object: return test('object', mp.objects, mp.Object); + case MpTypes.Pickup: return test('pickup', mp.pickups, mp.Pickup); + case MpTypes.Player: return test('player', mp.players, mp.Player); + case MpTypes.Vehicle: return test('vehicle', mp.vehicles, mp.Vehicle); + } + } + return false; +} + +export function uid(): string { + const first = (Math.random() * 46656) | 0; + const second = (Math.random() * 46656) | 0; + const firstPart = ('000' + first.toString(36)).slice(-3); + const secondPart = ('000' + second.toString(36)).slice(-3); + return firstPart + secondPart; +} + +export function getEnvironment(): string { + if ('mp' in window) return 'cef'; + if (mp.joaat) return 'server'; + else if (mp.game && mp.game.joaat) return 'client'; +} + +export function stringifyData(data: any): string { + const env = getEnvironment(); + return JSON.stringify(data, (_, value) => { + if(env === 'client' || env === 'server' && value && typeof value === 'object'){ + let type; + + if(isObjectMpType(value, MpTypes.Blip)) type = MpTypes.Blip; + else if(isObjectMpType(value, MpTypes.Checkpoint)) type = MpTypes.Checkpoint; + else if(isObjectMpType(value, MpTypes.Colshape)) type = MpTypes.Colshape; + else if(isObjectMpType(value, MpTypes.Marker)) type = MpTypes.Marker; + else if(isObjectMpType(value, MpTypes.Object)) type = MpTypes.Object; + else if(isObjectMpType(value, MpTypes.Pickup)) type = MpTypes.Pickup; + else if(isObjectMpType(value, MpTypes.Player)) type = MpTypes.Player; + else if(isObjectMpType(value, MpTypes.Vehicle)) type = MpTypes.Vehicle; + + if(type) return { + __t: type, + i: typeof value.remoteId === 'number' ? value.remoteId : value.id + }; + } + + return value; + }); +} + +export function parseData(data: string): any { + const env = getEnvironment(); + return JSON.parse(data, (_, value) => { + if((env === 'client' || env === 'server') && value && typeof value === 'object' && typeof value['__t'] === 'string' && typeof value.i === 'number' && Object.keys(value).length === 2){ + const id = value.i; + const type = value['__t']; + let collection; + + switch(type){ + case MpTypes.Blip: collection = mp.blips; break; + case MpTypes.Checkpoint: collection = mp.checkpoints; break; + case MpTypes.Colshape: collection = mp.colshapes; break; + case MpTypes.Label: collection = mp.labels; break; + case MpTypes.Marker: collection = mp.markers; break; + case MpTypes.Object: collection = mp.objects; break; + case MpTypes.Pickup: collection = mp.pickups; break; + case MpTypes.Player: collection = mp.players; break; + case MpTypes.Vehicle: collection = mp.vehicles; break; + } + + if(collection) return collection[env === 'client' ? 'atRemoteId' : 'at'](id); + } + + return value; + }); +} + +export function promiseResolve(result: any): Promise { + return new Promise(resolve => setTimeout(() => resolve(result), 0)); +} + +export function promiseReject(error: any): Promise { + return new Promise((_, reject) => setTimeout(() => reject(error), 0)); +} + +export function promiseTimeout(promise: Promise, timeout?: number){ + if(typeof timeout === 'number'){ + return Promise.race([ + new Promise((_, reject) => { + setTimeout(() => reject('TIMEOUT'), timeout); + }), + promise + ]); + }else return promise; +} + +export function isBrowserValid(browser: Browser): boolean { + try { + browser.url; + }catch(e){ return false; } + return true; +} \ No newline at end of file diff --git a/rage-rpc/tsconfig.json b/rage-rpc/tsconfig.json new file mode 100644 index 0000000..00ca838 --- /dev/null +++ b/rage-rpc/tsconfig.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/tsconfig", + "display": "Base", + "exclude": [ + "node_modules" + ], + "compilerOptions": { + "incremental": false, + "composite": false, + "target": "ES2022", + "experimentalDecorators": true, + "moduleDetection": "auto", + "module": "CommonJS", + "resolveJsonModule": true, + "declaration": false, + "declarationMap": false, + "sourceMap": false, + "downlevelIteration": false, + "inlineSourceMap": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +} diff --git a/rage-rpc/tsup.config.ts b/rage-rpc/tsup.config.ts new file mode 100644 index 0000000..af724fb --- /dev/null +++ b/rage-rpc/tsup.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'tsup' + +export default defineConfig({ + entry: ['src/index.ts'], + outDir: './dist', + format: ['cjs'], + noExternal: ['rage-rpc'], + experimentalDts: true, + splitting: false, + sourcemap: false, + clean: true, +})