reflet-sdk 0.5.1 → 0.6.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 (42) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +24 -4
  3. package/dist/{client-BWEBWk1k.js → client-DAdWdIFY.js} +17 -13
  4. package/dist/client-DAdWdIFY.js.map +1 -0
  5. package/dist/{client-w4Rkl10y.d.ts → client-DF1fdsSC.d.ts} +3 -1
  6. package/dist/{client-w4Rkl10y.d.ts.map → client-DF1fdsSC.d.ts.map} +1 -1
  7. package/dist/{devtools-layer-EyK7n9w3.js → devtools-layer-D_0UL_Wd.js} +2879 -2717
  8. package/dist/devtools-layer-D_0UL_Wd.js.map +1 -0
  9. package/dist/devtools-next.d.ts +1 -1
  10. package/dist/devtools-next.d.ts.map +1 -1
  11. package/dist/devtools-next.js +1 -1
  12. package/dist/devtools-vite.d.ts +1 -1
  13. package/dist/devtools-vite.d.ts.map +1 -1
  14. package/dist/devtools-vite.js +7 -3
  15. package/dist/devtools-vite.js.map +1 -1
  16. package/dist/feedback.d.ts.map +1 -1
  17. package/dist/feedback.js +51 -34
  18. package/dist/feedback.js.map +1 -1
  19. package/dist/{handler-CHP0m7KF.js → handler-C9tdKcl0.js} +335 -46
  20. package/dist/handler-C9tdKcl0.js.map +1 -0
  21. package/dist/index.d.ts +1 -1
  22. package/dist/index.js +1 -1
  23. package/dist/{options-7KtQZJj8.d.ts → options-CIBazDPp.d.ts} +3 -1
  24. package/dist/options-CIBazDPp.d.ts.map +1 -0
  25. package/dist/{picker-DFfC1uIY.js → picker-C7TkZcBE.js} +60 -44
  26. package/dist/picker-C7TkZcBE.js.map +1 -0
  27. package/dist/{protocol-Bn6Bg_0r.js → protocol-ClqTFzwR.js} +4 -1
  28. package/dist/protocol-ClqTFzwR.js.map +1 -0
  29. package/dist/react.d.ts +1 -1
  30. package/dist/react.d.ts.map +1 -1
  31. package/dist/react.js +43 -49
  32. package/dist/react.js.map +1 -1
  33. package/dist/{z-index-CKxBgZTG.js → z-index-BL0SGZEP.js} +7 -7
  34. package/dist/z-index-BL0SGZEP.js.map +1 -0
  35. package/package.json +3 -2
  36. package/dist/client-BWEBWk1k.js.map +0 -1
  37. package/dist/devtools-layer-EyK7n9w3.js.map +0 -1
  38. package/dist/handler-CHP0m7KF.js.map +0 -1
  39. package/dist/options-7KtQZJj8.d.ts.map +0 -1
  40. package/dist/picker-DFfC1uIY.js.map +0 -1
  41. package/dist/protocol-Bn6Bg_0r.js.map +0 -1
  42. package/dist/z-index-CKxBgZTG.js.map +0 -1
