mppx 0.3.3 → 0.3.4
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/dist/Errors.d.ts +7 -7
- package/dist/Errors.d.ts.map +1 -1
- package/dist/Errors.js +7 -7
- package/dist/Errors.js.map +1 -1
- package/dist/cli.js +98 -64
- package/dist/cli.js.map +1 -1
- package/dist/internal/env.js +2 -2
- package/dist/internal/env.js.map +1 -1
- package/dist/tempo/client/ChannelOps.d.ts +5 -5
- package/dist/tempo/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/client/ChannelOps.js +3 -3
- package/dist/tempo/client/ChannelOps.js.map +1 -1
- package/dist/tempo/client/Session.d.ts +2 -2
- package/dist/tempo/client/Session.d.ts.map +1 -1
- package/dist/tempo/client/Session.js +3 -3
- package/dist/tempo/client/Session.js.map +1 -1
- package/dist/tempo/client/SessionManager.d.ts +4 -4
- package/dist/tempo/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/client/SessionManager.js +4 -4
- package/dist/tempo/client/SessionManager.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -1
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -1
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/server/Charge.js +1 -1
- package/dist/tempo/server/Charge.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +1 -1
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Session.d.ts +8 -8
- package/dist/tempo/server/Session.d.ts.map +1 -1
- package/dist/tempo/server/Session.js +24 -24
- package/dist/tempo/server/Session.js.map +1 -1
- package/dist/tempo/server/index.d.ts +2 -2
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +2 -2
- package/dist/tempo/server/index.js.map +1 -1
- package/dist/tempo/server/internal/transport.d.ts +4 -4
- package/dist/tempo/server/internal/transport.d.ts.map +1 -1
- package/dist/tempo/server/internal/transport.js +3 -3
- package/dist/tempo/server/internal/transport.js.map +1 -1
- package/dist/tempo/session/Chain.d.ts.map +1 -0
- package/dist/tempo/session/Chain.js.map +1 -0
- package/dist/tempo/session/Channel.d.ts.map +1 -0
- package/dist/tempo/session/Channel.js.map +1 -0
- package/dist/tempo/session/ChannelStore.d.ts.map +1 -0
- package/dist/tempo/session/ChannelStore.js.map +1 -0
- package/dist/tempo/session/Receipt.d.ts +22 -0
- package/dist/tempo/session/Receipt.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Receipt.js +6 -6
- package/dist/tempo/session/Receipt.js.map +1 -0
- package/dist/tempo/{stream → session}/Sse.d.ts +7 -7
- package/dist/tempo/session/Sse.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Sse.js +4 -4
- package/dist/tempo/session/Sse.js.map +1 -0
- package/dist/tempo/{stream → session}/Types.d.ts +4 -4
- package/dist/tempo/session/Types.d.ts.map +1 -0
- package/dist/tempo/{stream → session}/Types.js.map +1 -1
- package/dist/tempo/session/Voucher.d.ts.map +1 -0
- package/dist/tempo/session/Voucher.js.map +1 -0
- package/dist/tempo/{stream → session}/escrow.abi.d.ts.map +1 -1
- package/dist/tempo/session/escrow.abi.js.map +1 -0
- package/dist/tempo/session/index.d.ts.map +1 -0
- package/dist/tempo/session/index.js.map +1 -0
- package/package.json +1 -1
- package/src/Errors.test.ts +10 -10
- package/src/Errors.ts +7 -7
- package/src/cli.test.ts +3 -3
- package/src/cli.ts +125 -70
- package/src/internal/env.ts +2 -2
- package/src/middlewares/express.test.ts +1 -1
- package/src/middlewares/hono.test.ts +1 -1
- package/src/middlewares/nextjs.test.ts +1 -1
- package/src/server/Transport.test.ts +1 -1
- package/src/tempo/client/ChannelOps.test.ts +2 -2
- package/src/tempo/client/ChannelOps.ts +8 -8
- package/src/tempo/client/Session.test.ts +3 -3
- package/src/tempo/client/Session.ts +9 -9
- package/src/tempo/client/SessionManager.test.ts +3 -3
- package/src/tempo/client/SessionManager.ts +9 -9
- package/src/tempo/index.ts +1 -1
- package/src/tempo/server/Charge.ts +1 -1
- package/src/tempo/server/Session.test.ts +9 -9
- package/src/tempo/server/Session.ts +47 -47
- package/src/tempo/server/Sse.test.ts +3 -3
- package/src/tempo/server/index.ts +2 -2
- package/src/tempo/server/internal/transport.ts +6 -6
- package/src/tempo/{stream → session}/Chain.test.ts +1 -1
- package/src/tempo/{stream → session}/Receipt.test.ts +16 -12
- package/src/tempo/{stream → session}/Receipt.ts +9 -9
- package/src/tempo/{stream → session}/Sse.test.ts +5 -5
- package/src/tempo/{stream → session}/Sse.ts +11 -11
- package/src/tempo/{stream → session}/Types.ts +4 -4
- package/dist/tempo/stream/Chain.d.ts.map +0 -1
- package/dist/tempo/stream/Chain.js.map +0 -1
- package/dist/tempo/stream/Channel.d.ts.map +0 -1
- package/dist/tempo/stream/Channel.js.map +0 -1
- package/dist/tempo/stream/ChannelStore.d.ts.map +0 -1
- package/dist/tempo/stream/ChannelStore.js.map +0 -1
- package/dist/tempo/stream/Receipt.d.ts +0 -22
- package/dist/tempo/stream/Receipt.d.ts.map +0 -1
- package/dist/tempo/stream/Receipt.js.map +0 -1
- package/dist/tempo/stream/Sse.d.ts.map +0 -1
- package/dist/tempo/stream/Sse.js.map +0 -1
- package/dist/tempo/stream/Types.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.d.ts.map +0 -1
- package/dist/tempo/stream/Voucher.js.map +0 -1
- package/dist/tempo/stream/escrow.abi.js.map +0 -1
- package/dist/tempo/stream/index.d.ts.map +0 -1
- package/dist/tempo/stream/index.js.map +0 -1
- /package/dist/tempo/{stream → session}/Chain.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Chain.js +0 -0
- /package/dist/tempo/{stream → session}/Channel.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Channel.js +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.d.ts +0 -0
- /package/dist/tempo/{stream → session}/ChannelStore.js +0 -0
- /package/dist/tempo/{stream → session}/Types.js +0 -0
- /package/dist/tempo/{stream → session}/Voucher.d.ts +0 -0
- /package/dist/tempo/{stream → session}/Voucher.js +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.d.ts +0 -0
- /package/dist/tempo/{stream → session}/escrow.abi.js +0 -0
- /package/dist/tempo/{stream → session}/index.d.ts +0 -0
- /package/dist/tempo/{stream → session}/index.js +0 -0
- /package/src/tempo/{stream → session}/Chain.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.test.ts +0 -0
- /package/src/tempo/{stream → session}/Channel.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.test.ts +0 -0
- /package/src/tempo/{stream → session}/ChannelStore.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.test.ts +0 -0
- /package/src/tempo/{stream → session}/Voucher.ts +0 -0
- /package/src/tempo/{stream → session}/escrow.abi.ts +0 -0
- /package/src/tempo/{stream → session}/index.ts +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Hex } from 'viem'
|
|
2
2
|
import { describe, expect, test, vi } from 'vitest'
|
|
3
3
|
import * as Challenge from '../../Challenge.js'
|
|
4
|
-
import { formatNeedVoucherEvent, parseEvent } from '../
|
|
5
|
-
import type { NeedVoucherEvent,
|
|
4
|
+
import { formatNeedVoucherEvent, parseEvent } from '../session/Sse.js'
|
|
5
|
+
import type { NeedVoucherEvent, SessionReceipt } from '../session/Types.js'
|
|
6
6
|
import { sessionManager } from './SessionManager.js'
|
|
7
7
|
|
|
8
8
|
const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
|
|
@@ -139,7 +139,7 @@ describe('Session', () => {
|
|
|
139
139
|
acceptedCumulative: '2000000',
|
|
140
140
|
spent: '2000000',
|
|
141
141
|
units: 2,
|
|
142
|
-
} satisfies
|
|
142
|
+
} satisfies SessionReceipt)}\n\n`,
|
|
143
143
|
]
|
|
144
144
|
|
|
145
145
|
let callCount = 0
|
|
@@ -4,9 +4,9 @@ import type * as Challenge from '../../Challenge.js'
|
|
|
4
4
|
import * as Fetch from '../../client/internal/Fetch.js'
|
|
5
5
|
import type * as Account from '../../viem/Account.js'
|
|
6
6
|
import type * as Client from '../../viem/Client.js'
|
|
7
|
-
import {
|
|
8
|
-
import { parseEvent } from '../
|
|
9
|
-
import type {
|
|
7
|
+
import { deserializeSessionReceipt } from '../session/Receipt.js'
|
|
8
|
+
import { parseEvent } from '../session/Sse.js'
|
|
9
|
+
import type { SessionReceipt } from '../session/Types.js'
|
|
10
10
|
import type { ChannelEntry } from './ChannelOps.js'
|
|
11
11
|
import { session as sessionPlugin } from './Session.js'
|
|
12
12
|
|
|
@@ -20,15 +20,15 @@ export type SessionManager = {
|
|
|
20
20
|
sse(
|
|
21
21
|
input: RequestInfo | URL,
|
|
22
22
|
init?: RequestInit & {
|
|
23
|
-
onReceipt?: ((receipt:
|
|
23
|
+
onReceipt?: ((receipt: SessionReceipt) => void) | undefined
|
|
24
24
|
signal?: AbortSignal | undefined
|
|
25
25
|
},
|
|
26
26
|
): Promise<AsyncIterable<string>>
|
|
27
|
-
close(): Promise<
|
|
27
|
+
close(): Promise<SessionReceipt | undefined>
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export type PaymentResponse = Response & {
|
|
31
|
-
receipt:
|
|
31
|
+
receipt: SessionReceipt | null
|
|
32
32
|
challenge: Challenge.Challenge | null
|
|
33
33
|
channelId: Hex.Hex | null
|
|
34
34
|
cumulative: bigint
|
|
@@ -83,7 +83,7 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
|
|
|
83
83
|
|
|
84
84
|
function toPaymentResponse(response: Response): PaymentResponse {
|
|
85
85
|
const receiptHeader = response.headers.get('Payment-Receipt')
|
|
86
|
-
const receipt = receiptHeader ?
|
|
86
|
+
const receipt = receiptHeader ? deserializeSessionReceipt(receiptHeader) : null
|
|
87
87
|
return Object.assign(response, {
|
|
88
88
|
receipt,
|
|
89
89
|
challenge: lastChallenge,
|
|
@@ -241,14 +241,14 @@ export function sessionManager(parameters: sessionManager.Parameters): SessionMa
|
|
|
241
241
|
},
|
|
242
242
|
})
|
|
243
243
|
|
|
244
|
-
let receipt:
|
|
244
|
+
let receipt: SessionReceipt | undefined
|
|
245
245
|
if (lastUrl) {
|
|
246
246
|
const response = await fetchFn(lastUrl, {
|
|
247
247
|
method: 'POST',
|
|
248
248
|
headers: { Authorization: credential },
|
|
249
249
|
})
|
|
250
250
|
const receiptHeader = response.headers.get('Payment-Receipt')
|
|
251
|
-
if (receiptHeader) receipt =
|
|
251
|
+
if (receiptHeader) receipt = deserializeSessionReceipt(receiptHeader)
|
|
252
252
|
}
|
|
253
253
|
|
|
254
254
|
return receipt
|
package/src/tempo/index.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export * as Methods from './Methods.js'
|
|
2
|
-
export * as
|
|
2
|
+
export * as Session from './session/index.js'
|
|
@@ -67,7 +67,7 @@ export function charge<const parameters extends charge.Parameters>(
|
|
|
67
67
|
recipient,
|
|
68
68
|
} as unknown as Defaults,
|
|
69
69
|
|
|
70
|
-
// TODO: dedupe `{charge,
|
|
70
|
+
// TODO: dedupe `{charge,session}.request`
|
|
71
71
|
async request({ credential, request }) {
|
|
72
72
|
const chainId = await (async () => {
|
|
73
73
|
if (request.chainId) return request.chainId
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
signOpenChannel,
|
|
9
9
|
signTopUpChannel,
|
|
10
10
|
topUpChannel,
|
|
11
|
-
} from '~test/tempo/
|
|
11
|
+
} from '~test/tempo/session.js'
|
|
12
12
|
import { accounts, asset, chain, client, fundAccount, http } from '~test/tempo/viem.js'
|
|
13
13
|
import {
|
|
14
14
|
ChannelClosedError,
|
|
@@ -18,9 +18,9 @@ import {
|
|
|
18
18
|
} from '../../Errors.js'
|
|
19
19
|
import * as Store from '../../Store.js'
|
|
20
20
|
import type * as Methods from '../Methods.js'
|
|
21
|
-
import * as ChannelStore from '../
|
|
22
|
-
import type {
|
|
23
|
-
import { signVoucher } from '../
|
|
21
|
+
import * as ChannelStore from '../session/ChannelStore.js'
|
|
22
|
+
import type { SessionReceipt } from '../session/Types.js'
|
|
23
|
+
import { signVoucher } from '../session/Voucher.js'
|
|
24
24
|
import { charge, session, settle } from './Session.js'
|
|
25
25
|
|
|
26
26
|
const payer = accounts[2]
|
|
@@ -367,7 +367,7 @@ describe('session', () => {
|
|
|
367
367
|
})
|
|
368
368
|
|
|
369
369
|
expect(receipt.status).toBe('success')
|
|
370
|
-
expect((receipt as
|
|
370
|
+
expect((receipt as SessionReceipt).acceptedCumulative).toBe('1000000')
|
|
371
371
|
})
|
|
372
372
|
|
|
373
373
|
test('rejects voucher exceeding deposit', async () => {
|
|
@@ -520,7 +520,7 @@ describe('session', () => {
|
|
|
520
520
|
},
|
|
521
521
|
},
|
|
522
522
|
request: makeRequest(),
|
|
523
|
-
})) as
|
|
523
|
+
})) as SessionReceipt
|
|
524
524
|
|
|
525
525
|
expect(receipt.status).toBe('success')
|
|
526
526
|
expect(receipt.spent).toBe('800000')
|
|
@@ -748,7 +748,7 @@ describe('session', () => {
|
|
|
748
748
|
})
|
|
749
749
|
|
|
750
750
|
expect(receipt.status).toBe('success')
|
|
751
|
-
expect((receipt as
|
|
751
|
+
expect((receipt as SessionReceipt).txHash).toMatch(/^0x/)
|
|
752
752
|
|
|
753
753
|
const ch = await store.getChannel(channelId)
|
|
754
754
|
expect(ch!.finalized).toBe(true)
|
|
@@ -1110,7 +1110,7 @@ describe('session', () => {
|
|
|
1110
1110
|
currency: asset,
|
|
1111
1111
|
escrowContract,
|
|
1112
1112
|
getClient: () => client,
|
|
1113
|
-
|
|
1113
|
+
sse: true,
|
|
1114
1114
|
}),
|
|
1115
1115
|
],
|
|
1116
1116
|
realm: 'api.example.com',
|
|
@@ -1147,7 +1147,7 @@ describe('session', () => {
|
|
|
1147
1147
|
}
|
|
1148
1148
|
})
|
|
1149
1149
|
|
|
1150
|
-
test('behavior: non-
|
|
1150
|
+
test('behavior: non-SSE session withReceipt only accepts Response', async () => {
|
|
1151
1151
|
const handler = Mppx_server.create({
|
|
1152
1152
|
methods: [
|
|
1153
1153
|
tempo_server.session({
|
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
* Server-side session payment method for request/response flows.
|
|
3
3
|
*
|
|
4
4
|
* Handles the full channel lifecycle (open, voucher, top-up, close) and
|
|
5
|
-
* one-shot settlement. Each incoming request carries a
|
|
5
|
+
* one-shot settlement. Each incoming request carries a session credential
|
|
6
6
|
* with a cumulative voucher that the server validates and records.
|
|
7
7
|
*
|
|
8
8
|
* Use `session()` for standard HTTP request/response patterns where each
|
|
9
9
|
* request is a discrete paid unit (for example, a page scrape or API call).
|
|
10
10
|
* For long-lived connections that emit multiple paid events over a single
|
|
11
|
-
* request, use {@link ../
|
|
11
|
+
* request, use {@link ../session/Sse} instead.
|
|
12
12
|
*/
|
|
13
13
|
import {
|
|
14
14
|
type Address,
|
|
@@ -44,15 +44,15 @@ import {
|
|
|
44
44
|
getOnChainChannel,
|
|
45
45
|
type OnChainChannel,
|
|
46
46
|
settleOnChain,
|
|
47
|
-
} from '../
|
|
48
|
-
import * as ChannelStore from '../
|
|
49
|
-
import {
|
|
50
|
-
import type {
|
|
51
|
-
import { parseVoucherFromPayload, verifyVoucher } from '../
|
|
47
|
+
} from '../session/Chain.js'
|
|
48
|
+
import * as ChannelStore from '../session/ChannelStore.js'
|
|
49
|
+
import { createSessionReceipt } from '../session/Receipt.js'
|
|
50
|
+
import type { SessionCredentialPayload, SessionReceipt, SignedVoucher } from '../session/Types.js'
|
|
51
|
+
import { parseVoucherFromPayload, verifyVoucher } from '../session/Voucher.js'
|
|
52
52
|
import * as Transport from './internal/transport.js'
|
|
53
53
|
|
|
54
|
-
/** Challenge methodDetails shape for
|
|
55
|
-
type
|
|
54
|
+
/** Challenge methodDetails shape for session methods. */
|
|
55
|
+
type SessionMethodDetails = {
|
|
56
56
|
escrowContract: Address
|
|
57
57
|
chainId: number
|
|
58
58
|
channelId?: Hex | undefined
|
|
@@ -61,7 +61,7 @@ type StreamMethodDetails = {
|
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
* Creates a
|
|
64
|
+
* Creates a session payment server using the Method.toServer() pattern.
|
|
65
65
|
*
|
|
66
66
|
* @example
|
|
67
67
|
* ```ts
|
|
@@ -101,11 +101,11 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
101
101
|
})
|
|
102
102
|
const { account, recipient, feePayer } = Account.resolve(parameters)
|
|
103
103
|
|
|
104
|
-
type Transport = parameters['
|
|
105
|
-
const transport = parameters.
|
|
104
|
+
type Transport = parameters['sse'] extends false | undefined ? undefined : Transport.Sse
|
|
105
|
+
const transport = parameters.sse
|
|
106
106
|
? Transport.sse({
|
|
107
107
|
store,
|
|
108
|
-
...(typeof parameters.
|
|
108
|
+
...(typeof parameters.sse === 'object' ? parameters.sse : undefined),
|
|
109
109
|
})
|
|
110
110
|
: undefined
|
|
111
111
|
|
|
@@ -122,7 +122,7 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
122
122
|
|
|
123
123
|
transport: transport as never,
|
|
124
124
|
|
|
125
|
-
// TODO: dedupe `{charge,
|
|
125
|
+
// TODO: dedupe `{charge,session}.request`
|
|
126
126
|
async request({ credential, request }) {
|
|
127
127
|
// Extract chainId from request or default.
|
|
128
128
|
const chainId = await (async () => {
|
|
@@ -160,9 +160,9 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
160
160
|
},
|
|
161
161
|
|
|
162
162
|
async verify({ credential }) {
|
|
163
|
-
const { challenge, payload } = credential as Credential.Credential<
|
|
163
|
+
const { challenge, payload } = credential as Credential.Credential<SessionCredentialPayload>
|
|
164
164
|
|
|
165
|
-
const methodDetails = challenge.request.methodDetails as
|
|
165
|
+
const methodDetails = challenge.request.methodDetails as SessionMethodDetails
|
|
166
166
|
const client = await getClient({ chainId: methodDetails.chainId })
|
|
167
167
|
|
|
168
168
|
const resolvedFeePayer = methodDetails.feePayer === true ? feePayer : undefined
|
|
@@ -171,11 +171,11 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
171
171
|
? BigInt(methodDetails.minVoucherDelta)
|
|
172
172
|
: minVoucherDelta
|
|
173
173
|
|
|
174
|
-
let
|
|
174
|
+
let sessionReceipt: SessionReceipt
|
|
175
175
|
|
|
176
176
|
switch (payload.action) {
|
|
177
177
|
case 'open':
|
|
178
|
-
|
|
178
|
+
sessionReceipt = await handleOpen(
|
|
179
179
|
store,
|
|
180
180
|
client,
|
|
181
181
|
challenge,
|
|
@@ -186,7 +186,7 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
186
186
|
break
|
|
187
187
|
|
|
188
188
|
case 'topUp':
|
|
189
|
-
|
|
189
|
+
sessionReceipt = await handleTopUp(
|
|
190
190
|
store,
|
|
191
191
|
client,
|
|
192
192
|
challenge,
|
|
@@ -197,7 +197,7 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
197
197
|
break
|
|
198
198
|
|
|
199
199
|
case 'voucher':
|
|
200
|
-
|
|
200
|
+
sessionReceipt = await handleVoucher(
|
|
201
201
|
store,
|
|
202
202
|
client,
|
|
203
203
|
effectiveMinVoucherDelta,
|
|
@@ -208,7 +208,7 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
208
208
|
break
|
|
209
209
|
|
|
210
210
|
case 'close':
|
|
211
|
-
|
|
211
|
+
sessionReceipt = await handleClose(
|
|
212
212
|
store,
|
|
213
213
|
client,
|
|
214
214
|
challenge,
|
|
@@ -224,7 +224,7 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
224
224
|
})
|
|
225
225
|
}
|
|
226
226
|
|
|
227
|
-
return
|
|
227
|
+
return sessionReceipt
|
|
228
228
|
},
|
|
229
229
|
|
|
230
230
|
// This hook acts as a gate: when it returns a Response, `withReceipt()`
|
|
@@ -236,14 +236,14 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
236
236
|
// return 204 regardless of request method.
|
|
237
237
|
//
|
|
238
238
|
// Voucher POSTs are gated only when they have no request body, which
|
|
239
|
-
// signals a mid-
|
|
239
|
+
// signals a mid-session voucher update (the client is just topping up
|
|
240
240
|
// the channel balance). Voucher POSTs WITH a body are content requests
|
|
241
241
|
// (e.g., an API call to a POST endpoint) and fall through to the
|
|
242
242
|
// user's handler. GET requests with vouchers always fall through so
|
|
243
243
|
// auto-mode clients (whose fetch wrapper bundles open+voucher into a
|
|
244
244
|
// single GET retry) receive content as expected.
|
|
245
245
|
respond({ credential, input }) {
|
|
246
|
-
const { payload } = credential as Credential.Credential<
|
|
246
|
+
const { payload } = credential as Credential.Credential<SessionCredentialPayload>
|
|
247
247
|
|
|
248
248
|
if (payload.action === 'close') return new Response(null, { status: 204 })
|
|
249
249
|
|
|
@@ -253,7 +253,7 @@ export function session<const parameters extends session.Parameters>(p?: paramet
|
|
|
253
253
|
const isVoucher = payload.action === 'voucher'
|
|
254
254
|
if (!isVoucher) return undefined
|
|
255
255
|
|
|
256
|
-
// Only gate voucher POSTs with no body (mid-
|
|
256
|
+
// Only gate voucher POSTs with no body (mid-session balance updates).
|
|
257
257
|
// POSTs with a body are content requests that should reach the handler.
|
|
258
258
|
if (input.method !== 'POST') return undefined
|
|
259
259
|
const contentLength = input.headers.get('content-length')
|
|
@@ -279,10 +279,10 @@ export declare namespace session {
|
|
|
279
279
|
* Enable SSE streaming.
|
|
280
280
|
*
|
|
281
281
|
* Pass `true` to enable with defaults, or an options object
|
|
282
|
-
* to configure
|
|
282
|
+
* to configure SSE (e.g. `{ poll: true }` for
|
|
283
283
|
* Cloudflare Workers compatibility).
|
|
284
284
|
*/
|
|
285
|
-
|
|
285
|
+
sse?: boolean | Transport.sse.Options | undefined
|
|
286
286
|
/** Testnet mode. */
|
|
287
287
|
testnet?: boolean | undefined
|
|
288
288
|
} & Account.resolve.Parameters &
|
|
@@ -332,7 +332,7 @@ export async function settle(
|
|
|
332
332
|
/**
|
|
333
333
|
* Charge against a channel's balance.
|
|
334
334
|
*
|
|
335
|
-
* Exported so consumers can deduct from a channel outside the `
|
|
335
|
+
* Exported so consumers can deduct from a channel outside the `session()`
|
|
336
336
|
* handler (e.g., custom middleware, the SSE `serve()` loop, or direct tests).
|
|
337
337
|
*
|
|
338
338
|
* Delegates to the shared `deductFromChannel` atomic helper and translates
|
|
@@ -403,8 +403,8 @@ async function verifyAndAcceptVoucher(parameters: {
|
|
|
403
403
|
channelId: Hex
|
|
404
404
|
voucher: SignedVoucher
|
|
405
405
|
onChain: OnChainChannel
|
|
406
|
-
methodDetails:
|
|
407
|
-
}): Promise<
|
|
406
|
+
methodDetails: SessionMethodDetails
|
|
407
|
+
}): Promise<SessionReceipt> {
|
|
408
408
|
const { store, minVoucherDelta, challenge, channel, channelId, voucher, onChain, methodDetails } =
|
|
409
409
|
parameters
|
|
410
410
|
|
|
@@ -426,7 +426,7 @@ async function verifyAndAcceptVoucher(parameters: {
|
|
|
426
426
|
}
|
|
427
427
|
|
|
428
428
|
if (voucher.cumulativeAmount <= channel.highestVoucherAmount) {
|
|
429
|
-
return
|
|
429
|
+
return createSessionReceipt({
|
|
430
430
|
challengeId: challenge.id,
|
|
431
431
|
channelId,
|
|
432
432
|
acceptedCumulative: channel.highestVoucherAmount,
|
|
@@ -467,7 +467,7 @@ async function verifyAndAcceptVoucher(parameters: {
|
|
|
467
467
|
})
|
|
468
468
|
if (!updated) throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
469
469
|
|
|
470
|
-
return
|
|
470
|
+
return createSessionReceipt({
|
|
471
471
|
challengeId: challenge.id,
|
|
472
472
|
channelId,
|
|
473
473
|
acceptedCumulative: updated.highestVoucherAmount,
|
|
@@ -483,10 +483,10 @@ async function handleOpen(
|
|
|
483
483
|
store: ChannelStore.ChannelStore,
|
|
484
484
|
client: viem_Client,
|
|
485
485
|
challenge: Challenge.Challenge,
|
|
486
|
-
payload:
|
|
487
|
-
methodDetails:
|
|
486
|
+
payload: SessionCredentialPayload & { action: 'open' },
|
|
487
|
+
methodDetails: SessionMethodDetails,
|
|
488
488
|
feePayer: viem_Account | undefined,
|
|
489
|
-
): Promise<
|
|
489
|
+
): Promise<SessionReceipt> {
|
|
490
490
|
const voucher = parseVoucherFromPayload(
|
|
491
491
|
payload.channelId,
|
|
492
492
|
payload.cumulativeAmount,
|
|
@@ -579,7 +579,7 @@ async function handleOpen(
|
|
|
579
579
|
|
|
580
580
|
if (!updated) throw new VerificationFailedError({ reason: 'failed to create channel' })
|
|
581
581
|
|
|
582
|
-
return
|
|
582
|
+
return createSessionReceipt({
|
|
583
583
|
challengeId: challenge.id,
|
|
584
584
|
channelId: payload.channelId,
|
|
585
585
|
acceptedCumulative: updated.highestVoucherAmount,
|
|
@@ -600,10 +600,10 @@ async function handleTopUp(
|
|
|
600
600
|
store: ChannelStore.ChannelStore,
|
|
601
601
|
client: viem_Client,
|
|
602
602
|
challenge: Challenge.Challenge,
|
|
603
|
-
payload:
|
|
604
|
-
methodDetails:
|
|
603
|
+
payload: SessionCredentialPayload & { action: 'topUp' },
|
|
604
|
+
methodDetails: SessionMethodDetails,
|
|
605
605
|
feePayer: viem_Account | undefined,
|
|
606
|
-
): Promise<
|
|
606
|
+
): Promise<SessionReceipt> {
|
|
607
607
|
const channel = await store.getChannel(payload.channelId)
|
|
608
608
|
if (!channel) {
|
|
609
609
|
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
@@ -626,7 +626,7 @@ async function handleTopUp(
|
|
|
626
626
|
return { ...current, deposit: onChainDeposit }
|
|
627
627
|
})
|
|
628
628
|
|
|
629
|
-
return
|
|
629
|
+
return createSessionReceipt({
|
|
630
630
|
challengeId: challenge.id,
|
|
631
631
|
channelId: payload.channelId,
|
|
632
632
|
acceptedCumulative: updated?.highestVoucherAmount ?? channel.highestVoucherAmount,
|
|
@@ -643,9 +643,9 @@ async function handleVoucher(
|
|
|
643
643
|
_client: viem_Client,
|
|
644
644
|
minVoucherDelta: bigint,
|
|
645
645
|
challenge: Challenge.Challenge,
|
|
646
|
-
payload:
|
|
647
|
-
methodDetails:
|
|
648
|
-
): Promise<
|
|
646
|
+
payload: SessionCredentialPayload & { action: 'voucher' },
|
|
647
|
+
methodDetails: SessionMethodDetails,
|
|
648
|
+
): Promise<SessionReceipt> {
|
|
649
649
|
const channel = await store.getChannel(payload.channelId)
|
|
650
650
|
if (!channel) {
|
|
651
651
|
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
@@ -696,10 +696,10 @@ async function handleClose(
|
|
|
696
696
|
store: ChannelStore.ChannelStore,
|
|
697
697
|
client: viem_Client,
|
|
698
698
|
challenge: Challenge.Challenge,
|
|
699
|
-
payload:
|
|
700
|
-
methodDetails:
|
|
699
|
+
payload: SessionCredentialPayload & { action: 'close' },
|
|
700
|
+
methodDetails: SessionMethodDetails,
|
|
701
701
|
account?: viem_Account,
|
|
702
|
-
): Promise<
|
|
702
|
+
): Promise<SessionReceipt> {
|
|
703
703
|
const channel = await store.getChannel(payload.channelId)
|
|
704
704
|
if (!channel) {
|
|
705
705
|
throw new ChannelNotFoundError({ reason: 'channel not found' })
|
|
@@ -762,7 +762,7 @@ async function handleClose(
|
|
|
762
762
|
}
|
|
763
763
|
})
|
|
764
764
|
|
|
765
|
-
return
|
|
765
|
+
return createSessionReceipt({
|
|
766
766
|
challengeId: challenge.id,
|
|
767
767
|
channelId: payload.channelId,
|
|
768
768
|
acceptedCumulative: voucher.cumulativeAmount,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Address, Hex } from 'viem'
|
|
2
2
|
import { describe, expect, test } from 'vitest'
|
|
3
|
-
import type * as ChannelStore from '../
|
|
4
|
-
import { serve, toResponse } from '../
|
|
3
|
+
import type * as ChannelStore from '../session/ChannelStore.js'
|
|
4
|
+
import { serve, toResponse } from '../session/Sse.js'
|
|
5
5
|
|
|
6
6
|
const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
|
|
7
7
|
const challengeId = 'test-challenge-id'
|
|
@@ -58,7 +58,7 @@ async function readStream(stream: ReadableStream<Uint8Array>): Promise<string> {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
describe('Sse.serve', () => {
|
|
61
|
-
test('emits message events for each yielded value (
|
|
61
|
+
test('emits message events for each yielded value (SessionController)', async () => {
|
|
62
62
|
const store = memoryStore()
|
|
63
63
|
await seedChannel(store, 3000000n)
|
|
64
64
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export * as ChannelStore from '../
|
|
2
|
-
export * as Sse from '../
|
|
1
|
+
export * as ChannelStore from '../session/ChannelStore.js'
|
|
2
|
+
export * as Sse from '../session/Sse.js'
|
|
3
3
|
export { charge } from './Charge.js'
|
|
4
4
|
export { tempo } from './Methods.js'
|
|
5
5
|
export { session, settle } from './Session.js'
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
* @internal
|
|
7
7
|
*/
|
|
8
8
|
import * as Transport from '../../../server/Transport.js'
|
|
9
|
-
import type * as ChannelStore from '../../
|
|
10
|
-
import * as Sse_core from '../../
|
|
9
|
+
import type * as ChannelStore from '../../session/ChannelStore.js'
|
|
10
|
+
import * as Sse_core from '../../session/Sse.js'
|
|
11
11
|
|
|
12
|
-
/** SSE transport with Tempo
|
|
13
|
-
export type Sse = Transport.Sse<Sse_core.
|
|
12
|
+
/** SSE transport with Tempo session controller. */
|
|
13
|
+
export type Sse = Transport.Sse<Sse_core.SessionController>
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Creates a Tempo-metered SSE transport.
|
|
@@ -46,7 +46,7 @@ export function sse(options: sse.Options & { store: ChannelStore.ChannelStore })
|
|
|
46
46
|
const ctx = Sse_core.fromRequest(request)
|
|
47
47
|
contextMap.set(ctx.challengeId, { ...ctx, signal: request.signal })
|
|
48
48
|
} catch {
|
|
49
|
-
// ignore — non-SSE credentials won't have
|
|
49
|
+
// ignore — non-SSE credentials won't have session context
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
return credential
|
|
@@ -72,7 +72,7 @@ export function sse(options: sse.Options & { store: ChannelStore.ChannelStore })
|
|
|
72
72
|
contextMap.delete(challengeId)
|
|
73
73
|
|
|
74
74
|
// Pass async generator functions directly so Sse.serve gives them
|
|
75
|
-
// a
|
|
75
|
+
// a SessionController for manual charge(). Pass raw AsyncIterables
|
|
76
76
|
// as-is so Sse.serve auto-charges per yielded value.
|
|
77
77
|
const generate: Sse_core.serve.Options['generate'] = isAsyncGeneratorFunction(resolved)
|
|
78
78
|
? (resolved as Sse_core.serve.Options['generate'])
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import type { Hex } from 'viem'
|
|
2
2
|
import { describe, expect, test } from 'vitest'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createSessionReceipt,
|
|
5
|
+
deserializeSessionReceipt,
|
|
6
|
+
serializeSessionReceipt,
|
|
7
|
+
} from './Receipt.js'
|
|
4
8
|
|
|
5
9
|
const channelId = '0x0000000000000000000000000000000000000000000000000000000000000001' as Hex
|
|
6
10
|
|
|
7
11
|
describe('Receipt', () => {
|
|
8
|
-
test('
|
|
9
|
-
const receipt =
|
|
12
|
+
test('createSessionReceipt', () => {
|
|
13
|
+
const receipt = createSessionReceipt({
|
|
10
14
|
challengeId: 'test-challenge-id',
|
|
11
15
|
channelId,
|
|
12
16
|
acceptedCumulative: 5000000n,
|
|
@@ -27,9 +31,9 @@ describe('Receipt', () => {
|
|
|
27
31
|
expect(receipt.txHash).toBeUndefined()
|
|
28
32
|
})
|
|
29
33
|
|
|
30
|
-
test('
|
|
34
|
+
test('createSessionReceipt with txHash', () => {
|
|
31
35
|
const txHash = '0xabcdef' as Hex
|
|
32
|
-
const receipt =
|
|
36
|
+
const receipt = createSessionReceipt({
|
|
33
37
|
challengeId: 'test-challenge-id',
|
|
34
38
|
channelId,
|
|
35
39
|
acceptedCumulative: 5000000n,
|
|
@@ -41,8 +45,8 @@ describe('Receipt', () => {
|
|
|
41
45
|
expect(receipt.units).toBeUndefined()
|
|
42
46
|
})
|
|
43
47
|
|
|
44
|
-
test('
|
|
45
|
-
const receipt =
|
|
48
|
+
test('createSessionReceipt omits optional fields when undefined', () => {
|
|
49
|
+
const receipt = createSessionReceipt({
|
|
46
50
|
challengeId: 'test-challenge-id',
|
|
47
51
|
channelId,
|
|
48
52
|
acceptedCumulative: 1000n,
|
|
@@ -54,7 +58,7 @@ describe('Receipt', () => {
|
|
|
54
58
|
})
|
|
55
59
|
|
|
56
60
|
test('serialize and deserialize round-trip', () => {
|
|
57
|
-
const receipt =
|
|
61
|
+
const receipt = createSessionReceipt({
|
|
58
62
|
challengeId: 'test-challenge-id',
|
|
59
63
|
channelId,
|
|
60
64
|
acceptedCumulative: 5000000n,
|
|
@@ -62,22 +66,22 @@ describe('Receipt', () => {
|
|
|
62
66
|
units: 42,
|
|
63
67
|
})
|
|
64
68
|
|
|
65
|
-
const encoded =
|
|
69
|
+
const encoded = serializeSessionReceipt(receipt)
|
|
66
70
|
expect(typeof encoded).toBe('string')
|
|
67
71
|
|
|
68
|
-
const decoded =
|
|
72
|
+
const decoded = deserializeSessionReceipt(encoded)
|
|
69
73
|
expect(decoded).toEqual(receipt)
|
|
70
74
|
})
|
|
71
75
|
|
|
72
76
|
test('serialize produces base64url without padding', () => {
|
|
73
|
-
const receipt =
|
|
77
|
+
const receipt = createSessionReceipt({
|
|
74
78
|
challengeId: 'test-challenge-id',
|
|
75
79
|
channelId,
|
|
76
80
|
acceptedCumulative: 1n,
|
|
77
81
|
spent: 0n,
|
|
78
82
|
})
|
|
79
83
|
|
|
80
|
-
const encoded =
|
|
84
|
+
const encoded = serializeSessionReceipt(receipt)
|
|
81
85
|
// base64url uses - and _ instead of + and /, no = padding
|
|
82
86
|
expect(encoded).not.toMatch(/[+/=]/)
|
|
83
87
|
})
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { Base64 } from 'ox'
|
|
2
2
|
import type { Hex } from 'viem'
|
|
3
|
-
import type {
|
|
3
|
+
import type { SessionReceipt } from './Types.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Create a
|
|
6
|
+
* Create a session receipt.
|
|
7
7
|
*/
|
|
8
|
-
export function
|
|
8
|
+
export function createSessionReceipt(params: {
|
|
9
9
|
challengeId: string
|
|
10
10
|
channelId: Hex
|
|
11
11
|
acceptedCumulative: bigint
|
|
12
12
|
spent: bigint
|
|
13
13
|
units?: number | undefined
|
|
14
14
|
txHash?: Hex | undefined
|
|
15
|
-
}):
|
|
15
|
+
}): SessionReceipt {
|
|
16
16
|
return {
|
|
17
17
|
method: 'tempo',
|
|
18
18
|
intent: 'session',
|
|
@@ -29,17 +29,17 @@ export function createStreamReceipt(params: {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
* Serialize a
|
|
32
|
+
* Serialize a session receipt to the Payment-Receipt header format.
|
|
33
33
|
*/
|
|
34
|
-
export function
|
|
34
|
+
export function serializeSessionReceipt(receipt: SessionReceipt): string {
|
|
35
35
|
const json = JSON.stringify(receipt)
|
|
36
36
|
return Base64.fromString(json, { pad: false, url: true })
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Deserialize a Payment-Receipt header value to a
|
|
40
|
+
* Deserialize a Payment-Receipt header value to a session receipt.
|
|
41
41
|
*/
|
|
42
|
-
export function
|
|
42
|
+
export function deserializeSessionReceipt(encoded: string): SessionReceipt {
|
|
43
43
|
const json = Base64.toString(encoded)
|
|
44
|
-
return JSON.parse(json) as
|
|
44
|
+
return JSON.parse(json) as SessionReceipt
|
|
45
45
|
}
|