@corbado/autocapture 0.1.0-next.2-dd9eb36

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 ADDED
@@ -0,0 +1,174 @@
1
+ # @corbado/autocapture
2
+
3
+ Observe browser authentication signals without changing application code.
4
+
5
+ `@corbado/autocapture` provides small, focused observers for the browser APIs that authentication
6
+ flows run through. Each observer reports a well-defined stream of events while leaving the
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.
11
+
12
+ The package observes five kinds of signal:
13
+
14
+ - **WebAuthn ceremonies** — the lifecycle of `navigator.credentials.get`/`create` passkey
15
+ ceremonies, from invocation to settlement, including silent cancellations.
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.
19
+ - **In-app navigation** — same-document (SPA) route changes.
20
+ - **Shadow roots** — open or closed roots attached after capture starts, plus discoverable existing
21
+ open roots when explicitly requested.
22
+ - **Rendered UI state** — caller-owned semantic snapshots read after selected UI events or
23
+ imperative triggers settle.
24
+
25
+ It has no runtime dependencies, ships TypeScript type definitions, and targets ES2019 so it parses
26
+ on older engines.
27
+
28
+ ## Installation
29
+
30
+ ```sh
31
+ npm install @corbado/autocapture
32
+ ```
33
+
34
+ ## Usage
35
+
36
+ Every observer follows the platform observer pattern (`MutationObserver`, `ResizeObserver`): create
37
+ it with a callback, receive events, and call `disconnect()` when finished.
38
+
39
+ ```ts
40
+ import {
41
+ captureWebAuthn,
42
+ captureNetwork,
43
+ captureNavigation,
44
+ captureShadowRoots,
45
+ } from "@corbado/autocapture";
46
+
47
+ const webauthn = captureWebAuthn({
48
+ onEvent: (event) => console.log(event.kind, event.phase, event.ceremonyId),
49
+ });
50
+
51
+ const network = captureNetwork({
52
+ match: (url) => url.origin === location.origin && url.pathname.startsWith("/auth-api/"),
53
+ bodyCapture: {
54
+ request: (body) => {
55
+ const value = typeof body === "string" ? JSON.parse(body) : body;
56
+ return value && typeof value === "object" ? { type: "type" in value ? value.type : undefined } : undefined;
57
+ },
58
+ response: (body) => {
59
+ const value = typeof body === "string" ? JSON.parse(body) : body;
60
+ return value && typeof value === "object" ? { status: "status" in value ? value.status : undefined } : undefined;
61
+ },
62
+ },
63
+ onRequest: (request) => console.log(request.requestId, request.method, request.path),
64
+ onExchange: (exchange) => console.log(exchange.method, exchange.path, exchange.status, exchange.outcome),
65
+ });
66
+
67
+ const navigation = captureNavigation({
68
+ onNavigate: (event) => console.log(event.source, event.path),
69
+ });
70
+
71
+ const shadowRoots = captureShadowRoots({
72
+ onAttached: ({ host, shadowRoot, mode }) => console.log(host, shadowRoot, mode),
73
+ });
74
+
75
+ // when finished
76
+ webauthn.disconnect();
77
+ network.disconnect();
78
+ navigation.disconnect();
79
+ shadowRoots.disconnect();
80
+ ```
81
+
82
+ Each call returns a `CaptureHandle` with a mode (`"standard"` or `"unavailable"`) and a
83
+ `disconnect()` method. Environments that cannot be instrumented report `"unavailable"` rather than
84
+ throwing; a misconfigured `captureNetwork` call (missing `match` or `onExchange`) throws a
85
+ `TypeError` instead, since that is a programming error, not a degraded environment. Full event and
86
+ option types ship with the package.
87
+
88
+ ## Safety invariants
89
+
90
+ The observers run inside application call paths, so their behaviour is defined by what they promise
91
+ never to disturb:
92
+
93
+ - **Exact host delegation.** Patched methods call the delegate once with the original receiver and
94
+ argument vector, and preserve its return value and synchronous exception. Non-WebAuthn Credential
95
+ Management calls are literal pass-throughs. Two Promise-observation choices are unavoidable:
96
+ public-key ceremonies return a derived Promise (identity and microtask timing change), and fetch
97
+ settlement is observed on the platform Promise itself, so an ignored failed fetch no longer
98
+ surfaces as an `unhandledrejection`.
99
+ - **Observation is firewalled.** Synchronous callback, projector, serializer, and logger faults
100
+ are contained; rejected asynchronous event callbacks are consumed and logged. The firewall covers
101
+ first-party faults on a real browser engine — hostile polyfills and patched built-ins are outside
102
+ the threat model.
103
+ - **Coexistence over ownership.** Existing delegates are preserved, slots are restored only while
104
+ still owned by the capture, and later wrappers are never overwritten. A buried wrapper becomes an
105
+ inert pass-through after disconnect. Locked WebAuthn methods are never bypassed through a container
106
+ proxy; the entire WebAuthn attachment is rolled back and reported as unavailable.
107
+ - **Exactly-once reporting.** Each patched surface holds a single capture wrapper, so one
108
+ application action passes through observation once. A later integration that overwrites a wrapped
109
+ method without forwarding through it displaces capture for that surface: the application call
110
+ still succeeds, telemetry is lost. A package wrapper hidden below an unknown third-party wrapper
111
+ cannot currently be detected by another copy.
112
+ - **Conditional removal.** `disconnect()` stops delivery immediately, releases active listeners, and
113
+ restores only slots still owned by the capture. If another integration sits above it, the inert
114
+ pass-through is deliberately left in that chain.
115
+
116
+ These are implementation invariants, not a claim that monkey-patching is undetectable or has zero
117
+ timing, memory, privacy, or Promise-semantics impact.
118
+
119
+ ## Data handling
120
+
121
+ Network observation is designed for minimal data exposure:
122
+
123
+ - Only URLs a caller's `match` predicate explicitly selects are observed; all other traffic is
124
+ ignored. The Corbado `/observe/events/{projectID}` sink is always excluded to prevent recursion.
125
+ - Requests and exchanges carry **metadata only** by default. `onRequest` runs after the platform
126
+ accepts the call; `onExchange` reports its eventual outcome, and their `requestId` values match. A
127
+ request or response body is read only when the caller configures a projector for that direction.
128
+ - Projected XHR request bodies are available at request time. For `fetch(new Request(...))`, reading
129
+ the request clone is asynchronous, so the request body may be absent from `onRequest` and present
130
+ only on `onExchange`; string bodies passed through `RequestInit` are available synchronously.
131
+ - Autocapture performs no generic body parsing or redaction. The endpoint-specific projector is the
132
+ privacy boundary: it receives the matched raw body and must return only the fields the consumer is
133
+ allowed to observe. Only that projected value is attached to the exchange. Projector failures,
134
+ promises, `undefined`, oversized output, and non-JSON output fail closed by omitting the body.
135
+ - Projected outputs are size-bounded (`maxBodyChars`, on the JSON-serialized projection); raw bodies
136
+ are not size-checked, since matched traffic is first-party. Readable bodies come from isolated
137
+ clones read within a 500 ms window; the source body is never directly consumed, and streaming and
138
+ binary payloads are never read. Body cloning still has inherent stream tee/backpressure costs, so
139
+ enabling it is a security and performance decision.
140
+
141
+ Network metadata currently includes the resolved full URL and final response URL. Query strings can
142
+ contain secrets or PII; callers should restrict `match` accordingly until a URL-redaction policy is
143
+ chosen.
144
+
145
+ Navigation events likewise contain the raw `search` and `hash`. OAuth responses, password-reset
146
+ links, and application routes can put secrets or PII there; no navigation redaction policy is
147
+ currently applied.
148
+
149
+ WebAuthn responses use an explicit serializer rather than `credential.toJSON()`. The allowlist keeps
150
+ the assertion and attestation fields the Observe backend parses for credential, authenticator,
151
+ backup-state, AAGUID, transport, and discoverability insight. Client-extension output is limited to
152
+ `credProps.rk`; PRF and unrelated extension results are omitted. The payload therefore still contains
153
+ credential IDs, user handles, challenges, signatures, authenticator data, and attestation material.
154
+
155
+ The tested 1Password browser extension exposes locked `get` and `create` accessors. WebAuthn capture
156
+ therefore reports `mode: "unavailable"`; passkey operations continue through 1Password unchanged but
157
+ produce no autocaptured WebAuthn events. Password managers that wrap the writable methods before
158
+ autocapture are delegated to; one that assigns its wrapper afterwards keeps working, and its
159
+ ceremonies stay observed when the wrapper forwards through the method it replaced (the surveyed
160
+ managers do). A later assignment that does not forward displaces WebAuthn telemetry.
161
+
162
+ Shadow capture can retain access to closed roots that the page normally cannot rediscover. Treat the
163
+ callback as a privileged capability and do not enable `includeExisting` without accepting a
164
+ synchronous full-DOM walk.
165
+
166
+ ## Runtime support
167
+
168
+ The published bundle targets ES2019 and assumes ES2019-era runtime primitives such as `Promise`,
169
+ `WeakMap`, `WeakSet`, `Symbol`, `Reflect`, and `URL`. Body capture additionally relies on the
170
+ Streams API and `TextDecoder`; these are part of the assumed baseline, not feature-detected at
171
+ runtime. The current real-browser suite covers current
172
+ Chromium, Firefox, and WebKit, with password-manager simulation and virtual WebAuthn in Chromium.
173
+ That is not yet evidence for old iOS, Android WebView, legacy Edge, or arbitrary polyfills; expanding
174
+ the declared browser floor and test matrix is an open release decision.
@@ -0,0 +1,378 @@
1
+ /** Receives optional diagnostics. Capture functions remain silent when no logger is provided. */
2
+ type Logger = {
3
+ /** Records a non-fatal diagnostic message. */
4
+ log: (...args: unknown[]) => void;
5
+ };
6
+ /** Controls a capture session. */
7
+ type CaptureHandle = {
8
+ /**
9
+ * Result of starting the capture.
10
+ *
11
+ * - `"standard"`: capture started normally.
12
+ * - `"unavailable"`: capture could not be started and will not emit events.
13
+ */
14
+ readonly mode: "standard" | "unavailable";
15
+ /** Stops future event delivery. Safe to call more than once. */
16
+ disconnect(): void;
17
+ };
18
+ /** WebAuthn operation that initiated a ceremony. */
19
+ type WebAuthnCeremonyKind = "get" | "create";
20
+ /**
21
+ * A lifecycle event for a WebAuthn ceremony initiated through
22
+ * `navigator.credentials.get()` or `navigator.credentials.create()`.
23
+ */
24
+ type WebAuthnCeremonyEvent = {
25
+ /** WebAuthn operation that initiated the ceremony. */
26
+ kind: WebAuthnCeremonyKind;
27
+ /** Identifier shared by all events belonging to the same ceremony. */
28
+ ceremonyId: string;
29
+ /** Current ceremony lifecycle phase. */
30
+ phase: "started" | "completed" | "failed";
31
+ /** Requested mediation mode, when available. */
32
+ mediation?: string;
33
+ /** Whether transient user activation was active when the ceremony started. Present on `started` events when exposed by the browser. */
34
+ userActivation?: boolean;
35
+ /**
36
+ * JSON-encoded request metadata for a `get` ceremony: `rpId`, `timeout`,
37
+ * `userVerification`, `hints`, `challenge`, and `allowCredentials`
38
+ * (`type`, `id`, and `transports`). Binary values are base64url-encoded.
39
+ * Unavailable fields are omitted. Present on `started` events when
40
+ * serialization succeeds. Treat this value as sensitive telemetry.
41
+ */
42
+ requestOptionsJson?: string;
43
+ /** Whether a `get` request included at least one allowed credential. Present on `started` events. */
44
+ hasAllowCredentials?: boolean;
45
+ /**
46
+ * JSON-encoded, allowlisted credential returned by a completed ceremony.
47
+ * Common fields are `id`, `rawId`, `type`, and `authenticatorAttachment`.
48
+ * Assertion responses include `clientDataJSON`, `authenticatorData`,
49
+ * `signature`, and `userHandle`; attestation responses include
50
+ * `clientDataJSON`, `attestationObject`, and `transports`. Binary values are
51
+ * base64url-encoded. Of the client-extension results, only `credProps.rk` may
52
+ * be included. Present when serialization succeeds. Treat this value as
53
+ * sensitive telemetry.
54
+ */
55
+ credentialJson?: string;
56
+ /** Rejection value from a failed ceremony. This may contain application-specific data. */
57
+ error?: unknown;
58
+ /** Error name for a failed ceremony, or `UnknownError` when no DOM exception name is available. */
59
+ errorName?: string;
60
+ /**
61
+ * `true` when the request signal was observed as aborted before settlement.
62
+ * A credential manager may still complete such a ceremony, so this can be
63
+ * present on either a `completed` or `failed` event.
64
+ */
65
+ aborted?: boolean;
66
+ };
67
+ /** Options for {@link captureWebAuthn}. */
68
+ type WebAuthnCaptureOptions = {
69
+ /** Receives WebAuthn lifecycle events. Return values are not awaited. */
70
+ onEvent: (event: WebAuthnCeremonyEvent) => void;
71
+ /** Optional diagnostic logger. */
72
+ logger?: Logger;
73
+ };
74
+ /** Network API used for an observed exchange. */
75
+ type NetworkTransport = "xhr" | "fetch";
76
+ /** A matched network request immediately after the platform accepted it. */
77
+ type HttpRequestStarted = {
78
+ /** Identifier shared with the eventual {@link HttpExchange}. */
79
+ requestId: string;
80
+ /** Network API used for the request. */
81
+ transport: NetworkTransport;
82
+ /** Uppercase HTTP method. */
83
+ method: string;
84
+ /** Resolved absolute request input URL. */
85
+ url: string;
86
+ /** Pathname component of the request URL. */
87
+ path: string;
88
+ /** Timestamp captured at the request boundary. */
89
+ startedAt: number;
90
+ /** Request-body value returned by the configured projector, when synchronously available. */
91
+ requestBody?: unknown;
92
+ };
93
+ /** An observed network request and its outcome. */
94
+ type HttpExchange = {
95
+ /** Identifier shared with the corresponding {@link HttpRequestStarted}. */
96
+ requestId: string;
97
+ /** Network API used for the request. */
98
+ transport: NetworkTransport;
99
+ /** Uppercase HTTP method. */
100
+ method: string;
101
+ /**
102
+ * Resolved absolute request input URL, including its query string and
103
+ * fragment. Redact it before forwarding if either component may be sensitive.
104
+ */
105
+ url: string;
106
+ /** Pathname component of the request URL. */
107
+ path: string;
108
+ /** HTTP status code, or `0` when a status is unavailable. */
109
+ status: number;
110
+ /** Whether the status code represents a successful response. */
111
+ ok: boolean;
112
+ /**
113
+ * How the request completed. `load` includes successful and non-successful
114
+ * HTTP responses; inspect `ok` or `status` for HTTP success.
115
+ */
116
+ outcome: "load" | "abort" | "timeout" | "error";
117
+ /** Elapsed request time in milliseconds, when available. */
118
+ durationMs?: number;
119
+ /**
120
+ * Final absolute response URL, when available. This may differ from `url`
121
+ * after redirects and may contain sensitive query or fragment values.
122
+ */
123
+ responseUrl?: string;
124
+ /** Request-body value returned by the configured projector, when available. */
125
+ requestBody?: unknown;
126
+ /** Response-body value returned by the configured projector, when available. */
127
+ responseBody?: unknown;
128
+ };
129
+ /** Metadata supplied to a body projector for one matched exchange. */
130
+ type NetworkBodyProjectionContext = {
131
+ /** Whether the observed body belongs to the request or response. */
132
+ direction: "request" | "response";
133
+ /** Network API used by the application. */
134
+ transport: NetworkTransport;
135
+ /** Uppercase HTTP method. */
136
+ method: string;
137
+ /** Resolved absolute request URL. */
138
+ url: string;
139
+ /** Pathname component of the request URL. */
140
+ path: string;
141
+ };
142
+ /**
143
+ * Synchronously selects the only body data that may leave network capture.
144
+ * Textual bodies are supplied as strings; an XHR with `responseType="json"`
145
+ * supplies the browser-parsed value. Returning `undefined`, throwing, or
146
+ * returning a Promise omits the body without affecting the application.
147
+ */
148
+ type NetworkBodyProjector = (body: unknown, context: NetworkBodyProjectionContext) => unknown;
149
+ /** Options for {@link captureNetwork}. */
150
+ type NetworkCaptureOptions = {
151
+ /**
152
+ * Selects requests to observe. The URL is resolved against the document base
153
+ * URL and the method is uppercase. Return `true` to observe the request. The
154
+ * Corbado Observe event endpoint is always excluded to prevent recursion.
155
+ * This callback must return synchronously; Promise results are not matched.
156
+ */
157
+ match: (url: URL, method: string) => boolean;
158
+ /** Receives completed network exchanges. Return values are not awaited. */
159
+ onExchange: (exchange: HttpExchange) => void;
160
+ /**
161
+ * Receives matched requests after the platform call succeeds. The request ID
162
+ * matches the eventual exchange. Return values are not awaited.
163
+ */
164
+ onRequest?: (request: HttpRequestStarted) => void;
165
+ /**
166
+ * Network APIs to observe. Defaults to both `xhr` and `fetch`. Unavailable
167
+ * transports are skipped if at least one requested transport can be observed.
168
+ */
169
+ transports?: readonly NetworkTransport[];
170
+ /**
171
+ * Best-effort body observation, enabled independently per direction by a
172
+ * mandatory projector. Without a projector, that direction is never read.
173
+ * Autocapture performs no generic parsing or redaction: the projector owns
174
+ * data minimization and its return value is the only body data reported.
175
+ */
176
+ bodyCapture?: {
177
+ /** Projects a bounded request body. */
178
+ request?: NetworkBodyProjector;
179
+ /** Projects a bounded response body. */
180
+ response?: NetworkBodyProjector;
181
+ };
182
+ /**
183
+ * Maximum size of a projected body attached to a request or exchange,
184
+ * measured on its JSON-serialized form; larger projections are omitted.
185
+ * Raw bodies are not size-checked before projection — matched traffic is
186
+ * first-party and assumed well-behaved. Must be a finite, non-negative
187
+ * number when provided. Defaults to 65,536.
188
+ */
189
+ maxBodyChars?: number;
190
+ /** Optional diagnostic logger. */
191
+ logger?: Logger;
192
+ };
193
+ /** Mechanism that produced a navigation event. */
194
+ type NavigationSource = "push" | "replace" | "pop" | "hash";
195
+ /** A same-document navigation. */
196
+ type NavigationEvent = {
197
+ /** Current URL pathname. */
198
+ path: string;
199
+ /** Current URL query string, including the leading `?` when present. May contain sensitive data. */
200
+ search: string;
201
+ /** Current URL fragment, including the leading `#` when present. May contain sensitive data. */
202
+ hash: string;
203
+ /** Mechanism that produced the event. */
204
+ source: NavigationSource;
205
+ };
206
+ /** Options for {@link captureNavigation}. */
207
+ type NavigationCaptureOptions = {
208
+ /** Receives same-document navigation events. Return values are not awaited. */
209
+ onNavigate: (event: NavigationEvent) => void;
210
+ /** Optional diagnostic logger. */
211
+ logger?: Logger;
212
+ };
213
+ /** A shadow root reported by {@link captureShadowRoots}. */
214
+ type ShadowRootAttachedEvent = {
215
+ /** Element that owns the shadow root. */
216
+ host: Element;
217
+ /**
218
+ * Attached shadow root. For a closed root, `host.shadowRoot` remains `null`;
219
+ * retain this reference if later access is required.
220
+ */
221
+ shadowRoot: ShadowRoot;
222
+ /** Shadow root mode. */
223
+ mode: "open" | "closed";
224
+ };
225
+ /** Options for {@link captureShadowRoots}. */
226
+ type ShadowRootCaptureOptions = {
227
+ /** Receives shadow roots synchronously. Return values are not awaited. */
228
+ onAttached: (event: ShadowRootAttachedEvent) => void;
229
+ /**
230
+ * Also reports open shadow roots that already exist when capture starts.
231
+ * These callbacks may run before `captureShadowRoots()` returns. Existing
232
+ * closed roots cannot be discovered. Defaults to `false`.
233
+ */
234
+ includeExisting?: boolean;
235
+ /** Optional diagnostic logger. */
236
+ logger?: Logger;
237
+ };
238
+ /** Event that should schedule a rendered UI-state read. */
239
+ type UiStateTrigger = {
240
+ target: EventTarget;
241
+ type: string;
242
+ capture?: boolean;
243
+ };
244
+ /** A capture handle that also accepts imperative triggers such as completed network exchanges. */
245
+ type UiStateCaptureHandle = CaptureHandle & {
246
+ /** Coalesces a state read onto the next task, after the application's synchronous layout work. */
247
+ schedule(): void;
248
+ };
249
+ /** Options for {@link captureUiState}. State interpretation remains application-owned. */
250
+ type UiStateCaptureOptions<T> = {
251
+ /** Reads one allowlisted semantic snapshot. Return undefined to emit nothing. */
252
+ read: () => T | undefined;
253
+ /** Receives the snapshot after the deferred read. Return values are not awaited. */
254
+ onState: (state: T) => void;
255
+ /** DOM/custom events that should schedule a read. */
256
+ triggers?: readonly UiStateTrigger[];
257
+ /** Task delay in milliseconds. Defaults to 0. */
258
+ delayMs?: number;
259
+ /** Optional diagnostic logger. */
260
+ logger?: Logger;
261
+ };
262
+
263
+ /**
264
+ * Observes WebAuthn ceremonies initiated through
265
+ * `navigator.credentials.get()` and `navigator.credentials.create()`.
266
+ *
267
+ * While capture is active, each ceremony emits a `started` event followed by a
268
+ * correlated `completed` or `failed` event. Credential Management calls
269
+ * without `publicKey` options are not reported. Observation never changes a
270
+ * ceremony's arguments, result, or error; a fault in event delivery is
271
+ * contained, and the ceremony proceeds as if unobserved.
272
+ *
273
+ * Attachment is all-or-nothing. When WebAuthn cannot be observed in the
274
+ * current environment — the API is missing, a capture is already active, or
275
+ * another integration holds the credential methods in a form that cannot be
276
+ * shared — nothing is modified and the returned handle reports
277
+ * `mode: "unavailable"`.
278
+ *
279
+ * Assumptions and limitations:
280
+ *
281
+ * - Options are expected to be plain dictionaries as defined by the Credential
282
+ * Management and WebAuthn specifications. Exotic option objects, such as
283
+ * proxies or dictionaries with accessor members, are not supported.
284
+ * - Some password-manager extensions expose the credential methods in a form
285
+ * that cannot be observed. Capture then reports `mode: "unavailable"`;
286
+ * authentication continues but produces no events.
287
+ * - A script that replaces the credential methods or the whole
288
+ * `navigator.credentials` object after capture starts is left untouched.
289
+ * Authentication continues, but ceremonies are no longer reported unless the
290
+ * replacement delegates to the methods it replaced.
291
+ *
292
+ * @param options Event callback and optional diagnostic logger.
293
+ * @returns A handle used to inspect the capture mode or stop event delivery.
294
+ */
295
+ declare function captureWebAuthn(options: WebAuthnCaptureOptions): CaptureHandle;
296
+
297
+ /**
298
+ * Observes selected requests made through `XMLHttpRequest` and `fetch`.
299
+ *
300
+ * While capture remains active, selected requests on available transports can
301
+ * produce one {@link HttpRequestStarted} after the platform accepts the call,
302
+ * followed by one correlated {@link HttpExchange} after completion. The
303
+ * Corbado Observe event endpoint is always excluded. If only some requested
304
+ * transports are available, capture continues on those transports and reports
305
+ * the others to the optional logger.
306
+ *
307
+ * Exchanges contain metadata by default; body capture must be enabled
308
+ * explicitly.
309
+ *
310
+ * @param options Request selection, event callback, and optional body controls.
311
+ * @returns A handle used to inspect the capture mode or stop event delivery.
312
+ * @throws TypeError when `onExchange` or `match` is not a function — a
313
+ * misconfigured integration is a programming error, not a degraded mode.
314
+ */
315
+ declare function captureNetwork(options: NetworkCaptureOptions): CaptureHandle;
316
+
317
+ /**
318
+ * Observes same-document navigations produced by `history.pushState()`,
319
+ * `history.replaceState()`, browser history traversal, and fragment changes.
320
+ *
321
+ * No event is emitted when the pathname, query string, and fragment are all
322
+ * unchanged. Callbacks run synchronously with the observed navigation.
323
+ *
324
+ * @param options Navigation callback and optional diagnostic logger.
325
+ * @returns A handle used to inspect the capture mode or stop event delivery.
326
+ */
327
+ declare function captureNavigation(options: NavigationCaptureOptions): CaptureHandle;
328
+
329
+ /**
330
+ * Observes shadow roots attached after capture starts. Both open and closed
331
+ * roots are reported with their host and mode.
332
+ *
333
+ * Set `includeExisting` to also report discoverable open roots that already
334
+ * exist. Closed roots created before capture starts cannot be discovered.
335
+ * Callbacks run synchronously; existing roots may be reported before this
336
+ * function returns.
337
+ *
338
+ * Assumes a browser environment whose DOM and shadow DOM primitives behave
339
+ * as specified, and a `logger` that never throws. Behavior in environments
340
+ * that violate these assumptions is undefined.
341
+ *
342
+ * @param options Shadow-root callback and discovery options.
343
+ * @returns A handle used to inspect the capture mode or stop event delivery.
344
+ */
345
+ declare function captureShadowRoots(options: ShadowRootCaptureOptions): CaptureHandle;
346
+ /**
347
+ * Returns the first entry in `event.composedPath()` when available, otherwise
348
+ * returns `event.target`. Closed shadow boundaries remain subject to browser
349
+ * retargeting.
350
+ *
351
+ * @param event Event whose target should be resolved.
352
+ * @returns The deepest visible target, or `null` when the event has no target.
353
+ */
354
+ declare function composedTarget(event: Event): EventTarget | null;
355
+ /** Returns the visible composed event path; outside dispatch the path is empty and the retargeted target stands in. */
356
+ declare function composedPath(event: Event): readonly EventTarget[];
357
+
358
+ /**
359
+ * Defers allowlisted UI-state reads until the application has handled the
360
+ * triggering event. This is useful when a component announces a transition
361
+ * before synchronously updating its DOM, and for state changes that are only
362
+ * observable after a network callback or mutation.
363
+ */
364
+ declare function captureUiState<T>(options: UiStateCaptureOptions<T>): UiStateCaptureHandle;
365
+
366
+ /**
367
+ * Creates a diagnostic logger that writes to `console.log` with an optional
368
+ * prefix. Logging failures are ignored.
369
+ *
370
+ * @param prefix Prefix added to each message. Defaults to `[autocapture]`.
371
+ * @returns A logger suitable for the capture option objects.
372
+ */
373
+ declare function createConsoleLogger(prefix?: string): Logger;
374
+
375
+ /** Version identifier embedded in the current build. */
376
+ declare const VERSION: string;
377
+
378
+ export { type CaptureHandle, type HttpExchange, type HttpRequestStarted, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkBodyProjectionContext, type NetworkBodyProjector, type NetworkCaptureOptions, type NetworkTransport, type ShadowRootAttachedEvent, type ShadowRootCaptureOptions, type UiStateCaptureHandle, type UiStateCaptureOptions, type UiStateTrigger, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyEvent, type WebAuthnCeremonyKind, captureNavigation, captureNetwork, captureShadowRoots, captureUiState, captureWebAuthn, composedPath, composedTarget, createConsoleLogger };
@@ -0,0 +1,378 @@
1
+ /** Receives optional diagnostics. Capture functions remain silent when no logger is provided. */
2
+ type Logger = {
3
+ /** Records a non-fatal diagnostic message. */
4
+ log: (...args: unknown[]) => void;
5
+ };
6
+ /** Controls a capture session. */
7
+ type CaptureHandle = {
8
+ /**
9
+ * Result of starting the capture.
10
+ *
11
+ * - `"standard"`: capture started normally.
12
+ * - `"unavailable"`: capture could not be started and will not emit events.
13
+ */
14
+ readonly mode: "standard" | "unavailable";
15
+ /** Stops future event delivery. Safe to call more than once. */
16
+ disconnect(): void;
17
+ };
18
+ /** WebAuthn operation that initiated a ceremony. */
19
+ type WebAuthnCeremonyKind = "get" | "create";
20
+ /**
21
+ * A lifecycle event for a WebAuthn ceremony initiated through
22
+ * `navigator.credentials.get()` or `navigator.credentials.create()`.
23
+ */
24
+ type WebAuthnCeremonyEvent = {
25
+ /** WebAuthn operation that initiated the ceremony. */
26
+ kind: WebAuthnCeremonyKind;
27
+ /** Identifier shared by all events belonging to the same ceremony. */
28
+ ceremonyId: string;
29
+ /** Current ceremony lifecycle phase. */
30
+ phase: "started" | "completed" | "failed";
31
+ /** Requested mediation mode, when available. */
32
+ mediation?: string;
33
+ /** Whether transient user activation was active when the ceremony started. Present on `started` events when exposed by the browser. */
34
+ userActivation?: boolean;
35
+ /**
36
+ * JSON-encoded request metadata for a `get` ceremony: `rpId`, `timeout`,
37
+ * `userVerification`, `hints`, `challenge`, and `allowCredentials`
38
+ * (`type`, `id`, and `transports`). Binary values are base64url-encoded.
39
+ * Unavailable fields are omitted. Present on `started` events when
40
+ * serialization succeeds. Treat this value as sensitive telemetry.
41
+ */
42
+ requestOptionsJson?: string;
43
+ /** Whether a `get` request included at least one allowed credential. Present on `started` events. */
44
+ hasAllowCredentials?: boolean;
45
+ /**
46
+ * JSON-encoded, allowlisted credential returned by a completed ceremony.
47
+ * Common fields are `id`, `rawId`, `type`, and `authenticatorAttachment`.
48
+ * Assertion responses include `clientDataJSON`, `authenticatorData`,
49
+ * `signature`, and `userHandle`; attestation responses include
50
+ * `clientDataJSON`, `attestationObject`, and `transports`. Binary values are
51
+ * base64url-encoded. Of the client-extension results, only `credProps.rk` may
52
+ * be included. Present when serialization succeeds. Treat this value as
53
+ * sensitive telemetry.
54
+ */
55
+ credentialJson?: string;
56
+ /** Rejection value from a failed ceremony. This may contain application-specific data. */
57
+ error?: unknown;
58
+ /** Error name for a failed ceremony, or `UnknownError` when no DOM exception name is available. */
59
+ errorName?: string;
60
+ /**
61
+ * `true` when the request signal was observed as aborted before settlement.
62
+ * A credential manager may still complete such a ceremony, so this can be
63
+ * present on either a `completed` or `failed` event.
64
+ */
65
+ aborted?: boolean;
66
+ };
67
+ /** Options for {@link captureWebAuthn}. */
68
+ type WebAuthnCaptureOptions = {
69
+ /** Receives WebAuthn lifecycle events. Return values are not awaited. */
70
+ onEvent: (event: WebAuthnCeremonyEvent) => void;
71
+ /** Optional diagnostic logger. */
72
+ logger?: Logger;
73
+ };
74
+ /** Network API used for an observed exchange. */
75
+ type NetworkTransport = "xhr" | "fetch";
76
+ /** A matched network request immediately after the platform accepted it. */
77
+ type HttpRequestStarted = {
78
+ /** Identifier shared with the eventual {@link HttpExchange}. */
79
+ requestId: string;
80
+ /** Network API used for the request. */
81
+ transport: NetworkTransport;
82
+ /** Uppercase HTTP method. */
83
+ method: string;
84
+ /** Resolved absolute request input URL. */
85
+ url: string;
86
+ /** Pathname component of the request URL. */
87
+ path: string;
88
+ /** Timestamp captured at the request boundary. */
89
+ startedAt: number;
90
+ /** Request-body value returned by the configured projector, when synchronously available. */
91
+ requestBody?: unknown;
92
+ };
93
+ /** An observed network request and its outcome. */
94
+ type HttpExchange = {
95
+ /** Identifier shared with the corresponding {@link HttpRequestStarted}. */
96
+ requestId: string;
97
+ /** Network API used for the request. */
98
+ transport: NetworkTransport;
99
+ /** Uppercase HTTP method. */
100
+ method: string;
101
+ /**
102
+ * Resolved absolute request input URL, including its query string and
103
+ * fragment. Redact it before forwarding if either component may be sensitive.
104
+ */
105
+ url: string;
106
+ /** Pathname component of the request URL. */
107
+ path: string;
108
+ /** HTTP status code, or `0` when a status is unavailable. */
109
+ status: number;
110
+ /** Whether the status code represents a successful response. */
111
+ ok: boolean;
112
+ /**
113
+ * How the request completed. `load` includes successful and non-successful
114
+ * HTTP responses; inspect `ok` or `status` for HTTP success.
115
+ */
116
+ outcome: "load" | "abort" | "timeout" | "error";
117
+ /** Elapsed request time in milliseconds, when available. */
118
+ durationMs?: number;
119
+ /**
120
+ * Final absolute response URL, when available. This may differ from `url`
121
+ * after redirects and may contain sensitive query or fragment values.
122
+ */
123
+ responseUrl?: string;
124
+ /** Request-body value returned by the configured projector, when available. */
125
+ requestBody?: unknown;
126
+ /** Response-body value returned by the configured projector, when available. */
127
+ responseBody?: unknown;
128
+ };
129
+ /** Metadata supplied to a body projector for one matched exchange. */
130
+ type NetworkBodyProjectionContext = {
131
+ /** Whether the observed body belongs to the request or response. */
132
+ direction: "request" | "response";
133
+ /** Network API used by the application. */
134
+ transport: NetworkTransport;
135
+ /** Uppercase HTTP method. */
136
+ method: string;
137
+ /** Resolved absolute request URL. */
138
+ url: string;
139
+ /** Pathname component of the request URL. */
140
+ path: string;
141
+ };
142
+ /**
143
+ * Synchronously selects the only body data that may leave network capture.
144
+ * Textual bodies are supplied as strings; an XHR with `responseType="json"`
145
+ * supplies the browser-parsed value. Returning `undefined`, throwing, or
146
+ * returning a Promise omits the body without affecting the application.
147
+ */
148
+ type NetworkBodyProjector = (body: unknown, context: NetworkBodyProjectionContext) => unknown;
149
+ /** Options for {@link captureNetwork}. */
150
+ type NetworkCaptureOptions = {
151
+ /**
152
+ * Selects requests to observe. The URL is resolved against the document base
153
+ * URL and the method is uppercase. Return `true` to observe the request. The
154
+ * Corbado Observe event endpoint is always excluded to prevent recursion.
155
+ * This callback must return synchronously; Promise results are not matched.
156
+ */
157
+ match: (url: URL, method: string) => boolean;
158
+ /** Receives completed network exchanges. Return values are not awaited. */
159
+ onExchange: (exchange: HttpExchange) => void;
160
+ /**
161
+ * Receives matched requests after the platform call succeeds. The request ID
162
+ * matches the eventual exchange. Return values are not awaited.
163
+ */
164
+ onRequest?: (request: HttpRequestStarted) => void;
165
+ /**
166
+ * Network APIs to observe. Defaults to both `xhr` and `fetch`. Unavailable
167
+ * transports are skipped if at least one requested transport can be observed.
168
+ */
169
+ transports?: readonly NetworkTransport[];
170
+ /**
171
+ * Best-effort body observation, enabled independently per direction by a
172
+ * mandatory projector. Without a projector, that direction is never read.
173
+ * Autocapture performs no generic parsing or redaction: the projector owns
174
+ * data minimization and its return value is the only body data reported.
175
+ */
176
+ bodyCapture?: {
177
+ /** Projects a bounded request body. */
178
+ request?: NetworkBodyProjector;
179
+ /** Projects a bounded response body. */
180
+ response?: NetworkBodyProjector;
181
+ };
182
+ /**
183
+ * Maximum size of a projected body attached to a request or exchange,
184
+ * measured on its JSON-serialized form; larger projections are omitted.
185
+ * Raw bodies are not size-checked before projection — matched traffic is
186
+ * first-party and assumed well-behaved. Must be a finite, non-negative
187
+ * number when provided. Defaults to 65,536.
188
+ */
189
+ maxBodyChars?: number;
190
+ /** Optional diagnostic logger. */
191
+ logger?: Logger;
192
+ };
193
+ /** Mechanism that produced a navigation event. */
194
+ type NavigationSource = "push" | "replace" | "pop" | "hash";
195
+ /** A same-document navigation. */
196
+ type NavigationEvent = {
197
+ /** Current URL pathname. */
198
+ path: string;
199
+ /** Current URL query string, including the leading `?` when present. May contain sensitive data. */
200
+ search: string;
201
+ /** Current URL fragment, including the leading `#` when present. May contain sensitive data. */
202
+ hash: string;
203
+ /** Mechanism that produced the event. */
204
+ source: NavigationSource;
205
+ };
206
+ /** Options for {@link captureNavigation}. */
207
+ type NavigationCaptureOptions = {
208
+ /** Receives same-document navigation events. Return values are not awaited. */
209
+ onNavigate: (event: NavigationEvent) => void;
210
+ /** Optional diagnostic logger. */
211
+ logger?: Logger;
212
+ };
213
+ /** A shadow root reported by {@link captureShadowRoots}. */
214
+ type ShadowRootAttachedEvent = {
215
+ /** Element that owns the shadow root. */
216
+ host: Element;
217
+ /**
218
+ * Attached shadow root. For a closed root, `host.shadowRoot` remains `null`;
219
+ * retain this reference if later access is required.
220
+ */
221
+ shadowRoot: ShadowRoot;
222
+ /** Shadow root mode. */
223
+ mode: "open" | "closed";
224
+ };
225
+ /** Options for {@link captureShadowRoots}. */
226
+ type ShadowRootCaptureOptions = {
227
+ /** Receives shadow roots synchronously. Return values are not awaited. */
228
+ onAttached: (event: ShadowRootAttachedEvent) => void;
229
+ /**
230
+ * Also reports open shadow roots that already exist when capture starts.
231
+ * These callbacks may run before `captureShadowRoots()` returns. Existing
232
+ * closed roots cannot be discovered. Defaults to `false`.
233
+ */
234
+ includeExisting?: boolean;
235
+ /** Optional diagnostic logger. */
236
+ logger?: Logger;
237
+ };
238
+ /** Event that should schedule a rendered UI-state read. */
239
+ type UiStateTrigger = {
240
+ target: EventTarget;
241
+ type: string;
242
+ capture?: boolean;
243
+ };
244
+ /** A capture handle that also accepts imperative triggers such as completed network exchanges. */
245
+ type UiStateCaptureHandle = CaptureHandle & {
246
+ /** Coalesces a state read onto the next task, after the application's synchronous layout work. */
247
+ schedule(): void;
248
+ };
249
+ /** Options for {@link captureUiState}. State interpretation remains application-owned. */
250
+ type UiStateCaptureOptions<T> = {
251
+ /** Reads one allowlisted semantic snapshot. Return undefined to emit nothing. */
252
+ read: () => T | undefined;
253
+ /** Receives the snapshot after the deferred read. Return values are not awaited. */
254
+ onState: (state: T) => void;
255
+ /** DOM/custom events that should schedule a read. */
256
+ triggers?: readonly UiStateTrigger[];
257
+ /** Task delay in milliseconds. Defaults to 0. */
258
+ delayMs?: number;
259
+ /** Optional diagnostic logger. */
260
+ logger?: Logger;
261
+ };
262
+
263
+ /**
264
+ * Observes WebAuthn ceremonies initiated through
265
+ * `navigator.credentials.get()` and `navigator.credentials.create()`.
266
+ *
267
+ * While capture is active, each ceremony emits a `started` event followed by a
268
+ * correlated `completed` or `failed` event. Credential Management calls
269
+ * without `publicKey` options are not reported. Observation never changes a
270
+ * ceremony's arguments, result, or error; a fault in event delivery is
271
+ * contained, and the ceremony proceeds as if unobserved.
272
+ *
273
+ * Attachment is all-or-nothing. When WebAuthn cannot be observed in the
274
+ * current environment — the API is missing, a capture is already active, or
275
+ * another integration holds the credential methods in a form that cannot be
276
+ * shared — nothing is modified and the returned handle reports
277
+ * `mode: "unavailable"`.
278
+ *
279
+ * Assumptions and limitations:
280
+ *
281
+ * - Options are expected to be plain dictionaries as defined by the Credential
282
+ * Management and WebAuthn specifications. Exotic option objects, such as
283
+ * proxies or dictionaries with accessor members, are not supported.
284
+ * - Some password-manager extensions expose the credential methods in a form
285
+ * that cannot be observed. Capture then reports `mode: "unavailable"`;
286
+ * authentication continues but produces no events.
287
+ * - A script that replaces the credential methods or the whole
288
+ * `navigator.credentials` object after capture starts is left untouched.
289
+ * Authentication continues, but ceremonies are no longer reported unless the
290
+ * replacement delegates to the methods it replaced.
291
+ *
292
+ * @param options Event callback and optional diagnostic logger.
293
+ * @returns A handle used to inspect the capture mode or stop event delivery.
294
+ */
295
+ declare function captureWebAuthn(options: WebAuthnCaptureOptions): CaptureHandle;
296
+
297
+ /**
298
+ * Observes selected requests made through `XMLHttpRequest` and `fetch`.
299
+ *
300
+ * While capture remains active, selected requests on available transports can
301
+ * produce one {@link HttpRequestStarted} after the platform accepts the call,
302
+ * followed by one correlated {@link HttpExchange} after completion. The
303
+ * Corbado Observe event endpoint is always excluded. If only some requested
304
+ * transports are available, capture continues on those transports and reports
305
+ * the others to the optional logger.
306
+ *
307
+ * Exchanges contain metadata by default; body capture must be enabled
308
+ * explicitly.
309
+ *
310
+ * @param options Request selection, event callback, and optional body controls.
311
+ * @returns A handle used to inspect the capture mode or stop event delivery.
312
+ * @throws TypeError when `onExchange` or `match` is not a function — a
313
+ * misconfigured integration is a programming error, not a degraded mode.
314
+ */
315
+ declare function captureNetwork(options: NetworkCaptureOptions): CaptureHandle;
316
+
317
+ /**
318
+ * Observes same-document navigations produced by `history.pushState()`,
319
+ * `history.replaceState()`, browser history traversal, and fragment changes.
320
+ *
321
+ * No event is emitted when the pathname, query string, and fragment are all
322
+ * unchanged. Callbacks run synchronously with the observed navigation.
323
+ *
324
+ * @param options Navigation callback and optional diagnostic logger.
325
+ * @returns A handle used to inspect the capture mode or stop event delivery.
326
+ */
327
+ declare function captureNavigation(options: NavigationCaptureOptions): CaptureHandle;
328
+
329
+ /**
330
+ * Observes shadow roots attached after capture starts. Both open and closed
331
+ * roots are reported with their host and mode.
332
+ *
333
+ * Set `includeExisting` to also report discoverable open roots that already
334
+ * exist. Closed roots created before capture starts cannot be discovered.
335
+ * Callbacks run synchronously; existing roots may be reported before this
336
+ * function returns.
337
+ *
338
+ * Assumes a browser environment whose DOM and shadow DOM primitives behave
339
+ * as specified, and a `logger` that never throws. Behavior in environments
340
+ * that violate these assumptions is undefined.
341
+ *
342
+ * @param options Shadow-root callback and discovery options.
343
+ * @returns A handle used to inspect the capture mode or stop event delivery.
344
+ */
345
+ declare function captureShadowRoots(options: ShadowRootCaptureOptions): CaptureHandle;
346
+ /**
347
+ * Returns the first entry in `event.composedPath()` when available, otherwise
348
+ * returns `event.target`. Closed shadow boundaries remain subject to browser
349
+ * retargeting.
350
+ *
351
+ * @param event Event whose target should be resolved.
352
+ * @returns The deepest visible target, or `null` when the event has no target.
353
+ */
354
+ declare function composedTarget(event: Event): EventTarget | null;
355
+ /** Returns the visible composed event path; outside dispatch the path is empty and the retargeted target stands in. */
356
+ declare function composedPath(event: Event): readonly EventTarget[];
357
+
358
+ /**
359
+ * Defers allowlisted UI-state reads until the application has handled the
360
+ * triggering event. This is useful when a component announces a transition
361
+ * before synchronously updating its DOM, and for state changes that are only
362
+ * observable after a network callback or mutation.
363
+ */
364
+ declare function captureUiState<T>(options: UiStateCaptureOptions<T>): UiStateCaptureHandle;
365
+
366
+ /**
367
+ * Creates a diagnostic logger that writes to `console.log` with an optional
368
+ * prefix. Logging failures are ignored.
369
+ *
370
+ * @param prefix Prefix added to each message. Defaults to `[autocapture]`.
371
+ * @returns A logger suitable for the capture option objects.
372
+ */
373
+ declare function createConsoleLogger(prefix?: string): Logger;
374
+
375
+ /** Version identifier embedded in the current build. */
376
+ declare const VERSION: string;
377
+
378
+ export { type CaptureHandle, type HttpExchange, type HttpRequestStarted, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkBodyProjectionContext, type NetworkBodyProjector, type NetworkCaptureOptions, type NetworkTransport, type ShadowRootAttachedEvent, type ShadowRootCaptureOptions, type UiStateCaptureHandle, type UiStateCaptureOptions, type UiStateTrigger, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyEvent, type WebAuthnCeremonyKind, captureNavigation, captureNetwork, captureShadowRoots, captureUiState, captureWebAuthn, composedPath, composedTarget, createConsoleLogger };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var oe=Object.defineProperty;var xe=Object.getOwnPropertyDescriptor;var Le=Object.getOwnPropertyNames;var Ne=Object.prototype.hasOwnProperty;var De=(e,t)=>{for(var n in t)oe(e,n,{get:t[n],enumerable:!0})},Te=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Le(t))!Ne.call(e,o)&&o!==n&&oe(e,o,{get:()=>t[o],enumerable:!(r=xe(t,o))||r.enumerable});return e};var He=e=>Te(oe({},"__esModule",{value:!0}),e);var Ke={};De(Ke,{VERSION:()=>Pe,captureNavigation:()=>Re,captureNetwork:()=>ve,captureShadowRoots:()=>Oe,captureUiState:()=>Ae,captureWebAuthn:()=>pe,composedPath:()=>ce,composedTarget:()=>Ce,createConsoleLogger:()=>ke});module.exports=He(Ke);var ae={log:()=>{}};function j(e){return e?{log:(...t)=>{try{let n=e.log(...t);if(n&&(typeof n=="object"||typeof n=="function"))try{Promise.resolve(n).catch(()=>{})}catch{}}catch{}}}:ae}var le=Symbol.for("corbado.autocapture.installed");function B(e){try{return typeof e=="function"&&Object.prototype.hasOwnProperty.call(e,le)}catch{return!1}}function M(e,t){var r,o;let n=t;try{Object.defineProperty(e,le,{value:t,configurable:!0})}catch{}try{Object.defineProperty(e,"name",{value:(r=n.name)!=null?r:"",configurable:!0})}catch{}try{Object.defineProperty(e,"length",{value:(o=n.length)!=null?o:0,configurable:!0})}catch{}return e}function w(){return{mode:"unavailable",disconnect:()=>{}}}function _(e,t,n){let r=j(e);return o=>{try{let a=n(o);if(a&&(typeof a=="object"||typeof a=="function"))try{Promise.resolve(a).catch(d=>r.log(`${t} listener failed`,d))}catch(d){r.log(`${t} listener failed`,d)}}catch(a){r.log(`${t} listener failed`,a)}}}function pe(e){let t=j(e.logger),n=e.onEvent;if(typeof n!="function")return w();let r=typeof navigator=="undefined"?void 0:navigator,o=r==null?void 0:r.credentials;if(!r||!o)return t.log("webauthn capture unavailable: navigator.credentials missing"),w();let a=o.get,d=o.create;if(typeof a!="function"||typeof d!="function")return t.log("webauthn capture unavailable: credentials methods missing"),w();if(B(a)||B(d))return t.log("webauthn capture unavailable: an equivalent capture is already active"),w();let p=!1,v=0,k=_(t,"webauthn",s=>{if(p)return n(s)}),D=(s,b)=>{let L={invoke(...q){let O=this,R=q[0];if(!p||!Q(R)||!Q(R.publicKey))return Reflect.apply(b,O,q);let Z=R.publicKey,V=`wa-${++v}`,ee=Reflect.apply(b,O,q),f=typeof R.mediation=="string"?R.mediation:void 0,m=Q(R.signal)?R.signal:void 0,C={kind:s,ceremonyId:V,phase:"started",mediation:f,userActivation:je(r)};if(s==="get"){let c=Z;C.requestOptionsJson=Ie(c,t),C.hasAllowCredentials=Array.isArray(c.allowCredentials)&&c.allowCredentials.length>0}return k(C),Promise.resolve(ee).then(c=>(p&&k({kind:s,ceremonyId:V,phase:"completed",mediation:f,credentialJson:Be(c,s,t),...(m==null?void 0:m.aborted)===!0?{aborted:!0}:{}}),c),c=>{throw p&&k({kind:s,ceremonyId:V,phase:"failed",mediation:f,error:c,errorName:_e(c),...(m==null?void 0:m.aborted)===!0?{aborted:!0}:{}}),c})}}.invoke;return M(L,{name:s,length:0})},h=(s,b)=>{let L=D(s,b),q=Object.getOwnPropertyDescriptor(o,s);if(q&&(!("value"in q)||q.writable!==!0))return null;try{o[s]=L}catch{return null}return()=>{o[s]===L&&(q?o[s]=q.value:delete o[s])}},S=h("get",a),u=S?h("create",d):null;return!S||!u?(S==null||S(),t.log("webauthn capture unavailable: credentials methods cannot be patched safely"),w()):(p=!0,{mode:"standard",disconnect:()=>{p=!1,S(),u()}})}function je(e){let t=e.userActivation;return typeof(t==null?void 0:t.isActive)=="boolean"?t.isActive:void 0}function Be(e,t,n){var r;if(e)try{let o=e,a=o.response,d={id:z(o.id),rawId:$(o.rawId),type:z(o.type),authenticatorAttachment:z(o.authenticatorAttachment),response:t==="create"?{clientDataJSON:$(a==null?void 0:a.clientDataJSON),attestationObject:$(a==null?void 0:a.attestationObject),transports:ie((r=a==null?void 0:a.getTransports)==null?void 0:r.call(a))}:{clientDataJSON:$(a==null?void 0:a.clientDataJSON),authenticatorData:$(a==null?void 0:a.authenticatorData),signature:$(a==null?void 0:a.signature),userHandle:$(a==null?void 0:a.userHandle)},...t==="create"?Me(o):{}};return JSON.stringify(d)}catch(o){n.log("credential serialization failed",o);return}}function Ie(e,t){try{let n={},r=z(e.rpId),o=z(e.userVerification),a=ie(e.hints);return r!==void 0&&(n.rpId=r),typeof e.timeout=="number"&&Number.isFinite(e.timeout)&&(n.timeout=e.timeout),o!==void 0&&(n.userVerification=o),a!==void 0&&(n.hints=a),n.challenge=$(e.challenge),Array.isArray(e.allowCredentials)&&(n.allowCredentials=Ue(e.allowCredentials)),JSON.stringify(n)}catch(n){t.log("request options serialization failed",n);return}}function Ue(e){let t=[];for(let n of e){if(!Q(n))continue;let r=n;t.push({type:z(r.type),id:$(r.id),transports:ie(r.transports)})}return t}function ie(e){if(Array.isArray(e))return e.filter(t=>typeof t=="string")}function z(e){return typeof e=="string"?e:void 0}function Me(e){var r;let t=(r=e.getClientExtensionResults)==null?void 0:r.call(e);if(!Q(t))return{};let n=t.credProps;return!n||typeof n.rk!="boolean"?{}:{clientExtensionResults:{credProps:{rk:n.rk}}}}function $(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 n="";for(let r of t)n+=String.fromCharCode(r);return btoa(n).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Q(e){return e!==null&&(typeof e=="object"||typeof e=="function")}function _e(e){return e instanceof DOMException?e.name:"UnknownError"}var fe=0;function he(){return fe+=1,`network-${fe}`}function Fe(e,t){let n=e.responseType;if(n===""||n==="text")return t(e.responseText);if(n==="json"){let r=e.response;return r==null?void 0:t(r)}}var Xe=500,We=65536,ge=Symbol("body-read-timeout");function ye(e){return/event-stream|octet-stream/i.test(e)?!1:/json|text|urlencoded/i.test(e)}async function me(e,t){let n=e.body;if(!n)return;let r=n.getReader(),o;try{let a=new Promise(v=>{o=setTimeout(()=>v(ge),Xe)}),d=new TextDecoder,p=[];for(;;){let v=await Promise.race([r.read(),a]);if(v===ge){t.log("body omitted: read timed out"),r.cancel().catch(()=>{});return}if(v.done)break;p.push(d.decode(v.value,{stream:!0}))}return p.push(d.decode()),p.join("")}catch{r.cancel().catch(()=>{});return}finally{o!==void 0&&clearTimeout(o)}}function se(e){var t;(t=e==null?void 0:e.body)==null||t.cancel().catch(()=>{})}function ue(e,t,n,r,o){try{let a=Object.getOwnPropertyDescriptor(e,t);Je(a,{...r,value:n})&&Object.defineProperty(e,t,r)}catch(a){o.log(`${String(t)} restoration skipped`,a)}}function Je(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 ve(e){var R,Z,V,ee;let{onExchange:t,onRequest:n,match:r}=e;if(typeof t!="function"||typeof r!="function")throw new TypeError("captureNetwork requires onExchange and match callbacks");let o=j(e.logger),a=(R=e.transports)!=null?R:["xhr","fetch"],d=(Z=e.bodyCapture)==null?void 0:Z.request,p=(V=e.bodyCapture)==null?void 0:V.response,v=typeof d=="function",k=typeof p=="function",D=(ee=e.maxBodyChars)!=null?ee:We,h=!1,S=_(o,"network",f=>{if(h)return t(f)}),u=_(o,"network request",f=>{if(h)return n==null?void 0:n(f)}),s=(f,m)=>{let C=m.direction==="request"?d:p;if(typeof C=="function")try{let c=C(f,m);if(c===void 0||typeof(c==null?void 0:c.then)=="function")return;let F=JSON.stringify(c);return F===void 0||F.length>D?void 0:JSON.parse(F)}catch(c){o.log("body projection failed",c);return}},b=(f,m)=>{try{let C=typeof document!="undefined"?document.baseURI:void 0,c=new URL(String(f),C);return/\/observe\/events\/[^/]+\/?$/.test(c.pathname)?void 0:r(c,m)===!0?{url:c.href,path:c.pathname}:void 0}catch{return}},L=()=>{if(typeof XMLHttpRequest=="undefined")return o.log("xhr capture unavailable: XMLHttpRequest missing"),null;let f=XMLHttpRequest.prototype,m=Object.getOwnPropertyDescriptor(f,"open"),C=Object.getOwnPropertyDescriptor(f,"send");if(!m||!("value"in m)||typeof m.value!="function"||!C||!("value"in C)||typeof C.value!="function")return o.log("xhr capture unavailable: XMLHttpRequest methods missing"),null;let c=m.value,F=C.value;if(B(c)||B(F))return o.log("xhr capture unavailable: an equivalent capture is already active"),null;let X=new WeakMap,A=new Set,x=!1,T={open(...E){var g;let i=Reflect.apply(c,this,E);if(!h||!x)return i;try{let N=X.get(this);N&&(N.reported=!0,(g=N.cleanupListeners)==null||g.call(N));let H=String(E[0]).toUpperCase(),P=b(E[1],H);P?X.set(this,{method:H,url:P.url,path:P.path,reported:!1}):X.delete(this)}catch(N){o.log("xhr open observation failed",N)}return i}}.open,J={send(...E){let i=h&&x?X.get(this):void 0;if(!i||i.reported||i.cleanupListeners)return Reflect.apply(F,this,E);let g=()=>{},N=!1;try{i.requestId=he();let P={direction:"request",transport:"xhr",method:i.method,url:i.url,path:i.path};i.requestBody=v&&typeof E[0]=="string"?s(E[0],P):void 0;let G=()=>{i.outcome===void 0&&(i.outcome="abort")},Y=()=>{i.outcome===void 0&&(i.outcome="timeout")},l=()=>{i.outcome===void 0&&(i.outcome="error")},y=()=>{var K;g();try{if(!h||!x||i.reported||!i.requestId)return;i.reported=!0;let U={direction:"response",transport:"xhr",method:i.method,url:i.url,path:i.path},re=this.status,de={requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,status:re,ok:re>=200&&re<300,outcome:(K=i.outcome)!=null?K:"load",durationMs:i.startedAt!==void 0?Date.now()-i.startedAt:void 0,responseUrl:this.responseURL||void 0,requestBody:i.requestBody,responseBody:k?Fe(this,qe=>s(qe,U)):void 0};i.requestEmitted?S(de):i.pendingExchange=de}catch(U){o.log("xhr exchange observation failed",U)}finally{X.get(this)===i&&X.delete(this)}},I=()=>{try{this.readyState===XMLHttpRequest.DONE&&this.status!==0&&y()}catch(K){o.log("xhr ready-state observation failed",K)}},W=()=>y();g=()=>{for(let[K,U]of[["readystatechange",I],["abort",G],["timeout",Y],["error",l],["loadend",W]])this.removeEventListener(K,U);i.cleanupListeners===g&&(i.cleanupListeners=void 0),A.delete(g)},this.addEventListener("readystatechange",I),this.addEventListener("abort",G),this.addEventListener("timeout",Y),this.addEventListener("error",l),this.addEventListener("loadend",W),i.cleanupListeners=g,A.add(g),i.startedAt=Date.now(),N=!0}catch(P){o.log("xhr send observation failed",P),g()}let H;try{H=Reflect.apply(F,this,E)}catch(P){throw g(),P}if(N&&i.requestId!==void 0&&i.startedAt!==void 0){i.requestEmitted=!0,u({requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,startedAt:i.startedAt,requestBody:i.requestBody});let P=i.pendingExchange;i.pendingExchange=void 0,P&&S(P)}return H}}.send;return M(T,c),M(J,F),m.writable!==!0||C.writable!==!0?(o.log("xhr capture unavailable: XMLHttpRequest methods are not writable"),null):(Object.defineProperty(f,"open",{...m,value:T}),Object.defineProperty(f,"send",{...C,value:J}),x=!0,()=>{x=!1;for(let E of[...A])E();ue(f,"open",T,m,o),ue(f,"send",J,C,o)})},q=()=>{let f=globalThis,m=Object.getOwnPropertyDescriptor(f,"fetch");if(!m||!("value"in m))return o.log("fetch capture unavailable: fetch is not an own data property"),null;let C=m.value;if(typeof C!="function")return o.log("fetch capture unavailable: fetch missing"),null;if(B(C))return o.log("fetch capture unavailable: an equivalent capture is already active"),null;let c=!1,F=async(A,x,T,J)=>{try{if(!h||!c){se(T);return}let[E,i]=await Promise.all([J!=null?J:Promise.resolve(void 0),T?me(T,o):Promise.resolve(void 0)]);if(!h||!c)return;let g={direction:"request",transport:"fetch",method:A.method,url:A.url,path:A.path},N={...g,direction:"response"},H=A.requestBody;H===void 0&&E!==void 0&&(H=s(E,g));let P=i!==void 0?s(i,N):void 0;S({requestId:A.requestId,transport:"fetch",method:A.method,url:A.url,path:A.path,status:x.status,ok:x.ok,outcome:"load",durationMs:Date.now()-A.startedAt,responseUrl:x.url||void 0,requestBody:H,responseBody:P})}catch(E){o.log("fetch exchange observation failed",E)}},X={fetch(...A){var H,P,G,Y;let x;try{let l=A[0],y=A[1];if(h&&c&&v&&typeof Request!="undefined"&&l instanceof Request&&(y==null?void 0:y.body)===void 0&&l.body){let I=String((H=y==null?void 0:y.method)!=null?H:l.method).toUpperCase(),W=(P=l.headers.get("content-type"))!=null?P:"";b(l.url,I)&&ye(W)&&(x=l.clone())}}catch(l){o.log("fetch observation failed",l)}let T,J=Date.now();try{T=Reflect.apply(C,this,A)}catch(l){throw se(x),l}let E,i;try{if(h&&c){let l=A[0],y=A[1],I=typeof Request!="undefined"&&l instanceof Request,W=String((G=y==null?void 0:y.method)!=null?G:I?l.method:"GET").toUpperCase(),K=I?l.url:l,U=b(K,W);U&&(E={requestId:he(),method:W,url:U.url,path:U.path,startedAt:J},i=(Y=y==null?void 0:y.signal)!=null?Y:I?l.signal:void 0,v&&typeof(y==null?void 0:y.body)=="string"&&(E.requestBody=s(y.body,{direction:"request",transport:"fetch",method:W,url:U.url,path:U.path})))}}catch(l){o.log("fetch observation failed",l)}if(!E)return se(x),T;let g=E;u({requestId:g.requestId,transport:"fetch",method:g.method,url:g.url,path:g.path,startedAt:g.startedAt,requestBody:g.requestBody});let N=x?me(x,o):void 0;return T.then(l=>{var I;let y;try{if(h&&c&&k){let W=(I=l.headers.get("content-type"))!=null?I:"";ye(W)&&l.body&&(y=l.clone())}}catch{}F(g,l,y,N)},l=>{try{if(!h||!c)return;let y=(i==null?void 0:i.aborted)===!0||(l==null?void 0:l.name)==="AbortError"||typeof DOMException!="undefined"&&l instanceof DOMException&&l.code===DOMException.ABORT_ERR;S({requestId:g.requestId,transport:"fetch",method:g.method,url:g.url,path:g.path,status:0,ok:!1,outcome:y?"abort":"error",durationMs:Date.now()-g.startedAt})}catch(y){o.log("fetch failure observation failed",y)}}),T}}.fetch;return M(X,C),m.writable!==!0?(o.log("fetch capture unavailable: fetch is not writable"),null):(Object.defineProperty(f,"fetch",{...m,value:X}),c=!0,()=>{c=!1,ue(f,"fetch",X,m,o)})},O=[];if(a.includes("xhr")){let f=L();f&&O.push(f)}if(a.includes("fetch")){let f=q();f&&O.push(f)}return O.length===0?w():(h=!0,{mode:"standard",disconnect:()=>{h=!1;for(let f of O.splice(0))f()}})}function Re(e){let t=j(),n;try{if(t=j(e.logger),n=e.onNavigate,typeof n!="function")return w()}catch{return w()}let r,o,a,d,p,v;try{if(r=window,o=r==null?void 0:r.history,!r||!o)return t.log("navigation capture unavailable: window/history missing"),w();if(p=we(o,"pushState"),v=we(o,"replaceState"),!p||!v)return t.log("navigation capture unavailable: history method descriptors missing"),w();if(a=p.descriptor.value,d=v.descriptor.value,typeof a!="function"||typeof d!="function")return t.log("navigation capture unavailable: history methods missing"),w();if(typeof r.addEventListener!="function"||typeof r.removeEventListener!="function")return t.log("navigation capture unavailable: window event methods missing"),w()}catch(O){return t.log("navigation capture unavailable: history inspection failed",O),w()}if(B(a)||B(d))return t.log("navigation capture unavailable: an equivalent capture is already active"),w();let k=!1,D=()=>({path:r.location.pathname,search:r.location.search,hash:r.location.hash}),h;try{h=D()}catch(O){return t.log("navigation capture unavailable: location inspection failed",O),w()}let S=_(t,"navigation",O=>{if(k)return n(O)}),u=O=>{if(k)try{let R=D();if(R.path===h.path&&R.search===h.search&&R.hash===h.hash)return;h=R,S({...R,source:O})}catch(R){t.log("navigation observation failed",R)}},s={pushState(...O){let R=Reflect.apply(a,this,O);return u("push"),R}}.pushState,b={replaceState(...O){let R=Reflect.apply(d,this,O);return u("replace"),R}}.replaceState;M(s,a),M(b,d);let L=()=>u("pop"),q=()=>u("hash");try{if(be(p,s),be(v,b),o.pushState!==s||o.replaceState!==b)throw new Error("history method replacement did not take effect");r.addEventListener("popstate",L),r.addEventListener("hashchange",q),k=!0}catch(O){return k=!1,ne(r,"popstate",L,t),ne(r,"hashchange",q,t),te(v,b,t),te(p,s,t),t.log("navigation capture unavailable: history methods are not writable",O),n=()=>{},S=()=>{},t=j(),w()}return{mode:"standard",disconnect:()=>{k=!1,n=()=>{},ne(r,"popstate",L,t),ne(r,"hashchange",q,t),te(p,s,t),te(v,b,t),S=()=>{},t=j()}}}function we(e,t){let n=e;for(;n;){let r=Object.getOwnPropertyDescriptor(n,t);if(r)return!("value"in r)||typeof r.value!="function"?null:{owner:n,key:t,descriptor:r};n=Object.getPrototypeOf(n)}return null}function be(e,t){if(e.descriptor.writable!==!0)throw new TypeError(`${e.key} is not writable`);let n=Object.getOwnPropertyDescriptor(e.owner,e.key);if(!Se(n,e.descriptor))throw new Error(`${e.key} changed during installation`);Object.defineProperty(e.owner,e.key,{...e.descriptor,value:t})}function Se(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 te(e,t,n){try{let r=Object.getOwnPropertyDescriptor(e.owner,e.key);Se(r,{...e.descriptor,value:t})&&Object.defineProperty(e.owner,e.key,e.descriptor)}catch(r){n.log(`navigation ${e.key} restoration skipped`,r)}}function ne(e,t,n,r){try{e.removeEventListener(t,n)}catch(o){r.log(`navigation ${t} listener removal skipped`,o)}}function Oe(e){var S;let t=(S=e.logger)!=null?S:ae,n=e.onAttached;if(typeof n!="function")return w();let r=Element.prototype,o=Object.getOwnPropertyDescriptor(r,"attachShadow");if(!o||typeof o.value!="function")return t.log("shadow-root capture unavailable: attachShadow not supported"),w();let a=o.value;if(B(a))return t.log("shadow-root capture unavailable: an equivalent capture is already active"),w();let d=!1,p=new WeakSet,v=_(t,"shadow-root",n),k=(u,s)=>{!d||p.has(s)||(p.add(s),v({host:u,shadowRoot:s,mode:s.mode==="closed"?"closed":"open"}))},D={attachShadow(...u){let s=Reflect.apply(a,this,u);return k(this,s),s}}.attachShadow;M(D,a);try{Object.defineProperty(r,"attachShadow",{...o,value:D})}catch(u){return t.log("shadow-root capture unavailable: attachShadow is locked",u),w()}d=!0;let h=u=>{let s=u.querySelectorAll("*");for(let b=0;b<s.length&&d;b++){let L=s[b].shadowRoot;L&&(k(s[b],L),h(L))}};return e.includeExisting&&h(document),{mode:"standard",disconnect:()=>{d=!1;let u=Object.getOwnPropertyDescriptor(r,"attachShadow");$e(u,{...o,value:D})&&Object.defineProperty(r,"attachShadow",o)}}}function Ce(e){var t;return(t=ce(e)[0])!=null?t:null}function ce(e){let t=e.composedPath();return t.length>0?t:e.target?[e.target]:[]}function $e(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 Ae(e){var D,h,S;let t=j(e==null?void 0:e.logger),n=e==null?void 0:e.read,r=e==null?void 0:e.onState;if(typeof n!="function"||typeof r!="function")return Ee();let o=Number.isFinite(e.delayMs)&&((D=e.delayMs)!=null?D:0)>=0&&(h=e.delayMs)!=null?h:0,a=_(t,"ui-state",r),d=!0,p,v=[],k=()=>{d&&(p!==void 0&&clearTimeout(p),p=setTimeout(()=>{if(p=void 0,!!d)try{let u=n();u!==void 0&&a(u)}catch(u){t.log("ui-state read failed",u)}},o))};try{for(let u of(S=e.triggers)!=null?S:[]){if(!(u!=null&&u.target)||typeof u.target.addEventListener!="function")continue;let s=k;u.target.addEventListener(u.type,s,u.capture===!0),v.push({trigger:u,listener:s})}}catch(u){t.log("ui-state trigger installation failed",u);for(let{trigger:s,listener:b}of v)try{s.target.removeEventListener(s.type,b,s.capture===!0)}catch{}return d=!1,Ee()}return{mode:"standard",schedule:k,disconnect:()=>{if(d){d=!1,p!==void 0&&clearTimeout(p),p=void 0;for(let{trigger:u,listener:s}of v)try{u.target.removeEventListener(u.type,s,u.capture===!0)}catch(b){t.log("ui-state trigger removal failed",b)}v.length=0}}}}function Ee(){return{mode:"unavailable",schedule:()=>{},disconnect:()=>{}}}function ke(e="[autocapture]"){return{log:(...t)=>{try{let n=console.log(e,...t);if(n&&(typeof n=="object"||typeof n=="function"))try{Promise.resolve(n).catch(()=>{})}catch{}}catch{}}}}var Pe="0.1.0-next.2-dd9eb36";0&&(module.exports={VERSION,captureNavigation,captureNetwork,captureShadowRoots,captureUiState,captureWebAuthn,composedPath,composedTarget,createConsoleLogger});
package/dist/index.mjs ADDED
@@ -0,0 +1 @@
1
+ var oe={log:()=>{}};function j(e){return e?{log:(...t)=>{try{let r=e.log(...t);if(r&&(typeof r=="object"||typeof r=="function"))try{Promise.resolve(r).catch(()=>{})}catch{}}catch{}}}:oe}var ce=Symbol.for("corbado.autocapture.installed");function B(e){try{return typeof e=="function"&&Object.prototype.hasOwnProperty.call(e,ce)}catch{return!1}}function M(e,t){var n,o;let r=t;try{Object.defineProperty(e,ce,{value:t,configurable:!0})}catch{}try{Object.defineProperty(e,"name",{value:(n=r.name)!=null?n:"",configurable:!0})}catch{}try{Object.defineProperty(e,"length",{value:(o=r.length)!=null?o:0,configurable:!0})}catch{}return e}function w(){return{mode:"unavailable",disconnect:()=>{}}}function _(e,t,r){let n=j(e);return o=>{try{let a=r(o);if(a&&(typeof a=="object"||typeof a=="function"))try{Promise.resolve(a).catch(d=>n.log(`${t} listener failed`,d))}catch(d){n.log(`${t} listener failed`,d)}}catch(a){n.log(`${t} listener failed`,a)}}}function Re(e){let t=j(e.logger),r=e.onEvent;if(typeof r!="function")return w();let n=typeof navigator=="undefined"?void 0:navigator,o=n==null?void 0:n.credentials;if(!n||!o)return t.log("webauthn capture unavailable: navigator.credentials missing"),w();let a=o.get,d=o.create;if(typeof a!="function"||typeof d!="function")return t.log("webauthn capture unavailable: credentials methods missing"),w();if(B(a)||B(d))return t.log("webauthn capture unavailable: an equivalent capture is already active"),w();let p=!1,v=0,k=_(t,"webauthn",s=>{if(p)return r(s)}),D=(s,b)=>{let L={invoke(...q){let O=this,R=q[0];if(!p||!Q(R)||!Q(R.publicKey))return Reflect.apply(b,O,q);let Z=R.publicKey,V=`wa-${++v}`,ee=Reflect.apply(b,O,q),f=typeof R.mediation=="string"?R.mediation:void 0,m=Q(R.signal)?R.signal:void 0,C={kind:s,ceremonyId:V,phase:"started",mediation:f,userActivation:Se(n)};if(s==="get"){let c=Z;C.requestOptionsJson=Ce(c,t),C.hasAllowCredentials=Array.isArray(c.allowCredentials)&&c.allowCredentials.length>0}return k(C),Promise.resolve(ee).then(c=>(p&&k({kind:s,ceremonyId:V,phase:"completed",mediation:f,credentialJson:Oe(c,s,t),...(m==null?void 0:m.aborted)===!0?{aborted:!0}:{}}),c),c=>{throw p&&k({kind:s,ceremonyId:V,phase:"failed",mediation:f,error:c,errorName:ke(c),...(m==null?void 0:m.aborted)===!0?{aborted:!0}:{}}),c})}}.invoke;return M(L,{name:s,length:0})},h=(s,b)=>{let L=D(s,b),q=Object.getOwnPropertyDescriptor(o,s);if(q&&(!("value"in q)||q.writable!==!0))return null;try{o[s]=L}catch{return null}return()=>{o[s]===L&&(q?o[s]=q.value:delete o[s])}},S=h("get",a),u=S?h("create",d):null;return!S||!u?(S==null||S(),t.log("webauthn capture unavailable: credentials methods cannot be patched safely"),w()):(p=!0,{mode:"standard",disconnect:()=>{p=!1,S(),u()}})}function Se(e){let t=e.userActivation;return typeof(t==null?void 0:t.isActive)=="boolean"?t.isActive:void 0}function Oe(e,t,r){var n;if(e)try{let o=e,a=o.response,d={id:z(o.id),rawId:$(o.rawId),type:z(o.type),authenticatorAttachment:z(o.authenticatorAttachment),response:t==="create"?{clientDataJSON:$(a==null?void 0:a.clientDataJSON),attestationObject:$(a==null?void 0:a.attestationObject),transports:ae((n=a==null?void 0:a.getTransports)==null?void 0:n.call(a))}:{clientDataJSON:$(a==null?void 0:a.clientDataJSON),authenticatorData:$(a==null?void 0:a.authenticatorData),signature:$(a==null?void 0:a.signature),userHandle:$(a==null?void 0:a.userHandle)},...t==="create"?Ae(o):{}};return JSON.stringify(d)}catch(o){r.log("credential serialization failed",o);return}}function Ce(e,t){try{let r={},n=z(e.rpId),o=z(e.userVerification),a=ae(e.hints);return n!==void 0&&(r.rpId=n),typeof e.timeout=="number"&&Number.isFinite(e.timeout)&&(r.timeout=e.timeout),o!==void 0&&(r.userVerification=o),a!==void 0&&(r.hints=a),r.challenge=$(e.challenge),Array.isArray(e.allowCredentials)&&(r.allowCredentials=Ee(e.allowCredentials)),JSON.stringify(r)}catch(r){t.log("request options serialization failed",r);return}}function Ee(e){let t=[];for(let r of e){if(!Q(r))continue;let n=r;t.push({type:z(n.type),id:$(n.id),transports:ae(n.transports)})}return t}function ae(e){if(Array.isArray(e))return e.filter(t=>typeof t=="string")}function z(e){return typeof e=="string"?e:void 0}function Ae(e){var n;let t=(n=e.getClientExtensionResults)==null?void 0:n.call(e);if(!Q(t))return{};let r=t.credProps;return!r||typeof r.rk!="boolean"?{}:{clientExtensionResults:{credProps:{rk:r.rk}}}}function $(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 r="";for(let n of t)r+=String.fromCharCode(n);return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/g,"")}function Q(e){return e!==null&&(typeof e=="object"||typeof e=="function")}function ke(e){return e instanceof DOMException?e.name:"UnknownError"}var de=0;function le(){return de+=1,`network-${de}`}function Pe(e,t){let r=e.responseType;if(r===""||r==="text")return t(e.responseText);if(r==="json"){let n=e.response;return n==null?void 0:t(n)}}var qe=500,xe=65536,pe=Symbol("body-read-timeout");function fe(e){return/event-stream|octet-stream/i.test(e)?!1:/json|text|urlencoded/i.test(e)}async function he(e,t){let r=e.body;if(!r)return;let n=r.getReader(),o;try{let a=new Promise(v=>{o=setTimeout(()=>v(pe),qe)}),d=new TextDecoder,p=[];for(;;){let v=await Promise.race([n.read(),a]);if(v===pe){t.log("body omitted: read timed out"),n.cancel().catch(()=>{});return}if(v.done)break;p.push(d.decode(v.value,{stream:!0}))}return p.push(d.decode()),p.join("")}catch{n.cancel().catch(()=>{});return}finally{o!==void 0&&clearTimeout(o)}}function ie(e){var t;(t=e==null?void 0:e.body)==null||t.cancel().catch(()=>{})}function se(e,t,r,n,o){try{let a=Object.getOwnPropertyDescriptor(e,t);Le(a,{...n,value:r})&&Object.defineProperty(e,t,n)}catch(a){o.log(`${String(t)} restoration skipped`,a)}}function Le(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 Ne(e){var R,Z,V,ee;let{onExchange:t,onRequest:r,match:n}=e;if(typeof t!="function"||typeof n!="function")throw new TypeError("captureNetwork requires onExchange and match callbacks");let o=j(e.logger),a=(R=e.transports)!=null?R:["xhr","fetch"],d=(Z=e.bodyCapture)==null?void 0:Z.request,p=(V=e.bodyCapture)==null?void 0:V.response,v=typeof d=="function",k=typeof p=="function",D=(ee=e.maxBodyChars)!=null?ee:xe,h=!1,S=_(o,"network",f=>{if(h)return t(f)}),u=_(o,"network request",f=>{if(h)return r==null?void 0:r(f)}),s=(f,m)=>{let C=m.direction==="request"?d:p;if(typeof C=="function")try{let c=C(f,m);if(c===void 0||typeof(c==null?void 0:c.then)=="function")return;let F=JSON.stringify(c);return F===void 0||F.length>D?void 0:JSON.parse(F)}catch(c){o.log("body projection failed",c);return}},b=(f,m)=>{try{let C=typeof document!="undefined"?document.baseURI:void 0,c=new URL(String(f),C);return/\/observe\/events\/[^/]+\/?$/.test(c.pathname)?void 0:n(c,m)===!0?{url:c.href,path:c.pathname}:void 0}catch{return}},L=()=>{if(typeof XMLHttpRequest=="undefined")return o.log("xhr capture unavailable: XMLHttpRequest missing"),null;let f=XMLHttpRequest.prototype,m=Object.getOwnPropertyDescriptor(f,"open"),C=Object.getOwnPropertyDescriptor(f,"send");if(!m||!("value"in m)||typeof m.value!="function"||!C||!("value"in C)||typeof C.value!="function")return o.log("xhr capture unavailable: XMLHttpRequest methods missing"),null;let c=m.value,F=C.value;if(B(c)||B(F))return o.log("xhr capture unavailable: an equivalent capture is already active"),null;let X=new WeakMap,A=new Set,x=!1,T={open(...E){var g;let i=Reflect.apply(c,this,E);if(!h||!x)return i;try{let N=X.get(this);N&&(N.reported=!0,(g=N.cleanupListeners)==null||g.call(N));let H=String(E[0]).toUpperCase(),P=b(E[1],H);P?X.set(this,{method:H,url:P.url,path:P.path,reported:!1}):X.delete(this)}catch(N){o.log("xhr open observation failed",N)}return i}}.open,J={send(...E){let i=h&&x?X.get(this):void 0;if(!i||i.reported||i.cleanupListeners)return Reflect.apply(F,this,E);let g=()=>{},N=!1;try{i.requestId=le();let P={direction:"request",transport:"xhr",method:i.method,url:i.url,path:i.path};i.requestBody=v&&typeof E[0]=="string"?s(E[0],P):void 0;let G=()=>{i.outcome===void 0&&(i.outcome="abort")},Y=()=>{i.outcome===void 0&&(i.outcome="timeout")},l=()=>{i.outcome===void 0&&(i.outcome="error")},y=()=>{var K;g();try{if(!h||!x||i.reported||!i.requestId)return;i.reported=!0;let U={direction:"response",transport:"xhr",method:i.method,url:i.url,path:i.path},re=this.status,ue={requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,status:re,ok:re>=200&&re<300,outcome:(K=i.outcome)!=null?K:"load",durationMs:i.startedAt!==void 0?Date.now()-i.startedAt:void 0,responseUrl:this.responseURL||void 0,requestBody:i.requestBody,responseBody:k?Pe(this,be=>s(be,U)):void 0};i.requestEmitted?S(ue):i.pendingExchange=ue}catch(U){o.log("xhr exchange observation failed",U)}finally{X.get(this)===i&&X.delete(this)}},I=()=>{try{this.readyState===XMLHttpRequest.DONE&&this.status!==0&&y()}catch(K){o.log("xhr ready-state observation failed",K)}},W=()=>y();g=()=>{for(let[K,U]of[["readystatechange",I],["abort",G],["timeout",Y],["error",l],["loadend",W]])this.removeEventListener(K,U);i.cleanupListeners===g&&(i.cleanupListeners=void 0),A.delete(g)},this.addEventListener("readystatechange",I),this.addEventListener("abort",G),this.addEventListener("timeout",Y),this.addEventListener("error",l),this.addEventListener("loadend",W),i.cleanupListeners=g,A.add(g),i.startedAt=Date.now(),N=!0}catch(P){o.log("xhr send observation failed",P),g()}let H;try{H=Reflect.apply(F,this,E)}catch(P){throw g(),P}if(N&&i.requestId!==void 0&&i.startedAt!==void 0){i.requestEmitted=!0,u({requestId:i.requestId,transport:"xhr",method:i.method,url:i.url,path:i.path,startedAt:i.startedAt,requestBody:i.requestBody});let P=i.pendingExchange;i.pendingExchange=void 0,P&&S(P)}return H}}.send;return M(T,c),M(J,F),m.writable!==!0||C.writable!==!0?(o.log("xhr capture unavailable: XMLHttpRequest methods are not writable"),null):(Object.defineProperty(f,"open",{...m,value:T}),Object.defineProperty(f,"send",{...C,value:J}),x=!0,()=>{x=!1;for(let E of[...A])E();se(f,"open",T,m,o),se(f,"send",J,C,o)})},q=()=>{let f=globalThis,m=Object.getOwnPropertyDescriptor(f,"fetch");if(!m||!("value"in m))return o.log("fetch capture unavailable: fetch is not an own data property"),null;let C=m.value;if(typeof C!="function")return o.log("fetch capture unavailable: fetch missing"),null;if(B(C))return o.log("fetch capture unavailable: an equivalent capture is already active"),null;let c=!1,F=async(A,x,T,J)=>{try{if(!h||!c){ie(T);return}let[E,i]=await Promise.all([J!=null?J:Promise.resolve(void 0),T?he(T,o):Promise.resolve(void 0)]);if(!h||!c)return;let g={direction:"request",transport:"fetch",method:A.method,url:A.url,path:A.path},N={...g,direction:"response"},H=A.requestBody;H===void 0&&E!==void 0&&(H=s(E,g));let P=i!==void 0?s(i,N):void 0;S({requestId:A.requestId,transport:"fetch",method:A.method,url:A.url,path:A.path,status:x.status,ok:x.ok,outcome:"load",durationMs:Date.now()-A.startedAt,responseUrl:x.url||void 0,requestBody:H,responseBody:P})}catch(E){o.log("fetch exchange observation failed",E)}},X={fetch(...A){var H,P,G,Y;let x;try{let l=A[0],y=A[1];if(h&&c&&v&&typeof Request!="undefined"&&l instanceof Request&&(y==null?void 0:y.body)===void 0&&l.body){let I=String((H=y==null?void 0:y.method)!=null?H:l.method).toUpperCase(),W=(P=l.headers.get("content-type"))!=null?P:"";b(l.url,I)&&fe(W)&&(x=l.clone())}}catch(l){o.log("fetch observation failed",l)}let T,J=Date.now();try{T=Reflect.apply(C,this,A)}catch(l){throw ie(x),l}let E,i;try{if(h&&c){let l=A[0],y=A[1],I=typeof Request!="undefined"&&l instanceof Request,W=String((G=y==null?void 0:y.method)!=null?G:I?l.method:"GET").toUpperCase(),K=I?l.url:l,U=b(K,W);U&&(E={requestId:le(),method:W,url:U.url,path:U.path,startedAt:J},i=(Y=y==null?void 0:y.signal)!=null?Y:I?l.signal:void 0,v&&typeof(y==null?void 0:y.body)=="string"&&(E.requestBody=s(y.body,{direction:"request",transport:"fetch",method:W,url:U.url,path:U.path})))}}catch(l){o.log("fetch observation failed",l)}if(!E)return ie(x),T;let g=E;u({requestId:g.requestId,transport:"fetch",method:g.method,url:g.url,path:g.path,startedAt:g.startedAt,requestBody:g.requestBody});let N=x?he(x,o):void 0;return T.then(l=>{var I;let y;try{if(h&&c&&k){let W=(I=l.headers.get("content-type"))!=null?I:"";fe(W)&&l.body&&(y=l.clone())}}catch{}F(g,l,y,N)},l=>{try{if(!h||!c)return;let y=(i==null?void 0:i.aborted)===!0||(l==null?void 0:l.name)==="AbortError"||typeof DOMException!="undefined"&&l instanceof DOMException&&l.code===DOMException.ABORT_ERR;S({requestId:g.requestId,transport:"fetch",method:g.method,url:g.url,path:g.path,status:0,ok:!1,outcome:y?"abort":"error",durationMs:Date.now()-g.startedAt})}catch(y){o.log("fetch failure observation failed",y)}}),T}}.fetch;return M(X,C),m.writable!==!0?(o.log("fetch capture unavailable: fetch is not writable"),null):(Object.defineProperty(f,"fetch",{...m,value:X}),c=!0,()=>{c=!1,se(f,"fetch",X,m,o)})},O=[];if(a.includes("xhr")){let f=L();f&&O.push(f)}if(a.includes("fetch")){let f=q();f&&O.push(f)}return O.length===0?w():(h=!0,{mode:"standard",disconnect:()=>{h=!1;for(let f of O.splice(0))f()}})}function De(e){let t=j(),r;try{if(t=j(e.logger),r=e.onNavigate,typeof r!="function")return w()}catch{return w()}let n,o,a,d,p,v;try{if(n=window,o=n==null?void 0:n.history,!n||!o)return t.log("navigation capture unavailable: window/history missing"),w();if(p=ge(o,"pushState"),v=ge(o,"replaceState"),!p||!v)return t.log("navigation capture unavailable: history method descriptors missing"),w();if(a=p.descriptor.value,d=v.descriptor.value,typeof a!="function"||typeof d!="function")return t.log("navigation capture unavailable: history methods missing"),w();if(typeof n.addEventListener!="function"||typeof n.removeEventListener!="function")return t.log("navigation capture unavailable: window event methods missing"),w()}catch(O){return t.log("navigation capture unavailable: history inspection failed",O),w()}if(B(a)||B(d))return t.log("navigation capture unavailable: an equivalent capture is already active"),w();let k=!1,D=()=>({path:n.location.pathname,search:n.location.search,hash:n.location.hash}),h;try{h=D()}catch(O){return t.log("navigation capture unavailable: location inspection failed",O),w()}let S=_(t,"navigation",O=>{if(k)return r(O)}),u=O=>{if(k)try{let R=D();if(R.path===h.path&&R.search===h.search&&R.hash===h.hash)return;h=R,S({...R,source:O})}catch(R){t.log("navigation observation failed",R)}},s={pushState(...O){let R=Reflect.apply(a,this,O);return u("push"),R}}.pushState,b={replaceState(...O){let R=Reflect.apply(d,this,O);return u("replace"),R}}.replaceState;M(s,a),M(b,d);let L=()=>u("pop"),q=()=>u("hash");try{if(ye(p,s),ye(v,b),o.pushState!==s||o.replaceState!==b)throw new Error("history method replacement did not take effect");n.addEventListener("popstate",L),n.addEventListener("hashchange",q),k=!0}catch(O){return k=!1,ne(n,"popstate",L,t),ne(n,"hashchange",q,t),te(v,b,t),te(p,s,t),t.log("navigation capture unavailable: history methods are not writable",O),r=()=>{},S=()=>{},t=j(),w()}return{mode:"standard",disconnect:()=>{k=!1,r=()=>{},ne(n,"popstate",L,t),ne(n,"hashchange",q,t),te(p,s,t),te(v,b,t),S=()=>{},t=j()}}}function ge(e,t){let r=e;for(;r;){let n=Object.getOwnPropertyDescriptor(r,t);if(n)return!("value"in n)||typeof n.value!="function"?null:{owner:r,key:t,descriptor:n};r=Object.getPrototypeOf(r)}return null}function ye(e,t){if(e.descriptor.writable!==!0)throw new TypeError(`${e.key} is not writable`);let r=Object.getOwnPropertyDescriptor(e.owner,e.key);if(!me(r,e.descriptor))throw new Error(`${e.key} changed during installation`);Object.defineProperty(e.owner,e.key,{...e.descriptor,value:t})}function me(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 te(e,t,r){try{let n=Object.getOwnPropertyDescriptor(e.owner,e.key);me(n,{...e.descriptor,value:t})&&Object.defineProperty(e.owner,e.key,e.descriptor)}catch(n){r.log(`navigation ${e.key} restoration skipped`,n)}}function ne(e,t,r,n){try{e.removeEventListener(t,r)}catch(o){n.log(`navigation ${t} listener removal skipped`,o)}}function Te(e){var S;let t=(S=e.logger)!=null?S:oe,r=e.onAttached;if(typeof r!="function")return w();let n=Element.prototype,o=Object.getOwnPropertyDescriptor(n,"attachShadow");if(!o||typeof o.value!="function")return t.log("shadow-root capture unavailable: attachShadow not supported"),w();let a=o.value;if(B(a))return t.log("shadow-root capture unavailable: an equivalent capture is already active"),w();let d=!1,p=new WeakSet,v=_(t,"shadow-root",r),k=(u,s)=>{!d||p.has(s)||(p.add(s),v({host:u,shadowRoot:s,mode:s.mode==="closed"?"closed":"open"}))},D={attachShadow(...u){let s=Reflect.apply(a,this,u);return k(this,s),s}}.attachShadow;M(D,a);try{Object.defineProperty(n,"attachShadow",{...o,value:D})}catch(u){return t.log("shadow-root capture unavailable: attachShadow is locked",u),w()}d=!0;let h=u=>{let s=u.querySelectorAll("*");for(let b=0;b<s.length&&d;b++){let L=s[b].shadowRoot;L&&(k(s[b],L),h(L))}};return e.includeExisting&&h(document),{mode:"standard",disconnect:()=>{d=!1;let u=Object.getOwnPropertyDescriptor(n,"attachShadow");je(u,{...o,value:D})&&Object.defineProperty(n,"attachShadow",o)}}}function He(e){var t;return(t=ve(e)[0])!=null?t:null}function ve(e){let t=e.composedPath();return t.length>0?t:e.target?[e.target]:[]}function je(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 Be(e){var D,h,S;let t=j(e==null?void 0:e.logger),r=e==null?void 0:e.read,n=e==null?void 0:e.onState;if(typeof r!="function"||typeof n!="function")return we();let o=Number.isFinite(e.delayMs)&&((D=e.delayMs)!=null?D:0)>=0&&(h=e.delayMs)!=null?h:0,a=_(t,"ui-state",n),d=!0,p,v=[],k=()=>{d&&(p!==void 0&&clearTimeout(p),p=setTimeout(()=>{if(p=void 0,!!d)try{let u=r();u!==void 0&&a(u)}catch(u){t.log("ui-state read failed",u)}},o))};try{for(let u of(S=e.triggers)!=null?S:[]){if(!(u!=null&&u.target)||typeof u.target.addEventListener!="function")continue;let s=k;u.target.addEventListener(u.type,s,u.capture===!0),v.push({trigger:u,listener:s})}}catch(u){t.log("ui-state trigger installation failed",u);for(let{trigger:s,listener:b}of v)try{s.target.removeEventListener(s.type,b,s.capture===!0)}catch{}return d=!1,we()}return{mode:"standard",schedule:k,disconnect:()=>{if(d){d=!1,p!==void 0&&clearTimeout(p),p=void 0;for(let{trigger:u,listener:s}of v)try{u.target.removeEventListener(u.type,s,u.capture===!0)}catch(b){t.log("ui-state trigger removal failed",b)}v.length=0}}}}function we(){return{mode:"unavailable",schedule:()=>{},disconnect:()=>{}}}function Ie(e="[autocapture]"){return{log:(...t)=>{try{let r=console.log(e,...t);if(r&&(typeof r=="object"||typeof r=="function"))try{Promise.resolve(r).catch(()=>{})}catch{}}catch{}}}}var Ue="0.1.0-next.2-dd9eb36";export{Ue as VERSION,De as captureNavigation,Ne as captureNetwork,Te as captureShadowRoots,Be as captureUiState,Re as captureWebAuthn,ve as composedPath,He as composedTarget,Ie as createConsoleLogger};
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@corbado/autocapture",
3
+ "version": "0.1.0-next.2-dd9eb36",
4
+ "description": "Observer-style capture helpers for WebAuthn ceremonies, selected network exchanges, in-app navigation, shadow roots, and rendered UI state.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --target es2019 --dts --minify --define.__CORBADO_AUTOCAPTURE_VERSION__='\"'${CORBADO_AUTOCAPTURE_VERSION:-0.0.0-dev}'\"'",
13
+ "dev": "tsup src/index.ts --format cjs,esm --target es2019 --watch --dts",
14
+ "lint": "tsc --noEmit",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:e2e": "pnpm build && playwright test --config e2e/playwright.config.ts",
18
+ "check:dist": "es-check es2019 './dist/index.js' && es-check es2019 './dist/index.mjs' --module",
19
+ "format": "prettier --write 'src/**/*.ts' 'e2e/**/*.ts'",
20
+ "format:check": "prettier --check 'src/**/*.ts' 'e2e/**/*.ts'"
21
+ },
22
+ "devDependencies": {
23
+ "@playwright/test": "^1.57.0",
24
+ "@types/node": "^20",
25
+ "es-check": "latest",
26
+ "eslint-config-prettier": "^10.1.8",
27
+ "jsdom": "latest",
28
+ "prettier": "^3.7.4",
29
+ "tsup": "latest",
30
+ "typescript": "latest",
31
+ "typescript-eslint": "^8.55.0",
32
+ "vitest": "^4.1.0"
33
+ }
34
+ }