diff --git a/CHANGELOG.md b/CHANGELOG.md index b02136f4c0..f00ef6ac69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,9 @@ Athena.systems.inventory.factory.getBaseItems AthenaClient.systems.playerConfig.get('account-data') : Returns Filtered Account Data AthenaClient.systems.playerConfig.get('character-data') : Returns Character Data +Athena.systems.rpc.invoke +AthenaClient.systems.rpc.on + -------------------------------------- --- Everything Below is Before April 2 -------------------------------------- diff --git a/src/core/client/systems/index.ts b/src/core/client/systems/index.ts index dca1742499..4dc9281bdf 100644 --- a/src/core/client/systems/index.ts +++ b/src/core/client/systems/index.ts @@ -6,5 +6,6 @@ export * as interaction from './interaction'; export * as inventory from './inventory'; export * as messenger from './messenger'; export * as playerConfig from './playerConfig'; +export * as rpc from './rpc'; export * as sound from './sound'; export * as wheelMenu from '../views/wheelMenu'; diff --git a/src/core/client/systems/rpc.ts b/src/core/client/systems/rpc.ts new file mode 100644 index 0000000000..00267dd8f9 --- /dev/null +++ b/src/core/client/systems/rpc.ts @@ -0,0 +1,25 @@ +import * as alt from 'alt-client'; + +/** + * Handle an RPC event from server-side, and return a result back to the server. + * + * The RPC event must be invoked from server-side through the `Athena.systems.rpc.invoke` function. + * + * #### Example + * ```ts + * AthenaClient.systems.rpc.on('returnPlayerLocalPosition', () => { + * return alt.Player.local.pos; + * }) + * ``` + * + * @export + * @template T + * @param {string} eventName + * @param {(...args: any[]) => T} callback + */ +export function on(eventName: string, callback: (...args: any[]) => T) { + alt.onServer(eventName, async (instancedName: string, ...args: any[]) => { + const result = await callback(...args); + alt.emitServer(instancedName, result); + }); +} diff --git a/src/core/server/systems/index.ts b/src/core/server/systems/index.ts index ee32b8cff0..7bab5cb8cc 100644 --- a/src/core/server/systems/index.ts +++ b/src/core/server/systems/index.ts @@ -14,6 +14,7 @@ export * as notification from '@AthenaServer/systems/notification'; export * as permission from '@AthenaServer/systems/permission'; export * as permissionGroup from '@AthenaServer/systems/permissionGroup'; export * as plugins from '@AthenaServer/systems/plugins'; +export * as rpc from '@AthenaServer/systems/rpc'; export * as sound from '@AthenaServer/systems/sound'; export * as storage from '@AthenaServer/systems/storage'; export * as streamer from '@AthenaServer/systems/streamer'; diff --git a/src/core/server/systems/rpc.ts b/src/core/server/systems/rpc.ts new file mode 100644 index 0000000000..a82fb7741b --- /dev/null +++ b/src/core/server/systems/rpc.ts @@ -0,0 +1,100 @@ +import * as alt from 'alt-server'; +import * as Athena from '@AthenaServer/api'; + +export type Callback = (player: alt.Player, ...args: any[]) => void; + +export interface ServerRpcEvent { + /** + * A general purpose name for the event. + * + * @type {string} + * @memberof ServerRpcEvent + */ + eventName: string; + + /** + * Arguments to pass down to the client. + * + * @type {any[]} + * @memberof ServerRpcEvent + */ + args?: any[]; + + /** + * Kick, and log the user for not responding to an event. + * + * The value should be a message to send to the user. + * + * @type {string} + * @memberof ServerRpcEvent + */ + kickOnNoResponse?: string; + + /** + * Timeout before the event is unregistered and removed. + * + * @type {number} + * @memberof ServerRpcEvent + */ + msTimeout: number; +} + +/** + * Invoke an RPC event, and get a result. + * + * If the timeout expires; the callback will pass undefined. + * + * #### Example + * ```ts + * Athena.systems.rpc.invoke(somePlayer, 'getLocalPos', (player: alt.Player, pos: alt.IVector3) => { + * alt.log('RPC Position was' + JSON.stringify(pos)); + * }) + * ``` + * + * @export + * @template T + * @param {alt.Player} player + * @param {ServerRpcEvent} event + * @param {(...T) => void} callback + * @return {*} + */ +export function invoke(player: alt.Player, event: ServerRpcEvent, callback: Callback) { + // Construct instanced event name + // Randomize it to help with collisions as well + const instancedName = Athena.utility.hash.sha256Random(`rpc:${player.id}:${event.eventName}`); + + const callbackHandler = (player: alt.Player, ...args: any[]) => { + alt.clearTimeout(timeout); + callback(player, ...args); + }; + + const timeoutHandler = () => { + alt.offClient(instancedName, callbackHandler); + if (!event.kickOnNoResponse) { + return; + } + + if (!player || !player.valid) { + return; + } + + const data = Athena.document.character.get(player); + const mongoId = data && data._id ? data._id : 'Not Logged In'; + alt.log(`${event.eventName} was called, but no response. ID: ${player.id} | MongoDB: ${mongoId}`); + player.kick(event.kickOnNoResponse); + }; + + // Clear old event if time is exceeded + const timeout = alt.setTimeout(timeoutHandler, event.msTimeout); + + // Automatically unregisters itself once a client has pushed up the data + alt.onceClient(instancedName, callbackHandler); + + // Call normal event name, and pass arguments down + if (event.args) { + player.emit(event.eventName, instancedName, ...event.args); + return; + } + + player.emit(event.eventName, instancedName); +} diff --git a/src/core/shared/utility/rpc.ts b/src/core/shared/utility/rpc.ts new file mode 100644 index 0000000000..162d92e4d5 --- /dev/null +++ b/src/core/shared/utility/rpc.ts @@ -0,0 +1,8 @@ + + +// server +// rpc.emit -> someEvent, someReturnValue + +// client +// rpc.handle -> someEvent, pushToServer +