@meshconnect/uwc-react 0.10.1 → 0.11.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/README.md +11 -0
- package/dist/providers/ConnectionProvider.d.ts +15 -3
- package/dist/providers/ConnectionProvider.d.ts.map +1 -1
- package/dist/providers/ConnectionProvider.js +53 -2
- package/dist/providers/ConnectionProvider.js.map +1 -1
- package/package.json +3 -3
- package/src/providers/ConnectionProvider.singleton.test.tsx +213 -3
- package/src/providers/ConnectionProvider.tsx +74 -11
package/README.md
CHANGED
|
@@ -239,6 +239,17 @@ Props:
|
|
|
239
239
|
- `usingIntegratedBrowser?: boolean` - Whether using integrated browser (default: false)
|
|
240
240
|
- `walletConnectConfig?: WalletConnectConfig` - Optional WalletConnect configuration
|
|
241
241
|
- `tonConnectConfig?: TonConnectConfig` - Optional TON Connect configuration (requires `manifestUrl`)
|
|
242
|
+
- `tronConnectorConfig?: TronConnectorConfig` - Optional TRON connector configuration
|
|
243
|
+
- `observer?: UWCObserver` - Optional telemetry sink (`onEvent`, optional `onLog`) for wallet-handoff observability; with no observer wired nothing leaves the browser
|
|
244
|
+
- `logger?: Logger` / `logLevel?: LogLevel` - Optional runtime logging configuration
|
|
245
|
+
- `correlationId?: string | (() => string)` - Optional id stamped on every telemetry event; pass the **getter form** for an id that resolves late (e.g. a session id)
|
|
246
|
+
|
|
247
|
+
> **Read once.** The provider builds one shared connector on the first render
|
|
248
|
+
> where `wallets` is non-empty, capturing `observer` / `logger` / `logLevel` /
|
|
249
|
+
> `correlationId` at that instant — later prop changes are ignored. Pass a
|
|
250
|
+
> stable `observer` from the first render (its `onEvent` can no-op until your
|
|
251
|
+
> sink is ready), use the `correlationId` getter for late ids, and
|
|
252
|
+
> `connector.setLogLevel()` for runtime level changes.
|
|
242
253
|
|
|
243
254
|
### Hooks
|
|
244
255
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { UniversalWalletConnector } from '@meshconnect/uwc-core';
|
|
3
|
-
import type { Session, Network, WalletMetadata, WalletConnectConfig, TonConnectConfig, TronConnectorConfig } from '@meshconnect/uwc-types';
|
|
2
|
+
import { UniversalWalletConnector, type UWCObserver } from '@meshconnect/uwc-core';
|
|
3
|
+
import type { Session, Network, WalletMetadata, WalletConnectConfig, TonConnectConfig, TronConnectorConfig, Logger, LogLevel } from '@meshconnect/uwc-types';
|
|
4
4
|
export interface ConnectionContextValue {
|
|
5
5
|
/**
|
|
6
6
|
* `null` when the provider is mounted without wallets. `@meshconnect/uwc-core`
|
|
@@ -25,7 +25,19 @@ interface ConnectionProviderProps {
|
|
|
25
25
|
walletConnectConfig?: WalletConnectConfig;
|
|
26
26
|
tonConnectConfig?: TonConnectConfig;
|
|
27
27
|
tronConnectorConfig?: TronConnectorConfig;
|
|
28
|
+
/**
|
|
29
|
+
* Observability — forwarded to the connector (see @meshconnect/uwc-core).
|
|
30
|
+
* READ ONCE at connector construction (the first render with a non-empty
|
|
31
|
+
* wallet list); the shared singleton ignores later prop changes. Pass a
|
|
32
|
+
* stable `observer`/`logger`, prefer the getter form of `correlationId` for
|
|
33
|
+
* an id that arrives late (it is resolved per-event), and use the
|
|
34
|
+
* connector's `setLogLevel()` for runtime level changes.
|
|
35
|
+
*/
|
|
36
|
+
observer?: UWCObserver;
|
|
37
|
+
logger?: Logger;
|
|
38
|
+
logLevel?: LogLevel;
|
|
39
|
+
correlationId?: string | (() => string);
|
|
28
40
|
}
|
|
29
|
-
export declare function ConnectionProvider({ children, networks, wallets, usingIntegratedBrowser, walletConnectConfig, tonConnectConfig, tronConnectorConfig }: ConnectionProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
41
|
+
export declare function ConnectionProvider({ children, networks, wallets, usingIntegratedBrowser, walletConnectConfig, tonConnectConfig, tronConnectorConfig, observer, logger, logLevel, correlationId }: ConnectionProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
30
42
|
export { ConnectionContext };
|
|
31
43
|
//# sourceMappingURL=ConnectionProvider.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionProvider.d.ts","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,
|
|
1
|
+
{"version":3,"file":"ConnectionProvider.d.ts","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA6C,MAAM,OAAO,CAAA;AACjE,OAAO,EACL,wBAAwB,EAExB,KAAK,WAAW,EACjB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,KAAK,EACV,OAAO,EACP,OAAO,EACP,cAAc,EACd,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,MAAM,EACN,QAAQ,EACT,MAAM,wBAAwB,CAAA;AAkC/B,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,SAAS,EAAE,wBAAwB,GAAG,IAAI,CAAA;IAC1C,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,QAAA,MAAM,iBAAiB,8CAAqD,CAAA;AAE5E,UAAU,uBAAuB;IAC/B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;IACzB,QAAQ,EAAE,OAAO,EAAE,CAAA;IACnB,OAAO,EAAE,cAAc,EAAE,CAAA;IACzB,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;IACnC,mBAAmB,CAAC,EAAE,mBAAmB,CAAA;IACzC;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,QAAQ,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,CAAA;CACxC;AAED,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,sBAA8B,EAC9B,mBAAmB,EACnB,gBAAgB,EAChB,mBAAmB,EACnB,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,aAAa,EACd,EAAE,uBAAuB,2CAkFzB;AAED,OAAO,EAAE,iBAAiB,EAAE,CAAA"}
|
|
@@ -3,6 +3,11 @@ import React, { createContext, useEffect, useState } from 'react';
|
|
|
3
3
|
import { UniversalWalletConnector } from '@meshconnect/uwc-core';
|
|
4
4
|
// Guard against double initialization
|
|
5
5
|
let sharedConnector = null;
|
|
6
|
+
// What the singleton captured at construction — kept to diagnose the one
|
|
7
|
+
// silent failure mode: an observability prop that ARRIVES on a later render
|
|
8
|
+
// is ignored, and without a warning telemetry just never fires.
|
|
9
|
+
let capturedObservability = null;
|
|
10
|
+
let warnedLateObservability = false;
|
|
6
11
|
/**
|
|
7
12
|
* Session served before any wallet is connected — and while the provider is
|
|
8
13
|
* mounted without wallets (a wallet-less session; see `connector` below). Every
|
|
@@ -22,14 +27,60 @@ const EMPTY_SESSION = {
|
|
|
22
27
|
availableAddresses: []
|
|
23
28
|
};
|
|
24
29
|
const ConnectionContext = createContext(null);
|
|
25
|
-
export function ConnectionProvider({ children, networks, wallets, usingIntegratedBrowser = false, walletConnectConfig, tonConnectConfig, tronConnectorConfig }) {
|
|
30
|
+
export function ConnectionProvider({ children, networks, wallets, usingIntegratedBrowser = false, walletConnectConfig, tonConnectConfig, tronConnectorConfig, observer, logger, logLevel, correlationId }) {
|
|
26
31
|
// Core rejects an empty wallet list, so only construct once there is at least
|
|
27
32
|
// one wallet. A wallet-less session keeps `connector` null; the effect below
|
|
28
33
|
// adopts the connector if wallets arrive later (async catalog population).
|
|
29
34
|
if (!sharedConnector && wallets.length > 0) {
|
|
30
|
-
|
|
35
|
+
// Build the config object conditionally — under exactOptionalPropertyTypes an
|
|
36
|
+
// explicit `undefined` can't be assigned to an optional field.
|
|
37
|
+
const config = { networks, wallets, usingIntegratedBrowser };
|
|
38
|
+
if (walletConnectConfig)
|
|
39
|
+
config.walletConnectConfig = walletConnectConfig;
|
|
40
|
+
if (tonConnectConfig)
|
|
41
|
+
config.tonConnectConfig = tonConnectConfig;
|
|
42
|
+
if (tronConnectorConfig)
|
|
43
|
+
config.tronConnectorConfig = tronConnectorConfig;
|
|
44
|
+
if (observer)
|
|
45
|
+
config.observer = observer;
|
|
46
|
+
if (logger)
|
|
47
|
+
config.logger = logger;
|
|
48
|
+
if (logLevel)
|
|
49
|
+
config.logLevel = logLevel;
|
|
50
|
+
if (correlationId !== undefined)
|
|
51
|
+
config.correlationId = correlationId;
|
|
52
|
+
sharedConnector = new UniversalWalletConnector(config);
|
|
53
|
+
capturedObservability = {
|
|
54
|
+
hadObserver: !!observer,
|
|
55
|
+
hadLogger: !!logger,
|
|
56
|
+
hadLogLevel: !!logLevel,
|
|
57
|
+
hadCorrelationId: correlationId !== undefined
|
|
58
|
+
};
|
|
31
59
|
}
|
|
32
60
|
const connector = sharedConnector;
|
|
61
|
+
// Diagnose the read-once footgun: an observability prop supplied only AFTER
|
|
62
|
+
// the shared connector was built is silently ignored — warn once instead of
|
|
63
|
+
// letting telemetry die with no signal. Props that were present at
|
|
64
|
+
// construction are fine (the captured values keep flowing). Runs in an
|
|
65
|
+
// effect: a render must stay side-effect free.
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (!connector || !capturedObservability || warnedLateObservability)
|
|
68
|
+
return;
|
|
69
|
+
const late = [];
|
|
70
|
+
if (observer && !capturedObservability.hadObserver)
|
|
71
|
+
late.push('observer');
|
|
72
|
+
if (logger && !capturedObservability.hadLogger)
|
|
73
|
+
late.push('logger');
|
|
74
|
+
if (logLevel && !capturedObservability.hadLogLevel)
|
|
75
|
+
late.push('logLevel');
|
|
76
|
+
if (correlationId !== undefined && !capturedObservability.hadCorrelationId)
|
|
77
|
+
late.push('correlationId');
|
|
78
|
+
if (late.length > 0) {
|
|
79
|
+
warnedLateObservability = true;
|
|
80
|
+
console.warn(`[uwc-react] ConnectionProvider received ${late.join(', ')} after the shared connector was constructed — these props are read once and the late value is ignored. ` +
|
|
81
|
+
'Pass them from the first render with a non-empty wallet list (observer/logger may be stable no-op shells until your sink is ready; correlationId supports a getter for late ids).');
|
|
82
|
+
}
|
|
83
|
+
}, [connector, observer, logger, logLevel, correlationId]);
|
|
33
84
|
const [session, setSession] = useState(() => connector?.getSession() ?? EMPTY_SESSION);
|
|
34
85
|
const [isReady, setIsReady] = useState(() => connector?.isReady() ?? false);
|
|
35
86
|
useEffect(() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ConnectionProvider.js","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":";AAAA,OAAO,KAAK,EAAE,EAAE,aAAa,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAA;AACjE,OAAO,
|
|
1
|
+
{"version":3,"file":"ConnectionProvider.js","sourceRoot":"","sources":["../../src/providers/ConnectionProvider.tsx"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@meshconnect/uwc-react",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "React hooks and components for Universal Wallet Connector",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -16,8 +16,8 @@
|
|
|
16
16
|
"src"
|
|
17
17
|
],
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@meshconnect/uwc-core": "1.0
|
|
20
|
-
"@meshconnect/uwc-types": "0.
|
|
19
|
+
"@meshconnect/uwc-core": "1.1.0",
|
|
20
|
+
"@meshconnect/uwc-types": "0.17.0"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
23
|
"react": "^18.0.0",
|
|
@@ -159,13 +159,223 @@ describe('ConnectionProvider singleton behavior', () => {
|
|
|
159
159
|
)
|
|
160
160
|
})
|
|
161
161
|
|
|
162
|
-
// Constructor
|
|
163
|
-
//
|
|
162
|
+
// Constructor now receives a single UWCConfig object (config-object form),
|
|
163
|
+
// so the provider can also forward the observability fields.
|
|
164
164
|
expect(ctorArgs.length).toBe(1)
|
|
165
|
-
expect(ctorArgs[0]?.[
|
|
165
|
+
expect(ctorArgs[0]?.[0]?.tronConnectorConfig).toBe(tronConnectorConfig)
|
|
166
166
|
|
|
167
167
|
await act(async () => {
|
|
168
168
|
root.unmount()
|
|
169
169
|
})
|
|
170
170
|
})
|
|
171
|
+
|
|
172
|
+
it('forwards the observer to the UniversalWalletConnector config', async () => {
|
|
173
|
+
const ctorArgs: unknown[][] = []
|
|
174
|
+
vi.doMock('@meshconnect/uwc-core', () => {
|
|
175
|
+
class UniversalWalletConnector {
|
|
176
|
+
constructor(...args: unknown[]) {
|
|
177
|
+
ctorArgs.push(args)
|
|
178
|
+
}
|
|
179
|
+
getSession() {
|
|
180
|
+
return {
|
|
181
|
+
isConnected: false,
|
|
182
|
+
walletId: undefined,
|
|
183
|
+
networkId: undefined
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
isReady() {
|
|
187
|
+
return true
|
|
188
|
+
}
|
|
189
|
+
subscribe() {
|
|
190
|
+
return () => {}
|
|
191
|
+
}
|
|
192
|
+
getWallets() {
|
|
193
|
+
return []
|
|
194
|
+
}
|
|
195
|
+
getNetworks() {
|
|
196
|
+
return []
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return { UniversalWalletConnector }
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
const { ConnectionProvider } = await import('./ConnectionProvider')
|
|
203
|
+
const observer = { onEvent: () => {} }
|
|
204
|
+
|
|
205
|
+
const container = document.createElement('div')
|
|
206
|
+
document.body.appendChild(container)
|
|
207
|
+
const root = createRoot(container)
|
|
208
|
+
|
|
209
|
+
await act(async () => {
|
|
210
|
+
root.render(
|
|
211
|
+
<ConnectionProvider
|
|
212
|
+
networks={[]}
|
|
213
|
+
wallets={TEST_WALLETS}
|
|
214
|
+
observer={observer}
|
|
215
|
+
>
|
|
216
|
+
<div />
|
|
217
|
+
</ConnectionProvider>
|
|
218
|
+
)
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
expect(ctorArgs.length).toBe(1)
|
|
222
|
+
expect((ctorArgs[0]?.[0] as { observer?: unknown })?.observer).toBe(
|
|
223
|
+
observer
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
await act(async () => {
|
|
227
|
+
root.unmount()
|
|
228
|
+
})
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('forwards logger, logLevel and correlationId (including the empty string) to the config', async () => {
|
|
232
|
+
const ctorArgs: unknown[][] = []
|
|
233
|
+
vi.doMock('@meshconnect/uwc-core', () => {
|
|
234
|
+
class UniversalWalletConnector {
|
|
235
|
+
constructor(...args: unknown[]) {
|
|
236
|
+
ctorArgs.push(args)
|
|
237
|
+
}
|
|
238
|
+
getSession() {
|
|
239
|
+
return {
|
|
240
|
+
isConnected: false,
|
|
241
|
+
walletId: undefined,
|
|
242
|
+
networkId: undefined
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
isReady() {
|
|
246
|
+
return true
|
|
247
|
+
}
|
|
248
|
+
subscribe() {
|
|
249
|
+
return () => {}
|
|
250
|
+
}
|
|
251
|
+
getWallets() {
|
|
252
|
+
return []
|
|
253
|
+
}
|
|
254
|
+
getNetworks() {
|
|
255
|
+
return []
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return { UniversalWalletConnector }
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
const { ConnectionProvider } = await import('./ConnectionProvider')
|
|
262
|
+
const logger = {
|
|
263
|
+
debug: () => {},
|
|
264
|
+
info: () => {},
|
|
265
|
+
warn: () => {},
|
|
266
|
+
error: () => {}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const container = document.createElement('div')
|
|
270
|
+
document.body.appendChild(container)
|
|
271
|
+
const root = createRoot(container)
|
|
272
|
+
|
|
273
|
+
await act(async () => {
|
|
274
|
+
root.render(
|
|
275
|
+
<ConnectionProvider
|
|
276
|
+
networks={[]}
|
|
277
|
+
wallets={TEST_WALLETS}
|
|
278
|
+
logger={logger}
|
|
279
|
+
logLevel="debug"
|
|
280
|
+
correlationId=""
|
|
281
|
+
>
|
|
282
|
+
<div />
|
|
283
|
+
</ConnectionProvider>
|
|
284
|
+
)
|
|
285
|
+
})
|
|
286
|
+
|
|
287
|
+
expect(ctorArgs.length).toBe(1)
|
|
288
|
+
const config = ctorArgs[0]?.[0] as {
|
|
289
|
+
logger?: unknown
|
|
290
|
+
logLevel?: unknown
|
|
291
|
+
correlationId?: unknown
|
|
292
|
+
}
|
|
293
|
+
expect(config?.logger).toBe(logger)
|
|
294
|
+
expect(config?.logLevel).toBe('debug')
|
|
295
|
+
// '' must forward: the guard is deliberately `!== undefined`, not
|
|
296
|
+
// truthiness — an empty-string correlation id is a valid consumer value.
|
|
297
|
+
expect(config?.correlationId).toBe('')
|
|
298
|
+
|
|
299
|
+
await act(async () => {
|
|
300
|
+
root.unmount()
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
it('warns once (from an effect, not render) when an observability prop arrives after construction', async () => {
|
|
305
|
+
vi.doMock('@meshconnect/uwc-core', () => {
|
|
306
|
+
class UniversalWalletConnector {
|
|
307
|
+
getSession() {
|
|
308
|
+
return {
|
|
309
|
+
isConnected: false,
|
|
310
|
+
walletId: undefined,
|
|
311
|
+
networkId: undefined
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
isReady() {
|
|
315
|
+
return true
|
|
316
|
+
}
|
|
317
|
+
subscribe() {
|
|
318
|
+
return () => {}
|
|
319
|
+
}
|
|
320
|
+
getWallets() {
|
|
321
|
+
return []
|
|
322
|
+
}
|
|
323
|
+
getNetworks() {
|
|
324
|
+
return []
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return { UniversalWalletConnector }
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
const { ConnectionProvider } = await import('./ConnectionProvider')
|
|
331
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
|
|
332
|
+
|
|
333
|
+
const container = document.createElement('div')
|
|
334
|
+
document.body.appendChild(container)
|
|
335
|
+
const root = createRoot(container)
|
|
336
|
+
|
|
337
|
+
// First render: connector constructed WITHOUT an observer.
|
|
338
|
+
await act(async () => {
|
|
339
|
+
root.render(
|
|
340
|
+
<ConnectionProvider networks={[]} wallets={TEST_WALLETS}>
|
|
341
|
+
<div />
|
|
342
|
+
</ConnectionProvider>
|
|
343
|
+
)
|
|
344
|
+
})
|
|
345
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
346
|
+
|
|
347
|
+
// Late observer: read-once means it is ignored — must warn, exactly once
|
|
348
|
+
// across re-renders.
|
|
349
|
+
const observer = { onEvent: () => {} }
|
|
350
|
+
await act(async () => {
|
|
351
|
+
root.render(
|
|
352
|
+
<ConnectionProvider
|
|
353
|
+
networks={[]}
|
|
354
|
+
wallets={TEST_WALLETS}
|
|
355
|
+
observer={observer}
|
|
356
|
+
>
|
|
357
|
+
<div />
|
|
358
|
+
</ConnectionProvider>
|
|
359
|
+
)
|
|
360
|
+
})
|
|
361
|
+
await act(async () => {
|
|
362
|
+
root.render(
|
|
363
|
+
<ConnectionProvider
|
|
364
|
+
networks={[]}
|
|
365
|
+
wallets={TEST_WALLETS}
|
|
366
|
+
observer={observer}
|
|
367
|
+
>
|
|
368
|
+
<div />
|
|
369
|
+
</ConnectionProvider>
|
|
370
|
+
)
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
expect(warnSpy).toHaveBeenCalledTimes(1)
|
|
374
|
+
expect(warnSpy.mock.calls[0]?.[0]).toContain('observer')
|
|
375
|
+
|
|
376
|
+
warnSpy.mockRestore()
|
|
377
|
+
await act(async () => {
|
|
378
|
+
root.unmount()
|
|
379
|
+
})
|
|
380
|
+
})
|
|
171
381
|
})
|
|
@@ -1,16 +1,32 @@
|
|
|
1
1
|
import React, { createContext, useEffect, useState } from 'react'
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
UniversalWalletConnector,
|
|
4
|
+
type UWCConfig,
|
|
5
|
+
type UWCObserver
|
|
6
|
+
} from '@meshconnect/uwc-core'
|
|
3
7
|
import type {
|
|
4
8
|
Session,
|
|
5
9
|
Network,
|
|
6
10
|
WalletMetadata,
|
|
7
11
|
WalletConnectConfig,
|
|
8
12
|
TonConnectConfig,
|
|
9
|
-
TronConnectorConfig
|
|
13
|
+
TronConnectorConfig,
|
|
14
|
+
Logger,
|
|
15
|
+
LogLevel
|
|
10
16
|
} from '@meshconnect/uwc-types'
|
|
11
17
|
|
|
12
18
|
// Guard against double initialization
|
|
13
19
|
let sharedConnector: UniversalWalletConnector | null = null
|
|
20
|
+
// What the singleton captured at construction — kept to diagnose the one
|
|
21
|
+
// silent failure mode: an observability prop that ARRIVES on a later render
|
|
22
|
+
// is ignored, and without a warning telemetry just never fires.
|
|
23
|
+
let capturedObservability: {
|
|
24
|
+
hadObserver: boolean
|
|
25
|
+
hadLogger: boolean
|
|
26
|
+
hadLogLevel: boolean
|
|
27
|
+
hadCorrelationId: boolean
|
|
28
|
+
} | null = null
|
|
29
|
+
let warnedLateObservability = false
|
|
14
30
|
|
|
15
31
|
/**
|
|
16
32
|
* Session served before any wallet is connected — and while the provider is
|
|
@@ -57,6 +73,18 @@ interface ConnectionProviderProps {
|
|
|
57
73
|
walletConnectConfig?: WalletConnectConfig
|
|
58
74
|
tonConnectConfig?: TonConnectConfig
|
|
59
75
|
tronConnectorConfig?: TronConnectorConfig
|
|
76
|
+
/**
|
|
77
|
+
* Observability — forwarded to the connector (see @meshconnect/uwc-core).
|
|
78
|
+
* READ ONCE at connector construction (the first render with a non-empty
|
|
79
|
+
* wallet list); the shared singleton ignores later prop changes. Pass a
|
|
80
|
+
* stable `observer`/`logger`, prefer the getter form of `correlationId` for
|
|
81
|
+
* an id that arrives late (it is resolved per-event), and use the
|
|
82
|
+
* connector's `setLogLevel()` for runtime level changes.
|
|
83
|
+
*/
|
|
84
|
+
observer?: UWCObserver
|
|
85
|
+
logger?: Logger
|
|
86
|
+
logLevel?: LogLevel
|
|
87
|
+
correlationId?: string | (() => string)
|
|
60
88
|
}
|
|
61
89
|
|
|
62
90
|
export function ConnectionProvider({
|
|
@@ -66,24 +94,59 @@ export function ConnectionProvider({
|
|
|
66
94
|
usingIntegratedBrowser = false,
|
|
67
95
|
walletConnectConfig,
|
|
68
96
|
tonConnectConfig,
|
|
69
|
-
tronConnectorConfig
|
|
97
|
+
tronConnectorConfig,
|
|
98
|
+
observer,
|
|
99
|
+
logger,
|
|
100
|
+
logLevel,
|
|
101
|
+
correlationId
|
|
70
102
|
}: ConnectionProviderProps) {
|
|
71
103
|
// Core rejects an empty wallet list, so only construct once there is at least
|
|
72
104
|
// one wallet. A wallet-less session keeps `connector` null; the effect below
|
|
73
105
|
// adopts the connector if wallets arrive later (async catalog population).
|
|
74
106
|
if (!sharedConnector && wallets.length > 0) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
107
|
+
// Build the config object conditionally — under exactOptionalPropertyTypes an
|
|
108
|
+
// explicit `undefined` can't be assigned to an optional field.
|
|
109
|
+
const config: UWCConfig = { networks, wallets, usingIntegratedBrowser }
|
|
110
|
+
if (walletConnectConfig) config.walletConnectConfig = walletConnectConfig
|
|
111
|
+
if (tonConnectConfig) config.tonConnectConfig = tonConnectConfig
|
|
112
|
+
if (tronConnectorConfig) config.tronConnectorConfig = tronConnectorConfig
|
|
113
|
+
if (observer) config.observer = observer
|
|
114
|
+
if (logger) config.logger = logger
|
|
115
|
+
if (logLevel) config.logLevel = logLevel
|
|
116
|
+
if (correlationId !== undefined) config.correlationId = correlationId
|
|
117
|
+
sharedConnector = new UniversalWalletConnector(config)
|
|
118
|
+
capturedObservability = {
|
|
119
|
+
hadObserver: !!observer,
|
|
120
|
+
hadLogger: !!logger,
|
|
121
|
+
hadLogLevel: !!logLevel,
|
|
122
|
+
hadCorrelationId: correlationId !== undefined
|
|
123
|
+
}
|
|
83
124
|
}
|
|
84
125
|
|
|
85
126
|
const connector = sharedConnector
|
|
86
127
|
|
|
128
|
+
// Diagnose the read-once footgun: an observability prop supplied only AFTER
|
|
129
|
+
// the shared connector was built is silently ignored — warn once instead of
|
|
130
|
+
// letting telemetry die with no signal. Props that were present at
|
|
131
|
+
// construction are fine (the captured values keep flowing). Runs in an
|
|
132
|
+
// effect: a render must stay side-effect free.
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
if (!connector || !capturedObservability || warnedLateObservability) return
|
|
135
|
+
const late: string[] = []
|
|
136
|
+
if (observer && !capturedObservability.hadObserver) late.push('observer')
|
|
137
|
+
if (logger && !capturedObservability.hadLogger) late.push('logger')
|
|
138
|
+
if (logLevel && !capturedObservability.hadLogLevel) late.push('logLevel')
|
|
139
|
+
if (correlationId !== undefined && !capturedObservability.hadCorrelationId)
|
|
140
|
+
late.push('correlationId')
|
|
141
|
+
if (late.length > 0) {
|
|
142
|
+
warnedLateObservability = true
|
|
143
|
+
console.warn(
|
|
144
|
+
`[uwc-react] ConnectionProvider received ${late.join(', ')} after the shared connector was constructed — these props are read once and the late value is ignored. ` +
|
|
145
|
+
'Pass them from the first render with a non-empty wallet list (observer/logger may be stable no-op shells until your sink is ready; correlationId supports a getter for late ids).'
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
}, [connector, observer, logger, logLevel, correlationId])
|
|
149
|
+
|
|
87
150
|
const [session, setSession] = useState<Session>(
|
|
88
151
|
() => connector?.getSession() ?? EMPTY_SESSION
|
|
89
152
|
)
|