reflet-sdk 0.7.4 → 0.7.5

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 (33) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +32 -1
  3. package/dist/desktop.d.ts +24 -0
  4. package/dist/desktop.d.ts.map +1 -0
  5. package/dist/desktop.js +118 -0
  6. package/dist/desktop.js.map +1 -0
  7. package/dist/{devtools-layer-CcTmlikp.js → devtools-layer-o1O55wu1.js} +3 -3
  8. package/dist/{devtools-layer-CcTmlikp.js.map → devtools-layer-o1O55wu1.js.map} +1 -1
  9. package/dist/devtools-next.d.ts.map +1 -1
  10. package/dist/devtools-next.js +1 -1
  11. package/dist/{devtools-styles-teRMfSzW.js → devtools-styles-TWR5Ps34.js} +24 -3
  12. package/dist/devtools-styles-TWR5Ps34.js.map +1 -0
  13. package/dist/devtools-vite.js +2 -1
  14. package/dist/devtools-vite.js.map +1 -1
  15. package/dist/feedback.d.ts +7 -3
  16. package/dist/feedback.d.ts.map +1 -1
  17. package/dist/feedback.js +21 -12
  18. package/dist/feedback.js.map +1 -1
  19. package/dist/{handler-B3_PQB1q.js → handler-ndF3gZBx.js} +11 -416
  20. package/dist/handler-ndF3gZBx.js.map +1 -0
  21. package/dist/reflet-proxy-BW6q9xds.js +467 -0
  22. package/dist/reflet-proxy-BW6q9xds.js.map +1 -0
  23. package/dist/{submit-CG4LXioK.js → submit-DJIKxRUp.js} +2 -2
  24. package/dist/submit-DJIKxRUp.js.map +1 -0
  25. package/dist/{team-layer-zLj_QPQX.js → team-layer-_It1_oSo.js} +127 -57
  26. package/dist/team-layer-_It1_oSo.js.map +1 -0
  27. package/dist/team-tools-bridge-C51PpkUI.d.ts +31 -0
  28. package/dist/team-tools-bridge-C51PpkUI.d.ts.map +1 -0
  29. package/package.json +5 -1
  30. package/dist/devtools-styles-teRMfSzW.js.map +0 -1
  31. package/dist/handler-B3_PQB1q.js.map +0 -1
  32. package/dist/submit-CG4LXioK.js.map +0 -1
  33. package/dist/team-layer-zLj_QPQX.js.map +0 -1
