@reticlehq/browser 3.7.0 → 3.8.1
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/action-report.d.ts +10 -0
- package/dist/actions/action-report.js +59 -14
- package/dist/actions/actions-dom.d.ts +4 -0
- package/dist/actions/actions-dom.js +20 -4
- package/dist/actions/actions.d.ts +1 -0
- package/dist/actions/actions.js +94 -46
- package/dist/actions/appeared-text.js +2 -2
- package/dist/actions/click-target.d.ts +1 -0
- package/dist/actions/click-target.js +14 -0
- package/dist/actions/danger-context.d.ts +2 -1
- package/dist/actions/danger-context.js +43 -17
- package/dist/actions/scroll.d.ts +2 -0
- package/dist/actions/scroll.js +10 -2
- package/dist/activate.d.ts +14 -0
- package/dist/activate.js +43 -0
- package/dist/busy.d.ts +3 -0
- package/dist/busy.js +42 -0
- package/dist/commands/commands.js +45 -17
- package/dist/connect-options.d.ts +12 -1
- package/dist/connect-options.js +18 -1
- package/dist/dom/a11y.d.ts +0 -2
- package/dist/dom/a11y.js +103 -363
- package/dist/dom/chart.d.ts +3 -2
- package/dist/dom/chart.js +3 -2
- package/dist/dom/dom-ignore.d.ts +6 -0
- package/dist/dom/dom-ignore.js +60 -5
- package/dist/dom/layout-audit.d.ts +15 -0
- package/dist/dom/layout-audit.js +208 -0
- package/dist/dom/paint-context.d.ts +15 -0
- package/dist/dom/paint-context.js +78 -0
- package/dist/dom/query-finders.d.ts +6 -0
- package/dist/dom/query-finders.js +216 -0
- package/dist/dom/query.d.ts +1 -1
- package/dist/dom/query.js +154 -227
- package/dist/dom/shadow-registry.d.ts +1 -0
- package/dist/dom/shadow-registry.js +27 -0
- package/dist/dom/snapshot.d.ts +5 -0
- package/dist/dom/snapshot.js +87 -35
- package/dist/dom/stable-selector.d.ts +1 -0
- package/dist/dom/stable-selector.js +19 -0
- package/dist/dom/text-match.d.ts +12 -0
- package/dist/dom/text-match.js +115 -0
- package/dist/dom/theme.d.ts +1 -0
- package/dist/dom/theme.js +21 -9
- package/dist/dom/virtualized.js +5 -1
- package/dist/dom/visibility.d.ts +7 -0
- package/dist/dom/visibility.js +322 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +7 -1
- package/dist/observers/blind-spots.js +3 -2
- package/dist/observers/canvas.d.ts +18 -0
- package/dist/observers/canvas.js +76 -0
- package/dist/observers/dom.js +68 -19
- package/dist/observers/field.d.ts +11 -0
- package/dist/observers/field.js +117 -13
- package/dist/observers/install-all.js +4 -0
- package/dist/observers/media.d.ts +2 -0
- package/dist/observers/media.js +32 -0
- package/dist/observers/net-detail/network-body.d.ts +2 -0
- package/dist/observers/net-detail/network-body.js +36 -9
- package/dist/observers/net-detail/network-event-stream.d.ts +5 -0
- package/dist/observers/net-detail/network-event-stream.js +55 -0
- package/dist/observers/net-detail/network-frames.d.ts +2 -0
- package/dist/observers/net-detail/network-frames.js +39 -0
- package/dist/observers/net-detail/network-response-body.d.ts +3 -0
- package/dist/observers/net-detail/network-response-body.js +97 -0
- package/dist/observers/network.js +111 -101
- package/dist/observers/route.d.ts +2 -0
- package/dist/observers/route.js +33 -4
- package/dist/presenter/chrome/presenter-report-copy.d.ts +0 -5
- package/dist/presenter/chrome/presenter-report-copy.js +0 -14
- package/dist/presenter/hud-telemetry.js +1 -1
- package/dist/presenter/presenter-chat-views.d.ts +3 -4
- package/dist/presenter/presenter-chat-views.js +47 -20
- package/dist/presenter/presenter-config.d.ts +1 -0
- package/dist/presenter/presenter-config.js +1 -0
- package/dist/presenter/presenter-frame.d.ts +3 -2
- package/dist/presenter/presenter-frame.js +24 -15
- package/dist/presenter/presenter-harness-row.d.ts +4 -15
- package/dist/presenter/presenter-harness-row.js +11 -50
- package/dist/presenter/presenter-report-styles.js +0 -17
- package/dist/presenter/presenter-report.d.ts +1 -2
- package/dist/presenter/presenter-report.js +4 -54
- package/dist/presenter/presenter-run-state.js +2 -1
- package/dist/presenter/presenter-settings.d.ts +2 -3
- package/dist/presenter/presenter-settings.js +14 -17
- package/dist/presenter/presenter-shell.d.ts +6 -4
- package/dist/presenter/presenter-shell.js +37 -7
- package/dist/presenter/presenter.js +32 -11
- package/dist/registry/store-adapters.d.ts +5 -2
- package/dist/registry/store-adapters.js +40 -10
- package/dist/reticle-inject.js +700 -645
- package/dist/reticle.d.ts +4 -2
- package/dist/reticle.js +36 -6
- package/dist/security/page-location.d.ts +2 -0
- package/dist/security/page-location.js +7 -0
- package/dist/security/serialization.js +1 -1
- package/dist/storage-keys.d.ts +1 -0
- package/dist/storage-keys.js +1 -0
- package/dist/timers/native/native-timers.d.ts +1 -0
- package/dist/timers/native/native-timers.js +5 -0
- package/dist/transport/transport.d.ts +1 -0
- package/dist/transport/transport.js +18 -0
- package/package.json +6 -2
|
@@ -1,42 +1,21 @@
|
|
|
1
|
-
import { BlindSpotKind, EventType, NEXT_ACTION_FIELD, NEXT_ACTION_HEADER, REQUEST_SHAPE_FIELD, REQUEST_SHAPE_NONE,
|
|
1
|
+
import { BlindSpotKind, EventType, NEXT_ACTION_FIELD, NEXT_ACTION_HEADER, REQUEST_SHAPE_FIELD, REQUEST_SHAPE_NONE, ResponseBodyReason, StreamTransport, StreamDirection, fnv1a, } from '@reticlehq/core';
|
|
2
2
|
import { captureMethod } from '../patching/capture-method.js';
|
|
3
3
|
import { observeSafely, observeValue } from './types.js';
|
|
4
|
-
import { isCapturableType, projectBody, projectErrorBody, shouldCaptureErrorBody,
|
|
4
|
+
import { isCapturableType, projectBody, projectErrorBody, shouldCaptureErrorBody, skippedBodyReason, } from './net-detail/network-body.js';
|
|
5
|
+
import { captureResponseBody, isEventStream } from './net-detail/network-response-body.js';
|
|
5
6
|
import { redactUrl, netUrlFields } from './net-detail/network-redact.js';
|
|
6
7
|
import { watchStreamedBody } from './network-stream.js';
|
|
8
|
+
import { frameFields, isBridgeSocket } from './net-detail/network-frames.js';
|
|
7
9
|
import { requireCapturedMethod } from '../util/captured-method.js';
|
|
8
10
|
export { redactUrl, netUrlFields };
|
|
9
|
-
function binaryFrameBytes(data) {
|
|
10
|
-
if (data instanceof ArrayBuffer)
|
|
11
|
-
return data.byteLength;
|
|
12
|
-
if (typeof Blob !== 'undefined' && data instanceof Blob)
|
|
13
|
-
return data.size;
|
|
14
|
-
if (ArrayBuffer.isView(data))
|
|
15
|
-
return data.byteLength;
|
|
16
|
-
return undefined;
|
|
17
|
-
}
|
|
18
|
-
function frameFields(data, captureBodies) {
|
|
19
|
-
if (typeof data !== 'string') {
|
|
20
|
-
const bytes = binaryFrameBytes(data);
|
|
21
|
-
return bytes === undefined
|
|
22
|
-
? { frameType: typeof data }
|
|
23
|
-
: { frameType: 'binary', frameBytes: bytes };
|
|
24
|
-
}
|
|
25
|
-
const out = { frameBytes: data.length };
|
|
26
|
-
if (captureBodies) {
|
|
27
|
-
const { body, truncated } = projectBody(data, 'application/json');
|
|
28
|
-
out['frame'] = body;
|
|
29
|
-
if (truncated)
|
|
30
|
-
out['frameTruncated'] = true;
|
|
31
|
-
}
|
|
32
|
-
return out;
|
|
33
|
-
}
|
|
34
11
|
const ShapeToken = { NULL: '0', STRING: 's', NUMBER: 'n', BOOLEAN: 'b', OTHER: '?' };
|
|
35
12
|
function shapeOf(value) {
|
|
36
|
-
if (null === value)
|
|
13
|
+
if (null === value) {
|
|
37
14
|
return ShapeToken.NULL;
|
|
38
|
-
|
|
15
|
+
}
|
|
16
|
+
if (Array.isArray(value)) {
|
|
39
17
|
return `[${value.map(shapeOf).join(',')}]`;
|
|
18
|
+
}
|
|
40
19
|
if ('object' === typeof value) {
|
|
41
20
|
const fields = value;
|
|
42
21
|
return `{${Object.keys(fields)
|
|
@@ -44,12 +23,15 @@ function shapeOf(value) {
|
|
|
44
23
|
.map((key) => `${key}:${shapeOf(fields[key])}`)
|
|
45
24
|
.join(',')}}`;
|
|
46
25
|
}
|
|
47
|
-
if ('string' === typeof value)
|
|
26
|
+
if ('string' === typeof value) {
|
|
48
27
|
return ShapeToken.STRING;
|
|
49
|
-
|
|
28
|
+
}
|
|
29
|
+
if ('number' === typeof value) {
|
|
50
30
|
return ShapeToken.NUMBER;
|
|
51
|
-
|
|
31
|
+
}
|
|
32
|
+
if ('boolean' === typeof value) {
|
|
52
33
|
return ShapeToken.BOOLEAN;
|
|
34
|
+
}
|
|
53
35
|
return ShapeToken.OTHER;
|
|
54
36
|
}
|
|
55
37
|
function fingerprintBody(shape, length) {
|
|
@@ -72,8 +54,9 @@ const DELETE = 0x7f;
|
|
|
72
54
|
function hasControlCharacter(text) {
|
|
73
55
|
for (let i = 0; i < text.length; i += 1) {
|
|
74
56
|
const code = text.charCodeAt(i);
|
|
75
|
-
if (code === DELETE)
|
|
57
|
+
if (code === DELETE) {
|
|
76
58
|
return true;
|
|
59
|
+
}
|
|
77
60
|
if (code < FIRST_PRINTABLE && code !== TAB && code !== LINE_FEED && code !== CARRIAGE_RETURN) {
|
|
78
61
|
return true;
|
|
79
62
|
}
|
|
@@ -81,10 +64,12 @@ function hasControlCharacter(text) {
|
|
|
81
64
|
return false;
|
|
82
65
|
}
|
|
83
66
|
function textOfBytes(body) {
|
|
84
|
-
if (!(body instanceof ArrayBuffer || ArrayBuffer.isView(body)))
|
|
67
|
+
if (!(body instanceof ArrayBuffer || ArrayBuffer.isView(body))) {
|
|
85
68
|
return undefined;
|
|
86
|
-
|
|
69
|
+
}
|
|
70
|
+
if (body.byteLength > MAX_DECODED_BODY_BYTES) {
|
|
87
71
|
return undefined;
|
|
72
|
+
}
|
|
88
73
|
try {
|
|
89
74
|
const text = new TextDecoder('utf-8', { fatal: true }).decode(body);
|
|
90
75
|
return hasControlCharacter(text) ? undefined : text;
|
|
@@ -97,8 +82,9 @@ function formShape(form) {
|
|
|
97
82
|
const fields = [];
|
|
98
83
|
let length = 0;
|
|
99
84
|
form.forEach((value, name) => {
|
|
100
|
-
if ('string' === typeof value)
|
|
85
|
+
if ('string' === typeof value) {
|
|
101
86
|
length += value.length;
|
|
87
|
+
}
|
|
102
88
|
fields.push(JSON.stringify([name, 'string' === typeof value ? 0 : [value.type, value.size]]));
|
|
103
89
|
});
|
|
104
90
|
return 0 === fields.length ? undefined : fingerprintBody(fields.sort().join(), length);
|
|
@@ -111,8 +97,9 @@ function projectRequestBody(body, captureBodies) {
|
|
|
111
97
|
const typeOf = () => body.constructor?.name ?? typeof body;
|
|
112
98
|
if ('string' === typeof body || bytesText !== undefined) {
|
|
113
99
|
text = bytesText ?? body;
|
|
114
|
-
if (text.length > 0)
|
|
100
|
+
if (text.length > 0) {
|
|
115
101
|
shape = fingerprintBody(jsonShape(text), text.length);
|
|
102
|
+
}
|
|
116
103
|
}
|
|
117
104
|
else if (typeof FormData !== 'undefined' && body instanceof FormData) {
|
|
118
105
|
const form = formShape(body);
|
|
@@ -133,8 +120,9 @@ function projectRequestBody(body, captureBodies) {
|
|
|
133
120
|
return captureBodies ? { requestBodyType: typeOf() } : {};
|
|
134
121
|
}
|
|
135
122
|
const fields = { [REQUEST_SHAPE_FIELD]: shape };
|
|
136
|
-
if (!captureBodies || text === undefined || 0 === text.length)
|
|
123
|
+
if (!captureBodies || text === undefined || 0 === text.length) {
|
|
137
124
|
return fields;
|
|
125
|
+
}
|
|
138
126
|
const { body: out, truncated } = projectBody(text, contentType);
|
|
139
127
|
return truncated
|
|
140
128
|
? { ...fields, requestBody: out, requestBodyTruncated: true }
|
|
@@ -145,27 +133,34 @@ function statusIsOk(status) {
|
|
|
145
133
|
}
|
|
146
134
|
function netResponseMeta(statusText, contentType, contentLength) {
|
|
147
135
|
const out = {};
|
|
148
|
-
if (statusText !== '')
|
|
136
|
+
if (statusText !== '') {
|
|
149
137
|
out['statusText'] = statusText;
|
|
150
|
-
|
|
138
|
+
}
|
|
139
|
+
if (contentType !== null && contentType !== '') {
|
|
151
140
|
out['contentType'] = contentType;
|
|
141
|
+
}
|
|
152
142
|
const size = contentLength !== null ? Number.parseInt(contentLength, 10) : Number.NaN;
|
|
153
|
-
if (Number.isFinite(size))
|
|
143
|
+
if (Number.isFinite(size)) {
|
|
154
144
|
out['responseSize'] = size;
|
|
145
|
+
}
|
|
155
146
|
return out;
|
|
156
147
|
}
|
|
157
148
|
function urlOf(input) {
|
|
158
|
-
if ('string' === typeof input)
|
|
149
|
+
if ('string' === typeof input) {
|
|
159
150
|
return input;
|
|
160
|
-
|
|
151
|
+
}
|
|
152
|
+
if (input instanceof URL) {
|
|
161
153
|
return input.href;
|
|
154
|
+
}
|
|
162
155
|
return input.url;
|
|
163
156
|
}
|
|
164
157
|
function methodOf(input, init) {
|
|
165
|
-
if (init?.method !== undefined)
|
|
158
|
+
if (init?.method !== undefined) {
|
|
166
159
|
return init.method.toUpperCase();
|
|
167
|
-
|
|
160
|
+
}
|
|
161
|
+
if (input instanceof Request) {
|
|
168
162
|
return input.method.toUpperCase();
|
|
163
|
+
}
|
|
169
164
|
return 'GET';
|
|
170
165
|
}
|
|
171
166
|
function nextActionOf(input, init) {
|
|
@@ -180,8 +175,9 @@ function nextActionOf(input, init) {
|
|
|
180
175
|
const NON_APP_FRAME = /@reticlehq|reticle\.ts|network\.ts|transport\.ts|<anonymous>|new Promise|node:internal/i;
|
|
181
176
|
const DEPENDENCY_FRAME = /node_modules|\/\.vite\/deps\//i;
|
|
182
177
|
export function firstAppFrame(stack) {
|
|
183
|
-
if (stack === undefined)
|
|
178
|
+
if (stack === undefined) {
|
|
184
179
|
return undefined;
|
|
180
|
+
}
|
|
185
181
|
const frames = stack
|
|
186
182
|
.split('\n')
|
|
187
183
|
.slice(1)
|
|
@@ -194,14 +190,16 @@ function initiatorFrame() {
|
|
|
194
190
|
return firstAppFrame(new Error().stack);
|
|
195
191
|
}
|
|
196
192
|
export function extractTiming(entry) {
|
|
197
|
-
if (entry === undefined)
|
|
193
|
+
if (entry === undefined) {
|
|
198
194
|
return {};
|
|
195
|
+
}
|
|
199
196
|
const timing = {};
|
|
200
197
|
if (entry.responseStart > 0 && entry.requestStart > 0) {
|
|
201
198
|
timing.ttfbMs = Math.round(entry.responseStart - entry.requestStart);
|
|
202
199
|
}
|
|
203
|
-
if (entry.transferSize > 0)
|
|
200
|
+
if (entry.transferSize > 0) {
|
|
204
201
|
timing.transferSize = entry.transferSize;
|
|
202
|
+
}
|
|
205
203
|
return timing;
|
|
206
204
|
}
|
|
207
205
|
function resourceTiming(rawUrl) {
|
|
@@ -213,14 +211,6 @@ function resourceTiming(rawUrl) {
|
|
|
213
211
|
return {};
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
|
-
function isBridgeSocket(url) {
|
|
217
|
-
try {
|
|
218
|
-
return new URL(url, location.href).pathname === RETICLE_WS_PATH;
|
|
219
|
-
}
|
|
220
|
-
catch {
|
|
221
|
-
return url.includes(RETICLE_WS_PATH);
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
214
|
function isNativeFetch(fn) {
|
|
225
215
|
try {
|
|
226
216
|
return Function.prototype.toString.call(fn).includes('native code');
|
|
@@ -230,8 +220,16 @@ function isNativeFetch(fn) {
|
|
|
230
220
|
}
|
|
231
221
|
}
|
|
232
222
|
const OURS = new WeakSet();
|
|
223
|
+
const PAGE_CAPTURE = Symbol.for('reticle.network');
|
|
224
|
+
const pageSlots = () => window;
|
|
233
225
|
export function installNetwork(emit, opts = {}) {
|
|
226
|
+
if (undefined !== pageSlots()[PAGE_CAPTURE]) {
|
|
227
|
+
return () => undefined;
|
|
228
|
+
}
|
|
229
|
+
const owner = {};
|
|
230
|
+
pageSlots()[PAGE_CAPTURE] = owner;
|
|
234
231
|
const captureBodies = true === opts.captureBodies;
|
|
232
|
+
const bodyCapture = new AbortController();
|
|
235
233
|
const captureErrorBodies = false !== opts.captureErrorBodies;
|
|
236
234
|
const reinterpret = opts.reinterpret;
|
|
237
235
|
const origFetch = requireCapturedMethod(window, 'fetch');
|
|
@@ -243,8 +241,9 @@ export function installNetwork(emit, opts = {}) {
|
|
|
243
241
|
const nextId = () => `n${++seq}`;
|
|
244
242
|
window.fetch = async (input, init) => {
|
|
245
243
|
const rawUrl = urlOf(input);
|
|
246
|
-
if (true === opts.ignore?.(rawUrl))
|
|
244
|
+
if (true === opts.ignore?.(rawUrl)) {
|
|
247
245
|
return callFetch(input, init);
|
|
246
|
+
}
|
|
248
247
|
const id = nextId();
|
|
249
248
|
const start = performance.now();
|
|
250
249
|
const method = methodOf(input, init);
|
|
@@ -293,41 +292,30 @@ export function installNetwork(emit, opts = {}) {
|
|
|
293
292
|
});
|
|
294
293
|
};
|
|
295
294
|
const wantErrorBody = shouldCaptureErrorBody(res.status, captureErrorBodies);
|
|
296
|
-
if (
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
}
|
|
303
|
-
void (async () => {
|
|
304
|
-
let responseBodyFields = {};
|
|
305
|
-
if (clone !== undefined) {
|
|
306
|
-
try {
|
|
307
|
-
const text = await withBodyDeadline(clone.text());
|
|
308
|
-
if (text !== undefined) {
|
|
309
|
-
const { body, truncated } = captureBodies
|
|
310
|
-
? projectBody(text, contentType)
|
|
311
|
-
: projectErrorBody(text, contentType);
|
|
312
|
-
responseBodyFields = {
|
|
313
|
-
responseBody: body,
|
|
314
|
-
...(truncated ? { responseBodyTruncated: true } : {}),
|
|
315
|
-
...(captureBodies ? {} : { responseBodyReason: 'error-status' }),
|
|
316
|
-
};
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
catch {
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
try {
|
|
323
|
-
emitRequest(responseBodyFields);
|
|
324
|
-
}
|
|
325
|
-
catch {
|
|
295
|
+
if (captureBodies && isEventStream(contentType)) {
|
|
296
|
+
emitRequest({ responseBodyReason: ResponseBodyReason.UNREADABLE });
|
|
297
|
+
void captureResponseBody(res, contentType, true, bodyCapture.signal)
|
|
298
|
+
.then((fields) => {
|
|
299
|
+
if (typeof fields['responseBody'] !== 'string') {
|
|
300
|
+
return;
|
|
326
301
|
}
|
|
327
|
-
|
|
302
|
+
observeSafely(() => emit(EventType.NET_RESPONSE_BODY, {
|
|
303
|
+
id,
|
|
304
|
+
method,
|
|
305
|
+
...urlFields,
|
|
306
|
+
...fields,
|
|
307
|
+
}));
|
|
308
|
+
})
|
|
309
|
+
.catch(() => undefined);
|
|
310
|
+
}
|
|
311
|
+
else if ((captureBodies || wantErrorBody) && isCapturableType(contentType)) {
|
|
312
|
+
void captureResponseBody(res, contentType, captureBodies, bodyCapture.signal)
|
|
313
|
+
.then(emitRequest)
|
|
314
|
+
.catch(() => undefined);
|
|
328
315
|
}
|
|
329
316
|
else {
|
|
330
|
-
|
|
317
|
+
const skipped = captureBodies ? skippedBodyReason(contentType) : undefined;
|
|
318
|
+
emitRequest(skipped === undefined ? {} : { responseBodyReason: skipped });
|
|
331
319
|
}
|
|
332
320
|
return res;
|
|
333
321
|
}
|
|
@@ -390,16 +378,25 @@ export function installNetwork(emit, opts = {}) {
|
|
|
390
378
|
this.addEventListener('loadend', () => {
|
|
391
379
|
observeSafely(() => {
|
|
392
380
|
const cur = meta.get(this);
|
|
393
|
-
if (cur === undefined)
|
|
381
|
+
if (cur === undefined) {
|
|
394
382
|
return;
|
|
383
|
+
}
|
|
395
384
|
reportedNetUrls.add(cur.rawUrl);
|
|
396
385
|
const xhrContentType = this.getResponseHeader('content-type');
|
|
397
386
|
let responseBodyFields = {};
|
|
398
387
|
const textReadable = '' === this.responseType || 'text' === this.responseType;
|
|
399
388
|
const wantXhrErrorBody = shouldCaptureErrorBody(this.status, captureErrorBodies);
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
389
|
+
const capturable = isCapturableType(xhrContentType) ||
|
|
390
|
+
(captureBodies && isEventStream(xhrContentType));
|
|
391
|
+
if (captureBodies) {
|
|
392
|
+
const skipped = capturable
|
|
393
|
+
? ResponseBodyReason.UNREADABLE
|
|
394
|
+
: skippedBodyReason(xhrContentType);
|
|
395
|
+
if (skipped !== undefined) {
|
|
396
|
+
responseBodyFields = { responseBodyReason: skipped };
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if ((captureBodies || wantXhrErrorBody) && textReadable && capturable) {
|
|
403
400
|
try {
|
|
404
401
|
const { body: rb, truncated } = captureBodies
|
|
405
402
|
? projectBody(this.responseText, xhrContentType)
|
|
@@ -407,7 +404,9 @@ export function installNetwork(emit, opts = {}) {
|
|
|
407
404
|
responseBodyFields = {
|
|
408
405
|
responseBody: rb,
|
|
409
406
|
...(truncated ? { responseBodyTruncated: true } : {}),
|
|
410
|
-
...(captureBodies
|
|
407
|
+
...(captureBodies
|
|
408
|
+
? {}
|
|
409
|
+
: { responseBodyReason: ResponseBodyReason.ERROR_STATUS }),
|
|
411
410
|
};
|
|
412
411
|
}
|
|
413
412
|
catch {
|
|
@@ -471,8 +470,9 @@ export function installNetwork(emit, opts = {}) {
|
|
|
471
470
|
constructor(u, protocols) {
|
|
472
471
|
super(u, protocols);
|
|
473
472
|
this.#isBridge = isBridgeSocket(String(u));
|
|
474
|
-
if (this.#isBridge)
|
|
473
|
+
if (this.#isBridge) {
|
|
475
474
|
return;
|
|
475
|
+
}
|
|
476
476
|
observeSafely(() => {
|
|
477
477
|
const urlFields = netUrlFields(String(u));
|
|
478
478
|
emit(EventType.NET_STREAM, {
|
|
@@ -546,14 +546,17 @@ export function installNetwork(emit, opts = {}) {
|
|
|
546
546
|
const observer = new PerformanceObserver((list) => {
|
|
547
547
|
for (const raw of list.getEntries()) {
|
|
548
548
|
const entry = raw;
|
|
549
|
-
if (subresourceEvents >= SUBRESOURCE_CAP)
|
|
549
|
+
if (subresourceEvents >= SUBRESOURCE_CAP) {
|
|
550
550
|
return;
|
|
551
|
+
}
|
|
551
552
|
const type = entry.initiatorType || 'other';
|
|
552
|
-
if ('fetch' === type || 'xmlhttprequest' === type)
|
|
553
|
+
if ('fetch' === type || 'xmlhttprequest' === type) {
|
|
553
554
|
continue;
|
|
555
|
+
}
|
|
554
556
|
const rawUrl = entry.name;
|
|
555
|
-
if (reportedNetUrls.has(rawUrl))
|
|
557
|
+
if (reportedNetUrls.has(rawUrl)) {
|
|
556
558
|
continue;
|
|
559
|
+
}
|
|
557
560
|
reportedNetUrls.add(rawUrl);
|
|
558
561
|
subresourceEvents += 1;
|
|
559
562
|
emit(EventType.NET_REQUEST, {
|
|
@@ -579,13 +582,20 @@ export function installNetwork(emit, opts = {}) {
|
|
|
579
582
|
}
|
|
580
583
|
}
|
|
581
584
|
return () => {
|
|
585
|
+
if (pageSlots()[PAGE_CAPTURE] === owner) {
|
|
586
|
+
delete pageSlots()[PAGE_CAPTURE];
|
|
587
|
+
}
|
|
588
|
+
bodyCapture.abort();
|
|
582
589
|
subresourceObserver?.disconnect();
|
|
583
|
-
if (window.fetch === patchedFetch)
|
|
590
|
+
if (window.fetch === patchedFetch) {
|
|
584
591
|
window.fetch = origFetch;
|
|
585
|
-
|
|
592
|
+
}
|
|
593
|
+
if (captureMethod(proto, 'open') === patchedOpen) {
|
|
586
594
|
proto.open = origOpen;
|
|
587
|
-
|
|
595
|
+
}
|
|
596
|
+
if (captureMethod(proto, 'send') === patchedSend) {
|
|
588
597
|
proto.send = origSend;
|
|
598
|
+
}
|
|
589
599
|
if (patchedEventSource !== undefined && window.EventSource === patchedEventSource) {
|
|
590
600
|
window.EventSource = origEventSource;
|
|
591
601
|
}
|
package/dist/observers/route.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { EventType, ROUTE_CHANGE_HOW_FIELD, RouteChangeHow } from '@reticlehq/core';
|
|
2
2
|
import { observeSafely } from './types.js';
|
|
3
3
|
import { captureMethod } from '../patching/capture-method.js';
|
|
4
|
+
import { redactUrl } from '@reticlehq/core';
|
|
4
5
|
function snapshotLocation() {
|
|
5
6
|
return {
|
|
6
7
|
pathname: location.pathname,
|
|
@@ -9,6 +10,32 @@ function snapshotLocation() {
|
|
|
9
10
|
href: location.href,
|
|
10
11
|
};
|
|
11
12
|
}
|
|
13
|
+
let declared = [];
|
|
14
|
+
export function declareRoutes(patterns) {
|
|
15
|
+
declared = patterns.map((pattern) => ({ pattern, parts: pattern.split('/').filter(Boolean) }));
|
|
16
|
+
}
|
|
17
|
+
function nextPattern() {
|
|
18
|
+
const raw = globalThis.next?.router?.pathname;
|
|
19
|
+
if ('string' !== typeof raw || !raw.startsWith('/'))
|
|
20
|
+
return undefined;
|
|
21
|
+
return raw.replace(/\[\.\.\.[^\]]+\]/g, '*').replace(/\[([^\]]+)\]/g, ':$1');
|
|
22
|
+
}
|
|
23
|
+
export function patternOf(pathname) {
|
|
24
|
+
const segments = pathname.split('/').filter(Boolean);
|
|
25
|
+
let best;
|
|
26
|
+
for (const { pattern, parts } of declared) {
|
|
27
|
+
const rest = '*' === parts.at(-1);
|
|
28
|
+
const fixed = rest ? parts.slice(0, -1) : parts;
|
|
29
|
+
if (rest ? segments.length < fixed.length : segments.length !== fixed.length)
|
|
30
|
+
continue;
|
|
31
|
+
if (!fixed.every((part, i) => part.startsWith(':') || part === segments[i]))
|
|
32
|
+
continue;
|
|
33
|
+
const params = parts.filter((part) => part.startsWith(':') || '*' === part).length;
|
|
34
|
+
if (best === undefined || params < best.params)
|
|
35
|
+
best = { pattern, params };
|
|
36
|
+
}
|
|
37
|
+
return best?.pattern ?? nextPattern();
|
|
38
|
+
}
|
|
12
39
|
export function installRoute(emit) {
|
|
13
40
|
const ac = new AbortController();
|
|
14
41
|
const origPush = captureMethod(history, 'pushState');
|
|
@@ -22,12 +49,14 @@ export function installRoute(emit) {
|
|
|
22
49
|
if (to.href === from)
|
|
23
50
|
return;
|
|
24
51
|
lastHref = to.href;
|
|
52
|
+
const pattern = patternOf(to.pathname);
|
|
25
53
|
emit(EventType.ROUTE_CHANGE, {
|
|
26
|
-
from,
|
|
27
|
-
to: to.href,
|
|
54
|
+
from: redactUrl(from),
|
|
55
|
+
to: redactUrl(to.href),
|
|
28
56
|
pathname: to.pathname,
|
|
29
|
-
|
|
30
|
-
|
|
57
|
+
...(pattern === undefined ? {} : { pattern }),
|
|
58
|
+
search: redactUrl(to.search),
|
|
59
|
+
hash: redactUrl(to.hash),
|
|
31
60
|
[ROUTE_CHANGE_HOW_FIELD]: how,
|
|
32
61
|
});
|
|
33
62
|
});
|
|
@@ -49,17 +49,12 @@ export declare const REPORT_TEXT: {
|
|
|
49
49
|
readonly KEY_REFUSED: "Key refused";
|
|
50
50
|
readonly NOT_SENT: "not synced";
|
|
51
51
|
readonly NOT_SENT_HELP: "Runs saved on this machine but not on your dashboard yet. Run reticle connect in this project to send them.";
|
|
52
|
-
readonly INSTRUMENTATION: "What Reticle can see";
|
|
53
|
-
readonly INSTRUMENTATION_HELP: "What Reticle can see in this app, from its handshake and the gaps its verdicts recorded";
|
|
54
|
-
readonly covered: (covered: number, total: number) => string;
|
|
55
|
-
readonly NOT_SEEN_YET: "not checked yet";
|
|
56
52
|
readonly COPY_PROMPT: "Copy prompt for your coding agent";
|
|
57
53
|
readonly SHARE_LABEL: "Share";
|
|
58
54
|
readonly POST_X: "X";
|
|
59
55
|
readonly POST_IN: "LinkedIn";
|
|
60
56
|
readonly REFER: "Invite a friend";
|
|
61
57
|
};
|
|
62
|
-
export declare const CAPABILITY_WORDS: Readonly<Record<string, string>>;
|
|
63
58
|
export declare function compactNumber(n: number): string;
|
|
64
59
|
export declare function compactDuration(ms: number): string;
|
|
65
60
|
export declare function buildShareText(scope: ImpactScope, projectName?: string): string;
|
|
@@ -50,26 +50,12 @@ export const REPORT_TEXT = {
|
|
|
50
50
|
KEY_REFUSED: 'Key refused',
|
|
51
51
|
NOT_SENT: 'not synced',
|
|
52
52
|
NOT_SENT_HELP: 'Runs saved on this machine but not on your dashboard yet. Run reticle connect in this project to send them.',
|
|
53
|
-
INSTRUMENTATION: 'What Reticle can see',
|
|
54
|
-
INSTRUMENTATION_HELP: 'What Reticle can see in this app, from its handshake and the gaps its verdicts recorded',
|
|
55
|
-
covered: (covered, total) => `${String(covered)} of ${String(total)}`,
|
|
56
|
-
NOT_SEEN_YET: 'not checked yet',
|
|
57
53
|
COPY_PROMPT: 'Copy prompt for your coding agent',
|
|
58
54
|
SHARE_LABEL: 'Share',
|
|
59
55
|
POST_X: 'X',
|
|
60
56
|
POST_IN: 'LinkedIn',
|
|
61
57
|
REFER: 'Invite a friend',
|
|
62
58
|
};
|
|
63
|
-
export const CAPABILITY_WORDS = {
|
|
64
|
-
'page connected': 'Page connected',
|
|
65
|
-
'DOM actions': 'Clicks and typing',
|
|
66
|
-
network: 'Network requests',
|
|
67
|
-
console: 'Console errors',
|
|
68
|
-
'app state (store registered)': 'App state',
|
|
69
|
-
'signals on mutation': 'Success signals from your code',
|
|
70
|
-
'file:line source mapping': 'Source file and line',
|
|
71
|
-
'stable test ids': 'Stable test ids',
|
|
72
|
-
};
|
|
73
59
|
export function compactNumber(n) {
|
|
74
60
|
if (n < 1000)
|
|
75
61
|
return String(Math.round(n));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type AccountState, type HarnessConfig, type AgentLink, type HarnessDrive } from '@reticlehq/core';
|
|
1
|
+
import { type AccountState, type HarnessConfig, type AgentLink, type HarnessDrive, type LastDrive } from '@reticlehq/core';
|
|
2
2
|
import type { AnnotationItem } from '../review/annotator.js';
|
|
3
|
-
import { type HarnessGateView } from './presenter-harness-row.js';
|
|
4
3
|
export type ChatView = 'activity' | 'flows' | 'annotations' | 'harness';
|
|
5
4
|
export declare function platformBase(config: HarnessConfig | undefined, account: AccountState): string;
|
|
6
5
|
export interface HudDriveHost {
|
|
@@ -35,8 +34,8 @@ export declare class ChatViews {
|
|
|
35
34
|
setProjectId(projectId: string | undefined): void;
|
|
36
35
|
paintAgent(link: AgentLink | undefined): void;
|
|
37
36
|
paintAccount(account: AccountState | undefined): void;
|
|
38
|
-
paintDrive(drive: HarnessDrive | undefined): void;
|
|
39
|
-
paintHarness(config: HarnessConfig | undefined
|
|
37
|
+
paintDrive(drive: HarnessDrive | undefined, last?: LastDrive): void;
|
|
38
|
+
paintHarness(config: HarnessConfig | undefined): void;
|
|
40
39
|
paintImpact(verdicts: number): void;
|
|
41
40
|
setImpactOpen(open: boolean): void;
|
|
42
41
|
paintAnnotations(items: readonly AnnotationItem[]): void;
|