mppx 0.12.0 → 0.13.2
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 +64 -0
- 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/cli.d.ts +8 -0
- 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/sessions/request.d.ts +1 -1
- package/dist/cli/sessions/request.js +6 -8
- 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 +34 -12
- 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 +22 -7
- 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/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 +14 -2
- package/dist/server/Mppx.js +85 -44
- 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/stripe/server/Methods.d.ts +10 -3
- package/dist/stripe/server/Methods.js +17 -2
- 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/fee-payer.d.ts +6 -3
- package/dist/tempo/internal/fee-payer.js +23 -6
- 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.js +87 -53
- package/dist/tempo/server/Methods.d.ts +55 -10
- package/dist/tempo/server/Subscription.d.ts +1 -1
- package/dist/tempo/server/Subscription.js +31 -4
- 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 +20 -2
- package/dist/tempo/session/server/ChannelStore.js +25 -4
- package/dist/tempo/session/server/CredentialVerification.d.ts +4 -1
- package/dist/tempo/session/server/CredentialVerification.js +34 -6
- package/dist/tempo/session/server/MeteredStream.d.ts +3 -2
- package/dist/tempo/session/server/MeteredStream.js +85 -29
- package/dist/tempo/session/server/Session.d.ts +2 -1
- package/dist/tempo/session/server/Session.js +2 -1
- package/dist/tempo/session/server/Settlement.d.ts +21 -0
- package/dist/tempo/session/server/Settlement.js +92 -17
- 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/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 +6 -6
- 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
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
* Useful when bridging Fetch API handlers with Node.js HTTP servers.
|
|
5
5
|
*/
|
|
6
6
|
export async function sendResponse(res, response) {
|
|
7
|
+
if (isResponseClosed(res)) {
|
|
8
|
+
await response.body?.cancel('response connection closed').catch(() => { });
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
7
11
|
const headers = {};
|
|
8
12
|
for (const [key, value] of response.headers) {
|
|
9
13
|
if (key in headers) {
|
|
@@ -23,21 +27,82 @@ export async function sendResponse(res, response) {
|
|
|
23
27
|
res.writeHead(response.status, headers);
|
|
24
28
|
if (response.body != null && res.req?.method !== 'HEAD') {
|
|
25
29
|
const reader = response.body.getReader();
|
|
30
|
+
let connectionClosed = false;
|
|
31
|
+
const onClose = () => {
|
|
32
|
+
connectionClosed = true;
|
|
33
|
+
void reader.cancel('response connection closed').catch(() => { });
|
|
34
|
+
};
|
|
35
|
+
res.once('close', onClose);
|
|
36
|
+
if (isResponseClosed(res))
|
|
37
|
+
onClose();
|
|
26
38
|
try {
|
|
39
|
+
if (res.destroyed || res.writableEnded) {
|
|
40
|
+
await reader.cancel('response connection closed');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
27
43
|
while (true) {
|
|
28
44
|
const { done, value } = await reader.read();
|
|
29
|
-
if (done)
|
|
45
|
+
if (done || connectionClosed)
|
|
30
46
|
break;
|
|
31
|
-
if (res.write(value) === false)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
47
|
+
if (res.write(value) === false) {
|
|
48
|
+
try {
|
|
49
|
+
if (!(await waitForDrain(res))) {
|
|
50
|
+
await reader.cancel('response connection closed');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
await reader.cancel(error).catch(() => { });
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
35
59
|
}
|
|
36
60
|
}
|
|
37
61
|
finally {
|
|
62
|
+
res.removeListener('close', onClose);
|
|
38
63
|
reader.releaseLock();
|
|
39
64
|
}
|
|
40
65
|
}
|
|
41
|
-
res
|
|
66
|
+
if (!isResponseClosed(res))
|
|
67
|
+
res.end();
|
|
68
|
+
}
|
|
69
|
+
/** Returns whether the HTTP/1 response or underlying HTTP/2 stream has closed. */
|
|
70
|
+
function isResponseClosed(res) {
|
|
71
|
+
const stream = 'stream' in res ? res.stream : undefined;
|
|
72
|
+
return Boolean(res.destroyed || res.writableEnded || stream?.destroyed || stream?.closed);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Waits for write backpressure to clear.
|
|
76
|
+
*
|
|
77
|
+
* Returns `true` on drain, `false` when the response closes, and rejects on a
|
|
78
|
+
* write-side error. Event listeners are always removed before settlement.
|
|
79
|
+
*/
|
|
80
|
+
function waitForDrain(res) {
|
|
81
|
+
if (isResponseClosed(res))
|
|
82
|
+
return Promise.resolve(false);
|
|
83
|
+
return new Promise((resolve, reject) => {
|
|
84
|
+
const cleanup = () => {
|
|
85
|
+
res.removeListener('drain', onDrain);
|
|
86
|
+
res.removeListener('close', onClose);
|
|
87
|
+
res.removeListener('error', onError);
|
|
88
|
+
};
|
|
89
|
+
const onDrain = () => {
|
|
90
|
+
cleanup();
|
|
91
|
+
resolve(true);
|
|
92
|
+
};
|
|
93
|
+
const onClose = () => {
|
|
94
|
+
cleanup();
|
|
95
|
+
resolve(false);
|
|
96
|
+
};
|
|
97
|
+
const onError = (error) => {
|
|
98
|
+
cleanup();
|
|
99
|
+
reject(error);
|
|
100
|
+
};
|
|
101
|
+
res.once('drain', onDrain);
|
|
102
|
+
res.once('close', onClose);
|
|
103
|
+
res.once('error', onError);
|
|
104
|
+
if (isResponseClosed(res))
|
|
105
|
+
onClose();
|
|
106
|
+
});
|
|
42
107
|
}
|
|
43
108
|
//# sourceMappingURL=NodeListener.js.map
|
package/dist/server/Request.d.ts
CHANGED
|
@@ -2,9 +2,17 @@ import type { IncomingMessage, RequestListener, ServerResponse } from 'node:http
|
|
|
2
2
|
export type FetchHandler = (request: Request) => Promise<Response> | Response;
|
|
3
3
|
export type RequestListenerOptions = {
|
|
4
4
|
host?: string | undefined;
|
|
5
|
+
/** Maximum accepted request body size in bytes. @default 10485760 (10 MiB) */
|
|
6
|
+
maxBodySize?: number | undefined;
|
|
5
7
|
onError?: ((error: unknown) => void | Response | Promise<void | Response>) | undefined;
|
|
6
8
|
protocol?: string | undefined;
|
|
7
9
|
};
|
|
10
|
+
/** Options used only while converting a Node request to a Fetch request. */
|
|
11
|
+
export type NodeConversionOptions = Omit<RequestListenerOptions, 'onError'>;
|
|
12
|
+
/** Raised when a Node request body exceeds the configured byte limit. */
|
|
13
|
+
export declare class RequestBodyTooLargeError extends Error {
|
|
14
|
+
readonly name = "RequestBodyTooLargeError";
|
|
15
|
+
}
|
|
8
16
|
/**
|
|
9
17
|
* Converts a Fetch API handler into a Node.js HTTP request listener.
|
|
10
18
|
*
|
|
@@ -20,5 +28,7 @@ export declare function toNodeListener(handler: FetchHandler, options?: RequestL
|
|
|
20
28
|
* @param res - The Node.js ServerResponse (used for abort signal lifecycle).
|
|
21
29
|
* @returns A Fetch API Request.
|
|
22
30
|
*/
|
|
23
|
-
export declare function fromNodeListener(req: IncomingMessage, res: ServerResponse, options?:
|
|
31
|
+
export declare function fromNodeListener(req: IncomingMessage, res: ServerResponse, options?: NodeConversionOptions | undefined): Request;
|
|
32
|
+
/** Waits for a Node-backed request body to finish and enforces its size limit. */
|
|
33
|
+
export declare function waitForBody(request: Request): Promise<void>;
|
|
24
34
|
//# sourceMappingURL=Request.d.ts.map
|
package/dist/server/Request.js
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import * as NodeListener from './NodeListener.js';
|
|
2
|
+
const defaultMaxBodySize = 10 * 1024 * 1024;
|
|
3
|
+
const bodyCompletions = new WeakMap();
|
|
4
|
+
/** Raised when a Node request body exceeds the configured byte limit. */
|
|
5
|
+
export class RequestBodyTooLargeError extends Error {
|
|
6
|
+
name = 'RequestBodyTooLargeError';
|
|
7
|
+
}
|
|
2
8
|
/**
|
|
3
9
|
* Converts a Fetch API handler into a Node.js HTTP request listener.
|
|
4
10
|
*
|
|
@@ -9,6 +15,11 @@ import * as NodeListener from './NodeListener.js';
|
|
|
9
15
|
export function toNodeListener(handler, options) {
|
|
10
16
|
const onError = options?.onError ??
|
|
11
17
|
((error) => {
|
|
18
|
+
if (error instanceof RequestBodyTooLargeError)
|
|
19
|
+
return new Response('Payload Too Large', {
|
|
20
|
+
status: 413,
|
|
21
|
+
headers: { 'Content-Type': 'text/plain' },
|
|
22
|
+
});
|
|
12
23
|
console.error(error);
|
|
13
24
|
return new Response('Internal Server Error', {
|
|
14
25
|
status: 500,
|
|
@@ -20,6 +31,7 @@ export function toNodeListener(handler, options) {
|
|
|
20
31
|
try {
|
|
21
32
|
const request = fromNodeListener(req, res, options);
|
|
22
33
|
response = await handler(request);
|
|
34
|
+
await waitForBody(request);
|
|
23
35
|
}
|
|
24
36
|
catch (error) {
|
|
25
37
|
try {
|
|
@@ -70,20 +82,127 @@ export function fromNodeListener(req, res, options) {
|
|
|
70
82
|
headers,
|
|
71
83
|
signal: controller.signal,
|
|
72
84
|
};
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
+
const maxBodySize = options?.maxBodySize ?? defaultMaxBodySize;
|
|
86
|
+
validateBodySize(req, maxBodySize);
|
|
87
|
+
let bodyCompletion;
|
|
88
|
+
let body;
|
|
89
|
+
if (hasBody(headers)) {
|
|
90
|
+
const monitored = createBodyStream(req, maxBodySize);
|
|
91
|
+
body = monitored.body;
|
|
92
|
+
if (method !== 'GET' && method !== 'HEAD') {
|
|
93
|
+
init.body = body;
|
|
94
|
+
init.duplex = 'half';
|
|
95
|
+
bodyCompletion = monitored.completion;
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
void consumeBody(body).catch(() => { });
|
|
99
|
+
bodyCompletion = monitored.completion;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
let request;
|
|
103
|
+
try {
|
|
104
|
+
request = new Request(url, init);
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
void body?.cancel(error).catch(() => { });
|
|
108
|
+
throw error;
|
|
109
|
+
}
|
|
110
|
+
if (bodyCompletion)
|
|
111
|
+
bodyCompletions.set(request, bodyCompletion);
|
|
112
|
+
return request;
|
|
113
|
+
}
|
|
114
|
+
/** Waits for a Node-backed request body to finish and enforces its size limit. */
|
|
115
|
+
export async function waitForBody(request) {
|
|
116
|
+
const completion = bodyCompletions.get(request);
|
|
117
|
+
if (!completion)
|
|
118
|
+
return;
|
|
119
|
+
if (request.body && !request.bodyUsed && !request.body.locked)
|
|
120
|
+
await consumeBody(request.body);
|
|
121
|
+
const error = await completion;
|
|
122
|
+
if (error !== undefined)
|
|
123
|
+
throw error;
|
|
124
|
+
}
|
|
125
|
+
async function consumeBody(body) {
|
|
126
|
+
const reader = body.getReader();
|
|
127
|
+
try {
|
|
128
|
+
while (!(await reader.read()).done) { }
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
reader.releaseLock();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function createBodyStream(req, maxBodySize) {
|
|
135
|
+
let received = 0;
|
|
136
|
+
let closed = false;
|
|
137
|
+
let cleanup = () => { };
|
|
138
|
+
let resolveCompletion;
|
|
139
|
+
const completion = new Promise((resolve) => {
|
|
140
|
+
resolveCompletion = resolve;
|
|
141
|
+
});
|
|
142
|
+
const body = new ReadableStream({
|
|
143
|
+
start(controller) {
|
|
144
|
+
const finish = (action, error) => {
|
|
145
|
+
if (closed)
|
|
146
|
+
return;
|
|
147
|
+
closed = true;
|
|
148
|
+
cleanup();
|
|
149
|
+
action();
|
|
150
|
+
resolveCompletion(error);
|
|
151
|
+
};
|
|
152
|
+
const onData = (value) => {
|
|
153
|
+
const chunk = typeof value === 'string' ? Buffer.from(value) : value;
|
|
154
|
+
received += chunk.byteLength;
|
|
155
|
+
if (received > maxBodySize) {
|
|
156
|
+
const error = new RequestBodyTooLargeError(`Request body exceeds ${maxBodySize} bytes`);
|
|
157
|
+
finish(() => controller.error(error), error);
|
|
158
|
+
req.resume();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
controller.enqueue(new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength));
|
|
162
|
+
if ((controller.desiredSize ?? 0) <= 0)
|
|
163
|
+
req.pause();
|
|
164
|
+
};
|
|
165
|
+
const onEnd = () => finish(() => controller.close());
|
|
166
|
+
const onError = (error) => finish(() => controller.error(error), error);
|
|
167
|
+
const onAborted = () => {
|
|
168
|
+
const error = new Error('request aborted');
|
|
169
|
+
finish(() => controller.error(error), error);
|
|
170
|
+
};
|
|
171
|
+
cleanup = () => {
|
|
172
|
+
req.removeListener('data', onData);
|
|
173
|
+
req.removeListener('end', onEnd);
|
|
174
|
+
req.removeListener('error', onError);
|
|
175
|
+
req.removeListener('aborted', onAborted);
|
|
176
|
+
};
|
|
177
|
+
req.pause();
|
|
178
|
+
req.on('data', onData);
|
|
179
|
+
req.once('end', onEnd);
|
|
180
|
+
req.once('error', onError);
|
|
181
|
+
req.once('aborted', onAborted);
|
|
182
|
+
},
|
|
183
|
+
pull() {
|
|
184
|
+
if (!closed)
|
|
185
|
+
req.resume();
|
|
186
|
+
},
|
|
187
|
+
cancel(reason) {
|
|
188
|
+
if (closed)
|
|
189
|
+
return;
|
|
190
|
+
closed = true;
|
|
191
|
+
cleanup();
|
|
192
|
+
req.destroy(reason instanceof Error ? reason : undefined);
|
|
193
|
+
resolveCompletion(reason);
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
return { body, completion };
|
|
197
|
+
}
|
|
198
|
+
function validateBodySize(req, maxBodySize) {
|
|
199
|
+
if (!Number.isSafeInteger(maxBodySize) || maxBodySize < 0)
|
|
200
|
+
throw new TypeError('maxBodySize must be a non-negative safe integer');
|
|
201
|
+
const contentLength = Number(req.headers['content-length']);
|
|
202
|
+
if (Number.isFinite(contentLength) && contentLength > maxBodySize) {
|
|
203
|
+
req.resume();
|
|
204
|
+
throw new RequestBodyTooLargeError(`Request body exceeds ${maxBodySize} bytes`);
|
|
85
205
|
}
|
|
86
|
-
return new Request(url, init);
|
|
87
206
|
}
|
|
88
207
|
function hasBody(headers) {
|
|
89
208
|
const contentLength = headers.get('content-length');
|
|
@@ -16,6 +16,10 @@ export { type McpSdk, mcpSdk } from '../mcp/server/Transport.js';
|
|
|
16
16
|
export type Transport<in out input = unknown, in out challengeOutput = unknown, in receiptResponse = challengeOutput, out receiptOutput = receiptResponse> = {
|
|
17
17
|
/** Transport name for identification. */
|
|
18
18
|
name: string;
|
|
19
|
+
/** Whether receipt wrapping accepts route-produced async iterables. */
|
|
20
|
+
supportsStreamingReceipts?: boolean | undefined;
|
|
21
|
+
/** HTTP header names from which this transport can read credentials. */
|
|
22
|
+
credentialHeaders?: readonly string[] | undefined;
|
|
19
23
|
/** Captures the transport request into an immutable verification snapshot. */
|
|
20
24
|
captureRequest?: ((input: input) => MaybePromise<Method.CapturedRequest>) | undefined;
|
|
21
25
|
/**
|
|
@@ -33,6 +37,11 @@ export type Transport<in out input = unknown, in out challengeOutput = unknown,
|
|
|
33
37
|
* Returns `null` if no credential was provided, or throws if malformed.
|
|
34
38
|
*/
|
|
35
39
|
getCredential: (input: input) => Credential.Credential | null;
|
|
40
|
+
/** Returns whether a transport-native credential targets a configured request. */
|
|
41
|
+
matchCredential?: ((options: {
|
|
42
|
+
input: input;
|
|
43
|
+
request: Record<string, unknown>;
|
|
44
|
+
}) => MaybePromise<boolean>) | undefined;
|
|
36
45
|
/** Creates a transport response for a payment challenge. */
|
|
37
46
|
respondChallenge: (options: {
|
|
38
47
|
challenge: Challenge.Challenge;
|
|
@@ -53,7 +62,11 @@ export type Transport<in out input = unknown, in out challengeOutput = unknown,
|
|
|
53
62
|
export type AnyTransport = Transport<any, any, any, any>;
|
|
54
63
|
export type Http = Transport<Request, Response>;
|
|
55
64
|
export type Mcp = Transport<core_Mcp.JsonRpcRequest, core_Mcp.Response>;
|
|
56
|
-
|
|
65
|
+
/**
|
|
66
|
+
* SSE receipt wrapping is asynchronous so accounting completes before even a
|
|
67
|
+
* bodyless response can be returned to the application.
|
|
68
|
+
*/
|
|
69
|
+
export type Sse<stream = any> = Transport<Request, Response, Response | AsyncIterable<string> | ((stream: stream) => AsyncIterable<string>), Promise<Response>>;
|
|
57
70
|
/** Extracts the input type from a transport. */
|
|
58
71
|
export type InputOf<transport extends AnyTransport = AnyTransport> = transport extends Transport<infer input, any, any> ? input : never;
|
|
59
72
|
/** Extracts the challenge output type from a transport. */
|
package/dist/server/Transport.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { charge as evmCharge } from '../../evm/server/Charge.js';
|
|
2
2
|
import type * as Method from '../../Method.js';
|
|
3
|
+
import type * as Store from '../../Store.js';
|
|
3
4
|
import { charge as tempoCharge } from '../../tempo/server/Charge.js';
|
|
4
5
|
import { session as tempoSession } from '../../tempo/session/server/Session.js';
|
|
5
6
|
import * as PaymentIntent from '../internal/payment-intent.js';
|
|
@@ -19,6 +20,10 @@ export declare namespace stripe {
|
|
|
19
20
|
networkId: string;
|
|
20
21
|
livemode: boolean;
|
|
21
22
|
hostedFeePayer?: boolean | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Atomic replay store shared by every Stripe-backed Tempo server instance.
|
|
25
|
+
*/
|
|
26
|
+
store: Store.AtomicStore;
|
|
22
27
|
connect?: ConnectConfig;
|
|
23
28
|
depositAddresses?: Partial<Record<Network, string>> | ((network: Network) => Promise<string>);
|
|
24
29
|
metadata?: Record<string, string> | undefined;
|
|
@@ -40,7 +45,7 @@ type CustomRailNetwork = Exclude<stripe.Network, 'tempo' | 'base'>;
|
|
|
40
45
|
type AdditionalConfig = {
|
|
41
46
|
base?: BaseConfig;
|
|
42
47
|
tempo?: {
|
|
43
|
-
session: Omit<tempoSession.Parameters, 'currency' | 'recipient'>;
|
|
48
|
+
session: Omit<tempoSession.Parameters, 'currency' | 'recipient' | 'store'>;
|
|
44
49
|
};
|
|
45
50
|
} & {
|
|
46
51
|
[K in CustomRailNetwork]?: CustomRailFactory;
|
|
@@ -75,10 +80,10 @@ interface StripeMachinePayments<P extends stripe.Parameters = stripe.Parameters>
|
|
|
75
80
|
charge: (params: {
|
|
76
81
|
recipient: stripe.DepositAddress<'tempo'>;
|
|
77
82
|
metadata?: Record<string, string>;
|
|
78
|
-
} & Partial<Omit<Parameters<typeof tempoCharge>[0], 'currency' | 'recipient'>>) => TempoServer;
|
|
83
|
+
} & Partial<Omit<Parameters<typeof tempoCharge>[0], 'currency' | 'recipient' | 'store' | 'storeKeyPrefix'>>) => TempoServer;
|
|
79
84
|
session: (params: {
|
|
80
85
|
recipient: stripe.DepositAddress<'tempo'>;
|
|
81
|
-
} & Omit<tempoSession.Parameters, 'currency' | 'recipient'>) => TempoSessionServer;
|
|
86
|
+
} & Omit<tempoSession.Parameters, 'currency' | 'recipient' | 'store'>) => TempoSessionServer;
|
|
82
87
|
};
|
|
83
88
|
base: {
|
|
84
89
|
charge: (params: {
|
|
@@ -106,6 +111,7 @@ declare class DefaultMethodsBuilder implements PromiseLike<DefaultMethods> {
|
|
|
106
111
|
* client: stripeClient,
|
|
107
112
|
* networkId: process.env.STRIPE_PROFILE_ID!,
|
|
108
113
|
* livemode: !process.env.STRIPE_SECRET_KEY!.includes('_test_'),
|
|
114
|
+
* store,
|
|
109
115
|
* })
|
|
110
116
|
*
|
|
111
117
|
* // Async: deposit addresses resolved from Stripe API
|
|
@@ -123,6 +129,7 @@ declare class DefaultMethodsBuilder implements PromiseLike<DefaultMethods> {
|
|
|
123
129
|
* networkId: process.env.STRIPE_PROFILE_ID!,
|
|
124
130
|
* livemode: !process.env.STRIPE_SECRET_KEY!.includes('_test_'),
|
|
125
131
|
* depositAddresses: { tempo: process.env.TEMPO_DEPOSIT_ADDRESS! },
|
|
132
|
+
* store,
|
|
126
133
|
* })
|
|
127
134
|
*
|
|
128
135
|
* const mppx = Mppx.create({
|
|
@@ -37,6 +37,7 @@ class DefaultMethodsBuilder {
|
|
|
37
37
|
* client: stripeClient,
|
|
38
38
|
* networkId: process.env.STRIPE_PROFILE_ID!,
|
|
39
39
|
* livemode: !process.env.STRIPE_SECRET_KEY!.includes('_test_'),
|
|
40
|
+
* store,
|
|
40
41
|
* })
|
|
41
42
|
*
|
|
42
43
|
* // Async: deposit addresses resolved from Stripe API
|
|
@@ -54,6 +55,7 @@ class DefaultMethodsBuilder {
|
|
|
54
55
|
* networkId: process.env.STRIPE_PROFILE_ID!,
|
|
55
56
|
* livemode: !process.env.STRIPE_SECRET_KEY!.includes('_test_'),
|
|
56
57
|
* depositAddresses: { tempo: process.env.TEMPO_DEPOSIT_ADDRESS! },
|
|
58
|
+
* store,
|
|
57
59
|
* })
|
|
58
60
|
*
|
|
59
61
|
* const mppx = Mppx.create({
|
|
@@ -80,11 +82,13 @@ class DefaultMethodsBuilder {
|
|
|
80
82
|
* retries the recording once without them. SPT payments do not use this fallback.
|
|
81
83
|
*/
|
|
82
84
|
export function stripe(parameters) {
|
|
83
|
-
const { client, networkId, livemode, hostedFeePayer, connect, depositAddresses, metadata } = parameters;
|
|
85
|
+
const { client, networkId, livemode, hostedFeePayer, connect, depositAddresses, metadata, store, } = parameters;
|
|
84
86
|
if (!client.rawRequest)
|
|
85
87
|
throw new Error('stripe.create() requires a Stripe SDK client with rawRequest() (v15+)');
|
|
86
88
|
if (hostedFeePayer && connect)
|
|
87
89
|
throw new Error('Stripe hosted fee payer does not support Connect account routing.');
|
|
90
|
+
if (typeof store?.update !== 'function')
|
|
91
|
+
throw new Error('stripe.create() requires a shared atomic `store` for replay protection.');
|
|
88
92
|
const tempoCurrency = (livemode ? tempoDefaults.tokens.usdc : tempoDefaults.tokens.pathUsd);
|
|
89
93
|
const hostedTempoFeePayer = hostedFeePayer && livemode ? HostedFeePayer.create(client) : undefined;
|
|
90
94
|
const tempoPaymentHandler = createPaymentSuccessHandler(client, 'tempo', connect, metadata);
|
|
@@ -104,7 +108,13 @@ export function stripe(parameters) {
|
|
|
104
108
|
});
|
|
105
109
|
}
|
|
106
110
|
function makeTempoCharge(params) {
|
|
107
|
-
const
|
|
111
|
+
const supplied = params;
|
|
112
|
+
const suppliedStore = supplied.store;
|
|
113
|
+
if (suppliedStore !== undefined && suppliedStore !== store)
|
|
114
|
+
throw new Error('stripe.create() Tempo charges must use the factory-level shared atomic `store`.');
|
|
115
|
+
if (supplied.storeKeyPrefix !== undefined)
|
|
116
|
+
throw new Error('stripe.create() Tempo charges cannot override the shared store namespace.');
|
|
117
|
+
const { recipient, metadata: callMetadata, store: _, storeKeyPrefix: __, ...rest } = params;
|
|
108
118
|
const handler = callMetadata
|
|
109
119
|
? createPaymentSuccessHandler(client, 'tempo', connect, { ...metadata, ...callMetadata })
|
|
110
120
|
: tempoPaymentHandler;
|
|
@@ -116,9 +126,13 @@ export function stripe(parameters) {
|
|
|
116
126
|
canOffer: cryptoCanOffer,
|
|
117
127
|
onPaymentSuccess: handler,
|
|
118
128
|
...rest,
|
|
129
|
+
store,
|
|
119
130
|
}));
|
|
120
131
|
}
|
|
121
132
|
function makeTempoSession(params) {
|
|
133
|
+
const suppliedStore = params.store;
|
|
134
|
+
if (suppliedStore !== undefined && suppliedStore !== store)
|
|
135
|
+
throw new Error('stripe.create() Tempo sessions must use the factory-level shared atomic `store`.');
|
|
122
136
|
const { recipient, onSessionSettlement, ...rest } = params;
|
|
123
137
|
return tempoSession({
|
|
124
138
|
currency: tempoCurrency,
|
|
@@ -126,6 +140,7 @@ export function stripe(parameters) {
|
|
|
126
140
|
...(!livemode && { chainId: tempoDefaults.chainId.testnet }),
|
|
127
141
|
...(hostedTempoFeePayer && { feePayer: hostedTempoFeePayer }),
|
|
128
142
|
...rest,
|
|
143
|
+
store,
|
|
129
144
|
async onSessionSettlement(context) {
|
|
130
145
|
await tempoPaymentHandler({
|
|
131
146
|
intent: 'session',
|