mppx 0.8.15 → 0.8.18
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/CHANGELOG.md +91 -0
- package/README.md +15 -0
- package/dist/Challenge.js +61 -39
- package/dist/Constants.d.ts +17 -17
- package/dist/Errors.d.ts +3 -3
- package/dist/Method.d.ts +35 -5
- package/dist/Method.js +7 -22
- package/dist/Store.d.ts +19 -0
- package/dist/Store.js +43 -0
- package/dist/attestation/Client.d.ts +17 -0
- package/dist/attestation/Client.js +28 -0
- package/dist/attestation/Constants.d.ts +15 -0
- package/dist/attestation/Constants.js +15 -0
- package/dist/attestation/Server.d.ts +4 -0
- package/dist/attestation/Server.js +8 -0
- package/dist/attestation/Types.d.ts +76 -0
- package/dist/attestation/Types.js +2 -0
- package/dist/attestation/index.d.ts +6 -0
- package/dist/attestation/index.js +6 -0
- package/dist/attestation/internal/HttpMessageSignature.d.ts +73 -0
- package/dist/attestation/internal/HttpMessageSignature.js +320 -0
- package/dist/attestation/internal/SigningContext.d.ts +4 -0
- package/dist/attestation/internal/SigningContext.js +11 -0
- package/dist/bin.js +3 -3
- package/dist/cli/THIRD-PARTY-LICENSES.txt +328 -0
- package/dist/cli/account.d.ts +14 -14
- package/dist/cli/cli.d.ts +6 -6
- package/dist/cli/cli.js +253 -1504
- package/dist/cli/plugins/index.js +19 -5
- package/dist/cli/plugins/tempo.js +7 -3
- package/dist/cli/sessions/Manager.js +7 -4
- package/dist/cli/sessions/commands.d.ts +4 -4
- package/dist/cli/sessions/request.d.ts +2 -0
- package/dist/cli/sessions/request.js +18 -0
- package/dist/cli/sessions/store.js +28 -14
- package/dist/cli/utils.d.ts +1 -1
- package/dist/cli/validate/index.d.ts +3 -3
- package/dist/client/Mppx.d.ts +3 -0
- package/dist/client/Mppx.js +13 -5
- package/dist/client/Transport.d.ts +3 -5
- package/dist/client/Transport.js +1 -17
- package/dist/client/internal/Fetch.js +2 -6
- package/dist/discovery/Discovery.js +2 -1
- package/dist/discovery/OpenApi.d.ts +3 -5
- package/dist/discovery/OpenApi.js +13 -34
- package/dist/discovery/internal/Metadata.d.ts +20 -0
- package/dist/discovery/internal/Metadata.js +36 -0
- package/dist/evm/Methods.d.ts +11 -11
- package/dist/evm/Types.d.ts +17 -17
- package/dist/evm/client/Charge.d.ts +11 -11
- package/dist/evm/client/Charge.js +8 -0
- package/dist/evm/client/Methods.d.ts +11 -11
- package/dist/evm/server/Charge.d.ts +1 -1
- package/dist/evm/server/Charge.js +2 -0
- package/dist/evm/server/Methods.d.ts +11 -11
- package/dist/internal/HeaderCodec.d.ts +2 -2
- package/dist/internal/types.d.ts +6 -0
- package/dist/proxy/Proxy.d.ts +2 -0
- package/dist/proxy/Proxy.js +117 -9
- package/dist/proxy/Service.d.ts +36 -1
- package/dist/proxy/Service.js +9 -8
- package/dist/proxy/services/anthropic.js +1 -0
- package/dist/proxy/services/openai.js +1 -0
- package/dist/proxy/services/stripe.js +1 -0
- package/dist/server/Mppx.d.ts +75 -13
- package/dist/server/Mppx.js +201 -23
- package/dist/server/core.d.ts +9 -0
- package/dist/server/core.js +9 -0
- package/dist/server/internal/html/config.d.ts +9 -9
- package/dist/server/internal/html/constants.d.ts +17 -17
- package/dist/stripe/Methods.d.ts +10 -10
- package/dist/stripe/client/Charge.d.ts +10 -10
- package/dist/stripe/client/Methods.d.ts +10 -10
- package/dist/stripe/internal/constants.d.ts +1 -1
- package/dist/stripe/internal/constants.js +1 -1
- package/dist/stripe/internal/types.d.ts +1 -0
- package/dist/stripe/server/Charge.d.ts +11 -11
- package/dist/stripe/server/Charge.js +51 -5
- package/dist/stripe/server/Methods.d.ts +137 -48
- package/dist/stripe/server/Methods.js +265 -4
- package/dist/stripe/server/internal/deposit-address.d.ts +13 -0
- package/dist/stripe/server/internal/deposit-address.js +28 -0
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/record-payment.d.ts +15 -0
- package/dist/stripe/server/internal/record-payment.js +42 -0
- package/dist/stripe/server/internal/request.d.ts +25 -0
- package/dist/stripe/server/internal/request.js +29 -0
- package/dist/stripe/server/spt.d.ts +8 -0
- package/dist/stripe/server/spt.js +8 -0
- package/dist/tap/Constants.d.ts +28 -0
- package/dist/tap/Constants.js +32 -0
- package/dist/tap/Types.d.ts +11 -0
- package/dist/tap/Types.js +2 -0
- package/dist/tap/client/index.d.ts +26 -0
- package/dist/tap/client/index.js +33 -0
- package/dist/tap/index.d.ts +5 -0
- package/dist/tap/index.js +5 -0
- package/dist/tap/server/index.d.ts +20 -0
- package/dist/tap/server/index.js +36 -0
- package/dist/tempo/Attribution.d.ts +1 -1
- package/dist/tempo/Methods.d.ts +73 -60
- package/dist/tempo/Methods.js +14 -3
- package/dist/tempo/Proof.d.ts +16 -16
- package/dist/tempo/client/Charge.d.ts +22 -19
- package/dist/tempo/client/Charge.js +32 -16
- package/dist/tempo/client/Methods.d.ts +51 -38
- package/dist/tempo/client/Subscription.d.ts +23 -23
- package/dist/tempo/internal/address.d.ts +2 -1
- package/dist/tempo/internal/address.js +3 -1
- package/dist/tempo/internal/auto-swap.js +4 -19
- package/dist/tempo/internal/defaults.d.ts +17 -6
- package/dist/tempo/internal/defaults.js +11 -0
- package/dist/tempo/internal/fee-payer.d.ts +12 -12
- package/dist/tempo/internal/machine-token-charge.d.ts +51 -0
- package/dist/tempo/internal/machine-token-charge.js +113 -0
- package/dist/tempo/internal/machine-token-session.d.ts +88 -0
- package/dist/tempo/internal/machine-token-session.js +173 -0
- package/dist/tempo/internal/proof.d.ts +17 -17
- package/dist/tempo/internal/signature-envelope.d.ts +14 -0
- package/dist/tempo/internal/signature-envelope.js +28 -0
- package/dist/tempo/legacy/client/ChannelOps.d.ts +2 -2
- package/dist/tempo/legacy/client/ChannelOps.js +11 -5
- package/dist/tempo/legacy/client/Session.d.ts +33 -21
- package/dist/tempo/legacy/client/Session.js +3 -3
- package/dist/tempo/legacy/client/SessionManager.d.ts +3 -1
- package/dist/tempo/legacy/client/SessionManager.js +2 -0
- package/dist/tempo/legacy/index.d.ts +1 -3
- package/dist/tempo/legacy/index.js +1 -3
- package/dist/tempo/legacy/session/escrow.abi.d.ts +374 -374
- package/dist/tempo/server/Charge.d.ts +28 -11
- package/dist/tempo/server/Charge.js +101 -55
- package/dist/tempo/server/Methods.d.ts +58 -56
- package/dist/tempo/server/Methods.js +8 -18
- package/dist/tempo/server/Relay.d.ts +1 -1
- package/dist/tempo/server/Relay.js +2 -2
- package/dist/tempo/server/Subscription.d.ts +25 -25
- package/dist/tempo/server/Subscription.js +2 -0
- package/dist/tempo/server/index.d.ts +1 -1
- package/dist/tempo/server/index.js +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +2 -0
- package/dist/tempo/server/internal/transport.js +9 -2
- package/dist/tempo/session/client/ChannelOps.d.ts +18 -3
- package/dist/tempo/session/client/ChannelOps.js +35 -6
- package/dist/tempo/session/client/ChannelStore.d.ts +0 -5
- package/dist/tempo/session/client/ChannelStore.js +2 -2
- package/dist/tempo/session/client/CredentialState.d.ts +18 -3
- package/dist/tempo/session/client/CredentialState.js +77 -76
- package/dist/tempo/session/client/Runtime.d.ts +4 -2
- package/dist/tempo/session/client/Runtime.js +21 -5
- package/dist/tempo/session/client/Session.d.ts +34 -22
- package/dist/tempo/session/client/Session.js +186 -63
- package/dist/tempo/session/client/SessionManager.d.ts +3 -1
- package/dist/tempo/session/client/SessionManager.js +35 -11
- package/dist/tempo/session/client/Transports.d.ts +2 -0
- package/dist/tempo/session/client/Transports.js +12 -2
- package/dist/tempo/session/precompile/Chain.d.ts +37 -2
- package/dist/tempo/session/precompile/Chain.js +116 -15
- package/dist/tempo/session/precompile/Protocol.d.ts +15 -0
- package/dist/tempo/session/precompile/Protocol.js +7 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +7 -5
- package/dist/tempo/session/precompile/Voucher.js +6 -12
- package/dist/tempo/session/precompile/escrow.abi.d.ts +306 -306
- package/dist/tempo/session/server/ChannelStore.d.ts +15 -0
- package/dist/tempo/session/server/ChannelStore.js +27 -12
- package/dist/tempo/session/server/CredentialVerification.d.ts +39 -20
- package/dist/tempo/session/server/CredentialVerification.js +359 -89
- package/dist/tempo/session/server/MeteredStream.d.ts +4 -1
- package/dist/tempo/session/server/MeteredStream.js +8 -5
- package/dist/tempo/session/server/RequestState.d.ts +19 -0
- package/dist/tempo/session/server/RequestState.js +61 -15
- package/dist/tempo/session/server/Session.d.ts +46 -23
- package/dist/tempo/session/server/Session.js +150 -56
- package/dist/tempo/session/server/Settlement.d.ts +11 -4
- package/dist/tempo/session/server/Settlement.js +53 -21
- package/dist/tempo/session/server/Sse.d.ts +3 -1
- package/dist/tempo/session/server/Sse.js +2 -1
- package/dist/tempo/session/server/Transports.d.ts +1 -1
- package/dist/tempo/session/server/Transports.js +1 -0
- package/dist/tempo/session/server/Ws.d.ts +6 -1
- package/dist/tempo/session/server/Ws.js +1 -0
- package/dist/tempo/subscription/KeyAuthorization.d.ts +39 -39
- package/dist/web-bot-auth/Constants.d.ts +20 -0
- package/dist/web-bot-auth/Constants.js +24 -0
- package/dist/web-bot-auth/Types.d.ts +10 -0
- package/dist/web-bot-auth/Types.js +2 -0
- package/dist/web-bot-auth/client/index.d.ts +29 -0
- package/dist/web-bot-auth/client/index.js +64 -0
- package/dist/web-bot-auth/index.d.ts +7 -0
- package/dist/web-bot-auth/index.js +7 -0
- package/dist/web-bot-auth/internal/JwkThumbprint.d.ts +5 -0
- package/dist/web-bot-auth/internal/JwkThumbprint.js +20 -0
- package/dist/web-bot-auth/internal/SignatureAgent.d.ts +3 -0
- package/dist/web-bot-auth/internal/SignatureAgent.js +18 -0
- package/dist/web-bot-auth/server/index.d.ts +35 -0
- package/dist/web-bot-auth/server/index.js +88 -0
- package/dist/x402/Types.d.ts +6 -6
- package/dist/x402/client/Exact.d.ts +8 -0
- package/dist/x402/client/Exact.js +40 -10
- package/dist/x402/server/EvmCharge.d.ts +22 -0
- package/dist/x402/server/EvmCharge.js +54 -20
- package/package.json +28 -34
- package/dist/BodyDigest.d.ts.map +0 -1
- package/dist/BodyDigest.js.map +0 -1
- package/dist/Challenge.d.ts.map +0 -1
- package/dist/Challenge.js.map +0 -1
- package/dist/Constants.d.ts.map +0 -1
- package/dist/Constants.js.map +0 -1
- package/dist/Credential.d.ts.map +0 -1
- package/dist/Credential.js.map +0 -1
- package/dist/Errors.d.ts.map +0 -1
- package/dist/Errors.js.map +0 -1
- package/dist/Expires.d.ts.map +0 -1
- package/dist/Expires.js.map +0 -1
- package/dist/Html.d.ts.map +0 -1
- package/dist/Html.js.map +0 -1
- package/dist/Mcp.d.ts.map +0 -1
- package/dist/Mcp.js.map +0 -1
- package/dist/Method.d.ts.map +0 -1
- package/dist/Method.js.map +0 -1
- package/dist/PaymentRequest.d.ts.map +0 -1
- package/dist/PaymentRequest.js.map +0 -1
- package/dist/Receipt.d.ts.map +0 -1
- package/dist/Receipt.js.map +0 -1
- package/dist/Store.d.ts.map +0 -1
- package/dist/Store.js.map +0 -1
- package/dist/bin.d.ts.map +0 -1
- package/dist/bin.js.map +0 -1
- package/dist/cli/account.d.ts.map +0 -1
- package/dist/cli/account.js.map +0 -1
- package/dist/cli/cli.d.ts.map +0 -1
- package/dist/cli/cli.js.map +0 -1
- package/dist/cli/config.d.ts.map +0 -1
- package/dist/cli/config.js.map +0 -1
- package/dist/cli/internal.d.ts.map +0 -1
- package/dist/cli/internal.js.map +0 -1
- package/dist/cli/plugins/evm.d.ts.map +0 -1
- package/dist/cli/plugins/evm.js.map +0 -1
- package/dist/cli/plugins/index.d.ts.map +0 -1
- package/dist/cli/plugins/index.js.map +0 -1
- package/dist/cli/plugins/plugin.d.ts.map +0 -1
- package/dist/cli/plugins/plugin.js.map +0 -1
- package/dist/cli/plugins/stripe.d.ts.map +0 -1
- package/dist/cli/plugins/stripe.js.map +0 -1
- package/dist/cli/plugins/tempo.d.ts.map +0 -1
- package/dist/cli/plugins/tempo.js.map +0 -1
- package/dist/cli/sessions/Manager.d.ts.map +0 -1
- package/dist/cli/sessions/Manager.js.map +0 -1
- package/dist/cli/sessions/commands.d.ts.map +0 -1
- package/dist/cli/sessions/commands.js.map +0 -1
- package/dist/cli/sessions/request.d.ts.map +0 -1
- package/dist/cli/sessions/request.js.map +0 -1
- package/dist/cli/sessions/store.d.ts.map +0 -1
- package/dist/cli/sessions/store.js.map +0 -1
- package/dist/cli/utils.d.ts.map +0 -1
- package/dist/cli/utils.js.map +0 -1
- package/dist/cli/validate/challenge.d.ts.map +0 -1
- package/dist/cli/validate/challenge.js.map +0 -1
- package/dist/cli/validate/discovery.d.ts.map +0 -1
- package/dist/cli/validate/discovery.js.map +0 -1
- package/dist/cli/validate/helpers.d.ts.map +0 -1
- package/dist/cli/validate/helpers.js.map +0 -1
- package/dist/cli/validate/index.d.ts.map +0 -1
- package/dist/cli/validate/index.js.map +0 -1
- package/dist/cli/validate/messages.d.ts.map +0 -1
- package/dist/cli/validate/messages.js.map +0 -1
- package/dist/cli/validate/payment.d.ts.map +0 -1
- package/dist/cli/validate/payment.js.map +0 -1
- package/dist/client/Methods.d.ts.map +0 -1
- package/dist/client/Methods.js.map +0 -1
- package/dist/client/Mppx.d.ts.map +0 -1
- package/dist/client/Mppx.js.map +0 -1
- package/dist/client/Transport.d.ts.map +0 -1
- package/dist/client/Transport.js.map +0 -1
- package/dist/client/index.d.ts.map +0 -1
- package/dist/client/index.js.map +0 -1
- package/dist/client/internal/Fetch.d.ts.map +0 -1
- package/dist/client/internal/Fetch.js.map +0 -1
- package/dist/client/internal/MethodChallenge.d.ts.map +0 -1
- package/dist/client/internal/MethodChallenge.js.map +0 -1
- package/dist/client/internal/MethodResponse.d.ts.map +0 -1
- package/dist/client/internal/MethodResponse.js.map +0 -1
- package/dist/client/internal/protocols/Mcp.d.ts.map +0 -1
- package/dist/client/internal/protocols/Mcp.js.map +0 -1
- package/dist/client/internal/protocols/Mpp.d.ts.map +0 -1
- package/dist/client/internal/protocols/Mpp.js.map +0 -1
- package/dist/client/internal/protocols/Protocol.d.ts.map +0 -1
- package/dist/client/internal/protocols/Protocol.js.map +0 -1
- package/dist/client/internal/protocols/Shared.d.ts.map +0 -1
- package/dist/client/internal/protocols/Shared.js.map +0 -1
- package/dist/client/internal/protocols/X402.d.ts.map +0 -1
- package/dist/client/internal/protocols/X402.js.map +0 -1
- package/dist/client/node.d.ts.map +0 -1
- package/dist/client/node.js.map +0 -1
- package/dist/discovery/Discovery.d.ts.map +0 -1
- package/dist/discovery/Discovery.js.map +0 -1
- package/dist/discovery/OpenApi.d.ts.map +0 -1
- package/dist/discovery/OpenApi.js.map +0 -1
- package/dist/discovery/Validate.d.ts.map +0 -1
- package/dist/discovery/Validate.js.map +0 -1
- package/dist/discovery/index.d.ts.map +0 -1
- package/dist/discovery/index.js.map +0 -1
- package/dist/evm/Assets.d.ts.map +0 -1
- package/dist/evm/Assets.js.map +0 -1
- package/dist/evm/Chains.d.ts.map +0 -1
- package/dist/evm/Chains.js.map +0 -1
- package/dist/evm/Methods.d.ts.map +0 -1
- package/dist/evm/Methods.js.map +0 -1
- package/dist/evm/Types.d.ts.map +0 -1
- package/dist/evm/Types.js.map +0 -1
- package/dist/evm/client/Charge.d.ts.map +0 -1
- package/dist/evm/client/Charge.js.map +0 -1
- package/dist/evm/client/Methods.d.ts.map +0 -1
- package/dist/evm/client/Methods.js.map +0 -1
- package/dist/evm/client/index.d.ts.map +0 -1
- package/dist/evm/client/index.js.map +0 -1
- package/dist/evm/index.d.ts.map +0 -1
- package/dist/evm/index.js.map +0 -1
- package/dist/evm/server/Charge.d.ts.map +0 -1
- package/dist/evm/server/Charge.js.map +0 -1
- package/dist/evm/server/Methods.d.ts.map +0 -1
- package/dist/evm/server/Methods.js.map +0 -1
- package/dist/evm/server/index.d.ts.map +0 -1
- package/dist/evm/server/index.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/internal/AcceptPayment.d.ts.map +0 -1
- package/dist/internal/AcceptPayment.js.map +0 -1
- package/dist/internal/HeaderCodec.d.ts.map +0 -1
- package/dist/internal/HeaderCodec.js.map +0 -1
- package/dist/internal/constantTimeEqual.d.ts.map +0 -1
- package/dist/internal/constantTimeEqual.js.map +0 -1
- package/dist/internal/env.d.ts.map +0 -1
- package/dist/internal/env.js.map +0 -1
- package/dist/internal/types.d.ts.map +0 -1
- package/dist/internal/types.js.map +0 -1
- package/dist/mcp/client/McpClient.d.ts.map +0 -1
- package/dist/mcp/client/McpClient.js.map +0 -1
- package/dist/mcp/client/index.d.ts.map +0 -1
- package/dist/mcp/client/index.js.map +0 -1
- package/dist/mcp/server/Transport.d.ts.map +0 -1
- package/dist/mcp/server/Transport.js.map +0 -1
- package/dist/mcp/server/index.d.ts.map +0 -1
- package/dist/mcp/server/index.js.map +0 -1
- package/dist/middlewares/elysia.d.ts.map +0 -1
- package/dist/middlewares/elysia.js.map +0 -1
- package/dist/middlewares/express.d.ts.map +0 -1
- package/dist/middlewares/express.js.map +0 -1
- package/dist/middlewares/hono.d.ts.map +0 -1
- package/dist/middlewares/hono.js.map +0 -1
- package/dist/middlewares/internal/mppx.d.ts.map +0 -1
- package/dist/middlewares/internal/mppx.js.map +0 -1
- package/dist/middlewares/nextjs.d.ts.map +0 -1
- package/dist/middlewares/nextjs.js.map +0 -1
- package/dist/proxy/Proxy.d.ts.map +0 -1
- package/dist/proxy/Proxy.js.map +0 -1
- package/dist/proxy/Service.d.ts.map +0 -1
- package/dist/proxy/Service.js.map +0 -1
- package/dist/proxy/index.d.ts.map +0 -1
- package/dist/proxy/index.js.map +0 -1
- package/dist/proxy/internal/Headers.d.ts.map +0 -1
- package/dist/proxy/internal/Headers.js.map +0 -1
- package/dist/proxy/internal/Route.d.ts.map +0 -1
- package/dist/proxy/internal/Route.js.map +0 -1
- package/dist/proxy/services/anthropic.d.ts.map +0 -1
- package/dist/proxy/services/anthropic.js.map +0 -1
- package/dist/proxy/services/openai.d.ts.map +0 -1
- package/dist/proxy/services/openai.js.map +0 -1
- package/dist/proxy/services/stripe.d.ts.map +0 -1
- package/dist/proxy/services/stripe.js.map +0 -1
- package/dist/server/Methods.d.ts.map +0 -1
- package/dist/server/Methods.js.map +0 -1
- package/dist/server/Mppx.d.ts.map +0 -1
- package/dist/server/Mppx.js.map +0 -1
- package/dist/server/NodeListener.d.ts.map +0 -1
- package/dist/server/NodeListener.js.map +0 -1
- package/dist/server/Request.d.ts.map +0 -1
- package/dist/server/Request.js.map +0 -1
- package/dist/server/Response.d.ts.map +0 -1
- package/dist/server/Response.js.map +0 -1
- package/dist/server/Transport.d.ts.map +0 -1
- package/dist/server/Transport.js.map +0 -1
- package/dist/server/index.d.ts.map +0 -1
- package/dist/server/index.js.map +0 -1
- package/dist/server/internal/html/compose.main.gen.d.ts.map +0 -1
- package/dist/server/internal/html/compose.main.gen.js.map +0 -1
- package/dist/server/internal/html/config.d.ts.map +0 -1
- package/dist/server/internal/html/config.js.map +0 -1
- package/dist/server/internal/html/constants.d.ts.map +0 -1
- package/dist/server/internal/html/constants.js.map +0 -1
- package/dist/server/internal/html/serviceWorker.client.d.ts.map +0 -1
- package/dist/server/internal/html/serviceWorker.client.js.map +0 -1
- package/dist/server/internal/html/serviceWorker.gen.d.ts.map +0 -1
- package/dist/server/internal/html/serviceWorker.gen.js.map +0 -1
- package/dist/server/internal/scope.d.ts.map +0 -1
- package/dist/server/internal/scope.js.map +0 -1
- package/dist/stripe/Methods.d.ts.map +0 -1
- package/dist/stripe/Methods.js.map +0 -1
- package/dist/stripe/client/Charge.d.ts.map +0 -1
- package/dist/stripe/client/Charge.js.map +0 -1
- package/dist/stripe/client/Methods.d.ts.map +0 -1
- package/dist/stripe/client/Methods.js.map +0 -1
- package/dist/stripe/client/index.d.ts.map +0 -1
- package/dist/stripe/client/index.js.map +0 -1
- package/dist/stripe/index.d.ts.map +0 -1
- package/dist/stripe/index.js.map +0 -1
- package/dist/stripe/internal/constants.d.ts.map +0 -1
- package/dist/stripe/internal/constants.js.map +0 -1
- package/dist/stripe/internal/types.d.ts.map +0 -1
- package/dist/stripe/internal/types.js.map +0 -1
- package/dist/stripe/server/Charge.d.ts.map +0 -1
- package/dist/stripe/server/Charge.js.map +0 -1
- package/dist/stripe/server/Methods.d.ts.map +0 -1
- package/dist/stripe/server/Methods.js.map +0 -1
- package/dist/stripe/server/index.d.ts.map +0 -1
- package/dist/stripe/server/index.js.map +0 -1
- package/dist/stripe/server/internal/html/types.d.ts.map +0 -1
- package/dist/stripe/server/internal/html/types.js.map +0 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +0 -1
- package/dist/stripe/server/internal/html.gen.js.map +0 -1
- package/dist/tempo/Attribution.d.ts.map +0 -1
- package/dist/tempo/Attribution.js.map +0 -1
- package/dist/tempo/Methods.d.ts.map +0 -1
- package/dist/tempo/Methods.js.map +0 -1
- package/dist/tempo/Proof.d.ts.map +0 -1
- package/dist/tempo/Proof.js.map +0 -1
- package/dist/tempo/client/Charge.d.ts.map +0 -1
- package/dist/tempo/client/Charge.js.map +0 -1
- package/dist/tempo/client/Methods.d.ts.map +0 -1
- package/dist/tempo/client/Methods.js.map +0 -1
- package/dist/tempo/client/ResolveAccount.d.ts.map +0 -1
- package/dist/tempo/client/ResolveAccount.js.map +0 -1
- package/dist/tempo/client/Subscription.d.ts.map +0 -1
- package/dist/tempo/client/Subscription.js.map +0 -1
- package/dist/tempo/client/index.d.ts.map +0 -1
- package/dist/tempo/client/index.js.map +0 -1
- package/dist/tempo/index.d.ts.map +0 -1
- package/dist/tempo/index.js.map +0 -1
- package/dist/tempo/internal/account.d.ts.map +0 -1
- package/dist/tempo/internal/account.js.map +0 -1
- package/dist/tempo/internal/address.d.ts.map +0 -1
- package/dist/tempo/internal/address.js.map +0 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +0 -1
- package/dist/tempo/internal/auto-swap.js.map +0 -1
- package/dist/tempo/internal/charge.d.ts.map +0 -1
- package/dist/tempo/internal/charge.js.map +0 -1
- package/dist/tempo/internal/defaults.d.ts.map +0 -1
- package/dist/tempo/internal/defaults.js.map +0 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +0 -1
- package/dist/tempo/internal/fee-payer.js.map +0 -1
- package/dist/tempo/internal/fee-token.d.ts.map +0 -1
- package/dist/tempo/internal/fee-token.js.map +0 -1
- package/dist/tempo/internal/proof.d.ts.map +0 -1
- package/dist/tempo/internal/proof.js.map +0 -1
- package/dist/tempo/internal/selectors.d.ts.map +0 -1
- package/dist/tempo/internal/selectors.js.map +0 -1
- package/dist/tempo/internal/types.d.ts.map +0 -1
- package/dist/tempo/internal/types.js.map +0 -1
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/legacy/client/ChannelOps.js.map +0 -1
- package/dist/tempo/legacy/client/Session.d.ts.map +0 -1
- package/dist/tempo/legacy/client/Session.js.map +0 -1
- package/dist/tempo/legacy/client/SessionManager.d.ts.map +0 -1
- package/dist/tempo/legacy/client/SessionManager.js.map +0 -1
- package/dist/tempo/legacy/client/index.d.ts.map +0 -1
- package/dist/tempo/legacy/client/index.js.map +0 -1
- package/dist/tempo/legacy/index.d.ts.map +0 -1
- package/dist/tempo/legacy/index.js.map +0 -1
- package/dist/tempo/legacy/server/Session.d.ts +0 -208
- package/dist/tempo/legacy/server/Session.d.ts.map +0 -1
- package/dist/tempo/legacy/server/Session.js +0 -698
- package/dist/tempo/legacy/server/Session.js.map +0 -1
- package/dist/tempo/legacy/server/index.d.ts +0 -5
- package/dist/tempo/legacy/server/index.d.ts.map +0 -1
- package/dist/tempo/legacy/server/index.js +0 -5
- package/dist/tempo/legacy/server/index.js.map +0 -1
- package/dist/tempo/legacy/session/Chain.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Chain.js.map +0 -1
- package/dist/tempo/legacy/session/Channel.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Channel.js.map +0 -1
- package/dist/tempo/legacy/session/ChannelStore.d.ts +0 -22
- package/dist/tempo/legacy/session/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/legacy/session/ChannelStore.js +0 -6
- package/dist/tempo/legacy/session/ChannelStore.js.map +0 -1
- package/dist/tempo/legacy/session/Types.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Types.js.map +0 -1
- package/dist/tempo/legacy/session/Voucher.d.ts.map +0 -1
- package/dist/tempo/legacy/session/Voucher.js.map +0 -1
- package/dist/tempo/legacy/session/escrow.abi.d.ts.map +0 -1
- package/dist/tempo/legacy/session/escrow.abi.js.map +0 -1
- package/dist/tempo/legacy/session/index.d.ts.map +0 -1
- package/dist/tempo/legacy/session/index.js.map +0 -1
- package/dist/tempo/server/Charge.d.ts.map +0 -1
- package/dist/tempo/server/Charge.js.map +0 -1
- package/dist/tempo/server/Methods.d.ts.map +0 -1
- package/dist/tempo/server/Methods.js.map +0 -1
- package/dist/tempo/server/Relay.d.ts.map +0 -1
- package/dist/tempo/server/Relay.js.map +0 -1
- package/dist/tempo/server/SponsorBudget.d.ts.map +0 -1
- package/dist/tempo/server/SponsorBudget.js.map +0 -1
- package/dist/tempo/server/Subscription.d.ts.map +0 -1
- package/dist/tempo/server/Subscription.js.map +0 -1
- package/dist/tempo/server/index.d.ts.map +0 -1
- package/dist/tempo/server/index.js.map +0 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +0 -1
- package/dist/tempo/server/internal/html.gen.js.map +0 -1
- package/dist/tempo/server/internal/request-body.d.ts.map +0 -1
- package/dist/tempo/server/internal/request-body.js.map +0 -1
- package/dist/tempo/server/internal/transport.d.ts.map +0 -1
- package/dist/tempo/server/internal/transport.js.map +0 -1
- package/dist/tempo/session/Snapshot.d.ts.map +0 -1
- package/dist/tempo/session/Snapshot.js.map +0 -1
- package/dist/tempo/session/client/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/session/client/ChannelOps.js.map +0 -1
- package/dist/tempo/session/client/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/session/client/ChannelStore.js.map +0 -1
- package/dist/tempo/session/client/CredentialState.d.ts.map +0 -1
- package/dist/tempo/session/client/CredentialState.js.map +0 -1
- package/dist/tempo/session/client/ReceiptCoordinator.d.ts.map +0 -1
- package/dist/tempo/session/client/ReceiptCoordinator.js.map +0 -1
- package/dist/tempo/session/client/Runtime.d.ts.map +0 -1
- package/dist/tempo/session/client/Runtime.js.map +0 -1
- package/dist/tempo/session/client/Session.d.ts.map +0 -1
- package/dist/tempo/session/client/Session.js.map +0 -1
- package/dist/tempo/session/client/SessionManager.d.ts.map +0 -1
- package/dist/tempo/session/client/SessionManager.js.map +0 -1
- package/dist/tempo/session/client/Transports.d.ts.map +0 -1
- package/dist/tempo/session/client/Transports.js.map +0 -1
- package/dist/tempo/session/client/index.d.ts.map +0 -1
- package/dist/tempo/session/client/index.js.map +0 -1
- package/dist/tempo/session/client/internal/SessionManager.d.ts.map +0 -1
- package/dist/tempo/session/client/internal/SessionManager.js.map +0 -1
- package/dist/tempo/session/index.d.ts.map +0 -1
- package/dist/tempo/session/index.js.map +0 -1
- package/dist/tempo/session/precompile/Chain.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Chain.js.map +0 -1
- package/dist/tempo/session/precompile/Channel.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Channel.js.map +0 -1
- package/dist/tempo/session/precompile/Protocol.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Protocol.js.map +0 -1
- package/dist/tempo/session/precompile/Voucher.d.ts.map +0 -1
- package/dist/tempo/session/precompile/Voucher.js.map +0 -1
- package/dist/tempo/session/precompile/escrow.abi.d.ts.map +0 -1
- package/dist/tempo/session/precompile/escrow.abi.js.map +0 -1
- package/dist/tempo/session/precompile/index.d.ts.map +0 -1
- package/dist/tempo/session/precompile/index.js.map +0 -1
- package/dist/tempo/session/server/ChannelOps.d.ts.map +0 -1
- package/dist/tempo/session/server/ChannelOps.js.map +0 -1
- package/dist/tempo/session/server/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/session/server/ChannelStore.js.map +0 -1
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +0 -1
- package/dist/tempo/session/server/CredentialVerification.js.map +0 -1
- package/dist/tempo/session/server/MeteredStream.d.ts.map +0 -1
- package/dist/tempo/session/server/MeteredStream.js.map +0 -1
- package/dist/tempo/session/server/RequestState.d.ts.map +0 -1
- package/dist/tempo/session/server/RequestState.js.map +0 -1
- package/dist/tempo/session/server/Session.d.ts.map +0 -1
- package/dist/tempo/session/server/Session.js.map +0 -1
- package/dist/tempo/session/server/Settlement.d.ts.map +0 -1
- package/dist/tempo/session/server/Settlement.js.map +0 -1
- package/dist/tempo/session/server/Sse.d.ts.map +0 -1
- package/dist/tempo/session/server/Sse.js.map +0 -1
- package/dist/tempo/session/server/Transports.d.ts.map +0 -1
- package/dist/tempo/session/server/Transports.js.map +0 -1
- package/dist/tempo/session/server/Ws.d.ts.map +0 -1
- package/dist/tempo/session/server/Ws.js.map +0 -1
- package/dist/tempo/session/server/index.d.ts.map +0 -1
- package/dist/tempo/session/server/index.js.map +0 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +0 -1
- package/dist/tempo/subscription/KeyAuthorization.js.map +0 -1
- package/dist/tempo/subscription/Receipt.d.ts.map +0 -1
- package/dist/tempo/subscription/Receipt.js.map +0 -1
- package/dist/tempo/subscription/Store.d.ts.map +0 -1
- package/dist/tempo/subscription/Store.js.map +0 -1
- package/dist/tempo/subscription/Types.d.ts.map +0 -1
- package/dist/tempo/subscription/Types.js.map +0 -1
- package/dist/tempo/subscription/index.d.ts.map +0 -1
- package/dist/tempo/subscription/index.js.map +0 -1
- package/dist/validation/core.d.ts.map +0 -1
- package/dist/validation/core.js.map +0 -1
- package/dist/validation/index.d.ts.map +0 -1
- package/dist/validation/index.js.map +0 -1
- package/dist/viem/Account.d.ts.map +0 -1
- package/dist/viem/Account.js.map +0 -1
- package/dist/viem/Client.d.ts.map +0 -1
- package/dist/viem/Client.js.map +0 -1
- package/dist/x402/Assets.d.ts.map +0 -1
- package/dist/x402/Assets.js.map +0 -1
- package/dist/x402/Header.d.ts.map +0 -1
- package/dist/x402/Header.js.map +0 -1
- package/dist/x402/Types.d.ts.map +0 -1
- package/dist/x402/Types.js.map +0 -1
- package/dist/x402/client/Exact.d.ts.map +0 -1
- package/dist/x402/client/Exact.js.map +0 -1
- package/dist/x402/index.d.ts.map +0 -1
- package/dist/x402/index.js.map +0 -1
- package/dist/x402/internal/ChallengeBrand.d.ts.map +0 -1
- package/dist/x402/internal/ChallengeBrand.js.map +0 -1
- package/dist/x402/internal/RouteBinding.d.ts.map +0 -1
- package/dist/x402/internal/RouteBinding.js.map +0 -1
- package/dist/x402/server/EvmCharge.d.ts.map +0 -1
- package/dist/x402/server/EvmCharge.js.map +0 -1
- package/dist/x402/server/Facilitator.d.ts.map +0 -1
- package/dist/x402/server/Facilitator.js.map +0 -1
- package/dist/zod.d.ts.map +0 -1
- package/dist/zod.js.map +0 -1
- package/src/BodyDigest.test.ts +0 -43
- package/src/BodyDigest.ts +0 -55
- package/src/Challenge.fuzz.test.ts +0 -121
- package/src/Challenge.test-d.ts +0 -82
- package/src/Challenge.test.ts +0 -995
- package/src/Challenge.ts +0 -683
- package/src/Constants.ts +0 -58
- package/src/Credential.fuzz.test.ts +0 -62
- package/src/Credential.test.ts +0 -378
- package/src/Credential.ts +0 -217
- package/src/Errors.test.ts +0 -520
- package/src/Errors.ts +0 -448
- package/src/Expires.test.ts +0 -112
- package/src/Expires.ts +0 -59
- package/src/Html.ts +0 -67
- package/src/Mcp.ts +0 -85
- package/src/Method.test.ts +0 -177
- package/src/Method.ts +0 -617
- package/src/PaymentRequest.test.ts +0 -147
- package/src/PaymentRequest.ts +0 -108
- package/src/Receipt.test.ts +0 -151
- package/src/Receipt.ts +0 -146
- package/src/Store.test-d.ts +0 -195
- package/src/Store.test.ts +0 -357
- package/src/Store.ts +0 -329
- package/src/bin.ts +0 -4
- package/src/cli/account.ts +0 -269
- package/src/cli/cli.test.ts +0 -2221
- package/src/cli/cli.ts +0 -1649
- package/src/cli/config.test.ts +0 -89
- package/src/cli/config.ts +0 -50
- package/src/cli/internal.ts +0 -114
- package/src/cli/mcp.test.ts +0 -262
- package/src/cli/plugins/evm.ts +0 -55
- package/src/cli/plugins/index.ts +0 -4
- package/src/cli/plugins/plugin.ts +0 -84
- package/src/cli/plugins/stripe.ts +0 -174
- package/src/cli/plugins/tempo.ts +0 -1102
- package/src/cli/sessions/Manager.test.ts +0 -249
- package/src/cli/sessions/Manager.ts +0 -93
- package/src/cli/sessions/commands.ts +0 -444
- package/src/cli/sessions/request.test.ts +0 -51
- package/src/cli/sessions/request.ts +0 -353
- package/src/cli/sessions/store.test.ts +0 -581
- package/src/cli/sessions/store.ts +0 -940
- package/src/cli/utils.test.ts +0 -156
- package/src/cli/utils.ts +0 -394
- package/src/cli/validate/challenge.ts +0 -565
- package/src/cli/validate/discovery.test.ts +0 -435
- package/src/cli/validate/discovery.ts +0 -221
- package/src/cli/validate/helpers.ts +0 -166
- package/src/cli/validate/index.ts +0 -229
- package/src/cli/validate/messages.ts +0 -7
- package/src/cli/validate/payment.ts +0 -774
- package/src/cli/validate.test.ts +0 -843
- package/src/client/Methods.ts +0 -8
- package/src/client/Mppx.test-d.ts +0 -270
- package/src/client/Mppx.test.ts +0 -1065
- package/src/client/Mppx.ts +0 -466
- package/src/client/Transport.test.ts +0 -619
- package/src/client/Transport.ts +0 -191
- package/src/client/index.ts +0 -29
- package/src/client/internal/Fetch.browser.test.ts +0 -197
- package/src/client/internal/Fetch.test-d.ts +0 -109
- package/src/client/internal/Fetch.test.ts +0 -2336
- package/src/client/internal/Fetch.ts +0 -1079
- package/src/client/internal/MethodChallenge.ts +0 -35
- package/src/client/internal/MethodResponse.ts +0 -67
- package/src/client/internal/protocols/Mcp.test.ts +0 -220
- package/src/client/internal/protocols/Mcp.ts +0 -186
- package/src/client/internal/protocols/Mpp.ts +0 -21
- package/src/client/internal/protocols/Protocol.ts +0 -10
- package/src/client/internal/protocols/Shared.ts +0 -25
- package/src/client/internal/protocols/X402.ts +0 -46
- package/src/client/node.test.ts +0 -115
- package/src/client/node.ts +0 -247
- package/src/discovery/Discovery.test.ts +0 -214
- package/src/discovery/Discovery.ts +0 -105
- package/src/discovery/OpenApi.test.ts +0 -453
- package/src/discovery/OpenApi.ts +0 -224
- package/src/discovery/Validate.test.ts +0 -305
- package/src/discovery/Validate.ts +0 -77
- package/src/discovery/index.ts +0 -3
- package/src/env.d.ts +0 -12
- package/src/evm/Assets.ts +0 -1
- package/src/evm/Chains.ts +0 -11
- package/src/evm/Methods.ts +0 -44
- package/src/evm/PublicInterface.test-d.ts +0 -134
- package/src/evm/Types.ts +0 -140
- package/src/evm/client/Charge.test.ts +0 -347
- package/src/evm/client/Charge.ts +0 -187
- package/src/evm/client/Methods.ts +0 -19
- package/src/evm/client/index.ts +0 -6
- package/src/evm/index.ts +0 -16
- package/src/evm/server/Charge.test.ts +0 -339
- package/src/evm/server/Charge.ts +0 -290
- package/src/evm/server/Methods.ts +0 -22
- package/src/evm/server/index.ts +0 -6
- package/src/index.ts +0 -14
- package/src/internal/AcceptPayment.test.ts +0 -343
- package/src/internal/AcceptPayment.ts +0 -366
- package/src/internal/HeaderCodec.ts +0 -36
- package/src/internal/changeset.test.ts +0 -106
- package/src/internal/constantTimeEqual.test.ts +0 -47
- package/src/internal/constantTimeEqual.ts +0 -10
- package/src/internal/env.test.ts +0 -42
- package/src/internal/env.ts +0 -49
- package/src/internal/types.test-d.ts +0 -21
- package/src/internal/types.ts +0 -470
- package/src/mcp/client/McpClient.integration.test.ts +0 -652
- package/src/mcp/client/McpClient.test-d.ts +0 -144
- package/src/mcp/client/McpClient.test.ts +0 -463
- package/src/mcp/client/McpClient.ts +0 -307
- package/src/mcp/client/McpClient.unit.test.ts +0 -135
- package/src/mcp/client/index.ts +0 -2
- package/src/mcp/server/Transport.test.ts +0 -191
- package/src/mcp/server/Transport.ts +0 -111
- package/src/mcp/server/index.ts +0 -1
- package/src/middlewares/elysia.test.ts +0 -295
- package/src/middlewares/elysia.ts +0 -116
- package/src/middlewares/express.test.ts +0 -371
- package/src/middlewares/express.ts +0 -140
- package/src/middlewares/hono.test.ts +0 -476
- package/src/middlewares/hono.ts +0 -153
- package/src/middlewares/internal/mppx.test.ts +0 -177
- package/src/middlewares/internal/mppx.ts +0 -67
- package/src/middlewares/nextjs.test.ts +0 -369
- package/src/middlewares/nextjs.ts +0 -107
- package/src/proxy/Proxy.test.ts +0 -1031
- package/src/proxy/Proxy.ts +0 -345
- package/src/proxy/Service.test.ts +0 -182
- package/src/proxy/Service.ts +0 -253
- package/src/proxy/index.ts +0 -6
- package/src/proxy/internal/Headers.test.ts +0 -139
- package/src/proxy/internal/Headers.ts +0 -66
- package/src/proxy/internal/Route.test.ts +0 -217
- package/src/proxy/internal/Route.ts +0 -64
- package/src/proxy/services/anthropic.test.ts +0 -132
- package/src/proxy/services/anthropic.ts +0 -52
- package/src/proxy/services/openai.test.ts +0 -157
- package/src/proxy/services/openai.ts +0 -59
- package/src/proxy/services/stripe.test.ts +0 -175
- package/src/proxy/services/stripe.ts +0 -58
- package/src/server/Methods.ts +0 -3
- package/src/server/Mppx.authorize.test.ts +0 -210
- package/src/server/Mppx.test-d.ts +0 -360
- package/src/server/Mppx.test.ts +0 -5813
- package/src/server/Mppx.ts +0 -2623
- package/src/server/NodeListener.test.ts +0 -293
- package/src/server/NodeListener.ts +0 -45
- package/src/server/Request.test.ts +0 -255
- package/src/server/Request.ts +0 -155
- package/src/server/Response.test.ts +0 -32
- package/src/server/Response.ts +0 -45
- package/src/server/Transport.test.ts +0 -598
- package/src/server/Transport.ts +0 -320
- package/src/server/index.ts +0 -9
- package/src/server/internal/html/compose.main.gen.ts +0 -2
- package/src/server/internal/html/compose.main.ts +0 -88
- package/src/server/internal/html/config.ts +0 -672
- package/src/server/internal/html/constants.ts +0 -28
- package/src/server/internal/html/serviceWorker.client.ts +0 -28
- package/src/server/internal/html/serviceWorker.gen.ts +0 -2
- package/src/server/internal/html/serviceWorker.ts +0 -27
- package/src/server/internal/html/tsconfig.compose.json +0 -8
- package/src/server/internal/html/tsconfig.worker.client.json +0 -8
- package/src/server/internal/html/tsconfig.worker.json +0 -8
- package/src/server/internal/scope.ts +0 -43
- package/src/stripe/Charge.integration.test.ts +0 -329
- package/src/stripe/Methods.test.ts +0 -62
- package/src/stripe/Methods.ts +0 -44
- package/src/stripe/client/Charge.test.ts +0 -205
- package/src/stripe/client/Charge.ts +0 -132
- package/src/stripe/client/Methods.ts +0 -37
- package/src/stripe/client/index.ts +0 -2
- package/src/stripe/index.ts +0 -1
- package/src/stripe/internal/constants.ts +0 -7
- package/src/stripe/internal/types.ts +0 -46
- package/src/stripe/server/Charge.test-d.ts +0 -66
- package/src/stripe/server/Charge.test.ts +0 -621
- package/src/stripe/server/Charge.ts +0 -434
- package/src/stripe/server/Methods.ts +0 -24
- package/src/stripe/server/index.ts +0 -2
- package/src/stripe/server/internal/html/main.ts +0 -165
- package/src/stripe/server/internal/html/node_modules/.bin/mppx +0 -22
- package/src/stripe/server/internal/html/node_modules/.bin/mppx.src +0 -22
- package/src/stripe/server/internal/html/package.json +0 -9
- package/src/stripe/server/internal/html/stripe-js-pure.d.ts +0 -7
- package/src/stripe/server/internal/html/tsconfig.json +0 -8
- package/src/stripe/server/internal/html/types.ts +0 -5
- package/src/stripe/server/internal/html.gen.ts +0 -2
- package/src/tempo/AccessKeyAuthorization.test.ts +0 -141
- package/src/tempo/Attribution.test.ts +0 -294
- package/src/tempo/Attribution.ts +0 -185
- package/src/tempo/Methods.test.ts +0 -425
- package/src/tempo/Methods.ts +0 -367
- package/src/tempo/Proof.conformance.test.ts +0 -146
- package/src/tempo/Proof.test-d.ts +0 -32
- package/src/tempo/Proof.test.ts +0 -40
- package/src/tempo/Proof.ts +0 -69
- package/src/tempo/PublicExports.test-d.ts +0 -130
- package/src/tempo/Subscription.integration.test.ts +0 -591
- package/src/tempo/client/Charge.test.ts +0 -501
- package/src/tempo/client/Charge.ts +0 -289
- package/src/tempo/client/Methods.ts +0 -45
- package/src/tempo/client/ResolveAccount.ts +0 -46
- package/src/tempo/client/Subscription.test.ts +0 -180
- package/src/tempo/client/Subscription.ts +0 -156
- package/src/tempo/client/index.ts +0 -17
- package/src/tempo/index.ts +0 -5
- package/src/tempo/internal/account.ts +0 -56
- package/src/tempo/internal/address.ts +0 -6
- package/src/tempo/internal/auto-swap.test.ts +0 -198
- package/src/tempo/internal/auto-swap.ts +0 -188
- package/src/tempo/internal/charge.test.ts +0 -66
- package/src/tempo/internal/charge.ts +0 -43
- package/src/tempo/internal/defaults.test.ts +0 -95
- package/src/tempo/internal/defaults.ts +0 -53
- package/src/tempo/internal/fee-payer.test.ts +0 -1177
- package/src/tempo/internal/fee-payer.ts +0 -808
- package/src/tempo/internal/fee-token.test.ts +0 -165
- package/src/tempo/internal/fee-token.ts +0 -72
- package/src/tempo/internal/proof.test.ts +0 -97
- package/src/tempo/internal/proof.ts +0 -87
- package/src/tempo/internal/selectors.ts +0 -10
- package/src/tempo/internal/types.ts +0 -19
- package/src/tempo/legacy/AccessKeyAuthorization.test.ts +0 -162
- package/src/tempo/legacy/README.md +0 -9
- package/src/tempo/legacy/client/ChannelOps.test.ts +0 -379
- package/src/tempo/legacy/client/ChannelOps.ts +0 -271
- package/src/tempo/legacy/client/Session.test.ts +0 -800
- package/src/tempo/legacy/client/Session.ts +0 -397
- package/src/tempo/legacy/client/SessionManager.test.ts +0 -468
- package/src/tempo/legacy/client/SessionManager.ts +0 -909
- package/src/tempo/legacy/client/index.ts +0 -6
- package/src/tempo/legacy/index.ts +0 -6
- package/src/tempo/legacy/server/Defaults.test-d.ts +0 -9
- package/src/tempo/legacy/server/Session.test.ts +0 -6370
- package/src/tempo/legacy/server/Session.ts +0 -1024
- package/src/tempo/legacy/server/index.ts +0 -4
- package/src/tempo/legacy/session/Chain.test.ts +0 -1349
- package/src/tempo/legacy/session/Chain.ts +0 -830
- package/src/tempo/legacy/session/Channel.test.ts +0 -109
- package/src/tempo/legacy/session/Channel.ts +0 -51
- package/src/tempo/legacy/session/ChannelStore.test.ts +0 -58
- package/src/tempo/legacy/session/ChannelStore.ts +0 -39
- package/src/tempo/legacy/session/Types.ts +0 -91
- package/src/tempo/legacy/session/Voucher.test.ts +0 -410
- package/src/tempo/legacy/session/Voucher.ts +0 -183
- package/src/tempo/legacy/session/escrow.abi.ts +0 -760
- package/src/tempo/legacy/session/index.ts +0 -8
- package/src/tempo/server/AtomicStore.test-d.ts +0 -50
- package/src/tempo/server/Charge.test.ts +0 -6121
- package/src/tempo/server/Charge.ts +0 -1351
- package/src/tempo/server/Methods.ts +0 -106
- package/src/tempo/server/Relay.test.ts +0 -677
- package/src/tempo/server/Relay.ts +0 -297
- package/src/tempo/server/SponsorBudget.test.ts +0 -154
- package/src/tempo/server/SponsorBudget.ts +0 -219
- package/src/tempo/server/Sse.test.ts +0 -321
- package/src/tempo/server/Subscription.test.ts +0 -1944
- package/src/tempo/server/Subscription.ts +0 -1333
- package/src/tempo/server/index.ts +0 -12
- package/src/tempo/server/internal/html/main.ts +0 -115
- package/src/tempo/server/internal/html/node_modules/.bin/mppx +0 -22
- package/src/tempo/server/internal/html/node_modules/.bin/mppx.src +0 -22
- package/src/tempo/server/internal/html/package.json +0 -10
- package/src/tempo/server/internal/html/tsconfig.json +0 -8
- package/src/tempo/server/internal/html.gen.ts +0 -2
- package/src/tempo/server/internal/request-body.test.ts +0 -210
- package/src/tempo/server/internal/request-body.ts +0 -61
- package/src/tempo/server/internal/transport.test.ts +0 -903
- package/src/tempo/server/internal/transport.ts +0 -310
- package/src/tempo/session/README.md +0 -208
- package/src/tempo/session/Snapshot.test.ts +0 -41
- package/src/tempo/session/Snapshot.ts +0 -95
- package/src/tempo/session/client/ChannelOps.test.ts +0 -218
- package/src/tempo/session/client/ChannelOps.ts +0 -348
- package/src/tempo/session/client/ChannelStore.ts +0 -111
- package/src/tempo/session/client/CredentialState.test.ts +0 -789
- package/src/tempo/session/client/CredentialState.ts +0 -942
- package/src/tempo/session/client/ReceiptCoordinator.ts +0 -95
- package/src/tempo/session/client/Runtime.test.ts +0 -1104
- package/src/tempo/session/client/Runtime.ts +0 -1008
- package/src/tempo/session/client/Session.test.ts +0 -1355
- package/src/tempo/session/client/Session.ts +0 -420
- package/src/tempo/session/client/SessionManager.test.ts +0 -1899
- package/src/tempo/session/client/SessionManager.ts +0 -996
- package/src/tempo/session/client/Transports.test.ts +0 -1141
- package/src/tempo/session/client/Transports.ts +0 -1534
- package/src/tempo/session/client/index.ts +0 -40
- package/src/tempo/session/client/internal/SessionManager.ts +0 -36
- package/src/tempo/session/index.ts +0 -7
- package/src/tempo/session/precompile/Chain.integration.test.ts +0 -339
- package/src/tempo/session/precompile/Chain.test.ts +0 -1641
- package/src/tempo/session/precompile/Chain.ts +0 -1149
- package/src/tempo/session/precompile/Channel.test.ts +0 -147
- package/src/tempo/session/precompile/Channel.ts +0 -108
- package/src/tempo/session/precompile/Protocol.test.ts +0 -358
- package/src/tempo/session/precompile/Protocol.ts +0 -520
- package/src/tempo/session/precompile/Voucher.test.ts +0 -354
- package/src/tempo/session/precompile/Voucher.ts +0 -162
- package/src/tempo/session/precompile/escrow.abi.ts +0 -226
- package/src/tempo/session/precompile/index.ts +0 -33
- package/src/tempo/session/server/ChannelOps.test.ts +0 -129
- package/src/tempo/session/server/ChannelOps.ts +0 -157
- package/src/tempo/session/server/ChannelStore.test.ts +0 -1093
- package/src/tempo/session/server/ChannelStore.ts +0 -835
- package/src/tempo/session/server/CredentialVerification.test.ts +0 -299
- package/src/tempo/session/server/CredentialVerification.ts +0 -775
- package/src/tempo/session/server/MeteredStream.ts +0 -95
- package/src/tempo/session/server/RequestState.test.ts +0 -574
- package/src/tempo/session/server/RequestState.ts +0 -507
- package/src/tempo/session/server/Session.integration.test.ts +0 -449
- package/src/tempo/session/server/Session.test.ts +0 -3862
- package/src/tempo/session/server/Session.ts +0 -570
- package/src/tempo/session/server/Settlement.test.ts +0 -345
- package/src/tempo/session/server/Settlement.ts +0 -527
- package/src/tempo/session/server/Sse.fuzz.test.ts +0 -138
- package/src/tempo/session/server/Sse.test.ts +0 -557
- package/src/tempo/session/server/Sse.ts +0 -254
- package/src/tempo/session/server/Transports.test.ts +0 -346
- package/src/tempo/session/server/Transports.ts +0 -255
- package/src/tempo/session/server/Ws.test.ts +0 -594
- package/src/tempo/session/server/Ws.ts +0 -385
- package/src/tempo/session/server/index.ts +0 -12
- package/src/tempo/subscription/KeyAuthorization.test.ts +0 -282
- package/src/tempo/subscription/KeyAuthorization.ts +0 -396
- package/src/tempo/subscription/Receipt.ts +0 -28
- package/src/tempo/subscription/Store.test.ts +0 -609
- package/src/tempo/subscription/Store.ts +0 -449
- package/src/tempo/subscription/Types.ts +0 -68
- package/src/tempo/subscription/index.ts +0 -23
- package/src/tsconfig.json +0 -10
- package/src/validation/core.ts +0 -386
- package/src/validation/index.ts +0 -11
- package/src/viem/Account.test.ts +0 -72
- package/src/viem/Account.ts +0 -30
- package/src/viem/Client.test.ts +0 -366
- package/src/viem/Client.ts +0 -93
- package/src/x402/Assets.test.ts +0 -253
- package/src/x402/Assets.ts +0 -221
- package/src/x402/Exact.e2e.test.ts +0 -448
- package/src/x402/Exact.localnet.test.ts +0 -475
- package/src/x402/Header.test.ts +0 -73
- package/src/x402/Header.ts +0 -79
- package/src/x402/PublicInterface.test-d.ts +0 -39
- package/src/x402/Types.ts +0 -248
- package/src/x402/client/Exact.test.ts +0 -309
- package/src/x402/client/Exact.ts +0 -186
- package/src/x402/index.ts +0 -6
- package/src/x402/internal/ChallengeBrand.ts +0 -14
- package/src/x402/internal/RouteBinding.ts +0 -18
- package/src/x402/server/EvmCharge.ts +0 -394
- package/src/x402/server/Facilitator.test.ts +0 -111
- package/src/x402/server/Facilitator.ts +0 -56
- package/src/zod.test.ts +0 -169
- package/src/zod.ts +0 -71
|
@@ -1,2336 +0,0 @@
|
|
|
1
|
-
import { Challenge, Credential, Errors, Mcp, Receipt } from 'mppx'
|
|
2
|
-
import { tempo } from 'mppx/client'
|
|
3
|
-
import { Mppx as Mppx_server, tempo as tempo_server } from 'mppx/server'
|
|
4
|
-
import { Header as x402_Header, Types as x402_Types, type PaymentRequired } from 'mppx/x402'
|
|
5
|
-
import { Base64 } from 'ox'
|
|
6
|
-
import { createClient, defineChain } from 'viem'
|
|
7
|
-
import { describe, expect, test, vi } from 'vp/test'
|
|
8
|
-
import * as Http from '~test/Http.js'
|
|
9
|
-
import { rpcUrl } from '~test/tempo/rpc.js'
|
|
10
|
-
import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
|
|
11
|
-
|
|
12
|
-
import * as Fetch from './Fetch.js'
|
|
13
|
-
import * as MethodChallenge from './MethodChallenge.js'
|
|
14
|
-
import * as MethodResponse from './MethodResponse.js'
|
|
15
|
-
|
|
16
|
-
const realm = 'api.example.com'
|
|
17
|
-
const secretKey = 'test-secret-key-test-secret-key-32'
|
|
18
|
-
|
|
19
|
-
const server = Mppx_server.create({
|
|
20
|
-
methods: [
|
|
21
|
-
tempo_server({
|
|
22
|
-
getClient: () => client,
|
|
23
|
-
account: accounts[0],
|
|
24
|
-
}),
|
|
25
|
-
],
|
|
26
|
-
realm,
|
|
27
|
-
secretKey,
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
describe('Fetch.from', () => {
|
|
31
|
-
test('default: account at creation', async () => {
|
|
32
|
-
const fetch = Fetch.from({
|
|
33
|
-
methods: [
|
|
34
|
-
tempo.charge({
|
|
35
|
-
account: accounts[1],
|
|
36
|
-
getClient: () => client,
|
|
37
|
-
}),
|
|
38
|
-
],
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
42
|
-
const result = await Mppx_server.toNodeListener(
|
|
43
|
-
server.charge({
|
|
44
|
-
amount: '1',
|
|
45
|
-
currency: asset,
|
|
46
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
47
|
-
recipient: accounts[0].address,
|
|
48
|
-
}),
|
|
49
|
-
)(req, res)
|
|
50
|
-
if (result.status === 402) return
|
|
51
|
-
res.end('OK')
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
const response = await fetch(httpServer.url)
|
|
55
|
-
expect(response.status).toBe(200)
|
|
56
|
-
|
|
57
|
-
const receipt = Receipt.fromResponse(response)
|
|
58
|
-
expect({
|
|
59
|
-
...receipt,
|
|
60
|
-
reference: '[reference]',
|
|
61
|
-
timestamp: '[timestamp]',
|
|
62
|
-
}).toMatchInlineSnapshot(`
|
|
63
|
-
{
|
|
64
|
-
"method": "tempo",
|
|
65
|
-
"reference": "[reference]",
|
|
66
|
-
"status": "success",
|
|
67
|
-
"timestamp": "[timestamp]",
|
|
68
|
-
}
|
|
69
|
-
`)
|
|
70
|
-
|
|
71
|
-
httpServer.close()
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
test('default: account via context', async () => {
|
|
75
|
-
const fetch = Fetch.from({
|
|
76
|
-
methods: [
|
|
77
|
-
tempo.charge({
|
|
78
|
-
getClient: () => client,
|
|
79
|
-
}),
|
|
80
|
-
],
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
84
|
-
const result = await Mppx_server.toNodeListener(
|
|
85
|
-
server.charge({
|
|
86
|
-
amount: '1',
|
|
87
|
-
currency: asset,
|
|
88
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
89
|
-
recipient: accounts[0].address,
|
|
90
|
-
}),
|
|
91
|
-
)(req, res)
|
|
92
|
-
if (result.status === 402) return
|
|
93
|
-
res.end('OK')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
const response = await fetch(httpServer.url, {
|
|
97
|
-
context: { account: accounts[1] },
|
|
98
|
-
})
|
|
99
|
-
expect(response.status).toBe(200)
|
|
100
|
-
|
|
101
|
-
const receipt = Receipt.fromResponse(response)
|
|
102
|
-
expect(receipt.status).toBe('success')
|
|
103
|
-
|
|
104
|
-
httpServer.close()
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
test('behavior: context overrides account at creation', async () => {
|
|
108
|
-
const fetch = Fetch.from({
|
|
109
|
-
methods: [
|
|
110
|
-
tempo.charge({
|
|
111
|
-
account: accounts[0],
|
|
112
|
-
getClient: () => client,
|
|
113
|
-
}),
|
|
114
|
-
],
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
118
|
-
const result = await Mppx_server.toNodeListener(
|
|
119
|
-
server.charge({
|
|
120
|
-
amount: '1',
|
|
121
|
-
currency: asset,
|
|
122
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
123
|
-
recipient: accounts[0].address,
|
|
124
|
-
}),
|
|
125
|
-
)(req, res)
|
|
126
|
-
if (result.status === 402) return
|
|
127
|
-
res.end('OK')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
const response = await fetch(httpServer.url, {
|
|
131
|
-
context: { account: accounts[1] },
|
|
132
|
-
})
|
|
133
|
-
expect(response.status).toBe(200)
|
|
134
|
-
|
|
135
|
-
httpServer.close()
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
test('behavior: throws when no account provided', async () => {
|
|
139
|
-
const fetch = Fetch.from({
|
|
140
|
-
methods: [
|
|
141
|
-
tempo.charge({
|
|
142
|
-
getClient: () => createClient({ chain, transport: http() }),
|
|
143
|
-
}),
|
|
144
|
-
],
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
148
|
-
const result = await Mppx_server.toNodeListener(
|
|
149
|
-
server.charge({
|
|
150
|
-
amount: '1',
|
|
151
|
-
currency: asset,
|
|
152
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
153
|
-
recipient: accounts[0].address,
|
|
154
|
-
}),
|
|
155
|
-
)(req, res)
|
|
156
|
-
if (result.status === 402) return
|
|
157
|
-
res.end('OK')
|
|
158
|
-
})
|
|
159
|
-
|
|
160
|
-
await expect(fetch(httpServer.url)).rejects.toThrow(
|
|
161
|
-
'No `account` provided. Pass `account` to parameters or context.',
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
httpServer.close()
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
test('behavior: passes through non-402 responses', async () => {
|
|
168
|
-
const fetch = Fetch.from({
|
|
169
|
-
methods: [
|
|
170
|
-
tempo.charge({
|
|
171
|
-
account: accounts[1],
|
|
172
|
-
getClient: () => client,
|
|
173
|
-
}),
|
|
174
|
-
],
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
const httpServer = await Http.createServer(async (_req, res) => {
|
|
178
|
-
res.writeHead(200)
|
|
179
|
-
res.end('OK')
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
const response = await fetch(httpServer.url)
|
|
183
|
-
expect(response.status).toBe(200)
|
|
184
|
-
expect(await response.text()).toBe('OK')
|
|
185
|
-
|
|
186
|
-
httpServer.close()
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
test('behavior: fee payer', async () => {
|
|
190
|
-
const serverWithFeePayer = Mppx_server.create({
|
|
191
|
-
methods: [
|
|
192
|
-
tempo_server.charge({
|
|
193
|
-
feePayer: accounts[0],
|
|
194
|
-
getClient: () => client,
|
|
195
|
-
}),
|
|
196
|
-
],
|
|
197
|
-
realm,
|
|
198
|
-
secretKey,
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
const fetch = Fetch.from({
|
|
202
|
-
methods: [
|
|
203
|
-
tempo.charge({
|
|
204
|
-
account: accounts[1],
|
|
205
|
-
getClient: () => client,
|
|
206
|
-
}),
|
|
207
|
-
],
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
211
|
-
const result = await Mppx_server.toNodeListener(
|
|
212
|
-
serverWithFeePayer.charge({
|
|
213
|
-
amount: '1',
|
|
214
|
-
currency: asset,
|
|
215
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
216
|
-
recipient: accounts[0].address,
|
|
217
|
-
}),
|
|
218
|
-
)(req, res)
|
|
219
|
-
if (result.status === 402) return
|
|
220
|
-
res.end('OK')
|
|
221
|
-
})
|
|
222
|
-
|
|
223
|
-
const response = await fetch(httpServer.url)
|
|
224
|
-
expect(response.status).toBe(200)
|
|
225
|
-
|
|
226
|
-
const receipt = Receipt.fromResponse(response)
|
|
227
|
-
expect({
|
|
228
|
-
...receipt,
|
|
229
|
-
reference: '[reference]',
|
|
230
|
-
timestamp: '[timestamp]',
|
|
231
|
-
}).toMatchInlineSnapshot(`
|
|
232
|
-
{
|
|
233
|
-
"method": "tempo",
|
|
234
|
-
"reference": "[reference]",
|
|
235
|
-
"status": "success",
|
|
236
|
-
"timestamp": "[timestamp]",
|
|
237
|
-
}
|
|
238
|
-
`)
|
|
239
|
-
|
|
240
|
-
httpServer.close()
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
test('behavior: fee payer with plain chain client (no Tempo serializers)', async () => {
|
|
244
|
-
const plainChain = defineChain({
|
|
245
|
-
id: chain.id,
|
|
246
|
-
name: chain.name,
|
|
247
|
-
nativeCurrency: chain.nativeCurrency,
|
|
248
|
-
rpcUrls: chain.rpcUrls,
|
|
249
|
-
})
|
|
250
|
-
const plainClient = createClient({
|
|
251
|
-
account: accounts[1],
|
|
252
|
-
chain: plainChain,
|
|
253
|
-
transport: http(rpcUrl),
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
const serverWithFeePayer = Mppx_server.create({
|
|
257
|
-
methods: [
|
|
258
|
-
tempo_server.charge({
|
|
259
|
-
feePayer: accounts[0],
|
|
260
|
-
getClient: () => client,
|
|
261
|
-
}),
|
|
262
|
-
],
|
|
263
|
-
realm,
|
|
264
|
-
secretKey,
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
const fetch = Fetch.from({
|
|
268
|
-
methods: [
|
|
269
|
-
tempo.charge({
|
|
270
|
-
account: accounts[1],
|
|
271
|
-
getClient: () => plainClient,
|
|
272
|
-
}),
|
|
273
|
-
],
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
277
|
-
const result = await Mppx_server.toNodeListener(
|
|
278
|
-
serverWithFeePayer.charge({
|
|
279
|
-
amount: '1',
|
|
280
|
-
currency: asset,
|
|
281
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
282
|
-
recipient: accounts[0].address,
|
|
283
|
-
}),
|
|
284
|
-
)(req, res)
|
|
285
|
-
if (result.status === 402) return
|
|
286
|
-
res.end('OK')
|
|
287
|
-
})
|
|
288
|
-
|
|
289
|
-
const response = await fetch(httpServer.url)
|
|
290
|
-
expect(response.status).toBe(200)
|
|
291
|
-
|
|
292
|
-
const receipt = Receipt.fromResponse(response)
|
|
293
|
-
expect(receipt.status).toBe('success')
|
|
294
|
-
|
|
295
|
-
httpServer.close()
|
|
296
|
-
})
|
|
297
|
-
|
|
298
|
-
test('behavior: onChallenge can create credential', async () => {
|
|
299
|
-
const onChallenge = vi.fn(async (_challenge, { createCredential }) =>
|
|
300
|
-
createCredential({ account: accounts[1] }),
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
const fetch = Fetch.from({
|
|
304
|
-
methods: [
|
|
305
|
-
tempo.charge({
|
|
306
|
-
getClient: () => client,
|
|
307
|
-
}),
|
|
308
|
-
],
|
|
309
|
-
onChallenge,
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
313
|
-
const result = await Mppx_server.toNodeListener(
|
|
314
|
-
server.charge({
|
|
315
|
-
amount: '1',
|
|
316
|
-
currency: asset,
|
|
317
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
318
|
-
recipient: accounts[0].address,
|
|
319
|
-
}),
|
|
320
|
-
)(req, res)
|
|
321
|
-
if (result.status === 402) return
|
|
322
|
-
res.end('OK')
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
const response = await fetch(httpServer.url)
|
|
326
|
-
expect(response.status).toBe(200)
|
|
327
|
-
expect(onChallenge).toHaveBeenCalledTimes(1)
|
|
328
|
-
|
|
329
|
-
httpServer.close()
|
|
330
|
-
})
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
// Minimal mock method — createCredential is only invoked on the 402 retry path.
|
|
334
|
-
const noopMethod = {
|
|
335
|
-
name: 'test',
|
|
336
|
-
intent: 'test',
|
|
337
|
-
context: undefined,
|
|
338
|
-
createCredential: async () => 'credential',
|
|
339
|
-
} as any
|
|
340
|
-
|
|
341
|
-
function trackedMethod(settlements: MethodResponse.AttemptOutcome['status'][]) {
|
|
342
|
-
const method = {
|
|
343
|
-
...noopMethod,
|
|
344
|
-
async createCredential(parameters: object) {
|
|
345
|
-
const attempt = MethodResponse.getAttempt(parameters)
|
|
346
|
-
if (attempt)
|
|
347
|
-
attempt.settle = (outcome) => {
|
|
348
|
-
settlements.push(outcome.status)
|
|
349
|
-
return outcome.status !== 'pending'
|
|
350
|
-
}
|
|
351
|
-
return 'credential'
|
|
352
|
-
},
|
|
353
|
-
}
|
|
354
|
-
MethodChallenge.register(method, () => undefined)
|
|
355
|
-
return MethodResponse.register(method, ({ response }) => response)
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const x402PaymentRequired = {
|
|
359
|
-
accepts: [
|
|
360
|
-
{
|
|
361
|
-
amount: '10000',
|
|
362
|
-
asset: '0x036CbD53842c5426634e7929541eC2318f3dCF7e',
|
|
363
|
-
maxTimeoutSeconds: 60,
|
|
364
|
-
network: 'eip155:84532',
|
|
365
|
-
payTo: '0x209693Bc6afc0C5328bA36FaF03C514EF312287C',
|
|
366
|
-
scheme: x402_Types.schemes[0],
|
|
367
|
-
},
|
|
368
|
-
],
|
|
369
|
-
resource: { url: 'https://example.com/api' },
|
|
370
|
-
x402Version: 2,
|
|
371
|
-
} satisfies PaymentRequired
|
|
372
|
-
|
|
373
|
-
/** Builds a valid 402 response with a WWW-Authenticate header. */
|
|
374
|
-
function make402(overrides?: { expires?: string; id?: string; intent?: string; method?: string }) {
|
|
375
|
-
const id = overrides?.id ?? 'abc'
|
|
376
|
-
const method = overrides?.method ?? 'test'
|
|
377
|
-
const intent = overrides?.intent ?? 'test'
|
|
378
|
-
const expires = overrides?.expires ? `, expires="${overrides.expires}"` : ''
|
|
379
|
-
const request = btoa(JSON.stringify({ amount: '1' }))
|
|
380
|
-
.replace(/\+/g, '-')
|
|
381
|
-
.replace(/\//g, '_')
|
|
382
|
-
.replace(/=+$/, '')
|
|
383
|
-
const header = `Payment id="${id}", realm="test", method="${method}", intent="${intent}", request="${request}"${expires}`
|
|
384
|
-
return new Response(null, {
|
|
385
|
-
status: 402,
|
|
386
|
-
headers: { 'WWW-Authenticate': header },
|
|
387
|
-
})
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
function makeCombined402(overrides?: {
|
|
391
|
-
expires?: string
|
|
392
|
-
id?: string
|
|
393
|
-
intent?: string
|
|
394
|
-
method?: string
|
|
395
|
-
}) {
|
|
396
|
-
const response = make402(overrides)
|
|
397
|
-
const headers = new Headers(response.headers)
|
|
398
|
-
headers.set(
|
|
399
|
-
x402_Types.paymentRequiredHeader,
|
|
400
|
-
x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
401
|
-
)
|
|
402
|
-
return new Response(null, {
|
|
403
|
-
status: 402,
|
|
404
|
-
headers,
|
|
405
|
-
})
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Strict x402 encoding rejects unsupported accepts before the transport can filter them.
|
|
409
|
-
function encodeRawPaymentRequiredHeader(value: unknown): string {
|
|
410
|
-
return Base64.fromString(JSON.stringify(value))
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
describe('Fetch.from: method responses', () => {
|
|
414
|
-
test('handles successful paid responses without touching free responses', async () => {
|
|
415
|
-
const method = { ...noopMethod }
|
|
416
|
-
const handle = vi.fn(
|
|
417
|
-
async ({ response }: MethodResponse.HandlerParameters) =>
|
|
418
|
-
new Response(`handled:${await response.text()}`, response),
|
|
419
|
-
)
|
|
420
|
-
MethodResponse.register(method, handle)
|
|
421
|
-
|
|
422
|
-
let paidResponse: Response | undefined
|
|
423
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
424
|
-
if (input.toString().endsWith('/free')) return new Response('free')
|
|
425
|
-
if (!new Headers(init?.headers).has('Authorization')) return make402()
|
|
426
|
-
paidResponse = new Response('paid')
|
|
427
|
-
vi.spyOn(paidResponse, 'clone')
|
|
428
|
-
return paidResponse
|
|
429
|
-
}
|
|
430
|
-
const fetch = Fetch.from({ fetch: mockFetch, methods: [method] })
|
|
431
|
-
|
|
432
|
-
expect(await (await fetch('https://example.com/free')).text()).toBe('free')
|
|
433
|
-
expect(handle).not.toHaveBeenCalled()
|
|
434
|
-
expect(await (await fetch('https://example.com/paid')).text()).toBe('handled:paid')
|
|
435
|
-
expect(handle).toHaveBeenCalledOnce()
|
|
436
|
-
expect(handle).toHaveBeenCalledWith(expect.objectContaining({ credential: 'credential' }))
|
|
437
|
-
expect(paidResponse?.clone).not.toHaveBeenCalled()
|
|
438
|
-
})
|
|
439
|
-
|
|
440
|
-
test.each([
|
|
441
|
-
['a rejected paid response', () => new Response('rejected', { status: 500 }), 500, undefined],
|
|
442
|
-
['the final payment retry', () => make402(), 402, 1],
|
|
443
|
-
[
|
|
444
|
-
'the paid request throwing',
|
|
445
|
-
() => Promise.reject(new Error('paid request failed')),
|
|
446
|
-
undefined,
|
|
447
|
-
undefined,
|
|
448
|
-
],
|
|
449
|
-
])('settles credential state after %s', async (_name, paidRequest, status, maxPaymentRetries) => {
|
|
450
|
-
const settlements: MethodResponse.AttemptOutcome['status'][] = []
|
|
451
|
-
let calls = 0
|
|
452
|
-
const fetch = Fetch.from({
|
|
453
|
-
fetch: async () => (++calls === 1 ? make402() : paidRequest()),
|
|
454
|
-
maxPaymentRetries,
|
|
455
|
-
methods: [trackedMethod(settlements)],
|
|
456
|
-
})
|
|
457
|
-
|
|
458
|
-
const result = fetch('https://example.com/paid')
|
|
459
|
-
if (status) expect((await result).status).toBe(status)
|
|
460
|
-
else await expect(result).rejects.toThrow('paid request failed')
|
|
461
|
-
expect(settlements).toEqual(['rejected'])
|
|
462
|
-
})
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
describe('Fetch.from: init passthrough (non-402)', () => {
|
|
466
|
-
test('preserves init object identity while adding Accept-Payment', async () => {
|
|
467
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
468
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
469
|
-
receivedInits.push(init)
|
|
470
|
-
return new Response('OK', { status: 200 })
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
const fetch = Fetch.from({
|
|
474
|
-
fetch: mockFetch,
|
|
475
|
-
methods: [noopMethod],
|
|
476
|
-
})
|
|
477
|
-
|
|
478
|
-
const customInit = {
|
|
479
|
-
method: 'POST',
|
|
480
|
-
headers: { 'X-Custom': 'value' },
|
|
481
|
-
body: JSON.stringify({ data: 'test' }),
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
await fetch('https://example.com/ws-upgrade', customInit)
|
|
485
|
-
|
|
486
|
-
expect(receivedInits[0]).toBe(customInit)
|
|
487
|
-
const headers = new Headers(receivedInits[0]?.headers)
|
|
488
|
-
expect(headers.get('X-Custom')).toBe('value')
|
|
489
|
-
expect(headers.get('Accept-Payment')).toBe('test/test')
|
|
490
|
-
})
|
|
491
|
-
|
|
492
|
-
test('preserves extra properties on init for non-402 responses', async () => {
|
|
493
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
494
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
495
|
-
receivedInits.push(init)
|
|
496
|
-
return new Response('OK', { status: 200 })
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
const fetch = Fetch.from({
|
|
500
|
-
fetch: mockFetch,
|
|
501
|
-
methods: [noopMethod],
|
|
502
|
-
})
|
|
503
|
-
|
|
504
|
-
const customInit = {
|
|
505
|
-
method: 'GET',
|
|
506
|
-
headers: { Authorization: 'Bearer token123' },
|
|
507
|
-
signal: AbortSignal.timeout(5000),
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
await fetch('https://example.com/api', customInit)
|
|
511
|
-
|
|
512
|
-
const received = receivedInits[0]!
|
|
513
|
-
expect(received.method).toBe('GET')
|
|
514
|
-
expect(new Headers(received.headers).get('Authorization')).toBe('Bearer token123')
|
|
515
|
-
expect(new Headers(received.headers).get('Accept-Payment')).toBe('test/test')
|
|
516
|
-
expect(received.signal).toBe(customInit.signal)
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
test('passes through undefined init', async () => {
|
|
520
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
521
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
522
|
-
receivedInits.push(init)
|
|
523
|
-
return new Response('OK', { status: 200 })
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
const fetch = Fetch.from({
|
|
527
|
-
fetch: mockFetch,
|
|
528
|
-
methods: [noopMethod],
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
await fetch('https://example.com/api')
|
|
532
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
533
|
-
})
|
|
534
|
-
|
|
535
|
-
test('passes init with context through untouched', async () => {
|
|
536
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
537
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
538
|
-
receivedInits.push(init)
|
|
539
|
-
return new Response('OK', { status: 200 })
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
const fetch = Fetch.from({
|
|
543
|
-
fetch: mockFetch,
|
|
544
|
-
methods: [noopMethod],
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
const customInit = { method: 'POST', context: { account: '0xabc' } }
|
|
548
|
-
await fetch('https://example.com/api', customInit as any)
|
|
549
|
-
|
|
550
|
-
expect(receivedInits[0]).toBe(customInit)
|
|
551
|
-
expect((receivedInits[0] as Record<string, unknown>).context).toEqual({ account: '0xabc' })
|
|
552
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
553
|
-
})
|
|
554
|
-
|
|
555
|
-
test('preserves Request-carried headers when injecting Accept-Payment', async () => {
|
|
556
|
-
const receivedInputs: (RequestInfo | URL)[] = []
|
|
557
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
558
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
559
|
-
receivedInputs.push(input)
|
|
560
|
-
receivedInits.push(init)
|
|
561
|
-
return new Response('OK', { status: 200 })
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
const fetch = Fetch.from({
|
|
565
|
-
fetch: mockFetch,
|
|
566
|
-
methods: [noopMethod],
|
|
567
|
-
})
|
|
568
|
-
|
|
569
|
-
const request = new Request('https://example.com/api', {
|
|
570
|
-
headers: { Authorization: 'Bearer token123', 'X-Custom': 'value' },
|
|
571
|
-
})
|
|
572
|
-
|
|
573
|
-
await fetch(request)
|
|
574
|
-
|
|
575
|
-
expect(receivedInputs[0]).toBe(request)
|
|
576
|
-
const headers = new Headers(receivedInits[0]?.headers)
|
|
577
|
-
expect(headers.get('Authorization')).toBe('Bearer token123')
|
|
578
|
-
expect(headers.get('X-Custom')).toBe('value')
|
|
579
|
-
expect(headers.get('Accept-Payment')).toBe('test/test')
|
|
580
|
-
})
|
|
581
|
-
|
|
582
|
-
test('does not overwrite an explicit Accept-Payment header', async () => {
|
|
583
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
584
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
585
|
-
receivedInits.push(init)
|
|
586
|
-
return new Response('OK', { status: 200 })
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
const fetch = Fetch.from({
|
|
590
|
-
fetch: mockFetch,
|
|
591
|
-
methods: [noopMethod],
|
|
592
|
-
})
|
|
593
|
-
|
|
594
|
-
await fetch('https://example.com/api', {
|
|
595
|
-
headers: { 'Accept-Payment': 'custom/charge;q=0.5' },
|
|
596
|
-
})
|
|
597
|
-
|
|
598
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('custom/charge;q=0.5')
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
test('uses an explicit Accept-Payment header to reprioritize challenge selection', async () => {
|
|
602
|
-
let callCount = 0
|
|
603
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
604
|
-
callCount++
|
|
605
|
-
if (callCount === 1) {
|
|
606
|
-
expect(new Headers(init?.headers).get('Accept-Payment')).toBe(
|
|
607
|
-
'stripe/charge, tempo/charge;q=0.1',
|
|
608
|
-
)
|
|
609
|
-
|
|
610
|
-
return new Response(null, {
|
|
611
|
-
status: 402,
|
|
612
|
-
headers: {
|
|
613
|
-
'WWW-Authenticate': [
|
|
614
|
-
'Payment id="tempo", realm="test", method="tempo", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
615
|
-
'Payment id="stripe", realm="test", method="stripe", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
616
|
-
].join(', '),
|
|
617
|
-
},
|
|
618
|
-
})
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('stripe-credential')
|
|
622
|
-
return new Response('OK', { status: 200 })
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
const fetch = Fetch.from({
|
|
626
|
-
fetch: mockFetch,
|
|
627
|
-
methods: [
|
|
628
|
-
{
|
|
629
|
-
name: 'tempo',
|
|
630
|
-
intent: 'charge',
|
|
631
|
-
context: undefined,
|
|
632
|
-
createCredential: async () => 'tempo-credential',
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
name: 'stripe',
|
|
636
|
-
intent: 'charge',
|
|
637
|
-
context: undefined,
|
|
638
|
-
createCredential: async () => 'stripe-credential',
|
|
639
|
-
},
|
|
640
|
-
] as const,
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
const response = await fetch('https://example.com/api', {
|
|
644
|
-
headers: { 'Accept-Payment': 'stripe/charge, tempo/charge;q=0.1' },
|
|
645
|
-
})
|
|
646
|
-
expect(response.status).toBe(200)
|
|
647
|
-
})
|
|
648
|
-
|
|
649
|
-
test('applies an explicit method opt-out before broader wildcard preferences', async () => {
|
|
650
|
-
let callCount = 0
|
|
651
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
652
|
-
callCount++
|
|
653
|
-
if (callCount === 1) {
|
|
654
|
-
expect(new Headers(init?.headers).get('Accept-Payment')).toBe(
|
|
655
|
-
'tempo/*;q=1, tempo/charge;q=0, stripe/*;q=0.5',
|
|
656
|
-
)
|
|
657
|
-
|
|
658
|
-
return new Response(null, {
|
|
659
|
-
status: 402,
|
|
660
|
-
headers: {
|
|
661
|
-
'WWW-Authenticate': [
|
|
662
|
-
'Payment id="tempo", realm="test", method="tempo", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
663
|
-
'Payment id="stripe", realm="test", method="stripe", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
664
|
-
].join(', '),
|
|
665
|
-
},
|
|
666
|
-
})
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('stripe-credential')
|
|
670
|
-
return new Response('OK', { status: 200 })
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
const fetch = Fetch.from({
|
|
674
|
-
fetch: mockFetch,
|
|
675
|
-
methods: [
|
|
676
|
-
{
|
|
677
|
-
name: 'tempo',
|
|
678
|
-
intent: 'charge',
|
|
679
|
-
context: undefined,
|
|
680
|
-
createCredential: async () => 'tempo-credential',
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
name: 'stripe',
|
|
684
|
-
intent: 'charge',
|
|
685
|
-
context: undefined,
|
|
686
|
-
createCredential: async () => 'stripe-credential',
|
|
687
|
-
},
|
|
688
|
-
] as const,
|
|
689
|
-
})
|
|
690
|
-
|
|
691
|
-
const response = await fetch('https://example.com/api', {
|
|
692
|
-
headers: { 'Accept-Payment': 'tempo/*;q=1, tempo/charge;q=0, stripe/*;q=0.5' },
|
|
693
|
-
})
|
|
694
|
-
expect(response.status).toBe(200)
|
|
695
|
-
})
|
|
696
|
-
|
|
697
|
-
test('preserves non-header init fields across all non-402 status codes', async () => {
|
|
698
|
-
for (const status of [200, 201, 204, 301, 400, 401, 403, 404, 500, 503]) {
|
|
699
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
700
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
701
|
-
receivedInits.push(init)
|
|
702
|
-
return new Response(null, { status })
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
const fetch = Fetch.from({
|
|
706
|
-
fetch: mockFetch,
|
|
707
|
-
methods: [noopMethod],
|
|
708
|
-
})
|
|
709
|
-
|
|
710
|
-
const customInit = { method: 'GET' }
|
|
711
|
-
await fetch('https://example.com/api', customInit)
|
|
712
|
-
expect(receivedInits[0]?.method).toBe(customInit.method)
|
|
713
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
714
|
-
}
|
|
715
|
-
})
|
|
716
|
-
})
|
|
717
|
-
|
|
718
|
-
describe('Fetch.from: 402 retry path', () => {
|
|
719
|
-
test('rejects expired challenges before hooks or credential creation', async () => {
|
|
720
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
721
|
-
const onChallenge = vi.fn(async () => undefined)
|
|
722
|
-
const mockFetch = vi.fn(async () =>
|
|
723
|
-
make402({ expires: new Date(Date.now() - 60_000).toISOString() }),
|
|
724
|
-
)
|
|
725
|
-
const fetch = Fetch.from({
|
|
726
|
-
fetch: mockFetch as typeof globalThis.fetch,
|
|
727
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
728
|
-
onChallenge,
|
|
729
|
-
})
|
|
730
|
-
|
|
731
|
-
await expect(fetch('https://example.com/api')).rejects.toThrow(Errors.PaymentExpiredError)
|
|
732
|
-
expect(onChallenge).not.toHaveBeenCalled()
|
|
733
|
-
expect(createCredential).not.toHaveBeenCalled()
|
|
734
|
-
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
735
|
-
})
|
|
736
|
-
|
|
737
|
-
test('strips context from init on retry', async () => {
|
|
738
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
739
|
-
let callCount = 0
|
|
740
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
741
|
-
calls.push({ init })
|
|
742
|
-
callCount++
|
|
743
|
-
if (callCount === 1) return make402()
|
|
744
|
-
return new Response('OK', { status: 200 })
|
|
745
|
-
}
|
|
746
|
-
|
|
747
|
-
const fetch = Fetch.from({
|
|
748
|
-
fetch: mockFetch,
|
|
749
|
-
methods: [noopMethod],
|
|
750
|
-
})
|
|
751
|
-
|
|
752
|
-
await fetch('https://example.com/api', {
|
|
753
|
-
method: 'POST',
|
|
754
|
-
context: { account: '0xabc' },
|
|
755
|
-
} as any)
|
|
756
|
-
|
|
757
|
-
expect(calls).toHaveLength(2)
|
|
758
|
-
const retryInit = calls[1]!.init as Record<string, unknown>
|
|
759
|
-
expect(retryInit).not.toHaveProperty('context')
|
|
760
|
-
})
|
|
761
|
-
|
|
762
|
-
test('adds Authorization header on retry', async () => {
|
|
763
|
-
let callCount = 0
|
|
764
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
765
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
766
|
-
calls.push({ init })
|
|
767
|
-
callCount++
|
|
768
|
-
if (callCount === 1) return make402()
|
|
769
|
-
return new Response('OK', { status: 200 })
|
|
770
|
-
}
|
|
771
|
-
|
|
772
|
-
const fetch = Fetch.from({
|
|
773
|
-
fetch: mockFetch,
|
|
774
|
-
methods: [noopMethod],
|
|
775
|
-
})
|
|
776
|
-
|
|
777
|
-
await fetch('https://example.com/api')
|
|
778
|
-
|
|
779
|
-
const retryInit = calls[1]!.init as Record<string, unknown>
|
|
780
|
-
const headers = new Headers(retryInit.headers as HeadersInit)
|
|
781
|
-
expect(headers.get('Authorization')).toBe('credential')
|
|
782
|
-
})
|
|
783
|
-
|
|
784
|
-
test('chooses native Payment-auth from combined MPP and x402 HTTP 402 offers', async () => {
|
|
785
|
-
let callCount = 0
|
|
786
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
787
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
788
|
-
calls.push({ init })
|
|
789
|
-
callCount++
|
|
790
|
-
if (callCount === 1) return makeCombined402()
|
|
791
|
-
return new Response('OK', { status: 200 })
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const fetch = Fetch.from({
|
|
795
|
-
fetch: mockFetch,
|
|
796
|
-
methods: [noopMethod],
|
|
797
|
-
})
|
|
798
|
-
|
|
799
|
-
const response = await fetch('https://example.com/api')
|
|
800
|
-
|
|
801
|
-
expect(response.status).toBe(200)
|
|
802
|
-
expect(calls).toHaveLength(2)
|
|
803
|
-
const retryHeaders = new Headers(calls[1]!.init?.headers)
|
|
804
|
-
expect(retryHeaders.get('Authorization')).toBe('credential')
|
|
805
|
-
expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBeNull()
|
|
806
|
-
})
|
|
807
|
-
|
|
808
|
-
test('prefers mpp Payment-auth over x402 when both are signable', async () => {
|
|
809
|
-
let callCount = 0
|
|
810
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
811
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
812
|
-
calls.push({ init })
|
|
813
|
-
callCount++
|
|
814
|
-
if (callCount === 1) return makeCombined402({ method: 'tempo', intent: 'charge' })
|
|
815
|
-
return new Response('OK', { status: 200 })
|
|
816
|
-
}
|
|
817
|
-
|
|
818
|
-
const fetch = Fetch.from({
|
|
819
|
-
fetch: mockFetch,
|
|
820
|
-
methods: [
|
|
821
|
-
{
|
|
822
|
-
name: 'tempo',
|
|
823
|
-
intent: 'charge',
|
|
824
|
-
context: undefined,
|
|
825
|
-
createCredential: async () => 'tempo-credential',
|
|
826
|
-
},
|
|
827
|
-
{
|
|
828
|
-
name: 'evm',
|
|
829
|
-
intent: 'charge',
|
|
830
|
-
context: undefined,
|
|
831
|
-
createCredential: async () => 'evm-credential',
|
|
832
|
-
},
|
|
833
|
-
] as const,
|
|
834
|
-
})
|
|
835
|
-
|
|
836
|
-
const response = await fetch('https://example.com/api')
|
|
837
|
-
|
|
838
|
-
expect(response.status).toBe(200)
|
|
839
|
-
const retryHeaders = new Headers(calls[1]!.init?.headers)
|
|
840
|
-
expect(retryHeaders.get('Authorization')).toBe('tempo-credential')
|
|
841
|
-
expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBeNull()
|
|
842
|
-
})
|
|
843
|
-
|
|
844
|
-
test('prefers native EVM Payment-auth over x402 for EVM-only clients', async () => {
|
|
845
|
-
let callCount = 0
|
|
846
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
847
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
848
|
-
calls.push({ init })
|
|
849
|
-
callCount++
|
|
850
|
-
if (callCount === 1) return makeCombined402({ method: 'evm', intent: 'charge' })
|
|
851
|
-
return new Response('OK', { status: 200 })
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
const fetch = Fetch.from({
|
|
855
|
-
fetch: mockFetch,
|
|
856
|
-
methods: [
|
|
857
|
-
{
|
|
858
|
-
name: 'evm',
|
|
859
|
-
intent: 'charge',
|
|
860
|
-
context: undefined,
|
|
861
|
-
createCredential: async () => 'evm-credential',
|
|
862
|
-
},
|
|
863
|
-
] as const,
|
|
864
|
-
})
|
|
865
|
-
|
|
866
|
-
const response = await fetch('https://example.com/api')
|
|
867
|
-
|
|
868
|
-
expect(response.status).toBe(200)
|
|
869
|
-
const retryHeaders = new Headers(calls[1]!.init?.headers)
|
|
870
|
-
expect(retryHeaders.get('Authorization')).toBe('evm-credential')
|
|
871
|
-
expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBeNull()
|
|
872
|
-
})
|
|
873
|
-
|
|
874
|
-
test('uses x402 when no signable Payment-auth challenge is available', async () => {
|
|
875
|
-
let callCount = 0
|
|
876
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
877
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
878
|
-
calls.push({ init })
|
|
879
|
-
callCount++
|
|
880
|
-
if (callCount === 1)
|
|
881
|
-
return new Response(null, {
|
|
882
|
-
status: 402,
|
|
883
|
-
headers: {
|
|
884
|
-
[x402_Types.paymentRequiredHeader]:
|
|
885
|
-
x402_Header.encodePaymentRequired(x402PaymentRequired),
|
|
886
|
-
},
|
|
887
|
-
})
|
|
888
|
-
return new Response('OK', { status: 200 })
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
const fetch = Fetch.from({
|
|
892
|
-
fetch: mockFetch,
|
|
893
|
-
methods: [
|
|
894
|
-
{
|
|
895
|
-
name: 'evm',
|
|
896
|
-
intent: 'charge',
|
|
897
|
-
context: undefined,
|
|
898
|
-
createCredential: async () => 'x402-credential',
|
|
899
|
-
},
|
|
900
|
-
] as const,
|
|
901
|
-
})
|
|
902
|
-
|
|
903
|
-
const response = await fetch('https://example.com/api')
|
|
904
|
-
|
|
905
|
-
expect(response.status).toBe(200)
|
|
906
|
-
const retryHeaders = new Headers(calls[1]!.init?.headers)
|
|
907
|
-
expect(retryHeaders.get('Authorization')).toBeNull()
|
|
908
|
-
expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBe('x402-credential')
|
|
909
|
-
})
|
|
910
|
-
|
|
911
|
-
test('lets orderChallenges force x402 before native Payment-auth', async () => {
|
|
912
|
-
let callCount = 0
|
|
913
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
914
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
915
|
-
calls.push({ init })
|
|
916
|
-
callCount++
|
|
917
|
-
if (callCount === 1) return makeCombined402({ method: 'evm', intent: 'charge' })
|
|
918
|
-
return new Response('OK', { status: 200 })
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
const fetch = Fetch.from({
|
|
922
|
-
fetch: mockFetch,
|
|
923
|
-
methods: [
|
|
924
|
-
{
|
|
925
|
-
name: 'evm',
|
|
926
|
-
intent: 'charge',
|
|
927
|
-
context: undefined,
|
|
928
|
-
createCredential: async () => 'x402-credential',
|
|
929
|
-
},
|
|
930
|
-
] as const,
|
|
931
|
-
orderChallenges: (candidates) =>
|
|
932
|
-
[...candidates].sort((left, right) => right.challenge.id.localeCompare(left.challenge.id)),
|
|
933
|
-
})
|
|
934
|
-
|
|
935
|
-
const response = await fetch('https://example.com/api')
|
|
936
|
-
|
|
937
|
-
expect(response.status).toBe(200)
|
|
938
|
-
const retryHeaders = new Headers(calls[1]!.init?.headers)
|
|
939
|
-
expect(retryHeaders.get('Authorization')).toBeNull()
|
|
940
|
-
expect(retryHeaders.get(x402_Types.paymentSignatureHeader)).toBe('x402-credential')
|
|
941
|
-
})
|
|
942
|
-
|
|
943
|
-
test('settles MCP-over-HTTP JSON-RPC payment challenges at the fetch boundary', async () => {
|
|
944
|
-
const mcpChallenge = Challenge.from({
|
|
945
|
-
id: 'mcp-challenge',
|
|
946
|
-
intent: 'test',
|
|
947
|
-
method: 'test',
|
|
948
|
-
realm: 'test',
|
|
949
|
-
request: { amount: '1' },
|
|
950
|
-
})
|
|
951
|
-
const method = {
|
|
952
|
-
...noopMethod,
|
|
953
|
-
createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
|
|
954
|
-
Credential.serialize({ challenge, payload: { source: 'mcp' } }),
|
|
955
|
-
}
|
|
956
|
-
const initialBody = JSON.stringify({
|
|
957
|
-
jsonrpc: '2.0',
|
|
958
|
-
id: 1,
|
|
959
|
-
method: 'tools/call',
|
|
960
|
-
params: { name: 'paid-tool' },
|
|
961
|
-
})
|
|
962
|
-
let callCount = 0
|
|
963
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
964
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
965
|
-
calls.push({ init })
|
|
966
|
-
callCount++
|
|
967
|
-
if (callCount === 1)
|
|
968
|
-
return Response.json({
|
|
969
|
-
jsonrpc: '2.0',
|
|
970
|
-
id: 1,
|
|
971
|
-
error: {
|
|
972
|
-
code: Mcp.paymentRequiredCode,
|
|
973
|
-
message: 'Payment Required',
|
|
974
|
-
data: { challenges: [mcpChallenge] },
|
|
975
|
-
},
|
|
976
|
-
})
|
|
977
|
-
|
|
978
|
-
const body = JSON.parse(init?.body as string)
|
|
979
|
-
expect(new Headers(init?.headers).get('Authorization')).toBeNull()
|
|
980
|
-
expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
|
|
981
|
-
payload: { source: 'mcp' },
|
|
982
|
-
})
|
|
983
|
-
return Response.json({ ok: true })
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
const fetch = Fetch.from({
|
|
987
|
-
fetch: mockFetch,
|
|
988
|
-
methods: [method],
|
|
989
|
-
})
|
|
990
|
-
|
|
991
|
-
const response = await fetch('https://example.com/mcp', {
|
|
992
|
-
method: 'POST',
|
|
993
|
-
headers: { accept: 'application/json, text/event-stream' },
|
|
994
|
-
body: initialBody,
|
|
995
|
-
})
|
|
996
|
-
|
|
997
|
-
expect(response.status).toBe(200)
|
|
998
|
-
expect(calls).toHaveLength(2)
|
|
999
|
-
})
|
|
1000
|
-
|
|
1001
|
-
test('settles MCP-over-HTTP result metadata payment challenges at the fetch boundary', async () => {
|
|
1002
|
-
const mcpChallenge = Challenge.from({
|
|
1003
|
-
id: 'mcp-result-challenge',
|
|
1004
|
-
intent: 'test',
|
|
1005
|
-
method: 'test',
|
|
1006
|
-
realm: 'test',
|
|
1007
|
-
request: { amount: '1' },
|
|
1008
|
-
})
|
|
1009
|
-
const method = {
|
|
1010
|
-
...noopMethod,
|
|
1011
|
-
createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
|
|
1012
|
-
Credential.serialize({ challenge, payload: { source: 'mcp-result' } }),
|
|
1013
|
-
}
|
|
1014
|
-
const initialBody = JSON.stringify({
|
|
1015
|
-
jsonrpc: '2.0',
|
|
1016
|
-
id: 1,
|
|
1017
|
-
method: 'tools/call',
|
|
1018
|
-
params: { name: 'paid-tool' },
|
|
1019
|
-
})
|
|
1020
|
-
let callCount = 0
|
|
1021
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1022
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1023
|
-
calls.push({ init })
|
|
1024
|
-
callCount++
|
|
1025
|
-
if (callCount === 1)
|
|
1026
|
-
return Response.json({
|
|
1027
|
-
jsonrpc: '2.0',
|
|
1028
|
-
id: 1,
|
|
1029
|
-
result: {
|
|
1030
|
-
content: [{ type: 'text', text: 'Payment Required' }],
|
|
1031
|
-
isError: true,
|
|
1032
|
-
_meta: {
|
|
1033
|
-
[Mcp.paymentRequiredMetaKey]: {
|
|
1034
|
-
httpStatus: 402,
|
|
1035
|
-
challenges: [mcpChallenge],
|
|
1036
|
-
},
|
|
1037
|
-
},
|
|
1038
|
-
},
|
|
1039
|
-
})
|
|
1040
|
-
|
|
1041
|
-
const body = JSON.parse(init?.body as string)
|
|
1042
|
-
expect(new Headers(init?.headers).get('Authorization')).toBeNull()
|
|
1043
|
-
expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
|
|
1044
|
-
payload: { source: 'mcp-result' },
|
|
1045
|
-
})
|
|
1046
|
-
return Response.json({ ok: true })
|
|
1047
|
-
}
|
|
1048
|
-
|
|
1049
|
-
const fetch = Fetch.from({
|
|
1050
|
-
fetch: mockFetch,
|
|
1051
|
-
methods: [method],
|
|
1052
|
-
})
|
|
1053
|
-
|
|
1054
|
-
const response = await fetch('https://example.com/mcp', {
|
|
1055
|
-
method: 'POST',
|
|
1056
|
-
headers: { accept: 'application/json, text/event-stream' },
|
|
1057
|
-
body: initialBody,
|
|
1058
|
-
})
|
|
1059
|
-
|
|
1060
|
-
expect(response.status).toBe(200)
|
|
1061
|
-
expect(calls).toHaveLength(2)
|
|
1062
|
-
})
|
|
1063
|
-
|
|
1064
|
-
test('settles MCP-over-HTTP when the JSON-RPC request body is carried by Request input', async () => {
|
|
1065
|
-
const mcpChallenge = Challenge.from({
|
|
1066
|
-
id: 'mcp-request-input-challenge',
|
|
1067
|
-
intent: 'test',
|
|
1068
|
-
method: 'test',
|
|
1069
|
-
realm: 'test',
|
|
1070
|
-
request: { amount: '1' },
|
|
1071
|
-
})
|
|
1072
|
-
const method = {
|
|
1073
|
-
...noopMethod,
|
|
1074
|
-
createCredential: async ({ challenge }: { challenge: Challenge.Challenge }) =>
|
|
1075
|
-
Credential.serialize({ challenge, payload: { source: 'request-input' } }),
|
|
1076
|
-
}
|
|
1077
|
-
const initialBody = JSON.stringify({
|
|
1078
|
-
jsonrpc: '2.0',
|
|
1079
|
-
id: 1,
|
|
1080
|
-
method: 'tools/call',
|
|
1081
|
-
params: { name: 'paid-tool' },
|
|
1082
|
-
})
|
|
1083
|
-
let callCount = 0
|
|
1084
|
-
const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
|
|
1085
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
1086
|
-
calls.push({ input, init })
|
|
1087
|
-
callCount++
|
|
1088
|
-
if (callCount === 1) {
|
|
1089
|
-
expect(input).toBeInstanceOf(Request)
|
|
1090
|
-
expect(await (input as Request).text()).toBe(initialBody)
|
|
1091
|
-
return Response.json({
|
|
1092
|
-
jsonrpc: '2.0',
|
|
1093
|
-
id: 1,
|
|
1094
|
-
error: {
|
|
1095
|
-
code: Mcp.paymentRequiredCode,
|
|
1096
|
-
message: 'Payment Required',
|
|
1097
|
-
data: { challenges: [mcpChallenge] },
|
|
1098
|
-
},
|
|
1099
|
-
})
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
const body = JSON.parse(init?.body as string)
|
|
1103
|
-
expect(body.params['_meta'][Mcp.credentialMetaKey]).toMatchObject({
|
|
1104
|
-
payload: { source: 'request-input' },
|
|
1105
|
-
})
|
|
1106
|
-
return Response.json({ ok: true })
|
|
1107
|
-
}
|
|
1108
|
-
|
|
1109
|
-
const fetch = Fetch.from({
|
|
1110
|
-
fetch: mockFetch,
|
|
1111
|
-
methods: [method],
|
|
1112
|
-
})
|
|
1113
|
-
const request = new Request('https://example.com/mcp', {
|
|
1114
|
-
method: 'POST',
|
|
1115
|
-
headers: { accept: 'application/json, text/event-stream' },
|
|
1116
|
-
body: initialBody,
|
|
1117
|
-
})
|
|
1118
|
-
|
|
1119
|
-
const response = await fetch(request)
|
|
1120
|
-
|
|
1121
|
-
expect(response.status).toBe(200)
|
|
1122
|
-
expect(calls).toHaveLength(2)
|
|
1123
|
-
expect(calls[1]?.input).toBe(request)
|
|
1124
|
-
})
|
|
1125
|
-
|
|
1126
|
-
test('settles native HTTP 402 when a POST body is carried by Request input', async () => {
|
|
1127
|
-
const initialBody = JSON.stringify({ ok: true })
|
|
1128
|
-
let callCount = 0
|
|
1129
|
-
const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
|
|
1130
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
1131
|
-
calls.push({ input, init })
|
|
1132
|
-
callCount++
|
|
1133
|
-
const request = input instanceof Request && !init ? input : new Request(input, init)
|
|
1134
|
-
expect(await request.text()).toBe(initialBody)
|
|
1135
|
-
if (callCount === 1) return make402()
|
|
1136
|
-
|
|
1137
|
-
expect(request.headers.get('Authorization')).toBe('credential')
|
|
1138
|
-
return new Response('OK', { status: 200 })
|
|
1139
|
-
}
|
|
1140
|
-
|
|
1141
|
-
const fetch = Fetch.from({
|
|
1142
|
-
fetch: mockFetch,
|
|
1143
|
-
methods: [noopMethod],
|
|
1144
|
-
})
|
|
1145
|
-
const request = new Request('https://example.com/api', {
|
|
1146
|
-
method: 'POST',
|
|
1147
|
-
headers: { 'Content-Type': 'application/json' },
|
|
1148
|
-
body: initialBody,
|
|
1149
|
-
})
|
|
1150
|
-
|
|
1151
|
-
const response = await fetch(request)
|
|
1152
|
-
|
|
1153
|
-
expect(response.status).toBe(200)
|
|
1154
|
-
expect(calls).toHaveLength(2)
|
|
1155
|
-
expect(calls[1]?.input).toBe(request)
|
|
1156
|
-
})
|
|
1157
|
-
|
|
1158
|
-
test('sends credential retry to the final same-origin 402 response URL', async () => {
|
|
1159
|
-
let callCount = 0
|
|
1160
|
-
const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
|
|
1161
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
1162
|
-
calls.push({ input, init })
|
|
1163
|
-
callCount++
|
|
1164
|
-
if (callCount === 1) {
|
|
1165
|
-
const response = make402()
|
|
1166
|
-
Object.defineProperty(response, 'url', {
|
|
1167
|
-
value: 'https://api.example.com/redirected-protected',
|
|
1168
|
-
})
|
|
1169
|
-
return response
|
|
1170
|
-
}
|
|
1171
|
-
return new Response('OK', { status: 200 })
|
|
1172
|
-
}
|
|
1173
|
-
|
|
1174
|
-
const fetch = Fetch.from({
|
|
1175
|
-
fetch: mockFetch,
|
|
1176
|
-
methods: [noopMethod],
|
|
1177
|
-
})
|
|
1178
|
-
|
|
1179
|
-
const response = await fetch('https://api.example.com/protected')
|
|
1180
|
-
|
|
1181
|
-
expect(response.status).toBe(200)
|
|
1182
|
-
expect(calls[0]!.input).toBe('https://api.example.com/protected')
|
|
1183
|
-
expect(calls[1]!.input).toBe('https://api.example.com/redirected-protected')
|
|
1184
|
-
expect(new Headers(calls[1]!.init?.headers).get('Authorization')).toBe('credential')
|
|
1185
|
-
})
|
|
1186
|
-
|
|
1187
|
-
test('rejects credential retry to a cross-origin 402 response URL', async () => {
|
|
1188
|
-
const calls: { input: RequestInfo | URL; init: RequestInit | undefined }[] = []
|
|
1189
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
1190
|
-
calls.push({ input, init })
|
|
1191
|
-
const response = make402()
|
|
1192
|
-
Object.defineProperty(response, 'url', {
|
|
1193
|
-
value: 'https://payments.example.com/protected',
|
|
1194
|
-
})
|
|
1195
|
-
return response
|
|
1196
|
-
}
|
|
1197
|
-
|
|
1198
|
-
const fetch = Fetch.from({
|
|
1199
|
-
fetch: mockFetch,
|
|
1200
|
-
methods: [noopMethod],
|
|
1201
|
-
})
|
|
1202
|
-
|
|
1203
|
-
await expect(fetch('https://api.example.com/protected')).rejects.toThrow(
|
|
1204
|
-
'Refusing to send payment credential across redirect from https://api.example.com to https://payments.example.com',
|
|
1205
|
-
)
|
|
1206
|
-
|
|
1207
|
-
expect(calls).toHaveLength(1)
|
|
1208
|
-
expect(calls[0]!.input).toBe('https://api.example.com/protected')
|
|
1209
|
-
expect(new Headers(calls[0]!.init?.headers).get('Authorization')).toBeNull()
|
|
1210
|
-
})
|
|
1211
|
-
|
|
1212
|
-
test('emits client events and allows challenge handler to provide credential', async () => {
|
|
1213
|
-
const events: string[] = []
|
|
1214
|
-
const createCredential = vi.fn(async () => 'method-credential')
|
|
1215
|
-
let callCount = 0
|
|
1216
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1217
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1218
|
-
calls.push({ init })
|
|
1219
|
-
callCount++
|
|
1220
|
-
if (callCount === 1) return make402()
|
|
1221
|
-
return new Response('OK', { status: 200 })
|
|
1222
|
-
}
|
|
1223
|
-
|
|
1224
|
-
const method = { ...noopMethod, createCredential }
|
|
1225
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1226
|
-
eventDispatcher.on('*', (event) => {
|
|
1227
|
-
events.push(`*:${event.name}`)
|
|
1228
|
-
})
|
|
1229
|
-
eventDispatcher.on('challenge.received', async (payload) => {
|
|
1230
|
-
events.push(`challenge:${payload.challenge.id}`)
|
|
1231
|
-
return 'event-credential'
|
|
1232
|
-
})
|
|
1233
|
-
eventDispatcher.on('credential.created', (payload) => {
|
|
1234
|
-
events.push(`credential:${payload.credential}`)
|
|
1235
|
-
throw new Error('observer failed')
|
|
1236
|
-
})
|
|
1237
|
-
eventDispatcher.on('payment.response', (payload) => {
|
|
1238
|
-
events.push(`response:${payload.response.status}`)
|
|
1239
|
-
throw new Error('observer failed')
|
|
1240
|
-
})
|
|
1241
|
-
|
|
1242
|
-
const fetch = Fetch.from({
|
|
1243
|
-
eventDispatcher,
|
|
1244
|
-
fetch: mockFetch,
|
|
1245
|
-
methods: [method],
|
|
1246
|
-
})
|
|
1247
|
-
|
|
1248
|
-
const response = await fetch('https://example.com/api')
|
|
1249
|
-
|
|
1250
|
-
expect(response.status).toBe(200)
|
|
1251
|
-
expect(createCredential).not.toHaveBeenCalled()
|
|
1252
|
-
const retryHeaders = new Headers((calls[1]!.init as RequestInit).headers)
|
|
1253
|
-
expect(retryHeaders.get('Authorization')).toBe('event-credential')
|
|
1254
|
-
expect(events).toEqual([
|
|
1255
|
-
'challenge:abc',
|
|
1256
|
-
'*:challenge.received',
|
|
1257
|
-
'credential:event-credential',
|
|
1258
|
-
'*:credential.created',
|
|
1259
|
-
'response:200',
|
|
1260
|
-
'*:payment.response',
|
|
1261
|
-
])
|
|
1262
|
-
})
|
|
1263
|
-
|
|
1264
|
-
test('uses the first challenge event credential', async () => {
|
|
1265
|
-
const events: string[] = []
|
|
1266
|
-
const createCredential = vi.fn(async () => 'method-credential')
|
|
1267
|
-
const method = { ...noopMethod, createCredential }
|
|
1268
|
-
let callCount = 0
|
|
1269
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1270
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1271
|
-
calls.push({ init })
|
|
1272
|
-
callCount++
|
|
1273
|
-
if (callCount === 1) return make402()
|
|
1274
|
-
return new Response('OK', { status: 200 })
|
|
1275
|
-
}
|
|
1276
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1277
|
-
eventDispatcher.on('challenge.received', () => {
|
|
1278
|
-
events.push('first')
|
|
1279
|
-
return 'first-credential'
|
|
1280
|
-
})
|
|
1281
|
-
eventDispatcher.on('challenge.received', () => {
|
|
1282
|
-
events.push('second')
|
|
1283
|
-
return 'second-credential'
|
|
1284
|
-
})
|
|
1285
|
-
|
|
1286
|
-
const fetch = Fetch.from({
|
|
1287
|
-
eventDispatcher,
|
|
1288
|
-
fetch: mockFetch,
|
|
1289
|
-
methods: [method],
|
|
1290
|
-
})
|
|
1291
|
-
|
|
1292
|
-
await fetch('https://example.com/api')
|
|
1293
|
-
|
|
1294
|
-
expect(createCredential).not.toHaveBeenCalled()
|
|
1295
|
-
const retryHeaders = new Headers((calls[1]!.init as RequestInit).headers)
|
|
1296
|
-
expect(retryHeaders.get('Authorization')).toBe('first-credential')
|
|
1297
|
-
expect(events).toEqual(['first'])
|
|
1298
|
-
})
|
|
1299
|
-
|
|
1300
|
-
test('does not emit payment.response for non-ok retry responses', async () => {
|
|
1301
|
-
const events: string[] = []
|
|
1302
|
-
const createCredential = vi.fn(async () => 'method-credential')
|
|
1303
|
-
const method = { ...noopMethod, createCredential }
|
|
1304
|
-
let callCount = 0
|
|
1305
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
1306
|
-
callCount++
|
|
1307
|
-
if (callCount === 1) return make402()
|
|
1308
|
-
return new Response('Internal Server Error', { status: 500 })
|
|
1309
|
-
}
|
|
1310
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1311
|
-
eventDispatcher.on('payment.response', (payload) => {
|
|
1312
|
-
events.push(`response:${payload.response.status}`)
|
|
1313
|
-
})
|
|
1314
|
-
|
|
1315
|
-
const fetch = Fetch.from({
|
|
1316
|
-
eventDispatcher,
|
|
1317
|
-
fetch: mockFetch,
|
|
1318
|
-
methods: [method],
|
|
1319
|
-
})
|
|
1320
|
-
|
|
1321
|
-
const response = await fetch('https://example.com/api')
|
|
1322
|
-
|
|
1323
|
-
expect(response.status).toBe(500)
|
|
1324
|
-
expect(events).toEqual([])
|
|
1325
|
-
})
|
|
1326
|
-
|
|
1327
|
-
test('ignores empty challenge event credentials and continues handlers', async () => {
|
|
1328
|
-
const createCredential = vi.fn(async () => 'method-credential')
|
|
1329
|
-
const method = { ...noopMethod, createCredential }
|
|
1330
|
-
let callCount = 0
|
|
1331
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1332
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1333
|
-
calls.push({ init })
|
|
1334
|
-
callCount++
|
|
1335
|
-
if (callCount === 1) return make402()
|
|
1336
|
-
return new Response('OK', { status: 200 })
|
|
1337
|
-
}
|
|
1338
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1339
|
-
eventDispatcher.on('challenge.received', () => '')
|
|
1340
|
-
eventDispatcher.on('challenge.received', () => 'second-credential')
|
|
1341
|
-
|
|
1342
|
-
const fetch = Fetch.from({
|
|
1343
|
-
eventDispatcher,
|
|
1344
|
-
fetch: mockFetch,
|
|
1345
|
-
methods: [method],
|
|
1346
|
-
})
|
|
1347
|
-
|
|
1348
|
-
await fetch('https://example.com/api')
|
|
1349
|
-
|
|
1350
|
-
expect(createCredential).not.toHaveBeenCalled()
|
|
1351
|
-
const retryHeaders = new Headers((calls[1]!.init as RequestInit).headers)
|
|
1352
|
-
expect(retryHeaders.get('Authorization')).toBe('second-credential')
|
|
1353
|
-
})
|
|
1354
|
-
|
|
1355
|
-
test('memoizes createCredential across wildcard observers and fallback', async () => {
|
|
1356
|
-
const createCredential = vi.fn(async () => 'method-credential')
|
|
1357
|
-
const method = { ...noopMethod, createCredential }
|
|
1358
|
-
let callCount = 0
|
|
1359
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
1360
|
-
callCount++
|
|
1361
|
-
if (callCount === 1) return make402()
|
|
1362
|
-
return new Response('OK', { status: 200 })
|
|
1363
|
-
}
|
|
1364
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1365
|
-
eventDispatcher.on('*', async (event) => {
|
|
1366
|
-
if (event.name === 'challenge.received') await event.payload.createCredential()
|
|
1367
|
-
})
|
|
1368
|
-
|
|
1369
|
-
const fetch = Fetch.from({
|
|
1370
|
-
eventDispatcher,
|
|
1371
|
-
fetch: mockFetch,
|
|
1372
|
-
methods: [method],
|
|
1373
|
-
})
|
|
1374
|
-
|
|
1375
|
-
await fetch('https://example.com/api')
|
|
1376
|
-
|
|
1377
|
-
expect(createCredential).toHaveBeenCalledTimes(1)
|
|
1378
|
-
})
|
|
1379
|
-
|
|
1380
|
-
test('does not expose live challenges or init headers to observers', async () => {
|
|
1381
|
-
const createCredential = vi.fn(async ({ challenge }) => `amount:${challenge.request.amount}`)
|
|
1382
|
-
const method = { ...noopMethod, createCredential }
|
|
1383
|
-
let callCount = 0
|
|
1384
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1385
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1386
|
-
calls.push({ init })
|
|
1387
|
-
callCount++
|
|
1388
|
-
if (callCount === 1) return make402()
|
|
1389
|
-
return new Response('OK', { status: 200 })
|
|
1390
|
-
}
|
|
1391
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1392
|
-
eventDispatcher.on('*', (event) => {
|
|
1393
|
-
if (event.name !== 'challenge.received') return
|
|
1394
|
-
try {
|
|
1395
|
-
;(event.payload.challenge.request as { amount: string }).amount = '999'
|
|
1396
|
-
} catch {}
|
|
1397
|
-
const headers = new Headers(event.payload.init?.headers)
|
|
1398
|
-
headers.set('Authorization', 'attacker')
|
|
1399
|
-
if (event.payload.init) event.payload.init.headers = headers
|
|
1400
|
-
})
|
|
1401
|
-
|
|
1402
|
-
const fetch = Fetch.from({
|
|
1403
|
-
eventDispatcher,
|
|
1404
|
-
fetch: mockFetch,
|
|
1405
|
-
methods: [method],
|
|
1406
|
-
})
|
|
1407
|
-
|
|
1408
|
-
await fetch('https://example.com/api', {
|
|
1409
|
-
headers: { 'X-Test': '1' },
|
|
1410
|
-
})
|
|
1411
|
-
|
|
1412
|
-
const retryHeaders = new Headers((calls[1]!.init as RequestInit).headers)
|
|
1413
|
-
expect(retryHeaders.get('Authorization')).toBe('amount:1')
|
|
1414
|
-
})
|
|
1415
|
-
|
|
1416
|
-
test('continues dispatching observer listeners after one throws', async () => {
|
|
1417
|
-
const events: string[] = []
|
|
1418
|
-
const method = { ...noopMethod, createCredential: vi.fn(async () => 'credential') }
|
|
1419
|
-
let callCount = 0
|
|
1420
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
1421
|
-
callCount++
|
|
1422
|
-
if (callCount === 1) return make402()
|
|
1423
|
-
return new Response('OK', { status: 200 })
|
|
1424
|
-
}
|
|
1425
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1426
|
-
eventDispatcher.on('credential.created', () => {
|
|
1427
|
-
events.push('first')
|
|
1428
|
-
throw new Error('observer failed')
|
|
1429
|
-
})
|
|
1430
|
-
eventDispatcher.on('credential.created', () => {
|
|
1431
|
-
events.push('second')
|
|
1432
|
-
})
|
|
1433
|
-
|
|
1434
|
-
const fetch = Fetch.from({
|
|
1435
|
-
eventDispatcher,
|
|
1436
|
-
fetch: mockFetch,
|
|
1437
|
-
methods: [method],
|
|
1438
|
-
})
|
|
1439
|
-
|
|
1440
|
-
await fetch('https://example.com/api')
|
|
1441
|
-
|
|
1442
|
-
expect(events).toEqual(['first', 'second'])
|
|
1443
|
-
})
|
|
1444
|
-
|
|
1445
|
-
test('emits payment.failed when automatic payment handling rejects', async () => {
|
|
1446
|
-
const events: string[] = []
|
|
1447
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
1448
|
-
const mockFetch = vi.fn(async () =>
|
|
1449
|
-
make402({ expires: new Date(Date.now() - 60_000).toISOString() }),
|
|
1450
|
-
)
|
|
1451
|
-
const method = { ...noopMethod, createCredential }
|
|
1452
|
-
const eventDispatcher = Fetch.createEventDispatcher<[typeof method]>()
|
|
1453
|
-
eventDispatcher.on('*', (event) => {
|
|
1454
|
-
events.push(`*:${event.name}`)
|
|
1455
|
-
})
|
|
1456
|
-
eventDispatcher.on('payment.failed', (payload) => {
|
|
1457
|
-
events.push(
|
|
1458
|
-
`failed:${payload.error instanceof Errors.PaymentExpiredError}:${payload.challenge?.id}`,
|
|
1459
|
-
)
|
|
1460
|
-
throw new Error('observer failed')
|
|
1461
|
-
})
|
|
1462
|
-
const fetch = Fetch.from({
|
|
1463
|
-
eventDispatcher,
|
|
1464
|
-
fetch: mockFetch as typeof globalThis.fetch,
|
|
1465
|
-
methods: [method],
|
|
1466
|
-
})
|
|
1467
|
-
|
|
1468
|
-
await expect(fetch('https://example.com/api')).rejects.toThrow(Errors.PaymentExpiredError)
|
|
1469
|
-
expect(createCredential).not.toHaveBeenCalled()
|
|
1470
|
-
expect(events).toEqual(['failed:true:abc', '*:payment.failed'])
|
|
1471
|
-
})
|
|
1472
|
-
|
|
1473
|
-
test('preserves existing headers on retry', async () => {
|
|
1474
|
-
let callCount = 0
|
|
1475
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1476
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1477
|
-
calls.push({ init })
|
|
1478
|
-
callCount++
|
|
1479
|
-
if (callCount === 1) return make402()
|
|
1480
|
-
return new Response('OK', { status: 200 })
|
|
1481
|
-
}
|
|
1482
|
-
|
|
1483
|
-
const fetch = Fetch.from({
|
|
1484
|
-
fetch: mockFetch,
|
|
1485
|
-
methods: [noopMethod],
|
|
1486
|
-
})
|
|
1487
|
-
|
|
1488
|
-
await fetch('https://example.com/api', {
|
|
1489
|
-
headers: { 'X-Custom': 'value', 'Content-Type': 'application/json' },
|
|
1490
|
-
})
|
|
1491
|
-
|
|
1492
|
-
const retryInit = calls[1]!.init as Record<string, unknown>
|
|
1493
|
-
const headers = new Headers(retryInit.headers as HeadersInit)
|
|
1494
|
-
expect(headers.get('X-Custom')).toBe('value')
|
|
1495
|
-
expect(headers.get('Content-Type')).toBe('application/json')
|
|
1496
|
-
expect(headers.get('Accept-Payment')).toBe('test/test')
|
|
1497
|
-
expect(headers.get('Authorization')).toBe('credential')
|
|
1498
|
-
})
|
|
1499
|
-
|
|
1500
|
-
test('preserves method and other init properties on retry', async () => {
|
|
1501
|
-
let callCount = 0
|
|
1502
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1503
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1504
|
-
calls.push({ init })
|
|
1505
|
-
callCount++
|
|
1506
|
-
if (callCount === 1) return make402()
|
|
1507
|
-
return new Response('OK', { status: 200 })
|
|
1508
|
-
}
|
|
1509
|
-
|
|
1510
|
-
const fetch = Fetch.from({
|
|
1511
|
-
fetch: mockFetch,
|
|
1512
|
-
methods: [noopMethod],
|
|
1513
|
-
})
|
|
1514
|
-
|
|
1515
|
-
await fetch('https://example.com/api', {
|
|
1516
|
-
method: 'PUT',
|
|
1517
|
-
body: JSON.stringify({ data: 'test' }),
|
|
1518
|
-
credentials: 'include',
|
|
1519
|
-
mode: 'cors',
|
|
1520
|
-
})
|
|
1521
|
-
|
|
1522
|
-
const retryInit = calls[1]!.init as Record<string, unknown>
|
|
1523
|
-
expect(retryInit.method).toBe('PUT')
|
|
1524
|
-
expect(retryInit.body).toBe(JSON.stringify({ data: 'test' }))
|
|
1525
|
-
expect(retryInit.credentials).toBe('include')
|
|
1526
|
-
expect(retryInit.mode).toBe('cors')
|
|
1527
|
-
})
|
|
1528
|
-
|
|
1529
|
-
test('handles undefined init on 402 retry', async () => {
|
|
1530
|
-
let callCount = 0
|
|
1531
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1532
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1533
|
-
calls.push({ init })
|
|
1534
|
-
callCount++
|
|
1535
|
-
if (callCount === 1) return make402()
|
|
1536
|
-
return new Response('OK', { status: 200 })
|
|
1537
|
-
}
|
|
1538
|
-
|
|
1539
|
-
const fetch = Fetch.from({
|
|
1540
|
-
fetch: mockFetch,
|
|
1541
|
-
methods: [noopMethod],
|
|
1542
|
-
})
|
|
1543
|
-
|
|
1544
|
-
await fetch('https://example.com/api')
|
|
1545
|
-
|
|
1546
|
-
expect(calls).toHaveLength(2)
|
|
1547
|
-
const retryInit = calls[1]!.init as Record<string, unknown>
|
|
1548
|
-
const headers = new Headers(retryInit.headers as HeadersInit)
|
|
1549
|
-
expect(headers.get('Accept-Payment')).toBe('test/test')
|
|
1550
|
-
expect(headers.get('Authorization')).toBe('credential')
|
|
1551
|
-
})
|
|
1552
|
-
|
|
1553
|
-
test('preserves Request-carried headers on retry after injecting Accept-Payment', async () => {
|
|
1554
|
-
let callCount = 0
|
|
1555
|
-
const calls: { init: RequestInit | undefined; input: RequestInfo | URL }[] = []
|
|
1556
|
-
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
1557
|
-
calls.push({ init, input })
|
|
1558
|
-
callCount++
|
|
1559
|
-
if (callCount === 1) return make402()
|
|
1560
|
-
return new Response('OK', { status: 200 })
|
|
1561
|
-
}
|
|
1562
|
-
|
|
1563
|
-
const fetch = Fetch.from({
|
|
1564
|
-
fetch: mockFetch,
|
|
1565
|
-
methods: [noopMethod],
|
|
1566
|
-
})
|
|
1567
|
-
|
|
1568
|
-
const request = new Request('https://example.com/api', {
|
|
1569
|
-
headers: { Authorization: 'Bearer token123', 'X-Custom': 'value' },
|
|
1570
|
-
})
|
|
1571
|
-
|
|
1572
|
-
await fetch(request)
|
|
1573
|
-
|
|
1574
|
-
expect(calls[0]?.input).toBe(request)
|
|
1575
|
-
expect(calls[1]?.input).toBe(request)
|
|
1576
|
-
const headers = new Headers(calls[1]?.init?.headers)
|
|
1577
|
-
expect(headers.get('X-Custom')).toBe('value')
|
|
1578
|
-
expect(headers.get('Accept-Payment')).toBe('test/test')
|
|
1579
|
-
expect(headers.get('Authorization')).toBe('credential')
|
|
1580
|
-
})
|
|
1581
|
-
|
|
1582
|
-
test('selects the highest-ranked supported challenge', async () => {
|
|
1583
|
-
let callCount = 0
|
|
1584
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1585
|
-
callCount++
|
|
1586
|
-
if (callCount === 1) {
|
|
1587
|
-
expect(new Headers(init?.headers).get('Accept-Payment')).toBe(
|
|
1588
|
-
'tempo/charge, stripe/charge;q=0.5',
|
|
1589
|
-
)
|
|
1590
|
-
|
|
1591
|
-
return new Response(null, {
|
|
1592
|
-
status: 402,
|
|
1593
|
-
headers: {
|
|
1594
|
-
'WWW-Authenticate': [
|
|
1595
|
-
'Payment id="stripe", realm="test", method="stripe", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
1596
|
-
'Payment id="tempo", realm="test", method="tempo", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
1597
|
-
].join(', '),
|
|
1598
|
-
},
|
|
1599
|
-
})
|
|
1600
|
-
}
|
|
1601
|
-
|
|
1602
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('tempo-credential')
|
|
1603
|
-
return new Response('OK', { status: 200 })
|
|
1604
|
-
}
|
|
1605
|
-
|
|
1606
|
-
const fetch = Fetch.from({
|
|
1607
|
-
fetch: mockFetch,
|
|
1608
|
-
methods: [
|
|
1609
|
-
{
|
|
1610
|
-
name: 'tempo',
|
|
1611
|
-
intent: 'charge',
|
|
1612
|
-
context: undefined,
|
|
1613
|
-
createCredential: async () => 'tempo-credential',
|
|
1614
|
-
},
|
|
1615
|
-
{
|
|
1616
|
-
name: 'stripe',
|
|
1617
|
-
intent: 'charge',
|
|
1618
|
-
context: undefined,
|
|
1619
|
-
createCredential: async () => 'stripe-credential',
|
|
1620
|
-
},
|
|
1621
|
-
] as const,
|
|
1622
|
-
acceptPayment: {
|
|
1623
|
-
definition: { 'stripe/charge': 0.5 },
|
|
1624
|
-
entries: [
|
|
1625
|
-
{ intent: 'charge', method: 'tempo', q: 1, index: 0 },
|
|
1626
|
-
{ intent: 'charge', method: 'stripe', q: 0.5, index: 1 },
|
|
1627
|
-
],
|
|
1628
|
-
header: 'tempo/charge, stripe/charge;q=0.5',
|
|
1629
|
-
keys: {
|
|
1630
|
-
stripe: { charge: 'stripe/charge' },
|
|
1631
|
-
tempo: { charge: 'tempo/charge' },
|
|
1632
|
-
},
|
|
1633
|
-
},
|
|
1634
|
-
})
|
|
1635
|
-
|
|
1636
|
-
const response = await fetch('https://example.com/api')
|
|
1637
|
-
expect(response.status).toBe(200)
|
|
1638
|
-
})
|
|
1639
|
-
|
|
1640
|
-
test('ignores unsupported x402 accepts when selecting a Payment auth challenge', async () => {
|
|
1641
|
-
let callCount = 0
|
|
1642
|
-
const createCredential = vi.fn(async () => 'tempo-credential')
|
|
1643
|
-
const tempoChallenge = Challenge.from({
|
|
1644
|
-
id: 'tempo',
|
|
1645
|
-
realm: 'test',
|
|
1646
|
-
method: 'tempo',
|
|
1647
|
-
intent: 'charge',
|
|
1648
|
-
request: { amount: '1' },
|
|
1649
|
-
})
|
|
1650
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1651
|
-
callCount++
|
|
1652
|
-
if (callCount === 1) {
|
|
1653
|
-
return new Response(null, {
|
|
1654
|
-
status: 402,
|
|
1655
|
-
headers: {
|
|
1656
|
-
[x402_Types.paymentRequiredHeader]: encodeRawPaymentRequiredHeader({
|
|
1657
|
-
accepts: [
|
|
1658
|
-
{
|
|
1659
|
-
amount: '10000',
|
|
1660
|
-
asset: 'USDC',
|
|
1661
|
-
maxTimeoutSeconds: 60,
|
|
1662
|
-
network: 'solana:mainnet',
|
|
1663
|
-
payTo: '9xQeWvG816bUx9EPjHmaT23yvVM2ZWdYqPxfowV5n2kg',
|
|
1664
|
-
scheme: 'exact',
|
|
1665
|
-
},
|
|
1666
|
-
],
|
|
1667
|
-
resource: { url: 'https://example.com/api' },
|
|
1668
|
-
x402Version: 2,
|
|
1669
|
-
}),
|
|
1670
|
-
'WWW-Authenticate': Challenge.serialize(tempoChallenge),
|
|
1671
|
-
},
|
|
1672
|
-
})
|
|
1673
|
-
}
|
|
1674
|
-
|
|
1675
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('tempo-credential')
|
|
1676
|
-
return new Response('OK', { status: 200 })
|
|
1677
|
-
}
|
|
1678
|
-
|
|
1679
|
-
const fetch = Fetch.from({
|
|
1680
|
-
fetch: mockFetch,
|
|
1681
|
-
methods: [
|
|
1682
|
-
{
|
|
1683
|
-
name: 'tempo',
|
|
1684
|
-
intent: 'charge',
|
|
1685
|
-
context: undefined,
|
|
1686
|
-
createCredential,
|
|
1687
|
-
},
|
|
1688
|
-
] as const,
|
|
1689
|
-
})
|
|
1690
|
-
|
|
1691
|
-
const response = await fetch('https://example.com/api')
|
|
1692
|
-
|
|
1693
|
-
expect(response.status).toBe(200)
|
|
1694
|
-
expect(createCredential).toHaveBeenCalledOnce()
|
|
1695
|
-
})
|
|
1696
|
-
|
|
1697
|
-
test('orderChallenges filters and sorts supported challenges before signing', async () => {
|
|
1698
|
-
let callCount = 0
|
|
1699
|
-
const pathUsd = Challenge.from({
|
|
1700
|
-
id: 'pathusd',
|
|
1701
|
-
realm: 'test',
|
|
1702
|
-
method: 'test',
|
|
1703
|
-
intent: 'test',
|
|
1704
|
-
request: { chainId: 11155111, currency: 'pathusd' },
|
|
1705
|
-
})
|
|
1706
|
-
const usdc = Challenge.from({
|
|
1707
|
-
id: 'usdc',
|
|
1708
|
-
realm: 'test',
|
|
1709
|
-
method: 'test',
|
|
1710
|
-
intent: 'test',
|
|
1711
|
-
request: { chainId: 8453, currency: 'usdc' },
|
|
1712
|
-
})
|
|
1713
|
-
const createCredential = vi.fn(
|
|
1714
|
-
async ({ challenge }: { challenge: Challenge.Challenge }) => `credential-${challenge.id}`,
|
|
1715
|
-
)
|
|
1716
|
-
|
|
1717
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1718
|
-
callCount++
|
|
1719
|
-
if (callCount === 1) {
|
|
1720
|
-
return new Response(null, {
|
|
1721
|
-
status: 402,
|
|
1722
|
-
headers: {
|
|
1723
|
-
'WWW-Authenticate': `${Challenge.serialize(pathUsd)}, ${Challenge.serialize(usdc)}`,
|
|
1724
|
-
},
|
|
1725
|
-
})
|
|
1726
|
-
}
|
|
1727
|
-
|
|
1728
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('credential-usdc')
|
|
1729
|
-
return new Response('OK', { status: 200 })
|
|
1730
|
-
}
|
|
1731
|
-
|
|
1732
|
-
const fetch = Fetch.from({
|
|
1733
|
-
fetch: mockFetch,
|
|
1734
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
1735
|
-
orderChallenges: (candidates) =>
|
|
1736
|
-
candidates.filter(({ challenge }) => challenge.request.currency === 'usdc'),
|
|
1737
|
-
})
|
|
1738
|
-
|
|
1739
|
-
const response = await fetch('https://example.com/api')
|
|
1740
|
-
|
|
1741
|
-
expect(response.status).toBe(200)
|
|
1742
|
-
expect(createCredential).toHaveBeenCalledOnce()
|
|
1743
|
-
expect(createCredential.mock.calls[0]?.[0].challenge.id).toBe('usdc')
|
|
1744
|
-
})
|
|
1745
|
-
|
|
1746
|
-
test('request-local orderChallenges overrides configured ordering', async () => {
|
|
1747
|
-
let callCount = 0
|
|
1748
|
-
const first = Challenge.from({
|
|
1749
|
-
id: 'first',
|
|
1750
|
-
realm: 'test',
|
|
1751
|
-
method: 'test',
|
|
1752
|
-
intent: 'test',
|
|
1753
|
-
request: { currency: 'first' },
|
|
1754
|
-
})
|
|
1755
|
-
const second = Challenge.from({
|
|
1756
|
-
id: 'second',
|
|
1757
|
-
realm: 'test',
|
|
1758
|
-
method: 'test',
|
|
1759
|
-
intent: 'test',
|
|
1760
|
-
request: { currency: 'second' },
|
|
1761
|
-
})
|
|
1762
|
-
const createCredential = vi.fn(
|
|
1763
|
-
async ({ challenge }: { challenge: Challenge.Challenge }) => `credential-${challenge.id}`,
|
|
1764
|
-
)
|
|
1765
|
-
|
|
1766
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1767
|
-
callCount++
|
|
1768
|
-
if (callCount === 1) {
|
|
1769
|
-
return new Response(null, {
|
|
1770
|
-
status: 402,
|
|
1771
|
-
headers: {
|
|
1772
|
-
'WWW-Authenticate': `${Challenge.serialize(first)}, ${Challenge.serialize(second)}`,
|
|
1773
|
-
},
|
|
1774
|
-
})
|
|
1775
|
-
}
|
|
1776
|
-
|
|
1777
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('credential-second')
|
|
1778
|
-
return new Response('OK', { status: 200 })
|
|
1779
|
-
}
|
|
1780
|
-
|
|
1781
|
-
const fetch = Fetch.from({
|
|
1782
|
-
fetch: mockFetch,
|
|
1783
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
1784
|
-
orderChallenges: (candidates) =>
|
|
1785
|
-
candidates.filter(({ challenge }) => challenge.id === 'first'),
|
|
1786
|
-
})
|
|
1787
|
-
|
|
1788
|
-
const response = await fetch('https://example.com/api', {
|
|
1789
|
-
orderChallenges: (candidates) =>
|
|
1790
|
-
candidates.filter(({ challenge }) => challenge.id === 'second'),
|
|
1791
|
-
})
|
|
1792
|
-
|
|
1793
|
-
expect(response.status).toBe(200)
|
|
1794
|
-
expect(createCredential.mock.calls[0]?.[0].challenge.id).toBe('second')
|
|
1795
|
-
})
|
|
1796
|
-
|
|
1797
|
-
test('throws when orderChallenges rejects every supported challenge', async () => {
|
|
1798
|
-
const mockFetch: typeof globalThis.fetch = async () => make402()
|
|
1799
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
1800
|
-
|
|
1801
|
-
const fetch = Fetch.from({
|
|
1802
|
-
fetch: mockFetch,
|
|
1803
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
1804
|
-
orderChallenges: () => [],
|
|
1805
|
-
})
|
|
1806
|
-
|
|
1807
|
-
await expect(fetch('https://example.com/api')).rejects.toThrow(
|
|
1808
|
-
'No method found for challenges: test.test',
|
|
1809
|
-
)
|
|
1810
|
-
expect(createCredential).not.toHaveBeenCalled()
|
|
1811
|
-
})
|
|
1812
|
-
|
|
1813
|
-
test('falls back to configured preferences when explicit Accept-Payment is invalid', async () => {
|
|
1814
|
-
let callCount = 0
|
|
1815
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1816
|
-
callCount++
|
|
1817
|
-
if (callCount === 1) {
|
|
1818
|
-
expect(new Headers(init?.headers).get('Accept-Payment')).toBe('not a valid header')
|
|
1819
|
-
|
|
1820
|
-
return new Response(null, {
|
|
1821
|
-
status: 402,
|
|
1822
|
-
headers: {
|
|
1823
|
-
'WWW-Authenticate': [
|
|
1824
|
-
'Payment id="stripe", realm="test", method="stripe", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
1825
|
-
'Payment id="tempo", realm="test", method="tempo", intent="charge", request="eyJhbW91bnQiOiIxIn0"',
|
|
1826
|
-
].join(', '),
|
|
1827
|
-
},
|
|
1828
|
-
})
|
|
1829
|
-
}
|
|
1830
|
-
|
|
1831
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('tempo-credential')
|
|
1832
|
-
return new Response('OK', { status: 200 })
|
|
1833
|
-
}
|
|
1834
|
-
|
|
1835
|
-
const fetch = Fetch.from({
|
|
1836
|
-
fetch: mockFetch,
|
|
1837
|
-
methods: [
|
|
1838
|
-
{
|
|
1839
|
-
name: 'tempo',
|
|
1840
|
-
intent: 'charge',
|
|
1841
|
-
context: undefined,
|
|
1842
|
-
createCredential: async () => 'tempo-credential',
|
|
1843
|
-
},
|
|
1844
|
-
{
|
|
1845
|
-
name: 'stripe',
|
|
1846
|
-
intent: 'charge',
|
|
1847
|
-
context: undefined,
|
|
1848
|
-
createCredential: async () => 'stripe-credential',
|
|
1849
|
-
},
|
|
1850
|
-
] as const,
|
|
1851
|
-
acceptPayment: {
|
|
1852
|
-
definition: { 'stripe/charge': 0.5 },
|
|
1853
|
-
entries: [
|
|
1854
|
-
{ intent: 'charge', method: 'tempo', q: 1, index: 0 },
|
|
1855
|
-
{ intent: 'charge', method: 'stripe', q: 0.5, index: 1 },
|
|
1856
|
-
],
|
|
1857
|
-
header: 'tempo/charge, stripe/charge;q=0.5',
|
|
1858
|
-
keys: {
|
|
1859
|
-
stripe: { charge: 'stripe/charge' },
|
|
1860
|
-
tempo: { charge: 'tempo/charge' },
|
|
1861
|
-
},
|
|
1862
|
-
},
|
|
1863
|
-
})
|
|
1864
|
-
|
|
1865
|
-
const response = await fetch('https://example.com/api', {
|
|
1866
|
-
headers: { 'Accept-Payment': 'not a valid header' },
|
|
1867
|
-
})
|
|
1868
|
-
expect(response.status).toBe(200)
|
|
1869
|
-
})
|
|
1870
|
-
|
|
1871
|
-
test('throws when no matching method for 402 challenge', async () => {
|
|
1872
|
-
const mockFetch: typeof globalThis.fetch = async () =>
|
|
1873
|
-
make402({ method: 'stripe', intent: 'charge' })
|
|
1874
|
-
|
|
1875
|
-
const fetch = Fetch.from({
|
|
1876
|
-
fetch: mockFetch,
|
|
1877
|
-
methods: [noopMethod],
|
|
1878
|
-
})
|
|
1879
|
-
|
|
1880
|
-
await expect(fetch('https://example.com/api')).rejects.toThrow(
|
|
1881
|
-
'No method found for challenges: stripe.charge',
|
|
1882
|
-
)
|
|
1883
|
-
})
|
|
1884
|
-
|
|
1885
|
-
test('creates a fresh credential for each incremental 402 challenge', async () => {
|
|
1886
|
-
const createCredential = vi.fn(
|
|
1887
|
-
async ({ challenge }: { challenge: Challenge.Challenge }) => `credential-${challenge.id}`,
|
|
1888
|
-
)
|
|
1889
|
-
const calls: { init: RequestInit | undefined }[] = []
|
|
1890
|
-
let callCount = 0
|
|
1891
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
1892
|
-
calls.push({ init })
|
|
1893
|
-
callCount++
|
|
1894
|
-
if (callCount === 1) return make402({ id: 'first' })
|
|
1895
|
-
if (callCount === 2) return make402({ id: 'second' })
|
|
1896
|
-
if (callCount === 3) return make402({ id: 'third' })
|
|
1897
|
-
return new Response('OK', { status: 200 })
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
|
-
const fetch = Fetch.from({
|
|
1901
|
-
fetch: mockFetch,
|
|
1902
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
1903
|
-
})
|
|
1904
|
-
|
|
1905
|
-
const response = await fetch('https://example.com/api')
|
|
1906
|
-
|
|
1907
|
-
expect(response.status).toBe(200)
|
|
1908
|
-
expect(createCredential.mock.calls.map((call) => call[0].challenge.id)).toEqual([
|
|
1909
|
-
'first',
|
|
1910
|
-
'second',
|
|
1911
|
-
'third',
|
|
1912
|
-
])
|
|
1913
|
-
expect(
|
|
1914
|
-
calls.slice(1).map((call) => new Headers(call.init?.headers).get('Authorization')),
|
|
1915
|
-
).toEqual(['credential-first', 'credential-second', 'credential-third'])
|
|
1916
|
-
})
|
|
1917
|
-
|
|
1918
|
-
test('invalidates a prepared credential across a generic challenge', async () => {
|
|
1919
|
-
const firstCredential = vi.fn(async () => 'first')
|
|
1920
|
-
const first = { ...noopMethod, name: 'first', createCredential: firstCredential }
|
|
1921
|
-
const second = { ...noopMethod, name: 'second', createCredential: async () => 'second' }
|
|
1922
|
-
MethodChallenge.register(first, () => undefined)
|
|
1923
|
-
const responses = [
|
|
1924
|
-
make402({ id: 'first', method: 'first' }),
|
|
1925
|
-
make402({ id: 'second', method: 'second' }),
|
|
1926
|
-
make402({ id: 'first', method: 'first' }),
|
|
1927
|
-
new Response('OK'),
|
|
1928
|
-
]
|
|
1929
|
-
const mockFetch = vi.fn(async () => responses.shift()!)
|
|
1930
|
-
const fetch = Fetch.from({ fetch: mockFetch, methods: [first, second] })
|
|
1931
|
-
|
|
1932
|
-
expect((await fetch('https://example.com/api')).status).toBe(200)
|
|
1933
|
-
expect(firstCredential).toHaveBeenCalledTimes(2)
|
|
1934
|
-
})
|
|
1935
|
-
|
|
1936
|
-
test('invalidates a prepared credential after an explicit context', async () => {
|
|
1937
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
1938
|
-
const method = { ...noopMethod, createCredential }
|
|
1939
|
-
MethodChallenge.register(method, () => undefined)
|
|
1940
|
-
let challenges = 0
|
|
1941
|
-
const mockFetch = vi.fn(async () => (++challenges <= 3 ? make402() : new Response('OK')))
|
|
1942
|
-
const fetch = Fetch.from({
|
|
1943
|
-
fetch: mockFetch as typeof globalThis.fetch,
|
|
1944
|
-
methods: [method],
|
|
1945
|
-
onChallenge: async (_challenge, { createCredential: create }) =>
|
|
1946
|
-
challenges === 2 ? create({ override: true } as never) : undefined,
|
|
1947
|
-
})
|
|
1948
|
-
|
|
1949
|
-
expect((await fetch('https://example.com/api')).status).toBe(200)
|
|
1950
|
-
expect(createCredential).toHaveBeenCalledTimes(3)
|
|
1951
|
-
})
|
|
1952
|
-
|
|
1953
|
-
test('caps repeated 402 retries at three', async () => {
|
|
1954
|
-
let callCount = 0
|
|
1955
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
1956
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
1957
|
-
callCount++
|
|
1958
|
-
return make402()
|
|
1959
|
-
}
|
|
1960
|
-
|
|
1961
|
-
const fetch = Fetch.from({
|
|
1962
|
-
fetch: mockFetch,
|
|
1963
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
1964
|
-
})
|
|
1965
|
-
|
|
1966
|
-
const response = await fetch('https://example.com/api')
|
|
1967
|
-
expect(callCount).toBe(4)
|
|
1968
|
-
expect(createCredential).toHaveBeenCalledTimes(3)
|
|
1969
|
-
expect(response.status).toBe(402)
|
|
1970
|
-
})
|
|
1971
|
-
|
|
1972
|
-
test('respects configured retry cap', async () => {
|
|
1973
|
-
let callCount = 0
|
|
1974
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
1975
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
1976
|
-
callCount++
|
|
1977
|
-
return make402()
|
|
1978
|
-
}
|
|
1979
|
-
|
|
1980
|
-
const fetch = Fetch.from({
|
|
1981
|
-
fetch: mockFetch,
|
|
1982
|
-
maxPaymentRetries: 1,
|
|
1983
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
1984
|
-
})
|
|
1985
|
-
|
|
1986
|
-
const response = await fetch('https://example.com/api')
|
|
1987
|
-
expect(callCount).toBe(2)
|
|
1988
|
-
expect(createCredential).toHaveBeenCalledOnce()
|
|
1989
|
-
expect(response.status).toBe(402)
|
|
1990
|
-
})
|
|
1991
|
-
|
|
1992
|
-
test('returns a challenge-less 402 after a rejected payment', async () => {
|
|
1993
|
-
let callCount = 0
|
|
1994
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
1995
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
1996
|
-
callCount++
|
|
1997
|
-
if (callCount === 1) return make402()
|
|
1998
|
-
return new Response(JSON.stringify({ title: 'Verification Failed' }), {
|
|
1999
|
-
status: 402,
|
|
2000
|
-
headers: { 'Content-Type': 'application/json' },
|
|
2001
|
-
})
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
const fetch = Fetch.from({
|
|
2005
|
-
fetch: mockFetch,
|
|
2006
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
2007
|
-
})
|
|
2008
|
-
|
|
2009
|
-
const response = await fetch('https://example.com/api')
|
|
2010
|
-
expect(callCount).toBe(2)
|
|
2011
|
-
expect(createCredential).toHaveBeenCalledOnce()
|
|
2012
|
-
expect(response.status).toBe(402)
|
|
2013
|
-
await expect(response.json()).resolves.toEqual({ title: 'Verification Failed' })
|
|
2014
|
-
})
|
|
2015
|
-
|
|
2016
|
-
test('returns a post-payment 402 with only unsupported challenges', async () => {
|
|
2017
|
-
let callCount = 0
|
|
2018
|
-
const createCredential = vi.fn(async () => 'credential')
|
|
2019
|
-
const mockFetch: typeof globalThis.fetch = async () => {
|
|
2020
|
-
callCount++
|
|
2021
|
-
if (callCount === 1) return make402()
|
|
2022
|
-
return make402({ method: 'stripe', intent: 'charge' })
|
|
2023
|
-
}
|
|
2024
|
-
|
|
2025
|
-
const fetch = Fetch.from({
|
|
2026
|
-
fetch: mockFetch,
|
|
2027
|
-
methods: [{ ...noopMethod, createCredential }],
|
|
2028
|
-
})
|
|
2029
|
-
|
|
2030
|
-
const response = await fetch('https://example.com/api')
|
|
2031
|
-
expect(callCount).toBe(2)
|
|
2032
|
-
expect(createCredential).toHaveBeenCalledOnce()
|
|
2033
|
-
expect(response.status).toBe(402)
|
|
2034
|
-
})
|
|
2035
|
-
})
|
|
2036
|
-
|
|
2037
|
-
describe('Fetch.from: acceptPaymentPolicy', () => {
|
|
2038
|
-
test('policy: "always" injects Accept-Payment on all requests', async () => {
|
|
2039
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2040
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2041
|
-
receivedInits.push(init)
|
|
2042
|
-
return new Response('OK', { status: 200 })
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
const fetch = Fetch.from({
|
|
2046
|
-
fetch: mockFetch,
|
|
2047
|
-
methods: [noopMethod],
|
|
2048
|
-
acceptPaymentPolicy: 'always',
|
|
2049
|
-
})
|
|
2050
|
-
|
|
2051
|
-
await fetch('https://cross-origin.com/api')
|
|
2052
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2053
|
-
})
|
|
2054
|
-
|
|
2055
|
-
test('policy: "never" skips Accept-Payment on all requests', async () => {
|
|
2056
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2057
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2058
|
-
receivedInits.push(init)
|
|
2059
|
-
return new Response('OK', { status: 200 })
|
|
2060
|
-
}
|
|
2061
|
-
|
|
2062
|
-
const fetch = Fetch.from({
|
|
2063
|
-
fetch: mockFetch,
|
|
2064
|
-
methods: [noopMethod],
|
|
2065
|
-
acceptPaymentPolicy: 'never',
|
|
2066
|
-
})
|
|
2067
|
-
|
|
2068
|
-
await fetch('https://example.com/api')
|
|
2069
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBeNull()
|
|
2070
|
-
})
|
|
2071
|
-
|
|
2072
|
-
test('policy: "same-origin" injects when no globalThis.location (server-side)', async () => {
|
|
2073
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2074
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2075
|
-
receivedInits.push(init)
|
|
2076
|
-
return new Response('OK', { status: 200 })
|
|
2077
|
-
}
|
|
2078
|
-
|
|
2079
|
-
const fetch = Fetch.from({
|
|
2080
|
-
fetch: mockFetch,
|
|
2081
|
-
methods: [noopMethod],
|
|
2082
|
-
acceptPaymentPolicy: 'same-origin',
|
|
2083
|
-
})
|
|
2084
|
-
|
|
2085
|
-
await fetch('https://example.com/api')
|
|
2086
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2087
|
-
})
|
|
2088
|
-
|
|
2089
|
-
test('policy: { origins } injects only for matching origins', async () => {
|
|
2090
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2091
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2092
|
-
receivedInits.push(init)
|
|
2093
|
-
return new Response('OK', { status: 200 })
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
const fetch = Fetch.from({
|
|
2097
|
-
fetch: mockFetch,
|
|
2098
|
-
methods: [noopMethod],
|
|
2099
|
-
acceptPaymentPolicy: { origins: ['https://pay.example.com'] },
|
|
2100
|
-
})
|
|
2101
|
-
|
|
2102
|
-
await fetch('https://pay.example.com/resource')
|
|
2103
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2104
|
-
|
|
2105
|
-
await fetch('https://other.example.com/resource')
|
|
2106
|
-
expect(new Headers(receivedInits[1]?.headers).get('Accept-Payment')).toBeNull()
|
|
2107
|
-
})
|
|
2108
|
-
|
|
2109
|
-
test('policy: { origins } matches origin regardless of path', async () => {
|
|
2110
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2111
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2112
|
-
receivedInits.push(init)
|
|
2113
|
-
return new Response('OK', { status: 200 })
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
const fetch = Fetch.from({
|
|
2117
|
-
fetch: mockFetch,
|
|
2118
|
-
methods: [noopMethod],
|
|
2119
|
-
acceptPaymentPolicy: { origins: ['https://pay.example.com/some/path'] },
|
|
2120
|
-
})
|
|
2121
|
-
|
|
2122
|
-
await fetch('https://pay.example.com/different/path')
|
|
2123
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2124
|
-
})
|
|
2125
|
-
|
|
2126
|
-
test('policy: { origins } supports wildcard subdomains', async () => {
|
|
2127
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2128
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2129
|
-
receivedInits.push(init)
|
|
2130
|
-
return new Response('OK', { status: 200 })
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
const fetch = Fetch.from({
|
|
2134
|
-
fetch: mockFetch,
|
|
2135
|
-
methods: [noopMethod],
|
|
2136
|
-
acceptPaymentPolicy: { origins: ['*.example.com'] },
|
|
2137
|
-
})
|
|
2138
|
-
|
|
2139
|
-
await fetch('https://pay.example.com/resource')
|
|
2140
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2141
|
-
|
|
2142
|
-
await fetch('https://api.pay.example.com/resource')
|
|
2143
|
-
expect(new Headers(receivedInits[1]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2144
|
-
|
|
2145
|
-
await fetch('https://example.com/resource')
|
|
2146
|
-
expect(new Headers(receivedInits[2]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2147
|
-
|
|
2148
|
-
await fetch('https://notexample.com/resource')
|
|
2149
|
-
expect(new Headers(receivedInits[3]?.headers).get('Accept-Payment')).toBeNull()
|
|
2150
|
-
})
|
|
2151
|
-
|
|
2152
|
-
test('policy: "never" still handles 402 responses', async () => {
|
|
2153
|
-
let callCount = 0
|
|
2154
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2155
|
-
callCount++
|
|
2156
|
-
if (callCount === 1) {
|
|
2157
|
-
expect(new Headers(init?.headers).get('Accept-Payment')).toBeNull()
|
|
2158
|
-
return make402()
|
|
2159
|
-
}
|
|
2160
|
-
expect(new Headers(init?.headers).get('Authorization')).toBe('credential')
|
|
2161
|
-
return new Response('OK', { status: 200 })
|
|
2162
|
-
}
|
|
2163
|
-
|
|
2164
|
-
const fetch = Fetch.from({
|
|
2165
|
-
fetch: mockFetch,
|
|
2166
|
-
methods: [noopMethod],
|
|
2167
|
-
acceptPaymentPolicy: 'never',
|
|
2168
|
-
})
|
|
2169
|
-
|
|
2170
|
-
const response = await fetch('https://example.com/api')
|
|
2171
|
-
expect(response.status).toBe(200)
|
|
2172
|
-
})
|
|
2173
|
-
|
|
2174
|
-
test('policy: explicit Accept-Payment header always takes precedence over "never"', async () => {
|
|
2175
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2176
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2177
|
-
receivedInits.push(init)
|
|
2178
|
-
return new Response('OK', { status: 200 })
|
|
2179
|
-
}
|
|
2180
|
-
|
|
2181
|
-
const fetch = Fetch.from({
|
|
2182
|
-
fetch: mockFetch,
|
|
2183
|
-
methods: [noopMethod],
|
|
2184
|
-
acceptPaymentPolicy: 'never',
|
|
2185
|
-
})
|
|
2186
|
-
|
|
2187
|
-
await fetch('https://example.com/api', {
|
|
2188
|
-
headers: { 'Accept-Payment': 'custom/charge' },
|
|
2189
|
-
})
|
|
2190
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('custom/charge')
|
|
2191
|
-
})
|
|
2192
|
-
|
|
2193
|
-
test('defaults to "always" for Fetch.from', async () => {
|
|
2194
|
-
const receivedInits: (RequestInit | undefined)[] = []
|
|
2195
|
-
const mockFetch: typeof globalThis.fetch = async (_input, init) => {
|
|
2196
|
-
receivedInits.push(init)
|
|
2197
|
-
return new Response('OK', { status: 200 })
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
const fetch = Fetch.from({
|
|
2201
|
-
fetch: mockFetch,
|
|
2202
|
-
methods: [noopMethod],
|
|
2203
|
-
})
|
|
2204
|
-
|
|
2205
|
-
await fetch('https://cross-origin.com/api')
|
|
2206
|
-
expect(new Headers(receivedInits[0]?.headers).get('Accept-Payment')).toBe('test/test')
|
|
2207
|
-
})
|
|
2208
|
-
})
|
|
2209
|
-
|
|
2210
|
-
describe('Fetch.from: input passthrough', () => {
|
|
2211
|
-
test('passes URL input through on both initial and retry calls', async () => {
|
|
2212
|
-
let callCount = 0
|
|
2213
|
-
const receivedInputs: (RequestInfo | URL)[] = []
|
|
2214
|
-
const mockFetch: typeof globalThis.fetch = async (input, _init) => {
|
|
2215
|
-
receivedInputs.push(input)
|
|
2216
|
-
callCount++
|
|
2217
|
-
if (callCount === 1) return make402()
|
|
2218
|
-
return new Response('OK', { status: 200 })
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
const fetch = Fetch.from({
|
|
2222
|
-
fetch: mockFetch,
|
|
2223
|
-
methods: [noopMethod],
|
|
2224
|
-
})
|
|
2225
|
-
|
|
2226
|
-
const url = new URL('https://example.com/resource')
|
|
2227
|
-
await fetch(url)
|
|
2228
|
-
|
|
2229
|
-
expect(receivedInputs[0]).toBe(url)
|
|
2230
|
-
expect(receivedInputs[1]).toBe(url)
|
|
2231
|
-
})
|
|
2232
|
-
})
|
|
2233
|
-
|
|
2234
|
-
describe('Fetch.polyfill', () => {
|
|
2235
|
-
test('default', async () => {
|
|
2236
|
-
Fetch.polyfill({
|
|
2237
|
-
methods: [
|
|
2238
|
-
tempo.charge({
|
|
2239
|
-
account: accounts[1],
|
|
2240
|
-
getClient: () => client,
|
|
2241
|
-
}),
|
|
2242
|
-
],
|
|
2243
|
-
})
|
|
2244
|
-
|
|
2245
|
-
const httpServer = await Http.createServer(async (req, res) => {
|
|
2246
|
-
const result = await Mppx_server.toNodeListener(
|
|
2247
|
-
server.charge({
|
|
2248
|
-
amount: '1',
|
|
2249
|
-
currency: asset,
|
|
2250
|
-
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
2251
|
-
recipient: accounts[0].address,
|
|
2252
|
-
}),
|
|
2253
|
-
)(req, res)
|
|
2254
|
-
if (result.status === 402) return
|
|
2255
|
-
res.end('OK')
|
|
2256
|
-
})
|
|
2257
|
-
|
|
2258
|
-
const response = await fetch(httpServer.url)
|
|
2259
|
-
expect(response.status).toBe(200)
|
|
2260
|
-
|
|
2261
|
-
const receipt = Receipt.fromResponse(response)
|
|
2262
|
-
expect({
|
|
2263
|
-
...receipt,
|
|
2264
|
-
reference: '[reference]',
|
|
2265
|
-
timestamp: '[timestamp]',
|
|
2266
|
-
}).toMatchInlineSnapshot(`
|
|
2267
|
-
{
|
|
2268
|
-
"method": "tempo",
|
|
2269
|
-
"reference": "[reference]",
|
|
2270
|
-
"status": "success",
|
|
2271
|
-
"timestamp": "[timestamp]",
|
|
2272
|
-
}
|
|
2273
|
-
`)
|
|
2274
|
-
|
|
2275
|
-
httpServer.close()
|
|
2276
|
-
Fetch.restore()
|
|
2277
|
-
})
|
|
2278
|
-
})
|
|
2279
|
-
|
|
2280
|
-
describe('Fetch.polyfill / restore', () => {
|
|
2281
|
-
test('restore is a no-op when polyfill was never called', () => {
|
|
2282
|
-
const before = globalThis.fetch
|
|
2283
|
-
Fetch.restore()
|
|
2284
|
-
expect(globalThis.fetch).toBe(before)
|
|
2285
|
-
})
|
|
2286
|
-
|
|
2287
|
-
test('restore reverts to original fetch', () => {
|
|
2288
|
-
const originalFetch = globalThis.fetch
|
|
2289
|
-
|
|
2290
|
-
Fetch.polyfill({ methods: [noopMethod] })
|
|
2291
|
-
expect(globalThis.fetch).not.toBe(originalFetch)
|
|
2292
|
-
|
|
2293
|
-
Fetch.restore()
|
|
2294
|
-
expect(globalThis.fetch).toBe(originalFetch)
|
|
2295
|
-
})
|
|
2296
|
-
|
|
2297
|
-
test('stacked polyfill calls preserve the true original fetch', () => {
|
|
2298
|
-
const originalFetch = globalThis.fetch
|
|
2299
|
-
|
|
2300
|
-
Fetch.polyfill({ methods: [noopMethod] })
|
|
2301
|
-
const firstPolyfill = globalThis.fetch
|
|
2302
|
-
|
|
2303
|
-
Fetch.polyfill({ methods: [noopMethod] })
|
|
2304
|
-
expect(globalThis.fetch).not.toBe(firstPolyfill)
|
|
2305
|
-
|
|
2306
|
-
Fetch.restore()
|
|
2307
|
-
expect(globalThis.fetch).toBe(originalFetch)
|
|
2308
|
-
})
|
|
2309
|
-
|
|
2310
|
-
test('double restore does not clobber fetch', () => {
|
|
2311
|
-
const originalFetch = globalThis.fetch
|
|
2312
|
-
|
|
2313
|
-
Fetch.polyfill({ methods: [noopMethod] })
|
|
2314
|
-
Fetch.restore()
|
|
2315
|
-
expect(globalThis.fetch).toBe(originalFetch)
|
|
2316
|
-
|
|
2317
|
-
Fetch.restore()
|
|
2318
|
-
expect(globalThis.fetch).toBe(originalFetch)
|
|
2319
|
-
})
|
|
2320
|
-
|
|
2321
|
-
test('restore is a no-op when fetch was replaced externally after polyfill', () => {
|
|
2322
|
-
const originalFetch = globalThis.fetch
|
|
2323
|
-
const externalFetch = vi.fn(
|
|
2324
|
-
async (_input: RequestInfo | URL, _init?: RequestInit) =>
|
|
2325
|
-
new Response('external', { status: 200 }),
|
|
2326
|
-
) as unknown as typeof globalThis.fetch
|
|
2327
|
-
|
|
2328
|
-
Fetch.polyfill({ methods: [noopMethod] })
|
|
2329
|
-
globalThis.fetch = externalFetch
|
|
2330
|
-
|
|
2331
|
-
Fetch.restore()
|
|
2332
|
-
expect(globalThis.fetch).toBe(externalFetch)
|
|
2333
|
-
|
|
2334
|
-
globalThis.fetch = originalFetch
|
|
2335
|
-
})
|
|
2336
|
-
})
|