@meshconnect/uwc-wallet-connect-connector 0.8.2 → 0.9.0
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/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/services/walletconnect-client.d.ts +89 -12
- package/dist/services/walletconnect-client.d.ts.map +1 -1
- package/dist/services/walletconnect-client.js +114 -19
- package/dist/services/walletconnect-client.js.map +1 -1
- package/dist/wallet-connect-connector.d.ts +7 -0
- package/dist/wallet-connect-connector.d.ts.map +1 -1
- package/dist/wallet-connect-connector.js +3 -3
- package/dist/wallet-connect-connector.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/services/walletconnect-client.test.ts +270 -12
- package/src/services/walletconnect-client.ts +186 -15
- package/src/wallet-connect-connector.test.ts +37 -0
- package/src/wallet-connect-connector.ts +25 -4
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,4BAA4B,CAAA;AAC1C,YAAY,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAA"}
|
|
@@ -6,25 +6,86 @@ import type { WalletConnectConfig } from '@meshconnect/uwc-types';
|
|
|
6
6
|
* session even if messages are high-cardinality (timestamps / urls).
|
|
7
7
|
*/
|
|
8
8
|
export declare const MAX_REPORTED_RELAY_ERRORS = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Cap on relay transport-lifecycle emissions PER EVENT TYPE (disconnect and
|
|
11
|
+
* reconnect budgeted independently) per client. A flapping socket IS the signal
|
|
12
|
+
* we want, but bounding emissions keeps a pathological relay from flooding
|
|
13
|
+
* telemetry on a long-lived session. Independent budgets so a drop-heavy flap
|
|
14
|
+
* that exhausts the disconnect cap can never silently swallow the paired
|
|
15
|
+
* reconnect — otherwise telemetry would show drops with no recovery for a socket
|
|
16
|
+
* that did recover.
|
|
17
|
+
*/
|
|
18
|
+
export declare const MAX_REPORTED_RELAY_LIFECYCLE = 50;
|
|
19
|
+
/**
|
|
20
|
+
* Optional telemetry sinks for the WalletConnect client. Covers both the relay
|
|
21
|
+
* transport (error / disconnect / reconnect) AND `SignClient.init` failures.
|
|
22
|
+
* Every field is opt-in — an empty observer leaves behaviour unchanged. All
|
|
23
|
+
* sinks are telemetry-only: they never alter connection behaviour, and a
|
|
24
|
+
* throwing sink is swallowed so it can't break the SDK's relayer dispatch (or,
|
|
25
|
+
* for `onInitError`, mask the always-rethrown init rejection).
|
|
26
|
+
*/
|
|
27
|
+
export interface WalletConnectTelemetryObserver {
|
|
28
|
+
/**
|
|
29
|
+
* Relay-socket failure: the relayer re-emits an abnormal WebSocket close under
|
|
30
|
+
* `RELAYER_EVENTS.error` (e.g. code 3000 "JWT Token is not yet valid" on clock
|
|
31
|
+
* skew), detached from the connect() promise. Deduped per message.
|
|
32
|
+
*/
|
|
33
|
+
onRelayError?: (error: unknown) => void;
|
|
34
|
+
/**
|
|
35
|
+
* `SignClient.init` failure (storage/crypto, NOT relay). Telemetry-only — the
|
|
36
|
+
* init rejection is always rethrown so `getClient()` callers see it unchanged.
|
|
37
|
+
*/
|
|
38
|
+
onInitError?: (error: unknown) => void;
|
|
39
|
+
/**
|
|
40
|
+
* Relay transport socket dropped (relayer `disconnect`), emitted once per
|
|
41
|
+
* outage. The lifecycle FACT — distinct from `onRelayError` (a close WITH an
|
|
42
|
+
* error). This is the CARE-263 signal a suspended WebView produces silently.
|
|
43
|
+
*/
|
|
44
|
+
onRelayDisconnect?: () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Relay socket recovered (relayer `connect` after a drop). Pair with
|
|
47
|
+
* `onRelayDisconnect` to measure the dead window.
|
|
48
|
+
*/
|
|
49
|
+
onRelayReconnect?: () => void;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Normalize the telemetry constructor slots into a `WalletConnectTelemetryObserver`.
|
|
53
|
+
*
|
|
54
|
+
* BACKWARD COMPATIBILITY: the published 0.8.x signature was positional —
|
|
55
|
+
* `(…, onRelayError?, onInitError?)`. This PR moved to a single observer object;
|
|
56
|
+
* the positional form is retained so external JS/TS consumers on 0.8.x don't
|
|
57
|
+
* break at runtime. Disambiguate on the first telemetry slot: a FUNCTION is the
|
|
58
|
+
* legacy `onRelayError` (the new form always passes an object). The legacy form
|
|
59
|
+
* predates `onRelayDisconnect`/`onRelayReconnect`, so those are simply absent.
|
|
60
|
+
*/
|
|
61
|
+
export declare function toTelemetryObserver(observerOrOnRelayError?: WalletConnectTelemetryObserver | ((error: unknown) => void), onInitError?: (error: unknown) => void): WalletConnectTelemetryObserver;
|
|
9
62
|
/**
|
|
10
63
|
* Service for managing WalletConnect client instance
|
|
11
64
|
*/
|
|
12
65
|
export declare class WalletConnectClient {
|
|
13
|
-
private config;
|
|
14
|
-
private onRelayError?;
|
|
15
|
-
private onInitError?;
|
|
16
66
|
private client;
|
|
17
67
|
private initPromise;
|
|
18
68
|
/** Dedupe relay-error reports so a reconnect storm surfaces once, not per tick. */
|
|
19
69
|
private readonly relayErrorDedup;
|
|
70
|
+
/** Independent bounded counters so drop/recover telemetry can never starve each other. */
|
|
71
|
+
private relayDisconnectCount;
|
|
72
|
+
private relayReconnectCount;
|
|
73
|
+
/** True once the socket has dropped — gates `relayReconnected` to real recoveries. */
|
|
74
|
+
private relayHasDisconnected;
|
|
75
|
+
private readonly config;
|
|
76
|
+
private readonly telemetryObserver;
|
|
20
77
|
/**
|
|
21
|
-
* @param
|
|
22
|
-
* `
|
|
23
|
-
*
|
|
24
|
-
* Opt-in: with nothing wired, behaviour is unchanged. Telemetry-only — the init
|
|
25
|
-
* rejection is always rethrown so `getClient()` callers see it exactly as before.
|
|
78
|
+
* @param telemetryObserver - optional telemetry sinks (see
|
|
79
|
+
* `WalletConnectTelemetryObserver`). Every field is opt-in; an empty/omitted
|
|
80
|
+
* observer leaves behaviour unchanged.
|
|
26
81
|
*/
|
|
27
|
-
constructor(config: WalletConnectConfig,
|
|
82
|
+
constructor(config: WalletConnectConfig, telemetryObserver?: WalletConnectTelemetryObserver);
|
|
83
|
+
/**
|
|
84
|
+
* @deprecated Pass a {@link WalletConnectTelemetryObserver}. The positional
|
|
85
|
+
* `(onRelayError, onInitError)` form is retained for backward compatibility
|
|
86
|
+
* with the published 0.8.x signature and will be removed in a future major.
|
|
87
|
+
*/
|
|
88
|
+
constructor(config: WalletConnectConfig, onRelayError?: (error: unknown) => void, onInitError?: (error: unknown) => void);
|
|
28
89
|
/**
|
|
29
90
|
* Initialize or get the WalletConnect client
|
|
30
91
|
*/
|
|
@@ -39,11 +100,27 @@ export declare class WalletConnectClient {
|
|
|
39
100
|
* "JWT Token is not yet valid" when the device clock is skewed past the relay's
|
|
40
101
|
* 120s leeway. That error is otherwise detached from the connect() promise, so it
|
|
41
102
|
* escapes the connect handoff and lands as an unhandled error. Forward it (deduped
|
|
42
|
-
* per message) so it becomes a classified signal instead.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
103
|
+
* per message) so it becomes a classified signal instead. This listener handles the
|
|
104
|
+
* relayer ERROR stream only; the socket's clean up/down lifecycle is captured
|
|
105
|
+
* separately by `attachRelayLifecycleListeners` (CARE-263 — a suspended WebView
|
|
106
|
+
* drops the socket without emitting an `error`). Telemetry-only; never changes behaviour.
|
|
45
107
|
*/
|
|
46
108
|
private attachRelayErrorListener;
|
|
109
|
+
/**
|
|
110
|
+
* Surface the relay transport socket's up/down lifecycle as telemetry. This is
|
|
111
|
+
* the signal `attachRelayErrorListener` misses: when iOS suspends the WebView
|
|
112
|
+
* during a wallet deep-link the socket drops with no `error` event, WC core only
|
|
113
|
+
* reconnects on its slow internal heartbeat, and any in-flight sign/connect
|
|
114
|
+
* response strands in the meantime (CARE-263). Emitting `disconnect` + the
|
|
115
|
+
* subsequent `connect` lets us see the drop and measure the dead window.
|
|
116
|
+
*
|
|
117
|
+
* Emitted at most once per outage: WC core re-emits `disconnect` on every failed
|
|
118
|
+
* reconnect attempt, so a transition flag gates the emit to keep drop/recover 1:1
|
|
119
|
+
* (and bounds emissions); a counter cap backstops many distinct outages. Both
|
|
120
|
+
* sinks are guarded so a throwing observer can never break the SDK's relayer
|
|
121
|
+
* dispatch. Telemetry-only; it never changes connection behaviour.
|
|
122
|
+
*/
|
|
123
|
+
private attachRelayLifecycleListeners;
|
|
47
124
|
/**
|
|
48
125
|
* The initialized client if `connect()` already ran, else null. Synchronous —
|
|
49
126
|
* lets callers attach lifecycle listeners post-connect without forcing a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walletconnect-client.d.ts","sourceRoot":"","sources":["../../src/services/walletconnect-client.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,4BAA4B,CAAA;AAGnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAEjE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C
|
|
1
|
+
{"version":3,"file":"walletconnect-client.d.ts","sourceRoot":"","sources":["../../src/services/walletconnect-client.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,4BAA4B,CAAA;AAGnD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAEjE;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,MAAM,CAAA;AAE5C;;;;;;;;GAQG;AACH,eAAO,MAAM,4BAA4B,KAAK,CAAA;AAE9C;;;;;;;GAOG;AACH,MAAM,WAAW,8BAA8B;IAC7C;;;;OAIG;IACH,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACvC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,MAAM,IAAI,CAAA;IAC9B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,IAAI,CAAA;CAC9B;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,sBAAsB,CAAC,EACnB,8BAA8B,GAC9B,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC,EAC9B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,GACrC,8BAA8B,CAsBhC;AAED;;GAEG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,WAAW,CAAmC;IACtD,mFAAmF;IACnF,OAAO,CAAC,QAAQ,CAAC,eAAe,CAE/B;IACD,0FAA0F;IAC1F,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,mBAAmB,CAAI;IAC/B,sFAAsF;IACtF,OAAO,CAAC,oBAAoB,CAAQ;IACpC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;IAC5C,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAgC;IAElE;;;;OAIG;gBAED,MAAM,EAAE,mBAAmB,EAC3B,iBAAiB,CAAC,EAAE,8BAA8B;IAEpD;;;;OAIG;gBAED,MAAM,EAAE,mBAAmB,EAC3B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI;IAgBxC;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,UAAU,CAAC;IAmBtC;;OAEG;YACW,UAAU;IAiDxB;;;;;;;;;;OAUG;IACH,OAAO,CAAC,wBAAwB;IAkBhC;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,6BAA6B;IA6CrC;;;;OAIG;IACH,iBAAiB,IAAI,UAAU,GAAG,IAAI;IAItC;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBpC;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAW/C"}
|
|
@@ -7,28 +7,64 @@ import { createBoundedDedup, safeErrorMessage } from '@meshconnect/uwc-types';
|
|
|
7
7
|
* session even if messages are high-cardinality (timestamps / urls).
|
|
8
8
|
*/
|
|
9
9
|
export const MAX_REPORTED_RELAY_ERRORS = 100;
|
|
10
|
+
/**
|
|
11
|
+
* Cap on relay transport-lifecycle emissions PER EVENT TYPE (disconnect and
|
|
12
|
+
* reconnect budgeted independently) per client. A flapping socket IS the signal
|
|
13
|
+
* we want, but bounding emissions keeps a pathological relay from flooding
|
|
14
|
+
* telemetry on a long-lived session. Independent budgets so a drop-heavy flap
|
|
15
|
+
* that exhausts the disconnect cap can never silently swallow the paired
|
|
16
|
+
* reconnect — otherwise telemetry would show drops with no recovery for a socket
|
|
17
|
+
* that did recover.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_REPORTED_RELAY_LIFECYCLE = 50;
|
|
20
|
+
/**
|
|
21
|
+
* Normalize the telemetry constructor slots into a `WalletConnectTelemetryObserver`.
|
|
22
|
+
*
|
|
23
|
+
* BACKWARD COMPATIBILITY: the published 0.8.x signature was positional —
|
|
24
|
+
* `(…, onRelayError?, onInitError?)`. This PR moved to a single observer object;
|
|
25
|
+
* the positional form is retained so external JS/TS consumers on 0.8.x don't
|
|
26
|
+
* break at runtime. Disambiguate on the first telemetry slot: a FUNCTION is the
|
|
27
|
+
* legacy `onRelayError` (the new form always passes an object). The legacy form
|
|
28
|
+
* predates `onRelayDisconnect`/`onRelayReconnect`, so those are simply absent.
|
|
29
|
+
*/
|
|
30
|
+
export function toTelemetryObserver(observerOrOnRelayError, onInitError) {
|
|
31
|
+
const onRelayError = typeof observerOrOnRelayError === 'function'
|
|
32
|
+
? observerOrOnRelayError
|
|
33
|
+
: undefined;
|
|
34
|
+
// Legacy positional form when EITHER positional callback is present — this
|
|
35
|
+
// includes the `(undefined, onInitError)` "init-only" call pattern, so that
|
|
36
|
+
// sink is never silently dropped. Keys are added conditionally because
|
|
37
|
+
// `exactOptionalPropertyTypes` forbids an explicit `undefined` on an optional.
|
|
38
|
+
if (onRelayError || onInitError) {
|
|
39
|
+
return {
|
|
40
|
+
...(onRelayError ? { onRelayError } : {}),
|
|
41
|
+
...(onInitError ? { onInitError } : {})
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// New form: an observer object (or nothing).
|
|
45
|
+
return typeof observerOrOnRelayError === 'object' &&
|
|
46
|
+
observerOrOnRelayError !== null
|
|
47
|
+
? observerOrOnRelayError
|
|
48
|
+
: {};
|
|
49
|
+
}
|
|
10
50
|
/**
|
|
11
51
|
* Service for managing WalletConnect client instance
|
|
12
52
|
*/
|
|
13
53
|
export class WalletConnectClient {
|
|
14
|
-
config;
|
|
15
|
-
onRelayError;
|
|
16
|
-
onInitError;
|
|
17
54
|
client = null;
|
|
18
55
|
initPromise = null;
|
|
19
56
|
/** Dedupe relay-error reports so a reconnect storm surfaces once, not per tick. */
|
|
20
57
|
relayErrorDedup = createBoundedDedup(MAX_REPORTED_RELAY_ERRORS);
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
constructor(config,
|
|
58
|
+
/** Independent bounded counters so drop/recover telemetry can never starve each other. */
|
|
59
|
+
relayDisconnectCount = 0;
|
|
60
|
+
relayReconnectCount = 0;
|
|
61
|
+
/** True once the socket has dropped — gates `relayReconnected` to real recoveries. */
|
|
62
|
+
relayHasDisconnected = false;
|
|
63
|
+
config;
|
|
64
|
+
telemetryObserver;
|
|
65
|
+
constructor(config, observerOrOnRelayError, onInitError) {
|
|
29
66
|
this.config = config;
|
|
30
|
-
this.
|
|
31
|
-
this.onInitError = onInitError;
|
|
67
|
+
this.telemetryObserver = toTelemetryObserver(observerOrOnRelayError, onInitError);
|
|
32
68
|
}
|
|
33
69
|
/**
|
|
34
70
|
* Initialize or get the WalletConnect client
|
|
@@ -85,7 +121,7 @@ export class WalletConnectClient {
|
|
|
85
121
|
// instead of a generic connect failure. The sink is guarded so a throwing observer
|
|
86
122
|
// can't mask the real rejection, which is always rethrown.
|
|
87
123
|
try {
|
|
88
|
-
this.onInitError?.(error);
|
|
124
|
+
this.telemetryObserver.onInitError?.(error);
|
|
89
125
|
}
|
|
90
126
|
catch {
|
|
91
127
|
// Telemetry must never replace the init rejection.
|
|
@@ -93,6 +129,7 @@ export class WalletConnectClient {
|
|
|
93
129
|
throw error;
|
|
94
130
|
}
|
|
95
131
|
this.attachRelayErrorListener(client);
|
|
132
|
+
this.attachRelayLifecycleListeners(client);
|
|
96
133
|
return client;
|
|
97
134
|
}
|
|
98
135
|
/**
|
|
@@ -101,12 +138,13 @@ export class WalletConnectClient {
|
|
|
101
138
|
* "JWT Token is not yet valid" when the device clock is skewed past the relay's
|
|
102
139
|
* 120s leeway. That error is otherwise detached from the connect() promise, so it
|
|
103
140
|
* escapes the connect handoff and lands as an unhandled error. Forward it (deduped
|
|
104
|
-
* per message) so it becomes a classified signal instead.
|
|
105
|
-
*
|
|
106
|
-
*
|
|
141
|
+
* per message) so it becomes a classified signal instead. This listener handles the
|
|
142
|
+
* relayer ERROR stream only; the socket's clean up/down lifecycle is captured
|
|
143
|
+
* separately by `attachRelayLifecycleListeners` (CARE-263 — a suspended WebView
|
|
144
|
+
* drops the socket without emitting an `error`). Telemetry-only; never changes behaviour.
|
|
107
145
|
*/
|
|
108
146
|
attachRelayErrorListener(client) {
|
|
109
|
-
if (!this.onRelayError)
|
|
147
|
+
if (!this.telemetryObserver.onRelayError)
|
|
110
148
|
return;
|
|
111
149
|
client.core?.relayer?.on(RELAYER_EVENTS.error, (error) => {
|
|
112
150
|
// safeErrorMessage: this runs INSIDE the SDK's relayer dispatch, before
|
|
@@ -116,7 +154,7 @@ export class WalletConnectClient {
|
|
|
116
154
|
if (!this.relayErrorDedup.firstSeen(message))
|
|
117
155
|
return;
|
|
118
156
|
try {
|
|
119
|
-
this.onRelayError?.(error);
|
|
157
|
+
this.telemetryObserver.onRelayError?.(error);
|
|
120
158
|
}
|
|
121
159
|
catch {
|
|
122
160
|
// A throwing sink would propagate synchronously out of the WC SDK's
|
|
@@ -125,6 +163,63 @@ export class WalletConnectClient {
|
|
|
125
163
|
}
|
|
126
164
|
});
|
|
127
165
|
}
|
|
166
|
+
/**
|
|
167
|
+
* Surface the relay transport socket's up/down lifecycle as telemetry. This is
|
|
168
|
+
* the signal `attachRelayErrorListener` misses: when iOS suspends the WebView
|
|
169
|
+
* during a wallet deep-link the socket drops with no `error` event, WC core only
|
|
170
|
+
* reconnects on its slow internal heartbeat, and any in-flight sign/connect
|
|
171
|
+
* response strands in the meantime (CARE-263). Emitting `disconnect` + the
|
|
172
|
+
* subsequent `connect` lets us see the drop and measure the dead window.
|
|
173
|
+
*
|
|
174
|
+
* Emitted at most once per outage: WC core re-emits `disconnect` on every failed
|
|
175
|
+
* reconnect attempt, so a transition flag gates the emit to keep drop/recover 1:1
|
|
176
|
+
* (and bounds emissions); a counter cap backstops many distinct outages. Both
|
|
177
|
+
* sinks are guarded so a throwing observer can never break the SDK's relayer
|
|
178
|
+
* dispatch. Telemetry-only; it never changes connection behaviour.
|
|
179
|
+
*/
|
|
180
|
+
attachRelayLifecycleListeners(client) {
|
|
181
|
+
if (!this.telemetryObserver.onRelayDisconnect &&
|
|
182
|
+
!this.telemetryObserver.onRelayReconnect)
|
|
183
|
+
return;
|
|
184
|
+
const relayer = client.core?.relayer;
|
|
185
|
+
if (!relayer)
|
|
186
|
+
return;
|
|
187
|
+
// Guarded + capped emit with an INDEPENDENT budget per event type. Cap is only
|
|
188
|
+
// consumed when a sink is wired; the guard keeps a throwing observer from
|
|
189
|
+
// breaking the relayer's own dispatch. Separate budgets ensure a drop-heavy
|
|
190
|
+
// flap can never exhaust the shared pool and silently drop the paired recovery.
|
|
191
|
+
const report = (sink, counter) => {
|
|
192
|
+
if (!sink)
|
|
193
|
+
return;
|
|
194
|
+
if (this[counter] >= MAX_REPORTED_RELAY_LIFECYCLE)
|
|
195
|
+
return;
|
|
196
|
+
this[counter]++;
|
|
197
|
+
try {
|
|
198
|
+
sink();
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// Telemetry must never break the relayer's lifecycle handling.
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
relayer.on(RELAYER_EVENTS.disconnect, () => {
|
|
205
|
+
// WC core re-emits `disconnect` on each failed reconnect during one outage —
|
|
206
|
+
// emit only on the transition INTO disconnected so drop/recover stay 1:1 (and
|
|
207
|
+
// relayDropCount counts outages, not re-emits). The relayer passes no payload
|
|
208
|
+
// (socket errors arrive via RELAYER_EVENTS.error / onRelayError).
|
|
209
|
+
if (this.relayHasDisconnected)
|
|
210
|
+
return;
|
|
211
|
+
this.relayHasDisconnected = true;
|
|
212
|
+
report(this.telemetryObserver.onRelayDisconnect, 'relayDisconnectCount');
|
|
213
|
+
});
|
|
214
|
+
relayer.on(RELAYER_EVENTS.connect, () => {
|
|
215
|
+
// `connect` also fires on the FIRST socket open — only a connect that follows
|
|
216
|
+
// a disconnect is a genuine recovery and pairs 1:1 with the dead window.
|
|
217
|
+
if (!this.relayHasDisconnected)
|
|
218
|
+
return;
|
|
219
|
+
this.relayHasDisconnected = false;
|
|
220
|
+
report(this.telemetryObserver.onRelayReconnect, 'relayReconnectCount');
|
|
221
|
+
});
|
|
222
|
+
}
|
|
128
223
|
/**
|
|
129
224
|
* The initialized client if `connect()` already ran, else null. Synchronous —
|
|
130
225
|
* lets callers attach lifecycle listeners post-connect without forcing a
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"walletconnect-client.js","sourceRoot":"","sources":["../../src/services/walletconnect-client.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,4BAA4B,CAAA;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAG7E;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAA;AAE5C
|
|
1
|
+
{"version":3,"file":"walletconnect-client.js","sourceRoot":"","sources":["../../src/services/walletconnect-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Network, Connector, ConnectorResult, SwitchNetworkResult, WalletConnectConfig, WalletConnectProvider, TransactionRequest, TransactionResult, NetworkRpcMap, SignatureType, EIP712TypedData, WalletLifecycleEvent, WalletLifecycleContext } from '@meshconnect/uwc-types';
|
|
2
|
+
import { type WalletConnectTelemetryObserver } from './services/walletconnect-client';
|
|
2
3
|
export declare class WalletConnectConnector implements Connector {
|
|
3
4
|
private walletConnectClient;
|
|
4
5
|
private connectionService;
|
|
@@ -6,6 +7,12 @@ export declare class WalletConnectConnector implements Connector {
|
|
|
6
7
|
private signatureService;
|
|
7
8
|
private transactionService;
|
|
8
9
|
private networkRpcMap;
|
|
10
|
+
constructor(config: WalletConnectConfig, networkRpcMap?: NetworkRpcMap, telemetryObserver?: WalletConnectTelemetryObserver);
|
|
11
|
+
/**
|
|
12
|
+
* @deprecated Pass a {@link WalletConnectTelemetryObserver}. The positional
|
|
13
|
+
* `(onRelayError, onInitError)` form is retained for backward compatibility
|
|
14
|
+
* with the published 0.8.x signature and will be removed in a future major.
|
|
15
|
+
*/
|
|
9
16
|
constructor(config: WalletConnectConfig, networkRpcMap?: NetworkRpcMap, onRelayError?: (error: unknown) => void, onInitError?: (error: unknown) => void);
|
|
10
17
|
connect(network: Network, provider?: WalletConnectProvider): Promise<ConnectorResult>;
|
|
11
18
|
getConnectionURI(): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wallet-connect-connector.d.ts","sourceRoot":"","sources":["../src/wallet-connect-connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAEP,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,wBAAwB,CAAA;
|
|
1
|
+
{"version":3,"file":"wallet-connect-connector.d.ts","sourceRoot":"","sources":["../src/wallet-connect-connector.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EAEP,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,mBAAmB,EACnB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,aAAa,EACb,aAAa,EACb,eAAe,EACf,oBAAoB,EACpB,sBAAsB,EACvB,MAAM,wBAAwB,CAAA;AAC/B,OAAO,EAGL,KAAK,8BAA8B,EACpC,MAAM,iCAAiC,CAAA;AAqGxC,qBAAa,sBAAuB,YAAW,SAAS;IACtD,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,iBAAiB,CAAmB;IAC5C,OAAO,CAAC,oBAAoB,CAAsB;IAClD,OAAO,CAAC,gBAAgB,CAAkB;IAC1C,OAAO,CAAC,kBAAkB,CAAoB;IAC9C,OAAO,CAAC,aAAa,CAAe;gBAGlC,MAAM,EAAE,mBAAmB,EAC3B,aAAa,CAAC,EAAE,aAAa,EAC7B,iBAAiB,CAAC,EAAE,8BAA8B;IAEpD;;;;OAIG;gBAED,MAAM,EAAE,mBAAmB,EAC3B,aAAa,CAAC,EAAE,aAAa,EAC7B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,EACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI;IA+BlC,OAAO,CACX,OAAO,EAAE,OAAO,EAChB,QAAQ,CAAC,EAAE,qBAAqB,GAC/B,OAAO,CAAC,eAAe,CAAC;IAU3B,gBAAgB,IAAI,MAAM;IAQpB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAI7D,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC;IAIpD,aAAa,CAAC,SAAS,EAAE,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC;IAI1D,eAAe,CACnB,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,iBAAiB,CAAC;IAI7B;;;;;;OAMG;IACG,0BAA0B,CAC9B,YAAY,EAAE,UAAU,GACvB,OAAO,CAAC,UAAU,CAAC;IAItB;;;;;;;;;;;;;;;;;OAiBG;IACH,gBAAgB,CACd,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,EAI/C,QAAQ,EAAE,sBAAsB,GAC/B,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS;CA8C5B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { WalletConnectClient } from './services/walletconnect-client';
|
|
1
|
+
import { WalletConnectClient, toTelemetryObserver } from './services/walletconnect-client';
|
|
2
2
|
import { ConnectionService } from './services/connection-service';
|
|
3
3
|
import { NetworkSwitchService } from './services/network-switch-service';
|
|
4
4
|
import { SignatureService } from './services/signature-service';
|
|
@@ -108,8 +108,8 @@ export class WalletConnectConnector {
|
|
|
108
108
|
signatureService;
|
|
109
109
|
transactionService;
|
|
110
110
|
networkRpcMap;
|
|
111
|
-
constructor(config, networkRpcMap = {},
|
|
112
|
-
this.walletConnectClient = new WalletConnectClient(config,
|
|
111
|
+
constructor(config, networkRpcMap = {}, observerOrOnRelayError, onInitError) {
|
|
112
|
+
this.walletConnectClient = new WalletConnectClient(config, toTelemetryObserver(observerOrOnRelayError, onInitError));
|
|
113
113
|
this.networkRpcMap = networkRpcMap;
|
|
114
114
|
this.connectionService = new ConnectionService(this.walletConnectClient);
|
|
115
115
|
this.networkSwitchService = new NetworkSwitchService(this.walletConnectClient, this.connectionService);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wallet-connect-connector.js","sourceRoot":"","sources":["../src/wallet-connect-connector.ts"],"names":[],"mappings":"AAgBA,OAAO,
|
|
1
|
+
{"version":3,"file":"wallet-connect-connector.js","sourceRoot":"","sources":["../src/wallet-connect-connector.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -21,7 +21,9 @@ vi.mock('@walletconnect/sign-client', () => ({
|
|
|
21
21
|
import SignClient from '@walletconnect/sign-client'
|
|
22
22
|
import {
|
|
23
23
|
WalletConnectClient,
|
|
24
|
-
|
|
24
|
+
toTelemetryObserver,
|
|
25
|
+
MAX_REPORTED_RELAY_ERRORS,
|
|
26
|
+
MAX_REPORTED_RELAY_LIFECYCLE
|
|
25
27
|
} from './walletconnect-client'
|
|
26
28
|
|
|
27
29
|
const CONFIG = { projectId: 'p' } as WalletConnectConfig
|
|
@@ -38,7 +40,9 @@ describe('WalletConnectClient relay-error surfacing', () => {
|
|
|
38
40
|
|
|
39
41
|
it('forwards a relayer error to onRelayError', async () => {
|
|
40
42
|
const errors: unknown[] = []
|
|
41
|
-
const client = new WalletConnectClient(CONFIG,
|
|
43
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
44
|
+
onRelayError: e => errors.push(e)
|
|
45
|
+
})
|
|
42
46
|
await client.getClient()
|
|
43
47
|
|
|
44
48
|
relayerHandlers[RELAYER_EVENTS.error]?.(
|
|
@@ -53,7 +57,9 @@ describe('WalletConnectClient relay-error surfacing', () => {
|
|
|
53
57
|
|
|
54
58
|
it('dedupes repeated identical relay errors (reconnect storm → one report)', async () => {
|
|
55
59
|
const errors: unknown[] = []
|
|
56
|
-
const client = new WalletConnectClient(CONFIG,
|
|
60
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
61
|
+
onRelayError: e => errors.push(e)
|
|
62
|
+
})
|
|
57
63
|
await client.getClient()
|
|
58
64
|
|
|
59
65
|
const fire = () =>
|
|
@@ -66,8 +72,10 @@ describe('WalletConnectClient relay-error surfacing', () => {
|
|
|
66
72
|
})
|
|
67
73
|
|
|
68
74
|
it('swallows a throwing onRelayError sink — must not break the relayer emitter', async () => {
|
|
69
|
-
const client = new WalletConnectClient(CONFIG,
|
|
70
|
-
|
|
75
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
76
|
+
onRelayError: () => {
|
|
77
|
+
throw new Error('sink exploded')
|
|
78
|
+
}
|
|
71
79
|
})
|
|
72
80
|
await client.getClient()
|
|
73
81
|
|
|
@@ -80,7 +88,9 @@ describe('WalletConnectClient relay-error surfacing', () => {
|
|
|
80
88
|
|
|
81
89
|
it('never throws when the relayer emits a value with a throwing toString()', async () => {
|
|
82
90
|
const errors: unknown[] = []
|
|
83
|
-
const client = new WalletConnectClient(CONFIG,
|
|
91
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
92
|
+
onRelayError: e => errors.push(e)
|
|
93
|
+
})
|
|
84
94
|
await client.getClient()
|
|
85
95
|
|
|
86
96
|
const evil = {
|
|
@@ -102,7 +112,9 @@ describe('WalletConnectClient relay-error surfacing', () => {
|
|
|
102
112
|
|
|
103
113
|
it('bounds the dedup set — an evicted message can report again past the cap', async () => {
|
|
104
114
|
const errors: unknown[] = []
|
|
105
|
-
const client = new WalletConnectClient(CONFIG,
|
|
115
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
116
|
+
onRelayError: e => errors.push(e)
|
|
117
|
+
})
|
|
106
118
|
await client.getClient()
|
|
107
119
|
const fire = (m: string) =>
|
|
108
120
|
relayerHandlers[RELAYER_EVENTS.error]?.(new Error(m))
|
|
@@ -125,14 +137,135 @@ describe('WalletConnectClient relay-error surfacing', () => {
|
|
|
125
137
|
})
|
|
126
138
|
})
|
|
127
139
|
|
|
140
|
+
describe('WalletConnectClient relay-lifecycle surfacing', () => {
|
|
141
|
+
beforeEach(() => {
|
|
142
|
+
for (const key of Object.keys(relayerHandlers)) delete relayerHandlers[key]
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('emits disconnect once per outage — ignores WC-core re-emits during the outage', async () => {
|
|
146
|
+
let drops = 0
|
|
147
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
148
|
+
onRelayDisconnect: () => {
|
|
149
|
+
drops++
|
|
150
|
+
}
|
|
151
|
+
})
|
|
152
|
+
await client.getClient()
|
|
153
|
+
|
|
154
|
+
// WC core re-emits `disconnect` on each failed reconnect attempt; only the
|
|
155
|
+
// transition INTO disconnected counts.
|
|
156
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
157
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
158
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
159
|
+
expect(drops).toBe(1)
|
|
160
|
+
|
|
161
|
+
// A reconnect closes the outage; the next disconnect is a NEW outage.
|
|
162
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.()
|
|
163
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
164
|
+
expect(drops).toBe(2)
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('emits reconnect only for a connect that follows a disconnect (not the initial connect)', async () => {
|
|
168
|
+
let reconnects = 0
|
|
169
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
170
|
+
onRelayReconnect: () => {
|
|
171
|
+
reconnects++
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
await client.getClient()
|
|
175
|
+
|
|
176
|
+
// Initial / spurious connect with no prior drop → not a recovery.
|
|
177
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.()
|
|
178
|
+
expect(reconnects).toBe(0)
|
|
179
|
+
|
|
180
|
+
// A drop then a connect → exactly one genuine recovery.
|
|
181
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.({})
|
|
182
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.()
|
|
183
|
+
expect(reconnects).toBe(1)
|
|
184
|
+
|
|
185
|
+
// Another connect with no new drop → not counted again.
|
|
186
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.()
|
|
187
|
+
expect(reconnects).toBe(1)
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
it('does not attach lifecycle listeners when neither sink is wired', async () => {
|
|
191
|
+
const client = new WalletConnectClient(CONFIG)
|
|
192
|
+
await client.getClient()
|
|
193
|
+
expect(relayerHandlers[RELAYER_EVENTS.disconnect]).toBeUndefined()
|
|
194
|
+
expect(relayerHandlers[RELAYER_EVENTS.connect]).toBeUndefined()
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('swallows a throwing lifecycle sink — must not break the relayer emitter', async () => {
|
|
198
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
199
|
+
onRelayDisconnect: () => {
|
|
200
|
+
throw new Error('sink exploded')
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
await client.getClient()
|
|
204
|
+
|
|
205
|
+
// Runs inside the WC SDK's relayer disconnect dispatch — a propagating throw
|
|
206
|
+
// would prevent the SDK's own transport teardown from running.
|
|
207
|
+
expect(() => relayerHandlers[RELAYER_EVENTS.disconnect]?.({})).not.toThrow()
|
|
208
|
+
})
|
|
209
|
+
|
|
210
|
+
it('caps disconnect and reconnect INDEPENDENTLY past the cap (recovery never starved by drops)', async () => {
|
|
211
|
+
let drops = 0
|
|
212
|
+
let recoveries = 0
|
|
213
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
214
|
+
onRelayDisconnect: () => {
|
|
215
|
+
drops++
|
|
216
|
+
},
|
|
217
|
+
onRelayReconnect: () => {
|
|
218
|
+
recoveries++
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
await client.getClient()
|
|
222
|
+
|
|
223
|
+
// Each outage is a disconnect→connect cycle. Fire well past the cap; each
|
|
224
|
+
// event type has its OWN budget, so BOTH reach MAX — a drop-heavy flap can
|
|
225
|
+
// never exhaust a shared pool and silently swallow the paired recovery.
|
|
226
|
+
for (let i = 0; i < MAX_REPORTED_RELAY_LIFECYCLE + 20; i++) {
|
|
227
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
228
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.()
|
|
229
|
+
}
|
|
230
|
+
expect(drops).toBe(MAX_REPORTED_RELAY_LIFECYCLE)
|
|
231
|
+
expect(recoveries).toBe(MAX_REPORTED_RELAY_LIFECYCLE)
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('still reports a recovery after the DISCONNECT budget alone is exhausted', async () => {
|
|
235
|
+
// Szymon's scenario: a socket that drops many times, then finally recovers.
|
|
236
|
+
// With a shared budget the recovery could be dropped; with independent
|
|
237
|
+
// budgets the reconnect must still surface.
|
|
238
|
+
let drops = 0
|
|
239
|
+
let recoveries = 0
|
|
240
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
241
|
+
onRelayDisconnect: () => {
|
|
242
|
+
drops++
|
|
243
|
+
},
|
|
244
|
+
onRelayReconnect: () => {
|
|
245
|
+
recoveries++
|
|
246
|
+
}
|
|
247
|
+
})
|
|
248
|
+
await client.getClient()
|
|
249
|
+
|
|
250
|
+
// Exhaust the disconnect budget across many full outages...
|
|
251
|
+
for (let i = 0; i < MAX_REPORTED_RELAY_LIFECYCLE + 5; i++) {
|
|
252
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
253
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.()
|
|
254
|
+
}
|
|
255
|
+
expect(drops).toBe(MAX_REPORTED_RELAY_LIFECYCLE)
|
|
256
|
+
// ...the reconnect budget is its own, so recoveries kept surfacing throughout.
|
|
257
|
+
expect(recoveries).toBe(MAX_REPORTED_RELAY_LIFECYCLE)
|
|
258
|
+
})
|
|
259
|
+
})
|
|
260
|
+
|
|
128
261
|
describe('WalletConnectClient init-error surfacing', () => {
|
|
129
262
|
it('forwards a failed SignClient.init to onInitError and rethrows unchanged', async () => {
|
|
130
263
|
const initErr = new Error('IndexedDB unavailable at init')
|
|
131
264
|
initMock.mockRejectedValueOnce(initErr)
|
|
132
265
|
const errors: unknown[] = []
|
|
133
|
-
const client = new WalletConnectClient(CONFIG,
|
|
134
|
-
errors.push(e)
|
|
135
|
-
)
|
|
266
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
267
|
+
onInitError: e => errors.push(e)
|
|
268
|
+
})
|
|
136
269
|
|
|
137
270
|
await expect(client.getClient()).rejects.toBe(initErr)
|
|
138
271
|
expect(errors).toEqual([initErr])
|
|
@@ -141,8 +274,10 @@ describe('WalletConnectClient init-error surfacing', () => {
|
|
|
141
274
|
it('rethrows the init rejection even when the onInitError sink throws', async () => {
|
|
142
275
|
const initErr = new Error('init boom')
|
|
143
276
|
initMock.mockRejectedValueOnce(initErr)
|
|
144
|
-
const client = new WalletConnectClient(CONFIG,
|
|
145
|
-
|
|
277
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
278
|
+
onInitError: () => {
|
|
279
|
+
throw new Error('sink exploded')
|
|
280
|
+
}
|
|
146
281
|
})
|
|
147
282
|
|
|
148
283
|
await expect(client.getClient()).rejects.toBe(initErr)
|
|
@@ -156,3 +291,126 @@ describe('WalletConnectClient init-error surfacing', () => {
|
|
|
156
291
|
await expect(client.getClient()).rejects.toBe(initErr)
|
|
157
292
|
})
|
|
158
293
|
})
|
|
294
|
+
|
|
295
|
+
describe('toTelemetryObserver (positional back-compat normalizer)', () => {
|
|
296
|
+
it('maps a legacy (onRelayError, onInitError) function pair to the observer shape', () => {
|
|
297
|
+
const onRelayError = () => {}
|
|
298
|
+
const onInitError = () => {}
|
|
299
|
+
expect(toTelemetryObserver(onRelayError, onInitError)).toEqual({
|
|
300
|
+
onRelayError,
|
|
301
|
+
onInitError
|
|
302
|
+
})
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('maps a lone legacy onRelayError with no init sink (onInitError key omitted)', () => {
|
|
306
|
+
const onRelayError = () => {}
|
|
307
|
+
expect(toTelemetryObserver(onRelayError)).toEqual({ onRelayError })
|
|
308
|
+
expect('onInitError' in toTelemetryObserver(onRelayError)).toBe(false)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('keeps a legacy init-only call — (undefined, onInitError) — without dropping the sink', () => {
|
|
312
|
+
// 0.8.x callers could skip onRelayError: `new WalletConnectClient(config, undefined, onInitError)`.
|
|
313
|
+
const onInitError = () => {}
|
|
314
|
+
expect(toTelemetryObserver(undefined, onInitError)).toEqual({ onInitError })
|
|
315
|
+
expect('onRelayError' in toTelemetryObserver(undefined, onInitError)).toBe(
|
|
316
|
+
false
|
|
317
|
+
)
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
it('passes a new-form observer object through untouched', () => {
|
|
321
|
+
const observer = { onRelayDisconnect: () => {}, onRelayReconnect: () => {} }
|
|
322
|
+
expect(toTelemetryObserver(observer)).toBe(observer)
|
|
323
|
+
})
|
|
324
|
+
|
|
325
|
+
it('returns an empty observer for an omitted argument', () => {
|
|
326
|
+
expect(toTelemetryObserver(undefined)).toEqual({})
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
describe('WalletConnectClient legacy positional constructor (0.8.x back-compat)', () => {
|
|
331
|
+
beforeEach(() => {
|
|
332
|
+
for (const key of Object.keys(relayerHandlers)) delete relayerHandlers[key]
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
it('forwards a relayer error when onRelayError is passed positionally', async () => {
|
|
336
|
+
const errors: unknown[] = []
|
|
337
|
+
// Legacy 0.8.x call site: (config, onRelayError) — a bare function, not an observer.
|
|
338
|
+
const client = new WalletConnectClient(CONFIG, e => errors.push(e))
|
|
339
|
+
await client.getClient()
|
|
340
|
+
|
|
341
|
+
relayerHandlers[RELAYER_EVENTS.error]?.(new Error('code: 3000'))
|
|
342
|
+
expect(errors).toHaveLength(1)
|
|
343
|
+
expect((errors[0] as Error).message).toContain('3000')
|
|
344
|
+
})
|
|
345
|
+
|
|
346
|
+
it('forwards a failed init when onInitError is passed as the 3rd positional arg', async () => {
|
|
347
|
+
const initErr = new Error('IndexedDB unavailable at init')
|
|
348
|
+
initMock.mockRejectedValueOnce(initErr)
|
|
349
|
+
const errors: unknown[] = []
|
|
350
|
+
// Legacy 0.8.x call site: (config, onRelayError, onInitError).
|
|
351
|
+
const client = new WalletConnectClient(
|
|
352
|
+
CONFIG,
|
|
353
|
+
() => {},
|
|
354
|
+
e => errors.push(e)
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
await expect(client.getClient()).rejects.toBe(initErr)
|
|
358
|
+
expect(errors).toEqual([initErr])
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
it('does NOT attach lifecycle listeners for the legacy form (it predates them)', async () => {
|
|
362
|
+
// The legacy signature never carried onRelayDisconnect/onRelayReconnect, so
|
|
363
|
+
// the lifecycle listeners must stay unattached — no phantom drop/reconnect.
|
|
364
|
+
const client = new WalletConnectClient(CONFIG, () => {})
|
|
365
|
+
await client.getClient()
|
|
366
|
+
|
|
367
|
+
expect(relayerHandlers[RELAYER_EVENTS.error]).toBeDefined()
|
|
368
|
+
expect(relayerHandlers[RELAYER_EVENTS.disconnect]).toBeUndefined()
|
|
369
|
+
expect(relayerHandlers[RELAYER_EVENTS.connect]).toBeUndefined()
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
describe('WalletConnectTelemetryObserver wiring (single options object)', () => {
|
|
374
|
+
beforeEach(() => {
|
|
375
|
+
for (const key of Object.keys(relayerHandlers)) delete relayerHandlers[key]
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
it('routes each relayer signal to its own sink with no cross-wiring', async () => {
|
|
379
|
+
// One observer wiring all three relayer sinks — each must fire only for its
|
|
380
|
+
// own event. Regression guard against the object mis-mapping a field.
|
|
381
|
+
const seen: string[] = []
|
|
382
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
383
|
+
onRelayError: () => seen.push('error'),
|
|
384
|
+
onRelayDisconnect: () => seen.push('disconnect'),
|
|
385
|
+
onRelayReconnect: () => seen.push('reconnect')
|
|
386
|
+
})
|
|
387
|
+
await client.getClient()
|
|
388
|
+
|
|
389
|
+
relayerHandlers[RELAYER_EVENTS.error]?.(new Error('boom'))
|
|
390
|
+
relayerHandlers[RELAYER_EVENTS.disconnect]?.()
|
|
391
|
+
relayerHandlers[RELAYER_EVENTS.connect]?.() // recovery after the drop above
|
|
392
|
+
|
|
393
|
+
expect(seen).toEqual(['error', 'disconnect', 'reconnect'])
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
it('attaches only the listeners for the sinks that are provided', async () => {
|
|
397
|
+
// Error sink only → the error listener attaches, lifecycle listeners do not.
|
|
398
|
+
const client = new WalletConnectClient(CONFIG, {
|
|
399
|
+
onRelayError: () => {}
|
|
400
|
+
})
|
|
401
|
+
await client.getClient()
|
|
402
|
+
|
|
403
|
+
expect(relayerHandlers[RELAYER_EVENTS.error]).toBeDefined()
|
|
404
|
+
expect(relayerHandlers[RELAYER_EVENTS.disconnect]).toBeUndefined()
|
|
405
|
+
expect(relayerHandlers[RELAYER_EVENTS.connect]).toBeUndefined()
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
it('attaches nothing for an empty observer (default) — behaviour unchanged', async () => {
|
|
409
|
+
const client = new WalletConnectClient(CONFIG, {})
|
|
410
|
+
await client.getClient()
|
|
411
|
+
|
|
412
|
+
expect(relayerHandlers[RELAYER_EVENTS.error]).toBeUndefined()
|
|
413
|
+
expect(relayerHandlers[RELAYER_EVENTS.disconnect]).toBeUndefined()
|
|
414
|
+
expect(relayerHandlers[RELAYER_EVENTS.connect]).toBeUndefined()
|
|
415
|
+
})
|
|
416
|
+
})
|
|
@@ -10,6 +10,89 @@ import type { WalletConnectConfig } from '@meshconnect/uwc-types'
|
|
|
10
10
|
*/
|
|
11
11
|
export const MAX_REPORTED_RELAY_ERRORS = 100
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* Cap on relay transport-lifecycle emissions PER EVENT TYPE (disconnect and
|
|
15
|
+
* reconnect budgeted independently) per client. A flapping socket IS the signal
|
|
16
|
+
* we want, but bounding emissions keeps a pathological relay from flooding
|
|
17
|
+
* telemetry on a long-lived session. Independent budgets so a drop-heavy flap
|
|
18
|
+
* that exhausts the disconnect cap can never silently swallow the paired
|
|
19
|
+
* reconnect — otherwise telemetry would show drops with no recovery for a socket
|
|
20
|
+
* that did recover.
|
|
21
|
+
*/
|
|
22
|
+
export const MAX_REPORTED_RELAY_LIFECYCLE = 50
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Optional telemetry sinks for the WalletConnect client. Covers both the relay
|
|
26
|
+
* transport (error / disconnect / reconnect) AND `SignClient.init` failures.
|
|
27
|
+
* Every field is opt-in — an empty observer leaves behaviour unchanged. All
|
|
28
|
+
* sinks are telemetry-only: they never alter connection behaviour, and a
|
|
29
|
+
* throwing sink is swallowed so it can't break the SDK's relayer dispatch (or,
|
|
30
|
+
* for `onInitError`, mask the always-rethrown init rejection).
|
|
31
|
+
*/
|
|
32
|
+
export interface WalletConnectTelemetryObserver {
|
|
33
|
+
/**
|
|
34
|
+
* Relay-socket failure: the relayer re-emits an abnormal WebSocket close under
|
|
35
|
+
* `RELAYER_EVENTS.error` (e.g. code 3000 "JWT Token is not yet valid" on clock
|
|
36
|
+
* skew), detached from the connect() promise. Deduped per message.
|
|
37
|
+
*/
|
|
38
|
+
onRelayError?: (error: unknown) => void
|
|
39
|
+
/**
|
|
40
|
+
* `SignClient.init` failure (storage/crypto, NOT relay). Telemetry-only — the
|
|
41
|
+
* init rejection is always rethrown so `getClient()` callers see it unchanged.
|
|
42
|
+
*/
|
|
43
|
+
onInitError?: (error: unknown) => void
|
|
44
|
+
/**
|
|
45
|
+
* Relay transport socket dropped (relayer `disconnect`), emitted once per
|
|
46
|
+
* outage. The lifecycle FACT — distinct from `onRelayError` (a close WITH an
|
|
47
|
+
* error). This is the CARE-263 signal a suspended WebView produces silently.
|
|
48
|
+
*/
|
|
49
|
+
onRelayDisconnect?: () => void
|
|
50
|
+
/**
|
|
51
|
+
* Relay socket recovered (relayer `connect` after a drop). Pair with
|
|
52
|
+
* `onRelayDisconnect` to measure the dead window.
|
|
53
|
+
*/
|
|
54
|
+
onRelayReconnect?: () => void
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Normalize the telemetry constructor slots into a `WalletConnectTelemetryObserver`.
|
|
59
|
+
*
|
|
60
|
+
* BACKWARD COMPATIBILITY: the published 0.8.x signature was positional —
|
|
61
|
+
* `(…, onRelayError?, onInitError?)`. This PR moved to a single observer object;
|
|
62
|
+
* the positional form is retained so external JS/TS consumers on 0.8.x don't
|
|
63
|
+
* break at runtime. Disambiguate on the first telemetry slot: a FUNCTION is the
|
|
64
|
+
* legacy `onRelayError` (the new form always passes an object). The legacy form
|
|
65
|
+
* predates `onRelayDisconnect`/`onRelayReconnect`, so those are simply absent.
|
|
66
|
+
*/
|
|
67
|
+
export function toTelemetryObserver(
|
|
68
|
+
observerOrOnRelayError?:
|
|
69
|
+
| WalletConnectTelemetryObserver
|
|
70
|
+
| ((error: unknown) => void),
|
|
71
|
+
onInitError?: (error: unknown) => void
|
|
72
|
+
): WalletConnectTelemetryObserver {
|
|
73
|
+
const onRelayError =
|
|
74
|
+
typeof observerOrOnRelayError === 'function'
|
|
75
|
+
? observerOrOnRelayError
|
|
76
|
+
: undefined
|
|
77
|
+
|
|
78
|
+
// Legacy positional form when EITHER positional callback is present — this
|
|
79
|
+
// includes the `(undefined, onInitError)` "init-only" call pattern, so that
|
|
80
|
+
// sink is never silently dropped. Keys are added conditionally because
|
|
81
|
+
// `exactOptionalPropertyTypes` forbids an explicit `undefined` on an optional.
|
|
82
|
+
if (onRelayError || onInitError) {
|
|
83
|
+
return {
|
|
84
|
+
...(onRelayError ? { onRelayError } : {}),
|
|
85
|
+
...(onInitError ? { onInitError } : {})
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// New form: an observer object (or nothing).
|
|
90
|
+
return typeof observerOrOnRelayError === 'object' &&
|
|
91
|
+
observerOrOnRelayError !== null
|
|
92
|
+
? observerOrOnRelayError
|
|
93
|
+
: {}
|
|
94
|
+
}
|
|
95
|
+
|
|
13
96
|
/**
|
|
14
97
|
* Service for managing WalletConnect client instance
|
|
15
98
|
*/
|
|
@@ -20,19 +103,46 @@ export class WalletConnectClient {
|
|
|
20
103
|
private readonly relayErrorDedup = createBoundedDedup(
|
|
21
104
|
MAX_REPORTED_RELAY_ERRORS
|
|
22
105
|
)
|
|
106
|
+
/** Independent bounded counters so drop/recover telemetry can never starve each other. */
|
|
107
|
+
private relayDisconnectCount = 0
|
|
108
|
+
private relayReconnectCount = 0
|
|
109
|
+
/** True once the socket has dropped — gates `relayReconnected` to real recoveries. */
|
|
110
|
+
private relayHasDisconnected = false
|
|
111
|
+
private readonly config: WalletConnectConfig
|
|
112
|
+
private readonly telemetryObserver: WalletConnectTelemetryObserver
|
|
23
113
|
|
|
24
114
|
/**
|
|
25
|
-
* @param
|
|
26
|
-
* `
|
|
27
|
-
*
|
|
28
|
-
|
|
29
|
-
|
|
115
|
+
* @param telemetryObserver - optional telemetry sinks (see
|
|
116
|
+
* `WalletConnectTelemetryObserver`). Every field is opt-in; an empty/omitted
|
|
117
|
+
* observer leaves behaviour unchanged.
|
|
118
|
+
*/
|
|
119
|
+
constructor(
|
|
120
|
+
config: WalletConnectConfig,
|
|
121
|
+
telemetryObserver?: WalletConnectTelemetryObserver
|
|
122
|
+
)
|
|
123
|
+
/**
|
|
124
|
+
* @deprecated Pass a {@link WalletConnectTelemetryObserver}. The positional
|
|
125
|
+
* `(onRelayError, onInitError)` form is retained for backward compatibility
|
|
126
|
+
* with the published 0.8.x signature and will be removed in a future major.
|
|
30
127
|
*/
|
|
31
128
|
constructor(
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
)
|
|
129
|
+
config: WalletConnectConfig,
|
|
130
|
+
onRelayError?: (error: unknown) => void,
|
|
131
|
+
onInitError?: (error: unknown) => void
|
|
132
|
+
)
|
|
133
|
+
constructor(
|
|
134
|
+
config: WalletConnectConfig,
|
|
135
|
+
observerOrOnRelayError?:
|
|
136
|
+
| WalletConnectTelemetryObserver
|
|
137
|
+
| ((error: unknown) => void),
|
|
138
|
+
onInitError?: (error: unknown) => void
|
|
139
|
+
) {
|
|
140
|
+
this.config = config
|
|
141
|
+
this.telemetryObserver = toTelemetryObserver(
|
|
142
|
+
observerOrOnRelayError,
|
|
143
|
+
onInitError
|
|
144
|
+
)
|
|
145
|
+
}
|
|
36
146
|
|
|
37
147
|
/**
|
|
38
148
|
* Initialize or get the WalletConnect client
|
|
@@ -95,7 +205,7 @@ export class WalletConnectClient {
|
|
|
95
205
|
// instead of a generic connect failure. The sink is guarded so a throwing observer
|
|
96
206
|
// can't mask the real rejection, which is always rethrown.
|
|
97
207
|
try {
|
|
98
|
-
this.onInitError?.(error)
|
|
208
|
+
this.telemetryObserver.onInitError?.(error)
|
|
99
209
|
} catch {
|
|
100
210
|
// Telemetry must never replace the init rejection.
|
|
101
211
|
}
|
|
@@ -103,6 +213,7 @@ export class WalletConnectClient {
|
|
|
103
213
|
}
|
|
104
214
|
|
|
105
215
|
this.attachRelayErrorListener(client)
|
|
216
|
+
this.attachRelayLifecycleListeners(client)
|
|
106
217
|
|
|
107
218
|
return client
|
|
108
219
|
}
|
|
@@ -113,12 +224,13 @@ export class WalletConnectClient {
|
|
|
113
224
|
* "JWT Token is not yet valid" when the device clock is skewed past the relay's
|
|
114
225
|
* 120s leeway. That error is otherwise detached from the connect() promise, so it
|
|
115
226
|
* escapes the connect handoff and lands as an unhandled error. Forward it (deduped
|
|
116
|
-
* per message) so it becomes a classified signal instead.
|
|
117
|
-
*
|
|
118
|
-
*
|
|
227
|
+
* per message) so it becomes a classified signal instead. This listener handles the
|
|
228
|
+
* relayer ERROR stream only; the socket's clean up/down lifecycle is captured
|
|
229
|
+
* separately by `attachRelayLifecycleListeners` (CARE-263 — a suspended WebView
|
|
230
|
+
* drops the socket without emitting an `error`). Telemetry-only; never changes behaviour.
|
|
119
231
|
*/
|
|
120
232
|
private attachRelayErrorListener(client: SignClient): void {
|
|
121
|
-
if (!this.onRelayError) return
|
|
233
|
+
if (!this.telemetryObserver.onRelayError) return
|
|
122
234
|
client.core?.relayer?.on(RELAYER_EVENTS.error, (error: unknown) => {
|
|
123
235
|
// safeErrorMessage: this runs INSIDE the SDK's relayer dispatch, before
|
|
124
236
|
// the sink guard below — a throwing toString() here would break the
|
|
@@ -126,7 +238,7 @@ export class WalletConnectClient {
|
|
|
126
238
|
const message = safeErrorMessage(error)
|
|
127
239
|
if (!this.relayErrorDedup.firstSeen(message)) return
|
|
128
240
|
try {
|
|
129
|
-
this.onRelayError?.(error)
|
|
241
|
+
this.telemetryObserver.onRelayError?.(error)
|
|
130
242
|
} catch {
|
|
131
243
|
// A throwing sink would propagate synchronously out of the WC SDK's
|
|
132
244
|
// relayer emitter and prevent its own error handling (transportClose)
|
|
@@ -135,6 +247,65 @@ export class WalletConnectClient {
|
|
|
135
247
|
})
|
|
136
248
|
}
|
|
137
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Surface the relay transport socket's up/down lifecycle as telemetry. This is
|
|
252
|
+
* the signal `attachRelayErrorListener` misses: when iOS suspends the WebView
|
|
253
|
+
* during a wallet deep-link the socket drops with no `error` event, WC core only
|
|
254
|
+
* reconnects on its slow internal heartbeat, and any in-flight sign/connect
|
|
255
|
+
* response strands in the meantime (CARE-263). Emitting `disconnect` + the
|
|
256
|
+
* subsequent `connect` lets us see the drop and measure the dead window.
|
|
257
|
+
*
|
|
258
|
+
* Emitted at most once per outage: WC core re-emits `disconnect` on every failed
|
|
259
|
+
* reconnect attempt, so a transition flag gates the emit to keep drop/recover 1:1
|
|
260
|
+
* (and bounds emissions); a counter cap backstops many distinct outages. Both
|
|
261
|
+
* sinks are guarded so a throwing observer can never break the SDK's relayer
|
|
262
|
+
* dispatch. Telemetry-only; it never changes connection behaviour.
|
|
263
|
+
*/
|
|
264
|
+
private attachRelayLifecycleListeners(client: SignClient): void {
|
|
265
|
+
if (
|
|
266
|
+
!this.telemetryObserver.onRelayDisconnect &&
|
|
267
|
+
!this.telemetryObserver.onRelayReconnect
|
|
268
|
+
)
|
|
269
|
+
return
|
|
270
|
+
const relayer = client.core?.relayer
|
|
271
|
+
if (!relayer) return
|
|
272
|
+
|
|
273
|
+
// Guarded + capped emit with an INDEPENDENT budget per event type. Cap is only
|
|
274
|
+
// consumed when a sink is wired; the guard keeps a throwing observer from
|
|
275
|
+
// breaking the relayer's own dispatch. Separate budgets ensure a drop-heavy
|
|
276
|
+
// flap can never exhaust the shared pool and silently drop the paired recovery.
|
|
277
|
+
const report = (
|
|
278
|
+
sink: (() => void) | undefined,
|
|
279
|
+
counter: 'relayDisconnectCount' | 'relayReconnectCount'
|
|
280
|
+
): void => {
|
|
281
|
+
if (!sink) return
|
|
282
|
+
if (this[counter] >= MAX_REPORTED_RELAY_LIFECYCLE) return
|
|
283
|
+
this[counter]++
|
|
284
|
+
try {
|
|
285
|
+
sink()
|
|
286
|
+
} catch {
|
|
287
|
+
// Telemetry must never break the relayer's lifecycle handling.
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
relayer.on(RELAYER_EVENTS.disconnect, () => {
|
|
292
|
+
// WC core re-emits `disconnect` on each failed reconnect during one outage —
|
|
293
|
+
// emit only on the transition INTO disconnected so drop/recover stay 1:1 (and
|
|
294
|
+
// relayDropCount counts outages, not re-emits). The relayer passes no payload
|
|
295
|
+
// (socket errors arrive via RELAYER_EVENTS.error / onRelayError).
|
|
296
|
+
if (this.relayHasDisconnected) return
|
|
297
|
+
this.relayHasDisconnected = true
|
|
298
|
+
report(this.telemetryObserver.onRelayDisconnect, 'relayDisconnectCount')
|
|
299
|
+
})
|
|
300
|
+
relayer.on(RELAYER_EVENTS.connect, () => {
|
|
301
|
+
// `connect` also fires on the FIRST socket open — only a connect that follows
|
|
302
|
+
// a disconnect is a genuine recovery and pairs 1:1 with the dead window.
|
|
303
|
+
if (!this.relayHasDisconnected) return
|
|
304
|
+
this.relayHasDisconnected = false
|
|
305
|
+
report(this.telemetryObserver.onRelayReconnect, 'relayReconnectCount')
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
138
309
|
/**
|
|
139
310
|
* The initialized client if `connect()` already ran, else null. Synchronous —
|
|
140
311
|
* lets callers attach lifecycle listeners post-connect without forcing a
|
|
@@ -199,3 +199,40 @@ describe('WalletConnectConnector.onLifecycleEvent', () => {
|
|
|
199
199
|
])
|
|
200
200
|
})
|
|
201
201
|
})
|
|
202
|
+
|
|
203
|
+
describe('WalletConnectConnector telemetry constructor (0.8.x back-compat)', () => {
|
|
204
|
+
// Reach into the private client to prove the constructor normalized whatever
|
|
205
|
+
// form it received into a single telemetryObserver (no relay contact here).
|
|
206
|
+
const observerOf = (c: WalletConnectConnector) =>
|
|
207
|
+
(
|
|
208
|
+
c as unknown as {
|
|
209
|
+
walletConnectClient: { telemetryObserver: Record<string, unknown> }
|
|
210
|
+
}
|
|
211
|
+
).walletConnectClient.telemetryObserver
|
|
212
|
+
|
|
213
|
+
it('forwards the new observer object form', () => {
|
|
214
|
+
const onRelayDisconnect = () => {}
|
|
215
|
+
const c = new WalletConnectConnector(
|
|
216
|
+
{ projectId: 'p' } as WalletConnectConfig,
|
|
217
|
+
{},
|
|
218
|
+
{
|
|
219
|
+
onRelayDisconnect
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
expect(observerOf(c).onRelayDisconnect).toBe(onRelayDisconnect)
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
it('forwards the legacy positional (onRelayError, onInitError) form', () => {
|
|
226
|
+
const onRelayError = () => {}
|
|
227
|
+
const onInitError = () => {}
|
|
228
|
+
// Legacy 0.8.x call site: (config, networkRpcMap, onRelayError, onInitError).
|
|
229
|
+
const c = new WalletConnectConnector(
|
|
230
|
+
{ projectId: 'p' } as WalletConnectConfig,
|
|
231
|
+
{},
|
|
232
|
+
onRelayError,
|
|
233
|
+
onInitError
|
|
234
|
+
)
|
|
235
|
+
expect(observerOf(c).onRelayError).toBe(onRelayError)
|
|
236
|
+
expect(observerOf(c).onInitError).toBe(onInitError)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
@@ -14,7 +14,11 @@ import type {
|
|
|
14
14
|
WalletLifecycleEvent,
|
|
15
15
|
WalletLifecycleContext
|
|
16
16
|
} from '@meshconnect/uwc-types'
|
|
17
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
WalletConnectClient,
|
|
19
|
+
toTelemetryObserver,
|
|
20
|
+
type WalletConnectTelemetryObserver
|
|
21
|
+
} from './services/walletconnect-client'
|
|
18
22
|
import { ConnectionService } from './services/connection-service'
|
|
19
23
|
import { NetworkSwitchService } from './services/network-switch-service'
|
|
20
24
|
import { SignatureService } from './services/signature-service'
|
|
@@ -125,14 +129,31 @@ export class WalletConnectConnector implements Connector {
|
|
|
125
129
|
|
|
126
130
|
constructor(
|
|
127
131
|
config: WalletConnectConfig,
|
|
128
|
-
networkRpcMap
|
|
132
|
+
networkRpcMap?: NetworkRpcMap,
|
|
133
|
+
telemetryObserver?: WalletConnectTelemetryObserver
|
|
134
|
+
)
|
|
135
|
+
/**
|
|
136
|
+
* @deprecated Pass a {@link WalletConnectTelemetryObserver}. The positional
|
|
137
|
+
* `(onRelayError, onInitError)` form is retained for backward compatibility
|
|
138
|
+
* with the published 0.8.x signature and will be removed in a future major.
|
|
139
|
+
*/
|
|
140
|
+
constructor(
|
|
141
|
+
config: WalletConnectConfig,
|
|
142
|
+
networkRpcMap?: NetworkRpcMap,
|
|
129
143
|
onRelayError?: (error: unknown) => void,
|
|
130
144
|
onInitError?: (error: unknown) => void
|
|
145
|
+
)
|
|
146
|
+
constructor(
|
|
147
|
+
config: WalletConnectConfig,
|
|
148
|
+
networkRpcMap: NetworkRpcMap = {},
|
|
149
|
+
observerOrOnRelayError?:
|
|
150
|
+
| WalletConnectTelemetryObserver
|
|
151
|
+
| ((error: unknown) => void),
|
|
152
|
+
onInitError?: (error: unknown) => void
|
|
131
153
|
) {
|
|
132
154
|
this.walletConnectClient = new WalletConnectClient(
|
|
133
155
|
config,
|
|
134
|
-
|
|
135
|
-
onInitError
|
|
156
|
+
toTelemetryObserver(observerOrOnRelayError, onInitError)
|
|
136
157
|
)
|
|
137
158
|
this.networkRpcMap = networkRpcMap
|
|
138
159
|
this.connectionService = new ConnectionService(this.walletConnectClient)
|