@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.
- package/README.md +116 -0
- package/dist/direct/access-request-client.cjs +149 -0
- package/dist/direct/access-request-client.cjs.map +1 -0
- package/dist/direct/access-request-client.d.ts +66 -0
- package/dist/direct/access-request-client.js +123 -0
- package/dist/direct/access-request-client.js.map +1 -0
- package/dist/direct/access-request-client.test.d.ts +1 -0
- package/dist/direct/connect-flow.cjs +152 -0
- package/dist/direct/connect-flow.cjs.map +1 -0
- package/dist/direct/connect-flow.d.ts +85 -0
- package/dist/direct/connect-flow.js +128 -0
- package/dist/direct/connect-flow.js.map +1 -0
- package/dist/direct/connect-flow.test.d.ts +1 -0
- package/dist/direct/controller.cjs +131 -0
- package/dist/direct/controller.cjs.map +1 -0
- package/dist/direct/controller.d.ts +152 -0
- package/dist/direct/controller.js +111 -0
- package/dist/direct/controller.js.map +1 -0
- package/dist/direct/controller.test.d.ts +1 -0
- package/dist/direct/endpoints.cjs +45 -0
- package/dist/direct/endpoints.cjs.map +1 -0
- package/dist/direct/endpoints.d.ts +22 -0
- package/dist/direct/endpoints.js +19 -0
- package/dist/direct/endpoints.js.map +1 -0
- package/dist/direct/errors.cjs +65 -0
- package/dist/direct/errors.cjs.map +1 -0
- package/dist/direct/errors.d.ts +44 -0
- package/dist/direct/errors.js +38 -0
- package/dist/direct/errors.js.map +1 -0
- package/dist/direct/escrow-payment.cjs +96 -0
- package/dist/direct/escrow-payment.cjs.map +1 -0
- package/dist/direct/escrow-payment.d.ts +81 -0
- package/dist/direct/escrow-payment.js +72 -0
- package/dist/direct/escrow-payment.js.map +1 -0
- package/dist/direct/escrow-payment.test.d.ts +1 -0
- package/dist/direct/personal-server-read.cjs +149 -0
- package/dist/direct/personal-server-read.cjs.map +1 -0
- package/dist/direct/personal-server-read.d.ts +103 -0
- package/dist/direct/personal-server-read.js +124 -0
- package/dist/direct/personal-server-read.js.map +1 -0
- package/dist/direct/personal-server-read.test.d.ts +1 -0
- package/dist/direct/types.cjs +35 -0
- package/dist/direct/types.cjs.map +1 -0
- package/dist/direct/types.d.ts +205 -0
- package/dist/direct/types.js +11 -0
- package/dist/direct/types.js.map +1 -0
- package/dist/direct/use-direct-vana-connect.cjs +68 -0
- package/dist/direct/use-direct-vana-connect.cjs.map +1 -0
- package/dist/direct/use-direct-vana-connect.d.ts +45 -0
- package/dist/direct/use-direct-vana-connect.js +46 -0
- package/dist/direct/use-direct-vana-connect.js.map +1 -0
- package/dist/index.browser.d.ts +7 -3
- package/dist/index.browser.js +438 -157
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +461 -162
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +7 -3
- package/dist/index.node.js +438 -157
- package/dist/index.node.js.map +4 -4
- package/dist/protocol/data-point-status.cjs +80 -0
- package/dist/protocol/data-point-status.cjs.map +1 -0
- package/dist/protocol/data-point-status.d.ts +34 -0
- package/dist/protocol/data-point-status.js +51 -0
- package/dist/protocol/data-point-status.js.map +1 -0
- package/dist/protocol/data-point-status.test.d.ts +1 -0
- package/dist/protocol/eip712.cjs +53 -31
- package/dist/protocol/eip712.cjs.map +1 -1
- package/dist/protocol/eip712.d.ts +98 -43
- package/dist/protocol/eip712.js +47 -27
- package/dist/protocol/eip712.js.map +1 -1
- package/dist/protocol/escrow-deposit.cjs +89 -0
- package/dist/protocol/escrow-deposit.cjs.map +1 -0
- package/dist/protocol/escrow-deposit.d.ts +47 -0
- package/dist/protocol/escrow-deposit.js +60 -0
- package/dist/protocol/escrow-deposit.js.map +1 -0
- package/dist/protocol/escrow-deposit.test.d.ts +1 -0
- package/dist/protocol/escrow-flow.test.d.ts +21 -0
- package/dist/protocol/fee-registry.cjs +116 -0
- package/dist/protocol/fee-registry.cjs.map +1 -0
- package/dist/protocol/fee-registry.d.ts +151 -0
- package/dist/protocol/fee-registry.js +89 -0
- package/dist/protocol/fee-registry.js.map +1 -0
- package/dist/protocol/fee-registry.test.d.ts +1 -0
- package/dist/protocol/gateway.cjs +107 -37
- package/dist/protocol/gateway.cjs.map +1 -1
- package/dist/protocol/gateway.d.ts +223 -57
- package/dist/protocol/gateway.js +107 -37
- package/dist/protocol/gateway.js.map +1 -1
- package/dist/protocol/grants.cjs +27 -64
- package/dist/protocol/grants.cjs.map +1 -1
- package/dist/protocol/grants.d.ts +6 -13
- package/dist/protocol/grants.js +27 -63
- package/dist/protocol/grants.js.map +1 -1
- package/dist/protocol/personal-server-data.cjs +71 -0
- package/dist/protocol/personal-server-data.cjs.map +1 -0
- package/dist/protocol/personal-server-data.d.ts +16 -0
- package/dist/protocol/personal-server-data.js +47 -0
- package/dist/protocol/personal-server-data.js.map +1 -0
- package/dist/protocol/personal-server-data.test.d.ts +1 -0
- package/dist/protocol/personal-server-lite-owner-binding.cjs +93 -0
- package/dist/protocol/personal-server-lite-owner-binding.cjs.map +1 -0
- package/dist/protocol/personal-server-lite-owner-binding.d.ts +44 -0
- package/dist/protocol/personal-server-lite-owner-binding.js +65 -0
- package/dist/protocol/personal-server-lite-owner-binding.js.map +1 -0
- package/dist/protocol/personal-server-lite-owner-binding.test.d.ts +1 -0
- package/dist/react.cjs +32 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.ts +33 -0
- package/dist/react.js +11 -0
- package/dist/react.js.map +1 -0
- package/dist/server.cjs +73 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.ts +32 -0
- package/dist/server.js +55 -0
- package/dist/server.js.map +1 -0
- 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
|