From 7e1630db1250035cd5ca48697eb1ef6e06e88cbc Mon Sep 17 00:00:00 2001 From: Mukesh Date: Thu, 19 Sep 2024 19:55:01 +0200 Subject: [PATCH] Audio/e2ee (#377) * feat: #310 - webrtc call added for audio * feat: #310 -e2e encrypted oneway audio call * feat: #310 -e2e encrypted oneway audio call added * fix: react hook warning * fix: few code smell * fix: minor cleanup --- .../components/Messaging/UserStatusInfo.tsx | 6 +-- service/src/public/types.ts | 10 ++-- service/src/sdk.ts | 46 ++++++++----------- service/src/socket/socket.ts | 10 ++-- service/src/webrtc.ts | 6 +-- 5 files changed, 32 insertions(+), 46 deletions(-) diff --git a/client/src/components/Messaging/UserStatusInfo.tsx b/client/src/components/Messaging/UserStatusInfo.tsx index 7082145..69b016d 100644 --- a/client/src/components/Messaging/UserStatusInfo.tsx +++ b/client/src/components/Messaging/UserStatusInfo.tsx @@ -24,15 +24,15 @@ export const UserStatusInfo = ({ const [ callState, setCallState ] = useState(undefined); useEffect(() => { - chate2ee.onCallAdded((call) => { + chate2ee.on('call-added', (call) => { setCall(call); }); - chate2ee.onCallRemoved(() => { + chate2ee.on('call-removed', () => { setCall(null); }); - chate2ee.onPCStateChanged((state) => { + chate2ee.on('pc-state-changed', (state) => { setCallState(state); }); }, [chate2ee]); diff --git a/service/src/public/types.ts b/service/src/public/types.ts index a030a3e..46417fe 100644 --- a/service/src/public/types.ts +++ b/service/src/public/types.ts @@ -1,7 +1,6 @@ -import { SocketListenerTypeInternal } from "../socket/socket"; -import { E2ECall } from "../webrtc"; +import { SocketListenerType } from "../socket/socket"; +import { E2ECall, PeerConnectionEventType } from "../webrtc"; -export type SocketListenerType = Omit; export type LinkObjType = { hash: string, link: string, @@ -27,13 +26,10 @@ export interface IChatE2EE { sendMessage(args: { image: string, text: string }): Promise; dispose(): void; encrypt({ image, text }): { send: () => Promise }; - on(listener: SocketListenerType, callback: (...args: any) => void): void; + on(listener: SocketListenerType | PeerConnectionEventType, callback: (...args: any) => void): void; // webrtc call startCall(): Promise; endCall(): void; - onPCStateChanged(cb: (state: RTCPeerConnectionState) => void) : void; - onCallAdded(cb: (call: E2ECall) => void): void, - onCallRemoved(cb: () => void): void activeCall: E2ECall | null } diff --git a/service/src/sdk.ts b/service/src/sdk.ts index 7f51037..ee580cc 100644 --- a/service/src/sdk.ts +++ b/service/src/sdk.ts @@ -4,14 +4,14 @@ import { cryptoUtils as _cryptoUtils } from './cryptoRSA'; import deleteLink from './deleteLink'; import getLink from './getLink'; import getUsersInChannel from './getUsersInChannel'; -import { configType, IChatE2EE, ISendMessageReturn, LinkObjType, SocketListenerType, TypeUsersInChannel } from './public/types'; +import { configType, IChatE2EE, ISendMessageReturn, LinkObjType, TypeUsersInChannel } from './public/types'; import { getPublicKey, sharePublicKey } from './publicKey'; import sendMessage from './sendMessage'; -import { SocketInstance, SubscriptionContextType } from './socket/socket'; +import { SocketInstance, SubscriptionType } from './socket/socket'; import { Logger } from './utils/logger'; export { setConfig } from './configContext'; import { generateUUID } from './utils/uuid'; -import { WebRTCCall, E2ECall } from './webrtc'; +import { WebRTCCall, E2ECall, peerConnectionEvents, PeerConnectionEventType } from './webrtc'; export const utils = { decryptMessage: (ciphertext: string, privateKey: string) => _cryptoUtils.decryptMessage(ciphertext, privateKey), @@ -33,14 +33,15 @@ export type chatJoinPayloadType = { class ChatE2EE implements IChatE2EE { private channelId?: string; private userId?: string; - private userName?: string; private privateKey?: string; private publicKey?: string; private receiverPublicKey?: string; - private subscriptions = new Map(); + //To Do: Fix types + private subscriptions: Map> = new Map(); + private callSubscriptions: Map> = new Map(); private socket: SocketInstance; private subscriptionLogger = logger.createChild('Subscription'); @@ -48,16 +49,12 @@ class ChatE2EE implements IChatE2EE { private initialized = false; private call?: WebRTCCall; - private onCallAddedHandler?: (call: E2ECall) => void; - private onCallRemovedHandler?: () => void; - private onPCStateChangedHandler?: (state: RTCPeerConnectionState) => void; - private iceCandidates = []; private symEncryption = new AesGcmEncryption(); private onPcConnectionChanged(state: RTCPeerConnectionState): void { - this.onPCStateChangedHandler(state) + this.callSubscriptions.get("pc-state-changed")?.forEach((cb) => cb(state)); if(state === 'failed' || state === 'closed') { this.callLogger.log(`Ending call, RTCPeerConnectionState: ${state}`); this.endCall(); @@ -97,7 +94,7 @@ class ChatE2EE implements IChatE2EE { if(data.type === 'offer') { evetLogger.log("New offer"); this.call = this.getWebRtcCall(); - this.onCallAddedHandler?.(this.activeCall); + this.callSubscriptions.get("call-added")?.forEach((cb) => cb(this.activeCall)); this.call.signal(data); // add ICE from buffer @@ -145,7 +142,6 @@ class ChatE2EE implements IChatE2EE { logger.log(`setChannel(), ${JSON.stringify({ channelId, userId,userName })}`); this.channelId = channelId; this.userId = userId; - this.userName = userName; const aesPlain = await this.symEncryption.getRawAesKeyToExport(); @@ -193,8 +189,14 @@ class ChatE2EE implements IChatE2EE { }) } - public on(listener: SocketListenerType, callback): void { + public on(listener: string, callback): void { const loggerWithCount = this.subscriptionLogger.count(); + let subscriptions = this.subscriptions; + + if(peerConnectionEvents.includes(listener as PeerConnectionEventType)) { + subscriptions = this.callSubscriptions; + } + const sub = this.subscriptions.get(listener); if (sub) { if (sub.has(callback)) { @@ -205,7 +207,7 @@ class ChatE2EE implements IChatE2EE { sub.add(callback); } else { loggerWithCount.log(`Created: ${listener}`); - this.subscriptions.set(listener, new Set([callback])); + subscriptions.set(listener, new Set([callback])); } } @@ -240,19 +242,7 @@ class ChatE2EE implements IChatE2EE { public async endCall(): Promise { this.call?.endCall(); this.call = null; - this.onCallRemovedHandler?.(); - } - - public onCallAdded(cb: (call: E2ECall) => void): void { - this.onCallAddedHandler = cb - } - - public onCallRemoved(cb: () => void): void { - this.onCallRemovedHandler = cb - } - - public onPCStateChanged(cb: (state: RTCPeerConnectionState) => void) : void { - this.onPCStateChangedHandler = cb; + this.callSubscriptions.get("call-removed")?.forEach((cb) => cb()); } //get receiver public key @@ -268,7 +258,7 @@ class ChatE2EE implements IChatE2EE { } private createSocketSubcription(): void { - const subscriptionContext: SubscriptionContextType = () => this.subscriptions; + const subscriptionContext = () => this.subscriptions as SubscriptionType; this.socket = new SocketInstance(subscriptionContext, logger.createChild('Socket')); } diff --git a/service/src/socket/socket.ts b/service/src/socket/socket.ts index 433e1b2..08d8d03 100644 --- a/service/src/socket/socket.ts +++ b/service/src/socket/socket.ts @@ -2,12 +2,12 @@ import socketIOClient, { Socket } from 'socket.io-client'; import { Logger } from '../utils/logger'; import { chatJoinPayloadType } from '../sdk'; import { configContext } from '../configContext'; -export type SocketListenerTypeInternal = "limit-reached" | "delivered" | "on-alice-join" | "on-alice-disconnect" | "chat-message" | "webrtc-session-description"; +export type SocketListenerType = "limit-reached" | "delivered" | "on-alice-join" | "on-alice-disconnect" | "chat-message" | "webrtc-session-description"; -export type SubscriptionType = Map void>>; +export type SubscriptionType = Map>; export type SubscriptionContextType = () => SubscriptionType; -const SOCKET_LISTENERS: Record = { +const SOCKET_LISTENERS: Record = { 'LIMIT_REACHED': "limit-reached", 'DELIVERED': "delivered", 'ON_ALICE_JOIN': "on-alice-join", @@ -26,7 +26,7 @@ export class SocketInstance { private socket: Socket; private eventHandlerLogger = this.logger.createChild('eventHandlerLogger'); - constructor(private subscriptionContext: SubscriptionContextType, private logger: Logger) { + constructor(private subscriptionContext: () => SubscriptionType, private logger: Logger) { this.socket = socketIOClient(`${getBaseURL()}/`); this.socket.on(SOCKET_LISTENERS.LIMIT_REACHED, (...args) => this.handler(SOCKET_LISTENERS.LIMIT_REACHED, args)); this.socket.on(SOCKET_LISTENERS.DELIVERED, (...args) => this.handler(SOCKET_LISTENERS.DELIVERED, args)); @@ -51,7 +51,7 @@ export class SocketInstance { this.socket.disconnect(); } - private handler(listener: SocketListenerTypeInternal, args) { + private handler(listener: SocketListenerType, args) { const loggerWithCount = this.eventHandlerLogger.count(); loggerWithCount.log(`handler called for ${listener}`); const callbacks = this.subscriptionContext().get(listener); diff --git a/service/src/webrtc.ts b/service/src/webrtc.ts index fd328b6..0c5475a 100644 --- a/service/src/webrtc.ts +++ b/service/src/webrtc.ts @@ -6,9 +6,9 @@ interface SignalData { type: RTCSdpType; sdp: string; } - -export class WebRTCCall { - private callStateChangeCallback?: (state: RTCPeerConnectionState) => void; +export type PeerConnectionEventType = "call-added" | "call-removed" | "pc-state-changed"; +export const peerConnectionEvents: PeerConnectionEventType[] = [ "call-added", "call-removed", "pc-state-changed" ]; +export class WebRTCCall { private peer: Peer; public static isSupported(): boolean {