@opendatalabs/vana-sdk 4.4.0 → 4.5.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 (106) hide show
  1. package/README.md +176 -0
  2. package/dist/connect/account-origin.cjs +72 -0
  3. package/dist/connect/account-origin.cjs.map +1 -0
  4. package/dist/connect/account-origin.d.ts +92 -0
  5. package/dist/connect/account-origin.js +47 -0
  6. package/dist/connect/account-origin.js.map +1 -0
  7. package/dist/connect/account-origin.test.d.ts +1 -0
  8. package/dist/connect/chrome.cjs +284 -0
  9. package/dist/connect/chrome.cjs.map +1 -0
  10. package/dist/connect/chrome.d.ts +42 -0
  11. package/dist/connect/chrome.js +256 -0
  12. package/dist/connect/chrome.js.map +1 -0
  13. package/dist/connect/http-transports.cjs +100 -0
  14. package/dist/connect/http-transports.cjs.map +1 -0
  15. package/dist/connect/http-transports.d.ts +77 -0
  16. package/dist/connect/http-transports.js +75 -0
  17. package/dist/connect/http-transports.js.map +1 -0
  18. package/dist/connect/http-transports.test.d.ts +1 -0
  19. package/dist/connect/launcher.cjs +232 -0
  20. package/dist/connect/launcher.cjs.map +1 -0
  21. package/dist/connect/launcher.d.ts +129 -0
  22. package/dist/connect/launcher.js +220 -0
  23. package/dist/connect/launcher.js.map +1 -0
  24. package/dist/connect/launcher.test.d.ts +1 -0
  25. package/dist/connect/protocol.cjs +259 -0
  26. package/dist/connect/protocol.cjs.map +1 -0
  27. package/dist/connect/protocol.d.ts +331 -0
  28. package/dist/connect/protocol.js +215 -0
  29. package/dist/connect/protocol.js.map +1 -0
  30. package/dist/connect/protocol.test.d.ts +1 -0
  31. package/dist/connect.cjs +55 -0
  32. package/dist/connect.cjs.map +1 -0
  33. package/dist/connect.d.ts +35 -0
  34. package/dist/connect.js +34 -0
  35. package/dist/connect.js.map +1 -0
  36. package/dist/direct/connect-flow-sheet.test.d.ts +1 -0
  37. package/dist/direct/connect-flow.cjs +211 -1
  38. package/dist/direct/connect-flow.cjs.map +1 -1
  39. package/dist/direct/connect-flow.d.ts +75 -0
  40. package/dist/direct/connect-flow.js +213 -1
  41. package/dist/direct/connect-flow.js.map +1 -1
  42. package/dist/direct/connect-handler.cjs +493 -0
  43. package/dist/direct/connect-handler.cjs.map +1 -0
  44. package/dist/direct/connect-handler.d.ts +246 -0
  45. package/dist/direct/connect-handler.js +476 -0
  46. package/dist/direct/connect-handler.js.map +1 -0
  47. package/dist/direct/connect-handler.test.d.ts +1 -0
  48. package/dist/direct/controller.cjs +86 -17
  49. package/dist/direct/controller.cjs.map +1 -1
  50. package/dist/direct/controller.d.ts +101 -6
  51. package/dist/direct/controller.js +86 -16
  52. package/dist/direct/controller.js.map +1 -1
  53. package/dist/direct/endpoints.cjs +5 -2
  54. package/dist/direct/endpoints.cjs.map +1 -1
  55. package/dist/direct/endpoints.js +5 -2
  56. package/dist/direct/endpoints.js.map +1 -1
  57. package/dist/direct/request-authorization.cjs +65 -0
  58. package/dist/direct/request-authorization.cjs.map +1 -0
  59. package/dist/direct/request-authorization.d.ts +65 -0
  60. package/dist/direct/request-authorization.js +39 -0
  61. package/dist/direct/request-authorization.js.map +1 -0
  62. package/dist/direct/request-authorization.test.d.ts +1 -0
  63. package/dist/direct/types.cjs.map +1 -1
  64. package/dist/direct/types.d.ts +6 -0
  65. package/dist/direct/types.js.map +1 -1
  66. package/dist/direct/use-direct-vana-connect.cjs +18 -1
  67. package/dist/direct/use-direct-vana-connect.cjs.map +1 -1
  68. package/dist/direct/use-direct-vana-connect.js +18 -1
  69. package/dist/direct/use-direct-vana-connect.js.map +1 -1
  70. package/dist/direct/use-vana-connect.cjs +84 -0
  71. package/dist/direct/use-vana-connect.cjs.map +1 -0
  72. package/dist/direct/use-vana-connect.d.ts +59 -0
  73. package/dist/direct/use-vana-connect.js +64 -0
  74. package/dist/direct/use-vana-connect.js.map +1 -0
  75. package/dist/direct/use-vana-connect.test.d.ts +1 -0
  76. package/dist/index.browser.d.ts +1 -0
  77. package/dist/index.browser.js +104 -0
  78. package/dist/index.browser.js.map +4 -4
  79. package/dist/index.node.cjs +112 -0
  80. package/dist/index.node.cjs.map +3 -3
  81. package/dist/index.node.d.ts +1 -0
  82. package/dist/index.node.js +104 -0
  83. package/dist/index.node.js.map +4 -4
  84. package/dist/protocol/fixtures/pdpp-scope-keys.json +133 -0
  85. package/dist/protocol/pdpp-scopes.cjs +134 -0
  86. package/dist/protocol/pdpp-scopes.cjs.map +1 -0
  87. package/dist/protocol/pdpp-scopes.d.ts +123 -0
  88. package/dist/protocol/pdpp-scopes.js +103 -0
  89. package/dist/protocol/pdpp-scopes.js.map +1 -0
  90. package/dist/protocol/pdpp-scopes.test.d.ts +1 -0
  91. package/dist/protocol/scopes.cjs +17 -0
  92. package/dist/protocol/scopes.cjs.map +1 -1
  93. package/dist/protocol/scopes.d.ts +1 -0
  94. package/dist/protocol/scopes.js +18 -0
  95. package/dist/protocol/scopes.js.map +1 -1
  96. package/dist/react.cjs +10 -2
  97. package/dist/react.cjs.map +1 -1
  98. package/dist/react.d.ts +4 -1
  99. package/dist/react.js +11 -1
  100. package/dist/react.js.map +1 -1
  101. package/dist/server.cjs +61 -2
  102. package/dist/server.cjs.map +1 -1
  103. package/dist/server.d.ts +16 -4
  104. package/dist/server.js +67 -3
  105. package/dist/server.js.map +1 -1
  106. package/package.json +11 -1
