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