Skip to content

Commit 7ac7b05

Browse files
authored
Merge pull request #987 from MyPureCloud/STREAM-781
STREAM-781: only hold active conversations
2 parents 6040f4e + e576287 commit 7ac7b05

3 files changed

Lines changed: 112 additions & 4 deletions

File tree

changelog.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
99

1010
### Fixed
1111
* [STREAM-905](https://inindca.atlassian.net/browse/STREAM-905) - Fix issue where `sessionStarted` was not emitted for calls that re-use persistent connections after a media recovery has occurred. Now initializing `_emittedSessionStarteds` for reinvites but only emitting if not a reinvite.
12-
1312
* [STREAM-1123](https://inindca.atlassian.net/browse/STREAM-1123) - Only update necessary tracks when changing devices. Prevent virtual background from getting replaced
13+
* [STREAM-781](https://inindca.atlassian.net/browse/STREAM-781) - Fix issue where hold caused `Unhandled promise rejection from setConversationHeld` for inactive conversations.
1414

1515
### Changed
1616
* [STREAM-1211](https://inindca.atlassian.net/browse/STREAM-1178) - Update `axios` to `v1.13.5`.

src/sessions/softphone-session-handler.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -901,16 +901,25 @@ export class SoftphoneSessionHandler extends BaseSessionHandler {
901901
* connection that will attempt to hold the previous ended call
902902
*/
903903
const otherSessions = sessions.filter(session => {
904+
const convo = this.conversations[session.conversationId];
904905
return session.sessionType === SessionTypes.softphone &&
905-
this.conversations[session.conversationId] &&
906+
convo &&
906907
!this.isConversationHeld(session.conversationId) &&
908+
!this.isEndedState(convo.mostRecentCallState) &&
907909
session !== currentSession;
908910
});
909911

910912
this.log('debug', 'Received new session or unheld previously held session with LA>1, holding other active sessions.');
911913

912914
otherSessions.forEach(session => {
913915
this.setConversationHeld(session, { conversationId: session.conversationId, held: true })
916+
.catch(err => {
917+
this.log('warn', 'Failed to hold other session during holdOtherSessions', {
918+
sessionId: session.id,
919+
conversationId: session.conversationId,
920+
error: err
921+
});
922+
});
914923
});
915924
}
916925

test/unit/sessions/softphone-session-handler.test.ts

Lines changed: 101 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -621,7 +621,7 @@ describe('acceptSession()', () => {
621621
});
622622

623623
it('should hold other sessions if LA>1', () => {
624-
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockImplementation();
624+
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockResolvedValue(undefined);
625625
const getActiveSessionsSpy = jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue(sessionsArray);
626626
const mockAudioElement = {} as HTMLAudioElement;
627627
jest.spyOn(BaseSessionHandler.prototype, 'acceptSession');
@@ -650,7 +650,7 @@ describe('acceptSession()', () => {
650650
});
651651

652652
it('should NOT hold other sessions if LA>1 and we are establishing an eager persistent connection', () => {
653-
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockImplementation();
653+
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockResolvedValue(undefined);
654654
const getActiveSessionsSpy = jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue(sessionsArray);
655655
const mockAudioElement = {} as HTMLAudioElement;
656656
jest.spyOn(BaseSessionHandler.prototype, 'acceptSession');
@@ -2451,6 +2451,105 @@ describe('setConversationHeld()', () => {
24512451
});
24522452
});
24532453

2454+
describe('holdOtherSessions()', () => {
2455+
let currentSession: IExtendedMediaSession;
2456+
let otherSession: IExtendedMediaSession;
2457+
2458+
beforeEach(() => {
2459+
currentSession = new MockSession(SessionTypes.softphone) as any;
2460+
otherSession = new MockSession(SessionTypes.softphone) as any;
2461+
otherSession.sessionType = SessionTypes.softphone;
2462+
});
2463+
2464+
it('should call setConversationHeld for other active softphone sessions', () => {
2465+
jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue([currentSession, otherSession]);
2466+
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockResolvedValue(undefined);
2467+
2468+
handler.conversations = {
2469+
[currentSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2470+
[otherSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2471+
};
2472+
2473+
handler.holdOtherSessions(currentSession);
2474+
2475+
expect(setHoldSpy).toHaveBeenCalledWith(otherSession, { conversationId: otherSession.conversationId, held: true });
2476+
expect(setHoldSpy).not.toHaveBeenCalledWith(currentSession, expect.anything());
2477+
});
2478+
2479+
it('should not attempt to hold sessions with ended conversation states', () => {
2480+
const endedSession = new MockSession(SessionTypes.softphone) as any;
2481+
endedSession.sessionType = SessionTypes.softphone;
2482+
2483+
jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue([currentSession, otherSession, endedSession]);
2484+
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockResolvedValue(undefined);
2485+
2486+
handler.conversations = {
2487+
[currentSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2488+
[otherSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2489+
[endedSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.disconnected, held: false } } as any,
2490+
};
2491+
2492+
handler.holdOtherSessions(currentSession);
2493+
2494+
expect(setHoldSpy).toHaveBeenCalledWith(otherSession, { conversationId: otherSession.conversationId, held: true });
2495+
expect(setHoldSpy).not.toHaveBeenCalledWith(endedSession, expect.anything());
2496+
});
2497+
2498+
it('should not attempt to hold sessions with terminated conversation states', () => {
2499+
jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue([currentSession, otherSession]);
2500+
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockResolvedValue(undefined);
2501+
2502+
handler.conversations = {
2503+
[currentSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2504+
[otherSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.terminated, held: false } } as any,
2505+
};
2506+
2507+
handler.holdOtherSessions(currentSession);
2508+
2509+
expect(setHoldSpy).not.toHaveBeenCalled();
2510+
});
2511+
2512+
it('should log a warning and not throw if setConversationHeld rejects', async () => {
2513+
jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue([currentSession, otherSession]);
2514+
const error = new Error('Bad Request');
2515+
jest.spyOn(handler, 'setConversationHeld').mockRejectedValue(error);
2516+
2517+
handler.conversations = {
2518+
[currentSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2519+
[otherSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2520+
};
2521+
2522+
// should not throw
2523+
handler.holdOtherSessions(currentSession);
2524+
2525+
// flush the microtask queue so the .catch() handler runs
2526+
await flushPromises();
2527+
2528+
expect(mockSdk.logger.warn).toHaveBeenCalledWith(
2529+
expect.stringContaining('Failed to hold other session during holdOtherSessions'),
2530+
expect.objectContaining({
2531+
sessionId: otherSession.id,
2532+
conversationId: otherSession.conversationId,
2533+
}),
2534+
undefined
2535+
);
2536+
});
2537+
2538+
it('should not attempt to hold sessions that are already held', () => {
2539+
jest.spyOn(mockSessionManager, 'getAllActiveSessions').mockReturnValue([currentSession, otherSession]);
2540+
const setHoldSpy = jest.spyOn(handler, 'setConversationHeld').mockResolvedValue(undefined);
2541+
2542+
handler.conversations = {
2543+
[currentSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: false } } as any,
2544+
[otherSession.conversationId]: { mostRecentCallState: { state: CommunicationStates.connected, held: true } } as any,
2545+
};
2546+
2547+
handler.holdOtherSessions(currentSession);
2548+
2549+
expect(setHoldSpy).not.toHaveBeenCalled();
2550+
});
2551+
});
2552+
24542553
describe('patchPhoneCall()', () => {
24552554
it('should patch a phone call', async () => {
24562555
const conversationId = 'talking-buddies';

0 commit comments

Comments
 (0)