@opendatalabs/vana-sdk 4.3.2 → 4.4.0-connect-sheet.0-pr.218.7f5d342

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 +178 -2
  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 +221 -0
  9. package/dist/connect/chrome.cjs.map +1 -0
  10. package/dist/connect/chrome.d.ts +40 -0
  11. package/dist/connect/chrome.js +193 -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 +224 -0
  20. package/dist/connect/launcher.cjs.map +1 -0
  21. package/dist/connect/launcher.d.ts +122 -0
  22. package/dist/connect/launcher.js +210 -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 +244 -0
  26. package/dist/connect/protocol.cjs.map +1 -0
  27. package/dist/connect/protocol.d.ts +307 -0
  28. package/dist/connect/protocol.js +203 -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
package/README.md CHANGED
@@ -261,6 +261,49 @@ accepts write entries in `scopes`, but its scope part must be a concrete
261
261
  scope: this flow reads approved scopes back one at a time, so wildcards are
262
262
  rejected there for read and write alike.
263
263
 
264
+ ### PDPP grants
265
+
266
+ Ask for PDPP data as **grants**: a registry `source_id` and the streams you
267
+ want from it. The controller turns each stream into one scope key,
268
+ `pdpp.<source>.<stream>`, and that key is what the request, the signed grant
269
+ and your reads carry.
270
+
271
+ ```typescript
272
+ export const vana = createDirectDataController({
273
+ // ...
274
+ source: "chatgpt",
275
+ grants: [
276
+ {
277
+ source_id: "https://registry.pdpp.dev/sources/chatgpt",
278
+ streams: ["conversations", "messages"],
279
+ },
280
+ ],
281
+ });
282
+ // Requests ["pdpp.chatgpt.conversations", "pdpp.chatgpt.messages"].
283
+
284
+ await vana.readApprovedData({ requestId, scope: "pdpp.chatgpt.messages" });
285
+ ```
286
+
287
+ - `grants` and `scopes` can be combined. The request is the union: your
288
+ `scopes` verbatim, then the derived keys that are not already in it.
289
+ Existing apps that pass only `scopes` are unchanged.
290
+ - The `pdpp.` namespace is separate from legacy scopes. PDPP's ChatGPT
291
+ `conversations` stream is `pdpp.chatgpt.conversations`, not the legacy
292
+ `chatgpt.conversations`, and `pdpp.chatgpt.*` covers only PDPP streams.
293
+ A legacy `scopes` entry under `pdpp.` that is not a valid key is rejected.
294
+ - The mapping is strict and never slugifies. `source_id` must be
295
+ `https://registry.pdpp.dev/sources/<source>`, and `<source>` and each stream
296
+ must be lowercase letters, digits and underscores, not starting with an
297
+ underscore. Anything else throws `InvalidPdppGrantError` when the controller
298
+ is created.
299
+ - `pdppScopeKey(source_id, stream)`, `parsePdppScopeKey(key)` (`{ source_id,
300
+ stream }` or `null`), `isPdppScopeKey(key)`, `pdppGrantsToScopes(grants)`
301
+ and `grantRequestScopes({ grants, scopes })` are exported from `/server`,
302
+ the root entry points and `@opendatalabs/vana-sdk/protocol/scopes`. Use them
303
+ rather than building keys by hand. Other implementations can check
304
+ themselves against the vectors in
305
+ [`src/protocol/fixtures/pdpp-scope-keys.json`](src/protocol/fixtures/pdpp-scope-keys.json).
306
+
264
307
  ### Backend controller
265
308
 
