@webex/internal-plugin-metrics 3.12.0 → 3.13.0-next.2

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.
Files changed (98) hide show
  1. package/.repo-context.json +466 -0
  2. package/.sdd/manifest.json +626 -0
  3. package/AGENTS.md +208 -0
  4. package/README.md +274 -0
  5. package/dist/automated-user.js +19 -0
  6. package/dist/automated-user.js.map +1 -0
  7. package/dist/batcher.js +3 -0
  8. package/dist/batcher.js.map +1 -1
  9. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js +639 -66
  10. package/dist/call-diagnostic/call-diagnostic-metrics-latencies.js.map +1 -1
  11. package/dist/call-diagnostic/call-diagnostic-metrics.js +240 -103
  12. package/dist/call-diagnostic/call-diagnostic-metrics.js.map +1 -1
  13. package/dist/call-diagnostic/call-diagnostic-metrics.util.js +7 -2
  14. package/dist/call-diagnostic/call-diagnostic-metrics.util.js.map +1 -1
  15. package/dist/call-diagnostic/config.js +19 -4
  16. package/dist/call-diagnostic/config.js.map +1 -1
  17. package/dist/config.js +8 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/index.js +25 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/metrics.js +118 -5
  22. package/dist/metrics.js.map +1 -1
  23. package/dist/metrics.types.js +4 -0
  24. package/dist/metrics.types.js.map +1 -1
  25. package/dist/network-telemetry.js +610 -0
  26. package/dist/network-telemetry.js.map +1 -0
  27. package/dist/new-metrics.js +63 -17
  28. package/dist/new-metrics.js.map +1 -1
  29. package/dist/privacy-and-security-permission-enricher/constants.js +18 -0
  30. package/dist/privacy-and-security-permission-enricher/constants.js.map +1 -0
  31. package/dist/privacy-and-security-permission-enricher/index.js +143 -0
  32. package/dist/privacy-and-security-permission-enricher/index.js.map +1 -0
  33. package/dist/privacy-and-security-permission-enricher/types.js +7 -0
  34. package/dist/privacy-and-security-permission-enricher/types.js.map +1 -0
  35. package/dist/privacy-and-security-permission-enricher/utils.js +71 -0
  36. package/dist/privacy-and-security-permission-enricher/utils.js.map +1 -0
  37. package/dist/types/automated-user.d.ts +2 -0
  38. package/dist/types/call-diagnostic/call-diagnostic-metrics-latencies.d.ts +208 -5
  39. package/dist/types/call-diagnostic/call-diagnostic-metrics.d.ts +83 -17
  40. package/dist/types/call-diagnostic/config.d.ts +6 -0
  41. package/dist/types/config.d.ts +9 -0
  42. package/dist/types/index.d.ts +5 -3
  43. package/dist/types/metrics.types.d.ts +11 -5
  44. package/dist/types/network-telemetry.d.ts +108 -0
  45. package/dist/types/new-metrics.d.ts +17 -1
  46. package/dist/types/privacy-and-security-permission-enricher/constants.d.ts +6 -0
  47. package/dist/types/privacy-and-security-permission-enricher/index.d.ts +40 -0
  48. package/dist/types/privacy-and-security-permission-enricher/types.d.ts +14 -0
  49. package/dist/types/privacy-and-security-permission-enricher/utils.d.ts +5 -0
  50. package/dist/types/unhandled-exception-telemetry/index.d.ts +60 -0
  51. package/dist/types/unhandled-exception-telemetry/utils.d.ts +6 -0
  52. package/dist/unhandled-exception-telemetry/index.js +331 -0
  53. package/dist/unhandled-exception-telemetry/index.js.map +1 -0
  54. package/dist/unhandled-exception-telemetry/utils.js +105 -0
  55. package/dist/unhandled-exception-telemetry/utils.js.map +1 -0
  56. package/docs/adr/index.md +27 -0
  57. package/docs/architecture.md +437 -0
  58. package/docs/getting-started.md +131 -0
  59. package/docs/index.md +52 -0
  60. package/docs/specs/README.md +88 -0
  61. package/package.json +12 -11
  62. package/schemas/repo-context.schema.json +796 -0
  63. package/src/automated-user.ts +16 -0
  64. package/src/batcher.js +4 -0
  65. package/src/call-diagnostic/call-diagnostic-metrics-latencies.ts +801 -74
  66. package/src/call-diagnostic/call-diagnostic-metrics.ts +184 -42
  67. package/src/call-diagnostic/call-diagnostic-metrics.util.ts +7 -1
  68. package/src/call-diagnostic/config.ts +16 -0
  69. package/src/call-diagnostic/docs/README.md +535 -0
  70. package/src/config.js +8 -0
  71. package/src/docs/README.md +703 -0
  72. package/src/index.ts +16 -0
  73. package/src/metrics.js +123 -5
  74. package/src/metrics.types.ts +29 -6
  75. package/src/network-telemetry.ts +757 -0
  76. package/src/new-metrics.ts +56 -6
  77. package/src/privacy-and-security-permission-enricher/constants.ts +33 -0
  78. package/src/privacy-and-security-permission-enricher/docs/README.md +356 -0
  79. package/src/privacy-and-security-permission-enricher/index.ts +142 -0
  80. package/src/privacy-and-security-permission-enricher/types.ts +21 -0
  81. package/src/privacy-and-security-permission-enricher/utils.ts +82 -0
  82. package/src/rtcMetrics/docs/README.md +447 -0
  83. package/src/unhandled-exception-telemetry/docs/README.md +536 -0
  84. package/src/unhandled-exception-telemetry/index.ts +404 -0
  85. package/src/unhandled-exception-telemetry/utils.ts +101 -0
  86. package/test/unit/spec/automated-user.ts +43 -0
  87. package/test/unit/spec/batcher.js +43 -0
  88. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-batcher.ts +14 -2
  89. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics-latencies.ts +1435 -304
  90. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.ts +1265 -207
  91. package/test/unit/spec/call-diagnostic/call-diagnostic-metrics.util.ts +27 -0
  92. package/test/unit/spec/metrics.js +14 -5
  93. package/test/unit/spec/network-telemetry.ts +610 -0
  94. package/test/unit/spec/new-metrics.ts +203 -50
  95. package/test/unit/spec/prelogin-metrics-batcher.ts +71 -36
  96. package/test/unit/spec/privacy-and-security-permission-enricher.ts +318 -0
  97. package/test/unit/spec/unhandled-exception-telemetry/utils.ts +109 -0
  98. package/test/unit/spec/unhandled-exception-telemetry.ts +689 -0
