@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,536 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Module Spec
|
|
3
|
+
title: 'Unhandled exception telemetry specification'
|
|
4
|
+
description: Responsibilities, boundaries, design, invariants, and verification for the browser unhandled exception 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
|
+
# Unhandled exception telemetry
|
|
17
|
+
|
|
18
|
+
This source-local document at `src/unhandled-exception-telemetry/docs/README.md` owns the stable
|
|
19
|
+
specification for **the browser unhandled exception reporter**. Ground every claim in repository
|
|
20
|
+
evidence and link to the
|
|
21
|
+
[repository architecture](../../../docs/architecture.md) instead of repeating broader facts.
|
|
22
|
+
|
|
23
|
+
Related context: [documentation index](../../../docs/index.md) ·
|
|
24
|
+
[documentation agent instructions](../../../AGENTS.md)
|
|
25
|
+
|
|
26
|
+
## Metadata
|
|
27
|
+
|
|
28
|
+
| Field | Value |
|
|
29
|
+
| ------------- | ------------------------------------------------- |
|
|
30
|
+
| Owner | Cisco Webex JS SDK — telemetry |
|
|
31
|
+
| Source path | `src/unhandled-exception-telemetry/` |
|
|
32
|
+
| Resource kind | module |
|
|
33
|
+
| Status | Active |
|
|
34
|
+
| Last verified | 2026-10-05 at `d6202dd73d` |
|
|
35
|
+
| Module id | `unhandled-exception-telemetry` |
|
|
36
|
+
| Parent spec | [`src/docs/README.md`](../../docs/README.md) |
|
|
37
|
+
| Doc kind | Module spec |
|
|
38
|
+
| Coverage score | Pending coverage assessment |
|
|
39
|
+
| 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) |
|
|
40
|
+
|
|
41
|
+
## Applicability
|
|
42
|
+
|
|
43
|
+
Sections preceded by an `Include if` comment are conditional. Retain a
|
|
44
|
+
conditional section only when source evidence or a confirmed developer answer
|
|
45
|
+
satisfies its condition.
|
|
46
|
+
|
|
47
|
+
| Condition ID | Status | Evidence or reason | Owned section |
|
|
48
|
+
| ------------------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
|
|
49
|
+
| `module.has_tiers` | N/A | The repository defines no operational or review tier policy for modules | Tier |
|
|
50
|
+
| `module.has_ui` | N/A | The module renders nothing; it listens to window events | UI use-case flow |
|
|
51
|
+
| `module.crosses_service_boundaries` | Applicable | `src/unhandled-exception-telemetry/index.ts` submits through the host plugin's client-metrics transport | Cross-boundary use-case flow |
|
|
52
|
+
| `module.holds_client_state` | Applicable | `src/unhandled-exception-telemetry/index.ts` holds the pending-event map, the flush timer and the module-level active reporter | Client state model |
|
|
53
|
+
| `module.enforces_domain_rules` | Applicable | Redaction, truncation and metadata validation in `src/unhandled-exception-telemetry/utils.ts` | Business rules and invariants |
|
|
54
|
+
| `module.is_concurrent_async` | Applicable | A self-rescheduling flush timer and capture-phase window listeners in `src/unhandled-exception-telemetry/index.ts` | Concurrency and reactive flow |
|
|
55
|
+
| `module.owns_persistence` | N/A | Events are never persisted; the pending map is in-memory and discarded on teardown | Data, schema, and migration |
|
|
56
|
+
| `module.stateful_transitions` | Applicable | An event is pending, merged, submitted or discarded depending on its deduplication window | State machine |
|
|
57
|
+
| `module.exposes_wire_protocol` | Applicable | The submitted event shape is declared in `src/unhandled-exception-telemetry/index.ts` | Protocol and wire format |
|
|
58
|
+
| `module.ui_multi_screen` | N/A | No user interface | UI flow |
|
|
59
|
+
| `module.large_data_model` | N/A | One event type with a flat error detail object | Data model |
|
|
60
|
+
| `module.returns_caller_errors` | N/A | The start function returns void and every internal failure is logged; nothing is thrown or rejected to a caller | Caller-visible failure modes |
|
|
61
|
+
| `module.module_specific_conventions` | Applicable | Every capture and submit path is individually wrapped so telemetry can never raise | Module-specific rules |
|
|
62
|
+
| `module.published_package` | N/A | Not re-exported from `src/index.ts`; started only by `src/new-metrics.ts` | Export stability |
|
|
63
|
+
| `module.embedded_in_host` | N/A | Not a registered plugin; the parent module owns the registration contract | Host integration and theming |
|
|
64
|
+
| `module.has_design_tradeoff` | Applicable | A single in-memory deduplication window with no persistence or retry, versus a durable reporter | Key design trade-off |
|
|
65
|
+
| `module.has_submodules` | N/A | Derived from the manifest module tree: this module has no child modules | Sub-modules |
|
|
66
|
+
|
|
67
|
+
## Evidence register
|
|
68
|
+
|
|
69
|
+
| Evidence | What it establishes |
|
|
70
|
+
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
71
|
+
| `src/unhandled-exception-telemetry/index.ts` | The start function, the reporter class, the capture paths, deduplication, metadata handling, submission and teardown |
|
|
72
|
+
| `src/unhandled-exception-telemetry/utils.ts` | Truncation, URL redaction, resource-URL sanitization, the fingerprint hash and rejection-reason stringification |
|
|
73
|
+
| `src/config.js` | That the feature is disabled by default |
|
|
74
|
+
| `src/new-metrics.ts` | The single call site and that it runs only after the SDK emits its ready event |
|
|
75
|
+
| `src/metrics.js` | The client-metrics transport this module reuses, including its pre-login parameter |
|
|
76
|
+
| `test/unit/spec/unhandled-exception-telemetry.ts` | Behavioral verification of deduplication, redaction, truncation, metadata, the pre-login route, teardown and the disabled default |
|
|
77
|
+
| `test/unit/spec/unhandled-exception-telemetry/utils.ts` | Unit verification of the redaction and sanitization helpers in isolation |
|
|
78
|
+
| Reconciled owner documentation | Owner-authored scope, configuration and privacy guidance for this feature, reconciled by meaning into this spec; its configuration code block is replaced by a native-declaration link |
|
|
79
|
+
|
|
80
|
+
## Purpose and boundary
|
|
81
|
+
|
|
82
|
+
- Responsibility: reporting browser-level failures the application did not handle — uncaught errors,
|
|
83
|
+
unhandled promise rejections and resource load failures — as a sanitized, deduplicated telemetry
|
|
84
|
+
event.
|
|
85
|
+
- In scope: installing and removing capture-phase window listeners; extracting details from each
|
|
86
|
+
event kind; redacting and truncating everything derived from the environment; deduplicating
|
|
87
|
+
matching failures inside a one-second window into a single event with an occurrence count;
|
|
88
|
+
collecting optional application metadata; submitting through the host plugin's client-metrics
|
|
89
|
+
transport, pre-login when the SDK cannot yet authorize.
|
|
90
|
+
- Capture and sanitization summary: the reporter captures uncaught errors, unhandled promise
|
|
91
|
+
rejections, and resource load failures. Matching failures captured in the same one-second in-memory
|
|
92
|
+
window are submitted once with an `occurrenceCount`. Non-HTTP(S) URLs are redacted; URL
|
|
93
|
+
credentials, query parameters, and fragments are stripped; and error names, messages, and stacks
|
|
94
|
+
are truncated before submission.
|
|
95
|
+
- Out of scope, by design: unhandled exception telemetry is currently supported only in browser
|
|
96
|
+
environments. It starts after the Webex SDK emits `ready`. It does not install a standalone
|
|
97
|
+
collector, capture errors before SDK initialization, persist events, or retry failed telemetry
|
|
98
|
+
submissions; pending events live only in memory.
|
|
99
|
+
- Consumers: `src/new-metrics.ts` only, which starts the reporter once the SDK emits its ready
|
|
100
|
+
event. The module is not re-exported from `src/index.ts`.
|
|
101
|
+
|
|
102
|
+
## Structure and key files
|
|
103
|
+
|
|
104
|
+
| Path | Responsibility |
|
|
105
|
+
| ------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
106
|
+
| `src/unhandled-exception-telemetry/index.ts` | The start function, the reporter class, the metric name constant and every bound: the deduplication window and the four truncation caps |
|
|
107
|
+
| `src/unhandled-exception-telemetry/utils.ts` | Pure sanitization helpers: truncation, URL detail removal, resource-URL scheme filtering, the deduplication hash and reason stringification |
|
|
108
|
+
|
|
109
|
+
## Public surface
|
|
110
|
+
|
|
111
|
+
| Surface | Consumer | Compatibility commitment | Source |
|
|
112
|
+
| ------------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
|
|
113
|
+
| `startUnhandledExceptionTelemetry` | `src/new-metrics.ts` | Idempotent per SDK instance: a later call supersedes the previous reporter. Returns void and never throws | `src/unhandled-exception-telemetry/index.ts` |
|
|
114
|
+
| `UNHANDLED_EXCEPTION_METRIC_NAME` | Tests | The submitted metric name. Changing it is breaking for ingestion and dashboards | `src/unhandled-exception-telemetry/index.ts` |
|
|
115
|
+
| `UnhandledExceptionEvent` type | Tests, and ingestion consumers | The submitted event shape, versioned by its own schema-version field | `src/unhandled-exception-telemetry/index.ts` |
|
|
116
|
+
| Configuration under the metrics namespace | First party clients | Two keys: an enable flag and an optional synchronous metadata provider. Disabled by default | `src/config.js` |
|
|
117
|
+
|
|
118
|
+
Telemetry is disabled by default. Enable it with `metrics.unhandledExceptionTelemetry.enabled: true`.
|
|
119
|
+
Applications may provide a synchronous `getMetadata` callback in the same configuration object. It
|
|
120
|
+
must return an object whose fields can include application context such as `orgId` and
|
|
121
|
+
`dataCenter`. Metadata must not contain personally identifiable information or credentials. A
|
|
122
|
+
typical configuration is `metrics.unhandledExceptionTelemetry.enabled: true` together with a
|
|
123
|
+
synchronous `getMetadata: () => ({orgId, dataCenter})` provider.
|
|
124
|
+
|
|
125
|
+
The enable flag and the metadata provider are declared, with their exact names and types, in the
|
|
126
|
+
structural configuration type in `src/unhandled-exception-telemetry/index.ts`, and their defaults in
|
|
127
|
+
`src/config.js`. The provider is called with no arguments and must return an object synchronously;
|
|
128
|
+
a return value of `undefined` attaches nothing, while a non-object, oversized or throwing provider is
|
|
129
|
+
recorded as a metadata capture status rather than submitted. These declarations are the only contract
|
|
130
|
+
surface for the configuration; no separate code block restates them.
|
|
131
|
+
|
|
132
|
+
## Dependencies
|
|
133
|
+
|
|
134
|
+
| Dependency | Why it is required | Failure behavior |
|
|
135
|
+
| ------------------------------------------------------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
136
|
+
| `src/metrics.js` client-metrics transport | The only submission path; it also accepts the pre-login identifier | An unavailable transport is logged once per event and the event is dropped |
|
|
137
|
+
| `src/unhandled-exception-telemetry/utils.ts` | All redaction, truncation and fingerprinting | Pure functions; an unexpected input returns undefined rather than throwing |
|
|
138
|
+
| `@webex/common-timers` | Schedules the deduplication flush so the timer survives SDK clock handling | None |
|
|
139
|
+
| `uuid` | Generates the per-event identifier and the per-reporter pre-login identifier | None |
|
|
140
|
+
| Browser window | Supplies the error and rejection events | Absent in Node.js, so the reporter is not started and any active reporter is stopped and its pending events discarded |
|
|
141
|
+
| Host SDK logger | Reports telemetry failures without raising them | A throwing logger is itself caught, so logging cannot turn a telemetry failure into an application failure |
|
|
142
|
+
|
|
143
|
+
## Requirements
|
|
144
|
+
|
|
145
|
+
| ID | WHAT | WHY | Source evidence | Test or example evidence | Assumptions or gaps | Confidence |
|
|
146
|
+
| --------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------- | ------------------- | ---------- |
|
|
147
|
+
| `MOD-001` | The feature is disabled by default and installs no listeners unless the enable flag is set to exactly `true` | Capturing every uncaught error in a host application is intrusive, so it must be an explicit opt-in rather than a default | `src/config.js` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
148
|
+
| `MOD-002` | Listeners are installed only in a browser that exposes an event-listener API, and only after the SDK emits its ready event | Before readiness there is no transport to submit through, and outside a browser there are no window events to capture | `src/new-metrics.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
149
|
+
| `MOD-003` | Uncaught errors, unhandled promise rejections and resource load failures are all captured, each with its own failure kind | The three arrive as different browser events with different shapes, and a consumer needs to tell them apart | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
150
|
+
| `MOD-004` | Listeners are registered in the capture phase | Resource load failures do not bubble, so a bubble-phase listener would miss them entirely | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
151
|
+
| `MOD-005` | Failures that match on kind, name, message, stack, source coordinates and resource URL within one second are submitted once with an occurrence count | A loop or a repeated failing request can raise the same error hundreds of times per second; one event with a count preserves the signal without the volume | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
152
|
+
| `MOD-006` | Each distinct failure gets its own full deduplication window rather than sharing one global window | Otherwise an error arriving late in another error's window would be submitted immediately and its own repeats would not be merged | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
153
|
+
| `MOD-007` | When a throttled timer leaves an expired event pending, that event is submitted before its fingerprint is reused for a new window | Otherwise a background tab's throttling would silently merge two separate bursts, or discard the earlier one | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
154
|
+
| `MOD-008` | URL credentials, query parameters and fragments are stripped from filenames, messages, stacks and resource URLs | Those positions routinely carry tokens and personal identifiers that must not reach telemetry | `src/unhandled-exception-telemetry/utils.ts` | `test/unit/spec/unhandled-exception-telemetry/utils.ts` | none | Present |
|
|
155
|
+
| `MOD-009` | Non-HTTP and non-HTTPS URLs are replaced wholesale in text, and a resource failure from such a URL is not reported at all | Schemes such as data and blob embed their payload in the URL, so partial redaction cannot make them safe | `src/unhandled-exception-telemetry/utils.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
156
|
+
| `MOD-010` | Error names, messages, stacks and resource URLs are truncated to fixed caps before submission | An unbounded message or stack would let one failure dominate a telemetry payload | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
157
|
+
| `MOD-011` | The selected resource candidate is preferred over the fallback source when a resource reports both | For a responsive image the browser's chosen candidate is the URL that actually failed; the fallback would misreport which asset is broken | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
158
|
+
| `MOD-012` | Application metadata is collected only from a configured synchronous provider, and is submitted only when it serializes to a plain object within the size cap | Metadata is caller-controlled, so a wrong type, an oversized value or a throwing provider must degrade to a recorded status rather than a dropped event | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
159
|
+
| `MOD-013` | Metadata must not contain personally identifiable information or credentials | The provider's output is submitted verbatim; the module validates its shape and size but cannot inspect its meaning | `src/unhandled-exception-telemetry/index.ts` | none found | Not machine-enforceable; it is a caller obligation on the configured provider, whose validated output is submitted verbatim | Approved unknown |
|
|
160
|
+
| `MOD-014` | A submission is routed through the pre-login path whenever the SDK cannot yet authorize, using one identifier per reporter | An error during sign-in is exactly the kind of failure worth reporting, and it must still be attributable to one browser session | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
161
|
+
| `MOD-015` | Starting a reporter supersedes any active one: listeners are removed, and pending events are flushed when the new configuration still enables telemetry or discarded when it does not | A second SDK instance must not leave duplicate listeners behind, and disabling the feature must not then submit what was captured while it was on | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
162
|
+
| `MOD-016` | No capture, metadata, submission or logging failure escapes the module | The listeners run inside the application's own error path; an exception here would turn one failure into two | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none | Present |
|
|
163
|
+
| `MOD-017` | A rejection reason whose `message`, `name` or `stack` property access throws is contained: the failure is logged, nothing is submitted and nothing propagates. A reason that only fails JSON serialization is still reported, falling back to `String(reason)` and then to a fixed unserializable-reason message | A rejection can carry a proxy or a throwing getter; the reporter runs inside the application's error path, so it must never throw there, and it cannot build a trustworthy report from a reason it cannot read | `src/unhandled-exception-telemetry/index.ts`, `src/unhandled-exception-telemetry/utils.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | The serialization fallbacks in `stringifyReason` have no dedicated case | Present |
|
|
164
|
+
| `MOD-018` | Events are never persisted and a failed submission is never retried | Persisting browser error state across loads would create its own privacy surface, and retrying during a failing page risks amplifying the failure | `src/unhandled-exception-telemetry/index.ts` | none found | The absence of persistence and retry is structural rather than asserted by a case | Present |
|
|
165
|
+
|
|
166
|
+
## Design overview
|
|
167
|
+
|
|
168
|
+
The module is one exported function and one class. The function,
|
|
169
|
+
`startUnhandledExceptionTelemetry`, is the whole lifecycle: it decides whether telemetry should be
|
|
170
|
+
running at all, tears down whatever reporter was active, and constructs a new one. A module-level
|
|
171
|
+
variable holds the single active reporter, which is what makes a second SDK instance supersede the
|
|
172
|
+
first rather than double-report. The teardown's flush decision is deliberately driven by the *new*
|
|
173
|
+
configuration: if telemetry is still enabled, pending events are submitted; if it has just been
|
|
174
|
+
turned off or the environment no longer supports it, they are discarded.
|
|
175
|
+
|
|
176
|
+
The class is a capture pipeline with a deduplication window in the middle.
|
|
177
|
+
|
|
178
|
+
Capture is per event kind. A browser `error` event is first inspected for a resource target: if the
|
|
179
|
+
target exposes a current source, source or href, the failure is a resource load failure and is
|
|
180
|
+
reported with the sanitized URL and the uppercase tag name — and dropped entirely if the URL's scheme
|
|
181
|
+
is not HTTP or HTTPS. Otherwise it is an ordinary uncaught error, and name, message, stack, filename,
|
|
182
|
+
line and column are taken from the event. A rejection event takes its name, message and stack from
|
|
183
|
+
the reason, falling back to a stringified reason. If reading those properties throws, as with a
|
|
184
|
+
proxy or a throwing getter, the extraction failure is logged and the event is dropped; nothing is
|
|
185
|
+
submitted and nothing propagates to the application.
|
|
186
|
+
|
|
187
|
+
Sanitization happens once, in `capture`, before anything else: name, message and stack are passed
|
|
188
|
+
through URL-detail removal and then truncated. This ordering matters — the fingerprint is computed
|
|
189
|
+
from the sanitized values, so two failures that differ only in a stripped query parameter
|
|
190
|
+
deduplicate together rather than producing two events.
|
|
191
|
+
|
|
192
|
+
Deduplication is a map from fingerprint to pending event plus a single self-rescheduling timer. When
|
|
193
|
+
a fingerprint is already pending, the event either increments its occurrence count (inside the
|
|
194
|
+
window) or is submitted and replaced (outside it — the case a throttled timer produces). The flush
|
|
195
|
+
pass walks every pending event, submits the expired ones, and reschedules itself for the soonest
|
|
196
|
+
remaining expiry, which is what gives each distinct failure its own full window from one timer.
|
|
197
|
+
|
|
198
|
+
Submission reuses the parent module's client-metrics transport rather than owning one, and chooses
|
|
199
|
+
the pre-login route when the SDK cannot yet authorize. Every stage — capture, metadata collection,
|
|
200
|
+
submission and even logging — is individually wrapped, because these listeners run inside the
|
|
201
|
+
application's own error path.
|
|
202
|
+
|
|
203
|
+
## Data flow and sequence coverage
|
|
204
|
+
|
|
205
|
+
The call style is browser event listeners inbound and an in-process call to the parent plugin's
|
|
206
|
+
transport outbound. There are three operation groups. Capture-and-deduplicate and flush-and-submit
|
|
207
|
+
are diagrammed separately because their actors and state outcomes differ; start-and-supersede is
|
|
208
|
+
diagrammed separately again because it is the only group that can discard data.
|
|
209
|
+
|
|
210
|
+
| Operation group | Entry and outcome | Diagram or evidence | Failure and recovery coverage |
|
|
211
|
+
| -------------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
212
|
+
| Start and supersede | The SDK becomes ready, or a new instance starts; the active reporter is replaced and its pending events flushed or discarded | Third sequence diagram below | A failing constructor is logged and leaves no active reporter; an unsupported environment stops and discards |
|
|
213
|
+
| Capture and deduplicate | A window error or rejection arrives; a sanitized event becomes pending, or merges into a pending one | First sequence diagram below | Extraction failure logged and the event dropped; a non-HTTP resource URL is ignored; an expired pending event is submitted first |
|
|
214
|
+
| Flush and submit | The deduplication window expires; the event is submitted and the timer reschedules for the next expiry | Second sequence diagram below | Missing transport logged and the event dropped; a rejected submission logged; a throwing submit call caught |
|
|
215
|
+
|
|
216
|
+
```mermaid
|
|
217
|
+
sequenceDiagram
|
|
218
|
+
participant Window as Browser window
|
|
219
|
+
participant Reporter as Reporter
|
|
220
|
+
participant Utils as Sanitization helpers
|
|
221
|
+
participant Pending as Pending events map
|
|
222
|
+
Window->>Reporter: error or unhandledrejection, capture phase
|
|
223
|
+
alt error target exposes a resource URL
|
|
224
|
+
Reporter->>Utils: sanitizeResourceUrl
|
|
225
|
+
alt scheme is not HTTP or HTTPS
|
|
226
|
+
Utils-->>Reporter: undefined
|
|
227
|
+
Reporter-->>Window: ignore this failure
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
Reporter->>Utils: remove URL details, then truncate name, message and stack
|
|
231
|
+
Reporter->>Utils: createFingerprint over the sanitized fields
|
|
232
|
+
Reporter->>Pending: look up fingerprint
|
|
233
|
+
alt pending and still inside the window
|
|
234
|
+
Reporter->>Pending: increment occurrence count
|
|
235
|
+
else pending but the window already expired
|
|
236
|
+
Reporter->>Pending: remove and submit the expired event
|
|
237
|
+
Reporter->>Pending: store the new event
|
|
238
|
+
else not pending
|
|
239
|
+
Reporter->>Reporter: collect optional metadata
|
|
240
|
+
Reporter->>Pending: store the new event
|
|
241
|
+
opt no flush timer armed
|
|
242
|
+
Reporter->>Reporter: arm the flush timer for one window
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
opt extraction threw
|
|
246
|
+
Reporter->>Reporter: log the failure and drop the event
|
|
247
|
+
end
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
```mermaid
|
|
251
|
+
sequenceDiagram
|
|
252
|
+
participant Timer as Flush timer
|
|
253
|
+
participant Reporter as Reporter
|
|
254
|
+
participant Pending as Pending events map
|
|
255
|
+
participant Metrics as Client metrics transport
|
|
256
|
+
Timer->>Reporter: flush
|
|
257
|
+
Reporter->>Pending: walk every pending event
|
|
258
|
+
loop for each pending event
|
|
259
|
+
alt window has expired
|
|
260
|
+
Reporter->>Pending: remove the event
|
|
261
|
+
Reporter->>Metrics: submitClientMetrics with tags, fields and the event payload
|
|
262
|
+
opt SDK cannot authorize yet
|
|
263
|
+
Reporter->>Metrics: include the reporter's pre-login identifier
|
|
264
|
+
end
|
|
265
|
+
opt transport unavailable or submission rejected
|
|
266
|
+
Reporter->>Reporter: log and drop
|
|
267
|
+
end
|
|
268
|
+
else window still open
|
|
269
|
+
Reporter->>Reporter: remember the soonest remaining expiry
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
opt any event still pending
|
|
273
|
+
Reporter->>Timer: reschedule for the soonest remaining expiry
|
|
274
|
+
end
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
```mermaid
|
|
278
|
+
sequenceDiagram
|
|
279
|
+
participant Facade as newMetrics facade
|
|
280
|
+
participant Start as startUnhandledExceptionTelemetry
|
|
281
|
+
participant Active as Active reporter
|
|
282
|
+
participant New as New reporter
|
|
283
|
+
Facade->>Start: SDK ready
|
|
284
|
+
Start->>Start: shouldStart = enabled and browser supports listeners
|
|
285
|
+
Start->>Active: stop(flushPending = shouldStart)
|
|
286
|
+
alt shouldStart
|
|
287
|
+
Active->>Active: remove listeners, submit pending events
|
|
288
|
+
else not shouldStart
|
|
289
|
+
Active->>Active: remove listeners, discard pending events
|
|
290
|
+
Start-->>Facade: return without a reporter
|
|
291
|
+
end
|
|
292
|
+
Start->>New: construct and register capture-phase listeners
|
|
293
|
+
opt construction threw
|
|
294
|
+
Start->>Start: log the failure and leave no active reporter
|
|
295
|
+
end
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
## Class and component relationships
|
|
299
|
+
|
|
300
|
+
```mermaid
|
|
301
|
+
classDiagram
|
|
302
|
+
class NewMetrics {
|
|
303
|
+
+onReady()
|
|
304
|
+
}
|
|
305
|
+
class StartFunction {
|
|
306
|
+
+startUnhandledExceptionTelemetry()
|
|
307
|
+
}
|
|
308
|
+
class UnhandledExceptionTelemetry {
|
|
309
|
+
-pendingEvents
|
|
310
|
+
-flushTimer
|
|
311
|
+
-preLoginId
|
|
312
|
+
-errorListener
|
|
313
|
+
-rejectionListener
|
|
314
|
+
+stop()
|
|
315
|
+
-captureError()
|
|
316
|
+
-captureRejection()
|
|
317
|
+
-capture()
|
|
318
|
+
-addMetadata()
|
|
319
|
+
-flush()
|
|
320
|
+
-submit()
|
|
321
|
+
-clearFlushTimer()
|
|
322
|
+
}
|
|
323
|
+
class SanitizationUtils {
|
|
324
|
+
+truncate()
|
|
325
|
+
+removeUrlDetails()
|
|
326
|
+
+sanitizeResourceUrl()
|
|
327
|
+
+removeUrlDetailsFromText()
|
|
328
|
+
+createFingerprint()
|
|
329
|
+
+stringifyReason()
|
|
330
|
+
}
|
|
331
|
+
class MetricsPlugin {
|
|
332
|
+
+submitClientMetrics()
|
|
333
|
+
}
|
|
334
|
+
NewMetrics --> StartFunction : calls once on ready
|
|
335
|
+
StartFunction --> UnhandledExceptionTelemetry : owns at most one active instance
|
|
336
|
+
UnhandledExceptionTelemetry --> SanitizationUtils : redacts, truncates, fingerprints
|
|
337
|
+
UnhandledExceptionTelemetry --> MetricsPlugin : submits through the host transport
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## Use cases and flows
|
|
341
|
+
|
|
342
|
+
| Use case | Actor or caller | Primary steps and outcome | Failure or boundary behavior | Evidence |
|
|
343
|
+
| -------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
344
|
+
| `UC-001` | Host application, enabled | The same uncaught error is raised several times in a second; one sanitized event is submitted with the occurrence count | The next identical error after the window starts a new event rather than reopening the old one | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
345
|
+
| `UC-002` | Host application, enabled | A promise rejects with no handler; the reason's name, message and stack are captured, or a stringified reason when they are absent | A reason whose property getters throw is logged as an extraction failure and dropped without submitting; an unserializable reason is reported with a `String(reason)` or fixed fallback message | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
346
|
+
| `UC-003` | Host application, enabled | A script or image fails to load; the sanitized URL and the uppercase tag name are reported as a resource failure | A non-HTTP scheme is ignored entirely; a responsive image reports the selected candidate, not the fallback | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
347
|
+
| `UC-004` | First party client | A metadata provider is configured; its returned object is attached to each event | A wrong type, an oversized value or a throwing provider records a capture status and the event still goes | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
348
|
+
| `UC-005` | Host application, pre sign-in | An error occurs before the SDK can authorize; the event is submitted on the pre-login route with the reporter's identifier | The identifier is stable for the reporter's lifetime, so pre-login events from one session group together | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
349
|
+
| `UC-006` | Host application, reconfigured | A second SDK instance is created with telemetry disabled; the active reporter is stopped and its pending events discarded | With telemetry still enabled instead, the pending events are flushed before the new reporter starts | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
350
|
+
| `UC-007` | Node.js host | The SDK becomes ready outside a browser; no listeners are installed and any active reporter is stopped with its events discarded | The start function returns without constructing a reporter | `src/unhandled-exception-telemetry/index.ts`, `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
351
|
+
|
|
352
|
+
<!-- Include if: the module calls another service or crosses an event or network boundary. [condition-id: module.crosses_service_boundaries] -->
|
|
353
|
+
|
|
354
|
+
### Cross-boundary use-case flow
|
|
355
|
+
|
|
356
|
+
Every use case that submits crosses one boundary: an in-process call into the parent module's
|
|
357
|
+
client-metrics transport, which then performs the HTTP POST.
|
|
358
|
+
|
|
359
|
+
- Boundary and transport: the host plugin's client-metrics operation, reached through the SDK
|
|
360
|
+
instance rather than by importing the plugin. The module reads the operation off the instance at
|
|
361
|
+
submission time and treats its absence as a recoverable condition.
|
|
362
|
+
- Ordering: events are submitted in pending-map iteration order as their windows expire. No ordering
|
|
363
|
+
is promised to the consumer, and none is needed: each event carries its own capture timestamp.
|
|
364
|
+
- Compatibility: the submitted payload carries an explicit schema version, so ingestion can
|
|
365
|
+
distinguish generations. Tags and fields are a flat projection of the event for indexing, with the
|
|
366
|
+
full event in the payload.
|
|
367
|
+
- Timeout and retry: none here. Retry and backoff belong to the batcher the transport uses; this
|
|
368
|
+
module neither awaits nor retries, and a rejected submission is logged and dropped.
|
|
369
|
+
- Recovery: none for a lost event. The module does not persist or replay, both explicit non-goals of
|
|
370
|
+
the feature.
|
|
371
|
+
|
|
372
|
+
<!-- Include if: the module holds client-side state. [condition-id: module.holds_client_state] -->
|
|
373
|
+
|
|
374
|
+
## Client state model
|
|
375
|
+
|
|
376
|
+
| State or slice | Owner | Initial state | Transition triggers | Reset or persistence boundary |
|
|
377
|
+
| ------------------------ | ------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------- |
|
|
378
|
+
| Active reporter | Module-level variable | Undefined | Set by a successful start; cleared by a start that should not run or whose construction threw | At most one reporter exists per process; never persisted |
|
|
379
|
+
| Pending events map | The reporter instance | Empty map | An event is stored, merged into, submitted or replaced | Cleared on teardown, after the optional flush |
|
|
380
|
+
| Flush timer handle | The reporter instance | Undefined | Armed when the map goes from empty to non-empty; rescheduled by each flush pass | Cleared on teardown and whenever no event remains pending |
|
|
381
|
+
| Pre-login identifier | The reporter instance | Generated at construction | None | Stable for the reporter's lifetime, so one browser session groups together |
|
|
382
|
+
| Bound listener references | The reporter instance | Created at construction | None | Held so teardown can remove exactly the listeners that were added |
|
|
383
|
+
|
|
384
|
+
<!-- Include if: the module enforces domain rules or entity invariants. [condition-id: module.enforces_domain_rules] -->
|
|
385
|
+
|
|
386
|
+
## Business rules and invariants
|
|
387
|
+
|
|
388
|
+
| ID | Invariant | WHY | Enforcement source | Test evidence |
|
|
389
|
+
| --------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ---------------------------------------------------------- |
|
|
390
|
+
| `INV-001` | No submitted string retains URL credentials, query parameters or a fragment | Those positions carry tokens and personal identifiers | `src/unhandled-exception-telemetry/utils.ts` | `test/unit/spec/unhandled-exception-telemetry/utils.ts` |
|
|
391
|
+
| `INV-002` | A URL whose scheme is neither HTTP nor HTTPS is replaced in text and rejected as a resource URL | Such schemes can embed the payload itself, so partial redaction is not sufficient | `src/unhandled-exception-telemetry/utils.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
392
|
+
| `INV-003` | Name, message, stack and resource URL never exceed their caps | One failure must not be able to dominate a telemetry payload | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
393
|
+
| `INV-004` | The fingerprint is computed from sanitized values, never raw ones | Otherwise two reports of the same failure differing only in a stripped query parameter would not deduplicate | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
394
|
+
| `INV-005` | At most one event is pending per fingerprint at any time | The occurrence count is the merge mechanism; two pending events for one fingerprint would double-report | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
395
|
+
| `INV-006` | Submitted metadata is a plain object within the size cap, or absent with a recorded capture status | Caller-controlled input must not be able to produce an unparseable or unbounded payload | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
396
|
+
| `INV-007` | At most one reporter is active per process | Two reporters would register duplicate listeners and double-report every failure | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
397
|
+
| `INV-008` | No code path in this module throws or rejects to its caller | The listeners run inside the application's own error path | `src/unhandled-exception-telemetry/index.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` |
|
|
398
|
+
|
|
399
|
+
<!-- Include if: the module is concurrent, asynchronous, reactive, or event-driven. [condition-id: module.is_concurrent_async] -->
|
|
400
|
+
|
|
401
|
+
## Concurrency and reactive flow
|
|
402
|
+
|
|
403
|
+
- Execution model: two capture-phase window listeners plus a single self-rescheduling timeout. There
|
|
404
|
+
is no interval: each flush pass arms the next one only if an event is still pending, so an idle
|
|
405
|
+
reporter holds no timer.
|
|
406
|
+
- Ordering guarantees: capture is synchronous on the browser event, so an event is pending before the
|
|
407
|
+
next one is handled. Submission order within a flush pass follows map iteration order and is not
|
|
408
|
+
promised to consumers; each event carries its own capture timestamp instead.
|
|
409
|
+
- Idempotency and retry: the fingerprint is the deduplication key within a window, not across
|
|
410
|
+
windows, so the same failure in two windows is two events — distinguishable by their event
|
|
411
|
+
identifiers. Nothing is retried.
|
|
412
|
+
- Shared-state protection: single-threaded by construction. The pending map is mutated only from the
|
|
413
|
+
listeners and the flush pass, both on the main task queue.
|
|
414
|
+
- Blocking restrictions: no capture or teardown path awaits a submission. The submission promise is
|
|
415
|
+
created, given a rejection handler and dropped, so a slow telemetry request cannot delay the page.
|
|
416
|
+
|
|
417
|
+
<!-- Include if: the module has non-trivial state transitions. [condition-id: module.stateful_transitions] -->
|
|
418
|
+
|
|
419
|
+
## State machine
|
|
420
|
+
|
|
421
|
+
The state belongs to one fingerprint's pending event.
|
|
422
|
+
|
|
423
|
+
```mermaid
|
|
424
|
+
stateDiagram-v2
|
|
425
|
+
[*] --> NotPending
|
|
426
|
+
NotPending --> Pending: captured, sanitized, metadata attached, flush timer armed
|
|
427
|
+
NotPending --> NotPending: non-HTTP resource URL, or extraction threw
|
|
428
|
+
Pending --> Pending: matching failure inside the window increments the occurrence count
|
|
429
|
+
Pending --> Submitted: flush pass finds the window expired
|
|
430
|
+
Pending --> Submitted: matching failure arrives after the window expired, so the old event is submitted first
|
|
431
|
+
Submitted --> Pending: the replacing event starts a fresh window
|
|
432
|
+
Pending --> Submitted: teardown with the feature still enabled flushes pending events
|
|
433
|
+
Pending --> Discarded: teardown with the feature disabled or the environment unsupported
|
|
434
|
+
Submitted --> [*]
|
|
435
|
+
Discarded --> [*]
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Guards and rejected transitions:
|
|
439
|
+
|
|
440
|
+
- A resource failure whose URL scheme is not HTTP or HTTPS never reaches `Pending`.
|
|
441
|
+
- The occurrence-count merge is guarded on the elapsed time since capture, not on the timer firing,
|
|
442
|
+
which is what makes the expired-but-pending transition reachable under tab throttling.
|
|
443
|
+
- `Discarded` is reachable only through teardown, and only when the new configuration would not start
|
|
444
|
+
telemetry.
|
|
445
|
+
|
|
446
|
+
<!-- Include if: callers depend on a wire protocol or serialized format. [condition-id: module.exposes_wire_protocol] -->
|
|
447
|
+
|
|
448
|
+
## Protocol and wire format
|
|
449
|
+
|
|
450
|
+
| Message or frame | Version | Producer or serializer | Consumer or parser | Compatibility and ordering rule |
|
|
451
|
+
| --------------------------------- | ------- | --------------------------------------------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
|
|
452
|
+
| Unhandled exception client metric | `1` | `src/unhandled-exception-telemetry/index.ts` | Webex metrics ingestion | The payload declares its own schema version. Field removal or rename is breaking; additions are compatible. No ordering is promised |
|
|
453
|
+
| Indexing tags and fields | n/a | `src/unhandled-exception-telemetry/index.ts` | Webex metrics ingestion | A flat projection of the event for querying. It duplicates payload values by design and must stay consistent with them |
|
|
454
|
+
|
|
455
|
+
The event type is declared in `src/unhandled-exception-telemetry/index.ts` and the metric name is a
|
|
456
|
+
constant exported from the same file; neither is restated here. The payload carries the sanitized
|
|
457
|
+
error details plus the deduplication fingerprint, the occurrence count, the capture timestamp, a
|
|
458
|
+
per-event identifier, the application and runtime identity, and either the validated metadata or a
|
|
459
|
+
metadata capture status explaining its absence.
|
|
460
|
+
|
|
461
|
+
## Pitfalls and constraints
|
|
462
|
+
|
|
463
|
+
- The active reporter is a module-level singleton. When two SDK instances in one page both start
|
|
464
|
+
telemetry, the later start supersedes the earlier reporter, so only one instance's configuration and
|
|
465
|
+
transport are used.
|
|
466
|
+
- Start is a silent no-op outside a browser: it requires the enable flag to be exactly `true` and a
|
|
467
|
+
global `window` with `addEventListener`. Nothing is logged when either check fails.
|
|
468
|
+
- Rejections are not filtered by origin. An unhandled rejection raised by other SDK code, such as a
|
|
469
|
+
telemetry request elsewhere in the package that nobody awaits, is reported exactly like an
|
|
470
|
+
application failure.
|
|
471
|
+
- A rejection reason whose `message`, `name` or `stack` getter throws produces no event at all, only a
|
|
472
|
+
log line. Exotic reasons such as revoked proxies are therefore invisible in telemetry.
|
|
473
|
+
- The metadata provider runs synchronously inside the capture path, once for each new event (merged
|
|
474
|
+
occurrences do not call it again), so a slow provider delays the application's own error handling.
|
|
475
|
+
|
|
476
|
+
<!-- Include if: the module has conventions beyond repository-wide rules. [condition-id: module.module_specific_conventions] -->
|
|
477
|
+
|
|
478
|
+
## Module-specific rules
|
|
479
|
+
|
|
480
|
+
- Do: wrap every new code path in its own guard. Capture, metadata collection, submission and the
|
|
481
|
+
failure logger are each wrapped individually, on purpose, because this code runs inside the
|
|
482
|
+
application's error path.
|
|
483
|
+
- Do: sanitize before fingerprinting. Reversing the order silently breaks deduplication for any
|
|
484
|
+
failure whose text contains a varying URL.
|
|
485
|
+
- Do: compare elapsed time against the window rather than trusting the timer to fire on schedule.
|
|
486
|
+
Background tabs throttle timers, and the expired-but-pending branch exists for that case.
|
|
487
|
+
- Do: keep the helpers in `src/unhandled-exception-telemetry/utils.ts` pure and individually
|
|
488
|
+
testable; they carry the privacy guarantees and have their own spec file.
|
|
489
|
+
- Do not: add a field to the submitted event without checking what it can contain in the worst case.
|
|
490
|
+
Everything derived from the environment goes through redaction and a cap first.
|
|
491
|
+
- Do not: put personally identifiable information or credentials in the metadata provider's return
|
|
492
|
+
value. The module validates shape and size, not meaning, and submits the object verbatim.
|
|
493
|
+
- Do not: add persistence or retry. Both are stated non-goals: persisting browser error state would
|
|
494
|
+
create a new privacy surface, and retrying during a failing page risks amplifying the failure.
|
|
495
|
+
- Do not: construct the reporter directly. Go through the start function so the supersede-and-flush
|
|
496
|
+
rule is applied.
|
|
497
|
+
|
|
498
|
+
<!-- Include if: the module has a non-obvious design trade-off consumers or maintainers must preserve. [condition-id: module.has_design_tradeoff] -->
|
|
499
|
+
|
|
500
|
+
## Key design trade-off
|
|
501
|
+
|
|
502
|
+
| Chosen trade-off | Preserved invariant or benefit | Cost or limitation | Decision evidence |
|
|
503
|
+
| ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ |
|
|
504
|
+
| One in-memory deduplication window, with no persistence and no retry | A failure storm costs one event per distinct failure per second, and the reporter adds no privacy surface or storage of its own | Events captured during a page that then unloads, or whose submission fails, are lost | `src/unhandled-exception-telemetry/index.ts` |
|
|
505
|
+
| Start only after the SDK is ready, rather than installing a standalone collector at import time | There is always a transport available, and the reporter never outlives or precedes the SDK it reports for | Errors raised before SDK initialization are not captured at all, an explicit non-goal of the feature | `src/new-metrics.ts` |
|
|
506
|
+
| Reuse the parent module's client-metrics transport instead of owning a batcher | Exception events inherit the existing batching, retry and pre-login behavior without this module knowing about any of it | The module depends on a plugin surface it reads off the SDK instance at submission time, and must treat its absence as a normal condition | `src/unhandled-exception-telemetry/index.ts` |
|
|
507
|
+
| Compare elapsed time per event instead of trusting one timer | Each distinct failure gets a full window even when the tab is throttled, and no burst is silently merged with another | The flush pass walks every pending event and reschedules itself, which is more work than a fixed interval | `src/unhandled-exception-telemetry/index.ts` |
|
|
508
|
+
| Drive the teardown flush from the new configuration rather than the old one | Turning telemetry off does not then submit what was captured while it was on | The flush decision is non-obvious at the call site: the same teardown call either submits or discards depending on an argument computed by the caller | `src/unhandled-exception-telemetry/index.ts` |
|
|
509
|
+
|
|
510
|
+
## Verification
|
|
511
|
+
|
|
512
|
+
| Requirement or invariant | Test level | Positive evidence | Negative or boundary evidence | Gap |
|
|
513
|
+
| ------------------------ | ---------- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
|
|
514
|
+
| `MOD-001` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
515
|
+
| `MOD-002` / `INV-007` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
516
|
+
| `MOD-003` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
517
|
+
| `MOD-004` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | none found | Registration and removal are asserted; no case proves a bubble-phase listener would have missed a resource failure |
|
|
518
|
+
| `MOD-005` / `INV-005` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
519
|
+
| `MOD-006` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
520
|
+
| `MOD-007` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
521
|
+
| `MOD-008` / `INV-001` | Unit | `test/unit/spec/unhandled-exception-telemetry/utils.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
522
|
+
| `MOD-009` / `INV-002` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
523
|
+
| `MOD-010` / `INV-003` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
524
|
+
| `MOD-011` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
525
|
+
| `MOD-012` / `INV-006` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
526
|
+
| `MOD-013` | — | none found | none found | Not machine-enforceable. It is a caller obligation; the module validates shape and size only |
|
|
527
|
+
| `MOD-014` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | none found | The pre-login route is covered; no case asserts the identifier is stable across two events from one reporter |
|
|
528
|
+
| `MOD-015` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
529
|
+
| `MOD-016` / `INV-008` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
530
|
+
| `MOD-017` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | The test asserts the throwing-getter reason is logged and not submitted; the JSON-serialization fallbacks are untested |
|
|
531
|
+
| `MOD-018` | — | none found | none found | Structural absence of persistence and retry; no case asserts it, and none can without inspecting the module's shape |
|
|
532
|
+
| `INV-004` | Unit | `test/unit/spec/unhandled-exception-telemetry.ts` | `test/unit/spec/unhandled-exception-telemetry.ts` | none |
|
|
533
|
+
|
|
534
|
+
Record coverage gaps explicitly and link follow-up work. A module specification
|
|
535
|
+
is complete only when its public surface, invariants, failure modes, and test
|
|
536
|
+
evidence agree with the implementation.
|