@formo/analytics 1.31.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.d.ts +21 -0
- package/dist/cjs/src/event/EventFactory.js +109 -23
- package/dist/cjs/src/event/constants.d.ts +13 -1
- package/dist/cjs/src/event/constants.js +18 -1
- package/dist/cjs/src/types/base.d.ts +28 -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.d.ts +21 -0
- package/dist/esm/src/event/EventFactory.js +111 -25
- package/dist/esm/src/event/constants.d.ts +13 -1
- package/dist/esm/src/event/constants.js +17 -1
- package/dist/esm/src/types/base.d.ts +28 -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
|
@@ -66,6 +66,7 @@ import { detectInjectedProviderInfo, isValidProvider } from "./provider";
|
|
|
66
66
|
import { FormoAnalyticsSession, SESSION_WALLET_DETECTED_KEY, SESSION_WALLET_IDENTIFIED_KEY, } from "./session";
|
|
67
67
|
import { SignatureStatus, TransactionStatus, WRAPPED_REQUEST_SYMBOL, WRAPPED_REQUEST_REF_SYMBOL, } from "./types";
|
|
68
68
|
import { validateAddress, validateAndChecksumAddress } from "./utils/address";
|
|
69
|
+
import { getTimezone } from "./utils/timezone";
|
|
69
70
|
import { isLocalhost } from "./validators";
|
|
70
71
|
import { parseChainId } from "./utils/chain";
|
|
71
72
|
import { WagmiEventHandler } from "./wagmi";
|
|
@@ -391,6 +392,14 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
391
392
|
logger.warn("Connect: Invalid address provided (\"".concat(address, "\"). Please provide a valid EVM or Solana address."));
|
|
392
393
|
return [2 /*return*/];
|
|
393
394
|
}
|
|
395
|
+
// connect() persists wallet/chain state (active-wallet cookie,
|
|
396
|
+
// currentAddress/currentChainId) before trackEvent's consent check —
|
|
397
|
+
// gate the whole method so a suppressed visitor or excluded environment
|
|
398
|
+
// (opt-out / timezone / host / path) leaves no session state.
|
|
399
|
+
if (this.isTrackingSuppressed()) {
|
|
400
|
+
logger.info("connect() skipped: tracking is suppressed for this visitor or environment");
|
|
401
|
+
return [2 /*return*/];
|
|
402
|
+
}
|
|
394
403
|
this.setChainState(chainId, { address: validAddress });
|
|
395
404
|
return [4 /*yield*/, this.trackEvent(EventType.CONNECT, {
|
|
396
405
|
chainId: chainId,
|
|
@@ -578,10 +587,11 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
578
587
|
case 0:
|
|
579
588
|
_d.trys.push([0, 12, , 13]);
|
|
580
589
|
// identify() writes the user-id cookie and marks wallet
|
|
581
|
-
// identification before trackEvent's consent check — gate the
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
|
|
590
|
+
// identification before trackEvent's consent check — gate the whole
|
|
591
|
+
// method so a suppressed visitor or excluded environment (opt-out /
|
|
592
|
+
// timezone / host / path) gets no identity persistence.
|
|
593
|
+
if (this.isTrackingSuppressed()) {
|
|
594
|
+
logger.info("identify() skipped: tracking is suppressed for this visitor or environment");
|
|
585
595
|
return [2 /*return*/];
|
|
586
596
|
}
|
|
587
597
|
if (!!params) return [3 /*break*/, 10];
|
|
@@ -709,9 +719,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
709
719
|
switch (_c.label) {
|
|
710
720
|
case 0:
|
|
711
721
|
// detect() marks wallet detection (a cookie write) before
|
|
712
|
-
// trackEvent's consent check — gate it
|
|
713
|
-
|
|
714
|
-
|
|
722
|
+
// trackEvent's consent check — gate it for a suppressed visitor or
|
|
723
|
+
// excluded environment (opt-out / timezone / host / path).
|
|
724
|
+
if (this.isTrackingSuppressed()) {
|
|
725
|
+
logger.info("detect() skipped: tracking is suppressed for this visitor or environment");
|
|
715
726
|
return [2 /*return*/];
|
|
716
727
|
}
|
|
717
728
|
if (this.session.isWalletDetected(rdns))
|
|
@@ -1075,9 +1086,16 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1075
1086
|
case 26:
|
|
1076
1087
|
nextChainId = _a.sent();
|
|
1077
1088
|
wasDisconnected = !this._evmAddress;
|
|
1078
|
-
//
|
|
1079
|
-
//
|
|
1080
|
-
|
|
1089
|
+
// Update state regardless of whether connect *event* tracking is enabled,
|
|
1090
|
+
// so disconnect events keep valid address/chainId values. (excludeChains is
|
|
1091
|
+
// NOT suppression — it still updates state so currentChainId can gate
|
|
1092
|
+
// events.)
|
|
1093
|
+
if (this.isTrackingSuppressed()) {
|
|
1094
|
+
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
1095
|
+
}
|
|
1096
|
+
else {
|
|
1097
|
+
this.setChainState('evm', { address: address, chainId: nextChainId });
|
|
1098
|
+
}
|
|
1081
1099
|
providerInfo = this.getProviderInfo(provider);
|
|
1082
1100
|
effectiveChainId = nextChainId || 0;
|
|
1083
1101
|
if (this.isAutocaptureEnabled("connect")) {
|
|
@@ -1250,13 +1268,19 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1250
1268
|
this._provider = provider;
|
|
1251
1269
|
}
|
|
1252
1270
|
isActiveProvider = this._provider === provider;
|
|
1253
|
-
//
|
|
1254
|
-
//
|
|
1271
|
+
// Update state from active provider so disconnect events keep valid
|
|
1272
|
+
// address/chainId values — except while suppressed, where we must not
|
|
1273
|
+
// LEARN identity (only drop a stale EVM wallet on a switch).
|
|
1255
1274
|
if (isActiveProvider) {
|
|
1256
|
-
this.
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1275
|
+
if (this.isTrackingSuppressed()) {
|
|
1276
|
+
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
1277
|
+
}
|
|
1278
|
+
else {
|
|
1279
|
+
this.setChainState('evm', {
|
|
1280
|
+
chainId: chainId,
|
|
1281
|
+
address: validateAndChecksumAddress(address) || undefined,
|
|
1282
|
+
});
|
|
1283
|
+
}
|
|
1260
1284
|
}
|
|
1261
1285
|
// Conditionally emit connect event based on tracking configuration
|
|
1262
1286
|
if (isActiveProvider && this._evmAddress) {
|
|
@@ -1625,6 +1649,108 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1625
1649
|
});
|
|
1626
1650
|
});
|
|
1627
1651
|
};
|
|
1652
|
+
/**
|
|
1653
|
+
* Visitor-level tracking suppression.
|
|
1654
|
+
*
|
|
1655
|
+
* Returns true when the SDK must not persist any identity/session/chain
|
|
1656
|
+
* state or send any events for this visitor — i.e. an explicit opt-out or a
|
|
1657
|
+
* jurisdiction/timezone exclusion. Public entry points that write state
|
|
1658
|
+
* before reaching the `shouldTrack()` event gate (identify/connect/detect)
|
|
1659
|
+
* check this first so suppressed visitors leave no cookies or session state.
|
|
1660
|
+
* @returns {boolean} True if all tracking and persistence must be suppressed
|
|
1661
|
+
*/
|
|
1662
|
+
FormoAnalytics.prototype.isTrackingSuppressed = function () {
|
|
1663
|
+
return this.hasOptedOutTracking() || this.isCurrentEnvironmentExcluded();
|
|
1664
|
+
};
|
|
1665
|
+
/**
|
|
1666
|
+
* Whether the current environment is excluded from tracking — the visitor's
|
|
1667
|
+
* timezone, the current hostname, or the current pathname matches a
|
|
1668
|
+
* configured exclusion.
|
|
1669
|
+
*
|
|
1670
|
+
* Timezone is visitor/session-level (stable for the session); host/path are
|
|
1671
|
+
* current-page-level and transient — if a SPA navigates to an allowed path,
|
|
1672
|
+
* tracking resumes for future actions. Used as the "do not write identity or
|
|
1673
|
+
* send events" gate at every entry point that would persist state before the
|
|
1674
|
+
* `shouldTrack()` event gate.
|
|
1675
|
+
* @returns {boolean} True if the current environment is excluded
|
|
1676
|
+
*/
|
|
1677
|
+
FormoAnalytics.prototype.isCurrentEnvironmentExcluded = function () {
|
|
1678
|
+
return (this.isTimezoneExcluded() ||
|
|
1679
|
+
this.isHostExcluded() ||
|
|
1680
|
+
this.isPathExcluded());
|
|
1681
|
+
};
|
|
1682
|
+
/**
|
|
1683
|
+
* Whether the current hostname matches a configured `tracking.excludeHosts`
|
|
1684
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
1685
|
+
* @returns {boolean} True if the current hostname is excluded
|
|
1686
|
+
*/
|
|
1687
|
+
FormoAnalytics.prototype.isHostExcluded = function () {
|
|
1688
|
+
var tracking = this.options.tracking;
|
|
1689
|
+
if (tracking === null ||
|
|
1690
|
+
typeof tracking !== "object" ||
|
|
1691
|
+
Array.isArray(tracking)) {
|
|
1692
|
+
return false;
|
|
1693
|
+
}
|
|
1694
|
+
if (typeof window === "undefined") {
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
var _a = tracking.excludeHosts, excludeHosts = _a === void 0 ? [] : _a;
|
|
1698
|
+
return excludeHosts.includes(window.location.hostname);
|
|
1699
|
+
};
|
|
1700
|
+
/**
|
|
1701
|
+
* Whether the current pathname matches a configured `tracking.excludePaths`
|
|
1702
|
+
* entry (exact match). Current-page-level — see isCurrentEnvironmentExcluded.
|
|
1703
|
+
* @returns {boolean} True if the current pathname is excluded
|
|
1704
|
+
*/
|
|
1705
|
+
FormoAnalytics.prototype.isPathExcluded = function () {
|
|
1706
|
+
var tracking = this.options.tracking;
|
|
1707
|
+
if (tracking === null ||
|
|
1708
|
+
typeof tracking !== "object" ||
|
|
1709
|
+
Array.isArray(tracking)) {
|
|
1710
|
+
return false;
|
|
1711
|
+
}
|
|
1712
|
+
if (typeof window === "undefined") {
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
var _a = tracking.excludePaths, excludePaths = _a === void 0 ? [] : _a;
|
|
1716
|
+
return excludePaths.includes(window.location.pathname);
|
|
1717
|
+
};
|
|
1718
|
+
/**
|
|
1719
|
+
* Whether the current call is in a visitor-level suppression state — opt-out
|
|
1720
|
+
* or excluded timezone — for which any persisted identity cookie should be
|
|
1721
|
+
* actively purged (not merely skipped). Host/path exclusions are
|
|
1722
|
+
* deliberately excluded here: they are transient current-page states, so a
|
|
1723
|
+
* cookie legitimately written on an allowed page must survive a visit to an
|
|
1724
|
+
* excluded route.
|
|
1725
|
+
* @returns {boolean} True if persisted identity must be purged
|
|
1726
|
+
*/
|
|
1727
|
+
FormoAnalytics.prototype.isPersistedIdentityPurgeRequired = function () {
|
|
1728
|
+
return this.hasOptedOutTracking() || this.isTimezoneExcluded();
|
|
1729
|
+
};
|
|
1730
|
+
/**
|
|
1731
|
+
* Whether the visitor's browser-resolved timezone matches a configured
|
|
1732
|
+
* `tracking.excludeTimezones` entry (case-insensitive). Client-side and
|
|
1733
|
+
* best-effort — see TrackingOptions.excludeTimezones.
|
|
1734
|
+
* @returns {boolean} True if the current timezone is excluded
|
|
1735
|
+
*/
|
|
1736
|
+
FormoAnalytics.prototype.isTimezoneExcluded = function () {
|
|
1737
|
+
var tracking = this.options.tracking;
|
|
1738
|
+
if (tracking === null ||
|
|
1739
|
+
typeof tracking !== "object" ||
|
|
1740
|
+
Array.isArray(tracking)) {
|
|
1741
|
+
return false;
|
|
1742
|
+
}
|
|
1743
|
+
var _a = tracking.excludeTimezones, excludeTimezones = _a === void 0 ? [] : _a;
|
|
1744
|
+
if (excludeTimezones.length === 0) {
|
|
1745
|
+
return false;
|
|
1746
|
+
}
|
|
1747
|
+
var timezone = getTimezone();
|
|
1748
|
+
if (!timezone) {
|
|
1749
|
+
return false;
|
|
1750
|
+
}
|
|
1751
|
+
var lowerTimezone = timezone.toLowerCase();
|
|
1752
|
+
return excludeTimezones.some(function (tz) { return typeof tz === "string" && tz.toLowerCase() === lowerTimezone; });
|
|
1753
|
+
};
|
|
1628
1754
|
/**
|
|
1629
1755
|
* Determines if tracking should be enabled based on configuration and consent
|
|
1630
1756
|
* @returns {boolean} True if tracking should be enabled
|
|
@@ -1642,20 +1768,11 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1642
1768
|
if (this.options.tracking !== null &&
|
|
1643
1769
|
typeof this.options.tracking === "object" &&
|
|
1644
1770
|
!Array.isArray(this.options.tracking)) {
|
|
1645
|
-
var _a = this.options.tracking
|
|
1646
|
-
//
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
return false;
|
|
1651
|
-
}
|
|
1652
|
-
}
|
|
1653
|
-
// Check path exclusions - use exact matching
|
|
1654
|
-
if (excludePaths.length > 0 && typeof window !== "undefined") {
|
|
1655
|
-
var pathname = window.location.pathname;
|
|
1656
|
-
if (excludePaths.includes(pathname)) {
|
|
1657
|
-
return false;
|
|
1658
|
-
}
|
|
1771
|
+
var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
1772
|
+
// Environment exclusions (timezone / host / path) — no identify / connect
|
|
1773
|
+
// / track events while excluded. Host/path are exact-match.
|
|
1774
|
+
if (this.isCurrentEnvironmentExcluded()) {
|
|
1775
|
+
return false;
|
|
1659
1776
|
}
|
|
1660
1777
|
// Check chainId exclusions
|
|
1661
1778
|
if (excludeChains.length > 0 &&
|
|
@@ -2046,10 +2163,31 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2046
2163
|
* value in the normal way; existing connections are never clobbered.
|
|
2047
2164
|
*/
|
|
2048
2165
|
FormoAnalytics.prototype.backfillActiveWallet = function (address, chainId) {
|
|
2166
|
+
// Never learn identity while suppressed (opt-out / timezone / excluded host
|
|
2167
|
+
// or path). A signature/transaction observed on an excluded route must not
|
|
2168
|
+
// populate currentAddress for later allowed-page events. backfill only ever
|
|
2169
|
+
// *adds* an address (it no-ops when one is already known), so there is no
|
|
2170
|
+
// stale state to clear here.
|
|
2171
|
+
if (this.isTrackingSuppressed())
|
|
2172
|
+
return;
|
|
2049
2173
|
if (this._evmAddress)
|
|
2050
2174
|
return;
|
|
2051
2175
|
this.setChainState('evm', { address: address, chainId: chainId });
|
|
2052
2176
|
};
|
|
2177
|
+
/**
|
|
2178
|
+
* Apply an EVM autocapture connect/switch while tracking is suppressed
|
|
2179
|
+
* (opt-out / timezone / excluded host or path): never LEARN the wallet, but
|
|
2180
|
+
* if it is a switch away from an already-learned EVM wallet, drop the stale
|
|
2181
|
+
* one (which also clears the active-wallet cookie) so it can't attach to a
|
|
2182
|
+
* later allowed-page event.
|
|
2183
|
+
*/
|
|
2184
|
+
FormoAnalytics.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
|
|
2185
|
+
var evmAddress = this._chainState.evm.address;
|
|
2186
|
+
var incoming = validateAndChecksumAddress(address);
|
|
2187
|
+
if (evmAddress && incoming && incoming !== evmAddress) {
|
|
2188
|
+
this.clearChainState('evm');
|
|
2189
|
+
}
|
|
2190
|
+
};
|
|
2053
2191
|
/**
|
|
2054
2192
|
* Polls for transaction receipt and emits tx.status = CONFIRMED or REVERTED.
|
|
2055
2193
|
*/
|
|
@@ -2256,6 +2394,39 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2256
2394
|
*/
|
|
2257
2395
|
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
2258
2396
|
var chainId = params.chainId, address = params.address;
|
|
2397
|
+
if (this.isTrackingSuppressed()) {
|
|
2398
|
+
// While suppressed (opt-out / timezone / excluded host or path) we must
|
|
2399
|
+
// never LEARN a new wallet — but we must still CLEAR stale identity.
|
|
2400
|
+
// Otherwise a disconnect or wallet switch observed on a suppressed route
|
|
2401
|
+
// would leave the previously-learned address in memory and in the
|
|
2402
|
+
// active-wallet cookie, attaching it to later allowed-page events.
|
|
2403
|
+
if (!address) {
|
|
2404
|
+
// Disconnect: drop the affected namespace(s).
|
|
2405
|
+
if (chainId !== undefined && chainId !== null) {
|
|
2406
|
+
this.clearChainState(chainId);
|
|
2407
|
+
}
|
|
2408
|
+
else {
|
|
2409
|
+
this.clearChainState("evm");
|
|
2410
|
+
this.clearChainState("solana");
|
|
2411
|
+
}
|
|
2412
|
+
return;
|
|
2413
|
+
}
|
|
2414
|
+
// Address present: a switch away from the wallet already learned in this
|
|
2415
|
+
// namespace invalidates it. Drop the stale one without learning the new
|
|
2416
|
+
// address; a fresh connect (nothing learned yet) or a re-confirmation of
|
|
2417
|
+
// the same address is a no-op.
|
|
2418
|
+
if (chainId === null || chainId === undefined)
|
|
2419
|
+
return;
|
|
2420
|
+
var namespace = this.getNamespace(chainId);
|
|
2421
|
+
var namespaceAddress = this._chainState[namespace].address;
|
|
2422
|
+
var validIncoming = validateAddress(address, chainId);
|
|
2423
|
+
if (namespaceAddress &&
|
|
2424
|
+
validIncoming &&
|
|
2425
|
+
validIncoming !== namespaceAddress) {
|
|
2426
|
+
this.clearChainState(chainId);
|
|
2427
|
+
}
|
|
2428
|
+
return;
|
|
2429
|
+
}
|
|
2259
2430
|
if (!address) {
|
|
2260
2431
|
if (chainId !== undefined && chainId !== null) {
|
|
2261
2432
|
this.clearChainState(chainId);
|
|
@@ -2313,18 +2484,28 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2313
2484
|
*/
|
|
2314
2485
|
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2315
2486
|
try {
|
|
2316
|
-
//
|
|
2317
|
-
// prior snapshot is
|
|
2318
|
-
if (this.
|
|
2487
|
+
// Visitor-level suppression (opt-out or excluded timezone): purge any
|
|
2488
|
+
// prior snapshot — these are stable for the session, so deletion is safe.
|
|
2489
|
+
if (this.isPersistedIdentityPurgeRequired()) {
|
|
2319
2490
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2320
2491
|
return;
|
|
2321
2492
|
}
|
|
2322
2493
|
if (this.currentAddress) {
|
|
2494
|
+
// Current-page exclusion (host/path): do not write a new snapshot while
|
|
2495
|
+
// on an excluded route, but leave any existing cookie intact. A cookie
|
|
2496
|
+
// written on an allowed page must survive a transient visit to an
|
|
2497
|
+
// excluded one (passive navigation does not call this method).
|
|
2498
|
+
if (this.isHostExcluded() || this.isPathExcluded()) {
|
|
2499
|
+
return;
|
|
2500
|
+
}
|
|
2323
2501
|
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
2324
2502
|
var domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
2325
2503
|
cookie().set(ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
2326
2504
|
}
|
|
2327
2505
|
else {
|
|
2506
|
+
// No active wallet → clear the snapshot. This runs even on an excluded
|
|
2507
|
+
// route, so a disconnect/switch observed while suppressed actively
|
|
2508
|
+
// removes stale identity instead of leaving it for later allowed events.
|
|
2328
2509
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2329
2510
|
}
|
|
2330
2511
|
}
|
|
@@ -2338,12 +2519,18 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2338
2519
|
*/
|
|
2339
2520
|
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
2340
2521
|
try {
|
|
2341
|
-
//
|
|
2342
|
-
//
|
|
2343
|
-
if (this.
|
|
2522
|
+
// Visitor-level suppression (opt-out or excluded timezone): never restore
|
|
2523
|
+
// identity into memory; drop the stale snapshot.
|
|
2524
|
+
if (this.isPersistedIdentityPurgeRequired()) {
|
|
2344
2525
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2345
2526
|
return;
|
|
2346
2527
|
}
|
|
2528
|
+
// Current-page exclusion (host/path): don't restore into memory while on
|
|
2529
|
+
// an excluded route, but keep the cookie so a later allowed-page load can
|
|
2530
|
+
// restore it.
|
|
2531
|
+
if (this.isHostExcluded() || this.isPathExcluded()) {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2347
2534
|
var raw = cookie().get(ACTIVE_WALLET_KEY);
|
|
2348
2535
|
if (!raw)
|
|
2349
2536
|
return;
|
|
@@ -3,6 +3,7 @@ import { IEventFactory } from "./type";
|
|
|
3
3
|
declare class EventFactory implements IEventFactory {
|
|
4
4
|
private options?;
|
|
5
5
|
private compiledPathPattern?;
|
|
6
|
+
private excludedQueryParams;
|
|
6
7
|
constructor(options?: Options);
|
|
7
8
|
/**
|
|
8
9
|
* Validate an address for both EVM and Solana chains.
|
|
@@ -16,6 +17,18 @@ declare class EventFactory implements IEventFactory {
|
|
|
16
17
|
private getLocation;
|
|
17
18
|
private getLanguage;
|
|
18
19
|
private getLibraryVersion;
|
|
20
|
+
private isExcludedQueryParam;
|
|
21
|
+
/**
|
|
22
|
+
* Strip excluded (sensitive) query parameters from a URL in place. Only the
|
|
23
|
+
* query string is touched; the path and hash/fragment are left as-is.
|
|
24
|
+
*/
|
|
25
|
+
private redactQueryParams;
|
|
26
|
+
/**
|
|
27
|
+
* Return the given absolute URL with excluded query parameters removed. The
|
|
28
|
+
* input is returned unchanged when it is empty or cannot be parsed (e.g. an
|
|
29
|
+
* empty referrer).
|
|
30
|
+
*/
|
|
31
|
+
private redactUrl;
|
|
19
32
|
private extractUTMParameters;
|
|
20
33
|
private extractClickIdParameters;
|
|
21
34
|
private extractReferralParameter;
|
|
@@ -27,6 +40,14 @@ declare class EventFactory implements IEventFactory {
|
|
|
27
40
|
* first-touch referrer after a direct landing.
|
|
28
41
|
*/
|
|
29
42
|
private getExternalReferrer;
|
|
43
|
+
/**
|
|
44
|
+
* Apply the current query-param denylist to a previously-persisted traffic
|
|
45
|
+
* source object. Traffic-source keys (utm_*, click ids, ref) are themselves
|
|
46
|
+
* query-parameter names, so an excluded key's stored value is dropped; the
|
|
47
|
+
* referrer is a URL and is re-redacted. Guards against a stored value
|
|
48
|
+
* outliving the config (or SDK version) under which it was first captured.
|
|
49
|
+
*/
|
|
50
|
+
private redactStoredTrafficSources;
|
|
30
51
|
private getTrafficSources;
|
|
31
52
|
private getScreen;
|
|
32
53
|
private generateContext;
|
|
@@ -45,8 +45,17 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
|
45
45
|
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
|
+
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
49
|
+
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
|
50
|
+
if (ar || !(i in from)) {
|
|
51
|
+
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
|
52
|
+
ar[i] = from[i];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return to.concat(ar || Array.prototype.slice.call(from));
|
|
56
|
+
};
|
|
48
57
|
import { COUNTRY_LIST, LOCAL_ANONYMOUS_ID_KEY, SESSION_TRAFFIC_SOURCE_KEY, } from "../constants";
|
|
49
|
-
import { toSnakeCase } from "../utils";
|
|
58
|
+
import { toSnakeCase, getTimezone } from "../utils";
|
|
50
59
|
import { validateAddress } from "../utils/address";
|
|
51
60
|
import { getCurrentTimeFormatted } from "../utils/timestamp";
|
|
52
61
|
import { isUndefined } from "../validators";
|
|
@@ -54,14 +63,14 @@ import { logger } from "../logger";
|
|
|
54
63
|
import mergeDeepRight from "../ramda/mergeDeepRight";
|
|
55
64
|
import { session } from "../storage";
|
|
56
65
|
import { version } from "../version";
|
|
57
|
-
import { CHANNEL, CLICK_ID_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, VERSION, } from "./constants";
|
|
66
|
+
import { CHANNEL, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, VERSION, } from "./constants";
|
|
58
67
|
import { generateAnonymousId } from "./utils";
|
|
59
68
|
import { detectBrowser } from "../browser/browsers";
|
|
60
69
|
var ISO_3166_ALPHA_2_REGEX = /^[A-Z]{2}$/;
|
|
61
70
|
var EventFactory = /** @class */ (function () {
|
|
62
71
|
function EventFactory(options) {
|
|
63
72
|
var _this = this;
|
|
64
|
-
var _a;
|
|
73
|
+
var _a, _b;
|
|
65
74
|
this.extractUTMParameters = function (url) {
|
|
66
75
|
var result = {
|
|
67
76
|
utm_campaign: "",
|
|
@@ -139,12 +148,16 @@ var EventFactory = /** @class */ (function () {
|
|
|
139
148
|
return "";
|
|
140
149
|
}
|
|
141
150
|
catch (_b) { }
|
|
142
|
-
return ref;
|
|
151
|
+
return _this.redactUrl(ref);
|
|
143
152
|
};
|
|
144
153
|
this.getTrafficSources = function (url) {
|
|
145
154
|
var urlObj = new URL(url);
|
|
146
155
|
var contextTrafficSources = __assign(__assign(__assign({}, _this.extractUTMParameters(url)), _this.extractClickIdParameters(urlObj)), { ref: _this.extractReferralParameter(urlObj), referrer: _this.getExternalReferrer() });
|
|
147
|
-
|
|
156
|
+
// Sticky traffic sources may have been persisted by an older SDK version or
|
|
157
|
+
// a looser config, before the current excludeQueryParams was in effect.
|
|
158
|
+
// Honor the current denylist on the way out so excluded values can never
|
|
159
|
+
// resurface from session storage (or get re-persisted below).
|
|
160
|
+
var storedTrafficSources = _this.redactStoredTrafficSources(session().get(SESSION_TRAFFIC_SOURCE_KEY) || {});
|
|
148
161
|
var mergedClickIds = {};
|
|
149
162
|
for (var _i = 0, CLICK_ID_PARAMS_2 = CLICK_ID_PARAMS; _i < CLICK_ID_PARAMS_2.length; _i++) {
|
|
150
163
|
var p = CLICK_ID_PARAMS_2[_i];
|
|
@@ -185,8 +198,17 @@ var EventFactory = /** @class */ (function () {
|
|
|
185
198
|
this.getPageProperties = function (properties) {
|
|
186
199
|
// Create a copy to avoid mutating the original properties object
|
|
187
200
|
var pageProps = __assign({}, properties);
|
|
201
|
+
// Parse the current URL once and strip any excluded (sensitive) query
|
|
202
|
+
// params up front, so nothing sensitive is forwarded via url, query, or the
|
|
203
|
+
// per-param explosion below. The hash/fragment is intentionally untouched.
|
|
204
|
+
var urlObj = null;
|
|
205
|
+
try {
|
|
206
|
+
urlObj = new URL(globalThis.location.href);
|
|
207
|
+
_this.redactQueryParams(urlObj);
|
|
208
|
+
}
|
|
209
|
+
catch (_a) { }
|
|
188
210
|
if (isUndefined(pageProps.url)) {
|
|
189
|
-
pageProps.url =
|
|
211
|
+
pageProps.url = urlObj ? urlObj.href : globalThis.location.href;
|
|
190
212
|
}
|
|
191
213
|
if (isUndefined(pageProps.path)) {
|
|
192
214
|
pageProps.path = globalThis.location.pathname;
|
|
@@ -196,18 +218,22 @@ var EventFactory = /** @class */ (function () {
|
|
|
196
218
|
}
|
|
197
219
|
// Add query string without the '?' prefix
|
|
198
220
|
if (isUndefined(pageProps.query)) {
|
|
199
|
-
pageProps.query =
|
|
221
|
+
pageProps.query = urlObj
|
|
222
|
+
? urlObj.search.slice(1)
|
|
223
|
+
: globalThis.location.search.slice(1);
|
|
200
224
|
}
|
|
201
225
|
// Parse query parameters and add as individual properties (don't overwrite existing)
|
|
202
|
-
// Skip fields that are already captured in context or are semantic event properties
|
|
226
|
+
// Skip fields that are already captured in context or are semantic event properties.
|
|
227
|
+
// Excluded params were already removed from urlObj above.
|
|
203
228
|
try {
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
if (urlObj) {
|
|
230
|
+
urlObj.searchParams.forEach(function (value, key) {
|
|
231
|
+
// Only add if the property doesn't already exist and is not excluded
|
|
232
|
+
if (isUndefined(pageProps[key]) && !PAGE_PROPERTIES_EXCLUDED_FIELDS.has(key)) {
|
|
233
|
+
pageProps[key] = value;
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
}
|
|
211
237
|
}
|
|
212
238
|
catch (error) {
|
|
213
239
|
logger.error("Error parsing query parameters for page properties:", error);
|
|
@@ -215,8 +241,13 @@ var EventFactory = /** @class */ (function () {
|
|
|
215
241
|
return pageProps;
|
|
216
242
|
};
|
|
217
243
|
this.options = options;
|
|
244
|
+
var tracking = options === null || options === void 0 ? void 0 : options.tracking;
|
|
245
|
+
var configuredExcludes = typeof tracking === "object" ? (_a = tracking.excludeQueryParams) !== null && _a !== void 0 ? _a : [] : [];
|
|
246
|
+
this.excludedQueryParams = new Set(__spreadArray(__spreadArray([], DEFAULT_EXCLUDED_QUERY_PARAMS, true), configuredExcludes, true).map(function (key) {
|
|
247
|
+
return key.toLowerCase();
|
|
248
|
+
}));
|
|
218
249
|
// Compile regex pattern once for better performance
|
|
219
|
-
if ((
|
|
250
|
+
if ((_b = options === null || options === void 0 ? void 0 : options.referral) === null || _b === void 0 ? void 0 : _b.pathPattern) {
|
|
220
251
|
try {
|
|
221
252
|
this.compiledPathPattern = new RegExp(options.referral.pathPattern);
|
|
222
253
|
}
|
|
@@ -239,13 +270,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
239
270
|
return validateAddress(address, chainId) || null;
|
|
240
271
|
};
|
|
241
272
|
EventFactory.prototype.getTimezone = function () {
|
|
242
|
-
|
|
243
|
-
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
244
|
-
}
|
|
245
|
-
catch (error) {
|
|
246
|
-
logger.error("Error resolving timezone:", error);
|
|
247
|
-
return "";
|
|
248
|
-
}
|
|
273
|
+
return getTimezone();
|
|
249
274
|
};
|
|
250
275
|
EventFactory.prototype.getLocation = function () {
|
|
251
276
|
try {
|
|
@@ -278,6 +303,66 @@ var EventFactory = /** @class */ (function () {
|
|
|
278
303
|
EventFactory.prototype.getLibraryVersion = function () {
|
|
279
304
|
return version;
|
|
280
305
|
};
|
|
306
|
+
EventFactory.prototype.isExcludedQueryParam = function (key) {
|
|
307
|
+
return this.excludedQueryParams.has(key.toLowerCase());
|
|
308
|
+
};
|
|
309
|
+
/**
|
|
310
|
+
* Strip excluded (sensitive) query parameters from a URL in place. Only the
|
|
311
|
+
* query string is touched; the path and hash/fragment are left as-is.
|
|
312
|
+
*/
|
|
313
|
+
EventFactory.prototype.redactQueryParams = function (url) {
|
|
314
|
+
var _this = this;
|
|
315
|
+
// Collect first, then delete: mutating searchParams while iterating is
|
|
316
|
+
// unsafe, and deleting a key removes all of its values at once.
|
|
317
|
+
var keysToDelete = new Set();
|
|
318
|
+
url.searchParams.forEach(function (_value, key) {
|
|
319
|
+
if (_this.isExcludedQueryParam(key)) {
|
|
320
|
+
keysToDelete.add(key);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
keysToDelete.forEach(function (key) { return url.searchParams.delete(key); });
|
|
324
|
+
};
|
|
325
|
+
/**
|
|
326
|
+
* Return the given absolute URL with excluded query parameters removed. The
|
|
327
|
+
* input is returned unchanged when it is empty or cannot be parsed (e.g. an
|
|
328
|
+
* empty referrer).
|
|
329
|
+
*/
|
|
330
|
+
EventFactory.prototype.redactUrl = function (href) {
|
|
331
|
+
if (!href)
|
|
332
|
+
return href;
|
|
333
|
+
try {
|
|
334
|
+
var url = new URL(href);
|
|
335
|
+
this.redactQueryParams(url);
|
|
336
|
+
return url.href;
|
|
337
|
+
}
|
|
338
|
+
catch (_a) {
|
|
339
|
+
return href;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
/**
|
|
343
|
+
* Apply the current query-param denylist to a previously-persisted traffic
|
|
344
|
+
* source object. Traffic-source keys (utm_*, click ids, ref) are themselves
|
|
345
|
+
* query-parameter names, so an excluded key's stored value is dropped; the
|
|
346
|
+
* referrer is a URL and is re-redacted. Guards against a stored value
|
|
347
|
+
* outliving the config (or SDK version) under which it was first captured.
|
|
348
|
+
*/
|
|
349
|
+
EventFactory.prototype.redactStoredTrafficSources = function (stored) {
|
|
350
|
+
var result = {};
|
|
351
|
+
for (var _i = 0, _a = Object.keys(stored); _i < _a.length; _i++) {
|
|
352
|
+
var key = _a[_i];
|
|
353
|
+
var value = stored[key];
|
|
354
|
+
if (key === "referrer") {
|
|
355
|
+
result[key] = this.redactUrl(value || "");
|
|
356
|
+
}
|
|
357
|
+
else if (this.isExcludedQueryParam(key)) {
|
|
358
|
+
result[key] = "";
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
result[key] = value;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return result;
|
|
365
|
+
};
|
|
281
366
|
// Get screen dimensions and pixel density
|
|
282
367
|
// Returns safe defaults if any error occurs to ensure event creation continues
|
|
283
368
|
EventFactory.prototype.getScreen = function () {
|
|
@@ -306,7 +391,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
306
391
|
// Contextual fields that are automatically collected and populated by the Formo SDK
|
|
307
392
|
EventFactory.prototype.generateContext = function (context) {
|
|
308
393
|
return __awaiter(this, void 0, void 0, function () {
|
|
309
|
-
var browserName, language, timezone, location, library_version, defaultContext, mergedContext;
|
|
394
|
+
var browserName, language, timezone, location, library_version, redactedHref, defaultContext, mergedContext;
|
|
310
395
|
return __generator(this, function (_a) {
|
|
311
396
|
switch (_a.label) {
|
|
312
397
|
case 0: return [4 /*yield*/, detectBrowser()];
|
|
@@ -316,7 +401,8 @@ var EventFactory = /** @class */ (function () {
|
|
|
316
401
|
timezone = this.getTimezone();
|
|
317
402
|
location = this.getLocation();
|
|
318
403
|
library_version = this.getLibraryVersion();
|
|
319
|
-
|
|
404
|
+
redactedHref = this.redactUrl(globalThis.location.href);
|
|
405
|
+
defaultContext = __assign(__assign(__assign({ user_agent: globalThis.navigator.userAgent, locale: language, timezone: timezone, location: location }, this.getTrafficSources(redactedHref)), { page_title: document.title, page_url: redactedHref, library_name: "Formo Web SDK", library_version: library_version, browser: browserName }), this.getScreen());
|
|
320
406
|
mergedContext = mergeDeepRight(defaultContext, context || {});
|
|
321
407
|
return [2 /*return*/, mergedContext];
|
|
322
408
|
}
|
|
@@ -6,6 +6,18 @@ declare const VERSION = "0";
|
|
|
6
6
|
* in src/types/events.ts derived from this array.
|
|
7
7
|
*/
|
|
8
8
|
declare const CLICK_ID_PARAMS: readonly ["gclid", "gad_source", "fbclid", "msclkid", "twclid", "li_fat_id", "rdt_cid", "ttclid"];
|
|
9
|
+
/**
|
|
10
|
+
* Query parameters that are ALWAYS stripped from forwarded and stored URLs,
|
|
11
|
+
* regardless of consumer configuration, because they carry high-sensitivity
|
|
12
|
+
* secrets that must never reach Formo:
|
|
13
|
+
* - privy_oauth_code: Privy OAuth authorization code
|
|
14
|
+
* - privy_oauth_state: Privy OAuth CSRF state token
|
|
15
|
+
* - privy_oauth_provider: Privy OAuth provider identifier
|
|
16
|
+
*
|
|
17
|
+
* Consumers can extend the denylist via `tracking.excludeQueryParams` but
|
|
18
|
+
* cannot remove these built-ins. Matched case-insensitively.
|
|
19
|
+
*/
|
|
20
|
+
declare const DEFAULT_EXCLUDED_QUERY_PARAMS: readonly ["privy_oauth_code", "privy_oauth_state", "privy_oauth_provider"];
|
|
9
21
|
/**
|
|
10
22
|
* Fields that should be excluded from page event properties parsing
|
|
11
23
|
* These are either:
|
|
@@ -13,5 +25,5 @@ declare const CLICK_ID_PARAMS: readonly ["gclid", "gad_source", "fbclid", "msclk
|
|
|
13
25
|
* - Semantic event properties that should not be overridden by URL params
|
|
14
26
|
*/
|
|
15
27
|
declare const PAGE_PROPERTIES_EXCLUDED_FIELDS: Set<string>;
|
|
16
|
-
export { CHANNEL, VERSION, CLICK_ID_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS };
|
|
28
|
+
export { CHANNEL, VERSION, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, PAGE_PROPERTIES_EXCLUDED_FIELDS, };
|
|
17
29
|
//# sourceMappingURL=constants.d.ts.map
|