diff --git a/apps/game/server/calls/calls.controller.ts b/apps/game/server/calls/calls.controller.ts index cab673d3f..cd411eb29 100644 --- a/apps/game/server/calls/calls.controller.ts +++ b/apps/game/server/calls/calls.controller.ts @@ -10,72 +10,11 @@ import { getSource, onNetTyped } from '../utils/miscUtils'; import CallService from './calls.service'; import { callLogger } from './calls.utils'; import { onNetPromise } from '../lib/PromiseNetEvents/onNetPromise'; -import { OnCallMap } from './middleware/onCall'; -import PlayerService from '../players/player.service'; -import MessagesService from '../messages/messages.service'; -import { v4 as uuidv4 } from 'uuid'; +import OncallService from "./middleware/oncall.service"; +import './middleware' onNetPromise(CallEvents.INITIALIZE_CALL, async (reqObj, resp) => { - const funcRef = OnCallMap.get(reqObj.data.receiverNumber); - if (funcRef) { - const caller = PlayerService.getPlayer(reqObj.source); - const incomingCaller = { - source: reqObj.source, - name: caller.getName(), - number: caller.getPhoneNumber(), - }; - try { - await new Promise((resolve, reject) => { - funcRef({ - receiverNumber: reqObj.data.receiverNumber, - incomingCaller, - next: () => { - resolve(); - }, - exit: () => { - reject(); - }, - reply: (message) => { - MessagesService.handleEmitMessage({ - senderNumber: reqObj.data.receiverNumber, - targetNumber: incomingCaller.number, - message, - }); - }, - forward: (receiverNumber: string, isAnonymous = false) => { - CallService.handleInitializeCall({ ...reqObj, data: { receiverNumber, isAnonymous } }, resp) - .catch((e) => { - resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); - callLogger.error(`Error occured handling init call: ${e.message}`); - }) - .then(() => { - return; - }) - .catch(reject); - }, - }); - }); - } catch (e) { - const tempSaveCallObj = { - identifier: uuidv4(), - isTransmitter: true, - transmitter: incomingCaller.number, - receiver: reqObj.data.receiverNumber, - is_accepted: false, - isUnavailable: true, - start: Math.floor(new Date().getTime() / 1000).toString(), - }; - - resp({ - status: 'ok', - data: tempSaveCallObj, - }); - - return setTimeout(() => { - emitNet(CallEvents.WAS_REJECTED, reqObj.source, tempSaveCallObj); - }, 2000); - } - } + await OncallService.handle(reqObj, resp) CallService.handleInitializeCall(reqObj, resp).catch((e) => { resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); diff --git a/apps/game/server/calls/middleware/index.ts b/apps/game/server/calls/middleware/index.ts new file mode 100644 index 000000000..be78d5c38 --- /dev/null +++ b/apps/game/server/calls/middleware/index.ts @@ -0,0 +1,94 @@ +import { + ActiveCall, + CallMiddlewareInvokable, + IncomingCallerCtx, + InitializeCallDTO, + OnCallExportCtx, OnCallStatus +} from "@typings/call"; +import OncallService from './oncall.service'; +import MessagesService from '../../messages/messages.service'; +import CallService from '../calls.service'; +import { callLogger } from '../calls.utils'; +import { PromiseEventResp, PromiseRequest } from '../../lib/PromiseNetEvents/promise.types'; + +const exp = global.exports; + +export const OnCallMap = new Map void>(); + +/* + Will add middleware for targeted numbers with the following context: + interface IncomingCallerCtx { + source: number; + number: string; + name: string; + } + + export interface OnCallExportCtx { + incomingCaller: IncomingCallerCtx; + exit: () => void; + next: () => void; + reply: (msg: string) => void; + forward: (tgt: string) => void; + } + */ +exp('onCall', (tgtNumber: string, cb: CallMiddlewareInvokable) => { + const resourceName = GetInvokingResource() + const handler = new CallMiddleware(cb, resourceName, tgtNumber.toString()) + callLogger.debug(`Resource [${resourceName}] registered an onCall handler for number [${tgtNumber}]`) + OncallService.addHandler(handler) +}); + +export class CallMiddleware { + constructor( + private funcRef: CallMiddlewareInvokable, + public hostResource: string, + public target: string, + ) {} + + invoke( + incomingCaller: IncomingCallerCtx, + reqObj: PromiseRequest, + resp: PromiseEventResp, + ) { + return new Promise((resolve, reject) => + this.funcRef({ + receiverNumber: reqObj.data.receiverNumber, + incomingCaller, + next: () => { + resolve(OnCallStatus.NEXT); + return; + }, + exit: () => { + reject(); + return; + }, + reply: (message) => { + MessagesService.handleEmitMessage({ + senderNumber: reqObj.data.receiverNumber, + targetNumber: incomingCaller.number, + message, + }); + }, + forward: (receiverNumber: string, isAnonymous = false) => { + CallService.handleInitializeCall( + { ...reqObj, data: { receiverNumber, isAnonymous } }, + resp, + ) + .catch((e) => { + resp({ status: 'error', errorMsg: 'SERVER_ERROR' }); + callLogger.error(`Error occured handling init call: ${e.message}`); + }) + .then(() => { + resolve(OnCallStatus.FORWARD) + return; + }) + .catch(reject); + }, + }), + ); + } +} + +on("onResourceStop", (resource: string) => { + OncallService.resetResource(resource) +}) diff --git a/apps/game/server/calls/middleware/onCall.ts b/apps/game/server/calls/middleware/onCall.ts deleted file mode 100644 index 119a8e912..000000000 --- a/apps/game/server/calls/middleware/onCall.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { OnCallExportCtx } from '@typings/call'; - -const exp = global.exports; - -export const OnCallMap = new Map void>(); - -/* - Will add middleware for targeted numbers with the following context: - interface IncomingCallerCtx { - source: number; - number: string; - name: string; - } - - export interface OnCallExportCtx { - incomingCaller: IncomingCallerCtx; - exit: () => void; - next: () => void; - reply: (msg: string) => void; - forward: (tgt: string) => void; - } - */ -exp('onCall', (tgtNumber: string, cb: (ctx: OnCallExportCtx) => void) => { - OnCallMap.set(tgtNumber, cb); -}); diff --git a/apps/game/server/calls/middleware/oncall.service.ts b/apps/game/server/calls/middleware/oncall.service.ts new file mode 100644 index 000000000..6d625985b --- /dev/null +++ b/apps/game/server/calls/middleware/oncall.service.ts @@ -0,0 +1,89 @@ +import { ActiveCall, CallEvents, InitializeCallDTO, OnCallStatus } from "@typings/call"; +import { CallMiddleware } from "./index"; +import { PromiseEventResp, PromiseRequest } from "../../lib/PromiseNetEvents/promise.types"; +import PlayerService from "../../players/player.service"; +import { uuidv4 } from "../../../utils/fivem"; +import { callLogger } from "../calls.utils"; + +class OnCallService { + private callHandlers: Map + private resourcesTracked: Set + + constructor() { + this.callHandlers = new Map() + this.resourcesTracked = new Set() + } + + addHandler(handler: CallMiddleware) { + this.resourcesTracked.add(handler.hostResource) + + if (this.callHandlers.has(handler.target)){ + const handlerList = this.callHandlers.get(handler.target) + handlerList.push(handler) + + return + } + + this.callHandlers.set(handler.target, [handler]) + } + + // Keep a set containing all the resources we are tracking so that we are not doing an O(n^2) compute unnecessarily + resetResource(resource: string) { + if (!this.resourcesTracked.has(resource)) return; + + this.callHandlers.forEach((value, key, map) => { + const newList = value.filter(c => c.hostResource !== resource) + map.set(key, newList) + }) + } + + async handle( + reqObj: PromiseRequest, + resp: PromiseEventResp + ) { + callLogger.debug("invoking onCall for number", reqObj.data.receiverNumber) + if (!this.callHandlers.has(reqObj.data.receiverNumber)) { + return + } + + const caller = PlayerService.getPlayer(reqObj.source); + + const incomingCaller = { + source: reqObj.source, + name: caller.getName(), + number: caller.getPhoneNumber(), + }; + + const handlerList = this.callHandlers.get(reqObj.data.receiverNumber) + console.log(handlerList.length) + for (const handler of handlerList) { + try { + const status = await handler.invoke(incomingCaller, reqObj, resp) + if (status === OnCallStatus.FORWARD) { + break; + } + } catch (e) { + const tempSaveCallObj = { + identifier: uuidv4(), + isTransmitter: true, + transmitter: incomingCaller.number, + receiver: reqObj.data.receiverNumber, + is_accepted: false, + isUnavailable: true, + start: Math.floor(new Date().getTime() / 1000).toString(), + }; + + resp({ + status: 'ok', + data: tempSaveCallObj, + }); + + return setTimeout(() => { + emitNet(CallEvents.WAS_REJECTED, reqObj.source, tempSaveCallObj); + }, 2000); + } + } + } +} + +export default new OnCallService() \ No newline at end of file diff --git a/typings/call.ts b/typings/call.ts index 6f6268454..071248d0c 100644 --- a/typings/call.ts +++ b/typings/call.ts @@ -83,7 +83,7 @@ export enum CallEvents { TOGGLE_MUTE_CALL = 'npwd:toggleMuteCall', } -interface IncomingCallerCtx { +export interface IncomingCallerCtx { source: number; number: string; name: string; @@ -91,8 +91,16 @@ interface IncomingCallerCtx { export interface OnCallExportCtx { incomingCaller: IncomingCallerCtx; + receiverNumber: string exit: () => void; next: () => void; reply: (msg: string) => void; forward: (tgt: string) => void; } + +export type CallMiddlewareInvokable = (ctx: OnCallExportCtx) => void; + +export enum OnCallStatus { + NEXT, + FORWARD +} \ No newline at end of file