@meshconnect/uwc-core 1.2.3-snapshot.bd9aae7 → 1.2.3-snapshot.dcfdbbf
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 +5 -42
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +0 -4
- package/dist/events.js.map +1 -1
- package/dist/observability/focus-tracker.d.ts +12 -25
- package/dist/observability/focus-tracker.d.ts.map +1 -1
- package/dist/observability/focus-tracker.js +19 -42
- package/dist/observability/focus-tracker.js.map +1 -1
- package/dist/observability/instrument-handoff.d.ts.map +1 -1
- package/dist/observability/instrument-handoff.js +6 -24
- package/dist/observability/instrument-handoff.js.map +1 -1
- package/dist/observability/lifecycle-bridge.d.ts +31 -13
- package/dist/observability/lifecycle-bridge.d.ts.map +1 -1
- package/dist/observability/lifecycle-bridge.js +167 -92
- package/dist/observability/lifecycle-bridge.js.map +1 -1
- package/dist/observability/telemetry.d.ts +0 -8
- package/dist/observability/telemetry.d.ts.map +1 -1
- package/dist/observability/telemetry.js +5 -49
- package/dist/observability/telemetry.js.map +1 -1
- package/dist/services/connection-service.d.ts +43 -0
- package/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +163 -9
- 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 +4 -1
- package/dist/services/network-switch-service.js.map +1 -1
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +1 -7
- package/dist/universal-wallet-connector.js.map +1 -1
- package/package.json +5 -5
- package/src/events.ts +6 -50
- package/src/observability/focus-tracker.test.ts +8 -60
- package/src/observability/focus-tracker.ts +23 -59
- package/src/observability/instrument-handoff.test.ts +0 -42
- package/src/observability/instrument-handoff.ts +6 -26
- package/src/observability/lifecycle-bridge.test.ts +197 -0
- package/src/observability/lifecycle-bridge.ts +211 -99
- package/src/observability/telemetry.test.ts +0 -35
- package/src/observability/telemetry.ts +6 -58
- package/src/services/connection-service.test.ts +272 -0
- package/src/services/connection-service.ts +190 -10
- package/src/services/network-switch-service.test.ts +33 -0
- package/src/services/network-switch-service.ts +4 -1
- package/src/universal-wallet-connector.ts +1 -11
|
@@ -29,11 +29,23 @@ export interface AttachLifecycleOptions {
|
|
|
29
29
|
* The active connector's `onLifecycleEvent` (already bound). `undefined` when the
|
|
30
30
|
* connector can't observe lifecycle at all; the value it returns is `undefined`
|
|
31
31
|
* when THIS session can't be observed (e.g. no live `.on`). Either way the
|
|
32
|
-
* bridge emits `lifecycleTelemetryUnavailable` and becomes a no-op
|
|
32
|
+
* bridge emits `lifecycleTelemetryUnavailable` and becomes a no-op FOR THIS
|
|
33
|
+
* STREAM — `subscribeAccounts` below is attached independently.
|
|
33
34
|
*/
|
|
34
35
|
subscribe:
|
|
35
36
|
| ((l: (e: WalletLifecycleEvent) => void) => (() => void) | undefined)
|
|
36
37
|
| undefined
|
|
38
|
+
/**
|
|
39
|
+
* The active connector's `onAccountsChanged` (already bound), carrying the
|
|
40
|
+
* REAL account list — a separate concern from `subscribe` above (some
|
|
41
|
+
* connectors, e.g. Tron today, implement one without the other). Absent
|
|
42
|
+
* entirely when the connector doesn't support real account-sync.
|
|
43
|
+
*/
|
|
44
|
+
subscribeAccounts?:
|
|
45
|
+
| ((l: (accounts: string[]) => void) => (() => void) | undefined)
|
|
46
|
+
| undefined
|
|
47
|
+
/** Invoked with the resolved (post-echo-filter) real account list whenever `subscribeAccounts` delivers one. Required whenever `subscribeAccounts` is provided. */
|
|
48
|
+
onAccountsChanged?: (accounts: string[]) => void
|
|
37
49
|
/** Resolved per-event so a mid-session account/chain change is reflected. */
|
|
38
50
|
getRouting: () => LifecycleRouting
|
|
39
51
|
connectionMode: ConnectionMode
|
|
@@ -42,8 +54,9 @@ export interface AttachLifecycleOptions {
|
|
|
42
54
|
/** Direct page vs production iframe — stamped on `lifecycleTelemetryUnavailable`. */
|
|
43
55
|
runtime?: 'direct' | 'iframe'
|
|
44
56
|
/**
|
|
45
|
-
* Window (ms) within which a wallet `chainChanged` is
|
|
46
|
-
* UWC-initiated `switchNetwork` and suppressed.
|
|
57
|
+
* Window (ms) within which a wallet `chainChanged`/`accountsChanged` is
|
|
58
|
+
* treated as the echo of a UWC-initiated `switchNetwork` and suppressed.
|
|
59
|
+
* Default 3000.
|
|
47
60
|
*/
|
|
48
61
|
echoWindowMs?: number
|
|
49
62
|
/**
|
|
@@ -53,29 +66,37 @@ export interface AttachLifecycleOptions {
|
|
|
53
66
|
* provider's echo would otherwise surface as wallet-initiated. Same one-shot
|
|
54
67
|
* consume semantics as a bus-armed switch.
|
|
55
68
|
*/
|
|
56
|
-
initialSwitch?:
|
|
69
|
+
initialSwitch?:
|
|
70
|
+
| { chainId: NetworkId; address?: string; at: number }
|
|
71
|
+
| undefined
|
|
57
72
|
now?: () => number
|
|
58
73
|
}
|
|
59
74
|
|
|
60
75
|
/**
|
|
61
76
|
* Bridge a connector's PII-safe `WalletLifecycleEvent` stream onto the event bus
|
|
62
77
|
* as `walletAccountChanged` / `walletChainChanged` / `walletDisconnected` /
|
|
63
|
-
* `walletSessionExpired
|
|
78
|
+
* `walletSessionExpired`, AND (separately) forward the connector's real
|
|
79
|
+
* account list to `onAccountsChanged` for session-sync.
|
|
64
80
|
*
|
|
65
|
-
*
|
|
81
|
+
* Three correctness concerns are handled here:
|
|
66
82
|
* - **Echo suppression.** When UWC drives a `switchNetwork`, the wallet emits its
|
|
67
|
-
* own `chainChanged` for the same
|
|
68
|
-
* `networkSwitched` / the switch handoff, so re-emitting it
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
* - **Availability is signal.** If the connector can't observe
|
|
72
|
-
* `lifecycleTelemetryUnavailable` ONCE
|
|
73
|
-
*
|
|
83
|
+
* own `chainChanged`/`accountsChanged` for the same change — that's already
|
|
84
|
+
* captured by UWC's `networkSwitched` / the switch handoff, so re-emitting it
|
|
85
|
+
* would double-count. We watch the bus for `networkSwitched` and drop a
|
|
86
|
+
* matching echo of either kind inside `echoWindowMs`.
|
|
87
|
+
* - **Availability is signal, per stream.** If the connector can't observe
|
|
88
|
+
* telemetry lifecycle, we emit `lifecycleTelemetryUnavailable` ONCE — but
|
|
89
|
+
* that signal is specific to the TELEMETRY stream. A connector missing
|
|
90
|
+
* `onLifecycleEvent` but implementing `onAccountsChanged` (e.g. Tron today)
|
|
91
|
+
* still gets real account-sync; the two streams are independently attached.
|
|
92
|
+
* - **Shared emission budget.** Both streams count against the same
|
|
93
|
+
* `MAX_LIFECYCLE_EVENTS_PER_ATTACHMENT` ceiling — a storming provider
|
|
94
|
+
* doesn't get double budget just because two streams exist.
|
|
74
95
|
*
|
|
75
|
-
* @returns a teardown that removes
|
|
96
|
+
* @returns a teardown that removes both connector subscriptions AND the bus watcher.
|
|
76
97
|
*/
|
|
77
98
|
export function attachLifecycle(opts: AttachLifecycleOptions): () => void {
|
|
78
|
-
const { eventManager, subscribe, getRouting } = opts
|
|
99
|
+
const { eventManager, subscribe, subscribeAccounts, getRouting } = opts
|
|
79
100
|
const runtime = opts.runtime ?? 'direct'
|
|
80
101
|
const now = opts.now ?? Date.now
|
|
81
102
|
const echoWindowMs = opts.echoWindowMs ?? 3000
|
|
@@ -89,11 +110,11 @@ export function attachLifecycle(opts: AttachLifecycleOptions): () => void {
|
|
|
89
110
|
runtime
|
|
90
111
|
})
|
|
91
112
|
|
|
92
|
-
if (!subscribe) {
|
|
113
|
+
if (!subscribe && !subscribeAccounts) {
|
|
93
114
|
// In the production iframe the injected subscription is deliberately not
|
|
94
115
|
// attempted (the provider is a Comlink proxy) — the BRIDGE, not the connector,
|
|
95
|
-
// is why lifecycle is unobservable. On the direct page, a connector with
|
|
96
|
-
//
|
|
116
|
+
// is why lifecycle is unobservable. On the direct page, a connector with
|
|
117
|
+
// neither stream simply has no lifecycle support for this namespace yet.
|
|
97
118
|
emitUnavailable(
|
|
98
119
|
runtime === 'iframe'
|
|
99
120
|
? 'bridge_subscription_unavailable'
|
|
@@ -102,115 +123,206 @@ export function attachLifecycle(opts: AttachLifecycleOptions): () => void {
|
|
|
102
123
|
return () => {}
|
|
103
124
|
}
|
|
104
125
|
|
|
105
|
-
// Record UWC-initiated switches so
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
//
|
|
110
|
-
//
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
|
|
114
|
-
|
|
126
|
+
// Record UWC-initiated switches so a wallet echo can be dropped. Armed from
|
|
127
|
+
// BOTH signals: `awaitingWallet` (operation switchNetwork) arms at the
|
|
128
|
+
// handoff START, because some providers emit their chainChanged BEFORE the
|
|
129
|
+
// switch request resolves, and `networkSwitched` re-arms at completion
|
|
130
|
+
// (also carrying the resolved address, for the accounts echo check — the
|
|
131
|
+
// target address isn't known before the switch resolves, so there is no
|
|
132
|
+
// early-arm equivalent for the address half).
|
|
133
|
+
//
|
|
134
|
+
// A LIST, not a single slot: two rapid, back-to-back switches (A then B)
|
|
135
|
+
// each get their own pending entry. A single slot would have the SECOND
|
|
136
|
+
// switch's arm silently erase the FIRST switch's still-unconsumed window,
|
|
137
|
+
// so a delayed wallet echo for A (arriving after B has already completed)
|
|
138
|
+
// would be misclassified as a genuine wallet-initiated change instead of
|
|
139
|
+
// the suppressed echo it actually is.
|
|
140
|
+
type PendingSwitch = {
|
|
141
|
+
chainId?: NetworkId | undefined
|
|
142
|
+
address?: string | undefined
|
|
143
|
+
at: number
|
|
144
|
+
}
|
|
145
|
+
let pendingSwitches: PendingSwitch[] = opts.initialSwitch
|
|
146
|
+
? [opts.initialSwitch]
|
|
147
|
+
: []
|
|
148
|
+
const pruneExpired = () => {
|
|
149
|
+
pendingSwitches = pendingSwitches.filter(p => now() - p.at <= echoWindowMs)
|
|
150
|
+
}
|
|
151
|
+
// `awaitingWallet` then `networkSwitched` are usually the START and
|
|
152
|
+
// COMPLETION of the SAME switch — re-arming updates that existing entry
|
|
153
|
+
// (refreshing `at`, adding the resolved address) rather than pushing a
|
|
154
|
+
// duplicate, which would otherwise leave a phantom entry behind after only
|
|
155
|
+
// one of the two is consumed by a single wallet echo.
|
|
156
|
+
const armOrRearm = (partial: {
|
|
157
|
+
chainId?: NetworkId | undefined
|
|
158
|
+
address?: string | undefined
|
|
159
|
+
}) => {
|
|
160
|
+
pruneExpired()
|
|
161
|
+
if (partial.chainId !== undefined) {
|
|
162
|
+
const idx = pendingSwitches.findIndex(p => p.chainId === partial.chainId)
|
|
163
|
+
if (idx !== -1) {
|
|
164
|
+
pendingSwitches[idx] = {
|
|
165
|
+
...pendingSwitches[idx]!,
|
|
166
|
+
...partial,
|
|
167
|
+
at: now()
|
|
168
|
+
}
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
pendingSwitches.push({ ...partial, at: now() })
|
|
173
|
+
}
|
|
115
174
|
const offAwaiting = eventManager.on('awaitingWallet', data => {
|
|
116
175
|
if (data.operation === 'switchNetwork' && data.chainId !== undefined) {
|
|
117
|
-
|
|
176
|
+
armOrRearm({ chainId: data.chainId })
|
|
118
177
|
}
|
|
119
178
|
})
|
|
120
179
|
const offSwitched = eventManager.on('networkSwitched', data => {
|
|
121
|
-
|
|
180
|
+
armOrRearm({ chainId: data.network.id, address: data.address })
|
|
122
181
|
})
|
|
123
182
|
const detachBusWatchers = () => {
|
|
124
183
|
offAwaiting()
|
|
125
184
|
offSwitched()
|
|
126
185
|
}
|
|
127
186
|
|
|
187
|
+
// Emission budget for THIS attachment, shared across both streams —
|
|
188
|
+
// suppressed echoes don't spend it.
|
|
189
|
+
let emitted = 0
|
|
190
|
+
let capNotified = false
|
|
191
|
+
const overCap = (): boolean => {
|
|
192
|
+
if (emitted >= MAX_LIFECYCLE_EVENTS_PER_ATTACHMENT) {
|
|
193
|
+
// Mark the trip ONCE — a silent cap would be indistinguishable from a
|
|
194
|
+
// provider that simply went quiet.
|
|
195
|
+
if (!capNotified) {
|
|
196
|
+
capNotified = true
|
|
197
|
+
emitUnavailable('emission_cap')
|
|
198
|
+
}
|
|
199
|
+
return true
|
|
200
|
+
}
|
|
201
|
+
return false
|
|
202
|
+
}
|
|
203
|
+
|
|
128
204
|
// Defense-in-depth: a connector whose subscribe THROWS (e.g. a provider whose
|
|
129
205
|
// `.on` rejects an un-proxied handler across the iframe bridge) must degrade to
|
|
130
206
|
// "unavailable", never break the connect path that called us.
|
|
131
207
|
let unsub: (() => void) | undefined
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
208
|
+
if (subscribe) {
|
|
209
|
+
try {
|
|
210
|
+
unsub = subscribe(e => {
|
|
211
|
+
// The whole body is guarded: wallet SDKs dispatch their listeners
|
|
212
|
+
// unguarded (e.g. TonConnect's bare `forEach`), so a throw here would
|
|
213
|
+
// abort delivery to the wallet's OWN later subscribers, including one
|
|
214
|
+
// resolving a pending connect. Telemetry must never do that.
|
|
215
|
+
try {
|
|
216
|
+
if (overCap()) return
|
|
217
|
+
const routing = getRouting()
|
|
218
|
+
switch (e.type) {
|
|
219
|
+
case 'accountChanged':
|
|
220
|
+
emitted++
|
|
221
|
+
eventManager.emit('walletAccountChanged', routing)
|
|
222
|
+
return
|
|
223
|
+
case 'chainChanged': {
|
|
224
|
+
pruneExpired()
|
|
225
|
+
const idx =
|
|
226
|
+
e.chainId !== undefined
|
|
227
|
+
? pendingSwitches.findIndex(
|
|
228
|
+
p => p.chainId !== undefined && p.chainId === e.chainId
|
|
229
|
+
)
|
|
230
|
+
: -1
|
|
231
|
+
if (idx !== -1) {
|
|
232
|
+
// Consume the echo: a UWC switch produces exactly one wallet echo, so
|
|
233
|
+
// a LATER chainChanged to the same chain inside the window is a genuine
|
|
234
|
+
// user-initiated switch-back and must not be suppressed. Only the
|
|
235
|
+
// chain half is consumed — a still-pending address echo (if the
|
|
236
|
+
// switch also changed address format) must still be checked; drop the
|
|
237
|
+
// entry entirely once nothing is left pending on it.
|
|
238
|
+
const entry = pendingSwitches[idx]!
|
|
239
|
+
if (entry.address !== undefined) {
|
|
240
|
+
pendingSwitches[idx] = { ...entry, chainId: undefined }
|
|
241
|
+
} else {
|
|
242
|
+
pendingSwitches.splice(idx, 1)
|
|
243
|
+
}
|
|
244
|
+
return
|
|
245
|
+
}
|
|
246
|
+
emitted++
|
|
247
|
+
eventManager.emit('walletChainChanged', {
|
|
248
|
+
...routing,
|
|
249
|
+
...(e.chainId !== undefined ? { chainId: e.chainId } : {})
|
|
250
|
+
})
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
case 'disconnect':
|
|
254
|
+
emitted++
|
|
255
|
+
eventManager.emit('walletDisconnected', routing)
|
|
256
|
+
return
|
|
257
|
+
case 'sessionExpired':
|
|
258
|
+
emitted++
|
|
259
|
+
eventManager.emit('walletSessionExpired', routing)
|
|
260
|
+
return
|
|
149
261
|
}
|
|
150
|
-
|
|
262
|
+
} catch {
|
|
263
|
+
// Swallowed: better to drop one lifecycle record than to break the
|
|
264
|
+
// wallet's event dispatch.
|
|
151
265
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
266
|
+
})
|
|
267
|
+
} catch {
|
|
268
|
+
unsub = undefined
|
|
269
|
+
}
|
|
270
|
+
if (!unsub) {
|
|
271
|
+
// The connector accepted the subscribe call but this session has no
|
|
272
|
+
// observable telemetry surface (returned undefined or threw). Over the
|
|
273
|
+
// bridge, that absence is the bridge's doing, not the provider's.
|
|
274
|
+
emitUnavailable(
|
|
275
|
+
runtime === 'iframe'
|
|
276
|
+
? 'bridge_subscription_unavailable'
|
|
277
|
+
: 'provider_subscription_unavailable'
|
|
278
|
+
)
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
let unsubAccounts: (() => void) | undefined
|
|
283
|
+
if (subscribeAccounts) {
|
|
284
|
+
try {
|
|
285
|
+
unsubAccounts = subscribeAccounts(accounts => {
|
|
286
|
+
try {
|
|
287
|
+
if (overCap()) return
|
|
288
|
+
const primary = accounts[0]
|
|
289
|
+
pruneExpired()
|
|
290
|
+
const idx =
|
|
291
|
+
accounts.length > 0
|
|
292
|
+
? pendingSwitches.findIndex(
|
|
293
|
+
p => p.address !== undefined && p.address === primary
|
|
294
|
+
)
|
|
295
|
+
: -1
|
|
296
|
+
if (idx !== -1) {
|
|
297
|
+
// Same one-shot consume as the chainChanged echo above, mirrored
|
|
298
|
+
// for the address half.
|
|
299
|
+
const entry = pendingSwitches[idx]!
|
|
300
|
+
if (entry.chainId !== undefined) {
|
|
301
|
+
pendingSwitches[idx] = { ...entry, address: undefined }
|
|
302
|
+
} else {
|
|
303
|
+
pendingSwitches.splice(idx, 1)
|
|
170
304
|
}
|
|
171
|
-
emitted++
|
|
172
|
-
eventManager.emit('walletChainChanged', {
|
|
173
|
-
...routing,
|
|
174
|
-
...(e.chainId !== undefined ? { chainId: e.chainId } : {})
|
|
175
|
-
})
|
|
176
305
|
return
|
|
177
306
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
case 'sessionExpired':
|
|
183
|
-
emitted++
|
|
184
|
-
eventManager.emit('walletSessionExpired', routing)
|
|
185
|
-
return
|
|
307
|
+
emitted++
|
|
308
|
+
opts.onAccountsChanged?.(accounts)
|
|
309
|
+
} catch {
|
|
310
|
+
// Swallowed: never break the wallet SDK's own listener dispatch.
|
|
186
311
|
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
})
|
|
192
|
-
} catch {
|
|
193
|
-
unsub = undefined
|
|
312
|
+
})
|
|
313
|
+
} catch {
|
|
314
|
+
unsubAccounts = undefined
|
|
315
|
+
}
|
|
194
316
|
}
|
|
195
317
|
|
|
196
|
-
|
|
197
|
-
// surface (returned undefined) or threw — treat as "unavailable" and don't
|
|
198
|
-
// leave the bus watchers attached.
|
|
199
|
-
if (!unsub) {
|
|
318
|
+
if (!unsub && !unsubAccounts) {
|
|
200
319
|
detachBusWatchers()
|
|
201
|
-
// `onLifecycleEvent` ran but exposed no live subscription surface for this
|
|
202
|
-
// session (returned undefined or threw). Over the bridge, that absence is the
|
|
203
|
-
// bridge's doing, not the provider's.
|
|
204
|
-
emitUnavailable(
|
|
205
|
-
runtime === 'iframe'
|
|
206
|
-
? 'bridge_subscription_unavailable'
|
|
207
|
-
: 'provider_subscription_unavailable'
|
|
208
|
-
)
|
|
209
320
|
return () => {}
|
|
210
321
|
}
|
|
211
322
|
|
|
212
323
|
return () => {
|
|
213
324
|
detachBusWatchers()
|
|
214
|
-
unsub()
|
|
325
|
+
unsub?.()
|
|
326
|
+
unsubAccounts?.()
|
|
215
327
|
}
|
|
216
328
|
}
|
|
@@ -520,41 +520,6 @@ describe('normalizeEvent', () => {
|
|
|
520
520
|
expect(JSON.stringify(out)).not.toContain('0xAbC123')
|
|
521
521
|
})
|
|
522
522
|
|
|
523
|
-
it('normalizes relayDisconnected as an unclassified walletConnect signal, message scrubbed', () => {
|
|
524
|
-
const out = normalizeEvent(
|
|
525
|
-
'relayDisconnected',
|
|
526
|
-
{
|
|
527
|
-
error: {
|
|
528
|
-
type: 'unknown',
|
|
529
|
-
message: 'transport closed 0xAbC1230000000000000000000000000000000000'
|
|
530
|
-
}
|
|
531
|
-
},
|
|
532
|
-
SDK_SESSION,
|
|
533
|
-
NOW
|
|
534
|
-
)
|
|
535
|
-
expect(out.connectionMode).toBe('walletConnect')
|
|
536
|
-
expect(out.source).toBe('provider')
|
|
537
|
-
// A drop is observational, NOT a failure — must never carry a failureKind
|
|
538
|
-
// (else it inflates failure metrics).
|
|
539
|
-
expect(out.failureKind).toBeUndefined()
|
|
540
|
-
expect(JSON.stringify(out)).not.toContain('0xAbC123')
|
|
541
|
-
})
|
|
542
|
-
|
|
543
|
-
it('normalizes relayDisconnected with no reason (clean drop) — no error field', () => {
|
|
544
|
-
const out = normalizeEvent('relayDisconnected', {}, SDK_SESSION, NOW)
|
|
545
|
-
expect(out.connectionMode).toBe('walletConnect')
|
|
546
|
-
expect(out.error).toBeUndefined()
|
|
547
|
-
expect(out.failureKind).toBeUndefined()
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
it('normalizes relayReconnected as an unclassified walletConnect signal', () => {
|
|
551
|
-
const out = normalizeEvent('relayReconnected', undefined, SDK_SESSION, NOW)
|
|
552
|
-
expect(out.name).toBe('relayReconnected')
|
|
553
|
-
expect(out.connectionMode).toBe('walletConnect')
|
|
554
|
-
expect(out.source).toBe('provider')
|
|
555
|
-
expect(out.failureKind).toBeUndefined()
|
|
556
|
-
})
|
|
557
|
-
|
|
558
523
|
it('normalizes walletConnectInitError as a walletConnect client_init_failed, message scrubbed', () => {
|
|
559
524
|
const out = normalizeEvent(
|
|
560
525
|
'walletConnectInitError',
|
|
@@ -154,14 +154,6 @@ export interface UWCTelemetryEvent {
|
|
|
154
154
|
* handoffCovered to avoid double-counting.
|
|
155
155
|
*/
|
|
156
156
|
handoffCovered?: boolean | undefined
|
|
157
|
-
/** `pageVisibleDuringHandoff` only: how long the page was hidden for that segment (ms). */
|
|
158
|
-
hiddenMs?: number | undefined
|
|
159
|
-
/** Handoff terminals: cumulative time the page was backgrounded during the handoff (ms). */
|
|
160
|
-
backgroundedMs?: number | undefined
|
|
161
|
-
/** Handoff terminals: how many times the page was backgrounded during the handoff. */
|
|
162
|
-
backgroundCount?: number | undefined
|
|
163
|
-
/** Handoff terminals, WalletConnect only: relay-socket drops seen while the handoff was pending. */
|
|
164
|
-
relayDropCount?: number | undefined
|
|
165
157
|
}
|
|
166
158
|
|
|
167
159
|
/**
|
|
@@ -370,10 +362,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
370
362
|
durationMs: d.durationMs,
|
|
371
363
|
timedOut: d.timedOut,
|
|
372
364
|
timeoutMs: d.timeoutMs,
|
|
373
|
-
targetAlreadyActive: d.targetAlreadyActive
|
|
374
|
-
backgroundedMs: d.backgroundedMs,
|
|
375
|
-
backgroundCount: d.backgroundCount,
|
|
376
|
-
relayDropCount: d.relayDropCount
|
|
365
|
+
targetAlreadyActive: d.targetAlreadyActive
|
|
377
366
|
}
|
|
378
367
|
}
|
|
379
368
|
case 'walletRejected': {
|
|
@@ -392,10 +381,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
392
381
|
targetAlreadyActive: d.targetAlreadyActive,
|
|
393
382
|
errorCode: d.errorCode,
|
|
394
383
|
failureKind: d.failureKind,
|
|
395
|
-
source: d.source
|
|
396
|
-
backgroundedMs: d.backgroundedMs,
|
|
397
|
-
backgroundCount: d.backgroundCount,
|
|
398
|
-
relayDropCount: d.relayDropCount
|
|
384
|
+
source: d.source
|
|
399
385
|
}
|
|
400
386
|
}
|
|
401
387
|
case 'walletTimedOut': {
|
|
@@ -410,10 +396,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
410
396
|
handoffId: d.handoffId,
|
|
411
397
|
durationMs: d.durationMs,
|
|
412
398
|
timeoutMs: d.timeoutMs,
|
|
413
|
-
targetAlreadyActive: d.targetAlreadyActive
|
|
414
|
-
backgroundedMs: d.backgroundedMs,
|
|
415
|
-
backgroundCount: d.backgroundCount,
|
|
416
|
-
relayDropCount: d.relayDropCount
|
|
399
|
+
targetAlreadyActive: d.targetAlreadyActive
|
|
417
400
|
}
|
|
418
401
|
}
|
|
419
402
|
case 'walletFailed': {
|
|
@@ -433,10 +416,7 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
433
416
|
errorCode: d.errorCode,
|
|
434
417
|
failureKind: d.failureKind,
|
|
435
418
|
source: d.source,
|
|
436
|
-
error: { type: d.error.type, message: scrubMessage(d.error.message) }
|
|
437
|
-
backgroundedMs: d.backgroundedMs,
|
|
438
|
-
backgroundCount: d.backgroundCount,
|
|
439
|
-
relayDropCount: d.relayDropCount
|
|
419
|
+
error: { type: d.error.type, message: scrubMessage(d.error.message) }
|
|
440
420
|
}
|
|
441
421
|
}
|
|
442
422
|
case 'capabilitiesUpdated': {
|
|
@@ -468,15 +448,8 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
468
448
|
}
|
|
469
449
|
case 'pageHiddenDuringHandoff':
|
|
470
450
|
case 'pageVisibleDuringHandoff': {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
const d = data as UWCEventMap['pageVisibleDuringHandoff']
|
|
474
|
-
return {
|
|
475
|
-
...base,
|
|
476
|
-
handoffId: d.handoffId,
|
|
477
|
-
platform: d.platform,
|
|
478
|
-
hiddenMs: d.hiddenMs
|
|
479
|
-
}
|
|
451
|
+
const d = data as UWCEventMap['pageHiddenDuringHandoff']
|
|
452
|
+
return { ...base, handoffId: d.handoffId, platform: d.platform }
|
|
480
453
|
}
|
|
481
454
|
case 'bridgeError': {
|
|
482
455
|
const d = data as UWCEventMap['bridgeError']
|
|
@@ -500,31 +473,6 @@ function buildTelemetryEvent<K extends UWCEventName>(
|
|
|
500
473
|
error: { type: d.error.type, message: scrubMessage(d.error.message) }
|
|
501
474
|
}
|
|
502
475
|
}
|
|
503
|
-
case 'relayDisconnected': {
|
|
504
|
-
const d = data as UWCEventMap['relayDisconnected']
|
|
505
|
-
// The relay socket dropped. Observational, NOT a failure: a drop only
|
|
506
|
-
// matters if a round-trip was in flight — deliberately left without a
|
|
507
|
-
// `failureKind` so it never inflates failure metrics. Pair with
|
|
508
|
-
// `relayReconnected` (same sdkSessionId) for the dead-window duration.
|
|
509
|
-
return {
|
|
510
|
-
...base,
|
|
511
|
-
connectionMode: 'walletConnect',
|
|
512
|
-
source: 'provider',
|
|
513
|
-
...(d.error
|
|
514
|
-
? {
|
|
515
|
-
error: {
|
|
516
|
-
type: d.error.type,
|
|
517
|
-
message: scrubMessage(d.error.message)
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
: {})
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
case 'relayReconnected': {
|
|
524
|
-
// Socket recovered. Name-only + mode; the diagnostic value is the timestamp
|
|
525
|
-
// relative to the paired `relayDisconnected`.
|
|
526
|
-
return { ...base, connectionMode: 'walletConnect', source: 'provider' }
|
|
527
|
-
}
|
|
528
476
|
case 'walletConnectInitError': {
|
|
529
477
|
const d = data as UWCEventMap['walletConnectInitError']
|
|
530
478
|
// SignClient.init failed — the WC client never came up. Distinct from
|