Skip to content

Commit 7adb286

Browse files
Merge pull request #969 from MyPureCloud/stream-1099
[STREAM-1099] - Emit `participantsUpdate` for JWT Video Guests
2 parents 06b6cf3 + 266339c commit 7adb286

6 files changed

Lines changed: 131 additions & 10 deletions

File tree

changelog.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
44
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
55

66
# [Unreleased](https://github.com/MyPureCloud/genesys-cloud-webrtc-sdk/compare/v11.5.1...HEAD)
7+
### Added
8+
* [STREAM-1099](https://inindca.atlassian.net/browse/STREAM-1099) - Emit `participantsUpdate` for JWT Video Guests
79
### Changed
810
* [STREAM-1178](https://inindca.atlassian.net/browse/STREAM-1178) - REVERT STREAM-825, removed ability to send multiple tracks when screensharing.
911

react-demo-app/package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client-private.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { GenesysCloudWebrtcSdk } from './client';
44
import { SessionManager } from './sessions/session-manager';
55
import { SubscriptionEvent } from './types/interfaces';
66
import { ConversationUpdate } from './conversations/conversation-update';
7+
import { isAgentVideoJid } from "./utils";
78

89
/**
910
* Establish the connection with the streaming client.
@@ -91,8 +92,19 @@ export async function setupStreamingClient (this: GenesysCloudWebrtcSdk): Promis
9192
export async function proxyStreamingClientEvents (this: GenesysCloudWebrtcSdk): Promise<void> {
9293
this.sessionManager = new SessionManager(this);
9394

94-
if (this._personDetails && !this.isJwtAuth) {
95-
await this._streamingConnection.notifications.subscribe(`v2.users.${this._personDetails.id}.conversations`, handleConversationUpdate.bind(this), true);
95+
if (this._personDetails) {
96+
if (this.isJwtAuth) {
97+
const convId = this._customerData.conversation?.id;
98+
const isGuest = !this._personDetails?.id;
99+
const roomJid = this._personDetails.chat?.jabberId;
100+
if (convId && isGuest && isAgentVideoJid(roomJid)) {
101+
this._streamingConnection.on(`notify:v2.guest.conversations.${convId}`, (conversationEvent) => {
102+
handleConversationUpdate.call(this, conversationEvent);
103+
});
104+
}
105+
} else {
106+
await this._streamingConnection.notifications.subscribe(`v2.users.${this._personDetails.id}.conversations`, handleConversationUpdate.bind(this), true);
107+
}
96108
}
97109

98110
// webrtc events

src/client.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,14 @@ export class GenesysCloudWebrtcSdk extends (EventEmitter as { new(): StrictEvent
694694
}
695695
};
696696

697+
this._customerData = {
698+
conversation: {
699+
id: decoded.data.conversationId,
700+
},
701+
sourceCommunicationId: decoded.data.sourceCommunicationId,
702+
jwt: this._config.jwt
703+
}
704+
697705
this._orgDetails = {
698706
id: decoded.org,
699707
name: null

src/types/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,8 @@ export interface JWTDetails {
714714
uid: string; // userId
715715
jid: string;
716716
gcbaid: string; // genesys cloud background assistant id
717+
conversationId?: string;
718+
sourceCommunicationId?: string;
717719
};
718720
exp: number; // exp in seconds
719721
iat: number; // issued at time in seconds,

test/unit/client-private.test.ts

Lines changed: 100 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import { GenesysCloudWebrtcSdk } from '../../src/client';
22
import { SimpleMockSdk } from '../test-utils';
33
import { CommunicationStates } from '../../src/types/enums';
4-
import { SubscriptionEvent } from '../../src/types/interfaces';
5-
import { handleConversationUpdate, setupStreamingClient, handleDisconnectedEvent } from '../../src/client-private';
4+
import { ICustomerData, IPersonDetails, SubscriptionEvent } from '../../src/types/interfaces';
5+
import { handleConversationUpdate, setupStreamingClient, handleDisconnectedEvent, proxyStreamingClientEvents } from '../../src/client-private';
66
import { ConversationUpdate } from '../../src/';
77
import StreamingClient from 'genesys-cloud-streaming-client';
8+
import { NotificationsApi } from "purecloud-platform-client-v2";
89

910
jest.mock('genesys-cloud-streaming-client');
1011

@@ -90,7 +91,7 @@ describe('setupStreamingClient', () => {
9091
describe('handleConversationUpdate', () => {
9192
it('should call sessionManager.handleConversationUpdate with the transformed event', () => {
9293
mockSdk.sessionManager = { handleConversationUpdateRaw: jest.fn(), handleConversationUpdate: jest.fn() } as any;
93-
94+
9495
const userId = '444kjskdk';
9596
const participant1 = {
9697
id: '7b809e10-fb79-4420-9d5f-69d232ddf490',
@@ -219,3 +220,99 @@ describe('handleDisconnectedEvent', () => {
219220
);
220221
});
221222
});
223+
224+
describe('proxyStreamingClientEvents', () => {
225+
it('should handle JWT auth path', async () => {
226+
const { handleConversationUpdateSpy, mockConversationUpdate } = await createEventDataAndCallProxyFunction();
227+
228+
expect(handleConversationUpdateSpy).toHaveBeenCalledWith(new ConversationUpdate(mockConversationUpdate.eventBody));
229+
});
230+
231+
it('should handle JWT auth path with no conversation id', async () => {
232+
const { handleConversationUpdateSpy, conversationUpdateHandler } = await createEventDataAndCallProxyFunction({ conversationId: undefined });
233+
234+
expect(conversationUpdateHandler).toBe(undefined);
235+
expect(handleConversationUpdateSpy).not.toHaveBeenCalled();
236+
});
237+
238+
it('should handle JWT auth path with non-agent conference', async () => {
239+
const { handleConversationUpdateSpy, conversationUpdateHandler } = await createEventDataAndCallProxyFunction({ jabberId: 'adhoc-123@conference.com' });
240+
241+
expect(conversationUpdateHandler).toBe(undefined);
242+
expect(handleConversationUpdateSpy).not.toHaveBeenCalled();
243+
});
244+
245+
it('should handle JWT auth path with missing conference jid', async () => {
246+
const { handleConversationUpdateSpy, conversationUpdateHandler } = await createEventDataAndCallProxyFunction({ jabberId: undefined });
247+
248+
expect(conversationUpdateHandler).toBe(undefined);
249+
expect(handleConversationUpdateSpy).not.toHaveBeenCalled();
250+
});
251+
252+
it('should handle JWT auth path with guest with userId', async () => {
253+
const { handleConversationUpdateSpy, conversationUpdateHandler } = await createEventDataAndCallProxyFunction({ userId: 'user123'});
254+
255+
expect(conversationUpdateHandler).toBe(undefined);
256+
expect(handleConversationUpdateSpy).not.toHaveBeenCalled();
257+
});
258+
259+
it('should still handle non-JWT auth path', async () => {
260+
const { handleConversationUpdateSpy, mockConversationUpdate } = await createEventDataAndCallProxyFunction({
261+
userId: 'user123',
262+
isJwtAuth: false
263+
});
264+
265+
expect(handleConversationUpdateSpy).toHaveBeenCalledWith(new ConversationUpdate(mockConversationUpdate.eventBody));
266+
expect(mockSdk._customerData).toBe(undefined);
267+
});
268+
269+
async function createEventDataAndCallProxyFunction(options = {} as { userId?: string, jabberId?: string, conversationId?: string, isJwtAuth?: boolean }) {
270+
const userId = 'userId' in options ? options.userId : undefined;
271+
const jabberId = 'jabberId' in options ? options.jabberId : 'agent-123@conference.com';
272+
const conversationId = 'conversationId' in options ? options.conversationId : 'conv123';
273+
const isJwtAuth = options.isJwtAuth ?? true;
274+
275+
mockSdk._personDetails = {
276+
id: userId,
277+
chat: { jabberId: jabberId }
278+
} as IPersonDetails;
279+
280+
if (isJwtAuth) { // we only assign this on jwt path
281+
mockSdk._customerData = {
282+
conversation: { id: conversationId }
283+
} as ICustomerData;
284+
}
285+
286+
Object.defineProperty(mockSdk, 'isJwtAuth', { get: () => isJwtAuth });
287+
288+
let conversationUpdateHandler;
289+
290+
mockSdk._streamingConnection = {
291+
on: jest.fn((event, handler) => {
292+
if (event === `notify:v2.guest.conversations.${conversationId}`) {
293+
if (conversationUpdateHandler) throw new Error('handler already assigned');
294+
conversationUpdateHandler = handler;
295+
}
296+
}),
297+
webrtcSessions: { on: jest.fn() },
298+
notifications: {
299+
subscribe: jest.fn((topic, handler) => {
300+
if (topic === `v2.users.${userId}.conversations`) {
301+
if (conversationUpdateHandler) throw new Error('handler already assigned');
302+
conversationUpdateHandler = handler;
303+
}
304+
})
305+
}
306+
} as unknown as StreamingClient;
307+
308+
await proxyStreamingClientEvents.call(mockSdk);
309+
310+
mockSdk.sessionManager = { handleConversationUpdate: jest.fn(), handleConversationUpdateRaw: jest.fn() } as never;
311+
const mockConversationUpdate = { eventBody: {}, metadata: { correlationId: '' }, topicName: '' };
312+
if (conversationUpdateHandler) conversationUpdateHandler(mockConversationUpdate);
313+
314+
const handleConversationUpdateSpy = mockSdk.sessionManager.handleConversationUpdate;
315+
316+
return { handleConversationUpdateSpy, conversationUpdateHandler, mockConversationUpdate};
317+
}
318+
});

0 commit comments

Comments
 (0)