@@ -0,0 +1,476 @@
1
+ import {
2
+ isRequestId,
3
+ PROTOCOL_VERSION,
4
+ VANA_ACCOUNT_ORIGINS
5
+ } from "../connect/protocol.js";
6
+ import {
7
+ missingConnectCapabilities
8
+ } from "./controller.js";
9
+ import {
10
+ AccessNotApprovedError,
11
+ DirectConfigError,
12
+ PaymentRequiredError,
13
+ PersonalServerReadError,
14
+ ScopeNotApprovedError
15
+ } from "./errors.js";
16
+ import { buildApprovalUrl } from "./access-request-client.js";
17
+ const nodeRemoteAddresses = /* @__PURE__ */ new WeakMap();
18
+ function defaultClientIp(request) {
19
+ const header = (name) => {
20
+ const value = request.headers.get(name)?.trim();
21
+ return value ? value : null;
22
+ };
23
+ const forwarded = header("x-forwarded-for")?.split(",").map((hop) => hop.trim()).filter(Boolean);
24
+ return header("cf-connecting-ip") ?? header("x-real-ip") ?? forwarded?.[forwarded.length - 1] ?? nodeRemoteAddresses.get(request) ?? null;
25
+ }
26
+ function createMemoryRateLimit(options = {}) {
27
+ const max = options.max ?? 10;
28
+ const windowMs = options.windowMs ?? 6e4;
29
+ const maxKeys = options.maxKeys ?? 1e4;
30
+ const clientIp = options.clientIp ?? defaultClientIp;
31
+ const now = options.now ?? Date.now;
32
+ const windows = /* @__PURE__ */ new Map();
33
+ function prune(at) {
34
+ for (const [key, window] of windows) {
35
+ if (window.resetAt <= at) windows.delete(key);
36
+ }
37
+ for (const key of windows.keys()) {
38
+ if (windows.size < maxKeys) break;
39
+ windows.delete(key);
40
+ }
41
+ }
42
+ return ({ request, session }) => {
43
+ const at = now();
44
+ const keys = [`ip:${clientIp(request) ?? "unknown"}`];
45
+ if (session) keys.push(`session:${session}`);
46
+ let retryAfterMs = 0;
47
+ for (const key of keys) {
48
+ const window = windows.get(key);
49
+ if (window && window.resetAt > at && window.count >= max) {
50
+ retryAfterMs = Math.max(retryAfterMs, window.resetAt - at);
51
+ }
52
+ }
53
+ if (retryAfterMs > 0) {
54
+ return { ok: false, retryAfterSeconds: Math.ceil(retryAfterMs / 1e3) };
55
+ }
56
+ if (windows.size >= maxKeys) prune(at);
57
+ for (const key of keys) {
58
+ const window = windows.get(key);
59
+ if (window && window.resetAt > at) {
60
+ window.count += 1;
61
+ } else {
62
+ windows.delete(key);
63
+ windows.set(key, { count: 1, resetAt: at + windowMs });
64
+ }
65
+ }
66
+ return { ok: true };
67
+ };
68
+ }
69
+ const COOKIE_PREFIX = "vana_dcr_";
70
+ const BINDING_VERSION = 1;
71
+ const MIN_BINDING_MS = 60 * 60 * 1e3;
72
+ const MAX_BINDING_MS = 7 * 24 * 60 * 60 * 1e3;
73
+ const MAX_BODY_BYTES = 4096;
74
+ const CONNECT_BINDING_KEY_PURPOSE = "vana-sdk/connect/request-binding/v1";
75
+ const encoder = new TextEncoder();
76
+ function base64url(bytes) {
77
+ let binary = "";
78
+ for (const byte of bytes) binary += String.fromCharCode(byte);
79
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
80
+ }
81
+ function fromBase64url(value) {
82
+ if (!/^[A-Za-z0-9_-]*$/.test(value)) return null;
83
+ try {
84
+ const binary = atob(value.replace(/-/g, "+").replace(/_/g, "/"));
85
+ return Uint8Array.from(binary, (char) => char.charCodeAt(0));
86
+ } catch {
87
+ return null;
88
+ }
89
+ }
90
+ function constantTimeEqual(a, b) {
91
+ if (a.length !== b.length) return false;
92
+ let diff = 0;
93
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
94
+ return diff === 0;
95
+ }
96
+ async function sha256(value) {
97
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
98
+ return base64url(new Uint8Array(digest));
99
+ }
100
+ function connectBindingCookieName(requestId) {
101
+ return `${COOKIE_PREFIX}${requestId}`;
102
+ }
103
+ function errorResponse(status, code, message, headers) {
104
+ return json({ error: { code, message } }, status, headers);
105
+ }
106
+ function json(body, status = 200, headers = {}) {
107
+ return new Response(JSON.stringify(body), {
108
+ status,
109
+ headers: {
110
+ "Content-Type": "application/json",
111
+ "Cache-Control": "no-store",
112
+ ...headers
113
+ }
114
+ });
115
+ }
116
+ function readCookie(request, name) {
117
+ const header = request.headers.get("cookie");
118
+ if (!header) return null;
119
+ for (const part of header.split(";")) {
120
+ const separator = part.indexOf("=");
121
+ if (separator < 0) continue;
122
+ if (part.slice(0, separator).trim() === name) {
123
+ return part.slice(separator + 1).trim();
124
+ }
125
+ }
126
+ return null;
127
+ }
128
+ function createVanaConnectHandler(options) {
129
+ const missing = missingConnectCapabilities(options.controller).filter(
130
+ (name) => !(name === "deriveSecret" && options.bindingSecret)
131
+ );
132
+ if (missing.length > 0) {
133
+ throw new DirectConfigError(
134
+ `createVanaConnectHandler needs a controller with ${missing.join(", ")} (createDirectDataController provides them)`,
135
+ { missing }
136
+ );
137
+ }
138
+ const controller = options.controller;
139
+ let returnOrigin;
140
+ try {
141
+ returnOrigin = new URL(options.returnUrl).origin;
142
+ } catch {
143
+ throw new DirectConfigError("returnUrl must be an absolute URL", {
144
+ returnUrl: options.returnUrl
145
+ });
146
+ }
147
+ const allowedOrigins = new Set(options.allowedOrigins ?? [returnOrigin]);
148
+ const now = options.now ?? Date.now;
149
+ const cookiePath = options.cookiePath ?? "/";
150
+ const rateLimit = options.rateLimit === false ? null : options.rateLimit ?? createMemoryRateLimit({ now });
151
+ let macKey = null;
152
+ function bindingKey() {
153
+ macKey ??= (async () => {
154
+ const raw = options.bindingSecret ? await crypto.subtle.digest(
155
+ "SHA-256",
156
+ encoder.encode(
157
+ `${CONNECT_BINDING_KEY_PURPOSE}\0${options.bindingSecret}`
158
+ )
159
+ ) : await controller.deriveSecret(CONNECT_BINDING_KEY_PURPOSE);
160
+ return crypto.subtle.importKey(
161
+ "raw",
162
+ raw,
163
+ { name: "HMAC", hash: "SHA-256" },
164
+ false,
165
+ ["sign"]
166
+ );
167
+ })();
168
+ return macKey;
169
+ }
170
+ async function tag(payload) {
171
+ const signature = await crypto.subtle.sign(
172
+ "HMAC",
173
+ await bindingKey(),
174
+ encoder.encode(`vana-connect-binding.${payload}`)
175
+ );
176
+ return base64url(new Uint8Array(signature));
177
+ }
178
+ async function sessionHash(request) {
179
+ if (!options.session) return void 0;
180
+ const id = await options.session(request);
181
+ return id ? sha256(id) : null;
182
+ }
183
+ async function bindingCookie(request, created, session) {
184
+ const current = now();
185
+ const requestExpiry = created.expiresAt ? Date.parse(created.expiresAt) : Number.NaN;
186
+ const expiresAt = Math.min(
187
+ current + MAX_BINDING_MS,
188
+ Math.max(
189
+ current + MIN_BINDING_MS,
190
+ Number.isFinite(requestExpiry) ? requestExpiry : 0
191
+ )
192
+ );
193
+ const payload = {
194
+ v: BINDING_VERSION,
195
+ r: created.requestId,
196
+ e: expiresAt,
197
+ ...session ? { s: session } : {}
198
+ };
199
+ const encoded = base64url(encoder.encode(JSON.stringify(payload)));
200
+ const attributes = [
201
+ `Path=${cookiePath}`,
202
+ `Max-Age=${Math.floor((expiresAt - current) / 1e3)}`,
203
+ "HttpOnly",
204
+ "SameSite=Lax",
205
+ ...new URL(request.url).protocol === "https:" ? ["Secure"] : []
206
+ ];
207
+ return `${connectBindingCookieName(created.requestId)}=${encoded}.${await tag(
208
+ encoded
209
+ )}; ${attributes.join("; ")}`;
210
+ }
211
+ async function readBinding(request, requestId, session) {
212
+ const value = readCookie(request, connectBindingCookieName(requestId));
213
+ if (!value || value.length > 1024) return null;
214
+ const separator = value.lastIndexOf(".");
215
+ if (separator <= 0) return null;
216
+ const encoded = value.slice(0, separator);
217
+ if (!constantTimeEqual(value.slice(separator + 1), await tag(encoded))) {
218
+ return null;
219
+ }
220
+ const bytes = fromBase64url(encoded);
221
+ if (!bytes) return null;
222
+ let payload;
223
+ try {
224
+ payload = JSON.parse(new TextDecoder().decode(bytes));
225
+ } catch {
226
+ return null;
227
+ }
228
+ const binding = payload;
229
+ if (!binding || binding.v !== BINDING_VERSION || binding.r !== requestId || typeof binding.e !== "number" || binding.e <= now() || binding.s !== session) {
230
+ return null;
231
+ }
232
+ return binding;
233
+ }
234
+ function originRefused(request) {
235
+ const origin = request.headers.get("origin");
236
+ if (origin !== null && !allowedOrigins.has(origin)) return true;
237
+ return request.headers.get("sec-fetch-site") === "cross-site";
238
+ }
239
+ async function readBody(request) {
240
+ const text = await request.text();
241
+ if (text.length > MAX_BODY_BYTES) return null;
242
+ if (text.trim() === "") return {};
243
+ try {
244
+ const parsed = JSON.parse(text);
245
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
246
+ } catch {
247
+ return null;
248
+ }
249
+ }
250
+ function config() {
251
+ const endpoints = controller.getEndpoints();
252
+ const env = controller.getEnv();
253
+ return {
254
+ protocolVersion: PROTOCOL_VERSION,
255
+ env,
256
+ clientId: options.clientId ?? null,
257
+ appName: options.appName ?? controller.getAppIdentity().name,
258
+ // The controller's own environment decides; never a production default.
259
+ accountOrigin: endpoints.accountOrigin ?? VANA_ACCOUNT_ORIGINS[env]
260
+ };
261
+ }
262
+ async function postRequest(request, session) {
263
+ const body = await readBody(request);
264
+ if (!body) {
265
+ return errorResponse(
266
+ 400,
267
+ "invalid_request",
268
+ "Body must be a JSON object."
269
+ );
270
+ }
271
+ if (body.requestId !== void 0) {
272
+ const requestId = body.requestId;
273
+ if (!isRequestId(requestId)) {
274
+ return errorResponse(
275
+ 400,
276
+ "invalid_request",
277
+ "requestId is not a request id."
278
+ );
279
+ }
280
+ if (!await readBinding(request, requestId, session)) {
281
+ return notBound();
282
+ }
283
+ const endpoints = controller.getEndpoints();
284
+ return json({
285
+ request: {
286
+ requestId,
287
+ approvalUrl: buildApprovalUrl(
288
+ endpoints.approvalAppBaseUrl,
289
+ requestId
290
+ ),
291
+ appAddress: controller.appAddress
292
+ },
293
+ embed: {
294
+ id: requestId,
295
+ authorization: await controller.authorizeRequestRead(requestId)
296
+ }
297
+ });
298
+ }
299
+ if (rateLimit) {
300
+ const decision = await rateLimit({
301
+ request,
302
+ ...session ? { session } : {}
303
+ });
304
+ if (!decision.ok) {
305
+ return errorResponse(
306
+ 429,
307
+ "rate_limited",
308
+ "Too many requests. Try again later.",
309
+ decision.retryAfterSeconds !== void 0 ? {
310
+ "Retry-After": String(
311
+ Math.max(0, Math.ceil(decision.retryAfterSeconds))
312
+ )
313
+ } : void 0
314
+ );
315
+ }
316
+ }
317
+ const created = await controller.createAccessRequest({
318
+ ...options.createOptions ? await options.createOptions(request) : {},
319
+ returnUrl: options.returnUrl
320
+ });
321
+ const sanitized = {
322
+ requestId: created.requestId,
323
+ approvalUrl: created.approvalUrl,
324
+ appAddress: created.appAddress,
325
+ ...created.network ? { network: created.network } : {},
326
+ ...created.expiresAt ? { expiresAt: created.expiresAt } : {},
327
+ ...created.mobileContinuationUrl ? { mobileContinuationUrl: created.mobileContinuationUrl } : {},
328
+ ...created.scopes ? { scopes: created.scopes } : {}
329
+ };
330
+ const embed = isRequestId(created.requestId) ? {
331
+ id: created.requestId,
332
+ authorization: await controller.authorizeRequestRead(
333
+ created.requestId
334
+ )
335
+ } : null;
336
+ return json({ request: sanitized, embed }, 200, {
337
+ "Set-Cookie": await bindingCookie(request, created, session)
338
+ });
339
+ }
340
+ function notBound() {
341
+ return errorResponse(
342
+ 403,
343
+ "request_not_bound",
344
+ "This request was not created in this browser session."
345
+ );
346
+ }
347
+ async function boundRequestId(request, session) {
348
+ const values = new URL(request.url).searchParams.getAll("requestId");
349
+ const requestId = values.length === 1 ? values[0] : void 0;
350
+ if (!isRequestId(requestId)) {
351
+ return errorResponse(
352
+ 400,
353
+ "invalid_request",
354
+ "A single requestId is required."
355
+ );
356
+ }
357
+ if (!await readBinding(request, requestId, session)) {
358
+ return notBound();
359
+ }
360
+ return requestId;
361
+ }
362
+ async function getStatus(request, session) {
363
+ const requestId = await boundRequestId(request, session);
364
+ if (requestId instanceof Response) return requestId;
365
+ const status = await controller.getAccessRequestStatus(requestId);
366
+ return json({
367
+ status: status.status,
368
+ ...status.mobileContinuationUrl ? { mobileContinuationUrl: status.mobileContinuationUrl } : {}
369
+ });
370
+ }
371
+ async function getData(request, session) {
372
+ const requestId = await boundRequestId(request, session);
373
+ if (requestId instanceof Response) return requestId;
374
+ const result = options.read ? await options.read({ controller, requestId, request }) : await controller.readApprovedData({ requestId });
375
+ return json(result);
376
+ }
377
+ return async function handle(request) {
378
+ const segments = new URL(request.url).pathname.split("/").filter(Boolean);
379
+ const action = segments[segments.length - 1];
380
+ const method = request.method.toUpperCase();
381
+ const route = (expected) => method === expected ? null : errorResponse(405, "method_not_allowed", `Use ${expected}.`, {
382
+ Allow: expected
383
+ });
384
+ try {
385
+ if (action === "config") {
386
+ return route("GET") ?? json(config());
387
+ }
388
+ if (action !== "request" && action !== "status" && action !== "data") {
389
+ return errorResponse(404, "not_found", "Unknown connect route.");
390
+ }
391
+ const wrongMethod = route(action === "request" ? "POST" : "GET");
392
+ if (wrongMethod) return wrongMethod;
393
+ if (action === "request" && originRefused(request)) {
394
+ return errorResponse(403, "forbidden_origin", "Origin not allowed.");
395
+ }
396
+ const session = await sessionHash(request);
397
+ if (session === null) {
398
+ return errorResponse(401, "unauthenticated", "Sign in first.");
399
+ }
400
+ if (action === "request") return await postRequest(request, session);
401
+ if (action === "status") return await getStatus(request, session);
402
+ return await getData(request, session);
403
+ } catch (error) {
404
+ return errorFor(error);
405
+ }
406
+ };
407
+ }
408
+ function errorFor(error) {
409
+ if (error instanceof AccessNotApprovedError) {
410
+ return errorResponse(409, "not_approved", error.message);
411
+ }
412
+ if (error instanceof ScopeNotApprovedError) {
413
+ return errorResponse(403, "scope_not_approved", error.message);
414
+ }
415
+ if (error instanceof PaymentRequiredError) {
416
+ return errorResponse(402, "payment_required", error.message);
417
+ }
418
+ if (error instanceof PersonalServerReadError) {
419
+ return errorResponse(502, "personal_server_error", error.message);
420
+ }
421
+ if (error instanceof DirectConfigError) {
422
+ return errorResponse(400, "invalid_request", error.message);
423
+ }
424
+ return errorResponse(500, "internal_error", "Something went wrong.");
425
+ }
426
+ function toNodeHandler(handler, options = {}) {
427
+ return async (req, res) => {
428
+ const header = (name) => {
429
+ const value = req.headers[name];
430
+ return Array.isArray(value) ? value[0] : value;
431
+ };
432
+ const origin = options.origin ?? `${header("x-forwarded-proto")?.split(",")[0]?.trim() ?? (req.socket?.encrypted ? "https" : "http")}://${header("host") ?? "localhost"}`;
433
+ const url = new URL(req.originalUrl ?? req.url ?? "/", origin);
434
+ const method = (req.method ?? "GET").toUpperCase();
435
+ const headers = new Headers();
436
+ for (const [name, value] of Object.entries(req.headers)) {
437
+ if (value === void 0) continue;
438
+ headers.set(name, Array.isArray(value) ? value.join(", ") : value);
439
+ }
440
+ let body;
441
+ if (method !== "GET" && method !== "HEAD") {
442
+ if (req.body !== void 0) {
443
+ body = typeof req.body === "string" ? req.body : JSON.stringify(req.body);
444
+ } else {
445
+ const decoder = new TextDecoder();
446
+ body = "";
447
+ for await (const chunk of req) {
448
+ body += typeof chunk === "string" ? chunk : decoder.decode(chunk, { stream: true });
449
+ if (body.length > MAX_BODY_BYTES) break;
450
+ }
451
+ body += decoder.decode();
452
+ }
453
+ }
454
+ const request = new Request(url, { method, headers, body });
455
+ if (req.socket?.remoteAddress) {
456
+ nodeRemoteAddresses.set(request, req.socket.remoteAddress);
457
+ }
458
+ const response = await handler(request);
459
+ res.statusCode = response.status;
460
+ response.headers.forEach((value, name) => {
461
+ if (name !== "set-cookie") res.setHeader(name, value);
462
+ });
463
+ const cookies = response.headers.getSetCookie();
464
+ if (cookies.length > 0) res.setHeader("set-cookie", cookies);
465
+ res.end(new Uint8Array(await response.arrayBuffer()));
466
+ };
467
+ }
468
+ export {
469
+ CONNECT_BINDING_KEY_PURPOSE,
470
+ connectBindingCookieName,
471
+ createMemoryRateLimit,
472
+ createVanaConnectHandler,
473
+ defaultClientIp,
474
+ toNodeHandler
475
+ };
476
+ //# sourceMappingURL=connect-handler.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/direct/connect-handler.ts"],"sourcesContent":["/**\n * One Fetch-API handler for every backend route the connect flow needs.\n *\n * @remarks\n * `createVanaConnectHandler` answers four routes under one base path. The\n * action is the last path segment, so mount it as a catch-all\n * (`app/api/vana/[action]/route.ts` in Next.js, or `app.use(\"/api/vana\",\n * toNodeHandler(handler))` in Express):\n *\n * | Route | Does |\n * | ----------------------------- | ---------------------------------------------------- |\n * | `GET config` | Sheet config: client id, app name, Account origin. |\n * | `POST request` | Create a request bound to this browser, or re-sign one it already holds. Returns `{ request, embed }`. |\n * | `GET status?requestId=` | Status of a bound request. |\n * | `GET data?requestId=` | Read the approved data of a bound request. |\n *\n * Binding: a created request is bound to the browser by an HttpOnly,\n * `SameSite=Lax` cookie carrying an HMAC-SHA256 tag, keyed by a secret\n * derived from the app key with HKDF. The handler signs, reports and reads\n * only requests bound to the calling browser (and, with `session`, to the\n * app's own signed-in user). See `src/connect/WIRE_CONTRACT.md` for the\n * wire contract a non-JS backend implements instead.\n *\n * What a request asks for is the controller's: its PDPP `grants` (each\n * stream requested as `pdpp.<source>.<stream>`) and legacy `scopes`. The\n * browser never chooses; `POST request` reports the scopes it sent.\n *\n * Creating a request is rate limited (`rateLimit`; by default in memory,\n * per client IP and per session, which is not production-grade). In\n * production, require a signed-in user with `session` and put a shared\n * limiter in front of `POST request`.\n *\n * @category Direct\n * @module direct/connect-handler\n */\n\nimport {\n isRequestId,\n PROTOCOL_VERSION,\n VANA_ACCOUNT_ORIGINS,\n} from \"../connect/protocol\";\nimport {\n missingConnectCapabilities,\n type ConnectCapableDirectDataController,\n type DirectDataController,\n} from \"./controller\";\nimport {\n AccessNotApprovedError,\n DirectConfigError,\n PaymentRequiredError,\n PersonalServerReadError,\n ScopeNotApprovedError,\n} from \"./errors\";\nimport { buildApprovalUrl } from \"./access-request-client\";\nimport type { AccessRequest } from \"./types\";\nimport type { VanaConnectConfig } from \"../connect/http-transports\";\n\nexport type { VanaConnectConfig };\n\n/** The create input the handler forwards, minus what it sets itself. */\nexport type VanaConnectCreateOptions = Omit<\n Parameters<DirectDataController[\"createAccessRequest\"]>[0],\n \"returnUrl\"\n>;\n\n/** What a {@link VanaConnectRateLimit} decides. */\nexport type VanaConnectRateLimitDecision =\n | { ok: true }\n | {\n ok: false;\n /** Sent as `Retry-After`, in whole seconds. */\n retryAfterSeconds?: number;\n };\n\n/**\n * Decide whether one `POST request` that creates a request may proceed.\n *\n * @remarks\n * Called after the origin and `session` checks and before the controller\n * creates anything. `session` is the hash of the app's session id when\n * `session` is configured. Re-signing a request the browser already holds is\n * not rate limited: it creates nothing and needs a valid binding.\n */\nexport type VanaConnectRateLimit = (input: {\n request: Request;\n session?: string;\n}) => VanaConnectRateLimitDecision | Promise<VanaConnectRateLimitDecision>;\n\n/** Options for {@link createMemoryRateLimit}. */\nexport interface MemoryRateLimitOptions {\n /** Creates allowed per key per window. Defaults to 10. */\n max?: number;\n /** Window length in ms. Defaults to 60000. */\n windowMs?: number;\n /**\n * The caller's IP. Defaults to {@link defaultClientIp}, which trusts proxy\n * headers; behind anything else, supply the address your platform vouches\n * for.\n */\n clientIp?: (request: Request) => string | null;\n /** Keys kept before the oldest are dropped. Defaults to 10000. */\n maxKeys?: number;\n /** Clock in ms. Defaults to `Date.now`. */\n now?: () => number;\n}\n\nconst nodeRemoteAddresses = new WeakMap<Request, string>();\n\n/**\n * The caller's IP as the default rate limit sees it.\n *\n * @remarks\n * `CF-Connecting-IP`, then `X-Real-IP`, then the last `X-Forwarded-For`\n * hop (the one your proxy appended), then the socket address when the\n * request came through {@link toNodeHandler}. Headers are only as honest as\n * the proxy in front of you: without one, a client can set them.\n *\n * @param request - The incoming request.\n * @returns An IP string, or `null` when nothing names one.\n */\nexport function defaultClientIp(request: Request): string | null {\n const header = (name: string): string | null => {\n const value = request.headers.get(name)?.trim();\n return value ? value : null;\n };\n const forwarded = header(\"x-forwarded-for\")\n ?.split(\",\")\n .map((hop) => hop.trim())\n .filter(Boolean);\n return (\n header(\"cf-connecting-ip\") ??\n header(\"x-real-ip\") ??\n forwarded?.[forwarded.length - 1] ??\n nodeRemoteAddresses.get(request) ??\n null\n );\n}\n\n/**\n * An in-memory, fixed-window rate limit per client IP and per session.\n *\n * @remarks\n * The default for `createVanaConnectHandler`. Not production-grade: each\n * server instance (and each serverless cold start) counts on its own, and\n * the IP comes from headers a client can forge without a trusted proxy.\n * In production, require `session` and pass a `rateLimit` backed by a\n * shared store (Redis, your platform's limiter).\n *\n * @param options - Limit, window, IP source and memory bound.\n * @returns A {@link VanaConnectRateLimit}.\n */\nexport function createMemoryRateLimit(\n options: MemoryRateLimitOptions = {},\n): VanaConnectRateLimit {\n const max = options.max ?? 10;\n const windowMs = options.windowMs ?? 60_000;\n const maxKeys = options.maxKeys ?? 10_000;\n const clientIp = options.clientIp ?? defaultClientIp;\n const now = options.now ?? Date.now;\n const windows = new Map<string, { count: number; resetAt: number }>();\n\n function prune(at: number): void {\n for (const [key, window] of windows) {\n if (window.resetAt <= at) windows.delete(key);\n }\n // Still full: drop the oldest keys (Map keeps insertion order).\n for (const key of windows.keys()) {\n if (windows.size < maxKeys) break;\n windows.delete(key);\n }\n }\n\n return ({ request, session }) => {\n const at = now();\n const keys = [`ip:${clientIp(request) ?? \"unknown\"}`];\n if (session) keys.push(`session:${session}`);\n let retryAfterMs = 0;\n for (const key of keys) {\n const window = windows.get(key);\n if (window && window.resetAt > at && window.count >= max) {\n retryAfterMs = Math.max(retryAfterMs, window.resetAt - at);\n }\n }\n if (retryAfterMs > 0) {\n return { ok: false, retryAfterSeconds: Math.ceil(retryAfterMs / 1000) };\n }\n if (windows.size >= maxKeys) prune(at);\n for (const key of keys) {\n const window = windows.get(key);\n if (window && window.resetAt > at) {\n window.count += 1;\n } else {\n windows.delete(key);\n windows.set(key, { count: 1, resetAt: at + windowMs });\n }\n }\n return { ok: true };\n };\n}\n\n/** Options for {@link createVanaConnectHandler}. */\nexport interface VanaConnectHandlerOptions {\n /**\n * The app's controller; it holds the key and talks to Vana. It must\n * implement the connect methods (`authorizeRequestRead`, `getEndpoints`,\n * `getEnv`, and `deriveSecret` unless `bindingSecret` is set), as every\n * `createDirectDataController` controller does.\n */\n controller: DirectDataController;\n /**\n * Absolute URL Vana returns the person to after approving in a tab. Its\n * origin is also the only `Origin` accepted on `POST` (see\n * {@link VanaConnectHandlerOptions.allowedOrigins}).\n */\n returnUrl: string;\n /**\n * The embed client id Vana Account registered for this app's origin. Without\n * it `GET config` reports `clientId: null` and the browser cannot open the\n * sheet (it falls back to a tab).\n */\n clientId?: string;\n /** Label on the sheet chrome. Defaults to the controller's app name. */\n appName?: string;\n /**\n * Bind requests to the app's own signed-in user too. Return a stable user or\n * session id, or `null` to refuse with `401`. Only a hash of it is stored.\n *\n * @remarks\n * Recommended for production. Without it, anyone who can load your page\n * can create requests on your app's behalf; the browser binding only stops\n * them reading someone else's.\n */\n session?: (request: Request) => string | null | Promise<string | null>;\n /**\n * Extra create input per request (questions, owner, ...). Called on each\n * create.\n */\n createOptions?: (\n request: Request,\n ) => VanaConnectCreateOptions | Promise<VanaConnectCreateOptions>;\n /**\n * Custom read for `GET data`. Defaults to\n * `controller.readApprovedData({ requestId })`. The result is sent as JSON.\n */\n read?: (input: {\n controller: DirectDataController;\n requestId: string;\n request: Request;\n }) => Promise<unknown>;\n /**\n * Origins accepted on `POST`. Defaults to the origin of `returnUrl`. A\n * request without an `Origin` header (a non-browser client) is accepted.\n */\n allowedOrigins?: string[];\n /**\n * Secret for the binding cookie. Defaults to a key derived from the app key\n * with HKDF-SHA256 (`controller.deriveSecret`). Set it to rotate bindings\n * independently of the app key.\n */\n bindingSecret?: string;\n /**\n * Rate limit for creating requests. Defaults to\n * {@link createMemoryRateLimit}() (10 per minute per IP and per session,\n * in memory, not production-grade). Pass your own shared limiter in\n * production, or `false` when one already sits in front of this route.\n * A refusal answers `429 rate_limited` with `Retry-After`.\n */\n rateLimit?: VanaConnectRateLimit | false;\n /** Cookie `Path`. Defaults to `/`. */\n cookiePath?: string;\n /** Clock in ms. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/** A Fetch-API request handler. */\nexport type VanaConnectHandler = (request: Request) => Promise<Response>;\n\n/** Error codes in a handler error body: `{ error: { code, message } }`. */\nexport type VanaConnectErrorCode =\n | \"not_found\"\n | \"method_not_allowed\"\n | \"invalid_request\"\n | \"forbidden_origin\"\n | \"unauthenticated\"\n | \"request_not_bound\"\n | \"not_approved\"\n | \"scope_not_approved\"\n | \"payment_required\"\n | \"personal_server_error\"\n | \"rate_limited\"\n | \"internal_error\";\n\nconst COOKIE_PREFIX = \"vana_dcr_\";\nconst BINDING_VERSION = 1;\nconst MIN_BINDING_MS = 60 * 60 * 1000;\nconst MAX_BINDING_MS = 7 * 24 * 60 * 60 * 1000;\nconst MAX_BODY_BYTES = 4096;\n/** HKDF `info` the default binding key is derived with. */\nexport const CONNECT_BINDING_KEY_PURPOSE =\n \"vana-sdk/connect/request-binding/v1\";\n\nconst encoder = new TextEncoder();\n\nfunction base64url(bytes: Uint8Array): string {\n let binary = \"\";\n for (const byte of bytes) binary += String.fromCharCode(byte);\n return btoa(binary)\n .replace(/\\+/g, \"-\")\n .replace(/\\//g, \"_\")\n .replace(/=+$/, \"\");\n}\n\nfunction fromBase64url(value: string): Uint8Array | null {\n if (!/^[A-Za-z0-9_-]*$/.test(value)) return null;\n try {\n const binary = atob(value.replace(/-/g, \"+\").replace(/_/g, \"/\"));\n return Uint8Array.from(binary, (char) => char.charCodeAt(0));\n } catch {\n return null;\n }\n}\n\nfunction constantTimeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\nasync function sha256(value: string): Promise<string> {\n const digest = await crypto.subtle.digest(\"SHA-256\", encoder.encode(value));\n return base64url(new Uint8Array(digest));\n}\n\n/** Cookie name binding one request to the browser. */\nexport function connectBindingCookieName(requestId: string): string {\n return `${COOKIE_PREFIX}${requestId}`;\n}\n\ninterface BindingPayload {\n v: typeof BINDING_VERSION;\n /** Request id. */\n r: string;\n /** Expiry, ms since epoch. */\n e: number;\n /** SHA-256 of the session id, when bound to a session. */\n s?: string;\n}\n\nfunction errorResponse(\n status: number,\n code: VanaConnectErrorCode,\n message: string,\n headers?: Record<string, string>,\n): Response {\n return json({ error: { code, message } }, status, headers);\n}\n\nfunction json(\n body: unknown,\n status = 200,\n headers: Record<string, string> = {},\n): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: {\n \"Content-Type\": \"application/json\",\n \"Cache-Control\": \"no-store\",\n ...headers,\n },\n });\n}\n\nfunction readCookie(request: Request, name: string): string | null {\n const header = request.headers.get(\"cookie\");\n if (!header) return null;\n for (const part of header.split(\";\")) {\n const separator = part.indexOf(\"=\");\n if (separator < 0) continue;\n if (part.slice(0, separator).trim() === name) {\n return part.slice(separator + 1).trim();\n }\n }\n return null;\n}\n\n/**\n * Create the connect handler.\n *\n * @example\n * ```typescript\n * // app/api/vana/[action]/route.ts\n * import { createVanaConnectHandler } from \"@opendatalabs/vana-sdk/server\";\n * // createDirectDataController({ grants: [{ source_id, streams }], ... })\n * import { vana } from \"@/lib/vana\";\n *\n * const handler = createVanaConnectHandler({\n * controller: vana,\n * returnUrl: `${process.env.APP_URL}/connect/return`,\n * clientId: \"my-app\",\n * });\n * export { handler as GET, handler as POST };\n * ```\n *\n * @param options - Controller, return URL, sheet client id and hooks.\n * @returns A `(request: Request) => Promise<Response>` handler.\n * @throws {@link DirectConfigError} when `returnUrl` is not an absolute URL,\n * or the controller lacks a connect method it needs.\n */\nexport function createVanaConnectHandler(\n options: VanaConnectHandlerOptions,\n): VanaConnectHandler {\n const missing = missingConnectCapabilities(options.controller).filter(\n (name) => !(name === \"deriveSecret\" && options.bindingSecret),\n );\n if (missing.length > 0) {\n throw new DirectConfigError(\n `createVanaConnectHandler needs a controller with ${missing.join(\", \")} (createDirectDataController provides them)`,\n { missing },\n );\n }\n // Checked above; `deriveSecret` is only called without `bindingSecret`.\n const controller = options.controller as ConnectCapableDirectDataController;\n let returnOrigin: string;\n try {\n returnOrigin = new URL(options.returnUrl).origin;\n } catch {\n throw new DirectConfigError(\"returnUrl must be an absolute URL\", {\n returnUrl: options.returnUrl,\n });\n }\n const allowedOrigins = new Set(options.allowedOrigins ?? [returnOrigin]);\n const now = options.now ?? Date.now;\n const cookiePath = options.cookiePath ?? \"/\";\n const rateLimit =\n options.rateLimit === false\n ? null\n : (options.rateLimit ?? createMemoryRateLimit({ now }));\n\n let macKey: Promise<CryptoKey> | null = null;\n function bindingKey(): Promise<CryptoKey> {\n macKey ??= (async () => {\n const raw = options.bindingSecret\n ? await crypto.subtle.digest(\n \"SHA-256\",\n encoder.encode(\n `${CONNECT_BINDING_KEY_PURPOSE}\\0${options.bindingSecret}`,\n ),\n )\n : await controller.deriveSecret(CONNECT_BINDING_KEY_PURPOSE);\n return crypto.subtle.importKey(\n \"raw\",\n raw,\n { name: \"HMAC\", hash: \"SHA-256\" },\n false,\n [\"sign\"],\n );\n })();\n return macKey;\n }\n\n async function tag(payload: string): Promise<string> {\n const signature = await crypto.subtle.sign(\n \"HMAC\",\n await bindingKey(),\n encoder.encode(`vana-connect-binding.${payload}`),\n );\n return base64url(new Uint8Array(signature));\n }\n\n async function sessionHash(\n request: Request,\n ): Promise<string | null | undefined> {\n if (!options.session) return undefined;\n const id = await options.session(request);\n return id ? sha256(id) : null;\n }\n\n async function bindingCookie(\n request: Request,\n created: AccessRequest,\n session: string | undefined,\n ): Promise<string> {\n const current = now();\n const requestExpiry = created.expiresAt\n ? Date.parse(created.expiresAt)\n : Number.NaN;\n const expiresAt = Math.min(\n current + MAX_BINDING_MS,\n Math.max(\n current + MIN_BINDING_MS,\n Number.isFinite(requestExpiry) ? requestExpiry : 0,\n ),\n );\n const payload: BindingPayload = {\n v: BINDING_VERSION,\n r: created.requestId,\n e: expiresAt,\n ...(session ? { s: session } : {}),\n };\n const encoded = base64url(encoder.encode(JSON.stringify(payload)));\n const attributes = [\n `Path=${cookiePath}`,\n `Max-Age=${Math.floor((expiresAt - current) / 1000)}`,\n \"HttpOnly\",\n \"SameSite=Lax\",\n ...(new URL(request.url).protocol === \"https:\" ? [\"Secure\"] : []),\n ];\n return `${connectBindingCookieName(created.requestId)}=${encoded}.${await tag(\n encoded,\n )}; ${attributes.join(\"; \")}`;\n }\n\n /** The binding for `requestId`, or `null` when absent, forged or expired. */\n async function readBinding(\n request: Request,\n requestId: string,\n session: string | undefined,\n ): Promise<BindingPayload | null> {\n const value = readCookie(request, connectBindingCookieName(requestId));\n if (!value || value.length > 1024) return null;\n const separator = value.lastIndexOf(\".\");\n if (separator <= 0) return null;\n const encoded = value.slice(0, separator);\n if (!constantTimeEqual(value.slice(separator + 1), await tag(encoded))) {\n return null;\n }\n const bytes = fromBase64url(encoded);\n if (!bytes) return null;\n let payload: unknown;\n try {\n payload = JSON.parse(new TextDecoder().decode(bytes));\n } catch {\n return null;\n }\n const binding = payload as Partial<BindingPayload> | null;\n if (\n !binding ||\n binding.v !== BINDING_VERSION ||\n binding.r !== requestId ||\n typeof binding.e !== \"number\" ||\n binding.e <= now() ||\n binding.s !== session\n ) {\n return null;\n }\n return binding as BindingPayload;\n }\n\n function originRefused(request: Request): boolean {\n const origin = request.headers.get(\"origin\");\n if (origin !== null && !allowedOrigins.has(origin)) return true;\n return request.headers.get(\"sec-fetch-site\") === \"cross-site\";\n }\n\n async function readBody(\n request: Request,\n ): Promise<Record<string, unknown> | null> {\n const text = await request.text();\n if (text.length > MAX_BODY_BYTES) return null;\n if (text.trim() === \"\") return {};\n try {\n const parsed: unknown = JSON.parse(text);\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed)\n ? (parsed as Record<string, unknown>)\n : null;\n } catch {\n return null;\n }\n }\n\n function config(): VanaConnectConfig {\n const endpoints = controller.getEndpoints();\n const env = controller.getEnv();\n return {\n protocolVersion: PROTOCOL_VERSION,\n env,\n clientId: options.clientId ?? null,\n appName: options.appName ?? controller.getAppIdentity().name,\n // The controller's own environment decides; never a production default.\n accountOrigin: endpoints.accountOrigin ?? VANA_ACCOUNT_ORIGINS[env],\n };\n }\n\n async function postRequest(\n request: Request,\n session: string | undefined,\n ): Promise<Response> {\n const body = await readBody(request);\n if (!body) {\n return errorResponse(\n 400,\n \"invalid_request\",\n \"Body must be a JSON object.\",\n );\n }\n if (body.requestId !== undefined) {\n // Re-sign a request this browser already holds.\n const requestId = body.requestId;\n if (!isRequestId(requestId)) {\n return errorResponse(\n 400,\n \"invalid_request\",\n \"requestId is not a request id.\",\n );\n }\n if (!(await readBinding(request, requestId, session))) {\n return notBound();\n }\n const endpoints = controller.getEndpoints();\n return json({\n request: {\n requestId,\n approvalUrl: buildApprovalUrl(\n endpoints.approvalAppBaseUrl,\n requestId,\n ),\n appAddress: controller.appAddress,\n },\n embed: {\n id: requestId,\n authorization: await controller.authorizeRequestRead(requestId),\n },\n });\n }\n\n if (rateLimit) {\n const decision = await rateLimit({\n request,\n ...(session ? { session } : {}),\n });\n if (!decision.ok) {\n return errorResponse(\n 429,\n \"rate_limited\",\n \"Too many requests. Try again later.\",\n decision.retryAfterSeconds !== undefined\n ? {\n \"Retry-After\": String(\n Math.max(0, Math.ceil(decision.retryAfterSeconds)),\n ),\n }\n : undefined,\n );\n }\n }\n const created = await controller.createAccessRequest({\n ...(options.createOptions ? await options.createOptions(request) : {}),\n returnUrl: options.returnUrl,\n });\n const sanitized: AccessRequest = {\n requestId: created.requestId,\n approvalUrl: created.approvalUrl,\n appAddress: created.appAddress,\n ...(created.network ? { network: created.network } : {}),\n ...(created.expiresAt ? { expiresAt: created.expiresAt } : {}),\n ...(created.mobileContinuationUrl\n ? { mobileContinuationUrl: created.mobileContinuationUrl }\n : {}),\n ...(created.scopes ? { scopes: created.scopes } : {}),\n };\n const embed = isRequestId(created.requestId)\n ? {\n id: created.requestId,\n authorization: await controller.authorizeRequestRead(\n created.requestId,\n ),\n }\n : null;\n return json({ request: sanitized, embed }, 200, {\n \"Set-Cookie\": await bindingCookie(request, created, session),\n });\n }\n\n function notBound(): Response {\n return errorResponse(\n 403,\n \"request_not_bound\",\n \"This request was not created in this browser session.\",\n );\n }\n\n async function boundRequestId(\n request: Request,\n session: string | undefined,\n ): Promise<string | Response> {\n const values = new URL(request.url).searchParams.getAll(\"requestId\");\n const requestId = values.length === 1 ? values[0] : undefined;\n if (!isRequestId(requestId)) {\n return errorResponse(\n 400,\n \"invalid_request\",\n \"A single requestId is required.\",\n );\n }\n if (!(await readBinding(request, requestId, session))) {\n return notBound();\n }\n return requestId;\n }\n\n async function getStatus(\n request: Request,\n session: string | undefined,\n ): Promise<Response> {\n const requestId = await boundRequestId(request, session);\n if (requestId instanceof Response) return requestId;\n const status = await controller.getAccessRequestStatus(requestId);\n // Only what the browser flow needs: no Personal Server URL or grant id.\n return json({\n status: status.status,\n ...(status.mobileContinuationUrl\n ? { mobileContinuationUrl: status.mobileContinuationUrl }\n : {}),\n });\n }\n\n async function getData(\n request: Request,\n session: string | undefined,\n ): Promise<Response> {\n const requestId = await boundRequestId(request, session);\n if (requestId instanceof Response) return requestId;\n const result = options.read\n ? await options.read({ controller, requestId, request })\n : await controller.readApprovedData({ requestId });\n return json(result);\n }\n\n return async function handle(request: Request): Promise<Response> {\n const segments = new URL(request.url).pathname.split(\"/\").filter(Boolean);\n const action = segments[segments.length - 1];\n const method = request.method.toUpperCase();\n const route = (expected: string) =>\n method === expected\n ? null\n : errorResponse(405, \"method_not_allowed\", `Use ${expected}.`, {\n Allow: expected,\n });\n try {\n if (action === \"config\") {\n return route(\"GET\") ?? json(config());\n }\n if (action !== \"request\" && action !== \"status\" && action !== \"data\") {\n return errorResponse(404, \"not_found\", \"Unknown connect route.\");\n }\n const wrongMethod = route(action === \"request\" ? \"POST\" : \"GET\");\n if (wrongMethod) return wrongMethod;\n if (action === \"request\" && originRefused(request)) {\n return errorResponse(403, \"forbidden_origin\", \"Origin not allowed.\");\n }\n const session = await sessionHash(request);\n if (session === null) {\n return errorResponse(401, \"unauthenticated\", \"Sign in first.\");\n }\n if (action === \"request\") return await postRequest(request, session);\n if (action === \"status\") return await getStatus(request, session);\n return await getData(request, session);\n } catch (error) {\n return errorFor(error);\n }\n };\n}\n\nfunction errorFor(error: unknown): Response {\n if (error instanceof AccessNotApprovedError) {\n return errorResponse(409, \"not_approved\", error.message);\n }\n if (error instanceof ScopeNotApprovedError) {\n return errorResponse(403, \"scope_not_approved\", error.message);\n }\n if (error instanceof PaymentRequiredError) {\n return errorResponse(402, \"payment_required\", error.message);\n }\n if (error instanceof PersonalServerReadError) {\n return errorResponse(502, \"personal_server_error\", error.message);\n }\n if (error instanceof DirectConfigError) {\n return errorResponse(400, \"invalid_request\", error.message);\n }\n // Unknown failures stay opaque to the browser.\n return errorResponse(500, \"internal_error\", \"Something went wrong.\");\n}\n\n/** The parts of a Node `IncomingMessage` {@link toNodeHandler} reads. */\nexport interface NodeRequestLike {\n method?: string;\n url?: string;\n /** Express sets this to the URL before mount-path stripping. */\n originalUrl?: string;\n headers: Record<string, string | string[] | undefined>;\n /** A body already parsed by middleware (Express `json()`), if any. */\n body?: unknown;\n socket?: { encrypted?: boolean; remoteAddress?: string };\n [Symbol.asyncIterator](): AsyncIterator<Uint8Array | string>;\n}\n\n/** The parts of a Node `ServerResponse` {@link toNodeHandler} writes. */\nexport interface NodeResponseLike {\n statusCode: number;\n setHeader(name: string, value: string | string[]): unknown;\n end(chunk?: Uint8Array | string): unknown;\n}\n\n/** Options for {@link toNodeHandler}. */\nexport interface ToNodeHandlerOptions {\n /**\n * Public origin of the app (`https://app.example`). Defaults to the\n * request's `Host` header and `X-Forwarded-Proto` (or the socket's TLS).\n */\n origin?: string;\n}\n\n/**\n * Adapt a Fetch-API handler to Node's `(req, res)` (http, Express, Fastify's\n * raw mode).\n *\n * @param handler - A handler such as {@link createVanaConnectHandler}'s.\n * @param options - Public origin override.\n * @returns A `(req, res) => Promise<void>` listener.\n */\nexport function toNodeHandler(\n handler: VanaConnectHandler,\n options: ToNodeHandlerOptions = {},\n): (req: NodeRequestLike, res: NodeResponseLike) => Promise<void> {\n return async (req, res) => {\n const header = (name: string): string | undefined => {\n const value = req.headers[name];\n return Array.isArray(value) ? value[0] : value;\n };\n const origin =\n options.origin ??\n `${header(\"x-forwarded-proto\")?.split(\",\")[0]?.trim() ?? (req.socket?.encrypted ? \"https\" : \"http\")}://${header(\"host\") ?? \"localhost\"}`;\n const url = new URL(req.originalUrl ?? req.url ?? \"/\", origin);\n const method = (req.method ?? \"GET\").toUpperCase();\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n headers.set(name, Array.isArray(value) ? value.join(\", \") : value);\n }\n let body: string | undefined;\n if (method !== \"GET\" && method !== \"HEAD\") {\n if (req.body !== undefined) {\n body =\n typeof req.body === \"string\" ? req.body : JSON.stringify(req.body);\n } else {\n const decoder = new TextDecoder();\n body = \"\";\n for await (const chunk of req) {\n body +=\n typeof chunk === \"string\"\n ? chunk\n : decoder.decode(chunk, { stream: true });\n if (body.length > MAX_BODY_BYTES) break;\n }\n body += decoder.decode();\n }\n }\n const request = new Request(url, { method, headers, body });\n if (req.socket?.remoteAddress) {\n nodeRemoteAddresses.set(request, req.socket.remoteAddress);\n }\n const response = await handler(request);\n res.statusCode = response.status;\n response.headers.forEach((value, name) => {\n if (name !== \"set-cookie\") res.setHeader(name, value);\n });\n const cookies = response.headers.getSetCookie();\n if (cookies.length > 0) res.setHeader(\"set-cookie\", cookies);\n res.end(new Uint8Array(await response.arrayBuffer()));\n };\n}\n"],"mappings":"AAoCA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP;AAAA,EACE;AAAA,OAGK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;AAqDjC,MAAM,sBAAsB,oBAAI,QAAyB;AAclD,SAAS,gBAAgB,SAAiC;AAC/D,QAAM,SAAS,CAAC,SAAgC;AAC9C,UAAM,QAAQ,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AAC9C,WAAO,QAAQ,QAAQ;AAAA,EACzB;AACA,QAAM,YAAY,OAAO,iBAAiB,GACtC,MAAM,GAAG,EACV,IAAI,CAAC,QAAQ,IAAI,KAAK,CAAC,EACvB,OAAO,OAAO;AACjB,SACE,OAAO,kBAAkB,KACzB,OAAO,WAAW,KAClB,YAAY,UAAU,SAAS,CAAC,KAChC,oBAAoB,IAAI,OAAO,KAC/B;AAEJ;AAeO,SAAS,sBACd,UAAkC,CAAC,GACb;AACtB,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,UAAU,QAAQ,WAAW;AACnC,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,UAAU,oBAAI,IAAgD;AAEpE,WAAS,MAAM,IAAkB;AAC/B,eAAW,CAAC,KAAK,MAAM,KAAK,SAAS;AACnC,UAAI,OAAO,WAAW,GAAI,SAAQ,OAAO,GAAG;AAAA,IAC9C;AAEA,eAAW,OAAO,QAAQ,KAAK,GAAG;AAChC,UAAI,QAAQ,OAAO,QAAS;AAC5B,cAAQ,OAAO,GAAG;AAAA,IACpB;AAAA,EACF;AAEA,SAAO,CAAC,EAAE,SAAS,QAAQ,MAAM;AAC/B,UAAM,KAAK,IAAI;AACf,UAAM,OAAO,CAAC,MAAM,SAAS,OAAO,KAAK,SAAS,EAAE;AACpD,QAAI,QAAS,MAAK,KAAK,WAAW,OAAO,EAAE;AAC3C,QAAI,eAAe;AACnB,eAAW,OAAO,MAAM;AACtB,YAAM,SAAS,QAAQ,IAAI,GAAG;AAC9B,UAAI,UAAU,OAAO,UAAU,MAAM,OAAO,SAAS,KAAK;AACxD,uBAAe,KAAK,IAAI,cAAc,OAAO,UAAU,EAAE;AAAA,MAC3D;AAAA,IACF;AACA,QAAI,eAAe,GAAG;AACpB,aAAO,EAAE,IAAI,OAAO,mBAAmB,KAAK,KAAK,eAAe,GAAI,EAAE;AAAA,IACxE;AACA,QAAI,QAAQ,QAAQ,QAAS,OAAM,EAAE;AACrC,eAAW,OAAO,MAAM;AACtB,YAAM,SAAS,QAAQ,IAAI,GAAG;AAC9B,UAAI,UAAU,OAAO,UAAU,IAAI;AACjC,eAAO,SAAS;AAAA,MAClB,OAAO;AACL,gBAAQ,OAAO,GAAG;AAClB,gBAAQ,IAAI,KAAK,EAAE,OAAO,GAAG,SAAS,KAAK,SAAS,CAAC;AAAA,MACvD;AAAA,IACF;AACA,WAAO,EAAE,IAAI,KAAK;AAAA,EACpB;AACF;AA8FA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,iBAAiB,KAAK,KAAK;AACjC,MAAM,iBAAiB,IAAI,KAAK,KAAK,KAAK;AAC1C,MAAM,iBAAiB;AAEhB,MAAM,8BACX;AAEF,MAAM,UAAU,IAAI,YAAY;AAEhC,SAAS,UAAU,OAA2B;AAC5C,MAAI,SAAS;AACb,aAAW,QAAQ,MAAO,WAAU,OAAO,aAAa,IAAI;AAC5D,SAAO,KAAK,MAAM,EACf,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAEA,SAAS,cAAc,OAAkC;AACvD,MAAI,CAAC,mBAAmB,KAAK,KAAK,EAAG,QAAO;AAC5C,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,CAAC;AAC/D,WAAO,WAAW,KAAK,QAAQ,CAAC,SAAS,KAAK,WAAW,CAAC,CAAC;AAAA,EAC7D,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,kBAAkB,GAAW,GAAoB;AACxD,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,SAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;AAC3E,SAAO,SAAS;AAClB;AAEA,eAAe,OAAO,OAAgC;AACpD,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW,QAAQ,OAAO,KAAK,CAAC;AAC1E,SAAO,UAAU,IAAI,WAAW,MAAM,CAAC;AACzC;AAGO,SAAS,yBAAyB,WAA2B;AAClE,SAAO,GAAG,aAAa,GAAG,SAAS;AACrC;AAYA,SAAS,cACP,QACA,MACA,SACA,SACU;AACV,SAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,EAAE,GAAG,QAAQ,OAAO;AAC3D;AAEA,SAAS,KACP,MACA,SAAS,KACT,UAAkC,CAAC,GACzB;AACV,SAAO,IAAI,SAAS,KAAK,UAAU,IAAI,GAAG;AAAA,IACxC;AAAA,IACA,SAAS;AAAA,MACP,gBAAgB;AAAA,MAChB,iBAAiB;AAAA,MACjB,GAAG;AAAA,IACL;AAAA,EACF,CAAC;AACH;AAEA,SAAS,WAAW,SAAkB,MAA6B;AACjE,QAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,MAAI,CAAC,OAAQ,QAAO;AACpB,aAAW,QAAQ,OAAO,MAAM,GAAG,GAAG;AACpC,UAAM,YAAY,KAAK,QAAQ,GAAG;AAClC,QAAI,YAAY,EAAG;AACnB,QAAI,KAAK,MAAM,GAAG,SAAS,EAAE,KAAK,MAAM,MAAM;AAC5C,aAAO,KAAK,MAAM,YAAY,CAAC,EAAE,KAAK;AAAA,IACxC;AAAA,EACF;AACA,SAAO;AACT;AAyBO,SAAS,yBACd,SACoB;AACpB,QAAM,UAAU,2BAA2B,QAAQ,UAAU,EAAE;AAAA,IAC7D,CAAC,SAAS,EAAE,SAAS,kBAAkB,QAAQ;AAAA,EACjD;AACA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,IAAI;AAAA,MACR,oDAAoD,QAAQ,KAAK,IAAI,CAAC;AAAA,MACtE,EAAE,QAAQ;AAAA,IACZ;AAAA,EACF;AAEA,QAAM,aAAa,QAAQ;AAC3B,MAAI;AACJ,MAAI;AACF,mBAAe,IAAI,IAAI,QAAQ,SAAS,EAAE;AAAA,EAC5C,QAAQ;AACN,UAAM,IAAI,kBAAkB,qCAAqC;AAAA,MAC/D,WAAW,QAAQ;AAAA,IACrB,CAAC;AAAA,EACH;AACA,QAAM,iBAAiB,IAAI,IAAI,QAAQ,kBAAkB,CAAC,YAAY,CAAC;AACvE,QAAM,MAAM,QAAQ,OAAO,KAAK;AAChC,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,YACJ,QAAQ,cAAc,QAClB,OACC,QAAQ,aAAa,sBAAsB,EAAE,IAAI,CAAC;AAEzD,MAAI,SAAoC;AACxC,WAAS,aAAiC;AACxC,gBAAY,YAAY;AACtB,YAAM,MAAM,QAAQ,gBAChB,MAAM,OAAO,OAAO;AAAA,QAClB;AAAA,QACA,QAAQ;AAAA,UACN,GAAG,2BAA2B,KAAK,QAAQ,aAAa;AAAA,QAC1D;AAAA,MACF,IACA,MAAM,WAAW,aAAa,2BAA2B;AAC7D,aAAO,OAAO,OAAO;AAAA,QACnB;AAAA,QACA;AAAA,QACA,EAAE,MAAM,QAAQ,MAAM,UAAU;AAAA,QAChC;AAAA,QACA,CAAC,MAAM;AAAA,MACT;AAAA,IACF,GAAG;AACH,WAAO;AAAA,EACT;AAEA,iBAAe,IAAI,SAAkC;AACnD,UAAM,YAAY,MAAM,OAAO,OAAO;AAAA,MACpC;AAAA,MACA,MAAM,WAAW;AAAA,MACjB,QAAQ,OAAO,wBAAwB,OAAO,EAAE;AAAA,IAClD;AACA,WAAO,UAAU,IAAI,WAAW,SAAS,CAAC;AAAA,EAC5C;AAEA,iBAAe,YACb,SACoC;AACpC,QAAI,CAAC,QAAQ,QAAS,QAAO;AAC7B,UAAM,KAAK,MAAM,QAAQ,QAAQ,OAAO;AACxC,WAAO,KAAK,OAAO,EAAE,IAAI;AAAA,EAC3B;AAEA,iBAAe,cACb,SACA,SACA,SACiB;AACjB,UAAM,UAAU,IAAI;AACpB,UAAM,gBAAgB,QAAQ,YAC1B,KAAK,MAAM,QAAQ,SAAS,IAC5B,OAAO;AACX,UAAM,YAAY,KAAK;AAAA,MACrB,UAAU;AAAA,MACV,KAAK;AAAA,QACH,UAAU;AAAA,QACV,OAAO,SAAS,aAAa,IAAI,gBAAgB;AAAA,MACnD;AAAA,IACF;AACA,UAAM,UAA0B;AAAA,MAC9B,GAAG;AAAA,MACH,GAAG,QAAQ;AAAA,MACX,GAAG;AAAA,MACH,GAAI,UAAU,EAAE,GAAG,QAAQ,IAAI,CAAC;AAAA,IAClC;AACA,UAAM,UAAU,UAAU,QAAQ,OAAO,KAAK,UAAU,OAAO,CAAC,CAAC;AACjE,UAAM,aAAa;AAAA,MACjB,QAAQ,UAAU;AAAA,MAClB,WAAW,KAAK,OAAO,YAAY,WAAW,GAAI,CAAC;AAAA,MACnD;AAAA,MACA;AAAA,MACA,GAAI,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,WAAW,CAAC,QAAQ,IAAI,CAAC;AAAA,IACjE;AACA,WAAO,GAAG,yBAAyB,QAAQ,SAAS,CAAC,IAAI,OAAO,IAAI,MAAM;AAAA,MACxE;AAAA,IACF,CAAC,KAAK,WAAW,KAAK,IAAI,CAAC;AAAA,EAC7B;AAGA,iBAAe,YACb,SACA,WACA,SACgC;AAChC,UAAM,QAAQ,WAAW,SAAS,yBAAyB,SAAS,CAAC;AACrE,QAAI,CAAC,SAAS,MAAM,SAAS,KAAM,QAAO;AAC1C,UAAM,YAAY,MAAM,YAAY,GAAG;AACvC,QAAI,aAAa,EAAG,QAAO;AAC3B,UAAM,UAAU,MAAM,MAAM,GAAG,SAAS;AACxC,QAAI,CAAC,kBAAkB,MAAM,MAAM,YAAY,CAAC,GAAG,MAAM,IAAI,OAAO,CAAC,GAAG;AACtE,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,cAAc,OAAO;AACnC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI;AACJ,QAAI;AACF,gBAAU,KAAK,MAAM,IAAI,YAAY,EAAE,OAAO,KAAK,CAAC;AAAA,IACtD,QAAQ;AACN,aAAO;AAAA,IACT;AACA,UAAM,UAAU;AAChB,QACE,CAAC,WACD,QAAQ,MAAM,mBACd,QAAQ,MAAM,aACd,OAAO,QAAQ,MAAM,YACrB,QAAQ,KAAK,IAAI,KACjB,QAAQ,MAAM,SACd;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAEA,WAAS,cAAc,SAA2B;AAChD,UAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;AAC3C,QAAI,WAAW,QAAQ,CAAC,eAAe,IAAI,MAAM,EAAG,QAAO;AAC3D,WAAO,QAAQ,QAAQ,IAAI,gBAAgB,MAAM;AAAA,EACnD;AAEA,iBAAe,SACb,SACyC;AACzC,UAAM,OAAO,MAAM,QAAQ,KAAK;AAChC,QAAI,KAAK,SAAS,eAAgB,QAAO;AACzC,QAAI,KAAK,KAAK,MAAM,GAAI,QAAO,CAAC;AAChC,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,IAAI;AACvC,aAAO,UAAU,OAAO,WAAW,YAAY,CAAC,MAAM,QAAQ,MAAM,IAC/D,SACD;AAAA,IACN,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAEA,WAAS,SAA4B;AACnC,UAAM,YAAY,WAAW,aAAa;AAC1C,UAAM,MAAM,WAAW,OAAO;AAC9B,WAAO;AAAA,MACL,iBAAiB;AAAA,MACjB;AAAA,MACA,UAAU,QAAQ,YAAY;AAAA,MAC9B,SAAS,QAAQ,WAAW,WAAW,eAAe,EAAE;AAAA;AAAA,MAExD,eAAe,UAAU,iBAAiB,qBAAqB,GAAG;AAAA,IACpE;AAAA,EACF;AAEA,iBAAe,YACb,SACA,SACmB;AACnB,UAAM,OAAO,MAAM,SAAS,OAAO;AACnC,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI,KAAK,cAAc,QAAW;AAEhC,YAAM,YAAY,KAAK;AACvB,UAAI,CAAC,YAAY,SAAS,GAAG;AAC3B,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AACA,UAAI,CAAE,MAAM,YAAY,SAAS,WAAW,OAAO,GAAI;AACrD,eAAO,SAAS;AAAA,MAClB;AACA,YAAM,YAAY,WAAW,aAAa;AAC1C,aAAO,KAAK;AAAA,QACV,SAAS;AAAA,UACP;AAAA,UACA,aAAa;AAAA,YACX,UAAU;AAAA,YACV;AAAA,UACF;AAAA,UACA,YAAY,WAAW;AAAA,QACzB;AAAA,QACA,OAAO;AAAA,UACL,IAAI;AAAA,UACJ,eAAe,MAAM,WAAW,qBAAqB,SAAS;AAAA,QAChE;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,WAAW;AACb,YAAM,WAAW,MAAM,UAAU;AAAA,QAC/B;AAAA,QACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;AAAA,MAC/B,CAAC;AACD,UAAI,CAAC,SAAS,IAAI;AAChB,eAAO;AAAA,UACL;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS,sBAAsB,SAC3B;AAAA,YACE,eAAe;AAAA,cACb,KAAK,IAAI,GAAG,KAAK,KAAK,SAAS,iBAAiB,CAAC;AAAA,YACnD;AAAA,UACF,IACA;AAAA,QACN;AAAA,MACF;AAAA,IACF;AACA,UAAM,UAAU,MAAM,WAAW,oBAAoB;AAAA,MACnD,GAAI,QAAQ,gBAAgB,MAAM,QAAQ,cAAc,OAAO,IAAI,CAAC;AAAA,MACpE,WAAW,QAAQ;AAAA,IACrB,CAAC;AACD,UAAM,YAA2B;AAAA,MAC/B,WAAW,QAAQ;AAAA,MACnB,aAAa,QAAQ;AAAA,MACrB,YAAY,QAAQ;AAAA,MACpB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;AAAA,MACtD,GAAI,QAAQ,YAAY,EAAE,WAAW,QAAQ,UAAU,IAAI,CAAC;AAAA,MAC5D,GAAI,QAAQ,wBACR,EAAE,uBAAuB,QAAQ,sBAAsB,IACvD,CAAC;AAAA,MACL,GAAI,QAAQ,SAAS,EAAE,QAAQ,QAAQ,OAAO,IAAI,CAAC;AAAA,IACrD;AACA,UAAM,QAAQ,YAAY,QAAQ,SAAS,IACvC;AAAA,MACE,IAAI,QAAQ;AAAA,MACZ,eAAe,MAAM,WAAW;AAAA,QAC9B,QAAQ;AAAA,MACV;AAAA,IACF,IACA;AACJ,WAAO,KAAK,EAAE,SAAS,WAAW,MAAM,GAAG,KAAK;AAAA,MAC9C,cAAc,MAAM,cAAc,SAAS,SAAS,OAAO;AAAA,IAC7D,CAAC;AAAA,EACH;AAEA,WAAS,WAAqB;AAC5B,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAEA,iBAAe,eACb,SACA,SAC4B;AAC5B,UAAM,SAAS,IAAI,IAAI,QAAQ,GAAG,EAAE,aAAa,OAAO,WAAW;AACnE,UAAM,YAAY,OAAO,WAAW,IAAI,OAAO,CAAC,IAAI;AACpD,QAAI,CAAC,YAAY,SAAS,GAAG;AAC3B,aAAO;AAAA,QACL;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,IACF;AACA,QAAI,CAAE,MAAM,YAAY,SAAS,WAAW,OAAO,GAAI;AACrD,aAAO,SAAS;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AAEA,iBAAe,UACb,SACA,SACmB;AACnB,UAAM,YAAY,MAAM,eAAe,SAAS,OAAO;AACvD,QAAI,qBAAqB,SAAU,QAAO;AAC1C,UAAM,SAAS,MAAM,WAAW,uBAAuB,SAAS;AAEhE,WAAO,KAAK;AAAA,MACV,QAAQ,OAAO;AAAA,MACf,GAAI,OAAO,wBACP,EAAE,uBAAuB,OAAO,sBAAsB,IACtD,CAAC;AAAA,IACP,CAAC;AAAA,EACH;AAEA,iBAAe,QACb,SACA,SACmB;AACnB,UAAM,YAAY,MAAM,eAAe,SAAS,OAAO;AACvD,QAAI,qBAAqB,SAAU,QAAO;AAC1C,UAAM,SAAS,QAAQ,OACnB,MAAM,QAAQ,KAAK,EAAE,YAAY,WAAW,QAAQ,CAAC,IACrD,MAAM,WAAW,iBAAiB,EAAE,UAAU,CAAC;AACnD,WAAO,KAAK,MAAM;AAAA,EACpB;AAEA,SAAO,eAAe,OAAO,SAAqC;AAChE,UAAM,WAAW,IAAI,IAAI,QAAQ,GAAG,EAAE,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACxE,UAAM,SAAS,SAAS,SAAS,SAAS,CAAC;AAC3C,UAAM,SAAS,QAAQ,OAAO,YAAY;AAC1C,UAAM,QAAQ,CAAC,aACb,WAAW,WACP,OACA,cAAc,KAAK,sBAAsB,OAAO,QAAQ,KAAK;AAAA,MAC3D,OAAO;AAAA,IACT,CAAC;AACP,QAAI;AACF,UAAI,WAAW,UAAU;AACvB,eAAO,MAAM,KAAK,KAAK,KAAK,OAAO,CAAC;AAAA,MACtC;AACA,UAAI,WAAW,aAAa,WAAW,YAAY,WAAW,QAAQ;AACpE,eAAO,cAAc,KAAK,aAAa,wBAAwB;AAAA,MACjE;AACA,YAAM,cAAc,MAAM,WAAW,YAAY,SAAS,KAAK;AAC/D,UAAI,YAAa,QAAO;AACxB,UAAI,WAAW,aAAa,cAAc,OAAO,GAAG;AAClD,eAAO,cAAc,KAAK,oBAAoB,qBAAqB;AAAA,MACrE;AACA,YAAM,UAAU,MAAM,YAAY,OAAO;AACzC,UAAI,YAAY,MAAM;AACpB,eAAO,cAAc,KAAK,mBAAmB,gBAAgB;AAAA,MAC/D;AACA,UAAI,WAAW,UAAW,QAAO,MAAM,YAAY,SAAS,OAAO;AACnE,UAAI,WAAW,SAAU,QAAO,MAAM,UAAU,SAAS,OAAO;AAChE,aAAO,MAAM,QAAQ,SAAS,OAAO;AAAA,IACvC,SAAS,OAAO;AACd,aAAO,SAAS,KAAK;AAAA,IACvB;AAAA,EACF;AACF;AAEA,SAAS,SAAS,OAA0B;AAC1C,MAAI,iBAAiB,wBAAwB;AAC3C,WAAO,cAAc,KAAK,gBAAgB,MAAM,OAAO;AAAA,EACzD;AACA,MAAI,iBAAiB,uBAAuB;AAC1C,WAAO,cAAc,KAAK,sBAAsB,MAAM,OAAO;AAAA,EAC/D;AACA,MAAI,iBAAiB,sBAAsB;AACzC,WAAO,cAAc,KAAK,oBAAoB,MAAM,OAAO;AAAA,EAC7D;AACA,MAAI,iBAAiB,yBAAyB;AAC5C,WAAO,cAAc,KAAK,yBAAyB,MAAM,OAAO;AAAA,EAClE;AACA,MAAI,iBAAiB,mBAAmB;AACtC,WAAO,cAAc,KAAK,mBAAmB,MAAM,OAAO;AAAA,EAC5D;AAEA,SAAO,cAAc,KAAK,kBAAkB,uBAAuB;AACrE;AAuCO,SAAS,cACd,SACA,UAAgC,CAAC,GAC+B;AAChE,SAAO,OAAO,KAAK,QAAQ;AACzB,UAAM,SAAS,CAAC,SAAqC;AACnD,YAAM,QAAQ,IAAI,QAAQ,IAAI;AAC9B,aAAO,MAAM,QAAQ,KAAK,IAAI,MAAM,CAAC,IAAI;AAAA,IAC3C;AACA,UAAM,SACJ,QAAQ,UACR,GAAG,OAAO,mBAAmB,GAAG,MAAM,GAAG,EAAE,CAAC,GAAG,KAAK,MAAM,IAAI,QAAQ,YAAY,UAAU,OAAO,MAAM,OAAO,MAAM,KAAK,WAAW;AACxI,UAAM,MAAM,IAAI,IAAI,IAAI,eAAe,IAAI,OAAO,KAAK,MAAM;AAC7D,UAAM,UAAU,IAAI,UAAU,OAAO,YAAY;AACjD,UAAM,UAAU,IAAI,QAAQ;AAC5B,eAAW,CAAC,MAAM,KAAK,KAAK,OAAO,QAAQ,IAAI,OAAO,GAAG;AACvD,UAAI,UAAU,OAAW;AACzB,cAAQ,IAAI,MAAM,MAAM,QAAQ,KAAK,IAAI,MAAM,KAAK,IAAI,IAAI,KAAK;AAAA,IACnE;AACA,QAAI;AACJ,QAAI,WAAW,SAAS,WAAW,QAAQ;AACzC,UAAI,IAAI,SAAS,QAAW;AAC1B,eACE,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO,KAAK,UAAU,IAAI,IAAI;AAAA,MACrE,OAAO;AACL,cAAM,UAAU,IAAI,YAAY;AAChC,eAAO;AACP,yBAAiB,SAAS,KAAK;AAC7B,kBACE,OAAO,UAAU,WACb,QACA,QAAQ,OAAO,OAAO,EAAE,QAAQ,KAAK,CAAC;AAC5C,cAAI,KAAK,SAAS,eAAgB;AAAA,QACpC;AACA,gBAAQ,QAAQ,OAAO;AAAA,MACzB;AAAA,IACF;AACA,UAAM,UAAU,IAAI,QAAQ,KAAK,EAAE,QAAQ,SAAS,KAAK,CAAC;AAC1D,QAAI,IAAI,QAAQ,eAAe;AAC7B,0BAAoB,IAAI,SAAS,IAAI,OAAO,aAAa;AAAA,IAC3D;AACA,UAAM,WAAW,MAAM,QAAQ,OAAO;AACtC,QAAI,aAAa,SAAS;AAC1B,aAAS,QAAQ,QAAQ,CAAC,OAAO,SAAS;AACxC,UAAI,SAAS,aAAc,KAAI,UAAU,MAAM,KAAK;AAAA,IACtD,CAAC;AACD,UAAM,UAAU,SAAS,QAAQ,aAAa;AAC9C,QAAI,QAAQ,SAAS,EAAG,KAAI,UAAU,cAAc,OAAO;AAC3D,QAAI,IAAI,IAAI,WAAW,MAAM,SAAS,YAAY,CAAC,CAAC;AAAA,EACtD;AACF;","names":[]}
@@ -0,0 +1 @@
1
+ export {};