docs: TSdoc

This commit is contained in:
2026-09-28 02:05:40 +01:00
parent 40c322e4b7
commit cca07468d5
9 changed files with 336 additions and 11 deletions
+4
View File
@@ -14,6 +14,10 @@ import {
type RPCStateRaw,
} from '../utils/types'
/**
* **Internal** Shared by `RPCInstanceServer`, `RPCInstanceClient` and
* `RPCInstanceWebview`.
*/
export class RPCInstanceBase {
protected readonly env: RPCEnvironment
protected readonly debug: boolean
+111
View File
@@ -26,6 +26,18 @@ import type {
} from '../utils/typing'
import { RPCInstanceBase } from './base'
/**
* RPC instance for client code, returned by `createRPC({ env: 'client' })`.
* Create one per client and import it from your own module.
*
* - `on*` registers the listener that answers calls from one direction. One
* listener per event: registering the same name again replaces it, `off*`
* removes it
* - `emit*` calls the listener on the target and resolves with its return
* value, or rejects with {@link RPCError}
* - also relays calls between its webview and the server, so every client
* needs an instance even without listeners of its own
*/
export class RPCInstanceClient extends RPCInstanceBase {
private readonly _emitterServer: Emitter
private readonly _emitterWeb: Emitter
@@ -123,6 +135,16 @@ export class RPCInstanceClient extends RPCInstanceBase {
// ===== SERVER =====
/**
* Listens for `emitClient` and `emitClientEveryone` calls from the server
* (server -> client). For `emitClientEveryone` the return value is not sent.
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onServer('askTrade', offer => showTradeDialog(offer))
*/
public onServer<EventName extends RPCEventName<s.RPCEvents_ServerClient>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_ServerClient, EventName>,
@@ -130,12 +152,23 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this.listen(this._emitterServer, 'onServer', eventName, cb)
}
/** Removes the `onServer` listener for `eventName` */
public offServer<EventName extends RPCEventName<s.RPCEvents_ServerClient>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterServer, 'offServer', eventName)
}
/**
* Calls the server's `onClient` listener (client -> server) and resolves
* with its return value. The server listener gets this player's id first.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener),
* `HANDLER_ERROR` (the listener threw) or `TIMEOUT`
*
* @example
* const money = await rpc.emitServer('getMoney', 'bank')
*/
public async emitServer<
EventName extends RPCEventName<s.RPCEvents_ClientServer>,
>(
@@ -151,6 +184,16 @@ export class RPCInstanceClient extends RPCInstanceBase {
// ===== WEBVIEW =====
/**
* Listens for `emitClient` calls from this player's webview
* (webview -> client).
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onWebview('getPosition', () => GetEntityCoords(PlayerPedId(), false))
*/
public onWebview<EventName extends RPCEventName<s.RPCEvents_WebviewClient>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_WebviewClient, EventName>,
@@ -158,12 +201,23 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this.listen(this._emitterWeb, 'onWebview', eventName, cb)
}
/** Removes the `onWebview` listener for `eventName` */
public offWebview<EventName extends RPCEventName<s.RPCEvents_WebviewClient>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterWeb, 'offWebview', eventName)
}
/**
* Calls the `onClient` listener in this player's webview (client -> webview)
* and resolves with its return value.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener),
* `HANDLER_ERROR` (the listener threw) or `TIMEOUT`
*
* @example
* const choice = await rpc.emitWebview('openMenu', items)
*/
public async emitWebview<
EventName extends RPCEventName<s.RPCEvents_ClientWebview>,
>(
@@ -182,6 +236,15 @@ export class RPCInstanceClient extends RPCInstanceBase {
// ===== SELF =====
/**
* Listens for `emitSelf` calls in this environment (client -> client).
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onSelf('add', (a, b) => a + b)
*/
public onSelf<EventName extends RPCEventName<s.RPCEvents_Client>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_Client, EventName>,
@@ -189,12 +252,23 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this.listen(this._emitterLocal, 'onSelf', eventName, cb)
}
/** Removes the `onSelf` listener for `eventName` */
public offSelf<EventName extends RPCEventName<s.RPCEvents_Client>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterLocal, 'offSelf', eventName)
}
/**
* Calls this environment's own `onSelf` listener directly and resolves with
* its return value. No timeout; errors thrown by the listener reach the caller
* unchanged.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` if no `onSelf` listener exists
*
* @example
* const total = await rpc.emitSelf('add', 2, 3)
*/
public async emitSelf<EventName extends RPCEventName<s.RPCEvents_Client>>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_Client, EventName>
@@ -204,6 +278,15 @@ export class RPCInstanceClient extends RPCInstanceBase {
// ===== OTHER =====
/**
* Registers a chat command (FiveM `RegisterCommand`).
*
* @param cb - gets FiveM's `source`, the strings typed after the command and
* the full command line. Validate `args` yourself
*
* @example
* rpc.onCommand('coords', () => console.log(GetEntityCoords(PlayerPedId(), false)))
*/
public onCommand<CommandName extends RPCCommandName<s.RPCCommands_Client>>(
command: CommandName,
cb: (player: number, args: string[], rawCommand: string) => void,
@@ -215,6 +298,15 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this
}
/**
* Listens to a native FiveM client event, e.g. `entityDamaged`.
*
* @throws {@link RPCError} `UNKNOWN_NATIVE` if `eventName` is not in
* `NATIVE_CLIENT_EVENTS`. Register other events with FiveM's `on` directly
*
* @example
* rpc.onNativeEvent('entityDamaged', (victim, culprit) => console.log(victim))
*/
public onNativeEvent<EventName extends keyof RPCNativeClientEvents>(
eventName: EventName,
cb: (...args: Parameters<RPCNativeClientEvents[EventName]>) => void,
@@ -233,6 +325,16 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this
}
/**
* Listens to a native game event, e.g. `CEventShockingCarCrash`.
*
* @throws {@link RPCError} `UNKNOWN_NATIVE` if `eventName` is not in
* `NATIVE_CLIENT_NETWORK_EVENTS`. Register other events with FiveM's `on`
* directly
*
* @example
* rpc.onNativeNetworkEvent('CEventShockingCarCrash', (entities, eventEntity) => {})
*/
public onNativeNetworkEvent<
EventName extends keyof RPCNativeClientNetworkEvents,
>(
@@ -253,6 +355,15 @@ export class RPCInstanceClient extends RPCInstanceBase {
return this
}
/**
* Focuses this player's webview (FiveM `SetNuiFocus`).
*
* @param hasFocus - the webview receives keyboard input
* @param hasCursor - the mouse cursor is shown
*
* @example
* rpc.setWebviewFocus(true, true)
*/
public setWebviewFocus(hasFocus: boolean, hasCursor: boolean): this {
this.log(`setWebviewFocus ${hasFocus} ${hasCursor}`)
+107 -1
View File
@@ -20,6 +20,18 @@ import type {
} from '../utils/typing'
import { RPCInstanceBase } from './base'
/**
* RPC instance for server code, returned by `createRPC({ env: 'server' })`.
* Create one per server and import it from your own module.
*
* - `on*` registers the listener that answers calls from one direction. One
* listener per event: registering the same name again replaces it, `off*`
* removes it
* - `emit*` calls the listener on the target and resolves with its return
* value, or rejects with {@link RPCError}
* - listeners for client and webview calls get the calling player's server id
* first, taken from FiveM `source`
*/
export class RPCInstanceServer extends RPCInstanceBase {
private readonly _emitterClient: Emitter
private readonly _emitterWeb: Emitter
@@ -75,6 +87,16 @@ export class RPCInstanceServer extends RPCInstanceBase {
// ===== CLIENT =====
/**
* Listens for `emitServer` calls from clients (client -> server).
*
* @param cb - gets the calling player's server id first (from FiveM
* `source`, never from the payload), then the event arguments. Its return
* value (awaited) is sent back to the caller
*
* @example
* rpc.onClient('getMoney', (player, account) => getMoney(player, account))
*/
public onClient<EventName extends RPCEventName<s.RPCEvents_ClientServer>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_ClientServer, EventName, [player: number]>,
@@ -82,12 +104,24 @@ export class RPCInstanceServer extends RPCInstanceBase {
return this.listen(this._emitterClient, 'onClient', eventName, cb)
}
/** Removes the `onClient` listener for `eventName` */
public offClient<EventName extends RPCEventName<s.RPCEvents_ClientServer>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterClient, 'offClient', eventName)
}
/**
* Calls the `onServer` listener on one client (server -> client) and
* resolves with its return value.
*
* @param player - server id of the target player
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener),
* `HANDLER_ERROR` (the listener threw) or `TIMEOUT`
*
* @example
* const accepted = await rpc.emitClient(player, 'askTrade', offer)
*/
public async emitClient<
EventName extends RPCEventName<s.RPCEvents_ServerClient>,
>(
@@ -102,6 +136,14 @@ export class RPCInstanceServer extends RPCInstanceBase {
return this._pending.wait(payload, player)
}
/**
* Runs the `onServer` listener on every client (server -> all clients).
* One-way: resolves once sent, clients do not answer and their failures stay
* on the client.
*
* @example
* await rpc.emitClientEveryone('weatherChanged', 'RAIN')
*/
public async emitClientEveryone<
EventName extends RPCEventName<s.RPCEvents_ServerClient>,
>(
@@ -115,6 +157,17 @@ export class RPCInstanceServer extends RPCInstanceBase {
// ===== WEBVIEW =====
/**
* Listens for `emitServer` calls from webviews (webview -> server, relayed
* by the player's client).
*
* @param cb - gets the calling player's server id first (from FiveM
* `source`, never from the payload), then the event arguments. Its return
* value (awaited) is sent back to the caller
*
* @example
* rpc.onWebview('buyItem', (player, item) => shop.buy(player, item))
*/
public onWebview<EventName extends RPCEventName<s.RPCEvents_WebviewServer>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_WebviewServer, EventName, [player: number]>,
@@ -122,12 +175,24 @@ export class RPCInstanceServer extends RPCInstanceBase {
return this.listen(this._emitterWeb, 'onWebview', eventName, cb)
}
/** Removes the `onWebview` listener for `eventName` */
public offWebview<EventName extends RPCEventName<s.RPCEvents_WebviewServer>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterWeb, 'offWebview', eventName)
}
/**
* Calls the `onServer` listener in one player's webview (server -> webview,
* relayed by that player's client) and resolves with its return value.
*
* @param player - server id of the target player
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener),
* `HANDLER_ERROR` (the listener threw) or `TIMEOUT`
*
* @example
* const confirmed = await rpc.emitWebview(player, 'confirmPurchase', item)
*/
public async emitWebview<
EventName extends RPCEventName<s.RPCEvents_ServerWebview>,
>(
@@ -144,19 +209,39 @@ export class RPCInstanceServer extends RPCInstanceBase {
// ===== SELF =====
/**
* Listens for `emitSelf` calls in this environment (server -> server).
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onSelf('add', (a, b) => a + b)
*/
public onSelf<EventName extends RPCEventName<s.RPCEvents_Server>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_Server, EventName, [player: number]>,
cb: RPCListener<s.RPCEvents_Server, EventName>,
): this {
return this.listen(this._emitterLocal, 'onSelf', eventName, cb)
}
/** Removes the `onSelf` listener for `eventName` */
public offSelf<EventName extends RPCEventName<s.RPCEvents_Server>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterLocal, 'offSelf', eventName)
}
/**
* Calls this environment's own `onSelf` listener directly and resolves with
* its return value. No timeout; errors thrown by the listener reach the caller
* unchanged.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` if no `onSelf` listener exists
*
* @example
* const total = await rpc.emitSelf('add', 2, 3)
*/
public async emitSelf<EventName extends RPCEventName<s.RPCEvents_Server>>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_Server, EventName>
@@ -166,6 +251,18 @@ export class RPCInstanceServer extends RPCInstanceBase {
// ===== OTHER =====
/**
* Registers a chat command (FiveM `RegisterCommand`).
*
* @param cb - gets the player's server id (`0` for the server console), the
* strings typed after the command and the full command line. Validate
* `args` yourself
* @param restricted - only players with the ACE permission `command.<name>`
* can use it
*
* @example
* rpc.onCommand('heal', (player, args) => heal(player, Number(args[0])), true)
*/
public onCommand<CommandName extends RPCCommandName<s.RPCCommands_Server>>(
command: CommandName,
cb: (player: number, args: string[], rawCommand: string) => void,
@@ -178,6 +275,15 @@ export class RPCInstanceServer extends RPCInstanceBase {
return this
}
/**
* Listens to a native FiveM server event, e.g. `playerJoining`.
*
* @throws {@link RPCError} `UNKNOWN_NATIVE` if `eventName` is not in
* `NATIVE_SERVER_EVENTS`. Register other events with FiveM's `on` directly
*
* @example
* rpc.onNativeEvent('playerJoining', (source, oldId) => console.log(source))
*/
public onNativeEvent<EventName extends keyof RPCNativeServerEvents>(
eventName: EventName,
cb: (...args: Parameters<RPCNativeServerEvents[EventName]>) => void,
+75
View File
@@ -16,6 +16,18 @@ import type {
} from '../utils/typing'
import { RPCInstanceBase } from './base'
/**
* RPC instance for webview code, returned by `createRPC({ env: 'webview' })`.
* Create one per webview and import it from your own module.
*
* - `on*` registers the listener that answers calls from one direction. One
* listener per event: registering the same name again replaces it, `off*`
* removes it
* - `emit*` calls the listener on the target and resolves with its return
* value, or rejects with {@link RPCError}
* - calls to and from the server are relayed by the player's client, which
* must run `createRPC({ env: 'client' })`
*/
export class RPCInstanceWebview extends RPCInstanceBase {
private readonly _emitterClient: Emitter
private readonly _emitterServer: Emitter
@@ -69,6 +81,16 @@ export class RPCInstanceWebview extends RPCInstanceBase {
// ===== CLIENT =====
/**
* Listens for `emitWebview` calls from this player's client
* (client -> webview).
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onClient('openMenu', items => menu.open(items))
*/
public onClient<EventName extends RPCEventName<s.RPCEvents_ClientWebview>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_ClientWebview, EventName>,
@@ -76,12 +98,23 @@ export class RPCInstanceWebview extends RPCInstanceBase {
return this.listen(this._emitterClient, 'onClient', eventName, cb)
}
/** Removes the `onClient` listener for `eventName` */
public offClient<EventName extends RPCEventName<s.RPCEvents_ClientWebview>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterClient, 'offClient', eventName)
}
/**
* Calls the client's `onWebview` listener (webview -> client) and resolves
* with its return value.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener),
* `HANDLER_ERROR` (the listener threw) or `TIMEOUT`
*
* @example
* const position = await rpc.emitClient('getPosition')
*/
public async emitClient<
EventName extends RPCEventName<s.RPCEvents_WebviewClient>,
>(
@@ -95,6 +128,16 @@ export class RPCInstanceWebview extends RPCInstanceBase {
// ===== SERVER =====
/**
* Listens for `emitWebview` calls from the server (server -> webview,
* relayed by the client).
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onServer('confirmPurchase', item => window.confirm(`Buy ${item}?`))
*/
public onServer<EventName extends RPCEventName<s.RPCEvents_ServerWebview>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_ServerWebview, EventName>,
@@ -102,12 +145,24 @@ export class RPCInstanceWebview extends RPCInstanceBase {
return this.listen(this._emitterServer, 'onServer', eventName, cb)
}
/** Removes the `onServer` listener for `eventName` */
public offServer<EventName extends RPCEventName<s.RPCEvents_ServerWebview>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterServer, 'offServer', eventName)
}
/**
* Calls the server's `onWebview` listener (webview -> server, relayed by
* the client) and resolves with its return value. The server listener gets
* this player's id first.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` (no listener),
* `HANDLER_ERROR` (the listener threw) or `TIMEOUT`
*
* @example
* const bought = await rpc.emitServer('buyItem', 'water')
*/
public async emitServer<
EventName extends RPCEventName<s.RPCEvents_WebviewServer>,
>(
@@ -121,6 +176,15 @@ export class RPCInstanceWebview extends RPCInstanceBase {
// ===== SELF =====
/**
* Listens for `emitSelf` calls in this environment (webview -> webview).
*
* @param cb - gets the event arguments. Its return value (awaited) is sent
* back to the caller
*
* @example
* rpc.onSelf('add', (a, b) => a + b)
*/
public onSelf<EventName extends RPCEventName<s.RPCEvents_Webview>>(
eventName: EventName,
cb: RPCListener<s.RPCEvents_Webview, EventName>,
@@ -128,12 +192,23 @@ export class RPCInstanceWebview extends RPCInstanceBase {
return this.listen(this._emitterLocal, 'onSelf', eventName, cb)
}
/** Removes the `onSelf` listener for `eventName` */
public offSelf<EventName extends RPCEventName<s.RPCEvents_Webview>>(
eventName: EventName,
): this {
return this.unlisten(this._emitterLocal, 'offSelf', eventName)
}
/**
* Calls this environment's own `onSelf` listener directly and resolves with
* its return value. No timeout; errors thrown by the listener reach the caller
* unchanged.
*
* @throws {@link RPCError} `EVENT_NOT_REGISTERED` if no `onSelf` listener exists
*
* @example
* const total = await rpc.emitSelf('add', 2, 3)
*/
public async emitSelf<EventName extends RPCEventName<s.RPCEvents_Webview>>(
eventName: EventName,
...args: RPCEventArgs<s.RPCEvents_Webview, EventName>
+4
View File
@@ -12,7 +12,11 @@ import {
/**
* Creates the RPC instance for one environment. Create exactly one per
* environment (server, client, webview) and export it from a local module.
* Every client needs one, even without listeners: it relays calls between
* its webview and the server.
*
* @returns `RPCInstanceServer`, `RPCInstanceClient` or `RPCInstanceWebview`,
* matching `config.env`
* @throws {@link RPCError} `UNKNOWN_ENVIRONMENT` if `config.env` is not
* `'server'`, `'client'` or `'webview'`
*
+9 -2
View File
@@ -23,7 +23,10 @@ const serverEvents = {
weaponDamageEvent: true,
} satisfies Record<keyof RPCNativeServerEvents, true>
/** https://docs.fivem.net/docs/scripting-reference/events/server-events/ */
/**
* Events `onNativeEvent` accepts on the server:
* https://docs.fivem.net/docs/scripting-reference/events/server-events/
*/
export const NATIVE_SERVER_EVENTS = Object.keys(
serverEvents,
) as readonly (keyof RPCNativeServerEvents)[]
@@ -41,12 +44,16 @@ const clientEvents = {
populationPedCreating: true,
} satisfies Record<keyof RPCNativeClientEvents, true>
/** https://docs.fivem.net/docs/scripting-reference/events/client-events/ */
/**
* Events `onNativeEvent` accepts on the client:
* https://docs.fivem.net/docs/scripting-reference/events/client-events/
*/
export const NATIVE_CLIENT_EVENTS = Object.keys(
clientEvents,
) as readonly (keyof RPCNativeClientEvents)[]
/**
* Events `onNativeNetworkEvent` accepts on the client:
* https://docs.fivem.net/docs/game-references/game-events/
*
* Source of truth for `RPCNativeClientNetworkEventsNames`.
+15 -5
View File
@@ -4,7 +4,8 @@ import type { RPCInstanceWebview } from '../core/webview'
import type { NATIVE_CLIENT_NETWORK_EVENTS } from './native'
/**
* Possible environment states for `RPCConfig`
* Where an instance runs: `server` (server scripts), `client` (client
* scripts) or `webview` (NUI page)
*/
export type RPCEnvironment = 'server' | 'client' | 'webview'
@@ -99,18 +100,22 @@ export enum RPCEvents {
LISTENER_WEB = '__rpc:listenerWeb',
}
/**
* Errors to check against
*/
/** Values of `RPCError.code` */
export enum RPCErrors {
/** The target has no listener for the event (or `emitSelf` has no `onSelf`) */
EVENT_NOT_REGISTERED = 'Event not registered',
/** `onNative*` got an event that is not in its `NATIVE_*` list */
UNKNOWN_NATIVE = 'Unknown native event',
/** `createRPC` got an `env` other than server, client or webview */
UNKNOWN_ENVIRONMENT = 'Unknown environment',
/** No response within `RPCConfig.timeout` */
TIMEOUT = 'Timed out waiting for response',
/** The listener on the target threw; the message carries its error */
HANDLER_ERROR = 'Listener threw an error',
}
/**
* Native server events accepted by `onNativeEvent` on the server:
* https://docs.fivem.net/docs/scripting-reference/events/server-events/
*/
export type RPCNativeServerEvents = {
@@ -245,6 +250,7 @@ export type RPCNativeServerEvents = {
}
/**
* Native client events accepted by `onNativeEvent` on the client:
* https://docs.fivem.net/docs/scripting-reference/events/client-events/
*/
export type RPCNativeClientEvents = {
@@ -277,6 +283,7 @@ export type RPCNativeClientEvents = {
): void
}
/** Game events accepted by `onNativeNetworkEvent` on the client */
export type RPCNativeClientNetworkEvents = {
[name in RPCNativeClientNetworkEventsNames]: (
entities: number[],
@@ -285,6 +292,9 @@ export type RPCNativeClientNetworkEvents = {
) => void
}
/** https://docs.fivem.net/docs/game-references/game-events/ */
/**
* Names in `NATIVE_CLIENT_NETWORK_EVENTS`:
* https://docs.fivem.net/docs/game-references/game-events/
*/
export type RPCNativeClientNetworkEventsNames =
(typeof NATIVE_CLIENT_NETWORK_EVENTS)[number]