@cubenest/rrweb-core 0.1.0-alpha.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/NOTICE +15 -0
- package/README.md +26 -0
- package/dist/compat/index.d.ts +37 -0
- package/dist/compat/index.d.ts.map +1 -0
- package/dist/compat/index.js +97 -0
- package/dist/compat/index.js.map +1 -0
- package/dist/compression/index.d.ts +24 -0
- package/dist/compression/index.d.ts.map +1 -0
- package/dist/compression/index.js +61 -0
- package/dist/compression/index.js.map +1 -0
- package/dist/console/buffer.d.ts +99 -0
- package/dist/console/buffer.d.ts.map +1 -0
- package/dist/console/buffer.js +169 -0
- package/dist/console/buffer.js.map +1 -0
- package/dist/console/index.d.ts +3 -0
- package/dist/console/index.d.ts.map +1 -0
- package/dist/console/index.js +16 -0
- package/dist/console/index.js.map +1 -0
- package/dist/console/types.d.ts +61 -0
- package/dist/console/types.d.ts.map +1 -0
- package/dist/console/types.js +11 -0
- package/dist/console/types.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/masking/body.d.ts +30 -0
- package/dist/masking/body.d.ts.map +1 -0
- package/dist/masking/body.js +33 -0
- package/dist/masking/body.js.map +1 -0
- package/dist/masking/headers.d.ts +16 -0
- package/dist/masking/headers.d.ts.map +1 -0
- package/dist/masking/headers.js +46 -0
- package/dist/masking/headers.js.map +1 -0
- package/dist/masking/index.d.ts +8 -0
- package/dist/masking/index.d.ts.map +1 -0
- package/dist/masking/index.js +11 -0
- package/dist/masking/index.js.map +1 -0
- package/dist/masking/inputs.d.ts +15 -0
- package/dist/masking/inputs.d.ts.map +1 -0
- package/dist/masking/inputs.js +36 -0
- package/dist/masking/inputs.js.map +1 -0
- package/dist/masking/regex.d.ts +96 -0
- package/dist/masking/regex.d.ts.map +1 -0
- package/dist/masking/regex.js +182 -0
- package/dist/masking/regex.js.map +1 -0
- package/dist/masking/selectors.d.ts +67 -0
- package/dist/masking/selectors.d.ts.map +1 -0
- package/dist/masking/selectors.js +137 -0
- package/dist/masking/selectors.js.map +1 -0
- package/dist/masking/text.d.ts +9 -0
- package/dist/masking/text.d.ts.map +1 -0
- package/dist/masking/text.js +15 -0
- package/dist/masking/text.js.map +1 -0
- package/dist/network/cdp.d.ts +54 -0
- package/dist/network/cdp.d.ts.map +1 -0
- package/dist/network/cdp.js +282 -0
- package/dist/network/cdp.js.map +1 -0
- package/dist/network/index.d.ts +4 -0
- package/dist/network/index.d.ts.map +1 -0
- package/dist/network/index.js +14 -0
- package/dist/network/index.js.map +1 -0
- package/dist/network/types.d.ts +133 -0
- package/dist/network/types.d.ts.map +1 -0
- package/dist/network/types.js +35 -0
- package/dist/network/types.js.map +1 -0
- package/dist/network/web-request.d.ts +76 -0
- package/dist/network/web-request.d.ts.map +1 -0
- package/dist/network/web-request.js +294 -0
- package/dist/network/web-request.js.map +1 -0
- package/dist/persistence/index.d.ts +3 -0
- package/dist/persistence/index.d.ts.map +1 -0
- package/dist/persistence/index.js +11 -0
- package/dist/persistence/index.js.map +1 -0
- package/dist/persistence/store.d.ts +18 -0
- package/dist/persistence/store.d.ts.map +1 -0
- package/dist/persistence/store.js +327 -0
- package/dist/persistence/store.js.map +1 -0
- package/dist/persistence/types.d.ts +76 -0
- package/dist/persistence/types.d.ts.map +1 -0
- package/dist/persistence/types.js +21 -0
- package/dist/persistence/types.js.map +1 -0
- package/dist/rrweb.d.ts +5 -0
- package/dist/rrweb.d.ts.map +1 -0
- package/dist/rrweb.js +13 -0
- package/dist/rrweb.js.map +1 -0
- package/dist/screenshot/base64.d.ts +8 -0
- package/dist/screenshot/base64.d.ts.map +1 -0
- package/dist/screenshot/base64.js +21 -0
- package/dist/screenshot/base64.js.map +1 -0
- package/dist/screenshot/cdp.d.ts +50 -0
- package/dist/screenshot/cdp.d.ts.map +1 -0
- package/dist/screenshot/cdp.js +65 -0
- package/dist/screenshot/cdp.js.map +1 -0
- package/dist/screenshot/index.d.ts +4 -0
- package/dist/screenshot/index.d.ts.map +1 -0
- package/dist/screenshot/index.js +10 -0
- package/dist/screenshot/index.js.map +1 -0
- package/dist/screenshot/tabs.d.ts +44 -0
- package/dist/screenshot/tabs.d.ts.map +1 -0
- package/dist/screenshot/tabs.js +63 -0
- package/dist/screenshot/tabs.js.map +1 -0
- package/dist/screenshot/types.d.ts +27 -0
- package/dist/screenshot/types.d.ts.map +1 -0
- package/dist/screenshot/types.js +18 -0
- package/dist/screenshot/types.js.map +1 -0
- package/dist/shadow-dom/index.d.ts +3 -0
- package/dist/shadow-dom/index.d.ts.map +1 -0
- package/dist/shadow-dom/index.js +8 -0
- package/dist/shadow-dom/index.js.map +1 -0
- package/dist/shadow-dom/traverse.d.ts +54 -0
- package/dist/shadow-dom/traverse.d.ts.map +1 -0
- package/dist/shadow-dom/traverse.js +209 -0
- package/dist/shadow-dom/traverse.js.map +1 -0
- package/dist/shadow-dom/types.d.ts +43 -0
- package/dist/shadow-dom/types.d.ts.map +1 -0
- package/dist/shadow-dom/types.js +25 -0
- package/dist/shadow-dom/types.js.map +1 -0
- package/dist/throttling/apply.d.ts +59 -0
- package/dist/throttling/apply.d.ts.map +1 -0
- package/dist/throttling/apply.js +101 -0
- package/dist/throttling/apply.js.map +1 -0
- package/dist/throttling/defaults.d.ts +60 -0
- package/dist/throttling/defaults.d.ts.map +1 -0
- package/dist/throttling/defaults.js +81 -0
- package/dist/throttling/defaults.js.map +1 -0
- package/dist/throttling/guards.d.ts +69 -0
- package/dist/throttling/guards.d.ts.map +1 -0
- package/dist/throttling/guards.js +212 -0
- package/dist/throttling/guards.js.map +1 -0
- package/dist/throttling/index.d.ts +5 -0
- package/dist/throttling/index.d.ts.map +1 -0
- package/dist/throttling/index.js +11 -0
- package/dist/throttling/index.js.map +1 -0
- package/package.json +35 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export type { ScreenshotAdapter } from './types';
|
|
2
|
+
export { createCDPScreenshotAdapter, type CDPTransport, type CDPScreenshotOptions, } from './cdp';
|
|
3
|
+
export { createTabsScreenshotAdapter, type CaptureVisibleTabFn, type TabsScreenshotOptions, } from './tabs';
|
|
4
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/screenshot/index.ts"],"names":[],"mappings":"AAQA,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EACL,0BAA0B,EAC1B,KAAK,YAAY,EACjB,KAAK,oBAAoB,GAC1B,MAAM,OAAO,CAAC;AACf,OAAO,EACL,2BAA2B,EAC3B,KAAK,mBAAmB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Public barrel for the screenshot fallback module.
|
|
2
|
+
//
|
|
3
|
+
// Locked surface per IMPLEMENTATION_PLAN.md Public API contract (lines
|
|
4
|
+
// 721-723): one type (`ScreenshotAdapter`) and two factories
|
|
5
|
+
// (`createCDPScreenshotAdapter`, `createTabsScreenshotAdapter`). The
|
|
6
|
+
// transport-shape and option types are re-exported so consumers can
|
|
7
|
+
// declare-and-pass without importing internal paths.
|
|
8
|
+
export { createCDPScreenshotAdapter, } from './cdp';
|
|
9
|
+
export { createTabsScreenshotAdapter, } from './tabs';
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/screenshot/index.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,uEAAuE;AACvE,6DAA6D;AAC7D,qEAAqE;AACrE,oEAAoE;AACpE,qDAAqD;AAGrD,OAAO,EACL,0BAA0B,GAG3B,MAAM,OAAO,CAAC;AACf,OAAO,EACL,2BAA2B,GAG5B,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { ScreenshotAdapter } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Structural shape of `chrome.tabs.captureVisibleTab`. The Chrome API
|
|
4
|
+
* resolves to a `data:image/<fmt>;base64,...` URL (or an empty string on
|
|
5
|
+
* failure in older Chromium builds — handled below).
|
|
6
|
+
*
|
|
7
|
+
* The argument order matches the Chrome API: `(windowId?, options?)`.
|
|
8
|
+
* Passing `undefined` for `windowId` selects the current window.
|
|
9
|
+
*/
|
|
10
|
+
export type CaptureVisibleTabFn = (windowId?: number, options?: {
|
|
11
|
+
format?: 'png' | 'jpeg';
|
|
12
|
+
quality?: number;
|
|
13
|
+
}) => Promise<string>;
|
|
14
|
+
/**
|
|
15
|
+
* Factory-time options for the tabs screenshot adapter. `windowId` is
|
|
16
|
+
* passed through verbatim; `format` and `quality` are forwarded to the
|
|
17
|
+
* Chrome API's `options` bag.
|
|
18
|
+
*/
|
|
19
|
+
export interface TabsScreenshotOptions {
|
|
20
|
+
/**
|
|
21
|
+
* The browser window to capture from. Defaults to `undefined`, which the
|
|
22
|
+
* Chrome API interprets as "the currently focused window".
|
|
23
|
+
*/
|
|
24
|
+
windowId?: number;
|
|
25
|
+
/** PNG (default) or JPEG. */
|
|
26
|
+
format?: 'png' | 'jpeg';
|
|
27
|
+
/** 0-100. JPEG only; Chrome ignores it for PNG. */
|
|
28
|
+
quality?: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Build a `ScreenshotAdapter` that captures via `chrome.tabs.captureVisibleTab`.
|
|
32
|
+
*
|
|
33
|
+
* The factory takes the `captureVisibleTab` reference as a parameter so the
|
|
34
|
+
* substrate is not coupled to the `chrome.*` namespace — pass
|
|
35
|
+
* `chrome.tabs.captureVisibleTab.bind(chrome.tabs)` from the extension
|
|
36
|
+
* service worker.
|
|
37
|
+
*
|
|
38
|
+
* The returned adapter is stateless; `dispose()` is a no-op.
|
|
39
|
+
*
|
|
40
|
+
* @param captureVisibleTab A function with the `chrome.tabs.captureVisibleTab` shape.
|
|
41
|
+
* @param options Factory-time defaults.
|
|
42
|
+
*/
|
|
43
|
+
export declare function createTabsScreenshotAdapter(captureVisibleTab: CaptureVisibleTabFn, options?: TabsScreenshotOptions): ScreenshotAdapter;
|
|
44
|
+
//# sourceMappingURL=tabs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.d.ts","sourceRoot":"","sources":["../../src/screenshot/tabs.ts"],"names":[],"mappings":"AAmBA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;;GAOG;AACH,MAAM,MAAM,mBAAmB,GAAG,CAChC,QAAQ,CAAC,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KACpD,OAAO,CAAC,MAAM,CAAC,CAAC;AAErB;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6BAA6B;IAC7B,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,2BAA2B,CACzC,iBAAiB,EAAE,mBAAmB,EACtC,OAAO,GAAE,qBAA0B,GAClC,iBAAiB,CAqCnB"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// chrome.tabs.captureVisibleTab adapter — Task 1.6.
|
|
2
|
+
//
|
|
3
|
+
// Used by P2/peek from the extension service worker. The MV3 service worker
|
|
4
|
+
// has access to `chrome.tabs.captureVisibleTab(windowId?, options?)`, which
|
|
5
|
+
// returns a `data:image/<fmt>;base64,...` URL. We accept the function as a
|
|
6
|
+
// parameter rather than reading `chrome.tabs` off a global so the substrate
|
|
7
|
+
// stays environment-agnostic and trivially testable.
|
|
8
|
+
//
|
|
9
|
+
// The function's argument ergonomics (windowId first, options second) match
|
|
10
|
+
// the Chrome API exactly — when no windowId is configured the factory
|
|
11
|
+
// passes `undefined`, mirroring `chrome.tabs.captureVisibleTab(undefined, opts)`,
|
|
12
|
+
// which the API treats as "the current window".
|
|
13
|
+
//
|
|
14
|
+
// Why not just `globalThis.chrome?.tabs?.captureVisibleTab`: (1) we want
|
|
15
|
+
// the substrate to typecheck and bundle outside an extension context;
|
|
16
|
+
// (2) callers often want to wrap the call with permission checks or
|
|
17
|
+
// telemetry; (3) tests stay synchronous-fixture-friendly.
|
|
18
|
+
import { decodeBase64 } from './base64';
|
|
19
|
+
/**
|
|
20
|
+
* Build a `ScreenshotAdapter` that captures via `chrome.tabs.captureVisibleTab`.
|
|
21
|
+
*
|
|
22
|
+
* The factory takes the `captureVisibleTab` reference as a parameter so the
|
|
23
|
+
* substrate is not coupled to the `chrome.*` namespace — pass
|
|
24
|
+
* `chrome.tabs.captureVisibleTab.bind(chrome.tabs)` from the extension
|
|
25
|
+
* service worker.
|
|
26
|
+
*
|
|
27
|
+
* The returned adapter is stateless; `dispose()` is a no-op.
|
|
28
|
+
*
|
|
29
|
+
* @param captureVisibleTab A function with the `chrome.tabs.captureVisibleTab` shape.
|
|
30
|
+
* @param options Factory-time defaults.
|
|
31
|
+
*/
|
|
32
|
+
export function createTabsScreenshotAdapter(captureVisibleTab, options = {}) {
|
|
33
|
+
const windowId = options.windowId;
|
|
34
|
+
const format = options.format ?? 'png';
|
|
35
|
+
const quality = options.quality;
|
|
36
|
+
return {
|
|
37
|
+
async capture() {
|
|
38
|
+
const tabOptions = { format };
|
|
39
|
+
if (quality !== undefined) {
|
|
40
|
+
tabOptions.quality = quality;
|
|
41
|
+
}
|
|
42
|
+
const dataUrl = await captureVisibleTab(windowId, tabOptions);
|
|
43
|
+
if (typeof dataUrl !== 'string' || dataUrl.length === 0) {
|
|
44
|
+
// Chrome historically resolved with an empty string on permission
|
|
45
|
+
// failures rather than rejecting; surface that as a useful error.
|
|
46
|
+
throw new Error('chrome.tabs.captureVisibleTab returned an empty result');
|
|
47
|
+
}
|
|
48
|
+
// Expected shape: `data:image/png;base64,AAAA…` — but we accept any
|
|
49
|
+
// mime-type, only the `;base64,` segment matters for decoding.
|
|
50
|
+
const marker = ';base64,';
|
|
51
|
+
const idx = dataUrl.indexOf(marker);
|
|
52
|
+
if (idx === -1) {
|
|
53
|
+
throw new Error('captureVisibleTab result is not a base64-encoded data URL (missing `;base64,` segment)');
|
|
54
|
+
}
|
|
55
|
+
const payload = dataUrl.slice(idx + marker.length);
|
|
56
|
+
return decodeBase64(payload);
|
|
57
|
+
},
|
|
58
|
+
async dispose() {
|
|
59
|
+
// No listeners or session state to release.
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=tabs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.js","sourceRoot":"","sources":["../../src/screenshot/tabs.ts"],"names":[],"mappings":"AAAA,oDAAoD;AACpD,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,2EAA2E;AAC3E,4EAA4E;AAC5E,qDAAqD;AACrD,EAAE;AACF,4EAA4E;AAC5E,sEAAsE;AACtE,kFAAkF;AAClF,gDAAgD;AAChD,EAAE;AACF,yEAAyE;AACzE,sEAAsE;AACtE,oEAAoE;AACpE,0DAA0D;AAE1D,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAiCxC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,2BAA2B,CACzC,iBAAsC,EACtC,UAAiC,EAAE;IAEnC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,KAAK,CAAC;IACvC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAEhC,OAAO;QACL,KAAK,CAAC,OAAO;YACX,MAAM,UAAU,GAAiD,EAAE,MAAM,EAAE,CAAC;YAC5E,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC1B,UAAU,CAAC,OAAO,GAAG,OAAO,CAAC;YAC/B,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;YAE9D,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxD,kEAAkE;gBAClE,kEAAkE;gBAClE,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;YAC5E,CAAC;YAED,oEAAoE;YACpE,+DAA+D;YAC/D,MAAM,MAAM,GAAG,UAAU,CAAC;YAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACpC,IAAI,GAAG,KAAK,CAAC,CAAC,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACb,wFAAwF,CACzF,CAAC;YACJ,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;YACnD,OAAO,YAAY,CAAC,OAAO,CAAC,CAAC;QAC/B,CAAC;QACD,KAAK,CAAC,OAAO;YACX,4CAA4C;QAC9C,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Capture the visible viewport as PNG (or JPEG, if the factory was
|
|
3
|
+
* configured with `format: 'jpeg'`) bytes.
|
|
4
|
+
*
|
|
5
|
+
* The bytes are the raw image payload — callers can persist directly, embed
|
|
6
|
+
* via `data:image/<fmt>;base64,...` URLs, or stream into a video assembler.
|
|
7
|
+
* No framing, no envelope.
|
|
8
|
+
*/
|
|
9
|
+
export interface ScreenshotAdapter {
|
|
10
|
+
/**
|
|
11
|
+
* Capture the visible viewport. Resolves to a Uint8Array of image bytes
|
|
12
|
+
* in the format configured at factory time (PNG by default).
|
|
13
|
+
*
|
|
14
|
+
* Rejections propagate the transport error verbatim — the substrate does
|
|
15
|
+
* not retry, throttle, or wrap. The caller decides whether a missed
|
|
16
|
+
* frame is recoverable.
|
|
17
|
+
*/
|
|
18
|
+
capture(): Promise<Uint8Array>;
|
|
19
|
+
/**
|
|
20
|
+
* Optional cleanup hook. The reference adapters resolve immediately — the
|
|
21
|
+
* field exists so product-specific adapters that *do* hold listeners
|
|
22
|
+
* (e.g. a long-lived CDP event subscription) can be torn down without a
|
|
23
|
+
* contract change.
|
|
24
|
+
*/
|
|
25
|
+
dispose?(): Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/screenshot/types.ts"],"names":[],"mappings":"AAiBA;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;;OAOG;IACH,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAE/B;;;;;OAKG;IACH,OAAO,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Screenshot fallback interface — Task 1.6.
|
|
2
|
+
//
|
|
3
|
+
// ADR-0002: when rrweb misbehaves on hard sites (canvas/webgl heavy, hostile
|
|
4
|
+
// CSS, very large DOMs throttled out of full-fidelity capture), a periodic
|
|
5
|
+
// screenshot keeps the recording useful for triage. Both products consume
|
|
6
|
+
// the same `ScreenshotAdapter` contract; the transport differs:
|
|
7
|
+
//
|
|
8
|
+
// - P1/tracelane → CDP `Page.captureScreenshot` via a WebDriver-supplied
|
|
9
|
+
// CDP session (see `createCDPScreenshotAdapter`).
|
|
10
|
+
// - P2/peek → `chrome.tabs.captureVisibleTab` from the extension
|
|
11
|
+
// service worker (see `createTabsScreenshotAdapter`).
|
|
12
|
+
//
|
|
13
|
+
// The substrate stays environment-agnostic by injecting the transport as a
|
|
14
|
+
// parameter rather than importing `chrome.*` or any CDP client library.
|
|
15
|
+
// Tests at this layer cover the contract; real-environment integration
|
|
16
|
+
// tests live in the product packages.
|
|
17
|
+
export {};
|
|
18
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/screenshot/types.ts"],"names":[],"mappings":"AAAA,4CAA4C;AAC5C,EAAE;AACF,6EAA6E;AAC7E,2EAA2E;AAC3E,0EAA0E;AAC1E,gEAAgE;AAChE,EAAE;AACF,2EAA2E;AAC3E,sDAAsD;AACtD,uEAAuE;AACvE,0DAA0D;AAC1D,EAAE;AACF,2EAA2E;AAC3E,wEAAwE;AACxE,uEAAuE;AACvE,sCAAsC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/shadow-dom/index.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// Public barrel for the shadow-DOM module.
|
|
2
|
+
//
|
|
3
|
+
// Locked surface per IMPLEMENTATION_PLAN.md Public API contract (lines
|
|
4
|
+
// 715-716): the `traverseShadowRoots` function plus the `ShadowRootInfo`
|
|
5
|
+
// type. The traversal options interface stays internal — callers pass an
|
|
6
|
+
// inline object literal and TS infers the shape.
|
|
7
|
+
export { traverseShadowRoots } from './traverse';
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/shadow-dom/index.ts"],"names":[],"mappings":"AAAA,2CAA2C;AAC3C,EAAE;AACF,uEAAuE;AACvE,yEAAyE;AACzE,yEAAyE;AACzE,iDAAiD;AAEjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ShadowRootInfo } from './types';
|
|
2
|
+
export interface TraverseShadowRootsOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Maximum shadow-root nesting depth to recurse into. Defaults to 16.
|
|
5
|
+
* Hosts at exactly `maxDepth - 1` are recorded; their shadow contents
|
|
6
|
+
* are not recursed into.
|
|
7
|
+
*/
|
|
8
|
+
maxDepth?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Called once for every host we detected but could not reach into. Fires
|
|
11
|
+
* for both the closed-shadow-root MAIN-world case and the custom-element
|
|
12
|
+
* heuristic. Throws are swallowed so a noisy consumer can't break the
|
|
13
|
+
* walk.
|
|
14
|
+
*/
|
|
15
|
+
onUnreachable?: (host: Element) => void;
|
|
16
|
+
/**
|
|
17
|
+
* When `true`, the walker will call `openOrClosedShadowRoot` (if
|
|
18
|
+
* provided) to try to reach closed shadow roots. Defaults to `false` —
|
|
19
|
+
* MAIN-world callers should leave this off; ISOLATED-world content
|
|
20
|
+
* scripts that have wired `chrome.dom.openOrClosedShadowRoot` should
|
|
21
|
+
* pass `true`.
|
|
22
|
+
*/
|
|
23
|
+
includeClosed?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Caller-injected helper, intended to be
|
|
26
|
+
* `chrome.dom.openOrClosedShadowRoot` in an ISOLATED-world content
|
|
27
|
+
* script. Returning `null`/`undefined` is interpreted as "no shadow
|
|
28
|
+
* root on this element"; returning a `ShadowRoot` is interpreted as
|
|
29
|
+
* "this element has a (probably closed) shadow root, here it is."
|
|
30
|
+
*
|
|
31
|
+
* Only consulted when `includeClosed === true`.
|
|
32
|
+
*/
|
|
33
|
+
openOrClosedShadowRoot?: (el: Element) => ShadowRoot | null | undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Walk the DOM from `root` and return every shadow host found.
|
|
37
|
+
*
|
|
38
|
+
* Open shadow roots are resolved via `el.shadowRoot`. Closed shadow roots
|
|
39
|
+
* are only reachable if the caller injects `openOrClosedShadowRoot` AND
|
|
40
|
+
* passes `includeClosed: true` — typically a content script forwarding
|
|
41
|
+
* `chrome.dom.openOrClosedShadowRoot` from an ISOLATED world. In MAIN
|
|
42
|
+
* world, closed shadow hosts are recorded as `'unreachable'`; browser
|
|
43
|
+
* encapsulation is respected (we do NOT reflect-hack into closed shadows).
|
|
44
|
+
*
|
|
45
|
+
* Does not descend into iframes — `iframe.contentDocument` is usually
|
|
46
|
+
* cross-origin and rrweb has its own iframe recording pathway.
|
|
47
|
+
*
|
|
48
|
+
* @param root Document, fragment, or element to walk.
|
|
49
|
+
* @param options See {@link TraverseShadowRootsOptions}.
|
|
50
|
+
* @returns A flat array of `ShadowRootInfo`, ordered by traversal (DFS
|
|
51
|
+
* pre-order). Each host appears at most once.
|
|
52
|
+
*/
|
|
53
|
+
export declare function traverseShadowRoots(root: Document | DocumentFragment | Element, options?: TraverseShadowRootsOptions): ShadowRootInfo[];
|
|
54
|
+
//# sourceMappingURL=traverse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traverse.d.ts","sourceRoot":"","sources":["../../src/shadow-dom/traverse.ts"],"names":[],"mappings":"AA2BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAK9C,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IACxC;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;;;;;OAQG;IACH,sBAAsB,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,KAAK,UAAU,GAAG,IAAI,GAAG,SAAS,CAAC;CACzE;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,mBAAmB,CACjC,IAAI,EAAE,QAAQ,GAAG,gBAAgB,GAAG,OAAO,EAC3C,OAAO,GAAE,0BAA+B,GACvC,cAAc,EAAE,CA+ClB"}
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Shadow DOM walker — Task 1.5.
|
|
2
|
+
//
|
|
3
|
+
// Walks the DOM from a given root and reports every shadow host it finds.
|
|
4
|
+
// Pure enumeration — does NOT serialize shadow contents, does NOT subscribe
|
|
5
|
+
// to mutations. rrweb does that elsewhere; we just answer "where are the
|
|
6
|
+
// hosts and what can we see through them?".
|
|
7
|
+
//
|
|
8
|
+
// Why this exists despite the PostHog fork already handling shadow hosts:
|
|
9
|
+
// ADR-0002 calls it "a thin wrapper around the PostHog fork's shadow-host
|
|
10
|
+
// handling that closes known gaps in custom-element traversal and documents
|
|
11
|
+
// the gaps that remain." The fork's logic is internal; we re-implement at
|
|
12
|
+
// this layer so consumers have a stable surface (`ShadowRootInfo[]`) and a
|
|
13
|
+
// single seam to inject the ISOLATED-world `chrome.dom.openOrClosedShadowRoot`
|
|
14
|
+
// helper from a content script.
|
|
15
|
+
//
|
|
16
|
+
// Closed shadow roots in MAIN world: the browser intentionally hides
|
|
17
|
+
// `el.shadowRoot` for `attachShadow({mode:'closed'})`. We respect that —
|
|
18
|
+
// no reflect-hacking, no Proxy tricks. Such hosts land on the
|
|
19
|
+
// `'unreachable'` path and (if the caller wired one) fire
|
|
20
|
+
// `options.onUnreachable(el)` so the consumer can log/breadcrumb.
|
|
21
|
+
//
|
|
22
|
+
// We do NOT traverse into `iframe.contentDocument`. In real usage iframes
|
|
23
|
+
// are very often cross-origin, where `contentDocument` access throws. Even
|
|
24
|
+
// when same-origin, iframes have independent recording semantics in rrweb
|
|
25
|
+
// and should be handled by the recorder's `recordCrossOriginIframes`
|
|
26
|
+
// pathway, not by us.
|
|
27
|
+
/** Default `maxDepth` — pathological apps have been observed nesting ~8 deep. */
|
|
28
|
+
const DEFAULT_MAX_DEPTH = 16;
|
|
29
|
+
/**
|
|
30
|
+
* Walk the DOM from `root` and return every shadow host found.
|
|
31
|
+
*
|
|
32
|
+
* Open shadow roots are resolved via `el.shadowRoot`. Closed shadow roots
|
|
33
|
+
* are only reachable if the caller injects `openOrClosedShadowRoot` AND
|
|
34
|
+
* passes `includeClosed: true` — typically a content script forwarding
|
|
35
|
+
* `chrome.dom.openOrClosedShadowRoot` from an ISOLATED world. In MAIN
|
|
36
|
+
* world, closed shadow hosts are recorded as `'unreachable'`; browser
|
|
37
|
+
* encapsulation is respected (we do NOT reflect-hack into closed shadows).
|
|
38
|
+
*
|
|
39
|
+
* Does not descend into iframes — `iframe.contentDocument` is usually
|
|
40
|
+
* cross-origin and rrweb has its own iframe recording pathway.
|
|
41
|
+
*
|
|
42
|
+
* @param root Document, fragment, or element to walk.
|
|
43
|
+
* @param options See {@link TraverseShadowRootsOptions}.
|
|
44
|
+
* @returns A flat array of `ShadowRootInfo`, ordered by traversal (DFS
|
|
45
|
+
* pre-order). Each host appears at most once.
|
|
46
|
+
*/
|
|
47
|
+
export function traverseShadowRoots(root, options = {}) {
|
|
48
|
+
const maxDepth = options.maxDepth ?? DEFAULT_MAX_DEPTH;
|
|
49
|
+
const includeClosed = options.includeClosed ?? false;
|
|
50
|
+
const openOrClosed = options.openOrClosedShadowRoot;
|
|
51
|
+
const onUnreachable = options.onUnreachable;
|
|
52
|
+
const seen = new Set();
|
|
53
|
+
const results = [];
|
|
54
|
+
// Avoid recursion to keep stack depth bounded for pathological nesting.
|
|
55
|
+
// The work list carries (subtree-root, depth) — depth is the depth the
|
|
56
|
+
// hosts inside that subtree-root will land at.
|
|
57
|
+
const work = [
|
|
58
|
+
{ subtree: root, depth: 0 },
|
|
59
|
+
];
|
|
60
|
+
while (work.length > 0) {
|
|
61
|
+
const item = work.shift();
|
|
62
|
+
if (!item)
|
|
63
|
+
break;
|
|
64
|
+
const { subtree, depth } = item;
|
|
65
|
+
if (depth >= maxDepth)
|
|
66
|
+
continue;
|
|
67
|
+
for (const el of iterDescendantElements(subtree)) {
|
|
68
|
+
if (seen.has(el))
|
|
69
|
+
continue;
|
|
70
|
+
const info = resolveHost(el, depth, includeClosed, openOrClosed);
|
|
71
|
+
if (!info)
|
|
72
|
+
continue;
|
|
73
|
+
seen.add(el);
|
|
74
|
+
results.push(info);
|
|
75
|
+
if (info.source === 'unreachable') {
|
|
76
|
+
if (onUnreachable) {
|
|
77
|
+
try {
|
|
78
|
+
onUnreachable(el);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Consumer callback errors must not abort the walk.
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
else if (info.root) {
|
|
86
|
+
// Queue the shadow root for traversal; nested hosts land at depth+1.
|
|
87
|
+
work.push({ subtree: info.root, depth: depth + 1 });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Resolve a single element into a `ShadowRootInfo`, or `null` if it isn't a
|
|
95
|
+
* shadow host. Implements the per-host resolution algorithm from
|
|
96
|
+
* IMPLEMENTATION_PLAN.md Task 1.5.
|
|
97
|
+
*/
|
|
98
|
+
function resolveHost(el, depth, includeClosed, openOrClosed) {
|
|
99
|
+
// Step 1: open shadow root via the standard reflection.
|
|
100
|
+
const openRoot = el.shadowRoot;
|
|
101
|
+
if (openRoot) {
|
|
102
|
+
return {
|
|
103
|
+
host: el,
|
|
104
|
+
root: openRoot,
|
|
105
|
+
mode: 'open',
|
|
106
|
+
source: 'attachShadow',
|
|
107
|
+
depth,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// Step 2: caller-provided helper (chrome.dom.openOrClosedShadowRoot).
|
|
111
|
+
if (includeClosed && openOrClosed) {
|
|
112
|
+
let injectedRoot;
|
|
113
|
+
try {
|
|
114
|
+
injectedRoot = openOrClosed(el);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// The helper may throw on detached nodes etc; treat as "no root".
|
|
118
|
+
injectedRoot = null;
|
|
119
|
+
}
|
|
120
|
+
if (injectedRoot) {
|
|
121
|
+
return {
|
|
122
|
+
host: el,
|
|
123
|
+
root: injectedRoot,
|
|
124
|
+
mode: 'closed',
|
|
125
|
+
source: 'chrome.dom',
|
|
126
|
+
depth,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// Step 3: best-effort heuristic — a custom element (tag name contains a
|
|
131
|
+
// hyphen) with no light-DOM children is *probably* a closed shadow host.
|
|
132
|
+
// This is intentionally permissive; the consumer's `onUnreachable`
|
|
133
|
+
// callback exists precisely so callers can downgrade these reports if
|
|
134
|
+
// they have richer information. See ADR-0002 — "closes known gaps in
|
|
135
|
+
// custom-element traversal and documents the gaps that remain."
|
|
136
|
+
if (isLikelyClosedCustomElementHost(el)) {
|
|
137
|
+
return {
|
|
138
|
+
host: el,
|
|
139
|
+
root: null,
|
|
140
|
+
mode: 'unknown',
|
|
141
|
+
source: 'unreachable',
|
|
142
|
+
depth,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
// Step 4: not a shadow host.
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Cheap heuristic: an element is "probably a closed shadow host" if its
|
|
150
|
+
* tag name contains a hyphen (Custom Elements v1 requirement) and it has
|
|
151
|
+
* no child nodes in the light DOM. False positives are intentional — the
|
|
152
|
+
* consumer's `onUnreachable` is the escape hatch.
|
|
153
|
+
*/
|
|
154
|
+
function isLikelyClosedCustomElementHost(el) {
|
|
155
|
+
const tag = el.tagName;
|
|
156
|
+
if (!tag)
|
|
157
|
+
return false;
|
|
158
|
+
// Built-ins like `<div>`/`<span>` have no hyphen. Custom elements must.
|
|
159
|
+
if (!tag.includes('-'))
|
|
160
|
+
return false;
|
|
161
|
+
// If we can see light DOM children, it's almost certainly not a closed
|
|
162
|
+
// shadow host (or, if it is, we'd still walk into the light tree below
|
|
163
|
+
// anyway — no information is lost by skipping it here).
|
|
164
|
+
if (el.childNodes.length > 0)
|
|
165
|
+
return false;
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Iterate the element descendants of a subtree root (the root itself is
|
|
170
|
+
* NOT yielded — the caller has already considered it on the previous
|
|
171
|
+
* iteration if it was a host).
|
|
172
|
+
*
|
|
173
|
+
* Uses a manual stack rather than `TreeWalker` to keep semantics
|
|
174
|
+
* predictable across jsdom/Chromium/WebKit, all of which have had
|
|
175
|
+
* shadow-related TreeWalker quirks.
|
|
176
|
+
*/
|
|
177
|
+
function* iterDescendantElements(subtree) {
|
|
178
|
+
// For a Document, start at documentElement (the <html>); for fragments
|
|
179
|
+
// and elements, start at the first child.
|
|
180
|
+
const starts = [];
|
|
181
|
+
if (isDocument(subtree)) {
|
|
182
|
+
if (subtree.documentElement)
|
|
183
|
+
starts.push(subtree.documentElement);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
for (const child of Array.from(subtree.children)) {
|
|
187
|
+
starts.push(child);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
const stack = [...starts].reverse();
|
|
191
|
+
while (stack.length > 0) {
|
|
192
|
+
const el = stack.pop();
|
|
193
|
+
if (!el)
|
|
194
|
+
break;
|
|
195
|
+
yield el;
|
|
196
|
+
// Push children in reverse so we visit them left-to-right (pre-order).
|
|
197
|
+
const children = el.children;
|
|
198
|
+
for (let i = children.length - 1; i >= 0; i--) {
|
|
199
|
+
const child = children[i];
|
|
200
|
+
if (child)
|
|
201
|
+
stack.push(child);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
function isDocument(x) {
|
|
206
|
+
// Document.nodeType === 9; DocumentFragment.nodeType === 11; Element.nodeType === 1.
|
|
207
|
+
return x.nodeType === 9;
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=traverse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"traverse.js","sourceRoot":"","sources":["../../src/shadow-dom/traverse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single shadow host detected by `traverseShadowRoots`.
|
|
3
|
+
*/
|
|
4
|
+
export interface ShadowRootInfo {
|
|
5
|
+
/** The host element on the light-DOM side of the boundary. */
|
|
6
|
+
host: Element;
|
|
7
|
+
/**
|
|
8
|
+
* The shadow root if we could reach it, otherwise `null`. `null` is
|
|
9
|
+
* always paired with `source: 'unreachable'`.
|
|
10
|
+
*/
|
|
11
|
+
root: ShadowRoot | null;
|
|
12
|
+
/**
|
|
13
|
+
* What the host's shadow root is declared as.
|
|
14
|
+
*
|
|
15
|
+
* - `'open'` — resolved via `el.shadowRoot`.
|
|
16
|
+
* - `'closed'` — confirmed closed (resolved via the injected helper, or
|
|
17
|
+
* attached as closed via `attachShadow({mode:'closed'})`
|
|
18
|
+
* and we noticed via the helper).
|
|
19
|
+
* - `'unknown'` — heuristic detection without confirmation (e.g.
|
|
20
|
+
* custom-element heuristic).
|
|
21
|
+
*/
|
|
22
|
+
mode: 'open' | 'closed' | 'unknown';
|
|
23
|
+
/**
|
|
24
|
+
* How the root was resolved.
|
|
25
|
+
*
|
|
26
|
+
* - `'attachShadow'` — `el.shadowRoot` reflection (open roots only).
|
|
27
|
+
* - `'chrome.dom'` — caller-injected `openOrClosedShadowRoot` helper,
|
|
28
|
+
* typically backed by `chrome.dom.openOrClosedShadowRoot`
|
|
29
|
+
* in an ISOLATED-world content script.
|
|
30
|
+
* - `'unreachable'` — host detected but root not accessible from this
|
|
31
|
+
* execution context. Browser encapsulation is being
|
|
32
|
+
* respected; this is the intended outcome in MAIN
|
|
33
|
+
* world for closed shadow roots.
|
|
34
|
+
*/
|
|
35
|
+
source: 'attachShadow' | 'chrome.dom' | 'unreachable';
|
|
36
|
+
/**
|
|
37
|
+
* Nesting depth. The first generation of hosts found directly under the
|
|
38
|
+
* traversal `root` is `0`; hosts inside those hosts' shadow roots are
|
|
39
|
+
* `1`, and so on.
|
|
40
|
+
*/
|
|
41
|
+
depth: number;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/shadow-dom/types.ts"],"names":[],"mappings":"AAwBA;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,8DAA8D;IAC9D,IAAI,EAAE,OAAO,CAAC;IACd;;;OAGG;IACH,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB;;;;;;;;;OASG;IACH,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAAC;IACpC;;;;;;;;;;;OAWG;IACH,MAAM,EAAE,cAAc,GAAG,YAAY,GAAG,aAAa,CAAC;IACtD;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;CACf"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Shadow DOM adapter types — Task 1.5.
|
|
2
|
+
//
|
|
3
|
+
// `ShadowRootInfo` is the single value the walker emits per host element.
|
|
4
|
+
// It captures both the resolved root (when reachable) and enough provenance
|
|
5
|
+
// for the consumer to understand WHY a given root is or is not in hand:
|
|
6
|
+
//
|
|
7
|
+
// - `mode` — what the host's shadow root was declared as. `'unknown'`
|
|
8
|
+
// is reserved for the heuristic "probably closed shadow,
|
|
9
|
+
// couldn't confirm" path so we don't lie and say `'closed'`
|
|
10
|
+
// when we never asked attachShadow at all.
|
|
11
|
+
// - `source` — how we resolved the root. `'attachShadow'` is the plain
|
|
12
|
+
// `el.shadowRoot` reflection (open roots). `'chrome.dom'`
|
|
13
|
+
// is the ISOLATED-world `chrome.dom.openOrClosedShadowRoot`
|
|
14
|
+
// helper that callers can inject. `'unreachable'` means we
|
|
15
|
+
// detected a host but couldn't reach into it (closed shadow
|
|
16
|
+
// in MAIN world, or the custom-element heuristic).
|
|
17
|
+
//
|
|
18
|
+
// `host` always points at the element on the document side. `root` is null
|
|
19
|
+
// only on the `'unreachable'` path — the consumer should not treat null as
|
|
20
|
+
// a value-tagged optional but as the literal "we cannot see inside this."
|
|
21
|
+
//
|
|
22
|
+
// See P2 PRD §A.3 for the closed-shadow-root MAIN-world limitation this
|
|
23
|
+
// shape documents.
|
|
24
|
+
export {};
|
|
25
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/shadow-dom/types.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,EAAE;AACF,0EAA0E;AAC1E,4EAA4E;AAC5E,wEAAwE;AACxE,EAAE;AACF,0EAA0E;AAC1E,wEAAwE;AACxE,2EAA2E;AAC3E,0DAA0D;AAC1D,yEAAyE;AACzE,yEAAyE;AACzE,2EAA2E;AAC3E,0EAA0E;AAC1E,2EAA2E;AAC3E,kEAAkE;AAClE,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,0EAA0E;AAC1E,EAAE;AACF,wEAAwE;AACxE,mBAAmB"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { customEvent, eventWithTime, recordOptions } from '../rrweb';
|
|
2
|
+
import { LARGE_DOM_DEFAULTS } from './defaults';
|
|
3
|
+
/**
|
|
4
|
+
* Options for `applyLargeDomGuards`. Everything is optional — the function
|
|
5
|
+
* defaults to `LARGE_DOM_DEFAULTS` and a no-op `onWarn` / `onLimit`.
|
|
6
|
+
*/
|
|
7
|
+
export interface ApplyLargeDomGuardsOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Override the bundled defaults. Useful for products that want to tune the
|
|
10
|
+
* mutation thresholds for their workload — but per IMPLEMENTATION_PLAN.md,
|
|
11
|
+
* tuning happens after we have real-world session data. Use sparingly.
|
|
12
|
+
*/
|
|
13
|
+
defaults?: typeof LARGE_DOM_DEFAULTS;
|
|
14
|
+
/**
|
|
15
|
+
* Invoked with the `tracelane.mutation.warn` and `tracelane.event.dropped`
|
|
16
|
+
* breadcrumb custom events. Consumers can forward these to their own
|
|
17
|
+
* telemetry pipeline. Default: no-op.
|
|
18
|
+
*/
|
|
19
|
+
onWarn?: (event: customEvent & {
|
|
20
|
+
timestamp: number;
|
|
21
|
+
}) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Invoked exactly once when the cumulative mutation count exceeds
|
|
24
|
+
* `defaults.mutationLimit`. After this fires, the guard's emit wrapper
|
|
25
|
+
* stops forwarding events. Consumers should call rrweb's `record()`
|
|
26
|
+
* teardown from here if they want to truly halt the recorder.
|
|
27
|
+
*/
|
|
28
|
+
onLimit?: () => void;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Compose the throttling defaults and guard chain onto an rrweb
|
|
32
|
+
* `recordOptions` object.
|
|
33
|
+
*
|
|
34
|
+
* Returns a new `recordOptions` with:
|
|
35
|
+
* - the first six `LARGE_DOM_DEFAULTS` (mousemoveWait, sampling,
|
|
36
|
+
* inlineImages, collectFonts, recordCanvas, plus the
|
|
37
|
+
* `checkoutEveryNms` mapped from `defaults.checkoutEveryMs`) merged in
|
|
38
|
+
* where the caller didn't already set them; and
|
|
39
|
+
* - an `emit` wrapper that runs dataUrl → eventSize → mutation in order.
|
|
40
|
+
*
|
|
41
|
+
* Note on semantics: because `applyLargeDomGuards` does not own the rrweb
|
|
42
|
+
* subscription, the hard mutation limit can only stop events from being
|
|
43
|
+
* forwarded from this wrapper onward — it cannot tear down the rrweb
|
|
44
|
+
* `record()` subscription on its own. Wire your own teardown via `onLimit`
|
|
45
|
+
* if you need the recorder itself to stop.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* const stop = record(
|
|
49
|
+
* applyLargeDomGuards(
|
|
50
|
+
* { emit: forwardToTransport },
|
|
51
|
+
* {
|
|
52
|
+
* onWarn: (e) => forwardToTransport(e),
|
|
53
|
+
* onLimit: () => stop?.(),
|
|
54
|
+
* },
|
|
55
|
+
* ),
|
|
56
|
+
* );
|
|
57
|
+
*/
|
|
58
|
+
export declare function applyLargeDomGuards(recordOpts: recordOptions<eventWithTime>, options?: ApplyLargeDomGuardsOptions): recordOptions<eventWithTime>;
|
|
59
|
+
//# sourceMappingURL=apply.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/throttling/apply.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAQhD;;;GAGG;AACH,MAAM,WAAW,0BAA0B;IACzC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,OAAO,kBAAkB,CAAC;IACrC;;;;OAIG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IAC9D;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,mBAAmB,CACjC,UAAU,EAAE,aAAa,CAAC,aAAa,CAAC,EACxC,OAAO,GAAE,0BAA+B,GACvC,aAAa,CAAC,aAAa,CAAC,CAiD9B"}
|