266
309
  ```typescript
@@ -457,10 +500,143 @@ pass the same explicit `idempotencyKey` on the retry to avoid a duplicate DCR.
457
500
  `react` is an optional peer dependency. The underlying
458
501
  `createDirectConnectFlow` store is also exported for non-React frontends.
459
502
 
503
+ ### Connect in Vana's in-page sheet
504
+
505
+ Instead of a Vana tab, the person can approve in a sheet Vana draws over your
506
+ page (it signs them in, shows the request, and finishes inside the sheet). The
507
+ sheet is `presentation: "sheet"` of the same flow: the request, the status poll
508
+ and the read are unchanged. The integration is two files.
509
+
510
+ Backend: one catch-all route serves `config`, `request`, `status` and `data`,
511
+ binding each request to the browser that created it. The controller decides
512
+ what is requested (its `grants` and `scopes`, see [PDPP grants](#pdpp-grants));
513
+ the browser never chooses.
514
+
515
+ ```ts
516
+ // app/api/vana/[action]/route.ts
517
+ import { createVanaConnectHandler } from "@opendatalabs/vana-sdk/server";
518
+ import { vana } from "@/lib/vana"; // your createDirectDataController({ grants, ... })
519
+
520
+ const handler = createVanaConnectHandler({
521
+ controller: vana,
522
+ returnUrl: `${process.env.APP_URL}/connect/return`,
523
+ clientId: "my-app", // the embed client id Vana Account registered for your origin
524
+ // Recommended in production: only your signed-in users create requests.
525
+ session: async (request) => (await getSession(request))?.userId ?? null,
526
+ });
527
+ export { handler as GET, handler as POST };
528
+ ```
529
+
530
+ The handler reports the Vana Account origin from the controller's `env`
531
+ (`account.vana.org` for `production`, `account-dev.vana.org` for `dev`), so a
532
+ dev app never frames production.
533
+
534
+ Frontend:
535
+
536
+ ```tsx
537
+ "use client";
538
+ import { useVanaConnect } from "@opendatalabs/vana-sdk/react";
539
+
540
+ export function ConnectButton() {
541
+ const { state, start } = useVanaConnect<MyData>({ endpoint: "/api/vana" });
542
+ if (state.type === "done") return <Result data={state.result.data} />;
543
+ return (
544
+ <>
545
+ <button
546
+ disabled={
547
+ state.type === "creating" || state.type === "awaiting_approval"
548
+ }
549
+ onClick={start}
550
+ >
551
+ Connect with Vana
552
+ </button>
553
+ {state.type === "awaiting_approval" && state.popupBlocked && (
554
+ // The sheet could not run here (state.sheetUnavailable says why).
555
+ <a href={state.request.approvalUrl} target="_blank" rel="noreferrer">
556
+ Open Vana
557
+ </a>
558
+ )}
559
+ </>
560
+ );
561
+ }
562
+ ```
563
+
564
+ - While the sheet is up, `state.sheet` reports `completion` (`in_browser` or
565
+ `remote_collect`) once the person approves, coarse `progress`
566
+ (`collecting`, `writing`), and any `error` it is showing.
567
+ - **Approval is not delivery.** The sheet's `connected` event (and
568
+ `state.sheet.completion`) means the person approved; the data may still be
569
+ collecting. The flow reaches `done` only when your backend's status poll
570
+ says the request is readable and the read succeeds, and `error` when the
571
+ poll reports `denied`, `expired` or `completed` or the read fails. The sheet
572
+ sends no terminal event of its own; its events only make the flow poll
573
+ sooner.
574
+ - Closing the sheet before approving returns to `idle`. Closing it after
575
+ approving keeps polling, since the data may still be readable.
576
+ - If the frame cannot run (`frame_blocked`, `origin_not_registered`,
577
+ `incompatible_version`), the flow falls back to the tab presentation with
578
+ `popupBlocked: true`, so the "Open approval" link above still finishes it.
579
+ `origin_not_registered` arrives at once: the launcher asks Vana
580
+ (`GET <account>/api/embed/connect/client`, no credentials) alongside
581
+ framing. `vana_unavailable` means Vana's own side is down or misconfigured;
582
+ the sheet stays open and says so.
583
+ - The partner reads; it cannot write through the sheet. Pass
584
+ `sheet: { closeAfterDoneMs: null }` to leave the sheet open after the read.
585
+ - If your page sets a CSP, allow the frame and the client check:
586
+ `vanaConnectCsp(env)` from `/server` (or `/connect/protocol`) returns
587
+ `{ "frame-src": [accountOrigin], "connect-src": [accountOrigin] }`. It has
588
+ no default environment.
589
+ - `createVanaConnectHandler` takes `session` to bind requests to your own
590
+ signed-in user (recommended: without it anyone who loads your page can
591
+ create requests as your app), `createOptions` for per-request create input
592
+ (questions, owner), and `read` to replace the default `readApprovedData`.
593
+ Use `toNodeHandler(handler)` for Express or plain `http`.
594
+ - Creating a request is rate limited. The default, `createMemoryRateLimit()`,
595
+ allows 10 creates a minute per client IP and per session, in memory. It is
596
+ not production-grade (each instance counts alone, and the IP comes from
597
+ proxy headers): pass `rateLimit` backed by a shared store, or `false` when
598
+ a limiter already sits in front of the route. A refusal is
599
+ `429 rate_limited` with `Retry-After`.
600
+
601
+ Choosing the Vana Account origin yourself (a custom flow, or preview deploys
602
+ of Vana Account) goes through `resolveVanaAccountOrigin`; never match origins
603
+ with your own pattern. It returns the environment's origin unless overrides
604
+ are switched on for this build and the override is exactly an `https` origin
605
+ on your allowlist:
606
+
607
+ ```ts
608
+ import {
609
+ parseOriginAllowlist,
610
+ resolveVanaAccountOrigin,
611
+ } from "@opendatalabs/vana-sdk/connect";
612
+
613
+ const accountOrigin = resolveVanaAccountOrigin({
614
+ env: "dev",
615
+ override: new URLSearchParams(location.search).get("vana_account"),
616
+ previewOrigins: parseOriginAllowlist(
617
+ process.env.NEXT_PUBLIC_VANA_ACCOUNT_PREVIEW_ORIGINS,
618
+ ),
619
+ // From build-time config, never the request. Off in production.
620
+ allowOverride: process.env.NEXT_PUBLIC_DEPLOY_ENV === "preview",
621
+ });
622
+ ```
623
+
624
+ `createDirectConnectFlow` and the launcher take that origin (or an `env`)
625
+ explicitly; neither falls back to production.
626
+
627
+ Lower-level pieces: `createDirectConnectFlow(transports, { presentation:
628
+ "sheet", sheet: { clientId, appName, accountOrigin } })` with an
629
+ `authorizeRequest` transport backed by
630
+ `controller.authorizeRequestRead(requestId)`; the launcher itself
631
+ (`@opendatalabs/vana-sdk/connect`); and the zero-dependency message protocol
632
+ (`@opendatalabs/vana-sdk/connect/protocol`). A backend in another language
633
+ implements the [connect wire contract](./src/connect/WIRE_CONTRACT.md), which has test
634
+ vectors.
635
+
460
636
  ### Test with large sample data
461
637
 
462
638
  When testing with realistic exports, use the public fixture catalog in
463
- [`vana-com/data-connectors`](https://github.com/vana-com/data-connectors). Keep
639
+ [`PDP-Connect/data-connectors`](https://github.com/PDP-Connect/data-connectors). Keep
464
640
  the payload in a file or raw URL and point your app or agent at that location.
465
641
  Do not paste large JSON into the terminal.
466
642
 
@@ -471,7 +647,7 @@ still calls `readApprovedData`.
471
647
 
472
648
  See [`examples/vana-app`](../../examples/vana-app) for a runnable Next.js Vana
473
649
  app. It includes the route handlers, return page, and React connect button from
474
- this flow, defaults to sample-data mode using `vana-com/data-connectors`, and
650
+ this flow, defaults to sample-data mode using `PDP-Connect/data-connectors`, and
475
651
  can be switched to live protocol mode with environment variables.
476
652
 
477
653
  ## Write into a Personal Server
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var account_origin_exports = {};
20
+ __export(account_origin_exports, {
21
+ parseOriginAllowlist: () => parseOriginAllowlist,
22
+ resolveVanaAccountOrigin: () => resolveVanaAccountOrigin
23
+ });
24
+ module.exports = __toCommonJS(account_origin_exports);
25
+ var import_protocol = require("./protocol");
26
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
27
+ const HOSTNAME = /^(?:\[[0-9a-f:.]+\]|[a-z0-9-]+(?:\.[a-z0-9-]+)*)$/;
28
+ function exactOrigin(value, protocol) {
29
+ let url;
30
+ try {
31
+ url = new URL(value);
32
+ } catch {
33
+ return null;
34
+ }
35
+ return url.protocol === protocol && url.origin === value && HOSTNAME.test(url.hostname) ? value : null;
36
+ }
37
+ function parseOriginAllowlist(csv) {
38
+ if (!csv) return [];
39
+ const origins = [];
40
+ for (const entry of csv.split(",")) {
41
+ const origin = exactOrigin(entry.trim(), "https:");
42
+ if (origin && !origins.includes(origin)) origins.push(origin);
43
+ }
44
+ return origins;
45
+ }
46
+ function resolveVanaAccountOrigin(input) {
47
+ if (input.env !== "production" && input.env !== "dev") {
48
+ throw new TypeError('env must be "production" or "dev"');
49
+ }
50
+ const fallback = import_protocol.VANA_ACCOUNT_ORIGINS[input.env];
51
+ const { override } = input;
52
+ if (input.allowOverride !== true || typeof override !== "string") {
53
+ return fallback;
54
+ }
55
+ const https = exactOrigin(override, "https:");
56
+ if (https && (input.previewOrigins ?? []).includes(https)) {
57
+ return https;
58
+ }
59
+ if (input.allowLocal === true) {
60
+ const local = exactOrigin(override, "http:");
61
+ if (local && LOOPBACK_HOSTS.has(new URL(local).hostname)) {
62
+ return local;
63
+ }
64
+ }
65
+ return fallback;
66
+ }
67
+ // Annotate the CommonJS export names for ESM import in node:
68
+ 0 && (module.exports = {
69
+ parseOriginAllowlist,
70
+ resolveVanaAccountOrigin
71
+ });
72
+ //# sourceMappingURL=account-origin.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/connect/account-origin.ts"],"sourcesContent":["/**\n * Which Vana Account origin serves the connect sheet.\n *\n * @remarks\n * The sheet origin decides who the person signs in to and who receives the\n * partner's request capability, so it must never come from a pattern the\n * page URL can satisfy. {@link resolveVanaAccountOrigin} returns the\n * environment's Vana Account origin and accepts an override only when the\n * caller switched overrides on and the override is an exact origin from an\n * explicit allowlist. There is deliberately no pattern or wildcard option.\n *\n * Zero-dependency and browser-safe, like the protocol it builds on.\n *\n * @category Connect\n * @module connect/account-origin\n */\n\nimport { VANA_ACCOUNT_ORIGINS, type VanaConnectEnv } from \"./protocol\";\n\n/** Input for {@link resolveVanaAccountOrigin}. */\nexport interface ResolveVanaAccountOriginInput {\n /** The deployment environment the app talks to. Required: there is no default. */\n env: VanaConnectEnv;\n /**\n * A requested Account origin, typically read from a query parameter on a\n * preview deploy. Untrusted. Ignored unless {@link allowOverride} is true.\n */\n override?: string | null;\n /**\n * Exact `https` origins (`https://host[:port]`, no path, no trailing slash)\n * an override may name. Build it with {@link parseOriginAllowlist}. Entries\n * that are not exact https origins never match.\n */\n previewOrigins?: readonly string[];\n /**\n * Whether this build honours an override at all. Decide it from build-time\n * configuration (a non-production deploy), never from the request. Keep it\n * `false` in production.\n */\n allowOverride: boolean;\n /**\n * Also accept an exact `http` loopback origin (`localhost`, `127.0.0.1`,\n * `[::1]`, any port) without listing it, for a locally run Vana Account.\n * Only applies when {@link allowOverride} is true. Defaults to `false`.\n */\n allowLocal?: boolean;\n}\n\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n/** DNS labels (punycode included) or a bracketed IPv6 literal; no wildcards. */\nconst HOSTNAME = /^(?:\\[[0-9a-f:.]+\\]|[a-z0-9-]+(?:\\.[a-z0-9-]+)*)$/;\n\n/** `value` itself when it is a bare origin with `protocol`, else `null`. */\nfunction exactOrigin(\n value: string,\n protocol: \"https:\" | \"http:\",\n): string | null {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n // `url.origin === value` rejects paths, queries, fragments, credentials,\n // trailing slashes and any spelling the URL parser would rewrite.\n return url.protocol === protocol &&\n url.origin === value &&\n HOSTNAME.test(url.hostname)\n ? value\n : null;\n}\n\n/**\n * Parse a comma-separated allowlist of exact `https` origins.\n *\n * @remarks\n * Whitespace around entries is trimmed. An entry that is not an exact\n * `https://host[:port]` origin (a path, a trailing slash, `http:`, a\n * wildcard such as `https://*.vercel.app`) is dropped, so a typo narrows\n * the list instead of widening it.\n *\n * @example\n * ```typescript\n * parseOriginAllowlist(process.env.VANA_ACCOUNT_PREVIEW_ORIGINS);\n * // [\"https://account-git-my-branch.vercel.app\"]\n * ```\n *\n * @param csv - The raw list, for example an environment variable.\n * @returns The exact https origins, deduplicated, in order.\n */\nexport function parseOriginAllowlist(csv: string | null | undefined): string[] {\n if (!csv) return [];\n const origins: string[] = [];\n for (const entry of csv.split(\",\")) {\n const origin = exactOrigin(entry.trim(), \"https:\");\n if (origin && !origins.includes(origin)) origins.push(origin);\n }\n return origins;\n}\n\n/**\n * Resolve the Vana Account origin that serves the connect sheet.\n *\n * @remarks\n * Returns `VANA_ACCOUNT_ORIGINS[env]` unless `allowOverride` is true and\n * `override` is exactly one of `previewOrigins` (an `https` origin), or,\n * with `allowLocal`, exactly an `http` loopback origin. Anything else (a\n * path, a different scheme, a near-miss host, a lookalike) falls back to the\n * environment's origin. Matching is exact string equality: no patterns, no\n * suffixes, no normalization that could widen a match.\n *\n * @example\n * ```typescript\n * const accountOrigin = resolveVanaAccountOrigin({\n * env: \"dev\",\n * override: new URLSearchParams(location.search).get(\"vana_account\"),\n * previewOrigins: parseOriginAllowlist(\n * process.env.NEXT_PUBLIC_VANA_ACCOUNT_PREVIEW_ORIGINS,\n * ),\n * allowOverride: process.env.NEXT_PUBLIC_DEPLOY_ENV === \"preview\",\n * });\n * ```\n *\n * @param input - Environment, untrusted override, allowlist and switches.\n * @returns A bare Vana Account origin.\n * @throws TypeError when `env` is not `\"production\"` or `\"dev\"`.\n */\nexport function resolveVanaAccountOrigin(\n input: ResolveVanaAccountOriginInput,\n): string {\n if (input.env !== \"production\" && input.env !== \"dev\") {\n throw new TypeError('env must be \"production\" or \"dev\"');\n }\n const fallback: string = VANA_ACCOUNT_ORIGINS[input.env];\n const { override } = input;\n if (input.allowOverride !== true || typeof override !== \"string\") {\n return fallback;\n }\n const https = exactOrigin(override, \"https:\");\n if (https && (input.previewOrigins ?? []).includes(https)) {\n return https;\n }\n if (input.allowLocal === true) {\n const local = exactOrigin(override, \"http:\");\n if (local && LOOPBACK_HOSTS.has(new URL(local).hostname)) {\n return local;\n }\n }\n return fallback;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAiBA,sBAA0D;AA+B1D,MAAM,iBAAiB,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAElE,MAAM,WAAW;AAGjB,SAAS,YACP,OACA,UACe;AACf,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,KAAK;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AAGA,SAAO,IAAI,aAAa,YACtB,IAAI,WAAW,SACf,SAAS,KAAK,IAAI,QAAQ,IACxB,QACA;AACN;AAoBO,SAAS,qBAAqB,KAA0C;AAC7E,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,UAAoB,CAAC;AAC3B,aAAW,SAAS,IAAI,MAAM,GAAG,GAAG;AAClC,UAAM,SAAS,YAAY,MAAM,KAAK,GAAG,QAAQ;AACjD,QAAI,UAAU,CAAC,QAAQ,SAAS,MAAM,EAAG,SAAQ,KAAK,MAAM;AAAA,EAC9D;AACA,SAAO;AACT;AA6BO,SAAS,yBACd,OACQ;AACR,MAAI,MAAM,QAAQ,gBAAgB,MAAM,QAAQ,OAAO;AACrD,UAAM,IAAI,UAAU,mCAAmC;AAAA,EACzD;AACA,QAAM,WAAmB,qCAAqB,MAAM,GAAG;AACvD,QAAM,EAAE,SAAS,IAAI;AACrB,MAAI,MAAM,kBAAkB,QAAQ,OAAO,aAAa,UAAU;AAChE,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,YAAY,UAAU,QAAQ;AAC5C,MAAI,UAAU,MAAM,kBAAkB,CAAC,GAAG,SAAS,KAAK,GAAG;AACzD,WAAO;AAAA,EACT;AACA,MAAI,MAAM,eAAe,MAAM;AAC7B,UAAM,QAAQ,YAAY,UAAU,OAAO;AAC3C,QAAI,SAAS,eAAe,IAAI,IAAI,IAAI,KAAK,EAAE,QAAQ,GAAG;AACxD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Which Vana Account origin serves the connect sheet.
3
+ *
4
+ * @remarks
5
+ * The sheet origin decides who the person signs in to and who receives the
6
+ * partner's request capability, so it must never come from a pattern the
7
+ * page URL can satisfy. {@link resolveVanaAccountOrigin} returns the
8
+ * environment's Vana Account origin and accepts an override only when the
9
+ * caller switched overrides on and the override is an exact origin from an
10
+ * explicit allowlist. There is deliberately no pattern or wildcard option.
11
+ *
12
+ * Zero-dependency and browser-safe, like the protocol it builds on.
13
+ *
14
+ * @category Connect
15
+ * @module connect/account-origin
16
+ */
17
+ import { type VanaConnectEnv } from "./protocol.js";
18
+ /** Input for {@link resolveVanaAccountOrigin}. */
19
+ export interface ResolveVanaAccountOriginInput {
20
+ /** The deployment environment the app talks to. Required: there is no default. */
21
+ env: VanaConnectEnv;
22
+ /**
23
+ * A requested Account origin, typically read from a query parameter on a
24
+ * preview deploy. Untrusted. Ignored unless {@link allowOverride} is true.
25
+ */
26
+ override?: string | null;
27
+ /**
28
+ * Exact `https` origins (`https://host[:port]`, no path, no trailing slash)
29
+ * an override may name. Build it with {@link parseOriginAllowlist}. Entries
30
+ * that are not exact https origins never match.
31
+ */
32
+ previewOrigins?: readonly string[];
33
+ /**
34
+ * Whether this build honours an override at all. Decide it from build-time
35
+ * configuration (a non-production deploy), never from the request. Keep it
36
+ * `false` in production.
37
+ */
38
+ allowOverride: boolean;
39
+ /**
40
+ * Also accept an exact `http` loopback origin (`localhost`, `127.0.0.1`,
41
+ * `[::1]`, any port) without listing it, for a locally run Vana Account.
42
+ * Only applies when {@link allowOverride} is true. Defaults to `false`.
43
+ */
44
+ allowLocal?: boolean;
45
+ }
46
+ /**
47
+ * Parse a comma-separated allowlist of exact `https` origins.
48
+ *
49
+ * @remarks
50
+ * Whitespace around entries is trimmed. An entry that is not an exact
51
+ * `https://host[:port]` origin (a path, a trailing slash, `http:`, a
52
+ * wildcard such as `https://*.vercel.app`) is dropped, so a typo narrows
53
+ * the list instead of widening it.
54
+ *
55
+ * @example
56
+ * ```typescript
57
+ * parseOriginAllowlist(process.env.VANA_ACCOUNT_PREVIEW_ORIGINS);
58
+ * // ["https://account-git-my-branch.vercel.app"]
59
+ * ```
60
+ *
61
+ * @param csv - The raw list, for example an environment variable.
62
+ * @returns The exact https origins, deduplicated, in order.
63
+ */
64
+ export declare function parseOriginAllowlist(csv: string | null | undefined): string[];
65
+ /**
66
+ * Resolve the Vana Account origin that serves the connect sheet.
67
+ *
68
+ * @remarks
69
+ * Returns `VANA_ACCOUNT_ORIGINS[env]` unless `allowOverride` is true and
70
+ * `override` is exactly one of `previewOrigins` (an `https` origin), or,
71
+ * with `allowLocal`, exactly an `http` loopback origin. Anything else (a
72
+ * path, a different scheme, a near-miss host, a lookalike) falls back to the
73
+ * environment's origin. Matching is exact string equality: no patterns, no
74
+ * suffixes, no normalization that could widen a match.
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const accountOrigin = resolveVanaAccountOrigin({
79
+ * env: "dev",
80
+ * override: new URLSearchParams(location.search).get("vana_account"),
81
+ * previewOrigins: parseOriginAllowlist(
82
+ * process.env.NEXT_PUBLIC_VANA_ACCOUNT_PREVIEW_ORIGINS,
83
+ * ),
84
+ * allowOverride: process.env.NEXT_PUBLIC_DEPLOY_ENV === "preview",
85
+ * });
86
+ * ```
87
+ *
88
+ * @param input - Environment, untrusted override, allowlist and switches.
89
+ * @returns A bare Vana Account origin.
90
+ * @throws TypeError when `env` is not `"production"` or `"dev"`.
91
+ */
92
+ export declare function resolveVanaAccountOrigin(input: ResolveVanaAccountOriginInput): string;
@@ -0,0 +1,47 @@
1
+ import { VANA_ACCOUNT_ORIGINS } from "./protocol.js";
2
+ const LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "127.0.0.1", "[::1]"]);
3
+ const HOSTNAME = /^(?:\[[0-9a-f:.]+\]|[a-z0-9-]+(?:\.[a-z0-9-]+)*)$/;
4
+ function exactOrigin(value, protocol) {
5
+ let url;
6
+ try {
7
+ url = new URL(value);
8
+ } catch {
9
+ return null;
10
+ }
11
+ return url.protocol === protocol && url.origin === value && HOSTNAME.test(url.hostname) ? value : null;
12
+ }
13
+ function parseOriginAllowlist(csv) {
14
+ if (!csv) return [];
15
+ const origins = [];
16
+ for (const entry of csv.split(",")) {
17
+ const origin = exactOrigin(entry.trim(), "https:");
18
+ if (origin && !origins.includes(origin)) origins.push(origin);
19
+ }
20
+ return origins;
21
+ }
22
+ function resolveVanaAccountOrigin(input) {
23
+ if (input.env !== "production" && input.env !== "dev") {
24
+ throw new TypeError('env must be "production" or "dev"');
25
+ }
26
+ const fallback = VANA_ACCOUNT_ORIGINS[input.env];
27
+ const { override } = input;
28
+ if (input.allowOverride !== true || typeof override !== "string") {
29
+ return fallback;
30
+ }
31
+ const https = exactOrigin(override, "https:");
32
+ if (https && (input.previewOrigins ?? []).includes(https)) {
33
+ return https;
34
+ }
35
+ if (input.allowLocal === true) {
36
+ const local = exactOrigin(override, "http:");
37
+ if (local && LOOPBACK_HOSTS.has(new URL(local).hostname)) {
38
+ return local;
39
+ }
40
+ }
41
+ return fallback;
42
+ }
43
+ export {
44
+ parseOriginAllowlist,
45
+ resolveVanaAccountOrigin
46
+ };
47
+ //# sourceMappingURL=account-origin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/connect/account-origin.ts"],"sourcesContent":["/**\n * Which Vana Account origin serves the connect sheet.\n *\n * @remarks\n * The sheet origin decides who the person signs in to and who receives the\n * partner's request capability, so it must never come from a pattern the\n * page URL can satisfy. {@link resolveVanaAccountOrigin} returns the\n * environment's Vana Account origin and accepts an override only when the\n * caller switched overrides on and the override is an exact origin from an\n * explicit allowlist. There is deliberately no pattern or wildcard option.\n *\n * Zero-dependency and browser-safe, like the protocol it builds on.\n *\n * @category Connect\n * @module connect/account-origin\n */\n\nimport { VANA_ACCOUNT_ORIGINS, type VanaConnectEnv } from \"./protocol\";\n\n/** Input for {@link resolveVanaAccountOrigin}. */\nexport interface ResolveVanaAccountOriginInput {\n /** The deployment environment the app talks to. Required: there is no default. */\n env: VanaConnectEnv;\n /**\n * A requested Account origin, typically read from a query parameter on a\n * preview deploy. Untrusted. Ignored unless {@link allowOverride} is true.\n */\n override?: string | null;\n /**\n * Exact `https` origins (`https://host[:port]`, no path, no trailing slash)\n * an override may name. Build it with {@link parseOriginAllowlist}. Entries\n * that are not exact https origins never match.\n */\n previewOrigins?: readonly string[];\n /**\n * Whether this build honours an override at all. Decide it from build-time\n * configuration (a non-production deploy), never from the request. Keep it\n * `false` in production.\n */\n allowOverride: boolean;\n /**\n * Also accept an exact `http` loopback origin (`localhost`, `127.0.0.1`,\n * `[::1]`, any port) without listing it, for a locally run Vana Account.\n * Only applies when {@link allowOverride} is true. Defaults to `false`.\n */\n allowLocal?: boolean;\n}\n\nconst LOOPBACK_HOSTS = new Set([\"localhost\", \"127.0.0.1\", \"[::1]\"]);\n/** DNS labels (punycode included) or a bracketed IPv6 literal; no wildcards. */\nconst HOSTNAME = /^(?:\\[[0-9a-f:.]+\\]|[a-z0-9-]+(?:\\.[a-z0-9-]+)*)$/;\n\n/** `value` itself when it is a bare origin with `protocol`, else `null`. */\nfunction exactOrigin(\n value: string,\n protocol: \"https:\" | \"http:\",\n): string | null {\n let url: URL;\n try {\n url = new URL(value);\n } catch {\n return null;\n }\n // `url.origin === value` rejects paths, queries, fragments, credentials,\n // trailing slashes and any spelling the URL parser would rewrite.\n return url.protocol === protocol &&\n url.origin === value &&\n HOSTNAME.test(url.hostname)\n ? value\n : null;\n}\n\n/**\n * Parse a comma-separated allowlist of exact `https` origins.\n *\n * @remarks\n * Whitespace around entries is trimmed. An entry that is not an exact\n * `https://host[:port]` origin (a path, a trailing slash, `http:`, a\n * wildcard such as `https://*.vercel.app`) is dropped, so a typo narrows\n * the list instead of widening it.\n *\n * @example\n * ```typescript\n * parseOriginAllowlist(process.env.VANA_ACCOUNT_PREVIEW_ORIGINS);\n * // [\"https://account-git-my-branch.vercel.app\"]\n * ```\n *\n * @param csv - The raw list, for example an environment variable.\n * @returns The exact https origins, deduplicated, in order.\n */\nexport function parseOriginAllowlist(csv: string | null | undefined): string[] {\n if (!csv) return [];\n const origins: string[] = [];\n for (const entry of csv.split(\",\")) {\n const origin = exactOrigin(entry.trim(), \"https:\");\n if (origin && !origins.includes(origin)) origins.push(origin);\n }\n return origins;\n}\n\n/**\n * Resolve the Vana Account origin that serves the connect sheet.\n *\n * @remarks\n * Returns `VANA_ACCOUNT_ORIGINS[env]` unless `allowOverride` is true and\n * `override` is exactly one of `previewOrigins` (an `https` origin), or,\n * with `allowLocal`, exactly an `http` loopback origin. Anything else (a\n * path, a different scheme, a near-miss host, a lookalike) falls back to the\n * environment's origin. Matching is exact string equality: no patterns, no\n * suffixes, no normalization that could widen a match.\n *\n * @example\n * ```typescript\n * const accountOrigin = resolveVanaAccountOrigin({\n * env: \"dev\",\n * override: new URLSearchParams(location.search).get(\"vana_account\"),\n * previewOrigins: parseOriginAllowlist(\n * process.env.NEXT_PUBLIC_VANA_ACCOUNT_PREVIEW_ORIGINS,\n * ),\n * allowOverride: process.env.NEXT_PUBLIC_DEPLOY_ENV === \"preview\",\n * });\n * ```\n *\n * @param input - Environment, untrusted override, allowlist and switches.\n * @returns A bare Vana Account origin.\n * @throws TypeError when `env` is not `\"production\"` or `\"dev\"`.\n */\nexport function resolveVanaAccountOrigin(\n input: ResolveVanaAccountOriginInput,\n): string {\n if (input.env !== \"production\" && input.env !== \"dev\") {\n throw new TypeError('env must be \"production\" or \"dev\"');\n }\n const fallback: string = VANA_ACCOUNT_ORIGINS[input.env];\n const { override } = input;\n if (input.allowOverride !== true || typeof override !== \"string\") {\n return fallback;\n }\n const https = exactOrigin(override, \"https:\");\n if (https && (input.previewOrigins ?? []).includes(https)) {\n return https;\n }\n if (input.allowLocal === true) {\n const local = exactOrigin(override, \"http:\");\n if (local && LOOPBACK_HOSTS.has(new URL(local).hostname)) {\n return local;\n }\n }\n return fallback;\n}\n"],"mappings":"AAiBA,SAAS,4BAAiD;AA+B1D,MAAM,iBAAiB,oBAAI,IAAI,CAAC,aAAa,aAAa,OAAO,CAAC;AAElE,MAAM,WAAW;AAGjB,SAAS,YACP,OACA,UACe;AACf,MAAI;AACJ,MAAI;AACF,UAAM,IAAI,IAAI,KAAK;AAAA,EACrB,QAAQ;AACN,WAAO;AAAA,EACT;AAGA,SAAO,IAAI,aAAa,YACtB,IAAI,WAAW,SACf,SAAS,KAAK,IAAI,QAAQ,IACxB,QACA;AACN;AAoBO,SAAS,qBAAqB,KAA0C;AAC7E,MAAI,CAAC,IAAK,QAAO,CAAC;AAClB,QAAM,UAAoB,CAAC;AAC3B,aAAW,SAAS,IAAI,MAAM,GAAG,GAAG;AAClC,UAAM,SAAS,YAAY,MAAM,KAAK,GAAG,QAAQ;AACjD,QAAI,UAAU,CAAC,QAAQ,SAAS,MAAM,EAAG,SAAQ,KAAK,MAAM;AAAA,EAC9D;AACA,SAAO;AACT;AA6BO,SAAS,yBACd,OACQ;AACR,MAAI,MAAM,QAAQ,gBAAgB,MAAM,QAAQ,OAAO;AACrD,UAAM,IAAI,UAAU,mCAAmC;AAAA,EACzD;AACA,QAAM,WAAmB,qBAAqB,MAAM,GAAG;AACvD,QAAM,EAAE,SAAS,IAAI;AACrB,MAAI,MAAM,kBAAkB,QAAQ,OAAO,aAAa,UAAU;AAChE,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,YAAY,UAAU,QAAQ;AAC5C,MAAI,UAAU,MAAM,kBAAkB,CAAC,GAAG,SAAS,KAAK,GAAG;AACzD,WAAO;AAAA,EACT;AACA,MAAI,MAAM,eAAe,MAAM;AAC7B,UAAM,QAAQ,YAAY,UAAU,OAAO;AAC3C,QAAI,SAAS,eAAe,IAAI,IAAI,IAAI,KAAK,EAAE,QAAQ,GAAG;AACxD,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,221 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+ var chrome_exports = {};
20
+ __export(chrome_exports, {
21
+ DRAG_CLOSE_PX: () => DRAG_CLOSE_PX,
22
+ MAX_SHEET_RATIO: () => MAX_SHEET_RATIO,
23
+ RETURN_TAP_GUARD_MS: () => RETURN_TAP_GUARD_MS,
24
+ createSheetChrome: () => createSheetChrome,
25
+ sheetTopPx: () => sheetTopPx
26
+ });
27
+ module.exports = __toCommonJS(chrome_exports);
28
+ const SHEET_RADIUS_PX = 28;
29
+ const SHEET_MAX_WIDTH_PX = 480;
30
+ const HANDLE_STRIP_PX = 20;
31
+ const INITIAL_CONTENT_PX = 320;
32
+ const TRANSITION_MS = 280;
33
+ const SHEET_BACKGROUND = "#f4f4f4";
34
+ const DRAG_CLOSE_PX = 120;
35
+ const MAX_SHEET_RATIO = 0.92;
36
+ const STYLES = `
37
+ :host { all: initial; }
38
+ .scrim {
39
+ position: fixed; inset: 0; z-index: 2147483646;
40
+ background: rgb(0 0 0 / 0.4);
41
+ opacity: 0; transition: opacity ${TRANSITION_MS}ms ease;
42
+ }
43
+ .sheet {
44
+ position: fixed; left: 0; right: 0; z-index: 2147483647;
45
+ margin: 0 auto; max-width: ${SHEET_MAX_WIDTH_PX}px;
46
+ top: 100vh;
47
+ transition: top ${TRANSITION_MS}ms cubic-bezier(0.32, 0.72, 0, 1);
48
+ }
49
+ .sheet.dragging { transition: none; }
50
+ .handle {
51
+ height: ${HANDLE_STRIP_PX}px;
52
+ display: flex; justify-content: center; align-items: center;
53
+ background: ${SHEET_BACKGROUND};
54
+ border-radius: ${SHEET_RADIUS_PX}px ${SHEET_RADIUS_PX}px 0 0;
55
+ touch-action: none; cursor: grab;
56
+ }
57
+ .handle::before {
58
+ content: ""; width: 36px; height: 5px; border-radius: 3px;
59
+ background: rgb(0 0 0 / 0.18);
60
+ }
61
+ iframe {
62
+ display: block; width: 100%; border: 0;
63
+ background: ${SHEET_BACKGROUND};
64
+ }
65
+ .open .scrim { opacity: 1; }
66
+ @media (prefers-reduced-motion: reduce) {
67
+ .scrim, .sheet { transition: none; }
68
+ }
69
+ `;
70
+ function viewportPx() {
71
+ return window.visualViewport?.height ?? window.innerHeight;
72
+ }
73
+ function capPx() {
74
+ return Math.round(viewportPx() * MAX_SHEET_RATIO);
75
+ }
76
+ function placeSheet(sheet, content) {
77
+ sheet.style.top = `${sheetTopPx(viewportPx(), content)}px`;
78
+ }
79
+ function sheetTopPx(viewport, content) {
80
+ const cap = Math.round(viewport * MAX_SHEET_RATIO);
81
+ const visible = Math.min(content, cap - HANDLE_STRIP_PX);
82
+ return Math.round(viewport - HANDLE_STRIP_PX - visible);
83
+ }
84
+ const RETURN_TAP_GUARD_MS = 500;
85
+ function installReturnGuard(sheet, iframe, state) {
86
+ const fitIframe = () => {
87
+ iframe.style.height = `${capPx() - HANDLE_STRIP_PX}px`;
88
+ if (state.shown) {
89
+ placeSheet(sheet, state.lastHeight ?? INITIAL_CONTENT_PX);
90
+ }
91
+ };
92
+ const onReturn = () => {
93
+ if (document.visibilityState !== "visible") {
94
+ return;
95
+ }
96
+ state.returnedAt = performance.now();
97
+ fitIframe();
98
+ };
99
+ fitIframe();
100
+ window.addEventListener("resize", fitIframe);
101
+ window.visualViewport?.addEventListener("resize", fitIframe);
102
+ document.addEventListener("visibilitychange", onReturn);
103
+ window.addEventListener("focus", onReturn);
104
+ return () => {
105
+ window.removeEventListener("resize", fitIframe);
106
+ window.visualViewport?.removeEventListener("resize", fitIframe);
107
+ document.removeEventListener("visibilitychange", onReturn);
108
+ window.removeEventListener("focus", onReturn);
109
+ sheet.style.transform = "";
110
+ };
111
+ }
112
+ function installDragToClose(sheet, handle, onClose) {
113
+ let startY = null;
114
+ handle.addEventListener("pointerdown", (event) => {
115
+ startY = event.clientY;
116
+ handle.setPointerCapture?.(event.pointerId);
117
+ sheet.classList.add("dragging");
118
+ });
119
+ handle.addEventListener("pointermove", (event) => {
120
+ if (startY === null) {
121
+ return;
122
+ }
123
+ const dy = Math.max(0, event.clientY - startY);
124
+ sheet.style.transform = `translateY(${dy}px)`;
125
+ });
126
+ const end = (event) => {
127
+ if (startY === null) {
128
+ return;
129
+ }
130
+ const dy = event.clientY - startY;
131
+ startY = null;
132
+ sheet.classList.remove("dragging");
133
+ sheet.style.transform = "";
134
+ if (dy > DRAG_CLOSE_PX) {
135
+ onClose("drag");
136
+ }
137
+ };
138
+ handle.addEventListener("pointerup", end);
139
+ handle.addEventListener("pointercancel", end);
140
+ }
141
+ function createSheetChrome(input) {
142
+ const returnState = {
143
+ lastHeight: null,
144
+ returnedAt: 0,
145
+ shown: false
146
+ };
147
+ const host = document.createElement("div");
148
+ host.setAttribute("data-vana-connect", "");
149
+ const root = host.attachShadow({ mode: "open" });
150
+ const style = document.createElement("style");
151
+ style.textContent = STYLES;
152
+ const frame = document.createElement("div");
153
+ const scrim = document.createElement("div");
154
+ scrim.className = "scrim";
155
+ scrim.setAttribute("data-slot", "scrim");
156
+ scrim.addEventListener("click", () => {
157
+ if (performance.now() - returnState.returnedAt < RETURN_TAP_GUARD_MS) {
158
+ return;
159
+ }
160
+ input.onClose("scrim");
161
+ });
162
+ const sheet = document.createElement("div");
163
+ sheet.className = "sheet";
164
+ sheet.setAttribute("role", "dialog");
165
+ sheet.setAttribute("aria-modal", "true");
166
+ sheet.setAttribute("aria-label", input.ariaLabel);
167
+ const iframe = document.createElement("iframe");
168
+ iframe.setAttribute(
169
+ "sandbox",
170
+ "allow-scripts allow-same-origin allow-forms allow-popups allow-popups-to-escape-sandbox"
171
+ );
172
+ iframe.setAttribute(
173
+ "allow",
174
+ "otp-credentials; publickey-credentials-get; clipboard-write"
175
+ );
176
+ iframe.setAttribute("title", "Vana");
177
+ iframe.referrerPolicy = "strict-origin-when-cross-origin";
178
+ iframe.src = input.src;
179
+ const handle = document.createElement("div");
180
+ handle.className = "handle";
181
+ handle.setAttribute("aria-hidden", "true");
182
+ sheet.append(handle, iframe);
183
+ frame.append(scrim, sheet);
184
+ root.append(style, frame);
185
+ input.container.append(host);
186
+ const removeReturnGuard = installReturnGuard(sheet, iframe, returnState);
187
+ installDragToClose(sheet, handle, input.onClose);
188
+ return {
189
+ host,
190
+ iframe,
191
+ remove() {
192
+ removeReturnGuard();
193
+ host.remove();
194
+ },
195
+ setContentHeight(px) {
196
+ if (document.visibilityState !== "visible") {
197
+ return;
198
+ }
199
+ returnState.lastHeight = px;
200
+ if (returnState.shown) {
201
+ placeSheet(sheet, px);
202
+ }
203
+ },
204
+ show() {
205
+ requestAnimationFrame(() => {
206
+ returnState.shown = true;
207
+ frame.classList.add("open");
208
+ placeSheet(sheet, returnState.lastHeight ?? INITIAL_CONTENT_PX);
209
+ });
210
+ }
211
+ };
212
+ }
213
+ // Annotate the CommonJS export names for ESM import in node:
214
+ 0 && (module.exports = {
215
+ DRAG_CLOSE_PX,
216
+ MAX_SHEET_RATIO,
217
+ RETURN_TAP_GUARD_MS,
218
+ createSheetChrome,
219
+ sheetTopPx
220
+ });
221
+ //# sourceMappingURL=chrome.cjs.map