mppx 0.8.11 → 0.8.12
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 +7 -0
- package/dist/client/internal/Fetch.d.ts.map +1 -1
- package/dist/client/internal/Fetch.js +35 -6
- package/dist/client/internal/Fetch.js.map +1 -1
- package/dist/client/internal/MethodResponse.d.ts +23 -0
- package/dist/client/internal/MethodResponse.d.ts.map +1 -0
- package/dist/client/internal/MethodResponse.js +15 -0
- package/dist/client/internal/MethodResponse.js.map +1 -0
- package/dist/tempo/legacy/client/ChannelOps.d.ts +2 -0
- package/dist/tempo/legacy/client/ChannelOps.d.ts.map +1 -1
- package/dist/tempo/legacy/client/ChannelOps.js +4 -0
- package/dist/tempo/legacy/client/ChannelOps.js.map +1 -1
- package/dist/tempo/legacy/client/Session.d.ts.map +1 -1
- package/dist/tempo/legacy/client/Session.js +7 -0
- package/dist/tempo/legacy/client/Session.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/Session.d.ts.map +1 -1
- package/dist/tempo/session/client/Session.js +53 -1
- package/dist/tempo/session/client/Session.js.map +1 -1
- package/dist/tempo/session/client/SessionManager.d.ts.map +1 -1
- package/dist/tempo/session/client/SessionManager.js +2 -0
- package/dist/tempo/session/client/SessionManager.js.map +1 -1
- package/dist/tempo/session/client/Transports.d.ts +10 -0
- package/dist/tempo/session/client/Transports.d.ts.map +1 -1
- package/dist/tempo/session/client/Transports.js +96 -9
- package/dist/tempo/session/client/Transports.js.map +1 -1
- package/package.json +1 -1
- package/src/client/internal/Fetch.test.ts +29 -0
- package/src/client/internal/Fetch.ts +51 -6
- package/src/client/internal/MethodResponse.ts +39 -0
- package/src/tempo/legacy/client/ChannelOps.test.ts +21 -0
- package/src/tempo/legacy/client/ChannelOps.ts +7 -0
- package/src/tempo/legacy/client/Session.test.ts +102 -1
- package/src/tempo/legacy/client/Session.ts +10 -0
- package/src/tempo/server/internal/html.gen.ts +1 -1
- package/src/tempo/session/client/Session.test.ts +83 -1
- package/src/tempo/session/client/Session.ts +73 -1
- package/src/tempo/session/client/SessionManager.ts +2 -0
- package/src/tempo/session/client/Transports.test.ts +75 -0
- package/src/tempo/session/client/Transports.ts +108 -11
|
@@ -10,6 +10,7 @@ import { rpcUrl } from '~test/tempo/rpc.js'
|
|
|
10
10
|
import { accounts, asset, chain, client, http } from '~test/tempo/viem.js'
|
|
11
11
|
|
|
12
12
|
import * as Fetch from './Fetch.js'
|
|
13
|
+
import * as MethodResponse from './MethodResponse.js'
|
|
13
14
|
|
|
14
15
|
const realm = 'api.example.com'
|
|
15
16
|
const secretKey = 'test-secret-key-test-secret-key-32'
|
|
@@ -391,6 +392,34 @@ function encodeRawPaymentRequiredHeader(value: unknown): string {
|
|
|
391
392
|
return Base64.fromString(JSON.stringify(value))
|
|
392
393
|
}
|
|
393
394
|
|
|
395
|
+
describe('Fetch.from: method responses', () => {
|
|
396
|
+
test('handles successful paid responses without touching free responses', async () => {
|
|
397
|
+
const method = { ...noopMethod }
|
|
398
|
+
const handle = vi.fn(
|
|
399
|
+
async ({ response }: MethodResponse.HandlerParameters) =>
|
|
400
|
+
new Response(`handled:${await response.text()}`, response),
|
|
401
|
+
)
|
|
402
|
+
MethodResponse.register(method, handle)
|
|
403
|
+
|
|
404
|
+
let paidResponse: Response | undefined
|
|
405
|
+
const mockFetch: typeof globalThis.fetch = async (input, init) => {
|
|
406
|
+
if (input.toString().endsWith('/free')) return new Response('free')
|
|
407
|
+
if (!new Headers(init?.headers).has('Authorization')) return make402()
|
|
408
|
+
paidResponse = new Response('paid')
|
|
409
|
+
vi.spyOn(paidResponse, 'clone')
|
|
410
|
+
return paidResponse
|
|
411
|
+
}
|
|
412
|
+
const fetch = Fetch.from({ fetch: mockFetch, methods: [method] })
|
|
413
|
+
|
|
414
|
+
expect(await (await fetch('https://example.com/free')).text()).toBe('free')
|
|
415
|
+
expect(handle).not.toHaveBeenCalled()
|
|
416
|
+
expect(await (await fetch('https://example.com/paid')).text()).toBe('handled:paid')
|
|
417
|
+
expect(handle).toHaveBeenCalledOnce()
|
|
418
|
+
expect(handle).toHaveBeenCalledWith(expect.objectContaining({ credential: 'credential' }))
|
|
419
|
+
expect(paidResponse?.clone).not.toHaveBeenCalled()
|
|
420
|
+
})
|
|
421
|
+
})
|
|
422
|
+
|
|
394
423
|
describe('Fetch.from: init passthrough (non-402)', () => {
|
|
395
424
|
test('preserves init object identity while adding Accept-Payment', async () => {
|
|
396
425
|
const receivedInits: (RequestInit | undefined)[] = []
|
|
@@ -6,6 +6,7 @@ import type { MaybePromise } from '../../internal/types.js'
|
|
|
6
6
|
import type * as Method from '../../Method.js'
|
|
7
7
|
import type * as z from '../../zod.js'
|
|
8
8
|
import * as Transport from '../Transport.js'
|
|
9
|
+
import * as MethodResponse from './MethodResponse.js'
|
|
9
10
|
|
|
10
11
|
// We tag wrappers with a global symbol so we can recognize wrappers created by mppx,
|
|
11
12
|
// even across multiple module instances/bundles. This lets restore() avoid clobbering
|
|
@@ -18,6 +19,7 @@ type WrappedFetch = typeof globalThis.fetch & {
|
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
let originalFetch: typeof globalThis.fetch | undefined
|
|
22
|
+
const eventObserverChecks = new WeakMap<object, (name: keyof ClientEventMap) => boolean>()
|
|
21
23
|
|
|
22
24
|
export type ClientEventMap<
|
|
23
25
|
methods extends readonly Method.AnyClient[] = readonly Method.AnyClient[],
|
|
@@ -174,7 +176,11 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
174
176
|
// which can duplicate retries and make restore semantics fragile.
|
|
175
177
|
const baseFetch = unwrapFetch(fetch)
|
|
176
178
|
|
|
177
|
-
const
|
|
179
|
+
const request = async (
|
|
180
|
+
input: RequestInfo | URL,
|
|
181
|
+
init: from.RequestInit<methods> | undefined,
|
|
182
|
+
canRefetch: boolean,
|
|
183
|
+
): Promise<Response> => {
|
|
178
184
|
const callerHeaders = getCallerHeaders(input, init?.headers)
|
|
179
185
|
const hasExplicitAcceptPayment = callerHeaders.has(Constants.Headers.acceptPayment)
|
|
180
186
|
const paymentPreferences = resolvePaymentPreferences(callerHeaders, resolvedAcceptPayment)
|
|
@@ -272,8 +278,13 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
272
278
|
}),
|
|
273
279
|
)
|
|
274
280
|
|
|
281
|
+
const paymentInput = resolvePaymentRetryInput(
|
|
282
|
+
response,
|
|
283
|
+
initialRequest.input,
|
|
284
|
+
initialRequest.input,
|
|
285
|
+
)
|
|
275
286
|
response = await baseFetch(
|
|
276
|
-
|
|
287
|
+
paymentInput,
|
|
277
288
|
transport.setCredential(
|
|
278
289
|
{
|
|
279
290
|
...fetchInit,
|
|
@@ -283,7 +294,8 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
283
294
|
{ challenge: selectedChallenge },
|
|
284
295
|
),
|
|
285
296
|
)
|
|
286
|
-
|
|
297
|
+
// Cloning an unobserved stream would tee and buffer it indefinitely.
|
|
298
|
+
if (response.ok && hasEventObservers(events, 'payment.response'))
|
|
287
299
|
await events.emit(
|
|
288
300
|
'payment.response',
|
|
289
301
|
createPaymentResponsePayload({
|
|
@@ -295,8 +307,23 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
295
307
|
response,
|
|
296
308
|
}),
|
|
297
309
|
)
|
|
298
|
-
if (!(await transport.isPaymentRequired(response, transportRequest as never)))
|
|
299
|
-
return response
|
|
310
|
+
if (!(await transport.isPaymentRequired(response, transportRequest as never))) {
|
|
311
|
+
if (!response.ok) return response
|
|
312
|
+
return MethodResponse.handle(selected.method, {
|
|
313
|
+
challenge: selectedChallenge,
|
|
314
|
+
credential,
|
|
315
|
+
fetch: baseFetch,
|
|
316
|
+
headers: initialRequest.headers,
|
|
317
|
+
input: paymentInput,
|
|
318
|
+
...(canRefetch
|
|
319
|
+
? {
|
|
320
|
+
refetch: () => request(cloneRequestInput(initialRequest.input), init, false),
|
|
321
|
+
}
|
|
322
|
+
: {}),
|
|
323
|
+
response,
|
|
324
|
+
signal: resolveRequestSignal(input, init),
|
|
325
|
+
})
|
|
326
|
+
}
|
|
300
327
|
}
|
|
301
328
|
return response
|
|
302
329
|
} catch (error) {
|
|
@@ -315,6 +342,8 @@ export function from<const methods extends readonly Method.AnyClient[]>(
|
|
|
315
342
|
throw error
|
|
316
343
|
}
|
|
317
344
|
}
|
|
345
|
+
const wrappedFetch = (input: RequestInfo | URL, init?: from.RequestInit<methods>) =>
|
|
346
|
+
request(input, init, true)
|
|
318
347
|
|
|
319
348
|
// Record the wrapped target so future polyfill() / restore() calls can detect origin
|
|
320
349
|
// and safely unwrap only mppx-installed wrappers.
|
|
@@ -491,7 +520,7 @@ export function createEventDispatcher<
|
|
|
491
520
|
}
|
|
492
521
|
}
|
|
493
522
|
|
|
494
|
-
|
|
523
|
+
const dispatcher: ClientEventDispatcher<methods, response> = {
|
|
495
524
|
async emit(name, payload) {
|
|
496
525
|
switch (name) {
|
|
497
526
|
case 'challenge.received': {
|
|
@@ -525,6 +554,15 @@ export function createEventDispatcher<
|
|
|
525
554
|
},
|
|
526
555
|
on,
|
|
527
556
|
}
|
|
557
|
+
eventObserverChecks.set(
|
|
558
|
+
dispatcher,
|
|
559
|
+
(name) => handlers[name as keyof typeof handlers].size > 0 || handlers['*'].size > 0,
|
|
560
|
+
)
|
|
561
|
+
return dispatcher
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function hasEventObservers(dispatcher: ClientEventDispatcher, name: keyof ClientEventMap): boolean {
|
|
565
|
+
return eventObserverChecks.get(dispatcher)?.(name) ?? true
|
|
528
566
|
}
|
|
529
567
|
|
|
530
568
|
async function emitChallengeReceived<methods extends readonly Method.AnyClient[], response>(
|
|
@@ -812,6 +850,13 @@ function cloneRequestInput(input: RequestInfo | URL): RequestInfo | URL {
|
|
|
812
850
|
}
|
|
813
851
|
}
|
|
814
852
|
|
|
853
|
+
function resolveRequestSignal(
|
|
854
|
+
input: RequestInfo | URL,
|
|
855
|
+
init: RequestInit | undefined,
|
|
856
|
+
): AbortSignal | undefined {
|
|
857
|
+
return init?.signal ?? (input instanceof Request ? input.signal : undefined)
|
|
858
|
+
}
|
|
859
|
+
|
|
815
860
|
/** @internal */
|
|
816
861
|
function isWrappedFetch(fetch: typeof globalThis.fetch): fetch is WrappedFetch {
|
|
817
862
|
return Boolean((fetch as WrappedFetch)[MPPX_FETCH_WRAPPER])
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type * as Challenge from '../../Challenge.js'
|
|
2
|
+
import type { MaybePromise } from '../../internal/types.js'
|
|
3
|
+
import type * as Method from '../../Method.js'
|
|
4
|
+
|
|
5
|
+
const handlers = new WeakMap<Method.AnyClient, Handler>()
|
|
6
|
+
|
|
7
|
+
/** Inputs available when a client method handles a successful paid response. */
|
|
8
|
+
export type HandlerParameters = {
|
|
9
|
+
challenge: Challenge.Challenge
|
|
10
|
+
credential: string
|
|
11
|
+
fetch: typeof globalThis.fetch
|
|
12
|
+
headers: Headers
|
|
13
|
+
input: RequestInfo | URL
|
|
14
|
+
refetch?: (() => Promise<Response>) | undefined
|
|
15
|
+
response: Response
|
|
16
|
+
signal?: AbortSignal | undefined
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Internal client-method response adapter. */
|
|
20
|
+
export type Handler = (parameters: HandlerParameters) => MaybePromise<Response>
|
|
21
|
+
|
|
22
|
+
/** Registers an internal response adapter without changing the public method shape. */
|
|
23
|
+
export function register<const method extends Method.AnyClient>(
|
|
24
|
+
method: method,
|
|
25
|
+
handler: Handler,
|
|
26
|
+
): method {
|
|
27
|
+
handlers.set(method, handler)
|
|
28
|
+
return method
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Removes response handling from a method whose caller owns the response lifecycle. */
|
|
32
|
+
export function unregister(method: Method.AnyClient): void {
|
|
33
|
+
handlers.delete(method)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Lets the selected client method handle a successful paid response. */
|
|
37
|
+
export function handle(method: Method.AnyClient, parameters: HandlerParameters): Promise<Response> {
|
|
38
|
+
return Promise.resolve(handlers.get(method)?.(parameters) ?? parameters.response)
|
|
39
|
+
}
|
|
@@ -340,6 +340,27 @@ describe.runIf(isLocalnet)('tryRecoverChannel', () => {
|
|
|
340
340
|
expect(result!.opened).toBe(true)
|
|
341
341
|
expect(result!.escrowContract).toBe(escrow)
|
|
342
342
|
expect(result!.chainId).toBe(chain.id)
|
|
343
|
+
// Channel opened with a zero authorizedSigner: the escrow verifies
|
|
344
|
+
// vouchers against the payer, so the recovered authority is the payer.
|
|
345
|
+
expect(result!.authorizedSigner.toLowerCase()).toBe(payer.address.toLowerCase())
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
test('recovers the on-chain authorizedSigner when set', async () => {
|
|
349
|
+
const authorizedSigner = accounts[4].address
|
|
350
|
+
const { channelId: signerChannelId } = await openChannel({
|
|
351
|
+
escrow,
|
|
352
|
+
payer,
|
|
353
|
+
payee,
|
|
354
|
+
token: currency,
|
|
355
|
+
deposit: 10_000_000n,
|
|
356
|
+
salt: Hex.random(32) as `0x${string}`,
|
|
357
|
+
authorizedSigner,
|
|
358
|
+
})
|
|
359
|
+
|
|
360
|
+
const result = await tryRecoverChannel(client, escrow, signerChannelId, chain.id)
|
|
361
|
+
|
|
362
|
+
expect(result).not.toBeUndefined()
|
|
363
|
+
expect(result!.authorizedSigner.toLowerCase()).toBe(authorizedSigner.toLowerCase())
|
|
343
364
|
})
|
|
344
365
|
|
|
345
366
|
test('returns undefined for non-existent channel', async () => {
|
|
@@ -27,6 +27,8 @@ import { signVoucher } from '../session/Voucher.js'
|
|
|
27
27
|
|
|
28
28
|
/** Cached channel metadata used by the legacy auto-driving session client. */
|
|
29
29
|
export type ChannelEntry = {
|
|
30
|
+
/** Voucher authority the channel was opened with. */
|
|
31
|
+
authorizedSigner: Address
|
|
30
32
|
/** Legacy contract-backed channel ID. */
|
|
31
33
|
channelId: Hex.Hex
|
|
32
34
|
/** Salt used to derive the channel ID. */
|
|
@@ -209,6 +211,7 @@ export async function createOpenPayload(
|
|
|
209
211
|
|
|
210
212
|
return {
|
|
211
213
|
entry: {
|
|
214
|
+
authorizedSigner,
|
|
212
215
|
channelId,
|
|
213
216
|
salt,
|
|
214
217
|
cumulativeAmount: initialAmount,
|
|
@@ -249,6 +252,10 @@ export async function tryRecoverChannel(
|
|
|
249
252
|
|
|
250
253
|
if (onChain.deposit > 0n && !onChain.finalized) {
|
|
251
254
|
return {
|
|
255
|
+
// A zero on-chain authorizedSigner means the escrow verifies vouchers
|
|
256
|
+
// against the payer, so record the payer as the voucher authority.
|
|
257
|
+
authorizedSigner:
|
|
258
|
+
BigInt(onChain.authorizedSigner) === 0n ? onChain.payer : onChain.authorizedSigner,
|
|
252
259
|
channelId,
|
|
253
260
|
salt: '0x' as Hex.Hex,
|
|
254
261
|
cumulativeAmount: onChain.settled,
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
import { SignatureEnvelope } from 'ox/tempo'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type Address,
|
|
4
|
+
createClient,
|
|
5
|
+
custom,
|
|
6
|
+
decodeFunctionData,
|
|
7
|
+
erc20Abi,
|
|
8
|
+
type Hex,
|
|
9
|
+
http,
|
|
10
|
+
} from 'viem'
|
|
3
11
|
import { privateKeyToAccount } from 'viem/accounts'
|
|
4
12
|
import { Account as TempoAccount, Addresses, Transaction, WebCryptoP256 } from 'viem/tempo'
|
|
13
|
+
import { tempoModerato } from 'viem/tempo/chains'
|
|
5
14
|
import { beforeAll, describe, expect, test } from 'vp/test'
|
|
6
15
|
import { tempoNetwork } from '~test/config.js'
|
|
7
16
|
import { deployEscrow, openChannel } from '~test/tempo/legacy/session.js'
|
|
@@ -374,6 +383,98 @@ describe('session (pure)', () => {
|
|
|
374
383
|
expect(cred.source).toBe(`did:pkh:eip155:42431:${pureAccount.address}`)
|
|
375
384
|
})
|
|
376
385
|
})
|
|
386
|
+
|
|
387
|
+
describe('auto-manage channel reuse', () => {
|
|
388
|
+
const payerAccount = privateKeyToAccount(
|
|
389
|
+
'0x59c6995e998f97a5a0044966f09453863d462d2b3f1446a99f0a3d7b5d0f5a0d',
|
|
390
|
+
)
|
|
391
|
+
const signerA = TempoAccount.fromSecp256k1(
|
|
392
|
+
'0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a',
|
|
393
|
+
{ access: payerAccount },
|
|
394
|
+
)
|
|
395
|
+
const signerB = TempoAccount.fromSecp256k1(
|
|
396
|
+
'0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6',
|
|
397
|
+
{ access: payerAccount },
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
function makeAutoClient(account: typeof signerA) {
|
|
401
|
+
return createClient({
|
|
402
|
+
account,
|
|
403
|
+
chain: tempoModerato,
|
|
404
|
+
transport: custom({
|
|
405
|
+
async request({ method }: { method: string }) {
|
|
406
|
+
if (method === 'eth_chainId') return `0x${tempoModerato.id.toString(16)}`
|
|
407
|
+
if (method === 'eth_fillTransaction')
|
|
408
|
+
return {
|
|
409
|
+
raw: '0x',
|
|
410
|
+
tx: {
|
|
411
|
+
chainId: `0x${tempoModerato.id.toString(16)}`,
|
|
412
|
+
from: payerAccount.address,
|
|
413
|
+
gas: '0x5208',
|
|
414
|
+
input: '0x',
|
|
415
|
+
maxFeePerGas: '0x1',
|
|
416
|
+
maxPriorityFeePerGas: '0x1',
|
|
417
|
+
nonce: '0x0',
|
|
418
|
+
to: escrowAddress,
|
|
419
|
+
type: '0x2',
|
|
420
|
+
value: '0x0',
|
|
421
|
+
},
|
|
422
|
+
}
|
|
423
|
+
throw new Error(`unexpected rpc request: ${method}`)
|
|
424
|
+
},
|
|
425
|
+
}),
|
|
426
|
+
})
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
test('reuses the cached channel while the voucher signer is unchanged', async () => {
|
|
430
|
+
const method = session({
|
|
431
|
+
getClient: () => makeAutoClient(signerA),
|
|
432
|
+
maxDeposit: '10',
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
const first = deserializePayload(
|
|
436
|
+
await method.createCredential({ challenge: makeChallenge(), context: {} }),
|
|
437
|
+
)
|
|
438
|
+
const second = deserializePayload(
|
|
439
|
+
await method.createCredential({ challenge: makeChallenge(), context: {} }),
|
|
440
|
+
)
|
|
441
|
+
|
|
442
|
+
expect(first.payload.action).toBe('open')
|
|
443
|
+
expect(second.payload.action).toBe('voucher')
|
|
444
|
+
expect(second.payload.channelId).toBe(first.payload.channelId)
|
|
445
|
+
})
|
|
446
|
+
|
|
447
|
+
test('opens a new channel instead of reusing when the authorizedSigner changes', async () => {
|
|
448
|
+
let account = signerA
|
|
449
|
+
const method = session({
|
|
450
|
+
getClient: () => makeAutoClient(account),
|
|
451
|
+
maxDeposit: '10',
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
const first = deserializePayload(
|
|
455
|
+
await method.createCredential({ challenge: makeChallenge(), context: {} }),
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
account = signerB
|
|
459
|
+
|
|
460
|
+
const second = deserializePayload(
|
|
461
|
+
await method.createCredential({ challenge: makeChallenge(), context: {} }),
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
expect(first.payload.action).toBe('open')
|
|
465
|
+
if (first.payload.action !== 'open') throw new Error('expected open payload')
|
|
466
|
+
expect(first.payload.authorizedSigner?.toLowerCase()).toBe(
|
|
467
|
+
signerA.accessKeyAddress.toLowerCase(),
|
|
468
|
+
)
|
|
469
|
+
|
|
470
|
+
expect(second.payload.action).toBe('open')
|
|
471
|
+
if (second.payload.action !== 'open') throw new Error('expected open payload')
|
|
472
|
+
expect(second.payload.authorizedSigner?.toLowerCase()).toBe(
|
|
473
|
+
signerB.accessKeyAddress.toLowerCase(),
|
|
474
|
+
)
|
|
475
|
+
expect(second.payload.channelId).not.toBe(first.payload.channelId)
|
|
476
|
+
})
|
|
477
|
+
})
|
|
377
478
|
})
|
|
378
479
|
|
|
379
480
|
describe.runIf(isLocalnet)('session (on-chain)', () => {
|
|
@@ -175,6 +175,16 @@ export function session(parameters: session.Parameters = {}) {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
+
// A channel only admits vouchers from the authorizedSigner it was opened
|
|
179
|
+
// with, so a cached or recovered entry stops being reusable once the
|
|
180
|
+
// resolved signer changes. Fall through to open a fresh channel instead
|
|
181
|
+
// of emitting a voucher the escrow would reject.
|
|
182
|
+
if (
|
|
183
|
+
entry?.opened &&
|
|
184
|
+
entry.authorizedSigner.toLowerCase() !== getAccountSignerAddress(voucherSigner).toLowerCase()
|
|
185
|
+
)
|
|
186
|
+
entry = undefined
|
|
187
|
+
|
|
178
188
|
let payload: LegacySessionCredentialPayload
|
|
179
189
|
|
|
180
190
|
if (entry?.opened) {
|