@tracepanel/js 0.3.3 → 0.4.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.
Files changed (74) hide show
  1. package/README.md +71 -4
  2. package/dist/agent.d.ts +25 -2
  3. package/dist/agent.js +75 -24
  4. package/dist/agent.js.map +1 -1
  5. package/dist/body.d.ts +32 -0
  6. package/dist/body.js +314 -0
  7. package/dist/body.js.map +1 -0
  8. package/dist/browser.d.ts +2 -2
  9. package/dist/browser.js +15 -7
  10. package/dist/browser.js.map +1 -1
  11. package/dist/bullmq.d.ts +76 -1
  12. package/dist/bullmq.js +215 -6
  13. package/dist/bullmq.js.map +1 -1
  14. package/dist/bun.js +81 -11
  15. package/dist/bun.js.map +1 -1
  16. package/dist/config.d.ts +20 -0
  17. package/dist/config.js +13 -0
  18. package/dist/config.js.map +1 -1
  19. package/dist/console.d.ts +5 -0
  20. package/dist/console.js +16 -0
  21. package/dist/console.js.map +1 -1
  22. package/dist/express.js +94 -23
  23. package/dist/express.js.map +1 -1
  24. package/dist/headers.js +54 -8
  25. package/dist/headers.js.map +1 -1
  26. package/dist/inspector.d.ts +9 -0
  27. package/dist/inspector.js +47 -0
  28. package/dist/inspector.js.map +1 -0
  29. package/dist/locals.d.ts +51 -0
  30. package/dist/locals.js +269 -0
  31. package/dist/locals.js.map +1 -0
  32. package/dist/loggers.d.ts +50 -0
  33. package/dist/loggers.js +201 -0
  34. package/dist/loggers.js.map +1 -0
  35. package/dist/model.d.ts +23 -2
  36. package/dist/model.js.map +1 -1
  37. package/dist/next-widget.d.ts +13 -2
  38. package/dist/next-widget.js +16 -6
  39. package/dist/next-widget.js.map +1 -1
  40. package/dist/next.d.ts +6 -0
  41. package/dist/next.js +63 -2
  42. package/dist/next.js.map +1 -1
  43. package/dist/nuxt-plugin.d.ts +1 -0
  44. package/dist/nuxt-plugin.js +6 -0
  45. package/dist/nuxt-plugin.js.map +1 -1
  46. package/dist/processor.d.ts +23 -2
  47. package/dist/processor.js +65 -17
  48. package/dist/processor.js.map +1 -1
  49. package/dist/profile.d.ts +63 -0
  50. package/dist/profile.js +300 -0
  51. package/dist/profile.js.map +1 -0
  52. package/dist/proxy.d.ts +22 -0
  53. package/dist/proxy.js +72 -0
  54. package/dist/proxy.js.map +1 -0
  55. package/dist/request.d.ts +61 -0
  56. package/dist/request.js +244 -0
  57. package/dist/request.js.map +1 -0
  58. package/dist/runtime.d.ts +9 -0
  59. package/dist/runtime.js.map +1 -1
  60. package/dist/session.d.ts +10 -0
  61. package/dist/session.js +45 -0
  62. package/dist/session.js.map +1 -0
  63. package/dist/setup.js +10 -1
  64. package/dist/setup.js.map +1 -1
  65. package/dist/stack.d.ts +27 -3
  66. package/dist/stack.js +106 -9
  67. package/dist/stack.js.map +1 -1
  68. package/dist/vite.d.ts +1 -1
  69. package/dist/vite.js +2 -45
  70. package/dist/vite.js.map +1 -1
  71. package/dist/widget.d.ts +24 -0
  72. package/dist/widget.js +66 -3
  73. package/dist/widget.js.map +1 -1
  74. package/package.json +17 -5
