@meshconnect/uwc-core 1.1.0 → 1.1.1-snapshot.7a97a0f

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.
@@ -161,7 +161,7 @@ export class ConnectionService {
161
161
  connectionMode: ConnectionMode,
162
162
  walletId: string,
163
163
  namespace: Namespace,
164
- initialSwitch?: { chainId: NetworkId; at: number }
164
+ initialSwitch?: { chainId: NetworkId; address?: string; at: number }
165
165
  ): void {
166
166
  this.teardownLifecycle()
167
167
  const connector = this.activeConnector
@@ -181,6 +181,16 @@ export class ConnectionService {
181
181
  walletId
182
182
  })
183
183
  : undefined,
184
+ subscribeAccounts: connector?.onAccountsChanged
185
+ ? listener =>
186
+ connector.onAccountsChanged!(listener, {
187
+ connectionMode,
188
+ namespace,
189
+ walletId
190
+ })
191
+ : undefined,
192
+ onAccountsChanged: accounts =>
193
+ this.handleAccountsChanged(accounts, namespace),
184
194
  getRouting: () => {
185
195
  const s = this.sessionManager.getSession()
186
196
  return {
@@ -203,6 +213,63 @@ export class ConnectionService {
203
213
  })
204
214
  }
205
215
 
216
+ /**
217
+ * Apply a REAL account-list change (from `Connector.onAccountsChanged`, via
218
+ * the lifecycle bridge's second stream) to the session. `namespace` is the
219
+ * namespace of the provider that reported the change — `activeAddress`
220
+ * only moves when that matches the session's currently active network; an
221
+ * empty list is a full, wallet-initiated disconnect.
222
+ */
223
+ private handleAccountsChanged(
224
+ accounts: string[],
225
+ namespace: Namespace
226
+ ): void {
227
+ if (accounts.length === 0) {
228
+ // Idempotency guard: this runs synchronously FROM INSIDE the
229
+ // connector's own accountsChanged dispatch — finishDisconnect() below
230
+ // tears down (among other things) the very subscription that is
231
+ // currently delivering this callback. If a wallet double-fires the
232
+ // empty-array disconnect signal (or one slips through before teardown
233
+ // takes effect), `activeConnector` is already null from the first call
234
+ // — skip re-clearing an already-cleared session and re-emitting
235
+ // disconnected/sessionChanged a second time. Scoped to THIS wallet-
236
+ // initiated path only; the public disconnect() below still always
237
+ // runs finishDisconnect() defensively, even with no active connector.
238
+ if (this.activeConnector === null) return
239
+ // The wallet ended the session on its own side, but the connector's
240
+ // OWN local state (e.g. EthereumWalletService's cached account/
241
+ // provider/uuid) is never cleared by finishDisconnect() below — it
242
+ // only clears core's sessionManager. Left uncleared, a later connect
243
+ // to a DIFFERENT wallet reads that stale cached account via
244
+ // checkExistingConnections() and never rebinds to the new wallet's
245
+ // provider/uuid, silently keeping the old wallet's address live under
246
+ // the new wallet's session. disconnect() is fire-and-forget and
247
+ // error-swallowed here: for injected connectors it's local-only
248
+ // cleanup (safe to call any number of times), and for protocol-based
249
+ // connectors that might reject on an already-closed session, there is
250
+ // no caller to propagate that error to anyway.
251
+ this.activeConnector?.disconnect?.().catch(() => {})
252
+ this.finishDisconnect()
253
+ return
254
+ }
255
+ const newAddress = accounts[0]!
256
+ const session = this.sessionManager.getSession()
257
+ const availableAddresses = session.availableAddresses.map(addr => {
258
+ const network = this.networks.find(n => n.id === addr.networkId)
259
+ return network?.namespace === namespace
260
+ ? { ...addr, address: newAddress }
261
+ : addr
262
+ })
263
+ const activeAddress =
264
+ session.activeNetwork?.namespace === namespace
265
+ ? newAddress
266
+ : session.activeAddress
267
+ this.sessionManager.updateSession({ activeAddress, availableAddresses })
268
+ this.eventManager.emit('sessionChanged', {
269
+ session: this.sessionManager.getSession()
270
+ })
271
+ }
272
+
206
273
  /**
207
274
  * Remove the active provider-lifecycle subscription (EIP-1193 `.on` / wallet-
208
275
  * adapter / SignClient listeners) WITHOUT disconnecting the wallet. Idempotent.
@@ -237,10 +304,15 @@ export class ConnectionService {
237
304
  session.connectionMode,
238
305
  session.activeWallet.id,
239
306
  session.activeNetwork.namespace,
240
- // The reattach follows a JUST-completed switch to the now-active chain
241
- // arm echo suppression for it so the new provider's echo isn't mistaken
242
- // for a wallet-initiated change (one-shot, same as bus-armed switches).
243
- { chainId: session.activeNetwork.id, at: Date.now() }
307
+ // The reattach follows a JUST-completed switch to the now-active chain
308
+ // (and address) — arm echo suppression for both so the new provider's
309
+ // echo isn't mistaken for a wallet-initiated change (one-shot, same as
310
+ // bus-armed switches).
311
+ {
312
+ chainId: session.activeNetwork.id,
313
+ ...(session.activeAddress && { address: session.activeAddress }),
314
+ at: Date.now()
315
+ }
244
316
  )
245
317
  }
246
318
 
@@ -420,14 +492,32 @@ export class ConnectionService {
420
492
  await this.activeConnector.disconnect()
421
493
  }
422
494
  } finally {
423
- this.activeConnector = null
424
- this.sessionManager.clearSession()
425
- const session = this.sessionManager.getSession()
426
- this.eventManager.emit('disconnected', undefined)
427
- this.eventManager.emit('sessionChanged', { session })
495
+ this.finishDisconnect()
428
496
  }
429
497
  }
430
498
 
499
+ /**
500
+ * Clears core's own local state (session, active connector reference,
501
+ * lifecycle subscription) and emits the disconnect events. Does NOT itself
502
+ * call the connector's `disconnect()` — that happens in each CALLER,
503
+ * because the two callers need different semantics: the public
504
+ * `disconnect()` above awaits it (the caller may care whether the wallet
505
+ * side actually tore down); the wallet-initiated path in
506
+ * `handleAccountsChanged` fires it and swallows the result (the wallet
507
+ * already ended the session on its own side, but the connector's own
508
+ * LOCAL state — e.g. a cached account/uuid — still needs clearing, or a
509
+ * later connect to a different wallet reads it as stale and never
510
+ * rebinds).
511
+ */
512
+ private finishDisconnect(): void {
513
+ this.teardownLifecycle()
514
+ this.activeConnector = null
515
+ this.sessionManager.clearSession()
516
+ const session = this.sessionManager.getSession()
517
+ this.eventManager.emit('disconnected', undefined)
518
+ this.eventManager.emit('sessionChanged', { session })
519
+ }
520
+
431
521
  getConnectionURI(): string | undefined {
432
522
  if (
433
523
  this.activeConnector &&
@@ -234,4 +234,37 @@ 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
+ })
237
270
  })
@@ -188,7 +188,10 @@ export class NetworkSwitchService {
188
188
  })
189
189
  })
190
190
 
191
- this.eventManager.emit('networkSwitched', { network })
191
+ this.eventManager.emit('networkSwitched', {
192
+ network,
193
+ address: result.address
194
+ })
192
195
  this.eventManager.emit('sessionChanged', {
193
196
  session: this.sessionManager.getSession()
194
197
  })
package/src/version.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  // Generated from package.json by scripts/generate-version.mjs — do not edit.
2
- export const UWC_CORE_VERSION = '1.1.0'
2
+ export const UWC_CORE_VERSION = '1.1.1'