@opendatalabs/vana-sdk 3.5.1 → 3.6.1

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 (116) hide show
  1. package/README.md +116 -0
  2. package/dist/direct/access-request-client.cjs +149 -0
  3. package/dist/direct/access-request-client.cjs.map +1 -0
  4. package/dist/direct/access-request-client.d.ts +66 -0
  5. package/dist/direct/access-request-client.js +123 -0
  6. package/dist/direct/access-request-client.js.map +1 -0
  7. package/dist/direct/access-request-client.test.d.ts +1 -0
  8. package/dist/direct/connect-flow.cjs +152 -0
  9. package/dist/direct/connect-flow.cjs.map +1 -0
  10. package/dist/direct/connect-flow.d.ts +85 -0
  11. package/dist/direct/connect-flow.js +128 -0
  12. package/dist/direct/connect-flow.js.map +1 -0
  13. package/dist/direct/connect-flow.test.d.ts +1 -0
  14. package/dist/direct/controller.cjs +131 -0
  15. package/dist/direct/controller.cjs.map +1 -0
  16. package/dist/direct/controller.d.ts +152 -0
  17. package/dist/direct/controller.js +111 -0
  18. package/dist/direct/controller.js.map +1 -0
  19. package/dist/direct/controller.test.d.ts +1 -0
  20. package/dist/direct/endpoints.cjs +45 -0
  21. package/dist/direct/endpoints.cjs.map +1 -0
  22. package/dist/direct/endpoints.d.ts +22 -0
  23. package/dist/direct/endpoints.js +19 -0
  24. package/dist/direct/endpoints.js.map +1 -0
  25. package/dist/direct/errors.cjs +65 -0
  26. package/dist/direct/errors.cjs.map +1 -0
  27. package/dist/direct/errors.d.ts +44 -0
  28. package/dist/direct/errors.js +38 -0
  29. package/dist/direct/errors.js.map +1 -0
  30. package/dist/direct/escrow-payment.cjs +96 -0
  31. package/dist/direct/escrow-payment.cjs.map +1 -0
  32. package/dist/direct/escrow-payment.d.ts +81 -0
  33. package/dist/direct/escrow-payment.js +72 -0
  34. package/dist/direct/escrow-payment.js.map +1 -0
  35. package/dist/direct/escrow-payment.test.d.ts +1 -0
  36. package/dist/direct/personal-server-read.cjs +149 -0
  37. package/dist/direct/personal-server-read.cjs.map +1 -0
  38. package/dist/direct/personal-server-read.d.ts +103 -0
  39. package/dist/direct/personal-server-read.js +124 -0
  40. package/dist/direct/personal-server-read.js.map +1 -0
  41. package/dist/direct/personal-server-read.test.d.ts +1 -0
  42. package/dist/direct/types.cjs +35 -0
  43. package/dist/direct/types.cjs.map +1 -0
  44. package/dist/direct/types.d.ts +205 -0
  45. package/dist/direct/types.js +11 -0
  46. package/dist/direct/types.js.map +1 -0
  47. package/dist/direct/use-direct-vana-connect.cjs +68 -0
  48. package/dist/direct/use-direct-vana-connect.cjs.map +1 -0
  49. package/dist/direct/use-direct-vana-connect.d.ts +45 -0
  50. package/dist/direct/use-direct-vana-connect.js +46 -0
  51. package/dist/direct/use-direct-vana-connect.js.map +1 -0
  52. package/dist/index.browser.d.ts +7 -3
  53. package/dist/index.browser.js +438 -157
  54. package/dist/index.browser.js.map +4 -4
  55. package/dist/index.node.cjs +461 -162
  56. package/dist/index.node.cjs.map +4 -4
  57. package/dist/index.node.d.ts +7 -3
  58. package/dist/index.node.js +438 -157
  59. package/dist/index.node.js.map +4 -4
  60. package/dist/protocol/data-point-status.cjs +80 -0
  61. package/dist/protocol/data-point-status.cjs.map +1 -0
  62. package/dist/protocol/data-point-status.d.ts +34 -0
  63. package/dist/protocol/data-point-status.js +51 -0
  64. package/dist/protocol/data-point-status.js.map +1 -0
  65. package/dist/protocol/data-point-status.test.d.ts +1 -0
  66. package/dist/protocol/eip712.cjs +53 -31
  67. package/dist/protocol/eip712.cjs.map +1 -1
  68. package/dist/protocol/eip712.d.ts +98 -43
  69. package/dist/protocol/eip712.js +47 -27
  70. package/dist/protocol/eip712.js.map +1 -1
  71. package/dist/protocol/escrow-deposit.cjs +89 -0
  72. package/dist/protocol/escrow-deposit.cjs.map +1 -0
  73. package/dist/protocol/escrow-deposit.d.ts +47 -0
  74. package/dist/protocol/escrow-deposit.js +60 -0
  75. package/dist/protocol/escrow-deposit.js.map +1 -0
  76. package/dist/protocol/escrow-deposit.test.d.ts +1 -0
  77. package/dist/protocol/escrow-flow.test.d.ts +21 -0
  78. package/dist/protocol/fee-registry.cjs +116 -0
  79. package/dist/protocol/fee-registry.cjs.map +1 -0
  80. package/dist/protocol/fee-registry.d.ts +151 -0
  81. package/dist/protocol/fee-registry.js +89 -0
  82. package/dist/protocol/fee-registry.js.map +1 -0
  83. package/dist/protocol/fee-registry.test.d.ts +1 -0
  84. package/dist/protocol/gateway.cjs +107 -37
  85. package/dist/protocol/gateway.cjs.map +1 -1
  86. package/dist/protocol/gateway.d.ts +223 -57
  87. package/dist/protocol/gateway.js +107 -37
  88. package/dist/protocol/gateway.js.map +1 -1
  89. package/dist/protocol/grants.cjs +27 -64
  90. package/dist/protocol/grants.cjs.map +1 -1
  91. package/dist/protocol/grants.d.ts +6 -13
  92. package/dist/protocol/grants.js +27 -63
  93. package/dist/protocol/grants.js.map +1 -1
  94. package/dist/protocol/personal-server-data.cjs +71 -0
  95. package/dist/protocol/personal-server-data.cjs.map +1 -0
  96. package/dist/protocol/personal-server-data.d.ts +16 -0
  97. package/dist/protocol/personal-server-data.js +47 -0
  98. package/dist/protocol/personal-server-data.js.map +1 -0
  99. package/dist/protocol/personal-server-data.test.d.ts +1 -0
  100. package/dist/protocol/personal-server-lite-owner-binding.cjs +93 -0
  101. package/dist/protocol/personal-server-lite-owner-binding.cjs.map +1 -0
  102. package/dist/protocol/personal-server-lite-owner-binding.d.ts +44 -0
  103. package/dist/protocol/personal-server-lite-owner-binding.js +65 -0
  104. package/dist/protocol/personal-server-lite-owner-binding.js.map +1 -0
  105. package/dist/protocol/personal-server-lite-owner-binding.test.d.ts +1 -0
  106. package/dist/react.cjs +32 -0
  107. package/dist/react.cjs.map +1 -0
  108. package/dist/react.d.ts +33 -0
  109. package/dist/react.js +11 -0
  110. package/dist/react.js.map +1 -0
  111. package/dist/server.cjs +73 -0
  112. package/dist/server.cjs.map +1 -0
  113. package/dist/server.d.ts +32 -0
  114. package/dist/server.js +55 -0
  115. package/dist/server.js.map +1 -0
  116. package/package.json +20 -1
