@flashphoner/websdk 2.0.274 → 2.0.276

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.
@@ -1,528 +0,0 @@
1
- 'use strict';
2
-
3
- const util = require('./util');
4
- const LOG_PREFIX = "stats-collector";
5
- const CONNECTION_TYPE = {
6
- WEBSOCKET: "ws",
7
- HTTP: "http"
8
- }
9
- const MAX_SEND_ERRORS = 3;
10
- const CONNECTION_STATUS = {
11
- INIT: 0,
12
- OK: 200,
13
- BAD_REQUEST: 400,
14
- INTERNAL_SERVER_ERROR: 500
15
- };
16
-
17
- // Collect and send WebRTC statistics periodically
18
- const StreamStatsCollector = function(description, id, mediaConnection, wsConnection, logger, maxErrors) {
19
- let statCollector = {
20
- description: description,
21
- id: id,
22
- mediaConnection: mediaConnection,
23
- connection: Connection(wsConnection, maxErrors),
24
- logger: getLogger(logger),
25
- headers: "",
26
- compression: "none",
27
- metricsBatch: null,
28
- timer: null,
29
- batchCount: 0,
30
- timerBusy: false,
31
- start: async function() {
32
- let error = "Can't collect WebRTC stats to send: ";
33
- if (!statCollector.description.types) {
34
- throw new Error(error + "no report types defined");
35
- }
36
- if (!statCollector.description.sampling) {
37
- throw new Error(error + "no sampling interval defined");
38
- }
39
- if (!statCollector.description.batchSize) {
40
- throw new Error(error + "no metrics batch size defined");
41
- }
42
- if (!statCollector.mediaConnection) {
43
- throw new Error(error + "no media connection available");
44
- }
45
-
46
- statCollector.logger.debug(LOG_PREFIX, "RTCMetricsServerDescription: " + JSON.stringify(statCollector.description));
47
- if (statCollector.description.ingestPoint) {
48
- let authHeader = null;
49
- if (statCollector.description.authorization) {
50
- authHeader = {
51
- Authorization: statCollector.description.authorization
52
- }
53
- }
54
- statCollector.connection.setUp(statCollector.description.ingestPoint, authHeader);
55
- }
56
-
57
- await statCollector.updateHeaders();
58
- await statCollector.updateCompression();
59
- await statCollector.sendHeaders();
60
- if (statCollector.description.collect === "on") {
61
- statCollector.collect(true);
62
- }
63
- },
64
- collect: function(enable) {
65
- if (enable && statCollector.connection.status === CONNECTION_STATUS.OK) {
66
- statCollector.startTimer();
67
- } else {
68
- statCollector.stopTimer();
69
- }
70
- },
71
- stop: function() {
72
- statCollector.stopTimer();
73
- statCollector.headers = "";
74
- },
75
- update: async function(description) {
76
- if (!description) {
77
- statCollector.logger.error(LOG_PREFIX + "-" + statCollector.id, "Can't update WebRTC metrics sending: no parameters passed");
78
- return;
79
- }
80
- statCollector.logger.debug(LOG_PREFIX, "New RTCMetricsServerDescription: " + JSON.stringify(description));
81
- if (description.types || description.compression) {
82
- statCollector.stop();
83
- if (description.types) {
84
- statCollector.description.types = description.types;
85
- await statCollector.updateHeaders();
86
- }
87
- if (description.compression) {
88
- statCollector.description.compression = description.compression;
89
- await statCollector.updateCompression();
90
- }
91
- await statCollector.sendHeaders();
92
- if (statCollector.connection.status !== CONNECTION_STATUS.OK) {
93
- return;
94
- }
95
- } else {
96
- statCollector.collect(false);
97
- }
98
- if (description.batchSize) {
99
- statCollector.description.batchSize = description.batchSize;
100
- }
101
- if (description.sampling) {
102
- statCollector.description.sampling = description.sampling;
103
- }
104
- if (description.collect) {
105
- statCollector.description.collect = description.collect;
106
- }
107
- switch(statCollector.description.collect) {
108
- case "on":
109
- statCollector.collect(true);
110
- break;
111
- case "off":
112
- statCollector.collect(false);
113
- break;
114
- }
115
- },
116
- updateHeaders: async function(stats = null) {
117
- let currentHeaders = "";
118
- let headersChanged = false;
119
- if (stats === null) {
120
- stats = await statCollector.mediaConnection.getWebRTCStats();
121
- }
122
- Object.keys(statCollector.description.types).forEach((type) => {
123
- let typeDescriptor = statCollector.description.types[type];
124
- let metricsString = "";
125
- let contentFilters = null;
126
- if (typeDescriptor.metrics) {
127
- metricsString = typeDescriptor.metrics;
128
- }
129
- if (typeDescriptor.contains) {
130
- contentFilters = typeDescriptor.contains;
131
- }
132
- if (stats[type]) {
133
- stats[type].forEach((report) => {
134
- statCollector.logger.debug(LOG_PREFIX + "-" + statCollector.id, type + " report: " + JSON.stringify(report));
135
- if (contentFilters) {
136
- let filtersMatched = true;
137
- for (const filter in contentFilters) {
138
- statCollector.logger.debug(LOG_PREFIX + "-" + statCollector.id, type + " filter by " + filter + ": " + JSON.stringify(contentFilters[filter]));
139
- let filterMatched = false;
140
- if (report[filter]) {
141
- for (const value of contentFilters[filter]) {
142
- statCollector.logger.debug(LOG_PREFIX + "-" + statCollector.id, filter + ": " + value + " <> " + report[filter]);
143
- if (report[filter] === value) {
144
- filterMatched = true;
145
- break;
146
- }
147
- }
148
- }
149
- filtersMatched = filtersMatched && filterMatched;
150
- if (!filterMatched) {
151
- break;
152
- }
153
- }
154
- if (filtersMatched) {
155
- currentHeaders = statCollector.addHeaders(currentHeaders, report, metricsString);
156
- }
157
- } else {
158
- currentHeaders = statCollector.addHeaders(currentHeaders, report, metricsString);
159
- }
160
- });
161
- } else {
162
- statCollector.logger.debug(LOG_PREFIX + "-" + statCollector.id, "No report type found in RTC stats: '" + type + "'");
163
- }
164
- });
165
- if (currentHeaders !== statCollector.headers) {
166
- headersChanged = true;
167
- let newMetrics = [];
168
- currentHeaders.split(",").forEach((header) => {
169
- if (statCollector.headers.indexOf(header) === -1) {
170
- newMetrics.push(header);
171
- }
172
- });
173
- if (newMetrics.length) {
174
- statCollector.logger.info(LOG_PREFIX + "-" + statCollector.id, "RTC metrics to be collected: " + newMetrics.toString());
175
- }
176
- statCollector.headers = currentHeaders;
177
- }
178
- return headersChanged;
179
- },
180
- addHeaders: function(currentHeaders, report, metricsString) {
181
- if (metricsString) {
182
- let metrics = metricsString.split(",");
183
- metrics.forEach((metric) => {
184
- for (const key of Object.keys(report)) {
185
- if (metric === key && report[key]) {
186
- currentHeaders = util.addFieldToCsvString(currentHeaders, report.type + "." + report.id + "." + metric, ",");
187
- break;
188
- }
189
- }
190
- });
191
- }
192
- return currentHeaders;
193
- },
194
- updateCompression: async function() {
195
- if (statCollector.description.compression) {
196
- if (statCollector.description.compression.indexOf("gzip") >= 0) {
197
- await statCollector.checkForCompression("gzip");
198
- } else if (statCollector.description.compression.indexOf("deflate") >= 0) {
199
- await statCollector.checkForCompression("deflate");
200
- }
201
- }
202
- },
203
- updateHttpConnection: function(url, authorization) {
204
- if (url.startsWith(CONNECTION_TYPE.HTTP) && authorization) {
205
- statCollector.connection.http.setAuthorization(authorization);
206
- }
207
- },
208
- checkForCompression: async function(compression) {
209
- try {
210
- await util.compress(compression, "test", false);
211
- statCollector.compression = compression;
212
- } catch (e) {
213
- statCollector.logger.warn(LOG_PREFIX + "-" + statCollector.id, "Can't compress metrics data using " + compression + ": " + e);
214
- statCollector.compression = "none";
215
- }
216
- },
217
- sendHeaders: async function() {
218
- let data = {
219
- mediaSessionId: statCollector.id,
220
- compression: statCollector.compression,
221
- headers: statCollector.headers
222
- };
223
- await statCollector.send("webRTCMetricsClientDescription", data);
224
- },
225
- send: async function(message, data) {
226
- if (statCollector.connection.status === CONNECTION_STATUS.INIT || statCollector.connection.status === CONNECTION_STATUS.OK) {
227
- statCollector.logger.debug(LOG_PREFIX + "-" + statCollector.id, data);
228
- await statCollector.connection.send(message, data);
229
- if (statCollector.connection.status !== CONNECTION_STATUS.OK) {
230
- statCollector.logger.error(LOG_PREFIX + "-" + statCollector.id, "Error " + statCollector.connection.status + " sending RTC metrics to the server, stop sending");
231
- statCollector.stop();
232
- }
233
- }
234
- },
235
- startTimer: function() {
236
- if (!statCollector.timer) {
237
- statCollector.batchCount = statCollector.description.batchSize;
238
- statCollector.timer = setInterval(statCollector.collectMetrics, statCollector.description.sampling);
239
- }
240
- },
241
- stopTimer: function() {
242
- if (statCollector.timer) {
243
- clearInterval(statCollector.timer);
244
- statCollector.timer = null;
245
- statCollector.metricsBatch = null;
246
- }
247
- },
248
- isMetricValid: function(value) {
249
- return value != null && value !== "" && value !== "undefined" && value !== "null";
250
- },
251
- collectMetrics: async function() {
252
- if (statCollector.timer && !statCollector.timerBusy) {
253
- // Unfortunately there are no real atomics in JS unless SharedArrayBuffer is used
254
- // So we guard the timer callback with a dumb boolean
255
- statCollector.timerBusy = true;
256
- let stats = await statCollector.mediaConnection.getWebRTCStats();
257
-
258
- statCollector.startNewBatch();
259
-
260
- let metrics = [];
261
- let lostMetrics = [];
262
- if (statCollector.headers) {
263
- statCollector.headers.split(",").forEach((header) => {
264
- let components = header.split(".");
265
- let descriptor = {
266
- type: components[0],
267
- id: components[1],
268
- name: components[2]
269
- }
270
- let value = null;
271
-
272
- if (stats[descriptor.type]) {
273
- for (const report of stats[descriptor.type]) {
274
- if (report.id === descriptor.id) {
275
- value = report[descriptor.name];
276
- break;
277
- }
278
- }
279
- }
280
- if (statCollector.isMetricValid(value)) {
281
- metrics.push(value);
282
- } else {
283
- lostMetrics.push(descriptor);
284
- }
285
- });
286
- } else {
287
- statCollector.logger.info(LOG_PREFIX + "-" + statCollector.id, "No RTC metrics to collect, trying to update metrics available list");
288
- }
289
- // Metrics list may change if some metrics are added or some metrics are lost #WCS-4627
290
- let headersUpdated = await statCollector.updateHeaders(stats);
291
- if (lostMetrics.length) {
292
- statCollector.logger.info(LOG_PREFIX + "-" + statCollector.id, "Missing metrics: " + JSON.stringify(lostMetrics));
293
- // Send metrics already collected and start a new batch with current metrics array to send them later #WCS-4627
294
- await statCollector.sendMetrics();
295
- statCollector.startNewBatch(metrics);
296
- } else if (metrics.length) {
297
- statCollector.metricsBatch.push(metrics);
298
- statCollector.batchCount--;
299
- if (statCollector.batchCount === 0 || headersUpdated) {
300
- await statCollector.sendMetrics();
301
- }
302
- }
303
- // Check if metrics list changed and send a new headers if needed #WCS-4619
304
- if (headersUpdated) {
305
- statCollector.logger.info(LOG_PREFIX + "-" + statCollector.id, "RTC metrics list has changed, sending a new metrics description");
306
- await statCollector.sendHeaders();
307
- }
308
- statCollector.timerBusy = false;
309
- }
310
- },
311
- sendMetrics: async function() {
312
- let previous;
313
- let metricsToSend = [];
314
- let metricsData;
315
-
316
- for (let i = 0; statCollector.metricsBatch && i < statCollector.metricsBatch.length; i++) {
317
- let metricsString = "";
318
- for (let j = 0; j < statCollector.metricsBatch[i].length; j++) {
319
- let valueString = valueToString(statCollector.metricsBatch[i][j]);
320
- let previousString = "";
321
- let delimiter = ";";
322
- if (previous) {
323
- previousString = valueToString(previous[j]);
324
- }
325
- if (valueString === previousString) {
326
- valueString = "";
327
- }
328
- metricsString = util.addFieldToCsvString(metricsString, valueString, delimiter);
329
- if (j > 0 && metricsString === "") {
330
- metricsString = delimiter;
331
- }
332
- }
333
- previous = statCollector.metricsBatch[i];
334
- metricsToSend.push(metricsString);
335
- }
336
- if (statCollector.compression !== "none") {
337
- try {
338
- metricsData = await util.compress(statCollector.compression, JSON.stringify(metricsToSend), true);
339
- } catch(e) {
340
- statCollector.logger.warn(LOG_PREFIX + "-" + statCollector.id, "Can't send metrics data using" + statCollector.compression + ": " + e);
341
- metricsData = null;
342
- }
343
- } else {
344
- metricsData = metricsToSend;
345
- }
346
- if (metricsData) {
347
- let data = {
348
- mediaSessionId: statCollector.id,
349
- metrics: metricsData
350
- };
351
- await statCollector.send("webRTCMetricsBatch", data);
352
- }
353
- statCollector.cleanBatch();
354
- },
355
- startNewBatch: function(metrics) {
356
- if (!statCollector.metricsBatch) {
357
- statCollector.metricsBatch = [];
358
- if (metrics) {
359
- statCollector.metricsBatch.push(metrics);
360
- }
361
- }
362
- },
363
- cleanBatch: function() {
364
- if (statCollector.metricsBatch) {
365
- statCollector.metricsBatch = null;
366
- statCollector.batchCount = statCollector.description.batchSize;
367
- }
368
- }
369
- }
370
- return statCollector;
371
- }
372
-
373
- // Wrapper to send metrics via Websocket or HTTP POST
374
- const Connection = function(existingConnection = null, maxErrors = MAX_SEND_ERRORS) {
375
- const connection = {
376
- type: "",
377
- websocket: null,
378
- http: null,
379
- maxErrors: maxErrors,
380
- errorsCount: 0,
381
- status: CONNECTION_STATUS.INIT,
382
- setUp: function(url, headers = null, existingConnection = null) {
383
- if (url.startsWith(CONNECTION_TYPE.WEBSOCKET)) {
384
- connection.type = CONNECTION_TYPE.WEBSOCKET;
385
- // ToDo: create a new Websocket connection
386
- } else if (url.startsWith(CONNECTION_TYPE.HTTP)) {
387
- connection.type = CONNECTION_TYPE.HTTP
388
- connection.http = HttpConnection(url, headers);
389
- } else if (existingConnection) {
390
- connection.type = CONNECTION_TYPE.WEBSOCKET;
391
- connection.websocket = WebsocketConnection(existingConnection);
392
- }
393
- connection.errorsCount = 0;
394
- connection.status = CONNECTION_STATUS.INIT;
395
- },
396
- send: async function(message, data) {
397
- let code = CONNECTION_STATUS.BAD_REQUEST;
398
- switch(connection.type) {
399
- case CONNECTION_TYPE.WEBSOCKET:
400
- if (connection.websocket) {
401
- code = connection.websocket.send(message, data);
402
- }
403
- break;
404
- case CONNECTION_TYPE.HTTP:
405
- if (connection.http) {
406
- code = await connection.http.send(message, data);
407
- }
408
- break;
409
- }
410
- connection.status = code;
411
- if (connection.status === CONNECTION_STATUS.OK) {
412
- connection.errorsCount = 0;
413
- }
414
- else {
415
- if (message === "webRTCMetricsBatch") {
416
- connection.errorsCount++;
417
- if (connection.errorsCount < connection.maxErrors) {
418
- connection.status = CONNECTION_STATUS.OK;
419
- }
420
- }
421
- }
422
- }
423
- };
424
- connection.setUp("", null, existingConnection);
425
- return connection;
426
- }
427
-
428
- // Websocket connection (using existing one)
429
- const WebsocketConnection = function(wsConnection) {
430
- const connection = {
431
- websocket: wsConnection,
432
- send: function(message, data) {
433
- let code = CONNECTION_STATUS.BAD_REQUEST;
434
- if (connection.websocket) {
435
- if (connection.websocket.readyState === WebSocket.OPEN) {
436
- connection.websocket.send(JSON.stringify({
437
- message: message,
438
- data: [data]
439
- }));
440
- }
441
- code = CONNECTION_STATUS.OK;
442
- }
443
- return code;
444
- }
445
- }
446
- return connection;
447
- }
448
-
449
- // HTTP connection using Fetch API
450
- const HttpConnection = function(url, headers) {
451
- const connection = {
452
- url: addSlash(url),
453
- headers: headers,
454
- setAuthorization(token) {
455
- this.headers.Authorization = token;
456
- },
457
- send: async function(message, data) {
458
- let code = CONNECTION_STATUS.BAD_REQUEST;
459
- if (connection.url) {
460
- try {
461
- const httpHeaders = new Headers();
462
- httpHeaders.append("Content-Type", "application/json");
463
-
464
- if (connection.headers) {
465
- for (const [header, value] of Object.entries(connection.headers)) {
466
- httpHeaders.append(header, value);
467
- }
468
- }
469
- let response = await fetch(connection.url + message,{
470
- method: "POST",
471
- headers: httpHeaders,
472
- mode: "cors",
473
- body: JSON.stringify(data)
474
- });
475
- code = response.status;
476
- } catch (e) {
477
- code = CONNECTION_STATUS.INTERNAL_SERVER_ERROR;
478
- }
479
- }
480
- return code;
481
- }
482
- }
483
- return connection;
484
- }
485
-
486
- // Helper function to stringify a value
487
- const valueToString = function(value) {
488
- let valueString = "undefined";
489
- if (value) {
490
- if (typeof value === "object") {
491
- valueString = JSON.stringify(value);
492
- } else {
493
- valueString = value.toString();
494
- }
495
- }
496
- return valueString;
497
- }
498
-
499
- // Helper function to get logger object
500
- const getLogger = function(logger) {
501
- if (logger) {
502
- if (logger.info !== undefined &&
503
- logger.warn !== undefined &&
504
- logger.error !== undefined &&
505
- logger.debug !== undefined) {
506
- return logger;
507
- }
508
- }
509
- return {
510
- info: function() {},
511
- warn: function() {},
512
- error: function() {},
513
- debug: function() {}
514
- };
515
- }
516
-
517
- // Helper function to add slash to endpoint
518
- const addSlash = function(value) {
519
- let endpoint = value;
520
- if (endpoint && !endpoint.endsWith("/")) {
521
- endpoint = endpoint + "/";
522
- }
523
- return endpoint;
524
- }
525
-
526
- module.exports = {
527
- StreamStatsCollector: StreamStatsCollector
528
- }
package/src/util.js CHANGED
@@ -502,52 +502,6 @@ const addFieldToCsvString = function(csvString, field, delimiter) {
502
502
  return csvString;
503
503
  }
