This commit is contained in:
Danya H 2024-10-03 12:56:21 +01:00
parent 30373960e5
commit 092693acb5
16 changed files with 487 additions and 4 deletions

View File

@ -14,7 +14,7 @@
"dependencies": { "dependencies": {
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"rage-fw-cef": "latest" "rpc": "workspace:^"
}, },
"devDependencies": { "devDependencies": {
"@types/react": "^18.2.66", "@types/react": "^18.2.66",

View File

@ -8,6 +8,6 @@
"build": "esbuild src/index.ts --bundle --platform=node --outfile=../../server/client_packages/index.js --format=esm" "build": "esbuild src/index.ts --bundle --platform=node --outfile=../../server/client_packages/index.js --format=esm"
}, },
"dependencies": { "dependencies": {
"rage-fw-client": "latest" "rpc": "workspace:^"
} }
} }

View File

@ -0,0 +1,6 @@
tabWidth: 4
printWidth: 80
singleQuote: true
semi: false
arrowParens: avoid
endOfLine: auto

9
apps/rpc/index.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
declare const mp: any
declare const global: {
rpcEvents: Record<string, (...args: any[]) => unknown>
}
declare const window: {
rpcEvents: Record<string, (...args: any[]) => unknown>
}

28
apps/rpc/package.json Normal file
View File

@ -0,0 +1,28 @@
{
"name": "rpc",
"version": "0.1.0",
"main": "dist/index.js",
"types": "dist/src/index.d.ts",
"scripts": {
"build": "tsup",
"start": "npx ./dist create"
},
"files": [
"dist/**/*"
],
"devDependencies": {
"prettier": "^3.3.2"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"description": "RageFW RPC",
"keywords": [],
"author": "SashaGoncharov19",
"contributors": [{
"name": "rilaxik",
"email": "dev.rilaxik@gmail.com",
"url": "https://github.com/rilaxik"
}],
"license": "MIT"
}

44
apps/rpc/src/browser.ts Normal file
View File

@ -0,0 +1,44 @@
import { Wrapper } from './wrapper'
import { Environment, Errors, Events, RPCState, Utils } from './utils'
class Browser extends Wrapper {
constructor() {
super()
}
public resolveEmitDestination(dataRaw: string) {
if (!dataRaw) throw new Error(Errors.NO_DATA)
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.EVENT_LISTENER, dataRaw)
}
private emit(dataRaw: string) {
let state = Utils.prepareExecution(dataRaw)
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
return
}
const { eventName, data } = Utils.prepareExecution(dataRaw)
this.state_[eventName](...data)
}
}
const browser = new Browser()
export { browser }

65
apps/rpc/src/client.ts Normal file
View File

@ -0,0 +1,65 @@
import { Wrapper } from './wrapper'
import { Environment, Errors, Events, Utils } from './utils'
import type { RPCState } from './utils'
class Client extends Wrapper {
private _browser: any = null
constructor() {
super()
}
set browser(browser: any) {
this._browser = browser
}
public resolveEmitDestination(dataRaw: string) {
if (!dataRaw) throw new Error(Errors.NO_DATA)
const state = Utils.prepareExecution(dataRaw)
switch (state.calledTo) {
case Environment.SERVER:
this.emitServer(dataRaw)
break
case Environment.BROWSER:
this.emitBrowser(dataRaw, state)
break
case Environment.CLIENT:
this.emit(state)
break
default:
this.triggerError_(state, Errors.UNKNOWN_ENVIRONMENT)
break
}
}
private emit(state: RPCState) {
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
return
}
this.state_[state.eventName](...state.data)
}
private emitServer(dataRaw: string) {
mp.events.callRemote(Events.EVENT_LISTENER, dataRaw)
}
private emitBrowser(dataRaw: string, state: RPCState) {
if (!this._browser) {
this.triggerError_(state, Errors.NO_BROWSER)
return
}
this._browser.call(Events.EVENT_LISTENER, dataRaw)
}
}
const client = new Client()
export { client }

131
apps/rpc/src/index.ts Normal file
View File

@ -0,0 +1,131 @@
import { Wrapper } from './wrapper'
import { Environment, Errors, Events, RPCState, Utils } from './utils'
import { server } from './server'
import { client } from './client'
import { browser } from './browser'
class Rpc extends Wrapper {
constructor() {
super()
if (this.environment_ === Environment.UNKNOWN)
throw new Error(Errors.UNKNOWN_ENVIRONMENT)
mp.events.add(
Events.EVENT_LISTENER,
async (player: any, dataRaw: string) => {
if (!dataRaw) throw new Error(Errors.NO_DATA)
switch (this.environment_) {
case Environment.SERVER:
server.resolveEmitDestination(player, dataRaw)
break
case Environment.CLIENT:
dataRaw = player
client.resolveEmitDestination(dataRaw)
break
case Environment.BROWSER:
dataRaw = player
browser.resolveEmitDestination(dataRaw)
break
default:
void { player, dataRaw }
break
}
},
)
}
public register(
eventName: string,
cb: (...args: unknown[]) => unknown,
): void {
Utils.errorUnknownEnvironment(this.environment_)
this.state_[eventName] = cb
}
public unregister(
eventName: string
): void {
Utils.errorUnknownEnvironment(this.environment_)
delete this.state_[eventName]
}
public callClient(eventName: string, args: unknown[]) {
Utils.errorUnknownEnvironment(this.environment_)
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName,
calledTo: Environment.CLIENT,
calledFrom: this.environment_,
knownError: undefined,
data: args,
}
const dataRaw = Utils.prepareTransfer(state)
mp.events.call(Events.EVENT_LISTENER, dataRaw)
}
public callServer(eventName: string, args: unknown[]) {
Utils.errorUnknownEnvironment(this.environment_)
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName,
calledTo: Environment.SERVER,
calledFrom: this.environment_,
knownError: undefined,
data: args,
}
const dataRaw = Utils.prepareTransfer(state)
mp.events.call(Events.EVENT_LISTENER, dataRaw)
}
public callBrowser(eventName: string, args: unknown[]) {
Utils.errorUnknownEnvironment(this.environment_)
const state: RPCState = {
uuid: Utils.generateUUID(),
eventName,
calledTo: Environment.BROWSER,
calledFrom: this.environment_,
knownError: undefined,
data: args,
}
const dataRaw = Utils.prepareTransfer(state)
mp.events.call(Events.EVENT_LISTENER, dataRaw)
}
public call(eventName: string, args: unknown[]) {
Utils.errorUnknownEnvironment(this.environment_)
let state: RPCState = {
uuid: Utils.generateUUID(),
eventName,
calledTo: this.environment_,
calledFrom: this.environment_,
knownError: undefined,
data: args,
}
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
return
}
}
}
const rpc = new Rpc()
export { rpc }

