Skip to content

Commit

Permalink
refactor(game/api/onCall): refactor and allow for multiple handlers p…
Browse files Browse the repository at this point in the history
…er number
  • Loading branch information
LiamDormon committed Jan 28, 2024
1 parent 666429a commit 3407bec
Show file tree
Hide file tree
Showing 5 changed files with 195 additions and 90 deletions.
67 changes: 3 additions & 64 deletions apps/game/server/calls/calls.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<InitializeCallDTO, ActiveCall>(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<void>((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' });
Expand Down
94 changes: 94 additions & 0 deletions apps/game/server/calls/middleware/index.ts
Original file line number Diff line number Diff line change
@@ -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<string, (ctx: OnCallExportCtx) => 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<InitializeCallDTO>,
resp: PromiseEventResp<ActiveCall>,
) {
return new Promise<OnCallStatus>((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)
})
25 changes: 0 additions & 25 deletions apps/game/server/calls/middleware/onCall.ts

This file was deleted.

89 changes: 89 additions & 0 deletions apps/game/server/calls/middleware/oncall.service.ts
Original file line number Diff line number Diff line change
@@ -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<string, CallMiddleware[]>
private resourcesTracked: Set<string>

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<InitializeCallDTO>,
resp: PromiseEventResp<ActiveCall>
) {
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()
10 changes: 9 additions & 1 deletion typings/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,24 @@ export enum CallEvents {
TOGGLE_MUTE_CALL = 'npwd:toggleMuteCall',
}

interface IncomingCallerCtx {
export interface IncomingCallerCtx {
source: number;
number: string;
name: string;
}

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
}

0 comments on commit 3407bec

Please sign in to comment.