504
504
 
505
- const compress = async function(compression, data, base64) {
506
- // Throw exception if CompessionStream is not available
507
- if (typeof CompressionStream === "undefined") {
508
- throw new Error("Compression is not available");
509
- }
510
-
511
- // Convert incoming string to a stream
512
- let stream;
513
- if(typeof data == "string") {
514
- stream = new Blob([data], {
515
- type: 'text/plain',
516
- }).stream();
517
- } else {
518
- // Assume blog
519
- stream = data.stream();
520
- }
521
-
522
- // gzip stream
523
- const compressedReadableStream = stream.pipeThrough(
524
- new CompressionStream(compression)
525
- );
526
-
527
- // create Response
528
- const compressedResponse = await new Response(compressedReadableStream);
529
-
530
- // Get response Blob
531
- const blob = await compressedResponse.blob();
532
-
533
- if(base64) {
534
- // Get the ArrayBuffer
535
- const buffer = await blob.arrayBuffer();
536
-
537
- // convert ArrayBuffer to base64 encoded string
538
- const compressedBase64 = btoa(
539
- String.fromCharCode(
540
- ...new Uint8Array(buffer)
541
- )
542
- );
543
-
544
- return compressedBase64;
545
-
546
- } else {
547
- return blob;
548
- }
549
- }
550
-
551
505
  module.exports = {
552
506
  isEmptyObject,
553
507
  copyObjectToArray,
@@ -561,5 +515,4 @@ module.exports = {
561
515
  isPromise,
562
516
  setPublishingBitrate,
563
517
  addFieldToCsvString,
564
- compress
565
518
  };
@@ -963,6 +963,10 @@ var createConnection = function (options) {
963
963
  });
964
964
  };
965
965
 
966
+ var getRTCPeerConnection = function() {
967
+ return connection;
968
+ }
969
+
966
970
  var exports = {};
967
971
  exports.state = state;
968
972
  exports.createOffer = createOffer;
@@ -996,6 +1000,7 @@ var createConnection = function (options) {
996
1000
  exports.setZoom = setZoom;
997
1001
  exports.getZoom = getZoom;
998
1002
  exports.getWebRTCStats = getWebRTCStats;
1003
+ exports.getRTCPeerConnection = getRTCPeerConnection;
999
1004
  connections[id] = exports;
1000
1005
  resolve(exports);
1001
1006
  });