@opendatalabs/vana-sdk 3.4.1 → 3.5.1-pr.159.2d90789

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 (136) hide show
  1. package/README.md +116 -0
  2. package/dist/account/personal-server-lite-owner-binding.cjs +3 -3
  3. package/dist/account/personal-server-lite-owner-binding.cjs.map +1 -1
  4. package/dist/account/personal-server-lite-owner-binding.d.ts +2 -2
  5. package/dist/account/personal-server-lite-owner-binding.js +1 -1
  6. package/dist/account/personal-server-lite-owner-binding.js.map +1 -1
  7. package/dist/direct/access-request-client.cjs +104 -0
  8. package/dist/direct/access-request-client.cjs.map +1 -0
  9. package/dist/direct/access-request-client.d.ts +51 -0
  10. package/dist/direct/access-request-client.js +79 -0
  11. package/dist/direct/access-request-client.js.map +1 -0
  12. package/dist/direct/access-request-client.test.d.ts +1 -0
  13. package/dist/direct/connect-flow.cjs +152 -0
  14. package/dist/direct/connect-flow.cjs.map +1 -0
  15. package/dist/direct/connect-flow.d.ts +85 -0
  16. package/dist/direct/connect-flow.js +128 -0
  17. package/dist/direct/connect-flow.js.map +1 -0
  18. package/dist/direct/connect-flow.test.d.ts +1 -0
  19. package/dist/direct/controller.cjs +129 -0
  20. package/dist/direct/controller.cjs.map +1 -0
  21. package/dist/direct/controller.d.ts +152 -0
  22. package/dist/direct/controller.js +109 -0
  23. package/dist/direct/controller.js.map +1 -0
  24. package/dist/direct/controller.test.d.ts +1 -0
  25. package/dist/direct/endpoints.cjs +45 -0
  26. package/dist/direct/endpoints.cjs.map +1 -0
  27. package/dist/direct/endpoints.d.ts +22 -0
  28. package/dist/direct/endpoints.js +19 -0
  29. package/dist/direct/endpoints.js.map +1 -0
  30. package/dist/direct/errors.cjs +65 -0
  31. package/dist/direct/errors.cjs.map +1 -0
  32. package/dist/direct/errors.d.ts +44 -0
  33. package/dist/direct/errors.js +38 -0
  34. package/dist/direct/errors.js.map +1 -0
  35. package/dist/direct/escrow-payment.cjs +96 -0
  36. package/dist/direct/escrow-payment.cjs.map +1 -0
  37. package/dist/direct/escrow-payment.d.ts +81 -0
  38. package/dist/direct/escrow-payment.js +72 -0
  39. package/dist/direct/escrow-payment.js.map +1 -0
  40. package/dist/direct/escrow-payment.test.d.ts +1 -0
  41. package/dist/direct/personal-server-read.cjs +149 -0
  42. package/dist/direct/personal-server-read.cjs.map +1 -0
  43. package/dist/direct/personal-server-read.d.ts +103 -0
  44. package/dist/direct/personal-server-read.js +124 -0
  45. package/dist/direct/personal-server-read.js.map +1 -0
  46. package/dist/direct/personal-server-read.test.d.ts +1 -0
  47. package/dist/direct/types.cjs +35 -0
  48. package/dist/direct/types.cjs.map +1 -0
  49. package/dist/direct/types.d.ts +205 -0
  50. package/dist/direct/types.js +11 -0
  51. package/dist/direct/types.js.map +1 -0
  52. package/dist/direct/use-direct-vana-connect.cjs +68 -0
  53. package/dist/direct/use-direct-vana-connect.cjs.map +1 -0
  54. package/dist/direct/use-direct-vana-connect.d.ts +45 -0
  55. package/dist/direct/use-direct-vana-connect.js +46 -0
  56. package/dist/direct/use-direct-vana-connect.js.map +1 -0
  57. package/dist/index.browser.d.ts +9 -4
  58. package/dist/index.browser.js +644 -178
  59. package/dist/index.browser.js.map +4 -4
  60. package/dist/index.node.cjs +672 -183
  61. package/dist/index.node.cjs.map +4 -4
  62. package/dist/index.node.d.ts +9 -4
  63. package/dist/index.node.js +644 -178
  64. package/dist/index.node.js.map +4 -4
  65. package/dist/personal-server-lite/owner-binding.cjs +93 -0
  66. package/dist/personal-server-lite/owner-binding.cjs.map +1 -0
  67. package/dist/personal-server-lite/owner-binding.d.ts +44 -0
  68. package/dist/personal-server-lite/owner-binding.js +65 -0
  69. package/dist/personal-server-lite/owner-binding.js.map +1 -0
  70. package/dist/personal-server-lite/owner-binding.test.d.ts +1 -0
  71. package/dist/protocol/data-point-status.cjs +80 -0
  72. package/dist/protocol/data-point-status.cjs.map +1 -0
  73. package/dist/protocol/data-point-status.d.ts +34 -0
  74. package/dist/protocol/data-point-status.js +51 -0
  75. package/dist/protocol/data-point-status.js.map +1 -0
  76. package/dist/protocol/data-point-status.test.d.ts +1 -0
  77. package/dist/protocol/eip712.cjs +53 -31
  78. package/dist/protocol/eip712.cjs.map +1 -1
  79. package/dist/protocol/eip712.d.ts +98 -43
  80. package/dist/protocol/eip712.js +47 -27
  81. package/dist/protocol/eip712.js.map +1 -1
  82. package/dist/protocol/escrow-deposit.cjs +89 -0
  83. package/dist/protocol/escrow-deposit.cjs.map +1 -0
  84. package/dist/protocol/escrow-deposit.d.ts +47 -0
  85. package/dist/protocol/escrow-deposit.js +60 -0
  86. package/dist/protocol/escrow-deposit.js.map +1 -0
  87. package/dist/protocol/escrow-deposit.test.d.ts +1 -0
  88. package/dist/protocol/escrow-flow.test.d.ts +21 -0
  89. package/dist/protocol/escrow.cjs +146 -0
  90. package/dist/protocol/escrow.cjs.map +1 -0
  91. package/dist/protocol/escrow.d.ts +336 -0
  92. package/dist/protocol/escrow.js +118 -0
  93. package/dist/protocol/escrow.js.map +1 -0
  94. package/dist/protocol/escrow.test.d.ts +1 -0
  95. package/dist/protocol/fee-registry.cjs +116 -0
  96. package/dist/protocol/fee-registry.cjs.map +1 -0
  97. package/dist/protocol/fee-registry.d.ts +151 -0
  98. package/dist/protocol/fee-registry.js +89 -0
  99. package/dist/protocol/fee-registry.js.map +1 -0
  100. package/dist/protocol/fee-registry.test.d.ts +1 -0
  101. package/dist/protocol/gateway.cjs +107 -37
  102. package/dist/protocol/gateway.cjs.map +1 -1
  103. package/dist/protocol/gateway.d.ts +223 -57
  104. package/dist/protocol/gateway.js +107 -37
  105. package/dist/protocol/gateway.js.map +1 -1
  106. package/dist/protocol/grants.cjs +27 -64
  107. package/dist/protocol/grants.cjs.map +1 -1
  108. package/dist/protocol/grants.d.ts +6 -13
  109. package/dist/protocol/grants.js +27 -63
  110. package/dist/protocol/grants.js.map +1 -1
  111. package/dist/protocol/personal-server-data.cjs +71 -0
  112. package/dist/protocol/personal-server-data.cjs.map +1 -0
  113. package/dist/protocol/personal-server-data.d.ts +16 -0
  114. package/dist/protocol/personal-server-data.js +47 -0
  115. package/dist/protocol/personal-server-data.js.map +1 -0
  116. package/dist/protocol/personal-server-data.test.d.ts +1 -0
  117. package/dist/protocol/personal-server-registration.cjs +16 -4
  118. package/dist/protocol/personal-server-registration.cjs.map +1 -1
  119. package/dist/protocol/personal-server-registration.d.ts +5 -2
  120. package/dist/protocol/personal-server-registration.js +16 -4
  121. package/dist/protocol/personal-server-registration.js.map +1 -1
  122. package/dist/react.cjs +32 -0
  123. package/dist/react.cjs.map +1 -0
  124. package/dist/react.d.ts +33 -0
  125. package/dist/react.js +11 -0
  126. package/dist/react.js.map +1 -0
  127. package/dist/server.cjs +73 -0
  128. package/dist/server.cjs.map +1 -0
  129. package/dist/server.d.ts +32 -0
  130. package/dist/server.js +55 -0
  131. package/dist/server.js.map +1 -0
  132. package/dist/storage/providers/vana-storage.cjs +75 -17
  133. package/dist/storage/providers/vana-storage.cjs.map +1 -1
  134. package/dist/storage/providers/vana-storage.js +75 -17
  135. package/dist/storage/providers/vana-storage.js.map +1 -1
  136. package/package.json +20 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/direct/connect-flow.ts"],"sourcesContent":["/**\n * Framework-agnostic connect-flow state machine for the browser two-tab helper.\n *\n * @remarks\n * This is the testable core behind {@link useDirectVanaConnect}. It is pure\n * TypeScript (no React, no DOM-only APIs beyond an injectable window opener and\n * timers) so the full flow — create request, open Vana, poll status, read data —\n * can be exercised in a Node test environment.\n *\n * The React hook is a thin `useSyncExternalStore` binding over this store.\n *\n * @category Direct\n * @module direct/connect-flow\n */\n\nimport type {\n AccessRequest,\n AccessRequestStatus,\n ApprovedDataResult,\n} from \"./types\";\n\n/**\n * Caller-supplied transports. These typically `fetch` the app's own backend\n * routes, which in turn delegate to a {@link DirectDataController}.\n */\nexport interface DirectConnectTransports<T = unknown> {\n /** Ask the backend to create an access request. */\n createRequest: () => Promise<AccessRequest>;\n /** Ask the backend for the current status of a request. */\n getStatus: (requestId: string) => Promise<AccessRequestStatus>;\n /** Ask the backend to read the approved data. */\n readResult: (requestId: string) => Promise<ApprovedDataResult<T>>;\n}\n\n/** Tunables for the connect flow. */\nexport interface DirectConnectOptions {\n /** Status poll interval in ms. Defaults to 1500. */\n pollIntervalMs?: number;\n /** Overall timeout in ms before giving up. Defaults to 300000 (5 min). */\n timeoutMs?: number;\n /** Opens the approval URL. Defaults to `window.open`. Injectable for tests. */\n openWindow?: (url: string) => void;\n /** `setTimeout`. Injectable for tests. Defaults to `globalThis.setTimeout`. */\n setTimeoutFn?: (cb: () => void, ms: number) => unknown;\n /** `clearTimeout`. Injectable for tests. Defaults to `globalThis.clearTimeout`. */\n clearTimeoutFn?: (handle: unknown) => void;\n /** Clock source in ms. Injectable for tests. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/**\n * Discriminated connect-flow state.\n *\n * @remarks\n * `type` matches the builder guide: it starts at `\"idle\"` and is non-idle while\n * connecting. The intermediate phases give richer UIs something to render.\n */\nexport type DirectConnectState<T = unknown> =\n | { type: \"idle\" }\n | { type: \"creating\" }\n | { type: \"awaiting_approval\"; request: AccessRequest }\n | { type: \"reading\"; request: AccessRequest }\n | { type: \"done\"; result: ApprovedDataResult<T> }\n | { type: \"error\"; error: Error };\n\n/** The store returned by {@link createDirectConnectFlow}. */\nexport interface DirectConnectFlow<T = unknown> {\n /** Current state. */\n getState(): DirectConnectState<T>;\n /** Subscribe to state changes; returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n /** Begin the flow. No-op if already running. */\n start(): Promise<void>;\n /** Reset to `idle` and stop any in-flight polling. */\n reset(): void;\n}\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\n/**\n * Create a connect-flow store.\n *\n * @param transports - Backend transports (`createRequest`, `getStatus`, `readResult`).\n * @param options - Polling/timeout tunables and injectable side effects.\n * @returns A {@link DirectConnectFlow} store.\n */\nexport function createDirectConnectFlow<T = unknown>(\n transports: DirectConnectTransports<T>,\n options: DirectConnectOptions = {},\n): DirectConnectFlow<T> {\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const openWindow =\n options.openWindow ??\n ((url: string) => {\n if (typeof window !== \"undefined\" && window.open) {\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n }\n });\n const setTimeoutFn =\n options.setTimeoutFn ??\n ((cb: () => void, ms: number) => globalThis.setTimeout(cb, ms));\n const clearTimeoutFn =\n options.clearTimeoutFn ??\n ((handle: unknown) => {\n globalThis.clearTimeout(handle as never);\n });\n const now = options.now ?? (() => Date.now());\n\n let state: DirectConnectState<T> = { type: \"idle\" };\n const listeners = new Set<() => void>();\n let pollHandle: unknown = null;\n let running = false;\n\n function emit(): void {\n for (const listener of listeners) listener();\n }\n\n function setState(next: DirectConnectState<T>): void {\n state = next;\n emit();\n }\n\n function clearPoll(): void {\n if (pollHandle !== null) {\n clearTimeoutFn(pollHandle);\n pollHandle = null;\n }\n }\n\n function isRunningPhase(): boolean {\n return (\n state.type === \"creating\" ||\n state.type === \"awaiting_approval\" ||\n state.type === \"reading\"\n );\n }\n\n async function readAndFinish(request: AccessRequest): Promise<void> {\n setState({ type: \"reading\", request });\n try {\n const result = await transports.readResult(request.requestId);\n if (!running) return;\n setState({ type: \"done\", result });\n } catch (err) {\n if (!running) return;\n setState({ type: \"error\", error: toError(err) });\n } finally {\n running = false;\n }\n }\n\n function scheduleNextPoll(request: AccessRequest, deadline: number): void {\n pollHandle = setTimeoutFn(() => {\n void poll(request, deadline);\n }, pollIntervalMs);\n }\n\n async function poll(request: AccessRequest, deadline: number): Promise<void> {\n if (!running) return;\n if (now() >= deadline) {\n running = false;\n setState({\n type: \"error\",\n error: new Error(\"Timed out waiting for approval\"),\n });\n return;\n }\n let status: AccessRequestStatus;\n try {\n status = await transports.getStatus(request.requestId);\n } catch (err) {\n if (!running) return;\n running = false;\n setState({ type: \"error\", error: toError(err) });\n return;\n }\n if (!running) return;\n\n if (status.status === \"approved\") {\n clearPoll();\n await readAndFinish(request);\n return;\n }\n if (status.status === \"denied\" || status.status === \"expired\") {\n running = false;\n setState({\n type: \"error\",\n error: new Error(`Access request ${status.status}`),\n });\n return;\n }\n scheduleNextPoll(request, deadline);\n }\n\n return {\n getState() {\n return state;\n },\n\n subscribe(listener: () => void) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n\n async start(): Promise<void> {\n if (running || isRunningPhase()) return;\n running = true;\n setState({ type: \"creating\" });\n\n let request: AccessRequest;\n try {\n request = await transports.createRequest();\n } catch (err) {\n running = false;\n setState({ type: \"error\", error: toError(err) });\n return;\n }\n if (!running) return;\n\n setState({ type: \"awaiting_approval\", request });\n openWindow(request.approvalUrl);\n\n const deadline = now() + timeoutMs;\n scheduleNextPoll(request, deadline);\n },\n\n reset(): void {\n running = false;\n clearPoll();\n setState({ type: \"idle\" });\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Framework-agnostic connect-flow state machine for the browser two-tab helper.
