Merge branch 'dev' into unstable
This commit is contained in:
commit
c8e5bdf375
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "rage-fw-cef",
|
"name": "@entityseven/rage-fw-cef",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
@ -11,14 +11,19 @@
|
|||||||
"build": "tsup"
|
"build": "tsup"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rage-rpc": "^0.4.0"
|
"@entityseven/rage-fw-rpc": "latest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ragempcommunity/types-cef": "^2.1.8",
|
"@ragempcommunity/types-cef": "^2.1.8",
|
||||||
"rage-fw-shared-types": "workspace:^"
|
"@entityseven/rage-fw-shared-types": "workspace:^"
|
||||||
},
|
},
|
||||||
|
"description": "RageFW CEF side",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "SashaGoncharov19",
|
"author": "SashaGoncharov19",
|
||||||
"license": "MIT",
|
"contributors": [{
|
||||||
"description": "CEF side for rage-fw"
|
"name": "rilaxik",
|
||||||
|
"email": "dev.rilaxik@gmail.com",
|
||||||
|
"url": "https://github.com/rilaxik"
|
||||||
|
}],
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
@ -1,73 +1,106 @@
|
|||||||
import rpc from 'rage-rpc'
|
import { Rpc } from '@entityseven/rage-fw-rpc'
|
||||||
|
|
||||||
import {
|
import type {
|
||||||
_CefEventHasArgs,
|
_CefEventHasArgs,
|
||||||
_ClientEventHasArgs,
|
_ClientEventHasArgs,
|
||||||
_ServerEventHasArgs,
|
_ServerEventHasArgs,
|
||||||
RageFW_CefArguments,
|
RageFW_CefArgs,
|
||||||
RageFW_CefCallback,
|
RageFW_CefCallback,
|
||||||
RageFW_CefReturn,
|
RageFW_CefReturn,
|
||||||
RageFW_ClientArguments,
|
RageFW_ClientArgs,
|
||||||
RageFW_ClientReturn,
|
RageFW_ClientReturn,
|
||||||
RageFW_ICustomCefEvent,
|
RageFW_ICustomCefEvent,
|
||||||
RageFW_ICustomClientEvent,
|
RageFW_ICustomClientEvent,
|
||||||
RageFW_ICustomServerEvent,
|
RageFW_ICustomServerEvent,
|
||||||
RageFW_ServerArguments,
|
RageFW_ServerArgs,
|
||||||
RageFW_ServerReturn,
|
RageFW_ServerReturn,
|
||||||
} from './types'
|
} from './types'
|
||||||
|
|
||||||
class Cef {
|
class Cef {
|
||||||
|
private _debugMode: boolean = false
|
||||||
|
private _rpc: Rpc = new Rpc()
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
set debug(debug: boolean) {
|
||||||
|
this._debugMode = debug
|
||||||
|
}
|
||||||
|
|
||||||
|
get rpc(): Rpc {
|
||||||
|
return this._rpc
|
||||||
|
}
|
||||||
|
|
||||||
public register<EventName extends keyof RageFW_ICustomCefEvent>(
|
public register<EventName extends keyof RageFW_ICustomCefEvent>(
|
||||||
eventName: EventName,
|
eventName: EventName,
|
||||||
callback: RageFW_CefCallback<EventName>,
|
callback: RageFW_CefCallback<EventName>,
|
||||||
): void {
|
): void {
|
||||||
|
if (this._debugMode) {
|
||||||
|
console.log('[RPC](register):', eventName, callback)
|
||||||
|
}
|
||||||
|
|
||||||
if ('mp' in window) {
|
if ('mp' in window) {
|
||||||
rpc.register(eventName, callback)
|
this._rpc.register(eventName, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public trigger<EventName extends keyof RageFW_ICustomCefEvent>(
|
public async trigger<EventName extends keyof RageFW_ICustomCefEvent>(
|
||||||
eventName: EventName,
|
eventName: EventName,
|
||||||
...args: _CefEventHasArgs<EventName> extends true
|
...args: _CefEventHasArgs<EventName> extends true
|
||||||
? [RageFW_CefArguments<EventName>]
|
? [RageFW_CefArgs<EventName>]
|
||||||
: []
|
: []
|
||||||
): Promise<RageFW_CefReturn<EventName>> {
|
): Promise<RageFW_CefReturn<EventName>> {
|
||||||
|
if (this._debugMode) {
|
||||||
|
console.log('[RPC](trigger):', eventName, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
if ('mp' in window) {
|
if ('mp' in window) {
|
||||||
return rpc.call(eventName, args)
|
return await this._rpc.call(eventName, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
'RageFW was started in window which not contain global variable MP!',
|
'RageFW was started in window which does not contain MP',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public triggerServer<EventName extends keyof RageFW_ICustomServerEvent>(
|
public async triggerServer<
|
||||||
|
EventName extends keyof RageFW_ICustomServerEvent,
|
||||||
|
>(
|
||||||
eventName: EventName,
|
eventName: EventName,
|
||||||
...args: _ServerEventHasArgs<EventName> extends true
|
...args: _ServerEventHasArgs<EventName> extends true
|
||||||
? [RageFW_ServerArguments<EventName>]
|
? [RageFW_ServerArgs<EventName>]
|
||||||
: []
|
: []
|
||||||
): Promise<RageFW_ServerReturn<EventName>> {
|
): Promise<RageFW_ServerReturn<EventName>> {
|
||||||
|
if (this._debugMode) {
|
||||||
|
console.log('[RPC](triggerServer):', eventName, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
if ('mp' in window) {
|
if ('mp' in window) {
|
||||||
return rpc.callServer(eventName, args)
|
return await this._rpc.callServer(eventName, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
'RageFW was started in window which not contain global variable MP!',
|
'RageFW was started in window which does not contain MP',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public triggerClient<EventName extends keyof RageFW_ICustomClientEvent>(
|
public async triggerClient<
|
||||||
|
EventName extends keyof RageFW_ICustomClientEvent,
|
||||||
|
>(
|
||||||
eventName: EventName,
|
eventName: EventName,
|
||||||
...args: _ClientEventHasArgs<EventName> extends true
|
...args: _ClientEventHasArgs<EventName> extends true
|
||||||
? [RageFW_ClientArguments<EventName>]
|
? [RageFW_ClientArgs<EventName>]
|
||||||
: []
|
: []
|
||||||
): Promise<RageFW_ClientReturn<EventName>> {
|
): Promise<RageFW_ClientReturn<EventName>> {
|
||||||
|
if (this._debugMode) {
|
||||||
|
console.log('[RPC](triggerClient):', eventName, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
if ('mp' in window) {
|
if ('mp' in window) {
|
||||||
return rpc.callClient(eventName, args)
|
return await this._rpc.callClient(eventName, args)
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(
|
return Promise.reject(
|
||||||
'RageFW was started in window which not contain global variable MP!',
|
'RageFW was started in window which does not contain MP',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -75,3 +108,6 @@ class Cef {
|
|||||||
export const fw = {
|
export const fw = {
|
||||||
event: new Cef(),
|
event: new Cef(),
|
||||||
}
|
}
|
||||||
|
;(async () => {
|
||||||
|
await fw.event.triggerClient('cefReady')
|
||||||
|
})()
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { RageFW_ICustomCefEvent } from 'rage-fw-shared-types'
|
import { RageFW_ICustomCefEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
export { RageFW_ICustomCefEvent } from 'rage-fw-shared-types'
|
|
||||||
|
export { RageFW_ICustomCefEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available cef event names
|
* Union of all available cef event names
|
||||||
@ -11,7 +12,7 @@ export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent
|
|||||||
* Array of arguments of an event you pass as a generic
|
* Array of arguments of an event you pass as a generic
|
||||||
* These only include custom cef events
|
* These only include custom cef events
|
||||||
*/
|
*/
|
||||||
export type RageFW_CefArguments<K extends RageFW_CefEvent> = Parameters<
|
export type RageFW_CefArgs<K extends RageFW_CefEvent> = Parameters<
|
||||||
RageFW_ICustomCefEvent[K]
|
RageFW_ICustomCefEvent[K]
|
||||||
>
|
>
|
||||||
|
|
||||||
@ -28,8 +29,8 @@ export type RageFW_CefReturn<K extends RageFW_CefEvent> = ReturnType<
|
|||||||
* These only include custom cef events
|
* These only include custom cef events
|
||||||
*/
|
*/
|
||||||
export type RageFW_CefCallback<K extends keyof RageFW_ICustomCefEvent> = (
|
export type RageFW_CefCallback<K extends keyof RageFW_ICustomCefEvent> = (
|
||||||
args: RageFW_CefArguments<K>,
|
args: RageFW_CefArgs<K>,
|
||||||
) => RageFW_CefReturn<K>
|
) => Promise<RageFW_CefReturn<K>>
|
||||||
|
|
||||||
export type _CefEventHasArgs<EventName extends keyof RageFW_ICustomCefEvent> =
|
export type _CefEventHasArgs<EventName extends keyof RageFW_ICustomCefEvent> =
|
||||||
keyof RageFW_ICustomCefEvent extends never
|
keyof RageFW_ICustomCefEvent extends never
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
|
import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
export type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
|
|
||||||
|
export type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available client event names
|
* Union of all available client event names
|
||||||
* These only include custom events
|
* These only include custom events and some extras from RageFW
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
|
export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
|
||||||
|
|
||||||
@ -11,7 +12,7 @@ export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
|
|||||||
* Array of arguments of event you pass as a generic
|
* Array of arguments of event you pass as a generic
|
||||||
* These only include custom client events
|
* These only include custom client events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientArguments<K extends RageFW_ClientEvent> = Parameters<
|
export type RageFW_ClientArgs<K extends RageFW_ClientEvent> = Parameters<
|
||||||
RageFW_ICustomClientEvent[K]
|
RageFW_ICustomClientEvent[K]
|
||||||
>
|
>
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { RageFW_ICustomServerEvent } from 'rage-fw-shared-types'
|
import type { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
export type { RageFW_ICustomServerEvent } from 'rage-fw-shared-types'
|
|
||||||
|
export type { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available server event names
|
* Union of all available server event names
|
||||||
@ -11,7 +12,7 @@ export type RageFW_ServerEvent = keyof RageFW_ICustomServerEvent
|
|||||||
* Array of arguments of event you pass as a generic
|
* Array of arguments of event you pass as a generic
|
||||||
* These only include custom server events
|
* These only include custom server events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerArguments<K extends RageFW_ServerEvent> = Parameters<
|
export type RageFW_ServerArgs<K extends RageFW_ServerEvent> = Parameters<
|
||||||
RageFW_ICustomServerEvent[K]
|
RageFW_ICustomServerEvent[K]
|
||||||
>
|
>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "create-rage-fw",
|
"name": "@entityseven/create-rage-fw",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"bin": {
|
"bin": {
|
||||||
"rage-fw": "dist/index.js"
|
"rage-fw": "dist/index.js"
|
||||||
},
|
},
|
||||||
@ -14,22 +14,23 @@
|
|||||||
"dist/**/*",
|
"dist/**/*",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
],
|
],
|
||||||
"description": "CLI to scaffold a template project for RageFW",
|
|
||||||
"keywords": [],
|
|
||||||
"author": "rilaxik",
|
|
||||||
"license": "ISC",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inquirer/prompts": "^5.0.5",
|
"@inquirer/prompts": "^5.0.5",
|
||||||
"axios": "^1.7.2",
|
"ky": "^1.7.2",
|
||||||
"chalk": "4.1.2",
|
"chalk": "4.1.2",
|
||||||
"git-clone": "^0.2.0",
|
|
||||||
"yargs": "^17.7.2"
|
"yargs": "^17.7.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/git-clone": "^0.2.4",
|
|
||||||
"@types/node": "^20.14.2",
|
"@types/node": "^20.14.2",
|
||||||
"@types/yargs": "^17.0.32",
|
"@types/yargs": "^17.0.32",
|
||||||
"prettier": "^3.3.2",
|
"prettier": "^3.3.2",
|
||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
}
|
},
|
||||||
|
"description": "CLI to scaffold a template project for RageFW",
|
||||||
|
"keywords": [],
|
||||||
|
"author": "rilaxik",
|
||||||
|
"contributors": [{
|
||||||
|
"name": "SashaGoncharov19"
|
||||||
|
}],
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
To make you life easier while using RageFW we created a basic CLI. At the moment automation we have only works via [pnpm](https://pnpm.io/)
|
To make you life easier while using RageFW we created a basic CLI. At the moment automation we have only works via [pnpm](https://pnpm.io/)
|
||||||
|
|
||||||
``pnpm create rage-fw@latest``
|
``pnpm create @entityseven/rage-fw@latest``
|
||||||
|
|
||||||
## TL;DR
|
## TL;DR
|
||||||
- ``Initialize new project`` - create new template project
|
- ``Initialize new project`` - create new template project
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import c from 'chalk'
|
import c from 'chalk'
|
||||||
import { input, select } from '@inquirer/prompts'
|
import { input, select } from '@inquirer/prompts'
|
||||||
import clone from 'git-clone'
|
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
import { cloneBranch } from '../utils/cloner'
|
||||||
|
|
||||||
export async function initProject() {
|
export async function initProject() {
|
||||||
let folder
|
let folder
|
||||||
@ -10,7 +10,7 @@ export async function initProject() {
|
|||||||
if (!folder) {
|
if (!folder) {
|
||||||
folder = await input({
|
folder = await input({
|
||||||
message: c.gray('Enter project name:'),
|
message: c.gray('Enter project name:'),
|
||||||
default: 'rage-fw',
|
default: 'rage-fw-example',
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
console.log(c.gray('Project name:'), folder)
|
console.log(c.gray('Project name:'), folder)
|
||||||
@ -19,19 +19,14 @@ export async function initProject() {
|
|||||||
if (!framework) {
|
if (!framework) {
|
||||||
framework = await select({
|
framework = await select({
|
||||||
message: c.gray('Select frontend:'),
|
message: c.gray('Select frontend:'),
|
||||||
default: 'react',
|
default: 'react-18',
|
||||||
loop: true,
|
loop: true,
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
name: 'React + TypeScript (Vite)',
|
name: 'React + TypeScript (Vite)',
|
||||||
value: 'react',
|
value: 'react-18',
|
||||||
description: 'React + TypeScript (Vite) as a frontend',
|
description: 'React + TypeScript (Vite) as a frontend',
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// name: 'vue',
|
|
||||||
// value: 'vue',
|
|
||||||
// description: 'npm is the most popular package manager',
|
|
||||||
// },
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@ -46,21 +41,20 @@ export async function initProject() {
|
|||||||
c.gray('as a frontend..'),
|
c.gray('as a frontend..'),
|
||||||
)
|
)
|
||||||
|
|
||||||
clone(
|
cloneBranch(
|
||||||
'https://git.entityseven.com/entityseven/rage-framework-example',
|
'https://git.entityseven.com/entityseven/rage-framework-example',
|
||||||
path.join(process.cwd(), folder),
|
path.join(process.cwd(), folder),
|
||||||
{},
|
framework,
|
||||||
err => {
|
)
|
||||||
if (err) {
|
.then(() => {
|
||||||
console.log(c.red('Error occured: \n', err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log(c.gray('Scaffolded project into'), folder)
|
console.log(c.gray('Scaffolded project into'), folder)
|
||||||
console.log(
|
console.log(
|
||||||
c.gray(
|
c.gray(
|
||||||
`Project was created ar dir: ${path.join(process.cwd(), folder)}`,
|
`Project was created ar dir: ${path.join(process.cwd(), folder)}`,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
})
|
||||||
)
|
.catch(e => {
|
||||||
|
console.log(c.red('Error occured: \n', e))
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import * as fs from 'node:fs'
|
import * as fs from 'node:fs'
|
||||||
|
|
||||||
const latestReleases =
|
const latestReleases =
|
||||||
@ -13,25 +12,33 @@ type Asset = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function downloadUpdater(): Promise<void> {
|
export async function downloadUpdater(): Promise<void> {
|
||||||
|
const ky = await import('ky').then(ky => ky.default)
|
||||||
const id = await getLatestReleaseID()
|
const id = await getLatestReleaseID()
|
||||||
|
|
||||||
const latestAssets = `https://git.entityseven.com/api/v1/repos/entityseven/rage-server-downloader/releases/${id}/assets?page=1&limit=1`
|
const latestAssets = `https://git.entityseven.com/api/v1/repos/entityseven/rage-server-downloader/releases/${id}/assets?page=1&limit=1`
|
||||||
|
|
||||||
axios.get<Asset[]>(latestAssets).then(async ({ data }) => {
|
ky.get<Asset[]>(latestAssets)
|
||||||
const downloadURL = data[0].browser_download_url
|
.then(response => response.json())
|
||||||
|
.then(async data => {
|
||||||
|
const downloadURL = data[0].browser_download_url
|
||||||
|
|
||||||
const file = await axios.get(data[0].browser_download_url, {
|
const file = await ky.get(data[0].browser_download_url)
|
||||||
responseType: 'arraybuffer',
|
const fileData = Buffer.from(
|
||||||
|
file as unknown as WithImplicitCoercion<string>,
|
||||||
|
'binary',
|
||||||
|
)
|
||||||
|
|
||||||
|
const fileSplit = downloadURL.split('/')
|
||||||
|
const fileName = fileSplit[fileSplit.length - 1]
|
||||||
|
|
||||||
|
fs.writeFileSync(`./${fileName}`, fileData)
|
||||||
})
|
})
|
||||||
const fileData = Buffer.from(file.data, 'binary')
|
|
||||||
|
|
||||||
const fileSplit = downloadURL.split('/')
|
|
||||||
const fileName = fileSplit[fileSplit.length - 1]
|
|
||||||
|
|
||||||
fs.writeFileSync(`./${fileName}`, fileData)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getLatestReleaseID() {
|
async function getLatestReleaseID() {
|
||||||
return axios.get<Release[]>(latestReleases).then(({ data }) => data[0].id)
|
const ky = await import('ky').then(ky => ky.default)
|
||||||
|
return ky
|
||||||
|
.get<Release[]>(latestReleases)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => data[0].id)
|
||||||
}
|
}
|
||||||
|
67
cli/src/commands/test-rpc.ts
Normal file
67
cli/src/commands/test-rpc.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import c from 'chalk'
|
||||||
|
import { input, select } from '@inquirer/prompts'
|
||||||
|
import path from 'node:path'
|
||||||
|
import { cloneBranch } from '../utils/cloner'
|
||||||
|
|
||||||
|
const choices = {
|
||||||
|
'rpc-react-18': {
|
||||||
|
name: 'Vite + React 18 + TypeScript',
|
||||||
|
value: 'rpc-react-18',
|
||||||
|
description: 'Vite + React 18 + TypeScript as a frontend',
|
||||||
|
},
|
||||||
|
'rpc-svelte-5': {
|
||||||
|
name: 'Vite + Svelte 5 + TypeScript',
|
||||||
|
value: 'rpc-svelte-5',
|
||||||
|
description: 'Vite + Svelte 5 + TypeScript as a frontend',
|
||||||
|
},
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export async function testRpc() {
|
||||||
|
let folder
|
||||||
|
let framework
|
||||||
|
|
||||||
|
if (!folder) {
|
||||||
|
folder = await input({
|
||||||
|
message: c.gray('Enter project name:'),
|
||||||
|
default: 'rage-fw-rpc-example',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(c.gray('Project name:'), folder)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!framework) {
|
||||||
|
framework = await select({
|
||||||
|
message: c.gray('Select frontend:'),
|
||||||
|
default: 'rpc-react-18',
|
||||||
|
loop: true,
|
||||||
|
choices: Object.values(choices),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
console.log(c.gray('Frontend:'), framework)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
c.gray('\nScaffolding template project into'),
|
||||||
|
folder,
|
||||||
|
c.gray('with'),
|
||||||
|
choices[framework].name,
|
||||||
|
c.gray('as a frontend..'),
|
||||||
|
)
|
||||||
|
|
||||||
|
cloneBranch(
|
||||||
|
'https://git.entityseven.com/entityseven/rage-framework-example',
|
||||||
|
path.join(process.cwd(), folder),
|
||||||
|
framework,
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
console.log(c.gray('Scaffolded project into'), folder)
|
||||||
|
console.log(
|
||||||
|
c.gray(
|
||||||
|
`Project was created at dir: ${path.join(process.cwd(), folder)}`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
console.log(c.red('Error occured: \n', e))
|
||||||
|
})
|
||||||
|
}
|
@ -1,40 +1,57 @@
|
|||||||
import c from 'chalk'
|
import c from 'chalk'
|
||||||
import { select } from '@inquirer/prompts'
|
import { select } from '@inquirer/prompts'
|
||||||
|
|
||||||
import { checkForUpdate } from './utils/update'
|
import { checkForUpdates } from './utils/update'
|
||||||
import { initProject } from './commands/create'
|
import { initProject } from './commands/create'
|
||||||
import { downloadUpdater } from './commands/download-updater'
|
import { downloadUpdater } from './commands/download-updater'
|
||||||
|
import { testRpc } from './commands/test-rpc'
|
||||||
|
|
||||||
enum Actions {
|
enum Actions {
|
||||||
INIT_PROJECT = 'INIT_PROJECT',
|
INIT_PROJECT = 'INIT_PROJECT',
|
||||||
|
TEST_RPC = 'TEST_RPC',
|
||||||
UPDATER = 'UPDATER',
|
UPDATER = 'UPDATER',
|
||||||
}
|
}
|
||||||
|
|
||||||
;(async () => {
|
;(async () => {
|
||||||
await checkForUpdate()
|
await checkForUpdates()
|
||||||
|
|
||||||
console.log(
|
console.log(c.blueBright('Rage FW CLI | Powered by Entity Seven Group ️<3'))
|
||||||
c.blueBright('Rage FW CLI | Powered by Entity Seven Group ️ <3'),
|
|
||||||
)
|
|
||||||
|
|
||||||
const action = await select({
|
const action = await select({
|
||||||
message: c.gray('Select action:'),
|
message: c.gray('Select action:'),
|
||||||
choices: [
|
choices: [
|
||||||
{
|
{
|
||||||
name: 'Initialize new project',
|
name: 'Initialize a new project',
|
||||||
value: Actions.INIT_PROJECT,
|
value: Actions.INIT_PROJECT,
|
||||||
description: 'Initialize new project and start develop',
|
description: 'Initialize a new project and start developing',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Test our RPC',
|
||||||
|
value: Actions.TEST_RPC,
|
||||||
|
description:
|
||||||
|
'Initialize a new skeleton project with our RPC set up',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Install RAGE:MP updater',
|
name: 'Install RAGE:MP updater',
|
||||||
value: Actions.UPDATER,
|
value: Actions.UPDATER,
|
||||||
description:
|
description:
|
||||||
'Use our custom updater to download and update RAGE:MP server files.',
|
'Use our tool to download or update RAGE:MP server files in two clicks',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
loop: true,
|
loop: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (action === Actions.INIT_PROJECT) await initProject()
|
switch (action) {
|
||||||
if (action === Actions.UPDATER) await downloadUpdater()
|
case Actions.INIT_PROJECT:
|
||||||
|
await initProject()
|
||||||
|
break
|
||||||
|
case Actions.TEST_RPC:
|
||||||
|
await testRpc()
|
||||||
|
break
|
||||||
|
case Actions.UPDATER:
|
||||||
|
await downloadUpdater()
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
console.log(c.red('Something went wrong..'))
|
||||||
|
}
|
||||||
})()
|
})()
|
||||||
|
24
cli/src/utils/cloner.ts
Normal file
24
cli/src/utils/cloner.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { exec } from 'child_process'
|
||||||
|
|
||||||
|
export async function cloneBranch(link: string, path: string, branch: string) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const args = ['--single-branch', '-b', branch, '--', link, path]
|
||||||
|
const proc = exec('git clone ' + args.join(' '))
|
||||||
|
|
||||||
|
proc.on('close', (status: number) => {
|
||||||
|
if (status == 0) {
|
||||||
|
resolve(true)
|
||||||
|
} else if (status == 128) {
|
||||||
|
reject(
|
||||||
|
`Folder already exists. 'git clone' from branch ${branch} failed with status ` +
|
||||||
|
status,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
`'git clone' from branch ${branch} failed with status ` +
|
||||||
|
status,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,3 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import c from 'chalk'
|
import c from 'chalk'
|
||||||
import yargs from 'yargs'
|
import yargs from 'yargs'
|
||||||
|
|
||||||
@ -10,12 +9,15 @@ type Version = {
|
|||||||
message: string
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function checkForUpdate(): Promise<void> {
|
export async function checkForUpdates(): Promise<void> {
|
||||||
|
const ky = await import('ky').then(ky => ky.default)
|
||||||
|
|
||||||
return new Promise(res => {
|
return new Promise(res => {
|
||||||
yargs.showVersion(version =>
|
yargs.showVersion(version =>
|
||||||
axios
|
ky
|
||||||
.get<Version[]>(latestVersionURL)
|
.get<Version[]>(latestVersionURL)
|
||||||
.then(({ data }) => {
|
.then(response => response.json<Version[]>())
|
||||||
|
.then(data => {
|
||||||
const latestVersion = data[0].name
|
const latestVersion = data[0].name
|
||||||
|
|
||||||
if (latestVersion !== `v${version}`)
|
if (latestVersion !== `v${version}`)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "rage-fw-client",
|
"name": "@entityseven/rage-fw-client",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
@ -11,15 +11,20 @@
|
|||||||
"build": "tsup"
|
"build": "tsup"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rage-rpc": "^0.4.0"
|
"@entityseven/rage-fw-rpc": "latest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ragempcommunity/types-client": "^2.1.8",
|
"@ragempcommunity/types-client": "^2.1.8",
|
||||||
"rage-fw-shared-types": "workspace:^"
|
"@entityseven/rage-fw-shared-types": "workspace:^"
|
||||||
},
|
},
|
||||||
|
"description": "RageFW Client side",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "SashaGoncharov19",
|
"author": "SashaGoncharov19",
|
||||||
|
"contributors": [{
|
||||||
|
"name": "rilaxik",
|
||||||
|
"email": "dev.rilaxik@gmail.com",
|
||||||
|
"url": "https://github.com/rilaxik"
|
||||||
|
}],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Client side of rage-fw",
|
|
||||||
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0"
|
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0"
|
||||||
}
|
}
|
||||||
|
33
client/src/core/client.ts
Normal file
33
client/src/core/client.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import { Rpc } from '@entityseven/rage-fw-rpc'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
RageFW_ClientArgs,
|
||||||
|
RageFW_ClientCallback,
|
||||||
|
RageFW_ClientEvent,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
export class Client {
|
||||||
|
private _rpc: Rpc = new Rpc()
|
||||||
|
|
||||||
|
get rpc(): Rpc {
|
||||||
|
return this._rpc
|
||||||
|
}
|
||||||
|
|
||||||
|
public register<EventName extends RageFW_ClientEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
callback: RageFW_ClientCallback<EventName>,
|
||||||
|
): void {
|
||||||
|
this._rpc.register(
|
||||||
|
eventName,
|
||||||
|
async (data: RageFW_ClientArgs<EventName>) => {
|
||||||
|
return await callback(data)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregister<EventName extends RageFW_ClientEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
): void {
|
||||||
|
this._rpc.unregister(eventName)
|
||||||
|
}
|
||||||
|
}
|
3
client/src/core/index.ts
Normal file
3
client/src/core/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './client'
|
||||||
|
export * from './player'
|
||||||
|
export * from './logger'
|
@ -1,19 +1,19 @@
|
|||||||
export default class Logger {
|
export class Logger {
|
||||||
public error(message: unknown) {
|
public error(...message: unknown[]) {
|
||||||
mp.console.logError(
|
mp.console.logError(
|
||||||
`[${new Date().toLocaleTimeString()}] [ERROR] ${message}`,
|
`[${new Date().toLocaleTimeString()}] [ERROR] ${message.join(' ')}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public warn(message: unknown) {
|
public warn(...message: unknown[]) {
|
||||||
mp.console.logWarning(
|
mp.console.logWarning(
|
||||||
`[${new Date().toLocaleTimeString()}] [WARN] ${message}`,
|
`[${new Date().toLocaleTimeString()}] [WARN] ${message.join(' ')}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
public info(message: unknown) {
|
public info(...message: unknown[]) {
|
||||||
mp.console.logInfo(
|
mp.console.logInfo(
|
||||||
`[${new Date().toLocaleTimeString()}] [INFO] ${message}`,
|
`[${new Date().toLocaleTimeString()}] [INFO] ${message.join(' ')}`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
53
client/src/core/player.ts
Normal file
53
client/src/core/player.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { Rpc } from '@entityseven/rage-fw-rpc'
|
||||||
|
import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
|
import {
|
||||||
|
_CefEventHasArgs,
|
||||||
|
_ClientEventHasArgs,
|
||||||
|
_ServerEventHasArgs,
|
||||||
|
RageFW_CefArgs,
|
||||||
|
RageFW_CefEvent,
|
||||||
|
RageFW_CefReturn,
|
||||||
|
RageFW_ClientArgs,
|
||||||
|
RageFW_ClientReturn,
|
||||||
|
RageFW_ClientServerEvent,
|
||||||
|
RageFW_ClientServerArgs,
|
||||||
|
RageFW_ClientServerReturn,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
private _rpc: Rpc = new Rpc()
|
||||||
|
public browser: BrowserMp | undefined
|
||||||
|
|
||||||
|
get rpc(): Rpc {
|
||||||
|
return this._rpc
|
||||||
|
}
|
||||||
|
public trigger<EventName extends keyof RageFW_ICustomClientEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
...args: _ClientEventHasArgs<EventName> extends true
|
||||||
|
? [RageFW_ClientArgs<EventName>]
|
||||||
|
: []
|
||||||
|
): Promise<RageFW_ClientReturn<EventName>> {
|
||||||
|
return this._rpc.call(eventName, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerServer<EventName extends RageFW_ClientServerEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
...args: _ServerEventHasArgs<EventName> extends true
|
||||||
|
? [RageFW_ClientServerArgs<EventName>]
|
||||||
|
: []
|
||||||
|
): Promise<RageFW_ClientServerReturn<EventName>> {
|
||||||
|
return this._rpc.callServer(eventName, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerBrowser<EventName extends RageFW_CefEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
...args: _CefEventHasArgs<EventName> extends true
|
||||||
|
? [RageFW_CefArgs<EventName>]
|
||||||
|
: []
|
||||||
|
): Promise<RageFW_CefReturn<EventName>> {
|
||||||
|
if (!this.browser)
|
||||||
|
throw new Error('You need to initialize browser first')
|
||||||
|
return this._rpc.callBrowser(this.browser, eventName, args)
|
||||||
|
}
|
||||||
|
}
|
@ -1,86 +1,8 @@
|
|||||||
import rpc from 'rage-rpc'
|
import { Client, Logger, Player } from './core'
|
||||||
|
|
||||||
import Logger from './logger'
|
|
||||||
|
|
||||||
import {
|
|
||||||
_CefEventHasArgs,
|
|
||||||
_ClientEventHasArgs,
|
|
||||||
_ServerEventHasArgs,
|
|
||||||
RageFW_CefArgs,
|
|
||||||
RageFW_CefEvent,
|
|
||||||
RageFW_CefReturn,
|
|
||||||
RageFW_ClientEvent,
|
|
||||||
RageFW_ClientEventArguments,
|
|
||||||
RageFW_ClientEventCallback,
|
|
||||||
RageFW_ClientEventReturn,
|
|
||||||
RageFW_ClientServerEvent,
|
|
||||||
RageFW_ClientServerEventArguments,
|
|
||||||
RageFW_ClientServerEventReturn,
|
|
||||||
} from './types'
|
|
||||||
|
|
||||||
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
|
|
||||||
|
|
||||||
class Client {
|
|
||||||
public register<EventName extends RageFW_ClientEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
callback: RageFW_ClientEventCallback<EventName>,
|
|
||||||
): void {
|
|
||||||
rpc.register(eventName, data => {
|
|
||||||
return callback(data)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
public unregister<EventName extends RageFW_ClientEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
): void {
|
|
||||||
rpc.unregister(eventName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Player {
|
|
||||||
public browser: BrowserMp | undefined
|
|
||||||
|
|
||||||
public trigger<EventName extends keyof RageFW_ICustomClientEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
...args: _ClientEventHasArgs<EventName> extends true
|
|
||||||
? [RageFW_ClientEventArguments<EventName>]
|
|
||||||
: []
|
|
||||||
): Promise<RageFW_ClientEventReturn<EventName>> {
|
|
||||||
return rpc.call<RageFW_ClientEventReturn<EventName>>(eventName, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerServer<EventName extends RageFW_ClientServerEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
...args: _ServerEventHasArgs<EventName> extends true
|
|
||||||
? [RageFW_ClientServerEventArguments<EventName>]
|
|
||||||
: []
|
|
||||||
): Promise<RageFW_ClientServerEventReturn<EventName>> {
|
|
||||||
return rpc.callServer(eventName, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerBrowser<EventName extends RageFW_CefEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
...args: _CefEventHasArgs<EventName> extends true
|
|
||||||
? [RageFW_CefArgs<EventName>]
|
|
||||||
: []
|
|
||||||
): Promise<RageFW_CefReturn<EventName>> {
|
|
||||||
if (!this.browser)
|
|
||||||
throw new Error('You need to initialize browser first!')
|
|
||||||
return rpc.callBrowser(this.browser, eventName, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Browser extends Player {
|
|
||||||
public registerBrowser(browser: BrowserMp) {
|
|
||||||
this.browser = browser
|
|
||||||
return browser
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fw = {
|
export const fw = {
|
||||||
event: new Client(),
|
event: new Client(),
|
||||||
player: new Player(),
|
player: new Player(),
|
||||||
browser: new Browser(),
|
|
||||||
system: {
|
system: {
|
||||||
log: new Logger(),
|
log: new Logger(),
|
||||||
},
|
},
|
||||||
|
99
client/src/keys.ts
Normal file
99
client/src/keys.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
export const KEY_NONE = 0
|
||||||
|
export const KEY_ENTER = 0x0d
|
||||||
|
export const KEY_TAB = 0x09
|
||||||
|
export const KEY_BACKSPACE = 0x08
|
||||||
|
export const KEY_SHIFT = 0x10
|
||||||
|
export const KEY_CTRL = 0x11
|
||||||
|
export const KEY_ALT = 0x12
|
||||||
|
export const KEY_PAUSE = 0x13
|
||||||
|
export const KEY_CAPS_LOCK = 0x14
|
||||||
|
export const KEY_ESCAPE = 0x1b
|
||||||
|
export const KEY_SPACE = 0x20
|
||||||
|
export const KEY_PAGE_UP = 0x21
|
||||||
|
export const KEY_PAGE_DOWN = 0x22
|
||||||
|
export const KEY_END = 0x23
|
||||||
|
export const KEY_HOME = 0x24
|
||||||
|
export const KEY_LEFT = 0x25
|
||||||
|
export const KEY_UP = 0x26
|
||||||
|
export const KEY_RIGHT = 0x27
|
||||||
|
export const KEY_DOWN = 0x28
|
||||||
|
export const KEY_INSERT = 0x2d
|
||||||
|
export const KEY_DELETE = 0x2e
|
||||||
|
export const KEY_0 = 0x30
|
||||||
|
export const KEY_1 = 0x31
|
||||||
|
export const KEY_2 = 0x32
|
||||||
|
export const KEY_3 = 0x33
|
||||||
|
export const KEY_4 = 0x34
|
||||||
|
export const KEY_5 = 0x35
|
||||||
|
export const KEY_6 = 0x36
|
||||||
|
export const KEY_7 = 0x37
|
||||||
|
export const KEY_8 = 0x38
|
||||||
|
export const KEY_9 = 0x39
|
||||||
|
export const KEY_A = 0x41
|
||||||
|
export const KEY_B = 0x42
|
||||||
|
export const KEY_C = 0x43
|
||||||
|
export const KEY_D = 0x44
|
||||||
|
export const KEY_E = 0x45
|
||||||
|
export const KEY_F = 0x46
|
||||||
|
export const KEY_G = 0x47
|
||||||
|
export const KEY_H = 0x48
|
||||||
|
export const KEY_I = 0x49
|
||||||
|
export const KEY_J = 0x4a
|
||||||
|
export const KEY_K = 0x4b
|
||||||
|
export const KEY_L = 0x4c
|
||||||
|
export const KEY_M = 0x4d
|
||||||
|
export const KEY_N = 0x4e
|
||||||
|
export const KEY_O = 0x4f
|
||||||
|
export const KEY_P = 0x50
|
||||||
|
export const KEY_Q = 0x51
|
||||||
|
export const KEY_R = 0x52
|
||||||
|
export const KEY_S = 0x53
|
||||||
|
export const KEY_T = 0x54
|
||||||
|
export const KEY_U = 0x55
|
||||||
|
export const KEY_V = 0x56
|
||||||
|
export const KEY_W = 0x57
|
||||||
|
export const KEY_X = 0x58
|
||||||
|
export const KEY_Y = 0x59
|
||||||
|
export const KEY_Z = 0x5a
|
||||||
|
export const KEY_LEFT_WINDOWS = 0x5b
|
||||||
|
export const KEY_RIGHT_WINDOWS = 0x5c
|
||||||
|
export const KEY_CONTEXT_MENU = 0x5d
|
||||||
|
export const KEY_NUMPAD_0 = 0x60
|
||||||
|
export const KEY_NUMPAD_1 = 0x61
|
||||||
|
export const KEY_NUMPAD_2 = 0x62
|
||||||
|
export const KEY_NUMPAD_3 = 0x63
|
||||||
|
export const KEY_NUMPAD_4 = 0x64
|
||||||
|
export const KEY_NUMPAD_5 = 0x65
|
||||||
|
export const KEY_NUMPAD_6 = 0x66
|
||||||
|
export const KEY_NUMPAD_7 = 0x67
|
||||||
|
export const KEY_NUMPAD_8 = 0x68
|
||||||
|
export const KEY_NUMPAD_9 = 0x69
|
||||||
|
export const KEY_MULTIPLY = 0x6a
|
||||||
|
export const KEY_ADD = 0x6b
|
||||||
|
export const KEY_SEPARATOR = 0x6c
|
||||||
|
export const KEY_SUBTRACT = 0x6d
|
||||||
|
export const KEY_DECIMAL = 0x6e
|
||||||
|
export const KEY_DIVIDE = 0x6f
|
||||||
|
export const KEY_F1 = 0x70
|
||||||
|
export const KEY_F2 = 0x71
|
||||||
|
export const KEY_F3 = 0x72
|
||||||
|
export const KEY_F4 = 0x73
|
||||||
|
export const KEY_F5 = 0x74
|
||||||
|
export const KEY_F6 = 0x75
|
||||||
|
export const KEY_F7 = 0x76
|
||||||
|
export const KEY_F8 = 0x77
|
||||||
|
export const KEY_F9 = 0x78
|
||||||
|
export const KEY_F10 = 0x79
|
||||||
|
export const KEY_F11 = 0x7a
|
||||||
|
export const KEY_F12 = 0x7b
|
||||||
|
export const KEY_NUM_LOCK = 0x90
|
||||||
|
export const KEY_SCROLL_LOCK = 0x91
|
||||||
|
export const KEY_LEFT_SHIFT = 0xa0
|
||||||
|
export const KEY_RIGHT_SHIFT = 0xa1
|
||||||
|
export const KEY_LEFT_CTRL = 0xa2
|
||||||
|
export const KEY_RIGHT_CTRL = 0xa3
|
||||||
|
export const KEY_LEFT_ALT = 0xa4
|
||||||
|
export const KEY_RIGHT_ALT = 0xa5
|
||||||
|
export const VK_TILDE = 0xc0
|
||||||
|
export const VK_LBUTTON = 0x01
|
||||||
|
export const VK_RBUTTON = 0x02
|
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="@ragempcommunity/types-client" />
|
/// <reference types="@ragempcommunity/types-client" />
|
||||||
|
|
||||||
import type { RageFW_ICustomCefEvent } from 'rage-fw-shared-types'
|
import type { RageFW_ICustomCefEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent
|
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="@ragempcommunity/types-client" />
|
/// <reference types="@ragempcommunity/types-client" />
|
||||||
|
|
||||||
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
|
import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available client event names
|
* Union of all available client event names
|
||||||
@ -14,7 +14,7 @@ export type RageFW_ClientEvent =
|
|||||||
* Array of arguments for an event, name of which you pass as a generic
|
* Array of arguments for an event, name of which you pass as a generic
|
||||||
* These include custom and system events
|
* These include custom and system events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientEventArguments<K extends RageFW_ClientEvent> =
|
export type RageFW_ClientArgs<K extends RageFW_ClientEvent> =
|
||||||
K extends keyof RageFW_ICustomClientEvent
|
K extends keyof RageFW_ICustomClientEvent
|
||||||
? Parameters<RageFW_ICustomClientEvent[K]>
|
? Parameters<RageFW_ICustomClientEvent[K]>
|
||||||
: K extends keyof IClientEvents
|
: K extends keyof IClientEvents
|
||||||
@ -25,18 +25,18 @@ export type RageFW_ClientEventArguments<K extends RageFW_ClientEvent> =
|
|||||||
* Callback (function) for an event, name of which you pass as a generic
|
* Callback (function) for an event, name of which you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientEventCallback<K extends RageFW_ClientEvent> = (
|
export type RageFW_ClientCallback<K extends RageFW_ClientEvent> = (
|
||||||
args: RageFW_ClientEventArguments<K>,
|
args: RageFW_ClientArgs<K>,
|
||||||
) => RageFW_ClientEventReturn<K>
|
) => Promise<RageFW_ClientReturn<K>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return type for an event, name of which you pass as a generic
|
* Return type for an event, name of which you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientEventReturn<K extends RageFW_ClientEvent> =
|
export type RageFW_ClientReturn<K extends RageFW_ClientEvent> =
|
||||||
K extends keyof RageFW_ICustomClientEvent
|
K extends keyof RageFW_ICustomClientEvent
|
||||||
? ReturnType<RageFW_ICustomClientEvent[K]>
|
? ReturnType<RageFW_ICustomClientEvent[K]>
|
||||||
: never
|
: void
|
||||||
|
|
||||||
export type _ClientEventHasArgs<
|
export type _ClientEventHasArgs<
|
||||||
EventName extends keyof RageFW_ICustomClientEvent,
|
EventName extends keyof RageFW_ICustomClientEvent,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import type {
|
import type {
|
||||||
RageFW_ICustomClientEvent,
|
RageFW_ICustomClientEvent,
|
||||||
RageFW_ICustomServerEvent,
|
RageFW_ICustomServerEvent,
|
||||||
} from 'rage-fw-shared-types'
|
} from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available server event names callable from client
|
* Union of all available server event names callable from client
|
||||||
@ -15,17 +15,16 @@ export type RageFW_ClientServerEvent = keyof RageFW_ICustomServerEvent
|
|||||||
* Array of arguments for an event, name of which you pass as a generic
|
* Array of arguments for an event, name of which you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientServerEventArguments<
|
export type RageFW_ClientServerArgs<K extends RageFW_ClientServerEvent> =
|
||||||
K extends RageFW_ClientServerEvent,
|
K extends keyof RageFW_ICustomServerEvent
|
||||||
> = K extends keyof RageFW_ICustomServerEvent
|
? Parameters<RageFW_ICustomServerEvent[K]>
|
||||||
? Parameters<RageFW_ICustomServerEvent[K]>
|
: never
|
||||||
: never
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return type for an event, name of which you pass as a generic
|
* Return type for an event, name of which you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ClientServerEventReturn<K extends RageFW_ClientServerEvent> =
|
export type RageFW_ClientServerReturn<K extends RageFW_ClientServerEvent> =
|
||||||
K extends keyof RageFW_ICustomServerEvent
|
K extends keyof RageFW_ICustomServerEvent
|
||||||
? ReturnType<RageFW_ICustomServerEvent[K]>
|
? ReturnType<RageFW_ICustomServerEvent[K]>
|
||||||
: never
|
: never
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
|
||||||
"version": "0.1.0",
|
"version": "0.2.5",
|
||||||
"npmClient": "pnpm"
|
"npmClient": "pnpm"
|
||||||
}
|
}
|
||||||
|
10
package.json
10
package.json
@ -1,9 +1,14 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"publish": "pnpm build && lerna publish --force-publish",
|
"publish": "lerna publish --force-publish",
|
||||||
"build": "lerna run build",
|
"build": "lerna run build",
|
||||||
"lint": "eslint --c .eslintrc.yaml --ext .ts client/ server/ shared-types/"
|
"lint": "eslint --c .eslintrc.yaml --ext .ts client/ server/ shared-types/",
|
||||||
|
|
||||||
|
"rebuild:cef": "cd cef && pnpm build",
|
||||||
|
"rebuild:client": "cd client && pnpm build",
|
||||||
|
"rebuild:server": "cd server && pnpm build",
|
||||||
|
"rebuild": "pnpm rebuild:cef && pnpm rebuild:client && pnpm rebuild:server"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@microsoft/api-extractor": "^7.47.0",
|
"@microsoft/api-extractor": "^7.47.0",
|
||||||
@ -12,6 +17,7 @@
|
|||||||
"@ragempcommunity/types-server": "^2.1.8",
|
"@ragempcommunity/types-server": "^2.1.8",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.13.0",
|
"@typescript-eslint/eslint-plugin": "^7.13.0",
|
||||||
"@typescript-eslint/parser": "^7.13.0",
|
"@typescript-eslint/parser": "^7.13.0",
|
||||||
|
"@types/node": "^22.8.1",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"lerna": "^8.1.3",
|
"lerna": "^8.1.3",
|
||||||
"prettier": "^3.3.1",
|
"prettier": "^3.3.1",
|
||||||
|
14918
pnpm-lock.yaml
14918
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -2,5 +2,4 @@ packages:
|
|||||||
- "server"
|
- "server"
|
||||||
- "client"
|
- "client"
|
||||||
- "cef"
|
- "cef"
|
||||||
- "cli"
|
|
||||||
- "shared-types"
|
- "shared-types"
|
@ -1 +0,0 @@
|
|||||||
Currently not maintained.
|
|
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "rage-fw-rpc",
|
|
||||||
"version": "0.0.23-alpha.0",
|
|
||||||
"main": "dist/index.js",
|
|
||||||
"types": "dist/src/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"dist/**/*"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsup"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"rage-rpc": "^0.4.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@ragempcommunity/types-client": "^2.1.8",
|
|
||||||
"rage-fw-shared-types": "workspace:^"
|
|
||||||
},
|
|
||||||
"keywords": [],
|
|
||||||
"author": "SashaGoncharov19",
|
|
||||||
"license": "MIT",
|
|
||||||
"description": "Client side of rage-fw",
|
|
||||||
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0"
|
|
||||||
}
|
|
42
rage-rpc/src/defs.d.ts
vendored
42
rage-rpc/src/defs.d.ts
vendored
@ -1,42 +0,0 @@
|
|||||||
declare var mp: any;
|
|
||||||
declare var global: any;
|
|
||||||
declare var window: any;
|
|
||||||
|
|
||||||
declare type ProcedureListener = (args: any, info: ProcedureListenerInfo) => any;
|
|
||||||
|
|
||||||
declare interface Player {
|
|
||||||
call: (eventName: string, args?: any[]) => void;
|
|
||||||
[property: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface Browser {
|
|
||||||
url: string;
|
|
||||||
execute: (code: string) => void;
|
|
||||||
[property: string]: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface ProcedureListenerInfo {
|
|
||||||
environment: string;
|
|
||||||
id?: string;
|
|
||||||
player?: Player;
|
|
||||||
browser?: Browser;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface CallOptions {
|
|
||||||
timeout?: number;
|
|
||||||
noRet?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
declare interface Event {
|
|
||||||
req?: number;
|
|
||||||
ret?: number;
|
|
||||||
b?: string;
|
|
||||||
id: string;
|
|
||||||
name?: string;
|
|
||||||
args?: any;
|
|
||||||
env: string;
|
|
||||||
fenv?: string;
|
|
||||||
res?: any;
|
|
||||||
err?: any;
|
|
||||||
noRet?: number;
|
|
||||||
}
|
|
@ -1,568 +0,0 @@
|
|||||||
import * as util from './util';
|
|
||||||
|
|
||||||
const environment = util.getEnvironment();
|
|
||||||
if(!environment) throw 'Unknown RAGE environment';
|
|
||||||
|
|
||||||
const ERR_NOT_FOUND = 'PROCEDURE_NOT_FOUND';
|
|
||||||
|
|
||||||
const IDENTIFIER = '__rpc:id';
|
|
||||||
const PROCESS_EVENT = '__rpc:process';
|
|
||||||
const BROWSER_REGISTER = '__rpc:browserRegister';
|
|
||||||
const BROWSER_UNREGISTER = '__rpc:browserUnregister';
|
|
||||||
const TRIGGER_EVENT = '__rpc:triggerEvent';
|
|
||||||
const TRIGGER_EVENT_BROWSERS = '__rpc:triggerEventBrowsers';
|
|
||||||
|
|
||||||
const glob = environment === 'cef' ? window : global;
|
|
||||||
|
|
||||||
if(!glob[PROCESS_EVENT]){
|
|
||||||
glob.__rpcListeners = {};
|
|
||||||
glob.__rpcPending = {};
|
|
||||||
glob.__rpcEvListeners = {};
|
|
||||||
|
|
||||||
glob[PROCESS_EVENT] = (player: Player | string, rawData?: string) => {
|
|
||||||
if(environment !== "server") rawData = player as string;
|
|
||||||
const data: Event = util.parseData(rawData);
|
|
||||||
|
|
||||||
if(data.req){ // someone is trying to remotely call a procedure
|
|
||||||
const info: ProcedureListenerInfo = {
|
|
||||||
id: data.id,
|
|
||||||
environment: data.fenv || data.env
|
|
||||||
};
|
|
||||||
if(environment === "server") info.player = player as Player;
|
|
||||||
const part = {
|
|
||||||
ret: 1,
|
|
||||||
id: data.id,
|
|
||||||
env: environment
|
|
||||||
};
|
|
||||||
let ret: (ev: Event) => void;
|
|
||||||
switch(environment){
|
|
||||||
case "server":
|
|
||||||
ret = ev => info.player.call(PROCESS_EVENT, [util.stringifyData(ev)]);
|
|
||||||
break;
|
|
||||||
case "client": {
|
|
||||||
if(data.env === "server"){
|
|
||||||
ret = ev => mp.events.callRemote(PROCESS_EVENT, util.stringifyData(ev));
|
|
||||||
}else if(data.env === "cef"){
|
|
||||||
const browser = data.b && glob.__rpcBrowsers[data.b];
|
|
||||||
info.browser = browser;
|
|
||||||
ret = ev => browser && util.isBrowserValid(browser) && passEventToBrowser(browser, ev, true);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "cef": {
|
|
||||||
ret = ev => mp.trigger(PROCESS_EVENT, util.stringifyData(ev));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(ret){
|
|
||||||
const promise = callProcedure(data.name, data.args, info);
|
|
||||||
if(!data.noRet) promise.then(res => ret({ ...part, res })).catch(err => ret({ ...part, err: err ? err : null }));
|
|
||||||
}
|
|
||||||
}else if(data.ret){ // a previously called remote procedure has returned
|
|
||||||
const info = glob.__rpcPending[data.id];
|
|
||||||
if(environment === "server" && info.player !== player) return;
|
|
||||||
if(info){
|
|
||||||
info.resolve(data.hasOwnProperty('err') ? util.promiseReject(data.err) : util.promiseResolve(data.res));
|
|
||||||
delete glob.__rpcPending[data.id];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if(environment !== "cef"){
|
|
||||||
mp.events.add(PROCESS_EVENT, glob[PROCESS_EVENT]);
|
|
||||||
|
|
||||||
if(environment === "client"){
|
|
||||||
// set up internal pass-through events
|
|
||||||
register('__rpc:callServer', ([name, args, noRet], info) => _callServer(name, args, { fenv: info.environment, noRet }));
|
|
||||||
register('__rpc:callBrowsers', ([name, args, noRet], info) => _callBrowsers(null, name, args, { fenv: info.environment, noRet }));
|
|
||||||
|
|
||||||
// set up browser identifiers
|
|
||||||
glob.__rpcBrowsers = {};
|
|
||||||
const initBrowser = (browser: Browser): void => {
|
|
||||||
const id = util.uid();
|
|
||||||
Object.keys(glob.__rpcBrowsers).forEach(key => {
|
|
||||||
const b = glob.__rpcBrowsers[key];
|
|
||||||
if(!b || !util.isBrowserValid(b) || b === browser) delete glob.__rpcBrowsers[key];
|
|
||||||
});
|
|
||||||
glob.__rpcBrowsers[id] = browser;
|
|
||||||
browser.execute(`
|
|
||||||
window.name = '${id}';
|
|
||||||
if(typeof window['${IDENTIFIER}'] === 'undefined'){
|
|
||||||
window['${IDENTIFIER}'] = Promise.resolve(window.name);
|
|
||||||
}else{
|
|
||||||
window['${IDENTIFIER}:resolve'](window.name);
|
|
||||||
}
|
|
||||||
`);
|
|
||||||
};
|
|
||||||
mp.browsers.forEach(initBrowser);
|
|
||||||
mp.events.add('browserCreated', initBrowser);
|
|
||||||
|
|
||||||
// set up browser registration map
|
|
||||||
glob.__rpcBrowserProcedures = {};
|
|
||||||
mp.events.add(BROWSER_REGISTER, (data: string) => {
|
|
||||||
const [browserId, name] = JSON.parse(data);
|
|
||||||
glob.__rpcBrowserProcedures[name] = browserId;
|
|
||||||
});
|
|
||||||
mp.events.add(BROWSER_UNREGISTER, (data: string) => {
|
|
||||||
const [browserId, name] = JSON.parse(data);
|
|
||||||
if(glob.__rpcBrowserProcedures[name] === browserId) delete glob.__rpcBrowserProcedures[name];
|
|
||||||
});
|
|
||||||
|
|
||||||
register(TRIGGER_EVENT_BROWSERS, ([name, args], info) => {
|
|
||||||
Object.values(glob.__rpcBrowsers).forEach(browser => {
|
|
||||||
_callBrowser(browser, TRIGGER_EVENT, [name, args], { fenv: info.environment, noRet: 1 });
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
if(typeof glob[IDENTIFIER] === 'undefined'){
|
|
||||||
glob[IDENTIFIER] = new Promise(resolve => {
|
|
||||||
if (window.name) {
|
|
||||||
resolve(window.name);
|
|
||||||
}else{
|
|
||||||
glob[IDENTIFIER+':resolve'] = resolve;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
register(TRIGGER_EVENT, ([name, args], info) => callEvent(name, args, info));
|
|
||||||
}
|
|
||||||
|
|
||||||
function passEventToBrowser(browser: Browser, data: Event, ignoreNotFound: boolean): void {
|
|
||||||
const raw = util.stringifyData(data);
|
|
||||||
browser.execute(`var process = window["${PROCESS_EVENT}"]; if(process){ process(${JSON.stringify(raw)}); }else{ ${ignoreNotFound ? '' : `mp.trigger("${PROCESS_EVENT}", '{"ret":1,"id":"${data.id}","err":"${ERR_NOT_FOUND}","env":"cef"}');`} }`);
|
|
||||||
}
|
|
||||||
|
|
||||||
function callProcedure(name: string, args: any, info: ProcedureListenerInfo): Promise<any> {
|
|
||||||
const listener = glob.__rpcListeners[name];
|
|
||||||
if(!listener) return util.promiseReject(ERR_NOT_FOUND);
|
|
||||||
return util.promiseResolve(listener(args, info));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a procedure.
|
|
||||||
* @param {string} name - The name of the procedure.
|
|
||||||
* @param {function} cb - The procedure's callback. The return value will be sent back to the caller.
|
|
||||||
* @returns {Function} The function, which unregister the event.
|
|
||||||
*/
|
|
||||||
export function register(name: string, cb: ProcedureListener): Function {
|
|
||||||
if(arguments.length !== 2) throw 'register expects 2 arguments: "name" and "cb"';
|
|
||||||
if(environment === "cef") glob[IDENTIFIER].then((id: string) => mp.trigger(BROWSER_REGISTER, JSON.stringify([id, name])));
|
|
||||||
glob.__rpcListeners[name] = cb;
|
|
||||||
|
|
||||||
return () => unregister(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister a procedure.
|
|
||||||
* @param {string} name - The name of the procedure.
|
|
||||||
*/
|
|
||||||
export function unregister(name: string): void {
|
|
||||||
if(arguments.length !== 1) throw 'unregister expects 1 argument: "name"';
|
|
||||||
if(environment === "cef") glob[IDENTIFIER].then((id: string) => mp.trigger(BROWSER_UNREGISTER, JSON.stringify([id, name])));
|
|
||||||
glob.__rpcListeners[name] = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a local procedure. Only procedures registered in the same context will be resolved.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param name - The name of the locally registered procedure.
|
|
||||||
* @param args - Any parameters for the procedure.
|
|
||||||
* @param options - Any options.
|
|
||||||
* @returns The result from the procedure.
|
|
||||||
*/
|
|
||||||
export function call(name: string, args?: any, options: CallOptions = {}): Promise<any> {
|
|
||||||
if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('call expects 1 to 3 arguments: "name", optional "args", and optional "options"');
|
|
||||||
return util.promiseTimeout(callProcedure(name, args, { environment }), options.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _callServer(name: string, args?: any, extraData: any = {}): Promise<any> {
|
|
||||||
switch(environment){
|
|
||||||
case "server": {
|
|
||||||
return call(name, args);
|
|
||||||
}
|
|
||||||
case "client": {
|
|
||||||
const id = util.uid();
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if(!extraData.noRet){
|
|
||||||
glob.__rpcPending[id] = {
|
|
||||||
resolve
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const event: Event = {
|
|
||||||
req: 1,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
env: environment,
|
|
||||||
args,
|
|
||||||
...extraData
|
|
||||||
};
|
|
||||||
mp.events.callRemote(PROCESS_EVENT, util.stringifyData(event));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case "cef": {
|
|
||||||
return callClient('__rpc:callServer', [name, args, +extraData.noRet]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a remote procedure registered on the server.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param name - The name of the registered procedure.
|
|
||||||
* @param args - Any parameters for the procedure.
|
|
||||||
* @param options - Any options.
|
|
||||||
* @returns The result from the procedure.
|
|
||||||
*/
|
|
||||||
export function callServer(name: string, args?: any, options: CallOptions = {}): Promise<any> {
|
|
||||||
if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('callServer expects 1 to 3 arguments: "name", optional "args", and optional "options"');
|
|
||||||
|
|
||||||
let extraData: any = {};
|
|
||||||
if(options.noRet) extraData.noRet = 1;
|
|
||||||
|
|
||||||
return util.promiseTimeout(_callServer(name, args, extraData), options.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _callClient(player: Player, name: string, args?: any, extraData: any = {}): Promise<any> {
|
|
||||||
switch(environment){
|
|
||||||
case 'client': {
|
|
||||||
return call(name, args);
|
|
||||||
}
|
|
||||||
case 'server': {
|
|
||||||
const id = util.uid();
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if(!extraData.noRet){
|
|
||||||
glob.__rpcPending[id] = {
|
|
||||||
resolve,
|
|
||||||
player
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const event: Event = {
|
|
||||||
req: 1,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
env: environment,
|
|
||||||
args,
|
|
||||||
...extraData
|
|
||||||
};
|
|
||||||
player.call(PROCESS_EVENT, [util.stringifyData(event)]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
case 'cef': {
|
|
||||||
const id = util.uid();
|
|
||||||
return glob[IDENTIFIER].then((browserId: string) => {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
if(!extraData.noRet){
|
|
||||||
glob.__rpcPending[id] = {
|
|
||||||
resolve
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const event: Event = {
|
|
||||||
b: browserId,
|
|
||||||
req: 1,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
env: environment,
|
|
||||||
args,
|
|
||||||
...extraData
|
|
||||||
};
|
|
||||||
mp.trigger(PROCESS_EVENT, util.stringifyData(event));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a remote procedure registered on the client.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param player - The player to call the procedure on.
|
|
||||||
* @param name - The name of the registered procedure.
|
|
||||||
* @param args - Any parameters for the procedure.
|
|
||||||
* @param options - Any options.
|
|
||||||
* @returns The result from the procedure.
|
|
||||||
*/
|
|
||||||
export function callClient(player: Player | string, name?: string | any, args?: any, options: CallOptions = {}): Promise<any> {
|
|
||||||
switch(environment){
|
|
||||||
case 'client': {
|
|
||||||
options = args || {};
|
|
||||||
args = name;
|
|
||||||
name = player;
|
|
||||||
player = null;
|
|
||||||
if((arguments.length < 1 || arguments.length > 3) || typeof name !== 'string') return util.promiseReject('callClient from the client expects 1 to 3 arguments: "name", optional "args", and optional "options"');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'server': {
|
|
||||||
if((arguments.length < 2 || arguments.length > 4) || typeof player !== 'object') return util.promiseReject('callClient from the server expects 2 to 4 arguments: "player", "name", optional "args", and optional "options"');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'cef': {
|
|
||||||
options = args || {};
|
|
||||||
args = name;
|
|
||||||
name = player;
|
|
||||||
player = null;
|
|
||||||
if((arguments.length < 1 || arguments.length > 3) || typeof name !== 'string') return util.promiseReject('callClient from the browser expects 1 to 3 arguments: "name", optional "args", and optional "options"');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let extraData: any = {};
|
|
||||||
if(options.noRet) extraData.noRet = 1;
|
|
||||||
|
|
||||||
return util.promiseTimeout(_callClient(player as Player, name, args, extraData), options.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function _callBrowser(browser: Browser, name: string, args?: any, extraData: any = {}): Promise<any> {
|
|
||||||
return new Promise(resolve => {
|
|
||||||
const id = util.uid();
|
|
||||||
if(!extraData.noRet){
|
|
||||||
glob.__rpcPending[id] = {
|
|
||||||
resolve
|
|
||||||
};
|
|
||||||
}
|
|
||||||
passEventToBrowser(browser, {
|
|
||||||
req: 1,
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
env: environment,
|
|
||||||
args,
|
|
||||||
...extraData
|
|
||||||
}, false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _callBrowsers(player: Player, name: string, args?: any, extraData: any = {}): Promise<any> {
|
|
||||||
switch(environment){
|
|
||||||
case 'client':
|
|
||||||
const browserId = glob.__rpcBrowserProcedures[name];
|
|
||||||
if(!browserId) return util.promiseReject(ERR_NOT_FOUND);
|
|
||||||
const browser = glob.__rpcBrowsers[browserId];
|
|
||||||
if(!browser || !util.isBrowserValid(browser)) return util.promiseReject(ERR_NOT_FOUND);
|
|
||||||
return _callBrowser(browser, name, args, extraData);
|
|
||||||
case 'server':
|
|
||||||
return _callClient(player, '__rpc:callBrowsers', [name, args, +extraData.noRet], extraData);
|
|
||||||
case 'cef':
|
|
||||||
return _callClient(null, '__rpc:callBrowsers', [name, args, +extraData.noRet], extraData);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a remote procedure registered in any browser context.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param player - The player to call the procedure on.
|
|
||||||
* @param name - The name of the registered procedure.
|
|
||||||
* @param args - Any parameters for the procedure.
|
|
||||||
* @param options - Any options.
|
|
||||||
* @returns The result from the procedure.
|
|
||||||
*/
|
|
||||||
export function callBrowsers(player: Player | string, name?: string | any, args?: any, options: CallOptions = {}): Promise<any> {
|
|
||||||
let promise;
|
|
||||||
let extraData: any = {};
|
|
||||||
|
|
||||||
switch(environment){
|
|
||||||
case 'client':
|
|
||||||
case 'cef':
|
|
||||||
options = args || {};
|
|
||||||
args = name;
|
|
||||||
name = player;
|
|
||||||
if(arguments.length < 1 || arguments.length > 3) return util.promiseReject('callBrowsers from the client or browser expects 1 to 3 arguments: "name", optional "args", and optional "options"');
|
|
||||||
if(options.noRet) extraData.noRet = 1;
|
|
||||||
promise = _callBrowsers(null, name, args, extraData);
|
|
||||||
break;
|
|
||||||
case 'server':
|
|
||||||
if(arguments.length < 2 || arguments.length > 4) return util.promiseReject('callBrowsers from the server expects 2 to 4 arguments: "player", "name", optional "args", and optional "options"');
|
|
||||||
if(options.noRet) extraData.noRet = 1;
|
|
||||||
promise = _callBrowsers(player as Player, name, args, extraData);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(promise){
|
|
||||||
return util.promiseTimeout(promise, options.timeout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calls a remote procedure registered in a specific browser instance.
|
|
||||||
*
|
|
||||||
* Client-side environment only.
|
|
||||||
*
|
|
||||||
* @param browser - The browser instance.
|
|
||||||
* @param name - The name of the registered procedure.
|
|
||||||
* @param args - Any parameters for the procedure.
|
|
||||||
* @param options - Any options.
|
|
||||||
* @returns The result from the procedure.
|
|
||||||
*/
|
|
||||||
export function callBrowser(browser: Browser, name: string, args?: any, options: CallOptions = {}): Promise<any> {
|
|
||||||
if(environment !== 'client') return util.promiseReject('callBrowser can only be used in the client environment');
|
|
||||||
if(arguments.length < 2 || arguments.length > 4) return util.promiseReject('callBrowser expects 2 to 4 arguments: "browser", "name", optional "args", and optional "options"');
|
|
||||||
|
|
||||||
let extraData: any = {};
|
|
||||||
if(options.noRet) extraData.noRet = 1;
|
|
||||||
|
|
||||||
return util.promiseTimeout(_callBrowser(browser, name, args, extraData), options.timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
function callEvent(name: string, args: any, info: ProcedureListenerInfo){
|
|
||||||
const listeners = glob.__rpcEvListeners[name];
|
|
||||||
if(listeners){
|
|
||||||
listeners.forEach(listener => listener(args, info));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register an event handler.
|
|
||||||
* @param {string} name - The name of the event.
|
|
||||||
* @param cb - The callback for the event.
|
|
||||||
* @returns {Function} The function, which off the event.
|
|
||||||
*/
|
|
||||||
export function on(name: string, cb: ProcedureListener): Function {
|
|
||||||
if(arguments.length !== 2) throw 'on expects 2 arguments: "name" and "cb"';
|
|
||||||
|
|
||||||
const listeners = glob.__rpcEvListeners[name] || new Set();
|
|
||||||
listeners.add(cb);
|
|
||||||
glob.__rpcEvListeners[name] = listeners;
|
|
||||||
|
|
||||||
return () => off(name, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unregister an event handler.
|
|
||||||
* @param {string} name - The name of the event.
|
|
||||||
* @param cb - The callback for the event.
|
|
||||||
*/
|
|
||||||
export function off(name: string, cb: ProcedureListener){
|
|
||||||
if(arguments.length !== 2) throw 'off expects 2 arguments: "name" and "cb"';
|
|
||||||
|
|
||||||
const listeners = glob.__rpcEvListeners[name];
|
|
||||||
if(listeners){
|
|
||||||
listeners.delete(cb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers a local event. Only events registered in the same context will be triggered.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param name - The name of the locally registered event.
|
|
||||||
* @param args - Any parameters for the event.
|
|
||||||
*/
|
|
||||||
export function trigger(name: string, args?: any){
|
|
||||||
if(arguments.length < 1 || arguments.length > 2) throw 'trigger expects 1 or 2 arguments: "name", and optional "args"';
|
|
||||||
callEvent(name, args, { environment });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers an event registered on the client.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param player - The player to call the procedure on.
|
|
||||||
* @param name - The name of the event.
|
|
||||||
* @param args - Any parameters for the event.
|
|
||||||
*/
|
|
||||||
export function triggerClient(player: Player | string, name?: string | any, args?: any){
|
|
||||||
switch(environment){
|
|
||||||
case 'client': {
|
|
||||||
args = name;
|
|
||||||
name = player;
|
|
||||||
player = null;
|
|
||||||
if((arguments.length < 1 || arguments.length > 2) || typeof name !== 'string') throw 'triggerClient from the client expects 1 or 2 arguments: "name", and optional "args"';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'server': {
|
|
||||||
if((arguments.length < 2 || arguments.length > 3) || typeof player !== 'object') throw 'triggerClient from the server expects 2 or 3 arguments: "player", "name", and optional "args"';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case 'cef': {
|
|
||||||
args = name;
|
|
||||||
name = player;
|
|
||||||
player = null;
|
|
||||||
if((arguments.length < 1 || arguments.length > 2) || typeof name !== 'string') throw 'triggerClient from the browser expects 1 or 2 arguments: "name", and optional "args"';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_callClient(player as Player, TRIGGER_EVENT, [name, args], { noRet: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers an event registered on the server.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param name - The name of the event.
|
|
||||||
* @param args - Any parameters for the event.
|
|
||||||
*/
|
|
||||||
export function triggerServer(name: string, args?: any){
|
|
||||||
if(arguments.length < 1 || arguments.length > 2) throw 'triggerServer expects 1 or 2 arguments: "name", and optional "args"';
|
|
||||||
|
|
||||||
_callServer(TRIGGER_EVENT, [name, args], { noRet: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers an event registered in any browser context.
|
|
||||||
*
|
|
||||||
* Can be called from any environment.
|
|
||||||
*
|
|
||||||
* @param player - The player to call the procedure on.
|
|
||||||
* @param name - The name of the event.
|
|
||||||
* @param args - Any parameters for the event.
|
|
||||||
*/
|
|
||||||
export function triggerBrowsers(player: Player | string, name?: string | any, args?: any){
|
|
||||||
switch(environment){
|
|
||||||
case 'client':
|
|
||||||
case 'cef':
|
|
||||||
args = name;
|
|
||||||
name = player;
|
|
||||||
player = null;
|
|
||||||
if(arguments.length < 1 || arguments.length > 2) throw 'triggerBrowsers from the client or browser expects 1 or 2 arguments: "name", and optional "args"';
|
|
||||||
break;
|
|
||||||
case 'server':
|
|
||||||
if(arguments.length < 2 || arguments.length > 3) throw 'triggerBrowsers from the server expects 2 or 3 arguments: "player", "name", and optional "args"';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
_callClient(player as Player, TRIGGER_EVENT_BROWSERS, [name, args], { noRet: 1 });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Triggers an event registered in a specific browser instance.
|
|
||||||
*
|
|
||||||
* Client-side environment only.
|
|
||||||
*
|
|
||||||
* @param browser - The browser instance.
|
|
||||||
* @param name - The name of the event.
|
|
||||||
* @param args - Any parameters for the event.
|
|
||||||
*/
|
|
||||||
export function triggerBrowser(browser: Browser, name: string, args?: any){
|
|
||||||
if(environment !== 'client') throw 'callBrowser can only be used in the client environment';
|
|
||||||
if(arguments.length < 2 || arguments.length > 4) throw 'callBrowser expects 2 or 3 arguments: "browser", "name", and optional "args"';
|
|
||||||
|
|
||||||
_callBrowser(browser, TRIGGER_EVENT, [name, args], { noRet: 1});
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
register,
|
|
||||||
unregister,
|
|
||||||
call,
|
|
||||||
callServer,
|
|
||||||
callClient,
|
|
||||||
callBrowsers,
|
|
||||||
callBrowser,
|
|
||||||
on,
|
|
||||||
off,
|
|
||||||
trigger,
|
|
||||||
triggerServer,
|
|
||||||
triggerClient,
|
|
||||||
triggerBrowsers,
|
|
||||||
triggerBrowser
|
|
||||||
};
|
|
@ -1,122 +0,0 @@
|
|||||||
enum MpTypes {
|
|
||||||
Blip = 'b',
|
|
||||||
Checkpoint = 'cp',
|
|
||||||
Colshape = 'c',
|
|
||||||
Label = 'l',
|
|
||||||
Marker = 'm',
|
|
||||||
Object = 'o',
|
|
||||||
Pickup = 'p',
|
|
||||||
Player = 'pl',
|
|
||||||
Vehicle = 'v'
|
|
||||||
}
|
|
||||||
|
|
||||||
function isObjectMpType(obj: any, type: MpTypes){
|
|
||||||
const client = getEnvironment() === 'client';
|
|
||||||
if(obj && typeof obj === 'object' && typeof obj.id !== 'undefined'){
|
|
||||||
const test = (type, collection, mpType) => client ? obj.type === type && collection.at(obj.id) === obj : obj instanceof mpType;
|
|
||||||
switch(type){
|
|
||||||
case MpTypes.Blip: return test('blip', mp.blips, mp.Blip);
|
|
||||||
case MpTypes.Checkpoint: return test('checkpoint', mp.checkpoints, mp.Checkpoint);
|
|
||||||
case MpTypes.Colshape: return test('colshape', mp.colshapes, mp.Colshape);
|
|
||||||
case MpTypes.Label: return test('textlabel', mp.labels, mp.TextLabel);
|
|
||||||
case MpTypes.Marker: return test('marker', mp.markers, mp.Marker);
|
|
||||||
case MpTypes.Object: return test('object', mp.objects, mp.Object);
|
|
||||||
case MpTypes.Pickup: return test('pickup', mp.pickups, mp.Pickup);
|
|
||||||
case MpTypes.Player: return test('player', mp.players, mp.Player);
|
|
||||||
case MpTypes.Vehicle: return test('vehicle', mp.vehicles, mp.Vehicle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function uid(): string {
|
|
||||||
const first = (Math.random() * 46656) | 0;
|
|
||||||
const second = (Math.random() * 46656) | 0;
|
|
||||||
const firstPart = ('000' + first.toString(36)).slice(-3);
|
|
||||||
const secondPart = ('000' + second.toString(36)).slice(-3);
|
|
||||||
return firstPart + secondPart;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getEnvironment(): string {
|
|
||||||
if ('mp' in window) return 'cef';
|
|
||||||
if (mp.joaat) return 'server';
|
|
||||||
else if (mp.game && mp.game.joaat) return 'client';
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringifyData(data: any): string {
|
|
||||||
const env = getEnvironment();
|
|
||||||
return JSON.stringify(data, (_, value) => {
|
|
||||||
if(env === 'client' || env === 'server' && value && typeof value === 'object'){
|
|
||||||
let type;
|
|
||||||
|
|
||||||
if(isObjectMpType(value, MpTypes.Blip)) type = MpTypes.Blip;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Checkpoint)) type = MpTypes.Checkpoint;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Colshape)) type = MpTypes.Colshape;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Marker)) type = MpTypes.Marker;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Object)) type = MpTypes.Object;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Pickup)) type = MpTypes.Pickup;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Player)) type = MpTypes.Player;
|
|
||||||
else if(isObjectMpType(value, MpTypes.Vehicle)) type = MpTypes.Vehicle;
|
|
||||||
|
|
||||||
if(type) return {
|
|
||||||
__t: type,
|
|
||||||
i: typeof value.remoteId === 'number' ? value.remoteId : value.id
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parseData(data: string): any {
|
|
||||||
const env = getEnvironment();
|
|
||||||
return JSON.parse(data, (_, value) => {
|
|
||||||
if((env === 'client' || env === 'server') && value && typeof value === 'object' && typeof value['__t'] === 'string' && typeof value.i === 'number' && Object.keys(value).length === 2){
|
|
||||||
const id = value.i;
|
|
||||||
const type = value['__t'];
|
|
||||||
let collection;
|
|
||||||
|
|
||||||
switch(type){
|
|
||||||
case MpTypes.Blip: collection = mp.blips; break;
|
|
||||||
case MpTypes.Checkpoint: collection = mp.checkpoints; break;
|
|
||||||
case MpTypes.Colshape: collection = mp.colshapes; break;
|
|
||||||
case MpTypes.Label: collection = mp.labels; break;
|
|
||||||
case MpTypes.Marker: collection = mp.markers; break;
|
|
||||||
case MpTypes.Object: collection = mp.objects; break;
|
|
||||||
case MpTypes.Pickup: collection = mp.pickups; break;
|
|
||||||
case MpTypes.Player: collection = mp.players; break;
|
|
||||||
case MpTypes.Vehicle: collection = mp.vehicles; break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(collection) return collection[env === 'client' ? 'atRemoteId' : 'at'](id);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function promiseResolve(result: any): Promise<any> {
|
|
||||||
return new Promise(resolve => setTimeout(() => resolve(result), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function promiseReject(error: any): Promise<any> {
|
|
||||||
return new Promise((_, reject) => setTimeout(() => reject(error), 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function promiseTimeout(promise: Promise<any>, timeout?: number){
|
|
||||||
if(typeof timeout === 'number'){
|
|
||||||
return Promise.race([
|
|
||||||
new Promise((_, reject) => {
|
|
||||||
setTimeout(() => reject('TIMEOUT'), timeout);
|
|
||||||
}),
|
|
||||||
promise
|
|
||||||
]);
|
|
||||||
}else return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isBrowserValid(browser: Browser): boolean {
|
|
||||||
try {
|
|
||||||
browser.url;
|
|
||||||
}catch(e){ return false; }
|
|
||||||
return true;
|
|
||||||
}
|
|
@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://json.schemastore.org/tsconfig",
|
|
||||||
"display": "Base",
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"incremental": false,
|
|
||||||
"composite": false,
|
|
||||||
"target": "ES2022",
|
|
||||||
"experimentalDecorators": true,
|
|
||||||
"moduleDetection": "auto",
|
|
||||||
"module": "CommonJS",
|
|
||||||
"resolveJsonModule": true,
|
|
||||||
"declaration": false,
|
|
||||||
"declarationMap": false,
|
|
||||||
"sourceMap": false,
|
|
||||||
"downlevelIteration": false,
|
|
||||||
"inlineSourceMap": false,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"strict": true,
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
|
6
rpc/.prettierrc.yaml
Normal file
6
rpc/.prettierrc.yaml
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
tabWidth: 4
|
||||||
|
printWidth: 80
|
||||||
|
singleQuote: true
|
||||||
|
semi: false
|
||||||
|
arrowParens: avoid
|
||||||
|
endOfLine: auto
|
21
rpc/LICENSE
Normal file
21
rpc/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2024 Entity Seven Group
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
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.
|
26
rpc/index.d.ts
vendored
Normal file
26
rpc/index.d.ts
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
export {}
|
||||||
|
|
||||||
|
interface Mp {
|
||||||
|
trigger(event: string, data?: any): void
|
||||||
|
events: {
|
||||||
|
add(event: string, data: any): void
|
||||||
|
call(event: string, data: any): void
|
||||||
|
callRemote(event: string, data: any): void
|
||||||
|
remove(event: string): void
|
||||||
|
}
|
||||||
|
joaat?: unknown
|
||||||
|
game: {
|
||||||
|
joaat?: unknown
|
||||||
|
}
|
||||||
|
console: {
|
||||||
|
logInfo: (...args: unknown[]) => void
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
const mp: Mp
|
||||||
|
const global: Record<string, (...args: any[]) => unknown>
|
||||||
|
interface Window {
|
||||||
|
[p: string]: any
|
||||||
|
}
|
||||||
|
}
|
42
rpc/package.json
Normal file
42
rpc/package.json
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
{
|
||||||
|
"name": "@entityseven/rage-fw-rpc",
|
||||||
|
"description": "Rage FW RPC",
|
||||||
|
"version": "0.2.5",
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"start": "npx ./dist create"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/**/*"
|
||||||
|
],
|
||||||
|
"devDependencies": {
|
||||||
|
"@microsoft/api-extractor": "^7.47.9",
|
||||||
|
"prettier": "^3.3.2",
|
||||||
|
"tsup": "^8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"typescript": "^5"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"ragemp",
|
||||||
|
"rage",
|
||||||
|
"rpc",
|
||||||
|
"rage-rpc",
|
||||||
|
"ragerpc"
|
||||||
|
],
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"types": "dist/src/index.d.ts",
|
||||||
|
"author": {
|
||||||
|
"name": "rilaxik",
|
||||||
|
"email": "dev.rilaxik@gmail.com",
|
||||||
|
"url": "https://github.com/rilaxik"
|
||||||
|
},
|
||||||
|
"contributors": [
|
||||||
|
{
|
||||||
|
"name": "SashaGoncharov19",
|
||||||
|
"url": "https://github.com/SashaGoncharov19"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"gitHead": "04eb7240735c4a0e4855ebabbe8d5b326819fa76"
|
||||||
|
}
|
98
rpc/readme.md
Normal file
98
rpc/readme.md
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
# Rage-FW-RPC
|
||||||
|
is an all-in package with asynchronous RPC implementation for RageMP servers in JS/TS
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
``` shell
|
||||||
|
npm i rage-fw-rpc
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
pnpm i rage-fw-rpc
|
||||||
|
```
|
||||||
|
```shell
|
||||||
|
yarn add rage-fw-rpc
|
||||||
|
```
|
||||||
|
|
||||||
|
Import installed package and initialize rpc:
|
||||||
|
```ts
|
||||||
|
// lib/rpc.js
|
||||||
|
|
||||||
|
import { Rpc } from 'rage-fw-rpc'
|
||||||
|
export const rpc = new Rpc(/* options */)
|
||||||
|
```
|
||||||
|
|
||||||
|
# Motivation
|
||||||
|
The idea was to create an extensible package, with various features to simplify the development process and provide as much comfort as possible. It should also be using similar architecture as the framework it was specially built for
|
||||||
|
|
||||||
|
Inspired by usage of [rage-rpc](https://github.com/micaww/rage-rpc)
|
||||||
|
|
||||||
|
# Features
|
||||||
|
- Type-safe events via [TS generics](https://www.typescriptlang.org/docs/handbook/2/generics.html), avoiding type wrappers
|
||||||
|
- Built-in logging options for each environment
|
||||||
|
- Error-safe developer mode for browser
|
||||||
|
- Calls can receive response from called environments via Promises (browser -> server -> browser, etc.)
|
||||||
|
- Actual human-readable errors
|
||||||
|
|
||||||
|
# Docs
|
||||||
|
## [Extended version available here](https://git.entityseven.com/entityseven/rage-framework/wiki/RPC%400.2.5)
|
||||||
|
|
||||||
|
## register
|
||||||
|
Registers a callback function for a specified event
|
||||||
|
```ts
|
||||||
|
rpc.register('playerJoin', (player) => {
|
||||||
|
console.log(`Connected: ${player.socialClub}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## unregister
|
||||||
|
Unregisters callback function for a specified event
|
||||||
|
```ts
|
||||||
|
rpc.unregister('playerDamage')
|
||||||
|
```
|
||||||
|
|
||||||
|
## callClient
|
||||||
|
Calls a client-side event from server or browser
|
||||||
|
|
||||||
|
From browser:
|
||||||
|
```ts
|
||||||
|
rpc.callClient('updatePlayerData', ['argument']).then(response => {
|
||||||
|
console.log(`Received: ${response}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
From server (requires player):
|
||||||
|
```ts
|
||||||
|
rpc.callClient(player, 'updatePlayerData', ['argument']).then(response => {
|
||||||
|
console.log(`Received: ${response}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## callServer
|
||||||
|
Calls a server-side event from browser or client
|
||||||
|
```ts
|
||||||
|
rpc.callServer('updatePlayerData', ['argument']).then(response => {
|
||||||
|
console.log(`Received: ${response}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## callBrowser
|
||||||
|
Calls a server-side event from browser or client
|
||||||
|
|
||||||
|
From client:
|
||||||
|
```ts
|
||||||
|
rpc.callBrowser('updatePlayerData', ['argument']).then(response => {
|
||||||
|
console.log(`Received: ${response}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
From client (requires player):
|
||||||
|
```ts
|
||||||
|
rpc.callBrowser(player, 'updatePlayerData', ['argument']).then(response => {
|
||||||
|
console.log(`Received: ${response}`)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## call
|
||||||
|
Calls an event in current environment
|
||||||
|
```ts
|
||||||
|
rpc.call('triggerSomething').then(response => {
|
||||||
|
console.log(`Received: ${response}`)
|
||||||
|
})
|
||||||
|
```
|
88
rpc/src/browser.ts
Normal file
88
rpc/src/browser.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Wrapper } from './wrapper'
|
||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
Events,
|
||||||
|
RPCEventType,
|
||||||
|
RPCState,
|
||||||
|
RpcWrapperConfig,
|
||||||
|
Utils,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT INTENDED FOR OUT-OF-CONTEXT USE
|
||||||
|
*/
|
||||||
|
export class Browser extends Wrapper {
|
||||||
|
constructor(
|
||||||
|
options: RpcWrapperConfig = {
|
||||||
|
forceBrowserDevMode: false,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
super(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT INTENDED FOR OUT-OF-CONTEXT USE
|
||||||
|
*/
|
||||||
|
public _resolveEmitDestination(dataRaw: string) {
|
||||||
|
let state = Utils.prepareExecution(dataRaw)
|
||||||
|
|
||||||
|
switch (state.calledTo) {
|
||||||
|
case Environment.BROWSER:
|
||||||
|
this.emit(dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.emitClient(dataRaw)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitClient(dataRaw: string) {
|
||||||
|
mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to browser
|
||||||
|
private async emit(dataRaw: string) {
|
||||||
|
let state = Utils.prepareExecution(dataRaw)
|
||||||
|
const responseEventName = Utils.generateResponseEventName(state.uuid)
|
||||||
|
|
||||||
|
// check availability
|
||||||
|
state = this.verifyEvent_(state)
|
||||||
|
if (state.knownError) {
|
||||||
|
this.triggerError_(state, state.knownError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute + generate response
|
||||||
|
const response = await this.state_[state.eventName](
|
||||||
|
...(Array.isArray(state.data) ? state.data : []),
|
||||||
|
)
|
||||||
|
const responseState: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName: state.eventName,
|
||||||
|
calledFrom: Environment.SERVER,
|
||||||
|
calledTo: state.calledFrom,
|
||||||
|
knownError: undefined,
|
||||||
|
data: response,
|
||||||
|
type: RPCEventType.RESPONSE,
|
||||||
|
}
|
||||||
|
const responseDataRaw = Utils.prepareTransfer(responseState)
|
||||||
|
|
||||||
|
// send response
|
||||||
|
switch (state.calledFrom) {
|
||||||
|
case Environment.BROWSER:
|
||||||
|
try {
|
||||||
|
mp.events.call(responseEventName, responseDataRaw)
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
try {
|
||||||
|
mp.trigger(responseEventName, responseDataRaw)
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
175
rpc/src/client.ts
Normal file
175
rpc/src/client.ts
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
import { Wrapper } from './wrapper'
|
||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
Errors,
|
||||||
|
Events,
|
||||||
|
RPCEventType,
|
||||||
|
RPCState,
|
||||||
|
RpcWrapperConfig,
|
||||||
|
Utils,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT INTENDED FOR OUT-OF-CONTEXT USE
|
||||||
|
*/
|
||||||
|
export class Client extends Wrapper {
|
||||||
|
private _browser: any = null
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: RpcWrapperConfig = {
|
||||||
|
forceBrowserDevMode: false,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
super(options)
|
||||||
|
}
|
||||||
|
|
||||||
|
set browser(browser: any) {
|
||||||
|
this._browser = browser
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT INTENDED FOR OUT-OF-CONTEXT USE
|
||||||
|
*/
|
||||||
|
public _resolveEmitDestination(dataRaw: string) {
|
||||||
|
const state = Utils.prepareExecution(dataRaw)
|
||||||
|
|
||||||
|
switch (state.calledTo) {
|
||||||
|
case Environment.SERVER:
|
||||||
|
this.emitServer(dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.BROWSER:
|
||||||
|
this.emitBrowser(dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.CLIENT:
|
||||||
|
this.emit(state)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.triggerError_(state, Errors.UNKNOWN_ENVIRONMENT)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to client
|
||||||
|
private async emit(state: RPCState) {
|
||||||
|
this.errorNoBrowser()
|
||||||
|
|
||||||
|
// check availability
|
||||||
|
state = this.verifyEvent_(state)
|
||||||
|
if (state.knownError) {
|
||||||
|
this.triggerError_(state, state.knownError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute + generate response
|
||||||
|
const responseEventName = Utils.generateResponseEventName(state.uuid)
|
||||||
|
const response = await this.state_[state.eventName](
|
||||||
|
...(Array.isArray(state.data) ? state.data : []),
|
||||||
|
)
|
||||||
|
const responseState: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName: state.eventName,
|
||||||
|
calledFrom: state.calledTo,
|
||||||
|
calledTo: state.calledFrom,
|
||||||
|
knownError: undefined,
|
||||||
|
data: response,
|
||||||
|
type: RPCEventType.RESPONSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
// send response
|
||||||
|
switch (state.calledFrom) {
|
||||||
|
case Environment.CLIENT:
|
||||||
|
try {
|
||||||
|
mp.events.call(
|
||||||
|
responseEventName,
|
||||||
|
Utils.prepareTransfer(responseState),
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case Environment.SERVER:
|
||||||
|
try {
|
||||||
|
mp.events.callRemote(
|
||||||
|
responseEventName,
|
||||||
|
Utils.prepareTransfer(responseState),
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.BROWSER:
|
||||||
|
try {
|
||||||
|
this._browser.call(
|
||||||
|
responseEventName,
|
||||||
|
Utils.prepareTransfer(responseState),
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to server
|
||||||
|
private emitServer(dataRaw: string) {
|
||||||
|
this.errorNoBrowser()
|
||||||
|
|
||||||
|
const state = Utils.prepareExecution(dataRaw)
|
||||||
|
|
||||||
|
// if event is called from browser we will forward response through client via this
|
||||||
|
if (state.calledFrom === Environment.BROWSER) {
|
||||||
|
const responseEventName = Utils.generateResponseEventName(
|
||||||
|
state.uuid,
|
||||||
|
)
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
mp.events.add(responseEventName, (responseDataRaw: string) => {
|
||||||
|
this._browser.call(responseEventName, responseDataRaw)
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mp.events.callRemote(Events.SERVER_EVENT_LISTENER, dataRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to browser
|
||||||
|
private emitBrowser(dataRaw: string) {
|
||||||
|
this.errorNoBrowser()
|
||||||
|
|
||||||
|
const state = Utils.prepareExecution(dataRaw)
|
||||||
|
|
||||||
|
// if event is called from server we will forward response through client via this
|
||||||
|
if (state.calledFrom === Environment.SERVER) {
|
||||||
|
const responseEventName = Utils.generateResponseEventName(
|
||||||
|
state.uuid,
|
||||||
|
)
|
||||||
|
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
mp.events.add(responseEventName, (responseDataRaw: string) => {
|
||||||
|
mp.events.callRemote(responseEventName, responseDataRaw)
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
this._browser.call(Events.LOCAL_EVENT_LISTENER, dataRaw)
|
||||||
|
}
|
||||||
|
|
||||||
|
private errorNoBrowser() {
|
||||||
|
if (!this._browser) throw new Error(Errors.NO_BROWSER)
|
||||||
|
}
|
||||||
|
}
|
536
rpc/src/index.ts
Normal file
536
rpc/src/index.ts
Normal file
@ -0,0 +1,536 @@
|
|||||||
|
import { Wrapper } from './wrapper'
|
||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
Errors,
|
||||||
|
Events,
|
||||||
|
nativeClientEvents,
|
||||||
|
nativeServerEvents,
|
||||||
|
type PlayerMp,
|
||||||
|
RpcConfig,
|
||||||
|
RPCEventType,
|
||||||
|
RPCState,
|
||||||
|
Utils,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
import { Server } from './server'
|
||||||
|
import { Client } from './client'
|
||||||
|
import { Browser } from './browser'
|
||||||
|
|
||||||
|
class Rpc extends Wrapper {
|
||||||
|
private _server: Server
|
||||||
|
private _client: Client
|
||||||
|
private _browser: Browser
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: RpcConfig = {
|
||||||
|
forceBrowserDevMode: false,
|
||||||
|
debugLogs: false,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
super(options)
|
||||||
|
|
||||||
|
this._server = new Server(options)
|
||||||
|
this._client = new Client(options)
|
||||||
|
this._browser = new Browser(options)
|
||||||
|
this.debug_ = !!options.debugLogs
|
||||||
|
|
||||||
|
if (options.forceBrowserDevMode) return
|
||||||
|
|
||||||
|
if (this.environment_ === Environment.UNKNOWN)
|
||||||
|
throw new Error(Errors.UNKNOWN_ENVIRONMENT)
|
||||||
|
|
||||||
|
mp.events.add(
|
||||||
|
Events.LOCAL_EVENT_LISTENER,
|
||||||
|
async (player: PlayerMp | string, dataRaw: string) => {
|
||||||
|
switch (this.environment_) {
|
||||||
|
case Environment.SERVER:
|
||||||
|
this._server._resolveEmitDestination(
|
||||||
|
player as PlayerMp,
|
||||||
|
dataRaw,
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.CLIENT:
|
||||||
|
dataRaw = player as string
|
||||||
|
this._client._resolveEmitDestination(dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.BROWSER:
|
||||||
|
dataRaw = player as string
|
||||||
|
this._browser._resolveEmitDestination(dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
void { player, dataRaw }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
set browser(browser: any) {
|
||||||
|
this._client.browser = browser
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a callback function for a specified event
|
||||||
|
*
|
||||||
|
* @template CallbackArguments - An array of argument types that the callback function accepts
|
||||||
|
* @template CallbackReturn - The type of the value returned by the callback function
|
||||||
|
* @template EventName - A string representing the event name or union of names
|
||||||
|
*
|
||||||
|
* @param {EventName} eventName - The name of the event to register the callback for
|
||||||
|
* @param {(...args: CallbackArguments) => CallbackReturn} cb - The callback function that is called when the event is triggered
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* register<[PlayerMp]>('playerJoin', (player) => {
|
||||||
|
* console.log(`Connected: ${player.socialClub}`)
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public register<
|
||||||
|
CallbackArguments extends unknown[] = unknown[],
|
||||||
|
CallbackReturn extends unknown = unknown,
|
||||||
|
EventName extends string = string,
|
||||||
|
>(
|
||||||
|
eventName: EventName,
|
||||||
|
cb: (...args: CallbackArguments) => CallbackReturn,
|
||||||
|
): void {
|
||||||
|
this.log('register', eventName, cb)
|
||||||
|
if (this.forceBrowserDevMode_) return
|
||||||
|
Utils.errorUnknownEnvironment(this.environment_)
|
||||||
|
|
||||||
|
if (
|
||||||
|
(this.environment_ === Environment.CLIENT &&
|
||||||
|
nativeClientEvents.has(eventName)) ||
|
||||||
|
(this.environment_ === Environment.SERVER &&
|
||||||
|
nativeServerEvents.has(eventName))
|
||||||
|
) {
|
||||||
|
mp.events.add(eventName, cb)
|
||||||
|
} else {
|
||||||
|
this.state_[eventName] = cb
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unregisters callback function for a specified event
|
||||||
|
*
|
||||||
|
* @template EventName - A string representing the event name or union of names
|
||||||
|
*
|
||||||
|
* @param {EventName} eventName - The name of the event to register the callback for
|
||||||
|
*
|
||||||
|
* @returns {void}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* unregister('playerJoin')
|
||||||
|
*/
|
||||||
|
public unregister<EventName extends string = string>(
|
||||||
|
eventName: EventName,
|
||||||
|
): void {
|
||||||
|
this.log('unregister', eventName)
|
||||||
|
if (this.forceBrowserDevMode_) return
|
||||||
|
Utils.errorUnknownEnvironment(this.environment_)
|
||||||
|
|
||||||
|
delete this.state_[eventName]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a client-side event from server or browser
|
||||||
|
*
|
||||||
|
* @template Arguments - An array of argument types to be passed to the client event
|
||||||
|
* @template EventName - A string representing the client event name or union of names
|
||||||
|
* @template Return - The type of the value returned by the client event
|
||||||
|
*
|
||||||
|
* @param {EventName} eventName - The name of the client event to be called
|
||||||
|
* @param {Arguments} [args] - Optional arguments to pass to the client event
|
||||||
|
* @returns {Promise<Return>} A promise resolving to the return value of the client event
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Calls an event on client without specifying a player
|
||||||
|
* callClient<[], string, object>('onDataRequest').then(response => {
|
||||||
|
* console.log(`Received: ${response}`) // ^ object
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public async callClient<
|
||||||
|
Arguments extends unknown[] = unknown[],
|
||||||
|
EventName extends string = string,
|
||||||
|
Return extends unknown = unknown,
|
||||||
|
>(eventName: EventName, args?: Arguments): Promise<Return>
|
||||||
|
/**
|
||||||
|
* Calls a client-side event from server or browser
|
||||||
|
*
|
||||||
|
* @template Arguments - An array of argument types to be passed to the client event
|
||||||
|
* @template EventName - A string representing the client event name or union of names
|
||||||
|
* @template Return - The type of the value returned by the client event
|
||||||
|
*
|
||||||
|
* @param {PlayerMp} player - The player for whom the client event is called
|
||||||
|
* @param {EventName} eventName - The name of the client event to be called
|
||||||
|
* @param {Arguments} [args] - Optional arguments to pass to the client event
|
||||||
|
* @returns {Promise<Return>} A promise resolving to the return value of the client event
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Calls an event on client for a specific player
|
||||||
|
* callClient<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => {
|
||||||
|
* console.log(`Action success: ${result}`) // ^ boolean
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public async callClient<
|
||||||
|
Arguments extends unknown[] = unknown[],
|
||||||
|
EventName extends string = string,
|
||||||
|
Return extends unknown = unknown,
|
||||||
|
>(player: PlayerMp, eventName: EventName, args?: Arguments): Promise<Return>
|
||||||
|
public async callClient(
|
||||||
|
playerOrEventName: PlayerMp | string,
|
||||||
|
eventNameOrArgs?: string | unknown[],
|
||||||
|
args?: unknown[],
|
||||||
|
) {
|
||||||
|
_is1StParamPlayer(playerOrEventName)
|
||||||
|
? this.log(
|
||||||
|
'callClient',
|
||||||
|
eventNameOrArgs as string,
|
||||||
|
playerOrEventName,
|
||||||
|
eventNameOrArgs,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
: this.log(
|
||||||
|
'callClient',
|
||||||
|
playerOrEventName as string,
|
||||||
|
eventNameOrArgs,
|
||||||
|
)
|
||||||
|
if (this.forceBrowserDevMode_) return
|
||||||
|
Utils.errorUnknownEnvironment(this.environment_)
|
||||||
|
|
||||||
|
function _is1StParamPlayer(x: unknown): x is PlayerMp {
|
||||||
|
return typeof x === 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
function _is2NdParamEventName(x: unknown): x is string {
|
||||||
|
return typeof x === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.environment_ === Environment.CLIENT) {
|
||||||
|
// client
|
||||||
|
return await this.call(
|
||||||
|
playerOrEventName as string,
|
||||||
|
args as unknown[],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// server
|
||||||
|
if (
|
||||||
|
this.environment_ === Environment.SERVER &&
|
||||||
|
_is1StParamPlayer(playerOrEventName) &&
|
||||||
|
_is2NdParamEventName(eventNameOrArgs)
|
||||||
|
) {
|
||||||
|
const state: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName: eventNameOrArgs,
|
||||||
|
calledTo: Environment.CLIENT,
|
||||||
|
calledFrom: this.environment_,
|
||||||
|
knownError: undefined,
|
||||||
|
data: args as unknown[],
|
||||||
|
type: RPCEventType.EVENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataRaw = Utils.prepareTransfer(state)
|
||||||
|
|
||||||
|
playerOrEventName.call(Events.LOCAL_EVENT_LISTENER, [dataRaw])
|
||||||
|
return (await this.responseHandler(state.uuid)).data
|
||||||
|
}
|
||||||
|
|
||||||
|
// browser
|
||||||
|
if (
|
||||||
|
this.environment_ === Environment.BROWSER &&
|
||||||
|
!_is1StParamPlayer(playerOrEventName) &&
|
||||||
|
!_is2NdParamEventName(eventNameOrArgs)
|
||||||
|
) {
|
||||||
|
const state: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName: playerOrEventName,
|
||||||
|
calledTo: Environment.CLIENT,
|
||||||
|
calledFrom: this.environment_,
|
||||||
|
knownError: undefined,
|
||||||
|
data: eventNameOrArgs,
|
||||||
|
type: RPCEventType.EVENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataRaw = Utils.prepareTransfer(state)
|
||||||
|
|
||||||
|
mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw)
|
||||||
|
return (await this.responseHandler(state.uuid)).data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a server-side event from browser or client
|
||||||
|
*
|
||||||
|
* @template Arguments - An array of argument types to be passed to the server event
|
||||||
|
* @template EventName - A string representing the server event name or union of names
|
||||||
|
* @template Return - The type of the value returned by the server event
|
||||||
|
*
|
||||||
|
* @param {EventName} eventName - The name of the server event to be called
|
||||||
|
* @param {Arguments} [args] - Optional arguments to pass to the server event
|
||||||
|
* @returns {Promise<Return>} A promise resolving to the return value of the server event
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Calls an event on server
|
||||||
|
* callServer<[], string, object>('onDataRequest').then(response => {
|
||||||
|
* console.log(`Received: ${response}`) // ^ object
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public async callServer<
|
||||||
|
Arguments extends unknown[] = unknown[],
|
||||||
|
EventName extends string = string,
|
||||||
|
Return extends unknown = unknown,
|
||||||
|
>(eventName: EventName, args?: Arguments): Promise<Return> {
|
||||||
|
this.log('callServer', eventName, args)
|
||||||
|
if (this.forceBrowserDevMode_)
|
||||||
|
return undefined as unknown as Promise<Return>
|
||||||
|
Utils.errorUnknownEnvironment(this.environment_)
|
||||||
|
|
||||||
|
const state: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName,
|
||||||
|
calledTo: Environment.SERVER,
|
||||||
|
calledFrom: this.environment_,
|
||||||
|
knownError: undefined,
|
||||||
|
data: args,
|
||||||
|
type: RPCEventType.EVENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataRaw = Utils.prepareTransfer(state)
|
||||||
|
|
||||||
|
switch (this.environment_) {
|
||||||
|
case Environment.SERVER:
|
||||||
|
return this.callSelf(state)
|
||||||
|
|
||||||
|
case Environment.CLIENT:
|
||||||
|
mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.BROWSER:
|
||||||
|
mp.trigger(Events.LOCAL_EVENT_LISTENER, dataRaw)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await this.responseHandler(state.uuid)).data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls a browser-side event from server or client
|
||||||
|
*
|
||||||
|
* @template Arguments - An array of argument types to be passed to the browser event
|
||||||
|
* @template EventName - A string representing the browser event name or union of names
|
||||||
|
* @template Return - The type of the value returned by the browser event
|
||||||
|
*
|
||||||
|
* @param {EventName} eventName - The name of the browser event to be called
|
||||||
|
* @param {Arguments} [args] - Optional arguments to pass to the browser event
|
||||||
|
* @returns {Promise<Return>} A promise resolving to the return value of the browser event
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Calls an event on browser without specifying a player
|
||||||
|
* callBrowser<[], string, object>('onDataRequest').then(response => {
|
||||||
|
* console.log(`Received: ${response}`) // ^ object
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public async callBrowser<
|
||||||
|
Arguments extends unknown[] = unknown[],
|
||||||
|
EventName extends string = string,
|
||||||
|
Return extends unknown = unknown,
|
||||||
|
>(eventName: EventName, args?: Arguments): Promise<Return>
|
||||||
|
/**
|
||||||
|
* Calls a browser-side event from server or client
|
||||||
|
*
|
||||||
|
* @template Arguments - An array of argument types to be passed to the browser event
|
||||||
|
* @template EventName - A string representing the browser event name or union of names
|
||||||
|
* @template Return - The type of the value returned by the browser event
|
||||||
|
*
|
||||||
|
* @param {PlayerMp} player - The player for whom the browser event is called
|
||||||
|
* @param {EventName} eventName - The name of the browser event to be called
|
||||||
|
* @param {Arguments} [args] - Optional arguments to pass to the browser event
|
||||||
|
* @returns {Promise<Return>} A promise resolving to the return value of the browser event
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Calls an event on a browser for a specific player
|
||||||
|
* callBrowser<[string, number], string, boolean>(player, 'onPlayerAction', ['jump', 2]).then(result => {
|
||||||
|
* console.log(`Action success: ${result}`) // ^ boolean
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public async callBrowser<
|
||||||
|
Arguments extends unknown[] = unknown[],
|
||||||
|
EventName extends string = string,
|
||||||
|
Return extends unknown = unknown,
|
||||||
|
>(player: PlayerMp, eventName: EventName, args?: Arguments): Promise<Return>
|
||||||
|
public async callBrowser(
|
||||||
|
playerOrEventName: PlayerMp | string,
|
||||||
|
eventNameOrArgs?: string | unknown[],
|
||||||
|
args?: unknown[],
|
||||||
|
) {
|
||||||
|
_is1StParamPlayer(playerOrEventName)
|
||||||
|
? this.log(
|
||||||
|
'DEV callClient',
|
||||||
|
eventNameOrArgs as string,
|
||||||
|
playerOrEventName,
|
||||||
|
eventNameOrArgs,
|
||||||
|
args,
|
||||||
|
)
|
||||||
|
: this.log(
|
||||||
|
'DEV callClient',
|
||||||
|
playerOrEventName as string,
|
||||||
|
eventNameOrArgs,
|
||||||
|
)
|
||||||
|
if (this.forceBrowserDevMode_) return
|
||||||
|
Utils.errorUnknownEnvironment(this.environment_)
|
||||||
|
|
||||||
|
function _is1StParamPlayer(x: unknown): x is PlayerMp {
|
||||||
|
return typeof x === 'object'
|
||||||
|
}
|
||||||
|
|
||||||
|
function _is2NdParamEventName(x: unknown): x is string {
|
||||||
|
return typeof x === 'string'
|
||||||
|
}
|
||||||
|
|
||||||
|
const state: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName: !_is1StParamPlayer(playerOrEventName)
|
||||||
|
? playerOrEventName
|
||||||
|
: _is2NdParamEventName(eventNameOrArgs)
|
||||||
|
? eventNameOrArgs
|
||||||
|
: '',
|
||||||
|
calledTo: Environment.BROWSER,
|
||||||
|
calledFrom: this.environment_,
|
||||||
|
knownError: undefined,
|
||||||
|
data: _is1StParamPlayer(playerOrEventName) ? args : eventNameOrArgs,
|
||||||
|
type: RPCEventType.EVENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataRaw = Utils.prepareTransfer(state)
|
||||||
|
|
||||||
|
switch (this.environment_) {
|
||||||
|
case Environment.BROWSER:
|
||||||
|
return this.callSelf(state)
|
||||||
|
|
||||||
|
case Environment.CLIENT:
|
||||||
|
mp.events.callRemote(Events.LOCAL_EVENT_LISTENER, dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.SERVER:
|
||||||
|
;(playerOrEventName as PlayerMp).call(
|
||||||
|
Events.LOCAL_EVENT_LISTENER,
|
||||||
|
[dataRaw],
|
||||||
|
)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await this.responseHandler(state.uuid)).data
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calls an event in current environment
|
||||||
|
*
|
||||||
|
* @template Arguments - An array of argument types to be passed to the event
|
||||||
|
* @template EventName - A string representing the event name or union of names
|
||||||
|
* @template Return - The type of the value returned by the event
|
||||||
|
*
|
||||||
|
* @param {EventName} eventName - The name of the event to be called
|
||||||
|
* @param {Arguments} [args] - Optional arguments to pass to the event
|
||||||
|
* @returns {Promise<Return>} A promise resolving to the return value of the event
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* // Calls an event in current environment
|
||||||
|
* call<[], string, number>('getSomething').then(response => {
|
||||||
|
* console.log(`Received: ${response}`) // ^ number
|
||||||
|
* })
|
||||||
|
*/
|
||||||
|
public async call<
|
||||||
|
Arguments extends unknown[] = unknown[],
|
||||||
|
EventName extends string = string,
|
||||||
|
Return extends unknown = unknown,
|
||||||
|
>(eventName: EventName, args?: Arguments): Promise<Return> {
|
||||||
|
this.log('call', eventName, args)
|
||||||
|
if (this.forceBrowserDevMode_)
|
||||||
|
return undefined as unknown as Promise<Return>
|
||||||
|
Utils.errorUnknownEnvironment(this.environment_)
|
||||||
|
|
||||||
|
let state: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName,
|
||||||
|
calledTo: this.environment_,
|
||||||
|
calledFrom: this.environment_,
|
||||||
|
knownError: undefined,
|
||||||
|
data: args,
|
||||||
|
type: RPCEventType.EVENT,
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.callSelf<Return>(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redirects an event in cases of it calling its own environment
|
||||||
|
*/
|
||||||
|
private async callSelf<Return extends unknown = unknown>(
|
||||||
|
state: RPCState,
|
||||||
|
): Promise<Return> {
|
||||||
|
state = this.verifyEvent_(state)
|
||||||
|
if (state.knownError) {
|
||||||
|
this.triggerError_(state, state.knownError)
|
||||||
|
}
|
||||||
|
|
||||||
|
return await this.state_[state.eventName](...state.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns cross-environment response
|
||||||
|
*/
|
||||||
|
private async responseHandler(uuid: string): Promise<RPCState> {
|
||||||
|
const responseEventName = Utils.generateResponseEventName(uuid)
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const timeout = setTimeout(() => {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
reject(Errors.EVENT_RESPONSE_TIMEOUT)
|
||||||
|
}, 10000)
|
||||||
|
|
||||||
|
mp.events.add(
|
||||||
|
responseEventName,
|
||||||
|
(player: PlayerMp | string, dataRaw: string) => {
|
||||||
|
switch (this.environment_) {
|
||||||
|
case Environment.SERVER:
|
||||||
|
resolve(Utils.prepareExecution(dataRaw))
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.CLIENT:
|
||||||
|
dataRaw = player as string
|
||||||
|
resolve(Utils.prepareExecution(dataRaw))
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
case Environment.BROWSER:
|
||||||
|
dataRaw = player as string
|
||||||
|
resolve(Utils.prepareExecution(dataRaw))
|
||||||
|
|
||||||
|
clearTimeout(timeout)
|
||||||
|
mp.events.remove(responseEventName)
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
void { player, dataRaw }
|
||||||
|
break
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Rpc }
|
104
rpc/src/server.ts
Normal file
104
rpc/src/server.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import { Wrapper } from './wrapper'
|
||||||
|
import {
|
||||||
|
Environment,
|
||||||
|
Events,
|
||||||
|
type PlayerMp,
|
||||||
|
RPCEventType,
|
||||||
|
RPCState,
|
||||||
|
RpcWrapperConfig,
|
||||||
|
Utils,
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT INTENDED FOR OUT-OF-CONTEXT USE
|
||||||
|
*/
|
||||||
|
export class Server extends Wrapper {
|
||||||
|
constructor(
|
||||||
|
options: RpcWrapperConfig = {
|
||||||
|
forceBrowserDevMode: false,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
super(options)
|
||||||
|
|
||||||
|
if (!!options.forceBrowserDevMode) return
|
||||||
|
|
||||||
|
// specific event to save player in context as it is not available on server -> server calls
|
||||||
|
mp.events.add(
|
||||||
|
Events.SERVER_EVENT_LISTENER,
|
||||||
|
async (player: PlayerMp, dataRaw: string) => {
|
||||||
|
this.emit(player, dataRaw)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* NOT INTENDED FOR OUT-OF-CONTEXT USE
|
||||||
|
*/
|
||||||
|
public _resolveEmitDestination(player: PlayerMp, dataRaw: string) {
|
||||||
|
let state = Utils.prepareExecution(dataRaw)
|
||||||
|
|
||||||
|
switch (state.calledTo) {
|
||||||
|
case Environment.SERVER:
|
||||||
|
this.emit(player, dataRaw)
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
this.emitClient(player as PlayerMp, dataRaw)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private emitClient(player: PlayerMp, dataRaw: string) {
|
||||||
|
player.call(Events.LOCAL_EVENT_LISTENER, [dataRaw])
|
||||||
|
}
|
||||||
|
|
||||||
|
// called to server
|
||||||
|
private async emit(player: PlayerMp, dataRaw: string) {
|
||||||
|
let state = Utils.prepareExecution(dataRaw)
|
||||||
|
const responseEventName = Utils.generateResponseEventName(state.uuid)
|
||||||
|
|
||||||
|
// check availability
|
||||||
|
state = this.verifyEvent_(state)
|
||||||
|
if (state.knownError) {
|
||||||
|
this.triggerError_(state, state.knownError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// execute + generate response
|
||||||
|
const response = await this.state_[state.eventName](
|
||||||
|
player,
|
||||||
|
...(Array.isArray(state.data) ? state.data : []),
|
||||||
|
)
|
||||||
|
const responseState: RPCState = {
|
||||||
|
uuid: Utils.generateUUID(),
|
||||||
|
eventName: state.eventName,
|
||||||
|
calledFrom: Environment.SERVER,
|
||||||
|
calledTo: state.calledFrom,
|
||||||
|
knownError: undefined,
|
||||||
|
data: response,
|
||||||
|
type: RPCEventType.RESPONSE,
|
||||||
|
}
|
||||||
|
|
||||||
|
// send response
|
||||||
|
switch (state.calledFrom) {
|
||||||
|
case Environment.SERVER:
|
||||||
|
try {
|
||||||
|
mp.events.call(
|
||||||
|
responseEventName,
|
||||||
|
Utils.prepareTransfer(responseState),
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
try {
|
||||||
|
player.call(responseEventName, [
|
||||||
|
Utils.prepareTransfer(responseState),
|
||||||
|
])
|
||||||
|
} catch (e) {
|
||||||
|
void e
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
rpc/src/utils.ts
Normal file
171
rpc/src/utils.ts
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
export enum Environment {
|
||||||
|
BROWSER = 'BROWSER',
|
||||||
|
CLIENT = 'CLIENT',
|
||||||
|
SERVER = 'SERVER',
|
||||||
|
UNKNOWN = 'UNKNOWN',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Events {
|
||||||
|
LOCAL_EVENT_LISTENER = '__rpc:listener',
|
||||||
|
SERVER_EVENT_LISTENER = '__rpc:serverListener',
|
||||||
|
EVENT_RESPONSE = '__rpc:response',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Errors {
|
||||||
|
EVENT_NOT_REGISTERED = 'Event not registered',
|
||||||
|
UNKNOWN_ENVIRONMENT = 'Unknown environment',
|
||||||
|
NO_BROWSER = 'You need to initialize browser first',
|
||||||
|
EVENT_RESPONSE_TIMEOUT = 'Response was timed out after 10s of inactivity',
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RPCState = {
|
||||||
|
eventName: string
|
||||||
|
uuid: string
|
||||||
|
calledFrom: Environment
|
||||||
|
calledTo: Environment
|
||||||
|
knownError?: string
|
||||||
|
data?: any
|
||||||
|
type: RPCEventType
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlayerMp = {
|
||||||
|
call: (event: string, args?: unknown[]) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RpcWrapperConfig {
|
||||||
|
forceBrowserDevMode?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RpcConfig extends RpcWrapperConfig {
|
||||||
|
debugLogs?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Utils {
|
||||||
|
// todo type for dev browser
|
||||||
|
public static getEnvironment(): Environment {
|
||||||
|
if ('joaat' in mp) return Environment.SERVER
|
||||||
|
if (
|
||||||
|
'game' in mp &&
|
||||||
|
'joaat' in (mp as { game: { joaat?: unknown } }).game
|
||||||
|
)
|
||||||
|
return Environment.CLIENT
|
||||||
|
if (window && 'mp' in window) return Environment.BROWSER
|
||||||
|
return Environment.UNKNOWN
|
||||||
|
}
|
||||||
|
|
||||||
|
public static prepareExecution(data: string): RPCState {
|
||||||
|
return JSON.parse(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static prepareTransfer(data: RPCState): string {
|
||||||
|
return JSON.stringify(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
public static generateUUID(): string {
|
||||||
|
let uuid = '',
|
||||||
|
random
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
public static generateResponseEventName(uuid: string): string {
|
||||||
|
return `${Events.EVENT_RESPONSE}_${uuid}`
|
||||||
|
}
|
||||||
|
|
||||||
|
public static errorUnknownEnvironment(environment: Environment) {
|
||||||
|
if (environment === Environment.UNKNOWN)
|
||||||
|
throw new Error(Errors.UNKNOWN_ENVIRONMENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum RPCEventType {
|
||||||
|
EVENT = 'event',
|
||||||
|
RESPONSE = 'response',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const nativeClientEvents = new Set([
|
||||||
|
'browserCreated',
|
||||||
|
'browserDomReady',
|
||||||
|
'browserLoadingFailed',
|
||||||
|
'playerEnterCheckpoint',
|
||||||
|
'playerExitCheckpoint',
|
||||||
|
'consoleCommand',
|
||||||
|
'click',
|
||||||
|
'playerChat',
|
||||||
|
'playerCommand',
|
||||||
|
'playerDeath',
|
||||||
|
'playerJoin',
|
||||||
|
'playerQuit',
|
||||||
|
'playerReady',
|
||||||
|
'playerResurrect',
|
||||||
|
'playerRuleTriggered',
|
||||||
|
'playerSpawn',
|
||||||
|
'playerWeaponShot',
|
||||||
|
'dummyEntityCreated',
|
||||||
|
'dummyEntityDestroyed',
|
||||||
|
'entityControllerChange',
|
||||||
|
'incomingDamage',
|
||||||
|
'outgoingDamage',
|
||||||
|
'meleeActionDamage',
|
||||||
|
'playerEnterVehicle',
|
||||||
|
'playerLeaveVehicle',
|
||||||
|
'playerStartTalking',
|
||||||
|
'playerStopTalking',
|
||||||
|
'entityStreamIn',
|
||||||
|
'entityStreamOut',
|
||||||
|
'render',
|
||||||
|
'playerCreateWaypoint',
|
||||||
|
'playerReachWaypoint',
|
||||||
|
'playerEnterColshape',
|
||||||
|
'playerExitColshape',
|
||||||
|
'explosion',
|
||||||
|
'projectile',
|
||||||
|
'uncaughtException',
|
||||||
|
'unhandledRejection',
|
||||||
|
])
|
||||||
|
|
||||||
|
export const nativeServerEvents = new Set([
|
||||||
|
'entityCreated',
|
||||||
|
// 'entityDestroyed',
|
||||||
|
'entityModelChange',
|
||||||
|
'incomingConnection',
|
||||||
|
'packagesLoaded',
|
||||||
|
'playerChat',
|
||||||
|
'playerCommand',
|
||||||
|
'playerDamage',
|
||||||
|
'playerDeath',
|
||||||
|
'playerEnterCheckpoint',
|
||||||
|
'playerEnterColshape',
|
||||||
|
'playerEnterVehicle',
|
||||||
|
'playerExitCheckpoint',
|
||||||
|
'playerExitColshape',
|
||||||
|
'playerExitVehicle',
|
||||||
|
'playerJoin',
|
||||||
|
'playerQuit',
|
||||||
|
'playerReachWaypoint',
|
||||||
|
'playerReady',
|
||||||
|
'playerSpawn',
|
||||||
|
'playerStartEnterVehicle',
|
||||||
|
'playerStartExitVehicle',
|
||||||
|
'playerStreamIn',
|
||||||
|
'playerStreamOut',
|
||||||
|
'playerWeaponChange',
|
||||||
|
'serverShutdown',
|
||||||
|
'trailerAttached',
|
||||||
|
'vehicleDamage',
|
||||||
|
'vehicleDeath',
|
||||||
|
'vehicleHornToggle',
|
||||||
|
'vehicleSirenToggle',
|
||||||
|
])
|
61
rpc/src/wrapper.ts
Normal file
61
rpc/src/wrapper.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { Environment, Errors, RPCState, RpcWrapperConfig, Utils } from './utils'
|
||||||
|
|
||||||
|
export class Wrapper {
|
||||||
|
protected environment_ = Environment.UNKNOWN
|
||||||
|
protected state_: any
|
||||||
|
protected console_ =
|
||||||
|
this.environment_ === Environment.CLIENT
|
||||||
|
? mp.console.logInfo
|
||||||
|
: console.log
|
||||||
|
protected debug_ = false
|
||||||
|
protected forceBrowserDevMode_ = false
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
options: RpcWrapperConfig = {
|
||||||
|
forceBrowserDevMode: false,
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
if (options.forceBrowserDevMode) {
|
||||||
|
this.environment_ = Environment.UNKNOWN
|
||||||
|
this.state_ = window
|
||||||
|
} else {
|
||||||
|
this.environment_ = Utils.getEnvironment()
|
||||||
|
this.state_ =
|
||||||
|
this.environment_ === Environment.BROWSER ? window : global
|
||||||
|
}
|
||||||
|
|
||||||
|
this.forceBrowserDevMode_ = !!options.forceBrowserDevMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// checks if event is available (registered) in current environment
|
||||||
|
protected verifyEvent_(data: string | RPCState): RPCState {
|
||||||
|
let rpcData =
|
||||||
|
typeof data === 'string' ? Utils.prepareExecution(data) : data
|
||||||
|
|
||||||
|
if (!this.state_[rpcData.eventName]) {
|
||||||
|
rpcData.knownError = Errors.EVENT_NOT_REGISTERED
|
||||||
|
}
|
||||||
|
|
||||||
|
return rpcData
|
||||||
|
}
|
||||||
|
|
||||||
|
protected triggerError_(rpcData: RPCState, error?: any) {
|
||||||
|
const errorMessage = [
|
||||||
|
`${rpcData.knownError}`,
|
||||||
|
`Caller: ${rpcData.calledFrom}`,
|
||||||
|
`Receiver: ${this.environment_}`,
|
||||||
|
`Event: ${rpcData.eventName}`,
|
||||||
|
]
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
errorMessage.push(`Additional Info: ${error}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(errorMessage.join('\n | '))
|
||||||
|
}
|
||||||
|
|
||||||
|
protected log(method: string, eventName: string, ...args: unknown[]): void {
|
||||||
|
if (this.debug_)
|
||||||
|
this.console_('RPC | [' + method + '] ' + eventName + ':', ...args)
|
||||||
|
}
|
||||||
|
}
|
29
rpc/tsconfig.json
Normal file
29
rpc/tsconfig.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"lib": [
|
||||||
|
"ES6",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
|
||||||
|
"outDir": "bin",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*",
|
||||||
|
"./index.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
|
}
|
@ -4,7 +4,6 @@ export default defineConfig({
|
|||||||
entry: ['src/index.ts'],
|
entry: ['src/index.ts'],
|
||||||
outDir: './dist',
|
outDir: './dist',
|
||||||
format: ['cjs'],
|
format: ['cjs'],
|
||||||
noExternal: ['rage-rpc'],
|
|
||||||
experimentalDts: true,
|
experimentalDts: true,
|
||||||
splitting: false,
|
splitting: false,
|
||||||
sourcemap: false,
|
sourcemap: false,
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "rage-fw-server",
|
"name": "@entityseven/rage-fw-server",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"types": "dist/src/index.d.ts",
|
"types": "dist/src/index.d.ts",
|
||||||
@ -11,15 +11,20 @@
|
|||||||
"build": "tsup"
|
"build": "tsup"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"rage-rpc": "^0.4.0"
|
"@entityseven/rage-fw-rpc": "latest"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@ragempcommunity/types-server": "^2.1.8",
|
"@ragempcommunity/types-server": "^2.1.8",
|
||||||
"rage-fw-shared-types": "workspace:^"
|
"@entityseven/rage-fw-shared-types": "workspace:^"
|
||||||
},
|
},
|
||||||
|
"description": "RageFW Server side",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "SashaGoncharov19",
|
"author": "SashaGoncharov19",
|
||||||
|
"contributors": [{
|
||||||
|
"name": "rilaxik",
|
||||||
|
"email": "dev.rilaxik@gmail.com",
|
||||||
|
"url": "https://github.com/rilaxik"
|
||||||
|
}],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"description": "Server side for rage-fw",
|
|
||||||
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0"
|
"gitHead": "053e4fd12aa120d53e11e0d2009c0df78c1a2ad0"
|
||||||
}
|
}
|
||||||
|
3
server/src/core/index.ts
Normal file
3
server/src/core/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './player'
|
||||||
|
export * from './server'
|
||||||
|
export * from './logger'
|
@ -1,7 +1,8 @@
|
|||||||
import winston, { format } from 'winston'
|
import winston, { format } from 'winston'
|
||||||
|
|
||||||
const { timestamp, printf, colorize } = format
|
const { timestamp, printf, colorize } = format
|
||||||
|
|
||||||
export default class Logger {
|
export class Logger {
|
||||||
private format = printf(({ message, level, timestamp }) => {
|
private format = printf(({ message, level, timestamp }) => {
|
||||||
return `[${new Date(timestamp).toLocaleTimeString()}] [${level}]: ${message}`
|
return `[${new Date(timestamp).toLocaleTimeString()}] [${level}]: ${message}`
|
||||||
})
|
})
|
||||||
@ -22,15 +23,15 @@ export default class Logger {
|
|||||||
),
|
),
|
||||||
})
|
})
|
||||||
|
|
||||||
public info(message: unknown) {
|
public info(...message: unknown[]) {
|
||||||
this.systemLogger.info(message)
|
this.systemLogger.info(message.join(' '))
|
||||||
}
|
}
|
||||||
|
|
||||||
public warn(message: unknown) {
|
public warn(...message: unknown[]) {
|
||||||
this.systemLogger.warn(message)
|
this.systemLogger.warn(message.join(' '))
|
||||||
}
|
}
|
||||||
|
|
||||||
public error(message: unknown) {
|
public error(...message: unknown[]) {
|
||||||
this.systemLogger.error(message)
|
this.systemLogger.error(message.join(' '))
|
||||||
}
|
}
|
||||||
}
|
}
|
40
server/src/core/player.ts
Normal file
40
server/src/core/player.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Rpc } from '@entityseven/rage-fw-rpc'
|
||||||
|
|
||||||
|
import type {
|
||||||
|
_CefEventHasArgs,
|
||||||
|
_ClientEventHasArgs,
|
||||||
|
RageFW_CefArgs,
|
||||||
|
RageFW_CefEvent,
|
||||||
|
RageFW_CefReturn,
|
||||||
|
RageFW_ClientEvent,
|
||||||
|
RageFW_ServerClientArgs,
|
||||||
|
RageFW_ServerClientReturn,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
export class Player {
|
||||||
|
private _rpc: Rpc = new Rpc()
|
||||||
|
|
||||||
|
get rpc(): Rpc {
|
||||||
|
return this._rpc
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerClient<EventName extends RageFW_ClientEvent>(
|
||||||
|
player: PlayerMp,
|
||||||
|
eventName: EventName,
|
||||||
|
...args: _ClientEventHasArgs<EventName> extends true
|
||||||
|
? [RageFW_ServerClientArgs<EventName>]
|
||||||
|
: []
|
||||||
|
): Promise<RageFW_ServerClientReturn<EventName>> {
|
||||||
|
return this._rpc.callClient(player, eventName, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
public triggerBrowser<EventName extends RageFW_CefEvent>(
|
||||||
|
player: PlayerMp,
|
||||||
|
eventName: EventName,
|
||||||
|
...args: _CefEventHasArgs<EventName> extends true
|
||||||
|
? [RageFW_CefArgs<EventName>]
|
||||||
|
: []
|
||||||
|
): Promise<RageFW_CefReturn<EventName>> {
|
||||||
|
return this._rpc.callBrowser(player, eventName, args)
|
||||||
|
}
|
||||||
|
}
|
117
server/src/core/server.ts
Normal file
117
server/src/core/server.ts
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { Rpc } from '@entityseven/rage-fw-rpc'
|
||||||
|
import { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
|
import { nativeEvents } from '../native.events'
|
||||||
|
import type {
|
||||||
|
_ServerEventHasArgs,
|
||||||
|
RageFW_ServerArgs,
|
||||||
|
RageFW_ServerCallback,
|
||||||
|
RageFW_ServerCallbackCustom,
|
||||||
|
RageFW_ServerCallbackNative,
|
||||||
|
RageFW_ServerEvent,
|
||||||
|
RageFW_ServerReturn,
|
||||||
|
} from '../types'
|
||||||
|
|
||||||
|
export class Server {
|
||||||
|
private _rpc: Rpc = new Rpc()
|
||||||
|
|
||||||
|
get rpc(): Rpc {
|
||||||
|
return this._rpc
|
||||||
|
}
|
||||||
|
|
||||||
|
private isNativeEvent(eventName: string): eventName is keyof IServerEvents {
|
||||||
|
return nativeEvents.includes(eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerCustom<EventName extends keyof RageFW_ICustomServerEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
callback: RageFW_ServerCallbackCustom<EventName>,
|
||||||
|
): void {
|
||||||
|
this._rpc.register(
|
||||||
|
eventName,
|
||||||
|
// fixme
|
||||||
|
async (args: RageFW_ServerArgs<EventName>, info) => {
|
||||||
|
await callback([info.player as PlayerMp, ...args])
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private registerNative<EventName extends keyof IServerEvents>(
|
||||||
|
eventName: EventName,
|
||||||
|
callback: RageFW_ServerCallbackNative<EventName>,
|
||||||
|
): void {
|
||||||
|
mp.events.add(
|
||||||
|
eventName,
|
||||||
|
(...args: Parameters<IServerEvents[EventName]>) =>
|
||||||
|
callback([...args]),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
public register<EventName extends RageFW_ServerEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
callback: RageFW_ServerCallback<EventName>,
|
||||||
|
): void {
|
||||||
|
if (this.isNativeEvent(eventName)) {
|
||||||
|
this.registerNative(
|
||||||
|
eventName,
|
||||||
|
callback as RageFW_ServerCallbackNative,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.registerCustom(
|
||||||
|
eventName,
|
||||||
|
callback as unknown as RageFW_ServerCallbackCustom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerMany<EventName extends RageFW_ServerEvent>(events: {
|
||||||
|
[event in EventName]: RageFW_ServerCallback<event>
|
||||||
|
}): void {
|
||||||
|
Object.entries<RageFW_ServerCallback<EventName>>(events).map(
|
||||||
|
([eventName, callback]) => {
|
||||||
|
if (this.isNativeEvent(eventName)) {
|
||||||
|
this.registerNative(
|
||||||
|
eventName,
|
||||||
|
callback as RageFW_ServerCallbackNative,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
this.registerCustom(
|
||||||
|
eventName as keyof RageFW_ICustomServerEvent,
|
||||||
|
callback as unknown as RageFW_ServerCallbackCustom,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterCustom<EventName extends keyof RageFW_ICustomServerEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
): void {
|
||||||
|
this._rpc.unregister(eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private unregisterNative<EventName extends keyof IServerEvents>(
|
||||||
|
eventName: EventName,
|
||||||
|
): void {
|
||||||
|
mp.events.remove(eventName)
|
||||||
|
}
|
||||||
|
|
||||||
|
public unregister<EventName extends RageFW_ServerEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
): void {
|
||||||
|
if (this.isNativeEvent(eventName)) {
|
||||||
|
this.unregisterNative(eventName)
|
||||||
|
} else {
|
||||||
|
this.unregisterCustom(eventName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public trigger<EventName extends keyof RageFW_ICustomServerEvent>(
|
||||||
|
eventName: EventName,
|
||||||
|
...args: _ServerEventHasArgs<EventName> extends true
|
||||||
|
? [RageFW_ServerArgs<EventName>]
|
||||||
|
: []
|
||||||
|
): Promise<RageFW_ServerReturn<EventName>> {
|
||||||
|
return this._rpc.call(eventName, args)
|
||||||
|
}
|
||||||
|
}
|
@ -1,145 +1,4 @@
|
|||||||
import rpc from 'rage-rpc'
|
import { Logger, Player, Server } from './core'
|
||||||
|
|
||||||
import Logger from './logger'
|
|
||||||
|
|
||||||
import {
|
|
||||||
_CefEventHasArgs,
|
|
||||||
_ClientEventHasArgs,
|
|
||||||
_ServerEventHasArgs,
|
|
||||||
RageFW_CefArgs,
|
|
||||||
RageFW_CefEvent,
|
|
||||||
RageFW_CefReturn,
|
|
||||||
RageFW_ClientEvent,
|
|
||||||
RageFW_ICustomServerEvent,
|
|
||||||
RageFW_ServerClientEventArguments,
|
|
||||||
RageFW_ServerClientEventReturn,
|
|
||||||
RageFW_ServerEvent,
|
|
||||||
RageFW_ServerEventArguments,
|
|
||||||
RageFW_ServerEventCallback,
|
|
||||||
RageFW_ServerEventCallbackCustom,
|
|
||||||
RageFW_ServerEventCallbackNative,
|
|
||||||
RageFW_ServerEventReturn,
|
|
||||||
} from './types'
|
|
||||||
import { nativeEvents } from './native.events'
|
|
||||||
|
|
||||||
class Server {
|
|
||||||
private isNativeEvent(eventName: string): eventName is keyof IServerEvents {
|
|
||||||
return nativeEvents.includes(eventName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerCustom<EventName extends keyof RageFW_ICustomServerEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
callback: RageFW_ServerEventCallbackCustom<EventName>,
|
|
||||||
): void {
|
|
||||||
rpc.register(
|
|
||||||
eventName,
|
|
||||||
async (args: RageFW_ServerEventArguments<EventName>, info) => {
|
|
||||||
callback([info.player as PlayerMp, ...args])
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private registerNative<EventName extends keyof IServerEvents>(
|
|
||||||
eventName: EventName,
|
|
||||||
callback: RageFW_ServerEventCallbackNative<EventName>,
|
|
||||||
): void {
|
|
||||||
mp.events.add(
|
|
||||||
eventName,
|
|
||||||
(...args: Parameters<IServerEvents[EventName]>) =>
|
|
||||||
callback([...args]),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
public register<EventName extends RageFW_ServerEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
callback: RageFW_ServerEventCallback<EventName>,
|
|
||||||
): void {
|
|
||||||
if (this.isNativeEvent(eventName)) {
|
|
||||||
this.registerNative(
|
|
||||||
eventName,
|
|
||||||
callback as RageFW_ServerEventCallbackNative,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.registerCustom(
|
|
||||||
eventName,
|
|
||||||
callback as unknown as RageFW_ServerEventCallbackCustom,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public registerMany<EventName extends RageFW_ServerEvent>(events: {
|
|
||||||
[event in EventName]: RageFW_ServerEventCallback<event>
|
|
||||||
}): void {
|
|
||||||
Object.entries<RageFW_ServerEventCallback<EventName>>(events).map(
|
|
||||||
([eventName, callback]) => {
|
|
||||||
if (this.isNativeEvent(eventName)) {
|
|
||||||
this.registerNative(
|
|
||||||
eventName,
|
|
||||||
callback as RageFW_ServerEventCallbackNative,
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.registerCustom(
|
|
||||||
eventName as keyof RageFW_ICustomServerEvent,
|
|
||||||
callback as unknown as RageFW_ServerEventCallbackCustom,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private unregisterCustom<EventName extends keyof RageFW_ICustomServerEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
): void {
|
|
||||||
rpc.unregister(eventName)
|
|
||||||
}
|
|
||||||
|
|
||||||
private unregisterNative<EventName extends keyof IServerEvents>(
|
|
||||||
eventName: EventName,
|
|
||||||
): void {
|
|
||||||
mp.events.remove(eventName)
|
|
||||||
}
|
|
||||||
|
|
||||||
public unregister<EventName extends RageFW_ServerEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
): void {
|
|
||||||
if (this.isNativeEvent(eventName)) {
|
|
||||||
this.unregisterNative(eventName)
|
|
||||||
} else {
|
|
||||||
this.unregisterCustom(eventName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public trigger<EventName extends keyof RageFW_ICustomServerEvent>(
|
|
||||||
eventName: EventName,
|
|
||||||
...args: _ServerEventHasArgs<EventName> extends true
|
|
||||||
? [RageFW_ServerEventArguments<EventName>]
|
|
||||||
: []
|
|
||||||
): Promise<RageFW_ServerEventReturn<EventName>> {
|
|
||||||
return rpc.call<RageFW_ServerEventReturn<EventName>>(eventName, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Player {
|
|
||||||
public triggerClient<EventName extends RageFW_ClientEvent>(
|
|
||||||
player: PlayerMp,
|
|
||||||
eventName: EventName,
|
|
||||||
...args: _ClientEventHasArgs<EventName> extends true
|
|
||||||
? [RageFW_ServerClientEventArguments<EventName>]
|
|
||||||
: []
|
|
||||||
): Promise<RageFW_ServerClientEventReturn<EventName>> {
|
|
||||||
return rpc.callClient(player, eventName, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
public triggerBrowser<EventName extends RageFW_CefEvent>(
|
|
||||||
player: PlayerMp,
|
|
||||||
eventName: EventName,
|
|
||||||
...args: _CefEventHasArgs<EventName> extends true
|
|
||||||
? [RageFW_CefArgs<EventName>]
|
|
||||||
: []
|
|
||||||
): Promise<RageFW_CefReturn<EventName>> {
|
|
||||||
return rpc.callBrowsers(player, eventName, args)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fw = {
|
export const fw = {
|
||||||
event: new Server(),
|
event: new Server(),
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { RageFW_ICustomCefEvent } from 'rage-fw-shared-types'
|
import type { RageFW_ICustomCefEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent
|
export type RageFW_CefEvent = keyof RageFW_ICustomCefEvent
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
/// <reference types="@ragempcommunity/types-server" />
|
/// <reference types="@ragempcommunity/types-server" />
|
||||||
|
|
||||||
import type { RageFW_ICustomClientEvent } from 'rage-fw-shared-types'
|
import type { RageFW_ICustomClientEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available client event names
|
* Union of all available client event names
|
||||||
@ -12,7 +12,7 @@ export type RageFW_ClientEvent = keyof RageFW_ICustomClientEvent
|
|||||||
* Array of arguments of an event you pass as a generic
|
* Array of arguments of an event you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerClientEventArguments<K extends RageFW_ClientEvent> =
|
export type RageFW_ServerClientArgs<K extends RageFW_ClientEvent> =
|
||||||
K extends RageFW_ClientEvent
|
K extends RageFW_ClientEvent
|
||||||
? Parameters<RageFW_ICustomClientEvent[K]>
|
? Parameters<RageFW_ICustomClientEvent[K]>
|
||||||
: never
|
: never
|
||||||
@ -21,7 +21,7 @@ export type RageFW_ServerClientEventArguments<K extends RageFW_ClientEvent> =
|
|||||||
* Return type of event you pass as a generic
|
* Return type of event you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerClientEventReturn<K extends RageFW_ClientEvent> =
|
export type RageFW_ServerClientReturn<K extends RageFW_ClientEvent> =
|
||||||
K extends RageFW_ClientEvent
|
K extends RageFW_ClientEvent
|
||||||
? ReturnType<RageFW_ICustomClientEvent[K]>
|
? ReturnType<RageFW_ICustomClientEvent[K]>
|
||||||
: never
|
: never
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
import type {
|
import type {
|
||||||
RageFW_ICustomClientEvent,
|
RageFW_ICustomClientEvent,
|
||||||
RageFW_ICustomServerEvent,
|
RageFW_ICustomServerEvent,
|
||||||
} from 'rage-fw-shared-types'
|
} from '@entityseven/rage-fw-shared-types'
|
||||||
export type { RageFW_ICustomServerEvent } from 'rage-fw-shared-types'
|
export type { RageFW_ICustomServerEvent } from '@entityseven/rage-fw-shared-types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union of all available server event names
|
* Union of all available server event names
|
||||||
@ -18,7 +18,7 @@ export type RageFW_ServerEvent =
|
|||||||
* Array of arguments for an event, name of which you pass as a generic
|
* Array of arguments for an event, name of which you pass as a generic
|
||||||
* These also include system events
|
* These also include system events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerEventArguments<K extends RageFW_ServerEvent> =
|
export type RageFW_ServerArgs<K extends RageFW_ServerEvent> =
|
||||||
K extends keyof RageFW_ICustomServerEvent
|
K extends keyof RageFW_ICustomServerEvent
|
||||||
? Parameters<RageFW_ICustomServerEvent[K]>
|
? Parameters<RageFW_ICustomServerEvent[K]>
|
||||||
: K extends keyof IServerEvents
|
: K extends keyof IServerEvents
|
||||||
@ -29,18 +29,18 @@ export type RageFW_ServerEventArguments<K extends RageFW_ServerEvent> =
|
|||||||
* Callback (function) for an event, name of which you pass as a generic
|
* Callback (function) for an event, name of which you pass as a generic
|
||||||
* These include system and custom events
|
* These include system and custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerEventCallback<K extends RageFW_ServerEvent> =
|
export type RageFW_ServerCallback<K extends RageFW_ServerEvent> =
|
||||||
K extends keyof RageFW_ICustomServerEvent
|
K extends keyof RageFW_ICustomServerEvent
|
||||||
? RageFW_ServerEventCallbackCustom<K>
|
? RageFW_ServerCallbackCustom<K>
|
||||||
: K extends keyof IServerEvents
|
: K extends keyof IServerEvents
|
||||||
? RageFW_ServerEventCallbackNative<K>
|
? RageFW_ServerCallbackNative<K>
|
||||||
: never
|
: never
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return type for an event, name of which you pass as a generic
|
* Return type for an event, name of which you pass as a generic
|
||||||
* These include system and custom events
|
* These include system and custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerEventReturn<K extends RageFW_ServerEvent> =
|
export type RageFW_ServerReturn<K extends RageFW_ServerEvent> =
|
||||||
K extends keyof RageFW_ICustomServerEvent
|
K extends keyof RageFW_ICustomServerEvent
|
||||||
? ReturnType<RageFW_ICustomServerEvent[K]>
|
? ReturnType<RageFW_ICustomServerEvent[K]>
|
||||||
: K extends keyof IServerEvents
|
: K extends keyof IServerEvents
|
||||||
@ -51,24 +51,35 @@ export type RageFW_ServerEventReturn<K extends RageFW_ServerEvent> =
|
|||||||
* Array of arguments for an event, name of which you pass as a generic
|
* Array of arguments for an event, name of which you pass as a generic
|
||||||
* These only include custom events
|
* These only include custom events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerEventCallbackCustom<
|
export type RageFW_ServerCallbackCustom<
|
||||||
K extends keyof RageFW_ICustomServerEvent = keyof RageFW_ICustomServerEvent,
|
K extends keyof RageFW_ICustomServerEvent = keyof RageFW_ICustomServerEvent,
|
||||||
> = (
|
> = (
|
||||||
payload: [player: PlayerMp, ...args: RageFW_ServerEventArguments<K>],
|
payload: [player: PlayerMp, ...args: RageFW_ServerArgs<K>],
|
||||||
) => RageFW_ServerEventReturn<K>
|
) => Promise<RageFW_ServerReturn<K>>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Array of arguments for an event, name of which you pass as a generic
|
* Array of arguments for an event, name of which you pass as a generic
|
||||||
* These only include system events
|
* These only include system events
|
||||||
*/
|
*/
|
||||||
export type RageFW_ServerEventCallbackNative<
|
export type RageFW_ServerCallbackNative<
|
||||||
K extends keyof IServerEvents = keyof IServerEvents,
|
K extends keyof IServerEvents = keyof IServerEvents,
|
||||||
> = (payload: Parameters<IServerEvents[K]>) => ReturnType<IServerEvents[K]>
|
> = (
|
||||||
|
payload: Parameters<IServerEvents[K]>,
|
||||||
|
) => Promise<ReturnType<IServerEvents[K]>>
|
||||||
|
|
||||||
export type _ServerEventHasArgs<
|
export type _ServerEventHasArgs<EventName extends RageFW_ServerEvent> =
|
||||||
EventName extends keyof RageFW_ICustomServerEvent,
|
EventName extends keyof RageFW_ICustomServerEvent
|
||||||
> = keyof RageFW_ICustomClientEvent extends never
|
? keyof RageFW_ICustomClientEvent extends never
|
||||||
? false
|
? false
|
||||||
: Parameters<RageFW_ICustomServerEvent[EventName]>[0] extends undefined
|
: Parameters<
|
||||||
? false
|
RageFW_ICustomServerEvent[EventName]
|
||||||
: true
|
>[0] extends undefined
|
||||||
|
? false
|
||||||
|
: true
|
||||||
|
: EventName extends keyof IServerEvents
|
||||||
|
? keyof IServerEvents extends never
|
||||||
|
? false
|
||||||
|
: Parameters<IServerEvents[EventName]>[0] extends undefined
|
||||||
|
? false
|
||||||
|
: true
|
||||||
|
: false
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "rage-fw-shared-types",
|
"name": "@entityseven/rage-fw-shared-types",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"types": "types/types/index.d.ts",
|
"types": "types/types/index.d.ts",
|
||||||
"files": [
|
"files": [
|
||||||
|
6
shared-types/types/types/index.d.ts
vendored
6
shared-types/types/types/index.d.ts
vendored
@ -1,7 +1,9 @@
|
|||||||
declare module 'rage-fw-shared-types' {
|
declare module '@entityseven/rage-fw-shared-types' {
|
||||||
export interface RageFW_ICustomServerEvent {}
|
export interface RageFW_ICustomServerEvent {}
|
||||||
|
|
||||||
export interface RageFW_ICustomClientEvent {}
|
export interface RageFW_ICustomClientEvent {
|
||||||
|
cefReady(): void
|
||||||
|
}
|
||||||
|
|
||||||
export interface RageFW_ICustomCefEvent {}
|
export interface RageFW_ICustomCefEvent {}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user