@opendatalabs/vana-sdk 3.4.1 → 3.5.1-pr.159.12a6f39
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/account/personal-server-lite-owner-binding.cjs +3 -3
- package/dist/account/personal-server-lite-owner-binding.cjs.map +1 -1
- package/dist/account/personal-server-lite-owner-binding.d.ts +2 -2
- package/dist/account/personal-server-lite-owner-binding.js +1 -1
- package/dist/account/personal-server-lite-owner-binding.js.map +1 -1
- package/dist/direct/access-request-client.cjs +104 -0
- package/dist/direct/access-request-client.cjs.map +1 -0
- package/dist/direct/access-request-client.d.ts +51 -0
- package/dist/direct/access-request-client.js +79 -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 +129 -0
- package/dist/direct/controller.cjs.map +1 -0
- package/dist/direct/controller.d.ts +152 -0
- package/dist/direct/controller.js +109 -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 +9 -4
- package/dist/index.browser.js +644 -178
- package/dist/index.browser.js.map +4 -4
- package/dist/index.node.cjs +672 -183
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +9 -4
- package/dist/index.node.js +644 -178
- package/dist/index.node.js.map +4 -4
- package/dist/personal-server-lite/owner-binding.cjs +93 -0
- package/dist/personal-server-lite/owner-binding.cjs.map +1 -0
- package/dist/personal-server-lite/owner-binding.d.ts +44 -0
- package/dist/personal-server-lite/owner-binding.js +65 -0
- package/dist/personal-server-lite/owner-binding.js.map +1 -0
- package/dist/personal-server-lite/owner-binding.test.d.ts +1 -0
- 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/escrow.cjs +146 -0
- package/dist/protocol/escrow.cjs.map +1 -0
- package/dist/protocol/escrow.d.ts +336 -0
- package/dist/protocol/escrow.js +118 -0
- package/dist/protocol/escrow.js.map +1 -0
- package/dist/protocol/escrow.test.d.ts +1 -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-registration.cjs +16 -4
- package/dist/protocol/personal-server-registration.cjs.map +1 -1
- package/dist/protocol/personal-server-registration.d.ts +5 -2
- package/dist/protocol/personal-server-registration.js +16 -4
- package/dist/protocol/personal-server-registration.js.map +1 -1
- 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/dist/storage/providers/vana-storage.cjs +75 -17
- package/dist/storage/providers/vana-storage.cjs.map +1 -1
- package/dist/storage/providers/vana-storage.js +75 -17
- package/dist/storage/providers/vana-storage.js.map +1 -1
- 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 |
|
|
@@ -22,7 +22,7 @@ __export(personal_server_lite_owner_binding_exports, {
|
|
|
22
22
|
signPersonalServerLiteOwnerBindingWithAccountClient: () => signPersonalServerLiteOwnerBindingWithAccountClient
|
|
23
23
|
});
|
|
24
24
|
module.exports = __toCommonJS(personal_server_lite_owner_binding_exports);
|
|
25
|
-
var
|
|
25
|
+
var import_owner_binding = require("../personal-server-lite/owner-binding");
|
|
26
26
|
class AccountPersonalServerLiteOwnerBindingError extends Error {
|
|
27
27
|
code;
|
|
28
28
|
details;
|
|
@@ -46,7 +46,7 @@ async function signPersonalServerLiteOwnerBindingWithAccountClient(config) {
|
|
|
46
46
|
code: "account_address_required"
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
|
-
const message = (0,
|
|
49
|
+
const message = (0, import_owner_binding.buildPersonalServerLiteOwnerBindingMessage)(address);
|
|
50
50
|
let signature;
|
|
51
51
|
try {
|
|
52
52
|
signature = await config.client.signMessage({ message });
|
|
@@ -57,7 +57,7 @@ async function signPersonalServerLiteOwnerBindingWithAccountClient(config) {
|
|
|
57
57
|
signature,
|
|
58
58
|
signerAddress: address,
|
|
59
59
|
message,
|
|
60
|
-
purpose:
|
|
60
|
+
purpose: import_owner_binding.PERSONAL_SERVER_LITE_OWNER_BINDING_PURPOSE
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
function accountOwnerBindingError(error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/account/personal-server-lite-owner-binding.ts"],"sourcesContent":["/**\n * Optional first-party Account integration for PS Lite owner binding.\n *\n * The
|
|
1
|
+
{"version":3,"sources":["../../src/account/personal-server-lite-owner-binding.ts"],"sourcesContent":["/**\n * Optional first-party Account integration for PS Lite owner binding.\n *\n * The PS Lite helper lives in `personal-server-lite/owner-binding`.\n * This module adapts any Account-style client exposing `getAddress` and\n * `signMessage` to the SDK owner-binding signature shape.\n *\n * @category Account\n */\n\nimport type { Address, Hex } from \"viem\";\nimport {\n buildPersonalServerLiteOwnerBindingMessage,\n PERSONAL_SERVER_LITE_OWNER_BINDING_PURPOSE,\n type PersonalServerLiteOwnerBindingSignature,\n} from \"../personal-server-lite/owner-binding\";\n\nexport interface AccountPersonalServerLiteOwnerBindingClient {\n getAddress(): Promise<Address | null> | Address | null;\n signMessage(input: {\n message: ReturnType<typeof buildPersonalServerLiteOwnerBindingMessage>;\n }): Promise<Hex> | Hex;\n}\n\nexport interface SignPersonalServerLiteOwnerBindingWithAccountClientConfig {\n client: AccountPersonalServerLiteOwnerBindingClient;\n}\n\nexport class AccountPersonalServerLiteOwnerBindingError extends Error {\n code?: number | string;\n details?: unknown;\n\n constructor(input: {\n message: string;\n code?: number | string;\n details?: unknown;\n }) {\n super(input.message);\n this.name = \"AccountPersonalServerLiteOwnerBindingError\";\n this.code = input.code;\n this.details = input.details;\n }\n}\n\nexport async function signPersonalServerLiteOwnerBindingWithAccountClient(\n config: SignPersonalServerLiteOwnerBindingWithAccountClientConfig,\n): Promise<PersonalServerLiteOwnerBindingSignature> {\n let address: Address | null;\n try {\n address = await config.client.getAddress();\n } catch (error) {\n throw accountOwnerBindingError(error);\n }\n\n if (!address) {\n throw new AccountPersonalServerLiteOwnerBindingError({\n message: \"Account did not return a wallet address\",\n code: \"account_address_required\",\n });\n }\n\n const message = buildPersonalServerLiteOwnerBindingMessage(address);\n let signature: Hex;\n try {\n signature = await config.client.signMessage({ message });\n } catch (error) {\n throw accountOwnerBindingError(error);\n }\n\n return {\n signature,\n signerAddress: address,\n message,\n purpose: PERSONAL_SERVER_LITE_OWNER_BINDING_PURPOSE,\n };\n}\n\nfunction accountOwnerBindingError(\n error: unknown,\n): AccountPersonalServerLiteOwnerBindingError {\n if (error instanceof AccountPersonalServerLiteOwnerBindingError) {\n return error;\n }\n\n const rpcError = error as\n | { code?: number | string; message?: string }\n | undefined;\n const code = rpcError?.code;\n const message =\n typeof rpcError?.message === \"string\" && rpcError.message.length > 0\n ? rpcError.message\n : \"Account PS Lite owner-binding signature failed\";\n\n return new AccountPersonalServerLiteOwnerBindingError({\n message,\n code,\n details: error,\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAWA,2BAIO;AAaA,MAAM,mDAAmD,MAAM;AAAA,EACpE;AAAA,EACA;AAAA,EAEA,YAAY,OAIT;AACD,UAAM,MAAM,OAAO;AACnB,SAAK,OAAO;AACZ,SAAK,OAAO,MAAM;AAClB,SAAK,UAAU,MAAM;AAAA,EACvB;AACF;AAEA,eAAsB,oDACpB,QACkD;AAClD,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,OAAO,OAAO,WAAW;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,yBAAyB,KAAK;AAAA,EACtC;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,2CAA2C;AAAA,MACnD,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,QAAM,cAAU,iEAA2C,OAAO;AAClE,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,OAAO,OAAO,YAAY,EAAE,QAAQ,CAAC;AAAA,EACzD,SAAS,OAAO;AACd,UAAM,yBAAyB,KAAK;AAAA,EACtC;AAEA,SAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA,SAAS;AAAA,EACX;AACF;AAEA,SAAS,yBACP,OAC4C;AAC5C,MAAI,iBAAiB,4CAA4C;AAC/D,WAAO;AAAA,EACT;AAEA,QAAM,WAAW;AAGjB,QAAM,OAAO,UAAU;AACvB,QAAM,UACJ,OAAO,UAAU,YAAY,YAAY,SAAS,QAAQ,SAAS,IAC/D,SAAS,UACT;AAEN,SAAO,IAAI,2CAA2C;AAAA,IACpD;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Optional first-party Account integration for PS Lite owner binding.
|
|
3
3
|
*
|
|
4
|
-
* The
|
|
4
|
+
* The PS Lite helper lives in `personal-server-lite/owner-binding`.
|
|
5
5
|
* This module adapts any Account-style client exposing `getAddress` and
|
|
6
6
|
* `signMessage` to the SDK owner-binding signature shape.
|
|
7
7
|
*
|
|
8
8
|
* @category Account
|
|
9
9
|
*/
|
|
10
10
|
import type { Address, Hex } from "viem";
|
|
11
|
-
import { buildPersonalServerLiteOwnerBindingMessage, type PersonalServerLiteOwnerBindingSignature } from "../
|
|
11
|
+
import { buildPersonalServerLiteOwnerBindingMessage, type PersonalServerLiteOwnerBindingSignature } from "../personal-server-lite/owner-binding";
|
|
12
12
|
export interface AccountPersonalServerLiteOwnerBindingClient {
|
|
13
13
|
getAddress(): Promise<Address | null> | Address | null;
|
|
14
14
|
signMessage(input: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildPersonalServerLiteOwnerBindingMessage,
|
|
3
3
|
PERSONAL_SERVER_LITE_OWNER_BINDING_PURPOSE
|
|
4
|
-
} from "../
|
|
4
|
+
} from "../personal-server-lite/owner-binding.js";
|
|
5
5
|
class AccountPersonalServerLiteOwnerBindingError extends Error {
|
|
6
6
|
code;
|
|
7
7
|
details;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/account/personal-server-lite-owner-binding.ts"],"sourcesContent":["/**\n * Optional first-party Account integration for PS Lite owner binding.\n *\n * The
|
|
1
|
+
{"version":3,"sources":["../../src/account/personal-server-lite-owner-binding.ts"],"sourcesContent":["/**\n * Optional first-party Account integration for PS Lite owner binding.\n *\n * The PS Lite helper lives in `personal-server-lite/owner-binding`.\n * This module adapts any Account-style client exposing `getAddress` and\n * `signMessage` to the SDK owner-binding signature shape.\n *\n * @category Account\n */\n\nimport type { Address, Hex } from \"viem\";\nimport {\n buildPersonalServerLiteOwnerBindingMessage,\n PERSONAL_SERVER_LITE_OWNER_BINDING_PURPOSE,\n type PersonalServerLiteOwnerBindingSignature,\n} from \"../personal-server-lite/owner-binding\";\n\nexport interface AccountPersonalServerLiteOwnerBindingClient {\n getAddress(): Promise<Address | null> | Address | null;\n signMessage(input: {\n message: ReturnType<typeof buildPersonalServerLiteOwnerBindingMessage>;\n }): Promise<Hex> | Hex;\n}\n\nexport interface SignPersonalServerLiteOwnerBindingWithAccountClientConfig {\n client: AccountPersonalServerLiteOwnerBindingClient;\n}\n\nexport class AccountPersonalServerLiteOwnerBindingError extends Error {\n code?: number | string;\n details?: unknown;\n\n constructor(input: {\n message: string;\n code?: number | string;\n details?: unknown;\n }) {\n super(input.message);\n this.name = \"AccountPersonalServerLiteOwnerBindingError\";\n this.code = input.code;\n this.details = input.details;\n }\n}\n\nexport async function signPersonalServerLiteOwnerBindingWithAccountClient(\n config: SignPersonalServerLiteOwnerBindingWithAccountClientConfig,\n): Promise<PersonalServerLiteOwnerBindingSignature> {\n let address: Address | null;\n try {\n address = await config.client.getAddress();\n } catch (error) {\n throw accountOwnerBindingError(error);\n }\n\n if (!address) {\n throw new AccountPersonalServerLiteOwnerBindingError({\n message: \"Account did not return a wallet address\",\n code: \"account_address_required\",\n });\n }\n\n const message = buildPersonalServerLiteOwnerBindingMessage(address);\n let signature: Hex;\n try {\n signature = await config.client.signMessage({ message });\n } catch (error) {\n throw accountOwnerBindingError(error);\n }\n\n return {\n signature,\n signerAddress: address,\n message,\n purpose: PERSONAL_SERVER_LITE_OWNER_BINDING_PURPOSE,\n };\n}\n\nfunction accountOwnerBindingError(\n error: unknown,\n): AccountPersonalServerLiteOwnerBindingError {\n if (error instanceof AccountPersonalServerLiteOwnerBindingError) {\n return error;\n }\n\n const rpcError = error as\n | { code?: number | string; message?: string }\n | undefined;\n const code = rpcError?.code;\n const message =\n typeof rpcError?.message === \"string\" && rpcError.message.length > 0\n ? rpcError.message\n : \"Account PS Lite owner-binding signature failed\";\n\n return new AccountPersonalServerLiteOwnerBindingError({\n message,\n code,\n details: error,\n });\n}\n"],"mappings":"AAWA;AAAA,EACE;AAAA,EACA;AAAA,OAEK;AAaA,MAAM,mDAAmD,MAAM;AAAA,EACpE;AAAA,EACA;AAAA,EAEA,YAAY,OAIT;AACD,UAAM,MAAM,OAAO;AACnB,SAAK,OAAO;AACZ,SAAK,OAAO,MAAM;AAClB,SAAK,UAAU,MAAM;AAAA,EACvB;AACF;AAEA,eAAsB,oDACpB,QACkD;AAClD,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,OAAO,OAAO,WAAW;AAAA,EAC3C,SAAS,OAAO;AACd,UAAM,yBAAyB,KAAK;AAAA,EACtC;AAEA,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,2CAA2C;AAAA,MACnD,SAAS;AAAA,MACT,MAAM;AAAA,IACR,CAAC;AAAA,EACH;AAEA,QAAM,UAAU,2CAA2C,OAAO;AAClE,MAAI;AACJ,MAAI;AACF,gBAAY,MAAM,OAAO,OAAO,YAAY,EAAE,QAAQ,CAAC;AAAA,EACzD,SAAS,OAAO;AACd,UAAM,yBAAyB,KAAK;AAAA,EACtC;AAEA,SAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf;AAAA,IACA,SAAS;AAAA,EACX;AACF;AAEA,SAAS,yBACP,OAC4C;AAC5C,MAAI,iBAAiB,4CAA4C;AAC/D,WAAO;AAAA,EACT;AAEA,QAAM,WAAW;AAGjB,QAAM,OAAO,UAAU;AACvB,QAAM,UACJ,OAAO,UAAU,YAAY,YAAY,SAAS,QAAQ,SAAS,IAC/D,SAAS,UACT;AAEN,SAAO,IAAI,2CAA2C;AAAA,IACpD;AAAA,IACA;AAAA,IACA,SAAS;AAAA,EACX,CAAC;AACH;","names":[]}
|
|
@@ -0,0 +1,104 @@
|
|
|
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
|
+
createDefaultAccessRequestClient: () => createDefaultAccessRequestClient
|
|
23
|
+
});
|
|
24
|
+
module.exports = __toCommonJS(access_request_client_exports);
|
|
25
|
+
const VALID_STATUSES = [
|
|
26
|
+
"pending",
|
|
27
|
+
"approved",
|
|
28
|
+
"denied",
|
|
29
|
+
"expired"
|
|
30
|
+
];
|
|
31
|
+
function normalizeStatus(value) {
|
|
32
|
+
return VALID_STATUSES.includes(value) ? value : "pending";
|
|
33
|
+
}
|
|
34
|
+
function stripTrailingSlash(url) {
|
|
35
|
+
return url.replace(/\/+$/, "");
|
|
36
|
+
}
|
|
37
|
+
function buildApprovalUrl(approvalBaseUrl, requestId) {
|
|
38
|
+
return `${stripTrailingSlash(approvalBaseUrl)}/data-connection-requests/${encodeURIComponent(
|
|
39
|
+
requestId
|
|
40
|
+
)}?mode=page`;
|
|
41
|
+
}
|
|
42
|
+
function createDefaultAccessRequestClient(options) {
|
|
43
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
44
|
+
if (!fetchFn) {
|
|
45
|
+
throw new Error(
|
|
46
|
+
"No fetch implementation available. Pass `fetchFn` to createDefaultAccessRequestClient."
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
const base = stripTrailingSlash(options.baseUrl);
|
|
50
|
+
return {
|
|
51
|
+
async createAccessRequest(input) {
|
|
52
|
+
const res = await fetchFn(`${base}/api/v1/data-connection-requests`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: { "Content-Type": "application/json" },
|
|
55
|
+
body: JSON.stringify({
|
|
56
|
+
appAddress: input.appAddress,
|
|
57
|
+
app: input.app,
|
|
58
|
+
source: input.source,
|
|
59
|
+
scopes: input.scopes,
|
|
60
|
+
returnUrl: input.returnUrl
|
|
61
|
+
})
|
|
62
|
+
});
|
|
63
|
+
if (!res.ok) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Access request service error: ${res.status} ${res.statusText}`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
const body = await res.json();
|
|
69
|
+
const requestId = body.requestId ?? body.id;
|
|
70
|
+
if (!requestId) {
|
|
71
|
+
throw new Error("Access request service returned no requestId");
|
|
72
|
+
}
|
|
73
|
+
return {
|
|
74
|
+
requestId,
|
|
75
|
+
approvalUrl: body.approvalUrl ?? buildApprovalUrl(options.approvalBaseUrl, requestId),
|
|
76
|
+
appAddress: body.appAddress ?? input.appAddress
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
async getAccessRequestStatus(requestId) {
|
|
80
|
+
const res = await fetchFn(
|
|
81
|
+
`${base}/api/v1/data-connection-requests/${encodeURIComponent(requestId)}`,
|
|
82
|
+
{ method: "GET" }
|
|
83
|
+
);
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`Access request service error: ${res.status} ${res.statusText}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
const body = await res.json();
|
|
90
|
+
return {
|
|
91
|
+
status: normalizeStatus(body.status),
|
|
92
|
+
personalServerUrl: body.personalServerUrl,
|
|
93
|
+
grantId: body.grantId,
|
|
94
|
+
scope: body.scope
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
100
|
+
0 && (module.exports = {
|
|
101
|
+
buildApprovalUrl,
|
|
102
|
+
createDefaultAccessRequestClient
|
|
103
|
+
});
|
|
104
|
+
//# 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\";\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}\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\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 res = await fetchFn(`${base}/api/v1/data-connection-requests`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n 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 });\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 requestId?: string;\n id?: string;\n approvalUrl?: string;\n appAddress?: string;\n };\n const requestId = body.requestId ?? body.id;\n if (!requestId) {\n throw new Error(\"Access request service returned no requestId\");\n }\n return {\n requestId,\n approvalUrl:\n body.approvalUrl ??\n buildApprovalUrl(options.approvalBaseUrl, requestId),\n appAddress: body.appAddress ?? input.appAddress,\n };\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const res = await fetchFn(\n `${base}/api/v1/data-connection-requests/${encodeURIComponent(requestId)}`,\n { method: \"GET\" },\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":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8CA,MAAM,iBAAsD;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,gBAAgB,OAA0C;AACjE,SAAO,eAAe,SAAS,KAAiC,IAC3D,QACD;AACN;AAEA,SAAS,mBAAmB,KAAqB;AAC/C,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAUO,SAAS,iBACd,iBACA,WACQ;AACR,SAAO,GAAG,mBAAmB,eAAe,CAAC,6BAA6B;AAAA,IACxE;AAAA,EACF,CAAC;AACH;AASO,SAAS,iCACd,SACqB;AACrB,QAAM,UAAU,QAAQ,WAAY,WAAW;AAC/C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,mBAAmB,QAAQ,OAAO;AAE/C,SAAO;AAAA,IACL,MAAM,oBAAoB,OAA+B;AACvD,YAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,oCAAoC;AAAA,QACnE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,YAAY,MAAM;AAAA,UAClB,KAAK,MAAM;AAAA,UACX,QAAQ,MAAM;AAAA,UACd,QAAQ,MAAM;AAAA,UACd,WAAW,MAAM;AAAA,QACnB,CAAC;AAAA,MACH,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,iCAAiC,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,QAC/D;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK;AAM7B,YAAM,YAAY,KAAK,aAAa,KAAK;AACzC,UAAI,CAAC,WAAW;AACd,cAAM,IAAI,MAAM,8CAA8C;AAAA,MAChE;AACA,aAAO;AAAA,QACL;AAAA,QACA,aACE,KAAK,eACL,iBAAiB,QAAQ,iBAAiB,SAAS;AAAA,QACrD,YAAY,KAAK,cAAc,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IAEA,MAAM,uBACJ,WAC8B;AAC9B,YAAM,MAAM,MAAM;AAAA,QAChB,GAAG,IAAI,oCAAoC,mBAAmB,SAAS,CAAC;AAAA,QACxE,EAAE,QAAQ,MAAM;AAAA,MAClB;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,iCAAiC,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,QAC/D;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK;AAM7B,aAAO;AAAA,QACL,QAAQ,gBAAgB,KAAK,MAAM;AAAA,QACnC,mBAAmB,KAAK;AAAA,QACxB,SAAS,KAAK;AAAA,QACd,OAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
/** Minimal `fetch` signature so the client is testable without a global fetch. */
|
|
15
|
+
export type FetchLike = (input: string, init?: {
|
|
16
|
+
method?: string;
|
|
17
|
+
headers?: Record<string, string>;
|
|
18
|
+
body?: string;
|
|
19
|
+
}) => Promise<{
|
|
20
|
+
ok: boolean;
|
|
21
|
+
status: number;
|
|
22
|
+
statusText: string;
|
|
23
|
+
json(): Promise<unknown>;
|
|
24
|
+
text(): Promise<string>;
|
|
25
|
+
}>;
|
|
26
|
+
/** Options for {@link createDefaultAccessRequestClient}. */
|
|
27
|
+
export interface DefaultAccessRequestClientOptions {
|
|
28
|
+
/** Base URL of the Vana Account access-request API. */
|
|
29
|
+
baseUrl: string;
|
|
30
|
+
/** Base URL the user is sent to for approval. */
|
|
31
|
+
approvalBaseUrl: string;
|
|
32
|
+
/** `fetch` implementation. Defaults to the global `fetch`. */
|
|
33
|
+
fetchFn?: FetchLike;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build an approval URL for a request id, matching the documented format
|
|
37
|
+
* (`{app}/data-connection-requests/{requestId}?mode=page`).
|
|
38
|
+
*
|
|
39
|
+
* @param approvalBaseUrl - Base URL of the Vana approval app.
|
|
40
|
+
* @param requestId - The `dcr_*` request id.
|
|
41
|
+
* @returns The full approval URL.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildApprovalUrl(approvalBaseUrl: string, requestId: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Create the default {@link AccessRequestClient} for the Vana Account
|
|
46
|
+
* access-request API.
|
|
47
|
+
*
|
|
48
|
+
* @param options - Base URLs and an optional `fetch` implementation.
|
|
49
|
+
* @returns An {@link AccessRequestClient} backed by HTTP calls.
|
|
50
|
+
*/
|
|
51
|
+
export declare function createDefaultAccessRequestClient(options: DefaultAccessRequestClientOptions): AccessRequestClient;
|
|
@@ -0,0 +1,79 @@
|
|
|
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
|
+
function buildApprovalUrl(approvalBaseUrl, requestId) {
|
|
14
|
+
return `${stripTrailingSlash(approvalBaseUrl)}/data-connection-requests/${encodeURIComponent(
|
|
15
|
+
requestId
|
|
16
|
+
)}?mode=page`;
|
|
17
|
+
}
|
|
18
|
+
function createDefaultAccessRequestClient(options) {
|
|
19
|
+
const fetchFn = options.fetchFn ?? globalThis.fetch;
|
|
20
|
+
if (!fetchFn) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"No fetch implementation available. Pass `fetchFn` to createDefaultAccessRequestClient."
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
const base = stripTrailingSlash(options.baseUrl);
|
|
26
|
+
return {
|
|
27
|
+
async createAccessRequest(input) {
|
|
28
|
+
const res = await fetchFn(`${base}/api/v1/data-connection-requests`, {
|
|
29
|
+
method: "POST",
|
|
30
|
+
headers: { "Content-Type": "application/json" },
|
|
31
|
+
body: JSON.stringify({
|
|
32
|
+
appAddress: input.appAddress,
|
|
33
|
+
app: input.app,
|
|
34
|
+
source: input.source,
|
|
35
|
+
scopes: input.scopes,
|
|
36
|
+
returnUrl: input.returnUrl
|
|
37
|
+
})
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Access request service error: ${res.status} ${res.statusText}`
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
const body = await res.json();
|
|
45
|
+
const requestId = body.requestId ?? body.id;
|
|
46
|
+
if (!requestId) {
|
|
47
|
+
throw new Error("Access request service returned no requestId");
|
|
48
|
+
}
|
|
49
|
+
return {
|
|
50
|
+
requestId,
|
|
51
|
+
approvalUrl: body.approvalUrl ?? buildApprovalUrl(options.approvalBaseUrl, requestId),
|
|
52
|
+
appAddress: body.appAddress ?? input.appAddress
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
async getAccessRequestStatus(requestId) {
|
|
56
|
+
const res = await fetchFn(
|
|
57
|
+
`${base}/api/v1/data-connection-requests/${encodeURIComponent(requestId)}`,
|
|
58
|
+
{ method: "GET" }
|
|
59
|
+
);
|
|
60
|
+
if (!res.ok) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
`Access request service error: ${res.status} ${res.statusText}`
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const body = await res.json();
|
|
66
|
+
return {
|
|
67
|
+
status: normalizeStatus(body.status),
|
|
68
|
+
personalServerUrl: body.personalServerUrl,
|
|
69
|
+
grantId: body.grantId,
|
|
70
|
+
scope: body.scope
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
export {
|
|
76
|
+
buildApprovalUrl,
|
|
77
|
+
createDefaultAccessRequestClient
|
|
78
|
+
};
|
|
79
|
+
//# 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\";\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}\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\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 res = await fetchFn(`${base}/api/v1/data-connection-requests`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n 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 });\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 requestId?: string;\n id?: string;\n approvalUrl?: string;\n appAddress?: string;\n };\n const requestId = body.requestId ?? body.id;\n if (!requestId) {\n throw new Error(\"Access request service returned no requestId\");\n }\n return {\n requestId,\n approvalUrl:\n body.approvalUrl ??\n buildApprovalUrl(options.approvalBaseUrl, requestId),\n appAddress: body.appAddress ?? input.appAddress,\n };\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const res = await fetchFn(\n `${base}/api/v1/data-connection-requests/${encodeURIComponent(requestId)}`,\n { method: \"GET\" },\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":"AA8CA,MAAM,iBAAsD;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,SAAS,gBAAgB,OAA0C;AACjE,SAAO,eAAe,SAAS,KAAiC,IAC3D,QACD;AACN;AAEA,SAAS,mBAAmB,KAAqB;AAC/C,SAAO,IAAI,QAAQ,QAAQ,EAAE;AAC/B;AAUO,SAAS,iBACd,iBACA,WACQ;AACR,SAAO,GAAG,mBAAmB,eAAe,CAAC,6BAA6B;AAAA,IACxE;AAAA,EACF,CAAC;AACH;AASO,SAAS,iCACd,SACqB;AACrB,QAAM,UAAU,QAAQ,WAAY,WAAW;AAC/C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,QAAM,OAAO,mBAAmB,QAAQ,OAAO;AAE/C,SAAO;AAAA,IACL,MAAM,oBAAoB,OAA+B;AACvD,YAAM,MAAM,MAAM,QAAQ,GAAG,IAAI,oCAAoC;AAAA,QACnE,QAAQ;AAAA,QACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB,YAAY,MAAM;AAAA,UAClB,KAAK,MAAM;AAAA,UACX,QAAQ,MAAM;AAAA,UACd,QAAQ,MAAM;AAAA,UACd,WAAW,MAAM;AAAA,QACnB,CAAC;AAAA,MACH,CAAC;AACD,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,iCAAiC,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,QAC/D;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK;AAM7B,YAAM,YAAY,KAAK,aAAa,KAAK;AACzC,UAAI,CAAC,WAAW;AACd,cAAM,IAAI,MAAM,8CAA8C;AAAA,MAChE;AACA,aAAO;AAAA,QACL;AAAA,QACA,aACE,KAAK,eACL,iBAAiB,QAAQ,iBAAiB,SAAS;AAAA,QACrD,YAAY,KAAK,cAAc,MAAM;AAAA,MACvC;AAAA,IACF;AAAA,IAEA,MAAM,uBACJ,WAC8B;AAC9B,YAAM,MAAM,MAAM;AAAA,QAChB,GAAG,IAAI,oCAAoC,mBAAmB,SAAS,CAAC;AAAA,QACxE,EAAE,QAAQ,MAAM;AAAA,MAClB;AACA,UAAI,CAAC,IAAI,IAAI;AACX,cAAM,IAAI;AAAA,UACR,iCAAiC,IAAI,MAAM,IAAI,IAAI,UAAU;AAAA,QAC/D;AAAA,MACF;AACA,YAAM,OAAQ,MAAM,IAAI,KAAK;AAM7B,aAAO;AAAA,QACL,QAAQ,gBAAgB,KAAK,MAAM;AAAA,QACnC,mBAAmB,KAAK;AAAA,QACxB,SAAS,KAAK;AAAA,QACd,OAAO,KAAK;AAAA,MACd;AAAA,IACF;AAAA,EACF;AACF;","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
|