mppx 0.7.0 → 0.8.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 +33 -0
- package/README.md +20 -11
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +18 -6
- package/dist/Challenge.js.map +1 -1
- package/dist/Mcp.d.ts +3 -0
- package/dist/Mcp.d.ts.map +1 -1
- package/dist/Mcp.js +2 -0
- package/dist/Mcp.js.map +1 -1
- package/dist/PaymentRequest.d.ts +10 -10
- package/dist/PaymentRequest.js +8 -8
- package/dist/client/Mppx.js +2 -2
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/Transport.d.ts +11 -16
- package/dist/client/Transport.d.ts.map +1 -1
- package/dist/client/Transport.js +55 -75
- package/dist/client/Transport.js.map +1 -1
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +1 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +46 -7
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/protocols/Mcp.d.ts +7 -0
- package/dist/client/internal/protocols/Mcp.d.ts.map +1 -0
- package/dist/client/internal/protocols/Mcp.js +159 -0
- package/dist/client/internal/protocols/Mcp.js.map +1 -0
- package/dist/client/internal/protocols/Mpp.d.ts +4 -0
- package/dist/client/internal/protocols/Mpp.d.ts.map +1 -0
- package/dist/client/internal/protocols/Mpp.js +18 -0
- package/dist/client/internal/protocols/Mpp.js.map +1 -0
- package/dist/client/internal/protocols/Protocol.d.ts +10 -0
- package/dist/client/internal/protocols/Protocol.d.ts.map +1 -0
- package/dist/client/internal/protocols/Protocol.js +2 -0
- package/dist/client/internal/protocols/Protocol.js.map +1 -0
- package/dist/client/internal/protocols/Shared.d.ts +5 -0
- package/dist/client/internal/protocols/Shared.d.ts.map +1 -0
- package/dist/client/internal/protocols/Shared.js +20 -0
- package/dist/client/internal/protocols/Shared.js.map +1 -0
- package/dist/client/internal/protocols/X402.d.ts +8 -0
- package/dist/client/internal/protocols/X402.d.ts.map +1 -0
- package/dist/client/internal/protocols/X402.js +39 -0
- package/dist/client/internal/protocols/X402.js.map +1 -0
- package/dist/evm/client/index.d.ts +1 -0
- package/dist/evm/client/index.d.ts.map +1 -1
- package/dist/evm/client/index.js +1 -0
- package/dist/evm/client/index.js.map +1 -1
- package/dist/evm/index.d.ts +2 -0
- package/dist/evm/index.d.ts.map +1 -1
- package/dist/evm/index.js +2 -0
- package/dist/evm/index.js.map +1 -1
- package/dist/evm/server/index.d.ts +1 -0
- package/dist/evm/server/index.d.ts.map +1 -1
- package/dist/evm/server/index.js +1 -0
- package/dist/evm/server/index.js.map +1 -1
- package/dist/mcp/client/McpClient.d.ts +101 -0
- package/dist/mcp/client/McpClient.d.ts.map +1 -0
- package/dist/mcp/client/McpClient.js +162 -0
- package/dist/mcp/client/McpClient.js.map +1 -0
- package/dist/mcp/client/index.d.ts.map +1 -0
- package/dist/mcp/client/index.js.map +1 -0
- package/dist/mcp/server/Transport.d.ts.map +1 -0
- package/dist/mcp/server/Transport.js.map +1 -0
- package/dist/mcp/server/index.d.ts.map +1 -0
- package/dist/mcp/server/index.js.map +1 -0
- package/dist/server/Mppx.d.ts +1 -1
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +9 -0
- package/dist/server/Mppx.js.map +1 -1
- package/dist/server/Transport.d.ts +1 -1
- package/dist/server/Transport.d.ts.map +1 -1
- package/dist/server/Transport.js +1 -1
- package/dist/server/Transport.js.map +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts +1 -1
- package/dist/stripe/server/internal/html.gen.d.ts.map +1 -1
- package/dist/stripe/server/internal/html.gen.js +1 -1
- package/dist/stripe/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/Proof.d.ts +85 -1
- package/dist/tempo/Proof.d.ts.map +1 -1
- package/dist/tempo/Proof.js +35 -0
- package/dist/tempo/Proof.js.map +1 -1
- package/dist/tempo/client/Charge.d.ts +13 -1
- package/dist/tempo/client/Charge.d.ts.map +1 -1
- package/dist/tempo/client/Charge.js +38 -25
- package/dist/tempo/client/Charge.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +5 -3
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/client/Methods.js +4 -2
- package/dist/tempo/client/Methods.js.map +1 -1
- package/dist/tempo/client/ResolveAccount.d.ts +40 -0
- package/dist/tempo/client/ResolveAccount.d.ts.map +1 -0
- package/dist/tempo/client/ResolveAccount.js +2 -0
- package/dist/tempo/client/ResolveAccount.js.map +1 -0
- package/dist/tempo/internal/fee-payer.d.ts +9 -1
- package/dist/tempo/internal/fee-payer.d.ts.map +1 -1
- package/dist/tempo/internal/fee-payer.js +35 -6
- package/dist/tempo/internal/fee-payer.js.map +1 -1
- package/dist/tempo/internal/proof.d.ts +71 -5
- package/dist/tempo/internal/proof.d.ts.map +1 -1
- package/dist/tempo/internal/proof.js +42 -6
- package/dist/tempo/internal/proof.js.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/legacy/client/SessionManager.js +10 -3
- package/dist/tempo/legacy/client/SessionManager.js.map +1 -1
- package/dist/tempo/server/Charge.d.ts.map +1 -1
- package/dist/tempo/server/Charge.js +42 -18
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +4 -2
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +4 -2
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +10 -0
- package/dist/tempo/server/Subscription.d.ts.map +1 -1
- package/dist/tempo/server/Subscription.js +135 -23
- package/dist/tempo/server/Subscription.js.map +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts +1 -1
- package/dist/tempo/server/internal/html.gen.d.ts.map +1 -1
- package/dist/tempo/server/internal/html.gen.js +1 -1
- package/dist/tempo/server/internal/html.gen.js.map +1 -1
- package/dist/tempo/session/client/ChannelOps.d.ts +2 -3
- package/dist/tempo/session/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/session/client/ChannelOps.js +7 -10
- package/dist/tempo/session/client/ChannelOps.js.map +1 -1
- package/dist/tempo/session/client/ChannelStore.d.ts +51 -0
- package/dist/tempo/session/client/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/client/ChannelStore.js +63 -0
- package/dist/tempo/session/client/ChannelStore.js.map +1 -0
- package/dist/tempo/session/client/CredentialState.d.ts +7 -24
- package/dist/tempo/session/client/CredentialState.d.ts.map +1 -1
- package/dist/tempo/session/client/CredentialState.js +51 -49
- package/dist/tempo/session/client/CredentialState.js.map +1 -1
- package/dist/tempo/session/client/Session.d.ts +8 -2
- package/dist/tempo/session/client/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +22 -8
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts +4 -40
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +124 -174
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/index.d.ts +3 -4
- package/dist/tempo/session/client/index.d.ts.map +1 -1
- package/dist/tempo/session/client/index.js +1 -0
- package/dist/tempo/session/client/index.js.map +1 -1
- package/dist/tempo/session/precompile/Voucher.d.ts +3 -3
- package/dist/tempo/session/precompile/Voucher.d.ts.map +1 -1
- package/dist/tempo/session/precompile/Voucher.js +24 -25
- package/dist/tempo/session/precompile/Voucher.js.map +1 -1
- package/dist/tempo/session/server/Settlement.d.ts.map +1 -1
- package/dist/tempo/session/server/Settlement.js +4 -2
- package/dist/tempo/session/server/Settlement.js.map +1 -1
- package/dist/tempo/session/server/Sse.d.ts.map +1 -1
- package/dist/tempo/session/server/Sse.js.map +1 -1
- package/dist/tempo/session/server/Ws.d.ts.map +1 -1
- package/dist/tempo/session/server/Ws.js.map +1 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts +712 -1
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -1
- package/dist/tempo/subscription/Store.d.ts +2 -0
- package/dist/tempo/subscription/Store.d.ts.map +1 -1
- package/dist/tempo/subscription/Store.js +16 -1
- package/dist/tempo/subscription/Store.js.map +1 -1
- package/dist/x402/index.d.ts +1 -0
- package/dist/x402/index.d.ts.map +1 -1
- package/dist/x402/index.js +1 -0
- package/dist/x402/index.js.map +1 -1
- package/package.json +21 -10
- package/src/Challenge.test.ts +40 -0
- package/src/Challenge.ts +19 -6
- package/src/Mcp.ts +4 -0
- package/src/PaymentRequest.ts +10 -10
- package/src/cli/cli.test.ts +15 -15
- package/src/client/Mppx.test-d.ts +21 -1
- package/src/client/Mppx.test.ts +1 -1
- package/src/client/Mppx.ts +2 -2
- package/src/client/Transport.test.ts +225 -178
- package/src/client/Transport.ts +77 -83
- package/src/client/index.ts +14 -0
- package/src/client/internal/Fetch.test.ts +207 -2
- package/src/client/internal/Fetch.ts +52 -6
- package/src/client/internal/protocols/Mcp.test.ts +220 -0
- package/src/client/internal/protocols/Mcp.ts +162 -0
- package/src/client/internal/protocols/Mpp.ts +21 -0
- package/src/client/internal/protocols/Protocol.ts +10 -0
- package/src/client/internal/protocols/Shared.ts +25 -0
- package/src/client/internal/protocols/X402.ts +42 -0
- package/src/discovery/OpenApi.test.ts +1 -1
- package/src/evm/PublicInterface.test-d.ts +1 -1
- package/src/evm/client/index.ts +1 -0
- package/src/evm/index.ts +2 -0
- package/src/evm/server/Charge.test.ts +1 -1
- package/src/evm/server/index.ts +1 -0
- package/src/{mcp-sdk → mcp}/client/McpClient.integration.test.ts +10 -4
- package/src/{mcp-sdk → mcp}/client/McpClient.test-d.ts +45 -18
- package/src/{mcp-sdk → mcp}/client/McpClient.test.ts +211 -5
- package/src/mcp/client/McpClient.ts +307 -0
- package/src/{mcp-sdk → mcp}/client/McpClient.unit.test.ts +9 -5
- package/src/middlewares/elysia.test.ts +1 -1
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/internal/mppx.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/proxy/Proxy.test.ts +1 -1
- package/src/proxy/services/anthropic.test.ts +1 -1
- package/src/proxy/services/openai.test.ts +1 -1
- package/src/proxy/services/stripe.test.ts +1 -1
- package/src/server/Mppx.authorize.test.ts +1 -1
- package/src/server/Mppx.test-d.ts +1 -1
- package/src/server/Mppx.test.ts +20 -2
- package/src/server/Mppx.ts +14 -1
- package/src/server/Transport.test.ts +6 -6
- package/src/server/Transport.ts +1 -1
- package/src/stripe/Charge.integration.test.ts +1 -1
- package/src/stripe/client/Charge.test.ts +1 -1
- package/src/stripe/server/Charge.test.ts +1 -1
- package/src/stripe/server/internal/html/package.json +1 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/Proof.conformance.test.ts +146 -0
- package/src/tempo/Proof.test-d.ts +15 -0
- package/src/tempo/Proof.ts +52 -1
- package/src/tempo/Subscription.integration.test.ts +1 -1
- package/src/tempo/client/Charge.test.ts +173 -0
- package/src/tempo/client/Charge.ts +65 -36
- package/src/tempo/client/Methods.ts +4 -2
- package/src/tempo/client/ResolveAccount.ts +46 -0
- package/src/tempo/internal/fee-payer.test.ts +65 -10
- package/src/tempo/internal/fee-payer.ts +42 -6
- package/src/tempo/internal/proof.test.ts +12 -4
- package/src/tempo/internal/proof.ts +55 -6
- package/src/tempo/legacy/client/SessionManager.ts +11 -3
- package/src/tempo/legacy/server/Session.test.ts +91 -26
- package/src/tempo/server/Charge.test.ts +388 -17
- package/src/tempo/server/Charge.ts +46 -24
- package/src/tempo/server/Methods.ts +4 -2
- package/src/tempo/server/Subscription.test.ts +465 -3
- package/src/tempo/server/Subscription.ts +174 -19
- package/src/tempo/server/internal/html/package.json +2 -2
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/client/ChannelOps.ts +5 -19
- package/src/tempo/session/client/ChannelStore.ts +111 -0
- package/src/tempo/session/client/CredentialState.test.ts +206 -62
- package/src/tempo/session/client/CredentialState.ts +58 -73
- package/src/tempo/session/client/Session.test.ts +41 -1
- package/src/tempo/session/client/Session.ts +36 -10
- package/src/tempo/session/client/SessionManager.test.ts +154 -65
- package/src/tempo/session/client/SessionManager.ts +141 -235
- package/src/tempo/session/client/index.ts +8 -5
- package/src/tempo/session/precompile/Voucher.test.ts +45 -7
- package/src/tempo/session/precompile/Voucher.ts +27 -25
- package/src/tempo/session/server/Session.test.ts +4 -4
- package/src/tempo/session/server/Settlement.test.ts +88 -1
- package/src/tempo/session/server/Settlement.ts +2 -1
- package/src/tempo/session/server/Sse.ts +0 -2
- package/src/tempo/session/server/Ws.ts +0 -4
- package/src/tempo/subscription/Store.ts +27 -9
- package/src/x402/Exact.e2e.test.ts +1 -1
- package/src/x402/PublicInterface.test-d.ts +1 -1
- package/src/x402/index.ts +1 -0
- package/dist/mcp-sdk/client/McpClient.d.ts +0 -85
- package/dist/mcp-sdk/client/McpClient.d.ts.map +0 -1
- package/dist/mcp-sdk/client/McpClient.js +0 -118
- package/dist/mcp-sdk/client/McpClient.js.map +0 -1
- package/dist/mcp-sdk/client/index.d.ts.map +0 -1
- package/dist/mcp-sdk/client/index.js.map +0 -1
- package/dist/mcp-sdk/server/Transport.d.ts.map +0 -1
- package/dist/mcp-sdk/server/Transport.js.map +0 -1
- package/dist/mcp-sdk/server/index.d.ts.map +0 -1
- package/dist/mcp-sdk/server/index.js.map +0 -1
- package/src/mcp-sdk/client/McpClient.ts +0 -228
- /package/dist/{mcp-sdk → mcp}/client/index.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/client/index.js +0 -0
- /package/dist/{mcp-sdk → mcp}/server/Transport.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/server/Transport.js +0 -0
- /package/dist/{mcp-sdk → mcp}/server/index.d.ts +0 -0
- /package/dist/{mcp-sdk → mcp}/server/index.js +0 -0
- /package/src/{mcp-sdk → mcp}/client/index.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/Transport.test.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/Transport.ts +0 -0
- /package/src/{mcp-sdk → mcp}/server/index.ts +0 -0
|
@@ -5,6 +5,8 @@ import { describe, expect, test, vi } from 'vp/test'
|
|
|
5
5
|
import * as Challenge from '../../../Challenge.js'
|
|
6
6
|
import * as Constants from '../../../Constants.js'
|
|
7
7
|
import * as Credential from '../../../Credential.js'
|
|
8
|
+
import type { ChannelEntry } from '../client/ChannelOps.js'
|
|
9
|
+
import { createJsonChannelStore, entryKey, type ChannelStore } from '../client/ChannelStore.js'
|
|
8
10
|
import * as Channel from '../precompile/Channel.js'
|
|
9
11
|
import { escrowAbi } from '../precompile/escrow.abi.js'
|
|
10
12
|
import { tip20ChannelEscrow } from '../precompile/Protocol.js'
|
|
@@ -13,7 +15,6 @@ import type { NeedVoucherEvent, SessionReceipt } from '../precompile/Protocol.js
|
|
|
13
15
|
import { formatNeedVoucherEvent, parseEvent } from '../precompile/Protocol.js'
|
|
14
16
|
import type { SessionCredentialPayload } from '../precompile/Protocol.js'
|
|
15
17
|
import { computeFallbackCloseAmount, sessionManager } from './SessionManager.js'
|
|
16
|
-
import type { StoredSessionChannel } from './SessionManager.js'
|
|
17
18
|
|
|
18
19
|
const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
|
|
19
20
|
const challengeId = 'test-challenge-1'
|
|
@@ -59,20 +60,40 @@ const storedChannelId = Channel.computeId({
|
|
|
59
60
|
escrow: tip20ChannelEscrow,
|
|
60
61
|
})
|
|
61
62
|
|
|
62
|
-
function
|
|
63
|
+
function channelEntry(overrides: Partial<ChannelEntry> = {}): ChannelEntry {
|
|
63
64
|
return {
|
|
64
65
|
channelId: storedChannelId,
|
|
65
|
-
cumulativeAmount:
|
|
66
|
-
deposit:
|
|
66
|
+
cumulativeAmount: 1_000_000n,
|
|
67
|
+
deposit: 10_000_000n,
|
|
67
68
|
descriptor: storedDescriptor,
|
|
68
69
|
escrow: tip20ChannelEscrow,
|
|
69
70
|
chainId: 4217,
|
|
70
71
|
opened: true,
|
|
71
|
-
updatedAt: 0,
|
|
72
72
|
...overrides,
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
/**
|
|
77
|
+
* In-memory {@link ChannelStore} with spied `set`/`delete`, optionally seeded.
|
|
78
|
+
* Seeded entries live in the entry index, so the plugin resumes them from
|
|
79
|
+
* `store.get(resolved.key)` after a 402 (there is no first-request hint).
|
|
80
|
+
*/
|
|
81
|
+
function makeChannelStore(seed: readonly ChannelEntry[] = []) {
|
|
82
|
+
const map = new Map<string, ChannelEntry>(seed.map((entry) => [entryKey(entry), entry]))
|
|
83
|
+
const set = vi.fn((entry: ChannelEntry) => {
|
|
84
|
+
map.set(entryKey(entry), entry)
|
|
85
|
+
})
|
|
86
|
+
const remove = vi.fn((key: string) => {
|
|
87
|
+
map.delete(key)
|
|
88
|
+
})
|
|
89
|
+
const store: ChannelStore = {
|
|
90
|
+
get: (key) => map.get(key),
|
|
91
|
+
set,
|
|
92
|
+
delete: remove,
|
|
93
|
+
}
|
|
94
|
+
return { store, set, delete: remove, map }
|
|
95
|
+
}
|
|
96
|
+
|
|
76
97
|
function makeChallenge(overrides: Record<string, unknown> = {}): Challenge.Challenge {
|
|
77
98
|
return Challenge.from({
|
|
78
99
|
id: challengeId,
|
|
@@ -234,6 +255,33 @@ describe('Session', () => {
|
|
|
234
255
|
expect(mockFetch).toHaveBeenCalledOnce()
|
|
235
256
|
})
|
|
236
257
|
|
|
258
|
+
test('rejects a concurrent request while one is in flight', async () => {
|
|
259
|
+
let release!: () => void
|
|
260
|
+
const gate = new Promise<void>((resolve) => {
|
|
261
|
+
release = resolve
|
|
262
|
+
})
|
|
263
|
+
const mockFetch = vi.fn().mockImplementation(async () => {
|
|
264
|
+
await gate
|
|
265
|
+
return makeOkResponse('hello')
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const s = sessionManager({
|
|
269
|
+
account: '0x0000000000000000000000000000000000000001',
|
|
270
|
+
fetch: mockFetch as typeof globalThis.fetch,
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
const first = s.fetch('https://api.example.com/data')
|
|
274
|
+
await expect(s.fetch('https://api.example.com/data')).rejects.toThrow(
|
|
275
|
+
'concurrent requests on one manager are not supported',
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
release()
|
|
279
|
+
expect((await first).status).toBe(200)
|
|
280
|
+
|
|
281
|
+
// The guard clears after the in-flight request settles.
|
|
282
|
+
expect((await s.fetch('https://api.example.com/data')).status).toBe(200)
|
|
283
|
+
})
|
|
284
|
+
|
|
237
285
|
test('binds the default global fetch for browser runtimes', async () => {
|
|
238
286
|
const originalFetch = globalThis.fetch
|
|
239
287
|
const mockFetch = vi.fn(function (this: unknown) {
|
|
@@ -255,28 +303,35 @@ describe('Session', () => {
|
|
|
255
303
|
}
|
|
256
304
|
})
|
|
257
305
|
|
|
258
|
-
test('
|
|
306
|
+
test('resumes a seeded channel after a 402 without a first-request hint', async () => {
|
|
307
|
+
const posted: SessionCredentialPayload[] = []
|
|
259
308
|
const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
|
|
260
|
-
|
|
309
|
+
const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
|
|
310
|
+
const payload = authorization
|
|
311
|
+
? Credential.deserialize<SessionCredentialPayload>(authorization).payload
|
|
312
|
+
: undefined
|
|
313
|
+
if (payload) posted.push(payload)
|
|
314
|
+
if (!payload) return Promise.resolve(make402Response())
|
|
261
315
|
return Promise.resolve(makeOkResponse())
|
|
262
316
|
})
|
|
263
317
|
const s = sessionManager({
|
|
264
318
|
account,
|
|
265
319
|
client,
|
|
266
320
|
fetch: mockFetch as typeof globalThis.fetch,
|
|
267
|
-
|
|
268
|
-
get: () => storedChannel(),
|
|
269
|
-
set: vi.fn(),
|
|
270
|
-
},
|
|
321
|
+
channelStore: makeChannelStore([channelEntry()]).store,
|
|
271
322
|
})
|
|
272
323
|
|
|
273
324
|
await s.fetch('https://api.example.com/data')
|
|
274
325
|
|
|
275
|
-
|
|
326
|
+
// No persisted hint: the first request carries no Payment-Session header,
|
|
327
|
+
// and the channel is resumed from the entry index with a voucher.
|
|
328
|
+
expect(new Headers(mockFetch.mock.calls[0]?.[1]?.headers).get('Payment-Session')).toBeNull()
|
|
329
|
+
expect(posted[0]).toMatchObject({ action: 'voucher', channelId: storedChannelId })
|
|
276
330
|
})
|
|
277
331
|
|
|
278
|
-
test('
|
|
279
|
-
const set =
|
|
332
|
+
test('seeds a same-route HEAD snapshot into the entry index and resumes it', async () => {
|
|
333
|
+
const { store, set } = makeChannelStore()
|
|
334
|
+
const posted: SessionCredentialPayload[] = []
|
|
280
335
|
const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
|
|
281
336
|
const headers = new Headers(init?.headers)
|
|
282
337
|
if (init?.method === 'HEAD' && !headers.get(Constants.Headers.authorization)) {
|
|
@@ -306,7 +361,14 @@ describe('Session', () => {
|
|
|
306
361
|
}),
|
|
307
362
|
)
|
|
308
363
|
}
|
|
309
|
-
|
|
364
|
+
// The seeded snapshot is not sent as a first-request hint; the content
|
|
365
|
+
// request gets a 402 and resumes from the entry index with a voucher.
|
|
366
|
+
const authorization = headers.get(Constants.Headers.authorization)
|
|
367
|
+
const payload = authorization
|
|
368
|
+
? Credential.deserialize<SessionCredentialPayload>(authorization).payload
|
|
369
|
+
: undefined
|
|
370
|
+
if (payload) posted.push(payload)
|
|
371
|
+
if (!payload) return Promise.resolve(make402Response())
|
|
310
372
|
return Promise.resolve(makeOkResponse())
|
|
311
373
|
})
|
|
312
374
|
const s = sessionManager({
|
|
@@ -314,20 +376,16 @@ describe('Session', () => {
|
|
|
314
376
|
bootstrap: true,
|
|
315
377
|
client,
|
|
316
378
|
fetch: mockFetch as typeof globalThis.fetch,
|
|
317
|
-
|
|
318
|
-
get: () => null,
|
|
319
|
-
set,
|
|
320
|
-
},
|
|
379
|
+
channelStore: store,
|
|
321
380
|
})
|
|
322
381
|
|
|
323
382
|
const response = await s.fetch('https://api.example.com/data')
|
|
324
383
|
|
|
325
384
|
expect(response.status).toBe(200)
|
|
326
|
-
expect(response.channelId).toBeNull()
|
|
327
|
-
expect(s.channelId).toBeUndefined()
|
|
328
|
-
expect(s.cumulative).toBe(0n)
|
|
329
385
|
expect(set).toHaveBeenCalledWith(expect.objectContaining({ channelId: storedChannelId }))
|
|
330
|
-
expect(
|
|
386
|
+
expect(posted[0]).toMatchObject({ action: 'voucher', channelId: storedChannelId })
|
|
387
|
+
const contentCall = mockFetch.mock.calls.find((call) => call[1]?.method !== 'HEAD')
|
|
388
|
+
expect(new Headers(contentCall?.[1]?.headers).get('Payment-Session')).toBeNull()
|
|
331
389
|
})
|
|
332
390
|
|
|
333
391
|
test('does not answer non-zero bootstrap charge challenges', async () => {
|
|
@@ -374,28 +432,8 @@ describe('Session', () => {
|
|
|
374
432
|
expect(new Headers(mockFetch.mock.calls[1]?.[1]?.headers).get('Payment-Session')).toBeNull()
|
|
375
433
|
})
|
|
376
434
|
|
|
377
|
-
test('
|
|
378
|
-
const remove =
|
|
379
|
-
const staleClient = createClient({
|
|
380
|
-
account,
|
|
381
|
-
chain: { id: 4217 } as never,
|
|
382
|
-
transport: custom({
|
|
383
|
-
async request(args) {
|
|
384
|
-
if (args.method === 'eth_chainId') return '0x1079'
|
|
385
|
-
if (args.method === 'eth_getTransactionCount') return '0x0'
|
|
386
|
-
if (args.method === 'eth_estimateGas') return '0x5208'
|
|
387
|
-
if (args.method === 'eth_maxPriorityFeePerGas') return '0x1'
|
|
388
|
-
if (args.method === 'eth_getBlockByNumber') return { baseFeePerGas: '0x1' }
|
|
389
|
-
if (args.method === 'eth_call')
|
|
390
|
-
return encodeFunctionResult({
|
|
391
|
-
abi: escrowAbi,
|
|
392
|
-
functionName: 'getChannelState',
|
|
393
|
-
result: { settled: 0n, deposit: 0n, closeRequestedAt: 0 },
|
|
394
|
-
})
|
|
395
|
-
throw new Error(`unexpected rpc request: ${args.method}`)
|
|
396
|
-
},
|
|
397
|
-
}),
|
|
398
|
-
})
|
|
435
|
+
test('drops a stale stored channel the server rejects and retries with a fresh one', async () => {
|
|
436
|
+
const { store, delete: remove } = makeChannelStore([channelEntry()])
|
|
399
437
|
const postedPayloads: SessionCredentialPayload[] = []
|
|
400
438
|
const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
|
|
401
439
|
const headers = new Headers(init?.headers)
|
|
@@ -405,28 +443,25 @@ describe('Session', () => {
|
|
|
405
443
|
: undefined
|
|
406
444
|
if (payload) postedPayloads.push(payload)
|
|
407
445
|
|
|
408
|
-
if (init?.method === 'HEAD') return Promise.resolve(new Response(null, { status: 204 }))
|
|
409
446
|
if (!payload) return Promise.resolve(make402Response())
|
|
447
|
+
// Reject any reuse of the stale stored channel; accept a freshly opened one.
|
|
448
|
+
if (payload.channelId === storedChannelId)
|
|
449
|
+
return Promise.resolve(new Response('gone', { status: 500 }))
|
|
410
450
|
return Promise.resolve(makeOkResponse())
|
|
411
451
|
})
|
|
412
452
|
const s = sessionManager({
|
|
413
453
|
account,
|
|
414
|
-
|
|
415
|
-
client: staleClient,
|
|
454
|
+
client,
|
|
416
455
|
fetch: mockFetch as typeof globalThis.fetch,
|
|
417
456
|
maxDeposit: '10',
|
|
418
|
-
|
|
419
|
-
get: () => storedChannel(),
|
|
420
|
-
set: vi.fn(),
|
|
421
|
-
delete: remove,
|
|
422
|
-
},
|
|
457
|
+
channelStore: store,
|
|
423
458
|
})
|
|
424
459
|
|
|
425
460
|
const response = await s.fetch('https://api.example.com/data')
|
|
426
461
|
|
|
427
462
|
expect(response.status).toBe(200)
|
|
428
463
|
expect(remove).toHaveBeenCalledOnce()
|
|
429
|
-
expect(postedPayloads.map((payload) => payload.action)).toEqual(['open'])
|
|
464
|
+
expect(postedPayloads.map((payload) => payload.action)).toEqual(['voucher', 'open'])
|
|
430
465
|
expect(s.opened).toBe(true)
|
|
431
466
|
expect(s.channelId).not.toBe(storedChannelId)
|
|
432
467
|
})
|
|
@@ -461,10 +496,7 @@ describe('Session', () => {
|
|
|
461
496
|
account,
|
|
462
497
|
client,
|
|
463
498
|
fetch: mockFetch as typeof globalThis.fetch,
|
|
464
|
-
|
|
465
|
-
get: () => storedChannel(),
|
|
466
|
-
set: vi.fn(),
|
|
467
|
-
},
|
|
499
|
+
channelStore: makeChannelStore([channelEntry()]).store,
|
|
468
500
|
})
|
|
469
501
|
|
|
470
502
|
await s.fetch('https://api.example.com/data')
|
|
@@ -475,9 +507,70 @@ describe('Session', () => {
|
|
|
475
507
|
})
|
|
476
508
|
})
|
|
477
509
|
|
|
510
|
+
test('resumes a persisted channel across a restart via the entry index', async () => {
|
|
511
|
+
// A shared durable KV backing two manager instances simulates a restart:
|
|
512
|
+
// the second manager shares only what survived to disk.
|
|
513
|
+
const backend = new Map<string, string>()
|
|
514
|
+
const durableStore = () =>
|
|
515
|
+
createJsonChannelStore({
|
|
516
|
+
get: (key) => backend.get(key),
|
|
517
|
+
set: (key, value) => {
|
|
518
|
+
backend.set(key, value)
|
|
519
|
+
},
|
|
520
|
+
delete: (key) => {
|
|
521
|
+
backend.delete(key)
|
|
522
|
+
},
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
const openFetch = vi.fn((_input: RequestInfo | URL, init?: RequestInit) => {
|
|
526
|
+
const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
|
|
527
|
+
if (!authorization) return Promise.resolve(make402Response())
|
|
528
|
+
return Promise.resolve(makeOkResponse())
|
|
529
|
+
})
|
|
530
|
+
const first = sessionManager({
|
|
531
|
+
account,
|
|
532
|
+
client,
|
|
533
|
+
fetch: openFetch as typeof globalThis.fetch,
|
|
534
|
+
maxDeposit: '10',
|
|
535
|
+
channelStore: durableStore(),
|
|
536
|
+
})
|
|
537
|
+
await first.fetch('https://api.example.com/data')
|
|
538
|
+
const channelId = first.channelId
|
|
539
|
+
expect(channelId).toBeDefined()
|
|
540
|
+
|
|
541
|
+
// The durable channel entry must have reached the KV.
|
|
542
|
+
expect([...backend.keys()].some((key) => key.startsWith('chan:'))).toBe(true)
|
|
543
|
+
// No persistent hints are written; only the durable channel entry survives.
|
|
544
|
+
expect([...backend.keys()].some((key) => key.startsWith('hint:'))).toBe(false)
|
|
545
|
+
|
|
546
|
+
const posted: SessionCredentialPayload[] = []
|
|
547
|
+
const resumeFetch = vi.fn((_input: RequestInfo | URL, init?: RequestInit) => {
|
|
548
|
+
const authorization = new Headers(init?.headers).get(Constants.Headers.authorization)
|
|
549
|
+
const payload = authorization
|
|
550
|
+
? Credential.deserialize<SessionCredentialPayload>(authorization).payload
|
|
551
|
+
: undefined
|
|
552
|
+
if (payload) posted.push(payload)
|
|
553
|
+
if (!payload) return Promise.resolve(make402Response())
|
|
554
|
+
return Promise.resolve(makeOkResponse())
|
|
555
|
+
})
|
|
556
|
+
const restarted = sessionManager({
|
|
557
|
+
account,
|
|
558
|
+
client,
|
|
559
|
+
fetch: resumeFetch as typeof globalThis.fetch,
|
|
560
|
+
maxDeposit: '10',
|
|
561
|
+
channelStore: durableStore(),
|
|
562
|
+
})
|
|
563
|
+
await restarted.fetch('https://api.example.com/data')
|
|
564
|
+
|
|
565
|
+
// The first request carries no hint header; after the 402 the restarted
|
|
566
|
+
// manager resumes the persisted channel from the entry index with a
|
|
567
|
+
// voucher rather than opening a new one.
|
|
568
|
+
expect(new Headers(resumeFetch.mock.calls[0]?.[1]?.headers).get('Payment-Session')).toBeNull()
|
|
569
|
+
expect(posted[0]).toMatchObject({ action: 'voucher', channelId })
|
|
570
|
+
})
|
|
571
|
+
|
|
478
572
|
test('persists opened channels and deletes closed channels when supported', async () => {
|
|
479
|
-
const set =
|
|
480
|
-
const remove = vi.fn()
|
|
573
|
+
const { store, set, delete: remove } = makeChannelStore()
|
|
481
574
|
let callCount = 0
|
|
482
575
|
const mockFetch = vi.fn().mockImplementation((_input, init?: RequestInit) => {
|
|
483
576
|
const authorization = new Headers(init?.headers).get('Authorization')
|
|
@@ -527,11 +620,7 @@ describe('Session', () => {
|
|
|
527
620
|
client,
|
|
528
621
|
fetch: mockFetch as typeof globalThis.fetch,
|
|
529
622
|
maxDeposit: '10',
|
|
530
|
-
|
|
531
|
-
get: () => null,
|
|
532
|
-
set,
|
|
533
|
-
delete: remove,
|
|
534
|
-
},
|
|
623
|
+
channelStore: store,
|
|
535
624
|
})
|
|
536
625
|
|
|
537
626
|
await s.fetch('https://api.example.com/data')
|