@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/llms-full.txt
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# observer-js (@observertc/observer-js) — full documentation
|
|
2
2
|
|
|
3
3
|
> Single-file export of the project's documentation for one-shot LLM ingestion.
|
|
4
|
-
> Concatenates: README.md, docs/logging.md, CHANGELOG.md. Generated
|
|
4
|
+
> Concatenates: README.md, docs/logging.md, CHANGELOG.md. Generated by scripts/build-llms-full.mjs —
|
|
5
5
|
> the canonical, always-current copies live in the repository; see llms.txt for the link index.
|
|
6
6
|
|
|
7
7
|
---
|
|
@@ -75,9 +75,10 @@ and emits a single, unified stream of typed events the application can react to.
|
|
|
75
75
|
11. [Remote track resolution (mediasoup / SFU)](#remote-track-resolution-mediasoup--sfu)
|
|
76
76
|
12. [Mediasoup router observation](#mediasoup-router-observation)
|
|
77
77
|
13. [Sinks (per-client sample persistence)](#sinks-per-client-sample-persistence)
|
|
78
|
-
14. [
|
|
79
|
-
15. [
|
|
80
|
-
16. [
|
|
78
|
+
14. [Injecting data into a client](#injecting-data-into-a-client)
|
|
79
|
+
15. [Logging](#logging)
|
|
80
|
+
16. [Error-handling philosophy](#error-handling-philosophy)
|
|
81
|
+
17. [Development & extension guide](#development--extension-guide)
|
|
81
82
|
|
|
82
83
|
---
|
|
83
84
|
|
|
@@ -381,7 +382,7 @@ additional field(s) on top of that scope.
|
|
|
381
382
|
| Event | Extra | Fires when |
|
|
382
383
|
|-------|-------|-----------|
|
|
383
384
|
| `mediasoup-router-added` | — | `observer.createObservedMediasoupRouter(...)` registered a router |
|
|
384
|
-
| `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. |
|
|
385
|
+
| `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`. |
|
|
385
386
|
| `mediasoup-router-removed` | — | the underlying mediasoup router closed (its `router.observer` `close` fired) |
|
|
386
387
|
|
|
387
388
|
See [Mediasoup router observation](#mediasoup-router-observation) for the full design and examples.
|
|
@@ -551,7 +552,7 @@ Key members:
|
|
|
551
552
|
- `readonly sink?: ClientSampleSink` — the per-client sink (see [Sinks](#sinks-per-client-sample-persistence)), if `createClientSink` is configured; listen on it for `close`/`error`
|
|
552
553
|
- **Injection API** (queue app data to be merged into the next sample processing):
|
|
553
554
|
`injectEvent(ClientEvent)`, `injectIssue(ClientIssue)`, `injectMetaData(ClientMetaData)`,
|
|
554
|
-
`injectExtensionStat(ExtensionStat)`, `injectAttachment(
|
|
555
|
+
`injectExtensionStat(ExtensionStat)`, `injectAttachment(attachments: Record<string, unknown>)`
|
|
555
556
|
- **Direct add API** (process immediately): `addIssue(ClientIssue)`, `addMetadata(ClientMetaData)`,
|
|
556
557
|
`addExtensionStats(ExtensionStat)`
|
|
557
558
|
- Metrics (current/derived): `currentAvgRttInMs?`, `currentMinRttInMs?`, `currentMaxRttInMs?`,
|
|
@@ -844,11 +845,13 @@ ancestry, so you get the router **and** the matched `observedPeerConnection`, `o
|
|
|
844
845
|
`observedCall` in one place. Stamp the `routerId` into the peer connection's / client's `appData`,
|
|
845
846
|
build your own index, attach the server sample to the call in your database — whatever fits.
|
|
846
847
|
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
848
|
+
This matching is **opt-in**: pass `matchPeerConnectionByWebRtcTransportId: true` to
|
|
849
|
+
`createObservedMediasoupRouter`. When enabled, as peer connections are observed
|
|
850
|
+
(`peer-connection-added`) the observer checks whether the peer connection's id is one of the router's
|
|
851
|
+
WebRTC transport ids; on a hit it emits — once per matching peer connection — and keeps watching, so a
|
|
852
|
+
router serving many participants emits one match per participant's transport. When the flag is omitted
|
|
853
|
+
or `false`, no matching is performed and the event never fires. The internal listener is removed
|
|
854
|
+
automatically when the router closes or the observer closes.
|
|
852
855
|
|
|
853
856
|
When the underlying mediasoup router closes, its `close` propagates to `ObservedMediasoupRouter`,
|
|
854
857
|
which emits **`mediasoup-router-removed`**. That is your cue to do whatever cleanup or persistence
|
|
@@ -862,10 +865,11 @@ you want with the now-final `sample` — again, the observer itself keeps nothin
|
|
|
862
865
|
| `routerId` | `string` | yes | your id for the router (the sample also carries `router.id`) |
|
|
863
866
|
| `appData` | `Record<string, unknown>` | no | application-owned bag on the `ObservedMediasoupRouter` |
|
|
864
867
|
| `attachments` | `Record<string, unknown>` | no | free-form data copied onto `sample.attachments` |
|
|
868
|
+
| `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 |
|
|
865
869
|
|
|
866
|
-
Peer-connection matching is
|
|
867
|
-
`ObservedMediasoupRouter`, or `undefined`
|
|
868
|
-
returns the existing instance — both warn).
|
|
870
|
+
Peer-connection matching is **off by default**; enable it with
|
|
871
|
+
`matchPeerConnectionByWebRtcTransportId: true`. Returns the `ObservedMediasoupRouter`, or `undefined`
|
|
872
|
+
if the observer is closed (a router with the same id returns the existing instance — both warn).
|
|
869
873
|
|
|
870
874
|
Useful members on the returned object: `.sample` (the `MediasoupRouterSample`), `.appData`,
|
|
871
875
|
`.attachments` (getter over `sample.attachments`), `.webrtcTransportIds: Set<string>`, `.id`,
|
|
@@ -882,9 +886,13 @@ const observer = new Observer();
|
|
|
882
886
|
// 1) Feed client samples as usual so the observer knows about calls, clients & peer connections.
|
|
883
887
|
// (e.g. transport-layer: observer.accept(clientSample, context))
|
|
884
888
|
|
|
885
|
-
// 2) Observe the SFU side
|
|
889
|
+
// 2) Observe the SFU side, opting in to peer-connection matching for this router's transports.
|
|
886
890
|
const router = /* your mediasoup router */ undefined as any;
|
|
887
|
-
const observedRouter = observer.createObservedMediasoupRouter({
|
|
891
|
+
const observedRouter = observer.createObservedMediasoupRouter({
|
|
892
|
+
router,
|
|
893
|
+
routerId: router.id,
|
|
894
|
+
matchPeerConnectionByWebRtcTransportId: true,
|
|
895
|
+
});
|
|
888
896
|
|
|
889
897
|
// 3) Every peer connection whose id matches one of the router's WebRTC transport ids fires this —
|
|
890
898
|
// WE decide what to do with each pairing. The payload carries the full ancestry.
|
|
@@ -1043,6 +1051,60 @@ const observer = new Observer({ createClientSink });
|
|
|
1043
1051
|
the bus with full ancestry. `ClientSampleSinkFactory` is
|
|
1044
1052
|
`(p: { clientId: string; observedCall: ObservedCall }) => ClientSampleSink | undefined`.
|
|
1045
1053
|
|
|
1054
|
+
---
|
|
1055
|
+
|
|
1056
|
+
## Injecting data into a client
|
|
1057
|
+
|
|
1058
|
+
Sometimes the application holds data that belongs on a client's record but isn't part of the
|
|
1059
|
+
client-reported `ClientSample` — a room id or display name, an application-level event
|
|
1060
|
+
(*"recording started"*), a server-detected issue, an extension stat, or a device/meta item.
|
|
1061
|
+
`ObservedClient` exposes **injection** methods that merge such data into the client's sample stream,
|
|
1062
|
+
so it updates the live model **and** is persisted to the client's
|
|
1063
|
+
[sink](#sinks-per-client-sample-persistence) exactly like sampled data.
|
|
1064
|
+
|
|
1065
|
+
| Method | Adds to the sample's | Surfaces as |
|
|
1066
|
+
|--------|----------------------|-------------|
|
|
1067
|
+
| `injectAttachment(attachments)` | `attachments` (merged via `Object.assign`) | `observedClient.attachments` |
|
|
1068
|
+
| `injectEvent(event: ClientEvent)` | `clientEvents` | `client-event` (plus any state the event drives) |
|
|
1069
|
+
| `injectIssue(issue: ClientIssue)` | `clientIssues` | `client-issue` |
|
|
1070
|
+
| `injectMetaData(meta: ClientMetaData)` | `clientMetaItems` | `client-metadata` |
|
|
1071
|
+
| `injectExtensionStat(stat: ExtensionStat)` | `extensionStats` | `client-extension-stats` |
|
|
1072
|
+
|
|
1073
|
+
### When the injected data lands
|
|
1074
|
+
|
|
1075
|
+
Injection is timing-aware so nothing is dropped, regardless of *when* you call it:
|
|
1076
|
+
|
|
1077
|
+
- **During a sample's processing** — e.g. from inside a `client-updated` / `client-event` handler,
|
|
1078
|
+
which run within `accept()` — the data is applied to the **current** sample immediately: reflected
|
|
1079
|
+
in entity state and written to the sink as part of that sample.
|
|
1080
|
+
- **Between samples** — the data is buffered and merged into the **next** `accept()`'s sample.
|
|
1081
|
+
- **On `close()` with pending injections and no further sample** — the buffer is flushed as a final
|
|
1082
|
+
synthetic sample (applied to state and written to the sink) before the sink is ended, so a
|
|
1083
|
+
last-moment injection is never lost.
|
|
1084
|
+
|
|
1085
|
+
In every case the injected data both updates the live `ObservedClient` and reaches the per-client
|
|
1086
|
+
sink — the sink always receives the final, **injection-merged** sample (the sink write happens at the
|
|
1087
|
+
end of `accept()`, after the merge).
|
|
1088
|
+
|
|
1089
|
+
### Example
|
|
1090
|
+
|
|
1091
|
+
```ts
|
|
1092
|
+
// Enrich at creation from your app's knowledge of the participant. Injecting in `client-added`
|
|
1093
|
+
// (which runs just before the first accept) lands on the first sample.
|
|
1094
|
+
observer.on('client-added', ({ observedClient }) => {
|
|
1095
|
+
observedClient.injectAttachment({ roomId: lookupRoomId(observedClient.clientId) });
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// Application-level signals at any time:
|
|
1099
|
+
const client = observer.getObservedCall(callId)?.getObservedClient(clientId);
|
|
1100
|
+
client?.injectEvent({ type: 'RECORDING_STARTED', timestamp: Date.now() });
|
|
1101
|
+
client?.injectIssue({ type: 'app-kicked-participant', timestamp: Date.now() });
|
|
1102
|
+
```
|
|
1103
|
+
|
|
1104
|
+
`attachments` are latest-wins (like sampled `attachments`): injecting a key overwrites its previous
|
|
1105
|
+
value. `appData` is unaffected — injections flow into the sample/telemetry, not the app-owned
|
|
1106
|
+
`appData` bag (see [Ingestion](#ingestion-accept-context--lifecycle)).
|
|
1107
|
+
|
|
1046
1108
|
## Logging
|
|
1047
1109
|
|
|
1048
1110
|
`observer-js` logs through a single, swappable sink. Out of the box it writes `debug` and
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@observertc/observer-js",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.11",
|
|
4
4
|
"description": "Server-side Node.js library for processing ObserveRTC Samples",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"module": "./dist/index.mjs",
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"build": "tsup",
|
|
33
33
|
"typecheck": "tsc --noEmit",
|
|
34
34
|
"test": "jest --config jest.config.js",
|
|
35
|
-
"test:coverage": "jest --config jest.config.js --coverage"
|
|
35
|
+
"test:coverage": "jest --config jest.config.js --coverage",
|
|
36
|
+
"docs:llms-full": "node scripts/build-llms-full.mjs"
|
|
36
37
|
},
|
|
37
38
|
"keywords": [
|
|
38
39
|
"webrtc",
|