@@ -0,0 +1,467 @@
1
+ import { a as EDITORS, n as DEVTOOLS_REQUEST_HEADER, o as PROXIED_API_PATHS, r as DEVTOOLS_ROUTE_BASE, t as DEVTOOLS_ENDPOINTS } from "./protocol-cJVVwnPe.js";
2
+ import "./client-oFFfRxAM.js";
3
+ import { z } from "zod";
4
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
5
+ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
6
+ import { homedir } from "node:os";
7
+ import { join, resolve } from "node:path";
8
+ //#region src/devtools/server/json-response.ts
9
+ const JSON_RESPONSE_HEADERS = {
10
+ "cache-control": "no-store",
11
+ "content-type": "application/json; charset=utf-8",
12
+ "x-content-type-options": "nosniff"
13
+ };
14
+ function jsonTextResponse(text, status) {
15
+ return new Response(text, {
16
+ headers: JSON_RESPONSE_HEADERS,
17
+ status
18
+ });
19
+ }
20
+ function jsonResponse(body, status = 200) {
21
+ return jsonTextResponse(JSON.stringify(body), status);
22
+ }
23
+ function errorResponse(error, status) {
24
+ return jsonResponse({ error }, status);
25
+ }
26
+ function failure(error, status) {
27
+ return {
28
+ error,
29
+ ok: false,
30
+ status
31
+ };
32
+ }
33
+ //#endregion
34
+ //#region src/devtools/server/request-guard.ts
35
+ const LOOPBACK_HOSTNAMES = ["localhost", "[::1]"];
36
+ const LOOPBACK_IPV4 = /^127(?:\.\d{1,3}){3}$/;
37
+ const IPV4_MAPPED_PREFIX = "::ffff:";
38
+ const TRUSTED_FETCH_SITES = ["same-origin", "none"];
39
+ function hostnameOf(request) {
40
+ const host = request.headers.get("host") ?? new URL(request.url).host;
41
+ try {
42
+ return new URL(`http://${host}`).hostname.toLowerCase();
43
+ } catch {
44
+ return null;
45
+ }
46
+ }
47
+ function isLoopbackHostname(hostname) {
48
+ return LOOPBACK_HOSTNAMES.includes(hostname) || hostname.endsWith(".localhost") || LOOPBACK_IPV4.test(hostname);
49
+ }
50
+ function isLoopbackAddress(address) {
51
+ if (!address) return false;
52
+ const ipv4 = address.startsWith(IPV4_MAPPED_PREFIX) ? address.slice(7) : address;
53
+ return address === "::1" || LOOPBACK_IPV4.test(ipv4);
54
+ }
55
+ /**
56
+ * The Host check stops DNS rebinding: a hostile domain resolved to 127.0.0.1
57
+ * is same-origin with itself, so origin checks alone would let it through.
58
+ * Host and header checks only stop browsers; keeping other machines out is the
59
+ * job of a dev server bound to loopback.
60
+ */
61
+ function rejectUntrustedHost(request, allowedHosts) {
62
+ const hostname = hostnameOf(request);
63
+ 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);
64
+ return null;
65
+ }
66
+ function rejectUntrustedCaller(request, allowedHosts) {
67
+ if (request.headers.get("x-reflet-devtools") !== "1") return errorResponse(`Reflet devtools requests must send the ${DEVTOOLS_REQUEST_HEADER} header.`, 403);
68
+ const fetchSite = request.headers.get("sec-fetch-site");
69
+ const hostname = hostnameOf(request);
70
+ const mayOmitFetchSite = hostname !== null && allowedHosts.includes(hostname) && !isLoopbackHostname(hostname);
71
+ if (!(fetchSite === null ? mayOmitFetchSite : TRUSTED_FETCH_SITES.includes(fetchSite))) return errorResponse("Reflet devtools only answers requests from the app's own origin.", 403);
72
+ return null;
73
+ }
74
+ //#endregion
75
+ //#region src/devtools/server/connect/connectable-host.ts
76
+ /** Mirrors the backend redirect allowlist, which stays the authority; this only decides whether Connect is offered. */
77
+ function isConnectableHost(hostname) {
78
+ return isLoopbackHostname(hostname) || hostname.endsWith(".test");
79
+ }
80
+ //#endregion
81
+ //#region src/devtools/server/connect/connection-store.ts
82
+ const storedConnectionSchema = z.object({
83
+ apiUrl: z.url(),
84
+ organizationName: z.string(),
85
+ token: z.string().startsWith("fb_dev_")
86
+ });
87
+ const connectionsDirectory = () => join(homedir(), ".reflet", "devtools");
88
+ function connectionFile(root, apiUrl) {
89
+ const key = createHash("sha256").update(JSON.stringify([root, apiUrl])).digest("hex");
90
+ return join(connectionsDirectory(), `${key}.json`);
91
+ }
92
+ async function readConnection(root, apiUrl) {
93
+ try {
94
+ const connection = storedConnectionSchema.parse(JSON.parse(await readFile(connectionFile(root, apiUrl), "utf8")));
95
+ return connection.apiUrl === apiUrl ? connection : null;
96
+ } catch (error) {
97
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") return null;
98
+ throw error;
99
+ }
100
+ }
101
+ async function saveConnection(root, connection) {
102
+ await mkdir(connectionsDirectory(), {
103
+ mode: 448,
104
+ recursive: true
105
+ });
106
+ const file = connectionFile(root, connection.apiUrl);
107
+ const temporaryFile = `${file}.${randomUUID()}.tmp`;
108
+ try {
109
+ await writeFile(temporaryFile, `${JSON.stringify(connection)}\n`, {
110
+ flag: "wx",
111
+ mode: 384
112
+ });
113
+ await rename(temporaryFile, file);
114
+ } finally {
115
+ await rm(temporaryFile, { force: true });
116
+ }
117
+ }
118
+ async function removeConnection(root, apiUrl, rejectedToken) {
119
+ const removed = await readConnection(root, apiUrl);
120
+ if (!removed || rejectedToken && removed.token !== rejectedToken) return null;
121
+ await rm(connectionFile(root, apiUrl), { force: true });
122
+ return removed;
123
+ }
124
+ //#endregion
125
+ //#region src/devtools/server/connect/html-page.ts
126
+ const HTML_ESCAPES = {
127
+ "'": "'",
128
+ "\"": """,
129
+ "&": "&",
130
+ "<": "&lt;",
131
+ ">": "&gt;"
132
+ };
133
+ const HTML_SPECIAL_CHARACTERS = /[&<>"']/g;
134
+ const escapeHtml = (text) => text.replace(HTML_SPECIAL_CHARACTERS, (character) => HTML_ESCAPES[character] ?? character);
135
+ function htmlPage(title, message, status) {
136
+ const safeTitle = escapeHtml(title);
137
+ const body = `<!doctype html>
138
+ <html lang="en">
139
+ <head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>${safeTitle} · Reflet devtools</title></head>
140
+ <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">
141
+ <main style="max-width:28rem;padding:1.5rem">
142
+ <h1 style="font-size:1.25rem;margin:0 0 .5rem">${safeTitle}</h1>
143
+ <p style="margin:0;line-height:1.5;color:#52525b">${escapeHtml(message)}</p>
144
+ </main>
145
+ </body>
146
+ </html>
147
+ `;
148
+ return new Response(body, {
149
+ headers: {
150
+ "cache-control": "no-store",
151
+ "content-security-policy": "default-src 'none'; style-src 'unsafe-inline'",
152
+ "content-type": "text/html; charset=utf-8",
153
+ "referrer-policy": "no-referrer",
154
+ "x-content-type-options": "nosniff"
155
+ },
156
+ status
157
+ });
158
+ }
159
+ //#endregion
160
+ //#region src/devtools/server/connect/pending-connects.ts
161
+ const PENDING_CONNECT_TTL_MS = 6e5;
162
+ const MAX_PENDING_CONNECTS = 10;
163
+ function pendingConnects() {
164
+ globalThis.refletDevtoolsPendingConnects ??= /* @__PURE__ */ new Map();
165
+ return globalThis.refletDevtoolsPendingConnects;
166
+ }
167
+ function beginConnect(binding) {
168
+ const state = randomBytes(32).toString("base64url");
169
+ const codeVerifier = randomBytes(32).toString("base64url");
170
+ const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
171
+ const pending = pendingConnects();
172
+ while (pending.size >= MAX_PENDING_CONNECTS) {
173
+ const oldestState = pending.keys().next().value;
174
+ if (oldestState === void 0) break;
175
+ pending.delete(oldestState);
176
+ }
177
+ pending.set(state, {
178
+ binding,
179
+ codeVerifier,
180
+ startedAt: Date.now()
181
+ });
182
+ return {
183
+ codeChallenge,
184
+ codeVerifier,
185
+ state
186
+ };
187
+ }
188
+ function takeConnect(state, binding) {
189
+ const pending = pendingConnects();
190
+ const connect = pending.get(state);
191
+ if (!connect) return null;
192
+ if (Date.now() - connect.startedAt > 6e5) {
193
+ pending.delete(state);
194
+ return null;
195
+ }
196
+ if (!(connect.binding.root === binding.root && connect.binding.apiUrl === binding.apiUrl && connect.binding.redirectUri === binding.redirectUri)) return null;
197
+ pending.delete(state);
198
+ return { codeVerifier: connect.codeVerifier };
199
+ }
200
+ //#endregion
201
+ //#region src/devtools/server/connect/connect-routes.ts
202
+ const publicKeySchema = z.object({ publicKey: z.string().startsWith("fb_pub_").max(200) });
203
+ const tokenExchangeSchema = z.object({
204
+ organizationName: z.string(),
205
+ token: z.string().startsWith("fb_dev_")
206
+ });
207
+ const upstreamErrorSchema = z.object({ error: z.string() });
208
+ const connectCallbackUriFor = (origin) => `${origin}${DEVTOOLS_ROUTE_BASE}${DEVTOOLS_ENDPOINTS.connectCallback}`;
209
+ const redirectUriFor = (request) => connectCallbackUriFor(new URL(request.url).origin);
210
+ async function readPublicKey(request) {
211
+ const parsed = publicKeySchema.safeParse(await request.json().catch(() => null));
212
+ return parsed.success ? parsed.data.publicKey : null;
213
+ }
214
+ async function readJson(response) {
215
+ try {
216
+ return await response.json();
217
+ } catch {
218
+ return null;
219
+ }
220
+ }
221
+ /** Starts a pending connect: Reflet sends the member back to `redirectUri` with a code only this process can redeem. */
222
+ function connectAuthorizeUrl(settings, target) {
223
+ const { codeChallenge, state } = beginConnect({
224
+ apiUrl: settings.apiUrl,
225
+ redirectUri: target.redirectUri,
226
+ root: settings.root
227
+ });
228
+ const authorizeUrl = new URL("/auth/devtools", settings.appUrl);
229
+ authorizeUrl.searchParams.set("public_key", target.publicKey);
230
+ authorizeUrl.searchParams.set("redirect_uri", target.redirectUri);
231
+ authorizeUrl.searchParams.set("state", state);
232
+ authorizeUrl.searchParams.set("code_challenge", codeChallenge);
233
+ return {
234
+ authorizeUrl: authorizeUrl.toString(),
235
+ state
236
+ };
237
+ }
238
+ async function startConnect(request, settings) {
239
+ const publicKey = await readPublicKey(request);
240
+ if (!publicKey) return errorResponse("Pass the widget's fb_pub_ public key to connect.", 400);
241
+ if (settings.secretKey) return errorResponse("REFLET_SECRET_KEY is set, so this dev server already reaches your board.", 409);
242
+ 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);
243
+ const { authorizeUrl } = connectAuthorizeUrl(settings, {
244
+ publicKey,
245
+ redirectUri: redirectUriFor(request)
246
+ });
247
+ return jsonResponse({ authorizeUrl });
248
+ }
249
+ /** Trades the code Reflet sent back for a token, kept outside the repository. */
250
+ async function redeemConnectCode(settings, callback) {
251
+ const { code, redirectUri, state } = callback;
252
+ const pending = code && state ? takeConnect(state, {
253
+ apiUrl: settings.apiUrl,
254
+ redirectUri,
255
+ root: settings.root
256
+ }) : null;
257
+ if (!(code && pending)) return {
258
+ message: "This connect link expired or was already used. Start again from the Reflet devtools.",
259
+ ok: false,
260
+ status: 400,
261
+ title: "Link expired"
262
+ };
263
+ let upstream;
264
+ try {
265
+ upstream = await fetch(`${settings.apiUrl}/api/v1/devtools/token`, {
266
+ body: JSON.stringify({
267
+ code,
268
+ codeVerifier: pending.codeVerifier,
269
+ redirectUri
270
+ }),
271
+ headers: {
272
+ Accept: "application/json",
273
+ "Content-Type": "application/json"
274
+ },
275
+ method: "POST",
276
+ signal: AbortSignal.timeout(1e4)
277
+ });
278
+ } catch {
279
+ return {
280
+ message: `Could not reach Reflet at ${settings.apiUrl}.`,
281
+ ok: false,
282
+ status: 502,
283
+ title: "Connection failed"
284
+ };
285
+ }
286
+ const payload = await readJson(upstream);
287
+ if (!upstream.ok) return {
288
+ message: upstreamErrorSchema.safeParse(payload).data?.error ?? "Reflet refused this connect code.",
289
+ ok: false,
290
+ status: 400,
291
+ title: "Connection refused"
292
+ };
293
+ const exchange = tokenExchangeSchema.safeParse(payload);
294
+ if (!exchange.success) return {
295
+ message: "Reflet answered with an unexpected response.",
296
+ ok: false,
297
+ status: 502,
298
+ title: "Connection failed"
299
+ };
300
+ await saveConnection(settings.root, {
301
+ apiUrl: settings.apiUrl,
302
+ organizationName: exchange.data.organizationName,
303
+ token: exchange.data.token
304
+ });
305
+ return {
306
+ ok: true,
307
+ organizationName: exchange.data.organizationName
308
+ };
309
+ }
310
+ function connectOutcomePage(outcome) {
311
+ if (!outcome.ok) return htmlPage(outcome.title, outcome.message, outcome.status);
312
+ return htmlPage("Connected", `Connected to ${outcome.organizationName}. Go back to your app: the Reflet devtools are ready.`, 200);
313
+ }
314
+ async function finishConnect(request, settings) {
315
+ const { searchParams } = new URL(request.url);
316
+ return connectOutcomePage(await redeemConnectCode(settings, {
317
+ code: searchParams.get("code"),
318
+ redirectUri: redirectUriFor(request),
319
+ state: searchParams.get("state")
320
+ }));
321
+ }
322
+ async function revokeUpstream(apiUrl, token) {
323
+ try {
324
+ return (await fetch(`${apiUrl}/api/v1/devtools/revoke`, {
325
+ headers: { Authorization: `Bearer ${token}` },
326
+ method: "POST",
327
+ signal: AbortSignal.timeout(1e4)
328
+ })).ok;
329
+ } catch {
330
+ return false;
331
+ }
332
+ }
333
+ /** Removes the stored token and revokes it on Reflet; false when nothing was revoked. */
334
+ async function forgetConnection(settings) {
335
+ const removed = await removeConnection(settings.root, settings.apiUrl);
336
+ return removed ? await revokeUpstream(removed.apiUrl, removed.token) : false;
337
+ }
338
+ async function disconnect(settings) {
339
+ return jsonResponse({ revoked: await forgetConnection(settings) });
340
+ }
341
+ //#endregion
342
+ //#region src/devtools/server/options.ts
343
+ const DEFAULT_APP_URL = "https://www.reflet.app";
344
+ const DEFAULT_EDITOR = "vscode";
345
+ const TRAILING_SLASHES = /\/+$/;
346
+ function isEditorId(value) {
347
+ return EDITORS.some((editor) => editor === value);
348
+ }
349
+ function presentValue(value) {
350
+ const trimmed = value?.trim();
351
+ return trimmed ? trimmed : void 0;
352
+ }
353
+ function resolveEditor(optionEditor, envEditor) {
354
+ if (isEditorId(optionEditor)) return optionEditor;
355
+ const normalizedEnvEditor = presentValue(envEditor)?.toLowerCase();
356
+ return isEditorId(normalizedEnvEditor) ? normalizedEnvEditor : DEFAULT_EDITOR;
357
+ }
358
+ function resolveDevtoolsOptions(options, env) {
359
+ const apiUrl = presentValue(options.apiUrl) ?? presentValue(env.REFLET_API_URL) ?? "https://harmless-clam-802.convex.site";
360
+ const appUrl = presentValue(options.appUrl) ?? presentValue(env.REFLET_APP_URL) ?? DEFAULT_APP_URL;
361
+ return {
362
+ allowedHosts: (options.allowedHosts ?? presentValue(env.REFLET_DEVTOOLS_HOSTS)?.split(",") ?? []).map((host) => host.trim().toLowerCase()).filter(Boolean),
363
+ apiUrl: apiUrl.replace(TRAILING_SLASHES, ""),
364
+ appUrl: appUrl.replace(TRAILING_SLASHES, ""),
365
+ editor: resolveEditor(options.editor, env.REFLET_EDITOR),
366
+ root: resolve(presentValue(options.root) ?? process.cwd()),
367
+ secretKey: presentValue(options.secretKey) ?? presentValue(env.REFLET_SECRET_KEY) ?? null
368
+ };
369
+ }
370
+ //#endregion
371
+ //#region src/devtools/server/connect/board-credential.ts
372
+ async function boardCredential(settings) {
373
+ if (settings.secretKey) return {
374
+ isStored: false,
375
+ token: settings.secretKey
376
+ };
377
+ const connection = await readConnection(settings.root, settings.apiUrl);
378
+ return connection ? {
379
+ isStored: true,
380
+ token: connection.token
381
+ } : null;
382
+ }
383
+ async function boardAccess(request, settings) {
384
+ if (settings.secretKey) return { kind: "secretKey" };
385
+ const connection = await readConnection(settings.root, settings.apiUrl);
386
+ if (connection) return {
387
+ kind: "connected",
388
+ organizationName: connection.organizationName
389
+ };
390
+ return {
391
+ canConnect: isConnectableHost(new URL(request.url).hostname),
392
+ kind: "disconnected"
393
+ };
394
+ }
395
+ //#endregion
396
+ //#region src/devtools/server/reflet-proxy.ts
397
+ const CREATE_FEEDBACK_PATH = "/api/v1/feedback/create";
398
+ const MISSING_BOARD_ACCESS_ERROR = "Connect to Reflet from the devtools panel footer, or set REFLET_SECRET_KEY in the dev server environment.";
399
+ const BODYLESS_STATUSES = [
400
+ 204,
401
+ 205,
402
+ 304
403
+ ];
404
+ function isProxiedApiPath(path) {
405
+ return PROXIED_API_PATHS.some((proxiedPath) => proxiedPath === path);
406
+ }
407
+ function isJsonObject(value) {
408
+ return typeof value === "object" && value !== null && !Array.isArray(value);
409
+ }
410
+ async function readForwardedBody(request, apiPath) {
411
+ if (request.method !== "POST") return { body: void 0 };
412
+ const text = await request.text();
413
+ if (apiPath !== CREATE_FEEDBACK_PATH) return { body: text || void 0 };
414
+ let payload;
415
+ try {
416
+ payload = JSON.parse(text);
417
+ } catch {
418
+ return { error: "Feedback must be a JSON object." };
419
+ }
420
+ if (!isJsonObject(payload)) return { error: "Feedback must be a JSON object." };
421
+ return { body: JSON.stringify({
422
+ ...payload,
423
+ internal: true
424
+ }) };
425
+ }
426
+ /** Forwards an allowlisted Reflet API call with the board token; devtools notes are always internal. */
427
+ async function proxyToReflet(request, apiPath, target) {
428
+ if (!isProxiedApiPath(apiPath)) return errorResponse(`Reflet devtools does not forward ${apiPath || "/"}.`, 404);
429
+ if (!target.token) return errorResponse(MISSING_BOARD_ACCESS_ERROR, 503);
430
+ if (request.method !== "GET" && request.method !== "POST") return errorResponse(`${request.method} is not supported here.`, 405);
431
+ const forwarded = await readForwardedBody(request, apiPath);
432
+ if ("error" in forwarded) return errorResponse(forwarded.error, 400);
433
+ const { search } = new URL(request.url);
434
+ let upstream;
435
+ try {
436
+ upstream = await fetch(`${target.apiUrl}${apiPath}${search}`, {
437
+ body: forwarded.body,
438
+ headers: {
439
+ Accept: "application/json",
440
+ Authorization: `Bearer ${target.token}`,
441
+ "Content-Type": "application/json"
442
+ },
443
+ method: request.method,
444
+ signal: AbortSignal.timeout(15e3)
445
+ });
446
+ } catch {
447
+ return errorResponse(`Could not reach Reflet at ${target.apiUrl}.`, 502);
448
+ }
449
+ return new Response(BODYLESS_STATUSES.includes(upstream.status) ? null : upstream.body, {
450
+ headers: JSON_RESPONSE_HEADERS,
451
+ status: upstream.status
452
+ });
453
+ }
454
+ /** A stored token Reflet refuses is dropped, so the next status shows Connect again. */
455
+ async function proxyWithBoardCredential(request, apiPath, settings) {
456
+ const credential = await boardCredential(settings);
457
+ const response = await proxyToReflet(request, apiPath, {
458
+ apiUrl: settings.apiUrl,
459
+ token: credential?.token ?? null
460
+ });
461
+ if (credential?.isStored && response.status === 401) await removeConnection(settings.root, settings.apiUrl, credential.token);
462
+ return response;
463
+ }
464
+ //#endregion
465
+ export { rejectUntrustedHost as _, connectCallbackUriFor as a, jsonResponse as b, finishConnect as c, startConnect as d, PENDING_CONNECT_TTL_MS as f, rejectUntrustedCaller as g, isLoopbackAddress as h, connectAuthorizeUrl as i, forgetConnection as l, readConnection as m, boardAccess as n, connectOutcomePage as o, htmlPage as p, resolveDevtoolsOptions as r, disconnect as s, proxyWithBoardCredential as t, redeemConnectCode as u, errorResponse as v, failure as y };
466
+
467
+ //# sourceMappingURL=reflet-proxy-BW6q9xds.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reflet-proxy-BW6q9xds.js","names":[],"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/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/connect/board-credential.ts","../src/devtools/server/reflet-proxy.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","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\nexport const 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\nexport type ConnectOutcome =\n | { ok: true; organizationName: string }\n | { message: string; ok: false; status: number; title: string };\n\nexport const connectCallbackUriFor = (origin: string): string =>\n `${origin}${DEVTOOLS_ROUTE_BASE}${DEVTOOLS_ENDPOINTS.connectCallback}`;\n\nconst redirectUriFor = (request: Request): string =>\n connectCallbackUriFor(new URL(request.url).origin);\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\n/** Starts a pending connect: Reflet sends the member back to `redirectUri` with a code only this process can redeem. */\nexport function connectAuthorizeUrl(\n settings: ResolvedDevtoolsOptions,\n target: { publicKey: string; redirectUri: string }\n): { authorizeUrl: string; state: string } {\n const { codeChallenge, state } = beginConnect({\n apiUrl: settings.apiUrl,\n redirectUri: target.redirectUri,\n root: settings.root,\n });\n const authorizeUrl = new URL(\"/auth/devtools\", settings.appUrl);\n authorizeUrl.searchParams.set(\"public_key\", target.publicKey);\n authorizeUrl.searchParams.set(\"redirect_uri\", target.redirectUri);\n authorizeUrl.searchParams.set(\"state\", state);\n authorizeUrl.searchParams.set(\"code_challenge\", codeChallenge);\n return { authorizeUrl: authorizeUrl.toString(), state };\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 const { authorizeUrl } = connectAuthorizeUrl(settings, {\n publicKey,\n redirectUri: redirectUriFor(request),\n });\n return jsonResponse({ authorizeUrl } satisfies ConnectStartResponse);\n}\n\n/** Trades the code Reflet sent back for a token, kept outside the repository. */\nexport async function redeemConnectCode(\n settings: ResolvedDevtoolsOptions,\n callback: { code: string | null; redirectUri: string; state: string | null }\n): Promise<ConnectOutcome> {\n const { code, redirectUri, state } = callback;\n const pending =\n code && state\n ? takeConnect(state, {\n apiUrl: settings.apiUrl,\n redirectUri,\n root: settings.root,\n })\n : null;\n if (!(code && pending)) {\n return {\n message:\n \"This connect link expired or was already used. Start again from the Reflet devtools.\",\n ok: false,\n status: 400,\n title: \"Link expired\",\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,\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 {\n message: `Could not reach Reflet at ${settings.apiUrl}.`,\n ok: false,\n status: 502,\n title: \"Connection failed\",\n };\n }\n const payload = await readJson(upstream);\n if (!upstream.ok) {\n return {\n message:\n upstreamErrorSchema.safeParse(payload).data?.error ??\n \"Reflet refused this connect code.\",\n ok: false,\n status: 400,\n title: \"Connection refused\",\n };\n }\n const exchange = tokenExchangeSchema.safeParse(payload);\n if (!exchange.success) {\n return {\n message: \"Reflet answered with an unexpected response.\",\n ok: false,\n status: 502,\n title: \"Connection failed\",\n };\n }\n await saveConnection(settings.root, {\n apiUrl: settings.apiUrl,\n organizationName: exchange.data.organizationName,\n token: exchange.data.token,\n });\n return { ok: true, organizationName: exchange.data.organizationName };\n}\n\nexport function connectOutcomePage(outcome: ConnectOutcome): Response {\n if (!outcome.ok) {\n return htmlPage(outcome.title, outcome.message, outcome.status);\n }\n return htmlPage(\n \"Connected\",\n `Connected to ${outcome.organizationName}. Go back to your app: the Reflet devtools are ready.`,\n 200\n );\n}\n\nexport async function finishConnect(\n request: Request,\n settings: ResolvedDevtoolsOptions\n): Promise<Response> {\n const { searchParams } = new URL(request.url);\n const outcome = await redeemConnectCode(settings, {\n code: searchParams.get(\"code\"),\n redirectUri: redirectUriFor(request),\n state: searchParams.get(\"state\"),\n });\n return connectOutcomePage(outcome);\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\n/** Removes the stored token and revokes it on Reflet; false when nothing was revoked. */\nexport async function forgetConnection(\n settings: ResolvedDevtoolsOptions\n): Promise<boolean> {\n const removed = await removeConnection(settings.root, settings.apiUrl);\n return removed ? await revokeUpstream(removed.apiUrl, removed.token) : false;\n}\n\nexport async function disconnect(\n settings: ResolvedDevtoolsOptions\n): Promise<Response> {\n const revoked = await forgetConnection(settings);\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 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","import { PROXIED_API_PATHS } from \"../protocol\";\nimport { boardCredential } from \"./connect/board-credential\";\nimport { removeConnection } from \"./connect/connection-store\";\nimport { errorResponse, JSON_RESPONSE_HEADERS } from \"./json-response\";\nimport type { ResolvedDevtoolsOptions } from \"./options\";\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 panel footer, 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\n/** A stored token Reflet refuses is dropped, so the next status shows Connect again. */\nexport async 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"],"mappings":"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"}
@@ -823,7 +823,7 @@ function collectPageContext(options) {
823
823
  }
824
824
  //#endregion
825
825
  //#region src/feedback/types.ts
826
- const SDK_VERSION = "0.7.4";
826
+ const SDK_VERSION = "0.7.5";
827
827
  const DEFAULT_WIDGET_LABELS = {
828
828
  annotateHint: "Draw on the screenshot to point at the problem.",
829
829
  attachElement: "Attach this element",
@@ -1999,4 +1999,4 @@ function createFeedbackTransport(client) {
1999
1999
  //#endregion
2000
2000
  export { shortenSourcePath as $, SquareIcon as A, clip as B, ChevronDownIcon as C, MoreIcon as D, HighlightIcon as E, UndoIcon as F, describeElement as G, MAX_SELECTION_COMMENT_LENGTH as H, isDegenerate as I, maskSecrets as J, getElementRect as K, normalizeRect as L, TargetIcon as M, TextIcon as N, PencilIcon as O, TrashIcon as P, resolveComponentStack as Q, DEFAULT_WIDGET_LABELS as R, CheckIcon as S, GripIcon as T, buildElementSelection as U, collectPageContext as V, buildSelector as W, getFiberFromNode as X, redactUrl as Y, inspectReactElement as Z, matchesHotkey as _, uploadBlob as a, CameraIcon as b, useFloatingPosition as c, drawAnnotations as d, WIDGET_MARKER as et, listenOnPageAndWidget as f, isSubmitEnter as g, stopClicksAtHost as h, submitWidgetFeedback as i, releaseCapture as it, Svg as j, SpotlightIcon as k, onViewportChange as l, startsInWidget as m, createFeedbackTransport as n, capturedFromCanvas as nt, ElementPicker as o, listenToKeydown as p, REDACT_ATTRIBUTE as q, deriveTitle as r, capturedViewportSize as rt, isWidgetOwned as s, attachPreparedScreenshots as t, captureViewport as tt, visibleViewport as u, ArrowIcon as v, CloseIcon as w, ChatIcon as x, BlurIcon as y, SDK_VERSION as z };
2001
2001
 
2002
- //# sourceMappingURL=submit-CG4LXioK.js.map
2002
+ //# sourceMappingURL=submit-DJIKxRUp.js.map