@formo/analytics 1.29.1 → 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 +20 -3
- package/dist/cjs/src/FormoAnalytics.js +102 -9
- package/dist/cjs/src/event/EventFactory.d.ts +1 -1
- package/dist/cjs/src/event/EventFactory.js +3 -3
- 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 +20 -3
- package/dist/esm/src/FormoAnalytics.js +105 -12
- package/dist/esm/src/event/EventFactory.d.ts +1 -1
- package/dist/esm/src/event/EventFactory.js +3 -3
- 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 +7 -18
|
@@ -173,6 +173,11 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
173
173
|
chainId: chainId,
|
|
174
174
|
});
|
|
175
175
|
if (!(status === "disconnected" && prevStatus === "connected")) return [3 /*break*/, 4];
|
|
176
|
+
// Clear central chain state regardless of autocapture so a later
|
|
177
|
+
// event can't carry a stale excluded/!excluded chainId.
|
|
178
|
+
this.formo.syncWalletState({
|
|
179
|
+
chainId: this.trackingState.lastChainId,
|
|
180
|
+
});
|
|
176
181
|
if (!this.formo.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 3];
|
|
177
182
|
return [4 /*yield*/, this.formo.disconnect({
|
|
178
183
|
chainId: this.trackingState.lastChainId,
|
|
@@ -190,6 +195,9 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
190
195
|
if (!(address && chainId !== undefined)) return [3 /*break*/, 6];
|
|
191
196
|
this.trackingState.lastAddress = address;
|
|
192
197
|
this.trackingState.lastChainId = chainId;
|
|
198
|
+
// Sync central state unconditionally so tracking.excludeChains
|
|
199
|
+
// is enforced even when connect autocapture is disabled.
|
|
200
|
+
this.formo.syncWalletState({ chainId: chainId, address: address });
|
|
193
201
|
if (!this.formo.isAutocaptureEnabled("connect")) return [3 /*break*/, 6];
|
|
194
202
|
connectorName = this.getConnectorName(state);
|
|
195
203
|
return [4 /*yield*/, this.formo.connect({ chainId: chainId, address: address }, __assign({}, (connectorName && { providerName: connectorName })))];
|
|
@@ -238,6 +246,9 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
238
246
|
address: address,
|
|
239
247
|
});
|
|
240
248
|
this.trackingState.lastChainId = chainId;
|
|
249
|
+
// Sync central state unconditionally so a chain switch to an
|
|
250
|
+
// excluded chain is honored even when chain autocapture is disabled.
|
|
251
|
+
this.formo.syncWalletState({ chainId: chainId, address: address });
|
|
241
252
|
if (!this.formo.isAutocaptureEnabled("chain")) return [3 /*break*/, 4];
|
|
242
253
|
_a.label = 1;
|
|
243
254
|
case 1:
|
|
@@ -359,14 +370,21 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
359
370
|
logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no hash found");
|
|
360
371
|
return;
|
|
361
372
|
}
|
|
362
|
-
// Retrieve stored transaction details from BROADCASTED event
|
|
363
|
-
// Normalize hash to lowercase for consistent lookup
|
|
373
|
+
// Retrieve stored transaction details from the BROADCASTED event.
|
|
374
|
+
// Normalize hash to lowercase for consistent lookup.
|
|
364
375
|
var normalizedHash = transactionHash.toLowerCase();
|
|
365
376
|
var pendingTx = this.pendingTransactions.get(normalizedHash);
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
377
|
+
// Only emit receipt-derived events for a hash we actually observed
|
|
378
|
+
// being broadcast through this handler. The QueryClient is supplied
|
|
379
|
+
// by the host app and its cache can be written by app code or other
|
|
380
|
+
// deps, so without this gate a forged waitForTransactionReceipt
|
|
381
|
+
// entry could fabricate a confirmed/reverted transaction for an
|
|
382
|
+
// arbitrary hash. No pendingTx → not our broadcast → ignore.
|
|
383
|
+
if (!pendingTx) {
|
|
384
|
+
logger_1.logger.debug("WagmiEventHandler: Receipt for unobserved tx hash; ignoring", { transactionHash: transactionHash });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
var address = pendingTx.address;
|
|
370
388
|
if (!address) {
|
|
371
389
|
logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no address available");
|
|
372
390
|
return;
|
|
@@ -374,9 +392,13 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
374
392
|
try {
|
|
375
393
|
// Extract receipt data
|
|
376
394
|
var receipt = state.data;
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
395
|
+
// Only act on an explicit on-chain outcome. A missing/unknown
|
|
396
|
+
// status must NOT be treated as a confirmation.
|
|
397
|
+
if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "success" && (receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "reverted") {
|
|
398
|
+
logger_1.logger.debug("WagmiEventHandler: Receipt without explicit success/reverted status; ignoring", { transactionHash: transactionHash, status: receipt === null || receipt === void 0 ? void 0 : receipt.status });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
var txStatus = receipt.status === "reverted"
|
|
380
402
|
? events_1.TransactionStatus.REVERTED
|
|
381
403
|
: events_1.TransactionStatus.CONFIRMED;
|
|
382
404
|
logger_1.logger.info("WagmiEventHandler: Tracking transaction confirmation", {
|
|
@@ -455,15 +477,13 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
455
477
|
return;
|
|
456
478
|
}
|
|
457
479
|
try {
|
|
458
|
-
// Map Wagmi mutation status to Formo signature status
|
|
480
|
+
// Map Wagmi mutation status to Formo signature status.
|
|
459
481
|
var status_1;
|
|
460
|
-
var signatureHash = void 0;
|
|
461
482
|
if (state.status === "pending") {
|
|
462
483
|
status_1 = events_1.SignatureStatus.REQUESTED;
|
|
463
484
|
}
|
|
464
485
|
else if (state.status === "success") {
|
|
465
486
|
status_1 = events_1.SignatureStatus.CONFIRMED;
|
|
466
|
-
signatureHash = state.data;
|
|
467
487
|
}
|
|
468
488
|
else if (state.status === "error") {
|
|
469
489
|
status_1 = events_1.SignatureStatus.REJECTED;
|
|
@@ -471,13 +491,11 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
471
491
|
else {
|
|
472
492
|
return; // Ignore idle state
|
|
473
493
|
}
|
|
474
|
-
// Extract message from variables
|
|
475
494
|
var message = void 0;
|
|
476
495
|
if (mutationType === "signMessage") {
|
|
477
496
|
message = variables.message || "";
|
|
478
497
|
}
|
|
479
498
|
else {
|
|
480
|
-
// For signTypedData, stringify the typed data
|
|
481
499
|
message = JSON.stringify(variables.message || variables.types || {});
|
|
482
500
|
}
|
|
483
501
|
logger_1.logger.info("WagmiEventHandler: Tracking signature event", {
|
|
@@ -486,7 +504,12 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
486
504
|
address: address,
|
|
487
505
|
chainId: chainId,
|
|
488
506
|
});
|
|
489
|
-
this.formo.signature(
|
|
507
|
+
this.formo.signature({
|
|
508
|
+
status: status_1,
|
|
509
|
+
chainId: chainId,
|
|
510
|
+
address: address,
|
|
511
|
+
message: message,
|
|
512
|
+
});
|
|
490
513
|
}
|
|
491
514
|
catch (error) {
|
|
492
515
|
logger_1.logger.error("WagmiEventHandler: Error handling signature mutation:", error);
|
|
@@ -136,18 +136,16 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
136
136
|
* @param {ChainID} params.chainId
|
|
137
137
|
* @param {Address} params.address
|
|
138
138
|
* @param {string} params.message
|
|
139
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
140
139
|
* @param {IFormoEventProperties} properties
|
|
141
140
|
* @param {IFormoEventContext} context
|
|
142
141
|
* @param {(...args: unknown[]) => void} callback
|
|
143
142
|
* @returns {Promise<void>}
|
|
144
143
|
*/
|
|
145
|
-
signature({ status, chainId, address, message,
|
|
144
|
+
signature({ status, chainId, address, message, }: {
|
|
146
145
|
status: SignatureStatus;
|
|
147
146
|
chainId?: ChainID;
|
|
148
147
|
address: Address;
|
|
149
148
|
message: string;
|
|
150
|
-
signatureHash?: string;
|
|
151
149
|
}, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
152
150
|
/**
|
|
153
151
|
* Emits a transaction event.
|
|
@@ -381,6 +379,25 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
381
379
|
* Clear per-chain state for a given namespace (or chainId) and sync derived state.
|
|
382
380
|
*/
|
|
383
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;
|
|
384
401
|
/**
|
|
385
402
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
386
403
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
@@ -55,9 +55,9 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
55
55
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
56
56
|
};
|
|
57
57
|
import { createStore } from "mipd";
|
|
58
|
-
import { EVENTS_API_HOST, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_USER_ID_KEY, ACTIVE_WALLET_KEY, ACTIVE_WALLET_TTL_MS, CONSENT_OPT_OUT_KEY, } from "./constants";
|
|
59
|
-
import { cookie, initStorageManager } from "./storage";
|
|
60
|
-
import { getIdentityCookieDomain } from "./storage/cookiePolicy";
|
|
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";
|
|
59
|
+
import { cookie, session, initStorageManager } from "./storage";
|
|
60
|
+
import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "./storage/cookiePolicy";
|
|
61
61
|
import { EventManager } from "./event";
|
|
62
62
|
import { EventQueue } from "./queue";
|
|
63
63
|
import { logger, Logger } from "./logger";
|
|
@@ -82,6 +82,7 @@ var PROVIDER_SWITCH_REASONS = {
|
|
|
82
82
|
var FormoAnalytics = /** @class */ (function () {
|
|
83
83
|
function FormoAnalytics(writeKey, options) {
|
|
84
84
|
if (options === void 0) { options = {}; }
|
|
85
|
+
var _this = this;
|
|
85
86
|
var _a, _b, _c;
|
|
86
87
|
this.writeKey = writeKey;
|
|
87
88
|
this.options = options;
|
|
@@ -157,6 +158,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
157
158
|
maxQueueSize: options.maxQueueSize,
|
|
158
159
|
flushInterval: options.flushInterval,
|
|
159
160
|
errorHandler: options.errorHandler,
|
|
161
|
+
// Hard consent gate at the queue boundary: nothing buffered is
|
|
162
|
+
// ever sent once the user has opted out, even via a timer or
|
|
163
|
+
// pagehide flush scheduled before opt-out.
|
|
164
|
+
canSend: function () { return !_this.hasOptedOutTracking(); },
|
|
160
165
|
}), options);
|
|
161
166
|
// Check consent status on initialization
|
|
162
167
|
if (this.hasOptedOutTracking()) {
|
|
@@ -294,11 +299,25 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
294
299
|
*/
|
|
295
300
|
FormoAnalytics.prototype.reset = function () {
|
|
296
301
|
this.currentUserId = undefined;
|
|
302
|
+
// Clear in-memory wallet identity too. Without this, a logout/reset
|
|
303
|
+
// (also triggered by optOutTracking) still leaks the previous wallet
|
|
304
|
+
// address on subsequent track()/page() events for the rest of the
|
|
305
|
+
// page lifetime, because they fall back to currentAddress. Keep the
|
|
306
|
+
// EVM provider reference so tracking can resume on the next connect.
|
|
307
|
+
this.currentAddress = undefined;
|
|
308
|
+
this.currentChainId = undefined;
|
|
309
|
+
var evmProvider = this._chainState.evm.provider;
|
|
310
|
+
this._chainState = { evm: { provider: evmProvider }, solana: {} };
|
|
311
|
+
this._activeNamespace = undefined;
|
|
297
312
|
cookie().remove(LOCAL_ANONYMOUS_ID_KEY);
|
|
298
313
|
cookie().remove(SESSION_USER_ID_KEY);
|
|
299
314
|
cookie().remove(SESSION_WALLET_DETECTED_KEY);
|
|
300
315
|
cookie().remove(SESSION_WALLET_IDENTIFIED_KEY);
|
|
301
316
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
317
|
+
// Stored traffic-source attribution (referrer/UTM) is tracking data;
|
|
318
|
+
// clear it too so reset()/optOutTracking() don't leave it to be
|
|
319
|
+
// re-attached to the next session's events.
|
|
320
|
+
session().remove(SESSION_TRAFFIC_SOURCE_KEY);
|
|
302
321
|
};
|
|
303
322
|
/**
|
|
304
323
|
* Clean up resources and event listeners
|
|
@@ -307,6 +326,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
307
326
|
*/
|
|
308
327
|
FormoAnalytics.prototype.cleanup = function () {
|
|
309
328
|
logger.debug("FormoAnalytics: Cleaning up resources");
|
|
329
|
+
// Drop buffered events so a torn-down instance can't flush later.
|
|
330
|
+
this.eventManager.clear();
|
|
310
331
|
// Clean up Wagmi handler if present
|
|
311
332
|
if (this.wagmiHandler) {
|
|
312
333
|
this.wagmiHandler.cleanup();
|
|
@@ -470,7 +491,6 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
470
491
|
* @param {ChainID} params.chainId
|
|
471
492
|
* @param {Address} params.address
|
|
472
493
|
* @param {string} params.message
|
|
473
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
474
494
|
* @param {IFormoEventProperties} properties
|
|
475
495
|
* @param {IFormoEventContext} context
|
|
476
496
|
* @param {(...args: unknown[]) => void} callback
|
|
@@ -478,10 +498,15 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
478
498
|
*/
|
|
479
499
|
FormoAnalytics.prototype.signature = function (_a, properties_1, context_1, callback_1) {
|
|
480
500
|
return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
|
|
481
|
-
var status = _b.status, chainId = _b.chainId, address = _b.address, message = _b.message
|
|
501
|
+
var status = _b.status, chainId = _b.chainId, address = _b.address, message = _b.message;
|
|
482
502
|
return __generator(this, function (_c) {
|
|
483
503
|
switch (_c.label) {
|
|
484
|
-
case 0: return [4 /*yield*/, this.trackEvent(EventType.SIGNATURE,
|
|
504
|
+
case 0: return [4 /*yield*/, this.trackEvent(EventType.SIGNATURE, {
|
|
505
|
+
status: status,
|
|
506
|
+
chainId: chainId,
|
|
507
|
+
address: address,
|
|
508
|
+
message: message,
|
|
509
|
+
}, properties, context, callback)];
|
|
485
510
|
case 1:
|
|
486
511
|
_c.sent();
|
|
487
512
|
return [2 /*return*/];
|
|
@@ -552,6 +577,13 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
552
577
|
switch (_d.label) {
|
|
553
578
|
case 0:
|
|
554
579
|
_d.trys.push([0, 12, , 13]);
|
|
580
|
+
// identify() writes the user-id cookie and marks wallet
|
|
581
|
+
// identification before trackEvent's consent check — gate the
|
|
582
|
+
// whole method so an opted-out user gets no identity persistence.
|
|
583
|
+
if (this.hasOptedOutTracking()) {
|
|
584
|
+
logger.info("identify() skipped: user has opted out of tracking");
|
|
585
|
+
return [2 /*return*/];
|
|
586
|
+
}
|
|
555
587
|
if (!!params) return [3 /*break*/, 10];
|
|
556
588
|
// If no params provided, auto-identify
|
|
557
589
|
logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
|
|
@@ -627,7 +659,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
627
659
|
if (userId) {
|
|
628
660
|
this.currentUserId = userId;
|
|
629
661
|
domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
630
|
-
cookie().set(SESSION_USER_ID_KEY, userId, __assign({ path: "/" }, (domain ? { domain: domain } : {})));
|
|
662
|
+
cookie().set(SESSION_USER_ID_KEY, userId, __assign(__assign({ path: "/" }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
631
663
|
}
|
|
632
664
|
isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "");
|
|
633
665
|
logger.debug("Identify: Checking deduplication", {
|
|
@@ -676,6 +708,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
676
708
|
return __generator(this, function (_c) {
|
|
677
709
|
switch (_c.label) {
|
|
678
710
|
case 0:
|
|
711
|
+
// detect() marks wallet detection (a cookie write) before
|
|
712
|
+
// trackEvent's consent check — gate it on opt-out.
|
|
713
|
+
if (this.hasOptedOutTracking()) {
|
|
714
|
+
logger.info("detect() skipped: user has opted out of tracking");
|
|
715
|
+
return [2 /*return*/];
|
|
716
|
+
}
|
|
679
717
|
if (this.session.isWalletDetected(rdns))
|
|
680
718
|
return [2 /*return*/, logger.warn("Detect: Wallet ".concat(providerName, " already detected in this session"))];
|
|
681
719
|
this.session.markWalletDetected(rdns);
|
|
@@ -722,6 +760,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
722
760
|
// Set opt-out flag in persistent storage using direct cookie access
|
|
723
761
|
// This must be done before switching storage to ensure persistence
|
|
724
762
|
setConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY, "true");
|
|
763
|
+
// Drop anything already buffered so a pending timer/pagehide flush
|
|
764
|
+
// cannot ship events after consent withdrawal.
|
|
765
|
+
this.eventManager.clear();
|
|
725
766
|
this.reset();
|
|
726
767
|
logger.info("Successfully opted out of tracking");
|
|
727
768
|
};
|
|
@@ -1941,7 +1982,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1941
1982
|
});
|
|
1942
1983
|
});
|
|
1943
1984
|
};
|
|
1944
|
-
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
1985
|
+
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
1986
|
+
// Intentionally not read. Kept for positional call-site arity.
|
|
1987
|
+
_response, chainId) {
|
|
1945
1988
|
var _a;
|
|
1946
1989
|
var rawAddress = method === "personal_sign"
|
|
1947
1990
|
? params[1]
|
|
@@ -1958,9 +2001,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1958
2001
|
};
|
|
1959
2002
|
if (method === "personal_sign") {
|
|
1960
2003
|
var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
|
|
1961
|
-
return __assign(__assign(
|
|
2004
|
+
return __assign(__assign({}, basePayload), { message: message });
|
|
1962
2005
|
}
|
|
1963
|
-
|
|
2006
|
+
// eth_signTypedData*: params[1] is the full EIP-712 struct.
|
|
2007
|
+
return __assign(__assign({}, basePayload), { message: params[1] });
|
|
1964
2008
|
};
|
|
1965
2009
|
FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
|
|
1966
2010
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -2076,7 +2120,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2076
2120
|
}
|
|
2077
2121
|
this._providerListenersMap.delete(provider);
|
|
2078
2122
|
};
|
|
2079
|
-
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2123
|
+
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2124
|
+
// and tracking
|
|
2080
2125
|
FormoAnalytics.prototype.untrackProvider = function (provider) {
|
|
2081
2126
|
try {
|
|
2082
2127
|
this.removeProviderListeners(provider);
|
|
@@ -2194,6 +2239,42 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2194
2239
|
}
|
|
2195
2240
|
this.syncDerivedState();
|
|
2196
2241
|
};
|
|
2242
|
+
/**
|
|
2243
|
+
* Sync validated wallet/chain state into the SDK's central state
|
|
2244
|
+
* WITHOUT emitting an event.
|
|
2245
|
+
*
|
|
2246
|
+
* Integrations (e.g. the wagmi handler) must call this on every
|
|
2247
|
+
* connect / chain-change / disconnect — even when the corresponding
|
|
2248
|
+
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
2249
|
+
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
2250
|
+
* check (which keys off `currentChainId`, not the event payload) can
|
|
2251
|
+
* be bypassed, letting wallet activity on an excluded chain still be
|
|
2252
|
+
* collected.
|
|
2253
|
+
*
|
|
2254
|
+
* - valid `address` present → record per-chain + derived state
|
|
2255
|
+
* - `address` absent → clear chain state (disconnect)
|
|
2256
|
+
*/
|
|
2257
|
+
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
2258
|
+
var chainId = params.chainId, address = params.address;
|
|
2259
|
+
if (!address) {
|
|
2260
|
+
if (chainId !== undefined && chainId !== null) {
|
|
2261
|
+
this.clearChainState(chainId);
|
|
2262
|
+
}
|
|
2263
|
+
else {
|
|
2264
|
+
this.clearChainState("evm");
|
|
2265
|
+
this.clearChainState("solana");
|
|
2266
|
+
}
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
if (chainId === null || chainId === undefined)
|
|
2270
|
+
return;
|
|
2271
|
+
var validAddress = validateAddress(address, chainId);
|
|
2272
|
+
if (!validAddress) {
|
|
2273
|
+
logger.warn("syncWalletState: invalid address (\"".concat(address, "\") for chain ").concat(chainId));
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
this.setChainState(chainId, { address: validAddress });
|
|
2277
|
+
};
|
|
2197
2278
|
/**
|
|
2198
2279
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
2199
2280
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
@@ -2232,10 +2313,16 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2232
2313
|
*/
|
|
2233
2314
|
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2234
2315
|
try {
|
|
2316
|
+
// Never write an identity cookie for an opted-out user; ensure any
|
|
2317
|
+
// prior snapshot is removed instead.
|
|
2318
|
+
if (this.hasOptedOutTracking()) {
|
|
2319
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2235
2322
|
if (this.currentAddress) {
|
|
2236
2323
|
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
2237
2324
|
var domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
2238
|
-
cookie().set(ACTIVE_WALLET_KEY, value, __assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, (domain ? { domain: domain } : {})));
|
|
2325
|
+
cookie().set(ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
2239
2326
|
}
|
|
2240
2327
|
else {
|
|
2241
2328
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
@@ -2251,6 +2338,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2251
2338
|
*/
|
|
2252
2339
|
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
2253
2340
|
try {
|
|
2341
|
+
// Never restore wallet identity into memory for an opted-out user
|
|
2342
|
+
// (mirrors persistActiveWallet's guard); drop any stale snapshot.
|
|
2343
|
+
if (this.hasOptedOutTracking()) {
|
|
2344
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2254
2347
|
var raw = cookie().get(ACTIVE_WALLET_KEY);
|
|
2255
2348
|
if (!raw)
|
|
2256
2349
|
return;
|
|
@@ -35,7 +35,7 @@ declare class EventFactory implements IEventFactory {
|
|
|
35
35
|
generateConnectEvent(chainId: ChainID, address: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
36
36
|
generateDisconnectEvent(chainId?: ChainID, address?: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
37
37
|
generateChainChangedEvent(chainId: ChainID, address: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
38
|
-
generateSignatureEvent(status: SignatureStatus, chainId: ChainID, address: Address, message: string,
|
|
38
|
+
generateSignatureEvent(status: SignatureStatus, chainId: ChainID, address: Address, message: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
39
39
|
generateTransactionEvent(status: TransactionStatus, chainId: ChainID, address: Address, data?: string, to?: string, value?: string, transactionHash?: string, function_name?: string, function_args?: Record<string, unknown>, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
40
40
|
generateTrackEvent(event: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
41
41
|
create(event: APIEvent, address?: Address, userId?: string): Promise<IFormoEvent>;
|
|
@@ -413,12 +413,12 @@ var EventFactory = /** @class */ (function () {
|
|
|
413
413
|
});
|
|
414
414
|
});
|
|
415
415
|
};
|
|
416
|
-
EventFactory.prototype.generateSignatureEvent = function (status, chainId, address, message,
|
|
416
|
+
EventFactory.prototype.generateSignatureEvent = function (status, chainId, address, message, properties, context) {
|
|
417
417
|
return __awaiter(this, void 0, void 0, function () {
|
|
418
418
|
var signatureEvent;
|
|
419
419
|
return __generator(this, function (_a) {
|
|
420
420
|
signatureEvent = {
|
|
421
|
-
properties: __assign(
|
|
421
|
+
properties: __assign({ status: status, chainId: chainId, message: message }, properties),
|
|
422
422
|
address: address,
|
|
423
423
|
type: "signature",
|
|
424
424
|
};
|
|
@@ -506,7 +506,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
506
506
|
case 12:
|
|
507
507
|
formoEvent = _b.sent();
|
|
508
508
|
return [3 /*break*/, 19];
|
|
509
|
-
case 13: return [4 /*yield*/, this.generateSignatureEvent(event.status, event.chainId, event.address, event.message, event.
|
|
509
|
+
case 13: return [4 /*yield*/, this.generateSignatureEvent(event.status, event.chainId, event.address, event.message, event.properties, event.context)];
|
|
510
510
|
case 14:
|
|
511
511
|
formoEvent = _b.sent();
|
|
512
512
|
return [3 /*break*/, 19];
|
|
@@ -18,6 +18,8 @@ declare class EventManager implements IEventManager {
|
|
|
18
18
|
* @param event Incoming event data
|
|
19
19
|
*/
|
|
20
20
|
addEvent(event: APIEvent, address?: Address, userId?: string): Promise<void>;
|
|
21
|
+
/** Drop any buffered events (consent withdrawal / teardown). */
|
|
22
|
+
clear(): void;
|
|
21
23
|
}
|
|
22
24
|
export { EventManager };
|
|
23
25
|
//# sourceMappingURL=EventManager.d.ts.map
|
|
@@ -93,6 +93,10 @@ var EventManager = /** @class */ (function () {
|
|
|
93
93
|
});
|
|
94
94
|
});
|
|
95
95
|
};
|
|
96
|
+
/** Drop any buffered events (consent withdrawal / teardown). */
|
|
97
|
+
EventManager.prototype.clear = function () {
|
|
98
|
+
this.eventQueue.clear();
|
|
99
|
+
};
|
|
96
100
|
return EventManager;
|
|
97
101
|
}());
|
|
98
102
|
export { EventManager };
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Address, APIEvent, IFormoEvent } from "../types";
|
|
2
2
|
export interface IEventManager {
|
|
3
3
|
addEvent(event: APIEvent, address?: Address, userId?: string): Promise<void>;
|
|
4
|
+
clear(): void;
|
|
4
5
|
}
|
|
5
6
|
export interface IEventFactory {
|
|
6
7
|
create(event: APIEvent, address?: Address, userId?: string): Promise<IFormoEvent>;
|
|
@@ -11,7 +11,7 @@ var __assign = (this && this.__assign) || function () {
|
|
|
11
11
|
};
|
|
12
12
|
import { generateNativeUUID } from "../utils";
|
|
13
13
|
import { cookie } from "../storage";
|
|
14
|
-
import { getIdentityCookieDomain } from "../storage/cookiePolicy";
|
|
14
|
+
import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "../storage/cookiePolicy";
|
|
15
15
|
var generateAnonymousId = function (key, crossSubdomainCookies) {
|
|
16
16
|
var storedAnonymousId = cookie().get(key);
|
|
17
17
|
var anonymousId = (storedAnonymousId && typeof storedAnonymousId === "string"
|
|
@@ -23,7 +23,7 @@ var generateAnonymousId = function (key, crossSubdomainCookies) {
|
|
|
23
23
|
// domain. Note: host-only cookies on other hosts (e.g. a cookie set on
|
|
24
24
|
// example.com is not visible from app.example.com) cannot be migrated
|
|
25
25
|
// until the user revisits that host.
|
|
26
|
-
cookie().set(key, anonymousId, __assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, (domain ? { domain: domain } : {})));
|
|
26
|
+
cookie().set(key, anonymousId, __assign(__assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
27
27
|
return anonymousId;
|
|
28
28
|
};
|
|
29
29
|
export { generateAnonymousId };
|
|
@@ -11,6 +11,7 @@ type Options = {
|
|
|
11
11
|
retryCount?: number;
|
|
12
12
|
errorHandler?: any;
|
|
13
13
|
maxQueueSize?: number;
|
|
14
|
+
canSend?: () => boolean;
|
|
14
15
|
};
|
|
15
16
|
export declare class EventQueue implements IEventQueue {
|
|
16
17
|
private writeKey;
|
|
@@ -21,12 +22,19 @@ export declare class EventQueue implements IEventQueue {
|
|
|
21
22
|
private flushIntervalMs;
|
|
22
23
|
private flushed;
|
|
23
24
|
private maxQueueSize;
|
|
25
|
+
private queueByteSize;
|
|
24
26
|
private errorHandler;
|
|
25
27
|
private retryCount;
|
|
26
28
|
private pendingFlush;
|
|
27
29
|
private payloadHashes;
|
|
30
|
+
private canSend?;
|
|
28
31
|
constructor(writeKey: string, options: Options);
|
|
29
32
|
private generateMessageId;
|
|
33
|
+
/**
|
|
34
|
+
* Drop all queued data and cancel the flush timer. Called on consent
|
|
35
|
+
* withdrawal / SDK teardown so nothing buffered can be sent later.
|
|
36
|
+
*/
|
|
37
|
+
clear(): void;
|
|
30
38
|
enqueue(event: IFormoEvent, callback?: (...args: any) => void): Promise<void>;
|
|
31
39
|
flush(callback?: (...args: any) => void, drainAll?: boolean): Promise<void | IFormoEventFlushPayload[]>;
|
|
32
40
|
/**
|
|
@@ -81,6 +81,7 @@ var EventQueue = /** @class */ (function () {
|
|
|
81
81
|
function EventQueue(writeKey, options) {
|
|
82
82
|
var _this = this;
|
|
83
83
|
this.queue = [];
|
|
84
|
+
this.queueByteSize = 0; // running total of queued items' byteSize
|
|
84
85
|
this.payloadHashes = new Set();
|
|
85
86
|
this.onPageLeave = function (callback) {
|
|
86
87
|
// To ensure the callback is only called once even if more than one events
|
|
@@ -136,6 +137,7 @@ var EventQueue = /** @class */ (function () {
|
|
|
136
137
|
this.queue = [];
|
|
137
138
|
this.writeKey = writeKey;
|
|
138
139
|
this.apiHost = options.apiHost;
|
|
140
|
+
this.canSend = options.canSend;
|
|
139
141
|
this.retryCount = clampNumber(options.retryCount || DEFAULT_RETRY, MAX_RETRY, MIN_RETRY);
|
|
140
142
|
this.flushAt = clampNumber(options.flushAt || DEFAULT_FLUSH_AT, MAX_FLUSH_AT, MIN_FLUSH_AT);
|
|
141
143
|
this.maxQueueSize = clampNumber(options.maxQueueSize || DEFAULT_QUEUE_SIZE, MAX_QUEUE_SIZE, MIN_QUEUE_SIZE);
|
|
@@ -169,13 +171,31 @@ var EventQueue = /** @class */ (function () {
|
|
|
169
171
|
});
|
|
170
172
|
});
|
|
171
173
|
};
|
|
174
|
+
/**
|
|
175
|
+
* Drop all queued data and cancel the flush timer. Called on consent
|
|
176
|
+
* withdrawal / SDK teardown so nothing buffered can be sent later.
|
|
177
|
+
*/
|
|
178
|
+
EventQueue.prototype.clear = function () {
|
|
179
|
+
if (this.timer) {
|
|
180
|
+
clearTimeout(this.timer);
|
|
181
|
+
this.timer = null;
|
|
182
|
+
}
|
|
183
|
+
this.queue = [];
|
|
184
|
+
this.queueByteSize = 0;
|
|
185
|
+
this.payloadHashes.clear();
|
|
186
|
+
};
|
|
172
187
|
EventQueue.prototype.enqueue = function (event, callback) {
|
|
173
188
|
return __awaiter(this, void 0, void 0, function () {
|
|
174
|
-
var message_id, hasReachedFlushAt, hasReachedQueueSize;
|
|
189
|
+
var message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
|
|
175
190
|
return __generator(this, function (_a) {
|
|
176
191
|
switch (_a.label) {
|
|
177
192
|
case 0:
|
|
178
193
|
callback = callback || noop;
|
|
194
|
+
// Refuse to buffer anything once consent is withdrawn.
|
|
195
|
+
if (this.canSend && !this.canSend()) {
|
|
196
|
+
this.clear();
|
|
197
|
+
return [2 /*return*/];
|
|
198
|
+
}
|
|
179
199
|
return [4 /*yield*/, this.generateMessageId(event)];
|
|
180
200
|
case 1:
|
|
181
201
|
message_id = _a.sent();
|
|
@@ -184,10 +204,18 @@ var EventQueue = /** @class */ (function () {
|
|
|
184
204
|
logger.warn("Event already enqueued, try again after ".concat(millisecondsToSecond(this.flushIntervalMs), " seconds."));
|
|
185
205
|
return [2 /*return*/];
|
|
186
206
|
}
|
|
187
|
-
|
|
207
|
+
queueItem = {
|
|
188
208
|
message: __assign(__assign({}, event), { message_id: message_id }),
|
|
189
209
|
callback: callback,
|
|
190
|
-
|
|
210
|
+
byteSize: 0,
|
|
211
|
+
};
|
|
212
|
+
// Measure once here (message only — JSON.stringify drops the
|
|
213
|
+
// callback function anyway), then track the total incrementally.
|
|
214
|
+
queueItem.byteSize = JSON.stringify({
|
|
215
|
+
message: queueItem.message,
|
|
216
|
+
}).length;
|
|
217
|
+
this.queue.push(queueItem);
|
|
218
|
+
this.queueByteSize += queueItem.byteSize;
|
|
191
219
|
logger.log("Event enqueued: ".concat(getActionDescriptor(event.type, event.properties)));
|
|
192
220
|
if (!this.flushed) {
|
|
193
221
|
this.flushed = true;
|
|
@@ -195,8 +223,7 @@ var EventQueue = /** @class */ (function () {
|
|
|
195
223
|
return [2 /*return*/];
|
|
196
224
|
}
|
|
197
225
|
hasReachedFlushAt = this.queue.length >= this.flushAt;
|
|
198
|
-
hasReachedQueueSize = this.
|
|
199
|
-
this.maxQueueSize;
|
|
226
|
+
hasReachedQueueSize = this.queueByteSize >= this.maxQueueSize;
|
|
200
227
|
if (hasReachedFlushAt || hasReachedQueueSize) {
|
|
201
228
|
this.flush();
|
|
202
229
|
return [2 /*return*/];
|
|
@@ -222,6 +249,13 @@ var EventQueue = /** @class */ (function () {
|
|
|
222
249
|
clearTimeout(this.timer);
|
|
223
250
|
this.timer = null;
|
|
224
251
|
}
|
|
252
|
+
// Final consent gate: a timer/pagehide flush may have been scheduled
|
|
253
|
+
// before opt-out. Drop everything rather than send post-withdrawal.
|
|
254
|
+
if (this.canSend && !this.canSend()) {
|
|
255
|
+
this.clear();
|
|
256
|
+
callback();
|
|
257
|
+
return [2 /*return*/, Promise.resolve()];
|
|
258
|
+
}
|
|
225
259
|
if (!this.queue.length) {
|
|
226
260
|
callback();
|
|
227
261
|
return [2 /*return*/, Promise.resolve()];
|
|
@@ -235,11 +269,17 @@ var EventQueue = /** @class */ (function () {
|
|
|
235
269
|
case 2:
|
|
236
270
|
items = this.queue.splice(0, drainAll ? this.queue.length : this.flushAt);
|
|
237
271
|
// Only remove hashes for flushed items so duplicate detection remains
|
|
238
|
-
// active for events still in the queue.
|
|
272
|
+
// active for events still in the queue. Also decrement the running
|
|
273
|
+
// byte total by exactly what left the queue.
|
|
239
274
|
for (_i = 0, items_1 = items; _i < items_1.length; _i++) {
|
|
240
275
|
item = items_1[_i];
|
|
241
276
|
this.payloadHashes.delete(item.message.message_id);
|
|
277
|
+
this.queueByteSize -= item.byteSize;
|
|
242
278
|
}
|
|
279
|
+
// Re-anchor to the exact invariant when the queue empties, so any
|
|
280
|
+
// accumulated drift can never wedge the size gate.
|
|
281
|
+
if (this.queue.length === 0)
|
|
282
|
+
this.queueByteSize = 0;
|
|
243
283
|
sentAt = new Date().toISOString();
|
|
244
284
|
data = items.map(function (item) { return (__assign(__assign({}, item.message), { sent_at: sentAt })); });
|
|
245
285
|
batches = this.splitIntoBatches(items, data);
|
|
@@ -342,6 +382,12 @@ var EventQueue = /** @class */ (function () {
|
|
|
342
382
|
case 1:
|
|
343
383
|
if (!(_i < batches_1.length)) return [3 /*break*/, 6];
|
|
344
384
|
batch = batches_1[_i];
|
|
385
|
+
// Consent can be withdrawn while a flush is already in flight:
|
|
386
|
+
// batches were spliced before opt-out, and split batches / retry
|
|
387
|
+
// backoff span seconds. Re-check before every send and abandon
|
|
388
|
+
// the remaining batches if consent was revoked mid-flush.
|
|
389
|
+
if (this.canSend && !this.canSend())
|
|
390
|
+
return [3 /*break*/, 6];
|
|
345
391
|
_a.label = 2;
|
|
346
392
|
case 2:
|
|
347
393
|
_a.trys.push([2, 4, , 5]);
|