@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
|
@@ -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.
|
|
@@ -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;
|
|
@@ -160,6 +161,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
160
161
|
maxQueueSize: options.maxQueueSize,
|
|
161
162
|
flushInterval: options.flushInterval,
|
|
162
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(); },
|
|
163
168
|
}), options);
|
|
164
169
|
// Check consent status on initialization
|
|
165
170
|
if (this.hasOptedOutTracking()) {
|
|
@@ -297,11 +302,25 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
297
302
|
*/
|
|
298
303
|
FormoAnalytics.prototype.reset = function () {
|
|
299
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;
|
|
300
315
|
(0, storage_1.cookie)().remove(constants_1.LOCAL_ANONYMOUS_ID_KEY);
|
|
301
316
|
(0, storage_1.cookie)().remove(constants_1.SESSION_USER_ID_KEY);
|
|
302
317
|
(0, storage_1.cookie)().remove(session_1.SESSION_WALLET_DETECTED_KEY);
|
|
303
318
|
(0, storage_1.cookie)().remove(session_1.SESSION_WALLET_IDENTIFIED_KEY);
|
|
304
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);
|
|
305
324
|
};
|
|
306
325
|
/**
|
|
307
326
|
* Clean up resources and event listeners
|
|
@@ -310,6 +329,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
310
329
|
*/
|
|
311
330
|
FormoAnalytics.prototype.cleanup = function () {
|
|
312
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();
|
|
313
334
|
// Clean up Wagmi handler if present
|
|
314
335
|
if (this.wagmiHandler) {
|
|
315
336
|
this.wagmiHandler.cleanup();
|
|
@@ -473,7 +494,6 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
473
494
|
* @param {ChainID} params.chainId
|
|
474
495
|
* @param {Address} params.address
|
|
475
496
|
* @param {string} params.message
|
|
476
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
477
497
|
* @param {IFormoEventProperties} properties
|
|
478
498
|
* @param {IFormoEventContext} context
|
|
479
499
|
* @param {(...args: unknown[]) => void} callback
|
|
@@ -481,10 +501,15 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
481
501
|
*/
|
|
482
502
|
FormoAnalytics.prototype.signature = function (_a, properties_1, context_1, callback_1) {
|
|
483
503
|
return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
|
|
484
|
-
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;
|
|
485
505
|
return __generator(this, function (_c) {
|
|
486
506
|
switch (_c.label) {
|
|
487
|
-
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)];
|
|
488
513
|
case 1:
|
|
489
514
|
_c.sent();
|
|
490
515
|
return [2 /*return*/];
|
|
@@ -555,6 +580,13 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
555
580
|
switch (_d.label) {
|
|
556
581
|
case 0:
|
|
557
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
|
+
}
|
|
558
590
|
if (!!params) return [3 /*break*/, 10];
|
|
559
591
|
// If no params provided, auto-identify
|
|
560
592
|
logger_1.logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
|
|
@@ -630,7 +662,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
630
662
|
if (userId) {
|
|
631
663
|
this.currentUserId = userId;
|
|
632
664
|
domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
|
|
633
|
-
(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 } : {})));
|
|
634
666
|
}
|
|
635
667
|
isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "");
|
|
636
668
|
logger_1.logger.debug("Identify: Checking deduplication", {
|
|
@@ -679,6 +711,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
679
711
|
return __generator(this, function (_c) {
|
|
680
712
|
switch (_c.label) {
|
|
681
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
|
+
}
|
|
682
720
|
if (this.session.isWalletDetected(rdns))
|
|
683
721
|
return [2 /*return*/, logger_1.logger.warn("Detect: Wallet ".concat(providerName, " already detected in this session"))];
|
|
684
722
|
this.session.markWalletDetected(rdns);
|
|
@@ -725,6 +763,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
725
763
|
// Set opt-out flag in persistent storage using direct cookie access
|
|
726
764
|
// This must be done before switching storage to ensure persistence
|
|
727
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();
|
|
728
769
|
this.reset();
|
|
729
770
|
logger_1.logger.info("Successfully opted out of tracking");
|
|
730
771
|
};
|
|
@@ -1944,7 +1985,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1944
1985
|
});
|
|
1945
1986
|
});
|
|
1946
1987
|
};
|
|
1947
|
-
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) {
|
|
1948
1991
|
var _a;
|
|
1949
1992
|
var rawAddress = method === "personal_sign"
|
|
1950
1993
|
? params[1]
|
|
@@ -1961,9 +2004,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1961
2004
|
};
|
|
1962
2005
|
if (method === "personal_sign") {
|
|
1963
2006
|
var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
|
|
1964
|
-
return __assign(__assign(
|
|
2007
|
+
return __assign(__assign({}, basePayload), { message: message });
|
|
1965
2008
|
}
|
|
1966
|
-
|
|
2009
|
+
// eth_signTypedData*: params[1] is the full EIP-712 struct.
|
|
2010
|
+
return __assign(__assign({}, basePayload), { message: params[1] });
|
|
1967
2011
|
};
|
|
1968
2012
|
FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
|
|
1969
2013
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -2079,7 +2123,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2079
2123
|
}
|
|
2080
2124
|
this._providerListenersMap.delete(provider);
|
|
2081
2125
|
};
|
|
2082
|
-
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2126
|
+
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2127
|
+
// and tracking
|
|
2083
2128
|
FormoAnalytics.prototype.untrackProvider = function (provider) {
|
|
2084
2129
|
try {
|
|
2085
2130
|
this.removeProviderListeners(provider);
|
|
@@ -2197,6 +2242,42 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2197
2242
|
}
|
|
2198
2243
|
this.syncDerivedState();
|
|
2199
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
|
+
};
|
|
2200
2281
|
/**
|
|
2201
2282
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
2202
2283
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
@@ -2235,10 +2316,16 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2235
2316
|
*/
|
|
2236
2317
|
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2237
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
|
+
}
|
|
2238
2325
|
if (this.currentAddress) {
|
|
2239
2326
|
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
2240
2327
|
var domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
|
|
2241
|
-
(0, storage_1.cookie)().set(constants_1.ACTIVE_WALLET_KEY, value, __assign({ path: "/", expires: new Date(Date.now() + constants_1.ACTIVE_WALLET_TTL_MS).toUTCString() }, (domain ? { domain: domain } : {})));
|
|
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 } : {})));
|
|
2242
2329
|
}
|
|
2243
2330
|
else {
|
|
2244
2331
|
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
@@ -2254,6 +2341,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2254
2341
|
*/
|
|
2255
2342
|
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
2256
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
|
+
}
|
|
2257
2350
|
var raw = (0, storage_1.cookie)().get(constants_1.ACTIVE_WALLET_KEY);
|
|
2258
2351
|
if (!raw)
|
|
2259
2352
|
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>;
|
|
@@ -419,12 +419,12 @@ var EventFactory = /** @class */ (function () {
|
|
|
419
419
|
});
|
|
420
420
|
});
|
|
421
421
|
};
|
|
422
|
-
EventFactory.prototype.generateSignatureEvent = function (status, chainId, address, message,
|
|
422
|
+
EventFactory.prototype.generateSignatureEvent = function (status, chainId, address, message, properties, context) {
|
|
423
423
|
return __awaiter(this, void 0, void 0, function () {
|
|
424
424
|
var signatureEvent;
|
|
425
425
|
return __generator(this, function (_a) {
|
|
426
426
|
signatureEvent = {
|
|
427
|
-
properties: __assign(
|
|
427
|
+
properties: __assign({ status: status, chainId: chainId, message: message }, properties),
|
|
428
428
|
address: address,
|
|
429
429
|
type: "signature",
|
|
430
430
|
};
|
|
@@ -512,7 +512,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
512
512
|
case 12:
|
|
513
513
|
formoEvent = _b.sent();
|
|
514
514
|
return [3 /*break*/, 19];
|
|
515
|
-
case 13: return [4 /*yield*/, this.generateSignatureEvent(event.status, event.chainId, event.address, event.message, event.
|
|
515
|
+
case 13: return [4 /*yield*/, this.generateSignatureEvent(event.status, event.chainId, event.address, event.message, event.properties, event.context)];
|
|
516
516
|
case 14:
|
|
517
517
|
formoEvent = _b.sent();
|
|
518
518
|
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
|
|
@@ -96,6 +96,10 @@ var EventManager = /** @class */ (function () {
|
|
|
96
96
|
});
|
|
97
97
|
});
|
|
98
98
|
};
|
|
99
|
+
/** Drop any buffered events (consent withdrawal / teardown). */
|
|
100
|
+
EventManager.prototype.clear = function () {
|
|
101
|
+
this.eventQueue.clear();
|
|
102
|
+
};
|
|
99
103
|
return EventManager;
|
|
100
104
|
}());
|
|
101
105
|
exports.EventManager = 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>;
|
|
@@ -26,7 +26,7 @@ var generateAnonymousId = function (key, crossSubdomainCookies) {
|
|
|
26
26
|
// domain. Note: host-only cookies on other hosts (e.g. a cookie set on
|
|
27
27
|
// example.com is not visible from app.example.com) cannot be migrated
|
|
28
28
|
// until the user revisits that host.
|
|
29
|
-
(0, storage_1.cookie)().set(key, anonymousId, __assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, (domain ? { domain: domain } : {})));
|
|
29
|
+
(0, storage_1.cookie)().set(key, anonymousId, __assign(__assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()), (domain ? { domain: domain } : {})));
|
|
30
30
|
return anonymousId;
|
|
31
31
|
};
|
|
32
32
|
exports.generateAnonymousId = 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
|
/**
|
|
@@ -87,6 +87,7 @@ var EventQueue = /** @class */ (function () {
|
|
|
87
87
|
function EventQueue(writeKey, options) {
|
|
88
88
|
var _this = this;
|
|
89
89
|
this.queue = [];
|
|
90
|
+
this.queueByteSize = 0; // running total of queued items' byteSize
|
|
90
91
|
this.payloadHashes = new Set();
|
|
91
92
|
this.onPageLeave = function (callback) {
|
|
92
93
|
// To ensure the callback is only called once even if more than one events
|
|
@@ -142,6 +143,7 @@ var EventQueue = /** @class */ (function () {
|
|
|
142
143
|
this.queue = [];
|
|
143
144
|
this.writeKey = writeKey;
|
|
144
145
|
this.apiHost = options.apiHost;
|
|
146
|
+
this.canSend = options.canSend;
|
|
145
147
|
this.retryCount = (0, utils_1.clampNumber)(options.retryCount || DEFAULT_RETRY, MAX_RETRY, MIN_RETRY);
|
|
146
148
|
this.flushAt = (0, utils_1.clampNumber)(options.flushAt || DEFAULT_FLUSH_AT, MAX_FLUSH_AT, MIN_FLUSH_AT);
|
|
147
149
|
this.maxQueueSize = (0, utils_1.clampNumber)(options.maxQueueSize || DEFAULT_QUEUE_SIZE, MAX_QUEUE_SIZE, MIN_QUEUE_SIZE);
|
|
@@ -175,13 +177,31 @@ var EventQueue = /** @class */ (function () {
|
|
|
175
177
|
});
|
|
176
178
|
});
|
|
177
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* Drop all queued data and cancel the flush timer. Called on consent
|
|
182
|
+
* withdrawal / SDK teardown so nothing buffered can be sent later.
|
|
183
|
+
*/
|
|
184
|
+
EventQueue.prototype.clear = function () {
|
|
185
|
+
if (this.timer) {
|
|
186
|
+
clearTimeout(this.timer);
|
|
187
|
+
this.timer = null;
|
|
188
|
+
}
|
|
189
|
+
this.queue = [];
|
|
190
|
+
this.queueByteSize = 0;
|
|
191
|
+
this.payloadHashes.clear();
|
|
192
|
+
};
|
|
178
193
|
EventQueue.prototype.enqueue = function (event, callback) {
|
|
179
194
|
return __awaiter(this, void 0, void 0, function () {
|
|
180
|
-
var message_id, hasReachedFlushAt, hasReachedQueueSize;
|
|
195
|
+
var message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
|
|
181
196
|
return __generator(this, function (_a) {
|
|
182
197
|
switch (_a.label) {
|
|
183
198
|
case 0:
|
|
184
199
|
callback = callback || noop;
|
|
200
|
+
// Refuse to buffer anything once consent is withdrawn.
|
|
201
|
+
if (this.canSend && !this.canSend()) {
|
|
202
|
+
this.clear();
|
|
203
|
+
return [2 /*return*/];
|
|
204
|
+
}
|
|
185
205
|
return [4 /*yield*/, this.generateMessageId(event)];
|
|
186
206
|
case 1:
|
|
187
207
|
message_id = _a.sent();
|
|
@@ -190,10 +210,18 @@ var EventQueue = /** @class */ (function () {
|
|
|
190
210
|
logger_1.logger.warn("Event already enqueued, try again after ".concat((0, utils_1.millisecondsToSecond)(this.flushIntervalMs), " seconds."));
|
|
191
211
|
return [2 /*return*/];
|
|
192
212
|
}
|
|
193
|
-
|
|
213
|
+
queueItem = {
|
|
194
214
|
message: __assign(__assign({}, event), { message_id: message_id }),
|
|
195
215
|
callback: callback,
|
|
196
|
-
|
|
216
|
+
byteSize: 0,
|
|
217
|
+
};
|
|
218
|
+
// Measure once here (message only — JSON.stringify drops the
|
|
219
|
+
// callback function anyway), then track the total incrementally.
|
|
220
|
+
queueItem.byteSize = JSON.stringify({
|
|
221
|
+
message: queueItem.message,
|
|
222
|
+
}).length;
|
|
223
|
+
this.queue.push(queueItem);
|
|
224
|
+
this.queueByteSize += queueItem.byteSize;
|
|
197
225
|
logger_1.logger.log("Event enqueued: ".concat((0, utils_1.getActionDescriptor)(event.type, event.properties)));
|
|
198
226
|
if (!this.flushed) {
|
|
199
227
|
this.flushed = true;
|
|
@@ -201,8 +229,7 @@ var EventQueue = /** @class */ (function () {
|
|
|
201
229
|
return [2 /*return*/];
|
|
202
230
|
}
|
|
203
231
|
hasReachedFlushAt = this.queue.length >= this.flushAt;
|
|
204
|
-
hasReachedQueueSize = this.
|
|
205
|
-
this.maxQueueSize;
|
|
232
|
+
hasReachedQueueSize = this.queueByteSize >= this.maxQueueSize;
|
|
206
233
|
if (hasReachedFlushAt || hasReachedQueueSize) {
|
|
207
234
|
this.flush();
|
|
208
235
|
return [2 /*return*/];
|
|
@@ -228,6 +255,13 @@ var EventQueue = /** @class */ (function () {
|
|
|
228
255
|
clearTimeout(this.timer);
|
|
229
256
|
this.timer = null;
|
|
230
257
|
}
|
|
258
|
+
// Final consent gate: a timer/pagehide flush may have been scheduled
|
|
259
|
+
// before opt-out. Drop everything rather than send post-withdrawal.
|
|
260
|
+
if (this.canSend && !this.canSend()) {
|
|
261
|
+
this.clear();
|
|
262
|
+
callback();
|
|
263
|
+
return [2 /*return*/, Promise.resolve()];
|
|
264
|
+
}
|
|
231
265
|
if (!this.queue.length) {
|
|
232
266
|
callback();
|
|
233
267
|
return [2 /*return*/, Promise.resolve()];
|
|
@@ -241,11 +275,17 @@ var EventQueue = /** @class */ (function () {
|
|
|
241
275
|
case 2:
|
|
242
276
|
items = this.queue.splice(0, drainAll ? this.queue.length : this.flushAt);
|
|
243
277
|
// Only remove hashes for flushed items so duplicate detection remains
|
|
244
|
-
// active for events still in the queue.
|
|
278
|
+
// active for events still in the queue. Also decrement the running
|
|
279
|
+
// byte total by exactly what left the queue.
|
|
245
280
|
for (_i = 0, items_1 = items; _i < items_1.length; _i++) {
|
|
246
281
|
item = items_1[_i];
|
|
247
282
|
this.payloadHashes.delete(item.message.message_id);
|
|
283
|
+
this.queueByteSize -= item.byteSize;
|
|
248
284
|
}
|
|
285
|
+
// Re-anchor to the exact invariant when the queue empties, so any
|
|
286
|
+
// accumulated drift can never wedge the size gate.
|
|
287
|
+
if (this.queue.length === 0)
|
|
288
|
+
this.queueByteSize = 0;
|
|
249
289
|
sentAt = new Date().toISOString();
|
|
250
290
|
data = items.map(function (item) { return (__assign(__assign({}, item.message), { sent_at: sentAt })); });
|
|
251
291
|
batches = this.splitIntoBatches(items, data);
|
|
@@ -348,6 +388,12 @@ var EventQueue = /** @class */ (function () {
|
|
|
348
388
|
case 1:
|
|
349
389
|
if (!(_i < batches_1.length)) return [3 /*break*/, 6];
|
|
350
390
|
batch = batches_1[_i];
|
|
391
|
+
// Consent can be withdrawn while a flush is already in flight:
|
|
392
|
+
// batches were spliced before opt-out, and split batches / retry
|
|
393
|
+
// backoff span seconds. Re-check before every send and abandon
|
|
394
|
+
// the remaining batches if consent was revoked mid-flush.
|
|
395
|
+
if (this.canSend && !this.canSend())
|
|
396
|
+
return [3 /*break*/, 6];
|
|
351
397
|
_a.label = 2;
|
|
352
398
|
case 2:
|
|
353
399
|
_a.trys.push([2, 4, , 5]);
|
|
@@ -5,9 +5,21 @@
|
|
|
5
5
|
* Handles tracking of detected wallets and identified wallet-address pairs
|
|
6
6
|
* using cookies to maintain state across page loads within a session.
|
|
7
7
|
*/
|
|
8
|
+
var __assign = (this && this.__assign) || function () {
|
|
9
|
+
__assign = Object.assign || function(t) {
|
|
10
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
11
|
+
s = arguments[i];
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
}
|
|
15
|
+
return t;
|
|
16
|
+
};
|
|
17
|
+
return __assign.apply(this, arguments);
|
|
18
|
+
};
|
|
8
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
20
|
exports.FormoAnalyticsSession = exports.SESSION_WALLET_IDENTIFIED_KEY = exports.SESSION_WALLET_DETECTED_KEY = void 0;
|
|
10
21
|
var storage_1 = require("../storage");
|
|
22
|
+
var cookiePolicy_1 = require("../storage/cookiePolicy");
|
|
11
23
|
var logger_1 = require("../logger");
|
|
12
24
|
/**
|
|
13
25
|
* Cookie keys for session tracking
|
|
@@ -66,11 +78,9 @@ var FormoAnalyticsSession = /** @class */ (function () {
|
|
|
66
78
|
if (rdnses.length > MAX_SESSION_ENTRIES) {
|
|
67
79
|
rdnses.splice(0, rdnses.length - MAX_SESSION_ENTRIES);
|
|
68
80
|
}
|
|
69
|
-
(0, storage_1.cookie)().set(exports.SESSION_WALLET_DETECTED_KEY, rdnses.join(","), {
|
|
81
|
+
(0, storage_1.cookie)().set(exports.SESSION_WALLET_DETECTED_KEY, rdnses.join(","), __assign({
|
|
70
82
|
// Expires by the end of the day
|
|
71
|
-
expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
|
|
72
|
-
path: "/",
|
|
73
|
-
});
|
|
83
|
+
expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()));
|
|
74
84
|
}
|
|
75
85
|
};
|
|
76
86
|
/**
|
|
@@ -110,11 +120,9 @@ var FormoAnalyticsSession = /** @class */ (function () {
|
|
|
110
120
|
identifiedWallets.splice(0, identifiedWallets.length - MAX_SESSION_ENTRIES);
|
|
111
121
|
}
|
|
112
122
|
var newValue = identifiedWallets.join(",");
|
|
113
|
-
(0, storage_1.cookie)().set(exports.SESSION_WALLET_IDENTIFIED_KEY, newValue, {
|
|
123
|
+
(0, storage_1.cookie)().set(exports.SESSION_WALLET_IDENTIFIED_KEY, newValue, __assign({
|
|
114
124
|
// Expires by the end of the day
|
|
115
|
-
expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
|
|
116
|
-
path: "/",
|
|
117
|
-
});
|
|
125
|
+
expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()));
|
|
118
126
|
logger_1.logger.debug("Session: Marked wallet as identified", {
|
|
119
127
|
identifiedKey: identifiedKey,
|
|
120
128
|
hasRdns: !!rdns,
|
|
@@ -7,4 +7,20 @@
|
|
|
7
7
|
* @param crossSubdomain Whether cookies should be shared across subdomains.
|
|
8
8
|
*/
|
|
9
9
|
export declare function getIdentityCookieDomain(crossSubdomain?: boolean): string;
|
|
10
|
+
/**
|
|
11
|
+
* Security attributes for identity/session cookies (user-id,
|
|
12
|
+
* active-wallet, wallet-detected/identified).
|
|
13
|
+
*
|
|
14
|
+
* - `sameSite: "lax"` — first-party analytics identity. Lax keeps the
|
|
15
|
+
* cookie on top-level navigations (so attribution survives a click-in
|
|
16
|
+
* from another site) while blocking it on cross-site subrequests.
|
|
17
|
+
* Strict would silently drop identity on inbound navigation.
|
|
18
|
+
* - `secure` — only on HTTPS. Setting Secure over plain HTTP (local dev)
|
|
19
|
+
* makes the browser reject the cookie outright, so it must be
|
|
20
|
+
* conditional.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getIdentityCookieSecurity(): {
|
|
23
|
+
sameSite: "lax";
|
|
24
|
+
secure: boolean;
|
|
25
|
+
};
|
|
10
26
|
//# sourceMappingURL=cookiePolicy.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getIdentityCookieDomain = getIdentityCookieDomain;
|
|
4
|
+
exports.getIdentityCookieSecurity = getIdentityCookieSecurity;
|
|
4
5
|
/**
|
|
5
6
|
* Cookie domain policy — centralizes the decision of whether identity
|
|
6
7
|
* cookies should be host-scoped or apex-scoped.
|
|
@@ -24,4 +25,22 @@ function getIdentityCookieDomain(crossSubdomain) {
|
|
|
24
25
|
var domain = (0, domain_1.getApexDomain)();
|
|
25
26
|
return domain ? ".".concat(domain) : "";
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Security attributes for identity/session cookies (user-id,
|
|
30
|
+
* active-wallet, wallet-detected/identified).
|
|
31
|
+
*
|
|
32
|
+
* - `sameSite: "lax"` — first-party analytics identity. Lax keeps the
|
|
33
|
+
* cookie on top-level navigations (so attribution survives a click-in
|
|
34
|
+
* from another site) while blocking it on cross-site subrequests.
|
|
35
|
+
* Strict would silently drop identity on inbound navigation.
|
|
36
|
+
* - `secure` — only on HTTPS. Setting Secure over plain HTTP (local dev)
|
|
37
|
+
* makes the browser reject the cookie outright, so it must be
|
|
38
|
+
* conditional.
|
|
39
|
+
*/
|
|
40
|
+
function getIdentityCookieSecurity() {
|
|
41
|
+
var _a;
|
|
42
|
+
var isHttps = typeof window !== "undefined" &&
|
|
43
|
+
((_a = window.location) === null || _a === void 0 ? void 0 : _a.protocol) === "https:";
|
|
44
|
+
return { sameSite: "lax", secure: isHttps };
|
|
45
|
+
}
|
|
27
46
|
//# sourceMappingURL=cookiePolicy.js.map
|
|
@@ -40,7 +40,6 @@ export interface IFormoAnalytics {
|
|
|
40
40
|
chainId?: ChainID;
|
|
41
41
|
address: Address;
|
|
42
42
|
message: string;
|
|
43
|
-
signatureHash?: string;
|
|
44
43
|
}, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
45
44
|
transaction(params: {
|
|
46
45
|
status: TransactionStatus;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "1.
|
|
1
|
+
export declare const version = "1.30.0";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/cjs/src/version.js
CHANGED
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.version = void 0;
|
|
4
4
|
// This file is auto-generated by scripts/update-version.js during npm version
|
|
5
5
|
// Do not edit manually - it will be overwritten
|
|
6
|
-
exports.version = '1.
|
|
6
|
+
exports.version = '1.30.0';
|
|
7
7
|
//# sourceMappingURL=version.js.map
|