@observertc/observer-js 1.3.0-rc.2 → 1.3.0-rc.4
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 +51 -20
- package/dist/index.d.mts +102 -78
- package/dist/index.d.ts +102 -78
- package/dist/index.js +135 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +135 -73
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -404,7 +404,7 @@ See [Mediasoup router observation](#mediasoup-router-observation) for the full d
|
|
|
404
404
|
| Event | Extra | Fires when |
|
|
405
405
|
|-------|-------|-----------|
|
|
406
406
|
| `client-added` | — | a client is created |
|
|
407
|
-
| `client-sink-created` | `{ sink:
|
|
407
|
+
| `client-sink-created` | `{ sink: Sink<ClientSample> }` | the client's sample sink was created through `saveClientSamples`; fires right after `client-added` |
|
|
408
408
|
| `client-updated` | `{ sample: ClientSample, elapsedTimeInMs: number, context?: AcceptContext }` | the client processed a sample |
|
|
409
409
|
| `client-closed` | — | the client closed |
|
|
410
410
|
| `client-joined` | — | first `CLIENT_JOINED` event seen |
|
|
@@ -480,7 +480,10 @@ type ObserverConfig<AppData = Record<string, unknown>> = {
|
|
|
480
480
|
createCallAppData?: (p: { callId: string; observer: Observer; acceptCtx?: AcceptContext }) => Record<string, unknown>;
|
|
481
481
|
createClientAppData?: (p: { clientId: string; observedCall: ObservedCall; acceptCtx?: AcceptContext }) => Record<string, unknown>;
|
|
482
482
|
// a JSONL file per client: return its path, or undefined for none (see Sinks).
|
|
483
|
-
|
|
483
|
+
// where each client's samples are saved: exactly one of jsonlPath / sink, each a per-client callback (see Sinks).
|
|
484
|
+
saveClientSamples?:
|
|
485
|
+
| { jsonlPath: (p: { id: string; clientId: string; observedCall: ObservedCall }) => string | undefined }
|
|
486
|
+
| { sink: (p: { id: string; clientId: string; observedCall: ObservedCall }) => Sink<ClientSample> | undefined };
|
|
484
487
|
// remote-track-resolver factory — produces a call's RemoteTrackResolver (see Remote track resolution).
|
|
485
488
|
createRemoteTrackResolver?: (observedCall: ObservedCall) => RemoteTrackResolver | undefined;
|
|
486
489
|
};
|
|
@@ -1929,6 +1932,9 @@ that are still open, since the observer does not close mediasoup routers.
|
|
|
1929
1932
|
| Method | Does |
|
|
1930
1933
|
|--------|------|
|
|
1931
1934
|
| `create(SinkClass, settings)` | constructs `new SinkClass(settings)` and registers it under `settings.id` (generated when omitted); `undefined` if the id is taken |
|
|
1935
|
+
| `add(sink): boolean` | registers a sink constructed elsewhere under its own `id`; `false` if the id is taken (the sink stays yours to end). Prefer `create` for sinks that open a resource on construction: it checks the id first |
|
|
1936
|
+
| `createFromDestination(id, destination)` | registers the sink a `SinkDestination` describes under `id`: `{ jsonlPath }` creates a `JsonlFileSink` (the id is checked first), `{ sink }` adds your sink (its `id` must be `id`). `undefined` if refused; a refused `sink` is ended |
|
|
1937
|
+
| `owns(id): boolean` | whether `id` was added to this registry itself, not to a descendant |
|
|
1932
1938
|
| `get<T>(id)` | the sink, if any (own or a descendant's) |
|
|
1933
1939
|
| `write(id, record): boolean` | writes to the sink; `false` if there is none, or on backpressure |
|
|
1934
1940
|
| `clear()` | ends every sink of the registry and its descendants |
|
|
@@ -1944,7 +1950,7 @@ and never crashes the process.
|
|
|
1944
1950
|
import type { Sink, SinkSettings } from '@observertc/observer-js';
|
|
1945
1951
|
|
|
1946
1952
|
interface Sink<T = unknown> { // typically: class X extends EventEmitter implements Sink<T>
|
|
1947
|
-
readonly id: string;
|
|
1953
|
+
readonly id: string; // unique per observer
|
|
1948
1954
|
write(record: T): boolean; // `false` once ended (nothing written), or on backpressure
|
|
1949
1955
|
end(): void; // idempotent; MUST emit `ended` synchronously, then flush and emit `close`
|
|
1950
1956
|
on / once / off(event, listener); // the events below
|
|
@@ -1974,21 +1980,38 @@ its id — refuse writes after it, and emit `close` / `error` for its destinatio
|
|
|
1974
1980
|
### Client samples
|
|
1975
1981
|
|
|
1976
1982
|
A client writes every accepted sample to the sink registered under its **client id** in its call's
|
|
1977
|
-
registry (`observedClient.sink` returns it), and
|
|
1978
|
-
|
|
1983
|
+
registry (`observedClient.sink` returns it), and ends that sink when it closes. The
|
|
1984
|
+
**`saveClientSamples`** config option decides it — exactly one of two per-client callbacks, the same
|
|
1985
|
+
`jsonlPath` / `sink` pair as `createExtensionStats` takes:
|
|
1986
|
+
|
|
1987
|
+
| `saveClientSamples` | Called per client with `{ id, observedCall, clientId }`, returns | Sink |
|
|
1988
|
+
|---------------------|------------------------------------------------------------------|------|
|
|
1989
|
+
| `{ jsonlPath }` | the path of the client's file, or `undefined` | a `JsonlFileSink` at that path, created by the library under the client id |
|
|
1990
|
+
| `{ sink }` | your `Sink<ClientSample>`, or `undefined` | that sink; its `id` must be `id` |
|
|
1979
1991
|
|
|
1980
1992
|
```ts
|
|
1981
1993
|
const observer = new Observer({
|
|
1982
|
-
|
|
1983
|
-
|
|
1994
|
+
saveClientSamples: {
|
|
1995
|
+
jsonlPath: ({ observedCall, clientId }) => `./stats/${observedCall.callId}__${clientId}.jsonl`,
|
|
1996
|
+
},
|
|
1984
1997
|
});
|
|
1985
1998
|
|
|
1986
1999
|
observer.on('client-sink-created', ({ observedClient, sink }) => {
|
|
1987
|
-
sink.once('close', () => uploadFile(sink.path)); // flushed & fd closed → ready
|
|
2000
|
+
if (sink instanceof JsonlFileSink) sink.once('close', () => uploadFile(sink.path)); // flushed & fd closed → ready
|
|
1988
2001
|
});
|
|
1989
2002
|
```
|
|
1990
2003
|
|
|
1991
|
-
|
|
2004
|
+
**A client's sample sink id is its client id.** The `id` the callbacks receive is that id, and
|
|
2005
|
+
with `jsonlPath` the library uses it for you. Sink ids are unique per observer, so **client ids
|
|
2006
|
+
must be unique across the observer's open calls** — client monitors generate uuids, so this
|
|
2007
|
+
normally holds. A client whose id is already a sink id gets no sink, and a warning is logged.
|
|
2008
|
+
|
|
2009
|
+
The callback is called only when the id is free, so a clash opens no file; `undefined` gives that
|
|
2010
|
+
client no sink. A returned sink with a different id is ended and not used. Giving both `jsonlPath`
|
|
2011
|
+
and `sink` is a compile error (and refused at runtime). For a JSONL file with a custom serializer,
|
|
2012
|
+
use `sink: ({ id }) => new JsonlFileSink({ id, path, serializeSample })`.
|
|
2013
|
+
|
|
2014
|
+
Any other destination works the same way — return your own implementation:
|
|
1992
2015
|
|
|
1993
2016
|
```ts
|
|
1994
2017
|
class HttpSink extends EventEmitter implements ClientSampleSink {
|
|
@@ -2017,9 +2040,14 @@ class HttpSink extends EventEmitter implements ClientSampleSink {
|
|
|
2017
2040
|
}
|
|
2018
2041
|
}
|
|
2019
2042
|
|
|
2020
|
-
|
|
2021
|
-
|
|
2043
|
+
new Observer({
|
|
2044
|
+
saveClientSamples: {
|
|
2045
|
+
sink: ({ id, clientId }) => new HttpSink({ id, url: `https://stats.example.com/${clientId}` }), // id: the client id
|
|
2046
|
+
},
|
|
2022
2047
|
});
|
|
2048
|
+
|
|
2049
|
+
// Or later, on `client-added`, if the client has no sink yet:
|
|
2050
|
+
// observedCall.sinks.create(HttpSink, { id: observedClient.clientId, url })
|
|
2023
2051
|
```
|
|
2024
2052
|
|
|
2025
2053
|
Client ids double as sink ids, so they must be unique across the observer's open calls (client
|
|
@@ -2037,8 +2065,11 @@ the call) or `observedMediasoupRouter` (a pipe transport's `getStats()`): the la
|
|
|
2037
2065
|
optionally a sink that receives every data. The API is the same on all three; a call is shown.
|
|
2038
2066
|
|
|
2039
2067
|
```ts
|
|
2040
|
-
// Register the id
|
|
2041
|
-
call.createExtensionStats({ id: `${call.callId}:sfu`,
|
|
2068
|
+
// Register the id, with a JSONL file that receives every data accepted for it.
|
|
2069
|
+
call.createExtensionStats({ id: `${call.callId}:sfu`, jsonlPath: './stats/sfu.jsonl' });
|
|
2070
|
+
|
|
2071
|
+
// Or with a sink of your own; its id must be the extension stats id.
|
|
2072
|
+
call.createExtensionStats({ id: `${call.callId}:metrics`, sink: new HttpSink({ id: `${call.callId}:metrics`, url }) });
|
|
2042
2073
|
|
|
2043
2074
|
// Your own polling: keep the last data, and write it to the sink with the same id, if any.
|
|
2044
2075
|
call.acceptExtensionStats(`${call.callId}:sfu`, await collectSfuStats(call.callId));
|
|
@@ -2049,7 +2080,7 @@ call.extensionStats.values; // Map<id, last data>
|
|
|
2049
2080
|
|
|
2050
2081
|
| Method | Behaviour |
|
|
2051
2082
|
|--------|-----------|
|
|
2052
|
-
| `createExtensionStats({ id,
|
|
2083
|
+
| `createExtensionStats({ id, jsonlPath? \| sink? })` | Registers `id`, optionally with a sink — at most one of: `jsonlPath`, the path of a JSONL file, for which a `JsonlFileSink` is created under `id`; or `sink`, a `Sink` of your own, whose `id` must be `id`. Either ends and replaces a sink the owner itself already holds under `id`. Returns the sink, or `undefined`: with neither, on a closed owner, when `id` is already in `extensionStats.values` (created or accepted before), with a `sink` whose id differs, or when another object of the observer holds a sink with this id. Giving both is a compile error (refused at runtime). A refused `sink` is ended; for `jsonlPath`, the id is checked before the file is opened |
|
|
2053
2084
|
| `acceptExtensionStats(id, data)` | Keeps `data` as the last data of `id` and writes it to the owner's sink with the same id, if there is one (a sink that throws is logged). `id` does not have to be created first. Dropped once the owner is closed |
|
|
2054
2085
|
| `getExtensionStats<T>(id)` | The last data accepted for `id`, or `undefined` |
|
|
2055
2086
|
| `extensionStats` | The owner's `ExtensionStatsRegistry`: `values` (`Map<id, last data>`), `closed`, `close()`, and `create` / `accept` / `get`, which the three methods above delegate to |
|
|
@@ -2057,12 +2088,12 @@ call.extensionStats.values; // Map<id, last data>
|
|
|
2057
2088
|
**Extension stats ids share the sink id namespace, so they must be unique per observer**: the sink
|
|
2058
2089
|
of extension stats `x` is the sink with id `x`. Consequently:
|
|
2059
2090
|
|
|
2060
|
-
- With `
|
|
2061
|
-
|
|
2062
|
-
-
|
|
2063
|
-
receives the data accepted from then on.
|
|
2064
|
-
- An id accepted before it was created can no longer be created with
|
|
2065
|
-
|
|
2091
|
+
- With `jsonlPath` or `sink`, a sink the owner itself already holds under the id is ended and replaced. A sink another object holds under it is never touched: no sink is created and the data
|
|
2092
|
+
is only kept.
|
|
2093
|
+
- With neither, a sink you add later under the id (`sinks.create(SinkClass, { id, ... })` or
|
|
2094
|
+
`sinks.add(sink)`) receives the data accepted from then on.
|
|
2095
|
+
- An id accepted before it was created can no longer be created with a sink; give it one with
|
|
2096
|
+
`sinks.create` / `sinks.add` instead.
|
|
2066
2097
|
|
|
2067
2098
|
**Lifecycle.** When the owner (the call, the router or the observer) closes, it closes its
|
|
2068
2099
|
`extensionStats`: data accepted afterwards is dropped, `createExtensionStats` returns `undefined`,
|
package/dist/index.d.mts
CHANGED
|
@@ -48,6 +48,23 @@ interface Sink<T = unknown> {
|
|
|
48
48
|
once(event: keyof SinkEvents, listener: (...args: any[]) => void): this;
|
|
49
49
|
off(event: keyof SinkEvents, listener: (...args: any[]) => void): this;
|
|
50
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Where records go, exactly one of: `jsonlPath`, the path of a JSONL file (a `JsonlFileSink` is
|
|
53
|
+
* created there), or `sink`, a sink of your own. Giving both is a compile error.
|
|
54
|
+
*/
|
|
55
|
+
type SinkDestination<T = unknown> = {
|
|
56
|
+
jsonlPath: string;
|
|
57
|
+
sink?: never;
|
|
58
|
+
} | {
|
|
59
|
+
sink: Sink<T>;
|
|
60
|
+
jsonlPath?: never;
|
|
61
|
+
};
|
|
62
|
+
/** What the `saveClientSamples` callbacks receive: `id` is the client's sample sink id, the client id. */
|
|
63
|
+
type ClientSinkParams<Call = unknown> = {
|
|
64
|
+
id: string;
|
|
65
|
+
observedCall: Call;
|
|
66
|
+
clientId: string;
|
|
67
|
+
};
|
|
51
68
|
|
|
52
69
|
/** A sink class `SinkRegistry.create` can construct: its constructor takes one settings object (with `id`). */
|
|
53
70
|
type SinkConstructor = new (settings: any) => Sink<any>;
|
|
@@ -70,6 +87,7 @@ type SinkCreateSettings<C extends SinkConstructor> = Omit<ConstructorParameters<
|
|
|
70
87
|
declare class SinkRegistry {
|
|
71
88
|
readonly parent?: SinkRegistry | undefined;
|
|
72
89
|
private readonly _sinks;
|
|
90
|
+
private readonly _ownIds;
|
|
73
91
|
constructor(parent?: SinkRegistry | undefined);
|
|
74
92
|
/** Number of open sinks in this registry and its descendants. */
|
|
75
93
|
get size(): number;
|
|
@@ -82,21 +100,41 @@ declare class SinkRegistry {
|
|
|
82
100
|
* `undefined` is returned and a warning logged.
|
|
83
101
|
*/
|
|
84
102
|
create<C extends SinkConstructor>(SinkClass: C, settings: SinkCreateSettings<C>): InstanceType<C> | undefined;
|
|
103
|
+
/**
|
|
104
|
+
* Register a sink constructed elsewhere, under its own `id`. Returns `false`, registering nothing,
|
|
105
|
+
* when the id is already in use in the observer (the sink is then the caller's to end). Prefer
|
|
106
|
+
* `create` when the sink opens a resource on construction: it checks the id first.
|
|
107
|
+
*/
|
|
108
|
+
add(sink: Sink<any>): boolean;
|
|
109
|
+
/**
|
|
110
|
+
* Register the sink a `SinkDestination` describes, under `id`: for `jsonlPath`, a `JsonlFileSink`
|
|
111
|
+
* is created (the id is checked before the file is opened); a `sink` is added, and its `id` must
|
|
112
|
+
* be `id`. Returns `undefined` when the id is taken, the ids differ, or both are given (possible
|
|
113
|
+
* from JavaScript); a given `sink` that is not registered is ended.
|
|
114
|
+
*/
|
|
115
|
+
createFromDestination<T>(id: string, destination: SinkDestination<T>): Sink<T> | undefined;
|
|
116
|
+
/** Whether the sink `id` was added to this registry itself, rather than to a descendant. */
|
|
117
|
+
owns(id: string): boolean;
|
|
85
118
|
/** Write `record` to the sink `id`. `false` when there is no such sink, or on backpressure. */
|
|
86
119
|
write(id: string, record: unknown): boolean;
|
|
87
120
|
/** End every sink of this registry and of its descendants. */
|
|
88
121
|
clear(): void;
|
|
89
122
|
/** This registry and its ancestors, up to the root. */
|
|
123
|
+
private _register;
|
|
90
124
|
private _lineage;
|
|
91
125
|
}
|
|
92
126
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
127
|
+
/**
|
|
128
|
+
* The settings of `createExtensionStats` on an observer, a call or a mediasoup router: the `id`,
|
|
129
|
+
* and at most one of `jsonlPath` and `sink` (a `SinkDestination`).
|
|
130
|
+
*/
|
|
131
|
+
type CreateExtensionStatsSettings<T = unknown> = {
|
|
95
132
|
/** Unique per observer: it is also the id of the sink that receives the stats. */
|
|
96
133
|
id: string;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
134
|
+
} & (SinkDestination<T> | {
|
|
135
|
+
jsonlPath?: never;
|
|
136
|
+
sink?: never;
|
|
137
|
+
});
|
|
100
138
|
/**
|
|
101
139
|
* The extension stats of one object — `observer.extensionStats`, `observedCall.extensionStats`,
|
|
102
140
|
* `observedMediasoupRouter.extensionStats`: the last data per id (`values`), written to the
|
|
@@ -118,7 +156,7 @@ declare class ExtensionStatsRegistry {
|
|
|
118
156
|
* owner's `sinks.clear()`. Called by the owner when it closes.
|
|
119
157
|
*/
|
|
120
158
|
close(): void;
|
|
121
|
-
create<
|
|
159
|
+
create<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
122
160
|
accept(id: string, data: unknown): void;
|
|
123
161
|
get<T>(id: string): T | undefined;
|
|
124
162
|
}
|
|
@@ -3147,39 +3185,6 @@ declare function isClientIssueResolutionEntry(issue: ClientIssue): boolean;
|
|
|
3147
3185
|
/** Strip the `-resolved` suffix, so both entries of a lifecycle share one logical type. */
|
|
3148
3186
|
declare function baseIssueType(type: string): string;
|
|
3149
3187
|
|
|
3150
|
-
type JsonlFileSinkOptions<T = ClientSample> = Partial<SinkSettings> & {
|
|
3151
|
-
/** Absolute or relative file path to append JSONL lines to. */
|
|
3152
|
-
path: string;
|
|
3153
|
-
/** Optional custom serializer; defaults to `JSON.stringify`. */
|
|
3154
|
-
serializeSample?: (record: T) => string;
|
|
3155
|
-
};
|
|
3156
|
-
/**
|
|
3157
|
-
* A sink that serializes each record to a JSON line and appends it to a file. Defaults to client
|
|
3158
|
-
* samples, but takes any record type: `sinks.create(JsonlFileSink<PipeTransportStats[]>, { id, path })`. It wraps an
|
|
3159
|
-
* `fs.WriteStream` and re-emits its lifecycle events, so `close` fires once the file is flushed
|
|
3160
|
-
* and its descriptor is closed (file ready) and `error` surfaces file errors.
|
|
3161
|
-
*/
|
|
3162
|
-
declare interface JsonlFileSink {
|
|
3163
|
-
on<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
3164
|
-
once<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
3165
|
-
off<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
3166
|
-
emit<E extends keyof SinkEvents>(event: E, ...args: SinkEvents[E]): boolean;
|
|
3167
|
-
}
|
|
3168
|
-
declare class JsonlFileSink<T = ClientSample> extends EventEmitter implements Sink<T> {
|
|
3169
|
-
readonly id: string;
|
|
3170
|
-
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
3171
|
-
readonly path: string;
|
|
3172
|
-
private readonly _stream;
|
|
3173
|
-
private readonly _serializeSample;
|
|
3174
|
-
private _ended;
|
|
3175
|
-
constructor(options: JsonlFileSinkOptions<T>);
|
|
3176
|
-
get ended(): boolean;
|
|
3177
|
-
write(record: T): boolean;
|
|
3178
|
-
end(): void;
|
|
3179
|
-
}
|
|
3180
|
-
/** Build a JSONL file sink for a single path. */
|
|
3181
|
-
declare function createJsonlFileSink<T = ClientSample>(options: JsonlFileSinkOptions<T>): JsonlFileSink<T>;
|
|
3182
|
-
|
|
3183
3188
|
/** mediasoup `TransportTuple` (the selected local/remote address pair of a transport). */
|
|
3184
3189
|
type TransportTuple = {
|
|
3185
3190
|
localAddress: string;
|
|
@@ -3467,7 +3472,7 @@ declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> =
|
|
|
3467
3472
|
*/
|
|
3468
3473
|
snapshot(): MediasoupRouterSample;
|
|
3469
3474
|
close(): void;
|
|
3470
|
-
createExtensionStats<
|
|
3475
|
+
createExtensionStats<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
3471
3476
|
acceptExtensionStats(id: string, data: unknown): void;
|
|
3472
3477
|
getExtensionStats<T>(id: string): T | undefined;
|
|
3473
3478
|
addTransport: (transport: types.Transport) => void;
|
|
@@ -3565,7 +3570,7 @@ type ObserverEvents = {
|
|
|
3565
3570
|
}];
|
|
3566
3571
|
'client-added': [ObservedClientScope];
|
|
3567
3572
|
'client-sink-created': [ObservedClientScope & {
|
|
3568
|
-
sink:
|
|
3573
|
+
sink: Sink<ClientSample>;
|
|
3569
3574
|
}];
|
|
3570
3575
|
'client-updated': [ObservedClientScope & {
|
|
3571
3576
|
sample: ClientSample;
|
|
@@ -4005,7 +4010,7 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
4005
4010
|
/** The distinct `publicAddress`es currently known by this client's open peer connections. */
|
|
4006
4011
|
/**
|
|
4007
4012
|
* The sink this client's samples are written to: whatever is registered under its client id in
|
|
4008
|
-
* `call.sinks` (the `
|
|
4013
|
+
* `call.sinks` (the one created through `saveClientSamples`, or a sink the application added), if any.
|
|
4009
4014
|
*/
|
|
4010
4015
|
get sink(): Sink<ClientSample> | undefined;
|
|
4011
4016
|
get publicAddresses(): string[];
|
|
@@ -5874,7 +5879,7 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
5874
5879
|
*/
|
|
5875
5880
|
addIssue(issue: Omit<CallIssue, 'scope'>): void;
|
|
5876
5881
|
close(): void;
|
|
5877
|
-
createExtensionStats<
|
|
5882
|
+
createExtensionStats<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
5878
5883
|
acceptExtensionStats(id: string, data: unknown): void;
|
|
5879
5884
|
getExtensionStats<T>(id: string): T | undefined;
|
|
5880
5885
|
getObservedClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(clientId: string): ObservedClient<ClientAppData> | undefined;
|
|
@@ -5884,6 +5889,7 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
5884
5889
|
private _onClientUpdate;
|
|
5885
5890
|
private _clientJoined;
|
|
5886
5891
|
private _clientLeft;
|
|
5892
|
+
private _createClientSink;
|
|
5887
5893
|
/** Emit an Observer-bus event scoped to this call. */
|
|
5888
5894
|
private _notify;
|
|
5889
5895
|
}
|
|
@@ -6445,6 +6451,14 @@ type ClientAppDataFactory = (params: {
|
|
|
6445
6451
|
observedCall: ObservedCall;
|
|
6446
6452
|
acceptCtx?: AcceptContext;
|
|
6447
6453
|
}) => Record<string, unknown>;
|
|
6454
|
+
/** `ObserverConfig.saveClientSamples`: exactly one of the two per-client callbacks. */
|
|
6455
|
+
type SaveClientSamplesConfig = {
|
|
6456
|
+
jsonlPath: (params: ClientSinkParams<ObservedCall>) => string | undefined;
|
|
6457
|
+
sink?: never;
|
|
6458
|
+
} | {
|
|
6459
|
+
sink: (params: ClientSinkParams<ObservedCall>) => Sink<ClientSample> | undefined;
|
|
6460
|
+
jsonlPath?: never;
|
|
6461
|
+
};
|
|
6448
6462
|
type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unknown>> = {
|
|
6449
6463
|
/**
|
|
6450
6464
|
* Application-owned data for the observer itself. Never read or modified by the library.
|
|
@@ -6598,38 +6612,15 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
|
|
|
6598
6612
|
/** Same as `createCallAppData`, for clients. Receives the (already-created) parent call. */
|
|
6599
6613
|
createClientAppData?: ClientAppDataFactory;
|
|
6600
6614
|
/**
|
|
6601
|
-
*
|
|
6602
|
-
* client
|
|
6603
|
-
* `
|
|
6604
|
-
* announces it, so a `close` handler can upload or move the file once it is flushed.
|
|
6605
|
-
*
|
|
6606
|
-
* For any other destination, add your own sink under the client id instead, e.g. on
|
|
6607
|
-
* `client-added`: `observedCall.sinks.create(MySink, { id: observedClient.clientId, ... })`.
|
|
6608
|
-
*
|
|
6609
|
-
* Replaces `createClientSink` (removed in 1.3.0), and with it `createJsonlFileSinkFactory`.
|
|
6610
|
-
* To migrate:
|
|
6615
|
+
* Where each client's samples are saved: exactly one of `jsonlPath` (return the path of the
|
|
6616
|
+
* client's JSONL file) and `sink` (return a sink whose `id` is `params.id`), each called per
|
|
6617
|
+
* client; `undefined` gives that client no sink. See the README's *Sinks* section.
|
|
6611
6618
|
*
|
|
6612
|
-
*
|
|
6613
|
-
*
|
|
6614
|
-
*
|
|
6615
|
-
* // after: return the path; `${callId}__${clientId}.jsonl` was the factory's default file name
|
|
6616
|
-
* new Observer({
|
|
6617
|
-
* createClientJsonlSink: ({ observedCall, clientId }) => path.join('./stats', `${observedCall.callId}__${clientId}.jsonl`),
|
|
6618
|
-
* });
|
|
6619
|
-
*
|
|
6620
|
-
* // before (1.2): any other sink
|
|
6621
|
-
* new Observer({ createClientSink: ({ clientId, observedCall }) => new HttpSink(url(observedCall, clientId)) });
|
|
6622
|
-
* // after: create it under the client id when the client is added (HttpSink implements
|
|
6623
|
-
* // `ClientSampleSink`, takes `SinkSettings & { ... }` and emits `ended` from `end()`)
|
|
6624
|
-
* observer.on('client-added', ({ observedCall, observedClient }) => {
|
|
6625
|
-
* observedCall.sinks.create(HttpSink, { id: observedClient.clientId, url: url(observedCall, observedClient.clientId) });
|
|
6626
|
-
* });
|
|
6627
|
-
* ```
|
|
6619
|
+
* A client's sample sink id is its client id (`params.id`). Sink ids are unique per observer, so
|
|
6620
|
+
* client ids must be unique across the observer's open calls; a client whose id is already a sink
|
|
6621
|
+
* id gets no sink, and a warning is logged.
|
|
6628
6622
|
*/
|
|
6629
|
-
|
|
6630
|
-
observedCall: ObservedCall;
|
|
6631
|
-
clientId: string;
|
|
6632
|
-
}) => string | undefined;
|
|
6623
|
+
saveClientSamples?: SaveClientSamplesConfig;
|
|
6633
6624
|
/**
|
|
6634
6625
|
* Optional factory invoked when a call is created, producing the call's `RemoteTrackResolver`
|
|
6635
6626
|
* (or `undefined` for none). Use the built-ins
|
|
@@ -6800,7 +6791,7 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
6800
6791
|
matchPeerConnectionByWebRtcTransportId?: boolean;
|
|
6801
6792
|
}): ObservedMediasoupRouter<Record<string, unknown>> | undefined;
|
|
6802
6793
|
close(): void;
|
|
6803
|
-
createExtensionStats<
|
|
6794
|
+
createExtensionStats<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
6804
6795
|
acceptExtensionStats(id: string, data: unknown): void;
|
|
6805
6796
|
getExtensionStats<T>(id: string): T | undefined;
|
|
6806
6797
|
accept(sample: ClientSample, context?: AcceptContext): void;
|
|
@@ -7199,9 +7190,9 @@ declare function setObserverLogger(logger: ObserverLogger): void;
|
|
|
7199
7190
|
/** The lifecycle events a client sample sink may emit. Same as {@link SinkEvents}. */
|
|
7200
7191
|
type ClientSampleSinkEvents = SinkEvents;
|
|
7201
7192
|
/**
|
|
7202
|
-
* A per-client destination for accepted samples.
|
|
7203
|
-
*
|
|
7204
|
-
* `
|
|
7193
|
+
* A per-client destination for accepted samples. Return one from `ObserverConfig.saveClientSamples.sink`
|
|
7194
|
+
* (with the `id` it receives), or create one under the client id with
|
|
7195
|
+
* `observedCall.sinks.create(MySink, { id: clientId, ... })`.
|
|
7205
7196
|
*
|
|
7206
7197
|
* The library calls `write(sample)` synchronously per accepted sample (not awaited) and `end()`s
|
|
7207
7198
|
* the sink when the client closes; the registry attaches an `error` listener so a failing sink
|
|
@@ -7210,6 +7201,39 @@ type ClientSampleSinkEvents = SinkEvents;
|
|
|
7210
7201
|
*/
|
|
7211
7202
|
type ClientSampleSink = Sink<ClientSample>;
|
|
7212
7203
|
|
|
7204
|
+
type JsonlFileSinkOptions<T = ClientSample> = Partial<SinkSettings> & {
|
|
7205
|
+
/** Absolute or relative file path to append JSONL lines to. */
|
|
7206
|
+
path: string;
|
|
7207
|
+
/** Optional custom serializer; defaults to `JSON.stringify`. */
|
|
7208
|
+
serializeSample?: (record: T) => string;
|
|
7209
|
+
};
|
|
7210
|
+
/**
|
|
7211
|
+
* A sink that serializes each record to a JSON line and appends it to a file. Defaults to client
|
|
7212
|
+
* samples, but takes any record type: `sinks.create(JsonlFileSink<PipeTransportStats[]>, { id, path })`. It wraps an
|
|
7213
|
+
* `fs.WriteStream` and re-emits its lifecycle events, so `close` fires once the file is flushed
|
|
7214
|
+
* and its descriptor is closed (file ready) and `error` surfaces file errors.
|
|
7215
|
+
*/
|
|
7216
|
+
declare interface JsonlFileSink {
|
|
7217
|
+
on<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7218
|
+
once<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7219
|
+
off<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7220
|
+
emit<E extends keyof SinkEvents>(event: E, ...args: SinkEvents[E]): boolean;
|
|
7221
|
+
}
|
|
7222
|
+
declare class JsonlFileSink<T = ClientSample> extends EventEmitter implements Sink<T> {
|
|
7223
|
+
readonly id: string;
|
|
7224
|
+
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
7225
|
+
readonly path: string;
|
|
7226
|
+
private readonly _stream;
|
|
7227
|
+
private readonly _serializeSample;
|
|
7228
|
+
private _ended;
|
|
7229
|
+
constructor(options: JsonlFileSinkOptions<T>);
|
|
7230
|
+
get ended(): boolean;
|
|
7231
|
+
write(record: T): boolean;
|
|
7232
|
+
end(): void;
|
|
7233
|
+
}
|
|
7234
|
+
/** Build a JSONL file sink for a single path. */
|
|
7235
|
+
declare function createJsonlFileSink<T = ClientSample>(options: JsonlFileSinkOptions<T>): JsonlFileSink<T>;
|
|
7236
|
+
|
|
7213
7237
|
declare interface InMemorySink {
|
|
7214
7238
|
on<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7215
7239
|
once<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
@@ -7236,4 +7260,4 @@ declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
|
|
|
7236
7260
|
declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
7237
7261
|
declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
7238
7262
|
|
|
7239
|
-
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, AudioEchoDetector, type AudioEchoDetectorConfig, AudioEchoTypes, type AudioInputDevice, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CalculatedScore, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssue, type CallIssueSpread, type CallScopedEventName, type CallSummary, type CallSummaryClients, CallSummaryCollector, type CallSummaryConfig, type CallSummaryEnricher, type CallSummaryEnrichers, type CallSummaryScores, type CallSummarySection, type CallSummaryTruncation, type CallSummaryTurnServers, type CertificateStats, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientLocation, type ClientLocationResolver, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, type ClientSampleSink, type ClientSampleSinkEvents, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CodecStats, type CorroboratedPublisherFault, type CreateExtensionStatsSettings, type DataChannelStats, type Detector, Detectors, type ExtensionStat, ExtensionStatsRegistry, GEOHASH_CELL_SIZES, type IceCandidatePairStats, type IceCandidateStats, type IceTransportStats, InMemorySink, type InboundRtpStats, type InboundTrackSample, type Issue, type IssueBase, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediaKind, type MediaPlayoutStats, type MediaSourceStats, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, MiddlewareProcessor, ObservedCall, type ObservedCallScope, type ObservedCallSettings, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, type ObservedClientSettings, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, ObservedTURN, type ObservedTURNEventMap, ObservedTurnServer, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type OutboundRtpStats, type OutboundTrackSample, type PageHinkleyResult, type PeerConnectionSample, type PeerConnectionTransportStats, type PsnrSum, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, type QualityLimitationDurations, RESOLVED_ISSUE_SUFFIX, RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS, RTT_HISTOGRAM_NUMBER_OF_BUCKETS, type RemoteInboundRtpStats, type RemoteOutboundRtpStats, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RttSummary, type RunningValidator, SCORE_HISTOGRAM_BUCKET_WIDTH, SCORE_HISTOGRAM_NUMBER_OF_BUCKETS, type SampleRejectedReason, type ScoreCalculator, type ScoreSummary, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, type Sink, type SinkConstructor, type SinkCreateSettings, type SinkEvents, SinkRegistry, type SinkSettings, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createCallSummary, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createLogger, createP2pRemoteTrackResolverFactory, defaultCallSummaryConfig, defaultClientHealthThresholds, geohash, isClientIssueResolutionEntry, isPublicIpAddress, issuePayloadAsString, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfHistogram, percentileOfSorted, robustZScore, schemaVersion, setObserverLogger, summarize };
|
|
7263
|
+
export { type AcceptContext, type AcceptMiddleware, type AcceptMiddlewarePayload, type ActiveClientIssue, type ActiveIssueTracker, ActiveIssuesRegistry, AudioEchoDetector, type AudioEchoDetectorConfig, AudioEchoTypes, type AudioInputDevice, type AvailableCallScopeDetectorsConfigs, type AvailableDetectorsConfigs, type AvailableObserverScopeDetectorsConfigs, type AvailableValidatorConfigs, CODEC_MISMATCH_ISSUE, type CalculatedScore, type CallAppDataFactory, CallConcurrentIssueDetector, type CallConcurrentIssueDetectorConfig, type CallConcurrentIssueGroup, CallConcurrentIssueTypes, type CallHealth, CallHealthAggregator, type CallIssue, type CallIssueSpread, type CallScopedEventName, type CallSummary, type CallSummaryClients, CallSummaryCollector, type CallSummaryConfig, type CallSummaryEnricher, type CallSummaryEnrichers, type CallSummaryScores, type CallSummarySection, type CallSummaryTruncation, type CallSummaryTurnServers, type CertificateStats, type ClientAppDataFactory, type ClientEvent, ClientEventTypes, type ClientHealth, type ClientHealthThresholds, type ClientIssue, type ClientLocation, type ClientLocationResolver, type ClientMetaData, ClientMetaTypes, type ClientPopulation, type ClientPopulationAxis, ClientPopulationIssueDetector, type ClientPopulationIssueDetectorConfig, ClientPopulationIssueTypes, type ClientSample, type ClientSampleSink, type ClientSampleSinkEvents, type ClientSinkParams, type CodecConsistencyReportPayload, CodecConsistencyValidator, type CodecConsistencyValidatorConfig, type CodecEvidence, type CodecStats, type CorroboratedPublisherFault, type CreateExtensionStatsSettings, type DataChannelStats, type Detector, Detectors, type ExtensionStat, ExtensionStatsRegistry, GEOHASH_CELL_SIZES, type IceCandidatePairStats, type IceCandidateStats, type IceTransportStats, InMemorySink, type InboundRtpStats, type InboundTrackSample, type Issue, type IssueBase, type IssueConclusion, IssueFanOutDetector, type IssueFanOutDetectorConfig, IssueFanOutTypes, type IssueFaultDomain, JsonlFileSink, type JsonlFileSinkOptions, LOWEST_COMMON_DENOMINATOR_ISSUE, type Logger, type MannKendallResult, type MediaKind, type MediaPlayoutStats, type MediaSourceStats, type MediasoupConsumerSample, type MediasoupConsumerSampleEvent, type MediasoupDataConsumerSample, type MediasoupDataProducerSample, type MediasoupDirectTransportSample, type MediasoupDirectTransportSampleEventMap, type MediasoupPipeTransportSample, type MediasoupPipeTransportSampleEventMap, type MediasoupPlainTransportSample, type MediasoupPlainTransportSampleEventMap, type MediasoupProducerSample, type MediasoupProducerSampleEvent, type MediasoupRouterSample, type MediasoupSampleEnricher, type MediasoupTransportSample, type MediasoupWebRtcTransportSample, type MediasoupWebRtcTransportSampleEventMap, type Middleware, MiddlewareProcessor, ObservedCall, type ObservedCallScope, type ObservedCallSettings, ObservedCertificate, ObservedClient, ObservedClientIssueRegistry, type ObservedClientScope, type ObservedClientSettings, ObservedCodec, ObservedDataChannel, ObservedIceCandidate, ObservedIceCandidatePair, ObservedIceTransport, ObservedInboundRtp, ObservedInboundTrack, ObservedMediaPlayout, ObservedMediaSource, ObservedMediasoupRouter, type ObservedMediasoupRouterEvents, type ObservedMediasoupRouterScope, type ObservedMediasoupRouterSettings, ObservedOutboundRtp, ObservedOutboundTrack, ObservedPeerConnection, type ObservedPeerConnectionScope, ObservedPeerConnectionTransport, ObservedRemoteInboundRtp, ObservedRemoteOutboundRtp, ObservedTURN, type ObservedTURNEventMap, ObservedTurnServer, Observer, ObserverConcurrentIssueDetector, type ObserverConcurrentIssueDetectorConfig, type ObserverConcurrentIssueGroup, ObserverConcurrentIssueTypes, type ObserverEventBase, type ObserverEvents, type ObserverIssue, type ObserverIssueSpread, type ObserverLogger, type OutboundRtpStats, type OutboundTrackSample, type PageHinkleyResult, type PeerConnectionSample, type PeerConnectionTransportStats, type PsnrSum, PublisherFaultCorroborationDetector, type PublisherFaultCorroborationDetectorConfig, PublisherFaultTypes, type QualityLimitationDurations, RESOLVED_ISSUE_SUFFIX, RTT_HISTOGRAM_BUCKET_WIDTH_IN_MS, RTT_HISTOGRAM_NUMBER_OF_BUCKETS, type RemoteInboundRtpStats, type RemoteOutboundRtpStats, type RemoteTrackLinkEvidence, RemoteTrackResolver, type RemoteTrackResolverFactory, type RemoteTrackResolverReportPayload, RemoteTrackResolverValidator, type RemoteTrackResolverValidatorConfig, type RemoteTrackResolvers, type ResolvedActiveClientIssue, type RttSummary, type RunningValidator, SCORE_HISTOGRAM_BUCKET_WIDTH, SCORE_HISTOGRAM_NUMBER_OF_BUCKETS, type SampleRejectedReason, type SaveClientSamplesConfig, type ScoreCalculator, type ScoreSummary, SfuCongestionDetector, type SfuCongestionDetectorBucket, type SfuCongestionDetectorConfig, type SfuCongestionDetectorEvaluation, type SfuCongestionDetectorReport, type SimulcastReceiverEvidence, type SimulcastReceiverReportPayload, SimulcastReceiverValidator, type SimulcastReceiverValidatorConfig, type Sink, type SinkConstructor, type SinkCreateSettings, type SinkDestination, type SinkEvents, SinkRegistry, type SinkSettings, SlidingWindow, type SlidingWindowEntry, type StatsSummary, TrackDeliveryMismatchDetector, type TrackDeliveryMismatchDetectorConfig, TrackDeliveryMismatchTypes, TrendTester, type TrendTesterConfig, type TurnServerHealth, TurnServerHealthDetector, type TurnServerHealthDetectorConfig, TurnServerHealthTypes, TurnServerOutageDetector, type TurnServerOutageDetectorConfig, TurnServerOutageTypes, UNRESOLVED_TRACK_LINKS_ISSUE, UnconsumedTrackDetector, type UnconsumedTrackDetectorConfig, UnconsumedTrackTypes, type ValidationReport, type Validator, type ValidatorName, baseIssueType, concludeCallIssue, concludeObserverIssue, correlation, counterDelta, createCallSummary, createDefaultMediasoupRemoteTrackResolverFactory, createInMemorySink, createJsonlFileSink, createLogger, createP2pRemoteTrackResolverFactory, defaultCallSummaryConfig, defaultClientHealthThresholds, geohash, isClientIssueResolutionEntry, isPublicIpAddress, issuePayloadAsString, mannKendall, mannKendallVerdict, median, medianAbsoluteDeviation, pageHinkley, percentile, percentileOfHistogram, percentileOfSorted, robustZScore, schemaVersion, setObserverLogger, summarize };
|