@formo/analytics 1.32.0 → 1.33.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 +61 -0
- package/dist/cjs/src/FormoAnalytics.js +223 -36
- package/dist/cjs/src/event/EventFactory.js +1 -7
- package/dist/cjs/src/types/base.d.ts +14 -0
- package/dist/cjs/src/utils/index.d.ts +1 -0
- package/dist/cjs/src/utils/index.js +1 -0
- package/dist/cjs/src/utils/timezone.d.ts +7 -0
- package/dist/cjs/src/utils/timezone.js +19 -0
- package/dist/esm/src/FormoAnalytics.d.ts +61 -0
- package/dist/esm/src/FormoAnalytics.js +223 -36
- package/dist/esm/src/event/EventFactory.js +2 -8
- package/dist/esm/src/types/base.d.ts +14 -0
- package/dist/esm/src/utils/index.d.ts +1 -0
- package/dist/esm/src/utils/index.js +1 -0
- package/dist/esm/src/utils/timezone.d.ts +7 -0
- package/dist/esm/src/utils/timezone.js +16 -0
- package/dist/index.umd.min.js +1 -1
- package/package.json +5 -5
|
@@ -282,6 +282,59 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
282
282
|
private static installHistoryHooksOnce;
|
|
283
283
|
private trackPageHit;
|
|
284
284
|
private trackEvent;
|
|
285
|
+
/**
|
|
286
|
+
* Visitor-level tracking suppression.
|
|
287
|
+
*
|
|
288
|
+
* Returns true when the SDK must not persist any identity/session/chain
|
|
289
|
+
* state or send any events for this visitor — i.e. an explicit opt-out or a
|
|
290
|
+
* jurisdiction/timezone exclusion. Public entry points that write state
|
|
291
|
+
* before reaching the `shouldTrack()` event gate (identify/connect/detect)
|
|
292
|
+
* check this first so suppressed visitors leave no cookies or session state.
|
|
293
|
+
* @returns {boolean} True if all tracking and persistence must be suppressed
|
|
294
|
+
*/
|
|
295
|
+
private isTrackingSuppressed;
|
|
296
|
+
/**
|
|
297
|
+
* Whether the current environment is excluded from tracking — the visitor's
|
|
298
|
+
* timezone, the current hostname, or the current pathname matches a
|
|
299
|
+
* configured exclusion.
|
|
300
|
+
*
|
|
301
|
+
* Timezone is visitor/session-level (stable for the session); host/path are
|
|
302
|
+
* current-page-level and transient — if a SPA navigates to an allowed path,
|
|
303
|
+
* tracking resumes for future actions. Used as the "do not write identity or
|
|
304
|
+
* send events" gate at every entry point that would persist state before the
|
|
305
|
+
* `shouldTrack()` event gate.
|
|
306
|
+
* @returns {boolean} True if the current environment is excluded
|
|
307
|
+
*/
|
|
308
|
+
private isCurrentEnvironmentExcluded;
|
|
309
|
+
/**
|
|
310
|
+
* Whether the current hostname matches a configured `tracking.excludeHosts`
|
|
311
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
312
|
+
* @returns {boolean} True if the current hostname is excluded
|
|
313
|
+
*/
|
|
314
|
+
private isHostExcluded;
|
|
315
|
+
/**
|
|
316
|
+
* Whether the current pathname matches a configured `tracking.excludePaths`
|
|
317
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
318
|
+
* @returns {boolean} True if the current pathname is excluded
|
|
319
|
+
*/
|
|
320
|
+
private isPathExcluded;
|
|
321
|
+
/**
|
|
322
|
+
* Whether the current call is in a visitor-level suppression state — opt-out
|
|
323
|
+
* or excluded timezone — for which any persisted identity cookie should be
|
|
324
|
+
* actively purged (not merely skipped). Host/path exclusions are
|
|
325
|
+
* deliberately excluded here: they are transient current-page states, so a
|
|
326
|
+
* cookie legitimately written on an allowed page must survive a visit to an
|
|
327
|
+
* excluded route.
|
|
328
|
+
* @returns {boolean} True if persisted identity must be purged
|
|
329
|
+
*/
|
|
330
|
+
private isPersistedIdentityPurgeRequired;
|
|
331
|
+
/**
|
|
332
|
+
* Whether the visitor's browser-resolved timezone matches a configured
|
|
333
|
+
* `tracking.excludeTimezones` entry (case-insensitive). Client-side and
|
|
334
|
+
* best-effort — see TrackingOptions.excludeTimezones.
|
|
335
|
+
* @returns {boolean} True if the current timezone is excluded
|
|
336
|
+
*/
|
|
337
|
+
private isTimezoneExcluded;
|
|
285
338
|
/**
|
|
286
339
|
* Determines if tracking should be enabled based on configuration and consent
|
|
287
340
|
* @returns {boolean} True if tracking should be enabled
|
|
@@ -329,6 +382,14 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
329
382
|
* value in the normal way; existing connections are never clobbered.
|
|
330
383
|
*/
|
|
331
384
|
private backfillActiveWallet;
|
|
385
|
+
/**
|
|
386
|
+
* Apply an EVM autocapture connect/switch while tracking is suppressed
|
|
387
|
+
* (opt-out / timezone / excluded host or path): never LEARN the wallet, but
|
|
388
|
+
* if it is a switch away from an already-learned EVM wallet, drop the stale
|
|
389
|
+
* one (which also clears the active-wallet cookie) so it can't attach to a
|
|
390
|
+
* later allowed-page event.
|
|
391
|
+
*/
|
|
392
|
+
private clearStaleEvmWalletOnSwitchWhileSuppressed;
|
|
332
393
|
/**
|
|
333
394
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
334
395
|
*/
|
|
@@ -69,6 +69,7 @@ var provider_1 = require("./provider");
|
|
|
69
69
|
var session_1 = require("./session");
|
|
70
70
|
var types_1 = require("./types");
|
|
71
71
|
var address_1 = require("./utils/address");
|
|
72
|
+
var timezone_1 = require("./utils/timezone");
|
|
72
73
|
var validators_1 = require("./validators");
|
|
73
74
|
var chain_1 = require("./utils/chain");
|
|
74
75
|
var wagmi_1 = require("./wagmi");
|
|
@@ -394,6 +395,14 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
394
395
|
logger_1.logger.warn("Connect: Invalid address provided (\"".concat(address, "\"). Please provide a valid EVM or Solana address."));
|
|
395
396
|
return [2 /*return*/];
|
|
396
397
|
}
|
|
398
|
+
// connect() persists wallet/chain state (active-wallet cookie,
|
|
399
|
+
// currentAddress/currentChainId) before trackEvent's consent check —
|
|
400
|
+
// gate the whole method so a suppressed visitor or excluded environment
|
|
401
|
+
// (opt-out / timezone / host / path) leaves no session state.
|
|
402
|
+
if (this.isTrackingSuppressed()) {
|
|
403
|
+
logger_1.logger.info("connect() skipped: tracking is suppressed for this visitor or environment");
|
|
404
|
+
return [2 /*return*/];
|
|
405
|
+
}
|
|
397
406
|
this.setChainState(chainId, { address: validAddress });
|
|
398
407
|
return [4 /*yield*/, this.trackEvent(constants_1.EventType.CONNECT, {
|
|
399
408
|
chainId: chainId,
|
|
@@ -581,10 +590,11 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
581
590
|
case 0:
|
|
582
591
|
_d.trys.push([0, 12, , 13]);
|
|
583
592
|
// identify() writes the user-id cookie and marks wallet
|
|
584
|
-
// identification before trackEvent's consent check — gate the
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
|
|
593
|
+
// identification before trackEvent's consent check — gate the whole
|
|
594
|
+
// method so a suppressed visitor or excluded environment (opt-out /
|
|
595
|
+
// timezone / host / path) gets no identity persistence.
|
|
596
|
+
if (this.isTrackingSuppressed()) {
|
|
597
|
+
logger_1.logger.info("identify() skipped: tracking is suppressed for this visitor or environment");
|
|
588
598
|
return [2 /*return*/];
|
|
589
599
|
}
|
|
590
600
|
if (!!params) return [3 /*break*/, 10];
|
|
@@ -712,9 +722,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
712
722
|
switch (_c.label) {
|
|
713
723
|
case 0:
|
|
714
724
|
// detect() marks wallet detection (a cookie write) before
|
|
715
|
-
// trackEvent's consent check — gate it
|
|
716
|
-
|
|
717
|
-
|
|
725
|
+
// trackEvent's consent check — gate it for a suppressed visitor or
|
|
726
|
+
// excluded environment (opt-out / timezone / host / path).
|
|
727
|
+
if (this.isTrackingSuppressed()) {
|
|
728
|
+
logger_1.logger.info("detect() skipped: tracking is suppressed for this visitor or environment");
|
|
718
729
|
return [2 /*return*/];
|
|
719
730
|
}
|
|
720
731
|
if (this.session.isWalletDetected(rdns))
|
|
@@ -1078,9 +1089,16 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1078
1089
|
case 26:
|
|
1079
1090
|
nextChainId = _a.sent();
|
|
1080
1091
|
wasDisconnected = !this._evmAddress;
|
|
1081
|
-
//
|
|
1082
|
-
//
|
|
1083
|
-
|
|
1092
|
+
// Update state regardless of whether connect *event* tracking is enabled,
|
|
1093
|
+
// so disconnect events keep valid address/chainId values. (excludeChains is
|
|
1094
|
+
// NOT suppression — it still updates state so currentChainId can gate
|
|
1095
|
+
// events.)
|
|
1096
|
+
if (this.isTrackingSuppressed()) {
|
|
1097
|
+
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
1098
|
+
}
|
|
1099
|
+
else {
|
|
1100
|
+
this.setChainState('evm', { address: address, chainId: nextChainId });
|
|
1101
|
+
}
|
|
1084
1102
|
providerInfo = this.getProviderInfo(provider);
|
|
1085
1103
|
effectiveChainId = nextChainId || 0;
|
|
1086
1104
|
if (this.isAutocaptureEnabled("connect")) {
|
|
@@ -1253,13 +1271,19 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1253
1271
|
this._provider = provider;
|
|
1254
1272
|
}
|
|
1255
1273
|
isActiveProvider = this._provider === provider;
|
|
1256
|
-
//
|
|
1257
|
-
//
|
|
1274
|
+
// Update state from active provider so disconnect events keep valid
|
|
1275
|
+
// address/chainId values — except while suppressed, where we must not
|
|
1276
|
+
// LEARN identity (only drop a stale EVM wallet on a switch).
|
|
1258
1277
|
if (isActiveProvider) {
|
|
1259
|
-
this.
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1278
|
+
if (this.isTrackingSuppressed()) {
|
|
1279
|
+
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
1280
|
+
}
|
|
1281
|
+
else {
|
|
1282
|
+
this.setChainState('evm', {
|
|
1283
|
+
chainId: chainId,
|
|
1284
|
+
address: (0, address_1.validateAndChecksumAddress)(address) || undefined,
|
|
1285
|
+
});
|
|
1286
|
+
}
|
|
1263
1287
|
}
|
|
1264
1288
|
// Conditionally emit connect event based on tracking configuration
|
|
1265
1289
|
if (isActiveProvider && this._evmAddress) {
|
|
@@ -1628,6 +1652,108 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1628
1652
|
});
|
|
1629
1653
|
});
|
|
1630
1654
|
};
|
|
1655
|
+
/**
|
|
1656
|
+
* Visitor-level tracking suppression.
|
|
1657
|
+
*
|
|
1658
|
+
* Returns true when the SDK must not persist any identity/session/chain
|
|
1659
|
+
* state or send any events for this visitor — i.e. an explicit opt-out or a
|
|
1660
|
+
* jurisdiction/timezone exclusion. Public entry points that write state
|
|
1661
|
+
* before reaching the `shouldTrack()` event gate (identify/connect/detect)
|
|
1662
|
+
* check this first so suppressed visitors leave no cookies or session state.
|
|
1663
|
+
* @returns {boolean} True if all tracking and persistence must be suppressed
|
|
1664
|
+
*/
|
|
1665
|
+
FormoAnalytics.prototype.isTrackingSuppressed = function () {
|
|
1666
|
+
return this.hasOptedOutTracking() || this.isCurrentEnvironmentExcluded();
|
|
1667
|
+
};
|
|
1668
|
+
/**
|
|
1669
|
+
* Whether the current environment is excluded from tracking — the visitor's
|
|
1670
|
+
* timezone, the current hostname, or the current pathname matches a
|
|
1671
|
+
* configured exclusion.
|
|
1672
|
+
*
|
|
1673
|
+
* Timezone is visitor/session-level (stable for the session); host/path are
|
|
1674
|
+
* current-page-level and transient — if a SPA navigates to an allowed path,
|
|
1675
|
+
* tracking resumes for future actions. Used as the "do not write identity or
|
|
1676
|
+
* send events" gate at every entry point that would persist state before the
|
|
1677
|
+
* `shouldTrack()` event gate.
|
|
1678
|
+
* @returns {boolean} True if the current environment is excluded
|
|
1679
|
+
*/
|
|
1680
|
+
FormoAnalytics.prototype.isCurrentEnvironmentExcluded = function () {
|
|
1681
|
+
return (this.isTimezoneExcluded() ||
|
|
1682
|
+
this.isHostExcluded() ||
|
|
1683
|
+
this.isPathExcluded());
|
|
1684
|
+
};
|
|
1685
|
+
/**
|
|
1686
|
+
* Whether the current hostname matches a configured `tracking.excludeHosts`
|
|
1687
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
1688
|
+
* @returns {boolean} True if the current hostname is excluded
|
|
1689
|
+
*/
|
|
1690
|
+
FormoAnalytics.prototype.isHostExcluded = function () {
|
|
1691
|
+
var tracking = this.options.tracking;
|
|
1692
|
+
if (tracking === null ||
|
|
1693
|
+
typeof tracking !== "object" ||
|
|
1694
|
+
Array.isArray(tracking)) {
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
if (typeof window === "undefined") {
|
|
1698
|
+
return false;
|
|
1699
|
+
}
|
|
1700
|
+
var _a = tracking.excludeHosts, excludeHosts = _a === void 0 ? [] : _a;
|
|
1701
|
+
return excludeHosts.includes(window.location.hostname);
|
|
1702
|
+
};
|
|
1703
|
+
/**
|
|
1704
|
+
* Whether the current pathname matches a configured `tracking.excludePaths`
|
|
1705
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
1706
|
+
* @returns {boolean} True if the current pathname is excluded
|
|
1707
|
+
*/
|
|
1708
|
+
FormoAnalytics.prototype.isPathExcluded = function () {
|
|
1709
|
+
var tracking = this.options.tracking;
|
|
1710
|
+
if (tracking === null ||
|
|
1711
|
+
typeof tracking !== "object" ||
|
|
1712
|
+
Array.isArray(tracking)) {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
if (typeof window === "undefined") {
|
|
1716
|
+
return false;
|
|
1717
|
+
}
|
|
1718
|
+
var _a = tracking.excludePaths, excludePaths = _a === void 0 ? [] : _a;
|
|
1719
|
+
return excludePaths.includes(window.location.pathname);
|
|
1720
|
+
};
|
|
1721
|
+
/**
|
|
1722
|
+
* Whether the current call is in a visitor-level suppression state — opt-out
|
|
1723
|
+
* or excluded timezone — for which any persisted identity cookie should be
|
|
1724
|
+
* actively purged (not merely skipped). Host/path exclusions are
|
|
1725
|
+
* deliberately excluded here: they are transient current-page states, so a
|
|
1726
|
+
* cookie legitimately written on an allowed page must survive a visit to an
|
|
1727
|
+
* excluded route.
|
|
1728
|
+
* @returns {boolean} True if persisted identity must be purged
|
|
1729
|
+
*/
|
|
1730
|
+
FormoAnalytics.prototype.isPersistedIdentityPurgeRequired = function () {
|
|
1731
|
+
return this.hasOptedOutTracking() || this.isTimezoneExcluded();
|
|
1732
|
+
};
|
|
1733
|
+
/**
|
|
1734
|
+
* Whether the visitor's browser-resolved timezone matches a configured
|
|
1735
|
+
* `tracking.excludeTimezones` entry (case-insensitive). Client-side and
|
|
1736
|
+
* best-effort — see TrackingOptions.excludeTimezones.
|
|
1737
|
+
* @returns {boolean} True if the current timezone is excluded
|
|
1738
|
+
*/
|
|
1739
|
+
FormoAnalytics.prototype.isTimezoneExcluded = function () {
|
|
1740
|
+
var tracking = this.options.tracking;
|
|
1741
|
+
if (tracking === null ||
|
|
1742
|
+
typeof tracking !== "object" ||
|
|
1743
|
+
Array.isArray(tracking)) {
|
|
1744
|
+
return false;
|
|
1745
|
+
}
|
|
1746
|
+
var _a = tracking.excludeTimezones, excludeTimezones = _a === void 0 ? [] : _a;
|
|
1747
|
+
if (excludeTimezones.length === 0) {
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
var timezone = (0, timezone_1.getTimezone)();
|
|
1751
|
+
if (!timezone) {
|
|
1752
|
+
return false;
|
|
1753
|
+
}
|
|
1754
|
+
var lowerTimezone = timezone.toLowerCase();
|
|
1755
|
+
return excludeTimezones.some(function (tz) { return typeof tz === "string" && tz.toLowerCase() === lowerTimezone; });
|
|
1756
|
+
};
|
|
1631
1757
|
/**
|
|
1632
1758
|
* Determines if tracking should be enabled based on configuration and consent
|
|
1633
1759
|
* @returns {boolean} True if tracking should be enabled
|
|
@@ -1645,20 +1771,11 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1645
1771
|
if (this.options.tracking !== null &&
|
|
1646
1772
|
typeof this.options.tracking === "object" &&
|
|
1647
1773
|
!Array.isArray(this.options.tracking)) {
|
|
1648
|
-
var _a = this.options.tracking
|
|
1649
|
-
//
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
return false;
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
// Check path exclusions - use exact matching
|
|
1657
|
-
if (excludePaths.length > 0 && typeof window !== "undefined") {
|
|
1658
|
-
var pathname = window.location.pathname;
|
|
1659
|
-
if (excludePaths.includes(pathname)) {
|
|
1660
|
-
return false;
|
|
1661
|
-
}
|
|
1774
|
+
var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
1775
|
+
// Environment exclusions (timezone / host / path) — no identify / connect
|
|
1776
|
+
// / track events while excluded. Host/path are exact-match.
|
|
1777
|
+
if (this.isCurrentEnvironmentExcluded()) {
|
|
1778
|
+
return false;
|
|
1662
1779
|
}
|
|
1663
1780
|
// Check chainId exclusions
|
|
1664
1781
|
if (excludeChains.length > 0 &&
|
|
@@ -2049,10 +2166,31 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2049
2166
|
* value in the normal way; existing connections are never clobbered.
|
|
2050
2167
|
*/
|
|
2051
2168
|
FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId) {
|
|
2169
|
+
// Never learn identity while suppressed (opt-out / timezone / excluded host
|
|
2170
|
+
// or path). A signature/transaction observed on an excluded route must not
|
|
2171
|
+
// populate currentAddress for later allowed-page events. backfill only ever
|
|
2172
|
+
// *adds* an address (it no-ops when one is already known), so there is no
|
|
2173
|
+
// stale state to clear here.
|
|
2174
|
+
if (this.isTrackingSuppressed())
|
|
2175
|
+
return;
|
|
2052
2176
|
if (this._evmAddress)
|
|
2053
2177
|
return;
|
|
2054
2178
|
this.setChainState('evm', { address: address, chainId: chainId });
|
|
2055
2179
|
};
|
|
2180
|
+
/**
|
|
2181
|
+
* Apply an EVM autocapture connect/switch while tracking is suppressed
|
|
2182
|
+
* (opt-out / timezone / excluded host or path): never LEARN the wallet, but
|
|
2183
|
+
* if it is a switch away from an already-learned EVM wallet, drop the stale
|
|
2184
|
+
* one (which also clears the active-wallet cookie) so it can't attach to a
|
|
2185
|
+
* later allowed-page event.
|
|
2186
|
+
*/
|
|
2187
|
+
FormoAnalytics.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
|
|
2188
|
+
var evmAddress = this._chainState.evm.address;
|
|
2189
|
+
var incoming = (0, address_1.validateAndChecksumAddress)(address);
|
|
2190
|
+
if (evmAddress && incoming && incoming !== evmAddress) {
|
|
2191
|
+
this.clearChainState('evm');
|
|
2192
|
+
}
|
|
2193
|
+
};
|
|
2056
2194
|
/**
|
|
2057
2195
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
2058
2196
|
*/
|
|
@@ -2259,6 +2397,39 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2259
2397
|
*/
|
|
2260
2398
|
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
2261
2399
|
var chainId = params.chainId, address = params.address;
|
|
2400
|
+
if (this.isTrackingSuppressed()) {
|
|
2401
|
+
// While suppressed (opt-out / timezone / excluded host or path) we must
|
|
2402
|
+
// never LEARN a new wallet — but we must still CLEAR stale identity.
|
|
2403
|
+
// Otherwise a disconnect or wallet switch observed on a suppressed route
|
|
2404
|
+
// would leave the previously-learned address in memory and in the
|
|
2405
|
+
// active-wallet cookie, attaching it to later allowed-page events.
|
|
2406
|
+
if (!address) {
|
|
2407
|
+
// Disconnect: drop the affected namespace(s).
|
|
2408
|
+
if (chainId !== undefined && chainId !== null) {
|
|
2409
|
+
this.clearChainState(chainId);
|
|
2410
|
+
}
|
|
2411
|
+
else {
|
|
2412
|
+
this.clearChainState("evm");
|
|
2413
|
+
this.clearChainState("solana");
|
|
2414
|
+
}
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
// Address present: a switch away from the wallet already learned in this
|
|
2418
|
+
// namespace invalidates it. Drop the stale one without learning the new
|
|
2419
|
+
// address; a fresh connect (nothing learned yet) or a re-confirmation of
|
|
2420
|
+
// the same address is a no-op.
|
|
2421
|
+
if (chainId === null || chainId === undefined)
|
|
2422
|
+
return;
|
|
2423
|
+
var namespace = this.getNamespace(chainId);
|
|
2424
|
+
var namespaceAddress = this._chainState[namespace].address;
|
|
2425
|
+
var validIncoming = (0, address_1.validateAddress)(address, chainId);
|
|
2426
|
+
if (namespaceAddress &&
|
|
2427
|
+
validIncoming &&
|
|
2428
|
+
validIncoming !== namespaceAddress) {
|
|
2429
|
+
this.clearChainState(chainId);
|
|
2430
|
+
}
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2262
2433
|
if (!address) {
|
|
2263
2434
|
if (chainId !== undefined && chainId !== null) {
|
|
2264
2435
|
this.clearChainState(chainId);
|
|
@@ -2316,18 +2487,28 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2316
2487
|
*/
|
|
2317
2488
|
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2318
2489
|
try {
|
|
2319
|
-
//
|
|
2320
|
-
// prior snapshot is
|
|
2321
|
-
if (this.
|
|
2490
|
+
// Visitor-level suppression (opt-out or excluded timezone): purge any
|
|
2491
|
+
// prior snapshot — these are stable for the session, so deletion is safe.
|
|
2492
|
+
if (this.isPersistedIdentityPurgeRequired()) {
|
|
2322
2493
|
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2323
2494
|
return;
|
|
2324
2495
|
}
|
|
2325
2496
|
if (this.currentAddress) {
|
|
2497
|
+
// Current-page exclusion (host/path): do not write a new snapshot while
|
|
2498
|
+
// on an excluded route, but leave any existing cookie intact. A cookie
|
|
2499
|
+
// written on an allowed page must survive a transient visit to an
|
|
2500
|
+
// excluded one (passive navigation does not call this method).
|
|
2501
|
+
if (this.isHostExcluded() || this.isPathExcluded()) {
|
|
2502
|
+
return;
|
|
2503
|
+
}
|
|
2326
2504
|
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
2327
2505
|
var domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
|
|
2328
2506
|
(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
2507
|
}
|
|
2330
2508
|
else {
|
|
2509
|
+
// No active wallet → clear the snapshot. This runs even on an excluded
|
|
2510
|
+
// route, so a disconnect/switch observed while suppressed actively
|
|
2511
|
+
// removes stale identity instead of leaving it for later allowed events.
|
|
2331
2512
|
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2332
2513
|
}
|
|
2333
2514
|
}
|
|
@@ -2341,12 +2522,18 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2341
2522
|
*/
|
|
2342
2523
|
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
2343
2524
|
try {
|
|
2344
|
-
//
|
|
2345
|
-
//
|
|
2346
|
-
if (this.
|
|
2525
|
+
// Visitor-level suppression (opt-out or excluded timezone): never restore
|
|
2526
|
+
// identity into memory; drop the stale snapshot.
|
|
2527
|
+
if (this.isPersistedIdentityPurgeRequired()) {
|
|
2347
2528
|
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2348
2529
|
return;
|
|
2349
2530
|
}
|
|
2531
|
+
// Current-page exclusion (host/path): don't restore into memory while on
|
|
2532
|
+
// an excluded route, but keep the cookie so a later allowed-page load can
|
|
2533
|
+
// restore it.
|
|
2534
|
+
if (this.isHostExcluded() || this.isPathExcluded()) {
|
|
2535
|
+
return;
|
|
2536
|
+
}
|
|
2350
2537
|
var raw = (0, storage_1.cookie)().get(constants_1.ACTIVE_WALLET_KEY);
|
|
2351
2538
|
if (!raw)
|
|
2352
2539
|
return;
|
|
@@ -276,13 +276,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
276
276
|
return (0, address_1.validateAddress)(address, chainId) || null;
|
|
277
277
|
};
|
|
278
278
|
EventFactory.prototype.getTimezone = function () {
|
|
279
|
-
|
|
280
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
281
|
-
}
|
|
282
|
-
catch (error) {
|
|
283
|
-
logger_1.logger.error("Error resolving timezone:", error);
|
|
284
|
-
return "";
|
|
285
|
-
}
|
|
279
|
+
return (0, utils_1.getTimezone)();
|
|
286
280
|
};
|
|
287
281
|
EventFactory.prototype.getLocation = function () {
|
|
288
282
|
try {
|
|
@@ -72,6 +72,20 @@ export interface TrackingOptions {
|
|
|
72
72
|
excludeHosts?: string[];
|
|
73
73
|
excludePaths?: string[];
|
|
74
74
|
excludeChains?: ChainID[];
|
|
75
|
+
/**
|
|
76
|
+
* IANA timezone names to opt out of tracking entirely. When the visitor's
|
|
77
|
+
* resolved timezone (via `Intl.DateTimeFormat().resolvedOptions().timeZone`)
|
|
78
|
+
* matches one of these, no events are enqueued or sent — including `identify`
|
|
79
|
+
* and `connect`. Matched case-insensitively against the full timezone string.
|
|
80
|
+
*
|
|
81
|
+
* Note: this is client-side, timezone-derived geolocation. It is best-effort
|
|
82
|
+
* and can be bypassed (a VPN does not change the browser timezone, and users
|
|
83
|
+
* can change their OS timezone). For authoritative jurisdiction blocking, use
|
|
84
|
+
* server-side IP geolocation at your ingest endpoint instead.
|
|
85
|
+
*
|
|
86
|
+
* @example ["Europe/London", "America/New_York"]
|
|
87
|
+
*/
|
|
88
|
+
excludeTimezones?: string[];
|
|
75
89
|
/**
|
|
76
90
|
* Additional query parameter names to strip from forwarded and stored URLs,
|
|
77
91
|
* on top of a built-in always-on denylist (currently `privy_oauth_code`,
|
|
@@ -19,4 +19,5 @@ __exportStar(require("./base"), exports);
|
|
|
19
19
|
__exportStar(require("./converter"), exports);
|
|
20
20
|
__exportStar(require("./generate"), exports);
|
|
21
21
|
__exportStar(require("./hash"), exports);
|
|
22
|
+
__exportStar(require("./timezone"), exports);
|
|
22
23
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve the current IANA timezone (e.g. "Europe/London") via the Intl API.
|
|
3
|
+
* Returns "" when the timezone cannot be resolved (e.g. Intl unavailable).
|
|
4
|
+
*/
|
|
5
|
+
declare const getTimezone: () => string;
|
|
6
|
+
export { getTimezone };
|
|
7
|
+
//# sourceMappingURL=timezone.d.ts.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getTimezone = void 0;
|
|
4
|
+
var logger_1 = require("../logger");
|
|
5
|
+
/**
|
|
6
|
+
* Resolve the current IANA timezone (e.g. "Europe/London") via the Intl API.
|
|
7
|
+
* Returns "" when the timezone cannot be resolved (e.g. Intl unavailable).
|
|
8
|
+
*/
|
|
9
|
+
var getTimezone = function () {
|
|
10
|
+
try {
|
|
11
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone || "";
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
logger_1.logger.error("Error resolving timezone:", error);
|
|
15
|
+
return "";
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
exports.getTimezone = getTimezone;
|
|
19
|
+
//# sourceMappingURL=timezone.js.map
|
|
@@ -282,6 +282,59 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
282
282
|
private static installHistoryHooksOnce;
|
|
283
283
|
private trackPageHit;
|
|
284
284
|
private trackEvent;
|
|
285
|
+
/**
|
|
286
|
+
* Visitor-level tracking suppression.
|
|
287
|
+
*
|
|
288
|
+
* Returns true when the SDK must not persist any identity/session/chain
|
|
289
|
+
* state or send any events for this visitor — i.e. an explicit opt-out or a
|
|
290
|
+
* jurisdiction/timezone exclusion. Public entry points that write state
|
|
291
|
+
* before reaching the `shouldTrack()` event gate (identify/connect/detect)
|
|
292
|
+
* check this first so suppressed visitors leave no cookies or session state.
|
|
293
|
+
* @returns {boolean} True if all tracking and persistence must be suppressed
|
|
294
|
+
*/
|
|
295
|
+
private isTrackingSuppressed;
|
|
296
|
+
/**
|
|
297
|
+
* Whether the current environment is excluded from tracking — the visitor's
|
|
298
|
+
* timezone, the current hostname, or the current pathname matches a
|
|
299
|
+
* configured exclusion.
|
|
300
|
+
*
|
|
301
|
+
* Timezone is visitor/session-level (stable for the session); host/path are
|
|
302
|
+
* current-page-level and transient — if a SPA navigates to an allowed path,
|
|
303
|
+
* tracking resumes for future actions. Used as the "do not write identity or
|
|
304
|
+
* send events" gate at every entry point that would persist state before the
|
|
305
|
+
* `shouldTrack()` event gate.
|
|
306
|
+
* @returns {boolean} True if the current environment is excluded
|
|
307
|
+
*/
|
|
308
|
+
private isCurrentEnvironmentExcluded;
|
|
309
|
+
/**
|
|
310
|
+
* Whether the current hostname matches a configured `tracking.excludeHosts`
|
|
311
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
312
|
+
* @returns {boolean} True if the current hostname is excluded
|
|
313
|
+
*/
|
|
314
|
+
private isHostExcluded;
|
|
315
|
+
/**
|
|
316
|
+
* Whether the current pathname matches a configured `tracking.excludePaths`
|
|
317
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
318
|
+
* @returns {boolean} True if the current pathname is excluded
|
|
319
|
+
*/
|
|
320
|
+
private isPathExcluded;
|
|
321
|
+
/**
|
|
322
|
+
* Whether the current call is in a visitor-level suppression state — opt-out
|
|
323
|
+
* or excluded timezone — for which any persisted identity cookie should be
|
|
324
|
+
* actively purged (not merely skipped). Host/path exclusions are
|
|
325
|
+
* deliberately excluded here: they are transient current-page states, so a
|
|
326
|
+
* cookie legitimately written on an allowed page must survive a visit to an
|
|
327
|
+
* excluded route.
|
|
328
|
+
* @returns {boolean} True if persisted identity must be purged
|
|
329
|
+
*/
|
|
330
|
+
private isPersistedIdentityPurgeRequired;
|
|
331
|
+
/**
|
|
332
|
+
* Whether the visitor's browser-resolved timezone matches a configured
|
|
333
|
+
* `tracking.excludeTimezones` entry (case-insensitive). Client-side and
|
|
334
|
+
* best-effort — see TrackingOptions.excludeTimezones.
|
|
335
|
+
* @returns {boolean} True if the current timezone is excluded
|
|
336
|
+
*/
|
|
337
|
+
private isTimezoneExcluded;
|
|
285
338
|
/**
|
|
286
339
|
* Determines if tracking should be enabled based on configuration and consent
|
|
287
340
|
* @returns {boolean} True if tracking should be enabled
|
|
@@ -329,6 +382,14 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
329
382
|
* value in the normal way; existing connections are never clobbered.
|
|
330
383
|
*/
|
|
331
384
|
private backfillActiveWallet;
|
|
385
|
+
/**
|
|
386
|
+
* Apply an EVM autocapture connect/switch while tracking is suppressed
|
|
387
|
+
* (opt-out / timezone / excluded host or path): never LEARN the wallet, but
|
|
388
|
+
* if it is a switch away from an already-learned EVM wallet, drop the stale
|
|
389
|
+
* one (which also clears the active-wallet cookie) so it can't attach to a
|
|
390
|
+
* later allowed-page event.
|
|
391
|
+
*/
|
|
392
|
+
private clearStaleEvmWalletOnSwitchWhileSuppressed;
|
|
332
393
|
/**
|
|
333
394
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
334
395
|
*/
|