OBSERVERTC / Observer

Ingestion & lifecycle

Validate at your boundary, then pass samples to Observer.

Each incoming sample updates the live view of one client. Observer uses the call and client identifiers to find that client, then updates its connections, tracks, and issues. Keep samples ordered for each client so the current state follows the actual session.

Accepting a sample

Observer.accept(sample, context?) completes synchronously. In the verified reference baseline, it invokes acceptMiddlewares, validates nonempty call/client IDs, gets or creates a call/client and calls ObservedClient.accept(). It does not perform full Avro validation, automatic codec decoding or client-side detector execution. The middleware implementation has a reproduced drop/replacement bug described below.

Client updates

ObservedClient.accept resets interval aggregates, normalizes score reason wire generations, merges pending injections, processes events/meta/issues/extensions, updates PCs, processes deferred events, sets attachments and sampled client score, emits update events, resets idle timeout and writes the final sample to a sink. Client-level bitrate uses server wall-clock arrival interval, so replaying historical samples rapidly is not equivalent to the browser stats cadence. Per-RTP entities retain their own stat processing rules.

Source references

Peer-connection updates

A peer-connection update creates or refreshes its tracks, RTP streams, codecs, and network-path objects. It calculates counters for the latest interval and removes children that are no longer represented. RTP maps commonly use SSRC, tracks use track ID, other objects use stat ID. Parent scopes are included on the central Observer bus; local emitters coordinate internals. State is live and removable; it is not a database.

Source references

Active issue lifecycle

ObservedClient.activeIssues mirrors keyed client lifecycle records. A raise gets both client-clock raisedAt and server-clock observedAt; cross-client onset comparisons must use the latter. Same-key re-raise refreshes payload; keyless records are one-shot. A -resolved record retires the key and emits resolution. Client close resolves outstanding state. Registries propagate into call and observer scopes, enabling cross-client queries without walking every healthy client.

Source references

Defaults and registration

Server defaults: automatic observer update on call updates, 60 s idle client timeout, 60 s empty-call timeout. Passing explicit undefined through config disables those timeout defaults because config is spread after defaults. Call summaries are absent unless configured. Detector registries start empty. Register observer-wide detectors with addObserverDetector, future-call detectors with addCallDetector, and an existing call’s detector with call.addDetector. Removal semantics and duplicate-name instances differ from a unique-name client registry.

Source references

Validate before ingestion

The inspected accept() implementation dispatches the original sample after invoking middleware. Not calling next(), throwing, or forwarding a replacement object does not reliably drop or replace that sample. In-place mutation of the original object is a different case.

Filter and validate before calling observer.accept(). Do not use its middleware as an authorization boundary. The tagged implementation continues with the original sample after middleware processing; use your authenticated handler as the validation and filtering boundary. Implementation.

Known differences and reproduction results

Version coverage varies by reference. Source revisions · Markdown source