@formo/analytics 1.37.0 → 1.38.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/FormoAnalytics.d.ts +64 -0
- package/dist/cjs/src/FormoAnalytics.js +155 -5
- package/dist/cjs/src/FormoAnalyticsProvider.js +1 -0
- package/dist/cjs/src/evm/EvmEventTracker.d.ts +38 -10
- package/dist/cjs/src/evm/EvmEventTracker.js +182 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.js +26 -4
- package/dist/cjs/src/evm/EvmRequestTracker.d.ts +28 -10
- package/dist/cjs/src/evm/EvmRequestTracker.js +199 -55
- package/dist/cjs/src/provider/detection.d.ts +30 -0
- package/dist/cjs/src/provider/detection.js +62 -0
- package/dist/cjs/src/provider/index.d.ts +1 -1
- package/dist/cjs/src/provider/index.js +3 -1
- package/dist/cjs/src/types/base.d.ts +23 -0
- package/dist/cjs/src/types/provider.d.ts +16 -0
- package/dist/cjs/src/types/provider.js +17 -1
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/src/wagmi/WagmiEventHandler.d.ts +44 -0
- package/dist/cjs/src/wagmi/WagmiEventHandler.js +306 -34
- package/dist/esm/src/FormoAnalytics.d.ts +64 -0
- package/dist/esm/src/FormoAnalytics.js +155 -5
- package/dist/esm/src/FormoAnalyticsProvider.js +1 -0
- package/dist/esm/src/evm/EvmEventTracker.d.ts +38 -10
- package/dist/esm/src/evm/EvmEventTracker.js +183 -1
- package/dist/esm/src/evm/EvmProviderRegistry.js +27 -5
- package/dist/esm/src/evm/EvmRequestTracker.d.ts +28 -10
- package/dist/esm/src/evm/EvmRequestTracker.js +198 -54
- package/dist/esm/src/provider/detection.d.ts +30 -0
- package/dist/esm/src/provider/detection.js +60 -0
- package/dist/esm/src/provider/index.d.ts +1 -1
- package/dist/esm/src/provider/index.js +1 -1
- package/dist/esm/src/types/base.d.ts +23 -0
- package/dist/esm/src/types/provider.d.ts +16 -0
- package/dist/esm/src/types/provider.js +16 -0
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/esm/src/wagmi/WagmiEventHandler.d.ts +44 -0
- package/dist/esm/src/wagmi/WagmiEventHandler.js +306 -34
- package/dist/index.umd.min.js +1 -1
- package/package.json +2 -2
|
@@ -96,6 +96,8 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
96
96
|
* Call this when destroying the analytics instance
|
|
97
97
|
* @returns {void}
|
|
98
98
|
*/
|
|
99
|
+
/** Set by cleanup(); a torn-down instance refuses new registrations. */
|
|
100
|
+
private isCleanedUp;
|
|
99
101
|
cleanup(): void;
|
|
100
102
|
/**
|
|
101
103
|
* Emits a connect wallet event.
|
|
@@ -373,6 +375,68 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
373
375
|
*/
|
|
374
376
|
get solana(): SolanaManager;
|
|
375
377
|
private getCurrentChainId;
|
|
378
|
+
/**
|
|
379
|
+
* Track an EIP-1193 provider the page constructed itself.
|
|
380
|
+
*
|
|
381
|
+
* Discovery covers EIP-6963 announcements and `window.ethereum`, which is
|
|
382
|
+
* every injected wallet and nothing else. WalletConnect and Ledger
|
|
383
|
+
* providers are built by the app (`EthereumProvider.init(...)`) and
|
|
384
|
+
* announce nothing, so their sessions were invisible: connects,
|
|
385
|
+
* signatures, and transactions all silently missing. Hand the provider
|
|
386
|
+
* here once it exists and it takes the exact pipeline a discovered
|
|
387
|
+
* provider takes - detect event, lifecycle listeners, request wrapper -
|
|
388
|
+
* and a session that is already live is adopted from the provider's
|
|
389
|
+
* synchronous state.
|
|
390
|
+
*
|
|
391
|
+
* Metadata resolution order: the caller's `info` overrides win; then a
|
|
392
|
+
* WalletConnect session's peer metadata, which names the REAL wallet on
|
|
393
|
+
* the far side of the transport (for example "Ledger Live"); then flag
|
|
394
|
+
* sniffing; then a generic fallback. One deliberate exception: a caller
|
|
395
|
+
* name of exactly "WalletConnect" is the generic transport name, so the
|
|
396
|
+
* live peer still replaces it on events - name the provider anything
|
|
397
|
+
* else to pin it verbatim.
|
|
398
|
+
*
|
|
399
|
+
* No-op outside the EIP-1193 path: in wagmi mode the connector system
|
|
400
|
+
* already tracks these sessions, and wrapping the same provider twice
|
|
401
|
+
* would double-report every event.
|
|
402
|
+
*
|
|
403
|
+
* With several live SDK instances (multi write-key pages) registering
|
|
404
|
+
* the SAME provider, request-derived events (signatures, transactions)
|
|
405
|
+
* go to the most recently registered live instance - the same
|
|
406
|
+
* single-observer semantics discovery has always had for the request
|
|
407
|
+
* wrapper. Lifecycle events (connect, chain, disconnect) reach every
|
|
408
|
+
* instance. Fanning request observations out to all instances is a
|
|
409
|
+
* separate feature.
|
|
410
|
+
*
|
|
411
|
+
* @returns true when the provider is (now) tracked, false when it was
|
|
412
|
+
* refused (wagmi mode, EVM disabled, or not a valid EIP-1193 provider).
|
|
413
|
+
*
|
|
414
|
+
* @example
|
|
415
|
+
* ```typescript
|
|
416
|
+
* const wcProvider = await EthereumProvider.init({ projectId, chains });
|
|
417
|
+
* formo.registerProvider(wcProvider);
|
|
418
|
+
* ```
|
|
419
|
+
*/
|
|
420
|
+
/**
|
|
421
|
+
* INTERNAL. Install the request wrapper on a wagmi connector's provider.
|
|
422
|
+
*
|
|
423
|
+
* Wagmi mode watches the store and caches, which see hook-driven calls
|
|
424
|
+
* only; imperative viem calls (walletClient.sendTransaction,
|
|
425
|
+
* .signMessage, .writeContract, raw request) create no mutation and were
|
|
426
|
+
* silently lost. Every viem client in a wagmi app is built on the
|
|
427
|
+
* connector's EIP-1193 provider, so wrapping that provider closes the
|
|
428
|
+
* gap. Lifecycle (connect/chain/disconnect) stays store-driven: only the
|
|
429
|
+
* request wrapper installs here. Double counting is prevented in the
|
|
430
|
+
* wrapper via `shouldSkipRequestCapture`.
|
|
431
|
+
*/
|
|
432
|
+
_wrapWagmiProvider(provider: EIP1193Provider): void;
|
|
433
|
+
registerProvider(provider: EIP1193Provider, info?: {
|
|
434
|
+
name?: string;
|
|
435
|
+
rdns?: string;
|
|
436
|
+
icon?: `data:image/${string}`;
|
|
437
|
+
}): boolean;
|
|
438
|
+
/** Fallback uuid suffix for platforms without crypto.randomUUID. */
|
|
439
|
+
private static registeredProviderSeq;
|
|
376
440
|
getTrackedProvidersCount(): number;
|
|
377
441
|
/**
|
|
378
442
|
* Get current provider state for debugging
|
|
@@ -60,6 +60,7 @@ var address_1 = require("./utils/address");
|
|
|
60
60
|
var TrackingPolicy_1 = require("./tracking/TrackingPolicy");
|
|
61
61
|
var WalletStateStore_1 = require("./wallet/WalletStateStore");
|
|
62
62
|
var EvmProviderRegistry_1 = require("./evm/EvmProviderRegistry");
|
|
63
|
+
var provider_1 = require("./provider");
|
|
63
64
|
var EvmEventTracker_1 = require("./evm/EvmEventTracker");
|
|
64
65
|
var EvmRequestTracker_1 = require("./evm/EvmRequestTracker");
|
|
65
66
|
var chain_1 = require("./utils/chain");
|
|
@@ -93,6 +94,13 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
93
94
|
this._currentUrl = "";
|
|
94
95
|
this._pageHooksDisposed = false;
|
|
95
96
|
this.currentUserId = "";
|
|
97
|
+
/**
|
|
98
|
+
* Clean up resources and event listeners
|
|
99
|
+
* Call this when destroying the analytics instance
|
|
100
|
+
* @returns {void}
|
|
101
|
+
*/
|
|
102
|
+
/** Set by cleanup(); a torn-down instance refuses new registrations. */
|
|
103
|
+
this.isCleanedUp = false;
|
|
96
104
|
this.config = {
|
|
97
105
|
writeKey: writeKey,
|
|
98
106
|
};
|
|
@@ -114,6 +122,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
114
122
|
this.transaction = this.transaction.bind(this);
|
|
115
123
|
this.detect = this.detect.bind(this);
|
|
116
124
|
this.track = this.track.bind(this);
|
|
125
|
+
this.registerProvider = this.registerProvider.bind(this);
|
|
117
126
|
this.page = this.page.bind(this);
|
|
118
127
|
this.reset = this.reset.bind(this);
|
|
119
128
|
this.cleanup = this.cleanup.bind(this);
|
|
@@ -171,6 +180,11 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
171
180
|
isAutocaptureEnabled: function (t) { return _this.isAutocaptureEnabled(t); },
|
|
172
181
|
signature: function (params, properties) { return _this.signature(params, properties); },
|
|
173
182
|
transaction: function (params, properties) { return _this.transaction(params, properties); },
|
|
183
|
+
// Hybrid capture: in wagmi mode the wrapper skips a request that a
|
|
184
|
+
// PENDING wagmi mutation already covers - the mutation handler
|
|
185
|
+
// captures it with ABI enrichment - and captures everything else
|
|
186
|
+
// (imperative viem calls that create no mutation).
|
|
187
|
+
shouldSkipRequestCapture: function (method, params) { var _a, _b; return (_b = (_a = _this.wagmiHandler) === null || _a === void 0 ? void 0 : _a.hasMatchingPendingMutation(method, params)) !== null && _b !== void 0 ? _b : false; },
|
|
174
188
|
});
|
|
175
189
|
this.evmEvents = new EvmEventTracker_1.EvmEventTracker(this.wallet, this.evm, {
|
|
176
190
|
isAutocaptureEnabled: function (t) { return _this.isAutocaptureEnabled(t); },
|
|
@@ -376,12 +390,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
376
390
|
// re-attached to the next session's events.
|
|
377
391
|
(0, storage_1.session)().remove(constants_1.SESSION_TRAFFIC_SOURCE_KEY);
|
|
378
392
|
};
|
|
379
|
-
/**
|
|
380
|
-
* Clean up resources and event listeners
|
|
381
|
-
* Call this when destroying the analytics instance
|
|
382
|
-
* @returns {void}
|
|
383
|
-
*/
|
|
384
393
|
FormoAnalytics.prototype.cleanup = function () {
|
|
394
|
+
this.isCleanedUp = true;
|
|
385
395
|
logger_1.logger.debug("FormoAnalytics: Cleaning up resources");
|
|
386
396
|
// Close the queue, don't just empty it. clear() only drops what is
|
|
387
397
|
// buffered at this instant; asynchronous work already in flight (event
|
|
@@ -1017,7 +1027,21 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1017
1027
|
* @returns {void}
|
|
1018
1028
|
*/
|
|
1019
1029
|
FormoAnalytics.prototype.optInTracking = function () {
|
|
1030
|
+
var _this = this;
|
|
1020
1031
|
var _a;
|
|
1032
|
+
// A provider registered while the visitor was opted out had its
|
|
1033
|
+
// session adoption refused; nothing else retries it. Guarded: a
|
|
1034
|
+
// cleanup() racing this timer must not drive the torn-down tracker.
|
|
1035
|
+
setTimeout(function () {
|
|
1036
|
+
if (_this.isCleanedUp)
|
|
1037
|
+
return;
|
|
1038
|
+
try {
|
|
1039
|
+
_this.evmEvents.retryExternalAdoptions();
|
|
1040
|
+
}
|
|
1041
|
+
catch (_a) {
|
|
1042
|
+
/* adoption retry must never break opt-in */
|
|
1043
|
+
}
|
|
1044
|
+
}, 0);
|
|
1021
1045
|
logger_1.logger.info("Opting back into tracking");
|
|
1022
1046
|
// Remove opt-out flag
|
|
1023
1047
|
(0, consent_1.removeConsentFlag)(this.writeKey, constants_1.CONSENT_OPT_OUT_KEY);
|
|
@@ -1108,6 +1132,17 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1108
1132
|
return __awaiter(this, void 0, void 0, function () {
|
|
1109
1133
|
var _this = this;
|
|
1110
1134
|
return __generator(this, function (_a) {
|
|
1135
|
+
// A route change can end path-based suppression; a provider registered
|
|
1136
|
+
// while suppressed gets its refused session adoption retried here.
|
|
1137
|
+
// Idempotent and cheap when nothing is pending.
|
|
1138
|
+
if (!this.isCleanedUp) {
|
|
1139
|
+
try {
|
|
1140
|
+
this.evmEvents.retryExternalAdoptions();
|
|
1141
|
+
}
|
|
1142
|
+
catch (_b) {
|
|
1143
|
+
/* never let the retry break a page hit */
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1111
1146
|
if (!this.shouldTrack()) {
|
|
1112
1147
|
logger_1.logger.info("Track page hit: Skipping event due to tracking configuration");
|
|
1113
1148
|
return [2 /*return*/];
|
|
@@ -1275,6 +1310,119 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1275
1310
|
};
|
|
1276
1311
|
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
1277
1312
|
// and tracking
|
|
1313
|
+
/**
|
|
1314
|
+
* Track an EIP-1193 provider the page constructed itself.
|
|
1315
|
+
*
|
|
1316
|
+
* Discovery covers EIP-6963 announcements and `window.ethereum`, which is
|
|
1317
|
+
* every injected wallet and nothing else. WalletConnect and Ledger
|
|
1318
|
+
* providers are built by the app (`EthereumProvider.init(...)`) and
|
|
1319
|
+
* announce nothing, so their sessions were invisible: connects,
|
|
1320
|
+
* signatures, and transactions all silently missing. Hand the provider
|
|
1321
|
+
* here once it exists and it takes the exact pipeline a discovered
|
|
1322
|
+
* provider takes - detect event, lifecycle listeners, request wrapper -
|
|
1323
|
+
* and a session that is already live is adopted from the provider's
|
|
1324
|
+
* synchronous state.
|
|
1325
|
+
*
|
|
1326
|
+
* Metadata resolution order: the caller's `info` overrides win; then a
|
|
1327
|
+
* WalletConnect session's peer metadata, which names the REAL wallet on
|
|
1328
|
+
* the far side of the transport (for example "Ledger Live"); then flag
|
|
1329
|
+
* sniffing; then a generic fallback. One deliberate exception: a caller
|
|
1330
|
+
* name of exactly "WalletConnect" is the generic transport name, so the
|
|
1331
|
+
* live peer still replaces it on events - name the provider anything
|
|
1332
|
+
* else to pin it verbatim.
|
|
1333
|
+
*
|
|
1334
|
+
* No-op outside the EIP-1193 path: in wagmi mode the connector system
|
|
1335
|
+
* already tracks these sessions, and wrapping the same provider twice
|
|
1336
|
+
* would double-report every event.
|
|
1337
|
+
*
|
|
1338
|
+
* With several live SDK instances (multi write-key pages) registering
|
|
1339
|
+
* the SAME provider, request-derived events (signatures, transactions)
|
|
1340
|
+
* go to the most recently registered live instance - the same
|
|
1341
|
+
* single-observer semantics discovery has always had for the request
|
|
1342
|
+
* wrapper. Lifecycle events (connect, chain, disconnect) reach every
|
|
1343
|
+
* instance. Fanning request observations out to all instances is a
|
|
1344
|
+
* separate feature.
|
|
1345
|
+
*
|
|
1346
|
+
* @returns true when the provider is (now) tracked, false when it was
|
|
1347
|
+
* refused (wagmi mode, EVM disabled, or not a valid EIP-1193 provider).
|
|
1348
|
+
*
|
|
1349
|
+
* @example
|
|
1350
|
+
* ```typescript
|
|
1351
|
+
* const wcProvider = await EthereumProvider.init({ projectId, chains });
|
|
1352
|
+
* formo.registerProvider(wcProvider);
|
|
1353
|
+
* ```
|
|
1354
|
+
*/
|
|
1355
|
+
/**
|
|
1356
|
+
* INTERNAL. Install the request wrapper on a wagmi connector's provider.
|
|
1357
|
+
*
|
|
1358
|
+
* Wagmi mode watches the store and caches, which see hook-driven calls
|
|
1359
|
+
* only; imperative viem calls (walletClient.sendTransaction,
|
|
1360
|
+
* .signMessage, .writeContract, raw request) create no mutation and were
|
|
1361
|
+
* silently lost. Every viem client in a wagmi app is built on the
|
|
1362
|
+
* connector's EIP-1193 provider, so wrapping that provider closes the
|
|
1363
|
+
* gap. Lifecycle (connect/chain/disconnect) stays store-driven: only the
|
|
1364
|
+
* request wrapper installs here. Double counting is prevented in the
|
|
1365
|
+
* wrapper via `shouldSkipRequestCapture`.
|
|
1366
|
+
*/
|
|
1367
|
+
FormoAnalytics.prototype._wrapWagmiProvider = function (provider) {
|
|
1368
|
+
if (this.isCleanedUp || !(0, provider_1.isValidProvider)(provider))
|
|
1369
|
+
return;
|
|
1370
|
+
try {
|
|
1371
|
+
this.evmRequests.registerRequestListeners(provider);
|
|
1372
|
+
}
|
|
1373
|
+
catch (e) {
|
|
1374
|
+
logger_1.logger.warn("Failed to wrap wagmi provider for hybrid capture", e);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
FormoAnalytics.prototype.registerProvider = function (provider, info) {
|
|
1378
|
+
var _a, _b, _c;
|
|
1379
|
+
if (this.isCleanedUp) {
|
|
1380
|
+
// Cleanup terminally closed the event queue; listeners attached now
|
|
1381
|
+
// would hold this instance forever and deliver nothing.
|
|
1382
|
+
logger_1.logger.warn("registerProvider: instance is cleaned up; refusing");
|
|
1383
|
+
return false;
|
|
1384
|
+
}
|
|
1385
|
+
if (this.isEvmDisabled) {
|
|
1386
|
+
logger_1.logger.warn("registerProvider: EVM tracking is disabled; refusing");
|
|
1387
|
+
return false;
|
|
1388
|
+
}
|
|
1389
|
+
if (this.isWagmiMode) {
|
|
1390
|
+
logger_1.logger.warn("registerProvider: wagmi mode tracks connectors already; registering the provider here would double-report its events. Refusing.");
|
|
1391
|
+
return false;
|
|
1392
|
+
}
|
|
1393
|
+
if (!(0, provider_1.isValidProvider)(provider)) {
|
|
1394
|
+
logger_1.logger.warn("registerProvider: not a valid EIP-1193 provider; refusing");
|
|
1395
|
+
return false;
|
|
1396
|
+
}
|
|
1397
|
+
var detected = (0, provider_1.detectInjectedProviderInfo)(provider);
|
|
1398
|
+
var peer = (0, provider_1.readWalletConnectPeer)(provider);
|
|
1399
|
+
// A live peer identifies the session as WalletConnect even when the
|
|
1400
|
+
// provider carries no isWalletConnect flag (v2 providers often do not).
|
|
1401
|
+
var rdns = (_a = info === null || info === void 0 ? void 0 : info.rdns) !== null && _a !== void 0 ? _a : (peer && detected.rdns === "io.injected.provider"
|
|
1402
|
+
? "com.walletconnect"
|
|
1403
|
+
: detected.rdns);
|
|
1404
|
+
// The peer name is deliberately NOT stored: sessions change wallets,
|
|
1405
|
+
// and metadata frozen at registration would misname every later one.
|
|
1406
|
+
// `infoFor` resolves the peer live on each read, over the generic
|
|
1407
|
+
// transport name; a caller's explicit name still wins everywhere.
|
|
1408
|
+
var name = (_b = info === null || info === void 0 ? void 0 : info.name) !== null && _b !== void 0 ? _b : (peer ? "WalletConnect" : detected.name);
|
|
1409
|
+
// Per-instance uuid: EIP-6963 consumers (mipd included) deduplicate on
|
|
1410
|
+
// it, so two registered instances sharing an rdns-derived uuid would
|
|
1411
|
+
// collapse into one. Random when the platform provides it; a
|
|
1412
|
+
// monotonic suffix otherwise.
|
|
1413
|
+
var uuid = typeof crypto !== "undefined" && typeof crypto.randomUUID === "function"
|
|
1414
|
+
? crypto.randomUUID()
|
|
1415
|
+
: "registered-".concat(rdns.replace(/[^a-zA-Z0-9]/g, "-"), "-").concat((FormoAnalytics.registeredProviderSeq += 1));
|
|
1416
|
+
return this.evmEvents.adoptExternalProvider({
|
|
1417
|
+
info: {
|
|
1418
|
+
name: name,
|
|
1419
|
+
rdns: rdns,
|
|
1420
|
+
uuid: uuid,
|
|
1421
|
+
icon: (_c = info === null || info === void 0 ? void 0 : info.icon) !== null && _c !== void 0 ? _c : detected.icon,
|
|
1422
|
+
},
|
|
1423
|
+
provider: provider,
|
|
1424
|
+
});
|
|
1425
|
+
};
|
|
1278
1426
|
// Debug/monitoring helpers
|
|
1279
1427
|
FormoAnalytics.prototype.getTrackedProvidersCount = function () {
|
|
1280
1428
|
return this.evm.counts.trackedProviders;
|
|
@@ -1286,6 +1434,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1286
1434
|
FormoAnalytics.prototype.getProviderState = function () {
|
|
1287
1435
|
return __assign(__assign({}, this.evm.counts), { activeProvider: !!this._provider });
|
|
1288
1436
|
};
|
|
1437
|
+
/** Fallback uuid suffix for platforms without crypto.randomUUID. */
|
|
1438
|
+
FormoAnalytics.registeredProviderSeq = 0;
|
|
1289
1439
|
return FormoAnalytics;
|
|
1290
1440
|
}());
|
|
1291
1441
|
exports.FormoAnalytics = FormoAnalytics;
|
|
@@ -58,6 +58,7 @@ var defaultContext = {
|
|
|
58
58
|
page: function () { return Promise.resolve(); },
|
|
59
59
|
reset: function () { },
|
|
60
60
|
cleanup: function () { },
|
|
61
|
+
registerProvider: function () { return false; },
|
|
61
62
|
detect: function () { return Promise.resolve(); },
|
|
62
63
|
connect: function () { return Promise.resolve(); },
|
|
63
64
|
disconnect: function () { return Promise.resolve(); },
|
|
@@ -43,20 +43,19 @@ export interface EvmEventTrackerDeps {
|
|
|
43
43
|
*/
|
|
44
44
|
registerRequestListeners(provider: EIP1193Provider): boolean;
|
|
45
45
|
}
|
|
46
|
-
/**
|
|
47
|
-
* The EIP-1193 side of wallet tracking: which providers to watch, and what
|
|
48
|
-
* their events mean.
|
|
49
|
-
*
|
|
50
|
-
* Split out of `FormoAnalytics` (#336). It holds no wallet state and no
|
|
51
|
-
* provider registry of its own - those have owners already - so what is left
|
|
52
|
-
* here is the part that is genuinely about interpreting wallet events:
|
|
53
|
-
* deciding when a connect has to be reported, when a switch is stale, and
|
|
54
|
-
* when a provider has stopped being the one we follow.
|
|
55
|
-
*/
|
|
56
46
|
export declare class EvmEventTracker {
|
|
57
47
|
private readonly wallet;
|
|
58
48
|
private readonly registry;
|
|
59
49
|
private readonly deps;
|
|
50
|
+
/**
|
|
51
|
+
* Providers adopted through `registerProvider` rather than discovered.
|
|
52
|
+
* Announcement-driven cleanup must not touch them: they are never in an
|
|
53
|
+
* announcement list, so "missing from the announcement" is their normal
|
|
54
|
+
* state, not evidence of removal. A Set rather than a WeakSet because
|
|
55
|
+
* suppressed adoptions retry from it; the registry holds these providers
|
|
56
|
+
* strongly anyway, and untrack removes them.
|
|
57
|
+
*/
|
|
58
|
+
private externallyRegistered;
|
|
60
59
|
/**
|
|
61
60
|
* The connect this SDK has already reported for a provider.
|
|
62
61
|
*
|
|
@@ -91,6 +90,35 @@ export declare class EvmEventTracker {
|
|
|
91
90
|
*/
|
|
92
91
|
trackEIP1193Provider(provider: EIP1193Provider): void;
|
|
93
92
|
trackProviders(providers: readonly EIP6963ProviderDetail[]): void;
|
|
93
|
+
/**
|
|
94
|
+
* Adopt a provider the page constructed rather than announced.
|
|
95
|
+
*
|
|
96
|
+
* Discovery only ever sees EIP-6963 announcements and `window.ethereum`.
|
|
97
|
+
* A WalletConnect or Ledger provider is a constructed object that does
|
|
98
|
+
* neither, so without this entry point its whole session is invisible -
|
|
99
|
+
* the P-2403 gap. The pipeline from here on is the same one every
|
|
100
|
+
* discovered provider takes: registry, detect event, listeners, request
|
|
101
|
+
* wrapper.
|
|
102
|
+
*
|
|
103
|
+
* A session that already exists at registration is seeded from the
|
|
104
|
+
* provider's SYNCHRONOUS `accounts` state (WalletConnect exposes it), via
|
|
105
|
+
* the same accounts-arrival path a live `accountsChanged` takes. No RPC:
|
|
106
|
+
* nothing analytics-only may go on a wallet's transport, and
|
|
107
|
+
* WalletConnect's serialised relay socket is the very case that rule
|
|
108
|
+
* exists for.
|
|
109
|
+
*/
|
|
110
|
+
adoptExternalProvider(detail: EIP6963ProviderDetail): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Re-run session adoption for every registered external provider.
|
|
113
|
+
*
|
|
114
|
+
* Registration while tracking was suppressed (opt-out, excluded route)
|
|
115
|
+
* reached the adoption path and was refused - and a provider whose
|
|
116
|
+
* session already exists may never emit another accountsChanged, so
|
|
117
|
+
* nothing would ever retry. Called when suppression can have ended
|
|
118
|
+
* (opt-in, page navigation). Idempotent: an already-adopted wallet is
|
|
119
|
+
* deduplicated by the same state and markers as any repeated signal.
|
|
120
|
+
*/
|
|
121
|
+
retryExternalAdoptions(): void;
|
|
94
122
|
private registerAccountsChangedListener;
|
|
95
123
|
private onAccountsChanged;
|
|
96
124
|
/**
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __assign = (this && this.__assign) || function () {
|
|
3
|
+
__assign = Object.assign || function(t) {
|
|
4
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
5
|
+
s = arguments[i];
|
|
6
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
7
|
+
t[p] = s[p];
|
|
8
|
+
}
|
|
9
|
+
return t;
|
|
10
|
+
};
|
|
11
|
+
return __assign.apply(this, arguments);
|
|
12
|
+
};
|
|
2
13
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
14
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
15
|
return new (P || (P = Promise))(function (resolve, reject) {
|
|
@@ -35,6 +46,15 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
35
46
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
47
|
}
|
|
37
48
|
};
|
|
49
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
50
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
51
|
+
if (ar || !(i in from)) {
|
|
52
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
53
|
+
ar[i] = from[i];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
57
|
+
};
|
|
38
58
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
59
|
exports.EvmEventTracker = void 0;
|
|
40
60
|
var mipd_1 = require("mipd");
|
|
@@ -58,11 +78,71 @@ var PROVIDER_SWITCH_REASONS = {
|
|
|
58
78
|
* deciding when a connect has to be reported, when a switch is stale, and
|
|
59
79
|
* when a provider has stopped being the one we follow.
|
|
60
80
|
*/
|
|
81
|
+
/**
|
|
82
|
+
* A registered provider's current accounts, from its synchronous state.
|
|
83
|
+
*
|
|
84
|
+
* `provider.accounts` first - but a LIVE MetaMask Mobile session over
|
|
85
|
+
* WalletConnect has been observed with `accounts` EMPTY while the session's
|
|
86
|
+
* namespaces held the approved account ("eip155:11155111:0xabc..."), which
|
|
87
|
+
* silently defeated adoption. The namespaces are the session's ground
|
|
88
|
+
* truth, so they are the fallback. Still purely synchronous property
|
|
89
|
+
* reads; nothing goes on the wallet transport.
|
|
90
|
+
*/
|
|
91
|
+
function readProviderAccounts(provider) {
|
|
92
|
+
var _a;
|
|
93
|
+
var direct = provider.accounts;
|
|
94
|
+
if (Array.isArray(direct) &&
|
|
95
|
+
direct.length > 0 &&
|
|
96
|
+
direct.every(function (a) { return typeof a === "string"; })) {
|
|
97
|
+
return direct;
|
|
98
|
+
}
|
|
99
|
+
var session = provider.session;
|
|
100
|
+
// eip155 ONLY: a session can also carry Solana or other namespaces, and
|
|
101
|
+
// feeding a non-EVM address into the EVM adoption path would make
|
|
102
|
+
// validation reject it and drop the whole adoption. A session can also
|
|
103
|
+
// authorize DIFFERENT accounts per chain, so entries for the provider's
|
|
104
|
+
// active chain come first - the adopted address should be the one this
|
|
105
|
+
// chain actually authorized.
|
|
106
|
+
var ns = (_a = session === null || session === void 0 ? void 0 : session.namespaces) === null || _a === void 0 ? void 0 : _a.eip155;
|
|
107
|
+
var chainId = provider.chainId;
|
|
108
|
+
var parsed = typeof chainId === "number"
|
|
109
|
+
? chainId
|
|
110
|
+
: typeof chainId === "string"
|
|
111
|
+
? (0, chain_1.parseChainId)(chainId)
|
|
112
|
+
: undefined;
|
|
113
|
+
var activePrefix = parsed ? "eip155:".concat(parsed, ":") : undefined;
|
|
114
|
+
var forChain = [];
|
|
115
|
+
var others = [];
|
|
116
|
+
if (Array.isArray(ns === null || ns === void 0 ? void 0 : ns.accounts)) {
|
|
117
|
+
for (var _i = 0, _b = ns.accounts; _i < _b.length; _i++) {
|
|
118
|
+
var entry = _b[_i];
|
|
119
|
+
if (typeof entry !== "string" || !entry.startsWith("eip155:"))
|
|
120
|
+
continue;
|
|
121
|
+
var address = entry.split(":")[2];
|
|
122
|
+
if (!address)
|
|
123
|
+
continue;
|
|
124
|
+
var bucket = activePrefix && entry.startsWith(activePrefix) ? forChain : others;
|
|
125
|
+
if (!bucket.includes(address))
|
|
126
|
+
bucket.push(address);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
var out = __spreadArray(__spreadArray([], forChain, true), others.filter(function (a) { return !forChain.includes(a); }), true);
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
61
132
|
var EvmEventTracker = /** @class */ (function () {
|
|
62
133
|
function EvmEventTracker(wallet, registry, deps) {
|
|
63
134
|
this.wallet = wallet;
|
|
64
135
|
this.registry = registry;
|
|
65
136
|
this.deps = deps;
|
|
137
|
+
/**
|
|
138
|
+
* Providers adopted through `registerProvider` rather than discovered.
|
|
139
|
+
* Announcement-driven cleanup must not touch them: they are never in an
|
|
140
|
+
* announcement list, so "missing from the announcement" is their normal
|
|
141
|
+
* state, not evidence of removal. A Set rather than a WeakSet because
|
|
142
|
+
* suppressed adoptions retry from it; the registry holds these providers
|
|
143
|
+
* strongly anyway, and untrack removes them.
|
|
144
|
+
*/
|
|
145
|
+
this.externallyRegistered = new Set();
|
|
66
146
|
/**
|
|
67
147
|
* The connect this SDK has already reported for a provider.
|
|
68
148
|
*
|
|
@@ -190,6 +270,103 @@ var EvmEventTracker = /** @class */ (function () {
|
|
|
190
270
|
logger_1.logger.error("Failed to track EIP-6963 providers during initialization:", error);
|
|
191
271
|
}
|
|
192
272
|
};
|
|
273
|
+
/**
|
|
274
|
+
* Adopt a provider the page constructed rather than announced.
|
|
275
|
+
*
|
|
276
|
+
* Discovery only ever sees EIP-6963 announcements and `window.ethereum`.
|
|
277
|
+
* A WalletConnect or Ledger provider is a constructed object that does
|
|
278
|
+
* neither, so without this entry point its whole session is invisible -
|
|
279
|
+
* the P-2403 gap. The pipeline from here on is the same one every
|
|
280
|
+
* discovered provider takes: registry, detect event, listeners, request
|
|
281
|
+
* wrapper.
|
|
282
|
+
*
|
|
283
|
+
* A session that already exists at registration is seeded from the
|
|
284
|
+
* provider's SYNCHRONOUS `accounts` state (WalletConnect exposes it), via
|
|
285
|
+
* the same accounts-arrival path a live `accountsChanged` takes. No RPC:
|
|
286
|
+
* nothing analytics-only may go on a wallet's transport, and
|
|
287
|
+
* WalletConnect's serialised relay socket is the very case that rule
|
|
288
|
+
* exists for.
|
|
289
|
+
*/
|
|
290
|
+
EvmEventTracker.prototype.adoptExternalProvider = function (detail) {
|
|
291
|
+
var provider = detail.provider;
|
|
292
|
+
// Exempt from announcement-driven cleanup BEFORE tracking: a registered
|
|
293
|
+
// provider is never in an EIP-6963 announcement, so without this the
|
|
294
|
+
// next wallet announcement would untrack it and its events would stop.
|
|
295
|
+
this.externallyRegistered.add(provider);
|
|
296
|
+
this.registry.add(detail);
|
|
297
|
+
this.trackProviders([detail]);
|
|
298
|
+
// Adoption and success both hinge on the wrapper actually installing:
|
|
299
|
+
// reporting success for a provider whose requests stay invisible would
|
|
300
|
+
// recreate the silent loss this API exists to close.
|
|
301
|
+
if (!this.registry.isTracked(provider)) {
|
|
302
|
+
this.externallyRegistered.delete(provider);
|
|
303
|
+
// Tracking got partway: lifecycle listeners may already be attached
|
|
304
|
+
// even though the request wrapper failed. Leaving them would leak
|
|
305
|
+
// callbacks that hold this instance for the life of the page.
|
|
306
|
+
this.untrackProvider(provider);
|
|
307
|
+
logger_1.logger.warn("adoptExternalProvider: provider could not be tracked");
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
// A provider that was ALREADY tracked skips the pipeline above, and
|
|
311
|
+
// "tracked" means lifecycle listeners - it says nothing about the
|
|
312
|
+
// request wrapper, which a wallet can have replaced since. Re-verify
|
|
313
|
+
// it on every registration: the call reinstalls a displaced wrapper,
|
|
314
|
+
// rebinds ownership of an intact one, and refuses when it cannot -
|
|
315
|
+
// and success here must mean capture actually works.
|
|
316
|
+
if (!this.deps.registerRequestListeners(provider)) {
|
|
317
|
+
this.externallyRegistered.delete(provider);
|
|
318
|
+
this.untrackProvider(provider);
|
|
319
|
+
logger_1.logger.warn("adoptExternalProvider: request wrapper could not be ensured");
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
// Detect with the LIVE name (peer-resolved when a session exists);
|
|
323
|
+
// the stored metadata stays generic so later sessions rename freely.
|
|
324
|
+
void this.detectWallets([
|
|
325
|
+
__assign(__assign({}, detail), { info: __assign(__assign({}, detail.info), this.registry.infoFor(provider)) }),
|
|
326
|
+
]);
|
|
327
|
+
var accounts = readProviderAccounts(provider);
|
|
328
|
+
if (accounts.length > 0) {
|
|
329
|
+
void this.onAccountsChanged(provider, accounts);
|
|
330
|
+
}
|
|
331
|
+
return true;
|
|
332
|
+
};
|
|
333
|
+
/**
|
|
334
|
+
* Re-run session adoption for every registered external provider.
|
|
335
|
+
*
|
|
336
|
+
* Registration while tracking was suppressed (opt-out, excluded route)
|
|
337
|
+
* reached the adoption path and was refused - and a provider whose
|
|
338
|
+
* session already exists may never emit another accountsChanged, so
|
|
339
|
+
* nothing would ever retry. Called when suppression can have ended
|
|
340
|
+
* (opt-in, page navigation). Idempotent: an already-adopted wallet is
|
|
341
|
+
* deduplicated by the same state and markers as any repeated signal.
|
|
342
|
+
*/
|
|
343
|
+
EvmEventTracker.prototype.retryExternalAdoptions = function () {
|
|
344
|
+
var _this = this;
|
|
345
|
+
this.externallyRegistered.forEach(function (provider) {
|
|
346
|
+
// A flagless provider registered BEFORE pairing detected as the
|
|
347
|
+
// generic injected identity; once the session's peer exists, the
|
|
348
|
+
// live identity differs and the corrected detect fires. The
|
|
349
|
+
// session-scoped rdns dedup keeps this from repeating.
|
|
350
|
+
var live = _this.registry.infoFor(provider);
|
|
351
|
+
if (live.rdns === "com.walletconnect") {
|
|
352
|
+
void _this.detectWallets([
|
|
353
|
+
{
|
|
354
|
+
info: {
|
|
355
|
+
name: live.name,
|
|
356
|
+
rdns: live.rdns,
|
|
357
|
+
uuid: "corrected-com-walletconnect",
|
|
358
|
+
icon: provider_1.DEFAULT_PROVIDER_ICON,
|
|
359
|
+
},
|
|
360
|
+
provider: provider,
|
|
361
|
+
},
|
|
362
|
+
]);
|
|
363
|
+
}
|
|
364
|
+
var accounts = readProviderAccounts(provider);
|
|
365
|
+
if (accounts.length > 0) {
|
|
366
|
+
void _this.onAccountsChanged(provider, accounts);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
};
|
|
193
370
|
EvmEventTracker.prototype.registerAccountsChangedListener = function (provider) {
|
|
194
371
|
var _this = this;
|
|
195
372
|
logger_1.logger.info("registerAccountsChangedListener");
|
|
@@ -1004,6 +1181,11 @@ var EvmEventTracker = /** @class */ (function () {
|
|
|
1004
1181
|
var currentProviderInstances = new Set(current.map(function (detail) { return detail.provider; }));
|
|
1005
1182
|
for (var _i = 0, _a = this.registry.trackedProviders(); _i < _a.length; _i++) {
|
|
1006
1183
|
var provider = _a[_i];
|
|
1184
|
+
// A registered external provider is never announced over EIP-6963;
|
|
1185
|
+
// its absence from an announcement list says nothing about it.
|
|
1186
|
+
if (this.externallyRegistered.has(provider)) {
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1007
1189
|
if (!currentProviderInstances.has(provider)) {
|
|
1008
1190
|
logger_1.logger.info("Cleaning up unavailable provider: ".concat(provider.constructor.name));
|
|
1009
1191
|
this.untrackProvider(provider);
|
|
@@ -143,11 +143,33 @@ var EvmProviderRegistry = /** @class */ (function () {
|
|
|
143
143
|
*/
|
|
144
144
|
EvmProviderRegistry.prototype.infoFor = function (provider) {
|
|
145
145
|
var announced = this.details.find(function (p) { return p.provider === provider; });
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
var info = announced
|
|
147
|
+
? { name: announced.info.name, rdns: announced.info.rdns }
|
|
148
|
+
: (function () {
|
|
149
|
+
var injected = (0, provider_1.detectInjectedProviderInfo)(provider);
|
|
150
|
+
return { name: injected.name, rdns: injected.rdns };
|
|
151
|
+
})();
|
|
152
|
+
// WalletConnect names the TRANSPORT; the session's peer names the
|
|
153
|
+
// wallet. Resolved live, per read: a session established after the
|
|
154
|
+
// provider was registered still gets its signer's name onto every
|
|
155
|
+
// event from then on. Only GENERIC names are replaced - a caller who
|
|
156
|
+
// registered an explicit display name keeps it. "Injected Provider"
|
|
157
|
+
// is included because a flagless WalletConnect-compatible provider
|
|
158
|
+
// registered BEFORE its session exists detects as nothing at all;
|
|
159
|
+
// the peer appearing later is itself the proof of what it was, so
|
|
160
|
+
// the rdns upgrades with it.
|
|
161
|
+
if (info.name === "WalletConnect" || info.name === "Injected Provider") {
|
|
162
|
+
var peer = (0, provider_1.readWalletConnectPeer)(provider);
|
|
163
|
+
if (peer === null || peer === void 0 ? void 0 : peer.name) {
|
|
164
|
+
return {
|
|
165
|
+
name: peer.name,
|
|
166
|
+
rdns: info.rdns === "io.injected.provider"
|
|
167
|
+
? "com.walletconnect"
|
|
168
|
+
: info.rdns,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
148
171
|
}
|
|
149
|
-
|
|
150
|
-
return { name: injected.name, rdns: injected.rdns };
|
|
172
|
+
return info;
|
|
151
173
|
};
|
|
152
174
|
// ── listener bookkeeping ─────────────────────────────────────────────────
|
|
153
175
|
EvmProviderRegistry.prototype.addListener = function (provider, event, listener) {
|