3
+ *
4
+ * @remarks
5
+ * This is the testable core behind {@link useDirectVanaConnect}. It is pure
6
+ * TypeScript (no React, no DOM-only APIs beyond an injectable window opener and
7
+ * timers) so the full flow — create request, open Vana, poll status, read data —
8
+ * can be exercised in a Node test environment.
9
+ *
10
+ * The React hook is a thin `useSyncExternalStore` binding over this store.
11
+ *
12
+ * @category Direct
13
+ * @module direct/connect-flow
14
+ */
15
+ import type { AccessRequest, AccessRequestStatus, ApprovedDataResult } from "./types";
16
+ /**
17
+ * Caller-supplied transports. These typically `fetch` the app's own backend
18
+ * routes, which in turn delegate to a {@link DirectDataController}.
19
+ */
20
+ export interface DirectConnectTransports<T = unknown> {
21
+ /** Ask the backend to create an access request. */
22
+ createRequest: () => Promise<AccessRequest>;
23
+ /** Ask the backend for the current status of a request. */
24
+ getStatus: (requestId: string) => Promise<AccessRequestStatus>;
25
+ /** Ask the backend to read the approved data. */
26
+ readResult: (requestId: string) => Promise<ApprovedDataResult<T>>;
27
+ }
28
+ /** Tunables for the connect flow. */
29
+ export interface DirectConnectOptions {
30
+ /** Status poll interval in ms. Defaults to 1500. */
31
+ pollIntervalMs?: number;
32
+ /** Overall timeout in ms before giving up. Defaults to 300000 (5 min). */
33
+ timeoutMs?: number;
34
+ /** Opens the approval URL. Defaults to `window.open`. Injectable for tests. */
35
+ openWindow?: (url: string) => void;
36
+ /** `setTimeout`. Injectable for tests. Defaults to `globalThis.setTimeout`. */
37
+ setTimeoutFn?: (cb: () => void, ms: number) => unknown;
38
+ /** `clearTimeout`. Injectable for tests. Defaults to `globalThis.clearTimeout`. */
39
+ clearTimeoutFn?: (handle: unknown) => void;
40
+ /** Clock source in ms. Injectable for tests. Defaults to `Date.now`. */
41
+ now?: () => number;
42
+ }
43
+ /**
44
+ * Discriminated connect-flow state.
45
+ *
46
+ * @remarks
47
+ * `type` matches the builder guide: it starts at `"idle"` and is non-idle while
48
+ * connecting. The intermediate phases give richer UIs something to render.
49
+ */
50
+ export type DirectConnectState<T = unknown> = {
51
+ type: "idle";
52
+ } | {
53
+ type: "creating";
54
+ } | {
55
+ type: "awaiting_approval";
56
+ request: AccessRequest;
57
+ } | {
58
+ type: "reading";
59
+ request: AccessRequest;
60
+ } | {
61
+ type: "done";
62
+ result: ApprovedDataResult<T>;
63
+ } | {
64
+ type: "error";
65
+ error: Error;
66
+ };
67
+ /** The store returned by {@link createDirectConnectFlow}. */
68
+ export interface DirectConnectFlow<T = unknown> {
69
+ /** Current state. */
70
+ getState(): DirectConnectState<T>;
71
+ /** Subscribe to state changes; returns an unsubscribe function. */
72
+ subscribe(listener: () => void): () => void;
73
+ /** Begin the flow. No-op if already running. */
74
+ start(): Promise<void>;
75
+ /** Reset to `idle` and stop any in-flight polling. */
76
+ reset(): void;
77
+ }
78
+ /**
79
+ * Create a connect-flow store.
80
+ *
81
+ * @param transports - Backend transports (`createRequest`, `getStatus`, `readResult`).
82
+ * @param options - Polling/timeout tunables and injectable side effects.
83
+ * @returns A {@link DirectConnectFlow} store.
84
+ */
85
+ export declare function createDirectConnectFlow<T = unknown>(transports: DirectConnectTransports<T>, options?: DirectConnectOptions): DirectConnectFlow<T>;
@@ -0,0 +1,128 @@
1
+ const DEFAULT_POLL_INTERVAL_MS = 1500;
2
+ const DEFAULT_TIMEOUT_MS = 3e5;
3
+ function toError(value) {
4
+ return value instanceof Error ? value : new Error(String(value));
5
+ }
6
+ function createDirectConnectFlow(transports, options = {}) {
7
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
8
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
9
+ const openWindow = options.openWindow ?? ((url) => {
10
+ if (typeof window !== "undefined" && window.open) {
11
+ window.open(url, "_blank", "noopener,noreferrer");
12
+ }
13
+ });
14
+ const setTimeoutFn = options.setTimeoutFn ?? ((cb, ms) => globalThis.setTimeout(cb, ms));
15
+ const clearTimeoutFn = options.clearTimeoutFn ?? ((handle) => {
16
+ globalThis.clearTimeout(handle);
17
+ });
18
+ const now = options.now ?? (() => Date.now());
19
+ let state = { type: "idle" };
20
+ const listeners = /* @__PURE__ */ new Set();
21
+ let pollHandle = null;
22
+ let running = false;
23
+ function emit() {
24
+ for (const listener of listeners) listener();
25
+ }
26
+ function setState(next) {
27
+ state = next;
28
+ emit();
29
+ }
30
+ function clearPoll() {
31
+ if (pollHandle !== null) {
32
+ clearTimeoutFn(pollHandle);
33
+ pollHandle = null;
34
+ }
35
+ }
36
+ function isRunningPhase() {
37
+ return state.type === "creating" || state.type === "awaiting_approval" || state.type === "reading";
38
+ }
39
+ async function readAndFinish(request) {
40
+ setState({ type: "reading", request });
41
+ try {
42
+ const result = await transports.readResult(request.requestId);
43
+ if (!running) return;
44
+ setState({ type: "done", result });
45
+ } catch (err) {
46
+ if (!running) return;
47
+ setState({ type: "error", error: toError(err) });
48
+ } finally {
49
+ running = false;
50
+ }
51
+ }
52
+ function scheduleNextPoll(request, deadline) {
53
+ pollHandle = setTimeoutFn(() => {
54
+ void poll(request, deadline);
55
+ }, pollIntervalMs);
56
+ }
57
+ async function poll(request, deadline) {
58
+ if (!running) return;
59
+ if (now() >= deadline) {
60
+ running = false;
61
+ setState({
62
+ type: "error",
63
+ error: new Error("Timed out waiting for approval")
64
+ });
65
+ return;
66
+ }
67
+ let status;
68
+ try {
69
+ status = await transports.getStatus(request.requestId);
70
+ } catch (err) {
71
+ if (!running) return;
72
+ running = false;
73
+ setState({ type: "error", error: toError(err) });
74
+ return;
75
+ }
76
+ if (!running) return;
77
+ if (status.status === "approved") {
78
+ clearPoll();
79
+ await readAndFinish(request);
80
+ return;
81
+ }
82
+ if (status.status === "denied" || status.status === "expired") {
83
+ running = false;
84
+ setState({
85
+ type: "error",
86
+ error: new Error(`Access request ${status.status}`)
87
+ });
88
+ return;
89
+ }
90
+ scheduleNextPoll(request, deadline);
91
+ }
92
+ return {
93
+ getState() {
94
+ return state;
95
+ },
96
+ subscribe(listener) {
97
+ listeners.add(listener);
98
+ return () => listeners.delete(listener);
99
+ },
100
+ async start() {
101
+ if (running || isRunningPhase()) return;
102
+ running = true;
103
+ setState({ type: "creating" });
104
+ let request;
105
+ try {
106
+ request = await transports.createRequest();
107
+ } catch (err) {
108
+ running = false;
109
+ setState({ type: "error", error: toError(err) });
110
+ return;
111
+ }
112
+ if (!running) return;
113
+ setState({ type: "awaiting_approval", request });
114
+ openWindow(request.approvalUrl);
115
+ const deadline = now() + timeoutMs;
116
+ scheduleNextPoll(request, deadline);
117
+ },
118
+ reset() {
119
+ running = false;
120
+ clearPoll();
121
+ setState({ type: "idle" });
122
+ }
123
+ };
124
+ }
125
+ export {
126
+ createDirectConnectFlow
127
+ };
128
+ //# sourceMappingURL=connect-flow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/direct/connect-flow.ts"],"sourcesContent":["/**\n * Framework-agnostic connect-flow state machine for the browser two-tab helper.\n *\n * @remarks\n * This is the testable core behind {@link useDirectVanaConnect}. It is pure\n * TypeScript (no React, no DOM-only APIs beyond an injectable window opener and\n * timers) so the full flow — create request, open Vana, poll status, read data —\n * can be exercised in a Node test environment.\n *\n * The React hook is a thin `useSyncExternalStore` binding over this store.\n *\n * @category Direct\n * @module direct/connect-flow\n */\n\nimport type {\n AccessRequest,\n AccessRequestStatus,\n ApprovedDataResult,\n} from \"./types\";\n\n/**\n * Caller-supplied transports. These typically `fetch` the app's own backend\n * routes, which in turn delegate to a {@link DirectDataController}.\n */\nexport interface DirectConnectTransports<T = unknown> {\n /** Ask the backend to create an access request. */\n createRequest: () => Promise<AccessRequest>;\n /** Ask the backend for the current status of a request. */\n getStatus: (requestId: string) => Promise<AccessRequestStatus>;\n /** Ask the backend to read the approved data. */\n readResult: (requestId: string) => Promise<ApprovedDataResult<T>>;\n}\n\n/** Tunables for the connect flow. */\nexport interface DirectConnectOptions {\n /** Status poll interval in ms. Defaults to 1500. */\n pollIntervalMs?: number;\n /** Overall timeout in ms before giving up. Defaults to 300000 (5 min). */\n timeoutMs?: number;\n /** Opens the approval URL. Defaults to `window.open`. Injectable for tests. */\n openWindow?: (url: string) => void;\n /** `setTimeout`. Injectable for tests. Defaults to `globalThis.setTimeout`. */\n setTimeoutFn?: (cb: () => void, ms: number) => unknown;\n /** `clearTimeout`. Injectable for tests. Defaults to `globalThis.clearTimeout`. */\n clearTimeoutFn?: (handle: unknown) => void;\n /** Clock source in ms. Injectable for tests. Defaults to `Date.now`. */\n now?: () => number;\n}\n\n/**\n * Discriminated connect-flow state.\n *\n * @remarks\n * `type` matches the builder guide: it starts at `\"idle\"` and is non-idle while\n * connecting. The intermediate phases give richer UIs something to render.\n */\nexport type DirectConnectState<T = unknown> =\n | { type: \"idle\" }\n | { type: \"creating\" }\n | { type: \"awaiting_approval\"; request: AccessRequest }\n | { type: \"reading\"; request: AccessRequest }\n | { type: \"done\"; result: ApprovedDataResult<T> }\n | { type: \"error\"; error: Error };\n\n/** The store returned by {@link createDirectConnectFlow}. */\nexport interface DirectConnectFlow<T = unknown> {\n /** Current state. */\n getState(): DirectConnectState<T>;\n /** Subscribe to state changes; returns an unsubscribe function. */\n subscribe(listener: () => void): () => void;\n /** Begin the flow. No-op if already running. */\n start(): Promise<void>;\n /** Reset to `idle` and stop any in-flight polling. */\n reset(): void;\n}\n\nconst DEFAULT_POLL_INTERVAL_MS = 1500;\nconst DEFAULT_TIMEOUT_MS = 300_000;\n\nfunction toError(value: unknown): Error {\n return value instanceof Error ? value : new Error(String(value));\n}\n\n/**\n * Create a connect-flow store.\n *\n * @param transports - Backend transports (`createRequest`, `getStatus`, `readResult`).\n * @param options - Polling/timeout tunables and injectable side effects.\n * @returns A {@link DirectConnectFlow} store.\n */\nexport function createDirectConnectFlow<T = unknown>(\n transports: DirectConnectTransports<T>,\n options: DirectConnectOptions = {},\n): DirectConnectFlow<T> {\n const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const openWindow =\n options.openWindow ??\n ((url: string) => {\n if (typeof window !== \"undefined\" && window.open) {\n window.open(url, \"_blank\", \"noopener,noreferrer\");\n }\n });\n const setTimeoutFn =\n options.setTimeoutFn ??\n ((cb: () => void, ms: number) => globalThis.setTimeout(cb, ms));\n const clearTimeoutFn =\n options.clearTimeoutFn ??\n ((handle: unknown) => {\n globalThis.clearTimeout(handle as never);\n });\n const now = options.now ?? (() => Date.now());\n\n let state: DirectConnectState<T> = { type: \"idle\" };\n const listeners = new Set<() => void>();\n let pollHandle: unknown = null;\n let running = false;\n\n function emit(): void {\n for (const listener of listeners) listener();\n }\n\n function setState(next: DirectConnectState<T>): void {\n state = next;\n emit();\n }\n\n function clearPoll(): void {\n if (pollHandle !== null) {\n clearTimeoutFn(pollHandle);\n pollHandle = null;\n }\n }\n\n function isRunningPhase(): boolean {\n return (\n state.type === \"creating\" ||\n state.type === \"awaiting_approval\" ||\n state.type === \"reading\"\n );\n }\n\n async function readAndFinish(request: AccessRequest): Promise<void> {\n setState({ type: \"reading\", request });\n try {\n const result = await transports.readResult(request.requestId);\n if (!running) return;\n setState({ type: \"done\", result });\n } catch (err) {\n if (!running) return;\n setState({ type: \"error\", error: toError(err) });\n } finally {\n running = false;\n }\n }\n\n function scheduleNextPoll(request: AccessRequest, deadline: number): void {\n pollHandle = setTimeoutFn(() => {\n void poll(request, deadline);\n }, pollIntervalMs);\n }\n\n async function poll(request: AccessRequest, deadline: number): Promise<void> {\n if (!running) return;\n if (now() >= deadline) {\n running = false;\n setState({\n type: \"error\",\n error: new Error(\"Timed out waiting for approval\"),\n });\n return;\n }\n let status: AccessRequestStatus;\n try {\n status = await transports.getStatus(request.requestId);\n } catch (err) {\n if (!running) return;\n running = false;\n setState({ type: \"error\", error: toError(err) });\n return;\n }\n if (!running) return;\n\n if (status.status === \"approved\") {\n clearPoll();\n await readAndFinish(request);\n return;\n }\n if (status.status === \"denied\" || status.status === \"expired\") {\n running = false;\n setState({\n type: \"error\",\n error: new Error(`Access request ${status.status}`),\n });\n return;\n }\n scheduleNextPoll(request, deadline);\n }\n\n return {\n getState() {\n return state;\n },\n\n subscribe(listener: () => void) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n\n async start(): Promise<void> {\n if (running || isRunningPhase()) return;\n running = true;\n setState({ type: \"creating\" });\n\n let request: AccessRequest;\n try {\n request = await transports.createRequest();\n } catch (err) {\n running = false;\n setState({ type: \"error\", error: toError(err) });\n return;\n }\n if (!running) return;\n\n setState({ type: \"awaiting_approval\", request });\n openWindow(request.approvalUrl);\n\n const deadline = now() + timeoutMs;\n scheduleNextPoll(request, deadline);\n },\n\n reset(): void {\n running = false;\n clearPoll();\n setState({ type: \"idle\" });\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,129 @@
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 controller_exports = {};
20
+ __export(controller_exports, {
21
+ createDirectDataController: () => createDirectDataController
22
+ });
23
+ module.exports = __toCommonJS(controller_exports);
24
+ var import_accounts = require("viem/accounts");
25
+ var import_scopes = require("../protocol/scopes");
26
+ var import_access_request_client = require("./access-request-client");
27
+ var import_endpoints = require("./endpoints");
28
+ var import_errors = require("./errors");
29
+ var import_personal_server_read = require("./personal-server-read");
30
+ function isHexPrivateKey(value) {
31
+ return /^0x[0-9a-fA-F]{64}$/.test(value);
32
+ }
33
+ function createDirectDataController(config) {
34
+ const privateKey = config.appPrivateKey ?? config.builderPrivateKey;
35
+ if (!privateKey || !isHexPrivateKey(privateKey)) {
36
+ throw new import_errors.DirectConfigError(
37
+ "appPrivateKey must be a 0x-prefixed 32-byte hex string"
38
+ );
39
+ }
40
+ if (!config.scopes || config.scopes.length === 0) {
41
+ throw new import_errors.DirectConfigError("At least one scope is required");
42
+ }
43
+ for (const scope of config.scopes) {
44
+ (0, import_scopes.parseScope)(scope);
45
+ }
46
+ const env = config.env ?? "production";
47
+ const endpoints = {
48
+ ...(0, import_endpoints.getDirectEndpoints)(env),
49
+ ...config.endpoints
50
+ };
51
+ const account = (0, import_accounts.privateKeyToAccount)(privateKey);
52
+ const signMessage = (message) => account.signMessage({ message });
53
+ const signTypedData = account.signTypedData;
54
+ const chainId = endpoints.chainId;
55
+ const accessRequestClient = config.accessRequestClient ?? (0, import_access_request_client.createDefaultAccessRequestClient)({
56
+ baseUrl: endpoints.accessRequestBaseUrl,
57
+ approvalBaseUrl: endpoints.approvalAppBaseUrl,
58
+ fetchFn: config.fetchFn
59
+ });
60
+ const escrow = config.escrow ? {
61
+ client: config.escrow.client,
62
+ escrowContract: config.escrow.escrowContract,
63
+ chainId: config.escrow.chainId ?? chainId,
64
+ nonceSource: config.escrow.nonceSource,
65
+ signTypedData
66
+ } : void 0;
67
+ return {
68
+ appAddress: account.address,
69
+ getAppAddress() {
70
+ return account.address;
71
+ },
72
+ getAppIdentity() {
73
+ return {
74
+ id: config.app.id,
75
+ name: config.app.name,
76
+ homepageUrl: config.app.homepageUrl,
77
+ address: account.address
78
+ };
79
+ },
80
+ async createAccessRequest(input) {
81
+ return accessRequestClient.createAccessRequest({
82
+ appAddress: account.address,
83
+ app: config.app,
84
+ source: config.source,
85
+ scopes: config.scopes,
86
+ returnUrl: input.returnUrl
87
+ });
88
+ },
89
+ async getAccessRequestStatus(requestId) {
90
+ return accessRequestClient.getAccessRequestStatus(requestId);
91
+ },
92
+ async readApprovedData(input) {
93
+ const status = await accessRequestClient.getAccessRequestStatus(
94
+ input.requestId
95
+ );
96
+ if (status.status !== "approved" || !status.personalServerUrl || !status.grantId || !status.scope) {
97
+ throw new import_errors.AccessNotApprovedError(
98
+ "Request is not approved or is missing grantId/scope/personalServerUrl",
99
+ {
100
+ requestId: input.requestId,
101
+ status: status.status,
102
+ hasPersonalServerUrl: Boolean(status.personalServerUrl),
103
+ hasGrantId: Boolean(status.grantId),
104
+ hasScope: Boolean(status.scope)
105
+ }
106
+ );
107
+ }
108
+ const result = await (0, import_personal_server_read.readPersonalServerData)({
109
+ personalServerUrl: status.personalServerUrl,
110
+ scope: status.scope,
111
+ grantId: status.grantId,
112
+ payerAddress: account.address,
113
+ signMessage,
114
+ escrow,
115
+ fetchFn: config.personalServerFetch
116
+ });
117
+ return {
118
+ scope: status.scope,
119
+ data: result.data,
120
+ payment: result.payment
121
+ };
122
+ }
123
+ };
124
+ }
125
+ // Annotate the CommonJS export names for ESM import in node:
126
+ 0 && (module.exports = {
127
+ createDirectDataController
128
+ });
129
+ //# sourceMappingURL=controller.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport { getDirectEndpoints } from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically, so you provide\n * the gateway `client`, the `escrowContract` address, and (optionally) the\n * `chainId` and a durable `nonceSource`. If omitted, a `402` from the Personal\n * Server throws {@link PaymentRequiredError} carrying the amount/asset owed.\n */\n escrow?: DirectEscrowConfig;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for production, 14800 for dev).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment and `escrow` is configured, it settles the grant via the escrow\n * gateway and retries, attaching a {@link DirectPaymentReceipt} under\n * `payment` so callers can inspect amount/asset/fee breakdown. If `escrow` is\n * not configured, it throws {@link PaymentRequiredError} carrying the\n * amount/asset owed.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const endpoints: DirectServiceEndpoints = {\n ...getDirectEndpoints(env),\n ...config.endpoints,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const chainId = endpoints.chainId;\n\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n });\n\n const escrow: EscrowPaymentConfig | undefined = config.escrow\n ? {\n client: config.escrow.client,\n escrowContract: config.escrow.escrowContract,\n chainId: config.escrow.chainId ?? chainId,\n nonceSource: config.escrow.nonceSource,\n signTypedData,\n }\n : undefined;\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n status.status !== \"approved\" ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n });\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Direct Data Controller — the server-side facade for the two-tab Data
3
+ * Portability flow.
4
+ *
5
+ * @remarks
6
+ * One controller owns an app's private key, source, scopes, app identity, and
7
+ * payment flow. It exposes the three methods the builder guide documents:
8
+ *
9
+ * - {@link DirectDataController.createAccessRequest} — start an approval request.
10
+ * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.
11
+ * - {@link DirectDataController.readApprovedData} — read from the Personal Server,
12
+ * handling 402 Payment Required.
13
+ *
14
+ * Access requests are created through the Vana Account access-request API; the
15
+ * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow
16
+ * surface (`protocol/escrow`) — when a read returns `402`, the controller signs
17
+ * a `GenericPayment` with the app key, settles it through the escrow gateway,
18
+ * and retries.
19
+ *
20
+ * @category Direct
21
+ * @module direct/controller
22
+ */
23
+ import { type FetchLike } from "./access-request-client";
24
+ import { type EscrowPaymentConfig } from "./escrow-payment";
25
+ import { type PersonalServerFetch } from "./personal-server-read";
26
+ import type { AccessRequest, AccessRequestClient, AccessRequestStatus, ApprovedDataResult, AppIdentity, DirectAppConfig, DirectEnv, DirectServiceEndpoints } from "./types";
27
+ /** Configuration for {@link createDirectDataController}. */
28
+ export interface DirectDataControllerConfig {
29
+ /** Target environment. Defaults to `"production"`. */
30
+ env?: DirectEnv;
31
+ /**
32
+ * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key
33
+ * is the app's on-chain identity and is never exposed to the browser.
34
+ */
35
+ appPrivateKey?: string;
36
+ /**
37
+ * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as
38
+ * a backwards-compatible alias; if both are set, `appPrivateKey` wins.
39
+ */
40
+ builderPrivateKey?: string;
41
+ /** App identity advertised during approval. */
42
+ app: DirectAppConfig;
43
+ /** Data source key (e.g. `"icloud_notes"`). */
44
+ source: string;
45
+ /** Scopes to request (e.g. `["icloud_notes.notes"]`). At least one required. */
46
+ scopes: string[];
47
+ /**
48
+ * Override the resolved service endpoints (partial). Useful for pointing at a
49
+ * non-standard deployment.
50
+ */
51
+ endpoints?: Partial<DirectServiceEndpoints>;
52
+ /**
53
+ * Client for the Vana Account access-request API. Defaults to a client against
54
+ * the resolved Vana Account endpoints; inject your own to point at a custom
55
+ * deployment or to supply a test double.
56
+ */
57
+ accessRequestClient?: AccessRequestClient;
58
+ /**
59
+ * Escrow settlement config used when a Personal Server read returns `402`.
60
+ *
61
+ * @remarks
62
+ * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies
63
+ * the EIP-712 `signTypedData` from the app key automatically, so you provide
64
+ * the gateway `client`, the `escrowContract` address, and (optionally) the
65
+ * `chainId` and a durable `nonceSource`. If omitted, a `402` from the Personal
66
+ * Server throws {@link PaymentRequiredError} carrying the amount/asset owed.
67
+ */
68
+ escrow?: DirectEscrowConfig;
69
+ /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */
70
+ fetchFn?: FetchLike;
71
+ /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */
72
+ personalServerFetch?: PersonalServerFetch;
73
+ }
74
+ /**
75
+ * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the
76
+ * `signTypedData` and `chainId` the controller injects itself.
77
+ */
78
+ export interface DirectEscrowConfig extends Omit<EscrowPaymentConfig, "signTypedData" | "chainId"> {
79
+ /**
80
+ * Chain id for the EIP-712 domain. Defaults to the controller's environment
81
+ * (1480 for production, 14800 for dev).
82
+ */
83
+ chainId?: number;
84
+ }
85
+ /**
86
+ * Server-side controller for the direct Data Portability flow.
87
+ *
88
+ * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.
89
+ */
90
+ export interface DirectDataController {
91
+ /** The on-chain address of the app, derived from `appPrivateKey`. */
92
+ readonly appAddress: string;
93
+ /**
94
+ * The app's on-chain address — the address to fund and inspect in the Builder
95
+ * activity report. Equivalent to {@link DirectDataController.appAddress}.
96
+ *
97
+ * @returns The app's `0x`-prefixed address.
98
+ */
99
+ getAppAddress(): string;
100
+ /**
101
+ * The app's full identity: its configured id/name/homepage plus the derived
102
+ * on-chain address. Useful for telling builders which app address to fund or
103
+ * look up.
104
+ *
105
+ * @returns `{ id, name, homepageUrl, address }`.
106
+ */
107
+ getAppIdentity(): AppIdentity;
108
+ /**
109
+ * Create an access request the user can approve.
110
+ *
111
+ * @param input - The post-approval return URL.
112
+ * @returns `{ requestId, approvalUrl, appAddress }`.
113
+ */
114
+ createAccessRequest(input: {
115
+ returnUrl: string;
116
+ }): Promise<AccessRequest>;
117
+ /**
118
+ * Fetch the current status of an access request.
119
+ *
120
+ * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.
121
+ * @returns `{ status, personalServerUrl?, grantId?, scope? }`.
122
+ */
123
+ getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;
124
+ /**
125
+ * Read the approved data from the user's Personal Server.
126
+ *
127
+ * @remarks
128
+ * Resolves the request to its grant + Personal Server and performs a Web3Signed
129
+ * read. Hides the `402 Payment Required` flow by default: if a read needs
130
+ * payment and `escrow` is configured, it settles the grant via the escrow
131
+ * gateway and retries, attaching a {@link DirectPaymentReceipt} under
132
+ * `payment` so callers can inspect amount/asset/fee breakdown. If `escrow` is
133
+ * not configured, it throws {@link PaymentRequiredError} carrying the
134
+ * amount/asset owed.
135
+ *
136
+ * @param input - The `dcr_*` request id to read.
137
+ * @returns `{ scope, data, payment? }`.
138
+ * @throws {@link AccessNotApprovedError} if the request is not approved.
139
+ * @throws {@link PaymentRequiredError} if payment is required but unsettled.
140
+ */
141
+ readApprovedData<T = unknown>(input: {
142
+ requestId: string;
143
+ }): Promise<ApprovedDataResult<T>>;
144
+ }
145
+ /**
146
+ * Create a {@link DirectDataController}.
147
+ *
148
+ * @param config - Controller configuration (env, key, app identity, source, scopes).
149
+ * @returns A ready-to-use controller.
150
+ * @throws {@link DirectConfigError} when the key or scopes are invalid.
151
+ */
152
+ export declare function createDirectDataController(config: DirectDataControllerConfig): DirectDataController;