@reticlehq/browser 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +5 -0
- package/dist/actions/actions-dom.d.ts.map +1 -1
- package/dist/actions/actions-dom.js +3 -20
- package/dist/actions/actions-dom.js.map +1 -1
- package/dist/actions/actions.d.ts +1 -1
- package/dist/actions/actions.d.ts.map +1 -1
- package/dist/actions/actions.js +37 -19
- package/dist/actions/actions.js.map +1 -1
- package/dist/commands/commands.d.ts +2 -0
- package/dist/commands/commands.d.ts.map +1 -1
- package/dist/commands/commands.js +30 -31
- package/dist/commands/commands.js.map +1 -1
- package/dist/dom/a11y.d.ts +17 -6
- package/dist/dom/a11y.d.ts.map +1 -1
- package/dist/dom/a11y.js +68 -32
- package/dist/dom/a11y.js.map +1 -1
- package/dist/dom/auto-anchor.d.ts +10 -10
- package/dist/dom/auto-anchor.js +10 -10
- package/dist/dom/blind-spots.d.ts +14 -0
- package/dist/dom/blind-spots.d.ts.map +1 -0
- package/dist/dom/blind-spots.js +30 -0
- package/dist/dom/blind-spots.js.map +1 -0
- package/dist/dom/chart.d.ts +93 -0
- package/dist/dom/chart.d.ts.map +1 -0
- package/dist/dom/chart.js +119 -0
- package/dist/dom/chart.js.map +1 -0
- package/dist/dom/dom-ignore.d.ts +2 -0
- package/dist/dom/dom-ignore.d.ts.map +1 -1
- package/dist/dom/dom-ignore.js +17 -2
- package/dist/dom/dom-ignore.js.map +1 -1
- package/dist/dom/occlusion.d.ts +9 -0
- package/dist/dom/occlusion.d.ts.map +1 -0
- package/dist/dom/occlusion.js +21 -0
- package/dist/dom/occlusion.js.map +1 -0
- package/dist/dom/query.d.ts +17 -4
- package/dist/dom/query.d.ts.map +1 -1
- package/dist/dom/query.js +185 -30
- package/dist/dom/query.js.map +1 -1
- package/dist/dom/refs.d.ts +26 -3
- package/dist/dom/refs.d.ts.map +1 -1
- package/dist/dom/refs.js +79 -4
- package/dist/dom/refs.js.map +1 -1
- package/dist/dom/snapshot.d.ts +7 -1
- package/dist/dom/snapshot.d.ts.map +1 -1
- package/dist/dom/snapshot.js +43 -16
- package/dist/dom/snapshot.js.map +1 -1
- package/dist/dom/source.d.ts +40 -0
- package/dist/dom/source.d.ts.map +1 -0
- package/dist/dom/source.js +59 -0
- package/dist/dom/source.js.map +1 -0
- package/dist/dom/theme.js +2 -2
- package/dist/dom/theme.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/observers/animation.d.ts +1 -1
- package/dist/observers/animation.d.ts.map +1 -1
- package/dist/observers/animation.js +1 -1
- package/dist/observers/animation.js.map +1 -1
- package/dist/observers/blind-spots.d.ts +19 -0
- package/dist/observers/blind-spots.d.ts.map +1 -0
- package/dist/observers/blind-spots.js +89 -0
- package/dist/observers/blind-spots.js.map +1 -0
- package/dist/observers/console.d.ts.map +1 -1
- package/dist/observers/console.js +39 -6
- package/dist/observers/console.js.map +1 -1
- package/dist/observers/dom.d.ts +1 -1
- package/dist/observers/dom.d.ts.map +1 -1
- package/dist/observers/dom.js +69 -10
- package/dist/observers/dom.js.map +1 -1
- package/dist/observers/focus.d.ts +10 -0
- package/dist/observers/focus.d.ts.map +1 -0
- package/dist/observers/focus.js +49 -0
- package/dist/observers/focus.js.map +1 -0
- package/dist/observers/network-body.d.ts +19 -0
- package/dist/observers/network-body.d.ts.map +1 -0
- package/dist/observers/network-body.js +143 -0
- package/dist/observers/network-body.js.map +1 -0
- package/dist/observers/network-redact.d.ts +8 -0
- package/dist/observers/network-redact.d.ts.map +1 -0
- package/dist/observers/network-redact.js +66 -0
- package/dist/observers/network-redact.js.map +1 -0
- package/dist/observers/network.d.ts +19 -7
- package/dist/observers/network.d.ts.map +1 -1
- package/dist/observers/network.js +439 -53
- package/dist/observers/network.js.map +1 -1
- package/dist/observers/perf.d.ts +13 -0
- package/dist/observers/perf.d.ts.map +1 -0
- package/dist/observers/perf.js +62 -0
- package/dist/observers/perf.js.map +1 -0
- package/dist/observers/route.d.ts.map +1 -1
- package/dist/observers/route.js +19 -10
- package/dist/observers/route.js.map +1 -1
- package/dist/observers/scroll.d.ts +1 -1
- package/dist/observers/scroll.d.ts.map +1 -1
- package/dist/observers/scroll.js +36 -9
- package/dist/observers/scroll.js.map +1 -1
- package/dist/observers/state.d.ts +20 -0
- package/dist/observers/state.d.ts.map +1 -0
- package/dist/observers/state.js +87 -0
- package/dist/observers/state.js.map +1 -0
- package/dist/observers/storage.d.ts +23 -0
- package/dist/observers/storage.d.ts.map +1 -0
- package/dist/observers/storage.js +177 -0
- package/dist/observers/storage.js.map +1 -0
- package/dist/observers/types.d.ts +15 -0
- package/dist/observers/types.d.ts.map +1 -1
- package/dist/observers/types.js +29 -1
- package/dist/observers/types.js.map +1 -1
- package/dist/patching/capture-method.d.ts +27 -0
- package/dist/patching/capture-method.d.ts.map +1 -0
- package/dist/patching/capture-method.js +37 -0
- package/dist/patching/capture-method.js.map +1 -0
- package/dist/presenter/presenter-config.d.ts +3 -3
- package/dist/presenter/presenter-config.d.ts.map +1 -1
- package/dist/presenter/presenter-config.js +3 -3
- package/dist/presenter/presenter-config.js.map +1 -1
- package/dist/presenter/presenter-controls.d.ts +1 -1
- package/dist/presenter/presenter-controls.d.ts.map +1 -1
- package/dist/presenter/presenter-controls.js +3 -3
- package/dist/presenter/presenter-controls.js.map +1 -1
- package/dist/presenter/presenter-glow.d.ts +1 -1
- package/dist/presenter/presenter-glow.js +1 -1
- package/dist/presenter/presenter-styles.d.ts +1 -1
- package/dist/presenter/presenter-styles.d.ts.map +1 -1
- package/dist/presenter/presenter-styles.js +12 -3
- package/dist/presenter/presenter-styles.js.map +1 -1
- package/dist/presenter/presenter-test-helpers.d.ts +16 -0
- package/dist/presenter/presenter-test-helpers.d.ts.map +1 -0
- package/dist/presenter/presenter-test-helpers.js +42 -0
- package/dist/presenter/presenter-test-helpers.js.map +1 -0
- package/dist/presenter/presenter.d.ts +4 -6
- package/dist/presenter/presenter.d.ts.map +1 -1
- package/dist/presenter/presenter.js +5 -9
- package/dist/presenter/presenter.js.map +1 -1
- package/dist/recorder/recorder.d.ts +8 -8
- package/dist/recorder/recorder.d.ts.map +1 -1
- package/dist/recorder/recorder.js +22 -9
- package/dist/recorder/recorder.js.map +1 -1
- package/dist/registry/capabilities.d.ts +2 -2
- package/dist/registry/capabilities.d.ts.map +1 -1
- package/dist/registry/capabilities.js +1 -1
- package/dist/registry/capabilities.js.map +1 -1
- package/dist/registry/domains.d.ts +1 -1
- package/dist/registry/domains.js +1 -1
- package/dist/registry/emitter.d.ts +2 -2
- package/dist/registry/emitter.js +2 -2
- package/dist/registry/store-adapters.d.ts +128 -0
- package/dist/registry/store-adapters.d.ts.map +1 -0
- package/dist/registry/store-adapters.js +135 -0
- package/dist/registry/store-adapters.js.map +1 -0
- package/dist/registry/stores.d.ts +45 -3
- package/dist/registry/stores.d.ts.map +1 -1
- package/dist/registry/stores.js +121 -8
- package/dist/registry/stores.js.map +1 -1
- package/dist/reticle.d.ts +28 -4
- package/dist/reticle.d.ts.map +1 -1
- package/dist/reticle.js +83 -15
- package/dist/reticle.js.map +1 -1
- package/dist/review/annotator.d.ts.map +1 -1
- package/dist/review/annotator.js +4 -2
- package/dist/review/annotator.js.map +1 -1
- package/dist/review/mark-anchor.d.ts.map +1 -1
- package/dist/review/mark-anchor.js +1 -21
- package/dist/review/mark-anchor.js.map +1 -1
- package/dist/security/serialization.d.ts +29 -1
- package/dist/security/serialization.d.ts.map +1 -1
- package/dist/security/serialization.js +172 -20
- package/dist/security/serialization.js.map +1 -1
- package/dist/timers/clock.d.ts +13 -0
- package/dist/timers/clock.d.ts.map +1 -1
- package/dist/timers/clock.js +76 -6
- package/dist/timers/clock.js.map +1 -1
- package/dist/transport/transport.d.ts +1 -1
- package/dist/transport/transport.d.ts.map +1 -1
- package/dist/transport/transport.js +49 -8
- package/dist/transport/transport.js.map +1 -1
- package/package.json +7 -3
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { EventType, PerfMetric } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* Observe the web-perf signals a screenshot tool fundamentally cannot verify — largest-contentful-paint
|
|
4
|
+
* (LCP), cumulative layout shift (CLS), and long tasks — and emit them into the ring buffer so an agent
|
|
5
|
+
* can assert "no layout shift on load" or "LCP under 2.5s". No-ops when PerformanceObserver or a given
|
|
6
|
+
* entry type is unavailable (jsdom / older browsers), so it never throws in an unsupported context.
|
|
7
|
+
*
|
|
8
|
+
* Each event carries `at` (the entry's own startTime) so a consumer can reason about WHEN the metric
|
|
9
|
+
* occurred rather than when it was flushed — buffered:true replays pre-install entries, whose emit time
|
|
10
|
+
* would otherwise be install time.
|
|
11
|
+
*/
|
|
12
|
+
export function installPerf(emit) {
|
|
13
|
+
if (typeof PerformanceObserver !== 'function')
|
|
14
|
+
return () => undefined;
|
|
15
|
+
const observers = [];
|
|
16
|
+
const observe = (type, handle) => {
|
|
17
|
+
try {
|
|
18
|
+
const po = new PerformanceObserver((list) => {
|
|
19
|
+
for (const entry of list.getEntries())
|
|
20
|
+
handle(entry);
|
|
21
|
+
});
|
|
22
|
+
po.observe({ type, buffered: true });
|
|
23
|
+
observers.push(po);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
/* entry type unsupported in this browser — skip it, leave the others installed */
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
// CLS is CUMULATIVE: sum qualifying shifts and report the running total, not each isolated shift.
|
|
30
|
+
// ponytail: naive running sum — no session-windowing (5s window / 1s gap); enough for
|
|
31
|
+
// "no layout shift on load" and trend, upgrade to windowed sessions if a CWV-exact number is needed.
|
|
32
|
+
let cls = 0;
|
|
33
|
+
// LCP only grows across candidates — surface a value only when it exceeds the last, not every candidate.
|
|
34
|
+
let lcp = 0;
|
|
35
|
+
observe('largest-contentful-paint', (e) => {
|
|
36
|
+
const value = Math.round(e.startTime);
|
|
37
|
+
if (value <= lcp)
|
|
38
|
+
return;
|
|
39
|
+
lcp = value;
|
|
40
|
+
emit(EventType.PERF, { metric: PerfMetric.LCP, value, at: value });
|
|
41
|
+
});
|
|
42
|
+
observe('layout-shift', (e) => {
|
|
43
|
+
const ls = e;
|
|
44
|
+
// Shifts within 500ms of a user input are expected (not CLS) — the spec's hadRecentInput flag.
|
|
45
|
+
if (ls.hadRecentInput === true)
|
|
46
|
+
return;
|
|
47
|
+
cls += ls.value ?? 0;
|
|
48
|
+
emit(EventType.PERF, { metric: PerfMetric.CLS, value: cls, at: Math.round(e.startTime) });
|
|
49
|
+
});
|
|
50
|
+
observe('longtask', (e) => {
|
|
51
|
+
emit(EventType.PERF, {
|
|
52
|
+
metric: PerfMetric.LONGTASK,
|
|
53
|
+
value: Math.round(e.duration),
|
|
54
|
+
at: Math.round(e.startTime),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
return () => {
|
|
58
|
+
for (const po of observers)
|
|
59
|
+
po.disconnect();
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=perf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"perf.js","sourceRoot":"","sources":["../../src/observers/perf.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AASxD;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CAAC,IAAU;IACpC,IAAI,OAAO,mBAAmB,KAAK,UAAU;QAAE,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;IACtE,MAAM,SAAS,GAA0B,EAAE,CAAC;IAC5C,MAAM,OAAO,GAAG,CAAC,IAAY,EAAE,MAAyC,EAAQ,EAAE;QAChF,IAAI,CAAC;YACH,MAAM,EAAE,GAAG,IAAI,mBAAmB,CAAC,CAAC,IAAI,EAAE,EAAE;gBAC1C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,UAAU,EAAE;oBAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACvD,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,OAAO,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;YACrC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC;QAAC,MAAM,CAAC;YACP,kFAAkF;QACpF,CAAC;IACH,CAAC,CAAC;IAEF,kGAAkG;IAClG,sFAAsF;IACtF,qGAAqG;IACrG,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,yGAAyG;IACzG,IAAI,GAAG,GAAG,CAAC,CAAC;IAEZ,OAAO,CAAC,0BAA0B,EAAE,CAAC,CAAC,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;QACtC,IAAI,KAAK,IAAI,GAAG;YAAE,OAAO;QACzB,GAAG,GAAG,KAAK,CAAC;QACZ,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC,EAAE,EAAE;QAC5B,MAAM,EAAE,GAAG,CAAqB,CAAC;QACjC,+FAA+F;QAC/F,IAAI,EAAE,CAAC,cAAc,KAAK,IAAI;YAAE,OAAO;QACvC,GAAG,IAAI,EAAE,CAAC,KAAK,IAAI,CAAC,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC;IAC5F,CAAC,CAAC,CAAC;IACH,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,EAAE,EAAE;QACxB,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE;YACnB,MAAM,EAAE,UAAU,CAAC,QAAQ;YAC3B,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC;YAC7B,EAAE,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;SAC5B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,EAAE;QACV,KAAK,MAAM,EAAE,IAAI,SAAS;YAAE,EAAE,CAAC,UAAU,EAAE,CAAC;IAC9C,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/observers/route.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"route.d.ts","sourceRoot":"","sources":["../../src/observers/route.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAYjD,kFAAkF;AAClF,wBAAgB,YAAY,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAsDjD"}
|
package/dist/observers/route.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { EventType } from '@reticlehq/core';
|
|
2
|
+
import { captureMethod } from '../patching/capture-method.js';
|
|
2
3
|
function snapshotLocation() {
|
|
3
4
|
return {
|
|
4
5
|
pathname: location.pathname,
|
|
@@ -11,16 +12,19 @@ function snapshotLocation() {
|
|
|
11
12
|
export function installRoute(emit) {
|
|
12
13
|
// Keep the true originals for teardown identity; bound copies are used for invocation
|
|
13
14
|
// (History methods throw "Illegal invocation" if called with the wrong `this`).
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const origReplace = history.replaceState;
|
|
17
|
-
/* eslint-enable @typescript-eslint/unbound-method */
|
|
15
|
+
const origPush = captureMethod(history, 'pushState');
|
|
16
|
+
const origReplace = captureMethod(history, 'replaceState');
|
|
18
17
|
const callPush = origPush.bind(history);
|
|
19
18
|
const callReplace = origReplace.bind(history);
|
|
19
|
+
// The `from` baseline for the NEXT popstate/hashchange. Updated in fire on every emitted change
|
|
20
|
+
// — including pushState/replaceState — so a later Back nav isn't compared against a stale href
|
|
21
|
+
// (which made `to === from` trip and silently drop the back navigation).
|
|
22
|
+
let lastHref = location.href;
|
|
20
23
|
const fire = (from) => {
|
|
21
24
|
const to = snapshotLocation();
|
|
22
25
|
if (to.href === from)
|
|
23
26
|
return;
|
|
27
|
+
lastHref = to.href;
|
|
24
28
|
emit(EventType.ROUTE_CHANGE, {
|
|
25
29
|
from,
|
|
26
30
|
to: to.href,
|
|
@@ -29,26 +33,31 @@ export function installRoute(emit) {
|
|
|
29
33
|
hash: to.hash,
|
|
30
34
|
});
|
|
31
35
|
};
|
|
32
|
-
|
|
36
|
+
const patchedPush = (data, unused, url) => {
|
|
33
37
|
const from = location.href;
|
|
34
38
|
callPush(data, unused, url ?? null);
|
|
35
39
|
fire(from);
|
|
36
40
|
};
|
|
37
|
-
|
|
41
|
+
const patchedReplace = (data, unused, url) => {
|
|
38
42
|
const from = location.href;
|
|
39
43
|
callReplace(data, unused, url ?? null);
|
|
40
44
|
fire(from);
|
|
41
45
|
};
|
|
42
|
-
|
|
46
|
+
history.pushState = patchedPush;
|
|
47
|
+
history.replaceState = patchedReplace;
|
|
43
48
|
const onNav = () => {
|
|
44
49
|
fire(lastHref);
|
|
45
|
-
lastHref = location.href;
|
|
46
50
|
};
|
|
47
51
|
window.addEventListener('popstate', onNav);
|
|
48
52
|
window.addEventListener('hashchange', onNav);
|
|
49
53
|
return () => {
|
|
50
|
-
|
|
51
|
-
history.
|
|
54
|
+
// Restore ONLY if the slot still holds our wrapper. If a router/analytics SDK wrapped
|
|
55
|
+
// history.pushState AFTER connect(), unconditionally writing origPush back would silently
|
|
56
|
+
// uninstall THEIR instrumentation too — the SDK harming the app it only meant to observe.
|
|
57
|
+
if (history.pushState === patchedPush)
|
|
58
|
+
history.pushState = origPush;
|
|
59
|
+
if (history.replaceState === patchedReplace)
|
|
60
|
+
history.replaceState = origReplace;
|
|
52
61
|
window.removeEventListener('popstate', onNav);
|
|
53
62
|
window.removeEventListener('hashchange', onNav);
|
|
54
63
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/observers/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"route.js","sourceRoot":"","sources":["../../src/observers/route.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,MAAM,+BAA+B,CAAC;AAE9D,SAAS,gBAAgB;IACvB,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,QAAQ;QAC3B,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,IAAI,EAAE,QAAQ,CAAC,IAAI;QACnB,IAAI,EAAE,QAAQ,CAAC,IAAI;KACpB,CAAC;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,YAAY,CAAC,IAAU;IACrC,sFAAsF;IACtF,gFAAgF;IAChF,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IACrD,MAAM,WAAW,GAAG,aAAa,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAE9C,gGAAgG;IAChG,+FAA+F;IAC/F,yEAAyE;IACzE,IAAI,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC;IAE7B,MAAM,IAAI,GAAG,CAAC,IAAY,EAAQ,EAAE;QAClC,MAAM,EAAE,GAAG,gBAAgB,EAAE,CAAC;QAC9B,IAAI,EAAE,CAAC,IAAI,KAAK,IAAI;YAAE,OAAO;QAC7B,QAAQ,GAAG,EAAE,CAAC,IAAI,CAAC;QACnB,IAAI,CAAC,SAAS,CAAC,YAAY,EAAE;YAC3B,IAAI;YACJ,EAAE,EAAE,EAAE,CAAC,IAAI;YACX,QAAQ,EAAE,EAAE,CAAC,QAAQ;YACrB,MAAM,EAAE,EAAE,CAAC,MAAM;YACjB,IAAI,EAAE,EAAE,CAAC,IAAI;SACd,CAAC,CAAC;IACL,CAAC,CAAC;IAEF,MAAM,WAAW,GAAG,CAAC,IAAa,EAAE,MAAc,EAAE,GAAyB,EAAQ,EAAE;QACrF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,CAAC,CAAC;IACF,MAAM,cAAc,GAAG,CAAC,IAAa,EAAE,MAAc,EAAE,GAAyB,EAAQ,EAAE;QACxF,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC;QAC3B,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,IAAI,CAAC,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,CAAC,CAAC;IACF,OAAO,CAAC,SAAS,GAAG,WAAW,CAAC;IAChC,OAAO,CAAC,YAAY,GAAG,cAAc,CAAC;IAEtC,MAAM,KAAK,GAAG,GAAS,EAAE;QACvB,IAAI,CAAC,QAAQ,CAAC,CAAC;IACjB,CAAC,CAAC;IACF,MAAM,CAAC,gBAAgB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAC3C,MAAM,CAAC,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAE7C,OAAO,GAAG,EAAE;QACV,sFAAsF;QACtF,0FAA0F;QAC1F,0FAA0F;QAC1F,IAAI,OAAO,CAAC,SAAS,KAAK,WAAW;YAAE,OAAO,CAAC,SAAS,GAAG,QAAQ,CAAC;QACpE,IAAI,OAAO,CAAC,YAAY,KAAK,cAAc;YAAE,OAAO,CAAC,YAAY,GAAG,WAAW,CAAC;QAChF,MAAM,CAAC,mBAAmB,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;QAC9C,MAAM,CAAC,mBAAmB,CAAC,YAAY,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import type { Emit, Teardown } from './types.js';
|
|
2
|
-
/** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs
|
|
2
|
+
/** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs. */
|
|
3
3
|
export declare function installScroll(emit: Emit): Teardown;
|
|
4
4
|
//# sourceMappingURL=scroll.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../src/observers/scroll.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"scroll.d.ts","sourceRoot":"","sources":["../../src/observers/scroll.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKjD,iFAAiF;AACjF,wBAAgB,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAsElD"}
|
package/dist/observers/scroll.js
CHANGED
|
@@ -1,26 +1,44 @@
|
|
|
1
|
-
import { EventType } from '@reticlehq/core';
|
|
1
|
+
import { EventType, ScrollDirection } from '@reticlehq/core';
|
|
2
2
|
import { refs } from '../dom/refs.js';
|
|
3
|
+
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
3
4
|
const THROTTLE_MS = 100;
|
|
4
5
|
const REVEAL_SELECTOR = '[data-reticle-reveal], [data-reveal], section';
|
|
5
|
-
/** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs
|
|
6
|
+
/** Observe scroll position + reveal-on-scroll for modern scroll-reactive UIs. */
|
|
6
7
|
export function installScroll(emit) {
|
|
7
8
|
let lastEmit = 0;
|
|
8
9
|
let lastY = 0;
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return;
|
|
13
|
-
lastEmit = now;
|
|
10
|
+
let trailingTimer;
|
|
11
|
+
const emitPosition = () => {
|
|
12
|
+
lastEmit = performance.now();
|
|
14
13
|
const y = window.scrollY;
|
|
15
14
|
const max = Math.max(1, document.documentElement.scrollHeight - window.innerHeight);
|
|
16
15
|
emit(EventType.SCROLL_POSITION, {
|
|
17
16
|
x: window.scrollX,
|
|
18
17
|
y,
|
|
19
18
|
percent: Math.round((y / max) * 100),
|
|
20
|
-
direction: y >= lastY ?
|
|
19
|
+
direction: y >= lastY ? ScrollDirection.DOWN : ScrollDirection.UP,
|
|
21
20
|
});
|
|
22
21
|
lastY = y;
|
|
23
22
|
};
|
|
23
|
+
const onScroll = () => {
|
|
24
|
+
const elapsed = performance.now() - lastEmit;
|
|
25
|
+
if (elapsed >= THROTTLE_MS) {
|
|
26
|
+
if (trailingTimer !== undefined) {
|
|
27
|
+
nativeClearTimeout(trailingTimer);
|
|
28
|
+
trailingTimer = undefined;
|
|
29
|
+
}
|
|
30
|
+
emitPosition();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
// Leading-edge only dropped the FINAL, resting position — an agent asserting "scrolled to the
|
|
34
|
+
// footer" saw the last mid-scroll sample, not where the page settled. Schedule a trailing emit.
|
|
35
|
+
if (trailingTimer === undefined) {
|
|
36
|
+
trailingTimer = nativeSetTimeout(() => {
|
|
37
|
+
trailingTimer = undefined;
|
|
38
|
+
emitPosition();
|
|
39
|
+
}, THROTTLE_MS - elapsed);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
24
42
|
window.addEventListener('scroll', onScroll, { passive: true });
|
|
25
43
|
let io;
|
|
26
44
|
if (typeof IntersectionObserver === 'function') {
|
|
@@ -31,11 +49,20 @@ export function installScroll(emit) {
|
|
|
31
49
|
}
|
|
32
50
|
}
|
|
33
51
|
}, { threshold: 0.25 });
|
|
52
|
+
const observer = io;
|
|
53
|
+
// Observe reveal targets present at install. Reveal-on-scroll sections are in the initial DOM by
|
|
54
|
+
// design (they exist hidden and animate in on scroll), so this covers the real case. Watching the
|
|
55
|
+
// whole body subtree for LATE-mounted reveal targets was tried and reverted: a second body-wide
|
|
56
|
+
// MutationObserver on EVERY app — running querySelectorAll(section,...) per mutation batch — is
|
|
57
|
+
// disproportionate overhead for a niche signal. Dynamically-mounted reveal targets are simply not
|
|
58
|
+
// tracked (an honest blind spot, consistent with the rest of the SDK).
|
|
34
59
|
for (const el of document.querySelectorAll(REVEAL_SELECTOR))
|
|
35
|
-
|
|
60
|
+
observer.observe(el);
|
|
36
61
|
}
|
|
37
62
|
return () => {
|
|
38
63
|
window.removeEventListener('scroll', onScroll);
|
|
64
|
+
if (trailingTimer !== undefined)
|
|
65
|
+
nativeClearTimeout(trailingTimer);
|
|
39
66
|
io?.disconnect();
|
|
40
67
|
};
|
|
41
68
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll.js","sourceRoot":"","sources":["../../src/observers/scroll.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"scroll.js","sourceRoot":"","sources":["../../src/observers/scroll.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Emit, Teardown } from './types.js';
|
|
2
|
+
interface StateChange {
|
|
3
|
+
path: string;
|
|
4
|
+
old: unknown;
|
|
5
|
+
new: unknown;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Shallow top-level diff of two store states — the keys whose value changed. Immutable updates
|
|
9
|
+
* (Zustand/Redux) replace the changed key's reference, so `Object.is` catches them. Pure; deep-nested
|
|
10
|
+
* diffing behind an unchanged top-level reference is a v2 concern.
|
|
11
|
+
*/
|
|
12
|
+
export declare function diffState(prev: unknown, next: unknown): StateChange[];
|
|
13
|
+
/**
|
|
14
|
+
* Subscribe to every registered store that exposed a subscribe method, emitting a STATE_CHANGE per
|
|
15
|
+
* changed top-level path with {name, path, old, new} — automatic diffs, not pull-only readings. Stores
|
|
16
|
+
* without a subscribe fall back to the pull path (reticle_state). Fully reversible.
|
|
17
|
+
*/
|
|
18
|
+
export declare function installStoreState(emit: Emit): Teardown;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/observers/state.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEjD,UAAU,WAAW;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;CACd;AAMD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,WAAW,EAAE,CAUrE;AAeD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CA8CtD"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { EventType, REDACTED_VALUE } from '@reticlehq/core';
|
|
2
|
+
import { subscribableStores, onStoreRegistered, } from '../registry/stores.js';
|
|
3
|
+
import { isSensitiveKey, sanitizeForTransport } from '../security/serialization.js';
|
|
4
|
+
function isRecord(value) {
|
|
5
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Shallow top-level diff of two store states — the keys whose value changed. Immutable updates
|
|
9
|
+
* (Zustand/Redux) replace the changed key's reference, so `Object.is` catches them. Pure; deep-nested
|
|
10
|
+
* diffing behind an unchanged top-level reference is a v2 concern.
|
|
11
|
+
*/
|
|
12
|
+
export function diffState(prev, next) {
|
|
13
|
+
if (!isRecord(prev) || !isRecord(next)) {
|
|
14
|
+
return Object.is(prev, next) ? [] : [{ path: '', old: prev, new: next }];
|
|
15
|
+
}
|
|
16
|
+
const changes = [];
|
|
17
|
+
for (const key of new Set([...Object.keys(prev), ...Object.keys(next)])) {
|
|
18
|
+
if (!Object.is(prev[key], next[key]))
|
|
19
|
+
changes.push({ path: key, old: prev[key], new: next[key] });
|
|
20
|
+
}
|
|
21
|
+
return changes;
|
|
22
|
+
}
|
|
23
|
+
/** Redact a changed value when its path is credential-bearing, else cap it for transport. */
|
|
24
|
+
function project(path, value) {
|
|
25
|
+
return isSensitiveKey(path) ? REDACTED_VALUE : sanitizeForTransport(value);
|
|
26
|
+
}
|
|
27
|
+
function safeRead(getter) {
|
|
28
|
+
try {
|
|
29
|
+
return getter();
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Subscribe to every registered store that exposed a subscribe method, emitting a STATE_CHANGE per
|
|
37
|
+
* changed top-level path with {name, path, old, new} — automatic diffs, not pull-only readings. Stores
|
|
38
|
+
* without a subscribe fall back to the pull path (reticle_state). Fully reversible.
|
|
39
|
+
*/
|
|
40
|
+
export function installStoreState(emit) {
|
|
41
|
+
const active = new Map();
|
|
42
|
+
const watch = ([name, getter, subscribe]) => {
|
|
43
|
+
// A re-registration under the same name is the HMR cycle: the store INSTANCE is replaced (new
|
|
44
|
+
// getter + new subscribe). Skipping it (the old `seen` guard) left the subscription bound to the
|
|
45
|
+
// DEAD getter forever and the live store invisible until a full reload. Rebind: drop the previous
|
|
46
|
+
// subscription for this name, then subscribe the new tuple. Guard the old unsubscribe — if it throws
|
|
47
|
+
// (a misbehaving store), the rebind must still proceed, or the live store stays invisible.
|
|
48
|
+
try {
|
|
49
|
+
active.get(name)?.();
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
/* a faulty unsubscribe must not block re-binding to the new instance */
|
|
53
|
+
}
|
|
54
|
+
let last = safeRead(getter);
|
|
55
|
+
active.set(name, subscribe(() => {
|
|
56
|
+
const next = safeRead(getter);
|
|
57
|
+
for (const change of diffState(last, next)) {
|
|
58
|
+
emit(EventType.STATE_CHANGE, {
|
|
59
|
+
name,
|
|
60
|
+
path: change.path,
|
|
61
|
+
value: project(change.path, change.new),
|
|
62
|
+
old: project(change.path, change.old),
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
last = next;
|
|
66
|
+
}));
|
|
67
|
+
};
|
|
68
|
+
// Stores already registered...
|
|
69
|
+
for (const entry of subscribableStores())
|
|
70
|
+
watch(entry);
|
|
71
|
+
//...and any registered LATER. The SDK installs observers during connect, but apps call
|
|
72
|
+
// registerStore after that, so without this the common case subscribes to nothing.
|
|
73
|
+
const offRegistered = onStoreRegistered(watch);
|
|
74
|
+
return () => {
|
|
75
|
+
offRegistered();
|
|
76
|
+
for (const unsubscribe of active.values()) {
|
|
77
|
+
try {
|
|
78
|
+
unsubscribe();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* one faulty store unsubscribe must not abort tearing the rest down */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
active.clear();
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
//# sourceMappingURL=state.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/observers/state.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Emit, type Teardown } from './types.js';
|
|
2
|
+
/** The three readable client-side storage areas. httpOnly cookies are invisible to JS by design. */
|
|
3
|
+
export interface StorageSnapshot {
|
|
4
|
+
local: Record<string, string>;
|
|
5
|
+
session: Record<string, string>;
|
|
6
|
+
cookies: Record<string, string>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Read client-side storage on demand (the STORAGE_READ command). Powers `reticle_storage` — the
|
|
10
|
+
* agent verifies "token persisted after login", "cart survived reload", "logout cleared the session"
|
|
11
|
+
* from the app's real storage rather than inferring it from the DOM. `area` scopes to one area; omit
|
|
12
|
+
* for all three. Sensitive keys are redacted; httpOnly cookies are unreadable by design (documented).
|
|
13
|
+
*/
|
|
14
|
+
export declare function readStorage(area?: string): StorageSnapshot | Record<string, string>;
|
|
15
|
+
/**
|
|
16
|
+
* Observe storage WRITES (not just reads): patch Storage.setItem/removeItem so a token persisted, a
|
|
17
|
+
* cart updated, or a session cleared emits a STORAGE_CHANGE {area, key, old?, new?} diff — the "before"
|
|
18
|
+
* the pull path can never see. localStorage and sessionStorage share one prototype, so `this` inside the
|
|
19
|
+
* patch identifies the area. The native originals are read off the prototype descriptor (not a bare
|
|
20
|
+
* method access) and re-dispatched via `.call(this)`. Fully reversible; values redacted like the reads.
|
|
21
|
+
*/
|
|
22
|
+
export declare function installStorage(emit: Emit): Teardown;
|
|
23
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/observers/storage.ts"],"names":[],"mappings":"AAEA,OAAO,EAA+B,KAAK,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEnF,oGAAoG;AACpG,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAmDD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CASnF;AAYD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAwFnD"}
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { EventType, REDACTED_VALUE, StorageArea } from '@reticlehq/core';
|
|
2
|
+
import { isSensitiveKey } from '../security/serialization.js';
|
|
3
|
+
import { observeSafely, observeValue } from './types.js';
|
|
4
|
+
/** Accessing localStorage/sessionStorage throws in a sandboxed iframe / disabled-storage context. */
|
|
5
|
+
function safeArea(get) {
|
|
6
|
+
try {
|
|
7
|
+
return get();
|
|
8
|
+
}
|
|
9
|
+
catch {
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function readArea(storage) {
|
|
14
|
+
const out = {};
|
|
15
|
+
if (storage === null)
|
|
16
|
+
return out;
|
|
17
|
+
for (let i = 0; i < storage.length; i += 1) {
|
|
18
|
+
const key = storage.key(i);
|
|
19
|
+
if (key === null)
|
|
20
|
+
continue;
|
|
21
|
+
// Redact credential-bearing keys (token/session/password/…) so auth state never leaks verbatim.
|
|
22
|
+
out[key] = isSensitiveKey(key) ? REDACTED_VALUE : (storage.getItem(key) ?? '');
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
function readCookies() {
|
|
27
|
+
const out = {};
|
|
28
|
+
// Reading document.cookie can throw (SecurityError) in a sandboxed / cookie-disabled context — guard
|
|
29
|
+
// it the same way safeArea guards localStorage, so one bad area never crashes the whole STORAGE read.
|
|
30
|
+
let raw = '';
|
|
31
|
+
try {
|
|
32
|
+
raw = typeof document !== 'undefined' ? document.cookie : '';
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
for (const part of raw.split(';')) {
|
|
38
|
+
const eq = part.indexOf('=');
|
|
39
|
+
if (eq === -1)
|
|
40
|
+
continue;
|
|
41
|
+
const key = part.slice(0, eq).trim();
|
|
42
|
+
if (key === '')
|
|
43
|
+
continue;
|
|
44
|
+
if (isSensitiveKey(key)) {
|
|
45
|
+
out[key] = REDACTED_VALUE;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
out[key] = decodeURIComponent(part.slice(eq + 1).trim());
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
out[key] = part.slice(eq + 1).trim();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Read client-side storage on demand (the STORAGE_READ command). Powers `reticle_storage` — the
|
|
59
|
+
* agent verifies "token persisted after login", "cart survived reload", "logout cleared the session"
|
|
60
|
+
* from the app's real storage rather than inferring it from the DOM. `area` scopes to one area; omit
|
|
61
|
+
* for all three. Sensitive keys are redacted; httpOnly cookies are unreadable by design (documented).
|
|
62
|
+
*/
|
|
63
|
+
export function readStorage(area) {
|
|
64
|
+
if (area === StorageArea.LOCAL)
|
|
65
|
+
return readArea(safeArea(() => window.localStorage));
|
|
66
|
+
if (area === StorageArea.SESSION)
|
|
67
|
+
return readArea(safeArea(() => window.sessionStorage));
|
|
68
|
+
if (area === 'cookies')
|
|
69
|
+
return readCookies();
|
|
70
|
+
return {
|
|
71
|
+
local: readArea(safeArea(() => window.localStorage)),
|
|
72
|
+
session: readArea(safeArea(() => window.sessionStorage)),
|
|
73
|
+
cookies: readCookies(),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Redact a value when its key is credential-bearing — the same rule the pull path applies. */
|
|
77
|
+
function redactFor(key, value) {
|
|
78
|
+
if (value === null)
|
|
79
|
+
return undefined;
|
|
80
|
+
return isSensitiveKey(key) ? REDACTED_VALUE : value;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Observe storage WRITES (not just reads): patch Storage.setItem/removeItem so a token persisted, a
|
|
84
|
+
* cart updated, or a session cleared emits a STORAGE_CHANGE {area, key, old?, new?} diff — the "before"
|
|
85
|
+
* the pull path can never see. localStorage and sessionStorage share one prototype, so `this` inside the
|
|
86
|
+
* patch identifies the area. The native originals are read off the prototype descriptor (not a bare
|
|
87
|
+
* method access) and re-dispatched via `.call(this)`. Fully reversible; values redacted like the reads.
|
|
88
|
+
*/
|
|
89
|
+
export function installStorage(emit) {
|
|
90
|
+
if (typeof Storage === 'undefined')
|
|
91
|
+
return () => undefined;
|
|
92
|
+
const proto = Storage.prototype;
|
|
93
|
+
const origSet = Object.getOwnPropertyDescriptor(proto, 'setItem')?.value;
|
|
94
|
+
const origRemove = Object.getOwnPropertyDescriptor(proto, 'removeItem')?.value;
|
|
95
|
+
const origClear = Object.getOwnPropertyDescriptor(proto, 'clear')?.value;
|
|
96
|
+
if (origSet === undefined || origRemove === undefined || origClear === undefined) {
|
|
97
|
+
return () => undefined;
|
|
98
|
+
}
|
|
99
|
+
// Resolve sessionStorage at CALL time, not once at install. If the property threw during install
|
|
100
|
+
// (a transient sandbox state) the captured reference would be null and every session write would be
|
|
101
|
+
// misreported as `local` for the life of the page. Reads are cheap and safeArea swallows throws.
|
|
102
|
+
const areaOf = (storage) => storage === safeArea(() => window.sessionStorage) ? StorageArea.SESSION : StorageArea.LOCAL;
|
|
103
|
+
const readOld = (storage, key) => {
|
|
104
|
+
try {
|
|
105
|
+
return storage.getItem(key);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const patchedSetItem = function (key, value) {
|
|
112
|
+
// The app's write happens FIRST and outside the guard — it must succeed or fail on its own terms.
|
|
113
|
+
const old = observeValue(() => readOld(this, key)) ?? null;
|
|
114
|
+
origSet.call(this, key, value);
|
|
115
|
+
observeSafely(() => {
|
|
116
|
+
emit(EventType.STORAGE_CHANGE, {
|
|
117
|
+
area: areaOf(this),
|
|
118
|
+
key,
|
|
119
|
+
...(redactFor(key, old) === undefined ? {} : { old: redactFor(key, old) }),
|
|
120
|
+
new: redactFor(key, value) ?? REDACTED_VALUE,
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
proto.setItem = patchedSetItem;
|
|
125
|
+
const patchedRemoveItem = function (key) {
|
|
126
|
+
const old = observeValue(() => readOld(this, key)) ?? null;
|
|
127
|
+
origRemove.call(this, key);
|
|
128
|
+
observeSafely(() => {
|
|
129
|
+
// No `new` field ⇒ the key was removed.
|
|
130
|
+
emit(EventType.STORAGE_CHANGE, {
|
|
131
|
+
area: areaOf(this),
|
|
132
|
+
key,
|
|
133
|
+
...(redactFor(key, old) === undefined ? {} : { old: redactFor(key, old) }),
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
proto.removeItem = patchedRemoveItem;
|
|
138
|
+
const patchedClear = function () {
|
|
139
|
+
// localStorage.clear() is the most common logout — but it emitted NOTHING, so "logout cleared the
|
|
140
|
+
// session" was unverifiable from the write path. Snapshot the keys BEFORE clearing (the values are
|
|
141
|
+
// gone after), then report each as a removal, reusing the {area, key, old} shape removeItem emits.
|
|
142
|
+
const area = areaOf(this);
|
|
143
|
+
const removed = [];
|
|
144
|
+
try {
|
|
145
|
+
for (let i = 0; i < this.length; i += 1) {
|
|
146
|
+
const key = this.key(i);
|
|
147
|
+
if (key !== null)
|
|
148
|
+
removed.push({ key, old: readOld(this, key) });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Enumerating keys can throw in a locked-down context; clear anyway, just without the diff.
|
|
153
|
+
}
|
|
154
|
+
origClear.call(this);
|
|
155
|
+
for (const { key, old } of removed) {
|
|
156
|
+
observeSafely(() => {
|
|
157
|
+
emit(EventType.STORAGE_CHANGE, {
|
|
158
|
+
area,
|
|
159
|
+
key,
|
|
160
|
+
...(redactFor(key, old) === undefined ? {} : { old: redactFor(key, old) }),
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
proto.clear = patchedClear;
|
|
166
|
+
return () => {
|
|
167
|
+
// Restore only if the slot still holds our wrapper — never uninstall a wrapper the app layered
|
|
168
|
+
// on top of ours after connect().
|
|
169
|
+
if (proto.setItem === patchedSetItem)
|
|
170
|
+
proto.setItem = origSet;
|
|
171
|
+
if (proto.removeItem === patchedRemoveItem)
|
|
172
|
+
proto.removeItem = origRemove;
|
|
173
|
+
if (proto.clear === patchedClear)
|
|
174
|
+
proto.clear = origClear;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/observers/storage.ts"],"names":[],"mappings":"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"}
|
|
@@ -3,4 +3,19 @@ import type { EventType } from '@reticlehq/core';
|
|
|
3
3
|
export type Emit = (type: EventType, data: Record<string, unknown>, ref?: string) => void;
|
|
4
4
|
/** Every observer returns a teardown that fully restores any patched globals. */
|
|
5
5
|
export type Teardown = () => void;
|
|
6
|
+
/**
|
|
7
|
+
* Run an observation so it can never escape into the app's call stack.
|
|
8
|
+
*
|
|
9
|
+
* Every monkey-patch calls its observation INSIDE the function it replaced, so a throw does not surface
|
|
10
|
+
* as an SDK error — it propagates out of `localStorage.setItem` after the write already succeeded, out
|
|
11
|
+
* of `history.pushState` (crashing a router's navigate), or out of `console.log` before the message
|
|
12
|
+
* reaches the console. Guarding the transport hop alone is not enough: the payload is BUILT here too
|
|
13
|
+
* (reading the old value, resolving the area, redacting), and any of that can throw on a hostile or
|
|
14
|
+
* exotic object.
|
|
15
|
+
*
|
|
16
|
+
* A dev-only observability SDK must never be able to break the app it is observing.
|
|
17
|
+
*/
|
|
18
|
+
export declare function observeSafely(observation: () => void): void;
|
|
19
|
+
/** Like observeSafely, but for reading a value the observation needs; undefined if the read throws. */
|
|
20
|
+
export declare function observeValue<T>(read: () => T): T | undefined;
|
|
6
21
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/observers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,yFAAyF;AACzF,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1F,iFAAiF;AACjF,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/observers/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,yFAAyF;AACzF,MAAM,MAAM,IAAI,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;AAE1F,iFAAiF;AACjF,MAAM,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC;AAElC;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAAC,WAAW,EAAE,MAAM,IAAI,GAAG,IAAI,CAM3D;AAED,uGAAuG;AACvG,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,GAAG,SAAS,CAM5D"}
|