mppx 0.11.0 → 0.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +83 -0
- package/README.md +22 -1
- package/dist/Method.d.ts +7 -1
- package/dist/Method.js +7 -2
- package/dist/Receipt.d.ts +2 -0
- package/dist/Receipt.js +2 -0
- package/dist/cli/agent.d.ts +3 -0
- package/dist/cli/agent.js +20 -0
- package/dist/cli/cli.d.ts +9 -1
- package/dist/cli/cli.js +134 -136
- package/dist/cli/plugins/index.js +12 -14
- package/dist/cli/plugins/tempo.js +9 -2
- package/dist/cli/utils.d.ts +11 -1
- package/dist/cli/utils.js +15 -2
- package/dist/cli/validate/challenge.d.ts +2 -0
- package/dist/cli/validate/challenge.js +19 -4
- package/dist/cli/validate/discovery.d.ts +1 -0
- package/dist/cli/validate/discovery.js +10 -1
- package/dist/cli/validate/payment.js +63 -24
- package/dist/client/Methods.d.ts +0 -2
- package/dist/client/Methods.js +0 -2
- package/dist/client/Mppx.js +1 -0
- package/dist/client/index.d.ts +1 -1
- package/dist/client/index.js +1 -1
- package/dist/client/internal/Fetch.d.ts +2 -0
- package/dist/client/internal/Fetch.js +8 -2
- package/dist/client/internal/MethodChallenge.d.ts +1 -0
- package/dist/evm/Types.js +1 -1
- package/dist/evm/server/Charge.js +5 -0
- package/dist/evm/server/Methods.d.ts +164 -3
- package/dist/evm/server/Methods.js +50 -2
- package/dist/integrations/x402/Evm.js +4 -3
- package/dist/internal/HeaderCodec.d.ts +2 -2
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.js +4 -7
- package/dist/middlewares/elysia.d.ts +11 -8
- package/dist/middlewares/elysia.js +67 -14
- package/dist/middlewares/express.js +13 -6
- package/dist/middlewares/hono.js +4 -4
- package/dist/middlewares/nextjs.js +9 -5
- package/dist/proxy/internal/Headers.js +65 -0
- package/dist/proxy/services/stripe.js +1 -0
- package/dist/server/Mppx.d.ts +18 -2
- package/dist/server/Mppx.js +204 -92
- package/dist/server/NodeListener.js +71 -6
- package/dist/server/Request.d.ts +11 -1
- package/dist/server/Request.js +132 -13
- package/dist/server/Transport.d.ts +14 -1
- package/dist/server/Transport.js +1 -0
- package/dist/server/internal/html/compose.main.gen.d.ts +1 -1
- package/dist/server/internal/html/serviceWorker.gen.d.ts +1 -1
- package/dist/stripe/server/Methods.d.ts +10 -3
- package/dist/stripe/server/Methods.js +19 -6
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/Methods.d.ts +1 -0
- package/dist/tempo/Methods.js +1 -0
- package/dist/tempo/client/Charge.js +17 -13
- package/dist/tempo/client/Methods.d.ts +1 -5
- package/dist/tempo/client/Methods.js +10 -5
- package/dist/tempo/client/ResolveAccount.d.ts +3 -0
- package/dist/tempo/client/index.d.ts +0 -3
- package/dist/tempo/client/index.js +0 -2
- package/dist/tempo/internal/currencies.d.ts +19 -0
- package/dist/tempo/internal/currencies.js +41 -0
- package/dist/tempo/internal/defaults.d.ts +5 -3
- package/dist/tempo/internal/defaults.js +6 -2
- package/dist/tempo/internal/fee-payer.d.ts +11 -7
- package/dist/tempo/internal/fee-payer.js +30 -11
- package/dist/tempo/internal/machine-token-charge.d.ts +23 -0
- package/dist/tempo/internal/machine-token-charge.js +2 -1
- package/dist/tempo/legacy/index.d.ts +1 -3
- package/dist/tempo/legacy/index.js +1 -3
- package/dist/tempo/server/Charge.d.ts +12 -1
- package/dist/tempo/server/Charge.js +109 -78
- package/dist/tempo/server/Methods.d.ts +5048 -40
- package/dist/tempo/server/Methods.js +104 -13
- package/dist/tempo/server/Subscription.d.ts +1 -1
- package/dist/tempo/server/Subscription.js +31 -4
- 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.js +28 -11
- package/dist/tempo/session/client/ChannelOps.js +5 -0
- package/dist/tempo/session/client/Session.d.ts +1 -0
- package/dist/tempo/session/client/Session.js +7 -4
- package/dist/tempo/session/client/SessionManager.js +3 -0
- package/dist/tempo/session/client/Transports.d.ts +8 -1
- package/dist/tempo/session/client/Transports.js +12 -3
- package/dist/tempo/session/precompile/Chain.js +16 -1
- package/dist/tempo/session/precompile/Protocol.d.ts +2 -0
- package/dist/tempo/session/precompile/Voucher.d.ts +16 -0
- package/dist/tempo/session/precompile/Voucher.js +48 -0
- package/dist/tempo/session/server/ChannelStore.d.ts +22 -2
- package/dist/tempo/session/server/ChannelStore.js +37 -7
- package/dist/tempo/session/server/CredentialVerification.d.ts +6 -1
- package/dist/tempo/session/server/CredentialVerification.js +56 -19
- package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
- package/dist/tempo/session/server/MeteredStream.js +85 -29
- package/dist/tempo/session/server/RequestState.d.ts +6 -0
- package/dist/tempo/session/server/RequestState.js +11 -4
- package/dist/tempo/session/server/Session.d.ts +2 -1
- package/dist/tempo/session/server/Session.js +10 -2
- package/dist/tempo/session/server/Settlement.d.ts +23 -0
- package/dist/tempo/session/server/Settlement.js +105 -25
- package/dist/tempo/session/server/Sse.d.ts +3 -1
- package/dist/tempo/session/server/Sse.js +105 -32
- package/dist/tempo/session/server/Transports.d.ts +24 -19
- package/dist/tempo/session/server/Transports.js +169 -58
- package/dist/tempo/session/server/Ws.d.ts +5 -1
- package/dist/tempo/session/server/Ws.js +75 -20
- package/dist/tempo/subscription/KeyAuthorization.d.ts +70 -8
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.js +3 -0
- package/dist/validation/core.js +2 -1
- package/dist/x402/server/EvmCharge.d.ts +3 -1
- package/dist/x402/server/EvmCharge.js +14 -1
- package/package.json +8 -8
- package/dist/tempo/legacy/client/ChannelOps.d.ts +0 -69
- package/dist/tempo/legacy/client/ChannelOps.js +0 -168
- package/dist/tempo/legacy/client/Session.d.ts +0 -185
- package/dist/tempo/legacy/client/Session.js +0 -279
- package/dist/tempo/legacy/client/SessionManager.d.ts +0 -100
- package/dist/tempo/legacy/client/SessionManager.js +0 -706
- package/dist/tempo/legacy/client/index.d.ts +0 -7
- package/dist/tempo/legacy/client/index.js +0 -5
|
@@ -1,9 +1,57 @@
|
|
|
1
1
|
import * as Assets from '../Assets.js';
|
|
2
2
|
import * as Chains from '../Chains.js';
|
|
3
3
|
import { charge as charge_ } from './Charge.js';
|
|
4
|
-
/**
|
|
4
|
+
/**
|
|
5
|
+
* Creates ordered EVM charge offers from shared parameters.
|
|
6
|
+
*
|
|
7
|
+
* Pass `currencies: [token]` for one asset or an ordered list for multiple assets.
|
|
8
|
+
* Viem token definitions resolve against `chainId`; known assets carry their own
|
|
9
|
+
* network. Unsupported chain entries are skipped and duplicate chain/address
|
|
10
|
+
* pairs are removed. An empty resolved list throws.
|
|
11
|
+
*
|
|
12
|
+
* Token definitions need explicit EIP-3009 domain metadata. Use
|
|
13
|
+
* `evm.assets.fromToken(token, { chainId, transfer })` for per-token domains.
|
|
14
|
+
* A token registry entry alone does not establish EIP-3009 support.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { evm } from 'mppx/server'
|
|
19
|
+
*
|
|
20
|
+
* evm({
|
|
21
|
+
* currencies: [evm.assets.base.USDC],
|
|
22
|
+
* recipient: '0x742d35Cc6634c0532925a3b844bC9e7595F8fE00',
|
|
23
|
+
* settle: async ({ payload }) => settleAuthorization(payload),
|
|
24
|
+
* })
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
5
27
|
export function evm(parameters) {
|
|
6
|
-
|
|
28
|
+
if (parameters.currency !== undefined) {
|
|
29
|
+
if (parameters.currencies !== undefined)
|
|
30
|
+
throw new Error('Specify either `currency` or `currencies`, not both.');
|
|
31
|
+
return [evm.charge(parameters)];
|
|
32
|
+
}
|
|
33
|
+
const { currencies, ...shared } = parameters;
|
|
34
|
+
const seen = new Set();
|
|
35
|
+
const methods = [];
|
|
36
|
+
for (const currency of currencies) {
|
|
37
|
+
const chainId = shared.chainId ?? (Assets.isAsset(currency) ? Assets.toChainId(currency.network) : undefined);
|
|
38
|
+
if (chainId === undefined)
|
|
39
|
+
throw new Error('EVM currencies require `chainId` or known asset metadata.');
|
|
40
|
+
const resolved = Assets.resolve(currency, Assets.toNetwork(chainId));
|
|
41
|
+
if (!resolved)
|
|
42
|
+
continue;
|
|
43
|
+
// One shared price cannot price non-USD or unknown-denomination token definitions.
|
|
44
|
+
if (Assets.isToken(currency) && currency.currency !== 'USD')
|
|
45
|
+
continue;
|
|
46
|
+
const key = `${chainId}:${resolved.address.toLowerCase()}`;
|
|
47
|
+
if (seen.has(key))
|
|
48
|
+
continue;
|
|
49
|
+
seen.add(key);
|
|
50
|
+
methods.push(evm.charge({ ...shared, chainId, currency }));
|
|
51
|
+
}
|
|
52
|
+
if (methods.length === 0)
|
|
53
|
+
throw new Error('No accepted EVM currencies for the configured chain.');
|
|
54
|
+
return methods;
|
|
7
55
|
}
|
|
8
56
|
(function (evm) {
|
|
9
57
|
/** Creates an EVM `charge` server method. */
|
|
@@ -4,7 +4,6 @@ import { evm } from '../../evm/server/Methods.js';
|
|
|
4
4
|
import { eip3009 } from '../../evm/Types.js';
|
|
5
5
|
import * as Expires from '../../Expires.js';
|
|
6
6
|
import * as Mppx from '../../server/Mppx.js';
|
|
7
|
-
import { tempo } from '../../tempo/server/Methods.js';
|
|
8
7
|
import * as Types from '../../x402/Types.js';
|
|
9
8
|
/** Creates a native MPP handler backed by a compatible x402 EVM requirement. */
|
|
10
9
|
export function createHandler(parameters) {
|
|
@@ -37,8 +36,10 @@ export function createHandler(parameters) {
|
|
|
37
36
|
return { reference: result.reference };
|
|
38
37
|
},
|
|
39
38
|
});
|
|
39
|
+
// Only advertise a native rail whose credential can be verified and settled
|
|
40
|
+
// by the selected x402 scheme. A Tempo transaction is not an EIP-3009 payload.
|
|
40
41
|
const payment = Mppx.create({
|
|
41
|
-
methods: [
|
|
42
|
+
methods: [evmMethod],
|
|
42
43
|
...(parameters.config.realm ? { realm: parameters.config.realm } : {}),
|
|
43
44
|
secretKey: parameters.config.secretKey,
|
|
44
45
|
});
|
|
@@ -50,7 +51,7 @@ export function createHandler(parameters) {
|
|
|
50
51
|
expires: Expires.seconds(requirement.maxTimeoutSeconds),
|
|
51
52
|
scope: parameters.paymentRequired.resource.url,
|
|
52
53
|
};
|
|
53
|
-
const handler = payment.compose([payment.
|
|
54
|
+
const handler = payment.compose([payment.evm.charge, options]);
|
|
54
55
|
return async (request) => ({
|
|
55
56
|
payment: await handler(request),
|
|
56
57
|
skipHandlerResponse,
|
|
@@ -7,8 +7,8 @@ import type * as z from '../zod.js';
|
|
|
7
7
|
* can opt into the exact wire encoding their protocol expects.
|
|
8
8
|
*/
|
|
9
9
|
export declare function createJson<const schema extends z.ZodMiniType>(schema: schema): {
|
|
10
|
-
encode(value: z.
|
|
11
|
-
decode(value: string): z.
|
|
10
|
+
encode(value: z.output<schema>): string;
|
|
11
|
+
decode(value: string): z.output<schema>;
|
|
12
12
|
};
|
|
13
13
|
/** Error thrown when a JSON header value is not valid base64-encoded JSON. */
|
|
14
14
|
export declare class InvalidJsonHeaderError extends Error {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Current mppx package version. */
|
|
2
|
-
export declare const version = "0.
|
|
2
|
+
export declare const version = "0.13.1";
|
|
3
3
|
/** Canonical SDK identifier for this mppx release. */
|
|
4
|
-
export declare const sdkIdentifier = "mppx/0.
|
|
4
|
+
export declare const sdkIdentifier = "mppx/0.13.1";
|
|
5
5
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/internal/version.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Generated by scripts/sync-version.ts.
|
|
2
2
|
/** Current mppx package version. */
|
|
3
|
-
export const version = '0.
|
|
3
|
+
export const version = '0.13.1';
|
|
4
4
|
/** Canonical SDK identifier for this mppx release. */
|
|
5
5
|
export const sdkIdentifier = `mppx/${version}`;
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
|
@@ -88,14 +88,11 @@ export function isPaymentRequiredError(error) {
|
|
|
88
88
|
}
|
|
89
89
|
/** @internal */
|
|
90
90
|
async function createCredential(challenge, config) {
|
|
91
|
-
const { context,
|
|
92
|
-
const mi = methods.find((m) => m.name === challenge.method && m.intent === challenge.intent);
|
|
93
|
-
if (!mi)
|
|
94
|
-
throw new Error(`No method found for "${challenge.method}.${challenge.intent}". Available: ${methods.map((m) => `${m.name}.${m.intent}`).join(', ')}`);
|
|
91
|
+
const { context, method } = config;
|
|
95
92
|
if (challenge.expires)
|
|
96
93
|
Expires.assert(challenge.expires, challenge.id);
|
|
97
|
-
const parsedContext =
|
|
98
|
-
return
|
|
94
|
+
const parsedContext = method.context && context !== undefined ? method.context.parse(context) : undefined;
|
|
95
|
+
return method.createCredential(parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge });
|
|
99
96
|
}
|
|
100
97
|
function createPaymentAwareCallTool(callTool, config) {
|
|
101
98
|
const methods = config.methods.flat();
|
|
@@ -121,7 +118,7 @@ function createPaymentAwareCallTool(callTool, config) {
|
|
|
121
118
|
}
|
|
122
119
|
const credential = await createCredential(selected.challenge, {
|
|
123
120
|
context: call.context,
|
|
124
|
-
|
|
121
|
+
method: selected.method,
|
|
125
122
|
});
|
|
126
123
|
const parsed = Credential.deserialize(credential);
|
|
127
124
|
try {
|
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { Elysia, type
|
|
1
|
+
import { Elysia, type AfterHandler, type OptionalHandler } from 'elysia';
|
|
2
2
|
import { type GenerateConfig, type RouteConfig } from '../discovery/OpenApi.js';
|
|
3
3
|
import * as Mppx_core from '../server/Mppx.js';
|
|
4
4
|
import * as Mppx_internal from './internal/mppx.js';
|
|
5
5
|
export * from '../server/Methods.js';
|
|
6
|
-
type ElysiaHook =
|
|
6
|
+
type ElysiaHook = OptionalHandler & {
|
|
7
|
+
afterHandle: AfterHandler;
|
|
8
|
+
beforeHandle: OptionalHandler;
|
|
9
|
+
};
|
|
7
10
|
export declare namespace Mppx {
|
|
8
11
|
/**
|
|
9
12
|
* Creates an Elysia-aware payment handler where each intent
|
|
10
|
-
* returns
|
|
13
|
+
* returns Elysia lifecycle hooks.
|
|
11
14
|
*
|
|
12
|
-
* Use with `.guard()`
|
|
13
|
-
*
|
|
15
|
+
* Use with `.guard()` so both verification and response wrapping are scoped
|
|
16
|
+
* to the same routes.
|
|
14
17
|
*
|
|
15
18
|
* @example
|
|
16
19
|
* ```ts
|
|
@@ -21,7 +24,7 @@ export declare namespace Mppx {
|
|
|
21
24
|
*
|
|
22
25
|
* const app = new Elysia()
|
|
23
26
|
* .guard(
|
|
24
|
-
*
|
|
27
|
+
* mppx.charge({ amount: '1' }),
|
|
25
28
|
* (app) => app.get('/premium', () => ({ data: 'paid content' })),
|
|
26
29
|
* )
|
|
27
30
|
* ```
|
|
@@ -29,7 +32,7 @@ export declare namespace Mppx {
|
|
|
29
32
|
function create<const methods extends Mppx_core.Methods>(config: Mppx_core.create.Config<methods>): Mppx_internal.Wrap<Mppx_core.Mppx<methods>, ElysiaHook>;
|
|
30
33
|
}
|
|
31
34
|
/**
|
|
32
|
-
* Elysia
|
|
35
|
+
* Elysia lifecycle hooks that gate a route behind a payment intent.
|
|
33
36
|
*
|
|
34
37
|
* Returns a 402 challenge if no valid credential is provided.
|
|
35
38
|
*
|
|
@@ -43,7 +46,7 @@ export declare namespace Mppx {
|
|
|
43
46
|
*
|
|
44
47
|
* const app = new Elysia()
|
|
45
48
|
* .guard(
|
|
46
|
-
*
|
|
49
|
+
* payment(mppx.charge, { amount: '1' }),
|
|
47
50
|
* (app) => app.get('/premium', () => ({ data: 'paid content' })),
|
|
48
51
|
* )
|
|
49
52
|
* ```
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Elysia } from 'elysia';
|
|
2
|
+
import { mapResponse as mapElysiaResponse } from 'elysia/adapter/web-standard/handler';
|
|
2
3
|
import { generate } from '../discovery/OpenApi.js';
|
|
4
|
+
import * as Scope from '../server/internal/scope.js';
|
|
3
5
|
import * as Mppx_core from '../server/Mppx.js';
|
|
4
6
|
import * as Mppx_internal from './internal/mppx.js';
|
|
5
7
|
export * from '../server/Methods.js';
|
|
@@ -7,10 +9,10 @@ export var Mppx;
|
|
|
7
9
|
(function (Mppx) {
|
|
8
10
|
/**
|
|
9
11
|
* Creates an Elysia-aware payment handler where each intent
|
|
10
|
-
* returns
|
|
12
|
+
* returns Elysia lifecycle hooks.
|
|
11
13
|
*
|
|
12
|
-
* Use with `.guard()`
|
|
13
|
-
*
|
|
14
|
+
* Use with `.guard()` so both verification and response wrapping are scoped
|
|
15
|
+
* to the same routes.
|
|
14
16
|
*
|
|
15
17
|
* @example
|
|
16
18
|
* ```ts
|
|
@@ -21,7 +23,7 @@ export var Mppx;
|
|
|
21
23
|
*
|
|
22
24
|
* const app = new Elysia()
|
|
23
25
|
* .guard(
|
|
24
|
-
*
|
|
26
|
+
* mppx.charge({ amount: '1' }),
|
|
25
27
|
* (app) => app.get('/premium', () => ({ data: 'paid content' })),
|
|
26
28
|
* )
|
|
27
29
|
* ```
|
|
@@ -32,7 +34,7 @@ export var Mppx;
|
|
|
32
34
|
Mppx.create = create;
|
|
33
35
|
})(Mppx || (Mppx = {}));
|
|
34
36
|
/**
|
|
35
|
-
* Elysia
|
|
37
|
+
* Elysia lifecycle hooks that gate a route behind a payment intent.
|
|
36
38
|
*
|
|
37
39
|
* Returns a 402 challenge if no valid credential is provided.
|
|
38
40
|
*
|
|
@@ -46,27 +48,78 @@ export var Mppx;
|
|
|
46
48
|
*
|
|
47
49
|
* const app = new Elysia()
|
|
48
50
|
* .guard(
|
|
49
|
-
*
|
|
51
|
+
* payment(mppx.charge, { amount: '1' }),
|
|
50
52
|
* (app) => app.get('/premium', () => ({ data: 'paid content' })),
|
|
51
53
|
* )
|
|
52
54
|
* ```
|
|
53
55
|
*/
|
|
54
56
|
export function payment(intent, options) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
const pending = new WeakMap();
|
|
58
|
+
const runBeforeHandle = async ({ request, route, set }, legacyRegistration) => {
|
|
59
|
+
const scopedRequest = options.scope === undefined && Scope.read(options.meta) === undefined
|
|
60
|
+
? Scope.attach(request, `${request.method.toUpperCase()} ${route || new URL(request.url).pathname}`)
|
|
61
|
+
: request;
|
|
62
|
+
const result = await intent(options)(scopedRequest);
|
|
57
63
|
if (result.status === 402)
|
|
58
64
|
return result.challenge;
|
|
59
|
-
const managementResponse = getManagementResponse(result);
|
|
65
|
+
const managementResponse = await getManagementResponse(result);
|
|
60
66
|
if (managementResponse)
|
|
61
67
|
return managementResponse;
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
68
|
+
const supportsStreamingReceipts = Mppx_core.supportsStreamingReceipts(result);
|
|
69
|
+
if (legacyRegistration) {
|
|
70
|
+
if (supportsStreamingReceipts)
|
|
71
|
+
return new Response('Streaming payment hooks require paired beforeHandle and afterHandle registration.', { status: 500 });
|
|
72
|
+
const receiptResponse = await result.withReceipt(new Response(null, { status: 204 }));
|
|
73
|
+
if (!receiptResponse.ok)
|
|
74
|
+
return receiptResponse;
|
|
75
|
+
copyHeaders(receiptResponse, set.headers);
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
pending.set(request, { result, supportsStreamingReceipts });
|
|
79
|
+
return undefined;
|
|
80
|
+
};
|
|
81
|
+
const beforeHandle = (context) => runBeforeHandle(context, false);
|
|
82
|
+
const legacyBeforeHandle = (context) => runBeforeHandle(context, true);
|
|
83
|
+
const afterHandle = async ({ request, responseValue, set }) => {
|
|
84
|
+
const entry = pending.get(request);
|
|
85
|
+
if (!entry)
|
|
86
|
+
return;
|
|
87
|
+
const { result, supportsStreamingReceipts } = entry;
|
|
88
|
+
if (isAsyncIterableResponse(responseValue)) {
|
|
89
|
+
if (supportsStreamingReceipts) {
|
|
90
|
+
const response = await result.withReceipt(responseValue);
|
|
91
|
+
pending.delete(request);
|
|
92
|
+
return response;
|
|
93
|
+
}
|
|
94
|
+
const receiptResponse = await result.withReceipt(new Response(null, { status: 204 }));
|
|
95
|
+
pending.delete(request);
|
|
96
|
+
if (!receiptResponse.ok)
|
|
97
|
+
return receiptResponse;
|
|
98
|
+
copyHeaders(receiptResponse, set.headers);
|
|
99
|
+
return responseValue;
|
|
100
|
+
}
|
|
101
|
+
const response = await mapElysiaResponse(responseValue, set, request);
|
|
102
|
+
const wrapped = await result.withReceipt(response);
|
|
103
|
+
pending.delete(request);
|
|
104
|
+
return wrapped;
|
|
65
105
|
};
|
|
106
|
+
return Object.assign(legacyBeforeHandle, { afterHandle, beforeHandle });
|
|
107
|
+
}
|
|
108
|
+
function isAsyncIterableResponse(response) {
|
|
109
|
+
if (typeof response === 'function')
|
|
110
|
+
return true;
|
|
111
|
+
return (typeof response === 'object' &&
|
|
112
|
+
response !== null &&
|
|
113
|
+
Symbol.asyncIterator in response &&
|
|
114
|
+
typeof response[Symbol.asyncIterator] === 'function');
|
|
115
|
+
}
|
|
116
|
+
function copyHeaders(response, headers) {
|
|
117
|
+
for (const [key, value] of response.headers)
|
|
118
|
+
headers[key] = value;
|
|
66
119
|
}
|
|
67
|
-
function getManagementResponse(result) {
|
|
120
|
+
async function getManagementResponse(result) {
|
|
68
121
|
try {
|
|
69
|
-
return result.withReceipt();
|
|
122
|
+
return await result.withReceipt();
|
|
70
123
|
}
|
|
71
124
|
catch (error) {
|
|
72
125
|
if (Mppx_core.isMissingReceiptResponseError(error)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generate } from '../discovery/OpenApi.js';
|
|
2
|
+
import * as Scope from '../server/internal/scope.js';
|
|
2
3
|
import * as Mppx_core from '../server/Mppx.js';
|
|
3
4
|
import * as ExpressAdapter from './internal/express.js';
|
|
4
5
|
import * as Mppx_internal from './internal/mppx.js';
|
|
@@ -49,16 +50,20 @@ export var Mppx;
|
|
|
49
50
|
*/
|
|
50
51
|
export function payment(intent, options) {
|
|
51
52
|
return async (req, res, next) => {
|
|
52
|
-
const
|
|
53
|
+
const rawRequest = ExpressAdapter.toRequest(req);
|
|
54
|
+
const routePath = typeof req.route?.path === 'string' ? req.route.path : req.path;
|
|
55
|
+
const request = options.scope === undefined && Scope.read(options.meta) === undefined
|
|
56
|
+
? Scope.attach(rawRequest, `${req.method.toUpperCase()} ${req.baseUrl}${routePath}`)
|
|
57
|
+
: rawRequest;
|
|
53
58
|
const result = await intent(options)(request);
|
|
54
59
|
if (result.status === 402) {
|
|
55
60
|
const challenge = result.challenge;
|
|
56
61
|
await ExpressAdapter.sendResponse(res, challenge);
|
|
57
62
|
return;
|
|
58
63
|
}
|
|
59
|
-
const managementResponse = (() => {
|
|
64
|
+
const managementResponse = await (async () => {
|
|
60
65
|
try {
|
|
61
|
-
return result.withReceipt();
|
|
66
|
+
return await result.withReceipt();
|
|
62
67
|
}
|
|
63
68
|
catch (error) {
|
|
64
69
|
if (Mppx_core.isMissingReceiptResponseError(error))
|
|
@@ -78,9 +83,11 @@ export function payment(intent, options) {
|
|
|
78
83
|
}
|
|
79
84
|
const originalJson = res.json.bind(res);
|
|
80
85
|
res.json = (body) => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
86
|
+
void Promise.resolve(result.withReceipt(Response.json(body))).then((wrapped) => {
|
|
87
|
+
ExpressAdapter.copyHeaders(res, wrapped.headers);
|
|
88
|
+
originalJson(body);
|
|
89
|
+
}, next);
|
|
90
|
+
return res;
|
|
84
91
|
};
|
|
85
92
|
next();
|
|
86
93
|
};
|
package/dist/middlewares/hono.js
CHANGED
|
@@ -55,16 +55,16 @@ export function payment(intent, options) {
|
|
|
55
55
|
const result = await intent(options)(request);
|
|
56
56
|
if (result.status === 402)
|
|
57
57
|
return result.challenge;
|
|
58
|
-
const managementResponse = getManagementResponse(result);
|
|
58
|
+
const managementResponse = await getManagementResponse(result);
|
|
59
59
|
if (managementResponse)
|
|
60
60
|
return managementResponse;
|
|
61
61
|
await next();
|
|
62
|
-
c.res = result.withReceipt(c.res);
|
|
62
|
+
c.res = await result.withReceipt(c.res);
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
function getManagementResponse(result) {
|
|
65
|
+
async function getManagementResponse(result) {
|
|
66
66
|
try {
|
|
67
|
-
return result.withReceipt();
|
|
67
|
+
return await result.withReceipt();
|
|
68
68
|
}
|
|
69
69
|
catch (error) {
|
|
70
70
|
if (Mppx_core.isMissingReceiptResponseError(error)) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generate } from '../discovery/OpenApi.js';
|
|
2
|
+
import * as Scope from '../server/internal/scope.js';
|
|
2
3
|
import * as Mppx_core from '../server/Mppx.js';
|
|
3
4
|
import * as Mppx_internal from './internal/mppx.js';
|
|
4
5
|
export * from '../server/Methods.js';
|
|
@@ -48,19 +49,22 @@ export var Mppx;
|
|
|
48
49
|
*/
|
|
49
50
|
export function payment(intent, options, handler) {
|
|
50
51
|
return async (request) => {
|
|
51
|
-
const
|
|
52
|
+
const scopedRequest = options.scope === undefined && Scope.read(options.meta) === undefined
|
|
53
|
+
? Scope.attach(request, `${request.method.toUpperCase()} ${new URL(request.url).pathname}`)
|
|
54
|
+
: request;
|
|
55
|
+
const result = await intent(options)(scopedRequest);
|
|
52
56
|
if (result.status === 402)
|
|
53
57
|
return result.challenge;
|
|
54
|
-
const managementResponse = getManagementResponse(result);
|
|
58
|
+
const managementResponse = await getManagementResponse(result);
|
|
55
59
|
if (managementResponse)
|
|
56
60
|
return managementResponse;
|
|
57
61
|
const response = await handler(request);
|
|
58
|
-
return result.withReceipt(response);
|
|
62
|
+
return await result.withReceipt(response);
|
|
59
63
|
};
|
|
60
64
|
}
|
|
61
|
-
function getManagementResponse(result) {
|
|
65
|
+
async function getManagementResponse(result) {
|
|
62
66
|
try {
|
|
63
|
-
return result.withReceipt();
|
|
67
|
+
return await result.withReceipt();
|
|
64
68
|
}
|
|
65
69
|
catch (error) {
|
|
66
70
|
if (Mppx_core.isMissingReceiptResponseError(error)) {
|
|
@@ -12,9 +12,12 @@ const hopByHopHeaders = new Set([
|
|
|
12
12
|
const paymentHeaders = new Set([
|
|
13
13
|
'accept-payment',
|
|
14
14
|
'authorization',
|
|
15
|
+
'payment-authorization',
|
|
15
16
|
'payment-receipt',
|
|
16
17
|
'payment-required',
|
|
17
18
|
'payment-response',
|
|
19
|
+
'payment-session',
|
|
20
|
+
'payment-session-snapshot',
|
|
18
21
|
'payment-signature',
|
|
19
22
|
'www-authenticate',
|
|
20
23
|
]);
|
|
@@ -57,10 +60,72 @@ export function scrubResponse(response) {
|
|
|
57
60
|
headers.delete('content-encoding');
|
|
58
61
|
headers.delete('content-length');
|
|
59
62
|
headers.delete('set-cookie');
|
|
63
|
+
for (const name of paymentHeaders) {
|
|
64
|
+
if (name !== 'authorization' && name !== 'www-authenticate')
|
|
65
|
+
headers.delete(name);
|
|
66
|
+
}
|
|
67
|
+
headers.delete('payment-authorization');
|
|
68
|
+
const authorization = headers.get('authorization');
|
|
69
|
+
if (authorization && /^\s*Payment(?:\s|$)/i.test(authorization))
|
|
70
|
+
headers.delete('authorization');
|
|
71
|
+
const authenticate = headers.get('www-authenticate');
|
|
72
|
+
if (authenticate) {
|
|
73
|
+
const remaining = removePaymentChallenges(authenticate);
|
|
74
|
+
if (remaining)
|
|
75
|
+
headers.set('www-authenticate', remaining);
|
|
76
|
+
else
|
|
77
|
+
headers.delete('www-authenticate');
|
|
78
|
+
}
|
|
60
79
|
return new Response(response.body, {
|
|
61
80
|
status: response.status,
|
|
62
81
|
statusText: response.statusText,
|
|
63
82
|
headers,
|
|
64
83
|
});
|
|
65
84
|
}
|
|
85
|
+
/** Removes Payment authentication challenges while preserving other schemes. */
|
|
86
|
+
function removePaymentChallenges(value) {
|
|
87
|
+
const starts = [];
|
|
88
|
+
let inQuotes = false;
|
|
89
|
+
let escaped = false;
|
|
90
|
+
for (let index = 0; index < value.length; index++) {
|
|
91
|
+
const character = value[index];
|
|
92
|
+
if (inQuotes) {
|
|
93
|
+
if (escaped)
|
|
94
|
+
escaped = false;
|
|
95
|
+
else if (character === '\\')
|
|
96
|
+
escaped = true;
|
|
97
|
+
else if (character === '"')
|
|
98
|
+
inQuotes = false;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (character === '"') {
|
|
102
|
+
inQuotes = true;
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const previous = value.slice(0, index).trimEnd().at(-1);
|
|
106
|
+
if (index !== 0 && previous !== ',')
|
|
107
|
+
continue;
|
|
108
|
+
const match = value.slice(index).match(/^\s*([!#$%&'*+.^_`|~0-9A-Za-z-]+)(?=\s|,|$)/);
|
|
109
|
+
if (!match)
|
|
110
|
+
continue;
|
|
111
|
+
const start = index + match[0].length - match[0].trimStart().length;
|
|
112
|
+
const afterScheme = start + match[1].length;
|
|
113
|
+
let next = afterScheme;
|
|
114
|
+
while (/\s/.test(value[next] ?? ''))
|
|
115
|
+
next++;
|
|
116
|
+
if (value[next] === '=')
|
|
117
|
+
continue;
|
|
118
|
+
starts.push({ index: start, scheme: match[1].toLowerCase() });
|
|
119
|
+
index = afterScheme - 1;
|
|
120
|
+
}
|
|
121
|
+
return starts
|
|
122
|
+
.filter(({ scheme }) => scheme !== 'payment')
|
|
123
|
+
.map(({ index }) => {
|
|
124
|
+
const originalPosition = starts.findIndex((start) => start.index === index);
|
|
125
|
+
const end = starts[originalPosition + 1]?.index ?? value.length;
|
|
126
|
+
return value.slice(index, end).replace(/,\s*$/, '').trim();
|
|
127
|
+
})
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.join(', ');
|
|
130
|
+
}
|
|
66
131
|
//# sourceMappingURL=Headers.js.map
|
|
@@ -30,6 +30,7 @@ export function stripe(config) {
|
|
|
30
30
|
rewriteRequest(request, ctx) {
|
|
31
31
|
const apiKey = ctx.apiKey ?? config.apiKey;
|
|
32
32
|
request.headers.delete('Stripe-Account');
|
|
33
|
+
request.headers.delete('Stripe-Context');
|
|
33
34
|
request.headers.set('Authorization', `Basic ${btoa(`${apiKey}:`)}`);
|
|
34
35
|
return request;
|
|
35
36
|
},
|
package/dist/server/Mppx.d.ts
CHANGED
|
@@ -8,8 +8,14 @@ import * as Method from '../Method.js';
|
|
|
8
8
|
import type * as Receipt from '../Receipt.js';
|
|
9
9
|
import * as z from '../zod.js';
|
|
10
10
|
import * as Html from './internal/html/config.js';
|
|
11
|
+
import * as Request from './Request.js';
|
|
11
12
|
import * as Transport from './Transport.js';
|
|
12
13
|
export type Methods = readonly (Method.AnyServer | readonly Method.AnyServer[])[];
|
|
14
|
+
type ReceiptWrapper = (response?: unknown) => unknown;
|
|
15
|
+
/** Returns whether a successful payment result accepts streaming receipt responses. @internal */
|
|
16
|
+
export declare function supportsStreamingReceipts(result: {
|
|
17
|
+
withReceipt: ReceiptWrapper;
|
|
18
|
+
}): boolean;
|
|
13
19
|
export type ServerEventMap<methods extends readonly Method.Method[] = readonly Method.Method[], transport extends Transport.AnyTransport = Transport.AnyTransport> = {
|
|
14
20
|
'challenge.created': ChallengeContext<methods[number], transport>;
|
|
15
21
|
'payment.failed': PaymentFailedContext<methods[number], transport>;
|
|
@@ -112,6 +118,7 @@ export type VerifyCredentialOptions = {
|
|
|
112
118
|
capturedRequest?: Method.CapturedRequest | undefined;
|
|
113
119
|
meta?: Record<string, string> | undefined;
|
|
114
120
|
realm?: string | undefined;
|
|
121
|
+
/** Route input used for binding checks and disambiguating configured methods before schema transforms. */
|
|
115
122
|
request?: Record<string, unknown> | undefined;
|
|
116
123
|
/** Optional expected route/resource scope bound via challenge `opaque`. */
|
|
117
124
|
scope?: string | undefined;
|
|
@@ -140,7 +147,8 @@ export type Mppx<methods extends Methods = Methods, transport extends Transport.
|
|
|
140
147
|
* all methods through HTTP headers or an MCP payment-required challenge list.
|
|
141
148
|
*
|
|
142
149
|
* Each entry is a `[method, options]` tuple where `method` is one of the
|
|
143
|
-
* server methods passed to `Mppx.create()`, looked up by
|
|
150
|
+
* server methods passed to `Mppx.create()`, looked up by object identity.
|
|
151
|
+
* String keys use the named handler; function references use their configured method.
|
|
144
152
|
*
|
|
145
153
|
* Available on HTTP, MCP JSON-RPC, and MCP SDK transports. MCP handlers
|
|
146
154
|
* accept the transport's input and return its challenge and receipt types.
|
|
@@ -372,15 +380,19 @@ type ConfiguredHandler<transport extends Transport.AnyTransport = Transport.Http
|
|
|
372
380
|
name: string;
|
|
373
381
|
intent: string;
|
|
374
382
|
html: Html.Options | undefined;
|
|
383
|
+
credentialHeaders?: readonly string[] | undefined;
|
|
375
384
|
meta?: Record<string, string> | undefined;
|
|
376
385
|
scope?: string | undefined;
|
|
377
386
|
_canonicalRequest: Record<string, unknown>;
|
|
378
387
|
_stableBinding?: Method.StableBindingFn<Method.Method> | undefined;
|
|
388
|
+
_transport: Transport.AnyTransport;
|
|
379
389
|
};
|
|
380
390
|
};
|
|
381
391
|
type ComposedHandler<transport extends Transport.AnyTransport = Transport.Http> = ((input: Transport.InputOf<transport>) => Promise<MethodFn.Response<transport>>) & {
|
|
382
392
|
_internal?: {
|
|
383
393
|
offers: readonly ConfiguredHandler['_internal'][];
|
|
394
|
+
handlers?: readonly ConfiguredHandler<transport>[];
|
|
395
|
+
selectOffers?: SelectOffers<readonly Method.AnyServer[]> | undefined;
|
|
384
396
|
};
|
|
385
397
|
};
|
|
386
398
|
/** An entry for `compose()`: a method reference, handler function ref, or string key paired with its options. */
|
|
@@ -409,6 +421,9 @@ export declare function compose(...args: readonly unknown[]): ComposedHandler;
|
|
|
409
421
|
* On 402: writes the challenge response and ends the connection.
|
|
410
422
|
* On 200: sets the Payment-Receipt header; caller should write response body.
|
|
411
423
|
*
|
|
424
|
+
* @param handler - Payment handler to adapt.
|
|
425
|
+
* @param options - Node request conversion and body-size options.
|
|
426
|
+
*
|
|
412
427
|
* @example
|
|
413
428
|
* ```ts
|
|
414
429
|
* import * as http from 'node:http'
|
|
@@ -427,7 +442,8 @@ export declare function compose(...args: readonly unknown[]): ComposedHandler;
|
|
|
427
442
|
* })
|
|
428
443
|
* ```
|
|
429
444
|
*/
|
|
430
|
-
export declare function toNodeListener(handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http
|
|
445
|
+
export declare function toNodeListener(handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Http>>, options?: Request.NodeConversionOptions | undefined): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Http>>;
|
|
446
|
+
export declare function toNodeListener(handler: (input: globalThis.Request) => Promise<MethodFn.Response<Transport.Sse>>, options?: Request.NodeConversionOptions | undefined): (req: IncomingMessage, res: ServerResponse) => Promise<MethodFn.Response<Transport.Sse>>;
|
|
431
447
|
/**
|
|
432
448
|
* Flattens a methods config tuple, preserving positional types.
|
|
433
449
|
* @internal
|