@reticlehq/browser 2.13.0 → 2.14.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/dist/actions/actions-dom.d.ts +0 -20
- package/dist/actions/actions-dom.js +0 -66
- package/dist/actions/actions-press.d.ts +10 -0
- package/dist/actions/actions-press.js +77 -0
- package/dist/actions/actions.d.ts +0 -89
- package/dist/actions/actions.js +24 -358
- package/dist/actions/appeared-text.d.ts +0 -20
- package/dist/actions/appeared-text.js +0 -36
- package/dist/actions/danger-context.d.ts +3 -0
- package/dist/actions/danger-context.js +39 -0
- package/dist/actions/scroll.d.ts +0 -9
- package/dist/actions/scroll.js +0 -7
- package/dist/actions/synthetic-input.d.ts +0 -7
- package/dist/actions/synthetic-input.js +0 -27
- package/dist/actions/value-input.d.ts +0 -30
- package/dist/actions/value-input.js +0 -33
- package/dist/commands/commands.d.ts +0 -2
- package/dist/commands/commands.js +0 -67
- package/dist/connect-options.d.ts +2 -96
- package/dist/dom/a11y.d.ts +0 -29
- package/dist/dom/a11y.js +1 -88
- package/dist/dom/auto-anchor.d.ts +0 -40
- package/dist/dom/auto-anchor.js +0 -33
- package/dist/dom/chart.d.ts +0 -61
- package/dist/dom/chart.js +0 -60
- package/dist/dom/desktop-capture.d.ts +0 -30
- package/dist/dom/desktop-capture.js +0 -72
- package/dist/dom/dom-ignore.d.ts +0 -30
- package/dist/dom/dom-ignore.js +0 -47
- package/dist/dom/occlusion.d.ts +0 -7
- package/dist/dom/occlusion.js +0 -7
- package/dist/dom/query.d.ts +0 -14
- package/dist/dom/query.js +48 -214
- package/dist/dom/realm.d.ts +0 -20
- package/dist/dom/realm.js +0 -28
- package/dist/dom/refs.d.ts +0 -76
- package/dist/dom/refs.js +2 -133
- package/dist/dom/shadow-registry.d.ts +0 -33
- package/dist/dom/shadow-registry.js +2 -72
- package/dist/dom/snapshot.d.ts +2 -43
- package/dist/dom/snapshot.js +26 -151
- package/dist/dom/source.d.ts +0 -44
- package/dist/dom/source.js +0 -45
- package/dist/dom/theme.d.ts +0 -37
- package/dist/dom/theme.js +2 -52
- package/dist/dom/virtualized.d.ts +0 -17
- package/dist/dom/virtualized.js +0 -27
- package/dist/edit-epoch.d.ts +0 -22
- package/dist/edit-epoch.js +0 -52
- package/dist/index.js +1 -23
- package/dist/observers/animation.d.ts +0 -1
- package/dist/observers/animation.js +0 -3
- package/dist/observers/blind-spots.d.ts +0 -26
- package/dist/observers/blind-spots.js +2 -61
- package/dist/observers/console.d.ts +1 -2
- package/dist/observers/console.js +41 -30
- package/dist/observers/context-open.d.ts +1 -11
- package/dist/observers/context-open.js +10 -14
- package/dist/observers/departure.d.ts +3 -0
- package/dist/observers/departure.js +52 -0
- package/dist/observers/dialogs.d.ts +2 -0
- package/dist/observers/dialogs.js +39 -0
- package/dist/observers/dom.js +0 -44
- package/dist/observers/download.d.ts +1 -2
- package/dist/observers/download.js +38 -55
- package/dist/observers/focus.d.ts +0 -6
- package/dist/observers/focus.js +0 -7
- package/dist/observers/frames.d.ts +0 -9
- package/dist/observers/frames.js +1 -28
- package/dist/observers/health.d.ts +0 -5
- package/dist/observers/health.js +1 -33
- package/dist/observers/install-all.d.ts +2 -0
- package/dist/observers/install-all.js +26 -27
- package/dist/observers/ipc.d.ts +0 -25
- package/dist/observers/ipc.js +0 -65
- package/dist/observers/navigation.d.ts +2 -0
- package/dist/observers/navigation.js +33 -0
- package/dist/observers/network-body.d.ts +5 -28
- package/dist/observers/network-body.js +11 -108
- package/dist/observers/network-redact.d.ts +1 -20
- package/dist/observers/network-redact.js +1 -80
- package/dist/observers/network-stream.d.ts +0 -21
- package/dist/observers/network-stream.js +1 -44
- package/dist/observers/network.d.ts +2 -26
- package/dist/observers/network.js +177 -294
- package/dist/observers/perf.d.ts +0 -10
- package/dist/observers/perf.js +0 -16
- package/dist/observers/route.d.ts +1 -2
- package/dist/observers/route.js +13 -19
- package/dist/observers/scroll.d.ts +0 -1
- package/dist/observers/scroll.js +0 -9
- package/dist/observers/sdk-failure.d.ts +1 -9
- package/dist/observers/sdk-failure.js +1 -30
- package/dist/observers/state.d.ts +1 -11
- package/dist/observers/state.js +13 -41
- package/dist/observers/storage.d.ts +0 -14
- package/dist/observers/storage.js +10 -34
- package/dist/observers/types.d.ts +0 -15
- package/dist/observers/types.js +0 -14
- package/dist/patching/capture-method.d.ts +0 -24
- package/dist/patching/capture-method.js +0 -26
- package/dist/presenter/overlay.d.ts +0 -1
- package/dist/presenter/overlay.js +0 -1
- package/dist/presenter/presenter-brand.d.ts +0 -19
- package/dist/presenter/presenter-brand.js +0 -21
- package/dist/presenter/presenter-config.d.ts +1 -105
- package/dist/presenter/presenter-config.js +2 -91
- package/dist/presenter/presenter-controls.d.ts +0 -29
- package/dist/presenter/presenter-controls.js +3 -131
- package/dist/presenter/presenter-dock-layout.d.ts +0 -3
- package/dist/presenter/presenter-dock-layout.js +0 -4
- package/dist/presenter/presenter-drag.d.ts +2 -24
- package/dist/presenter/presenter-drag.js +0 -26
- package/dist/presenter/presenter-effects.d.ts +0 -9
- package/dist/presenter/presenter-effects.js +0 -9
- package/dist/presenter/presenter-glow.d.ts +0 -21
- package/dist/presenter/presenter-glow.js +1 -27
- package/dist/presenter/presenter-heroicons-data.d.ts +0 -8
- package/dist/presenter/presenter-heroicons-data.js +0 -8
- package/dist/presenter/presenter-hud-chrome.d.ts +0 -10
- package/dist/presenter/presenter-hud-chrome.js +0 -10
- package/dist/presenter/presenter-icons.d.ts +0 -6
- package/dist/presenter/presenter-icons.js +0 -6
- package/dist/presenter/presenter-log.d.ts +0 -38
- package/dist/presenter/presenter-log.js +1 -45
- package/dist/presenter/presenter-report-copy.d.ts +5 -66
- package/dist/presenter/presenter-report-copy.js +5 -66
- package/dist/presenter/presenter-report-styles.d.ts +0 -6
- package/dist/presenter/presenter-report-styles.js +6 -6
- package/dist/presenter/presenter-report.d.ts +4 -7
- package/dist/presenter/presenter-report.js +18 -76
- package/dist/presenter/presenter-run-state.d.ts +0 -7
- package/dist/presenter/presenter-run-state.js +0 -5
- package/dist/presenter/presenter-settings-styles.d.ts +0 -1
- package/dist/presenter/presenter-settings-styles.js +0 -1
- package/dist/presenter/presenter-settings.d.ts +1 -23
- package/dist/presenter/presenter-settings.js +1 -31
- package/dist/presenter/presenter-shell-styles.js +2 -5
- package/dist/presenter/presenter-shell.d.ts +2 -18
- package/dist/presenter/presenter-shell.js +1 -73
- package/dist/presenter/presenter-styles.d.ts +0 -9
- package/dist/presenter/presenter-styles.js +0 -9
- package/dist/presenter/presenter-tally.d.ts +0 -5
- package/dist/presenter/presenter-tally.js +0 -4
- package/dist/presenter/presenter-test-helpers.d.ts +0 -2
- package/dist/presenter/presenter-test-helpers.js +0 -2
- package/dist/presenter/presenter-verbs.js +0 -7
- package/dist/presenter/presenter-workspace.d.ts +0 -4
- package/dist/presenter/presenter-workspace.js +1 -8
- package/dist/presenter/presenter.d.ts +0 -60
- package/dist/presenter/presenter.js +4 -118
- package/dist/recorder/recorder-styles.d.ts +0 -6
- package/dist/recorder/recorder-styles.js +0 -6
- package/dist/recorder/recorder.d.ts +0 -34
- package/dist/recorder/recorder.js +0 -49
- package/dist/registry/adapters.d.ts +0 -10
- package/dist/registry/adapters.js +0 -10
- package/dist/registry/auto-testids.d.ts +0 -19
- package/dist/registry/auto-testids.js +1 -21
- package/dist/registry/capabilities.d.ts +0 -31
- package/dist/registry/capabilities.js +1 -46
- package/dist/registry/commit-and-signal.d.ts +0 -6
- package/dist/registry/commit-and-signal.js +1 -7
- package/dist/registry/domains.d.ts +0 -8
- package/dist/registry/domains.js +0 -7
- package/dist/registry/emitter.d.ts +0 -14
- package/dist/registry/emitter.js +0 -10
- package/dist/registry/store-adapters.d.ts +0 -186
- package/dist/registry/store-adapters.js +0 -163
- package/dist/registry/stores.d.ts +0 -33
- package/dist/registry/stores.js +0 -59
- package/dist/reticle-presenter-helpers.d.ts +0 -13
- package/dist/reticle-presenter-helpers.js +0 -14
- package/dist/reticle.d.ts +1 -52
- package/dist/reticle.js +20 -145
- package/dist/review/annotator-styles.js +0 -7
- package/dist/review/annotator.d.ts +2 -13
- package/dist/review/annotator.js +0 -40
- package/dist/review/mark-anchor.d.ts +0 -11
- package/dist/review/mark-anchor.js +0 -8
- package/dist/security/serialization.d.ts +0 -20
- package/dist/security/serialization.js +1 -66
- package/dist/session-continuity.d.ts +0 -16
- package/dist/session-continuity.js +1 -20
- package/dist/timers/clock.d.ts +1 -14
- package/dist/timers/clock.js +59 -60
- package/dist/timers/native-console.js +0 -4
- package/dist/timers/native-timers.d.ts +0 -25
- package/dist/timers/native-timers.js +3 -33
- package/dist/transport/transport.d.ts +0 -51
- package/dist/transport/transport.js +3 -129
- package/dist/transport/unreachable-message.d.ts +0 -17
- package/dist/transport/unreachable-message.js +0 -17
- package/dist/util/captured-method.d.ts +0 -35
- package/dist/util/captured-method.js +0 -35
- package/package.json +2 -2
|
@@ -2,7 +2,6 @@ import { CHURN_TYPES, CommandMessageSchema, EventType, MessageKind, SESSION_LIFE
|
|
|
2
2
|
import { nativeSetTimeout, nativeNow } from '../timers/native-timers.js';
|
|
3
3
|
import { nativeWarn } from '../timers/native-console.js';
|
|
4
4
|
import { safeStringify } from '../security/serialization.js';
|
|
5
|
-
/** Default visibility source: fire `handler` whenever the document returns to the foreground. */
|
|
6
5
|
function subscribeDocumentVisible(handler) {
|
|
7
6
|
if ('undefined' === typeof document)
|
|
8
7
|
return () => undefined;
|
|
@@ -13,69 +12,29 @@ function subscribeDocumentVisible(handler) {
|
|
|
13
12
|
document.addEventListener('visibilitychange', listener);
|
|
14
13
|
return () => document.removeEventListener('visibilitychange', listener);
|
|
15
14
|
}
|
|
16
|
-
/** The first retry after a drop. Kept short: the common outage is a daemon restarting. */
|
|
17
15
|
const RECONNECT_DELAY_MS = 1000;
|
|
18
|
-
/**
|
|
19
|
-
* The ceiling on reconnect backoff.
|
|
20
|
-
*
|
|
21
|
-
* A flat 1s retry with no growth and no cap meant a tab left open against a stopped daemon made
|
|
22
|
-
* ~600 failed WebSocket opens in ten minutes — CPU, and a devtools console full of
|
|
23
|
-
* ERR_CONNECTION_REFUSED. Backing off is only safe BECAUSE this transport also reconnects on
|
|
24
|
-
* focus/visibility, so a human returning to the tab retries immediately rather than waiting out
|
|
25
|
-
* the delay; the backoff resets on that path for the same reason.
|
|
26
|
-
*/
|
|
27
16
|
export const RECONNECT_MAX_DELAY_MS = 30_000;
|
|
28
|
-
/**
|
|
29
|
-
* The next reconnect delay: double, then clamp. Pure, and exported so the POLICY is testable —
|
|
30
|
-
* the scheduling itself runs on `nativeSetTimeout`, which is bound to the real timer at module load
|
|
31
|
-
* (deliberately, so a frozen `reticle_clock` cannot deadlock the SDK) and therefore cannot be driven
|
|
32
|
-
* by a fake clock.
|
|
33
|
-
*/
|
|
34
17
|
export function nextReconnectDelay(previous) {
|
|
35
18
|
return Math.min(previous * 2, RECONNECT_MAX_DELAY_MS);
|
|
36
19
|
}
|
|
37
|
-
/** Offline queue cap. Exported so tests overflow the real bound instead of a mirrored copy of it. */
|
|
38
20
|
export const MAX_QUEUE = 500;
|
|
39
|
-
/**
|
|
40
|
-
* Standard WebSocket close code 1008 ("policy violation") — the bridge sends it for TERMINAL refusals:
|
|
41
|
-
* a protocol-version mismatch (the browser package is older than the bridge and must be upgraded),
|
|
42
|
-
* auth failure, or a duplicate/invalid handshake. Reconnecting cannot fix any of those, so a blind
|
|
43
|
-
* retry loop just hammers the bridge every second forever and buries the actionable reason. 1013
|
|
44
|
-
* ("try again later" — rate/session limits) and transport-level closes stay retryable.
|
|
45
|
-
*/
|
|
46
21
|
const WS_POLICY_VIOLATION = 1008;
|
|
47
|
-
/** Warn that the bridge is unreachable after this many consecutive failed INITIAL connects (~3s). */
|
|
48
22
|
const UNREACHABLE_WARN_AFTER = 3;
|
|
49
|
-
/** WebSocket client to the bridge. Reconnects across reloads; buffers events while down. */
|
|
50
23
|
export class Transport {
|
|
51
24
|
#ws;
|
|
52
25
|
#churnQueue = [];
|
|
53
26
|
#signalQueue = [];
|
|
54
27
|
#insertOrder = 0;
|
|
55
28
|
#closed = false;
|
|
56
|
-
/** When the current continuous outage began (nativeNow), or undefined while connected. */
|
|
57
29
|
#disconnectedSince;
|
|
58
|
-
/** Current reconnect delay; grows on each failure, resets on a successful open or on focus. */
|
|
59
30
|
#reconnectDelay = RECONNECT_DELAY_MS;
|
|
60
|
-
/** Whether onConnectionLost has already fired for the current outage (fire-once). */
|
|
61
31
|
#lost = false;
|
|
62
|
-
/** True once the socket has opened at least once — gates the "unreachable" first-connect warning. */
|
|
63
32
|
#everConnected = false;
|
|
64
|
-
/** Consecutive failed INITIAL connects (before any success). */
|
|
65
33
|
#initialFailures = 0;
|
|
66
|
-
/** Whether the unreachable warning has fired (fire-once). */
|
|
67
34
|
#warnedUnreachable = false;
|
|
68
|
-
/** Events the offline queue has evicted since the last gap marker reached the bridge. */
|
|
69
35
|
#dropped = 0;
|
|
70
|
-
/**
|
|
71
|
-
* Envelope coordinates of the most recent evicted event — the pending marker's tombstone. Set while
|
|
72
|
-
* a gap is undeclared, cleared once the bridge has been told about it.
|
|
73
|
-
*/
|
|
74
36
|
#gap;
|
|
75
|
-
/** Session id, cached from the first hello(). Stable for a Transport's life — no need to rebuild the
|
|
76
|
-
* whole HelloMessage (url/title/adapters) on every inbound command just to read it. */
|
|
77
37
|
#sessionId;
|
|
78
|
-
/** Teardown for the visibility subscription (foreground-triggered reconnect), while connected. */
|
|
79
38
|
#unsubscribeVisible;
|
|
80
39
|
#deps;
|
|
81
40
|
#now;
|
|
@@ -90,18 +49,9 @@ export class Transport {
|
|
|
90
49
|
this.#unsubscribeVisible ??= (this.#deps.onVisible ?? subscribeDocumentVisible)(() => this.#onVisible());
|
|
91
50
|
this.#open();
|
|
92
51
|
}
|
|
93
|
-
/**
|
|
94
|
-
* The tab returned to the foreground. If we're disconnected (and not deliberately closed), reconnect
|
|
95
|
-
* NOW — a hidden tab's throttled timer may be minutes from firing. A no-op when a socket already
|
|
96
|
-
* exists (connected or mid-connect), so we never open a duplicate racing the scheduled retry.
|
|
97
|
-
*/
|
|
98
52
|
#onVisible() {
|
|
99
53
|
if (this.#closed)
|
|
100
54
|
return;
|
|
101
|
-
// A human just came back to the tab, which is the strongest signal the outage may be over — and
|
|
102
|
-
// it is what makes a 30s ceiling safe. Reset the backoff BEFORE the mid-connect early return: if
|
|
103
|
-
// a socket is already in flight and then fails, that retry should be prompt too, rather than
|
|
104
|
-
// inheriting a delay earned while nobody was watching.
|
|
105
55
|
this.#reconnectDelay = RECONNECT_DELAY_MS;
|
|
106
56
|
if (this.#ws !== undefined)
|
|
107
57
|
return;
|
|
@@ -113,12 +63,6 @@ export class Transport {
|
|
|
113
63
|
ws = new WebSocket(this.#deps.url);
|
|
114
64
|
}
|
|
115
65
|
catch {
|
|
116
|
-
// `new WebSocket()` throws SYNCHRONOUSLY on a mixed-content SecurityError — a ws:// URL from an
|
|
117
|
-
// https page, which Safari does not exempt for localhost the way Chrome does. Unguarded, that
|
|
118
|
-
// exception propagates out of connect() into the host app's bootstrap (the SDK's cardinal sin:
|
|
119
|
-
// it must never break the app it observes) AND leaves connect() half-run, so #connected never
|
|
120
|
-
// flips true and disconnect() can't remove the monkeypatches. Treat it like any failed open:
|
|
121
|
-
// note the outage and schedule the normal retry.
|
|
122
66
|
this.#noteOutage();
|
|
123
67
|
this.#noteInitialFailure();
|
|
124
68
|
this.#scheduleReopen();
|
|
@@ -126,18 +70,13 @@ export class Transport {
|
|
|
126
70
|
}
|
|
127
71
|
this.#ws = ws;
|
|
128
72
|
ws.onopen = () => {
|
|
129
|
-
this.#disconnectedSince = undefined;
|
|
73
|
+
this.#disconnectedSince = undefined;
|
|
130
74
|
this.#lost = false;
|
|
131
|
-
this.#everConnected = true;
|
|
132
|
-
this.#reconnectDelay = RECONNECT_DELAY_MS;
|
|
133
|
-
// HELLO is SDK-owned schema data. Preserve its pairing token; the generic sanitizer
|
|
134
|
-
// intentionally redacts fields named "token" from app-controlled payloads.
|
|
75
|
+
this.#everConnected = true;
|
|
76
|
+
this.#reconnectDelay = RECONNECT_DELAY_MS;
|
|
135
77
|
const greeting = this.#deps.hello();
|
|
136
78
|
this.#sessionId ??= greeting.sessionId;
|
|
137
79
|
ws.send(JSON.stringify(greeting));
|
|
138
|
-
// Declare any gap BEFORE replaying the backlog, stamped with the OLDEST event the queue
|
|
139
|
-
// dropped — see #sendRaw. The two queues are drained back into one stream in insertion order,
|
|
140
|
-
// so what the bridge receives is the order the page produced, minus the holes.
|
|
141
80
|
this.#sendGapMarker(ws, greeting.sessionId);
|
|
142
81
|
const replay = [...this.#churnQueue, ...this.#signalQueue].sort((a, b) => a.order - b.order);
|
|
143
82
|
for (const msg of replay)
|
|
@@ -152,10 +91,6 @@ export class Transport {
|
|
|
152
91
|
};
|
|
153
92
|
ws.onclose = (event) => {
|
|
154
93
|
this.#ws = undefined;
|
|
155
|
-
// A 1008 is the bridge saying "don't come back as you are" — a version mismatch, a rejected
|
|
156
|
-
// handshake. Retrying every second cannot change the outcome; stop and surface the reason (the
|
|
157
|
-
// bridge puts an actionable message there, e.g. "upgrade @reticlehq/browser") instead of a
|
|
158
|
-
// silent reconnect storm. End the session cleanly so the HUD doesn't hang on "running".
|
|
159
94
|
if (event.code === WS_POLICY_VIOLATION) {
|
|
160
95
|
this.#closed = true;
|
|
161
96
|
const reason = event.reason.length > 0 ? event.reason : 'policy violation';
|
|
@@ -171,16 +106,6 @@ export class Transport {
|
|
|
171
106
|
ws.close();
|
|
172
107
|
};
|
|
173
108
|
}
|
|
174
|
-
/**
|
|
175
|
-
* A scheduled reconnect — skipped if the SDK was torn down, or if a foreground-triggered reconnect
|
|
176
|
-
* already opened a socket (guard against the throttled timer racing #onVisible into a duplicate).
|
|
177
|
-
*/
|
|
178
|
-
/**
|
|
179
|
-
* Schedule the next retry and grow the delay. Nothing is scheduled once the SDK is torn down.
|
|
180
|
-
*
|
|
181
|
-
* The delay grows AFTER scheduling, so the first retry of any outage is prompt (a daemon
|
|
182
|
-
* restarting must not cost 30 seconds) and only a persistent outage backs off.
|
|
183
|
-
*/
|
|
184
109
|
#scheduleReopen() {
|
|
185
110
|
if (this.#closed)
|
|
186
111
|
return;
|
|
@@ -194,11 +119,6 @@ export class Transport {
|
|
|
194
119
|
return;
|
|
195
120
|
this.#open();
|
|
196
121
|
}
|
|
197
|
-
/**
|
|
198
|
-
* The first connection has never opened: count failures and, once they cross the threshold, fire
|
|
199
|
-
* onUnreachable ONCE so the app surfaces an actionable hint (wrong port / container network) rather
|
|
200
|
-
* than retrying silently forever. Suppressed entirely once any connection has succeeded.
|
|
201
|
-
*/
|
|
202
122
|
#noteInitialFailure() {
|
|
203
123
|
if (this.#everConnected || this.#warnedUnreachable)
|
|
204
124
|
return;
|
|
@@ -208,11 +128,6 @@ export class Transport {
|
|
|
208
128
|
this.#deps.onUnreachable?.({ url: this.#deps.url, attempts: this.#initialFailures });
|
|
209
129
|
}
|
|
210
130
|
}
|
|
211
|
-
/**
|
|
212
|
-
* Track how long the bridge has been unreachable. Once the outage exceeds BRIDGE_LOST_MS, fire
|
|
213
|
-
* onConnectionLost exactly once so the SDK can end the session (the server/agent is gone and can
|
|
214
|
-
* no longer push an end itself).
|
|
215
|
-
*/
|
|
216
131
|
#noteOutage() {
|
|
217
132
|
const at = this.#now();
|
|
218
133
|
this.#disconnectedSince ??= at;
|
|
@@ -231,10 +146,6 @@ export class Transport {
|
|
|
231
146
|
}
|
|
232
147
|
const result = CommandMessageSchema.safeParse(parsed);
|
|
233
148
|
if (!result.success) {
|
|
234
|
-
// Also once silent. A command the page cannot parse is a version skew between the SDK and the
|
|
235
|
-
// daemon, and dropping it produces the same unfalsifiable timeout every other hang produces — the one
|
|
236
|
-
// failure mode where the cause is knowable and was being thrown away. Reply if we can recover
|
|
237
|
-
// an id; a message without one is not a command we can answer at all.
|
|
238
149
|
const id = parsed?.id;
|
|
239
150
|
if ('string' === typeof id && id.length > 0) {
|
|
240
151
|
this.#sendRaw(safeStringify({
|
|
@@ -248,13 +159,6 @@ export class Transport {
|
|
|
248
159
|
return;
|
|
249
160
|
}
|
|
250
161
|
const command = result.data;
|
|
251
|
-
// #sessionId is set in onopen, which always precedes any inbound command on the same socket.
|
|
252
|
-
//
|
|
253
|
-
// A mismatch used to `return` — silently. The command then had no reply of any kind, so the agent
|
|
254
|
-
// waited out its full timeout and got "command timed out", which reads as a hung or suspended
|
|
255
|
-
// page. Measured on a Tauri shell: the session was connected and streaming events the whole time
|
|
256
|
-
// while every command vanished here, and three rounds of debugging went looking at the webview.
|
|
257
|
-
// A dropped command is a real fault, so it now says so and names both ids.
|
|
258
162
|
if (command.sessionId !== undefined && command.sessionId !== this.#sessionId) {
|
|
259
163
|
this.#sendRaw(safeStringify({
|
|
260
164
|
kind: MessageKind.COMMAND_RESULT,
|
|
@@ -291,15 +195,6 @@ export class Transport {
|
|
|
291
195
|
type: event.type,
|
|
292
196
|
});
|
|
293
197
|
}
|
|
294
|
-
/**
|
|
295
|
-
* Re-send the HELLO on a live socket.
|
|
296
|
-
*
|
|
297
|
-
* The hello carries `hasCapabilities`, and it goes out at connect() — before the app has had a
|
|
298
|
-
* chance to call `registerCapabilities`, which by design happens AFTER connect so `registerStore`
|
|
299
|
-
* has a live SDK to subscribe through. So an app that declared its whole testable surface still
|
|
300
|
-
* appeared to the agent as having none, and nothing ever corrected it. Cheap and idempotent: the
|
|
301
|
-
* bridge treats a hello as the session's current identity, not as a new session.
|
|
302
|
-
*/
|
|
303
198
|
reannounce() {
|
|
304
199
|
if (this.#ws === undefined || this.#ws.readyState !== WebSocket.OPEN)
|
|
305
200
|
return;
|
|
@@ -311,17 +206,9 @@ export class Transport {
|
|
|
311
206
|
return;
|
|
312
207
|
}
|
|
313
208
|
if (this.#churnQueue.length + this.#signalQueue.length >= MAX_QUEUE) {
|
|
314
|
-
// Prefer sacrificing churn (high-volume, low-signal) over signal events. Falls back to signal
|
|
315
|
-
// FIFO only when the churn queue is empty — the buffer is bounded either way.
|
|
316
209
|
const evicted = this.#churnQueue.length > 0 ? this.#churnQueue.shift() : this.#signalQueue.shift();
|
|
317
210
|
if (evicted?.event !== undefined) {
|
|
318
211
|
this.#dropped += 1;
|
|
319
|
-
// Keep the EARLIEST drop, not the latest. Under plain FIFO the two were the same thing, so
|
|
320
|
-
// "latest evicted" was a precise boundary: everything before it was gone. Churn-first
|
|
321
|
-
// eviction breaks that — a churn event newer than a surviving signal event can be the one
|
|
322
|
-
// sacrificed, and stamping the marker with it would claim a hole that swallows events the
|
|
323
|
-
// agent is about to receive. The oldest drop is the honest floor: the hole starts no later
|
|
324
|
-
// than here. A truncation marker may over-state its reach; it must never under-state it.
|
|
325
212
|
const previous = this.#gap;
|
|
326
213
|
if (previous === undefined || evicted.event.t < previous.t)
|
|
327
214
|
this.#gap = evicted.event;
|
|
@@ -336,19 +223,6 @@ export class Transport {
|
|
|
336
223
|
this.#signalQueue.push({ text, event, order });
|
|
337
224
|
}
|
|
338
225
|
}
|
|
339
|
-
/**
|
|
340
|
-
* Tell the bridge the offline queue swallowed events, then consider the gap declared.
|
|
341
|
-
*
|
|
342
|
-
* Written straight to the open socket instead of going back through `sendEvent`, for two reasons
|
|
343
|
-
* that both bite at exactly the moment the marker matters. The queue is full when a drop happens,
|
|
344
|
-
* so a queued marker would evict another real event — every declared gap widening the gap it
|
|
345
|
-
* declares. And it would re-enter `#sendRaw` from inside the eviction branch, between the `shift`
|
|
346
|
-
* and the `push`, growing the queue by one on every later send. Writing here makes both impossible.
|
|
347
|
-
*
|
|
348
|
-
* The marker inherits the seq/t of the last event it displaced. That event never reaches the
|
|
349
|
-
* bridge, so the coordinates are free, and the server orders a session by `seq` — a marker minted
|
|
350
|
-
* at reconnect would carry the highest seq and blame a segment that never had a hole in it.
|
|
351
|
-
*/
|
|
352
226
|
#sendGapMarker(ws, sessionId) {
|
|
353
227
|
const gap = this.#gap;
|
|
354
228
|
if (gap === undefined)
|
|
@@ -1,19 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What the page says when its websocket never opened.
|
|
3
|
-
*
|
|
4
|
-
* Pulled out of the connect path so it is a pure string function with a test on its CLAIMS rather
|
|
5
|
-
* than a template inlined in a warn call. The rule it exists to hold: report what this page observed
|
|
6
|
-
* and what it could not determine, never a cause. See unreachable-message.test.ts.
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
|
-
* The page tried, failed, and cannot see why. Names the address and the attempt count, refuses to
|
|
10
|
-
* diagnose the daemon, and lists the checks in the order that actually resolves the ambiguity.
|
|
11
|
-
*/
|
|
12
1
|
export declare function unreachableMessage(url: string, attempts: number): string;
|
|
13
|
-
/**
|
|
14
|
-
* The same fact, sized for the HUD's one-line status row.
|
|
15
|
-
*
|
|
16
|
-
* The console warning explains; this one only has room to name the URL, which is the answer in
|
|
17
|
-
* most cases anyway — a bridge on a port the page is not dialling.
|
|
18
|
-
*/
|
|
19
2
|
export declare function unreachableStripText(url: string, attempts: number): string;
|
|
@@ -1,15 +1,4 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* What the page says when its websocket never opened.
|
|
3
|
-
*
|
|
4
|
-
* Pulled out of the connect path so it is a pure string function with a test on its CLAIMS rather
|
|
5
|
-
* than a template inlined in a warn call. The rule it exists to hold: report what this page observed
|
|
6
|
-
* and what it could not determine, never a cause. See unreachable-message.test.ts.
|
|
7
|
-
*/
|
|
8
1
|
import { UNREACHABLE_NOTICE_PREFIX } from '@reticlehq/core';
|
|
9
|
-
/**
|
|
10
|
-
* The page tried, failed, and cannot see why. Names the address and the attempt count, refuses to
|
|
11
|
-
* diagnose the daemon, and lists the checks in the order that actually resolves the ambiguity.
|
|
12
|
-
*/
|
|
13
2
|
export function unreachableMessage(url, attempts) {
|
|
14
3
|
return (`${UNREACHABLE_NOTICE_PREFIX}${url}. ${String(attempts)} attempts, all ` +
|
|
15
4
|
`failed. That is everything the page can see: from inside the browser it cannot tell a daemon ` +
|
|
@@ -19,12 +8,6 @@ export function unreachableMessage(url, attempts) {
|
|
|
19
8
|
`different host, so set the URL explicitly (Vite: VITE_RETICLE_WS_URL, or ` +
|
|
20
9
|
`reticle.connect({ url })). An https page cannot open a ws:// socket at all. Still retrying…`);
|
|
21
10
|
}
|
|
22
|
-
/**
|
|
23
|
-
* The same fact, sized for the HUD's one-line status row.
|
|
24
|
-
*
|
|
25
|
-
* The console warning explains; this one only has room to name the URL, which is the answer in
|
|
26
|
-
* most cases anyway — a bridge on a port the page is not dialling.
|
|
27
|
-
*/
|
|
28
11
|
export function unreachableStripText(url, attempts) {
|
|
29
12
|
return `no bridge at ${url} — ${String(attempts)} attempt${1 === attempts ? '' : 's'}`;
|
|
30
13
|
}
|
|
@@ -1,37 +1,2 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Read a host object's method as a STORED VALUE, for code that has to put it back.
|
|
3
|
-
*
|
|
4
|
-
* Every observer here patches a global and must be able to undo it: `console.error`, `window.fetch`,
|
|
5
|
-
* `window.setTimeout`, `Date.now`. Undoing means holding the true original — the same function
|
|
6
|
-
* object, so teardown restores exactly what was there and a later install can recognise it by
|
|
7
|
-
* identity — and calling through a separately bound copy, because a host method invoked with the
|
|
8
|
-
* wrong receiver throws (`fetch` gives "Illegal invocation").
|
|
9
|
-
*
|
|
10
|
-
* Written as `const original = window.fetch`, that is a bare unbound method reference, and
|
|
11
|
-
* `@typescript-eslint/unbound-method` is right to flag the SHAPE even though this use is deliberate:
|
|
12
|
-
* the rule cannot see that the next line binds it, nor that the raw value only ever goes back into
|
|
13
|
-
* the slot it came from.
|
|
14
|
-
*
|
|
15
|
-
* The two obvious ways out are both wrong here. An `eslint-disable` is against this repo's standing
|
|
16
|
-
* rule and hides the shape from every future reader. Annotating `this: void` asserts the function
|
|
17
|
-
* does not use its receiver — which for a console method or `fetch` is simply false, and is the very
|
|
18
|
-
* reason the binding exists.
|
|
19
|
-
*
|
|
20
|
-
* So the intent is expressed instead of suppressed. A property descriptor's `value` is a stored
|
|
21
|
-
* value, not a method being detached from its object, which is exactly the distinction the rule is
|
|
22
|
-
* drawing and exactly what this code wants. It is also strictly more accurate for the restore case:
|
|
23
|
-
* it reads what the slot HOLDS rather than what calling the property would do, so an accessor
|
|
24
|
-
* defined on the object cannot be silently flattened into its result.
|
|
25
|
-
*
|
|
26
|
-
* Returns undefined when the property is absent or inherited rather than own — a caller that needs a
|
|
27
|
-
* fallback to the prototype chain should say so at its own call site rather than have it hidden here.
|
|
28
|
-
*/
|
|
29
1
|
export declare function capturedMethod<T>(target: object, key: PropertyKey): T | undefined;
|
|
30
|
-
/**
|
|
31
|
-
* The same, for a property this code knows is present — the globals every observer patches.
|
|
32
|
-
*
|
|
33
|
-
* Throws rather than returning a broken teardown. A missing `window.fetch` is not a condition any
|
|
34
|
-
* caller here can recover from, and silently installing a patch that cannot be undone is worse than
|
|
35
|
-
* refusing: the page keeps the wrapper for the rest of its life with nothing able to remove it.
|
|
36
|
-
*/
|
|
37
2
|
export declare function requireCapturedMethod<T>(target: object, key: PropertyKey): T;
|
|
@@ -1,41 +1,6 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Read a host object's method as a STORED VALUE, for code that has to put it back.
|
|
3
|
-
*
|
|
4
|
-
* Every observer here patches a global and must be able to undo it: `console.error`, `window.fetch`,
|
|
5
|
-
* `window.setTimeout`, `Date.now`. Undoing means holding the true original — the same function
|
|
6
|
-
* object, so teardown restores exactly what was there and a later install can recognise it by
|
|
7
|
-
* identity — and calling through a separately bound copy, because a host method invoked with the
|
|
8
|
-
* wrong receiver throws (`fetch` gives "Illegal invocation").
|
|
9
|
-
*
|
|
10
|
-
* Written as `const original = window.fetch`, that is a bare unbound method reference, and
|
|
11
|
-
* `@typescript-eslint/unbound-method` is right to flag the SHAPE even though this use is deliberate:
|
|
12
|
-
* the rule cannot see that the next line binds it, nor that the raw value only ever goes back into
|
|
13
|
-
* the slot it came from.
|
|
14
|
-
*
|
|
15
|
-
* The two obvious ways out are both wrong here. An `eslint-disable` is against this repo's standing
|
|
16
|
-
* rule and hides the shape from every future reader. Annotating `this: void` asserts the function
|
|
17
|
-
* does not use its receiver — which for a console method or `fetch` is simply false, and is the very
|
|
18
|
-
* reason the binding exists.
|
|
19
|
-
*
|
|
20
|
-
* So the intent is expressed instead of suppressed. A property descriptor's `value` is a stored
|
|
21
|
-
* value, not a method being detached from its object, which is exactly the distinction the rule is
|
|
22
|
-
* drawing and exactly what this code wants. It is also strictly more accurate for the restore case:
|
|
23
|
-
* it reads what the slot HOLDS rather than what calling the property would do, so an accessor
|
|
24
|
-
* defined on the object cannot be silently flattened into its result.
|
|
25
|
-
*
|
|
26
|
-
* Returns undefined when the property is absent or inherited rather than own — a caller that needs a
|
|
27
|
-
* fallback to the prototype chain should say so at its own call site rather than have it hidden here.
|
|
28
|
-
*/
|
|
29
1
|
export function capturedMethod(target, key) {
|
|
30
2
|
return Object.getOwnPropertyDescriptor(target, key)?.value;
|
|
31
3
|
}
|
|
32
|
-
/**
|
|
33
|
-
* The same, for a property this code knows is present — the globals every observer patches.
|
|
34
|
-
*
|
|
35
|
-
* Throws rather than returning a broken teardown. A missing `window.fetch` is not a condition any
|
|
36
|
-
* caller here can recover from, and silently installing a patch that cannot be undone is worse than
|
|
37
|
-
* refusing: the page keeps the wrapper for the rest of its life with nothing able to remove it.
|
|
38
|
-
*/
|
|
39
4
|
export function requireCapturedMethod(target, key) {
|
|
40
5
|
const value = capturedMethod(target, key);
|
|
41
6
|
if (value === undefined) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reticlehq/browser",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "Reticle browser SDK — installs observers, builds semantic snapshots, executes actions, talks to the bridge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"./package.json": "./package.json"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@reticlehq/core": "2.
|
|
16
|
+
"@reticlehq/core": "2.14.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@iconify-json/heroicons-outline": "^1.2.1",
|