@atolljs/devtools 0.1.7 → 0.1.9
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/README.md +408 -71
- package/app/api.js +68 -0
- package/app/fw-logos.js +14 -0
- package/app/index.html +161 -2
- package/app/main.js +897 -222
- package/app/panels/actions.js +324 -0
- package/app/panels/audit-rules.js +728 -0
- package/app/panels/audits.js +438 -0
- package/app/panels/inspect-island.js +351 -0
- package/app/panels/memory-values.js +287 -0
- package/app/panels/performance.js +304 -0
- package/app/panels/reactivity.js +271 -0
- package/app/panels/recorder.js +344 -0
- package/app/panels/recording-format.js +166 -0
- package/app/panels/route.d.ts +44 -0
- package/app/panels/route.js +264 -0
- package/app/panels/shell.js +789 -0
- package/app/panels/toast.js +38 -0
- package/dist/bin.js +704 -5
- package/dist/chunk-57NGHQUL.js +344 -0
- package/dist/chunk-6E54FXKS.js +495 -0
- package/dist/chunk-WIL2P7HG.js +661 -0
- package/dist/client.d.ts +7 -0
- package/dist/control.d.ts +4 -0
- package/dist/index.js +2 -1
- package/dist/node.js +2 -1
- package/dist/nodeWs.d.ts +1 -0
- package/dist/otel.d.ts +35 -0
- package/dist/otel.js +87 -0
- package/dist/otelMap.d.ts +232 -0
- package/dist/otelTransport.d.ts +69 -0
- package/dist/overlay.d.ts +16 -2
- package/dist/probes.d.ts +7 -0
- package/dist/protocol.d.ts +46 -4
- package/dist/server.d.ts +19 -0
- package/dist/server.js +61 -3
- package/package.json +6 -2
- package/src/bin.ts +27 -2
- package/src/broadcast.ts +6 -0
- package/src/client.ts +26 -2
- package/src/control.ts +51 -0
- package/src/nodeWs.ts +7 -1
- package/src/otel.ts +140 -0
- package/src/otelMap.ts +661 -0
- package/src/otelTransport.ts +286 -0
- package/src/overlay.ts +239 -47
- package/src/probes.ts +155 -0
- package/src/protocol.ts +52 -4
- package/src/server.ts +83 -3
- package/dist/chunk-4N3NBFL3.js +0 -474
package/dist/node.js
CHANGED
package/dist/nodeWs.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export interface NodeWebSocket {
|
|
|
4
4
|
close(): void;
|
|
5
5
|
onOpen: (() => void) | null;
|
|
6
6
|
onClose: (() => void) | null;
|
|
7
|
+
onMessage: ((text: string) => void) | null;
|
|
7
8
|
}
|
|
8
9
|
/** Connect a WebSocket to `url` — handshake, then framed text IO. */
|
|
9
10
|
export declare function connectNodeWebSocket(url: string): NodeWebSocket;
|
package/dist/otel.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type OtelSignals } from './otelMap.js';
|
|
2
|
+
import { type OtlpSenderOptions, type ResourceAttributes } from './otelTransport.js';
|
|
3
|
+
export interface ExportOtelOptions extends OtlpSenderOptions {
|
|
4
|
+
/** Resource `service.name` (default 'unknown_service'). */
|
|
5
|
+
serviceName?: string;
|
|
6
|
+
/** Extra resource attributes, merged over the detected ones. */
|
|
7
|
+
resource?: ResourceAttributes;
|
|
8
|
+
/** Signals to export: all on by default. */
|
|
9
|
+
signals?: OtelSignals;
|
|
10
|
+
/** Periodic flush interval (default 5000ms). */
|
|
11
|
+
flushIntervalMs?: number;
|
|
12
|
+
/** Spans / log records per request (default 512). */
|
|
13
|
+
maxBatch?: number;
|
|
14
|
+
/** Queued spans and log records each before the oldest drop (default 2048). */
|
|
15
|
+
maxQueue?: number;
|
|
16
|
+
/** Series per metric before new attribute sets drop (default 2000). */
|
|
17
|
+
maxSeries?: number;
|
|
18
|
+
/** Patch global fetch for `net:fetch` spans (default true). */
|
|
19
|
+
network?: boolean;
|
|
20
|
+
/** Sample JS heap for `atoll.runtime.heap` (default true). */
|
|
21
|
+
memory?: boolean;
|
|
22
|
+
/** Browser main thread: long-frame / fps probe (default true). */
|
|
23
|
+
jank?: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface OtelExporter {
|
|
26
|
+
/** Ship everything queued now (plus a metrics snapshot). Never rejects. */
|
|
27
|
+
flush(): Promise<void>;
|
|
28
|
+
/** Uninstall the listener and lifecycle hooks, then flush. Never rejects. */
|
|
29
|
+
close(): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
export declare function exportOtel(opts?: ExportOtelOptions): OtelExporter;
|
|
32
|
+
export { OtelMapper, SEVERITY, newSpanId, newTraceId, toUnixNano } from './otelMap.js';
|
|
33
|
+
export type { KeyValue, OtelMapperOptions, OtelSignals, OtlpLogRecord, OtlpMetric, OtlpSpan } from './otelMap.js';
|
|
34
|
+
export { createOtelPipeline, createOtlpSender, localResource, sessionResource } from './otelTransport.js';
|
|
35
|
+
export type { OtelPipeline, OtelPipelineOptions, OtlpSender, OtlpSenderOptions, OtlpSignal, ResourceAttributes, } from './otelTransport.js';
|
package/dist/otel.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import {
|
|
2
|
+
addDevtoolsSink,
|
|
3
|
+
installFetchProbe,
|
|
4
|
+
installJankProbe,
|
|
5
|
+
installMemoryProbe
|
|
6
|
+
} from "./chunk-57NGHQUL.js";
|
|
7
|
+
import {
|
|
8
|
+
OtelMapper,
|
|
9
|
+
SEVERITY,
|
|
10
|
+
createOtelPipeline,
|
|
11
|
+
createOtlpSender,
|
|
12
|
+
localResource,
|
|
13
|
+
newSpanId,
|
|
14
|
+
newTraceId,
|
|
15
|
+
sessionResource,
|
|
16
|
+
toUnixNano
|
|
17
|
+
} from "./chunk-WIL2P7HG.js";
|
|
18
|
+
|
|
19
|
+
// src/otel.ts
|
|
20
|
+
function exportOtel(opts = {}) {
|
|
21
|
+
const sender = createOtlpSender(opts);
|
|
22
|
+
const endpoint = sender.endpoint;
|
|
23
|
+
const perf = globalThis.performance;
|
|
24
|
+
const originMs = perf.timeOrigin ?? Date.now() - perf.now();
|
|
25
|
+
const mapperOpts = {
|
|
26
|
+
resource: localResource(opts.serviceName, opts.resource),
|
|
27
|
+
originMs,
|
|
28
|
+
signals: opts.signals,
|
|
29
|
+
maxQueue: opts.maxQueue,
|
|
30
|
+
maxSeries: opts.maxSeries,
|
|
31
|
+
// An earlier probe (connectDevtools) may already wrap the captured fetch.
|
|
32
|
+
ignoreUrl: (url) => url.startsWith(endpoint)
|
|
33
|
+
};
|
|
34
|
+
const pipeline = createOtelPipeline({
|
|
35
|
+
sender,
|
|
36
|
+
newMapper: () => new OtelMapper(mapperOpts),
|
|
37
|
+
flushIntervalMs: opts.flushIntervalMs,
|
|
38
|
+
maxBatch: opts.maxBatch,
|
|
39
|
+
signals: opts.signals
|
|
40
|
+
});
|
|
41
|
+
const remove = addDevtoolsSink((ev) => pipeline.ingest(ev, originMs + perf.now()));
|
|
42
|
+
if (opts.network !== false) installFetchProbe(globalThis);
|
|
43
|
+
if (opts.memory !== false) installMemoryProbe(globalThis);
|
|
44
|
+
const releaseJank = opts.jank !== false ? installJankProbe() : () => {
|
|
45
|
+
};
|
|
46
|
+
const g = globalThis;
|
|
47
|
+
const onHide = () => {
|
|
48
|
+
if (g.document?.visibilityState === "hidden") void pipeline.flush({ keepalive: true });
|
|
49
|
+
};
|
|
50
|
+
const onPageHide = () => void pipeline.flush({ keepalive: true });
|
|
51
|
+
const inBrowser = typeof g.document !== "undefined" && typeof g.addEventListener === "function";
|
|
52
|
+
if (inBrowser) {
|
|
53
|
+
g.addEventListener("pagehide", onPageHide);
|
|
54
|
+
g.document.addEventListener?.("visibilitychange", onHide);
|
|
55
|
+
}
|
|
56
|
+
const onBeforeExit = () => void pipeline.flush();
|
|
57
|
+
const inNode = !inBrowser && typeof g.process?.once === "function" && g.process.versions?.node !== void 0;
|
|
58
|
+
if (inNode) g.process.once("beforeExit", onBeforeExit);
|
|
59
|
+
let closed = false;
|
|
60
|
+
return {
|
|
61
|
+
flush: () => pipeline.flush(),
|
|
62
|
+
async close() {
|
|
63
|
+
if (closed) return;
|
|
64
|
+
closed = true;
|
|
65
|
+
remove();
|
|
66
|
+
releaseJank();
|
|
67
|
+
if (inBrowser) {
|
|
68
|
+
g.removeEventListener?.("pagehide", onPageHide);
|
|
69
|
+
g.document?.removeEventListener?.("visibilitychange", onHide);
|
|
70
|
+
}
|
|
71
|
+
if (inNode) g.process.off?.("beforeExit", onBeforeExit);
|
|
72
|
+
await pipeline.close();
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
OtelMapper,
|
|
78
|
+
SEVERITY,
|
|
79
|
+
createOtelPipeline,
|
|
80
|
+
createOtlpSender,
|
|
81
|
+
exportOtel,
|
|
82
|
+
localResource,
|
|
83
|
+
newSpanId,
|
|
84
|
+
newTraceId,
|
|
85
|
+
sessionResource,
|
|
86
|
+
toUnixNano
|
|
87
|
+
};
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure devtools-event → OTLP/HTTP JSON mapping (no transport, no globals
|
|
3
|
+
* beyond crypto for ids). `otel.ts` drives it from a devtools sink; the
|
|
4
|
+
* aggregate server drives one mapper per app session.
|
|
5
|
+
*
|
|
6
|
+
* Encoding follows the OTLP JSON Protobuf mapping: lowerCamelCase keys,
|
|
7
|
+
* traceId/spanId as hex strings, enums as integers, 64-bit integers
|
|
8
|
+
* (timestamps, intValue, counts) as decimal strings.
|
|
9
|
+
*/
|
|
10
|
+
import type { EmittedDevtoolsEvent } from '@atolljs/core';
|
|
11
|
+
export type AnyValue = {
|
|
12
|
+
stringValue: string;
|
|
13
|
+
} | {
|
|
14
|
+
intValue: string;
|
|
15
|
+
} | {
|
|
16
|
+
doubleValue: number;
|
|
17
|
+
} | {
|
|
18
|
+
boolValue: boolean;
|
|
19
|
+
};
|
|
20
|
+
export interface KeyValue {
|
|
21
|
+
key: string;
|
|
22
|
+
value: AnyValue;
|
|
23
|
+
}
|
|
24
|
+
export interface OtlpSpanEvent {
|
|
25
|
+
timeUnixNano: string;
|
|
26
|
+
name: string;
|
|
27
|
+
attributes?: KeyValue[];
|
|
28
|
+
}
|
|
29
|
+
export interface OtlpSpan {
|
|
30
|
+
traceId: string;
|
|
31
|
+
spanId: string;
|
|
32
|
+
parentSpanId?: string;
|
|
33
|
+
name: string;
|
|
34
|
+
kind: number;
|
|
35
|
+
startTimeUnixNano: string;
|
|
36
|
+
endTimeUnixNano: string;
|
|
37
|
+
attributes: KeyValue[];
|
|
38
|
+
events?: OtlpSpanEvent[];
|
|
39
|
+
status?: {
|
|
40
|
+
code: number;
|
|
41
|
+
message?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface OtlpLogRecord {
|
|
45
|
+
timeUnixNano: string;
|
|
46
|
+
observedTimeUnixNano: string;
|
|
47
|
+
severityNumber: number;
|
|
48
|
+
severityText: string;
|
|
49
|
+
body: AnyValue;
|
|
50
|
+
attributes: KeyValue[];
|
|
51
|
+
}
|
|
52
|
+
export interface OtlpNumberPoint {
|
|
53
|
+
attributes: KeyValue[];
|
|
54
|
+
startTimeUnixNano?: string;
|
|
55
|
+
timeUnixNano: string;
|
|
56
|
+
asInt?: string;
|
|
57
|
+
asDouble?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface OtlpHistogramPoint {
|
|
60
|
+
attributes: KeyValue[];
|
|
61
|
+
startTimeUnixNano: string;
|
|
62
|
+
timeUnixNano: string;
|
|
63
|
+
count: string;
|
|
64
|
+
sum: number;
|
|
65
|
+
min: number;
|
|
66
|
+
max: number;
|
|
67
|
+
bucketCounts: string[];
|
|
68
|
+
explicitBounds: number[];
|
|
69
|
+
}
|
|
70
|
+
export interface OtlpMetric {
|
|
71
|
+
name: string;
|
|
72
|
+
unit: string;
|
|
73
|
+
description: string;
|
|
74
|
+
sum?: {
|
|
75
|
+
aggregationTemporality: number;
|
|
76
|
+
isMonotonic: boolean;
|
|
77
|
+
dataPoints: OtlpNumberPoint[];
|
|
78
|
+
};
|
|
79
|
+
gauge?: {
|
|
80
|
+
dataPoints: OtlpNumberPoint[];
|
|
81
|
+
};
|
|
82
|
+
histogram?: {
|
|
83
|
+
aggregationTemporality: number;
|
|
84
|
+
dataPoints: OtlpHistogramPoint[];
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/** Span.SpanKind */
|
|
88
|
+
export declare const SPAN_KIND_CLIENT = 3;
|
|
89
|
+
/** Status.StatusCode */
|
|
90
|
+
export declare const STATUS_ERROR = 2;
|
|
91
|
+
/** AggregationTemporality */
|
|
92
|
+
export declare const TEMPORALITY_CUMULATIVE = 2;
|
|
93
|
+
/** SeverityNumber base value per level. */
|
|
94
|
+
export declare const SEVERITY: Record<'trace' | 'debug' | 'info' | 'warn' | 'error', number>;
|
|
95
|
+
export declare const OTEL_SCOPE: {
|
|
96
|
+
name: string;
|
|
97
|
+
};
|
|
98
|
+
export declare const attr: {
|
|
99
|
+
str: (key: string, v: string) => KeyValue;
|
|
100
|
+
int: (key: string, v: number) => KeyValue;
|
|
101
|
+
dbl: (key: string, v: number) => KeyValue;
|
|
102
|
+
bool: (key: string, v: boolean) => KeyValue;
|
|
103
|
+
};
|
|
104
|
+
/** Untyped attribute bag → KeyValues: integers as intValue, other numbers as doubleValue. */
|
|
105
|
+
export declare const toAttributes: (bag: Record<string, string | number | boolean | undefined>) => KeyValue[];
|
|
106
|
+
/** Epoch milliseconds (fractional) → nanoseconds as a decimal string, without float overflow. */
|
|
107
|
+
export declare const toUnixNano: (epochMs: number) => string;
|
|
108
|
+
/** 16 random bytes → 32 hex chars. */
|
|
109
|
+
export declare const newTraceId: () => string;
|
|
110
|
+
/** 8 random bytes → 16 hex chars. */
|
|
111
|
+
export declare const newSpanId: () => string;
|
|
112
|
+
/**
|
|
113
|
+
* Converts an event's `at` (performance.now() on the emitting thread) to
|
|
114
|
+
* epoch ms. Main-thread events with a known `originMs`
|
|
115
|
+
* (performance.timeOrigin of that thread) convert exactly. Everything else
|
|
116
|
+
* (worker-forwarded events carry the worker's own clock; remote sessions
|
|
117
|
+
* have an unknown origin) is rebased per clock: offset = min over samples
|
|
118
|
+
* of (receive time − at), i.e. the least-latency delivery seen so far.
|
|
119
|
+
* Durations and intra-thread order survive; absolute times are late by at
|
|
120
|
+
* most that minimum delivery latency. A jump of more than `resetMs` above
|
|
121
|
+
* the current estimate is treated as a new clock (a respawned worker in a
|
|
122
|
+
* reused slot) and re-seeds the offset.
|
|
123
|
+
*/
|
|
124
|
+
export declare class ClockRebaser {
|
|
125
|
+
private readonly originMs?;
|
|
126
|
+
private readonly resetMs;
|
|
127
|
+
private offsets;
|
|
128
|
+
constructor(originMs?: number | undefined, resetMs?: number);
|
|
129
|
+
epochMs(ev: Pick<EmittedDevtoolsEvent, 'at' | 'thread' | 'worker'>, recvEpochMs: number): number;
|
|
130
|
+
}
|
|
131
|
+
/** Default ms histogram bounds: sub-ms through 10s. */
|
|
132
|
+
export declare const MS_BOUNDS: number[];
|
|
133
|
+
export interface OtelSignals {
|
|
134
|
+
traces?: boolean;
|
|
135
|
+
metrics?: boolean;
|
|
136
|
+
logs?: boolean;
|
|
137
|
+
}
|
|
138
|
+
export interface OtelMapperOptions {
|
|
139
|
+
/** Resource attributes (service.name, telemetry.sdk.*, …). */
|
|
140
|
+
resource: KeyValue[];
|
|
141
|
+
/** performance.timeOrigin of the main thread, when events come from this process. */
|
|
142
|
+
originMs?: number;
|
|
143
|
+
signals?: OtelSignals;
|
|
144
|
+
/** net:fetch URLs to skip: the exporter's own OTLP posts. */
|
|
145
|
+
ignoreUrl?: (url: string) => boolean;
|
|
146
|
+
/** Queued spans / log records each, oldest dropped past this (default 2048). */
|
|
147
|
+
maxQueue?: number;
|
|
148
|
+
/** Open task calls tracked for correlation (default 10_000, oldest dropped). */
|
|
149
|
+
maxPending?: number;
|
|
150
|
+
/** Series per metric before new attribute sets are dropped (default 2000). */
|
|
151
|
+
maxSeries?: number;
|
|
152
|
+
}
|
|
153
|
+
export declare class OtelMapper {
|
|
154
|
+
readonly resource: KeyValue[];
|
|
155
|
+
readonly spans: OtlpSpan[];
|
|
156
|
+
readonly logs: OtlpLogRecord[];
|
|
157
|
+
/** Items dropped by the queue bound since creation. */
|
|
158
|
+
dropped: number;
|
|
159
|
+
private readonly clock;
|
|
160
|
+
private readonly traces;
|
|
161
|
+
private readonly metricsOn;
|
|
162
|
+
private readonly logsOn;
|
|
163
|
+
private readonly maxQueue;
|
|
164
|
+
private readonly maxPending;
|
|
165
|
+
private readonly ignoreUrl?;
|
|
166
|
+
private readonly pending;
|
|
167
|
+
private readonly pools;
|
|
168
|
+
private readonly instruments;
|
|
169
|
+
private readonly m;
|
|
170
|
+
constructor(opts: OtelMapperOptions);
|
|
171
|
+
private pushSpan;
|
|
172
|
+
private pushLog;
|
|
173
|
+
private log;
|
|
174
|
+
/** Thread attribution shared by logs and runtime metrics. */
|
|
175
|
+
private threadAttrs;
|
|
176
|
+
/** Feed one event; `recvEpochMs` is when the sink saw it (rebases foreign clocks). */
|
|
177
|
+
ingest(ev: EmittedDevtoolsEvent, recvEpochMs: number): void;
|
|
178
|
+
/** Remove up to `max` queued spans. */
|
|
179
|
+
takeSpans(max: number): OtlpSpan[];
|
|
180
|
+
/** Remove up to `max` queued log records. */
|
|
181
|
+
takeLogs(max: number): OtlpLogRecord[];
|
|
182
|
+
/** Cumulative snapshot of every metric with data; empty when metrics are off. */
|
|
183
|
+
collectMetrics(nowEpochMs: number): OtlpMetric[];
|
|
184
|
+
}
|
|
185
|
+
export declare const encodeTraces: (groups: {
|
|
186
|
+
resource: KeyValue[];
|
|
187
|
+
spans: OtlpSpan[];
|
|
188
|
+
}[]) => {
|
|
189
|
+
resourceSpans: {
|
|
190
|
+
resource: {
|
|
191
|
+
attributes: KeyValue[];
|
|
192
|
+
};
|
|
193
|
+
scopeSpans: {
|
|
194
|
+
scope: {
|
|
195
|
+
name: string;
|
|
196
|
+
};
|
|
197
|
+
spans: OtlpSpan[];
|
|
198
|
+
}[];
|
|
199
|
+
}[];
|
|
200
|
+
};
|
|
201
|
+
export declare const encodeMetrics: (groups: {
|
|
202
|
+
resource: KeyValue[];
|
|
203
|
+
metrics: OtlpMetric[];
|
|
204
|
+
}[]) => {
|
|
205
|
+
resourceMetrics: {
|
|
206
|
+
resource: {
|
|
207
|
+
attributes: KeyValue[];
|
|
208
|
+
};
|
|
209
|
+
scopeMetrics: {
|
|
210
|
+
scope: {
|
|
211
|
+
name: string;
|
|
212
|
+
};
|
|
213
|
+
metrics: OtlpMetric[];
|
|
214
|
+
}[];
|
|
215
|
+
}[];
|
|
216
|
+
};
|
|
217
|
+
export declare const encodeLogs: (groups: {
|
|
218
|
+
resource: KeyValue[];
|
|
219
|
+
logs: OtlpLogRecord[];
|
|
220
|
+
}[]) => {
|
|
221
|
+
resourceLogs: {
|
|
222
|
+
resource: {
|
|
223
|
+
attributes: KeyValue[];
|
|
224
|
+
};
|
|
225
|
+
scopeLogs: {
|
|
226
|
+
scope: {
|
|
227
|
+
name: string;
|
|
228
|
+
};
|
|
229
|
+
logRecords: OtlpLogRecord[];
|
|
230
|
+
}[];
|
|
231
|
+
}[];
|
|
232
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OTLP/HTTP JSON transport: a retrying `fetch` sender and the batching
|
|
3
|
+
* pipeline that drains `OtelMapper`s into it. No @atolljs/core runtime
|
|
4
|
+
* imports, so the aggregate server can forward without bundling the SDK.
|
|
5
|
+
*/
|
|
6
|
+
import type { EmittedDevtoolsEvent } from '@atolljs/core';
|
|
7
|
+
import { type KeyValue, type OtelMapper, type OtelSignals } from './otelMap.js';
|
|
8
|
+
import type { SessionInfo } from './protocol.js';
|
|
9
|
+
export type OtlpSignal = 'traces' | 'metrics' | 'logs';
|
|
10
|
+
export interface OtlpSenderOptions {
|
|
11
|
+
/** OTLP/HTTP base URL; `/v1/<signal>` is appended (default http://localhost:4318). */
|
|
12
|
+
endpoint?: string;
|
|
13
|
+
/** Extra request headers (auth: `x-honeycomb-team`, `authorization`, …). */
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
/** fetch implementation (default: the global fetch captured at creation). */
|
|
16
|
+
fetch?: typeof fetch;
|
|
17
|
+
/** Retries per request on 429/502/503/504 or network failure (default 3). */
|
|
18
|
+
maxRetries?: number;
|
|
19
|
+
/** Exponential backoff base when no Retry-After is sent (default 500ms). */
|
|
20
|
+
retryBaseMs?: number;
|
|
21
|
+
/** Cap on any single retry delay (default 30s). */
|
|
22
|
+
retryMaxMs?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Export failures (non-retryable status, retries exhausted). Never thrown
|
|
25
|
+
* into app code. Don't route this through atoll's `log()`: that tees back
|
|
26
|
+
* into the devtools stream and becomes the next export.
|
|
27
|
+
*/
|
|
28
|
+
onError?: (error: Error, signal: OtlpSignal) => void;
|
|
29
|
+
}
|
|
30
|
+
export interface OtlpSender {
|
|
31
|
+
readonly endpoint: string;
|
|
32
|
+
/** POST one request body; resolves true when accepted. Never rejects. */
|
|
33
|
+
send(signal: OtlpSignal, body: unknown, opts?: {
|
|
34
|
+
keepalive?: boolean;
|
|
35
|
+
}): Promise<boolean>;
|
|
36
|
+
}
|
|
37
|
+
export declare const normalizeEndpoint: (endpoint?: string) => string;
|
|
38
|
+
export declare function createOtlpSender(opts?: OtlpSenderOptions): OtlpSender;
|
|
39
|
+
export interface OtelPipelineOptions {
|
|
40
|
+
sender: OtlpSender;
|
|
41
|
+
/** Builds the mapper for a key on first use ('' for a single-app exporter). */
|
|
42
|
+
newMapper: (key: string) => OtelMapper;
|
|
43
|
+
/** Periodic flush interval (default 5000ms). 0 disables the timer. */
|
|
44
|
+
flushIntervalMs?: number;
|
|
45
|
+
/** Spans / log records per request; reaching it triggers an early flush (default 512). */
|
|
46
|
+
maxBatch?: number;
|
|
47
|
+
signals?: OtelSignals;
|
|
48
|
+
/** Epoch-ms clock for metric collection (default Date.now). */
|
|
49
|
+
now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
export interface OtelPipeline {
|
|
52
|
+
ingest(ev: EmittedDevtoolsEvent, recvEpochMs: number, key?: string): void;
|
|
53
|
+
/** Stop tracking a key after its queued data ships on the next flush. */
|
|
54
|
+
release(key: string): void;
|
|
55
|
+
/** Serialized: concurrent calls queue behind the in-flight flush. Never rejects. */
|
|
56
|
+
flush(opts?: {
|
|
57
|
+
keepalive?: boolean;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
close(): Promise<void>;
|
|
60
|
+
}
|
|
61
|
+
export declare function createOtelPipeline(opts: OtelPipelineOptions): OtelPipeline;
|
|
62
|
+
export type ResourceAttributes = Record<string, string | number | boolean | undefined>;
|
|
63
|
+
/** Resource attributes for this process: service.name, telemetry.sdk.*, runtime hints. */
|
|
64
|
+
export declare function localResource(serviceName?: string, extra?: ResourceAttributes): KeyValue[];
|
|
65
|
+
/**
|
|
66
|
+
* Resource attributes for a remote app session (aggregate-server
|
|
67
|
+
* forwarding): the session name is the service unless a fixed one is given.
|
|
68
|
+
*/
|
|
69
|
+
export declare function sessionResource(session: SessionInfo, serviceName?: string, extra?: ResourceAttributes): KeyValue[];
|
package/dist/overlay.d.ts
CHANGED
|
@@ -9,18 +9,30 @@
|
|
|
9
9
|
* connectDevtools();
|
|
10
10
|
* mountDevtoolsOverlay();
|
|
11
11
|
* }
|
|
12
|
+
*
|
|
13
|
+
* Alt+Shift+D toggles the flyout from the host page (`hotkey` option). The
|
|
14
|
+
* dashboard inside the iframe forwards the same chord via postMessage, so
|
|
15
|
+
* it works wherever focus is. "full page ↗" opens the dashboard on the
|
|
16
|
+
* view the flyout is showing (its `#/<view>…` hash route).
|
|
12
17
|
*/
|
|
13
18
|
export type DevtoolsOverlayPosition = 'topleft' | 'topcenter' | 'topright' | 'bottomleft' | 'bottomcenter' | 'bottomright';
|
|
14
19
|
export interface DevtoolsOverlayOptions {
|
|
15
20
|
/** Dashboard URL — default '/__atoll/?mini=1' (vite-plugin mount, mini layout). */
|
|
16
21
|
src?: string;
|
|
17
|
-
/** Start expanded (default
|
|
22
|
+
/** Start expanded (default: the remembered state, else collapsed to a corner toggle). */
|
|
18
23
|
startOpen?: boolean;
|
|
19
|
-
/** Initial size (default 760×580). */
|
|
24
|
+
/** Initial size (default 760×580; a remembered resize wins). */
|
|
20
25
|
width?: number;
|
|
21
26
|
height?: number;
|
|
22
27
|
/** Where the flyout (and its collapsed toggle) anchors — default 'bottomright'. */
|
|
23
28
|
position?: DevtoolsOverlayPosition;
|
|
29
|
+
/**
|
|
30
|
+
* Remember position, size and open state in localStorage across reloads —
|
|
31
|
+
* default true; a string is the storage key (default 'atoll-devtools:overlay').
|
|
32
|
+
*/
|
|
33
|
+
persist?: boolean | string;
|
|
34
|
+
/** Host-page shortcut that toggles the flyout — default 'Alt+Shift+D'; false disables. */
|
|
35
|
+
hotkey?: string | false;
|
|
24
36
|
}
|
|
25
37
|
export interface DevtoolsOverlay {
|
|
26
38
|
open(): void;
|
|
@@ -28,4 +40,6 @@ export interface DevtoolsOverlay {
|
|
|
28
40
|
toggle(): void;
|
|
29
41
|
unmount(): void;
|
|
30
42
|
}
|
|
43
|
+
/** 'Alt+Shift+D' → a keydown predicate. Letters/digits match on `code`, so Alt/Option layouts still hit. */
|
|
44
|
+
export declare function parseHotkey(spec: string): (e: KeyboardEvent) => boolean;
|
|
31
45
|
export declare function mountDevtoolsOverlay(opts?: DevtoolsOverlayOptions): DevtoolsOverlay;
|
package/dist/probes.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** While hidden, a frame is reported only with at least this much script time. */
|
|
2
|
+
export declare const HIDDEN_SCRIPT_MS = 100;
|
|
3
|
+
/**
|
|
4
|
+
* Start the long-frame observer and the frame sampler; returns a release
|
|
5
|
+
* function (the probes stop when every installer has released).
|
|
6
|
+
*/
|
|
7
|
+
export declare function installJankProbe(): () => void;
|
package/dist/protocol.d.ts
CHANGED
|
@@ -12,6 +12,11 @@ export interface SessionInfo {
|
|
|
12
12
|
id: string;
|
|
13
13
|
/** Human label — config `name`, app title, process argv, etc. */
|
|
14
14
|
name?: string;
|
|
15
|
+
/**
|
|
16
|
+
* The shell's own framework ('react', 'vue', 'svelte', 'solid', 'angular',
|
|
17
|
+
* 'vanilla', …): drawn as the main-thread hub's mark on the app map.
|
|
18
|
+
*/
|
|
19
|
+
framework?: string;
|
|
15
20
|
runtime: 'browser' | 'node';
|
|
16
21
|
/** Browser location.href / process title — whatever helps identify it. */
|
|
17
22
|
hint?: string;
|
|
@@ -25,6 +30,17 @@ export interface SessionInfo {
|
|
|
25
30
|
closedAt?: number;
|
|
26
31
|
/** Viewer-pinned closed sessions are exempt from the retention sweep. */
|
|
27
32
|
pinned?: boolean;
|
|
33
|
+
/** Runtime facts captured at connect, for the dashboard's audits. */
|
|
34
|
+
env?: SessionEnv;
|
|
35
|
+
}
|
|
36
|
+
export interface SessionEnv {
|
|
37
|
+
/** `crossOriginIsolated` — false means no SharedArrayBuffer doorbell (islands poll). */
|
|
38
|
+
crossOriginIsolated?: boolean;
|
|
39
|
+
sharedArrayBuffer?: boolean;
|
|
40
|
+
hardwareConcurrency?: number;
|
|
41
|
+
userAgent?: string;
|
|
42
|
+
/** Node `process.version` on the Node path. */
|
|
43
|
+
node?: string;
|
|
28
44
|
}
|
|
29
45
|
export interface HelloMessage {
|
|
30
46
|
type: 'hello';
|
|
@@ -34,8 +50,30 @@ export interface BatchMessage {
|
|
|
34
50
|
type: 'batch';
|
|
35
51
|
events: EmittedDevtoolsEvent[];
|
|
36
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Dashboard → app command (`runDevtoolsCommand` on the app side). `id` is
|
|
55
|
+
* viewer-generated and echoed on the result.
|
|
56
|
+
*/
|
|
57
|
+
export interface ControlRequest {
|
|
58
|
+
type: 'control';
|
|
59
|
+
sessionId: string;
|
|
60
|
+
id: string;
|
|
61
|
+
cmd: string;
|
|
62
|
+
args?: Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
/** App → dashboard reply to a ControlRequest. */
|
|
65
|
+
export interface ControlResult {
|
|
66
|
+
type: 'control-result';
|
|
67
|
+
sessionId: string;
|
|
68
|
+
id: string;
|
|
69
|
+
ok: boolean;
|
|
70
|
+
result?: unknown;
|
|
71
|
+
error?: string;
|
|
72
|
+
}
|
|
37
73
|
/** What an instrumented app sends on /events. */
|
|
38
|
-
export type ClientMessage = HelloMessage | BatchMessage;
|
|
74
|
+
export type ClientMessage = HelloMessage | BatchMessage | ControlResult;
|
|
75
|
+
/** What the server sends an instrumented app on /events. */
|
|
76
|
+
export type AppMessage = ControlRequest;
|
|
39
77
|
/** What a dashboard viewer receives on /view. */
|
|
40
78
|
export type ViewerMessage = {
|
|
41
79
|
type: 'sessions';
|
|
@@ -44,7 +82,7 @@ export type ViewerMessage = {
|
|
|
44
82
|
type: 'batch';
|
|
45
83
|
session: SessionInfo;
|
|
46
84
|
events: EmittedDevtoolsEvent[];
|
|
47
|
-
};
|
|
85
|
+
} | ControlResult;
|
|
48
86
|
/** What a dashboard viewer sends on /view. */
|
|
49
87
|
export type ViewerRequest =
|
|
50
88
|
/** Drop a closed session and its replayed history. Live sessions are ignored. */
|
|
@@ -57,7 +95,9 @@ export type ViewerRequest =
|
|
|
57
95
|
type: 'pin';
|
|
58
96
|
sessionId: string;
|
|
59
97
|
pinned: boolean;
|
|
60
|
-
}
|
|
98
|
+
}
|
|
99
|
+
/** Relay a command to the live app with this session id. */
|
|
100
|
+
| ControlRequest;
|
|
61
101
|
/**
|
|
62
102
|
* Pure-client transport: frames on a same-origin BroadcastChannel named
|
|
63
103
|
* 'atoll-devtools'. Origin scoping replaces the server's session list —
|
|
@@ -80,4 +120,6 @@ export type BroadcastMessage = {
|
|
|
80
120
|
/** Dashboard announcing itself — apps re-hello and replay their tail. */
|
|
81
121
|
| {
|
|
82
122
|
type: 'view';
|
|
83
|
-
}
|
|
123
|
+
}
|
|
124
|
+
/** Dashboard → app command; only the app whose session id matches answers. */
|
|
125
|
+
| ControlRequest | ControlResult;
|
package/dist/server.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
import type { SessionInfo } from './protocol.js';
|
|
2
|
+
import { type OtelSignals } from './otelMap.js';
|
|
3
|
+
import { type OtlpSenderOptions, type ResourceAttributes } from './otelTransport.js';
|
|
4
|
+
/** Forward every ingested session's events to an OTLP/HTTP endpoint. */
|
|
5
|
+
export interface DevtoolsServerOtlpOptions extends OtlpSenderOptions {
|
|
6
|
+
/** Fixed `service.name` for every session (default: each session's name). */
|
|
7
|
+
serviceName?: string;
|
|
8
|
+
/** Extra resource attributes on every session. */
|
|
9
|
+
resource?: ResourceAttributes;
|
|
10
|
+
signals?: OtelSignals;
|
|
11
|
+
/** Default 5000ms. */
|
|
12
|
+
flushIntervalMs?: number;
|
|
13
|
+
/** Default 512. */
|
|
14
|
+
maxBatch?: number;
|
|
15
|
+
}
|
|
2
16
|
export interface DevtoolsServerOptions {
|
|
3
17
|
/** Default 4780. 0 picks an ephemeral port (tests). */
|
|
4
18
|
port?: number;
|
|
@@ -12,6 +26,11 @@ export interface DevtoolsServerOptions {
|
|
|
12
26
|
closedTtlMs?: number;
|
|
13
27
|
/** How often the retention sweep runs (default 60s — tests lower it). */
|
|
14
28
|
sweepMs?: number;
|
|
29
|
+
/**
|
|
30
|
+
* Also export everything ingested to OpenTelemetry (one OTLP resource per
|
|
31
|
+
* app session). Off by default.
|
|
32
|
+
*/
|
|
33
|
+
otlp?: DevtoolsServerOtlpOptions;
|
|
15
34
|
onListen?: (url: string) => void;
|
|
16
35
|
}
|
|
17
36
|
export interface DevtoolsServer {
|