feat
- rpc
This commit is contained in:
		
							parent
							
								
									7369ff8868
								
							
						
					
					
						commit
						640adae8a5
					
				| @ -11,7 +11,7 @@ | ||||
|         "build": "tsup" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "rage-rpc": "^0.4.0" | ||||
|         "ragefw-rpc": "workspace:^" | ||||
|     }, | ||||
|     "peerDependencies": { | ||||
|         "@ragempcommunity/types-cef": "^2.1.8", | ||||
|  | ||||
| @ -11,7 +11,7 @@ | ||||
|         "build": "tsup" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "rage-rpc": "^0.4.0" | ||||
|         "ragefw-rpc": "workspace:^" | ||||
|     }, | ||||
|     "peerDependencies": { | ||||
|         "@ragempcommunity/types-client": "^2.1.8", | ||||
|  | ||||
| @ -3,7 +3,11 @@ | ||||
|   "scripts": { | ||||
|     "publish": "pnpm build && lerna publish --force-publish", | ||||
|     "build": "lerna run build", | ||||
|     "lint": "eslint --c .eslintrc.yaml --ext .ts client/ server/ shared-types/" | ||||
|     "lint": "eslint --c .eslintrc.yaml --ext .ts client/ server/ shared-types/", | ||||
|     "rebuild:cef": "cd cef && pnpm build", | ||||
|     "rebuild:client": "cd client && pnpm build", | ||||
|     "rebuild:server": "cd server && pnpm build", | ||||
|     "rebuild": "pnpm rebuild:cef && pnpm rebuild:client && pnpm rebuild:server" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@microsoft/api-extractor": "^7.47.0", | ||||
|  | ||||
							
								
								
									
										15735
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										15735
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -2,5 +2,6 @@ packages: | ||||
|   - "server" | ||||
|   - "client" | ||||
|   - "cef" | ||||
|   - "rpc" | ||||
|   - "cli" | ||||
|   - "shared-types" | ||||
| @ -1 +0,0 @@ | ||||
| Currently not maintained. | ||||
| @ -1,24 +0,0 @@ | ||||
| { | ||||
|     "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" | ||||
| } | ||||
							
								
								
									
										42
									
								
								rage-rpc/src/defs.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										42
									
								
								rage-rpc/src/defs.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,42 +0,0 @@ | ||||
| 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; | ||||
| } | ||||
| @ -1,568 +0,0 @@ | ||||
| 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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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<any> { | ||||
|     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 | ||||
| }; | ||||
| @ -1,122 +0,0 @@ | ||||
| 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<any> { | ||||
|     return new Promise(resolve => setTimeout(() => resolve(result), 0)); | ||||
| } | ||||
| 
 | ||||
| export function promiseReject(error: any): Promise<any> { | ||||
|     return new Promise((_, reject) => setTimeout(() => reject(error), 0)); | ||||
| } | ||||
| 
 | ||||
| export function promiseTimeout(promise: Promise<any>, 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; | ||||
| } | ||||
| @ -1,25 +0,0 @@ | ||||
| { | ||||
|   "$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 | ||||
|   } | ||||
| } | ||||
| @ -1,12 +0,0 @@ | ||||
| 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, | ||||
| }) | ||||
							
								
								
									
										33
									
								
								rpc/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										33
									
								
								rpc/index.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,13 +1,26 @@ | ||||
| declare const mp: any | ||||
| // declare const console: any
 | ||||
| export {} | ||||
| 
 | ||||
| // declare const setTimeout: (fn: Function, time: number) => number
 | ||||
| // declare const clearTimeout: (id: number) => void
 | ||||
| 
 | ||||
