Skip to content

Commit

Permalink
feat: #310 - webrtc call added for audio
Browse files Browse the repository at this point in the history
  • Loading branch information
muke1908 committed Sep 17, 2024
1 parent 26a7c18 commit 888c167
Show file tree
Hide file tree
Showing 11 changed files with 291 additions and 18 deletions.
46 changes: 46 additions & 0 deletions backend/api/call/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import express, { Request, Response } from 'express';
import asyncHandler from '../../middleware/asyncHandler';
import { WebrtcSessionResponse } from '../messaging/types';
import channelValid from '../chatLink/utils/validateChannel';
import getClientInstance from '../../socket.io/clients';
import { SOCKET_TOPIC, socketEmit } from '../../socket.io';
const router = express.Router({ mergeParams: true });

const clients = getClientInstance();

router.post(
"/",
asyncHandler(async (req: Request, res: Response): Promise<Response<WebrtcSessionResponse>> => {
const { description, sender, channel } = req.body;

if (!description) {
return res.send(400);
}

const { valid } = await channelValid(channel);

if (!valid) {
return res.sendStatus(404);
}
const usersInChannel = clients.getClientsByChannel(channel);
const usersInChannelArr = Object.keys(usersInChannel);
const ifSenderIsInChannel = usersInChannelArr.find((u) => u === sender);

if (!ifSenderIsInChannel) {
console.error('Sender is not in channel');
return res.status(401).send({ error: "Permission denied" });
}

const receiver = usersInChannelArr.find((u) => u !== sender);
if(!receiver) {
console.error('No receiver is in the channel');
return res.status(406).send({ error: "No user available to accept offer" });
}

const receiverSid = usersInChannel[receiver].sid;
socketEmit<SOCKET_TOPIC.WEBRTC_SESSION_DESCRIPTION>(SOCKET_TOPIC.WEBRTC_SESSION_DESCRIPTION, receiverSid, description);
return res.send({ status: "ok" });
})
);

export default router;
2 changes: 2 additions & 0 deletions backend/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import express, { Request, Response } from 'express';

import chatLinkController from './chatLink';
import chatController from './messaging';
import sessionController from './call/session';

const router = express.Router({ mergeParams: true });

