@corbado/autocapture 0.1.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 ADDED
@@ -0,0 +1,89 @@
1
+ # @corbado/autocapture
2
+
3
+ Observe browser authentication signals without changing application code.
4
+
5
+ `@corbado/autocapture` provides small, focused observers for the browser APIs that authentication
6
+ flows run through. Each observer reports a well-defined stream of events while leaving the
7
+ application — and any other code sharing the same APIs — completely undisturbed. It is designed to
8
+ be dropped into a page it does not own and behave as a careful guest.
9
+
10
+ The package observes three kinds of signal:
11
+
12
+ - **WebAuthn ceremonies** — the lifecycle of `navigator.credentials.get`/`create` passkey
13
+ ceremonies, from invocation to settlement, including silent cancellations.
14
+ - **Network exchanges** — request/response round trips over `XMLHttpRequest` and `fetch`, limited to
15
+ URLs a caller-provided predicate selects.
16
+ - **In-app navigation** — same-document (SPA) route changes.
17
+
18
+ It has no runtime dependencies, ships TypeScript type definitions, and targets ES2019 so it parses
19
+ on older engines.
20
+
21
+ ## Installation
22
+
23
+ ```sh
24
+ npm install @corbado/autocapture
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Every observer follows the platform observer pattern (`MutationObserver`, `ResizeObserver`): create
30
+ it with a callback, receive events, and call `disconnect()` when finished.
31
+
32
+ ```ts
33
+ import { captureWebAuthn, captureNetwork, captureNavigation } from "@corbado/autocapture";
34
+
35
+ const webauthn = captureWebAuthn({
36
+ onEvent: (event) => console.log(event.kind, event.phase, event.ceremonyId),
37
+ });
38
+
39
+ const network = captureNetwork({
40
+ match: (url) => url.origin === location.origin && url.pathname.startsWith("/auth-api/"),
41
+ onExchange: (exchange) => console.log(exchange.method, exchange.path, exchange.status, exchange.outcome),
42
+ });
43
+
44
+ const navigation = captureNavigation({
45
+ onNavigate: (event) => console.log(event.source, event.path),
46
+ });
47
+
48
+ // when finished
49
+ webauthn.disconnect();
50
+ network.disconnect();
51
+ navigation.disconnect();
52
+ ```
53
+
54
+ Each call returns a `CaptureHandle` with a `mode` (`"standard"`, `"compatibility"`, or
55
+ `"unavailable"`) and a `disconnect()` method. Full event and option types ship with the package.
56
+
57
+ ## Guarantees
58
+
59
+ The observers run inside application call paths, so their behaviour is defined by what they promise
60
+ never to disturb:
61
+
62
+ - **The application always comes first.** Original browser methods are invoked with their arguments,
63
+ results, and exceptions unchanged. Every observation path is guarded, so a fault in an observer or
64
+ in a caller's own callback can never surface into the application's call.
65
+ - **Coexistence, not ownership.** Password managers, monitoring SDKs, and frameworks routinely
66
+ instrument the same APIs. The observers share those surfaces rather than seize them: other
67
+ integrations keep working regardless of load order, and each handle's `mode` reports how it
68
+ attached. Where attaching cleanly is impossible, an observer reports `mode: "unavailable"` rather
69
+ than throwing.
70
+ - **Exactly-once reporting.** A single application action can pass through several layered
71
+ integrations; each ceremony, exchange, and navigation is reported once, correlated by id where its
72
+ lifecycle spans multiple events.
73
+ - **Clean removal.** `disconnect()` stops delivery immediately and removes the observer's
74
+ instrumentation without disrupting the application or any integration layered on top.
75
+
76
+ ## Data handling
77
+
78
+ Network observation is designed for minimal data exposure:
79
+
80
+ - Only URLs a caller's `match` predicate explicitly selects are observed; all other traffic is
81
+ untouched.
82
+ - Exchanges carry **metadata only** (method, URL, status, outcome, duration) by default. Request and
83
+ response bodies are captured only when a caller opts in per direction.
84
+ - When bodies are captured, a built-in credential redactor always runs and cannot be disabled;
85
+ callers may add further redaction but cannot re-expose what the built-in removed. Bodies are bounded
86
+ in size and read from clones, so the application's own body consumption is never disturbed, and
87
+ streaming and binary payloads are never read.
88
+
89
+ WebAuthn payloads are reported in the standard `PublicKeyCredential.toJSON()` shape.
@@ -0,0 +1,193 @@
1
+ /** Minimal logging seam. All capture modules are silent unless a logger is provided. */
2
+ type Logger = {
3
+ log: (...args: unknown[]) => void;
4
+ };
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
+ */
10
+ type CaptureHandle = {
11
+ /**
12
+ * Whether and how the capture attached:
13
+ * - `"standard"`: attached and delivering events.
14
+ * - `"compatibility"`: attached via a fallback path (another integration held
15
+ * the primary one) and delivering events.
16
+ * - `"unavailable"`: could not attach (the environment disallows it, or an
17
+ * equivalent capture is already active); no events are delivered and
18
+ * `disconnect()` is a no-op.
19
+ */
20
+ readonly mode: "standard" | "compatibility" | "unavailable";
21
+ /**
22
+ * Stop event delivery immediately and permanently, removing the capture's
23
+ * instrumentation without disrupting the application or other integrations
24
+ * sharing the same API. Safe to call more than once.
25
+ */
26
+ disconnect(): void;
27
+ };
28
+ type WebAuthnCeremonyKind = "get" | "create";
29
+ /**
30
+ * One lifecycle event of an observed WebAuthn ceremony
31
+ * (`navigator.credentials.get`/`create` invoked with `publicKey` options).
32
+ * `started` is delivered when the application invokes the API,
33
+ * `completed`/`failed` when the returned promise settles.
34
+ */
35
+ type WebAuthnCeremonyEvent = {
36
+ kind: WebAuthnCeremonyKind;
37
+ /** Correlates the `started` event with the `completed`/`failed` event of the same ceremony. */
38
+ ceremonyId: string;
39
+ phase: "started" | "completed" | "failed";
40
+ /** Mediation requirement exactly as passed by the application (undefined = platform default). */
41
+ mediation?: string;
42
+ /** Whether transient user activation was active when the ceremony began (when the platform exposes it). */
43
+ userActivation?: boolean;
44
+ /** started, get only: request options as WebAuthn JSON (binary fields base64url-encoded). */
45
+ requestOptionsJson?: string;
46
+ /** started, get only: the request options carried a non-empty allowCredentials list. */
47
+ hasAllowCredentials?: boolean;
48
+ /** completed: the resulting credential as WebAuthn JSON (assertion or attestation shape, by `kind`). */
49
+ credentialJson?: string;
50
+ /** failed: the raw rejection value. */
51
+ error?: unknown;
52
+ /** failed: DOMException name of the rejection, e.g. NotAllowedError / AbortError. */
53
+ errorName?: string;
54
+ /** failed: the ceremony's own AbortSignal was aborted before the promise settled. */
55
+ aborted?: boolean;
56
+ };
57
+ type WebAuthnCaptureOptions = {
58
+ onEvent: (event: WebAuthnCeremonyEvent) => void;
59
+ logger?: Logger;
60
+ };
61
+ type NetworkTransport = "xhr" | "fetch";
62
+ /** One observed request/response round trip on a matched URL. */
63
+ type HttpExchange = {
64
+ transport: NetworkTransport;
65
+ method: string;
66
+ /** Resolved absolute request URL. */
67
+ url: string;
68
+ /** Pathname of the resolved request URL. */
69
+ path: string;
70
+ /** HTTP status; 0 when the request did not produce a response (abort, timeout, network error). */
71
+ status: number;
72
+ ok: boolean;
73
+ /** How the request settled: a normal response, or an abort / timeout / network error. */
74
+ outcome: "load" | "abort" | "timeout" | "error";
75
+ durationMs?: number;
76
+ /** Final response URL when the platform exposes it (differs from `url` after redirects). */
77
+ responseUrl?: string;
78
+ requestBody?: unknown;
79
+ responseBody?: unknown;
80
+ };
81
+ type NetworkCaptureOptions = {
82
+ /**
83
+ * Selects which requests to report. Called with the request URL resolved
84
+ * against the document base URL and the uppercased method. Only matched
85
+ * requests are observed; everything else passes through untouched.
86
+ */
87
+ match: (url: URL, method: string) => boolean;
88
+ onExchange: (exchange: HttpExchange) => void;
89
+ /** Which transports to observe. Defaults to both. */
90
+ transports?: NetworkTransport[];
91
+ /**
92
+ * Opt in to capturing request and/or response bodies. Both default to
93
+ * `false`: an exchange carries only metadata (method, URL, status, outcome,
94
+ * duration, responseUrl) unless a direction is explicitly enabled here.
95
+ * Matched URLs are auth endpoints whose bodies carry credentials, so body
96
+ * capture is never on by default. When enabled, captured bodies still pass
97
+ * through the built-in credential redactor (always) and any `redact` hook.
98
+ */
99
+ captureBodies?: {
100
+ request?: boolean;
101
+ response?: boolean;
102
+ };
103
+ /**
104
+ * Converts a raw body into the `requestBody`/`responseBody` value. The
105
+ * default parses JSON strings and reports everything else as undefined.
106
+ * Return undefined to omit the body from the exchange. Only consulted for a
107
+ * direction enabled via `captureBodies`.
108
+ */
109
+ parseBody?: (raw: unknown, direction: "request" | "response") => unknown;
110
+ /**
111
+ * Additional redaction applied to a parsed body before it is reported. Runs
112
+ * AFTER the built-in credential-key redactor, so it can remove more but can
113
+ * never re-expose what the built-in removed. Return the (possibly mutated)
114
+ * value, or undefined to drop the body entirely.
115
+ */
116
+ redact?: (body: unknown, direction: "request" | "response") => unknown;
117
+ /**
118
+ * Upper bound on a captured body's size, in characters. Bodies larger than
119
+ * this (by declared content-length or actual length) are omitted rather than
120
+ * buffered and reported. Applies to both transports. Defaults to 65536.
121
+ */
122
+ maxBodyChars?: number;
123
+ logger?: Logger;
124
+ };
125
+ type NavigationSource = "push" | "replace" | "pop" | "hash";
126
+ /** One in-app (same-document) navigation. */
127
+ type NavigationEvent = {
128
+ path: string;
129
+ search: string;
130
+ hash: string;
131
+ source: NavigationSource;
132
+ };
133
+ type NavigationCaptureOptions = {
134
+ onNavigate: (event: NavigationEvent) => void;
135
+ logger?: Logger;
136
+ };
137
+
138
+ /**
139
+ * Observe WebAuthn ceremonies. Each `navigator.credentials.get`/`create` call
140
+ * made with `publicKey` options produces a `started` event when it is invoked
141
+ * and a `completed` or `failed` event when it settles, correlated by
142
+ * `ceremonyId` — including silent cancellations that surface no UI. Calls
143
+ * without `publicKey` (WebOTP, federated or password credentials) are never
144
+ * reported and pass through untouched.
145
+ *
146
+ * Coexists with password managers and other integrations that instrument the
147
+ * same API: the application and any such integration keep working unchanged,
148
+ * and `mode` reports how the observation attached. In the rare case that
149
+ * another integration replaces the credentials container entirely, ceremonies
150
+ * routed solely through it are not observed.
151
+ *
152
+ * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
153
+ */
154
+ declare function captureWebAuthn(options: WebAuthnCaptureOptions): CaptureHandle;
155
+
156
+ /**
157
+ * Observe request/response round trips over `XMLHttpRequest` and/or `fetch`.
158
+ * Each request accepted by the `match` predicate produces one
159
+ * {@link HttpExchange}; every other request is left untouched. Observation
160
+ * never affects the application's own request — its arguments, result, and
161
+ * errors are unchanged even if a capture callback fails.
162
+ *
163
+ * Exchanges carry metadata only unless body capture is opted in via
164
+ * `captureBodies`; see {@link NetworkCaptureOptions} for the body, redaction,
165
+ * and size controls.
166
+ *
167
+ * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
168
+ */
169
+ declare function captureNetwork(options: NetworkCaptureOptions): CaptureHandle;
170
+
171
+ /**
172
+ * Observe same-document (SPA) navigations: `pushState`, `replaceState`, and
173
+ * back/forward changes. Each produces one {@link NavigationEvent} carrying the
174
+ * path, search, hash, and how it occurred. The application's router runs first
175
+ * and is unaffected by observation; repeated navigations to the same location
176
+ * are reported once.
177
+ *
178
+ * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
179
+ */
180
+ declare function captureNavigation(options: NavigationCaptureOptions): CaptureHandle;
181
+
182
+ /**
183
+ * Console-backed logger for development and debugging. The captures run inside
184
+ * application call paths (credentials.get, pushState, fetch), so a
185
+ * page-overridden or unavailable console must never break them — every call is
186
+ * guarded.
187
+ */
188
+ declare function createConsoleLogger(prefix?: string): Logger;
189
+
190
+ /** Package version, injected at build time; "0.0.0-dev" in unbuilt contexts (tests, watch mode). */
191
+ declare const VERSION: string;
192
+
193
+ export { type CaptureHandle, type HttpExchange, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkCaptureOptions, type NetworkTransport, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyEvent, type WebAuthnCeremonyKind, captureNavigation, captureNetwork, captureWebAuthn, createConsoleLogger };
@@ -0,0 +1,193 @@
1
+ /** Minimal logging seam. All capture modules are silent unless a logger is provided. */
2
+ type Logger = {
3
+ log: (...args: unknown[]) => void;
4
+ };
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
+ */
10
+ type CaptureHandle = {
11
+ /**
12
+ * Whether and how the capture attached:
13
+ * - `"standard"`: attached and delivering events.
14
+ * - `"compatibility"`: attached via a fallback path (another integration held
15
+ * the primary one) and delivering events.
16
+ * - `"unavailable"`: could not attach (the environment disallows it, or an
17
+ * equivalent capture is already active); no events are delivered and
18
+ * `disconnect()` is a no-op.
19
+ */
20
+ readonly mode: "standard" | "compatibility" | "unavailable";
21
+ /**
22
+ * Stop event delivery immediately and permanently, removing the capture's
23
+ * instrumentation without disrupting the application or other integrations
24
+ * sharing the same API. Safe to call more than once.
25
+ */
26
+ disconnect(): void;
27
+ };
28
+ type WebAuthnCeremonyKind = "get" | "create";
29
+ /**
30
+ * One lifecycle event of an observed WebAuthn ceremony
31
+ * (`navigator.credentials.get`/`create` invoked with `publicKey` options).
32
+ * `started` is delivered when the application invokes the API,
33
+ * `completed`/`failed` when the returned promise settles.
34
+ */
35
+ type WebAuthnCeremonyEvent = {
36
+ kind: WebAuthnCeremonyKind;
37
+ /** Correlates the `started` event with the `completed`/`failed` event of the same ceremony. */
38
+ ceremonyId: string;
39
+ phase: "started" | "completed" | "failed";
40
+ /** Mediation requirement exactly as passed by the application (undefined = platform default). */
41
+ mediation?: string;
42
+ /** Whether transient user activation was active when the ceremony began (when the platform exposes it). */
43
+ userActivation?: boolean;
44
+ /** started, get only: request options as WebAuthn JSON (binary fields base64url-encoded). */
45
+ requestOptionsJson?: string;
46
+ /** started, get only: the request options carried a non-empty allowCredentials list. */
47
+ hasAllowCredentials?: boolean;
48
+ /** completed: the resulting credential as WebAuthn JSON (assertion or attestation shape, by `kind`). */
49
+ credentialJson?: string;
50
+ /** failed: the raw rejection value. */
51
+ error?: unknown;
52
+ /** failed: DOMException name of the rejection, e.g. NotAllowedError / AbortError. */
53
+ errorName?: string;
54
+ /** failed: the ceremony's own AbortSignal was aborted before the promise settled. */
55
+ aborted?: boolean;
56
+ };
57
+ type WebAuthnCaptureOptions = {
58
+ onEvent: (event: WebAuthnCeremonyEvent) => void;
59
+ logger?: Logger;
60
+ };
61
+ type NetworkTransport = "xhr" | "fetch";
62
+ /** One observed request/response round trip on a matched URL. */
63
+ type HttpExchange = {
64
+ transport: NetworkTransport;
65
+ method: string;
66
+ /** Resolved absolute request URL. */
67
+ url: string;
68
+ /** Pathname of the resolved request URL. */
69
+ path: string;
70
+ /** HTTP status; 0 when the request did not produce a response (abort, timeout, network error). */
71
+ status: number;
72
+ ok: boolean;
73
+ /** How the request settled: a normal response, or an abort / timeout / network error. */
74
+ outcome: "load" | "abort" | "timeout" | "error";
75
+ durationMs?: number;
76
+ /** Final response URL when the platform exposes it (differs from `url` after redirects). */
77
+ responseUrl?: string;
78
+ requestBody?: unknown;
79
+ responseBody?: unknown;
80
+ };
81
+ type NetworkCaptureOptions = {
82
+ /**
83
+ * Selects which requests to report. Called with the request URL resolved
84
+ * against the document base URL and the uppercased method. Only matched
85
+ * requests are observed; everything else passes through untouched.
86
+ */
87
+ match: (url: URL, method: string) => boolean;
88
+ onExchange: (exchange: HttpExchange) => void;
89
+ /** Which transports to observe. Defaults to both. */
90
+ transports?: NetworkTransport[];
91
+ /**
92
+ * Opt in to capturing request and/or response bodies. Both default to
93
+ * `false`: an exchange carries only metadata (method, URL, status, outcome,
94
+ * duration, responseUrl) unless a direction is explicitly enabled here.
95
+ * Matched URLs are auth endpoints whose bodies carry credentials, so body
96
+ * capture is never on by default. When enabled, captured bodies still pass
97
+ * through the built-in credential redactor (always) and any `redact` hook.
98
+ */
99
+ captureBodies?: {
100
+ request?: boolean;
101
+ response?: boolean;
102
+ };
103
+ /**
104
+ * Converts a raw body into the `requestBody`/`responseBody` value. The
105
+ * default parses JSON strings and reports everything else as undefined.
106
+ * Return undefined to omit the body from the exchange. Only consulted for a
107
+ * direction enabled via `captureBodies`.
108
+ */
109
+ parseBody?: (raw: unknown, direction: "request" | "response") => unknown;
110
+ /**
111
+ * Additional redaction applied to a parsed body before it is reported. Runs
112
+ * AFTER the built-in credential-key redactor, so it can remove more but can
113
+ * never re-expose what the built-in removed. Return the (possibly mutated)
114
+ * value, or undefined to drop the body entirely.
115
+ */
116
+ redact?: (body: unknown, direction: "request" | "response") => unknown;
117
+ /**
118
+ * Upper bound on a captured body's size, in characters. Bodies larger than
119
+ * this (by declared content-length or actual length) are omitted rather than
120
+ * buffered and reported. Applies to both transports. Defaults to 65536.
121
+ */
122
+ maxBodyChars?: number;
123
+ logger?: Logger;
124
+ };
125
+ type NavigationSource = "push" | "replace" | "pop" | "hash";
126
+ /** One in-app (same-document) navigation. */
127
+ type NavigationEvent = {
128
+ path: string;
129
+ search: string;
130
+ hash: string;
131
+ source: NavigationSource;
132
+ };
133
+ type NavigationCaptureOptions = {
134
+ onNavigate: (event: NavigationEvent) => void;
135
+ logger?: Logger;
136
+ };
137
+
138
+ /**
139
+ * Observe WebAuthn ceremonies. Each `navigator.credentials.get`/`create` call
140
+ * made with `publicKey` options produces a `started` event when it is invoked
141
+ * and a `completed` or `failed` event when it settles, correlated by
142
+ * `ceremonyId` — including silent cancellations that surface no UI. Calls
143
+ * without `publicKey` (WebOTP, federated or password credentials) are never
144
+ * reported and pass through untouched.
145
+ *
146
+ * Coexists with password managers and other integrations that instrument the
147
+ * same API: the application and any such integration keep working unchanged,
148
+ * and `mode` reports how the observation attached. In the rare case that
149
+ * another integration replaces the credentials container entirely, ceremonies
150
+ * routed solely through it are not observed.
151
+ *
152
+ * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
153
+ */
154
+ declare function captureWebAuthn(options: WebAuthnCaptureOptions): CaptureHandle;
155
+
156
+ /**
157
+ * Observe request/response round trips over `XMLHttpRequest` and/or `fetch`.
158
+ * Each request accepted by the `match` predicate produces one
159
+ * {@link HttpExchange}; every other request is left untouched. Observation
160
+ * never affects the application's own request — its arguments, result, and
161
+ * errors are unchanged even if a capture callback fails.
162
+ *
163
+ * Exchanges carry metadata only unless body capture is opted in via
164
+ * `captureBodies`; see {@link NetworkCaptureOptions} for the body, redaction,
165
+ * and size controls.
166
+ *
167
+ * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
168
+ */
169
+ declare function captureNetwork(options: NetworkCaptureOptions): CaptureHandle;
170
+
171
+ /**
172
+ * Observe same-document (SPA) navigations: `pushState`, `replaceState`, and
173
+ * back/forward changes. Each produces one {@link NavigationEvent} carrying the
174
+ * path, search, hash, and how it occurred. The application's router runs first
175
+ * and is unaffected by observation; repeated navigations to the same location
176
+ * are reported once.
177
+ *
178
+ * Returns a {@link CaptureHandle}; call `disconnect()` to stop.
179
+ */
180
+ declare function captureNavigation(options: NavigationCaptureOptions): CaptureHandle;
181
+
182
+ /**
183
+ * Console-backed logger for development and debugging. The captures run inside
184
+ * application call paths (credentials.get, pushState, fetch), so a
185
+ * page-overridden or unavailable console must never break them — every call is
186
+ * guarded.
187
+ */
188
+ declare function createConsoleLogger(prefix?: string): Logger;
189
+
190
+ /** Package version, injected at build time; "0.0.0-dev" in unbuilt contexts (tests, watch mode). */
191
+ declare const VERSION: string;
192
+
193
+ export { type CaptureHandle, type HttpExchange, type Logger, type NavigationCaptureOptions, type NavigationEvent, type NavigationSource, type NetworkCaptureOptions, type NetworkTransport, VERSION, type WebAuthnCaptureOptions, type WebAuthnCeremonyEvent, type WebAuthnCeremonyKind, captureNavigation, captureNetwork, captureWebAuthn, createConsoleLogger };
package/dist/index.js ADDED
@@ -0,0 +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});
package/dist/index.mjs ADDED
@@ -0,0 +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};
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@corbado/autocapture",
3
+ "version": "0.1.0",
4
+ "description": "Observer-style capture helpers for browser authentication signals: WebAuthn ceremonies, selected network exchanges, and in-app navigation.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "scripts": {
12
+ "build": "tsup src/index.ts --format cjs,esm --target es2019 --dts --minify --define.__CORBADO_AUTOCAPTURE_VERSION__='\"'${CORBADO_AUTOCAPTURE_VERSION:-0.0.0-dev}'\"'",
13
+ "dev": "tsup src/index.ts --format cjs,esm --target es2019 --watch --dts",
14
+ "lint": "tsc --noEmit",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:e2e": "pnpm build && playwright test --config e2e/playwright.config.ts",
18
+ "check:dist": "es-check es2019 './dist/index.js' && es-check es2019 './dist/index.mjs' --module",
19
+ "format": "prettier --write 'src/**/*.ts' 'e2e/**/*.ts'",
20
+ "format:check": "prettier --check 'src/**/*.ts' 'e2e/**/*.ts'"
21
+ },
22
+ "devDependencies": {
23
+ "@playwright/test": "^1.57.0",
24
+ "@types/node": "^20",
25
+ "es-check": "latest",
26
+ "eslint-config-prettier": "^10.1.8",
27
+ "jsdom": "latest",
28
+ "prettier": "^3.7.4",
29
+ "tsup": "latest",
30
+ "typescript": "latest",
31
+ "typescript-eslint": "^8.55.0",
32
+ "vitest": "^4.1.0"
33
+ }
34
+ }