@meshconnect/uwc-core 1.2.3-snapshot.dcfdbbf → 1.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/events.d.ts +1 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js.map +1 -1
- package/dist/observability/lifecycle-bridge.d.ts +13 -31
- package/dist/observability/lifecycle-bridge.d.ts.map +1 -1
- package/dist/observability/lifecycle-bridge.js +92 -167
- package/dist/observability/lifecycle-bridge.js.map +1 -1
- package/dist/services/connection-service.d.ts +0 -43
- package/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +9 -163
- package/dist/services/connection-service.js.map +1 -1
- package/dist/services/network-switch-service.d.ts.map +1 -1
- package/dist/services/network-switch-service.js +1 -4
- package/dist/services/network-switch-service.js.map +1 -1
- package/package.json +5 -5
- package/src/events.ts +2 -5
- package/src/observability/lifecycle-bridge.test.ts +0 -197
- package/src/observability/lifecycle-bridge.ts +99 -211
- package/src/services/connection-service.test.ts +0 -272
- package/src/services/connection-service.ts +10 -190
- package/src/services/network-switch-service.test.ts +0 -33
- package/src/services/network-switch-service.ts +1 -4
|
@@ -26,12 +26,6 @@ export class ConnectionService {
|
|
|
26
26
|
private activeConnector: Connector | null = null
|
|
27
27
|
/** Tears down the post-connect lifecycle subscription; set while connected. */
|
|
28
28
|
private lifecycleTeardown: (() => void) | undefined
|
|
29
|
-
/**
|
|
30
|
-
* Teardowns for the accounts-sync attachments of the session's SECONDARY
|
|
31
|
-
* namespaces (see `attachSecondaryAccountSync`); set while connected,
|
|
32
|
-
* cleared together with `lifecycleTeardown`.
|
|
33
|
-
*/
|
|
34
|
-
private secondaryAccountSyncTeardowns: Array<() => void> = []
|
|
35
29
|
/**
|
|
36
30
|
* Abort handle for the CURRENT in-flight connect attempt. A re-entrant
|
|
37
31
|
* connect() aborts this before starting, so a superseded attempt's URI poll
|
|
@@ -206,11 +200,6 @@ export class ConnectionService {
|
|
|
206
200
|
// disconnect, expiry) from the live provider — telemetry only, never changes
|
|
207
201
|
// connection behaviour. Torn down on disconnect.
|
|
208
202
|
this.attachLifecycle(connectionMode, wallet.id, network.namespace)
|
|
209
|
-
this.attachSecondaryAccountSync(
|
|
210
|
-
connectionMode,
|
|
211
|
-
wallet.id,
|
|
212
|
-
network.namespace
|
|
213
|
-
)
|
|
214
203
|
}
|
|
215
204
|
|
|
216
205
|
/** Wire the active connector's lifecycle stream onto the bus (or signal absence). */
|
|
@@ -218,7 +207,7 @@ export class ConnectionService {
|
|
|
218
207
|
connectionMode: ConnectionMode,
|
|
219
208
|
walletId: string,
|
|
220
209
|
namespace: Namespace,
|
|
221
|
-
initialSwitch?: { chainId: NetworkId;
|
|
210
|
+
initialSwitch?: { chainId: NetworkId; at: number }
|
|
222
211
|
): void {
|
|
223
212
|
this.teardownLifecycle()
|
|
224
213
|
const connector = this.activeConnector
|
|
@@ -238,16 +227,6 @@ export class ConnectionService {
|
|
|
238
227
|
walletId
|
|
239
228
|
})
|
|
240
229
|
: undefined,
|
|
241
|
-
subscribeAccounts: connector?.onAccountsChanged
|
|
242
|
-
? listener =>
|
|
243
|
-
connector.onAccountsChanged!(listener, {
|
|
244
|
-
connectionMode,
|
|
245
|
-
namespace,
|
|
246
|
-
walletId
|
|
247
|
-
})
|
|
248
|
-
: undefined,
|
|
249
|
-
onAccountsChanged: accounts =>
|
|
250
|
-
this.handleAccountsChanged(accounts, namespace),
|
|
251
230
|
getRouting: () => {
|
|
252
231
|
const s = this.sessionManager.getSession()
|
|
253
232
|
return {
|
|
@@ -270,63 +249,6 @@ export class ConnectionService {
|
|
|
270
249
|
})
|
|
271
250
|
}
|
|
272
251
|
|
|
273
|
-
/**
|
|
274
|
-
* Apply a REAL account-list change (from `Connector.onAccountsChanged`, via
|
|
275
|
-
* the lifecycle bridge's second stream) to the session. `namespace` is the
|
|
276
|
-
* namespace of the provider that reported the change — `activeAddress`
|
|
277
|
-
* only moves when that matches the session's currently active network; an
|
|
278
|
-
* empty list is a full, wallet-initiated disconnect.
|
|
279
|
-
*/
|
|
280
|
-
private handleAccountsChanged(
|
|
281
|
-
accounts: string[],
|
|
282
|
-
namespace: Namespace
|
|
283
|
-
): void {
|
|
284
|
-
if (accounts.length === 0) {
|
|
285
|
-
// Idempotency guard: this runs synchronously FROM INSIDE the
|
|
286
|
-
// connector's own accountsChanged dispatch — finishDisconnect() below
|
|
287
|
-
// tears down (among other things) the very subscription that is
|
|
288
|
-
// currently delivering this callback. If a wallet double-fires the
|
|
289
|
-
// empty-array disconnect signal (or one slips through before teardown
|
|
290
|
-
// takes effect), `activeConnector` is already null from the first call
|
|
291
|
-
// — skip re-clearing an already-cleared session and re-emitting
|
|
292
|
-
// disconnected/sessionChanged a second time. Scoped to THIS wallet-
|
|
293
|
-
// initiated path only; the public disconnect() below still always
|
|
294
|
-
// runs finishDisconnect() defensively, even with no active connector.
|
|
295
|
-
if (this.activeConnector === null) return
|
|
296
|
-
// The wallet ended the session on its own side, but the connector's
|
|
297
|
-
// OWN local state (e.g. EthereumWalletService's cached account/
|
|
298
|
-
// provider/uuid) is never cleared by finishDisconnect() below — it
|
|
299
|
-
// only clears core's sessionManager. Left uncleared, a later connect
|
|
300
|
-
// to a DIFFERENT wallet reads that stale cached account via
|
|
301
|
-
// checkExistingConnections() and never rebinds to the new wallet's
|
|
302
|
-
// provider/uuid, silently keeping the old wallet's address live under
|
|
303
|
-
// the new wallet's session. disconnect() is fire-and-forget and
|
|
304
|
-
// error-swallowed here: for injected connectors it's local-only
|
|
305
|
-
// cleanup (safe to call any number of times), and for protocol-based
|
|
306
|
-
// connectors that might reject on an already-closed session, there is
|
|
307
|
-
// no caller to propagate that error to anyway.
|
|
308
|
-
this.activeConnector?.disconnect?.().catch(() => {})
|
|
309
|
-
this.finishDisconnect()
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
const newAddress = accounts[0]!
|
|
313
|
-
const session = this.sessionManager.getSession()
|
|
314
|
-
const availableAddresses = session.availableAddresses.map(addr => {
|
|
315
|
-
const network = this.networks.find(n => n.id === addr.networkId)
|
|
316
|
-
return network?.namespace === namespace
|
|
317
|
-
? { ...addr, address: newAddress }
|
|
318
|
-
: addr
|
|
319
|
-
})
|
|
320
|
-
const activeAddress =
|
|
321
|
-
session.activeNetwork?.namespace === namespace
|
|
322
|
-
? newAddress
|
|
323
|
-
: session.activeAddress
|
|
324
|
-
this.sessionManager.updateSession({ activeAddress, availableAddresses })
|
|
325
|
-
this.eventManager.emit('sessionChanged', {
|
|
326
|
-
session: this.sessionManager.getSession()
|
|
327
|
-
})
|
|
328
|
-
}
|
|
329
|
-
|
|
330
252
|
/**
|
|
331
253
|
* Remove the active provider-lifecycle subscription (EIP-1193 `.on` / wallet-
|
|
332
254
|
* adapter / SignClient listeners) WITHOUT disconnecting the wallet. Idempotent.
|
|
@@ -337,8 +259,6 @@ export class ConnectionService {
|
|
|
337
259
|
teardownLifecycle(): void {
|
|
338
260
|
this.lifecycleTeardown?.()
|
|
339
261
|
this.lifecycleTeardown = undefined
|
|
340
|
-
for (const teardown of this.secondaryAccountSyncTeardowns) teardown()
|
|
341
|
-
this.secondaryAccountSyncTeardowns = []
|
|
342
262
|
}
|
|
343
263
|
|
|
344
264
|
/**
|
|
@@ -363,93 +283,11 @@ export class ConnectionService {
|
|
|
363
283
|
session.connectionMode,
|
|
364
284
|
session.activeWallet.id,
|
|
365
285
|
session.activeNetwork.namespace,
|
|
366
|
-
// The reattach follows a JUST-completed switch to the now-active chain
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
370
|
-
{
|
|
371
|
-
chainId: session.activeNetwork.id,
|
|
372
|
-
...(session.activeAddress && { address: session.activeAddress }),
|
|
373
|
-
at: Date.now()
|
|
374
|
-
}
|
|
286
|
+
// The reattach follows a JUST-completed switch to the now-active chain —
|
|
287
|
+
// arm echo suppression for it so the new provider's echo isn't mistaken
|
|
288
|
+
// for a wallet-initiated change (one-shot, same as bus-armed switches).
|
|
289
|
+
{ chainId: session.activeNetwork.id, at: Date.now() }
|
|
375
290
|
)
|
|
376
|
-
// Re-derive the secondary set against the NOW-active namespace — a
|
|
377
|
-
// cross-namespace switch swaps which namespace is "secondary" (the
|
|
378
|
-
// attachLifecycle above already tore the previous set down via
|
|
379
|
-
// teardownLifecycle).
|
|
380
|
-
this.attachSecondaryAccountSync(
|
|
381
|
-
session.connectionMode,
|
|
382
|
-
session.activeWallet.id,
|
|
383
|
-
session.activeNetwork.namespace
|
|
384
|
-
)
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Attach account-sync for every OTHER namespace the session's
|
|
389
|
-
* `availableAddresses` span. `attachLifecycle` observes only the ACTIVE
|
|
390
|
-
* network's namespace, but the cross-namespace gather seeds addresses from
|
|
391
|
-
* other namespaces' live surfaces at connect time — without their own
|
|
392
|
-
* subscription, a wallet-side account switch on such a namespace is never
|
|
393
|
-
* observed and the session serves the connect-time addresses forever (e.g.
|
|
394
|
-
* a Solana-active MetaMask session whose eip155 addresses never follow the
|
|
395
|
-
* extension's account switch).
|
|
396
|
-
*
|
|
397
|
-
* Accounts-sync ONLY, no lifecycle-telemetry stream: the active namespace
|
|
398
|
-
* stays the single source of wallet* telemetry and of the wallet-initiated
|
|
399
|
-
* disconnect signal.
|
|
400
|
-
*/
|
|
401
|
-
private attachSecondaryAccountSync(
|
|
402
|
-
connectionMode: ConnectionMode,
|
|
403
|
-
walletId: string,
|
|
404
|
-
activeNamespace: Namespace
|
|
405
|
-
): void {
|
|
406
|
-
const connector = this.activeConnector
|
|
407
|
-
if (!connector?.onAccountsChanged) return
|
|
408
|
-
|
|
409
|
-
const session = this.sessionManager.getSession()
|
|
410
|
-
const secondaryNamespaces = new Set<Namespace>()
|
|
411
|
-
for (const addr of session.availableAddresses) {
|
|
412
|
-
const namespace = this.networks.find(
|
|
413
|
-
n => n.id === addr.networkId
|
|
414
|
-
)?.namespace
|
|
415
|
-
if (namespace && namespace !== activeNamespace) {
|
|
416
|
-
secondaryNamespaces.add(namespace)
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
const runtime =
|
|
421
|
-
(globalThis as { UWCBridgeChildInitialized?: boolean })
|
|
422
|
-
.UWCBridgeChildInitialized === true
|
|
423
|
-
? ('iframe' as const)
|
|
424
|
-
: ('direct' as const)
|
|
425
|
-
|
|
426
|
-
for (const namespace of secondaryNamespaces) {
|
|
427
|
-
this.secondaryAccountSyncTeardowns.push(
|
|
428
|
-
attachLifecycle({
|
|
429
|
-
eventManager: this.eventManager,
|
|
430
|
-
subscribe: undefined,
|
|
431
|
-
subscribeAccounts: listener =>
|
|
432
|
-
connector.onAccountsChanged!(listener, {
|
|
433
|
-
connectionMode,
|
|
434
|
-
namespace,
|
|
435
|
-
walletId
|
|
436
|
-
}),
|
|
437
|
-
onAccountsChanged: accounts => {
|
|
438
|
-
// An empty list from a SECONDARY namespace means that namespace's
|
|
439
|
-
// own authorization ended — NOT the session. Only the active
|
|
440
|
-
// namespace's stream may signal the full wallet-initiated
|
|
441
|
-
// disconnect (handleAccountsChanged's empty-list path).
|
|
442
|
-
if (accounts.length === 0) return
|
|
443
|
-
this.handleAccountsChanged(accounts, namespace)
|
|
444
|
-
},
|
|
445
|
-
getRouting: () => ({ connectionMode, walletId, namespace }),
|
|
446
|
-
connectionMode,
|
|
447
|
-
walletId,
|
|
448
|
-
namespace,
|
|
449
|
-
runtime
|
|
450
|
-
})
|
|
451
|
-
)
|
|
452
|
-
}
|
|
453
291
|
}
|
|
454
292
|
|
|
455
293
|
private async connectWithURIPoll(
|
|
@@ -648,32 +486,14 @@ export class ConnectionService {
|
|
|
648
486
|
await this.activeConnector.disconnect()
|
|
649
487
|
}
|
|
650
488
|
} finally {
|
|
651
|
-
this.
|
|
489
|
+
this.activeConnector = null
|
|
490
|
+
this.sessionManager.clearSession()
|
|
491
|
+
const session = this.sessionManager.getSession()
|
|
492
|
+
this.eventManager.emit('disconnected', undefined)
|
|
493
|
+
this.eventManager.emit('sessionChanged', { session })
|
|
652
494
|
}
|
|
653
495
|
}
|
|
654
496
|
|
|
655
|
-
/**
|
|
656
|
-
* Clears core's own local state (session, active connector reference,
|
|
657
|
-
* lifecycle subscription) and emits the disconnect events. Does NOT itself
|
|
658
|
-
* call the connector's `disconnect()` — that happens in each CALLER,
|
|
659
|
-
* because the two callers need different semantics: the public
|
|
660
|
-
* `disconnect()` above awaits it (the caller may care whether the wallet
|
|
661
|
-
* side actually tore down); the wallet-initiated path in
|
|
662
|
-
* `handleAccountsChanged` fires it and swallows the result (the wallet
|
|
663
|
-
* already ended the session on its own side, but the connector's own
|
|
664
|
-
* LOCAL state — e.g. a cached account/uuid — still needs clearing, or a
|
|
665
|
-
* later connect to a different wallet reads it as stale and never
|
|
666
|
-
* rebinds).
|
|
667
|
-
*/
|
|
668
|
-
private finishDisconnect(): void {
|
|
669
|
-
this.teardownLifecycle()
|
|
670
|
-
this.activeConnector = null
|
|
671
|
-
this.sessionManager.clearSession()
|
|
672
|
-
const session = this.sessionManager.getSession()
|
|
673
|
-
this.eventManager.emit('disconnected', undefined)
|
|
674
|
-
this.eventManager.emit('sessionChanged', { session })
|
|
675
|
-
}
|
|
676
|
-
|
|
677
497
|
getConnectionURI(): string | undefined {
|
|
678
498
|
if (
|
|
679
499
|
this.activeConnector &&
|
|
@@ -234,37 +234,4 @@ describe('NetworkSwitchService', () => {
|
|
|
234
234
|
expect(events[0].data.operation).toBe('switchNetwork')
|
|
235
235
|
expect(events[0].data.chainId).toBe('eip155:137')
|
|
236
236
|
})
|
|
237
|
-
|
|
238
|
-
it('includes the new address on the emitted networkSwitched event', async () => {
|
|
239
|
-
sessionManager.updateSession({
|
|
240
|
-
connectionMode: 'injected',
|
|
241
|
-
activeWallet: {
|
|
242
|
-
id: 'metamask',
|
|
243
|
-
name: 'MetaMask',
|
|
244
|
-
metadata: {
|
|
245
|
-
icon: 'test',
|
|
246
|
-
downloadUrl: 'test',
|
|
247
|
-
supportedChains: ['eip155:1', 'eip155:137']
|
|
248
|
-
},
|
|
249
|
-
extensionInjectedProvider: {
|
|
250
|
-
supportedNetworkIds: ['eip155:1', 'eip155:137']
|
|
251
|
-
}
|
|
252
|
-
} as any
|
|
253
|
-
})
|
|
254
|
-
mockConnector.switchNetwork = vi.fn().mockResolvedValue({
|
|
255
|
-
address: '0xabc',
|
|
256
|
-
availableAddresses: [{ address: '0xabc', networkId: 'eip155:137' }]
|
|
257
|
-
})
|
|
258
|
-
const switched: Array<{ network: unknown; address?: string }> = []
|
|
259
|
-
eventManager.on('networkSwitched', d => switched.push(d))
|
|
260
|
-
|
|
261
|
-
await networkSwitchService.switchNetwork('eip155:137')
|
|
262
|
-
|
|
263
|
-
expect(switched).toEqual([
|
|
264
|
-
{
|
|
265
|
-
network: expect.objectContaining({ id: 'eip155:137' }),
|
|
266
|
-
address: '0xabc'
|
|
267
|
-
}
|
|
268
|
-
])
|
|
269
|
-
})
|
|
270
237
|
})
|
|
@@ -188,10 +188,7 @@ export class NetworkSwitchService {
|
|
|
188
188
|
})
|
|
189
189
|
})
|
|
190
190
|
|
|
191
|
-
this.eventManager.emit('networkSwitched', {
|
|
192
|
-
network,
|
|
193
|
-
address: result.address
|
|
194
|
-
})
|
|
191
|
+
this.eventManager.emit('networkSwitched', { network })
|
|
195
192
|
this.eventManager.emit('sessionChanged', {
|
|
196
193
|
session: this.sessionManager.getSession()
|
|
197
194
|
})
|