mppx 0.8.14 → 0.8.17
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 +57 -0
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +40 -35
- package/dist/Challenge.js.map +1 -1
- package/dist/Constants.d.ts +17 -17
- package/dist/Constants.d.ts.map +1 -1
- package/dist/Credential.d.ts.map +1 -1
- package/dist/Errors.d.ts +3 -3
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js.map +1 -1
- package/dist/Method.d.ts +35 -5
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +7 -22
- package/dist/Method.js.map +1 -1
- package/dist/Store.d.ts +19 -0
- package/dist/Store.d.ts.map +1 -1
- package/dist/Store.js +43 -0
- package/dist/Store.js.map +1 -1
- package/dist/attestation/Client.d.ts +17 -0
- package/dist/attestation/Client.d.ts.map +1 -0
- package/dist/attestation/Client.js +28 -0
- package/dist/attestation/Client.js.map +1 -0
- package/dist/attestation/Constants.d.ts +15 -0
- package/dist/attestation/Constants.d.ts.map +1 -0
- package/dist/attestation/Constants.js +15 -0
- package/dist/attestation/Constants.js.map +1 -0
- package/dist/attestation/Server.d.ts +4 -0
- package/dist/attestation/Server.d.ts.map +1 -0
- package/dist/attestation/Server.js +8 -0
- package/dist/attestation/Server.js.map +1 -0
- package/dist/attestation/Types.d.ts +76 -0
- package/dist/attestation/Types.d.ts.map +1 -0
- package/dist/attestation/Types.js +2 -0
- package/dist/attestation/Types.js.map +1 -0
- package/dist/attestation/index.d.ts +6 -0
- package/dist/attestation/index.d.ts.map +1 -0
- package/dist/attestation/index.js +6 -0
- package/dist/attestation/index.js.map +1 -0
- package/dist/attestation/internal/HttpMessageSignature.d.ts +73 -0
- package/dist/attestation/internal/HttpMessageSignature.d.ts.map +1 -0
- package/dist/attestation/internal/HttpMessageSignature.js +320 -0
- package/dist/attestation/internal/HttpMessageSignature.js.map +1 -0
- package/dist/attestation/internal/SigningContext.d.ts +4 -0
- package/dist/attestation/internal/SigningContext.d.ts.map +1 -0
- package/dist/attestation/internal/SigningContext.js +11 -0
- package/dist/attestation/internal/SigningContext.js.map +1 -0
- package/dist/cli/account.d.ts +14 -14
- package/dist/cli/account.d.ts.map +1 -1
- package/dist/cli/cli.d.ts +6 -6
- package/dist/cli/cli.d.ts.map +1 -1
- package/dist/cli/cli.js +1 -0
- package/dist/cli/cli.js.map +1 -1
- package/dist/cli/plugins/tempo.js.map +1 -1
- package/dist/cli/sessions/commands.d.ts +4 -4
- package/dist/cli/sessions/commands.d.ts.map +1 -1
- package/dist/cli/sessions/commands.js.map +1 -1
- package/dist/cli/sessions/store.d.ts.map +1 -1
- package/dist/cli/sessions/store.js.map +1 -1
- package/dist/cli/utils.d.ts +1 -1
- package/dist/cli/utils.d.ts.map +1 -1
- package/dist/cli/utils.js.map +1 -1
- package/dist/cli/validate/discovery.js.map +1 -1
- package/dist/cli/validate/index.d.ts +3 -3
- package/dist/cli/validate/index.d.ts.map +1 -1
- package/dist/cli/validate/payment.d.ts.map +1 -1
- package/dist/cli/validate/payment.js +18 -4
- package/dist/cli/validate/payment.js.map +1 -1
- package/dist/client/Mppx.d.ts +3 -0
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +13 -5
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/Transport.d.ts +3 -5
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +1 -17
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +92 -13
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodChallenge.d.ts +2 -0
- package/dist/client/internal/MethodChallenge.d.ts.map +1 -1
- package/dist/client/internal/MethodChallenge.js +4 -0
- package/dist/client/internal/MethodChallenge.js.map +1 -1
- package/dist/client/internal/MethodResponse.d.ts +15 -0
- package/dist/client/internal/MethodResponse.d.ts.map +1 -1
- package/dist/client/internal/MethodResponse.js +10 -0
- package/dist/client/internal/MethodResponse.js.map +1 -1
- package/dist/client/node.js.map +1 -1
- package/dist/discovery/OpenApi.d.ts +3 -5
- package/dist/discovery/OpenApi.d.ts.map +1 -1
- package/dist/discovery/OpenApi.js +13 -34
- package/dist/discovery/OpenApi.js.map +1 -1
- package/dist/discovery/internal/Metadata.d.ts +20 -0
- package/dist/discovery/internal/Metadata.d.ts.map +1 -0
- package/dist/discovery/internal/Metadata.js +36 -0
- package/dist/discovery/internal/Metadata.js.map +1 -0
- package/dist/evm/Methods.d.ts +11 -11
- package/dist/evm/Methods.d.ts.map +1 -1
- package/dist/evm/Types.d.ts +17 -17
- package/dist/evm/Types.d.ts.map +1 -1
- package/dist/evm/client/Charge.d.ts +11 -11
- package/dist/evm/client/Methods.d.ts +11 -11
- package/dist/evm/server/Charge.d.ts +1 -1
- package/dist/evm/server/Charge.d.ts.map +1 -1
- package/dist/evm/server/Charge.js +2 -0
- package/dist/evm/server/Charge.js.map +1 -1
- package/dist/evm/server/Methods.d.ts +11 -11
- package/dist/internal/HeaderCodec.d.ts +2 -2
- package/dist/internal/HeaderCodec.d.ts.map +1 -1
- package/dist/internal/types.d.ts +6 -0
- package/dist/internal/types.d.ts.map +1 -1
- package/dist/middlewares/elysia.js.map +1 -1
- package/dist/middlewares/express.js.map +1 -1
- package/dist/middlewares/hono.js.map +1 -1
- package/dist/middlewares/nextjs.js.map +1 -1
- package/dist/proxy/Proxy.d.ts +2 -0
- package/dist/proxy/Proxy.d.ts.map +1 -1
- package/dist/proxy/Proxy.js +117 -9
- package/dist/proxy/Proxy.js.map +1 -1
- package/dist/proxy/Service.d.ts +36 -1
- package/dist/proxy/Service.d.ts.map +1 -1
- package/dist/proxy/Service.js +9 -8
- package/dist/proxy/Service.js.map +1 -1
- package/dist/proxy/services/anthropic.d.ts.map +1 -1
- package/dist/proxy/services/anthropic.js +1 -0
- package/dist/proxy/services/anthropic.js.map +1 -1
- package/dist/proxy/services/openai.d.ts.map +1 -1
- package/dist/proxy/services/openai.js +1 -0
- package/dist/proxy/services/openai.js.map +1 -1
- package/dist/proxy/services/stripe.d.ts.map +1 -1
- package/dist/proxy/services/stripe.js +1 -0
- package/dist/proxy/services/stripe.js.map +1 -1
- package/dist/server/Mppx.d.ts +75 -13
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +201 -23
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/internal/html/config.d.ts +9 -9
- package/dist/server/internal/html/config.d.ts.map +1 -1
- package/dist/server/internal/html/constants.d.ts +17 -17
- package/dist/server/internal/html/constants.d.ts.map +1 -1
- package/dist/stripe/Methods.d.ts +10 -10
- package/dist/stripe/Methods.d.ts.map +1 -1
- 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/internal/types.d.ts.map +1 -1
- package/dist/stripe/server/Charge.d.ts +11 -11
- package/dist/stripe/server/Charge.d.ts.map +1 -1
- package/dist/stripe/server/Charge.js +49 -1
- package/dist/stripe/server/Charge.js.map +1 -1
- package/dist/stripe/server/Methods.d.ts +137 -48
- package/dist/stripe/server/Methods.d.ts.map +1 -1
- package/dist/stripe/server/Methods.js +259 -4
- package/dist/stripe/server/Methods.js.map +1 -1
- package/dist/stripe/server/internal/deposit-address.d.ts +13 -0
- package/dist/stripe/server/internal/deposit-address.d.ts.map +1 -0
- package/dist/stripe/server/internal/deposit-address.js +28 -0
- package/dist/stripe/server/internal/deposit-address.js.map +1 -0
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/stripe/server/internal/record-payment.d.ts +15 -0
- package/dist/stripe/server/internal/record-payment.d.ts.map +1 -0
- package/dist/stripe/server/internal/record-payment.js +42 -0
- package/dist/stripe/server/internal/record-payment.js.map +1 -0
- package/dist/stripe/server/internal/request.d.ts +25 -0
- package/dist/stripe/server/internal/request.d.ts.map +1 -0
- package/dist/stripe/server/internal/request.js +29 -0
- package/dist/stripe/server/internal/request.js.map +1 -0
- package/dist/tap/Constants.d.ts +28 -0
- package/dist/tap/Constants.d.ts.map +1 -0
- package/dist/tap/Constants.js +32 -0
- package/dist/tap/Constants.js.map +1 -0
- package/dist/tap/Types.d.ts +11 -0
- package/dist/tap/Types.d.ts.map +1 -0
- package/dist/tap/Types.js +2 -0
- package/dist/tap/Types.js.map +1 -0
- package/dist/tap/client/index.d.ts +26 -0
- package/dist/tap/client/index.d.ts.map +1 -0
- package/dist/tap/client/index.js +33 -0
- package/dist/tap/client/index.js.map +1 -0
- package/dist/tap/index.d.ts +5 -0
- package/dist/tap/index.d.ts.map +1 -0
- package/dist/tap/index.js +5 -0
- package/dist/tap/index.js.map +1 -0
- package/dist/tap/server/index.d.ts +20 -0
- package/dist/tap/server/index.d.ts.map +1 -0
- package/dist/tap/server/index.js +36 -0
- package/dist/tap/server/index.js.map +1 -0
- package/dist/tempo/Attribution.d.ts +1 -1
- package/dist/tempo/Methods.d.ts +60 -60
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Proof.d.ts +16 -16
- package/dist/tempo/client/Charge.d.ts +19 -19
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +1 -7
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +38 -38
- package/dist/tempo/client/Methods.js.map +1 -1
- package/dist/tempo/client/Subscription.d.ts +23 -23
- package/dist/tempo/client/Subscription.js.map +1 -1
- package/dist/tempo/internal/address.d.ts +2 -1
- package/dist/tempo/internal/address.d.ts.map +1 -1
- package/dist/tempo/internal/address.js +3 -1
- package/dist/tempo/internal/address.js.map +1 -1
- package/dist/tempo/internal/auto-swap.d.ts.map +1 -1
- package/dist/tempo/internal/auto-swap.js +4 -19
- package/dist/tempo/internal/auto-swap.js.map +1 -1
- package/dist/tempo/internal/defaults.d.ts +6 -6
- package/dist/tempo/internal/defaults.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.d.ts +12 -12
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/internal/proof.d.ts +17 -17
- package/dist/tempo/internal/proof.d.ts.map +1 -1
- package/dist/tempo/legacy/client/Session.d.ts +20 -20
- package/dist/tempo/legacy/client/Session.js.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
- package/dist/tempo/legacy/index.d.ts +1 -3
- package/dist/tempo/legacy/index.d.ts.map +1 -1
- package/dist/tempo/legacy/index.js +1 -3
- package/dist/tempo/legacy/index.js.map +1 -1
- package/dist/tempo/legacy/session/escrow.abi.d.ts +374 -374
- package/dist/tempo/legacy/session/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/server/Charge.d.ts +34 -12
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +44 -39
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +37 -52
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +0 -14
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Relay.d.ts +14 -5
- package/dist/tempo/server/Relay.d.ts.map +1 -1
- package/dist/tempo/server/Relay.js +6 -4
- package/dist/tempo/server/Relay.js.map +1 -1
- package/dist/tempo/server/SponsorBudget.d.ts.map +1 -1
- package/dist/tempo/server/SponsorBudget.js +7 -1
- package/dist/tempo/server/SponsorBudget.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +25 -25
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +2 -0
- package/dist/tempo/server/Subscription.js.map +1 -1
- package/dist/tempo/server/index.d.ts +1 -1
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +1 -1
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +2 -0
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +9 -2
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/CredentialState.d.ts +1 -1
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/ReceiptCoordinator.js.map +1 -1
- package/dist/tempo/session/client/Runtime.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +19 -19
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +128 -14
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +62 -20
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +9 -1
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +36 -13
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/dist/tempo/session/precompile/Chain.d.ts +27 -0
- package/dist/tempo/session/precompile/Chain.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Chain.js +68 -7
- package/dist/tempo/session/precompile/Chain.js.map +1 -1
- package/dist/tempo/session/precompile/Protocol.js.map +1 -1
- package/dist/tempo/session/precompile/Voucher.d.ts +4 -4
- package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/precompile/escrow.abi.d.ts +306 -306
- package/dist/tempo/session/precompile/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/session/server/ChannelStore.d.ts +9 -0
- package/dist/tempo/session/server/ChannelStore.d.ts.map +1 -1
- package/dist/tempo/session/server/ChannelStore.js +16 -8
- package/dist/tempo/session/server/ChannelStore.js.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.d.ts +30 -13
- package/dist/tempo/session/server/CredentialVerification.d.ts.map +1 -1
- package/dist/tempo/session/server/CredentialVerification.js +207 -13
- package/dist/tempo/session/server/CredentialVerification.js.map +1 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +3 -0
- package/dist/tempo/session/server/MeteredStream.d.ts.map +1 -1
- package/dist/tempo/session/server/MeteredStream.js +3 -1
- package/dist/tempo/session/server/MeteredStream.js.map +1 -1
- package/dist/tempo/session/server/Session.d.ts +32 -21
- package/dist/tempo/session/server/Session.d.ts.map +1 -1
- package/dist/tempo/session/server/Session.js +116 -53
- package/dist/tempo/session/server/Session.js.map +1 -1
- package/dist/tempo/session/server/Sse.d.ts +3 -1
- package/dist/tempo/session/server/Sse.d.ts.map +1 -1
- package/dist/tempo/session/server/Sse.js +2 -1
- package/dist/tempo/session/server/Sse.js.map +1 -1
- package/dist/tempo/session/server/Transports.d.ts +1 -1
- package/dist/tempo/session/server/Transports.d.ts.map +1 -1
- package/dist/tempo/session/server/Transports.js +1 -0
- package/dist/tempo/session/server/Transports.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts +6 -1
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js +4 -0
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +38 -866
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.js +14 -3
- package/dist/tempo/subscription/KeyAuthorization.js.map +1 -1
- package/dist/viem/Account.d.ts.map +1 -1
- package/dist/viem/Account.js.map +1 -1
- package/dist/viem/Client.d.ts.map +1 -1
- package/dist/viem/Client.js +6 -1
- package/dist/viem/Client.js.map +1 -1
- package/dist/web-bot-auth/Constants.d.ts +20 -0
- package/dist/web-bot-auth/Constants.d.ts.map +1 -0
- package/dist/web-bot-auth/Constants.js +24 -0
- package/dist/web-bot-auth/Constants.js.map +1 -0
- package/dist/web-bot-auth/Types.d.ts +10 -0
- package/dist/web-bot-auth/Types.d.ts.map +1 -0
- package/dist/web-bot-auth/Types.js +2 -0
- package/dist/web-bot-auth/Types.js.map +1 -0
- package/dist/web-bot-auth/client/index.d.ts +29 -0
- package/dist/web-bot-auth/client/index.d.ts.map +1 -0
- package/dist/web-bot-auth/client/index.js +64 -0
- package/dist/web-bot-auth/client/index.js.map +1 -0
- package/dist/web-bot-auth/index.d.ts +7 -0
- package/dist/web-bot-auth/index.d.ts.map +1 -0
- package/dist/web-bot-auth/index.js +7 -0
- package/dist/web-bot-auth/index.js.map +1 -0
- package/dist/web-bot-auth/internal/JwkThumbprint.d.ts +5 -0
- package/dist/web-bot-auth/internal/JwkThumbprint.d.ts.map +1 -0
- package/dist/web-bot-auth/internal/JwkThumbprint.js +20 -0
- package/dist/web-bot-auth/internal/JwkThumbprint.js.map +1 -0
- package/dist/web-bot-auth/internal/SignatureAgent.d.ts +3 -0
- package/dist/web-bot-auth/internal/SignatureAgent.d.ts.map +1 -0
- package/dist/web-bot-auth/internal/SignatureAgent.js +18 -0
- package/dist/web-bot-auth/internal/SignatureAgent.js.map +1 -0
- package/dist/web-bot-auth/server/index.d.ts +35 -0
- package/dist/web-bot-auth/server/index.d.ts.map +1 -0
- package/dist/web-bot-auth/server/index.js +88 -0
- package/dist/web-bot-auth/server/index.js.map +1 -0
- package/dist/x402/Assets.d.ts.map +1 -1
- package/dist/x402/Types.d.ts +6 -6
- package/dist/x402/Types.d.ts.map +1 -1
- package/dist/x402/server/EvmCharge.d.ts.map +1 -1
- package/dist/x402/server/EvmCharge.js +32 -20
- package/dist/x402/server/EvmCharge.js.map +1 -1
- package/package.json +20 -4
- package/src/Challenge.fuzz.test.ts +80 -47
- package/src/Challenge.test.ts +65 -0
- package/src/Challenge.ts +39 -35
- package/src/Method.ts +82 -68
- package/src/Store.test-d.ts +16 -0
- package/src/Store.test.ts +50 -0
- package/src/Store.ts +68 -0
- package/src/attestation/Attestation.integration.test.ts +208 -0
- package/src/attestation/Client.test.ts +83 -0
- package/src/attestation/Client.ts +34 -0
- package/src/attestation/Constants.ts +15 -0
- package/src/attestation/Server.test.ts +60 -0
- package/src/attestation/Server.ts +12 -0
- package/src/attestation/Types.test-d.ts +54 -0
- package/src/attestation/Types.ts +77 -0
- package/src/attestation/index.ts +5 -0
- package/src/attestation/internal/HttpMessageSignature.test.ts +101 -0
- package/src/attestation/internal/HttpMessageSignature.ts +429 -0
- package/src/attestation/internal/SigningContext.ts +13 -0
- package/src/bin.js +2 -0
- package/src/cli/cli.ts +1 -0
- package/src/cli/validate/payment.ts +18 -4
- package/src/cli/validate.test.ts +16 -0
- package/src/client/Mppx.test-d.ts +17 -0
- package/src/client/Mppx.test.ts +14 -14
- package/src/client/Mppx.ts +25 -4
- package/src/client/Transport.test.ts +12 -45
- package/src/client/Transport.ts +3 -24
- package/src/client/internal/Fetch.test.ts +77 -0
- package/src/client/internal/Fetch.ts +112 -14
- package/src/client/internal/MethodChallenge.ts +5 -0
- package/src/client/internal/MethodResponse.ts +28 -0
- package/src/discovery/OpenApi.test.ts +170 -0
- package/src/discovery/OpenApi.ts +16 -48
- package/src/discovery/internal/Metadata.ts +51 -0
- package/src/evm/server/Charge.ts +6 -1
- package/src/internal/AcceptPayment.test.ts +12 -8
- package/src/internal/types.ts +9 -0
- package/src/mcp/client/McpClient.integration.test.ts +19 -32
- package/src/middlewares/elysia.test.ts +2 -12
- package/src/middlewares/express.test.ts +2 -12
- package/src/middlewares/hono.test.ts +6 -9
- package/src/middlewares/nextjs.test.ts +2 -12
- package/src/proxy/Proxy.test.ts +26 -19
- package/src/proxy/Proxy.ts +147 -9
- package/src/proxy/Proxy.upstreamError.test.ts +364 -0
- package/src/proxy/Service.test.ts +73 -3
- package/src/proxy/Service.ts +50 -23
- package/src/proxy/services/anthropic.ts +1 -0
- package/src/proxy/services/hooks.test-d.ts +14 -0
- package/src/proxy/services/hooks.test.ts +44 -0
- package/src/proxy/services/openai.ts +1 -0
- package/src/proxy/services/stripe.ts +1 -0
- package/src/server/Methods.test-d.ts +88 -0
- package/src/server/Methods.test.ts +69 -0
- package/src/server/Mppx.test-d.ts +141 -7
- package/src/server/Mppx.test.ts +1017 -119
- package/src/server/Mppx.ts +350 -54
- package/src/stripe/internal/constants.ts +1 -1
- package/src/stripe/internal/types.ts +1 -0
- package/src/stripe/server/Charge.test-d.ts +16 -1
- package/src/stripe/server/Charge.test.ts +79 -0
- package/src/stripe/server/Charge.ts +54 -1
- package/src/stripe/server/Methods.test-d.ts +157 -0
- package/src/stripe/server/Methods.test.ts +403 -0
- package/src/stripe/server/Methods.ts +423 -6
- package/src/stripe/server/internal/deposit-address.ts +57 -0
- package/src/stripe/server/internal/html/node_modules/.bin/mppx +3 -3
- package/src/stripe/server/internal/html/package.json +1 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/stripe/server/internal/record-payment.ts +66 -0
- package/src/stripe/server/internal/request.ts +49 -0
- package/src/tap/Constants.ts +32 -0
- package/src/tap/Tap.test.ts +125 -0
- package/src/tap/Types.ts +11 -0
- package/src/tap/client/index.ts +54 -0
- package/src/tap/index.ts +4 -0
- package/src/tap/server/index.ts +48 -0
- package/src/tempo/PublicExports.test-d.ts +3 -3
- package/src/tempo/client/Charge.ts +1 -19
- package/src/tempo/internal/address.test.ts +42 -0
- package/src/tempo/internal/address.ts +3 -3
- package/src/tempo/internal/auto-swap.test.ts +4 -4
- package/src/tempo/internal/auto-swap.ts +7 -50
- package/src/tempo/legacy/README.md +5 -5
- package/src/tempo/legacy/index.ts +1 -3
- package/src/tempo/server/AtomicStore.test-d.ts +13 -0
- package/src/tempo/server/Charge.test.ts +19 -84
- package/src/tempo/server/Charge.ts +92 -56
- package/src/tempo/server/Methods.ts +0 -20
- package/src/tempo/server/Relay.test.ts +161 -7
- package/src/tempo/server/Relay.ts +15 -6
- package/src/tempo/server/SponsorBudget.test.ts +29 -0
- package/src/tempo/server/SponsorBudget.ts +7 -1
- package/src/tempo/server/Subscription.ts +4 -1
- package/src/tempo/server/index.ts +1 -1
- package/src/tempo/server/internal/html/node_modules/.bin/mppx +3 -3
- package/src/tempo/server/internal/html/package.json +1 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/server/internal/transport.test.ts +22 -3
- package/src/tempo/server/internal/transport.ts +15 -3
- package/src/tempo/session/README.md +119 -0
- package/src/tempo/session/client/Session.test.ts +299 -33
- package/src/tempo/session/client/Session.ts +152 -16
- package/src/tempo/session/client/SessionManager.test.ts +216 -9
- package/src/tempo/session/client/SessionManager.ts +63 -17
- package/src/tempo/session/client/Transports.test.ts +22 -0
- package/src/tempo/session/client/Transports.ts +58 -22
- package/src/tempo/session/precompile/Chain.ts +131 -11
- package/src/tempo/session/server/ChannelStore.ts +33 -12
- package/src/tempo/session/server/CredentialVerification.test.ts +4 -4
- package/src/tempo/session/server/CredentialVerification.ts +340 -28
- package/src/tempo/session/server/MeteredStream.ts +5 -1
- package/src/tempo/session/server/Session.test.ts +483 -36
- package/src/tempo/session/server/Session.ts +159 -60
- package/src/tempo/session/server/Sse.test.ts +77 -0
- package/src/tempo/session/server/Sse.ts +8 -1
- package/src/tempo/session/server/Transports.ts +2 -1
- package/src/tempo/session/server/Ws.test.ts +171 -0
- package/src/tempo/session/server/Ws.ts +10 -1
- package/src/tempo/subscription/KeyAuthorization.test.ts +70 -1
- package/src/tempo/subscription/KeyAuthorization.ts +14 -3
- package/src/viem/Client.test.ts +18 -1
- package/src/viem/Client.ts +7 -1
- package/src/web-bot-auth/Constants.ts +24 -0
- package/src/web-bot-auth/Types.ts +9 -0
- package/src/web-bot-auth/WebBotAuth.test.ts +182 -0
- package/src/web-bot-auth/client/index.ts +84 -0
- package/src/web-bot-auth/index.ts +6 -0
- package/src/web-bot-auth/internal/JwkThumbprint.ts +21 -0
- package/src/web-bot-auth/internal/SignatureAgent.ts +18 -0
- package/src/web-bot-auth/server/index.ts +113 -0
- package/src/x402/Exact.e2e.test.ts +121 -81
- package/src/x402/Exact.localnet.test.ts +1 -1
- package/src/x402/server/EvmCharge.ts +35 -19
- 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/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/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/ChannelStore.test.ts +0 -58
- package/src/tempo/legacy/session/ChannelStore.ts +0 -39
package/src/server/Mppx.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import * as http from 'node:http'
|
|
|
3
3
|
import { Challenge, Credential, Errors, Method, Receipt, z } from 'mppx'
|
|
4
4
|
import {
|
|
5
5
|
Mppx as Mppx_client,
|
|
6
|
-
|
|
6
|
+
session as tempo_session_client,
|
|
7
7
|
tempo as tempo_client,
|
|
8
8
|
} from 'mppx/client'
|
|
9
9
|
import { Types as evm_Types } from 'mppx/evm'
|
|
@@ -12,7 +12,6 @@ import { Header as x402_Header, Types as x402_Types, type PaymentPayload } from
|
|
|
12
12
|
import { getTransactionReceipt } from 'viem/actions'
|
|
13
13
|
import { describe, expect, test, vi } from 'vp/test'
|
|
14
14
|
import * as Http from '~test/Http.js'
|
|
15
|
-
import { deployEscrow } from '~test/tempo/legacy/session.js'
|
|
16
15
|
import { accounts, asset, client } from '~test/tempo/viem.js'
|
|
17
16
|
|
|
18
17
|
import type { SessionReceipt } from '../tempo/session/precompile/Protocol.js'
|
|
@@ -60,6 +59,82 @@ describe('create', () => {
|
|
|
60
59
|
})
|
|
61
60
|
})
|
|
62
61
|
|
|
62
|
+
describe('multi-method intent handler', () => {
|
|
63
|
+
test('composes all methods sharing an intent into a single handler', async () => {
|
|
64
|
+
const schema = {
|
|
65
|
+
credential: { payload: z.object({ sig: z.string() }) },
|
|
66
|
+
request: z.object({ amount: z.string(), currency: z.string() }),
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const method1 = Method.toServer(Method.from({ name: 'foo', intent: 'charge', schema }), {
|
|
70
|
+
defaults: { currency: 'usd' },
|
|
71
|
+
verify: async () => ({
|
|
72
|
+
method: 'foo',
|
|
73
|
+
status: 'success' as const,
|
|
74
|
+
reference: '0x1',
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
}),
|
|
77
|
+
})
|
|
78
|
+
const method2 = Method.toServer(Method.from({ name: 'bar', intent: 'charge', schema }), {
|
|
79
|
+
defaults: { currency: 'eur' },
|
|
80
|
+
verify: async () => ({
|
|
81
|
+
method: 'bar',
|
|
82
|
+
status: 'success' as const,
|
|
83
|
+
reference: '0x2',
|
|
84
|
+
timestamp: new Date().toISOString(),
|
|
85
|
+
}),
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const mppx = Mppx.create({ methods: [method1, method2], secretKey, realm })
|
|
89
|
+
|
|
90
|
+
expect(typeof mppx.charge).toBe('function')
|
|
91
|
+
|
|
92
|
+
const result = await (mppx as any).charge({ amount: '100' })(new Request('http://localhost'))
|
|
93
|
+
expect(result.status).toBe(402)
|
|
94
|
+
|
|
95
|
+
const wwwAuth = result.challenge.headers.get('www-authenticate')
|
|
96
|
+
expect(wwwAuth).toContain('method="foo"')
|
|
97
|
+
expect(wwwAuth).toContain('method="bar"')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('single method with unique intent gets direct handler', async () => {
|
|
101
|
+
const schema = {
|
|
102
|
+
credential: { payload: z.object({ sig: z.string() }) },
|
|
103
|
+
request: z.object({ amount: z.string(), currency: z.string() }),
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const method1 = Method.toServer(Method.from({ name: 'foo', intent: 'charge', schema }), {
|
|
107
|
+
defaults: { currency: 'usd' },
|
|
108
|
+
verify: async () => ({
|
|
109
|
+
method: 'foo',
|
|
110
|
+
status: 'success' as const,
|
|
111
|
+
reference: '0x1',
|
|
112
|
+
timestamp: new Date().toISOString(),
|
|
113
|
+
}),
|
|
114
|
+
})
|
|
115
|
+
const method2 = Method.toServer(Method.from({ name: 'bar', intent: 'session', schema }), {
|
|
116
|
+
defaults: { currency: 'usd' },
|
|
117
|
+
verify: async () => ({
|
|
118
|
+
method: 'bar',
|
|
119
|
+
status: 'success' as const,
|
|
120
|
+
reference: '0x2',
|
|
121
|
+
timestamp: new Date().toISOString(),
|
|
122
|
+
}),
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const mppx = Mppx.create({ methods: [method1, method2], secretKey, realm })
|
|
126
|
+
|
|
127
|
+
const result = await (mppx as any).charge({ amount: '100', currency: 'usd' })(
|
|
128
|
+
new Request('http://localhost'),
|
|
129
|
+
)
|
|
130
|
+
expect(result.status).toBe(402)
|
|
131
|
+
|
|
132
|
+
const wwwAuth = result.challenge.headers.get('www-authenticate')
|
|
133
|
+
expect(wwwAuth).toContain('method="foo"')
|
|
134
|
+
expect(wwwAuth).not.toContain('method="bar"')
|
|
135
|
+
})
|
|
136
|
+
})
|
|
137
|
+
|
|
63
138
|
describe('request handler', () => {
|
|
64
139
|
test('returns 402 when no Authorization header', async () => {
|
|
65
140
|
const handler = Mppx.create({ methods: [method], realm, secretKey })
|
|
@@ -1466,6 +1541,131 @@ describe('server events', () => {
|
|
|
1466
1541
|
expect(events).toEqual(['success:tx-standalone:undefined:/standalone'])
|
|
1467
1542
|
})
|
|
1468
1543
|
|
|
1544
|
+
test('per-method onPaymentSuccess hook fires on successful payment', async () => {
|
|
1545
|
+
const calls: string[] = []
|
|
1546
|
+
const serverMethod = Method.toServer(eventCharge, {
|
|
1547
|
+
onPaymentSuccess: async ({ receipt }) => {
|
|
1548
|
+
calls.push(receipt.reference)
|
|
1549
|
+
},
|
|
1550
|
+
async verify() {
|
|
1551
|
+
return receipt('tx-hook')
|
|
1552
|
+
},
|
|
1553
|
+
})
|
|
1554
|
+
const handler = Mppx.create({ methods: [serverMethod], realm, secretKey })
|
|
1555
|
+
|
|
1556
|
+
const challenge = await handler.charge(options())(new Request('https://example.com/resource'))
|
|
1557
|
+
if (challenge.status !== 402) throw new Error()
|
|
1558
|
+
await handler.verifyCredential(
|
|
1559
|
+
Credential.from({
|
|
1560
|
+
challenge: Challenge.fromResponse(challenge.challenge),
|
|
1561
|
+
payload: { token: 'valid' },
|
|
1562
|
+
}),
|
|
1563
|
+
{ request: options() },
|
|
1564
|
+
)
|
|
1565
|
+
|
|
1566
|
+
expect(calls).toEqual(['tx-hook'])
|
|
1567
|
+
})
|
|
1568
|
+
|
|
1569
|
+
test('onPaymentSuccess does not fire for a different method name', async () => {
|
|
1570
|
+
const hookCalls: string[] = []
|
|
1571
|
+
|
|
1572
|
+
const methodWithHook = Method.toServer(eventCharge, {
|
|
1573
|
+
onPaymentSuccess: async ({ receipt }) => {
|
|
1574
|
+
hookCalls.push(receipt.reference)
|
|
1575
|
+
},
|
|
1576
|
+
async verify() {
|
|
1577
|
+
return receipt('tx-a')
|
|
1578
|
+
},
|
|
1579
|
+
})
|
|
1580
|
+
const otherName = Method.from({ name: 'other', intent: 'charge', schema: eventCharge.schema })
|
|
1581
|
+
const methodWithoutHook = Method.toServer(otherName, {
|
|
1582
|
+
async verify() {
|
|
1583
|
+
return receipt('tx-b')
|
|
1584
|
+
},
|
|
1585
|
+
})
|
|
1586
|
+
|
|
1587
|
+
const handler = Mppx.create({ methods: [methodWithHook, methodWithoutHook], realm, secretKey })
|
|
1588
|
+
|
|
1589
|
+
// Pay the method WITHOUT the hook
|
|
1590
|
+
const challenge = await handler.compose(['other/charge', options()])(
|
|
1591
|
+
new Request('https://example.com'),
|
|
1592
|
+
)
|
|
1593
|
+
if (challenge.status !== 402) throw new Error()
|
|
1594
|
+
await handler.verifyCredential(
|
|
1595
|
+
Credential.from({
|
|
1596
|
+
challenge: Challenge.fromResponse(challenge.challenge),
|
|
1597
|
+
payload: { token: 'valid' },
|
|
1598
|
+
}),
|
|
1599
|
+
{ request: options() },
|
|
1600
|
+
)
|
|
1601
|
+
|
|
1602
|
+
expect(hookCalls).toEqual([])
|
|
1603
|
+
})
|
|
1604
|
+
|
|
1605
|
+
test('onPaymentSuccess does not fire for a different intent', async () => {
|
|
1606
|
+
const hookCalls: string[] = []
|
|
1607
|
+
|
|
1608
|
+
const chargeMethod = Method.toServer(eventCharge, {
|
|
1609
|
+
onPaymentSuccess: async ({ receipt }) => {
|
|
1610
|
+
hookCalls.push(receipt.reference)
|
|
1611
|
+
},
|
|
1612
|
+
async verify() {
|
|
1613
|
+
return receipt('tx-charge')
|
|
1614
|
+
},
|
|
1615
|
+
})
|
|
1616
|
+
const sessionSchema = Method.from({
|
|
1617
|
+
name: 'mock',
|
|
1618
|
+
intent: 'session',
|
|
1619
|
+
schema: eventCharge.schema,
|
|
1620
|
+
})
|
|
1621
|
+
const sessionMethod = Method.toServer(sessionSchema, {
|
|
1622
|
+
async verify() {
|
|
1623
|
+
return receipt('tx-session')
|
|
1624
|
+
},
|
|
1625
|
+
})
|
|
1626
|
+
|
|
1627
|
+
const handler = Mppx.create({ methods: [chargeMethod, sessionMethod], realm, secretKey })
|
|
1628
|
+
|
|
1629
|
+
// Pay the session method - charge hook should NOT fire
|
|
1630
|
+
const challenge = await handler.session(options())(new Request('https://example.com'))
|
|
1631
|
+
if (challenge.status !== 402) throw new Error()
|
|
1632
|
+
await handler.verifyCredential(
|
|
1633
|
+
Credential.from({
|
|
1634
|
+
challenge: Challenge.fromResponse(challenge.challenge),
|
|
1635
|
+
payload: { token: 'valid' },
|
|
1636
|
+
}),
|
|
1637
|
+
{ request: options() },
|
|
1638
|
+
)
|
|
1639
|
+
|
|
1640
|
+
expect(hookCalls).toEqual([])
|
|
1641
|
+
})
|
|
1642
|
+
|
|
1643
|
+
test('per-method onPaymentSuccess hook errors do not propagate', async () => {
|
|
1644
|
+
const serverMethod = Method.toServer(eventCharge, {
|
|
1645
|
+
onPaymentSuccess: async () => {
|
|
1646
|
+
throw new Error('hook failure')
|
|
1647
|
+
},
|
|
1648
|
+
async verify() {
|
|
1649
|
+
return receipt('tx-error')
|
|
1650
|
+
},
|
|
1651
|
+
})
|
|
1652
|
+
const handler = Mppx.create({ methods: [serverMethod], realm, secretKey })
|
|
1653
|
+
|
|
1654
|
+
const first = await handler.charge(options())(new Request('https://example.com/resource'))
|
|
1655
|
+
expect(first.status).toBe(402)
|
|
1656
|
+
if (first.status !== 402) throw new Error()
|
|
1657
|
+
|
|
1658
|
+
const result = await handler.verifyCredential(
|
|
1659
|
+
Credential.from({
|
|
1660
|
+
challenge: Challenge.fromResponse(first.challenge),
|
|
1661
|
+
payload: { token: 'valid' },
|
|
1662
|
+
}),
|
|
1663
|
+
{ request: options() },
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
expect(result.reference).toBe('tx-error')
|
|
1667
|
+
})
|
|
1668
|
+
|
|
1469
1669
|
test('emits payment failure from standalone verifyCredential failures', async () => {
|
|
1470
1670
|
const events: string[] = []
|
|
1471
1671
|
const capturedRequest = {
|
|
@@ -1901,10 +2101,10 @@ describe('compose', () => {
|
|
|
1901
2101
|
},
|
|
1902
2102
|
})
|
|
1903
2103
|
|
|
1904
|
-
const
|
|
1905
|
-
alias: '
|
|
2104
|
+
const alternateSessionMethod = Method.toServer(mockSession, {
|
|
2105
|
+
alias: 'alternateSession',
|
|
1906
2106
|
async verify() {
|
|
1907
|
-
return mockReceipt('
|
|
2107
|
+
return mockReceipt('alternate')
|
|
1908
2108
|
},
|
|
1909
2109
|
})
|
|
1910
2110
|
|
|
@@ -1947,9 +2147,9 @@ describe('compose', () => {
|
|
|
1947
2147
|
unitType: 'request',
|
|
1948
2148
|
}
|
|
1949
2149
|
|
|
1950
|
-
const
|
|
2150
|
+
const alternateSessionChallengeOpts = {
|
|
1951
2151
|
...sessionChallengeOpts,
|
|
1952
|
-
methodDetails: { sessionProtocol: '
|
|
2152
|
+
methodDetails: { sessionProtocol: 'alternate' },
|
|
1953
2153
|
}
|
|
1954
2154
|
|
|
1955
2155
|
test('returns 402 with multiple WWW-Authenticate headers when no credential', async () => {
|
|
@@ -1968,165 +2168,694 @@ describe('compose', () => {
|
|
|
1968
2168
|
expect(wwwAuth).toContain('method="beta"')
|
|
1969
2169
|
})
|
|
1970
2170
|
|
|
1971
|
-
test('
|
|
2171
|
+
test('selectOffers filters by method, configured request, and incoming request', async () => {
|
|
2172
|
+
const challenged: string[] = []
|
|
2173
|
+
const input = new Request('https://example.com/resource?cards=disabled', {
|
|
2174
|
+
body: 'request body',
|
|
2175
|
+
method: 'POST',
|
|
2176
|
+
})
|
|
1972
2177
|
const mppx = Mppx.create({
|
|
1973
|
-
methods: [
|
|
2178
|
+
methods: [alphaMethod, betaMethod],
|
|
1974
2179
|
realm,
|
|
1975
2180
|
secretKey,
|
|
2181
|
+
async selectOffers(offers, { request }) {
|
|
2182
|
+
expect(request).not.toBe(input)
|
|
2183
|
+
expect(await request.text()).toBe('request body')
|
|
2184
|
+
expect(offers).toMatchObject([
|
|
2185
|
+
{
|
|
2186
|
+
key: 'alpha/charge',
|
|
2187
|
+
method: { intent: 'charge', name: 'alpha' },
|
|
2188
|
+
request: { amount: challengeOpts.amount },
|
|
2189
|
+
},
|
|
2190
|
+
{
|
|
2191
|
+
key: 'beta/charge',
|
|
2192
|
+
method: { intent: 'charge', name: 'beta' },
|
|
2193
|
+
request: { amount: '50' },
|
|
2194
|
+
},
|
|
2195
|
+
])
|
|
2196
|
+
|
|
2197
|
+
if (new URL(request.url).searchParams.get('cards') === 'disabled')
|
|
2198
|
+
return offers.filter(
|
|
2199
|
+
(offer) => offer.method.name !== 'beta' || BigInt(offer.request.amount) >= 100n,
|
|
2200
|
+
)
|
|
2201
|
+
return offers
|
|
2202
|
+
},
|
|
2203
|
+
})
|
|
2204
|
+
mppx.onChallengeCreated(({ method }) => {
|
|
2205
|
+
challenged.push(method.name)
|
|
1976
2206
|
})
|
|
1977
2207
|
|
|
1978
2208
|
const result = await mppx.compose(
|
|
1979
|
-
[
|
|
1980
|
-
[
|
|
1981
|
-
)(
|
|
2209
|
+
[alphaMethod, challengeOpts],
|
|
2210
|
+
[betaMethod, { ...challengeOpts, amount: '50' }],
|
|
2211
|
+
)(input)
|
|
1982
2212
|
|
|
1983
2213
|
expect(result.status).toBe(402)
|
|
1984
2214
|
if (result.status !== 402) throw new Error()
|
|
2215
|
+
expect(
|
|
2216
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
2217
|
+
).toEqual(['alpha'])
|
|
2218
|
+
expect(challenged).toEqual(['alpha'])
|
|
2219
|
+
expect(input.bodyUsed).toBe(false)
|
|
2220
|
+
})
|
|
2221
|
+
|
|
2222
|
+
test('offer selectors receive deeply immutable method and request snapshots', async () => {
|
|
2223
|
+
const guardedSession = Method.toServer(mockSession, {
|
|
2224
|
+
canOffer({ request }) {
|
|
2225
|
+
expect(Object.isFrozen(request)).toBe(true)
|
|
2226
|
+
expect(Object.isFrozen(request.methodDetails)).toBe(true)
|
|
2227
|
+
expect(Reflect.set(request.methodDetails, 'sessionProtocol', 'changed')).toBe(false)
|
|
2228
|
+
return true
|
|
2229
|
+
},
|
|
2230
|
+
async verify() {
|
|
2231
|
+
return mockReceipt('v2')
|
|
2232
|
+
},
|
|
2233
|
+
})
|
|
2234
|
+
const mppx = Mppx.create({
|
|
2235
|
+
methods: [guardedSession],
|
|
2236
|
+
realm,
|
|
2237
|
+
secretKey,
|
|
2238
|
+
selectOffers(offers) {
|
|
2239
|
+
const offer = offers[0]!
|
|
2240
|
+
expect(Object.isFrozen(offers)).toBe(true)
|
|
2241
|
+
expect(Object.isFrozen(offer)).toBe(true)
|
|
2242
|
+
expect(Object.isFrozen(offer.method)).toBe(true)
|
|
2243
|
+
expect(Object.keys(offer.method)).toEqual(['intent', 'name'])
|
|
2244
|
+
expect(Object.isFrozen(offer.request)).toBe(true)
|
|
2245
|
+
expect(Object.isFrozen(offer.request.methodDetails)).toBe(true)
|
|
2246
|
+
expect(Reflect.set(offer.method, 'name', 'changed')).toBe(false)
|
|
2247
|
+
expect(Reflect.set(offer.request.methodDetails, 'sessionProtocol', 'changed')).toBe(false)
|
|
2248
|
+
return offers
|
|
2249
|
+
},
|
|
2250
|
+
})
|
|
2251
|
+
const handler = mppx.compose([guardedSession, sessionChallengeOpts])
|
|
1985
2252
|
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
2253
|
+
for (let call = 0; call < 2; call++) {
|
|
2254
|
+
const result = await handler(new Request('https://example.com/resource'))
|
|
2255
|
+
expect(result.status).toBe(402)
|
|
2256
|
+
if (result.status !== 402) throw new Error()
|
|
2257
|
+
const challenge = Challenge.fromResponse(result.challenge)
|
|
2258
|
+
expect(challenge.method).toBe('tempo')
|
|
2259
|
+
expect(challenge.request.methodDetails).toEqual({ sessionProtocol: 'v2' })
|
|
2260
|
+
}
|
|
1991
2261
|
})
|
|
1992
2262
|
|
|
1993
|
-
test('
|
|
2263
|
+
test.each(['consumed', 'locked'] as const)(
|
|
2264
|
+
'offer hooks preserve request metadata when the upstream body is $state',
|
|
2265
|
+
async (state) => {
|
|
2266
|
+
const hookInputs: Request[] = []
|
|
2267
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
2268
|
+
async canOffer({ input }) {
|
|
2269
|
+
hookInputs.push(input)
|
|
2270
|
+
expect(await input.text()).toBe('')
|
|
2271
|
+
return true
|
|
2272
|
+
},
|
|
2273
|
+
async verify() {
|
|
2274
|
+
return mockReceipt('beta')
|
|
2275
|
+
},
|
|
2276
|
+
})
|
|
2277
|
+
const input = new Request('https://example.com/resource?source=upstream', {
|
|
2278
|
+
body: 'request body',
|
|
2279
|
+
headers: { 'X-Request-Id': 'request-1' },
|
|
2280
|
+
method: 'POST',
|
|
2281
|
+
})
|
|
2282
|
+
const reader = state === 'locked' ? input.body!.getReader() : undefined
|
|
2283
|
+
if (state === 'consumed') await input.text()
|
|
2284
|
+
const mppx = Mppx.create({
|
|
2285
|
+
methods: [guardedBeta],
|
|
2286
|
+
realm,
|
|
2287
|
+
secretKey,
|
|
2288
|
+
async selectOffers(offers, { request }) {
|
|
2289
|
+
hookInputs.push(request)
|
|
2290
|
+
expect(await request.text()).toBe('')
|
|
2291
|
+
return offers
|
|
2292
|
+
},
|
|
2293
|
+
})
|
|
2294
|
+
|
|
2295
|
+
try {
|
|
2296
|
+
const result = await mppx.compose([guardedBeta, challengeOpts])(input)
|
|
2297
|
+
expect(result.status).toBe(402)
|
|
2298
|
+
} finally {
|
|
2299
|
+
reader?.releaseLock()
|
|
2300
|
+
}
|
|
2301
|
+
|
|
2302
|
+
expect(hookInputs).toHaveLength(2)
|
|
2303
|
+
for (const hookInput of hookInputs) {
|
|
2304
|
+
expect(hookInput).not.toBe(input)
|
|
2305
|
+
expect(hookInput.method).toBe('POST')
|
|
2306
|
+
expect(hookInput.headers.get('X-Request-Id')).toBe('request-1')
|
|
2307
|
+
expect(new URL(hookInput.url).searchParams.get('source')).toBe('upstream')
|
|
2308
|
+
}
|
|
2309
|
+
},
|
|
2310
|
+
)
|
|
2311
|
+
|
|
2312
|
+
test('method canOffer runs before selectOffers with offer and upstream request context', async () => {
|
|
2313
|
+
const calls: string[] = []
|
|
2314
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
2315
|
+
async canOffer({ input, request }) {
|
|
2316
|
+
calls.push('canOffer')
|
|
2317
|
+
expect(input).not.toBe(upstreamRequest)
|
|
2318
|
+
expect(await input.text()).toBe('request body')
|
|
2319
|
+
expect(request).toEqual({
|
|
2320
|
+
amount: '50',
|
|
2321
|
+
currency: challengeOpts.currency,
|
|
2322
|
+
decimals: challengeOpts.decimals,
|
|
2323
|
+
recipient: challengeOpts.recipient,
|
|
2324
|
+
})
|
|
2325
|
+
return BigInt(request.amount) >= 100n
|
|
2326
|
+
},
|
|
2327
|
+
async verify() {
|
|
2328
|
+
return mockReceipt('beta')
|
|
2329
|
+
},
|
|
2330
|
+
})
|
|
2331
|
+
const upstreamRequest = new Request('https://example.com/resource', {
|
|
2332
|
+
body: 'request body',
|
|
2333
|
+
method: 'POST',
|
|
2334
|
+
})
|
|
1994
2335
|
const mppx = Mppx.create({
|
|
1995
|
-
methods: [
|
|
2336
|
+
methods: [alphaMethod, guardedBeta],
|
|
1996
2337
|
realm,
|
|
1997
2338
|
secretKey,
|
|
2339
|
+
selectOffers(offers) {
|
|
2340
|
+
calls.push('selectOffers')
|
|
2341
|
+
expect(offers.map((offer) => offer.method.name)).toEqual(['alpha'])
|
|
2342
|
+
return offers
|
|
2343
|
+
},
|
|
1998
2344
|
})
|
|
1999
2345
|
|
|
2000
2346
|
const result = await mppx.compose(
|
|
2001
|
-
[
|
|
2002
|
-
[
|
|
2003
|
-
)(
|
|
2004
|
-
new Request('https://example.com/resource', {
|
|
2005
|
-
headers: { 'Accept-Payment': 'tempo/session' },
|
|
2006
|
-
}),
|
|
2007
|
-
)
|
|
2347
|
+
[alphaMethod, challengeOpts],
|
|
2348
|
+
[guardedBeta, { ...challengeOpts, amount: '50' }],
|
|
2349
|
+
)(upstreamRequest)
|
|
2008
2350
|
|
|
2009
2351
|
expect(result.status).toBe(402)
|
|
2010
2352
|
if (result.status !== 402) throw new Error()
|
|
2353
|
+
expect(
|
|
2354
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
2355
|
+
).toEqual(['alpha'])
|
|
2356
|
+
expect(calls).toEqual(['canOffer', 'selectOffers'])
|
|
2357
|
+
expect(upstreamRequest.bodyUsed).toBe(false)
|
|
2358
|
+
})
|
|
2359
|
+
|
|
2360
|
+
test('method canOffer evaluates each configured offer without selectOffers', async () => {
|
|
2361
|
+
const amounts: string[] = []
|
|
2362
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
2363
|
+
canOffer({ request }) {
|
|
2364
|
+
amounts.push(request.amount)
|
|
2365
|
+
return BigInt(request.amount) >= 100n
|
|
2366
|
+
},
|
|
2367
|
+
async verify() {
|
|
2368
|
+
return mockReceipt('beta')
|
|
2369
|
+
},
|
|
2370
|
+
})
|
|
2371
|
+
const mppx = Mppx.create({ methods: [guardedBeta], realm, secretKey })
|
|
2011
2372
|
|
|
2012
|
-
|
|
2373
|
+
const handler = mppx.compose(
|
|
2374
|
+
[guardedBeta, { ...challengeOpts, amount: '50' }],
|
|
2375
|
+
[guardedBeta, { ...challengeOpts, amount: '100' }],
|
|
2376
|
+
)
|
|
2377
|
+
expect(handler._internal?.offers).toHaveLength(2)
|
|
2378
|
+
|
|
2379
|
+
const result = await handler(new Request('https://example.com/resource'))
|
|
2380
|
+
|
|
2381
|
+
expect(result.status).toBe(402)
|
|
2382
|
+
if (result.status !== 402) throw new Error()
|
|
2383
|
+
expect(amounts).toEqual(['50', '100'])
|
|
2384
|
+
expect(
|
|
2385
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.request.amount),
|
|
2386
|
+
).toEqual(['100'])
|
|
2013
2387
|
})
|
|
2014
2388
|
|
|
2015
|
-
test('
|
|
2016
|
-
const
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2389
|
+
test('static compose applies method canOffer', async () => {
|
|
2390
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
2391
|
+
canOffer: () => false,
|
|
2392
|
+
async verify() {
|
|
2393
|
+
return mockReceipt('beta')
|
|
2394
|
+
},
|
|
2020
2395
|
})
|
|
2396
|
+
const mppx = Mppx.create({ methods: [alphaMethod, guardedBeta], realm, secretKey })
|
|
2021
2397
|
|
|
2022
|
-
const result = await
|
|
2023
|
-
|
|
2024
|
-
|
|
2398
|
+
const result = await Mppx.compose(
|
|
2399
|
+
mppx.alpha.charge(challengeOpts),
|
|
2400
|
+
mppx.beta.charge(challengeOpts),
|
|
2401
|
+
)(new Request('https://example.com/resource'))
|
|
2025
2402
|
|
|
2026
2403
|
expect(result.status).toBe(402)
|
|
2027
2404
|
if (result.status !== 402) throw new Error()
|
|
2028
|
-
expect(
|
|
2029
|
-
|
|
2030
|
-
|
|
2405
|
+
expect(
|
|
2406
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
2407
|
+
).toEqual(['alpha'])
|
|
2031
2408
|
})
|
|
2032
2409
|
|
|
2033
|
-
test('
|
|
2034
|
-
const
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2410
|
+
test('method canOffer does not gate a direct method handler', async () => {
|
|
2411
|
+
const canOffer = vi.fn(() => false)
|
|
2412
|
+
const guardedAlpha = Method.toServer(mockChargeA, {
|
|
2413
|
+
canOffer,
|
|
2414
|
+
async verify() {
|
|
2415
|
+
return mockReceipt('alpha')
|
|
2416
|
+
},
|
|
2038
2417
|
})
|
|
2039
|
-
const
|
|
2040
|
-
new Request('https://example.com/resource'),
|
|
2041
|
-
)
|
|
2042
|
-
expect(tip1034ChallengeResponse.status).toBe(402)
|
|
2043
|
-
if (tip1034ChallengeResponse.status !== 402) throw new Error()
|
|
2044
|
-
const tip1034Challenge = Challenge.fromResponse(tip1034ChallengeResponse.challenge)
|
|
2418
|
+
const mppx = Mppx.create({ methods: [guardedAlpha], realm, secretKey })
|
|
2045
2419
|
|
|
2046
|
-
const
|
|
2047
|
-
|
|
2048
|
-
payload: { token: 'valid' },
|
|
2049
|
-
})
|
|
2050
|
-
const legacyResult = await mppx.compose([mppx.tempo.sessionLegacy, legacySessionChallengeOpts])(
|
|
2051
|
-
new Request('https://example.com/resource', {
|
|
2052
|
-
headers: { Authorization: credential },
|
|
2053
|
-
}),
|
|
2420
|
+
const result = await mppx.alpha.charge(challengeOpts)(
|
|
2421
|
+
new Request('https://example.com/resource'),
|
|
2054
2422
|
)
|
|
2055
2423
|
|
|
2056
|
-
expect(
|
|
2424
|
+
expect(result.status).toBe(402)
|
|
2425
|
+
expect(canOffer).not.toHaveBeenCalled()
|
|
2057
2426
|
})
|
|
2058
2427
|
|
|
2059
|
-
test('
|
|
2060
|
-
const
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2428
|
+
test('method canOffer filters before preflight and authorize hooks', async () => {
|
|
2429
|
+
const authorize = vi.fn()
|
|
2430
|
+
const preflight = vi.fn()
|
|
2431
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
2432
|
+
async authorize() {
|
|
2433
|
+
authorize()
|
|
2434
|
+
return undefined
|
|
2435
|
+
},
|
|
2436
|
+
canOffer: () => false,
|
|
2437
|
+
async preflight() {
|
|
2438
|
+
preflight()
|
|
2439
|
+
return undefined
|
|
2440
|
+
},
|
|
2441
|
+
async verify() {
|
|
2442
|
+
return mockReceipt('beta')
|
|
2443
|
+
},
|
|
2064
2444
|
})
|
|
2065
|
-
const
|
|
2066
|
-
const legacyChallenge = await mppx.challenge.tempo.sessionLegacy(legacySessionChallengeOpts)
|
|
2445
|
+
const mppx = Mppx.create({ methods: [alphaMethod, guardedBeta], realm, secretKey })
|
|
2067
2446
|
|
|
2068
|
-
const
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
}),
|
|
2073
|
-
)
|
|
2074
|
-
const legacyReceipt = await mppx.verifyCredential(
|
|
2075
|
-
Credential.serialize({
|
|
2076
|
-
challenge: legacyChallenge,
|
|
2077
|
-
payload: { token: 'valid' },
|
|
2078
|
-
}),
|
|
2079
|
-
)
|
|
2447
|
+
const result = await mppx.compose(
|
|
2448
|
+
[alphaMethod, challengeOpts],
|
|
2449
|
+
[guardedBeta, challengeOpts],
|
|
2450
|
+
)(new Request('https://example.com/resource'))
|
|
2080
2451
|
|
|
2081
|
-
expect(
|
|
2082
|
-
expect(
|
|
2452
|
+
expect(result.status).toBe(402)
|
|
2453
|
+
expect(preflight).not.toHaveBeenCalled()
|
|
2454
|
+
expect(authorize).not.toHaveBeenCalled()
|
|
2083
2455
|
})
|
|
2084
2456
|
|
|
2085
|
-
test(
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
}
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2457
|
+
test.each([
|
|
2458
|
+
{
|
|
2459
|
+
canOffer: () => 'yes',
|
|
2460
|
+
expected: 'canOffer() must return a boolean',
|
|
2461
|
+
name: 'non-boolean result',
|
|
2462
|
+
},
|
|
2463
|
+
{
|
|
2464
|
+
canOffer: () => false,
|
|
2465
|
+
expected: 'No payment offers are available for this request',
|
|
2466
|
+
name: 'no eligible offers',
|
|
2467
|
+
},
|
|
2468
|
+
])('rejects invalid method offer availability: $name', async ({ canOffer, expected }) => {
|
|
2469
|
+
const guardedAlpha = Method.toServer(mockChargeA, {
|
|
2470
|
+
canOffer: canOffer as never,
|
|
2471
|
+
async verify() {
|
|
2472
|
+
return mockReceipt('alpha')
|
|
2473
|
+
},
|
|
2094
2474
|
})
|
|
2475
|
+
const mppx = Mppx.create({ methods: [guardedAlpha], realm, secretKey })
|
|
2095
2476
|
|
|
2096
2477
|
await expect(
|
|
2097
|
-
mppx.
|
|
2098
|
-
|
|
2099
|
-
challenge: unmarkedLegacyChallenge,
|
|
2100
|
-
payload: { token: 'valid' },
|
|
2101
|
-
}),
|
|
2102
|
-
),
|
|
2103
|
-
).resolves.toMatchObject({ reference: 'tx-v1' })
|
|
2478
|
+
mppx.compose([guardedAlpha, challengeOpts])(new Request('https://example.com/resource')),
|
|
2479
|
+
).rejects.toThrow(expected)
|
|
2104
2480
|
})
|
|
2105
2481
|
|
|
2106
|
-
test('
|
|
2107
|
-
const
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2482
|
+
test('propagates method canOffer rejections', async () => {
|
|
2483
|
+
const error = new Error('method offer selection failed')
|
|
2484
|
+
const guardedAlpha = Method.toServer(mockChargeA, {
|
|
2485
|
+
async canOffer() {
|
|
2486
|
+
throw error
|
|
2487
|
+
},
|
|
2488
|
+
async verify() {
|
|
2489
|
+
return mockReceipt('alpha')
|
|
2490
|
+
},
|
|
2115
2491
|
})
|
|
2492
|
+
const mppx = Mppx.create({ methods: [guardedAlpha], realm, secretKey })
|
|
2116
2493
|
|
|
2117
2494
|
await expect(
|
|
2118
|
-
mppx.
|
|
2119
|
-
).rejects.
|
|
2495
|
+
mppx.compose([guardedAlpha, challengeOpts])(new Request('https://example.com/resource')),
|
|
2496
|
+
).rejects.toBe(error)
|
|
2120
2497
|
})
|
|
2121
2498
|
|
|
2122
|
-
test('
|
|
2123
|
-
const
|
|
2499
|
+
test('method canOffer does not prevent redemption of an issued offer', async () => {
|
|
2500
|
+
const canOffer = vi.fn(() => false)
|
|
2501
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
2502
|
+
canOffer,
|
|
2503
|
+
async verify() {
|
|
2504
|
+
return mockReceipt('beta')
|
|
2505
|
+
},
|
|
2506
|
+
})
|
|
2507
|
+
const mppx = Mppx.create({ methods: [guardedBeta], realm, secretKey })
|
|
2508
|
+
const challenge = await mppx.challenge.beta.charge(challengeOpts)
|
|
2509
|
+
const credential = Credential.from({ challenge, payload: { token: 'valid' } })
|
|
2124
2510
|
|
|
2125
|
-
const result = await mppx.compose([
|
|
2126
|
-
new Request('https://example.com/resource'
|
|
2511
|
+
const result = await mppx.compose([guardedBeta, challengeOpts])(
|
|
2512
|
+
new Request('https://example.com/resource', {
|
|
2513
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
2514
|
+
}),
|
|
2127
2515
|
)
|
|
2128
2516
|
|
|
2129
|
-
expect(result.status).toBe(
|
|
2517
|
+
expect(result.status).toBe(200)
|
|
2518
|
+
expect(canOffer).not.toHaveBeenCalled()
|
|
2519
|
+
})
|
|
2520
|
+
|
|
2521
|
+
test('selectOffers does not prevent redemption of a previously issued offer', async () => {
|
|
2522
|
+
let selectionCount = 0
|
|
2523
|
+
const mppx = Mppx.create({
|
|
2524
|
+
methods: [alphaMethod, betaMethod],
|
|
2525
|
+
realm,
|
|
2526
|
+
secretKey,
|
|
2527
|
+
selectOffers(offers, { request }) {
|
|
2528
|
+
selectionCount++
|
|
2529
|
+
return request.headers.has('X-Hide-Beta')
|
|
2530
|
+
? offers.filter((offer) => offer.method.name !== 'beta')
|
|
2531
|
+
: offers
|
|
2532
|
+
},
|
|
2533
|
+
})
|
|
2534
|
+
const handler = mppx.compose([alphaMethod, challengeOpts], [betaMethod, challengeOpts])
|
|
2535
|
+
const challengeResult = await handler(new Request('https://example.com/resource'))
|
|
2536
|
+
expect(challengeResult.status).toBe(402)
|
|
2537
|
+
if (challengeResult.status !== 402) throw new Error()
|
|
2538
|
+
const betaChallenge = Challenge.fromResponseList(challengeResult.challenge).find(
|
|
2539
|
+
(challenge) => challenge.method === 'beta',
|
|
2540
|
+
)!
|
|
2541
|
+
const credential = Credential.from({
|
|
2542
|
+
challenge: betaChallenge,
|
|
2543
|
+
payload: { token: 'valid' },
|
|
2544
|
+
})
|
|
2545
|
+
|
|
2546
|
+
const result = await handler(
|
|
2547
|
+
new Request('https://example.com/resource', {
|
|
2548
|
+
headers: {
|
|
2549
|
+
Authorization: Credential.serialize(credential),
|
|
2550
|
+
'X-Hide-Beta': 'true',
|
|
2551
|
+
},
|
|
2552
|
+
}),
|
|
2553
|
+
)
|
|
2554
|
+
|
|
2555
|
+
expect(result.status).toBe(200)
|
|
2556
|
+
expect(selectionCount).toBe(1)
|
|
2557
|
+
})
|
|
2558
|
+
|
|
2559
|
+
test.each(['malformed', 'unmatched'] as const)(
|
|
2560
|
+
'applies selectOffers before rejecting a $kind payment credential',
|
|
2561
|
+
async (kind) => {
|
|
2562
|
+
let selectionCount = 0
|
|
2563
|
+
const mppx = Mppx.create({
|
|
2564
|
+
methods: [alphaMethod, betaMethod],
|
|
2565
|
+
realm,
|
|
2566
|
+
secretKey,
|
|
2567
|
+
selectOffers(offers) {
|
|
2568
|
+
selectionCount++
|
|
2569
|
+
return offers.filter((offer) => offer.method.name === 'beta')
|
|
2570
|
+
},
|
|
2571
|
+
})
|
|
2572
|
+
const authorization =
|
|
2573
|
+
kind === 'malformed'
|
|
2574
|
+
? 'Payment invalid'
|
|
2575
|
+
: Credential.serialize(
|
|
2576
|
+
Credential.from({
|
|
2577
|
+
challenge: {
|
|
2578
|
+
...(await mppx.challenge.alpha.charge(challengeOpts)),
|
|
2579
|
+
method: 'unknown',
|
|
2580
|
+
},
|
|
2581
|
+
payload: { token: 'invalid' },
|
|
2582
|
+
}),
|
|
2583
|
+
)
|
|
2584
|
+
|
|
2585
|
+
const result = await mppx.compose(
|
|
2586
|
+
[alphaMethod, challengeOpts],
|
|
2587
|
+
[betaMethod, challengeOpts],
|
|
2588
|
+
)(
|
|
2589
|
+
new Request('https://example.com/resource', {
|
|
2590
|
+
headers: { Authorization: authorization },
|
|
2591
|
+
}),
|
|
2592
|
+
)
|
|
2593
|
+
|
|
2594
|
+
expect(result.status).toBe(402)
|
|
2595
|
+
if (result.status !== 402) throw new Error()
|
|
2596
|
+
expect(Challenge.fromResponse(result.challenge).method).toBe('beta')
|
|
2597
|
+
expect(selectionCount).toBe(1)
|
|
2598
|
+
},
|
|
2599
|
+
)
|
|
2600
|
+
|
|
2601
|
+
test.each([
|
|
2602
|
+
{
|
|
2603
|
+
expected: 'return an array',
|
|
2604
|
+
name: 'non-array result',
|
|
2605
|
+
select: () => null,
|
|
2606
|
+
},
|
|
2607
|
+
{
|
|
2608
|
+
expected: 'select at least one offer',
|
|
2609
|
+
name: 'empty selection',
|
|
2610
|
+
select: (offers: readonly unknown[]) => offers.slice(0, 0),
|
|
2611
|
+
},
|
|
2612
|
+
{
|
|
2613
|
+
expected: 'only offers from its input array',
|
|
2614
|
+
name: 'copied offer',
|
|
2615
|
+
select: (offers: readonly unknown[]) => [{ ...(offers[0] as object) }],
|
|
2616
|
+
},
|
|
2617
|
+
{
|
|
2618
|
+
expected: 'preserve offer order',
|
|
2619
|
+
name: 'duplicate offer',
|
|
2620
|
+
select: (offers: readonly unknown[]) => [offers[0], offers[0]],
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
expected: 'preserve offer order',
|
|
2624
|
+
name: 'reordered offers',
|
|
2625
|
+
select: (offers: readonly unknown[]) => [offers[1], offers[0]],
|
|
2626
|
+
},
|
|
2627
|
+
])('rejects invalid selectOffers result: $name', async ({ expected, select }) => {
|
|
2628
|
+
const mppx = Mppx.create({
|
|
2629
|
+
methods: [alphaMethod, betaMethod],
|
|
2630
|
+
realm,
|
|
2631
|
+
secretKey,
|
|
2632
|
+
selectOffers: select as never,
|
|
2633
|
+
})
|
|
2634
|
+
const handler = mppx.compose([alphaMethod, challengeOpts], [betaMethod, challengeOpts])
|
|
2635
|
+
|
|
2636
|
+
await expect(handler(new Request('https://example.com/resource'))).rejects.toThrow(expected)
|
|
2637
|
+
})
|
|
2638
|
+
|
|
2639
|
+
test('retains all configured offers as discovery metadata when selectOffers is dynamic', () => {
|
|
2640
|
+
const mppx = Mppx.create({
|
|
2641
|
+
methods: [alphaMethod, betaMethod],
|
|
2642
|
+
realm,
|
|
2643
|
+
secretKey,
|
|
2644
|
+
selectOffers: (offers) => offers.slice(0, 1),
|
|
2645
|
+
})
|
|
2646
|
+
const configured = mppx.compose([alphaMethod, challengeOpts], [betaMethod, challengeOpts])
|
|
2647
|
+
|
|
2648
|
+
expect(configured._internal?.offers).toMatchObject([
|
|
2649
|
+
{
|
|
2650
|
+
_canonicalRequest: {
|
|
2651
|
+
amount: challengeOpts.amount,
|
|
2652
|
+
currency: challengeOpts.currency,
|
|
2653
|
+
recipient: challengeOpts.recipient,
|
|
2654
|
+
},
|
|
2655
|
+
intent: 'charge',
|
|
2656
|
+
name: 'alpha',
|
|
2657
|
+
},
|
|
2658
|
+
{
|
|
2659
|
+
_canonicalRequest: {
|
|
2660
|
+
amount: challengeOpts.amount,
|
|
2661
|
+
currency: challengeOpts.currency,
|
|
2662
|
+
recipient: challengeOpts.recipient,
|
|
2663
|
+
},
|
|
2664
|
+
intent: 'charge',
|
|
2665
|
+
name: 'beta',
|
|
2666
|
+
},
|
|
2667
|
+
])
|
|
2668
|
+
|
|
2669
|
+
const custom = async () =>
|
|
2670
|
+
({
|
|
2671
|
+
status: 402,
|
|
2672
|
+
challenge: new Response(null, { status: 402 }),
|
|
2673
|
+
}) as const
|
|
2674
|
+
const composed = Mppx.compose(mppx.alpha.charge(challengeOpts), custom)
|
|
2675
|
+
expect(composed._internal?.offers).toMatchObject([{ intent: 'charge', name: 'alpha' }])
|
|
2676
|
+
})
|
|
2677
|
+
|
|
2678
|
+
test('does not run preflight or authorize hooks for filtered offers', async () => {
|
|
2679
|
+
const authorize = vi.fn()
|
|
2680
|
+
const preflight = vi.fn()
|
|
2681
|
+
const filteredMethod = Method.toServer(mockChargeB, {
|
|
2682
|
+
async authorize() {
|
|
2683
|
+
authorize()
|
|
2684
|
+
return undefined
|
|
2685
|
+
},
|
|
2686
|
+
async preflight() {
|
|
2687
|
+
preflight()
|
|
2688
|
+
return undefined
|
|
2689
|
+
},
|
|
2690
|
+
async verify() {
|
|
2691
|
+
return mockReceipt('beta')
|
|
2692
|
+
},
|
|
2693
|
+
})
|
|
2694
|
+
const mppx = Mppx.create({
|
|
2695
|
+
methods: [alphaMethod, filteredMethod],
|
|
2696
|
+
realm,
|
|
2697
|
+
secretKey,
|
|
2698
|
+
selectOffers: (offers) => offers.filter((offer) => offer.method.name !== 'beta'),
|
|
2699
|
+
})
|
|
2700
|
+
|
|
2701
|
+
const result = await mppx.compose(
|
|
2702
|
+
[alphaMethod, challengeOpts],
|
|
2703
|
+
[filteredMethod, challengeOpts],
|
|
2704
|
+
)(new Request('https://example.com/resource'))
|
|
2705
|
+
|
|
2706
|
+
expect(result.status).toBe(402)
|
|
2707
|
+
expect(preflight).not.toHaveBeenCalled()
|
|
2708
|
+
expect(authorize).not.toHaveBeenCalled()
|
|
2709
|
+
})
|
|
2710
|
+
|
|
2711
|
+
test('selectOffers distinguishes aliased methods that share a canonical key', async () => {
|
|
2712
|
+
const mppx = Mppx.create({
|
|
2713
|
+
methods: [tip1034SessionMethod, alternateSessionMethod],
|
|
2714
|
+
realm,
|
|
2715
|
+
secretKey,
|
|
2716
|
+
selectOffers(offers) {
|
|
2717
|
+
expect(offers.map((offer) => offer.key)).toEqual(['tempo/session', 'tempo/session'])
|
|
2718
|
+
expect(offers.map((offer) => offer.method.alias)).toEqual([undefined, 'alternateSession'])
|
|
2719
|
+
expect(offers.map((offer) => offer.request.methodDetails)).toEqual([
|
|
2720
|
+
{ sessionProtocol: 'v2' },
|
|
2721
|
+
{ sessionProtocol: 'alternate' },
|
|
2722
|
+
])
|
|
2723
|
+
return offers.filter((offer) => offer.method.alias === 'alternateSession')
|
|
2724
|
+
},
|
|
2725
|
+
})
|
|
2726
|
+
|
|
2727
|
+
const result = await mppx.compose(
|
|
2728
|
+
[mppx.tempo.session, sessionChallengeOpts],
|
|
2729
|
+
[mppx.tempo.alternateSession, alternateSessionChallengeOpts],
|
|
2730
|
+
)(new Request('https://example.com/resource'))
|
|
2731
|
+
|
|
2732
|
+
expect(result.status).toBe(402)
|
|
2733
|
+
if (result.status !== 402) throw new Error()
|
|
2734
|
+
expect(Challenge.fromResponse(result.challenge).request.methodDetails).toEqual({
|
|
2735
|
+
sessionProtocol: 'alternate',
|
|
2736
|
+
})
|
|
2737
|
+
})
|
|
2738
|
+
|
|
2739
|
+
test('selectOffers receives schema-transformed request values', async () => {
|
|
2740
|
+
const transformedCharge = Method.from({
|
|
2741
|
+
name: 'transformed',
|
|
2742
|
+
intent: 'charge',
|
|
2743
|
+
schema: {
|
|
2744
|
+
credential: {
|
|
2745
|
+
payload: z.object({ token: z.string() }),
|
|
2746
|
+
},
|
|
2747
|
+
request: z.pipe(
|
|
2748
|
+
z.object({
|
|
2749
|
+
amount: z.string(),
|
|
2750
|
+
currency: z.string(),
|
|
2751
|
+
decimals: z.number(),
|
|
2752
|
+
recipient: z.string(),
|
|
2753
|
+
}),
|
|
2754
|
+
z.transform(({ amount, currency, decimals, recipient }) => ({
|
|
2755
|
+
amount: String(Number(amount) * 10 ** decimals),
|
|
2756
|
+
currency,
|
|
2757
|
+
methodDetails: { decimals },
|
|
2758
|
+
recipient,
|
|
2759
|
+
})),
|
|
2760
|
+
),
|
|
2761
|
+
},
|
|
2762
|
+
})
|
|
2763
|
+
const transformedMethod = Method.toServer(transformedCharge, {
|
|
2764
|
+
async verify() {
|
|
2765
|
+
return mockReceipt('transformed')
|
|
2766
|
+
},
|
|
2767
|
+
})
|
|
2768
|
+
const mppx = Mppx.create({
|
|
2769
|
+
methods: [transformedMethod],
|
|
2770
|
+
realm,
|
|
2771
|
+
secretKey,
|
|
2772
|
+
selectOffers(offers) {
|
|
2773
|
+
expect(offers[0]?.request).toEqual({
|
|
2774
|
+
amount: '125',
|
|
2775
|
+
currency: challengeOpts.currency,
|
|
2776
|
+
methodDetails: { decimals: 2 },
|
|
2777
|
+
recipient: challengeOpts.recipient,
|
|
2778
|
+
})
|
|
2779
|
+
return offers
|
|
2780
|
+
},
|
|
2781
|
+
})
|
|
2782
|
+
|
|
2783
|
+
const result = await mppx.compose([
|
|
2784
|
+
transformedMethod,
|
|
2785
|
+
{ ...challengeOpts, amount: '1.25', decimals: 2 },
|
|
2786
|
+
])(new Request('https://example.com/resource'))
|
|
2787
|
+
|
|
2788
|
+
expect(result.status).toBe(402)
|
|
2789
|
+
})
|
|
2790
|
+
|
|
2791
|
+
test('propagates selectOffers rejections', async () => {
|
|
2792
|
+
const error = new Error('offer selection failed')
|
|
2793
|
+
const mppx = Mppx.create({
|
|
2794
|
+
methods: [alphaMethod],
|
|
2795
|
+
realm,
|
|
2796
|
+
secretKey,
|
|
2797
|
+
async selectOffers() {
|
|
2798
|
+
throw error
|
|
2799
|
+
},
|
|
2800
|
+
})
|
|
2801
|
+
|
|
2802
|
+
await expect(
|
|
2803
|
+
mppx.compose([alphaMethod, challengeOpts])(new Request('https://example.com/resource')),
|
|
2804
|
+
).rejects.toBe(error)
|
|
2805
|
+
})
|
|
2806
|
+
|
|
2807
|
+
test('broadcasts duplicate tempo/session variants in compose order', async () => {
|
|
2808
|
+
const mppx = Mppx.create({
|
|
2809
|
+
methods: [tip1034SessionMethod, alternateSessionMethod],
|
|
2810
|
+
realm,
|
|
2811
|
+
secretKey,
|
|
2812
|
+
})
|
|
2813
|
+
|
|
2814
|
+
const result = await mppx.compose(
|
|
2815
|
+
[mppx.tempo.session, sessionChallengeOpts],
|
|
2816
|
+
[mppx.tempo.alternateSession, alternateSessionChallengeOpts],
|
|
2817
|
+
)(new Request('https://example.com/resource'))
|
|
2818
|
+
|
|
2819
|
+
expect(result.status).toBe(402)
|
|
2820
|
+
if (result.status !== 402) throw new Error()
|
|
2821
|
+
|
|
2822
|
+
const challenges = Challenge.fromResponseList(result.challenge)
|
|
2823
|
+
expect(challenges.map((challenge) => challenge.request.methodDetails)).toEqual([
|
|
2824
|
+
{ sessionProtocol: 'v2' },
|
|
2825
|
+
{ sessionProtocol: 'alternate' },
|
|
2826
|
+
])
|
|
2827
|
+
})
|
|
2828
|
+
|
|
2829
|
+
test('keeps duplicate tempo/session variants when Accept-Payment allows tempo/session', async () => {
|
|
2830
|
+
const mppx = Mppx.create({
|
|
2831
|
+
methods: [tip1034SessionMethod, alternateSessionMethod],
|
|
2832
|
+
realm,
|
|
2833
|
+
secretKey,
|
|
2834
|
+
})
|
|
2835
|
+
|
|
2836
|
+
const result = await mppx.compose(
|
|
2837
|
+
[mppx.tempo.session, sessionChallengeOpts],
|
|
2838
|
+
[mppx.tempo.alternateSession, alternateSessionChallengeOpts],
|
|
2839
|
+
)(
|
|
2840
|
+
new Request('https://example.com/resource', {
|
|
2841
|
+
headers: { 'Accept-Payment': 'tempo/session' },
|
|
2842
|
+
}),
|
|
2843
|
+
)
|
|
2844
|
+
|
|
2845
|
+
expect(result.status).toBe(402)
|
|
2846
|
+
if (result.status !== 402) throw new Error()
|
|
2847
|
+
|
|
2848
|
+
expect(Challenge.fromResponseList(result.challenge)).toHaveLength(2)
|
|
2849
|
+
})
|
|
2850
|
+
|
|
2851
|
+
test('returns composed x402 challenge headers when no credential', async () => {
|
|
2852
|
+
const mppx = Mppx.create({ methods: [x402Method], realm, secretKey })
|
|
2853
|
+
|
|
2854
|
+
const result = await mppx.compose(['evm/charge', { amount: '0.01' }])(
|
|
2855
|
+
new Request('https://example.com/resource'),
|
|
2856
|
+
)
|
|
2857
|
+
|
|
2858
|
+
expect(result.status).toBe(402)
|
|
2130
2859
|
if (result.status !== 402) throw new Error()
|
|
2131
2860
|
|
|
2132
2861
|
expect(result.challenge.headers.get('WWW-Authenticate')).toContain('method="evm"')
|
|
@@ -2164,6 +2893,44 @@ describe('compose', () => {
|
|
|
2164
2893
|
expect(paymentRequired.accepts.map((accepted) => accepted.amount)).toEqual(['10000'])
|
|
2165
2894
|
})
|
|
2166
2895
|
|
|
2896
|
+
test('selectOffers filters x402 challenge headers', async () => {
|
|
2897
|
+
const mppx = Mppx.create({
|
|
2898
|
+
methods: [alphaMethod, x402Method],
|
|
2899
|
+
realm,
|
|
2900
|
+
secretKey,
|
|
2901
|
+
selectOffers: (offers) => offers.filter((offer) => offer.method.name !== 'evm'),
|
|
2902
|
+
})
|
|
2903
|
+
|
|
2904
|
+
const result = await mppx.compose(
|
|
2905
|
+
[alphaMethod, challengeOpts],
|
|
2906
|
+
['evm/charge', { amount: '0.01' }],
|
|
2907
|
+
)(new Request('https://example.com/resource'))
|
|
2908
|
+
|
|
2909
|
+
expect(result.status).toBe(402)
|
|
2910
|
+
if (result.status !== 402) throw new Error()
|
|
2911
|
+
expect(
|
|
2912
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
2913
|
+
).toEqual(['alpha'])
|
|
2914
|
+
expect(result.challenge.headers.get(x402_Types.paymentRequiredHeader)).toBeNull()
|
|
2915
|
+
})
|
|
2916
|
+
|
|
2917
|
+
test('method canOffer filters x402 challenge headers', async () => {
|
|
2918
|
+
const guardedX402 = { ...x402Method, canOffer: () => false }
|
|
2919
|
+
const mppx = Mppx.create({ methods: [alphaMethod, guardedX402], realm, secretKey })
|
|
2920
|
+
|
|
2921
|
+
const result = await mppx.compose(
|
|
2922
|
+
[alphaMethod, challengeOpts],
|
|
2923
|
+
[guardedX402, { amount: '0.01' }],
|
|
2924
|
+
)(new Request('https://example.com/resource'))
|
|
2925
|
+
|
|
2926
|
+
expect(result.status).toBe(402)
|
|
2927
|
+
if (result.status !== 402) throw new Error()
|
|
2928
|
+
expect(
|
|
2929
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
2930
|
+
).toEqual(['alpha'])
|
|
2931
|
+
expect(result.challenge.headers.get(x402_Types.paymentRequiredHeader)).toBeNull()
|
|
2932
|
+
})
|
|
2933
|
+
|
|
2167
2934
|
test('keeps pure x402 challenge headers when Payment auth challenges are present', async () => {
|
|
2168
2935
|
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey })
|
|
2169
2936
|
const pureX402 = async () =>
|
|
@@ -2275,6 +3042,55 @@ describe('compose', () => {
|
|
|
2275
3042
|
expect(challenges[0]?.method).toBe('beta')
|
|
2276
3043
|
})
|
|
2277
3044
|
|
|
3045
|
+
test('applies selectOffers before Accept-Payment negotiation', async () => {
|
|
3046
|
+
const mppx = Mppx.create({
|
|
3047
|
+
methods: [alphaMethod, betaMethod],
|
|
3048
|
+
realm,
|
|
3049
|
+
secretKey,
|
|
3050
|
+
selectOffers: (offers) => offers.filter((offer) => offer.method.name !== 'beta'),
|
|
3051
|
+
})
|
|
3052
|
+
|
|
3053
|
+
const result = await mppx.compose(
|
|
3054
|
+
[alphaMethod, challengeOpts],
|
|
3055
|
+
[betaMethod, challengeOpts],
|
|
3056
|
+
)(
|
|
3057
|
+
new Request('https://example.com/resource', {
|
|
3058
|
+
headers: { 'Accept-Payment': 'beta/charge' },
|
|
3059
|
+
}),
|
|
3060
|
+
)
|
|
3061
|
+
|
|
3062
|
+
expect(result.status).toBe(402)
|
|
3063
|
+
if (result.status !== 402) throw new Error()
|
|
3064
|
+
expect(
|
|
3065
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
3066
|
+
).toEqual(['alpha'])
|
|
3067
|
+
})
|
|
3068
|
+
|
|
3069
|
+
test('applies method canOffer before Accept-Payment negotiation', async () => {
|
|
3070
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
3071
|
+
canOffer: () => false,
|
|
3072
|
+
async verify() {
|
|
3073
|
+
return mockReceipt('beta')
|
|
3074
|
+
},
|
|
3075
|
+
})
|
|
3076
|
+
const mppx = Mppx.create({ methods: [alphaMethod, guardedBeta], realm, secretKey })
|
|
3077
|
+
|
|
3078
|
+
const result = await mppx.compose(
|
|
3079
|
+
[alphaMethod, challengeOpts],
|
|
3080
|
+
[guardedBeta, challengeOpts],
|
|
3081
|
+
)(
|
|
3082
|
+
new Request('https://example.com/resource', {
|
|
3083
|
+
headers: { 'Accept-Payment': 'beta/charge' },
|
|
3084
|
+
}),
|
|
3085
|
+
)
|
|
3086
|
+
|
|
3087
|
+
expect(result.status).toBe(402)
|
|
3088
|
+
if (result.status !== 402) throw new Error()
|
|
3089
|
+
expect(
|
|
3090
|
+
Challenge.fromResponseList(result.challenge).map((challenge) => challenge.method),
|
|
3091
|
+
).toEqual(['alpha'])
|
|
3092
|
+
})
|
|
3093
|
+
|
|
2278
3094
|
test('filters compose x402 challenge headers using Accept-Payment', async () => {
|
|
2279
3095
|
const mppx = Mppx.create({ methods: [alphaMethod, x402Method], realm, secretKey })
|
|
2280
3096
|
|
|
@@ -2429,6 +3245,34 @@ describe('compose', () => {
|
|
|
2429
3245
|
expect(result.status).toBe(200)
|
|
2430
3246
|
})
|
|
2431
3247
|
|
|
3248
|
+
test('dispatches credentials to a nested composed handler', async () => {
|
|
3249
|
+
const mppx = Mppx.create({ methods: [alphaMethod, betaMethod], realm, secretKey })
|
|
3250
|
+
const handle = Mppx.compose(
|
|
3251
|
+
mppx.alpha.charge(challengeOpts),
|
|
3252
|
+
Mppx.compose(mppx.beta.charge(challengeOpts)),
|
|
3253
|
+
)
|
|
3254
|
+
|
|
3255
|
+
const firstResult = await handle(new Request('https://example.com/resource'))
|
|
3256
|
+
expect(firstResult.status).toBe(402)
|
|
3257
|
+
if (firstResult.status !== 402) throw new Error()
|
|
3258
|
+
|
|
3259
|
+
const betaChallenge = Challenge.fromResponseList(firstResult.challenge).find(
|
|
3260
|
+
(challenge) => challenge.method === 'beta',
|
|
3261
|
+
)!
|
|
3262
|
+
const credential = Credential.from({
|
|
3263
|
+
challenge: betaChallenge,
|
|
3264
|
+
payload: { token: 'valid' },
|
|
3265
|
+
})
|
|
3266
|
+
|
|
3267
|
+
const result = await handle(
|
|
3268
|
+
new Request('https://example.com/resource', {
|
|
3269
|
+
headers: { Authorization: Credential.serialize(credential) },
|
|
3270
|
+
}),
|
|
3271
|
+
)
|
|
3272
|
+
|
|
3273
|
+
expect(result.status).toBe(200)
|
|
3274
|
+
})
|
|
3275
|
+
|
|
2432
3276
|
test('returns 402 when credential method does not match any handler', async () => {
|
|
2433
3277
|
const mppx = Mppx.create({ methods: [alphaMethod], realm, secretKey })
|
|
2434
3278
|
|
|
@@ -2797,6 +3641,64 @@ describe('compose', () => {
|
|
|
2797
3641
|
expect(dataValues[1]!.config).toEqual({ providerB: true })
|
|
2798
3642
|
})
|
|
2799
3643
|
|
|
3644
|
+
test('selectOffers filters html payment options', async () => {
|
|
3645
|
+
const mppx = Mppx.create({
|
|
3646
|
+
methods: [alphaWithHtml, betaWithHtml],
|
|
3647
|
+
realm,
|
|
3648
|
+
secretKey,
|
|
3649
|
+
selectOffers: (offers) => offers.filter((offer) => offer.method.name !== 'beta'),
|
|
3650
|
+
})
|
|
3651
|
+
|
|
3652
|
+
const result = await mppx.compose(
|
|
3653
|
+
[alphaWithHtml, challengeOpts],
|
|
3654
|
+
[betaWithHtml, challengeOpts],
|
|
3655
|
+
)(
|
|
3656
|
+
new Request('https://example.com/resource', {
|
|
3657
|
+
headers: { Accept: 'text/html' },
|
|
3658
|
+
}),
|
|
3659
|
+
)
|
|
3660
|
+
|
|
3661
|
+
expect(result.status).toBe(402)
|
|
3662
|
+
if (result.status !== 402) throw new Error()
|
|
3663
|
+
const body = await result.challenge.text()
|
|
3664
|
+
expect(body).toContain('/alpha-bundle.js')
|
|
3665
|
+
expect(body).not.toContain('/beta-bundle.js')
|
|
3666
|
+
expect(body).not.toContain('role="tablist"')
|
|
3667
|
+
|
|
3668
|
+
const dataMatch = body.match(
|
|
3669
|
+
/<script[^>]*id="__MPPX_DATA__"[^>]*type="application\/json"[^>]*>\s*([\s\S]*?)\s*<\/script>/s,
|
|
3670
|
+
)
|
|
3671
|
+
const dataMap = JSON.parse(dataMatch![1]!.replace(/\\u003c/g, '<'))
|
|
3672
|
+
expect(Object.values(dataMap)).toMatchObject([{ label: 'alpha' }])
|
|
3673
|
+
})
|
|
3674
|
+
|
|
3675
|
+
test('method canOffer filters html payment options', async () => {
|
|
3676
|
+
const guardedBeta = Method.toServer(mockChargeB, {
|
|
3677
|
+
canOffer: () => false,
|
|
3678
|
+
html: htmlOptionsB,
|
|
3679
|
+
async verify() {
|
|
3680
|
+
return mockReceipt('beta')
|
|
3681
|
+
},
|
|
3682
|
+
})
|
|
3683
|
+
const mppx = Mppx.create({ methods: [alphaWithHtml, guardedBeta], realm, secretKey })
|
|
3684
|
+
|
|
3685
|
+
const result = await mppx.compose(
|
|
3686
|
+
[alphaWithHtml, challengeOpts],
|
|
3687
|
+
[guardedBeta, challengeOpts],
|
|
3688
|
+
)(
|
|
3689
|
+
new Request('https://example.com/resource', {
|
|
3690
|
+
headers: { Accept: 'text/html' },
|
|
3691
|
+
}),
|
|
3692
|
+
)
|
|
3693
|
+
|
|
3694
|
+
expect(result.status).toBe(402)
|
|
3695
|
+
if (result.status !== 402) throw new Error()
|
|
3696
|
+
const body = await result.challenge.text()
|
|
3697
|
+
expect(body).toContain('/alpha-bundle.js')
|
|
3698
|
+
expect(body).not.toContain('/beta-bundle.js')
|
|
3699
|
+
expect(body).not.toContain('role="tablist"')
|
|
3700
|
+
})
|
|
3701
|
+
|
|
2800
3702
|
test('returns html without tabs when single method has html', async () => {
|
|
2801
3703
|
const mppx = Mppx.create({
|
|
2802
3704
|
methods: [alphaWithHtml, betaMethod],
|
|
@@ -5594,15 +6496,13 @@ describe('verifyCredential', () => {
|
|
|
5594
6496
|
})
|
|
5595
6497
|
|
|
5596
6498
|
test('verifies real session open and voucher credentials created from 402 responses', async () => {
|
|
5597
|
-
const escrowContract = await deployEscrow()
|
|
5598
6499
|
const server = Mppx.create({
|
|
5599
6500
|
methods: [
|
|
5600
|
-
tempo.
|
|
6501
|
+
tempo.session({
|
|
5601
6502
|
store: Store.memory(),
|
|
5602
6503
|
getClient: () => client,
|
|
5603
6504
|
account: accounts[0],
|
|
5604
6505
|
currency: asset,
|
|
5605
|
-
escrowContract,
|
|
5606
6506
|
chainId: client.chain!.id,
|
|
5607
6507
|
}),
|
|
5608
6508
|
],
|
|
@@ -5622,7 +6522,7 @@ describe('verifyCredential', () => {
|
|
|
5622
6522
|
|
|
5623
6523
|
const httpServer = await Http.createServer(async (req, res) => {
|
|
5624
6524
|
const result = await Mppx.toNodeListener(
|
|
5625
|
-
server.session({ amount: '1', unitType: 'request' }),
|
|
6525
|
+
server.session({ amount: '1', suggestedDeposit: '10', unitType: 'request' }),
|
|
5626
6526
|
)(req, res)
|
|
5627
6527
|
if (result.status === 402) return
|
|
5628
6528
|
res.end('OK')
|
|
@@ -5657,22 +6557,20 @@ describe('verifyCredential', () => {
|
|
|
5657
6557
|
})
|
|
5658
6558
|
|
|
5659
6559
|
test('verifyCredential charges repeated session voucher content requests when capturedRequest is provided', async () => {
|
|
5660
|
-
const escrowContract = await deployEscrow()
|
|
5661
6560
|
const server = Mppx.create({
|
|
5662
6561
|
methods: [
|
|
5663
|
-
tempo.
|
|
6562
|
+
tempo.session({
|
|
5664
6563
|
store: Store.memory(),
|
|
5665
6564
|
getClient: () => client,
|
|
5666
6565
|
account: accounts[0],
|
|
5667
6566
|
currency: asset,
|
|
5668
|
-
escrowContract,
|
|
5669
6567
|
chainId: client.chain!.id,
|
|
5670
6568
|
}),
|
|
5671
6569
|
],
|
|
5672
6570
|
realm,
|
|
5673
6571
|
secretKey,
|
|
5674
6572
|
})
|
|
5675
|
-
const route = server.session({ amount: '1', unitType: 'request' })
|
|
6573
|
+
const route = server.session({ amount: '1', suggestedDeposit: '10', unitType: 'request' })
|
|
5676
6574
|
const clientMppx = Mppx_client.create({
|
|
5677
6575
|
polyfill: false,
|
|
5678
6576
|
methods: [
|