This commit is contained in:
Danya H 2025-07-08 22:27:10 +01:00
commit 821cd589d9
23 changed files with 2434 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
.idea
.vscode
dist
node_modules
bun.lock

40
biome.json Normal file
View File

@ -0,0 +1,40 @@
{
"$schema": "./node_modules/@biomejs/biome/configuration_schema.json",
"vcs": {
"enabled": false,
"clientKind": "git",
"useIgnoreFile": false
},
"files": {
"ignoreUnknown": false
},
"formatter": {
"enabled": true
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
},
"javascript": {
"formatter": {
"arrowParentheses": "asNeeded",
"bracketSpacing": true,
"indentWidth": 2,
"lineEnding": "crlf",
"lineWidth": 80,
"quoteStyle": "single",
"semicolons": "asNeeded",
"trailingCommas": "all"
}
},
"assist": {
"enabled": true,
"actions": {
"source": {
"organizeImports": "on"
}
}
}
}

15
license.md Normal file
View File

@ -0,0 +1,15 @@
Custom Attribution-NoDerivs Software License
Copyright (c) 2025 Entity Seven Group
This license allows you to use, copy, and distribute these packages (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

37
package.json Normal file
View File

@ -0,0 +1,37 @@
{
"workspaces": [
"rpc",
"shared-types"
],
"devDependencies": {
"@biomejs/biome": "^2.1.1",
"@types/bun": "^1.2.18"
},
"peerDependencies": {
"typescript": "^5"
},
"scripts": {
"check": "bunx biome check",
"format": "bunx biome format --write"
},
"private": true,
"type": "module",
"license": "CC0-1.0",
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
},
{
"name": "Oleksandr Honcharov",
"email": "0976053529@ukr.net",
"url": "https://github.com/SashaGoncharov19/"
}
],
"repository": {
"type": "git",
"url": "https://github.com/rilaxik/fivem-rpc.git"
}
}

0
readme.md Normal file
View File

15
rpc/license.md Normal file
View File

@ -0,0 +1,15 @@
Custom Attribution-NoDerivs Software License
Copyright (c) 2025 Entity Seven Group
This license allows you to use, copy, and distribute these packages (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

37
rpc/package.json Normal file
View File

@ -0,0 +1,37 @@
{
"name": "@entityseven/fivem-rpc",
"description": "FiveM RPC is an abstraction for events in GTA V FiveM servers in JS/TS",
"version": "0.1.0",
"main": "",
"types": "",
"files": [
"types/**/*",
"readme.md",
"license.md"
],
"keywords": [
"fivem-rpc",
"fivem-rpc-shared-types",
"fivem",
"gta"
],
"type": "module",
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
}
],
"license": "Custom-Attribution-NoDerivs",
"devDependencies": {
"@microsoft/api-extractor": "^7.47.9",
"@citizenfx/client": "^2.0.15015-1",
"@citizenfx/server": "^2.0.14862-1",
"tsup": "^8.3.0"
},
"peerDependencies": {
"typescript": "^5"
}
}

0
rpc/readme.md Normal file
View File

402
rpc/src/core/client.ts Normal file
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
rpc/src/core/server.ts Normal file
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
rpc/src/core/webview.ts Normal file
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
rpc/src/core/wrapper.ts Normal file
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
rpc/src/index.ts Normal file
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
rpc/src/utils/emitter.ts Normal file
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
rpc/src/utils/funcs.ts Normal file
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
rpc/src/utils/native.ts Normal file
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
rpc/src/utils/types.ts Normal file
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'

19
rpc/tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["ES6", "dom"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true
},
"include": ["src/**/*"]
}

14
rpc/tsup.config.ts Normal file
View File

@ -0,0 +1,14 @@
import { defineConfig } from 'tsup'
export default defineConfig({
entry: ['src/index.ts'],
outDir: './dist',
target: 'node16',
platform: 'node',
format: ['cjs'],
splitting: false,
sourcemap: false,
clean: false,
experimentalDts: true,
noExternal: [/.*/],
})

15
shared-types/license.md Normal file
View File

@ -0,0 +1,15 @@
Custom Attribution-NoDerivs Software License
Copyright (c) 2025 Entity Seven Group
This license allows you to use, copy, and distribute these packages (the "Software"), including for commercial purposes, provided that the following conditions are met:
1. **Attribution:** You must give appropriate credit to the original author of the Software, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
2. **No Derivative Works:** You may not modify, transform, or build upon the Software.
3. **Usage and Commercial Use:** You are allowed to use, sell, and gain income from projects that utilize the Software, as long as you comply with the terms of this license.
4. **No Additional Restrictions:** You may not apply legal terms or technological measures that legally restrict others from doing anything the license permits.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

30
shared-types/package.json Normal file
View File

@ -0,0 +1,30 @@
{
"name": "@entityseven/fivem-rpc-shared-types",
"description": "Shared types for @entityseven/fivem-rpc. Highly recommended to install together",
"version": "0.1.0",
"types": "types/types/index.d.ts",
"files": [
"types/**/*",
"readme.md",
"license.md"
],
"keywords": [
"fivem-rpc-shared-types",
"fivem-rpc",
"fivem",
"gta"
],
"type": "module",
"author": "Entity Seven Group",
"contributors": [
{
"name": "Danya H",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik/"
}
],
"license": "Custom-Attribution-NoDerivs",
"peerDependencies": {
"typescript": "^5"
}
}

0
shared-types/readme.md Normal file
View File

52
shared-types/types/types/index.d.ts vendored Normal file
View File

@ -0,0 +1,52 @@
declare module '@entityseven/fivem-rpc-shared-types' {
// Client commands names
export type RPCCommands_Client = ''
// Server commands names
export type RPCCommands_Server = ''
// Client -> Client events
export interface RPCEvents_Client {
_(): void
}
// Client -> Server events
export interface RPCEvents_ClientServer {
_(): void
}
// Client -> Webview events
export interface RPCEvents_ClientWebview {
_(): void
}
// Server -> Server events
export interface RPCEvents_Server {
_(): void
}
// Server -> Client events
export interface RPCEvents_ServerClient {
_(): void
}
// Server -> Server events
export interface RPCEvents_ServerWebview {
_(): void
}
// Webview -> Webview events
export interface RPCEvents_Webview {
_(): void
}
// Webview -> Client events
export interface RPCEvents_WebviewClient {
_(): void
}
// Webview -> Server events
export interface RPCEvents_WebviewServer {
_(): void
}
}