@formo/analytics 1.29.0 → 1.30.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 +52 -3
- package/dist/cjs/src/FormoAnalytics.js +262 -51
- package/dist/cjs/src/constants/base.d.ts +9 -0
- package/dist/cjs/src/constants/base.js +10 -1
- package/dist/cjs/src/constants/config.d.ts +3 -58
- package/dist/cjs/src/constants/config.js +3 -73
- package/dist/cjs/src/event/EventFactory.d.ts +1 -1
- package/dist/cjs/src/event/EventFactory.js +12 -6
- package/dist/cjs/src/event/EventManager.d.ts +2 -0
- package/dist/cjs/src/event/EventManager.js +4 -0
- package/dist/cjs/src/event/type.d.ts +1 -0
- package/dist/cjs/src/event/utils.js +1 -1
- package/dist/cjs/src/queue/EventQueue.d.ts +8 -0
- package/dist/cjs/src/queue/EventQueue.js +52 -6
- package/dist/cjs/src/queue/type.d.ts +1 -0
- package/dist/cjs/src/session/index.js +16 -8
- package/dist/cjs/src/storage/cookiePolicy.d.ts +16 -0
- package/dist/cjs/src/storage/cookiePolicy.js +19 -0
- package/dist/cjs/src/types/base.d.ts +0 -1
- package/dist/cjs/src/types/events.d.ts +0 -1
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/src/wagmi/WagmiEventHandler.js +38 -15
- package/dist/esm/src/FormoAnalytics.d.ts +52 -3
- package/dist/esm/src/FormoAnalytics.js +265 -54
- package/dist/esm/src/constants/base.d.ts +9 -0
- package/dist/esm/src/constants/base.js +9 -0
- package/dist/esm/src/constants/config.d.ts +3 -58
- package/dist/esm/src/constants/config.js +3 -73
- package/dist/esm/src/event/EventFactory.d.ts +1 -1
- package/dist/esm/src/event/EventFactory.js +12 -6
- package/dist/esm/src/event/EventManager.d.ts +2 -0
- package/dist/esm/src/event/EventManager.js +4 -0
- package/dist/esm/src/event/type.d.ts +1 -0
- package/dist/esm/src/event/utils.js +2 -2
- package/dist/esm/src/queue/EventQueue.d.ts +8 -0
- package/dist/esm/src/queue/EventQueue.js +52 -6
- package/dist/esm/src/queue/type.d.ts +1 -0
- package/dist/esm/src/session/index.js +16 -8
- package/dist/esm/src/storage/cookiePolicy.d.ts +16 -0
- package/dist/esm/src/storage/cookiePolicy.js +18 -0
- package/dist/esm/src/types/base.d.ts +0 -1
- package/dist/esm/src/types/events.d.ts +0 -1
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/esm/src/wagmi/WagmiEventHandler.js +38 -15
- package/dist/index.umd.min.js +1 -1
- package/package.json +13 -23
|
@@ -54,6 +54,10 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
54
54
|
private crossSubdomainCookies;
|
|
55
55
|
/** In-memory URL used to deduplicate SPA pageview events. */
|
|
56
56
|
private _currentUrl;
|
|
57
|
+
/** Page-hit hooks installed in trackPageHits() so cleanup() can undo them. */
|
|
58
|
+
private _onPopStateListener?;
|
|
59
|
+
private _onLocationChangeListener?;
|
|
60
|
+
private _pageHooksDisposed;
|
|
57
61
|
config: Config;
|
|
58
62
|
currentChainId?: ChainID;
|
|
59
63
|
currentAddress?: Address;
|
|
@@ -132,18 +136,16 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
132
136
|
* @param {ChainID} params.chainId
|
|
133
137
|
* @param {Address} params.address
|
|
134
138
|
* @param {string} params.message
|
|
135
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
136
139
|
* @param {IFormoEventProperties} properties
|
|
137
140
|
* @param {IFormoEventContext} context
|
|
138
141
|
* @param {(...args: unknown[]) => void} callback
|
|
139
142
|
* @returns {Promise<void>}
|
|
140
143
|
*/
|
|
141
|
-
signature({ status, chainId, address, message,
|
|
144
|
+
signature({ status, chainId, address, message, }: {
|
|
142
145
|
status: SignatureStatus;
|
|
143
146
|
chainId?: ChainID;
|
|
144
147
|
address: Address;
|
|
145
148
|
message: string;
|
|
146
|
-
signatureHash?: string;
|
|
147
149
|
}, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
148
150
|
/**
|
|
149
151
|
* Emits a transaction event.
|
|
@@ -272,6 +274,12 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
272
274
|
private registerRequestListeners;
|
|
273
275
|
private onLocationChange;
|
|
274
276
|
private trackPageHits;
|
|
277
|
+
/**
|
|
278
|
+
* Wrap history.pushState / replaceState exactly once per `history` object,
|
|
279
|
+
* regardless of how many SDK instances are constructed. Uses a Symbol
|
|
280
|
+
* marker so we recognize our own wrapper across module reloads in HMR.
|
|
281
|
+
*/
|
|
282
|
+
private static installHistoryHooksOnce;
|
|
275
283
|
private trackPageHit;
|
|
276
284
|
private trackEvent;
|
|
277
285
|
/**
|
|
@@ -312,6 +320,15 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
312
320
|
private getCurrentChainId;
|
|
313
321
|
private buildSignatureEventPayload;
|
|
314
322
|
private buildTransactionEventPayload;
|
|
323
|
+
/**
|
|
324
|
+
* Persist an EVM address discovered through autocapture (signature / transaction)
|
|
325
|
+
* as the current EVM address when none is currently set. This lets subsequent
|
|
326
|
+
* track()/page() calls carry the address even when the underlying wallet never
|
|
327
|
+
* fires an EIP-1193 `accountsChanged` event (embedded wallets, smart accounts,
|
|
328
|
+
* social-login wrappers). If `accountsChanged` later fires it overwrites this
|
|
329
|
+
* value in the normal way; existing connections are never clobbered.
|
|
330
|
+
*/
|
|
331
|
+
private backfillActiveWallet;
|
|
315
332
|
/**
|
|
316
333
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
317
334
|
*/
|
|
@@ -362,11 +379,43 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
362
379
|
* Clear per-chain state for a given namespace (or chainId) and sync derived state.
|
|
363
380
|
*/
|
|
364
381
|
private clearChainState;
|
|
382
|
+
/**
|
|
383
|
+
* Sync validated wallet/chain state into the SDK's central state
|
|
384
|
+
* WITHOUT emitting an event.
|
|
385
|
+
*
|
|
386
|
+
* Integrations (e.g. the wagmi handler) must call this on every
|
|
387
|
+
* connect / chain-change / disconnect — even when the corresponding
|
|
388
|
+
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
389
|
+
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
390
|
+
* check (which keys off `currentChainId`, not the event payload) can
|
|
391
|
+
* be bypassed, letting wallet activity on an excluded chain still be
|
|
392
|
+
* collected.
|
|
393
|
+
*
|
|
394
|
+
* - valid `address` present → record per-chain + derived state
|
|
395
|
+
* - `address` absent → clear chain state (disconnect)
|
|
396
|
+
*/
|
|
397
|
+
syncWalletState(params: {
|
|
398
|
+
chainId?: ChainID;
|
|
399
|
+
address?: Address;
|
|
400
|
+
}): void;
|
|
365
401
|
/**
|
|
366
402
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
367
403
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
368
404
|
*/
|
|
369
405
|
private syncDerivedState;
|
|
406
|
+
/**
|
|
407
|
+
* Persist (or clear) the current wallet snapshot in a cookie so that the
|
|
408
|
+
* SDK can repopulate `currentAddress`/`currentChainId` at init on the next
|
|
409
|
+
* page load — closing the gap between page-show and wagmi/EIP-1193
|
|
410
|
+
* reconnection during which track()/page() events would otherwise ship
|
|
411
|
+
* with an empty address.
|
|
412
|
+
*/
|
|
413
|
+
private persistActiveWallet;
|
|
414
|
+
/**
|
|
415
|
+
* Seed `currentAddress`/`currentChainId` from the persisted snapshot, if
|
|
416
|
+
* any. Called once during construction before the first page hit fires.
|
|
417
|
+
*/
|
|
418
|
+
private loadActiveWallet;
|
|
370
419
|
/**
|
|
371
420
|
* Helper method to clear the active provider state
|
|
372
421
|
* Centralizes provider clearing logic for consistency
|
|
@@ -85,6 +85,7 @@ var PROVIDER_SWITCH_REASONS = {
|
|
|
85
85
|
var FormoAnalytics = /** @class */ (function () {
|
|
86
86
|
function FormoAnalytics(writeKey, options) {
|
|
87
87
|
if (options === void 0) { options = {}; }
|
|
88
|
+
var _this = this;
|
|
88
89
|
var _a, _b, _c;
|
|
89
90
|
this.writeKey = writeKey;
|
|
90
91
|
this.options = options;
|
|
@@ -124,6 +125,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
124
125
|
this.isEvmDisabled = false;
|
|
125
126
|
/** In-memory URL used to deduplicate SPA pageview events. */
|
|
126
127
|
this._currentUrl = "";
|
|
128
|
+
this._pageHooksDisposed = false;
|
|
127
129
|
this.currentUserId = "";
|
|
128
130
|
this.config = {
|
|
129
131
|
writeKey: writeKey,
|
|
@@ -159,6 +161,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
159
161
|
maxQueueSize: options.maxQueueSize,
|
|
160
162
|
flushInterval: options.flushInterval,
|
|
161
163
|
errorHandler: options.errorHandler,
|
|
164
|
+
// Hard consent gate at the queue boundary: nothing buffered is
|
|
165
|
+
// ever sent once the user has opted out, even via a timer or
|
|
166
|
+
// pagehide flush scheduled before opt-out.
|
|
167
|
+
canSend: function () { return !_this.hasOptedOutTracking(); },
|
|
162
168
|
}), options);
|
|
163
169
|
// Check consent status on initialization
|
|
164
170
|
if (this.hasOptedOutTracking()) {
|
|
@@ -191,6 +197,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
191
197
|
this.solanaManager = new SolanaManager_1.SolanaManager(this, options.solana);
|
|
192
198
|
}
|
|
193
199
|
this._currentUrl = window.location.href;
|
|
200
|
+
// Seed currentAddress/currentChainId from the persisted snapshot before
|
|
201
|
+
// the first page hit queues so reload-time track()/page() carry the
|
|
202
|
+
// wallet even before wagmi/EIP-1193 reconnection completes.
|
|
203
|
+
this.loadActiveWallet();
|
|
194
204
|
this.trackPageHit();
|
|
195
205
|
this.trackPageHits();
|
|
196
206
|
}
|
|
@@ -292,10 +302,25 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
292
302
|
*/
|
|
293
303
|
FormoAnalytics.prototype.reset = function () {
|
|
294
304
|
this.currentUserId = undefined;
|
|
305
|
+
// Clear in-memory wallet identity too. Without this, a logout/reset
|
|
306
|
+
// (also triggered by optOutTracking) still leaks the previous wallet
|
|
307
|
+
// address on subsequent track()/page() events for the rest of the
|
|
308
|
+
// page lifetime, because they fall back to currentAddress. Keep the
|
|
309
|
+
// EVM provider reference so tracking can resume on the next connect.
|
|
310
|
+
this.currentAddress = undefined;
|
|
311
|
+
this.currentChainId = undefined;
|
|
312
|
+
var evmProvider = this._chainState.evm.provider;
|
|
313
|
+
this._chainState = { evm: { provider: evmProvider }, solana: {} };
|
|
314
|
+
this._activeNamespace = undefined;
|
|
295
315
|
(0, storage_1.cookie)().remove(constants_1.LOCAL_ANONYMOUS_ID_KEY);
|
|
296
316
|
(0, storage_1.cookie)().remove(constants_1.SESSION_USER_ID_KEY);
|
|
297
317
|
(0, storage_1.cookie)().remove(session_1.SESSION_WALLET_DETECTED_KEY);
|
|
298
318
|
(0, storage_1.cookie)().remove(session_1.SESSION_WALLET_IDENTIFIED_KEY);
|
|
319
|
+
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
320
|
+
// Stored traffic-source attribution (referrer/UTM) is tracking data;
|
|
321
|
+
// clear it too so reset()/optOutTracking() don't leave it to be
|
|
322
|
+
// re-attached to the next session's events.
|
|
323
|
+
(0, storage_1.session)().remove(constants_1.SESSION_TRAFFIC_SOURCE_KEY);
|
|
299
324
|
};
|
|
300
325
|
/**
|
|
301
326
|
* Clean up resources and event listeners
|
|
@@ -304,6 +329,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
304
329
|
*/
|
|
305
330
|
FormoAnalytics.prototype.cleanup = function () {
|
|
306
331
|
logger_1.logger.debug("FormoAnalytics: Cleaning up resources");
|
|
332
|
+
// Drop buffered events so a torn-down instance can't flush later.
|
|
333
|
+
this.eventManager.clear();
|
|
307
334
|
// Clean up Wagmi handler if present
|
|
308
335
|
if (this.wagmiHandler) {
|
|
309
336
|
this.wagmiHandler.cleanup();
|
|
@@ -321,6 +348,21 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
321
348
|
this.untrackProvider(provider);
|
|
322
349
|
}
|
|
323
350
|
}
|
|
351
|
+
// Tear down page-hit hooks: remove window listeners and silence the
|
|
352
|
+
// history.pushState/replaceState wrappers so an orphaned instance (e.g.
|
|
353
|
+
// from a re-mount in React Strict Mode / HMR) stops emitting page events
|
|
354
|
+
// with stale state.
|
|
355
|
+
this._pageHooksDisposed = true;
|
|
356
|
+
if (typeof window !== "undefined") {
|
|
357
|
+
if (this._onPopStateListener) {
|
|
358
|
+
window.removeEventListener("popstate", this._onPopStateListener);
|
|
359
|
+
this._onPopStateListener = undefined;
|
|
360
|
+
}
|
|
361
|
+
if (this._onLocationChangeListener) {
|
|
362
|
+
window.removeEventListener("locationchange", this._onLocationChangeListener);
|
|
363
|
+
this._onLocationChangeListener = undefined;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
324
366
|
logger_1.logger.debug("FormoAnalytics: Cleanup complete");
|
|
325
367
|
};
|
|
326
368
|
/**
|
|
@@ -452,7 +494,6 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
452
494
|
* @param {ChainID} params.chainId
|
|
453
495
|
* @param {Address} params.address
|
|
454
496
|
* @param {string} params.message
|
|
455
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
456
497
|
* @param {IFormoEventProperties} properties
|
|
457
498
|
* @param {IFormoEventContext} context
|
|
458
499
|
* @param {(...args: unknown[]) => void} callback
|
|
@@ -460,10 +501,15 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
460
501
|
*/
|
|
461
502
|
FormoAnalytics.prototype.signature = function (_a, properties_1, context_1, callback_1) {
|
|
462
503
|
return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
|
|
463
|
-
var status = _b.status, chainId = _b.chainId, address = _b.address, message = _b.message
|
|
504
|
+
var status = _b.status, chainId = _b.chainId, address = _b.address, message = _b.message;
|
|
464
505
|
return __generator(this, function (_c) {
|
|
465
506
|
switch (_c.label) {
|
|
466
|
-
case 0: return [4 /*yield*/, this.trackEvent(constants_1.EventType.SIGNATURE,
|
|
507
|
+
case 0: return [4 /*yield*/, this.trackEvent(constants_1.EventType.SIGNATURE, {
|
|
508
|
+
status: status,
|
|
509
|
+
chainId: chainId,
|
|
510
|
+
address: address,
|
|
511
|
+
message: message,
|
|
512
|
+
}, properties, context, callback)];
|
|
467
513
|
case 1:
|
|
468
514
|
_c.sent();
|
|
469
515
|
return [2 /*return*/];
|
|
@@ -534,6 +580,13 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
534
580
|
switch (_d.label) {
|
|
535
581
|
case 0:
|
|
536
582
|
_d.trys.push([0, 12, , 13]);
|
|
583
|
+
// identify() writes the user-id cookie and marks wallet
|
|
584
|
+
// identification before trackEvent's consent check — gate the
|
|
585
|
+
// whole method so an opted-out user gets no identity persistence.
|
|
586
|
+
if (this.hasOptedOutTracking()) {
|
|
587
|
+
logger_1.logger.info("identify() skipped: user has opted out of tracking");
|
|
588
|
+
return [2 /*return*/];
|
|
589
|
+
}
|
|
537
590
|
if (!!params) return [3 /*break*/, 10];
|
|
538
591
|
// If no params provided, auto-identify
|
|
539
592
|
logger_1.logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
|
|
@@ -600,6 +653,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
600
653
|
validAddress = (0, address_1.validateAddress)(address);
|
|
601
654
|
if (validAddress) {
|
|
602
655
|
this.currentAddress = validAddress;
|
|
656
|
+
this.persistActiveWallet();
|
|
603
657
|
}
|
|
604
658
|
else {
|
|
605
659
|
(_c = logger_1.logger.warn) === null || _c === void 0 ? void 0 : _c.call(logger_1.logger, "Invalid address provided to identify:", address);
|
|
@@ -608,7 +662,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
608
662
|
if (userId) {
|
|
609
663
|
this.currentUserId = userId;
|
|
610
664
|
domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
|
|
611
|
-
(0, storage_1.cookie)().set(constants_1.SESSION_USER_ID_KEY, userId, __assign({ path: "/" }, (domain ? { domain: domain } : {})));
|
|
665
|
+
(0, storage_1.cookie)().set(constants_1.SESSION_USER_ID_KEY, userId, __assign(__assign({ path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()), (domain ? { domain: domain } : {})));
|
|
612
666
|
}
|
|
613
667
|
isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "");
|
|
614
668
|
logger_1.logger.debug("Identify: Checking deduplication", {
|
|
@@ -657,6 +711,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
657
711
|
return __generator(this, function (_c) {
|
|
658
712
|
switch (_c.label) {
|
|
659
713
|
case 0:
|
|
714
|
+
// detect() marks wallet detection (a cookie write) before
|
|
715
|
+
// trackEvent's consent check — gate it on opt-out.
|
|
716
|
+
if (this.hasOptedOutTracking()) {
|
|
717
|
+
logger_1.logger.info("detect() skipped: user has opted out of tracking");
|
|
718
|
+
return [2 /*return*/];
|
|
719
|
+
}
|
|
660
720
|
if (this.session.isWalletDetected(rdns))
|
|
661
721
|
return [2 /*return*/, logger_1.logger.warn("Detect: Wallet ".concat(providerName, " already detected in this session"))];
|
|
662
722
|
this.session.markWalletDetected(rdns);
|
|
@@ -703,6 +763,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
703
763
|
// Set opt-out flag in persistent storage using direct cookie access
|
|
704
764
|
// This must be done before switching storage to ensure persistence
|
|
705
765
|
(0, consent_1.setConsentFlag)(this.writeKey, constants_1.CONSENT_OPT_OUT_KEY, "true");
|
|
766
|
+
// Drop anything already buffered so a pending timer/pagehide flush
|
|
767
|
+
// cannot ship events after consent withdrawal.
|
|
768
|
+
this.eventManager.clear();
|
|
706
769
|
this.reset();
|
|
707
770
|
logger_1.logger.info("Successfully opted out of tracking");
|
|
708
771
|
};
|
|
@@ -1453,35 +1516,52 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1453
1516
|
});
|
|
1454
1517
|
};
|
|
1455
1518
|
FormoAnalytics.prototype.trackPageHits = function () {
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1519
|
+
var _this = this;
|
|
1520
|
+
// Install a single, instance-agnostic wrapper around history.pushState /
|
|
1521
|
+
// replaceState so concurrent SDK instances (React Strict Mode, HMR) don't
|
|
1522
|
+
// each stack their own wrapper — which would dispatch N synthetic events
|
|
1523
|
+
// per navigation and produce O(N^2) onLocationChange calls. The wrapper
|
|
1524
|
+
// dispatches once; per-instance bookkeeping is done by per-instance
|
|
1525
|
+
// listeners that each register/unregister themselves.
|
|
1526
|
+
FormoAnalytics.installHistoryHooksOnce();
|
|
1527
|
+
this._onPopStateListener = function () { return _this.onLocationChange(); };
|
|
1528
|
+
this._onLocationChangeListener = function () { return _this.onLocationChange(); };
|
|
1529
|
+
window.addEventListener("popstate", this._onPopStateListener);
|
|
1530
|
+
window.addEventListener("locationchange", this._onLocationChangeListener);
|
|
1531
|
+
};
|
|
1532
|
+
/**
|
|
1533
|
+
* Wrap history.pushState / replaceState exactly once per `history` object,
|
|
1534
|
+
* regardless of how many SDK instances are constructed. Uses a Symbol
|
|
1535
|
+
* marker so we recognize our own wrapper across module reloads in HMR.
|
|
1536
|
+
*/
|
|
1537
|
+
FormoAnalytics.installHistoryHooksOnce = function () {
|
|
1538
|
+
if (typeof history === "undefined" || typeof window === "undefined")
|
|
1539
|
+
return;
|
|
1540
|
+
var marker = Symbol.for("formo.historyWrapped");
|
|
1541
|
+
if (history[marker])
|
|
1542
|
+
return;
|
|
1543
|
+
history[marker] = true;
|
|
1544
|
+
var dispatch = function () { return window.dispatchEvent(new window.Event("locationchange")); };
|
|
1545
|
+
var oldPushState = history.pushState;
|
|
1546
|
+
history.pushState = function pushState() {
|
|
1547
|
+
var args = [];
|
|
1548
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1549
|
+
args[_i] = arguments[_i];
|
|
1550
|
+
}
|
|
1551
|
+
var ret = oldPushState.apply(this, args);
|
|
1552
|
+
dispatch();
|
|
1553
|
+
return ret;
|
|
1554
|
+
};
|
|
1555
|
+
var oldReplaceState = history.replaceState;
|
|
1556
|
+
history.replaceState = function replaceState() {
|
|
1557
|
+
var args = [];
|
|
1558
|
+
for (var _i = 0; _i < arguments.length; _i++) {
|
|
1559
|
+
args[_i] = arguments[_i];
|
|
1560
|
+
}
|
|
1561
|
+
var ret = oldReplaceState.apply(this, args);
|
|
1562
|
+
dispatch();
|
|
1563
|
+
return ret;
|
|
1564
|
+
};
|
|
1485
1565
|
};
|
|
1486
1566
|
FormoAnalytics.prototype.trackPageHit = function (category, name, properties, context, callback) {
|
|
1487
1567
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -1492,6 +1572,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1492
1572
|
return [2 /*return*/];
|
|
1493
1573
|
}
|
|
1494
1574
|
setTimeout(function () {
|
|
1575
|
+
// Drop in-flight page hits from an SDK instance that was torn down
|
|
1576
|
+
// between scheduling and firing (e.g. provider remount in React Strict
|
|
1577
|
+
// Mode / HMR). Otherwise the orphan instance would queue a page event
|
|
1578
|
+
// here with its stale, never-populated `currentAddress`.
|
|
1579
|
+
if (_this._pageHooksDisposed)
|
|
1580
|
+
return;
|
|
1495
1581
|
(function () { return __awaiter(_this, void 0, void 0, function () {
|
|
1496
1582
|
var e_7;
|
|
1497
1583
|
return __generator(this, function (_a) {
|
|
@@ -1899,7 +1985,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1899
1985
|
});
|
|
1900
1986
|
});
|
|
1901
1987
|
};
|
|
1902
|
-
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
1988
|
+
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
1989
|
+
// Intentionally not read. Kept for positional call-site arity.
|
|
1990
|
+
_response, chainId) {
|
|
1903
1991
|
var _a;
|
|
1904
1992
|
var rawAddress = method === "personal_sign"
|
|
1905
1993
|
? params[1]
|
|
@@ -1908,45 +1996,63 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1908
1996
|
if (!validAddress) {
|
|
1909
1997
|
throw new Error("Invalid address in signature payload: ".concat(rawAddress));
|
|
1910
1998
|
}
|
|
1999
|
+
var effectiveChainId = (_a = chainId !== null && chainId !== void 0 ? chainId : this._evmChainId) !== null && _a !== void 0 ? _a : undefined;
|
|
2000
|
+
this.backfillActiveWallet(validAddress, effectiveChainId);
|
|
1911
2001
|
var basePayload = {
|
|
1912
|
-
chainId:
|
|
2002
|
+
chainId: effectiveChainId,
|
|
1913
2003
|
address: validAddress,
|
|
1914
2004
|
};
|
|
1915
2005
|
if (method === "personal_sign") {
|
|
1916
2006
|
var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
|
|
1917
|
-
return __assign(__assign(
|
|
2007
|
+
return __assign(__assign({}, basePayload), { message: message });
|
|
1918
2008
|
}
|
|
1919
|
-
|
|
2009
|
+
// eth_signTypedData*: params[1] is the full EIP-712 struct.
|
|
2010
|
+
return __assign(__assign({}, basePayload), { message: params[1] });
|
|
1920
2011
|
};
|
|
1921
2012
|
FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
|
|
1922
2013
|
return __awaiter(this, void 0, void 0, function () {
|
|
1923
|
-
var _a, data, from, to, value, validAddress, _b;
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
switch (_d.label) {
|
|
2014
|
+
var _a, data, from, to, value, validAddress, chainId, _b;
|
|
2015
|
+
return __generator(this, function (_c) {
|
|
2016
|
+
switch (_c.label) {
|
|
1927
2017
|
case 0:
|
|
1928
2018
|
_a = params[0], data = _a.data, from = _a.from, to = _a.to, value = _a.value;
|
|
1929
2019
|
validAddress = (0, address_1.validateAndChecksumAddress)(from);
|
|
1930
2020
|
if (!validAddress) {
|
|
1931
2021
|
throw new Error("Invalid address in transaction payload: ".concat(from));
|
|
1932
2022
|
}
|
|
1933
|
-
_c = {};
|
|
1934
2023
|
_b = this._evmChainId;
|
|
1935
2024
|
if (_b) return [3 /*break*/, 2];
|
|
1936
2025
|
return [4 /*yield*/, this.getCurrentChainId(provider)];
|
|
1937
2026
|
case 1:
|
|
1938
|
-
_b = (
|
|
1939
|
-
|
|
1940
|
-
case 2:
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
2027
|
+
_b = (_c.sent());
|
|
2028
|
+
_c.label = 2;
|
|
2029
|
+
case 2:
|
|
2030
|
+
chainId = _b;
|
|
2031
|
+
this.backfillActiveWallet(validAddress, chainId);
|
|
2032
|
+
return [2 /*return*/, {
|
|
2033
|
+
chainId: chainId,
|
|
2034
|
+
data: data,
|
|
2035
|
+
address: validAddress,
|
|
2036
|
+
to: to,
|
|
2037
|
+
value: value,
|
|
2038
|
+
}];
|
|
1946
2039
|
}
|
|
1947
2040
|
});
|
|
1948
2041
|
});
|
|
1949
2042
|
};
|
|
2043
|
+
/**
|
|
2044
|
+
* Persist an EVM address discovered through autocapture (signature / transaction)
|
|
2045
|
+
* as the current EVM address when none is currently set. This lets subsequent
|
|
2046
|
+
* track()/page() calls carry the address even when the underlying wallet never
|
|
2047
|
+
* fires an EIP-1193 `accountsChanged` event (embedded wallets, smart accounts,
|
|
2048
|
+
* social-login wrappers). If `accountsChanged` later fires it overwrites this
|
|
2049
|
+
* value in the normal way; existing connections are never clobbered.
|
|
2050
|
+
*/
|
|
2051
|
+
FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId) {
|
|
2052
|
+
if (this._evmAddress)
|
|
2053
|
+
return;
|
|
2054
|
+
this.setChainState('evm', { address: address, chainId: chainId });
|
|
2055
|
+
};
|
|
1950
2056
|
/**
|
|
1951
2057
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
1952
2058
|
*/
|
|
@@ -2017,7 +2123,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2017
2123
|
}
|
|
2018
2124
|
this._providerListenersMap.delete(provider);
|
|
2019
2125
|
};
|
|
2020
|
-
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2126
|
+
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2127
|
+
// and tracking
|
|
2021
2128
|
FormoAnalytics.prototype.untrackProvider = function (provider) {
|
|
2022
2129
|
try {
|
|
2023
2130
|
this.removeProviderListeners(provider);
|
|
@@ -2135,6 +2242,42 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2135
2242
|
}
|
|
2136
2243
|
this.syncDerivedState();
|
|
2137
2244
|
};
|
|
2245
|
+
/**
|
|
2246
|
+
* Sync validated wallet/chain state into the SDK's central state
|
|
2247
|
+
* WITHOUT emitting an event.
|
|
2248
|
+
*
|
|
2249
|
+
* Integrations (e.g. the wagmi handler) must call this on every
|
|
2250
|
+
* connect / chain-change / disconnect — even when the corresponding
|
|
2251
|
+
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
2252
|
+
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
2253
|
+
* check (which keys off `currentChainId`, not the event payload) can
|
|
2254
|
+
* be bypassed, letting wallet activity on an excluded chain still be
|
|
2255
|
+
* collected.
|
|
2256
|
+
*
|
|
2257
|
+
* - valid `address` present → record per-chain + derived state
|
|
2258
|
+
* - `address` absent → clear chain state (disconnect)
|
|
2259
|
+
*/
|
|
2260
|
+
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
2261
|
+
var chainId = params.chainId, address = params.address;
|
|
2262
|
+
if (!address) {
|
|
2263
|
+
if (chainId !== undefined && chainId !== null) {
|
|
2264
|
+
this.clearChainState(chainId);
|
|
2265
|
+
}
|
|
2266
|
+
else {
|
|
2267
|
+
this.clearChainState("evm");
|
|
2268
|
+
this.clearChainState("solana");
|
|
2269
|
+
}
|
|
2270
|
+
return;
|
|
2271
|
+
}
|
|
2272
|
+
if (chainId === null || chainId === undefined)
|
|
2273
|
+
return;
|
|
2274
|
+
var validAddress = (0, address_1.validateAddress)(address, chainId);
|
|
2275
|
+
if (!validAddress) {
|
|
2276
|
+
logger_1.logger.warn("syncWalletState: invalid address (\"".concat(address, "\") for chain ").concat(chainId));
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
this.setChainState(chainId, { address: validAddress });
|
|
2280
|
+
};
|
|
2138
2281
|
/**
|
|
2139
2282
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
2140
2283
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
@@ -2146,6 +2289,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2146
2289
|
if (state.address || state.chainId) {
|
|
2147
2290
|
this.currentAddress = state.address;
|
|
2148
2291
|
this.currentChainId = state.chainId;
|
|
2292
|
+
this.persistActiveWallet();
|
|
2149
2293
|
return;
|
|
2150
2294
|
}
|
|
2151
2295
|
}
|
|
@@ -2156,10 +2300,77 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2156
2300
|
this.currentAddress = otherState.address;
|
|
2157
2301
|
this.currentChainId = otherState.chainId;
|
|
2158
2302
|
this._activeNamespace = other;
|
|
2303
|
+
this.persistActiveWallet();
|
|
2159
2304
|
return;
|
|
2160
2305
|
}
|
|
2161
2306
|
this.currentAddress = undefined;
|
|
2162
2307
|
this.currentChainId = undefined;
|
|
2308
|
+
this.persistActiveWallet();
|
|
2309
|
+
};
|
|
2310
|
+
/**
|
|
2311
|
+
* Persist (or clear) the current wallet snapshot in a cookie so that the
|
|
2312
|
+
* SDK can repopulate `currentAddress`/`currentChainId` at init on the next
|
|
2313
|
+
* page load — closing the gap between page-show and wagmi/EIP-1193
|
|
2314
|
+
* reconnection during which track()/page() events would otherwise ship
|
|
2315
|
+
* with an empty address.
|
|
2316
|
+
*/
|
|
2317
|
+
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2318
|
+
try {
|
|
2319
|
+
// Never write an identity cookie for an opted-out user; ensure any
|
|
2320
|
+
// prior snapshot is removed instead.
|
|
2321
|
+
if (this.hasOptedOutTracking()) {
|
|
2322
|
+
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
if (this.currentAddress) {
|
|
2326
|
+
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
2327
|
+
var domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
|
|
2328
|
+
(0, storage_1.cookie)().set(constants_1.ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + constants_1.ACTIVE_WALLET_TTL_MS).toUTCString() }, (0, cookiePolicy_1.getIdentityCookieSecurity)()), (domain ? { domain: domain } : {})));
|
|
2329
|
+
}
|
|
2330
|
+
else {
|
|
2331
|
+
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
catch (err) {
|
|
2335
|
+
logger_1.logger.warn("Failed to persist current wallet snapshot", err);
|
|
2336
|
+
}
|
|
2337
|
+
};
|
|
2338
|
+
/**
|
|
2339
|
+
* Seed `currentAddress`/`currentChainId` from the persisted snapshot, if
|
|
2340
|
+
* any. Called once during construction before the first page hit fires.
|
|
2341
|
+
*/
|
|
2342
|
+
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
2343
|
+
try {
|
|
2344
|
+
// Never restore wallet identity into memory for an opted-out user
|
|
2345
|
+
// (mirrors persistActiveWallet's guard); drop any stale snapshot.
|
|
2346
|
+
if (this.hasOptedOutTracking()) {
|
|
2347
|
+
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2348
|
+
return;
|
|
2349
|
+
}
|
|
2350
|
+
var raw = (0, storage_1.cookie)().get(constants_1.ACTIVE_WALLET_KEY);
|
|
2351
|
+
if (!raw)
|
|
2352
|
+
return;
|
|
2353
|
+
var parsed = JSON.parse(raw);
|
|
2354
|
+
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.address))
|
|
2355
|
+
return;
|
|
2356
|
+
var namespace = (0, solana_1.isSolanaChainId)(parsed.chainId) ? "solana" : "evm";
|
|
2357
|
+
var validated = (0, address_1.validateAddress)(parsed.address, parsed.chainId);
|
|
2358
|
+
if (!validated) {
|
|
2359
|
+
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2360
|
+
return;
|
|
2361
|
+
}
|
|
2362
|
+
var ns = this._chainState[namespace];
|
|
2363
|
+
ns.address = validated;
|
|
2364
|
+
if (parsed.chainId !== undefined)
|
|
2365
|
+
ns.chainId = parsed.chainId;
|
|
2366
|
+
this._activeNamespace = namespace;
|
|
2367
|
+
this.currentAddress = validated;
|
|
2368
|
+
this.currentChainId = parsed.chainId;
|
|
2369
|
+
}
|
|
2370
|
+
catch (err) {
|
|
2371
|
+
logger_1.logger.warn("Failed to restore persisted wallet snapshot", err);
|
|
2372
|
+
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2373
|
+
}
|
|
2163
2374
|
};
|
|
2164
2375
|
/**
|
|
2165
2376
|
* Helper method to clear the active provider state
|
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
export declare const SESSION_TRAFFIC_SOURCE_KEY = "traffic-source";
|
|
2
2
|
export declare const SESSION_USER_ID_KEY = "user-id";
|
|
3
|
+
/**
|
|
4
|
+
* Persisted snapshot of the currently-active EVM/Solana wallet
|
|
5
|
+
* (address + chainId). Lets the SDK seed `currentAddress`/`currentChainId`
|
|
6
|
+
* at init so the first page hit after a reload carries the address, without
|
|
7
|
+
* having to wait for wagmi/EIP-1193 reconnection to fire.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ACTIVE_WALLET_KEY = "active-wallet";
|
|
10
|
+
/** TTL for the persisted active-wallet snapshot cookie. */
|
|
11
|
+
export declare const ACTIVE_WALLET_TTL_MS: number;
|
|
3
12
|
export declare const LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
|
|
4
13
|
export declare const CONSENT_OPT_OUT_KEY = "opt-out-tracking";
|
|
5
14
|
export declare const DEFAULT_PROVIDER_ICON = "data:image/svg+xml;base64,";
|
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.BLOCKED_ADDRESSES = exports.DEAD_ADDRESS = exports.ZERO_ADDRESS = exports.DEFAULT_PROVIDER_ICON = exports.CONSENT_OPT_OUT_KEY = exports.LOCAL_ANONYMOUS_ID_KEY = exports.SESSION_USER_ID_KEY = exports.SESSION_TRAFFIC_SOURCE_KEY = void 0;
|
|
3
|
+
exports.BLOCKED_ADDRESSES = exports.DEAD_ADDRESS = exports.ZERO_ADDRESS = exports.DEFAULT_PROVIDER_ICON = exports.CONSENT_OPT_OUT_KEY = exports.LOCAL_ANONYMOUS_ID_KEY = exports.ACTIVE_WALLET_TTL_MS = exports.ACTIVE_WALLET_KEY = exports.SESSION_USER_ID_KEY = exports.SESSION_TRAFFIC_SOURCE_KEY = void 0;
|
|
4
4
|
exports.SESSION_TRAFFIC_SOURCE_KEY = "traffic-source";
|
|
5
5
|
// SESSION_WALLET_DETECTED_KEY and SESSION_WALLET_IDENTIFIED_KEY
|
|
6
6
|
// are now defined in src/session/index.ts
|
|
7
7
|
exports.SESSION_USER_ID_KEY = "user-id";
|
|
8
|
+
/**
|
|
9
|
+
* Persisted snapshot of the currently-active EVM/Solana wallet
|
|
10
|
+
* (address + chainId). Lets the SDK seed `currentAddress`/`currentChainId`
|
|
11
|
+
* at init so the first page hit after a reload carries the address, without
|
|
12
|
+
* having to wait for wagmi/EIP-1193 reconnection to fire.
|
|
13
|
+
*/
|
|
14
|
+
exports.ACTIVE_WALLET_KEY = "active-wallet";
|
|
15
|
+
/** TTL for the persisted active-wallet snapshot cookie. */
|
|
16
|
+
exports.ACTIVE_WALLET_TTL_MS = 24 * 60 * 60 * 1000;
|
|
8
17
|
exports.LOCAL_ANONYMOUS_ID_KEY = "anonymous-id";
|
|
9
18
|
// Consent management keys
|
|
10
19
|
exports.CONSENT_OPT_OUT_KEY = "opt-out-tracking";
|