Expand All @@ -11,5 +12,6 @@ router.get("/", async (req: Request, res: Response) => {

router.use("/chat", chatController);
router.use("/chat-link", chatLinkController);
router.use("/session", sessionController);

export default router;
1 change: 1 addition & 0 deletions backend/api/messaging/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// router.response
export type MessageResponse = { message: string, id: string, timestamp: number }
export type SharePublicKeyResponse = { status: string }
export type WebrtcSessionResponse = { status: string }
export type GetPublicKeyResponse = { public_key: string }
export type UsersInChannelResponse = { uuid: string }[]

Expand Down
1 change: 1 addition & 0 deletions backend/socket.io/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export enum SOCKET_TOPIC {
DELIVERED = 'delivered',
ON_ALICE_DISCONNECTED = 'on-alice-disconnect',
MESSAGE = 'message',
WEBRTC_SESSION_DESCRIPTION = 'webrtc-session-description'
}

type emitDataTypes = {
Expand Down
8 changes: 4 additions & 4 deletions service/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@chat-e2ee/service",
"version": "1.5.0",
"version": "1.6.0",
"description": "SDK to create realtime messaging with chat-e2ee",
"main": "dist/bundle.js",
"author": "Mukesh",
Expand All @@ -14,10 +14,10 @@
},
"scripts": {
"build": "webpack --mode production",
"watch": "concurrently \"webpack --watch\" \"npm run watch-dts\"",
"build:dev": "webpack",
"watch": "webpack --watch",
"publish-sdk": "npm publish",
"test": "jest",
"watch-dts": "../node_modules/.bin/nodemon --watch src/public/types.ts --exec \"npm run dts\""
"test": "jest"
},
"dependencies": {
"socket.io-client": "^4.6.1"
Expand Down
12 changes: 6 additions & 6 deletions service/src/public/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type SocketListenerType = "limit-reached" | "delivered" | "on-alice-join" | "on-alice-disconnect" | "chat-message";
import { Call } from "../sdk";
export type SocketListenerType = Omit<SocketListenerTypeInternal, "webrtc-session-description">;
export type SocketListenerTypeInternal = "limit-reached" | "delivered" | "on-alice-join" | "on-alice-disconnect" | "chat-message" | "webrtc-session-description";
export type LinkObjType = {
hash: string,
link: string,
Expand All @@ -25,6 +27,9 @@ export interface IChatE2EE {
dispose(): void;
encrypt({ image, text }): { send: () => Promise<ISendMessageReturn> };
on(listener: SocketListenerType, callback: (...args: any) => void): void;
// webrtc call
startCall(): Promise<Call>;
endCall(): void;
}

export interface IUtils {
Expand All @@ -41,8 +46,3 @@ export type configType = {
}
export type SetConfigType = (config: Partial<configType>) => void;

export declare const createChatInstance: () => IChatE2EE;
export declare const utils: IUtils;
export declare const setConfig: SetConfigType;


77 changes: 75 additions & 2 deletions service/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SocketInstance, SubscriptionContextType } from './socket/socket';
import { Logger } from './utils/logger';
export { setConfig } from './configContext';
import { generateUUID } from './utils/uuid';
import { WebRTCCall } from './webrtc';

export const utils = {
decryptMessage: (ciphertext: string, privateKey: string) => _cryptoUtils.decryptMessage(ciphertext, privateKey),
Expand Down Expand Up @@ -42,31 +43,74 @@ class ChatE2EE implements IChatE2EE {
private socket: SocketInstance;

private subscriptionLogger = logger.createChild('Subscription');
private callLogger = logger.createChild('Call');

private initialized = false;
private call?: WebRTCCall;
private iceCandidates = [];

constructor(config?: Partial<configType>) {
config && setConfig(config);
}

public async init(): Promise<void> {
const initLogger = logger.createChild('Init');
const evetLogger = logger.createChild('Events');
initLogger.log(`Started.`);

this.createSocketSubcription();
const { privateKey, publicKey } = await _cryptoUtils.generateKeypairs();
this.privateKey = privateKey;
this.publicKey = publicKey;
this.on('on-alice-join', () => {
initLogger.log("Receiver connected.");
evetLogger.log("Receiver connected.");
this.getPublicKey(initLogger);
})

this.on("on-alice-disconnect", () => {
initLogger.log("Receiver disconnected");
evetLogger.log("Receiver disconnected");
this.receiverPublicKey = null;
});

/**
* Related to webrtc connection
*/
this.on('webrtc-session-description', (data) => {
evetLogger.log("New session description");
if(data.type === 'offer') {
evetLogger.log("New offer");
this.call = this.getWebRtcCall();
}else if(data.type === 'answer') {
evetLogger.log("New answer");
if(!this.call) {
evetLogger.log("No all to answer");
return;
}
}else if(data.type === 'candidate') {
evetLogger.log("New candidate");
if(!this.call) {
evetLogger.log("call not created yet, storing ICE candidate");
this.iceCandidates.push(data);
}
}
this.call?.signal(data);
});

/**
* TO DO:
* Use better approach to add ICE Candidate
*/
const timer = setInterval(() => {
if(this.iceCandidates.length && this.call) {
console.log('setting ICE candidate')
this.iceCandidates.forEach((ice) => {
this.call.signal(ice);
})

clearTimeout(timer);
}
}, 1000)

initLogger.log(`Finished.`);
this.initialized = true;
}
Expand Down Expand Up @@ -158,6 +202,19 @@ class ChatE2EE implements IChatE2EE {
}
}

public async startCall(): Promise<Call> {
if(this.call) {
throw new Error('Call already active');
}
const call = new Call(this.getWebRtcCall());
await call.startCall();
return call;
}

public async endCall(): Promise<void> {
return this.call?.endCall();
}

//get receiver public key
private async getPublicKey(logger: Logger): Promise<void> {
logger.log(`getPublicKey()`);
Expand All @@ -177,6 +234,22 @@ class ChatE2EE implements IChatE2EE {
throw new Error('ChatE2EE is not initialized, call init()');
}
}

private getWebRtcCall(): WebRTCCall {
this.checkInitialized();
return new WebRTCCall(this.userId, this.channelId, this.subscriptions, this.callLogger);
}
}

export class Call {
constructor(private webRtcCall: WebRTCCall) {}

public async startCall(): Promise<void> {
return this.webRtcCall.startCall();
}
public async endCall(): Promise<void> {
return this.webRtcCall.endCall();
}
}

export * from './public/types';
12 changes: 7 additions & 5 deletions service/src/socket/socket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,18 @@ import socketIOClient, { Socket } from 'socket.io-client';
import { Logger } from '../utils/logger';
import { chatJoinPayloadType } from '../sdk';
import { configContext } from '../configContext';
import { SocketListenerType } from '../public/types';
import { SocketListenerTypeInternal } from '../public/types';

export type SubscriptionType = Map<SocketListenerType, Set<(...args: any) => void>>;
export type SubscriptionType = Map<SocketListenerTypeInternal, Set<(...args: any) => void>>;
export type SubscriptionContextType = () => SubscriptionType;

const SOCKET_LISTENERS: Record<string, SocketListenerType> = {
const SOCKET_LISTENERS: Record<string, SocketListenerTypeInternal> = {
'LIMIT_REACHED': "limit-reached",
'DELIVERED': "delivered",
'ON_ALICE_JOIN': "on-alice-join",
'ON_ALICE_DISCONNECT': "on-alice-disconnect",
'CHAT_MESSAGE': "chat-message"
'CHAT_MESSAGE': "chat-message",
"WEBRTC_SESSION_DESCRIPTION": "webrtc-session-description"
}

const getBaseURL = (): string => {
Expand All @@ -35,6 +36,7 @@ export class SocketInstance {
this.handler(SOCKET_LISTENERS.CHAT_MESSAGE, args);
this.markDelivered(args[0]);
});
this.socket.on(SOCKET_LISTENERS.WEBRTC_SESSION_DESCRIPTION, (...args) => this.handler(SOCKET_LISTENERS.WEBRTC_SESSION_DESCRIPTION, args))
logger.log('Initiialized');
}

Expand All @@ -49,7 +51,7 @@ export class SocketInstance {
this.socket.disconnect();
}

private handler(listener: SocketListenerType, args) {
private handler(listener: SocketListenerTypeInternal, args) {
const loggerWithCount = this.eventHandlerLogger.count();
loggerWithCount.log(`handler called for ${listener}`);
const callbacks = this.subscriptionContext().get(listener);
Expand Down
2 changes: 1 addition & 1 deletion service/src/utils/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export class Logger {
return new Logger(`${this.name}`, [...this.childs, name]);
}

public log(...args: any[]) {
public log(...args: any[]): void {
if(this.disableLog) {
// Logs are disabled and will not be printed
// set disableLog: false in configContext to enable logs
Expand Down
Loading

0 comments on commit 888c167

Please sign in to comment.