@@ -0,0 +1,63 @@
1
+ import type { Config } from "./config.js";
2
+ import type { Profile } from "./model.js";
3
+ import { type RequestHeaders } from "./request.js";
4
+ export declare const HEADER = "x-tracepanel-profile";
5
+ export declare const COOKIE = "tracepanel_profile";
6
+ /** On a request's root span: the request asked to be profiled. */
7
+ export declare const ATTRIBUTE = "tracepanel.profile";
8
+ /** In the context a framework starts the request's root span in (Next.js's): the request asked to be profiled. */
9
+ export declare const ASKED: symbol;
10
+ /** Whether a request asks to be profiled: the X-Tracepanel-Profile header, or the cookie the panel's toggle sets. */
11
+ export declare function requested(headers: RequestHeaders): boolean;
12
+ /** Whether to profile a trace: the setting profiles every one, a request may ask; nothing when profiling is off. */
13
+ export declare function wanted(config: Config, asked: boolean): boolean;
14
+ /** Whether this runtime can profile: Node, with its inspector. */
15
+ export declare function available(): boolean;
16
+ /** One trace's profile while it runs: its console profile's title, and when it started (performance.now()). */
17
+ export interface Recording {
18
+ title: string;
19
+ startedMs: number;
20
+ /** The trace it profiles, as the profiler knows every trace of the process (see enter()). */
21
+ trace: object;
22
+ }
23
+ /** The trace's profile and the samples left out because another trace ran at the same time. */
24
+ export interface Result {
25
+ profile: Profile | undefined;
26
+ dropped: number;
27
+ }
28
+ /** The app's profiler: the profile that runs while traces are profiled, and when each trace of the process ran. */
29
+ export declare class Profiler {
30
+ private readonly root;
31
+ private readonly intervalMs;
32
+ private session;
33
+ private running;
34
+ private runningSince;
35
+ private idle;
36
+ private readonly recordings;
37
+ /** Every trace of the process running now, and when it started. */
38
+ private readonly open;
39
+ /** Traces that ended while one was profiled: when each started and ended. */
40
+ private ended;
41
+ /** The console profiles stopped, by title, as the inspector hands them over. */
42
+ private readonly finished;
43
+ constructor(root: string, intervalMs: number);
44
+ /** A trace started (each one, profiled or not): a sample taken while it runs may be its. */
45
+ enter(trace: object): void;
46
+ /** A trace ended. */
47
+ leave(trace: object): void;
48
+ /** Begins profiling a trace that started (enter() first); undefined when the inspector can't. */
49
+ start(trace: object): Recording | undefined;
50
+ /** Ends a trace's profile, before it leaves: what was sampled while it alone ran. */
51
+ stop(recording: Recording): Result;
52
+ /** The profile that runs under the traces' own: started with the first profiled trace. */
53
+ private ensure;
54
+ /** No trace is profiled: the profile under them stops after a while, or now when it ran long. */
55
+ private rest;
56
+ private halt;
57
+ /** Whether another trace than the recording's ran at `at` (performance.now()). */
58
+ private shared;
59
+ /** The trace's `profile`: the functions its stacks name, each once, and each stack as indexes into them. */
60
+ private document;
61
+ /** A function as the profile names it; null for this package's own. */
62
+ private frame;
63
+ }
@@ -0,0 +1,300 @@
1
+ // Where a trace's time goes in code: V8's sampling profiler, through Node's inspector (the trace's `profile`, a flame
2
+ // graph in the console). Nothing runs until a trace is profiled: the setting profiles every one, a request asks with
3
+ // the X-Tracepanel-Profile: 1 header or the panel's tracepanel_profile=1 cookie.
4
+ //
5
+ // The profiler samples the whole thread, and one event loop runs every request of the process at once: a sample
6
+ // can't tell which request a frame belongs to. So a profiled trace keeps the samples taken while it was the only trace
7
+ // running here, as the Python SDK keeps a thread's samples only while it runs one trace; those taken while another
8
+ // ran too are dropped and counted (`tracepanel.dropped_samples`). What the process did meanwhile outside any request
9
+ // (a timer, the garbage collector) is in the profile too. Samples of an idle loop, the request waiting on I/O, are
10
+ // left out: the profile is where the time went in code, on the CPU.
11
+ //
12
+ // One profile runs for as long as traces are profiled, so starting the next one costs next to nothing (the first
13
+ // start reads every function V8 has compiled: tens of milliseconds); each trace's own profile is a console.profile()
14
+ // under it, from its start to its end. It stops IDLE_MS after the last one.
15
+ import { relative } from "node:path";
16
+ import { createContextKey } from "@opentelemetry/api";
17
+ import { call, connect, inspector } from "./inspector.js";
18
+ import { headerValue, parseCookies } from "./request.js";
19
+ import { inApp, libraryPath, ownFile, place } from "./stack.js";
20
+ export const HEADER = "x-tracepanel-profile";
21
+ export const COOKIE = "tracepanel_profile";
22
+ /** On a request's root span: the request asked to be profiled. */
23
+ export const ATTRIBUTE = "tracepanel.profile";
24
+ /** In the context a framework starts the request's root span in (Next.js's): the request asked to be profiled. */
25
+ export const ASKED = createContextKey("tracepanel.profile");
26
+ // How deep a stack is kept: a recursion beyond it is cut, outermost frames kept.
27
+ const MAX_DEPTH = 128;
28
+ // Distinct stacks one trace keeps; the rest are counted as one "(other)" frame, so a profile stays small.
29
+ const MAX_STACKS = 2000;
30
+ // How long the profiler runs on with no trace profiled, ready for the next one.
31
+ const IDLE_MS = 30_000;
32
+ // A profiler running this long is started anew once no trace is profiled: its own samples pile up meanwhile.
33
+ const RENEW_MS = 60_000;
34
+ // V8's own entries: the loop waiting (no work to profile), and the root every stack starts from.
35
+ const IDLE = "(idle)";
36
+ const ROOT = "(root)";
37
+ const OTHER = { function: "(other)", module: null, file: "", line: 0, in_app: false };
38
+ /** Whether a request asks to be profiled: the X-Tracepanel-Profile header, or the cookie the panel's toggle sets. */
39
+ export function requested(headers) {
40
+ if (headerValue(headers[HEADER])?.trim() === "1")
41
+ return true;
42
+ const cookies = headerValue(headers.cookie);
43
+ return (cookies?.includes(COOKIE) === true &&
44
+ parseCookies(cookies).some(([name, value]) => name === COOKIE && value === "1"));
45
+ }
46
+ /** Whether to profile a trace: the setting profiles every one, a request may ask; nothing when profiling is off. */
47
+ export function wanted(config, asked) {
48
+ return config.profileEnabled && (config.profile || asked) && available();
49
+ }
50
+ /** Whether this runtime can profile: Node, with its inspector. */
51
+ export function available() {
52
+ return inspector() !== null;
53
+ }
54
+ let next = 0;
55
+ /** The app's profiler: the profile that runs while traces are profiled, and when each trace of the process ran. */
56
+ export class Profiler {
57
+ root;
58
+ intervalMs;
59
+ session;
60
+ running = false;
61
+ runningSince = 0;
62
+ idle;
63
+ recordings = new Set();
64
+ /** Every trace of the process running now, and when it started. */
65
+ open = new Map();
66
+ /** Traces that ended while one was profiled: when each started and ended. */
67
+ ended = [];
68
+ /** The console profiles stopped, by title, as the inspector hands them over. */
69
+ finished = new Map();
70
+ constructor(root, intervalMs) {
71
+ this.root = root;
72
+ this.intervalMs = intervalMs;
73
+ }
74
+ /** A trace started (each one, profiled or not): a sample taken while it runs may be its. */
75
+ enter(trace) {
76
+ this.open.set(trace, performance.now());
77
+ }
78
+ /** A trace ended. */
79
+ leave(trace) {
80
+ const started = this.open.get(trace);
81
+ if (started === undefined)
82
+ return;
83
+ this.open.delete(trace);
84
+ if (this.recordings.size > 0)
85
+ this.ended.push([started, performance.now()]);
86
+ }
87
+ /** Begins profiling a trace that started (enter() first); undefined when the inspector can't. */
88
+ start(trace) {
89
+ const console = inspector()?.console;
90
+ if (!console || !this.ensure())
91
+ return undefined;
92
+ clearTimeout(this.idle);
93
+ next += 1;
94
+ const recording = { title: `tracepanel:${next}`, startedMs: performance.now(), trace };
95
+ console.profile(recording.title);
96
+ this.recordings.add(recording);
97
+ return recording;
98
+ }
99
+ /** Ends a trace's profile, before it leaves: what was sampled while it alone ran. */
100
+ stop(recording) {
101
+ if (!this.recordings.has(recording))
102
+ return { profile: undefined, dropped: 0 };
103
+ inspector()?.console.profileEnd(recording.title);
104
+ this.recordings.delete(recording);
105
+ const profile = this.finished.get(recording.title);
106
+ this.finished.delete(recording.title);
107
+ const result = profile ? this.document(recording, profile) : { profile: undefined, dropped: 0 };
108
+ if (this.recordings.size === 0)
109
+ this.rest();
110
+ else {
111
+ const oldest = Math.min(...[...this.recordings].map((other) => other.startedMs));
112
+ this.ended = this.ended.filter(([, end]) => end >= oldest);
113
+ }
114
+ return result;
115
+ }
116
+ /** The profile that runs under the traces' own: started with the first profiled trace. */
117
+ ensure() {
118
+ if (!this.session) {
119
+ const session = connect();
120
+ if (!session)
121
+ return false;
122
+ try {
123
+ call(session, "Profiler.enable");
124
+ call(session, "Profiler.setSamplingInterval", { interval: Math.round(this.intervalMs * 1000) });
125
+ }
126
+ catch {
127
+ session.disconnect();
128
+ return false;
129
+ }
130
+ session.on("Profiler.consoleProfileFinished", (message) => {
131
+ const { title, profile } = message.params;
132
+ // Only this profiler's: another session's console.profile() reaches every session.
133
+ if ([...this.recordings].some((recording) => recording.title === title))
134
+ this.finished.set(title ?? "", profile);
135
+ });
136
+ this.session = session;
137
+ }
138
+ if (!this.running) {
139
+ try {
140
+ call(this.session, "Profiler.start");
141
+ }
142
+ catch {
143
+ return false;
144
+ }
145
+ this.running = true;
146
+ this.runningSince = performance.now();
147
+ }
148
+ return true;
149
+ }
150
+ /** No trace is profiled: the profile under them stops after a while, or now when it ran long. */
151
+ rest() {
152
+ this.ended = [];
153
+ if (performance.now() - this.runningSince > RENEW_MS) {
154
+ this.halt();
155
+ return;
156
+ }
157
+ clearTimeout(this.idle);
158
+ this.idle = setTimeout(() => this.halt(), IDLE_MS);
159
+ this.idle.unref();
160
+ }
161
+ halt() {
162
+ if (!this.session || !this.running || this.recordings.size > 0)
163
+ return;
164
+ this.running = false;
165
+ try {
166
+ call(this.session, "Profiler.stop");
167
+ }
168
+ catch {
169
+ // Stopped already.
170
+ }
171
+ }
172
+ /** Whether another trace than the recording's ran at `at` (performance.now()). */
173
+ shared(recording, at) {
174
+ for (const [trace, started] of this.open) {
175
+ if (trace !== recording.trace && started <= at)
176
+ return true;
177
+ }
178
+ return this.ended.some(([started, end]) => started <= at && at <= end);
179
+ }
180
+ /** The trace's `profile`: the functions its stacks name, each once, and each stack as indexes into them. */
181
+ document(recording, profile) {
182
+ const nodes = new Map(profile.nodes.map((node) => [node.id, node]));
183
+ const parents = new Map();
184
+ for (const node of profile.nodes)
185
+ for (const child of node.children ?? [])
186
+ parents.set(child, node.id);
187
+ const index = new Map();
188
+ const frames = [];
189
+ /** A node's stack as indexes into frames, outermost first; null for the idle loop. */
190
+ const stackOf = new Map();
191
+ const frameIndex = (callFrame) => {
192
+ const key = `${callFrame.url}:${callFrame.lineNumber}:${callFrame.columnNumber}:${callFrame.functionName}`;
193
+ const known = index.get(key);
194
+ if (known !== undefined)
195
+ return known;
196
+ const frame = this.frame(callFrame);
197
+ if (!frame)
198
+ return null;
199
+ index.set(key, frames.length);
200
+ frames.push(frame);
201
+ return frames.length - 1;
202
+ };
203
+ const stack = (id) => {
204
+ const known = stackOf.get(id);
205
+ if (known !== undefined)
206
+ return known;
207
+ const chain = [];
208
+ for (let at = id; at !== undefined; at = parents.get(at)) {
209
+ const node = nodes.get(at);
210
+ if (node && node.callFrame.functionName !== ROOT)
211
+ chain.push(node.callFrame);
212
+ }
213
+ let found = null;
214
+ if (chain[0]?.functionName !== IDLE) {
215
+ found = [];
216
+ for (const callFrame of chain.reverse()) {
217
+ if (found.length >= MAX_DEPTH)
218
+ break;
219
+ const at = frameIndex(callFrame);
220
+ if (at !== null)
221
+ found.push(at);
222
+ }
223
+ }
224
+ stackOf.set(id, found);
225
+ return found;
226
+ };
227
+ const counts = new Map();
228
+ let samples = 0;
229
+ let dropped = 0;
230
+ let time = profile.startTime;
231
+ const deltas = profile.timeDeltas ?? [];
232
+ (profile.samples ?? []).forEach((id, at) => {
233
+ time += deltas[at] ?? 0;
234
+ let keys = stack(id);
235
+ if (keys === null || keys.length === 0)
236
+ return;
237
+ // The profile's clock is V8's; it started as the recording did.
238
+ if (this.shared(recording, recording.startedMs + (time - profile.startTime) / 1000)) {
239
+ dropped += 1;
240
+ return;
241
+ }
242
+ let key = keys.join(",");
243
+ if (!counts.has(key) && counts.size >= MAX_STACKS) {
244
+ let other = index.get(OTHER.function);
245
+ if (other === undefined) {
246
+ other = frames.length;
247
+ index.set(OTHER.function, other);
248
+ frames.push(OTHER);
249
+ }
250
+ keys = [other];
251
+ key = String(other);
252
+ }
253
+ const counted = counts.get(key);
254
+ if (counted)
255
+ counted.count += 1;
256
+ else
257
+ counts.set(key, { frames: keys, count: 1 });
258
+ samples += 1;
259
+ });
260
+ if (samples === 0)
261
+ return { profile: undefined, dropped };
262
+ // Only the frames the kept stacks name, in the order the most frequent stacks name them.
263
+ const stacks = [...counts.values()].sort((a, b) => b.count - a.count);
264
+ const kept = [];
265
+ const renumber = new Map();
266
+ for (const counted of stacks) {
267
+ counted.frames = counted.frames.map((frame) => {
268
+ let to = renumber.get(frame);
269
+ if (to === undefined) {
270
+ to = kept.length;
271
+ renumber.set(frame, to);
272
+ kept.push(frames[frame]);
273
+ }
274
+ return to;
275
+ });
276
+ }
277
+ return {
278
+ profile: { interval_us: Math.round(this.intervalMs * 1000), samples, frames: kept, stacks },
279
+ dropped,
280
+ };
281
+ }
282
+ /** A function as the profile names it; null for this package's own. */
283
+ frame(callFrame) {
284
+ const name = callFrame.functionName || "(anonymous)";
285
+ if (!callFrame.url)
286
+ return { function: name, module: null, file: "", line: 0, in_app: false };
287
+ const found = place(callFrame.url, callFrame.lineNumber + 1, callFrame.columnNumber + 1);
288
+ if (ownFile(found.file))
289
+ return null;
290
+ const own = inApp(found.file, this.root);
291
+ return {
292
+ function: name,
293
+ module: null,
294
+ file: own ? relative(this.root, found.file) : libraryPath(found.file),
295
+ line: found.line,
296
+ in_app: own,
297
+ };
298
+ }
299
+ }
300
+ //# sourceMappingURL=profile.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile.js","sourceRoot":"","sources":["../src/profile.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import { type IncomingMessage, type ServerResponse } from "node:http";
2
+ import type { BrowserOptions } from "./browser.js";
3
+ export declare const PREFIX = "/__tracepanel__/";
4
+ /** A client on this machine: the console's traces, and what it lets a local caller do, are for its developer only. */
5
+ export declare function isLocal(req: IncomingMessage): boolean;
6
+ /** The browser recording as an ES module, for `import { record } from "/__tracepanel__/browser.js"`. */
7
+ export declare function browserModule(): string;
8
+ /**
9
+ * The browser recording (its ES module) as a classic script that starts it at once: inline in the page's <head>, it
10
+ * wraps fetch before the app's own scripts run, and so before any of them keeps a reference to it.
11
+ */
12
+ export declare function recordingScript(module: string, options: BrowserOptions): string;
13
+ /**
14
+ * The request, sent on to the console as it came, and its answer back. A GET the agent doesn't answer (its address
15
+ * left over from one that exited) goes once more to what `restart` gives: a new agent's console.
16
+ */
17
+ export declare function forward(target: URL, req: IncomingMessage, res: ServerResponse, restart?: () => string | undefined): void;
18
+ /**
19
+ * What the proxy answers itself (the URL with the prefix taken off), true when it did: another machine is refused,
20
+ * and the browser recording comes from this package. The rest goes on to the agent.
21
+ */
22
+ export declare function answeredHere(req: IncomingMessage, res: ServerResponse): boolean;
package/dist/proxy.js ADDED
@@ -0,0 +1,72 @@
1
+ // The console on the app's own origin, at /__tracepanel__/, in development: the Vite plugin and Next.js send what comes
2
+ // there on to the console of the project's agent, so the widget, the page's recording and the panel share the page's
3
+ // origin. Only this machine reaches it: the agent sees the proxy's own address, so its local-only checks (switching
4
+ // the user) would let anyone through.
5
+ import { readFileSync } from "node:fs";
6
+ import { request } from "node:http";
7
+ import { fileURLToPath } from "node:url";
8
+ export const PREFIX = "/__tracepanel__/";
9
+ // The browser recording, served from this package: the console doesn't have it.
10
+ const BROWSER = fileURLToPath(new URL("./browser.js", import.meta.url));
11
+ /** A client on this machine: the console's traces, and what it lets a local caller do, are for its developer only. */
12
+ export function isLocal(req) {
13
+ const address = req.socket.remoteAddress ?? "";
14
+ return address === "::1" || address.startsWith("127.") || address.startsWith("::ffff:127.");
15
+ }
16
+ /** The browser recording as an ES module, for `import { record } from "/__tracepanel__/browser.js"`. */
17
+ export function browserModule() {
18
+ return readFileSync(BROWSER, "utf8");
19
+ }
20
+ /**
21
+ * The browser recording (its ES module) as a classic script that starts it at once: inline in the page's <head>, it
22
+ * wraps fetch before the app's own scripts run, and so before any of them keeps a reference to it.
23
+ */
24
+ export function recordingScript(module, options) {
25
+ // Inside a <script>: no "</script>" in the settings, whatever a service is called.
26
+ const settings = JSON.stringify(options).replace(/</g, "\\u003c");
27
+ return `(() => {\n${module.replace(/^export /gm, "")}\nrecord(${settings});\n})();`;
28
+ }
29
+ /**
30
+ * The request, sent on to the console as it came, and its answer back. A GET the agent doesn't answer (its address
31
+ * left over from one that exited) goes once more to what `restart` gives: a new agent's console.
32
+ */
33
+ export function forward(target, req, res, restart) {
34
+ const upstream = request({
35
+ host: target.hostname,
36
+ port: target.port,
37
+ method: req.method,
38
+ path: `${target.pathname.replace(/\/$/, "")}${req.url ?? "/"}`,
39
+ headers: { ...req.headers, host: target.host },
40
+ }, (answer) => {
41
+ res.writeHead(answer.statusCode ?? 502, answer.headers);
42
+ answer.pipe(res);
43
+ });
44
+ upstream.on("error", () => {
45
+ const again = req.method === "GET" ? restart?.() : undefined;
46
+ if (again) {
47
+ forward(new URL(again), req, res);
48
+ return;
49
+ }
50
+ res.writeHead(502, { "Content-Type": "text/plain" });
51
+ res.end("Tracepanel's agent doesn't answer: is the backend running in development?");
52
+ });
53
+ req.pipe(upstream);
54
+ }
55
+ /**
56
+ * What the proxy answers itself (the URL with the prefix taken off), true when it did: another machine is refused,
57
+ * and the browser recording comes from this package. The rest goes on to the agent.
58
+ */
59
+ export function answeredHere(req, res) {
60
+ if (!isLocal(req)) {
61
+ res.writeHead(403, { "Content-Type": "text/plain" });
62
+ res.end("The Tracepanel console answers on this machine only.");
63
+ return true;
64
+ }
65
+ if (req.url === "/browser.js") {
66
+ res.writeHead(200, { "Content-Type": "text/javascript", "Cache-Control": "no-store" });
67
+ res.end(browserModule());
68
+ return true;
69
+ }
70
+ return false;
71
+ }
72
+ //# sourceMappingURL=proxy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proxy.js","sourceRoot":"","sources":["../src/proxy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,61 @@
1
+ import type { Attrs } from "./model.js";
2
+ /** How much of a body is kept, in characters. */
3
+ export declare const MAX_BODY = 16384;
4
+ export declare const MASK = "********";
5
+ /** A name as one segment of an attribute's key (content-type -> content_type), as the trace format has keys. */
6
+ export declare function key(name: string): string;
7
+ /** Whether a secret goes under the name: x-api-key, csrftoken, sessionid, password. */
8
+ export declare function sensitive(name: string): boolean;
9
+ /** A header's value as one string: several values joined, as Node joins them. */
10
+ export declare function headerValue(value: unknown): string | undefined;
11
+ /** http.request.header.<name>, the name as a key; Cookie is read as cookies instead. */
12
+ export declare function requestHeaders(entries: Iterable<[string, unknown]>): Attrs;
13
+ /** http.response.header.<name>: several values joined, Set-Cookie masked. */
14
+ export declare function responseHeaders(entries: Iterable<[string, unknown]>): Attrs;
15
+ /** url.query: the query string, the values of sensitive parameters masked. */
16
+ export declare function query(search: string): Attrs;
17
+ /** The Cookie header's cookies by name, as they came. */
18
+ export declare function parseCookies(header: string): [string, string][];
19
+ /** http.request.cookie.<name>, from the Cookie header; a session id or a CSRF token masked. */
20
+ export declare function cookies(header: string): Attrs;
21
+ /** The request's headers by lower-case name, from Node's IncomingMessage or a fetch Request. */
22
+ export type RequestHeaders = Record<string, string | string[] | undefined>;
23
+ /** What the request carried before the app read it: its headers, query and cookies. */
24
+ export declare function requestAttrs(requested: RequestHeaders, search: string): Attrs;
25
+ /** The query string of a request's URL (/books?page=2 -> ?page=2). */
26
+ export declare function searchOf(url: string): string;
27
+ export declare function cut(text: string): Attrs;
28
+ /** A JSON value with the values under sensitive keys masked, at any depth. */
29
+ export declare function masked(value: unknown): unknown;
30
+ /** A form's fields as they would have been sent, sensitive values masked, then its files by name and size. */
31
+ export declare function formText(fields: Iterable<[string, unknown]>, files?: [string, string, number][]): string;
32
+ export declare function kindOf(contentType: string): string;
33
+ /**
34
+ * http.request.body from the bytes the app read, as far as it read them: JSON and forms with secrets masked, text as
35
+ * it is, binary not. `complete` is false when only the start of the body is at hand: JSON or a form can't be read
36
+ * (nor its secrets masked) from a part of it.
37
+ */
38
+ export declare function readBody(contentType: string, data: Uint8Array, complete: boolean): Attrs;
39
+ /** What a request's body parser left on it: `req.body`, and `req.file`/`req.files` of an upload. */
40
+ export interface ParsedBody {
41
+ contentType: string;
42
+ body: unknown;
43
+ file?: unknown;
44
+ files?: unknown;
45
+ }
46
+ /**
47
+ * http.request.body from what the app's body parser read: JSON and forms with secrets masked, text as it is, an
48
+ * upload's files by name and size. Nothing when no parser read it.
49
+ */
50
+ export declare function parsedBody({ contentType, body, file, files }: ParsedBody): Attrs;
51
+ /** Whether a request came with a body: one declared by its size, or sent in chunks. */
52
+ export declare function hasBody(requested: RequestHeaders): boolean;
53
+ /**
54
+ * The requests that answered a page's navigation with a redirect, by id, on the way to the page the widget will see:
55
+ * its panel lists them before that page (a form's POST, then the page it redirected to).
56
+ */
57
+ export declare const REDIRECTS = "tracepanel_redirect";
58
+ /** Whether the browser asked for a page to show (a link, a form): not fetch() or an image. */
59
+ export declare function navigated(requested: RequestHeaders): boolean;
60
+ /** The Set-Cookie naming this request too, for a redirect that answers a page's navigation; undefined otherwise. */
61
+ export declare function redirectCookie(id: string, status: number, requested: RequestHeaders): string | undefined;