@@ -1,10 +1,13 @@
1
- import { a as EDITORS, i as DEVTOOLS_STATUS_MARKER, n as DEVTOOLS_REQUEST_HEADER, o as PROXIED_API_PATHS, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-Bn6Bg_0r.js";
2
- import "./client-BWEBWk1k.js";
1
+ import { a as EDITORS, i as DEVTOOLS_STATUS_MARKER, n as DEVTOOLS_REQUEST_HEADER, o as PROXIED_API_PATHS, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-ClqTFzwR.js";
2
+ import "./client-DAdWdIFY.js";
3
3
  import { SourceMap } from "node:module";
4
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
5
+ import { mkdir, readFile, realpath, rename, rm, stat, writeFile } from "node:fs/promises";
6
+ import { homedir } from "node:os";
4
7
  import { dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
8
+ import { z } from "zod";
5
9
  import { execFile } from "node:child_process";
6
10
  import { existsSync, realpathSync } from "node:fs";
7
- import { readFile, realpath, stat } from "node:fs/promises";
8
11
  import { fileURLToPath } from "node:url";
9
12
  //#region src/devtools/server/json-response.ts
10
13
  const JSON_RESPONSE_HEADERS = {
@@ -32,7 +35,291 @@ function failure(error, status) {
32
35
  };
33
36
  }
34
37
  //#endregion
38
+ //#region src/devtools/server/request-guard.ts
39
+ const LOOPBACK_HOSTNAMES = ["localhost", "[::1]"];
40
+ const LOOPBACK_IPV4 = /^127(?:\.\d{1,3}){3}$/;
41
+ const IPV4_MAPPED_PREFIX = "::ffff:";
42
+ const TRUSTED_FETCH_SITES = ["same-origin", "none"];
43
+ function hostnameOf(request) {
44
+ const host = request.headers.get("host") ?? new URL(request.url).host;
45
+ try {
46
+ return new URL(`http://${host}`).hostname.toLowerCase();
47
+ } catch {
48
+ return null;
49
+ }
50
+ }
51
+ function isLoopbackHostname(hostname) {
52
+ return LOOPBACK_HOSTNAMES.includes(hostname) || hostname.endsWith(".localhost") || LOOPBACK_IPV4.test(hostname);
53
+ }
54
+ function isLoopbackAddress(address) {
55
+ if (!address) return false;
56
+ const ipv4 = address.startsWith(IPV4_MAPPED_PREFIX) ? address.slice(7) : address;
57
+ return address === "::1" || LOOPBACK_IPV4.test(ipv4);
58
+ }
59
+ /**
60
+ * The Host check stops DNS rebinding: a hostile domain resolved to 127.0.0.1
61
+ * is same-origin with itself, so origin checks alone would let it through.
62
+ * Host and header checks only stop browsers; keeping other machines out is the
63
+ * job of a dev server bound to loopback.
64
+ */
65
+ function rejectUntrustedHost(request, allowedHosts) {
66
+ const hostname = hostnameOf(request);
67
+ if (!(hostname !== null && (isLoopbackHostname(hostname) || allowedHosts.includes(hostname)))) return errorResponse("Reflet devtools only answers on localhost. Add other dev hostnames to allowedHosts or REFLET_DEVTOOLS_HOSTS.", 403);
68
+ return null;
69
+ }
70
+ function rejectUntrustedCaller(request, allowedHosts) {
71
+ if (request.headers.get("x-reflet-devtools") !== "1") return errorResponse(`Reflet devtools requests must send the ${DEVTOOLS_REQUEST_HEADER} header.`, 403);
72
+ const fetchSite = request.headers.get("sec-fetch-site");
73
+ const hostname = hostnameOf(request);
74
+ const mayOmitFetchSite = hostname !== null && allowedHosts.includes(hostname) && !isLoopbackHostname(hostname);
75
+ if (!(fetchSite === null ? mayOmitFetchSite : TRUSTED_FETCH_SITES.includes(fetchSite))) return errorResponse("Reflet devtools only answers requests from the app's own origin.", 403);
76
+ return null;
77
+ }
78
+ //#endregion
79
+ //#region src/devtools/server/connect/connectable-host.ts
80
+ /** Mirrors the backend redirect allowlist, which stays the authority; this only decides whether Connect is offered. */
81
+ function isConnectableHost(hostname) {
82
+ return isLoopbackHostname(hostname) || hostname.endsWith(".test");
83
+ }
84
+ //#endregion
85
+ //#region src/devtools/server/connect/connection-store.ts
86
+ const storedConnectionSchema = z.object({
87
+ apiUrl: z.url(),
88
+ organizationName: z.string(),
89
+ token: z.string().startsWith("fb_dev_")
90
+ });
91
+ const connectionsDirectory = () => join(homedir(), ".reflet", "devtools");
92
+ function connectionFile(root, apiUrl) {
93
+ const key = createHash("sha256").update(JSON.stringify([root, apiUrl])).digest("hex");
94
+ return join(connectionsDirectory(), `${key}.json`);
95
+ }
96
+ async function readConnection(root, apiUrl) {
97
+ try {
98
+ const connection = storedConnectionSchema.parse(JSON.parse(await readFile(connectionFile(root, apiUrl), "utf8")));
99
+ return connection.apiUrl === apiUrl ? connection : null;
100
+ } catch (error) {
101
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
102
+ throw error;
103
+ }
104
+ }
105
+ async function saveConnection(root, connection) {
106
+ await mkdir(connectionsDirectory(), {
107
+ mode: 448,
108
+ recursive: true
109
+ });
110
+ const file = connectionFile(root, connection.apiUrl);
111
+ const temporaryFile = `${file}.${randomUUID()}.tmp`;
112
+ try {
113
+ await writeFile(temporaryFile, `${JSON.stringify(connection)}\n`, {
114
+ flag: "wx",
115
+ mode: 384
116
+ });
117
+ await rename(temporaryFile, file);
118
+ } finally {
119
+ await rm(temporaryFile, { force: true });
120
+ }
121
+ }
122
+ async function removeConnection(root, apiUrl, rejectedToken) {
123
+ const removed = await readConnection(root, apiUrl);
124
+ if (!removed || rejectedToken && removed.token !== rejectedToken) return null;
125
+ await rm(connectionFile(root, apiUrl), { force: true });
126
+ return removed;
127
+ }
128
+ //#endregion
129
+ //#region src/devtools/server/connect/board-credential.ts
130
+ async function boardCredential(settings) {
131
+ if (settings.secretKey) return {
132
+ isStored: false,
133
+ token: settings.secretKey
134
+ };
135
+ const connection = await readConnection(settings.root, settings.apiUrl);
136
+ return connection ? {
137
+ isStored: true,
138
+ token: connection.token
139
+ } : null;
140
+ }
141
+ async function boardAccess(request, settings) {
142
+ if (settings.secretKey) return { kind: "secretKey" };
143
+ const connection = await readConnection(settings.root, settings.apiUrl);
144
+ if (connection) return {
145
+ kind: "connected",
146
+ organizationName: connection.organizationName
147
+ };
148
+ return {
149
+ canConnect: isConnectableHost(new URL(request.url).hostname),
150
+ kind: "disconnected"
151
+ };
152
+ }
153
+ //#endregion
154
+ //#region src/devtools/server/connect/html-page.ts
155
+ const HTML_ESCAPES = {
156
+ "'": "'",
157
+ "\"": """,
158
+ "&": "&",
159
+ "<": "&lt;",
160
+ ">": "&gt;"
161
+ };
162
+ const HTML_SPECIAL_CHARACTERS = /[&<>"']/g;
163
+ const escapeHtml = (text) => text.replace(HTML_SPECIAL_CHARACTERS, (character) => HTML_ESCAPES[character] ?? character);
164
+ function htmlPage(title, message, status) {
165
+ const safeTitle = escapeHtml(title);
166
+ const body = `<!doctype html>
167
+ <html lang="en">
168
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${safeTitle} · Reflet devtools</title></head>
169
+ <body style="margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:system-ui,sans-serif;background:#fafafa;color:#18181b">
170
+ <main style="max-width:28rem;padding:1.5rem">
171
+ <h1 style="font-size:1.25rem;margin:0 0 .5rem">${safeTitle}</h1>
172
+ <p style="margin:0;line-height:1.5;color:#52525b">${escapeHtml(message)}</p>
173
+ </main>
174
+ </body>
175
+ </html>
176
+ `;
177
+ return new Response(body, {
178
+ headers: {
179
+ "cache-control": "no-store",
180
+ "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
181
+ "content-type": "text/html; charset=utf-8",
182
+ "referrer-policy": "no-referrer",
183
+ "x-content-type-options": "nosniff"
184
+ },
185
+ status
186
+ });
187
+ }
188
+ //#endregion
189
+ //#region src/devtools/server/connect/pending-connects.ts
190
+ const PENDING_CONNECT_TTL_MS = 6e5;
191
+ const MAX_PENDING_CONNECTS = 10;
192
+ function pendingConnects() {
193
+ globalThis.refletDevtoolsPendingConnects ??= /* @__PURE__ */ new Map();
194
+ return globalThis.refletDevtoolsPendingConnects;
195
+ }
196
+ function beginConnect(binding) {
197
+ const state = randomBytes(32).toString("base64url");
198
+ const codeVerifier = randomBytes(32).toString("base64url");
199
+ const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
200
+ const pending = pendingConnects();
201
+ while (pending.size >= MAX_PENDING_CONNECTS) {
202
+ const oldestState = pending.keys().next().value;
203
+ if (oldestState === void 0) break;
204
+ pending.delete(oldestState);
205
+ }
206
+ pending.set(state, {
207
+ binding,
208
+ codeVerifier,
209
+ startedAt: Date.now()
210
+ });
211
+ return {
212
+ codeChallenge,
213
+ codeVerifier,
214
+ state
215
+ };
216
+ }
217
+ function takeConnect(state, binding) {
218
+ const pending = pendingConnects();
219
+ const connect = pending.get(state);
220
+ if (!connect) return null;
221
+ if (Date.now() - connect.startedAt > PENDING_CONNECT_TTL_MS) {
222
+ pending.delete(state);
223
+ return null;
224
+ }
225
+ if (!(connect.binding.root === binding.root && connect.binding.apiUrl === binding.apiUrl && connect.binding.redirectUri === binding.redirectUri)) return null;
226
+ pending.delete(state);
227
+ return { codeVerifier: connect.codeVerifier };
228
+ }
229
+ //#endregion
230
+ //#region src/devtools/server/connect/connect-routes.ts
231
+ const publicKeySchema = z.object({ publicKey: z.string().startsWith("fb_pub_").max(200) });
232
+ const tokenExchangeSchema = z.object({
233
+ organizationName: z.string(),
234
+ token: z.string().startsWith("fb_dev_")
235
+ });
236
+ const upstreamErrorSchema = z.object({ error: z.string() });
237
+ const redirectUriFor = (request) => `${new URL(request.url).origin}${DEVTOOLS_ROUTE_BASE}${DEVTOOLS_ENDPOINTS.connectCallback}`;
238
+ async function readPublicKey(request) {
239
+ const parsed = publicKeySchema.safeParse(await request.json().catch(() => null));
240
+ return parsed.success ? parsed.data.publicKey : null;
241
+ }
242
+ async function readJson(response) {
243
+ try {
244
+ return await response.json();
245
+ } catch {
246
+ return null;
247
+ }
248
+ }
249
+ async function startConnect(request, settings) {
250
+ const publicKey = await readPublicKey(request);
251
+ if (!publicKey) return errorResponse("Pass the widget's fb_pub_ public key to connect.", 400);
252
+ if (settings.secretKey) return errorResponse("REFLET_SECRET_KEY is set, so this dev server already reaches your board.", 409);
253
+ if (!isConnectableHost(new URL(request.url).hostname)) return errorResponse("Connecting from the browser works on localhost, *.localhost and *.test. Set REFLET_SECRET_KEY instead.", 403);
254
+ const { codeChallenge, state } = beginConnect({
255
+ apiUrl: settings.apiUrl,
256
+ redirectUri: redirectUriFor(request),
257
+ root: settings.root
258
+ });
259
+ const authorizeUrl = new URL("/auth/devtools", settings.appUrl);
260
+ authorizeUrl.searchParams.set("public_key", publicKey);
261
+ authorizeUrl.searchParams.set("redirect_uri", redirectUriFor(request));
262
+ authorizeUrl.searchParams.set("state", state);
263
+ authorizeUrl.searchParams.set("code_challenge", codeChallenge);
264
+ return jsonResponse({ authorizeUrl: authorizeUrl.toString() });
265
+ }
266
+ async function finishConnect(request, settings) {
267
+ const { searchParams } = new URL(request.url);
268
+ const code = searchParams.get("code");
269
+ const state = searchParams.get("state");
270
+ const pending = code && state ? takeConnect(state, {
271
+ apiUrl: settings.apiUrl,
272
+ redirectUri: redirectUriFor(request),
273
+ root: settings.root
274
+ }) : null;
275
+ if (!(code && pending)) return htmlPage("Link expired", "This connect link expired or was already used. Start again from the Board tab.", 400);
276
+ let upstream;
277
+ try {
278
+ upstream = await fetch(`${settings.apiUrl}/api/v1/devtools/token`, {
279
+ body: JSON.stringify({
280
+ code,
281
+ codeVerifier: pending.codeVerifier,
282
+ redirectUri: redirectUriFor(request)
283
+ }),
284
+ headers: {
285
+ Accept: "application/json",
286
+ "Content-Type": "application/json"
287
+ },
288
+ method: "POST",
289
+ signal: AbortSignal.timeout(1e4)
290
+ });
291
+ } catch {
292
+ return htmlPage("Connection failed", `Could not reach Reflet at ${settings.apiUrl}.`, 502);
293
+ }
294
+ const payload = await readJson(upstream);
295
+ if (!upstream.ok) return htmlPage("Connection refused", upstreamErrorSchema.safeParse(payload).data?.error ?? "Reflet refused this connect code.", 400);
296
+ const exchange = tokenExchangeSchema.safeParse(payload);
297
+ if (!exchange.success) return htmlPage("Connection failed", "Reflet answered with an unexpected response.", 502);
298
+ await saveConnection(settings.root, {
299
+ apiUrl: settings.apiUrl,
300
+ organizationName: exchange.data.organizationName,
301
+ token: exchange.data.token
302
+ });
303
+ return htmlPage("Connected", `Connected to ${exchange.data.organizationName}. Go back to your app: the Board tab is ready.`, 200);
304
+ }
305
+ async function revokeUpstream(apiUrl, token) {
306
+ try {
307
+ return (await fetch(`${apiUrl}/api/v1/devtools/revoke`, {
308
+ headers: { Authorization: `Bearer ${token}` },
309
+ method: "POST",
310
+ signal: AbortSignal.timeout(1e4)
311
+ })).ok;
312
+ } catch {
313
+ return false;
314
+ }
315
+ }
316
+ async function disconnect(settings) {
317
+ const removed = await removeConnection(settings.root, settings.apiUrl);
318
+ return jsonResponse({ revoked: removed ? await revokeUpstream(removed.apiUrl, removed.token) : false });
319
+ }
320
+ //#endregion
35
321
  //#region src/devtools/server/options.ts
322
+ const DEFAULT_APP_URL = "https://www.reflet.app";
36
323
  const DEFAULT_EDITOR = "vscode";
37
324
  const TRAILING_SLASHES$1 = /\/+$/;
38
325
  function isEditorId(value) {
@@ -49,9 +336,11 @@ function resolveEditor(optionEditor, envEditor) {
49
336
  }
50
337
  function resolveDevtoolsOptions(options, env) {
51
338
  const apiUrl = presentValue(options.apiUrl) ?? presentValue(env.REFLET_API_URL) ?? "https://harmless-clam-802.convex.site";
339
+ const appUrl = presentValue(options.appUrl) ?? presentValue(env.REFLET_APP_URL) ?? DEFAULT_APP_URL;
52
340
  return {
53
341
  allowedHosts: (options.allowedHosts ?? presentValue(env.REFLET_DEVTOOLS_HOSTS)?.split(",") ?? []).map((host) => host.trim().toLowerCase()).filter(Boolean),
54
342
  apiUrl: apiUrl.replace(TRAILING_SLASHES$1, ""),
343
+ appUrl: appUrl.replace(TRAILING_SLASHES$1, ""),
55
344
  editor: resolveEditor(options.editor, env.REFLET_EDITOR),
56
345
  root: resolve(presentValue(options.root) ?? process.cwd()),
57
346
  secretKey: presentValue(options.secretKey) ?? presentValue(env.REFLET_SECRET_KEY) ?? null
@@ -60,7 +349,7 @@ function resolveDevtoolsOptions(options, env) {
60
349
  //#endregion
61
350
  //#region src/devtools/server/reflet-proxy.ts
62
351
  const CREATE_FEEDBACK_PATH = "/api/v1/feedback/create";
63
- const MISSING_SECRET_KEY_ERROR = "Set REFLET_SECRET_KEY in the dev server environment to reach your Reflet board.";
352
+ const MISSING_BOARD_ACCESS_ERROR = "Connect to Reflet from the devtools Board tab, or set REFLET_SECRET_KEY in the dev server environment.";
64
353
  const BODYLESS_STATUSES = [
65
354
  204,
66
355
  205,
@@ -88,10 +377,10 @@ async function readForwardedBody(request, apiPath) {
88
377
  internal: true
89
378
  }) };
90
379
  }
91
- /** Forwards an allowlisted Reflet API call with the secret key; devtools notes are always internal. */
380
+ /** Forwards an allowlisted Reflet API call with the board token; devtools notes are always internal. */
92
381
  async function proxyToReflet(request, apiPath, target) {
93
382
  if (!isProxiedApiPath(apiPath)) return errorResponse(`Reflet devtools does not forward ${apiPath || "/"}.`, 404);
94
- if (!target.secretKey) return errorResponse(MISSING_SECRET_KEY_ERROR, 503);
383
+ if (!target.token) return errorResponse(MISSING_BOARD_ACCESS_ERROR, 503);
95
384
  if (request.method !== "GET" && request.method !== "POST") return errorResponse(`${request.method} is not supported here.`, 405);
96
385
  const forwarded = await readForwardedBody(request, apiPath);
97
386
  if ("error" in forwarded) return errorResponse(forwarded.error, 400);
@@ -102,42 +391,19 @@ async function proxyToReflet(request, apiPath, target) {
102
391
  body: forwarded.body,
103
392
  headers: {
104
393
  Accept: "application/json",
105
- Authorization: `Bearer ${target.secretKey}`,
394
+ Authorization: `Bearer ${target.token}`,
106
395
  "Content-Type": "application/json"
107
396
  },
108
- method: request.method
397
+ method: request.method,
398
+ signal: AbortSignal.timeout(15e3)
109
399
  });
110
400
  } catch {
111
401
  return errorResponse(`Could not reach Reflet at ${target.apiUrl}.`, 502);
112
402
  }
113
- const text = await upstream.text();
114
- return jsonTextResponse(BODYLESS_STATUSES.includes(upstream.status) ? null : text, upstream.status);
115
- }
116
- //#endregion
117
- //#region src/devtools/server/request-guard.ts
118
- const LOOPBACK_HOSTNAMES = ["localhost", "[::1]"];
119
- const LOOPBACK_IPV4 = /^127(?:\.\d{1,3}){3}$/;
120
- function hostnameOf(host) {
121
- try {
122
- return new URL(`http://${host}`).hostname.toLowerCase();
123
- } catch {
124
- return null;
125
- }
126
- }
127
- function isAllowedHost(hostname, allowedHosts) {
128
- return LOOPBACK_HOSTNAMES.includes(hostname) || hostname.endsWith(".localhost") || LOOPBACK_IPV4.test(hostname) || allowedHosts.includes(hostname);
129
- }
130
- /**
131
- * The Host check stops DNS rebinding: a hostile domain resolved to 127.0.0.1
132
- * is same-origin with itself, so origin checks alone would let it through.
133
- */
134
- function rejectUntrustedRequest(request, allowedHosts) {
135
- const hostname = hostnameOf(request.headers.get("host") ?? new URL(request.url).host);
136
- if (!(hostname && isAllowedHost(hostname, allowedHosts))) return errorResponse("Reflet devtools only answers on localhost. Add other dev hostnames to allowedHosts or REFLET_DEVTOOLS_HOSTS.", 403);
137
- if (request.headers.get("x-reflet-devtools") !== "1") return errorResponse(`Reflet devtools requests must send the ${DEVTOOLS_REQUEST_HEADER} header.`, 403);
138
- const fetchSite = request.headers.get("sec-fetch-site");
139
- if (!(fetchSite === null || fetchSite === "same-origin" || fetchSite === "none")) return errorResponse("Reflet devtools only answers requests from the app's own origin.", 403);
140
- return null;
403
+ return new Response(BODYLESS_STATUSES.includes(upstream.status) ? null : upstream.body, {
404
+ headers: JSON_RESPONSE_HEADERS,
405
+ status: upstream.status
406
+ });
141
407
  }
142
408
  //#endregion
143
409
  //#region src/devtools/server/source/code-search.ts
@@ -657,6 +923,14 @@ async function readSourceFile(frame, roots) {
657
923
  //#endregion
658
924
  //#region src/devtools/server/handler.ts
659
925
  const TRAILING_SLASHES = /\/+$/;
926
+ const ENDPOINT_METHODS = {
927
+ [DEVTOOLS_ENDPOINTS.connectCallback]: "GET",
928
+ [DEVTOOLS_ENDPOINTS.connectStart]: "POST",
929
+ [DEVTOOLS_ENDPOINTS.disconnect]: "POST",
930
+ [DEVTOOLS_ENDPOINTS.search]: "GET",
931
+ [DEVTOOLS_ENDPOINTS.source]: "GET",
932
+ [DEVTOOLS_ENDPOINTS.status]: "GET"
933
+ };
660
934
  function outcomeResponse(outcome) {
661
935
  return outcome.ok ? jsonResponse(outcome.value) : errorResponse(outcome.error, outcome.status);
662
936
  }
@@ -670,17 +944,29 @@ function readInteger(value, minimum) {
670
944
  const parsed = Number(value);
671
945
  return Number.isInteger(parsed) && parsed >= minimum ? parsed : null;
672
946
  }
673
- async function routeRequest(request, settings, projectRoots) {
947
+ async function proxyWithBoardCredential(request, apiPath, settings) {
948
+ const credential = await boardCredential(settings);
949
+ const response = await proxyToReflet(request, apiPath, {
950
+ apiUrl: settings.apiUrl,
951
+ token: credential?.token ?? null
952
+ });
953
+ if (credential?.isStored && response.status === 401) await removeConnection(settings.root, settings.apiUrl, credential.token);
954
+ return response;
955
+ }
956
+ async function routeRequest(request, subPath, settings, projectRoots) {
674
957
  const url = new URL(request.url);
675
- const subPath = routeSubPath(url.pathname);
676
- if (subPath?.startsWith(`${DEVTOOLS_ENDPOINTS.proxy}/`)) return await proxyToReflet(request, subPath.slice(DEVTOOLS_ENDPOINTS.proxy.length), settings);
677
- if ((subPath === DEVTOOLS_ENDPOINTS.status || subPath === DEVTOOLS_ENDPOINTS.source || subPath === DEVTOOLS_ENDPOINTS.search) && request.method !== "GET") return errorResponse(`${subPath} only answers GET requests.`, 405);
958
+ if (subPath?.startsWith(`${DEVTOOLS_ENDPOINTS.proxy}/`)) return await proxyWithBoardCredential(request, subPath.slice(DEVTOOLS_ENDPOINTS.proxy.length), settings);
959
+ const requiredMethod = subPath ? ENDPOINT_METHODS[subPath] : void 0;
960
+ if (requiredMethod && request.method !== requiredMethod) return errorResponse(`${subPath} only answers ${requiredMethod} requests.`, 405);
678
961
  switch (subPath) {
679
962
  case DEVTOOLS_ENDPOINTS.status: return jsonResponse({
963
+ board: await boardAccess(request, settings),
680
964
  editor: settings.editor,
681
- hasSecretKey: settings.secretKey !== null,
682
965
  marker: DEVTOOLS_STATUS_MARKER
683
966
  });
967
+ case DEVTOOLS_ENDPOINTS.connectStart: return await startConnect(request, settings);
968
+ case DEVTOOLS_ENDPOINTS.connectCallback: return await finishConnect(request, settings);
969
+ case DEVTOOLS_ENDPOINTS.disconnect: return await disconnect(settings);
684
970
  case DEVTOOLS_ENDPOINTS.source: return outcomeResponse(await readSourceFile({
685
971
  column: readInteger(url.searchParams.get("column"), 0),
686
972
  fileName: url.searchParams.get("file") ?? "",
@@ -698,16 +984,19 @@ function createDevtoolsHandler(options = {}, env = process.env) {
698
984
  return roots;
699
985
  };
700
986
  return async (request) => {
701
- const rejection = rejectUntrustedRequest(request, settings.allowedHosts);
702
- if (rejection) return rejection;
987
+ const hostRejection = rejectUntrustedHost(request, settings.allowedHosts);
988
+ if (hostRejection) return hostRejection;
989
+ const subPath = routeSubPath(new URL(request.url).pathname);
990
+ const callerRejection = request.method === "GET" && subPath === DEVTOOLS_ENDPOINTS.connectCallback ? null : rejectUntrustedCaller(request, settings.allowedHosts);
991
+ if (callerRejection) return callerRejection;
703
992
  try {
704
- return await routeRequest(request, settings, projectRoots);
993
+ return await routeRequest(request, subPath, settings, projectRoots);
705
994
  } catch (error) {
706
995
  return errorResponse(`Reflet devtools route failed: ${error instanceof Error ? error.message : String(error)}`, 500);
707
996
  }
708
997
  };
709
998
  }
710
999
  //#endregion
711
- export { errorResponse as n, createDevtoolsHandler as t };
1000
+ export { isLoopbackAddress as n, errorResponse as r, createDevtoolsHandler as t };
712
1001
 
713
- //# sourceMappingURL=handler-CHP0m7KF.js.map
1002
+ //# sourceMappingURL=handler-C9tdKcl0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handler-C9tdKcl0.js","names":["TRAILING_SLASHES","QUERY_OR_HASH"],"sources":["../src/devtools/server/json-response.ts","../src/devtools/server/request-guard.ts","../src/devtools/server/connect/connectable-host.ts","../src/devtools/server/connect/connection-store.ts","../src/devtools/server/connect/board-credential.ts","../src/devtools/server/connect/html-page.ts","../src/devtools/server/connect/pending-connects.ts","../src/devtools/server/connect/connect-routes.ts","../src/devtools/server/options.ts","../src/devtools/server/reflet-proxy.ts","../src/devtools/server/source/code-search.ts","../src/devtools/server/source/project-roots.ts","../src/devtools/server/source/jsx-range.ts","../src/devtools/server/source/server-frame.ts","../src/devtools/server/source/source-file.ts","../src/devtools/server/handler.ts"],"sourcesContent":["import type { DevtoolsErrorBody } from \"../protocol\";\n\nexport type DevtoolsOutcome<T> =\n | { ok: true; value: T }\n | { error: string; ok: false; status: number };\n\nexport const JSON_RESPONSE_HEADERS = {\n \"cache-control\": \"no-store\",\n \"content-type\": \"application/json; charset=utf-8\",\n \"x-content-type-options\": \"nosniff\",\n};\n\nexport function jsonTextResponse(\n text: string | null,\n status: number\n): Response {\n return new Response(text, { headers: JSON_RESPONSE_HEADERS, status });\n}\n\nexport function jsonResponse(body: unknown, status = 200): Response {\n return jsonTextResponse(JSON.stringify(body), status);\n}\n\nexport function errorResponse(error: string, status: number): Response {\n const body: DevtoolsErrorBody = { error };\n return jsonResponse(body, status);\n}\n\nexport function failure<T>(error: string, status: number): DevtoolsOutcome<T> {\n return { error, ok: false, status };\n}\n","import { DEVTOOLS_REQUEST_HEADER } from \"../protocol\";\nimport { errorResponse } from \"./json-response\";\n\nconst LOOPBACK_HOSTNAMES = [\"localhost\", \"[::1]\"];\nconst LOOPBACK_IPV4 = /^127(?:\\.\\d{1,3}){3}$/;\nconst IPV4_MAPPED_PREFIX = \"::ffff:\";\nconst TRUSTED_FETCH_SITES = [\"same-origin\", \"none\"];\n\nfunction hostnameOf(request: Request): string | null {\n const host = request.headers.get(\"host\") ?? new URL(request.url).host;\n try {\n return new URL(`http://${host}`).hostname.toLowerCase();\n } catch {\n return null;\n }\n}\n\nexport function isLoopbackHostname(hostname: string): boolean {\n return (\n LOOPBACK_HOSTNAMES.includes(hostname) ||\n hostname.endsWith(\".localhost\") ||\n LOOPBACK_IPV4.test(hostname)\n );\n}\n\nexport function isLoopbackAddress(address: string | undefined): boolean {\n if (!address) {\n return false;\n }\n const ipv4 = address.startsWith(IPV4_MAPPED_PREFIX)\n ? address.slice(IPV4_MAPPED_PREFIX.length)\n : address;\n return address === \"::1\" || LOOPBACK_IPV4.test(ipv4);\n}\n\n/**\n * The Host check stops DNS rebinding: a hostile domain resolved to 127.0.0.1\n * is same-origin with itself, so origin checks alone would let it through.\n * Host and header checks only stop browsers; keeping other machines out is the\n * job of a dev server bound to loopback.\n */\nexport function rejectUntrustedHost(\n request: Request,\n allowedHosts: string[]\n): Response | null {\n const hostname = hostnameOf(request);\n const isAllowedHost =\n hostname !== null &&\n (isLoopbackHostname(hostname) || allowedHosts.includes(hostname));\n if (!isAllowedHost) {\n return errorResponse(\n \"Reflet devtools only answers on localhost. Add other dev hostnames to allowedHosts or REFLET_DEVTOOLS_HOSTS.\",\n 403\n );\n }\n return null;\n}\n\nexport function rejectUntrustedCaller(\n request: Request,\n allowedHosts: string[]\n): Response | null {\n if (request.headers.get(DEVTOOLS_REQUEST_HEADER) !== \"1\") {\n return errorResponse(\n `Reflet devtools requests must send the ${DEVTOOLS_REQUEST_HEADER} header.`,\n 403\n );\n }\n\n const fetchSite = request.headers.get(\"sec-fetch-site\");\n const hostname = hostnameOf(request);\n // Browsers omit Sec-Fetch-Site only outside secure contexts: a plain-HTTP allowed host, never loopback.\n const mayOmitFetchSite =\n hostname !== null &&\n allowedHosts.includes(hostname) &&\n !isLoopbackHostname(hostname);\n const isTrustedSite =\n fetchSite === null\n ? mayOmitFetchSite\n : TRUSTED_FETCH_SITES.includes(fetchSite);\n if (!isTrustedSite) {\n return errorResponse(\n \"Reflet devtools only answers requests from the app's own origin.\",\n 403\n );\n }\n\n return null;\n}\n","import { isLoopbackHostname } from \"../request-guard\";\n\n/** Mirrors the backend redirect allowlist, which stays the authority; this only decides whether Connect is offered. */\nexport function isConnectableHost(hostname: string): boolean {\n return isLoopbackHostname(hostname) || hostname.endsWith(\".test\");\n}\n","import { createHash, randomUUID } from \"node:crypto\";\nimport { mkdir, readFile, rename, rm, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { z } from \"zod\";\n\nconst storedConnectionSchema = z.object({\n apiUrl: z.url(),\n organizationName: z.string(),\n token: z.string().startsWith(\"fb_dev_\"),\n});\nexport type StoredConnection = z.infer<typeof storedConnectionSchema>;\n\nconst connectionsDirectory = () => join(homedir(), \".reflet\", \"devtools\");\n\nfunction connectionFile(root: string, apiUrl: string) {\n const key = createHash(\"sha256\")\n .update(JSON.stringify([root, apiUrl]))\n .digest(\"hex\");\n return join(connectionsDirectory(), `${key}.json`);\n}\n\nexport async function readConnection(\n root: string,\n apiUrl: string\n): Promise<StoredConnection | null> {\n try {\n const connection = storedConnectionSchema.parse(\n JSON.parse(await readFile(connectionFile(root, apiUrl), \"utf8\"))\n );\n return connection.apiUrl === apiUrl ? connection : null;\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n return null;\n }\n throw error;\n }\n}\n\nexport async function saveConnection(\n root: string,\n connection: StoredConnection\n): Promise<void> {\n await mkdir(connectionsDirectory(), { mode: 0o700, recursive: true });\n const file = connectionFile(root, connection.apiUrl);\n const temporaryFile = `${file}.${randomUUID()}.tmp`;\n try {\n await writeFile(temporaryFile, `${JSON.stringify(connection)}\\n`, {\n flag: \"wx\",\n mode: 0o600,\n });\n await rename(temporaryFile, file);\n } finally {\n await rm(temporaryFile, { force: true });\n }\n}\n\nexport async function removeConnection(\n root: string,\n apiUrl: string,\n rejectedToken?: string\n): Promise<StoredConnection | null> {\n const removed = await readConnection(root, apiUrl);\n if (!removed || (rejectedToken && removed.token !== rejectedToken)) {\n return null;\n }\n await rm(connectionFile(root, apiUrl), { force: true });\n return removed;\n}\n","import type { BoardAccess } from \"../../protocol\";\nimport type { ResolvedDevtoolsOptions } from \"../options\";\nimport { isConnectableHost } from \"./connectable-host\";\nimport { readConnection } from \"./connection-store\";\n\nexport interface BoardCredential {\n isStored: boolean;\n token: string;\n}\n\nexport async function boardCredential(\n settings: ResolvedDevtoolsOptions\n): Promise<BoardCredential | null> {\n if (settings.secretKey) {\n return { isStored: false, token: settings.secretKey };\n }\n const connection = await readConnection(settings.root, settings.apiUrl);\n return connection ? { isStored: true, token: connection.token } : null;\n}\n\nexport async function boardAccess(\n request: Request,\n settings: ResolvedDevtoolsOptions\n): Promise<BoardAccess> {\n if (settings.secretKey) {\n return { kind: \"secretKey\" };\n }\n const connection = await readConnection(settings.root, settings.apiUrl);\n if (connection) {\n return { kind: \"connected\", organizationName: connection.organizationName };\n }\n return {\n canConnect: isConnectableHost(new URL(request.url).hostname),\n kind: \"disconnected\",\n };\n}\n","const HTML_ESCAPES: Record<string, string> = {\n \"'\": \"&#39;\",\n '\"': \"&quot;\",\n \"&\": \"&amp;\",\n \"<\": \"&lt;\",\n \">\": \"&gt;\",\n};\nconst HTML_SPECIAL_CHARACTERS = /[&<>\"']/g;\n\nconst escapeHtml = (text: string): string =>\n text.replace(\n HTML_SPECIAL_CHARACTERS,\n (character) => HTML_ESCAPES[character] ?? character\n );\n\nexport function htmlPage(\n title: string,\n message: string,\n status: number\n): Response {\n const safeTitle = escapeHtml(title);\n const body = `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"><title>${safeTitle} · Reflet devtools</title></head>\n<body style=\"margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:system-ui,sans-serif;background:#fafafa;color:#18181b\">\n<main style=\"max-width:28rem;padding:1.5rem\">\n<h1 style=\"font-size:1.25rem;margin:0 0 .5rem\">${safeTitle}</h1>\n<p style=\"margin:0;line-height:1.5;color:#52525b\">${escapeHtml(message)}</p>\n</main>\n</body>\n</html>\n`;\n return new Response(body, {\n headers: {\n \"cache-control\": \"no-store\",\n \"content-security-policy\":\n \"default-src 'none'; style-src 'unsafe-inline'\",\n \"content-type\": \"text/html; charset=utf-8\",\n \"referrer-policy\": \"no-referrer\",\n \"x-content-type-options\": \"nosniff\",\n },\n status,\n });\n}\n","import { createHash, randomBytes } from \"node:crypto\";\n\ninterface ConnectBinding {\n apiUrl: string;\n redirectUri: string;\n root: string;\n}\n\ninterface PendingConnect {\n binding: ConnectBinding;\n codeVerifier: string;\n startedAt: number;\n}\n\nconst PENDING_CONNECT_TTL_MS = 10 * 60_000;\nconst MAX_PENDING_CONNECTS = 10;\ndeclare global {\n // Preserve pending connects across dev server module reloads.\n var refletDevtoolsPendingConnects: Map<string, PendingConnect> | undefined;\n}\n\nfunction pendingConnects(): Map<string, PendingConnect> {\n globalThis.refletDevtoolsPendingConnects ??= new Map();\n return globalThis.refletDevtoolsPendingConnects;\n}\n\nexport function beginConnect(binding: ConnectBinding): {\n codeChallenge: string;\n codeVerifier: string;\n state: string;\n} {\n const state = randomBytes(32).toString(\"base64url\");\n const codeVerifier = randomBytes(32).toString(\"base64url\");\n const codeChallenge = createHash(\"sha256\")\n .update(codeVerifier)\n .digest(\"base64url\");\n\n const pending = pendingConnects();\n while (pending.size >= MAX_PENDING_CONNECTS) {\n const oldestState = pending.keys().next().value;\n if (oldestState === undefined) {\n break;\n }\n pending.delete(oldestState);\n }\n pending.set(state, { binding, codeVerifier, startedAt: Date.now() });\n\n return { codeChallenge, codeVerifier, state };\n}\n\nexport function takeConnect(\n state: string,\n binding: ConnectBinding\n): { codeVerifier: string } | null {\n const pending = pendingConnects();\n const connect = pending.get(state);\n if (!connect) {\n return null;\n }\n if (Date.now() - connect.startedAt > PENDING_CONNECT_TTL_MS) {\n pending.delete(state);\n return null;\n }\n const matchesServer =\n connect.binding.root === binding.root &&\n connect.binding.apiUrl === binding.apiUrl &&\n connect.binding.redirectUri === binding.redirectUri;\n if (!matchesServer) {\n return null;\n }\n pending.delete(state);\n return { codeVerifier: connect.codeVerifier };\n}\n","import { z } from \"zod\";\nimport {\n type ConnectStartResponse,\n DEVTOOLS_ENDPOINTS,\n DEVTOOLS_ROUTE_BASE,\n type DisconnectResponse,\n} from \"../../protocol\";\nimport { errorResponse, jsonResponse } from \"../json-response\";\nimport type { ResolvedDevtoolsOptions } from \"../options\";\nimport { isConnectableHost } from \"./connectable-host\";\nimport { removeConnection, saveConnection } from \"./connection-store\";\nimport { htmlPage } from \"./html-page\";\nimport { beginConnect, takeConnect } from \"./pending-connects\";\n\nconst publicKeySchema = z.object({\n publicKey: z.string().startsWith(\"fb_pub_\").max(200),\n});\nconst tokenExchangeSchema = z.object({\n organizationName: z.string(),\n token: z.string().startsWith(\"fb_dev_\"),\n});\nconst upstreamErrorSchema = z.object({ error: z.string() });\n\nconst redirectUriFor = (request: Request): string =>\n `${new URL(request.url).origin}${DEVTOOLS_ROUTE_BASE}${DEVTOOLS_ENDPOINTS.connectCallback}`;\n\nasync function readPublicKey(request: Request): Promise<string | null> {\n const parsed = publicKeySchema.safeParse(\n await request.json().catch(() => null)\n );\n return parsed.success ? parsed.data.publicKey : null;\n}\n\nasync function readJson(response: Response): Promise<unknown> {\n try {\n return await response.json();\n } catch {\n return null;\n }\n}\n\nexport async function startConnect(\n request: Request,\n settings: ResolvedDevtoolsOptions\n): Promise<Response> {\n const publicKey = await readPublicKey(request);\n if (!publicKey) {\n return errorResponse(\n \"Pass the widget's fb_pub_ public key to connect.\",\n 400\n );\n }\n if (settings.secretKey) {\n return errorResponse(\n \"REFLET_SECRET_KEY is set, so this dev server already reaches your board.\",\n 409\n );\n }\n if (!isConnectableHost(new URL(request.url).hostname)) {\n return errorResponse(\n \"Connecting from the browser works on localhost, *.localhost and *.test. Set REFLET_SECRET_KEY instead.\",\n 403\n );\n }\n\n const { codeChallenge, state } = beginConnect({\n apiUrl: settings.apiUrl,\n redirectUri: redirectUriFor(request),\n root: settings.root,\n });\n const authorizeUrl = new URL(\"/auth/devtools\", settings.appUrl);\n authorizeUrl.searchParams.set(\"public_key\", publicKey);\n authorizeUrl.searchParams.set(\"redirect_uri\", redirectUriFor(request));\n authorizeUrl.searchParams.set(\"state\", state);\n authorizeUrl.searchParams.set(\"code_challenge\", codeChallenge);\n return jsonResponse({\n authorizeUrl: authorizeUrl.toString(),\n } satisfies ConnectStartResponse);\n}\n\nexport async function finishConnect(\n request: Request,\n settings: ResolvedDevtoolsOptions\n): Promise<Response> {\n const { searchParams } = new URL(request.url);\n const code = searchParams.get(\"code\");\n const state = searchParams.get(\"state\");\n const pending =\n code && state\n ? takeConnect(state, {\n apiUrl: settings.apiUrl,\n redirectUri: redirectUriFor(request),\n root: settings.root,\n })\n : null;\n if (!(code && pending)) {\n return htmlPage(\n \"Link expired\",\n \"This connect link expired or was already used. Start again from the Board tab.\",\n 400\n );\n }\n\n let upstream: Response;\n try {\n upstream = await fetch(`${settings.apiUrl}/api/v1/devtools/token`, {\n body: JSON.stringify({\n code,\n codeVerifier: pending.codeVerifier,\n redirectUri: redirectUriFor(request),\n }),\n headers: {\n Accept: \"application/json\",\n \"Content-Type\": \"application/json\",\n },\n method: \"POST\",\n signal: AbortSignal.timeout(10_000),\n });\n } catch {\n return htmlPage(\n \"Connection failed\",\n `Could not reach Reflet at ${settings.apiUrl}.`,\n 502\n );\n }\n\n const payload = await readJson(upstream);\n if (!upstream.ok) {\n return htmlPage(\n \"Connection refused\",\n upstreamErrorSchema.safeParse(payload).data?.error ??\n \"Reflet refused this connect code.\",\n 400\n );\n }\n const exchange = tokenExchangeSchema.safeParse(payload);\n if (!exchange.success) {\n return htmlPage(\n \"Connection failed\",\n \"Reflet answered with an unexpected response.\",\n 502\n );\n }\n\n await saveConnection(settings.root, {\n apiUrl: settings.apiUrl,\n organizationName: exchange.data.organizationName,\n token: exchange.data.token,\n });\n return htmlPage(\n \"Connected\",\n `Connected to ${exchange.data.organizationName}. Go back to your app: the Board tab is ready.`,\n 200\n );\n}\n\nasync function revokeUpstream(apiUrl: string, token: string): Promise<boolean> {\n try {\n const response = await fetch(`${apiUrl}/api/v1/devtools/revoke`, {\n headers: { Authorization: `Bearer ${token}` },\n method: \"POST\",\n signal: AbortSignal.timeout(10_000),\n });\n return response.ok;\n } catch {\n return false;\n }\n}\n\nexport async function disconnect(\n settings: ResolvedDevtoolsOptions\n): Promise<Response> {\n const removed = await removeConnection(settings.root, settings.apiUrl);\n const revoked = removed\n ? await revokeUpstream(removed.apiUrl, removed.token)\n : false;\n return jsonResponse({ revoked } satisfies DisconnectResponse);\n}\n","import { resolve } from \"node:path\";\nimport { DEFAULT_API_URL } from \"../../client\";\nimport { EDITORS, type EditorId } from \"../protocol\";\n\nexport interface DevtoolsServerOptions {\n /** Dev hostnames besides localhost, e.g. `app.test`. Falls back to REFLET_DEVTOOLS_HOSTS, comma-separated. */\n allowedHosts?: string[];\n /** Reflet API origin. Falls back to REFLET_API_URL, then the hosted API. */\n apiUrl?: string;\n /** Reflet web app origin for Connect. Falls back to REFLET_APP_URL, then https://www.reflet.app. */\n appUrl?: string;\n /** Editor that \"open in editor\" links target. Falls back to REFLET_EDITOR. */\n editor?: EditorId;\n /** App directory source paths resolve against. Defaults to the working directory. */\n root?: string;\n /** Falls back to REFLET_SECRET_KEY. Never sent to the browser. */\n secretKey?: string;\n}\n\nexport type DevtoolsEnv = Readonly<Record<string, string | undefined>>;\n\nexport interface ResolvedDevtoolsOptions {\n allowedHosts: string[];\n apiUrl: string;\n appUrl: string;\n editor: EditorId;\n root: string;\n secretKey: string | null;\n}\n\nconst DEFAULT_APP_URL = \"https://www.reflet.app\";\nconst DEFAULT_EDITOR: EditorId = \"vscode\";\nconst TRAILING_SLASHES = /\\/+$/;\n\nfunction isEditorId(value: string | undefined): value is EditorId {\n return EDITORS.some((editor) => editor === value);\n}\n\nfunction presentValue(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed ? trimmed : undefined;\n}\n\nfunction resolveEditor(\n optionEditor: string | undefined,\n envEditor: string | undefined\n): EditorId {\n if (isEditorId(optionEditor)) {\n return optionEditor;\n }\n const normalizedEnvEditor = presentValue(envEditor)?.toLowerCase();\n return isEditorId(normalizedEnvEditor) ? normalizedEnvEditor : DEFAULT_EDITOR;\n}\n\nexport function resolveDevtoolsOptions(\n options: DevtoolsServerOptions,\n env: DevtoolsEnv\n): ResolvedDevtoolsOptions {\n const apiUrl =\n presentValue(options.apiUrl) ??\n presentValue(env.REFLET_API_URL) ??\n DEFAULT_API_URL;\n const appUrl =\n presentValue(options.appUrl) ??\n presentValue(env.REFLET_APP_URL) ??\n DEFAULT_APP_URL;\n\n return {\n allowedHosts: (\n options.allowedHosts ??\n presentValue(env.REFLET_DEVTOOLS_HOSTS)?.split(\",\") ??\n []\n )\n .map((host) => host.trim().toLowerCase())\n .filter(Boolean),\n apiUrl: apiUrl.replace(TRAILING_SLASHES, \"\"),\n appUrl: appUrl.replace(TRAILING_SLASHES, \"\"),\n editor: resolveEditor(options.editor, env.REFLET_EDITOR),\n root: resolve(presentValue(options.root) ?? process.cwd()),\n secretKey:\n presentValue(options.secretKey) ??\n presentValue(env.REFLET_SECRET_KEY) ??\n null,\n };\n}\n","import { PROXIED_API_PATHS } from \"../protocol\";\nimport { errorResponse, JSON_RESPONSE_HEADERS } from \"./json-response\";\n\ntype ProxiedApiPath = (typeof PROXIED_API_PATHS)[number];\n\nconst CREATE_FEEDBACK_PATH: ProxiedApiPath = \"/api/v1/feedback/create\";\nconst MISSING_BOARD_ACCESS_ERROR =\n \"Connect to Reflet from the devtools Board tab, or set REFLET_SECRET_KEY in the dev server environment.\";\nconst BODYLESS_STATUSES = [204, 205, 304];\n\nexport interface RefletProxyTarget {\n apiUrl: string;\n token: string | null;\n}\n\ntype ForwardedBody = { body: string | undefined } | { error: string };\n\nfunction isProxiedApiPath(path: string): path is ProxiedApiPath {\n return PROXIED_API_PATHS.some((proxiedPath) => proxiedPath === path);\n}\n\nfunction isJsonObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nasync function readForwardedBody(\n request: Request,\n apiPath: ProxiedApiPath\n): Promise<ForwardedBody> {\n if (request.method !== \"POST\") {\n return { body: undefined };\n }\n const text = await request.text();\n if (apiPath !== CREATE_FEEDBACK_PATH) {\n return { body: text || undefined };\n }\n\n let payload: unknown;\n try {\n payload = JSON.parse(text);\n } catch {\n return { error: \"Feedback must be a JSON object.\" };\n }\n if (!isJsonObject(payload)) {\n return { error: \"Feedback must be a JSON object.\" };\n }\n return { body: JSON.stringify({ ...payload, internal: true }) };\n}\n\n/** Forwards an allowlisted Reflet API call with the board token; devtools notes are always internal. */\nexport async function proxyToReflet(\n request: Request,\n apiPath: string,\n target: RefletProxyTarget\n): Promise<Response> {\n if (!isProxiedApiPath(apiPath)) {\n return errorResponse(\n `Reflet devtools does not forward ${apiPath || \"/\"}.`,\n 404\n );\n }\n if (!target.token) {\n return errorResponse(MISSING_BOARD_ACCESS_ERROR, 503);\n }\n if (request.method !== \"GET\" && request.method !== \"POST\") {\n return errorResponse(`${request.method} is not supported here.`, 405);\n }\n\n const forwarded = await readForwardedBody(request, apiPath);\n if (\"error\" in forwarded) {\n return errorResponse(forwarded.error, 400);\n }\n\n const { search } = new URL(request.url);\n let upstream: Response;\n try {\n upstream = await fetch(`${target.apiUrl}${apiPath}${search}`, {\n body: forwarded.body,\n headers: {\n Accept: \"application/json\",\n Authorization: `Bearer ${target.token}`,\n \"Content-Type\": \"application/json\",\n },\n method: request.method,\n signal: AbortSignal.timeout(15_000),\n });\n } catch {\n return errorResponse(`Could not reach Reflet at ${target.apiUrl}.`, 502);\n }\n\n return new Response(\n BODYLESS_STATUSES.includes(upstream.status) ? null : upstream.body,\n { headers: JSON_RESPONSE_HEADERS, status: upstream.status }\n );\n}\n","import { type ExecFileException, execFile } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport type { CodeMatch, CodeSearchResult } from \"../../protocol\";\nimport { type DevtoolsOutcome, failure } from \"../json-response\";\n\nconst MIN_QUERY_LENGTH = 2;\nconst MAX_QUERY_LENGTH = 200;\nconst MAX_MATCHES = 20;\nconst PREVIEW_LENGTH = 160;\nconst PREVIEW_LEAD = 40;\nconst SEARCH_TIMEOUT_MS = 4000;\nconst MAX_OUTPUT_BYTES = 1_048_576;\nconst NO_MATCHES_EXIT_CODE = 1;\nconst NOT_A_REPOSITORY_EXIT_CODE = 128;\nconst SEARCHED_FILES = [\n \"*.tsx\",\n \"*.jsx\",\n \"*.ts\",\n \"*.js\",\n \"*.vue\",\n \"*.svelte\",\n \"*.astro\",\n \"*.mdx\",\n];\nconst LINE_BREAK = /[\\r\\n]/;\n\ninterface GitGrepRun {\n error: ExecFileException | null;\n stdout: string;\n}\n\nfunction runGitGrep(workspaceRoot: string, query: string): Promise<GitGrepRun> {\n const args = [\n \"grep\",\n \"-n\",\n \"-z\",\n \"-I\",\n \"-F\",\n \"--no-color\",\n \"--untracked\",\n \"-e\",\n query,\n \"--\",\n ...SEARCHED_FILES,\n ];\n return new Promise((resolveRun) => {\n execFile(\n \"git\",\n args,\n {\n cwd: workspaceRoot,\n encoding: \"utf8\",\n maxBuffer: MAX_OUTPUT_BYTES,\n timeout: SEARCH_TIMEOUT_MS,\n },\n (error, stdout) => resolveRun({ error, stdout })\n );\n });\n}\n\nfunction previewLine(content: string, query: string): string {\n const text = content.trim();\n if (text.length <= PREVIEW_LENGTH) {\n return text;\n }\n const matchIndex = text.indexOf(query);\n const start = Math.max(\n 0,\n Math.min(matchIndex - PREVIEW_LEAD, text.length - PREVIEW_LENGTH)\n );\n const end = start + PREVIEW_LENGTH;\n const head = start > 0 ? \"…\" : \"\";\n const tail = end < text.length ? \"…\" : \"\";\n return `${head}${text.slice(start, end)}${tail}`;\n}\n\nfunction parseMatches(\n stdout: string,\n workspaceRoot: string,\n query: string\n): CodeMatch[] {\n const matches: CodeMatch[] = [];\n for (const record of stdout.split(\"\\n\")) {\n const [path, lineText, content] = record.split(\"\\0\");\n const line = Number(lineText);\n const isComplete =\n path !== undefined && content !== undefined && Number.isInteger(line);\n if (isComplete && !path.split(\"/\").includes(\"node_modules\")) {\n matches.push({\n absolutePath: join(workspaceRoot, path),\n line,\n path,\n preview: previewLine(content, query),\n });\n if (matches.length === MAX_MATCHES) {\n break;\n }\n }\n }\n return matches;\n}\n\nfunction describeGitFailure(error: ExecFileException): DevtoolsOutcome<never> {\n if (error.code === \"ENOENT\") {\n return failure(\"Code search needs git installed on the dev server.\", 503);\n }\n if (error.code === NOT_A_REPOSITORY_EXIT_CODE) {\n return failure(\n \"Code search needs the app to live in a git repository.\",\n 503\n );\n }\n if (error.killed) {\n return failure(\"Code search timed out.\", 504);\n }\n return failure(`Code search failed: ${error.message}`, 500);\n}\n\nexport async function searchCode(\n workspaceRoot: string,\n rawQuery: string\n): Promise<DevtoolsOutcome<CodeSearchResult>> {\n const query = rawQuery.trim();\n if (query.length < MIN_QUERY_LENGTH || query.length > MAX_QUERY_LENGTH) {\n return failure(\n `Search for ${MIN_QUERY_LENGTH} to ${MAX_QUERY_LENGTH} characters.`,\n 400\n );\n }\n if (LINE_BREAK.test(query)) {\n return failure(\"Search for a single line of text.\", 400);\n }\n\n const { error, stdout } = await runGitGrep(workspaceRoot, query);\n const matches = parseMatches(stdout, workspaceRoot, query);\n const hasNoMatches = error?.code === NO_MATCHES_EXIT_CODE;\n if (!error || hasNoMatches || matches.length > 0) {\n return { ok: true, value: { matches } };\n }\n return describeGitFailure(error);\n}\n","import { existsSync, realpathSync } from \"node:fs\";\nimport { dirname, join, resolve } from \"node:path\";\n\nexport interface ProjectRoots {\n /** The app directory, where the dev server runs. */\n root: string;\n /** Nearest ancestor of the app directory holding `.git`, else the app directory. */\n workspaceRoot: string;\n}\n\nexport function resolveProjectRoots(appRoot: string): ProjectRoots {\n const resolvedRoot = resolve(appRoot);\n const root = existsSync(resolvedRoot)\n ? realpathSync(resolvedRoot)\n : resolvedRoot;\n\n for (let directory = root; ; directory = dirname(directory)) {\n if (existsSync(join(directory, \".git\"))) {\n return { root, workspaceRoot: directory };\n }\n if (dirname(directory) === directory) {\n return { root, workspaceRoot: root };\n }\n }\n}\n","import type { LineRange } from \"../../protocol\";\n\nconst FAILED = -1;\nconst MAX_TAG_LOOKBACK = 2;\nconst TAG_NAME_START = /[A-Za-z_$]/;\nconst TAG_NAME_CHAR = /[\\w$.:-]/;\nconst IDENTIFIER_CHAR = /[\\w$]/;\nconst WHITESPACE = /\\s/;\nconst JSX_EXPRESSION_LEAD = /[([{,;=?:&|!>]/;\nconst JSX_KEYWORD_LEADS = [\"return\", \"yield\", \"default\", \"await\"];\n\ninterface OpeningTag {\n end: number;\n name: string;\n selfClosing: boolean;\n}\n\nfunction isTagStart(code: string, index: number): boolean {\n if (code.charAt(index) !== \"<\") {\n return false;\n }\n const next = code.charAt(index + 1);\n return next === \">\" || TAG_NAME_START.test(next);\n}\n\nfunction startsJsxInExpression(code: string, index: number): boolean {\n let cursor = index - 1;\n while (cursor >= 0 && WHITESPACE.test(code.charAt(cursor))) {\n cursor -= 1;\n }\n if (cursor < 0) {\n return true;\n }\n const previous = code.charAt(cursor);\n if (JSX_EXPRESSION_LEAD.test(previous)) {\n return true;\n }\n let wordStart = cursor;\n while (wordStart > 0 && IDENTIFIER_CHAR.test(code.charAt(wordStart - 1))) {\n wordStart -= 1;\n }\n return JSX_KEYWORD_LEADS.includes(code.slice(wordStart, cursor + 1));\n}\n\nfunction skipJsString(code: string, index: number): number {\n const quote = code.charAt(index);\n let cursor = index + 1;\n while (cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"\\\\\") {\n cursor += 2;\n } else if (char === quote) {\n return cursor + 1;\n } else if (char === \"\\n\") {\n return index + 1;\n } else {\n cursor += 1;\n }\n }\n return index + 1;\n}\n\nfunction skipTemplate(code: string, index: number): number {\n let cursor = index + 1;\n while (cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"\\\\\") {\n cursor += 2;\n } else if (char === \"`\") {\n return cursor + 1;\n } else if (char === \"$\" && code.charAt(cursor + 1) === \"{\") {\n cursor = skipJsExpression(code, cursor + 1);\n if (cursor === FAILED) {\n return FAILED;\n }\n } else {\n cursor += 1;\n }\n }\n return FAILED;\n}\n\nfunction skipJsToken(code: string, index: number): number {\n const char = code.charAt(index);\n const next = code.charAt(index + 1);\n if (char === '\"' || char === \"'\") {\n return skipJsString(code, index);\n }\n if (char === \"`\") {\n return skipTemplate(code, index);\n }\n if (char === \"/\" && next === \"/\") {\n const lineEnd = code.indexOf(\"\\n\", index);\n return lineEnd === -1 ? code.length : lineEnd;\n }\n if (char === \"/\" && next === \"*\") {\n const commentEnd = code.indexOf(\"*/\", index + 2);\n return commentEnd === -1 ? FAILED : commentEnd + 2;\n }\n if (isTagStart(code, index) && startsJsxInExpression(code, index)) {\n return skipElement(code, index);\n }\n return index + 1;\n}\n\nfunction skipJsExpression(code: string, index: number): number {\n let depth = 0;\n let cursor = index;\n while (cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"{\" || char === \"}\") {\n depth += char === \"{\" ? 1 : -1;\n cursor += 1;\n if (depth === 0) {\n return cursor;\n }\n } else {\n cursor = skipJsToken(code, cursor);\n if (cursor === FAILED) {\n return FAILED;\n }\n }\n }\n return FAILED;\n}\n\nfunction skipTypeArguments(code: string, index: number): number {\n let depth = 0;\n for (let cursor = index; cursor < code.length; cursor += 1) {\n const char = code.charAt(cursor);\n if (char === \"<\") {\n depth += 1;\n } else if (char === \">\") {\n depth -= 1;\n if (depth === 0) {\n return cursor + 1;\n }\n }\n }\n return FAILED;\n}\n\nfunction skipAttributeToken(code: string, index: number): number {\n const char = code.charAt(index);\n if (char === \"{\") {\n return skipJsExpression(code, index);\n }\n if (char === '\"' || char === \"'\") {\n const closingQuote = code.indexOf(char, index + 1);\n return closingQuote === -1 ? FAILED : closingQuote + 1;\n }\n return index + 1;\n}\n\nfunction readTagName(code: string, index: number): number {\n let cursor = index;\n while (TAG_NAME_CHAR.test(code.charAt(cursor))) {\n cursor += 1;\n }\n return cursor;\n}\n\nfunction readOpeningTag(code: string, index: number): OpeningTag | null {\n const nameEnd = readTagName(code, index + 1);\n const name = code.slice(index + 1, nameEnd);\n let cursor =\n code.charAt(nameEnd) === \"<\" ? skipTypeArguments(code, nameEnd) : nameEnd;\n\n while (cursor !== FAILED && cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \">\") {\n return { end: cursor + 1, name, selfClosing: false };\n }\n if (char === \"/\" && code.charAt(cursor + 1) === \">\") {\n return { end: cursor + 2, name, selfClosing: true };\n }\n cursor = skipAttributeToken(code, cursor);\n }\n return null;\n}\n\nfunction readClosingTagEnd(code: string, index: number, name: string): number {\n let cursor = index + 2;\n while (WHITESPACE.test(code.charAt(cursor))) {\n cursor += 1;\n }\n const nameEnd = readTagName(code, cursor);\n if (code.slice(cursor, nameEnd) !== name) {\n return FAILED;\n }\n cursor = nameEnd;\n while (WHITESPACE.test(code.charAt(cursor))) {\n cursor += 1;\n }\n return code.charAt(cursor) === \">\" ? cursor + 1 : FAILED;\n}\n\nfunction skipChildren(code: string, index: number, name: string): number {\n let cursor = index;\n while (cursor !== FAILED && cursor < code.length) {\n const char = code.charAt(cursor);\n if (char === \"<\" && code.charAt(cursor + 1) === \"/\") {\n return readClosingTagEnd(code, cursor, name);\n }\n if (char === \"{\") {\n cursor = skipJsExpression(code, cursor);\n } else if (isTagStart(code, cursor)) {\n cursor = skipElement(code, cursor);\n } else {\n cursor += 1;\n }\n }\n return FAILED;\n}\n\nfunction skipElement(code: string, index: number): number {\n const tag = readOpeningTag(code, index);\n if (!tag) {\n return FAILED;\n }\n return tag.selfClosing ? tag.end : skipChildren(code, tag.end, tag.name);\n}\n\nfunction lineOffset(code: string, line: number): number {\n if (!Number.isInteger(line) || line < 1) {\n return FAILED;\n }\n let offset = 0;\n for (let current = 1; current < line; current += 1) {\n const newline = code.indexOf(\"\\n\", offset);\n if (newline === -1) {\n return FAILED;\n }\n offset = newline + 1;\n }\n return offset;\n}\n\nfunction findTagStart(\n code: string,\n lineStart: number,\n lineEnd: number,\n column: number\n): number {\n const origin = Math.min(lineStart + Math.max(column, 0), lineEnd);\n for (let back = 0; back <= MAX_TAG_LOOKBACK; back += 1) {\n const candidate = origin - back;\n if (candidate >= lineStart && isTagStart(code, candidate)) {\n return candidate;\n }\n }\n for (let candidate = origin + 1; candidate < lineEnd; candidate += 1) {\n if (isTagStart(code, candidate)) {\n return candidate;\n }\n }\n return FAILED;\n}\n\nfunction countNewlines(code: string, from: number, to: number): number {\n let count = 0;\n let newline = code.indexOf(\"\\n\", from);\n while (newline !== -1 && newline < to) {\n count += 1;\n newline = code.indexOf(\"\\n\", newline + 1);\n }\n return count;\n}\n\n/** Lines spanned by the JSX element at a 1-based line and 0-based column. */\nexport function jsxElementLines(\n code: string,\n line: number,\n column: number\n): LineRange | null {\n const lineStart = lineOffset(code, line);\n if (lineStart === FAILED) {\n return null;\n }\n const newline = code.indexOf(\"\\n\", lineStart);\n const lineEnd = newline === -1 ? code.length : newline;\n const tagStart = findTagStart(code, lineStart, lineEnd, column);\n if (tagStart === FAILED) {\n return null;\n }\n const elementEnd = skipElement(code, tagStart);\n if (elementEnd === FAILED) {\n return null;\n }\n return {\n end: line + countNewlines(code, tagStart, elementEnd - 1),\n start: line,\n };\n}\n","import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { SourceMap } from \"node:module\";\nimport { dirname, join, relative, sep } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { type DevtoolsOutcome, failure } from \"../json-response\";\nimport type { SourceRequest } from \"./source-file\";\n\nconst SERVER_FRAME_PREFIX = /^(?:about|rsc):\\/\\/React\\/Server\\//;\nconst QUERY_OR_HASH = /[?#].*$/;\nconst SOURCE_MAPPING_URL = /\\/\\/# sourceMappingURL=(\\S+)\\s*$/;\nconst MAX_MAP_BYTES = 50 * 1_048_576;\n\nconst mapCache = new Map<string, { map: SourceMap; mtimeMs: number }>();\n\nexport function isServerFrame(fileName: string): boolean {\n return SERVER_FRAME_PREFIX.test(fileName);\n}\n\nfunction isInside(path: string, root: string): boolean {\n const fromRoot = relative(root, path);\n return (\n fromRoot !== \"\" &&\n !fromRoot.startsWith(\"..\") &&\n !fromRoot.includes(`..${sep}`)\n );\n}\n\nasync function mapPathFor(chunkPath: string): Promise<string> {\n const code = await readFile(chunkPath, \"utf8\");\n const declared = SOURCE_MAPPING_URL.exec(code)?.[1];\n return declared\n ? join(dirname(chunkPath), decodeURIComponent(declared))\n : `${chunkPath}.map`;\n}\n\nasync function loadMap(mapPath: string): Promise<SourceMap> {\n const { mtimeMs, size } = await stat(mapPath);\n if (size > MAX_MAP_BYTES) {\n throw new Error(\"source map too large\");\n }\n const cached = mapCache.get(mapPath);\n if (cached?.mtimeMs === mtimeMs) {\n return cached.map;\n }\n const map = new SourceMap(JSON.parse(await readFile(mapPath, \"utf8\")));\n mapCache.set(mapPath, { map, mtimeMs });\n return map;\n}\n\n/**\n * Server Components stamp their elements with frames into the dev server's\n * own chunks (`about://React/Server/file:///…/.next/…`); the browser cannot\n * fetch those maps, so they are resolved here from disk.\n */\nexport async function mapServerFrame(\n request: SourceRequest,\n workspaceRoot: string\n): Promise<DevtoolsOutcome<SourceRequest>> {\n const unreadable = failure<SourceRequest>(\n \"This element comes from a Server Component whose source map could not be read.\",\n 404\n );\n if (request.line === null) {\n return unreadable;\n }\n try {\n const chunkUrl = request.fileName\n .replace(SERVER_FRAME_PREFIX, \"\")\n .replace(QUERY_OR_HASH, \"\");\n const chunkPath = await realpath(fileURLToPath(chunkUrl));\n if (!isInside(chunkPath, workspaceRoot)) {\n return failure<SourceRequest>(\n \"Reflet devtools only reads files inside your repository.\",\n 403\n );\n }\n const map = await loadMap(await mapPathFor(chunkPath));\n const origin = map.findOrigin(request.line, (request.column ?? 0) + 1);\n if (!(\"fileName\" in origin)) {\n return unreadable;\n }\n return {\n ok: true,\n value: {\n column: origin.columnNumber - 1,\n fileName: origin.fileName,\n line: origin.lineNumber,\n },\n };\n } catch {\n return unreadable;\n }\n}\n","import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { extname, isAbsolute, join, relative, sep } from \"node:path\";\nimport type { SourceFile } from \"../../protocol\";\nimport { type DevtoolsOutcome, failure } from \"../json-response\";\nimport { jsxElementLines } from \"./jsx-range\";\nimport type { ProjectRoots } from \"./project-roots\";\nimport { isServerFrame, mapServerFrame } from \"./server-frame\";\n\nconst MAX_SOURCE_BYTES = 1_048_576;\nconst MAX_FILE_NAME_LENGTH = 2048;\nconst MAX_NORMALIZE_PASSES = 8;\nconst SOURCE_EXTENSIONS = [\n \".astro\",\n \".cjs\",\n \".js\",\n \".jsx\",\n \".mdx\",\n \".mjs\",\n \".svelte\",\n \".ts\",\n \".tsx\",\n \".vue\",\n];\nconst EXTENSION_REFUSAL = `Reflet devtools only reads source files (${SOURCE_EXTENSIONS.join(\", \")}).`;\nconst OUTSIDE_WORKSPACE_REFUSAL =\n \"Reflet devtools only reads files inside your repository.\";\nconst NODE_MODULES_REFUSAL =\n \"Reflet devtools does not read files inside node_modules.\";\nconst QUERY_OR_HASH = /[?#].*$/;\nconst TURBOPACK_MODULE_SUFFIX = /(?: \\[[\\w-]+\\])+(?: \\([\\w-]+\\))*$/;\nconst URL_PREFIXES = [/^file:\\/\\//, /^https?:\\/\\/[^/]*/];\nconst BUNDLER_PREFIXES = [\n /^rsc:\\/\\/React\\/[^/]+\\//,\n /^webpack-internal:\\/{2,}/,\n /^webpack:\\/\\/[^/]*\\//,\n /^turbopack:\\/{2,}/,\n /^\\[project\\]\\//,\n /^\\(app-pages-browser\\)\\//,\n /^\\([\\w-]+\\)\\/(?=\\.\\/)/,\n /^\\/@fs(?=\\/)/,\n /^\\.\\//,\n];\n\ninterface LocatedFile {\n realPath: string;\n relativePath: string;\n size: number;\n}\n\ntype CandidateCheck =\n | ({ kind: \"found\" } & LocatedFile)\n | { kind: \"missing\" }\n | { kind: \"refused\"; reason: string };\n\nexport interface SourceRequest {\n column: number | null;\n fileName: string;\n line: number | null;\n}\n\nfunction decodePath(path: string): string {\n try {\n return decodeURIComponent(path);\n } catch {\n return path;\n }\n}\n\nfunction stripFirstPrefix(fileName: string, prefixes: RegExp[]): string {\n for (const prefix of prefixes) {\n if (prefix.test(fileName)) {\n return fileName.replace(prefix, \"\");\n }\n }\n return fileName;\n}\n\n/** Turns a file name from a React debug stack into a filesystem path. */\nexport function normalizeSourceFileName(rawFileName: string): string {\n let fileName = rawFileName\n .trim()\n .replace(QUERY_OR_HASH, \"\")\n .replace(TURBOPACK_MODULE_SUFFIX, \"\");\n let isUrlEncoded = false;\n\n for (let pass = 0; pass < MAX_NORMALIZE_PASSES; pass += 1) {\n const withoutUrl = stripFirstPrefix(fileName, URL_PREFIXES);\n isUrlEncoded ||= withoutUrl !== fileName;\n const stripped = stripFirstPrefix(withoutUrl, BUNDLER_PREFIXES);\n if (stripped === fileName) {\n break;\n }\n fileName = stripped;\n }\n\n return isUrlEncoded ? decodePath(fileName) : fileName;\n}\n\nfunction candidatePaths(fileName: string, roots: ProjectRoots): string[] {\n const candidates = [\n join(roots.root, fileName),\n join(roots.workspaceRoot, fileName),\n ];\n if (isAbsolute(fileName)) {\n candidates.unshift(fileName);\n }\n return [...new Set(candidates)];\n}\n\nfunction isSourceExtension(path: string): boolean {\n return SOURCE_EXTENSIONS.includes(extname(path).toLowerCase());\n}\n\nfunction workspaceRelativePath(\n realPath: string,\n workspaceRoot: string\n): string | null {\n const relativePath = relative(workspaceRoot, realPath);\n const escapes =\n relativePath === \"\" ||\n relativePath === \"..\" ||\n relativePath.startsWith(`..${sep}`) ||\n isAbsolute(relativePath);\n return escapes ? null : relativePath.split(sep).join(\"/\");\n}\n\nasync function checkCandidate(\n candidate: string,\n workspaceRoot: string\n): Promise<CandidateCheck> {\n let realPath: string;\n let size: number;\n try {\n realPath = await realpath(candidate);\n const stats = await stat(realPath);\n if (!stats.isFile()) {\n return { kind: \"missing\" };\n }\n size = stats.size;\n } catch {\n return { kind: \"missing\" };\n }\n\n const relativePath = workspaceRelativePath(realPath, workspaceRoot);\n if (relativePath === null) {\n return { kind: \"refused\", reason: OUTSIDE_WORKSPACE_REFUSAL };\n }\n if (relativePath.split(\"/\").includes(\"node_modules\")) {\n return { kind: \"refused\", reason: NODE_MODULES_REFUSAL };\n }\n if (!isSourceExtension(realPath)) {\n return { kind: \"refused\", reason: EXTENSION_REFUSAL };\n }\n return { kind: \"found\", realPath, relativePath, size };\n}\n\nasync function locateSourceFile(\n fileName: string,\n roots: ProjectRoots\n): Promise<DevtoolsOutcome<LocatedFile>> {\n let refusal: string | null = null;\n for (const candidate of candidatePaths(fileName, roots)) {\n const check = await checkCandidate(candidate, roots.workspaceRoot);\n if (check.kind === \"found\") {\n return { ok: true, value: check };\n }\n if (check.kind === \"refused\") {\n refusal ??= check.reason;\n }\n }\n if (refusal) {\n return failure(refusal, 403);\n }\n return failure(`Could not find ${fileName} in ${roots.workspaceRoot}.`, 404);\n}\n\n/** Server-map columns can point past the line; the element then starts at its first tag. */\nfunction columnWithinLine(code: string, line: number, column: number | null) {\n const text = code.split(\"\\n\")[line - 1] ?? \"\";\n return column !== null && column < text.length ? column : null;\n}\n\nexport async function readSourceFile(\n frame: SourceRequest,\n roots: ProjectRoots\n): Promise<DevtoolsOutcome<SourceFile>> {\n if (\n frame.fileName.length > MAX_FILE_NAME_LENGTH ||\n frame.fileName.includes(\"\\0\")\n ) {\n return failure(\"That source file name is not a valid path.\", 400);\n }\n const mapped = isServerFrame(frame.fileName)\n ? await mapServerFrame(frame, roots.workspaceRoot)\n : { ok: true as const, value: frame };\n if (!mapped.ok) {\n return mapped;\n }\n const request = mapped.value;\n const fileName = normalizeSourceFileName(request.fileName);\n if (!fileName) {\n return failure(\"A source file name is required.\", 400);\n }\n if (!isSourceExtension(fileName)) {\n return failure(EXTENSION_REFUSAL, 403);\n }\n\n const located = await locateSourceFile(fileName, roots);\n if (!located.ok) {\n return located;\n }\n const { realPath, relativePath, size } = located.value;\n if (size > MAX_SOURCE_BYTES) {\n return failure(\n `${relativePath} is larger than 1 MB, too big to preview.`,\n 413\n );\n }\n\n const code = await readFile(realPath, \"utf8\");\n const column =\n request.line === null\n ? request.column\n : columnWithinLine(code, request.line, request.column);\n const elementLines =\n request.line === null\n ? null\n : jsxElementLines(code, request.line, column ?? 0);\n\n return {\n ok: true,\n value: {\n absolutePath: realPath,\n code,\n column,\n elementLines,\n line: request.line,\n path: relativePath,\n },\n };\n}\n","import {\n DEVTOOLS_ENDPOINTS,\n DEVTOOLS_ROUTE_BASE,\n DEVTOOLS_STATUS_MARKER,\n type DevtoolsStatus,\n} from \"../protocol\";\nimport { boardAccess, boardCredential } from \"./connect/board-credential\";\nimport {\n disconnect,\n finishConnect,\n startConnect,\n} from \"./connect/connect-routes\";\nimport { removeConnection } from \"./connect/connection-store\";\nimport {\n type DevtoolsOutcome,\n errorResponse,\n jsonResponse,\n} from \"./json-response\";\nimport {\n type DevtoolsEnv,\n type DevtoolsServerOptions,\n type ResolvedDevtoolsOptions,\n resolveDevtoolsOptions,\n} from \"./options\";\nimport { proxyToReflet } from \"./reflet-proxy\";\nimport { rejectUntrustedCaller, rejectUntrustedHost } from \"./request-guard\";\nimport { searchCode } from \"./source/code-search\";\nimport { type ProjectRoots, resolveProjectRoots } from \"./source/project-roots\";\nimport { readSourceFile } from \"./source/source-file\";\n\nexport type DevtoolsHandler = (request: Request) => Promise<Response>;\n\nconst TRAILING_SLASHES = /\\/+$/;\n\nconst ENDPOINT_METHODS: Record<string, \"GET\" | \"POST\"> = {\n [DEVTOOLS_ENDPOINTS.connectCallback]: \"GET\",\n [DEVTOOLS_ENDPOINTS.connectStart]: \"POST\",\n [DEVTOOLS_ENDPOINTS.disconnect]: \"POST\",\n [DEVTOOLS_ENDPOINTS.search]: \"GET\",\n [DEVTOOLS_ENDPOINTS.source]: \"GET\",\n [DEVTOOLS_ENDPOINTS.status]: \"GET\",\n};\n\nfunction outcomeResponse<T>(outcome: DevtoolsOutcome<T>): Response {\n return outcome.ok\n ? jsonResponse(outcome.value)\n : errorResponse(outcome.error, outcome.status);\n}\n\nfunction routeSubPath(pathname: string): string | null {\n const baseIndex = pathname.indexOf(DEVTOOLS_ROUTE_BASE);\n if (baseIndex === -1) {\n return null;\n }\n return pathname\n .slice(baseIndex + DEVTOOLS_ROUTE_BASE.length)\n .replace(TRAILING_SLASHES, \"\");\n}\n\nfunction readInteger(value: string | null, minimum: number): number | null {\n if (value === null || value === \"\") {\n return null;\n }\n const parsed = Number(value);\n return Number.isInteger(parsed) && parsed >= minimum ? parsed : null;\n}\n\nasync function proxyWithBoardCredential(\n request: Request,\n apiPath: string,\n settings: ResolvedDevtoolsOptions\n): Promise<Response> {\n const credential = await boardCredential(settings);\n const response = await proxyToReflet(request, apiPath, {\n apiUrl: settings.apiUrl,\n token: credential?.token ?? null,\n });\n if (credential?.isStored && response.status === 401) {\n await removeConnection(settings.root, settings.apiUrl, credential.token);\n }\n return response;\n}\n\nasync function routeRequest(\n request: Request,\n subPath: string | null,\n settings: ResolvedDevtoolsOptions,\n projectRoots: () => ProjectRoots\n): Promise<Response> {\n const url = new URL(request.url);\n\n if (subPath?.startsWith(`${DEVTOOLS_ENDPOINTS.proxy}/`)) {\n const apiPath = subPath.slice(DEVTOOLS_ENDPOINTS.proxy.length);\n return await proxyWithBoardCredential(request, apiPath, settings);\n }\n\n const requiredMethod = subPath ? ENDPOINT_METHODS[subPath] : undefined;\n if (requiredMethod && request.method !== requiredMethod) {\n return errorResponse(\n `${subPath} only answers ${requiredMethod} requests.`,\n 405\n );\n }\n\n switch (subPath) {\n case DEVTOOLS_ENDPOINTS.status:\n return jsonResponse({\n board: await boardAccess(request, settings),\n editor: settings.editor,\n marker: DEVTOOLS_STATUS_MARKER,\n } satisfies DevtoolsStatus);\n case DEVTOOLS_ENDPOINTS.connectStart:\n return await startConnect(request, settings);\n case DEVTOOLS_ENDPOINTS.connectCallback:\n return await finishConnect(request, settings);\n case DEVTOOLS_ENDPOINTS.disconnect:\n return await disconnect(settings);\n case DEVTOOLS_ENDPOINTS.source:\n return outcomeResponse(\n await readSourceFile(\n {\n column: readInteger(url.searchParams.get(\"column\"), 0),\n fileName: url.searchParams.get(\"file\") ?? \"\",\n line: readInteger(url.searchParams.get(\"line\"), 1),\n },\n projectRoots()\n )\n );\n case DEVTOOLS_ENDPOINTS.search:\n return outcomeResponse(\n await searchCode(\n projectRoots().workspaceRoot,\n url.searchParams.get(\"q\") ?? \"\"\n )\n );\n default:\n return errorResponse(\n `No Reflet devtools endpoint at ${url.pathname}.`,\n 404\n );\n }\n}\n\nexport function createDevtoolsHandler(\n options: DevtoolsServerOptions = {},\n env: DevtoolsEnv = process.env\n): DevtoolsHandler {\n const settings = resolveDevtoolsOptions(options, env);\n let roots: ProjectRoots | null = null;\n const projectRoots = (): ProjectRoots => {\n roots ??= resolveProjectRoots(settings.root);\n return roots;\n };\n\n return async (request) => {\n const hostRejection = rejectUntrustedHost(request, settings.allowedHosts);\n if (hostRejection) {\n return hostRejection;\n }\n const subPath = routeSubPath(new URL(request.url).pathname);\n const isConnectCallback =\n request.method === \"GET\" &&\n subPath === DEVTOOLS_ENDPOINTS.connectCallback;\n const callerRejection = isConnectCallback\n ? null\n : rejectUntrustedCaller(request, settings.allowedHosts);\n if (callerRejection) {\n return callerRejection;\n }\n try {\n return await routeRequest(request, subPath, settings, projectRoots);\n } catch (error) {\n const reason = error instanceof Error ? error.message : String(error);\n return errorResponse(`Reflet devtools route failed: ${reason}`, 500);\n }\n };\n}\n"],"mappings":"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"}