@meshconnect/uwc-core 1.0.3 → 1.1.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 +247 -76
- package/dist/events.d.ts +241 -5
- package/dist/events.d.ts.map +1 -1
- package/dist/events.js +77 -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 +2 -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 +79 -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 +33 -0
- package/dist/observability/connect-observer.d.ts.map +1 -0
- package/dist/observability/connect-observer.js +115 -0
- package/dist/observability/connect-observer.js.map +1 -0
- package/dist/observability/focus-tracker.d.ts +42 -0
- package/dist/observability/focus-tracker.d.ts.map +1 -0
- package/dist/observability/focus-tracker.js +57 -0
- package/dist/observability/focus-tracker.js.map +1 -0
- package/dist/observability/instrument-handoff.d.ts +54 -0
- package/dist/observability/instrument-handoff.d.ts.map +1 -0
- package/dist/observability/instrument-handoff.js +173 -0
- package/dist/observability/instrument-handoff.js.map +1 -0
- package/dist/observability/lifecycle-bridge.d.ts +69 -0
- package/dist/observability/lifecycle-bridge.d.ts.map +1 -0
- package/dist/observability/lifecycle-bridge.js +155 -0
- package/dist/observability/lifecycle-bridge.js.map +1 -0
- package/dist/observability/scrub.d.ts +31 -0
- package/dist/observability/scrub.d.ts.map +1 -0
- package/dist/observability/scrub.js +167 -0
- package/dist/observability/scrub.js.map +1 -0
- package/dist/observability/telemetry.d.ts +157 -0
- package/dist/observability/telemetry.d.ts.map +1 -0
- package/dist/observability/telemetry.js +373 -0
- package/dist/observability/telemetry.js.map +1 -0
- package/dist/services/connection-service.d.ts +23 -1
- package/dist/services/connection-service.d.ts.map +1 -1
- package/dist/services/connection-service.js +128 -19
- 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 +45 -16
- package/dist/services/network-switch-service.js.map +1 -1
- package/dist/services/signature-service.d.ts +14 -7
- package/dist/services/signature-service.d.ts.map +1 -1
- package/dist/services/signature-service.js +56 -58
- package/dist/services/signature-service.js.map +1 -1
- package/dist/services/transaction-service.d.ts +19 -5
- package/dist/services/transaction-service.d.ts.map +1 -1
- package/dist/services/transaction-service.js +55 -38
- package/dist/services/transaction-service.js.map +1 -1
- package/dist/universal-wallet-connector.d.ts +56 -1
- package/dist/universal-wallet-connector.d.ts.map +1 -1
- package/dist/universal-wallet-connector.js +207 -110
- 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/resolve-session-provider.d.ts +10 -0
- package/dist/utils/resolve-session-provider.d.ts.map +1 -0
- package/dist/utils/resolve-session-provider.js +21 -0
- package/dist/utils/resolve-session-provider.js.map +1 -0
- package/dist/utils/to-wallet-error.d.ts +35 -0
- package/dist/utils/to-wallet-error.d.ts.map +1 -0
- package/dist/utils/to-wallet-error.js +130 -0
- package/dist/utils/to-wallet-error.js.map +1 -0
- package/dist/utils/validate-wallet-session.d.ts +15 -0
- package/dist/utils/validate-wallet-session.d.ts.map +1 -0
- package/dist/utils/validate-wallet-session.js +29 -0
- package/dist/utils/validate-wallet-session.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/package.json +6 -6
- package/src/events.ts +326 -14
- package/src/index.ts +10 -0
- package/src/logger/create-logger.test.ts +126 -0
- package/src/logger/create-logger.ts +112 -0
- package/src/managers/event-manager.test.ts +55 -0
- package/src/managers/event-manager.ts +5 -1
- package/src/observability/connect-observer.test.ts +214 -0
- package/src/observability/connect-observer.ts +128 -0
- package/src/observability/focus-tracker.test.ts +191 -0
- package/src/observability/focus-tracker.ts +96 -0
- package/src/observability/instrument-handoff.test.ts +397 -0
- package/src/observability/instrument-handoff.ts +231 -0
- package/src/observability/lifecycle-bridge.test.ts +398 -0
- package/src/observability/lifecycle-bridge.ts +216 -0
- package/src/observability/scrub.test.ts +183 -0
- package/src/observability/scrub.ts +174 -0
- package/src/observability/telemetry.test.ts +549 -0
- package/src/observability/telemetry.ts +546 -0
- package/src/services/connection-service.test.ts +261 -1
- package/src/services/connection-service.ts +167 -38
- package/src/services/network-switch-service.test.ts +51 -1
- package/src/services/network-switch-service.ts +56 -24
- package/src/services/signature-service.test.ts +181 -76
- package/src/services/signature-service.ts +75 -85
- package/src/services/transaction-service.test.ts +270 -69
- package/src/services/transaction-service.ts +74 -51
- package/src/universal-wallet-connector.instrumentation-guard.test.ts +199 -0
- package/src/universal-wallet-connector.observability.test.ts +610 -0
- package/src/universal-wallet-connector.test.ts +27 -146
- package/src/universal-wallet-connector.ts +279 -142
- package/src/utils/id.test.ts +15 -0
- package/src/utils/id.ts +48 -0
- package/src/utils/resolve-session-provider.test.ts +54 -0
- package/src/utils/resolve-session-provider.ts +25 -0
- package/src/utils/to-wallet-error.test.ts +181 -0
- package/src/utils/to-wallet-error.ts +152 -0
- package/src/utils/validate-wallet-session.test.ts +77 -0
- package/src/utils/validate-wallet-session.ts +40 -0
- package/src/version.test.ts +11 -0
- package/src/version.ts +2 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hard ceiling on lifecycle emissions per attachment. Every other telemetry
|
|
3
|
+
* sink this layer added is bounded (relay / bridge / observer-error dedup sets
|
|
4
|
+
* cap at 100); a provider looping `chainChanged`/`accountsChanged` must not be
|
|
5
|
+
* the one unbounded path to the observer. A legitimate session stays far below
|
|
6
|
+
* this; a storming provider goes silent instead of driving egress cost.
|
|
7
|
+
*/
|
|
8
|
+
export const MAX_LIFECYCLE_EVENTS_PER_ATTACHMENT = 100;
|
|
9
|
+
/**
|
|
10
|
+
* Bridge a connector's PII-safe `WalletLifecycleEvent` stream onto the event bus
|
|
11
|
+
* as `walletAccountChanged` / `walletChainChanged` / `walletDisconnected` /
|
|
12
|
+
* `walletSessionExpired`.
|
|
13
|
+
*
|
|
14
|
+
* Two correctness concerns are handled here:
|
|
15
|
+
* - **Echo suppression.** When UWC drives a `switchNetwork`, the wallet emits its
|
|
16
|
+
* own `chainChanged` for the same chain — that's already captured by UWC's
|
|
17
|
+
* `networkSwitched` / the switch handoff, so re-emitting it as a wallet-
|
|
18
|
+
* initiated `walletChainChanged` would double-count. We watch the bus for
|
|
19
|
+
* `networkSwitched` and drop a matching `chainChanged` inside `echoWindowMs`.
|
|
20
|
+
* - **Availability is signal.** If the connector can't observe lifecycle, we emit
|
|
21
|
+
* `lifecycleTelemetryUnavailable` ONCE so a dashboard can tell "silent" from
|
|
22
|
+
* "unwired".
|
|
23
|
+
*
|
|
24
|
+
* @returns a teardown that removes the connector subscription AND the bus watcher.
|
|
25
|
+
*/
|
|
26
|
+
export function attachLifecycle(opts) {
|
|
27
|
+
const { eventManager, subscribe, getRouting } = opts;
|
|
28
|
+
const runtime = opts.runtime ?? 'direct';
|
|
29
|
+
const now = opts.now ?? Date.now;
|
|
30
|
+
const echoWindowMs = opts.echoWindowMs ?? 3000;
|
|
31
|
+
const emitUnavailable = (reason) => eventManager.emit('lifecycleTelemetryUnavailable', {
|
|
32
|
+
reason,
|
|
33
|
+
connectionMode: opts.connectionMode,
|
|
34
|
+
namespace: opts.namespace,
|
|
35
|
+
walletId: opts.walletId,
|
|
36
|
+
runtime
|
|
37
|
+
});
|
|
38
|
+
if (!subscribe) {
|
|
39
|
+
// In the production iframe the injected subscription is deliberately not
|
|
40
|
+
// attempted (the provider is a Comlink proxy) — the BRIDGE, not the connector,
|
|
41
|
+
// is why lifecycle is unobservable. On the direct page, a connector with no
|
|
42
|
+
// `onLifecycleEvent` simply has no lifecycle support for this namespace yet.
|
|
43
|
+
emitUnavailable(runtime === 'iframe'
|
|
44
|
+
? 'bridge_subscription_unavailable'
|
|
45
|
+
: 'unsupported_namespace');
|
|
46
|
+
return () => { };
|
|
47
|
+
}
|
|
48
|
+
// Record UWC-initiated switches so the wallet's chainChanged echo can be
|
|
49
|
+
// dropped. Armed from BOTH signals: `awaitingWallet` (operation switchNetwork)
|
|
50
|
+
// arms at the handoff START, because some providers emit their chainChanged
|
|
51
|
+
// BEFORE the switch request resolves — i.e. before `networkSwitched` exists —
|
|
52
|
+
// and `networkSwitched` re-arms at completion for the late-echo case. The
|
|
53
|
+
// re-arm means one extra suppression window can follow an early echo; the
|
|
54
|
+
// only event it could swallow is a sub-window user re-switch to the chain
|
|
55
|
+
// that just became active — accepted over double-counting every early echo.
|
|
56
|
+
let lastSwitch = opts.initialSwitch;
|
|
57
|
+
const offAwaiting = eventManager.on('awaitingWallet', data => {
|
|
58
|
+
if (data.operation === 'switchNetwork' && data.chainId !== undefined) {
|
|
59
|
+
lastSwitch = { chainId: data.chainId, at: now() };
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const offSwitched = eventManager.on('networkSwitched', data => {
|
|
63
|
+
lastSwitch = { chainId: data.network.id, at: now() };
|
|
64
|
+
});
|
|
65
|
+
const detachBusWatchers = () => {
|
|
66
|
+
offAwaiting();
|
|
67
|
+
offSwitched();
|
|
68
|
+
};
|
|
69
|
+
// Defense-in-depth: a connector whose subscribe THROWS (e.g. a provider whose
|
|
70
|
+
// `.on` rejects an un-proxied handler across the iframe bridge) must degrade to
|
|
71
|
+
// "unavailable", never break the connect path that called us.
|
|
72
|
+
let unsub;
|
|
73
|
+
// Emission budget for THIS attachment — suppressed echoes don't spend it.
|
|
74
|
+
let emitted = 0;
|
|
75
|
+
let capNotified = false;
|
|
76
|
+
try {
|
|
77
|
+
unsub = subscribe(e => {
|
|
78
|
+
// The whole body is guarded: wallet SDKs dispatch their listeners
|
|
79
|
+
// unguarded (e.g. TonConnect's bare `forEach`), so a throw here — from
|
|
80
|
+
// `getRouting()` or any future edit — would abort delivery to the
|
|
81
|
+
// wallet's OWN later subscribers, including one resolving a pending
|
|
82
|
+
// connect. Telemetry must never do that.
|
|
83
|
+
try {
|
|
84
|
+
if (emitted >= MAX_LIFECYCLE_EVENTS_PER_ATTACHMENT) {
|
|
85
|
+
// Mark the trip ONCE — a silent cap would be indistinguishable from a
|
|
86
|
+
// provider that simply went quiet.
|
|
87
|
+
if (!capNotified) {
|
|
88
|
+
capNotified = true;
|
|
89
|
+
emitUnavailable('emission_cap');
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
const routing = getRouting();
|
|
94
|
+
switch (e.type) {
|
|
95
|
+
case 'accountChanged':
|
|
96
|
+
emitted++;
|
|
97
|
+
eventManager.emit('walletAccountChanged', routing);
|
|
98
|
+
return;
|
|
99
|
+
case 'chainChanged': {
|
|
100
|
+
const isEcho = !!lastSwitch &&
|
|
101
|
+
e.chainId !== undefined &&
|
|
102
|
+
lastSwitch.chainId === e.chainId &&
|
|
103
|
+
now() - lastSwitch.at <= echoWindowMs;
|
|
104
|
+
if (isEcho) {
|
|
105
|
+
// Consume the echo: a UWC switch produces exactly one wallet echo, so
|
|
106
|
+
// a LATER chainChanged to the same chain inside the window is a genuine
|
|
107
|
+
// user-initiated switch-back and must not be suppressed.
|
|
108
|
+
lastSwitch = undefined;
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
emitted++;
|
|
112
|
+
eventManager.emit('walletChainChanged', {
|
|
113
|
+
...routing,
|
|
114
|
+
...(e.chainId !== undefined ? { chainId: e.chainId } : {})
|
|
115
|
+
});
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
case 'disconnect':
|
|
119
|
+
emitted++;
|
|
120
|
+
eventManager.emit('walletDisconnected', routing);
|
|
121
|
+
return;
|
|
122
|
+
case 'sessionExpired':
|
|
123
|
+
emitted++;
|
|
124
|
+
eventManager.emit('walletSessionExpired', routing);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Swallowed: better to drop one lifecycle record than to break the
|
|
130
|
+
// wallet's event dispatch.
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
unsub = undefined;
|
|
136
|
+
}
|
|
137
|
+
// The connector accepted the subscribe call but this session has no observable
|
|
138
|
+
// surface (returned undefined) or threw — treat as "unavailable" and don't
|
|
139
|
+
// leave the bus watchers attached.
|
|
140
|
+
if (!unsub) {
|
|
141
|
+
detachBusWatchers();
|
|
142
|
+
// `onLifecycleEvent` ran but exposed no live subscription surface for this
|
|
143
|
+
// session (returned undefined or threw). Over the bridge, that absence is the
|
|
144
|
+
// bridge's doing, not the provider's.
|
|
145
|
+
emitUnavailable(runtime === 'iframe'
|
|
146
|
+
? 'bridge_subscription_unavailable'
|
|
147
|
+
: 'provider_subscription_unavailable');
|
|
148
|
+
return () => { };
|
|
149
|
+
}
|
|
150
|
+
return () => {
|
|
151
|
+
detachBusWatchers();
|
|
152
|
+
unsub();
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
//# sourceMappingURL=lifecycle-bridge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-bridge.js","sourceRoot":"","sources":["../../src/observability/lifecycle-bridge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PII / secret scrubber for data leaving UWC through the observer seam.
|
|
3
|
+
*
|
|
4
|
+
* Security-first: the normalized `UWCTelemetryEvent` excludes signing payloads
|
|
5
|
+
* and addresses by construction — its STRUCTURED fields have no slot for them
|
|
6
|
+
* (§ telemetry.ts). This scrubber is the second line of defence for the two
|
|
7
|
+
* surfaces that CAN still carry free-form data on the observer path: the
|
|
8
|
+
* `WalletError.message` forwarded to `observer.onEvent`, and the `args` forwarded
|
|
9
|
+
* to `observer.onLog`. (The console / custom-logger sink receives raw data — it's
|
|
10
|
+
* local, not egress.)
|
|
11
|
+
*
|
|
12
|
+
* Two detection layers:
|
|
13
|
+
* - KEY-based redaction of sensitive object keys — substring match (not regex),
|
|
14
|
+
* avoiding the ReDoS class that Wiz flags on user-input regexes.
|
|
15
|
+
* - CONTENT-based redaction of secrets embedded in free-form strings (hex blobs,
|
|
16
|
+
* secret k/v params, bearer tokens) — via linear regexes with no nested or
|
|
17
|
+
* overlapping quantifiers (see SECRET_CONTENT_PATTERNS), so they carry no
|
|
18
|
+
* catastrophic-backtracking risk.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Recursively copy `value`, redacting sensitive object-keys, capping long
|
|
22
|
+
* strings, and rendering non-serializable values inert. The returned objects use
|
|
23
|
+
* a null prototype so a malicious `__proto__`/`constructor` key in attacker-shaped
|
|
24
|
+
* input can't pollute `Object.prototype` downstream.
|
|
25
|
+
*/
|
|
26
|
+
export declare function scrubValue(value: unknown, depth?: number): unknown;
|
|
27
|
+
/** Scrub a free-form error message (string in → capped string out). */
|
|
28
|
+
export declare function scrubMessage(message: string): string;
|
|
29
|
+
/** Scrub a variadic `args` array before it reaches `observer.onLog`. */
|
|
30
|
+
export declare function scrubArgs(args: unknown[]): unknown[];
|
|
31
|
+
//# sourceMappingURL=scrub.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub.d.ts","sourceRoot":"","sources":["../../src/observability/scrub.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAmGH;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,SAAI,GAAG,OAAO,CAwC7D;AAED,uEAAuE;AACvE,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED,wEAAwE;AACxE,wBAAgB,SAAS,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAEpD"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PII / secret scrubber for data leaving UWC through the observer seam.
|
|
3
|
+
*
|
|
4
|
+
* Security-first: the normalized `UWCTelemetryEvent` excludes signing payloads
|
|
5
|
+
* and addresses by construction — its STRUCTURED fields have no slot for them
|
|
6
|
+
* (§ telemetry.ts). This scrubber is the second line of defence for the two
|
|
7
|
+
* surfaces that CAN still carry free-form data on the observer path: the
|
|
8
|
+
* `WalletError.message` forwarded to `observer.onEvent`, and the `args` forwarded
|
|
9
|
+
* to `observer.onLog`. (The console / custom-logger sink receives raw data — it's
|
|
10
|
+
* local, not egress.)
|
|
11
|
+
*
|
|
12
|
+
* Two detection layers:
|
|
13
|
+
* - KEY-based redaction of sensitive object keys — substring match (not regex),
|
|
14
|
+
* avoiding the ReDoS class that Wiz flags on user-input regexes.
|
|
15
|
+
* - CONTENT-based redaction of secrets embedded in free-form strings (hex blobs,
|
|
16
|
+
* secret k/v params, bearer tokens) — via linear regexes with no nested or
|
|
17
|
+
* overlapping quantifiers (see SECRET_CONTENT_PATTERNS), so they carry no
|
|
18
|
+
* catastrophic-backtracking risk.
|
|
19
|
+
*/
|
|
20
|
+
/**
|
|
21
|
+
* Separator-free, lower-cased fragments that mark a value as sensitive. Keys are
|
|
22
|
+
* normalized (separators stripped) before matching, so `walletPrivateKey`,
|
|
23
|
+
* `private_key`, `x-api-key`, and `authToken` all hit the same short list. This
|
|
24
|
+
* over-redacts by design (a `security`-first scrubber prefers a false redaction
|
|
25
|
+
* to a leak).
|
|
26
|
+
*/
|
|
27
|
+
const SENSITIVE_KEY_FRAGMENTS = [
|
|
28
|
+
'seed',
|
|
29
|
+
'mnemonic',
|
|
30
|
+
'privatekey',
|
|
31
|
+
'secret',
|
|
32
|
+
'password',
|
|
33
|
+
'passphrase',
|
|
34
|
+
'signature',
|
|
35
|
+
'message',
|
|
36
|
+
'payload',
|
|
37
|
+
'token',
|
|
38
|
+
'apikey',
|
|
39
|
+
'authorization',
|
|
40
|
+
'auth'
|
|
41
|
+
];
|
|
42
|
+
const MAX_STRING_LENGTH = 500;
|
|
43
|
+
const MAX_DEPTH = 4;
|
|
44
|
+
const REDACTED = '[Redacted]';
|
|
45
|
+
function isSensitiveKey(key) {
|
|
46
|
+
// Strip separators so `private_key` / `x-api-key` normalize to the bare form.
|
|
47
|
+
const normalized = key.toLowerCase().split('-').join('').split('_').join('');
|
|
48
|
+
return SENSITIVE_KEY_FRAGMENTS.some(fragment => normalized.includes(fragment));
|
|
49
|
+
}
|
|
50
|
+
// Content patterns for secrets embedded in FREE-FORM strings (error messages,
|
|
51
|
+
// log args) where key-based redaction can't reach. All are linear expressions
|
|
52
|
+
// with no nested/overlapping quantifiers, so they carry no catastrophic-
|
|
53
|
+
// backtracking (ReDoS) risk on attacker-controlled input.
|
|
54
|
+
const SECRET_CONTENT_PATTERNS = [
|
|
55
|
+
// 0x-prefixed hex blobs: EVM addresses (40 nibbles) and longer
|
|
56
|
+
// signatures/keys/hashes/calldata (64+). Over-redacts public hashes by design.
|
|
57
|
+
[/0x[a-fA-F0-9]{40,}/g, '0x[redacted-hex]'],
|
|
58
|
+
// `secret=…` / `token=…` / `key=…` query-string or k/v fragments.
|
|
59
|
+
[
|
|
60
|
+
/\b(token|key|secret|auth|sig|signature|password|passphrase|mnemonic|seed|apikey)=[^\s&"']+/gi,
|
|
61
|
+
'$1=[redacted]'
|
|
62
|
+
],
|
|
63
|
+
// `Bearer <opaque>` / `Basic <opaque>` authorization values.
|
|
64
|
+
[/\b(bearer|basic)\s+[A-Za-z0-9._~+/-]+=*/gi, '$1 [redacted]'],
|
|
65
|
+
// TON user-friendly addresses: `EQ…` / `UQ…` + 46 base64url chars (48 total).
|
|
66
|
+
// Trailing edge is a negative lookahead, not `\b`: a base64url address can end
|
|
67
|
+
// in `-`/`_`, where `\b` fails to anchor and the address would slip through.
|
|
68
|
+
[/\b[EU]Q[A-Za-z0-9_-]{46}(?![A-Za-z0-9_-])/g, '[redacted-ton-address]'],
|
|
69
|
+
// TON raw-form addresses: `workchain:64-hex` (`0:…`, `-1:…`). Before base58 so
|
|
70
|
+
// the hex tail gets the specific TON marker, not a generic base58 hit.
|
|
71
|
+
// Anchor-free like base58 below: digits and `_` are word chars, so a `\b`
|
|
72
|
+
// never anchors when the address is glued to one (`account_0:…` leaked).
|
|
73
|
+
// The trailing edge is a hex negative lookahead — a LONGER contiguous hex
|
|
74
|
+
// run is a different blob, not a 64-hex address.
|
|
75
|
+
[/-?\d+:[0-9a-fA-F]{64}(?![0-9a-fA-F])/g, '[redacted-ton-address]'],
|
|
76
|
+
// Solana / generic base58, 32 chars or MORE — bare addresses AND longer blobs
|
|
77
|
+
// (a signature is ~88 chars, a serialized tx longer still). No upper bound: a
|
|
78
|
+
// capped quantifier can never match inside a longer contiguous run. No `\b`
|
|
79
|
+
// anchors either: `0`/`O`/`I`/`l`/`_` are word chars OUTSIDE the base58
|
|
80
|
+
// alphabet, so they break the class AND defeat a boundary anchor — an address
|
|
81
|
+
// glued to one (`account_<addr>`) would slip through anchored matching.
|
|
82
|
+
// Over-redaction inside longer tokens is accepted by design. Placed LAST: it
|
|
83
|
+
// is the broadest pattern and would otherwise swallow narrower matches above.
|
|
84
|
+
[/[1-9A-HJ-NP-Za-km-z]{32,}/g, '[redacted-base58]']
|
|
85
|
+
];
|
|
86
|
+
/**
|
|
87
|
+
* Redact secrets that live INSIDE a free-form string. Best-effort and
|
|
88
|
+
* deliberately conservative toward over-redaction: a wallet error string can
|
|
89
|
+
* embed an address, a reverted-tx calldata blob, or a URL with a token, none of
|
|
90
|
+
* which key-based redaction sees.
|
|
91
|
+
*
|
|
92
|
+
* Base58 (Solana) and TON addresses (user-friendly `EQ…`/`UQ…` AND the raw
|
|
93
|
+
* `workchain:hex` form) are redacted as a security gate: a leaked
|
|
94
|
+
* recipient/wallet address on the egress path is treated as a defect, so we
|
|
95
|
+
* accept that any base58-looking token of 32+ chars (some ids, tx hashes,
|
|
96
|
+
* IPFS CIDs) is over-redacted by design. The narrower TON patterns run before
|
|
97
|
+
* the broad base58 one so TON addresses get the specific marker.
|
|
98
|
+
*
|
|
99
|
+
* One documented gap remains: BARE (non-`0x`) hex blobs — a raw 64-char hex
|
|
100
|
+
* string is far more often a legitimate id/hash than a secret, so only
|
|
101
|
+
* `0x`-prefixed hex is redacted. Structured telemetry fields never carry
|
|
102
|
+
* addresses regardless (see telemetry.ts); this pass only hardens the free-form
|
|
103
|
+
* `error.message` / log-arg surface.
|
|
104
|
+
*/
|
|
105
|
+
function redactSecretsInString(input) {
|
|
106
|
+
let out = input;
|
|
107
|
+
for (const [pattern, replacement] of SECRET_CONTENT_PATTERNS) {
|
|
108
|
+
out = out.replace(pattern, replacement);
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Recursively copy `value`, redacting sensitive object-keys, capping long
|
|
114
|
+
* strings, and rendering non-serializable values inert. The returned objects use
|
|
115
|
+
* a null prototype so a malicious `__proto__`/`constructor` key in attacker-shaped
|
|
116
|
+
* input can't pollute `Object.prototype` downstream.
|
|
117
|
+
*/
|
|
118
|
+
export function scrubValue(value, depth = 0) {
|
|
119
|
+
if (depth > MAX_DEPTH)
|
|
120
|
+
return '[Truncated: max depth]';
|
|
121
|
+
if (value === null || value === undefined)
|
|
122
|
+
return value;
|
|
123
|
+
const type = typeof value;
|
|
124
|
+
if (type === 'string') {
|
|
125
|
+
// Redact embedded secrets BEFORE capping, so the cap can't slice a pattern
|
|
126
|
+
// in half and leak its tail.
|
|
127
|
+
const str = redactSecretsInString(value);
|
|
128
|
+
return str.length > MAX_STRING_LENGTH
|
|
129
|
+
? str.slice(0, MAX_STRING_LENGTH) + '…[truncated]'
|
|
130
|
+
: str;
|
|
131
|
+
}
|
|
132
|
+
if (type === 'number' || type === 'boolean')
|
|
133
|
+
return value;
|
|
134
|
+
if (type === 'bigint')
|
|
135
|
+
return value.toString();
|
|
136
|
+
if (type === 'function' || type === 'symbol')
|
|
137
|
+
return `[${type}]`;
|
|
138
|
+
if (Array.isArray(value)) {
|
|
139
|
+
return value.map(item => scrubValue(item, depth + 1));
|
|
140
|
+
}
|
|
141
|
+
if (type === 'object') {
|
|
142
|
+
// Error instances carry a useful message but also a stack we don't want.
|
|
143
|
+
// Null prototype here too — the doc above promises it for every returned
|
|
144
|
+
// object, and an Error-shaped attacker value lands in this branch.
|
|
145
|
+
if (value instanceof Error) {
|
|
146
|
+
const out = Object.create(null);
|
|
147
|
+
out['name'] = value.name;
|
|
148
|
+
out['message'] = scrubValue(value.message, depth + 1);
|
|
149
|
+
return out;
|
|
150
|
+
}
|
|
151
|
+
const out = Object.create(null);
|
|
152
|
+
for (const [key, val] of Object.entries(value)) {
|
|
153
|
+
out[key] = isSensitiveKey(key) ? REDACTED : scrubValue(val, depth + 1);
|
|
154
|
+
}
|
|
155
|
+
return out;
|
|
156
|
+
}
|
|
157
|
+
return '[Unserializable]';
|
|
158
|
+
}
|
|
159
|
+
/** Scrub a free-form error message (string in → capped string out). */
|
|
160
|
+
export function scrubMessage(message) {
|
|
161
|
+
return scrubValue(message);
|
|
162
|
+
}
|
|
163
|
+
/** Scrub a variadic `args` array before it reaches `observer.onLog`. */
|
|
164
|
+
export function scrubArgs(args) {
|
|
165
|
+
return args.map(arg => scrubValue(arg));
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=scrub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scrub.js","sourceRoot":"","sources":["../../src/observability/scrub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import type { ConnectionMode, WalletError, WalletFlowType, Platform, LogLevel, Namespace, NetworkId, FailureKind, ErrorSource, LifecycleUnavailableReason } from '@meshconnect/uwc-types';
|
|
2
|
+
import type { UWCEventName, UWCEventMap, UWCOperation } from '../events';
|
|
3
|
+
/**
|
|
4
|
+
* Telemetry record schema version. Bump on any breaking change to the
|
|
5
|
+
* `UWCTelemetryEvent` shape so a downstream consumer can branch on it.
|
|
6
|
+
*/
|
|
7
|
+
export declare const TELEMETRY_SCHEMA_VERSION = 1;
|
|
8
|
+
/**
|
|
9
|
+
* A normalized, PII-safe telemetry record derived from a `UWCEventMap` event.
|
|
10
|
+
*
|
|
11
|
+
* This is the ONLY shape that reaches `observer.onEvent`. It is the PII boundary:
|
|
12
|
+
* it has no field for a signing payload, signature, raw transaction, or wallet
|
|
13
|
+
* address — leaking those is structurally impossible, not merely discouraged.
|
|
14
|
+
*/
|
|
15
|
+
export interface UWCTelemetryEvent {
|
|
16
|
+
/**
|
|
17
|
+
* Record schema version — the literal type IS the version this package
|
|
18
|
+
* emits, so a consumer can branch exhaustively (`event.schemaVersion === 1`)
|
|
19
|
+
* and gets a compile break when a bump lands. Import the runtime value as
|
|
20
|
+
* `TELEMETRY_SCHEMA_VERSION`.
|
|
21
|
+
*/
|
|
22
|
+
schemaVersion: typeof TELEMETRY_SCHEMA_VERSION;
|
|
23
|
+
/**
|
|
24
|
+
* The `@meshconnect/uwc-core` version that produced the record (generated
|
|
25
|
+
* from package.json at build time) — aggregate failures per released
|
|
26
|
+
* version to spot version-specific regressions.
|
|
27
|
+
*/
|
|
28
|
+
uwcVersion: string;
|
|
29
|
+
/** The originating event name. */
|
|
30
|
+
name: UWCEventName;
|
|
31
|
+
/** Per-connector-instance id (stamped on every event). */
|
|
32
|
+
sdkSessionId: string;
|
|
33
|
+
/**
|
|
34
|
+
* Consumer-supplied correlation id (e.g. their `link_sess_id`), present only
|
|
35
|
+
* when supplied — never aliased to `sdkSessionId`. Lets UWC events join the
|
|
36
|
+
* consumer's existing telemetry without a separate mapping.
|
|
37
|
+
*/
|
|
38
|
+
correlationId?: string | undefined;
|
|
39
|
+
/** ms since epoch when the record was produced. */
|
|
40
|
+
timestamp: number;
|
|
41
|
+
/** Wallet-boundary op, when the event relates to one. */
|
|
42
|
+
operation?: UWCOperation | undefined;
|
|
43
|
+
connectionMode?: ConnectionMode | undefined;
|
|
44
|
+
/** CAIP namespace (eip155 | solana | tron | tvm …) — never an address. */
|
|
45
|
+
namespace?: Namespace | undefined;
|
|
46
|
+
/** CAIP chain id (e.g. `eip155:1`) — never an address. */
|
|
47
|
+
chainId?: NetworkId | undefined;
|
|
48
|
+
/** Catalog wallet id — never an address. */
|
|
49
|
+
walletId?: string | undefined;
|
|
50
|
+
/** Correlates a handoff start with its terminal. */
|
|
51
|
+
handoffId?: string | undefined;
|
|
52
|
+
durationMs?: number | undefined;
|
|
53
|
+
walletFlowType?: WalletFlowType | undefined;
|
|
54
|
+
platform?: Platform | undefined;
|
|
55
|
+
/** A `walletTimedOut` marker fired before the terminal (late completion). */
|
|
56
|
+
timedOut?: boolean | undefined;
|
|
57
|
+
/** Marker deadline — present on `walletTimedOut` and on a late terminal. */
|
|
58
|
+
timeoutMs?: number | undefined;
|
|
59
|
+
/** switchNetwork only: target chain was already the active one (no-op switch). */
|
|
60
|
+
targetAlreadyActive?: boolean | undefined;
|
|
61
|
+
/** Non-success outcomes: stringified error code when reliably known. */
|
|
62
|
+
errorCode?: string | undefined;
|
|
63
|
+
/** Non-success outcomes: coarse cause bucket. `unknown` when not classifiable. */
|
|
64
|
+
failureKind?: FailureKind | undefined;
|
|
65
|
+
/** Non-success outcomes: where the error originated, when inferable. */
|
|
66
|
+
source?: ErrorSource | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Connect `no_common_network` failures only: the wallet's advertised vs the
|
|
69
|
+
* configured CAIP-2 id lists (non-PII). Lets a dashboard show "wallet offered
|
|
70
|
+
* X, token configured Y" — the diagnosis for the #1 prod connection error.
|
|
71
|
+
*/
|
|
72
|
+
networkMismatch?: {
|
|
73
|
+
walletNetworkIds: string[];
|
|
74
|
+
configuredNetworkIds: string[];
|
|
75
|
+
} | undefined;
|
|
76
|
+
/** Present on failure events. `message` is scrubbed before egress. */
|
|
77
|
+
error?: WalletError | undefined;
|
|
78
|
+
/** Log level, present only for `name === 'log'`. */
|
|
79
|
+
level?: LogLevel | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* Per-chain supported capability names (e.g. `{ 'eip155:8453': ['atomic'] }`),
|
|
82
|
+
* present only for `capabilitiesUpdated`. Feature flags + chain ids — not PII.
|
|
83
|
+
*/
|
|
84
|
+
capabilities?: Record<string, string[]> | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* Per-namespace requested-vs-matched discovery summary, present only on
|
|
87
|
+
* `walletsDetected`. Counts only — never a wallet id or address.
|
|
88
|
+
*/
|
|
89
|
+
discovery?: Record<string, {
|
|
90
|
+
requested: number;
|
|
91
|
+
matched: number;
|
|
92
|
+
}> | undefined;
|
|
93
|
+
/**
|
|
94
|
+
* Per-namespace count of the session's available addresses, present on
|
|
95
|
+
* `connected` / `sessionChanged`. Counts only — never an address.
|
|
96
|
+
*/
|
|
97
|
+
addressNamespaces?: Record<string, number> | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Namespaces the connected wallet supports for this session's configured
|
|
100
|
+
* networks but returned NO address for — present on `connected` /
|
|
101
|
+
* `sessionChanged` only when non-empty. A RAW FACT (supported-but-absent), not
|
|
102
|
+
* an asserted bug: it's the observable symptom of a silently dropped
|
|
103
|
+
* secondary-namespace gather — e.g. injected-connector's framed-embed skip,
|
|
104
|
+
* where the wallet supports Solana but the session has zero Solana addresses.
|
|
105
|
+
* The consumer interprets it (link-v2 deposit ⇒ lost holdings; a single-
|
|
106
|
+
* namespace deposit ⇒ intended). Pairs with `discovery` for the full picture.
|
|
107
|
+
*/
|
|
108
|
+
missingNamespaces?: string[] | undefined;
|
|
109
|
+
/** `lifecycleTelemetryUnavailable` only: why lifecycle telemetry couldn't attach. */
|
|
110
|
+
reason?: LifecycleUnavailableReason | undefined;
|
|
111
|
+
/** `lifecycleTelemetryUnavailable` only: direct page vs production iframe. */
|
|
112
|
+
runtime?: 'direct' | 'iframe' | undefined;
|
|
113
|
+
/**
|
|
114
|
+
* `error` only: a handoff terminal already reported this failure — this
|
|
115
|
+
* record is the back-compat duplicate. Count terminals + `error` where NOT
|
|
116
|
+
* handoffCovered to avoid double-counting.
|
|
117
|
+
*/
|
|
118
|
+
handoffCovered?: boolean | undefined;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Vendor-agnostic telemetry sink. Consumers implement; UWC ships no vendor SDK.
|
|
122
|
+
* Both methods are optional and called best-effort (throws are swallowed upstream).
|
|
123
|
+
*
|
|
124
|
+
* CONTRACT — both methods are invoked **synchronously on the wallet-operation
|
|
125
|
+
* path**. A terminal event (e.g. `walletSucceeded`) fires between the wallet
|
|
126
|
+
* returning a result and that result reaching the caller, so a slow handler adds
|
|
127
|
+
* latency to connect / sign / sendTransaction. Keep handlers **non-blocking**:
|
|
128
|
+
* hand off to a queue-backed sink (Datadog `addAction`, Segment `track`, etc. are
|
|
129
|
+
* already non-blocking) or defer heavy work yourself — e.g.
|
|
130
|
+
* `onEvent: e => queueMicrotask(() => doHeavyWork(e))`. Do not perform synchronous
|
|
131
|
+
* network/IO or expensive serialization inline.
|
|
132
|
+
*/
|
|
133
|
+
export interface UWCObserver {
|
|
134
|
+
/** A normalized, PII-safe event. Forward to Datadog / Amplitude / Segment / … (non-blocking). */
|
|
135
|
+
onEvent?(event: UWCTelemetryEvent): void;
|
|
136
|
+
/** A log line (all levels). `args` is already scrubbed. Keep non-blocking. */
|
|
137
|
+
onLog?(level: LogLevel, message: string, args: unknown[]): void;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Best-effort coarse runtime surface. HEURISTIC — user agents lie and in-app
|
|
141
|
+
* browsers are hard to detect; treat `webview` as a hint, not a guarantee.
|
|
142
|
+
* Never returns the raw UA (PII / high-cardinality).
|
|
143
|
+
*/
|
|
144
|
+
export declare function derivePlatform(): Platform;
|
|
145
|
+
/**
|
|
146
|
+
* Derive the flow taxonomy from connection mode + surface. HEURISTIC — UWC does
|
|
147
|
+
* not always know whether a WalletConnect handoff used a deeplink vs a QR scan;
|
|
148
|
+
* we infer from the platform (mobile ⇒ deeplink, desktop ⇒ QR), matching link-v2's
|
|
149
|
+
* historical intent. Consumers with better context may override downstream.
|
|
150
|
+
*/
|
|
151
|
+
export declare function deriveWalletFlowType(connectionMode: ConnectionMode, platform: Platform): WalletFlowType;
|
|
152
|
+
/**
|
|
153
|
+
* Normalize a bus event into a PII-safe `UWCTelemetryEvent`, dropping undefined
|
|
154
|
+
* optionals so the emitted shape is consistent across event kinds.
|
|
155
|
+
*/
|
|
156
|
+
export declare function normalizeEvent<K extends UWCEventName>(name: K, data: UWCEventMap[K], sdkSessionId: string, now: number, correlationId?: string): UWCTelemetryEvent;
|
|
157
|
+
//# sourceMappingURL=telemetry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.d.ts","sourceRoot":"","sources":["../../src/observability/telemetry.ts"],"names":[],"mappings":"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"}
|