@observertc/observer-js 0.40.0-beta → 0.40.2-beta

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.
@@ -5,6 +5,7 @@ const events_1 = require("events");
5
5
  const ObservedInboundTrack_1 = require("./ObservedInboundTrack");
6
6
  const ObservedOutboundTrack_1 = require("./ObservedOutboundTrack");
7
7
  const ObservedICE_1 = require("./ObservedICE");
8
+ const ObservedDataChannel_1 = require("./ObservedDataChannel");
8
9
  class ObservedPeerConnection extends events_1.EventEmitter {
9
10
  constructor(_model, client) {
10
11
  super();
@@ -48,6 +49,8 @@ class ObservedPeerConnection extends events_1.EventEmitter {
48
49
  this._inboundVideoTracks = new Map();
49
50
  this._outboundAudioTracks = new Map();
50
51
  this._outboundVideoTracks = new Map();
52
+ this._dataChannels = new Map();
53
+ this.setMaxListeners(Infinity);
51
54
  }
52
55
  get serviceId() {
53
56
  return this.client.serviceId;
@@ -107,6 +110,9 @@ class ObservedPeerConnection extends events_1.EventEmitter {
107
110
  get outboundVideoTracks() {
108
111
  return this._outboundVideoTracks;
109
112
  }
113
+ get dataChannels() {
114
+ return this._dataChannels;
115
+ }
110
116
  get uptimeInMs() {
111
117
  return this._updated - this.created;
112
118
  }
@@ -210,6 +216,12 @@ class ObservedPeerConnection extends events_1.EventEmitter {
210
216
  this.totalSentVideoPackets += track.totalSentPackets;
211
217
  sumRttInMs = ((_a = track.rttInMs) !== null && _a !== void 0 ? _a : 0);
212
218
  });
219
+ this._dataChannels.forEach((channel) => {
220
+ this.deltaDataChannelBytesSent += channel.deltaBytesSent;
221
+ this.deltaDataChannelBytesReceived += channel.deltaBytesReceived;
222
+ this.totalDataChannelBytesSent += channel.totalBytesSent;
223
+ this.totalDataChannelBytesReceived += channel.totalBytesReceived;
224
+ });
213
225
  const iceRttInMs = (_a = this.ICE.stats) === null || _a === void 0 ? void 0 : _a.currentRoundTripTime;
214
226
  let nrOfBelongings = this._inboundAudioTracks.size + this._inboundVideoTracks.size + this._outboundAudioTracks.size + this._outboundVideoTracks.size;
215
227
  if (iceRttInMs) {
@@ -276,5 +288,17 @@ class ObservedPeerConnection extends events_1.EventEmitter {
276
288
  this.emit('newoutboundvideotrack', result);
277
289
  return result;
278
290
  }
291
+ createDataChannel(channelId) {
292
+ if (this._closed)
293
+ throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
294
+ const result = new ObservedDataChannel_1.ObservedDataChannel({
295
+ channelId,
296
+ }, this);
297
+ result.on('close', () => {
298
+ this._dataChannels.delete(result.channelId);
299
+ });
300
+ this._dataChannels.set(result.channelId, result);
301
+ return result;
302
+ }
279
303
  }
280
304
  exports.ObservedPeerConnection = ObservedPeerConnection;
@@ -1 +1 @@
1
- {"version":3,"file":"ObservedSfu.d.ts","sourceRoot":"","sources":["../src/ObservedSfu.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAE;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,MAAM,EAAE,EAAE,CAAC;IACX,KAAK,EAAE,EAAE,CAAC;CACV,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,WAAW;IACnC,EAAE,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACzG,GAAG,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1G,IAAI,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3G,IAAI,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CAC1F;AAED,qBAAa,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,YAAY;IAO9G,OAAO,CAAC,QAAQ,CAAC,MAAM;aACP,QAAQ,EAAE,QAAQ;aAClB,OAAO,EAAE,OAAO;IARjC,SAAgB,OAAO,SAAc;IAErC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAS;gBAGN,MAAM,EAAE,gBAAgB,EACzB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO;IAKjC,IAAW,SAAS,WAGnB;IAED,IAAW,KAAK,WAGf;IAED,IAAW,MAAM,WAGhB;IAED,IAAW,OAAO,WAEjB;IAED,IAAW,MAAM,YAEhB;IAEM,KAAK;IAML,MAAM,CAAC,MAAM,EAAE,SAAS;CAO/B"}
1
+ {"version":3,"file":"ObservedSfu.d.ts","sourceRoot":"","sources":["../src/ObservedSfu.ts"],"names":[],"mappings":";AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,+BAA+B,CAAC;AAE1D,MAAM,MAAM,gBAAgB,GAAE;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,iBAAiB,GAAG;IAC/B,MAAM,EAAE,EAAE,CAAC;IACX,KAAK,EAAE,EAAE,CAAC;CACV,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,WAAW;IACnC,EAAE,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACzG,GAAG,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IAC1G,IAAI,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IAC3G,IAAI,CAAC,CAAC,SAAS,MAAM,iBAAiB,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CAC1F;AAED,qBAAa,WAAW,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,YAAY;IAO9G,OAAO,CAAC,QAAQ,CAAC,MAAM;aACP,QAAQ,EAAE,QAAQ;aAClB,OAAO,EAAE,OAAO;IARjC,SAAgB,OAAO,SAAc;IAErC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,OAAO,CAAS;gBAGN,MAAM,EAAE,gBAAgB,EACzB,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO;IAMjC,IAAW,SAAS,WAGnB;IAED,IAAW,KAAK,WAGf;IAED,IAAW,MAAM,WAGhB;IAED,IAAW,OAAO,WAEjB;IAED,IAAW,MAAM,YAEhB;IAEM,KAAK;IAML,MAAM,CAAC,MAAM,EAAE,SAAS;CAO/B"}
@@ -11,6 +11,7 @@ class ObservedSfu extends events_1.EventEmitter {
11
11
  this.created = Date.now();
12
12
  this._updated = Date.now();
13
13
  this._closed = false;
14
+ this.setMaxListeners(Infinity);
14
15
  }
15
16
  get serviceId() {
16
17
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
package/lib/Observer.d.ts CHANGED
@@ -3,8 +3,10 @@ import { ObservedCall, ObservedCallModel } from './ObservedCall';
3
3
  import { ReportsCollector } from './ReportsCollector';
4
4
  import { EventEmitter } from 'events';
5
5
  import { PartialBy } from './common/utils';
6
+ import { ObserverSinkContext } from './common/types';
6
7
  export type ObserverEvents = {
7
8
  'newcall': [ObservedCall];
9
+ 'reports': [ObserverSinkContext];
8
10
  'close': [];
9
11
  };
10
12
  export type ObserverConfig = {
@@ -1 +1 @@
1
- {"version":3,"file":"Observer.d.ts","sourceRoot":"","sources":["../src/Observer.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAK3C,MAAM,MAAM,cAAc,GAAG;IAC5B,SAAS,EAAE,CAAC,YAAY,CAAC,CAAC;IAE1B,OAAO,EAAE,EAAE,CAAC;CACZ,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAE5B;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAE3B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE3C,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,QAAQ;IAChC,EAAE,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACnG,GAAG,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACpG,IAAI,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACrG,IAAI,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACpF;AAED,qBAAa,QAAS,SAAQ,YAAY;aAwBxB,MAAM,EAAE,cAAc;WAvBzB,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,QAAQ;IAkBvE,SAAgB,OAAO,mBAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,YAAY,CAAC,CAAgC;IACrD,OAAO,CAAC,OAAO,CAAS;gBAEP,MAAM,EAAE,cAAc;IAiBhC,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpF,MAAM,EAAE,SAAS,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC,CAAA;KAAE,GAC5E,YAAY,CAAC,CAAC,CAAC;IAsClB,IAAW,aAAa,IAAI,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAE5D;IAED,IAAW,MAAM,YAEhB;IAEM,KAAK;IASZ,OAAO,CAAC,YAAY;CAepB"}
1
+ {"version":3,"file":"Observer.d.ts","sourceRoot":"","sources":["../src/Observer.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAE3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAIrD,MAAM,MAAM,cAAc,GAAG;IAC5B,SAAS,EAAE,CAAC,YAAY,CAAC,CAAC;IAE1B,SAAS,EAAE,CAAC,mBAAmB,CAAC,CAAC;IACjC,OAAO,EAAE,EAAE,CAAC;CACZ,CAAA;AAED,MAAM,MAAM,cAAc,GAAG;IAE5B;;;;;;OAMG;IACH,gBAAgB,EAAE,MAAM,CAAC;IAEzB;;;;OAIG;IACH,kBAAkB,EAAE,MAAM,CAAC;IAE3B,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAE3C,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC1C,CAAC;AAEF,MAAM,CAAC,OAAO,WAAW,QAAQ;IAChC,EAAE,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACnG,GAAG,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACpG,IAAI,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,IAAI,CAAC;IACrG,IAAI,CAAC,CAAC,SAAS,MAAM,cAAc,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC;CACpF;AAED,qBAAa,QAAS,SAAQ,YAAY;aAwBxB,MAAM,EAAE,cAAc;WAvBzB,MAAM,CAAC,cAAc,EAAE,OAAO,CAAC,cAAc,CAAC,GAAG,QAAQ;IAkBvE,SAAgB,OAAO,mBAA0B;IACjD,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAmC;IAClE,OAAO,CAAC,YAAY,CAAC,CAAgC;IACrD,OAAO,CAAC,OAAO,CAAS;gBAEP,MAAM,EAAE,cAAc;IAyBhC,kBAAkB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpF,MAAM,EAAE,SAAS,CAAC,iBAAiB,EAAE,WAAW,GAAG,SAAS,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC,CAAA;KAAE,GAC5E,YAAY,CAAC,CAAC,CAAC;IAsClB,IAAW,aAAa,IAAI,WAAW,CAAC,MAAM,EAAE,YAAY,CAAC,CAE5D;IAED,IAAW,MAAM,YAEhB;IAEM,KAAK;IAWZ,OAAO,CAAC,YAAY;CAepB"}
package/lib/Observer.js CHANGED
@@ -38,7 +38,9 @@ class Observer extends events_1.EventEmitter {
38
38
  this.reports = new ReportsCollector_1.ReportsCollector();
39
39
  this._observedCalls = new Map();
40
40
  this._closed = false;
41
+ this.setMaxListeners(Infinity);
41
42
  logger.debug('Observer is created with config', this.config);
43
+ const onReports = (context) => this.emit('reports', context);
42
44
  const onNewReport = (collectedReports) => {
43
45
  if (!this.config.maxReports || this._closed)
44
46
  return;
@@ -47,8 +49,12 @@ class Observer extends events_1.EventEmitter {
47
49
  }
48
50
  };
49
51
  this._emitReports();
50
- this.once('close', () => this.reports.off('newreport', onNewReport));
52
+ this.once('close', () => {
53
+ this.reports.off('newreport', onNewReport);
54
+ this.reports.off('reports', onReports);
55
+ });
51
56
  this.reports.on('newreport', onNewReport);
57
+ this.reports.on('reports', onReports);
52
58
  }
53
59
  createObservedCall(config) {
54
60
  if (this._closed) {
@@ -80,6 +86,7 @@ class Observer extends events_1.EventEmitter {
80
86
  return logger.debug('Attempted to close twice');
81
87
  }
82
88
  this._closed = true;
89
+ this._observedCalls.forEach((call) => call.close());
83
90
  this.emit('close');
84
91
  }
85
92
  _emitReports() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@observertc/observer-js",
3
- "version": "0.40.0-beta",
3
+ "version": "0.40.2-beta",
4
4
  "description": "Server Side NodeJS Library for processing ObserveRTC Samples",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
@@ -38,6 +38,7 @@ export class ObservedCall<AppData extends Record<string, unknown> = Record<strin
38
38
  public readonly appData: AppData,
39
39
  ) {
40
40
  super();
41
+ this.setMaxListeners(Infinity);
41
42
  }
42
43
 
43
44
  public get serviceId(): string {
@@ -85,10 +86,10 @@ export class ObservedCall<AppData extends Record<string, unknown> = Record<strin
85
86
  this.emit('close');
86
87
  }
87
88
 
88
- public createClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(config: ObservedClientModel & { appData: ClientAppData, generateClientJoinedReport?: boolean }) {
89
+ public createClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(config: ObservedClientModel & { appData: ClientAppData, generateClientJoinedReport?: boolean, joined?: number }) {
89
90
  if (this._closed) throw new Error(`Call ${this.callId} is closed`);
90
91
 
91
- const { appData, generateClientJoinedReport, ...model } = config;
92
+ const { appData, generateClientJoinedReport, joined = Date.now(), ...model } = config;
92
93
 
93
94
  const result = new ObservedClient(model, this, appData);
94
95
 
@@ -104,7 +105,7 @@ export class ObservedCall<AppData extends Record<string, unknown> = Record<strin
104
105
  this.roomId,
105
106
  this.callId,
106
107
  result.clientId,
107
- Date.now(),
108
+ joined,
108
109
  result.userId,
109
110
  result.marker,
110
111
  ));
@@ -14,18 +14,12 @@ const logger = createLogger('ObservedClient');
14
14
  export type ObservedClientModel= {
15
15
  clientId: string;
16
16
  mediaUnitId: string;
17
- overflowingProcessingThreshold?: number;
18
17
  userId?: string,
19
18
  marker?: string,
20
19
  operationSystem?: OperationSystem,
21
20
  engine?: Engine,
22
21
  platform?: Platform,
23
22
  browser?: Browser,
24
-
25
- /**
26
- * before client-monitor-js 3.4.0 the client joined event was generated by the observer, this flag is to keep the compatibility with the old behavior
27
- */
28
- addJoinAndDetachClientEvent?: boolean;
29
23
  };
30
24
 
31
25
  export type ObservedClientEvents = {
@@ -101,6 +95,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
101
95
  public readonly appData: AppData
102
96
  ) {
103
97
  super();
98
+ this.setMaxListeners(Infinity);
104
99
  }
105
100
 
106
101
  public get clientId(): string {
@@ -194,6 +189,8 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
194
189
  if (sample.clientId && sample.clientId !== 'NULL' && sample.clientId !== this.clientId) {
195
190
  throw new Error(`Sample client id (${sample.clientId}) does not match the client id of the observed client (${this.clientId})`);
196
191
  }
192
+ const now = Date.now();
193
+
197
194
  ++this._acceptedSample;
198
195
 
199
196
  for (const peerConnection of this._peerConnections.values()) {
@@ -612,34 +609,60 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
612
609
  peerConnection.ICE.update(candidatePair, sample.timestamp);
613
610
  }
614
611
 
612
+ for (const dataChannel of sample.dataChannels ?? []) {
613
+ if (!dataChannel.peerConnectionId || !dataChannel.dataChannelIdentifier) {
614
+ logger.warn(`DataChannel without peerConnectionId or dataChannelIdentifier: ${JSON.stringify(dataChannel)}`);
615
+
616
+ continue;
617
+ }
618
+
619
+ try {
620
+ const peerConnection = this._peerConnections.get(dataChannel.peerConnectionId) ?? this._createPeerConnection(dataChannel.peerConnectionId);
621
+
622
+ if (!peerConnection) continue;
623
+
624
+ const observedDataChannel = peerConnection.dataChannels.get(dataChannel.dataChannelIdentifier) ?? peerConnection.createDataChannel(dataChannel.dataChannelIdentifier);
625
+
626
+ observedDataChannel.update(dataChannel, sample.timestamp);
627
+ } catch (err) {
628
+ logger.error(`Error creating data channel: ${(err as Error)?.message}`);
629
+ }
630
+ }
631
+
615
632
  // close entries that are idle for too long
616
633
  if (this.call.observer.config.maxEntryIdleTimeInMs && this._updated < sample.timestamp) {
617
634
 
618
635
  for (const peerConnection of this._peerConnections.values()) {
619
- if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - peerConnection.updated) {
636
+ if (this.call.observer.config.maxEntryIdleTimeInMs < peerConnection.created - peerConnection.updated) {
620
637
  logger.debug(`Closing peer connection ${peerConnection.peerConnectionId} due to inactivity`);
621
638
  peerConnection.close();
622
639
 
623
640
  continue;
624
641
  }
625
642
  for (const track of peerConnection.inboundAudioTracks.values()) {
626
- if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - track.updated) {
643
+ if (this.call.observer.config.maxEntryIdleTimeInMs < track.created - track.updated) {
627
644
  logger.debug(`Closing inbound audio track ${track.trackId} due to inactivity`);
628
645
  track.close();
629
646
  }
630
647
  }
631
648
  for (const track of peerConnection.inboundVideoTracks.values()) {
632
- if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - track.updated) {
649
+ if (this.call.observer.config.maxEntryIdleTimeInMs < track.created - track.updated) {
633
650
  logger.debug(`Closing inbound video track ${track.trackId} due to inactivity`);
634
651
  track.close();
635
652
  }
636
653
  }
637
654
  for (const track of peerConnection.outboundAudioTracks.values()) {
638
- if (this.call.observer.config.maxEntryIdleTimeInMs < sample.timestamp - track.updated) {
655
+ if (this.call.observer.config.maxEntryIdleTimeInMs < track.created - track.updated) {
639
656
  logger.debug(`Closing outbound audio track ${track.trackId} due to inactivity`);
640
657
  track.close();
641
658
  }
642
659
  }
660
+ for (const dataChannel of peerConnection.dataChannels.values()) {
661
+ if (this.call.observer.config.maxEntryIdleTimeInMs < dataChannel.created - dataChannel.updated) {
662
+ logger.debug(`Closing DataChannel ${dataChannel.channelId} due to inactivity`);
663
+ dataChannel.close();
664
+ }
665
+ }
643
666
  }
644
667
  }
645
668
 
@@ -683,7 +706,7 @@ export class ObservedClient<AppData extends Record<string, unknown> = Record<str
683
706
 
684
707
  if (wasUsingTURN !== this.usingTURN) this.emit('usingturn', this.usingTURN);
685
708
 
686
- this._updated = Date.now();
709
+ this._updated = now;
687
710
  this.emit('update');
688
711
  }
689
712
 
@@ -0,0 +1,187 @@
1
+ import { EventEmitter } from 'events';
2
+ import { ObservedPeerConnection } from './ObservedPeerConnection';
3
+ import { DataChannel } from '@observertc/sample-schemas-js';
4
+ import { ClientDataChannelReport } from '@observertc/report-schemas-js';
5
+
6
+ export type ObservedDataChannelState = 'connecting' | 'open' | 'closing' | 'closed';
7
+
8
+ export type ObservedDataChannelModel = {
9
+ channelId: number;
10
+ }
11
+
12
+ export type ObservedDataChannelEvents = {
13
+ update: [],
14
+ close: [],
15
+ };
16
+
17
+ export type ObservedInboundTrackStats = {
18
+ ssrc: number;
19
+ bitrate: number;
20
+ fractionLost: number;
21
+ rttInMs?: number;
22
+ lostPackets?: number;
23
+ receivedPackets?: number;
24
+ receivedFrames?: number;
25
+ decodedFrames?: number;
26
+ droppedFrames?: number;
27
+ receivedSamples?: number;
28
+ silentConcealedSamples?: number;
29
+ fractionLoss?: number;
30
+ };
31
+
32
+ // {
33
+ // [Property in keyof ObservedInboundTrackStats<K>]: ObservedInboundTrackStats<K>[Property];
34
+ // }
35
+
36
+ export declare interface ObservedDataChannel {
37
+ on<U extends keyof ObservedDataChannelEvents>(event: U, listener: (...args: ObservedDataChannelEvents[U]) => void): this;
38
+ off<U extends keyof ObservedDataChannelEvents>(event: U, listener: (...args: ObservedDataChannelEvents[U]) => void): this;
39
+ once<U extends keyof ObservedDataChannelEvents>(event: U, listener: (...args: ObservedDataChannelEvents[U]) => void): this;
40
+ emit<U extends keyof ObservedDataChannelEvents>(event: U, ...args: ObservedDataChannelEvents[U]): boolean;
41
+ update(sample: DataChannel, timestamp: number): void;
42
+ }
43
+
44
+ export class ObservedDataChannel extends EventEmitter {
45
+ public readonly created = Date.now();
46
+
47
+ private _stats?: DataChannel;
48
+
49
+ private _closed = false;
50
+ private _updated = Date.now();
51
+ public bitrate = 0;
52
+
53
+ public totalReceivedMessages = 0;
54
+ public totalSentMessages = 0;
55
+ public totalBytesReceived = 0;
56
+ public totalBytesSent = 0;
57
+
58
+ public deltaReceivedMessages = 0;
59
+ public deltaSentMessages = 0;
60
+ public deltaBytesReceived = 0;
61
+ public deltaBytesSent = 0;
62
+
63
+ public constructor(
64
+ private readonly _model: ObservedDataChannelModel,
65
+ public readonly peerConnection: ObservedPeerConnection,
66
+ ) {
67
+ super();
68
+ this.setMaxListeners(Infinity);
69
+ }
70
+
71
+ public get serviceId() {
72
+ return this.peerConnection.serviceId;
73
+ }
74
+
75
+ public get roomId() {
76
+ return this.peerConnection.roomId;
77
+ }
78
+
79
+ public get callId() {
80
+ return this.peerConnection.callId;
81
+ }
82
+
83
+ public get clientId() {
84
+ return this.peerConnection.clientId;
85
+ }
86
+
87
+ public get mediaUnitId() {
88
+ return this.peerConnection.mediaUnitId;
89
+ }
90
+
91
+ public get peerConnectionId() {
92
+ return this.peerConnection.peerConnectionId;
93
+ }
94
+
95
+ public get channelId() {
96
+ return this._model.channelId;
97
+ }
98
+
99
+ public get updated() {
100
+ return this._updated;
101
+ }
102
+
103
+ public get state(): ObservedDataChannelState | undefined {
104
+ return this._stats?.state as ObservedDataChannelState | undefined;
105
+ }
106
+
107
+ public get label() {
108
+ return this._stats?.label;
109
+ }
110
+
111
+ public get protocol() {
112
+ return this._stats?.protocol;
113
+ }
114
+
115
+ public get reports() {
116
+ return this.peerConnection.reports;
117
+ }
118
+
119
+ public get stats() {
120
+ return this._stats;
121
+ }
122
+
123
+ public get closed() {
124
+ return this._closed;
125
+ }
126
+
127
+ public close() {
128
+ if (this._closed) return;
129
+
130
+ this._closed = true;
131
+
132
+ this.emit('close');
133
+ }
134
+
135
+ public resetMetrics() {
136
+ this.bitrate = 0;
137
+ this.totalReceivedMessages = 0;
138
+ this.totalSentMessages = 0;
139
+ this.totalBytesReceived = 0;
140
+ this.totalBytesSent = 0;
141
+ this.deltaReceivedMessages = 0;
142
+ this.deltaSentMessages = 0;
143
+ this.deltaBytesReceived = 0;
144
+ }
145
+
146
+ public update(sample: DataChannel, timestamp: number) {
147
+ if (this._closed) return;
148
+
149
+ const now = Date.now();
150
+
151
+ const report: ClientDataChannelReport = {
152
+ serviceId: this.peerConnection.client.call.serviceId,
153
+ roomId: this.peerConnection.client.call.roomId,
154
+ callId: this.peerConnection.client.call.callId,
155
+ clientId: this.peerConnection.client.clientId,
156
+ userId: this.peerConnection.client.userId,
157
+ mediaUnitId: this.peerConnection.client.mediaUnitId,
158
+ ...sample,
159
+ timestamp,
160
+ sampleSeq: -1,
161
+ };
162
+
163
+ this.reports.addClientDataChannelReport(report);
164
+
165
+ sample.bytesReceived;
166
+ sample.bytesSent;
167
+ sample.messageReceived;
168
+ sample.messageSent;
169
+
170
+ const elapsedTimeInMs = Math.max(1, now - this._updated);
171
+
172
+ this.bitrate = ((sample.bytesReceived ?? 0) - (this._stats?.bytesReceived ?? 0)) * 8 / (elapsedTimeInMs / 1000);
173
+ this.deltaSentMessages = (sample.messageSent ?? 0) - (this._stats?.messageSent ?? 0);
174
+ this.deltaReceivedMessages = (sample.messageReceived ?? 0) - (this._stats?.messageReceived ?? 0);
175
+ this.deltaBytesSent = (sample.bytesSent ?? 0) - (this._stats?.bytesSent ?? 0);
176
+ this.deltaBytesReceived = (sample.bytesReceived ?? 0) - (this._stats?.bytesReceived ?? 0);
177
+
178
+ this.totalBytesReceived = sample.bytesReceived ?? 0;
179
+ this.totalBytesSent = sample.bytesSent ?? 0;
180
+ this.totalReceivedMessages = sample.messageReceived ?? 0;
181
+ this.totalSentMessages = sample.messageSent ?? 0;
182
+
183
+ this._stats = sample;
184
+ this._updated = timestamp;
185
+ this.emit('update');
186
+ }
187
+ }
@@ -40,6 +40,7 @@ export class ObservedICE extends EventEmitter {
40
40
  public readonly peerConnection: ObservedPeerConnection,
41
41
  ) {
42
42
  super();
43
+ this.setMaxListeners(Infinity);
43
44
  }
44
45
 
45
46
  public get reports() {
@@ -147,7 +148,7 @@ export class ObservedICE extends EventEmitter {
147
148
  if (!candidatePair.nominated) return;
148
149
 
149
150
  this._stats = candidatePair;
150
-
151
+
151
152
  if (
152
153
  candidatePair.localCandidateId &&
153
154
  candidatePair.localCandidateId !== this._selectedLocalCandidateId &&
@@ -82,6 +82,7 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
82
82
  public readonly peerConnection: ObservedPeerConnection,
83
83
  ) {
84
84
  super();
85
+ this.setMaxListeners(Infinity);
85
86
  }
86
87
 
87
88
  public get serviceId() {
@@ -136,10 +137,6 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
136
137
  return this.peerConnection.reports;
137
138
  }
138
139
 
139
- public get uptimeInMs() {
140
- return this._updated - this.created;
141
- }
142
-
143
140
  public set remoteOutboundTrack(track: ObservedOutboundTrack<Kind> | undefined) {
144
141
  if (this._closed) return;
145
142
  if (this._remoteOutboundTrack) return;
@@ -182,7 +179,8 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
182
179
 
183
180
  public update(sample: ObservedInboundTrackStatsUpdate<Kind>, timestamp: number) {
184
181
  if (this._closed) return;
185
-
182
+
183
+ const now = Date.now();
186
184
  const report: InboundAudioTrackReport | InboundVideoTrackReport = {
187
185
  serviceId: this.peerConnection.client.call.serviceId,
188
186
  roomId: this.peerConnection.client.call.roomId,
@@ -212,7 +210,7 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
212
210
  this._model.sfuSinkId = sample.sfuSinkId;
213
211
  }
214
212
 
215
- const elapsedTimeInMs = Math.max(1, timestamp - this._updated);
213
+ const elapsedTimeInMs = Math.max(1, now - this._updated);
216
214
  const lastStat = this._stats.get(sample.ssrc);
217
215
  const rttInMs = sample.roundTripTime ? sample.roundTripTime * 1000 : undefined;
218
216
  const bitrate = ((sample.bytesReceived ?? 0) - (lastStat?.bytesReceived ?? 0)) * 8 / (elapsedTimeInMs / 1000);
@@ -275,7 +273,7 @@ export class ObservedInboundTrack<Kind extends MediaKind> extends EventEmitter {
275
273
  this.deltaSilentConcealedSamples = [ ...this._stats.values() ].reduce((acc, stat) => acc + (stat.silentConcealedSamples ?? 0), 0);
276
274
  this.fractionLoss = 0 < this.deltaReceivedPackets && 0 < this.deltaLostPackets ? (this.deltaLostPackets / (this.deltaReceivedPackets + this.deltaLostPackets)) : 0;
277
275
 
278
- this._updated = timestamp;
276
+ this._updated = now;
279
277
  this.emit('update');
280
278
  }
281
279
  }
@@ -70,6 +70,7 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
70
70
  public readonly peerConnection: ObservedPeerConnection,
71
71
  ) {
72
72
  super();
73
+ this.setMaxListeners(Infinity);
73
74
  }
74
75
 
75
76
  public get serviceId() {
@@ -124,10 +125,6 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
124
125
  return this._stats;
125
126
  }
126
127
 
127
- public get uptimeInMs() {
128
- return this._updated - this.created;
129
- }
130
-
131
128
  public get closed() {
132
129
  return this._closed;
133
130
  }
@@ -152,7 +149,8 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
152
149
 
153
150
  public update(sample: ObservedOutboundTrackStatsUpdate<Kind>, timestamp: number): void {
154
151
  if (this._closed) return;
155
-
152
+
153
+ const now = Date.now();
156
154
  const report: OutboundAudioTrackReport | OutboundVideoTrackReport = {
157
155
  serviceId: this.peerConnection.client.serviceId,
158
156
  roomId: this.peerConnection.client.call.roomId,
@@ -169,7 +167,7 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
169
167
  if (this.kind === 'audio') this.reports.addOutboundAudioTrackReport(report);
170
168
  else if (this.kind === 'video') this.reports.addOutboundVideoTrackReport(report);
171
169
 
172
- const elapsedTimeInMs = Math.max(1, timestamp - this._updated);
170
+ const elapsedTimeInMs = Math.max(1, now - this._updated);
173
171
  const lastStat = this._stats.get(sample.ssrc);
174
172
  const rttInMs = sample.roundTripTime ? sample.roundTripTime * 1000 : undefined;
175
173
  const bitrate = ((sample.bytesSent ?? 0) - (lastStat?.bytesSent ?? 0)) * 8 / (elapsedTimeInMs / 1000);
@@ -228,7 +226,7 @@ export class ObservedOutboundTrack<Kind extends MediaKind> extends EventEmitter
228
226
  else if (this.kind === 'video') this.peerConnection.client.call.sfuStreamIdToOutboundVideoTrack.set(this._model.sfuStreamId, this as ObservedOutboundTrack<'video'>);
229
227
  }
230
228
 
231
- this._updated = timestamp;
229
+ this._updated = now;
232
230
  this.emit('update');
233
231
  }
234
232
 
@@ -5,6 +5,7 @@ import { ObservedInboundTrack, ObservedInboundTrackModel } from './ObservedInbou
5
5
  import { ObservedOutboundTrack, ObservedOutboundTrackModel } from './ObservedOutboundTrack';
6
6
  import { PeerConnectionTransportReport } from '@observertc/report-schemas-js';
7
7
  import { ObservedICE } from './ObservedICE';
8
+ import { ObservedDataChannel } from './ObservedDataChannel';
8
9
 
9
10
  export type ObservedPeerConnectionEvents = {
10
11
  update: [],
@@ -77,12 +78,14 @@ export class ObservedPeerConnection extends EventEmitter {
77
78
  private readonly _inboundVideoTracks = new Map<string, ObservedInboundTrack<'video'>>();
78
79
  private readonly _outboundAudioTracks = new Map<string, ObservedOutboundTrack<'audio'>>();
79
80
  private readonly _outboundVideoTracks = new Map<string, ObservedOutboundTrack<'video'>>();
81
+ private readonly _dataChannels = new Map<number, ObservedDataChannel>();
80
82
 
81
83
  public constructor(
82
84
  private readonly _model: ObservedPeerConnectionModel,
83
85
  public readonly client: ObservedClient,
84
86
  ) {
85
87
  super();
88
+ this.setMaxListeners(Infinity);
86
89
  }
87
90
 
88
91
  public get serviceId() {
@@ -158,6 +161,10 @@ export class ObservedPeerConnection extends EventEmitter {
158
161
  return this._outboundVideoTracks;
159
162
  }
160
163
 
164
+ public get dataChannels(): ReadonlyMap<number, ObservedDataChannel> {
165
+ return this._dataChannels;
166
+ }
167
+
161
168
  public get uptimeInMs() {
162
169
  return this._updated - this.created;
163
170
  }
@@ -285,6 +292,14 @@ export class ObservedPeerConnection extends EventEmitter {
285
292
 
286
293
  sumRttInMs = (track.rttInMs ?? 0);
287
294
  });
295
+
296
+ this._dataChannels.forEach((channel) => {
297
+ this.deltaDataChannelBytesSent += channel.deltaBytesSent;
298
+ this.deltaDataChannelBytesReceived += channel.deltaBytesReceived;
299
+ this.totalDataChannelBytesSent += channel.totalBytesSent;
300
+ this.totalDataChannelBytesReceived += channel.totalBytesReceived;
301
+ });
302
+
288
303
  const iceRttInMs = this.ICE.stats?.currentRoundTripTime;
289
304
  let nrOfBelongings = this._inboundAudioTracks.size + this._inboundVideoTracks.size + this._outboundAudioTracks.size + this._outboundVideoTracks.size;
290
305
 
@@ -393,4 +408,19 @@ export class ObservedPeerConnection extends EventEmitter {
393
408
 
394
409
  return result;
395
410
  }
411
+
412
+ public createDataChannel(channelId: number) {
413
+ if (this._closed) throw new Error(`PeerConnection ${this.peerConnectionId} is closed`);
414
+
415
+ const result = new ObservedDataChannel({
416
+ channelId,
417
+ }, this);
418
+
419
+ result.on('close', () => {
420
+ this._dataChannels.delete(result.channelId);
421
+ });
422
+ this._dataChannels.set(result.channelId, result);
423
+
424
+ return result;
425
+ }
396
426
  }
@@ -32,6 +32,7 @@ export class ObservedSfu<AppData extends Record<string, unknown> = Record<string
32
32
  public readonly appData: AppData,
33
33
  ) {
34
34
  super();
35
+ this.setMaxListeners(Infinity);
35
36
  }
36
37
 
37
38
  public get serviceId() {