mppx 0.13.4 → 0.13.6
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 +48 -0
- package/dist/Credential.d.ts +3 -0
- package/dist/Credential.js +9 -0
- package/dist/Errors.d.ts +63 -2
- package/dist/Errors.js +52 -2
- package/dist/Method.d.ts +10 -0
- package/dist/cli/cli.js +92 -92
- package/dist/cli/plugins/evm.js +2 -2
- package/dist/cli/plugins/index.js +12 -12
- package/dist/client/Mppx.d.ts +2 -0
- package/dist/client/Mppx.js +8 -10
- package/dist/client/internal/Fetch.js +107 -8
- package/dist/client/internal/MethodChallenge.d.ts +2 -0
- package/dist/client/internal/MethodChallenge.js +4 -0
- package/dist/client/internal/MethodResponse.d.ts +6 -0
- package/dist/client/internal/MethodResponse.js +16 -0
- package/dist/client/internal/protocols/Mcp.js +36 -5
- package/dist/evm/server/Charge.js +3 -1
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/internal/warnOnce.d.ts +3 -0
- package/dist/internal/warnOnce.js +9 -0
- package/dist/server/Mppx.d.ts +5 -0
- package/dist/server/Mppx.js +45 -18
- package/dist/server/Transport.js +10 -0
- package/dist/stripe/server/Methods.js +4 -1
- package/dist/tempo/Attribution.d.ts +24 -9
- package/dist/tempo/Attribution.js +25 -5
- package/dist/tempo/client/Charge.d.ts +116 -10
- package/dist/tempo/client/Charge.js +117 -22
- package/dist/tempo/client/Methods.d.ts +88 -9
- package/dist/tempo/index.d.ts +1 -0
- package/dist/tempo/index.js +1 -0
- package/dist/tempo/internal/auto-swap.d.ts +10 -0
- package/dist/tempo/internal/auto-swap.js +35 -8
- package/dist/tempo/internal/fee-payer.d.ts +8 -0
- package/dist/tempo/internal/fee-payer.js +20 -8
- package/dist/tempo/legacy/session/Chain.js +8 -2
- package/dist/tempo/server/Charge.d.ts +11 -0
- package/dist/tempo/server/Charge.js +160 -29
- package/dist/tempo/server/Methods.js +28 -5
- package/dist/tempo/server/Relay.js +6 -22
- package/dist/tempo/server/Subscription.js +5 -1
- package/dist/tempo/server/internal/fallback-store.d.ts +9 -0
- package/dist/tempo/server/internal/fallback-store.js +13 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +3 -0
- package/dist/tempo/server/internal/transport.js +5 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.d.ts +12 -1
- package/dist/tempo/session/client/ChannelStore.js +47 -0
- package/dist/tempo/session/client/CredentialState.js +8 -3
- package/dist/tempo/session/client/Session.js +50 -33
- package/dist/tempo/session/client/SessionManager.js +186 -65
- package/dist/tempo/session/client/Transports.d.ts +6 -0
- package/dist/tempo/session/client/Transports.js +52 -7
- package/dist/tempo/session/precompile/Chain.d.ts +2 -0
- package/dist/tempo/session/precompile/Chain.js +8 -3
- package/dist/tempo/session/server/ChannelStore.d.ts +34 -0
- package/dist/tempo/session/server/ChannelStore.js +69 -9
- package/dist/tempo/session/server/CredentialVerification.d.ts +7 -1
- package/dist/tempo/session/server/CredentialVerification.js +220 -48
- package/dist/tempo/session/server/MeteredStream.d.ts +6 -5
- package/dist/tempo/session/server/MeteredStream.js +10 -26
- package/dist/tempo/session/server/Session.d.ts +29 -2
- package/dist/tempo/session/server/Session.js +124 -44
- package/dist/tempo/session/server/Settlement.d.ts +9 -0
- package/dist/tempo/session/server/Settlement.js +23 -3
- package/dist/tempo/session/server/Sse.d.ts +4 -3
- package/dist/tempo/session/server/Sse.js +3 -3
- package/dist/tempo/session/server/Ws.d.ts +9 -1
- package/dist/tempo/session/server/Ws.js +1 -0
- package/package.json +1 -1
package/dist/client/Mppx.d.ts
CHANGED
|
@@ -62,6 +62,8 @@ export type Mppx<methods extends Methods = Methods, transport extends Transport.
|
|
|
62
62
|
transport: transport;
|
|
63
63
|
/**
|
|
64
64
|
* Selects a payment challenge without creating its credential.
|
|
65
|
+
* Native HTTP responses in payment events preserve status and headers only;
|
|
66
|
+
* the caller retains ownership of the original response body.
|
|
65
67
|
*
|
|
66
68
|
* @example
|
|
67
69
|
* ```ts
|
package/dist/client/Mppx.js
CHANGED
|
@@ -405,17 +405,15 @@ function snapshotMethod(method) {
|
|
|
405
405
|
}
|
|
406
406
|
function snapshotResponse(response) {
|
|
407
407
|
if (response instanceof Response) {
|
|
408
|
-
|
|
408
|
+
if (response.body === null)
|
|
409
409
|
return response.clone();
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
});
|
|
418
|
-
}
|
|
410
|
+
// Event context can outlive an SSE response. Retaining a clone would queue
|
|
411
|
+
// every chunk consumed by the caller on an unread body branch.
|
|
412
|
+
return new Response(null, {
|
|
413
|
+
headers: response.headers,
|
|
414
|
+
status: response.status,
|
|
415
|
+
statusText: response.statusText,
|
|
416
|
+
});
|
|
419
417
|
}
|
|
420
418
|
return snapshotValue(response);
|
|
421
419
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as Challenge from '../../Challenge.js';
|
|
2
2
|
import * as Constants from '../../Constants.js';
|
|
3
|
+
import { PaymentOutcomeUnknownError } from '../../Errors.js';
|
|
3
4
|
import * as Expires from '../../Expires.js';
|
|
4
5
|
import * as AcceptPayment from '../../internal/AcceptPayment.js';
|
|
5
6
|
import * as Transport from '../Transport.js';
|
|
@@ -10,8 +11,64 @@ import * as MethodResponse from './MethodResponse.js';
|
|
|
10
11
|
// an unrelated fetch installed by user code or another library.
|
|
11
12
|
const MPPX_FETCH_WRAPPER = Symbol.for('mppx.fetch.wrapper');
|
|
12
13
|
const defaultMaxPaymentRetries = 3;
|
|
14
|
+
const maxErrorBodyBytes = 64 * 1024;
|
|
15
|
+
const errorBodyTimeoutMs = 2_000;
|
|
16
|
+
/** Response header set when a paid-retry error body was cut off by {@link bufferErrorBody}. */
|
|
17
|
+
const bodyTruncatedHeader = 'Mppx-Body-Truncated';
|
|
13
18
|
let originalFetch;
|
|
14
19
|
const eventObserverChecks = new WeakMap();
|
|
20
|
+
/**
|
|
21
|
+
* Buffers a paid-retry error body within a size and time bound so its connection is
|
|
22
|
+
* released. A complete body stays in the original response. The body is diagnostic
|
|
23
|
+
* only: one larger than 64 KiB, or still arriving after 2 seconds, is cancelled and
|
|
24
|
+
* replaced with what arrived, marked with an `Mppx-Body-Truncated: true` header.
|
|
25
|
+
*/
|
|
26
|
+
async function bufferErrorBody(response) {
|
|
27
|
+
if (!response.body)
|
|
28
|
+
return response;
|
|
29
|
+
// Reading a clone pulls the shared source, buffering it in the original response.
|
|
30
|
+
const reader = response.clone().body.getReader();
|
|
31
|
+
const chunks = [];
|
|
32
|
+
let received = 0;
|
|
33
|
+
let truncated = false;
|
|
34
|
+
const timeout = setTimeout(() => {
|
|
35
|
+
truncated = true;
|
|
36
|
+
void reader.cancel().catch(() => { });
|
|
37
|
+
}, errorBodyTimeoutMs);
|
|
38
|
+
try {
|
|
39
|
+
for (;;) {
|
|
40
|
+
const { done, value } = await reader.read();
|
|
41
|
+
if (done)
|
|
42
|
+
break;
|
|
43
|
+
chunks.push(value);
|
|
44
|
+
received += value.byteLength;
|
|
45
|
+
if (received > maxErrorBodyBytes) {
|
|
46
|
+
truncated = true;
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
// A tee's cancellation can wait for the other branch; do not await it.
|
|
54
|
+
void reader.cancel().catch(() => { });
|
|
55
|
+
}
|
|
56
|
+
if (!truncated)
|
|
57
|
+
return response;
|
|
58
|
+
// Cancelling both branches cancels the source and releases the connection.
|
|
59
|
+
void response.body.cancel().catch(() => { });
|
|
60
|
+
const headers = new Headers(response.headers);
|
|
61
|
+
headers.delete('content-length');
|
|
62
|
+
headers.set(bodyTruncatedHeader, 'true');
|
|
63
|
+
const truncatedResponse = new Response(new Blob(chunks).slice(0, maxErrorBodyBytes), {
|
|
64
|
+
headers,
|
|
65
|
+
status: response.status,
|
|
66
|
+
statusText: response.statusText,
|
|
67
|
+
});
|
|
68
|
+
for (const key of ['redirected', 'type', 'url'])
|
|
69
|
+
Object.defineProperty(truncatedResponse, key, { value: response[key] });
|
|
70
|
+
return truncatedResponse;
|
|
71
|
+
}
|
|
15
72
|
async function settleAttempt(prepared, outcome) {
|
|
16
73
|
const settle = prepared?.attempt.settle;
|
|
17
74
|
if (!settle)
|
|
@@ -67,6 +124,8 @@ export function from(config) {
|
|
|
67
124
|
let challenges;
|
|
68
125
|
let mi;
|
|
69
126
|
let preparedCredential;
|
|
127
|
+
// Set when the paid request throws after the credential was sent.
|
|
128
|
+
let outcomeUnknown;
|
|
70
129
|
try {
|
|
71
130
|
for (let retry = 0; retry < maxPaymentRetries; retry++) {
|
|
72
131
|
challenges = await transport.getChallenges(response, transportRequest);
|
|
@@ -166,14 +225,26 @@ export function from(config) {
|
|
|
166
225
|
method: selected.method,
|
|
167
226
|
response,
|
|
168
227
|
}));
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// Runtimes do not strip custom payment headers on cross-origin redirects.
|
|
175
|
-
redirect: 'manual',
|
|
228
|
+
const unknownOutcome = (cause) => new PaymentOutcomeUnknownError({
|
|
229
|
+
cause,
|
|
230
|
+
challenge: selectedChallenge,
|
|
231
|
+
credential,
|
|
232
|
+
transactionHash: MethodResponse.getTransactionHash(selected.method, credential),
|
|
176
233
|
});
|
|
234
|
+
try {
|
|
235
|
+
response = await baseFetch(paymentInput, {
|
|
236
|
+
...transport.setCredential({
|
|
237
|
+
...fetchInit,
|
|
238
|
+
headers: initialRequest.headers,
|
|
239
|
+
}, credential, { challenge: selectedChallenge }),
|
|
240
|
+
// Runtimes do not strip custom payment headers on cross-origin redirects.
|
|
241
|
+
redirect: 'manual',
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
catch (cause) {
|
|
245
|
+
outcomeUnknown = unknownOutcome(cause);
|
|
246
|
+
throw cause;
|
|
247
|
+
}
|
|
177
248
|
const paymentRequired = await transport.isPaymentRequired(response, transportRequest);
|
|
178
249
|
if (!paymentRequired) {
|
|
179
250
|
await settleAttempt(prepared, {
|
|
@@ -198,6 +269,33 @@ export function from(config) {
|
|
|
198
269
|
response,
|
|
199
270
|
}));
|
|
200
271
|
if (!paymentRequired) {
|
|
272
|
+
// The server may have settled before failing: report the credential for
|
|
273
|
+
// reconciliation and return the response. Retrying could pay twice.
|
|
274
|
+
if (response.status >= 500 && hasEventObservers(events, 'payment.failed')) {
|
|
275
|
+
try {
|
|
276
|
+
// Release the connection before observers can make requests through it.
|
|
277
|
+
response = await bufferErrorBody(response);
|
|
278
|
+
}
|
|
279
|
+
catch (cause) {
|
|
280
|
+
outcomeUnknown = unknownOutcome(cause);
|
|
281
|
+
// The failed body cannot be cloned for observers; keep its status and headers.
|
|
282
|
+
response = new Response(null, {
|
|
283
|
+
headers: response.headers,
|
|
284
|
+
status: response.status,
|
|
285
|
+
statusText: response.statusText,
|
|
286
|
+
});
|
|
287
|
+
throw cause;
|
|
288
|
+
}
|
|
289
|
+
await events.emit('payment.failed', createPaymentFailedPayload({
|
|
290
|
+
challenge: selectedChallenge,
|
|
291
|
+
challenges,
|
|
292
|
+
error: unknownOutcome(),
|
|
293
|
+
init,
|
|
294
|
+
input,
|
|
295
|
+
method: selected.method,
|
|
296
|
+
response,
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
201
299
|
if (!response.ok)
|
|
202
300
|
return response;
|
|
203
301
|
return MethodResponse.handle(selected.method, {
|
|
@@ -237,7 +335,8 @@ export function from(config) {
|
|
|
237
335
|
await events.emit('payment.failed', createPaymentFailedPayload({
|
|
238
336
|
challenge,
|
|
239
337
|
challenges,
|
|
240
|
-
error
|
|
338
|
+
// Observers get the credential to reconcile; callers keep the original error.
|
|
339
|
+
error: outcomeUnknown ?? error,
|
|
241
340
|
init,
|
|
242
341
|
input,
|
|
243
342
|
method: mi,
|
|
@@ -17,4 +17,6 @@ export declare function register<const method extends Method.AnyClient>(method:
|
|
|
17
17
|
export declare function has(method: Method.AnyClient): boolean;
|
|
18
18
|
/** Runs method-specific work before creating a challenge credential. */
|
|
19
19
|
export declare function handle(method: Method.AnyClient, parameters: HandlerParameters): Promise<void>;
|
|
20
|
+
/** Returns the registered internal hook so a manager can coordinate its work. */
|
|
21
|
+
export declare function get(method: Method.AnyClient): Handler | undefined;
|
|
20
22
|
//# sourceMappingURL=MethodChallenge.d.ts.map
|
|
@@ -12,4 +12,8 @@ export function has(method) {
|
|
|
12
12
|
export function handle(method, parameters) {
|
|
13
13
|
return Promise.resolve(handlers.get(method)?.(parameters));
|
|
14
14
|
}
|
|
15
|
+
/** Returns the registered internal hook so a manager can coordinate its work. */
|
|
16
|
+
export function get(method) {
|
|
17
|
+
return handlers.get(method);
|
|
18
|
+
}
|
|
15
19
|
//# sourceMappingURL=MethodChallenge.js.map
|
|
@@ -23,6 +23,8 @@ export type HandlerParameters = {
|
|
|
23
23
|
response: Response;
|
|
24
24
|
signal?: AbortSignal | undefined;
|
|
25
25
|
};
|
|
26
|
+
/** Derives the on-chain hash a credential's payment will have, when it is final before settlement. */
|
|
27
|
+
export type TransactionHash = (credential: string) => string | undefined;
|
|
26
28
|
/** Internal client-method response adapter. */
|
|
27
29
|
export type Handler = (parameters: HandlerParameters) => MaybePromise<Response>;
|
|
28
30
|
/** Registers an internal response adapter without changing the public method shape. */
|
|
@@ -35,4 +37,8 @@ export declare function attachAttempt(method: Method.AnyClient, parameters: obje
|
|
|
35
37
|
export declare function getAttempt(parameters: object): Attempt | undefined;
|
|
36
38
|
/** Lets the selected client method handle a successful paid response. */
|
|
37
39
|
export declare function handle(method: Method.AnyClient, parameters: HandlerParameters): Promise<Response>;
|
|
40
|
+
/** Registers credential hash derivation without changing the public method shape. */
|
|
41
|
+
export declare function registerTransactionHash<const method extends Method.AnyClient>(method: method, derive: TransactionHash): method;
|
|
42
|
+
/** Returns the credential's on-chain hash when its method can derive it. */
|
|
43
|
+
export declare function getTransactionHash(method: Method.AnyClient, credential: string): string | undefined;
|
|
38
44
|
//# sourceMappingURL=MethodResponse.d.ts.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const handlers = new WeakMap();
|
|
2
2
|
const attempts = new WeakMap();
|
|
3
|
+
const transactionHashes = new WeakMap();
|
|
3
4
|
/** Registers an internal response adapter without changing the public method shape. */
|
|
4
5
|
export function register(method, handler) {
|
|
5
6
|
handlers.set(method, handler);
|
|
@@ -22,4 +23,19 @@ export function getAttempt(parameters) {
|
|
|
22
23
|
export function handle(method, parameters) {
|
|
23
24
|
return Promise.resolve(handlers.get(method)?.(parameters) ?? parameters.response);
|
|
24
25
|
}
|
|
26
|
+
/** Registers credential hash derivation without changing the public method shape. */
|
|
27
|
+
export function registerTransactionHash(method, derive) {
|
|
28
|
+
transactionHashes.set(method, derive);
|
|
29
|
+
return method;
|
|
30
|
+
}
|
|
31
|
+
/** Returns the credential's on-chain hash when its method can derive it. */
|
|
32
|
+
export function getTransactionHash(method, credential) {
|
|
33
|
+
try {
|
|
34
|
+
return transactionHashes.get(method)?.(credential);
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
// Reconciliation metadata must never mask the payment outcome itself.
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
25
41
|
//# sourceMappingURL=MethodResponse.js.map
|
|
@@ -18,6 +18,8 @@ function jsonRpcRequestId(body) {
|
|
|
18
18
|
return undefined;
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
// Bound automatic inspection; the caller retains the original response stream.
|
|
22
|
+
const maxInspectionBytes = 1024 * 1024;
|
|
21
23
|
const responseCache = new WeakMap();
|
|
22
24
|
function mcpHttpRequestId(request) {
|
|
23
25
|
const id = jsonRpcRequestId(request?.body);
|
|
@@ -94,6 +96,7 @@ async function parseSseJsonRpcResponse(response) {
|
|
|
94
96
|
if (!reader)
|
|
95
97
|
return undefined;
|
|
96
98
|
const decoder = new TextDecoder();
|
|
99
|
+
let received = 0;
|
|
97
100
|
let dataEventSeen = false;
|
|
98
101
|
let message;
|
|
99
102
|
const parser = createParser({
|
|
@@ -119,6 +122,9 @@ async function parseSseJsonRpcResponse(response) {
|
|
|
119
122
|
parser.reset({ consume: true });
|
|
120
123
|
return message;
|
|
121
124
|
}
|
|
125
|
+
received += value.byteLength;
|
|
126
|
+
if (received > maxInspectionBytes)
|
|
127
|
+
return undefined;
|
|
122
128
|
parser.feed(decoder.decode(value, { stream: true }));
|
|
123
129
|
}
|
|
124
130
|
}
|
|
@@ -126,6 +132,33 @@ async function parseSseJsonRpcResponse(response) {
|
|
|
126
132
|
void reader.cancel().catch(() => { });
|
|
127
133
|
}
|
|
128
134
|
}
|
|
135
|
+
/** Reads only a bounded JSON response, leaving the caller's stream untouched. */
|
|
136
|
+
async function parseJsonResponse(response) {
|
|
137
|
+
const reader = response.clone().body?.getReader();
|
|
138
|
+
if (!reader)
|
|
139
|
+
return undefined;
|
|
140
|
+
const decoder = new TextDecoder();
|
|
141
|
+
let received = 0;
|
|
142
|
+
let text = '';
|
|
143
|
+
try {
|
|
144
|
+
for (;;) {
|
|
145
|
+
const { done, value } = await reader.read();
|
|
146
|
+
if (done)
|
|
147
|
+
return parseMessage(JSON.parse(text + decoder.decode()));
|
|
148
|
+
received += value.byteLength;
|
|
149
|
+
if (received > maxInspectionBytes)
|
|
150
|
+
return undefined;
|
|
151
|
+
text += decoder.decode(value, { stream: true });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
catch {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
finally {
|
|
158
|
+
// A tee's cancellation can wait for the original stream; do not await it.
|
|
159
|
+
void reader.cancel().catch(() => { });
|
|
160
|
+
}
|
|
161
|
+
}
|
|
129
162
|
/** Reads a cloned HTTP body into the first JSON-RPC response message, if any. */
|
|
130
163
|
function parseJsonRpcResponse(response) {
|
|
131
164
|
const cached = responseCache.get(response);
|
|
@@ -133,12 +166,10 @@ function parseJsonRpcResponse(response) {
|
|
|
133
166
|
return cached;
|
|
134
167
|
const promise = (async () => {
|
|
135
168
|
const contentType = response.headers.get('content-type')?.toLowerCase() ?? '';
|
|
169
|
+
if (Number(response.headers.get('content-length')) > maxInspectionBytes)
|
|
170
|
+
return undefined;
|
|
136
171
|
if (contentType.includes('application/json'))
|
|
137
|
-
return response
|
|
138
|
-
.clone()
|
|
139
|
-
.json()
|
|
140
|
-
.then(parseMessage)
|
|
141
|
-
.catch(() => undefined);
|
|
172
|
+
return parseJsonResponse(response);
|
|
142
173
|
if (contentType.includes('text/event-stream'))
|
|
143
174
|
return parseSseJsonRpcResponse(response);
|
|
144
175
|
return undefined;
|
|
@@ -77,12 +77,14 @@ export function charge(parameters) {
|
|
|
77
77
|
source,
|
|
78
78
|
};
|
|
79
79
|
await config.validate?.(authorization);
|
|
80
|
+
const payer = getAddress(payload.from);
|
|
80
81
|
return {
|
|
81
82
|
challenge: credential.challenge,
|
|
82
83
|
credential,
|
|
83
|
-
details: { payer
|
|
84
|
+
details: { payer },
|
|
84
85
|
intent: Methods.charge.intent,
|
|
85
86
|
method: Methods.charge.name,
|
|
87
|
+
payer,
|
|
86
88
|
request: credential.challenge.request,
|
|
87
89
|
source,
|
|
88
90
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Current mppx package version. */
|
|
2
|
-
export declare const version = "0.13.
|
|
2
|
+
export declare const version = "0.13.6";
|
|
3
3
|
/** Canonical SDK identifier for this mppx release. */
|
|
4
|
-
export declare const sdkIdentifier = "mppx/0.13.
|
|
4
|
+
export declare const sdkIdentifier = "mppx/0.13.6";
|
|
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.13.
|
|
3
|
+
export const version = '0.13.6';
|
|
4
4
|
/** Canonical SDK identifier for this mppx release. */
|
|
5
5
|
export const sdkIdentifier = `mppx/${version}`;
|
|
6
6
|
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const warned = new Set();
|
|
2
|
+
/** Logs `message` with `console.warn` the first time `key` is seen in this process. */
|
|
3
|
+
export function warnOnce(key, message) {
|
|
4
|
+
if (warned.has(key))
|
|
5
|
+
return;
|
|
6
|
+
warned.add(key);
|
|
7
|
+
console.warn(`[mppx] ${message}`);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=warnOnce.js.map
|
package/dist/server/Mppx.d.ts
CHANGED
|
@@ -104,6 +104,11 @@ export type PaymentSuccessContext<method extends Method.Method = Method.Method,
|
|
|
104
104
|
envelope?: Method.VerifiedChallengeEnvelope<z.output<method['schema']['request']>, z.output<method['schema']['credential']['payload']>, method['intent'], method['name']> | undefined;
|
|
105
105
|
input?: Transport.InputOf<transport> | undefined;
|
|
106
106
|
method: ServerMethodDescriptor<method>;
|
|
107
|
+
/**
|
|
108
|
+
* Payer identity verified by the method before settlement (for example, the
|
|
109
|
+
* Tempo sender address). Absent when the method does not report one.
|
|
110
|
+
*/
|
|
111
|
+
payer?: string | undefined;
|
|
107
112
|
receipt: Receipt.Receipt;
|
|
108
113
|
/** Canonical request represented by the challenge. */
|
|
109
114
|
request: z.output<method['schema']['request']>;
|
package/dist/server/Mppx.js
CHANGED
|
@@ -7,6 +7,7 @@ import * as Errors from '../Errors.js';
|
|
|
7
7
|
import * as Expires from '../Expires.js';
|
|
8
8
|
import * as AcceptPayment from '../internal/AcceptPayment.js';
|
|
9
9
|
import * as Env from '../internal/env.js';
|
|
10
|
+
import { warnOnce } from '../internal/warnOnce.js';
|
|
10
11
|
import * as Mcp from '../Mcp.js';
|
|
11
12
|
import * as PaymentRequest from '../PaymentRequest.js';
|
|
12
13
|
import * as x402_Header from '../x402/Header.js';
|
|
@@ -332,9 +333,10 @@ export function create(config) {
|
|
|
332
333
|
}));
|
|
333
334
|
};
|
|
334
335
|
let receipt;
|
|
336
|
+
let validation;
|
|
335
337
|
try {
|
|
336
338
|
if (mi.broadcast && mi.validate)
|
|
337
|
-
await mi.validate({
|
|
339
|
+
validation = await mi.validate({
|
|
338
340
|
credential: parsedCredential,
|
|
339
341
|
envelope,
|
|
340
342
|
operation: 'broadcast',
|
|
@@ -344,7 +346,7 @@ export function create(config) {
|
|
|
344
346
|
receipt = await broadcast({ credential: parsedCredential, envelope, request });
|
|
345
347
|
}
|
|
346
348
|
catch (e) {
|
|
347
|
-
const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError();
|
|
349
|
+
const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError({ cause: e });
|
|
348
350
|
await emitStandalonePaymentFailed({
|
|
349
351
|
challenge: prepared.credential.challenge,
|
|
350
352
|
credential: parsedCredential,
|
|
@@ -360,6 +362,7 @@ export function create(config) {
|
|
|
360
362
|
credential: parsedCredential,
|
|
361
363
|
envelope,
|
|
362
364
|
method: mi,
|
|
365
|
+
payer: validation?.payer,
|
|
363
366
|
receipt,
|
|
364
367
|
request: parsedRequest,
|
|
365
368
|
...(requestInput !== undefined && { requestInput }),
|
|
@@ -608,7 +611,7 @@ function createMethodFn(parameters) {
|
|
|
608
611
|
? credentialError
|
|
609
612
|
: reason
|
|
610
613
|
? new Errors.MalformedCredentialError({ reason })
|
|
611
|
-
: new Errors.InternalPaymentError();
|
|
614
|
+
: new Errors.InternalPaymentError({ cause: credentialError });
|
|
612
615
|
await emitPaymentFailed({
|
|
613
616
|
challenge,
|
|
614
617
|
credential: null,
|
|
@@ -674,7 +677,7 @@ function createMethodFn(parameters) {
|
|
|
674
677
|
catch (e) {
|
|
675
678
|
if (!(e instanceof Errors.PaymentError))
|
|
676
679
|
console.error('mppx: internal authorization error', e);
|
|
677
|
-
const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError();
|
|
680
|
+
const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError({ cause: e });
|
|
678
681
|
await emitPaymentFailed({
|
|
679
682
|
challenge,
|
|
680
683
|
credential: null,
|
|
@@ -834,9 +837,10 @@ function createMethodFn(parameters) {
|
|
|
834
837
|
// User-provided verification (e.g., check signature, submit tx, verify payment).
|
|
835
838
|
// If verification fails, re-issue the challenge so the client can retry.
|
|
836
839
|
let receiptData;
|
|
840
|
+
let validation;
|
|
837
841
|
try {
|
|
838
842
|
if (broadcast && validate)
|
|
839
|
-
await validate({
|
|
843
|
+
validation = await validate({
|
|
840
844
|
credential: parsedCredential,
|
|
841
845
|
envelope,
|
|
842
846
|
operation: 'broadcast',
|
|
@@ -852,7 +856,7 @@ function createMethodFn(parameters) {
|
|
|
852
856
|
catch (e) {
|
|
853
857
|
if (!(e instanceof Errors.PaymentError))
|
|
854
858
|
console.error('mppx: internal verification error', e);
|
|
855
|
-
const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError();
|
|
859
|
+
const error = e instanceof Errors.PaymentError ? e : new Errors.InternalPaymentError({ cause: e });
|
|
856
860
|
await emitPaymentFailed({
|
|
857
861
|
challenge,
|
|
858
862
|
credential: parsedCredential,
|
|
@@ -890,6 +894,7 @@ function createMethodFn(parameters) {
|
|
|
890
894
|
envelope,
|
|
891
895
|
input,
|
|
892
896
|
method,
|
|
897
|
+
payer: validation?.payer,
|
|
893
898
|
receipt: receiptData,
|
|
894
899
|
request: parsedRequest,
|
|
895
900
|
requestInput: request,
|
|
@@ -986,6 +991,7 @@ function createServerEventDispatcher() {
|
|
|
986
991
|
await method.onPaymentSuccess({
|
|
987
992
|
challenge: success.challenge,
|
|
988
993
|
input: success.input,
|
|
994
|
+
...(success.payer !== undefined && { payer: success.payer }),
|
|
989
995
|
receipt: success.receipt,
|
|
990
996
|
request: success.request,
|
|
991
997
|
...(success.requestInput !== undefined && { requestInput: success.requestInput }),
|
|
@@ -1066,6 +1072,7 @@ function createPaymentSuccessContext(parameters) {
|
|
|
1066
1072
|
...(parameters.envelope ? { envelope: snapshotVerifiedEnvelope(parameters.envelope) } : {}),
|
|
1067
1073
|
...snapshotInputProperty(parameters.input),
|
|
1068
1074
|
method: snapshotMethod(parameters.method),
|
|
1075
|
+
...(parameters.payer !== undefined && { payer: parameters.payer }),
|
|
1069
1076
|
receipt: snapshotValue(parameters.receipt),
|
|
1070
1077
|
request: snapshotValue(parameters.request),
|
|
1071
1078
|
...(parameters.requestInput !== undefined && {
|
|
@@ -1084,6 +1091,7 @@ function snapshotError(error) {
|
|
|
1084
1091
|
return error;
|
|
1085
1092
|
const snapshot = Object.assign(Object.create(Object.getPrototypeOf(error)), error);
|
|
1086
1093
|
Object.defineProperties(snapshot, {
|
|
1094
|
+
...('cause' in error && { cause: { value: error.cause, enumerable: false } }),
|
|
1087
1095
|
message: { value: error.message, enumerable: false },
|
|
1088
1096
|
name: { value: error.name, enumerable: false },
|
|
1089
1097
|
});
|
|
@@ -1261,13 +1269,6 @@ export function isMissingReceiptResponseError(error) {
|
|
|
1261
1269
|
function normalizeExpires(expires) {
|
|
1262
1270
|
return expires === undefined ? undefined : z.toDatetimeString(expires);
|
|
1263
1271
|
}
|
|
1264
|
-
const _warned = new Set();
|
|
1265
|
-
function warnOnce(key, message) {
|
|
1266
|
-
if (_warned.has(key))
|
|
1267
|
-
return;
|
|
1268
|
-
_warned.add(key);
|
|
1269
|
-
console.warn(`[mppx] ${message}`);
|
|
1270
|
-
}
|
|
1271
1272
|
/** Extracts hostname from the captured request URL, falling back to a default. */
|
|
1272
1273
|
function resolveRealmFromCapturedRequest(capturedRequest) {
|
|
1273
1274
|
try {
|
|
@@ -1416,6 +1417,19 @@ function getPinnedRequestBindingMismatch(expectedRequest, actualRequest) {
|
|
|
1416
1417
|
return (getCoreBindingMismatch(expected.coreBinding, actual.coreBinding) ??
|
|
1417
1418
|
getMethodBindingMismatch(expected.methodBinding, actual.methodBinding));
|
|
1418
1419
|
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Compares a credential with a composed handler's route request when dispatching. The route
|
|
1422
|
+
* request lacks method details that the handler's `request()` hook adds (for example a session's
|
|
1423
|
+
* `chainId` and `sessionProtocol`), so only the method fields it defines are compared. The
|
|
1424
|
+
* selected handler still checks every pinned field against its full challenge.
|
|
1425
|
+
*/
|
|
1426
|
+
function getConfiguredRequestBindingMismatch(configuredRequest, actualRequest) {
|
|
1427
|
+
const expected = getPinnedRequestBinding(configuredRequest);
|
|
1428
|
+
const actual = getPinnedRequestBinding(actualRequest);
|
|
1429
|
+
return (getCoreBindingMismatch(expected.coreBinding, actual.coreBinding) ??
|
|
1430
|
+
methodBindingFields.find((field) => expected.methodBinding[field] !== undefined &&
|
|
1431
|
+
!isDeepStrictEqual(expected.methodBinding[field], actual.methodBinding[field])));
|
|
1432
|
+
}
|
|
1419
1433
|
function getCoreBindingMismatch(expected, actual) {
|
|
1420
1434
|
return coreBindingFields.find((field) => !isDeepStrictEqual(expected[field], actual[field]));
|
|
1421
1435
|
}
|
|
@@ -1447,6 +1461,15 @@ function getPinnedRequestBinding(request) {
|
|
|
1447
1461
|
},
|
|
1448
1462
|
};
|
|
1449
1463
|
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Compares a credential with a composed handler's stable route binding when dispatching. The route
|
|
1466
|
+
* request lacks fields its `request()` hook adds, so only the fields it defines are compared; the
|
|
1467
|
+
* selected handler still checks the full binding against its challenge.
|
|
1468
|
+
*/
|
|
1469
|
+
function getConfiguredStableBindingMismatch(expected, actual) {
|
|
1470
|
+
return Object.keys(expected).find((field) => expected[field] !== undefined &&
|
|
1471
|
+
!isDeepStrictEqual(normalizeComparable(expected[field]), normalizeComparable(actual[field])));
|
|
1472
|
+
}
|
|
1450
1473
|
function getRequestBindingMismatch(expected, actual) {
|
|
1451
1474
|
const fields = [
|
|
1452
1475
|
...Object.keys(expected),
|
|
@@ -1601,9 +1624,13 @@ function composeHandlers(handlers, composeOptions, selectOffers) {
|
|
|
1601
1624
|
if (internal.name !== credMethod || internal.intent !== credIntent)
|
|
1602
1625
|
return false;
|
|
1603
1626
|
const mismatch = internal._stableBinding
|
|
1604
|
-
?
|
|
1605
|
-
:
|
|
1606
|
-
|
|
1627
|
+
? getConfiguredStableBindingMismatch(getStableBinding(internal._canonicalRequest, internal._stableBinding), getStableBinding(credReq, internal._stableBinding))
|
|
1628
|
+
: getConfiguredRequestBindingMismatch(internal._canonicalRequest, credReq);
|
|
1629
|
+
// A handler without a static scope issues challenges scoped to the request's route.
|
|
1630
|
+
const effectiveMeta = internal.scope === undefined
|
|
1631
|
+
? Scope.merge({ meta: internal.meta, scope: Scope.get(input) })
|
|
1632
|
+
: internal.meta;
|
|
1633
|
+
return !mismatch && opaqueValuesMatch(effectiveMeta, credential.challenge.meta);
|
|
1607
1634
|
}
|
|
1608
1635
|
catch {
|
|
1609
1636
|
return false;
|
|
@@ -1805,8 +1832,8 @@ function composeMcpHandlers(handlers, transport) {
|
|
|
1805
1832
|
const match = candidates.find((handler) => getConfiguredOffers(handler).some((internal) => {
|
|
1806
1833
|
try {
|
|
1807
1834
|
const mismatch = internal._stableBinding
|
|
1808
|
-
?
|
|
1809
|
-
:
|
|
1835
|
+
? getConfiguredStableBindingMismatch(getStableBinding(internal._canonicalRequest, internal._stableBinding), getStableBinding(credential.challenge.request, internal._stableBinding))
|
|
1836
|
+
: getConfiguredRequestBindingMismatch(internal._canonicalRequest, credential.challenge.request);
|
|
1810
1837
|
return !mismatch && opaqueValuesMatch(internal.meta, credential.challenge.meta);
|
|
1811
1838
|
}
|
|
1812
1839
|
catch {
|
package/dist/server/Transport.js
CHANGED
|
@@ -59,6 +59,16 @@ export function http(options = {}) {
|
|
|
59
59
|
},
|
|
60
60
|
async respondChallenge(options) {
|
|
61
61
|
const { challenge, error, input } = options;
|
|
62
|
+
// A 403 means the payment was valid but cannot pay for this request.
|
|
63
|
+
// Omit the challenge so clients do not pay again.
|
|
64
|
+
if (error?.status === 403)
|
|
65
|
+
return new Response(JSON.stringify(error.toProblemDetails()), {
|
|
66
|
+
status: error.status,
|
|
67
|
+
headers: {
|
|
68
|
+
'Cache-Control': 'no-store',
|
|
69
|
+
'Content-Type': 'application/problem+json',
|
|
70
|
+
},
|
|
71
|
+
});
|
|
62
72
|
if (options.html && new URL(input.url).searchParams.has(Html.params.serviceWorker))
|
|
63
73
|
return new Response(serviceWorker, {
|
|
64
74
|
status: 200,
|
|
@@ -133,7 +133,7 @@ export function stripe(parameters) {
|
|
|
133
133
|
const suppliedStore = params.store;
|
|
134
134
|
if (suppliedStore !== undefined && suppliedStore !== store)
|
|
135
135
|
throw new Error('stripe.create() Tempo sessions must use the factory-level shared atomic `store`.');
|
|
136
|
-
const { recipient, onSessionSettlement, ...rest } = params;
|
|
136
|
+
const { recipient, canOffer, onSessionSettlement, ...rest } = params;
|
|
137
137
|
return tempoSession({
|
|
138
138
|
currency: tempoCurrency,
|
|
139
139
|
recipient,
|
|
@@ -141,6 +141,9 @@ export function stripe(parameters) {
|
|
|
141
141
|
...(hostedTempoFeePayer && { feePayer: hostedTempoFeePayer }),
|
|
142
142
|
...rest,
|
|
143
143
|
store,
|
|
144
|
+
canOffer: canOffer
|
|
145
|
+
? async (context) => cryptoCanOffer(context) && (await canOffer(context))
|
|
146
|
+
: cryptoCanOffer,
|
|
144
147
|
async onSessionSettlement(context) {
|
|
145
148
|
await tempoPaymentHandler({
|
|
146
149
|
intent: 'session',
|