44
apps/rpc/src/server.ts Normal file
View File

@ -0,0 +1,44 @@
import { Wrapper } from './wrapper'
import { Environment, Errors, Events, Utils } from './utils'
class Server extends Wrapper {
constructor() {
super()
}
public resolveEmitDestination(player: any, dataRaw: string) {
if (!dataRaw) throw new Error(Errors.NO_DATA)
let state = Utils.prepareExecution(dataRaw)
switch (state.calledTo) {
case Environment.SERVER:
this.emit(player, dataRaw)
break
default:
this.emitClient(player, dataRaw)
break
}
}
private emitClient(player: any, dataRaw: string) {
player.call(Events.EVENT_LISTENER, dataRaw)
}
private emit(player: any, dataRaw: string) {
let state = Utils.prepareExecution(dataRaw)
state = this.verifyEvent_(state)
if (state.knownError) {
this.triggerError_(state, state.knownError)
return
}
const { eventName, data } = Utils.prepareExecution(dataRaw)
this.state_[eventName](...data)
}
}
const server = new Server()
export { server }

73
apps/rpc/src/utils.ts Normal file
View File

@ -0,0 +1,73 @@
export enum Environment {
BROWSER = 'BROWSER',
CLIENT = 'CLIENT',
SERVER = 'SERVER',
UNKNOWN = 'UNKNOWN',
}
export enum Events {
EVENT_LISTENER = '__rpc:listener',
EVENT_RESPONSE = '__rpc:response',
}
export enum Errors {
EVENT_NOT_REGISTERED = 'Event not registered',
UNKNOWN_ENVIRONMENT = 'Unknown environment',
NO_DATA = 'No data',
NO_BROWSER = 'You need to initialize browser first',
}
export type RPCState = {
eventName: string
uuid: string
knownError?: string
data?: any
calledFrom: Environment
calledTo: Environment
}
export class Utils {
public static getEnvironment(): Environment {
if ('joaat' in mp) return Environment.SERVER
if ('game' in mp && 'joaat' in (mp.game as { joaat?: unknown }))
return Environment.CLIENT
if ('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)
}
}

41
apps/rpc/src/wrapper.ts Normal file
View File

@ -0,0 +1,41 @@
import { Environment, Errors, RPCState, Utils } from './utils'
export class Wrapper {
protected environment_ = Utils.getEnvironment()
protected state_ =
this.environment_ === Environment.BROWSER
? (window.rpcEvents = {} as Record<
string,
(...args: any[]) => unknown
>)
: (global.rpcEvents = {} as Record<
string,
(...args: any[]) => unknown
>)
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 | '))
}
}

25
apps/rpc/tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"lib": ["ES6"],
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "bin",
"esModuleInterop": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noImplicitAny": true,
},
"include": [
"src/**/*",
"./index.d.ts"
],
"exclude": [
"node_modules"
]
}

11
apps/rpc/tsup.config.ts Normal file
View File

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

View File

@ -8,6 +8,6 @@
"build": "esbuild src/index.ts --bundle --platform=node --target=node10.4 --outfile=../../server/packages/server/index.js" "build": "esbuild src/index.ts --bundle --platform=node --target=node10.4 --outfile=../../server/packages/server/index.js"
}, },
"dependencies": { "dependencies": {
"rage-fw-server": "latest" "rpc": "workspace:^"
} }
} }

View File

@ -7,6 +7,11 @@
"scripts": { "scripts": {
"server:update": "cd server && rage-win64.exe", "server:update": "cd server && rage-win64.exe",
"i:client": "cd apps/client && pnpm i rpc",
"i:server": "cd apps/server && pnpm i rpc",
"i:cef": "cd apps/cef && pnpm i rpc",
"i:all": "pnpm i:client && pnpm i:server && pnpm i:cef",
"build:client": "cd apps/client && pnpm build", "build:client": "cd apps/client && pnpm build",
"build:server": "cd apps/server && pnpm build", "build:server": "cd apps/server && pnpm build",
"build:cef": "cd apps/cef && pnpm build", "build:cef": "cd apps/cef && pnpm build",

View File

@ -3,3 +3,4 @@ packages:
- "apps/client" - "apps/client"
- "apps/server" - "apps/server"
- "apps/shared" - "apps/shared"
- "apps/rpc"