diff --git a/rpc/src/index.ts b/rpc/src/index.ts index 1716f00..d9fc457 100644 --- a/rpc/src/index.ts +++ b/rpc/src/index.ts @@ -77,7 +77,7 @@ class Rpc extends Wrapper { * * @template CallbackArguments - An array of argument types that the callback function accepts * @template CallbackReturn - The type of the value returned by the callback function - * @template EventName - A string representing the event name or union of names + * @template EventName - A string or union representing the event name * * @param {EventName} eventName - The name of the event to register the callback for * @param {(...args: CallbackArguments) => CallbackReturn} cb - The callback function that is called when the event is triggered @@ -116,7 +116,7 @@ class Rpc extends Wrapper { /** * Unregisters callback function for a specified event * - * @template EventName - A string representing the event name or union of names + * @template EventName - A string or union representing the event name * * @param {EventName} eventName - The name of the event to register the callback for * @@ -133,54 +133,49 @@ class Rpc extends Wrapper { Utils.errorUnknownEnvironment(this.environment_) delete this.state_[eventName] + mp.events.remove(eventName) } /** - * Calls a client-side event from server or browser + * Calls a client-side event from browser or server. Use 'call' to call client from client * * @template Arguments - An array of argument types to be passed to the client event - * @template EventName - A string representing the client event name or union of names - * @template Return - The type of the value returned by the client event + * @template EventName - A string or union representing the client event name * * @param {EventName} eventName - The name of the client event to be called * @param {Arguments} [args] - Optional arguments to pass to the client event - * @returns {Promise} A promise resolving to the return value of the client event + * @returns {void} * * @example - * // Calls an event on client without specifying a player - * callClient<[], string, object>('onDataRequest').then(response => { - * console.log(`Received: ${response}`) // ^ object - * }) + * // Calls an event on a client without specifying a player + * callClient<[], string, object>('onDataRequest') */ - public async callClient< + public callClient< Arguments extends unknown[] = unknown[], EventName extends string = string, Return = unknown, - >(eventName: EventName, args?: Arguments): Promise + >(eventName: EventName, args?: Arguments): void /** - * Calls a client-side event from server or browser + * Calls a client-side event from server or server. Use 'call' to call client from client * * @template Arguments - An array of argument types to be passed to the client event - * @template EventName - A string representing the client event name or union of names - * @template Return - The type of the value returned by the client event + * @template EventName - A string or union representing the client event name * * @param {PlayerMp} player - The player for whom the client event is called * @param {EventName} eventName - The name of the client event to be called * @param {Arguments} [args] - Optional arguments to pass to the client event - * @returns {Promise} A promise resolving to the return value of the client event + * @returns {void} A promise resolving to the return value of the client event * * @example - * // Calls an event on client for a specific player - * callClient<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => { - * console.log(`Action success: ${result}`) // ^ boolean - * }) + * // Calls an event on a client for a specific player + * callClient<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]) */ - public async callClient< + public callClient< Arguments extends unknown[] = unknown[], EventName extends string = string, Return = unknown, - >(player: PlayerMp, eventName: EventName, args?: Arguments): Promise - public async callClient( + >(player: PlayerMp, eventName: EventName, args?: Arguments): void + public callClient( playerOrEventName: PlayerMp | string, eventNameOrArgs?: string | unknown[], args?: unknown[], @@ -202,10 +197,7 @@ class Rpc extends Wrapper { if (this.environment_ === Environment.CLIENT) { // client - return await this.call( - playerOrEventName as string, - args as unknown[], - ) + this.call(playerOrEventName as string, args as unknown[]) } // server @@ -227,7 +219,6 @@ class Rpc extends Wrapper { const dataRaw = Utils.prepareTransfer(state) playerOrEventName.call(Events.LOCAL_EVENT_LISTENER, [dataRaw]) - return (await this.responseHandler(state.uuid)).data } // browser @@ -249,7 +240,6 @@ class Rpc extends Wrapper { const dataRaw = Utils.prepareTransfer(state) mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw) - return (await this.responseHandler(state.uuid)).data } function _is1StParamPlayer(x: unknown): x is PlayerMp { @@ -262,10 +252,201 @@ class Rpc extends Wrapper { } /** - * Calls a server-side event from browser or client + * Calls an asynchronous client-side event from server or browser, expecting a response. Use 'callAsync' to call client from client + * + * @template Arguments - An array of argument types to be passed to the client event + * @template EventName - A string or union representing the client event name + * @template Return - The type of the value returned by the client event + * + * @param {EventName} eventName - The name of the client event to be called + * @param {Arguments} [args] - Optional arguments to pass to the client event + * @returns {Promise} A promise resolving to the return value of the client event + * + * @example + * // Calls an event on a client without specifying a player + * callClient<[], string, object>('onDataRequest').then(response => { + * console.log(`Received: ${response}`) // ^ object + * }) + */ + public async callClientAsync< + Arguments extends unknown[] = unknown[], + EventName extends string = string, + Return = unknown, + >(eventName: EventName, args?: Arguments): Promise + /** + * Calls an asynchronous client-side event from server or browser, expecting a response. Use 'callAsync' to call client from client + * + * @template Arguments - An array of argument types to be passed to the client event + * @template EventName - A string or union representing the client event name + * @template Return - The type of the value returned by the client event + * + * @param {PlayerMp} player - The player for whom the client event is called + * @param {EventName} eventName - The name of the client event to be called + * @param {Arguments} [args] - Optional arguments to pass to the client event + * @returns {Promise} A promise resolving to the return value of the client event + * + * @example + * // Calls an event on a client for a specific player + * callClient<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => { + * console.log(`Action success: ${result}`) // ^ boolean + * }) + */ + public async callClientAsync< + Arguments extends unknown[] = unknown[], + EventName extends string = string, + Return = unknown, + >(player: PlayerMp, eventName: EventName, args?: Arguments): Promise + public async callClientAsync( + playerOrEventName: PlayerMp | string, + eventNameOrArgs?: string | unknown[], + args?: unknown[], + ) { + if (_is1StParamPlayer(playerOrEventName)) { + this.log( + 'callClientAsync', + eventNameOrArgs as string, + playerOrEventName, + eventNameOrArgs, + args, + ) + } else { + this.log( + 'callClientAsync', + playerOrEventName as string, + eventNameOrArgs, + ) + } + + if (this.forceBrowserDevMode_) return + Utils.errorUnknownEnvironment(this.environment_) + + if (this.environment_ === Environment.CLIENT) { + // client + return await this.callAsync( + playerOrEventName as string, + args as unknown[], + ) + } + + // server + if ( + this.environment_ === Environment.SERVER && + _is1StParamPlayer(playerOrEventName) && + _is2NdParamEventName(eventNameOrArgs) + ) { + let 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) + .then(r => r.data) + .catch((e: string) => { + state.knownError = e + return this.triggerError_(state) + }) + } + + // 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) + .then(r => r.data) + .catch((e: string) => { + state.knownError = e + return this.triggerError_(state) + }) + } + + function _is1StParamPlayer(x: unknown): x is PlayerMp { + return typeof x === 'object' + } + + function _is2NdParamEventName(x: unknown): x is string { + return typeof x === 'string' + } + } + + /** + * Calls a server-side event from browser or client. Use 'call' to call server from server * * @template Arguments - An array of argument types to be passed to the server event - * @template EventName - A string representing the server event name or union of names + * @template EventName - A string or union representing the server event name + * + * @param {EventName} eventName - The name of the server event to be called + * @param {Arguments} [args] - Optional arguments to pass to the server event + * @returns {void} + * + * @example + * // Calls an event on a server + * callServer<[], string, object>('onDataRequest') + */ + public callServer< + Arguments extends unknown[] = unknown[], + EventName extends string = string, + >(eventName: EventName, args?: Arguments): void { + this.log('callServer', eventName, args) + if (this.forceBrowserDevMode_) 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: + this.callSelf(state) + break + + case Environment.CLIENT: + mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw) + break + + case Environment.BROWSER: + mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw) + break + } + } + + /** + * Calls an asynchronous server-side event from browser or client, expecting a response. Use 'callAsync' to call server from server + * + * @template Arguments - An array of argument types to be passed to the server event + * @template EventName - A string or union representing the server event name * @template Return - The type of the value returned by the server event * * @param {EventName} eventName - The name of the server event to be called @@ -273,17 +454,17 @@ class Rpc extends Wrapper { * @returns {Promise} A promise resolving to the return value of the server event * * @example - * // Calls an event on server + * // Calls an event on a server * callServer<[], string, object>('onDataRequest').then(response => { * console.log(`Received: ${response}`) // ^ object * }) */ - public async callServer< + public async callServerAsync< Arguments extends unknown[] = unknown[], EventName extends string = string, Return = unknown, >(eventName: EventName, args?: Arguments): Promise { - this.log('callServer', eventName, args) + this.log('callServerAsync', eventName, args) if (this.forceBrowserDevMode_) return undefined as unknown as Promise Utils.errorUnknownEnvironment(this.environment_) @@ -302,7 +483,7 @@ class Rpc extends Wrapper { switch (this.environment_) { case Environment.SERVER: - return this.callSelf(state) + return this.callSelfAsync(state) case Environment.CLIENT: mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw) @@ -313,14 +494,124 @@ class Rpc extends Wrapper { break } - return (await this.responseHandler(state.uuid)).data + return await this.responseHandler(state.uuid) + .then(r => r.data) + .catch((e: string) => { + state.knownError = e + return this.triggerError_(state) + }) } /** - * Calls a browser-side event from server or client + * Calls a browser-side event from client or server. Use 'call' to call browser from browser + * + * @template Arguments - An array of argument types to be passed to the browser event + * @template EventName - A string or union representing the browser event name + * + * @param {EventName} eventName - The name of the browser event to be called + * @param {Arguments} [args] - Optional arguments to pass to the browser event + * @returns {void} + * + * @example + * // Calls an event on a browser without specifying a player + * callBrowser<[], string, object>('onDataRequest') + */ + public callBrowser< + Arguments extends unknown[] = unknown[], + EventName extends string = string, + Return = unknown, + >(eventName: EventName, args?: Arguments): void + /** + * Calls a browser-side event from client or server. Use 'call' to call browser from browser * * @template Arguments - An array of argument types to be passed to the browser event * @template EventName - A string representing the browser event name or union of names + * + * @param {PlayerMp} player - The player for whom the browser event is called + * @param {EventName} eventName - The name of the browser event to be called + * @param {Arguments} [args] - Optional arguments to pass to the browser event + * @returns {void} + * + * @example + * // Calls an event on a browser for a specific player + * callBrowser<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]) + */ + public callBrowser< + Arguments extends unknown[] = unknown[], + EventName extends string = string, + Return = unknown, + >(player: PlayerMp, eventName: EventName, args?: Arguments): void + public callBrowser( + playerOrEventName: PlayerMp | string, + eventNameOrArgs?: string | unknown[], + args?: unknown[], + ) { + if (_is1StParamPlayer(playerOrEventName)) { + this.log( + 'callBrowser', + eventNameOrArgs as string, + playerOrEventName, + eventNameOrArgs, + args, + ) + } else { + this.log( + 'callBrowser', + playerOrEventName as string, + eventNameOrArgs, + ) + } + + if (this.forceBrowserDevMode_) return + Utils.errorUnknownEnvironment(this.environment_) + + 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: + this.callSelf(state) + break + + 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 + } + + function _is1StParamPlayer(x: unknown): x is PlayerMp { + return typeof x === 'object' + } + + function _is2NdParamEventName(x: unknown): x is string { + return typeof x === 'string' + } + } + + /** + * Calls an asynchronous browser-side event from client or server, expecting response. Use 'callAsync' to call browser from browser + * + * @template Arguments - An array of argument types to be passed to the browser event + * @template EventName - A string or union representing the browser event name * @template Return - The type of the value returned by the browser event * * @param {EventName} eventName - The name of the browser event to be called @@ -328,18 +619,18 @@ class Rpc extends Wrapper { * @returns {Promise} A promise resolving to the return value of the browser event * * @example - * // Calls an event on browser without specifying a player + * // Calls an event on a browser without specifying a player * callBrowser<[], string, object>('onDataRequest').then(response => { * console.log(`Received: ${response}`) // ^ object * }) */ - public async callBrowser< + public async callBrowserAsync< Arguments extends unknown[] = unknown[], EventName extends string = string, Return = unknown, >(eventName: EventName, args?: Arguments): Promise /** - * Calls a browser-side event from server or client + * Calls an asynchronous browser-side event from client or server, expecting response. Use 'callAsync' to call browser from browser * * @template Arguments - An array of argument types to be passed to the browser event * @template EventName - A string representing the browser event name or union of names @@ -351,24 +642,24 @@ class Rpc extends Wrapper { * @returns {Promise} A promise resolving to the return value of the browser event * * @example - * // Calls an event on a browser for a specific player + * // Calls an asynchronous event on a browser for a specific player * callBrowser<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => { * console.log(`Action success: ${result}`) // ^ boolean * }) */ - public async callBrowser< + public async callBrowserAsync< Arguments extends unknown[] = unknown[], EventName extends string = string, Return = unknown, >(player: PlayerMp, eventName: EventName, args?: Arguments): Promise - public async callBrowser( + public async callBrowserAsync( playerOrEventName: PlayerMp | string, eventNameOrArgs?: string | unknown[], args?: unknown[], ) { if (_is1StParamPlayer(playerOrEventName)) { this.log( - 'DEV callClient', + 'callBrowserAsync', eventNameOrArgs as string, playerOrEventName, eventNameOrArgs, @@ -376,7 +667,7 @@ class Rpc extends Wrapper { ) } else { this.log( - 'DEV callClient', + 'callBrowserAsync', playerOrEventName as string, eventNameOrArgs, ) @@ -417,7 +708,12 @@ class Rpc extends Wrapper { break } - return (await this.responseHandler(state.uuid)).data + return await this.responseHandler(state.uuid) + .then(r => r.data) + .catch((e: string) => { + state.knownError = e + return this.triggerError_(state) + }) function _is1StParamPlayer(x: unknown): x is PlayerMp { return typeof x === 'object' @@ -432,7 +728,43 @@ class Rpc extends Wrapper { * Calls an event in current environment * * @template Arguments - An array of argument types to be passed to the event - * @template EventName - A string representing the event name or union of names + * @template EventName - A string or union representing the event name + * + * @param {EventName} eventName - The name of the event to be called + * @param {Arguments} [args] - Optional arguments to pass to the event + * @returns {void} + * + * @example + * // Calls an event in current environment + * call<[], string>('getSomething') + */ + public call< + Arguments extends unknown[] = unknown[], + EventName extends string = string, + Return = unknown, + >(eventName: EventName, args?: Arguments): void { + this.log('call', eventName, args) + if (this.forceBrowserDevMode_) return + Utils.errorUnknownEnvironment(this.environment_) + + const state: RPCState = { + uuid: Utils.generateUUID(), + eventName, + calledTo: this.environment_, + calledFrom: this.environment_, + knownError: undefined, + data: args, + type: RPCEventType.EVENT, + } + + this.callSelf(state) + } + + /** + * Calls an asynchronous event in current environment, expecting a response + * + * @template Arguments - An array of argument types to be passed to the event + * @template EventName - A string or union representing the event name * @template Return - The type of the value returned by the event * * @param {EventName} eventName - The name of the event to be called @@ -445,12 +777,12 @@ class Rpc extends Wrapper { * console.log(`Received: ${response}`) // ^ number * }) */ - public async call< + public async callAsync< Arguments extends unknown[] = unknown[], EventName extends string = string, Return = unknown, >(eventName: EventName, args?: Arguments): Promise { - this.log('call', eventName, args) + this.log('callAsync', eventName, args) if (this.forceBrowserDevMode_) return undefined as unknown as Promise Utils.errorUnknownEnvironment(this.environment_) @@ -465,13 +797,27 @@ class Rpc extends Wrapper { type: RPCEventType.EVENT, } - return await this.callSelf(state) + return await this.callSelfAsync(state) } /** * redirects an event in cases of it calling its own environment */ - private async callSelf(state: RPCState): Promise { + private callSelf(state: RPCState): void { + state = this.verifyEvent_(state) + if (state.knownError) { + this.triggerError_(state, state.knownError) + } + + this.state_[state.eventName](...state.data) + } + + /** + * redirects an asynchronous event in cases of it calling its own environment + */ + private async callSelfAsync( + state: RPCState, + ): Promise { state = this.verifyEvent_(state) if (state.knownError) { this.triggerError_(state, state.knownError)