import type * as s from '@entityseven/fivem-rpc-shared-types' import { Emitter } from '../utils/emitter' import { generateUUID, stringify } from '../utils/funcs' import { RPCEvents, type RPCConfig, type RPCState, type RPCStateRaw, type RPCStateWeb, } from '../utils/types' import { Wrapper } from './wrapper' export class RPCInstanceWebview extends Wrapper { private readonly _emitterClient: Emitter private readonly _emitterServer: Emitter constructor(props: RPCConfig<'webview'>) { super(props) this._emitterClient = new Emitter() this._emitterServer = new Emitter() this.console.log('[RPC] Initialized Webview') window.addEventListener('message', (e: MessageEvent) => { if (e.data.origin === RPCEvents.LISTENER_CLIENT) { this._handleClient(e.data.data) } if (e.data.origin === RPCEvents.LISTENER_SERVER) { this._handleServer(e.data.data) } }) } // ===== HANDLERS ===== private async _handleClient(payload: RPCState) { if (this.debug) { this.console.log( `[RPC]:webview:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, ) } if (payload.calledFrom === 'client' && payload.type === 'event') { this.verifyEvent(this._emitterClient, payload) const responseData = await this._emitterClient.emit( payload.event, ...(payload.data && payload.data.length > 0 ? payload.data : []), ) const response: RPCState = { event: payload.event, uuid: payload.uuid, calledFrom: 'webview', calledTo: 'client', error: null, data: [responseData], player: payload.player, type: 'response', } await this._createHttpClientRequest(response).then() } } private async _handleServer(payload: RPCState) { if (this.debug) { this.console.log( `[RPC]:webview:accepted ${payload.type} ${payload.event} from ${payload.calledFrom}`, ) } if (payload.calledFrom === 'server' && payload.type === 'event') { this.verifyEvent(this._emitterServer, payload) const responseData = await this._emitterServer.emit( payload.event, ...(payload.data && payload.data.length > 0 ? payload.data : []), ) const response: RPCState = { event: payload.event, uuid: payload.uuid, calledFrom: 'webview', calledTo: 'server', error: null, data: [responseData], player: payload.player, type: 'response', } await this._createHttpClientRequest(response) } } // ===== CLIENT ===== public onClient< EventName extends keyof s.RPCEvents_ClientWebview, CallbackArguments extends Parameters, CallbackReturn extends ReturnType, >( eventName: EventName, cb: ( ...args: CallbackArguments ) => Awaited | Promise>, ): this { if (this.debug) { this.console.log(`[RPC]:onClient ${eventName}`) } this._emitterClient.on(eventName, cb) return this } public offClient( eventName: EventName, ): this { if (this.debug) { this.console.log(`[RPC]:offClient ${eventName}`) } this._emitterClient.off(eventName) return this } public async emitClient< EventName extends keyof s.RPCEvents_WebviewClient, Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { const payload: RPCState = { event: eventName, uuid: generateUUID(), calledFrom: 'webview', calledTo: 'client', error: null, data: args.length ? args : null, player: null, type: 'event', } return await this._createHttpClientRequest>(payload) } // ===== SERVER ===== public onServer< EventName extends keyof s.RPCEvents_ServerWebview, CallbackArguments extends Parameters, CallbackReturn extends ReturnType, >( eventName: EventName, cb: ( ...args: CallbackArguments ) => Awaited | Promise>, ): this { if (this.debug) { this.console.log(`[RPC]:onServer ${eventName}`) } this._emitterServer.on(eventName, cb) return this } public offServer( eventName: EventName, ): RPCInstanceWebview { if (this.debug) { this.console.log(`[RPC]:offServer ${eventName}`) } this._emitterServer.off(eventName) return this } public async emitServer< EventName extends keyof s.RPCEvents_WebviewServer, Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { const payload: RPCState = { event: eventName, uuid: generateUUID(), calledFrom: 'webview', calledTo: 'server', error: null, data: args.length ? args : null, player: null, type: 'event', } return await this._createHttpClientRequest>(payload) } // ===== SELF ===== public onSelf< EventName extends keyof s.RPCEvents_Webview, CallbackArguments extends Parameters, CallbackReturn extends ReturnType, >( eventName: EventName, cb: ( ...args: CallbackArguments ) => Awaited | Promise>, ): this { if (this.debug) { this.console.log(`[RPC]:onSelf ${eventName}`) } this._emitterLocal.on(eventName, cb) return this } public offSelf( eventName: EventName, ): this { if (this.debug) { this.console.log(`[RPC]:offSelf ${eventName}`) } this._emitterLocal.off(eventName) return this } public async emitSelf< EventName extends keyof s.RPCEvents_Webview, Arguments extends Parameters, Response extends ReturnType, >(eventName: EventName, ...args: Arguments): Promise> { const payload: RPCState = { event: eventName, uuid: generateUUID(), calledFrom: 'webview', calledTo: 'webview', error: null, data: args.length ? args : null, player: null, type: 'event', } if (this.debug) { this.console.log( `[RPC]:accepted ${payload.event} from ${payload.calledFrom}`, ) } this.verifyEvent(this._emitterLocal, payload) return await this._emitterLocal.emit>( payload.event, ...(payload.data && payload.data.length > 0 ? payload.data : []), ) } // ===== UTILS ===== private async _createHttpClientRequest( data: RPCStateRaw | RPCState, ): Promise { const dataRaw = typeof data === 'string' ? data : stringify(data) const options = { method: 'post', headers: { 'Content-Type': 'application/json; charset=UTF-8', }, body: dataRaw, } const resourceName = window?.GetParentResourceName?.() ?? 'nui-frame-app' return fetch( `https://${resourceName}/${RPCEvents.LISTENER_WEB}`, options, ).then(res => res.json()) } }