mppx 0.6.19 → 0.6.21
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 +14 -0
- package/dist/Challenge.d.ts +2 -2
- package/dist/Challenge.d.ts.map +1 -1
- package/dist/Challenge.js +1 -1
- package/dist/Challenge.js.map +1 -1
- package/dist/Method.d.ts +34 -0
- package/dist/Method.d.ts.map +1 -1
- package/dist/Method.js +3 -1
- package/dist/Method.js.map +1 -1
- package/dist/Receipt.d.ts +1 -0
- package/dist/Receipt.d.ts.map +1 -1
- package/dist/Receipt.js +2 -0
- package/dist/Receipt.js.map +1 -1
- package/dist/client/Methods.d.ts +1 -0
- package/dist/client/Methods.d.ts.map +1 -1
- package/dist/client/Methods.js +1 -0
- package/dist/client/Methods.js.map +1 -1
- package/dist/client/Mppx.d.ts +12 -1
- package/dist/client/Mppx.d.ts.map +1 -1
- package/dist/client/Mppx.js +127 -10
- package/dist/client/Mppx.js.map +1 -1
- package/dist/client/internal/Fetch.d.ts +69 -1
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +250 -20
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/middlewares/elysia.d.ts.map +1 -1
- package/dist/middlewares/elysia.js +14 -0
- package/dist/middlewares/elysia.js.map +1 -1
- package/dist/middlewares/express.d.ts.map +1 -1
- package/dist/middlewares/express.js +1 -2
- package/dist/middlewares/express.js.map +1 -1
- package/dist/middlewares/hono.d.ts.map +1 -1
- package/dist/middlewares/hono.js +14 -0
- package/dist/middlewares/hono.js.map +1 -1
- package/dist/middlewares/internal/mppx.d.ts +1 -1
- package/dist/middlewares/internal/mppx.d.ts.map +1 -1
- package/dist/middlewares/internal/mppx.js +2 -1
- package/dist/middlewares/internal/mppx.js.map +1 -1
- package/dist/middlewares/nextjs.d.ts.map +1 -1
- package/dist/middlewares/nextjs.js +14 -0
- package/dist/middlewares/nextjs.js.map +1 -1
- package/dist/proxy/Proxy.d.ts.map +1 -1
- package/dist/proxy/Proxy.js +2 -2
- package/dist/proxy/Proxy.js.map +1 -1
- package/dist/proxy/Service.d.ts.map +1 -1
- package/dist/proxy/Service.js +1 -1
- package/dist/proxy/Service.js.map +1 -1
- package/dist/server/Mppx.d.ts +96 -5
- package/dist/server/Mppx.d.ts.map +1 -1
- package/dist/server/Mppx.js +739 -115
- package/dist/server/Mppx.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/Methods.d.ts +96 -0
- package/dist/tempo/Methods.d.ts.map +1 -1
- package/dist/tempo/Methods.js +97 -0
- package/dist/tempo/Methods.js.map +1 -1
- package/dist/tempo/client/Methods.d.ts +3 -0
- package/dist/tempo/client/Methods.d.ts.map +1 -1
- package/dist/tempo/client/Methods.js +3 -0
- package/dist/tempo/client/Methods.js.map +1 -1
- package/dist/tempo/client/Subscription.d.ts +114 -0
- package/dist/tempo/client/Subscription.d.ts.map +1 -0
- package/dist/tempo/client/Subscription.js +100 -0
- package/dist/tempo/client/Subscription.js.map +1 -0
- package/dist/tempo/client/index.d.ts +1 -0
- package/dist/tempo/client/index.d.ts.map +1 -1
- package/dist/tempo/client/index.js +1 -0
- package/dist/tempo/client/index.js.map +1 -1
- package/dist/tempo/index.d.ts +1 -0
- package/dist/tempo/index.d.ts.map +1 -1
- package/dist/tempo/index.js +1 -0
- package/dist/tempo/index.js.map +1 -1
- package/dist/tempo/server/Methods.d.ts +5 -0
- package/dist/tempo/server/Methods.d.ts.map +1 -1
- package/dist/tempo/server/Methods.js +5 -0
- package/dist/tempo/server/Methods.js.map +1 -1
- package/dist/tempo/server/Subscription.d.ts +221 -0
- package/dist/tempo/server/Subscription.d.ts.map +1 -0
- package/dist/tempo/server/Subscription.js +637 -0
- package/dist/tempo/server/Subscription.js.map +1 -0
- package/dist/tempo/server/index.d.ts +1 -0
- package/dist/tempo/server/index.d.ts.map +1 -1
- package/dist/tempo/server/index.js +1 -0
- package/dist/tempo/server/index.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/subscription/KeyAuthorization.d.ts +282 -0
- package/dist/tempo/subscription/KeyAuthorization.d.ts.map +1 -0
- package/dist/tempo/subscription/KeyAuthorization.js +297 -0
- package/dist/tempo/subscription/KeyAuthorization.js.map +1 -0
- package/dist/tempo/subscription/Receipt.d.ts +10 -0
- package/dist/tempo/subscription/Receipt.d.ts.map +1 -0
- package/dist/tempo/subscription/Receipt.js +16 -0
- package/dist/tempo/subscription/Receipt.js.map +1 -0
- package/dist/tempo/subscription/Store.d.ts +99 -0
- package/dist/tempo/subscription/Store.d.ts.map +1 -0
- package/dist/tempo/subscription/Store.js +292 -0
- package/dist/tempo/subscription/Store.js.map +1 -0
- package/dist/tempo/subscription/Types.d.ts +65 -0
- package/dist/tempo/subscription/Types.d.ts.map +1 -0
- package/dist/tempo/subscription/Types.js +2 -0
- package/dist/tempo/subscription/Types.js.map +1 -0
- package/dist/tempo/subscription/index.d.ts +6 -0
- package/dist/tempo/subscription/index.d.ts.map +1 -0
- package/dist/tempo/subscription/index.js +4 -0
- package/dist/tempo/subscription/index.js.map +1 -0
- package/dist/zod.d.ts +7 -0
- package/dist/zod.d.ts.map +1 -1
- package/dist/zod.js +18 -0
- package/dist/zod.js.map +1 -1
- package/package.json +3 -3
- package/src/Challenge.test.ts +13 -0
- package/src/Challenge.ts +3 -3
- package/src/Method.ts +46 -1
- package/src/Receipt.ts +2 -0
- package/src/client/Methods.ts +1 -0
- package/src/client/Mppx.test-d.ts +55 -0
- package/src/client/Mppx.test.ts +181 -0
- package/src/client/Mppx.ts +248 -16
- package/src/client/internal/Fetch.test-d.ts +31 -0
- package/src/client/internal/Fetch.test.ts +261 -0
- package/src/client/internal/Fetch.ts +467 -24
- package/src/middlewares/elysia.test.ts +31 -1
- package/src/middlewares/elysia.ts +13 -0
- package/src/middlewares/express.ts +1 -5
- package/src/middlewares/hono.test.ts +30 -1
- package/src/middlewares/hono.ts +13 -0
- package/src/middlewares/internal/mppx.ts +5 -6
- package/src/middlewares/nextjs.test.ts +28 -1
- package/src/middlewares/nextjs.ts +13 -0
- package/src/proxy/Proxy.test.ts +69 -0
- package/src/proxy/Proxy.ts +2 -5
- package/src/proxy/Service.test.ts +34 -0
- package/src/proxy/Service.ts +7 -0
- package/src/server/Mppx.authorize.test.ts +210 -0
- package/src/server/Mppx.test-d.ts +73 -1
- package/src/server/Mppx.test.ts +965 -3
- package/src/server/Mppx.ts +1138 -140
- package/src/stripe/server/internal/html/package.json +1 -1
- package/src/stripe/server/internal/html.gen.ts +1 -1
- package/src/tempo/Methods.test.ts +131 -0
- package/src/tempo/Methods.ts +136 -0
- package/src/tempo/Subscription.integration.test.ts +591 -0
- package/src/tempo/client/Methods.ts +3 -0
- package/src/tempo/client/Subscription.test.ts +131 -0
- package/src/tempo/client/Subscription.ts +155 -0
- package/src/tempo/client/index.ts +1 -0
- package/src/tempo/index.ts +1 -0
- package/src/tempo/server/Methods.ts +5 -0
- package/src/tempo/server/Subscription.test.ts +1410 -0
- package/src/tempo/server/Subscription.ts +1014 -0
- package/src/tempo/server/index.ts +1 -0
- package/src/tempo/server/internal/html/package.json +1 -1
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/subscription/KeyAuthorization.test.ts +204 -0
- package/src/tempo/subscription/KeyAuthorization.ts +394 -0
- package/src/tempo/subscription/Receipt.ts +28 -0
- package/src/tempo/subscription/Store.test.ts +554 -0
- package/src/tempo/subscription/Store.ts +431 -0
- package/src/tempo/subscription/Types.ts +68 -0
- package/src/tempo/subscription/index.ts +23 -0
- package/src/zod.test.ts +23 -1
- package/src/zod.ts +24 -0
package/src/Method.ts
CHANGED
|
@@ -130,10 +130,12 @@ export type Server<
|
|
|
130
130
|
defaults extends ExactPartial<z.input<method['schema']['request']>> = {},
|
|
131
131
|
transportOverride = undefined,
|
|
132
132
|
> = method & {
|
|
133
|
+
authorize?: AuthorizeFn<method> | undefined
|
|
133
134
|
defaults?: defaults | undefined
|
|
134
135
|
html?: Html.Options | undefined
|
|
135
136
|
request?: RequestFn<method> | undefined
|
|
136
137
|
respond?: RespondFn<method> | undefined
|
|
138
|
+
stableBinding?: StableBindingFn<method> | undefined
|
|
137
139
|
transport?: transportOverride | undefined
|
|
138
140
|
verify: VerifyFn<method>
|
|
139
141
|
}
|
|
@@ -155,6 +157,45 @@ export type RequestFn<method extends Method> = (
|
|
|
155
157
|
options: RequestContext<method>,
|
|
156
158
|
) => MaybePromise<z.input<method['schema']['request']>>
|
|
157
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Optional authorization hook for a server-side method.
|
|
162
|
+
*
|
|
163
|
+
* Called after request normalization but before the 402 challenge path. This lets
|
|
164
|
+
* a server grant access based on existing application state (for example, an
|
|
165
|
+
* active subscription) without requiring a fresh `Payment` credential.
|
|
166
|
+
*
|
|
167
|
+
* **HTTP-only.** The `input` parameter is a Fetch `Request`; non-HTTP transports
|
|
168
|
+
* do not invoke this hook.
|
|
169
|
+
*
|
|
170
|
+
* Transports that require credential context for `withReceipt()` should return a
|
|
171
|
+
* `response` from this hook so adapters can short-circuit protected handlers.
|
|
172
|
+
*/
|
|
173
|
+
export type AuthorizeFn<method extends Method> = (parameters: {
|
|
174
|
+
challenge: Challenge.Challenge<
|
|
175
|
+
z.output<method['schema']['request']>,
|
|
176
|
+
method['intent'],
|
|
177
|
+
method['name']
|
|
178
|
+
>
|
|
179
|
+
input: globalThis.Request
|
|
180
|
+
request: z.output<method['schema']['request']>
|
|
181
|
+
}) => MaybePromise<AuthorizeResult | undefined>
|
|
182
|
+
|
|
183
|
+
/** Successful result returned from an {@link AuthorizeFn}. */
|
|
184
|
+
export type AuthorizeResult = {
|
|
185
|
+
receipt: Receipt.Receipt
|
|
186
|
+
response?: globalThis.Response | undefined
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Produces the stable request fields used to bind credentials to a route.
|
|
191
|
+
*
|
|
192
|
+
* Methods can override this to opt into additional request fields beyond the
|
|
193
|
+
* default amount/currency/recipient binding used by generic methods.
|
|
194
|
+
*/
|
|
195
|
+
export type StableBindingFn<method extends Method> = (
|
|
196
|
+
request: z.output<method['schema']['request']>,
|
|
197
|
+
) => Record<string, unknown>
|
|
198
|
+
|
|
158
199
|
/** Verification function for a single method. */
|
|
159
200
|
export type VerifyFn<method extends Method> = (
|
|
160
201
|
parameters: VerifyContext<method>,
|
|
@@ -251,13 +292,15 @@ export function toServer<
|
|
|
251
292
|
method: method,
|
|
252
293
|
options: toServer.Options<method, defaults, transportOverride>,
|
|
253
294
|
): Server<method, defaults, transportOverride> {
|
|
254
|
-
const { defaults, html, request, respond, transport, verify } = options
|
|
295
|
+
const { authorize, defaults, html, request, respond, stableBinding, transport, verify } = options
|
|
255
296
|
return {
|
|
256
297
|
...method,
|
|
298
|
+
authorize,
|
|
257
299
|
defaults,
|
|
258
300
|
html,
|
|
259
301
|
request,
|
|
260
302
|
respond,
|
|
303
|
+
stableBinding,
|
|
261
304
|
transport,
|
|
262
305
|
verify,
|
|
263
306
|
} as Server<method, defaults, transportOverride>
|
|
@@ -269,10 +312,12 @@ export declare namespace toServer {
|
|
|
269
312
|
defaults extends RequestDefaults<method> = {},
|
|
270
313
|
transportOverride extends Transport.AnyTransport | undefined = undefined,
|
|
271
314
|
> = {
|
|
315
|
+
authorize?: AuthorizeFn<method> | undefined
|
|
272
316
|
defaults?: defaults | undefined
|
|
273
317
|
html?: Html.Options | undefined
|
|
274
318
|
request?: RequestFn<method> | undefined
|
|
275
319
|
respond?: RespondFn<method> | undefined
|
|
320
|
+
stableBinding?: StableBindingFn<method> | undefined
|
|
276
321
|
transport?: transportOverride | undefined
|
|
277
322
|
verify: VerifyFn<method>
|
|
278
323
|
}
|
package/src/Receipt.ts
CHANGED
|
@@ -19,6 +19,8 @@ export const Schema = z.object({
|
|
|
19
19
|
reference: z.string(),
|
|
20
20
|
/** Optional external reference ID echoed from the credential payload. */
|
|
21
21
|
externalId: z.optional(z.string()),
|
|
22
|
+
/** Optional server-issued subscription identifier for recurring payments. */
|
|
23
|
+
subscriptionId: z.optional(z.string()),
|
|
22
24
|
/** Payment status. Always "success" — failures use 402 + Problem Details. */
|
|
23
25
|
status: z.literal('success'),
|
|
24
26
|
/** RFC 3339 settlement timestamp. */
|
package/src/client/Methods.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import type { Account } from 'viem'
|
|
2
2
|
import { describe, expectTypeOf, test } from 'vp/test'
|
|
3
3
|
|
|
4
|
+
import * as Challenge from '../Challenge.js'
|
|
5
|
+
import type * as Mcp from '../Mcp.js'
|
|
4
6
|
import * as Method from '../Method.js'
|
|
5
7
|
import { charge } from '../tempo/client/Charge.js'
|
|
6
8
|
import { tempo } from '../tempo/client/Methods.js'
|
|
7
9
|
import type * as AutoSwap from '../tempo/internal/auto-swap.js'
|
|
8
10
|
import * as Methods from '../tempo/Methods.js'
|
|
9
11
|
import * as z from '../zod.js'
|
|
12
|
+
import * as Fetch from './internal/Fetch.js'
|
|
10
13
|
import * as Mppx from './Mppx.js'
|
|
14
|
+
import * as Transport from './Transport.js'
|
|
11
15
|
|
|
12
16
|
describe('Mppx', () => {
|
|
13
17
|
test('has methods array', () => {
|
|
@@ -63,6 +67,57 @@ describe('create.Config', () => {
|
|
|
63
67
|
|
|
64
68
|
expectTypeOf(mppx.fetch).toBeFunction()
|
|
65
69
|
})
|
|
70
|
+
|
|
71
|
+
test('client events expose typed payloads', () => {
|
|
72
|
+
const method = charge()
|
|
73
|
+
const mppx = Mppx.create({
|
|
74
|
+
methods: [method],
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const unsubscribe = mppx.on('payment.response', (payload) => {
|
|
78
|
+
expectTypeOf(payload.response).toEqualTypeOf<Response>()
|
|
79
|
+
})
|
|
80
|
+
expectTypeOf(unsubscribe).toEqualTypeOf<Fetch.Unsubscribe>()
|
|
81
|
+
|
|
82
|
+
mppx.on('*', (event) => {
|
|
83
|
+
if (event.name === 'credential.created')
|
|
84
|
+
expectTypeOf(event.payload.credential).toEqualTypeOf<string>()
|
|
85
|
+
if (event.name === 'payment.response')
|
|
86
|
+
expectTypeOf(event.payload.response).toEqualTypeOf<Response>()
|
|
87
|
+
})
|
|
88
|
+
mppx.onChallengeReceived((payload) => {
|
|
89
|
+
expectTypeOf(payload.challenge.id).toEqualTypeOf<string>()
|
|
90
|
+
expectTypeOf(payload.challenges).toEqualTypeOf<readonly Challenge.Challenge[]>()
|
|
91
|
+
expectTypeOf(payload.method.intent).toEqualTypeOf<'charge'>()
|
|
92
|
+
expectTypeOf(payload.createCredential({ account: {} as Account })).toEqualTypeOf<
|
|
93
|
+
Promise<string>
|
|
94
|
+
>()
|
|
95
|
+
return payload.createCredential({ account: {} as Account })
|
|
96
|
+
})
|
|
97
|
+
mppx.onCredentialCreated((payload) => {
|
|
98
|
+
expectTypeOf(payload.credential).toEqualTypeOf<string>()
|
|
99
|
+
expectTypeOf(payload.method.intent).toEqualTypeOf<'charge'>()
|
|
100
|
+
})
|
|
101
|
+
mppx.onPaymentFailed((payload) => {
|
|
102
|
+
expectTypeOf(payload.error).toEqualTypeOf<unknown>()
|
|
103
|
+
expectTypeOf(payload.challenge).toEqualTypeOf<Challenge.Challenge | undefined>()
|
|
104
|
+
})
|
|
105
|
+
mppx.onPaymentResponse((payload) => {
|
|
106
|
+
expectTypeOf(payload.response).toEqualTypeOf<Response>()
|
|
107
|
+
expectTypeOf(payload.credential).toEqualTypeOf<string>()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test('client events use transport response types', () => {
|
|
112
|
+
const mppx = Mppx.create({
|
|
113
|
+
methods: [tempo({ account: {} as Account })],
|
|
114
|
+
transport: Transport.mcp(),
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
mppx.onChallengeReceived((payload) => {
|
|
118
|
+
expectTypeOf(payload.response).toMatchTypeOf<Response | Mcp.Response>()
|
|
119
|
+
})
|
|
120
|
+
})
|
|
66
121
|
})
|
|
67
122
|
|
|
68
123
|
describe('Method.toClient', () => {
|
package/src/client/Mppx.test.ts
CHANGED
|
@@ -108,11 +108,143 @@ describe('createCredential', () => {
|
|
|
108
108
|
expect(parsed.challenge.method).toBe('tempo')
|
|
109
109
|
})
|
|
110
110
|
|
|
111
|
+
test('behavior: createCredential emits client events and supports runtime handlers', async () => {
|
|
112
|
+
const events: string[] = []
|
|
113
|
+
const createCredential = vi.fn(async ({ challenge }) =>
|
|
114
|
+
Credential.serialize({
|
|
115
|
+
challenge,
|
|
116
|
+
payload: { signature: '0xsignature', type: 'transaction' },
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
const method = Method.toClient(Methods.charge, { createCredential })
|
|
120
|
+
const mppx = Mppx.create({
|
|
121
|
+
polyfill: false,
|
|
122
|
+
methods: [method],
|
|
123
|
+
})
|
|
124
|
+
mppx.onCredentialCreated((payload) => {
|
|
125
|
+
events.push(`credential:${payload.credential.startsWith('Payment ')}`)
|
|
126
|
+
})
|
|
127
|
+
const offCredential = mppx.onCredentialCreated(() => {
|
|
128
|
+
events.push('removed')
|
|
129
|
+
})
|
|
130
|
+
const offChallenge = mppx.onChallengeReceived((payload) => {
|
|
131
|
+
events.push(`runtime:${payload.method.intent}`)
|
|
132
|
+
return payload.createCredential()
|
|
133
|
+
})
|
|
134
|
+
mppx.on('*', (event) => {
|
|
135
|
+
events.push(`*:${event.name}`)
|
|
136
|
+
})
|
|
137
|
+
offCredential()
|
|
138
|
+
|
|
139
|
+
const challenge = Challenge.fromMethod(Methods.charge, {
|
|
140
|
+
realm,
|
|
141
|
+
secretKey,
|
|
142
|
+
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
143
|
+
request: {
|
|
144
|
+
amount: '1000',
|
|
145
|
+
currency: '0x1234567890123456789012345678901234567890',
|
|
146
|
+
decimals: 6,
|
|
147
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
148
|
+
},
|
|
149
|
+
})
|
|
150
|
+
const response = new Response(null, {
|
|
151
|
+
status: 402,
|
|
152
|
+
headers: {
|
|
153
|
+
'WWW-Authenticate': Challenge.serialize(challenge),
|
|
154
|
+
},
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const credential = await mppx.createCredential(response)
|
|
158
|
+
offChallenge()
|
|
159
|
+
|
|
160
|
+
expect(credential).toMatch(/^Payment /)
|
|
161
|
+
expect(createCredential).toHaveBeenCalledTimes(1)
|
|
162
|
+
expect(events).toEqual([
|
|
163
|
+
'runtime:charge',
|
|
164
|
+
'*:challenge.received',
|
|
165
|
+
'credential:true',
|
|
166
|
+
'*:credential.created',
|
|
167
|
+
])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
test('behavior: createCredential memoizes event helper calls', async () => {
|
|
171
|
+
const createCredential = vi.fn(async ({ challenge }) =>
|
|
172
|
+
Credential.serialize({
|
|
173
|
+
challenge,
|
|
174
|
+
payload: { signature: '0xsignature', type: 'transaction' },
|
|
175
|
+
}),
|
|
176
|
+
)
|
|
177
|
+
const method = Method.toClient(Methods.charge, { createCredential })
|
|
178
|
+
const mppx = Mppx.create({
|
|
179
|
+
polyfill: false,
|
|
180
|
+
methods: [method],
|
|
181
|
+
})
|
|
182
|
+
mppx.on('*', async (event) => {
|
|
183
|
+
if (event.name === 'challenge.received') await event.payload.createCredential()
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
const challenge = Challenge.fromMethod(Methods.charge, {
|
|
187
|
+
realm,
|
|
188
|
+
secretKey,
|
|
189
|
+
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
190
|
+
request: {
|
|
191
|
+
amount: '1000',
|
|
192
|
+
currency: '0x1234567890123456789012345678901234567890',
|
|
193
|
+
decimals: 6,
|
|
194
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
const response = new Response(null, {
|
|
198
|
+
status: 402,
|
|
199
|
+
headers: {
|
|
200
|
+
'WWW-Authenticate': Challenge.serialize(challenge),
|
|
201
|
+
},
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
await mppx.createCredential(response)
|
|
205
|
+
|
|
206
|
+
expect(createCredential).toHaveBeenCalledTimes(1)
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
test('behavior: createCredential validates event credentials', async () => {
|
|
210
|
+
const mppx = Mppx.create({
|
|
211
|
+
polyfill: false,
|
|
212
|
+
methods: [tempo({ account: accounts[1], getClient: () => client })],
|
|
213
|
+
})
|
|
214
|
+
mppx.onChallengeReceived(() => 'Payment invalid\r\nX-Injected: true')
|
|
215
|
+
|
|
216
|
+
const challenge = Challenge.fromMethod(Methods.charge, {
|
|
217
|
+
realm,
|
|
218
|
+
secretKey,
|
|
219
|
+
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
220
|
+
request: {
|
|
221
|
+
amount: '1000',
|
|
222
|
+
currency: '0x1234567890123456789012345678901234567890',
|
|
223
|
+
decimals: 6,
|
|
224
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
225
|
+
},
|
|
226
|
+
})
|
|
227
|
+
const response = new Response(null, {
|
|
228
|
+
status: 402,
|
|
229
|
+
headers: {
|
|
230
|
+
'WWW-Authenticate': Challenge.serialize(challenge),
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
await expect(mppx.createCredential(response)).rejects.toThrow('illegal newline')
|
|
235
|
+
})
|
|
236
|
+
|
|
111
237
|
test('behavior: throws when method not found', async () => {
|
|
238
|
+
const events: string[] = []
|
|
112
239
|
const mppx = Mppx.create({
|
|
113
240
|
polyfill: false,
|
|
114
241
|
methods: [tempo({ account: accounts[1], getClient: () => client })],
|
|
115
242
|
})
|
|
243
|
+
mppx.onPaymentFailed((payload) => {
|
|
244
|
+
events.push(
|
|
245
|
+
`failed:${payload.challenge === undefined}:${payload.challenges?.length}:${payload.error instanceof Error}`,
|
|
246
|
+
)
|
|
247
|
+
})
|
|
116
248
|
|
|
117
249
|
const challenge = Challenge.from({
|
|
118
250
|
id: 'test-id',
|
|
@@ -132,6 +264,7 @@ describe('createCredential', () => {
|
|
|
132
264
|
await expect(mppx.createCredential(response)).rejects.toThrow(
|
|
133
265
|
'No method found for challenges: unknown.charge. Available: tempo.charge, tempo.session',
|
|
134
266
|
)
|
|
267
|
+
expect(events).toEqual(['failed:true:1:true'])
|
|
135
268
|
})
|
|
136
269
|
|
|
137
270
|
test('behavior: rejects expired challenges before creating credential', async () => {
|
|
@@ -451,6 +584,54 @@ describe('createCredential', () => {
|
|
|
451
584
|
expect((parsed.payload as { type: string }).type).toBe('transaction')
|
|
452
585
|
expect(parsed.challenge.method).toBe('tempo')
|
|
453
586
|
})
|
|
587
|
+
|
|
588
|
+
test('behavior: mcp transport event responses are not cast to DOM Response', async () => {
|
|
589
|
+
const method = Method.toClient(Methods.charge, {
|
|
590
|
+
async createCredential({ challenge }) {
|
|
591
|
+
return Credential.serialize({
|
|
592
|
+
challenge,
|
|
593
|
+
payload: { signature: '0xsignature', type: 'transaction' },
|
|
594
|
+
})
|
|
595
|
+
},
|
|
596
|
+
})
|
|
597
|
+
const mppx = Mppx.create({
|
|
598
|
+
polyfill: false,
|
|
599
|
+
methods: [method],
|
|
600
|
+
transport: Transport.mcp(),
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
const challenge = Challenge.fromMethod(Methods.charge, {
|
|
604
|
+
realm,
|
|
605
|
+
secretKey,
|
|
606
|
+
expires: new Date(Date.now() + 60_000).toISOString(),
|
|
607
|
+
request: {
|
|
608
|
+
amount: '1000',
|
|
609
|
+
currency: '0x1234567890123456789012345678901234567890',
|
|
610
|
+
decimals: 6,
|
|
611
|
+
recipient: '0x1234567890123456789012345678901234567890',
|
|
612
|
+
},
|
|
613
|
+
})
|
|
614
|
+
const mcpResponse: Mcp.Response = {
|
|
615
|
+
jsonrpc: '2.0',
|
|
616
|
+
id: 1,
|
|
617
|
+
error: {
|
|
618
|
+
code: Mcp.paymentRequiredCode,
|
|
619
|
+
message: 'Payment Required',
|
|
620
|
+
data: {
|
|
621
|
+
httpStatus: 402,
|
|
622
|
+
challenges: [challenge],
|
|
623
|
+
},
|
|
624
|
+
},
|
|
625
|
+
}
|
|
626
|
+
const seen: unknown[] = []
|
|
627
|
+
mppx.onChallengeReceived((event) => {
|
|
628
|
+
seen.push(event.response)
|
|
629
|
+
})
|
|
630
|
+
|
|
631
|
+
await mppx.createCredential(mcpResponse)
|
|
632
|
+
|
|
633
|
+
expect(seen).toEqual([mcpResponse])
|
|
634
|
+
})
|
|
454
635
|
})
|
|
455
636
|
|
|
456
637
|
const server = Mppx_server.create({
|