diff --git a/changelog.md b/changelog.md index 9a946eff2..fee5752ea 100644 --- a/changelog.md +++ b/changelog.md @@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). # [Unreleased](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v9.0.5...HEAD) +### Added +* [PCM-2081](https://inindca.atlassian.net/browse/PCM-2081) Add ability to join a video conference using a meeting id # [v9.0.5](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v9.0.4...v9.0.5) ### Added diff --git a/package-lock.json b/package-lock.json index 5614ef047..4947b884b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "browserama": "^3.1.1", "core-js": "^3.7.0", "genesys-cloud-client-logger": "^4.2.10", - "genesys-cloud-streaming-client": "^17.0.3", + "genesys-cloud-streaming-client": "^17.1.0", "jwt-decode": "^3.1.2", "lodash": "^4.17.15", "process-fast": "^1.0.0", @@ -34,13 +34,14 @@ "@babel/preset-typescript": "^7.12.1", "@rollup/plugin-commonjs": "^22.0.0-1", "@rollup/plugin-node-resolve": "^13.0.6", + "@types/dom-webcodecs": "^0.1.11", "@types/jest": "^26.0.15", "@types/lodash": "^4.14.165", "@types/nock": "^11.1.0", - "@types/node": "^15.12.0", + "@types/node": "^20.11.30", "@types/safe-json-stringify": "^1.1.0", "@types/uuid": "^3.4.9", - "@types/webrtc": "^0.0.30", + "@types/webrtc": "^0.0.42", "@types/ws": "^7.2.9", "@typescript-eslint/eslint-plugin": "^4.29.3", "@typescript-eslint/parser": "^4.29.3", @@ -67,7 +68,7 @@ "stupid-server": "^0.2.5", "ts-jest": "^26.4.4", "ts-node": "^10.2.1", - "typescript": "^4.0.5", + "typescript": "^5.4.2", "uglify-js": "^3.11.5", "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", @@ -2587,6 +2588,12 @@ "@babel/types": "^7.3.0" } }, + "node_modules/@types/dom-webcodecs": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.11.tgz", + "integrity": "sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==", + "dev": true + }, "node_modules/@types/estree": { "version": "0.0.39", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", @@ -2655,8 +2662,12 @@ } }, "node_modules/@types/node": { - "version": "15.14.9", - "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "dependencies": { + "undici-types": "~5.26.4" + } }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", @@ -2697,8 +2708,9 @@ "dev": true }, "node_modules/@types/webrtc": { - "version": "0.0.30", - "integrity": "sha512-6RtNrygZrRP8dPMzYejjfRQHiNOGqb6T9dbXaDXorm9opA2N8t6VfFKtHPoJUHErjsSBX/XhDDEf+r+ptmLZJw==", + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.42.tgz", + "integrity": "sha512-YyrG3yzw/JvZs0q3f9SY+AiJr0FJw1qhBjvoQJEUy07udiyakAax2A87BHpB3qnaxsm2ExCvhyUvjTxdsn+qSw==", "dev": true }, "node_modules/@types/ws": { @@ -7454,9 +7466,9 @@ } }, "node_modules/genesys-cloud-streaming-client": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/genesys-cloud-streaming-client/-/genesys-cloud-streaming-client-17.0.3.tgz", - "integrity": "sha512-udIaimS8Q6WhJiKAn8m4PssDUdyt7wQRDsE/ysgCALy8vC72ISvNrQbqVlpyAYE/xdI00g4Dkv94r+2M1pTN1Q==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/genesys-cloud-streaming-client/-/genesys-cloud-streaming-client-17.1.0.tgz", + "integrity": "sha512-czeTFhN3YeI4EpMa4O1ALNnHzqtSTDahpwxdl6TID8g8g3zo+M5Wd5BaNn59SdFLqxiM7Kq9AsX++ikejTs1RA==", "dependencies": { "@babel/runtime-corejs3": "^7.10.4", "axios": "^1.6.5", @@ -15014,15 +15026,16 @@ } }, "node_modules/typescript": { - "version": "4.4.3", - "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/uglify-js": { @@ -15055,6 +15068,11 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", @@ -18086,6 +18104,12 @@ "@babel/types": "^7.3.0" } }, + "@types/dom-webcodecs": { + "version": "0.1.11", + "resolved": "https://registry.npmjs.org/@types/dom-webcodecs/-/dom-webcodecs-0.1.11.tgz", + "integrity": "sha512-yPEZ3z7EohrmOxbk/QTAa0yonMFkNkjnVXqbGb7D4rMr+F1dGQ8ZUFxXkyLLJuiICPejZ0AZE9Rrk9wUCczx4A==", + "dev": true + }, "@types/estree": { "version": "0.0.39", "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", @@ -18153,8 +18177,12 @@ } }, "@types/node": { - "version": "15.14.9", - "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==" + "version": "20.11.30", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz", + "integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==", + "requires": { + "undici-types": "~5.26.4" + } }, "@types/normalize-package-data": { "version": "2.4.1", @@ -18195,8 +18223,9 @@ "dev": true }, "@types/webrtc": { - "version": "0.0.30", - "integrity": "sha512-6RtNrygZrRP8dPMzYejjfRQHiNOGqb6T9dbXaDXorm9opA2N8t6VfFKtHPoJUHErjsSBX/XhDDEf+r+ptmLZJw==", + "version": "0.0.42", + "resolved": "https://registry.npmjs.org/@types/webrtc/-/webrtc-0.0.42.tgz", + "integrity": "sha512-YyrG3yzw/JvZs0q3f9SY+AiJr0FJw1qhBjvoQJEUy07udiyakAax2A87BHpB3qnaxsm2ExCvhyUvjTxdsn+qSw==", "dev": true }, "@types/ws": { @@ -22053,9 +22082,9 @@ } }, "genesys-cloud-streaming-client": { - "version": "17.0.3", - "resolved": "https://registry.npmjs.org/genesys-cloud-streaming-client/-/genesys-cloud-streaming-client-17.0.3.tgz", - "integrity": "sha512-udIaimS8Q6WhJiKAn8m4PssDUdyt7wQRDsE/ysgCALy8vC72ISvNrQbqVlpyAYE/xdI00g4Dkv94r+2M1pTN1Q==", + "version": "17.1.0", + "resolved": "https://registry.npmjs.org/genesys-cloud-streaming-client/-/genesys-cloud-streaming-client-17.1.0.tgz", + "integrity": "sha512-czeTFhN3YeI4EpMa4O1ALNnHzqtSTDahpwxdl6TID8g8g3zo+M5Wd5BaNn59SdFLqxiM7Kq9AsX++ikejTs1RA==", "requires": { "@babel/runtime-corejs3": "^7.10.4", "axios": "^1.6.5", @@ -27824,8 +27853,9 @@ } }, "typescript": { - "version": "4.4.3", - "integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==", + "version": "5.4.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz", + "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==", "dev": true }, "uglify-js": { @@ -27849,6 +27879,11 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true }, + "undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, "unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", diff --git a/package.json b/package.json index 0f664ba53..92f43b07a 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,7 @@ "browserama": "^3.1.1", "core-js": "^3.7.0", "genesys-cloud-client-logger": "^4.2.10", - "genesys-cloud-streaming-client": "^17.0.3", + "genesys-cloud-streaming-client": "^17.1.0", "jwt-decode": "^3.1.2", "lodash": "^4.17.15", "process-fast": "^1.0.0", @@ -80,13 +80,14 @@ "@babel/preset-typescript": "^7.12.1", "@rollup/plugin-commonjs": "^22.0.0-1", "@rollup/plugin-node-resolve": "^13.0.6", + "@types/dom-webcodecs": "^0.1.11", "@types/jest": "^26.0.15", "@types/lodash": "^4.14.165", "@types/nock": "^11.1.0", - "@types/node": "^15.12.0", + "@types/node": "^20.11.30", "@types/safe-json-stringify": "^1.1.0", "@types/uuid": "^3.4.9", - "@types/webrtc": "^0.0.30", + "@types/webrtc": "^0.0.42", "@types/ws": "^7.2.9", "@typescript-eslint/eslint-plugin": "^4.29.3", "@typescript-eslint/parser": "^4.29.3", @@ -113,7 +114,7 @@ "stupid-server": "^0.2.5", "ts-jest": "^26.4.4", "ts-node": "^10.2.1", - "typescript": "^4.0.5", + "typescript": "^5.4.2", "uglify-js": "^3.11.5", "vinyl-buffer": "^1.0.1", "vinyl-source-stream": "^2.0.0", diff --git a/src/client.ts b/src/client.ts index aac6a9afc..4c6fb5bf8 100644 --- a/src/client.ts +++ b/src/client.ts @@ -373,6 +373,25 @@ export class GenesysCloudWebrtcSdk extends (EventEmitter as { new(): StrictEvent } } + /** + * Start a video conference using a meeting id. Not supported for guests. + * Conferences can only be joined by authenticated users + * from the same organization. + * + * `initialize()` must be called first. + * + * @param meetingId meetingId of the conference to join. + * + * @returns a promise with an object with the newly created 'conversationId' + */ + async startVideoMeeting (meetingId: string): Promise<{ conversationId: string }> { + if (!this.isGuest) { + return this.sessionManager.startSession({ meetingId, sessionType: SessionTypes.collaborateVideo }); + } else { + throw createAndEmitSdkError.call(this, SdkErrorTypes.not_supported, 'video conferencing not supported for guests'); + } + } + /** * Start a softphone session with the given peer or peers. * `initialize()` must be called first. diff --git a/src/sessions/session-manager.ts b/src/sessions/session-manager.ts index 77cd822c6..b9c5c8de5 100644 --- a/src/sessions/session-manager.ts +++ b/src/sessions/session-manager.ts @@ -14,6 +14,7 @@ import { ISessionMuteRequest, IUpdateOutgoingMedia, IStartVideoSessionParams, + IStartVideoMeetingSessionParams, IExtendedMediaSession, IStartSoftphoneSessionParams, ISessionIdAndConversationId, @@ -174,7 +175,7 @@ export class SessionManager { return handler; } - async startSession (startSessionParams: IStartSessionParams | IStartVideoSessionParams | IStartSoftphoneSessionParams): Promise { + async startSession (startSessionParams: IStartSessionParams | IStartVideoSessionParams | IStartVideoMeetingSessionParams | IStartSoftphoneSessionParams): Promise { if (!this.sdk.connected) { throw createAndEmitSdkError.call(this.sdk, SdkErrorTypes.session, 'A session cannot be started as streaming client is not yet connected', { sessionType: startSessionParams.sessionType }); } @@ -305,7 +306,8 @@ export class SessionManager { originalRoomJid: sessionInfo.originalRoomJid, fromUserId: sessionInfo.fromUserId, toJid: sessionInfo.toJid, - fromJid: sessionInfo.fromJid + fromJid: sessionInfo.fromJid, + meetingId: sessionInfo.meetingId }; this.pendingSessions.push(pendingSession); @@ -396,6 +398,8 @@ export class SessionManager { return; } + const frame = new VideoFrame('lskdjf' as any) + session._alreadyAccepted = true; const sessionHandler = this.getSessionHandler({ jingleSession: session }); diff --git a/src/sessions/video-session-handler.ts b/src/sessions/video-session-handler.ts index 05dc02143..91127533e 100644 --- a/src/sessions/video-session-handler.ts +++ b/src/sessions/video-session-handler.ts @@ -13,6 +13,7 @@ import { IConversationParticipant, IMediaRequestOptions, IStartVideoSessionParams, + IStartVideoMeetingSessionParams, VideoMediaSession, MemberStatusMessage } from '../types/interfaces'; @@ -54,6 +55,7 @@ export interface IMediaChangeEventParticipant { export class VideoSessionHandler extends BaseSessionHandler { requestedSessions: { [roomJid: string]: boolean } = {}; + requestedMeetingSessions: { [meetingId: string]: boolean } = {}; sessionType = SessionTypes.collaborateVideo; @@ -216,33 +218,58 @@ export class VideoSessionHandler extends BaseSessionHandler { } // triggers a propose from the backend - async startSession (startParams: IStartVideoSessionParams): Promise<{ conversationId: string }> { - let participant: { address: string }; + async startSession(startParams: IStartVideoSessionParams | IStartVideoMeetingSessionParams): Promise<{ conversationId: string }> { + if ((startParams).jid) { + const conferenceParams = (startParams); + let participant: { address: string }; - if (startParams.inviteeJid) { - participant = { address: startParams.inviteeJid }; - } else { - participant = { address: this.sdk._personDetails.chat.jabberId }; - } + if (conferenceParams.inviteeJid) { + participant = { address: conferenceParams.inviteeJid }; + } else { + participant = { address: this.sdk._personDetails.chat.jabberId }; + } - const data = JSON.stringify({ - roomId: startParams.jid, - participant - }); + const data = JSON.stringify({ + roomId: conferenceParams.jid, + participant + }); - this.requestedSessions[startParams.jid] = true; + this.requestedSessions[conferenceParams.jid] = true; - try { - const response = await requestApi.call(this.sdk, `/conversations/videos`, { - method: 'post', - data + try { + const response = await requestApi.call(this.sdk, `/conversations/videos`, { + method: 'post', + data + }); + + return { conversationId: response.data.conversationId }; + } catch (err) { + delete this.requestedSessions[conferenceParams.jid]; + this.log('error', 'Failed to request video session', err); + throw err; + } + } else { + const meetingParams = (startParams); + const participant = { address: this.sdk._personDetails.chat.jabberId }; + const data = JSON.stringify({ + meetingId: meetingParams.meetingId, + participant }); - return { conversationId: response.data.conversationId }; - } catch (err) { - delete this.requestedSessions[startParams.jid]; - this.log('error', 'Failed to request video session', err); - throw err; + this.requestedMeetingSessions[meetingParams.meetingId] = true; + + try { + const response = await requestApi.call(this.sdk, `/conversations/videos/participants`, { + method: 'post', + data + }); + + return { conversationId: response.data.conversationId }; + } catch (err) { + delete this.requestedMeetingSessions[meetingParams.meetingId]; + this.log('error', 'Failed to request video session', err); + throw err; + } } } @@ -255,6 +282,13 @@ export class VideoSessionHandler extends BaseSessionHandler { return; } + if (this.requestedMeetingSessions[pendingSession.meetingId]) { + logPendingSession(this.sdk.logger, 'Propose received for requested video session, accepting automatically', pendingSession, 'debug'); + delete this.requestedMeetingSessions[pendingSession.meetingId]; + await this.proceedWithSession(pendingSession); + return; + } + if (isPeerVideoJid(pendingSession.fromJid)) { if (pendingSession.fromUserId === this.sdk._personDetails.id) { logPendingSession(this.sdk.logger, diff --git a/src/types/interfaces.ts b/src/types/interfaces.ts index e81b70182..705ed8554 100644 --- a/src/types/interfaces.ts +++ b/src/types/interfaces.ts @@ -810,6 +810,10 @@ export interface IStartVideoSessionParams extends IStartSessionParams { inviteeJid?: string; } +export interface IStartVideoMeetingSessionParams extends IStartSessionParams { + meetingId: string; +} + export interface ISessionMuteRequest { /** conversation id */ conversationId: string; diff --git a/src/utils.ts b/src/utils.ts index 368c6cb22..af5bab9be 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -126,7 +126,8 @@ export const logPendingSession = function ( sessionId: (pendingSession as IPendingSession).id || (pendingSession as ISessionInfo).sessionId, autoAnswer: pendingSession.autoAnswer, conversationId: pendingSession.conversationId, - fromUserId: pendingSession.fromUserId + fromUserId: pendingSession.fromUserId, + meetingId: pendingSession.meetingId }; /* for pending sessions */ diff --git a/test/unit/client.test.ts b/test/unit/client.test.ts index 8398b3a07..3025e1dad 100644 --- a/test/unit/client.test.ts +++ b/test/unit/client.test.ts @@ -215,7 +215,7 @@ describe('Client', () => { }); describe('startVideoConference()', () => { - it('should call session manager to start screenshare', async () => { + it('should call session manager to start video conference', async () => { sdk = constructSdk(); sessionManagerMock.startSession.mockResolvedValue({}); @@ -235,6 +235,27 @@ describe('Client', () => { }); }); + describe('startVideoMeeting()', () => { + it('should call session manager to start video meeting', async () => { + sdk = constructSdk(); + + sessionManagerMock.startSession.mockResolvedValue({}); + await sdk.startVideoMeeting('123abc'); + expect(sessionManagerMock.startSession).toBeCalledWith({ meetingId: '123abc', sessionType: SessionTypes.collaborateVideo }); + }); + + it('should throw if guest user', async () => { + sdk = constructSdk({ organizationId: 'some-org' }); // no access_token is a guest user + try { + await sdk.startVideoMeeting('123'); + fail('should have failed'); + } catch (e) { + expect(e).toEqual(new Error('video conferencing not supported for guests')); + expect(sessionManagerMock.startSession).not.toHaveBeenCalled(); + } + }); + }); + describe('startScreenShare()', () => { it('should reject if authenticated user', async () => { sdk = constructSdk(); diff --git a/test/unit/sessions/video-session-handler.test.ts b/test/unit/sessions/video-session-handler.test.ts index 435bec11a..656fc5a09 100644 --- a/test/unit/sessions/video-session-handler.test.ts +++ b/test/unit/sessions/video-session-handler.test.ts @@ -391,7 +391,7 @@ describe('mediaUpdateEvent', () => { }); describe('startSession', () => { - it('should post to api', async () => { + it('should post to video conference api', async () => { const roomJid = '123@conference.com'; mockSdk._personDetails = { @@ -413,7 +413,7 @@ describe('startSession', () => { expect(utils.requestApi).toHaveBeenCalledWith('/conversations/videos', { method: 'post', data: expected }); }); - it('should post to api with an invitee', async () => { + it('should post to video conference api with an invitee', async () => { const roomJid = '123@conference.com'; mockSdk._personDetails = { @@ -435,7 +435,7 @@ describe('startSession', () => { expect(utils.requestApi).toHaveBeenCalledWith('/conversations/videos', { method: 'post', data: expected }); }); - it('should log error on failure', async () => { + it('should log error on video conference failure', async () => { const roomJid = '123@conference.com'; const error = new Error('test'); jest.spyOn(utils, 'requestApi').mockRejectedValue(error); @@ -451,6 +451,45 @@ describe('startSession', () => { expect(logSpy).toHaveBeenCalledWith('Failed to request video session', expect.anything(), undefined); }); + + it('should post to video meeting api', async () => { + const meetingId = '123abc'; + + mockSdk._personDetails = { + chat: { + jabberId: 'part1@test.com' + } + } as any; + + jest.spyOn(utils, 'requestApi').mockResolvedValue({ data: 'sldk' }); + await handler.startSession({ meetingId: meetingId, sessionType: SessionTypes.collaborateVideo }); + + const expected = JSON.stringify({ + meetingId: meetingId, + participant: { + address: 'part1@test.com' + } + }); + + expect(utils.requestApi).toHaveBeenCalledWith('/conversations/videos/participants', { method: 'post', data: expected }); + }); + + it('should log error on meeting failure', async () => { + const meetingId = '123@conference.com'; + const error = new Error('test'); + jest.spyOn(utils, 'requestApi').mockRejectedValue(error); + + mockSdk._personDetails = { + chat: { + jabberId: 'part1@test.com' + } + } as any; + + const logSpy = jest.spyOn(mockSdk.logger, 'error'); + await expect(handler.startSession({ meetingId: meetingId, sessionType: SessionTypes.collaborateVideo })).rejects.toBe(error); + + expect(logSpy).toHaveBeenCalledWith('Failed to request video session', expect.anything(), undefined); + }); }); describe('handlePropose', () => { @@ -476,6 +515,30 @@ describe('handlePropose', () => { expect(parentHandler).not.toHaveBeenCalled(); }); + it('should handle requested meeting sessions automatically', async () => { + const conferenceJid = '123@conference.com'; + const previouslyRequestedMeetingId = 'abc123'; + handler.requestedMeetingSessions[previouslyRequestedMeetingId] = true; + + const parentHandler = jest.spyOn(BaseSessionHandler.prototype, 'handlePropose'); + jest.spyOn(handler, 'proceedWithSession').mockResolvedValue({}); + + await handler.handlePropose({ + id: '1241241', + sessionId: '1241241', + sessionType: SessionTypes.collaborateVideo, + fromJid: conferenceJid, + toJid: '', + autoAnswer: false, + conversationId: '141241241', + originalRoomJid: conferenceJid, + meetingId: previouslyRequestedMeetingId + }); + + expect(handler.proceedWithSession).toBeCalled(); + expect(parentHandler).not.toHaveBeenCalled(); + }); + it('should not emit session if not requested and its a conference', async () => { const jid = '123@conference.com'; diff --git a/tsconfig.json b/tsconfig.json index 662016e7d..b2507600f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -59,7 +59,8 @@ "jest", "lodash", "uuid", - "webrtc" + "webrtc", + "dom-webcodecs" ], /* Type declaration files to be included in compilation. */ // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */