@observertc/observer-js 1.3.0-rc.1 → 1.3.0-rc.3
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 +38 -19
- package/dist/index.d.mts +91 -74
- package/dist/index.d.ts +91 -74
- package/dist/index.js +124 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +124 -71
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -48,6 +48,17 @@ 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
|
+
};
|
|
51
62
|
|
|
52
63
|
/** A sink class `SinkRegistry.create` can construct: its constructor takes one settings object (with `id`). */
|
|
53
64
|
type SinkConstructor = new (settings: any) => Sink<any>;
|
|
@@ -70,6 +81,7 @@ type SinkCreateSettings<C extends SinkConstructor> = Omit<ConstructorParameters<
|
|
|
70
81
|
declare class SinkRegistry {
|
|
71
82
|
readonly parent?: SinkRegistry | undefined;
|
|
72
83
|
private readonly _sinks;
|
|
84
|
+
private readonly _ownIds;
|
|
73
85
|
constructor(parent?: SinkRegistry | undefined);
|
|
74
86
|
/** Number of open sinks in this registry and its descendants. */
|
|
75
87
|
get size(): number;
|
|
@@ -82,21 +94,41 @@ declare class SinkRegistry {
|
|
|
82
94
|
* `undefined` is returned and a warning logged.
|
|
83
95
|
*/
|
|
84
96
|
create<C extends SinkConstructor>(SinkClass: C, settings: SinkCreateSettings<C>): InstanceType<C> | undefined;
|
|
97
|
+
/**
|
|
98
|
+
* Register a sink constructed elsewhere, under its own `id`. Returns `false`, registering nothing,
|
|
99
|
+
* when the id is already in use in the observer (the sink is then the caller's to end). Prefer
|
|
100
|
+
* `create` when the sink opens a resource on construction: it checks the id first.
|
|
101
|
+
*/
|
|
102
|
+
add(sink: Sink<any>): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* Register the sink a `SinkDestination` describes, under `id`: for `jsonlPath`, a `JsonlFileSink`
|
|
105
|
+
* is created (the id is checked before the file is opened); a `sink` is added, and its `id` must
|
|
106
|
+
* be `id`. Returns `undefined` when the id is taken, the ids differ, or both are given (possible
|
|
107
|
+
* from JavaScript); a given `sink` that is not registered is ended.
|
|
108
|
+
*/
|
|
109
|
+
createFromDestination<T>(id: string, destination: SinkDestination<T>): Sink<T> | undefined;
|
|
110
|
+
/** Whether the sink `id` was added to this registry itself, rather than to a descendant. */
|
|
111
|
+
owns(id: string): boolean;
|
|
85
112
|
/** Write `record` to the sink `id`. `false` when there is no such sink, or on backpressure. */
|
|
86
113
|
write(id: string, record: unknown): boolean;
|
|
87
114
|
/** End every sink of this registry and of its descendants. */
|
|
88
115
|
clear(): void;
|
|
89
116
|
/** This registry and its ancestors, up to the root. */
|
|
117
|
+
private _register;
|
|
90
118
|
private _lineage;
|
|
91
119
|
}
|
|
92
120
|
|
|
93
|
-
/**
|
|
94
|
-
|
|
121
|
+
/**
|
|
122
|
+
* The settings of `createExtensionStats` on an observer, a call or a mediasoup router: the `id`,
|
|
123
|
+
* and at most one of `jsonlPath` and `sink` (a `SinkDestination`).
|
|
124
|
+
*/
|
|
125
|
+
type CreateExtensionStatsSettings<T = unknown> = {
|
|
95
126
|
/** Unique per observer: it is also the id of the sink that receives the stats. */
|
|
96
127
|
id: string;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
128
|
+
} & (SinkDestination<T> | {
|
|
129
|
+
jsonlPath?: never;
|
|
130
|
+
sink?: never;
|
|
131
|
+
});
|
|
100
132
|
/**
|
|
101
133
|
* The extension stats of one object — `observer.extensionStats`, `observedCall.extensionStats`,
|
|
102
134
|
* `observedMediasoupRouter.extensionStats`: the last data per id (`values`), written to the
|
|
@@ -118,7 +150,7 @@ declare class ExtensionStatsRegistry {
|
|
|
118
150
|
* owner's `sinks.clear()`. Called by the owner when it closes.
|
|
119
151
|
*/
|
|
120
152
|
close(): void;
|
|
121
|
-
create<
|
|
153
|
+
create<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
122
154
|
accept(id: string, data: unknown): void;
|
|
123
155
|
get<T>(id: string): T | undefined;
|
|
124
156
|
}
|
|
@@ -3147,39 +3179,6 @@ declare function isClientIssueResolutionEntry(issue: ClientIssue): boolean;
|
|
|
3147
3179
|
/** Strip the `-resolved` suffix, so both entries of a lifecycle share one logical type. */
|
|
3148
3180
|
declare function baseIssueType(type: string): string;
|
|
3149
3181
|
|
|
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
3182
|
/** mediasoup `TransportTuple` (the selected local/remote address pair of a transport). */
|
|
3184
3183
|
type TransportTuple = {
|
|
3185
3184
|
localAddress: string;
|
|
@@ -3467,7 +3466,7 @@ declare class ObservedMediasoupRouter<AppData extends Record<string, unknown> =
|
|
|
3467
3466
|
*/
|
|
3468
3467
|
snapshot(): MediasoupRouterSample;
|
|
3469
3468
|
close(): void;
|
|
3470
|
-
createExtensionStats<
|
|
3469
|
+
createExtensionStats<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
3471
3470
|
acceptExtensionStats(id: string, data: unknown): void;
|
|
3472
3471
|
getExtensionStats<T>(id: string): T | undefined;
|
|
3473
3472
|
addTransport: (transport: types.Transport) => void;
|
|
@@ -3565,7 +3564,7 @@ type ObserverEvents = {
|
|
|
3565
3564
|
}];
|
|
3566
3565
|
'client-added': [ObservedClientScope];
|
|
3567
3566
|
'client-sink-created': [ObservedClientScope & {
|
|
3568
|
-
sink:
|
|
3567
|
+
sink: Sink<ClientSample>;
|
|
3569
3568
|
}];
|
|
3570
3569
|
'client-updated': [ObservedClientScope & {
|
|
3571
3570
|
sample: ClientSample;
|
|
@@ -4005,7 +4004,7 @@ declare class ObservedClient<AppData extends Record<string, unknown> = Record<st
|
|
|
4005
4004
|
/** The distinct `publicAddress`es currently known by this client's open peer connections. */
|
|
4006
4005
|
/**
|
|
4007
4006
|
* The sink this client's samples are written to: whatever is registered under its client id in
|
|
4008
|
-
* `call.sinks` (the `
|
|
4007
|
+
* `call.sinks` (the one `createClientSink` returned, or a sink the application added), if any.
|
|
4009
4008
|
*/
|
|
4010
4009
|
get sink(): Sink<ClientSample> | undefined;
|
|
4011
4010
|
get publicAddresses(): string[];
|
|
@@ -5874,7 +5873,7 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
5874
5873
|
*/
|
|
5875
5874
|
addIssue(issue: Omit<CallIssue, 'scope'>): void;
|
|
5876
5875
|
close(): void;
|
|
5877
|
-
createExtensionStats<
|
|
5876
|
+
createExtensionStats<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
5878
5877
|
acceptExtensionStats(id: string, data: unknown): void;
|
|
5879
5878
|
getExtensionStats<T>(id: string): T | undefined;
|
|
5880
5879
|
getObservedClient<ClientAppData extends Record<string, unknown> = Record<string, unknown>>(clientId: string): ObservedClient<ClientAppData> | undefined;
|
|
@@ -5885,6 +5884,7 @@ declare class ObservedCall<AppData extends Record<string, unknown> = Record<stri
|
|
|
5885
5884
|
private _clientJoined;
|
|
5886
5885
|
private _clientLeft;
|
|
5887
5886
|
/** Emit an Observer-bus event scoped to this call. */
|
|
5887
|
+
private _createClientSink;
|
|
5888
5888
|
private _notify;
|
|
5889
5889
|
}
|
|
5890
5890
|
|
|
@@ -6598,38 +6598,22 @@ type ObserverConfig<AppData extends Record<string, unknown> = Record<string, unk
|
|
|
6598
6598
|
/** Same as `createCallAppData`, for clients. Receives the (already-created) parent call. */
|
|
6599
6599
|
createClientAppData?: ClientAppDataFactory;
|
|
6600
6600
|
/**
|
|
6601
|
-
*
|
|
6602
|
-
*
|
|
6603
|
-
* `
|
|
6604
|
-
*
|
|
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:
|
|
6601
|
+
* The sink a newly-created client writes its samples to: `{ jsonlPath }` (a `JsonlFileSink` is
|
|
6602
|
+
* created at that path under the client id), `{ sink }` (a `Sink` whose `id` is `params.id`, the
|
|
6603
|
+
* client id), or `undefined` for none. The sink is added to `observedCall.sinks` and is ended
|
|
6604
|
+
* when the client closes; `client-sink-created` announces it.
|
|
6611
6605
|
*
|
|
6612
6606
|
* ```ts
|
|
6613
|
-
*
|
|
6614
|
-
* new Observer({ createClientSink: createJsonlFileSinkFactory({ directory: './stats' }) });
|
|
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
|
-
* });
|
|
6607
|
+
* createClientSink: ({ observedCall, clientId }) => ({ jsonlPath: `./stats/${observedCall.callId}__${clientId}.jsonl` }),
|
|
6627
6608
|
* ```
|
|
6609
|
+
*
|
|
6610
|
+
* Called only when the client id is free as a sink id, so a clash opens no file.
|
|
6628
6611
|
*/
|
|
6629
|
-
|
|
6612
|
+
createClientSink?: (params: {
|
|
6613
|
+
id: string;
|
|
6630
6614
|
observedCall: ObservedCall;
|
|
6631
6615
|
clientId: string;
|
|
6632
|
-
}) =>
|
|
6616
|
+
}) => SinkDestination<ClientSample> | undefined;
|
|
6633
6617
|
/**
|
|
6634
6618
|
* Optional factory invoked when a call is created, producing the call's `RemoteTrackResolver`
|
|
6635
6619
|
* (or `undefined` for none). Use the built-ins
|
|
@@ -6800,7 +6784,7 @@ declare class Observer<AppData extends Record<string, unknown> = Record<string,
|
|
|
6800
6784
|
matchPeerConnectionByWebRtcTransportId?: boolean;
|
|
6801
6785
|
}): ObservedMediasoupRouter<Record<string, unknown>> | undefined;
|
|
6802
6786
|
close(): void;
|
|
6803
|
-
createExtensionStats<
|
|
6787
|
+
createExtensionStats<T = unknown>(settings: CreateExtensionStatsSettings<T>): Sink<T> | undefined;
|
|
6804
6788
|
acceptExtensionStats(id: string, data: unknown): void;
|
|
6805
6789
|
getExtensionStats<T>(id: string): T | undefined;
|
|
6806
6790
|
accept(sample: ClientSample, context?: AcceptContext): void;
|
|
@@ -7199,9 +7183,9 @@ declare function setObserverLogger(logger: ObserverLogger): void;
|
|
|
7199
7183
|
/** The lifecycle events a client sample sink may emit. Same as {@link SinkEvents}. */
|
|
7200
7184
|
type ClientSampleSinkEvents = SinkEvents;
|
|
7201
7185
|
/**
|
|
7202
|
-
* A per-client destination for accepted samples.
|
|
7203
|
-
*
|
|
7204
|
-
* `
|
|
7186
|
+
* A per-client destination for accepted samples. Return one from `ObserverConfig.createClientSink`
|
|
7187
|
+
* (with the `id` it receives), or create one under the client id with
|
|
7188
|
+
* `observedCall.sinks.create(MySink, { id: clientId, ... })`.
|
|
7205
7189
|
*
|
|
7206
7190
|
* The library calls `write(sample)` synchronously per accepted sample (not awaited) and `end()`s
|
|
7207
7191
|
* the sink when the client closes; the registry attaches an `error` listener so a failing sink
|
|
@@ -7210,6 +7194,39 @@ type ClientSampleSinkEvents = SinkEvents;
|
|
|
7210
7194
|
*/
|
|
7211
7195
|
type ClientSampleSink = Sink<ClientSample>;
|
|
7212
7196
|
|
|
7197
|
+
type JsonlFileSinkOptions<T = ClientSample> = Partial<SinkSettings> & {
|
|
7198
|
+
/** Absolute or relative file path to append JSONL lines to. */
|
|
7199
|
+
path: string;
|
|
7200
|
+
/** Optional custom serializer; defaults to `JSON.stringify`. */
|
|
7201
|
+
serializeSample?: (record: T) => string;
|
|
7202
|
+
};
|
|
7203
|
+
/**
|
|
7204
|
+
* A sink that serializes each record to a JSON line and appends it to a file. Defaults to client
|
|
7205
|
+
* samples, but takes any record type: `sinks.create(JsonlFileSink<PipeTransportStats[]>, { id, path })`. It wraps an
|
|
7206
|
+
* `fs.WriteStream` and re-emits its lifecycle events, so `close` fires once the file is flushed
|
|
7207
|
+
* and its descriptor is closed (file ready) and `error` surfaces file errors.
|
|
7208
|
+
*/
|
|
7209
|
+
declare interface JsonlFileSink {
|
|
7210
|
+
on<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7211
|
+
once<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7212
|
+
off<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7213
|
+
emit<E extends keyof SinkEvents>(event: E, ...args: SinkEvents[E]): boolean;
|
|
7214
|
+
}
|
|
7215
|
+
declare class JsonlFileSink<T = ClientSample> extends EventEmitter implements Sink<T> {
|
|
7216
|
+
readonly id: string;
|
|
7217
|
+
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
7218
|
+
readonly path: string;
|
|
7219
|
+
private readonly _stream;
|
|
7220
|
+
private readonly _serializeSample;
|
|
7221
|
+
private _ended;
|
|
7222
|
+
constructor(options: JsonlFileSinkOptions<T>);
|
|
7223
|
+
get ended(): boolean;
|
|
7224
|
+
write(record: T): boolean;
|
|
7225
|
+
end(): void;
|
|
7226
|
+
}
|
|
7227
|
+
/** Build a JSONL file sink for a single path. */
|
|
7228
|
+
declare function createJsonlFileSink<T = ClientSample>(options: JsonlFileSinkOptions<T>): JsonlFileSink<T>;
|
|
7229
|
+
|
|
7213
7230
|
declare interface InMemorySink {
|
|
7214
7231
|
on<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
7215
7232
|
once<E extends keyof SinkEvents>(event: E, listener: (...args: SinkEvents[E]) => void): this;
|
|
@@ -7236,4 +7253,4 @@ declare function createInMemorySink(samples?: ClientSample[]): InMemorySink;
|
|
|
7236
7253
|
declare function createDefaultMediasoupRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
7237
7254
|
declare function createP2pRemoteTrackResolverFactory(): RemoteTrackResolverFactory;
|
|
7238
7255
|
|
|
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 };
|
|
7256
|
+
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 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 };
|
package/dist/index.js
CHANGED
|
@@ -178,6 +178,46 @@ function setObserverLogger(logger13) {
|
|
|
178
178
|
// src/ObservedCall.ts
|
|
179
179
|
var import_node_events4 = require("events");
|
|
180
180
|
|
|
181
|
+
// src/sinks/JsonlFileSink.ts
|
|
182
|
+
var fs = __toESM(require("fs"));
|
|
183
|
+
var import_node_events = require("events");
|
|
184
|
+
var JsonlFileSink = class extends import_node_events.EventEmitter {
|
|
185
|
+
id;
|
|
186
|
+
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
187
|
+
path;
|
|
188
|
+
_stream;
|
|
189
|
+
_serializeSample;
|
|
190
|
+
_ended = false;
|
|
191
|
+
constructor(options) {
|
|
192
|
+
super();
|
|
193
|
+
this.id = options.id ?? options.path;
|
|
194
|
+
this.path = options.path;
|
|
195
|
+
this._stream = fs.createWriteStream(this.path, { flags: "a" });
|
|
196
|
+
this._stream.on("close", () => this.emit("close"));
|
|
197
|
+
this._stream.on("finish", () => this.emit("finish"));
|
|
198
|
+
this._stream.on("drain", () => this.emit("drain"));
|
|
199
|
+
this._stream.on("error", (err) => this.emit("error", err));
|
|
200
|
+
this._serializeSample = options.serializeSample ?? ((record) => JSON.stringify(record));
|
|
201
|
+
}
|
|
202
|
+
get ended() {
|
|
203
|
+
return this._ended;
|
|
204
|
+
}
|
|
205
|
+
write(record) {
|
|
206
|
+
if (this._ended) return false;
|
|
207
|
+
return this._stream.write(`${this._serializeSample(record)}
|
|
208
|
+
`);
|
|
209
|
+
}
|
|
210
|
+
end() {
|
|
211
|
+
if (this._ended) return;
|
|
212
|
+
this._ended = true;
|
|
213
|
+
this.emit("ended");
|
|
214
|
+
this._stream.end();
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
function createJsonlFileSink(options) {
|
|
218
|
+
return new JsonlFileSink(options);
|
|
219
|
+
}
|
|
220
|
+
|
|
181
221
|
// src/sinks/SinkRegistry.ts
|
|
182
222
|
var import_uuid = require("uuid");
|
|
183
223
|
var logger = createLogger("SinkRegistry");
|
|
@@ -189,6 +229,8 @@ var SinkRegistry = class {
|
|
|
189
229
|
// This registry's sinks and its descendants'.
|
|
190
230
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
191
231
|
_sinks = /* @__PURE__ */ new Map();
|
|
232
|
+
// The ids of the sinks added to this registry itself (not to a descendant).
|
|
233
|
+
_ownIds = /* @__PURE__ */ new Set();
|
|
192
234
|
/** Number of open sinks in this registry and its descendants. */
|
|
193
235
|
get size() {
|
|
194
236
|
return this._sinks.size;
|
|
@@ -217,17 +259,48 @@ var SinkRegistry = class {
|
|
|
217
259
|
return void 0;
|
|
218
260
|
}
|
|
219
261
|
const sink = new SinkClass({ ...settings, id });
|
|
220
|
-
|
|
221
|
-
sink
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
262
|
+
this._register(sink, lineage);
|
|
263
|
+
return sink;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Register a sink constructed elsewhere, under its own `id`. Returns `false`, registering nothing,
|
|
267
|
+
* when the id is already in use in the observer (the sink is then the caller's to end). Prefer
|
|
268
|
+
* `create` when the sink opens a resource on construction: it checks the id first.
|
|
269
|
+
*/
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
|
+
add(sink) {
|
|
272
|
+
const lineage = this._lineage();
|
|
273
|
+
if (lineage[lineage.length - 1]._sinks.has(sink.id)) {
|
|
274
|
+
logger.warn("Sink id %s is already in use; sink ids must be unique per observer. The sink was not added", sink.id);
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
this._register(sink, lineage);
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Register the sink a `SinkDestination` describes, under `id`: for `jsonlPath`, a `JsonlFileSink`
|
|
282
|
+
* is created (the id is checked before the file is opened); a `sink` is added, and its `id` must
|
|
283
|
+
* be `id`. Returns `undefined` when the id is taken, the ids differ, or both are given (possible
|
|
284
|
+
* from JavaScript); a given `sink` that is not registered is ended.
|
|
285
|
+
*/
|
|
286
|
+
createFromDestination(id, destination) {
|
|
287
|
+
const { jsonlPath, sink } = destination;
|
|
288
|
+
const refuse = (reason, ...args) => {
|
|
289
|
+
logger.warn(`Sink ${id}: ${reason}`, ...args);
|
|
290
|
+
sink?.end();
|
|
291
|
+
return void 0;
|
|
292
|
+
};
|
|
293
|
+
if (jsonlPath !== void 0 && sink) return refuse("give either jsonlPath or sink, not both");
|
|
294
|
+
if (jsonlPath !== void 0) return this.create(JsonlFileSink, { id, path: jsonlPath });
|
|
295
|
+
if (!sink) return void 0;
|
|
296
|
+
if (sink.id !== id) return refuse("the sink id %s must be %s; it is ended", sink.id, id);
|
|
297
|
+
if (!this.add(sink)) return refuse("the id is already in use; the sink is ended");
|
|
229
298
|
return sink;
|
|
230
299
|
}
|
|
300
|
+
/** Whether the sink `id` was added to this registry itself, rather than to a descendant. */
|
|
301
|
+
owns(id) {
|
|
302
|
+
return this._ownIds.has(id);
|
|
303
|
+
}
|
|
231
304
|
/** Write `record` to the sink `id`. `false` when there is no such sink, or on backpressure. */
|
|
232
305
|
write(id, record) {
|
|
233
306
|
const sink = this._sinks.get(id);
|
|
@@ -248,6 +321,21 @@ var SinkRegistry = class {
|
|
|
248
321
|
}
|
|
249
322
|
}
|
|
250
323
|
/** This registry and its ancestors, up to the root. */
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
325
|
+
_register(sink, lineage) {
|
|
326
|
+
const id = sink.id;
|
|
327
|
+
const onError = (err) => logger.warn("Sink %s failed: %o", id, err);
|
|
328
|
+
sink.on("error", onError);
|
|
329
|
+
sink.once("close", () => sink.off("error", onError));
|
|
330
|
+
sink.once("ended", () => {
|
|
331
|
+
if (this._sinks.get(id) === sink) this._ownIds.delete(id);
|
|
332
|
+
for (const registry of lineage) {
|
|
333
|
+
if (registry._sinks.get(id) === sink) registry._sinks.delete(id);
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
this._ownIds.add(id);
|
|
337
|
+
for (const registry of lineage) registry._sinks.set(id, sink);
|
|
338
|
+
}
|
|
251
339
|
_lineage() {
|
|
252
340
|
return [this, ...this.parent?._lineage() ?? []];
|
|
253
341
|
}
|
|
@@ -278,19 +366,20 @@ var ExtensionStatsRegistry = class {
|
|
|
278
366
|
}
|
|
279
367
|
}
|
|
280
368
|
create(settings) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
if (this.values.has(settings.id)) {
|
|
286
|
-
logger2.warn("Extension stats with id %s already exists on %s", settings.id, this.id);
|
|
369
|
+
const { id, jsonlPath, sink } = settings;
|
|
370
|
+
const refuse = (reason) => {
|
|
371
|
+
logger2.warn("Extension stats %s on %s: %s", id, this.id, reason);
|
|
372
|
+
sink?.end();
|
|
287
373
|
return void 0;
|
|
288
|
-
}
|
|
289
|
-
this.
|
|
290
|
-
if (
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
374
|
+
};
|
|
375
|
+
if (this.closed) return refuse("the owner is closed");
|
|
376
|
+
if (this.values.has(id)) return refuse("the id already exists");
|
|
377
|
+
if (jsonlPath !== void 0 && sink) return refuse("give either jsonlPath or sink, not both");
|
|
378
|
+
if (sink && sink.id !== id) return refuse(`the sink id ${sink.id} differs from the extension stats id`);
|
|
379
|
+
this.values.set(id, void 0);
|
|
380
|
+
if (jsonlPath === void 0 && !sink) return void 0;
|
|
381
|
+
if (this._sinks.owns(id)) this._sinks.get(id)?.end();
|
|
382
|
+
return this._sinks.createFromDestination(id, settings);
|
|
294
383
|
}
|
|
295
384
|
accept(id, data) {
|
|
296
385
|
if (this.closed) {
|
|
@@ -309,46 +398,6 @@ var ExtensionStatsRegistry = class {
|
|
|
309
398
|
}
|
|
310
399
|
};
|
|
311
400
|
|
|
312
|
-
// src/sinks/JsonlFileSink.ts
|
|
313
|
-
var fs = __toESM(require("fs"));
|
|
314
|
-
var import_node_events = require("events");
|
|
315
|
-
var JsonlFileSink = class extends import_node_events.EventEmitter {
|
|
316
|
-
id;
|
|
317
|
-
/** The file this sink writes to. Read it (e.g. in a `close` handler) to upload/move the file. */
|
|
318
|
-
path;
|
|
319
|
-
_stream;
|
|
320
|
-
_serializeSample;
|
|
321
|
-
_ended = false;
|
|
322
|
-
constructor(options) {
|
|
323
|
-
super();
|
|
324
|
-
this.id = options.id ?? options.path;
|
|
325
|
-
this.path = options.path;
|
|
326
|
-
this._stream = fs.createWriteStream(this.path, { flags: "a" });
|
|
327
|
-
this._stream.on("close", () => this.emit("close"));
|
|
328
|
-
this._stream.on("finish", () => this.emit("finish"));
|
|
329
|
-
this._stream.on("drain", () => this.emit("drain"));
|
|
330
|
-
this._stream.on("error", (err) => this.emit("error", err));
|
|
331
|
-
this._serializeSample = options.serializeSample ?? ((record) => JSON.stringify(record));
|
|
332
|
-
}
|
|
333
|
-
get ended() {
|
|
334
|
-
return this._ended;
|
|
335
|
-
}
|
|
336
|
-
write(record) {
|
|
337
|
-
if (this._ended) return false;
|
|
338
|
-
return this._stream.write(`${this._serializeSample(record)}
|
|
339
|
-
`);
|
|
340
|
-
}
|
|
341
|
-
end() {
|
|
342
|
-
if (this._ended) return;
|
|
343
|
-
this._ended = true;
|
|
344
|
-
this.emit("ended");
|
|
345
|
-
this._stream.end();
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
function createJsonlFileSink(options) {
|
|
349
|
-
return new JsonlFileSink(options);
|
|
350
|
-
}
|
|
351
|
-
|
|
352
401
|
// src/ObservedClient.ts
|
|
353
402
|
var import_node_events3 = require("events");
|
|
354
403
|
|
|
@@ -3209,7 +3258,7 @@ var ObservedClient = class extends import_node_events3.EventEmitter {
|
|
|
3209
3258
|
/** The distinct `publicAddress`es currently known by this client's open peer connections. */
|
|
3210
3259
|
/**
|
|
3211
3260
|
* The sink this client's samples are written to: whatever is registered under its client id in
|
|
3212
|
-
* `call.sinks` (the `
|
|
3261
|
+
* `call.sinks` (the one `createClientSink` returned, or a sink the application added), if any.
|
|
3213
3262
|
*/
|
|
3214
3263
|
get sink() {
|
|
3215
3264
|
return this.call.sinks.get(this.clientId);
|
|
@@ -5312,17 +5361,11 @@ var ObservedCall = class extends import_node_events4.EventEmitter {
|
|
|
5312
5361
|
clearTimeout(this.closeTimer);
|
|
5313
5362
|
this.closeTimer = void 0;
|
|
5314
5363
|
}
|
|
5315
|
-
const
|
|
5316
|
-
let jsonlSink;
|
|
5317
|
-
if (jsonlPath !== void 0 && this.observer.sinks.get(settings.clientId)) {
|
|
5318
|
-
logger6.warn("No sample file for client %s in call %s: sink id %s is already in use", settings.clientId, this.callId, settings.clientId);
|
|
5319
|
-
} else if (jsonlPath !== void 0) {
|
|
5320
|
-
jsonlSink = this.sinks.create(JsonlFileSink, { id: settings.clientId, path: jsonlPath });
|
|
5321
|
-
}
|
|
5364
|
+
const clientSink = this._createClientSink(settings.clientId);
|
|
5322
5365
|
this.emit("newclient", result);
|
|
5323
5366
|
this._notify("client-added", { ...this.eventScope, observedClient: result });
|
|
5324
|
-
if (
|
|
5325
|
-
this._notify("client-sink-created", { ...this.eventScope, observedClient: result, sink:
|
|
5367
|
+
if (clientSink) {
|
|
5368
|
+
this._notify("client-sink-created", { ...this.eventScope, observedClient: result, sink: clientSink });
|
|
5326
5369
|
}
|
|
5327
5370
|
if (wasEmpty) {
|
|
5328
5371
|
this.emit("not-empty");
|
|
@@ -5369,6 +5412,16 @@ var ObservedCall = class extends import_node_events4.EventEmitter {
|
|
|
5369
5412
|
this.endedAt = Math.max(this.endedAt ?? client.leftAt, client.leftAt);
|
|
5370
5413
|
}
|
|
5371
5414
|
/** Emit an Observer-bus event scoped to this call. */
|
|
5415
|
+
_createClientSink(clientId) {
|
|
5416
|
+
const createClientSink = this.observer.config.createClientSink;
|
|
5417
|
+
if (!createClientSink) return void 0;
|
|
5418
|
+
if (this.observer.sinks.get(clientId)) {
|
|
5419
|
+
logger6.warn("No sample sink for client %s in call %s: sink id %s is already in use", clientId, this.callId, clientId);
|
|
5420
|
+
return void 0;
|
|
5421
|
+
}
|
|
5422
|
+
const destination = createClientSink({ id: clientId, observedCall: this, clientId });
|
|
5423
|
+
return destination ? this.sinks.createFromDestination(clientId, destination) : void 0;
|
|
5424
|
+
}
|
|
5372
5425
|
_notify(type, ...args) {
|
|
5373
5426
|
this.observer.emit(type, ...args);
|
|
5374
5427
|
}
|