mirror of
https://github.com/rilaxik/fivem-rpc.git
synced 2026-09-28 01:29:13 +00:00
feat(rpc): extract _pending* maps to Pending util
This commit is contained in:
+6
-30
@@ -20,19 +20,13 @@ import { Wrapper } from './wrapper'
|
|||||||
|
|
||||||
export class RPCInstanceClient extends Wrapper {
|
export class RPCInstanceClient extends Wrapper {
|
||||||
private readonly _emitterServer: Emitter
|
private readonly _emitterServer: Emitter
|
||||||
private readonly _pendingServer: Emitter
|
|
||||||
private readonly _emitterWeb: Emitter
|
private readonly _emitterWeb: Emitter
|
||||||
private readonly _pendingWeb: Emitter
|
|
||||||
private readonly _pendingWebToServer: Emitter
|
|
||||||
|
|
||||||
constructor(props: RPCConfig<'client'>) {
|
constructor(props: RPCConfig<'client'>) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this._emitterServer = new Emitter()
|
this._emitterServer = new Emitter()
|
||||||
this._pendingServer = new Emitter()
|
|
||||||
this._emitterWeb = new Emitter()
|
this._emitterWeb = new Emitter()
|
||||||
this._pendingWeb = new Emitter()
|
|
||||||
this._pendingWebToServer = new Emitter()
|
|
||||||
|
|
||||||
this.console.log('[RPC] Initialized Client')
|
this.console.log('[RPC] Initialized Client')
|
||||||
|
|
||||||
@@ -93,17 +87,8 @@ export class RPCInstanceClient extends Wrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (payload.type === 'response') {
|
if (payload.type === 'response') {
|
||||||
if (payload.calledTo === 'client') {
|
if (payload.calledTo === 'client' || payload.calledTo === 'webview') {
|
||||||
await this._pendingServer.emit(
|
this.resolvePending(payload)
|
||||||
payload.uuid,
|
|
||||||
...(payload.data && payload.data.length > 0 ? payload.data : []),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (payload.calledTo === 'webview') {
|
|
||||||
await this._pendingWebToServer.emit(
|
|
||||||
payload.uuid,
|
|
||||||
...(payload.data && payload.data.length > 0 ? payload.data : []),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -126,18 +111,13 @@ export class RPCInstanceClient extends Wrapper {
|
|||||||
payload.player = GetPlayerServerId(PlayerId())
|
payload.player = GetPlayerServerId(PlayerId())
|
||||||
emitNet(RPCEvents.LISTENER_WEB, stringify(payload))
|
emitNet(RPCEvents.LISTENER_WEB, stringify(payload))
|
||||||
|
|
||||||
return new Promise(res => {
|
return this._pending.wait(payload.uuid)
|
||||||
this._pendingWebToServer.once(payload.uuid, res)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload.type === 'response') {
|
if (payload.type === 'response') {
|
||||||
if (payload.calledTo === 'client') {
|
if (payload.calledTo === 'client') {
|
||||||
await this._pendingWeb.emit(
|
this.resolvePending(payload)
|
||||||
payload.uuid,
|
|
||||||
...(payload.data && payload.data.length > 0 ? payload.data : []),
|
|
||||||
)
|
|
||||||
|
|
||||||
return { status: 'ok' }
|
return { status: 'ok' }
|
||||||
}
|
}
|
||||||
@@ -202,9 +182,7 @@ export class RPCInstanceClient extends Wrapper {
|
|||||||
|
|
||||||
emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload))
|
emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload))
|
||||||
|
|
||||||
return new Promise<Awaited<Response>>(res => {
|
return this._pending.wait<Awaited<Response>>(payload.uuid)
|
||||||
this._pendingServer.once(payload.uuid, res)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== WEBVIEW =====
|
// ===== WEBVIEW =====
|
||||||
@@ -261,9 +239,7 @@ export class RPCInstanceClient extends Wrapper {
|
|||||||
data: payload,
|
data: payload,
|
||||||
})
|
})
|
||||||
|
|
||||||
return new Promise<Awaited<Response>>(res => {
|
return this._pending.wait<Awaited<Response>>(payload.uuid)
|
||||||
this._pendingWeb.once(payload.uuid, res)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SELF =====
|
// ===== SELF =====
|
||||||
|
|||||||
+4
-18
@@ -15,17 +15,13 @@ import { Wrapper } from './wrapper'
|
|||||||
|
|
||||||
export class RPCInstanceServer extends Wrapper {
|
export class RPCInstanceServer extends Wrapper {
|
||||||
private readonly _emitterClient: Emitter
|
private readonly _emitterClient: Emitter
|
||||||
private readonly _pendingClient: Emitter
|
|
||||||
private readonly _emitterWeb: Emitter
|
private readonly _emitterWeb: Emitter
|
||||||
private readonly _pendingWeb: Emitter
|
|
||||||
|
|
||||||
constructor(props: RPCConfig<'server'>) {
|
constructor(props: RPCConfig<'server'>) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
this._emitterClient = new Emitter()
|
this._emitterClient = new Emitter()
|
||||||
this._pendingClient = new Emitter()
|
|
||||||
this._emitterWeb = new Emitter()
|
this._emitterWeb = new Emitter()
|
||||||
this._pendingWeb = new Emitter()
|
|
||||||
|
|
||||||
this.console.log('[RPC] Initialized Server')
|
this.console.log('[RPC] Initialized Server')
|
||||||
|
|
||||||
@@ -78,10 +74,7 @@ export class RPCInstanceServer extends Wrapper {
|
|||||||
emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response))
|
emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response))
|
||||||
}
|
}
|
||||||
if (payload.type === 'response') {
|
if (payload.type === 'response') {
|
||||||
await this._pendingClient.emit(
|
this.resolvePending(payload)
|
||||||
payload.uuid,
|
|
||||||
...(payload.data && payload.data.length > 0 ? payload.data : []),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,10 +122,7 @@ export class RPCInstanceServer extends Wrapper {
|
|||||||
emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response))
|
emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response))
|
||||||
}
|
}
|
||||||
if (payload.type === 'response') {
|
if (payload.type === 'response') {
|
||||||
await this._pendingWeb.emit(
|
this.resolvePending(payload)
|
||||||
payload.uuid,
|
|
||||||
...(payload.data && payload.data.length > 0 ? payload.data : []),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -193,9 +183,7 @@ export class RPCInstanceServer extends Wrapper {
|
|||||||
|
|
||||||
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
|
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
|
||||||
|
|
||||||
return new Promise<Awaited<Response>>(res => {
|
return this._pending.wait<Awaited<Response>>(payload.uuid)
|
||||||
this._pendingClient.once(payload.uuid, res)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async emitClientEveryone<
|
public async emitClientEveryone<
|
||||||
@@ -272,9 +260,7 @@ export class RPCInstanceServer extends Wrapper {
|
|||||||
|
|
||||||
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
|
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
|
||||||
|
|
||||||
return new Promise<Awaited<Response>>(res => {
|
return this._pending.wait<Awaited<Response>>(payload.uuid)
|
||||||
this._pendingWeb.once(payload.uuid, res)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SELF =====
|
// ===== SELF =====
|
||||||
|
|||||||
+12
-2
@@ -144,7 +144,7 @@ export class RPCInstanceWebview extends Wrapper {
|
|||||||
type: 'event',
|
type: 'event',
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this._createHttpClientRequest<Awaited<Response>>(payload)
|
return this._request<Awaited<Response>>(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SERVER =====
|
// ===== SERVER =====
|
||||||
@@ -196,7 +196,7 @@ export class RPCInstanceWebview extends Wrapper {
|
|||||||
type: 'event',
|
type: 'event',
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this._createHttpClientRequest<Awaited<Response>>(payload)
|
return this._request<Awaited<Response>>(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ===== SELF =====
|
// ===== SELF =====
|
||||||
@@ -264,6 +264,16 @@ export class RPCInstanceWebview extends Wrapper {
|
|||||||
|
|
||||||
// ===== UTILS =====
|
// ===== UTILS =====
|
||||||
|
|
||||||
|
/** Sends an event to the client and waits for its response (with timeout) */
|
||||||
|
private _request<R>(payload: RPCState): Promise<R> {
|
||||||
|
const response = this._pending.wait<R>(payload.uuid)
|
||||||
|
this._createHttpClientRequest(payload).then(
|
||||||
|
data => this._pending.resolve(payload.uuid, data),
|
||||||
|
(error: Error) => this._pending.reject(payload.uuid, error),
|
||||||
|
)
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
private async _createHttpClientRequest<R>(
|
private async _createHttpClientRequest<R>(
|
||||||
data: RPCStateRaw | RPCState,
|
data: RPCStateRaw | RPCState,
|
||||||
): Promise<R> {
|
): Promise<R> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { Emitter } from '../utils/emitter'
|
import { Emitter } from '../utils/emitter'
|
||||||
import { parse } from '../utils/funcs'
|
import { parse } from '../utils/funcs'
|
||||||
|
import { Pending } from '../utils/pending'
|
||||||
import {
|
import {
|
||||||
type RPCConfig,
|
type RPCConfig,
|
||||||
type RPCEnvironment,
|
type RPCEnvironment,
|
||||||
@@ -11,16 +12,28 @@ import {
|
|||||||
export class Wrapper {
|
export class Wrapper {
|
||||||
protected env: RPCEnvironment
|
protected env: RPCEnvironment
|
||||||
protected _emitterLocal: Emitter
|
protected _emitterLocal: Emitter
|
||||||
|
protected _pending: Pending
|
||||||
protected debug: boolean
|
protected debug: boolean
|
||||||
protected console: Console
|
protected console: Console
|
||||||
|
|
||||||
constructor(cfg: RPCConfig<RPCEnvironment>) {
|
constructor(cfg: RPCConfig<RPCEnvironment>) {
|
||||||
this.env = cfg.env
|
this.env = cfg.env
|
||||||
this._emitterLocal = new Emitter()
|
this._emitterLocal = new Emitter()
|
||||||
|
this._pending = new Pending(cfg.timeout ?? 5000)
|
||||||
this.debug = cfg.debug ?? false
|
this.debug = cfg.debug ?? false
|
||||||
this.console = console
|
this.console = console
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Settles the call waiting for this response; ignores late or unexpected ones */
|
||||||
|
protected resolvePending(payload: RPCState): void {
|
||||||
|
const found = this._pending.resolve(payload.uuid, payload.data?.[0])
|
||||||
|
if (!found && this.debug) {
|
||||||
|
this.console.log(
|
||||||
|
`[RPC]:ignored response ${payload.event} ${payload.uuid} (no pending call, possibly timed out)`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected verifyEvent(state: Emitter, data: RPCStateRaw | RPCState) {
|
protected verifyEvent(state: Emitter, data: RPCStateRaw | RPCState) {
|
||||||
const rpcData = typeof data === 'string' ? parse(data) : data
|
const rpcData = typeof data === 'string' ? parse(data) : data
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { RPCErrors } from './types'
|
||||||
|
|
||||||
|
type Call = {
|
||||||
|
resolve: (data: unknown) => void
|
||||||
|
reject: (error: Error) => void
|
||||||
|
timer: ReturnType<typeof setTimeout> | undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calls waiting for a response, keyed by payload uuid. */
|
||||||
|
export class Pending {
|
||||||
|
private _calls = new Map<string, Call>()
|
||||||
|
|
||||||
|
/** @param timeout - ms before a call rejects, `0` or less disables it */
|
||||||
|
constructor(private readonly _timeout: number) {}
|
||||||
|
|
||||||
|
public wait<R>(uuid: string): Promise<R> {
|
||||||
|
return new Promise<R>((resolve, reject) => {
|
||||||
|
const timer =
|
||||||
|
this._timeout > 0
|
||||||
|
? setTimeout(
|
||||||
|
() => this.reject(uuid, new Error(RPCErrors.TIMEOUT)),
|
||||||
|
this._timeout,
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
|
||||||
|
this._calls.set(uuid, {
|
||||||
|
resolve: resolve as (data: unknown) => void,
|
||||||
|
reject,
|
||||||
|
timer,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns `false` if no call waits for this uuid (late or unexpected response) */
|
||||||
|
public resolve(uuid: string, data: unknown): boolean {
|
||||||
|
const call = this._take(uuid)
|
||||||
|
call?.resolve(data)
|
||||||
|
return call !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns `false` if no call waits for this uuid */
|
||||||
|
public reject(uuid: string, error: Error): boolean {
|
||||||
|
const call = this._take(uuid)
|
||||||
|
call?.reject(error)
|
||||||
|
return call !== undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
private _take(uuid: string): Call | undefined {
|
||||||
|
const call = this._calls.get(uuid)
|
||||||
|
if (call) {
|
||||||
|
this._calls.delete(uuid)
|
||||||
|
clearTimeout(call.timer)
|
||||||
|
}
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,6 +24,13 @@ export type RPCEnvironmentResolved<T extends RPCEnvironment> =
|
|||||||
export type RPCConfig<T extends RPCEnvironment | unknown> = {
|
export type RPCConfig<T extends RPCEnvironment | unknown> = {
|
||||||
env: T
|
env: T
|
||||||
debug?: boolean
|
debug?: boolean
|
||||||
|
/**
|
||||||
|
* Milliseconds to wait for a response before the call rejects with
|
||||||
|
* `RPCErrors.TIMEOUT`. `0` disables the timeout.
|
||||||
|
*
|
||||||
|
* @defaultValue 5000
|
||||||
|
*/
|
||||||
|
timeout?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
/** **Internal** */
|
/** **Internal** */
|
||||||
@@ -85,6 +92,7 @@ export enum RPCErrors {
|
|||||||
NO_PLAYER = 'No player (failed to resolve from local index)',
|
NO_PLAYER = 'No player (failed to resolve from local index)',
|
||||||
UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)',
|
UNKNOWN_NATIVE = 'Unknown native event (if you are sure this exists - use native handler)',
|
||||||
UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")',
|
UNKNOWN_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")',
|
||||||
|
TIMEOUT = 'Timed out waiting for response',
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user