| declare const global: { | ||||
|     [p: string]: (...args: any[]) => unknown | ||||
| interface Mp { | ||||
|     trigger(event: string, data?: any): void | ||||
|     events: { | ||||
|         add(event: string, data: any): void | ||||
|         call(event: string, data: any): void | ||||
|         callRemote(event: string, data: any): void | ||||
|         remove(event: string): void | ||||
|     } | ||||
|     joaat?: unknown | ||||
|     game: { | ||||
|         joaat?: unknown | ||||
|     } | ||||
|     console: { | ||||
|         logInfo: (...args: unknown[]) => void | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // declare const window: {
 | ||||
| //     [p: string]: (...args: any[]) => unknown
 | ||||
| // }
 | ||||
| declare global { | ||||
|     const mp: Mp | ||||
|     const global: Record<string, (...args: any[]) => unknown> | ||||
|     interface Window { | ||||
|         [p: string]: any | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,8 +1,7 @@ | ||||
| { | ||||
|     "name": "rage-fw-rpc", | ||||
|     "version": "0.1.0", | ||||
|     "main": "dist/index.js", | ||||
|     "types": "dist/src/index.d.ts", | ||||
|     "name": "ragefw-rpc", | ||||
|     "description": "RageFW RPC", | ||||
|     "version": "0.2.0", | ||||
|     "scripts": { | ||||
|         "build": "tsup", | ||||
|         "start": "npx ./dist create" | ||||
| @ -11,18 +10,26 @@ | ||||
|         "dist/**/*" | ||||
|     ], | ||||
|     "devDependencies": { | ||||
|         "prettier": "^3.3.2" | ||||
|         "@microsoft/api-extractor": "^7.47.9", | ||||
|         "prettier": "^3.3.2", | ||||
|         "tsup": "^8.3.0" | ||||
|     }, | ||||
|     "peerDependencies": { | ||||
|         "typescript": "^5.0.0" | ||||
|         "typescript": "^5" | ||||
|     }, | ||||
|     "description": "RageFW RPC", | ||||
|     "keywords": [], | ||||
|     "author": "SashaGoncharov19", | ||||
|     "contributors": [{ | ||||
|     "keywords": ["ragemp", "rage", "rpc", "rage-rpc", "ragerpc"], | ||||
|     "main": "dist/index.js", | ||||
|     "types": "dist/src/index.d.ts", | ||||
|     "author": { | ||||
|         "name": "rilaxik", | ||||
|         "email": "dev.rilaxik@gmail.com", | ||||
|         "url": "https://github.com/rilaxik" | ||||
|     }], | ||||
|     }, | ||||
|     "contributors": [ | ||||
|         { | ||||
|             "name": "SashaGoncharov19", | ||||
|             "url": "https://github.com/SashaGoncharov19" | ||||
|         } | ||||
|     ], | ||||
|     "license": "MIT" | ||||
| } | ||||
|  | ||||
							
								
								
									
										70
									
								
								rpc/src/browser.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								rpc/src/browser.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,70 @@ | ||||
| import { Wrapper } from './wrapper' | ||||
| import { Environment, Events, RPCEventType, RPCState, Utils } from './utils' | ||||
| 
 | ||||
| class Browser extends Wrapper { | ||||
|     constructor() { | ||||
|         super() | ||||
|     } | ||||
| 
 | ||||
|     public _resolveEmitDestination(dataRaw: string) { | ||||
|         let state = Utils.prepareExecution(dataRaw) | ||||
| 
 | ||||
|         switch (state.calledTo) { | ||||
|             case Environment.BROWSER: | ||||
|                 this.emit(dataRaw) | ||||
|                 break | ||||
| 
 | ||||
|             default: | ||||
|                 this.emitClient(dataRaw) | ||||
|                 break | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private emitClient(dataRaw: string) { | ||||
|         mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw) | ||||
|     } | ||||
| 
 | ||||
|     private async emit(dataRaw: string) { | ||||
|         let state = Utils.prepareExecution(dataRaw) | ||||
|         const responseEventName = Utils.generateResponseEventName(state.uuid) | ||||
| 
 | ||||
|         state = this.verifyEvent_(state) | ||||
|         if (state.knownError) { | ||||
|             this.triggerError_(state, state.knownError) | ||||
|         } | ||||
| 
 | ||||
|         const response = await this.state_[state.eventName]( | ||||
|             ...(Array.isArray(state.data) ? state.data : []), | ||||
|         ) | ||||
|         const responseState: RPCState = { | ||||
|             uuid: Utils.generateUUID(), | ||||
|             eventName: state.eventName, | ||||
|             calledFrom: Environment.SERVER, | ||||
|             calledTo: state.calledFrom, | ||||
|             knownError: undefined, | ||||
|             data: response, | ||||
|             type: RPCEventType.RESPONSE, | ||||
|         } | ||||
|         const responseDataRaw = Utils.prepareTransfer(responseState) | ||||
| 
 | ||||
|         switch (state.calledFrom) { | ||||
|             case Environment.BROWSER: | ||||
|                 try { | ||||
|                     mp.events.call(responseEventName, responseDataRaw) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
|             default: | ||||
|                 try { | ||||
|                     mp.trigger(responseEventName, responseDataRaw) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const browser = new Browser() | ||||
| export { browser } | ||||
							
								
								
									
										159
									
								
								rpc/src/client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								rpc/src/client.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,159 @@ | ||||
| import { Wrapper } from './wrapper' | ||||
| import { | ||||
|     Environment, | ||||
|     Errors, | ||||
|     Events, | ||||
|     RPCEventType, | ||||
|     RPCState, | ||||
|     Utils, | ||||
| } from './utils' | ||||
| 
 | ||||
| class Client extends Wrapper { | ||||
|     private _browser: any = null | ||||
| 
 | ||||
|     constructor() { | ||||
|         super() | ||||
|     } | ||||
| 
 | ||||
|     set browser(browser: any) { | ||||
|         this._browser = browser | ||||
|     } | ||||
| 
 | ||||
|     public _resolveEmitDestination(dataRaw: string) { | ||||
|         const state = Utils.prepareExecution(dataRaw) | ||||
| 
 | ||||
|         switch (state.calledTo) { | ||||
|             case Environment.SERVER: | ||||
|                 this.emitServer(dataRaw) | ||||
|                 break | ||||
| 
 | ||||
|             case Environment.BROWSER: | ||||
|                 this.emitBrowser(dataRaw) | ||||
|                 break | ||||
| 
 | ||||
|             case Environment.CLIENT: | ||||
|                 this.emit(state) | ||||
|                 break | ||||
| 
 | ||||
|             default: | ||||
|                 this.triggerError_(state, Errors.UNKNOWN_ENVIRONMENT) | ||||
|                 break | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private async emit(state: RPCState) { | ||||
|         this.errorNoBrowser() | ||||
| 
 | ||||
|         state = this.verifyEvent_(state) | ||||
|         if (state.knownError) { | ||||
|             this.triggerError_(state, state.knownError) | ||||
|         } | ||||
| 
 | ||||
|         const responseEventName = Utils.generateResponseEventName(state.uuid) | ||||
|         const response = await this.state_[state.eventName]( | ||||
|             ...(Array.isArray(state.data) ? state.data : []), | ||||
|         ) | ||||
|         const responseState: RPCState = { | ||||
|             uuid: Utils.generateUUID(), | ||||
|             eventName: state.eventName, | ||||
|             calledFrom: state.calledTo, | ||||
|             calledTo: state.calledFrom, | ||||
|             knownError: undefined, | ||||
|             data: response, | ||||
|             type: RPCEventType.RESPONSE, | ||||
|         } | ||||
| 
 | ||||
|         switch (state.calledFrom) { | ||||
|             case Environment.CLIENT: | ||||
|                 try { | ||||
|                     mp.events.call( | ||||
|                         responseEventName, | ||||
|                         Utils.prepareTransfer(responseState), | ||||
|                     ) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
|             case Environment.SERVER: | ||||
|                 try { | ||||
|                     mp.events.callRemote( | ||||
|                         responseEventName, | ||||
|                         Utils.prepareTransfer(responseState), | ||||
|                     ) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
| 
 | ||||
|             case Environment.BROWSER: | ||||
|                 try { | ||||
|                     this._browser.call( | ||||
|                         responseEventName, | ||||
|                         Utils.prepareTransfer(responseState), | ||||
|                     ) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private emitServer(dataRaw: string) { | ||||
|         this.errorNoBrowser() | ||||
| 
 | ||||
|         const state = Utils.prepareExecution(dataRaw) | ||||
| 
 | ||||
|         if (state.calledFrom === Environment.BROWSER) { | ||||
|             const responseEventName = Utils.generateResponseEventName( | ||||
|                 state.uuid, | ||||
|             ) | ||||
| 
 | ||||
|             const timeout = setTimeout(() => { | ||||
|                 clearTimeout(timeout) | ||||
|                 mp.events.remove(responseEventName) | ||||
|             }, 10000) | ||||
| 
 | ||||
|             mp.events.add(responseEventName, (responseDataRaw: string) => { | ||||
|                 this._browser.call(responseEventName, responseDataRaw) | ||||
| 
 | ||||
|                 clearTimeout(timeout) | ||||
|                 mp.events.remove(responseEventName) | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         mp.events.callRemote(Events.SERVER_EVENT_LISTENER, dataRaw) | ||||
|     } | ||||
| 
 | ||||
|     private emitBrowser(dataRaw: string) { | ||||
|         this.errorNoBrowser() | ||||
| 
 | ||||
|         const state = Utils.prepareExecution(dataRaw) | ||||
| 
 | ||||
|         if (state.calledFrom === Environment.SERVER) { | ||||
|             const responseEventName = Utils.generateResponseEventName( | ||||
|                 state.uuid, | ||||
|             ) | ||||
| 
 | ||||
|             const timeout = setTimeout(() => { | ||||
|                 clearTimeout(timeout) | ||||
|                 mp.events.remove(responseEventName) | ||||
|             }, 10000) | ||||
| 
 | ||||
|             mp.events.add(responseEventName, (responseDataRaw: string) => { | ||||
|                 mp.events.callRemote(responseEventName, responseDataRaw) | ||||
| 
 | ||||
|                 clearTimeout(timeout) | ||||
|                 mp.events.remove(responseEventName) | ||||
|             }) | ||||
|         } | ||||
| 
 | ||||
|         this._browser.call(Events.LOCAL_EVENT_LISTENER, dataRaw) | ||||
|     } | ||||
| 
 | ||||
|     private errorNoBrowser() { | ||||
|         if (!this._browser) throw new Error(Errors.NO_BROWSER) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const client = new Client() | ||||
| export { client } | ||||
| @ -1,5 +0,0 @@ | ||||
| export const EVENT_LISTENER = '__rpc:listener' | ||||
| export const EVENT_RESPONSE = '__rpc:response' | ||||
| export const CEF_EVENT_LISTENER = '__rpc:cef_listener' | ||||
| 
 | ||||
| export const CLIENT_ROUTER_EVENT = '__rpc:clientRouter' | ||||
							
								
								
									
										373
									
								
								rpc/src/index.ts
									
									
									
									
									
								
							
							
						
						
									
										373
									
								
								rpc/src/index.ts
									
									
									
									
									
								
							| @ -1,78 +1,337 @@ | ||||
| import { Environment, utils } from './utils' | ||||
| import { EVENT_LISTENER } from './events' | ||||
| import { Wrapper } from './wrapper' | ||||
| import { | ||||
|     Environment, | ||||
|     Errors, | ||||
|     Events, | ||||
|     nativeClientEvents, | ||||
|     nativeServerEvents, | ||||
|     RPCEventType, | ||||
|     RPCState, | ||||
|     Utils, | ||||
|     type PlayerMp, | ||||
| } from './utils' | ||||
| 
 | ||||
| import { client } from './modules/client' | ||||
| import { server } from './modules/server' | ||||
| import { cef } from './modules/cef' | ||||
| 
 | ||||
| class Rpc { | ||||
|     private _environment = utils.getEnvironment() | ||||
|     private _state = | ||||
|         utils.getEnvironment() === Environment.CEF | ||||
|             ? window.rpcEvents | ||||
|             : global.rpcEvents | ||||
| import { server } from './server' | ||||
| import { client } from './client' | ||||
| import { browser } from './browser' | ||||
| 
 | ||||
| class Rpc extends Wrapper { | ||||
|     constructor() { | ||||
|         if (this._environment === Environment.UNKNOWN) return | ||||
|         super() | ||||
| 
 | ||||
|         mp.events.add(EVENT_LISTENER, async (player: any, request: string) => { | ||||
|             switch (this._environment) { | ||||
|                 case Environment.SERVER: | ||||
|                     await server.listenEvent(player, request) | ||||
|                     break | ||||
|         if (this.environment_ === Environment.UNKNOWN) | ||||
|             throw new Error(Errors.UNKNOWN_ENVIRONMENT) | ||||
| 
 | ||||
|                 case Environment.CLIENT: | ||||
|                     request = player | ||||
|         mp.events.add( | ||||
|             Events.LOCAL_EVENT_LISTENER, | ||||
|             async (player: PlayerMp | string, dataRaw: string) => { | ||||
|                 switch (this.environment_) { | ||||
|                     case Environment.SERVER: | ||||
|                         server._resolveEmitDestination( | ||||
|                             player as PlayerMp, | ||||
|                             dataRaw, | ||||
|                         ) | ||||
|                         break | ||||
| 
 | ||||
|                     // await client.listenEvent(request)
 | ||||
|                     break | ||||
|                     case Environment.CLIENT: | ||||
|                         dataRaw = player as string | ||||
|                         client._resolveEmitDestination(dataRaw) | ||||
|                         break | ||||
| 
 | ||||
|                 case Environment.CEF: | ||||
|                     request = player | ||||
|                 //
 | ||||
|                 // await cef
 | ||||
|             } | ||||
|         }) | ||||
|                     case Environment.BROWSER: | ||||
|                         dataRaw = player as string | ||||
|                         browser._resolveEmitDestination(dataRaw) | ||||
|                         break | ||||
| 
 | ||||
|                     default: | ||||
|                         void { player, dataRaw } | ||||
|                         break | ||||
|                 } | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     public register<Callback extends any[] = unknown[], Return = unknown>( | ||||
|         eventName: string, | ||||
|         cb: (...args: Callback) => Return, | ||||
|     ) { | ||||
|         if (this._environment === Environment.UNKNOWN) return | ||||
|         this._state[eventName] = cb | ||||
|     } | ||||
|     public register< | ||||
|         CallbackArguments extends unknown[] = unknown[], | ||||
|         CallbackReturn extends unknown = unknown, | ||||
|         EventName extends string = string, | ||||
|     >( | ||||
|         eventName: EventName, | ||||
|         cb: (...args: CallbackArguments) => CallbackReturn, | ||||
|     ): void { | ||||
|         Utils.errorUnknownEnvironment(this.environment_) | ||||
| 
 | ||||
|     public async callClient<Args extends any[] = unknown[], Return = unknown>( | ||||
|         player: any, | ||||
|         eventName: string, | ||||
|         ...args: Args | ||||
|     ): Promise<Return | unknown> { | ||||
|         if (this._environment === Environment.UNKNOWN) return | ||||
|         if (this._environment === Environment.SERVER) { | ||||
|             // return client.executeClient(player, eventName, args)
 | ||||
|         if ( | ||||
|             (this.environment_ === Environment.CLIENT && | ||||
|                 nativeClientEvents.has(eventName)) || | ||||
|             (this.environment_ === Environment.SERVER && | ||||
|                 nativeServerEvents.has(eventName)) | ||||
|         ) { | ||||
|             mp.events.add(eventName, cb) | ||||
|         } else { | ||||
|             this.state_[eventName] = cb | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async callServer<Args extends any[] = unknown[], Return = unknown>( | ||||
|         eventName: string, | ||||
|         ...args: Args | ||||
|     ): Promise<Return | unknown> { | ||||
|         switch (this._environment) { | ||||
|             case Environment.UNKNOWN: | ||||
|                 return | ||||
|     public unregister<EventName extends string = string>( | ||||
|         eventName: EventName, | ||||
|     ): void { | ||||
|         Utils.errorUnknownEnvironment(this.environment_) | ||||
| 
 | ||||
|         delete this.state_[eventName] | ||||
|     } | ||||
| 
 | ||||
|     public async callClient< | ||||
|         Arguments extends unknown[] = unknown[], | ||||
|         EventName extends string = string, | ||||
|         Return extends unknown = unknown, | ||||
|     >(eventName: EventName, args?: Arguments): Promise<Return> | ||||
|     public async callClient< | ||||
|         Arguments extends unknown[] = unknown[], | ||||
|         EventName extends string = string, | ||||
|         Return extends unknown = unknown, | ||||
|     >(player: PlayerMp, eventName: EventName, args?: Arguments): Promise<Return> | ||||
|     public async callClient( | ||||
|         playerOrEventName: PlayerMp | string, | ||||
|         eventNameOrArgs?: string | unknown[], | ||||
|         args?: unknown[], | ||||
|     ) { | ||||
|         Utils.errorUnknownEnvironment(this.environment_) | ||||
| 
 | ||||
|         function _is1StParamPlayer(x: unknown): x is PlayerMp { | ||||
|             return typeof x === 'object' | ||||
|         } | ||||
|         function _is2NdParamEventName(x: unknown): x is string { | ||||
|             return typeof x === 'string' | ||||
|         } | ||||
| 
 | ||||
|         // client
 | ||||
|         if (this.environment_ === Environment.CLIENT) { | ||||
|             return await this.call( | ||||
|                 playerOrEventName as string, | ||||
|                 args as unknown[], | ||||
|             ) | ||||
|         } | ||||
| 
 | ||||
|         // server
 | ||||
|         if ( | ||||
|             this.environment_ === Environment.SERVER && | ||||
|             _is1StParamPlayer(playerOrEventName) && | ||||
|             _is2NdParamEventName(eventNameOrArgs) | ||||
|         ) { | ||||
|             const state: RPCState = { | ||||
|                 uuid: Utils.generateUUID(), | ||||
|                 eventName: eventNameOrArgs, | ||||
|                 calledTo: Environment.CLIENT, | ||||
|                 calledFrom: this.environment_, | ||||
|                 knownError: undefined, | ||||
|                 data: args as unknown[], | ||||
|                 type: RPCEventType.EVENT, | ||||
|             } | ||||
| 
 | ||||
|             const dataRaw = Utils.prepareTransfer(state) | ||||
| 
 | ||||
|             playerOrEventName.call(Events.LOCAL_EVENT_LISTENER, [dataRaw]) | ||||
|             return (await this.responseHandler(state.uuid)).data | ||||
|         } | ||||
| 
 | ||||
|         // browser
 | ||||
|         if ( | ||||
|             this.environment_ === Environment.BROWSER && | ||||
|             !_is1StParamPlayer(playerOrEventName) && | ||||
|             !_is2NdParamEventName(eventNameOrArgs) | ||||
|         ) { | ||||
|             const state: RPCState = { | ||||
|                 uuid: Utils.generateUUID(), | ||||
|                 eventName: playerOrEventName, | ||||
|                 calledTo: Environment.CLIENT, | ||||
|                 calledFrom: this.environment_, | ||||
|                 knownError: undefined, | ||||
|                 data: eventNameOrArgs, | ||||
|                 type: RPCEventType.EVENT, | ||||
|             } | ||||
| 
 | ||||
|             const dataRaw = Utils.prepareTransfer(state) | ||||
| 
 | ||||
|             mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw) | ||||
|             return (await this.responseHandler(state.uuid)).data | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public async callServer< | ||||
|         Arguments extends unknown[] = unknown[], | ||||
|         EventName extends string = string, | ||||
|         Return extends unknown = unknown, | ||||
|     >(eventName: EventName, args?: Arguments): Promise<Return> { | ||||
|         Utils.errorUnknownEnvironment(this.environment_) | ||||
| 
 | ||||
|         const state: RPCState = { | ||||
|             uuid: Utils.generateUUID(), | ||||
|             eventName, | ||||
|             calledTo: Environment.SERVER, | ||||
|             calledFrom: this.environment_, | ||||
|             knownError: undefined, | ||||
|             data: args, | ||||
|             type: RPCEventType.EVENT, | ||||
|         } | ||||
| 
 | ||||
|         const dataRaw = Utils.prepareTransfer(state) | ||||
| 
 | ||||
|         switch (this.environment_) { | ||||
|             case Environment.SERVER: | ||||
|                 return | ||||
| 
 | ||||
|             case Environment.CEF: | ||||
|                 return client | ||||
|                 return this.callSelf(state) | ||||
| 
 | ||||
|             case Environment.CLIENT: | ||||
|                 return server.executeServer(eventName, args) | ||||
|                 mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw) | ||||
|                 break | ||||
| 
 | ||||
|             case Environment.BROWSER: | ||||
|                 mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw) | ||||
|                 break | ||||
|         } | ||||
| 
 | ||||
|         return (await this.responseHandler(state.uuid)).data | ||||
|     } | ||||
| 
 | ||||
|     public async callBrowser< | ||||
|         Arguments extends unknown[] = unknown[], | ||||
|         EventName extends string = string, | ||||
|         Return extends unknown = unknown, | ||||
|     >(eventName: EventName, args?: Arguments): Promise<Return> | ||||
|     public async callBrowser< | ||||
|         Arguments extends unknown[] = unknown[], | ||||
|         EventName extends string = string, | ||||
|         Return extends unknown = unknown, | ||||
|     >(player: PlayerMp, eventName: EventName, args?: Arguments): Promise<Return> | ||||
|     public async callBrowser( | ||||
|         playerOrEventName: PlayerMp | string, | ||||
|         eventNameOrArgs?: string | unknown[], | ||||
|         args?: unknown[], | ||||
|     ) { | ||||
|         Utils.errorUnknownEnvironment(this.environment_) | ||||
| 
 | ||||
|         function _is1StParamPlayer(x: unknown): x is PlayerMp { | ||||
|             return typeof x === 'object' | ||||
|         } | ||||
|         function _is2NdParamEventName(x: unknown): x is string { | ||||
|             return typeof x === 'string' | ||||
|         } | ||||
| 
 | ||||
|         const state: RPCState = { | ||||
|             uuid: Utils.generateUUID(), | ||||
|             eventName: !_is1StParamPlayer(playerOrEventName) | ||||
|                 ? playerOrEventName | ||||
|                 : _is2NdParamEventName(eventNameOrArgs) | ||||
|                   ? eventNameOrArgs | ||||
|                   : '', | ||||
|             calledTo: Environment.BROWSER, | ||||
|             calledFrom: this.environment_, | ||||
|             knownError: undefined, | ||||
|             data: _is1StParamPlayer(playerOrEventName) ? args : eventNameOrArgs, | ||||
|             type: RPCEventType.EVENT, | ||||
|         } | ||||
| 
 | ||||
|         const dataRaw = Utils.prepareTransfer(state) | ||||
| 
 | ||||
|         switch (this.environment_) { | ||||
|             case Environment.BROWSER: | ||||
|                 return this.callSelf(state) | ||||
| 
 | ||||
|             case Environment.CLIENT: | ||||
|                 mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw) | ||||
|                 break | ||||
| 
 | ||||
|             case Environment.SERVER: | ||||
|                 ;(playerOrEventName as PlayerMp).call( | ||||
|                     Events.LOCAL_EVENT_LISTENER, | ||||
|                     [dataRaw], | ||||
|                 ) | ||||
|                 break | ||||
|         } | ||||
| 
 | ||||
|         return (await this.responseHandler(state.uuid)).data | ||||
|     } | ||||
| 
 | ||||
|     public async call< | ||||
|         Arguments extends unknown[] = unknown[], | ||||
|         EventName extends string = string, | ||||
|         Return extends unknown = unknown, | ||||
|     >(eventName: EventName, args?: Arguments): Promise<Return> { | ||||
|         Utils.errorUnknownEnvironment(this.environment_) | ||||
| 
 | ||||
|         let state: RPCState = { | ||||
|             uuid: Utils.generateUUID(), | ||||
|             eventName, | ||||
|             calledTo: this.environment_, | ||||
|             calledFrom: this.environment_, | ||||
|             knownError: undefined, | ||||
|             data: args, | ||||
|             type: RPCEventType.EVENT, | ||||
|         } | ||||
| 
 | ||||
|         return this.callSelf<Return>(state) | ||||
|     } | ||||
| 
 | ||||
|     private callSelf<Return extends unknown = unknown>( | ||||
|         state: RPCState, | ||||
|     ): Return { | ||||
|         state = this.verifyEvent_(state) | ||||
|         if (state.knownError) { | ||||
|             this.triggerError_(state, state.knownError) | ||||
|         } | ||||
| 
 | ||||
|         return this.state_[state.eventName](...state.data) | ||||
|     } | ||||
| 
 | ||||
|     private async responseHandler(uuid: string): Promise<RPCState> { | ||||
|         const responseEventName = Utils.generateResponseEventName(uuid) | ||||
| 
 | ||||
|         return new Promise((resolve, reject) => { | ||||
|             const timeout = setTimeout(() => { | ||||
|                 clearTimeout(timeout) | ||||
|                 mp.events.remove(responseEventName) | ||||
|                 reject(Errors.EVENT_RESPONSE_TIMEOUT) | ||||
|             }, 10000) | ||||
| 
 | ||||
|             mp.events.add( | ||||
|                 responseEventName, | ||||
|                 (player: PlayerMp | string, dataRaw: string) => { | ||||
|                     switch (this.environment_) { | ||||
|                         case Environment.SERVER: | ||||
|                             resolve(Utils.prepareExecution(dataRaw)) | ||||
| 
 | ||||
|                             clearTimeout(timeout) | ||||
|                             mp.events.remove(responseEventName) | ||||
| 
 | ||||
|                             break | ||||
| 
 | ||||
|                         case Environment.CLIENT: | ||||
|                             dataRaw = player as string | ||||
|                             resolve(Utils.prepareExecution(dataRaw)) | ||||
| 
 | ||||
|                             clearTimeout(timeout) | ||||
|                             mp.events.remove(responseEventName) | ||||
| 
 | ||||
|                             break | ||||
| 
 | ||||
|                         case Environment.BROWSER: | ||||
|                             dataRaw = player as string | ||||
|                             resolve(Utils.prepareExecution(dataRaw)) | ||||
| 
 | ||||
|                             clearTimeout(timeout) | ||||
|                             mp.events.remove(responseEventName) | ||||
| 
 | ||||
|                             break | ||||
| 
 | ||||
|                         default: | ||||
|                             void { player, dataRaw } | ||||
|                             break | ||||
|                     } | ||||
|                 }, | ||||
|             ) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const testRpc = new Rpc() | ||||
| export { testRpc } | ||||
| const rpc = new Rpc() | ||||
| export { rpc, client } | ||||
|  | ||||
| @ -1,68 +0,0 @@ | ||||
| import { Wrapper } from './wrapper' | ||||
| import { Environment, RPCState } from '../utils' | ||||
| import { CEF_EVENT_LISTENER } from '../events' | ||||
| 
 | ||||
| class Cef extends Wrapper { | ||||
|     public async callClient<Args extends any[] = unknown[], Return = unknown>( | ||||
|         eventName: string, | ||||
|         ...args: Args | ||||
|     ): Promise<Return | unknown> { | ||||
|         return new Promise((resolve, reject) => { | ||||
|             const uuid = this._utils.generateUUID() | ||||
| 
 | ||||
|             const data: RPCState = { | ||||
|                 uuid, | ||||
|                 eventName, | ||||
|                 calledFrom: Environment.CEF, | ||||
|                 calledTo: Environment.CLIENT, | ||||
|                 data: args, | ||||
|             } | ||||
| 
 | ||||
|             mp.trigger(CEF_EVENT_LISTENER, this._utils.prepareForTransfer(data)) | ||||
| 
 | ||||
|             this.handleReturn(uuid, resolve, reject) | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public async callServer<Args extends any[] = unknown[], Return = unknown>( | ||||
|         eventName: string, | ||||
|         ...args: Args | ||||
|     ): Promise<Return | void> {} | ||||
| 
 | ||||
|     private async handleReturn( | ||||
|         uuid: string, | ||||
|         resolve: (value: unknown) => void, | ||||
|         reject: (reason?: unknown) => void, | ||||
|     ) { | ||||
|         const responseEvent = this._utils.generateResponseEventName(uuid) | ||||
|         const timeoutDuration = 1000 * 10 | ||||
| 
 | ||||
|         const timeoutID = setTimeout(() => { | ||||
|             reject(new Error('Timeout ended')) | ||||
|             mp.events.remove(responseEvent) | ||||
|         }, timeoutDuration) | ||||
| 
 | ||||
|         const handler = (response: string) => { | ||||
|             const { knownError, data } = this._utils.prepareForExecute(response) | ||||
| 
 | ||||
|             if (knownError) { | ||||
|                 try { | ||||
|                     clearTimeout(timeoutID) | ||||
|                     reject(knownError) | ||||
|                     return | ||||
|                 } catch (e) {} | ||||
|             } | ||||
| 
 | ||||
|             resolve(data) | ||||
|             mp.events.remove(responseEvent) | ||||
| 
 | ||||
|             try { | ||||
|                 clearTimeout(timeoutID) | ||||
|             } catch (e) {} | ||||
|         } | ||||
| 
 | ||||
|         mp.events.add(responseEvent, handler) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export const cef = new Cef() | ||||
| @ -1,167 +0,0 @@ | ||||
| import { CLIENT_ROUTER_EVENT, EVENT_LISTENER } from '../events' | ||||
| import { Wrapper } from './wrapper' | ||||
| import { Environment } from '../utils' | ||||
| 
 | ||||
| class Client extends Wrapper { | ||||
|     private browser: any | ||||
| 
 | ||||
|     constructor() { | ||||
|         super() | ||||
| 
 | ||||
|         mp.events.add(CLIENT_ROUTER_EVENT, (data: string) => { | ||||
|             const parsedData = this._utils.prepareForExecute(data) | ||||
|             const environment = this._environment | ||||
| 
 | ||||
|             if (environment === Environment.CLIENT) { | ||||
|                 switch (parsedData.calledTo) { | ||||
|                     case Environment.SERVER: | ||||
|                         // route to server listener
 | ||||
|                         this.requestToServer(data) | ||||
|                         break | ||||
| 
 | ||||
|                     case Environment.CEF: | ||||
|                         this.requestToBrowser(data) | ||||
|                         break | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| 
 | ||||
|     public useBrowser(browser: any) { | ||||
|         this.browser = browser | ||||
|     } | ||||
| 
 | ||||
|     private async createCallbackListener(uuid: string) { | ||||
|         const eventName = this._utils.generateResponseEventName(uuid) | ||||
| 
 | ||||
|         const handler = async (data: string) => { | ||||
|             mp.events.remove(eventName) | ||||
| 
 | ||||
|             //TODO: transfer to CEF/Server
 | ||||
|         } | ||||
| 
 | ||||
|         mp.events.add(eventName, handler) | ||||
| 
 | ||||
|         return eventName | ||||
|     } | ||||
| 
 | ||||
|     private async requestToServer(data: string) { | ||||
|         const { uuid } = this._utils.prepareForExecute(data) | ||||
|         await this.createCallbackListener(uuid) | ||||
| 
 | ||||
|         mp.events.callRemote(EVENT_LISTENER, data) | ||||
|     } | ||||
| 
 | ||||
|     private async requestToBrowser(data: string) { | ||||
|         const { uuid } = this._utils.prepareForExecute(data) | ||||
|         await this.createCallbackListener(uuid) | ||||
| 
 | ||||
|         if (!this.browser) return //TODO: Error browser not initialized
 | ||||
| 
 | ||||
|         this.browser.call(EVENT_LISTENER, data) | ||||
|     } | ||||
| 
 | ||||
|     // private sendResponseToServer(data: RPCState) {
 | ||||
|     //     const eventName = this._utils.generateResponseEventName(data.uuid)
 | ||||
|     //     const preparedData = this._utils.prepareForTransfer(data)
 | ||||
|     //     mp.events.callRemote(eventName, preparedData)
 | ||||
|     // }
 | ||||
|     //
 | ||||
|     // private sendEventToServer(data: RPCState) {
 | ||||
|     //     const eventName = this._utils.generateResponseEventName(data.uuid)
 | ||||
|     //     const preparedData = this._utils.prepareForTransfer(data)
 | ||||
|     // }
 | ||||
|     //
 | ||||
|     // public async listenEvent(data: string) {
 | ||||
|     //     const rpcData = this._verifyEvent(data)
 | ||||
|     //
 | ||||
|     //     if (rpcData.knownError) {
 | ||||
|     //         this._triggerError(rpcData)
 | ||||
|     //         return
 | ||||
|     //     }
 | ||||
|     //
 | ||||
|     //     await this.navigateTo(rpcData.calledTo, rpcData)
 | ||||
|     //
 | ||||
|     //     // try {
 | ||||
|     //     //     const fnResponse = await this._state[rpcData.eventName](
 | ||||
|     //     //         ...rpcData.data,
 | ||||
|     //     //     )
 | ||||
|     //     //     const response = {
 | ||||
|     //     //         ...rpcData,
 | ||||
|     //     //         data: fnResponse,
 | ||||
|     //     //     }
 | ||||
|     //     //
 | ||||
|     //     //     this.sendResponseToServer(response)
 | ||||
|     //     // } catch (e) {
 | ||||
|     //     //     this._triggerError(rpcData, e)
 | ||||
|     //     // }
 | ||||
|     // }
 | ||||
|     //
 | ||||
|     // private async navigateTo(toEnvironment: Environment, data: RPCState) {
 | ||||
|     //     switch (toEnvironment) {
 | ||||
|     //         case Environment.SERVER:
 | ||||
|     //             this.sendEventToServer()
 | ||||
|     //     }
 | ||||
|     // }
 | ||||
|     //
 | ||||
|     // private handleClientServerReturn(
 | ||||
|     //     uuid: string,
 | ||||
|     //     resolve: (value: unknown) => void,
 | ||||
|     //     reject: (reason?: any) => void,
 | ||||
|     // ) {
 | ||||
|     //     const responseEvent = this._utils.generateResponseEventName(uuid)
 | ||||
|     //     const timeoutDuration = 1000 * 10
 | ||||
|     //
 | ||||
|     //     const timeoutID = setTimeout(() => {
 | ||||
|     //         reject(new Error('Timeout ended'))
 | ||||
|     //         mp.events.remove(responseEvent)
 | ||||
|     //     }, timeoutDuration)
 | ||||
|     //
 | ||||
|     //     const handler = (_: any, response: string) => {
 | ||||
|     //         const { knownError, data } = this._utils.prepareForExecute(response)
 | ||||
|     //
 | ||||
|     //         if (knownError)
 | ||||
|     //             try {
 | ||||
|     //                 clearTimeout(timeoutID)
 | ||||
|     //                 reject(knownError)
 | ||||
|     //                 return
 | ||||
|     //             } catch (e) {}
 | ||||
|     //
 | ||||
|     //         resolve(data)
 | ||||
|     //         mp.events.remove(responseEvent)
 | ||||
|     //
 | ||||
|     //         try {
 | ||||
|     //             clearTimeout(timeoutID)
 | ||||
|     //         } catch (e) {}
 | ||||
|     //     }
 | ||||
|     //
 | ||||
|     //     mp.events.add(responseEvent, handler)
 | ||||
|     // }
 | ||||
|     //
 | ||||
|     // public async executeClient<
 | ||||
|     //     Args extends any[] = unknown[],
 | ||||
|     //     Return = unknown,
 | ||||
|     // >(
 | ||||
|     //     player: any,
 | ||||
|     //     eventName: string,
 | ||||
|     //     ...args: Args
 | ||||
|     // ): Promise<Return | unknown> {
 | ||||
|     //     return new Promise((resolve, reject) => {
 | ||||
|     //         const uuid = this._utils.generateUUID()
 | ||||
|     //
 | ||||
|     //         const data: RPCState = {
 | ||||
|     //             uuid,
 | ||||
|     //             eventName,
 | ||||
|     //             calledFrom: this._environment,
 | ||||
|     //             calledTo: Environment.CLIENT,
 | ||||
|     //             data: args,
 | ||||
|     //         }
 | ||||
|     //
 | ||||
|     //         player.call(EVENT_LISTENER, [this._utils.prepareForTransfer(data)])
 | ||||
|     //
 | ||||
|     //         this.handleClientServerReturn(uuid, resolve, reject)
 | ||||
|     //     })
 | ||||
|     // }
 | ||||
| } | ||||
| 
 | ||||
| export const client = new Client() | ||||
| @ -1,96 +0,0 @@ | ||||
| import { Wrapper } from './wrapper' | ||||
| import { Environment, RPCState } from '../utils' | ||||
| import { EVENT_LISTENER } from '../events' | ||||
| 
 | ||||
| class Server extends Wrapper { | ||||
|     private sendResponseToClient(player: any, data: RPCState) { | ||||
|         const eventName = this._utils.generateResponseEventName(data.uuid) | ||||
|         const preparedData = this._utils.prepareForTransfer(data) | ||||
| 
 | ||||
|         player.call(eventName, [preparedData]) | ||||
|     } | ||||
| 
 | ||||
|     public async listenEvent(player: any, data: string) { | ||||
|         const rpcData = this._verifyEvent(data) | ||||
| 
 | ||||
|         if (rpcData.knownError) { | ||||
|             this._triggerError(rpcData) | ||||
|             return | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             const fnResponse = await this._state[rpcData.eventName]( | ||||
|                 ...rpcData.data, | ||||
|             ) | ||||
|             const response = { | ||||
|                 ...rpcData, | ||||
|                 data: fnResponse, | ||||
|             } | ||||
| 
 | ||||
|             this.sendResponseToClient(player, response) | ||||
|         } catch (e) { | ||||
|             this._triggerError(rpcData, e) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private handleServerClientReturn( | ||||
|         uuid: string, | ||||
|         resolve: (value: unknown) => void, | ||||
|         reject: (reason?: any) => void, | ||||
|     ) { | ||||
|         const responseEvent = this._utils.generateResponseEventName(uuid) | ||||
|         const timeoutDuration = 1000 * 10 | ||||
| 
 | ||||
|         const timeoutID = setTimeout(() => { | ||||
|             reject(new Error('Timeout ended')) | ||||
|             mp.events.remove(responseEvent) | ||||
|         }, timeoutDuration) | ||||
| 
 | ||||
|         const handler = (response: string) => { | ||||
|             const { knownError, data } = this._utils.prepareForExecute(response) | ||||
| 
 | ||||
|             if (knownError) { | ||||
|                 try { | ||||
|                     clearTimeout(timeoutID) | ||||
|                     reject(knownError) | ||||
|                     return | ||||
|                 } catch (e) {} | ||||
|             } | ||||
| 
 | ||||
|             resolve(data) | ||||
|             mp.events.remove(responseEvent) | ||||
| 
 | ||||
|             try { | ||||
|                 clearTimeout(timeoutID) | ||||
|             } catch (e) {} | ||||
|         } | ||||
| 
 | ||||
|         mp.events.add(responseEvent, handler) | ||||
|     } | ||||
| 
 | ||||
|     public async executeServer< | ||||
|         Args extends any[] = unknown[], | ||||
|         Return = unknown, | ||||
|     >(eventName: string, ...args: Args): Promise<Return | unknown> { | ||||
|         return new Promise((resolve, reject) => { | ||||
|             const uuid = this._utils.generateUUID() | ||||
| 
 | ||||
|             const data: RPCState = { | ||||
|                 uuid, | ||||
|                 eventName, | ||||
|                 calledFrom: this._environment, | ||||
|                 calledTo: Environment.SERVER, | ||||
|                 data: args, | ||||
|             } | ||||
| 
 | ||||
|             mp.events.callRemote( | ||||
|                 EVENT_LISTENER, | ||||
|                 this._utils.prepareForTransfer(data), | ||||
|             ) | ||||
| 
 | ||||
|             this.handleServerClientReturn(uuid, resolve, reject) | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export const server = new Server() | ||||
| @ -1,35 +0,0 @@ | ||||
| import { Environment, Errors, RPCState, utils } from '../utils' | ||||
| 
 | ||||
| export class Wrapper { | ||||
|     protected _utils = utils | ||||
|     protected _environment = utils.getEnvironment() | ||||
|     protected _state = | ||||
|         this._environment === Environment.CEF | ||||
|             ? window.rpcEvents | ||||
|             : global.rpcEvents | ||||
| 
 | ||||
|     protected _verifyEvent(data: string): RPCState { | ||||
|         const rpcData = utils.prepareForExecute(data) | ||||
| 
 | ||||
|         if (!this._state[rpcData.eventName]) { | ||||
|             rpcData.knownError = Errors.EVENT_NOT_REGISTERED | ||||
|         } | ||||
| 
 | ||||
|         return rpcData | ||||
|     } | ||||
| 
 | ||||
|     protected _triggerError(rpcData: RPCState, error?: any) { | ||||
|         const errorMessage = [ | ||||
|             `${rpcData.knownError}`, | ||||
|             `Caller: ${rpcData.calledFrom}`, | ||||
|             `Receiver: ${this._environment}`, | ||||
|             `Event: ${rpcData.eventName}`, | ||||
|         ] | ||||
| 
 | ||||
|         if (error) { | ||||
|             errorMessage.push(`Additional Info: ${error}`) | ||||
|         } | ||||
| 
 | ||||
|         throw new Error(errorMessage.join(' | ')) | ||||
|     } | ||||
| } | ||||
							
								
								
									
										89
									
								
								rpc/src/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								rpc/src/server.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,89 @@ | ||||
| import { Wrapper } from './wrapper' | ||||
| import { | ||||
|     Environment, | ||||
|     Events, | ||||
|     type PlayerMp, | ||||
|     RPCEventType, | ||||
|     RPCState, | ||||
|     Utils, | ||||
| } from './utils' | ||||
| 
 | ||||
| class Server extends Wrapper { | ||||
|     constructor() { | ||||
|         super() | ||||
| 
 | ||||
|         mp.events.add( | ||||
|             Events.SERVER_EVENT_LISTENER, | ||||
|             async (player: PlayerMp, dataRaw: string) => { | ||||
|                 this.emit(player, dataRaw) | ||||
|             }, | ||||
|         ) | ||||
|     } | ||||
| 
 | ||||
|     public _resolveEmitDestination(player: PlayerMp, dataRaw: string) { | ||||
|         let state = Utils.prepareExecution(dataRaw) | ||||
| 
 | ||||
|         switch (state.calledTo) { | ||||
|             case Environment.SERVER: | ||||
|                 this.emit(player, dataRaw) | ||||
|                 break | ||||
| 
 | ||||
|             default: | ||||
|                 this.emitClient(player as PlayerMp, dataRaw) | ||||
|                 break | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private emitClient(player: PlayerMp, dataRaw: string) { | ||||
|         player.call(Events.LOCAL_EVENT_LISTENER, [dataRaw]) | ||||
|     } | ||||
| 
 | ||||
|     private async emit(player: PlayerMp, dataRaw: string) { | ||||
|         let state = Utils.prepareExecution(dataRaw) | ||||
|         const responseEventName = Utils.generateResponseEventName(state.uuid) | ||||
| 
 | ||||
|         state = this.verifyEvent_(state) | ||||
|         if (state.knownError) { | ||||
|             this.triggerError_(state, state.knownError) | ||||
|         } | ||||
| 
 | ||||
|         const response = await this.state_[state.eventName]( | ||||
|             player, | ||||
|             ...(Array.isArray(state.data) ? state.data : []), | ||||
|         ) | ||||
|         const responseState: RPCState = { | ||||
|             uuid: Utils.generateUUID(), | ||||
|             eventName: state.eventName, | ||||
|             calledFrom: Environment.SERVER, | ||||
|             calledTo: state.calledFrom, | ||||
|             knownError: undefined, | ||||
|             data: response, | ||||
|             type: RPCEventType.RESPONSE, | ||||
|         } | ||||
| 
 | ||||
|         switch (state.calledFrom) { | ||||
|             case Environment.SERVER: | ||||
|                 try { | ||||
|                     mp.events.call( | ||||
|                         responseEventName, | ||||
|                         Utils.prepareTransfer(responseState), | ||||
|                     ) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
|             default: | ||||
|                 try { | ||||
|                     player.call(responseEventName, [ | ||||
|                         Utils.prepareTransfer(responseState), | ||||
|                     ]) | ||||
|                 } catch (e) { | ||||
|                     void e | ||||
|                 } | ||||
|                 break | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| const server = new Server() | ||||
| export { server } | ||||
							
								
								
									
										145
									
								
								rpc/src/utils.ts
									
									
									
									
									
								
							
							
						
						
									
										145
									
								
								rpc/src/utils.ts
									
									
									
									
									
								
							| @ -1,42 +1,44 @@ | ||||
| import { EVENT_RESPONSE } from './events' | ||||
| 
 | ||||
| export enum Environment { | ||||
|     CEF = 'CEF', | ||||
|     BROWSER = 'BROWSER', | ||||
|     CLIENT = 'CLIENT', | ||||
|     SERVER = 'SERVER', | ||||
|     UNKNOWN = 'UNKNOWN', | ||||
| } | ||||
| 
 | ||||
| export enum Events { | ||||
|     LOCAL_EVENT_LISTENER = '__rpc:listener', | ||||
|     SERVER_EVENT_LISTENER = '__rpc:serverListener', | ||||
|     EVENT_RESPONSE = '__rpc:response', | ||||
| } | ||||
| 
 | ||||
| export enum Errors { | ||||
|     EVENT_NOT_REGISTERED = 'Event not registered', | ||||
|     UNKNOWN_ENVIRONMENT = 'Unknown environment', | ||||
|     NO_BROWSER = 'You need to initialize browser first', | ||||
|     EVENT_RESPONSE_TIMEOUT = 'Response was timed out after 10s of inactivity', | ||||
| } | ||||
| 
 | ||||
| export type RPCState = { | ||||
|     eventName: string | ||||
|     uuid: string | ||||
|     knownError?: string | ||||
|     data?: any | ||||
|     calledFrom: Environment | ||||
|     calledTo: Environment | ||||
| } | ||||
| 
 | ||||
| class Utils { | ||||
|     public getEnvironment(): Environment { | ||||
|         if (mp.joaat) return Environment.SERVER | ||||
|         if (mp.game && mp.game.joaat) return Environment.CLIENT | ||||
|         if ('mp' in window) return Environment.CEF | ||||
| export class Utils { | ||||
|     public static getEnvironment(): Environment { | ||||
|         if ('joaat' in mp) return Environment.SERVER | ||||
|         if ( | ||||
|             'game' in mp && | ||||
|             'joaat' in (mp as { game: { joaat?: unknown } }).game | ||||
|         ) | ||||
|             return Environment.CLIENT | ||||
|         if (window && 'mp' in window) return Environment.BROWSER | ||||
|         return Environment.UNKNOWN | ||||
|     } | ||||
| 
 | ||||
|     public prepareForExecute(data: string): RPCState { | ||||
|     public static prepareExecution(data: string): RPCState { | ||||
|         return JSON.parse(data) | ||||
|     } | ||||
| 
 | ||||
|     public prepareForTransfer(data: RPCState): string { | ||||
|     public static prepareTransfer(data: RPCState): string { | ||||
|         return JSON.stringify(data) | ||||
|     } | ||||
| 
 | ||||
|     public generateUUID(): string { | ||||
|     public static generateUUID(): string { | ||||
|         let uuid = '', | ||||
|             random | ||||
| 
 | ||||
| @ -55,9 +57,106 @@ class Utils { | ||||
|         return uuid | ||||
|     } | ||||
| 
 | ||||
|     public generateResponseEventName(uuid: string): string { | ||||
|         return `${EVENT_RESPONSE}_${uuid}` | ||||
|     public static generateResponseEventName(uuid: string): string { | ||||
|         return `${Events.EVENT_RESPONSE}_${uuid}` | ||||
|     } | ||||
| 
 | ||||
|     public static errorUnknownEnvironment(environment: Environment) { | ||||
|         if (environment === Environment.UNKNOWN) | ||||
|             throw new Error(Errors.UNKNOWN_ENVIRONMENT) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| export const utils = new Utils() | ||||
| export type RPCState = { | ||||
|     eventName: string | ||||
|     uuid: string | ||||
|     calledFrom: Environment | ||||
|     calledTo: Environment | ||||
|     knownError?: string | ||||
|     data?: any | ||||
|     type: RPCEventType | ||||
| } | ||||
| 
 | ||||
| export enum RPCEventType { | ||||
|     EVENT = 'event', | ||||
|     RESPONSE = 'response', | ||||
| } | ||||
| 
 | ||||
| export type PlayerMp = { | ||||
|     call: (event: string, args?: unknown[]) => void | ||||
| } | ||||
| 
 | ||||
| export const nativeClientEvents = new Set([ | ||||
|     'browserCreated', | ||||
|     'browserDomReady', | ||||
|     'browserLoadingFailed', | ||||
|     'playerEnterCheckpoint', | ||||
|     'playerExitCheckpoint', | ||||
|     'consoleCommand', | ||||
|     'click', | ||||
|     'playerChat', | ||||
|     'playerCommand', | ||||
|     'playerDeath', | ||||
|     'playerJoin', | ||||
|     'playerQuit', | ||||
|     'playerReady', | ||||
|     'playerResurrect', | ||||
|     'playerRuleTriggered', | ||||
|     'playerSpawn', | ||||
|     'playerWeaponShot', | ||||
|     'dummyEntityCreated', | ||||
|     'dummyEntityDestroyed', | ||||
|     'entityControllerChange', | ||||
|     'incomingDamage', | ||||
|     'outgoingDamage', | ||||
|     'meleeActionDamage', | ||||
|     'playerEnterVehicle', | ||||
|     'playerLeaveVehicle', | ||||
|     'playerStartTalking', | ||||
|     'playerStopTalking', | ||||
|     'entityStreamIn', | ||||
|     'entityStreamOut', | ||||
|     'render', | ||||
|     'playerCreateWaypoint', | ||||
|     'playerReachWaypoint', | ||||
|     'playerEnterColshape', | ||||
|     'playerExitColshape', | ||||
|     'explosion', | ||||
|     'projectile', | ||||
|     'uncaughtException', | ||||
|     'unhandledRejection', | ||||
| ]) | ||||
| 
 | ||||
| export const nativeServerEvents = new Set([ | ||||
|     'entityCreated', | ||||
|     // 'entityDestroyed',
 | ||||
|     'entityModelChange', | ||||
|     'incomingConnection', | ||||
|     'packagesLoaded', | ||||
|     'playerChat', | ||||
|     'playerCommand', | ||||
|     'playerDamage', | ||||
|     'playerDeath', | ||||
|     'playerEnterCheckpoint', | ||||
|     'playerEnterColshape', | ||||
|     'playerEnterVehicle', | ||||
|     'playerExitCheckpoint', | ||||
|     'playerExitColshape', | ||||
|     'playerExitVehicle', | ||||
|     'playerJoin', | ||||
|     'playerQuit', | ||||
|     'playerReachWaypoint', | ||||
|     'playerReady', | ||||
|     'playerSpawn', | ||||
|     'playerStartEnterVehicle', | ||||
|     'playerStartExitVehicle', | ||||
|     'playerStreamIn', | ||||
|     'playerStreamOut', | ||||
|     'playerWeaponChange', | ||||
|     'serverShutdown', | ||||
|     'trailerAttached', | ||||
|     'vehicleDamage', | ||||
|     'vehicleDeath', | ||||
|     'vehicleHornToggle', | ||||
|     'vehicleSirenToggle', | ||||
| ]) | ||||
|  | ||||
							
								
								
									
										33
									
								
								rpc/src/wrapper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								rpc/src/wrapper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | ||||
| import { Environment, Errors, RPCState, Utils } from './utils' | ||||
| 
 | ||||
| export class Wrapper { | ||||
|     protected environment_ = Utils.getEnvironment() | ||||
|     protected state_ = | ||||
|         this.environment_ === Environment.BROWSER ? window : global | ||||
| 
 | ||||
|     protected verifyEvent_(data: string | RPCState): RPCState { | ||||
|         let rpcData = | ||||
|             typeof data === 'string' ? Utils.prepareExecution(data) : data | ||||
| 
 | ||||
|         if (!this.state_[rpcData.eventName]) { | ||||
|             rpcData.knownError = Errors.EVENT_NOT_REGISTERED | ||||
|         } | ||||
| 
 | ||||
|         return rpcData | ||||
|     } | ||||
| 
 | ||||
|     protected triggerError_(rpcData: RPCState, error?: any) { | ||||
|         const errorMessage = [ | ||||
|             `${rpcData.knownError}`, | ||||
|             `Caller: ${rpcData.calledFrom}`, | ||||
|             `Receiver: ${this.environment_}`, | ||||
|             `Event: ${rpcData.eventName}`, | ||||
|         ] | ||||
| 
 | ||||
|         if (error) { | ||||
|             errorMessage.push(`Additional Info: ${error}`) | ||||
|         } | ||||
| 
 | ||||
|         throw new Error(errorMessage.join('\n | ')) | ||||
|     } | ||||
| } | ||||
| @ -3,7 +3,10 @@ | ||||
|     "target": "es6", | ||||
|     "module": "commonjs", | ||||
|     "moduleResolution": "node", | ||||
|     "lib": ["ES6"], | ||||
|     "lib": [ | ||||
|       "ES6", | ||||
|       "dom" | ||||
|     ], | ||||
|     "declaration": true, | ||||
|     "declarationMap": true, | ||||
|     "sourceMap": true, | ||||
| @ -17,9 +20,10 @@ | ||||
|   }, | ||||
|   "include": [ | ||||
|     "src/**/*", | ||||
|     "./types.d.ts" | ||||
|     "./index.d.ts" | ||||
|   ], | ||||
|   "exclude": [ | ||||
|     "node_modules" | ||||
|     "node_modules", | ||||
|     "dist" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										13
									
								
								rpc/types.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								rpc/types.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -1,13 +0,0 @@ | ||||
| declare const mp: any | ||||
| declare const console: any | ||||
| 
 | ||||
| declare const setTimeout: (fn: Function, time: number) => number | ||||
| declare const clearTimeout: (id: number) => void | ||||
| 
 | ||||
| declare const global: { | ||||
|     rpcEvents: Record<string, (...args: any[]) => unknown> | ||||
| } | ||||
| 
 | ||||
| declare const window: { | ||||
|     rpcEvents: Record<string, (...args: any[]) => unknown> | ||||
| } | ||||
| @ -11,7 +11,7 @@ | ||||
|         "build": "tsup" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "rage-rpc": "^0.4.0" | ||||
|         "ragefw-rpc": "workspace:^" | ||||
|     }, | ||||
|     "peerDependencies": { | ||||
|         "@ragempcommunity/types-server": "^2.1.8", | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user