@meshconnect/uwc-core 1.0.0 → 1.0.1-snapshot.c7e65ce
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 +211 -66
- package/dist/events.d.ts +69 -2
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +40 -1
- package/dist/events.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/logger/create-logger.d.ts +32 -0
- package/dist/logger/create-logger.d.ts.map +1 -0
- package/dist/logger/create-logger.js +69 -0
- package/dist/logger/create-logger.js.map +1 -0
- package/dist/managers/event-manager.d.ts.map +1 -1
- package/dist/managers/event-manager.js +5 -1
- package/dist/managers/event-manager.js.map +1 -1
- package/dist/observability/connect-observer.d.ts +22 -0
- package/dist/observability/connect-observer.d.ts.map +1 -0
- package/dist/observability/connect-observer.js +60 -0
- package/dist/observability/connect-observer.js.map +1 -0
- package/dist/observability/instrument-handoff.d.ts +39 -0
- package/dist/observability/instrument-handoff.d.ts.map +1 -0
- package/dist/observability/instrument-handoff.js +86 -0
- package/dist/observability/instrument-handoff.js.map +1 -0
- package/dist/observability/scrub.d.ts +30 -0
- package/dist/observability/scrub.d.ts.map +1 -0
- package/dist/observability/scrub.js +135 -0
- package/dist/observability/scrub.js.map +1 -0
- package/dist/observability/telemetry.d.ts +77 -0
- package/dist/observability/telemetry.d.ts.map +1 -0
- package/dist/observability/telemetry.js +138 -0
- package/dist/observability/telemetry.js.map +1 -0
- package/dist/services/connection-service.d.ts +2 -1
- package/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +35 -15
- package/dist/services/connection-service.js.map +1 -1
- package/dist/services/network-switch-service.d.ts +2 -1
- package/dist/services/network-switch-service.d.ts.map +1 -1
- package/dist/services/network-switch-service.js +33 -15
- package/dist/services/network-switch-service.js.map +1 -1
- package/dist/universal-wallet-connector.d.ts +51 -2
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +163 -61
- package/dist/universal-wallet-connector.js.map +1 -1
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +41 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/to-wallet-error.d.ts +16 -0
- package/dist/utils/to-wallet-error.d.ts.map +1 -0
- package/dist/utils/to-wallet-error.js +33 -0
- package/dist/utils/to-wallet-error.js.map +1 -0
- package/package.json +5 -5
- package/src/events.ts +114 -1
- package/src/index.ts +6 -0
- package/src/logger/create-logger.test.ts +111 -0
- package/src/logger/create-logger.ts +101 -0
- package/src/managers/event-manager.test.ts +25 -0
- package/src/managers/event-manager.ts +5 -1
- package/src/observability/connect-observer.test.ts +100 -0
- package/src/observability/connect-observer.ts +71 -0
- package/src/observability/instrument-handoff.test.ts +150 -0
- package/src/observability/instrument-handoff.ts +122 -0
- package/src/observability/scrub.test.ts +109 -0
- package/src/observability/scrub.ts +142 -0
- package/src/observability/telemetry.test.ts +134 -0
- package/src/observability/telemetry.ts +211 -0
- package/src/services/connection-service.test.ts +66 -1
- package/src/services/connection-service.ts +54 -32
- package/src/services/network-switch-service.test.ts +51 -1
- package/src/services/network-switch-service.ts +43 -23
- package/src/universal-wallet-connector.observability.test.ts +265 -0
- package/src/universal-wallet-connector.ts +244 -71
- package/src/utils/id.test.ts +15 -0
- package/src/utils/id.ts +48 -0
- package/src/utils/to-wallet-error.ts +36 -0
|
@@ -18,9 +18,10 @@ import type {
|
|
|
18
18
|
EVMCapabilities,
|
|
19
19
|
SignatureType,
|
|
20
20
|
EIP712TypedData,
|
|
21
|
-
|
|
21
|
+
LogLevel,
|
|
22
|
+
Logger,
|
|
23
|
+
Session
|
|
22
24
|
} from '@meshconnect/uwc-types'
|
|
23
|
-
import { WalletConnectorError } from '@meshconnect/uwc-types'
|
|
24
25
|
import { InjectedConnector } from '@meshconnect/uwc-injected-connector'
|
|
25
26
|
import { WalletConnectConnector } from '@meshconnect/uwc-wallet-connect-connector'
|
|
26
27
|
import { TonConnectConnector } from '@meshconnect/uwc-ton-connector'
|
|
@@ -33,8 +34,20 @@ import { SignatureService } from './services/signature-service'
|
|
|
33
34
|
import { TransactionService } from './services/transaction-service'
|
|
34
35
|
import { WalletCapabilitiesService } from './services/wallet-capabilities-service'
|
|
35
36
|
import { createNetworkRpcMap } from './utils/network-rpc-utils'
|
|
37
|
+
import { generateId } from './utils/id'
|
|
38
|
+
import { toWalletError, isAbortError } from './utils/to-wallet-error'
|
|
39
|
+
import { createLogger, type ManagedLogger } from './logger/create-logger'
|
|
40
|
+
import { connectObserver } from './observability/connect-observer'
|
|
41
|
+
import type { UWCObserver } from './observability/telemetry'
|
|
42
|
+
import {
|
|
43
|
+
instrumentHandoff,
|
|
44
|
+
type HandoffContext
|
|
45
|
+
} from './observability/instrument-handoff'
|
|
36
46
|
import type { UWCEventName, UWCEventListener, UWCOperation } from './events'
|
|
37
47
|
|
|
48
|
+
/** Default observational wallet-response deadline (ms). Matches TON's 60s. */
|
|
49
|
+
const DEFAULT_WALLET_RESPONSE_TIMEOUT_MS = 60_000
|
|
50
|
+
|
|
38
51
|
/**
|
|
39
52
|
* Configuration object form of the UniversalWalletConnector constructor.
|
|
40
53
|
* Prefer this over the positional constructor in new code.
|
|
@@ -49,6 +62,35 @@ export interface UWCConfig {
|
|
|
49
62
|
walletConnectConfig?: WalletConnectConfig
|
|
50
63
|
tonConnectConfig?: TonConnectConfig
|
|
51
64
|
tronConnectorConfig?: TronConnectorConfig
|
|
65
|
+
// ---- Observability (all optional; defaults preserve current behaviour) -----
|
|
66
|
+
/**
|
|
67
|
+
* Vendor-agnostic telemetry sink. When omitted, UWC emits NOTHING externally —
|
|
68
|
+
* zero egress is the security-first default for a sign-only wallet layer. The
|
|
69
|
+
* consumer implements the sink and wires it to their backend (Datadog/…).
|
|
70
|
+
*/
|
|
71
|
+
observer?: UWCObserver
|
|
72
|
+
/**
|
|
73
|
+
* Redirect UWC's internal logging into your own logger. Receives raw
|
|
74
|
+
* `(message, ...args)` at/above `logLevel`. When omitted, a console-backed
|
|
75
|
+
* logger is used.
|
|
76
|
+
*/
|
|
77
|
+
logger?: Logger
|
|
78
|
+
/** Console threshold for the default logger. Default `'warn'`. */
|
|
79
|
+
logLevel?: LogLevel
|
|
80
|
+
/**
|
|
81
|
+
* Correlation id stamped on every telemetry record (`sdkSessionId`). Supply
|
|
82
|
+
* your own (e.g. the consumer's `link_sess_id`) so UWC events join your existing
|
|
83
|
+
* telemetry in Datadog/RUM without a separate mapping. Pass a getter if the id
|
|
84
|
+
* isn't known at construction or can change. Defaults to a generated per-instance
|
|
85
|
+
* id. Resolved per-event; a throwing/empty getter falls back to the default.
|
|
86
|
+
*/
|
|
87
|
+
correlationId?: string | (() => string)
|
|
88
|
+
/**
|
|
89
|
+
* Observational wallet-response deadline (ms) for the `walletTimedOut` marker.
|
|
90
|
+
* `0` disables it. TON is always exempt (it bounds its own response). Default
|
|
91
|
+
* 60_000.
|
|
92
|
+
*/
|
|
93
|
+
walletResponseTimeoutMs?: number
|
|
52
94
|
}
|
|
53
95
|
|
|
54
96
|
/** Common options for user-initiated async operations. */
|
|
@@ -75,6 +117,12 @@ export class UniversalWalletConnector {
|
|
|
75
117
|
private usingIntegratedBrowser: boolean
|
|
76
118
|
private detectionComplete = false
|
|
77
119
|
private networkRpcMap: NetworkRpcMap
|
|
120
|
+
private logger: ManagedLogger
|
|
121
|
+
/** Per-instance correlation id stamped on every telemetry record. */
|
|
122
|
+
private readonly sdkSessionId: string = generateId()
|
|
123
|
+
private walletResponseTimeoutMs: number
|
|
124
|
+
/** Tears down the observer subscriptions; set only when an observer is wired. */
|
|
125
|
+
private observerTeardown: (() => void) | undefined
|
|
78
126
|
|
|
79
127
|
/**
|
|
80
128
|
* Recommended entry point: returns the shared instance, creating it on the
|
|
@@ -152,14 +200,49 @@ export class UniversalWalletConnector {
|
|
|
152
200
|
tronConnectorConfig: configuredTronConnectorConfig
|
|
153
201
|
} = config
|
|
154
202
|
|
|
203
|
+
// Observability seam — wire the event bus, logger, and (optional) observer
|
|
204
|
+
// FIRST so even construction-time logs/events are captured. `sdkSessionId` is
|
|
205
|
+
// already assigned as a field initializer.
|
|
206
|
+
this.eventManager = new EventManager()
|
|
207
|
+
this.walletResponseTimeoutMs =
|
|
208
|
+
config.walletResponseTimeoutMs ?? DEFAULT_WALLET_RESPONSE_TIMEOUT_MS
|
|
209
|
+
this.logger = createLogger({
|
|
210
|
+
level: config.logLevel ?? 'warn',
|
|
211
|
+
sink: config.logger,
|
|
212
|
+
// Pre-gate tap → typed `log` event → observer.onLog. Fires for all levels
|
|
213
|
+
// regardless of the console threshold.
|
|
214
|
+
onLog: (level, message, args) =>
|
|
215
|
+
this.eventManager.emit('log', { level, message, args })
|
|
216
|
+
})
|
|
217
|
+
if (config.observer) {
|
|
218
|
+
// Resolve the correlation id per-event. A consumer can align it to their
|
|
219
|
+
// own session key (e.g. link_sess_id); the generated `sdkSessionId` is the
|
|
220
|
+
// always-available fallback if none is supplied or a getter misbehaves.
|
|
221
|
+
const cid = config.correlationId
|
|
222
|
+
const getCorrelationId: () => string =
|
|
223
|
+
typeof cid === 'function'
|
|
224
|
+
? () => {
|
|
225
|
+
try {
|
|
226
|
+
return cid() || this.sdkSessionId
|
|
227
|
+
} catch {
|
|
228
|
+
return this.sdkSessionId
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
: () => cid ?? this.sdkSessionId
|
|
232
|
+
this.observerTeardown = connectObserver(
|
|
233
|
+
this.eventManager,
|
|
234
|
+
config.observer,
|
|
235
|
+
getCorrelationId
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
155
239
|
// Track instance creation and warn on duplicates
|
|
156
240
|
UniversalWalletConnector.instanceCount++
|
|
157
241
|
if (
|
|
158
242
|
UniversalWalletConnector.instanceCount > 1 &&
|
|
159
243
|
!UniversalWalletConnector.hasWarnedAboutMultipleInstances
|
|
160
244
|
) {
|
|
161
|
-
|
|
162
|
-
console.error(
|
|
245
|
+
this.logger.error(
|
|
163
246
|
`⚠️ WARNING: Multiple instances of UniversalWalletConnector detected (${UniversalWalletConnector.instanceCount} instances). ` +
|
|
164
247
|
'This can lead to state inconsistencies and unexpected behavior. ' +
|
|
165
248
|
'Please ensure you create only one instance and reuse it throughout your application. ' +
|
|
@@ -220,21 +303,22 @@ export class UniversalWalletConnector {
|
|
|
220
303
|
}
|
|
221
304
|
|
|
222
305
|
this.sessionManager = new SessionManager()
|
|
223
|
-
this.eventManager = new EventManager()
|
|
224
306
|
this.connectionService = new ConnectionService(
|
|
225
307
|
networks,
|
|
226
308
|
this.wallets,
|
|
227
309
|
this.sessionManager,
|
|
228
310
|
this.connectors,
|
|
229
311
|
configuredUsingIntegratedBrowser,
|
|
230
|
-
this.eventManager
|
|
312
|
+
this.eventManager,
|
|
313
|
+
this.walletResponseTimeoutMs
|
|
231
314
|
)
|
|
232
315
|
this.networkSwitchService = new NetworkSwitchService(
|
|
233
316
|
networks,
|
|
234
317
|
this.sessionManager,
|
|
235
318
|
this.connectors,
|
|
236
319
|
configuredUsingIntegratedBrowser,
|
|
237
|
-
this.eventManager
|
|
320
|
+
this.eventManager,
|
|
321
|
+
this.walletResponseTimeoutMs
|
|
238
322
|
)
|
|
239
323
|
this.signatureService = new SignatureService(
|
|
240
324
|
this.sessionManager,
|
|
@@ -518,6 +602,9 @@ export class UniversalWalletConnector {
|
|
|
518
602
|
options?: OperationOptions
|
|
519
603
|
): Promise<void> {
|
|
520
604
|
try {
|
|
605
|
+
// Hand-off instrumentation lives in ConnectionService.connect (emitted
|
|
606
|
+
// after validation, at the real wallet boundary) — not here, so pre-wallet
|
|
607
|
+
// validation failures don't pollute the funnel.
|
|
521
608
|
await this.connectionService.connect(
|
|
522
609
|
connectionMode,
|
|
523
610
|
walletId,
|
|
@@ -548,6 +635,8 @@ export class UniversalWalletConnector {
|
|
|
548
635
|
options?: OperationOptions
|
|
549
636
|
): Promise<void> {
|
|
550
637
|
try {
|
|
638
|
+
// Hand-off instrumentation lives in NetworkSwitchService.switchNetwork
|
|
639
|
+
// (emitted after validation, at the real wallet boundary).
|
|
551
640
|
await this.networkSwitchService.switchNetwork(networkId, options)
|
|
552
641
|
} catch (error) {
|
|
553
642
|
this.emitError(error, 'switchNetwork')
|
|
@@ -580,27 +669,36 @@ export class UniversalWalletConnector {
|
|
|
580
669
|
}
|
|
581
670
|
|
|
582
671
|
try {
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
672
|
+
return await this.instrument(
|
|
673
|
+
this.sessionHandoffContext('signMessage', session),
|
|
674
|
+
async () => {
|
|
675
|
+
// Get the provider from the active wallet based on connection mode
|
|
676
|
+
let provider
|
|
677
|
+
if (session.connectionMode === 'injected') {
|
|
678
|
+
provider = this.usingIntegratedBrowser
|
|
679
|
+
? session.activeWallet!.integratedBrowserInjectedProvider
|
|
680
|
+
: session.activeWallet!.extensionInjectedProvider
|
|
681
|
+
} else if (session.connectionMode === 'walletConnect') {
|
|
682
|
+
provider = session.activeWallet!.walletConnectProvider
|
|
683
|
+
} else if (session.connectionMode === 'tonConnect') {
|
|
684
|
+
return await this.signatureService.signMessage(
|
|
685
|
+
message,
|
|
686
|
+
undefined,
|
|
687
|
+
options
|
|
688
|
+
)
|
|
689
|
+
}
|
|
598
690
|
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
691
|
+
if (!provider) {
|
|
692
|
+
throw new Error('No provider available for the active wallet')
|
|
693
|
+
}
|
|
602
694
|
|
|
603
|
-
|
|
695
|
+
return await this.signatureService.signMessage(
|
|
696
|
+
message,
|
|
697
|
+
provider,
|
|
698
|
+
options
|
|
699
|
+
)
|
|
700
|
+
}
|
|
701
|
+
)
|
|
604
702
|
} catch (error) {
|
|
605
703
|
this.emitError(error, 'signMessage')
|
|
606
704
|
throw error
|
|
@@ -630,20 +728,30 @@ export class UniversalWalletConnector {
|
|
|
630
728
|
throw new Error('signTypedData is not supported for TON wallets')
|
|
631
729
|
}
|
|
632
730
|
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
731
|
+
try {
|
|
732
|
+
return await this.instrument(
|
|
733
|
+
this.sessionHandoffContext('signTypedData', session),
|
|
734
|
+
async () => {
|
|
735
|
+
// Resolve the injected provider from the active wallet. WalletConnect
|
|
736
|
+
// resolves its own provider inside the connector, so it stays undefined.
|
|
737
|
+
let provider
|
|
738
|
+
if (session.connectionMode === 'injected') {
|
|
739
|
+
provider = this.usingIntegratedBrowser
|
|
740
|
+
? session.activeWallet!.integratedBrowserInjectedProvider
|
|
741
|
+
: session.activeWallet!.extensionInjectedProvider
|
|
742
|
+
|
|
743
|
+
if (!provider) {
|
|
744
|
+
throw new Error('No provider available for the active wallet')
|
|
745
|
+
}
|
|
746
|
+
}
|
|
640
747
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
748
|
+
return await this.signatureService.signTypedData(typedData, provider)
|
|
749
|
+
}
|
|
750
|
+
)
|
|
751
|
+
} catch (error) {
|
|
752
|
+
this.emitError(error, 'signTypedData')
|
|
753
|
+
throw error
|
|
644
754
|
}
|
|
645
|
-
|
|
646
|
-
return await this.signatureService.signTypedData(typedData, provider)
|
|
647
755
|
}
|
|
648
756
|
|
|
649
757
|
/**
|
|
@@ -667,30 +775,35 @@ export class UniversalWalletConnector {
|
|
|
667
775
|
}
|
|
668
776
|
|
|
669
777
|
try {
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
778
|
+
return await this.instrument(
|
|
779
|
+
this.sessionHandoffContext('sendTransaction', session),
|
|
780
|
+
async () => {
|
|
781
|
+
// Get the provider from the active wallet based on connection mode
|
|
782
|
+
let provider
|
|
783
|
+
if (session.connectionMode === 'injected') {
|
|
784
|
+
provider = this.usingIntegratedBrowser
|
|
785
|
+
? session.activeWallet!.integratedBrowserInjectedProvider
|
|
786
|
+
: session.activeWallet!.extensionInjectedProvider
|
|
787
|
+
} else if (session.connectionMode === 'walletConnect') {
|
|
788
|
+
provider = session.activeWallet!.walletConnectProvider
|
|
789
|
+
} else if (session.connectionMode === 'tonConnect') {
|
|
790
|
+
return await this.transactionService.sendTransaction(
|
|
791
|
+
request,
|
|
792
|
+
undefined,
|
|
793
|
+
options
|
|
794
|
+
)
|
|
795
|
+
}
|
|
685
796
|
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
797
|
+
if (!provider) {
|
|
798
|
+
throw new Error('No provider available for the active wallet')
|
|
799
|
+
}
|
|
689
800
|
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
801
|
+
return await this.transactionService.sendTransaction(
|
|
802
|
+
request,
|
|
803
|
+
provider,
|
|
804
|
+
options
|
|
805
|
+
)
|
|
806
|
+
}
|
|
694
807
|
)
|
|
695
808
|
} catch (error) {
|
|
696
809
|
this.emitError(error, 'sendTransaction')
|
|
@@ -722,7 +835,15 @@ export class UniversalWalletConnector {
|
|
|
722
835
|
)
|
|
723
836
|
}
|
|
724
837
|
|
|
725
|
-
|
|
838
|
+
try {
|
|
839
|
+
return await this.instrument(
|
|
840
|
+
this.sessionHandoffContext('signSolanaTransaction', session),
|
|
841
|
+
() => connector.signSolanaTransactionBytes!(serializedTx)
|
|
842
|
+
)
|
|
843
|
+
} catch (error) {
|
|
844
|
+
this.emitError(error, 'signSolanaTransaction')
|
|
845
|
+
throw error
|
|
846
|
+
}
|
|
726
847
|
}
|
|
727
848
|
|
|
728
849
|
/**
|
|
@@ -809,22 +930,74 @@ export class UniversalWalletConnector {
|
|
|
809
930
|
return session.activeWalletCapabilities
|
|
810
931
|
}
|
|
811
932
|
|
|
933
|
+
/**
|
|
934
|
+
* Set the console log threshold at runtime (e.g. from a support toggle). The
|
|
935
|
+
* global `window.UWC_DEBUG` override still takes precedence for ad-hoc triage.
|
|
936
|
+
*/
|
|
937
|
+
setLogLevel(level: LogLevel): void {
|
|
938
|
+
this.logger.setLevel(level)
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Release observer subscriptions and clear all event listeners. Call on full
|
|
943
|
+
* teardown / logout to avoid leaking listeners across connector lifecycles.
|
|
944
|
+
*/
|
|
945
|
+
dispose(): void {
|
|
946
|
+
this.observerTeardown?.()
|
|
947
|
+
this.observerTeardown = undefined
|
|
948
|
+
this.eventManager.clearAllListeners()
|
|
949
|
+
// Full teardown: if this was the shared singleton, clear the static refs too
|
|
950
|
+
// so a later getInstance()/new starts clean — otherwise getInstance() would
|
|
951
|
+
// hand back this now-listener-less instance, and the instance-count warning
|
|
952
|
+
// would mis-fire on the next construction.
|
|
953
|
+
if (UniversalWalletConnector.instance === this) {
|
|
954
|
+
UniversalWalletConnector.resetInstance()
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
|
|
812
958
|
// ---- private helpers ----
|
|
813
959
|
|
|
960
|
+
/** Instrument a wallet-boundary op (emits awaitingWallet + a terminal). */
|
|
961
|
+
private instrument<T>(
|
|
962
|
+
context: HandoffContext,
|
|
963
|
+
fn: () => Promise<T>
|
|
964
|
+
): Promise<T> {
|
|
965
|
+
return instrumentHandoff(
|
|
966
|
+
{
|
|
967
|
+
eventManager: this.eventManager,
|
|
968
|
+
timeoutMs: this.walletResponseTimeoutMs
|
|
969
|
+
},
|
|
970
|
+
context,
|
|
971
|
+
fn
|
|
972
|
+
)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/** Build handoff routing facets from the active session (no address). */
|
|
976
|
+
private sessionHandoffContext(
|
|
977
|
+
operation: UWCOperation,
|
|
978
|
+
session: Session
|
|
979
|
+
): HandoffContext {
|
|
980
|
+
return {
|
|
981
|
+
operation,
|
|
982
|
+
connectionMode: session.connectionMode ?? 'injected',
|
|
983
|
+
walletId: session.activeWallet?.id,
|
|
984
|
+
namespace: session.activeNetwork?.namespace,
|
|
985
|
+
chainId: session.activeNetwork?.id ?? undefined
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
814
989
|
private emitError(error: unknown, operation: UWCOperation): void {
|
|
815
990
|
// Intentional cancellations (AbortSignal.throwIfAborted, fetch abort,
|
|
816
991
|
// DOMException) should NOT surface as wallet errors — the caller already
|
|
817
|
-
// knows it asked to abort.
|
|
818
|
-
|
|
992
|
+
// knows it asked to abort. Uses the same name-based check as
|
|
993
|
+
// `instrumentHandoff` so a DOMException abort (not an `Error` subclass in
|
|
994
|
+
// every runtime) is skipped consistently on both paths.
|
|
995
|
+
if (isAbortError(error)) {
|
|
819
996
|
return
|
|
820
997
|
}
|
|
821
|
-
|
|
822
|
-
error
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
type: 'unknown',
|
|
826
|
-
message: error instanceof Error ? error.message : String(error)
|
|
827
|
-
}
|
|
828
|
-
this.eventManager.emit('error', { error: walletError, operation })
|
|
998
|
+
this.eventManager.emit('error', {
|
|
999
|
+
error: toWalletError(error),
|
|
1000
|
+
operation
|
|
1001
|
+
})
|
|
829
1002
|
}
|
|
830
1003
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest'
|
|
2
|
+
import { generateId } from './id'
|
|
3
|
+
|
|
4
|
+
describe('generateId', () => {
|
|
5
|
+
it('produces unique values', () => {
|
|
6
|
+
const ids = new Set(Array.from({ length: 1000 }, () => generateId()))
|
|
7
|
+
expect(ids.size).toBe(1000)
|
|
8
|
+
})
|
|
9
|
+
|
|
10
|
+
it('produces a non-empty string', () => {
|
|
11
|
+
const id = generateId()
|
|
12
|
+
expect(typeof id).toBe('string')
|
|
13
|
+
expect(id.length).toBeGreaterThan(0)
|
|
14
|
+
})
|
|
15
|
+
})
|
package/src/utils/id.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generate a correlation id (RFC-4122 v4 when available).
|
|
3
|
+
*
|
|
4
|
+
* Used for the per-instance `sdkSessionId` and per-handoff `handoffId` — both are
|
|
5
|
+
* opaque correlation tokens, never derived from wallet data (no address, no key).
|
|
6
|
+
* Prefers the native `crypto.randomUUID()` (browsers + Node ≥ 16); falls back to a
|
|
7
|
+
* `crypto.getRandomValues`-backed v4, and finally to a timestamp + monotonic
|
|
8
|
+
* counter only in environments that expose neither. Correlation ids need to be
|
|
9
|
+
* collision-resistant enough to pair a start event with its terminal — NOT
|
|
10
|
+
* cryptographically unpredictable — so the last-resort branch uses no PRNG.
|
|
11
|
+
*/
|
|
12
|
+
// Monotonic counter for the no-crypto fallback: guarantees per-process uniqueness
|
|
13
|
+
// without any pseudo-random generator (correlation ids need uniqueness, not
|
|
14
|
+
// unpredictability — and a counter is strictly more collision-resistant here).
|
|
15
|
+
let fallbackCounter = 0
|
|
16
|
+
|
|
17
|
+
export function generateId(): string {
|
|
18
|
+
const c = typeof globalThis !== 'undefined' ? globalThis.crypto : undefined
|
|
19
|
+
|
|
20
|
+
if (c?.randomUUID) {
|
|
21
|
+
return c.randomUUID()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (c?.getRandomValues) {
|
|
25
|
+
const bytes = c.getRandomValues(new Uint8Array(16))
|
|
26
|
+
// Set the version (4) and variant (10xx) bits per RFC 4122 §4.4.
|
|
27
|
+
bytes[6] = (bytes[6]! & 0x0f) | 0x40
|
|
28
|
+
bytes[8] = (bytes[8]! & 0x3f) | 0x80
|
|
29
|
+
const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0'))
|
|
30
|
+
return (
|
|
31
|
+
hex.slice(0, 4).join('') +
|
|
32
|
+
'-' +
|
|
33
|
+
hex.slice(4, 6).join('') +
|
|
34
|
+
'-' +
|
|
35
|
+
hex.slice(6, 8).join('') +
|
|
36
|
+
'-' +
|
|
37
|
+
hex.slice(8, 10).join('') +
|
|
38
|
+
'-' +
|
|
39
|
+
hex.slice(10, 16).join('')
|
|
40
|
+
)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Last-resort fallback (no Web Crypto). Timestamp + monotonic counter keeps
|
|
44
|
+
// ids unique within the process without a PRNG. Sufficient for correlation.
|
|
45
|
+
return (
|
|
46
|
+
'uwc-' + Date.now().toString(36) + '-' + (fallbackCounter++).toString(36)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { WalletConnectorError, type WalletError } from '@meshconnect/uwc-types'
|
|
2
|
+
|
|
3
|
+
/** Normalize any thrown value into the PII-light `WalletError` shape. */
|
|
4
|
+
export function toWalletError(error: unknown): WalletError {
|
|
5
|
+
if (error instanceof WalletConnectorError) {
|
|
6
|
+
return { type: error.type, message: error.message }
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
type: 'unknown',
|
|
10
|
+
message: error instanceof Error ? error.message : String(error)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Whether a thrown value is an explicit user rejection. Connectors usually wrap
|
|
16
|
+
* these as `WalletConnectorError{type:'rejected'}`, but a raw EIP-1193 reject
|
|
17
|
+
* (code `4001`) or ethers' `ACTION_REJECTED` can also reach the façade.
|
|
18
|
+
*/
|
|
19
|
+
export function isUserRejection(error: unknown): boolean {
|
|
20
|
+
if (error instanceof WalletConnectorError) return error.type === 'rejected'
|
|
21
|
+
const code = (error as { code?: unknown } | null | undefined)?.code
|
|
22
|
+
return code === 4001 || code === 'ACTION_REJECTED'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Whether a thrown value is an intentional cancellation (AbortSignal / fetch
|
|
27
|
+
* abort). Matched by `name` rather than `instanceof Error` because a real abort is
|
|
28
|
+
* a `DOMException`, which is not an `Error` subclass in every runtime.
|
|
29
|
+
*/
|
|
30
|
+
export function isAbortError(error: unknown): boolean {
|
|
31
|
+
return (
|
|
32
|
+
typeof error === 'object' &&
|
|
33
|
+
error !== null &&
|
|
34
|
+
(error as { name?: unknown }).name === 'AbortError'
|
|
35
|
+
)
|
|
36
|
+
}
|