@corbado/autocapture 0.1.0 → 0.2.0-next.9-c4893f6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,17 +4,22 @@ Observe browser authentication signals without changing application code.
4
4
 
5
5
  `@corbado/autocapture` provides small, focused observers for the browser APIs that authentication
6
6
  flows run through. Each observer reports a well-defined stream of events while leaving the
7
- application — and any other code sharing the same APIs — completely undisturbed. It is designed to
8
- be dropped into a page it does not own and behave as a careful guest.
7
+ application — and other code sharing the same APIs — as safely as browser monkey-patching permits.
8
+ It is designed to be dropped into a page it does not own and fail closed when a surface cannot be
9
+ instrumented safely. The remaining cases where observation cannot be perfectly transparent are
10
+ called out in the safety invariants and data handling sections below.
9
11
 
10
12
  The package observes three kinds of signal:
11
13
 
12
14
  - **WebAuthn ceremonies** — the lifecycle of `navigator.credentials.get`/`create` passkey
13
15
  ceremonies, from invocation to settlement, including silent cancellations.
14
- - **Network exchanges** — request/response round trips over `XMLHttpRequest` and `fetch`, limited to
15
- URLs a caller-provided predicate selects.
16
+ - **Network requests and exchanges** — request starts and their eventual outcomes over
17
+ `XMLHttpRequest` and `fetch`, limited to URLs a caller-provided predicate selects. A page-local
18
+ request ID correlates both callbacks.
16
19
  - **In-app navigation** — same-document (SPA) route changes.
17
20
 
21
+ It also exposes `composedPath()` for selector matching across visible shadow-DOM boundaries.
22
+
18
23
  It has no runtime dependencies, ships TypeScript type definitions, and targets ES2019 so it parses
19
24
  on older engines.
20
25
 
@@ -30,14 +35,25 @@ Every observer follows the platform observer pattern (`MutationObserver`, `Resiz
30
35
  it with a callback, receive events, and call `disconnect()` when finished.
31
36
 
32
37
  ```ts
33
- import { captureWebAuthn, captureNetwork, captureNavigation } from "@corbado/autocapture";
38
+ import { captureWebAuthn, captureNetwork, captureNavigation, composedPath } from "@corbado/autocapture";
34
39
 
35
40
  const webauthn = captureWebAuthn({
36
- onEvent: (event) => console.log(event.kind, event.phase, event.ceremonyId),
41
+ onEvent: (event) => console.log(event.kind, event.mediation, event.phase, event.ceremonyId),
37
42
  });
38
43
 
39
44
  const network = captureNetwork({
40
45
  match: (url) => url.origin === location.origin && url.pathname.startsWith("/auth-api/"),
46
+ bodyCapture: {
47
+ request: (body) => {
48
+ const value = typeof body === "string" ? JSON.parse(body) : body;
49
+ return value && typeof value === "object" ? { type: "type" in value ? value.type : undefined } : undefined;
50
+ },
51
+ response: (body) => {
52
+ const value = typeof body === "string" ? JSON.parse(body) : body;
53
+ return value && typeof value === "object" ? { status: "status" in value ? value.status : undefined } : undefined;
54
+ },
55
+ },
56
+ onRequest: (request) => console.log(request.requestId, request.method, request.path),
41
57
  onExchange: (exchange) => console.log(exchange.method, exchange.path, exchange.status, exchange.outcome),
42
58
  });
43
59
 
@@ -45,45 +61,111 @@ const navigation = captureNavigation({
45
61
  onNavigate: (event) => console.log(event.source, event.path),
46
62
  });
47
63
 
64
+ document.addEventListener("click", (event) => {
65
+ const clickedButton = composedPath(event).find(
66
+ (entry) => entry instanceof HTMLButtonElement,
67
+ );
68
+ if (clickedButton) console.log(clickedButton);
69
+ });
70
+
48
71
  // when finished
49
72
  webauthn.disconnect();
50
73
  network.disconnect();
51
74
  navigation.disconnect();
52
75
  ```
53
76
 
54
- Each call returns a `CaptureHandle` with a `mode` (`"standard"`, `"compatibility"`, or
55
- `"unavailable"`) and a `disconnect()` method. Full event and option types ship with the package.
77
+ Each capture call returns a `CaptureHandle` with a mode (`"standard"` or `"unavailable"`) and a
78
+ `disconnect()` method. Environments that cannot be instrumented report `"unavailable"` rather than
79
+ throwing; a misconfigured `captureNetwork` call (missing `match` or `onExchange`) throws a
80
+ `TypeError` instead, since that is a programming error, not a degraded environment. Full event and
81
+ option types ship with the package.
82
+
83
+ WebAuthn events use a protocol-native discriminated union. Authentication phases carry normalized
84
+ `mediation`, `uiMode`, and `credentialSet` (`"allowlisted"` or `"discoverable"`); registration
85
+ phases carry normalized `mediation`. The same profile is present on every phase of one ceremony.
86
+ Autocapture deliberately does not infer product concepts such as conditional-UI spec types,
87
+ known-identifier state, enrollment intent, or the user/system actor. Integrations map those facts in
88
+ their own state context.
56
89
 
57
- ## Guarantees
90
+ ## Safety invariants
58
91
 
59
92
  The observers run inside application call paths, so their behaviour is defined by what they promise
60
93
  never to disturb:
61
94
 
62
- - **The application always comes first.** Original browser methods are invoked with their arguments,
63
- results, and exceptions unchanged. Every observation path is guarded, so a fault in an observer or
64
- in a caller's own callback can never surface into the application's call.
65
- - **Coexistence, not ownership.** Password managers, monitoring SDKs, and frameworks routinely
66
- instrument the same APIs. The observers share those surfaces rather than seize them: other
67
- integrations keep working regardless of load order, and each handle's `mode` reports how it
68
- attached. Where attaching cleanly is impossible, an observer reports `mode: "unavailable"` rather
69
- than throwing.
70
- - **Exactly-once reporting.** A single application action can pass through several layered
71
- integrations; each ceremony, exchange, and navigation is reported once, correlated by id where its
72
- lifecycle spans multiple events.
73
- - **Clean removal.** `disconnect()` stops delivery immediately and removes the observer's
74
- instrumentation without disrupting the application or any integration layered on top.
95
+ - **Exact host delegation.** Patched methods call the delegate once with the original receiver and
96
+ argument vector, and preserve its return value and synchronous exception. Non-WebAuthn Credential
97
+ Management calls are literal pass-throughs. Two Promise-observation choices are unavoidable:
98
+ public-key ceremonies return a derived Promise (identity and microtask timing change), and fetch
99
+ settlement is observed on the platform Promise itself, so an ignored failed fetch no longer
100
+ surfaces as an `unhandledrejection`.
101
+ - **Observation is firewalled.** Synchronous callback, projector, serializer, and logger faults
102
+ are contained; rejected asynchronous event callbacks are consumed and logged. The firewall covers
103
+ first-party faults on a real browser engine — hostile polyfills and patched built-ins are outside
104
+ the threat model.
105
+ - **Coexistence over ownership.** Existing delegates are preserved, slots are restored only while
106
+ still owned by the capture, and later wrappers are never overwritten. A buried wrapper becomes an
107
+ inert pass-through after disconnect. Locked WebAuthn methods are never bypassed through a container
108
+ proxy; the entire WebAuthn attachment is rolled back and reported as unavailable.
109
+ - **Exactly-once reporting.** Each patched surface holds a single capture wrapper, so one
110
+ application action passes through observation once. A later integration that overwrites a wrapped
111
+ method without forwarding through it displaces capture for that surface: the application call
112
+ still succeeds, telemetry is lost. A package wrapper hidden below an unknown third-party wrapper
113
+ cannot currently be detected by another copy.
114
+ - **Conditional removal.** `disconnect()` stops delivery immediately, releases active listeners, and
115
+ restores only slots still owned by the capture. If another integration sits above it, the inert
116
+ pass-through is deliberately left in that chain.
117
+
118
+ These are implementation invariants, not a claim that monkey-patching is undetectable or has zero
119
+ timing, memory, privacy, or Promise-semantics impact.
75
120
 
76
121
  ## Data handling
77
122
 
78
123
  Network observation is designed for minimal data exposure:
79
124
 
80
125
  - Only URLs a caller's `match` predicate explicitly selects are observed; all other traffic is
81
- untouched.
82
- - Exchanges carry **metadata only** (method, URL, status, outcome, duration) by default. Request and
83
- response bodies are captured only when a caller opts in per direction.
84
- - When bodies are captured, a built-in credential redactor always runs and cannot be disabled;
85
- callers may add further redaction but cannot re-expose what the built-in removed. Bodies are bounded
86
- in size and read from clones, so the application's own body consumption is never disturbed, and
87
- streaming and binary payloads are never read.
88
-
89
- WebAuthn payloads are reported in the standard `PublicKeyCredential.toJSON()` shape.
126
+ ignored. The Corbado `/observe/events/{projectID}` sink is always excluded to prevent recursion.
127
+ - Requests and exchanges carry **metadata only** by default. `onRequest` runs after the platform
128
+ accepts the call; `onExchange` reports its eventual outcome, and their `requestId` values match. A
129
+ request or response body is read only when the caller configures a projector for that direction.
130
+ - Projected XHR request bodies are available at request time. For `fetch(new Request(...))`, reading
131
+ the request clone is asynchronous, so the request body may be absent from `onRequest` and present
132
+ only on `onExchange`; string bodies passed through `RequestInit` are available synchronously.
133
+ - Autocapture performs no generic body parsing or redaction. The endpoint-specific projector is the
134
+ privacy boundary: it receives the matched raw body and must return only the fields the consumer is
135
+ allowed to observe. The exact projected value is attached to the exchange without generic
136
+ serialization, cloning, or size limiting. Projector failures, promises, and `undefined` fail closed
137
+ by omitting the body.
138
+ - Readable bodies come from isolated clones read within a 500 ms window; the source body is never
139
+ directly consumed, and streaming and binary payloads are never read. Body cloning still has
140
+ inherent stream tee/backpressure costs, so enabling it is a security and performance decision.
141
+
142
+ Network metadata currently includes the resolved full URL and final response URL. Query strings can
143
+ contain secrets or PII; callers should restrict `match` accordingly until a URL-redaction policy is
144
+ chosen.
145
+
146
+ Navigation events likewise contain the raw `search` and `hash`. OAuth responses, password-reset
147
+ links, and application routes can put secrets or PII there; no navigation redaction policy is
148
+ currently applied.
149
+
150
+ WebAuthn responses use an explicit serializer rather than `credential.toJSON()`. The allowlist keeps
151
+ the assertion and attestation fields the Observe backend parses for credential, authenticator,
152
+ backup-state, AAGUID, transport, and discoverability insight. Client-extension output is limited to
153
+ `credProps.rk`; PRF and unrelated extension results are omitted. The payload therefore still contains
154
+ credential IDs, user handles, challenges, signatures, authenticator data, and attestation material.
155
+
156
+ The tested 1Password browser extension exposes locked `get` and `create` accessors. WebAuthn capture
157
+ therefore reports `mode: "unavailable"`; passkey operations continue through 1Password unchanged but
158
+ produce no autocaptured WebAuthn events. Password managers that wrap the writable methods before
159
+ autocapture are delegated to; one that assigns its wrapper afterwards keeps working, and its
160
+ ceremonies stay observed when the wrapper forwards through the method it replaced (the surveyed
161
+ managers do). A later assignment that does not forward displaces WebAuthn telemetry.
162
+
163
+ ## Runtime support
164
+
165
+ The published bundle targets ES2019 and assumes ES2019-era runtime primitives such as `Promise`,
166
+ `WeakMap`, `WeakSet`, `Symbol`, `Reflect`, and `URL`. Body capture additionally relies on the
167
+ Streams API and `TextDecoder`; these are part of the assumed baseline, not feature-detected at
168
+ runtime. The current real-browser suite covers current
169
+ Chromium, Firefox, and WebKit, with password-manager simulation and virtual WebAuthn in Chromium.
170
+ That is not yet evidence for old iOS, Android WebView, legacy Edge, or arbitrary polyfills; expanding
171
+ the declared browser floor and test matrix is an open release decision.
package/dist/index.d.mts CHANGED
@@ -1,193 +1,294 @@
1
- /** Minimal logging seam. All capture modules are silent unless a logger is provided. */
1
+ /** Receives optional diagnostics. Capture functions remain silent when no logger is provided. */
2
2
  type Logger = {
3
+ /** Records a non-fatal diagnostic message. */
3
4
  log: (...args: unknown[]) => void;
4
5
  };
5
- /**
6
- * Handle returned by every capture function, following the platform observer
7
- * pattern (MutationObserver, ResizeObserver): create it with a callback,
8
- * receive events, and `disconnect()` when done.
9
- */
6
+ /** Controls a capture session. */
10
7
  type CaptureHandle = {
11
8
  /**
12
- * Whether and how the capture attached:
13
- * - `"standard"`: attached and delivering events.
14
- * - `"compatibility"`: attached via a fallback path (another integration held
15
- * the primary one) and delivering events.
16
- * - `"unavailable"`: could not attach (the environment disallows it, or an
17
- * equivalent capture is already active); no events are delivered and
18
- * `disconnect()` is a no-op.
19
- */
20
- readonly mode: "standard" | "compatibility" | "unavailable";
21
- /**
22
- * Stop event delivery immediately and permanently, removing the capture's
23
- * instrumentation without disrupting the application or other integrations
24
- * sharing the same API. Safe to call more than once.
9
+ * Result of starting the capture.
10
+ *
11
+ * - `"standard"`: capture started normally.
12
+ * - `"unavailable"`: capture could not be started and will not emit events.
25
13
  */
14
+ readonly mode: "standard" | "unavailable";
15
+ /** Stops future event delivery. Safe to call more than once. */
26
16
  disconnect(): void;
27
17
  };
28
- type WebAuthnCeremonyKind = "get" | "create";
29
- /**
30
- * One lifecycle event of an observed WebAuthn ceremony
31
- * (`navigator.credentials.get`/`create` invoked with `publicKey` options).
32
- * `started` is delivered when the application invokes the API,
33
- * `completed`/`failed` when the returned promise settles.
34
- */
35
- type WebAuthnCeremonyEvent = {
36
- kind: WebAuthnCeremonyKind;
37
- /** Correlates the `started` event with the `completed`/`failed` event of the same ceremony. */
18
+ /** Normalized Credential Management mediation. Missing mediation defaults to `optional`. */
19
+ type WebAuthnMediation = "silent" | "optional" | "conditional" | "required" | "unknown";
20
+ /** Normalized Credential Management UI mode for authentication ceremonies. */
21
+ type WebAuthnUiMode = "default" | "immediate" | "unknown";
22
+ /** Browser-native facts that remain stable for one ceremony. */
23
+ type WebAuthnCeremonyContext = {
24
+ kind: "authentication";
25
+ mediation: WebAuthnMediation;
26
+ uiMode: WebAuthnUiMode;
27
+ /** Whether the RP supplied a non-empty allow list or requested discoverable credentials. */
28
+ credentialSet: "allowlisted" | "discoverable";
29
+ } | {
30
+ kind: "registration";
31
+ mediation: WebAuthnMediation;
32
+ };
33
+ type WebAuthnCeremonyBase = WebAuthnCeremonyContext & {
34
+ /** Identifier shared by all events belonging to the same ceremony. */
38
35
  ceremonyId: string;
39
- phase: "started" | "completed" | "failed";
40
- /** Mediation requirement exactly as passed by the application (undefined = platform default). */
41
- mediation?: string;
42
- /** Whether transient user activation was active when the ceremony began (when the platform exposes it). */
43
- userActivation?: boolean;
44
- /** started, get only: request options as WebAuthn JSON (binary fields base64url-encoded). */
36
+ /** Authentication request metadata; present only when applicable and serializable. */
45
37
  requestOptionsJson?: string;
46
- /** started, get only: the request options carried a non-empty allowCredentials list. */
47
- hasAllowCredentials?: boolean;
48
- /** completed: the resulting credential as WebAuthn JSON (assertion or attestation shape, by `kind`). */
38
+ /** Completed credential metadata; present only when applicable and serializable. */
49
39
  credentialJson?: string;
50
- /** failed: the raw rejection value. */
40
+ /** Failure details; present only for failed ceremonies. */
51
41
  error?: unknown;
52
- /** failed: DOMException name of the rejection, e.g. NotAllowedError / AbortError. */
53
42
  errorName?: string;
54
- /** failed: the ceremony's own AbortSignal was aborted before the promise settled. */
55
- aborted?: boolean;
43
+ /** Present when the request signal was aborted before settlement. */
44
+ aborted?: true;
45
+ };
46
+ type WebAuthnCeremonyStartedEvent = WebAuthnCeremonyBase & {
47
+ phase: "started";
48
+ /** Whether transient user activation was active when the ceremony started, when exposed by the browser. */
49
+ userActivation?: boolean;
50
+ /**
51
+ * JSON-encoded request metadata for authentication: `rpId`, `timeout`,
52
+ * `userVerification`, `hints`, `challenge`, and `allowCredentials`.
53
+ * Binary values are base64url-encoded. Treat this value as sensitive telemetry.
54
+ */
55
+ requestOptionsJson?: string;
56
+ };
57
+ type WebAuthnCeremonyCompletedEvent = WebAuthnCeremonyBase & {
58
+ phase: "completed";
59
+ /** JSON-encoded allowlisted credential. Treat this value as sensitive telemetry. */
60
+ credentialJson?: string;
61
+ /** A credential manager may complete even after the ceremony signal was aborted. */
62
+ aborted?: true;
56
63
  };
64
+ type WebAuthnCeremonyFailedEvent = WebAuthnCeremonyBase & {
65
+ phase: "failed";
66
+ /** Rejection value from the failed ceremony. This may contain application-specific data. */
67
+ error: unknown;
68
+ /** DOM exception name, or `UnknownError` when no name is available. */
69
+ errorName: string;
70
+ aborted?: true;
71
+ };
72
+ /** A protocol-native lifecycle event for WebAuthn authentication or registration. */
73
+ type WebAuthnCeremonyEvent = WebAuthnCeremonyStartedEvent | WebAuthnCeremonyCompletedEvent | WebAuthnCeremonyFailedEvent;
74
+ /** Options for {@link captureWebAuthn}. */
57
75
  type WebAuthnCaptureOptions = {
76
+ /** Receives WebAuthn lifecycle events. Return values are not awaited. */
58
77
  onEvent: (event: WebAuthnCeremonyEvent) => void;
78
+ /** Optional diagnostic logger. */
59
79
  logger?: Logger;
60
80
  };
81
+ /** Network API used for an observed exchange. */
61
82
  type NetworkTransport = "xhr" | "fetch";
62
- /** One observed request/response round trip on a matched URL. */
63
- type HttpExchange = {
83
+ /** Direction of a projected body within an observed network exchange. */
84
+ type NetworkBodyDirection = "request" | "response";
85
+ /** A matched network request immediately after the platform accepted it. */
86
+ type HttpRequestStarted<RequestBody = unknown> = {
87
+ /** Identifier shared with the eventual {@link HttpExchange}. */
88
+ requestId: string;
89
+ /** Network API used for the request. */
64
90
  transport: NetworkTransport;
91
+ /** Uppercase HTTP method. */
65
92
  method: string;
66
- /** Resolved absolute request URL. */
93
+ /** Resolved absolute request input URL. */
94
+ url: string;
95
+ /** Pathname component of the request URL. */
96
+ path: string;
97
+ /** Timestamp captured at the request boundary. */
98
+ startedAt: number;
99
+ /** Request-body value returned by the configured projector, when synchronously available. */
100
+ requestBody?: RequestBody;
101
+ };
102
+ /** An observed network request and its outcome. */
103
+ type HttpExchange<RequestBody = unknown, ResponseBody = unknown> = {
104
+ /** Identifier shared with the corresponding {@link HttpRequestStarted}. */
105
+ requestId: string;
106
+ /** Network API used for the request. */
107
+ transport: NetworkTransport;
108
+ /** Uppercase HTTP method. */
109
+ method: string;
110
+ /**
111
+ * Resolved absolute request input URL, including its query string and
112
+ * fragment. Redact it before forwarding if either component may be sensitive.
113
+ */
67
114
  url: string;
68
- /** Pathname of the resolved request URL. */
115
+ /** Pathname component of the request URL. */
69
116
  path: string;
70
- /** HTTP status; 0 when the request did not produce a response (abort, timeout, network error). */
117
+ /** HTTP status code, or `0` when a status is unavailable. */
71
118
  status: number;
119
+ /** Whether the status code represents a successful response. */
72
120
  ok: boolean;
73
- /** How the request settled: a normal response, or an abort / timeout / network error. */
121
+ /**
122
+ * How the request completed. `load` includes successful and non-successful
123
+ * HTTP responses; inspect `ok` or `status` for HTTP success.
124
+ */
74
125
  outcome: "load" | "abort" | "timeout" | "error";
126
+ /** Elapsed request time in milliseconds, when available. */
75
127
  durationMs?: number;
76
- /** Final response URL when the platform exposes it (differs from `url` after redirects). */
128
+ /**
129
+ * Final absolute response URL, when available. This may differ from `url`
130
+ * after redirects and may contain sensitive query or fragment values.
131
+ */
77
132
  responseUrl?: string;
78
- requestBody?: unknown;
79
- responseBody?: unknown;
133
+ /** Request-body value returned by the configured projector, when available. */
134
+ requestBody?: RequestBody;
135
+ /** Response-body value returned by the configured projector, when available. */
136
+ responseBody?: ResponseBody;
137
+ };
138
+ /** Metadata supplied to a body projector for one matched exchange. */
139
+ type NetworkBodyProjectionContext<Direction extends NetworkBodyDirection = NetworkBodyDirection> = {
140
+ /** Whether the observed body belongs to the request or response. */
141
+ direction: Direction;
142
+ /** Network API used by the application. */
143
+ transport: NetworkTransport;
144
+ /** Uppercase HTTP method. */
145
+ method: string;
146
+ /** Resolved absolute request URL. */
147
+ url: string;
148
+ /** Pathname component of the request URL. */
149
+ path: string;
80
150
  };
81
- type NetworkCaptureOptions = {
151
+ /**
152
+ * Synchronously selects the only body data that may leave network capture.
153
+ * Textual bodies are supplied as strings; an XHR with `responseType="json"`
154
+ * supplies the browser-parsed value. Returning `undefined`, throwing, or
155
+ * returning a Promise omits the body without affecting the application.
156
+ * `ProjectedBody` describes the exact value delivered to callbacks.
157
+ */
158
+ type NetworkBodyProjector<ProjectedBody = unknown, Direction extends NetworkBodyDirection = NetworkBodyDirection> = (body: unknown, context: NetworkBodyProjectionContext<Direction>) => ProjectedBody | undefined;
159
+ /**
160
+ * Options for {@link captureNetwork}. Request and response body types are
161
+ * inferred from their projectors and propagated to the corresponding callbacks.
162
+ */
163
+ type NetworkCaptureOptions<RequestBody = unknown, ResponseBody = unknown> = {
82
164
  /**
83
- * Selects which requests to report. Called with the request URL resolved
84
- * against the document base URL and the uppercased method. Only matched
85
- * requests are observed; everything else passes through untouched.
165
+ * Selects requests to observe. The URL is resolved against the document base
166
+ * URL and the method is uppercase. Return `true` to observe the request. The
167
+ * Corbado Observe event endpoint is always excluded to prevent recursion.
168
+ * This callback must return synchronously; Promise results are not matched.
86
169
  */
87
170
  match: (url: URL, method: string) => boolean;
88
- onExchange: (exchange: HttpExchange) => void;
89
- /** Which transports to observe. Defaults to both. */
90
- transports?: NetworkTransport[];
91
- /**
92
- * Opt in to capturing request and/or response bodies. Both default to
93
- * `false`: an exchange carries only metadata (method, URL, status, outcome,
94
- * duration, responseUrl) unless a direction is explicitly enabled here.
95
- * Matched URLs are auth endpoints whose bodies carry credentials, so body
96
- * capture is never on by default. When enabled, captured bodies still pass
97
- * through the built-in credential redactor (always) and any `redact` hook.
98
- */
99
- captureBodies?: {
100
- request?: boolean;
101
- response?: boolean;
102
- };
171
+ /** Receives completed network exchanges. Return values are not awaited. */
172
+ onExchange: (exchange: HttpExchange<NoInfer<RequestBody>, NoInfer<ResponseBody>>) => void;
103
173
  /**
104
- * Converts a raw body into the `requestBody`/`responseBody` value. The
105
- * default parses JSON strings and reports everything else as undefined.
106
- * Return undefined to omit the body from the exchange. Only consulted for a
107
- * direction enabled via `captureBodies`.
174
+ * Receives matched requests after the platform call succeeds. The request ID
175
+ * matches the eventual exchange. Return values are not awaited.
108
176
  */
109
- parseBody?: (raw: unknown, direction: "request" | "response") => unknown;
177
+ onRequest?: (request: HttpRequestStarted<NoInfer<RequestBody>>) => void;
110
178
  /**
111
- * Additional redaction applied to a parsed body before it is reported. Runs
112
- * AFTER the built-in credential-key redactor, so it can remove more but can
113
- * never re-expose what the built-in removed. Return the (possibly mutated)
114
- * value, or undefined to drop the body entirely.
179
+ * Network APIs to observe. Defaults to both `xhr` and `fetch`. Unavailable
180
+ * transports are skipped if at least one requested transport can be observed.
115
181
  */
116
- redact?: (body: unknown, direction: "request" | "response") => unknown;
182
+ transports?: readonly NetworkTransport[];
117
183
  /**
118
- * Upper bound on a captured body's size, in characters. Bodies larger than
119
- * this (by declared content-length or actual length) are omitted rather than
120
- * buffered and reported. Applies to both transports. Defaults to 65536.
184
+ * Best-effort body observation, enabled independently per direction by a
185
+ * mandatory projector. Without a projector, that direction is never read.
186
+ * Autocapture performs no generic parsing or redaction: the projector owns
187
+ * data minimization and its return value is the only body data reported.
121
188
  */
122
- maxBodyChars?: number;
189
+ bodyCapture?: {
190
+ /** Projects a request body. */
191
+ request?: NetworkBodyProjector<RequestBody, "request">;
192
+ /** Projects a response body. */
193
+ response?: NetworkBodyProjector<ResponseBody, "response">;
194
+ };
195
+ /** Optional diagnostic logger. */
123
196
  logger?: Logger;
124
197
  };
198
+ /** Mechanism that produced a navigation event. */
125
199
  type NavigationSource = "push" | "replace" | "pop" | "hash";
126
- /** One in-app (same-document) navigation. */
200
+ /** A same-document navigation. */
127
201
  type NavigationEvent = {
202
+ /** Current URL pathname. */
128
203
  path: string;
204
+ /** Current URL query string, including the leading `?` when present. May contain sensitive data. */
129
205
  search: string;
206
+ /** Current URL fragment, including the leading `#` when present. May contain sensitive data. */
130
207
  hash: string;
208
+ /** Mechanism that produced the event. */
131
209
  source: NavigationSource;
132
210
  };
