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