@wazo/sdk 0.37.23 → 0.37.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +95 -57
- package/dist/wazo-sdk.js +3 -3
- package/esm/__tests__/api-client.test.js +12 -7
- package/esm/__tests__/web-rtc-client.test.js +47 -20
- package/esm/api/agentd.js +1 -1
- package/esm/api/amid.js +1 -1
- package/esm/api/application.js +1 -1
- package/esm/api/auth.js +1 -1
- package/esm/api/call-logd.js +1 -1
- package/esm/api/calld.js +1 -1
- package/esm/api/chatd.js +1 -1
- package/esm/api/confd.js +11 -6
- package/esm/api/ctid-ng.js +1 -1
- package/esm/api/dird.js +2 -2
- package/esm/api/webhookd.js +1 -1
- package/esm/api-client.js +12 -7
- package/esm/checker/Checker.js +47 -20
- package/esm/checker/checks/aor.js +12 -7
- package/esm/checker/checks/api.js +12 -7
- package/esm/checker/checks/index.js +47 -20
- package/esm/checker/checks/wazo-websocket.js +1 -1
- package/esm/checker/checks/webrtc-transport.js +47 -20
- package/esm/checker/checks/webrtc.js +47 -20
- package/esm/domain/AdHocAPIConference.js +12 -7
- package/esm/domain/Contact.js +1 -1
- package/esm/domain/Features.js +12 -7
- package/esm/domain/Meeting.js +12 -5
- package/esm/domain/Phone/CTIPhone.js +12 -7
- package/esm/domain/Phone/WebRTCPhone.js +34 -6
- package/esm/domain/__tests__/Contact.test.js +1 -1
- package/esm/domain/__tests__/Meeting.test.js +144 -0
- package/esm/index.js +80 -25
- package/esm/lib/WazoSessionDescriptionHandler.js +9 -4
- package/esm/service/CallApi.js +12 -7
- package/esm/service/IssueReporter.js +1 -1
- package/esm/service/__tests__/IssueReporter.test.js +1 -1
- package/esm/service/getApiClient.js +12 -7
- package/esm/simple/Auth.js +80 -25
- package/esm/simple/Configuration.js +80 -25
- package/esm/simple/Directory.js +12 -7
- package/esm/simple/Phone.js +80 -25
- package/esm/simple/Websocket.js +1 -1
- package/esm/simple/index.js +80 -25
- package/esm/simple/room/LocalParticipant.js +80 -25
- package/esm/simple/room/Participant.js +80 -25
- package/esm/simple/room/RemoteParticipant.js +80 -25
- package/esm/simple/room/Room.js +80 -25
- package/esm/simple/room/SipRoom.js +80 -25
- package/esm/simple/room/Stream.js +80 -25
- package/esm/simple/room/__tests__/RemoteParticipant.test.js +80 -25
- package/esm/simple/utils.js +80 -25
- package/esm/types/WebSocketMessage.js +1 -1
- package/esm/utils/__tests__/api-requester.test.js +1 -1
- package/esm/utils/api-requester.js +1 -1
- package/esm/web-rtc-client.js +47 -20
- package/esm/websocket-client.js +1 -1
- package/lib/__tests__/api-client.test.js +12 -7
- package/lib/__tests__/web-rtc-client.test.js +47 -20
- package/lib/api/agentd.js +1 -1
- package/lib/api/amid.js +1 -1
- package/lib/api/application.js +1 -1
- package/lib/api/auth.js +1 -1
- package/lib/api/call-logd.js +1 -1
- package/lib/api/calld.js +1 -1
- package/lib/api/chatd.js +1 -1
- package/lib/api/confd.js +11 -6
- package/lib/api/ctid-ng.js +1 -1
- package/lib/api/dird.js +2 -2
- package/lib/api/webhookd.js +1 -1
- package/lib/api-client.js +12 -7
- package/lib/checker/Checker.js +47 -20
- package/lib/checker/checks/aor.js +12 -7
- package/lib/checker/checks/api.js +12 -7
- package/lib/checker/checks/index.js +47 -20
- package/lib/checker/checks/wazo-websocket.js +1 -1
- package/lib/checker/checks/webrtc-transport.js +47 -20
- package/lib/checker/checks/webrtc.js +47 -20
- package/lib/domain/AdHocAPIConference.js +12 -7
- package/lib/domain/Contact.js +1 -1
- package/lib/domain/Features.js +12 -7
- package/lib/domain/Meeting.js +16 -5
- package/lib/domain/Phone/CTIPhone.js +12 -7
- package/lib/domain/Phone/WebRTCPhone.js +34 -6
- package/lib/domain/__tests__/Contact.test.js +1 -1
- package/lib/domain/__tests__/Meeting.test.js +150 -0
- package/lib/index.js +80 -25
- package/lib/lib/WazoSessionDescriptionHandler.js +9 -4
- package/lib/service/CallApi.js +12 -7
- package/lib/service/IssueReporter.js +1 -1
- package/lib/service/__tests__/IssueReporter.test.js +1 -1
- package/lib/service/getApiClient.js +12 -7
- package/lib/simple/Auth.js +80 -25
- package/lib/simple/Configuration.js +80 -25
- package/lib/simple/Directory.js +12 -7
- package/lib/simple/Phone.js +80 -25
- package/lib/simple/Websocket.js +1 -1
- package/lib/simple/index.js +80 -25
- package/lib/simple/room/LocalParticipant.js +80 -25
- package/lib/simple/room/Participant.js +80 -25
- package/lib/simple/room/RemoteParticipant.js +80 -25
- package/lib/simple/room/Room.js +80 -25
- package/lib/simple/room/SipRoom.js +80 -25
- package/lib/simple/room/Stream.js +80 -25
- package/lib/simple/room/__tests__/RemoteParticipant.test.js +80 -25
- package/lib/simple/utils.js +80 -25
- package/lib/types/WebSocketMessage.js +2 -0
- package/lib/utils/__tests__/api-requester.test.js +1 -1
- package/lib/utils/api-requester.js +1 -1
- package/lib/web-rtc-client.js +47 -20
- package/lib/websocket-client.js +1 -1
- package/package.json +1 -1
- package/src/domain/Contact.js +1 -1
- package/src/domain/Meeting.js +12 -5
- package/src/domain/Phone/WebRTCPhone.js +33 -5
- package/src/domain/__tests__/Meeting.test.js +50 -0
- package/src/lib/WazoSessionDescriptionHandler.js +8 -3
- package/src/utils/api-requester.js +1 -1
- package/src/web-rtc-client.js +27 -10
|
@@ -17,7 +17,7 @@ IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
|
|
|
17
17
|
|
|
18
18
|
// Use a function here to be able to mock it in tests
|
|
19
19
|
const realFetch = () => {
|
|
20
|
-
if (typeof document !== 'undefined') {
|
|
20
|
+
if (typeof document !== 'undefined' && window.fetch) {
|
|
21
21
|
// Browser
|
|
22
22
|
return window.fetch;
|
|
23
23
|
}
|
|
@@ -452,7 +452,7 @@ const logger$1 = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
|
|
|
452
452
|
|
|
453
453
|
// Use a function here to be able to mock it in tests
|
|
454
454
|
const realFetch = () => {
|
|
455
|
-
if (typeof document !== 'undefined') {
|
|
455
|
+
if (typeof document !== 'undefined' && window.fetch) {
|
|
456
456
|
// Browser
|
|
457
457
|
return window.fetch;
|
|
458
458
|
}
|
|
@@ -1912,7 +1912,7 @@ class Contact {
|
|
|
1912
1912
|
this[key] = old[key] || this[key];
|
|
1913
1913
|
});
|
|
1914
1914
|
|
|
1915
|
-
if (old.lineState
|
|
1915
|
+
if (old.lineState) {
|
|
1916
1916
|
this.lineState = old.lineState;
|
|
1917
1917
|
}
|
|
1918
1918
|
|
|
@@ -2619,8 +2619,9 @@ class ExternalApp {
|
|
|
2619
2619
|
|
|
2620
2620
|
|
|
2621
2621
|
|
|
2622
|
-
|
|
2622
|
+
|
|
2623
2623
|
|
|
2624
|
+
|
|
2624
2625
|
|
|
2625
2626
|
|
|
2626
2627
|
class Meeting {
|
|
@@ -2631,9 +2632,10 @@ class Meeting {
|
|
|
2631
2632
|
|
|
2632
2633
|
|
|
2633
2634
|
|
|
2634
|
-
|
|
2635
|
+
|
|
2635
2636
|
|
|
2636
2637
|
|
|
2638
|
+
|
|
2637
2639
|
|
|
2638
2640
|
static parse(plain ) {
|
|
2639
2641
|
return new Meeting({
|
|
@@ -2643,8 +2645,9 @@ class Meeting {
|
|
|
2643
2645
|
ownerUuids: plain.owner_uuids,
|
|
2644
2646
|
port: plain.port,
|
|
2645
2647
|
uuid: plain.uuid,
|
|
2646
|
-
|
|
2648
|
+
extension: plain.exten,
|
|
2647
2649
|
persistent: plain.persistent,
|
|
2650
|
+
creationTime: moment__default['default'](plain.creation_time).toDate(),
|
|
2648
2651
|
});
|
|
2649
2652
|
}
|
|
2650
2653
|
|
|
@@ -2666,8 +2669,9 @@ class Meeting {
|
|
|
2666
2669
|
ownerUuids,
|
|
2667
2670
|
port,
|
|
2668
2671
|
uri,
|
|
2669
|
-
|
|
2672
|
+
extension,
|
|
2670
2673
|
persistent,
|
|
2674
|
+
creationTime,
|
|
2671
2675
|
} = {}) {
|
|
2672
2676
|
this.guestSipAuthorization = guestSipAuthorization;
|
|
2673
2677
|
this.uri = uri;
|
|
@@ -2675,8 +2679,9 @@ class Meeting {
|
|
|
2675
2679
|
this.ownerUuids = ownerUuids;
|
|
2676
2680
|
this.port = port;
|
|
2677
2681
|
this.uuid = uuid;
|
|
2678
|
-
this.
|
|
2682
|
+
this.extension = extension;
|
|
2679
2683
|
this.persistent = persistent;
|
|
2684
|
+
this.creationTime = creationTime;
|
|
2680
2685
|
|
|
2681
2686
|
// Useful to compare instead of instanceof with minified code
|
|
2682
2687
|
this.type = 'Meeting';
|
package/lib/simple/Auth.js
CHANGED
|
@@ -1248,7 +1248,7 @@ class Contact {
|
|
|
1248
1248
|
this[key] = old[key] || this[key];
|
|
1249
1249
|
});
|
|
1250
1250
|
|
|
1251
|
-
if (old.lineState
|
|
1251
|
+
if (old.lineState) {
|
|
1252
1252
|
this.lineState = old.lineState;
|
|
1253
1253
|
}
|
|
1254
1254
|
|
|
@@ -1963,7 +1963,7 @@ const logger$c = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
|
|
|
1963
1963
|
|
|
1964
1964
|
// Use a function here to be able to mock it in tests
|
|
1965
1965
|
const realFetch = () => {
|
|
1966
|
-
if (typeof document !== 'undefined') {
|
|
1966
|
+
if (typeof document !== 'undefined' && window.fetch) {
|
|
1967
1967
|
// Browser
|
|
1968
1968
|
return window.fetch;
|
|
1969
1969
|
}
|
|
@@ -2642,8 +2642,9 @@ class ExternalApp {
|
|
|
2642
2642
|
|
|
2643
2643
|
|
|
2644
2644
|
|
|
2645
|
-
|
|
2645
|
+
|
|
2646
2646
|
|
|
2647
|
+
|
|
2647
2648
|
|
|
2648
2649
|
|
|
2649
2650
|
class Meeting {
|
|
@@ -2654,9 +2655,10 @@ class Meeting {
|
|
|
2654
2655
|
|
|
2655
2656
|
|
|
2656
2657
|
|
|
2657
|
-
|
|
2658
|
+
|
|
2658
2659
|
|
|
2659
2660
|
|
|
2661
|
+
|
|
2660
2662
|
|
|
2661
2663
|
static parse(plain ) {
|
|
2662
2664
|
return new Meeting({
|
|
@@ -2666,8 +2668,9 @@ class Meeting {
|
|
|
2666
2668
|
ownerUuids: plain.owner_uuids,
|
|
2667
2669
|
port: plain.port,
|
|
2668
2670
|
uuid: plain.uuid,
|
|
2669
|
-
|
|
2671
|
+
extension: plain.exten,
|
|
2670
2672
|
persistent: plain.persistent,
|
|
2673
|
+
creationTime: moment__default['default'](plain.creation_time).toDate(),
|
|
2671
2674
|
});
|
|
2672
2675
|
}
|
|
2673
2676
|
|
|
@@ -2689,8 +2692,9 @@ class Meeting {
|
|
|
2689
2692
|
ownerUuids,
|
|
2690
2693
|
port,
|
|
2691
2694
|
uri,
|
|
2692
|
-
|
|
2695
|
+
extension,
|
|
2693
2696
|
persistent,
|
|
2697
|
+
creationTime,
|
|
2694
2698
|
} = {}) {
|
|
2695
2699
|
this.guestSipAuthorization = guestSipAuthorization;
|
|
2696
2700
|
this.uri = uri;
|
|
@@ -2698,8 +2702,9 @@ class Meeting {
|
|
|
2698
2702
|
this.ownerUuids = ownerUuids;
|
|
2699
2703
|
this.port = port;
|
|
2700
2704
|
this.uuid = uuid;
|
|
2701
|
-
this.
|
|
2705
|
+
this.extension = extension;
|
|
2702
2706
|
this.persistent = persistent;
|
|
2707
|
+
this.creationTime = creationTime;
|
|
2703
2708
|
|
|
2704
2709
|
// Useful to compare instead of instanceof with minified code
|
|
2705
2710
|
this.type = 'Meeting';
|
|
@@ -5805,11 +5810,16 @@ class WazoSessionDescriptionHandler extends sessionDescriptionHandler.SessionDes
|
|
|
5805
5810
|
|
|
5806
5811
|
// $FlowFixMe
|
|
5807
5812
|
setRemoteSessionDescription(sessionDescription ) {
|
|
5808
|
-
|
|
5813
|
+
try {
|
|
5814
|
+
const result = super.setRemoteSessionDescription(sessionDescription);
|
|
5809
5815
|
|
|
5810
|
-
|
|
5816
|
+
this.eventEmitter.emit('setDescription', sessionDescription);
|
|
5811
5817
|
|
|
5812
|
-
|
|
5818
|
+
return result;
|
|
5819
|
+
} catch (error) {
|
|
5820
|
+
this.logger.error('SessionDescriptionHandler.setRemoteSessionDescription error', error);
|
|
5821
|
+
throw error;
|
|
5822
|
+
}
|
|
5813
5823
|
}
|
|
5814
5824
|
|
|
5815
5825
|
// Overridden to avoid to fix ice-candidates missing in the SDP in react-native and chrome canary.
|
|
@@ -6714,7 +6724,7 @@ class WebRTCClient extends Emitter {
|
|
|
6714
6724
|
}
|
|
6715
6725
|
|
|
6716
6726
|
call(number , enableVideo , audioOnly = false, conference = false) {
|
|
6717
|
-
logger$9.info('sdk webrtc creating call', { number, enableVideo, audioOnly });
|
|
6727
|
+
logger$9.info('sdk webrtc creating call', { number, enableVideo, audioOnly, conference });
|
|
6718
6728
|
const inviterOptions = {
|
|
6719
6729
|
sessionDescriptionHandlerOptionsReInvite: {
|
|
6720
6730
|
conference,
|
|
@@ -7790,6 +7800,7 @@ class WebRTCClient extends Emitter {
|
|
|
7790
7800
|
const screenSharing = constraints && 'screen' in constraints ? constraints.screen : false;
|
|
7791
7801
|
const isDesktop = constraints && 'desktop' in constraints ? constraints.desktop : false;
|
|
7792
7802
|
const withAudio = constraints && 'audio' in constraints ? constraints.audio : true;
|
|
7803
|
+
const mandatoryVideo = constraints && typeof constraints.video === 'object' ? constraints.video.mandatory : {};
|
|
7793
7804
|
|
|
7794
7805
|
logger$9.info('Retrieving media a configuration', { enableVideo, screenSharing, isDesktop, withAudio, constraints });
|
|
7795
7806
|
|
|
@@ -7797,7 +7808,8 @@ class WebRTCClient extends Emitter {
|
|
|
7797
7808
|
constraints: {
|
|
7798
7809
|
// Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
|
|
7799
7810
|
audio: screenSharing ? !isDesktop : (withAudio ? this._getAudioConstraints() : false),
|
|
7800
|
-
video: screenSharing ? (isDesktop
|
|
7811
|
+
video: screenSharing ? (isDesktop
|
|
7812
|
+
? ({ mandatory: { chromeMediaSource: 'desktop', ...(mandatoryVideo || {}) } }) : { cursor: 'always' })
|
|
7801
7813
|
: this._getVideoConstraints(enableVideo),
|
|
7802
7814
|
screen: screenSharing,
|
|
7803
7815
|
desktop: isDesktop,
|
|
@@ -8132,12 +8144,14 @@ class WebRTCClient extends Emitter {
|
|
|
8132
8144
|
|
|
8133
8145
|
session.sessionDescriptionHandler.remoteMediaStream.onaddtrack = onTrack;
|
|
8134
8146
|
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8139
|
-
|
|
8140
|
-
|
|
8147
|
+
if (pc) {
|
|
8148
|
+
pc.oniceconnectionstatechange = () => {
|
|
8149
|
+
logger$9.info('on ice connection state changed', { state: pc.iceConnectionState });
|
|
8150
|
+
if (pc.iceConnectionState === 'disconnected') {
|
|
8151
|
+
this.eventEmitter.emit(ON_DISCONNECTED$1);
|
|
8152
|
+
}
|
|
8153
|
+
};
|
|
8154
|
+
}
|
|
8141
8155
|
|
|
8142
8156
|
if (withEvent) {
|
|
8143
8157
|
this.eventEmitter.emit(ACCEPTED, session, sessionDialog);
|
|
@@ -8347,13 +8361,17 @@ class WebRTCClient extends Emitter {
|
|
|
8347
8361
|
const nbStats = this.sessionNetworkStats[sessionId].length;
|
|
8348
8362
|
const lastNetworkStats = this.sessionNetworkStats[sessionId][nbStats - 1];
|
|
8349
8363
|
const lastAudioSent = lastNetworkStats ? lastNetworkStats.audioBytesSent : 0;
|
|
8364
|
+
const lastAudioContentSent = lastNetworkStats ? lastNetworkStats.audioContentSent : 0;
|
|
8350
8365
|
const lastVideoSent = lastNetworkStats ? lastNetworkStats.videoBytesSent : 0;
|
|
8351
8366
|
const lastAudioReceived = lastNetworkStats ? lastNetworkStats.audioBytesReceived : 0;
|
|
8367
|
+
const lastAudioContentReceived = lastNetworkStats ? lastNetworkStats.audioContentReceived : 0;
|
|
8352
8368
|
const lastVideoReceived = lastNetworkStats ? lastNetworkStats.videoBytesReceived : 0;
|
|
8353
8369
|
const lastTransportSent = lastNetworkStats ? lastNetworkStats.transportSent : 0;
|
|
8354
8370
|
const lastTransportReceived = lastNetworkStats ? lastNetworkStats.transportReceived : 0;
|
|
8355
|
-
let audioBytesSent = 0;
|
|
8356
|
-
let audioBytesReceived = 0;
|
|
8371
|
+
let audioBytesSent = 0; // content + header
|
|
8372
|
+
let audioBytesReceived = 0; // content + header
|
|
8373
|
+
let audioContentSent = 0; // useful to detect blank call
|
|
8374
|
+
let audioContentReceived = 0;
|
|
8357
8375
|
let videoBytesSent = 0;
|
|
8358
8376
|
let videoBytesReceived = 0;
|
|
8359
8377
|
let transportSent = 0;
|
|
@@ -8363,6 +8381,7 @@ class WebRTCClient extends Emitter {
|
|
|
8363
8381
|
stats.forEach(report => {
|
|
8364
8382
|
if (report.type === 'outbound-rtp' && report.kind === 'audio') {
|
|
8365
8383
|
audioBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
|
|
8384
|
+
audioContentSent += Number(report.bytesSent);
|
|
8366
8385
|
}
|
|
8367
8386
|
if (report.type === 'remote-outbound-rtp' && report.kind === 'audio') {
|
|
8368
8387
|
audioBytesSent += Number(report.bytesSent);
|
|
@@ -8374,6 +8393,7 @@ class WebRTCClient extends Emitter {
|
|
|
8374
8393
|
packetsLost += Number(report.packetsLost);
|
|
8375
8394
|
networkStats.packetsReceived = Number(report.packetsReceived);
|
|
8376
8395
|
audioBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
8396
|
+
audioContentReceived += Number(report.bytesReceived);
|
|
8377
8397
|
}
|
|
8378
8398
|
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
|
8379
8399
|
videoBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
@@ -8402,10 +8422,17 @@ class WebRTCClient extends Emitter {
|
|
|
8402
8422
|
});
|
|
8403
8423
|
|
|
8404
8424
|
networkStats.packetsLost = packetsLost;
|
|
8425
|
+
|
|
8405
8426
|
networkStats.totalAudioBytesSent = audioBytesSent;
|
|
8406
8427
|
networkStats.audioBytesSent = audioBytesSent - lastAudioSent;
|
|
8407
8428
|
networkStats.totalAudioBytesReceived = audioBytesReceived - lastAudioReceived;
|
|
8408
8429
|
networkStats.audioBytesReceived = audioBytesReceived;
|
|
8430
|
+
|
|
8431
|
+
networkStats.totalAudioContentSent = audioContentSent;
|
|
8432
|
+
networkStats.audioContentSent = audioContentSent - lastAudioContentSent;
|
|
8433
|
+
networkStats.totalAudioBytesReceived = audioContentReceived - lastAudioContentReceived;
|
|
8434
|
+
networkStats.audioContentReceived = audioContentReceived;
|
|
8435
|
+
|
|
8409
8436
|
networkStats.totalVideoBytesSent = videoBytesSent;
|
|
8410
8437
|
networkStats.videoBytesSent = videoBytesSent - lastVideoSent;
|
|
8411
8438
|
networkStats.totalVideoBytesReceived = videoBytesReceived;
|
|
@@ -8617,6 +8644,7 @@ class WebRTCPhone extends Emitter {
|
|
|
8617
8644
|
constraints,
|
|
8618
8645
|
audioOnly,
|
|
8619
8646
|
conference,
|
|
8647
|
+
iceRestart,
|
|
8620
8648
|
});
|
|
8621
8649
|
|
|
8622
8650
|
if (!sipSession) {
|
|
@@ -8720,6 +8748,11 @@ class WebRTCPhone extends Emitter {
|
|
|
8720
8748
|
|
|
8721
8749
|
this.client.downgradeToAudio(sipSession);
|
|
8722
8750
|
|
|
8751
|
+
if (callSession) {
|
|
8752
|
+
callSession.cameraEnabled = false;
|
|
8753
|
+
this._updateCallSession(callSession);
|
|
8754
|
+
}
|
|
8755
|
+
|
|
8723
8756
|
if (withMessage) {
|
|
8724
8757
|
this._sendReinviteMessage(callSession, false);
|
|
8725
8758
|
}
|
|
@@ -8747,6 +8780,11 @@ class WebRTCPhone extends Emitter {
|
|
|
8747
8780
|
|
|
8748
8781
|
const newStream = await this.client.upgradeToVideo(sipSession, constraints, isConference);
|
|
8749
8782
|
|
|
8783
|
+
if (callSession) {
|
|
8784
|
+
callSession.cameraEnabled = true;
|
|
8785
|
+
this._updateCallSession(callSession);
|
|
8786
|
+
}
|
|
8787
|
+
|
|
8750
8788
|
// If no stream is returned, it means we have to reinvite
|
|
8751
8789
|
if (!newStream) {
|
|
8752
8790
|
return true;
|
|
@@ -9016,7 +9054,7 @@ class WebRTCPhone extends Emitter {
|
|
|
9016
9054
|
}
|
|
9017
9055
|
|
|
9018
9056
|
_onCallAccepted(sipSession , cameraEnabled ) {
|
|
9019
|
-
logger$8.info('WebRTC phone -
|
|
9057
|
+
logger$8.info('WebRTC phone - on call accepted', { sipId: sipSession.id, cameraEnabled });
|
|
9020
9058
|
|
|
9021
9059
|
const callSession = this._createAcceptedCallSession(sipSession, cameraEnabled);
|
|
9022
9060
|
this.currentSipSession = sipSession;
|
|
@@ -9085,6 +9123,7 @@ class WebRTCPhone extends Emitter {
|
|
|
9085
9123
|
_onCallTerminated(sipSession ) {
|
|
9086
9124
|
logger$8.info('WebRTC phone - on call terminated', { sipId: sipSession.id });
|
|
9087
9125
|
|
|
9126
|
+
const sipSessionId = this.getSipSessionId(sipSession);
|
|
9088
9127
|
const callSession = this._createCallSession(sipSession);
|
|
9089
9128
|
const isCurrentSession = this.isCurrentCallSipSession(callSession);
|
|
9090
9129
|
const isCurrentIncomingCall = callSession.is(this.getIncomingCallSession());
|
|
@@ -9102,7 +9141,6 @@ class WebRTCPhone extends Emitter {
|
|
|
9102
9141
|
}, 5);
|
|
9103
9142
|
}
|
|
9104
9143
|
|
|
9105
|
-
const sipSessionId = this.getSipSessionId(sipSession);
|
|
9106
9144
|
if (sipSessionId) {
|
|
9107
9145
|
this.removeIncomingSessions(sipSessionId);
|
|
9108
9146
|
}
|
|
@@ -9528,7 +9566,7 @@ class WebRTCPhone extends Emitter {
|
|
|
9528
9566
|
// @TODO: line is not used here
|
|
9529
9567
|
async makeCall(number , line , cameraEnabled ,
|
|
9530
9568
|
audioOnly = false, conference = false) {
|
|
9531
|
-
logger$8.info('make WebRTC call', { number, lineId: line ? line.id : null, cameraEnabled });
|
|
9569
|
+
logger$8.info('make WebRTC call', { number, lineId: line ? line.id : null, cameraEnabled, audioOnly, conference });
|
|
9532
9570
|
if (!number) {
|
|
9533
9571
|
return Promise.resolve(null);
|
|
9534
9572
|
}
|
|
@@ -9747,8 +9785,12 @@ class WebRTCPhone extends Emitter {
|
|
|
9747
9785
|
}
|
|
9748
9786
|
|
|
9749
9787
|
const sessionId = this.incomingSessions[0];
|
|
9788
|
+
const sipSession = this.client.getSipSession(sessionId);
|
|
9789
|
+
if (!sipSession) {
|
|
9790
|
+
return null;
|
|
9791
|
+
}
|
|
9750
9792
|
|
|
9751
|
-
return this._createCallSession(
|
|
9793
|
+
return this._createCallSession(sipSession);
|
|
9752
9794
|
}
|
|
9753
9795
|
|
|
9754
9796
|
sendMessage(sipSession = null, body , contentType = 'text/plain') {
|
|
@@ -9889,9 +9931,13 @@ class WebRTCPhone extends Emitter {
|
|
|
9889
9931
|
if (this.shouldSendReinvite && this.currentSipSession
|
|
9890
9932
|
&& this.currentSipSession.state === sessionState.SessionState.Established) {
|
|
9891
9933
|
this.shouldSendReinvite = false;
|
|
9934
|
+
const pc = this.currentSipSession.sessionDescriptionHandler.peerConnection;
|
|
9935
|
+
const isConference = pc ? pc.sfu : false;
|
|
9936
|
+
const hasVideo = this.currentCallSession && this.currentCallSession.cameraEnabled;
|
|
9937
|
+
|
|
9892
9938
|
try {
|
|
9893
9939
|
// Send reinvite with iceRestart
|
|
9894
|
-
this.sendReinvite(this.currentCallSession, null,
|
|
9940
|
+
this.sendReinvite(this.currentCallSession, null, isConference, !hasVideo, true);
|
|
9895
9941
|
} catch (e) {
|
|
9896
9942
|
logger$8.error('WebRTC reinvite after register, error', { message: e.message, stack: e.stack });
|
|
9897
9943
|
}
|
|
@@ -9975,6 +10021,15 @@ class WebRTCPhone extends Emitter {
|
|
|
9975
10021
|
return this.client.getSipSessionId(sipSession);
|
|
9976
10022
|
}
|
|
9977
10023
|
|
|
10024
|
+
_updateCallSession(callSession ) {
|
|
10025
|
+
const sipSession = this.findSipSession(callSession);
|
|
10026
|
+
const sipSessionId = sipSession ? this.getSipSessionId(sipSession) : null;
|
|
10027
|
+
|
|
10028
|
+
if (sipSessionId) {
|
|
10029
|
+
this.callSessions[sipSessionId] = callSession;
|
|
10030
|
+
}
|
|
10031
|
+
}
|
|
10032
|
+
|
|
9978
10033
|
_onMessage(message ) {
|
|
9979
10034
|
if (!message || message.method !== 'MESSAGE') {
|
|
9980
10035
|
return;
|
|
@@ -1246,7 +1246,7 @@ class Contact {
|
|
|
1246
1246
|
this[key] = old[key] || this[key];
|
|
1247
1247
|
});
|
|
1248
1248
|
|
|
1249
|
-
if (old.lineState
|
|
1249
|
+
if (old.lineState) {
|
|
1250
1250
|
this.lineState = old.lineState;
|
|
1251
1251
|
}
|
|
1252
1252
|
|
|
@@ -1961,7 +1961,7 @@ const logger$c = IssueReporter$1 ? IssueReporter$1.loggerFor('api') : console;
|
|
|
1961
1961
|
|
|
1962
1962
|
// Use a function here to be able to mock it in tests
|
|
1963
1963
|
const realFetch = () => {
|
|
1964
|
-
if (typeof document !== 'undefined') {
|
|
1964
|
+
if (typeof document !== 'undefined' && window.fetch) {
|
|
1965
1965
|
// Browser
|
|
1966
1966
|
return window.fetch;
|
|
1967
1967
|
}
|
|
@@ -2640,8 +2640,9 @@ class ExternalApp {
|
|
|
2640
2640
|
|
|
2641
2641
|
|
|
2642
2642
|
|
|
2643
|
-
|
|
2643
|
+
|
|
2644
2644
|
|
|
2645
|
+
|
|
2645
2646
|
|
|
2646
2647
|
|
|
2647
2648
|
class Meeting {
|
|
@@ -2652,9 +2653,10 @@ class Meeting {
|
|
|
2652
2653
|
|
|
2653
2654
|
|
|
2654
2655
|
|
|
2655
|
-
|
|
2656
|
+
|
|
2656
2657
|
|
|
2657
2658
|
|
|
2659
|
+
|
|
2658
2660
|
|
|
2659
2661
|
static parse(plain ) {
|
|
2660
2662
|
return new Meeting({
|
|
@@ -2664,8 +2666,9 @@ class Meeting {
|
|
|
2664
2666
|
ownerUuids: plain.owner_uuids,
|
|
2665
2667
|
port: plain.port,
|
|
2666
2668
|
uuid: plain.uuid,
|
|
2667
|
-
|
|
2669
|
+
extension: plain.exten,
|
|
2668
2670
|
persistent: plain.persistent,
|
|
2671
|
+
creationTime: moment__default['default'](plain.creation_time).toDate(),
|
|
2669
2672
|
});
|
|
2670
2673
|
}
|
|
2671
2674
|
|
|
@@ -2687,8 +2690,9 @@ class Meeting {
|
|
|
2687
2690
|
ownerUuids,
|
|
2688
2691
|
port,
|
|
2689
2692
|
uri,
|
|
2690
|
-
|
|
2693
|
+
extension,
|
|
2691
2694
|
persistent,
|
|
2695
|
+
creationTime,
|
|
2692
2696
|
} = {}) {
|
|
2693
2697
|
this.guestSipAuthorization = guestSipAuthorization;
|
|
2694
2698
|
this.uri = uri;
|
|
@@ -2696,8 +2700,9 @@ class Meeting {
|
|
|
2696
2700
|
this.ownerUuids = ownerUuids;
|
|
2697
2701
|
this.port = port;
|
|
2698
2702
|
this.uuid = uuid;
|
|
2699
|
-
this.
|
|
2703
|
+
this.extension = extension;
|
|
2700
2704
|
this.persistent = persistent;
|
|
2705
|
+
this.creationTime = creationTime;
|
|
2701
2706
|
|
|
2702
2707
|
// Useful to compare instead of instanceof with minified code
|
|
2703
2708
|
this.type = 'Meeting';
|
|
@@ -5803,11 +5808,16 @@ class WazoSessionDescriptionHandler extends sessionDescriptionHandler.SessionDes
|
|
|
5803
5808
|
|
|
5804
5809
|
// $FlowFixMe
|
|
5805
5810
|
setRemoteSessionDescription(sessionDescription ) {
|
|
5806
|
-
|
|
5811
|
+
try {
|
|
5812
|
+
const result = super.setRemoteSessionDescription(sessionDescription);
|
|
5807
5813
|
|
|
5808
|
-
|
|
5814
|
+
this.eventEmitter.emit('setDescription', sessionDescription);
|
|
5809
5815
|
|
|
5810
|
-
|
|
5816
|
+
return result;
|
|
5817
|
+
} catch (error) {
|
|
5818
|
+
this.logger.error('SessionDescriptionHandler.setRemoteSessionDescription error', error);
|
|
5819
|
+
throw error;
|
|
5820
|
+
}
|
|
5811
5821
|
}
|
|
5812
5822
|
|
|
5813
5823
|
// Overridden to avoid to fix ice-candidates missing in the SDP in react-native and chrome canary.
|
|
@@ -6712,7 +6722,7 @@ class WebRTCClient extends Emitter {
|
|
|
6712
6722
|
}
|
|
6713
6723
|
|
|
6714
6724
|
call(number , enableVideo , audioOnly = false, conference = false) {
|
|
6715
|
-
logger$9.info('sdk webrtc creating call', { number, enableVideo, audioOnly });
|
|
6725
|
+
logger$9.info('sdk webrtc creating call', { number, enableVideo, audioOnly, conference });
|
|
6716
6726
|
const inviterOptions = {
|
|
6717
6727
|
sessionDescriptionHandlerOptionsReInvite: {
|
|
6718
6728
|
conference,
|
|
@@ -7788,6 +7798,7 @@ class WebRTCClient extends Emitter {
|
|
|
7788
7798
|
const screenSharing = constraints && 'screen' in constraints ? constraints.screen : false;
|
|
7789
7799
|
const isDesktop = constraints && 'desktop' in constraints ? constraints.desktop : false;
|
|
7790
7800
|
const withAudio = constraints && 'audio' in constraints ? constraints.audio : true;
|
|
7801
|
+
const mandatoryVideo = constraints && typeof constraints.video === 'object' ? constraints.video.mandatory : {};
|
|
7791
7802
|
|
|
7792
7803
|
logger$9.info('Retrieving media a configuration', { enableVideo, screenSharing, isDesktop, withAudio, constraints });
|
|
7793
7804
|
|
|
@@ -7795,7 +7806,8 @@ class WebRTCClient extends Emitter {
|
|
|
7795
7806
|
constraints: {
|
|
7796
7807
|
// Exact constraint are not supported with `getDisplayMedia` and we must have a video=false in desktop screenshare
|
|
7797
7808
|
audio: screenSharing ? !isDesktop : (withAudio ? this._getAudioConstraints() : false),
|
|
7798
|
-
video: screenSharing ? (isDesktop
|
|
7809
|
+
video: screenSharing ? (isDesktop
|
|
7810
|
+
? ({ mandatory: { chromeMediaSource: 'desktop', ...(mandatoryVideo || {}) } }) : { cursor: 'always' })
|
|
7799
7811
|
: this._getVideoConstraints(enableVideo),
|
|
7800
7812
|
screen: screenSharing,
|
|
7801
7813
|
desktop: isDesktop,
|
|
@@ -8130,12 +8142,14 @@ class WebRTCClient extends Emitter {
|
|
|
8130
8142
|
|
|
8131
8143
|
session.sessionDescriptionHandler.remoteMediaStream.onaddtrack = onTrack;
|
|
8132
8144
|
|
|
8133
|
-
|
|
8134
|
-
|
|
8135
|
-
|
|
8136
|
-
|
|
8137
|
-
|
|
8138
|
-
|
|
8145
|
+
if (pc) {
|
|
8146
|
+
pc.oniceconnectionstatechange = () => {
|
|
8147
|
+
logger$9.info('on ice connection state changed', { state: pc.iceConnectionState });
|
|
8148
|
+
if (pc.iceConnectionState === 'disconnected') {
|
|
8149
|
+
this.eventEmitter.emit(ON_DISCONNECTED$1);
|
|
8150
|
+
}
|
|
8151
|
+
};
|
|
8152
|
+
}
|
|
8139
8153
|
|
|
8140
8154
|
if (withEvent) {
|
|
8141
8155
|
this.eventEmitter.emit(ACCEPTED, session, sessionDialog);
|
|
@@ -8345,13 +8359,17 @@ class WebRTCClient extends Emitter {
|
|
|
8345
8359
|
const nbStats = this.sessionNetworkStats[sessionId].length;
|
|
8346
8360
|
const lastNetworkStats = this.sessionNetworkStats[sessionId][nbStats - 1];
|
|
8347
8361
|
const lastAudioSent = lastNetworkStats ? lastNetworkStats.audioBytesSent : 0;
|
|
8362
|
+
const lastAudioContentSent = lastNetworkStats ? lastNetworkStats.audioContentSent : 0;
|
|
8348
8363
|
const lastVideoSent = lastNetworkStats ? lastNetworkStats.videoBytesSent : 0;
|
|
8349
8364
|
const lastAudioReceived = lastNetworkStats ? lastNetworkStats.audioBytesReceived : 0;
|
|
8365
|
+
const lastAudioContentReceived = lastNetworkStats ? lastNetworkStats.audioContentReceived : 0;
|
|
8350
8366
|
const lastVideoReceived = lastNetworkStats ? lastNetworkStats.videoBytesReceived : 0;
|
|
8351
8367
|
const lastTransportSent = lastNetworkStats ? lastNetworkStats.transportSent : 0;
|
|
8352
8368
|
const lastTransportReceived = lastNetworkStats ? lastNetworkStats.transportReceived : 0;
|
|
8353
|
-
let audioBytesSent = 0;
|
|
8354
|
-
let audioBytesReceived = 0;
|
|
8369
|
+
let audioBytesSent = 0; // content + header
|
|
8370
|
+
let audioBytesReceived = 0; // content + header
|
|
8371
|
+
let audioContentSent = 0; // useful to detect blank call
|
|
8372
|
+
let audioContentReceived = 0;
|
|
8355
8373
|
let videoBytesSent = 0;
|
|
8356
8374
|
let videoBytesReceived = 0;
|
|
8357
8375
|
let transportSent = 0;
|
|
@@ -8361,6 +8379,7 @@ class WebRTCClient extends Emitter {
|
|
|
8361
8379
|
stats.forEach(report => {
|
|
8362
8380
|
if (report.type === 'outbound-rtp' && report.kind === 'audio') {
|
|
8363
8381
|
audioBytesSent += Number(report.bytesSent) + Number(report.headerBytesSent);
|
|
8382
|
+
audioContentSent += Number(report.bytesSent);
|
|
8364
8383
|
}
|
|
8365
8384
|
if (report.type === 'remote-outbound-rtp' && report.kind === 'audio') {
|
|
8366
8385
|
audioBytesSent += Number(report.bytesSent);
|
|
@@ -8372,6 +8391,7 @@ class WebRTCClient extends Emitter {
|
|
|
8372
8391
|
packetsLost += Number(report.packetsLost);
|
|
8373
8392
|
networkStats.packetsReceived = Number(report.packetsReceived);
|
|
8374
8393
|
audioBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
8394
|
+
audioContentReceived += Number(report.bytesReceived);
|
|
8375
8395
|
}
|
|
8376
8396
|
if (report.type === 'inbound-rtp' && report.kind === 'video') {
|
|
8377
8397
|
videoBytesReceived += Number(report.bytesReceived) + Number(report.headerBytesReceived);
|
|
@@ -8400,10 +8420,17 @@ class WebRTCClient extends Emitter {
|
|
|
8400
8420
|
});
|
|
8401
8421
|
|
|
8402
8422
|
networkStats.packetsLost = packetsLost;
|
|
8423
|
+
|
|
8403
8424
|
networkStats.totalAudioBytesSent = audioBytesSent;
|
|
8404
8425
|
networkStats.audioBytesSent = audioBytesSent - lastAudioSent;
|
|
8405
8426
|
networkStats.totalAudioBytesReceived = audioBytesReceived - lastAudioReceived;
|
|
8406
8427
|
networkStats.audioBytesReceived = audioBytesReceived;
|
|
8428
|
+
|
|
8429
|
+
networkStats.totalAudioContentSent = audioContentSent;
|
|
8430
|
+
networkStats.audioContentSent = audioContentSent - lastAudioContentSent;
|
|
8431
|
+
networkStats.totalAudioBytesReceived = audioContentReceived - lastAudioContentReceived;
|
|
8432
|
+
networkStats.audioContentReceived = audioContentReceived;
|
|
8433
|
+
|
|
8407
8434
|
networkStats.totalVideoBytesSent = videoBytesSent;
|
|
8408
8435
|
networkStats.videoBytesSent = videoBytesSent - lastVideoSent;
|
|
8409
8436
|
networkStats.totalVideoBytesReceived = videoBytesReceived;
|
|
@@ -8615,6 +8642,7 @@ class WebRTCPhone extends Emitter {
|
|
|
8615
8642
|
constraints,
|
|
8616
8643
|
audioOnly,
|
|
8617
8644
|
conference,
|
|
8645
|
+
iceRestart,
|
|
8618
8646
|
});
|
|
8619
8647
|
|
|
8620
8648
|
if (!sipSession) {
|
|
@@ -8718,6 +8746,11 @@ class WebRTCPhone extends Emitter {
|
|
|
8718
8746
|
|
|
8719
8747
|
this.client.downgradeToAudio(sipSession);
|
|
8720
8748
|
|
|
8749
|
+
if (callSession) {
|
|
8750
|
+
callSession.cameraEnabled = false;
|
|
8751
|
+
this._updateCallSession(callSession);
|
|
8752
|
+
}
|
|
8753
|
+
|
|
8721
8754
|
if (withMessage) {
|
|
8722
8755
|
this._sendReinviteMessage(callSession, false);
|
|
8723
8756
|
}
|
|
@@ -8745,6 +8778,11 @@ class WebRTCPhone extends Emitter {
|
|
|
8745
8778
|
|
|
8746
8779
|
const newStream = await this.client.upgradeToVideo(sipSession, constraints, isConference);
|
|
8747
8780
|
|
|
8781
|
+
if (callSession) {
|
|
8782
|
+
callSession.cameraEnabled = true;
|
|
8783
|
+
this._updateCallSession(callSession);
|
|
8784
|
+
}
|
|
8785
|
+
|
|
8748
8786
|
// If no stream is returned, it means we have to reinvite
|
|
8749
8787
|
if (!newStream) {
|
|
8750
8788
|
return true;
|
|
@@ -9014,7 +9052,7 @@ class WebRTCPhone extends Emitter {
|
|
|
9014
9052
|
}
|
|
9015
9053
|
|
|
9016
9054
|
_onCallAccepted(sipSession , cameraEnabled ) {
|
|
9017
|
-
logger$8.info('WebRTC phone -
|
|
9055
|
+
logger$8.info('WebRTC phone - on call accepted', { sipId: sipSession.id, cameraEnabled });
|
|
9018
9056
|
|
|
9019
9057
|
const callSession = this._createAcceptedCallSession(sipSession, cameraEnabled);
|
|
9020
9058
|
this.currentSipSession = sipSession;
|
|
@@ -9083,6 +9121,7 @@ class WebRTCPhone extends Emitter {
|
|
|
9083
9121
|
_onCallTerminated(sipSession ) {
|
|
9084
9122
|
logger$8.info('WebRTC phone - on call terminated', { sipId: sipSession.id });
|
|
9085
9123
|
|
|
9124
|
+
const sipSessionId = this.getSipSessionId(sipSession);
|
|
9086
9125
|
const callSession = this._createCallSession(sipSession);
|
|
9087
9126
|
const isCurrentSession = this.isCurrentCallSipSession(callSession);
|
|
9088
9127
|
const isCurrentIncomingCall = callSession.is(this.getIncomingCallSession());
|
|
@@ -9100,7 +9139,6 @@ class WebRTCPhone extends Emitter {
|
|
|
9100
9139
|
}, 5);
|
|
9101
9140
|
}
|
|
9102
9141
|
|
|
9103
|
-
const sipSessionId = this.getSipSessionId(sipSession);
|
|
9104
9142
|
if (sipSessionId) {
|
|
9105
9143
|
this.removeIncomingSessions(sipSessionId);
|
|
9106
9144
|
}
|
|
@@ -9526,7 +9564,7 @@ class WebRTCPhone extends Emitter {
|
|
|
9526
9564
|
// @TODO: line is not used here
|
|
9527
9565
|
async makeCall(number , line , cameraEnabled ,
|
|
9528
9566
|
audioOnly = false, conference = false) {
|
|
9529
|
-
logger$8.info('make WebRTC call', { number, lineId: line ? line.id : null, cameraEnabled });
|
|
9567
|
+
logger$8.info('make WebRTC call', { number, lineId: line ? line.id : null, cameraEnabled, audioOnly, conference });
|
|
9530
9568
|
if (!number) {
|
|
9531
9569
|
return Promise.resolve(null);
|
|
9532
9570
|
}
|
|
@@ -9745,8 +9783,12 @@ class WebRTCPhone extends Emitter {
|
|
|
9745
9783
|
}
|
|
9746
9784
|
|
|
9747
9785
|
const sessionId = this.incomingSessions[0];
|
|
9786
|
+
const sipSession = this.client.getSipSession(sessionId);
|
|
9787
|
+
if (!sipSession) {
|
|
9788
|
+
return null;
|
|
9789
|
+
}
|
|
9748
9790
|
|
|
9749
|
-
return this._createCallSession(
|
|
9791
|
+
return this._createCallSession(sipSession);
|
|
9750
9792
|
}
|
|
9751
9793
|
|
|
9752
9794
|
sendMessage(sipSession = null, body , contentType = 'text/plain') {
|
|
@@ -9887,9 +9929,13 @@ class WebRTCPhone extends Emitter {
|
|
|
9887
9929
|
if (this.shouldSendReinvite && this.currentSipSession
|
|
9888
9930
|
&& this.currentSipSession.state === sessionState.SessionState.Established) {
|
|
9889
9931
|
this.shouldSendReinvite = false;
|
|
9932
|
+
const pc = this.currentSipSession.sessionDescriptionHandler.peerConnection;
|
|
9933
|
+
const isConference = pc ? pc.sfu : false;
|
|
9934
|
+
const hasVideo = this.currentCallSession && this.currentCallSession.cameraEnabled;
|
|
9935
|
+
|
|
9890
9936
|
try {
|
|
9891
9937
|
// Send reinvite with iceRestart
|
|
9892
|
-
this.sendReinvite(this.currentCallSession, null,
|
|
9938
|
+
this.sendReinvite(this.currentCallSession, null, isConference, !hasVideo, true);
|
|
9893
9939
|
} catch (e) {
|
|
9894
9940
|
logger$8.error('WebRTC reinvite after register, error', { message: e.message, stack: e.stack });
|
|
9895
9941
|
}
|
|
@@ -9973,6 +10019,15 @@ class WebRTCPhone extends Emitter {
|
|
|
9973
10019
|
return this.client.getSipSessionId(sipSession);
|
|
9974
10020
|
}
|
|
9975
10021
|
|
|
10022
|
+
_updateCallSession(callSession ) {
|
|
10023
|
+
const sipSession = this.findSipSession(callSession);
|
|
10024
|
+
const sipSessionId = sipSession ? this.getSipSessionId(sipSession) : null;
|
|
10025
|
+
|
|
10026
|
+
if (sipSessionId) {
|
|
10027
|
+
this.callSessions[sipSessionId] = callSession;
|
|
10028
|
+
}
|
|
10029
|
+
}
|
|
10030
|
+
|
|
9976
10031
|
_onMessage(message ) {
|
|
9977
10032
|
if (!message || message.method !== 'MESSAGE') {
|
|
9978
10033
|
return;
|