211
+ /** Options for {@link captureNavigation}. */
133
212
  type NavigationCaptureOptions = {
213
+ /** Receives same-document navigation events. Return values are not awaited. */
134
214
  onNavigate: (event: NavigationEvent) => void;
215
+ /** Optional diagnostic logger. */
135
216
  logger?: Logger;
136
217
  };
137
218
 
138
219
  /**
139
- * Observe WebAuthn ceremonies. Each `navigator.credentials.get`/`create` call
140
- * made with `publicKey` options produces a `started` event when it is invoked
141
- * and a `completed` or `failed` event when it settles, correlated by
142
- * `ceremonyId` — including silent cancellations that surface no UI. Calls
143
- * without `publicKey` (WebOTP, federated or password credentials) are never
144
- * reported and pass through untouched.
220
+ * Observes WebAuthn ceremonies initiated through
221
+ * `navigator.credentials.get()` and `navigator.credentials.create()`.
145
222
  *
146
- * Coexists with password managers and other integrations that instrument the
147
- * same API: the application and any such integration keep working unchanged,
148
- * and `mode` reports how the observation attached. In the rare case that
149
- * another integration replaces the credentials container entirely, ceremonies
150
- * routed solely through it are not observed.
223
+ * While capture is active, each ceremony emits a `started` event followed by a
224
+ * correlated `completed` or `failed` event. Credential Management calls
225
+ * without `publicKey` options are not reported. Observation never changes a
226
+ * ceremony's arguments, result, or error; a fault in event delivery is
227
+ * contained, and the ceremony proceeds as if unobserved.
151
228
  *
152
- * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
229
+ * Attachment is all-or-nothing. When WebAuthn cannot be observed in the
230
+ * current environment — the API is missing, a capture is already active, or
231
+ * another integration holds the credential methods in a form that cannot be
232
+ * shared — nothing is modified and the returned handle reports
233
+ * `mode: "unavailable"`.
234
+ *
235
+ * Assumptions and limitations:
236
+ *
237
+ * - Options are expected to be plain dictionaries as defined by the Credential
238
+ * Management and WebAuthn specifications. Exotic option objects, such as
239
+ * proxies or dictionaries with accessor members, are not supported.
240
+ * - Some password-manager extensions expose the credential methods in a form
241
+ * that cannot be observed. Capture then reports `mode: "unavailable"`;
242
+ * authentication continues but produces no events.
243
+ * - A script that replaces the credential methods or the whole
244
+ * `navigator.credentials` object after capture starts is left untouched.
245
+ * Authentication continues, but ceremonies are no longer reported unless the
246
+ * replacement delegates to the methods it replaced.
247
+ *
248
+ * @param options Event callback and optional diagnostic logger.
249
+ * @returns A handle used to inspect the capture mode or stop event delivery.
153
250
  */
154
251
  declare function captureWebAuthn(options: WebAuthnCaptureOptions): CaptureHandle;
155
252
 
156
253
  /**
157
- * Observe request/response round trips over `XMLHttpRequest` and/or `fetch`.
158
- * Each request accepted by the `match` predicate produces one
159
- * {@link HttpExchange}; every other request is left untouched. Observation
160
- * never affects the application's own request — its arguments, result, and
161
- * errors are unchanged even if a capture callback fails.
254
+ * Observes selected requests made through `XMLHttpRequest` and `fetch`.
162
255
  *
163
- * Exchanges carry metadata only unless body capture is opted in via
164
- * `captureBodies`; see {@link NetworkCaptureOptions} for the body, redaction,
165
- * and size controls.
256
+ * While capture remains active, selected requests on available transports can
257
+ * produce one {@link HttpRequestStarted} after the platform accepts the call,
258
+ * followed by one correlated {@link HttpExchange} after completion. The
259
+ * Corbado Observe event endpoint is always excluded. If only some requested
260
+ * transports are available, capture continues on those transports and reports
261
+ * the others to the optional logger.
166
262
  *
167
- * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
263
+ * Exchanges contain metadata by default; body capture must be enabled
264
+ * explicitly.
265
+ *
266
+ * @param options Request selection, event callback, and optional body controls.
267
+ * @returns A handle used to inspect the capture mode or stop event delivery.
268
+ * @throws TypeError when `onExchange` or `match` is not a function — a
269
+ * misconfigured integration is a programming error, not a degraded mode.
168
270
  */
169
- declare function captureNetwork(options: NetworkCaptureOptions): CaptureHandle;
271
+ declare function captureNetwork<RequestBody = unknown, ResponseBody = unknown>(options: NetworkCaptureOptions<RequestBody, ResponseBody>): CaptureHandle;
170
272
 
171
273
  /**
172
- * Observe same-document (SPA) navigations: `pushState`, `replaceState`, and
173
- * back/forward changes. Each produces one {@link NavigationEvent} carrying the
174
- * path, search, hash, and how it occurred. The application's router runs first
175
- * and is unaffected by observation; repeated navigations to the same location
176
- * are reported once.
274
+ * Observes same-document navigations produced by `history.pushState()`,
275
+ * `history.replaceState()`, browser history traversal, and fragment changes.
276
+ *
277
+ * No event is emitted when the pathname, query string, and fragment are all
278
+ * unchanged. Callbacks run synchronously with the observed navigation.
177
279
  *
178
- * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
280
+ * @param options Navigation callback and optional diagnostic logger.
281
+ * @returns A handle used to inspect the capture mode or stop event delivery.
179
282
  */
180
283
  declare function captureNavigation(options: NavigationCaptureOptions): CaptureHandle;
181
284
 
