@opendatalabs/vana-sdk 3.13.4 → 3.14.0
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/dist/account/personal-server-lite-owner-binding.d.ts +1 -1
- package/dist/account/personal-server-registration.d.ts +1 -1
- package/dist/auth/errors.d.ts +1 -1
- package/dist/auth/oauth-client.d.ts +1 -1
- package/dist/browser.d.ts +1 -1
- package/dist/chains/index.d.ts +2 -2
- package/dist/chains.browser.d.ts +2 -2
- package/dist/chains.d.ts +2 -2
- package/dist/chains.node.d.ts +2 -2
- package/dist/config/contracts.config.cjs +1 -1
- package/dist/config/contracts.config.cjs.map +1 -1
- package/dist/config/contracts.config.js +1 -1
- package/dist/config/contracts.config.js.map +1 -1
- package/dist/contracts/contractController.d.ts +4 -4
- package/dist/core/client.d.ts +1 -1
- package/dist/crypto/ecies/base.d.ts +1 -1
- package/dist/crypto/ecies/browser.d.ts +1 -1
- package/dist/crypto/ecies/index.d.ts +2 -2
- package/dist/crypto/ecies/node.d.ts +1 -1
- package/dist/crypto/services/WalletKeyEncryptionService.d.ts +1 -1
- package/dist/direct/access-request-client.cjs +3 -1
- package/dist/direct/access-request-client.cjs.map +1 -1
- package/dist/direct/access-request-client.d.ts +2 -2
- package/dist/direct/access-request-client.js +3 -1
- package/dist/direct/access-request-client.js.map +1 -1
- package/dist/direct/connect-flow.d.ts +1 -1
- package/dist/direct/controller.cjs +72 -32
- package/dist/direct/controller.cjs.map +1 -1
- package/dist/direct/controller.d.ts +48 -10
- package/dist/direct/controller.js +77 -33
- package/dist/direct/controller.js.map +1 -1
- package/dist/direct/endpoints.d.ts +1 -1
- package/dist/direct/errors.cjs +11 -2
- package/dist/direct/errors.cjs.map +1 -1
- package/dist/direct/errors.d.ts +14 -1
- package/dist/direct/errors.js +9 -1
- package/dist/direct/errors.js.map +1 -1
- package/dist/direct/escrow-payment.cjs +147 -12
- package/dist/direct/escrow-payment.cjs.map +1 -1
- package/dist/direct/escrow-payment.d.ts +65 -12
- package/dist/direct/escrow-payment.js +143 -12
- package/dist/direct/escrow-payment.js.map +1 -1
- package/dist/direct/personal-server-read.cjs +164 -23
- package/dist/direct/personal-server-read.cjs.map +1 -1
- package/dist/direct/personal-server-read.d.ts +28 -18
- package/dist/direct/personal-server-read.js +167 -25
- package/dist/direct/personal-server-read.js.map +1 -1
- package/dist/direct/types.cjs.map +1 -1
- package/dist/direct/types.d.ts +81 -21
- package/dist/direct/types.js.map +1 -1
- package/dist/direct/use-direct-vana-connect.d.ts +1 -1
- package/dist/generated/abi/index.d.ts +25 -25
- package/dist/generated/addresses.cjs +6 -6
- package/dist/generated/addresses.cjs.map +1 -1
- package/dist/generated/addresses.d.ts +6 -6
- package/dist/generated/addresses.js +6 -6
- package/dist/generated/addresses.js.map +1 -1
- package/dist/index.browser.d.ts +44 -44
- package/dist/index.browser.js +6 -6
- package/dist/index.browser.js.map +2 -2
- package/dist/index.node.cjs +498 -6
- package/dist/index.node.cjs.map +4 -4
- package/dist/index.node.d.ts +48 -45
- package/dist/index.node.js +486 -6
- package/dist/index.node.js.map +4 -4
- package/dist/node.d.ts +1 -1
- package/dist/platform/browser-only.d.ts +3 -3
- package/dist/platform/browser-safe.d.ts +3 -3
- package/dist/platform/browser.d.ts +1 -1
- package/dist/platform/index.d.ts +5 -5
- package/dist/platform/node.d.ts +1 -1
- package/dist/platform/ports/openpgp-port.d.ts +1 -1
- package/dist/platform/utils.d.ts +1 -1
- package/dist/platform.browser.d.ts +4 -4
- package/dist/platform.d.ts +5 -5
- package/dist/platform.node.d.ts +5 -5
- package/dist/protocol/data-point-status.d.ts +1 -1
- package/dist/protocol/eip712.cjs.map +1 -1
- package/dist/protocol/eip712.js.map +1 -1
- package/dist/protocol/escrow-deposit.d.ts +1 -1
- package/dist/protocol/escrow.cjs.map +1 -1
- package/dist/protocol/escrow.d.ts +11 -4
- package/dist/protocol/escrow.js.map +1 -1
- package/dist/protocol/fee-registry.d.ts +1 -1
- package/dist/protocol/gateway.cjs.map +1 -1
- package/dist/protocol/gateway.js.map +1 -1
- package/dist/protocol/grants.d.ts +1 -1
- package/dist/protocol/personal-server-data.d.ts +2 -2
- package/dist/protocol/personal-server-registration.d.ts +2 -2
- package/dist/react.d.ts +3 -3
- package/dist/server.cjs +12 -0
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.ts +8 -8
- package/dist/server.js +12 -0
- package/dist/server.js.map +1 -1
- package/dist/session-relay/client.d.ts +1 -1
- package/dist/session-relay/endpoints.d.ts +1 -1
- package/dist/session-relay/errors.d.ts +1 -1
- package/dist/session-relay/index.d.ts +5 -5
- package/dist/session-relay/signing.d.ts +1 -1
- package/dist/session-relay/types.d.ts +1 -1
- package/dist/session-relay.d.ts +5 -5
- package/dist/storage/default.d.ts +2 -2
- package/dist/storage/index.d.ts +16 -16
- package/dist/storage/manager.d.ts +1 -1
- package/dist/storage/providers/callback-storage.d.ts +2 -2
- package/dist/storage/providers/dropbox.d.ts +1 -1
- package/dist/storage/providers/google-drive.d.ts +1 -1
- package/dist/storage/providers/ipfs.d.ts +1 -1
- package/dist/storage/providers/pinata.d.ts +1 -1
- package/dist/storage/providers/r2.d.ts +1 -1
- package/dist/storage/providers/vana-storage.d.ts +3 -3
- package/dist/types/config.d.ts +1 -1
- package/dist/types/index.d.ts +6 -6
- package/dist/types.d.ts +1 -1
- package/package.json +13 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches a {@link DirectPaymentReceipt} under `payment`\n * when the Personal Server returns one. After a successful read, the\n * controller acknowledges the DCR so Vana Web can close/redirect the approval\n * tab.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n// A DCR is read-ready only while the grant exists and the Personal Server is\n// still serving it: `approved` (durable PS) or `ready_for_read` (browser PS).\n// `completed` is terminal — the app already read and acknowledged, and the\n// browser PS may be gone — so it is deliberately excluded here.\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n try {\n await accessRequestClient.acknowledgeRead?.(input.requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport {\n AccessNotApprovedError,\n DirectConfigError,\n ScopeNotApprovedError,\n} from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectPaymentResponseMetadata,\n DirectServiceEndpoints,\n MultiScopeDataResult,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope?, scopes? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches shape-validated but unauthenticated\n * {@link DirectPaymentResponseMetadata} under `payment` when the Personal\n * Server returns it. After a successful read, the controller acknowledges\n * the DCR so Vana Web can close/redirect the approval tab.\n *\n * A request can approve several scopes. This reads **one** of them — `scope`\n * when given, otherwise the first approved scope. Use\n * {@link DirectDataController.readAllApprovedData} to read them all.\n *\n * Acknowledging moves the DCR to `completed`, which is terminal and no longer\n * read-ready. To read several scopes with your own loop, pass\n * `acknowledge: false` on every call but the last.\n *\n * @param input - The `dcr_*` request id, the optional `scope` to read, and an\n * optional `acknowledge` flag (default `true`).\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link ScopeNotApprovedError} if `scope` is not an approved scope.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>>;\n\n /**\n * Read every scope the user approved on a request.\n *\n * @remarks\n * Reads the scopes in approval order, then acknowledges the DCR **once**,\n * after the last read — acknowledging earlier would move the request to\n * `completed` and make the remaining scopes unreadable.\n *\n * Each scope is a separate Personal Server read that settles its own\n * `data_access` fee from escrow, so reading N scopes costs N times a\n * single-scope read. The one-off registration fee is charged per grant, not\n * per scope.\n *\n * A scope that fails does not abort the rest: successes land in `results` and\n * failures in `errors`, because the fees for earlier scopes are already spent.\n * If any scope fails the request is left unacknowledged, so the scopes that\n * failed stay retryable — read them with `readApprovedData({ scope })` and\n * acknowledge on the last one.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ results, errors }`, both keyed by scope.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n */\n readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n// A DCR is read-ready only while the grant exists and the Personal Server is\n// still serving it: `approved` (durable PS) or `ready_for_read` (browser PS).\n// `completed` is terminal — the app already read and acknowledged, and the\n// browser PS may be gone — so it is deliberately excluded here.\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scope = resolveRequestedScope(status, input.scope);\n\n const result = await readScope<T>(status, scope);\n if (input.acknowledge !== false) {\n await acknowledgeQuietly(input.requestId);\n }\n return result;\n },\n\n async readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scopes = approvedScopes(status);\n\n const results: Record<string, ApprovedDataResult<T>> = {};\n const errors: Record<string, Error> = {};\n // Sequential, not parallel: each read settles its own escrow payment and\n // the default nonce source is process-local, so concurrent reads would\n // race on the payment nonce.\n for (const scope of scopes) {\n try {\n results[scope] = await readScope<T>(status, scope);\n } catch (error) {\n errors[scope] =\n error instanceof Error ? error : new Error(String(error));\n }\n }\n\n // Acknowledge only after the last read, and only if every scope read —\n // acking moves the DCR to `completed`, which is terminal and no longer\n // read-ready, so acking on a partial failure would make the scope that\n // failed impossible to retry.\n if (Object.keys(errors).length === 0) {\n await acknowledgeQuietly(input.requestId);\n }\n\n return { results, errors };\n },\n };\n\n async function requireReadReady(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const status = await accessRequestClient.getAccessRequestStatus(requestId);\n // `scope` and `scopes` are both optional on the public status type, and a\n // client may return either one — require at least one approved scope rather\n // than the singular field specifically.\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n approvedScopes(status).length === 0\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: approvedScopes(status).length > 0,\n },\n );\n }\n return status;\n }\n\n /** Approved scopes in approval order, falling back to the single `scope`. */\n function approvedScopes(status: AccessRequestStatus): string[] {\n if (status.scopes && status.scopes.length > 0) return status.scopes;\n return status.scope ? [status.scope] : [];\n }\n\n /**\n * Resolve which scope to read. Rejects an unapproved scope up front so it\n * never reaches the Personal Server and never settles a fee.\n */\n function resolveRequestedScope(\n status: AccessRequestStatus,\n requested?: string,\n ): string {\n const scopes = approvedScopes(status);\n if (requested === undefined) return scopes[0];\n if (!scopes.includes(requested)) {\n throw new ScopeNotApprovedError(\n `Scope \"${requested}\" is not approved on this request`,\n { requestedScope: requested, approvedScopes: scopes },\n );\n }\n return requested;\n }\n\n async function readScope<T>(\n status: AccessRequestStatus,\n scope: string,\n ): Promise<ApprovedDataResult<T>> {\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl as string,\n scope,\n grantId: status.grantId as string,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n return { scope, data: result.data as T, payment: result.payment };\n }\n\n async function acknowledgeQuietly(requestId: string): Promise<void> {\n try {\n await accessRequestClient.acknowledgeRead?.(requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -20,10 +20,10 @@
|
|
|
20
20
|
* @category Direct
|
|
21
21
|
* @module direct/controller
|
|
22
22
|
*/
|
|
23
|
-
import { type FetchLike } from "./access-request-client";
|
|
24
|
-
import { type EscrowPaymentConfig } from "./escrow-payment";
|
|
25
|
-
import { type PersonalServerFetch, type PersonalServerTransportRetryOptions } from "./personal-server-read";
|
|
26
|
-
import type { AccessRequest, AccessRequestClient, AccessRequestStatus, ApprovedDataResult, AppIdentity, DirectAppConfig, DirectEnv, DirectNetwork, DirectServiceEndpoints } from "./types";
|
|
23
|
+
import { type FetchLike } from "./access-request-client.js";
|
|
24
|
+
import { type EscrowPaymentConfig } from "./escrow-payment.js";
|
|
25
|
+
import { type PersonalServerFetch, type PersonalServerTransportRetryOptions } from "./personal-server-read.js";
|
|
26
|
+
import type { AccessRequest, AccessRequestClient, AccessRequestStatus, ApprovedDataResult, AppIdentity, DirectAppConfig, DirectEnv, DirectNetwork, DirectServiceEndpoints, MultiScopeDataResult } from "./types.js";
|
|
27
27
|
/** Configuration for {@link createDirectDataController}. */
|
|
28
28
|
export interface DirectDataControllerConfig {
|
|
29
29
|
/** Target environment. Defaults to `"production"`. */
|
|
@@ -138,7 +138,7 @@ export interface DirectDataController {
|
|
|
138
138
|
* Fetch the current status of an access request.
|
|
139
139
|
*
|
|
140
140
|
* @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.
|
|
141
|
-
* @returns `{ status, personalServerUrl?, grantId?, scope? }`.
|
|
141
|
+
* @returns `{ status, personalServerUrl?, grantId?, scope?, scopes? }`.
|
|
142
142
|
*/
|
|
143
143
|
getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;
|
|
144
144
|
/**
|
|
@@ -148,19 +148,57 @@ export interface DirectDataController {
|
|
|
148
148
|
* Resolves the request to its grant + Personal Server and performs a Web3Signed
|
|
149
149
|
* read. Hides the `402 Payment Required` flow by default: if a read needs
|
|
150
150
|
* payment, it signs the Personal Server's payment challenge, retries with
|
|
151
|
-
* `X-PAYMENT`, and attaches
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* tab.
|
|
151
|
+
* `X-PAYMENT`, and attaches shape-validated but unauthenticated
|
|
152
|
+
* {@link DirectPaymentResponseMetadata} under `payment` when the Personal
|
|
153
|
+
* Server returns it. After a successful read, the controller acknowledges
|
|
154
|
+
* the DCR so Vana Web can close/redirect the approval tab.
|
|
155
155
|
*
|
|
156
|
-
*
|
|
156
|
+
* A request can approve several scopes. This reads **one** of them — `scope`
|
|
157
|
+
* when given, otherwise the first approved scope. Use
|
|
158
|
+
* {@link DirectDataController.readAllApprovedData} to read them all.
|
|
159
|
+
*
|
|
160
|
+
* Acknowledging moves the DCR to `completed`, which is terminal and no longer
|
|
161
|
+
* read-ready. To read several scopes with your own loop, pass
|
|
162
|
+
* `acknowledge: false` on every call but the last.
|
|
163
|
+
*
|
|
164
|
+
* @param input - The `dcr_*` request id, the optional `scope` to read, and an
|
|
165
|
+
* optional `acknowledge` flag (default `true`).
|
|
157
166
|
* @returns `{ scope, data, payment? }`.
|
|
158
167
|
* @throws {@link AccessNotApprovedError} if the request is not approved.
|
|
168
|
+
* @throws {@link ScopeNotApprovedError} if `scope` is not an approved scope.
|
|
159
169
|
* @throws {@link PaymentRequiredError} if payment is required but unsettled.
|
|
160
170
|
*/
|
|
161
171
|
readApprovedData<T = unknown>(input: {
|
|
162
172
|
requestId: string;
|
|
173
|
+
scope?: string;
|
|
174
|
+
acknowledge?: boolean;
|
|
163
175
|
}): Promise<ApprovedDataResult<T>>;
|
|
176
|
+
/**
|
|
177
|
+
* Read every scope the user approved on a request.
|
|
178
|
+
*
|
|
179
|
+
* @remarks
|
|
180
|
+
* Reads the scopes in approval order, then acknowledges the DCR **once**,
|
|
181
|
+
* after the last read — acknowledging earlier would move the request to
|
|
182
|
+
* `completed` and make the remaining scopes unreadable.
|
|
183
|
+
*
|
|
184
|
+
* Each scope is a separate Personal Server read that settles its own
|
|
185
|
+
* `data_access` fee from escrow, so reading N scopes costs N times a
|
|
186
|
+
* single-scope read. The one-off registration fee is charged per grant, not
|
|
187
|
+
* per scope.
|
|
188
|
+
*
|
|
189
|
+
* A scope that fails does not abort the rest: successes land in `results` and
|
|
190
|
+
* failures in `errors`, because the fees for earlier scopes are already spent.
|
|
191
|
+
* If any scope fails the request is left unacknowledged, so the scopes that
|
|
192
|
+
* failed stay retryable — read them with `readApprovedData({ scope })` and
|
|
193
|
+
* acknowledge on the last one.
|
|
194
|
+
*
|
|
195
|
+
* @param input - The `dcr_*` request id to read.
|
|
196
|
+
* @returns `{ results, errors }`, both keyed by scope.
|
|
197
|
+
* @throws {@link AccessNotApprovedError} if the request is not approved.
|
|
198
|
+
*/
|
|
199
|
+
readAllApprovedData<T = unknown>(input: {
|
|
200
|
+
requestId: string;
|
|
201
|
+
}): Promise<MultiScopeDataResult<T>>;
|
|
164
202
|
}
|
|
165
203
|
/**
|
|
166
204
|
* Create a {@link DirectDataController}.
|
|
@@ -10,7 +10,11 @@ import {
|
|
|
10
10
|
getDirectEndpoints,
|
|
11
11
|
getDirectNetworkChainId
|
|
12
12
|
} from "./endpoints.js";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
AccessNotApprovedError,
|
|
15
|
+
DirectConfigError,
|
|
16
|
+
ScopeNotApprovedError
|
|
17
|
+
} from "./errors.js";
|
|
14
18
|
import {
|
|
15
19
|
readPersonalServerData
|
|
16
20
|
} from "./personal-server-read.js";
|
|
@@ -93,42 +97,82 @@ function createDirectDataController(config) {
|
|
|
93
97
|
return accessRequestClient.getAccessRequestStatus(requestId);
|
|
94
98
|
},
|
|
95
99
|
async readApprovedData(input) {
|
|
96
|
-
const status = await
|
|
97
|
-
|
|
98
|
-
);
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
"Request is not approved or is missing grantId/scope/personalServerUrl",
|
|
102
|
-
{
|
|
103
|
-
requestId: input.requestId,
|
|
104
|
-
status: status.status,
|
|
105
|
-
hasPersonalServerUrl: Boolean(status.personalServerUrl),
|
|
106
|
-
hasGrantId: Boolean(status.grantId),
|
|
107
|
-
hasScope: Boolean(status.scope)
|
|
108
|
-
}
|
|
109
|
-
);
|
|
100
|
+
const status = await requireReadReady(input.requestId);
|
|
101
|
+
const scope = resolveRequestedScope(status, input.scope);
|
|
102
|
+
const result = await readScope(status, scope);
|
|
103
|
+
if (input.acknowledge !== false) {
|
|
104
|
+
await acknowledgeQuietly(input.requestId);
|
|
110
105
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
106
|
+
return result;
|
|
107
|
+
},
|
|
108
|
+
async readAllApprovedData(input) {
|
|
109
|
+
const status = await requireReadReady(input.requestId);
|
|
110
|
+
const scopes = approvedScopes(status);
|
|
111
|
+
const results = {};
|
|
112
|
+
const errors = {};
|
|
113
|
+
for (const scope of scopes) {
|
|
114
|
+
try {
|
|
115
|
+
results[scope] = await readScope(status, scope);
|
|
116
|
+
} catch (error) {
|
|
117
|
+
errors[scope] = error instanceof Error ? error : new Error(String(error));
|
|
118
|
+
}
|
|
124
119
|
}
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
};
|
|
120
|
+
if (Object.keys(errors).length === 0) {
|
|
121
|
+
await acknowledgeQuietly(input.requestId);
|
|
122
|
+
}
|
|
123
|
+
return { results, errors };
|
|
130
124
|
}
|
|
131
125
|
};
|
|
126
|
+
async function requireReadReady(requestId) {
|
|
127
|
+
const status = await accessRequestClient.getAccessRequestStatus(requestId);
|
|
128
|
+
if (!isReadReadyStatus(status.status) || !status.personalServerUrl || !status.grantId || approvedScopes(status).length === 0) {
|
|
129
|
+
throw new AccessNotApprovedError(
|
|
130
|
+
"Request is not approved or is missing grantId/scope/personalServerUrl",
|
|
131
|
+
{
|
|
132
|
+
requestId,
|
|
133
|
+
status: status.status,
|
|
134
|
+
hasPersonalServerUrl: Boolean(status.personalServerUrl),
|
|
135
|
+
hasGrantId: Boolean(status.grantId),
|
|
136
|
+
hasScope: approvedScopes(status).length > 0
|
|
137
|
+
}
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return status;
|
|
141
|
+
}
|
|
142
|
+
function approvedScopes(status) {
|
|
143
|
+
if (status.scopes && status.scopes.length > 0) return status.scopes;
|
|
144
|
+
return status.scope ? [status.scope] : [];
|
|
145
|
+
}
|
|
146
|
+
function resolveRequestedScope(status, requested) {
|
|
147
|
+
const scopes = approvedScopes(status);
|
|
148
|
+
if (requested === void 0) return scopes[0];
|
|
149
|
+
if (!scopes.includes(requested)) {
|
|
150
|
+
throw new ScopeNotApprovedError(
|
|
151
|
+
`Scope "${requested}" is not approved on this request`,
|
|
152
|
+
{ requestedScope: requested, approvedScopes: scopes }
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
return requested;
|
|
156
|
+
}
|
|
157
|
+
async function readScope(status, scope) {
|
|
158
|
+
const result = await readPersonalServerData({
|
|
159
|
+
personalServerUrl: status.personalServerUrl,
|
|
160
|
+
scope,
|
|
161
|
+
grantId: status.grantId,
|
|
162
|
+
payerAddress: account.address,
|
|
163
|
+
signMessage,
|
|
164
|
+
escrow,
|
|
165
|
+
fetchFn: config.personalServerFetch,
|
|
166
|
+
transportRetry: config.personalServerTransportRetry
|
|
167
|
+
});
|
|
168
|
+
return { scope, data: result.data, payment: result.payment };
|
|
169
|
+
}
|
|
170
|
+
async function acknowledgeQuietly(requestId) {
|
|
171
|
+
try {
|
|
172
|
+
await accessRequestClient.acknowledgeRead?.(requestId);
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
132
176
|
}
|
|
133
177
|
export {
|
|
134
178
|
createDirectDataController
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport { AccessNotApprovedError, DirectConfigError } from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectServiceEndpoints,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches a {@link DirectPaymentReceipt} under `payment`\n * when the Personal Server returns one. After a successful read, the\n * controller acknowledges the DCR so Vana Web can close/redirect the approval\n * tab.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n// A DCR is read-ready only while the grant exists and the Personal Server is\n// still serving it: `approved` (durable PS) or `ready_for_read` (browser PS).\n// `completed` is terminal — the app already read and acknowledged, and the\n// browser PS may be gone — so it is deliberately excluded here.\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<ApprovedDataResult<T>> {\n const status = await accessRequestClient.getAccessRequestStatus(\n input.requestId,\n );\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n !status.scope\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId: input.requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: Boolean(status.scope),\n },\n );\n }\n\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl,\n scope: status.scope,\n grantId: status.grantId,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n try {\n await accessRequestClient.acknowledgeRead?.(input.requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n\n return {\n scope: status.scope,\n data: result.data as T,\n payment: result.payment,\n };\n },\n };\n}\n"],"mappings":"AAuBA,SAAS,2BAA2B;AAGpC,SAAS,kBAAkB;AAC3B,SAAS,iCAAiC;AAC1C,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,OAEK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB,yBAAyB;AAK1D;AAAA,EACE;AAAA,OAGK;AAiKP,SAAS,gBAAgB,OAA6B;AACpD,SAAO,sBAAsB,KAAK,KAAK;AACzC;AAMA,SAAS,kBAAkB,QAA2C;AACpE,SAAO,WAAW,cAAc,WAAW;AAC7C;AASO,SAAS,2BACd,QACsB;AAGtB,QAAM,aAAa,OAAO,iBAAiB,OAAO;AAClD,MAAI,CAAC,cAAc,CAAC,gBAAgB,UAAU,GAAG;AAC/C,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,OAAO,UAAU,OAAO,OAAO,WAAW,GAAG;AAChD,UAAM,IAAI,kBAAkB,gCAAgC;AAAA,EAC9D;AAEA,aAAW,SAAS,OAAO,QAAQ;AACjC,eAAW,KAAK;AAAA,EAClB;AAEA,QAAM,MAAiB,OAAO,OAAO;AACrC,QAAM,UAAyB,OAAO,WAAW,wBAAwB,GAAG;AAC5E,QAAM,mBAAmB,mBAAmB,GAAG;AAC/C,QAAM,UAAU,OAAO,WAAW,WAAW,wBAAwB,OAAO;AAC5E,QAAM,YAAoC;AAAA,IACxC,GAAG;AAAA,IACH,GAAG,OAAO;AAAA,IACV;AAAA,EACF;AAEA,QAAM,UAAU,oBAAoB,UAAiB;AACrD,QAAM,cAAgC,CAAC,YACrC,QAAQ,YAAY,EAAE,QAAQ,CAAC;AAGjC,QAAM,gBAAgB,QAAQ;AAC9B,QAAM,sBACJ,OAAO,uBACP,iCAAiC;AAAA,IAC/B,SAAS,UAAU;AAAA,IACnB,iBAAiB,UAAU;AAAA,IAC3B,SAAS,OAAO;AAAA,IAChB,YAAY,QAAQ;AAAA,IACpB;AAAA,EACF,CAAC;AAIH,QAAM,gBAAgB,OAAO,QAAQ,WAAW;AAChD,QAAM,wBACJ,UAAU,sBAAsB,UAC9B,aACF,KAAK;AACP,MAAI,CAAC,OAAO,QAAQ,kBAAkB,CAAC,uBAAuB;AAC5D,UAAM,IAAI;AAAA,MACR,sEAAsE,aAAa;AAAA,IAErF;AAAA,EACF;AACA,QAAM,SAA8B;AAAA,IAClC,QACE,OAAO,QAAQ,UACf,0BAA0B,UAAU,gBAAgB;AAAA,IACtD,gBACE,OAAO,QAAQ,kBAAmB;AAAA,IACpC,SAAS;AAAA,IACT,aAAa,OAAO,QAAQ;AAAA,IAC5B;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY,QAAQ;AAAA,IAEpB,gBAAwB;AACtB,aAAO,QAAQ;AAAA,IACjB;AAAA,IAEA,iBAA8B;AAC5B,aAAO;AAAA,QACL,IAAI,OAAO,IAAI;AAAA,QACf,MAAM,OAAO,IAAI;AAAA,QACjB,aAAa,OAAO,IAAI;AAAA,QACxB,SAAS,QAAQ;AAAA,MACnB;AAAA,IACF;AAAA,IAEA,MAAM,oBAAoB,OAA+B;AACvD,aAAO,oBAAoB,oBAAoB;AAAA,QAC7C,YAAY,QAAQ;AAAA,QACpB,KAAK,OAAO;AAAA,QACZ,QAAQ,OAAO;AAAA,QACf,QAAQ,OAAO;AAAA,QACf,WAAW,MAAM;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IAEA,MAAM,uBACJ,WAC8B;AAC9B,aAAO,oBAAoB,uBAAuB,SAAS;AAAA,IAC7D;AAAA,IAEA,MAAM,iBAA8B,OAED;AACjC,YAAM,SAAS,MAAM,oBAAoB;AAAA,QACvC,MAAM;AAAA,MACR;AACA,UACE,CAAC,kBAAkB,OAAO,MAAM,KAChC,CAAC,OAAO,qBACR,CAAC,OAAO,WACR,CAAC,OAAO,OACR;AACA,cAAM,IAAI;AAAA,UACR;AAAA,UACA;AAAA,YACE,WAAW,MAAM;AAAA,YACjB,QAAQ,OAAO;AAAA,YACf,sBAAsB,QAAQ,OAAO,iBAAiB;AAAA,YACtD,YAAY,QAAQ,OAAO,OAAO;AAAA,YAClC,UAAU,QAAQ,OAAO,KAAK;AAAA,UAChC;AAAA,QACF;AAAA,MACF;AAEA,YAAM,SAAS,MAAM,uBAAuB;AAAA,QAC1C,mBAAmB,OAAO;AAAA,QAC1B,OAAO,OAAO;AAAA,QACd,SAAS,OAAO;AAAA,QAChB,cAAc,QAAQ;AAAA,QACtB;AAAA,QACA;AAAA,QACA,SAAS,OAAO;AAAA,QAChB,gBAAgB,OAAO;AAAA,MACzB,CAAC;AACD,UAAI;AACF,cAAM,oBAAoB,kBAAkB,MAAM,SAAS;AAAA,MAC7D,QAAQ;AAAA,MAER;AAEA,aAAO;AAAA,QACL,OAAO,OAAO;AAAA,QACd,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,MAClB;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/direct/controller.ts"],"sourcesContent":["/**\n * Direct Data Controller — the server-side facade for the two-tab Data\n * Portability flow.\n *\n * @remarks\n * One controller owns an app's private key, source, scopes, app identity, and\n * payment flow. It exposes the three methods the builder guide documents:\n *\n * - {@link DirectDataController.createAccessRequest} — start an approval request.\n * - {@link DirectDataController.getAccessRequestStatus} — poll while the Vana tab is open.\n * - {@link DirectDataController.readApprovedData} — read from the Personal Server,\n * handling 402 Payment Required.\n *\n * Access requests are created through the Vana Account access-request API; the\n * Personal Server read uses Web3Signed auth; and payment uses the DPv2 escrow\n * surface (`protocol/escrow`) — when a read returns `402`, the controller signs\n * a `GenericPayment` with the app key, settles it through the escrow gateway,\n * and retries.\n *\n * @category Direct\n * @module direct/controller\n */\n\nimport { privateKeyToAccount } from \"viem/accounts\";\nimport type { Hex } from \"viem\";\nimport type { Web3SignedSignFn } from \"../auth/web3-signed-builder\";\nimport { parseScope } from \"../protocol/scopes\";\nimport { createEscrowGatewayClient } from \"../protocol/escrow\";\nimport { CONTRACTS } from \"../generated/addresses\";\nimport {\n createDefaultAccessRequestClient,\n type FetchLike,\n} from \"./access-request-client\";\nimport {\n getDirectDefaultNetwork,\n getDirectEndpoints,\n getDirectNetworkChainId,\n} from \"./endpoints\";\nimport {\n AccessNotApprovedError,\n DirectConfigError,\n ScopeNotApprovedError,\n} from \"./errors\";\nimport {\n type EscrowPaymentConfig,\n type SignTypedDataFn,\n} from \"./escrow-payment\";\nimport {\n readPersonalServerData,\n type PersonalServerFetch,\n type PersonalServerTransportRetryOptions,\n} from \"./personal-server-read\";\nimport type {\n AccessRequest,\n AccessRequestClient,\n AccessRequestStatus,\n AccessRequestStatusValue,\n ApprovedDataResult,\n AppIdentity,\n DirectAppConfig,\n DirectEnv,\n DirectNetwork,\n DirectPaymentResponseMetadata,\n DirectServiceEndpoints,\n MultiScopeDataResult,\n} from \"./types\";\n\n/** Configuration for {@link createDirectDataController}. */\nexport interface DirectDataControllerConfig {\n /** Target environment. Defaults to `\"production\"`. */\n env?: DirectEnv;\n /**\n * Target Vana network for chain-aware defaults. Defaults to the selected\n * environment's historical network (`mainnet` for production, `moksha` for\n * dev). Use `network: \"moksha\"` with the default production env for\n * production app/API URLs on testnet.\n */\n network?: DirectNetwork;\n /**\n * The app private key (`0x`-prefixed, 32 bytes). Server-side only — this key\n * is the app's on-chain identity and is never exposed to the browser.\n */\n appPrivateKey?: string;\n /**\n * @deprecated Use {@link DirectDataControllerConfig.appPrivateKey}. Accepted as\n * a backwards-compatible alias; if both are set, `appPrivateKey` wins.\n */\n builderPrivateKey?: string;\n /** App identity advertised during approval. */\n app: DirectAppConfig;\n /** Data source key (e.g. `\"icloud_notes\"`). */\n source: string;\n /** Scopes to request (e.g. `[\"icloud_notes.notes\"]`). At least one required. */\n scopes: string[];\n /**\n * Override the resolved service endpoints (partial). Useful for pointing at a\n * non-standard deployment.\n */\n endpoints?: Partial<DirectServiceEndpoints>;\n /**\n * Client for the Vana Account access-request API. Defaults to a client against\n * the resolved Vana Account endpoints; inject your own to point at a custom\n * deployment or to supply a test double.\n */\n accessRequestClient?: AccessRequestClient;\n /**\n * Escrow settlement config used when a Personal Server read returns `402`.\n *\n * @remarks\n * Wires the DPv2 escrow gateway (`protocol/escrow`). The controller supplies\n * the EIP-712 `signTypedData` from the app key automatically.\n *\n * When omitted (or partially omitted), the SDK derives defaults from the\n * per-network endpoints table and the contract registry:\n * - `client` defaults to a gateway client at `endpoints.escrowGatewayUrl`\n * - `escrowContract` defaults to `CONTRACTS.DataPortabilityEscrow.addresses[chainId]`\n * - `chainId` defaults to the controller's resolved chain id\n *\n * Provide this field only to override a specific default.\n */\n escrow?: Partial<DirectEscrowConfig>;\n /** `fetch` used by the default access-request client. Defaults to `globalThis.fetch`. */\n fetchFn?: FetchLike;\n /** `fetch` used for the Personal Server read. Defaults to `globalThis.fetch`. */\n personalServerFetch?: PersonalServerFetch;\n /**\n * Transport-retry knobs for the Personal Server read\n * ({@link PersonalServerTransportRetryOptions}). Defaults to 3 attempts with\n * exponential backoff. Retries fire only when fetch throws (the browser-PS\n * relay reconnect window), never on a received HTTP status, and never\n * re-sign a payment.\n */\n personalServerTransportRetry?: PersonalServerTransportRetryOptions;\n}\n\n/**\n * Controller-level escrow config — the {@link EscrowPaymentConfig} minus the\n * `signTypedData` and `chainId` the controller injects itself.\n */\nexport interface DirectEscrowConfig extends Omit<\n EscrowPaymentConfig,\n \"signTypedData\" | \"chainId\"\n> {\n /**\n * Chain id for the EIP-712 domain. Defaults to the controller's environment\n * (1480 for mainnet, 14800 for moksha).\n */\n chainId?: number;\n}\n\n/**\n * Server-side controller for the direct Data Portability flow.\n *\n * @typeParam T - Shape of the data returned by {@link DirectDataController.readApprovedData}.\n */\nexport interface DirectDataController {\n /** The on-chain address of the app, derived from `appPrivateKey`. */\n readonly appAddress: string;\n\n /**\n * The app's on-chain address — the address to fund and inspect in the Builder\n * activity report. Equivalent to {@link DirectDataController.appAddress}.\n *\n * @returns The app's `0x`-prefixed address.\n */\n getAppAddress(): string;\n\n /**\n * The app's full identity: its configured id/name/homepage plus the derived\n * on-chain address. Useful for telling builders which app address to fund or\n * look up.\n *\n * @returns `{ id, name, homepageUrl, address }`.\n */\n getAppIdentity(): AppIdentity;\n\n /**\n * Create an access request the user can approve.\n *\n * @param input - The post-approval return URL.\n * @returns `{ requestId, approvalUrl, appAddress }`.\n */\n createAccessRequest(input: { returnUrl: string }): Promise<AccessRequest>;\n\n /**\n * Fetch the current status of an access request.\n *\n * @param requestId - The `dcr_*` id from {@link DirectDataController.createAccessRequest}.\n * @returns `{ status, personalServerUrl?, grantId?, scope?, scopes? }`.\n */\n getAccessRequestStatus(requestId: string): Promise<AccessRequestStatus>;\n\n /**\n * Read the approved data from the user's Personal Server.\n *\n * @remarks\n * Resolves the request to its grant + Personal Server and performs a Web3Signed\n * read. Hides the `402 Payment Required` flow by default: if a read needs\n * payment, it signs the Personal Server's payment challenge, retries with\n * `X-PAYMENT`, and attaches shape-validated but unauthenticated\n * {@link DirectPaymentResponseMetadata} under `payment` when the Personal\n * Server returns it. After a successful read, the controller acknowledges\n * the DCR so Vana Web can close/redirect the approval tab.\n *\n * A request can approve several scopes. This reads **one** of them — `scope`\n * when given, otherwise the first approved scope. Use\n * {@link DirectDataController.readAllApprovedData} to read them all.\n *\n * Acknowledging moves the DCR to `completed`, which is terminal and no longer\n * read-ready. To read several scopes with your own loop, pass\n * `acknowledge: false` on every call but the last.\n *\n * @param input - The `dcr_*` request id, the optional `scope` to read, and an\n * optional `acknowledge` flag (default `true`).\n * @returns `{ scope, data, payment? }`.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n * @throws {@link ScopeNotApprovedError} if `scope` is not an approved scope.\n * @throws {@link PaymentRequiredError} if payment is required but unsettled.\n */\n readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>>;\n\n /**\n * Read every scope the user approved on a request.\n *\n * @remarks\n * Reads the scopes in approval order, then acknowledges the DCR **once**,\n * after the last read — acknowledging earlier would move the request to\n * `completed` and make the remaining scopes unreadable.\n *\n * Each scope is a separate Personal Server read that settles its own\n * `data_access` fee from escrow, so reading N scopes costs N times a\n * single-scope read. The one-off registration fee is charged per grant, not\n * per scope.\n *\n * A scope that fails does not abort the rest: successes land in `results` and\n * failures in `errors`, because the fees for earlier scopes are already spent.\n * If any scope fails the request is left unacknowledged, so the scopes that\n * failed stay retryable — read them with `readApprovedData({ scope })` and\n * acknowledge on the last one.\n *\n * @param input - The `dcr_*` request id to read.\n * @returns `{ results, errors }`, both keyed by scope.\n * @throws {@link AccessNotApprovedError} if the request is not approved.\n */\n readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>>;\n}\n\nfunction isHexPrivateKey(value: string): value is Hex {\n return /^0x[0-9a-fA-F]{64}$/.test(value);\n}\n\n// A DCR is read-ready only while the grant exists and the Personal Server is\n// still serving it: `approved` (durable PS) or `ready_for_read` (browser PS).\n// `completed` is terminal — the app already read and acknowledged, and the\n// browser PS may be gone — so it is deliberately excluded here.\nfunction isReadReadyStatus(status: AccessRequestStatusValue): boolean {\n return status === \"approved\" || status === \"ready_for_read\";\n}\n\n/**\n * Create a {@link DirectDataController}.\n *\n * @param config - Controller configuration (env, key, app identity, source, scopes).\n * @returns A ready-to-use controller.\n * @throws {@link DirectConfigError} when the key or scopes are invalid.\n */\nexport function createDirectDataController(\n config: DirectDataControllerConfig,\n): DirectDataController {\n // `appPrivateKey` is the documented field; `builderPrivateKey` is a\n // deprecated alias kept for backwards compatibility.\n const privateKey = config.appPrivateKey ?? config.builderPrivateKey;\n if (!privateKey || !isHexPrivateKey(privateKey)) {\n throw new DirectConfigError(\n \"appPrivateKey must be a 0x-prefixed 32-byte hex string\",\n );\n }\n if (!config.scopes || config.scopes.length === 0) {\n throw new DirectConfigError(\"At least one scope is required\");\n }\n // Validate scopes eagerly so misconfiguration fails at construction.\n for (const scope of config.scopes) {\n parseScope(scope);\n }\n\n const env: DirectEnv = config.env ?? \"production\";\n const network: DirectNetwork = config.network ?? getDirectDefaultNetwork(env);\n const defaultEndpoints = getDirectEndpoints(env);\n const chainId = config.endpoints?.chainId ?? getDirectNetworkChainId(network);\n const endpoints: DirectServiceEndpoints = {\n ...defaultEndpoints,\n ...config.endpoints,\n chainId,\n };\n\n const account = privateKeyToAccount(privateKey as Hex);\n const signMessage: Web3SignedSignFn = (message: string) =>\n account.signMessage({ message });\n // viem's account.signTypedData satisfies the structural SignTypedDataFn used\n // by the escrow GenericPayment signer.\n const signTypedData = account.signTypedData as unknown as SignTypedDataFn;\n const accessRequestClient: AccessRequestClient =\n config.accessRequestClient ??\n createDefaultAccessRequestClient({\n baseUrl: endpoints.accessRequestBaseUrl,\n approvalBaseUrl: endpoints.approvalAppBaseUrl,\n fetchFn: config.fetchFn,\n appAddress: account.address,\n signMessage,\n });\n\n // Build the escrow payment config, defaulting from the per-network endpoints\n // table and the contract registry when `config.escrow` is omitted or partial.\n const escrowChainId = config.escrow?.chainId ?? chainId;\n const defaultEscrowContract =\n CONTRACTS.DataPortabilityEscrow.addresses[\n escrowChainId as keyof typeof CONTRACTS.DataPortabilityEscrow.addresses\n ] ?? undefined;\n if (!config.escrow?.escrowContract && !defaultEscrowContract) {\n throw new DirectConfigError(\n `No DataPortabilityEscrow address found in the registry for chainId ${escrowChainId}. ` +\n `Provide an explicit escrow.escrowContract in the controller config.`,\n );\n }\n const escrow: EscrowPaymentConfig = {\n client:\n config.escrow?.client ??\n createEscrowGatewayClient(endpoints.escrowGatewayUrl),\n escrowContract:\n config.escrow?.escrowContract ?? (defaultEscrowContract as `0x${string}`),\n chainId: escrowChainId,\n nonceSource: config.escrow?.nonceSource,\n signTypedData,\n };\n\n return {\n appAddress: account.address,\n\n getAppAddress(): string {\n return account.address;\n },\n\n getAppIdentity(): AppIdentity {\n return {\n id: config.app.id,\n name: config.app.name,\n homepageUrl: config.app.homepageUrl,\n address: account.address,\n };\n },\n\n async createAccessRequest(input): Promise<AccessRequest> {\n return accessRequestClient.createAccessRequest({\n appAddress: account.address,\n app: config.app,\n source: config.source,\n scopes: config.scopes,\n returnUrl: input.returnUrl,\n network,\n });\n },\n\n async getAccessRequestStatus(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n return accessRequestClient.getAccessRequestStatus(requestId);\n },\n\n async readApprovedData<T = unknown>(input: {\n requestId: string;\n scope?: string;\n acknowledge?: boolean;\n }): Promise<ApprovedDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scope = resolveRequestedScope(status, input.scope);\n\n const result = await readScope<T>(status, scope);\n if (input.acknowledge !== false) {\n await acknowledgeQuietly(input.requestId);\n }\n return result;\n },\n\n async readAllApprovedData<T = unknown>(input: {\n requestId: string;\n }): Promise<MultiScopeDataResult<T>> {\n const status = await requireReadReady(input.requestId);\n const scopes = approvedScopes(status);\n\n const results: Record<string, ApprovedDataResult<T>> = {};\n const errors: Record<string, Error> = {};\n // Sequential, not parallel: each read settles its own escrow payment and\n // the default nonce source is process-local, so concurrent reads would\n // race on the payment nonce.\n for (const scope of scopes) {\n try {\n results[scope] = await readScope<T>(status, scope);\n } catch (error) {\n errors[scope] =\n error instanceof Error ? error : new Error(String(error));\n }\n }\n\n // Acknowledge only after the last read, and only if every scope read —\n // acking moves the DCR to `completed`, which is terminal and no longer\n // read-ready, so acking on a partial failure would make the scope that\n // failed impossible to retry.\n if (Object.keys(errors).length === 0) {\n await acknowledgeQuietly(input.requestId);\n }\n\n return { results, errors };\n },\n };\n\n async function requireReadReady(\n requestId: string,\n ): Promise<AccessRequestStatus> {\n const status = await accessRequestClient.getAccessRequestStatus(requestId);\n // `scope` and `scopes` are both optional on the public status type, and a\n // client may return either one — require at least one approved scope rather\n // than the singular field specifically.\n if (\n !isReadReadyStatus(status.status) ||\n !status.personalServerUrl ||\n !status.grantId ||\n approvedScopes(status).length === 0\n ) {\n throw new AccessNotApprovedError(\n \"Request is not approved or is missing grantId/scope/personalServerUrl\",\n {\n requestId,\n status: status.status,\n hasPersonalServerUrl: Boolean(status.personalServerUrl),\n hasGrantId: Boolean(status.grantId),\n hasScope: approvedScopes(status).length > 0,\n },\n );\n }\n return status;\n }\n\n /** Approved scopes in approval order, falling back to the single `scope`. */\n function approvedScopes(status: AccessRequestStatus): string[] {\n if (status.scopes && status.scopes.length > 0) return status.scopes;\n return status.scope ? [status.scope] : [];\n }\n\n /**\n * Resolve which scope to read. Rejects an unapproved scope up front so it\n * never reaches the Personal Server and never settles a fee.\n */\n function resolveRequestedScope(\n status: AccessRequestStatus,\n requested?: string,\n ): string {\n const scopes = approvedScopes(status);\n if (requested === undefined) return scopes[0];\n if (!scopes.includes(requested)) {\n throw new ScopeNotApprovedError(\n `Scope \"${requested}\" is not approved on this request`,\n { requestedScope: requested, approvedScopes: scopes },\n );\n }\n return requested;\n }\n\n async function readScope<T>(\n status: AccessRequestStatus,\n scope: string,\n ): Promise<ApprovedDataResult<T>> {\n const result = await readPersonalServerData({\n personalServerUrl: status.personalServerUrl as string,\n scope,\n grantId: status.grantId as string,\n payerAddress: account.address,\n signMessage,\n escrow,\n fetchFn: config.personalServerFetch,\n transportRetry: config.personalServerTransportRetry,\n });\n return { scope, data: result.data as T, payment: result.payment };\n }\n\n async function acknowledgeQuietly(requestId: string): Promise<void> {\n try {\n await accessRequestClient.acknowledgeRead?.(requestId);\n } catch {\n // The read already succeeded; ack only drives Vana Web completion UX.\n }\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @category Direct
|
|
10
10
|
* @module direct/endpoints
|
|
11
11
|
*/
|
|
12
|
-
import type { DirectEnv, DirectNetwork, DirectServiceEndpoints } from "./types";
|
|
12
|
+
import type { DirectEnv, DirectNetwork, DirectServiceEndpoints } from "./types.js";
|
|
13
13
|
/** Production (mainnet) service URLs. */
|
|
14
14
|
export declare const PRODUCTION_ENDPOINTS: DirectServiceEndpoints;
|
|
15
15
|
/** Internal dev stack service URLs. */
|
package/dist/direct/errors.cjs
CHANGED
|
@@ -21,7 +21,8 @@ __export(errors_exports, {
|
|
|
21
21
|
AccessNotApprovedError: () => AccessNotApprovedError,
|
|
22
22
|
DirectConfigError: () => DirectConfigError,
|
|
23
23
|
PaymentRequiredError: () => PaymentRequiredError,
|
|
24
|
-
PersonalServerReadError: () => PersonalServerReadError
|
|
24
|
+
PersonalServerReadError: () => PersonalServerReadError,
|
|
25
|
+
ScopeNotApprovedError: () => ScopeNotApprovedError
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(errors_exports);
|
|
27
28
|
var import_errors = require("../errors");
|
|
@@ -39,6 +40,13 @@ class AccessNotApprovedError extends import_errors.VanaError {
|
|
|
39
40
|
}
|
|
40
41
|
details;
|
|
41
42
|
}
|
|
43
|
+
class ScopeNotApprovedError extends import_errors.VanaError {
|
|
44
|
+
constructor(message, details) {
|
|
45
|
+
super(message, "DIRECT_SCOPE_NOT_APPROVED");
|
|
46
|
+
this.details = details;
|
|
47
|
+
}
|
|
48
|
+
details;
|
|
49
|
+
}
|
|
42
50
|
class PersonalServerReadError extends import_errors.VanaError {
|
|
43
51
|
constructor(message, status, details) {
|
|
44
52
|
super(message, "DIRECT_PERSONAL_SERVER_READ_ERROR");
|
|
@@ -60,6 +68,7 @@ class PaymentRequiredError extends import_errors.VanaError {
|
|
|
60
68
|
AccessNotApprovedError,
|
|
61
69
|
DirectConfigError,
|
|
62
70
|
PaymentRequiredError,
|
|
63
|
-
PersonalServerReadError
|
|
71
|
+
PersonalServerReadError,
|
|
72
|
+
ScopeNotApprovedError
|
|
64
73
|
});
|
|
65
74
|
//# sourceMappingURL=errors.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/errors.ts"],"sourcesContent":["/**\n * Typed errors for the Direct Data Controller flow.\n *\n * @remarks\n * These extend {@link VanaError} so direct-controller callers can branch on the\n * structured `code` field. The messages map to the failure modes documented in\n * the builder guide (\"Common Errors\").\n *\n * @category Direct\n * @module direct/errors\n */\n\nimport { VanaError } from \"../errors\";\n\n/** Thrown when configuration passed to {@link createDirectDataController} is invalid. */\nexport class DirectConfigError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_CONFIG_ERROR\");\n }\n}\n\n/**\n * Thrown when {@link DirectDataController.readApprovedData} is called for a\n * request that is not yet approved (missing grantId, scope, or personalServerUrl).\n */\nexport class AccessNotApprovedError extends VanaError {\n constructor(\n message = \"Access request is not approved yet\",\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_ACCESS_NOT_APPROVED\");\n }\n}\n\n/** Thrown when the Personal Server cannot be reached or returns an error. */\nexport class PersonalServerReadError extends VanaError {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PERSONAL_SERVER_READ_ERROR\");\n }\n}\n\n/**\n * Thrown when a Personal Server requires payment (HTTP 402) but no escrow config\n * is set, or the read still requires payment after escrow settlement.\n *\n * @remarks\n * `details` carries structured debug data (`scope`, `grantId`, `asset`,\n * `amount`, and — after a settlement attempt — the `payment` receipt) so callers\n * can see exactly what was owed.\n */\nexport class PaymentRequiredError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PAYMENT_REQUIRED\");\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,oBAA0B;AAGnB,MAAM,0BAA0B,wBAAU;AAAA,EAC/C,YACE,SACgB,SAChB;AACA,UAAM,SAAS,qBAAqB;AAFpB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAMO,MAAM,+BAA+B,wBAAU;AAAA,EACpD,YACE,UAAU,sCACM,SAChB;AACA,UAAM,SAAS,4BAA4B;AAF3B;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAGO,MAAM,gCAAgC,wBAAU;AAAA,EACrD,YACE,SACgB,QACA,SAChB;AACA,UAAM,SAAS,mCAAmC;AAHlC;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;AAWO,MAAM,6BAA6B,wBAAU;AAAA,EAClD,YACE,SACgB,SAChB;AACA,UAAM,SAAS,yBAAyB;AAFxB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/direct/errors.ts"],"sourcesContent":["/**\n * Typed errors for the Direct Data Controller flow.\n *\n * @remarks\n * These extend {@link VanaError} so direct-controller callers can branch on the\n * structured `code` field. The messages map to the failure modes documented in\n * the builder guide (\"Common Errors\").\n *\n * @category Direct\n * @module direct/errors\n */\n\nimport { VanaError } from \"../errors\";\n\n/** Thrown when configuration passed to {@link createDirectDataController} is invalid. */\nexport class DirectConfigError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_CONFIG_ERROR\");\n }\n}\n\n/**\n * Thrown when {@link DirectDataController.readApprovedData} is called for a\n * request that is not yet approved (missing grantId, scope, or personalServerUrl).\n */\nexport class AccessNotApprovedError extends VanaError {\n constructor(\n message = \"Access request is not approved yet\",\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_ACCESS_NOT_APPROVED\");\n }\n}\n\n/**\n * Thrown when a caller asks {@link DirectDataController.readApprovedData} for a\n * scope the user did not approve on this request.\n *\n * @remarks\n * Raised before any network call, so an unapproved scope never reaches the\n * Personal Server and never settles a fee. `details` carries the requested\n * scope and the scopes actually approved.\n */\nexport class ScopeNotApprovedError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_SCOPE_NOT_APPROVED\");\n }\n}\n\n/** Thrown when the Personal Server cannot be reached or returns an error. */\nexport class PersonalServerReadError extends VanaError {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PERSONAL_SERVER_READ_ERROR\");\n }\n}\n\n/**\n * Thrown when a Personal Server requires payment (HTTP 402) but no escrow config\n * is set, or the read still requires payment after escrow settlement.\n *\n * @remarks\n * `details` carries structured debug data (`scope`, `grantId`, `asset`,\n * `amount`, and — after a settlement attempt — the `payment` receipt) so callers\n * can see exactly what was owed.\n */\nexport class PaymentRequiredError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PAYMENT_REQUIRED\");\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAYA,oBAA0B;AAGnB,MAAM,0BAA0B,wBAAU;AAAA,EAC/C,YACE,SACgB,SAChB;AACA,UAAM,SAAS,qBAAqB;AAFpB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAMO,MAAM,+BAA+B,wBAAU;AAAA,EACpD,YACE,UAAU,sCACM,SAChB;AACA,UAAM,SAAS,4BAA4B;AAF3B;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAWO,MAAM,8BAA8B,wBAAU;AAAA,EACnD,YACE,SACgB,SAChB;AACA,UAAM,SAAS,2BAA2B;AAF1B;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAGO,MAAM,gCAAgC,wBAAU;AAAA,EACrD,YACE,SACgB,QACA,SAChB;AACA,UAAM,SAAS,mCAAmC;AAHlC;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;AAWO,MAAM,6BAA6B,wBAAU;AAAA,EAClD,YACE,SACgB,SAChB;AACA,UAAM,SAAS,yBAAyB;AAFxB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;","names":[]}
|
package/dist/direct/errors.d.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* @category Direct
|
|
10
10
|
* @module direct/errors
|
|
11
11
|
*/
|
|
12
|
-
import { VanaError } from "../errors";
|
|
12
|
+
import { VanaError } from "../errors.js";
|
|
13
13
|
/** Thrown when configuration passed to {@link createDirectDataController} is invalid. */
|
|
14
14
|
export declare class DirectConfigError extends VanaError {
|
|
15
15
|
readonly details?: Record<string, unknown> | undefined;
|
|
@@ -23,6 +23,19 @@ export declare class AccessNotApprovedError extends VanaError {
|
|
|
23
23
|
readonly details?: Record<string, unknown> | undefined;
|
|
24
24
|
constructor(message?: string, details?: Record<string, unknown> | undefined);
|
|
25
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Thrown when a caller asks {@link DirectDataController.readApprovedData} for a
|
|
28
|
+
* scope the user did not approve on this request.
|
|
29
|
+
*
|
|
30
|
+
* @remarks
|
|
31
|
+
* Raised before any network call, so an unapproved scope never reaches the
|
|
32
|
+
* Personal Server and never settles a fee. `details` carries the requested
|
|
33
|
+
* scope and the scopes actually approved.
|
|
34
|
+
*/
|
|
35
|
+
export declare class ScopeNotApprovedError extends VanaError {
|
|
36
|
+
readonly details?: Record<string, unknown> | undefined;
|
|
37
|
+
constructor(message: string, details?: Record<string, unknown> | undefined);
|
|
38
|
+
}
|
|
26
39
|
/** Thrown when the Personal Server cannot be reached or returns an error. */
|
|
27
40
|
export declare class PersonalServerReadError extends VanaError {
|
|
28
41
|
readonly status?: number | undefined;
|
package/dist/direct/errors.js
CHANGED
|
@@ -13,6 +13,13 @@ class AccessNotApprovedError extends VanaError {
|
|
|
13
13
|
}
|
|
14
14
|
details;
|
|
15
15
|
}
|
|
16
|
+
class ScopeNotApprovedError extends VanaError {
|
|
17
|
+
constructor(message, details) {
|
|
18
|
+
super(message, "DIRECT_SCOPE_NOT_APPROVED");
|
|
19
|
+
this.details = details;
|
|
20
|
+
}
|
|
21
|
+
details;
|
|
22
|
+
}
|
|
16
23
|
class PersonalServerReadError extends VanaError {
|
|
17
24
|
constructor(message, status, details) {
|
|
18
25
|
super(message, "DIRECT_PERSONAL_SERVER_READ_ERROR");
|
|
@@ -33,6 +40,7 @@ export {
|
|
|
33
40
|
AccessNotApprovedError,
|
|
34
41
|
DirectConfigError,
|
|
35
42
|
PaymentRequiredError,
|
|
36
|
-
PersonalServerReadError
|
|
43
|
+
PersonalServerReadError,
|
|
44
|
+
ScopeNotApprovedError
|
|
37
45
|
};
|
|
38
46
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/direct/errors.ts"],"sourcesContent":["/**\n * Typed errors for the Direct Data Controller flow.\n *\n * @remarks\n * These extend {@link VanaError} so direct-controller callers can branch on the\n * structured `code` field. The messages map to the failure modes documented in\n * the builder guide (\"Common Errors\").\n *\n * @category Direct\n * @module direct/errors\n */\n\nimport { VanaError } from \"../errors\";\n\n/** Thrown when configuration passed to {@link createDirectDataController} is invalid. */\nexport class DirectConfigError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_CONFIG_ERROR\");\n }\n}\n\n/**\n * Thrown when {@link DirectDataController.readApprovedData} is called for a\n * request that is not yet approved (missing grantId, scope, or personalServerUrl).\n */\nexport class AccessNotApprovedError extends VanaError {\n constructor(\n message = \"Access request is not approved yet\",\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_ACCESS_NOT_APPROVED\");\n }\n}\n\n/** Thrown when the Personal Server cannot be reached or returns an error. */\nexport class PersonalServerReadError extends VanaError {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PERSONAL_SERVER_READ_ERROR\");\n }\n}\n\n/**\n * Thrown when a Personal Server requires payment (HTTP 402) but no escrow config\n * is set, or the read still requires payment after escrow settlement.\n *\n * @remarks\n * `details` carries structured debug data (`scope`, `grantId`, `asset`,\n * `amount`, and — after a settlement attempt — the `payment` receipt) so callers\n * can see exactly what was owed.\n */\nexport class PaymentRequiredError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PAYMENT_REQUIRED\");\n }\n}\n"],"mappings":"AAYA,SAAS,iBAAiB;AAGnB,MAAM,0BAA0B,UAAU;AAAA,EAC/C,YACE,SACgB,SAChB;AACA,UAAM,SAAS,qBAAqB;AAFpB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAMO,MAAM,+BAA+B,UAAU;AAAA,EACpD,YACE,UAAU,sCACM,SAChB;AACA,UAAM,SAAS,4BAA4B;AAF3B;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAGO,MAAM,gCAAgC,UAAU;AAAA,EACrD,YACE,SACgB,QACA,SAChB;AACA,UAAM,SAAS,mCAAmC;AAHlC;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;AAWO,MAAM,6BAA6B,UAAU;AAAA,EAClD,YACE,SACgB,SAChB;AACA,UAAM,SAAS,yBAAyB;AAFxB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/direct/errors.ts"],"sourcesContent":["/**\n * Typed errors for the Direct Data Controller flow.\n *\n * @remarks\n * These extend {@link VanaError} so direct-controller callers can branch on the\n * structured `code` field. The messages map to the failure modes documented in\n * the builder guide (\"Common Errors\").\n *\n * @category Direct\n * @module direct/errors\n */\n\nimport { VanaError } from \"../errors\";\n\n/** Thrown when configuration passed to {@link createDirectDataController} is invalid. */\nexport class DirectConfigError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_CONFIG_ERROR\");\n }\n}\n\n/**\n * Thrown when {@link DirectDataController.readApprovedData} is called for a\n * request that is not yet approved (missing grantId, scope, or personalServerUrl).\n */\nexport class AccessNotApprovedError extends VanaError {\n constructor(\n message = \"Access request is not approved yet\",\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_ACCESS_NOT_APPROVED\");\n }\n}\n\n/**\n * Thrown when a caller asks {@link DirectDataController.readApprovedData} for a\n * scope the user did not approve on this request.\n *\n * @remarks\n * Raised before any network call, so an unapproved scope never reaches the\n * Personal Server and never settles a fee. `details` carries the requested\n * scope and the scopes actually approved.\n */\nexport class ScopeNotApprovedError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_SCOPE_NOT_APPROVED\");\n }\n}\n\n/** Thrown when the Personal Server cannot be reached or returns an error. */\nexport class PersonalServerReadError extends VanaError {\n constructor(\n message: string,\n public readonly status?: number,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PERSONAL_SERVER_READ_ERROR\");\n }\n}\n\n/**\n * Thrown when a Personal Server requires payment (HTTP 402) but no escrow config\n * is set, or the read still requires payment after escrow settlement.\n *\n * @remarks\n * `details` carries structured debug data (`scope`, `grantId`, `asset`,\n * `amount`, and — after a settlement attempt — the `payment` receipt) so callers\n * can see exactly what was owed.\n */\nexport class PaymentRequiredError extends VanaError {\n constructor(\n message: string,\n public readonly details?: Record<string, unknown>,\n ) {\n super(message, \"DIRECT_PAYMENT_REQUIRED\");\n }\n}\n"],"mappings":"AAYA,SAAS,iBAAiB;AAGnB,MAAM,0BAA0B,UAAU;AAAA,EAC/C,YACE,SACgB,SAChB;AACA,UAAM,SAAS,qBAAqB;AAFpB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAMO,MAAM,+BAA+B,UAAU;AAAA,EACpD,YACE,UAAU,sCACM,SAChB;AACA,UAAM,SAAS,4BAA4B;AAF3B;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAWO,MAAM,8BAA8B,UAAU;AAAA,EACnD,YACE,SACgB,SAChB;AACA,UAAM,SAAS,2BAA2B;AAF1B;AAAA,EAGlB;AAAA,EAHkB;AAIpB;AAGO,MAAM,gCAAgC,UAAU;AAAA,EACrD,YACE,SACgB,QACA,SAChB;AACA,UAAM,SAAS,mCAAmC;AAHlC;AACA;AAAA,EAGlB;AAAA,EAJkB;AAAA,EACA;AAIpB;AAWO,MAAM,6BAA6B,UAAU;AAAA,EAClD,YACE,SACgB,SAChB;AACA,UAAM,SAAS,yBAAyB;AAFxB;AAAA,EAGlB;AAAA,EAHkB;AAIpB;","names":[]}
|