Skip to content

Commit 6e10f68

Browse files
committed
[STREAM-1180] Allow proposes for autoAnswer calls
1 parent 6048b6d commit 6e10f68

7 files changed

Lines changed: 93 additions & 36 deletions

File tree

src/client.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,12 @@ export class GenesysCloudWebrtcSdk extends (EventEmitter as { new(): StrictEvent
866866
setMediaHandling (mediaHandling: MediaHandling): void {
867867
this._mediaHandling = mediaHandling;
868868

869-
if (mediaHandling === MediaHandling.noMedia) {
869+
// The intent behind this comes from requirements for alerting leader
870+
// where a client shouldn't handle media (mostly) if it isn't the alerting
871+
// leader. Ideally this would only handle outbound calls, but we can't
872+
// distinguish between outbound calls and inbound autoanswer ACD calls when
873+
// we receive the `propose`.
874+
if (mediaHandling === MediaHandling.autoAnswerOnly) {
870875
this.setUseHeadsets(false);
871876

872877
// Disconnect any persistent connections we might have

src/headsets/headset.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export class HeadsetProxyService implements ISdkHeadsetService {
4949
// TODO: PCM-2060 - remove this
5050
this.useHeadsetOrchestration = !this.sdk._config.disableHeadsetControlsOrchestration;
5151

52-
if (this.sdk._mediaHandling === MediaHandling.noMedia) {
52+
if (this.sdk._mediaHandling === MediaHandling.autoAnswerOnly) {
5353
this.sdk.logger.warn('setUseHeadsets was called with `true` but headsets are not supported in this configuration - not handling media. Not activating headsets.');
5454
useHeadsets = false;
5555
}

src/sessions/softphone-session-handler.ts

Lines changed: 34 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -149,37 +149,50 @@ export class SoftphoneSessionHandler extends BaseSessionHandler {
149149
async handlePropose (pendingSession: IPendingSession): Promise<void> {
150150
const isPrivAnswerAuto = pendingSession.privAnswerMode === 'Auto';
151151
const eagerConnectionEstablishmentMode = this.sdk._config.eagerPersistentConnectionEstablishment;
152+
const autoAnswerOnly = this.sdk._mediaHandling === MediaHandling.autoAnswerOnly;
152153
const logInfo = { sessionId: pendingSession?.id, conversationId: pendingSession.conversationId };
153154

154-
if (this.sdk._mediaHandling === MediaHandling.noMedia) {
155-
this.log('info', 'media handling is set to "no-media" so propose will be ignored', logInfo);
156-
return;
157-
}
158-
159-
if (isPrivAnswerAuto) {
160-
this.log('info', 'received a propose with privAnswerMode=Auto', logInfo);
161-
162-
if (eagerConnectionEstablishmentMode === 'none') {
163-
this.log('info', 'eagerPersistentConnectionEstablishment is "none" so propose with privAnswerMode=Auto will be ignored', logInfo);
164-
return;
165-
} else if (eagerConnectionEstablishmentMode === 'auto') {
166-
// we don't need to emit a pendingSession event when we auto-answer eager persistent connections
167-
return await this.proceedWithSession(pendingSession);
168-
} else {
155+
if (autoAnswerOnly) {
156+
if (pendingSession.autoAnswer) {
157+
// emit the pendingSession event
169158
await super.handlePropose(pendingSession);
170-
}
171-
} else {
172-
// we want to emit the pendingSession event in all other cases
173-
await super.handlePropose(pendingSession);
174159

175-
// calls will can be marked as auto-answer or priv-answer-mode: Auto, but never both
176-
if (pendingSession.autoAnswer) {
177160
if (this.sdk._config.disableAutoAnswer) {
178161
// It is possible that the consuming client has its own logic for auto-answering calls (e.g. web-dir).
179162
this.log('info', 'received an autoAnswer tagged propose but the SDK was configured to not auto-answer, deferring to the consuming client.', logInfo);
180163
} else {
181164
await this.proceedWithSession(pendingSession);
182165
}
166+
} else {
167+
this.log('info', 'media handling is set to "auto-answer-only", but this propose is not marked as autoAnswer and will be ignored', logInfo);
168+
return;
169+
}
170+
} else {
171+
if (isPrivAnswerAuto) {
172+
this.log('info', 'received a propose with privAnswerMode=Auto', logInfo);
173+
174+
if (eagerConnectionEstablishmentMode === 'none') {
175+
this.log('info', 'eagerPersistentConnectionEstablishment is "none" so propose with privAnswerMode=Auto will be ignored', logInfo);
176+
return;
177+
} else if (eagerConnectionEstablishmentMode === 'auto') {
178+
// we don't need to emit a pendingSession event when we auto-answer eager persistent connections
179+
return await this.proceedWithSession(pendingSession);
180+
} else {
181+
await super.handlePropose(pendingSession);
182+
}
183+
} else {
184+
// we want to emit the pendingSession event in all other cases
185+
await super.handlePropose(pendingSession);
186+
187+
// calls will can be marked as auto-answer or priv-answer-mode: Auto, but never both
188+
if (pendingSession.autoAnswer) {
189+
if (this.sdk._config.disableAutoAnswer) {
190+
// It is possible that the consuming client has its own logic for auto-answering calls (e.g. web-dir).
191+
this.log('info', 'received an autoAnswer tagged propose but the SDK was configured to not auto-answer, deferring to the consuming client.', logInfo);
192+
} else {
193+
await this.proceedWithSession(pendingSession);
194+
}
195+
}
183196
}
184197
}
185198
}

src/types/enums.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ export enum JingleReasons {
4646
}
4747

4848
export enum MediaHandling {
49+
/** Handle all media; headset controls use traditional orchestration */
4950
media = 'media',
51+
/** Handle all media; headset controls follow alerting leader */
5052
alertingLeaderMedia = 'alerting-leader-media',
51-
noMedia = 'no-media'
53+
/** Only handle autoAnswer calls; headset controls are not used */
54+
// Ideally we would only allow outbound calls, but currently we can't distinguish between an outbound call and an inbound autoanswer ACD call
55+
autoAnswerOnly = 'auto-answer-only'
5256
}

test/unit/client.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,7 +1161,7 @@ describe('Client', () => {
11611161
});
11621162

11631163
describe('setMediaHandling()', () => {
1164-
it('should stop using headsets and disconnect any active sessions when set to "noMedia"', () => {
1164+
it('should stop using headsets and disconnect any active sessions when set to "autoAnswerOnly"', () => {
11651165
sdk = constructSdk();
11661166
const useHeadsetsSpy = jest.fn();
11671167
sdk.setUseHeadsets = useHeadsetsSpy;
@@ -1172,7 +1172,7 @@ describe('Client', () => {
11721172
const forceTerminateSpy = jest.fn();
11731173
sessionManagerMock.forceTerminateSession = forceTerminateSpy;
11741174

1175-
sdk.setMediaHandling(MediaHandling.noMedia);
1175+
sdk.setMediaHandling(MediaHandling.autoAnswerOnly);
11761176

11771177
expect(useHeadsetsSpy).toHaveBeenCalledWith(false);
11781178
expect(forceTerminateSpy).toHaveBeenCalledWith(sessionId);

test/unit/headset/headset.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ describe('HeadsetProxyService', () => {
301301
expect(proxyService['currentHeadsetService']).toBeInstanceOf(SdkHeadsetServiceFake);
302302
});
303303

304-
it('should use fake service if useHeadsets and not handling media', () => {
305-
proxyService['sdk']._mediaHandling = MediaHandling.noMedia;
304+
it('should use fake service if useHeadsets and only handling autoAnswer calls', () => {
305+
proxyService['sdk']._mediaHandling = MediaHandling.autoAnswerOnly;
306306
const spy = jest.spyOn(proxyService, 'updateAudioInputDevice');
307307

308308
proxyService.setUseHeadsets(true);

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

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -70,37 +70,72 @@ describe('shouldHandleSessionByJid()', () => {
7070
});
7171

7272
describe('handlePropose()', () => {
73-
it('should emit pending session and proceed immediately if autoAnswer', async () => {
73+
it('should ignore the propose if mediaHandling is "autoAnswerOnly" and autoAnswer is false', async () => {
7474
const superSpyHandlePropose = jest.spyOn(BaseSessionHandler.prototype, 'handlePropose');
7575
const superSpyProceed = jest.spyOn(BaseSessionHandler.prototype, 'proceedWithSession').mockImplementation();
76+
const spy = jest.fn();
77+
mockSdk.on('pendingSession', spy);
78+
const pendingSession = createPendingSession(SessionTypes.softphone);
79+
pendingSession.autoAnswer = false;
80+
mockSdk._mediaHandling = MediaHandling.autoAnswerOnly;
81+
82+
await handler.handlePropose(pendingSession);
83+
84+
expect(spy).not.toHaveBeenCalled();
85+
expect(superSpyHandlePropose).not.toHaveBeenCalled();
86+
expect(superSpyProceed).not.toHaveBeenCalled();
87+
});
7688

89+
it('should not autoAnswer if mediaHandling is "autoAnswerOnly", autoAnswer is true, but disableAutoAnswer is configured', async () => {
90+
const superSpyHandlePropose = jest.spyOn(BaseSessionHandler.prototype, 'handlePropose');
91+
const superSpyProceed = jest.spyOn(BaseSessionHandler.prototype, 'proceedWithSession').mockImplementation();
7792
const spy = jest.fn();
7893
mockSdk.on('pendingSession', spy);
94+
mockSdk._config.disableAutoAnswer = true;
95+
const pendingSession = createPendingSession(SessionTypes.softphone);
96+
pendingSession.autoAnswer = true;
97+
mockSdk._mediaHandling = MediaHandling.autoAnswerOnly;
7998

80-
mockSdk._config.disableAutoAnswer = false;
99+
await handler.handlePropose(pendingSession);
81100

101+
expect(spy).toHaveBeenCalled();
102+
expect(superSpyHandlePropose).toHaveBeenCalled();
103+
expect(superSpyProceed).not.toHaveBeenCalled();
104+
});
105+
106+
it('should emit pending session and proceed immediately if mediaHandling is "autoAnswerOnly", autoAnswer is true, and disableAutoAnswer is not configured', async () => {
107+
const superSpyHandlePropose = jest.spyOn(BaseSessionHandler.prototype, 'handlePropose');
108+
const superSpyProceed = jest.spyOn(BaseSessionHandler.prototype, 'proceedWithSession').mockImplementation();
109+
const spy = jest.fn();
110+
mockSdk.on('pendingSession', spy);
111+
mockSdk._config.disableAutoAnswer = false;
82112
const pendingSession = createPendingSession(SessionTypes.softphone);
83113
pendingSession.autoAnswer = true;
114+
mockSdk._mediaHandling = MediaHandling.autoAnswerOnly;
115+
84116
await handler.handlePropose(pendingSession);
85117

86118
expect(spy).toHaveBeenCalled();
87119
expect(superSpyHandlePropose).toHaveBeenCalled();
88120
expect(superSpyProceed).toHaveBeenCalled();
89121
});
90122

91-
it('should ignore the propose if mediaHandling is set to "noMedia"', async () => {
123+
it('should emit pending session and proceed immediately if autoAnswer', async () => {
92124
const superSpyHandlePropose = jest.spyOn(BaseSessionHandler.prototype, 'handlePropose');
93125
const superSpyProceed = jest.spyOn(BaseSessionHandler.prototype, 'proceedWithSession').mockImplementation();
126+
94127
const spy = jest.fn();
95128
mockSdk.on('pendingSession', spy);
96-
const pendingSession = createPendingSession(SessionTypes.softphone);
97-
mockSdk._mediaHandling = MediaHandling.noMedia;
98129

130+
mockSdk._config.disableAutoAnswer = false;
131+
132+
const pendingSession = createPendingSession(SessionTypes.softphone);
133+
pendingSession.autoAnswer = true;
99134
await handler.handlePropose(pendingSession);
100135

101-
expect(spy).not.toHaveBeenCalled();
102-
expect(superSpyHandlePropose).not.toHaveBeenCalled();
103-
expect(superSpyProceed).not.toHaveBeenCalled();
136+
expect(spy).toHaveBeenCalled();
137+
expect(superSpyHandlePropose).toHaveBeenCalled();
138+
expect(superSpyProceed).toHaveBeenCalled();
104139
});
105140

106141
it('should not auto answer if pending session is not autoAnswer', async () => {

0 commit comments

Comments
 (0)