This commit is contained in:
2025-07-08 22:27:10 +01:00
commit 821cd589d9
23 changed files with 2434 additions and 0 deletions
+402
View File
@@ -0,0 +1,402 @@
/// <reference types="@citizenfx/client" />
import type * as s from '@entityseven/fivem-rpc-shared-types'
import { Emitter } from '../utils/emitter'
import { generateUUID, parse, stringify, stringifyWeb } from '../utils/funcs'
import {
NATIVE_CLIENT_EVENTS,
NATIVE_CLIENT_NETWORK_EVENTS,
} from '../utils/native'
import {
type RPCConfig,
RPCErrors,
RPCEvents,
type RPCNativeClientEvents,
type RPCNativeClientNetworksEvents,
type RPCState,
type RPCStateRaw,
type RPCStateWeb,
} from '../utils/types'
import { Wrapper } from './wrapper'
export class RPCInstanceClient extends Wrapper {
private readonly _emitterServer: Emitter
private readonly _pendingServer: Emitter
private readonly _emitterWeb: Emitter
private readonly _pendingWeb: Emitter
private readonly _pendingWebToServer: Emitter
constructor(props: RPCConfig<'client'>) {
super(props)
this._emitterServer = new Emitter()
this._pendingServer = new Emitter()
this._emitterWeb = new Emitter()
this._pendingWeb = new Emitter()
this._pendingWebToServer = new Emitter()
this.console.log('[RPC] Initialized Client')
onNet(RPCEvents.LISTENER_SERVER, this._handleServer.bind(this))
RegisterNuiCallbackType(RPCEvents.LISTENER_WEB)
on(
`__cfx_nui:${RPCEvents.LISTENER_WEB}`,
async (data: RPCState, callback: (res: unknown) => void) => {
const res = await this._handleWeb(data)
callback(res)
},
)
}
// ===== HANDLERS =====
private async _handleServer(payloadRaw: RPCStateRaw) {
try {
parse(payloadRaw)
} catch (e) {
throw new Error(RPCErrors.INVALID_DATA)
}
const payload = parse(payloadRaw)
if (this.debug) {
this.console.log(
`[RPC]:client:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`,
)
}
if (payload.type === 'event') {
if (payload.calledTo === 'client') {
this.verifyEvent(this._emitterServer, payload)
const responseData = await this._emitterServer.emit(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
const response: RPCState = {
event: payload.event,
uuid: payload.uuid,
calledFrom: 'client',
calledTo: 'server',
error: null,
data: [responseData],
player: payload.player,
type: 'response',
}
emitNet(RPCEvents.LISTENER_CLIENT, stringify(response))
}
if (payload.calledTo === 'webview') {
this._sendWebMessage({
origin: RPCEvents.LISTENER_SERVER,
data: payload,
})
}
}
if (payload.type === 'response') {
if (payload.calledTo === 'client') {
await this._pendingServer.emit(
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 : []),
)
}
}
}
private async _handleWeb(payload: RPCState): Promise<unknown> {
if (this.debug) {
this.console.log(
`[RPC]:client:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`,
)
}
if (payload.type === 'event') {
if (payload.calledTo === 'client') {
return await this._emitterWeb.emit(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
}
if (payload.calledTo === 'server') {
payload.player = GetPlayerServerId(PlayerId())
emitNet(RPCEvents.LISTENER_WEB, stringify(payload))
return new Promise(res => {
this._pendingWebToServer.once(payload.uuid, res)
})
}
}
if (payload.type === 'response') {
if (payload.calledTo === 'client') {
await this._pendingWeb.emit(
payload.uuid,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
return { status: 'ok' }
}
if (payload.calledTo === 'server') {
payload.player = GetPlayerServerId(PlayerId())
emitNet(RPCEvents.LISTENER_WEB, stringify(payload))
return { status: 'ok' }
}
}
return { status: 'unknown' }
}
// ===== SERVER =====
public onServer<
EventName extends keyof s.RPCEvents_ServerClient,
CallbackArguments extends Parameters<s.RPCEvents_ServerClient[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ServerClient[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onServer ${eventName}`)
}
this._emitterServer.on(eventName, cb)
return this
}
public offServer<EventName extends keyof s.RPCEvents_ServerClient>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offServer ${eventName}`)
}
this._emitterServer.off(eventName)
return this
}
public async emitServer<
EventName extends keyof s.RPCEvents_ClientServer,
Arguments extends Parameters<s.RPCEvents_ClientServer[EventName]>,
Response extends ReturnType<s.RPCEvents_ClientServer[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'client',
calledTo: 'server',
error: null,
data: args.length ? args : null,
player: GetPlayerServerId(PlayerId()),
type: 'event',
}
emitNet(RPCEvents.LISTENER_CLIENT, stringify(payload))
return new Promise<Awaited<Response>>(res => {
this._pendingServer.once(payload.uuid, res)
})
}
// ===== WEBVIEW =====
public onWebview<
EventName extends keyof s.RPCEvents_WebviewClient,
CallbackArguments extends Parameters<s.RPCEvents_WebviewClient[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_WebviewClient[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onWebview ${eventName}`)
}
this._emitterWeb.on(eventName, cb)
return this
}
public offWebview<EventName extends keyof s.RPCEvents_WebviewClient>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offWebview ${eventName}`)
}
this._emitterWeb.off(eventName)
return this
}
public async emitWebview<
EventName extends keyof s.RPCEvents_ClientWebview,
Arguments extends Parameters<s.RPCEvents_ClientWebview[EventName]>,
Response extends ReturnType<s.RPCEvents_ClientWebview[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'client',
calledTo: 'webview',
error: null,
data: args.length ? args : null,
player: PlayerId(),
type: 'event',
}
this._sendWebMessage({
origin: RPCEvents.LISTENER_CLIENT,
data: payload,
})
return new Promise<Awaited<Response>>(res => {
this._pendingWeb.once(payload.uuid, res)
})
}
// ===== SELF =====
public onSelf<
EventName extends keyof s.RPCEvents_Client,
CallbackArguments extends Parameters<s.RPCEvents_Client[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_Client[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onSelf ${eventName}`)
}
this._emitterLocal.on(eventName, cb)
return this
}
public offSelf<EventName extends keyof s.RPCEvents_Client>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offSelf ${eventName}`)
}
this._emitterLocal.off(eventName)
return this
}
public async emitSelf<
EventName extends keyof s.RPCEvents_Client,
Arguments extends Parameters<s.RPCEvents_Client[EventName]>,
Response extends ReturnType<s.RPCEvents_Client[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'client',
calledTo: 'client',
error: null,
data: args.length ? args : null,
player: null,
type: 'event',
}
if (this.debug) {
this.console.log(
`[RPC]:accepted ${payload.event} from ${payload.calledFrom}`,
)
}
this.verifyEvent(this._emitterLocal, payload)
return await this._emitterLocal.emit<Awaited<Response>>(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
}
// ===== OTHER =====
public onCommand<
CommandName extends s.RPCCommands_Client,
CallbackArguments extends unknown[],
>(
command: CommandName,
cb: (player: number, args: CallbackArguments, commandRaw: string) => void,
): this {
if (this.debug) {
this.console.log(`[RPC]:onCommand ${command}`)
}
RegisterCommand(command, cb, false)
return this
}
public onNativeEvent<
EventName extends keyof RPCNativeClientEvents,
CallbackArguments extends Parameters<RPCNativeClientEvents[EventName]>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
if (!NATIVE_CLIENT_EVENTS.includes(eventName)) {
throw new Error(RPCErrors.UNKNOWN_NATIVE)
}
if (this.debug) {
this.console.log(`[RPC]:onNativeEvent ${eventName}`)
}
on(eventName, cb)
return this
}
public onNativeNetworkEvent<
EventName extends keyof RPCNativeClientNetworksEvents,
CallbackArguments extends Parameters<
RPCNativeClientNetworksEvents[EventName]
>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
if (!NATIVE_CLIENT_NETWORK_EVENTS.includes(eventName)) {
throw new Error(RPCErrors.UNKNOWN_NATIVE)
}
if (this.debug) {
this.console.log(`[RPC]:onNativeNetworkEvent ${eventName}`)
}
on(eventName, cb)
return this
}
public setWebviewFocus(hasFocus: boolean, hasCursor: boolean): this {
if (this.debug) {
this.console.log(`[RPC]:setWebviewFocus ${hasFocus} ${hasCursor}`)
}
SetNuiFocus(hasFocus, hasCursor)
return this
}
// ===== UTILS =====
private _sendWebMessage(payload: RPCStateWeb): void {
SendNuiMessage(stringifyWeb(payload))
}
}
+379
View File
@@ -0,0 +1,379 @@
/// <reference types="@citizenfx/server" />
import type * as s from '@entityseven/fivem-rpc-shared-types'
import { Emitter } from '../utils/emitter'
import { generateUUID, parse, stringify } from '../utils/funcs'
import { NATIVE_SERVER_EVENTS } from '../utils/native'
import {
type RPCConfig,
RPCErrors,
RPCEvents,
type RPCNativeServerEvents,
type RPCState,
type RPCStateRaw,
} from '../utils/types'
import { Wrapper } from './wrapper'
export class RPCInstanceServer extends Wrapper {
private readonly _emitterClient: Emitter
private readonly _pendingClient: Emitter
private readonly _emitterWeb: Emitter
private readonly _pendingWeb: Emitter
constructor(props: RPCConfig<'server'>) {
super(props)
this._emitterClient = new Emitter()
this._pendingClient = new Emitter()
this._emitterWeb = new Emitter()
this._pendingWeb = new Emitter()
this.console.log('[RPC] Initialized Server')
onNet(RPCEvents.LISTENER_CLIENT, this._handleClient.bind(this))
onNet(RPCEvents.LISTENER_WEB, this._handleWeb.bind(this))
}
// ===== HANDLERS =====
private async _handleClient(payloadRaw: RPCStateRaw) {
try {
parse(payloadRaw)
} catch (e) {
throw new Error(RPCErrors.INVALID_DATA)
}
const payload = parse(payloadRaw)
if (this.debug) {
this.console.log(
`[RPC]:server:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`,
)
}
if (payload.calledFrom === 'client') {
if (payload.type === 'event') {
this.verifyEvent(this._emitterClient, payload)
if (payload.player === null || payload.player === -1) {
payload.error = RPCErrors.NO_PLAYER
this.triggerError(payload)
return
}
const responseData = await this._emitterClient.emit(
payload.event,
payload.player,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
const response: RPCState = {
event: payload.event,
uuid: payload.uuid,
calledFrom: 'server',
calledTo: 'client',
error: null,
data: [responseData],
player: payload.player,
type: 'response',
}
emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response))
}
if (payload.type === 'response') {
await this._pendingClient.emit(
payload.uuid,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
}
}
}
private async _handleWeb(payloadRaw: RPCStateRaw) {
try {
parse(payloadRaw)
} catch (e) {
throw new Error(RPCErrors.INVALID_DATA)
}
const payload = parse(payloadRaw)
if (this.debug) {
this.console.log(
`[RPC]:server:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`,
)
}
if (payload.calledFrom === 'webview') {
if (payload.type === 'event') {
this.verifyEvent(this._emitterWeb, payload)
if (payload.player === null || payload.player === -1) {
payload.error = RPCErrors.NO_PLAYER
this.triggerError(payload)
return
}
const responseData = await this._emitterWeb.emit(
payload.event,
payload.player,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
const response: RPCState = {
event: payload.event,
uuid: payload.uuid,
calledFrom: 'server',
calledTo: 'webview',
error: null,
data: [responseData],
player: payload.player,
type: 'response',
}
emitNet(RPCEvents.LISTENER_SERVER, response.player, stringify(response))
}
if (payload.type === 'response') {
await this._pendingWeb.emit(
payload.uuid,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
}
}
}
// ===== CLIENT =====
public onClient<
EventName extends keyof s.RPCEvents_ClientServer,
CallbackArguments extends Parameters<s.RPCEvents_ClientServer[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ClientServer[EventName]>,
>(
eventName: EventName,
cb: (
player: number,
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onClient ${eventName}`)
}
this._emitterClient.on(eventName, cb)
return this
}
public offClient<EventName extends keyof s.RPCEvents_ClientServer>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offClient ${eventName}`)
}
this._emitterClient.off(eventName)
return this
}
public async emitClient<
EventName extends keyof s.RPCEvents_ServerClient,
Arguments extends Parameters<s.RPCEvents_ServerClient[EventName]>,
Response extends ReturnType<s.RPCEvents_ServerClient[EventName]>,
>(
player: number,
eventName: EventName,
...args: Arguments
): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'server',
calledTo: 'client',
error: null,
data: args.length ? args : null,
player: player,
type: 'event',
}
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
return new Promise<Awaited<Response>>(res => {
this._pendingClient.once(payload.uuid, res)
})
}
public async emitClientEveryone<
EventName extends keyof s.RPCEvents_ServerClient,
Arguments extends Parameters<s.RPCEvents_ServerClient[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<void> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'server',
calledTo: 'client',
error: null,
data: args.length ? args : null,
player: -1,
type: 'event',
}
emitNet(RPCEvents.LISTENER_SERVER, -1, stringify(payload))
}
// ===== WEBVIEW =====
public onWebview<
EventName extends keyof s.RPCEvents_WebviewServer,
CallbackArguments extends Parameters<s.RPCEvents_WebviewServer[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_WebviewServer[EventName]>,
>(
eventName: EventName,
cb: (
player: number,
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onWebview ${eventName}`)
}
this._emitterWeb.on(eventName, cb)
return this
}
public offWebview<EventName extends keyof s.RPCEvents_WebviewServer>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offWebview ${eventName}`)
}
this._emitterWeb.off(eventName)
return this
}
public async emitWebview<
EventName extends keyof s.RPCEvents_ServerWebview,
Arguments extends Parameters<s.RPCEvents_ServerWebview[EventName]>,
Response extends ReturnType<s.RPCEvents_ServerWebview[EventName]>,
>(
player: number,
eventName: EventName,
...args: Arguments
): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'server',
calledTo: 'webview',
error: null,
data: args.length ? args : null,
player: player,
type: 'event',
}
emitNet(RPCEvents.LISTENER_SERVER, player, stringify(payload))
return new Promise<Awaited<Response>>(res => {
this._pendingWeb.once(payload.uuid, res)
})
}
// ===== SELF =====
public onSelf<
EventName extends keyof s.RPCEvents_Server,
CallbackArguments extends Parameters<s.RPCEvents_Server[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_Server[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onSelf ${eventName}`)
}
this._emitterLocal.on(eventName, cb)
return this
}
public offSelf<EventName extends keyof s.RPCEvents_Server>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offSelf ${eventName}`)
}
this._emitterLocal.off(eventName)
return this
}
public async emitSelf<
EventName extends keyof s.RPCEvents_Server,
Arguments extends Parameters<s.RPCEvents_Server[EventName]>,
Response extends ReturnType<s.RPCEvents_Server[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'server',
calledTo: 'server',
error: null,
data: args.length ? args : null,
player: null,
type: 'event',
}
if (this.debug) {
this.console.log(
`[RPC]:accepted ${payload.event} from ${payload.calledFrom}`,
)
}
this.verifyEvent(this._emitterLocal, payload)
return await this._emitterLocal.emit<Awaited<Response>>(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
}
// ===== OTHER =====
public onCommand<
CommandName extends s.RPCCommands_Server,
CallbackArguments extends unknown[],
>(
command: CommandName,
cb: (player: number, args: CallbackArguments, commandRaw: string) => void,
restricted = false,
): this {
if (this.debug) {
this.console.log(`[RPC]:onCommand ${command}`)
}
RegisterCommand(command, cb, restricted)
return this
}
public onNativeEvent<
EventName extends keyof RPCNativeServerEvents,
CallbackArguments extends Parameters<RPCNativeServerEvents[EventName]>,
>(eventName: EventName, cb: (...args: CallbackArguments) => void): this {
if (!NATIVE_SERVER_EVENTS.includes(eventName)) {
throw new Error(RPCErrors.UNKNOWN_NATIVE)
}
if (this.debug) {
this.console.log(`[RPC]:onNativeEvent ${eventName}`)
}
on(eventName, cb)
return this
}
}
+289
View File
@@ -0,0 +1,289 @@
import type * as s from '@entityseven/fivem-rpc-shared-types'
import { Emitter } from '../utils/emitter'
import { generateUUID, stringify } from '../utils/funcs'
import {
RPCEvents,
type RPCConfig,
type RPCState,
type RPCStateRaw,
type RPCStateWeb,
} from '../utils/types'
import { Wrapper } from './wrapper'
declare global {
interface Window {
GetParentResourceName?: () => string
}
}
export class RPCInstanceWebview extends Wrapper {
private readonly _emitterClient: Emitter
private readonly _emitterServer: Emitter
constructor(props: RPCConfig<'webview'>) {
super(props)
this._emitterClient = new Emitter()
this._emitterServer = new Emitter()
this.console.log('[RPC] Initialized Webview')
window.addEventListener('message', (e: MessageEvent<RPCStateWeb>) => {
if (e.data.origin === RPCEvents.LISTENER_CLIENT) {
this._handleClient(e.data.data)
}
if (e.data.origin === RPCEvents.LISTENER_SERVER) {
this._handleServer(e.data.data)
}
})
}
// ===== HANDLERS =====
private async _handleClient(payload: RPCState) {
if (this.debug) {
this.console.log(
`[RPC]:webview:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`,
)
}
if (payload.calledFrom === 'client' && payload.type === 'event') {
this.verifyEvent(this._emitterClient, payload)
const responseData = await this._emitterClient.emit(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
const response: RPCState = {
event: payload.event,
uuid: payload.uuid,
calledFrom: 'webview',
calledTo: 'client',
error: null,
data: [responseData],
player: payload.player,
type: 'response',
}
await this._createHttpClientRequest(response).then()
}
}
private async _handleServer(payload: RPCState) {
if (this.debug) {
this.console.log(
`[RPC]:webview:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`,
)
}
if (payload.calledFrom === 'server' && payload.type === 'event') {
this.verifyEvent(this._emitterServer, payload)
const responseData = await this._emitterServer.emit(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
const response: RPCState = {
event: payload.event,
uuid: payload.uuid,
calledFrom: 'webview',
calledTo: 'server',
error: null,
data: [responseData],
player: payload.player,
type: 'response',
}
await this._createHttpClientRequest(response)
}
}
// ===== CLIENT =====
public onClient<
EventName extends keyof s.RPCEvents_ClientWebview,
CallbackArguments extends Parameters<s.RPCEvents_ClientWebview[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ClientWebview[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onClient ${eventName}`)
}
this._emitterClient.on(eventName, cb)
return this
}
public offClient<EventName extends keyof s.RPCEvents_ClientWebview>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offClient ${eventName}`)
}
this._emitterClient.off(eventName)
return this
}
public async emitClient<
EventName extends keyof s.RPCEvents_WebviewClient,
Arguments extends Parameters<s.RPCEvents_WebviewClient[EventName]>,
Response extends ReturnType<s.RPCEvents_WebviewClient[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'webview',
calledTo: 'client',
error: null,
data: args.length ? args : null,
player: null,
type: 'event',
}
return await this._createHttpClientRequest<Awaited<Response>>(payload)
}
// ===== SERVER =====
public onServer<
EventName extends keyof s.RPCEvents_ServerWebview,
CallbackArguments extends Parameters<s.RPCEvents_ServerWebview[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_ServerWebview[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onServer ${eventName}`)
}
this._emitterServer.on(eventName, cb)
return this
}
public offServer<EventName extends keyof s.RPCEvents_ServerWebview>(
eventName: EventName,
): RPCInstanceWebview {
if (this.debug) {
this.console.log(`[RPC]:offServer ${eventName}`)
}
this._emitterServer.off(eventName)
return this
}
public async emitServer<
EventName extends keyof s.RPCEvents_WebviewServer,
Arguments extends Parameters<s.RPCEvents_WebviewServer[EventName]>,
Response extends ReturnType<s.RPCEvents_WebviewServer[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'webview',
calledTo: 'server',
error: null,
data: args.length ? args : null,
player: null,
type: 'event',
}
return await this._createHttpClientRequest<Awaited<Response>>(payload)
}
// ===== SELF =====
public onSelf<
EventName extends keyof s.RPCEvents_Webview,
CallbackArguments extends Parameters<s.RPCEvents_Webview[EventName]>,
CallbackReturn extends ReturnType<s.RPCEvents_Webview[EventName]>,
>(
eventName: EventName,
cb: (
...args: CallbackArguments
) => Awaited<CallbackReturn> | Promise<Awaited<CallbackReturn>>,
): this {
if (this.debug) {
this.console.log(`[RPC]:onSelf ${eventName}`)
}
this._emitterLocal.on(eventName, cb)
return this
}
public offSelf<EventName extends keyof s.RPCEvents_Webview>(
eventName: EventName,
): this {
if (this.debug) {
this.console.log(`[RPC]:offSelf ${eventName}`)
}
this._emitterLocal.off(eventName)
return this
}
public async emitSelf<
EventName extends keyof s.RPCEvents_Webview,
Arguments extends Parameters<s.RPCEvents_Webview[EventName]>,
Response extends ReturnType<s.RPCEvents_Webview[EventName]>,
>(eventName: EventName, ...args: Arguments): Promise<Awaited<Response>> {
const payload: RPCState = {
event: eventName,
uuid: generateUUID(),
calledFrom: 'webview',
calledTo: 'webview',
error: null,
data: args.length ? args : null,
player: null,
type: 'event',
}
if (this.debug) {
this.console.log(
`[RPC]:accepted ${payload.event} from ${payload.calledFrom}`,
)
}
this.verifyEvent(this._emitterLocal, payload)
return await this._emitterLocal.emit<Awaited<Response>>(
payload.event,
...(payload.data && payload.data.length > 0 ? payload.data : []),
)
}
// ===== UTILS =====
private async _createHttpClientRequest<R>(
data: RPCStateRaw | RPCState,
): Promise<R> {
const dataRaw = typeof data === 'string' ? data : stringify(data)
const options = {
method: 'post',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
body: dataRaw,
}
const resourceName = window?.GetParentResourceName?.() ?? 'nui-frame-app'
return fetch(
`https://${resourceName}/${RPCEvents.LISTENER_WEB}`,
options,
).then(res => res.json())
}
}
+51
View File
@@ -0,0 +1,51 @@
import { Emitter } from '../utils/emitter'
import { parse } from '../utils/funcs'
import {
type RPCConfig,
type RPCEnvironment,
RPCErrors,
type RPCState,
type RPCStateRaw,
} from '../utils/types'
export class Wrapper {
protected env: RPCEnvironment
protected _emitterLocal: Emitter
protected debug: boolean
protected console: Console
constructor(cfg: RPCConfig<RPCEnvironment>) {
this.env = cfg.env
this._emitterLocal = new Emitter()
this.debug = cfg.debug ?? false
this.console = console
}
protected verifyEvent(state: Emitter, data: RPCStateRaw | RPCState) {
const rpcData = typeof data === 'string' ? parse(data) : data
if (!state.has(rpcData.event)) {
rpcData.error = RPCErrors.EVENT_NOT_REGISTERED
this.triggerError(rpcData)
}
}
protected triggerError(rpcData: RPCState, error?: string): Error {
const errorMessage = [
`${rpcData.error}`,
`Event: ${rpcData.event}`,
`Uuid: ${rpcData.uuid}`,
`From: ${rpcData.calledFrom}`,
`To: ${rpcData.calledTo}`,
`Player: ${rpcData.player}`,
`Type: ${rpcData.type}`,
`Data: ${rpcData.data}`,
]
if (error) {
errorMessage.push(`Info: ${error}`)
}
throw new Error(errorMessage.join('\n | '))
}
}
+74
View File
@@ -0,0 +1,74 @@
import { RPCInstanceClient } from './core/client'
import { RPCInstanceServer } from './core/server'
import { RPCInstanceWebview } from './core/webview'
import { Wrapper } from './core/wrapper'
import {
type RPCConfig,
type RPCEnvironment,
type RPCEnvironmentResolved,
RPCErrors,
} from './utils/types'
/**
* RPC Factory
*
* @example
* // returns RPCInstanceServer
* const rpc = new RPCFactory({ env: "server" }).get()
*
* @example
* // returns RPCInstanceClient
* const rpc = new RPCFactory({ env: "client" }).get()
*
* @example
* // returns RPCInstanceWebview
* const rpc = new RPCFactory({ env: "webview" }).get()
*
* @class
*/
class RPCFactory<T extends RPCEnvironment> extends Wrapper {
private readonly operator:
| RPCInstanceServer
| RPCInstanceClient
| RPCInstanceWebview
/**
* Instance options
* @param {object} opts - Options
* @param {string} opts.env - Instance environment
* @param {boolean} opts.debug - Show additional logs
*/
constructor(opts: RPCConfig<T>) {
super(opts)
this.console.log('[RPC] Initializing...')
switch (opts.env) {
case 'server':
this.operator = new RPCInstanceServer(opts as RPCConfig<'server'>)
break
case 'client':
this.operator = new RPCInstanceClient(opts as RPCConfig<'client'>)
break
case 'webview':
this.operator = new RPCInstanceWebview(opts as RPCConfig<'webview'>)
break
default:
throw new Error(RPCErrors.UNKNOWN_ENVIRONMENT)
}
}
public get(): RPCEnvironmentResolved<T> {
return this.operator as RPCEnvironmentResolved<T>
}
}
export { RPCFactory }
// export const rpcClient = new RPCFactory({ env: "client" }).get();
// export const rpcServer = new RPCFactory({ env: "server" }).get();
// export const rpcWebview = new RPCFactory({ env: "webview" }).get();
export * from './utils/types'
export * from './utils/native'
export type * from './core/server'
export type * from './core/client'
export type * from './core/webview'
+54
View File
@@ -0,0 +1,54 @@
import { RPCErrors } from './types'
export class Emitter {
/** Map<event, [callback function, once]> */
private _storage: Map<string, [(...args: any[]) => any, boolean]>
constructor() {
this._storage = new Map()
}
get _raw_storage() {
return this._storage
}
public on(event: string, cb: (...args: any[]) => any): this {
this._storage.set(event, [cb, false])
return this
}
public once(event: string, cb: (...args: any[]) => any): this {
this._storage.set(event, [cb, true])
return this
}
public off(event: string): this {
this._storage.delete(event)
return this
}
public has(event: string): boolean {
return this._storage.has(event)
}
public async emit<R>(event: string, ...args: any[]): Promise<R> {
return new Promise((res, rej) => {
if (!this._storage.has(event)) {
rej(RPCErrors.EVENT_NOT_REGISTERED)
}
const [cb, once] = this._storage.get(event) as [
(...args: any[]) => any,
boolean,
]
if (once) {
this._storage.delete(event)
}
Promise.resolve(cb(...args))
.then(res)
.catch(rej)
})
}
}
+50
View File
@@ -0,0 +1,50 @@
import type {
RPCState,
RPCStateRaw,
RPCStateWeb,
RPCStateWebRaw,
} from './types'
/**
* **Internal**
*
* Typed data parser
*/
export function parse(data: RPCStateRaw): RPCState {
return JSON.parse(data)
}
/**
* **Internal**
*
* Typed data serializer
*/
export function stringify(data: RPCState): RPCStateRaw {
return JSON.stringify(data) as RPCStateRaw
}
// automatically parsed by FiveM
// export function parseWeb(data: RPCStateWebRaw): RPCStateWeb {
// return JSON.parse(data)
// }
/**
* **Internal**
*
* Typed data serializer
*/
export function stringifyWeb(data: RPCStateWeb): RPCStateWebRaw {
return JSON.stringify(data) as RPCStateWebRaw
}
/** **Internal** */
export function generateUUID(): string {
let uuid = ''
let random = 0
for (let i = 0; i < 32; i++) {
random = (Math.random() * 16) | 0
if (i === 8 || i === 12 || i === 16 || i === 20) uuid += '-'
uuid += (i === 12 ? 4 : i === 16 ? (random & 3) | 8 : random).toString(16)
}
return uuid
}
+321
View File
@@ -0,0 +1,321 @@
import type {
RPCNativeClientEvents,
RPCNativeClientNetworkEventsNames,
RPCNativeServerEvents,
} from './types'
/**
* https://docs.fivem.net/docs/scripting-reference/events/server-events/
* @readonly
*/
export const NATIVE_SERVER_EVENTS: readonly (keyof RPCNativeServerEvents)[] = [
'entityCreated',
'entityCreating',
'entityRemoved',
'onResourceListRefresh',
'onResourceStart',
'onResourceStarting',
'onResourceStop',
'onServerResourceStart',
'onServerResourceStop',
'playerConnecting',
'playerEnteredScope',
'playerJoining',
'playerLeftScope',
'ptFxEvent',
'removeAllWeaponsEvent',
'startProjectileEvent',
'weaponDamageEvent',
] as const
/**
* https://docs.fivem.net/docs/scripting-reference/events/client-events/
* @readonly
*/
export const NATIVE_CLIENT_EVENTS: readonly (keyof RPCNativeClientEvents)[] = [
'entityDamaged',
'gameEventTriggered',
'mumbleConnected',
'mumbleDisconnected',
'onClientResourceStart',
'onClientResourceStop',
'onResourceStart',
'onResourceStarting',
'onResourceStop',
'populationPedCreating',
] as const
/**
* https://docs.fivem.net/docs/game-references/game-events/
* @readonly
*/
export const NATIVE_CLIENT_NETWORK_EVENTS: readonly RPCNativeClientNetworkEventsNames[] =
[
'CEventAcquaintancePed',
'CEventAcquaintancePedDead',
'CEventAcquaintancePedDislike',
'CEventAcquaintancePedHate',
'CEventAcquaintancePedLike',
'CEventAcquaintancePedWanted',
'CEventAgitated',
'CEventAgitatedAction',
'CEventCallForCover',
'CEventCarUndriveable',
'CEventClimbLadderOnRoute',
'CEventClimbNavMeshOnRoute',
'CEventCombatTaunt',
'CEventCommunicateEvent',
'CEventCopCarBeingStolen',
'CEventCrimeCryForHelp',
'CEventCrimeReported',
'CEventDamage',
'CEventDataDecisionMaker',
'CEventDataFileMounter',
'CEventDataResponseAggressiveRubberneck',
'CEventDataResponseDeferToScenarioPointFlags',
'CEventDataResponseFriendlyAimedAt',
'CEventDataResponseFriendlyNearMiss',
'CEventDataResponsePlayerDeath',
'CEventDataResponsePoliceTaskWanted',
'CEventDataResponseSwatTaskWanted',
'CEventDataResponseTask',
'CEventDataResponseTaskAgitated',
'CEventDataResponseTaskCombat',
'CEventDataResponseTaskCower',
'CEventDataResponseTaskCrouch',
'CEventDataResponseTaskDuckAndCover',
'CEventDataResponseTaskEscapeBlast',
'CEventDataResponseTaskEvasiveStep',
'CEventDataResponseTaskExhaustedFlee',
'CEventDataResponseTaskExplosion',
'CEventDataResponseTaskFlee',
'CEventDataResponseTaskFlyAway',
'CEventDataResponseTaskGrowlAndFlee',
'CEventDataResponseTaskGunAimedAt',
'CEventDataResponseTaskHandsUp',
'CEventDataResponseTaskHeadTrack',
'CEventDataResponseTaskLeaveCarAndFlee',
'CEventDataResponseTaskScenarioFlee',
'CEventDataResponseTaskSharkAttack',
'CEventDataResponseTaskShockingEventBackAway',
'CEventDataResponseTaskShockingEventGoto',
'CEventDataResponseTaskShockingEventHurryAway',
'CEventDataResponseTaskShockingEventReact',
'CEventDataResponseTaskShockingEventReactToAircraft',
'CEventDataResponseTaskShockingEventStopAndStare',
'CEventDataResponseTaskShockingEventThreatResponse',
'CEventDataResponseTaskShockingEventWatch',
'CEventDataResponseTaskShockingNiceCar',
'CEventDataResponseTaskShockingPoliceInvestigate',
'CEventDataResponseTaskThreat',
'CEventDataResponseTaskTurnToFace',
'CEventDataResponseTaskWalkAway',
'CEventDataResponseTaskWalkRoundEntity',
'CEventDataResponseTaskWalkRoundFire',
'CEventDeadPedFound',
'CEventDeath',
'CEventDecisionMakerResponse',
'CEventDisturbance',
'CEventDraggedOutCar',
'CEventEditableResponse',
'CEventEncroachingPed',
'CEventEntityDamaged',
'CEventEntityDestroyed',
'CEventExplosion',
'CEventExplosionHeard',
'CEventFireNearby',
'CEventFootStepHeard',
'CEventFriendlyAimedAt',
'CEventFriendlyFireNearMiss',
'CEventGetOutOfWater',
'CEventGivePedTask',
'CEventGroupScriptAI',
'CEventGroupScriptNetwork',
'CEventGunAimedAt',
'CEventGunShot',
'CEventGunShotBulletImpact',
'CEventGunShotWhizzedBy',
'CEventHelpAmbientFriend',
'CEventHurtTransition',
'CEventInAir',
'CEventInfo',
'CEventInfoBase',
'CEventInjuredCryForHelp',
'CEventLeaderEnteredCarAsDriver',
'CEventLeaderExitedCarAsDriver',
'CEventLeaderHolsteredWeapon',
'CEventLeaderLeftCover',
'CEventLeaderUnholsteredWeapon',
'CEventMeleeAction',
'CEventMustLeaveBoat',
'CEventNetworkAdminInvited',
'CEventNetworkAttemptHostMigration',
'CEventNetworkBail',
'CEventNetworkCashTransactionLog',
'CEventNetworkCheatTriggered',
'CEventNetworkClanInviteReceived',
'CEventNetworkClanJoined',
'CEventNetworkClanKicked',
'CEventNetworkClanLeft',
'CEventNetworkClanRankChanged',
'CEventNetworkCloudEvent',
'CEventNetworkCloudFileResponse',
'CEventNetworkEmailReceivedEvent',
'CEventNetworkEndMatch',
'CEventNetworkEndSession',
'CEventNetworkEntityDamage',
'CEventNetworkFindSession',
'CEventNetworkFollowInviteReceived',
'CEventNetworkHostMigration',
'CEventNetworkHostSession',
'CEventNetworkIncrementStat',
'CEventNetworkInviteAccepted',
'CEventNetworkInviteConfirmed',
'CEventNetworkInviteRejected',
'CEventNetworkJoinSession',
'CEventNetworkJoinSessionResponse',
'CEventNetworkOnlinePermissionsUpdated',
'CEventNetworkPedLeftBehind',
'CEventNetworkPickupRespawned',
'CEventNetworkPlayerArrest',
'CEventNetworkPlayerCollectedAmbientPickup',
'CEventNetworkPlayerCollectedPickup',
'CEventNetworkPlayerCollectedPortablePickup',
'CEventNetworkPlayerDroppedPortablePickup',
'CEventNetworkPlayerEnteredVehicle',
'CEventNetworkPlayerJoinScript',
'CEventNetworkPlayerLeftScript',
'CEventNetworkPlayerScript',
'CEventNetworkPlayerSession',
'CEventNetworkPlayerSpawn',
'CEventNetworkPresenceInvite',
'CEventNetworkPresenceInviteRemoved',
'CEventNetworkPresenceInviteReply',
'CEventNetworkPresenceTriggerEvent',
'CEventNetworkPresence_StatUpdate',
'CEventNetworkPrimaryClanChanged',
'CEventNetworkRequestDelay',
'CEventNetworkRosChanged',
'CEventNetworkScAdminPlayerUpdated',
'CEventNetworkScAdminReceivedCash',
'CEventNetworkScriptEvent',
'CEventNetworkSessionEvent',
'CEventNetworkShopTransaction',
'CEventNetworkSignInStateChanged',
'CEventNetworkSocialClubAccountLinked',
'CEventNetworkSpectateLocal',
'CEventNetworkStartMatch',
'CEventNetworkStartSession',
'CEventNetworkStorePlayerLeft',
'CEventNetworkSummon',
'CEventNetworkSystemServiceEvent',
'CEventNetworkTextMessageReceived',
'CEventNetworkTimedExplosion',
'CEventNetworkTransitionEvent',
'CEventNetworkTransitionGamerInstruction',
'CEventNetworkTransitionMemberJoined',
'CEventNetworkTransitionMemberLeft',
'CEventNetworkTransitionParameterChanged',
'CEventNetworkTransitionStarted',
'CEventNetworkTransitionStringChanged',
'CEventNetworkVehicleUndrivable',
'CEventNetworkVoiceConnectionRequested',
'CEventNetworkVoiceConnectionResponse',
'CEventNetworkVoiceConnectionTerminated',
'CEventNetworkVoiceSessionEnded',
'CEventNetworkVoiceSessionStarted',
'CEventNetworkWithData',
'CEventNetwork_InboxMsgReceived',
'CEventNewTask',
'CEventObjectCollision',
'CEventOnFire',
'CEventOpenDoor',
'CEventPedCollisionWithPed',
'CEventPedCollisionWithPlayer',
'CEventPedEnteredMyVehicle',
'CEventPedJackingMyVehicle',
'CEventPedOnCarRoof',
'CEventPedSeenDeadPed',
'CEventPlayerCollisionWithPed',
'CEventPlayerDeath',
'CEventPlayerUnableToEnterVehicle',
'CEventPotentialBeWalkedInto',
'CEventPotentialBlast',
'CEventPotentialGetRunOver',
'CEventPotentialWalkIntoVehicle',
'CEventProvidingCover',
'CEventRanOverPed',
'CEventReactionEnemyPed',
'CEventReactionInvestigateDeadPed',
'CEventReactionInvestigateThreat',
'CEventRequestHelp',
'CEventRequestHelpWithConfrontation',
'CEventRespondedToThreat',
'CEventScanner',
'CEventScenarioForceAction',
'CEventScriptCommand',
'CEventScriptWithData',
'CEventShocking',
'CEventShockingBicycleCrash',
'CEventShockingBicycleOnPavement',
'CEventShockingCarAlarm',
'CEventShockingCarChase',
'CEventShockingCarCrash',
'CEventShockingCarOnCar',
'CEventShockingCarPileUp',
'CEventShockingDangerousAnimal',
'CEventShockingDeadBody',
'CEventShockingDrivingOnPavement',
'CEventShockingEngineRevved',
'CEventShockingExplosion',
'CEventShockingFire',
'CEventShockingGunFight',
'CEventShockingGunshotFired',
'CEventShockingHelicopterOverhead',
'CEventShockingHornSounded',
'CEventShockingInDangerousVehicle',
'CEventShockingInjuredPed',
'CEventShockingMadDriver',
'CEventShockingMadDriverBicycle',
'CEventShockingMadDriverExtreme',
'CEventShockingMugging',
'CEventShockingNonViolentWeaponAimedAt',
'CEventShockingParachuterOverhead',
'CEventShockingPedKnockedIntoByPlayer',
'CEventShockingPedRunOver',
'CEventShockingPedShot',
'CEventShockingPlaneFlyby',
'CEventShockingPotentialBlast',
'CEventShockingPropertyDamage',
'CEventShockingRunningPed',
'CEventShockingRunningStampede',
'CEventShockingSeenCarStolen',
'CEventShockingSeenConfrontation',
'CEventShockingSeenGangFight',
'CEventShockingSeenInsult',
'CEventShockingSeenMeleeAction',
'CEventShockingSeenNiceCar',
'CEventShockingSeenPedKilled',
'CEventShockingSiren',
'CEventShockingStudioBomb',
'CEventShockingVehicleTowed',
'CEventShockingVisibleWeapon',
'CEventShockingWeaponThreat',
'CEventShockingWeirdPed',
'CEventShockingWeirdPedApproaching',
'CEventShoutBlockingLos',
'CEventShoutTargetPosition',
'CEventShovePed',
'CEventSoundBase',
'CEventStatChangedValue',
'CEventStaticCountReachedMax',
'CEventStuckInAir',
'CEventSuspiciousActivity',
'CEventSwitch2NM',
'CEventUnidentifiedPed',
'CEventVehicleCollision',
'CEventVehicleDamage',
'CEventVehicleDamageWeapon',
'CEventVehicleOnFire',
'CEventWrithe',
] as const
+535
View File
@@ -0,0 +1,535 @@
import type { RPCInstanceClient } from '../core/client'
import type { RPCInstanceServer } from '../core/server'
import type { RPCInstanceWebview } from '../core/webview'
/**
* Possible environment states for `RPCConfig`
*/
export type RPCEnvironment = 'server' | 'client' | 'webview'
export type RPCEnvironmentResolved<T extends RPCEnvironment> =
T extends 'server'
? RPCInstanceServer
: T extends 'client'
? RPCInstanceClient
: T extends 'webview'
? RPCInstanceWebview
: never
/**
* `RPCFactory` config.
*
* If environment does not match will throw `RPCErrors.UNKNOWN_ENVIRONMENT`
*/
export type RPCConfig<T extends RPCEnvironment | unknown> = {
env: T
debug?: boolean
}
/** **Internal** */
export type RPCEventType = 'event' | 'response'
/**
* **Internal**
*
* Similar to what Errors look like
*/
export type RPCState = {
event: string
uuid: string
calledFrom: RPCEnvironment
calledTo: RPCEnvironment
error: string | null
data: unknown[] | null
player: number | null
type: RPCEventType
}
/**
* **Internal**
*
* `JSON.stringify` version of `RPCState`. Makes TS think this is not type `string` for better dx
*/
export type RPCStateRaw = string & { __brand: 'RPCStateRaw' }
/** Internal */
export type RPCStateWeb = {
origin: RPCEvents
data: RPCState
}
/**
* **Internal**
*
* `JSON.stringify` version of `RPCStateWeb`. Makes TS think this is not type `string` for better dx
*/
export type RPCStateWebRaw = string & { __brand: 'RPCWebStateRaw' }
/**
* **Internal**
*
* Do not create same listeners to avoid unexpected behaviour
*/
export enum RPCEvents {
LISTENER_SERVER = '__rpc:listenerServer',
LISTENER_CLIENT = '__rpc:listenerClient',
LISTENER_WEB = '__rpc:listenerWeb',
}
/**
* Errors to check against
*/
export enum RPCErrors {
EVENT_NOT_REGISTERED = 'Event not registered',
INVALID_DATA = 'Invalid data (possibly broken JSON)',
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_ENVIRONMENT = 'Unknown environment (must be either "server", "client" or "webview")',
}
/**
* https://docs.fivem.net/docs/scripting-reference/events/server-events/
*/
export type RPCNativeServerEvents = {
entityCreated(handle: number): void
entityCreating(handle: number): void
entityRemoved(entity: number): void
onResourceListRefresh(): void
onResourceStart(resource: string): void
onResourceStarting(resource: string): void
onResourceStop(resource: string): void
onServerResourceStart(resource: string): void
onServerResourceStop(resource: string): void
playerConnecting(
playerName: string,
setKickReason: (reason: string) => void,
deferrals: {
defer: () => void
done: (failureReason?: string) => void
handover: (data: Record<string, unknown>) => void
presentCard: (
card: string | object,
cb?: (data: unknown, rawData: string) => void,
) => void
update: (message: string) => void
},
source: number,
): void
playerEnteredScope(data: { for: string; player: string }): void
playerJoining(source: string, oldID: string): void
playerLeftScope(data: { for: string; player: string }): void
ptFxEvent(
sender: number,
data: {
assetHash: number
axisBitset: number
effectHash: number
entityNetId: number
f100: number
f105: number
f106: number
f107: number
f109: boolean
f110: boolean
f111: boolean
f92: number
isOnEntity: boolean
offsetX: number
offsetY: number
offsetZ: number
posX: number
posY: number
posZ: number
rotX: number
rotY: number
rotZ: number
scale: number
},
): void
removeAllWeaponsEvent(sender: number, data: { pedId: number }): void
startProjectileEvent(
sender: number,
data: {
commandFireSingleBullet: boolean
effectGroup: number
firePositionX: number
firePositionY: number
firePositionZ: number
initialPositionX: number
initialPositionY: number
initialPositionZ: number
ownerId: number
projectileHash: number
targetEntity: number
throwTaskSequence: number
unk10: number
unk11: number
unk12: number
unk13: number
unk14: number
unk15: number
unk16: number
unk3: number
unk4: number
unk5: number
unk6: number
unk7: number
unk9: number
unkX8: number
unkY8: number
unkZ8: number
weaponHash: number
},
): void
weaponDamageEvent(
sender: number,
data: {
actionResultId: number
actionResultName: number
damageFlags: number
damageTime: number
damageType: number
f104: number
f112: boolean
f112_1: number
f120: number
f133: boolean
hasActionResult: boolean
hasImpactDir: boolean
hasVehicleData: boolean
hitComponent: number
hitEntityWeapon: boolean
hitGlobalId: number
hitGlobalIds: number[]
hitWeaponAmmoAttachment: boolean
impactDirX: number
impactDirY: number
impactDirZ: number
isNetTargetPos: boolean
localPosX: number
localPosY: number
localPosZ: number
overrideDefaultDamage: boolean
parentGlobalId: number
silenced: boolean
suspensionIndex: number
tyreIndex: number
weaponDamage: number
weaponType: number
willKill: boolean
},
): void
}
/**
* https://docs.fivem.net/docs/scripting-reference/events/client-events/
*/
export type RPCNativeClientEvents = {
entityDamaged(
victim: number,
culprit: number,
weapon: number,
baseDamage: number,
): void
gameEventTriggered(
name: RPCNativeClientNetworksEvents | string,
data: number[],
): void
mumbleConnected(address: string, reconnecting: boolean): void
mumbleDisconnected(address: string): void
onClientResourceStart(resource: string): void
onClientResourceStop(resource: string): void
onResourceStart(resource: string): void
onResourceStarting(resource: string): void
onResourceStop(resource: string): void
populationPedCreating(
x: number,
y: number,
z: number,
model: number,
overrideCalls: {
setModel: (model: string | number) => void
setPosition: (x: number, y: number, z: number) => void
},
): void
}
export type RPCNativeClientNetworksEvents = {
[name in RPCNativeClientNetworkEventsNames]: (
entities: number[],
eventEntity: number,
data: unknown[],
) => void
}
/**
* https://docs.fivem.net/docs/game-references/game-events/
*/
export type RPCNativeClientNetworkEventsNames =
| 'CEventAcquaintancePed'
| 'CEventAcquaintancePedDead'
| 'CEventAcquaintancePedDislike'
| 'CEventAcquaintancePedHate'
| 'CEventAcquaintancePedLike'
| 'CEventAcquaintancePedWanted'
| 'CEventAgitated'
| 'CEventAgitatedAction'
| 'CEventCallForCover'
| 'CEventCarUndriveable'
| 'CEventClimbLadderOnRoute'
| 'CEventClimbNavMeshOnRoute'
| 'CEventCombatTaunt'
| 'CEventCommunicateEvent'
| 'CEventCopCarBeingStolen'
| 'CEventCrimeCryForHelp'
| 'CEventCrimeReported'
| 'CEventDamage'
| 'CEventDataDecisionMaker'
| 'CEventDataFileMounter'
| 'CEventDataResponseAggressiveRubberneck'
| 'CEventDataResponseDeferToScenarioPointFlags'
| 'CEventDataResponseFriendlyAimedAt'
| 'CEventDataResponseFriendlyNearMiss'
| 'CEventDataResponsePlayerDeath'
| 'CEventDataResponsePoliceTaskWanted'
| 'CEventDataResponseSwatTaskWanted'
| 'CEventDataResponseTask'
| 'CEventDataResponseTaskAgitated'
| 'CEventDataResponseTaskCombat'
| 'CEventDataResponseTaskCower'
| 'CEventDataResponseTaskCrouch'
| 'CEventDataResponseTaskDuckAndCover'
| 'CEventDataResponseTaskEscapeBlast'
| 'CEventDataResponseTaskEvasiveStep'
| 'CEventDataResponseTaskExhaustedFlee'
| 'CEventDataResponseTaskExplosion'
| 'CEventDataResponseTaskFlee'
| 'CEventDataResponseTaskFlyAway'
| 'CEventDataResponseTaskGrowlAndFlee'
| 'CEventDataResponseTaskGunAimedAt'
| 'CEventDataResponseTaskHandsUp'
| 'CEventDataResponseTaskHeadTrack'
| 'CEventDataResponseTaskLeaveCarAndFlee'
| 'CEventDataResponseTaskScenarioFlee'
| 'CEventDataResponseTaskSharkAttack'
| 'CEventDataResponseTaskShockingEventBackAway'
| 'CEventDataResponseTaskShockingEventGoto'
| 'CEventDataResponseTaskShockingEventHurryAway'
| 'CEventDataResponseTaskShockingEventReact'
| 'CEventDataResponseTaskShockingEventReactToAircraft'
| 'CEventDataResponseTaskShockingEventStopAndStare'
| 'CEventDataResponseTaskShockingEventThreatResponse'
| 'CEventDataResponseTaskShockingEventWatch'
| 'CEventDataResponseTaskShockingNiceCar'
| 'CEventDataResponseTaskShockingPoliceInvestigate'
| 'CEventDataResponseTaskThreat'
| 'CEventDataResponseTaskTurnToFace'
| 'CEventDataResponseTaskWalkAway'
| 'CEventDataResponseTaskWalkRoundEntity'
| 'CEventDataResponseTaskWalkRoundFire'
| 'CEventDeadPedFound'
| 'CEventDeath'
| 'CEventDecisionMakerResponse'
| 'CEventDisturbance'
| 'CEventDraggedOutCar'
| 'CEventEditableResponse'
| 'CEventEncroachingPed'
| 'CEventEntityDamaged'
| 'CEventEntityDestroyed'
| 'CEventExplosion'
| 'CEventExplosionHeard'
| 'CEventFireNearby'
| 'CEventFootStepHeard'
| 'CEventFriendlyAimedAt'
| 'CEventFriendlyFireNearMiss'
| 'CEventGetOutOfWater'
| 'CEventGivePedTask'
| 'CEventGroupScriptAI'
| 'CEventGroupScriptNetwork'
| 'CEventGunAimedAt'
| 'CEventGunShot'
| 'CEventGunShotBulletImpact'
| 'CEventGunShotWhizzedBy'
| 'CEventHelpAmbientFriend'
| 'CEventHurtTransition'
| 'CEventInAir'
| 'CEventInfo'
| 'CEventInfoBase'
| 'CEventInjuredCryForHelp'
| 'CEventLeaderEnteredCarAsDriver'
| 'CEventLeaderExitedCarAsDriver'
| 'CEventLeaderHolsteredWeapon'
| 'CEventLeaderLeftCover'
| 'CEventLeaderUnholsteredWeapon'
| 'CEventMeleeAction'
| 'CEventMustLeaveBoat'
| 'CEventNetworkAdminInvited'
| 'CEventNetworkAttemptHostMigration'
| 'CEventNetworkBail'
| 'CEventNetworkCashTransactionLog'
| 'CEventNetworkCheatTriggered'
| 'CEventNetworkClanInviteReceived'
| 'CEventNetworkClanJoined'
| 'CEventNetworkClanKicked'
| 'CEventNetworkClanLeft'
| 'CEventNetworkClanRankChanged'
| 'CEventNetworkCloudEvent'
| 'CEventNetworkCloudFileResponse'
| 'CEventNetworkEmailReceivedEvent'
| 'CEventNetworkEndMatch'
| 'CEventNetworkEndSession'
| 'CEventNetworkEntityDamage'
| 'CEventNetworkFindSession'
| 'CEventNetworkFollowInviteReceived'
| 'CEventNetworkHostMigration'
| 'CEventNetworkHostSession'
| 'CEventNetworkIncrementStat'
| 'CEventNetworkInviteAccepted'
| 'CEventNetworkInviteConfirmed'
| 'CEventNetworkInviteRejected'
| 'CEventNetworkJoinSession'
| 'CEventNetworkJoinSessionResponse'
| 'CEventNetworkOnlinePermissionsUpdated'
| 'CEventNetworkPedLeftBehind'
| 'CEventNetworkPickupRespawned'
| 'CEventNetworkPlayerArrest'
| 'CEventNetworkPlayerCollectedAmbientPickup'
| 'CEventNetworkPlayerCollectedPickup'
| 'CEventNetworkPlayerCollectedPortablePickup'
| 'CEventNetworkPlayerDroppedPortablePickup'
| 'CEventNetworkPlayerEnteredVehicle'
| 'CEventNetworkPlayerJoinScript'
| 'CEventNetworkPlayerLeftScript'
| 'CEventNetworkPlayerScript'
| 'CEventNetworkPlayerSession'
| 'CEventNetworkPlayerSpawn'
| 'CEventNetworkPresenceInvite'
| 'CEventNetworkPresenceInviteRemoved'
| 'CEventNetworkPresenceInviteReply'
| 'CEventNetworkPresenceTriggerEvent'
| 'CEventNetworkPresence_StatUpdate'
| 'CEventNetworkPrimaryClanChanged'
| 'CEventNetworkRequestDelay'
| 'CEventNetworkRosChanged'
| 'CEventNetworkScAdminPlayerUpdated'
| 'CEventNetworkScAdminReceivedCash'
| 'CEventNetworkScriptEvent'
| 'CEventNetworkSessionEvent'
| 'CEventNetworkShopTransaction'
| 'CEventNetworkSignInStateChanged'
| 'CEventNetworkSocialClubAccountLinked'
| 'CEventNetworkSpectateLocal'
| 'CEventNetworkStartMatch'
| 'CEventNetworkStartSession'
| 'CEventNetworkStorePlayerLeft'
| 'CEventNetworkSummon'
| 'CEventNetworkSystemServiceEvent'
| 'CEventNetworkTextMessageReceived'
| 'CEventNetworkTimedExplosion'
| 'CEventNetworkTransitionEvent'
| 'CEventNetworkTransitionGamerInstruction'
| 'CEventNetworkTransitionMemberJoined'
| 'CEventNetworkTransitionMemberLeft'
| 'CEventNetworkTransitionParameterChanged'
| 'CEventNetworkTransitionStarted'
| 'CEventNetworkTransitionStringChanged'
| 'CEventNetworkVehicleUndrivable'
| 'CEventNetworkVoiceConnectionRequested'
| 'CEventNetworkVoiceConnectionResponse'
| 'CEventNetworkVoiceConnectionTerminated'
| 'CEventNetworkVoiceSessionEnded'
| 'CEventNetworkVoiceSessionStarted'
| 'CEventNetworkWithData'
| 'CEventNetwork_InboxMsgReceived'
| 'CEventNewTask'
| 'CEventObjectCollision'
| 'CEventOnFire'
| 'CEventOpenDoor'
| 'CEventPedCollisionWithPed'
| 'CEventPedCollisionWithPlayer'
| 'CEventPedEnteredMyVehicle'
| 'CEventPedJackingMyVehicle'
| 'CEventPedOnCarRoof'
| 'CEventPedSeenDeadPed'
| 'CEventPlayerCollisionWithPed'
| 'CEventPlayerDeath'
| 'CEventPlayerUnableToEnterVehicle'
| 'CEventPotentialBeWalkedInto'
| 'CEventPotentialBlast'
| 'CEventPotentialGetRunOver'
| 'CEventPotentialWalkIntoVehicle'
| 'CEventProvidingCover'
| 'CEventRanOverPed'
| 'CEventReactionEnemyPed'
| 'CEventReactionInvestigateDeadPed'
| 'CEventReactionInvestigateThreat'
| 'CEventRequestHelp'
| 'CEventRequestHelpWithConfrontation'
| 'CEventRespondedToThreat'
| 'CEventScanner'
| 'CEventScenarioForceAction'
| 'CEventScriptCommand'
| 'CEventScriptWithData'
| 'CEventShocking'
| 'CEventShockingBicycleCrash'
| 'CEventShockingBicycleOnPavement'
| 'CEventShockingCarAlarm'
| 'CEventShockingCarChase'
| 'CEventShockingCarCrash'
| 'CEventShockingCarOnCar'
| 'CEventShockingCarPileUp'
| 'CEventShockingDangerousAnimal'
| 'CEventShockingDeadBody'
| 'CEventShockingDrivingOnPavement'
| 'CEventShockingEngineRevved'
| 'CEventShockingExplosion'
| 'CEventShockingFire'
| 'CEventShockingGunFight'
| 'CEventShockingGunshotFired'
| 'CEventShockingHelicopterOverhead'
| 'CEventShockingHornSounded'
| 'CEventShockingInDangerousVehicle'
| 'CEventShockingInjuredPed'
| 'CEventShockingMadDriver'
| 'CEventShockingMadDriverBicycle'
| 'CEventShockingMadDriverExtreme'
| 'CEventShockingMugging'
| 'CEventShockingNonViolentWeaponAimedAt'
| 'CEventShockingParachuterOverhead'
| 'CEventShockingPedKnockedIntoByPlayer'
| 'CEventShockingPedRunOver'
| 'CEventShockingPedShot'
| 'CEventShockingPlaneFlyby'
| 'CEventShockingPotentialBlast'
| 'CEventShockingPropertyDamage'
| 'CEventShockingRunningPed'
| 'CEventShockingRunningStampede'
| 'CEventShockingSeenCarStolen'
| 'CEventShockingSeenConfrontation'
| 'CEventShockingSeenGangFight'
| 'CEventShockingSeenInsult'
| 'CEventShockingSeenMeleeAction'
| 'CEventShockingSeenNiceCar'
| 'CEventShockingSeenPedKilled'
| 'CEventShockingSiren'
| 'CEventShockingStudioBomb'
| 'CEventShockingVehicleTowed'
| 'CEventShockingVisibleWeapon'
| 'CEventShockingWeaponThreat'
| 'CEventShockingWeirdPed'
| 'CEventShockingWeirdPedApproaching'
| 'CEventShoutBlockingLos'
| 'CEventShoutTargetPosition'
| 'CEventShovePed'
| 'CEventSoundBase'
| 'CEventStatChangedValue'
| 'CEventStaticCountReachedMax'
| 'CEventStuckInAir'
| 'CEventSuspiciousActivity'
| 'CEventSwitch2NM'
| 'CEventUnidentifiedPed'
| 'CEventVehicleCollision'
| 'CEventVehicleDamage'
| 'CEventVehicleDamageWeapon'
| 'CEventVehicleOnFire'
| 'CEventWrithe'