182
285
  /**
183
- * Console-backed logger for development and debugging. The captures run inside
184
- * application call paths (credentials.get, pushState, fetch), so a
185
- * page-overridden or unavailable console must never break them — every call is
186
- * guarded.
286
+ * Returns the visible composed event path. Outside dispatch the browser path is
287
+ * empty, so the event's retargeted target stands in when one is available.
187
288
  */
188
- declare function createConsoleLogger(prefix?: string): Logger;
289
+ declare function composedPath(event: Event): readonly EventTarget[];
189
290
 
190
- /** Package version, injected at build time; "0.0.0-dev" in unbuilt contexts (tests, watch mode). */
291
+ /** Version identifier embedded in the current build. */
191
292
  declare const VERSION: string;
192
293
 
193
- export { type CaptureHandle, type HttpExchange, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkCaptureOptions, type NetworkTransport, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyEvent, type WebAuthnCeremonyKind, captureNavigation, captureNetwork, captureWebAuthn, createConsoleLogger };
294
+ export { type CaptureHandle, type HttpExchange, type HttpRequestStarted, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkBodyDirection, type NetworkBodyProjectionContext, type NetworkBodyProjector, type NetworkCaptureOptions, type NetworkTransport, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyCompletedEvent, type WebAuthnCeremonyContext, type WebAuthnCeremonyEvent, type WebAuthnCeremonyFailedEvent, type WebAuthnCeremonyStartedEvent, type WebAuthnMediation, type WebAuthnUiMode, captureNavigation, captureNetwork, captureWebAuthn, composedPath };
package/dist/index.d.ts CHANGED
@@ -1,193 +1,294 @@
1
- /** Minimal logging seam. All capture modules are silent unless a logger is provided. */
1
+ /** Receives optional diagnostics. Capture functions remain silent when no logger is provided. */
2
2
  type Logger = {
3
+ /** Records a non-fatal diagnostic message. */
3
4
  log: (...args: unknown[]) => void;
4
5
  };
5
- /**
6
- * Handle returned by every capture function, following the platform observer
7
- * pattern (MutationObserver, ResizeObserver): create it with a callback,
8
- * receive events, and `disconnect()` when done.
9
- */
6
+ /** Controls a capture session. */
10
7
  type CaptureHandle = {
11
8
  /**
12
- * Whether and how the capture attached:
13
- * - `"standard"`: attached and delivering events.
14
- * - `"compatibility"`: attached via a fallback path (another integration held
15
- * the primary one) and delivering events.
16
- * - `"unavailable"`: could not attach (the environment disallows it, or an
17
- * equivalent capture is already active); no events are delivered and
18
- * `disconnect()` is a no-op.
19
- */
20
- readonly mode: "standard" | "compatibility" | "unavailable";
21
- /**
22
- * Stop event delivery immediately and permanently, removing the capture's
23
- * instrumentation without disrupting the application or other integrations
24
- * sharing the same API. Safe to call more than once.
9
+ * Result of starting the capture.
10
+ *
11
+ * - `"standard"`: capture started normally.
12
+ * - `"unavailable"`: capture could not be started and will not emit events.
25
13
  */
14
+ readonly mode: "standard" | "unavailable";
15
+ /** Stops future event delivery. Safe to call more than once. */
26
16
  disconnect(): void;
27
17
  };
