@observertc/observer-js 1.0.0-beta.10 → 1.0.0-beta.11
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 +77 -15
- package/dist/index.d.mts +8 -4
- package/dist/index.d.ts +8 -4
- package/dist/index.js +100 -45
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +100 -45
- package/dist/index.mjs.map +1 -1
- package/llms-full.txt +78 -16
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -63,9 +63,10 @@ and emits a single, unified stream of typed events the application can react to.
|
|
|
63
63
|
11. [Remote track resolution (mediasoup / SFU)](#remote-track-resolution-mediasoup--sfu)
|
|
64
64
|
12. [Mediasoup router observation](#mediasoup-router-observation)
|
|
65
65
|
13. [Sinks (per-client sample persistence)](#sinks-per-client-sample-persistence)
|
|
66
|
-
14. [
|
|
67
|
-
15. [
|
|
68
|
-
16. [
|
|
66
|
+
14. [Injecting data into a client](#injecting-data-into-a-client)
|
|
67
|
+
15. [Logging](#logging)
|
|
68
|
+
16. [Error-handling philosophy](#error-handling-philosophy)
|
|
69
|
+
17. [Development & extension guide](#development--extension-guide)
|
|
69
70
|
|
|
70
71
|
---
|
|
71
72
|
|
|
@@ -369,7 +370,7 @@ additional field(s) on top of that scope.
|
|
|
369
370
|
| Event | Extra | Fires when |
|
|
370
371
|
|-------|-------|-----------|
|
|
371
372
|
| `mediasoup-router-added` | — | `observer.createObservedMediasoupRouter(...)` registered a router |
|
|
372
|
-
| `mediasoup-router-matched-with-peer-connection` | `{ observedCall, observedClient, observedPeerConnection }` | a newly added peer connection's id matched one of the router's WebRTC transport ids. |
|
|
373
|
+
| `mediasoup-router-matched-with-peer-connection` | `{ observedCall, observedClient, observedPeerConnection }` | a newly added peer connection's id matched one of the router's WebRTC transport ids. **Opt-in** via `matchPeerConnectionByWebRtcTransportId: true`. |
|
|
373
374
|
| `mediasoup-router-removed` | — | the underlying mediasoup router closed (its `router.observer` `close` fired) |
|
|
374
375
|
|
|
375
376
|
See [Mediasoup router observation](#mediasoup-router-observation) for the full design and examples.
|
|
@@ -539,7 +540,7 @@ Key members:
|
|
|
539
540
|
- `readonly sink?: ClientSampleSink` — the per-client sink (see [Sinks](#sinks-per-client-sample-persistence)), if `createClientSink` is configured; listen on it for `close`/`error`
|
|
540
541
|
- **Injection API** (queue app data to be merged into the next sample processing):
|
|
541
542
|
`injectEvent(ClientEvent)`, `injectIssue(ClientIssue)`, `injectMetaData(ClientMetaData)`,
|
|
542
|
-
`injectExtensionStat(ExtensionStat)`, `injectAttachment(
|
|
543
|
+
`injectExtensionStat(ExtensionStat)`, `injectAttachment(attachments: Record<string, unknown>)`
|
|
543
544
|
- **Direct add API** (process immediately): `addIssue(ClientIssue)`, `addMetadata(ClientMetaData)`,
|
|
544
545
|
`addExtensionStats(ExtensionStat)`
|
|
545
546
|
- Metrics (current/derived): `currentAvgRttInMs?`, `currentMinRttInMs?`, `currentMaxRttInMs?`,
|
|
@@ -832,11 +833,13 @@ ancestry, so you get the router **and** the matched `observedPeerConnection`, `o
|
|
|
832
833
|
`observedCall` in one place. Stamp the `routerId` into the peer connection's / client's `appData`,
|
|
833
834
|
build your own index, attach the server sample to the call in your database — whatever fits.
|
|
834
835
|
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
836
|
+
This matching is **opt-in**: pass `matchPeerConnectionByWebRtcTransportId: true` to
|
|
837
|
+
`createObservedMediasoupRouter`. When enabled, as peer connections are observed
|
|
838
|
+
(`peer-connection-added`) the observer checks whether the peer connection's id is one of the router's
|
|
839
|
+
WebRTC transport ids; on a hit it emits — once per matching peer connection — and keeps watching, so a
|
|
840
|
+
router serving many participants emits one match per participant's transport. When the flag is omitted
|
|
841
|
+
or `false`, no matching is performed and the event never fires. The internal listener is removed
|
|
842
|
+
automatically when the router closes or the observer closes.
|
|
840
843
|
|
|
841
844
|
When the underlying mediasoup router closes, its `close` propagates to `ObservedMediasoupRouter`,
|
|
842
845
|
which emits **`mediasoup-router-removed`**. That is your cue to do whatever cleanup or persistence
|
|
@@ -850,10 +853,11 @@ you want with the now-final `sample` — again, the observer itself keeps nothin
|
|
|
850
853
|
| `routerId` | `string` | yes | your id for the router (the sample also carries `router.id`) |
|
|
851
854
|
| `appData` | `Record<string, unknown>` | no | application-owned bag on the `ObservedMediasoupRouter` |
|
|
852
855
|
| `attachments` | `Record<string, unknown>` | no | free-form data copied onto `sample.attachments` |
|
|
856
|
+
| `matchPeerConnectionByWebRtcTransportId` | `boolean` | no | opt in to peer-connection matching: emit `mediasoup-router-matched-with-peer-connection` for each peer connection whose id matches one of the router's WebRTC transport ids. Omitted / `false` → no matching, the event never fires |
|
|
853
857
|
|
|
854
|
-
Peer-connection matching is
|
|
855
|
-
`ObservedMediasoupRouter`, or `undefined`
|
|
856
|
-
returns the existing instance — both warn).
|
|
858
|
+
Peer-connection matching is **off by default**; enable it with
|
|
859
|
+
`matchPeerConnectionByWebRtcTransportId: true`. Returns the `ObservedMediasoupRouter`, or `undefined`
|
|
860
|
+
if the observer is closed (a router with the same id returns the existing instance — both warn).
|
|
857
861
|
|
|
858
862
|
Useful members on the returned object: `.sample` (the `MediasoupRouterSample`), `.appData`,
|
|
859
863
|
`.attachments` (getter over `sample.attachments`), `.webrtcTransportIds: Set<string>`, `.id`,
|
|
@@ -870,9 +874,13 @@ const observer = new Observer();
|
|
|
870
874
|
// 1) Feed client samples as usual so the observer knows about calls, clients & peer connections.
|
|
871
875
|
// (e.g. transport-layer: observer.accept(clientSample, context))
|
|
872
876
|
|
|
873
|
-
// 2) Observe the SFU side
|
|
877
|
+
// 2) Observe the SFU side, opting in to peer-connection matching for this router's transports.
|
|
874
878
|
const router = /* your mediasoup router */ undefined as any;
|
|
875
|
-
const observedRouter = observer.createObservedMediasoupRouter({
|
|
879
|
+
const observedRouter = observer.createObservedMediasoupRouter({
|
|
880
|
+
router,
|
|
881
|
+
routerId: router.id,
|
|
882
|
+
matchPeerConnectionByWebRtcTransportId: true,
|
|
883
|
+
});
|
|
876
884
|
|
|
877
885
|
// 3) Every peer connection whose id matches one of the router's WebRTC transport ids fires this —
|
|
878
886
|
// WE decide what to do with each pairing. The payload carries the full ancestry.
|
|
@@ -1031,6 +1039,60 @@ const observer = new Observer({ createClientSink });
|
|
|
1031
1039
|
the bus with full ancestry. `ClientSampleSinkFactory` is
|
|
1032
1040
|
`(p: { clientId: string; observedCall: ObservedCall }) => ClientSampleSink | undefined`.
|
|
1033
1041
|
|
|
1042
|
+
---
|
|
1043
|
+
|
|
1044
|
+
## Injecting data into a client
|
|
1045
|
+
|
|
1046
|
+
Sometimes the application holds data that belongs on a client's record but isn't part of the
|
|
1047
|
+
client-reported `ClientSample` — a room id or display name, an application-level event
|
|
1048
|
+
(*"recording started"*), a server-detected issue, an extension stat, or a device/meta item.
|
|
1049
|
+
`ObservedClient` exposes **injection** methods that merge such data into the client's sample stream,
|
|
1050
|
+
so it updates the live model **and** is persisted to the client's
|
|
1051
|
+
[sink](#sinks-per-client-sample-persistence) exactly like sampled data.
|
|
1052
|
+
|
|
1053
|
+
| Method | Adds to the sample's | Surfaces as |
|
|
1054
|
+
|--------|----------------------|-------------|
|
|
1055
|
+
| `injectAttachment(attachments)` | `attachments` (merged via `Object.assign`) | `observedClient.attachments` |
|
|
1056
|
+
| `injectEvent(event: ClientEvent)` | `clientEvents` | `client-event` (plus any state the event drives) |
|
|
1057
|
+
| `injectIssue(issue: ClientIssue)` | `clientIssues` | `client-issue` |
|
|
1058
|
+
| `injectMetaData(meta: ClientMetaData)` | `clientMetaItems` | `client-metadata` |
|
|
1059
|
+
| `injectExtensionStat(stat: ExtensionStat)` | `extensionStats` | `client-extension-stats` |
|
|
1060
|
+
|
|
1061
|
+
### When the injected data lands
|
|
1062
|
+
|
|
1063
|
+
Injection is timing-aware so nothing is dropped, regardless of *when* you call it:
|
|
1064
|
+
|
|
1065
|
+
- **During a sample's processing** — e.g. from inside a `client-updated` / `client-event` handler,
|
|
1066
|
+
which run within `accept()` — the data is applied to the **current** sample immediately: reflected
|
|
1067
|
+
in entity state and written to the sink as part of that sample.
|
|
1068
|
+
- **Between samples** — the data is buffered and merged into the **next** `accept()`'s sample.
|
|
1069
|
+
- **On `close()` with pending injections and no further sample** — the buffer is flushed as a final
|
|
1070
|
+
synthetic sample (applied to state and written to the sink) before the sink is ended, so a
|
|
1071
|
+
last-moment injection is never lost.
|
|
1072
|
+
|
|
1073
|
+
In every case the injected data both updates the live `ObservedClient` and reaches the per-client
|
|
1074
|
+
sink — the sink always receives the final, **injection-merged** sample (the sink write happens at the
|
|
1075
|
+
end of `accept()`, after the merge).
|
|
1076
|
+
|
|
1077
|
+
### Example
|
|
1078
|
+
|
|
1079
|
+
```ts
|
|
1080
|
+
// Enrich at creation from your app's knowledge of the participant. Injecting in `client-added`
|
|
1081
|
+
// (which runs just before the first accept) lands on the first sample.
|
|
1082
|
+
observer.on('client-added', ({ observedClient }) => {
|
|
1083
|
+
observedClient.injectAttachment({ roomId: lookupRoomId(observedClient.clientId) });
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
// Application-level signals at any time:
|
|
1087
|
+
const client = observer.getObservedCall(callId)?.getObservedClient(clientId);
|
|
1088
|
+
client?.injectEvent({ type: 'RECORDING_STARTED', timestamp: Date.now() });
|
|
1089
|
+
client?.injectIssue({ type: 'app-kicked-participant', timestamp: Date.now() });
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
`attachments` are latest-wins (like sampled `attachments`): injecting a key overwrites its previous
|
|
1093
|
+
value. `appData` is unaffected — injections flow into the sample/telemetry, not the app-owned
|
|
1094
|
+
`appData` bag (see [Ingestion](#ingestion-accept-context--lifecycle)).
|
|
1095
|
+
|
|
1034
1096
|
## Logging
|
|
1035
1097
|
|
|
1036
1098
|
`observer-js` logs through a single, swappable sink. Out of the box it writes `debug` and
|
package/dist/index.d.mts
CHANGED
|
@@ -2570,7 +2570,8 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
2570
2570
|
numberOfScoreMeasurements: number;
|
|
2571
2571
|
readonly mediaDevices: MediaDeviceInfo[];
|
|
2572
2572
|
issues: ClientIssue[];
|
|
2573
|
-
private
|
|
2573
|
+
private _pendingInjections;
|
|
2574
|
+
private _activeSample?;
|
|
2574
2575
|
private closeTimer?;
|
|
2575
2576
|
constructor(settings: ObservedClientSettings<AppData>, call: ObservedCall);
|
|
2576
2577
|
get numberOfPeerConnections(): number;
|
|
@@ -2581,13 +2582,14 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
2581
2582
|
injectEvent(event: ClientEvent): void;
|
|
2582
2583
|
injectIssue(issue: ClientIssue): void;
|
|
2583
2584
|
injectExtensionStat(stat: ExtensionStat): void;
|
|
2584
|
-
injectAttachment(
|
|
2585
|
+
injectAttachment(attachments: Record<string, unknown>): void;
|
|
2585
2586
|
addMetadata(metadata: ClientMetaData): void;
|
|
2586
2587
|
addIssue(issue: ClientIssue): void;
|
|
2587
2588
|
addExtensionStats(stats: ExtensionStat): void;
|
|
2588
2589
|
private _processClientEvent;
|
|
2589
2590
|
private _updatePeerConnection;
|
|
2590
|
-
private
|
|
2591
|
+
private _mergePendingInjections;
|
|
2592
|
+
private _flushPendingInjections;
|
|
2591
2593
|
/** Emit an Observer-bus event scoped to this client (or a peer connection under it). */
|
|
2592
2594
|
private _notify;
|
|
2593
2595
|
}
|
|
@@ -2836,7 +2838,9 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
2836
2838
|
getObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(callId: string): ObservedCall<T> | undefined;
|
|
2837
2839
|
createObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
2838
2840
|
getOrCreateObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
2839
|
-
createObservedMediasoupRouter<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedMediasoupRouterSettings<T>
|
|
2841
|
+
createObservedMediasoupRouter<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedMediasoupRouterSettings<T> & {
|
|
2842
|
+
matchPeerConnectionByWebRtcTransportId?: boolean;
|
|
2843
|
+
}): ObservedMediasoupRouter<Record<string, unknown>> | undefined;
|
|
2840
2844
|
close(): void;
|
|
2841
2845
|
accept(sample: ClientSample, context?: AcceptContext): void;
|
|
2842
2846
|
update(): void;
|
package/dist/index.d.ts
CHANGED
|
@@ -2570,7 +2570,8 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
2570
2570
|
numberOfScoreMeasurements: number;
|
|
2571
2571
|
readonly mediaDevices: MediaDeviceInfo[];
|
|
2572
2572
|
issues: ClientIssue[];
|
|
2573
|
-
private
|
|
2573
|
+
private _pendingInjections;
|
|
2574
|
+
private _activeSample?;
|
|
2574
2575
|
private closeTimer?;
|
|
2575
2576
|
constructor(settings: ObservedClientSettings<AppData>, call: ObservedCall);
|
|
2576
2577
|
get numberOfPeerConnections(): number;
|
|
@@ -2581,13 +2582,14 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
2581
2582
|
injectEvent(event: ClientEvent): void;
|
|
2582
2583
|
injectIssue(issue: ClientIssue): void;
|
|
2583
2584
|
injectExtensionStat(stat: ExtensionStat): void;
|
|
2584
|
-
injectAttachment(
|
|
2585
|
+
injectAttachment(attachments: Record<string, unknown>): void;
|
|
2585
2586
|
addMetadata(metadata: ClientMetaData): void;
|
|
2586
2587
|
addIssue(issue: ClientIssue): void;
|
|
2587
2588
|
addExtensionStats(stats: ExtensionStat): void;
|
|
2588
2589
|
private _processClientEvent;
|
|
2589
2590
|
private _updatePeerConnection;
|
|
2590
|
-
private
|
|
2591
|
+
private _mergePendingInjections;
|
|
2592
|
+
private _flushPendingInjections;
|
|
2591
2593
|
/** Emit an Observer-bus event scoped to this client (or a peer connection under it). */
|
|
2592
2594
|
private _notify;
|
|
2593
2595
|
}
|
|
@@ -2836,7 +2838,9 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
2836
2838
|
getObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(callId: string): ObservedCall<T> | undefined;
|
|
2837
2839
|
createObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
2838
2840
|
getOrCreateObservedCall<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedCallSettings<T>): ObservedCall<T> | undefined;
|
|
2839
|
-
createObservedMediasoupRouter<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedMediasoupRouterSettings<T>
|
|
2841
|
+
createObservedMediasoupRouter<T extends Record<string, unknown> = Record<string, unknown>>(settings: ObservedMediasoupRouterSettings<T> & {
|
|
2842
|
+
matchPeerConnectionByWebRtcTransportId?: boolean;
|
|
2843
|
+
}): ObservedMediasoupRouter<Record<string, unknown>> | undefined;
|
|
2840
2844
|
close(): void;
|
|
2841
2845
|
accept(sample: ClientSample, context?: AcceptContext): void;
|
|
2842
2846
|
update(): void;
|
package/dist/index.js
CHANGED
|
@@ -2250,7 +2250,8 @@ var ObservedClient = class extends import_events2.EventEmitter {
|
|
|
2250
2250
|
// public totalNumberOfIssues = 0;
|
|
2251
2251
|
mediaDevices = [];
|
|
2252
2252
|
issues = [];
|
|
2253
|
-
|
|
2253
|
+
_pendingInjections = {};
|
|
2254
|
+
_activeSample;
|
|
2254
2255
|
closeTimer;
|
|
2255
2256
|
get numberOfPeerConnections() {
|
|
2256
2257
|
return this.observedPeerConnections.size;
|
|
@@ -2260,11 +2261,8 @@ var ObservedClient = class extends import_events2.EventEmitter {
|
|
|
2260
2261
|
}
|
|
2261
2262
|
close() {
|
|
2262
2263
|
if (this.closed) return;
|
|
2264
|
+
this._flushPendingInjections();
|
|
2263
2265
|
this.closed = true;
|
|
2264
|
-
this._injections.clientEvents?.forEach((clientEvent) => this._processClientEvent(clientEvent));
|
|
2265
|
-
this._injections.clientIssues?.forEach((clientIssue) => this.addIssue(clientIssue));
|
|
2266
|
-
this._injections.extensionStats?.forEach((extensionStat) => this.addExtensionStats(extensionStat));
|
|
2267
|
-
this._injections.clientMetaItems?.forEach((clientMetaItem) => this.addMetadata(clientMetaItem));
|
|
2268
2266
|
Array.from(this.observedPeerConnections.values()).forEach((peerConnection) => peerConnection.close());
|
|
2269
2267
|
if (!this.leftAt) {
|
|
2270
2268
|
this.leftAt = this.lastSampleTimestamp;
|
|
@@ -2287,7 +2285,6 @@ var ObservedClient = class extends import_events2.EventEmitter {
|
|
|
2287
2285
|
logger2.warn("Attempted to accept a sample on a closed client %s", this.clientId);
|
|
2288
2286
|
return;
|
|
2289
2287
|
}
|
|
2290
|
-
this.sink?.write(sample);
|
|
2291
2288
|
if (this.closeTimer) {
|
|
2292
2289
|
clearTimeout(this.closeTimer);
|
|
2293
2290
|
this.closeTimer = void 0;
|
|
@@ -2327,7 +2324,8 @@ var ObservedClient = class extends import_events2.EventEmitter {
|
|
|
2327
2324
|
this.usingTCP = false;
|
|
2328
2325
|
this.currentMinRttInMs = void 0;
|
|
2329
2326
|
this.currentMaxRttInMs = void 0;
|
|
2330
|
-
this.
|
|
2327
|
+
this._mergePendingInjections(sample);
|
|
2328
|
+
this._activeSample = sample;
|
|
2331
2329
|
const clientEventsPostBuffer = [];
|
|
2332
2330
|
for (const clientEvent of sample.clientEvents ?? []) {
|
|
2333
2331
|
this._processClientEvent(clientEvent, clientEventsPostBuffer);
|
|
@@ -2415,31 +2413,68 @@ var ObservedClient = class extends import_events2.EventEmitter {
|
|
|
2415
2413
|
this.close();
|
|
2416
2414
|
}, this.settings.closeClientIfIdleForMs);
|
|
2417
2415
|
}
|
|
2416
|
+
this._activeSample = void 0;
|
|
2417
|
+
try {
|
|
2418
|
+
this.sink?.write(sample);
|
|
2419
|
+
} catch (err) {
|
|
2420
|
+
logger2.warn("ClientSampleSink.write failed for client %s: %o", this.clientId, err);
|
|
2421
|
+
}
|
|
2418
2422
|
}
|
|
2419
2423
|
injectMetaData(metaData) {
|
|
2420
2424
|
if (this.closed) return;
|
|
2421
|
-
if (
|
|
2422
|
-
|
|
2425
|
+
if (this._activeSample) {
|
|
2426
|
+
if (!this._activeSample.clientMetaItems) this._activeSample.clientMetaItems = [];
|
|
2427
|
+
this._activeSample.clientMetaItems.push(metaData);
|
|
2428
|
+
this.addMetadata(metaData);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
if (!this._pendingInjections.clientMetaItems) this._pendingInjections.clientMetaItems = [];
|
|
2432
|
+
this._pendingInjections.clientMetaItems.push(metaData);
|
|
2423
2433
|
}
|
|
2424
2434
|
injectEvent(event) {
|
|
2425
2435
|
if (this.closed) return;
|
|
2426
|
-
if (
|
|
2427
|
-
|
|
2436
|
+
if (this._activeSample) {
|
|
2437
|
+
if (!this._activeSample.clientEvents) this._activeSample.clientEvents = [];
|
|
2438
|
+
this._activeSample.clientEvents.push(event);
|
|
2439
|
+
this._processClientEvent(event);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
if (!this._pendingInjections.clientEvents) this._pendingInjections.clientEvents = [];
|
|
2443
|
+
this._pendingInjections.clientEvents.push(event);
|
|
2428
2444
|
}
|
|
2429
2445
|
injectIssue(issue) {
|
|
2430
2446
|
if (this.closed) return;
|
|
2431
|
-
if (
|
|
2432
|
-
|
|
2447
|
+
if (this._activeSample) {
|
|
2448
|
+
if (!this._activeSample.clientIssues) this._activeSample.clientIssues = [];
|
|
2449
|
+
this._activeSample.clientIssues.push(issue);
|
|
2450
|
+
this.addIssue(issue);
|
|
2451
|
+
++this.deltaNumberOfIssues;
|
|
2452
|
+
return;
|
|
2453
|
+
}
|
|
2454
|
+
if (!this._pendingInjections.clientIssues) this._pendingInjections.clientIssues = [];
|
|
2455
|
+
this._pendingInjections.clientIssues.push(issue);
|
|
2433
2456
|
}
|
|
2434
2457
|
injectExtensionStat(stat) {
|
|
2435
2458
|
if (this.closed) return;
|
|
2436
|
-
if (
|
|
2437
|
-
|
|
2459
|
+
if (this._activeSample) {
|
|
2460
|
+
if (!this._activeSample.extensionStats) this._activeSample.extensionStats = [];
|
|
2461
|
+
this._activeSample.extensionStats.push(stat);
|
|
2462
|
+
this.addExtensionStats(stat);
|
|
2463
|
+
return;
|
|
2464
|
+
}
|
|
2465
|
+
if (!this._pendingInjections.extensionStats) this._pendingInjections.extensionStats = [];
|
|
2466
|
+
this._pendingInjections.extensionStats.push(stat);
|
|
2438
2467
|
}
|
|
2439
|
-
injectAttachment(
|
|
2468
|
+
injectAttachment(attachments) {
|
|
2440
2469
|
if (this.closed) return;
|
|
2441
|
-
if (
|
|
2442
|
-
|
|
2470
|
+
if (this._activeSample) {
|
|
2471
|
+
if (!this._activeSample.attachments) this._activeSample.attachments = {};
|
|
2472
|
+
Object.assign(this._activeSample.attachments, attachments);
|
|
2473
|
+
this.attachments = this._activeSample.attachments;
|
|
2474
|
+
return;
|
|
2475
|
+
}
|
|
2476
|
+
if (!this._pendingInjections.attachments) this._pendingInjections.attachments = {};
|
|
2477
|
+
Object.assign(this._pendingInjections.attachments, attachments);
|
|
2443
2478
|
}
|
|
2444
2479
|
addMetadata(metadata) {
|
|
2445
2480
|
if (this.closed) return;
|
|
@@ -2701,35 +2736,53 @@ var ObservedClient = class extends import_events2.EventEmitter {
|
|
|
2701
2736
|
observedPeerConnection.accept(sample, context);
|
|
2702
2737
|
return observedPeerConnection;
|
|
2703
2738
|
}
|
|
2704
|
-
|
|
2739
|
+
_mergePendingInjections(sample) {
|
|
2705
2740
|
if (this.closed) return sample;
|
|
2706
|
-
if (this.
|
|
2741
|
+
if (this._pendingInjections.clientEvents) {
|
|
2707
2742
|
if (!sample.clientEvents) sample.clientEvents = [];
|
|
2708
|
-
sample.clientEvents.push(...this.
|
|
2709
|
-
this.
|
|
2743
|
+
sample.clientEvents.push(...this._pendingInjections.clientEvents);
|
|
2744
|
+
this._pendingInjections.clientEvents = void 0;
|
|
2710
2745
|
}
|
|
2711
|
-
if (this.
|
|
2746
|
+
if (this._pendingInjections.clientIssues) {
|
|
2712
2747
|
if (!sample.clientIssues) sample.clientIssues = [];
|
|
2713
|
-
sample.clientIssues.push(...this.
|
|
2714
|
-
this.
|
|
2748
|
+
sample.clientIssues.push(...this._pendingInjections.clientIssues);
|
|
2749
|
+
this._pendingInjections.clientIssues = void 0;
|
|
2715
2750
|
}
|
|
2716
|
-
if (this.
|
|
2751
|
+
if (this._pendingInjections.extensionStats) {
|
|
2717
2752
|
if (!sample.extensionStats) sample.extensionStats = [];
|
|
2718
|
-
sample.extensionStats.push(...this.
|
|
2719
|
-
this.
|
|
2753
|
+
sample.extensionStats.push(...this._pendingInjections.extensionStats);
|
|
2754
|
+
this._pendingInjections.extensionStats = void 0;
|
|
2720
2755
|
}
|
|
2721
|
-
if (this.
|
|
2756
|
+
if (this._pendingInjections.attachments) {
|
|
2722
2757
|
if (!sample.attachments) sample.attachments = {};
|
|
2723
|
-
Object.assign(sample.attachments, this.
|
|
2724
|
-
this.
|
|
2758
|
+
Object.assign(sample.attachments, this._pendingInjections.attachments);
|
|
2759
|
+
this._pendingInjections.attachments = void 0;
|
|
2725
2760
|
}
|
|
2726
|
-
if (this.
|
|
2761
|
+
if (this._pendingInjections.clientMetaItems) {
|
|
2727
2762
|
if (!sample.clientMetaItems) sample.clientMetaItems = [];
|
|
2728
|
-
sample.clientMetaItems.push(...this.
|
|
2729
|
-
this.
|
|
2763
|
+
sample.clientMetaItems.push(...this._pendingInjections.clientMetaItems);
|
|
2764
|
+
this._pendingInjections.clientMetaItems = void 0;
|
|
2730
2765
|
}
|
|
2731
2766
|
return sample;
|
|
2732
2767
|
}
|
|
2768
|
+
_flushPendingInjections() {
|
|
2769
|
+
const hasPending = Boolean(
|
|
2770
|
+
this._pendingInjections.clientEvents || this._pendingInjections.clientIssues || this._pendingInjections.extensionStats || this._pendingInjections.clientMetaItems || this._pendingInjections.attachments
|
|
2771
|
+
);
|
|
2772
|
+
if (!hasPending) return;
|
|
2773
|
+
const finalSample = { timestamp: Date.now() };
|
|
2774
|
+
this._mergePendingInjections(finalSample);
|
|
2775
|
+
for (const clientEvent of finalSample.clientEvents ?? []) this._processClientEvent(clientEvent);
|
|
2776
|
+
for (const metaData of finalSample.clientMetaItems ?? []) this.addMetadata(metaData);
|
|
2777
|
+
for (const issue of finalSample.clientIssues ?? []) this.addIssue(issue);
|
|
2778
|
+
for (const extensionStat of finalSample.extensionStats ?? []) this.addExtensionStats(extensionStat);
|
|
2779
|
+
if (finalSample.attachments) this.attachments = { ...this.attachments, ...finalSample.attachments };
|
|
2780
|
+
try {
|
|
2781
|
+
this.sink?.write(finalSample);
|
|
2782
|
+
} catch (err) {
|
|
2783
|
+
logger2.warn("ClientSampleSink.write failed during injection flush for client %s: %o", this.clientId, err);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2733
2786
|
/** Emit an Observer-bus event scoped to this client (or a peer connection under it). */
|
|
2734
2787
|
_notify(type, ...args) {
|
|
2735
2788
|
this.call.observer.emit(type, ...args);
|
|
@@ -3729,17 +3782,19 @@ var Observer = class extends import_events6.EventEmitter {
|
|
|
3729
3782
|
observedMediasoupRouter,
|
|
3730
3783
|
observer: this
|
|
3731
3784
|
};
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3785
|
+
if (settings.matchPeerConnectionByWebRtcTransportId) {
|
|
3786
|
+
const onPeerConnectionAdded = (observedPeerConnectionScope) => {
|
|
3787
|
+
if (!observedMediasoupRouter.webrtcTransportIds.has(observedPeerConnectionScope.observedPeerConnection.peerConnectionId)) return;
|
|
3788
|
+
this.emit("mediasoup-router-matched-with-peer-connection", {
|
|
3789
|
+
...observedMediasoupRouterScope,
|
|
3790
|
+
...observedPeerConnectionScope
|
|
3791
|
+
});
|
|
3792
|
+
};
|
|
3793
|
+
const stopMatching = () => this.off("peer-connection-added", onPeerConnectionAdded);
|
|
3794
|
+
this.on("peer-connection-added", onPeerConnectionAdded);
|
|
3795
|
+
this.once("observer-closed", stopMatching);
|
|
3796
|
+
observedMediasoupRouter.once("close", stopMatching);
|
|
3797
|
+
}
|
|
3743
3798
|
observedMediasoupRouter.once("close", () => {
|
|
3744
3799
|
this.observedMediasoupRouters.delete(observedMediasoupRouter.id);
|
|
3745
3800
|
this.emit("mediasoup-router-removed", observedMediasoupRouterScope);
|