@formo/analytics 1.33.1 → 1.34.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/README.md +3 -0
- package/dist/cjs/src/FormoAnalytics.d.ts +65 -19
- package/dist/cjs/src/FormoAnalytics.js +187 -94
- package/dist/cjs/src/core.d.ts +2 -2
- package/dist/cjs/src/core.js +2 -1
- package/dist/cjs/src/event/EventFactory.d.ts +1 -1
- package/dist/cjs/src/event/EventFactory.js +32 -16
- package/dist/cjs/src/event/sanitize.d.ts +13 -0
- package/dist/cjs/src/event/sanitize.js +94 -0
- package/dist/cjs/src/privy/index.d.ts +8 -3
- package/dist/cjs/src/privy/index.js +8 -3
- package/dist/cjs/src/privy/types.d.ts +25 -2
- package/dist/cjs/src/privy/utils.d.ts +100 -0
- package/dist/cjs/src/privy/utils.js +375 -16
- package/dist/cjs/src/session/index.d.ts +73 -6
- package/dist/cjs/src/session/index.js +309 -12
- package/dist/cjs/src/solana/SolanaManager.d.ts +1 -1
- package/dist/cjs/src/solana/SolanaManager.js +1 -1
- package/dist/cjs/src/solana/storeTypes.d.ts +1 -1
- package/dist/cjs/src/solana/storeTypes.js +1 -1
- package/dist/cjs/src/solana/types.d.ts +2 -2
- package/dist/cjs/src/types/base.d.ts +17 -1
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/esm/src/FormoAnalytics.d.ts +65 -19
- package/dist/esm/src/FormoAnalytics.js +187 -94
- package/dist/esm/src/core.d.ts +2 -2
- package/dist/esm/src/core.js +1 -1
- package/dist/esm/src/event/EventFactory.d.ts +1 -1
- package/dist/esm/src/event/EventFactory.js +32 -16
- package/dist/esm/src/event/sanitize.d.ts +13 -0
- package/dist/esm/src/event/sanitize.js +88 -0
- package/dist/esm/src/privy/index.d.ts +8 -3
- package/dist/esm/src/privy/index.js +7 -3
- package/dist/esm/src/privy/types.d.ts +25 -2
- package/dist/esm/src/privy/utils.d.ts +100 -0
- package/dist/esm/src/privy/utils.js +374 -16
- package/dist/esm/src/session/index.d.ts +73 -6
- package/dist/esm/src/session/index.js +309 -12
- package/dist/esm/src/solana/SolanaManager.d.ts +1 -1
- package/dist/esm/src/solana/SolanaManager.js +1 -1
- package/dist/esm/src/solana/storeTypes.d.ts +1 -1
- package/dist/esm/src/solana/storeTypes.js +1 -1
- package/dist/esm/src/solana/types.d.ts +2 -2
- package/dist/esm/src/types/base.d.ts +17 -1
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/index.umd.min.js +1 -1
- package/package.json +7 -7
|
@@ -75,6 +75,7 @@ var chain_1 = require("./utils/chain");
|
|
|
75
75
|
var wagmi_1 = require("./wagmi");
|
|
76
76
|
var solana_1 = require("./solana");
|
|
77
77
|
var SolanaManager_1 = require("./solana/SolanaManager");
|
|
78
|
+
var privy_1 = require("./privy");
|
|
78
79
|
/**
|
|
79
80
|
* Constants for provider switching reasons
|
|
80
81
|
*/
|
|
@@ -90,7 +91,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
90
91
|
var _a, _b, _c;
|
|
91
92
|
this.writeKey = writeKey;
|
|
92
93
|
this.options = options;
|
|
93
|
-
// Per-chain namespace state
|
|
94
|
+
// Per-chain namespace state - isolates EVM and Solana connection state
|
|
94
95
|
this._chainState = {
|
|
95
96
|
evm: {},
|
|
96
97
|
solana: {},
|
|
@@ -206,7 +207,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
206
207
|
this.trackPageHits();
|
|
207
208
|
}
|
|
208
209
|
Object.defineProperty(FormoAnalytics.prototype, "_provider", {
|
|
209
|
-
// EVM state accessors
|
|
210
|
+
// EVM state accessors - EVM listener paths must use these instead of
|
|
210
211
|
// currentAddress/currentChainId to avoid cross-namespace reads.
|
|
211
212
|
get: function () {
|
|
212
213
|
return this._chainState.evm.provider;
|
|
@@ -396,7 +397,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
396
397
|
return [2 /*return*/];
|
|
397
398
|
}
|
|
398
399
|
// connect() persists wallet/chain state (active-wallet cookie,
|
|
399
|
-
// currentAddress/currentChainId) before trackEvent's consent check
|
|
400
|
+
// currentAddress/currentChainId) before trackEvent's consent check -
|
|
400
401
|
// gate the whole method so a suppressed visitor or excluded environment
|
|
401
402
|
// (opt-out / timezone / host / path) leaves no session state.
|
|
402
403
|
if (this.isTrackingSuppressed()) {
|
|
@@ -553,79 +554,85 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
553
554
|
});
|
|
554
555
|
});
|
|
555
556
|
};
|
|
556
|
-
|
|
557
|
-
* Emits an identify event with current wallet address and provider info.
|
|
558
|
-
*
|
|
559
|
-
* @param {string} params.address - Wallet address
|
|
560
|
-
* @param {string} params.userId - External user ID
|
|
561
|
-
* @param {string} params.rdns - Provider reverse domain name
|
|
562
|
-
* @param {string} params.providerName - Provider display name
|
|
563
|
-
* @param {IFormoEventProperties} properties - Additional properties to include with the identify event
|
|
564
|
-
* @param {IFormoEventContext} context
|
|
565
|
-
* @param {(...args: unknown[]) => void} callback
|
|
566
|
-
* @returns {Promise<void>}
|
|
567
|
-
*
|
|
568
|
-
* @example
|
|
569
|
-
* ```ts
|
|
570
|
-
* // Basic identify
|
|
571
|
-
* formo.identify({ address: '0x...', userId: 'user123' });
|
|
572
|
-
*
|
|
573
|
-
* // With Privy user
|
|
574
|
-
* import { parsePrivyProperties } from '@formo/analytics';
|
|
575
|
-
* const { user } = usePrivy();
|
|
576
|
-
* if (user) {
|
|
577
|
-
* const { properties, wallets } = parsePrivyProperties(user);
|
|
578
|
-
* for (const wallet of wallets) {
|
|
579
|
-
* formo.identify({ address: wallet.address, userId: user.id }, properties);
|
|
580
|
-
* }
|
|
581
|
-
* }
|
|
582
|
-
* ```
|
|
583
|
-
*/
|
|
584
|
-
FormoAnalytics.prototype.identify = function (params, properties, context, callback) {
|
|
557
|
+
FormoAnalytics.prototype.identify = function (paramsOrUser, propertiesOrOptions, context, callback) {
|
|
585
558
|
return __awaiter(this, void 0, void 0, function () {
|
|
586
|
-
var _i, _a, providerDetail, provider, address_2, validAddress_1, err_1, address, providerName, userId, rdns, validAddress, domain, isAlreadyIdentified, e_1;
|
|
559
|
+
var maybeUser, opts, params, properties, _i, _a, providerDetail, provider, address_2, validAddress_1, err_1, address, providerName, userId, rdns, setActive, validAddress, domain, isAlreadyIdentified, e_1;
|
|
587
560
|
var _b, _c;
|
|
588
561
|
return __generator(this, function (_d) {
|
|
589
562
|
switch (_d.label) {
|
|
590
563
|
case 0:
|
|
591
|
-
_d.trys.push([0,
|
|
564
|
+
_d.trys.push([0, 14, , 15]);
|
|
565
|
+
maybeUser = paramsOrUser;
|
|
566
|
+
if (!(maybeUser &&
|
|
567
|
+
typeof maybeUser.id === "string" &&
|
|
568
|
+
maybeUser.address === undefined)) return [3 /*break*/, 2];
|
|
569
|
+
opts = (propertiesOrOptions !== null && propertiesOrOptions !== void 0 ? propertiesOrOptions : {});
|
|
570
|
+
// identifyPrivyUser records every linked wallet for clustering WITHOUT
|
|
571
|
+
// touching active state (internal setActive:false), promotes only the
|
|
572
|
+
// resolved active wallet, and reconciles the chain id with that wallet's
|
|
573
|
+
// namespace before emitting. It reads this.currentAddress itself to
|
|
574
|
+
// preserve an already-connected wallet, so this dispatch is a thin
|
|
575
|
+
// pass-through and both entry points behave identically.
|
|
576
|
+
return [4 /*yield*/, (0, privy_1.identifyPrivyUser)(this, maybeUser, {
|
|
577
|
+
activeAddress: opts.activeAddress,
|
|
578
|
+
properties: opts.properties,
|
|
579
|
+
})];
|
|
580
|
+
case 1:
|
|
581
|
+
// identifyPrivyUser records every linked wallet for clustering WITHOUT
|
|
582
|
+
// touching active state (internal setActive:false), promotes only the
|
|
583
|
+
// resolved active wallet, and reconciles the chain id with that wallet's
|
|
584
|
+
// namespace before emitting. It reads this.currentAddress itself to
|
|
585
|
+
// preserve an already-connected wallet, so this dispatch is a thin
|
|
586
|
+
// pass-through and both entry points behave identically.
|
|
587
|
+
_d.sent();
|
|
588
|
+
return [2 /*return*/];
|
|
589
|
+
case 2:
|
|
590
|
+
params = paramsOrUser;
|
|
591
|
+
properties = propertiesOrOptions;
|
|
592
592
|
// identify() writes the user-id cookie and marks wallet
|
|
593
|
-
// identification before trackEvent's consent check
|
|
593
|
+
// identification before trackEvent's consent check - gate the whole
|
|
594
594
|
// method so a suppressed visitor or excluded environment (opt-out /
|
|
595
595
|
// timezone / host / path) gets no identity persistence.
|
|
596
|
-
|
|
597
|
-
|
|
596
|
+
//
|
|
597
|
+
// The chain check belongs here and NOT in isTrackingSuppressed(), which
|
|
598
|
+
// identifyPrivyUser calls before it reconciles the chain. Folding it in
|
|
599
|
+
// there would block a Privy sync that reconciliation was about to make
|
|
600
|
+
// valid (activating a Solana wallet while an excluded EVM chain id is
|
|
601
|
+
// still current). By the time the Privy path reaches this guard it has
|
|
602
|
+
// already reconciled, so each inner identify is judged on the right chain.
|
|
603
|
+
if (this.isTrackingSuppressed() || this.isCurrentChainExcluded()) {
|
|
604
|
+
logger_1.logger.info("identify() skipped: tracking is suppressed for this visitor, environment, or chain");
|
|
598
605
|
return [2 /*return*/];
|
|
599
606
|
}
|
|
600
|
-
if (!!params) return [3 /*break*/,
|
|
607
|
+
if (!!params) return [3 /*break*/, 12];
|
|
601
608
|
// If no params provided, auto-identify
|
|
602
609
|
logger_1.logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
|
|
603
610
|
_i = 0, _a = this._providers;
|
|
604
|
-
_d.label =
|
|
605
|
-
case
|
|
606
|
-
if (!(_i < _a.length)) return [3 /*break*/,
|
|
611
|
+
_d.label = 3;
|
|
612
|
+
case 3:
|
|
613
|
+
if (!(_i < _a.length)) return [3 /*break*/, 11];
|
|
607
614
|
providerDetail = _a[_i];
|
|
608
615
|
provider = providerDetail.provider;
|
|
609
616
|
if (!provider)
|
|
610
|
-
return [3 /*break*/,
|
|
611
|
-
_d.label =
|
|
612
|
-
case
|
|
613
|
-
_d.trys.push([
|
|
617
|
+
return [3 /*break*/, 10];
|
|
618
|
+
_d.label = 4;
|
|
619
|
+
case 4:
|
|
620
|
+
_d.trys.push([4, 9, , 10]);
|
|
614
621
|
return [4 /*yield*/, this.getAddress(provider)];
|
|
615
|
-
case
|
|
622
|
+
case 5:
|
|
616
623
|
address_2 = _d.sent();
|
|
617
|
-
if (!address_2) return [3 /*break*/,
|
|
624
|
+
if (!address_2) return [3 /*break*/, 8];
|
|
618
625
|
validAddress_1 = (0, address_1.validateAndChecksumAddress)(address_2);
|
|
619
626
|
logger_1.logger.info("Auto-identify: Checking deduplication", {
|
|
620
627
|
validAddress: validAddress_1,
|
|
621
628
|
rdns: providerDetail.info.rdns,
|
|
622
629
|
providerName: providerDetail.info.name,
|
|
623
630
|
isAlreadyIdentified: validAddress_1
|
|
624
|
-
? this.session.isWalletIdentified(validAddress_1, providerDetail.info.rdns)
|
|
631
|
+
? this.session.isWalletIdentified(validAddress_1, providerDetail.info.rdns, undefined, properties)
|
|
625
632
|
: false,
|
|
626
633
|
});
|
|
627
634
|
if (!(validAddress_1 &&
|
|
628
|
-
!this.session.isWalletIdentified(validAddress_1, providerDetail.info.rdns))) return [3 /*break*/,
|
|
635
|
+
!this.session.isWalletIdentified(validAddress_1, providerDetail.info.rdns, undefined, properties))) return [3 /*break*/, 7];
|
|
629
636
|
logger_1.logger.info("Auto-identifying", validAddress_1, providerDetail.info.name, providerDetail.info.rdns);
|
|
630
637
|
// NOTE: do not set this.currentAddress without explicit connect or identify
|
|
631
638
|
return [4 /*yield*/, this.identify({
|
|
@@ -633,26 +640,26 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
633
640
|
providerName: providerDetail.info.name,
|
|
634
641
|
rdns: providerDetail.info.rdns,
|
|
635
642
|
}, properties, context, callback)];
|
|
636
|
-
case
|
|
643
|
+
case 6:
|
|
637
644
|
// NOTE: do not set this.currentAddress without explicit connect or identify
|
|
638
645
|
_d.sent();
|
|
639
|
-
return [3 /*break*/,
|
|
640
|
-
case
|
|
646
|
+
return [3 /*break*/, 8];
|
|
647
|
+
case 7:
|
|
641
648
|
if (validAddress_1) {
|
|
642
649
|
logger_1.logger.info("Auto-identify: Skipping already identified wallet", validAddress_1, providerDetail.info.name, providerDetail.info.rdns);
|
|
643
650
|
}
|
|
644
|
-
_d.label =
|
|
645
|
-
case
|
|
646
|
-
case
|
|
651
|
+
_d.label = 8;
|
|
652
|
+
case 8: return [3 /*break*/, 10];
|
|
653
|
+
case 9:
|
|
647
654
|
err_1 = _d.sent();
|
|
648
655
|
logger_1.logger.error("Failed to identify provider ".concat(providerDetail.info.name, ":"), err_1);
|
|
649
|
-
return [3 /*break*/,
|
|
650
|
-
case 8:
|
|
651
|
-
_i++;
|
|
652
|
-
return [3 /*break*/, 1];
|
|
653
|
-
case 9: return [2 /*return*/];
|
|
656
|
+
return [3 /*break*/, 10];
|
|
654
657
|
case 10:
|
|
655
|
-
|
|
658
|
+
_i++;
|
|
659
|
+
return [3 /*break*/, 3];
|
|
660
|
+
case 11: return [2 /*return*/];
|
|
661
|
+
case 12:
|
|
662
|
+
address = params.address, providerName = params.providerName, userId = params.userId, rdns = params.rdns, setActive = params.setActive;
|
|
656
663
|
// Runtime validation: address is required
|
|
657
664
|
if (!address) {
|
|
658
665
|
(_b = logger_1.logger.warn) === null || _b === void 0 ? void 0 : _b.call(logger_1.logger, "identify() called without address - address is required");
|
|
@@ -661,20 +668,26 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
661
668
|
// Explicit identify
|
|
662
669
|
logger_1.logger.info("Identify", address, userId, providerName, rdns);
|
|
663
670
|
validAddress = (0, address_1.validateAddress)(address);
|
|
664
|
-
if (validAddress) {
|
|
665
|
-
this.currentAddress = validAddress;
|
|
666
|
-
this.persistActiveWallet();
|
|
667
|
-
}
|
|
668
|
-
else {
|
|
671
|
+
if (!validAddress) {
|
|
669
672
|
(_c = logger_1.logger.warn) === null || _c === void 0 ? void 0 : _c.call(logger_1.logger, "Invalid address provided to identify:", address);
|
|
670
673
|
return [2 /*return*/];
|
|
671
674
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
675
|
+
// Promote this wallet to the SDK's active identity - the (currentAddress,
|
|
676
|
+
// currentUserId) pair later events are attributed to - unless the caller
|
|
677
|
+
// opts out with setActive:false. A non-active identify still emits its
|
|
678
|
+
// event and marks dedup below (for clustering), it just doesn't repoint
|
|
679
|
+
// attribution. Gating address and userId together prevents leaving the
|
|
680
|
+
// active address paired with a different wallet's user id.
|
|
681
|
+
if (setActive !== false) {
|
|
682
|
+
this.currentAddress = validAddress;
|
|
683
|
+
this.persistActiveWallet();
|
|
684
|
+
if (userId) {
|
|
685
|
+
this.currentUserId = userId;
|
|
686
|
+
domain = (0, cookiePolicy_1.getIdentityCookieDomain)(this.crossSubdomainCookies);
|
|
687
|
+
(0, storage_1.cookie)().set(constants_1.SESSION_USER_ID_KEY, userId, __assign(__assign({ path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()), (domain ? { domain: domain } : {})));
|
|
688
|
+
}
|
|
676
689
|
}
|
|
677
|
-
isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "");
|
|
690
|
+
isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "", userId, properties);
|
|
678
691
|
logger_1.logger.debug("Identify: Checking deduplication", {
|
|
679
692
|
validAddress: validAddress,
|
|
680
693
|
rdns: rdns,
|
|
@@ -687,25 +700,77 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
687
700
|
return [2 /*return*/];
|
|
688
701
|
}
|
|
689
702
|
// Mark as identified before emitting the event
|
|
690
|
-
this.session.markWalletIdentified(validAddress, rdns || "");
|
|
703
|
+
this.session.markWalletIdentified(validAddress, rdns || "", userId, properties);
|
|
691
704
|
return [4 /*yield*/, this.trackEvent(constants_1.EventType.IDENTIFY, {
|
|
692
705
|
address: validAddress,
|
|
693
706
|
providerName: providerName,
|
|
694
707
|
userId: userId,
|
|
695
708
|
rdns: rdns,
|
|
696
709
|
}, properties, context, callback)];
|
|
697
|
-
case
|
|
710
|
+
case 13:
|
|
698
711
|
_d.sent();
|
|
699
|
-
return [3 /*break*/,
|
|
700
|
-
case
|
|
712
|
+
return [3 /*break*/, 15];
|
|
713
|
+
case 14:
|
|
701
714
|
e_1 = _d.sent();
|
|
702
715
|
logger_1.logger.log("identify error", e_1);
|
|
703
|
-
return [3 /*break*/,
|
|
704
|
-
case
|
|
716
|
+
return [3 /*break*/, 15];
|
|
717
|
+
case 15: return [2 /*return*/];
|
|
705
718
|
}
|
|
706
719
|
});
|
|
707
720
|
});
|
|
708
721
|
};
|
|
722
|
+
/**
|
|
723
|
+
* Reconcile currentChainId with a newly-activated Privy wallet's chain
|
|
724
|
+
* namespace. identify() sets currentAddress but never touches the chain id
|
|
725
|
+
* (that comes from connect()/chain()/wagmi), so activating e.g. a Solana
|
|
726
|
+
* wallet while an EVM chain id is current would leave the address paired with
|
|
727
|
+
* a mismatched chain in events, excludeChains, and the active-wallet cookie.
|
|
728
|
+
*
|
|
729
|
+
* We can't infer the wallet's specific chain id from Privy's chainType, so on
|
|
730
|
+
* a namespace mismatch we clear the chain id rather than assert a wrong one; a
|
|
731
|
+
* real wallet connect will set the correct chain. Same-namespace activations
|
|
732
|
+
* (and wallets whose namespace can't be determined) leave the chain id alone.
|
|
733
|
+
*
|
|
734
|
+
* Privy doesn't always supply `chainType`: a `smart_wallet` entry is
|
|
735
|
+
* `{ type, address, smartWalletType }`, and `cross_app` wallets are bare
|
|
736
|
+
* `{ address }`. A `0x`-prefixed 20-byte address is unambiguously EVM though,
|
|
737
|
+
* so fall back to the address shape - otherwise activating an EVM smart
|
|
738
|
+
* wallet while a Solana chain id is current would leave the address paired
|
|
739
|
+
* with the wrong chain, and an `excludeChains` gate could drop the identify
|
|
740
|
+
* after it was already dedup-marked.
|
|
741
|
+
*
|
|
742
|
+
* @internal Not part of the public IFormoAnalytics contract - invoked by
|
|
743
|
+
* `identifyPrivyUser` (via a structural cast) before it emits, so both the
|
|
744
|
+
* `identify(user,{privy:true})` and direct `identifyPrivyUser()` paths
|
|
745
|
+
* reconcile the chain.
|
|
746
|
+
*/
|
|
747
|
+
FormoAnalytics.prototype.syncPrivyActiveChain = function (chainType, address) {
|
|
748
|
+
if (this.currentChainId === undefined || this.currentChainId === null)
|
|
749
|
+
return;
|
|
750
|
+
var walletIsSolana;
|
|
751
|
+
var namespace = chainType === null || chainType === void 0 ? void 0 : chainType.toLowerCase();
|
|
752
|
+
// Only recognized namespaces decide. An unknown or future chainType
|
|
753
|
+
// ("bitcoin", "cosmos", …) must stay undecided rather than defaulting to
|
|
754
|
+
// EVM, which would wrongly clear a valid Solana chain id.
|
|
755
|
+
if (namespace === "solana") {
|
|
756
|
+
walletIsSolana = true;
|
|
757
|
+
}
|
|
758
|
+
else if (namespace === "ethereum") {
|
|
759
|
+
walletIsSolana = false;
|
|
760
|
+
}
|
|
761
|
+
else if (!namespace && address && /^0x[0-9a-f]{40}$/i.test(address)) {
|
|
762
|
+
// Only the EVM shape is inferable: a non-0x address could be Solana,
|
|
763
|
+
// Bitcoin, Cosmos, … so absence of 0x proves nothing.
|
|
764
|
+
walletIsSolana = false;
|
|
765
|
+
}
|
|
766
|
+
if (walletIsSolana === undefined)
|
|
767
|
+
return;
|
|
768
|
+
var currentIsSolana = (0, solana_1.isSolanaChainId)(this.currentChainId);
|
|
769
|
+
if (walletIsSolana !== currentIsSolana) {
|
|
770
|
+
this.currentChainId = undefined;
|
|
771
|
+
this.persistActiveWallet();
|
|
772
|
+
}
|
|
773
|
+
};
|
|
709
774
|
/**
|
|
710
775
|
* Emits a detect wallet event with current wallet provider info.
|
|
711
776
|
* @param {string} params.providerName
|
|
@@ -722,7 +787,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
722
787
|
switch (_c.label) {
|
|
723
788
|
case 0:
|
|
724
789
|
// detect() marks wallet detection (a cookie write) before
|
|
725
|
-
// trackEvent's consent check
|
|
790
|
+
// trackEvent's consent check - gate it for a suppressed visitor or
|
|
726
791
|
// excluded environment (opt-out / timezone / host / path).
|
|
727
792
|
if (this.isTrackingSuppressed()) {
|
|
728
793
|
logger_1.logger.info("detect() skipped: tracking is suppressed for this visitor or environment");
|
|
@@ -1091,7 +1156,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1091
1156
|
wasDisconnected = !this._evmAddress;
|
|
1092
1157
|
// Update state regardless of whether connect *event* tracking is enabled,
|
|
1093
1158
|
// so disconnect events keep valid address/chainId values. (excludeChains is
|
|
1094
|
-
// NOT suppression
|
|
1159
|
+
// NOT suppression - it still updates state so currentChainId can gate
|
|
1095
1160
|
// events.)
|
|
1096
1161
|
if (this.isTrackingSuppressed()) {
|
|
1097
1162
|
this.clearStaleEvmWalletOnSwitchWhileSuppressed(address);
|
|
@@ -1272,7 +1337,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1272
1337
|
}
|
|
1273
1338
|
isActiveProvider = this._provider === provider;
|
|
1274
1339
|
// Update state from active provider so disconnect events keep valid
|
|
1275
|
-
// address/chainId values
|
|
1340
|
+
// address/chainId values - except while suppressed, where we must not
|
|
1276
1341
|
// LEARN identity (only drop a stale EVM wallet on a switch).
|
|
1277
1342
|
if (isActiveProvider) {
|
|
1278
1343
|
if (this.isTrackingSuppressed()) {
|
|
@@ -1543,7 +1608,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1543
1608
|
var _this = this;
|
|
1544
1609
|
// Install a single, instance-agnostic wrapper around history.pushState /
|
|
1545
1610
|
// replaceState so concurrent SDK instances (React Strict Mode, HMR) don't
|
|
1546
|
-
// each stack their own wrapper
|
|
1611
|
+
// each stack their own wrapper - which would dispatch N synthetic events
|
|
1547
1612
|
// per navigation and produce O(N^2) onLocationChange calls. The wrapper
|
|
1548
1613
|
// dispatches once; per-instance bookkeeping is done by per-instance
|
|
1549
1614
|
// listeners that each register/unregister themselves.
|
|
@@ -1656,22 +1721,50 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1656
1721
|
* Visitor-level tracking suppression.
|
|
1657
1722
|
*
|
|
1658
1723
|
* Returns true when the SDK must not persist any identity/session/chain
|
|
1659
|
-
* state or send any events for this visitor
|
|
1724
|
+
* state or send any events for this visitor - i.e. an explicit opt-out or a
|
|
1660
1725
|
* jurisdiction/timezone exclusion. Public entry points that write state
|
|
1661
1726
|
* before reaching the `shouldTrack()` event gate (identify/connect/detect)
|
|
1662
1727
|
* check this first so suppressed visitors leave no cookies or session state.
|
|
1663
1728
|
* @returns {boolean} True if all tracking and persistence must be suppressed
|
|
1729
|
+
* @internal Also read by `identifyPrivyUser` (via a structural cast) so the
|
|
1730
|
+
* Privy sync skips chain reconciliation and emission for suppressed visitors.
|
|
1664
1731
|
*/
|
|
1665
1732
|
FormoAnalytics.prototype.isTrackingSuppressed = function () {
|
|
1666
1733
|
return this.hasOptedOutTracking() || this.isCurrentEnvironmentExcluded();
|
|
1667
1734
|
};
|
|
1668
1735
|
/**
|
|
1669
|
-
* Whether the current
|
|
1736
|
+
* Whether the current chain id is in `tracking.excludeChains`.
|
|
1737
|
+
*
|
|
1738
|
+
* Split out from `shouldTrack()` so `identify()` can check it *before*
|
|
1739
|
+
* mutating identity state. `trackEvent()` drops an excluded event silently
|
|
1740
|
+
* and returns void, but `identify()` marks the wallet as identified first, so
|
|
1741
|
+
* without this guard an identify on an excluded chain is dedup-marked and
|
|
1742
|
+
* then discarded, and the wallet never re-emits for the rest of the session
|
|
1743
|
+
* even after switching to an allowed chain. On the Privy path that loses the
|
|
1744
|
+
* user's whole cluster at once rather than a single wallet.
|
|
1745
|
+
*
|
|
1746
|
+
* Mirrors the chain rule in `shouldTrack()`: only applies when `tracking` is
|
|
1747
|
+
* an options object with `excludeChains` set, and only once a chain id is
|
|
1748
|
+
* known.
|
|
1749
|
+
*/
|
|
1750
|
+
FormoAnalytics.prototype.isCurrentChainExcluded = function () {
|
|
1751
|
+
if (this.options.tracking === null ||
|
|
1752
|
+
typeof this.options.tracking !== "object" ||
|
|
1753
|
+
Array.isArray(this.options.tracking)) {
|
|
1754
|
+
return false;
|
|
1755
|
+
}
|
|
1756
|
+
var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
1757
|
+
return (excludeChains.length > 0 &&
|
|
1758
|
+
!!this.currentChainId &&
|
|
1759
|
+
excludeChains.includes(this.currentChainId));
|
|
1760
|
+
};
|
|
1761
|
+
/**
|
|
1762
|
+
* Whether the current environment is excluded from tracking - the visitor's
|
|
1670
1763
|
* timezone, the current hostname, or the current pathname matches a
|
|
1671
1764
|
* configured exclusion.
|
|
1672
1765
|
*
|
|
1673
1766
|
* Timezone is visitor/session-level (stable for the session); host/path are
|
|
1674
|
-
* current-page-level and transient
|
|
1767
|
+
* current-page-level and transient - if a SPA navigates to an allowed path,
|
|
1675
1768
|
* tracking resumes for future actions. Used as the "do not write identity or
|
|
1676
1769
|
* send events" gate at every entry point that would persist state before the
|
|
1677
1770
|
* `shouldTrack()` event gate.
|
|
@@ -1684,7 +1777,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1684
1777
|
};
|
|
1685
1778
|
/**
|
|
1686
1779
|
* Whether the current hostname matches a configured `tracking.excludeHosts`
|
|
1687
|
-
* entry (exact match). Current-page-level
|
|
1780
|
+
* entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
|
|
1688
1781
|
* @returns {boolean} True if the current hostname is excluded
|
|
1689
1782
|
*/
|
|
1690
1783
|
FormoAnalytics.prototype.isHostExcluded = function () {
|
|
@@ -1702,7 +1795,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1702
1795
|
};
|
|
1703
1796
|
/**
|
|
1704
1797
|
* Whether the current pathname matches a configured `tracking.excludePaths`
|
|
1705
|
-
* entry (exact match). Current-page-level
|
|
1798
|
+
* entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
|
|
1706
1799
|
* @returns {boolean} True if the current pathname is excluded
|
|
1707
1800
|
*/
|
|
1708
1801
|
FormoAnalytics.prototype.isPathExcluded = function () {
|
|
@@ -1719,8 +1812,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1719
1812
|
return excludePaths.includes(window.location.pathname);
|
|
1720
1813
|
};
|
|
1721
1814
|
/**
|
|
1722
|
-
* Whether the current call is in a visitor-level suppression state
|
|
1723
|
-
* or excluded timezone
|
|
1815
|
+
* Whether the current call is in a visitor-level suppression state - opt-out
|
|
1816
|
+
* or excluded timezone - for which any persisted identity cookie should be
|
|
1724
1817
|
* actively purged (not merely skipped). Host/path exclusions are
|
|
1725
1818
|
* deliberately excluded here: they are transient current-page states, so a
|
|
1726
1819
|
* cookie legitimately written on an allowed page must survive a visit to an
|
|
@@ -1733,7 +1826,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1733
1826
|
/**
|
|
1734
1827
|
* Whether the visitor's browser-resolved timezone matches a configured
|
|
1735
1828
|
* `tracking.excludeTimezones` entry (case-insensitive). Client-side and
|
|
1736
|
-
* best-effort
|
|
1829
|
+
* best-effort - see TrackingOptions.excludeTimezones.
|
|
1737
1830
|
* @returns {boolean} True if the current timezone is excluded
|
|
1738
1831
|
*/
|
|
1739
1832
|
FormoAnalytics.prototype.isTimezoneExcluded = function () {
|
|
@@ -1772,7 +1865,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1772
1865
|
typeof this.options.tracking === "object" &&
|
|
1773
1866
|
!Array.isArray(this.options.tracking)) {
|
|
1774
1867
|
var _a = this.options.tracking.excludeChains, excludeChains = _a === void 0 ? [] : _a;
|
|
1775
|
-
// Environment exclusions (timezone / host / path)
|
|
1868
|
+
// Environment exclusions (timezone / host / path) - no identify / connect
|
|
1776
1869
|
// / track events while excluded. Host/path are exact-match.
|
|
1777
1870
|
if (this.isCurrentEnvironmentExcluded()) {
|
|
1778
1871
|
return false;
|
|
@@ -2385,7 +2478,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2385
2478
|
* WITHOUT emitting an event.
|
|
2386
2479
|
*
|
|
2387
2480
|
* Integrations (e.g. the wagmi handler) must call this on every
|
|
2388
|
-
* connect / chain-change / disconnect
|
|
2481
|
+
* connect / chain-change / disconnect - even when the corresponding
|
|
2389
2482
|
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
2390
2483
|
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
2391
2484
|
* check (which keys off `currentChainId`, not the event payload) can
|
|
@@ -2399,7 +2492,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2399
2492
|
var chainId = params.chainId, address = params.address;
|
|
2400
2493
|
if (this.isTrackingSuppressed()) {
|
|
2401
2494
|
// While suppressed (opt-out / timezone / excluded host or path) we must
|
|
2402
|
-
// never LEARN a new wallet
|
|
2495
|
+
// never LEARN a new wallet - but we must still CLEAR stale identity.
|
|
2403
2496
|
// Otherwise a disconnect or wallet switch observed on a suppressed route
|
|
2404
2497
|
// would leave the previously-learned address in memory and in the
|
|
2405
2498
|
// active-wallet cookie, attaching it to later allowed-page events.
|
|
@@ -2481,14 +2574,14 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2481
2574
|
/**
|
|
2482
2575
|
* Persist (or clear) the current wallet snapshot in a cookie so that the
|
|
2483
2576
|
* SDK can repopulate `currentAddress`/`currentChainId` at init on the next
|
|
2484
|
-
* page load
|
|
2577
|
+
* page load - closing the gap between page-show and wagmi/EIP-1193
|
|
2485
2578
|
* reconnection during which track()/page() events would otherwise ship
|
|
2486
2579
|
* with an empty address.
|
|
2487
2580
|
*/
|
|
2488
2581
|
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2489
2582
|
try {
|
|
2490
2583
|
// Visitor-level suppression (opt-out or excluded timezone): purge any
|
|
2491
|
-
// prior snapshot
|
|
2584
|
+
// prior snapshot - these are stable for the session, so deletion is safe.
|
|
2492
2585
|
if (this.isPersistedIdentityPurgeRequired()) {
|
|
2493
2586
|
(0, storage_1.cookie)().remove(constants_1.ACTIVE_WALLET_KEY);
|
|
2494
2587
|
return;
|
package/dist/cjs/src/core.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export * from "./FormoAnalytics";
|
|
2
2
|
export * from "./types";
|
|
3
3
|
export { formofy } from "./initialization";
|
|
4
|
-
export { parsePrivyProperties } from "./privy";
|
|
5
|
-
export type { PrivyUser, PrivyLinkedAccount, PrivyAccountType, PrivyProfileProperties, PrivyWalletInfo, } from "./privy";
|
|
4
|
+
export { parsePrivyProperties, identifyPrivyUser } from "./privy";
|
|
5
|
+
export type { IdentifyPrivyUserOptions, PrivyUser, PrivyLinkedAccount, PrivyAccountType, PrivyProfileProperties, PrivyWalletInfo, } from "./privy";
|
|
6
6
|
export { SolanaManager } from "./solana";
|
|
7
7
|
export { SOLANA_CHAIN_IDS, DEFAULT_SOLANA_CHAIN_ID, isSolanaChainId, } from "./solana";
|
|
8
8
|
export type { SolanaOptions, SolanaCluster } from "./solana";
|
package/dist/cjs/src/core.js
CHANGED
|
@@ -20,13 +20,14 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
20
20
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
-
exports.isSolanaChainId = exports.DEFAULT_SOLANA_CHAIN_ID = exports.SOLANA_CHAIN_IDS = exports.SolanaManager = exports.parsePrivyProperties = exports.formofy = void 0;
|
|
23
|
+
exports.isSolanaChainId = exports.DEFAULT_SOLANA_CHAIN_ID = exports.SOLANA_CHAIN_IDS = exports.SolanaManager = exports.identifyPrivyUser = exports.parsePrivyProperties = exports.formofy = void 0;
|
|
24
24
|
__exportStar(require("./FormoAnalytics"), exports);
|
|
25
25
|
__exportStar(require("./types"), exports);
|
|
26
26
|
var initialization_1 = require("./initialization");
|
|
27
27
|
Object.defineProperty(exports, "formofy", { enumerable: true, get: function () { return initialization_1.formofy; } });
|
|
28
28
|
var privy_1 = require("./privy");
|
|
29
29
|
Object.defineProperty(exports, "parsePrivyProperties", { enumerable: true, get: function () { return privy_1.parsePrivyProperties; } });
|
|
30
|
+
Object.defineProperty(exports, "identifyPrivyUser", { enumerable: true, get: function () { return privy_1.identifyPrivyUser; } });
|
|
30
31
|
var solana_1 = require("./solana");
|
|
31
32
|
Object.defineProperty(exports, "SolanaManager", { enumerable: true, get: function () { return solana_1.SolanaManager; } });
|
|
32
33
|
var solana_2 = require("./solana");
|
|
@@ -41,7 +41,7 @@ declare class EventFactory implements IEventFactory {
|
|
|
41
41
|
/**
|
|
42
42
|
* Returns the document referrer with same-host referrers filtered out.
|
|
43
43
|
* Internal navigation populates `document.referrer` with the previous page
|
|
44
|
-
* on the same site, which is not an attribution signal
|
|
44
|
+
* on the same site, which is not an attribution signal - treating it as
|
|
45
45
|
* "external" would otherwise let an internal URL become the session's
|
|
46
46
|
* first-touch referrer after a direct landing.
|
|
47
47
|
*/
|