mppx 0.14.1 → 0.16.0
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 +42 -0
- package/README.md +1 -1
- package/dist/Expires.js +1 -1
- package/dist/Store.d.ts +2 -2
- package/dist/Store.js +13 -3
- package/dist/cli/cli.js +137 -131
- package/dist/cli/plugins/index.js +11 -11
- package/dist/cli/validate/payment.d.ts +12 -0
- package/dist/cli/validate/payment.js +113 -14
- package/dist/client/Mppx.d.ts +13 -3
- package/dist/client/Mppx.js +47 -22
- package/dist/client/internal/Fetch.d.ts +11 -0
- package/dist/client/internal/Fetch.js +46 -8
- package/dist/client/internal/ProofRealm.d.ts +21 -0
- package/dist/client/internal/ProofRealm.js +55 -0
- package/dist/{attestation → extensions/identity}/Constants.d.ts +2 -2
- package/dist/{attestation → extensions/identity}/Constants.js +2 -2
- package/dist/extensions/identity/Types.d.ts +21 -0
- package/dist/extensions/identity/index.d.ts +7 -0
- package/dist/extensions/identity/index.js +6 -0
- package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.d.ts +3 -3
- package/dist/{attestation → extensions/identity}/internal/HttpMessageSignature.js +12 -10
- package/dist/extensions/identity/internal/SigningContext.d.ts +6 -0
- package/dist/{attestation → extensions/identity}/internal/SigningContext.js +3 -3
- package/dist/extensions/identity/internal/Verification.d.ts +9 -0
- package/dist/extensions/identity/internal/Verification.js +10 -0
- package/dist/{tap → extensions/identity/tap}/Constants.js +1 -1
- package/dist/extensions/identity/tap/client/index.d.ts +32 -0
- package/dist/{tap → extensions/identity/tap}/client/index.js +13 -9
- package/dist/extensions/identity/tap/server/index.d.ts +27 -0
- package/dist/{tap → extensions/identity/tap}/server/index.js +12 -4
- package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.js +1 -1
- package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.d.ts +6 -5
- package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/client/index.js +5 -5
- package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.d.ts +6 -5
- package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/server/index.js +6 -4
- package/dist/identity/Client.d.ts +34 -0
- package/dist/identity/Client.js +133 -0
- package/dist/identity/Server.d.ts +46 -0
- package/dist/identity/Server.js +145 -0
- package/dist/identity/Types.d.ts +90 -0
- package/dist/identity/Types.js +2 -0
- package/dist/{attestation → identity}/index.d.ts +2 -2
- package/dist/{attestation → identity}/index.js +1 -2
- package/dist/identity/internal/SigningContext.d.ts +6 -0
- package/dist/identity/internal/SigningContext.js +11 -0
- package/dist/integrations/x402/Evm.d.ts +2 -0
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.d.ts +5 -0
- package/dist/mcp/client/McpClient.js +2 -0
- package/dist/middlewares/express.js +18 -6
- package/dist/proxy/Proxy.js +10 -5
- package/dist/proxy/Service.d.ts +42 -4
- package/dist/proxy/Service.js +21 -5
- package/dist/server/Mppx.d.ts +32 -8
- package/dist/server/Mppx.js +144 -54
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/Methods.d.ts +5 -0
- package/dist/tempo/Methods.js +1 -0
- package/dist/tempo/client/Subscription.d.ts +5 -0
- package/dist/tempo/internal/charge.js +2 -1
- package/dist/tempo/internal/fee-payer.d.ts +0 -2
- package/dist/tempo/internal/fee-payer.js +0 -4
- package/dist/tempo/server/Charge.js +4 -0
- package/dist/tempo/server/Methods.d.ts +26 -0
- package/dist/tempo/server/Subscription.d.ts +5 -0
- package/dist/tempo/server/Subscription.js +8 -5
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/session/client/Runtime.d.ts +2 -0
- package/dist/tempo/session/client/Session.js +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +6 -0
- package/dist/tempo/session/client/SessionManager.js +14 -7
- package/dist/tempo/session/client/Transports.d.ts +12 -3
- package/dist/tempo/session/client/Transports.js +37 -11
- package/dist/tempo/session/precompile/Chain.js +0 -3
- package/dist/tempo/session/server/Ws.d.ts +5 -2
- package/dist/tempo/session/server/Ws.js +11 -2
- package/dist/tempo/subscription/Store.d.ts +7 -1
- package/dist/tempo/subscription/Store.js +66 -13
- package/dist/tempo/subscription/Types.d.ts +2 -0
- package/package.json +15 -11
- package/dist/attestation/Client.d.ts +0 -17
- package/dist/attestation/Client.js +0 -28
- package/dist/attestation/Server.d.ts +0 -4
- package/dist/attestation/Server.js +0 -8
- package/dist/attestation/Types.d.ts +0 -76
- package/dist/attestation/internal/SigningContext.d.ts +0 -4
- package/dist/tap/client/index.d.ts +0 -26
- package/dist/tap/server/index.d.ts +0 -20
- /package/dist/{attestation → extensions/identity}/Types.js +0 -0
- /package/dist/{tap → extensions/identity/tap}/Constants.d.ts +0 -0
- /package/dist/{tap → extensions/identity/tap}/Types.d.ts +0 -0
- /package/dist/{tap → extensions/identity/tap}/Types.js +0 -0
- /package/dist/{tap → extensions/identity/tap}/index.d.ts +0 -0
- /package/dist/{tap → extensions/identity/tap}/index.js +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Constants.d.ts +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.d.ts +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/Types.js +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.d.ts +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/index.js +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.d.ts +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/JwkThumbprint.js +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.d.ts +0 -0
- /package/dist/{web-bot-auth → extensions/identity/web-bot-auth}/internal/SignatureAgent.js +0 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type SessionReceipt } from '../../tempo/session/precompile/Protocol.js';
|
|
1
2
|
import type { CheckResult, EndpointSpec } from './helpers.js';
|
|
2
3
|
export declare function validatePaymentFlow(baseUrl: string, endpoint: EndpointSpec, verbose: boolean, options: {
|
|
3
4
|
body?: string | undefined;
|
|
@@ -8,4 +9,15 @@ export declare function validatePaymentFlow(baseUrl: string, endpoint: EndpointS
|
|
|
8
9
|
interactive?: boolean | undefined;
|
|
9
10
|
onResults?: (results: CheckResult[]) => void;
|
|
10
11
|
}): Promise<CheckResult[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Wraps a fetch so in-stream session receipts are seen as the session client reads them: the
|
|
14
|
+
* client consumes `payment-receipt` events before the caller sees the body. Event-stream
|
|
15
|
+
* responses without a `Payment-Receipt` header pass through a scanner that keeps only the
|
|
16
|
+
* current partial event, so nothing is buffered or left unread.
|
|
17
|
+
*/
|
|
18
|
+
/** @internal */
|
|
19
|
+
export declare function captureEventStream(fetch: (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>): {
|
|
20
|
+
fetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response>;
|
|
21
|
+
receipt: () => SessionReceipt | null | undefined;
|
|
22
|
+
};
|
|
11
23
|
//# sourceMappingURL=payment.d.ts.map
|
|
@@ -6,12 +6,14 @@ import * as viemChains from 'viem/chains';
|
|
|
6
6
|
import { Actions } from 'viem/tempo';
|
|
7
7
|
import { tempoModerato, tempo as tempoMainnetChain } from 'viem/tempo/chains';
|
|
8
8
|
import * as Challenge from '../../Challenge.js';
|
|
9
|
+
import * as ProofRealm from '../../client/internal/ProofRealm.js';
|
|
9
10
|
import * as Mppx from '../../client/Mppx.js';
|
|
10
11
|
import * as Constants from '../../Constants.js';
|
|
11
12
|
import * as Receipt from '../../Receipt.js';
|
|
12
13
|
import { tempo as tempoMethods } from '../../tempo/client/index.js';
|
|
13
14
|
import { chainId as tempoChainIds } from '../../tempo/internal/defaults.js';
|
|
14
15
|
import { isTempoSessionChallenge } from '../../tempo/session/client/Transports.js';
|
|
16
|
+
import { isEventStream, parseEvent, } from '../../tempo/session/precompile/Protocol.js';
|
|
15
17
|
import { resolveAccount, resolveAccountName } from '../account.js';
|
|
16
18
|
import { assertSamePaymentRequest, flattenConfigMethods, loadConfig, preparePayment, resolvePlugin, } from '../internal.js';
|
|
17
19
|
import { fetchTokenInfo, confirm, pc } from '../utils.js';
|
|
@@ -189,6 +191,7 @@ export async function validatePaymentFlow(baseUrl, endpoint, verbose, options) {
|
|
|
189
191
|
const provisioned = await provisionAndPayTestnet(tempoTestnetChallenge, verbose, options.silent);
|
|
190
192
|
if (provisioned) {
|
|
191
193
|
try {
|
|
194
|
+
const raw = captureEventStream((input, init) => fetchWithTimeout(input, init ?? {}, 30_000));
|
|
192
195
|
const mppx = Mppx.create({
|
|
193
196
|
methods: provisioned.methods,
|
|
194
197
|
polyfill: false,
|
|
@@ -199,14 +202,14 @@ export async function validatePaymentFlow(baseUrl, endpoint, verbose, options) {
|
|
|
199
202
|
results.push(check('Payment: submitted', 'ephemeral testnet wallet'));
|
|
200
203
|
return credential;
|
|
201
204
|
},
|
|
202
|
-
fetch:
|
|
205
|
+
fetch: raw.fetch,
|
|
203
206
|
});
|
|
204
207
|
const response = await mppx.fetch(url, {
|
|
205
208
|
method: endpoint.method,
|
|
206
209
|
headers: fetchHeaders,
|
|
207
210
|
body: fetchBody ?? null,
|
|
208
211
|
});
|
|
209
|
-
await validatePaymentResponse(results, response, verbose, tempoModerato);
|
|
212
|
+
await validatePaymentResponse(results, response, verbose, tempoModerato, isTempoSessionChallenge(tempoTestnetChallenge), raw.receipt);
|
|
210
213
|
}
|
|
211
214
|
catch (error) {
|
|
212
215
|
results.push(fail('Payment: create credential', error.message));
|
|
@@ -408,6 +411,7 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
|
|
|
408
411
|
}
|
|
409
412
|
if (directMethod && isTempoSessionChallenge(challenge)) {
|
|
410
413
|
let initial = true;
|
|
414
|
+
const raw = captureEventStream((input, init) => fetchWithTimeout(input, init ?? {}, 30_000));
|
|
411
415
|
const mppx = Mppx.create({
|
|
412
416
|
methods: [directMethod],
|
|
413
417
|
polyfill: false,
|
|
@@ -428,7 +432,7 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
|
|
|
428
432
|
},
|
|
429
433
|
});
|
|
430
434
|
}
|
|
431
|
-
return
|
|
435
|
+
return raw.fetch(input, init);
|
|
432
436
|
},
|
|
433
437
|
});
|
|
434
438
|
try {
|
|
@@ -437,18 +441,18 @@ async function attemptCryptoPayment(challenge, tag, ctx) {
|
|
|
437
441
|
headers: fetchHeaders,
|
|
438
442
|
body: fetchBody ?? null,
|
|
439
443
|
});
|
|
440
|
-
await validatePaymentResponse(results, response, verbose, paymentChain);
|
|
444
|
+
await validatePaymentResponse(results, response, verbose, paymentChain, true, raw.receipt);
|
|
441
445
|
}
|
|
442
446
|
catch (error) {
|
|
443
447
|
results.push(fail(tag, error.message));
|
|
444
448
|
}
|
|
445
449
|
return;
|
|
446
450
|
}
|
|
447
|
-
const credential = await createAndSend(challenge, methods, createCredentialFn, tag, results, loaded?.config.extensions, credentialContext);
|
|
451
|
+
const credential = await createAndSend(challenge, methods, createCredentialFn, tag, results, loaded?.config.extensions, credentialContext, url);
|
|
448
452
|
if (!credential)
|
|
449
453
|
return;
|
|
450
454
|
plugin?.prepareCredentialRequest?.({ challenge, credential, headers: fetchHeaders });
|
|
451
|
-
await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, paymentChain, Challenge.credentialHeader(challenge));
|
|
455
|
+
await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, paymentChain, Challenge.credentialHeader(challenge), isTempoSessionChallenge(challenge));
|
|
452
456
|
}
|
|
453
457
|
async function attemptStripePayment(challenge, tag, ctx) {
|
|
454
458
|
const { results, url, endpoint, fetchHeaders, fetchBody, verbose, loaded, options, stripeKey } = ctx;
|
|
@@ -511,7 +515,7 @@ async function attemptStripePayment(challenge, tag, ctx) {
|
|
|
511
515
|
else {
|
|
512
516
|
methods = [directMethod];
|
|
513
517
|
}
|
|
514
|
-
const credential = await createAndSend(challenge, methods, createCredentialFn, tag, results, loaded?.config.extensions, credentialContext);
|
|
518
|
+
const credential = await createAndSend(challenge, methods, createCredentialFn, tag, results, loaded?.config.extensions, credentialContext, url);
|
|
515
519
|
if (!credential)
|
|
516
520
|
return;
|
|
517
521
|
plugin?.prepareCredentialRequest?.({ challenge, credential, headers: fetchHeaders });
|
|
@@ -532,12 +536,14 @@ async function attemptStripePayment(challenge, tag, ctx) {
|
|
|
532
536
|
}
|
|
533
537
|
await sendAndValidateResponse(results, url, endpoint, credential, fetchHeaders, fetchBody, verbose, undefined, Challenge.credentialHeader(challenge));
|
|
534
538
|
}
|
|
535
|
-
async function createAndSend(challenge, methods, createCredentialFn, tag, results, extensions, initialCredentialContext) {
|
|
539
|
+
async function createAndSend(challenge, methods, createCredentialFn, tag, results, extensions, initialCredentialContext, url) {
|
|
536
540
|
const fakeResponse = new Response(null, {
|
|
537
541
|
status: 402,
|
|
538
542
|
headers: { [Constants.Headers.wwwAuthenticate]: Challenge.serialize(challenge) },
|
|
539
543
|
});
|
|
540
544
|
try {
|
|
545
|
+
// The credential goes to url, so only sign a zero-amount proof for a realm its server owns.
|
|
546
|
+
ProofRealm.assert(challenge, new URL(url), undefined);
|
|
541
547
|
const credentialContext = await preparePayment(challenge, extensions, initialCredentialContext);
|
|
542
548
|
let credential;
|
|
543
549
|
if (createCredentialFn) {
|
|
@@ -561,7 +567,7 @@ async function createAndSend(challenge, methods, createCredentialFn, tag, result
|
|
|
561
567
|
return undefined;
|
|
562
568
|
}
|
|
563
569
|
}
|
|
564
|
-
async function sendAndValidateResponse(results, url, endpoint, credential, baseHeaders, fetchBody, verbose, explorerChain, credentialHeader = Constants.Headers.authorization) {
|
|
570
|
+
async function sendAndValidateResponse(results, url, endpoint, credential, baseHeaders, fetchBody, verbose, explorerChain, credentialHeader = Constants.Headers.authorization, session = false) {
|
|
565
571
|
let paymentResponse;
|
|
566
572
|
try {
|
|
567
573
|
paymentResponse = await fetchWithTimeout(url, {
|
|
@@ -574,9 +580,9 @@ async function sendAndValidateResponse(results, url, endpoint, credential, baseH
|
|
|
574
580
|
results.push(fail('Payment: send credential', error.message));
|
|
575
581
|
return results;
|
|
576
582
|
}
|
|
577
|
-
return validatePaymentResponse(results, paymentResponse, verbose, explorerChain);
|
|
583
|
+
return validatePaymentResponse(results, paymentResponse, verbose, explorerChain, session);
|
|
578
584
|
}
|
|
579
|
-
async function validatePaymentResponse(results, paymentResponse, verbose, explorerChain) {
|
|
585
|
+
async function validatePaymentResponse(results, paymentResponse, verbose, explorerChain, session = false, streamReceipt) {
|
|
580
586
|
if (paymentResponse.status === 402) {
|
|
581
587
|
const body = await paymentResponse.text().catch(() => '');
|
|
582
588
|
let detail = 'Payment rejected';
|
|
@@ -599,8 +605,22 @@ async function validatePaymentResponse(results, paymentResponse, verbose, explor
|
|
|
599
605
|
results.push(check('Payment: successful', `HTTP ${paymentResponse.status}`));
|
|
600
606
|
}
|
|
601
607
|
// Validate receipt
|
|
608
|
+
const contentType = paymentResponse.headers.get('content-type') ?? '';
|
|
602
609
|
const receiptHeader = paymentResponse.headers.get(Constants.Headers.paymentReceipt);
|
|
603
|
-
|
|
610
|
+
const stream = !receiptHeader && session && isEventStream(paymentResponse);
|
|
611
|
+
const body = await paymentResponse.text().catch(() => '');
|
|
612
|
+
if (stream) {
|
|
613
|
+
// SSE sessions deliver the receipt as a final payment-receipt event instead of a header.
|
|
614
|
+
// The session client consumes that event, so it is captured from the server's stream as
|
|
615
|
+
// the client reads it; a stream read directly is parsed here.
|
|
616
|
+
const captured = streamReceipt?.();
|
|
617
|
+
const receipt = captured === undefined ? findReceipt(body) : (captured ?? undefined);
|
|
618
|
+
if (receipt)
|
|
619
|
+
results.push(check('Payment receipt in event stream', receipt.reference));
|
|
620
|
+
else
|
|
621
|
+
results.push(fail('Payment receipt in event stream', undefined, 'SSE session responses without a Payment-Receipt header must end with an "event: payment-receipt" carrying the session receipt.'));
|
|
622
|
+
}
|
|
623
|
+
else if (!receiptHeader) {
|
|
604
624
|
results.push(fail('Payment-Receipt header present', undefined, 'After accepting payment, include a Payment-Receipt header with a base64url-encoded JSON object containing: method, reference, status ("success"), and timestamp (ISO 8601).'));
|
|
605
625
|
}
|
|
606
626
|
else {
|
|
@@ -646,8 +666,6 @@ async function validatePaymentResponse(results, paymentResponse, verbose, explor
|
|
|
646
666
|
}
|
|
647
667
|
}
|
|
648
668
|
// Validate response body
|
|
649
|
-
const contentType = paymentResponse.headers.get('content-type') ?? '';
|
|
650
|
-
const body = await paymentResponse.text().catch(() => '');
|
|
651
669
|
if (body.length > 0) {
|
|
652
670
|
results.push(check('Response body non-empty', `${contentType.split(';')[0]}, ${formatBytes(body.length)}`));
|
|
653
671
|
}
|
|
@@ -696,4 +714,85 @@ async function validatePaymentResponse(results, paymentResponse, verbose, explor
|
|
|
696
714
|
}
|
|
697
715
|
return results;
|
|
698
716
|
}
|
|
717
|
+
/** Returns the first session receipt in an event-stream body. */
|
|
718
|
+
function findReceipt(body) {
|
|
719
|
+
for (const raw of body.replace(/\r\n|\r/g, '\n').split('\n\n')) {
|
|
720
|
+
const event = parseEvent(raw);
|
|
721
|
+
if (event?.type === 'payment-receipt')
|
|
722
|
+
return event.data;
|
|
723
|
+
}
|
|
724
|
+
return undefined;
|
|
725
|
+
}
|
|
726
|
+
/** Longest partial event kept while scanning; a receipt event is far smaller. */
|
|
727
|
+
const maxPendingEventBytes = 1024 * 1024;
|
|
728
|
+
/**
|
|
729
|
+
* Wraps a fetch so in-stream session receipts are seen as the session client reads them: the
|
|
730
|
+
* client consumes `payment-receipt` events before the caller sees the body. Event-stream
|
|
731
|
+
* responses without a `Payment-Receipt` header pass through a scanner that keeps only the
|
|
732
|
+
* current partial event, so nothing is buffered or left unread.
|
|
733
|
+
*/
|
|
734
|
+
/** @internal */
|
|
735
|
+
export function captureEventStream(fetch) {
|
|
736
|
+
// `undefined`: no stream scanned; `null`: scanned without a receipt.
|
|
737
|
+
let receipt;
|
|
738
|
+
return {
|
|
739
|
+
async fetch(input, init) {
|
|
740
|
+
const response = await fetch(input, init);
|
|
741
|
+
if (!response.body ||
|
|
742
|
+
!isEventStream(response) ||
|
|
743
|
+
response.headers.has(Constants.Headers.paymentReceipt))
|
|
744
|
+
return response;
|
|
745
|
+
// Session management requests (such as mid-stream voucher posts) share this fetch, so a
|
|
746
|
+
// later stream without a receipt must not discard one already seen.
|
|
747
|
+
receipt ??= null;
|
|
748
|
+
const decoder = new TextDecoder();
|
|
749
|
+
let pending = '';
|
|
750
|
+
// Set after an oversized event is dropped, until its end, so its remaining lines are
|
|
751
|
+
// never parsed as an event of their own.
|
|
752
|
+
let skipping = false;
|
|
753
|
+
const scan = (text, done) => {
|
|
754
|
+
pending += text;
|
|
755
|
+
// Keep a trailing CR: the next chunk may complete a CRLF.
|
|
756
|
+
const end = done || !pending.endsWith('\r') ? pending.length : pending.length - 1;
|
|
757
|
+
const events = pending
|
|
758
|
+
.slice(0, end)
|
|
759
|
+
.replace(/\r\n|\r/g, '\n')
|
|
760
|
+
.split('\n\n');
|
|
761
|
+
const rest = done ? '' : (events.pop() ?? '');
|
|
762
|
+
if (skipping && (events.length > 0 || done)) {
|
|
763
|
+
events.shift();
|
|
764
|
+
skipping = false;
|
|
765
|
+
}
|
|
766
|
+
pending = rest + pending.slice(end);
|
|
767
|
+
if (pending.length > maxPendingEventBytes) {
|
|
768
|
+
// Keep a trailing line ending: it may be the first half of the event's boundary.
|
|
769
|
+
pending = /[\r\n]*$/.exec(pending)?.[0] ?? '';
|
|
770
|
+
skipping = true;
|
|
771
|
+
}
|
|
772
|
+
for (const raw of events) {
|
|
773
|
+
const event = parseEvent(raw);
|
|
774
|
+
if (event?.type === 'payment-receipt' && !receipt)
|
|
775
|
+
receipt = event.data;
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
const body = response.body.pipeThrough(new TransformStream({
|
|
779
|
+
transform(chunk, controller) {
|
|
780
|
+
scan(decoder.decode(chunk, { stream: true }), false);
|
|
781
|
+
controller.enqueue(chunk);
|
|
782
|
+
},
|
|
783
|
+
flush() {
|
|
784
|
+
scan(decoder.decode(), true);
|
|
785
|
+
},
|
|
786
|
+
}));
|
|
787
|
+
const scanned = new Response(body, {
|
|
788
|
+
headers: response.headers,
|
|
789
|
+
status: response.status,
|
|
790
|
+
statusText: response.statusText,
|
|
791
|
+
});
|
|
792
|
+
Object.defineProperty(scanned, 'url', { value: response.url });
|
|
793
|
+
return scanned;
|
|
794
|
+
},
|
|
795
|
+
receipt: () => receipt,
|
|
796
|
+
};
|
|
797
|
+
}
|
|
699
798
|
//# sourceMappingURL=payment.js.map
|
package/dist/client/Mppx.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type * as Attestation from '../attestation/Types.js';
|
|
2
1
|
import type * as Challenge from '../Challenge.js';
|
|
2
|
+
import type * as Identity from '../identity/Types.js';
|
|
3
3
|
import * as AcceptPayment from '../internal/AcceptPayment.js';
|
|
4
4
|
import type * as Method from '../Method.js';
|
|
5
5
|
import type * as z from '../zod.js';
|
|
@@ -163,8 +163,11 @@ export declare namespace createCredential {
|
|
|
163
163
|
export declare function restore(): void;
|
|
164
164
|
export declare namespace create {
|
|
165
165
|
type Config<methods extends Methods = Methods, transport extends Transport.AnyTransport = Transport.Transport> = {
|
|
166
|
-
/**
|
|
167
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Identity provers applied to every in-scope HTTP attempt, in order.
|
|
168
|
+
* List request signers last so they cover headers added by earlier provers.
|
|
169
|
+
*/
|
|
170
|
+
identity?: transport extends Transport.Transport<RequestInit, Response> ? readonly Identity.Prover[] | undefined : never;
|
|
168
171
|
/** Controls when `Accept-Payment` is injected. */
|
|
169
172
|
acceptPaymentPolicy?: Fetch.from.Config['acceptPaymentPolicy'] | undefined;
|
|
170
173
|
/** Custom fetch function to wrap. Defaults to `globalThis.fetch`. */
|
|
@@ -185,6 +188,13 @@ export declare namespace create {
|
|
|
185
188
|
polyfill?: boolean | undefined;
|
|
186
189
|
/** Transport to use (defaults to HTTP). */
|
|
187
190
|
transport?: transport | undefined;
|
|
191
|
+
/**
|
|
192
|
+
* Server origins allowed to request zero-amount Tempo proofs for a realm other than their
|
|
193
|
+
* hostname, mapped to that realm, such as `{ 'https://api.example.com': 'MPP Payment' }`.
|
|
194
|
+
* With `Transport.mcp()`, which has no origin, only realms listed here are trusted,
|
|
195
|
+
* whatever their origin.
|
|
196
|
+
*/
|
|
197
|
+
trustedTempoProofRealms?: Fetch.from.Config['trustedTempoProofRealms'] | undefined;
|
|
188
198
|
};
|
|
189
199
|
}
|
|
190
200
|
/**
|
package/dist/client/Mppx.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import * as AttestationClient from '../attestation/Client.js';
|
|
2
1
|
import * as Constants from '../Constants.js';
|
|
3
2
|
import * as Expires from '../Expires.js';
|
|
3
|
+
import * as IdentityClient from '../identity/Client.js';
|
|
4
4
|
import * as AcceptPayment from '../internal/AcceptPayment.js';
|
|
5
5
|
import * as Fetch from './internal/Fetch.js';
|
|
6
6
|
import * as MethodChallenge from './internal/MethodChallenge.js';
|
|
7
|
+
import * as ProofRealm from './internal/ProofRealm.js';
|
|
7
8
|
import * as Transport from './Transport.js';
|
|
8
9
|
const preparedPaymentMethods = new WeakMap();
|
|
9
10
|
/**
|
|
@@ -28,16 +29,16 @@ const preparedPaymentMethods = new WeakMap();
|
|
|
28
29
|
* ```
|
|
29
30
|
*/
|
|
30
31
|
export function create(config) {
|
|
31
|
-
const {
|
|
32
|
+
const { identity, maxPaymentRetries, onChallenge, orderChallenges, polyfill = true, acceptPaymentPolicy = polyfill && typeof globalThis.location !== 'undefined'
|
|
32
33
|
? 'same-origin'
|
|
33
|
-
: 'always', transport = Transport.http(), } = config;
|
|
34
|
+
: 'always', transport = Transport.http(), trustedTempoProofRealms, } = config;
|
|
35
|
+
if ('attestation' in config)
|
|
36
|
+
throw new TypeError('Mppx client `attestation` was replaced by `identity: [prover]`.');
|
|
34
37
|
const rawFetch = Fetch.unwrapFetch(config.fetch ?? globalThis.fetch);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
? AttestationClient.wrapFetch(rawFetch, attestationSigner)
|
|
40
|
-
: rawFetch;
|
|
38
|
+
// Rewrapping an mppx fetch keeps its identity provers unless new ones are configured.
|
|
39
|
+
const identityClient = identity
|
|
40
|
+
? IdentityClient.from(identity)
|
|
41
|
+
: config.fetch && Fetch.identityOf(config.fetch);
|
|
41
42
|
const methods = config.methods.flat();
|
|
42
43
|
const acceptPayment = AcceptPayment.resolve(methods, config.paymentPreferences);
|
|
43
44
|
const events = Fetch.createEventDispatcher();
|
|
@@ -45,13 +46,15 @@ export function create(config) {
|
|
|
45
46
|
const config_fetch = {
|
|
46
47
|
acceptPayment,
|
|
47
48
|
acceptPaymentPolicy,
|
|
48
|
-
...(
|
|
49
|
+
...(config.fetch && { fetch: rawFetch }),
|
|
50
|
+
...(identityClient && { identity: identityClient }),
|
|
49
51
|
eventDispatcher: events,
|
|
50
52
|
...(maxPaymentRetries !== undefined && { maxPaymentRetries }),
|
|
51
53
|
...(resolvedOnChallenge && { onChallenge: resolvedOnChallenge }),
|
|
52
54
|
...(orderChallenges && { orderChallenges }),
|
|
53
55
|
methods,
|
|
54
56
|
transport,
|
|
57
|
+
...(trustedTempoProofRealms && { trustedTempoProofRealms }),
|
|
55
58
|
};
|
|
56
59
|
const fetch = Fetch.from(config_fetch);
|
|
57
60
|
if (polyfill)
|
|
@@ -88,6 +91,13 @@ export function create(config) {
|
|
|
88
91
|
mi = selected.method;
|
|
89
92
|
if (challenge.expires)
|
|
90
93
|
Expires.assert(challenge.expires, challenge.id);
|
|
94
|
+
const responseUrl = resolveResponseUrl(response);
|
|
95
|
+
if (responseUrl)
|
|
96
|
+
ProofRealm.assert(challenge, responseUrl, trustedTempoProofRealms);
|
|
97
|
+
// The MCP transport has no origin that could own the realm. Custom transports, such as the
|
|
98
|
+
// CLI's, check the realm against their own target.
|
|
99
|
+
else if (transport.name === 'mcp')
|
|
100
|
+
ProofRealm.assertWithoutOrigin(challenge, trustedTempoProofRealms && Object.values(trustedTempoProofRealms));
|
|
91
101
|
const selectedChallenge = challenge;
|
|
92
102
|
const selectedMethod = mi;
|
|
93
103
|
const createPreparedCredential = memoizeCreateCredential(async (context) => {
|
|
@@ -156,7 +166,7 @@ export function create(config) {
|
|
|
156
166
|
init,
|
|
157
167
|
input,
|
|
158
168
|
maxRedirects,
|
|
159
|
-
|
|
169
|
+
identity: identityClient,
|
|
160
170
|
});
|
|
161
171
|
const requestInit = requestToInit(preparedHttp.replayRequest, preparedHttp.body);
|
|
162
172
|
if (!(await transport.isPaymentRequired(preparedHttp.response, requestInit))) {
|
|
@@ -173,13 +183,17 @@ export function create(config) {
|
|
|
173
183
|
...paymentOptions,
|
|
174
184
|
request: requestInit,
|
|
175
185
|
}));
|
|
186
|
+
ProofRealm.assert(payment.challenge, new URL(preparedHttp.replayRequest.url), trustedTempoProofRealms);
|
|
176
187
|
const paymentMethod = preparedPaymentMethods.get(payment) ?? payment.method;
|
|
177
188
|
const createRequestCredential = memoizeCreateCredential(async (context) => {
|
|
189
|
+
await identityClient?.challenge(preparedHttp.response, { challenge: payment.challenge }, preparedHttp.replayRequest.url);
|
|
178
190
|
if (MethodChallenge.has(paymentMethod))
|
|
179
191
|
await MethodChallenge.handle(paymentMethod, {
|
|
180
192
|
challenge: payment.challenge,
|
|
181
193
|
context,
|
|
182
|
-
fetch:
|
|
194
|
+
fetch: identityClient
|
|
195
|
+
? IdentityClient.wrapFetch(rawFetch, identityClient, { challenge: payment.challenge })
|
|
196
|
+
: rawFetch,
|
|
183
197
|
input: preparedHttp.replayRequest,
|
|
184
198
|
signal: preparedHttp.replayRequest.signal,
|
|
185
199
|
});
|
|
@@ -191,10 +205,16 @@ export function create(config) {
|
|
|
191
205
|
async pay(context) {
|
|
192
206
|
const credential = await createRequestCredential(context);
|
|
193
207
|
const paidInit = payment.setCredential(requestToInit(preparedHttp.replayRequest, preparedHttp.body), credential);
|
|
194
|
-
|
|
208
|
+
const paidRequest = new Request(preparedHttp.replayRequest.url, {
|
|
195
209
|
...paidInit,
|
|
196
210
|
redirect: 'manual',
|
|
197
211
|
});
|
|
212
|
+
if (!identityClient)
|
|
213
|
+
return rawFetch(paidRequest);
|
|
214
|
+
const { response } = await identityClient.send(rawFetch, paidRequest, {
|
|
215
|
+
challenge: payment.challenge,
|
|
216
|
+
});
|
|
217
|
+
return response;
|
|
198
218
|
},
|
|
199
219
|
});
|
|
200
220
|
return Object.freeze({
|
|
@@ -239,12 +259,6 @@ export function create(config) {
|
|
|
239
259
|
export function restore() {
|
|
240
260
|
Fetch.restore();
|
|
241
261
|
}
|
|
242
|
-
function createAttestationSigner(signers) {
|
|
243
|
-
const values = Object.values(signers);
|
|
244
|
-
if (values.length === 0)
|
|
245
|
-
throw new TypeError('Mppx client attestation must configure at least one signer.');
|
|
246
|
-
return AttestationClient.composeSigners(...values);
|
|
247
|
-
}
|
|
248
262
|
const redirectStatuses = new Set([301, 302, 303, 307, 308]);
|
|
249
263
|
const bodyHeaders = [
|
|
250
264
|
'content-encoding',
|
|
@@ -265,7 +279,7 @@ const crossOriginHeaders = [
|
|
|
265
279
|
'x-payment',
|
|
266
280
|
];
|
|
267
281
|
async function prepareHttpRequest(parameters) {
|
|
268
|
-
const { acceptPayment, acceptPaymentPolicy, fetch, init, input, maxRedirects
|
|
282
|
+
const { acceptPayment, acceptPaymentPolicy, fetch, identity, init, input, maxRedirects } = parameters;
|
|
269
283
|
if (!Number.isInteger(maxRedirects) || maxRedirects < 0)
|
|
270
284
|
throw new TypeError('maxRedirects must be a non-negative integer.');
|
|
271
285
|
let request = new Request(input, { ...init, redirect: 'manual' });
|
|
@@ -274,8 +288,13 @@ async function prepareHttpRequest(parameters) {
|
|
|
274
288
|
const redirects = [];
|
|
275
289
|
for (;;) {
|
|
276
290
|
request = withAcceptPayment(request, acceptPayment, explicitAcceptPayment, acceptPaymentPolicy);
|
|
277
|
-
const sentRequest
|
|
278
|
-
|
|
291
|
+
const { request: sentRequest, response } = await (async () => {
|
|
292
|
+
// `request` stays unread for replay; the reported request is the one actually sent.
|
|
293
|
+
if (identity)
|
|
294
|
+
return identity.send(fetch, request.clone());
|
|
295
|
+
const sent = request.clone();
|
|
296
|
+
return { request: sent, response: await fetch(sent) };
|
|
297
|
+
})();
|
|
279
298
|
if (response.type === 'opaqueredirect' || response.status === 0)
|
|
280
299
|
throw new Error('prepareRequest requires a runtime that exposes manual redirect responses.');
|
|
281
300
|
if (!redirectStatuses.has(response.status))
|
|
@@ -459,4 +478,10 @@ async function createCredentialForMethod(challenge, mi, context) {
|
|
|
459
478
|
const parsedContext = mi.context && context !== undefined ? mi.context.parse(context) : undefined;
|
|
460
479
|
return mi.createCredential(parsedContext !== undefined ? { challenge, context: parsedContext } : { challenge });
|
|
461
480
|
}
|
|
481
|
+
/** Returns the URL of the HTTP server that sent `response`, or `undefined` when it has none. */
|
|
482
|
+
function resolveResponseUrl(response) {
|
|
483
|
+
if (typeof Response !== 'undefined' && response instanceof Response && response.url)
|
|
484
|
+
return new URL(response.url);
|
|
485
|
+
return undefined;
|
|
486
|
+
}
|
|
462
487
|
//# sourceMappingURL=Mppx.js.map
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import * as Challenge from '../../Challenge.js';
|
|
2
|
+
import * as IdentityClient from '../../identity/Client.js';
|
|
2
3
|
import * as AcceptPayment from '../../internal/AcceptPayment.js';
|
|
3
4
|
import type { MaybePromise } from '../../internal/types.js';
|
|
4
5
|
import type * as Method from '../../Method.js';
|
|
5
6
|
import type * as z from '../../zod.js';
|
|
6
7
|
import * as Transport from '../Transport.js';
|
|
8
|
+
import * as ProofRealm from './ProofRealm.js';
|
|
7
9
|
export type ClientEventMap<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[], response = Response> = {
|
|
8
10
|
'challenge.received': ChallengeReceivedPayload<methods, response>;
|
|
9
11
|
'credential.created': CredentialCreatedPayload<methods, response>;
|
|
@@ -95,6 +97,8 @@ export declare namespace from {
|
|
|
95
97
|
} | undefined;
|
|
96
98
|
/** Custom fetch function to wrap. Defaults to `globalThis.fetch`. */
|
|
97
99
|
fetch?: typeof globalThis.fetch;
|
|
100
|
+
/** Identity provers applied to every in-scope HTTP attempt. */
|
|
101
|
+
identity?: IdentityClient.Client | undefined;
|
|
98
102
|
/** Advanced shared event dispatcher. `challenge.received` handlers run before `onChallenge`; the first non-empty credential returned by a handler skips `onChallenge`. */
|
|
99
103
|
eventDispatcher?: ClientEventDispatcher<methods, any> | undefined;
|
|
100
104
|
/** Maximum number of payment challenge retries after the initial response. @default 3 */
|
|
@@ -109,6 +113,11 @@ export declare namespace from {
|
|
|
109
113
|
orderChallenges?: AcceptPayment.OrderChallenges<methods> | undefined;
|
|
110
114
|
/** Transport to use for challenge extraction and credential attachment. */
|
|
111
115
|
transport?: Transport.AnyTransport | undefined;
|
|
116
|
+
/**
|
|
117
|
+
* Server origins allowed to request zero-amount Tempo proofs for a realm other than their
|
|
118
|
+
* hostname, mapped to that realm, such as `{ 'https://api.example.com': 'MPP Payment' }`.
|
|
119
|
+
*/
|
|
120
|
+
trustedTempoProofRealms?: ProofRealm.TrustedRealms | undefined;
|
|
112
121
|
};
|
|
113
122
|
type Fetch<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = (input: RequestInfo | URL, init?: RequestInit<methods>) => Promise<Response>;
|
|
114
123
|
type RequestInit<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[]> = globalThis.RequestInit & {
|
|
@@ -168,6 +177,8 @@ export declare function normalizeHeaders(headers: unknown): Record<string, strin
|
|
|
168
177
|
*/
|
|
169
178
|
export declare function createEventDispatcher<methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[], response = Response>(): ClientEventDispatcher<methods, response>;
|
|
170
179
|
/** @internal */
|
|
180
|
+
/** Returns the identity runner of an mppx fetch wrapper, if it has one. @internal */
|
|
181
|
+
export declare function identityOf(fetch: typeof globalThis.fetch): IdentityClient.Client | undefined;
|
|
171
182
|
/** @internal */
|
|
172
183
|
export declare function unwrapFetch(fetch: typeof globalThis.fetch): typeof globalThis.fetch;
|
|
173
184
|
/** @internal */
|
|
@@ -2,14 +2,18 @@ import * as Challenge from '../../Challenge.js';
|
|
|
2
2
|
import * as Constants from '../../Constants.js';
|
|
3
3
|
import { PaymentOutcomeUnknownError } from '../../Errors.js';
|
|
4
4
|
import * as Expires from '../../Expires.js';
|
|
5
|
+
import * as IdentityClient from '../../identity/Client.js';
|
|
5
6
|
import * as AcceptPayment from '../../internal/AcceptPayment.js';
|
|
6
7
|
import * as Transport from '../Transport.js';
|
|
7
8
|
import * as MethodChallenge from './MethodChallenge.js';
|
|
8
9
|
import * as MethodResponse from './MethodResponse.js';
|
|
10
|
+
import * as ProofRealm from './ProofRealm.js';
|
|
9
11
|
// We tag wrappers with a global symbol so we can recognize wrappers created by mppx,
|
|
10
12
|
// even across multiple module instances/bundles. This lets restore() avoid clobbering
|
|
11
13
|
// an unrelated fetch installed by user code or another library.
|
|
12
14
|
const MPPX_FETCH_WRAPPER = Symbol.for('mppx.fetch.wrapper');
|
|
15
|
+
/** Identity runner of an mppx fetch wrapper, inherited when that wrapper is wrapped again. */
|
|
16
|
+
const MPPX_FETCH_IDENTITY = Symbol.for('mppx.fetch.identity');
|
|
13
17
|
const defaultMaxPaymentRetries = 3;
|
|
14
18
|
const maxErrorBodyBytes = 64 * 1024;
|
|
15
19
|
const errorBodyTimeoutMs = 2_000;
|
|
@@ -101,19 +105,25 @@ async function settleAttempt(prepared, outcome) {
|
|
|
101
105
|
*
|
|
102
106
|
*/
|
|
103
107
|
export function from(config) {
|
|
104
|
-
const { acceptPayment, acceptPaymentPolicy = 'always', fetch = globalThis.fetch, maxPaymentRetries = defaultMaxPaymentRetries, methods, onChallenge, orderChallenges, transport = Transport.http(), } = config;
|
|
108
|
+
const { acceptPayment, acceptPaymentPolicy = 'always', fetch = globalThis.fetch, maxPaymentRetries = defaultMaxPaymentRetries, methods, onChallenge, orderChallenges, transport = Transport.http(), trustedTempoProofRealms, } = config;
|
|
109
|
+
// Rewrapping an mppx fetch keeps its identity provers unless new ones are configured.
|
|
110
|
+
// Only an explicitly passed mppx fetch hands down its provers; the global fetch never does.
|
|
111
|
+
const identity = config.identity ?? (config.fetch && identityOf(config.fetch));
|
|
105
112
|
const events = config.eventDispatcher ?? createEventDispatcher();
|
|
106
113
|
const resolvedAcceptPayment = acceptPayment ?? AcceptPayment.resolve(methods);
|
|
107
114
|
// Always operate on the true underlying fetch to avoid wrapper-on-wrapper stacking,
|
|
108
115
|
// which can duplicate retries and make restore semantics fragile.
|
|
109
116
|
const baseFetch = unwrapFetch(fetch);
|
|
117
|
+
const send = async (input, init, context) => identity
|
|
118
|
+
? (await identity.send(baseFetch, new Request(input, init), context)).response
|
|
119
|
+
: baseFetch(input, init);
|
|
110
120
|
const request = async (input, init, canRefetch) => {
|
|
111
121
|
const callerHeaders = getCallerHeaders(input, init?.headers);
|
|
112
122
|
const hasExplicitAcceptPayment = callerHeaders.has(Constants.Headers.acceptPayment);
|
|
113
123
|
const paymentPreferences = resolvePaymentPreferences(callerHeaders, resolvedAcceptPayment);
|
|
114
124
|
const capturedBody = captureRequestBody(input, init, callerHeaders);
|
|
115
125
|
const initialRequest = prepareInitialRequest(input, init, callerHeaders, paymentPreferences.header, hasExplicitAcceptPayment, acceptPaymentPolicy);
|
|
116
|
-
let response = await
|
|
126
|
+
let response = await send(cloneRequestInput(initialRequest.input), initialRequest.init);
|
|
117
127
|
const transportRequest = withCapturedBody(initialRequest.init, await capturedBody);
|
|
118
128
|
if (!(await transport.isPaymentRequired(response, transportRequest)))
|
|
119
129
|
return response;
|
|
@@ -150,7 +160,9 @@ export function from(config) {
|
|
|
150
160
|
mi = selected.method;
|
|
151
161
|
if (challenge.expires)
|
|
152
162
|
Expires.assert(challenge.expires, challenge.id);
|
|
163
|
+
await identity?.challenge(response, { challenge: selectedChallenge }, resolveRequestUrl(initialRequest.input).href);
|
|
153
164
|
const paymentInput = resolvePaymentRetryInput(response, initialRequest.input, initialRequest.input);
|
|
165
|
+
ProofRealm.assert(selectedChallenge, () => resolveRequestUrl(paymentInput), trustedTempoProofRealms);
|
|
154
166
|
const previousCredential = preparedCredential;
|
|
155
167
|
const key = Challenge.serialize(selectedChallenge);
|
|
156
168
|
const sameAttempt = previousCredential?.method === selected.method && previousCredential?.key === key;
|
|
@@ -179,7 +191,12 @@ export function from(config) {
|
|
|
179
191
|
prepare: () => MethodChallenge.handle(selected.method, {
|
|
180
192
|
challenge: selectedChallenge,
|
|
181
193
|
context: credentialContext,
|
|
182
|
-
|
|
194
|
+
// Management requests (e.g. session top-ups) carry the same identity proofs.
|
|
195
|
+
fetch: identity
|
|
196
|
+
? IdentityClient.wrapFetch(baseFetch, identity, {
|
|
197
|
+
challenge: selectedChallenge,
|
|
198
|
+
})
|
|
199
|
+
: baseFetch,
|
|
183
200
|
input: paymentInput,
|
|
184
201
|
signal: resolveRequestSignal(input, init),
|
|
185
202
|
}),
|
|
@@ -232,14 +249,14 @@ export function from(config) {
|
|
|
232
249
|
transactionHash: MethodResponse.getTransactionHash(selected.method, credential),
|
|
233
250
|
});
|
|
234
251
|
try {
|
|
235
|
-
response = await
|
|
252
|
+
response = await send(paymentInput, {
|
|
236
253
|
...transport.setCredential({
|
|
237
254
|
...fetchInit,
|
|
238
255
|
headers: initialRequest.headers,
|
|
239
256
|
}, credential, { challenge: selectedChallenge }),
|
|
240
257
|
// Runtimes do not strip custom payment headers on cross-origin redirects.
|
|
241
258
|
redirect: 'manual',
|
|
242
|
-
});
|
|
259
|
+
}, { challenge: selectedChallenge });
|
|
243
260
|
}
|
|
244
261
|
catch (cause) {
|
|
245
262
|
outcomeUnknown = unknownOutcome(cause);
|
|
@@ -347,6 +364,8 @@ export function from(config) {
|
|
|
347
364
|
};
|
|
348
365
|
const wrappedFetch = (input, init) => request(input, init, true);
|
|
349
366
|
wrappedFetch[MPPX_FETCH_WRAPPER] = baseFetch;
|
|
367
|
+
if (identity)
|
|
368
|
+
wrappedFetch[MPPX_FETCH_IDENTITY] = identity;
|
|
350
369
|
return wrappedFetch;
|
|
351
370
|
}
|
|
352
371
|
/**
|
|
@@ -380,7 +399,8 @@ export function polyfill(config) {
|
|
|
380
399
|
globalThis.fetch = from({
|
|
381
400
|
...config,
|
|
382
401
|
acceptPaymentPolicy: config.acceptPaymentPolicy ?? (isBrowser() ? 'same-origin' : 'always'),
|
|
383
|
-
|
|
402
|
+
// Unwrapped so a previous polyfill's identity provers are not inherited.
|
|
403
|
+
fetch: unwrapFetch(globalThis.fetch),
|
|
384
404
|
});
|
|
385
405
|
}
|
|
386
406
|
/**
|
|
@@ -586,8 +606,22 @@ function snapshotMethod(method) {
|
|
|
586
606
|
return freezeSnapshot(Object.assign({}, method));
|
|
587
607
|
}
|
|
588
608
|
function snapshotResponse(response) {
|
|
589
|
-
if (response instanceof Response)
|
|
590
|
-
|
|
609
|
+
if (response instanceof Response) {
|
|
610
|
+
try {
|
|
611
|
+
return response.clone();
|
|
612
|
+
}
|
|
613
|
+
catch {
|
|
614
|
+
// Consumed or locked bodies cannot be cloned; retain isolated status, headers and URL.
|
|
615
|
+
const snapshot = new Response(null, {
|
|
616
|
+
headers: response.headers,
|
|
617
|
+
status: response.status,
|
|
618
|
+
statusText: response.statusText,
|
|
619
|
+
});
|
|
620
|
+
for (const key of ['redirected', 'type', 'url'])
|
|
621
|
+
Object.defineProperty(snapshot, key, { value: response[key] });
|
|
622
|
+
return snapshot;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
591
625
|
return snapshotValue(response);
|
|
592
626
|
}
|
|
593
627
|
function snapshotValue(value) {
|
|
@@ -667,6 +701,10 @@ function getCallerHeaders(input, headers) {
|
|
|
667
701
|
return new Headers(input instanceof Request ? input.headers : undefined);
|
|
668
702
|
}
|
|
669
703
|
/** @internal */
|
|
704
|
+
/** Returns the identity runner of an mppx fetch wrapper, if it has one. @internal */
|
|
705
|
+
export function identityOf(fetch) {
|
|
706
|
+
return fetch[MPPX_FETCH_IDENTITY];
|
|
707
|
+
}
|
|
670
708
|
/** @internal */
|
|
671
709
|
export function unwrapFetch(fetch) {
|
|
672
710
|
let current = fetch;
|