From 16fa2653ab00899d420d004068e027a13a504153 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Mon, 18 Dec 2023 15:28:44 +0530 Subject: [PATCH 01/13] feat(video-v2): add highlevel video class --- packages/restapi/src/lib/pushapi/PushAPI.ts | 8 + .../restapi/src/lib/pushapi/pushAPITypes.ts | 18 +- packages/restapi/src/lib/pushapi/video.ts | 69 ++++++ packages/restapi/src/lib/types/index.ts | 1 + packages/restapi/src/lib/types/videoTypes.ts | 8 + packages/restapi/src/lib/video/VideoV2.ts | 209 ++++++++++++++++++ .../src/lib/video/helpers/validatePeerInfo.ts | 19 ++ 7 files changed, 330 insertions(+), 2 deletions(-) create mode 100644 packages/restapi/src/lib/pushapi/video.ts create mode 100644 packages/restapi/src/lib/types/videoTypes.ts create mode 100644 packages/restapi/src/lib/video/VideoV2.ts create mode 100644 packages/restapi/src/lib/video/helpers/validatePeerInfo.ts diff --git a/packages/restapi/src/lib/pushapi/PushAPI.ts b/packages/restapi/src/lib/pushapi/PushAPI.ts index 0549b5643..1c9f71c1e 100644 --- a/packages/restapi/src/lib/pushapi/PushAPI.ts +++ b/packages/restapi/src/lib/pushapi/PushAPI.ts @@ -16,6 +16,7 @@ import { STREAM, } from '../pushstream/pushStreamTypes'; import { ALPHA_FEATURE_CONFIG } from '../config'; +import { Video } from './video'; export class PushAPI { private signer?: SignerType; @@ -28,6 +29,8 @@ export class PushAPI { private progressHook?: (progress: ProgressHookType) => void; public chat: Chat; // Public instances to be accessed from outside the class + public video: Video; + public profile: Profile; public encryption: Encryption; private user: User; @@ -81,6 +84,11 @@ export class PushAPI { this.progressHook ); this.user = new User(this.account, this.env); + + this.video = new Video(this.account, + this.env, + this.decryptedPgpPvtKey, + this.signer) } // Overloaded initialize method signatures static async initialize( diff --git a/packages/restapi/src/lib/pushapi/pushAPITypes.ts b/packages/restapi/src/lib/pushapi/pushAPITypes.ts index 278e43250..76300613d 100644 --- a/packages/restapi/src/lib/pushapi/pushAPITypes.ts +++ b/packages/restapi/src/lib/pushapi/pushAPITypes.ts @@ -1,5 +1,5 @@ import Constants, { ENV } from '../constants'; -import { ChatMemberCounts, ChatMemberProfile, ChatStatus, ProgressHookType, Rules } from '../types'; +import { ChatStatus, ProgressHookType, Rules, SignerType } from '../types'; export enum ChatListType { CHATS = 'CHATS', @@ -66,4 +66,18 @@ export interface ParticipantStatus { pending: boolean; role: 'ADMIN' | 'MEMBER'; participant: boolean; -} \ No newline at end of file +} + +export interface VideoInitializeOptions { + /* + - If the signer and decryptedPgpPvtKey were not provided during the initialization of the PushAPI class, + - They can be provided when initializing the video. + */ + signer?: SignerType; + decryptedPgpPvtKey?: string; + media: { + video?: boolean; + audio?: boolean; + }; + stream?: MediaStream; +} diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts new file mode 100644 index 000000000..5a5167adc --- /dev/null +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -0,0 +1,69 @@ +import { ENV } from '../constants'; +import { SignerType, VideoCallData } from '../types'; + +import { Video as VideoV1 } from '../video/Video'; +import { VideoV2 } from '../video/VideoV2'; +import { VideoInitializeOptions } from './pushAPITypes'; + +export class Video { + constructor( + private account: string, + private env: ENV, + private decryptedPgpPvtKey?: string, + private signer?: SignerType + ) {} + + async initialize( + setVideoData: (fn: (data: VideoCallData) => VideoCallData) => void, + options?: VideoInitializeOptions + ) { + const { media, signer, decryptedPgpPvtKey, stream } = options || {}; + + const chainId = await this.signer?.getChainId(); + + if (!chainId) { + throw new Error('Chain Id not retrievable from signer'); + } + + if (!this.signer && !signer) { + throw new Error('Signer is required for push video'); + } + + if (!this.decryptedPgpPvtKey && !decryptedPgpPvtKey) { + throw new Error('Decrypted PGP private key is required for push video'); + } + + this.decryptedPgpPvtKey ??= decryptedPgpPvtKey; + this.signer ??= signer; + + // Initialize the video instance with the provided options + const videoV1Instance = new VideoV1({ + signer: this.signer!, + chainId, + pgpPrivateKey: this.decryptedPgpPvtKey!, + env: this.env, + setData: setVideoData, + }); + + // Create the media stream with the provided options + await videoV1Instance.create({ + ...(stream && { + stream, + }), + ...(media?.audio && { + audio: media.audio, + }), + ...(media?.video && { + video: media.video, + }), + }); + + // Return an instance of the video v2 class + return new VideoV2({ + videoV1Instance, + account: this.account, + decryptedPgpPvtKey: this.decryptedPgpPvtKey!, + env: this.env, + }); + } +} diff --git a/packages/restapi/src/lib/types/index.ts b/packages/restapi/src/lib/types/index.ts index d091d909f..0435bfc05 100644 --- a/packages/restapi/src/lib/types/index.ts +++ b/packages/restapi/src/lib/types/index.ts @@ -12,6 +12,7 @@ import { ENV, MessageType } from '../constants'; import { EthEncryptedData } from '@metamask/eth-sig-util'; import { Message, MessageObj } from './messageTypes'; export * from './messageTypes'; +export * from './videoTypes'; export type Env = typeof ENV[keyof typeof ENV]; diff --git a/packages/restapi/src/lib/types/videoTypes.ts b/packages/restapi/src/lib/types/videoTypes.ts new file mode 100644 index 000000000..aed9fae26 --- /dev/null +++ b/packages/restapi/src/lib/types/videoTypes.ts @@ -0,0 +1,8 @@ +export type VideoPeerInfo = { + address: string; + signal: any; + meta: { + // TODO: replace this type once old PR is merged + rules: any; + }; +}; diff --git a/packages/restapi/src/lib/video/VideoV2.ts b/packages/restapi/src/lib/video/VideoV2.ts new file mode 100644 index 000000000..9bba2b2f6 --- /dev/null +++ b/packages/restapi/src/lib/video/VideoV2.ts @@ -0,0 +1,209 @@ +import { chats } from '../chat'; +import { ENV } from '../constants'; +import { + isValidETHAddress, + pCAIP10ToWallet, + walletToPCAIP10, +} from '../helpers'; +import { VideoPeerInfo } from '../types'; +import { Video as VideoV1 } from './Video'; +import { validatePeerInfo } from './helpers/validatePeerInfo'; + +/** + * VideoV2 class + */ +export class VideoV2 { + private account: string; + private decryptedPgpPvtKey: string; + private env: ENV; + + private videoInstance: VideoV1; + + /** + * VideoV2 constructor + * @param {object} params - The constructor parameters + * @param {VideoV1} params.videoV1Instance - The VideoV1 instance + * @param {string} params.account - The account + * @param {string} params.decryptedPgpPvtKey - The decrypted PGP private key + * @param {ENV} params.env - The environment + */ + constructor({ + videoV1Instance, + account, + decryptedPgpPvtKey, + env, + }: { + videoV1Instance: VideoV1; + account: string; + decryptedPgpPvtKey: string; + env: ENV; + }) { + this.videoInstance = videoV1Instance; + this.account = account; + this.decryptedPgpPvtKey = decryptedPgpPvtKey; + this.env = env; + } + + /** + * Request a video call + * @param {string[]} recipients - The recipients of the video call + * @param {object} options - The options for the video call + * @param {object} options.rules - The rules for the video call + * @param {object} options.rules.access - The access rules for the video call + * @param {string} options.rules.access.type - The type of the video call + * @param {object} options.rules.access.data - The data for the video call + * @param {string} options.rules.access.data.chatId - The chat ID for the video call + */ + async request( + recipients: string[], + options?: { + rules: { + access: { + // TODO: Replace type once the initial video PR is merged + type: ''; + data: { + chatId?: string; + }; + }; + }; + } + ) { + const { rules } = options || {}; + + for (const recipient of recipients) { + if (!isValidETHAddress(recipient)) { + throw new Error('Invalid recipient address found'); + } + } + + if (recipients.length === 0) { + throw new Error( + 'Alteast one recipient address is required for a video call' + ); + } + + // TODO: Update the rules type after the PR is merged, type should be push chat + if ( + recipients.length > 1 && + rules?.access.type === '' && + !rules.access.data.chatId + ) { + throw new Error( + 'For multiple recipient addresses, chatId is required for a video call' + ); + } + + // If chatId is not passed, find a w2w chat between the addresses and use the chatId from there + let retrievedChatId = ''; + if (!rules?.access.data.chatId) { + let page = 1; + const limit = 30; + while (!retrievedChatId) { + const response = await chats({ + account: this.account, + toDecrypt: true, + pgpPrivateKey: this.decryptedPgpPvtKey, + env: this.env, + page, + limit, + }); + + if (response.length === 0) break; + + response.forEach((chat) => { + if (chat.did === walletToPCAIP10(recipients[0]) && chat.chatId) { + retrievedChatId = chat.chatId; + } + }); + + page++; + } + + if (!retrievedChatId) { + throw new Error( + `ChatId not found between local user (${this.account}) and recipient (${recipients[0]}).` + ); + } + } + + await this.videoInstance.request({ + senderAddress: pCAIP10ToWallet(this.account), + recipientAddress: recipients.map((recipient) => + pCAIP10ToWallet(recipient) + ), + chatId: rules?.access.data.chatId ?? retrievedChatId, + }); + } + + /** + * Approve a video call + * @param {VideoPeerInfo} peerInfo - The peer information + */ + async approve(peerInfo: VideoPeerInfo) { + validatePeerInfo(peerInfo); + + const { signal, address, meta } = peerInfo; + + await this.videoInstance.acceptRequest({ + senderAddress: pCAIP10ToWallet(this.account), + recipientAddress: pCAIP10ToWallet(address), + signalData: signal, + chatId: meta.rules.access.data, + }); + } + + /** + * Deny a video call + * @param {VideoPeerInfo} peerInfo - The peer information + */ + async deny(peerInfo: VideoPeerInfo) { + validatePeerInfo(peerInfo); + + const { address } = peerInfo; + + await this.videoInstance.disconnect({ + peerAddress: pCAIP10ToWallet(address), + }); + } + + /** + * Connect to a video call + * @param {VideoPeerInfo} peerInfo - The peer information + */ + async connect(peerInfo: VideoPeerInfo) { + validatePeerInfo(peerInfo); + + const { signal, address } = peerInfo; + + await this.videoInstance.connect({ + peerAddress: address, + signalData: signal, + }); + } + + /** + * Disconnect from a video call + * @param {string} address - The address to disconnect from + */ + async disconnect(address: string) { + await this.videoInstance.disconnect({ + peerAddress: pCAIP10ToWallet(address), + }); + } + + /** + * Enable or disable media + * @param {object} params - The parameters + * @param {boolean} params.video - The video state + * @param {boolean} params.audio - The audio state + */ + media({ video, audio }: { video?: boolean; audio?: boolean }) { + if (typeof video === 'boolean') { + this.videoInstance.enableVideo({ state: video }); + } + + if (typeof audio === 'boolean') { + this.videoInstance.enableAudio({ state: audio }); + } + } +} diff --git a/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts b/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts new file mode 100644 index 000000000..c793026ac --- /dev/null +++ b/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts @@ -0,0 +1,19 @@ +import { isValidETHAddress } from '../../helpers'; +import { VideoPeerInfo } from '../../types'; + +export const validatePeerInfo = (peerInfo: VideoPeerInfo) => { + const { signal, address, meta } = peerInfo; + + if (!signal) { + throw new Error('Invalid signal data received'); + } + + if (!isValidETHAddress(address)) { + throw new Error('Invalid address received'); + } + + // TODO: comparing type should be PUSH_CHAT + if (meta.rules.access.type === '' && !meta.rules.access.data.chatId) { + throw new Error('ChatId not found in meta.rules'); + } +}; From ee8f2f9a9786f47722f17104e38cf4dbbc922603 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Thu, 21 Dec 2023 12:00:19 +0530 Subject: [PATCH 02/13] feat(video): add video stream --- .../src/lib/pushstream/DataModifier.ts | 56 +++++++++++++++++++ .../restapi/src/lib/pushstream/PushStream.ts | 53 ++++++++++++++---- .../src/lib/pushstream/pushStreamTypes.ts | 20 ++++++- 3 files changed, 117 insertions(+), 12 deletions(-) diff --git a/packages/restapi/src/lib/pushstream/DataModifier.ts b/packages/restapi/src/lib/pushstream/DataModifier.ts index 8b18e8cf1..182b1fefe 100644 --- a/packages/restapi/src/lib/pushstream/DataModifier.ts +++ b/packages/restapi/src/lib/pushstream/DataModifier.ts @@ -17,7 +17,12 @@ import { NotificationType, NOTIFICATION, ProposedEventNames, + VideoEventType, + MessageOrigin, + VideoEvent, } from './pushStreamTypes'; +import { VideoCallStatus, VideoPeerInfo } from '../types'; +import { VideoDataType } from '../video'; export class DataModifier { public static handleChatGroupEvent(data: any, includeRaw = false): any { @@ -388,4 +393,55 @@ export class DataModifier { break; } } + + public static convertToProposedNameForVideo( + currentVideoStatus: VideoCallStatus + ): VideoEventType { + switch (currentVideoStatus) { + case VideoCallStatus.INITIALIZED: + return VideoEventType.RequestVideo; + case VideoCallStatus.RECEIVED: + return VideoEventType.ApproveVideo; + case VideoCallStatus.DISCONNECTED: + return VideoEventType.DenyVideo; + default: + throw new Error(`Unknown video call status: ${currentVideoStatus}`); + } + } + + public static mapToVideoEvent( + data: any, + origin: MessageOrigin, + includeRaw = false + ): VideoEvent { + const { senderAddress, signalData, status }: VideoDataType = JSON.parse( + data.payload.data.additionalMeta?.data + ); + + const peerInfo: VideoPeerInfo = { + address: senderAddress, + signal: signalData, + meta: { + rules: data.payload.rules, + }, + }; + + const videoEventType: VideoEventType = + DataModifier.convertToProposedNameForVideo(status); + + const videoEvent: VideoEvent = { + event: videoEventType, + origin: origin, + timestamp: data.epoch, + peerInfo, + }; + + if (includeRaw) { + videoEvent.raw = { + verificationProof: data.payload.verificationProof, + }; + } + + return videoEvent; + } } diff --git a/packages/restapi/src/lib/pushstream/PushStream.ts b/packages/restapi/src/lib/pushstream/PushStream.ts index 192dd42a5..0323b60f6 100644 --- a/packages/restapi/src/lib/pushstream/PushStream.ts +++ b/packages/restapi/src/lib/pushstream/PushStream.ts @@ -4,15 +4,17 @@ import { ENV, PACKAGE_BUILD } from '../constants'; import { GroupEventType, MessageEventType, + MessageOrigin, NotificationEventType, PushStreamInitializeProps, - STREAM, + STREAM } from './pushStreamTypes'; import { DataModifier } from './DataModifier'; import { pCAIP10ToWallet, walletToPCAIP10 } from '../helpers'; import { Chat } from '../pushapi/chat'; import { ProgressHookType, SignerType } from '../types'; import { ALPHA_FEATURE_CONFIG } from '../config'; +import { ADDITIONAL_META_TYPE } from '../payloads'; export class PushStream extends EventEmitter { private pushChatSocket: any; @@ -102,7 +104,8 @@ export class PushStream extends EventEmitter { !this.listen || this.listen.length === 0 || this.listen.includes(STREAM.NOTIF) || - this.listen.includes(STREAM.NOTIF_OPS); + this.listen.includes(STREAM.NOTIF_OPS) || + this.listen.includes(STREAM.VIDEO); let isChatSocketConnected = false; let isNotifSocketConnected = false; @@ -312,16 +315,33 @@ export class PushStream extends EventEmitter { this.pushNotificationSocket.on(EVENTS.USER_FEEDS, (data: any) => { try { - const modifiedData = DataModifier.mapToNotificationEvent( - data, - NotificationEventType.INBOX, - this.account === data.sender ? 'self' : 'other', - this.raw - ); + if ( + data.payload.data.additionalMeta?.type === + `${ADDITIONAL_META_TYPE.PUSH_VIDEO}+1` && + shouldEmit(STREAM.VIDEO) && + this.shouldEmitVideo(data.sender) + ) { + // Video Notification + const modifiedData = DataModifier.mapToVideoEvent( + data, + this.account === data.sender ? MessageOrigin.Self : MessageOrigin.Other, + this.raw + ); - if (this.shouldEmitChannel(modifiedData.from)) { - if (shouldEmit(STREAM.NOTIF)) { - this.emit(STREAM.NOTIF, modifiedData); + this.emit(STREAM.VIDEO, modifiedData); + } else { + // Channel Notification + const modifiedData = DataModifier.mapToNotificationEvent( + data, + NotificationEventType.INBOX, + this.account === data.sender ? 'self' : 'other', + this.raw + ); + + if (this.shouldEmitChannel(modifiedData.from)) { + if (shouldEmit(STREAM.NOTIF)) { + this.emit(STREAM.NOTIF, modifiedData); + } } } } catch (error) { @@ -396,4 +416,15 @@ export class PushStream extends EventEmitter { } return this.options.filter.channels.includes(dataChannelId); } + + private shouldEmitVideo(dataVideoId: string): boolean { + if ( + !this.options.filter?.video || + this.options.filter.video.length === 0 || + this.options.filter.video.includes('*') + ) { + return true; + } + return this.options.filter.video.includes(dataVideoId); + } } diff --git a/packages/restapi/src/lib/pushstream/pushStreamTypes.ts b/packages/restapi/src/lib/pushstream/pushStreamTypes.ts index 7e0cbbad9..84e30792b 100644 --- a/packages/restapi/src/lib/pushstream/pushStreamTypes.ts +++ b/packages/restapi/src/lib/pushstream/pushStreamTypes.ts @@ -1,10 +1,11 @@ -import { Rules } from '../types'; +import { Rules, VideoPeerInfo } from '../types'; import { ENV } from '../constants'; export type PushStreamInitializeProps = { filter?: { channels?: string[]; chats?: string[]; + video?: string[]; }; connection?: { auto?: boolean; @@ -22,6 +23,7 @@ export enum STREAM { NOTIF_OPS = 'STREAM.NOTIF_OPS', CHAT = 'STREAM.CHAT', CHAT_OPS = 'STREAM.CHAT_OPS', + VIDEO = 'STREAM.VIDEO', CONNECT = 'STREAM.CONNECT', DISCONNECT = 'STREAM.DISCONNECT', } @@ -51,6 +53,14 @@ export enum GroupEventType { Remove = 'remove', } +export enum VideoEventType { + RequestVideo = 'video.request', + ApproveVideo = 'video.approve', + DenyVideo = 'video.deny', + ConnectVideo = 'video.connect', + DisconnectVideo = 'video.disconnect', +} + export enum ProposedEventNames { Message = 'chat.message', Request = 'chat.request', @@ -224,3 +234,11 @@ export interface MessageRawData { verificationProof: string; previousReference: string; } + +export interface VideoEvent { + event: VideoEventType; + origin: MessageOrigin; + timestamp: string; + peerInfo: VideoPeerInfo; + raw?: GroupEventRawData; +} From c8b2a0784f2c5abf77a22113a22bc51a886c49d4 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Thu, 21 Dec 2023 16:12:27 +0530 Subject: [PATCH 03/13] fix(sendnotification): modify rules.access.data to be an object & code cleanup --- .../src/lib/payloads/sendNotifications.ts | 4 ++-- .../restapi/src/lib/pushapi/pushAPITypes.ts | 5 ++--- packages/restapi/src/lib/pushapi/video.ts | 5 ++--- packages/restapi/src/lib/types/index.ts | 4 +++- packages/restapi/src/lib/types/videoTypes.ts | 5 +++-- packages/restapi/src/lib/video/Video.ts | 15 ++++++++++---- packages/restapi/src/lib/video/VideoV2.ts | 20 ++++++------------- .../helpers/sendVideoCallNotification.ts | 2 +- .../src/lib/video/helpers/validatePeerInfo.ts | 7 +++++-- .../lib/video/helpers/validateVideoRules.ts | 13 ++++++++++++ .../lib/video/sendVideoNotification.test.ts | 2 +- 11 files changed, 49 insertions(+), 33 deletions(-) create mode 100644 packages/restapi/src/lib/video/helpers/validateVideoRules.ts diff --git a/packages/restapi/src/lib/payloads/sendNotifications.ts b/packages/restapi/src/lib/payloads/sendNotifications.ts index 6101d6a54..09fd65085 100644 --- a/packages/restapi/src/lib/payloads/sendNotifications.ts +++ b/packages/restapi/src/lib/payloads/sendNotifications.ts @@ -185,7 +185,7 @@ export async function sendNotification(options: ISendNotificationInputOptions) { uuid, // for the pgpv2 verfication proof chatId: - rules?.access.data ?? // for backwards compatibilty with 'chatId' param + rules?.access.data.chatId ?? // for backwards compatibilty with 'chatId' param chatId, pgpPrivateKey, }); @@ -231,7 +231,7 @@ export async function sendNotification(options: ISendNotificationInputOptions) { ? { rules: rules ?? { access: { - data: chatId, + data: { chatId }, type: VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT, }, }, diff --git a/packages/restapi/src/lib/pushapi/pushAPITypes.ts b/packages/restapi/src/lib/pushapi/pushAPITypes.ts index 76300613d..04790642d 100644 --- a/packages/restapi/src/lib/pushapi/pushAPITypes.ts +++ b/packages/restapi/src/lib/pushapi/pushAPITypes.ts @@ -70,11 +70,10 @@ export interface ParticipantStatus { export interface VideoInitializeOptions { /* - - If the signer and decryptedPgpPvtKey were not provided during the initialization of the PushAPI class, - - They can be provided when initializing the video. + - If the signer was not provided during the initialization of the PushAPI class, + - It can be provided when initializing video. */ signer?: SignerType; - decryptedPgpPvtKey?: string; media: { video?: boolean; audio?: boolean; diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts index 5a5167adc..a1146a668 100644 --- a/packages/restapi/src/lib/pushapi/video.ts +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -17,7 +17,7 @@ export class Video { setVideoData: (fn: (data: VideoCallData) => VideoCallData) => void, options?: VideoInitializeOptions ) { - const { media, signer, decryptedPgpPvtKey, stream } = options || {}; + const { media, signer, stream } = options || {}; const chainId = await this.signer?.getChainId(); @@ -29,11 +29,10 @@ export class Video { throw new Error('Signer is required for push video'); } - if (!this.decryptedPgpPvtKey && !decryptedPgpPvtKey) { + if (!this.decryptedPgpPvtKey) { throw new Error('Decrypted PGP private key is required for push video'); } - this.decryptedPgpPvtKey ??= decryptedPgpPvtKey; this.signer ??= signer; // Initialize the video instance with the provided options diff --git a/packages/restapi/src/lib/types/index.ts b/packages/restapi/src/lib/types/index.ts index 54fa77a9a..0a04ca344 100644 --- a/packages/restapi/src/lib/types/index.ts +++ b/packages/restapi/src/lib/types/index.ts @@ -87,7 +87,9 @@ export type ParsedResponseType = { export interface VideNotificationRules { access: { type: VIDEO_NOTIFICATION_ACCESS_TYPE; - data: string; + data: { + chatId?: string; + }; }; } diff --git a/packages/restapi/src/lib/types/videoTypes.ts b/packages/restapi/src/lib/types/videoTypes.ts index aed9fae26..1e9904d1f 100644 --- a/packages/restapi/src/lib/types/videoTypes.ts +++ b/packages/restapi/src/lib/types/videoTypes.ts @@ -1,8 +1,9 @@ +import { VideNotificationRules } from "."; + export type VideoPeerInfo = { address: string; signal: any; meta: { - // TODO: replace this type once old PR is merged - rules: any; + rules: VideNotificationRules; }; }; diff --git a/packages/restapi/src/lib/video/Video.ts b/packages/restapi/src/lib/video/Video.ts index 380e3c507..07da6605b 100644 --- a/packages/restapi/src/lib/video/Video.ts +++ b/packages/restapi/src/lib/video/Video.ts @@ -35,6 +35,7 @@ import { SPACE_REQUEST_TYPE, VIDEO_CALL_TYPE, } from '../payloads/constants'; +import { validateVideoRules } from './helpers/validateVideoRules'; export const initVideoCallData: VideoCallData = { meta: { @@ -171,6 +172,9 @@ export class Video { details, } = options || {}; + // If rules object is passed, validate it + rules && validateVideoRules(rules); + const recipientAddresses = Array.isArray(recipientAddress) ? recipientAddress : [recipientAddress]; @@ -181,7 +185,7 @@ export class Video { this.setData((oldData) => { return produce(oldData, (draft) => { draft.local.address = senderAddress; - draft.meta.chatId = chatId ?? rules!.access.data; + draft.meta.chatId = chatId ?? rules!.access.data.chatId!; draft.meta.initiator.address = senderAddress; const incomingIndex = getIncomingIndexFromAddress( @@ -382,7 +386,7 @@ export class Video { this.setData(() => initVideoCallData); } } - } else if(onReceiveMessage) { + } else if (onReceiveMessage) { onReceiveMessage(data); } }); @@ -424,6 +428,9 @@ export class Video { details, } = options || {}; + // If rules object is passed, validate it + rules && validateVideoRules(rules); + try { // if peerInstance is not null -> acceptRequest/request was called before if (this.peerInstances[recipientAddress]) { @@ -448,7 +455,7 @@ export class Video { this.setData((oldData) => { return produce(oldData, (draft) => { draft.local.address = senderAddress; - draft.meta.chatId = chatId ?? rules!.access.data; + draft.meta.chatId = chatId ?? rules!.access.data.chatId!; draft.meta.initiator.address = senderAddress; const incomingIndex = getIncomingIndexFromAddress( @@ -674,7 +681,7 @@ export class Video { this.setData(() => initVideoCallData); } } - } else if(onReceiveMessage) { + } else if (onReceiveMessage) { onReceiveMessage(data); } }); diff --git a/packages/restapi/src/lib/video/VideoV2.ts b/packages/restapi/src/lib/video/VideoV2.ts index 9bba2b2f6..213b9a88a 100644 --- a/packages/restapi/src/lib/video/VideoV2.ts +++ b/packages/restapi/src/lib/video/VideoV2.ts @@ -5,7 +5,8 @@ import { pCAIP10ToWallet, walletToPCAIP10, } from '../helpers'; -import { VideoPeerInfo } from '../types'; +import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../payloads/constants'; +import { VideNotificationRules, VideoPeerInfo } from '../types'; import { Video as VideoV1 } from './Video'; import { validatePeerInfo } from './helpers/validatePeerInfo'; @@ -57,15 +58,7 @@ export class VideoV2 { async request( recipients: string[], options?: { - rules: { - access: { - // TODO: Replace type once the initial video PR is merged - type: ''; - data: { - chatId?: string; - }; - }; - }; + rules: VideNotificationRules; } ) { const { rules } = options || {}; @@ -82,10 +75,9 @@ export class VideoV2 { ); } - // TODO: Update the rules type after the PR is merged, type should be push chat if ( recipients.length > 1 && - rules?.access.type === '' && + rules?.access.type === VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT && !rules.access.data.chatId ) { throw new Error( @@ -148,7 +140,7 @@ export class VideoV2 { senderAddress: pCAIP10ToWallet(this.account), recipientAddress: pCAIP10ToWallet(address), signalData: signal, - chatId: meta.rules.access.data, + chatId: meta.rules.access.data.chatId, }); } @@ -192,7 +184,7 @@ export class VideoV2 { } /** - * Enable or disable media + * Enable or disable media (video, audio) * @param {object} params - The parameters * @param {boolean} params.video - The video state * @param {boolean} params.audio - The audio state diff --git a/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts b/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts index 4675e5be4..f6d10a501 100644 --- a/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts +++ b/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts @@ -60,7 +60,7 @@ const sendVideoCallNotification = async ( const videoData: VideoDataType = { recipientAddress, senderAddress, - chatId: rules?.access.data ?? chatId, + chatId: rules?.access.data.chatId ?? chatId, signalData, status, callDetails, diff --git a/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts b/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts index c793026ac..b72f20a4b 100644 --- a/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts +++ b/packages/restapi/src/lib/video/helpers/validatePeerInfo.ts @@ -1,4 +1,5 @@ import { isValidETHAddress } from '../../helpers'; +import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../../payloads/constants'; import { VideoPeerInfo } from '../../types'; export const validatePeerInfo = (peerInfo: VideoPeerInfo) => { @@ -12,8 +13,10 @@ export const validatePeerInfo = (peerInfo: VideoPeerInfo) => { throw new Error('Invalid address received'); } - // TODO: comparing type should be PUSH_CHAT - if (meta.rules.access.type === '' && !meta.rules.access.data.chatId) { + if ( + meta.rules.access.type === VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT && + !meta.rules.access.data.chatId + ) { throw new Error('ChatId not found in meta.rules'); } }; diff --git a/packages/restapi/src/lib/video/helpers/validateVideoRules.ts b/packages/restapi/src/lib/video/helpers/validateVideoRules.ts new file mode 100644 index 000000000..f5c8a40a6 --- /dev/null +++ b/packages/restapi/src/lib/video/helpers/validateVideoRules.ts @@ -0,0 +1,13 @@ +import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../../payloads/constants'; +import { VideNotificationRules } from '../../types'; + +export const validateVideoRules = (rules: VideNotificationRules) => { + if ( + rules.access.type === VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT && + (!rules.access.data.chatId || rules.access.data.chatId === '') + ) { + throw new Error( + 'Invalid rules object recieved. For access as Push Chat, chatId is required!' + ); + } +}; diff --git a/packages/restapi/tests/lib/video/sendVideoNotification.test.ts b/packages/restapi/tests/lib/video/sendVideoNotification.test.ts index d45055b25..bbc8c73e3 100644 --- a/packages/restapi/tests/lib/video/sendVideoNotification.test.ts +++ b/packages/restapi/tests/lib/video/sendVideoNotification.test.ts @@ -113,7 +113,7 @@ describe('sendNotification functionality for video calls', () => { rules: { access: { type: VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT, - data: chatId, + data: { chatId }, }, }, notification: { From 0fff331c32a5b3f4de2edbfe8686d9a039e199c6 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 22 Dec 2023 16:26:37 +0530 Subject: [PATCH 04/13] fix(video): remove signer from input, throw err if signer, decrypted pgp key not found --- packages/restapi/src/lib/pushapi/pushAPITypes.ts | 5 ----- packages/restapi/src/lib/pushapi/video.ts | 8 +++----- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/restapi/src/lib/pushapi/pushAPITypes.ts b/packages/restapi/src/lib/pushapi/pushAPITypes.ts index 04790642d..d64852592 100644 --- a/packages/restapi/src/lib/pushapi/pushAPITypes.ts +++ b/packages/restapi/src/lib/pushapi/pushAPITypes.ts @@ -69,11 +69,6 @@ export interface ParticipantStatus { } export interface VideoInitializeOptions { - /* - - If the signer was not provided during the initialization of the PushAPI class, - - It can be provided when initializing video. - */ - signer?: SignerType; media: { video?: boolean; audio?: boolean; diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts index a1146a668..edf45775f 100644 --- a/packages/restapi/src/lib/pushapi/video.ts +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -17,7 +17,7 @@ export class Video { setVideoData: (fn: (data: VideoCallData) => VideoCallData) => void, options?: VideoInitializeOptions ) { - const { media, signer, stream } = options || {}; + const { media, stream } = options || {}; const chainId = await this.signer?.getChainId(); @@ -25,16 +25,14 @@ export class Video { throw new Error('Chain Id not retrievable from signer'); } - if (!this.signer && !signer) { + if (!this.signer) { throw new Error('Signer is required for push video'); } if (!this.decryptedPgpPvtKey) { - throw new Error('Decrypted PGP private key is required for push video'); + throw new Error('PushSDK was initialized in readonly mode. Video functionality is not available.'); } - this.signer ??= signer; - // Initialize the video instance with the provided options const videoV1Instance = new VideoV1({ signer: this.signer!, From e96a9b728668bd81d9c6fbf64cec98533e1e93b1 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Sat, 23 Dec 2023 01:03:02 +0530 Subject: [PATCH 05/13] feat(video): add sendNotification calls in connect, disconnect methods --- .../src/lib/pushstream/DataModifier.ts | 4 + packages/restapi/src/lib/types/index.ts | 1 + packages/restapi/src/lib/video/Video.ts | 79 +++++++++++++------ 3 files changed, 61 insertions(+), 23 deletions(-) diff --git a/packages/restapi/src/lib/pushstream/DataModifier.ts b/packages/restapi/src/lib/pushstream/DataModifier.ts index 182b1fefe..dfeceb167 100644 --- a/packages/restapi/src/lib/pushstream/DataModifier.ts +++ b/packages/restapi/src/lib/pushstream/DataModifier.ts @@ -402,6 +402,10 @@ export class DataModifier { return VideoEventType.RequestVideo; case VideoCallStatus.RECEIVED: return VideoEventType.ApproveVideo; + case VideoCallStatus.CONNECTED: + return VideoEventType.ConnectVideo; + case VideoCallStatus.ENDED: + return VideoEventType.DisconnectVideo case VideoCallStatus.DISCONNECTED: return VideoEventType.DenyVideo; default: diff --git a/packages/restapi/src/lib/types/index.ts b/packages/restapi/src/lib/types/index.ts index 0a04ca344..9e086f623 100644 --- a/packages/restapi/src/lib/types/index.ts +++ b/packages/restapi/src/lib/types/index.ts @@ -766,6 +766,7 @@ export enum VideoCallStatus { RECEIVED, CONNECTED, DISCONNECTED, + ENDED, RETRY_INITIALIZED, RETRY_RECEIVED, } diff --git a/packages/restapi/src/lib/video/Video.ts b/packages/restapi/src/lib/video/Video.ts index 07da6605b..31226337a 100644 --- a/packages/restapi/src/lib/video/Video.ts +++ b/packages/restapi/src/lib/video/Video.ts @@ -34,6 +34,7 @@ import { SPACE_DISCONNECT_TYPE, SPACE_REQUEST_TYPE, VIDEO_CALL_TYPE, + VIDEO_NOTIFICATION_ACCESS_TYPE, } from '../payloads/constants'; import { validateVideoRules } from './helpers/validateVideoRules'; @@ -756,6 +757,32 @@ export class Video { draft.incoming[incomingIndex].status = VideoCallStatus.CONNECTED; }); }); + + // Notifying the recipient that the video call is now connected + sendVideoCallNotification( + { + signer: this.signer, + chainId: this.chainId, + pgpPrivateKey: this.pgpPrivateKey, + }, + { + senderAddress: this.data.local.address, + recipientAddress: peerAddress + ? peerAddress + : this.data.incoming[0].address, + status: VideoCallStatus.CONNECTED, + rules: { + access: { + type: VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT, + data: { + chatId: this.data.meta.chatId, + }, + }, + }, + signalData, + env: this.env, + } + ); } catch (err) { console.error('error in connect', err); } @@ -773,37 +800,43 @@ export class Video { ? getIncomingIndexFromAddress(this.data.incoming, peerAddress) : 0; - if ( - this.data.incoming[incomingIndex].status === VideoCallStatus.CONNECTED - ) { + const isCallConnected = + this.data.incoming[incomingIndex].status === VideoCallStatus.CONNECTED; + + if (isCallConnected) { this.peerInstances[ peerAddress ? peerAddress : this.data.incoming[0].address ]?.send(JSON.stringify({ type: 'endCall', value: true, details })); this.peerInstances[ peerAddress ? peerAddress : this.data.incoming[0].address ]?.destroy(); - } else { - // for disconnecting during status INITIALIZED, RECEIVED, RETRY_INITIALIZED, RETRY_RECEIVED - // send a notif to the other user signaling status = DISCONNECTED - sendVideoCallNotification( - { - signer: this.signer, - chainId: this.chainId, - pgpPrivateKey: this.pgpPrivateKey, - }, - { - senderAddress: this.data.local.address, - recipientAddress: this.data.incoming[incomingIndex].address, - status: VideoCallStatus.DISCONNECTED, - chatId: this.data.meta.chatId, - signalData: null, - env: this.env, - callType: this.callType, - callDetails: details, - } - ); } + /* + * Send a notification to the other user signaling: + * status = ENDED if the call was connected + * status = DISCONNECTED otherwise. + */ + sendVideoCallNotification( + { + signer: this.signer, + chainId: this.chainId, + pgpPrivateKey: this.pgpPrivateKey, + }, + { + senderAddress: this.data.local.address, + recipientAddress: this.data.incoming[incomingIndex].address, + status: isCallConnected + ? VideoCallStatus.ENDED + : VideoCallStatus.DISCONNECTED, + chatId: this.data.meta.chatId, + signalData: null, + env: this.env, + callType: this.callType, + callDetails: details, + } + ); + // destroy the peerInstance this.peerInstances[ peerAddress ? peerAddress : this.data.incoming[0].address From 3cf48a4ae54549fde8a91a19d5532596b2c80ee9 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Tue, 2 Jan 2024 14:43:11 +0530 Subject: [PATCH 06/13] fix(videonotificationrules): typo in VideoNotificationRules interface --- packages/restapi/src/lib/payloads/helpers.ts | 4 ++-- packages/restapi/src/lib/types/index.ts | 8 ++++---- packages/restapi/src/lib/types/videoTypes.ts | 4 ++-- packages/restapi/src/lib/video/VideoV2.ts | 4 ++-- .../src/lib/video/helpers/sendVideoCallNotification.ts | 4 ++-- .../restapi/src/lib/video/helpers/validateVideoRules.ts | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/restapi/src/lib/payloads/helpers.ts b/packages/restapi/src/lib/payloads/helpers.ts index 779154e6f..44f1594fd 100644 --- a/packages/restapi/src/lib/payloads/helpers.ts +++ b/packages/restapi/src/lib/payloads/helpers.ts @@ -7,7 +7,7 @@ import { ISendNotificationInputOptions, INotificationPayload, walletType, - VideNotificationRules, + VideoNotificationRules, } from '../types'; import { IDENTITY_TYPE, @@ -212,7 +212,7 @@ export async function getVerificationProof({ wallet?: walletType; pgpPrivateKey?: string; env?: ENV; - rules?:VideNotificationRules; + rules?:VideoNotificationRules; }) { let message = null; let verificationProof = null; diff --git a/packages/restapi/src/lib/types/index.ts b/packages/restapi/src/lib/types/index.ts index 9e086f623..b408d9fca 100644 --- a/packages/restapi/src/lib/types/index.ts +++ b/packages/restapi/src/lib/types/index.ts @@ -84,7 +84,7 @@ export type ParsedResponseType = { }; }; -export interface VideNotificationRules { +export interface VideoNotificationRules { access: { type: VIDEO_NOTIFICATION_ACCESS_TYPE; data: { @@ -94,7 +94,7 @@ export interface VideNotificationRules { } // SendNotificationRules can be extended in the future for other use cases -export type SendNotificationRules = VideNotificationRules; +export type SendNotificationRules = VideoNotificationRules; export interface ISendNotificationInputOptions { senderType?: 0 | 1; @@ -813,7 +813,7 @@ export type VideoRequestInputOptions = { recipientAddress: string | string[]; /** @deprecated - Use `rules` object instead */ chatId?: string; - rules?: VideNotificationRules; + rules?: VideoNotificationRules; onReceiveMessage?: (message: string) => void; retry?: boolean; details?: { @@ -828,7 +828,7 @@ export type VideoAcceptRequestInputOptions = { recipientAddress: string; /** @deprecated - Use `rules` object instead */ chatId?: string; - rules?: VideNotificationRules; + rules?: VideoNotificationRules; onReceiveMessage?: (message: string) => void; retry?: boolean; details?: { diff --git a/packages/restapi/src/lib/types/videoTypes.ts b/packages/restapi/src/lib/types/videoTypes.ts index 1e9904d1f..e6f018a2c 100644 --- a/packages/restapi/src/lib/types/videoTypes.ts +++ b/packages/restapi/src/lib/types/videoTypes.ts @@ -1,9 +1,9 @@ -import { VideNotificationRules } from "."; +import { VideoNotificationRules } from "."; export type VideoPeerInfo = { address: string; signal: any; meta: { - rules: VideNotificationRules; + rules: VideoNotificationRules; }; }; diff --git a/packages/restapi/src/lib/video/VideoV2.ts b/packages/restapi/src/lib/video/VideoV2.ts index 213b9a88a..38cfaddfa 100644 --- a/packages/restapi/src/lib/video/VideoV2.ts +++ b/packages/restapi/src/lib/video/VideoV2.ts @@ -6,7 +6,7 @@ import { walletToPCAIP10, } from '../helpers'; import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../payloads/constants'; -import { VideNotificationRules, VideoPeerInfo } from '../types'; +import { VideoNotificationRules, VideoPeerInfo } from '../types'; import { Video as VideoV1 } from './Video'; import { validatePeerInfo } from './helpers/validatePeerInfo'; @@ -58,7 +58,7 @@ export class VideoV2 { async request( recipients: string[], options?: { - rules: VideNotificationRules; + rules: VideoNotificationRules; } ) { const { rules } = options || {}; diff --git a/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts b/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts index f6d10a501..5408a8c6b 100644 --- a/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts +++ b/packages/restapi/src/lib/video/helpers/sendVideoCallNotification.ts @@ -13,7 +13,7 @@ import { EnvOptionsType, SignerType, VideoCallStatus, - VideNotificationRules, + VideoNotificationRules, } from '../../types'; interface CallDetailsType { @@ -33,7 +33,7 @@ export interface VideoDataType { interface VideoCallInfoType extends VideoDataType, EnvOptionsType { callType?: VIDEO_CALL_TYPE; - rules?: VideNotificationRules; + rules?: VideoNotificationRules; } interface UserInfoType { diff --git a/packages/restapi/src/lib/video/helpers/validateVideoRules.ts b/packages/restapi/src/lib/video/helpers/validateVideoRules.ts index f5c8a40a6..bb185fe8a 100644 --- a/packages/restapi/src/lib/video/helpers/validateVideoRules.ts +++ b/packages/restapi/src/lib/video/helpers/validateVideoRules.ts @@ -1,7 +1,7 @@ import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../../payloads/constants'; -import { VideNotificationRules } from '../../types'; +import { VideoNotificationRules } from '../../types'; -export const validateVideoRules = (rules: VideNotificationRules) => { +export const validateVideoRules = (rules: VideoNotificationRules) => { if ( rules.access.type === VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT && (!rules.access.data.chatId || rules.access.data.chatId === '') From 10d05a9014029675b1f8b45db6f6476404df1038 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 5 Jan 2024 17:04:57 +0530 Subject: [PATCH 07/13] feat(video stream): handle connect, retry internally from the SDK --- .../restapi/src/lib/pushapi/pushAPITypes.ts | 4 +- packages/restapi/src/lib/pushapi/video.ts | 57 ++++++++++++++++++- .../src/lib/pushstream/DataModifier.ts | 6 +- .../src/lib/pushstream/pushStreamTypes.ts | 3 + packages/restapi/src/lib/video/Video.ts | 2 +- 5 files changed, 66 insertions(+), 6 deletions(-) diff --git a/packages/restapi/src/lib/pushapi/pushAPITypes.ts b/packages/restapi/src/lib/pushapi/pushAPITypes.ts index b33e6c406..3c8363546 100644 --- a/packages/restapi/src/lib/pushapi/pushAPITypes.ts +++ b/packages/restapi/src/lib/pushapi/pushAPITypes.ts @@ -1,5 +1,6 @@ import Constants, { ENV } from '../constants'; -import { ChatStatus, ProgressHookType, Rules, SignerType } from '../types'; +import type { PushStream } from '../pushstream/PushStream'; +import { ChatStatus, ProgressHookType, Rules } from '../types'; export enum ChatListType { CHATS = 'CHATS', @@ -68,6 +69,7 @@ export interface ParticipantStatus { } export interface VideoInitializeOptions { + socketStream: PushStream; media: { video?: boolean; audio?: boolean; diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts index edf45775f..11aaddd8d 100644 --- a/packages/restapi/src/lib/pushapi/video.ts +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -1,9 +1,11 @@ import { ENV } from '../constants'; +import CONSTANTS from '../constantsV2'; import { SignerType, VideoCallData } from '../types'; import { Video as VideoV1 } from '../video/Video'; import { VideoV2 } from '../video/VideoV2'; import { VideoInitializeOptions } from './pushAPITypes'; +import { VideoEvent, VideoEventType } from '../pushstream/pushStreamTypes'; export class Video { constructor( @@ -15,9 +17,9 @@ export class Video { async initialize( setVideoData: (fn: (data: VideoCallData) => VideoCallData) => void, - options?: VideoInitializeOptions + options: VideoInitializeOptions ) { - const { media, stream } = options || {}; + const { socketStream, media, stream } = options; const chainId = await this.signer?.getChainId(); @@ -30,7 +32,9 @@ export class Video { } if (!this.decryptedPgpPvtKey) { - throw new Error('PushSDK was initialized in readonly mode. Video functionality is not available.'); + throw new Error( + 'PushSDK was initialized in readonly mode. Video functionality is not available.' + ); } // Initialize the video instance with the provided options @@ -55,6 +59,53 @@ export class Video { }), }); + // Setup video event handlers + socketStream.on(CONSTANTS.STREAM.VIDEO, (data: VideoEvent) => { + const { + address, + signal, + meta: { rules }, + } = data.peerInfo; + + // Check if the chatId from the incoming video event matches the chatId of the current video instance + if (rules.access.data.chatId === videoV1Instance.data.meta.chatId) { + // If the event is a ConnectVideo or RetryApproveVideo event, connect to the video + if ( + data.event === VideoEventType.ConnectVideo || + data.event === VideoEventType.RetryApproveVideo + ) { + videoV1Instance.connect({ peerAddress: address, signalData: signal }); + } + + // If the event is a RetryRequestVideo event and the current instance is the initiator, send a request + if ( + data.event === VideoEventType.RetryRequestVideo && + videoV1Instance.isInitiator() + ) { + videoV1Instance.request({ + senderAddress: this.account, + recipientAddress: address, + chatId: rules.access.data.chatId, + retry: true, + }); + } + + // If the event is a RetryRequestVideo event and the current instance is not the initiator, accept the request + if ( + data.event === VideoEventType.RetryRequestVideo && + !videoV1Instance.isInitiator() + ) { + videoV1Instance.acceptRequest({ + signalData: signal, + senderAddress: this.account, + recipientAddress: address, + chatId: rules.access.data.chatId, + retry: true, + }); + } + } + }); + // Return an instance of the video v2 class return new VideoV2({ videoV1Instance, diff --git a/packages/restapi/src/lib/pushstream/DataModifier.ts b/packages/restapi/src/lib/pushstream/DataModifier.ts index dfeceb167..6aedbc092 100644 --- a/packages/restapi/src/lib/pushstream/DataModifier.ts +++ b/packages/restapi/src/lib/pushstream/DataModifier.ts @@ -405,9 +405,13 @@ export class DataModifier { case VideoCallStatus.CONNECTED: return VideoEventType.ConnectVideo; case VideoCallStatus.ENDED: - return VideoEventType.DisconnectVideo + return VideoEventType.DisconnectVideo; case VideoCallStatus.DISCONNECTED: return VideoEventType.DenyVideo; + case VideoCallStatus.RETRY_INITIALIZED: + return VideoEventType.RetryRequestVideo; + case VideoCallStatus.RETRY_RECEIVED: + return VideoEventType.RetryApproveVideo; default: throw new Error(`Unknown video call status: ${currentVideoStatus}`); } diff --git a/packages/restapi/src/lib/pushstream/pushStreamTypes.ts b/packages/restapi/src/lib/pushstream/pushStreamTypes.ts index c4e5f587b..01cf61e7b 100644 --- a/packages/restapi/src/lib/pushstream/pushStreamTypes.ts +++ b/packages/restapi/src/lib/pushstream/pushStreamTypes.ts @@ -59,6 +59,9 @@ export enum VideoEventType { DenyVideo = 'video.deny', ConnectVideo = 'video.connect', DisconnectVideo = 'video.disconnect', + // retry events + RetryRequestVideo = 'video.retry.request', + RetryApproveVideo = 'video.retry.approve' } export enum ProposedEventNames { diff --git a/packages/restapi/src/lib/video/Video.ts b/packages/restapi/src/lib/video/Video.ts index 31226337a..66870c62d 100644 --- a/packages/restapi/src/lib/video/Video.ts +++ b/packages/restapi/src/lib/video/Video.ts @@ -88,7 +88,7 @@ export class Video { [key: string]: any; } = {}; - protected data: VideoCallData; + data: VideoCallData; setData: (fn: (data: VideoCallData) => VideoCallData) => void; constructor({ From 3dcace4d026b1286ad6ed950609c83ef2457974b Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Fri, 5 Jan 2024 18:01:04 +0530 Subject: [PATCH 08/13] feat(video connect): remove the connect method from the videov2 SDK --- packages/restapi/src/lib/video/VideoV2.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/packages/restapi/src/lib/video/VideoV2.ts b/packages/restapi/src/lib/video/VideoV2.ts index 38cfaddfa..479dee5f1 100644 --- a/packages/restapi/src/lib/video/VideoV2.ts +++ b/packages/restapi/src/lib/video/VideoV2.ts @@ -158,21 +158,6 @@ export class VideoV2 { }); } - /** - * Connect to a video call - * @param {VideoPeerInfo} peerInfo - The peer information - */ - async connect(peerInfo: VideoPeerInfo) { - validatePeerInfo(peerInfo); - - const { signal, address } = peerInfo; - - await this.videoInstance.connect({ - peerAddress: address, - signalData: signal, - }); - } - /** * Disconnect from a video call * @param {string} address - The address to disconnect from From 194e06481400893e8d7e21e25a201c037592a257 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Mon, 8 Jan 2024 15:19:55 +0530 Subject: [PATCH 09/13] fix(videov2): create push signer instance to get chain id in initialize() --- packages/restapi/src/lib/pushapi/video.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts index 11aaddd8d..5051cf052 100644 --- a/packages/restapi/src/lib/pushapi/video.ts +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -1,6 +1,7 @@ import { ENV } from '../constants'; import CONSTANTS from '../constantsV2'; import { SignerType, VideoCallData } from '../types'; +import { Signer as PushSigner } from '../helpers'; import { Video as VideoV1 } from '../video/Video'; import { VideoV2 } from '../video/VideoV2'; @@ -21,12 +22,6 @@ export class Video { ) { const { socketStream, media, stream } = options; - const chainId = await this.signer?.getChainId(); - - if (!chainId) { - throw new Error('Chain Id not retrievable from signer'); - } - if (!this.signer) { throw new Error('Signer is required for push video'); } @@ -37,6 +32,12 @@ export class Video { ); } + const chainId = await new PushSigner(this.signer).getChainId(); + + if (!chainId) { + throw new Error('Chain Id not retrievable from signer'); + } + // Initialize the video instance with the provided options const videoV1Instance = new VideoV1({ signer: this.signer!, From df4b6f8a9d61e7d670806465bc066516c1995eb9 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Wed, 10 Jan 2024 11:57:41 +0530 Subject: [PATCH 10/13] fix(video stream): add backwards compatibilty to rules object for older SDK versions --- packages/restapi/src/lib/pushstream/DataModifier.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/restapi/src/lib/pushstream/DataModifier.ts b/packages/restapi/src/lib/pushstream/DataModifier.ts index 6aedbc092..172b13f74 100644 --- a/packages/restapi/src/lib/pushstream/DataModifier.ts +++ b/packages/restapi/src/lib/pushstream/DataModifier.ts @@ -23,6 +23,7 @@ import { } from './pushStreamTypes'; import { VideoCallStatus, VideoPeerInfo } from '../types'; import { VideoDataType } from '../video'; +import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../payloads/constants'; export class DataModifier { public static handleChatGroupEvent(data: any, includeRaw = false): any { @@ -426,11 +427,21 @@ export class DataModifier { data.payload.data.additionalMeta?.data ); + // add backward compatibility to rules object + const rules = data.payload.rules ?? { + access: { + type: VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT, + data: { + chatId: data.payloads.chatId, + }, + }, + }; + const peerInfo: VideoPeerInfo = { address: senderAddress, signal: signalData, meta: { - rules: data.payload.rules, + rules, }, }; From 272667b1bb01e8742d89e742b1f410ba58b83d53 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Wed, 10 Jan 2024 12:15:29 +0530 Subject: [PATCH 11/13] fix(video stream): fix bug in rules object creation --- packages/restapi/src/lib/pushstream/DataModifier.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/restapi/src/lib/pushstream/DataModifier.ts b/packages/restapi/src/lib/pushstream/DataModifier.ts index 172b13f74..2c4cb62cd 100644 --- a/packages/restapi/src/lib/pushstream/DataModifier.ts +++ b/packages/restapi/src/lib/pushstream/DataModifier.ts @@ -423,16 +423,16 @@ export class DataModifier { origin: MessageOrigin, includeRaw = false ): VideoEvent { - const { senderAddress, signalData, status }: VideoDataType = JSON.parse( - data.payload.data.additionalMeta?.data - ); + const { senderAddress, signalData, status, chatId }: VideoDataType = + JSON.parse(data.payload.data.additionalMeta?.data); - // add backward compatibility to rules object + // To maintain backward compatibility, if the rules object is not present in the payload, + // we create a new rules object with chatId from additionalMeta.data const rules = data.payload.rules ?? { access: { type: VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT, data: { - chatId: data.payloads.chatId, + chatId, }, }, }; From e27725e4f571b742819b9e987e19f772a64bb814 Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Thu, 11 Jan 2024 18:21:10 +0530 Subject: [PATCH 12/13] feat(video): update param names for video.initialize() --- .../restapi/src/lib/pushapi/pushAPITypes.ts | 6 +++--- packages/restapi/src/lib/pushapi/video.ts | 20 +++++++++---------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/restapi/src/lib/pushapi/pushAPITypes.ts b/packages/restapi/src/lib/pushapi/pushAPITypes.ts index 3c8363546..48753dbc4 100644 --- a/packages/restapi/src/lib/pushapi/pushAPITypes.ts +++ b/packages/restapi/src/lib/pushapi/pushAPITypes.ts @@ -69,10 +69,10 @@ export interface ParticipantStatus { } export interface VideoInitializeOptions { - socketStream: PushStream; - media: { + stream: PushStream; + config: { video?: boolean; audio?: boolean; }; - stream?: MediaStream; + media?: MediaStream; } diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts index 5051cf052..068840508 100644 --- a/packages/restapi/src/lib/pushapi/video.ts +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -17,10 +17,10 @@ export class Video { ) {} async initialize( - setVideoData: (fn: (data: VideoCallData) => VideoCallData) => void, + onChange: (fn: (data: VideoCallData) => VideoCallData) => void, options: VideoInitializeOptions ) { - const { socketStream, media, stream } = options; + const { stream, config, media } = options; if (!this.signer) { throw new Error('Signer is required for push video'); @@ -44,24 +44,24 @@ export class Video { chainId, pgpPrivateKey: this.decryptedPgpPvtKey!, env: this.env, - setData: setVideoData, + setData: onChange, }); // Create the media stream with the provided options await videoV1Instance.create({ - ...(stream && { - stream, + ...(media && { + stream: media, }), - ...(media?.audio && { - audio: media.audio, + ...(config?.audio && { + audio: config.audio, }), - ...(media?.video && { - video: media.video, + ...(config?.video && { + video: config.video, }), }); // Setup video event handlers - socketStream.on(CONSTANTS.STREAM.VIDEO, (data: VideoEvent) => { + stream.on(CONSTANTS.STREAM.VIDEO, (data: VideoEvent) => { const { address, signal, From 9bd3ac8e22a9f04be34f646af77fe5163bb4a87f Mon Sep 17 00:00:00 2001 From: Madhur Gupta Date: Mon, 15 Jan 2024 12:37:43 +0530 Subject: [PATCH 13/13] feat(video): add internal event handlers for stream in video SDK --- packages/restapi/src/lib/pushapi/video.ts | 46 ++++++++++++++++++----- packages/restapi/src/lib/video/VideoV2.ts | 29 ++++++++++++-- 2 files changed, 63 insertions(+), 12 deletions(-) diff --git a/packages/restapi/src/lib/pushapi/video.ts b/packages/restapi/src/lib/pushapi/video.ts index 068840508..9a1495841 100644 --- a/packages/restapi/src/lib/pushapi/video.ts +++ b/packages/restapi/src/lib/pushapi/video.ts @@ -1,12 +1,14 @@ import { ENV } from '../constants'; import CONSTANTS from '../constantsV2'; -import { SignerType, VideoCallData } from '../types'; +import { SignerType, VideoCallData, VideoCallStatus } from '../types'; import { Signer as PushSigner } from '../helpers'; -import { Video as VideoV1 } from '../video/Video'; +import { Video as VideoV1, initVideoCallData } from '../video/Video'; import { VideoV2 } from '../video/VideoV2'; import { VideoInitializeOptions } from './pushAPITypes'; import { VideoEvent, VideoEventType } from '../pushstream/pushStreamTypes'; +import { produce } from 'immer'; +import { endStream } from '../video/helpers/mediaToggle'; export class Video { constructor( @@ -68,17 +70,43 @@ export class Video { meta: { rules }, } = data.peerInfo; + const chatId = rules.access.data.chatId; + + // If the event is RequestVideo, update the video call 'data' state with the incoming call data + if (data.event === VideoEventType.RequestVideo) { + videoV1Instance.setData((oldData) => { + return produce(oldData, (draft) => { + draft.local.address = this.account; + draft.incoming[0].address = address; + draft.incoming[0].status = VideoCallStatus.RECEIVED; + draft.meta.chatId = chatId!; + draft.meta.initiator.address = address; + draft.meta.initiator.signal = signal; + }); + }); + } + // Check if the chatId from the incoming video event matches the chatId of the current video instance - if (rules.access.data.chatId === videoV1Instance.data.meta.chatId) { - // If the event is a ConnectVideo or RetryApproveVideo event, connect to the video + if (chatId && chatId === videoV1Instance.data.meta.chatId) { + // If the event is DenyVideo, destroy the local stream & reset the video call data + if (data.event === VideoEventType.DenyVideo) { + // destroy the local stream + if (videoV1Instance.data.local.stream) { + endStream(videoV1Instance.data.local.stream); + } + + videoV1Instance.setData(() => initVideoCallData); + } + + // If the event is ApproveVideo or RetryApproveVideo, connect to the video if ( - data.event === VideoEventType.ConnectVideo || + data.event === VideoEventType.ApproveVideo || data.event === VideoEventType.RetryApproveVideo ) { videoV1Instance.connect({ peerAddress: address, signalData: signal }); } - // If the event is a RetryRequestVideo event and the current instance is the initiator, send a request + // If the event is RetryRequestVideo and the current instance is the initiator, send a request if ( data.event === VideoEventType.RetryRequestVideo && videoV1Instance.isInitiator() @@ -86,12 +114,12 @@ export class Video { videoV1Instance.request({ senderAddress: this.account, recipientAddress: address, - chatId: rules.access.data.chatId, + rules, retry: true, }); } - // If the event is a RetryRequestVideo event and the current instance is not the initiator, accept the request + // If the event is RetryRequestVideo and the current instance is not the initiator, accept the request if ( data.event === VideoEventType.RetryRequestVideo && !videoV1Instance.isInitiator() @@ -100,7 +128,7 @@ export class Video { signalData: signal, senderAddress: this.account, recipientAddress: address, - chatId: rules.access.data.chatId, + rules, retry: true, }); } diff --git a/packages/restapi/src/lib/video/VideoV2.ts b/packages/restapi/src/lib/video/VideoV2.ts index 479dee5f1..cad064e07 100644 --- a/packages/restapi/src/lib/video/VideoV2.ts +++ b/packages/restapi/src/lib/video/VideoV2.ts @@ -1,3 +1,4 @@ +import { produce } from 'immer'; import { chats } from '../chat'; import { ENV } from '../constants'; import { @@ -6,7 +7,11 @@ import { walletToPCAIP10, } from '../helpers'; import { VIDEO_NOTIFICATION_ACCESS_TYPE } from '../payloads/constants'; -import { VideoNotificationRules, VideoPeerInfo } from '../types'; +import { + VideoCallStatus, + VideoNotificationRules, + VideoPeerInfo, +} from '../types'; import { Video as VideoV1 } from './Video'; import { validatePeerInfo } from './helpers/validatePeerInfo'; @@ -118,12 +123,30 @@ export class VideoV2 { } } + this.videoInstance.setData((oldData) => { + return produce(oldData, (draft: any) => { + draft.local.address = this.account; + draft.incoming = recipients.map((recipient) => ({ + address: pCAIP10ToWallet(recipient), + status: VideoCallStatus.INITIALIZED, + })); + draft.meta.chatId = rules?.access.data.chatId ?? retrievedChatId; + }); + }); + await this.videoInstance.request({ senderAddress: pCAIP10ToWallet(this.account), recipientAddress: recipients.map((recipient) => pCAIP10ToWallet(recipient) ), - chatId: rules?.access.data.chatId ?? retrievedChatId, + rules: rules ?? { + access: { + type: VIDEO_NOTIFICATION_ACCESS_TYPE.PUSH_CHAT, + data: { + chatId: retrievedChatId, + }, + }, + }, }); } @@ -140,7 +163,7 @@ export class VideoV2 { senderAddress: pCAIP10ToWallet(this.account), recipientAddress: pCAIP10ToWallet(address), signalData: signal, - chatId: meta.rules.access.data.chatId, + rules: meta.rules, }); }