@formo/analytics 1.35.2 → 1.37.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 +79 -363
- package/dist/cjs/src/FormoAnalytics.js +368 -2234
- package/dist/cjs/src/event/EventManager.d.ts +3 -1
- package/dist/cjs/src/event/EventManager.js +5 -1
- package/dist/cjs/src/event/type.d.ts +1 -0
- package/dist/cjs/src/evm/EvmEventTracker.d.ts +175 -0
- package/dist/cjs/src/evm/EvmEventTracker.js +1030 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +132 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.js +348 -0
- package/dist/cjs/src/evm/EvmRequestTracker.d.ts +98 -0
- package/dist/cjs/src/evm/EvmRequestTracker.js +713 -0
- package/dist/cjs/src/evm/batch.d.ts +94 -0
- package/dist/cjs/src/evm/batch.js +130 -0
- package/dist/cjs/src/queue/EventQueue.d.ts +28 -0
- package/dist/cjs/src/queue/EventQueue.js +97 -19
- package/dist/cjs/src/queue/type.d.ts +1 -0
- package/dist/cjs/src/tracking/TrackingPolicy.d.ts +146 -0
- package/dist/cjs/src/tracking/TrackingPolicy.js +200 -0
- package/dist/cjs/src/validators/address.d.ts +1 -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 +43 -0
- package/dist/cjs/src/wagmi/WagmiEventHandler.js +253 -2
- package/dist/cjs/src/wallet/WalletStateStore.d.ts +223 -0
- package/dist/cjs/src/wallet/WalletStateStore.js +515 -0
- package/dist/esm/src/FormoAnalytics.d.ts +79 -363
- package/dist/esm/src/FormoAnalytics.js +369 -2235
- package/dist/esm/src/event/EventManager.d.ts +3 -1
- package/dist/esm/src/event/EventManager.js +5 -1
- package/dist/esm/src/event/type.d.ts +1 -0
- package/dist/esm/src/evm/EvmEventTracker.d.ts +175 -0
- package/dist/esm/src/evm/EvmEventTracker.js +1027 -0
- package/dist/esm/src/evm/EvmProviderRegistry.d.ts +132 -0
- package/dist/esm/src/evm/EvmProviderRegistry.js +345 -0
- package/dist/esm/src/evm/EvmRequestTracker.d.ts +98 -0
- package/dist/esm/src/evm/EvmRequestTracker.js +710 -0
- package/dist/esm/src/evm/batch.d.ts +94 -0
- package/dist/esm/src/evm/batch.js +123 -0
- package/dist/esm/src/queue/EventQueue.d.ts +28 -0
- package/dist/esm/src/queue/EventQueue.js +97 -19
- package/dist/esm/src/queue/type.d.ts +1 -0
- package/dist/esm/src/tracking/TrackingPolicy.d.ts +146 -0
- package/dist/esm/src/tracking/TrackingPolicy.js +197 -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 +43 -0
- package/dist/esm/src/wagmi/WagmiEventHandler.js +253 -2
- package/dist/esm/src/wallet/WalletStateStore.d.ts +223 -0
- package/dist/esm/src/wallet/WalletStateStore.js +512 -0
- package/dist/index.umd.min.js +1 -1
- package/package.json +6 -4
|
@@ -45,29 +45,20 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
45
45
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
-
|
|
49
|
-
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
50
|
-
if (ar || !(i in from)) {
|
|
51
|
-
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
52
|
-
ar[i] = from[i];
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return to.concat(ar || Array.prototype.slice.call(from));
|
|
56
|
-
};
|
|
57
|
-
import { createStore } from "mipd";
|
|
58
|
-
import { EVENTS_API_HOST, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_USER_ID_KEY, SESSION_TRAFFIC_SOURCE_KEY, ACTIVE_WALLET_KEY, ACTIVE_WALLET_TTL_MS, CONSENT_OPT_OUT_KEY, } from "./constants";
|
|
48
|
+
import { EVENTS_API_HOST, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_USER_ID_KEY, SESSION_TRAFFIC_SOURCE_KEY, ACTIVE_WALLET_KEY, CONSENT_OPT_OUT_KEY, } from "./constants";
|
|
59
49
|
import { cookie, session, initStorageManager } from "./storage";
|
|
60
50
|
import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "./storage/cookiePolicy";
|
|
61
51
|
import { EventManager } from "./event";
|
|
62
52
|
import { EventQueue } from "./queue";
|
|
63
53
|
import { logger, Logger } from "./logger";
|
|
64
54
|
import { setConsentFlag, getConsentFlag, removeConsentFlag, } from "./consent";
|
|
65
|
-
import { detectInjectedProviderInfo, isValidProvider } from "./provider";
|
|
66
55
|
import { FormoAnalyticsSession, SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY, } from "./session";
|
|
67
|
-
import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "./types";
|
|
68
56
|
import { validateAddress, validateAndChecksumAddress } from "./utils/address";
|
|
69
|
-
import {
|
|
70
|
-
import {
|
|
57
|
+
import { TrackingPolicy, } from "./tracking/TrackingPolicy";
|
|
58
|
+
import { WalletStateStore } from "./wallet/WalletStateStore";
|
|
59
|
+
import { EvmProviderRegistry } from "./evm/EvmProviderRegistry";
|
|
60
|
+
import { EvmEventTracker } from "./evm/EvmEventTracker";
|
|
61
|
+
import { EvmRequestTracker } from "./evm/EvmRequestTracker";
|
|
71
62
|
import { parseChainId } from "./utils/chain";
|
|
72
63
|
import { WagmiEventHandler } from "./wagmi";
|
|
73
64
|
import { isSolanaChainId } from "./solana";
|
|
@@ -77,11 +68,6 @@ import { identifyPrivyUser } from "./privy/utils";
|
|
|
77
68
|
/**
|
|
78
69
|
* Constants for provider switching reasons
|
|
79
70
|
*/
|
|
80
|
-
var PROVIDER_SWITCH_REASONS = {
|
|
81
|
-
ADDRESS_MISMATCH: "Address mismatch indicates wallet switch",
|
|
82
|
-
NO_ACCOUNTS: "Current provider has no accounts",
|
|
83
|
-
CHECK_FAILED: "Could not check current provider accounts",
|
|
84
|
-
};
|
|
85
71
|
var FormoAnalytics = /** @class */ (function () {
|
|
86
72
|
function FormoAnalytics(writeKey, options) {
|
|
87
73
|
if (options === void 0) { options = {}; }
|
|
@@ -89,63 +75,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
89
75
|
var _a, _b, _c;
|
|
90
76
|
this.writeKey = writeKey;
|
|
91
77
|
this.options = options;
|
|
92
|
-
// Per-chain namespace state - isolates EVM and Solana connection state
|
|
93
|
-
this._chainState = {
|
|
94
|
-
evm: {},
|
|
95
|
-
solana: {},
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* Last known chain per tracked provider, fed by `chainChanged` / `connect`
|
|
99
|
-
* and by a one-off probe at tracking time. Read synchronously when labelling
|
|
100
|
-
* an autocaptured event, so the wallet's request transport is never used for
|
|
101
|
-
* analytics. Weak so an untracked provider can be collected.
|
|
102
|
-
*/
|
|
103
|
-
/**
|
|
104
|
-
* The connect this SDK has actually REPORTED for a provider.
|
|
105
|
-
*
|
|
106
|
-
* Deduplicating on "is an address known" was wrong in both directions.
|
|
107
|
-
* `onConnected` and `onAccountsChanged` both observe one connection, so
|
|
108
|
-
* something has to stop them double-reporting - but an address can be
|
|
109
|
-
* present without a connect ever having been sent: restored from the
|
|
110
|
-
* active-wallet cookie, or reported with an unresolved chain and then
|
|
111
|
-
* refused by `tracking.excludeChains`. Suppressing on address presence lost
|
|
112
|
-
* the connect entirely in those cases.
|
|
113
|
-
*
|
|
114
|
-
* Records what was reported, so a later event can be recognised as a
|
|
115
|
-
* genuine improvement on it rather than a duplicate.
|
|
116
|
-
*/
|
|
117
78
|
this._announcedConnect = new WeakMap();
|
|
118
|
-
this._providerChainIds = new WeakMap();
|
|
119
|
-
/**
|
|
120
|
-
* Bumped on every chain observation, PER PROVIDER. An `eth_chainId` answer
|
|
121
|
-
* that resolves after a newer observation for the same provider must not
|
|
122
|
-
* overwrite it.
|
|
123
|
-
*
|
|
124
|
-
* Deliberately per provider, not per SDK instance: a global counter meant
|
|
125
|
-
* any activity on wallet B discarded a perfectly valid in-flight answer for
|
|
126
|
-
* wallet A, leaving A at chain 0 - and, since an unresolved chain fails
|
|
127
|
-
* closed, dropping all of A's events whenever `excludeChains` is set.
|
|
128
|
-
*/
|
|
129
|
-
this._providerChainGenerations = new WeakMap();
|
|
130
|
-
this._providerListenersMap = new Map();
|
|
131
|
-
/**
|
|
132
|
-
* EIP-6963 provider details discovered through the browser
|
|
133
|
-
* This array contains all available providers with their metadata
|
|
134
|
-
*/
|
|
135
|
-
this._providers = [];
|
|
136
|
-
/**
|
|
137
|
-
* Set of providers that have been tracked with event listeners
|
|
138
|
-
* This is separate from _providers because:
|
|
139
|
-
* - _providers contains all discovered providers (EIP-6963)
|
|
140
|
-
* - _trackedProviders contains only providers that have been set up with listeners
|
|
141
|
-
* - A provider can be discovered but not yet tracked (e.g., during initialization)
|
|
142
|
-
* - A provider can be tracked but later removed from discovery
|
|
143
|
-
*/
|
|
144
|
-
this._trackedProviders = new Set();
|
|
145
|
-
// Flag to prevent concurrent processing of accountsChanged events
|
|
146
|
-
this._processingAccountsChanged = false;
|
|
147
|
-
// Set to efficiently track seen providers for deduplication and O(1) lookup
|
|
148
|
-
this._seenProviders = new Set();
|
|
149
79
|
/**
|
|
150
80
|
* Flag indicating if Wagmi mode is enabled
|
|
151
81
|
* When true, EIP-1193 provider wrapping is skipped
|
|
@@ -198,6 +128,68 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
198
128
|
enabled: ((_b = options.logger) === null || _b === void 0 ? void 0 : _b.enabled) || false,
|
|
199
129
|
enabledLevels: ((_c = options.logger) === null || _c === void 0 ? void 0 : _c.levels) || [],
|
|
200
130
|
});
|
|
131
|
+
this.evm = new EvmProviderRegistry({
|
|
132
|
+
activeProvider: function () { return _this.wallet.provider; },
|
|
133
|
+
activeChainId: function () { return _this.wallet.evmChainId; },
|
|
134
|
+
knownEvmAddress: function () { return _this.wallet.evmAddress; },
|
|
135
|
+
// Whether central state should follow a provider's chain report is the
|
|
136
|
+
// SDK's call, not the registry's: it depends on which namespace is
|
|
137
|
+
// active. Recording it per provider is unconditional; syncing is not.
|
|
138
|
+
onChainObserved: function (provider, chainId) {
|
|
139
|
+
// Guarded on EVM already being active. `set()` makes whichever
|
|
140
|
+
// namespace it touches active, so syncing unconditionally let a
|
|
141
|
+
// background EVM wallet's chain report steal the slot from a live
|
|
142
|
+
// Solana wallet, and every later page or track event was attributed
|
|
143
|
+
// to the wrong wallet entirely.
|
|
144
|
+
if (provider === _this.wallet.provider &&
|
|
145
|
+
_this.wallet.activeNamespace === "evm" &&
|
|
146
|
+
_this.wallet.evmChainId !== chainId) {
|
|
147
|
+
_this.wallet.set("evm", { chainId: chainId });
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
});
|
|
151
|
+
this.wallet = new WalletStateStore({
|
|
152
|
+
isPersistedIdentityPurgeRequired: function () {
|
|
153
|
+
return _this.trackingPolicy.isPersistedIdentityPurgeRequired();
|
|
154
|
+
},
|
|
155
|
+
isPageExcluded: function () { return _this.trackingPolicy.isPageExcluded(); },
|
|
156
|
+
isTrackingSuppressed: function () { return _this.trackingPolicy.isTrackingSuppressed(); },
|
|
157
|
+
crossSubdomainCookies: function () { return _this.crossSubdomainCookies; },
|
|
158
|
+
providerChainId: function (provider) { return _this.evm.chainIdOf(provider); },
|
|
159
|
+
// A provider that stops being active has, from this SDK's point of
|
|
160
|
+
// view, ended its connection, so the connect it reported must stop
|
|
161
|
+
// counting. Otherwise toggling between two installed wallets silently
|
|
162
|
+
// loses every connect after the first.
|
|
163
|
+
onProviderDisplaced: function (previous) {
|
|
164
|
+
return _this.evmEvents.forgetAnnouncedConnect(previous);
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
this.evmRequests = new EvmRequestTracker(this.wallet, this.evm, {
|
|
168
|
+
isAutocaptureEnabled: function (t) { return _this.isAutocaptureEnabled(t); },
|
|
169
|
+
signature: function (params, properties) { return _this.signature(params, properties); },
|
|
170
|
+
transaction: function (params, properties) { return _this.transaction(params, properties); },
|
|
171
|
+
});
|
|
172
|
+
this.evmEvents = new EvmEventTracker(this.wallet, this.evm, {
|
|
173
|
+
isAutocaptureEnabled: function (t) { return _this.isAutocaptureEnabled(t); },
|
|
174
|
+
isTrackingSuppressed: function () { return _this.isTrackingSuppressed(); },
|
|
175
|
+
willTrackEvent: function (chainId) { return _this.willTrackEvent(chainId); },
|
|
176
|
+
isWagmiMode: function () { return _this.isWagmiMode; },
|
|
177
|
+
connect: function (params, properties) { return _this.connect(params, properties); },
|
|
178
|
+
disconnect: function (params) { return _this.disconnect(params); },
|
|
179
|
+
chain: function (params, properties) { return _this.chain(params, properties); },
|
|
180
|
+
detect: function (params) { return _this.detect(params); },
|
|
181
|
+
registerRequestListeners: function (provider) {
|
|
182
|
+
return _this.evmRequests.registerRequestListeners(provider);
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
this.trackingPolicy = new TrackingPolicy({
|
|
186
|
+
// All three read lazily. `options` is public and mutable, and the
|
|
187
|
+
// central chain moves as wallets connect and switch, so the policy
|
|
188
|
+
// must see both at decision time rather than at construction.
|
|
189
|
+
options: function () { return _this.options; },
|
|
190
|
+
hasOptedOut: function () { return _this.hasOptedOutTracking(); },
|
|
191
|
+
currentChainId: function () { return _this.currentChainId; },
|
|
192
|
+
});
|
|
201
193
|
this.eventManager = new EventManager(new EventQueue(this.config.writeKey, {
|
|
202
194
|
apiHost: options.apiHost || EVENTS_API_HOST,
|
|
203
195
|
flushAt: options.flushAt,
|
|
@@ -233,7 +225,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
233
225
|
provider = window.ethereum;
|
|
234
226
|
}
|
|
235
227
|
if (provider) {
|
|
236
|
-
this.trackEIP1193Provider(provider);
|
|
228
|
+
this.evmEvents.trackEIP1193Provider(provider);
|
|
237
229
|
}
|
|
238
230
|
}
|
|
239
231
|
// Initialize Solana manager if Solana options are provided
|
|
@@ -244,77 +236,73 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
244
236
|
// Seed currentAddress/currentChainId from the persisted snapshot before
|
|
245
237
|
// the first page hit queues so reload-time track()/page() carry the
|
|
246
238
|
// wallet even before wagmi/EIP-1193 reconnection completes.
|
|
247
|
-
this.
|
|
239
|
+
this.wallet.load();
|
|
248
240
|
this.trackPageHit();
|
|
249
241
|
this.trackPageHits();
|
|
250
242
|
}
|
|
251
243
|
Object.defineProperty(FormoAnalytics.prototype, "_provider", {
|
|
252
|
-
// EVM state accessors - EVM listener paths must use these instead of
|
|
253
|
-
// currentAddress/currentChainId to avoid cross-namespace reads.
|
|
254
244
|
get: function () {
|
|
255
|
-
return this.
|
|
245
|
+
return this.wallet.provider;
|
|
256
246
|
},
|
|
257
247
|
set: function (value) {
|
|
258
|
-
|
|
259
|
-
// A provider that stops being the active one has, from this SDK's point of
|
|
260
|
-
// view, ended its connection - so the connect it reported must stop
|
|
261
|
-
// counting. Otherwise toggling between two installed wallets silently
|
|
262
|
-
// loses every connect after the first: A's stale record suppresses the
|
|
263
|
-
// connect when the user comes back to it.
|
|
264
|
-
//
|
|
265
|
-
// Done in the setter rather than at each switch site because the active
|
|
266
|
-
// provider is reassigned from several paths - `accountsChanged`,
|
|
267
|
-
// `chainChanged`, `connect`, `handleProviderMismatch`, `untrackProvider` -
|
|
268
|
-
// and any one of them missed would reopen the same hole.
|
|
269
|
-
this.forgetConnectRecord(previous, value);
|
|
270
|
-
this._chainState.evm.provider = value;
|
|
248
|
+
this.wallet.provider = value;
|
|
271
249
|
},
|
|
272
250
|
enumerable: false,
|
|
273
251
|
configurable: true
|
|
274
252
|
});
|
|
275
|
-
/**
|
|
276
|
-
* Drop a provider's reported-connect record when it stops being active.
|
|
277
|
-
*
|
|
278
|
-
* The active provider is displaced from several places, and not all of them
|
|
279
|
-
* go through the `_provider` setter: `setChainState()` writes
|
|
280
|
-
* `_chainState.evm.provider` directly and `clearChainState()` replaces the
|
|
281
|
-
* whole namespace. Guarding only the setter left A's record alive across a
|
|
282
|
-
* real A-to-B switch, so returning to A was suppressed.
|
|
283
|
-
*/
|
|
284
|
-
FormoAnalytics.prototype.forgetConnectRecord = function (previous, next) {
|
|
285
|
-
if (previous && previous !== next) {
|
|
286
|
-
this._announcedConnect.delete(previous);
|
|
287
|
-
}
|
|
288
|
-
};
|
|
289
253
|
Object.defineProperty(FormoAnalytics.prototype, "_evmAddress", {
|
|
290
254
|
get: function () {
|
|
291
|
-
return this.
|
|
255
|
+
return this.wallet.evmAddress;
|
|
292
256
|
},
|
|
293
257
|
enumerable: false,
|
|
294
258
|
configurable: true
|
|
295
259
|
});
|
|
296
260
|
Object.defineProperty(FormoAnalytics.prototype, "_evmChainId", {
|
|
297
261
|
get: function () {
|
|
298
|
-
return this.
|
|
262
|
+
return this.wallet.evmChainId;
|
|
263
|
+
},
|
|
264
|
+
enumerable: false,
|
|
265
|
+
configurable: true
|
|
266
|
+
});
|
|
267
|
+
Object.defineProperty(FormoAnalytics.prototype, "currentAddress", {
|
|
268
|
+
/**
|
|
269
|
+
* The wallet later events are attributed to, derived from whichever
|
|
270
|
+
* namespace last claimed the slot. Read by the wagmi and Privy
|
|
271
|
+
* integrations; the store is the only writer.
|
|
272
|
+
*/
|
|
273
|
+
get: function () {
|
|
274
|
+
return this.wallet.address;
|
|
275
|
+
},
|
|
276
|
+
/**
|
|
277
|
+
* These stay writable.
|
|
278
|
+
*
|
|
279
|
+
* Both were public mutable fields, so a consumer assigning one is using a
|
|
280
|
+
* documented public API. Getter-only replacements would break that
|
|
281
|
+
* silently: TypeScript rejects the assignment, and plain JavaScript throws
|
|
282
|
+
* on an accessor with no setter. The writes forward into the store, which
|
|
283
|
+
* keeps a single owner without turning a refactor into a breaking change.
|
|
284
|
+
*/
|
|
285
|
+
set: function (value) {
|
|
286
|
+
this.wallet.setActiveAddress(value);
|
|
287
|
+
},
|
|
288
|
+
enumerable: false,
|
|
289
|
+
configurable: true
|
|
290
|
+
});
|
|
291
|
+
Object.defineProperty(FormoAnalytics.prototype, "currentChainId", {
|
|
292
|
+
get: function () {
|
|
293
|
+
return this.wallet.chainId;
|
|
294
|
+
},
|
|
295
|
+
set: function (value) {
|
|
296
|
+
this.wallet.setActiveChainId(value);
|
|
299
297
|
},
|
|
300
298
|
enumerable: false,
|
|
301
299
|
configurable: true
|
|
302
300
|
});
|
|
303
|
-
/**
|
|
304
|
-
* Helper method to check if a provider is different from the currently active one
|
|
305
|
-
* @param provider The provider to check
|
|
306
|
-
* @returns true if there's a provider mismatch, false otherwise
|
|
307
|
-
*/
|
|
308
|
-
FormoAnalytics.prototype.isProviderMismatch = function (provider) {
|
|
309
|
-
// Only consider it a mismatch if we have an active provider AND the provider is different
|
|
310
|
-
// This allows legitimate provider switching while preventing race conditions
|
|
311
|
-
return this._provider != null && this._provider !== provider;
|
|
312
|
-
};
|
|
313
301
|
FormoAnalytics.init = function (writeKey, options) {
|
|
314
302
|
return __awaiter(this, void 0, void 0, function () {
|
|
315
|
-
var analytics,
|
|
316
|
-
return __generator(this, function (
|
|
317
|
-
switch (
|
|
303
|
+
var analytics, discovered;
|
|
304
|
+
return __generator(this, function (_a) {
|
|
305
|
+
switch (_a.label) {
|
|
318
306
|
case 0:
|
|
319
307
|
initStorageManager(writeKey);
|
|
320
308
|
analytics = new FormoAnalytics(writeKey, options);
|
|
@@ -323,20 +311,17 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
323
311
|
return [3 /*break*/, 5];
|
|
324
312
|
case 1:
|
|
325
313
|
if (!!analytics.isWagmiMode) return [3 /*break*/, 4];
|
|
326
|
-
|
|
327
|
-
_a = analytics;
|
|
328
|
-
return [4 /*yield*/, analytics.getProviders()];
|
|
314
|
+
return [4 /*yield*/, analytics.evmEvents.getProviders()];
|
|
329
315
|
case 2:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
return [4 /*yield*/, analytics.detectWallets(analytics._providers)];
|
|
316
|
+
discovered = _a.sent();
|
|
317
|
+
return [4 /*yield*/, analytics.evmEvents.detectWallets(discovered)];
|
|
333
318
|
case 3:
|
|
334
|
-
|
|
335
|
-
analytics.trackProviders(
|
|
319
|
+
_a.sent();
|
|
320
|
+
analytics.evmEvents.trackProviders(discovered);
|
|
336
321
|
return [3 /*break*/, 5];
|
|
337
322
|
case 4:
|
|
338
323
|
logger.info("FormoAnalytics: Skipping provider detection (Wagmi mode)");
|
|
339
|
-
|
|
324
|
+
_a.label = 5;
|
|
340
325
|
case 5: return [2 /*return*/, analytics];
|
|
341
326
|
}
|
|
342
327
|
});
|
|
@@ -377,11 +362,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
377
362
|
// address on subsequent track()/page() events for the rest of the
|
|
378
363
|
// page lifetime, because they fall back to currentAddress. Keep the
|
|
379
364
|
// EVM provider reference so tracking can resume on the next connect.
|
|
380
|
-
this.
|
|
381
|
-
this.currentChainId = undefined;
|
|
382
|
-
var evmProvider = this._chainState.evm.provider;
|
|
383
|
-
this._chainState = { evm: { provider: evmProvider }, solana: {} };
|
|
384
|
-
this._activeNamespace = undefined;
|
|
365
|
+
this.wallet.reset();
|
|
385
366
|
cookie().remove(LOCAL_ANONYMOUS_ID_KEY);
|
|
386
367
|
cookie().remove(SESSION_USER_ID_KEY);
|
|
387
368
|
cookie().remove(SESSION_WALLET_DETECTED_KEY);
|
|
@@ -399,8 +380,19 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
399
380
|
*/
|
|
400
381
|
FormoAnalytics.prototype.cleanup = function () {
|
|
401
382
|
logger.debug("FormoAnalytics: Cleaning up resources");
|
|
402
|
-
//
|
|
403
|
-
this
|
|
383
|
+
// Close the queue, don't just empty it. clear() only drops what is
|
|
384
|
+
// buffered at this instant; asynchronous work already in flight (event
|
|
385
|
+
// creation is async on every emit path) would still enqueue afterwards,
|
|
386
|
+
// and an empty queue flushes immediately. close() is terminal, so a
|
|
387
|
+
// continuation that outlives this instance cannot send with its stale
|
|
388
|
+
// options. See issue #339.
|
|
389
|
+
this.eventManager.close();
|
|
390
|
+
// Stop any receipt or batch-status polling: a torn-down instance must
|
|
391
|
+
// not keep asking a wallet about transactions nobody is listening for.
|
|
392
|
+
this.evmRequests.cleanup();
|
|
393
|
+
// Stop reacting to wallet announcements: a disposed instance must not
|
|
394
|
+
// wrap new providers or emit detect events.
|
|
395
|
+
this.evmEvents.cleanup();
|
|
404
396
|
// Clean up Wagmi handler if present
|
|
405
397
|
if (this.wagmiHandler) {
|
|
406
398
|
this.wagmiHandler.cleanup();
|
|
@@ -413,9 +405,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
413
405
|
}
|
|
414
406
|
// Clean up EIP-1193 providers if not in Wagmi mode
|
|
415
407
|
if (!this.isWagmiMode) {
|
|
416
|
-
for (var _i = 0, _a =
|
|
408
|
+
for (var _i = 0, _a = this.evm.trackedProviders(); _i < _a.length; _i++) {
|
|
417
409
|
var provider = _a[_i];
|
|
418
|
-
this.untrackProvider(provider);
|
|
410
|
+
this.evmEvents.untrackProvider(provider);
|
|
419
411
|
}
|
|
420
412
|
}
|
|
421
413
|
// Tear down page-hit hooks: remove window listeners and silence the
|
|
@@ -472,6 +464,11 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
472
464
|
logger.info("connect() skipped: tracking is suppressed for this visitor or environment");
|
|
473
465
|
return [2 /*return*/];
|
|
474
466
|
}
|
|
467
|
+
// A new wallet now owns this namespace. Anything already tearing down
|
|
468
|
+
// the previous session must not undo this. An emitted connect always
|
|
469
|
+
// counts, even when the address is the one already there: a wallet that
|
|
470
|
+
// disconnects and reconnects leaves identical state.
|
|
471
|
+
this.wallet.observe(this.wallet.namespaceOf(chainId));
|
|
475
472
|
this.setChainState(chainId, { address: validAddress });
|
|
476
473
|
return [4 /*yield*/, this.trackEvent(EventType.CONNECT, {
|
|
477
474
|
chainId: chainId,
|
|
@@ -495,7 +492,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
495
492
|
*/
|
|
496
493
|
FormoAnalytics.prototype.disconnect = function (params, properties, context, callback) {
|
|
497
494
|
return __awaiter(this, void 0, void 0, function () {
|
|
498
|
-
var chainId, address, isSolana, providerInfo, disconnectProperties;
|
|
495
|
+
var chainId, address, isSolana, providerInfo, disconnectProperties, namespace, before;
|
|
499
496
|
return __generator(this, function (_a) {
|
|
500
497
|
switch (_a.label) {
|
|
501
498
|
case 0:
|
|
@@ -515,9 +512,25 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
515
512
|
providerName: providerInfo.name,
|
|
516
513
|
rdns: providerInfo.rdns,
|
|
517
514
|
})), properties);
|
|
515
|
+
namespace = this.wallet.namespaceOf(chainId);
|
|
516
|
+
// Every teardown announces itself here, before anything is awaited,
|
|
517
|
+
// whichever path reached it - a provider event or a direct call from the
|
|
518
|
+
// host app. A connect observation that began earlier is invalidated; one
|
|
519
|
+
// that begins after this point is a genuine reconnect and still reports.
|
|
520
|
+
this.wallet.beginDisconnect(namespace);
|
|
521
|
+
before = this.wallet.snapshot(namespace);
|
|
518
522
|
return [4 /*yield*/, this.trackEvent(EventType.DISCONNECT, __assign(__assign({}, (chainId && { chainId: chainId })), (address && { address: address })), disconnectProperties, context, callback)];
|
|
519
523
|
case 1:
|
|
520
524
|
_a.sent();
|
|
525
|
+
// If a wallet claimed this namespace while the event was being built, a
|
|
526
|
+
// new session owns it and this cleanup is stale. Running it anyway would
|
|
527
|
+
// wipe that session's state AND its reported-connect record, so a later
|
|
528
|
+
// wallet signal would emit a second connect for a connection that is
|
|
529
|
+
// already reported. See issue #344.
|
|
530
|
+
if (!this.wallet.isUnchangedSince(namespace, before)) {
|
|
531
|
+
logger.info("Disconnect: A new session claimed this namespace while the event was in flight; leaving its state intact");
|
|
532
|
+
return [2 /*return*/];
|
|
533
|
+
}
|
|
521
534
|
// Clear the disconnecting chain's namespace state.
|
|
522
535
|
// Per-chain isolation ensures a Solana disconnect never wipes EVM state (and vice versa).
|
|
523
536
|
this.clearChainState(chainId);
|
|
@@ -527,6 +540,78 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
527
540
|
});
|
|
528
541
|
});
|
|
529
542
|
};
|
|
543
|
+
/** @see WalletStateStore.set */
|
|
544
|
+
FormoAnalytics.prototype.setChainState = function (namespaceOrChainId, update) {
|
|
545
|
+
this.wallet.set(namespaceOrChainId, update);
|
|
546
|
+
};
|
|
547
|
+
/** @see WalletStateStore.clear */
|
|
548
|
+
FormoAnalytics.prototype.clearChainState = function (namespaceOrChainId) {
|
|
549
|
+
this.wallet.clear(namespaceOrChainId);
|
|
550
|
+
};
|
|
551
|
+
/**
|
|
552
|
+
* Record validated wallet/chain state WITHOUT emitting an event.
|
|
553
|
+
*
|
|
554
|
+
* Integrations must call this on every connect / chain change / disconnect,
|
|
555
|
+
* even when the matching autocapture event is disabled, or the exclusion
|
|
556
|
+
* gate (which keys off the central chain, not the event payload) can be
|
|
557
|
+
* bypassed. Stays on the class because integrations bind to it.
|
|
558
|
+
* @see WalletStateStore.syncWalletState
|
|
559
|
+
*/
|
|
560
|
+
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
561
|
+
this.wallet.syncWalletState(params);
|
|
562
|
+
};
|
|
563
|
+
/** @see WalletStateStore.clearProvider */
|
|
564
|
+
FormoAnalytics.prototype.clearActiveProvider = function () {
|
|
565
|
+
this.wallet.clearProvider();
|
|
566
|
+
};
|
|
567
|
+
/** @see WalletStateStore.backfill */
|
|
568
|
+
FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId, provider) {
|
|
569
|
+
this.wallet.backfill(address, chainId, provider);
|
|
570
|
+
};
|
|
571
|
+
/** @see WalletStateStore.clearStaleEvmWalletOnSwitchWhileSuppressed */
|
|
572
|
+
FormoAnalytics.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
|
|
573
|
+
this.wallet.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
574
|
+
};
|
|
575
|
+
/** @see EvmProviderRegistry.addListener */
|
|
576
|
+
FormoAnalytics.prototype.addProviderListener = function (provider, event, listener) {
|
|
577
|
+
this.evm.addListener(provider, event, listener);
|
|
578
|
+
};
|
|
579
|
+
/** @see EvmProviderRegistry.removeListeners */
|
|
580
|
+
FormoAnalytics.prototype.removeProviderListeners = function (provider) {
|
|
581
|
+
this.evm.removeListeners(provider);
|
|
582
|
+
};
|
|
583
|
+
/** @see EvmProviderRegistry.infoFor */
|
|
584
|
+
FormoAnalytics.prototype.getProviderInfo = function (provider) {
|
|
585
|
+
return this.evm.infoFor(provider);
|
|
586
|
+
};
|
|
587
|
+
/** @see EvmProviderRegistry.isWrapped */
|
|
588
|
+
FormoAnalytics.prototype.isProviderAlreadyWrapped = function (provider, currentRequest) {
|
|
589
|
+
return this.evm.isWrapped(provider, currentRequest);
|
|
590
|
+
};
|
|
591
|
+
/** @see EvmProviderRegistry.resolveChainId */
|
|
592
|
+
FormoAnalytics.prototype.resolveChainIdForProvider = function (provider) {
|
|
593
|
+
return this.evm.resolveChainId(provider);
|
|
594
|
+
};
|
|
595
|
+
/** @see EvmProviderRegistry.rememberChain */
|
|
596
|
+
FormoAnalytics.prototype.rememberProviderChain = function (provider, chainId) {
|
|
597
|
+
this.evm.rememberChain(provider, chainId);
|
|
598
|
+
};
|
|
599
|
+
/** @see EvmProviderRegistry.addressOf */
|
|
600
|
+
FormoAnalytics.prototype.getAddress = function (provider) {
|
|
601
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
602
|
+
return __generator(this, function (_a) {
|
|
603
|
+
return [2 /*return*/, this.evm.addressOf(provider)];
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
};
|
|
607
|
+
/** @see EvmProviderRegistry.accountsOf */
|
|
608
|
+
FormoAnalytics.prototype.getAccounts = function (provider) {
|
|
609
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
610
|
+
return __generator(this, function (_a) {
|
|
611
|
+
return [2 /*return*/, this.evm.accountsOf(provider)];
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
};
|
|
530
615
|
/**
|
|
531
616
|
* Emits a chain network change event.
|
|
532
617
|
* @param {ChainID} params.chainId
|
|
@@ -668,14 +753,14 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
668
753
|
// valid (activating a Solana wallet while an excluded EVM chain id is
|
|
669
754
|
// still current). By the time the Privy path reaches this guard it has
|
|
670
755
|
// already reconciled, so each inner identify is judged on the right chain.
|
|
671
|
-
if (this.isTrackingSuppressed() || this.
|
|
756
|
+
if (this.isTrackingSuppressed() || this.trackingPolicy.isChainExcluded()) {
|
|
672
757
|
logger.info("identify() skipped: tracking is suppressed for this visitor, environment, or chain");
|
|
673
758
|
return [2 /*return*/];
|
|
674
759
|
}
|
|
675
760
|
if (!!params) return [3 /*break*/, 12];
|
|
676
761
|
// If no params provided, auto-identify
|
|
677
|
-
logger.info("Auto-identifying with providers:", this.
|
|
678
|
-
_i = 0, _a = this.
|
|
762
|
+
logger.info("Auto-identifying with providers:", this.evm.all.map(function (p) { return p.info.name; }));
|
|
763
|
+
_i = 0, _a = this.evm.all;
|
|
679
764
|
_d.label = 3;
|
|
680
765
|
case 3:
|
|
681
766
|
if (!(_i < _a.length)) return [3 /*break*/, 11];
|
|
@@ -747,8 +832,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
747
832
|
// attribution. Gating address and userId together prevents leaving the
|
|
748
833
|
// active address paired with a different wallet's user id.
|
|
749
834
|
if (setActive !== false) {
|
|
750
|
-
this.
|
|
751
|
-
this.persistActiveWallet();
|
|
835
|
+
this.wallet.setActiveAddress(validAddress);
|
|
752
836
|
if (userId) {
|
|
753
837
|
this.currentUserId = userId;
|
|
754
838
|
domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
@@ -835,8 +919,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
835
919
|
return;
|
|
836
920
|
var currentIsSolana = isSolanaChainId(this.currentChainId);
|
|
837
921
|
if (walletIsSolana !== currentIsSolana) {
|
|
838
|
-
this.
|
|
839
|
-
this.persistActiveWallet();
|
|
922
|
+
this.wallet.setActiveChainId(undefined);
|
|
840
923
|
}
|
|
841
924
|
};
|
|
842
925
|
/**
|
|
@@ -952,1256 +1035,161 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
952
1035
|
/*
|
|
953
1036
|
SDK tracking and event listener functions
|
|
954
1037
|
*/
|
|
1038
|
+
FormoAnalytics.prototype.onLocationChange = function () {
|
|
1039
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1040
|
+
var _a;
|
|
1041
|
+
return __generator(this, function (_b) {
|
|
1042
|
+
if (this._currentUrl !== window.location.href) {
|
|
1043
|
+
this._currentUrl = window.location.href;
|
|
1044
|
+
// Host/path exclusions are evaluated per navigation, so a SPA can leave
|
|
1045
|
+
// an excluded route and become trackable without any wallet event
|
|
1046
|
+
// firing. The wagmi handler only observes *changes*, so an unchanged
|
|
1047
|
+
// connection it was forced to decline earlier would stay invisible for
|
|
1048
|
+
// the rest of the page load. Give it a chance to adopt it now.
|
|
1049
|
+
(_a = this.wagmiHandler) === null || _a === void 0 ? void 0 : _a.retryAdoption();
|
|
1050
|
+
this.trackPageHit();
|
|
1051
|
+
}
|
|
1052
|
+
return [2 /*return*/];
|
|
1053
|
+
});
|
|
1054
|
+
});
|
|
1055
|
+
};
|
|
1056
|
+
FormoAnalytics.prototype.trackPageHits = function () {
|
|
1057
|
+
var _this = this;
|
|
1058
|
+
// Install a single, instance-agnostic wrapper around history.pushState /
|
|
1059
|
+
// replaceState so concurrent SDK instances (React Strict Mode, HMR) don't
|
|
1060
|
+
// each stack their own wrapper - which would dispatch N synthetic events
|
|
1061
|
+
// per navigation and produce O(N^2) onLocationChange calls. The wrapper
|
|
1062
|
+
// dispatches once; per-instance bookkeeping is done by per-instance
|
|
1063
|
+
// listeners that each register/unregister themselves.
|
|
1064
|
+
FormoAnalytics.installHistoryHooksOnce();
|
|
1065
|
+
this._onPopStateListener = function () { return _this.onLocationChange(); };
|
|
1066
|
+
this._onLocationChangeListener = function () { return _this.onLocationChange(); };
|
|
1067
|
+
window.addEventListener("popstate", this._onPopStateListener);
|
|
1068
|
+
window.addEventListener("locationchange", this._onLocationChangeListener);
|
|
1069
|
+
};
|
|
955
1070
|
/**
|
|
956
|
-
*
|
|
957
|
-
*
|
|
958
|
-
*
|
|
959
|
-
* @param provider The EIP-1193 provider to track
|
|
1071
|
+
* Wrap history.pushState / replaceState exactly once per `history` object,
|
|
1072
|
+
* regardless of how many SDK instances are constructed. Uses a Symbol
|
|
1073
|
+
* marker so we recognize our own wrapper across module reloads in HMR.
|
|
960
1074
|
*/
|
|
961
|
-
FormoAnalytics.
|
|
962
|
-
|
|
963
|
-
// Defensive check: Skip provider tracking in Wagmi mode
|
|
964
|
-
// This should never be called in Wagmi mode due to guards in init(),
|
|
965
|
-
// but we check here for safety in case of future code changes
|
|
966
|
-
if (this.isWagmiMode) {
|
|
967
|
-
logger.debug("trackEIP1193Provider: Skipping EIP-1193 provider tracking (Wagmi mode - using connector system instead)");
|
|
1075
|
+
FormoAnalytics.installHistoryHooksOnce = function () {
|
|
1076
|
+
if (typeof history === "undefined" || typeof window === "undefined")
|
|
968
1077
|
return;
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
}
|
|
980
|
-
// CRITICAL: Always register accountsChanged for state management
|
|
981
|
-
// This ensures currentAddress, currentChainId, and _provider are always up-to-date
|
|
982
|
-
// Event emission is controlled conditionally inside the handlers
|
|
983
|
-
this.registerAccountsChangedListener(provider);
|
|
984
|
-
// `chainChanged` and `connect` are registered UNCONDITIONALLY: they are
|
|
985
|
-
// how this provider's chain is observed, and every signature and
|
|
986
|
-
// transaction has to be labelled with it. Gating registration on
|
|
987
|
-
// `autocapture.chain` conflated observing a chain with reporting one, so
|
|
988
|
-
// `{ chain: false, signature: true }` left the chain frozen at whatever
|
|
989
|
-
// was first seen - a switch to an excluded chain went unnoticed and its
|
|
990
|
-
// signatures were emitted under the old, allowed chain. Whether an
|
|
991
|
-
// event is emitted is decided inside each handler.
|
|
992
|
-
this.registerChainChangedListener(provider);
|
|
993
|
-
if (this.isAutocaptureEnabled("connect")) {
|
|
994
|
-
this.registerConnectListener(provider);
|
|
995
|
-
}
|
|
996
|
-
else {
|
|
997
|
-
// Observation only. The full connect handler calls `getAddress()`,
|
|
998
|
-
// which issues `eth_accounts`, and nothing analytics-only may go on
|
|
999
|
-
// the wallet's transport - a stalled request there sits in front of
|
|
1000
|
-
// the next signature the dapp makes. The chain rides along on the
|
|
1001
|
-
// event itself, so it costs nothing to record.
|
|
1002
|
-
this.registerConnectChainObserver(provider);
|
|
1003
|
-
}
|
|
1004
|
-
// Seed the chain from the provider's own synchronous state if it exposes
|
|
1005
|
-
// one. Deliberately a property read and never an RPC: see
|
|
1006
|
-
// resolveChainIdForProvider for why nothing analytics-only may go on the
|
|
1007
|
-
// wallet's transport.
|
|
1008
|
-
this.seedProviderChainFromState(provider);
|
|
1009
|
-
if (this.isAutocaptureEnabled("signature") || this.isAutocaptureEnabled("transaction")) {
|
|
1010
|
-
this.registerRequestListeners(provider);
|
|
1011
|
-
}
|
|
1012
|
-
else {
|
|
1013
|
-
logger.debug("TrackProvider: Skipping request wrapping (both signature and transaction autocapture disabled)");
|
|
1014
|
-
}
|
|
1015
|
-
// Registered UNCONDITIONALLY: this listener also ends the provider's
|
|
1016
|
-
// reported-connect record, and with `{ connect: true, disconnect: false }`
|
|
1017
|
-
// a wallet that disconnected and reconnected would otherwise find its
|
|
1018
|
-
// old record still standing and have the new connect suppressed. Whether
|
|
1019
|
-
// a disconnect EVENT is emitted is decided inside the handler.
|
|
1020
|
-
{
|
|
1021
|
-
this.registerDisconnectListener(provider);
|
|
1022
|
-
}
|
|
1023
|
-
// Only add to tracked providers after all listeners are successfully registered
|
|
1024
|
-
this._trackedProviders.add(provider);
|
|
1025
|
-
}
|
|
1026
|
-
catch (error) {
|
|
1027
|
-
logger.error("Error tracking provider:", error);
|
|
1028
|
-
}
|
|
1029
|
-
};
|
|
1030
|
-
FormoAnalytics.prototype.trackProviders = function (providers) {
|
|
1031
|
-
try {
|
|
1032
|
-
for (var _i = 0, providers_1 = providers; _i < providers_1.length; _i++) {
|
|
1033
|
-
var eip6963ProviderDetail = providers_1[_i];
|
|
1034
|
-
var provider = eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.provider;
|
|
1035
|
-
if (provider && !this._trackedProviders.has(provider)) {
|
|
1036
|
-
this.trackEIP1193Provider(provider);
|
|
1037
|
-
}
|
|
1078
|
+
var marker = Symbol.for("formo.historyWrapped");
|
|
1079
|
+
if (history[marker])
|
|
1080
|
+
return;
|
|
1081
|
+
history[marker] = true;
|
|
1082
|
+
var dispatch = function () { return window.dispatchEvent(new window.Event("locationchange")); };
|
|
1083
|
+
var oldPushState = history.pushState;
|
|
1084
|
+
history.pushState = function pushState() {
|
|
1085
|
+
var args = [];
|
|
1086
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1087
|
+
args[_i] = arguments[_i];
|
|
1038
1088
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
var map = this._providerListenersMap.get(provider) || {};
|
|
1046
|
-
map[event] = listener;
|
|
1047
|
-
this._providerListenersMap.set(provider, map);
|
|
1048
|
-
};
|
|
1049
|
-
FormoAnalytics.prototype.registerAccountsChangedListener = function (provider) {
|
|
1050
|
-
var _this = this;
|
|
1051
|
-
logger.info("registerAccountsChangedListener");
|
|
1052
|
-
var listener = function () {
|
|
1089
|
+
var ret = oldPushState.apply(this, args);
|
|
1090
|
+
dispatch();
|
|
1091
|
+
return ret;
|
|
1092
|
+
};
|
|
1093
|
+
var oldReplaceState = history.replaceState;
|
|
1094
|
+
history.replaceState = function replaceState() {
|
|
1053
1095
|
var args = [];
|
|
1054
1096
|
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1055
1097
|
args[_i] = arguments[_i];
|
|
1056
1098
|
}
|
|
1057
|
-
|
|
1099
|
+
var ret = oldReplaceState.apply(this, args);
|
|
1100
|
+
dispatch();
|
|
1101
|
+
return ret;
|
|
1058
1102
|
};
|
|
1059
|
-
provider.on("accountsChanged", listener);
|
|
1060
|
-
this.addProviderListener(provider, "accountsChanged", listener);
|
|
1061
1103
|
};
|
|
1062
|
-
FormoAnalytics.prototype.
|
|
1104
|
+
FormoAnalytics.prototype.trackPageHit = function (category, name, properties, context, callback) {
|
|
1105
|
+
return __awaiter(this, void 0, void 0, function () {
|
|
1106
|
+
var _this = this;
|
|
1107
|
+
return __generator(this, function (_a) {
|
|
1108
|
+
if (!this.shouldTrack()) {
|
|
1109
|
+
logger.info("Track page hit: Skipping event due to tracking configuration");
|
|
1110
|
+
return [2 /*return*/];
|
|
1111
|
+
}
|
|
1112
|
+
setTimeout(function () {
|
|
1113
|
+
// Drop in-flight page hits from an SDK instance that was torn down
|
|
1114
|
+
// between scheduling and firing (e.g. provider remount in React Strict
|
|
1115
|
+
// Mode / HMR). Otherwise the orphan instance would queue a page event
|
|
1116
|
+
// here with its stale, never-populated `currentAddress`.
|
|
1117
|
+
if (_this._pageHooksDisposed)
|
|
1118
|
+
return;
|
|
1119
|
+
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1120
|
+
var e_2;
|
|
1121
|
+
return __generator(this, function (_a) {
|
|
1122
|
+
switch (_a.label) {
|
|
1123
|
+
case 0:
|
|
1124
|
+
_a.trys.push([0, 2, , 3]);
|
|
1125
|
+
return [4 /*yield*/, this.trackEvent(EventType.PAGE, {
|
|
1126
|
+
category: category,
|
|
1127
|
+
name: name,
|
|
1128
|
+
}, properties, context, callback)];
|
|
1129
|
+
case 1:
|
|
1130
|
+
_a.sent();
|
|
1131
|
+
return [3 /*break*/, 3];
|
|
1132
|
+
case 2:
|
|
1133
|
+
e_2 = _a.sent();
|
|
1134
|
+
logger.error("Formo: Failed to track page hit", e_2);
|
|
1135
|
+
return [3 /*break*/, 3];
|
|
1136
|
+
case 3: return [2 /*return*/];
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
}); })();
|
|
1140
|
+
}, 300);
|
|
1141
|
+
return [2 /*return*/];
|
|
1142
|
+
});
|
|
1143
|
+
});
|
|
1144
|
+
};
|
|
1145
|
+
FormoAnalytics.prototype.trackEvent = function (type, payload, properties, context, callback) {
|
|
1063
1146
|
return __awaiter(this, void 0, void 0, function () {
|
|
1147
|
+
var error_1;
|
|
1064
1148
|
return __generator(this, function (_a) {
|
|
1065
1149
|
switch (_a.label) {
|
|
1066
1150
|
case 0:
|
|
1067
|
-
|
|
1068
|
-
//
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1151
|
+
_a.trys.push([0, 2, , 3]);
|
|
1152
|
+
// Gate on the chain the event actually carries. `connect`, `disconnect`,
|
|
1153
|
+
// `chain`, `signature` and `transaction` all put one in the payload, and
|
|
1154
|
+
// it is authoritative: it can name a provider or a wagmi mutation chain
|
|
1155
|
+
// that is not the active one. Events without a chain (page, track,
|
|
1156
|
+
// identify) fall back to the central value.
|
|
1157
|
+
if (!this.shouldTrack(payload === null || payload === void 0 ? void 0 : payload.chainId)) {
|
|
1158
|
+
logger.info("Skipping ".concat(type, " event due to tracking configuration"));
|
|
1073
1159
|
return [2 /*return*/];
|
|
1074
1160
|
}
|
|
1075
|
-
this.
|
|
1076
|
-
_a.label = 1;
|
|
1161
|
+
return [4 /*yield*/, this.eventManager.addEvent(__assign(__assign({ type: type }, payload), { properties: properties, context: context, callback: callback }), this.currentAddress, this.currentUserId)];
|
|
1077
1162
|
case 1:
|
|
1078
|
-
_a.trys.push([1, , 3, 4]);
|
|
1079
|
-
return [4 /*yield*/, this._handleAccountsChanged(provider, accounts)];
|
|
1080
|
-
case 2:
|
|
1081
1163
|
_a.sent();
|
|
1082
|
-
return [3 /*break*/,
|
|
1083
|
-
case
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1164
|
+
return [3 /*break*/, 3];
|
|
1165
|
+
case 2:
|
|
1166
|
+
error_1 = _a.sent();
|
|
1167
|
+
logger.error("Error tracking event:", error_1);
|
|
1168
|
+
return [3 /*break*/, 3];
|
|
1169
|
+
case 3: return [2 /*return*/];
|
|
1087
1170
|
}
|
|
1088
1171
|
});
|
|
1089
1172
|
});
|
|
1090
1173
|
};
|
|
1091
1174
|
/**
|
|
1092
|
-
*
|
|
1175
|
+
* Visitor-level tracking suppression.
|
|
1093
1176
|
*
|
|
1094
|
-
*
|
|
1095
|
-
*
|
|
1096
|
-
*
|
|
1177
|
+
* True when the SDK must not persist identity/session/chain state or send
|
|
1178
|
+
* events for this visitor. Public entry points that write state before
|
|
1179
|
+
* reaching the `shouldTrack()` gate (identify / connect / detect) check this
|
|
1180
|
+
* first, so a suppressed visitor leaves no cookies or session state.
|
|
1097
1181
|
*
|
|
1098
|
-
*
|
|
1099
|
-
*
|
|
1182
|
+
* The rule lives in `TrackingPolicy`; this stays on the class because it is
|
|
1183
|
+
* part of the surface integrations bind to.
|
|
1184
|
+
* @internal Also read by `identifyPrivyUser` (via a structural cast) so the
|
|
1185
|
+
* Privy sync skips chain reconciliation and emission for suppressed visitors.
|
|
1100
1186
|
*/
|
|
1101
|
-
FormoAnalytics.prototype.
|
|
1102
|
-
return
|
|
1103
|
-
var error_1, address, currentStoredAddress, newProviderAddress, activeProviderAccounts, error_2, nextChainId, wasDisconnected, providerInfo, effectiveChainId;
|
|
1104
|
-
return __generator(this, function (_a) {
|
|
1105
|
-
switch (_a.label) {
|
|
1106
|
-
case 0:
|
|
1107
|
-
if (!(accounts.length === 0)) return [3 /*break*/, 9];
|
|
1108
|
-
if (!(this._provider === provider)) return [3 /*break*/, 7];
|
|
1109
|
-
logger.info("OnAccountsChanged: Detecting disconnect, current state:", {
|
|
1110
|
-
evmAddress: this._evmAddress,
|
|
1111
|
-
evmChainId: this._evmChainId,
|
|
1112
|
-
providerMatch: this._provider === provider,
|
|
1113
|
-
});
|
|
1114
|
-
// The reported connect ends with the connection, so a genuine
|
|
1115
|
-
// reconnect later reports again rather than being taken for a
|
|
1116
|
-
// duplicate.
|
|
1117
|
-
this._announcedConnect.delete(provider);
|
|
1118
|
-
if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
|
|
1119
|
-
_a.label = 1;
|
|
1120
|
-
case 1:
|
|
1121
|
-
_a.trys.push([1, 3, , 4]);
|
|
1122
|
-
// Pass EVM state explicitly to ensure we have the data for the disconnect event
|
|
1123
|
-
return [4 /*yield*/, this.disconnect({
|
|
1124
|
-
chainId: this._evmChainId,
|
|
1125
|
-
address: this._evmAddress,
|
|
1126
|
-
})];
|
|
1127
|
-
case 2:
|
|
1128
|
-
// Pass EVM state explicitly to ensure we have the data for the disconnect event
|
|
1129
|
-
_a.sent();
|
|
1130
|
-
return [3 /*break*/, 4];
|
|
1131
|
-
case 3:
|
|
1132
|
-
error_1 = _a.sent();
|
|
1133
|
-
logger.error("Failed to disconnect provider on accountsChanged", error_1);
|
|
1134
|
-
return [3 /*break*/, 4];
|
|
1135
|
-
case 4: return [3 /*break*/, 6];
|
|
1136
|
-
case 5:
|
|
1137
|
-
logger.debug("OnAccountsChanged: Disconnect event skipped (autocapture.disconnect: false)");
|
|
1138
|
-
// Still clear state even if not tracking the event
|
|
1139
|
-
this.clearChainState('evm');
|
|
1140
|
-
_a.label = 6;
|
|
1141
|
-
case 6: return [3 /*break*/, 8];
|
|
1142
|
-
case 7:
|
|
1143
|
-
logger.info("OnAccountsChanged: Ignoring disconnect for non-active provider");
|
|
1144
|
-
_a.label = 8;
|
|
1145
|
-
case 8: return [2 /*return*/];
|
|
1146
|
-
case 9:
|
|
1147
|
-
address = validateAndChecksumAddress(accounts[0]);
|
|
1148
|
-
if (!address) {
|
|
1149
|
-
logger.warn("onAccountsChanged: Invalid address received", accounts[0]);
|
|
1150
|
-
return [2 /*return*/];
|
|
1151
|
-
}
|
|
1152
|
-
if (!(this._provider && this._provider !== provider)) return [3 /*break*/, 25];
|
|
1153
|
-
currentStoredAddress = this._evmAddress;
|
|
1154
|
-
newProviderAddress = validateAndChecksumAddress(address);
|
|
1155
|
-
logger.info("OnAccountsChanged: Different provider attempting to connect", {
|
|
1156
|
-
activeProvider: this.getProviderInfo(this._provider).name,
|
|
1157
|
-
eventProvider: this.getProviderInfo(provider).name,
|
|
1158
|
-
currentStoredAddress: currentStoredAddress,
|
|
1159
|
-
newProviderAddress: newProviderAddress,
|
|
1160
|
-
});
|
|
1161
|
-
_a.label = 10;
|
|
1162
|
-
case 10:
|
|
1163
|
-
_a.trys.push([10, 21, , 25]);
|
|
1164
|
-
return [4 /*yield*/, this.getAccounts(this._provider)];
|
|
1165
|
-
case 11:
|
|
1166
|
-
activeProviderAccounts = _a.sent();
|
|
1167
|
-
logger.info("OnAccountsChanged: Checking current provider accounts", {
|
|
1168
|
-
activeProvider: this.getProviderInfo(this._provider).name,
|
|
1169
|
-
accountsLength: activeProviderAccounts
|
|
1170
|
-
? activeProviderAccounts.length
|
|
1171
|
-
: 0,
|
|
1172
|
-
accounts: activeProviderAccounts,
|
|
1173
|
-
});
|
|
1174
|
-
if (!(activeProviderAccounts && activeProviderAccounts.length > 0)) return [3 /*break*/, 17];
|
|
1175
|
-
if (!(newProviderAddress &&
|
|
1176
|
-
currentStoredAddress &&
|
|
1177
|
-
newProviderAddress !== currentStoredAddress)) return [3 /*break*/, 15];
|
|
1178
|
-
logger.info("OnAccountsChanged: Different address detected, switching providers despite current provider having accounts", {
|
|
1179
|
-
activeProvider: this.getProviderInfo(this._provider).name,
|
|
1180
|
-
eventProvider: this.getProviderInfo(provider).name,
|
|
1181
|
-
currentAddress: currentStoredAddress,
|
|
1182
|
-
newAddress: newProviderAddress,
|
|
1183
|
-
reason: PROVIDER_SWITCH_REASONS.ADDRESS_MISMATCH,
|
|
1184
|
-
});
|
|
1185
|
-
if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 13];
|
|
1186
|
-
return [4 /*yield*/, this.disconnect({
|
|
1187
|
-
chainId: this._evmChainId,
|
|
1188
|
-
address: this._evmAddress,
|
|
1189
|
-
})];
|
|
1190
|
-
case 12:
|
|
1191
|
-
_a.sent();
|
|
1192
|
-
return [3 /*break*/, 14];
|
|
1193
|
-
case 13:
|
|
1194
|
-
logger.debug("OnAccountsChanged: Disconnect event skipped during provider switch (autocapture.disconnect: false)");
|
|
1195
|
-
// Still clear state even if not tracking the event
|
|
1196
|
-
this.clearChainState('evm');
|
|
1197
|
-
_a.label = 14;
|
|
1198
|
-
case 14:
|
|
1199
|
-
// Clear state and let the new provider become active
|
|
1200
|
-
this.clearActiveProvider();
|
|
1201
|
-
return [3 /*break*/, 16];
|
|
1202
|
-
case 15:
|
|
1203
|
-
logger.info("OnAccountsChanged: Current provider still has accounts and same address, ignoring new provider", {
|
|
1204
|
-
activeProvider: this.getProviderInfo(this._provider).name,
|
|
1205
|
-
eventProvider: this.getProviderInfo(provider).name,
|
|
1206
|
-
activeProviderAccountsCount: activeProviderAccounts.length,
|
|
1207
|
-
currentAddress: currentStoredAddress,
|
|
1208
|
-
newAddress: newProviderAddress,
|
|
1209
|
-
});
|
|
1210
|
-
return [2 /*return*/];
|
|
1211
|
-
case 16: return [3 /*break*/, 20];
|
|
1212
|
-
case 17:
|
|
1213
|
-
logger.info("OnAccountsChanged: Current provider has no accounts, switching to new provider", {
|
|
1214
|
-
oldProvider: this.getProviderInfo(this._provider).name,
|
|
1215
|
-
newProvider: this.getProviderInfo(provider).name,
|
|
1216
|
-
reason: PROVIDER_SWITCH_REASONS.NO_ACCOUNTS,
|
|
1217
|
-
});
|
|
1218
|
-
if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 19];
|
|
1219
|
-
return [4 /*yield*/, this.disconnect({
|
|
1220
|
-
chainId: this._evmChainId,
|
|
1221
|
-
address: this._evmAddress,
|
|
1222
|
-
})];
|
|
1223
|
-
case 18:
|
|
1224
|
-
_a.sent();
|
|
1225
|
-
return [3 /*break*/, 20];
|
|
1226
|
-
case 19:
|
|
1227
|
-
logger.debug("OnAccountsChanged: Disconnect event skipped for old provider (autocapture.disconnect: false)");
|
|
1228
|
-
// Still clear state even if not tracking the event
|
|
1229
|
-
this.clearChainState('evm');
|
|
1230
|
-
_a.label = 20;
|
|
1231
|
-
case 20: return [3 /*break*/, 25];
|
|
1232
|
-
case 21:
|
|
1233
|
-
error_2 = _a.sent();
|
|
1234
|
-
logger.warn("OnAccountsChanged: Could not check current provider accounts, switching to new provider", {
|
|
1235
|
-
error: error_2 instanceof Error ? error_2.message : String(error_2),
|
|
1236
|
-
errorType: error_2 instanceof Error ? error_2.constructor.name : typeof error_2,
|
|
1237
|
-
oldProvider: this._provider
|
|
1238
|
-
? this.getProviderInfo(this._provider).name
|
|
1239
|
-
: "unknown",
|
|
1240
|
-
newProvider: this.getProviderInfo(provider).name,
|
|
1241
|
-
reason: PROVIDER_SWITCH_REASONS.CHECK_FAILED,
|
|
1242
|
-
});
|
|
1243
|
-
if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 23];
|
|
1244
|
-
return [4 /*yield*/, this.disconnect({
|
|
1245
|
-
chainId: this._evmChainId,
|
|
1246
|
-
address: this._evmAddress,
|
|
1247
|
-
})];
|
|
1248
|
-
case 22:
|
|
1249
|
-
_a.sent();
|
|
1250
|
-
return [3 /*break*/, 24];
|
|
1251
|
-
case 23:
|
|
1252
|
-
logger.debug("OnAccountsChanged: Disconnect event skipped for failed provider check (autocapture.disconnect: false)");
|
|
1253
|
-
// Still clear state even if not tracking the event
|
|
1254
|
-
this.clearChainState('evm');
|
|
1255
|
-
_a.label = 24;
|
|
1256
|
-
case 24: return [3 /*break*/, 25];
|
|
1257
|
-
case 25:
|
|
1258
|
-
// Set provider if none exists (first connection)
|
|
1259
|
-
if (!this._provider) {
|
|
1260
|
-
this._provider = provider;
|
|
1261
|
-
}
|
|
1262
|
-
// If both the provider and address are the same, no-op
|
|
1263
|
-
if (this._provider === provider && address === this._evmAddress) {
|
|
1264
|
-
return [2 /*return*/];
|
|
1265
|
-
}
|
|
1266
|
-
nextChainId = this.resolveChainIdForProvider(provider);
|
|
1267
|
-
wasDisconnected = !this._evmAddress;
|
|
1268
|
-
// Update state regardless of whether connect *event* tracking is enabled,
|
|
1269
|
-
// so disconnect events keep valid address/chainId values. (excludeChains is
|
|
1270
|
-
// NOT suppression - it still updates state so currentChainId can gate
|
|
1271
|
-
// events.)
|
|
1272
|
-
if (this.isTrackingSuppressed()) {
|
|
1273
|
-
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
1274
|
-
}
|
|
1275
|
-
else {
|
|
1276
|
-
this.setChainState('evm', { address: address, chainId: nextChainId });
|
|
1277
|
-
}
|
|
1278
|
-
providerInfo = this.getProviderInfo(provider);
|
|
1279
|
-
effectiveChainId = nextChainId || 0;
|
|
1280
|
-
if (this.isAutocaptureEnabled("connect") &&
|
|
1281
|
-
this.shouldReportConnect(provider, address)) {
|
|
1282
|
-
logger.info("OnAccountsChanged: Detected wallet connection, emitting connect event", {
|
|
1283
|
-
chainId: nextChainId,
|
|
1284
|
-
address: address,
|
|
1285
|
-
wasDisconnected: wasDisconnected,
|
|
1286
|
-
providerName: providerInfo.name,
|
|
1287
|
-
rdns: providerInfo.rdns,
|
|
1288
|
-
hasChainId: !!nextChainId,
|
|
1289
|
-
});
|
|
1290
|
-
if (effectiveChainId === 0) {
|
|
1291
|
-
logger.info("OnAccountsChanged: Using fallback chainId 0 for connect event");
|
|
1292
|
-
}
|
|
1293
|
-
this.markConnectReported(provider, address, effectiveChainId);
|
|
1294
|
-
this.connect({
|
|
1295
|
-
chainId: effectiveChainId,
|
|
1296
|
-
address: address,
|
|
1297
|
-
}, {
|
|
1298
|
-
providerName: providerInfo.name,
|
|
1299
|
-
rdns: providerInfo.rdns,
|
|
1300
|
-
}).catch(function (error) {
|
|
1301
|
-
logger.error("Failed to track connect event during account change:", error);
|
|
1302
|
-
});
|
|
1303
|
-
}
|
|
1304
|
-
else {
|
|
1305
|
-
logger.debug("OnAccountsChanged: Connect event skipped (autocapture.connect: false)", {
|
|
1306
|
-
chainId: nextChainId,
|
|
1307
|
-
address: address,
|
|
1308
|
-
providerName: providerInfo.name,
|
|
1309
|
-
});
|
|
1310
|
-
}
|
|
1311
|
-
return [2 /*return*/];
|
|
1312
|
-
}
|
|
1313
|
-
});
|
|
1314
|
-
});
|
|
1315
|
-
};
|
|
1316
|
-
FormoAnalytics.prototype.registerChainChangedListener = function (provider) {
|
|
1317
|
-
var _this = this;
|
|
1318
|
-
logger.info("registerChainChangedListener");
|
|
1319
|
-
var listener = function () {
|
|
1320
|
-
var args = [];
|
|
1321
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1322
|
-
args[_i] = arguments[_i];
|
|
1323
|
-
}
|
|
1324
|
-
return _this.onChainChanged(provider, args[0]);
|
|
1325
|
-
};
|
|
1326
|
-
provider.on("chainChanged", listener);
|
|
1327
|
-
this.addProviderListener(provider, "chainChanged", listener);
|
|
1328
|
-
};
|
|
1329
|
-
FormoAnalytics.prototype.onChainChanged = function (provider, chainIdHex) {
|
|
1330
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1331
|
-
var nextChainId, error_3;
|
|
1332
|
-
return __generator(this, function (_a) {
|
|
1333
|
-
switch (_a.label) {
|
|
1334
|
-
case 0:
|
|
1335
|
-
logger.info("onChainChanged", chainIdHex);
|
|
1336
|
-
nextChainId = parseChainId(chainIdHex);
|
|
1337
|
-
// Record it for THIS provider regardless of which one is active. This is
|
|
1338
|
-
// the only way an autocaptured event from a non-active wallet can learn
|
|
1339
|
-
// its chain without putting an RPC on that wallet's transport.
|
|
1340
|
-
this.rememberProviderChain(provider, nextChainId);
|
|
1341
|
-
// Beyond that, a chain event from a NON-active provider is observation
|
|
1342
|
-
// only when chain autocapture is off.
|
|
1343
|
-
//
|
|
1344
|
-
// This listener is now registered unconditionally, so that a signature can
|
|
1345
|
-
// be labelled with its signer's chain. `handleProviderMismatch()` treats a
|
|
1346
|
-
// chain event from another wallet as a wallet switch and clears the active
|
|
1347
|
-
// wallet's address and chain. That is the established behaviour of the
|
|
1348
|
-
// chain feature and stays exactly as it was, but it must not start firing
|
|
1349
|
-
// for apps that never asked for chain tracking: a second wallet switching
|
|
1350
|
-
// network would silently erase the active wallet's attribution.
|
|
1351
|
-
// Observation only when chain autocapture is off, whether or not an
|
|
1352
|
-
// active provider has been established yet.
|
|
1353
|
-
//
|
|
1354
|
-
// `isProviderMismatch()` is false while `_provider` is undefined, which is
|
|
1355
|
-
// exactly the state left by restoring a wallet from the active-wallet
|
|
1356
|
-
// cookie. A background wallet's `chainChanged` could therefore claim the
|
|
1357
|
-
// active slot and overwrite the restored wallet's chain - suppressing
|
|
1358
|
-
// allowed events, or letting excluded ones through. The active provider is
|
|
1359
|
-
// established by an actual account/connect/request association, not by
|
|
1360
|
-
// another wallet changing network.
|
|
1361
|
-
if (!this.isAutocaptureEnabled("chain") && provider !== this._provider) {
|
|
1362
|
-
return [2 /*return*/];
|
|
1363
|
-
}
|
|
1364
|
-
// Only handle chain changes for the active provider (or if none is set yet)
|
|
1365
|
-
if (this.isProviderMismatch(provider)) {
|
|
1366
|
-
this.handleProviderMismatch(provider);
|
|
1367
|
-
}
|
|
1368
|
-
// Chain changes only matter for connected users
|
|
1369
|
-
if (!this._evmAddress) {
|
|
1370
|
-
logger.info("OnChainChanged: No current address, user appears disconnected");
|
|
1371
|
-
return [2 /*return*/, Promise.resolve()];
|
|
1372
|
-
}
|
|
1373
|
-
// Set provider if none exists
|
|
1374
|
-
if (!this._provider) {
|
|
1375
|
-
this._provider = provider;
|
|
1376
|
-
}
|
|
1377
|
-
this.setChainState('evm', { chainId: nextChainId });
|
|
1378
|
-
_a.label = 1;
|
|
1379
|
-
case 1:
|
|
1380
|
-
_a.trys.push([1, 5, , 6]);
|
|
1381
|
-
if (!this.isAutocaptureEnabled("chain")) return [3 /*break*/, 3];
|
|
1382
|
-
// Awaited, so a failing emission is caught below rather than escaping
|
|
1383
|
-
// as an unhandled rejection out of the provider's event listener.
|
|
1384
|
-
// `return`ing the promise left the catch here unreachable.
|
|
1385
|
-
return [4 /*yield*/, this.chain({
|
|
1386
|
-
chainId: nextChainId,
|
|
1387
|
-
address: this._evmAddress,
|
|
1388
|
-
})];
|
|
1389
|
-
case 2:
|
|
1390
|
-
// Awaited, so a failing emission is caught below rather than escaping
|
|
1391
|
-
// as an unhandled rejection out of the provider's event listener.
|
|
1392
|
-
// `return`ing the promise left the catch here unreachable.
|
|
1393
|
-
_a.sent();
|
|
1394
|
-
return [3 /*break*/, 4];
|
|
1395
|
-
case 3:
|
|
1396
|
-
logger.debug("OnChainChanged: Chain event skipped (autocapture.chain: false)", {
|
|
1397
|
-
chainId: this._evmChainId,
|
|
1398
|
-
address: this._evmAddress,
|
|
1399
|
-
});
|
|
1400
|
-
_a.label = 4;
|
|
1401
|
-
case 4: return [3 /*break*/, 6];
|
|
1402
|
-
case 5:
|
|
1403
|
-
error_3 = _a.sent();
|
|
1404
|
-
logger.error("OnChainChanged: Failed to emit chain event:", error_3);
|
|
1405
|
-
return [3 /*break*/, 6];
|
|
1406
|
-
case 6: return [2 /*return*/];
|
|
1407
|
-
}
|
|
1408
|
-
});
|
|
1409
|
-
});
|
|
1410
|
-
};
|
|
1411
|
-
/**
|
|
1412
|
-
* Record a provider's chain from its `connect` event, and nothing else.
|
|
1413
|
-
*
|
|
1414
|
-
* Used when connect autocapture is off. `connect` carries `chainId` in its
|
|
1415
|
-
* payload, so this needs no RPC - unlike the full handler, which resolves
|
|
1416
|
-
* the account.
|
|
1417
|
-
*/
|
|
1418
|
-
FormoAnalytics.prototype.registerConnectChainObserver = function (provider) {
|
|
1419
|
-
var _this = this;
|
|
1420
|
-
var listener = function () {
|
|
1421
|
-
var args = [];
|
|
1422
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1423
|
-
args[_i] = arguments[_i];
|
|
1424
|
-
}
|
|
1425
|
-
var connection = args[0];
|
|
1426
|
-
if (typeof (connection === null || connection === void 0 ? void 0 : connection.chainId) !== "string")
|
|
1427
|
-
return;
|
|
1428
|
-
_this.rememberProviderChain(provider, parseChainId(connection.chainId));
|
|
1429
|
-
};
|
|
1430
|
-
provider.on("connect", listener);
|
|
1431
|
-
this.addProviderListener(provider, "connect", listener);
|
|
1432
|
-
};
|
|
1433
|
-
/**
|
|
1434
|
-
* Whether a connect for this wallet still needs reporting.
|
|
1435
|
-
*
|
|
1436
|
-
* True when nothing has been reported for this provider, or when the account
|
|
1437
|
-
* changed. A wallet already reported is not reported again.
|
|
1438
|
-
*
|
|
1439
|
-
* Deliberately does NOT re-report to correct a chain. When `accountsChanged`
|
|
1440
|
-
* wins the race on a provider that exposes no synchronous `chainId`, the
|
|
1441
|
-
* connect carries 0 - honestly, since the chain is unknown at that instant -
|
|
1442
|
-
* and the `connect` payload that follows knows the real one. Emitting again
|
|
1443
|
-
* to relabel would mean two connects for one connection, which is the bug
|
|
1444
|
-
* this whole path exists to prevent. That payload still corrects
|
|
1445
|
-
* `currentChainId`, so everything after it is attributed properly.
|
|
1446
|
-
*/
|
|
1447
|
-
FormoAnalytics.prototype.shouldReportConnect = function (provider, address) {
|
|
1448
|
-
var reported = this._announcedConnect.get(provider);
|
|
1449
|
-
if (!reported)
|
|
1450
|
-
return true;
|
|
1451
|
-
return reported.address.toLowerCase() !== address.toLowerCase();
|
|
1452
|
-
};
|
|
1453
|
-
/**
|
|
1454
|
-
* Record a connect as reported - but only if it will actually be sent.
|
|
1455
|
-
*
|
|
1456
|
-
* `connect()` passes through `shouldTrack()`, which refuses an unresolvable
|
|
1457
|
-
* chain when `tracking.excludeChains` is configured. Marking a refused event
|
|
1458
|
-
* as reported would suppress the authoritative one that follows.
|
|
1459
|
-
*/
|
|
1460
|
-
FormoAnalytics.prototype.markConnectReported = function (provider, address, chainId) {
|
|
1461
|
-
if (!this.willTrackEvent(chainId))
|
|
1462
|
-
return;
|
|
1463
|
-
this._announcedConnect.set(provider, { address: address, chainId: chainId });
|
|
1464
|
-
};
|
|
1465
|
-
FormoAnalytics.prototype.registerConnectListener = function (provider) {
|
|
1466
|
-
var _this = this;
|
|
1467
|
-
logger.info("registerConnectListener");
|
|
1468
|
-
var listener = function () {
|
|
1469
|
-
var args = [];
|
|
1470
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1471
|
-
args[_i] = arguments[_i];
|
|
1472
|
-
}
|
|
1473
|
-
var connection = args[0];
|
|
1474
|
-
_this.onConnected(provider, connection);
|
|
1475
|
-
};
|
|
1476
|
-
provider.on("connect", listener);
|
|
1477
|
-
this.addProviderListener(provider, "connect", listener);
|
|
1478
|
-
};
|
|
1479
|
-
FormoAnalytics.prototype.registerDisconnectListener = function (provider) {
|
|
1480
|
-
var _this = this;
|
|
1481
|
-
logger.info("registerDisconnectListener");
|
|
1482
|
-
var listener = function (_error) { return __awaiter(_this, void 0, void 0, function () {
|
|
1483
|
-
var e_2;
|
|
1484
|
-
return __generator(this, function (_a) {
|
|
1485
|
-
switch (_a.label) {
|
|
1486
|
-
case 0:
|
|
1487
|
-
if (this._provider !== provider)
|
|
1488
|
-
return [2 /*return*/];
|
|
1489
|
-
// As in the accountsChanged disconnect path: the reported connect ends
|
|
1490
|
-
// with the connection.
|
|
1491
|
-
this._announcedConnect.delete(provider);
|
|
1492
|
-
logger.info("OnDisconnect: Wallet disconnect event received, current state:", {
|
|
1493
|
-
currentAddress: this._evmAddress,
|
|
1494
|
-
currentChainId: this._evmChainId,
|
|
1495
|
-
});
|
|
1496
|
-
if (!this.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 5];
|
|
1497
|
-
_a.label = 1;
|
|
1498
|
-
case 1:
|
|
1499
|
-
_a.trys.push([1, 3, , 4]);
|
|
1500
|
-
// Pass current state explicitly to ensure we have the data for the disconnect event
|
|
1501
|
-
return [4 /*yield*/, this.disconnect({
|
|
1502
|
-
chainId: this._evmChainId,
|
|
1503
|
-
address: this._evmAddress,
|
|
1504
|
-
})];
|
|
1505
|
-
case 2:
|
|
1506
|
-
// Pass current state explicitly to ensure we have the data for the disconnect event
|
|
1507
|
-
_a.sent();
|
|
1508
|
-
return [3 /*break*/, 4];
|
|
1509
|
-
case 3:
|
|
1510
|
-
e_2 = _a.sent();
|
|
1511
|
-
logger.error("Error during disconnect in disconnect listener", e_2);
|
|
1512
|
-
return [3 /*break*/, 4];
|
|
1513
|
-
case 4: return [3 /*break*/, 6];
|
|
1514
|
-
case 5:
|
|
1515
|
-
logger.debug("OnDisconnect: Disconnect event skipped (autocapture.disconnect: false)");
|
|
1516
|
-
// Still clear state even if not tracking the event
|
|
1517
|
-
this.clearChainState('evm');
|
|
1518
|
-
_a.label = 6;
|
|
1519
|
-
case 6: return [2 /*return*/];
|
|
1520
|
-
}
|
|
1521
|
-
});
|
|
1522
|
-
}); };
|
|
1523
|
-
provider.on("disconnect", listener);
|
|
1524
|
-
this.addProviderListener(provider, "disconnect", listener);
|
|
1525
|
-
};
|
|
1526
|
-
FormoAnalytics.prototype.onConnected = function (provider, connection) {
|
|
1527
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1528
|
-
var chainId, address, wasDisconnected, isActiveProvider, providerInfo, effectiveChainId, providerInfo, e_3;
|
|
1529
|
-
return __generator(this, function (_a) {
|
|
1530
|
-
switch (_a.label) {
|
|
1531
|
-
case 0:
|
|
1532
|
-
logger.info("onConnected", connection);
|
|
1533
|
-
_a.label = 1;
|
|
1534
|
-
case 1:
|
|
1535
|
-
_a.trys.push([1, 3, , 4]);
|
|
1536
|
-
if (!(connection === null || connection === void 0 ? void 0 : connection.chainId) || typeof connection.chainId !== "string")
|
|
1537
|
-
return [2 /*return*/];
|
|
1538
|
-
chainId = parseChainId(connection.chainId);
|
|
1539
|
-
// Record it for this provider before anything can bail out below.
|
|
1540
|
-
this.rememberProviderChain(provider, chainId);
|
|
1541
|
-
return [4 /*yield*/, this.getAddress(provider)];
|
|
1542
|
-
case 2:
|
|
1543
|
-
address = _a.sent();
|
|
1544
|
-
if (chainId && address) {
|
|
1545
|
-
wasDisconnected = !this._evmAddress;
|
|
1546
|
-
// Set provider if none exists
|
|
1547
|
-
if (!this._provider) {
|
|
1548
|
-
this._provider = provider;
|
|
1549
|
-
}
|
|
1550
|
-
isActiveProvider = this._provider === provider;
|
|
1551
|
-
// Update state from active provider so disconnect events keep valid
|
|
1552
|
-
// address/chainId values - except while suppressed, where we must not
|
|
1553
|
-
// LEARN identity (only drop a stale EVM wallet on a switch).
|
|
1554
|
-
if (isActiveProvider) {
|
|
1555
|
-
if (this.isTrackingSuppressed()) {
|
|
1556
|
-
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
1557
|
-
}
|
|
1558
|
-
else {
|
|
1559
|
-
this.setChainState('evm', {
|
|
1560
|
-
chainId: chainId,
|
|
1561
|
-
address: validateAndChecksumAddress(address) || undefined,
|
|
1562
|
-
});
|
|
1563
|
-
}
|
|
1564
|
-
}
|
|
1565
|
-
// Conditionally emit connect event based on tracking configuration.
|
|
1566
|
-
//
|
|
1567
|
-
// Both handlers observe one connection, so `shouldReportConnect()`
|
|
1568
|
-
// decides which of them reports it. It keys on what was actually
|
|
1569
|
-
// REPORTED, not on whether an address is known: an address can be
|
|
1570
|
-
// present with no connect ever sent - restored from the active-wallet
|
|
1571
|
-
// cookie, or reported with an unresolved chain and then refused by
|
|
1572
|
-
// `excludeChains` - and this payload carries the authoritative chain,
|
|
1573
|
-
// so it must be able to supersede such a report.
|
|
1574
|
-
if (isActiveProvider &&
|
|
1575
|
-
this._evmAddress &&
|
|
1576
|
-
this.shouldReportConnect(provider, address)) {
|
|
1577
|
-
providerInfo = this.getProviderInfo(provider);
|
|
1578
|
-
effectiveChainId = chainId || 0;
|
|
1579
|
-
if (this.isAutocaptureEnabled("connect")) {
|
|
1580
|
-
logger.info("OnConnected: Detected wallet connection, emitting connect event", {
|
|
1581
|
-
chainId: chainId,
|
|
1582
|
-
wasDisconnected: wasDisconnected,
|
|
1583
|
-
providerName: providerInfo.name,
|
|
1584
|
-
rdns: providerInfo.rdns,
|
|
1585
|
-
hasChainId: !!chainId,
|
|
1586
|
-
isActiveProvider: isActiveProvider,
|
|
1587
|
-
});
|
|
1588
|
-
if (effectiveChainId === 0) {
|
|
1589
|
-
logger.info("OnConnected: Using fallback chainId 0 for connect event");
|
|
1590
|
-
}
|
|
1591
|
-
this.markConnectReported(provider, address, effectiveChainId);
|
|
1592
|
-
this.connect({
|
|
1593
|
-
chainId: effectiveChainId,
|
|
1594
|
-
address: address,
|
|
1595
|
-
}, {
|
|
1596
|
-
providerName: providerInfo.name,
|
|
1597
|
-
rdns: providerInfo.rdns,
|
|
1598
|
-
}).catch(function (error) {
|
|
1599
|
-
logger.error("Failed to track connect event during provider connection:", error);
|
|
1600
|
-
});
|
|
1601
|
-
}
|
|
1602
|
-
else {
|
|
1603
|
-
logger.debug("OnConnected: Connect event skipped (autocapture.connect: false)", {
|
|
1604
|
-
chainId: chainId,
|
|
1605
|
-
address: address,
|
|
1606
|
-
providerName: providerInfo.name,
|
|
1607
|
-
});
|
|
1608
|
-
}
|
|
1609
|
-
}
|
|
1610
|
-
else if (address && !isActiveProvider) {
|
|
1611
|
-
providerInfo = this.getProviderInfo(provider);
|
|
1612
|
-
logger.debug("OnConnected: Skipping connect event for non-active provider", {
|
|
1613
|
-
chainId: chainId,
|
|
1614
|
-
providerName: providerInfo.name,
|
|
1615
|
-
rdns: providerInfo.rdns,
|
|
1616
|
-
isActiveProvider: isActiveProvider,
|
|
1617
|
-
activeProviderInfo: this._provider
|
|
1618
|
-
? this.getProviderInfo(this._provider)
|
|
1619
|
-
: null,
|
|
1620
|
-
});
|
|
1621
|
-
}
|
|
1622
|
-
}
|
|
1623
|
-
return [3 /*break*/, 4];
|
|
1624
|
-
case 3:
|
|
1625
|
-
e_3 = _a.sent();
|
|
1626
|
-
logger.error("Error handling connect event", e_3);
|
|
1627
|
-
return [3 /*break*/, 4];
|
|
1628
|
-
case 4: return [2 /*return*/];
|
|
1629
|
-
}
|
|
1630
|
-
});
|
|
1631
|
-
});
|
|
1632
|
-
};
|
|
1633
|
-
FormoAnalytics.prototype.registerRequestListeners = function (provider) {
|
|
1634
|
-
var _this = this;
|
|
1635
|
-
logger.info("registerRequestListeners");
|
|
1636
|
-
if (!provider) {
|
|
1637
|
-
logger.error("Provider not found for request (signature, transaction) tracking");
|
|
1638
|
-
return;
|
|
1639
|
-
}
|
|
1640
|
-
// Check if the provider is already wrapped with our SDK's wrapper
|
|
1641
|
-
var currentRequest = provider.request;
|
|
1642
|
-
if (this.isProviderAlreadyWrapped(provider, currentRequest)) {
|
|
1643
|
-
logger.info("Provider already wrapped with our SDK; skipping request wrapping.");
|
|
1644
|
-
return;
|
|
1645
|
-
}
|
|
1646
|
-
var request = provider.request.bind(provider);
|
|
1647
|
-
var wrappedRequest = function (_a) { return __awaiter(_this, [_a], void 0, function (_b) {
|
|
1648
|
-
var generation_1, responsePromise, capturedChainId_1, response_1, error_4, rpcError, txPromise, txChainId_1, transactionHash_1, error_5, rpcError;
|
|
1649
|
-
var _this = this;
|
|
1650
|
-
var _c;
|
|
1651
|
-
var method = _b.method, params = _b.params;
|
|
1652
|
-
return __generator(this, function (_d) {
|
|
1653
|
-
switch (_d.label) {
|
|
1654
|
-
case 0:
|
|
1655
|
-
// Learn the chain from a call the APP was making anyway.
|
|
1656
|
-
//
|
|
1657
|
-
// A standards-compliant provider need not expose a synchronous `chainId`
|
|
1658
|
-
// property, and if it connected before the SDK initialised, its
|
|
1659
|
-
// `connect` event is never replayed. Such a provider stayed unknown -
|
|
1660
|
-
// reported as chain 0, and with `excludeChains` configured its events
|
|
1661
|
-
// were dropped even on an allowed chain. This adds no request of its
|
|
1662
|
-
// own; it only reads the answer to one the dapp already sent.
|
|
1663
|
-
if (method === "eth_chainId") {
|
|
1664
|
-
generation_1 = (_c = this._providerChainGenerations.get(provider)) !== null && _c !== void 0 ? _c : 0;
|
|
1665
|
-
return [2 /*return*/, request({ method: method, params: params }).then(function (result) {
|
|
1666
|
-
var _a;
|
|
1667
|
-
// A `chainChanged` for THIS provider may have landed while this was
|
|
1668
|
-
// in flight. It is newer by definition, so it must not be
|
|
1669
|
-
// overwritten by this answer.
|
|
1670
|
-
if (generation_1 === ((_a = _this._providerChainGenerations.get(provider)) !== null && _a !== void 0 ? _a : 0) &&
|
|
1671
|
-
typeof result === "string") {
|
|
1672
|
-
_this.rememberProviderChain(provider, parseChainId(result));
|
|
1673
|
-
}
|
|
1674
|
-
return result;
|
|
1675
|
-
})];
|
|
1676
|
-
}
|
|
1677
|
-
if (!(Array.isArray(params) &&
|
|
1678
|
-
["eth_signTypedData_v4", "personal_sign"].includes(method))) return [3 /*break*/, 4];
|
|
1679
|
-
if (!this.isAutocaptureEnabled("signature")) {
|
|
1680
|
-
logger.debug("Signature event skipped (autocapture.signature: false)", { method: method });
|
|
1681
|
-
return [2 /*return*/, request({ method: method, params: params })];
|
|
1682
|
-
}
|
|
1683
|
-
responsePromise = request({ method: method, params: params });
|
|
1684
|
-
// Attach a no-op handler now so a rejection arriving before the await
|
|
1685
|
-
// below is never reported as unhandled. The real handling is there.
|
|
1686
|
-
responsePromise.catch(function () { return undefined; });
|
|
1687
|
-
capturedChainId_1 = this.resolveChainIdForProvider(provider);
|
|
1688
|
-
// Fire-and-forget tracking
|
|
1689
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1690
|
-
var e_4;
|
|
1691
|
-
return __generator(this, function (_a) {
|
|
1692
|
-
switch (_a.label) {
|
|
1693
|
-
case 0:
|
|
1694
|
-
_a.trys.push([0, 2, , 3]);
|
|
1695
|
-
return [4 /*yield*/, this.signature(__assign({ status: SignatureStatus.REQUESTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
|
|
1696
|
-
case 1:
|
|
1697
|
-
_a.sent();
|
|
1698
|
-
return [3 /*break*/, 3];
|
|
1699
|
-
case 2:
|
|
1700
|
-
e_4 = _a.sent();
|
|
1701
|
-
logger.error("Formo: Failed to track signature request", e_4);
|
|
1702
|
-
return [3 /*break*/, 3];
|
|
1703
|
-
case 3: return [2 /*return*/];
|
|
1704
|
-
}
|
|
1705
|
-
});
|
|
1706
|
-
}); })();
|
|
1707
|
-
_d.label = 1;
|
|
1708
|
-
case 1:
|
|
1709
|
-
_d.trys.push([1, 3, , 4]);
|
|
1710
|
-
return [4 /*yield*/, responsePromise];
|
|
1711
|
-
case 2:
|
|
1712
|
-
response_1 = _d.sent();
|
|
1713
|
-
// Track signature confirmation only for truthy responses
|
|
1714
|
-
if (response_1) {
|
|
1715
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1716
|
-
var e_5;
|
|
1717
|
-
return __generator(this, function (_a) {
|
|
1718
|
-
switch (_a.label) {
|
|
1719
|
-
case 0:
|
|
1720
|
-
_a.trys.push([0, 2, , 3]);
|
|
1721
|
-
return [4 /*yield*/, this.signature(__assign({ status: SignatureStatus.CONFIRMED }, this.buildSignatureEventPayload(method, params, response_1, capturedChainId_1, provider)))];
|
|
1722
|
-
case 1:
|
|
1723
|
-
_a.sent();
|
|
1724
|
-
return [3 /*break*/, 3];
|
|
1725
|
-
case 2:
|
|
1726
|
-
e_5 = _a.sent();
|
|
1727
|
-
logger.error("Formo: Failed to track signature confirmation", e_5);
|
|
1728
|
-
return [3 /*break*/, 3];
|
|
1729
|
-
case 3: return [2 /*return*/];
|
|
1730
|
-
}
|
|
1731
|
-
});
|
|
1732
|
-
}); })();
|
|
1733
|
-
}
|
|
1734
|
-
return [2 /*return*/, response_1];
|
|
1735
|
-
case 3:
|
|
1736
|
-
error_4 = _d.sent();
|
|
1737
|
-
rpcError = error_4;
|
|
1738
|
-
if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
|
|
1739
|
-
// Use the already cast rpcError to avoid duplication
|
|
1740
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1741
|
-
var e_6;
|
|
1742
|
-
return __generator(this, function (_a) {
|
|
1743
|
-
switch (_a.label) {
|
|
1744
|
-
case 0:
|
|
1745
|
-
_a.trys.push([0, 2, , 3]);
|
|
1746
|
-
return [4 /*yield*/, this.signature(__assign({ status: SignatureStatus.REJECTED }, this.buildSignatureEventPayload(method, params, undefined, capturedChainId_1, provider)))];
|
|
1747
|
-
case 1:
|
|
1748
|
-
_a.sent();
|
|
1749
|
-
return [3 /*break*/, 3];
|
|
1750
|
-
case 2:
|
|
1751
|
-
e_6 = _a.sent();
|
|
1752
|
-
logger.error("Formo: Failed to track signature rejection", e_6);
|
|
1753
|
-
return [3 /*break*/, 3];
|
|
1754
|
-
case 3: return [2 /*return*/];
|
|
1755
|
-
}
|
|
1756
|
-
});
|
|
1757
|
-
}); })();
|
|
1758
|
-
}
|
|
1759
|
-
throw error_4;
|
|
1760
|
-
case 4:
|
|
1761
|
-
if (!(Array.isArray(params) &&
|
|
1762
|
-
method === "eth_sendTransaction" &&
|
|
1763
|
-
params[0])) return [3 /*break*/, 8];
|
|
1764
|
-
if (!this.isAutocaptureEnabled("transaction")) {
|
|
1765
|
-
logger.debug("Transaction event skipped (autocapture.transaction: false)", { method: method });
|
|
1766
|
-
return [2 /*return*/, request({ method: method, params: params })];
|
|
1767
|
-
}
|
|
1768
|
-
txPromise = request({ method: method, params: params });
|
|
1769
|
-
txPromise.catch(function () { return undefined; });
|
|
1770
|
-
txChainId_1 = this.resolveChainIdForProvider(provider);
|
|
1771
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1772
|
-
var payload, e_7;
|
|
1773
|
-
return __generator(this, function (_a) {
|
|
1774
|
-
switch (_a.label) {
|
|
1775
|
-
case 0:
|
|
1776
|
-
_a.trys.push([0, 3, , 4]);
|
|
1777
|
-
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
1778
|
-
case 1:
|
|
1779
|
-
payload = _a.sent();
|
|
1780
|
-
return [4 /*yield*/, this.transaction(__assign({ status: TransactionStatus.STARTED }, payload))];
|
|
1781
|
-
case 2:
|
|
1782
|
-
_a.sent();
|
|
1783
|
-
return [3 /*break*/, 4];
|
|
1784
|
-
case 3:
|
|
1785
|
-
e_7 = _a.sent();
|
|
1786
|
-
logger.error("Formo: Failed to track transaction start", e_7);
|
|
1787
|
-
return [3 /*break*/, 4];
|
|
1788
|
-
case 4: return [2 /*return*/];
|
|
1789
|
-
}
|
|
1790
|
-
});
|
|
1791
|
-
}); })();
|
|
1792
|
-
_d.label = 5;
|
|
1793
|
-
case 5:
|
|
1794
|
-
_d.trys.push([5, 7, , 8]);
|
|
1795
|
-
return [4 /*yield*/, txPromise];
|
|
1796
|
-
case 6:
|
|
1797
|
-
transactionHash_1 = _d.sent();
|
|
1798
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1799
|
-
var payload, e_8;
|
|
1800
|
-
return __generator(this, function (_a) {
|
|
1801
|
-
switch (_a.label) {
|
|
1802
|
-
case 0:
|
|
1803
|
-
_a.trys.push([0, 3, , 4]);
|
|
1804
|
-
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
1805
|
-
case 1:
|
|
1806
|
-
payload = _a.sent();
|
|
1807
|
-
return [4 /*yield*/, this.transaction(__assign(__assign({ status: TransactionStatus.BROADCASTED }, payload), { transactionHash: transactionHash_1 }))];
|
|
1808
|
-
case 2:
|
|
1809
|
-
_a.sent();
|
|
1810
|
-
// Start async polling for transaction receipt
|
|
1811
|
-
this.pollTransactionReceipt(provider, transactionHash_1, payload);
|
|
1812
|
-
return [3 /*break*/, 4];
|
|
1813
|
-
case 3:
|
|
1814
|
-
e_8 = _a.sent();
|
|
1815
|
-
logger.error("Formo: Failed to track transaction broadcast", e_8);
|
|
1816
|
-
return [3 /*break*/, 4];
|
|
1817
|
-
case 4: return [2 /*return*/];
|
|
1818
|
-
}
|
|
1819
|
-
});
|
|
1820
|
-
}); })();
|
|
1821
|
-
return [2 /*return*/, transactionHash_1];
|
|
1822
|
-
case 7:
|
|
1823
|
-
error_5 = _d.sent();
|
|
1824
|
-
rpcError = error_5;
|
|
1825
|
-
if ((rpcError === null || rpcError === void 0 ? void 0 : rpcError.code) === 4001) {
|
|
1826
|
-
// Use the already cast rpcError to avoid duplication
|
|
1827
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1828
|
-
var payload, e_9;
|
|
1829
|
-
return __generator(this, function (_a) {
|
|
1830
|
-
switch (_a.label) {
|
|
1831
|
-
case 0:
|
|
1832
|
-
_a.trys.push([0, 3, , 4]);
|
|
1833
|
-
return [4 /*yield*/, this.buildTransactionEventPayload(params, provider, txChainId_1)];
|
|
1834
|
-
case 1:
|
|
1835
|
-
payload = _a.sent();
|
|
1836
|
-
return [4 /*yield*/, this.transaction(__assign({ status: TransactionStatus.REJECTED }, payload))];
|
|
1837
|
-
case 2:
|
|
1838
|
-
_a.sent();
|
|
1839
|
-
return [3 /*break*/, 4];
|
|
1840
|
-
case 3:
|
|
1841
|
-
e_9 = _a.sent();
|
|
1842
|
-
logger.error("Formo: Failed to track transaction rejection", e_9);
|
|
1843
|
-
return [3 /*break*/, 4];
|
|
1844
|
-
case 4: return [2 /*return*/];
|
|
1845
|
-
}
|
|
1846
|
-
});
|
|
1847
|
-
}); })();
|
|
1848
|
-
}
|
|
1849
|
-
throw error_5;
|
|
1850
|
-
case 8: return [2 /*return*/, request({ method: method, params: params })];
|
|
1851
|
-
}
|
|
1852
|
-
});
|
|
1853
|
-
}); };
|
|
1854
|
-
// Mark the wrapper so we can detect if request is replaced externally and keep a reference on provider
|
|
1855
|
-
wrappedRequest[WRAPPED_REQUEST_SYMBOL] = true;
|
|
1856
|
-
provider[WRAPPED_REQUEST_REF_SYMBOL] =
|
|
1857
|
-
wrappedRequest;
|
|
1858
|
-
try {
|
|
1859
|
-
// Attempt to assign the wrapped request function (rely on try-catch for mutability errors)
|
|
1860
|
-
provider.request = wrappedRequest;
|
|
1861
|
-
}
|
|
1862
|
-
catch (e) {
|
|
1863
|
-
logger.warn("Failed to wrap provider.request; skipping", e);
|
|
1864
|
-
}
|
|
1865
|
-
};
|
|
1866
|
-
FormoAnalytics.prototype.onLocationChange = function () {
|
|
1867
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1868
|
-
var _a;
|
|
1869
|
-
return __generator(this, function (_b) {
|
|
1870
|
-
if (this._currentUrl !== window.location.href) {
|
|
1871
|
-
this._currentUrl = window.location.href;
|
|
1872
|
-
// Host/path exclusions are evaluated per navigation, so a SPA can leave
|
|
1873
|
-
// an excluded route and become trackable without any wallet event
|
|
1874
|
-
// firing. The wagmi handler only observes *changes*, so an unchanged
|
|
1875
|
-
// connection it was forced to decline earlier would stay invisible for
|
|
1876
|
-
// the rest of the page load. Give it a chance to adopt it now.
|
|
1877
|
-
(_a = this.wagmiHandler) === null || _a === void 0 ? void 0 : _a.retryAdoption();
|
|
1878
|
-
this.trackPageHit();
|
|
1879
|
-
}
|
|
1880
|
-
return [2 /*return*/];
|
|
1881
|
-
});
|
|
1882
|
-
});
|
|
1883
|
-
};
|
|
1884
|
-
FormoAnalytics.prototype.trackPageHits = function () {
|
|
1885
|
-
var _this = this;
|
|
1886
|
-
// Install a single, instance-agnostic wrapper around history.pushState /
|
|
1887
|
-
// replaceState so concurrent SDK instances (React Strict Mode, HMR) don't
|
|
1888
|
-
// each stack their own wrapper - which would dispatch N synthetic events
|
|
1889
|
-
// per navigation and produce O(N^2) onLocationChange calls. The wrapper
|
|
1890
|
-
// dispatches once; per-instance bookkeeping is done by per-instance
|
|
1891
|
-
// listeners that each register/unregister themselves.
|
|
1892
|
-
FormoAnalytics.installHistoryHooksOnce();
|
|
1893
|
-
this._onPopStateListener = function () { return _this.onLocationChange(); };
|
|
1894
|
-
this._onLocationChangeListener = function () { return _this.onLocationChange(); };
|
|
1895
|
-
window.addEventListener("popstate", this._onPopStateListener);
|
|
1896
|
-
window.addEventListener("locationchange", this._onLocationChangeListener);
|
|
1897
|
-
};
|
|
1898
|
-
/**
|
|
1899
|
-
* Wrap history.pushState / replaceState exactly once per `history` object,
|
|
1900
|
-
* regardless of how many SDK instances are constructed. Uses a Symbol
|
|
1901
|
-
* marker so we recognize our own wrapper across module reloads in HMR.
|
|
1902
|
-
*/
|
|
1903
|
-
FormoAnalytics.installHistoryHooksOnce = function () {
|
|
1904
|
-
if (typeof history === "undefined" || typeof window === "undefined")
|
|
1905
|
-
return;
|
|
1906
|
-
var marker = Symbol.for("formo.historyWrapped");
|
|
1907
|
-
if (history[marker])
|
|
1908
|
-
return;
|
|
1909
|
-
history[marker] = true;
|
|
1910
|
-
var dispatch = function () { return window.dispatchEvent(new window.Event("locationchange")); };
|
|
1911
|
-
var oldPushState = history.pushState;
|
|
1912
|
-
history.pushState = function pushState() {
|
|
1913
|
-
var args = [];
|
|
1914
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1915
|
-
args[_i] = arguments[_i];
|
|
1916
|
-
}
|
|
1917
|
-
var ret = oldPushState.apply(this, args);
|
|
1918
|
-
dispatch();
|
|
1919
|
-
return ret;
|
|
1920
|
-
};
|
|
1921
|
-
var oldReplaceState = history.replaceState;
|
|
1922
|
-
history.replaceState = function replaceState() {
|
|
1923
|
-
var args = [];
|
|
1924
|
-
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1925
|
-
args[_i] = arguments[_i];
|
|
1926
|
-
}
|
|
1927
|
-
var ret = oldReplaceState.apply(this, args);
|
|
1928
|
-
dispatch();
|
|
1929
|
-
return ret;
|
|
1930
|
-
};
|
|
1931
|
-
};
|
|
1932
|
-
FormoAnalytics.prototype.trackPageHit = function (category, name, properties, context, callback) {
|
|
1933
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1934
|
-
var _this = this;
|
|
1935
|
-
return __generator(this, function (_a) {
|
|
1936
|
-
if (!this.shouldTrack()) {
|
|
1937
|
-
logger.info("Track page hit: Skipping event due to tracking configuration");
|
|
1938
|
-
return [2 /*return*/];
|
|
1939
|
-
}
|
|
1940
|
-
setTimeout(function () {
|
|
1941
|
-
// Drop in-flight page hits from an SDK instance that was torn down
|
|
1942
|
-
// between scheduling and firing (e.g. provider remount in React Strict
|
|
1943
|
-
// Mode / HMR). Otherwise the orphan instance would queue a page event
|
|
1944
|
-
// here with its stale, never-populated `currentAddress`.
|
|
1945
|
-
if (_this._pageHooksDisposed)
|
|
1946
|
-
return;
|
|
1947
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1948
|
-
var e_10;
|
|
1949
|
-
return __generator(this, function (_a) {
|
|
1950
|
-
switch (_a.label) {
|
|
1951
|
-
case 0:
|
|
1952
|
-
_a.trys.push([0, 2, , 3]);
|
|
1953
|
-
return [4 /*yield*/, this.trackEvent(EventType.PAGE, {
|
|
1954
|
-
category: category,
|
|
1955
|
-
name: name,
|
|
1956
|
-
}, properties, context, callback)];
|
|
1957
|
-
case 1:
|
|
1958
|
-
_a.sent();
|
|
1959
|
-
return [3 /*break*/, 3];
|
|
1960
|
-
case 2:
|
|
1961
|
-
e_10 = _a.sent();
|
|
1962
|
-
logger.error("Formo: Failed to track page hit", e_10);
|
|
1963
|
-
return [3 /*break*/, 3];
|
|
1964
|
-
case 3: return [2 /*return*/];
|
|
1965
|
-
}
|
|
1966
|
-
});
|
|
1967
|
-
}); })();
|
|
1968
|
-
}, 300);
|
|
1969
|
-
return [2 /*return*/];
|
|
1970
|
-
});
|
|
1971
|
-
});
|
|
1972
|
-
};
|
|
1973
|
-
FormoAnalytics.prototype.trackEvent = function (type, payload, properties, context, callback) {
|
|
1974
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
1975
|
-
var error_6;
|
|
1976
|
-
return __generator(this, function (_a) {
|
|
1977
|
-
switch (_a.label) {
|
|
1978
|
-
case 0:
|
|
1979
|
-
_a.trys.push([0, 2, , 3]);
|
|
1980
|
-
// Gate on the chain the event actually carries. `connect`, `disconnect`,
|
|
1981
|
-
// `chain`, `signature` and `transaction` all put one in the payload, and
|
|
1982
|
-
// it is authoritative: it can name a provider or a wagmi mutation chain
|
|
1983
|
-
// that is not the active one. Events without a chain (page, track,
|
|
1984
|
-
// identify) fall back to the central value.
|
|
1985
|
-
if (!this.shouldTrack(payload === null || payload === void 0 ? void 0 : payload.chainId)) {
|
|
1986
|
-
logger.info("Skipping ".concat(type, " event due to tracking configuration"));
|
|
1987
|
-
return [2 /*return*/];
|
|
1988
|
-
}
|
|
1989
|
-
return [4 /*yield*/, this.eventManager.addEvent(__assign(__assign({ type: type }, payload), { properties: properties, context: context, callback: callback }), this.currentAddress, this.currentUserId)];
|
|
1990
|
-
case 1:
|
|
1991
|
-
_a.sent();
|
|
1992
|
-
return [3 /*break*/, 3];
|
|
1993
|
-
case 2:
|
|
1994
|
-
error_6 = _a.sent();
|
|
1995
|
-
logger.error("Error tracking event:", error_6);
|
|
1996
|
-
return [3 /*break*/, 3];
|
|
1997
|
-
case 3: return [2 /*return*/];
|
|
1998
|
-
}
|
|
1999
|
-
});
|
|
2000
|
-
});
|
|
2001
|
-
};
|
|
2002
|
-
/**
|
|
2003
|
-
* Visitor-level tracking suppression.
|
|
2004
|
-
*
|
|
2005
|
-
* Returns true when the SDK must not persist any identity/session/chain
|
|
2006
|
-
* state or send any events for this visitor - i.e. an explicit opt-out or a
|
|
2007
|
-
* jurisdiction/timezone exclusion. Public entry points that write state
|
|
2008
|
-
* before reaching the `shouldTrack()` event gate (identify/connect/detect)
|
|
2009
|
-
* check this first so suppressed visitors leave no cookies or session state.
|
|
2010
|
-
* @returns {boolean} True if all tracking and persistence must be suppressed
|
|
2011
|
-
* @internal Also read by `identifyPrivyUser` (via a structural cast) so the
|
|
2012
|
-
* Privy sync skips chain reconciliation and emission for suppressed visitors.
|
|
2013
|
-
*/
|
|
2014
|
-
FormoAnalytics.prototype.isTrackingSuppressed = function () {
|
|
2015
|
-
return this.hasOptedOutTracking() || this.isCurrentEnvironmentExcluded();
|
|
2016
|
-
};
|
|
2017
|
-
/**
|
|
2018
|
-
* Whether the current chain id is in `tracking.excludeChains`.
|
|
2019
|
-
*
|
|
2020
|
-
* Split out from `shouldTrack()` so `identify()` can check it *before*
|
|
2021
|
-
* mutating identity state. `trackEvent()` drops an excluded event silently
|
|
2022
|
-
* and returns void, but `identify()` marks the wallet as identified first, so
|
|
2023
|
-
* without this guard an identify on an excluded chain is dedup-marked and
|
|
2024
|
-
* then discarded, and the wallet never re-emits for the rest of the session
|
|
2025
|
-
* even after switching to an allowed chain. On the Privy path that loses the
|
|
2026
|
-
* user's whole cluster at once rather than a single wallet.
|
|
2027
|
-
*
|
|
2028
|
-
* Mirrors the chain rule in `shouldTrack()`: only applies when `tracking` is
|
|
2029
|
-
* an options object with `excludeChains` set, and only once a chain id is
|
|
2030
|
-
* known.
|
|
2031
|
-
*/
|
|
2032
|
-
FormoAnalytics.prototype.isCurrentChainExcluded = function (eventChainId) {
|
|
2033
|
-
if (this.options.tracking === null ||
|
|
2034
|
-
typeof this.options.tracking !== "object" ||
|
|
2035
|
-
Array.isArray(this.options.tracking)) {
|
|
2036
|
-
return false;
|
|
2037
|
-
}
|
|
2038
|
-
var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
2039
|
-
if (excludeChains.length === 0)
|
|
2040
|
-
return false;
|
|
2041
|
-
// Mirrors `shouldTrack()`: the event's own chain wins when it has one,
|
|
2042
|
-
// and an unresolvable chain on a known wallet counts as excluded rather
|
|
2043
|
-
// than allowed.
|
|
2044
|
-
var chainToCheck = eventChainId !== null && eventChainId !== void 0 ? eventChainId : this.currentChainId;
|
|
2045
|
-
if (chainToCheck === 0)
|
|
2046
|
-
return true;
|
|
2047
|
-
if (chainToCheck === undefined)
|
|
2048
|
-
return false;
|
|
2049
|
-
return excludeChains.includes(chainToCheck);
|
|
2050
|
-
};
|
|
2051
|
-
/**
|
|
2052
|
-
* Whether the current environment is excluded from tracking - the visitor's
|
|
2053
|
-
* timezone, the current hostname, or the current pathname matches a
|
|
2054
|
-
* configured exclusion.
|
|
2055
|
-
*
|
|
2056
|
-
* Timezone is visitor/session-level (stable for the session); host/path are
|
|
2057
|
-
* current-page-level and transient - if a SPA navigates to an allowed path,
|
|
2058
|
-
* tracking resumes for future actions. Used as the "do not write identity or
|
|
2059
|
-
* send events" gate at every entry point that would persist state before the
|
|
2060
|
-
* `shouldTrack()` event gate.
|
|
2061
|
-
* @returns {boolean} True if the current environment is excluded
|
|
2062
|
-
*/
|
|
2063
|
-
FormoAnalytics.prototype.isCurrentEnvironmentExcluded = function () {
|
|
2064
|
-
return (this.isTimezoneExcluded() ||
|
|
2065
|
-
this.isHostExcluded() ||
|
|
2066
|
-
this.isPathExcluded());
|
|
2067
|
-
};
|
|
2068
|
-
/**
|
|
2069
|
-
* Whether the current hostname matches a configured `tracking.excludeHosts`
|
|
2070
|
-
* entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
|
|
2071
|
-
* @returns {boolean} True if the current hostname is excluded
|
|
2072
|
-
*/
|
|
2073
|
-
FormoAnalytics.prototype.isHostExcluded = function () {
|
|
2074
|
-
var tracking = this.options.tracking;
|
|
2075
|
-
if (tracking === null ||
|
|
2076
|
-
typeof tracking !== "object" ||
|
|
2077
|
-
Array.isArray(tracking)) {
|
|
2078
|
-
return false;
|
|
2079
|
-
}
|
|
2080
|
-
if (typeof window === "undefined") {
|
|
2081
|
-
return false;
|
|
2082
|
-
}
|
|
2083
|
-
var _a = tracking.excludeHosts, excludeHosts = _a === void 0 ? [] : _a;
|
|
2084
|
-
return excludeHosts.includes(window.location.hostname);
|
|
2085
|
-
};
|
|
2086
|
-
/**
|
|
2087
|
-
* Whether the current pathname matches a configured `tracking.excludePaths`
|
|
2088
|
-
* entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
|
|
2089
|
-
* @returns {boolean} True if the current pathname is excluded
|
|
2090
|
-
*/
|
|
2091
|
-
FormoAnalytics.prototype.isPathExcluded = function () {
|
|
2092
|
-
var tracking = this.options.tracking;
|
|
2093
|
-
if (tracking === null ||
|
|
2094
|
-
typeof tracking !== "object" ||
|
|
2095
|
-
Array.isArray(tracking)) {
|
|
2096
|
-
return false;
|
|
2097
|
-
}
|
|
2098
|
-
if (typeof window === "undefined") {
|
|
2099
|
-
return false;
|
|
2100
|
-
}
|
|
2101
|
-
var _a = tracking.excludePaths, excludePaths = _a === void 0 ? [] : _a;
|
|
2102
|
-
return excludePaths.includes(window.location.pathname);
|
|
2103
|
-
};
|
|
2104
|
-
/**
|
|
2105
|
-
* Whether the current call is in a visitor-level suppression state - opt-out
|
|
2106
|
-
* or excluded timezone - for which any persisted identity cookie should be
|
|
2107
|
-
* actively purged (not merely skipped). Host/path exclusions are
|
|
2108
|
-
* deliberately excluded here: they are transient current-page states, so a
|
|
2109
|
-
* cookie legitimately written on an allowed page must survive a visit to an
|
|
2110
|
-
* excluded route.
|
|
2111
|
-
* @returns {boolean} True if persisted identity must be purged
|
|
2112
|
-
*/
|
|
2113
|
-
FormoAnalytics.prototype.isPersistedIdentityPurgeRequired = function () {
|
|
2114
|
-
return this.hasOptedOutTracking() || this.isTimezoneExcluded();
|
|
2115
|
-
};
|
|
2116
|
-
/**
|
|
2117
|
-
* Whether the visitor's browser-resolved timezone matches a configured
|
|
2118
|
-
* `tracking.excludeTimezones` entry (case-insensitive). Client-side and
|
|
2119
|
-
* best-effort - see TrackingOptions.excludeTimezones.
|
|
2120
|
-
* @returns {boolean} True if the current timezone is excluded
|
|
2121
|
-
*/
|
|
2122
|
-
FormoAnalytics.prototype.isTimezoneExcluded = function () {
|
|
2123
|
-
var tracking = this.options.tracking;
|
|
2124
|
-
if (tracking === null ||
|
|
2125
|
-
typeof tracking !== "object" ||
|
|
2126
|
-
Array.isArray(tracking)) {
|
|
2127
|
-
return false;
|
|
2128
|
-
}
|
|
2129
|
-
var _a = tracking.excludeTimezones, excludeTimezones = _a === void 0 ? [] : _a;
|
|
2130
|
-
if (excludeTimezones.length === 0) {
|
|
2131
|
-
return false;
|
|
2132
|
-
}
|
|
2133
|
-
var timezone = getTimezone();
|
|
2134
|
-
if (!timezone) {
|
|
2135
|
-
return false;
|
|
2136
|
-
}
|
|
2137
|
-
var lowerTimezone = timezone.toLowerCase();
|
|
2138
|
-
return excludeTimezones.some(function (tz) { return typeof tz === "string" && tz.toLowerCase() === lowerTimezone; });
|
|
1187
|
+
FormoAnalytics.prototype.isTrackingSuppressed = function () {
|
|
1188
|
+
return this.trackingPolicy.isTrackingSuppressed();
|
|
2139
1189
|
};
|
|
2140
|
-
/**
|
|
2141
|
-
* Determines if tracking should be enabled based on configuration and consent
|
|
2142
|
-
* @returns {boolean} True if tracking should be enabled
|
|
2143
|
-
*/
|
|
1190
|
+
/** @see TrackingPolicy.shouldTrack */
|
|
2144
1191
|
FormoAnalytics.prototype.shouldTrack = function (eventChainId) {
|
|
2145
|
-
|
|
2146
|
-
if (this.hasOptedOutTracking()) {
|
|
2147
|
-
return false;
|
|
2148
|
-
}
|
|
2149
|
-
// Check if tracking is explicitly provided as a boolean
|
|
2150
|
-
if (typeof this.options.tracking === "boolean") {
|
|
2151
|
-
return this.options.tracking;
|
|
2152
|
-
}
|
|
2153
|
-
// Handle object configuration with exclusion rules
|
|
2154
|
-
if (this.options.tracking !== null &&
|
|
2155
|
-
typeof this.options.tracking === "object" &&
|
|
2156
|
-
!Array.isArray(this.options.tracking)) {
|
|
2157
|
-
var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
2158
|
-
// Environment exclusions (timezone / host / path) - no identify / connect
|
|
2159
|
-
// / track events while excluded. Host/path are exact-match.
|
|
2160
|
-
if (this.isCurrentEnvironmentExcluded()) {
|
|
2161
|
-
return false;
|
|
2162
|
-
}
|
|
2163
|
-
// Check chainId exclusions.
|
|
2164
|
-
//
|
|
2165
|
-
// The event's OWN chain wins over `currentChainId`. They differ whenever
|
|
2166
|
-
// the event did not come from the active provider: a second wallet
|
|
2167
|
-
// signing through its own provider, or a wagmi mutation that names an
|
|
2168
|
-
// explicit chain. Reading only the central field there would emit an
|
|
2169
|
-
// event that is labelled with an excluded chain, which is precisely what
|
|
2170
|
-
// the exclusion forbids - and would drop an allowed event whenever the
|
|
2171
|
-
// active provider happened to sit on an excluded chain.
|
|
2172
|
-
var chainToCheck = eventChainId !== null && eventChainId !== void 0 ? eventChainId : this.currentChainId;
|
|
2173
|
-
if (excludeChains.length > 0) {
|
|
2174
|
-
// Fail CLOSED on an unknown chain. `resolveChainIdForProvider` reports
|
|
2175
|
-
// 0 when it has never heard a chain from the signing wallet, and 0 is
|
|
2176
|
-
// in no exclusion list, so treating it as "not excluded" would let
|
|
2177
|
-
// exactly the events an operator excluded through - the wallet on the
|
|
2178
|
-
// excluded chain is often the one we know least about. An explicit
|
|
2179
|
-
// exclusion is a directive, so an unresolvable chain is refused.
|
|
2180
|
-
//
|
|
2181
|
-
// This covers the central value too, not just an explicit event
|
|
2182
|
-
// chain: `page`, `track` and `identify` carry no chain of their own
|
|
2183
|
-
// and fall back to `currentChainId`, so an unknown chain there would
|
|
2184
|
-
// otherwise send wallet-attributed events for a wallet that may well
|
|
2185
|
-
// be sitting on an excluded chain.
|
|
2186
|
-
// Deliberately keyed on 0 and NOT on `undefined`. 0 is the explicit
|
|
2187
|
-
// "we asked and could not tell" marker. `undefined` means no chain
|
|
2188
|
-
// state yet, which is a legitimate transient - the Privy path
|
|
2189
|
-
// reconciles a Solana wallet through exactly that state - and
|
|
2190
|
-
// refusing it would drop real events.
|
|
2191
|
-
//
|
|
2192
|
-
// `backfillActiveWallet()` never persists 0, so an unresolvable chain
|
|
2193
|
-
// cannot leak into `currentChainId` and reach the unscoped events
|
|
2194
|
-
// (page / track / identify) that fall back to it.
|
|
2195
|
-
if (chainToCheck === 0)
|
|
2196
|
-
return false;
|
|
2197
|
-
if (chainToCheck && excludeChains.includes(chainToCheck))
|
|
2198
|
-
return false;
|
|
2199
|
-
}
|
|
2200
|
-
// If nothing is excluded, tracking is enabled
|
|
2201
|
-
return true;
|
|
2202
|
-
}
|
|
2203
|
-
// Default behavior: track everywhere except localhost
|
|
2204
|
-
return !isLocalhost();
|
|
1192
|
+
return this.trackingPolicy.shouldTrack({ chainId: eventChainId });
|
|
2205
1193
|
};
|
|
2206
1194
|
/**
|
|
2207
1195
|
* Check if a specific wallet event type is enabled for autocapture
|
|
@@ -2209,184 +1197,18 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2209
1197
|
* @returns {boolean} True if the event type should be autocaptured
|
|
2210
1198
|
*/
|
|
2211
1199
|
FormoAnalytics.prototype.isAutocaptureEnabled = function (eventType) {
|
|
2212
|
-
|
|
2213
|
-
if (this.options.autocapture === undefined) {
|
|
2214
|
-
return true;
|
|
2215
|
-
}
|
|
2216
|
-
// If boolean, return that value for all events
|
|
2217
|
-
if (typeof this.options.autocapture === "boolean") {
|
|
2218
|
-
return this.options.autocapture;
|
|
2219
|
-
}
|
|
2220
|
-
// If it's an object, check the specific event configuration
|
|
2221
|
-
if (this.options.autocapture !== null &&
|
|
2222
|
-
typeof this.options.autocapture === "object") {
|
|
2223
|
-
var eventConfig = this.options.autocapture[eventType];
|
|
2224
|
-
// Default to true if not explicitly set to false
|
|
2225
|
-
return eventConfig !== false;
|
|
2226
|
-
}
|
|
2227
|
-
// Default to enabled if no specific configuration
|
|
2228
|
-
return true;
|
|
2229
|
-
};
|
|
2230
|
-
/*
|
|
2231
|
-
Utility functions
|
|
2232
|
-
*/
|
|
2233
|
-
/**
|
|
2234
|
-
* Get provider information for a given provider
|
|
2235
|
-
* @param provider The provider to get info for
|
|
2236
|
-
* @returns Provider information
|
|
2237
|
-
*/
|
|
2238
|
-
FormoAnalytics.prototype.getProviderInfo = function (provider) {
|
|
2239
|
-
// First check if provider is in our EIP-6963 providers list
|
|
2240
|
-
var eip6963Provider = this._providers.find(function (p) { return p.provider === provider; });
|
|
2241
|
-
if (eip6963Provider) {
|
|
2242
|
-
return {
|
|
2243
|
-
name: eip6963Provider.info.name,
|
|
2244
|
-
rdns: eip6963Provider.info.rdns,
|
|
2245
|
-
};
|
|
2246
|
-
}
|
|
2247
|
-
// Fallback to injected provider detection
|
|
2248
|
-
var injectedInfo = detectInjectedProviderInfo(provider);
|
|
2249
|
-
return {
|
|
2250
|
-
name: injectedInfo.name,
|
|
2251
|
-
rdns: injectedInfo.rdns,
|
|
2252
|
-
};
|
|
2253
|
-
};
|
|
2254
|
-
FormoAnalytics.prototype.getProviders = function () {
|
|
2255
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
2256
|
-
var store, providers, injected_1, injectedProviderInfo, injectedDetail, uniqueProviders, _i, uniqueProviders_1, detail;
|
|
2257
|
-
var _this = this;
|
|
2258
|
-
return __generator(this, function (_a) {
|
|
2259
|
-
store = createStore();
|
|
2260
|
-
providers = store.getProviders();
|
|
2261
|
-
store.subscribe(function (providerDetails) {
|
|
2262
|
-
providers = providerDetails;
|
|
2263
|
-
// Process newly added providers with proper deduplication
|
|
2264
|
-
var newlyAddedDetails = providerDetails.filter(function (detail) {
|
|
2265
|
-
var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
|
|
2266
|
-
return provider && !_this._seenProviders.has(provider);
|
|
2267
|
-
});
|
|
2268
|
-
// Add new providers to the array without overwriting existing ones
|
|
2269
|
-
for (var _i = 0, newlyAddedDetails_1 = newlyAddedDetails; _i < newlyAddedDetails_1.length; _i++) {
|
|
2270
|
-
var detail = newlyAddedDetails_1[_i];
|
|
2271
|
-
_this.safeAddProviderDetail(detail);
|
|
2272
|
-
}
|
|
2273
|
-
// Track listeners for newly discovered providers only
|
|
2274
|
-
var newDetails = providerDetails.filter(function (detail) {
|
|
2275
|
-
var p = detail === null || detail === void 0 ? void 0 : detail.provider;
|
|
2276
|
-
return !!p && !_this._trackedProviders.has(p);
|
|
2277
|
-
});
|
|
2278
|
-
if (newDetails.length > 0) {
|
|
2279
|
-
_this.trackProviders(newDetails);
|
|
2280
|
-
// Detect newly discovered wallets (session de-dupes) with error handling
|
|
2281
|
-
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
2282
|
-
var e_11;
|
|
2283
|
-
return __generator(this, function (_a) {
|
|
2284
|
-
switch (_a.label) {
|
|
2285
|
-
case 0:
|
|
2286
|
-
_a.trys.push([0, 2, , 3]);
|
|
2287
|
-
return [4 /*yield*/, this.detectWallets(newDetails)];
|
|
2288
|
-
case 1:
|
|
2289
|
-
_a.sent();
|
|
2290
|
-
return [3 /*break*/, 3];
|
|
2291
|
-
case 2:
|
|
2292
|
-
e_11 = _a.sent();
|
|
2293
|
-
logger.error("Formo: Failed to detect wallets", e_11);
|
|
2294
|
-
return [3 /*break*/, 3];
|
|
2295
|
-
case 3: return [2 /*return*/];
|
|
2296
|
-
}
|
|
2297
|
-
});
|
|
2298
|
-
}); })();
|
|
2299
|
-
}
|
|
2300
|
-
// Clean up providers that are no longer available
|
|
2301
|
-
_this.cleanupUnavailableProviders();
|
|
2302
|
-
});
|
|
2303
|
-
// Fallback to injected provider if no providers are found
|
|
2304
|
-
if (providers.length === 0) {
|
|
2305
|
-
injected_1 = typeof window !== "undefined" ? window.ethereum : undefined;
|
|
2306
|
-
if (injected_1) {
|
|
2307
|
-
// If we have already detected and cached the injected provider, and it's the same instance, return the cached result
|
|
2308
|
-
if (this._injectedProviderDetail &&
|
|
2309
|
-
this._injectedProviderDetail.provider === injected_1) {
|
|
2310
|
-
// Ensure it's tracked
|
|
2311
|
-
if (!this._trackedProviders.has(injected_1)) {
|
|
2312
|
-
this.trackEIP1193Provider(injected_1);
|
|
2313
|
-
}
|
|
2314
|
-
// Merge with existing providers instead of overwriting
|
|
2315
|
-
if (!this._providers.some(function (existing) { return existing.provider === injected_1; })) {
|
|
2316
|
-
this._providers = __spreadArray(__spreadArray([], this._providers, true), [
|
|
2317
|
-
this._injectedProviderDetail,
|
|
2318
|
-
], false);
|
|
2319
|
-
}
|
|
2320
|
-
return [2 /*return*/, this._providers];
|
|
2321
|
-
}
|
|
2322
|
-
// Re-check if the injected provider is already tracked just before tracking
|
|
2323
|
-
if (!this._trackedProviders.has(injected_1)) {
|
|
2324
|
-
this.trackEIP1193Provider(injected_1);
|
|
2325
|
-
}
|
|
2326
|
-
injectedProviderInfo = detectInjectedProviderInfo(injected_1);
|
|
2327
|
-
injectedDetail = {
|
|
2328
|
-
provider: injected_1,
|
|
2329
|
-
info: injectedProviderInfo,
|
|
2330
|
-
};
|
|
2331
|
-
// Cache the detected injected provider detail
|
|
2332
|
-
this._injectedProviderDetail = injectedDetail;
|
|
2333
|
-
// Merge with existing providers instead of overwriting
|
|
2334
|
-
this.safeAddProviderDetail(injectedDetail);
|
|
2335
|
-
}
|
|
2336
|
-
return [2 /*return*/, this._providers];
|
|
2337
|
-
}
|
|
2338
|
-
uniqueProviders = providers.filter(function (detail) {
|
|
2339
|
-
var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
|
|
2340
|
-
return provider && !_this._seenProviders.has(provider);
|
|
2341
|
-
});
|
|
2342
|
-
// Add to seen providers and instances, ensuring no duplicates in _providers
|
|
2343
|
-
for (_i = 0, uniqueProviders_1 = uniqueProviders; _i < uniqueProviders_1.length; _i++) {
|
|
2344
|
-
detail = uniqueProviders_1[_i];
|
|
2345
|
-
this.safeAddProviderDetail(detail);
|
|
2346
|
-
}
|
|
2347
|
-
return [2 /*return*/, this._providers];
|
|
2348
|
-
});
|
|
2349
|
-
});
|
|
1200
|
+
return this.trackingPolicy.isAutocaptureEnabled(eventType);
|
|
2350
1201
|
};
|
|
2351
1202
|
Object.defineProperty(FormoAnalytics.prototype, "providers", {
|
|
1203
|
+
/*
|
|
1204
|
+
Utility functions
|
|
1205
|
+
*/
|
|
2352
1206
|
get: function () {
|
|
2353
|
-
return this.
|
|
1207
|
+
return this.evm.all;
|
|
2354
1208
|
},
|
|
2355
1209
|
enumerable: false,
|
|
2356
1210
|
configurable: true
|
|
2357
1211
|
});
|
|
2358
|
-
FormoAnalytics.prototype.detectWallets = function (providers) {
|
|
2359
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
2360
|
-
var _i, providers_2, eip6963ProviderDetail, err_2;
|
|
2361
|
-
return __generator(this, function (_a) {
|
|
2362
|
-
switch (_a.label) {
|
|
2363
|
-
case 0:
|
|
2364
|
-
_a.trys.push([0, 5, , 6]);
|
|
2365
|
-
_i = 0, providers_2 = providers;
|
|
2366
|
-
_a.label = 1;
|
|
2367
|
-
case 1:
|
|
2368
|
-
if (!(_i < providers_2.length)) return [3 /*break*/, 4];
|
|
2369
|
-
eip6963ProviderDetail = providers_2[_i];
|
|
2370
|
-
return [4 /*yield*/, this.detect({
|
|
2371
|
-
providerName: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.name,
|
|
2372
|
-
rdns: eip6963ProviderDetail === null || eip6963ProviderDetail === void 0 ? void 0 : eip6963ProviderDetail.info.rdns,
|
|
2373
|
-
})];
|
|
2374
|
-
case 2:
|
|
2375
|
-
_a.sent();
|
|
2376
|
-
_a.label = 3;
|
|
2377
|
-
case 3:
|
|
2378
|
-
_i++;
|
|
2379
|
-
return [3 /*break*/, 1];
|
|
2380
|
-
case 4: return [3 /*break*/, 6];
|
|
2381
|
-
case 5:
|
|
2382
|
-
err_2 = _a.sent();
|
|
2383
|
-
logger.error("Error detect all wallets:", err_2);
|
|
2384
|
-
return [3 /*break*/, 6];
|
|
2385
|
-
case 6: return [2 /*return*/];
|
|
2386
|
-
}
|
|
2387
|
-
});
|
|
2388
|
-
});
|
|
2389
|
-
};
|
|
2390
1212
|
Object.defineProperty(FormoAnalytics.prototype, "provider", {
|
|
2391
1213
|
get: function () {
|
|
2392
1214
|
return this._provider;
|
|
@@ -2415,176 +1237,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2415
1237
|
enumerable: false,
|
|
2416
1238
|
configurable: true
|
|
2417
1239
|
});
|
|
2418
|
-
FormoAnalytics.prototype.getAddress = function (provider) {
|
|
2419
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
2420
|
-
var p, accounts, err_3, code;
|
|
2421
|
-
return __generator(this, function (_a) {
|
|
2422
|
-
switch (_a.label) {
|
|
2423
|
-
case 0:
|
|
2424
|
-
// Use EVM-specific state to avoid returning a Solana address in an EVM context
|
|
2425
|
-
if (this._chainState.evm.address)
|
|
2426
|
-
return [2 /*return*/, this._chainState.evm.address];
|
|
2427
|
-
p = provider || this.provider;
|
|
2428
|
-
if (!p) {
|
|
2429
|
-
logger.info("The provider is not set");
|
|
2430
|
-
return [2 /*return*/, null];
|
|
2431
|
-
}
|
|
2432
|
-
_a.label = 1;
|
|
2433
|
-
case 1:
|
|
2434
|
-
_a.trys.push([1, 3, , 4]);
|
|
2435
|
-
return [4 /*yield*/, this.getAccounts(p)];
|
|
2436
|
-
case 2:
|
|
2437
|
-
accounts = _a.sent();
|
|
2438
|
-
if (accounts && accounts.length > 0) {
|
|
2439
|
-
return [2 /*return*/, validateAndChecksumAddress(accounts[0]) || null];
|
|
2440
|
-
}
|
|
2441
|
-
return [3 /*break*/, 4];
|
|
2442
|
-
case 3:
|
|
2443
|
-
err_3 = _a.sent();
|
|
2444
|
-
code = err_3 === null || err_3 === void 0 ? void 0 : err_3.code;
|
|
2445
|
-
if (code !== 4001) {
|
|
2446
|
-
logger.error("FormoAnalytics::getAccounts: eth_accounts threw an error", err_3);
|
|
2447
|
-
}
|
|
2448
|
-
return [2 /*return*/, null];
|
|
2449
|
-
case 4: return [2 /*return*/, null];
|
|
2450
|
-
}
|
|
2451
|
-
});
|
|
2452
|
-
});
|
|
2453
|
-
};
|
|
2454
|
-
FormoAnalytics.prototype.getAccounts = function (provider) {
|
|
2455
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
2456
|
-
var p, res, err_4, code;
|
|
2457
|
-
return __generator(this, function (_a) {
|
|
2458
|
-
switch (_a.label) {
|
|
2459
|
-
case 0:
|
|
2460
|
-
p = provider || this.provider;
|
|
2461
|
-
_a.label = 1;
|
|
2462
|
-
case 1:
|
|
2463
|
-
_a.trys.push([1, 3, , 4]);
|
|
2464
|
-
return [4 /*yield*/, (p === null || p === void 0 ? void 0 : p.request({
|
|
2465
|
-
method: "eth_accounts",
|
|
2466
|
-
}))];
|
|
2467
|
-
case 2:
|
|
2468
|
-
res = _a.sent();
|
|
2469
|
-
if (!res || res.length === 0)
|
|
2470
|
-
return [2 /*return*/, null];
|
|
2471
|
-
return [2 /*return*/, res
|
|
2472
|
-
.map(function (e) { return validateAndChecksumAddress(e); })
|
|
2473
|
-
.filter(function (e) { return e !== undefined; })];
|
|
2474
|
-
case 3:
|
|
2475
|
-
err_4 = _a.sent();
|
|
2476
|
-
code = err_4 === null || err_4 === void 0 ? void 0 : err_4.code;
|
|
2477
|
-
if (code !== 4001) {
|
|
2478
|
-
logger.error("FormoAnalytics::getAccounts: eth_accounts threw an error", err_4);
|
|
2479
|
-
}
|
|
2480
|
-
return [2 /*return*/, null];
|
|
2481
|
-
case 4: return [2 /*return*/];
|
|
2482
|
-
}
|
|
2483
|
-
});
|
|
2484
|
-
});
|
|
2485
|
-
};
|
|
2486
|
-
/**
|
|
2487
|
-
* Resolve the chain an autocaptured request actually ran on.
|
|
2488
|
-
*
|
|
2489
|
-
* `_evmChainId` is maintained by `chainChanged` from whichever provider is
|
|
2490
|
-
* currently active. When a request arrives from a *different* tracked
|
|
2491
|
-
* provider - which happens whenever a visitor has two wallets installed -
|
|
2492
|
-
* that cached value describes the wrong wallet, and tagging the event with
|
|
2493
|
-
* it silently mis-attributes the chain.
|
|
2494
|
-
*
|
|
2495
|
-
* Answered entirely from a per-provider snapshot. This is deliberately
|
|
2496
|
-
* SYNCHRONOUS and never issues an RPC.
|
|
2497
|
-
*
|
|
2498
|
-
* An earlier version called `eth_chainId` on the signing provider and
|
|
2499
|
-
* time-boxed it with `Promise.race`. That is not safe: the race abandons the
|
|
2500
|
-
* SDK's promise but cannot cancel the provider's request. On a transport
|
|
2501
|
-
* that serializes - WalletConnect's relay socket, the very case this path
|
|
2502
|
-
* exists to serve - an abandoned lookup stays at the head of the wallet's
|
|
2503
|
-
* queue, and every later RPC the dapp makes queues behind it until reload.
|
|
2504
|
-
* Mislabelling a chain is a reporting defect; wedging the user's wallet is
|
|
2505
|
-
* not acceptable to avoid one.
|
|
2506
|
-
*
|
|
2507
|
-
* When nothing is known, this reports 0 ("unknown") rather than guessing
|
|
2508
|
-
* with the active provider's chain, which is known-wrong for another wallet.
|
|
2509
|
-
*/
|
|
2510
|
-
FormoAnalytics.prototype.resolveChainIdForProvider = function (provider) {
|
|
2511
|
-
if (provider) {
|
|
2512
|
-
var known = this._providerChainIds.get(provider);
|
|
2513
|
-
if (known)
|
|
2514
|
-
return known;
|
|
2515
|
-
// Only the active provider's chain is described by the central cache.
|
|
2516
|
-
if (provider === this._provider && this._evmChainId) {
|
|
2517
|
-
return this._evmChainId;
|
|
2518
|
-
}
|
|
2519
|
-
// A tracked provider we have never heard a chain from. Deliberately no
|
|
2520
|
-
// fall back to `_evmChainId`: it belongs to a different wallet.
|
|
2521
|
-
return 0;
|
|
2522
|
-
}
|
|
2523
|
-
return this._evmChainId || 0;
|
|
2524
|
-
};
|
|
2525
|
-
/**
|
|
2526
|
-
* Record a provider's chain. Fed by `chainChanged` and `connect`, and by the
|
|
2527
|
-
* one-off probe at tracking time - never from inside a user request.
|
|
2528
|
-
*/
|
|
2529
|
-
FormoAnalytics.prototype.rememberProviderChain = function (provider, chainId) {
|
|
2530
|
-
if (!provider || !chainId)
|
|
2531
|
-
return;
|
|
2532
|
-
// Any observation is newer than an `eth_chainId` still in flight for this
|
|
2533
|
-
// provider.
|
|
2534
|
-
this.bumpProviderChainGeneration(provider);
|
|
2535
|
-
this._providerChainIds.set(provider, chainId);
|
|
2536
|
-
// If this IS the active provider, central state has to follow. Recording
|
|
2537
|
-
// it only per provider left `currentChainId` on a chain restored from a
|
|
2538
|
-
// previous session, and the unscoped events that fall back to it were sent
|
|
2539
|
-
// despite an exclusion covering the chain the wallet was really on.
|
|
2540
|
-
//
|
|
2541
|
-
// Guarded on EVM already being the active namespace. `setChainState()`
|
|
2542
|
-
// makes whichever namespace it touches active, so syncing here
|
|
2543
|
-
// unconditionally let a background EVM wallet's chain report steal the
|
|
2544
|
-
// active slot from a live Solana wallet, and every later page or track
|
|
2545
|
-
// event was attributed to the wrong wallet entirely.
|
|
2546
|
-
if (provider === this._provider &&
|
|
2547
|
-
this._activeNamespace === "evm" &&
|
|
2548
|
-
this._evmChainId !== chainId) {
|
|
2549
|
-
this.setChainState('evm', { chainId: chainId });
|
|
2550
|
-
}
|
|
2551
|
-
};
|
|
2552
|
-
/** Advance and return this provider's chain-observation generation. */
|
|
2553
|
-
FormoAnalytics.prototype.bumpProviderChainGeneration = function (provider) {
|
|
2554
|
-
var _a;
|
|
2555
|
-
var next = ((_a = this._providerChainGenerations.get(provider)) !== null && _a !== void 0 ? _a : 0) + 1;
|
|
2556
|
-
this._providerChainGenerations.set(provider, next);
|
|
2557
|
-
return next;
|
|
2558
|
-
};
|
|
2559
|
-
/**
|
|
2560
|
-
* Seed a provider's chain from whatever it already exposes synchronously.
|
|
2561
|
-
*
|
|
2562
|
-
* Most EIP-1193 implementations carry a `chainId` property (MetaMask,
|
|
2563
|
-
* WalletConnect, Coinbase). Reading it costs nothing and cannot block.
|
|
2564
|
-
*
|
|
2565
|
-
* There is deliberately no RPC fallback. An earlier version probed with
|
|
2566
|
-
* `eth_chainId` when a provider was first tracked, on the theory that
|
|
2567
|
-
* tracking time is off the user's critical path. It is not: a serialized
|
|
2568
|
-
* transport has ONE queue, so a stalled probe sits in front of every later
|
|
2569
|
-
* signature and transaction the dapp makes. It could also land out of order
|
|
2570
|
-
* - a slow probe response overwriting a newer `chainChanged` - and relabel
|
|
2571
|
-
* events onto a chain the wallet had already left.
|
|
2572
|
-
*
|
|
2573
|
-
* A provider that exposes nothing stays unknown until it emits
|
|
2574
|
-
* `chainChanged` or `connect`, and unknown is reported honestly as 0.
|
|
2575
|
-
*/
|
|
2576
|
-
FormoAnalytics.prototype.seedProviderChainFromState = function (provider) {
|
|
2577
|
-
var raw = provider.chainId;
|
|
2578
|
-
var chainId = typeof raw === "string"
|
|
2579
|
-
? parseChainId(raw)
|
|
2580
|
-
: typeof raw === "number"
|
|
2581
|
-
? raw
|
|
2582
|
-
: undefined;
|
|
2583
|
-
this.rememberProviderChain(provider, chainId);
|
|
2584
|
-
};
|
|
2585
1240
|
FormoAnalytics.prototype.getCurrentChainId = function (provider) {
|
|
2586
1241
|
return __awaiter(this, void 0, void 0, function () {
|
|
2587
|
-
var p, chainIdHex,
|
|
1242
|
+
var p, chainIdHex, err_2;
|
|
2588
1243
|
return __generator(this, function (_a) {
|
|
2589
1244
|
switch (_a.label) {
|
|
2590
1245
|
case 0:
|
|
@@ -2607,547 +1262,26 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2607
1262
|
}
|
|
2608
1263
|
return [2 /*return*/, parseChainId(chainIdHex)];
|
|
2609
1264
|
case 3:
|
|
2610
|
-
|
|
2611
|
-
logger.error("eth_chainId threw an error:",
|
|
1265
|
+
err_2 = _a.sent();
|
|
1266
|
+
logger.error("eth_chainId threw an error:", err_2);
|
|
2612
1267
|
return [2 /*return*/, 0];
|
|
2613
1268
|
case 4: return [2 /*return*/];
|
|
2614
1269
|
}
|
|
2615
1270
|
});
|
|
2616
1271
|
});
|
|
2617
1272
|
};
|
|
2618
|
-
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
2619
|
-
// Intentionally not read. Kept for positional call-site arity.
|
|
2620
|
-
_response, chainId, provider) {
|
|
2621
|
-
var _a;
|
|
2622
|
-
var rawAddress = method === "personal_sign"
|
|
2623
|
-
? params[1]
|
|
2624
|
-
: params[0];
|
|
2625
|
-
var validAddress = validateAndChecksumAddress(rawAddress);
|
|
2626
|
-
if (!validAddress) {
|
|
2627
|
-
throw new Error("Invalid address in signature payload: ".concat(rawAddress));
|
|
2628
|
-
}
|
|
2629
|
-
var effectiveChainId = (_a = chainId !== null && chainId !== void 0 ? chainId : this._evmChainId) !== null && _a !== void 0 ? _a : undefined;
|
|
2630
|
-
// Only the active provider may write central wallet state - see the same
|
|
2631
|
-
// guard in buildTransactionEventPayload.
|
|
2632
|
-
if (!provider || provider === this._provider || !this._provider) {
|
|
2633
|
-
this.backfillActiveWallet(validAddress, effectiveChainId, provider);
|
|
2634
|
-
}
|
|
2635
|
-
var basePayload = {
|
|
2636
|
-
chainId: effectiveChainId,
|
|
2637
|
-
address: validAddress,
|
|
2638
|
-
};
|
|
2639
|
-
if (method === "personal_sign") {
|
|
2640
|
-
var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
|
|
2641
|
-
return __assign(__assign({}, basePayload), { message: message });
|
|
2642
|
-
}
|
|
2643
|
-
// eth_signTypedData*: params[1] is the full EIP-712 struct.
|
|
2644
|
-
return __assign(__assign({}, basePayload), { message: params[1] });
|
|
2645
|
-
};
|
|
2646
|
-
FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider,
|
|
2647
|
-
/**
|
|
2648
|
-
* Chain resolved once for this request's whole lifecycle. Passing it keeps
|
|
2649
|
-
* every status of one transaction on the same chain even if the user
|
|
2650
|
-
* switches network while the wallet prompt is open.
|
|
2651
|
-
*/
|
|
2652
|
-
capturedChainId) {
|
|
2653
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
2654
|
-
var _a, data, from, to, value, validAddress, chainId;
|
|
2655
|
-
return __generator(this, function (_b) {
|
|
2656
|
-
_a = params[0], data = _a.data, from = _a.from, to = _a.to, value = _a.value;
|
|
2657
|
-
validAddress = validateAndChecksumAddress(from);
|
|
2658
|
-
if (!validAddress) {
|
|
2659
|
-
throw new Error("Invalid address in transaction payload: ".concat(from));
|
|
2660
|
-
}
|
|
2661
|
-
chainId = capturedChainId !== null && capturedChainId !== void 0 ? capturedChainId : this.resolveChainIdForProvider(provider);
|
|
2662
|
-
// Only the ACTIVE provider may write central wallet state. A request from
|
|
2663
|
-
// a second, non-active wallet would otherwise overwrite the active
|
|
2664
|
-
// provider's address and chain, and every later request through the active
|
|
2665
|
-
// provider would then trust the other wallet's chain - persistent
|
|
2666
|
-
// mis-attribution, and a way around `excludeChains`.
|
|
2667
|
-
if (!provider || provider === this._provider || !this._provider) {
|
|
2668
|
-
this.backfillActiveWallet(validAddress, chainId, provider);
|
|
2669
|
-
}
|
|
2670
|
-
return [2 /*return*/, {
|
|
2671
|
-
chainId: chainId,
|
|
2672
|
-
data: data,
|
|
2673
|
-
address: validAddress,
|
|
2674
|
-
to: to,
|
|
2675
|
-
value: value,
|
|
2676
|
-
}];
|
|
2677
|
-
});
|
|
2678
|
-
});
|
|
2679
|
-
};
|
|
2680
|
-
/**
|
|
2681
|
-
* Persist an EVM address discovered through autocapture (signature / transaction)
|
|
2682
|
-
* as the current EVM address when none is currently set. This lets subsequent
|
|
2683
|
-
* track()/page() calls carry the address even when the underlying wallet never
|
|
2684
|
-
* fires an EIP-1193 `accountsChanged` event (embedded wallets, smart accounts,
|
|
2685
|
-
* social-login wrappers). If `accountsChanged` later fires it overwrites this
|
|
2686
|
-
* value in the normal way; existing connections are never clobbered.
|
|
2687
|
-
*/
|
|
2688
|
-
FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId, provider) {
|
|
2689
|
-
// Refuse a chain the provider has since moved off.
|
|
2690
|
-
//
|
|
2691
|
-
// A request captures its chain once and reuses that snapshot for every
|
|
2692
|
-
// status it emits, which is right for the event payload - a confirmation
|
|
2693
|
-
// must not be relabelled mid-flight. But writing that captured value back
|
|
2694
|
-
// into central state on the LATER statuses restored a chain the wallet had
|
|
2695
|
-
// already left, and the unscoped events that fall back to it then bypassed
|
|
2696
|
-
// an exclusion that should have caught them.
|
|
2697
|
-
if (provider && chainId !== undefined && chainId !== 0) {
|
|
2698
|
-
var current = this._providerChainIds.get(provider);
|
|
2699
|
-
if (current !== undefined && current !== chainId) {
|
|
2700
|
-
chainId = current;
|
|
2701
|
-
}
|
|
2702
|
-
}
|
|
2703
|
-
// `0` is kept deliberately. It means "could not resolve", and persisting
|
|
2704
|
-
// it is what lets the exclusion gate refuse the unscoped events - `page`,
|
|
2705
|
-
// `track`, `identify` - that carry no chain of their own and fall back to
|
|
2706
|
-
// `currentChainId`. Erasing it to `undefined` looked tidier but removed
|
|
2707
|
-
// the only marker distinguishing "unknown" from "no wallet yet", and those
|
|
2708
|
-
// events then went out attributed to a wallet that might be sitting on an
|
|
2709
|
-
// excluded chain.
|
|
2710
|
-
// Never learn identity while suppressed (opt-out / timezone / excluded host
|
|
2711
|
-
// or path). A signature/transaction observed on an excluded route must not
|
|
2712
|
-
// populate currentAddress for later allowed-page events.
|
|
2713
|
-
if (this.isTrackingSuppressed())
|
|
2714
|
-
return;
|
|
2715
|
-
var known = this._evmAddress;
|
|
2716
|
-
if (known) {
|
|
2717
|
-
// Same wallet, newer chain: correct it rather than returning.
|
|
2718
|
-
//
|
|
2719
|
-
// A persisted wallet restores a chain from a previous session. If the
|
|
2720
|
-
// provider has since moved - to an excluded chain, or to one we cannot
|
|
2721
|
-
// resolve - the autocaptured event is gated correctly, but the stale
|
|
2722
|
-
// restored chain stayed in `currentChainId` and the unscoped events
|
|
2723
|
-
// that fall back to it went out under an exclusion that should have
|
|
2724
|
-
// caught them.
|
|
2725
|
-
if (chainId !== undefined &&
|
|
2726
|
-
known.toLowerCase() === address.toLowerCase() &&
|
|
2727
|
-
this._evmChainId !== chainId) {
|
|
2728
|
-
this.setChainState('evm', { address: known, chainId: chainId });
|
|
2729
|
-
}
|
|
2730
|
-
// A DIFFERENT address is another wallet's business; never overwrite.
|
|
2731
|
-
return;
|
|
2732
|
-
}
|
|
2733
|
-
this.setChainState('evm', { address: address, chainId: chainId });
|
|
2734
|
-
};
|
|
2735
|
-
/**
|
|
2736
|
-
* Apply an EVM autocapture connect/switch while tracking is suppressed
|
|
2737
|
-
* (opt-out / timezone / excluded host or path): never LEARN the wallet, but
|
|
2738
|
-
* if it is a switch away from an already-learned EVM wallet, drop the stale
|
|
2739
|
-
* one (which also clears the active-wallet cookie) so it can't attach to a
|
|
2740
|
-
* later allowed-page event.
|
|
2741
|
-
*/
|
|
2742
|
-
FormoAnalytics.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
|
|
2743
|
-
var evmAddress = this._chainState.evm.address;
|
|
2744
|
-
var incoming = validateAndChecksumAddress(address);
|
|
2745
|
-
if (evmAddress && incoming && incoming !== evmAddress) {
|
|
2746
|
-
this.clearChainState('evm');
|
|
2747
|
-
}
|
|
2748
|
-
};
|
|
2749
|
-
/**
|
|
2750
|
-
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
2751
|
-
*/
|
|
2752
|
-
FormoAnalytics.prototype.pollTransactionReceipt = function (provider_1, transactionHash_2, payload_1) {
|
|
2753
|
-
return __awaiter(this, arguments, void 0, function (provider, transactionHash, payload, maxAttempts, intervalMs) {
|
|
2754
|
-
var attempts, poll;
|
|
2755
|
-
var _this = this;
|
|
2756
|
-
if (maxAttempts === void 0) { maxAttempts = 10; }
|
|
2757
|
-
if (intervalMs === void 0) { intervalMs = 3000; }
|
|
2758
|
-
return __generator(this, function (_a) {
|
|
2759
|
-
attempts = 0;
|
|
2760
|
-
if (!provider)
|
|
2761
|
-
return [2 /*return*/];
|
|
2762
|
-
poll = function () { return __awaiter(_this, void 0, void 0, function () {
|
|
2763
|
-
var receipt, e_12;
|
|
2764
|
-
return __generator(this, function (_a) {
|
|
2765
|
-
switch (_a.label) {
|
|
2766
|
-
case 0:
|
|
2767
|
-
_a.trys.push([0, 2, , 3]);
|
|
2768
|
-
return [4 /*yield*/, provider.request({
|
|
2769
|
-
method: "eth_getTransactionReceipt",
|
|
2770
|
-
params: [transactionHash],
|
|
2771
|
-
})];
|
|
2772
|
-
case 1:
|
|
2773
|
-
receipt = (_a.sent());
|
|
2774
|
-
if (receipt) {
|
|
2775
|
-
// status: 1 = success, 0 = reverted
|
|
2776
|
-
if (receipt.status === "0x1" || receipt.status === 1) {
|
|
2777
|
-
this.transaction(__assign(__assign({ status: TransactionStatus.CONFIRMED }, payload), { transactionHash: transactionHash }));
|
|
2778
|
-
return [2 /*return*/];
|
|
2779
|
-
}
|
|
2780
|
-
else if (receipt.status === "0x0" || receipt.status === 0) {
|
|
2781
|
-
this.transaction(__assign(__assign({ status: TransactionStatus.REVERTED }, payload), { transactionHash: transactionHash }));
|
|
2782
|
-
return [2 /*return*/];
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
return [3 /*break*/, 3];
|
|
2786
|
-
case 2:
|
|
2787
|
-
e_12 = _a.sent();
|
|
2788
|
-
logger.error("Error polling transaction receipt", e_12);
|
|
2789
|
-
return [3 /*break*/, 3];
|
|
2790
|
-
case 3:
|
|
2791
|
-
attempts++;
|
|
2792
|
-
if (attempts < maxAttempts) {
|
|
2793
|
-
setTimeout(poll, intervalMs);
|
|
2794
|
-
}
|
|
2795
|
-
return [2 /*return*/];
|
|
2796
|
-
}
|
|
2797
|
-
});
|
|
2798
|
-
}); };
|
|
2799
|
-
poll();
|
|
2800
|
-
return [2 /*return*/];
|
|
2801
|
-
});
|
|
2802
|
-
});
|
|
2803
|
-
};
|
|
2804
|
-
FormoAnalytics.prototype.removeProviderListeners = function (provider) {
|
|
2805
|
-
var listeners = this._providerListenersMap.get(provider);
|
|
2806
|
-
if (!listeners)
|
|
2807
|
-
return;
|
|
2808
|
-
for (var _i = 0, _a = Object.entries(listeners); _i < _a.length; _i++) {
|
|
2809
|
-
var _b = _a[_i], event_1 = _b[0], fn = _b[1];
|
|
2810
|
-
try {
|
|
2811
|
-
provider.removeListener(event_1, fn);
|
|
2812
|
-
}
|
|
2813
|
-
catch (e) {
|
|
2814
|
-
logger.warn("Failed to remove listener for ".concat(String(event_1)), e);
|
|
2815
|
-
}
|
|
2816
|
-
}
|
|
2817
|
-
this._providerListenersMap.delete(provider);
|
|
2818
|
-
};
|
|
2819
1273
|
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2820
1274
|
// and tracking
|
|
2821
|
-
FormoAnalytics.prototype.untrackProvider = function (provider) {
|
|
2822
|
-
try {
|
|
2823
|
-
this.removeProviderListeners(provider);
|
|
2824
|
-
this._trackedProviders.delete(provider);
|
|
2825
|
-
if (this._provider === provider) {
|
|
2826
|
-
this.clearActiveProvider();
|
|
2827
|
-
}
|
|
2828
|
-
}
|
|
2829
|
-
catch (e) {
|
|
2830
|
-
logger.warn("Failed to untrack provider", e);
|
|
2831
|
-
}
|
|
2832
|
-
};
|
|
2833
1275
|
// Debug/monitoring helpers
|
|
2834
1276
|
FormoAnalytics.prototype.getTrackedProvidersCount = function () {
|
|
2835
|
-
return this.
|
|
1277
|
+
return this.evm.counts.trackedProviders;
|
|
2836
1278
|
};
|
|
2837
1279
|
/**
|
|
2838
1280
|
* Get current provider state for debugging
|
|
2839
1281
|
* @returns Object containing current provider state information
|
|
2840
1282
|
*/
|
|
2841
1283
|
FormoAnalytics.prototype.getProviderState = function () {
|
|
2842
|
-
return {
|
|
2843
|
-
totalProviders: this._providers.length,
|
|
2844
|
-
trackedProviders: this._trackedProviders.size,
|
|
2845
|
-
seenProviders: this._seenProviders.size,
|
|
2846
|
-
activeProvider: !!this._provider,
|
|
2847
|
-
};
|
|
2848
|
-
};
|
|
2849
|
-
/**
|
|
2850
|
-
* Clean up providers that are no longer available
|
|
2851
|
-
* This helps maintain consistent state and prevents memory leaks
|
|
2852
|
-
*/
|
|
2853
|
-
FormoAnalytics.prototype.cleanupUnavailableProviders = function () {
|
|
2854
|
-
// Remove providers that are no longer in the current providers list
|
|
2855
|
-
var currentProviderInstances = new Set(this._providers.map(function (detail) { return detail.provider; }));
|
|
2856
|
-
for (var _i = 0, _a = Array.from(this._trackedProviders); _i < _a.length; _i++) {
|
|
2857
|
-
var provider = _a[_i];
|
|
2858
|
-
if (!currentProviderInstances.has(provider)) {
|
|
2859
|
-
logger.info("Cleaning up unavailable provider: ".concat(provider.constructor.name));
|
|
2860
|
-
this.untrackProvider(provider);
|
|
2861
|
-
}
|
|
2862
|
-
}
|
|
2863
|
-
};
|
|
2864
|
-
/**
|
|
2865
|
-
* Helper method to check if a provider is already wrapped
|
|
2866
|
-
* @param provider The provider to check
|
|
2867
|
-
* @param currentRequest The current request function
|
|
2868
|
-
* @returns true if the provider is already wrapped
|
|
2869
|
-
*/
|
|
2870
|
-
FormoAnalytics.prototype.isProviderAlreadyWrapped = function (provider, currentRequest) {
|
|
2871
|
-
return !!(currentRequest &&
|
|
2872
|
-
typeof currentRequest === "function" &&
|
|
2873
|
-
currentRequest[WRAPPED_REQUEST_SYMBOL] &&
|
|
2874
|
-
provider[WRAPPED_REQUEST_REF_SYMBOL] ===
|
|
2875
|
-
currentRequest);
|
|
2876
|
-
};
|
|
2877
|
-
/**
|
|
2878
|
-
* Handle provider mismatch by switching to the new provider and invalidating old tokens
|
|
2879
|
-
* @param provider The new provider to switch to
|
|
2880
|
-
*/
|
|
2881
|
-
FormoAnalytics.prototype.handleProviderMismatch = function (provider) {
|
|
2882
|
-
// If this is a different provider, allow the switch
|
|
2883
|
-
if (this._provider) {
|
|
2884
|
-
// Clear any provider-specific state when switching
|
|
2885
|
-
this.setChainState('evm', { address: undefined, chainId: undefined, provider: provider });
|
|
2886
|
-
}
|
|
2887
|
-
else {
|
|
2888
|
-
this._provider = provider;
|
|
2889
|
-
}
|
|
2890
|
-
};
|
|
2891
|
-
/**
|
|
2892
|
-
* Determine which namespace a chainId belongs to.
|
|
2893
|
-
*/
|
|
2894
|
-
FormoAnalytics.prototype.getNamespace = function (chainId) {
|
|
2895
|
-
return isSolanaChainId(chainId) ? 'solana' : 'evm';
|
|
2896
|
-
};
|
|
2897
|
-
/**
|
|
2898
|
-
* Update per-chain state and sync the derived currentAddress/currentChainId.
|
|
2899
|
-
* Accepts either a namespace string ('evm'/'solana') or a chainId number
|
|
2900
|
-
* to resolve the namespace automatically. When a chainId number is passed,
|
|
2901
|
-
* it is also stored as the namespace's chainId (unless explicitly overridden
|
|
2902
|
-
* in the update object).
|
|
2903
|
-
*/
|
|
2904
|
-
FormoAnalytics.prototype.setChainState = function (namespaceOrChainId, update) {
|
|
2905
|
-
var namespace = typeof namespaceOrChainId === 'string'
|
|
2906
|
-
? namespaceOrChainId
|
|
2907
|
-
: this.getNamespace(namespaceOrChainId);
|
|
2908
|
-
var ns = this._chainState[namespace];
|
|
2909
|
-
if ('address' in update)
|
|
2910
|
-
ns.address = update.address;
|
|
2911
|
-
if ('chainId' in update) {
|
|
2912
|
-
ns.chainId = update.chainId;
|
|
2913
|
-
}
|
|
2914
|
-
else if (typeof namespaceOrChainId === 'number') {
|
|
2915
|
-
ns.chainId = namespaceOrChainId;
|
|
2916
|
-
}
|
|
2917
|
-
if (namespace === 'evm' && 'provider' in update) {
|
|
2918
|
-
// Displacing the active provider ends its connection as far as this SDK
|
|
2919
|
-
// is concerned, so the connect it reported stops counting.
|
|
2920
|
-
this.forgetConnectRecord(ns.provider, update.provider);
|
|
2921
|
-
ns.provider = update.provider;
|
|
2922
|
-
}
|
|
2923
|
-
this._activeNamespace = namespace;
|
|
2924
|
-
this.syncDerivedState();
|
|
2925
|
-
};
|
|
2926
|
-
/**
|
|
2927
|
-
* Clear per-chain state for a given namespace (or chainId) and sync derived state.
|
|
2928
|
-
*/
|
|
2929
|
-
FormoAnalytics.prototype.clearChainState = function (namespaceOrChainId) {
|
|
2930
|
-
var namespace = typeof namespaceOrChainId === 'string'
|
|
2931
|
-
? namespaceOrChainId
|
|
2932
|
-
: this.getNamespace(namespaceOrChainId);
|
|
2933
|
-
if (namespace === 'evm') {
|
|
2934
|
-
// Same rule: wiping the namespace drops the active provider.
|
|
2935
|
-
this.forgetConnectRecord(this._chainState.evm.provider, undefined);
|
|
2936
|
-
this._chainState.evm = {};
|
|
2937
|
-
}
|
|
2938
|
-
else {
|
|
2939
|
-
this._chainState.solana = {};
|
|
2940
|
-
}
|
|
2941
|
-
this.syncDerivedState();
|
|
2942
|
-
};
|
|
2943
|
-
/**
|
|
2944
|
-
* Sync validated wallet/chain state into the SDK's central state
|
|
2945
|
-
* WITHOUT emitting an event.
|
|
2946
|
-
*
|
|
2947
|
-
* Integrations (e.g. the wagmi handler) must call this on every
|
|
2948
|
-
* connect / chain-change / disconnect - even when the corresponding
|
|
2949
|
-
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
2950
|
-
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
2951
|
-
* check (which keys off `currentChainId`, not the event payload) can
|
|
2952
|
-
* be bypassed, letting wallet activity on an excluded chain still be
|
|
2953
|
-
* collected.
|
|
2954
|
-
*
|
|
2955
|
-
* - valid `address` present → record per-chain + derived state
|
|
2956
|
-
* - `address` absent → clear chain state (disconnect)
|
|
2957
|
-
*/
|
|
2958
|
-
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
2959
|
-
var chainId = params.chainId, address = params.address;
|
|
2960
|
-
if (this.isTrackingSuppressed()) {
|
|
2961
|
-
// While suppressed (opt-out / timezone / excluded host or path) we must
|
|
2962
|
-
// never LEARN a new wallet - but we must still CLEAR stale identity.
|
|
2963
|
-
// Otherwise a disconnect or wallet switch observed on a suppressed route
|
|
2964
|
-
// would leave the previously-learned address in memory and in the
|
|
2965
|
-
// active-wallet cookie, attaching it to later allowed-page events.
|
|
2966
|
-
if (!address) {
|
|
2967
|
-
// Disconnect: drop the affected namespace(s).
|
|
2968
|
-
if (chainId !== undefined && chainId !== null) {
|
|
2969
|
-
this.clearChainState(chainId);
|
|
2970
|
-
}
|
|
2971
|
-
else {
|
|
2972
|
-
this.clearChainState("evm");
|
|
2973
|
-
this.clearChainState("solana");
|
|
2974
|
-
}
|
|
2975
|
-
return;
|
|
2976
|
-
}
|
|
2977
|
-
// Address present: a switch away from the wallet already learned in this
|
|
2978
|
-
// namespace invalidates it. Drop the stale one without learning the new
|
|
2979
|
-
// address; a fresh connect (nothing learned yet) or a re-confirmation of
|
|
2980
|
-
// the same address is a no-op.
|
|
2981
|
-
if (chainId === null || chainId === undefined)
|
|
2982
|
-
return;
|
|
2983
|
-
var namespace = this.getNamespace(chainId);
|
|
2984
|
-
var namespaceAddress = this._chainState[namespace].address;
|
|
2985
|
-
var validIncoming = validateAddress(address, chainId);
|
|
2986
|
-
if (namespaceAddress &&
|
|
2987
|
-
validIncoming &&
|
|
2988
|
-
validIncoming !== namespaceAddress) {
|
|
2989
|
-
this.clearChainState(chainId);
|
|
2990
|
-
}
|
|
2991
|
-
return;
|
|
2992
|
-
}
|
|
2993
|
-
if (!address) {
|
|
2994
|
-
if (chainId !== undefined && chainId !== null) {
|
|
2995
|
-
this.clearChainState(chainId);
|
|
2996
|
-
}
|
|
2997
|
-
else {
|
|
2998
|
-
this.clearChainState("evm");
|
|
2999
|
-
this.clearChainState("solana");
|
|
3000
|
-
}
|
|
3001
|
-
return;
|
|
3002
|
-
}
|
|
3003
|
-
if (chainId === null || chainId === undefined)
|
|
3004
|
-
return;
|
|
3005
|
-
var validAddress = validateAddress(address, chainId);
|
|
3006
|
-
if (!validAddress) {
|
|
3007
|
-
logger.warn("syncWalletState: invalid address (\"".concat(address, "\") for chain ").concat(chainId));
|
|
3008
|
-
return;
|
|
3009
|
-
}
|
|
3010
|
-
this.setChainState(chainId, { address: validAddress });
|
|
3011
|
-
};
|
|
3012
|
-
/**
|
|
3013
|
-
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
3014
|
-
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
3015
|
-
*/
|
|
3016
|
-
FormoAnalytics.prototype.syncDerivedState = function () {
|
|
3017
|
-
var active = this._activeNamespace;
|
|
3018
|
-
if (active) {
|
|
3019
|
-
var state = this._chainState[active];
|
|
3020
|
-
if (state.address || state.chainId) {
|
|
3021
|
-
this.currentAddress = state.address;
|
|
3022
|
-
this.currentChainId = state.chainId;
|
|
3023
|
-
this.persistActiveWallet();
|
|
3024
|
-
return;
|
|
3025
|
-
}
|
|
3026
|
-
}
|
|
3027
|
-
// Fall through to the other namespace
|
|
3028
|
-
var other = active === 'evm' ? 'solana' : 'evm';
|
|
3029
|
-
var otherState = this._chainState[other];
|
|
3030
|
-
if (otherState.address || otherState.chainId) {
|
|
3031
|
-
this.currentAddress = otherState.address;
|
|
3032
|
-
this.currentChainId = otherState.chainId;
|
|
3033
|
-
this._activeNamespace = other;
|
|
3034
|
-
this.persistActiveWallet();
|
|
3035
|
-
return;
|
|
3036
|
-
}
|
|
3037
|
-
this.currentAddress = undefined;
|
|
3038
|
-
this.currentChainId = undefined;
|
|
3039
|
-
this.persistActiveWallet();
|
|
3040
|
-
};
|
|
3041
|
-
/**
|
|
3042
|
-
* Persist (or clear) the current wallet snapshot in a cookie so that the
|
|
3043
|
-
* SDK can repopulate `currentAddress`/`currentChainId` at init on the next
|
|
3044
|
-
* page load - closing the gap between page-show and wagmi/EIP-1193
|
|
3045
|
-
* reconnection during which track()/page() events would otherwise ship
|
|
3046
|
-
* with an empty address.
|
|
3047
|
-
*/
|
|
3048
|
-
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
3049
|
-
try {
|
|
3050
|
-
// Visitor-level suppression (opt-out or excluded timezone): purge any
|
|
3051
|
-
// prior snapshot - these are stable for the session, so deletion is safe.
|
|
3052
|
-
if (this.isPersistedIdentityPurgeRequired()) {
|
|
3053
|
-
cookie().remove(ACTIVE_WALLET_KEY);
|
|
3054
|
-
return;
|
|
3055
|
-
}
|
|
3056
|
-
if (this.currentAddress) {
|
|
3057
|
-
// Current-page exclusion (host/path): do not write a new snapshot while
|
|
3058
|
-
// on an excluded route, but leave any existing cookie intact. A cookie
|
|
3059
|
-
// written on an allowed page must survive a transient visit to an
|
|
3060
|
-
// excluded one (passive navigation does not call this method).
|
|
3061
|
-
if (this.isHostExcluded() || this.isPathExcluded()) {
|
|
3062
|
-
return;
|
|
3063
|
-
}
|
|
3064
|
-
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
3065
|
-
var domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
3066
|
-
cookie().set(ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
3067
|
-
}
|
|
3068
|
-
else {
|
|
3069
|
-
// No active wallet → clear the snapshot. This runs even on an excluded
|
|
3070
|
-
// route, so a disconnect/switch observed while suppressed actively
|
|
3071
|
-
// removes stale identity instead of leaving it for later allowed events.
|
|
3072
|
-
cookie().remove(ACTIVE_WALLET_KEY);
|
|
3073
|
-
}
|
|
3074
|
-
}
|
|
3075
|
-
catch (err) {
|
|
3076
|
-
logger.warn("Failed to persist current wallet snapshot", err);
|
|
3077
|
-
}
|
|
3078
|
-
};
|
|
3079
|
-
/**
|
|
3080
|
-
* Seed `currentAddress`/`currentChainId` from the persisted snapshot, if
|
|
3081
|
-
* any. Called once during construction before the first page hit fires.
|
|
3082
|
-
*/
|
|
3083
|
-
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
3084
|
-
try {
|
|
3085
|
-
// Visitor-level suppression (opt-out or excluded timezone): never restore
|
|
3086
|
-
// identity into memory; drop the stale snapshot.
|
|
3087
|
-
if (this.isPersistedIdentityPurgeRequired()) {
|
|
3088
|
-
cookie().remove(ACTIVE_WALLET_KEY);
|
|
3089
|
-
return;
|
|
3090
|
-
}
|
|
3091
|
-
// Current-page exclusion (host/path): don't restore into memory while on
|
|
3092
|
-
// an excluded route, but keep the cookie so a later allowed-page load can
|
|
3093
|
-
// restore it.
|
|
3094
|
-
if (this.isHostExcluded() || this.isPathExcluded()) {
|
|
3095
|
-
return;
|
|
3096
|
-
}
|
|
3097
|
-
var raw = cookie().get(ACTIVE_WALLET_KEY);
|
|
3098
|
-
if (!raw)
|
|
3099
|
-
return;
|
|
3100
|
-
var parsed = JSON.parse(raw);
|
|
3101
|
-
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.address))
|
|
3102
|
-
return;
|
|
3103
|
-
var namespace = isSolanaChainId(parsed.chainId) ? "solana" : "evm";
|
|
3104
|
-
var validated = validateAddress(parsed.address, parsed.chainId);
|
|
3105
|
-
if (!validated) {
|
|
3106
|
-
cookie().remove(ACTIVE_WALLET_KEY);
|
|
3107
|
-
return;
|
|
3108
|
-
}
|
|
3109
|
-
var ns = this._chainState[namespace];
|
|
3110
|
-
ns.address = validated;
|
|
3111
|
-
if (parsed.chainId !== undefined)
|
|
3112
|
-
ns.chainId = parsed.chainId;
|
|
3113
|
-
this._activeNamespace = namespace;
|
|
3114
|
-
this.currentAddress = validated;
|
|
3115
|
-
this.currentChainId = parsed.chainId;
|
|
3116
|
-
}
|
|
3117
|
-
catch (err) {
|
|
3118
|
-
logger.warn("Failed to restore persisted wallet snapshot", err);
|
|
3119
|
-
cookie().remove(ACTIVE_WALLET_KEY);
|
|
3120
|
-
}
|
|
3121
|
-
};
|
|
3122
|
-
/**
|
|
3123
|
-
* Helper method to clear the active provider state
|
|
3124
|
-
* Centralizes provider clearing logic for consistency
|
|
3125
|
-
*/
|
|
3126
|
-
FormoAnalytics.prototype.clearActiveProvider = function () {
|
|
3127
|
-
this._provider = undefined;
|
|
3128
|
-
};
|
|
3129
|
-
/**
|
|
3130
|
-
* Helper method to safely add a provider detail to _providers array, ensuring no duplicates
|
|
3131
|
-
* @param detail The provider detail to add
|
|
3132
|
-
* @returns true if the provider was added, false if it was already present
|
|
3133
|
-
*/
|
|
3134
|
-
FormoAnalytics.prototype.safeAddProviderDetail = function (detail) {
|
|
3135
|
-
var provider = detail === null || detail === void 0 ? void 0 : detail.provider;
|
|
3136
|
-
if (!provider)
|
|
3137
|
-
return false;
|
|
3138
|
-
// Check if provider already exists in _providers array
|
|
3139
|
-
var alreadyExists = this._providers.some(function (existing) { return existing.provider === provider; });
|
|
3140
|
-
if (!alreadyExists) {
|
|
3141
|
-
// Add to providers array and mark as seen
|
|
3142
|
-
this._providers = __spreadArray(__spreadArray([], this._providers, true), [detail], false);
|
|
3143
|
-
this._seenProviders.add(provider);
|
|
3144
|
-
return true;
|
|
3145
|
-
}
|
|
3146
|
-
else {
|
|
3147
|
-
// Ensure provider is marked as seen even if it already exists in _providers
|
|
3148
|
-
this._seenProviders.add(provider);
|
|
3149
|
-
return false;
|
|
3150
|
-
}
|
|
1284
|
+
return __assign(__assign({}, this.evm.counts), { activeProvider: !!this._provider });
|
|
3151
1285
|
};
|
|
3152
1286
|
return FormoAnalytics;
|
|
3153
1287
|
}());
|