package/README.md CHANGED
@@ -104,6 +104,122 @@ const result = await storage.upload(myBlob, "report.json");
104
104
  console.log(result.url);
105
105
  ```
106
106
 
107
+ ## Build a direct Vana app
108
+
109
+ Request user-approved data, read it from the user's Personal Server, and pay
110
+ for the read — without the browser ever seeing your app private key or choosing
111
+ scopes. Your **backend** owns the controller (`@opendatalabs/vana-sdk/server`);
112
+ your **frontend** drives a two-tab approval flow with a React hook
113
+ (`@opendatalabs/vana-sdk/react`).
114
+
115
+ > **How it fits together.** Access requests are created through the Vana Account
116
+ > access-request API; the Personal Server read uses Web3Signed auth; and payment
117
+ > settles on a `402` through the DPv2 escrow surface (`protocol/escrow`), where the
118
+ > controller signs a `GenericPayment` with your app key. You can inject your own
119
+ > `accessRequestClient` to target a custom deployment, and `escrow` config to wire
120
+ > the escrow gateway.
121
+
122
+ ### Backend controller
123
+
124
+ ```typescript
125
+ // lib/vana.ts
126
+ import { createDirectDataController } from "@opendatalabs/vana-sdk/server";
127
+
128
+ import { createEscrowGatewayClient } from "@opendatalabs/vana-sdk/node";
129
+
130
+ export const vana = createDirectDataController({
131
+ env: process.env.VANA_ENV === "dev" ? "dev" : "production",
132
+ appPrivateKey: process.env.VANA_APP_PRIVATE_KEY!,
133
+ app: {
134
+ id: "notes-lens",
135
+ name: "Notes Lens",
136
+ homepageUrl: process.env.VANA_APP_URL!,
137
+ },
138
+ source: "icloud_notes",
139
+ scopes: ["icloud_notes.notes"],
140
+ // Settle paid reads through the DPv2 escrow gateway. The controller signs the
141
+ // GenericPayment with your app key; you supply the gateway client + contract.
142
+ escrow: {
143
+ client: createEscrowGatewayClient(process.env.VANA_DP_RPC_URL!),
144
+ escrowContract: process.env.VANA_ESCROW_CONTRACT! as `0x${string}`,
145
+ },
146
+ });
147
+
148
+ // The app's on-chain address — fund and inspect this in the Builder activity
149
+ // report. (`vana.getAppIdentity()` also returns the configured id/name/homepage.)
150
+ console.log(vana.getAppAddress()); // 0x...
151
+ ```
152
+
153
+ Wire it to three routes — your backend chooses the source and scopes, owns the
154
+ private key, and handles `402 Payment Required`:
155
+
156
+ ```typescript
157
+ // POST /api/vana/request
158
+ const request = await vana.createAccessRequest({
159
+ returnUrl: `${process.env.VANA_APP_URL}/connect/return`,
160
+ });
161
+ // -> { requestId: "dcr_...", approvalUrl: "https://app.vana.org/...", appAddress: "0x..." }
162
+
163
+ // GET /api/vana/status?requestId=...
164
+ const status = await vana.getAccessRequestStatus(requestId);
165
+ // -> { status: "approved", personalServerUrl, grantId, scope }
166
+
167
+ // GET /api/vana/data?requestId=...
168
+ const result = await vana.readApprovedData({ requestId });
169
+ // -> {
170
+ // scope: "icloud_notes.notes",
171
+ // data: ...,
172
+ // payment?: { // present only when this read settled a payment
173
+ // amount, asset, paymentNonce, paidAt,
174
+ // breakdown: { registrationFee, dataAccessFee, registrationPaid },
175
+ // },
176
+ // }
177
+ ```
178
+
179
+ `readApprovedData` hides the payment flow for normal builders. If the Personal
180
+ Server returns `402 Payment Required`, the controller settles the grant through
181
+ the escrow gateway and retries, attaching a `payment` receipt so you can inspect
182
+ the amount, asset, and fee breakdown. If `escrow` is not configured (or the read
183
+ still requires payment afterward), it throws `PaymentRequiredError` carrying the
184
+ amount and asset owed.
185
+
186
+ ### Frontend hook
187
+
188
+ ```tsx
189
+ "use client";
190
+ import { useDirectVanaConnect } from "@opendatalabs/vana-sdk/react";
191
+
192
+ export function ConnectNotesButton() {
193
+ const connect = useDirectVanaConnect({
194
+ createRequest: () =>
195
+ fetch("/api/vana/request", { method: "POST" }).then((r) => r.json()),
196
+ getStatus: (requestId) =>
197
+ fetch(`/api/vana/status?requestId=${encodeURIComponent(requestId)}`).then(
198
+ (r) => r.json(),
199
+ ),
200
+ readResult: (requestId) =>
201
+ fetch(`/api/vana/data?requestId=${encodeURIComponent(requestId)}`).then(
202
+ (r) => r.json(),
203
+ ),
204
+ });
205
+
206
+ return (
207
+ <button
208
+ disabled={connect.state.type !== "idle"}
209
+ onClick={connect.start}
210
+ type="button"
211
+ >
212
+ {connect.state.type === "idle" ? "Connect Apple Notes" : "Connecting..."}
213
+ </button>
214
+ );
215
+ }
216
+ ```
217
+
218
+ The hook calls `createRequest`, opens the Vana approval URL, polls `getStatus`
219
+ until the request is approved, then calls `readResult`. `react` is an optional
220
+ peer dependency. The underlying `createDirectConnectFlow` store is also exported
221
+ for non-React frontends.
222
+
107
223
  ## Networks
108
224
 
109
225
  | Network | Chain ID | RPC URL |
@@ -0,0 +1,149 @@
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 access_request_client_exports = {};
20
+ __export(access_request_client_exports, {
21
+ buildApprovalUrl: () => buildApprovalUrl,
22
+ buildDirectAccessRequestAuthMessage: () => buildDirectAccessRequestAuthMessage,
23
+ createDefaultAccessRequestClient: () => createDefaultAccessRequestClient
24
+ });
25
+ module.exports = __toCommonJS(access_request_client_exports);
26
+ const VALID_STATUSES = [
27
+ "pending",
28
+ "approved",
29
+ "denied",
30
+ "expired"
31
+ ];
32
+ function normalizeStatus(value) {
33
+ return VALID_STATUSES.includes(value) ? value : "pending";
34
+ }
35
+ function stripTrailingSlash(url) {
36
+ return url.replace(/\/+$/, "");
37
+ }
38
+ const DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX = "Vana Direct Access Request v1";
39
+ function buildDirectAccessRequestAuthMessage(input) {
40
+ return [
41
+ DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX,
42
+ `method:${input.method.toUpperCase()}`,
43
+ `path:${input.path}`,
44
+ `timestamp:${input.timestamp}`,
45
+ `body:${input.body}`
46
+ ].join("\n");
47
+ }
48
+ async function buildDirectAccessRequestHeaders(options, input) {
49
+ if (!options.appAddress && !options.signMessage) {
50
+ return {};
51
+ }
52
+ if (!options.appAddress || !options.signMessage) {
53
+ throw new Error(
54
+ "Direct access-request authentication requires both `appAddress` and `signMessage`."
55
+ );
56
+ }
57
+ const timestamp = String(options.now?.() ?? Date.now());
58
+ const signature = await options.signMessage(
59
+ buildDirectAccessRequestAuthMessage({ ...input, timestamp })
60
+ );
61
+ return {
62
+ "X-Vana-App-Address": options.appAddress,
63
+ "X-Vana-App-Signature": signature,
64
+ "X-Vana-App-Timestamp": timestamp
65
+ };
66
+ }
67
+ function buildApprovalUrl(approvalBaseUrl, requestId) {
68
+ return `${stripTrailingSlash(approvalBaseUrl)}/data-connection-requests/${encodeURIComponent(
69
+ requestId
70
+ )}?mode=page`;
71
+ }
72
+ function createDefaultAccessRequestClient(options) {
73
+ const fetchFn = options.fetchFn ?? globalThis.fetch;
74
+ if (!fetchFn) {
75
+ throw new Error(
76
+ "No fetch implementation available. Pass `fetchFn` to createDefaultAccessRequestClient."
77
+ );
78
+ }
79
+ const base = stripTrailingSlash(options.baseUrl);
80
+ return {
81
+ async createAccessRequest(input) {
82
+ const path = "/api/data-connection-requests";
83
+ const body = JSON.stringify({
84
+ appAddress: input.appAddress,
85
+ app: input.app,
86
+ source: input.source,
87
+ scopes: input.scopes,
88
+ returnUrl: input.returnUrl
89
+ });
90
+ const res = await fetchFn(`${base}${path}`, {
91
+ method: "POST",
92
+ headers: {
93
+ "Content-Type": "application/json",
94
+ ...await buildDirectAccessRequestHeaders(options, {
95
+ body,
96
+ method: "POST",
97
+ path
98
+ })
99
+ },
100
+ body
101
+ });
102
+ if (!res.ok) {
103
+ throw new Error(
104
+ `Access request service error: ${res.status} ${res.statusText}`
105
+ );
106
+ }
107
+ const responseBody = await res.json();
108
+ const requestId = responseBody.requestId ?? responseBody.id;
109
+ if (!requestId) {
110
+ throw new Error("Access request service returned no requestId");
111
+ }
112
+ return {
113
+ requestId,
114
+ approvalUrl: responseBody.approvalUrl ?? buildApprovalUrl(options.approvalBaseUrl, requestId),
115
+ appAddress: responseBody.appAddress ?? input.appAddress
116
+ };
117
+ },
118
+ async getAccessRequestStatus(requestId) {
119
+ const path = `/api/data-connection-requests/${encodeURIComponent(requestId)}`;
120
+ const res = await fetchFn(`${base}${path}`, {
121
+ method: "GET",
122
+ headers: await buildDirectAccessRequestHeaders(options, {
123
+ body: "",
124
+ method: "GET",
125
+ path
126
+ })
127
+ });
128
+ if (!res.ok) {
129
+ throw new Error(
130
+ `Access request service error: ${res.status} ${res.statusText}`
131
+ );
132
+ }
133
+ const body = await res.json();
134
+ return {
135
+ status: normalizeStatus(body.status),
136
+ personalServerUrl: body.personalServerUrl,
137
+ grantId: body.grantId,
138
+ scope: body.scope
139
+ };
140
+ }
141
+ };
142
+ }
143
+ // Annotate the CommonJS export names for ESM import in node:
144
+ 0 && (module.exports = {
145
+ buildApprovalUrl,
146
+ buildDirectAccessRequestAuthMessage,
147
+ createDefaultAccessRequestClient
148
+ });
149
+ //# sourceMappingURL=access-request-client.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/direct/access-request-client.ts"],"sourcesContent":["/**\n * Default client for the Vana Account access-request API.\n *\n * @remarks\n * Calls the Vana Account endpoints that issue `dcr_*` ids and approval URLs and\n * report request status. Inject a custom {@link AccessRequestClient} on the\n * controller to point at a different deployment; pass `fetchFn` to supply a test\n * double for the HTTP layer.\n *\n * @category Direct\n * @module direct/access-request-client\n */\n\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n} from \"./types\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\n\n/** Minimal `fetch` signature so the client is testable without a global fetch. */\nexport type FetchLike = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n statusText: string;\n json(): Promise<unknown>;\n text(): Promise<string>;\n}>;\n\n/** Options for {@link createDefaultAccessRequestClient}. */\nexport interface DefaultAccessRequestClientOptions {\n /** Base URL of the Vana Account access-request API. */\n baseUrl: string;\n /** Base URL the user is sent to for approval. */\n approvalBaseUrl: string;\n /** `fetch` implementation. Defaults to the global `fetch`. */\n fetchFn?: FetchLike;\n /** App identity address used for direct access-request authentication. */\n appAddress?: string;\n /** EIP-191 signer for direct access-request authentication. */\n signMessage?: Web3SignedSignFn;\n /** Clock source used for signed request timestamps. */\n now?: () => number;\n}\n\nconst VALID_STATUSES: readonly AccessRequestStatusValue[] = [\n \"pending\",\n \"approved\",\n \"denied\",\n \"expired\",\n];\n\nfunction normalizeStatus(value: unknown): AccessRequestStatusValue {\n return VALID_STATUSES.includes(value as AccessRequestStatusValue)\n ? (value as AccessRequestStatusValue)\n : \"pending\";\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nconst DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX = \"Vana Direct Access Request v1\";\n\ninterface DirectAccessRequestAuthInput {\n body: string;\n method: string;\n path: string;\n timestamp: string;\n}\n\nexport function buildDirectAccessRequestAuthMessage(\n input: DirectAccessRequestAuthInput,\n): string {\n return [\n DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX,\n `method:${input.method.toUpperCase()}`,\n `path:${input.path}`,\n `timestamp:${input.timestamp}`,\n `body:${input.body}`,\n ].join(\"\\n\");\n}\n\nasync function buildDirectAccessRequestHeaders(\n options: DefaultAccessRequestClientOptions,\n input: Omit<DirectAccessRequestAuthInput, \"timestamp\">,\n): Promise<Record<string, string>> {\n if (!options.appAddress && !options.signMessage) {\n return {};\n }\n if (!options.appAddress || !options.signMessage) {\n throw new Error(\n \"Direct access-request authentication requires both `appAddress` and `signMessage`.\",\n );\n }\n\n const timestamp = String(options.now?.() ?? Date.now());\n const signature = await options.signMessage(\n buildDirectAccessRequestAuthMessage({ ...input, timestamp }),\n );\n\n return {\n \"X-Vana-App-Address\": options.appAddress,\n \"X-Vana-App-Signature\": signature,\n \"X-Vana-App-Timestamp\": timestamp,\n };\n}\n\n/**\n * Build an approval URL for a request id, matching the documented format\n * (`{app}/data-connection-requests/{requestId}?mode=page`).\n *\n * @param approvalBaseUrl - Base URL of the Vana approval app.\n * @param requestId - The `dcr_*` request id.\n * @returns The full approval URL.\n */\nexport function buildApprovalUrl(\n approvalBaseUrl: string,\n requestId: string,\n): string {\n return `${stripTrailingSlash(approvalBaseUrl)}/data-connection-requests/${encodeURIComponent(\n requestId,\n )}?mode=page`;\n}\n\n/**\n * Create the default {@link AccessRequestClient} for the Vana Account\n * access-request API.\n *\n * @param options - Base URLs and an optional `fetch` implementation.\n * @returns An {@link AccessRequestClient} backed by HTTP calls.\n */\nexport function createDefaultAccessRequestClient(\n options: DefaultAccessRequestClientOptions,\n): AccessRequestClient {\n const fetchFn = options.fetchFn ?? (globalThis.fetch as FetchLike);\n if (!fetchFn) {\n throw new Error(\n \"No fetch implementation available. Pass `fetchFn` to createDefaultAccessRequestClient.\",\n );\n }\n const base = stripTrailingSlash(options.baseUrl);\n\n return {\n async createAccessRequest(input): Promise<AccessRequest> {\n const path = \"/api/data-connection-requests\";\n const body = JSON.stringify({\n appAddress: input.appAddress,\n app: input.app,\n source: input.source,\n scopes: input.scopes,\n returnUrl: input.returnUrl,\n });\n const res = await fetchFn(`${base}${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await buildDirectAccessRequestHeaders(options, {\n body,\n method: \"POST\",\n path,\n })),\n },\n body,\n });\n if (!res.ok) {\n throw new Error(\n `Access request service error: ${res.status} ${res.statusText}`,\n );\n }\n const responseBody = (await res.json()) as {\n requestId?: string;\n id?: string;\n approvalUrl?: string;\n appAddress?: string;\n };\n const requestId = responseBody.requestId ?? responseBody.id;\n if (!requestId) {\n throw new Error(\"Access request service returned no requestId\");\n }\n return {\n requestId,\n approvalUrl:\n responseBody.approvalUrl ??\n buildApprovalUrl(options.approvalBaseUrl, requestId),\n appAddress: responseBody.appAddress ?? input.appAddress,\n };\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const path = `/api/data-connection-requests/${encodeURIComponent(requestId)}`;\n const res = await fetchFn(`${base}${path}`, {\n method: \"GET\",\n headers: await buildDirectAccessRequestHeaders(options, {\n body: \"\",\n method: \"GET\",\n path,\n }),\n });\n if (!res.ok) {\n throw new Error(\n `Access request service error: ${res.status} ${res.statusText}`,\n );\n }\n const body = (await res.json()) as {\n status?: string;\n personalServerUrl?: string;\n grantId?: string;\n scope?: string;\n };\n return {\n status: normalizeStatus(body.status),\n personalServerUrl: body.personalServerUrl,\n grantId: body.grantId,\n scope: body.scope,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Default client for the Vana Account access-request API.
3
+ *
4
+ * @remarks
5
+ * Calls the Vana Account endpoints that issue `dcr_*` ids and approval URLs and
6
+ * report request status. Inject a custom {@link AccessRequestClient} on the
7
+ * controller to point at a different deployment; pass `fetchFn` to supply a test
8
+ * double for the HTTP layer.
9
+ *
10
+ * @category Direct
11
+ * @module direct/access-request-client
12
+ */
13
+ import type { AccessRequestClient } from "./types";
14
+ import type { Web3SignedSignFn } from "../auth/web3-signed-builder";
15
+ /** Minimal `fetch` signature so the client is testable without a global fetch. */
16
+ export type FetchLike = (input: string, init?: {
17
+ method?: string;
18
+ headers?: Record<string, string>;
19
+ body?: string;
20
+ }) => Promise<{
21
+ ok: boolean;
22
+ status: number;
23
+ statusText: string;
24
+ json(): Promise<unknown>;
25
+ text(): Promise<string>;
26
+ }>;
27
+ /** Options for {@link createDefaultAccessRequestClient}. */
28
+ export interface DefaultAccessRequestClientOptions {
29
+ /** Base URL of the Vana Account access-request API. */
30
+ baseUrl: string;
31
+ /** Base URL the user is sent to for approval. */
32
+ approvalBaseUrl: string;
33
+ /** `fetch` implementation. Defaults to the global `fetch`. */
34
+ fetchFn?: FetchLike;
35
+ /** App identity address used for direct access-request authentication. */
36
+ appAddress?: string;
37
+ /** EIP-191 signer for direct access-request authentication. */
38
+ signMessage?: Web3SignedSignFn;
39
+ /** Clock source used for signed request timestamps. */
40
+ now?: () => number;
41
+ }
42
+ interface DirectAccessRequestAuthInput {
43
+ body: string;
44
+ method: string;
45
+ path: string;
46
+ timestamp: string;
47
+ }
48
+ export declare function buildDirectAccessRequestAuthMessage(input: DirectAccessRequestAuthInput): string;
49
+ /**
50
+ * Build an approval URL for a request id, matching the documented format
51
+ * (`{app}/data-connection-requests/{requestId}?mode=page`).
52
+ *
53
+ * @param approvalBaseUrl - Base URL of the Vana approval app.
54
+ * @param requestId - The `dcr_*` request id.
55
+ * @returns The full approval URL.
56
+ */
57
+ export declare function buildApprovalUrl(approvalBaseUrl: string, requestId: string): string;
58
+ /**
59
+ * Create the default {@link AccessRequestClient} for the Vana Account
60
+ * access-request API.
61
+ *
62
+ * @param options - Base URLs and an optional `fetch` implementation.
63
+ * @returns An {@link AccessRequestClient} backed by HTTP calls.
64
+ */
65
+ export declare function createDefaultAccessRequestClient(options: DefaultAccessRequestClientOptions): AccessRequestClient;
66
+ export {};
@@ -0,0 +1,123 @@
1
+ const VALID_STATUSES = [
2
+ "pending",
3
+ "approved",
4
+ "denied",
5
+ "expired"
6
+ ];
7
+ function normalizeStatus(value) {
8
+ return VALID_STATUSES.includes(value) ? value : "pending";
9
+ }
10
+ function stripTrailingSlash(url) {
11
+ return url.replace(/\/+$/, "");
12
+ }
13
+ const DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX = "Vana Direct Access Request v1";
14
+ function buildDirectAccessRequestAuthMessage(input) {
15
+ return [
16
+ DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX,
17
+ `method:${input.method.toUpperCase()}`,
18
+ `path:${input.path}`,
19
+ `timestamp:${input.timestamp}`,
20
+ `body:${input.body}`
21
+ ].join("\n");
22
+ }
23
+ async function buildDirectAccessRequestHeaders(options, input) {
24
+ if (!options.appAddress && !options.signMessage) {
25
+ return {};
26
+ }
27
+ if (!options.appAddress || !options.signMessage) {
28
+ throw new Error(
29
+ "Direct access-request authentication requires both `appAddress` and `signMessage`."
30
+ );
31
+ }
32
+ const timestamp = String(options.now?.() ?? Date.now());
33
+ const signature = await options.signMessage(
34
+ buildDirectAccessRequestAuthMessage({ ...input, timestamp })
35
+ );
36
+ return {
37
+ "X-Vana-App-Address": options.appAddress,
38
+ "X-Vana-App-Signature": signature,
39
+ "X-Vana-App-Timestamp": timestamp
40
+ };
41
+ }
42
+ function buildApprovalUrl(approvalBaseUrl, requestId) {
43
+ return `${stripTrailingSlash(approvalBaseUrl)}/data-connection-requests/${encodeURIComponent(
44
+ requestId
45
+ )}?mode=page`;
46
+ }
47
+ function createDefaultAccessRequestClient(options) {
48
+ const fetchFn = options.fetchFn ?? globalThis.fetch;
49
+ if (!fetchFn) {
50
+ throw new Error(
51
+ "No fetch implementation available. Pass `fetchFn` to createDefaultAccessRequestClient."
52
+ );
53
+ }
54
+ const base = stripTrailingSlash(options.baseUrl);
55
+ return {
56
+ async createAccessRequest(input) {
57
+ const path = "/api/data-connection-requests";
58
+ const body = JSON.stringify({
59
+ appAddress: input.appAddress,
60
+ app: input.app,
61
+ source: input.source,
62
+ scopes: input.scopes,
63
+ returnUrl: input.returnUrl
64
+ });
65
+ const res = await fetchFn(`${base}${path}`, {
66
+ method: "POST",
67
+ headers: {
68
+ "Content-Type": "application/json",
69
+ ...await buildDirectAccessRequestHeaders(options, {
70
+ body,
71
+ method: "POST",
72
+ path
73
+ })
74
+ },
75
+ body
76
+ });
77
+ if (!res.ok) {
78
+ throw new Error(
79
+ `Access request service error: ${res.status} ${res.statusText}`
80
+ );
81
+ }
82
+ const responseBody = await res.json();
83
+ const requestId = responseBody.requestId ?? responseBody.id;
84
+ if (!requestId) {
85
+ throw new Error("Access request service returned no requestId");
86
+ }
87
+ return {
88
+ requestId,
89
+ approvalUrl: responseBody.approvalUrl ?? buildApprovalUrl(options.approvalBaseUrl, requestId),
90
+ appAddress: responseBody.appAddress ?? input.appAddress
91
+ };
92
+ },
93
+ async getAccessRequestStatus(requestId) {
94
+ const path = `/api/data-connection-requests/${encodeURIComponent(requestId)}`;
95
+ const res = await fetchFn(`${base}${path}`, {
96
+ method: "GET",
97
+ headers: await buildDirectAccessRequestHeaders(options, {
98
+ body: "",
99
+ method: "GET",
100
+ path
101
+ })
102
+ });
103
+ if (!res.ok) {
104
+ throw new Error(
105
+ `Access request service error: ${res.status} ${res.statusText}`
106
+ );
107
+ }
108
+ const body = await res.json();
109
+ return {
110
+ status: normalizeStatus(body.status),
111
+ personalServerUrl: body.personalServerUrl,
112
+ grantId: body.grantId,
113
+ scope: body.scope
114
+ };
115
+ }
116
+ };
117
+ }
118
+ export {
119
+ buildApprovalUrl,
120
+ buildDirectAccessRequestAuthMessage,
121
+ createDefaultAccessRequestClient
122
+ };
123
+ //# sourceMappingURL=access-request-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/direct/access-request-client.ts"],"sourcesContent":["/**\n * Default client for the Vana Account access-request API.\n *\n * @remarks\n * Calls the Vana Account endpoints that issue `dcr_*` ids and approval URLs and\n * report request status. Inject a custom {@link AccessRequestClient} on the\n * controller to point at a different deployment; pass `fetchFn` to supply a test\n * double for the HTTP layer.\n *\n * @category Direct\n * @module direct/access-request-client\n */\n\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n} from \"./types\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\n\n/** Minimal `fetch` signature so the client is testable without a global fetch. */\nexport type FetchLike = (\n input: string,\n init?: {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n },\n) => Promise<{\n ok: boolean;\n status: number;\n statusText: string;\n json(): Promise<unknown>;\n text(): Promise<string>;\n}>;\n\n/** Options for {@link createDefaultAccessRequestClient}. */\nexport interface DefaultAccessRequestClientOptions {\n /** Base URL of the Vana Account access-request API. */\n baseUrl: string;\n /** Base URL the user is sent to for approval. */\n approvalBaseUrl: string;\n /** `fetch` implementation. Defaults to the global `fetch`. */\n fetchFn?: FetchLike;\n /** App identity address used for direct access-request authentication. */\n appAddress?: string;\n /** EIP-191 signer for direct access-request authentication. */\n signMessage?: Web3SignedSignFn;\n /** Clock source used for signed request timestamps. */\n now?: () => number;\n}\n\nconst VALID_STATUSES: readonly AccessRequestStatusValue[] = [\n \"pending\",\n \"approved\",\n \"denied\",\n \"expired\",\n];\n\nfunction normalizeStatus(value: unknown): AccessRequestStatusValue {\n return VALID_STATUSES.includes(value as AccessRequestStatusValue)\n ? (value as AccessRequestStatusValue)\n : \"pending\";\n}\n\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, \"\");\n}\n\nconst DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX = \"Vana Direct Access Request v1\";\n\ninterface DirectAccessRequestAuthInput {\n body: string;\n method: string;\n path: string;\n timestamp: string;\n}\n\nexport function buildDirectAccessRequestAuthMessage(\n input: DirectAccessRequestAuthInput,\n): string {\n return [\n DIRECT_ACCESS_REQUEST_MESSAGE_PREFIX,\n `method:${input.method.toUpperCase()}`,\n `path:${input.path}`,\n `timestamp:${input.timestamp}`,\n `body:${input.body}`,\n ].join(\"\\n\");\n}\n\nasync function buildDirectAccessRequestHeaders(\n options: DefaultAccessRequestClientOptions,\n input: Omit<DirectAccessRequestAuthInput, \"timestamp\">,\n): Promise<Record<string, string>> {\n if (!options.appAddress && !options.signMessage) {\n return {};\n }\n if (!options.appAddress || !options.signMessage) {\n throw new Error(\n \"Direct access-request authentication requires both `appAddress` and `signMessage`.\",\n );\n }\n\n const timestamp = String(options.now?.() ?? Date.now());\n const signature = await options.signMessage(\n buildDirectAccessRequestAuthMessage({ ...input, timestamp }),\n );\n\n return {\n \"X-Vana-App-Address\": options.appAddress,\n \"X-Vana-App-Signature\": signature,\n \"X-Vana-App-Timestamp\": timestamp,\n };\n}\n\n/**\n * Build an approval URL for a request id, matching the documented format\n * (`{app}/data-connection-requests/{requestId}?mode=page`).\n *\n * @param approvalBaseUrl - Base URL of the Vana approval app.\n * @param requestId - The `dcr_*` request id.\n * @returns The full approval URL.\n */\nexport function buildApprovalUrl(\n approvalBaseUrl: string,\n requestId: string,\n): string {\n return `${stripTrailingSlash(approvalBaseUrl)}/data-connection-requests/${encodeURIComponent(\n requestId,\n )}?mode=page`;\n}\n\n/**\n * Create the default {@link AccessRequestClient} for the Vana Account\n * access-request API.\n *\n * @param options - Base URLs and an optional `fetch` implementation.\n * @returns An {@link AccessRequestClient} backed by HTTP calls.\n */\nexport function createDefaultAccessRequestClient(\n options: DefaultAccessRequestClientOptions,\n): AccessRequestClient {\n const fetchFn = options.fetchFn ?? (globalThis.fetch as FetchLike);\n if (!fetchFn) {\n throw new Error(\n \"No fetch implementation available. Pass `fetchFn` to createDefaultAccessRequestClient.\",\n );\n }\n const base = stripTrailingSlash(options.baseUrl);\n\n return {\n async createAccessRequest(input): Promise<AccessRequest> {\n const path = \"/api/data-connection-requests\";\n const body = JSON.stringify({\n appAddress: input.appAddress,\n app: input.app,\n source: input.source,\n scopes: input.scopes,\n returnUrl: input.returnUrl,\n });\n const res = await fetchFn(`${base}${path}`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(await buildDirectAccessRequestHeaders(options, {\n body,\n method: \"POST\",\n path,\n })),\n },\n body,\n });\n if (!res.ok) {\n throw new Error(\n `Access request service error: ${res.status} ${res.statusText}`,\n );\n }\n const responseBody = (await res.json()) as {\n requestId?: string;\n id?: string;\n approvalUrl?: string;\n appAddress?: string;\n };\n const requestId = responseBody.requestId ?? responseBody.id;\n if (!requestId) {\n throw new Error(\"Access request service returned no requestId\");\n }\n return {\n requestId,\n approvalUrl:\n responseBody.approvalUrl ??\n buildApprovalUrl(options.approvalBaseUrl, requestId),\n appAddress: responseBody.appAddress ?? input.appAddress,\n };\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const path = `/api/data-connection-requests/${encodeURIComponent(requestId)}`;\n const res = await fetchFn(`${base}${path}`, {\n method: \"GET\",\n headers: await buildDirectAccessRequestHeaders(options, {\n body: \"\",\n method: \"GET\",\n path,\n }),\n });\n if (!res.ok) {\n throw new Error(\n `Access request service error: ${res.status} ${res.statusText}`,\n );\n }\n const body = (await res.json()) as {\n status?: string;\n personalServerUrl?: string;\n grantId?: string;\n scope?: string;\n };\n return {\n status: normalizeStatus(body.status),\n personalServerUrl: body.personalServerUrl,\n grantId: body.grantId,\n scope: body.scope,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,152 @@
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 connect_flow_exports = {};
20
+ __export(connect_flow_exports, {
21
+ createDirectConnectFlow: () => createDirectConnectFlow
22
+ });
23
+ module.exports = __toCommonJS(connect_flow_exports);
24
+ const DEFAULT_POLL_INTERVAL_MS = 1500;
25
+ const DEFAULT_TIMEOUT_MS = 3e5;
26
+ function toError(value) {
27
+ return value instanceof Error ? value : new Error(String(value));
28
+ }
29
+ function createDirectConnectFlow(transports, options = {}) {
30
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
31
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
32
+ const openWindow = options.openWindow ?? ((url) => {
33
+ if (typeof window !== "undefined" && window.open) {
34
+ window.open(url, "_blank", "noopener,noreferrer");
35
+ }
36
+ });
37
+ const setTimeoutFn = options.setTimeoutFn ?? ((cb, ms) => globalThis.setTimeout(cb, ms));
38
+ const clearTimeoutFn = options.clearTimeoutFn ?? ((handle) => {
39
+ globalThis.clearTimeout(handle);
40
+ });
41
+ const now = options.now ?? (() => Date.now());
42
+ let state = { type: "idle" };
43
+ const listeners = /* @__PURE__ */ new Set();
44
+ let pollHandle = null;
45
+ let running = false;
46
+ function emit() {
47
+ for (const listener of listeners) listener();
48
+ }
49
+ function setState(next) {
50
+ state = next;
51
+ emit();
52
+ }
53
+ function clearPoll() {
54
+ if (pollHandle !== null) {
55
+ clearTimeoutFn(pollHandle);
56
+ pollHandle = null;
57
+ }
58
+ }
59
+ function isRunningPhase() {
60
+ return state.type === "creating" || state.type === "awaiting_approval" || state.type === "reading";
61
+ }
62
+ async function readAndFinish(request) {
63
+ setState({ type: "reading", request });
64
+ try {
65
+ const result = await transports.readResult(request.requestId);
66
+ if (!running) return;
67
+ setState({ type: "done", result });
68
+ } catch (err) {
69
+ if (!running) return;
70
+ setState({ type: "error", error: toError(err) });
71
+ } finally {
72
+ running = false;
73
+ }
74
+ }
75
+ function scheduleNextPoll(request, deadline) {
76
+ pollHandle = setTimeoutFn(() => {
77
+ void poll(request, deadline);
78
+ }, pollIntervalMs);
79
+ }
80
+ async function poll(request, deadline) {
81
+ if (!running) return;
82
+ if (now() >= deadline) {
83
+ running = false;
84
+ setState({
85
+ type: "error",
86
+ error: new Error("Timed out waiting for approval")
87
+ });
88
+ return;
89
+ }
90
+ let status;
91
+ try {
92
+ status = await transports.getStatus(request.requestId);
93
+ } catch (err) {
94
+ if (!running) return;
95
+ running = false;
96
+ setState({ type: "error", error: toError(err) });
97
+ return;
98
+ }
99
+ if (!running) return;
100
+ if (status.status === "approved") {
101
+ clearPoll();
102
+ await readAndFinish(request);
103
+ return;
104
+ }
105
+ if (status.status === "denied" || status.status === "expired") {
106
+ running = false;
107
+ setState({
108
+ type: "error",
109
+ error: new Error(`Access request ${status.status}`)
110
+ });
111
+ return;
112
+ }
113
+ scheduleNextPoll(request, deadline);
114
+ }
115
+ return {
116
+ getState() {
117
+ return state;
118
+ },
119
+ subscribe(listener) {
120
+ listeners.add(listener);
121
+ return () => listeners.delete(listener);
122
+ },
123
+ async start() {
124
+ if (running || isRunningPhase()) return;
125
+ running = true;
126
+ setState({ type: "creating" });
127
+ let request;
128
+ try {
129
+ request = await transports.createRequest();
130
+ } catch (err) {
131
+ running = false;
132
+ setState({ type: "error", error: toError(err) });
133
+ return;
134
+ }
135
+ if (!running) return;
136
+ setState({ type: "awaiting_approval", request });
137
+ openWindow(request.approvalUrl);
138
+ const deadline = now() + timeoutMs;
139
+ scheduleNextPoll(request, deadline);
140
+ },
141
+ reset() {
142
+ running = false;
143
+ clearPoll();
144
+ setState({ type: "idle" });
145
+ }
146
+ };
147
+ }
148
+ // Annotate the CommonJS export names for ESM import in node:
149
+ 0 && (module.exports = {
150
+ createDirectConnectFlow
151
+ });
152
+ //# sourceMappingURL=connect-flow.cjs.map