@ohos-ports/genesys-cloud-streaming-client 20.1.0-beta.1
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 +47 -0
- package/dist/cjs/alerting-leader.d.ts +29 -0
- package/dist/cjs/alerting-leader.js +163 -0
- package/dist/cjs/client.d.ts +84 -0
- package/dist/cjs/client.js +680 -0
- package/dist/cjs/connection-manager.d.ts +18 -0
- package/dist/cjs/connection-manager.js +120 -0
- package/dist/cjs/connection-transfer.d.ts +7 -0
- package/dist/cjs/connection-transfer.js +20 -0
- package/dist/cjs/http-client.d.ts +18 -0
- package/dist/cjs/http-client.js +230 -0
- package/dist/cjs/index.d.ts +10 -0
- package/dist/cjs/index.js +31 -0
- package/dist/cjs/messenger.d.ts +37 -0
- package/dist/cjs/messenger.js +54 -0
- package/dist/cjs/notifications.d.ts +94 -0
- package/dist/cjs/notifications.js +495 -0
- package/dist/cjs/ping.d.ts +21 -0
- package/dist/cjs/ping.js +60 -0
- package/dist/cjs/polyfills.d.ts +0 -0
- package/dist/cjs/polyfills.js +6 -0
- package/dist/cjs/server-monitor.d.ts +16 -0
- package/dist/cjs/server-monitor.js +44 -0
- package/dist/cjs/stanza-definitions/webrtc-signaling.d.ts +29 -0
- package/dist/cjs/stanza-definitions/webrtc-signaling.js +113 -0
- package/dist/cjs/stanza-definitions/xep0051.d.ts +19 -0
- package/dist/cjs/stanza-definitions/xep0051.js +28 -0
- package/dist/cjs/stats-formatter.d.ts +6 -0
- package/dist/cjs/stats-formatter.js +63 -0
- package/dist/cjs/types/genesys-cloud-media-session.d.ts +64 -0
- package/dist/cjs/types/genesys-cloud-media-session.js +353 -0
- package/dist/cjs/types/interfaces.d.ts +310 -0
- package/dist/cjs/types/interfaces.js +22 -0
- package/dist/cjs/types/libs.d.ts +32 -0
- package/dist/cjs/types/libs.js +1 -0
- package/dist/cjs/types/media-session.d.ts +76 -0
- package/dist/cjs/types/media-session.js +2 -0
- package/dist/cjs/types/named-agent.d.ts +13 -0
- package/dist/cjs/types/named-agent.js +2 -0
- package/dist/cjs/types/offline-error.d.ts +2 -0
- package/dist/cjs/types/offline-error.js +5 -0
- package/dist/cjs/types/sasl-error.d.ts +8 -0
- package/dist/cjs/types/sasl-error.js +12 -0
- package/dist/cjs/types/stanza-media-session.d.ts +56 -0
- package/dist/cjs/types/stanza-media-session.js +255 -0
- package/dist/cjs/types/streaming-client-extension.d.ts +14 -0
- package/dist/cjs/types/streaming-client-extension.js +2 -0
- package/dist/cjs/types/timeout-error.d.ts +3 -0
- package/dist/cjs/types/timeout-error.js +9 -0
- package/dist/cjs/types/user-cancelled-error.d.ts +2 -0
- package/dist/cjs/types/user-cancelled-error.js +5 -0
- package/dist/cjs/utils.d.ts +34 -0
- package/dist/cjs/utils.js +202 -0
- package/dist/cjs/webrtc.d.ts +126 -0
- package/dist/cjs/webrtc.js +891 -0
- package/dist/deploy-info.json +21 -0
- package/dist/es/alerting-leader.d.ts +29 -0
- package/dist/es/alerting-leader.js +177 -0
- package/dist/es/client.d.ts +84 -0
- package/dist/es/client.js +699 -0
- package/dist/es/connection-manager.d.ts +18 -0
- package/dist/es/connection-manager.js +124 -0
- package/dist/es/connection-transfer.d.ts +7 -0
- package/dist/es/connection-transfer.js +16 -0
- package/dist/es/http-client.d.ts +18 -0
- package/dist/es/http-client.js +194 -0
- package/dist/es/index.bundle.js +39629 -0
- package/dist/es/index.d.ts +10 -0
- package/dist/es/index.js +10 -0
- package/dist/es/messenger.d.ts +37 -0
- package/dist/es/messenger.js +62 -0
- package/dist/es/notifications.d.ts +94 -0
- package/dist/es/notifications.js +507 -0
- package/dist/es/ping.d.ts +21 -0
- package/dist/es/ping.js +68 -0
- package/dist/es/polyfills.d.ts +0 -0
- package/dist/es/polyfills.js +6 -0
- package/dist/es/server-monitor.d.ts +16 -0
- package/dist/es/server-monitor.js +41 -0
- package/dist/es/stanza-definitions/webrtc-signaling.d.ts +29 -0
- package/dist/es/stanza-definitions/webrtc-signaling.js +110 -0
- package/dist/es/stanza-definitions/xep0051.d.ts +19 -0
- package/dist/es/stanza-definitions/xep0051.js +25 -0
- package/dist/es/stats-formatter.d.ts +6 -0
- package/dist/es/stats-formatter.js +54 -0
- package/dist/es/types/genesys-cloud-media-session.d.ts +64 -0
- package/dist/es/types/genesys-cloud-media-session.js +379 -0
- package/dist/es/types/interfaces.d.ts +310 -0
- package/dist/es/types/interfaces.js +19 -0
- package/dist/es/types/libs.d.ts +32 -0
- package/dist/es/types/libs.js +1 -0
- package/dist/es/types/media-session.d.ts +76 -0
- package/dist/es/types/media-session.js +1 -0
- package/dist/es/types/named-agent.d.ts +13 -0
- package/dist/es/types/named-agent.js +1 -0
- package/dist/es/types/offline-error.d.ts +2 -0
- package/dist/es/types/offline-error.js +2 -0
- package/dist/es/types/sasl-error.d.ts +8 -0
- package/dist/es/types/sasl-error.js +9 -0
- package/dist/es/types/stanza-media-session.d.ts +56 -0
- package/dist/es/types/stanza-media-session.js +264 -0
- package/dist/es/types/streaming-client-extension.d.ts +14 -0
- package/dist/es/types/streaming-client-extension.js +1 -0
- package/dist/es/types/timeout-error.d.ts +3 -0
- package/dist/es/types/timeout-error.js +5 -0
- package/dist/es/types/user-cancelled-error.d.ts +2 -0
- package/dist/es/types/user-cancelled-error.js +2 -0
- package/dist/es/utils.d.ts +34 -0
- package/dist/es/utils.js +192 -0
- package/dist/es/webrtc.d.ts +126 -0
- package/dist/es/webrtc.js +917 -0
- package/dist/manifest.json +19 -0
- package/dist/npm/CHANGELOG.md +637 -0
- package/dist/npm/README.md +47 -0
- package/dist/npm/alerting-leader.d.ts +29 -0
- package/dist/npm/alerting-leader.js +163 -0
- package/dist/npm/client.d.ts +84 -0
- package/dist/npm/client.js +680 -0
- package/dist/npm/connection-manager.d.ts +18 -0
- package/dist/npm/connection-manager.js +120 -0
- package/dist/npm/connection-transfer.d.ts +7 -0
- package/dist/npm/connection-transfer.js +20 -0
- package/dist/npm/http-client.d.ts +18 -0
- package/dist/npm/http-client.js +230 -0
- package/dist/npm/index.d.ts +10 -0
- package/dist/npm/index.js +31 -0
- package/dist/npm/messenger.d.ts +37 -0
- package/dist/npm/messenger.js +54 -0
- package/dist/npm/module.js +10 -0
- package/dist/npm/notifications.d.ts +94 -0
- package/dist/npm/notifications.js +495 -0
- package/dist/npm/ping.d.ts +21 -0
- package/dist/npm/ping.js +60 -0
- package/dist/npm/polyfills.d.ts +0 -0
- package/dist/npm/polyfills.js +6 -0
- package/dist/npm/server-monitor.d.ts +16 -0
- package/dist/npm/server-monitor.js +44 -0
- package/dist/npm/stanza-definitions/webrtc-signaling.d.ts +29 -0
- package/dist/npm/stanza-definitions/webrtc-signaling.js +113 -0
- package/dist/npm/stanza-definitions/xep0051.d.ts +19 -0
- package/dist/npm/stanza-definitions/xep0051.js +28 -0
- package/dist/npm/stats-formatter.d.ts +6 -0
- package/dist/npm/stats-formatter.js +63 -0
- package/dist/npm/types/genesys-cloud-media-session.d.ts +64 -0
- package/dist/npm/types/genesys-cloud-media-session.js +353 -0
- package/dist/npm/types/interfaces.d.ts +310 -0
- package/dist/npm/types/interfaces.js +22 -0
- package/dist/npm/types/libs.d.ts +32 -0
- package/dist/npm/types/libs.js +1 -0
- package/dist/npm/types/media-session.d.ts +76 -0
- package/dist/npm/types/media-session.js +2 -0
- package/dist/npm/types/named-agent.d.ts +13 -0
- package/dist/npm/types/named-agent.js +2 -0
- package/dist/npm/types/offline-error.d.ts +2 -0
- package/dist/npm/types/offline-error.js +5 -0
- package/dist/npm/types/sasl-error.d.ts +8 -0
- package/dist/npm/types/sasl-error.js +12 -0
- package/dist/npm/types/stanza-media-session.d.ts +56 -0
- package/dist/npm/types/stanza-media-session.js +255 -0
- package/dist/npm/types/streaming-client-extension.d.ts +14 -0
- package/dist/npm/types/streaming-client-extension.js +2 -0
- package/dist/npm/types/timeout-error.d.ts +3 -0
- package/dist/npm/types/timeout-error.js +9 -0
- package/dist/npm/types/user-cancelled-error.d.ts +2 -0
- package/dist/npm/types/user-cancelled-error.js +5 -0
- package/dist/npm/utils.d.ts +34 -0
- package/dist/npm/utils.js +207 -0
- package/dist/npm/webrtc.d.ts +126 -0
- package/dist/npm/webrtc.js +893 -0
- package/dist/streaming-client.browser.js +2 -0
- package/dist/streaming-client.browser.js.LICENSE.txt +12 -0
- package/dist/v20/streaming-client.browser.js +2 -0
- package/dist/v20/streaming-client.browser.js.LICENSE.txt +12 -0
- package/dist/v20.1.0/streaming-client.browser.js +2 -0
- package/dist/v20.1.0/streaming-client.browser.js.LICENSE.txt +12 -0
- package/package.json +132 -0
|
@@ -0,0 +1,917 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { EventEmitter } from 'events';
|
|
11
|
+
import { toBare } from 'stanza/JID';
|
|
12
|
+
import { LRUCache } from 'lru-cache';
|
|
13
|
+
import { JingleAction } from 'stanza/Constants';
|
|
14
|
+
import throttle from 'lodash.throttle';
|
|
15
|
+
import { isFirefox } from 'browserama';
|
|
16
|
+
import { definitions } from './stanza-definitions/webrtc-signaling';
|
|
17
|
+
import { isAcdJid, isScreenRecordingJid, isLiveScreenMonitoringJid, isSoftphoneJid, isVideoJid, calculatePayloadSize, retryPromise, iceIsDifferent } from './utils';
|
|
18
|
+
import { Client } from './client';
|
|
19
|
+
import { deepFlatten, formatStatsEvent } from './stats-formatter';
|
|
20
|
+
import { SessionTypes } from './types/interfaces';
|
|
21
|
+
import { StanzaMediaSession } from './types/stanza-media-session';
|
|
22
|
+
import { GenesysCloudMediaSession } from './types/genesys-cloud-media-session';
|
|
23
|
+
import { HttpClient } from './http-client';
|
|
24
|
+
const events = {
|
|
25
|
+
REQUEST_WEBRTC_DUMP: 'requestWebrtcDump',
|
|
26
|
+
/* jingle messaging */
|
|
27
|
+
REQUEST_INCOMING_RTCSESSION: 'requestIncomingRtcSession',
|
|
28
|
+
CANCEL_INCOMING_RTCSESSION: 'cancelIncomingRtcSession',
|
|
29
|
+
HANDLED_INCOMING_RTCSESSION: 'handledIncomingRtcSession',
|
|
30
|
+
OUTGOING_RTCSESSION_PROCEED: 'outgoingRtcSessionProceed',
|
|
31
|
+
OUTGOING_RTCSESSION_REJECTED: 'outgoingRtcSessionRejected',
|
|
32
|
+
// /* jingle */
|
|
33
|
+
// RTC_ICESERVERS: 'rtcIceServers', // ice servers have been discovered
|
|
34
|
+
INCOMING_RTCSESSION: 'incomingRtcSession',
|
|
35
|
+
OUTGOING_RTCSESSION: 'outgoingRtcSession',
|
|
36
|
+
RTCSESSION_ERROR: 'rtcSessionError' // jingle error occurred
|
|
37
|
+
// TRACE_RTCSESSION: 'traceRtcSession', // trace messages for logging, etc
|
|
38
|
+
// UPGRADE_MEDIA_ERROR: 'upgradeMediaError', // error occurred joining conference
|
|
39
|
+
// /* other */
|
|
40
|
+
// UPDATE_MEDIA_PRESENCE: 'updateMediaPresence',
|
|
41
|
+
// LASTN_CHANGE: 'lastNChange'
|
|
42
|
+
};
|
|
43
|
+
const desiredMaxStatsSize = 15000;
|
|
44
|
+
const MAX_DISCO_RETRIES = 2;
|
|
45
|
+
const ICE_SERVER_TIMEOUT = 15000; // 15 seconds
|
|
46
|
+
const ICE_SERVER_REFRESH_PERIOD = 1000 * 60 * 60 * 6; // 6 hours
|
|
47
|
+
export class WebrtcExtension extends EventEmitter {
|
|
48
|
+
constructor(client, clientOptions) {
|
|
49
|
+
super();
|
|
50
|
+
// sessionId maps to boolean where `true` means the sessionId is ignored
|
|
51
|
+
this.ignoredSessions = new LRUCache({ max: 10, ttl: 10 * 60 * 60 * 6 });
|
|
52
|
+
// sessionId maps to a list of sdp ice candidates
|
|
53
|
+
// hold onto early candidates for 1 minute
|
|
54
|
+
this.earlyIceCandidates = new LRUCache({ max: 10, ttl: 1000 * 60, ttlAutopurge: true });
|
|
55
|
+
this.pendingSessions = {};
|
|
56
|
+
this.statsArr = [];
|
|
57
|
+
this.throttleSendStatsInterval = 25000;
|
|
58
|
+
this.currentMaxStatSize = desiredMaxStatsSize;
|
|
59
|
+
this.statsSizeDecreaseAmount = 3000;
|
|
60
|
+
this.statBuffer = 0;
|
|
61
|
+
this.discoRetries = 0;
|
|
62
|
+
this.iceServers = [];
|
|
63
|
+
this.stanzaSessions = [];
|
|
64
|
+
this.webrtcSessions = [];
|
|
65
|
+
// Store a maximum of 5 previous non-duplicate reinvites.
|
|
66
|
+
// These will automatically be purged after three minutes.
|
|
67
|
+
this.reinviteCache = new LRUCache({
|
|
68
|
+
max: 5,
|
|
69
|
+
ttl: 1000 * 60 * 3
|
|
70
|
+
});
|
|
71
|
+
this.sessionsMap = {};
|
|
72
|
+
this.client = client;
|
|
73
|
+
this.config = {
|
|
74
|
+
allowIPv6: clientOptions.allowIPv6 === true,
|
|
75
|
+
optOutOfWebrtcStatsTelemetry: clientOptions.optOutOfWebrtcStatsTelemetry
|
|
76
|
+
};
|
|
77
|
+
this.logger = client.logger;
|
|
78
|
+
this.addEventListeners();
|
|
79
|
+
this.throttledSendStats = throttle(this.sendStats.bind(this), this.throttleSendStatsInterval, { leading: false, trailing: true });
|
|
80
|
+
this.client.on('jingle:outgoing', (session) => {
|
|
81
|
+
this.logger.info('Emitting jingle:outgoing media-session (session-init)', session.sid);
|
|
82
|
+
return this.emit(events.OUTGOING_RTCSESSION, session);
|
|
83
|
+
});
|
|
84
|
+
this.client.on('jingle:incoming', (session) => {
|
|
85
|
+
this.logger.info('Emitting jingle:incoming media-session (session-init)', session.sid);
|
|
86
|
+
return this.emit(events.INCOMING_RTCSESSION, session);
|
|
87
|
+
});
|
|
88
|
+
this.client.on('jingle:created', (session) => {
|
|
89
|
+
// if this is not a JingleSession, this is a generic BaseSession from jingle, aka dummy session.
|
|
90
|
+
// in this case, we can just kill this session because we will be using
|
|
91
|
+
if (!(session instanceof StanzaMediaSession)) {
|
|
92
|
+
session.end('cancel', true);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
window.addEventListener('offline', () => this.onOnlineStatusChange(false));
|
|
96
|
+
window.addEventListener('online', () => this.onOnlineStatusChange(true));
|
|
97
|
+
}
|
|
98
|
+
get jid() {
|
|
99
|
+
var _a;
|
|
100
|
+
return (_a = this.stanzaInstance) === null || _a === void 0 ? void 0 : _a.jid;
|
|
101
|
+
}
|
|
102
|
+
onOnlineStatusChange(online) {
|
|
103
|
+
this.addStatToQueue({
|
|
104
|
+
actionName: 'WebrtcStats',
|
|
105
|
+
details: {
|
|
106
|
+
_eventType: 'onlineStatus',
|
|
107
|
+
_eventTimestamp: new Date().getTime(),
|
|
108
|
+
online
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
handleStanzaInstanceChange(stanzaInstance) {
|
|
113
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
114
|
+
this.stanzaInstance = stanzaInstance;
|
|
115
|
+
// We create a new Stanza instance every time we connect. If we still have active StanzaMediaSessions,
|
|
116
|
+
// the new instance doesn't know about them and won't properly process actions for those sessions
|
|
117
|
+
// (e.g. it will reply to a `session-terminate`, but the session itself won't receive that action,
|
|
118
|
+
// because Stanza doesn't know about it). Since we track those sessions ourselves, we can tell
|
|
119
|
+
// the new Stanza instance about them so those actions will be processed properly.
|
|
120
|
+
const sessionsMap = this.stanzaSessions.reduce((currentMap, session) => {
|
|
121
|
+
session.parent = stanzaInstance.jingle;
|
|
122
|
+
return Object.assign(Object.assign({}, currentMap), { [session.id]: session });
|
|
123
|
+
}, {});
|
|
124
|
+
this.stanzaInstance.jingle.sessions = sessionsMap;
|
|
125
|
+
if (this.refreshIceServersTimer) {
|
|
126
|
+
clearInterval(this.refreshIceServersTimer);
|
|
127
|
+
this.refreshIceServersTimer = null;
|
|
128
|
+
}
|
|
129
|
+
stanzaInstance.on('iq:set:genesysWebrtc', this.handleGenesysWebrtcStanza.bind(this));
|
|
130
|
+
this.refreshIceServersTimer = setInterval(this.refreshIceServers.bind(this), ICE_SERVER_REFRESH_PERIOD);
|
|
131
|
+
this.client.emit('sessionManagerChange', stanzaInstance);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
configureNewStanzaInstance(stanzaInstance) {
|
|
135
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
136
|
+
Object.assign(stanzaInstance.jingle.config.peerConnectionConfig, {
|
|
137
|
+
sdpSemantics: 'unified-plan'
|
|
138
|
+
});
|
|
139
|
+
yield this.configureStanzaIceServers(stanzaInstance);
|
|
140
|
+
stanzaInstance.stanzas.define(definitions);
|
|
141
|
+
stanzaInstance.jingle.prepareSession = this.prepareSession.bind(this);
|
|
142
|
+
stanzaInstance.jingle.on('log', (level, message, data) => {
|
|
143
|
+
this.logger[level](message, data);
|
|
144
|
+
});
|
|
145
|
+
const eventsToProxy = [
|
|
146
|
+
'iceConnectionType',
|
|
147
|
+
'peerTrackAdded',
|
|
148
|
+
'peerTrackRemoved',
|
|
149
|
+
'mute',
|
|
150
|
+
'unmute',
|
|
151
|
+
'sessionState',
|
|
152
|
+
'connectionState',
|
|
153
|
+
'terminated',
|
|
154
|
+
'stats',
|
|
155
|
+
'endOfCandidates'
|
|
156
|
+
];
|
|
157
|
+
for (const e of eventsToProxy) {
|
|
158
|
+
stanzaInstance.jingle.on(e, (session, ...data) => {
|
|
159
|
+
if (!(session instanceof StanzaMediaSession)) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
session.emit(e, ...data);
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
configureStanzaIceServers(stanzaInstance) {
|
|
168
|
+
/* clear out stanzas default use of google's stun server */
|
|
169
|
+
stanzaInstance.jingle.config.iceServers = [];
|
|
170
|
+
/**
|
|
171
|
+
* NOTE: calling this here should not interfere with the `webrtc.ts` extension
|
|
172
|
+
* refreshingIceServers since that is async and this constructor is sync
|
|
173
|
+
*/
|
|
174
|
+
this.setIceServers([], stanzaInstance);
|
|
175
|
+
return this._refreshIceServers(stanzaInstance);
|
|
176
|
+
}
|
|
177
|
+
handleGenesysOffer(iq) {
|
|
178
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
179
|
+
const message = iq.genesysWebrtc;
|
|
180
|
+
const params = message.params;
|
|
181
|
+
// XMPP-SIP-Gateway will repeat reinvite offers until the client has responded.
|
|
182
|
+
// We don't want to process the duplicate reinvites and instead will ignore them.
|
|
183
|
+
if (params.reinvite && this.reinviteCache.get(message.id)) {
|
|
184
|
+
this.logger.info('Ignoring duplicate reinvite offer', message.id);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
// If the reinvite isn't a duplicate, we should cache it so we can check against new offers.
|
|
188
|
+
if (params.reinvite) {
|
|
189
|
+
this.reinviteCache.set(message.id, true);
|
|
190
|
+
}
|
|
191
|
+
const ignoreHostCandidatesForForceTurnFF = this.getIceTransportPolicy() === 'relay' && isFirefox;
|
|
192
|
+
const commonParams = {
|
|
193
|
+
id: params.sessionId,
|
|
194
|
+
logger: this.logger,
|
|
195
|
+
peerID: iq.from,
|
|
196
|
+
fromJid: iq.from,
|
|
197
|
+
sessionType: this.getSessionTypeByJid(iq.from),
|
|
198
|
+
conversationId: params.conversationId,
|
|
199
|
+
ignoreHostCandidatesFromRemote: ignoreHostCandidatesForForceTurnFF,
|
|
200
|
+
optOutOfWebrtcStatsTelemetry: !!this.config.optOutOfWebrtcStatsTelemetry,
|
|
201
|
+
allowIPv6: this.config.allowIPv6,
|
|
202
|
+
iceServers: this.iceServers,
|
|
203
|
+
reinvite: !!params.reinvite,
|
|
204
|
+
iceTransportPolicy: this.getIceTransportPolicy()
|
|
205
|
+
};
|
|
206
|
+
let mediaSessionParams;
|
|
207
|
+
// we should only do something if the pending session tells us to
|
|
208
|
+
const pendingSession = this.pendingSessions[params.sessionId];
|
|
209
|
+
if (pendingSession) {
|
|
210
|
+
mediaSessionParams = Object.assign(Object.assign({}, commonParams), { meetingId: pendingSession.meetingId, fromUserId: pendingSession.fromUserId, originalRoomJid: pendingSession.originalRoomJid, privAnswerMode: pendingSession.privAnswerMode });
|
|
211
|
+
delete this.pendingSessions[pendingSession.sessionId];
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
mediaSessionParams = commonParams;
|
|
215
|
+
}
|
|
216
|
+
// if we receive an offer for an existing session and the ice info has not changed, this is
|
|
217
|
+
// a renogotiate. If the ice has changed, it's a re-invite and we need to create a new session.
|
|
218
|
+
const existingSession = this.webrtcSessions.find(s => s.id === mediaSessionParams.id);
|
|
219
|
+
this.logger.info('offer received', { existingSession: !!existingSession, mediaSessionParams });
|
|
220
|
+
if (existingSession) {
|
|
221
|
+
existingSession.conversationId = params.conversationId;
|
|
222
|
+
// renego
|
|
223
|
+
if (!iceIsDifferent(existingSession.peerConnection.remoteDescription.sdp, params.sdp)) {
|
|
224
|
+
return this.handleGenesysRenegotiate(existingSession, params.sdp);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// reinvite/new session handled the same way here
|
|
228
|
+
const session = new GenesysCloudMediaSession(this, mediaSessionParams);
|
|
229
|
+
yield session.setRemoteDescription(params.sdp);
|
|
230
|
+
this.proxyStatsForSession(session);
|
|
231
|
+
session.on('sendIq', (iq) => { var _a; return (_a = this.stanzaInstance) === null || _a === void 0 ? void 0 : _a.sendIQ(iq); });
|
|
232
|
+
session.on('terminated', () => {
|
|
233
|
+
delete this.sessionsMap[session.id];
|
|
234
|
+
this.webrtcSessions = this.webrtcSessions.filter(s => s.id !== session.id);
|
|
235
|
+
});
|
|
236
|
+
this.webrtcSessions.push(session);
|
|
237
|
+
this.logger.info('emitting sdp media-session (offer');
|
|
238
|
+
this.applyEarlyIceCandidates(session);
|
|
239
|
+
return this.emit(events.INCOMING_RTCSESSION, session);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
applyEarlyIceCandidates(session) {
|
|
243
|
+
const earlyCandidates = this.earlyIceCandidates.get(session.id);
|
|
244
|
+
if (earlyCandidates) {
|
|
245
|
+
this.earlyIceCandidates.delete(session.id);
|
|
246
|
+
for (const candidate of earlyCandidates) {
|
|
247
|
+
void session.addRemoteIceCandidate(candidate);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
handleGenesysRenegotiate(existingSession, newSdp) {
|
|
252
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
253
|
+
yield existingSession.peerConnection.setRemoteDescription({ sdp: newSdp, type: 'offer' });
|
|
254
|
+
yield existingSession.accept();
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
handleGenesysIceCandidate(iq) {
|
|
258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
const message = iq.genesysWebrtc;
|
|
260
|
+
const params = message.params;
|
|
261
|
+
const session = this.getSessionById(params.sessionId, true);
|
|
262
|
+
if (session) {
|
|
263
|
+
yield session.addRemoteIceCandidate(params.sdp);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
const earlyCandidates = this.earlyIceCandidates.get(params.sessionId);
|
|
267
|
+
if (earlyCandidates) {
|
|
268
|
+
this.earlyIceCandidates.set(params.sessionId, [...earlyCandidates, params.sdp]);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
this.earlyIceCandidates.set(params.sessionId, [params.sdp]);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
handleGenesysTerminate(iq) {
|
|
277
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
278
|
+
const message = iq.genesysWebrtc;
|
|
279
|
+
const params = message.params;
|
|
280
|
+
const session = this.getSessionById(params.sessionId, true);
|
|
281
|
+
if (!session) {
|
|
282
|
+
this.logger.warn('Received terminate for session that does not exist', { sessionId: params.sessionId });
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
session.onSessionTerminate(params.reason);
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
getSessionById(id, nullIfNotFound = false) {
|
|
289
|
+
const session = this.getAllSessions().find(session => session.id === id);
|
|
290
|
+
if (!session && !nullIfNotFound) {
|
|
291
|
+
const error = new Error('Failed to find session by id');
|
|
292
|
+
this.logger.error(error, { sessionId: id });
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
return session;
|
|
296
|
+
}
|
|
297
|
+
sendIq(iq) {
|
|
298
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
299
|
+
if (!this.stanzaInstance) {
|
|
300
|
+
throw new Error('Failed to send iq because there was no stanza instance');
|
|
301
|
+
}
|
|
302
|
+
return this.stanzaInstance.sendIQ(iq);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
handleMessage(msg) {
|
|
306
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
307
|
+
if (msg.propose) {
|
|
308
|
+
yield this.handlePropose(msg);
|
|
309
|
+
}
|
|
310
|
+
else if (msg.retract) {
|
|
311
|
+
this.handleRetract(msg.retract.sessionId);
|
|
312
|
+
}
|
|
313
|
+
else if (msg.accept) {
|
|
314
|
+
this.handledIncomingRtcSession(msg.accept.sessionId, msg);
|
|
315
|
+
}
|
|
316
|
+
else if (msg.reject) {
|
|
317
|
+
this.handledIncomingRtcSession(msg.reject.sessionId, msg);
|
|
318
|
+
}
|
|
319
|
+
});
|
|
320
|
+
}
|
|
321
|
+
handleGenesysWebrtcStanza(iq) {
|
|
322
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
323
|
+
const webrtcInfo = iq.genesysWebrtc;
|
|
324
|
+
if (webrtcInfo.method === 'offer') {
|
|
325
|
+
return this.handleGenesysOffer(iq);
|
|
326
|
+
}
|
|
327
|
+
else if (webrtcInfo.method === 'iceCandidate') {
|
|
328
|
+
return this.handleGenesysIceCandidate(iq);
|
|
329
|
+
}
|
|
330
|
+
else if (webrtcInfo.method === 'terminate') {
|
|
331
|
+
return this.handleGenesysTerminate(iq);
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
prepareSession(options) {
|
|
336
|
+
if (options.sid && this.sessionsMap[options.sid]) {
|
|
337
|
+
this.logger.debug('skipping creation of jingle webrtc session due to sdpOverXmpp');
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
const pendingSession = this.pendingSessions[options.sid];
|
|
341
|
+
if (pendingSession) {
|
|
342
|
+
delete this.pendingSessions[pendingSession.sessionId];
|
|
343
|
+
}
|
|
344
|
+
const ignoreHostCandidatesForForceTurnFF = this.getIceTransportPolicy() === 'relay' && isFirefox;
|
|
345
|
+
const gcSessionOpts = {
|
|
346
|
+
options,
|
|
347
|
+
logger: this.logger,
|
|
348
|
+
id: options.sid,
|
|
349
|
+
fromJid: options.peerID,
|
|
350
|
+
peerID: options.peerID,
|
|
351
|
+
optOutOfWebrtcStatsTelemetry: !!this.config.optOutOfWebrtcStatsTelemetry,
|
|
352
|
+
conversationId: pendingSession === null || pendingSession === void 0 ? void 0 : pendingSession.conversationId,
|
|
353
|
+
fromUserId: pendingSession === null || pendingSession === void 0 ? void 0 : pendingSession.fromJid,
|
|
354
|
+
originalRoomJid: pendingSession === null || pendingSession === void 0 ? void 0 : pendingSession.originalRoomJid,
|
|
355
|
+
sessionType: pendingSession === null || pendingSession === void 0 ? void 0 : pendingSession.sessionType,
|
|
356
|
+
allowIPv6: this.config.allowIPv6,
|
|
357
|
+
ignoreHostCandidatesFromRemote: ignoreHostCandidatesForForceTurnFF,
|
|
358
|
+
meetingId: pendingSession === null || pendingSession === void 0 ? void 0 : pendingSession.meetingId
|
|
359
|
+
};
|
|
360
|
+
const session = new StanzaMediaSession(gcSessionOpts);
|
|
361
|
+
this.proxyStatsForSession(session);
|
|
362
|
+
this.stanzaSessions.push(session);
|
|
363
|
+
session.on('terminated', () => {
|
|
364
|
+
delete this.sessionsMap[session.id];
|
|
365
|
+
this.stanzaSessions = this.stanzaSessions.filter(s => s.id !== session.id);
|
|
366
|
+
});
|
|
367
|
+
return session;
|
|
368
|
+
}
|
|
369
|
+
// This should probably go into the webrtc sdk, but for now I'm putting here so it's in a central location.
|
|
370
|
+
// This should be moved when the sdk is the primary consumer
|
|
371
|
+
proxyStatsForSession(session) {
|
|
372
|
+
session.on('stats', (statsEvent) => {
|
|
373
|
+
const statsCopy = JSON.parse(JSON.stringify(statsEvent));
|
|
374
|
+
const extraDetails = {
|
|
375
|
+
conversationId: session.conversationId,
|
|
376
|
+
sessionId: session.id,
|
|
377
|
+
sessionType: session.sessionType
|
|
378
|
+
};
|
|
379
|
+
// format the event to what the api expects
|
|
380
|
+
const event = formatStatsEvent(statsCopy, extraDetails);
|
|
381
|
+
this.addStatToQueue(event);
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
// this fn checks to see if the new stat fits inside the buffer. If not, send the queue;
|
|
385
|
+
addStatToQueue(stat) {
|
|
386
|
+
if (this.config.optOutOfWebrtcStatsTelemetry) {
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (!stat.details._appId) {
|
|
390
|
+
stat.details._appId = this.logger.clientId;
|
|
391
|
+
stat.details._appName = 'streamingclient';
|
|
392
|
+
stat.details._appVersion = Client.version;
|
|
393
|
+
}
|
|
394
|
+
stat.details._originAppId = this.client.config.appId;
|
|
395
|
+
// nr only accepts single level objects so we must flatten everything just in case
|
|
396
|
+
const flattenedDetails = deepFlatten(stat.details);
|
|
397
|
+
// new relic doesn't accept booleans so we convert them to strings
|
|
398
|
+
Object.keys(flattenedDetails).forEach((key) => {
|
|
399
|
+
const val = flattenedDetails[key];
|
|
400
|
+
if (typeof val === 'boolean') {
|
|
401
|
+
flattenedDetails[key] = `${val}`;
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
const formattedStat = Object.assign(Object.assign({}, stat), { details: flattenedDetails });
|
|
405
|
+
const currentEventSize = calculatePayloadSize(formattedStat);
|
|
406
|
+
// Check if the size of the current event plus the size of the previous stats exceeds max size.
|
|
407
|
+
const exceedsMaxStatSize = this.statBuffer + currentEventSize > this.currentMaxStatSize;
|
|
408
|
+
this.statsArr.push(formattedStat);
|
|
409
|
+
this.statBuffer += currentEventSize;
|
|
410
|
+
// If it exceeds max size, don't append just send current payload.
|
|
411
|
+
if (exceedsMaxStatSize) {
|
|
412
|
+
this.sendStatsImmediately();
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
this.throttledSendStats();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
getLogDetailsForPendingSessionId(sessionId) {
|
|
419
|
+
const logDetails = {
|
|
420
|
+
sessionId
|
|
421
|
+
};
|
|
422
|
+
const pendingSession = this.pendingSessions[sessionId];
|
|
423
|
+
if (pendingSession) {
|
|
424
|
+
logDetails.sessionType = pendingSession.sessionType;
|
|
425
|
+
logDetails.conversationId = pendingSession.conversationId;
|
|
426
|
+
}
|
|
427
|
+
return logDetails;
|
|
428
|
+
}
|
|
429
|
+
sendStatsImmediately() {
|
|
430
|
+
// `throttledSendStats` needs to have a scheduled exeuction for `flush` to invoke the throttled function
|
|
431
|
+
this.throttledSendStats();
|
|
432
|
+
this.throttledSendStats.flush();
|
|
433
|
+
}
|
|
434
|
+
sendStats() {
|
|
435
|
+
var _a, _b;
|
|
436
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
437
|
+
if (!navigator.onLine) {
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
const statsToSend = [];
|
|
441
|
+
let currentSize = 0;
|
|
442
|
+
for (const stats of this.statsArr) {
|
|
443
|
+
const statSize = calculatePayloadSize(stats);
|
|
444
|
+
if (currentSize + statSize < this.currentMaxStatSize) {
|
|
445
|
+
statsToSend.push(stats);
|
|
446
|
+
currentSize += statSize;
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
break;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
this.statsArr.splice(0, statsToSend.length);
|
|
453
|
+
this.statBuffer = this.statsArr.reduce((currentSize, stats) => currentSize + calculatePayloadSize(stats), 0);
|
|
454
|
+
if (!statsToSend.length || this.client.isGuest) {
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
const data = {
|
|
458
|
+
appName: 'streamingclient',
|
|
459
|
+
appVersion: Client.version,
|
|
460
|
+
originAppName: this.client.config.appName,
|
|
461
|
+
originAppVersion: this.client.config.appVersion,
|
|
462
|
+
actions: statsToSend
|
|
463
|
+
};
|
|
464
|
+
// At least for now, we'll just fire and forget. Since this is non-critical, we'll not retry failures
|
|
465
|
+
try {
|
|
466
|
+
let authToken = this.client.config.authToken;
|
|
467
|
+
let url = 'diagnostics/newrelic/insights';
|
|
468
|
+
if (this.client.backgroundAssistantMode) {
|
|
469
|
+
authToken = this.client.config.jwt;
|
|
470
|
+
url += '/backgroundassistant';
|
|
471
|
+
}
|
|
472
|
+
yield this.client.http.requestApi(url, {
|
|
473
|
+
method: 'post',
|
|
474
|
+
responseType: 'text',
|
|
475
|
+
host: this.client.config.apiHost,
|
|
476
|
+
authToken,
|
|
477
|
+
logger: this.client.logger,
|
|
478
|
+
data
|
|
479
|
+
});
|
|
480
|
+
this.currentMaxStatSize = desiredMaxStatsSize;
|
|
481
|
+
}
|
|
482
|
+
catch (err) {
|
|
483
|
+
// re-add the stats to the buffer
|
|
484
|
+
if (HttpClient.retryStatusCodes.has((_a = err.response) === null || _a === void 0 ? void 0 : _a.status) || !navigator.onLine) {
|
|
485
|
+
this.statsArr = [...statsToSend, ...this.statsArr];
|
|
486
|
+
this.statBuffer = this.statsArr.reduce((currentSize, stats) => currentSize + calculatePayloadSize(stats), 0);
|
|
487
|
+
}
|
|
488
|
+
if (((_b = err.response) === null || _b === void 0 ? void 0 : _b.status) === 413) {
|
|
489
|
+
const attemptedPayloadSize = this.currentMaxStatSize;
|
|
490
|
+
this.currentMaxStatSize -= this.statsSizeDecreaseAmount;
|
|
491
|
+
this.logger.info('Failed to send stats due to 413, retrying with smaller set', { attemptedPayloadSize, newPayloadSize: this.currentMaxStatSize });
|
|
492
|
+
yield this.sendStats();
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
this.logger.error('Failed to send stats', {
|
|
496
|
+
err,
|
|
497
|
+
numberOfFailedStats: statsToSend.length
|
|
498
|
+
}, { skipServer: !navigator.onLine });
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
addEventListeners() {
|
|
504
|
+
this.client.on('connected', () => {
|
|
505
|
+
if (this.refreshIceServersTimer) {
|
|
506
|
+
clearInterval(this.refreshIceServersTimer);
|
|
507
|
+
this.refreshIceServersTimer = null;
|
|
508
|
+
}
|
|
509
|
+
this.refreshIceServersTimer = setInterval(this.refreshIceServers.bind(this), ICE_SERVER_REFRESH_PERIOD);
|
|
510
|
+
return this.refreshIceServers()
|
|
511
|
+
.catch((error) => this.logger.error('Error fetching ice servers after streaming-client connected', {
|
|
512
|
+
error,
|
|
513
|
+
channelId: this.client.config.channelId
|
|
514
|
+
}));
|
|
515
|
+
});
|
|
516
|
+
this.client.on('disconnected', () => {
|
|
517
|
+
clearInterval(this.refreshIceServersTimer);
|
|
518
|
+
this.refreshIceServersTimer = null;
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* Stanza Handlers
|
|
523
|
+
*/
|
|
524
|
+
handlePropose(msg) {
|
|
525
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
526
|
+
if (msg.from === this.jid) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
const sessionId = msg.propose.sessionId;
|
|
530
|
+
let sessionInfo = this.pendingSessions[sessionId];
|
|
531
|
+
const isDuplicatePropose = !!sessionInfo;
|
|
532
|
+
const sessionType = this.getSessionTypeByJid(msg.from);
|
|
533
|
+
const loggingParams = { sessionId, conversationId: msg.propose.conversationId, sessionType, isDuplicatePropose };
|
|
534
|
+
this.logger.info('propose received', loggingParams);
|
|
535
|
+
if (!isDuplicatePropose) {
|
|
536
|
+
const { appId } = this.client.config;
|
|
537
|
+
const proposeStat = {
|
|
538
|
+
actionName: 'WebrtcStats',
|
|
539
|
+
details: {
|
|
540
|
+
_eventTimestamp: new Date().getTime(),
|
|
541
|
+
_eventType: 'firstPropose',
|
|
542
|
+
conversationId: loggingParams.conversationId,
|
|
543
|
+
sdpViaXmppRequested: !!msg.propose.sdpOverXmpp,
|
|
544
|
+
sessionId,
|
|
545
|
+
sessionType
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
this.addStatToQueue(proposeStat);
|
|
549
|
+
// TODO: is ofrom used?
|
|
550
|
+
// const roomJid = (msg.ofrom && msg.ofrom.full) || msg.from.full || msg.from;
|
|
551
|
+
const fromJid = msg.from;
|
|
552
|
+
const roomJid = fromJid;
|
|
553
|
+
msg.propose.originalRoomJid = msg.propose.originalRoomJid || roomJid;
|
|
554
|
+
sessionInfo = Object.assign(Object.assign({}, msg.propose), { toJid: msg.to, fromJid,
|
|
555
|
+
sessionType,
|
|
556
|
+
roomJid, id: sessionId, sdpOverXmpp: msg.propose.sdpOverXmpp, privAnswerMode: msg.propose.privAnswerMode });
|
|
557
|
+
if (!isDuplicatePropose) {
|
|
558
|
+
this.sessionsMap[sessionInfo.id] = !!sessionInfo.sdpOverXmpp;
|
|
559
|
+
this.pendingSessions[sessionId] = sessionInfo;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (sessionInfo.accepted) {
|
|
563
|
+
this.logger.info('proceed already sent for this session, but sending another', loggingParams);
|
|
564
|
+
yield this.acceptRtcSession(sessionId);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
this.emit(events.REQUEST_INCOMING_RTCSESSION, Object.assign(sessionInfo));
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
handleRetract(sessionId) {
|
|
571
|
+
this.logger.info('retract received', this.getLogDetailsForPendingSessionId(sessionId));
|
|
572
|
+
delete this.sessionsMap[sessionId];
|
|
573
|
+
delete this.pendingSessions[sessionId];
|
|
574
|
+
return this.emit(events.CANCEL_INCOMING_RTCSESSION, sessionId);
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Inform the client that another client has already taken care of the pendingSession
|
|
578
|
+
*/
|
|
579
|
+
handledIncomingRtcSession(sessionId, msg) {
|
|
580
|
+
const acceptedOrRejected = msg.accept ? 'accept' : 'reject';
|
|
581
|
+
this.logger.info(`${acceptedOrRejected} received`, this.getLogDetailsForPendingSessionId(sessionId));
|
|
582
|
+
return this.emit(events.HANDLED_INCOMING_RTCSESSION, sessionId);
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Exposed Api
|
|
586
|
+
*/
|
|
587
|
+
initiateRtcSession(opts) {
|
|
588
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
589
|
+
// send media presence to join conference or screen screenRecording
|
|
590
|
+
// or send propose to single client for 1:1 video chat
|
|
591
|
+
const session = {
|
|
592
|
+
to: opts.jid,
|
|
593
|
+
propose: {
|
|
594
|
+
id: globalThis.crypto.randomUUID(),
|
|
595
|
+
descriptions: []
|
|
596
|
+
}
|
|
597
|
+
};
|
|
598
|
+
if (opts.stream) {
|
|
599
|
+
for (const track of Array.from(opts.stream.getTracks())) {
|
|
600
|
+
session.propose.descriptions.push({ media: track.kind });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (opts.provideVideo) {
|
|
604
|
+
const videoDescriptionAlreadyExists = session.propose.descriptions.filter((desciption) => desciption.media === 'video').length > 0;
|
|
605
|
+
if (!videoDescriptionAlreadyExists) {
|
|
606
|
+
session.propose.descriptions.push({ media: 'video' });
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
if (opts.provideAudio) {
|
|
610
|
+
const audioDescriptionAlreadyExists = session.propose.descriptions.filter((desciption) => desciption.media === 'audio').length > 0;
|
|
611
|
+
if (!audioDescriptionAlreadyExists) {
|
|
612
|
+
session.propose.descriptions.push({ media: 'audio' });
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
if (opts.mediaPurpose) {
|
|
616
|
+
session.propose.descriptions.push({ media: opts.mediaPurpose });
|
|
617
|
+
}
|
|
618
|
+
if (opts.jid && opts.jid.match(/@conference/)) {
|
|
619
|
+
let mediaDescriptions = session.propose.descriptions;
|
|
620
|
+
if (mediaDescriptions.length === 0) {
|
|
621
|
+
mediaDescriptions = [{ media: 'listener' }];
|
|
622
|
+
}
|
|
623
|
+
const mediaPresence = {
|
|
624
|
+
type: 'upgradeMedia',
|
|
625
|
+
to: opts.jid,
|
|
626
|
+
id: globalThis.crypto.randomUUID(),
|
|
627
|
+
from: this.jid,
|
|
628
|
+
media: {
|
|
629
|
+
conversationId: opts.conversationId,
|
|
630
|
+
sourceCommunicationId: opts.sourceCommunicationId
|
|
631
|
+
}
|
|
632
|
+
};
|
|
633
|
+
if (opts.mediaPurpose === SessionTypes.collaborateVideo) {
|
|
634
|
+
Object.assign(mediaPresence.media, {
|
|
635
|
+
video: true,
|
|
636
|
+
audio: true,
|
|
637
|
+
videoGuest: true
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
// TODO? can't set last-n on parent element because it invalidates presence root schema
|
|
641
|
+
for (const mediaDescription of mediaDescriptions) {
|
|
642
|
+
mediaPresence.media[mediaDescription.media] = true;
|
|
643
|
+
}
|
|
644
|
+
yield this.stanzaInstance.send('presence', mediaPresence);
|
|
645
|
+
}
|
|
646
|
+
else {
|
|
647
|
+
yield this.stanzaInstance.send('message', session); // send as Message
|
|
648
|
+
this.pendingSessions[session.propose.id] = session;
|
|
649
|
+
}
|
|
650
|
+
return session.propose.id;
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
// jingle proceed
|
|
654
|
+
acceptRtcSession(sessionId) {
|
|
655
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
656
|
+
const session = this.pendingSessions[sessionId];
|
|
657
|
+
if (!session) {
|
|
658
|
+
this.emit(events.RTCSESSION_ERROR, 'Cannot accept session because it is not pending or does not exist');
|
|
659
|
+
return;
|
|
660
|
+
}
|
|
661
|
+
const proceed = {
|
|
662
|
+
to: session.fromJid,
|
|
663
|
+
proceed: {
|
|
664
|
+
sessionId
|
|
665
|
+
}
|
|
666
|
+
};
|
|
667
|
+
session.accepted = true;
|
|
668
|
+
const details = this.getLogDetailsForPendingSessionId(sessionId);
|
|
669
|
+
this.logger.info('sending jingle proceed', details);
|
|
670
|
+
yield this.stanzaInstance.send('message', proceed); // send as Message
|
|
671
|
+
this.logger.info('sent jingle proceed', details);
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
rejectRtcSession(sessionId, ignore = false) {
|
|
675
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
676
|
+
const logDetails = this.getLogDetailsForPendingSessionId(sessionId);
|
|
677
|
+
const session = this.pendingSessions[sessionId];
|
|
678
|
+
if (!session) {
|
|
679
|
+
this.emit(events.RTCSESSION_ERROR, 'Cannot reject session because it is not pending or does not exist');
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
delete this.sessionsMap[sessionId];
|
|
683
|
+
delete this.pendingSessions[sessionId];
|
|
684
|
+
if (ignore) {
|
|
685
|
+
this.ignoredSessions.set(sessionId, true);
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
const reject1 = {
|
|
689
|
+
to: toBare(this.jid),
|
|
690
|
+
reject: {
|
|
691
|
+
sessionId
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
const firstMessage = this.stanzaInstance.send('message', reject1); // send as Message
|
|
695
|
+
const reject2 = {
|
|
696
|
+
to: session.fromJid,
|
|
697
|
+
reject: {
|
|
698
|
+
sessionId
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
const secondMessage = this.stanzaInstance.send('message', reject2); // send as Message
|
|
702
|
+
this.logger.info('sending jingle reject', logDetails);
|
|
703
|
+
yield Promise.all([firstMessage, secondMessage]);
|
|
704
|
+
this.logger.info('sent jingle reject', logDetails);
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
rtcSessionAccepted(sessionId) {
|
|
709
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
710
|
+
const pendingSession = this.pendingSessions[sessionId];
|
|
711
|
+
const logDetails = this.getLogDetailsForPendingSessionId(sessionId);
|
|
712
|
+
const accept = {
|
|
713
|
+
to: toBare(this.jid),
|
|
714
|
+
accept: {
|
|
715
|
+
sessionId
|
|
716
|
+
}
|
|
717
|
+
};
|
|
718
|
+
this.logger.info('sending session-info:accept', logDetails);
|
|
719
|
+
yield this.stanzaInstance.send('message', accept); // send as Message
|
|
720
|
+
this.logger.info('sent session-info:accept', logDetails);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
notifyScreenShareStart(session) {
|
|
724
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
725
|
+
return this.stanzaInstance.send('iq', {
|
|
726
|
+
to: `${session.peerID}`,
|
|
727
|
+
from: this.jid,
|
|
728
|
+
type: 'set',
|
|
729
|
+
jingle: {
|
|
730
|
+
action: JingleAction.SessionInfo,
|
|
731
|
+
sid: session.id,
|
|
732
|
+
screenstart: {}
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
});
|
|
736
|
+
}
|
|
737
|
+
notifyScreenShareStop(session) {
|
|
738
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
739
|
+
return this.stanzaInstance.send('iq', {
|
|
740
|
+
to: `${session.peerID}`,
|
|
741
|
+
from: this.jid,
|
|
742
|
+
type: 'set',
|
|
743
|
+
jingle: {
|
|
744
|
+
action: JingleAction.SessionInfo,
|
|
745
|
+
sid: session.id,
|
|
746
|
+
screenstop: {}
|
|
747
|
+
}
|
|
748
|
+
});
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
cancelRtcSession(sessionId) {
|
|
752
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
753
|
+
const session = this.pendingSessions[sessionId];
|
|
754
|
+
const logDetails = this.getLogDetailsForPendingSessionId(sessionId);
|
|
755
|
+
if (!session) {
|
|
756
|
+
this.emit(events.RTCSESSION_ERROR, 'Cannot cancel session because it is not pending or does not exist');
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
const retract = {
|
|
760
|
+
to: session.toJid,
|
|
761
|
+
retract: {
|
|
762
|
+
sessionId
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
delete this.pendingSessions[sessionId];
|
|
766
|
+
delete this.sessionsMap[sessionId];
|
|
767
|
+
this.logger.info('sending jingle retract', logDetails);
|
|
768
|
+
yield this.stanzaInstance.send('message', retract); // send as Message
|
|
769
|
+
this.logger.info('sent jingle retract', logDetails);
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
refreshIceServers() {
|
|
773
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
774
|
+
if (!this.refreshIceServersRetryPromise) {
|
|
775
|
+
this.refreshIceServersRetryPromise = retryPromise(this._refreshIceServers.bind(this, this.stanzaInstance), (error) => {
|
|
776
|
+
if (++this.discoRetries > MAX_DISCO_RETRIES) {
|
|
777
|
+
this.logger.warn('fetching ice servers failed. max retries reached.', {
|
|
778
|
+
retryAttempt: this.discoRetries,
|
|
779
|
+
MAX_DISCO_RETRIES,
|
|
780
|
+
error,
|
|
781
|
+
channelId: this.client.config.channelId
|
|
782
|
+
});
|
|
783
|
+
return false;
|
|
784
|
+
}
|
|
785
|
+
this.logger.warn('fetching ice servers failed. retrying', {
|
|
786
|
+
retryAttempt: this.discoRetries,
|
|
787
|
+
error,
|
|
788
|
+
channelId: this.client.config.channelId
|
|
789
|
+
});
|
|
790
|
+
return true;
|
|
791
|
+
}, 0, this.client.logger);
|
|
792
|
+
}
|
|
793
|
+
return this.refreshIceServersRetryPromise.promise
|
|
794
|
+
.finally(() => {
|
|
795
|
+
this.discoRetries = 0;
|
|
796
|
+
this.refreshIceServersRetryPromise = undefined;
|
|
797
|
+
});
|
|
798
|
+
});
|
|
799
|
+
}
|
|
800
|
+
_refreshIceServers(stanzaInstance) {
|
|
801
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
802
|
+
if (!stanzaInstance) {
|
|
803
|
+
throw new Error('No stanza instance to refresh ice servers');
|
|
804
|
+
}
|
|
805
|
+
const server = stanzaInstance.config.server;
|
|
806
|
+
const turnServersPromise = stanzaInstance.getServices(server, 'turn', '1');
|
|
807
|
+
const stunServersPromise = stanzaInstance.getServices(server, 'stun', '1');
|
|
808
|
+
const servicesPromise = new Promise((resolve, reject) => {
|
|
809
|
+
setTimeout(() => {
|
|
810
|
+
reject(new Error('Timeout waiting for refresh ice servers to finish'));
|
|
811
|
+
}, ICE_SERVER_TIMEOUT);
|
|
812
|
+
Promise.all([
|
|
813
|
+
turnServersPromise,
|
|
814
|
+
stunServersPromise
|
|
815
|
+
])
|
|
816
|
+
.then(([turn, stun]) => {
|
|
817
|
+
resolve([turn, stun]);
|
|
818
|
+
})
|
|
819
|
+
.catch(reject);
|
|
820
|
+
});
|
|
821
|
+
const [turnServers, stunServers] = yield servicesPromise;
|
|
822
|
+
const iceServers = [
|
|
823
|
+
...turnServers.services,
|
|
824
|
+
...stunServers.services
|
|
825
|
+
].map((service) => {
|
|
826
|
+
const port = service.port ? `:${service.port}` : '';
|
|
827
|
+
const ice = {
|
|
828
|
+
type: service.type,
|
|
829
|
+
urls: `${service.type}:${service.host}${port}`
|
|
830
|
+
};
|
|
831
|
+
if (['turn', 'turns'].includes(service.type)) {
|
|
832
|
+
if (service.transport && service.transport !== 'udp') {
|
|
833
|
+
ice.urls += `?transport=${service.transport}`;
|
|
834
|
+
}
|
|
835
|
+
if (service.username) {
|
|
836
|
+
ice.username = service.username;
|
|
837
|
+
}
|
|
838
|
+
if (service.password) {
|
|
839
|
+
ice.credential = service.password;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
return ice;
|
|
843
|
+
});
|
|
844
|
+
this.setIceServers(iceServers, stanzaInstance);
|
|
845
|
+
if (!stunServers.services.length) {
|
|
846
|
+
this.logger.info('No stun servers received, setting iceTransportPolicy to "relay"');
|
|
847
|
+
this.setIceTransportPolicy('relay', stanzaInstance);
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
this.setIceTransportPolicy('all', stanzaInstance);
|
|
851
|
+
}
|
|
852
|
+
return iceServers;
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
setIceServers(iceServers, stanzaInstance) {
|
|
856
|
+
stanzaInstance.jingle.iceServers = iceServers;
|
|
857
|
+
this.iceServers = iceServers;
|
|
858
|
+
}
|
|
859
|
+
getIceTransportPolicy() {
|
|
860
|
+
var _a;
|
|
861
|
+
return (_a = this.stanzaInstance) === null || _a === void 0 ? void 0 : _a.jingle.config.peerConnectionConfig.iceTransportPolicy;
|
|
862
|
+
}
|
|
863
|
+
setIceTransportPolicy(policy, stanzaInstance) {
|
|
864
|
+
stanzaInstance.jingle.config.peerConnectionConfig.iceTransportPolicy = policy;
|
|
865
|
+
}
|
|
866
|
+
getSessionTypeByJid(jid) {
|
|
867
|
+
if (isAcdJid(jid)) {
|
|
868
|
+
return SessionTypes.acdScreenShare;
|
|
869
|
+
}
|
|
870
|
+
else if (isScreenRecordingJid(jid)) {
|
|
871
|
+
return SessionTypes.screenRecording;
|
|
872
|
+
}
|
|
873
|
+
else if (isLiveScreenMonitoringJid(jid)) {
|
|
874
|
+
return SessionTypes.liveScreenMonitoring;
|
|
875
|
+
}
|
|
876
|
+
else if (isSoftphoneJid(jid)) {
|
|
877
|
+
return SessionTypes.softphone;
|
|
878
|
+
}
|
|
879
|
+
else if (isVideoJid(jid)) {
|
|
880
|
+
return SessionTypes.collaborateVideo;
|
|
881
|
+
}
|
|
882
|
+
else {
|
|
883
|
+
return SessionTypes.unknown;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
getSessionManager() {
|
|
887
|
+
var _a;
|
|
888
|
+
return (_a = this.stanzaInstance) === null || _a === void 0 ? void 0 : _a.jingle;
|
|
889
|
+
}
|
|
890
|
+
getAllSessions() {
|
|
891
|
+
return [...this.stanzaSessions, ...this.webrtcSessions];
|
|
892
|
+
}
|
|
893
|
+
proxyNRStat(stat) {
|
|
894
|
+
this.addStatToQueue(stat);
|
|
895
|
+
}
|
|
896
|
+
get expose() {
|
|
897
|
+
return {
|
|
898
|
+
on: this.on.bind(this),
|
|
899
|
+
once: this.once.bind(this),
|
|
900
|
+
off: this.off.bind(this),
|
|
901
|
+
removeAllListeners: this.removeAllListeners.bind(this),
|
|
902
|
+
removeListener: this.removeListener.bind(this),
|
|
903
|
+
refreshIceServers: this.refreshIceServers.bind(this),
|
|
904
|
+
acceptRtcSession: this.acceptRtcSession.bind(this),
|
|
905
|
+
rejectRtcSession: this.rejectRtcSession.bind(this),
|
|
906
|
+
cancelRtcSession: this.cancelRtcSession.bind(this),
|
|
907
|
+
notifyScreenShareStart: this.notifyScreenShareStart.bind(this),
|
|
908
|
+
notifyScreenShareStop: this.notifyScreenShareStop.bind(this),
|
|
909
|
+
rtcSessionAccepted: this.rtcSessionAccepted.bind(this),
|
|
910
|
+
initiateRtcSession: this.initiateRtcSession.bind(this),
|
|
911
|
+
getSessionTypeByJid: this.getSessionTypeByJid.bind(this),
|
|
912
|
+
getSessionManager: this.getSessionManager.bind(this),
|
|
913
|
+
getAllSessions: this.getAllSessions.bind(this),
|
|
914
|
+
proxyNRStat: this.proxyNRStat.bind(this)
|
|
915
|
+
};
|
|
916
|
+
}
|
|
917
|
+
}
|