@webex/internal-plugin-metrics 3.12.0-next.55 → 3.12.0-next.57
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/.repo-context.json +466 -0
- package/.sdd/manifest.json +626 -0
- package/AGENTS.md +208 -0
- package/dist/call-diagnostic/call-diagnostic-metrics.js +26 -14
- package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
- package/dist/metrics.js +1 -1
- package/dist/metrics.types.js.map +1 -1
- package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +1 -0
- package/dist/types/metrics.types.d.ts +4 -3
- package/docs/adr/index.md +27 -0
- package/docs/architecture.md +437 -0
- package/docs/getting-started.md +131 -0
- package/docs/index.md +52 -0
- package/docs/specs/README.md +88 -0
- package/package.json +2 -2
- package/schemas/repo-context.schema.json +796 -0
- package/src/call-diagnostic/call-diagnostic-metrics.ts +27 -7
- package/src/call-diagnostic/docs/README.md +535 -0
- package/src/docs/README.md +703 -0
- package/src/metrics.types.ts +4 -3
- package/src/privacy-and-security-permission-enricher/docs/README.md +356 -0
- package/src/rtcMetrics/docs/README.md +447 -0
- package/src/unhandled-exception-telemetry/docs/README.md +536 -0
- package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +175 -0
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Module Spec
|
|
3
|
+
title: 'WebRTC stats telemetry specification'
|
|
4
|
+
description: Responsibilities, boundaries, design, invariants, and verification for the WebRTC stats telemetry reporter.
|
|
5
|
+
tags: [module, specification]
|
|
6
|
+
---
|
|
7
|
+
<!-- sdd-generated-metadata
|
|
8
|
+
doc_kind: module-spec
|
|
9
|
+
generated_from: module-spec@0.3.0
|
|
10
|
+
generated_by: claude-code
|
|
11
|
+
approved_by: rarajes2@cisco.com
|
|
12
|
+
updated_at: 2026-10-07T04:50:31Z
|
|
13
|
+
validation_status: pass
|
|
14
|
+
-->
|
|
15
|
+
|
|
16
|
+
# WebRTC stats telemetry
|
|
17
|
+
|
|
18
|
+
This source-local document at `src/rtcMetrics/docs/README.md` owns the stable specification for
|
|
19
|
+
**the WebRTC stats telemetry reporter**. Ground every claim in repository evidence and link to the
|
|
20
|
+
[repository architecture](../../../docs/architecture.md) instead of repeating broader facts.
|
|
21
|
+
|
|
22
|
+
Related context: [documentation index](../../../docs/index.md) ·
|
|
23
|
+
[documentation agent instructions](../../../AGENTS.md)
|
|
24
|
+
|
|
25
|
+
## Metadata
|
|
26
|
+
|
|
27
|
+
| Field | Value |
|
|
28
|
+
| ------------- | -------------------------------------------- |
|
|
29
|
+
| Owner | Cisco Webex JS SDK — telemetry |
|
|
30
|
+
| Source path | `src/rtcMetrics/` |
|
|
31
|
+
| Resource kind | module |
|
|
32
|
+
| Status | Active |
|
|
33
|
+
| Last verified | 2026-10-05 at `d6202dd73d` |
|
|
34
|
+
| Module id | `rtc-metrics` |
|
|
35
|
+
| Parent spec | [`src/docs/README.md`](../../docs/README.md) |
|
|
36
|
+
| Doc kind | Module spec |
|
|
37
|
+
| Coverage score | Pending coverage assessment |
|
|
38
|
+
| Validation status | pass (2026-10-07; validator runtime 01a1147a-c744-7083-9b15-95e91170c79a; 0 blocking, 0 important, 0 medium, 0 minor; conformance and source-fidelity gates pass, no code/spec mismatches found) |
|
|
39
|
+
|
|
40
|
+
## Applicability
|
|
41
|
+
|
|
42
|
+
Sections preceded by an `Include if` comment are conditional. Retain a
|
|
43
|
+
conditional section only when source evidence or a confirmed developer answer
|
|
44
|
+
satisfies its condition.
|
|
45
|
+
|
|
46
|
+
| Condition ID | Status | Evidence or reason | Owned section |
|
|
47
|
+
| ------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
48
|
+
| `module.has_tiers` | N/A | The repository defines no operational or review tier policy for modules | Tier |
|
|
49
|
+
| `module.has_ui` | N/A | The module renders nothing | UI use-case flow |
|
|
50
|
+
| `module.crosses_service_boundaries` | Applicable | `src/rtcMetrics/index.ts` posts directly to the unified telemetry service | Cross-boundary use-case flow |
|
|
51
|
+
| `module.holds_client_state` | Applicable | `src/rtcMetrics/index.ts` holds the metrics queue, the connection identifier and the force-send flag | Client state model |
|
|
52
|
+
| `module.enforces_domain_rules` | Applicable | Candidate address anonymization and connection-identifier rotation in `src/rtcMetrics/index.ts` | Business rules and invariants |
|
|
53
|
+
| `module.is_concurrent_async` | Applicable | A repeating browser interval drives submission in `src/rtcMetrics/index.ts` | Concurrency and reactive flow |
|
|
54
|
+
| `module.owns_persistence` | N/A | No store, schema or migration; the queue is in-memory and emptied on submission | Data, schema, and migration |
|
|
55
|
+
| `module.stateful_transitions` | Applicable | The connection lifecycle rotates the identifier and re-arms the force-send flag in `src/rtcMetrics/index.ts` | State machine |
|
|
56
|
+
| `module.exposes_wire_protocol` | Applicable | The submitted batch shape is declared in `src/metrics.types.ts` and assembled in `src/rtcMetrics/index.ts` | Protocol and wire format |
|
|
57
|
+
| `module.ui_multi_screen` | N/A | No user interface | UI flow |
|
|
58
|
+
| `module.large_data_model` | N/A | One attribute object plus an opaque array of serialized stats items | Data model |
|
|
59
|
+
| `module.returns_caller_errors` | N/A | Every operation returns void and the submission promise is not returned to a caller | Caller-visible failure modes |
|
|
60
|
+
| `module.module_specific_conventions` | Applicable | Stats items are handled as JSON strings rather than objects throughout `src/rtcMetrics/index.ts` | Module-specific rules |
|
|
61
|
+
| `module.published_package` | Applicable | Re-exported from `src/index.ts`, which is the file that crosses the package boundary to the meetings plugin | Export stability |
|
|
62
|
+
| `module.embedded_in_host` | N/A | A plain class, not a registered plugin; the parent module owns the registration contract | Host integration and theming |
|
|
63
|
+
| `module.has_design_tradeoff` | Applicable | Immediate submission of the first useful stats report, versus waiting for the normal interval | Key design trade-off |
|
|
64
|
+
| `module.has_submodules` | N/A | Derived from the manifest module tree: this module has no child modules | Sub-modules |
|
|
65
|
+
|
|
66
|
+
## Evidence register
|
|
67
|
+
|
|
68
|
+
| Evidence | What it establishes |
|
|
69
|
+
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
|
|
70
|
+
| `src/rtcMetrics/index.ts` | The public surface, the queue and interval behavior, anonymization, connection rotation and the submitted batch |
|
|
71
|
+
| `src/rtcMetrics/constants.ts` | The fixed application identifier sent as a request header |
|
|
72
|
+
| `src/metrics.types.ts` | The identifier-pair input type and the submitted attribute shape |
|
|
73
|
+
| `src/call-diagnostic/call-diagnostic-metrics.util.ts` | The IP anonymization helper this module reuses |
|
|
74
|
+
| `src/index.ts` | That the class is part of the published export surface |
|
|
75
|
+
| `test/unit/spec/rtcMetrics/index.ts` | Behavioral verification of queueing, the interval, anonymization, connection rotation and the force-send path |
|
|
76
|
+
|
|
77
|
+
## Purpose and boundary
|
|
78
|
+
|
|
79
|
+
- Responsibility: collecting WebRTC media statistics items produced by the media engine, anonymizing
|
|
80
|
+
candidate addresses, and submitting them in batches to the Webex unified telemetry service.
|
|
81
|
+
- In scope: the metrics queue; the repeating submission interval; anonymization of local and remote
|
|
82
|
+
candidate addresses; rotating the connection identifier when a media connection fails; forcing an
|
|
83
|
+
immediate submission on the next stats report; assembling the submitted attribute object.
|
|
84
|
+
- Out of scope: producing the statistics themselves, which come from the media engine through the
|
|
85
|
+
meetings layer; the meaning of individual stats fields, which this module never parses beyond
|
|
86
|
+
candidate addresses; and the upstream ingestion contract, which the telemetry service owns.
|
|
87
|
+
- Consumers: `packages/@webex/plugin-meetings/src/meeting/index.ts` and
|
|
88
|
+
`packages/@webex/plugin-meetings/src/media/index.ts`, which construct one instance per media
|
|
89
|
+
connection. Exported publicly from `src/index.ts`.
|
|
90
|
+
|
|
91
|
+
## Structure and key files
|
|
92
|
+
|
|
93
|
+
| Path | Responsibility |
|
|
94
|
+
| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
95
|
+
| `src/rtcMetrics/index.ts` | The reporter class: queue, interval, anonymization, connection lifecycle and submission; authoritative for all module behavior |
|
|
96
|
+
| `src/rtcMetrics/constants.ts` | The registered application identifier; authoritative for the value sent in the request header |
|
|
97
|
+
|
|
98
|
+
## Public surface
|
|
99
|
+
|
|
100
|
+
| Surface | Consumer | Compatibility commitment | Source |
|
|
101
|
+
| ---------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
102
|
+
| `RtcMetrics` constructor | `packages/@webex/plugin-meetings/src/meeting/index.ts` | Takes the SDK instance, a meeting or call identifier pair, and a correlation identifier. Starts the interval immediately | `src/rtcMetrics/index.ts` |
|
|
103
|
+
| `addMetrics` | `packages/@webex/plugin-meetings/src/media/index.ts` | Accepts a named payload array of serialized stats items; ignores an empty payload | `src/rtcMetrics/index.ts` |
|
|
104
|
+
| `sendMetricsInQueue` | `packages/@webex/plugin-meetings/src/meeting/index.ts` | Submits and clears the queue only when it is non-empty | `src/rtcMetrics/index.ts` |
|
|
105
|
+
| `sendNextMetrics` | `packages/@webex/plugin-meetings/src/meeting/index.ts` | Arms immediate submission on the next stats report | `src/rtcMetrics/index.ts` |
|
|
106
|
+
| `updateCallId` | `packages/@webex/plugin-meetings/src/meeting/index.ts` | Replaces the call identifier used by later submissions | `src/rtcMetrics/index.ts` |
|
|
107
|
+
| `closeMetrics` | `packages/@webex/plugin-meetings/src/meeting/index.ts` | Flushes the queue, then clears the interval. Must be called or the interval leaks | `src/rtcMetrics/index.ts` |
|
|
108
|
+
| `anonymizeIp` | Tests, and internally per stats item | Anonymizes candidate addresses in one serialized stats item and returns it re-serialized | `src/rtcMetrics/index.ts` |
|
|
109
|
+
|
|
110
|
+
The submitted batch shape is declared in `src/metrics.types.ts`; it is not restated here.
|
|
111
|
+
|
|
112
|
+
## Dependencies
|
|
113
|
+
|
|
114
|
+
| Dependency | Why it is required | Failure behavior |
|
|
115
|
+
| ------------------------------------------------------ | -------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
116
|
+
| `src/call-diagnostic/call-diagnostic-metrics.util.ts` | Supplies the IP anonymization helper applied to candidate addresses | An unanonymizable value becomes undefined, which drops the field rather than emitting the raw address |
|
|
117
|
+
| The host SDK request pipeline | Performs the HTTP POST to the unified telemetry service | The returned promise is not awaited or handled, so a failed submission is silently lost |
|
|
118
|
+
| The host SDK device registration | Supplies the user identifier included in every batch | A missing device makes the field undefined; submission still proceeds |
|
|
119
|
+
| `uuid` | Generates each connection identifier | No failure path |
|
|
120
|
+
| Browser `setInterval` | Drives periodic submission; used explicitly so the handle is typed as a browser timer | Not available outside a browser, so the class is a browser-only surface |
|
|
121
|
+
|
|
122
|
+
## Requirements
|
|
123
|
+
|
|
124
|
+
| ID | WHAT | WHY | Source evidence | Test or example evidence | Assumptions or gaps | Confidence |
|
|
125
|
+
| --------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------- | ---------- |
|
|
126
|
+
| `MOD-001` | Queued stats items are submitted every thirty seconds and the queue is emptied after each submission | Per-item submission would multiply request volume during a call; a fixed window bounds it without losing data | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
127
|
+
| `MOD-002` | An interval tick with an empty queue submits nothing | An idle media connection must not generate empty telemetry requests for the whole duration of a call | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
128
|
+
| `MOD-003` | Local and remote candidate addresses are anonymized before a stats report is queued | Candidate addresses are personally identifying network data and must not leave the client intact | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
129
|
+
| `MOD-004` | The first stats report of a connection is submitted immediately rather than waiting for the interval | The media engine produces the first useful data a few seconds in; submitting at once means a user who abandons the call early is still represented | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
130
|
+
| `MOD-005` | A failed connection state change flushes the queue and rotates to a new connection identifier | Statistics from a dead connection must not be attributed to its replacement, so the identifier is the boundary between attempts | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
131
|
+
| `MOD-006` | A successful connection keeps the same connection identifier across submissions | Consumers correlate all batches from one media connection by that identifier | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
132
|
+
| `MOD-007` | Each batch carries either a meeting identifier or a call identifier, never both, with the meeting identifier preferred | The two identify different products, and ingestion routes on exactly one of them | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
133
|
+
| `MOD-008` | The call identifier can be replaced after construction | A calling session learns its call identifier after the media connection is already reporting | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
134
|
+
| `MOD-009` | Closing the reporter flushes the queue and then clears the interval | A call ending must not discard the last window, and the interval must not outlive the connection | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none | Present |
|
|
135
|
+
| `MOD-010` | Stats-report items must be valid JSON: anonymization parses each item and a malformed one throws from the add operation before the item is queued. The connection-failure detector parses only the first item of the payload, treats an unparseable or absent first item as "not failed" without throwing, and still queues the original item | Anonymization has to reach candidate address fields, so it cannot pass an unparseable stats report through; the failure detector is a best-effort trigger and must not drop data it cannot read | `src/rtcMetrics/index.ts` | none found | No case feeds a malformed stats-report item or an unparseable connection-state payload; both parse paths are unverified | Weak |
|
|
136
|
+
| `MOD-011` | Submission failures are not surfaced to the caller: the telemetry request promise is neither returned nor caught | Telemetry must never affect a call. Every operation returns void; a rejected request is left unhandled and that batch is lost | `src/rtcMetrics/index.ts` | none found | No case rejects the request and asserts the call is unaffected | Weak |
|
|
137
|
+
|
|
138
|
+
## Design overview
|
|
139
|
+
|
|
140
|
+
The reporter is a plain class, not an SDK plugin, because one instance exists per media connection
|
|
141
|
+
rather than per SDK instance. The meetings layer constructs it when a media connection is created
|
|
142
|
+
and closes it when the connection ends.
|
|
143
|
+
|
|
144
|
+
Its internal shape is a queue plus two timers' worth of policy. `metricsQueue` accumulates whatever
|
|
145
|
+
the media engine hands over; the thirty-second browser interval submits and empties it. Everything
|
|
146
|
+
else in the class exists to handle the two cases where the plain interval is the wrong answer.
|
|
147
|
+
|
|
148
|
+
The first is the start of a connection. The media engine produces its first useful statistics report
|
|
149
|
+
a few seconds after the connection comes up, and a user who is unhappy with media quality often
|
|
150
|
+
closes the browser within the first window. `shouldSendMetricsOnNextStatsReport` is therefore armed
|
|
151
|
+
on construction and after every connection rotation, and the first stats report that arrives
|
|
152
|
+
triggers an immediate submission before the flag is cleared. `sendNextMetrics` re-arms it so the
|
|
153
|
+
meetings layer can force the same behavior after a significant media event, such as moving from the
|
|
154
|
+
lobby into the meeting.
|
|
155
|
+
|
|
156
|
+
The second is connection failure. When a connection-state-change item reports a failed connection,
|
|
157
|
+
the queue is flushed and `resetConnection` generates a new connection identifier, which is the field
|
|
158
|
+
consumers use to group batches from one connection. Flushing before rotating is what keeps the
|
|
159
|
+
failed connection's statistics attributed to the failed connection.
|
|
160
|
+
|
|
161
|
+
Stats items are handled as JSON strings end to end. `addMetrics` receives them serialized, the
|
|
162
|
+
anonymization step parses one item, rewrites candidate addresses and re-serializes it, and the
|
|
163
|
+
submitted batch carries the array of strings unchanged. Anonymization parses without a guard, so a
|
|
164
|
+
malformed stats-report item throws out of `addMetrics` before anything is queued; the
|
|
165
|
+
connection-failure check uses a guarded parse of the first item and falls back to "not failed". The module never builds an object model of a
|
|
166
|
+
statistics report, which is why a schema change in the media engine does not require a change here.
|
|
167
|
+
|
|
168
|
+
## Data flow and sequence coverage
|
|
169
|
+
|
|
170
|
+
The call style is an in-process method call for collection and a direct HTTP POST for submission.
|
|
171
|
+
There are two operation groups, and they are diagrammed separately because their actor order and
|
|
172
|
+
state outcome differ: collection is driven by the media engine, while submission is driven by a timer
|
|
173
|
+
or by a connection event.
|
|
174
|
+
|
|
175
|
+
| Operation group | Entry and outcome | Diagram or evidence | Failure and recovery coverage |
|
|
176
|
+
| ---------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
177
|
+
| Collect a stats item | The media engine calls the add operation; the item is anonymized if it is a stats report and appended | First sequence diagram below | An empty payload is ignored; a malformed stats-report item throws from `addMetrics` and is not queued; an unparseable connection-state payload is queued but never triggers rotation |
|
|
178
|
+
| Submit a batch | An interval tick, a forced first report, a connection failure or a close flushes the queue to the service | Second sequence diagram below | An empty queue submits nothing; a rejected request is not handled, so that batch is lost |
|
|
179
|
+
|
|
180
|
+
```mermaid
|
|
181
|
+
sequenceDiagram
|
|
182
|
+
participant Engine as Media engine
|
|
183
|
+
participant Rtc as RtcMetrics
|
|
184
|
+
participant Anon as IP anonymizer
|
|
185
|
+
participant Queue as Metrics queue
|
|
186
|
+
Engine->>Rtc: addMetrics(named payload array)
|
|
187
|
+
alt payload is empty
|
|
188
|
+
Rtc-->>Engine: return without queueing
|
|
189
|
+
end
|
|
190
|
+
opt payload is a stats report
|
|
191
|
+
Rtc->>Anon: anonymize each item's candidate addresses
|
|
192
|
+
alt item is not valid JSON
|
|
193
|
+
Anon-->>Engine: parse error thrown, nothing queued
|
|
194
|
+
else valid JSON
|
|
195
|
+
Anon-->>Rtc: re-serialized items
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
Rtc->>Queue: append item
|
|
199
|
+
opt first stats report of the connection
|
|
200
|
+
Rtc->>Rtc: submit immediately and clear the force-send flag
|
|
201
|
+
end
|
|
202
|
+
opt connection state change first item parses and reports failed
|
|
203
|
+
Rtc->>Rtc: submit, then rotate the connection identifier
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
```mermaid
|
|
208
|
+
sequenceDiagram
|
|
209
|
+
participant Timer as Thirty-second interval
|
|
210
|
+
participant Rtc as RtcMetrics
|
|
211
|
+
participant Queue as Metrics queue
|
|
212
|
+
participant Telemetry as Unified telemetry service
|
|
213
|
+
Timer->>Rtc: tick
|
|
214
|
+
alt queue is empty
|
|
215
|
+
Rtc-->>Timer: submit nothing
|
|
216
|
+
else queue has items
|
|
217
|
+
Rtc->>Queue: read items
|
|
218
|
+
Rtc->>Telemetry: POST batch with type, version, identifiers and items
|
|
219
|
+
Rtc->>Queue: clear
|
|
220
|
+
opt request rejected
|
|
221
|
+
Note over Rtc,Telemetry: the promise is not handled, so this batch is lost
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## Class and component relationships
|
|
227
|
+
|
|
228
|
+
```mermaid
|
|
229
|
+
classDiagram
|
|
230
|
+
class Meeting {
|
|
231
|
+
+correlationId
|
|
232
|
+
+createRtcMetrics()
|
|
233
|
+
+closeRtcMetrics()
|
|
234
|
+
}
|
|
235
|
+
class RtcMetrics {
|
|
236
|
+
+metricsQueue
|
|
237
|
+
+intervalId
|
|
238
|
+
+correlationId
|
|
239
|
+
+connectionId
|
|
240
|
+
+shouldSendMetricsOnNextStatsReport
|
|
241
|
+
+addMetrics()
|
|
242
|
+
+sendMetricsInQueue()
|
|
243
|
+
+sendNextMetrics()
|
|
244
|
+
+updateCallId()
|
|
245
|
+
+closeMetrics()
|
|
246
|
+
+anonymizeIp()
|
|
247
|
+
-resetConnection()
|
|
248
|
+
-sendMetrics()
|
|
249
|
+
}
|
|
250
|
+
class CallDiagnosticUtils {
|
|
251
|
+
+anonymizeIPAddress()
|
|
252
|
+
}
|
|
253
|
+
class RtcMetricsConstants {
|
|
254
|
+
+APP_ID
|
|
255
|
+
}
|
|
256
|
+
Meeting --> RtcMetrics : one per media connection
|
|
257
|
+
RtcMetrics --> CallDiagnosticUtils : anonymizes candidate addresses
|
|
258
|
+
RtcMetrics --> RtcMetricsConstants : reads the application identifier
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Use cases and flows
|
|
262
|
+
|
|
263
|
+
| Use case | Actor or caller | Primary steps and outcome | Failure or boundary behavior | Evidence |
|
|
264
|
+
| -------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- |
|
|
265
|
+
| `UC-001` | Media engine | A stats report arrives during a healthy call; candidate addresses are anonymized and the item is queued until the next interval tick | An empty payload is ignored entirely | `src/rtcMetrics/index.ts`, `test/unit/spec/rtcMetrics/index.ts` |
|
|
266
|
+
| `UC-002` | Media engine | The first stats report of a connection arrives; it is submitted immediately instead of waiting up to thirty seconds | The flag is cleared, so the second report waits for the interval | `src/rtcMetrics/index.ts`, `test/unit/spec/rtcMetrics/index.ts` |
|
|
267
|
+
| `UC-003` | Media engine | A connection-state item reports a failed connection; the queue is flushed and a new connection identifier is generated and re-armed | Items queued afterwards are attributed to the new connection identifier | `src/rtcMetrics/index.ts`, `test/unit/spec/rtcMetrics/index.ts` |
|
|
268
|
+
| `UC-004` | Calling session | The call identifier becomes known after construction; it is set and appears in later batches | A meeting identifier, if present, still takes precedence over the call identifier | `src/rtcMetrics/index.ts`, `test/unit/spec/rtcMetrics/index.ts` |
|
|
269
|
+
| `UC-005` | Meetings layer | The call ends; closing flushes the final window and clears the interval | Closing with an empty queue clears the interval without submitting | `src/rtcMetrics/index.ts`, `test/unit/spec/rtcMetrics/index.ts` |
|
|
270
|
+
|
|
271
|
+
<!-- Include if: the module calls another service or crosses an event or network boundary. [condition-id: module.crosses_service_boundaries] -->
|
|
272
|
+
|
|
273
|
+
### Cross-boundary use-case flow
|
|
274
|
+
|
|
275
|
+
All five use cases converge on one outbound boundary: a POST to the unified telemetry service's
|
|
276
|
+
versioned metric resource, through the host SDK request pipeline.
|
|
277
|
+
|
|
278
|
+
- Boundary and transport: HTTPS POST to the `unifiedTelemetry` service, resource `metric/v2`, with a
|
|
279
|
+
media type header and the registered application identifier from `src/rtcMetrics/constants.ts`.
|
|
280
|
+
- Ordering: batches are submitted in queue order and one submission is started per flush. Nothing
|
|
281
|
+
serializes concurrent flushes, so a forced submission and an interval tick in the same turn can
|
|
282
|
+
produce two in-flight requests; because the queue is cleared between them, they carry disjoint data.
|
|
283
|
+
- Compatibility: the batch declares its own version field, so ingestion can distinguish payload
|
|
284
|
+
generations. The statistics items themselves are opaque strings and pass through unchanged.
|
|
285
|
+
- Timeout and retry: none. This module does not use the batcher family, so there is no queueing,
|
|
286
|
+
backoff or re-enqueue. A failed request loses that batch.
|
|
287
|
+
- Recovery: the next window submits new data. There is no replay of a lost batch, which is an
|
|
288
|
+
accepted limitation for sampled media statistics.
|
|
289
|
+
|
|
290
|
+
<!-- Include if: the module holds client-side state. [condition-id: module.holds_client_state] -->
|
|
291
|
+
|
|
292
|
+
## Client state model
|
|
293
|
+
|
|
294
|
+
| State or slice | Owner | Initial state | Transition triggers | Reset or persistence boundary |
|
|
295
|
+
| ------------------------------------ | ---------------------- | ---------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------ |
|
|
296
|
+
| Metrics queue | The reporter instance | Empty array | An item is appended by the add operation | Emptied on every submission; never persisted |
|
|
297
|
+
| Connection identifier | The reporter instance | Generated at construction | Regenerated when a connection-state item reports a failed connection | Lives for one media connection attempt |
|
|
298
|
+
| Force-send flag | The reporter instance | Armed at construction | Cleared after the first stats report is submitted; re-armed on rotation or on demand | Per connection attempt |
|
|
299
|
+
| Interval handle | The reporter instance | Started at construction | None | Cleared only by closing the reporter |
|
|
300
|
+
| Meeting and call identifiers | The reporter instance | Supplied at construction | The call identifier can be replaced through the public setter | Lives for the reporter's lifetime |
|
|
301
|
+
|
|
302
|
+
<!-- Include if: the module enforces domain rules or entity invariants. [condition-id: module.enforces_domain_rules] -->
|
|
303
|
+
|
|
304
|
+
## Business rules and invariants
|
|
305
|
+
|
|
306
|
+
| ID | Invariant | WHY | Enforcement source | Test evidence |
|
|
307
|
+
| --------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | -------------------------- | ------------------------------------ |
|
|
308
|
+
| `INV-001` | No candidate address leaves the client un-anonymized | Candidate addresses are personally identifying network data | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` |
|
|
309
|
+
| `INV-002` | Anonymization is applied only to stats reports, and only to local and remote candidate entries | Other item kinds carry no address fields, so parsing and rewriting them would be wasted work and risk | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` |
|
|
310
|
+
| `INV-003` | A submitted batch is never empty | Empty requests would add call-duration-proportional noise to ingestion | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` |
|
|
311
|
+
| `INV-004` | The queue is flushed before the connection identifier rotates | Otherwise a failed connection's statistics would be attributed to its replacement | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` |
|
|
312
|
+
| `INV-005` | A batch carries at most one of the meeting and call identifiers | Ingestion routes on exactly one; both would be ambiguous | `src/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` |
|
|
313
|
+
|
|
314
|
+
<!-- Include if: the module is concurrent, asynchronous, reactive, or event-driven. [condition-id: module.is_concurrent_async] -->
|
|
315
|
+
|
|
316
|
+
## Concurrency and reactive flow
|
|
317
|
+
|
|
318
|
+
- Execution model: a single repeating browser interval, started in the constructor, plus synchronous
|
|
319
|
+
calls from the media engine and the meetings layer. The browser `setInterval` is used explicitly so
|
|
320
|
+
the handle is typed as a browser timer rather than a Node.js one.
|
|
321
|
+
- Ordering guarantees: items are appended and submitted in arrival order. Within one submission the
|
|
322
|
+
queue is read, sent and cleared without yielding, so an item appended during the request belongs to
|
|
323
|
+
the next batch.
|
|
324
|
+
- Idempotency and retry: none. There is no deduplication key and no retry; each batch is submitted at
|
|
325
|
+
most once and a rejected request is not re-sent.
|
|
326
|
+
- Shared-state protection: single-threaded by construction. Nothing locks the queue, and nothing
|
|
327
|
+
needs to, because every mutation happens in a synchronous block.
|
|
328
|
+
- Blocking restrictions: no operation awaits the submission. Collection, forced submission and close
|
|
329
|
+
all return void, so a slow telemetry request can never delay the media path.
|
|
330
|
+
|
|
331
|
+
<!-- Include if: the module has non-trivial state transitions. [condition-id: module.stateful_transitions] -->
|
|
332
|
+
|
|
333
|
+
## State machine
|
|
334
|
+
|
|
335
|
+
The state is the connection's reporting lifecycle, keyed by the connection identifier.
|
|
336
|
+
|
|
337
|
+
```mermaid
|
|
338
|
+
stateDiagram-v2
|
|
339
|
+
[*] --> AwaitingFirstReport: construct and arm force-send
|
|
340
|
+
AwaitingFirstReport --> Reporting: first stats report submitted immediately
|
|
341
|
+
AwaitingFirstReport --> AwaitingFirstReport: non-stats item queued
|
|
342
|
+
Reporting --> Reporting: interval tick submits a non-empty queue
|
|
343
|
+
Reporting --> Reporting: interval tick with an empty queue submits nothing
|
|
344
|
+
Reporting --> AwaitingFirstReport: connection failed, so flush then rotate the identifier
|
|
345
|
+
AwaitingFirstReport --> AwaitingFirstReport: force-send re-armed on demand
|
|
346
|
+
Reporting --> AwaitingFirstReport: force-send re-armed on demand
|
|
347
|
+
Reporting --> [*]: close flushes then clears the interval
|
|
348
|
+
AwaitingFirstReport --> [*]: close flushes then clears the interval
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
Guards and rejected transitions:
|
|
352
|
+
|
|
353
|
+
- Only a stats report can leave `AwaitingFirstReport`; queueing any other item kind keeps the flag
|
|
354
|
+
armed.
|
|
355
|
+
- Only a connection-state item whose parsed value reports a failed connection triggers rotation. An
|
|
356
|
+
unparseable connection-state payload is still queued, but the guarded parse returns nothing and no
|
|
357
|
+
rotation occurs.
|
|
358
|
+
- A malformed stats-report item is rejected before queueing: the unguarded anonymization parse throws
|
|
359
|
+
out of `addMetrics`, so neither the queue nor the force-send flag changes.
|
|
360
|
+
- There is no terminal state other than close. A failed connection returns to the start of the
|
|
361
|
+
lifecycle rather than ending it.
|
|
362
|
+
|
|
363
|
+
<!-- Include if: callers depend on a wire protocol or serialized format. [condition-id: module.exposes_wire_protocol] -->
|
|
364
|
+
|
|
365
|
+
## Protocol and wire format
|
|
366
|
+
|
|
367
|
+
| Message or frame | Version | Producer or serializer | Consumer or parser | Compatibility and ordering rule |
|
|
368
|
+
| ------------------------------ | ------- | --------------------------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
|
|
369
|
+
| WebRTC media metrics batch | `1.1.0` | `src/rtcMetrics/index.ts` | Webex unified telemetry ingestion | The version is carried in the payload. Field removal or rename is breaking for ingestion; additions are compatible |
|
|
370
|
+
| Serialized stats item | n/a | The media engine | Webex unified telemetry ingestion | Opaque to this module except for candidate address fields. Items are submitted in arrival order within a batch |
|
|
371
|
+
|
|
372
|
+
The batch is posted as the single element of a metrics array, with the media type and the registered
|
|
373
|
+
application identifier carried as request headers rather than in the body. The attribute shape is
|
|
374
|
+
declared in `src/metrics.types.ts` and the identifier value in `src/rtcMetrics/constants.ts`; neither
|
|
375
|
+
is restated here.
|
|
376
|
+
|
|
377
|
+
## Pitfalls and constraints
|
|
378
|
+
|
|
379
|
+
- `sendMetrics` neither returns nor catches the telemetry request, so a failed POST becomes an
|
|
380
|
+
unhandled promise rejection in the host page rather than a logged failure. With unhandled exception
|
|
381
|
+
telemetry enabled, that rejection is itself captured and reported as an application failure.
|
|
382
|
+
- Only a connection-state value of exactly `failed` rotates the connection identifier. `disconnected`
|
|
383
|
+
and `closed` states are queued like any other item and keep the current identifier.
|
|
384
|
+
- When both a meeting identifier and a call identifier are present, only the meeting identifier is
|
|
385
|
+
sent; `updateCallId` therefore has no visible effect on a meeting-scoped instance.
|
|
386
|
+
- The 30-second interval starts in the constructor and is cleared only by `closeMetrics`. An instance
|
|
387
|
+
that is dropped without being closed keeps its timer and its queue alive.
|
|
388
|
+
- Callers must pass stats-report items as serialized JSON. A malformed item throws out of
|
|
389
|
+
`addMetrics` into the media-engine callback that delivered it.
|
|
390
|
+
|
|
391
|
+
<!-- Include if: the module has conventions beyond repository-wide rules. [condition-id: module.module_specific_conventions] -->
|
|
392
|
+
|
|
393
|
+
## Module-specific rules
|
|
394
|
+
|
|
395
|
+
- Do: keep stats items as JSON strings. Parse one item, rewrite only what must change, and
|
|
396
|
+
re-serialize. Building an object model of a statistics report would couple this module to a schema
|
|
397
|
+
the media engine owns.
|
|
398
|
+
- Do: flush before rotating the connection identifier, and flush before clearing the interval. Both
|
|
399
|
+
orderings are load-bearing, not incidental.
|
|
400
|
+
- Do: use the browser interval explicitly, as the current code does, so the handle keeps its browser
|
|
401
|
+
timer type.
|
|
402
|
+
- Do not: add a field to the batch without confirming the unified telemetry service accepts it. The
|
|
403
|
+
payload declares its own version for exactly this reason.
|
|
404
|
+
- Do not: return or await the submission promise from any public operation. Every operation returns
|
|
405
|
+
void so that telemetry cannot delay or fail the media path.
|
|
406
|
+
- Do not: rely on this class outside a browser. It calls the browser interval API directly at
|
|
407
|
+
construction time.
|
|
408
|
+
|
|
409
|
+
<!-- Include if: the module is published or consumed as a package. [condition-id: module.published_package] -->
|
|
410
|
+
|
|
411
|
+
## Export stability
|
|
412
|
+
|
|
413
|
+
| Export or entry point | Consumer | Stability | Versioning and deprecation rule | Declaration or API report |
|
|
414
|
+
| --------------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
415
|
+
| `RtcMetrics` | `packages/@webex/plugin-meetings/src/media/index.ts` | Internal plugin export; additive | Constructor arity and the public operation names are depended on by the meetings layer; change them together in one monorepo change | `src/index.ts` |
|
|
416
|
+
| `IdType` | `packages/@webex/plugin-meetings/src/meeting/index.ts` | Internal type export | Declared in `src/metrics.types.ts` and re-exported from the package entry | `src/metrics.types.ts` |
|
|
417
|
+
|
|
418
|
+
<!-- Include if: the module has a non-obvious design trade-off consumers or maintainers must preserve. [condition-id: module.has_design_tradeoff] -->
|
|
419
|
+
|
|
420
|
+
## Key design trade-off
|
|
421
|
+
|
|
422
|
+
| Chosen trade-off | Preserved invariant or benefit | Cost or limitation | Decision evidence |
|
|
423
|
+
| ----------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
424
|
+
| Submit the first stats report immediately instead of waiting for the first interval | A user who abandons a call in the first thirty seconds — often because media quality is bad — is still represented in the data | One extra request per connection, and the first batch is smaller than a full window | `src/rtcMetrics/index.ts` |
|
|
425
|
+
| Post directly rather than through the batcher family used by the rest of the package | No queueing, retry or prepare step stands between the media engine and a sampled statistics batch | No retry and no backoff: a failed request loses that batch permanently, where a batcher would have re-enqueued it | `src/rtcMetrics/index.ts` |
|
|
426
|
+
| Rotate the connection identifier on failure rather than keeping one identifier per call | Consumers can tell one media connection attempt from the next, which is what makes a reconnect analyzable | Correlating a whole call requires joining batches on the correlation identifier instead of the connection identifier | `src/rtcMetrics/index.ts` |
|
|
427
|
+
| Treat stats items as opaque strings | A schema change in the media engine needs no change here | The module cannot validate or reduce what it forwards, and candidate anonymization has to parse and re-serialize to reach the fields | `src/rtcMetrics/index.ts` |
|
|
428
|
+
|
|
429
|
+
## Verification
|
|
430
|
+
|
|
431
|
+
| Requirement or invariant | Test level | Positive evidence | Negative or boundary evidence | Gap |
|
|
432
|
+
| ------------------------ | ---------- | ------------------------------------ | ------------------------------------- | --------------------------------------------------------------------------------------------- |
|
|
433
|
+
| `MOD-001` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
434
|
+
| `MOD-002` / `INV-003` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
435
|
+
| `MOD-003` / `INV-001` | Unit | `test/unit/spec/rtcMetrics/index.ts` | none found | No case asserts that a non-candidate stats item is left untouched, so `INV-002` is partly unverified |
|
|
436
|
+
| `MOD-004` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
437
|
+
| `MOD-005` / `INV-004` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
438
|
+
| `MOD-006` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
439
|
+
| `MOD-007` / `INV-005` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
440
|
+
| `MOD-008` | Unit | `test/unit/spec/rtcMetrics/index.ts` | none found | No case asserts the meeting identifier still wins after the call identifier is replaced |
|
|
441
|
+
| `MOD-009` | Unit | `test/unit/spec/rtcMetrics/index.ts` | `test/unit/spec/rtcMetrics/index.ts` | none |
|
|
442
|
+
| `MOD-010` | Unit | none found | none found | Neither the throwing stats-report parse nor the guarded connection-state parse has a case |
|
|
443
|
+
| `MOD-011` | Unit | none found | none found | No case rejects the telemetry request and asserts the caller is unaffected |
|
|
444
|
+
|
|
445
|
+
Record coverage gaps explicitly and link follow-up work. A module specification
|
|
446
|
+
is complete only when its public surface, invariants, failure modes, and test
|
|
447
|
+
evidence agree with the implementation.
|