@observertc/observer-js 1.2.0 → 1.3.0-rc.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 +149 -86
- package/dist/index.d.mts +237 -92
- package/dist/index.d.ts +237 -92
- package/dist/index.js +342 -150
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +340 -148
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -15
package/dist/index.mjs
CHANGED
|
@@ -37,18 +37,189 @@ function createLogger(moduleName) {
|
|
|
37
37
|
}
|
|
38
38
|
}();
|
|
39
39
|
}
|
|
40
|
-
function setObserverLogger(
|
|
41
|
-
mainLogger =
|
|
40
|
+
function setObserverLogger(logger13) {
|
|
41
|
+
mainLogger = logger13;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
// src/ObservedCall.ts
|
|
45
|
-
import { EventEmitter as
|
|
45
|
+
import { EventEmitter as EventEmitter4 } from "events";
|
|
46
|
+
|
|
47
|
+
// src/sinks/SinkRegistry.ts
|
|
48
|
+
import { v4 as uuidv4 } from "uuid";
|
|
49
|
+
var logger = createLogger("SinkRegistry");
|
|
50
|
+
var SinkRegistry = class {
|
|
51
|
+
constructor(parent) {
|
|
52
|
+
this.parent = parent;
|
|
53
|
+
}
|
|
54
|
+
parent;
|
|
55
|
+
// This registry's sinks and its descendants'.
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
57
|
+
_sinks = /* @__PURE__ */ new Map();
|
|
58
|
+
/** Number of open sinks in this registry and its descendants. */
|
|
59
|
+
get size() {
|
|
60
|
+
return this._sinks.size;
|
|
61
|
+
}
|
|
62
|
+
keys() {
|
|
63
|
+
return this._sinks.keys();
|
|
64
|
+
}
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
66
|
+
values() {
|
|
67
|
+
return this._sinks.values();
|
|
68
|
+
}
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
70
|
+
get(id) {
|
|
71
|
+
return this._sinks.get(id);
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Construct `new SinkClass(settings)` and register it. The id (`settings.id`, generated when
|
|
75
|
+
* omitted) is checked first, so nothing is constructed — no file is opened — when it is taken:
|
|
76
|
+
* `undefined` is returned and a warning logged.
|
|
77
|
+
*/
|
|
78
|
+
create(SinkClass, settings) {
|
|
79
|
+
const lineage = this._lineage();
|
|
80
|
+
const id = settings.id ?? uuidv4();
|
|
81
|
+
if (lineage[lineage.length - 1]._sinks.has(id)) {
|
|
82
|
+
logger.warn("Sink id %s is already in use; sink ids must be unique per observer. No sink was created", id);
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
const sink = new SinkClass({ ...settings, id });
|
|
86
|
+
const onError = (err) => logger.warn("Sink %s failed: %o", id, err);
|
|
87
|
+
sink.on("error", onError);
|
|
88
|
+
sink.once("close", () => sink.off("error", onError));
|
|
89
|
+
sink.once("ended", () => {
|
|
90
|
+
for (const registry of lineage) {
|
|
91
|
+
if (registry._sinks.get(id) === sink) registry._sinks.delete(id);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
for (const registry of lineage) registry._sinks.set(id, sink);
|
|
95
|
+
return sink;
|
|
96
|
+
}
|
|
97
|
+
/** Write `record` to the sink `id`. `false` when there is no such sink, or on backpressure. */
|
|
98
|
+
write(id, record) {
|
|
99
|
+
const sink = this._sinks.get(id);
|
|
100
|
+
if (!sink) {
|
|
101
|
+
logger.debug("No sink %s; record dropped", id);
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return sink.write(record);
|
|
105
|
+
}
|
|
106
|
+
/** End every sink of this registry and of its descendants. */
|
|
107
|
+
clear() {
|
|
108
|
+
for (const sink of [...this._sinks.values()]) {
|
|
109
|
+
try {
|
|
110
|
+
sink.end();
|
|
111
|
+
} catch (err) {
|
|
112
|
+
logger.warn("Sink %s failed to end: %o", sink.id, err);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** This registry and its ancestors, up to the root. */
|
|
117
|
+
_lineage() {
|
|
118
|
+
return [this, ...this.parent?._lineage() ?? []];
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// src/sinks/ExtensionStatsRegistry.ts
|
|
123
|
+
var logger2 = createLogger("ExtensionStats");
|
|
124
|
+
var ExtensionStatsRegistry = class {
|
|
125
|
+
constructor(id, _sinks) {
|
|
126
|
+
this.id = id;
|
|
127
|
+
this._sinks = _sinks;
|
|
128
|
+
}
|
|
129
|
+
id;
|
|
130
|
+
_sinks;
|
|
131
|
+
/** The last data accepted for each extension stats id. */
|
|
132
|
+
values = /* @__PURE__ */ new Map();
|
|
133
|
+
closed = false;
|
|
134
|
+
/**
|
|
135
|
+
* Refuse further `create` / `accept`, and end the extension stats' sinks (the sinks whose id is
|
|
136
|
+
* an extension stats id). Other sinks of the owner, e.g. client sample sinks, are left to the
|
|
137
|
+
* owner's `sinks.clear()`. Called by the owner when it closes.
|
|
138
|
+
*/
|
|
139
|
+
close() {
|
|
140
|
+
if (this.closed) return;
|
|
141
|
+
this.closed = true;
|
|
142
|
+
for (const id of this.values.keys()) {
|
|
143
|
+
this._sinks.get(id)?.end();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
create(settings) {
|
|
147
|
+
if (this.closed) {
|
|
148
|
+
logger2.warn("Attempted to create extension stats %s on closed %s", settings.id, this.id);
|
|
149
|
+
return void 0;
|
|
150
|
+
}
|
|
151
|
+
if (this.values.has(settings.id)) {
|
|
152
|
+
logger2.warn("Extension stats with id %s already exists on %s", settings.id, this.id);
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
this.values.set(settings.id, void 0);
|
|
156
|
+
if (!settings.createSink) return void 0;
|
|
157
|
+
const [SinkClass, sinkSettings] = settings.createSink;
|
|
158
|
+
this._sinks.get(settings.id)?.end();
|
|
159
|
+
return this._sinks.create(SinkClass, { ...sinkSettings, id: settings.id });
|
|
160
|
+
}
|
|
161
|
+
accept(id, data) {
|
|
162
|
+
if (this.closed) {
|
|
163
|
+
logger2.debug("Extension stats %s dropped: %s is closed", id, this.id);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
this.values.set(id, data);
|
|
167
|
+
try {
|
|
168
|
+
this._sinks.get(id)?.write(data);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
logger2.warn("Failed to write extension stats %s to sink: %o", id, err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
get(id) {
|
|
174
|
+
return this.values.get(id);
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
// src/sinks/JsonlFileSink.ts
|
|
179
|
+
import * as fs from "fs";
|
|
180
|
+
import { EventEmitter } from "events";
|
|
181
|
+
var JsonlFileSink = class extends EventEmitter {
|
|
182
|
+
id;
|
|
183
|
+
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
184
|
+
path;
|
|
185
|
+
_stream;
|
|
186
|
+
_serializeSample;
|
|
187
|
+
_ended = false;
|
|
188
|
+
constructor(options) {
|
|
189
|
+
super();
|
|
190
|
+
this.id = options.id ?? options.path;
|
|
191
|
+
this.path = options.path;
|
|
192
|
+
this._stream = fs.createWriteStream(this.path, { flags: "a" });
|
|
193
|
+
this._stream.on("close", () => this.emit("close"));
|
|
194
|
+
this._stream.on("finish", () => this.emit("finish"));
|
|
195
|
+
this._stream.on("drain", () => this.emit("drain"));
|
|
196
|
+
this._stream.on("error", (err) => this.emit("error", err));
|
|
197
|
+
this._serializeSample = options.serializeSample ?? ((record) => JSON.stringify(record));
|
|
198
|
+
}
|
|
199
|
+
get ended() {
|
|
200
|
+
return this._ended;
|
|
201
|
+
}
|
|
202
|
+
write(record) {
|
|
203
|
+
if (this._ended) return false;
|
|
204
|
+
return this._stream.write(`${this._serializeSample(record)}
|
|
205
|
+
`);
|
|
206
|
+
}
|
|
207
|
+
end() {
|
|
208
|
+
if (this._ended) return;
|
|
209
|
+
this._ended = true;
|
|
210
|
+
this.emit("ended");
|
|
211
|
+
this._stream.end();
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
function createJsonlFileSink(options) {
|
|
215
|
+
return new JsonlFileSink(options);
|
|
216
|
+
}
|
|
46
217
|
|
|
47
218
|
// src/ObservedClient.ts
|
|
48
|
-
import { EventEmitter as
|
|
219
|
+
import { EventEmitter as EventEmitter3 } from "events";
|
|
49
220
|
|
|
50
221
|
// src/ObservedPeerConnection.ts
|
|
51
|
-
import { EventEmitter } from "events";
|
|
222
|
+
import { EventEmitter as EventEmitter2 } from "events";
|
|
52
223
|
|
|
53
224
|
// src/utils/stats.ts
|
|
54
225
|
function percentile(values, p) {
|
|
@@ -205,8 +376,9 @@ function percentileOfHistogram(histogram, bucketWidth, p, options = {}) {
|
|
|
205
376
|
continue;
|
|
206
377
|
}
|
|
207
378
|
if (options.openEnded && index === histogram.length - 1 && options.max !== void 0) return options.max;
|
|
208
|
-
const
|
|
209
|
-
|
|
379
|
+
const lower = Math.max(index * bucketWidth, options.min ?? -Infinity);
|
|
380
|
+
const upper = Math.min((index + 1) * bucketWidth, options.max ?? Infinity);
|
|
381
|
+
return lower + (rank - cumulative) / count * Math.max(0, upper - lower);
|
|
210
382
|
}
|
|
211
383
|
return options.max;
|
|
212
384
|
}
|
|
@@ -1740,14 +1912,14 @@ var ObservedOutboundTrack = class {
|
|
|
1740
1912
|
|
|
1741
1913
|
// src/ObservedPeerConnection.ts
|
|
1742
1914
|
var ascending2 = (a, b) => a - b;
|
|
1743
|
-
var
|
|
1915
|
+
var logger3 = createLogger("ObservedPeerConnection");
|
|
1744
1916
|
function publicAddressOfLocalCandidate(candidate) {
|
|
1745
1917
|
if (!candidate) return void 0;
|
|
1746
1918
|
if (candidate.candidateType !== "relay" && isPublicIpAddress(candidate.address)) return candidate.address;
|
|
1747
1919
|
if (isPublicIpAddress(candidate.relatedAddress)) return candidate.relatedAddress;
|
|
1748
1920
|
return void 0;
|
|
1749
1921
|
}
|
|
1750
|
-
var ObservedPeerConnection = class extends
|
|
1922
|
+
var ObservedPeerConnection = class extends EventEmitter2 {
|
|
1751
1923
|
constructor(peerConnectionId, client) {
|
|
1752
1924
|
super();
|
|
1753
1925
|
this.peerConnectionId = peerConnectionId;
|
|
@@ -2329,7 +2501,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2329
2501
|
let observedCertificate = this.observedCertificates.get(stats.id);
|
|
2330
2502
|
if (!observedCertificate) {
|
|
2331
2503
|
if (!stats.timestamp || !stats.id || !stats.fingerprint) {
|
|
2332
|
-
return
|
|
2504
|
+
return logger3.warn(
|
|
2333
2505
|
`ObservedPeerConnection received an invalid CertificateStats (missing timestamp OR id OR fingerprint field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2334
2506
|
stats
|
|
2335
2507
|
);
|
|
@@ -2348,7 +2520,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2348
2520
|
let observedCodec = this.observedCodecs.get(stats.id);
|
|
2349
2521
|
if (!observedCodec) {
|
|
2350
2522
|
if (!stats.timestamp || !stats.id || !stats.mimeType) {
|
|
2351
|
-
return
|
|
2523
|
+
return logger3.warn(
|
|
2352
2524
|
`ObservedPeerConnection received an invalid CodecStats (missing timestamp OR id OR mimeType field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2353
2525
|
stats
|
|
2354
2526
|
);
|
|
@@ -2367,7 +2539,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2367
2539
|
let observedDataChannel = this.observedDataChannels.get(stats.id);
|
|
2368
2540
|
if (!observedDataChannel) {
|
|
2369
2541
|
if (!stats.timestamp || !stats.id) {
|
|
2370
|
-
return
|
|
2542
|
+
return logger3.warn(
|
|
2371
2543
|
`ObservedPeerConnection received an invalid DataChannelStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2372
2544
|
stats
|
|
2373
2545
|
);
|
|
@@ -2386,7 +2558,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2386
2558
|
let observedIceCandidate = this.observedIceCandidates.get(stats.id);
|
|
2387
2559
|
if (!observedIceCandidate) {
|
|
2388
2560
|
if (!stats.timestamp || !stats.id) {
|
|
2389
|
-
return
|
|
2561
|
+
return logger3.warn(
|
|
2390
2562
|
`ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2391
2563
|
stats
|
|
2392
2564
|
);
|
|
@@ -2405,7 +2577,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2405
2577
|
let observedIceCandidatePair = this.observedIceCandidatesPair.get(stats.id);
|
|
2406
2578
|
if (!observedIceCandidatePair) {
|
|
2407
2579
|
if (!stats.timestamp || !stats.id) {
|
|
2408
|
-
return
|
|
2580
|
+
return logger3.warn(
|
|
2409
2581
|
`ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2410
2582
|
stats
|
|
2411
2583
|
);
|
|
@@ -2424,7 +2596,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2424
2596
|
let observedIceTransport = this.observedIceTransports.get(stats.id);
|
|
2425
2597
|
if (!observedIceTransport) {
|
|
2426
2598
|
if (!stats.timestamp || !stats.id) {
|
|
2427
|
-
return
|
|
2599
|
+
return logger3.warn(
|
|
2428
2600
|
`ObservedPeerConnection received an invalid IceCandidateStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2429
2601
|
stats
|
|
2430
2602
|
);
|
|
@@ -2443,7 +2615,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2443
2615
|
let observedInboundRtp = this.observedInboundRtps.get(stats.ssrc);
|
|
2444
2616
|
if (!observedInboundRtp) {
|
|
2445
2617
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind || !stats.trackIdentifier) {
|
|
2446
|
-
return
|
|
2618
|
+
return logger3.warn(
|
|
2447
2619
|
`ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR ssrc OR kind OR trackIdentifier field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2448
2620
|
stats
|
|
2449
2621
|
);
|
|
@@ -2469,7 +2641,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2469
2641
|
let observedInboundTrack = this.observedInboundTracks.get(stats.id);
|
|
2470
2642
|
if (!observedInboundTrack) {
|
|
2471
2643
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2472
|
-
return
|
|
2644
|
+
return logger3.warn(
|
|
2473
2645
|
`ObservedPeerConnection received an invalid InboundTrackSample (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2474
2646
|
stats
|
|
2475
2647
|
);
|
|
@@ -2497,7 +2669,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2497
2669
|
let observedMediaPlayout = this.observedMediaPlayouts.get(stats.id);
|
|
2498
2670
|
if (!observedMediaPlayout) {
|
|
2499
2671
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2500
|
-
return
|
|
2672
|
+
return logger3.warn(
|
|
2501
2673
|
`ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR kind OR trackIdentifier field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2502
2674
|
stats
|
|
2503
2675
|
);
|
|
@@ -2521,7 +2693,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2521
2693
|
let observedMediaSource = this.observedMediaSources.get(stats.id);
|
|
2522
2694
|
if (!observedMediaSource) {
|
|
2523
2695
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2524
|
-
return
|
|
2696
|
+
return logger3.warn(
|
|
2525
2697
|
`ObservedPeerConnection received an invalid InboundRtpStats (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2526
2698
|
stats
|
|
2527
2699
|
);
|
|
@@ -2545,7 +2717,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2545
2717
|
let observedOutboundRtp = this.observedOutboundRtps.get(stats.ssrc);
|
|
2546
2718
|
if (!observedOutboundRtp) {
|
|
2547
2719
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
|
|
2548
|
-
return
|
|
2720
|
+
return logger3.warn(
|
|
2549
2721
|
`ObservedPeerConnection received an invalid OutboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2550
2722
|
stats
|
|
2551
2723
|
);
|
|
@@ -2570,7 +2742,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2570
2742
|
let observedOutboundTrack = this.observedOutboundTracks.get(stats.id);
|
|
2571
2743
|
if (!observedOutboundTrack) {
|
|
2572
2744
|
if (!stats.timestamp || !stats.id || !stats.kind) {
|
|
2573
|
-
return
|
|
2745
|
+
return logger3.warn(
|
|
2574
2746
|
`ObservedPeerConnection received an invalid OutboundTrackSample (missing timestamp OR id OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2575
2747
|
stats
|
|
2576
2748
|
);
|
|
@@ -2598,7 +2770,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2598
2770
|
let observedPeerConnectionTransport = this.observedPeerConnectionTransports.get(stats.id);
|
|
2599
2771
|
if (!observedPeerConnectionTransport) {
|
|
2600
2772
|
if (!stats.timestamp || !stats.id) {
|
|
2601
|
-
return
|
|
2773
|
+
return logger3.warn(
|
|
2602
2774
|
`ObservedPeerConnection received an invalid PeerConnectionTransportStats (missing timestamp OR id field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2603
2775
|
stats
|
|
2604
2776
|
);
|
|
@@ -2617,7 +2789,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2617
2789
|
let observedRemoteInboundRtp = this.observedRemoteInboundRtps.get(stats.ssrc);
|
|
2618
2790
|
if (!observedRemoteInboundRtp) {
|
|
2619
2791
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
|
|
2620
|
-
return
|
|
2792
|
+
return logger3.warn(
|
|
2621
2793
|
`ObservedPeerConnection received an invalid RemoteInboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2622
2794
|
stats
|
|
2623
2795
|
);
|
|
@@ -2642,7 +2814,7 @@ var ObservedPeerConnection = class extends EventEmitter {
|
|
|
2642
2814
|
let observedRemoteOutboundRtp = this.observedRemoteOutboundRtps.get(stats.ssrc);
|
|
2643
2815
|
if (!observedRemoteOutboundRtp) {
|
|
2644
2816
|
if (!stats.timestamp || !stats.id || !stats.ssrc || !stats.kind) {
|
|
2645
|
-
return
|
|
2817
|
+
return logger3.warn(
|
|
2646
2818
|
`ObservedPeerConnection received an invalid RemoteOutboundRtpStats (missing timestamp OR id OR ssrc OR kind field). PeerConnectionId: ${this.peerConnectionId} ClientId: ${this.client.clientId}, CallId: ${this.client.call.callId}`,
|
|
2647
2819
|
stats
|
|
2648
2820
|
);
|
|
@@ -2742,35 +2914,27 @@ function baseIssueType(type) {
|
|
|
2742
2914
|
}
|
|
2743
2915
|
|
|
2744
2916
|
// src/ObservedClient.ts
|
|
2745
|
-
var
|
|
2917
|
+
var logger4 = createLogger("ObservedClient");
|
|
2746
2918
|
var RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS = 20;
|
|
2747
2919
|
var RTT_HISTOGRAM_NUMBER_OF_BUCKETS = 50;
|
|
2748
2920
|
var SCORE_HISTOGRAM_BUCKET_WIDTH = 1;
|
|
2749
2921
|
var SCORE_HISTOGRAM_NUMBER_OF_BUCKETS = 5;
|
|
2750
|
-
var ObservedClient = class extends
|
|
2922
|
+
var ObservedClient = class extends EventEmitter3 {
|
|
2751
2923
|
constructor(settings, call, activeIssues) {
|
|
2752
2924
|
super();
|
|
2753
2925
|
this.call = call;
|
|
2754
2926
|
this.setMaxListeners(Infinity);
|
|
2755
2927
|
this.eventScope = { observer: call.observer, observedCall: call, observedClient: this };
|
|
2756
|
-
this.sink = call.observer.config.createClientSink?.({ clientId: settings.clientId, observedCall: call });
|
|
2757
2928
|
this.clientId = settings.clientId;
|
|
2758
2929
|
this.appData = settings.appData ?? {};
|
|
2759
2930
|
this.settings = {
|
|
2760
2931
|
closeClientIfIdleForMs: settings.closeClientIfIdleForMs
|
|
2761
2932
|
};
|
|
2762
|
-
if (this.sink) {
|
|
2763
|
-
const onError = (err) => logger2.warn("ClientSampleSink error for client %s: %o", this.clientId, err);
|
|
2764
|
-
this.sink.once("close", () => this.sink?.off("error", onError));
|
|
2765
|
-
this.sink.on("error", onError);
|
|
2766
|
-
}
|
|
2767
2933
|
this.activeIssues = activeIssues;
|
|
2768
2934
|
}
|
|
2769
2935
|
call;
|
|
2770
2936
|
clientId;
|
|
2771
2937
|
observedPeerConnections = /* @__PURE__ */ new Map();
|
|
2772
|
-
/** Per-client sink (from `ObserverConfig.createClientSink`), if any. */
|
|
2773
|
-
sink;
|
|
2774
2938
|
/** Ancestry base shared by all Observer-bus events originating at this client. */
|
|
2775
2939
|
eventScope;
|
|
2776
2940
|
calculatedScore = {
|
|
@@ -2909,6 +3073,13 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
2909
3073
|
_closedPeerConnectionIds = /* @__PURE__ */ new Set();
|
|
2910
3074
|
closeTimer;
|
|
2911
3075
|
/** The distinct `publicAddress`es currently known by this client's open peer connections. */
|
|
3076
|
+
/**
|
|
3077
|
+
* The sink this client's samples are written to: whatever is registered under its client id in
|
|
3078
|
+
* `call.sinks` (the `createClientJsonlSink` file, or a sink the application added), if any.
|
|
3079
|
+
*/
|
|
3080
|
+
get sink() {
|
|
3081
|
+
return this.call.sinks.get(this.clientId);
|
|
3082
|
+
}
|
|
2912
3083
|
get publicAddresses() {
|
|
2913
3084
|
const result = /* @__PURE__ */ new Set();
|
|
2914
3085
|
for (const observedPeerConnection of this.observedPeerConnections.values()) {
|
|
@@ -3003,12 +3174,12 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3003
3174
|
try {
|
|
3004
3175
|
this.sink?.end();
|
|
3005
3176
|
} catch (err) {
|
|
3006
|
-
|
|
3177
|
+
logger4.warn("Sink end failed for client %s: %o", this.clientId, err);
|
|
3007
3178
|
}
|
|
3008
3179
|
}
|
|
3009
3180
|
accept(sample, context) {
|
|
3010
3181
|
if (this.closed) {
|
|
3011
|
-
|
|
3182
|
+
logger4.warn("Attempted to accept a sample on a closed client %s", this.clientId);
|
|
3012
3183
|
return;
|
|
3013
3184
|
}
|
|
3014
3185
|
if (this.closeTimer) {
|
|
@@ -3198,7 +3369,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3198
3369
|
try {
|
|
3199
3370
|
this.sink?.write(sample);
|
|
3200
3371
|
} catch (err) {
|
|
3201
|
-
|
|
3372
|
+
logger4.warn("Sink write failed for client %s: %o", this.clientId, err);
|
|
3202
3373
|
}
|
|
3203
3374
|
}
|
|
3204
3375
|
injectMetaData(metaData) {
|
|
@@ -3381,10 +3552,10 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3381
3552
|
this._notify("client-rejoined", { ...this.eventScope, timestamp: event.timestamp });
|
|
3382
3553
|
} else {
|
|
3383
3554
|
this.joinedAt = event.timestamp;
|
|
3384
|
-
|
|
3555
|
+
logger4.warn(`Client ${this.clientId} joinedAt timestamp was updated to ${event.timestamp}. the joined event will not be emitted.`);
|
|
3385
3556
|
}
|
|
3386
3557
|
}
|
|
3387
|
-
|
|
3558
|
+
logger4.debug("Client %s joined at %o", this.clientId, event);
|
|
3388
3559
|
break;
|
|
3389
3560
|
}
|
|
3390
3561
|
case "CLIENT_LEFT" /* CLIENT_LEFT */: {
|
|
@@ -3394,10 +3565,10 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3394
3565
|
this.emit("left");
|
|
3395
3566
|
this._notify("client-left", { ...this.eventScope });
|
|
3396
3567
|
} else {
|
|
3397
|
-
|
|
3568
|
+
logger4.warn(`Client ${this.clientId} leftAt timestamp was already set`);
|
|
3398
3569
|
}
|
|
3399
3570
|
}
|
|
3400
|
-
|
|
3571
|
+
logger4.debug("Client %s left at %o", this.clientId, event);
|
|
3401
3572
|
break;
|
|
3402
3573
|
}
|
|
3403
3574
|
case "PEER_CONNECTION_OPENED" /* PEER_CONNECTION_OPENED */: {
|
|
@@ -3409,7 +3580,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3409
3580
|
} else if (postBuffer) {
|
|
3410
3581
|
postBuffer.push(event);
|
|
3411
3582
|
} else {
|
|
3412
|
-
|
|
3583
|
+
logger4.warn("Received PEER_CONNECTION_OPENED event without a corresponding observedPeerConnection: %o", event);
|
|
3413
3584
|
}
|
|
3414
3585
|
}
|
|
3415
3586
|
break;
|
|
@@ -3426,7 +3597,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3426
3597
|
} else if (postBuffer) {
|
|
3427
3598
|
postBuffer.push(event);
|
|
3428
3599
|
} else {
|
|
3429
|
-
|
|
3600
|
+
logger4.warn("Received PEER_CONNECTION_CLOSED event without a corresponding observedPeerConnection: %o", event);
|
|
3430
3601
|
}
|
|
3431
3602
|
}
|
|
3432
3603
|
break;
|
|
@@ -3441,7 +3612,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3441
3612
|
} else if (postBuffer) {
|
|
3442
3613
|
postBuffer.push(event);
|
|
3443
3614
|
} else {
|
|
3444
|
-
|
|
3615
|
+
logger4.warn("Received MEDIA_TRACK_ADDED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3445
3616
|
}
|
|
3446
3617
|
}
|
|
3447
3618
|
break;
|
|
@@ -3456,7 +3627,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3456
3627
|
} else if (postBuffer) {
|
|
3457
3628
|
postBuffer.push(event);
|
|
3458
3629
|
} else {
|
|
3459
|
-
|
|
3630
|
+
logger4.warn("Received MEDIA_TRACK_REMOVED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3460
3631
|
}
|
|
3461
3632
|
}
|
|
3462
3633
|
break;
|
|
@@ -3471,7 +3642,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3471
3642
|
} else if (postBuffer) {
|
|
3472
3643
|
postBuffer.push(event);
|
|
3473
3644
|
} else {
|
|
3474
|
-
|
|
3645
|
+
logger4.warn("Received DATA_CHANNEL_OPENED event without a corresponding observedPeerConnection or observedDataChannel: %o", event);
|
|
3475
3646
|
}
|
|
3476
3647
|
}
|
|
3477
3648
|
break;
|
|
@@ -3486,7 +3657,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3486
3657
|
} else if (postBuffer) {
|
|
3487
3658
|
postBuffer.push(event);
|
|
3488
3659
|
} else {
|
|
3489
|
-
|
|
3660
|
+
logger4.warn("Received DATA_CHANNEL_CLOSE event without a corresponding observedPeerConnection or observedDataChannel: %o", event);
|
|
3490
3661
|
}
|
|
3491
3662
|
}
|
|
3492
3663
|
break;
|
|
@@ -3506,7 +3677,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3506
3677
|
} else if (postBuffer) {
|
|
3507
3678
|
postBuffer.push(event);
|
|
3508
3679
|
} else {
|
|
3509
|
-
|
|
3680
|
+
logger4.warn("Received MEDIA_TRACK_MUTED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3510
3681
|
}
|
|
3511
3682
|
}
|
|
3512
3683
|
break;
|
|
@@ -3526,7 +3697,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3526
3697
|
} else if (postBuffer) {
|
|
3527
3698
|
postBuffer.push(event);
|
|
3528
3699
|
} else {
|
|
3529
|
-
|
|
3700
|
+
logger4.warn("Received MEDIA_TRACK_UNMUTED event without a corresponding observedPeerConnection or observedTrack: %o", event);
|
|
3530
3701
|
}
|
|
3531
3702
|
}
|
|
3532
3703
|
break;
|
|
@@ -3541,7 +3712,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3541
3712
|
} else if (postBuffer) {
|
|
3542
3713
|
postBuffer.push(event);
|
|
3543
3714
|
} else {
|
|
3544
|
-
|
|
3715
|
+
logger4.warn("Received ICE_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o", event);
|
|
3545
3716
|
}
|
|
3546
3717
|
}
|
|
3547
3718
|
break;
|
|
@@ -3556,7 +3727,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3556
3727
|
} else if (postBuffer) {
|
|
3557
3728
|
postBuffer.push(event);
|
|
3558
3729
|
} else {
|
|
3559
|
-
|
|
3730
|
+
logger4.warn("Received ICE_GATHERING_STATE_CHANGED event without a corresponding observedPeerConnection: %o", event);
|
|
3560
3731
|
}
|
|
3561
3732
|
}
|
|
3562
3733
|
break;
|
|
@@ -3571,7 +3742,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3571
3742
|
} else if (postBuffer) {
|
|
3572
3743
|
postBuffer.push(event);
|
|
3573
3744
|
} else {
|
|
3574
|
-
|
|
3745
|
+
logger4.warn("Received PEER_CONNECTION_STATE_CHANGED event without a corresponding observedPeerConnection: %o", event);
|
|
3575
3746
|
}
|
|
3576
3747
|
}
|
|
3577
3748
|
break;
|
|
@@ -3590,11 +3761,11 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3590
3761
|
let observedPeerConnection = this.observedPeerConnections.get(sample.peerConnectionId);
|
|
3591
3762
|
if (!observedPeerConnection) {
|
|
3592
3763
|
if (this._closedPeerConnectionIds.has(sample.peerConnectionId)) {
|
|
3593
|
-
|
|
3764
|
+
logger4.debug("Ignoring stats of peer connection %s, already closed by client %s", sample.peerConnectionId, this.clientId);
|
|
3594
3765
|
return void 0;
|
|
3595
3766
|
}
|
|
3596
3767
|
if (!sample.peerConnectionId) {
|
|
3597
|
-
return
|
|
3768
|
+
return logger4.warn(
|
|
3598
3769
|
`ObservedClient received an invalid PeerConnectionSample (missing peerConnectionId field). ClientId: ${this.clientId}, CallId: ${this.call.callId}`,
|
|
3599
3770
|
sample
|
|
3600
3771
|
), void 0;
|
|
@@ -3654,7 +3825,7 @@ var ObservedClient = class extends EventEmitter2 {
|
|
|
3654
3825
|
try {
|
|
3655
3826
|
this.sink?.write(finalSample);
|
|
3656
3827
|
} catch (err) {
|
|
3657
|
-
|
|
3828
|
+
logger4.warn("Sink write failed during injection flush for client %s: %o", this.clientId, err);
|
|
3658
3829
|
}
|
|
3659
3830
|
}
|
|
3660
3831
|
/** Emit an Observer-bus event scoped to this client (or a peer connection under it). */
|
|
@@ -3682,7 +3853,7 @@ var DefaultCallScoreCalculator = class {
|
|
|
3682
3853
|
};
|
|
3683
3854
|
|
|
3684
3855
|
// src/detectors/Detectors.ts
|
|
3685
|
-
var
|
|
3856
|
+
var logger5 = createLogger("Detectors");
|
|
3686
3857
|
var Detectors = class {
|
|
3687
3858
|
_detectors;
|
|
3688
3859
|
constructor(...detectors) {
|
|
@@ -3763,7 +3934,7 @@ var Detectors = class {
|
|
|
3763
3934
|
try {
|
|
3764
3935
|
detector.update();
|
|
3765
3936
|
} catch (err) {
|
|
3766
|
-
|
|
3937
|
+
logger5.warn(`Error updating detector ${detector?.name}`, err);
|
|
3767
3938
|
}
|
|
3768
3939
|
}
|
|
3769
3940
|
}
|
|
@@ -3776,7 +3947,7 @@ var Detectors = class {
|
|
|
3776
3947
|
try {
|
|
3777
3948
|
detector.close?.();
|
|
3778
3949
|
} catch (err) {
|
|
3779
|
-
|
|
3950
|
+
logger5.warn(`Error closing detector ${detector?.name}`, err);
|
|
3780
3951
|
}
|
|
3781
3952
|
}
|
|
3782
3953
|
};
|
|
@@ -4743,8 +4914,8 @@ var AudioEchoDetector = class _AudioEchoDetector {
|
|
|
4743
4914
|
};
|
|
4744
4915
|
|
|
4745
4916
|
// src/ObservedCall.ts
|
|
4746
|
-
var
|
|
4747
|
-
var ObservedCall = class extends
|
|
4917
|
+
var logger6 = createLogger("ObservedCall");
|
|
4918
|
+
var ObservedCall = class extends EventEmitter4 {
|
|
4748
4919
|
constructor(settings, observer, activeIssuesRegistry) {
|
|
4749
4920
|
super();
|
|
4750
4921
|
this.observer = observer;
|
|
@@ -4752,6 +4923,8 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
4752
4923
|
this.setMaxListeners(Infinity);
|
|
4753
4924
|
this.eventScope = { observer: this.observer, observedCall: this };
|
|
4754
4925
|
this.callId = settings.callId;
|
|
4926
|
+
this.sinks = new SinkRegistry(this.observer.sinks);
|
|
4927
|
+
this.extensionStats = new ExtensionStatsRegistry(this.callId, this.sinks);
|
|
4755
4928
|
this.appData = settings.appData ?? {};
|
|
4756
4929
|
this.scoreCalculator = new DefaultCallScoreCalculator(this);
|
|
4757
4930
|
this.detectors = new Detectors();
|
|
@@ -4808,6 +4981,8 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
4808
4981
|
settings;
|
|
4809
4982
|
/** Ancestry base shared by all Observer-bus events originating at this call. */
|
|
4810
4983
|
eventScope;
|
|
4984
|
+
sinks;
|
|
4985
|
+
extensionStats;
|
|
4811
4986
|
closeTimer;
|
|
4812
4987
|
get numberOfClients() {
|
|
4813
4988
|
return this.observedClients.size;
|
|
@@ -4850,7 +5025,7 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
4850
5025
|
break;
|
|
4851
5026
|
}
|
|
4852
5027
|
default: {
|
|
4853
|
-
|
|
5028
|
+
logger6.warn("Unknown detector name %s; skipping", name);
|
|
4854
5029
|
return this;
|
|
4855
5030
|
}
|
|
4856
5031
|
}
|
|
@@ -4920,6 +5095,8 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
4920
5095
|
if (client.joinedAt) minSampleTimestamps = Math.min(minSampleTimestamps ?? client.joinedAt, client.joinedAt);
|
|
4921
5096
|
if (client.leftAt) maxSampleTimestamps = Math.max(maxSampleTimestamps ?? client.leftAt, client.leftAt);
|
|
4922
5097
|
}
|
|
5098
|
+
this.extensionStats.close();
|
|
5099
|
+
this.sinks.clear();
|
|
4923
5100
|
if (this.startedAt === void 0) this.startedAt = minSampleTimestamps;
|
|
4924
5101
|
if (this.endedAt === void 0) this.endedAt = maxSampleTimestamps;
|
|
4925
5102
|
this.closedAt = Date.now();
|
|
@@ -4932,17 +5109,26 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
4932
5109
|
this.emit("close");
|
|
4933
5110
|
this._notify("call-closed", { ...this.eventScope });
|
|
4934
5111
|
}
|
|
5112
|
+
createExtensionStats(settings) {
|
|
5113
|
+
return this.extensionStats.create(settings);
|
|
5114
|
+
}
|
|
5115
|
+
acceptExtensionStats(id, data) {
|
|
5116
|
+
this.extensionStats.accept(id, data);
|
|
5117
|
+
}
|
|
5118
|
+
getExtensionStats(id) {
|
|
5119
|
+
return this.extensionStats.get(id);
|
|
5120
|
+
}
|
|
4935
5121
|
getObservedClient(clientId) {
|
|
4936
5122
|
if (this.closed || !this.observedClients.has(clientId)) return;
|
|
4937
5123
|
return this.observedClients.get(clientId);
|
|
4938
5124
|
}
|
|
4939
5125
|
createObservedClient(settings, acceptCtx) {
|
|
4940
5126
|
if (this.closed) {
|
|
4941
|
-
|
|
5127
|
+
logger6.warn("Attempted to create a client (clientId: %s) on a closed call %s", settings.clientId, this.callId);
|
|
4942
5128
|
return void 0;
|
|
4943
5129
|
}
|
|
4944
5130
|
if (this.observedClients.has(settings.clientId)) {
|
|
4945
|
-
|
|
5131
|
+
logger6.warn("Client with id %s already exists in call %s; returning the existing instance", settings.clientId, this.callId);
|
|
4946
5132
|
return this.observedClients.get(settings.clientId);
|
|
4947
5133
|
}
|
|
4948
5134
|
const clientSettings = {
|
|
@@ -4992,10 +5178,17 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
4992
5178
|
clearTimeout(this.closeTimer);
|
|
4993
5179
|
this.closeTimer = void 0;
|
|
4994
5180
|
}
|
|
5181
|
+
const jsonlPath = this.observer.config.createClientJsonlSink?.({ observedCall: this, clientId: settings.clientId });
|
|
5182
|
+
let jsonlSink;
|
|
5183
|
+
if (jsonlPath !== void 0 && this.observer.sinks.get(settings.clientId)) {
|
|
5184
|
+
logger6.warn("No sample file for client %s in call %s: sink id %s is already in use", settings.clientId, this.callId, settings.clientId);
|
|
5185
|
+
} else if (jsonlPath !== void 0) {
|
|
5186
|
+
jsonlSink = this.sinks.create(JsonlFileSink, { id: settings.clientId, path: jsonlPath });
|
|
5187
|
+
}
|
|
4995
5188
|
this.emit("newclient", result);
|
|
4996
5189
|
this._notify("client-added", { ...this.eventScope, observedClient: result });
|
|
4997
|
-
if (
|
|
4998
|
-
this._notify("client-sink-created", { ...this.eventScope, observedClient: result, sink:
|
|
5190
|
+
if (jsonlSink) {
|
|
5191
|
+
this._notify("client-sink-created", { ...this.eventScope, observedClient: result, sink: jsonlSink });
|
|
4999
5192
|
}
|
|
5000
5193
|
if (wasEmpty) {
|
|
5001
5194
|
this.emit("not-empty");
|
|
@@ -5048,10 +5241,10 @@ var ObservedCall = class extends EventEmitter3 {
|
|
|
5048
5241
|
};
|
|
5049
5242
|
|
|
5050
5243
|
// src/Observer.ts
|
|
5051
|
-
import { EventEmitter as
|
|
5244
|
+
import { EventEmitter as EventEmitter7 } from "events";
|
|
5052
5245
|
|
|
5053
5246
|
// src/ObservedTURN.ts
|
|
5054
|
-
import { EventEmitter as
|
|
5247
|
+
import { EventEmitter as EventEmitter5 } from "events";
|
|
5055
5248
|
|
|
5056
5249
|
// src/ObservedTurnServer.ts
|
|
5057
5250
|
var ObservedTurnServer = class {
|
|
@@ -5111,8 +5304,8 @@ var ObservedTurnServer = class {
|
|
|
5111
5304
|
};
|
|
5112
5305
|
|
|
5113
5306
|
// src/ObservedTURN.ts
|
|
5114
|
-
var
|
|
5115
|
-
var ObservedTURN = class extends
|
|
5307
|
+
var logger7 = createLogger("ObservedTURN");
|
|
5308
|
+
var ObservedTURN = class extends EventEmitter5 {
|
|
5116
5309
|
totalBytesSent = 0;
|
|
5117
5310
|
totalBytesReceived = 0;
|
|
5118
5311
|
totalPacketsSent = 0;
|
|
@@ -5141,12 +5334,12 @@ var ObservedTURN = class extends EventEmitter4 {
|
|
|
5141
5334
|
addPeerConnection(peerConnection) {
|
|
5142
5335
|
const turnPairs = peerConnection.selectedIceCandiadtePairForTurn;
|
|
5143
5336
|
if (turnPairs.length !== 1) {
|
|
5144
|
-
return
|
|
5337
|
+
return logger7.warn(`Expected exactly one TURN pair, but found for peerconnection ${peerConnection.peerConnectionId}`, turnPairs.length), void 0;
|
|
5145
5338
|
}
|
|
5146
5339
|
const candidatePair = turnPairs[0];
|
|
5147
5340
|
const rawUrl = candidatePair.getLocalCandidate()?.url;
|
|
5148
5341
|
if (!rawUrl) {
|
|
5149
|
-
return
|
|
5342
|
+
return logger7.warn(`No local candidate URL found for peerconnection ${peerConnection.peerConnectionId}`), void 0;
|
|
5150
5343
|
}
|
|
5151
5344
|
const turnServerUrl = rawUrl.split("?")[0];
|
|
5152
5345
|
let turnServer = this.servers.get(turnServerUrl);
|
|
@@ -5222,14 +5415,16 @@ var MiddlewareProcessor = class {
|
|
|
5222
5415
|
};
|
|
5223
5416
|
|
|
5224
5417
|
// src/ObservedMediasoupRouter.ts
|
|
5225
|
-
import { EventEmitter as
|
|
5226
|
-
var
|
|
5227
|
-
var ObservedMediasoupRouter = class extends
|
|
5418
|
+
import { EventEmitter as EventEmitter6 } from "events";
|
|
5419
|
+
var logger8 = createLogger("ObservedMediasoupRouter");
|
|
5420
|
+
var ObservedMediasoupRouter = class extends EventEmitter6 {
|
|
5228
5421
|
router;
|
|
5229
5422
|
appData;
|
|
5230
5423
|
sample;
|
|
5231
5424
|
webrtcTransportIds = /* @__PURE__ */ new Set();
|
|
5232
5425
|
closed = false;
|
|
5426
|
+
sinks;
|
|
5427
|
+
extensionStats;
|
|
5233
5428
|
// Id -> sample indexes over the arrays in `this.sample`, so an entity can be reached in O(1)
|
|
5234
5429
|
// instead of scanning (`sample.producers.find(...)`). They hold the *same* objects as the arrays.
|
|
5235
5430
|
_transportSamples = /* @__PURE__ */ new Map();
|
|
@@ -5238,11 +5433,17 @@ var ObservedMediasoupRouter = class extends EventEmitter5 {
|
|
|
5238
5433
|
_dataProducerSamples = /* @__PURE__ */ new Map();
|
|
5239
5434
|
_dataConsumerSamples = /* @__PURE__ */ new Map();
|
|
5240
5435
|
_enrich;
|
|
5241
|
-
|
|
5436
|
+
/**
|
|
5437
|
+
* @param parentSinks the parent of `sinks`: `observer.sinks`, passed by
|
|
5438
|
+
* `Observer.createObservedMediasoupRouter`. Internal; a router constructed directly has none.
|
|
5439
|
+
*/
|
|
5440
|
+
constructor(settings, parentSinks) {
|
|
5242
5441
|
super();
|
|
5243
5442
|
this.router = settings.router;
|
|
5244
5443
|
this.appData = settings.appData ?? {};
|
|
5245
5444
|
this._enrich = settings.enrich;
|
|
5445
|
+
this.sinks = new SinkRegistry(parentSinks);
|
|
5446
|
+
this.extensionStats = new ExtensionStatsRegistry(this.router.id, this.sinks);
|
|
5246
5447
|
this.sample = {
|
|
5247
5448
|
routerId: this.router.id,
|
|
5248
5449
|
attachments: settings.attachments ?? {},
|
|
@@ -5311,8 +5512,19 @@ var ObservedMediasoupRouter = class extends EventEmitter5 {
|
|
|
5311
5512
|
if (this.closed) return;
|
|
5312
5513
|
this.closed = true;
|
|
5313
5514
|
if (this.sample.closedAt === void 0) this.sample.closedAt = Date.now();
|
|
5515
|
+
this.extensionStats.close();
|
|
5516
|
+
this.sinks.clear();
|
|
5314
5517
|
this.emit("close");
|
|
5315
5518
|
}
|
|
5519
|
+
createExtensionStats(settings) {
|
|
5520
|
+
return this.extensionStats.create(settings);
|
|
5521
|
+
}
|
|
5522
|
+
acceptExtensionStats(id, data) {
|
|
5523
|
+
this.extensionStats.accept(id, data);
|
|
5524
|
+
}
|
|
5525
|
+
getExtensionStats(id) {
|
|
5526
|
+
return this.extensionStats.get(id);
|
|
5527
|
+
}
|
|
5316
5528
|
addTransport = (transport) => {
|
|
5317
5529
|
let transportType = transport.type;
|
|
5318
5530
|
if (!transportType) {
|
|
@@ -5321,7 +5533,7 @@ var ObservedMediasoupRouter = class extends EventEmitter5 {
|
|
|
5321
5533
|
else if (transport.constructor.name === "PipeTransport") transportType = "pipe";
|
|
5322
5534
|
else if (transport.constructor.name === "DirectTransport") transportType = "direct";
|
|
5323
5535
|
}
|
|
5324
|
-
if (!transportType) return
|
|
5536
|
+
if (!transportType) return logger8.warn(`Unknown transport type for transport ${transport.id}: ${transport.constructor.name}`);
|
|
5325
5537
|
switch (transportType) {
|
|
5326
5538
|
case "webrtc":
|
|
5327
5539
|
return this.addWebRtcTransport(transport);
|
|
@@ -5332,7 +5544,7 @@ var ObservedMediasoupRouter = class extends EventEmitter5 {
|
|
|
5332
5544
|
case "direct":
|
|
5333
5545
|
return this.addDirectTransport(transport);
|
|
5334
5546
|
default:
|
|
5335
|
-
return
|
|
5547
|
+
return logger8.warn(`Unsupported transport type for transport ${transport.id}: ${transportType}`);
|
|
5336
5548
|
}
|
|
5337
5549
|
};
|
|
5338
5550
|
addWebRtcTransport(transport) {
|
|
@@ -5574,7 +5786,7 @@ var ObservedMediasoupRouter = class extends EventEmitter5 {
|
|
|
5574
5786
|
const attachments = enrich();
|
|
5575
5787
|
if (attachments) sample.attachments = { ...sample.attachments, ...attachments };
|
|
5576
5788
|
} catch (err) {
|
|
5577
|
-
|
|
5789
|
+
logger8.warn("Mediasoup sample enricher threw for router %s: %o", this.id, err);
|
|
5578
5790
|
}
|
|
5579
5791
|
}
|
|
5580
5792
|
_attachTransportObserverListeners(transport, transportSample, onClose) {
|
|
@@ -5599,7 +5811,7 @@ var ObservedMediasoupRouter = class extends EventEmitter5 {
|
|
|
5599
5811
|
};
|
|
5600
5812
|
|
|
5601
5813
|
// src/issues/ActiveIssuesRegistry.ts
|
|
5602
|
-
var
|
|
5814
|
+
var logger9 = createLogger("ActiveIssuesRegistry");
|
|
5603
5815
|
var ActiveIssuesRegistry = class {
|
|
5604
5816
|
constructor(parent) {
|
|
5605
5817
|
this.parent = parent;
|
|
@@ -5685,7 +5897,7 @@ var ActiveIssuesRegistry = class {
|
|
|
5685
5897
|
try {
|
|
5686
5898
|
apply(target);
|
|
5687
5899
|
} catch (err) {
|
|
5688
|
-
|
|
5900
|
+
logger9.warn("An issue tracker threw while being notified; continuing with the rest. %o", err);
|
|
5689
5901
|
}
|
|
5690
5902
|
}
|
|
5691
5903
|
};
|
|
@@ -5714,7 +5926,7 @@ function createCallSummary(callId, config) {
|
|
|
5714
5926
|
}
|
|
5715
5927
|
|
|
5716
5928
|
// src/summaries/CallSummaryCollector.ts
|
|
5717
|
-
var
|
|
5929
|
+
var logger10 = createLogger("CallSummaryCollector");
|
|
5718
5930
|
var CallSummaryCollector = class {
|
|
5719
5931
|
constructor(_observer, _config) {
|
|
5720
5932
|
this._observer = _observer;
|
|
@@ -5789,7 +6001,7 @@ var CallSummaryCollector = class {
|
|
|
5789
6001
|
try {
|
|
5790
6002
|
handler(summary, scratch, ...args);
|
|
5791
6003
|
} catch (err) {
|
|
5792
|
-
|
|
6004
|
+
logger10.warn("A call-summary handler for %s threw; continuing. %o", event, err);
|
|
5793
6005
|
}
|
|
5794
6006
|
};
|
|
5795
6007
|
this._observer.on(event, listener);
|
|
@@ -6214,7 +6426,7 @@ function geohash(location, precision = 3) {
|
|
|
6214
6426
|
}
|
|
6215
6427
|
|
|
6216
6428
|
// src/detectors/ClientPopulationIssueDetector.ts
|
|
6217
|
-
var
|
|
6429
|
+
var logger11 = createLogger("ClientPopulationIssueDetector");
|
|
6218
6430
|
var ClientPopulationIssueTypes = {
|
|
6219
6431
|
/** One issue type is concentrated on one client population while the rest of the fleet is fine. */
|
|
6220
6432
|
clientPopulationIssue: "CLIENT_POPULATION_ISSUE"
|
|
@@ -6236,7 +6448,7 @@ var ClientPopulationIssueDetector = class _ClientPopulationIssueDetector {
|
|
|
6236
6448
|
...config
|
|
6237
6449
|
};
|
|
6238
6450
|
if (this._config.groupBy === "location" && !this._config.resolveClientLocation) {
|
|
6239
|
-
|
|
6451
|
+
logger11.warn(
|
|
6240
6452
|
"%s is configured with groupBy: 'location' but no resolveClientLocation, so no client can be placed and nothing will ever be detected",
|
|
6241
6453
|
this.name
|
|
6242
6454
|
);
|
|
@@ -7070,11 +7282,13 @@ function mediaKindOf(mimeType) {
|
|
|
7070
7282
|
}
|
|
7071
7283
|
|
|
7072
7284
|
// src/Observer.ts
|
|
7073
|
-
var
|
|
7074
|
-
var Observer = class extends
|
|
7285
|
+
var logger12 = createLogger("Observer");
|
|
7286
|
+
var Observer = class extends EventEmitter7 {
|
|
7075
7287
|
observedTURN = new ObservedTURN();
|
|
7076
7288
|
observedCalls = /* @__PURE__ */ new Map();
|
|
7077
7289
|
observedMediasoupRouters = /* @__PURE__ */ new Map();
|
|
7290
|
+
sinks = new SinkRegistry();
|
|
7291
|
+
extensionStats = new ExtensionStatsRegistry("observer", this.sinks);
|
|
7078
7292
|
/** Ancestry base shared by all Observer-bus events originating at the observer. */
|
|
7079
7293
|
eventScope = { observer: this };
|
|
7080
7294
|
config;
|
|
@@ -7189,7 +7403,7 @@ var Observer = class extends EventEmitter6 {
|
|
|
7189
7403
|
break;
|
|
7190
7404
|
}
|
|
7191
7405
|
default: {
|
|
7192
|
-
|
|
7406
|
+
logger12.warn("Unknown detector name %s; skipping", name);
|
|
7193
7407
|
return this;
|
|
7194
7408
|
}
|
|
7195
7409
|
}
|
|
@@ -7274,7 +7488,7 @@ var Observer = class extends EventEmitter6 {
|
|
|
7274
7488
|
break;
|
|
7275
7489
|
}
|
|
7276
7490
|
if (!validator) {
|
|
7277
|
-
|
|
7491
|
+
logger12.warn("Unknown validator name %s; skipping", name);
|
|
7278
7492
|
return this;
|
|
7279
7493
|
}
|
|
7280
7494
|
this.validators.add(validator);
|
|
@@ -7305,7 +7519,7 @@ var Observer = class extends EventEmitter6 {
|
|
|
7305
7519
|
try {
|
|
7306
7520
|
validator.cancel(reason);
|
|
7307
7521
|
} catch (err) {
|
|
7308
|
-
|
|
7522
|
+
logger12.warn("Error cancelling validator %s: %o", validator.name, err);
|
|
7309
7523
|
}
|
|
7310
7524
|
}
|
|
7311
7525
|
return matching.length;
|
|
@@ -7321,11 +7535,11 @@ var Observer = class extends EventEmitter6 {
|
|
|
7321
7535
|
*/
|
|
7322
7536
|
createObservedCall(settings, acceptCtx) {
|
|
7323
7537
|
if (this.closed) {
|
|
7324
|
-
|
|
7538
|
+
logger12.warn("Attempted to create a call (callId: %s) on a closed observer", settings.callId);
|
|
7325
7539
|
return void 0;
|
|
7326
7540
|
}
|
|
7327
7541
|
if (this.observedCalls.has(settings.callId)) {
|
|
7328
|
-
|
|
7542
|
+
logger12.warn("Observed Call with id %s already exists; returning the existing instance", settings.callId);
|
|
7329
7543
|
return this.observedCalls.get(settings.callId);
|
|
7330
7544
|
}
|
|
7331
7545
|
const callSettings = {
|
|
@@ -7363,14 +7577,14 @@ var Observer = class extends EventEmitter6 {
|
|
|
7363
7577
|
}
|
|
7364
7578
|
createObservedMediasoupRouter(settings) {
|
|
7365
7579
|
if (this.closed) {
|
|
7366
|
-
|
|
7580
|
+
logger12.warn("Attempted to create mediasoup router (id: %d) on a closed observer", settings.router.id);
|
|
7367
7581
|
return void 0;
|
|
7368
7582
|
}
|
|
7369
7583
|
if (this.observedMediasoupRouters.has(settings.router.id)) {
|
|
7370
|
-
|
|
7584
|
+
logger12.warn("Observed Mediasoup Router (id %s) already exists; returning the existing instance", settings.router.id);
|
|
7371
7585
|
return this.observedMediasoupRouters.get(settings.router.id);
|
|
7372
7586
|
}
|
|
7373
|
-
const observedMediasoupRouter = new ObservedMediasoupRouter(settings);
|
|
7587
|
+
const observedMediasoupRouter = new ObservedMediasoupRouter(settings, this.sinks);
|
|
7374
7588
|
const observedMediasoupRouterScope = {
|
|
7375
7589
|
observedMediasoupRouter,
|
|
7376
7590
|
observer: this
|
|
@@ -7398,7 +7612,7 @@ var Observer = class extends EventEmitter6 {
|
|
|
7398
7612
|
}
|
|
7399
7613
|
close() {
|
|
7400
7614
|
if (this.closed) {
|
|
7401
|
-
return
|
|
7615
|
+
return logger12.debug("Attempted to close twice");
|
|
7402
7616
|
}
|
|
7403
7617
|
this.closed = true;
|
|
7404
7618
|
for (const call of [...this.observedCalls.values()]) call.close();
|
|
@@ -7407,8 +7621,19 @@ var Observer = class extends EventEmitter6 {
|
|
|
7407
7621
|
this.validators.clear();
|
|
7408
7622
|
this.callSummaryCollector?.close();
|
|
7409
7623
|
this.activeIssuesRegistry.clear();
|
|
7624
|
+
this.extensionStats.close();
|
|
7625
|
+
this.sinks.clear();
|
|
7410
7626
|
this._notify("observer-closed", { ...this.eventScope });
|
|
7411
7627
|
}
|
|
7628
|
+
createExtensionStats(settings) {
|
|
7629
|
+
return this.extensionStats.create(settings);
|
|
7630
|
+
}
|
|
7631
|
+
acceptExtensionStats(id, data) {
|
|
7632
|
+
this.extensionStats.accept(id, data);
|
|
7633
|
+
}
|
|
7634
|
+
getExtensionStats(id) {
|
|
7635
|
+
return this.extensionStats.get(id);
|
|
7636
|
+
}
|
|
7412
7637
|
accept(sample, context) {
|
|
7413
7638
|
if (this.closed) {
|
|
7414
7639
|
return this._notify("sample-rejected", { ...this.eventScope, reason: "observer-closed", sample });
|
|
@@ -7416,7 +7641,7 @@ var Observer = class extends EventEmitter6 {
|
|
|
7416
7641
|
try {
|
|
7417
7642
|
this.acceptMiddlewares.process({ sample, context });
|
|
7418
7643
|
} catch (err) {
|
|
7419
|
-
|
|
7644
|
+
logger12.warn("An accept middleware threw; dropping the sample. %o", err);
|
|
7420
7645
|
}
|
|
7421
7646
|
if (!sample.callId) {
|
|
7422
7647
|
this._notify("sample-rejected", { ...this.eventScope, reason: "missing-callId", sample });
|
|
@@ -7456,7 +7681,7 @@ var Observer = class extends EventEmitter6 {
|
|
|
7456
7681
|
try {
|
|
7457
7682
|
validator.update();
|
|
7458
7683
|
} catch (err) {
|
|
7459
|
-
|
|
7684
|
+
logger12.warn("Error running validator %s: %o", validator.name, err);
|
|
7460
7685
|
}
|
|
7461
7686
|
}
|
|
7462
7687
|
this._notify("observer-updated", { ...this.eventScope });
|
|
@@ -7692,67 +7917,34 @@ var TrendTester = class {
|
|
|
7692
7917
|
}
|
|
7693
7918
|
};
|
|
7694
7919
|
|
|
7695
|
-
// src/sinks/ClientSampleSink.ts
|
|
7696
|
-
import { EventEmitter as EventEmitter7 } from "events";
|
|
7697
|
-
var ClientSampleSink = class extends EventEmitter7 {
|
|
7698
|
-
};
|
|
7699
|
-
|
|
7700
|
-
// src/sinks/JsonlFileSink.ts
|
|
7701
|
-
import * as fs from "fs";
|
|
7702
|
-
import * as path from "path";
|
|
7703
|
-
var JsonlFileSink = class extends ClientSampleSink {
|
|
7704
|
-
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
7705
|
-
path;
|
|
7706
|
-
_stream;
|
|
7707
|
-
_serializeSample;
|
|
7708
|
-
constructor(options) {
|
|
7709
|
-
super();
|
|
7710
|
-
this.path = options.path;
|
|
7711
|
-
this._stream = fs.createWriteStream(this.path, { flags: "a" });
|
|
7712
|
-
this._stream.on("close", () => this.emit("close"));
|
|
7713
|
-
this._stream.on("finish", () => this.emit("finish"));
|
|
7714
|
-
this._stream.on("drain", () => this.emit("drain"));
|
|
7715
|
-
this._stream.on("error", (err) => this.emit("error", err));
|
|
7716
|
-
this._serializeSample = options.serializeSample ?? ((sample) => JSON.stringify(sample));
|
|
7717
|
-
}
|
|
7718
|
-
write(sample) {
|
|
7719
|
-
return this._stream.write(`${this._serializeSample(sample)}
|
|
7720
|
-
`);
|
|
7721
|
-
}
|
|
7722
|
-
end() {
|
|
7723
|
-
this._stream.end();
|
|
7724
|
-
}
|
|
7725
|
-
};
|
|
7726
|
-
function createJsonlFileSink(options) {
|
|
7727
|
-
return new JsonlFileSink(options);
|
|
7728
|
-
}
|
|
7729
|
-
function createJsonlFileSinkFactory(options) {
|
|
7730
|
-
return ({ clientId, observedCall }) => {
|
|
7731
|
-
const filename = options.getFileName?.({ callId: observedCall.callId, clientId }) ?? `${observedCall.callId}__${clientId}.jsonl`;
|
|
7732
|
-
return createJsonlFileSink({
|
|
7733
|
-
path: path.join(options.directory, filename),
|
|
7734
|
-
serializeSample: options.serializeSample
|
|
7735
|
-
});
|
|
7736
|
-
};
|
|
7737
|
-
}
|
|
7738
|
-
|
|
7739
7920
|
// src/sinks/InMemorySink.ts
|
|
7740
|
-
|
|
7741
|
-
|
|
7921
|
+
import { EventEmitter as EventEmitter8 } from "events";
|
|
7922
|
+
var InMemorySink = class extends EventEmitter8 {
|
|
7923
|
+
id;
|
|
7924
|
+
samples;
|
|
7925
|
+
_ended = false;
|
|
7926
|
+
constructor(settings = {}) {
|
|
7742
7927
|
super();
|
|
7743
|
-
this.
|
|
7928
|
+
this.id = settings.id ?? "in-memory";
|
|
7929
|
+
this.samples = settings.samples ?? [];
|
|
7930
|
+
}
|
|
7931
|
+
get ended() {
|
|
7932
|
+
return this._ended;
|
|
7744
7933
|
}
|
|
7745
|
-
samples;
|
|
7746
7934
|
write(sample) {
|
|
7935
|
+
if (this._ended) return false;
|
|
7747
7936
|
this.samples.push(sample);
|
|
7748
7937
|
return true;
|
|
7749
7938
|
}
|
|
7750
7939
|
end() {
|
|
7940
|
+
if (this._ended) return;
|
|
7941
|
+
this._ended = true;
|
|
7942
|
+
this.emit("ended");
|
|
7751
7943
|
this.emit("close");
|
|
7752
7944
|
}
|
|
7753
7945
|
};
|
|
7754
7946
|
function createInMemorySink(samples) {
|
|
7755
|
-
return new InMemorySink(samples);
|
|
7947
|
+
return new InMemorySink({ samples });
|
|
7756
7948
|
}
|
|
7757
7949
|
|
|
7758
7950
|
// src/resolvers/RemoteTrackResolver.ts
|
|
@@ -7940,9 +8132,9 @@ export {
|
|
|
7940
8132
|
ClientMetaTypes,
|
|
7941
8133
|
ClientPopulationIssueDetector,
|
|
7942
8134
|
ClientPopulationIssueTypes,
|
|
7943
|
-
ClientSampleSink,
|
|
7944
8135
|
CodecConsistencyValidator,
|
|
7945
8136
|
Detectors,
|
|
8137
|
+
ExtensionStatsRegistry,
|
|
7946
8138
|
GEOHASH_CELL_SIZES,
|
|
7947
8139
|
InMemorySink,
|
|
7948
8140
|
IssueFanOutDetector,
|
|
@@ -7986,6 +8178,7 @@ export {
|
|
|
7986
8178
|
SCORE_HISTOGRAM_NUMBER_OF_BUCKETS,
|
|
7987
8179
|
SfuCongestionDetector,
|
|
7988
8180
|
SimulcastReceiverValidator,
|
|
8181
|
+
SinkRegistry,
|
|
7989
8182
|
SlidingWindow,
|
|
7990
8183
|
TrackDeliveryMismatchDetector,
|
|
7991
8184
|
TrackDeliveryMismatchTypes,
|
|
@@ -8006,7 +8199,6 @@ export {
|
|
|
8006
8199
|
createDefaultMediasoupRemoteTrackResolverFactory,
|
|
8007
8200
|
createInMemorySink,
|
|
8008
8201
|
createJsonlFileSink,
|
|
8009
|
-
createJsonlFileSinkFactory,
|
|
8010
8202
|
createLogger,
|
|
8011
8203
|
createP2pRemoteTrackResolverFactory,
|
|
8012
8204
|
defaultCallSummaryConfig,
|