28
- type WebAuthnCeremonyKind = "get" | "create";
29
- /**
30
- * One lifecycle event of an observed WebAuthn ceremony
31
- * (`navigator.credentials.get`/`create` invoked with `publicKey` options).
32
- * `started` is delivered when the application invokes the API,
33
- * `completed`/`failed` when the returned promise settles.
34
- */
35
- type WebAuthnCeremonyEvent = {
36
- kind: WebAuthnCeremonyKind;
37
- /** Correlates the `started` event with the `completed`/`failed` event of the same ceremony. */
18
+ /** Normalized Credential Management mediation. Missing mediation defaults to `optional`. */
19
+ type WebAuthnMediation = "silent" | "optional" | "conditional" | "required" | "unknown";
20
+ /** Normalized Credential Management UI mode for authentication ceremonies. */
21
+ type WebAuthnUiMode = "default" | "immediate" | "unknown";
22
+ /** Browser-native facts that remain stable for one ceremony. */
23
+ type WebAuthnCeremonyContext = {
24
+ kind: "authentication";
25
+ mediation: WebAuthnMediation;
26
+ uiMode: WebAuthnUiMode;
27
+ /** Whether the RP supplied a non-empty allow list or requested discoverable credentials. */
28
+ credentialSet: "allowlisted" | "discoverable";
29
+ } | {
30
+ kind: "registration";
31
+ mediation: WebAuthnMediation;
32
+ };
33
+ type WebAuthnCeremonyBase = WebAuthnCeremonyContext & {
34
+ /** Identifier shared by all events belonging to the same ceremony. */
38
35
  ceremonyId: string;
39
- phase: "started" | "completed" | "failed";
40
- /** Mediation requirement exactly as passed by the application (undefined = platform default). */
41
- mediation?: string;
42
- /** Whether transient user activation was active when the ceremony began (when the platform exposes it). */
43
- userActivation?: boolean;
44
- /** started, get only: request options as WebAuthn JSON (binary fields base64url-encoded). */
36
+ /** Authentication request metadata; present only when applicable and serializable. */
45
37
  requestOptionsJson?: string;
46
- /** started, get only: the request options carried a non-empty allowCredentials list. */
47
- hasAllowCredentials?: boolean;
48
- /** completed: the resulting credential as WebAuthn JSON (assertion or attestation shape, by `kind`). */
38
+ /** Completed credential metadata; present only when applicable and serializable. */
49
39
  credentialJson?: string;
50
- /** failed: the raw rejection value. */
40
+ /** Failure details; present only for failed ceremonies. */
51
41
  error?: unknown;
52
- /** failed: DOMException name of the rejection, e.g. NotAllowedError / AbortError. */
53
42
  errorName?: string;
54
- /** failed: the ceremony's own AbortSignal was aborted before the promise settled. */
55
- aborted?: boolean;
43
+ /** Present when the request signal was aborted before settlement. */
44
+ aborted?: true;
45
+ };
46
+ type WebAuthnCeremonyStartedEvent = WebAuthnCeremonyBase & {
47
+ phase: "started";
48
+ /** Whether transient user activation was active when the ceremony started, when exposed by the browser. */
49
+ userActivation?: boolean;
50
+ /**
51
+ * JSON-encoded request metadata for authentication: `rpId`, `timeout`,
52
+ * `userVerification`, `hints`, `challenge`, and `allowCredentials`.
53
+ * Binary values are base64url-encoded. Treat this value as sensitive telemetry.
54
+ */
55
+ requestOptionsJson?: string;
56
+ };
57
+ type WebAuthnCeremonyCompletedEvent = WebAuthnCeremonyBase & {
58
+ phase: "completed";
59
+ /** JSON-encoded allowlisted credential. Treat this value as sensitive telemetry. */
60
+ credentialJson?: string;
61
+ /** A credential manager may complete even after the ceremony signal was aborted. */
62
+ aborted?: true;
56
63
  };
64
+ type WebAuthnCeremonyFailedEvent = WebAuthnCeremonyBase & {
65
+ phase: "failed";
66
+ /** Rejection value from the failed ceremony. This may contain application-specific data. */
67
+ error: unknown;
68
+ /** DOM exception name, or `UnknownError` when no name is available. */
69
+ errorName: string;
70
+ aborted?: true;
71
+ };
72
+ /** A protocol-native lifecycle event for WebAuthn authentication or registration. */
73
+ type WebAuthnCeremonyEvent = WebAuthnCeremonyStartedEvent | WebAuthnCeremonyCompletedEvent | WebAuthnCeremonyFailedEvent;
74
+ /** Options for {@link captureWebAuthn}. */
57
75
  type WebAuthnCaptureOptions = {
76
+ /** Receives WebAuthn lifecycle events. Return values are not awaited. */
58
77
  onEvent: (event: WebAuthnCeremonyEvent) => void;
78
+ /** Optional diagnostic logger. */
59
79
  logger?: Logger;
60
80
  };
81
+ /** Network API used for an observed exchange. */
61
82
  type NetworkTransport = "xhr" | "fetch";
62
- /** One observed request/response round trip on a matched URL. */
63
- type HttpExchange = {
83
+ /** Direction of a projected body within an observed network exchange. */
84
+ type NetworkBodyDirection = "request" | "response";
85
+ /** A matched network request immediately after the platform accepted it. */
86
+ type HttpRequestStarted<RequestBody = unknown> = {
87
+ /** Identifier shared with the eventual {@link HttpExchange}. */
88
+ requestId: string;
89
+ /** Network API used for the request. */
64
90
  transport: NetworkTransport;
91
+ /** Uppercase HTTP method. */
65
92
  method: string;
66
- /** Resolved absolute request URL. */
93
+ /** Resolved absolute request input URL. */
94
+ url: string;
95
+ /** Pathname component of the request URL. */
96
+ path: string;
97
+ /** Timestamp captured at the request boundary. */
98
+ startedAt: number;
99
+ /** Request-body value returned by the configured projector, when synchronously available. */
100
+ requestBody?: RequestBody;
101
+ };
102
+ /** An observed network request and its outcome. */
103
+ type HttpExchange<RequestBody = unknown, ResponseBody = unknown> = {
104
+ /** Identifier shared with the corresponding {@link HttpRequestStarted}. */
105
+ requestId: string;
106
+ /** Network API used for the request. */
107
+ transport: NetworkTransport;
108
+ /** Uppercase HTTP method. */
109
+ method: string;
110
+ /**
111
+ * Resolved absolute request input URL, including its query string and
112
+ * fragment. Redact it before forwarding if either component may be sensitive.
113
+ */
67
114
  url: string;
68
- /** Pathname of the resolved request URL. */
115
+ /** Pathname component of the request URL. */
69
116
  path: string;
70
- /** HTTP status; 0 when the request did not produce a response (abort, timeout, network error). */
117
+ /** HTTP status code, or `0` when a status is unavailable. */
71
118
  status: number;
119
+ /** Whether the status code represents a successful response. */
72
120
  ok: boolean;
73
- /** How the request settled: a normal response, or an abort / timeout / network error. */
121
+ /**
122
+ * How the request completed. `load` includes successful and non-successful
123
+ * HTTP responses; inspect `ok` or `status` for HTTP success.
124
+ */
74
125
  outcome: "load" | "abort" | "timeout" | "error";
126
+ /** Elapsed request time in milliseconds, when available. */
75
127
  durationMs?: number;
76
- /** Final response URL when the platform exposes it (differs from `url` after redirects). */
128
+ /**
129
+ * Final absolute response URL, when available. This may differ from `url`
130
+ * after redirects and may contain sensitive query or fragment values.
131
+ */
77
132
  responseUrl?: string;
78
- requestBody?: unknown;
79
- responseBody?: unknown;
133
+ /** Request-body value returned by the configured projector, when available. */
134
+ requestBody?: RequestBody;
135
+ /** Response-body value returned by the configured projector, when available. */
136
+ responseBody?: ResponseBody;
137
+ };
138
+ /** Metadata supplied to a body projector for one matched exchange. */
139
+ type NetworkBodyProjectionContext<Direction extends NetworkBodyDirection = NetworkBodyDirection> = {
140
+ /** Whether the observed body belongs to the request or response. */
141
+ direction: Direction;
142
+ /** Network API used by the application. */
143
+ transport: NetworkTransport;
144
+ /** Uppercase HTTP method. */
145
+ method: string;
146
+ /** Resolved absolute request URL. */
147
+ url: string;
148
+ /** Pathname component of the request URL. */
149
+ path: string;
80
150
  };
81
- type NetworkCaptureOptions = {
151
+ /**
152
+ * Synchronously selects the only body data that may leave network capture.
153
+ * Textual bodies are supplied as strings; an XHR with `responseType="json"`
154
+ * supplies the browser-parsed value. Returning `undefined`, throwing, or
155
+ * returning a Promise omits the body without affecting the application.
156
+ * `ProjectedBody` describes the exact value delivered to callbacks.
157
+ */
158
+ type NetworkBodyProjector<ProjectedBody = unknown, Direction extends NetworkBodyDirection = NetworkBodyDirection> = (body: unknown, context: NetworkBodyProjectionContext<Direction>) => ProjectedBody | undefined;
159
+ /**
160
+ * Options for {@link captureNetwork}. Request and response body types are
161
+ * inferred from their projectors and propagated to the corresponding callbacks.
162
+ */
163
+ type NetworkCaptureOptions<RequestBody = unknown, ResponseBody = unknown> = {
82
164
  /**
83
- * Selects which requests to report. Called with the request URL resolved
84
- * against the document base URL and the uppercased method. Only matched
85
- * requests are observed; everything else passes through untouched.
165
+ * Selects requests to observe. The URL is resolved against the document base
166
+ * URL and the method is uppercase. Return `true` to observe the request. The
167
+ * Corbado Observe event endpoint is always excluded to prevent recursion.
168
+ * This callback must return synchronously; Promise results are not matched.
86
169
  */
87
170
  match: (url: URL, method: string) => boolean;
88
- onExchange: (exchange: HttpExchange) => void;
89
- /** Which transports to observe. Defaults to both. */
90
- transports?: NetworkTransport[];
91
- /**
92
- * Opt in to capturing request and/or response bodies. Both default to
93
- * `false`: an exchange carries only metadata (method, URL, status, outcome,
94
- * duration, responseUrl) unless a direction is explicitly enabled here.
95
- * Matched URLs are auth endpoints whose bodies carry credentials, so body
96
- * capture is never on by default. When enabled, captured bodies still pass
97
- * through the built-in credential redactor (always) and any `redact` hook.
98
- */
99
- captureBodies?: {
100
- request?: boolean;
101
- response?: boolean;
102
- };
171
+ /** Receives completed network exchanges. Return values are not awaited. */
172
+ onExchange: (exchange: HttpExchange<NoInfer<RequestBody>, NoInfer<ResponseBody>>) => void;
103
173
  /**
104
- * Converts a raw body into the `requestBody`/`responseBody` value. The
105
- * default parses JSON strings and reports everything else as undefined.
106
- * Return undefined to omit the body from the exchange. Only consulted for a
107
- * direction enabled via `captureBodies`.
174
+ * Receives matched requests after the platform call succeeds. The request ID
175
+ * matches the eventual exchange. Return values are not awaited.
108
176
  */
109
- parseBody?: (raw: unknown, direction: "request" | "response") => unknown;
177
+ onRequest?: (request: HttpRequestStarted<NoInfer<RequestBody>>) => void;
110
178
  /**
111
- * Additional redaction applied to a parsed body before it is reported. Runs
112
- * AFTER the built-in credential-key redactor, so it can remove more but can
113
- * never re-expose what the built-in removed. Return the (possibly mutated)
114
- * value, or undefined to drop the body entirely.
179
+ * Network APIs to observe. Defaults to both `xhr` and `fetch`. Unavailable
180
+ * transports are skipped if at least one requested transport can be observed.
115
181
  */
116
- redact?: (body: unknown, direction: "request" | "response") => unknown;
182
+ transports?: readonly NetworkTransport[];
117
183
  /**
118
- * Upper bound on a captured body's size, in characters. Bodies larger than
119
- * this (by declared content-length or actual length) are omitted rather than
120
- * buffered and reported. Applies to both transports. Defaults to 65536.
184
+ * Best-effort body observation, enabled independently per direction by a
185
+ * mandatory projector. Without a projector, that direction is never read.
186
+ * Autocapture performs no generic parsing or redaction: the projector owns
187
+ * data minimization and its return value is the only body data reported.
121
188
  */
122
- maxBodyChars?: number;
189
+ bodyCapture?: {
190
+ /** Projects a request body. */
191
+ request?: NetworkBodyProjector<RequestBody, "request">;
192
+ /** Projects a response body. */
193
+ response?: NetworkBodyProjector<ResponseBody, "response">;
194
+ };
195
+ /** Optional diagnostic logger. */
123
196
  logger?: Logger;
124
197
  };
198
+ /** Mechanism that produced a navigation event. */
125
199
  type NavigationSource = "push" | "replace" | "pop" | "hash";
126
- /** One in-app (same-document) navigation. */
200
+ /** A same-document navigation. */
127
201
  type NavigationEvent = {
202
+ /** Current URL pathname. */
128
203
  path: string;
204
+ /** Current URL query string, including the leading `?` when present. May contain sensitive data. */
129
205
  search: string;
206
+ /** Current URL fragment, including the leading `#` when present. May contain sensitive data. */
130
207
  hash: string;
208
+ /** Mechanism that produced the event. */
131
209
  source: NavigationSource;
132
210
  };
211
+ /** Options for {@link captureNavigation}. */
133
212
  type NavigationCaptureOptions = {
213
+ /** Receives same-document navigation events. Return values are not awaited. */
134
214
  onNavigate: (event: NavigationEvent) => void;
215
+ /** Optional diagnostic logger. */
135
216
  logger?: Logger;
136
217
  };
137
218
 
138
219
  /**
139
- * Observe WebAuthn ceremonies. Each `navigator.credentials.get`/`create` call
140
- * made with `publicKey` options produces a `started` event when it is invoked
141
- * and a `completed` or `failed` event when it settles, correlated by
142
- * `ceremonyId` — including silent cancellations that surface no UI. Calls
143
- * without `publicKey` (WebOTP, federated or password credentials) are never
144
- * reported and pass through untouched.
220
+ * Observes WebAuthn ceremonies initiated through
221
+ * `navigator.credentials.get()` and `navigator.credentials.create()`.
145
222
  *
146
- * Coexists with password managers and other integrations that instrument the
147
- * same API: the application and any such integration keep working unchanged,
148
- * and `mode` reports how the observation attached. In the rare case that
149
- * another integration replaces the credentials container entirely, ceremonies
150
- * routed solely through it are not observed.
223
+ * While capture is active, each ceremony emits a `started` event followed by a
224
+ * correlated `completed` or `failed` event. Credential Management calls
225
+ * without `publicKey` options are not reported. Observation never changes a
226
+ * ceremony's arguments, result, or error; a fault in event delivery is
227
+ * contained, and the ceremony proceeds as if unobserved.
151
228
  *
152
- * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
229
+ * Attachment is all-or-nothing. When WebAuthn cannot be observed in the
230
+ * current environment — the API is missing, a capture is already active, or
231
+ * another integration holds the credential methods in a form that cannot be
232
+ * shared — nothing is modified and the returned handle reports
233
+ * `mode: "unavailable"`.
234
+ *
235
+ * Assumptions and limitations:
236
+ *
237
+ * - Options are expected to be plain dictionaries as defined by the Credential
238
+ * Management and WebAuthn specifications. Exotic option objects, such as
239
+ * proxies or dictionaries with accessor members, are not supported.
240
+ * - Some password-manager extensions expose the credential methods in a form
241
+ * that cannot be observed. Capture then reports `mode: "unavailable"`;
242
+ * authentication continues but produces no events.
243
+ * - A script that replaces the credential methods or the whole
244
+ * `navigator.credentials` object after capture starts is left untouched.
245
+ * Authentication continues, but ceremonies are no longer reported unless the
246
+ * replacement delegates to the methods it replaced.
247
+ *
248
+ * @param options Event callback and optional diagnostic logger.
249
+ * @returns A handle used to inspect the capture mode or stop event delivery.
153
250
  */
154
251
  declare function captureWebAuthn(options: WebAuthnCaptureOptions): CaptureHandle;
155
252
 
156
253
  /**
157
- * Observe request/response round trips over `XMLHttpRequest` and/or `fetch`.
158
- * Each request accepted by the `match` predicate produces one
159
- * {@link HttpExchange}; every other request is left untouched. Observation
160
- * never affects the application's own request — its arguments, result, and
161
- * errors are unchanged even if a capture callback fails.
254
+ * Observes selected requests made through `XMLHttpRequest` and `fetch`.
162
255
  *
163
- * Exchanges carry metadata only unless body capture is opted in via
164
- * `captureBodies`; see {@link NetworkCaptureOptions} for the body, redaction,
165
- * and size controls.
256
+ * While capture remains active, selected requests on available transports can
257
+ * produce one {@link HttpRequestStarted} after the platform accepts the call,
258
+ * followed by one correlated {@link HttpExchange} after completion. The
259
+ * Corbado Observe event endpoint is always excluded. If only some requested
260
+ * transports are available, capture continues on those transports and reports
261
+ * the others to the optional logger.
166
262
  *
167
- * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
263
+ * Exchanges contain metadata by default; body capture must be enabled
264
+ * explicitly.
265
+ *
266
+ * @param options Request selection, event callback, and optional body controls.
267
+ * @returns A handle used to inspect the capture mode or stop event delivery.
268
+ * @throws TypeError when `onExchange` or `match` is not a function — a
269
+ * misconfigured integration is a programming error, not a degraded mode.
168
270
  */
169
- declare function captureNetwork(options: NetworkCaptureOptions): CaptureHandle;
271
+ declare function captureNetwork<RequestBody = unknown, ResponseBody = unknown>(options: NetworkCaptureOptions<RequestBody, ResponseBody>): CaptureHandle;
170
272
 
171
273
  /**
172
- * Observe same-document (SPA) navigations: `pushState`, `replaceState`, and
173
- * back/forward changes. Each produces one {@link NavigationEvent} carrying the
174
- * path, search, hash, and how it occurred. The application's router runs first
175
- * and is unaffected by observation; repeated navigations to the same location
176
- * are reported once.
274
+ * Observes same-document navigations produced by `history.pushState()`,
275
+ * `history.replaceState()`, browser history traversal, and fragment changes.
276
+ *
277
+ * No event is emitted when the pathname, query string, and fragment are all
278
+ * unchanged. Callbacks run synchronously with the observed navigation.
177
279
  *
178
- * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
280
+ * @param options Navigation callback and optional diagnostic logger.
281
+ * @returns A handle used to inspect the capture mode or stop event delivery.
179
282
  */
180
283
  declare function captureNavigation(options: NavigationCaptureOptions): CaptureHandle;
181
284
 
182
285
  /**
183
- * Console-backed logger for development and debugging. The captures run inside
184
- * application call paths (credentials.get, pushState, fetch), so a
185
- * page-overridden or unavailable console must never break them — every call is
186
- * guarded.
286
+ * Returns the visible composed event path. Outside dispatch the browser path is
287
+ * empty, so the event's retargeted target stands in when one is available.
187
288
  */
188
- declare function createConsoleLogger(prefix?: string): Logger;
289
+ declare function composedPath(event: Event): readonly EventTarget[];
189
290
 
190
- /** Package version, injected at build time; "0.0.0-dev" in unbuilt contexts (tests, watch mode). */
291
+ /** Version identifier embedded in the current build. */
191
292
  declare const VERSION: string;
192
293
 
193
- export { type CaptureHandle, type HttpExchange, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkCaptureOptions, type NetworkTransport, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyEvent, type WebAuthnCeremonyKind, captureNavigation, captureNetwork, captureWebAuthn, createConsoleLogger };
294
+ export { type CaptureHandle, type HttpExchange, type HttpRequestStarted, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkBodyDirection, type NetworkBodyProjectionContext, type NetworkBodyProjector, type NetworkCaptureOptions, type NetworkTransport, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyCompletedEvent, type WebAuthnCeremonyContext, type WebAuthnCeremonyEvent, type WebAuthnCeremonyFailedEvent, type WebAuthnCeremonyStartedEvent, type WebAuthnMediation, type WebAuthnUiMode, captureNavigation, captureNetwork, captureWebAuthn, composedPath };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var J=Object.defineProperty;var ae=Object.getOwnPropertyDescriptor;var se=Object.getOwnPropertyNames;var ie=Object.prototype.hasOwnProperty;var ue=(e,t)=>{for(var n in t)J(e,n,{get:t[n],enumerable:!0})},ce=(e,t,n,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let c of se(t))!ie.call(e,c)&&c!==n&&J(e,c,{get:()=>t[c],enumerable:!(a=ae(t,c))||a.enumerable});return e};var de=e=>ce(J({},"__esModule",{value:!0}),e);var Ce={};ue(Ce,{VERSION:()=>re,captureNavigation:()=>te,captureNetwork:()=>ee,captureWebAuthn:()=>z,createConsoleLogger:()=>ne});module.exports=de(Ce);var I={log:()=>{}},$=Symbol.for("corbado.autocapture.installed");function N(e){try{return typeof e=="function"&&Object.prototype.hasOwnProperty.call(e,$)}catch{return!1}}function B(e,t){var a,c;let n=t;try{Object.defineProperty(e,"name",{value:(a=n.name)!=null?a:"",configurable:!0}),Object.defineProperty(e,"length",{value:(c=n.length)!=null?c:0,configurable:!0}),Object.defineProperty(e,$,{value:t,configurable:!0})}catch{}return e}function T(){return{mode:"unavailable",disconnect:()=>{}}}function X(e,t,n){return a=>{try{n(a)}catch(c){e.log(`${t} listener failed`,c)}}}function z(e){var m;let t=(m=e.logger)!=null?m:I,n=navigator.credentials;if(!n)return t.log("webauthn capture unavailable: navigator.credentials missing"),T();if(N(n.get)||N(n.create))return t.log("webauthn capture unavailable: an equivalent capture is already active"),T();let a=!0,c=0,s=new WeakSet,l=X(t,"webauthn",h=>{a&&e.onEvent(h)}),C=(h,P)=>B(async f=>{var i,R;if(!a||!(f!=null&&f.publicKey))return P(f);let w=f.publicKey;if(s.has(f)||s.has(w))return P(f);s.add(f),s.add(w);let r=`wa-${++c}`,o=typeof f.mediation=="string"?f.mediation:void 0,u=f.signal,g=!1,y=()=>{g=!0};try{(i=u==null?void 0:u.addEventListener)==null||i.call(u,"abort",y,{once:!0})}catch{}let d={kind:h,ceremonyId:r,phase:"started",mediation:o,userActivation:le()};if(h==="get"){let p=f.publicKey;d.requestOptionsJson=pe(p,t),d.hasAllowCredentials=Array.isArray(p.allowCredentials)&&p.allowCredentials.length>0}l(d);try{let p=await P(f);return l({kind:h,ceremonyId:r,phase:"completed",mediation:o,credentialJson:fe(p,t)}),p}catch(p){let b=g||(u==null?void 0:u.aborted)===!0;throw l({kind:h,ceremonyId:r,phase:"failed",mediation:o,error:p,errorName:p instanceof DOMException?p.name:"UnknownError",...b?{aborted:!0}:{}}),p}finally{try{(R=u==null?void 0:u.removeEventListener)==null||R.call(u,"abort",y)}catch{}s.delete(f),s.delete(w)}},{name:h,length:0}),k=n.get.bind(n),A=n.create.bind(n),E=(h,P)=>{let H=C(h,P),f=H,w=()=>f;try{let r=Object.getOwnPropertyDescriptor(n,h);return Object.defineProperty(n,h,{configurable:!0,enumerable:!0,get:w,set:o=>{if(typeof o=="function"){if(N(o)){f=o;return}t.log(`webauthn ${h}: adopted a third-party integration`),f=C(h,o.bind(n))}}}),()=>{try{let o=Object.getOwnPropertyDescriptor(n,h);if((o==null?void 0:o.get)!==w||f!==H)return;r?Object.defineProperty(n,h,r):delete n[h]}catch(o){t.log(`webauthn ${h} detach skipped`,o)}}}catch{return null}},x=E("get",k),L=E("create",A),_,S="standard";if(!x||!L)try{let h=C("get",w=>n.get.call(n,w)),P=C("create",w=>n.create.call(n,w)),H=new Map,f=new Proxy(Object.create(Object.getPrototypeOf(n)),{get(w,r){if(r==="get")return h;if(r==="create")return P;let o=Reflect.get(n,r,n);if(typeof o!="function")return o;let u=H.get(r);if(u&&u.source===o)return u.bound;let g=o.bind(n);return H.set(r,{source:o,bound:g}),g},set(w,r,o){try{Reflect.set(n,r,o)}catch{}return!0},has(w,r){return Reflect.has(n,r)},getOwnPropertyDescriptor(w,r){let o=Reflect.getOwnPropertyDescriptor(n,r);return o?{...o,configurable:!0}:void 0},ownKeys(){return Reflect.ownKeys(n)}});if(Object.defineProperty(navigator,"credentials",{configurable:!0,enumerable:!0,get:()=>f}),navigator.credentials!==f)throw new Error("compatibility layer did not take effect");t.log("webauthn capture attached via compatibility layer (container methods are locked)"),S="compatibility",_=()=>{try{navigator.credentials===f&&delete navigator.credentials}catch(w){t.log("webauthn compatibility layer removal skipped",w)}}}catch(h){if(t.log("webauthn capture unavailable: credentials API locked by another integration",h),!x&&!L)return T();t.log(`webauthn capture: credentials.${x?"create":"get"} is locked and will not be reported`)}return{mode:S,disconnect:()=>{a=!1,x==null||x(),L==null||L(),_==null||_()}}}function le(){try{let e=navigator.userActivation;return typeof(e==null?void 0:e.isActive)=="boolean"?e.isActive:void 0}catch{return}}function fe(e,t){var n,a,c;if(e)try{let s=e;if(typeof s.toJSON=="function")return JSON.stringify(s.toJSON());let l=s.response;return JSON.stringify({id:s.id,rawId:M(s.rawId),type:s.type,authenticatorAttachment:(n=s.authenticatorAttachment)!=null?n:void 0,clientExtensionResults:(c=(a=s.getClientExtensionResults)==null?void 0:a.call(s))!=null?c:{},response:{clientDataJSON:M(l.clientDataJSON),...l.attestationObject?{attestationObject:M(l.attestationObject)}:{},...l.authenticatorData?{authenticatorData:M(l.authenticatorData)}:{},...l.signature?{signature:M(l.signature)}:{},...l.userHandle?{userHandle:M(l.userHandle)}:{}}})}catch(s){t.log("credential serialization failed",s);return}}function pe(e,t){var n;try{return JSON.stringify({...e,challenge:M(e.challenge),allowCredentials:(n=e.allowCredentials)==null?void 0:n.map(a=>({...a,id:M(a.id)}))})}catch(a){t.log("request options serialization failed",a);return}}function M(e){if(!e)return;let t=e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength),n="";for(let a of t)n+=String.fromCharCode(a);return window.btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}var K=Symbol.for("corbado.autocapture.request");function Y(e){return e[K]}function he(e,t){try{Object.defineProperty(e,K,{value:t,writable:!0,configurable:!0,enumerable:!1})}catch{e[K]=t}}function ge(e,t,n,a){try{let c=e.responseType;if(c===""||c==="text"){let s=e.responseText;return typeof s=="string"&&s.length>t?void 0:n(s,"response")}if(c==="json"){let s=e.response;if(s==null)return;try{if(JSON.stringify(s).length>t)return}catch{return}return a(s,"response")}return}catch{return}}function ye(e,t,n){if(typeof e=="string"&&!(e.length>t))return n(e,"request")}function be(e){try{return e.responseURL||void 0}catch{return}}function me(e){if(typeof e=="string")try{return JSON.parse(e)}catch{return}}var we=/pass|secret|token|otp|csrf|auth|credential|assertion|session|pin|code|challenge/i,Q="[redacted]";function W(e,t=0){if(t>8||e===null||typeof e!="object")return e;try{if(Array.isArray(e))return e.map(a=>W(a,t+1));let n={};for(let[a,c]of Object.entries(e))n[a]=we.test(a)?Q:W(c,t+1);return n}catch{return Q}}var ve=500,Re=65536;function Oe(e){return/event-stream|octet-stream/i.test(e)?!1:/json|text|urlencoded/i.test(e)}async function Z(e,t,n){var a,c,s;try{let l=Number((s=(c=(a=e.headers)==null?void 0:a.get)==null?void 0:c.call(a,"content-length"))!=null?s:"");if(Number.isFinite(l)&&l>t){n.log("body omitted: content-length exceeds limit",l);return}let C,k=new Promise(E=>{C=setTimeout(()=>E(void 0),ve)}),A=await Promise.race([e.text(),k]);if(C!==void 0&&clearTimeout(C),A===void 0){n.log("body omitted: read timed out");return}if(A.length>t){n.log("body omitted: exceeds size limit",A.length);return}return A}catch{return}}function ee(e){var h,P,H,f,w;let t=(h=e.logger)!=null?h:I,n=(P=e.transports)!=null?P:["xhr","fetch"],a=(H=e.maxBodyChars)!=null?H:Re,c=((f=e.captureBodies)==null?void 0:f.request)===!0,s=((w=e.captureBodies)==null?void 0:w.response)===!0,l=!0,C=X(t,"network",r=>{l&&e.onExchange(r)}),k=r=>r==="request"?c:s,A=(r,o)=>{if(r===void 0)return;let u=W(r);if(!e.redact)return u;try{return e.redact(u,o)}catch(g){t.log("redact hook failed",g);return}},E=(r,o)=>{var g;if(!k(o))return;let u;try{u=((g=e.parseBody)!=null?g:me)(r,o)}catch(y){t.log("body parsing failed",y);return}return A(u,o)},x=(r,o)=>{if(k(o))return A(r,o)},L=(r,o)=>{try{let u=typeof document!="undefined"?document.baseURI:void 0,g=new URL(String(r),u);return e.match(g,o)?{url:g.href,path:g.pathname}:void 0}catch{return}},_=()=>{if(typeof XMLHttpRequest=="undefined")return t.log("xhr capture unavailable: XMLHttpRequest missing"),null;let r=XMLHttpRequest.prototype,o=r.open,u=r.send;if(N(o)||N(u))return t.log("xhr capture unavailable: an equivalent capture is already active"),null;let g=function(d,i,...R){try{let p=Y(this);p&&(p.reported=!0);let b=String(d).toUpperCase(),q=l?L(i,b):void 0;he(this,q?{method:b,url:q.url,path:q.path,reported:!1}:void 0)}catch(p){t.log("xhr open observation failed",p)}return o.call(this,d,i,...R)},y=function(d){try{let i=Y(this);if(i&&!i.reported){i.startedAt=Date.now();let R=b=>()=>{i.outcome||(i.outcome=b)};this.addEventListener("abort",R("abort"),{once:!0}),this.addEventListener("timeout",R("timeout"),{once:!0}),this.addEventListener("error",R("error"),{once:!0});let p=()=>{try{if(this.readyState!==4||i.settledStatus!==void 0)return;let b=this.status;b!==0&&(i.settledStatus=b)}catch{}};this.addEventListener("readystatechange",p),this.addEventListener("loadend",()=>{var b,q;try{try{this.removeEventListener("readystatechange",p)}catch{}if(i.reported)return;i.reported=!0;let v=(b=i.settledStatus)!=null?b:0;if(i.settledStatus===void 0)try{v=this.status}catch{}let F=i.settledStatus!==void 0?"load":(q=i.outcome)!=null?q:"load";C({transport:"xhr",method:i.method,url:i.url,path:i.path,status:v,ok:v>=200&&v<300,outcome:F,durationMs:i.startedAt!==void 0?Date.now()-i.startedAt:void 0,responseUrl:be(this),requestBody:ye(d,a,E),responseBody:ge(this,a,E,x)})}catch(v){t.log("xhr exchange observation failed",v)}},{once:!0})}}catch(i){t.log("xhr send observation failed",i)}return u.call(this,d)};B(g,o),B(y,u);try{r.open=g,r.send=y}catch(d){return r.open===g&&(r.open=o),t.log("xhr capture unavailable: XMLHttpRequest methods are not writable",d),null}return()=>{r.open===g&&(r.open=o),r.send===y&&(r.send=u)}},S=()=>{let r=globalThis,o=r.fetch;if(typeof o!="function")return t.log("fetch capture unavailable: fetch missing"),null;if(N(o))return t.log("fetch capture unavailable: an equivalent capture is already active"),null;let u=async(y,d,i,R,p)=>{try{if(!l)return;let b=typeof R=="string"&&R.length>a?void 0:R!==void 0?E(R,"request"):void 0;if(b===void 0&&p){let v=await Z(p,a,t);v!==void 0&&(b=E(v,"request"))}let q;if(i){let v=await Z(i,a,t);v!==void 0&&(q=E(v,"response"))}C({transport:"fetch",method:y.method,url:y.url,path:y.path,status:d.status,ok:d.ok,outcome:"load",durationMs:Date.now()-y.startedAt,responseUrl:d.url||void 0,requestBody:b,responseBody:q})}catch(b){t.log("fetch exchange observation failed",b)}},g=function(y,d){var F,G;let i,R,p,b;try{if(l){let O=typeof Request!="undefined"&&y instanceof Request,D=String((F=d==null?void 0:d.method)!=null?F:O?y.method:"GET").toUpperCase(),U=O?y.url:y,j=L(U,D);if(j&&(i={method:D,url:j.url,path:j.path,startedAt:Date.now()},b=(G=d==null?void 0:d.signal)!=null?G:O?y.signal:void 0,c)){if(typeof(d==null?void 0:d.body)=="string")R=d.body;else if(O&&!(d!=null&&d.body))try{p=y.clone()}catch{}}}}catch(O){t.log("fetch observation failed",O)}let q=o.call(this!=null?this:globalThis,y,d);if(!i)return q;let v=i;return q.then(O=>{var U,j,V;let D;try{if(l&&s){let oe=(V=(j=(U=O.headers)==null?void 0:U.get)==null?void 0:j.call(U,"content-type"))!=null?V:"";Oe(oe)&&(D=O.clone())}}catch{}u(v,O,D,R,p)},O=>{try{let D=(b==null?void 0:b.aborted)===!0||(O==null?void 0:O.name)==="AbortError"||typeof DOMException!="undefined"&&O instanceof DOMException&&O.code===DOMException.ABORT_ERR;C({transport:"fetch",method:v.method,url:v.url,path:v.path,status:0,ok:!1,outcome:D?"abort":"error",durationMs:Date.now()-v.startedAt})}catch(D){t.log("fetch failure observation failed",D)}}),q};B(g,o);try{r.fetch=g}catch(y){return t.log("fetch capture unavailable: fetch is not writable",y),null}return()=>{r.fetch===g&&(r.fetch=o)}},m=[];if(n.includes("xhr")){let r=_();r&&m.push(r)}if(n.includes("fetch")){let r=S();r&&m.push(r)}return m.length===0?T():{mode:"standard",disconnect:()=>{l=!1;for(let r of m.splice(0))r()}}}function te(e){var _;let t=(_=e.logger)!=null?_:I;if(typeof window=="undefined"||typeof history=="undefined")return t.log("navigation capture unavailable: window/history missing"),T();let n=history.pushState,a=history.replaceState;if(N(n)||N(a))return t.log("navigation capture unavailable: an equivalent capture is already active"),T();let c=!0,s=()=>({path:window.location.pathname,search:window.location.search,hash:window.location.hash}),l=s(),C=X(t,"navigation",S=>{c&&e.onNavigate(S)}),k=S=>{try{let m=s();if(m.path===l.path&&m.search===l.search&&m.hash===l.hash)return;l=m,C({...m,source:S})}catch(m){t.log("navigation observation failed",m)}},A=function(...S){let m=n.apply(this,S);return k("push"),m},E=function(...S){let m=a.apply(this,S);return k("replace"),m};B(A,n),B(E,a);let x=()=>k("pop"),L=()=>k("hash");try{history.pushState=A,history.replaceState=E}catch(S){return history.pushState===A&&(history.pushState=n),t.log("navigation capture unavailable: history methods are not writable",S),T()}return window.addEventListener("popstate",x),window.addEventListener("hashchange",L),{mode:"standard",disconnect:()=>{c=!1,history.pushState===A&&(history.pushState=n),history.replaceState===E&&(history.replaceState=a),window.removeEventListener("popstate",x),window.removeEventListener("hashchange",L)}}}function ne(e="[autocapture]"){return{log:(...t)=>{try{console.log(e,...t)}catch{}}}}var re="0.1.0";0&&(module.exports={VERSION,captureNavigation,captureNetwork,captureWebAuthn,createConsoleLogger});
1
+ "use strict";var ne=Object.defineProperty;var Ce=Object.getOwnPropertyDescriptor;var ke=Object.getOwnPropertyNames;var qe=Object.prototype.hasOwnProperty;var Oe=(e,t)=>{for(var o in t)ne(e,o,{get:t[o],enumerable:!0})},Ae=(e,t,o,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of ke(t))!qe.call(e,r)&&r!==o&&ne(e,r,{get:()=>t[r],enumerable:!(n=Ce(t,r))||n.enumerable});return e};var Pe=e=>Ae(ne({},"__esModule",{value:!0}),e);var _e={};Oe(_e,{VERSION:()=>we,captureNavigation:()=>me,captureNetwork:()=>he,captureWebAuthn:()=>ue,composedPath:()=>ve});module.exports=Pe(_e);var Ee={log:()=>{}};function U(e){return e?{log:(...t)=>{try{let o=e.log(...t);if(o&&(typeof o=="object"||typeof o=="function"))try{Promise.resolve(o).catch(()=>{})}catch{}}catch{}}}:Ee}var se=Symbol.for("corbado.autocapture.installed");function _(e){try{return typeof e=="function"&&Object.prototype.hasOwnProperty.call(e,se)}catch{return!1}}function X(e,t){var n,r;let o=t;try{Object.defineProperty(e,se,{value:t,configurable:!0})}catch{}try{Object.defineProperty(e,"name",{value:(n=o.name)!=null?n:"",configurable:!0})}catch{}try{Object.defineProperty(e,"length",{value:(r=o.length)!=null?r:0,configurable:!0})}catch{}return e}function C(){return{mode:"unavailable",disconnect:()=>{}}}function $(e,t,o){let n=U(e);return r=>{try{let a=o(r);if(a&&(typeof a=="object"||typeof a=="function"))try{Promise.resolve(a).catch(p=>n.log(`${t} listener failed`,p))}catch(p){n.log(`${t} listener failed`,p)}}catch(a){n.log(`${t} listener failed`,a)}}}function ue(e){let t=U(e.logger),o=e.onEvent;if(typeof o!="function")return C();let n=typeof navigator=="undefined"?void 0:navigator,r=n==null?void 0:n.credentials;if(!n||!r)return t.log("webauthn capture unavailable: navigator.credentials missing"),C();let a=r.get,p=r.create;if(typeof a!="function"||typeof p!="function")return t.log("webauthn capture unavailable: credentials methods missing"),C();if(_(a)||_(p))return t.log("webauthn capture unavailable: an equivalent capture is already active"),C();let y=!1,k=0,B=$(t,"webauthn",h=>{if(y)return o(h)}),q=(h,D)=>{let I={invoke(...O){let v=this,g=O[0];if(!y||!Q(g)||!Q(g.publicKey))return Reflect.apply(D,v,O);let z=g.publicKey,u=`wa-${++k}`,m=Reflect.apply(D,v,O),l=Q(g.signal)?g.signal:void 0,f=xe(h,g,z),W={...f,ceremonyId:u,phase:"started",userActivation:Be(n)};if(f.kind==="authentication"){let w=z;W.requestOptionsJson=Le(w,t)}return B(W),Promise.resolve(m).then(w=>(y&&B({...f,ceremonyId:u,phase:"completed",credentialJson:De(w,h,t),...(l==null?void 0:l.aborted)===!0?{aborted:!0}:{}}),w),w=>{throw y&&B({...f,ceremonyId:u,phase:"failed",error:w,errorName:Te(w),...(l==null?void 0:l.aborted)===!0?{aborted:!0}:{}}),w})}}.invoke;return X(I,{name:h,length:0})},x=(h,D)=>{let I=q(h,D),O=Object.getOwnPropertyDescriptor(r,h);if(O&&(!("value"in O)||O.writable!==!0))return null;try{r[h]=I}catch{return null}return()=>{r[h]===I&&(O?r[h]=O.value:delete r[h])}},S=x("get",a),N=S?x("create",p):null;return!S||!N?(S==null||S(),t.log("webauthn capture unavailable: credentials methods cannot be patched safely"),C()):(y=!0,{mode:"standard",disconnect:()=>{y=!1,S(),N()}})}function xe(e,t,o){let n=Se(t.mediation);if(e==="create")return{kind:"registration",mediation:n};let r=o;return{kind:"authentication",mediation:n,uiMode:Ne(t.uiMode),credentialSet:Array.isArray(r.allowCredentials)&&r.allowCredentials.length>0?"allowlisted":"discoverable"}}function Se(e){return e===void 0?"optional":e==="silent"||e==="optional"||e==="conditional"||e==="required"?e:"unknown"}function Ne(e){return e===void 0?"default":e==="immediate"?"immediate":"unknown"}function Be(e){let t=e.userActivation;return typeof(t==null?void 0:t.isActive)=="boolean"?t.isActive:void 0}function De(e,t,o){var n;if(e)try{let r=e,a=r.response,p={id:J(r.id),rawId:K(r.rawId),type:J(r.type),authenticatorAttachment:J(r.authenticatorAttachment),response:t==="create"?{clientDataJSON:K(a==null?void 0:a.clientDataJSON),attestationObject:K(a==null?void 0:a.attestationObject),transports:re((n=a==null?void 0:a.getTransports)==null?void 0:n.call(a))}:{clientDataJSON:K(a==null?void 0:a.clientDataJSON),authenticatorData:K(a==null?void 0:a.authenticatorData),signature:K(a==null?void 0:a.signature),userHandle:K(a==null?void 0:a.userHandle)},...t==="create"?He(r):{}};return JSON.stringify(p)}catch(r){o.log("credential serialization failed",r);return}}function Le(e,t){try{let o={},n=J(e.rpId),r=J(e.userVerification),a=re(e.hints);return n!==void 0&&(o.rpId=n),typeof e.timeout=="number"&&Number.isFinite(e.timeout)&&(o.timeout=e.timeout),r!==void 0&&(o.userVerification=r),a!==void 0&&(o.hints=a),o.challenge=K(e.challenge),Array.isArray(e.allowCredentials)&&(o.allowCredentials=je(e.allowCredentials)),JSON.stringify(o)}catch(o){t.log("request options serialization failed",o);return}}function je(e){let t=[];for(let o of e){if(!Q(o))continue;let n=o;t.push({type:J(n.type),id:K(n.id),transports:re(n.transports)})}return t}function re(e){if(Array.isArray(e))return e.filter(t=>typeof t=="string")}function J(e){return typeof e=="string"?e:void 0}function He(e){var n;let t=(n=e.getClientExtensionResults)==null?void 0:n.call(e);if(!Q(t))return{};let o=t.credProps;return!o||typeof o.rk!="boolean"?{}:{clientExtensionResults:{credProps:{rk:o.rk}}}}function K(e){let t;if(e instanceof ArrayBuffer)t=new Uint8Array(e);else if(ArrayBuffer.isView(e))t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);else return;let o="";for(let n of t)o+=String.fromCharCode(n);return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Q(e){return e!==null&&(typeof e=="object"||typeof e=="function")}function Te(e){return e instanceof DOMException?e.name:"UnknownError"}var ce=0;function de(){return ce+=1,`network-${ce}`}function Ie(e,t){let o=e.responseType;if(o===""||o==="text")return t(e.responseText);if(o==="json"){let n=e.response;return n==null?void 0:t(n)}}var Me=500,le=Symbol("body-read-timeout");function pe(e){return/event-stream|octet-stream/i.test(e)?!1:/json|text|urlencoded/i.test(e)}async function fe(e,t){let o=e.body;if(!o)return;let n=o.getReader(),r;try{let a=new Promise(k=>{r=setTimeout(()=>k(le),Me)}),p=new TextDecoder,y=[];for(;;){let k=await Promise.race([n.read(),a]);if(k===le){t.log("body omitted: read timed out"),n.cancel().catch(()=>{});return}if(k.done)break;y.push(p.decode(k.value,{stream:!0}))}return y.push(p.decode()),y.join("")}catch{n.cancel().catch(()=>{});return}finally{r!==void 0&&clearTimeout(r)}}function oe(e){var t;(t=e==null?void 0:e.body)==null||t.cancel().catch(()=>{})}function ie(e,t,o,n,r){try{let a=Object.getOwnPropertyDescriptor(e,t);Ue(a,{...n,value:o})&&Object.defineProperty(e,t,n)}catch(a){r.log(`${String(t)} restoration skipped`,a)}}function Ue(e,t){return!!e&&"value"in e&&"value"in t&&e.value===t.value&&e.writable===t.writable&&e.enumerable===t.enumerable&&e.configurable===t.configurable}function he(e){var v,g,z;let{onExchange:t,onRequest:o,match:n}=e;if(typeof t!="function"||typeof n!="function")throw new TypeError("captureNetwork requires onExchange and match callbacks");let r=U(e.logger),a=(v=e.transports)!=null?v:["xhr","fetch"],p=(g=e.bodyCapture)==null?void 0:g.request,y=(z=e.bodyCapture)==null?void 0:z.response,k=typeof p=="function",B=typeof y=="function",q=!1,x=$(r,"network",u=>{if(q)return t(u)}),S=$(r,"network request",u=>{if(q)return o==null?void 0:o(u)}),N=(u,m,l)=>{if(typeof l=="function")try{return l(u,m)}catch(f){r.log("body projection failed",f);return}},h=(u,m)=>{try{let l=typeof document!="undefined"?document.baseURI:void 0,f=new URL(String(u),l);return n(f,m)===!0?{url:f.href,path:f.pathname}:void 0}catch{return}},D=()=>{if(typeof XMLHttpRequest=="undefined")return r.log("xhr capture unavailable: XMLHttpRequest missing"),null;let u=XMLHttpRequest.prototype,m=Object.getOwnPropertyDescriptor(u,"open"),l=Object.getOwnPropertyDescriptor(u,"send");if(!m||!("value"in m)||typeof m.value!="function"||!l||!("value"in l)||typeof l.value!="function")return r.log("xhr capture unavailable: XMLHttpRequest methods missing"),null;let f=m.value,W=l.value;if(_(f)||_(W))return r.log("xhr capture unavailable: an equivalent capture is already active"),null;let w=new WeakMap,R=new Set,P=!1,L={open(...b){var c;let i=Reflect.apply(f,this,b);if(!q||!P)return i;try{let E=w.get(this);E&&(E.reported=!0,(c=E.cleanupListeners)==null||c.call(E));let j=String(b[0]).toUpperCase(),A=h(b[1],j);A?w.set(this,{method:j,url:A.url,path:A.path,reported:!1}):w.delete(this)}catch(E){r.log("xhr open observation failed",E)}return i}}.open,F={send(...b){let i=q&&P?w.get(this):void 0;if(!i||i.reported||i.cleanupListeners)return Reflect.apply(W,this,b);let c=()=>{},E=!1;try{i.requestId=de();let A={direction:"request",transport:"xhr",method:i.method,url:i.url,path:i.path};i.requestBody=k&&typeof b[0]=="string"?N(b[0],A,p):void 0;let G=()=>{i.outcome===void 0&&(i.outcome="abort")},Y=()=>{i.outcome===void 0&&(i.outcome="timeout")},s=()=>{i.outcome===void 0&&(i.outcome="error")},d=()=>{var V;c();try{if(!q||!P||i.reported||!i.requestId)return;i.reported=!0;let T={direction:"response",transport:"xhr",method:i.method,url:i.url,path:i.path},te=this.status,ae={requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,status:te,ok:te>=200&&te<300,outcome:(V=i.outcome)!=null?V:"load",durationMs:i.startedAt!==void 0?Date.now()-i.startedAt:void 0,responseUrl:this.responseURL||void 0,requestBody:i.requestBody,responseBody:B?Ie(this,Re=>N(Re,T,y)):void 0};i.requestEmitted?x(ae):i.pendingExchange=ae}catch(T){r.log("xhr exchange observation failed",T)}finally{w.get(this)===i&&w.delete(this)}},H=()=>{try{this.readyState===XMLHttpRequest.DONE&&this.status!==0&&d()}catch(V){r.log("xhr ready-state observation failed",V)}},M=()=>d();c=()=>{for(let[V,T]of[["readystatechange",H],["abort",G],["timeout",Y],["error",s],["loadend",M]])this.removeEventListener(V,T);i.cleanupListeners===c&&(i.cleanupListeners=void 0),R.delete(c)},this.addEventListener("readystatechange",H),this.addEventListener("abort",G),this.addEventListener("timeout",Y),this.addEventListener("error",s),this.addEventListener("loadend",M),i.cleanupListeners=c,R.add(c),i.startedAt=Date.now(),E=!0}catch(A){r.log("xhr send observation failed",A),c()}let j;try{j=Reflect.apply(W,this,b)}catch(A){throw c(),A}if(E&&i.requestId!==void 0&&i.startedAt!==void 0){i.requestEmitted=!0,S({requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,startedAt:i.startedAt,requestBody:i.requestBody});let A=i.pendingExchange;i.pendingExchange=void 0,A&&x(A)}return j}}.send;return X(L,f),X(F,W),m.writable!==!0||l.writable!==!0?(r.log("xhr capture unavailable: XMLHttpRequest methods are not writable"),null):(Object.defineProperty(u,"open",{...m,value:L}),Object.defineProperty(u,"send",{...l,value:F}),P=!0,()=>{P=!1;for(let b of[...R])b();ie(u,"open",L,m,r),ie(u,"send",F,l,r)})},I=()=>{let u=globalThis,m=Object.getOwnPropertyDescriptor(u,"fetch");if(!m||!("value"in m))return r.log("fetch capture unavailable: fetch is not an own data property"),null;let l=m.value;if(typeof l!="function")return r.log("fetch capture unavailable: fetch missing"),null;if(_(l))return r.log("fetch capture unavailable: an equivalent capture is already active"),null;let f=!1,W=async(R,P,L,F)=>{try{if(!q||!f){oe(L);return}let[b,i]=await Promise.all([F!=null?F:Promise.resolve(void 0),L?fe(L,r):Promise.resolve(void 0)]);if(!q||!f)return;let c={direction:"request",transport:"fetch",method:R.method,url:R.url,path:R.path},E={...c,direction:"response"},j=R.requestBody;j===void 0&&b!==void 0&&(j=N(b,c,p));let A=i!==void 0?N(i,E,y):void 0;x({requestId:R.requestId,transport:"fetch",method:R.method,url:R.url,path:R.path,status:P.status,ok:P.ok,outcome:"load",durationMs:Date.now()-R.startedAt,responseUrl:P.url||void 0,requestBody:j,responseBody:A})}catch(b){r.log("fetch exchange observation failed",b)}},w={fetch(...R){var j,A,G,Y;let P;try{let s=R[0],d=R[1];if(q&&f&&k&&typeof Request!="undefined"&&s instanceof Request&&(d==null?void 0:d.body)===void 0&&s.body){let H=String((j=d==null?void 0:d.method)!=null?j:s.method).toUpperCase(),M=(A=s.headers.get("content-type"))!=null?A:"";h(s.url,H)&&pe(M)&&(P=s.clone())}}catch(s){r.log("fetch observation failed",s)}let L,F=Date.now();try{L=Reflect.apply(l,this,R)}catch(s){throw oe(P),s}let b,i;try{if(q&&f){let s=R[0],d=R[1],H=typeof Request!="undefined"&&s instanceof Request,M=String((G=d==null?void 0:d.method)!=null?G:H?s.method:"GET").toUpperCase(),V=H?s.url:s,T=h(V,M);T&&(b={requestId:de(),method:M,url:T.url,path:T.path,startedAt:F},i=(Y=d==null?void 0:d.signal)!=null?Y:H?s.signal:void 0,k&&typeof(d==null?void 0:d.body)=="string"&&(b.requestBody=N(d.body,{direction:"request",transport:"fetch",method:M,url:T.url,path:T.path},p)))}}catch(s){r.log("fetch observation failed",s)}if(!b)return oe(P),L;let c=b;S({requestId:c.requestId,transport:"fetch",method:c.method,url:c.url,path:c.path,startedAt:c.startedAt,requestBody:c.requestBody});let E=P?fe(P,r):void 0;return L.then(s=>{var H;let d;try{if(q&&f&&B){let M=(H=s.headers.get("content-type"))!=null?H:"";pe(M)&&s.body&&(d=s.clone())}}catch{}W(c,s,d,E)},s=>{try{if(!q||!f)return;let d=(i==null?void 0:i.aborted)===!0||(s==null?void 0:s.name)==="AbortError"||typeof DOMException!="undefined"&&s instanceof DOMException&&s.code===DOMException.ABORT_ERR;x({requestId:c.requestId,transport:"fetch",method:c.method,url:c.url,path:c.path,status:0,ok:!1,outcome:d?"abort":"error",durationMs:Date.now()-c.startedAt})}catch(d){r.log("fetch failure observation failed",d)}}),L}}.fetch;return X(w,l),m.writable!==!0?(r.log("fetch capture unavailable: fetch is not writable"),null):(Object.defineProperty(u,"fetch",{...m,value:w}),f=!0,()=>{f=!1,ie(u,"fetch",w,m,r)})},O=[];if(a.includes("xhr")){let u=D();u&&O.push(u)}if(a.includes("fetch")){let u=I();u&&O.push(u)}return O.length===0?C():(q=!0,{mode:"standard",disconnect:()=>{q=!1;for(let u of O.splice(0))u()}})}function me(e){let t=U(),o;try{if(t=U(e.logger),o=e.onNavigate,typeof o!="function")return C()}catch{return C()}let n,r,a,p,y,k;try{if(n=window,r=n==null?void 0:n.history,!n||!r)return t.log("navigation capture unavailable: window/history missing"),C();if(y=ye(r,"pushState"),k=ye(r,"replaceState"),!y||!k)return t.log("navigation capture unavailable: history method descriptors missing"),C();if(a=y.descriptor.value,p=k.descriptor.value,typeof a!="function"||typeof p!="function")return t.log("navigation capture unavailable: history methods missing"),C();if(typeof n.addEventListener!="function"||typeof n.removeEventListener!="function")return t.log("navigation capture unavailable: window event methods missing"),C()}catch(v){return t.log("navigation capture unavailable: history inspection failed",v),C()}if(_(a)||_(p))return t.log("navigation capture unavailable: an equivalent capture is already active"),C();let B=!1,q=()=>({path:n.location.pathname,search:n.location.search,hash:n.location.hash}),x;try{x=q()}catch(v){return t.log("navigation capture unavailable: location inspection failed",v),C()}let S=$(t,"navigation",v=>{if(B)return o(v)}),N=v=>{if(B)try{let g=q();if(g.path===x.path&&g.search===x.search&&g.hash===x.hash)return;x=g,S({...g,source:v})}catch(g){t.log("navigation observation failed",g)}},h={pushState(...v){let g=Reflect.apply(a,this,v);return N("push"),g}}.pushState,D={replaceState(...v){let g=Reflect.apply(p,this,v);return N("replace"),g}}.replaceState;X(h,a),X(D,p);let I=()=>N("pop"),O=()=>N("hash");try{if(ge(y,h),ge(k,D),r.pushState!==h||r.replaceState!==D)throw new Error("history method replacement did not take effect");n.addEventListener("popstate",I),n.addEventListener("hashchange",O),B=!0}catch(v){return B=!1,ee(n,"popstate",I,t),ee(n,"hashchange",O,t),Z(k,D,t),Z(y,h,t),t.log("navigation capture unavailable: history methods are not writable",v),o=()=>{},S=()=>{},t=U(),C()}return{mode:"standard",disconnect:()=>{B=!1,o=()=>{},ee(n,"popstate",I,t),ee(n,"hashchange",O,t),Z(y,h,t),Z(k,D,t),S=()=>{},t=U()}}}function ye(e,t){let o=e;for(;o;){let n=Object.getOwnPropertyDescriptor(o,t);if(n)return!("value"in n)||typeof n.value!="function"?null:{owner:o,key:t,descriptor:n};o=Object.getPrototypeOf(o)}return null}function ge(e,t){if(e.descriptor.writable!==!0)throw new TypeError(`${e.key} is not writable`);let o=Object.getOwnPropertyDescriptor(e.owner,e.key);if(!be(o,e.descriptor))throw new Error(`${e.key} changed during installation`);Object.defineProperty(e.owner,e.key,{...e.descriptor,value:t})}function be(e,t){return!!e&&"value"in e&&"value"in t&&e.value===t.value&&e.writable===t.writable&&e.enumerable===t.enumerable&&e.configurable===t.configurable}function Z(e,t,o){try{let n=Object.getOwnPropertyDescriptor(e.owner,e.key);be(n,{...e.descriptor,value:t})&&Object.defineProperty(e.owner,e.key,e.descriptor)}catch(n){o.log(`navigation ${e.key} restoration skipped`,n)}}function ee(e,t,o,n){try{e.removeEventListener(t,o)}catch(r){n.log(`navigation ${t} listener removal skipped`,r)}}function ve(e){let t=e.composedPath();return t.length>0?t:e.target?[e.target]:[]}var we="0.2.0-next.9-c4893f6";0&&(module.exports={VERSION,captureNavigation,captureNetwork,captureWebAuthn,composedPath});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var I={log:()=>{}},V=Symbol.for("corbado.autocapture.installed");function N(e){try{return typeof e=="function"&&Object.prototype.hasOwnProperty.call(e,V)}catch{return!1}}function B(e,t){var a,p;let n=t;try{Object.defineProperty(e,"name",{value:(a=n.name)!=null?a:"",configurable:!0}),Object.defineProperty(e,"length",{value:(p=n.length)!=null?p:0,configurable:!0}),Object.defineProperty(e,V,{value:t,configurable:!0})}catch{}return e}function T(){return{mode:"unavailable",disconnect:()=>{}}}function X(e,t,n){return a=>{try{n(a)}catch(p){e.log(`${t} listener failed`,p)}}}function Z(e){var m;let t=(m=e.logger)!=null?m:I,n=navigator.credentials;if(!n)return t.log("webauthn capture unavailable: navigator.credentials missing"),T();if(N(n.get)||N(n.create))return t.log("webauthn capture unavailable: an equivalent capture is already active"),T();let a=!0,p=0,s=new WeakSet,d=X(t,"webauthn",h=>{a&&e.onEvent(h)}),C=(h,P)=>B(async l=>{var i,R;if(!a||!(l!=null&&l.publicKey))return P(l);let w=l.publicKey;if(s.has(l)||s.has(w))return P(l);s.add(l),s.add(w);let r=`wa-${++p}`,o=typeof l.mediation=="string"?l.mediation:void 0,u=l.signal,g=!1,y=()=>{g=!0};try{(i=u==null?void 0:u.addEventListener)==null||i.call(u,"abort",y,{once:!0})}catch{}let c={kind:h,ceremonyId:r,phase:"started",mediation:o,userActivation:ee()};if(h==="get"){let f=l.publicKey;c.requestOptionsJson=ne(f,t),c.hasAllowCredentials=Array.isArray(f.allowCredentials)&&f.allowCredentials.length>0}d(c);try{let f=await P(l);return d({kind:h,ceremonyId:r,phase:"completed",mediation:o,credentialJson:te(f,t)}),f}catch(f){let b=g||(u==null?void 0:u.aborted)===!0;throw d({kind:h,ceremonyId:r,phase:"failed",mediation:o,error:f,errorName:f instanceof DOMException?f.name:"UnknownError",...b?{aborted:!0}:{}}),f}finally{try{(R=u==null?void 0:u.removeEventListener)==null||R.call(u,"abort",y)}catch{}s.delete(l),s.delete(w)}},{name:h,length:0}),k=n.get.bind(n),A=n.create.bind(n),E=(h,P)=>{let H=C(h,P),l=H,w=()=>l;try{let r=Object.getOwnPropertyDescriptor(n,h);return Object.defineProperty(n,h,{configurable:!0,enumerable:!0,get:w,set:o=>{if(typeof o=="function"){if(N(o)){l=o;return}t.log(`webauthn ${h}: adopted a third-party integration`),l=C(h,o.bind(n))}}}),()=>{try{let o=Object.getOwnPropertyDescriptor(n,h);if((o==null?void 0:o.get)!==w||l!==H)return;r?Object.defineProperty(n,h,r):delete n[h]}catch(o){t.log(`webauthn ${h} detach skipped`,o)}}}catch{return null}},x=E("get",k),L=E("create",A),_,S="standard";if(!x||!L)try{let h=C("get",w=>n.get.call(n,w)),P=C("create",w=>n.create.call(n,w)),H=new Map,l=new Proxy(Object.create(Object.getPrototypeOf(n)),{get(w,r){if(r==="get")return h;if(r==="create")return P;let o=Reflect.get(n,r,n);if(typeof o!="function")return o;let u=H.get(r);if(u&&u.source===o)return u.bound;let g=o.bind(n);return H.set(r,{source:o,bound:g}),g},set(w,r,o){try{Reflect.set(n,r,o)}catch{}return!0},has(w,r){return Reflect.has(n,r)},getOwnPropertyDescriptor(w,r){let o=Reflect.getOwnPropertyDescriptor(n,r);return o?{...o,configurable:!0}:void 0},ownKeys(){return Reflect.ownKeys(n)}});if(Object.defineProperty(navigator,"credentials",{configurable:!0,enumerable:!0,get:()=>l}),navigator.credentials!==l)throw new Error("compatibility layer did not take effect");t.log("webauthn capture attached via compatibility layer (container methods are locked)"),S="compatibility",_=()=>{try{navigator.credentials===l&&delete navigator.credentials}catch(w){t.log("webauthn compatibility layer removal skipped",w)}}}catch(h){if(t.log("webauthn capture unavailable: credentials API locked by another integration",h),!x&&!L)return T();t.log(`webauthn capture: credentials.${x?"create":"get"} is locked and will not be reported`)}return{mode:S,disconnect:()=>{a=!1,x==null||x(),L==null||L(),_==null||_()}}}function ee(){try{let e=navigator.userActivation;return typeof(e==null?void 0:e.isActive)=="boolean"?e.isActive:void 0}catch{return}}function te(e,t){var n,a,p;if(e)try{let s=e;if(typeof s.toJSON=="function")return JSON.stringify(s.toJSON());let d=s.response;return JSON.stringify({id:s.id,rawId:M(s.rawId),type:s.type,authenticatorAttachment:(n=s.authenticatorAttachment)!=null?n:void 0,clientExtensionResults:(p=(a=s.getClientExtensionResults)==null?void 0:a.call(s))!=null?p:{},response:{clientDataJSON:M(d.clientDataJSON),...d.attestationObject?{attestationObject:M(d.attestationObject)}:{},...d.authenticatorData?{authenticatorData:M(d.authenticatorData)}:{},...d.signature?{signature:M(d.signature)}:{},...d.userHandle?{userHandle:M(d.userHandle)}:{}}})}catch(s){t.log("credential serialization failed",s);return}}function ne(e,t){var n;try{return JSON.stringify({...e,challenge:M(e.challenge),allowCredentials:(n=e.allowCredentials)==null?void 0:n.map(a=>({...a,id:M(a.id)}))})}catch(a){t.log("request options serialization failed",a);return}}function M(e){if(!e)return;let t=e instanceof ArrayBuffer?new Uint8Array(e):new Uint8Array(e.buffer,e.byteOffset,e.byteLength),n="";for(let a of t)n+=String.fromCharCode(a);return window.btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}var J=Symbol.for("corbado.autocapture.request");function $(e){return e[J]}function re(e,t){try{Object.defineProperty(e,J,{value:t,writable:!0,configurable:!0,enumerable:!1})}catch{e[J]=t}}function oe(e,t,n,a){try{let p=e.responseType;if(p===""||p==="text"){let s=e.responseText;return typeof s=="string"&&s.length>t?void 0:n(s,"response")}if(p==="json"){let s=e.response;if(s==null)return;try{if(JSON.stringify(s).length>t)return}catch{return}return a(s,"response")}return}catch{return}}function ae(e,t,n){if(typeof e=="string"&&!(e.length>t))return n(e,"request")}function se(e){try{return e.responseURL||void 0}catch{return}}function ie(e){if(typeof e=="string")try{return JSON.parse(e)}catch{return}}var ue=/pass|secret|token|otp|csrf|auth|credential|assertion|session|pin|code|challenge/i,z="[redacted]";function K(e,t=0){if(t>8||e===null||typeof e!="object")return e;try{if(Array.isArray(e))return e.map(a=>K(a,t+1));let n={};for(let[a,p]of Object.entries(e))n[a]=ue.test(a)?z:K(p,t+1);return n}catch{return z}}var ce=500,de=65536;function le(e){return/event-stream|octet-stream/i.test(e)?!1:/json|text|urlencoded/i.test(e)}async function Y(e,t,n){var a,p,s;try{let d=Number((s=(p=(a=e.headers)==null?void 0:a.get)==null?void 0:p.call(a,"content-length"))!=null?s:"");if(Number.isFinite(d)&&d>t){n.log("body omitted: content-length exceeds limit",d);return}let C,k=new Promise(E=>{C=setTimeout(()=>E(void 0),ce)}),A=await Promise.race([e.text(),k]);if(C!==void 0&&clearTimeout(C),A===void 0){n.log("body omitted: read timed out");return}if(A.length>t){n.log("body omitted: exceeds size limit",A.length);return}return A}catch{return}}function fe(e){var h,P,H,l,w;let t=(h=e.logger)!=null?h:I,n=(P=e.transports)!=null?P:["xhr","fetch"],a=(H=e.maxBodyChars)!=null?H:de,p=((l=e.captureBodies)==null?void 0:l.request)===!0,s=((w=e.captureBodies)==null?void 0:w.response)===!0,d=!0,C=X(t,"network",r=>{d&&e.onExchange(r)}),k=r=>r==="request"?p:s,A=(r,o)=>{if(r===void 0)return;let u=K(r);if(!e.redact)return u;try{return e.redact(u,o)}catch(g){t.log("redact hook failed",g);return}},E=(r,o)=>{var g;if(!k(o))return;let u;try{u=((g=e.parseBody)!=null?g:ie)(r,o)}catch(y){t.log("body parsing failed",y);return}return A(u,o)},x=(r,o)=>{if(k(o))return A(r,o)},L=(r,o)=>{try{let u=typeof document!="undefined"?document.baseURI:void 0,g=new URL(String(r),u);return e.match(g,o)?{url:g.href,path:g.pathname}:void 0}catch{return}},_=()=>{if(typeof XMLHttpRequest=="undefined")return t.log("xhr capture unavailable: XMLHttpRequest missing"),null;let r=XMLHttpRequest.prototype,o=r.open,u=r.send;if(N(o)||N(u))return t.log("xhr capture unavailable: an equivalent capture is already active"),null;let g=function(c,i,...R){try{let f=$(this);f&&(f.reported=!0);let b=String(c).toUpperCase(),q=d?L(i,b):void 0;re(this,q?{method:b,url:q.url,path:q.path,reported:!1}:void 0)}catch(f){t.log("xhr open observation failed",f)}return o.call(this,c,i,...R)},y=function(c){try{let i=$(this);if(i&&!i.reported){i.startedAt=Date.now();let R=b=>()=>{i.outcome||(i.outcome=b)};this.addEventListener("abort",R("abort"),{once:!0}),this.addEventListener("timeout",R("timeout"),{once:!0}),this.addEventListener("error",R("error"),{once:!0});let f=()=>{try{if(this.readyState!==4||i.settledStatus!==void 0)return;let b=this.status;b!==0&&(i.settledStatus=b)}catch{}};this.addEventListener("readystatechange",f),this.addEventListener("loadend",()=>{var b,q;try{try{this.removeEventListener("readystatechange",f)}catch{}if(i.reported)return;i.reported=!0;let v=(b=i.settledStatus)!=null?b:0;if(i.settledStatus===void 0)try{v=this.status}catch{}let F=i.settledStatus!==void 0?"load":(q=i.outcome)!=null?q:"load";C({transport:"xhr",method:i.method,url:i.url,path:i.path,status:v,ok:v>=200&&v<300,outcome:F,durationMs:i.startedAt!==void 0?Date.now()-i.startedAt:void 0,responseUrl:se(this),requestBody:ae(c,a,E),responseBody:oe(this,a,E,x)})}catch(v){t.log("xhr exchange observation failed",v)}},{once:!0})}}catch(i){t.log("xhr send observation failed",i)}return u.call(this,c)};B(g,o),B(y,u);try{r.open=g,r.send=y}catch(c){return r.open===g&&(r.open=o),t.log("xhr capture unavailable: XMLHttpRequest methods are not writable",c),null}return()=>{r.open===g&&(r.open=o),r.send===y&&(r.send=u)}},S=()=>{let r=globalThis,o=r.fetch;if(typeof o!="function")return t.log("fetch capture unavailable: fetch missing"),null;if(N(o))return t.log("fetch capture unavailable: an equivalent capture is already active"),null;let u=async(y,c,i,R,f)=>{try{if(!d)return;let b=typeof R=="string"&&R.length>a?void 0:R!==void 0?E(R,"request"):void 0;if(b===void 0&&f){let v=await Y(f,a,t);v!==void 0&&(b=E(v,"request"))}let q;if(i){let v=await Y(i,a,t);v!==void 0&&(q=E(v,"response"))}C({transport:"fetch",method:y.method,url:y.url,path:y.path,status:c.status,ok:c.ok,outcome:"load",durationMs:Date.now()-y.startedAt,responseUrl:c.url||void 0,requestBody:b,responseBody:q})}catch(b){t.log("fetch exchange observation failed",b)}},g=function(y,c){var F,W;let i,R,f,b;try{if(d){let O=typeof Request!="undefined"&&y instanceof Request,D=String((F=c==null?void 0:c.method)!=null?F:O?y.method:"GET").toUpperCase(),U=O?y.url:y,j=L(U,D);if(j&&(i={method:D,url:j.url,path:j.path,startedAt:Date.now()},b=(W=c==null?void 0:c.signal)!=null?W:O?y.signal:void 0,p)){if(typeof(c==null?void 0:c.body)=="string")R=c.body;else if(O&&!(c!=null&&c.body))try{f=y.clone()}catch{}}}}catch(O){t.log("fetch observation failed",O)}let q=o.call(this!=null?this:globalThis,y,c);if(!i)return q;let v=i;return q.then(O=>{var U,j,G;let D;try{if(d&&s){let Q=(G=(j=(U=O.headers)==null?void 0:U.get)==null?void 0:j.call(U,"content-type"))!=null?G:"";le(Q)&&(D=O.clone())}}catch{}u(v,O,D,R,f)},O=>{try{let D=(b==null?void 0:b.aborted)===!0||(O==null?void 0:O.name)==="AbortError"||typeof DOMException!="undefined"&&O instanceof DOMException&&O.code===DOMException.ABORT_ERR;C({transport:"fetch",method:v.method,url:v.url,path:v.path,status:0,ok:!1,outcome:D?"abort":"error",durationMs:Date.now()-v.startedAt})}catch(D){t.log("fetch failure observation failed",D)}}),q};B(g,o);try{r.fetch=g}catch(y){return t.log("fetch capture unavailable: fetch is not writable",y),null}return()=>{r.fetch===g&&(r.fetch=o)}},m=[];if(n.includes("xhr")){let r=_();r&&m.push(r)}if(n.includes("fetch")){let r=S();r&&m.push(r)}return m.length===0?T():{mode:"standard",disconnect:()=>{d=!1;for(let r of m.splice(0))r()}}}function pe(e){var _;let t=(_=e.logger)!=null?_:I;if(typeof window=="undefined"||typeof history=="undefined")return t.log("navigation capture unavailable: window/history missing"),T();let n=history.pushState,a=history.replaceState;if(N(n)||N(a))return t.log("navigation capture unavailable: an equivalent capture is already active"),T();let p=!0,s=()=>({path:window.location.pathname,search:window.location.search,hash:window.location.hash}),d=s(),C=X(t,"navigation",S=>{p&&e.onNavigate(S)}),k=S=>{try{let m=s();if(m.path===d.path&&m.search===d.search&&m.hash===d.hash)return;d=m,C({...m,source:S})}catch(m){t.log("navigation observation failed",m)}},A=function(...S){let m=n.apply(this,S);return k("push"),m},E=function(...S){let m=a.apply(this,S);return k("replace"),m};B(A,n),B(E,a);let x=()=>k("pop"),L=()=>k("hash");try{history.pushState=A,history.replaceState=E}catch(S){return history.pushState===A&&(history.pushState=n),t.log("navigation capture unavailable: history methods are not writable",S),T()}return window.addEventListener("popstate",x),window.addEventListener("hashchange",L),{mode:"standard",disconnect:()=>{p=!1,history.pushState===A&&(history.pushState=n),history.replaceState===E&&(history.replaceState=a),window.removeEventListener("popstate",x),window.removeEventListener("hashchange",L)}}}function he(e="[autocapture]"){return{log:(...t)=>{try{console.log(e,...t)}catch{}}}}var ge="0.1.0";export{ge as VERSION,pe as captureNavigation,fe as captureNetwork,Z as captureWebAuthn,he as createConsoleLogger};
1
+ var ge={log:()=>{}};function U(e){return e?{log:(...t)=>{try{let o=e.log(...t);if(o&&(typeof o=="object"||typeof o=="function"))try{Promise.resolve(o).catch(()=>{})}catch{}}catch{}}}:ge}var ae=Symbol.for("corbado.autocapture.installed");function _(e){try{return typeof e=="function"&&Object.prototype.hasOwnProperty.call(e,ae)}catch{return!1}}function X(e,t){var n,r;let o=t;try{Object.defineProperty(e,ae,{value:t,configurable:!0})}catch{}try{Object.defineProperty(e,"name",{value:(n=o.name)!=null?n:"",configurable:!0})}catch{}try{Object.defineProperty(e,"length",{value:(r=o.length)!=null?r:0,configurable:!0})}catch{}return e}function C(){return{mode:"unavailable",disconnect:()=>{}}}function $(e,t,o){let n=U(e);return r=>{try{let a=o(r);if(a&&(typeof a=="object"||typeof a=="function"))try{Promise.resolve(a).catch(p=>n.log(`${t} listener failed`,p))}catch(p){n.log(`${t} listener failed`,p)}}catch(a){n.log(`${t} listener failed`,a)}}}function me(e){let t=U(e.logger),o=e.onEvent;if(typeof o!="function")return C();let n=typeof navigator=="undefined"?void 0:navigator,r=n==null?void 0:n.credentials;if(!n||!r)return t.log("webauthn capture unavailable: navigator.credentials missing"),C();let a=r.get,p=r.create;if(typeof a!="function"||typeof p!="function")return t.log("webauthn capture unavailable: credentials methods missing"),C();if(_(a)||_(p))return t.log("webauthn capture unavailable: an equivalent capture is already active"),C();let y=!1,k=0,B=$(t,"webauthn",h=>{if(y)return o(h)}),q=(h,D)=>{let I={invoke(...O){let v=this,g=O[0];if(!y||!Q(g)||!Q(g.publicKey))return Reflect.apply(D,v,O);let z=g.publicKey,u=`wa-${++k}`,m=Reflect.apply(D,v,O),l=Q(g.signal)?g.signal:void 0,f=be(h,g,z),W={...f,ceremonyId:u,phase:"started",userActivation:Re(n)};if(f.kind==="authentication"){let w=z;W.requestOptionsJson=ke(w,t)}return B(W),Promise.resolve(m).then(w=>(y&&B({...f,ceremonyId:u,phase:"completed",credentialJson:Ce(w,h,t),...(l==null?void 0:l.aborted)===!0?{aborted:!0}:{}}),w),w=>{throw y&&B({...f,ceremonyId:u,phase:"failed",error:w,errorName:Ae(w),...(l==null?void 0:l.aborted)===!0?{aborted:!0}:{}}),w})}}.invoke;return X(I,{name:h,length:0})},x=(h,D)=>{let I=q(h,D),O=Object.getOwnPropertyDescriptor(r,h);if(O&&(!("value"in O)||O.writable!==!0))return null;try{r[h]=I}catch{return null}return()=>{r[h]===I&&(O?r[h]=O.value:delete r[h])}},S=x("get",a),N=S?x("create",p):null;return!S||!N?(S==null||S(),t.log("webauthn capture unavailable: credentials methods cannot be patched safely"),C()):(y=!0,{mode:"standard",disconnect:()=>{y=!1,S(),N()}})}function be(e,t,o){let n=ve(t.mediation);if(e==="create")return{kind:"registration",mediation:n};let r=o;return{kind:"authentication",mediation:n,uiMode:we(t.uiMode),credentialSet:Array.isArray(r.allowCredentials)&&r.allowCredentials.length>0?"allowlisted":"discoverable"}}function ve(e){return e===void 0?"optional":e==="silent"||e==="optional"||e==="conditional"||e==="required"?e:"unknown"}function we(e){return e===void 0?"default":e==="immediate"?"immediate":"unknown"}function Re(e){let t=e.userActivation;return typeof(t==null?void 0:t.isActive)=="boolean"?t.isActive:void 0}function Ce(e,t,o){var n;if(e)try{let r=e,a=r.response,p={id:J(r.id),rawId:K(r.rawId),type:J(r.type),authenticatorAttachment:J(r.authenticatorAttachment),response:t==="create"?{clientDataJSON:K(a==null?void 0:a.clientDataJSON),attestationObject:K(a==null?void 0:a.attestationObject),transports:ne((n=a==null?void 0:a.getTransports)==null?void 0:n.call(a))}:{clientDataJSON:K(a==null?void 0:a.clientDataJSON),authenticatorData:K(a==null?void 0:a.authenticatorData),signature:K(a==null?void 0:a.signature),userHandle:K(a==null?void 0:a.userHandle)},...t==="create"?Oe(r):{}};return JSON.stringify(p)}catch(r){o.log("credential serialization failed",r);return}}function ke(e,t){try{let o={},n=J(e.rpId),r=J(e.userVerification),a=ne(e.hints);return n!==void 0&&(o.rpId=n),typeof e.timeout=="number"&&Number.isFinite(e.timeout)&&(o.timeout=e.timeout),r!==void 0&&(o.userVerification=r),a!==void 0&&(o.hints=a),o.challenge=K(e.challenge),Array.isArray(e.allowCredentials)&&(o.allowCredentials=qe(e.allowCredentials)),JSON.stringify(o)}catch(o){t.log("request options serialization failed",o);return}}function qe(e){let t=[];for(let o of e){if(!Q(o))continue;let n=o;t.push({type:J(n.type),id:K(n.id),transports:ne(n.transports)})}return t}function ne(e){if(Array.isArray(e))return e.filter(t=>typeof t=="string")}function J(e){return typeof e=="string"?e:void 0}function Oe(e){var n;let t=(n=e.getClientExtensionResults)==null?void 0:n.call(e);if(!Q(t))return{};let o=t.credProps;return!o||typeof o.rk!="boolean"?{}:{clientExtensionResults:{credProps:{rk:o.rk}}}}function K(e){let t;if(e instanceof ArrayBuffer)t=new Uint8Array(e);else if(ArrayBuffer.isView(e))t=new Uint8Array(e.buffer,e.byteOffset,e.byteLength);else return;let o="";for(let n of t)o+=String.fromCharCode(n);return btoa(o).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Q(e){return e!==null&&(typeof e=="object"||typeof e=="function")}function Ae(e){return e instanceof DOMException?e.name:"UnknownError"}var se=0;function ue(){return se+=1,`network-${se}`}function Pe(e,t){let o=e.responseType;if(o===""||o==="text")return t(e.responseText);if(o==="json"){let n=e.response;return n==null?void 0:t(n)}}var Ee=500,ce=Symbol("body-read-timeout");function de(e){return/event-stream|octet-stream/i.test(e)?!1:/json|text|urlencoded/i.test(e)}async function le(e,t){let o=e.body;if(!o)return;let n=o.getReader(),r;try{let a=new Promise(k=>{r=setTimeout(()=>k(ce),Ee)}),p=new TextDecoder,y=[];for(;;){let k=await Promise.race([n.read(),a]);if(k===ce){t.log("body omitted: read timed out"),n.cancel().catch(()=>{});return}if(k.done)break;y.push(p.decode(k.value,{stream:!0}))}return y.push(p.decode()),y.join("")}catch{n.cancel().catch(()=>{});return}finally{r!==void 0&&clearTimeout(r)}}function re(e){var t;(t=e==null?void 0:e.body)==null||t.cancel().catch(()=>{})}function oe(e,t,o,n,r){try{let a=Object.getOwnPropertyDescriptor(e,t);xe(a,{...n,value:o})&&Object.defineProperty(e,t,n)}catch(a){r.log(`${String(t)} restoration skipped`,a)}}function xe(e,t){return!!e&&"value"in e&&"value"in t&&e.value===t.value&&e.writable===t.writable&&e.enumerable===t.enumerable&&e.configurable===t.configurable}function Se(e){var v,g,z;let{onExchange:t,onRequest:o,match:n}=e;if(typeof t!="function"||typeof n!="function")throw new TypeError("captureNetwork requires onExchange and match callbacks");let r=U(e.logger),a=(v=e.transports)!=null?v:["xhr","fetch"],p=(g=e.bodyCapture)==null?void 0:g.request,y=(z=e.bodyCapture)==null?void 0:z.response,k=typeof p=="function",B=typeof y=="function",q=!1,x=$(r,"network",u=>{if(q)return t(u)}),S=$(r,"network request",u=>{if(q)return o==null?void 0:o(u)}),N=(u,m,l)=>{if(typeof l=="function")try{return l(u,m)}catch(f){r.log("body projection failed",f);return}},h=(u,m)=>{try{let l=typeof document!="undefined"?document.baseURI:void 0,f=new URL(String(u),l);return n(f,m)===!0?{url:f.href,path:f.pathname}:void 0}catch{return}},D=()=>{if(typeof XMLHttpRequest=="undefined")return r.log("xhr capture unavailable: XMLHttpRequest missing"),null;let u=XMLHttpRequest.prototype,m=Object.getOwnPropertyDescriptor(u,"open"),l=Object.getOwnPropertyDescriptor(u,"send");if(!m||!("value"in m)||typeof m.value!="function"||!l||!("value"in l)||typeof l.value!="function")return r.log("xhr capture unavailable: XMLHttpRequest methods missing"),null;let f=m.value,W=l.value;if(_(f)||_(W))return r.log("xhr capture unavailable: an equivalent capture is already active"),null;let w=new WeakMap,R=new Set,P=!1,L={open(...b){var c;let i=Reflect.apply(f,this,b);if(!q||!P)return i;try{let E=w.get(this);E&&(E.reported=!0,(c=E.cleanupListeners)==null||c.call(E));let j=String(b[0]).toUpperCase(),A=h(b[1],j);A?w.set(this,{method:j,url:A.url,path:A.path,reported:!1}):w.delete(this)}catch(E){r.log("xhr open observation failed",E)}return i}}.open,F={send(...b){let i=q&&P?w.get(this):void 0;if(!i||i.reported||i.cleanupListeners)return Reflect.apply(W,this,b);let c=()=>{},E=!1;try{i.requestId=ue();let A={direction:"request",transport:"xhr",method:i.method,url:i.url,path:i.path};i.requestBody=k&&typeof b[0]=="string"?N(b[0],A,p):void 0;let G=()=>{i.outcome===void 0&&(i.outcome="abort")},Y=()=>{i.outcome===void 0&&(i.outcome="timeout")},s=()=>{i.outcome===void 0&&(i.outcome="error")},d=()=>{var V;c();try{if(!q||!P||i.reported||!i.requestId)return;i.reported=!0;let T={direction:"response",transport:"xhr",method:i.method,url:i.url,path:i.path},te=this.status,ie={requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,status:te,ok:te>=200&&te<300,outcome:(V=i.outcome)!=null?V:"load",durationMs:i.startedAt!==void 0?Date.now()-i.startedAt:void 0,responseUrl:this.responseURL||void 0,requestBody:i.requestBody,responseBody:B?Pe(this,ye=>N(ye,T,y)):void 0};i.requestEmitted?x(ie):i.pendingExchange=ie}catch(T){r.log("xhr exchange observation failed",T)}finally{w.get(this)===i&&w.delete(this)}},H=()=>{try{this.readyState===XMLHttpRequest.DONE&&this.status!==0&&d()}catch(V){r.log("xhr ready-state observation failed",V)}},M=()=>d();c=()=>{for(let[V,T]of[["readystatechange",H],["abort",G],["timeout",Y],["error",s],["loadend",M]])this.removeEventListener(V,T);i.cleanupListeners===c&&(i.cleanupListeners=void 0),R.delete(c)},this.addEventListener("readystatechange",H),this.addEventListener("abort",G),this.addEventListener("timeout",Y),this.addEventListener("error",s),this.addEventListener("loadend",M),i.cleanupListeners=c,R.add(c),i.startedAt=Date.now(),E=!0}catch(A){r.log("xhr send observation failed",A),c()}let j;try{j=Reflect.apply(W,this,b)}catch(A){throw c(),A}if(E&&i.requestId!==void 0&&i.startedAt!==void 0){i.requestEmitted=!0,S({requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,startedAt:i.startedAt,requestBody:i.requestBody});let A=i.pendingExchange;i.pendingExchange=void 0,A&&x(A)}return j}}.send;return X(L,f),X(F,W),m.writable!==!0||l.writable!==!0?(r.log("xhr capture unavailable: XMLHttpRequest methods are not writable"),null):(Object.defineProperty(u,"open",{...m,value:L}),Object.defineProperty(u,"send",{...l,value:F}),P=!0,()=>{P=!1;for(let b of[...R])b();oe(u,"open",L,m,r),oe(u,"send",F,l,r)})},I=()=>{let u=globalThis,m=Object.getOwnPropertyDescriptor(u,"fetch");if(!m||!("value"in m))return r.log("fetch capture unavailable: fetch is not an own data property"),null;let l=m.value;if(typeof l!="function")return r.log("fetch capture unavailable: fetch missing"),null;if(_(l))return r.log("fetch capture unavailable: an equivalent capture is already active"),null;let f=!1,W=async(R,P,L,F)=>{try{if(!q||!f){re(L);return}let[b,i]=await Promise.all([F!=null?F:Promise.resolve(void 0),L?le(L,r):Promise.resolve(void 0)]);if(!q||!f)return;let c={direction:"request",transport:"fetch",method:R.method,url:R.url,path:R.path},E={...c,direction:"response"},j=R.requestBody;j===void 0&&b!==void 0&&(j=N(b,c,p));let A=i!==void 0?N(i,E,y):void 0;x({requestId:R.requestId,transport:"fetch",method:R.method,url:R.url,path:R.path,status:P.status,ok:P.ok,outcome:"load",durationMs:Date.now()-R.startedAt,responseUrl:P.url||void 0,requestBody:j,responseBody:A})}catch(b){r.log("fetch exchange observation failed",b)}},w={fetch(...R){var j,A,G,Y;let P;try{let s=R[0],d=R[1];if(q&&f&&k&&typeof Request!="undefined"&&s instanceof Request&&(d==null?void 0:d.body)===void 0&&s.body){let H=String((j=d==null?void 0:d.method)!=null?j:s.method).toUpperCase(),M=(A=s.headers.get("content-type"))!=null?A:"";h(s.url,H)&&de(M)&&(P=s.clone())}}catch(s){r.log("fetch observation failed",s)}let L,F=Date.now();try{L=Reflect.apply(l,this,R)}catch(s){throw re(P),s}let b,i;try{if(q&&f){let s=R[0],d=R[1],H=typeof Request!="undefined"&&s instanceof Request,M=String((G=d==null?void 0:d.method)!=null?G:H?s.method:"GET").toUpperCase(),V=H?s.url:s,T=h(V,M);T&&(b={requestId:ue(),method:M,url:T.url,path:T.path,startedAt:F},i=(Y=d==null?void 0:d.signal)!=null?Y:H?s.signal:void 0,k&&typeof(d==null?void 0:d.body)=="string"&&(b.requestBody=N(d.body,{direction:"request",transport:"fetch",method:M,url:T.url,path:T.path},p)))}}catch(s){r.log("fetch observation failed",s)}if(!b)return re(P),L;let c=b;S({requestId:c.requestId,transport:"fetch",method:c.method,url:c.url,path:c.path,startedAt:c.startedAt,requestBody:c.requestBody});let E=P?le(P,r):void 0;return L.then(s=>{var H;let d;try{if(q&&f&&B){let M=(H=s.headers.get("content-type"))!=null?H:"";de(M)&&s.body&&(d=s.clone())}}catch{}W(c,s,d,E)},s=>{try{if(!q||!f)return;let d=(i==null?void 0:i.aborted)===!0||(s==null?void 0:s.name)==="AbortError"||typeof DOMException!="undefined"&&s instanceof DOMException&&s.code===DOMException.ABORT_ERR;x({requestId:c.requestId,transport:"fetch",method:c.method,url:c.url,path:c.path,status:0,ok:!1,outcome:d?"abort":"error",durationMs:Date.now()-c.startedAt})}catch(d){r.log("fetch failure observation failed",d)}}),L}}.fetch;return X(w,l),m.writable!==!0?(r.log("fetch capture unavailable: fetch is not writable"),null):(Object.defineProperty(u,"fetch",{...m,value:w}),f=!0,()=>{f=!1,oe(u,"fetch",w,m,r)})},O=[];if(a.includes("xhr")){let u=D();u&&O.push(u)}if(a.includes("fetch")){let u=I();u&&O.push(u)}return O.length===0?C():(q=!0,{mode:"standard",disconnect:()=>{q=!1;for(let u of O.splice(0))u()}})}function Ne(e){let t=U(),o;try{if(t=U(e.logger),o=e.onNavigate,typeof o!="function")return C()}catch{return C()}let n,r,a,p,y,k;try{if(n=window,r=n==null?void 0:n.history,!n||!r)return t.log("navigation capture unavailable: window/history missing"),C();if(y=pe(r,"pushState"),k=pe(r,"replaceState"),!y||!k)return t.log("navigation capture unavailable: history method descriptors missing"),C();if(a=y.descriptor.value,p=k.descriptor.value,typeof a!="function"||typeof p!="function")return t.log("navigation capture unavailable: history methods missing"),C();if(typeof n.addEventListener!="function"||typeof n.removeEventListener!="function")return t.log("navigation capture unavailable: window event methods missing"),C()}catch(v){return t.log("navigation capture unavailable: history inspection failed",v),C()}if(_(a)||_(p))return t.log("navigation capture unavailable: an equivalent capture is already active"),C();let B=!1,q=()=>({path:n.location.pathname,search:n.location.search,hash:n.location.hash}),x;try{x=q()}catch(v){return t.log("navigation capture unavailable: location inspection failed",v),C()}let S=$(t,"navigation",v=>{if(B)return o(v)}),N=v=>{if(B)try{let g=q();if(g.path===x.path&&g.search===x.search&&g.hash===x.hash)return;x=g,S({...g,source:v})}catch(g){t.log("navigation observation failed",g)}},h={pushState(...v){let g=Reflect.apply(a,this,v);return N("push"),g}}.pushState,D={replaceState(...v){let g=Reflect.apply(p,this,v);return N("replace"),g}}.replaceState;X(h,a),X(D,p);let I=()=>N("pop"),O=()=>N("hash");try{if(fe(y,h),fe(k,D),r.pushState!==h||r.replaceState!==D)throw new Error("history method replacement did not take effect");n.addEventListener("popstate",I),n.addEventListener("hashchange",O),B=!0}catch(v){return B=!1,ee(n,"popstate",I,t),ee(n,"hashchange",O,t),Z(k,D,t),Z(y,h,t),t.log("navigation capture unavailable: history methods are not writable",v),o=()=>{},S=()=>{},t=U(),C()}return{mode:"standard",disconnect:()=>{B=!1,o=()=>{},ee(n,"popstate",I,t),ee(n,"hashchange",O,t),Z(y,h,t),Z(k,D,t),S=()=>{},t=U()}}}function pe(e,t){let o=e;for(;o;){let n=Object.getOwnPropertyDescriptor(o,t);if(n)return!("value"in n)||typeof n.value!="function"?null:{owner:o,key:t,descriptor:n};o=Object.getPrototypeOf(o)}return null}function fe(e,t){if(e.descriptor.writable!==!0)throw new TypeError(`${e.key} is not writable`);let o=Object.getOwnPropertyDescriptor(e.owner,e.key);if(!he(o,e.descriptor))throw new Error(`${e.key} changed during installation`);Object.defineProperty(e.owner,e.key,{...e.descriptor,value:t})}function he(e,t){return!!e&&"value"in e&&"value"in t&&e.value===t.value&&e.writable===t.writable&&e.enumerable===t.enumerable&&e.configurable===t.configurable}function Z(e,t,o){try{let n=Object.getOwnPropertyDescriptor(e.owner,e.key);he(n,{...e.descriptor,value:t})&&Object.defineProperty(e.owner,e.key,e.descriptor)}catch(n){o.log(`navigation ${e.key} restoration skipped`,n)}}function ee(e,t,o,n){try{e.removeEventListener(t,o)}catch(r){n.log(`navigation ${t} listener removal skipped`,r)}}function Be(e){let t=e.composedPath();return t.length>0?t:e.target?[e.target]:[]}var De="0.2.0-next.9-c4893f6";export{De as VERSION,Ne as captureNavigation,Se as captureNetwork,me as captureWebAuthn,Be as composedPath};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@corbado/autocapture",
3
- "version": "0.1.0",
4
- "description": "Observer-style capture helpers for browser authentication signals: WebAuthn ceremonies, selected network exchanges, and in-app navigation.",
3
+ "version": "0.2.0-next.9-c4893f6",
4
+ "description": "Observer-style capture helpers for WebAuthn ceremonies, selected network exchanges, in-app navigation, and composed event paths.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
7
7
  "types": "./dist/index.d.ts",