@@ -0,0 +1,703 @@
1
+ ---
2
+ type: Module Spec
3
+ title: 'Metrics plugin root specification'
4
+ description: Responsibilities, boundaries, design, invariants, and verification for the metrics plugin root module.
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
+ # Metrics plugin root
17
+
18
+ This source-local document at `src/docs/README.md` owns the stable specification for **the metrics
19
+ plugin root module**: plugin registration, the legacy `metrics` and current `newMetrics` façades, the
20
+ batcher family, tagged-event metrics and SDK network telemetry. Ground every claim in repository
21
+ evidence and link to the [repository architecture](../../docs/architecture.md) instead of repeating
22
+ broader facts.
23
+
24
+ Related context: [documentation index](../../docs/index.md) ·
25
+ [documentation agent instructions](../../AGENTS.md)
26
+
27
+ ## Metadata
28
+
29
+ | Field | Value |
30
+ | ------------- | ------------------------------------------------------------ |
31
+ | Owner | Cisco Webex JS SDK — telemetry |
32
+ | Source path | `src/` |
33
+ | Resource kind | package |
34
+ | Status | Active |
35
+ | Last verified | 2026-10-06 at `d6202dd73d` |
36
+ | Module id | `metrics-root` |
37
+ | Parent spec | — |
38
+ | Doc kind | Module spec |
39
+ | Coverage score | Pending coverage assessment |
40
+ | 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) |
41
+
42
+ ## Applicability
43
+
44
+ Sections preceded by an `Include if` comment are conditional. Retain a
45
+ conditional section only when source evidence or a confirmed developer answer
46
+ satisfies its condition.
47
+
48
+ | Condition ID | Status | Evidence or reason | Owned section |
49
+ | ------------------------------------ | ---------- | -------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
50
+ | `module.has_tiers` | N/A | The repository defines no operational or review tier policy for modules | Tier |
51
+ | `module.has_ui` | N/A | The package renders nothing | UI use-case flow |
52
+ | `module.crosses_service_boundaries` | Applicable | `src/batcher.js` and `src/client-metrics-batcher.js` post to the metrics service; `src/metrics.js` subscribes to SDK request events | Cross-boundary use-case flow |
53
+ | `module.holds_client_state` | Applicable | The network telemetry window in `src/network-telemetry.ts` and the readiness and delay flags in `src/new-metrics.ts` | Client state model |
54
+ | `module.enforces_domain_rules` | Applicable | Counter mapping, failure classification, self-exclusion and endpoint normalization in `src/network-telemetry.ts` | Business rules and invariants |
55
+ | `module.is_concurrent_async` | Applicable | The telemetry interval, in-flight submission tracking and batcher backoff in `src/network-telemetry.ts` and `src/batcher.js` | Concurrency and reactive flow |
56
+ | `module.owns_persistence` | N/A | Nothing is persisted; every aggregate and queue is in-memory | Data, schema, and migration |
57
+ | `module.stateful_transitions` | Applicable | Façade readiness in `src/new-metrics.ts` and the collector lifecycle in `src/metrics.js` | State machine |
58
+ | `module.exposes_wire_protocol` | Applicable | The client metric payload in `src/metrics.js` and the summary metric in `src/network-telemetry.ts` | Protocol and wire format |
59
+ | `module.ui_multi_screen` | N/A | No user interface | UI flow |
60
+ | `module.large_data_model` | N/A | Event types are derived from the external schema in `src/metrics.types.ts` rather than modeled here | Data model |
61
+ | `module.returns_caller_errors` | Applicable | `src/metrics.js` throws when a client metric has no name; pre-login submission rejects without an identifier | Caller-visible failure modes |
62
+ | `module.module_specific_conventions` | Applicable | Ampersand-based JavaScript plugins coexist with TypeScript classes, and telemetry must never fail a caller | Module-specific rules |
63
+ | `module.published_package` | Applicable | `package.json` declares the published entry points and `src/index.ts` the export surface | Export stability |
64
+ | `module.embedded_in_host` | Applicable | `src/index.ts` mounts both plugins into a host SDK instance with a before-logout lifecycle hook | Host integration and theming |
65
+ | `module.has_design_tradeoff` | Applicable | Reset-before-submit telemetry windows and the two-façade layout | Key design trade-off |
66
+ | `module.has_submodules` | Applicable | Derived from the manifest module tree: four child modules | Sub-modules |
67
+
68
+ ## Evidence register
69
+
70
+ | Evidence | What it establishes |
71
+ | ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
72
+ | `src/index.ts` | Plugin registration, the logout hook and the full export surface |
73
+ | `src/metrics.js` | The legacy façade, its batcher children, client metric payload construction and the network telemetry wiring |
74
+ | `src/new-metrics.ts` | The current façade, readiness gating, lazy backends, delay control and composition of the child modules |
75
+ | `src/network-telemetry.ts` | The request-outcome collector, its classification and normalization rules and the summary payload |
76
+ | `src/batcher.js` | Item defaults, the metrics resource transport and network-error backoff |
77
+ | `src/client-metrics-batcher.js` | The client-metrics resource transport |
78
+ | `src/prelogin-metrics-batcher.ts` | The unauthenticated pre-login transport and its identifier requirement |
79
+ | `src/client-metrics-prelogin-batcher.ts` | The pre-login transport used by the legacy façade |
80
+ | `src/generic-metrics.ts` | The shared tagged-event base: context, browser details, device identifier and readiness |
81
+ | `src/behavioral-metrics.ts` | Behavioral event naming and submission |
82
+ | `src/operational-metrics.ts` | Operational event submission |
83
+ | `src/business-metrics.ts` | Business event table routing |
84
+ | `src/prelogin-metrics.ts` | Pre-login business event construction |
85
+ | `src/config.js` | Plugin defaults, including both telemetry features disabled and the ten-minute interval |
86
+ | `src/automated-user.ts` | Automated user classification and its memoization |
87
+ | `src/metrics.types.ts` | The exported type surface derived from the external Call Analyzer schema |
88
+ | `test/unit/spec/network-telemetry.ts` | End-to-end verification of the collector against SDK request events |
89
+ | `test/unit/spec/metrics.js` | Verification of the legacy façade payload, routing and alias request |
90
+ | `test/unit/spec/new-metrics.ts` | Verification of readiness, lazy backends, enrichment wiring and the unhandled-exception start |
91
+ | `test/unit/spec/batcher.js` | Verification of queue clearing, network-error re-enqueue and the retry opt-out |
92
+ | Reconciled owner documentation | Owner-authored network telemetry design and package purpose, reconciled by meaning into this spec; one stale initialization code block corrected against `src/metrics.js` |
93
+
94
+ ## Purpose and boundary
95
+
96
+ - Responsibility: the plugin for the Webex Metrics service — the package's registration and transport
97
+ layer — mounting the `metrics` and
98
+ `newMetrics` internal plugins, owning every batcher, building client metric and tagged-event
99
+ payloads, and summarizing SDK request outcomes.
100
+ - In scope: plugin registration and the logout hook; the legacy client-metrics path, including
101
+ pre-login submission and user aliasing; the current façade's readiness gate, lazy backends and
102
+ delay control; behavioral, operational, business and pre-login tagged events; the batcher family;
103
+ and network request telemetry.
104
+ - Network telemetry scope, confirmed by `src/network-telemetry.ts`: the collector counts request and
105
+ response outcomes that pass through `webex.request()` and its interceptors. For failures, it also
106
+ records aggregated error details covering:
107
+ - HTTP responses rejected by the SDK, such as `4xx` and `5xx` responses.
108
+ - Network or CORS failures where no HTTP response is available.
109
+ - Aborted and timed-out SDK requests when the error exposes that information.
110
+ - Request preparation failures that emit `request:failure`.
111
+ - Out of scope: network telemetry does not monitor arbitrary application `fetch()`,
112
+ `XMLHttpRequest`, WebSocket, or other traffic outside the SDK request pipeline. It also does not use
113
+ `PerformanceObserver` or browser-wide request interception. The Call Analyzer event model,
114
+ permission enrichment, WebRTC stats and browser exception capture are each owned by a child module.
115
+ - Consumers: sibling SDK plugins and first party clients, through `webex.internal.metrics` and
116
+ `webex.internal.newMetrics`. This is an internal Cisco Webex plugin. As such, it does not strictly
117
+ adhere to semantic versioning; use it at your own risk. Anyone not working on one of the first party
118
+ clients should use the public developer API and stick to the public plugins.
119
+
120
+ ## Structure and key files
121
+
122
+ | Path | Responsibility |
123
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
124
+ | `src/index.ts` | Registration of both plugins and the export surface; authoritative for what the package publishes |
125
+ | `src/metrics.js` | The legacy `metrics` plugin; authoritative for the client metric payload and the network telemetry lifecycle |
126
+ | `src/new-metrics.ts` | The `newMetrics` plugin; authoritative for readiness gating and composition of the child modules |
127
+ | `src/network-telemetry.ts` | The request-outcome collector; authoritative for counters, classification, normalization and the summary shape |
128
+ | `src/batcher.js` | Base batcher for this package; authoritative for item defaults and network-error backoff |
129
+ | `src/client-metrics-batcher.js` | Batcher bound to the client-metrics resource |
130
+ | `src/prelogin-metrics-batcher.ts` | Batcher bound to the unauthenticated pre-login resource |
131
+ | `src/client-metrics-prelogin-batcher.ts` | Pre-login batcher variant without diagnostic preparation, used by the legacy façade |
132
+ | `src/generic-metrics.ts` | Abstract base for tagged events |
133
+ | `src/behavioral-metrics.ts` | Behavioral events |
134
+ | `src/operational-metrics.ts` | Operational events |
135
+ | `src/business-metrics.ts` | Business events and table routing |
136
+ | `src/prelogin-metrics.ts` | Pre-login business events |
137
+ | `src/config.js` | Plugin configuration defaults and OS name mapping |
138
+ | `src/utils.ts` | Shared error serialization for log lines |
139
+ | `src/automated-user.ts` | Automated user classification |
140
+ | `src/metrics.types.ts` | The exported type surface |
141
+
142
+ <!-- Include if: this module contains child modules that own their own specifications. [condition-id: module.has_submodules] -->
143
+
144
+ ## Sub-modules
145
+
146
+ | Sub-module | Responsibility | Specification |
147
+ | ------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------- |
148
+ | `src/call-diagnostic/` | Call Analyzer events, the latency ledger and error-code mapping | [`src/call-diagnostic/docs/README.md`](../call-diagnostic/docs/README.md) |
149
+ | `src/privacy-and-security-permission-enricher/` | Permission-change enrichment of client events | [`src/privacy-and-security-permission-enricher/docs/README.md`](../privacy-and-security-permission-enricher/docs/README.md) |
150
+ | `src/rtcMetrics/` | WebRTC stats telemetry to the unified telemetry service | [`src/rtcMetrics/docs/README.md`](../rtcMetrics/docs/README.md) |
151
+ | `src/unhandled-exception-telemetry/` | Browser uncaught error and rejection reporting | [`src/unhandled-exception-telemetry/docs/README.md`](../unhandled-exception-telemetry/docs/README.md) |
152
+
153
+ This module references child behavior by contract identifier only: `call-diagnostic-event`,
154
+ `privacy-permission-enricher-sdk`, `rtc-metrics-payload` and `unhandled-exception-metric` in
155
+ `.sdd/manifest.json`.
156
+
157
+ ## Public surface
158
+
159
+ | Surface | Consumer | Compatibility commitment | Source |
160
+ | ------------------------------------------ | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------- |
161
+ | Package import side effect | Any host that imports the package | Importing registers `metrics` and `newMetrics` before the host SDK instance is constructed | `src/index.ts` |
162
+ | `webex.internal.metrics` — `submit`, `submitClientMetrics`, `getClientMetricsPayload`, `aliasUser` | Sibling plugins and the child modules | Additive; `submitClientMetrics` is the only path accepting a pre-login identifier | `src/metrics.js` |
163
+ | `webex.internal.metrics` — `flushNetworkTelemetry`, `stopNetworkTelemetry` | Callers needing an immediate summary; the logout hook | Both resolve immediately when telemetry is disabled | `src/metrics.js` |
164
+ | `webex.internal.newMetrics` — client, feature, MQE, behavioral, operational, business, pre-login and internal submissions | Sibling plugins and first party clients | Client, feature, behavioral, operational, business and pre-login submissions before SDK readiness are logged and resolve without submitting; `submitMQE` and `submitInternalEvent` are not readiness-guarded | `src/new-metrics.ts` |
165
+ | `webex.internal.newMetrics` — readiness probes, delay control, permission setter, alias, fetch helpers, expected-error check | Sibling plugins and first party clients | Additive | `src/new-metrics.ts` |
166
+ | Named exports — classes, utilities, config and types | Workspace siblings | Removing a named export is breaking for the monorepo | `src/index.ts` |
167
+ | Network telemetry configuration | First party clients | `enabled` defaults to false; `intervalMs` defaults to ten minutes and falls back to it when not a positive finite number | `src/config.js` |
168
+
169
+ Network request telemetry is disabled by default. Enable it with
170
+ `metrics.networkTelemetry.enabled: true`. When enabled, the metrics plugin summarizes requests made
171
+ through the Webex SDK request pipeline. The reporting interval is configurable with
172
+ `metrics.networkTelemetry.intervalMs` and defaults to ten minutes. It listens to the SDK-wide
173
+ `request:start`, `request:success`, and `request:failure` events, so individual plugins do not need
174
+ to wrap or replace `webex.request()`. For example, to collect one-minute windows, set
175
+ `metrics.networkTelemetry: {enabled: true, intervalMs: 60 * 1_000}`.
176
+
177
+ Initialization, current in `src/metrics.js`: the listeners and the accumulator are registered during
178
+ metrics plugin initialization. `initialize(...args)` calls `WebexPlugin.prototype.initialize`, then
179
+ returns early when the plugin is constructed standalone with no parent or collection. Otherwise an
180
+ idempotent initializer, guarded on an existing collector and on the enable flag being exactly `true`,
181
+ calls `createNetworkTelemetryCollector` with the interval, a submit callback and a failure callback,
182
+ and uses `listenTo` to subscribe to `request:start`, `request:success` and `request:failure`. Because
183
+ the SDK initializes child plugins before `webex.config` is populated, that initializer runs once
184
+ immediately and once more on the first `change:config` event. The listeners are installed once for
185
+ each Webex instance when `webex.internal.metrics` is constructed and the feature is enabled. The
186
+ metrics package must be imported before constructing the Webex instance; standard Webex SDK bundles
187
+ already import and register the metrics plugin. A caller that needs the current window submitted
188
+ immediately uses `await webex.internal.metrics.flushNetworkTelemetry()`.
189
+
190
+ ## Dependencies
191
+
192
+ | Dependency | Why it is required | Failure behavior |
193
+ | ------------------------------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
194
+ | `@webex/webex-core` | Plugin base classes, `Batcher`, HTTP error types and plugin registration | A breaking change fails registration at import time |
195
+ | `@webex/webex-core` request events | Source of network telemetry | Absent events produce empty windows: each interval still submits a zeroed summary, while flush and stop skip an empty window |
196
+ | `@webex/common` | Browser and OS detection, the browser-environment flag | Detection degrades to undefined values rather than throwing |
197
+ | `@webex/common-timers` | Telemetry interval and batcher backoff timers | None |
198
+ | Device plugin | Device URL for tagged-event readiness and context | No device means tagged events report not ready |
199
+ | Child modules | Call Analyzer events, enrichment, WebRTC stats, exception capture | Each child's failures are contained inside it |
200
+ | `@webex/event-dictionary-ts` | The authoritative event schema behind `src/metrics.types.ts` | A schema change surfaces at build time |
201
+ | `isbot`, `lodash`, `uuid` | Automated user detection, merging, identifiers | None |
202
+
203
+ ## Requirements
204
+
205
+ | ID | WHAT | WHY | Source evidence | Test or example evidence | Assumptions or gaps | Confidence |
206
+ | --------- | ----------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------------------------------------------- | ------------------- | ---------- |
207
+ | `MOD-001` | Importing the package registers `metrics` and `newMetrics`, and logout stops network telemetry first | Every SDK consumer relies on these mounts existing, and the final telemetry window must be submitted while credentials are still valid | `src/index.ts` | `test/unit/spec/network-telemetry.ts` | The logout hook wiring itself has no case; `stopNetworkTelemetry` is tested directly | Weak |
208
+ | `MOD-002` | `newMetrics` client, feature, behavioral, operational, business and pre-login submissions before the SDK emits ready are logged and resolve without submitting; `submitMQE` and `submitInternalEvent` have no readiness guard and delegate immediately | Before readiness the device and configuration needed to build events are not available | `src/new-metrics.ts` | `test/unit/spec/new-metrics.ts` | none | Present |
209
+ | `MOD-003` | Behavioral, operational and business backends are created lazily, and only after readiness | Their constructors read the device and logger from the SDK instance, which are not usable earlier | `src/new-metrics.ts` | `test/unit/spec/new-metrics.ts` | none | Present |
210
+ | `MOD-004` | Tagged events report ready only once a device identifier can be parsed from the device URL | Tagged events are keyed by device identifier downstream; submitting with an empty one would be unattributable | `src/generic-metrics.ts` | `test/unit/spec/behavioral/behavioral-metrics.ts` | none | Present |
211
+ | `MOD-005` | Client event payloads are enriched with permission changes before reaching Call Analyzer, scoped by correlation identifier | Permission history must follow the call, including across identifier transitions | `src/new-metrics.ts` | `test/unit/spec/new-metrics.ts` | none | Present |
212
+ | `MOD-006` | Unhandled exception telemetry is started when the SDK emits ready | It submits through this module's transport, which is only usable after readiness | `src/new-metrics.ts` | `test/unit/spec/new-metrics.ts` | none | Present |
213
+ | `MOD-007` | A client metric carries application name, version and URL, browser and OS details, SDK version and the client identifier | Ingestion segments every client metric by application and platform | `src/metrics.js` | `test/unit/spec/metrics.js` | none | Present |
214
+ | `MOD-008` | A client metric without a name throws | A nameless metric cannot be routed by ingestion | `src/metrics.js` | `test/unit/spec/metrics.js` | none | Present |
215
+ | `MOD-009` | A client metric with a pre-login identifier is submitted through the unauthenticated pre-login transport | Telemetry before sign-in must still be attributable to one session | `src/metrics.js` | `test/unit/spec/metrics.js` | none | Present |
216
+ | `MOD-010` | Pre-login submission rejects when no pre-login identifier has been saved | An unauthenticated request without its attribution header would be unusable and must never be sent | `src/prelogin-metrics-batcher.ts` | `test/unit/spec/prelogin-metrics-batcher.ts` | none | Present |
217
+ | `MOD-011` | Batched items that fail with a network or CORS error are re-enqueued with a delay that doubles up to the configured plateau, unless retry is disabled | Transient connectivity loss must not lose telemetry, but retry pressure must stay bounded | `src/batcher.js` | `test/unit/spec/batcher.js` | none | Present |
218
+ | `MOD-012` | Network telemetry is disabled by default and listens only when the enable flag is exactly `true` | Request telemetry adds a submission per window to every SDK instance, so it must be opt-in | `src/config.js` | `test/unit/spec/network-telemetry.ts` | none | Present |
219
+ | `MOD-013` | Telemetry initialization runs immediately and again on the first configuration change, at most once, and never for a standalone plugin | The SDK initializes children before configuration is populated, and a standalone plugin has no request events to observe | `src/metrics.js` | none found | The deferred and standalone paths have no dedicated case | Weak |
220
+ | `MOD-014` | Request outcomes are counted per summary and per host-and-endpoint, using the counter mapping in Business rules | Separating send, failed-request, received and failed-response counts distinguishes network failure from server failure | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none | Present |
221
+ | `MOD-015` | Each failure is classified into exactly one error type in a fixed order | Aggregation needs a bounded vocabulary, and the order makes overlapping signals deterministic | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | The `request_error` fallback has no case | Present |
222
+ | `MOD-016` | Identical failures are aggregated with a count and at most ten unique tracking identifiers | Keeps payload size bounded during a failure storm while retaining enough identifiers to trace examples | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none | Present |
223
+ | `MOD-017` | Requests to the metrics and unified telemetry services, including the legacy metrics API option, are excluded from collection | A telemetry upload must not appear in its own summary | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none | Present |
224
+ | `MOD-018` | Endpoints drop query data and fragments, and replace numeric, encoded, mixed-case and otherwise non-route-looking segments with `:id` | Keeps aggregate cardinality bounded and keeps identifiers out of telemetry | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none | Present |
225
+ | `MOD-019` | Direct-URI requests use the sanitized URI host and path; service requests use the logical service name as host | The start event can fire before the service URL is resolved, so the logical name is the stable identity | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none | Present |
226
+ | `MOD-020` | Each endpoint reports average, nearest-rank p90 and p99 network duration computed only from samples whose `networkStart` and `networkEnd` timings are finite numbers with end not before start; an endpoint with no valid sample reports zero for all three | Tail latency is the useful signal for SDK request health, and a missing or inverted timing pair would corrupt it rather than describe it | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | Requests with no timing metadata are covered and report zero; non-finite or inverted timing pairs have no case | Present |
227
+ | `MOD-021` | Every interval submits the current window, including an empty one as a zeroed summary; the window resets before submission | Requests during an in-flight submission must belong to the next window. The periodic path has no emptiness check, so an idle instance keeps a fixed reporting cadence | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | The zeroed second-interval payload is asserted directly; no documented rationale for reporting empty windows was found | Present |
228
+ | `MOD-022` | Flush submits the current window immediately only when it is non-empty and keeps collecting; stop clears the interval, submits any non-empty partial window once and waits for every in-flight submission | Callers need an immediate summary after early call failures, and shutdown must not lose or race the last window | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | No case flushes or stops with an empty window | Present |
229
+ | `MOD-023` | A summary submission failure is caught and logged without affecting SDK requests | Telemetry must never degrade the requests it observes | `src/metrics.js` | `test/unit/spec/network-telemetry.ts` | none | Present |
230
+ | `MOD-024` | Business events route by table, defaulting the table and the application type | Each ingestion table has a different schema, and the shim keeps callers unaware of it | `src/business-metrics.ts` | `test/unit/spec/business/business-metrics.ts` | none | Present |
231
+ | `MOD-025` | The automated user verdict is computed once per process | User agent parsing is relatively expensive and the verdict cannot change within a page | `src/automated-user.ts` | `test/unit/spec/automated-user.ts` | none | Present |
232
+
233
+ ## Design overview
234
+
235
+ The root module is the package's registration and transport layer. Its design is a deliberate split
236
+ between two façades that share one family of batchers.
237
+
238
+ `metrics` (`src/metrics.js`) is an Ampersand `WebexPlugin` with three `Batcher` children: one for the
239
+ `metrics` resource, one for `clientmetrics` and one for `clientmetrics-prelogin`. It builds the client
240
+ metric payload from host configuration and browser detection, and routes to the pre-login batcher
241
+ whenever a pre-login identifier is supplied. It also owns the network telemetry collector, because the
242
+ collector needs the SDK request events and a transport, and this plugin has both.
243
+
244
+ `newMetrics` (`src/new-metrics.ts`) owns no transport. It eagerly constructs the latency ledger, the
245
+ Call Analyzer builder and the permission enricher, and on the SDK `ready` event constructs the
246
+ pre-login backend, marks itself ready, replays the current delay setting and starts unhandled
247
+ exception telemetry. Behavioral, operational and business backends are created on first use after
248
+ readiness. The client, feature, behavioral, operational, business and pre-login submission methods
249
+ check readiness first, so a caller that fires too early gets a logged no-op rather than a malformed
250
+ event. `submitMQE` and `submitInternalEvent` are the exceptions: they record the latency timestamp and,
251
+ for `submitMQE`, delegate straight to the eagerly constructed Call Analyzer builder without a
252
+ readiness check.
253
+
254
+ The tagged-event classes share `src/generic-metrics.ts`, which builds a context block and browser
255
+ details and derives the device identifier from the device URL. Each class composes the metric name
256
+ its own way — behavioral events join product, agent, target and verb — and posts through its own
257
+ client-metrics batcher.
258
+
259
+ Network telemetry (`src/network-telemetry.ts`) is a closure-based collector with no class. Its state
260
+ is one window of counters plus a set of in-flight submissions. At each interval boundary it
261
+ serializes the window, replaces it with an empty one, and only then calls the submit callback. Event
262
+ flow:
263
+
264
+ ```text
265
+ webex.request()
266
+ -> SDK request interceptors
267
+ -> request:start(options)
268
+ -> request:success(options, response) or request:failure(options, reason)
269
+ -> metrics request outcome listeners
270
+ -> aggregate host, endpoint, response, and error counts
271
+ -> every configured interval, submit JS_SDK_NETWORK_REQUEST_SUMMARY
272
+ ```
273
+
274
+ ## Data flow and sequence coverage
275
+
276
+ Collection is in-process method calls and SDK events; submission is a batched HTTP POST through the
277
+ host request pipeline. Four operation groups have distinct actors or outcomes and are diagrammed
278
+ separately. Child-module flows are owned by their specs.
279
+
280
+ | Operation group | Entry and outcome | Diagram or evidence | Failure and recovery coverage |
281
+ | ----------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------ | ----------------------------------------------------------------------------------------------------- |
282
+ | Façade submission | A caller submits through `newMetrics`; guarded events are gated on readiness, then delegated (`submitMQE` is not gated) | First sequence diagram below | Not ready: logged no-op for guarded methods. Child failures are contained in the child |
283
+ | Client metric submission | A caller or child submits through `metrics`; the payload is built and batched | Second sequence diagram below | Missing name throws; missing pre-login identifier rejects; network errors re-enqueue with backoff |
284
+ | Network telemetry window | Request events accumulate; the interval, a flush or a stop submits the window | Third sequence diagram below | The interval submits even an empty window; flush and stop skip an empty window; submission failure logged and the window dropped |
285
+ | Plugin lifecycle | Construction, configuration change and logout | State machine section | Standalone construction skips telemetry; logout waits for in-flight submissions |
286
+
287
+ ```mermaid
288
+ sequenceDiagram
289
+ participant Caller as SDK plugin or client
290
+ participant New as newMetrics
291
+ participant Child as Child module or tagged backend
292
+ Caller->>New: submit event
293
+ alt SDK not ready and method is readiness-guarded
294
+ New->>New: log attempted submission
295
+ New-->>Caller: resolve without submitting
296
+ else ready
297
+ opt tagged backend not built yet
298
+ New->>New: construct backend lazily
299
+ end
300
+ opt client event
301
+ New->>New: record latency timestamp and enrich permissions
302
+ end
303
+ New->>Child: delegate submission
304
+ Child-->>Caller: submission promise
305
+ end
306
+ ```
307
+
308
+ ```mermaid
309
+ sequenceDiagram
310
+ participant Caller as Caller or child module
311
+ participant Metrics as metrics plugin
312
+ participant Batcher as Client metrics or pre-login batcher
313
+ participant Svc as Webex metrics service
314
+ Caller->>Metrics: submitClientMetrics(name, props, preLoginId)
315
+ alt no name
316
+ Metrics-->>Caller: throw
317
+ end
318
+ Metrics->>Metrics: build tags, fields, context and timestamp
319
+ alt pre-login identifier supplied
320
+ Metrics->>Batcher: save identifier, request on pre-login batcher
321
+ else
322
+ Metrics->>Batcher: request on client metrics batcher
323
+ end
324
+ Batcher->>Svc: POST metrics array
325
+ opt network or CORS error and retry enabled
326
+ Batcher->>Batcher: re-enqueue after a doubling delay
327
+ end
328
+ ```
329
+
330
+ ```mermaid
331
+ sequenceDiagram
332
+ participant Core as WebexCore request pipeline
333
+ participant Coll as Network telemetry collector
334
+ participant Metrics as metrics plugin
335
+ Core->>Coll: request:start / request:success / request:failure
336
+ alt metrics or unifiedTelemetry service
337
+ Coll-->>Core: ignore
338
+ else tracked request
339
+ Coll->>Coll: update summary, endpoint and error aggregates
340
+ end
341
+ Note over Coll: interval boundary, flush, or stop
342
+ alt flush or stop with an empty window
343
+ Coll-->>Metrics: nothing to submit
344
+ else interval boundary, or window has data
345
+ Coll->>Coll: serialize window, then reset to an empty window
346
+ Coll->>Metrics: submit JS_SDK_NETWORK_REQUEST_SUMMARY
347
+ opt submission rejects or throws
348
+ Coll->>Metrics: report failure, which logs a warning
349
+ end
350
+ end
351
+ opt stop
352
+ Coll->>Coll: clear interval and wait for every in-flight submission
353
+ end
354
+ ```
355
+
356
+ ## Class and component relationships
357
+
358
+ ```mermaid
359
+ classDiagram
360
+ class Index {
361
+ +registerInternalPlugin metrics
362
+ +registerInternalPlugin newMetrics
363
+ }
364
+ class Metrics {
365
+ +batcher
366
+ +clientMetricsBatcher
367
+ +clientMetricsPreloginBatcher
368
+ +networkTelemetry
369
+ +submitClientMetrics()
370
+ +flushNetworkTelemetry()
371
+ +stopNetworkTelemetry()
372
+ }
373
+ class NewMetrics {
374
+ +isReady
375
+ +callDiagnosticLatencies
376
+ +callDiagnosticMetrics
377
+ +submitClientEvent()
378
+ +submitBusinessEvent()
379
+ }
380
+ class NetworkTelemetryCollector
381
+ class MetricsBatcher
382
+ class ClientMetricsBatcher
383
+ class PreLoginMetricsBatcher
384
+ class ClientMetricsPreloginBatcher
385
+ class GenericMetrics
386
+ class BehavioralMetrics
387
+ class OperationalMetrics
388
+ class BusinessMetrics
389
+ class PreLoginMetrics
390
+ Index --> Metrics
391
+ Index --> NewMetrics
392
+ Metrics --> NetworkTelemetryCollector : creates when enabled
393
+ Metrics --> MetricsBatcher
394
+ Metrics --> ClientMetricsBatcher
395
+ Metrics --> ClientMetricsPreloginBatcher
396
+ ClientMetricsBatcher --|> MetricsBatcher
397
+ PreLoginMetricsBatcher --|> MetricsBatcher
398
+ ClientMetricsPreloginBatcher --|> PreLoginMetricsBatcher
399
+ NewMetrics --> BehavioralMetrics : lazy
400
+ NewMetrics --> OperationalMetrics : lazy
401
+ NewMetrics --> BusinessMetrics : lazy
402
+ NewMetrics --> PreLoginMetrics : on ready
403
+ BehavioralMetrics --|> GenericMetrics
404
+ OperationalMetrics --|> GenericMetrics
405
+ BusinessMetrics --|> GenericMetrics
406
+ PreLoginMetrics --|> GenericMetrics
407
+ GenericMetrics --> ClientMetricsBatcher
408
+ PreLoginMetrics --> PreLoginMetricsBatcher
409
+ ```
410
+
411
+ ## Use cases and flows
412
+
413
+ | Use case | Actor or caller | Primary steps and outcome | Failure or boundary behavior | Evidence |
414
+ | -------- | -------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
415
+ | `UC-001` | First party client | Enables network telemetry; SDK requests accumulate and one summary is submitted per interval | Telemetry uploads are excluded; an interval with no tracked request still submits a zeroed summary | `src/network-telemetry.ts`, `test/unit/spec/network-telemetry.ts` |
416
+ | `UC-002` | Meetings plugin | An early call failure occurs; it flushes network telemetry for an immediate summary | Disabled telemetry resolves immediately | `src/metrics.js`, `test/unit/spec/network-telemetry.ts` |
417
+ | `UC-003` | Host SDK | Logout stops network telemetry; the partial window is submitted once and in-flight submissions are awaited | Disabled telemetry resolves immediately | `src/index.ts`, `test/unit/spec/network-telemetry.ts` |
418
+ | `UC-004` | First party client | Submits a behavioral event after readiness; the backend is built lazily and the tagged event is batched | Before readiness the call is logged and resolves | `src/new-metrics.ts`, `test/unit/spec/new-metrics.ts` |
419
+ | `UC-005` | Guest before sign-in | Submits a client metric with a pre-login identifier; it is sent unauthenticated with the identifier header, then aliased after sign-in | No saved identifier rejects the batch | `src/metrics.js`, `test/unit/spec/metrics.js` |
420
+ | `UC-006` | Web client | Submits a business event to a specific table; the payload is shaped for that table's schema | Unknown or missing table falls back to the default shape | `src/business-metrics.ts`, `test/unit/spec/business/business-metrics.ts` |
421
+
422
+ <!-- Include if: the module calls another service or crosses an event or network boundary. [condition-id: module.crosses_service_boundaries] -->
423
+
424
+ ### Cross-boundary use-case flow
425
+
426
+ - Boundary and transport: inbound, the SDK-wide request lifecycle events; outbound, HTTPS POST to the
427
+ `metrics` service resources `metrics`, `clientmetrics` and `clientmetrics-prelogin`, plus the
428
+ `clientmetrics` alias request with the pre-login header.
429
+ - Ordering: batch items keep arrival order; no cross-batch ordering is promised. Network telemetry
430
+ windows are disjoint by construction because the window resets before submission.
431
+ - Compatibility: the summary metric and the client metric payload are wire contracts with ingestion;
432
+ additions are compatible, removals and renames are not.
433
+ - Timeout and retry: the batchers wait up to the configured service-discovery timeout, and re-enqueue
434
+ network failures with doubling delay up to the plateau. Network telemetry never retries a window.
435
+ - Recovery: a lost telemetry window is not replayed; the next window starts empty.
436
+
437
+ <!-- Include if: the module holds client-side state. [condition-id: module.holds_client_state] -->
438
+
439
+ ## Client state model
440
+
441
+ | State or slice | Owner | Initial state | Transition triggers | Reset or persistence boundary |
442
+ | --------------------------- | ---------------- | ------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------- |
443
+ | Network telemetry window | Collector closure | Empty aggregate | Tracked request events | Replaced at every interval, flush and stop |
444
+ | In-flight submissions | Collector closure | Empty set | A window submission starts or settles | Drained by stop |
445
+ | Collector reference | `metrics` | Undefined | Created once when enabled | Stopped on logout; not recreated |
446
+ | Readiness flag | `newMetrics` | False | SDK `ready` event | Instance lifetime |
447
+ | Delay flags and overrides | `newMetrics` | False, empty | The two delay setters | Disabling delay flushes the child's queue |
448
+ | Lazy tagged backends | `newMetrics` | Undefined | First use after readiness | Instance lifetime |
449
+ | Batcher queues and backoff | Each batcher | Empty | Enqueue, accept, re-enqueue | Per-item delay cleared on acceptance |
450
+ | Saved pre-login identifier | Pre-login batchers | Undefined | Saved on each pre-login submission | Overwritten by the next submission |
451
+ | Device identifier memo | `GenericMetrics` | Empty string | First successful parse of the device URL | Instance lifetime |
452
+
453
+ <!-- Include if: the module enforces domain rules or entity invariants. [condition-id: module.enforces_domain_rules] -->
454
+
455
+ ## Business rules and invariants
456
+
457
+ Counter mapping, current in `src/network-telemetry.ts`:
458
+
459
+ | Event or outcome | Summary and endpoint counters |
460
+ | --------------------------------------------------- | --------------------------------------------------------------------------- |
461
+ | `request:start` | `totalSendRequest`, `countSendRequest` |
462
+ | `request:success` | `totalRecvdResponse`, `countRecvdResponse` |
463
+ | `request:failure` without an HTTP response | `totalFailedRequest`, `countFailedRequest` |
464
+ | `request:failure` with an HTTP status (`4xx`/`5xx`) | Received-response counters plus `totalFailedResponse`/`countFailedResponse` |
465
+
466
+ Failure classification, evaluated in this order:
467
+
468
+ | Condition | `errorType` |
469
+ | --------------------------------------------------------------------------------------------------------- | --------------- |
470
+ | Error name is `AbortError` | `aborted` |
471
+ | Status is `408` or `504`, timeout appears in the error details, or the configured request timeout elapsed | `timeout` |
472
+ | Status is `429` | `rate_limited` |
473
+ | Status is `5xx` | `server_error` |
474
+ | Status is `4xx` | `client_error` |
475
+ | No response status is available, or the error name contains `network` | `network_error` |
476
+ | Any other SDK request failure | `request_error` |
477
+
478
+ Telemetry request exclusion: requests to the `metrics` and `unifiedTelemetry` services are excluded
479
+ from both success and failure collection. The exclusion also covers the legacy `api: 'metrics'`
480
+ request option. This prevents a telemetry upload from affecting its own summary. The exclusion is
481
+ centralized in the collector; telemetry request call sites do not require special flags.
482
+
483
+ Endpoint normalization: the `endPoint` value is derived from `options.resource`, or from the URI path
484
+ for a direct-URI request:
485
+
486
+ - Query parameters and fragments are removed.
487
+ - Numeric, percent-encoded, uppercase, and otherwise non-route-looking segments are replaced with `:id`.
488
+
489
+ For example, `rooms/Y2lzY29zcGFyazovL3VzL1JPT00/messages?personId=secret` becomes
490
+ `rooms/:id/messages`.
491
+
492
+ | ID | Invariant | WHY | Enforcement source | Test evidence |
493
+ | --------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------- | ---------------------------- | ------------------------------------- |
494
+ | `INV-001` | Requests to the `metrics` and `unifiedTelemetry` services never enter a telemetry window | Without it every window would count the SDK's own summary and client-metric uploads | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` |
495
+ | `INV-002` | A status of zero means no HTTP response; only statuses 100 to 599 count as responses | Separates transport failure from server failure in every counter | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` |
496
+ | `INV-003` | Every failure has exactly one error type | Bounded aggregation vocabulary | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` |
497
+ | `INV-004` | An error aggregate holds at most ten unique tracking identifiers | Bounds payload size during failure storms | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` |
498
+ | `INV-005` | Endpoints contain no query data, fragment or non-route-looking segment | Bounds cardinality and keeps identifiers out of telemetry | `src/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` |
499
+ | `INV-006` | Flush and stop never submit an empty window; the periodic interval is the only path that does | Flush serves early-failure and shutdown delivery of collected data, so an empty flush would carry nothing | `src/network-telemetry.ts` | none found |
500
+ | `INV-007` | Non-production builds tag metrics with a test environment | Keeps non-production data out of production metrics | `src/batcher.js` | `test/unit/spec/batcher.js` |
501
+
502
+ <!-- Include if: the module is concurrent, asynchronous, reactive, or event-driven. [condition-id: module.is_concurrent_async] -->
503
+
504
+ ## Concurrency and reactive flow
505
+
506
+ - Execution model: SDK event listeners, one `safeSetInterval` per collector, and the `Batcher` queue
507
+ with `safeSetTimeout` backoff.
508
+ - Ordering guarantees: the window reset happens synchronously before the submit callback, so an event
509
+ arriving while a submission is pending always lands in the next window.
510
+ - Idempotency and retry: windows are never retried. Batch items are re-enqueued on network errors with
511
+ per-item delay starting at one second and doubling below the plateau.
512
+ - Shared-state protection: single-threaded; every mutation is synchronous. The in-flight set lets stop
513
+ wait for submissions whose window was already reset.
514
+ - Flush and shutdown: the metrics plugin exposes `flushNetworkTelemetry()` for callers that need to
515
+ submit the current non-empty window immediately, such as after an early call failure. When the
516
+ metrics plugin is stopped, any non-empty partial window is submitted once before the collector is
517
+ disposed. Empty windows are not submitted during shutdown, and the shutdown hook waits for the
518
+ submission attempt to settle.
519
+ - Blocking restrictions: no request listener awaits anything, and summary submission failures are
520
+ caught inside the collector, so telemetry cannot slow or fail an SDK request.
521
+
522
+ <!-- Include if: the module has non-trivial state transitions. [condition-id: module.stateful_transitions] -->
523
+
524
+ ## State machine
525
+
526
+ ```mermaid
527
+ stateDiagram-v2
528
+ state "newMetrics" as NM {
529
+ [*] --> NotReady
530
+ NotReady --> NotReady: guarded submission logged and resolved; MQE delegated
531
+ NotReady --> Ready: SDK ready, pre-login backend built, delay replayed, exception telemetry started
532
+ Ready --> Ready: submissions delegated, tagged backends built lazily
533
+ }
534
+ state "network telemetry" as NT {
535
+ [*] --> Disabled: standalone, or enable flag not true
536
+ Disabled --> Collecting: enable flag true at construction or first config change
537
+ Collecting --> Collecting: interval submits and resets the window, even when empty; flush does so only when non-empty
538
+ Collecting --> Stopped: logout stops, submits partial window, waits for in-flight
539
+ Stopped --> [*]
540
+ }
541
+ ```
542
+
543
+ Rejected transitions: a collector is never created twice; `Stopped` does not return to `Collecting`.
544
+
545
+ <!-- Include if: callers depend on a wire protocol or serialized format. [condition-id: module.exposes_wire_protocol] -->
546
+
547
+ ## Protocol and wire format
548
+
549
+ | Message or frame | Version | Producer or serializer | Consumer or parser | Compatibility and ordering rule |
550
+ | ----------------------------------- | ------- | ----------------------------- | ------------------------- | ------------------------------------------------------------------------------------------- |
551
+ | Client metric payload | n/a | `src/metrics.js` | Webex metrics ingestion | Additive only; tags and fields are indexed by ingestion |
552
+ | `JS_SDK_NETWORK_REQUEST_SUMMARY` | n/a | `src/network-telemetry.ts` | Webex metrics ingestion | Operational type; the summary is duplicated in `fields` for indexing |
553
+ | Tagged event | n/a | `src/generic-metrics.ts` | Webex metrics ingestion | Metric name, type list, tags, context and timestamp |
554
+ | Business event | n/a | `src/business-metrics.ts` | Webex business tables | Shape depends on the table; the business-metrics table merges context into the value |
555
+ | Metrics batch body | n/a | `src/batcher.js` | Webex metrics service | A `metrics` array; each item gets app type, environment, time, version and post time |
556
+
557
+ One operational client metric named `JS_SDK_NETWORK_REQUEST_SUMMARY` is submitted at the configured
558
+ interval. Its `eventPayload` shape is declared by the `NetworkTelemetry` type in
559
+ [`src/network-telemetry.ts`](../network-telemetry.ts), which is linked here rather than reproduced: a four-counter
560
+ `metricsSummary`, per host-and-endpoint `metrics`, grouped `errorMetrics`, and an
561
+ `errorMetricsSummary`. The outer client metric carries `metricName`
562
+ `JS_SDK_NETWORK_REQUEST_SUMMARY`, `type: 'operational'`, `fields` duplicating `metricsSummary` for
563
+ indexing, and that `eventPayload`.
564
+
565
+ The `metrics` array is grouped by `host` and `endPoint`. For service-based SDK requests, `host` is the
566
+ normalized logical service name. Direct-URI requests use the URI host. `endPoint` is a sanitized
567
+ resource or URI path. `averageNetworkDurationMs` is the average measured network duration of the
568
+ group's requests that carry valid timing, while `maxNetworkDurationP90Ms` and
569
+ `maxNetworkDurationP99Ms` are the maximum durations at the nearest-rank 90th and 99th percentiles of
570
+ those samples; a group with no valid timing sample reports zero for all three. All values are in
571
+ milliseconds.
572
+
573
+ The `errorMetrics` array groups identical failures by host, endpoint, status code, error code,
574
+ method, error type, and error message. `countError` records the number of occurrences, while
575
+ `trackingIds` retains at most ten unique identifiers. `errorMetricsSummary` counts errors by status
576
+ code; status `0` means no HTTP response was available.
577
+
578
+ The SDK adds application metadata to the outer client metric when it wraps this `eventPayload`, as it
579
+ does for every client metric built in `src/metrics.js`:
580
+
581
+ | Property | Source |
582
+ | -------------------- | --------------------------------------------------------------------------- |
583
+ | `tags.app_name` | webex.config.appName, or unknown |
584
+ | `tags.app_version` | webex.config.appVersion, or unknown |
585
+ | `tags.app_url` | Browser origin without path/query data, hostname fallback, or non-browser |
586
+ | `fields.sdk_version` | webex.version |
587
+
588
+ <!-- Include if: the module returns or raises errors callers must handle. [condition-id: module.returns_caller_errors] -->
589
+
590
+ ## Caller-visible failure modes
591
+
592
+ | Condition | Signal or result | Caller behavior | Retry or recovery | Evidence |
593
+ | ------------------------------------------- | ---------------------------------------------------- | -------------------------------------------- | ---------------------------------------------- | ----------------------------------- |
594
+ | Client metric submitted without a name | Synchronous `Error` | Supply a metric name | None | `src/metrics.js` |
595
+ | Pre-login submission with no saved identifier | Rejected promise | Pass a pre-login identifier | None | `src/prelogin-metrics-batcher.ts` |
596
+ | Alias request fails | Rejected promise, logged | Caller decides whether to retry | None built in | `src/new-metrics.ts` |
597
+ | Batch transport fails with a network error | Item re-enqueued; the promise stays pending | None required | Doubling delay up to the plateau | `src/batcher.js` |
598
+ | Batch transport fails otherwise | Rejected promise | Callers normally ignore it | None | `src/batcher.js` |
599
+ | Guarded `newMetrics` submission used before ready | Logged; resolved promise with nothing submitted (`submitMQE` is not guarded and delegates immediately) | Submit after the SDK is ready | Use delay mode to defer instead | `src/new-metrics.ts` |
600
+
601
+ ## Pitfalls and constraints
602
+
603
+ - Endpoint normalization is heuristic. A short lowercase alphabetic identifier can look like a static
604
+ route segment and survive. Do not place sensitive values directly in a resource path; use normal SDK
605
+ resource identifiers and avoid embedding credentials or personal data in route names. Error messages are
606
+ included because they are part of the error aggregate schema, so callers must not put credentials or
607
+ personal data in thrown error messages.
608
+ - An enabled collector submits a summary every interval even when no tracked request occurred, so an
609
+ idle SDK instance emits one zeroed `JS_SDK_NETWORK_REQUEST_SUMMARY` per interval. Only flush and stop
610
+ skip an empty window.
611
+ - The host of a service request is the logical service name, not the resolved URL host, because the
612
+ start event can fire before the service interceptor resolves a URL.
613
+ - Do not collapse the deferred `change:config` initialization in `src/metrics.js` into a direct call:
614
+ the SDK populates configuration after initializing children.
615
+ - `src/generic-metrics.ts` imports the OS-name helper from `src/metrics.js`; do not add the reverse
616
+ import.
617
+ - The `test` script in `package.json` is broken because it chains scripts this package does not
618
+ define; use `test:unit` and `test:style`.
619
+
620
+ <!-- Include if: the module has conventions beyond repository-wide rules. [condition-id: module.module_specific_conventions] -->
621
+
622
+ ## Module-specific rules
623
+
624
+ - Do: keep Ampersand-based plugins and batchers (`src/metrics.js`, `src/batcher.js`,
625
+ `src/client-metrics-batcher.js`, `src/config.js`) in JavaScript and in-style; write new code in
626
+ TypeScript.
627
+ - Do: add new telemetry transports to the self-exclusion rule in `src/network-telemetry.ts`, not at
628
+ the call site.
629
+ - Do: catch and log every telemetry submission failure.
630
+ - Do not: gate a `newMetrics` submission on anything other than the readiness flag at the top of the
631
+ method; the early-return log line is how premature callers are diagnosed.
632
+ - Do not: submit a network telemetry window without resetting it first.
633
+
634
+ <!-- Include if: the module is published or consumed as a package. [condition-id: module.published_package] -->
635
+
636
+ ## Export stability
637
+
638
+ | Export or entry point | Consumer | Stability | Versioning and deprecation rule | Declaration or API report |
639
+ | --------------------------------------------- | --------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------- |
640
+ | Default export, `getOSNameInternal` | Workspace siblings | Internal plugin; additive | Internal plugin: not strictly semver; coordinate removals with every dependent workspace package | `src/index.ts` |
641
+ | `NewMetrics`, `BehavioralMetrics`, `OperationalMetrics`, `BusinessMetrics`, `PreLoginMetrics` | Workspace siblings | Internal; additive | Same | `src/index.ts` |
642
+ | `config`, `Utils`, `AutomatedUserUtils`, `isAutomatedUser`, `isAutomatedUserAgent` | Workspace siblings | Internal; additive | Same | `src/index.ts` |
643
+ | Exported types | Workspace siblings | Internal; derived from the event dictionary | Type changes follow the pinned event dictionary version | `src/metrics.types.ts` |
644
+ | `main` and `types` entry points | npm consumers | Published | Must keep resolving to `dist/index.js` and `dist/types/index.d.ts` | `package.json` |
645
+
646
+ <!-- Include if: the module is embedded in a host application. [condition-id: module.embedded_in_host] -->
647
+
648
+ ## Host integration and theming
649
+
650
+ - Mount or entry contract: importing `src/index.ts` registers `metrics` with the shared config and an
651
+ `onBeforeLogout` hook that stops network telemetry, and registers `newMetrics` with the same config.
652
+ - Required providers, peers, or host versions: a `WebexCore` instance from `@webex/webex-core`; a
653
+ registered device for tagged events; host configuration under the `metrics` namespace; and, for
654
+ Call Analyzer origin, meetings configuration supplying client types.
655
+ - Theme and design-token contract: not applicable — the package renders nothing.
656
+ - Accessibility and lifecycle obligations: the host must import the package before constructing the
657
+ SDK instance; logout awaits the final telemetry submission.
658
+
659
+ <!-- Include if: the module has a non-obvious design trade-off consumers or maintainers must preserve. [condition-id: module.has_design_tradeoff] -->
660
+
661
+ ## Key design trade-off
662
+
663
+ | Chosen trade-off | Preserved invariant or benefit | Cost or limitation | Decision evidence |
664
+ | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ | -------------------------- |
665
+ | Reset the telemetry window at the reporting boundary before the asynchronous submission | Requests received while a summary is being sent belong to the next window, and the transport's own requests cannot mutate a completed window | A telemetry submission failure is caught and logged, and that window is dropped; nothing is retried | `src/network-telemetry.ts` |
666
+ | Reuse `submitClientMetrics` batching and transport for summaries | Summaries inherit batching and network-error backoff with no new transport; they never pass a pre-login identifier, so they always use the client-metrics batcher | Summaries are subject to the same batcher delays as all client metrics | `src/metrics.js` |
667
+ | Listen to SDK-wide request events instead of wrapping `webex.request()` | Individual plugins need no changes, and every request through the pipeline is observed | Traffic outside the SDK pipeline is invisible, and the events have no shared typed contract | `src/network-telemetry.ts` |
668
+ | Keep two façades rather than merging them | Existing consumers of `metrics` keep working while new families land on `newMetrics` | Two entry points with overlapping responsibility, and the pre-login path lives only on the older one | `src/new-metrics.ts` |
669
+
670
+ ## Verification
671
+
672
+ | Requirement or invariant | Test level | Positive evidence | Negative or boundary evidence | Gap |
673
+ | ------------------------ | ---------- | ---------------------------------------------------- | ---------------------------------------------------- | -------------------------------------------------------------------------------- |
674
+ | `MOD-001` | Unit | `test/unit/spec/network-telemetry.ts` | none found | The `onBeforeLogout` registration is not exercised |
675
+ | `MOD-002` | Unit | `test/unit/spec/new-metrics.ts` | `test/unit/spec/new-metrics.ts` | none |
676
+ | `MOD-003` | Unit | `test/unit/spec/new-metrics.ts` | none found | none |
677
+ | `MOD-004` | Unit | `test/unit/spec/behavioral/behavioral-metrics.ts` | `test/unit/spec/behavioral/behavioral-metrics.ts` | none |
678
+ | `MOD-005` | Unit | `test/unit/spec/new-metrics.ts` | `test/unit/spec/new-metrics.ts` | none |
679
+ | `MOD-006` | Unit | `test/unit/spec/new-metrics.ts` | none found | none |
680
+ | `MOD-007` | Unit | `test/unit/spec/metrics.js` | `test/unit/spec/metrics.js` | none |
681
+ | `MOD-008` | Unit | `test/unit/spec/metrics.js` | `test/unit/spec/metrics.js` | none |
682
+ | `MOD-009` | Unit | `test/unit/spec/metrics.js` | `test/unit/spec/client-metrics-prelogin-batcher.ts` | none |
683
+ | `MOD-010` | Unit | `test/unit/spec/prelogin-metrics-batcher.ts` | `test/unit/spec/prelogin-metrics-batcher.ts` | none |
684
+ | `MOD-011` / `INV-007` | Unit | `test/unit/spec/batcher.js` | `test/unit/spec/batcher.js` | none |
685
+ | `MOD-012` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
686
+ | `MOD-013` | Unit | none found | none found | Deferred `change:config` initialization and the standalone early return are untested |
687
+ | `MOD-014` / `INV-002` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
688
+ | `MOD-015` / `INV-003` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | The `request_error` fallback has no case |
689
+ | `MOD-016` / `INV-004` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
690
+ | `MOD-017` / `INV-001` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
691
+ | `MOD-018` / `INV-005` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
692
+ | `MOD-019` | Unit | `test/unit/spec/network-telemetry.ts` | none found | none |
693
+ | `MOD-020` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | Requests without timing metadata report zero; no case feeds non-finite or inverted timing pairs |
694
+ | `MOD-021` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
695
+ | `INV-006` | Unit | none found | none found | No case flushes or stops an empty window; the skip in `flush()` is unverified |
696
+ | `MOD-022` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | No case flushes or stops with an empty window |
697
+ | `MOD-023` | Unit | `test/unit/spec/network-telemetry.ts` | `test/unit/spec/network-telemetry.ts` | none |
698
+ | `MOD-024` | Unit | `test/unit/spec/business/business-metrics.ts` | `test/unit/spec/business/business-metrics.ts` | none |
699
+ | `MOD-025` | Unit | `test/unit/spec/automated-user.ts` | `test/unit/spec/automated-user.ts` | none |
700
+
701
+ Record coverage gaps explicitly and link follow-up work. A module specification
702
+ is complete only when its public surface, invariants, failure modes, and test
703
+ evidence agree with the implementation.