mppx 0.17.0 → 0.17.3
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 +45 -0
- package/dist/BodyDigest.js +13 -1
- package/dist/Method.d.ts +5 -0
- package/dist/Store.d.ts +225 -0
- package/dist/Store.js +358 -0
- package/dist/cli/cli.js +136 -135
- package/dist/cli/plugins/index.js +12 -11
- package/dist/cli/sessions/Manager.js +12 -1
- package/dist/cli/sessions/store.js +7 -1
- package/dist/cli/utils.d.ts +2 -0
- package/dist/cli/utils.js +6 -0
- package/dist/cli/validate/helpers.d.ts +1 -1
- package/dist/cli/validate/helpers.js +14 -2
- package/dist/client/Mppx.d.ts +1 -1
- package/dist/client/Mppx.js +18 -6
- package/dist/client/internal/Fetch.d.ts +3 -1
- package/dist/client/internal/Fetch.js +68 -3
- package/dist/client/internal/MethodResponse.d.ts +8 -0
- package/dist/client/internal/MethodResponse.js +9 -0
- package/dist/client/internal/ProofRealm.d.ts +3 -3
- package/dist/client/internal/ProofRealm.js +4 -4
- package/dist/client/internal/protocols/Mcp.d.ts +4 -0
- package/dist/client/internal/protocols/Mcp.js +27 -14
- package/dist/identity/Client.js +7 -1
- package/dist/internal/AcceptPayment.d.ts +12 -2
- package/dist/internal/AcceptPayment.js +12 -3
- package/dist/internal/sse.d.ts +21 -0
- package/dist/internal/sse.js +65 -0
- package/dist/internal/version.d.ts +2 -2
- package/dist/internal/version.js +1 -1
- package/dist/mcp/client/McpClient.d.ts +2 -2
- package/dist/mcp/client/McpClient.js +6 -3
- package/dist/proxy/Proxy.d.ts +17 -0
- package/dist/proxy/Proxy.js +116 -3
- package/dist/proxy/internal/Replay.d.ts +105 -0
- package/dist/proxy/internal/Replay.js +257 -0
- package/dist/server/Mppx.d.ts +2 -0
- package/dist/server/Mppx.js +7 -5
- package/dist/server/Transport.js +1 -1
- package/dist/server/internal/html/config.d.ts +7 -1
- package/dist/server/internal/html/config.js +4 -0
- package/dist/stripe/internal/types.d.ts +82 -6
- package/dist/stripe/server/Charge.js +1 -2
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/tempo/client/Charge.d.ts +9 -0
- package/dist/tempo/client/Charge.js +13 -9
- package/dist/tempo/client/Methods.d.ts +2 -0
- package/dist/tempo/internal/fee-payer.d.ts +85 -10
- package/dist/tempo/internal/fee-payer.js +183 -19
- package/dist/tempo/legacy/session/Chain.js +2 -0
- package/dist/tempo/server/Charge.js +6 -3
- package/dist/tempo/server/Subscription.js +1 -0
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/transport.d.ts +6 -0
- package/dist/tempo/server/internal/transport.js +21 -21
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.d.ts +2 -0
- package/dist/tempo/session/client/ChannelStore.js +5 -0
- package/dist/tempo/session/client/CredentialState.js +2 -0
- package/dist/tempo/session/client/Session.d.ts +37 -2
- package/dist/tempo/session/client/Session.js +111 -7
- package/dist/tempo/session/client/SessionManager.d.ts +2 -1
- package/dist/tempo/session/client/SessionManager.js +60 -9
- package/dist/tempo/session/client/Transports.d.ts +4 -0
- package/dist/tempo/session/client/Transports.js +35 -3
- package/dist/tempo/session/precompile/Chain.js +1 -0
- package/dist/tempo/session/precompile/Voucher.js +20 -7
- package/dist/tempo/session/server/ChannelStore.d.ts +9 -0
- package/dist/tempo/session/server/ChannelStore.js +8 -1
- package/dist/tempo/session/server/MeteredStream.d.ts +4 -0
- package/dist/tempo/session/server/MeteredStream.js +6 -1
- 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 +4 -2
- package/dist/tempo/session/server/Settlement.js +2 -2
- package/dist/tempo/session/server/Sse.d.ts +59 -0
- package/dist/tempo/session/server/Sse.js +175 -10
- package/dist/tempo/session/server/Ws.js +29 -12
- package/dist/zod.d.ts +2 -1
- package/dist/zod.js +1 -1
- package/package.json +1 -3
- package/dist/stripe/server/internal/html/types.d.ts +0 -2
- package/dist/stripe/server/internal/html/types.js +0 -2
|
@@ -4,11 +4,13 @@ import * as ChannelStore from '../../session/server/ChannelStore.js';
|
|
|
4
4
|
import type { SettleChargedSessionChannel } from '../../session/server/Settlement.js';
|
|
5
5
|
import * as Sse_core from '../../session/server/Sse.js';
|
|
6
6
|
declare const prepaidSessionTick: unique symbol;
|
|
7
|
+
declare const prepaidRefundKey: unique symbol;
|
|
7
8
|
/** SSE transport with Tempo session controller. */
|
|
8
9
|
export type Sse = Transport.Sse<Sse_core.SessionController>;
|
|
9
10
|
/** Receipt marker used to avoid double-charging a request already charged during verification. */
|
|
10
11
|
export type PrepaidSessionReceipt = SessionReceipt & {
|
|
11
12
|
[prepaidSessionTick]?: true | undefined;
|
|
13
|
+
[prepaidRefundKey]?: string | undefined;
|
|
12
14
|
};
|
|
13
15
|
/** Marks a session receipt as already charged for the current request. */
|
|
14
16
|
export declare function markPrepaidSessionTick(receipt: SessionReceipt): SessionReceipt;
|
|
@@ -42,6 +44,10 @@ export declare namespace sse {
|
|
|
42
44
|
* @default false
|
|
43
45
|
*/
|
|
44
46
|
poll?: boolean | undefined;
|
|
47
|
+
/** Opt in to SDK-owned prepaid credits; leave false with an existing merchant refund handler. */
|
|
48
|
+
autoCreditPrepaid?: boolean | undefined;
|
|
49
|
+
/** Merchant reconciliation hook for prepaid items that fail before delivery; see `Sse.serve.Options.onRefundRequired`. */
|
|
50
|
+
onRefundRequired?: Sse_core.serve.Options['onRefundRequired'];
|
|
45
51
|
/** Polling interval (in milliseconds). @default 10 */
|
|
46
52
|
pollingInterval?: number | undefined;
|
|
47
53
|
};
|
|
@@ -14,8 +14,15 @@ import * as ChannelStore from '../../session/server/ChannelStore.js';
|
|
|
14
14
|
import * as Sse_core from '../../session/server/Sse.js';
|
|
15
15
|
import { captureRequestBodyProbe, shouldChargePlainResponse } from './request-body.js';
|
|
16
16
|
const prepaidSessionTick = Symbol('mppx.prepaidSessionTick');
|
|
17
|
+
const consumedPrepaidReceipts = new WeakSet();
|
|
18
|
+
const prepaidRefundKey = Symbol('mppx.prepaidRefundKey');
|
|
17
19
|
/** Marks a session receipt as already charged for the current request. */
|
|
18
20
|
export function markPrepaidSessionTick(receipt) {
|
|
21
|
+
if (hasPrepaidSessionTick(receipt))
|
|
22
|
+
return receipt;
|
|
23
|
+
Object.defineProperty(receipt, prepaidRefundKey, {
|
|
24
|
+
value: `stream:${globalThis.crypto.randomUUID()}`,
|
|
25
|
+
});
|
|
19
26
|
Object.defineProperty(receipt, prepaidSessionTick, {
|
|
20
27
|
configurable: false,
|
|
21
28
|
enumerable: false,
|
|
@@ -65,6 +72,11 @@ export function sse(options) {
|
|
|
65
72
|
return base.respondChallenge(options);
|
|
66
73
|
},
|
|
67
74
|
async respondReceipt({ credential, envelope, receipt, response, challengeId, input }) {
|
|
75
|
+
if (hasPrepaidSessionTick(receipt)) {
|
|
76
|
+
if (consumedPrepaidReceipts.has(receipt))
|
|
77
|
+
throw new Error('Prepaid receipt already used; verify a new payment before retrying delivery');
|
|
78
|
+
consumedPrepaidReceipts.add(receipt);
|
|
79
|
+
}
|
|
68
80
|
const verifiedCredential = envelope?.credential ?? credential;
|
|
69
81
|
const verifiedChallengeId = envelope?.challenge.id ?? challengeId;
|
|
70
82
|
const verifiedRequest = envelope?.request ?? verifiedCredential.challenge.request;
|
|
@@ -86,20 +98,25 @@ export function sse(options) {
|
|
|
86
98
|
// Pass async generator functions directly so Sse.serve gives them
|
|
87
99
|
// a SessionController for manual charge(). Pass raw AsyncIterables
|
|
88
100
|
// as-is so Sse.serve auto-charges per yielded value.
|
|
89
|
-
const generate =
|
|
90
|
-
const
|
|
101
|
+
const generate = resolved;
|
|
102
|
+
const streamOptions = {
|
|
91
103
|
store,
|
|
92
104
|
channelId,
|
|
93
105
|
challengeId: verifiedChallengeId,
|
|
94
106
|
tickCost,
|
|
95
107
|
pollIntervalMs: pollingInterval,
|
|
96
108
|
generate,
|
|
109
|
+
chargeOnce: unitType === 'request',
|
|
97
110
|
beforeCharge: () => options.beforeCharge?.(channelId),
|
|
98
111
|
onChargeCommitted: options.settleCharged,
|
|
99
112
|
prepaidUnits: hasPrepaidSessionTick(receipt) ? 1 : 0,
|
|
113
|
+
refundIdempotencyKey: receipt[prepaidRefundKey],
|
|
114
|
+
onRefundRequired: options.onRefundRequired,
|
|
115
|
+
autoCreditPrepaid: options.autoCreditPrepaid,
|
|
100
116
|
signal: input.signal,
|
|
101
|
-
}
|
|
102
|
-
|
|
117
|
+
};
|
|
118
|
+
await Sse_core.preparePrepaidRefund(streamOptions);
|
|
119
|
+
return Sse_core.toResponse(Sse_core.serve(streamOptions));
|
|
103
120
|
}
|
|
104
121
|
const baseResponse = base.respondReceipt({
|
|
105
122
|
credential: verifiedCredential,
|
|
@@ -246,23 +263,6 @@ function isAsyncGeneratorFunction(value) {
|
|
|
246
263
|
function isAsyncIterable(value) {
|
|
247
264
|
return value !== null && typeof value === 'object' && Symbol.asyncIterator in value;
|
|
248
265
|
}
|
|
249
|
-
function resolveMeteredGenerate(value, unitType) {
|
|
250
|
-
if (isAsyncGeneratorFunction(value))
|
|
251
|
-
return value;
|
|
252
|
-
if (unitType !== 'request')
|
|
253
|
-
return value;
|
|
254
|
-
const iterable = value;
|
|
255
|
-
return async function* chargeOnce(stream) {
|
|
256
|
-
let charged = false;
|
|
257
|
-
for await (const chunk of iterable) {
|
|
258
|
-
if (!charged) {
|
|
259
|
-
await stream.charge();
|
|
260
|
-
charged = true;
|
|
261
|
-
}
|
|
262
|
-
yield chunk;
|
|
263
|
-
}
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
266
|
function isNullBodyStatus(status) {
|
|
267
267
|
return [101, 204, 205, 304].includes(status);
|
|
268
268
|
}
|
|
@@ -19,6 +19,8 @@ export type TempoChannelCall = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Client-side cached channel metadata used for automatic voucher management. */
|
|
21
21
|
export type ChannelEntry = {
|
|
22
|
+
/** HTTP origin bound by a session manager; absent on legacy or standalone method entries. */
|
|
23
|
+
origin?: string | undefined;
|
|
22
24
|
/** TIP-1034 channel ID derived from descriptor, escrow, and chain ID. */
|
|
23
25
|
channelId: Hex.Hex;
|
|
24
26
|
/** Highest cumulative amount this client has locally authorized. */
|
|
@@ -15,6 +15,8 @@ export type ChannelStore = {
|
|
|
15
15
|
};
|
|
16
16
|
/** Channel persistence and update notification for credential results. */
|
|
17
17
|
export type ChannelSink = {
|
|
18
|
+
/** Origin attached to newly created or recovered channels before persistence. */
|
|
19
|
+
origin?: string | undefined;
|
|
18
20
|
store: ChannelStore;
|
|
19
21
|
notifyUpdate: (entry: ChannelEntry) => void;
|
|
20
22
|
};
|
|
@@ -91,6 +91,11 @@ export function withFundingLock(store, key, operation) {
|
|
|
91
91
|
export async function persistChannel(store, entry) {
|
|
92
92
|
await exclusive(store, `write:${entryKey(entry)}`, async () => {
|
|
93
93
|
const current = await store.get(entryKey(entry));
|
|
94
|
+
if (current?.origin !== undefined) {
|
|
95
|
+
if (entry.origin !== undefined && entry.origin !== current.origin)
|
|
96
|
+
throw new Error('SessionManager: stored channel origin mismatch');
|
|
97
|
+
entry.origin = current.origin;
|
|
98
|
+
}
|
|
94
99
|
if (current?.uncertainTopUp) {
|
|
95
100
|
if (current.channelId !== entry.channelId)
|
|
96
101
|
throw new Error('Session funding outcome is unknown; reconcile the pending payment before replacing the channel.');
|
|
@@ -25,6 +25,8 @@ export function readCredentialCumulativeAmount(payload) {
|
|
|
25
25
|
* can react to the close.
|
|
26
26
|
*/
|
|
27
27
|
async function storeChannelEntry(sink, entry) {
|
|
28
|
+
if (sink.origin !== undefined)
|
|
29
|
+
entry = { ...entry, origin: sink.origin };
|
|
28
30
|
if (entry.opened)
|
|
29
31
|
await persistChannel(sink.store, entry);
|
|
30
32
|
else
|
|
@@ -132,9 +132,32 @@ export declare namespace session {
|
|
|
132
132
|
allowCustomEscrow?: boolean | undefined;
|
|
133
133
|
/** Automatically acquire the session currency from fallback stablecoins before open/top-up. */
|
|
134
134
|
autoSwap?: AutoSwap.resolve.Value | undefined;
|
|
135
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* HTTP origin authorized for credential creation and server-snapshot recovery.
|
|
137
|
+
* Fetch supplies the request origin, but recovering an unknown channel from its snapshot
|
|
138
|
+
* requires this explicit origin pin or a caller-supplied descriptor.
|
|
139
|
+
*/
|
|
140
|
+
origin?: string | undefined;
|
|
141
|
+
/**
|
|
142
|
+
* Pluggable persistence for reusable channels. Defaults to an in-memory store.
|
|
143
|
+
* Use a separate store per HTTP origin: each payment scope holds one channel, and a
|
|
144
|
+
* channel already bound to another origin is rejected rather than overwritten or reused.
|
|
145
|
+
*/
|
|
136
146
|
channelStore?: ChannelStore | undefined;
|
|
137
|
-
/**
|
|
147
|
+
/**
|
|
148
|
+
* Allowlist of TIP-20 currency addresses this client will pay sessions in, compared
|
|
149
|
+
* case-insensitively. Offers in other currencies are skipped during selection and
|
|
150
|
+
* rejected before signing; an empty list rejects all. Required with `maxAmount` or
|
|
151
|
+
* `maxAtomicAmount`, which are denominated in the challenge currency.
|
|
152
|
+
*/
|
|
153
|
+
currencies?: readonly Address[] | undefined;
|
|
154
|
+
/**
|
|
155
|
+
* Allowlist of session recipients (the channel payee), compared case-insensitively. Offers
|
|
156
|
+
* to other recipients, or without one, are skipped during selection and rejected before
|
|
157
|
+
* signing, as for charge. Explicit closes are exempt.
|
|
158
|
+
*/
|
|
159
|
+
expectedRecipients?: readonly Address[] | undefined;
|
|
160
|
+
/** Token decimals for parsing human-readable amounts (default: 6). `maxAmount` requires it explicitly. */
|
|
138
161
|
decimals?: number | undefined;
|
|
139
162
|
/** Exact TIP20EscrowChannel address pin. Takes precedence over `allowCustomEscrow`. */
|
|
140
163
|
escrow?: Address | undefined;
|
|
@@ -144,6 +167,18 @@ export declare namespace session {
|
|
|
144
167
|
* challenge or resolved client must select an allowed chain.
|
|
145
168
|
*/
|
|
146
169
|
allowedChainIds?: readonly number[] | undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Maximum display-unit amount (e.g. `'1.5'`) the client is willing to pay per session
|
|
172
|
+
* unit (the challenge `amount`), parsed with an explicit `decimals`. Values with more
|
|
173
|
+
* fractional digits than `decimals` are rejected rather than rounded. Larger offers are
|
|
174
|
+
* skipped during selection and rejected before signing. Requires `currencies`.
|
|
175
|
+
*/
|
|
176
|
+
maxAmount?: string | undefined;
|
|
177
|
+
/**
|
|
178
|
+
* Maximum amount the client is willing to pay per session unit, in raw base units of the
|
|
179
|
+
* challenge currency. Checked like `maxAmount`. Requires `currencies`.
|
|
180
|
+
*/
|
|
181
|
+
maxAtomicAmount?: string | undefined;
|
|
147
182
|
/** Maximum channel deposit in human-readable units. Caps server-suggested opens and automatic top-ups. */
|
|
148
183
|
maxDeposit?: string | undefined;
|
|
149
184
|
/**
|
|
@@ -4,6 +4,7 @@ import * as MethodChallenge from '../../../client/internal/MethodChallenge.js';
|
|
|
4
4
|
import * as MethodResponse from '../../../client/internal/MethodResponse.js';
|
|
5
5
|
import * as Constants from '../../../Constants.js';
|
|
6
6
|
import * as Credential from '../../../Credential.js';
|
|
7
|
+
import * as AcceptPayment from '../../../internal/AcceptPayment.js';
|
|
7
8
|
import * as Method from '../../../Method.js';
|
|
8
9
|
import * as Account from '../../../viem/Account.js';
|
|
9
10
|
import * as Client from '../../../viem/Client.js';
|
|
@@ -69,6 +70,65 @@ function acknowledgesOpen(outcome, challenge, opened) {
|
|
|
69
70
|
return matchesOpen(opened, getSessionSnapshot(candidate));
|
|
70
71
|
}) ?? false);
|
|
71
72
|
}
|
|
73
|
+
/** Validates and parses the client's session spending options once, at construction. */
|
|
74
|
+
function resolveSpendingPolicy(parameters) {
|
|
75
|
+
const { currencies, decimals, expectedRecipients, maxAmount, maxAtomicAmount } = parameters;
|
|
76
|
+
// Caps are denominated in the challenge currency, so they require a currency pin.
|
|
77
|
+
if ((maxAmount !== undefined || maxAtomicAmount !== undefined) && currencies === undefined)
|
|
78
|
+
throw new Error('Tempo session maxAmount and maxAtomicAmount require currencies.');
|
|
79
|
+
return {
|
|
80
|
+
currencies: currencies?.map((currency) => currency.toLowerCase()),
|
|
81
|
+
expectedRecipients: expectedRecipients
|
|
82
|
+
? new Set(expectedRecipients.map((recipient) => recipient.toLowerCase()))
|
|
83
|
+
: undefined,
|
|
84
|
+
maxAmount: parseMaxAmount(maxAmount, decimals),
|
|
85
|
+
maxAtomicAmount: maxAtomicAmount === undefined ? undefined : BigInt(maxAtomicAmount),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Parses a display-unit cap, rejecting values that `parseUnits` would round. */
|
|
89
|
+
function parseMaxAmount(maxAmount, decimals) {
|
|
90
|
+
if (maxAmount === undefined)
|
|
91
|
+
return undefined;
|
|
92
|
+
if (decimals === undefined)
|
|
93
|
+
throw new Error('Tempo session maxAmount requires currency decimals.');
|
|
94
|
+
const fraction = maxAmount.split('.')[1]?.replace(/0+$/, '') ?? '';
|
|
95
|
+
if (fraction.length > decimals)
|
|
96
|
+
throw new Error(`Tempo session maxAmount ${maxAmount} has more fractional digits than ${decimals} decimals allow.`);
|
|
97
|
+
return parseUnits(maxAmount, decimals);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Rejects a session challenge whose currency, recipient or per-unit amount violates the client
|
|
101
|
+
* policy. Runs during challenge selection, in the automatic top-up hook, and before any signing work.
|
|
102
|
+
*/
|
|
103
|
+
function assertSpendingPolicy(policy, request) {
|
|
104
|
+
if (policy.currencies && !policy.currencies.includes(request.currency.toLowerCase()))
|
|
105
|
+
throw new Error(`Tempo session currency is not allowed: ${request.currency}.`);
|
|
106
|
+
// The recipient is the channel payee, so it must be one the client expects, as for charge.
|
|
107
|
+
if (policy.expectedRecipients &&
|
|
108
|
+
(!request.recipient || !policy.expectedRecipients.has(request.recipient.toLowerCase())))
|
|
109
|
+
throw new Error(`Unexpected session recipient: ${request.recipient}`);
|
|
110
|
+
const amount = BigInt(request.amount);
|
|
111
|
+
if (policy.maxAtomicAmount !== undefined && amount > policy.maxAtomicAmount)
|
|
112
|
+
throw new Error('Tempo session amount exceeds maxAtomicAmount.');
|
|
113
|
+
if (policy.maxAmount !== undefined && amount > policy.maxAmount)
|
|
114
|
+
throw new Error('Tempo session amount exceeds maxAmount.');
|
|
115
|
+
}
|
|
116
|
+
/** Returns whether a session challenge satisfies the client policy. */
|
|
117
|
+
function satisfiesSpendingPolicy(policy, request) {
|
|
118
|
+
try {
|
|
119
|
+
assertSpendingPolicy(policy, request);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
catch {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Returns whether a caller-supplied credential context asks to close a channel. */
|
|
127
|
+
function isExplicitClose(context) {
|
|
128
|
+
return (typeof context === 'object' &&
|
|
129
|
+
context !== null &&
|
|
130
|
+
context.action === 'close');
|
|
131
|
+
}
|
|
72
132
|
/**
|
|
73
133
|
* Creates the low-level TIP-1034 session payment method for use with `Mppx.create()`.
|
|
74
134
|
*
|
|
@@ -78,6 +138,7 @@ function acknowledgesOpen(outcome, challenge, opened) {
|
|
|
78
138
|
*/
|
|
79
139
|
export function session(parameters = {}) {
|
|
80
140
|
const { account, allowCustomEscrow = false, autoSwap: autoSwapParameter, channelStore, decimals = defaults.decimals, escrow: escrowOverride, allowedChainIds, getClient: getClientParameter, maxDeposit: maxDepositParameter, topUpAmount: topUpAmountParameter, onChannelUpdate, resolveAccount, } = parameters;
|
|
141
|
+
const policy = resolveSpendingPolicy(parameters);
|
|
81
142
|
const getClient = Client.getResolver({
|
|
82
143
|
chain: tempo_chain,
|
|
83
144
|
getClient: getClientParameter,
|
|
@@ -86,6 +147,9 @@ export function session(parameters = {}) {
|
|
|
86
147
|
const getAccount = Account.getResolver({ account });
|
|
87
148
|
const maxDeposit = maxDepositParameter !== undefined ? parseUnits(maxDepositParameter, decimals) : undefined;
|
|
88
149
|
const topUpAmount = topUpAmountParameter !== undefined ? parseUnits(topUpAmountParameter, decimals) : undefined;
|
|
150
|
+
const configuredOrigin = parameters.origin === undefined ? undefined : new URL(parameters.origin);
|
|
151
|
+
if (configuredOrigin && !['http:', 'https:'].includes(configuredOrigin.protocol))
|
|
152
|
+
throw new Error('Session origins require HTTP or HTTPS');
|
|
89
153
|
const store = channelStore ?? createChannelStore();
|
|
90
154
|
const sink = { store, notifyUpdate: (entry) => onChannelUpdate?.(entry) };
|
|
91
155
|
const resolveCredentialAccount = async (resolved, context, entry) => {
|
|
@@ -112,10 +176,19 @@ export function session(parameters = {}) {
|
|
|
112
176
|
resolved,
|
|
113
177
|
});
|
|
114
178
|
const method = Method.toClient(Methods.session, {
|
|
115
|
-
|
|
179
|
+
// Skip offers that violate client policy so an acceptable offer can be chosen. An explicit
|
|
180
|
+
// close, when the caller supplies it before selection, stays available for any offer. Flows
|
|
181
|
+
// that learn the context only after selection defer the policy to createCredential.
|
|
182
|
+
canHandleChallenge: ({ challenge, context }) => isTip1034SessionChallenge(challenge) &&
|
|
183
|
+
(isExplicitClose(context) ||
|
|
184
|
+
context === AcceptPayment.deferredContext ||
|
|
185
|
+
satisfiesSpendingPolicy(policy, challenge.request)),
|
|
116
186
|
context: sessionContextSchema,
|
|
117
187
|
async createCredential(parameters) {
|
|
118
188
|
const { challenge, context } = parameters;
|
|
189
|
+
// Closing settles what the channel already spent, so it stays available for any offer.
|
|
190
|
+
if (context?.action !== 'close')
|
|
191
|
+
assertSpendingPolicy(policy, challenge.request);
|
|
119
192
|
const resolved = await resolveChallengeContext({
|
|
120
193
|
allowCustomEscrow,
|
|
121
194
|
challenge,
|
|
@@ -124,6 +197,13 @@ export function session(parameters = {}) {
|
|
|
124
197
|
getClient,
|
|
125
198
|
});
|
|
126
199
|
const attempt = MethodResponse.getAttempt(parameters);
|
|
200
|
+
const origin = MethodResponse.getOrigin(parameters) ?? configuredOrigin?.origin;
|
|
201
|
+
if (origin !== undefined &&
|
|
202
|
+
(origin === 'null' || !['http:', 'https:'].includes(new URL(origin).protocol)))
|
|
203
|
+
throw new Error('Session payments require an HTTP or HTTPS origin');
|
|
204
|
+
if (configuredOrigin && origin !== configuredOrigin.origin)
|
|
205
|
+
throw new Error('Session channel origin mismatch');
|
|
206
|
+
const scopedSink = { ...sink, origin };
|
|
127
207
|
let release = () => { };
|
|
128
208
|
try {
|
|
129
209
|
const automatic = !hasSessionAction(context) && context?.descriptor === undefined;
|
|
@@ -133,10 +213,16 @@ export function session(parameters = {}) {
|
|
|
133
213
|
await attempt.prepare();
|
|
134
214
|
}
|
|
135
215
|
let entry = await store.get(resolved.key);
|
|
216
|
+
if (entry && entry.origin !== origin)
|
|
217
|
+
throw new Error('Session channel origin mismatch');
|
|
218
|
+
if (origin && !entry && resolved.snapshot && !context?.descriptor && !configuredOrigin)
|
|
219
|
+
throw new Error('Session snapshot recovery requires an explicitly configured origin or descriptor');
|
|
136
220
|
let plan = await resolveCredentialPlan(resolved, context, entry);
|
|
137
221
|
if (!automatic && attempt && plan.type === 'open') {
|
|
138
222
|
release = await lockChannel(store, resolved.key);
|
|
139
223
|
const nextEntry = await store.get(resolved.key);
|
|
224
|
+
if (nextEntry && nextEntry.origin !== origin)
|
|
225
|
+
throw new Error('Session channel origin mismatch');
|
|
140
226
|
if (nextEntry?.channelId !== entry?.channelId) {
|
|
141
227
|
entry = nextEntry;
|
|
142
228
|
if (entry?.opened) {
|
|
@@ -154,13 +240,13 @@ export function session(parameters = {}) {
|
|
|
154
240
|
store: {
|
|
155
241
|
get: (key) => store.get(key),
|
|
156
242
|
async set(next) {
|
|
157
|
-
pendingOpen = next;
|
|
243
|
+
pendingOpen = origin ? { ...next, origin } : next;
|
|
158
244
|
},
|
|
159
245
|
delete: (key) => store.delete(key),
|
|
160
246
|
},
|
|
161
247
|
notifyUpdate() { },
|
|
162
248
|
}
|
|
163
|
-
:
|
|
249
|
+
: scopedSink;
|
|
164
250
|
const payload = await executeCredentialPlan(plan, credentialSink, AutoSwap.resolve(context?.autoSwap ?? autoSwapParameter, AutoSwap.defaultCurrencies));
|
|
165
251
|
const credential = await serializeCredential(challenge, payload, resolved.chainId, plan.account);
|
|
166
252
|
if (attempt && pendingOpen) {
|
|
@@ -195,7 +281,12 @@ export function session(parameters = {}) {
|
|
|
195
281
|
}
|
|
196
282
|
},
|
|
197
283
|
});
|
|
198
|
-
|
|
284
|
+
function createSessionCredential(challenge, context, input) {
|
|
285
|
+
const parameters = { challenge, context };
|
|
286
|
+
MethodResponse.attachOrigin(parameters, resolveInputOrigin(input));
|
|
287
|
+
return method.createCredential(parameters);
|
|
288
|
+
}
|
|
289
|
+
const topUpChannelIfNeeded = async ({ autoSwap, challenge, channel, deposit, fetch, input, requiredCumulative, signal, }) => {
|
|
199
290
|
return withFundingLock(store, entryKey(channel), async () => {
|
|
200
291
|
signal?.throwIfAborted();
|
|
201
292
|
const fundingChannel = await store.get(entryKey(channel));
|
|
@@ -215,11 +306,12 @@ export function session(parameters = {}) {
|
|
|
215
306
|
});
|
|
216
307
|
if (additionalDeposit > 0n)
|
|
217
308
|
await postTopUp({
|
|
309
|
+
autoSwap,
|
|
218
310
|
additionalDeposit,
|
|
219
311
|
challenge,
|
|
220
312
|
channel,
|
|
221
313
|
channelId: channel.channelId,
|
|
222
|
-
createSessionCredential: (challenge, context) =>
|
|
314
|
+
createSessionCredential: (challenge, context) => createSessionCredential(challenge, context, input),
|
|
223
315
|
onUncertainFunding: async (entry) => {
|
|
224
316
|
const current = await store.get(entryKey(entry));
|
|
225
317
|
const unresolved = {
|
|
@@ -252,6 +344,7 @@ export function session(parameters = {}) {
|
|
|
252
344
|
const sessionContext = context === undefined ? undefined : sessionContextSchema.parse(context);
|
|
253
345
|
if (hasSessionAction(sessionContext))
|
|
254
346
|
return;
|
|
347
|
+
assertSpendingPolicy(policy, challenge.request);
|
|
255
348
|
const resolved = await resolveChallengeContext({
|
|
256
349
|
allowCustomEscrow,
|
|
257
350
|
challenge,
|
|
@@ -262,6 +355,9 @@ export function session(parameters = {}) {
|
|
|
262
355
|
const channel = await store.get(resolved.key);
|
|
263
356
|
if (!channel?.opened)
|
|
264
357
|
return;
|
|
358
|
+
const origin = resolveInputOrigin(input);
|
|
359
|
+
if (channel.origin !== origin)
|
|
360
|
+
throw new Error('Session channel origin mismatch');
|
|
265
361
|
const snapshot = resolved.snapshot?.channelId.toLowerCase() === channel.channelId.toLowerCase()
|
|
266
362
|
? resolved.snapshot
|
|
267
363
|
: undefined;
|
|
@@ -276,6 +372,7 @@ export function session(parameters = {}) {
|
|
|
276
372
|
if (!canSignDescriptor(account, channel.descriptor))
|
|
277
373
|
return;
|
|
278
374
|
await topUpChannelIfNeeded({
|
|
375
|
+
autoSwap: sessionContext?.autoSwap,
|
|
279
376
|
challenge,
|
|
280
377
|
channel,
|
|
281
378
|
deposit,
|
|
@@ -285,7 +382,7 @@ export function session(parameters = {}) {
|
|
|
285
382
|
signal,
|
|
286
383
|
});
|
|
287
384
|
});
|
|
288
|
-
return MethodResponse.register(method, async ({ challenge, credential, fetch, headers, input, refetch, response, signal }) => {
|
|
385
|
+
return MethodResponse.register(method, async ({ challenge, context, credential, fetch, headers, input, refetch, response, signal, }) => {
|
|
289
386
|
if (!isTip1034SessionChallenge(challenge))
|
|
290
387
|
return response;
|
|
291
388
|
if (!isEventStream(response)) {
|
|
@@ -305,13 +402,14 @@ export function session(parameters = {}) {
|
|
|
305
402
|
let channel = await store.get(channelKey);
|
|
306
403
|
const driver = {
|
|
307
404
|
assertVoucherWithinLocalLimit: (cumulativeAmount) => assertWithinMaxDeposit(cumulativeAmount, maxDeposit),
|
|
308
|
-
createSessionCredential: (challenge, context) =>
|
|
405
|
+
createSessionCredential: (challenge, context) => createSessionCredential(challenge, context, input),
|
|
309
406
|
fetch,
|
|
310
407
|
getChannel: () => channel ?? null,
|
|
311
408
|
async topUpIfNeeded({ deposit, requiredCumulative, signal: topUpSignal }) {
|
|
312
409
|
if (!channel)
|
|
313
410
|
return;
|
|
314
411
|
await topUpChannelIfNeeded({
|
|
412
|
+
autoSwap: context === undefined ? undefined : sessionContextSchema.parse(context).autoSwap,
|
|
315
413
|
challenge,
|
|
316
414
|
channel,
|
|
317
415
|
deposit: channel.deposit > deposit ? channel.deposit : deposit,
|
|
@@ -330,4 +428,10 @@ export function session(parameters = {}) {
|
|
|
330
428
|
});
|
|
331
429
|
});
|
|
332
430
|
}
|
|
431
|
+
function resolveInputOrigin(input) {
|
|
432
|
+
const url = new URL(input instanceof Request ? input.url : input, typeof globalThis.location === 'undefined' ? undefined : globalThis.location.href);
|
|
433
|
+
if (!['http:', 'https:'].includes(url.protocol))
|
|
434
|
+
throw new Error('Session payments require an HTTP or HTTPS origin');
|
|
435
|
+
return url.origin;
|
|
436
|
+
}
|
|
333
437
|
//# sourceMappingURL=Session.js.map
|
|
@@ -60,6 +60,7 @@ export declare function mergeSessionCredentialContext(credentialContext: unknown
|
|
|
60
60
|
* channel state management and credential creation, and to `Fetch.from`
|
|
61
61
|
* for the 402 challenge/retry flow.
|
|
62
62
|
*
|
|
63
|
+
* Each manager is pinned to its first request origin. Use a separate manager per origin.
|
|
63
64
|
* `channelStore` can persist reusable channels between manager instances.
|
|
64
65
|
*/
|
|
65
66
|
export declare function sessionManager(parameters: sessionManager.Parameters): SessionManager;
|
|
@@ -75,7 +76,7 @@ export declare namespace sessionManager {
|
|
|
75
76
|
/** Enables same-route HEAD bootstrap from a server session snapshot before opening a new channel. */
|
|
76
77
|
bootstrap?: boolean | undefined;
|
|
77
78
|
/**
|
|
78
|
-
* Server origins allowed to request bootstrap proofs for a realm other than their hostname,
|
|
79
|
+
* Server origins allowed to request bootstrap proofs or session payments for a realm other than their hostname,
|
|
79
80
|
* mapped to that realm, such as `{ 'https://api.example.com': 'MPP Payment' }`.
|
|
80
81
|
*/
|
|
81
82
|
trustedTempoProofRealms?: ProofRealm.TrustedRealms | undefined;
|
|
@@ -7,6 +7,7 @@ import * as MethodResponse from '../../../client/internal/MethodResponse.js';
|
|
|
7
7
|
import * as ProofRealm from '../../../client/internal/ProofRealm.js';
|
|
8
8
|
import { setCredentialHeader } from '../../../client/internal/protocols/Shared.js';
|
|
9
9
|
import * as Constants from '../../../Constants.js';
|
|
10
|
+
import * as IdentityClient from '../../../identity/Client.js';
|
|
10
11
|
import * as Account from '../../../viem/Account.js';
|
|
11
12
|
import * as Client from '../../../viem/Client.js';
|
|
12
13
|
import { charge as chargePlugin } from '../../client/Charge.js';
|
|
@@ -23,7 +24,7 @@ import { resolveCloseTarget } from './Runtime.js';
|
|
|
23
24
|
import { assertVoucherWithinLocalLimit as assertVoucherWithinLocalAuthorization } from './Runtime.js';
|
|
24
25
|
import { applySessionReceiptToRuntime, captureRuntimeSnapshot as captureRuntimeStateSnapshot, computeFallbackCloseAmount, createSessionManagerRuntime, dispatchSessionEvent, restoreCumulativeAuthorization, restoreRuntimeSnapshot as restoreRuntimeStateSnapshot, resolveAutomaticTopUp, } from './Runtime.js';
|
|
25
26
|
import { closeSocketSession } from './Runtime.js';
|
|
26
|
-
import { closeHttpSession, getSessionSnapshot, isTempoSessionChallenge, postTopUp, retryHttpPaymentRequired, webSocketProbeUrl, } from './Transports.js';
|
|
27
|
+
import { closeHttpSession, getSessionSnapshot, isTempoSessionChallenge, isRequest, postTopUp, retryHttpPaymentRequired, webSocketProbeUrl, } from './Transports.js';
|
|
27
28
|
import { WebSocketReadyState, } from './Transports.js';
|
|
28
29
|
import { consumeSseSessionResponse, openSseSession } from './Transports.js';
|
|
29
30
|
import { UncertainFundingError, applyTopUpResult, resolveManualTopUp, } from './Transports.js';
|
|
@@ -47,7 +48,7 @@ function isZeroAmountChargeChallenge(challenge) {
|
|
|
47
48
|
function requestInitWithSessionHint(input, init, channelId) {
|
|
48
49
|
if (!channelId)
|
|
49
50
|
return init;
|
|
50
|
-
const requestHeaders = input
|
|
51
|
+
const requestHeaders = isRequest(input) ? input.headers : undefined;
|
|
51
52
|
const headers = {
|
|
52
53
|
...Fetch.normalizeHeaders(requestHeaders),
|
|
53
54
|
...Fetch.normalizeHeaders(init?.headers),
|
|
@@ -88,6 +89,7 @@ function resolveSessionManagerConfig(parameters) {
|
|
|
88
89
|
* channel state management and credential creation, and to `Fetch.from`
|
|
89
90
|
* for the 402 challenge/retry flow.
|
|
90
91
|
*
|
|
92
|
+
* Each manager is pinned to its first request origin. Use a separate manager per origin.
|
|
91
93
|
* `channelStore` can persist reusable channels between manager instances.
|
|
92
94
|
*/
|
|
93
95
|
export function sessionManager(parameters) {
|
|
@@ -96,6 +98,34 @@ export function sessionManager(parameters) {
|
|
|
96
98
|
Client.assertAllowedChainId(parameters.allowedChainIds, clientChainId);
|
|
97
99
|
const allowCustomEscrow = parameters.allowCustomEscrow ?? false;
|
|
98
100
|
const config = resolveSessionManagerConfig(parameters);
|
|
101
|
+
let sessionOrigin;
|
|
102
|
+
function bindOrigin(input) {
|
|
103
|
+
const url = new URL(isRequest(input) ? input.url : String(input), typeof location === 'undefined' ? undefined : location.href);
|
|
104
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:')
|
|
105
|
+
throw new Error('SessionManager: session payments require an HTTP origin');
|
|
106
|
+
const origin = url.origin;
|
|
107
|
+
if (sessionOrigin !== undefined && sessionOrigin !== origin)
|
|
108
|
+
throw new Error('SessionManager: session payment origin mismatch');
|
|
109
|
+
sessionOrigin = origin;
|
|
110
|
+
return origin;
|
|
111
|
+
}
|
|
112
|
+
const inheritedIdentity = Fetch.identityOf(config.fetch);
|
|
113
|
+
const rawFetch = Fetch.unwrapFetch(config.fetch);
|
|
114
|
+
config.fetch = async (input, init) => {
|
|
115
|
+
const origin = bindOrigin(input);
|
|
116
|
+
const response = await rawFetch(input, {
|
|
117
|
+
...init,
|
|
118
|
+
redirect: (init?.redirect ?? (isRequest(input) ? input.redirect : undefined)) === 'manual'
|
|
119
|
+
? 'manual'
|
|
120
|
+
: 'error',
|
|
121
|
+
});
|
|
122
|
+
if (response.url && new URL(response.url).origin !== origin)
|
|
123
|
+
throw new Error('SessionManager: session response origin mismatch');
|
|
124
|
+
return response;
|
|
125
|
+
};
|
|
126
|
+
const guardedFetch = config.fetch;
|
|
127
|
+
if (inheritedIdentity)
|
|
128
|
+
config.fetch = IdentityClient.wrapFetch(guardedFetch, inheritedIdentity);
|
|
99
129
|
const getClient = Client.getResolver({
|
|
100
130
|
chain: tempo_chain,
|
|
101
131
|
getClient: parameters.client ? () => parameters.client : parameters.getClient,
|
|
@@ -115,11 +145,19 @@ export function sessionManager(parameters) {
|
|
|
115
145
|
/** Returns the backing entry for `key` only when it is open and not ignored. */
|
|
116
146
|
async function getReusable(key) {
|
|
117
147
|
const entry = await backing.get(key);
|
|
118
|
-
if (entry?.opened && !ignoredChannelIds.has(entry.channelId))
|
|
148
|
+
if (entry?.opened && !ignoredChannelIds.has(entry.channelId)) {
|
|
149
|
+
if (entry.origin !== sessionOrigin)
|
|
150
|
+
throw new Error('SessionManager: stored channel origin mismatch');
|
|
119
151
|
return entry;
|
|
152
|
+
}
|
|
120
153
|
return undefined;
|
|
121
154
|
}
|
|
122
|
-
const persistChannel = (entry) =>
|
|
155
|
+
const persistChannel = (entry) => {
|
|
156
|
+
if (entry.origin !== undefined && entry.origin !== sessionOrigin)
|
|
157
|
+
throw new Error('SessionManager: stored channel origin mismatch');
|
|
158
|
+
entry.origin = sessionOrigin;
|
|
159
|
+
return persistStoredChannel(backing, entry);
|
|
160
|
+
};
|
|
123
161
|
const store = {
|
|
124
162
|
async get(key) {
|
|
125
163
|
const entry = await getReusable(key);
|
|
@@ -252,7 +290,9 @@ export function sessionManager(parameters) {
|
|
|
252
290
|
resolveAccount: parameters.resolveAccount,
|
|
253
291
|
});
|
|
254
292
|
const wrappedFetch = Fetch.from({
|
|
255
|
-
|
|
293
|
+
trustedTempoProofRealms: parameters.trustedTempoProofRealms,
|
|
294
|
+
fetch: guardedFetch,
|
|
295
|
+
identity: inheritedIdentity,
|
|
256
296
|
methods: [method],
|
|
257
297
|
onChallenge: async (challenge, _helpers) => {
|
|
258
298
|
if (!isTempoSessionChallenge(challenge))
|
|
@@ -277,10 +317,13 @@ export function sessionManager(parameters) {
|
|
|
277
317
|
},
|
|
278
318
|
});
|
|
279
319
|
function createSessionCredential(challenge, context) {
|
|
280
|
-
|
|
320
|
+
const parameters = {
|
|
281
321
|
challenge,
|
|
282
322
|
context: mergeSessionCredentialContext(credentialContext, context),
|
|
283
|
-
}
|
|
323
|
+
};
|
|
324
|
+
if (sessionOrigin)
|
|
325
|
+
MethodResponse.attachOrigin(parameters, sessionOrigin);
|
|
326
|
+
return method.createCredential(parameters);
|
|
284
327
|
}
|
|
285
328
|
function updateSpentFromReceipt(receipt) {
|
|
286
329
|
applySessionReceiptToRuntime({
|
|
@@ -334,9 +377,9 @@ export function sessionManager(parameters) {
|
|
|
334
377
|
return undefined;
|
|
335
378
|
if (runtime.channel?.opened)
|
|
336
379
|
return undefined;
|
|
337
|
-
const requestHeaders = input
|
|
380
|
+
const requestHeaders = isRequest(input) ? input.headers : undefined;
|
|
338
381
|
const { body: _body, method: _method, ...bootstrapInit } = init ?? {};
|
|
339
|
-
const bootstrapInput = input
|
|
382
|
+
const bootstrapInput = isRequest(input) ? input.url : input;
|
|
340
383
|
const headInit = {
|
|
341
384
|
...bootstrapInit,
|
|
342
385
|
method: 'HEAD',
|
|
@@ -646,6 +689,7 @@ export function sessionManager(parameters) {
|
|
|
646
689
|
// corrupt each other's runtime and channel tracking.
|
|
647
690
|
if (channelUse)
|
|
648
691
|
throw new Error('SessionManager: a request is already in flight; concurrent requests on one manager are not supported');
|
|
692
|
+
bindOrigin(input);
|
|
649
693
|
runtime.lastUrl = input;
|
|
650
694
|
runtime.lastHeaders = init?.headers;
|
|
651
695
|
const previous = captureRuntimeStateSnapshot({
|
|
@@ -818,6 +862,7 @@ export function sessionManager(parameters) {
|
|
|
818
862
|
throw new Error('No WebSocket implementation available. Pass `webSocket` to sessionManager() in this runtime.');
|
|
819
863
|
}
|
|
820
864
|
const probeUrl = webSocketProbeUrl(input);
|
|
865
|
+
bindOrigin(probeUrl);
|
|
821
866
|
const signalInit = init?.signal ? { signal: init.signal } : undefined;
|
|
822
867
|
await bootstrapSession(probeUrl, signalInit);
|
|
823
868
|
// Cold starts resume from `channelStore` after the probe's 402.
|
|
@@ -906,9 +951,15 @@ export function sessionManager(parameters) {
|
|
|
906
951
|
};
|
|
907
952
|
registerSessionManagerInternals(self, {
|
|
908
953
|
consumeSseResponse(input, response, options) {
|
|
954
|
+
const origin = bindOrigin(input);
|
|
955
|
+
if (response.url && new URL(response.url).origin !== origin)
|
|
956
|
+
throw new Error('SessionManager: session response origin mismatch');
|
|
909
957
|
return consumeSseSessionResponse(input, response, options, sseDriver);
|
|
910
958
|
},
|
|
911
959
|
rehydrate({ channel, challenge, input, spent }) {
|
|
960
|
+
const origin = bindOrigin(input);
|
|
961
|
+
if (channel.origin !== origin)
|
|
962
|
+
throw new Error('SessionManager: stored channel origin mismatch');
|
|
912
963
|
if (!channel.opened)
|
|
913
964
|
throw new Error('Cannot restore a closed session channel.');
|
|
914
965
|
if (!isTempoSessionChallenge(challenge)) {
|
|
@@ -201,6 +201,8 @@ export type TempoSessionChallenge = Challenge.Challenge<z.output<typeof Methods.
|
|
|
201
201
|
export type CreateSessionCredential = (challenge: TempoSessionChallenge, context: SessionContext) => Promise<string>;
|
|
202
202
|
/** Inputs for posting a precompile session top-up credential. */
|
|
203
203
|
export type PostTopUpParameters = {
|
|
204
|
+
/** Request-local swap policy; undefined inherits the method default. */
|
|
205
|
+
autoSwap?: SessionContext['autoSwap'] | undefined;
|
|
204
206
|
/** Additional deposit in raw token units. */
|
|
205
207
|
additionalDeposit: bigint;
|
|
206
208
|
/** Challenge used to authorize the management request. */
|
|
@@ -282,6 +284,8 @@ export declare function getSessionSnapshot(challenge: TempoSessionChallenge): Se
|
|
|
282
284
|
/** Merges request headers and sets the challenge's credential header for a retry. */
|
|
283
285
|
export declare function requestInitWithAuthorization(input: RequestInfo | URL, init: RequestInit | undefined, credential: string, challenge: TempoSessionChallenge): RequestInit;
|
|
284
286
|
/** Returns the exact resource URL used for out-of-band management POSTs, without its fragment. */
|
|
287
|
+
/** Returns whether a value is a `Request`, including one from another realm such as an iframe. */
|
|
288
|
+
export declare function isRequest(value: unknown): value is Request;
|
|
285
289
|
export declare function managementInput(input: RequestInfo | URL): RequestInfo | URL;
|
|
286
290
|
/** Converts a WebSocket URL into the HTTP URL used for its payment challenge probe. */
|
|
287
291
|
export declare function webSocketProbeUrl(input: string | URL): URL;
|