@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 +174 -0
- package/dist/index.d.mts +378 -0
- package/dist/index.d.ts +378 -0
- package/dist/index.js +1 -0
- package/dist/index.mjs +1 -0
- package/package.json +34 -0
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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.d.ts
ADDED
|
@@ -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
|
+
}
|