@reticlehq/browser 2.0.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/LICENSE +202 -0
- package/README.md +17 -0
- package/dist/actions/actions-dom.d.ts +29 -0
- package/dist/actions/actions-dom.d.ts.map +1 -0
- package/dist/actions/actions-dom.js +153 -0
- package/dist/actions/actions-dom.js.map +1 -0
- package/dist/actions/actions.d.ts +92 -0
- package/dist/actions/actions.d.ts.map +1 -0
- package/dist/actions/actions.js +348 -0
- package/dist/actions/actions.js.map +1 -0
- package/dist/actions/scroll.d.ts +19 -0
- package/dist/actions/scroll.d.ts.map +1 -0
- package/dist/actions/scroll.js +44 -0
- package/dist/actions/scroll.js.map +1 -0
- package/dist/commands/commands.d.ts +5 -0
- package/dist/commands/commands.d.ts.map +1 -0
- package/dist/commands/commands.js +246 -0
- package/dist/commands/commands.js.map +1 -0
- package/dist/dom/a11y.d.ts +20 -0
- package/dist/dom/a11y.d.ts.map +1 -0
- package/dist/dom/a11y.js +263 -0
- package/dist/dom/a11y.js.map +1 -0
- package/dist/dom/auto-anchor.d.ts +66 -0
- package/dist/dom/auto-anchor.d.ts.map +1 -0
- package/dist/dom/auto-anchor.js +75 -0
- package/dist/dom/auto-anchor.js.map +1 -0
- package/dist/dom/dom-ignore.d.ts +7 -0
- package/dist/dom/dom-ignore.d.ts.map +1 -0
- package/dist/dom/dom-ignore.js +21 -0
- package/dist/dom/dom-ignore.js.map +1 -0
- package/dist/dom/query.d.ts +6 -0
- package/dist/dom/query.d.ts.map +1 -0
- package/dist/dom/query.js +236 -0
- package/dist/dom/query.js.map +1 -0
- package/dist/dom/refs.d.ts +15 -0
- package/dist/dom/refs.d.ts.map +1 -0
- package/dist/dom/refs.js +36 -0
- package/dist/dom/refs.js.map +1 -0
- package/dist/dom/snapshot.d.ts +22 -0
- package/dist/dom/snapshot.d.ts.map +1 -0
- package/dist/dom/snapshot.js +171 -0
- package/dist/dom/snapshot.js.map +1 -0
- package/dist/dom/theme.d.ts +26 -0
- package/dist/dom/theme.d.ts.map +1 -0
- package/dist/dom/theme.js +90 -0
- package/dist/dom/theme.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +32 -0
- package/dist/index.js.map +1 -0
- package/dist/observers/animation.d.ts +4 -0
- package/dist/observers/animation.d.ts.map +1 -0
- package/dist/observers/animation.js +35 -0
- package/dist/observers/animation.js.map +1 -0
- package/dist/observers/console.d.ts +4 -0
- package/dist/observers/console.d.ts.map +1 -0
- package/dist/observers/console.js +58 -0
- package/dist/observers/console.js.map +1 -0
- package/dist/observers/dom.d.ts +4 -0
- package/dist/observers/dom.d.ts.map +1 -0
- package/dist/observers/dom.js +96 -0
- package/dist/observers/dom.js.map +1 -0
- package/dist/observers/health.d.ts +8 -0
- package/dist/observers/health.d.ts.map +1 -0
- package/dist/observers/health.js +33 -0
- package/dist/observers/health.js.map +1 -0
- package/dist/observers/network.d.ts +11 -0
- package/dist/observers/network.d.ts.map +1 -0
- package/dist/observers/network.js +128 -0
- package/dist/observers/network.js.map +1 -0
- package/dist/observers/route.d.ts +4 -0
- package/dist/observers/route.d.ts.map +1 -0
- package/dist/observers/route.js +56 -0
- package/dist/observers/route.js.map +1 -0
- package/dist/observers/scroll.d.ts +4 -0
- package/dist/observers/scroll.d.ts.map +1 -0
- package/dist/observers/scroll.js +42 -0
- package/dist/observers/scroll.js.map +1 -0
- package/dist/observers/types.d.ts +6 -0
- package/dist/observers/types.d.ts.map +1 -0
- package/dist/observers/types.js +2 -0
- package/dist/observers/types.js.map +1 -0
- package/dist/presenter/overlay.d.ts +12 -0
- package/dist/presenter/overlay.d.ts.map +1 -0
- package/dist/presenter/overlay.js +31 -0
- package/dist/presenter/overlay.js.map +1 -0
- package/dist/presenter/presenter-config.d.ts +86 -0
- package/dist/presenter/presenter-config.d.ts.map +1 -0
- package/dist/presenter/presenter-config.js +51 -0
- package/dist/presenter/presenter-config.js.map +1 -0
- package/dist/presenter/presenter-controls.d.ts +74 -0
- package/dist/presenter/presenter-controls.d.ts.map +1 -0
- package/dist/presenter/presenter-controls.js +360 -0
- package/dist/presenter/presenter-controls.js.map +1 -0
- package/dist/presenter/presenter-effects.d.ts +14 -0
- package/dist/presenter/presenter-effects.d.ts.map +1 -0
- package/dist/presenter/presenter-effects.js +40 -0
- package/dist/presenter/presenter-effects.js.map +1 -0
- package/dist/presenter/presenter-glow.d.ts +43 -0
- package/dist/presenter/presenter-glow.d.ts.map +1 -0
- package/dist/presenter/presenter-glow.js +126 -0
- package/dist/presenter/presenter-glow.js.map +1 -0
- package/dist/presenter/presenter-log.d.ts +43 -0
- package/dist/presenter/presenter-log.d.ts.map +1 -0
- package/dist/presenter/presenter-log.js +118 -0
- package/dist/presenter/presenter-log.js.map +1 -0
- package/dist/presenter/presenter-run-state.d.ts +48 -0
- package/dist/presenter/presenter-run-state.d.ts.map +1 -0
- package/dist/presenter/presenter-run-state.js +37 -0
- package/dist/presenter/presenter-run-state.js.map +1 -0
- package/dist/presenter/presenter-styles.d.ts +7 -0
- package/dist/presenter/presenter-styles.d.ts.map +1 -0
- package/dist/presenter/presenter-styles.js +118 -0
- package/dist/presenter/presenter-styles.js.map +1 -0
- package/dist/presenter/presenter-tally.d.ts +20 -0
- package/dist/presenter/presenter-tally.d.ts.map +1 -0
- package/dist/presenter/presenter-tally.js +38 -0
- package/dist/presenter/presenter-tally.js.map +1 -0
- package/dist/presenter/presenter-verbs.d.ts +2 -0
- package/dist/presenter/presenter-verbs.d.ts.map +1 -0
- package/dist/presenter/presenter-verbs.js +29 -0
- package/dist/presenter/presenter-verbs.js.map +1 -0
- package/dist/presenter/presenter.d.ts +89 -0
- package/dist/presenter/presenter.d.ts.map +1 -0
- package/dist/presenter/presenter.js +428 -0
- package/dist/presenter/presenter.js.map +1 -0
- package/dist/recorder/recorder-styles.d.ts +12 -0
- package/dist/recorder/recorder-styles.d.ts.map +1 -0
- package/dist/recorder/recorder-styles.js +45 -0
- package/dist/recorder/recorder-styles.js.map +1 -0
- package/dist/recorder/recorder.d.ts +60 -0
- package/dist/recorder/recorder.d.ts.map +1 -0
- package/dist/recorder/recorder.js +436 -0
- package/dist/recorder/recorder.js.map +1 -0
- package/dist/registry/adapters.d.ts +31 -0
- package/dist/registry/adapters.d.ts.map +1 -0
- package/dist/registry/adapters.js +47 -0
- package/dist/registry/adapters.js.map +1 -0
- package/dist/registry/capabilities.d.ts +25 -0
- package/dist/registry/capabilities.d.ts.map +1 -0
- package/dist/registry/capabilities.js +48 -0
- package/dist/registry/capabilities.js.map +1 -0
- package/dist/registry/commit-and-signal.d.ts +9 -0
- package/dist/registry/commit-and-signal.d.ts.map +1 -0
- package/dist/registry/commit-and-signal.js +12 -0
- package/dist/registry/commit-and-signal.js.map +1 -0
- package/dist/registry/domains.d.ts +15 -0
- package/dist/registry/domains.d.ts.map +1 -0
- package/dist/registry/domains.js +19 -0
- package/dist/registry/domains.js.map +1 -0
- package/dist/registry/emitter.d.ts +28 -0
- package/dist/registry/emitter.d.ts.map +1 -0
- package/dist/registry/emitter.js +27 -0
- package/dist/registry/emitter.js.map +1 -0
- package/dist/registry/stores.d.ts +9 -0
- package/dist/registry/stores.d.ts.map +1 -0
- package/dist/registry/stores.js +30 -0
- package/dist/registry/stores.js.map +1 -0
- package/dist/reticle-presenter-helpers.d.ts +19 -0
- package/dist/reticle-presenter-helpers.d.ts.map +1 -0
- package/dist/reticle-presenter-helpers.js +81 -0
- package/dist/reticle-presenter-helpers.js.map +1 -0
- package/dist/reticle.d.ts +113 -0
- package/dist/reticle.d.ts.map +1 -0
- package/dist/reticle.js +379 -0
- package/dist/reticle.js.map +1 -0
- package/dist/review/annotator.d.ts +36 -0
- package/dist/review/annotator.d.ts.map +1 -0
- package/dist/review/annotator.js +321 -0
- package/dist/review/annotator.js.map +1 -0
- package/dist/review/mark-anchor.d.ts +23 -0
- package/dist/review/mark-anchor.d.ts.map +1 -0
- package/dist/review/mark-anchor.js +74 -0
- package/dist/review/mark-anchor.js.map +1 -0
- package/dist/security/serialization.d.ts +6 -0
- package/dist/security/serialization.d.ts.map +1 -0
- package/dist/security/serialization.js +103 -0
- package/dist/security/serialization.js.map +1 -0
- package/dist/timers/clock.d.ts +6 -0
- package/dist/timers/clock.d.ts.map +1 -0
- package/dist/timers/clock.js +80 -0
- package/dist/timers/clock.js.map +1 -0
- package/dist/timers/native-timers.d.ts +37 -0
- package/dist/timers/native-timers.d.ts.map +1 -0
- package/dist/timers/native-timers.js +79 -0
- package/dist/timers/native-timers.js.map +1 -0
- package/dist/transport/transport.d.ts +54 -0
- package/dist/transport/transport.d.ts.map +1 -0
- package/dist/transport/transport.js +182 -0
- package/dist/transport/transport.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { EventType } from '@reticlehq/core';
|
|
2
|
+
import { resolveMarkAnchor } from './mark-anchor.js';
|
|
3
|
+
import { nativeSetTimeout, nativeClearTimeout } from '../timers/native-timers.js';
|
|
4
|
+
/** The cursor must rest this long before the outline boxes the element under it — calm, not jumpy. */
|
|
5
|
+
const HIGHLIGHT_REST_MS = 130;
|
|
6
|
+
/** Single base attribute on every UI node (varied by VALUE) so `closest('[data-reticle-mark]')`
|
|
7
|
+
* catches the FAB, popover, and pins in one check, and the SDK's observers ignore them all. */
|
|
8
|
+
const MARK_ATTR = 'data-reticle-mark';
|
|
9
|
+
const ACTIVE_ATTR = 'data-reticle-mark-active';
|
|
10
|
+
const Z = 2147483640;
|
|
11
|
+
const sel = (role) => `[${MARK_ATTR}="${role}"]`;
|
|
12
|
+
const CSS = `
|
|
13
|
+
${sel('fab')}{position:fixed;left:18px;bottom:18px;z-index:${String(Z + 2)};
|
|
14
|
+
font:500 13px/1 "Inter",system-ui,sans-serif;display:inline-flex;align-items:center;gap:7px;
|
|
15
|
+
padding:9px 13px;border-radius:11px;cursor:pointer;color:#e9ebf2;
|
|
16
|
+
background:linear-gradient(180deg,rgba(19,22,32,.92),rgba(13,15,22,.92));
|
|
17
|
+
border:1px solid rgba(255,255,255,.12);box-shadow:0 10px 30px -10px rgba(0,0,0,.6);
|
|
18
|
+
-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);transition:transform .12s,border-color .15s;}
|
|
19
|
+
${sel('fab')}:hover{transform:translateY(-1px);border-color:rgba(124,131,255,.55);}
|
|
20
|
+
${sel('fab')}[data-on="1"]{color:#fff;border-color:#7c83ff;background:linear-gradient(180deg,#6366f1,#4f46e5);}
|
|
21
|
+
${sel('dot')}{width:8px;height:8px;border-radius:50%;background:#ff7a7a;flex:none;}
|
|
22
|
+
${sel('fab')}[data-on="1"] ${sel('dot')}{background:#fff;}
|
|
23
|
+
html[${ACTIVE_ATTR}] *{cursor:crosshair !important;}
|
|
24
|
+
/* The Flag button + its popover are interactive — keep the pointer cursor over them, not crosshair. */
|
|
25
|
+
html[${ACTIVE_ATTR}] ${sel('fab')},html[${ACTIVE_ATTR}] ${sel('fab')} *,html[${ACTIVE_ATTR}] ${sel('pop')},html[${ACTIVE_ATTR}] ${sel('pop')} *{cursor:pointer !important;}
|
|
26
|
+
/* The outline glides to the rested element with an ease (soothing), and fades rather than snapping. */
|
|
27
|
+
${sel('hi')}{position:fixed;z-index:${String(Z + 1)};pointer-events:none;opacity:0;box-sizing:border-box;
|
|
28
|
+
border:2px solid #7c83ff;border-radius:6px;background:rgba(124,131,255,.12);box-shadow:0 0 0 2px rgba(124,131,255,.22);
|
|
29
|
+
transition:left .22s cubic-bezier(.22,1,.36,1),top .22s cubic-bezier(.22,1,.36,1),width .22s cubic-bezier(.22,1,.36,1),height .22s cubic-bezier(.22,1,.36,1),opacity .18s ease;}
|
|
30
|
+
${sel('hi')}[data-on="1"]{opacity:1;}
|
|
31
|
+
${sel('hilabel')}{position:absolute;top:-21px;left:-2px;background:#6366f1;color:#fff;
|
|
32
|
+
font:600 10.5px/1 "Inter",system-ui,sans-serif;padding:3px 6px;border-radius:5px;white-space:nowrap;
|
|
33
|
+
max-width:300px;overflow:hidden;text-overflow:ellipsis;}
|
|
34
|
+
${sel('pin')}{position:fixed;z-index:${String(Z + 1)};width:22px;height:22px;margin:-11px 0 0 -11px;
|
|
35
|
+
border-radius:50% 50% 50% 2px;background:#ff5d5d;border:2px solid #fff;box-shadow:0 4px 12px -2px rgba(0,0,0,.5);
|
|
36
|
+
color:#fff;font:700 11px/18px "Inter",system-ui,sans-serif;text-align:center;pointer-events:none;}
|
|
37
|
+
${sel('pop')}{position:fixed;z-index:${String(Z + 3)};width:280px;box-sizing:border-box;
|
|
38
|
+
background:linear-gradient(180deg,rgba(19,22,32,.96),rgba(13,15,22,.96));border:1px solid rgba(255,255,255,.14);
|
|
39
|
+
border-radius:14px;padding:12px;box-shadow:0 24px 60px -16px rgba(0,0,0,.7);
|
|
40
|
+
-webkit-backdrop-filter:blur(24px);backdrop-filter:blur(24px);font:13px/1.5 "Inter",system-ui,sans-serif;color:#e9ebf2;}
|
|
41
|
+
${sel('pop')} .reticle-mark-where{color:#9aa0b2;font-size:11.5px;margin-bottom:7px;
|
|
42
|
+
white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
|
|
43
|
+
${sel('pop')} textarea{width:100%;box-sizing:border-box;min-height:62px;resize:vertical;
|
|
44
|
+
background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.12);border-radius:9px;color:#e9ebf2;
|
|
45
|
+
font:13px/1.45 "Inter",system-ui,sans-serif;padding:8px;outline:none;}
|
|
46
|
+
${sel('pop')} textarea:focus{border-color:#7c83ff;}
|
|
47
|
+
${sel('pop')} .reticle-mark-row{display:flex;gap:8px;align-items:center;margin-top:9px;}
|
|
48
|
+
${sel('pop')} .reticle-mark-hint{margin-right:auto;color:#6a7186;font-size:10.5px;letter-spacing:.02em;}
|
|
49
|
+
${sel('pop')} button{font:600 12px/1 "Inter",system-ui,sans-serif;padding:8px 12px;border-radius:9px;cursor:pointer;border:1px solid rgba(255,255,255,.12);background:rgba(255,255,255,.05);color:#cdd2e2;}
|
|
50
|
+
${sel('pop')} button[data-send]{background:#6366f1;border-color:#7c83ff;color:#fff;}
|
|
51
|
+
${sel('pop')} button[data-send]:disabled{opacity:.5;cursor:default;}`;
|
|
52
|
+
export class Annotator {
|
|
53
|
+
#emit;
|
|
54
|
+
#now;
|
|
55
|
+
#onMark;
|
|
56
|
+
#root;
|
|
57
|
+
#fab;
|
|
58
|
+
#pop;
|
|
59
|
+
/** Hover outline box that shows WHICH element a click would flag (agentation-style). */
|
|
60
|
+
#hi;
|
|
61
|
+
#hiLabel;
|
|
62
|
+
/** Debounce timer — boxes the element only once the cursor rests, so a fast sweep doesn't flicker. */
|
|
63
|
+
#hiTimer;
|
|
64
|
+
#active = false;
|
|
65
|
+
#markCount = 0;
|
|
66
|
+
#onClick;
|
|
67
|
+
#onKeydown;
|
|
68
|
+
#onMove;
|
|
69
|
+
constructor(deps) {
|
|
70
|
+
this.#emit = deps.emit;
|
|
71
|
+
this.#now = deps.now;
|
|
72
|
+
this.#onMark = deps.onMark;
|
|
73
|
+
}
|
|
74
|
+
/** Whether annotate mode is currently capturing clicks. */
|
|
75
|
+
get active() {
|
|
76
|
+
return this.#active;
|
|
77
|
+
}
|
|
78
|
+
/** Number of marks sent this session (drives the pin numbering). */
|
|
79
|
+
get markCount() {
|
|
80
|
+
return this.#markCount;
|
|
81
|
+
}
|
|
82
|
+
mount() {
|
|
83
|
+
if (this.#root !== undefined || typeof document === 'undefined')
|
|
84
|
+
return;
|
|
85
|
+
const style = document.createElement('style');
|
|
86
|
+
style.setAttribute(MARK_ATTR, 'style');
|
|
87
|
+
style.textContent = CSS;
|
|
88
|
+
document.head.appendChild(style);
|
|
89
|
+
const root = document.createElement('div');
|
|
90
|
+
root.setAttribute(MARK_ATTR, 'root');
|
|
91
|
+
root.innerHTML = `<button type="button" ${MARK_ATTR}="fab" aria-label="Flag a bug for the agent">
|
|
92
|
+
<span ${MARK_ATTR}="dot"></span><span>Flag a bug</span></button>
|
|
93
|
+
<div ${MARK_ATTR}="hi"><span ${MARK_ATTR}="hilabel"></span></div>`;
|
|
94
|
+
document.body.appendChild(root);
|
|
95
|
+
this.#root = root;
|
|
96
|
+
this.#fab = root.querySelector(sel('fab')) ?? undefined;
|
|
97
|
+
this.#hi = root.querySelector(sel('hi')) ?? undefined;
|
|
98
|
+
this.#hiLabel = root.querySelector(sel('hilabel')) ?? undefined;
|
|
99
|
+
this.#fab?.addEventListener('click', (e) => {
|
|
100
|
+
e.stopPropagation();
|
|
101
|
+
this.toggle();
|
|
102
|
+
});
|
|
103
|
+
// Capture-phase click: intercept the FIRST click on a real page element while active.
|
|
104
|
+
this.#onClick = (ev) => this.#handleClick(ev);
|
|
105
|
+
document.addEventListener('click', this.#onClick, { capture: true });
|
|
106
|
+
// Hover outline: while active, box the element a click would flag — so you see what you're pointing at.
|
|
107
|
+
this.#onMove = (ev) => this.#scheduleMove(ev);
|
|
108
|
+
document.addEventListener('mousemove', this.#onMove, { passive: true, capture: true });
|
|
109
|
+
// Escape is the universal "back out": close an open popover, else leave annotate mode.
|
|
110
|
+
this.#onKeydown = (ev) => {
|
|
111
|
+
if (ev.key !== 'Escape' || !this.#active)
|
|
112
|
+
return;
|
|
113
|
+
if (this.#pop !== undefined)
|
|
114
|
+
this.#closePopover();
|
|
115
|
+
else
|
|
116
|
+
this.toggle(false);
|
|
117
|
+
};
|
|
118
|
+
document.addEventListener('keydown', this.#onKeydown);
|
|
119
|
+
}
|
|
120
|
+
destroy() {
|
|
121
|
+
if (this.#onClick !== undefined) {
|
|
122
|
+
document.removeEventListener('click', this.#onClick, { capture: true });
|
|
123
|
+
this.#onClick = undefined;
|
|
124
|
+
}
|
|
125
|
+
if (this.#onKeydown !== undefined) {
|
|
126
|
+
document.removeEventListener('keydown', this.#onKeydown);
|
|
127
|
+
this.#onKeydown = undefined;
|
|
128
|
+
}
|
|
129
|
+
if (this.#onMove !== undefined) {
|
|
130
|
+
document.removeEventListener('mousemove', this.#onMove, { capture: true });
|
|
131
|
+
this.#onMove = undefined;
|
|
132
|
+
}
|
|
133
|
+
if (this.#hiTimer !== undefined) {
|
|
134
|
+
nativeClearTimeout(this.#hiTimer);
|
|
135
|
+
this.#hiTimer = undefined;
|
|
136
|
+
}
|
|
137
|
+
this.#closePopover();
|
|
138
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
139
|
+
this.#root?.remove();
|
|
140
|
+
document.querySelectorAll(`style[${MARK_ATTR}="style"]`).forEach((s) => s.remove());
|
|
141
|
+
this.#root = undefined;
|
|
142
|
+
this.#fab = undefined;
|
|
143
|
+
this.#active = false;
|
|
144
|
+
}
|
|
145
|
+
/** Turn annotate mode on/off. With no argument, flips the current state. */
|
|
146
|
+
toggle(on) {
|
|
147
|
+
this.#active = on ?? !this.#active;
|
|
148
|
+
this.#fab?.setAttribute('data-on', this.#active ? '1' : '0');
|
|
149
|
+
if (this.#active)
|
|
150
|
+
document.documentElement.setAttribute(ACTIVE_ATTR, '1');
|
|
151
|
+
else {
|
|
152
|
+
document.documentElement.removeAttribute(ACTIVE_ATTR);
|
|
153
|
+
this.#hideHighlight();
|
|
154
|
+
this.#closePopover();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
#handleClick(ev) {
|
|
158
|
+
if (!this.#active)
|
|
159
|
+
return;
|
|
160
|
+
const target = ev.target;
|
|
161
|
+
if (!(target instanceof Element))
|
|
162
|
+
return;
|
|
163
|
+
if (target.closest(`[${MARK_ATTR}]`) !== null)
|
|
164
|
+
return; // never mark our own UI
|
|
165
|
+
ev.preventDefault();
|
|
166
|
+
ev.stopPropagation();
|
|
167
|
+
this.#openPopover(target, ev.clientX, ev.clientY);
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* Debounce the outline: while the cursor is moving the human is still travelling, so we wait for a
|
|
171
|
+
* brief rest before boxing the element under it (the box then eases into place via CSS). A pending
|
|
172
|
+
* timer is replaced on every move, so only the resting position ever paints.
|
|
173
|
+
*/
|
|
174
|
+
#scheduleMove(ev) {
|
|
175
|
+
if (this.#hiTimer !== undefined)
|
|
176
|
+
nativeClearTimeout(this.#hiTimer);
|
|
177
|
+
this.#hiTimer = nativeSetTimeout(() => {
|
|
178
|
+
this.#hiTimer = undefined;
|
|
179
|
+
this.#handleMove(ev);
|
|
180
|
+
}, HIGHLIGHT_REST_MS);
|
|
181
|
+
}
|
|
182
|
+
/** Box the element under the cursor (when active, no popover open) so you see what a click flags. */
|
|
183
|
+
#handleMove(ev) {
|
|
184
|
+
if (this.#hi === undefined)
|
|
185
|
+
return;
|
|
186
|
+
const target = ev.target;
|
|
187
|
+
const skip = !this.#active ||
|
|
188
|
+
this.#pop !== undefined ||
|
|
189
|
+
!(target instanceof Element) ||
|
|
190
|
+
target.closest(`[${MARK_ATTR}]`) !== null;
|
|
191
|
+
if (skip) {
|
|
192
|
+
this.#hi.setAttribute('data-on', '0');
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const rect = target.getBoundingClientRect();
|
|
196
|
+
if (rect.width === 0 && rect.height === 0) {
|
|
197
|
+
this.#hi.setAttribute('data-on', '0');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
this.#hi.style.left = `${String(rect.left)}px`;
|
|
201
|
+
this.#hi.style.top = `${String(rect.top)}px`;
|
|
202
|
+
this.#hi.style.width = `${String(rect.width)}px`;
|
|
203
|
+
this.#hi.style.height = `${String(rect.height)}px`;
|
|
204
|
+
this.#hi.setAttribute('data-on', '1');
|
|
205
|
+
if (this.#hiLabel !== undefined)
|
|
206
|
+
this.#hiLabel.textContent = describeEl(target);
|
|
207
|
+
}
|
|
208
|
+
/** Hide the hover outline (annotate mode off, or a popover took over). Cancels any pending box. */
|
|
209
|
+
#hideHighlight() {
|
|
210
|
+
if (this.#hiTimer !== undefined) {
|
|
211
|
+
nativeClearTimeout(this.#hiTimer);
|
|
212
|
+
this.#hiTimer = undefined;
|
|
213
|
+
}
|
|
214
|
+
this.#hi?.setAttribute('data-on', '0');
|
|
215
|
+
}
|
|
216
|
+
#openPopover(el, x, y) {
|
|
217
|
+
this.#closePopover();
|
|
218
|
+
this.#hideHighlight(); // the popover is now the focus; drop the hover box
|
|
219
|
+
const resolved = resolveMarkAnchor(el);
|
|
220
|
+
const pop = document.createElement('div');
|
|
221
|
+
pop.setAttribute(MARK_ATTR, 'pop');
|
|
222
|
+
const where = resolved.source !== undefined
|
|
223
|
+
? `${resolved.label} · ${resolved.source.file}:${String(resolved.source.line)}`
|
|
224
|
+
: resolved.label;
|
|
225
|
+
pop.innerHTML = `<div class="reticle-mark-where"></div>
|
|
226
|
+
<textarea placeholder="What's wrong here? The agent will read this and fix it."></textarea>
|
|
227
|
+
<div class="reticle-mark-row"><span class="reticle-mark-hint">⌘↵ send · esc cancel</span><button type="button" data-cancel>Cancel</button>
|
|
228
|
+
<button type="button" data-send disabled>Send to agent</button></div>`;
|
|
229
|
+
const whereEl = pop.querySelector('.reticle-mark-where');
|
|
230
|
+
if (whereEl !== null)
|
|
231
|
+
whereEl.textContent = where;
|
|
232
|
+
const left = Math.min(x, window.innerWidth - 296);
|
|
233
|
+
const top = Math.min(y + 12, window.innerHeight - 170);
|
|
234
|
+
pop.style.left = `${String(Math.max(8, left))}px`;
|
|
235
|
+
pop.style.top = `${String(Math.max(8, top))}px`;
|
|
236
|
+
document.body.appendChild(pop);
|
|
237
|
+
this.#pop = pop;
|
|
238
|
+
const textarea = pop.querySelector('textarea');
|
|
239
|
+
const send = pop.querySelector('button[data-send]');
|
|
240
|
+
const submit = () => {
|
|
241
|
+
const note = textarea?.value.trim() ?? '';
|
|
242
|
+
if (note.length === 0)
|
|
243
|
+
return; // never send an empty mark
|
|
244
|
+
this.#sendMark(note, resolved, x, y);
|
|
245
|
+
this.#closePopover();
|
|
246
|
+
};
|
|
247
|
+
textarea?.addEventListener('input', () => {
|
|
248
|
+
if (send !== null)
|
|
249
|
+
send.disabled = textarea.value.trim().length === 0;
|
|
250
|
+
});
|
|
251
|
+
// Keyboard ergonomics devs expect: ⌘/Ctrl+Enter sends, Esc cancels — without leaving the textarea.
|
|
252
|
+
textarea?.addEventListener('keydown', (e) => {
|
|
253
|
+
if (e.key === 'Escape') {
|
|
254
|
+
e.preventDefault();
|
|
255
|
+
this.#closePopover();
|
|
256
|
+
}
|
|
257
|
+
else if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
|
258
|
+
e.preventDefault();
|
|
259
|
+
submit();
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
textarea?.focus();
|
|
263
|
+
pop.querySelector('button[data-cancel]')?.addEventListener('click', () => this.#closePopover());
|
|
264
|
+
send?.addEventListener('click', submit);
|
|
265
|
+
}
|
|
266
|
+
#sendMark(note, resolved, x, y) {
|
|
267
|
+
const data = {
|
|
268
|
+
note,
|
|
269
|
+
anchor: resolved.anchor,
|
|
270
|
+
strategy: resolved.strategy,
|
|
271
|
+
label: resolved.label,
|
|
272
|
+
route: typeof location === 'undefined' ? '' : location.pathname + location.search,
|
|
273
|
+
};
|
|
274
|
+
if (resolved.source !== undefined)
|
|
275
|
+
data['source'] = resolved.source;
|
|
276
|
+
this.#emit(EventType.HUMAN_MARK, data);
|
|
277
|
+
this.#onMark?.(note, resolved.label); // echo into the live panel so the human sees the flag land
|
|
278
|
+
this.#markCount += 1;
|
|
279
|
+
this.#dropPin(x, y, this.#markCount);
|
|
280
|
+
}
|
|
281
|
+
#dropPin(x, y, n) {
|
|
282
|
+
if (this.#root === undefined)
|
|
283
|
+
return;
|
|
284
|
+
const pin = document.createElement('div');
|
|
285
|
+
pin.setAttribute(MARK_ATTR, 'pin');
|
|
286
|
+
pin.style.left = `${String(x)}px`;
|
|
287
|
+
pin.style.top = `${String(y)}px`;
|
|
288
|
+
pin.textContent = String(n);
|
|
289
|
+
this.#root.appendChild(pin);
|
|
290
|
+
// The pin fades after a moment so it confirms the mark landed without cluttering the page.
|
|
291
|
+
const ref = pin;
|
|
292
|
+
this.#now(); // touch the injected clock (kept for parity with the SDK's timing seams)
|
|
293
|
+
window.setTimeout(() => ref.remove(), 2600);
|
|
294
|
+
}
|
|
295
|
+
#closePopover() {
|
|
296
|
+
this.#pop?.remove();
|
|
297
|
+
this.#pop = undefined;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* A short label for the hovered element — testid > aria-label > tag + text. Cheap on purpose (runs
|
|
302
|
+
* on every mousemove, so it does NOT call resolveMarkAnchor; the full anchor is resolved on click).
|
|
303
|
+
*/
|
|
304
|
+
function describeEl(el) {
|
|
305
|
+
const testid = el.getAttribute('data-testid');
|
|
306
|
+
if (testid !== null && testid.length > 0)
|
|
307
|
+
return testid;
|
|
308
|
+
const tag = el.tagName.toLowerCase();
|
|
309
|
+
const aria = el.getAttribute('aria-label');
|
|
310
|
+
if (aria !== null && aria.length > 0)
|
|
311
|
+
return `${tag} "${aria}"`;
|
|
312
|
+
const text = (el.textContent ?? '').trim().replace(/\s+/g, ' ').slice(0, 40);
|
|
313
|
+
return text.length > 0 ? `${tag} "${text}"` : tag;
|
|
314
|
+
}
|
|
315
|
+
/** Construct + mount the annotator (mirrors installRecorder's ergonomics). */
|
|
316
|
+
export function installAnnotator(deps) {
|
|
317
|
+
const annotator = new Annotator(deps);
|
|
318
|
+
annotator.mount();
|
|
319
|
+
return annotator;
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=annotator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"annotator.js","sourceRoot":"","sources":["../../src/review/annotator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { MarkAnchorStrategy } from '@reticlehq/core';
|
|
2
|
+
/**
|
|
3
|
+
* The element address carried by a human review mark: a re-resolvable anchor (auto-anchor's most
|
|
4
|
+
* durable tier for this element), its durability strategy, a human-legible label, and — crucially —
|
|
5
|
+
* the source file:line the agent should open. Source is reported INDEPENDENTLY of the anchor tier:
|
|
6
|
+
* a role-anchored element still carries its babel-stamped source so the agent can jump to the code.
|
|
7
|
+
*/
|
|
8
|
+
export interface MarkAnchor {
|
|
9
|
+
anchor: string;
|
|
10
|
+
strategy: MarkAnchorStrategy;
|
|
11
|
+
label: string;
|
|
12
|
+
source?: {
|
|
13
|
+
file: string;
|
|
14
|
+
line: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Resolve a clicked element into the address a human review mark carries. Reuses the same
|
|
19
|
+
* auto-anchor machinery a recorded flow uses, so a mark pins to the element the way Reticle addresses
|
|
20
|
+
* it everywhere else — and always reports the source file:line when one is available.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveMarkAnchor(el: Element): MarkAnchor;
|
|
23
|
+
//# sourceMappingURL=mark-anchor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mark-anchor.d.ts","sourceRoot":"","sources":["../../src/review/mark-anchor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AASrD;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAqCD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,OAAO,GAAG,UAAU,CAuBzD"}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { MarkAnchorStrategy } from '@reticlehq/core';
|
|
2
|
+
import { AnchorStrategy, synthesizeAnchor } from '../dom/auto-anchor.js';
|
|
3
|
+
import { getAccessibleName, getRole } from '../dom/a11y.js';
|
|
4
|
+
import { identifyComponent } from '../registry/adapters.js';
|
|
5
|
+
/** Attribute names — defined locally per the recorder/query convention (no shared free string). */
|
|
6
|
+
const TESTID_ATTR = 'data-testid';
|
|
7
|
+
const SOURCE_ATTR = 'data-reticle-source';
|
|
8
|
+
/** AnchorStrategy and MarkAnchorStrategy share string values; map explicitly (no cross-cast). */
|
|
9
|
+
const STRATEGY = {
|
|
10
|
+
[AnchorStrategy.TESTID]: MarkAnchorStrategy.TESTID,
|
|
11
|
+
[AnchorStrategy.COMPONENT]: MarkAnchorStrategy.COMPONENT,
|
|
12
|
+
[AnchorStrategy.ROLE]: MarkAnchorStrategy.ROLE,
|
|
13
|
+
[AnchorStrategy.POSITION]: MarkAnchorStrategy.POSITION,
|
|
14
|
+
};
|
|
15
|
+
/** Parse a `data-reticle-source="file:line:column"` value into `{ file, line }` (column dropped). */
|
|
16
|
+
function parseSourceAttr(value) {
|
|
17
|
+
if (value === null)
|
|
18
|
+
return undefined;
|
|
19
|
+
const m = /^(.*):(\d+):(\d+)$/.exec(value);
|
|
20
|
+
if (m === null)
|
|
21
|
+
return undefined;
|
|
22
|
+
const file = m[1];
|
|
23
|
+
const line = Number(m[2]);
|
|
24
|
+
if (file === undefined || file.length === 0 || !Number.isFinite(line))
|
|
25
|
+
return undefined;
|
|
26
|
+
return { file, line };
|
|
27
|
+
}
|
|
28
|
+
/** Best source for the element: the framework adapter's, else the nearest babel-stamped attribute. */
|
|
29
|
+
function sourceFor(el, adapterSource) {
|
|
30
|
+
if (adapterSource !== undefined)
|
|
31
|
+
return { file: adapterSource.file, line: adapterSource.line };
|
|
32
|
+
const host = el.closest(`[${SOURCE_ATTR}]`);
|
|
33
|
+
return host !== null ? parseSourceAttr(host.getAttribute(SOURCE_ATTR)) : undefined;
|
|
34
|
+
}
|
|
35
|
+
/** A short, human-legible label for the element: `role "name"`, falling back to role / tag name. */
|
|
36
|
+
function labelFor(el, role, name) {
|
|
37
|
+
if (name.length > 0)
|
|
38
|
+
return role.length > 0 ? `${role} "${name}"` : `"${name}"`;
|
|
39
|
+
return role.length > 0 ? role : el.tagName.toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve a clicked element into the address a human review mark carries. Reuses the same
|
|
43
|
+
* auto-anchor machinery a recorded flow uses, so a mark pins to the element the way Reticle addresses
|
|
44
|
+
* it everywhere else — and always reports the source file:line when one is available.
|
|
45
|
+
*/
|
|
46
|
+
export function resolveMarkAnchor(el) {
|
|
47
|
+
const testid = el.getAttribute(TESTID_ATTR) ?? undefined;
|
|
48
|
+
const info = identifyComponent(el);
|
|
49
|
+
const component = info?.componentStack[0];
|
|
50
|
+
const source = sourceFor(el, info?.source);
|
|
51
|
+
const role = getRole(el);
|
|
52
|
+
const name = getAccessibleName(el);
|
|
53
|
+
const input = {};
|
|
54
|
+
if (testid !== undefined)
|
|
55
|
+
input.testid = testid;
|
|
56
|
+
if (component !== undefined)
|
|
57
|
+
input.component = component;
|
|
58
|
+
if (source !== undefined)
|
|
59
|
+
input.source = source;
|
|
60
|
+
if (role.length > 0)
|
|
61
|
+
input.role = role;
|
|
62
|
+
if (name.length > 0)
|
|
63
|
+
input.name = name;
|
|
64
|
+
const synthesized = synthesizeAnchor(input);
|
|
65
|
+
const out = {
|
|
66
|
+
anchor: synthesized.value,
|
|
67
|
+
strategy: STRATEGY[synthesized.strategy],
|
|
68
|
+
label: labelFor(el, role, name),
|
|
69
|
+
};
|
|
70
|
+
if (source !== undefined)
|
|
71
|
+
out.source = source;
|
|
72
|
+
return out;
|
|
73
|
+
}
|
|
74
|
+
//# sourceMappingURL=mark-anchor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mark-anchor.js","sourceRoot":"","sources":["../../src/review/mark-anchor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isSensitiveKey(key: string): boolean;
|
|
2
|
+
/** Convert arbitrary app state into a bounded, redacted JSON-compatible value. */
|
|
3
|
+
export declare function sanitizeForTransport(value: unknown): unknown;
|
|
4
|
+
/** Serialize without allowing cycles, BigInt, getters, or secrets to break the transport. */
|
|
5
|
+
export declare function safeStringify(value: unknown): string;
|
|
6
|
+
//# sourceMappingURL=serialization.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialization.d.ts","sourceRoot":"","sources":["../../src/security/serialization.ts"],"names":[],"mappings":"AAgBA,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAEnD;AAmFD,kFAAkF;AAClF,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAW5D;AAED,6FAA6F;AAC7F,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMpD"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { REDACTED_VALUE, TRANSPORT_LIMITS } from '@reticlehq/core';
|
|
2
|
+
const TRUNCATED_VALUE = '[TRUNCATED]';
|
|
3
|
+
const UNSERIALIZABLE_VALUE = '[UNSERIALIZABLE]';
|
|
4
|
+
const OMIT_VALUE = Symbol('omit');
|
|
5
|
+
const MAX_KEY_LENGTH = 256;
|
|
6
|
+
const MAX_TOTAL_CHARACTERS = Math.floor(TRANSPORT_LIMITS.MAX_MESSAGE_BYTES / 8);
|
|
7
|
+
const MAX_TOTAL_NODES = TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS * 5;
|
|
8
|
+
// `token` must match auth CREDENTIALS, not compound design fields. Bare/separated `token(s)` and
|
|
9
|
+
// auth-prefixed tokens (accessToken, auth_token, sessionToken, …) are redacted; `colorToken`,
|
|
10
|
+
// `backgroundToken`, `tokenCount`, `designToken` are NOT — they were false-positives that redacted
|
|
11
|
+
// legitimate reticle_inspect/reticle_state output.
|
|
12
|
+
const SENSITIVE_KEY = /password|passwd|passcode|secret|(?:(?:access|refresh|auth|bearer|api|id|session|csrf|client)[-_]?tokens?|(?:^|[-_])tokens?(?=$|[-_]))|authorization|api[-_]?key|access[-_]?key|private[-_]?key|client[-_]?secret|credit[-_]?card|card[-_]?number|cvv|cvc|ssn/i;
|
|
13
|
+
export function isSensitiveKey(key) {
|
|
14
|
+
return SENSITIVE_KEY.test(key);
|
|
15
|
+
}
|
|
16
|
+
function boundedString(value, state, max) {
|
|
17
|
+
const allowed = Math.max(0, Math.min(max, state.remainingCharacters));
|
|
18
|
+
if (value.length <= allowed) {
|
|
19
|
+
state.remainingCharacters -= value.length;
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
const truncated = allowed <= TRUNCATED_VALUE.length
|
|
23
|
+
? TRUNCATED_VALUE.slice(0, allowed)
|
|
24
|
+
: `${value.slice(0, allowed - TRUNCATED_VALUE.length)}${TRUNCATED_VALUE}`;
|
|
25
|
+
state.remainingCharacters -= truncated.length;
|
|
26
|
+
return truncated;
|
|
27
|
+
}
|
|
28
|
+
function sanitize(value, state, depth, key) {
|
|
29
|
+
if (key !== undefined && isSensitiveKey(key))
|
|
30
|
+
return REDACTED_VALUE;
|
|
31
|
+
if (depth > TRANSPORT_LIMITS.MAX_SERIALIZE_DEPTH || state.nodes >= MAX_TOTAL_NODES) {
|
|
32
|
+
return TRUNCATED_VALUE;
|
|
33
|
+
}
|
|
34
|
+
state.nodes += 1;
|
|
35
|
+
if (value === null || typeof value === 'boolean')
|
|
36
|
+
return value;
|
|
37
|
+
if (typeof value === 'string') {
|
|
38
|
+
return boundedString(value, state, key?.toLowerCase() === 'error'
|
|
39
|
+
? TRANSPORT_LIMITS.MAX_ERROR_LENGTH
|
|
40
|
+
: TRANSPORT_LIMITS.MAX_STRING_LENGTH);
|
|
41
|
+
}
|
|
42
|
+
if (typeof value === 'number')
|
|
43
|
+
return Number.isFinite(value) ? value : null;
|
|
44
|
+
if (typeof value === 'bigint')
|
|
45
|
+
return value.toString();
|
|
46
|
+
if (typeof value === 'undefined' || typeof value === 'function' || typeof value === 'symbol') {
|
|
47
|
+
return OMIT_VALUE;
|
|
48
|
+
}
|
|
49
|
+
if (value instanceof Date)
|
|
50
|
+
return value.toISOString();
|
|
51
|
+
if (value instanceof Error) {
|
|
52
|
+
return {
|
|
53
|
+
name: boundedString(value.name, state, 256),
|
|
54
|
+
message: boundedString(value.message, state, TRANSPORT_LIMITS.MAX_ERROR_LENGTH),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (state.seen.has(value))
|
|
58
|
+
return '[CIRCULAR]';
|
|
59
|
+
state.seen.add(value);
|
|
60
|
+
try {
|
|
61
|
+
if (Array.isArray(value)) {
|
|
62
|
+
return value.slice(0, TRANSPORT_LIMITS.MAX_COLLECTION_ITEMS).map((item) => {
|
|
63
|
+
const sanitized = sanitize(item, state, depth + 1);
|
|
64
|
+
return sanitized === OMIT_VALUE ? null : sanitized;
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
const out = Object.create(null);
|
|
68
|
+
for (const rawKey of Object.keys(value).slice(0, TRANSPORT_LIMITS.MAX_OBJECT_KEYS)) {
|
|
69
|
+
const safeKey = boundedString(rawKey, state, MAX_KEY_LENGTH);
|
|
70
|
+
try {
|
|
71
|
+
const sanitized = sanitize(value[rawKey], state, depth + 1, rawKey);
|
|
72
|
+
if (sanitized !== OMIT_VALUE)
|
|
73
|
+
out[safeKey] = sanitized;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
out[safeKey] = UNSERIALIZABLE_VALUE;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
state.seen.delete(value);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** Convert arbitrary app state into a bounded, redacted JSON-compatible value. */
|
|
86
|
+
export function sanitizeForTransport(value) {
|
|
87
|
+
const sanitized = sanitize(value, {
|
|
88
|
+
seen: new WeakSet(),
|
|
89
|
+
remainingCharacters: MAX_TOTAL_CHARACTERS,
|
|
90
|
+
nodes: 0,
|
|
91
|
+
}, 0);
|
|
92
|
+
return sanitized === OMIT_VALUE ? null : sanitized;
|
|
93
|
+
}
|
|
94
|
+
/** Serialize without allowing cycles, BigInt, getters, or secrets to break the transport. */
|
|
95
|
+
export function safeStringify(value) {
|
|
96
|
+
try {
|
|
97
|
+
return JSON.stringify(sanitizeForTransport(value));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return JSON.stringify(UNSERIALIZABLE_VALUE);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=serialization.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialization.js","sourceRoot":"","sources":["../../src/security/serialization.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function isClockFrozen(): boolean;
|
|
2
|
+
export declare function freezeClock(): void;
|
|
3
|
+
/** Run all timers due within the next `ms` of virtual time, in order. */
|
|
4
|
+
export declare function advanceClock(ms: number): void;
|
|
5
|
+
export declare function resetClock(): void;
|
|
6
|
+
//# sourceMappingURL=clock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clock.d.ts","sourceRoot":"","sources":["../../src/timers/clock.ts"],"names":[],"mappings":"AA2BA,wBAAgB,aAAa,IAAI,OAAO,CAEvC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAgClC;AAED,yEAAyE;AACzE,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,CAkB7C;AAED,wBAAgB,UAAU,IAAI,IAAI,CAWjC"}
|