@brett_lamy/docstream 1.22.1 → 1.23.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/replay/ReplayDevtools.d.ts +6 -3
- package/dist/replay/ReplayDevtools.d.ts.map +1 -1
- package/dist/replay/ReplayDevtools.js +38 -16
- package/dist/replay/ReplayDevtools.js.map +1 -1
- package/dist/replay/ReplayPlayer.d.ts +12 -3
- package/dist/replay/ReplayPlayer.d.ts.map +1 -1
- package/dist/replay/ReplayPlayer.js +48 -9
- package/dist/replay/ReplayPlayer.js.map +1 -1
- package/dist/replay/devtools.d.ts +5 -0
- package/dist/replay/devtools.d.ts.map +1 -1
- package/dist/replay/devtools.js +3 -2
- package/dist/replay/devtools.js.map +1 -1
- package/dist/replay/index.d.ts +2 -0
- package/dist/replay/index.d.ts.map +1 -1
- package/dist/replay/index.js +1 -0
- package/dist/replay/index.js.map +1 -1
- package/dist/replay/inspections.d.ts +36 -0
- package/dist/replay/inspections.d.ts.map +1 -0
- package/dist/replay/inspections.js +64 -0
- package/dist/replay/inspections.js.map +1 -0
- package/dist/styles.css +11 -8
- package/package.json +1 -1
- package/src/replay/ReplayDevtools.tsx +42 -19
- package/src/replay/ReplayPlayer.tsx +60 -11
- package/src/replay/devtools.ts +8 -2
- package/src/replay/index.ts +2 -0
- package/src/replay/inspections.ts +105 -0
- package/src/styles.css +11 -8
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/* Inspections: moments in a recording where someone (a person, a debugging agent) looked at a request or a console
|
|
2
|
+
message — "at 0:35 we inspected GET /api/search?q=te". The player follows them by default: as the playhead
|
|
3
|
+
reaches one, DevTools opens on its tab with that request (or message) selected and the inspection's label above
|
|
4
|
+
it, and playback carries on.
|
|
5
|
+
|
|
6
|
+
They come from the `inspections` prop or from the recording itself, as custom events tagged `inspect`
|
|
7
|
+
(`record.addCustomEvent('inspect', { tab, request, message, label })`), so an .rrweb file can carry its own.
|
|
8
|
+
An inspection names its target by the recording's id for it (a request's `requestId` / `id`, a custom console
|
|
9
|
+
event's `id`) or, failing that, by URL (the request with that URL sent nearest the inspection) or by text (the
|
|
10
|
+
last message containing it). Pure readers, no rrweb import. */
|
|
11
|
+
import type { ReplayConsoleEntry, ReplayDevtools, ReplayRequest } from './devtools'
|
|
12
|
+
import { ReplayEventType, type ReplayEvent } from './types'
|
|
13
|
+
|
|
14
|
+
export type ReplayInspectionTab = 'network' | 'console'
|
|
15
|
+
|
|
16
|
+
export interface ReplayInspection {
|
|
17
|
+
/** Stable id; defaults to `inspect-<n>`. */
|
|
18
|
+
id?: string
|
|
19
|
+
/** Milliseconds from the start of the recording. */
|
|
20
|
+
time: number
|
|
21
|
+
/** The DevTools tab to open (default: `network` when `request` is set, else `console`). */
|
|
22
|
+
tab?: ReplayInspectionTab
|
|
23
|
+
/** The request to select: the recording's id for it (`key`), or its URL. */
|
|
24
|
+
request?: string
|
|
25
|
+
/** The console message to select: the recording's id for it (`key`), or text it contains. */
|
|
26
|
+
message?: string
|
|
27
|
+
/** What was found, shown above the panel while the inspection is current. */
|
|
28
|
+
label?: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** An inspection with its defaults filled in and its target resolved against the recording's DevTools. */
|
|
32
|
+
export interface ResolvedReplayInspection {
|
|
33
|
+
id: string
|
|
34
|
+
time: number
|
|
35
|
+
tab: ReplayInspectionTab
|
|
36
|
+
label?: string
|
|
37
|
+
/** The `ReplayRequest.id` to select, when the target was found. */
|
|
38
|
+
requestId?: string
|
|
39
|
+
/** The `ReplayConsoleEntry.id` to select, when the target was found. */
|
|
40
|
+
messageId?: string
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const INSPECT_TAG = 'inspect'
|
|
44
|
+
|
|
45
|
+
const str = (value: unknown): string | undefined => (typeof value === 'string' && value ? value : undefined)
|
|
46
|
+
|
|
47
|
+
/** The `inspect` custom events in a recording, as inspections (times in ms from its first event). */
|
|
48
|
+
export function getReplayInspections(events: readonly ReplayEvent[]): ReplayInspection[] {
|
|
49
|
+
const list = [...events].sort((a, b) => a.timestamp - b.timestamp)
|
|
50
|
+
const start = list[0]?.timestamp ?? 0
|
|
51
|
+
const out: ReplayInspection[] = []
|
|
52
|
+
for (const event of list) {
|
|
53
|
+
if (event.type !== ReplayEventType.Custom) continue
|
|
54
|
+
const data = (event.data ?? {}) as { tag?: unknown; payload?: Record<string, unknown> }
|
|
55
|
+
if (data.tag !== INSPECT_TAG) continue
|
|
56
|
+
const p = data.payload ?? {}
|
|
57
|
+
const tab = p.tab === 'network' || p.tab === 'console' ? p.tab : undefined
|
|
58
|
+
out.push({ id: str(p.id), time: event.timestamp - start, tab, request: str(p.request), message: str(p.message), label: str(p.label) })
|
|
59
|
+
}
|
|
60
|
+
return out
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function findRequest(network: readonly ReplayRequest[], target: string, time: number): ReplayRequest | undefined {
|
|
64
|
+
const byKey = network.find((r) => r.key === target)
|
|
65
|
+
if (byKey) return byKey
|
|
66
|
+
// By URL (exact, else the address without its origin): the one sent nearest the inspection.
|
|
67
|
+
const matches = network.filter((r) => r.url === target || r.url.replace(/^\w+:\/\/[^/]+/, '') === target)
|
|
68
|
+
return matches.sort((a, b) => Math.abs(a.time - time) - Math.abs(b.time - time))[0]
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function findMessage(messages: readonly ReplayConsoleEntry[], target: string, time: number): ReplayConsoleEntry | undefined {
|
|
72
|
+
const byKey = messages.find((m) => m.key === target)
|
|
73
|
+
if (byKey) return byKey
|
|
74
|
+
const matches = messages.filter((m) => m.message.includes(target))
|
|
75
|
+
// The last one logged by the inspection, else the first after it.
|
|
76
|
+
return matches.filter((m) => m.time <= time).at(-1) ?? matches[0]
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Fills in each inspection's id and tab and finds its request or message; sorted by time. */
|
|
80
|
+
export function resolveReplayInspections(inspections: readonly ReplayInspection[], tools: ReplayDevtools): ResolvedReplayInspection[] {
|
|
81
|
+
return inspections
|
|
82
|
+
.map((inspection, i) => {
|
|
83
|
+
const request = inspection.request ? findRequest(tools.network, inspection.request, inspection.time) : undefined
|
|
84
|
+
const message = inspection.message ? findMessage(tools.console, inspection.message, inspection.time) : undefined
|
|
85
|
+
return {
|
|
86
|
+
id: inspection.id ?? `inspect-${i}`,
|
|
87
|
+
time: Math.max(0, inspection.time),
|
|
88
|
+
tab: inspection.tab ?? (inspection.request ? 'network' : 'console'),
|
|
89
|
+
label: inspection.label,
|
|
90
|
+
requestId: request?.id,
|
|
91
|
+
messageId: message?.id,
|
|
92
|
+
} satisfies ResolvedReplayInspection
|
|
93
|
+
})
|
|
94
|
+
.sort((a, b) => a.time - b.time)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** The inspection current at `time`: the latest at or before it. */
|
|
98
|
+
export function replayInspectionAt(inspections: readonly ResolvedReplayInspection[], time: number): ResolvedReplayInspection | undefined {
|
|
99
|
+
let current: ResolvedReplayInspection | undefined
|
|
100
|
+
for (const inspection of inspections) {
|
|
101
|
+
if (inspection.time > time) break
|
|
102
|
+
current = inspection
|
|
103
|
+
}
|
|
104
|
+
return current
|
|
105
|
+
}
|
package/src/styles.css
CHANGED
|
@@ -4601,19 +4601,17 @@
|
|
|
4601
4601
|
box-shadow: inset 0 0 0 2px var(--ring, #6366f1);
|
|
4602
4602
|
}
|
|
4603
4603
|
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
align-items: center;
|
|
4607
|
-
margin-inline-start: auto;
|
|
4608
|
-
padding-inline: 8px;
|
|
4609
|
-
color: var(--replay-devtools-dim);
|
|
4610
|
-
font-variant-numeric: tabular-nums;
|
|
4611
|
-
}
|
|
4604
|
+
|
|
4605
|
+
|
|
4612
4606
|
|
|
4613
4607
|
:where([data-slot="replay-devtools-tabs"] > [data-slot="replay-devtools-close"], [data-slot="replay-devtools-detail-tabs"] > [data-slot="replay-devtools-close"]) {
|
|
4614
4608
|
padding: 0 8px;
|
|
4615
4609
|
}
|
|
4616
4610
|
|
|
4611
|
+
:where([data-slot="replay-devtools-tabs"] > [data-slot="replay-devtools-close"]) {
|
|
4612
|
+
margin-inline-start: auto;
|
|
4613
|
+
}
|
|
4614
|
+
|
|
4617
4615
|
:where([data-slot="replay-devtools-pane"]) {
|
|
4618
4616
|
display: flex;
|
|
4619
4617
|
flex: 1 1 0%;
|
|
@@ -4882,6 +4880,11 @@
|
|
|
4882
4880
|
:where([data-slot="replay-devtools-table"] tbody tr:focus-visible) {
|
|
4883
4881
|
box-shadow: inset 0 0 0 2px var(--ring, #6366f1);
|
|
4884
4882
|
}
|
|
4883
|
+
/* The inspected request or message: a bar down its leading edge (the selection tint shows it's open). */
|
|
4884
|
+
:where([data-slot="replay-devtools-table"] tbody tr[data-inspected], [data-slot="replay-devtools-message"][data-inspected]) {
|
|
4885
|
+
box-shadow: inset 3px 0 0 var(--primary, #2563eb);
|
|
4886
|
+
}
|
|
4887
|
+
|
|
4885
4888
|
|
|
4886
4889
|
:where([data-slot="replay-devtools-table"] tr[data-failed]) {
|
|
4887
4890
|
color: var(--replay-devtools-red);
|