Rpc integration + type fixes #3
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										22
									
								
								rpc/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								rpc/package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "rpc",
 | 
			
		||||
    "version": "0.1.0",
 | 
			
		||||
    "main": "dist/index.js",
 | 
			
		||||
    "types": "dist/src/index.d.ts",
 | 
			
		||||
    "scripts": {
 | 
			
		||||
        "watch": "tsc -w",
 | 
			
		||||
        "build": "tsup",
 | 
			
		||||
        "start": "npx ./dist create"
 | 
			
		||||
    },
 | 
			
		||||
    "files": [
 | 
			
		||||
        "dist/**/*"
 | 
			
		||||
    ],
 | 
			
		||||
    "description": "CLI to scaffold a template project for RageFW",
 | 
			
		||||
    "keywords": [],
 | 
			
		||||
    "author": "rilaxik",
 | 
			
		||||
    "license": "ISC",
 | 
			
		||||
    "devDependencies": {
 | 
			
		||||
        "prettier": "^3.3.2",
 | 
			
		||||
        "typescript": "^5.4.5"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										2
									
								
								rpc/src/events.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								rpc/src/events.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
			
		||||
export const EVENT_LISTENER = '__rpc:listener'
 | 
			
		||||
export const EVENT_RESPONSE = '__rpc:response'
 | 
			
		||||
							
								
								
									
										61
									
								
								rpc/src/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								rpc/src/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,61 @@
 | 
			
		||||
import { Environment, utils } from './utils'
 | 
			
		||||
import { EVENT_LISTENER } from './events'
 | 
			
		||||
 | 
			
		||||
import { client } from './modules/client'
 | 
			
		||||
import { server } from './modules/server'
 | 
			
		||||
 | 
			
		||||
const environment = utils.getEnvironment()
 | 
			
		||||
 | 
			
		||||
const state = environment === Environment.CEF ? window : global
 | 
			
		||||
 | 
			
		||||
class rpc {
 | 
			
		||||
    constructor() {
 | 
			
		||||
        if (environment === Environment.UNKNOWN) return
 | 
			
		||||
 | 
			
		||||
        mp.events.add(EVENT_LISTENER, async (player: any, request: string) => {
 | 
			
		||||
            switch (environment) {
 | 
			
		||||
                case Environment.SERVER:
 | 
			
		||||
                    await server.listenEvent(player, request)
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
                case Environment.CLIENT:
 | 
			
		||||
                    request = player
 | 
			
		||||
 | 
			
		||||
                    await client.listenEvent(request)
 | 
			
		||||
                    break
 | 
			
		||||
            }
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public register<Callback extends any[] = unknown[], Return = unknown>(
 | 
			
		||||
        eventName: string,
 | 
			
		||||
        cb: (...args: Callback) => Return,
 | 
			
		||||
    ) {
 | 
			
		||||
        if (environment === Environment.UNKNOWN) return
 | 
			
		||||
        state[eventName] = cb
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async callClient<Args extends any[] = unknown[], Return = unknown>(
 | 
			
		||||
        player: any,
 | 
			
		||||
        eventName: string,
 | 
			
		||||
        ...args: Args
 | 
			
		||||
    ): Promise<Return | unknown> {
 | 
			
		||||
        if (environment === Environment.UNKNOWN) return
 | 
			
		||||
        if (environment === Environment.SERVER) {
 | 
			
		||||
            return client.executeClient(player, eventName, args)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async callServer<Args extends any[] = unknown[], Return = unknown>(
 | 
			
		||||
        eventName: string,
 | 
			
		||||
        ...args: Args
 | 
			
		||||
    ): Promise<Return | unknown> {
 | 
			
		||||
        if (environment === Environment.UNKNOWN) return
 | 
			
		||||
        if (environment === Environment.CLIENT) {
 | 
			
		||||
            return server.executeServer(eventName, args)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const testRpc = new rpc()
 | 
			
		||||
export { testRpc }
 | 
			
		||||
							
								
								
									
										94
									
								
								rpc/src/modules/client.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								rpc/src/modules/client.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,94 @@
 | 
			
		||||
import { EVENT_LISTENER } from '../events'
 | 
			
		||||
import { Wrapper } from './wrapper'
 | 
			
		||||
import { RPCState } from '../utils'
 | 
			
		||||
 | 
			
		||||
class Client extends Wrapper {
 | 
			
		||||
    private sendResponseToServer(data: RPCState) {
 | 
			
		||||
        const eventName = this._utils.generateResponseEventName(data.uuid)
 | 
			
		||||
        const preparedData = this._utils.prepareForTransfer(data)
 | 
			
		||||
        mp.events.callRemote(eventName, preparedData)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async listenEvent(data: string) {
 | 
			
		||||
        const rpcData = this._verifyEvent(data)
 | 
			
		||||
 | 
			
		||||
        if (rpcData.knownError) {
 | 
			
		||||
            this._triggerError(rpcData)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const fnResponse = await this._state[rpcData.eventName](
 | 
			
		||||
                ...rpcData.data,
 | 
			
		||||
            )
 | 
			
		||||
            const response = {
 | 
			
		||||
                ...rpcData,
 | 
			
		||||
                data: fnResponse,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.sendResponseToServer(response)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            this._triggerError(rpcData, e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleClientServerReturn(
 | 
			
		||||
        uuid: string,
 | 
			
		||||
        resolve: (value: unknown) => void,
 | 
			
		||||
        reject: (reason?: any) => void,
 | 
			
		||||
    ) {
 | 
			
		||||
        const responseEvent = this._utils.generateResponseEventName(uuid)
 | 
			
		||||
        const timeoutDuration = 1000 * 10
 | 
			
		||||
 | 
			
		||||
        const timeoutID = setTimeout(() => {
 | 
			
		||||
            reject(new Error('Timeout ended'))
 | 
			
		||||
            mp.events.remove(responseEvent)
 | 
			
		||||
        }, timeoutDuration)
 | 
			
		||||
 | 
			
		||||
        const handler = (_: any, response: string) => {
 | 
			
		||||
            const { knownError, data } = this._utils.prepareForExecute(response)
 | 
			
		||||
 | 
			
		||||
            if (knownError)
 | 
			
		||||
                try {
 | 
			
		||||
                    clearTimeout(timeoutID)
 | 
			
		||||
                    reject(knownError)
 | 
			
		||||
                    return
 | 
			
		||||
                } catch (e) {}
 | 
			
		||||
 | 
			
		||||
            resolve(data)
 | 
			
		||||
            mp.events.remove(responseEvent)
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                clearTimeout(timeoutID)
 | 
			
		||||
            } catch (e) {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mp.events.add(responseEvent, handler)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async executeClient<
 | 
			
		||||
        Args extends any[] = unknown[],
 | 
			
		||||
        Return = unknown,
 | 
			
		||||
    >(
 | 
			
		||||
        player: any,
 | 
			
		||||
        eventName: string,
 | 
			
		||||
        ...args: Args
 | 
			
		||||
    ): Promise<Return | unknown> {
 | 
			
		||||
        return new Promise((resolve, reject) => {
 | 
			
		||||
            const uuid = this._utils.generateUUID()
 | 
			
		||||
 | 
			
		||||
            const data: RPCState = {
 | 
			
		||||
                uuid,
 | 
			
		||||
                eventName,
 | 
			
		||||
                calledFrom: this._environment,
 | 
			
		||||
                data: args,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            player.call(EVENT_LISTENER, [this._utils.prepareForTransfer(data)])
 | 
			
		||||
 | 
			
		||||
            this.handleClientServerReturn(uuid, resolve, reject)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const client = new Client()
 | 
			
		||||
							
								
								
									
										95
									
								
								rpc/src/modules/server.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										95
									
								
								rpc/src/modules/server.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,95 @@
 | 
			
		||||
import { Wrapper } from './wrapper'
 | 
			
		||||
import { RPCState, utils } from '../utils'
 | 
			
		||||
import { EVENT_LISTENER } from '../events'
 | 
			
		||||
 | 
			
		||||
class Server extends Wrapper {
 | 
			
		||||
    private sendResponseToClient(player: any, data: RPCState) {
 | 
			
		||||
        const eventName = this._utils.generateResponseEventName(data.uuid)
 | 
			
		||||
        const preparedData = this._utils.prepareForTransfer(data)
 | 
			
		||||
 | 
			
		||||
        player.call(eventName, [preparedData])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async listenEvent(player: any, data: string) {
 | 
			
		||||
        const rpcData = this._verifyEvent(data)
 | 
			
		||||
 | 
			
		||||
        if (rpcData.knownError) {
 | 
			
		||||
            this._triggerError(rpcData)
 | 
			
		||||
            return
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const fnResponse = await this._state[rpcData.eventName](
 | 
			
		||||
                ...rpcData.data,
 | 
			
		||||
            )
 | 
			
		||||
            const response = {
 | 
			
		||||
                ...rpcData,
 | 
			
		||||
                data: fnResponse,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            this.sendResponseToClient(player, response)
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            this._triggerError(rpcData, e)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private handleServerClientReturn(
 | 
			
		||||
        uuid: string,
 | 
			
		||||
        resolve: (value: unknown) => void,
 | 
			
		||||
        reject: (reason?: any) => void,
 | 
			
		||||
    ) {
 | 
			
		||||
        const responseEvent = this._utils.generateResponseEventName(uuid)
 | 
			
		||||
        const timeoutDuration = 1000 * 10
 | 
			
		||||
 | 
			
		||||
        const timeoutID = setTimeout(() => {
 | 
			
		||||
            reject(new Error('Timeout ended'))
 | 
			
		||||
            mp.events.remove(responseEvent)
 | 
			
		||||
        }, timeoutDuration)
 | 
			
		||||
 | 
			
		||||
        const handler = (response: string) => {
 | 
			
		||||
            const { knownError, data } = this._utils.prepareForExecute(response)
 | 
			
		||||
 | 
			
		||||
            if (knownError) {
 | 
			
		||||
                try {
 | 
			
		||||
                    clearTimeout(timeoutID)
 | 
			
		||||
                    reject(knownError)
 | 
			
		||||
                    return
 | 
			
		||||
                } catch (e) {}
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            resolve(data)
 | 
			
		||||
            mp.events.remove(responseEvent)
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                clearTimeout(timeoutID)
 | 
			
		||||
            } catch (e) {}
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        mp.events.add(responseEvent, handler)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async executeServer<
 | 
			
		||||
        Args extends any[] = unknown[],
 | 
			
		||||
        Return = unknown,
 | 
			
		||||
    >(eventName: string, ...args: Args): Promise<Return | unknown> {
 | 
			
		||||
        return new Promise((resolve, reject) => {
 | 
			
		||||
            const uuid = this._utils.generateUUID()
 | 
			
		||||
 | 
			
		||||
            const data: RPCState = {
 | 
			
		||||
                uuid,
 | 
			
		||||
                eventName,
 | 
			
		||||
                calledFrom: this._environment,
 | 
			
		||||
                data: args,
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            mp.events.callRemote(
 | 
			
		||||
                EVENT_LISTENER,
 | 
			
		||||
                this._utils.prepareForTransfer(data),
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            this.handleServerClientReturn(uuid, resolve, reject)
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const server = new Server()
 | 
			
		||||
							
								
								
									
										32
									
								
								rpc/src/modules/wrapper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								rpc/src/modules/wrapper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
import { Environment, Errors, RPCState, utils } from '../utils'
 | 
			
		||||
 | 
			
		||||
export class Wrapper {
 | 
			
		||||
    public _utils = utils
 | 
			
		||||
    public _environment = utils.getEnvironment()
 | 
			
		||||
    public _state = this._environment === Environment.CEF ? window : global
 | 
			
		||||
 | 
			
		||||
    public _verifyEvent(data: string): RPCState {
 | 
			
		||||
        const rpcData = utils.prepareForExecute(data)
 | 
			
		||||
 | 
			
		||||
        if (!this._state[rpcData.eventName]) {
 | 
			
		||||
            rpcData.knownError = Errors.EVENT_NOT_REGISTERED
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return rpcData
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public _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(' | '))
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										13
									
								
								rpc/src/types.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								rpc/src/types.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
			
		||||
declare const mp: any
 | 
			
		||||
declare const console: any
 | 
			
		||||
 | 
			
		||||
declare const setTimeout: (fn: Function, time: number) => number
 | 
			
		||||
declare const clearTimeout: (id: number) => void
 | 
			
		||||
 | 
			
		||||
declare const global: {
 | 
			
		||||
    [p: string]: (...args: any[]) => unknown
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
declare const window: {
 | 
			
		||||
    [p: string]: (...args: any[]) => unknown
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										62
									
								
								rpc/src/utils.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								rpc/src/utils.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,62 @@
 | 
			
		||||
import { EVENT_RESPONSE } from './events'
 | 
			
		||||
 | 
			
		||||
export enum Environment {
 | 
			
		||||
    CEF = 'CEF',
 | 
			
		||||
    CLIENT = 'CLIENT',
 | 
			
		||||
    SERVER = 'SERVER',
 | 
			
		||||
    UNKNOWN = 'UNKNOWN',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum Errors {
 | 
			
		||||
    EVENT_NOT_REGISTERED = 'Event not registered',
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type RPCState = {
 | 
			
		||||
    eventName: string
 | 
			
		||||
    uuid: string
 | 
			
		||||
    knownError?: string
 | 
			
		||||
    data?: any
 | 
			
		||||
    calledFrom: Environment
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class Utils {
 | 
			
		||||
    public getEnvironment(): Environment {
 | 
			
		||||
        if (mp.joaat) return Environment.SERVER
 | 
			
		||||
        if (mp.game && mp.game.joaat) return Environment.CLIENT
 | 
			
		||||
        if ('mp' in window) return Environment.CEF
 | 
			
		||||
        return Environment.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public prepareForExecute(data: string): RPCState {
 | 
			
		||||
        return JSON.parse(data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public prepareForTransfer(data: RPCState): string {
 | 
			
		||||
        return JSON.stringify(data)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public 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 generateResponseEventName(uuid: string): string {
 | 
			
		||||
        return `${EVENT_RESPONSE}_${uuid}`
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const utils = new Utils()
 | 
			
		||||
							
								
								
									
										24
									
								
								rpc/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								rpc/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,24 @@
 | 
			
		||||
{
 | 
			
		||||
  "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/**/*"
 | 
			
		||||
  ],
 | 
			
		||||
  "exclude": [
 | 
			
		||||
    "node_modules"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										12
									
								
								rpc/tsup.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								rpc/tsup.config.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,12 @@
 | 
			
		||||
import { defineConfig } from 'tsup'
 | 
			
		||||
 | 
			
		||||
export default defineConfig({
 | 
			
		||||
    entry: ['src/index.ts'],
 | 
			
		||||
    outDir: './dist',
 | 
			
		||||
    format: ['cjs'],
 | 
			
		||||
    noExternal: ['rage-rpc'],
 | 
			
		||||
    experimentalDts: true,
 | 
			
		||||
    splitting: false,
 | 
			
		||||
    sourcemap: false,
 | 
			
		||||
    clean: true,
 | 
			
		||||
})
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user