@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,115 @@
|
|
|
1
|
+
import { createBoundedDedup, safeErrorMessage } from '@meshconnect/uwc-types';
|
|
2
|
+
import { UWC_EVENT_NAMES } from '../events';
|
|
3
|
+
import { normalizeEvent } from './telemetry';
|
|
4
|
+
import { scrubArgs, scrubMessage } from './scrub';
|
|
5
|
+
/**
|
|
6
|
+
* Bridge the internal event bus to a consumer-supplied `UWCObserver`.
|
|
7
|
+
*
|
|
8
|
+
* Subscribes to every `UWCEventMap` event, normalizes each to a PII-safe
|
|
9
|
+
* `UWCTelemetryEvent`, and forwards it to `observer.onEvent`. The `log` event is
|
|
10
|
+
* routed to `observer.onLog` instead (with scrubbed args) — it's the logging
|
|
11
|
+
* channel, not a telemetry record. `change` is skipped: it's the legacy
|
|
12
|
+
* re-render cascade and carries no payload.
|
|
13
|
+
*
|
|
14
|
+
* Returns a teardown function that removes every subscription. The caller MUST
|
|
15
|
+
* invoke it on connector teardown — leaving these attached is the same listener-
|
|
16
|
+
* leak class as the historical `BridgeParent.destroy()` zombie bug.
|
|
17
|
+
*
|
|
18
|
+
* @param sdkSessionId - per-connector-instance id stamped on every record.
|
|
19
|
+
* @param getCorrelationId - resolves the optional consumer correlation id,
|
|
20
|
+
* called per-event so an id that arrives/changes after construction is
|
|
21
|
+
* honoured. Must not throw (the caller pre-wraps it). Defaults to `undefined`.
|
|
22
|
+
* @param now - clock injectable for deterministic tests; defaults to `Date.now`.
|
|
23
|
+
* @param logger - optional; a throwing observer is reported via `logger.debug`.
|
|
24
|
+
* When omitted the throw is swallowed silently (the historical behaviour).
|
|
25
|
+
*/
|
|
26
|
+
/**
|
|
27
|
+
* Cap on distinct observer-error messages remembered for dedup. Same storm-guard
|
|
28
|
+
* contract as the relay/bridge dedup sets: bounding keeps memory flat on a
|
|
29
|
+
* long-lived instance even when a broken observer throws high-cardinality
|
|
30
|
+
* messages (e.g. embedding event data or timestamps).
|
|
31
|
+
*/
|
|
32
|
+
export const MAX_REPORTED_OBSERVER_ERRORS = 100;
|
|
33
|
+
export function connectObserver(eventManager, observer, sdkSessionId, getCorrelationId = () => undefined, now = Date.now, logger) {
|
|
34
|
+
const unsubscribers = [];
|
|
35
|
+
// Set while we log an observer failure so the resulting `logger.debug(...)` —
|
|
36
|
+
// which re-enters the bus as a `log` event — is NOT forwarded back to the same
|
|
37
|
+
// (throwing) observer, which would recurse. One logical dispatch at a time.
|
|
38
|
+
let loggingObserverError = false;
|
|
39
|
+
// "Once per error site": dedupe by the error's message so a wallet whose
|
|
40
|
+
// observer throws on every event logs once, not on a tight loop. Scoped to THIS
|
|
41
|
+
// connector instance (not module-global) so one connector's failures don't
|
|
42
|
+
// suppress another's; bounded so it can't grow for the instance's lifetime.
|
|
43
|
+
const observerErrorDedup = createBoundedDedup(MAX_REPORTED_OBSERVER_ERRORS);
|
|
44
|
+
const reportObserverError = (err) => {
|
|
45
|
+
if (!logger)
|
|
46
|
+
return;
|
|
47
|
+
// safeErrorMessage: a poisoned getter / throwing toString on the observer's
|
|
48
|
+
// own error must not escape this reporter, and the dedup key stays bounded.
|
|
49
|
+
const key = safeErrorMessage(err);
|
|
50
|
+
if (!observerErrorDedup.firstSeen(key))
|
|
51
|
+
return;
|
|
52
|
+
loggingObserverError = true;
|
|
53
|
+
try {
|
|
54
|
+
logger.debug('UWC observer threw', err);
|
|
55
|
+
}
|
|
56
|
+
finally {
|
|
57
|
+
loggingObserverError = false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
for (const name of UWC_EVENT_NAMES) {
|
|
61
|
+
if (name === 'change')
|
|
62
|
+
continue;
|
|
63
|
+
if (name === 'log') {
|
|
64
|
+
unsubscribers.push(eventManager.on('log', data => {
|
|
65
|
+
if (!observer.onLog)
|
|
66
|
+
return;
|
|
67
|
+
// The observer-error debug log re-enters here as a `log` event; drop it
|
|
68
|
+
// so we never forward our own failure notice back to the bad observer.
|
|
69
|
+
if (loggingObserverError)
|
|
70
|
+
return;
|
|
71
|
+
const { level, message, args } = data;
|
|
72
|
+
try {
|
|
73
|
+
// Scrub the message too, not just args: the message reaches the same
|
|
74
|
+
// consumer sink, so the egress contract can't rely on every internal
|
|
75
|
+
// log string staying free of dynamic (potentially sensitive) data.
|
|
76
|
+
observer.onLog(level, scrubMessage(message), scrubArgs(args));
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
// A throwing observer must not break the dispatch chain.
|
|
80
|
+
reportObserverError(err);
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
unsubscribers.push(eventManager.on(name, data => {
|
|
86
|
+
if (!observer.onEvent)
|
|
87
|
+
return;
|
|
88
|
+
// Resolve the id in its OWN guard: a throwing getter is a config
|
|
89
|
+
// problem, not an observer failure — degrade to "no id" (the getter's
|
|
90
|
+
// documented contract) instead of dropping the record.
|
|
91
|
+
let correlationId;
|
|
92
|
+
try {
|
|
93
|
+
correlationId = getCorrelationId();
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
correlationId = undefined;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
// Normalize OUTSIDE the EventManager's own try/catch (which guards the
|
|
100
|
+
// listener, not the work the listener does), then hand off.
|
|
101
|
+
observer.onEvent(normalizeEvent(name, data, sdkSessionId, now(), correlationId));
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
// Same: never let a bad observer break other listeners.
|
|
105
|
+
reportObserverError(err);
|
|
106
|
+
}
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
return () => {
|
|
110
|
+
for (const unsubscribe of unsubscribers)
|
|
111
|
+
unsubscribe();
|
|
112
|
+
unsubscribers.length = 0;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=connect-observer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connect-observer.js","sourceRoot":"","sources":["../../src/observability/connect-observer.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Platform } from '@meshconnect/uwc-types';
|
|
2
|
+
import type { EventManager } from '../managers/event-manager';
|
|
3
|
+
/**
|
|
4
|
+
* Minimal `document` surface this tracker touches. Declared locally (rather than
|
|
5
|
+
* relying on the DOM lib) so the module is SSR-safe and trivially testable with a
|
|
6
|
+
* fake — no jsdom required.
|
|
7
|
+
*/
|
|
8
|
+
export interface FocusDocumentLike {
|
|
9
|
+
visibilityState?: string;
|
|
10
|
+
hidden?: boolean;
|
|
11
|
+
addEventListener(type: string, listener: () => void): void;
|
|
12
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Minimal `window` surface for the `pageshow` listener. Separate from
|
|
16
|
+
* `FocusDocumentLike` because the two events have different targets:
|
|
17
|
+
* `visibilitychange` fires on `document`, while `pageshow` is a WINDOW event
|
|
18
|
+
* (HTML spec / MDN: "sent to a Window") — a document-level listener would never
|
|
19
|
+
* fire in a real browser.
|
|
20
|
+
*/
|
|
21
|
+
export interface FocusWindowLike {
|
|
22
|
+
addEventListener(type: string, listener: () => void): void;
|
|
23
|
+
removeEventListener(type: string, listener: () => void): void;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* While a wallet handoff is pending, emit a FACT on every tab-visibility
|
|
27
|
+
* transition — `pageHiddenDuringHandoff` when the page is backgrounded (the user
|
|
28
|
+
* likely left for the wallet app), `pageVisibleDuringHandoff` when it returns
|
|
29
|
+
* (including a bfcache `pageshow`, which iOS uses when coming back from a wallet).
|
|
30
|
+
*
|
|
31
|
+
* This is the raw signal behind "did the user actually app-switch to the wallet?"
|
|
32
|
+
* for deep-link flows — it does NOT interpret abandonment. The listeners are
|
|
33
|
+
* scoped to the single handoff: attach right after `awaitingWallet`, and the
|
|
34
|
+
* returned teardown (called the instant the op settles) removes them, so the
|
|
35
|
+
* page's visibility is only observed during the blind window. SSR-safe (no-op
|
|
36
|
+
* when there is neither `document` nor `window`).
|
|
37
|
+
*
|
|
38
|
+
* @returns a teardown that removes the listeners. Always call it (even when
|
|
39
|
+
* nothing was attached — it's a no-op then) to keep the call site symmetric.
|
|
40
|
+
*/
|
|
41
|
+
export declare function trackFocusDuringHandoff(eventManager: EventManager, handoffId: string, platform: Platform, doc?: FocusDocumentLike | undefined, win?: FocusWindowLike | undefined): () => void;
|
|
42
|
+
//# sourceMappingURL=focus-tracker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-tracker.d.ts","sourceRoot":"","sources":["../../src/observability/focus-tracker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAC1D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC9D;AAED;;;;;;GAMG;AACH,MAAM,WAAW,eAAe;IAC9B,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;IAC1D,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI,CAAA;CAC9D;AAYD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,YAAY,EAC1B,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,GAAG,GAAE,iBAAiB,GAAG,SAA6B,EACtD,GAAG,GAAE,eAAe,GAAG,SAA2B,GACjD,MAAM,IAAI,CAoCZ"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
function resolveDocument() {
|
|
2
|
+
const doc = globalThis.document;
|
|
3
|
+
return doc && typeof doc.addEventListener === 'function' ? doc : undefined;
|
|
4
|
+
}
|
|
5
|
+
function resolveWindow() {
|
|
6
|
+
const win = globalThis.window;
|
|
7
|
+
return win && typeof win.addEventListener === 'function' ? win : undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* While a wallet handoff is pending, emit a FACT on every tab-visibility
|
|
11
|
+
* transition — `pageHiddenDuringHandoff` when the page is backgrounded (the user
|
|
12
|
+
* likely left for the wallet app), `pageVisibleDuringHandoff` when it returns
|
|
13
|
+
* (including a bfcache `pageshow`, which iOS uses when coming back from a wallet).
|
|
14
|
+
*
|
|
15
|
+
* This is the raw signal behind "did the user actually app-switch to the wallet?"
|
|
16
|
+
* for deep-link flows — it does NOT interpret abandonment. The listeners are
|
|
17
|
+
* scoped to the single handoff: attach right after `awaitingWallet`, and the
|
|
18
|
+
* returned teardown (called the instant the op settles) removes them, so the
|
|
19
|
+
* page's visibility is only observed during the blind window. SSR-safe (no-op
|
|
20
|
+
* when there is neither `document` nor `window`).
|
|
21
|
+
*
|
|
22
|
+
* @returns a teardown that removes the listeners. Always call it (even when
|
|
23
|
+
* nothing was attached — it's a no-op then) to keep the call site symmetric.
|
|
24
|
+
*/
|
|
25
|
+
export function trackFocusDuringHandoff(eventManager, handoffId, platform, doc = resolveDocument(), win = resolveWindow()) {
|
|
26
|
+
if (!doc && !win)
|
|
27
|
+
return () => { };
|
|
28
|
+
// One physical return can fire BOTH `pageshow` (window) and a
|
|
29
|
+
// `visibilitychange`→visible (document) — an iOS bfcache restore does. Dedupe
|
|
30
|
+
// consecutive same-state emits so the hidden/visible events actually pair.
|
|
31
|
+
let lastState;
|
|
32
|
+
const emitState = (state) => {
|
|
33
|
+
if (state === lastState)
|
|
34
|
+
return;
|
|
35
|
+
lastState = state;
|
|
36
|
+
eventManager.emit(state === 'hidden'
|
|
37
|
+
? 'pageHiddenDuringHandoff'
|
|
38
|
+
: 'pageVisibleDuringHandoff', { handoffId, platform });
|
|
39
|
+
};
|
|
40
|
+
const onVisibility = () => {
|
|
41
|
+
const hidden = doc?.visibilityState === 'hidden' || doc?.hidden === true;
|
|
42
|
+
emitState(hidden ? 'hidden' : 'visible');
|
|
43
|
+
};
|
|
44
|
+
// `pageshow` fires on a bfcache restore (returning from the wallet app on iOS),
|
|
45
|
+
// a "came back" signal `visibilitychange` doesn't always deliver. It targets
|
|
46
|
+
// the WINDOW — attaching it to `document` would silently never fire.
|
|
47
|
+
const onPageShow = () => {
|
|
48
|
+
emitState('visible');
|
|
49
|
+
};
|
|
50
|
+
doc?.addEventListener('visibilitychange', onVisibility);
|
|
51
|
+
win?.addEventListener('pageshow', onPageShow);
|
|
52
|
+
return () => {
|
|
53
|
+
doc?.removeEventListener('visibilitychange', onVisibility);
|
|
54
|
+
win?.removeEventListener('pageshow', onPageShow);
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=focus-tracker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"focus-tracker.js","sourceRoot":"","sources":["../../src/observability/focus-tracker.ts"],"names":[],"mappings":"AA2BA,SAAS,eAAe;IACtB,MAAM,GAAG,GAAI,UAA+C,CAAC,QAAQ,CAAA;IACrE,OAAO,GAAG,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5E,CAAC;AAED,SAAS,aAAa;IACpB,MAAM,GAAG,GAAI,UAA2C,CAAC,MAAM,CAAA;IAC/D,OAAO,GAAG,IAAI,OAAO,GAAG,CAAC,gBAAgB,KAAK,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5E,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,uBAAuB,CACrC,YAA0B,EAC1B,SAAiB,EACjB,QAAkB,EAClB,MAAqC,eAAe,EAAE,EACtD,MAAmC,aAAa,EAAE;IAElD,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;QAAE,OAAO,GAAG,EAAE,GAAE,CAAC,CAAA;IAEjC,8DAA8D;IAC9D,8EAA8E;IAC9E,2EAA2E;IAC3E,IAAI,SAA2C,CAAA;IAC/C,MAAM,SAAS,GAAG,CAAC,KAA2B,EAAE,EAAE;QAChD,IAAI,KAAK,KAAK,SAAS;YAAE,OAAM;QAC/B,SAAS,GAAG,KAAK,CAAA;QACjB,YAAY,CAAC,IAAI,CACf,KAAK,KAAK,QAAQ;YAChB,CAAC,CAAC,yBAAyB;YAC3B,CAAC,CAAC,0BAA0B,EAC9B,EAAE,SAAS,EAAE,QAAQ,EAAE,CACxB,CAAA;IACH,CAAC,CAAA;IAED,MAAM,YAAY,GAAG,GAAG,EAAE;QACxB,MAAM,MAAM,GAAG,GAAG,EAAE,eAAe,KAAK,QAAQ,IAAI,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;QACxE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAA;IAC1C,CAAC,CAAA;IACD,gFAAgF;IAChF,6EAA6E;IAC7E,qEAAqE;IACrE,MAAM,UAAU,GAAG,GAAG,EAAE;QACtB,SAAS,CAAC,SAAS,CAAC,CAAA;IACtB,CAAC,CAAA;IAED,GAAG,EAAE,gBAAgB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAA;IACvD,GAAG,EAAE,gBAAgB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IAE7C,OAAO,GAAG,EAAE;QACV,GAAG,EAAE,mBAAmB,CAAC,kBAAkB,EAAE,YAAY,CAAC,CAAA;QAC1D,GAAG,EAAE,mBAAmB,CAAC,UAAU,EAAE,UAAU,CAAC,CAAA;IAClD,CAAC,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { ConnectionMode, Namespace, NetworkId, Session } from '@meshconnect/uwc-types';
|
|
2
|
+
import type { EventManager } from '../managers/event-manager';
|
|
3
|
+
import type { UWCOperation } from '../events';
|
|
4
|
+
/** Routing facets known at the moment of a wallet handoff. No address, no payload. */
|
|
5
|
+
export interface HandoffContext {
|
|
6
|
+
operation: UWCOperation;
|
|
7
|
+
connectionMode: ConnectionMode;
|
|
8
|
+
walletId?: string | undefined;
|
|
9
|
+
namespace?: Namespace | undefined;
|
|
10
|
+
chainId?: NetworkId | undefined;
|
|
11
|
+
/** switchNetwork only: target chain already the active one (no-op switch). */
|
|
12
|
+
targetAlreadyActive?: boolean | undefined;
|
|
13
|
+
}
|
|
14
|
+
/** Build handoff routing facets from the active session (no address). */
|
|
15
|
+
export declare function sessionHandoffContext(operation: UWCOperation, session: Session): HandoffContext;
|
|
16
|
+
/** Whether this thrown value already produced a handoff terminal. */
|
|
17
|
+
export declare function hasHandoffTerminal(error: unknown): boolean;
|
|
18
|
+
export interface InstrumentHandoffOptions {
|
|
19
|
+
eventManager: EventManager;
|
|
20
|
+
/**
|
|
21
|
+
* Deadline after which `walletTimedOut` fires as a SIDE marker (never rejects).
|
|
22
|
+
* `0` disables the marker. `tonConnect` is always skipped — TON bounds its own
|
|
23
|
+
* wallet response internally, so a second observational timer would be noise.
|
|
24
|
+
*/
|
|
25
|
+
timeoutMs: number;
|
|
26
|
+
/**
|
|
27
|
+
* The caller's cancellation signal, when the op accepts one. Consulted in the
|
|
28
|
+
* catch path: a rejection observed while the signal is aborted is treated as
|
|
29
|
+
* the cancellation itself (no terminal) even when the rejection value is a
|
|
30
|
+
* custom `abort(reason)` — which is NOT an AbortError by name.
|
|
31
|
+
*/
|
|
32
|
+
signal?: AbortSignal | undefined;
|
|
33
|
+
/** Clock injectable for deterministic tests. */
|
|
34
|
+
now?: () => number;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Instrument one wallet-boundary op so the blind window is measured.
|
|
38
|
+
*
|
|
39
|
+
* Emits `awaitingWallet` immediately (before the await that hands control to the
|
|
40
|
+
* wallet), then exactly one terminal — `walletSucceeded` (success),
|
|
41
|
+
* `walletRejected` (explicit user no), or `walletFailed` (any other error) — all
|
|
42
|
+
* carrying the same `handoffId` and a `durationMs`. An intentional abort emits no
|
|
43
|
+
* terminal (the caller cancelled; it isn't a wallet outcome) — services check
|
|
44
|
+
* their signal AFTER this wrapper returns, so a wallet op that settled before
|
|
45
|
+
* the abort still records its truthful terminal first.
|
|
46
|
+
*
|
|
47
|
+
* A non-cancelling `walletTimedOut` marker fires if the deadline passes while the
|
|
48
|
+
* op is still pending; it is cleared the instant the op settles, so a fast op
|
|
49
|
+
* never false-fires it, and it NEVER rejects/aborts/mutates state — the wrapped
|
|
50
|
+
* promise stays the single terminal source of truth. This is a deliberate
|
|
51
|
+
* deviation from TON's cancelling `withWalletResponseTimeout`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function instrumentHandoff<T>(options: InstrumentHandoffOptions, context: HandoffContext, fn: () => Promise<T>): Promise<T>;
|
|
54
|
+
//# sourceMappingURL=instrument-handoff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument-handoff.d.ts","sourceRoot":"","sources":["../../src/observability/instrument-handoff.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,cAAc,EACd,SAAS,EACT,SAAS,EACT,OAAO,EACR,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAC7D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAW7C,sFAAsF;AACtF,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,YAAY,CAAA;IACvB,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACjC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC/B,8EAA8E;IAC9E,mBAAmB,CAAC,EAAE,OAAO,GAAG,SAAS,CAAA;CAC1C;AAED,yEAAyE;AACzE,wBAAgB,qBAAqB,CACnC,SAAS,EAAE,YAAY,EACvB,OAAO,EAAE,OAAO,GACf,cAAc,CAQhB;AAmBD,qEAAqE;AACrE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAM1D;AAQD,MAAM,WAAW,wBAAwB;IACvC,YAAY,EAAE,YAAY,CAAA;IAC1B;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAA;IACjB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;IAChC,gDAAgD;IAChD,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,iBAAiB,CAAC,CAAC,EACvC,OAAO,EAAE,wBAAwB,EACjC,OAAO,EAAE,cAAc,EACvB,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GACnB,OAAO,CAAC,CAAC,CAAC,CAmHZ"}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { generateId } from '../utils/id';
|
|
2
|
+
import { classifyError, isAbortError, isUserRejection, toWalletError } from '../utils/to-wallet-error';
|
|
3
|
+
import { derivePlatform, deriveWalletFlowType } from './telemetry';
|
|
4
|
+
import { trackFocusDuringHandoff } from './focus-tracker';
|
|
5
|
+
/** Build handoff routing facets from the active session (no address). */
|
|
6
|
+
export function sessionHandoffContext(operation, session) {
|
|
7
|
+
return {
|
|
8
|
+
operation,
|
|
9
|
+
connectionMode: session.connectionMode ?? 'injected',
|
|
10
|
+
walletId: session.activeWallet?.id,
|
|
11
|
+
namespace: session.activeNetwork?.namespace,
|
|
12
|
+
chainId: session.activeNetwork?.id ?? undefined
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Errors that already produced a funnel terminal (`walletRejected` /
|
|
17
|
+
* `walletFailed`). `emitError` reads this to tag its back-compat `error` record
|
|
18
|
+
* as `handoffCovered`, so a dashboard can count wallet failures from terminals
|
|
19
|
+
* and pre-wallet failures from `error` without double-counting. A WeakSet keyed
|
|
20
|
+
* on the error object — no mutation of the thrown value, and entries die with
|
|
21
|
+
* the error.
|
|
22
|
+
*
|
|
23
|
+
* DOCUMENTED GAP: a WeakSet cannot hold primitives. A wallet that rejects with
|
|
24
|
+
* a bare string/number still produces its terminal, but the façade's `error`
|
|
25
|
+
* record can't be marked — that failure double-counts under the "terminals +
|
|
26
|
+
* uncovered `error`" metric. Accepted: EIP-1193 requires object errors, and
|
|
27
|
+
* wrapping the thrown value to make it markable would mutate what the caller
|
|
28
|
+
* receives.
|
|
29
|
+
*/
|
|
30
|
+
const handoffTerminalErrors = new WeakSet();
|
|
31
|
+
/** Whether this thrown value already produced a handoff terminal. */
|
|
32
|
+
export function hasHandoffTerminal(error) {
|
|
33
|
+
return (typeof error === 'object' &&
|
|
34
|
+
error !== null &&
|
|
35
|
+
handoffTerminalErrors.has(error));
|
|
36
|
+
}
|
|
37
|
+
function markHandoffTerminal(error) {
|
|
38
|
+
if (typeof error === 'object' && error !== null) {
|
|
39
|
+
handoffTerminalErrors.add(error);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Instrument one wallet-boundary op so the blind window is measured.
|
|
44
|
+
*
|
|
45
|
+
* Emits `awaitingWallet` immediately (before the await that hands control to the
|
|
46
|
+
* wallet), then exactly one terminal — `walletSucceeded` (success),
|
|
47
|
+
* `walletRejected` (explicit user no), or `walletFailed` (any other error) — all
|
|
48
|
+
* carrying the same `handoffId` and a `durationMs`. An intentional abort emits no
|
|
49
|
+
* terminal (the caller cancelled; it isn't a wallet outcome) — services check
|
|
50
|
+
* their signal AFTER this wrapper returns, so a wallet op that settled before
|
|
51
|
+
* the abort still records its truthful terminal first.
|
|
52
|
+
*
|
|
53
|
+
* A non-cancelling `walletTimedOut` marker fires if the deadline passes while the
|
|
54
|
+
* op is still pending; it is cleared the instant the op settles, so a fast op
|
|
55
|
+
* never false-fires it, and it NEVER rejects/aborts/mutates state — the wrapped
|
|
56
|
+
* promise stays the single terminal source of truth. This is a deliberate
|
|
57
|
+
* deviation from TON's cancelling `withWalletResponseTimeout`.
|
|
58
|
+
*/
|
|
59
|
+
export async function instrumentHandoff(options, context, fn) {
|
|
60
|
+
const { eventManager, timeoutMs } = options;
|
|
61
|
+
const now = options.now ?? Date.now;
|
|
62
|
+
const handoffId = generateId();
|
|
63
|
+
const platform = derivePlatform();
|
|
64
|
+
const start = now();
|
|
65
|
+
// Flipped by the observational timeout timer; rides every terminal so a late
|
|
66
|
+
// completion is distinguishable from a fast one without a handoffId self-join.
|
|
67
|
+
let timedOut = false;
|
|
68
|
+
eventManager.emit('awaitingWallet', {
|
|
69
|
+
operation: context.operation,
|
|
70
|
+
connectionMode: context.connectionMode,
|
|
71
|
+
walletId: context.walletId,
|
|
72
|
+
namespace: context.namespace,
|
|
73
|
+
chainId: context.chainId,
|
|
74
|
+
handoffId,
|
|
75
|
+
walletFlowType: deriveWalletFlowType(context.connectionMode, platform),
|
|
76
|
+
platform,
|
|
77
|
+
...(context.targetAlreadyActive !== undefined && {
|
|
78
|
+
targetAlreadyActive: context.targetAlreadyActive
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
// Observe page-visibility transitions ONLY while this handoff is pending — the
|
|
82
|
+
// app-switch signal for deep-link flows (left for the wallet / came back). The
|
|
83
|
+
// teardown in `finally` removes the listeners the instant the op settles.
|
|
84
|
+
const stopFocus = trackFocusDuringHandoff(eventManager, handoffId, platform);
|
|
85
|
+
// Same routing facets as `awaitingWallet` so every terminal is self-describing
|
|
86
|
+
// (no handoffId self-join needed to know which wallet / chain / mode it closed).
|
|
87
|
+
const routing = {
|
|
88
|
+
connectionMode: context.connectionMode,
|
|
89
|
+
walletId: context.walletId,
|
|
90
|
+
namespace: context.namespace,
|
|
91
|
+
chainId: context.chainId,
|
|
92
|
+
...(context.targetAlreadyActive !== undefined && {
|
|
93
|
+
targetAlreadyActive: context.targetAlreadyActive
|
|
94
|
+
})
|
|
95
|
+
};
|
|
96
|
+
const shouldTime = timeoutMs > 0 && context.connectionMode !== 'tonConnect';
|
|
97
|
+
const timer = shouldTime
|
|
98
|
+
? setTimeout(() => {
|
|
99
|
+
timedOut = true;
|
|
100
|
+
eventManager.emit('walletTimedOut', {
|
|
101
|
+
handoffId,
|
|
102
|
+
operation: context.operation,
|
|
103
|
+
durationMs: now() - start,
|
|
104
|
+
timeoutMs,
|
|
105
|
+
...routing
|
|
106
|
+
});
|
|
107
|
+
}, timeoutMs)
|
|
108
|
+
: undefined;
|
|
109
|
+
try {
|
|
110
|
+
const result = await fn();
|
|
111
|
+
eventManager.emit('walletSucceeded', {
|
|
112
|
+
handoffId,
|
|
113
|
+
operation: context.operation,
|
|
114
|
+
durationMs: now() - start,
|
|
115
|
+
timedOut,
|
|
116
|
+
timeoutMs: timedOut ? timeoutMs : undefined,
|
|
117
|
+
...routing
|
|
118
|
+
});
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
// Caller-driven cancellation isn't a wallet outcome — leave it unterminated,
|
|
123
|
+
// mirroring the façade's `emitError` AbortError skip. Checked BEFORE rejection
|
|
124
|
+
// is safe: real user rejections surface as WalletConnectorError{rejected} or
|
|
125
|
+
// EIP-1193 4001 (see isUserRejection), never as an AbortError — within UWC's
|
|
126
|
+
// dependency surface AbortError is produced only by the caller's AbortSignal.
|
|
127
|
+
// The signal covers `abort(customReason)`, whose rejection value carries the
|
|
128
|
+
// caller's own name; a real wallet failure that lands while the caller has
|
|
129
|
+
// already aborted is intentionally folded into the cancellation — the caller
|
|
130
|
+
// walked away, so the outcome no longer describes a user-visible interaction.
|
|
131
|
+
if (isAbortError(error) || options.signal?.aborted)
|
|
132
|
+
throw error;
|
|
133
|
+
const durationMs = now() - start;
|
|
134
|
+
const { errorCode, failureKind, source } = classifyError(error);
|
|
135
|
+
// Either branch below emits a terminal for this error — record that so the
|
|
136
|
+
// façade's back-compat `error` event can mark itself as a duplicate.
|
|
137
|
+
markHandoffTerminal(error);
|
|
138
|
+
if (isUserRejection(error)) {
|
|
139
|
+
eventManager.emit('walletRejected', {
|
|
140
|
+
handoffId,
|
|
141
|
+
operation: context.operation,
|
|
142
|
+
durationMs,
|
|
143
|
+
timedOut,
|
|
144
|
+
timeoutMs: timedOut ? timeoutMs : undefined,
|
|
145
|
+
errorCode,
|
|
146
|
+
failureKind,
|
|
147
|
+
source,
|
|
148
|
+
...routing
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
eventManager.emit('walletFailed', {
|
|
153
|
+
handoffId,
|
|
154
|
+
operation: context.operation,
|
|
155
|
+
durationMs,
|
|
156
|
+
error: toWalletError(error),
|
|
157
|
+
timedOut,
|
|
158
|
+
timeoutMs: timedOut ? timeoutMs : undefined,
|
|
159
|
+
errorCode,
|
|
160
|
+
failureKind,
|
|
161
|
+
source,
|
|
162
|
+
...routing
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
if (timer)
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
stopFocus();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=instrument-handoff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instrument-handoff.js","sourceRoot":"","sources":["../../src/observability/instrument-handoff.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { WalletLifecycleEvent, ConnectionMode, Namespace, NetworkId } from '@meshconnect/uwc-types';
|
|
2
|
+
import type { EventManager } from '../managers/event-manager';
|
|
3
|
+
/** Routing facets stamped on every lifecycle event. No address. */
|
|
4
|
+
export interface LifecycleRouting {
|
|
5
|
+
connectionMode: ConnectionMode;
|
|
6
|
+
walletId?: string | undefined;
|
|
7
|
+
namespace?: Namespace | undefined;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Hard ceiling on lifecycle emissions per attachment. Every other telemetry
|
|
11
|
+
* sink this layer added is bounded (relay / bridge / observer-error dedup sets
|
|
12
|
+
* cap at 100); a provider looping `chainChanged`/`accountsChanged` must not be
|
|
13
|
+
* the one unbounded path to the observer. A legitimate session stays far below
|
|
14
|
+
* this; a storming provider goes silent instead of driving egress cost.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MAX_LIFECYCLE_EVENTS_PER_ATTACHMENT = 100;
|
|
17
|
+
export interface AttachLifecycleOptions {
|
|
18
|
+
eventManager: EventManager;
|
|
19
|
+
/**
|
|
20
|
+
* The active connector's `onLifecycleEvent` (already bound). `undefined` when the
|
|
21
|
+
* connector can't observe lifecycle at all; the value it returns is `undefined`
|
|
22
|
+
* when THIS session can't be observed (e.g. no live `.on`). Either way the
|
|
23
|
+
* bridge emits `lifecycleTelemetryUnavailable` and becomes a no-op.
|
|
24
|
+
*/
|
|
25
|
+
subscribe: ((l: (e: WalletLifecycleEvent) => void) => (() => void) | undefined) | undefined;
|
|
26
|
+
/** Resolved per-event so a mid-session account/chain change is reflected. */
|
|
27
|
+
getRouting: () => LifecycleRouting;
|
|
28
|
+
connectionMode: ConnectionMode;
|
|
29
|
+
namespace?: Namespace | undefined;
|
|
30
|
+
walletId?: string | undefined;
|
|
31
|
+
/** Direct page vs production iframe — stamped on `lifecycleTelemetryUnavailable`. */
|
|
32
|
+
runtime?: 'direct' | 'iframe';
|
|
33
|
+
/**
|
|
34
|
+
* Window (ms) within which a wallet `chainChanged` is treated as the echo of a
|
|
35
|
+
* UWC-initiated `switchNetwork` and suppressed. Default 3000.
|
|
36
|
+
*/
|
|
37
|
+
echoWindowMs?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Seed for echo suppression when (re)attaching right AFTER a UWC-initiated
|
|
40
|
+
* switch completed (the cross-namespace reattach path): the fresh closure
|
|
41
|
+
* never saw that switch's `awaitingWallet`/`networkSwitched`, so the new
|
|
42
|
+
* provider's echo would otherwise surface as wallet-initiated. Same one-shot
|
|
43
|
+
* consume semantics as a bus-armed switch.
|
|
44
|
+
*/
|
|
45
|
+
initialSwitch?: {
|
|
46
|
+
chainId: NetworkId;
|
|
47
|
+
at: number;
|
|
48
|
+
} | undefined;
|
|
49
|
+
now?: () => number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Bridge a connector's PII-safe `WalletLifecycleEvent` stream onto the event bus
|
|
53
|
+
* as `walletAccountChanged` / `walletChainChanged` / `walletDisconnected` /
|
|
54
|
+
* `walletSessionExpired`.
|
|
55
|
+
*
|
|
56
|
+
* Two correctness concerns are handled here:
|
|
57
|
+
* - **Echo suppression.** When UWC drives a `switchNetwork`, the wallet emits its
|
|
58
|
+
* own `chainChanged` for the same chain — that's already captured by UWC's
|
|
59
|
+
* `networkSwitched` / the switch handoff, so re-emitting it as a wallet-
|
|
60
|
+
* initiated `walletChainChanged` would double-count. We watch the bus for
|
|
61
|
+
* `networkSwitched` and drop a matching `chainChanged` inside `echoWindowMs`.
|
|
62
|
+
* - **Availability is signal.** If the connector can't observe lifecycle, we emit
|
|
63
|
+
* `lifecycleTelemetryUnavailable` ONCE so a dashboard can tell "silent" from
|
|
64
|
+
* "unwired".
|
|
65
|
+
*
|
|
66
|
+
* @returns a teardown that removes the connector subscription AND the bus watcher.
|
|
67
|
+
*/
|
|
68
|
+
export declare function attachLifecycle(opts: AttachLifecycleOptions): () => void;
|
|
69
|
+
//# sourceMappingURL=lifecycle-bridge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lifecycle-bridge.d.ts","sourceRoot":"","sources":["../../src/observability/lifecycle-bridge.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,cAAc,EACd,SAAS,EACT,SAAS,EAEV,MAAM,wBAAwB,CAAA;AAC/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAA;AAE7D,mEAAmE;AACnE,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,cAAc,CAAA;IAC9B,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;CAClC;AAED;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,MAAM,CAAA;AAEtD,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,YAAY,CAAA;IAC1B;;;;;OAKG;IACH,SAAS,EACL,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,oBAAoB,KAAK,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,GAAG,SAAS,CAAC,GACpE,SAAS,CAAA;IACb,6EAA6E;IAC7E,UAAU,EAAE,MAAM,gBAAgB,CAAA;IAClC,cAAc,EAAE,cAAc,CAAA;IAC9B,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IACjC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC7B,qFAAqF;IACrF,OAAO,CAAC,EAAE,QAAQ,GAAG,QAAQ,CAAA;IAC7B;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB;;;;;;OAMG;IACH,aAAa,CAAC,EAAE;QAAE,OAAO,EAAE,SAAS,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS,CAAA;IAC9D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,sBAAsB,GAAG,MAAM,IAAI,CA2IxE"}
|