From 092693acb5de9965f8fb1ab73e462fb8225bd3d7 Mon Sep 17 00:00:00 2001 From: Danya H Date: Thu, 3 Oct 2024 12:56:21 +0100 Subject: [PATCH] rpc test --- apps/cef/package.json | 2 +- apps/client/package.json | 2 +- apps/rpc/.prettierrc.yaml | 6 ++ apps/rpc/index.d.ts | 9 +++ apps/rpc/package.json | 28 ++++++++ apps/rpc/src/browser.ts | 44 +++++++++++++ apps/rpc/src/client.ts | 65 +++++++++++++++++++ apps/rpc/src/index.ts | 131 ++++++++++++++++++++++++++++++++++++++ apps/rpc/src/server.ts | 44 +++++++++++++ apps/rpc/src/utils.ts | 73 +++++++++++++++++++++ apps/rpc/src/wrapper.ts | 41 ++++++++++++ apps/rpc/tsconfig.json | 25 ++++++++ apps/rpc/tsup.config.ts | 11 ++++ apps/server/package.json | 2 +- package.json | 5 ++ pnpm-workspace.yaml | 3 +- 16 files changed, 487 insertions(+), 4 deletions(-) create mode 100644 apps/rpc/.prettierrc.yaml create mode 100644 apps/rpc/index.d.ts create mode 100644 apps/rpc/package.json create mode 100644 apps/rpc/src/browser.ts create mode 100644 apps/rpc/src/client.ts create mode 100644 apps/rpc/src/index.ts create mode 100644 apps/rpc/src/server.ts create mode 100644 apps/rpc/src/utils.ts create mode 100644 apps/rpc/src/wrapper.ts create mode 100644 apps/rpc/tsconfig.json create mode 100644 apps/rpc/tsup.config.ts diff --git a/apps/cef/package.json b/apps/cef/package.json index e16d3d4..8af2839 100644 --- a/apps/cef/package.json +++ b/apps/cef/package.json @@ -14,7 +14,7 @@ "dependencies": { "react": "^18.2.0", "react-dom": "^18.2.0", - "rage-fw-cef": "latest" + "rpc": "workspace:^" }, "devDependencies": { "@types/react": "^18.2.66", diff --git a/apps/client/package.json b/apps/client/package.json index 0dada23..b3d2f88 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -8,6 +8,6 @@ "build": "esbuild src/index.ts --bundle --platform=node --outfile=../../server/client_packages/index.js --format=esm" }, "dependencies": { - "rage-fw-client": "latest" + "rpc": "workspace:^" } } diff --git a/apps/rpc/.prettierrc.yaml b/apps/rpc/.prettierrc.yaml new file mode 100644 index 0000000..aa7ff98 --- /dev/null +++ b/apps/rpc/.prettierrc.yaml @@ -0,0 +1,6 @@ +tabWidth: 4 +printWidth: 80 +singleQuote: true +semi: false +arrowParens: avoid +endOfLine: auto diff --git a/apps/rpc/index.d.ts b/apps/rpc/index.d.ts new file mode 100644 index 0000000..5991e99 --- /dev/null +++ b/apps/rpc/index.d.ts @@ -0,0 +1,9 @@ +declare const mp: any + +declare const global: { + rpcEvents: Record unknown> +} + +declare const window: { + rpcEvents: Record unknown> +} diff --git a/apps/rpc/package.json b/apps/rpc/package.json new file mode 100644 index 0000000..b0a239d --- /dev/null +++ b/apps/rpc/package.json @@ -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" +} diff --git a/apps/rpc/src/browser.ts b/apps/rpc/src/browser.ts new file mode 100644 index 0000000..994d93e --- /dev/null +++ b/apps/rpc/src/browser.ts @@ -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 } diff --git a/apps/rpc/src/client.ts b/apps/rpc/src/client.ts new file mode 100644 index 0000000..314ac73 --- /dev/null +++ b/apps/rpc/src/client.ts @@ -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 } diff --git a/apps/rpc/src/index.ts b/apps/rpc/src/index.ts new file mode 100644 index 0000000..9534107 --- /dev/null +++ b/apps/rpc/src/index.ts @@ -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 } diff --git a/apps/rpc/src/server.ts b/apps/rpc/src/server.ts new file mode 100644 index 0000000..bf747d2 --- /dev/null +++ b/apps/rpc/src/server.ts @@ -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 } diff --git a/apps/rpc/src/utils.ts b/apps/rpc/src/utils.ts new file mode 100644 index 0000000..a350c5f --- /dev/null +++ b/apps/rpc/src/utils.ts @@ -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) + } +} diff --git a/apps/rpc/src/wrapper.ts b/apps/rpc/src/wrapper.ts new file mode 100644 index 0000000..f3e054a --- /dev/null +++ b/apps/rpc/src/wrapper.ts @@ -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 | ')) + } +} diff --git a/apps/rpc/tsconfig.json b/apps/rpc/tsconfig.json new file mode 100644 index 0000000..f6ed9e9 --- /dev/null +++ b/apps/rpc/tsconfig.json @@ -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" + ] +} \ No newline at end of file diff --git a/apps/rpc/tsup.config.ts b/apps/rpc/tsup.config.ts new file mode 100644 index 0000000..b1987c1 --- /dev/null +++ b/apps/rpc/tsup.config.ts @@ -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, +}) diff --git a/apps/server/package.json b/apps/server/package.json index 5c2b8b6..5bc860e 100644 --- a/apps/server/package.json +++ b/apps/server/package.json @@ -8,6 +8,6 @@ "build": "esbuild src/index.ts --bundle --platform=node --target=node10.4 --outfile=../../server/packages/server/index.js" }, "dependencies": { - "rage-fw-server": "latest" + "rpc": "workspace:^" } } diff --git a/package.json b/package.json index 129698b..82f938d 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,11 @@ "scripts": { "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:server": "cd apps/server && pnpm build", "build:cef": "cd apps/cef && pnpm build", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 1aaea1b..85b7e6f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,4 +2,5 @@ packages: - "apps/cef" - "apps/client" - "apps/server" - - "apps/shared" \ No newline at end of file + - "apps/shared" + - "apps/rpc" \ No newline at end of file