@formo/analytics 1.29.1 → 1.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/FormoAnalytics.d.ts +20 -3
- package/dist/cjs/src/FormoAnalytics.js +102 -9
- package/dist/cjs/src/FormoAnalyticsProvider.d.ts +8 -2
- package/dist/cjs/src/core.d.ts +10 -0
- package/dist/cjs/src/core.js +36 -0
- package/dist/cjs/src/event/EventFactory.d.ts +1 -1
- package/dist/cjs/src/event/EventFactory.js +3 -3
- package/dist/cjs/src/event/EventManager.d.ts +2 -0
- package/dist/cjs/src/event/EventManager.js +4 -0
- package/dist/cjs/src/event/type.d.ts +1 -0
- package/dist/cjs/src/event/utils.js +1 -1
- package/dist/cjs/src/index.d.ts +1 -8
- package/dist/cjs/src/index.js +1 -12
- package/dist/cjs/src/queue/EventQueue.d.ts +8 -0
- package/dist/cjs/src/queue/EventQueue.js +52 -6
- package/dist/cjs/src/queue/type.d.ts +1 -0
- package/dist/cjs/src/session/index.js +16 -8
- package/dist/cjs/src/storage/cookiePolicy.d.ts +16 -0
- package/dist/cjs/src/storage/cookiePolicy.js +19 -0
- package/dist/cjs/src/types/base.d.ts +0 -8
- package/dist/cjs/src/types/events.d.ts +0 -1
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/src/wagmi/WagmiEventHandler.js +38 -15
- package/dist/esm/src/FormoAnalytics.d.ts +20 -3
- package/dist/esm/src/FormoAnalytics.js +105 -12
- package/dist/esm/src/FormoAnalyticsProvider.d.ts +8 -2
- package/dist/esm/src/core.d.ts +10 -0
- package/dist/esm/src/core.js +13 -0
- package/dist/esm/src/event/EventFactory.d.ts +1 -1
- package/dist/esm/src/event/EventFactory.js +3 -3
- package/dist/esm/src/event/EventManager.d.ts +2 -0
- package/dist/esm/src/event/EventManager.js +4 -0
- package/dist/esm/src/event/type.d.ts +1 -0
- package/dist/esm/src/event/utils.js +2 -2
- package/dist/esm/src/index.d.ts +1 -8
- package/dist/esm/src/index.js +1 -6
- package/dist/esm/src/queue/EventQueue.d.ts +8 -0
- package/dist/esm/src/queue/EventQueue.js +52 -6
- package/dist/esm/src/queue/type.d.ts +1 -0
- package/dist/esm/src/session/index.js +16 -8
- package/dist/esm/src/storage/cookiePolicy.d.ts +16 -0
- package/dist/esm/src/storage/cookiePolicy.js +18 -0
- package/dist/esm/src/types/base.d.ts +0 -8
- package/dist/esm/src/types/events.d.ts +0 -1
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/esm/src/wagmi/WagmiEventHandler.js +38 -15
- package/dist/index.umd.min.js +1 -1
- package/package.json +18 -18
|
@@ -5,9 +5,21 @@
|
|
|
5
5
|
* Handles tracking of detected wallets and identified wallet-address pairs
|
|
6
6
|
* using cookies to maintain state across page loads within a session.
|
|
7
7
|
*/
|
|
8
|
+
var __assign = (this && this.__assign) || function () {
|
|
9
|
+
__assign = Object.assign || function(t) {
|
|
10
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
11
|
+
s = arguments[i];
|
|
12
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
13
|
+
t[p] = s[p];
|
|
14
|
+
}
|
|
15
|
+
return t;
|
|
16
|
+
};
|
|
17
|
+
return __assign.apply(this, arguments);
|
|
18
|
+
};
|
|
8
19
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
20
|
exports.FormoAnalyticsSession = exports.SESSION_WALLET_IDENTIFIED_KEY = exports.SESSION_WALLET_DETECTED_KEY = void 0;
|
|
10
21
|
var storage_1 = require("../storage");
|
|
22
|
+
var cookiePolicy_1 = require("../storage/cookiePolicy");
|
|
11
23
|
var logger_1 = require("../logger");
|
|
12
24
|
/**
|
|
13
25
|
* Cookie keys for session tracking
|
|
@@ -66,11 +78,9 @@ var FormoAnalyticsSession = /** @class */ (function () {
|
|
|
66
78
|
if (rdnses.length > MAX_SESSION_ENTRIES) {
|
|
67
79
|
rdnses.splice(0, rdnses.length - MAX_SESSION_ENTRIES);
|
|
68
80
|
}
|
|
69
|
-
(0, storage_1.cookie)().set(exports.SESSION_WALLET_DETECTED_KEY, rdnses.join(","), {
|
|
81
|
+
(0, storage_1.cookie)().set(exports.SESSION_WALLET_DETECTED_KEY, rdnses.join(","), __assign({
|
|
70
82
|
// Expires by the end of the day
|
|
71
|
-
expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
|
|
72
|
-
path: "/",
|
|
73
|
-
});
|
|
83
|
+
expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()));
|
|
74
84
|
}
|
|
75
85
|
};
|
|
76
86
|
/**
|
|
@@ -110,11 +120,9 @@ var FormoAnalyticsSession = /** @class */ (function () {
|
|
|
110
120
|
identifiedWallets.splice(0, identifiedWallets.length - MAX_SESSION_ENTRIES);
|
|
111
121
|
}
|
|
112
122
|
var newValue = identifiedWallets.join(",");
|
|
113
|
-
(0, storage_1.cookie)().set(exports.SESSION_WALLET_IDENTIFIED_KEY, newValue, {
|
|
123
|
+
(0, storage_1.cookie)().set(exports.SESSION_WALLET_IDENTIFIED_KEY, newValue, __assign({
|
|
114
124
|
// Expires by the end of the day
|
|
115
|
-
expires: new Date(Date.now() + 86400 * 1000).toUTCString(),
|
|
116
|
-
path: "/",
|
|
117
|
-
});
|
|
125
|
+
expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, (0, cookiePolicy_1.getIdentityCookieSecurity)()));
|
|
118
126
|
logger_1.logger.debug("Session: Marked wallet as identified", {
|
|
119
127
|
identifiedKey: identifiedKey,
|
|
120
128
|
hasRdns: !!rdns,
|
|
@@ -7,4 +7,20 @@
|
|
|
7
7
|
* @param crossSubdomain Whether cookies should be shared across subdomains.
|
|
8
8
|
*/
|
|
9
9
|
export declare function getIdentityCookieDomain(crossSubdomain?: boolean): string;
|
|
10
|
+
/**
|
|
11
|
+
* Security attributes for identity/session cookies (user-id,
|
|
12
|
+
* active-wallet, wallet-detected/identified).
|
|
13
|
+
*
|
|
14
|
+
* - `sameSite: "lax"` — first-party analytics identity. Lax keeps the
|
|
15
|
+
* cookie on top-level navigations (so attribution survives a click-in
|
|
16
|
+
* from another site) while blocking it on cross-site subrequests.
|
|
17
|
+
* Strict would silently drop identity on inbound navigation.
|
|
18
|
+
* - `secure` — only on HTTPS. Setting Secure over plain HTTP (local dev)
|
|
19
|
+
* makes the browser reject the cookie outright, so it must be
|
|
20
|
+
* conditional.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getIdentityCookieSecurity(): {
|
|
23
|
+
sameSite: "lax";
|
|
24
|
+
secure: boolean;
|
|
25
|
+
};
|
|
10
26
|
//# sourceMappingURL=cookiePolicy.d.ts.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.getIdentityCookieDomain = getIdentityCookieDomain;
|
|
4
|
+
exports.getIdentityCookieSecurity = getIdentityCookieSecurity;
|
|
4
5
|
/**
|
|
5
6
|
* Cookie domain policy — centralizes the decision of whether identity
|
|
6
7
|
* cookies should be host-scoped or apex-scoped.
|
|
@@ -24,4 +25,22 @@ function getIdentityCookieDomain(crossSubdomain) {
|
|
|
24
25
|
var domain = (0, domain_1.getApexDomain)();
|
|
25
26
|
return domain ? ".".concat(domain) : "";
|
|
26
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Security attributes for identity/session cookies (user-id,
|
|
30
|
+
* active-wallet, wallet-detected/identified).
|
|
31
|
+
*
|
|
32
|
+
* - `sameSite: "lax"` — first-party analytics identity. Lax keeps the
|
|
33
|
+
* cookie on top-level navigations (so attribution survives a click-in
|
|
34
|
+
* from another site) while blocking it on cross-site subrequests.
|
|
35
|
+
* Strict would silently drop identity on inbound navigation.
|
|
36
|
+
* - `secure` — only on HTTPS. Setting Secure over plain HTTP (local dev)
|
|
37
|
+
* makes the browser reject the cookie outright, so it must be
|
|
38
|
+
* conditional.
|
|
39
|
+
*/
|
|
40
|
+
function getIdentityCookieSecurity() {
|
|
41
|
+
var _a;
|
|
42
|
+
var isHttps = typeof window !== "undefined" &&
|
|
43
|
+
((_a = window.location) === null || _a === void 0 ? void 0 : _a.protocol) === "https:";
|
|
44
|
+
return { sameSite: "lax", secure: isHttps };
|
|
45
|
+
}
|
|
27
46
|
//# sourceMappingURL=cookiePolicy.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { LogLevel } from "../logger";
|
|
2
2
|
import { IFormoEventContext, IFormoEventProperties, SignatureStatus, TransactionStatus } from "./events";
|
|
3
3
|
import { EIP1193Provider } from "./provider";
|
|
4
|
-
import { ReactNode } from "react";
|
|
5
4
|
import { SolanaOptions } from "../solana/types";
|
|
6
5
|
export type Nullable<T> = T | null;
|
|
7
6
|
export type ChainID = number;
|
|
@@ -40,7 +39,6 @@ export interface IFormoAnalytics {
|
|
|
40
39
|
chainId?: ChainID;
|
|
41
40
|
address: Address;
|
|
42
41
|
message: string;
|
|
43
|
-
signatureHash?: string;
|
|
44
42
|
}, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
45
43
|
transaction(params: {
|
|
46
44
|
status: TransactionStatus;
|
|
@@ -222,10 +220,4 @@ export interface Options {
|
|
|
222
220
|
errorHandler?: (err: Error) => void;
|
|
223
221
|
ready?: (formo: IFormoAnalytics) => void;
|
|
224
222
|
}
|
|
225
|
-
export interface FormoAnalyticsProviderProps {
|
|
226
|
-
writeKey: string;
|
|
227
|
-
options?: Options;
|
|
228
|
-
disabled?: boolean;
|
|
229
|
-
children: ReactNode;
|
|
230
|
-
}
|
|
231
223
|
//# sourceMappingURL=base.d.ts.map
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const version = "1.
|
|
1
|
+
export declare const version = "1.30.1";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/cjs/src/version.js
CHANGED
|
@@ -3,5 +3,5 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.version = void 0;
|
|
4
4
|
// This file is auto-generated by scripts/update-version.js during npm version
|
|
5
5
|
// Do not edit manually - it will be overwritten
|
|
6
|
-
exports.version = '1.
|
|
6
|
+
exports.version = '1.30.1';
|
|
7
7
|
//# sourceMappingURL=version.js.map
|
|
@@ -173,6 +173,11 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
173
173
|
chainId: chainId,
|
|
174
174
|
});
|
|
175
175
|
if (!(status === "disconnected" && prevStatus === "connected")) return [3 /*break*/, 4];
|
|
176
|
+
// Clear central chain state regardless of autocapture so a later
|
|
177
|
+
// event can't carry a stale excluded/!excluded chainId.
|
|
178
|
+
this.formo.syncWalletState({
|
|
179
|
+
chainId: this.trackingState.lastChainId,
|
|
180
|
+
});
|
|
176
181
|
if (!this.formo.isAutocaptureEnabled("disconnect")) return [3 /*break*/, 3];
|
|
177
182
|
return [4 /*yield*/, this.formo.disconnect({
|
|
178
183
|
chainId: this.trackingState.lastChainId,
|
|
@@ -190,6 +195,9 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
190
195
|
if (!(address && chainId !== undefined)) return [3 /*break*/, 6];
|
|
191
196
|
this.trackingState.lastAddress = address;
|
|
192
197
|
this.trackingState.lastChainId = chainId;
|
|
198
|
+
// Sync central state unconditionally so tracking.excludeChains
|
|
199
|
+
// is enforced even when connect autocapture is disabled.
|
|
200
|
+
this.formo.syncWalletState({ chainId: chainId, address: address });
|
|
193
201
|
if (!this.formo.isAutocaptureEnabled("connect")) return [3 /*break*/, 6];
|
|
194
202
|
connectorName = this.getConnectorName(state);
|
|
195
203
|
return [4 /*yield*/, this.formo.connect({ chainId: chainId, address: address }, __assign({}, (connectorName && { providerName: connectorName })))];
|
|
@@ -238,6 +246,9 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
238
246
|
address: address,
|
|
239
247
|
});
|
|
240
248
|
this.trackingState.lastChainId = chainId;
|
|
249
|
+
// Sync central state unconditionally so a chain switch to an
|
|
250
|
+
// excluded chain is honored even when chain autocapture is disabled.
|
|
251
|
+
this.formo.syncWalletState({ chainId: chainId, address: address });
|
|
241
252
|
if (!this.formo.isAutocaptureEnabled("chain")) return [3 /*break*/, 4];
|
|
242
253
|
_a.label = 1;
|
|
243
254
|
case 1:
|
|
@@ -359,14 +370,21 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
359
370
|
logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no hash found");
|
|
360
371
|
return;
|
|
361
372
|
}
|
|
362
|
-
// Retrieve stored transaction details from BROADCASTED event
|
|
363
|
-
// Normalize hash to lowercase for consistent lookup
|
|
373
|
+
// Retrieve stored transaction details from the BROADCASTED event.
|
|
374
|
+
// Normalize hash to lowercase for consistent lookup.
|
|
364
375
|
var normalizedHash = transactionHash.toLowerCase();
|
|
365
376
|
var pendingTx = this.pendingTransactions.get(normalizedHash);
|
|
366
|
-
//
|
|
367
|
-
//
|
|
368
|
-
//
|
|
369
|
-
|
|
377
|
+
// Only emit receipt-derived events for a hash we actually observed
|
|
378
|
+
// being broadcast through this handler. The QueryClient is supplied
|
|
379
|
+
// by the host app and its cache can be written by app code or other
|
|
380
|
+
// deps, so without this gate a forged waitForTransactionReceipt
|
|
381
|
+
// entry could fabricate a confirmed/reverted transaction for an
|
|
382
|
+
// arbitrary hash. No pendingTx → not our broadcast → ignore.
|
|
383
|
+
if (!pendingTx) {
|
|
384
|
+
logger_1.logger.debug("WagmiEventHandler: Receipt for unobserved tx hash; ignoring", { transactionHash: transactionHash });
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
var address = pendingTx.address;
|
|
370
388
|
if (!address) {
|
|
371
389
|
logger_1.logger.warn("WagmiEventHandler: Transaction receipt query but no address available");
|
|
372
390
|
return;
|
|
@@ -374,9 +392,13 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
374
392
|
try {
|
|
375
393
|
// Extract receipt data
|
|
376
394
|
var receipt = state.data;
|
|
377
|
-
//
|
|
378
|
-
//
|
|
379
|
-
|
|
395
|
+
// Only act on an explicit on-chain outcome. A missing/unknown
|
|
396
|
+
// status must NOT be treated as a confirmation.
|
|
397
|
+
if ((receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "success" && (receipt === null || receipt === void 0 ? void 0 : receipt.status) !== "reverted") {
|
|
398
|
+
logger_1.logger.debug("WagmiEventHandler: Receipt without explicit success/reverted status; ignoring", { transactionHash: transactionHash, status: receipt === null || receipt === void 0 ? void 0 : receipt.status });
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
var txStatus = receipt.status === "reverted"
|
|
380
402
|
? events_1.TransactionStatus.REVERTED
|
|
381
403
|
: events_1.TransactionStatus.CONFIRMED;
|
|
382
404
|
logger_1.logger.info("WagmiEventHandler: Tracking transaction confirmation", {
|
|
@@ -455,15 +477,13 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
455
477
|
return;
|
|
456
478
|
}
|
|
457
479
|
try {
|
|
458
|
-
// Map Wagmi mutation status to Formo signature status
|
|
480
|
+
// Map Wagmi mutation status to Formo signature status.
|
|
459
481
|
var status_1;
|
|
460
|
-
var signatureHash = void 0;
|
|
461
482
|
if (state.status === "pending") {
|
|
462
483
|
status_1 = events_1.SignatureStatus.REQUESTED;
|
|
463
484
|
}
|
|
464
485
|
else if (state.status === "success") {
|
|
465
486
|
status_1 = events_1.SignatureStatus.CONFIRMED;
|
|
466
|
-
signatureHash = state.data;
|
|
467
487
|
}
|
|
468
488
|
else if (state.status === "error") {
|
|
469
489
|
status_1 = events_1.SignatureStatus.REJECTED;
|
|
@@ -471,13 +491,11 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
471
491
|
else {
|
|
472
492
|
return; // Ignore idle state
|
|
473
493
|
}
|
|
474
|
-
// Extract message from variables
|
|
475
494
|
var message = void 0;
|
|
476
495
|
if (mutationType === "signMessage") {
|
|
477
496
|
message = variables.message || "";
|
|
478
497
|
}
|
|
479
498
|
else {
|
|
480
|
-
// For signTypedData, stringify the typed data
|
|
481
499
|
message = JSON.stringify(variables.message || variables.types || {});
|
|
482
500
|
}
|
|
483
501
|
logger_1.logger.info("WagmiEventHandler: Tracking signature event", {
|
|
@@ -486,7 +504,12 @@ var WagmiEventHandler = /** @class */ (function () {
|
|
|
486
504
|
address: address,
|
|
487
505
|
chainId: chainId,
|
|
488
506
|
});
|
|
489
|
-
this.formo.signature(
|
|
507
|
+
this.formo.signature({
|
|
508
|
+
status: status_1,
|
|
509
|
+
chainId: chainId,
|
|
510
|
+
address: address,
|
|
511
|
+
message: message,
|
|
512
|
+
});
|
|
490
513
|
}
|
|
491
514
|
catch (error) {
|
|
492
515
|
logger_1.logger.error("WagmiEventHandler: Error handling signature mutation:", error);
|
|
@@ -136,18 +136,16 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
136
136
|
* @param {ChainID} params.chainId
|
|
137
137
|
* @param {Address} params.address
|
|
138
138
|
* @param {string} params.message
|
|
139
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
140
139
|
* @param {IFormoEventProperties} properties
|
|
141
140
|
* @param {IFormoEventContext} context
|
|
142
141
|
* @param {(...args: unknown[]) => void} callback
|
|
143
142
|
* @returns {Promise<void>}
|
|
144
143
|
*/
|
|
145
|
-
signature({ status, chainId, address, message,
|
|
144
|
+
signature({ status, chainId, address, message, }: {
|
|
146
145
|
status: SignatureStatus;
|
|
147
146
|
chainId?: ChainID;
|
|
148
147
|
address: Address;
|
|
149
148
|
message: string;
|
|
150
|
-
signatureHash?: string;
|
|
151
149
|
}, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
|
|
152
150
|
/**
|
|
153
151
|
* Emits a transaction event.
|
|
@@ -381,6 +379,25 @@ export declare class FormoAnalytics implements IFormoAnalytics {
|
|
|
381
379
|
* Clear per-chain state for a given namespace (or chainId) and sync derived state.
|
|
382
380
|
*/
|
|
383
381
|
private clearChainState;
|
|
382
|
+
/**
|
|
383
|
+
* Sync validated wallet/chain state into the SDK's central state
|
|
384
|
+
* WITHOUT emitting an event.
|
|
385
|
+
*
|
|
386
|
+
* Integrations (e.g. the wagmi handler) must call this on every
|
|
387
|
+
* connect / chain-change / disconnect — even when the corresponding
|
|
388
|
+
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
389
|
+
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
390
|
+
* check (which keys off `currentChainId`, not the event payload) can
|
|
391
|
+
* be bypassed, letting wallet activity on an excluded chain still be
|
|
392
|
+
* collected.
|
|
393
|
+
*
|
|
394
|
+
* - valid `address` present → record per-chain + derived state
|
|
395
|
+
* - `address` absent → clear chain state (disconnect)
|
|
396
|
+
*/
|
|
397
|
+
syncWalletState(params: {
|
|
398
|
+
chainId?: ChainID;
|
|
399
|
+
address?: Address;
|
|
400
|
+
}): void;
|
|
384
401
|
/**
|
|
385
402
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
386
403
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
@@ -55,9 +55,9 @@ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
|
|
|
55
55
|
return to.concat(ar || Array.prototype.slice.call(from));
|
|
56
56
|
};
|
|
57
57
|
import { createStore } from "mipd";
|
|
58
|
-
import { EVENTS_API_HOST, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_USER_ID_KEY, ACTIVE_WALLET_KEY, ACTIVE_WALLET_TTL_MS, CONSENT_OPT_OUT_KEY, } from "./constants";
|
|
59
|
-
import { cookie, initStorageManager } from "./storage";
|
|
60
|
-
import { getIdentityCookieDomain } from "./storage/cookiePolicy";
|
|
58
|
+
import { EVENTS_API_HOST, EventType, LOCAL_ANONYMOUS_ID_KEY, SESSION_USER_ID_KEY, SESSION_TRAFFIC_SOURCE_KEY, ACTIVE_WALLET_KEY, ACTIVE_WALLET_TTL_MS, CONSENT_OPT_OUT_KEY, } from "./constants";
|
|
59
|
+
import { cookie, session, initStorageManager } from "./storage";
|
|
60
|
+
import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "./storage/cookiePolicy";
|
|
61
61
|
import { EventManager } from "./event";
|
|
62
62
|
import { EventQueue } from "./queue";
|
|
63
63
|
import { logger, Logger } from "./logger";
|
|
@@ -82,6 +82,7 @@ var PROVIDER_SWITCH_REASONS = {
|
|
|
82
82
|
var FormoAnalytics = /** @class */ (function () {
|
|
83
83
|
function FormoAnalytics(writeKey, options) {
|
|
84
84
|
if (options === void 0) { options = {}; }
|
|
85
|
+
var _this = this;
|
|
85
86
|
var _a, _b, _c;
|
|
86
87
|
this.writeKey = writeKey;
|
|
87
88
|
this.options = options;
|
|
@@ -157,6 +158,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
157
158
|
maxQueueSize: options.maxQueueSize,
|
|
158
159
|
flushInterval: options.flushInterval,
|
|
159
160
|
errorHandler: options.errorHandler,
|
|
161
|
+
// Hard consent gate at the queue boundary: nothing buffered is
|
|
162
|
+
// ever sent once the user has opted out, even via a timer or
|
|
163
|
+
// pagehide flush scheduled before opt-out.
|
|
164
|
+
canSend: function () { return !_this.hasOptedOutTracking(); },
|
|
160
165
|
}), options);
|
|
161
166
|
// Check consent status on initialization
|
|
162
167
|
if (this.hasOptedOutTracking()) {
|
|
@@ -294,11 +299,25 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
294
299
|
*/
|
|
295
300
|
FormoAnalytics.prototype.reset = function () {
|
|
296
301
|
this.currentUserId = undefined;
|
|
302
|
+
// Clear in-memory wallet identity too. Without this, a logout/reset
|
|
303
|
+
// (also triggered by optOutTracking) still leaks the previous wallet
|
|
304
|
+
// address on subsequent track()/page() events for the rest of the
|
|
305
|
+
// page lifetime, because they fall back to currentAddress. Keep the
|
|
306
|
+
// EVM provider reference so tracking can resume on the next connect.
|
|
307
|
+
this.currentAddress = undefined;
|
|
308
|
+
this.currentChainId = undefined;
|
|
309
|
+
var evmProvider = this._chainState.evm.provider;
|
|
310
|
+
this._chainState = { evm: { provider: evmProvider }, solana: {} };
|
|
311
|
+
this._activeNamespace = undefined;
|
|
297
312
|
cookie().remove(LOCAL_ANONYMOUS_ID_KEY);
|
|
298
313
|
cookie().remove(SESSION_USER_ID_KEY);
|
|
299
314
|
cookie().remove(SESSION_WALLET_DETECTED_KEY);
|
|
300
315
|
cookie().remove(SESSION_WALLET_IDENTIFIED_KEY);
|
|
301
316
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
317
|
+
// Stored traffic-source attribution (referrer/UTM) is tracking data;
|
|
318
|
+
// clear it too so reset()/optOutTracking() don't leave it to be
|
|
319
|
+
// re-attached to the next session's events.
|
|
320
|
+
session().remove(SESSION_TRAFFIC_SOURCE_KEY);
|
|
302
321
|
};
|
|
303
322
|
/**
|
|
304
323
|
* Clean up resources and event listeners
|
|
@@ -307,6 +326,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
307
326
|
*/
|
|
308
327
|
FormoAnalytics.prototype.cleanup = function () {
|
|
309
328
|
logger.debug("FormoAnalytics: Cleaning up resources");
|
|
329
|
+
// Drop buffered events so a torn-down instance can't flush later.
|
|
330
|
+
this.eventManager.clear();
|
|
310
331
|
// Clean up Wagmi handler if present
|
|
311
332
|
if (this.wagmiHandler) {
|
|
312
333
|
this.wagmiHandler.cleanup();
|
|
@@ -470,7 +491,6 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
470
491
|
* @param {ChainID} params.chainId
|
|
471
492
|
* @param {Address} params.address
|
|
472
493
|
* @param {string} params.message
|
|
473
|
-
* @param {string} params.signatureHash - only provided if status is confirmed
|
|
474
494
|
* @param {IFormoEventProperties} properties
|
|
475
495
|
* @param {IFormoEventContext} context
|
|
476
496
|
* @param {(...args: unknown[]) => void} callback
|
|
@@ -478,10 +498,15 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
478
498
|
*/
|
|
479
499
|
FormoAnalytics.prototype.signature = function (_a, properties_1, context_1, callback_1) {
|
|
480
500
|
return __awaiter(this, arguments, void 0, function (_b, properties, context, callback) {
|
|
481
|
-
var status = _b.status, chainId = _b.chainId, address = _b.address, message = _b.message
|
|
501
|
+
var status = _b.status, chainId = _b.chainId, address = _b.address, message = _b.message;
|
|
482
502
|
return __generator(this, function (_c) {
|
|
483
503
|
switch (_c.label) {
|
|
484
|
-
case 0: return [4 /*yield*/, this.trackEvent(EventType.SIGNATURE,
|
|
504
|
+
case 0: return [4 /*yield*/, this.trackEvent(EventType.SIGNATURE, {
|
|
505
|
+
status: status,
|
|
506
|
+
chainId: chainId,
|
|
507
|
+
address: address,
|
|
508
|
+
message: message,
|
|
509
|
+
}, properties, context, callback)];
|
|
485
510
|
case 1:
|
|
486
511
|
_c.sent();
|
|
487
512
|
return [2 /*return*/];
|
|
@@ -552,6 +577,13 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
552
577
|
switch (_d.label) {
|
|
553
578
|
case 0:
|
|
554
579
|
_d.trys.push([0, 12, , 13]);
|
|
580
|
+
// identify() writes the user-id cookie and marks wallet
|
|
581
|
+
// identification before trackEvent's consent check — gate the
|
|
582
|
+
// whole method so an opted-out user gets no identity persistence.
|
|
583
|
+
if (this.hasOptedOutTracking()) {
|
|
584
|
+
logger.info("identify() skipped: user has opted out of tracking");
|
|
585
|
+
return [2 /*return*/];
|
|
586
|
+
}
|
|
555
587
|
if (!!params) return [3 /*break*/, 10];
|
|
556
588
|
// If no params provided, auto-identify
|
|
557
589
|
logger.info("Auto-identifying with providers:", this._providers.map(function (p) { return p.info.name; }));
|
|
@@ -627,7 +659,7 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
627
659
|
if (userId) {
|
|
628
660
|
this.currentUserId = userId;
|
|
629
661
|
domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
630
|
-
cookie().set(SESSION_USER_ID_KEY, userId, __assign({ path: "/" }, (domain ? { domain: domain } : {})));
|
|
662
|
+
cookie().set(SESSION_USER_ID_KEY, userId, __assign(__assign({ path: "/" }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
631
663
|
}
|
|
632
664
|
isAlreadyIdentified = this.session.isWalletIdentified(validAddress, rdns || "");
|
|
633
665
|
logger.debug("Identify: Checking deduplication", {
|
|
@@ -676,6 +708,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
676
708
|
return __generator(this, function (_c) {
|
|
677
709
|
switch (_c.label) {
|
|
678
710
|
case 0:
|
|
711
|
+
// detect() marks wallet detection (a cookie write) before
|
|
712
|
+
// trackEvent's consent check — gate it on opt-out.
|
|
713
|
+
if (this.hasOptedOutTracking()) {
|
|
714
|
+
logger.info("detect() skipped: user has opted out of tracking");
|
|
715
|
+
return [2 /*return*/];
|
|
716
|
+
}
|
|
679
717
|
if (this.session.isWalletDetected(rdns))
|
|
680
718
|
return [2 /*return*/, logger.warn("Detect: Wallet ".concat(providerName, " already detected in this session"))];
|
|
681
719
|
this.session.markWalletDetected(rdns);
|
|
@@ -722,6 +760,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
722
760
|
// Set opt-out flag in persistent storage using direct cookie access
|
|
723
761
|
// This must be done before switching storage to ensure persistence
|
|
724
762
|
setConsentFlag(this.writeKey, CONSENT_OPT_OUT_KEY, "true");
|
|
763
|
+
// Drop anything already buffered so a pending timer/pagehide flush
|
|
764
|
+
// cannot ship events after consent withdrawal.
|
|
765
|
+
this.eventManager.clear();
|
|
725
766
|
this.reset();
|
|
726
767
|
logger.info("Successfully opted out of tracking");
|
|
727
768
|
};
|
|
@@ -1941,7 +1982,9 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1941
1982
|
});
|
|
1942
1983
|
});
|
|
1943
1984
|
};
|
|
1944
|
-
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
1985
|
+
FormoAnalytics.prototype.buildSignatureEventPayload = function (method, params,
|
|
1986
|
+
// Intentionally not read. Kept for positional call-site arity.
|
|
1987
|
+
_response, chainId) {
|
|
1945
1988
|
var _a;
|
|
1946
1989
|
var rawAddress = method === "personal_sign"
|
|
1947
1990
|
? params[1]
|
|
@@ -1958,9 +2001,10 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
1958
2001
|
};
|
|
1959
2002
|
if (method === "personal_sign") {
|
|
1960
2003
|
var message = Buffer.from(params[0].slice(2), "hex").toString("utf8");
|
|
1961
|
-
return __assign(__assign(
|
|
2004
|
+
return __assign(__assign({}, basePayload), { message: message });
|
|
1962
2005
|
}
|
|
1963
|
-
|
|
2006
|
+
// eth_signTypedData*: params[1] is the full EIP-712 struct.
|
|
2007
|
+
return __assign(__assign({}, basePayload), { message: params[1] });
|
|
1964
2008
|
};
|
|
1965
2009
|
FormoAnalytics.prototype.buildTransactionEventPayload = function (params, provider) {
|
|
1966
2010
|
return __awaiter(this, void 0, void 0, function () {
|
|
@@ -2076,7 +2120,8 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2076
2120
|
}
|
|
2077
2121
|
this._providerListenersMap.delete(provider);
|
|
2078
2122
|
};
|
|
2079
|
-
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2123
|
+
// Explicitly untrack a provider: remove listeners, clear wrapper flag
|
|
2124
|
+
// and tracking
|
|
2080
2125
|
FormoAnalytics.prototype.untrackProvider = function (provider) {
|
|
2081
2126
|
try {
|
|
2082
2127
|
this.removeProviderListeners(provider);
|
|
@@ -2194,6 +2239,42 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2194
2239
|
}
|
|
2195
2240
|
this.syncDerivedState();
|
|
2196
2241
|
};
|
|
2242
|
+
/**
|
|
2243
|
+
* Sync validated wallet/chain state into the SDK's central state
|
|
2244
|
+
* WITHOUT emitting an event.
|
|
2245
|
+
*
|
|
2246
|
+
* Integrations (e.g. the wagmi handler) must call this on every
|
|
2247
|
+
* connect / chain-change / disconnect — even when the corresponding
|
|
2248
|
+
* autocapture event is disabled. Otherwise `currentChainId` stays
|
|
2249
|
+
* stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
|
|
2250
|
+
* check (which keys off `currentChainId`, not the event payload) can
|
|
2251
|
+
* be bypassed, letting wallet activity on an excluded chain still be
|
|
2252
|
+
* collected.
|
|
2253
|
+
*
|
|
2254
|
+
* - valid `address` present → record per-chain + derived state
|
|
2255
|
+
* - `address` absent → clear chain state (disconnect)
|
|
2256
|
+
*/
|
|
2257
|
+
FormoAnalytics.prototype.syncWalletState = function (params) {
|
|
2258
|
+
var chainId = params.chainId, address = params.address;
|
|
2259
|
+
if (!address) {
|
|
2260
|
+
if (chainId !== undefined && chainId !== null) {
|
|
2261
|
+
this.clearChainState(chainId);
|
|
2262
|
+
}
|
|
2263
|
+
else {
|
|
2264
|
+
this.clearChainState("evm");
|
|
2265
|
+
this.clearChainState("solana");
|
|
2266
|
+
}
|
|
2267
|
+
return;
|
|
2268
|
+
}
|
|
2269
|
+
if (chainId === null || chainId === undefined)
|
|
2270
|
+
return;
|
|
2271
|
+
var validAddress = validateAddress(address, chainId);
|
|
2272
|
+
if (!validAddress) {
|
|
2273
|
+
logger.warn("syncWalletState: invalid address (\"".concat(address, "\") for chain ").concat(chainId));
|
|
2274
|
+
return;
|
|
2275
|
+
}
|
|
2276
|
+
this.setChainState(chainId, { address: validAddress });
|
|
2277
|
+
};
|
|
2197
2278
|
/**
|
|
2198
2279
|
* Synchronize currentAddress/currentChainId from the active namespace.
|
|
2199
2280
|
* Last-connected-chain-wins: _activeNamespace takes precedence.
|
|
@@ -2232,10 +2313,16 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2232
2313
|
*/
|
|
2233
2314
|
FormoAnalytics.prototype.persistActiveWallet = function () {
|
|
2234
2315
|
try {
|
|
2316
|
+
// Never write an identity cookie for an opted-out user; ensure any
|
|
2317
|
+
// prior snapshot is removed instead.
|
|
2318
|
+
if (this.hasOptedOutTracking()) {
|
|
2319
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2320
|
+
return;
|
|
2321
|
+
}
|
|
2235
2322
|
if (this.currentAddress) {
|
|
2236
2323
|
var value = JSON.stringify(__assign({ address: this.currentAddress }, (this.currentChainId !== undefined && { chainId: this.currentChainId })));
|
|
2237
2324
|
var domain = getIdentityCookieDomain(this.crossSubdomainCookies);
|
|
2238
|
-
cookie().set(ACTIVE_WALLET_KEY, value, __assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, (domain ? { domain: domain } : {})));
|
|
2325
|
+
cookie().set(ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
2239
2326
|
}
|
|
2240
2327
|
else {
|
|
2241
2328
|
cookie().remove(ACTIVE_WALLET_KEY);
|
|
@@ -2251,6 +2338,12 @@ var FormoAnalytics = /** @class */ (function () {
|
|
|
2251
2338
|
*/
|
|
2252
2339
|
FormoAnalytics.prototype.loadActiveWallet = function () {
|
|
2253
2340
|
try {
|
|
2341
|
+
// Never restore wallet identity into memory for an opted-out user
|
|
2342
|
+
// (mirrors persistActiveWallet's guard); drop any stale snapshot.
|
|
2343
|
+
if (this.hasOptedOutTracking()) {
|
|
2344
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
2345
|
+
return;
|
|
2346
|
+
}
|
|
2254
2347
|
var raw = cookie().get(ACTIVE_WALLET_KEY);
|
|
2255
2348
|
if (!raw)
|
|
2256
2349
|
return;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import { FC } from "react";
|
|
2
|
-
import {
|
|
1
|
+
import { FC, ReactNode } from "react";
|
|
2
|
+
import { IFormoAnalytics, Options } from "./types";
|
|
3
|
+
export interface FormoAnalyticsProviderProps {
|
|
4
|
+
writeKey: string;
|
|
5
|
+
options?: Options;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
children: ReactNode;
|
|
8
|
+
}
|
|
3
9
|
export declare const FormoAnalyticsContext: import("react").Context<IFormoAnalytics>;
|
|
4
10
|
export declare const FormoAnalyticsProvider: FC<FormoAnalyticsProviderProps>;
|
|
5
11
|
export declare const useFormo: () => IFormoAnalytics;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from "./FormoAnalytics";
|
|
2
|
+
export * from "./types";
|
|
3
|
+
export { formofy } from "./initialization";
|
|
4
|
+
export { parsePrivyProperties } from "./privy";
|
|
5
|
+
export type { PrivyUser, PrivyLinkedAccount, PrivyAccountType, PrivyProfileProperties, PrivyWalletInfo, } from "./privy";
|
|
6
|
+
export { SolanaManager } from "./solana";
|
|
7
|
+
export { SOLANA_CHAIN_IDS, DEFAULT_SOLANA_CHAIN_ID, isSolanaChainId, } from "./solana";
|
|
8
|
+
export type { SolanaOptions, SolanaCluster } from "./solana";
|
|
9
|
+
export type { SolanaClientStore, SolanaClientState } from "./solana";
|
|
10
|
+
//# sourceMappingURL=core.d.ts.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// React-free entry point.
|
|
2
|
+
// Use this from non-React frameworks (Angular, Vue, Svelte, vanilla JS) to
|
|
3
|
+
// avoid pulling the React provider into the dependency graph.
|
|
4
|
+
//
|
|
5
|
+
// Equivalent to the package root entry minus FormoAnalyticsProvider/useFormo
|
|
6
|
+
// and the `window.formofy = formofy` side effect.
|
|
7
|
+
export * from "./FormoAnalytics";
|
|
8
|
+
export * from "./types";
|
|
9
|
+
export { formofy } from "./initialization";
|
|
10
|
+
export { parsePrivyProperties } from "./privy";
|
|
11
|
+
export { SolanaManager } from "./solana";
|
|
12
|
+
export { SOLANA_CHAIN_IDS, DEFAULT_SOLANA_CHAIN_ID, isSolanaChainId, } from "./solana";
|
|
13
|
+
//# sourceMappingURL=core.js.map
|
|
@@ -35,7 +35,7 @@ declare class EventFactory implements IEventFactory {
|
|
|
35
35
|
generateConnectEvent(chainId: ChainID, address: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
36
36
|
generateDisconnectEvent(chainId?: ChainID, address?: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
37
37
|
generateChainChangedEvent(chainId: ChainID, address: Address, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
38
|
-
generateSignatureEvent(status: SignatureStatus, chainId: ChainID, address: Address, message: string,
|
|
38
|
+
generateSignatureEvent(status: SignatureStatus, chainId: ChainID, address: Address, message: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
39
39
|
generateTransactionEvent(status: TransactionStatus, chainId: ChainID, address: Address, data?: string, to?: string, value?: string, transactionHash?: string, function_name?: string, function_args?: Record<string, unknown>, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
40
40
|
generateTrackEvent(event: string, properties?: IFormoEventProperties, context?: IFormoEventContext): Promise<IFormoEvent>;
|
|
41
41
|
create(event: APIEvent, address?: Address, userId?: string): Promise<IFormoEvent>;
|
|
@@ -413,12 +413,12 @@ var EventFactory = /** @class */ (function () {
|
|
|
413
413
|
});
|
|
414
414
|
});
|
|
415
415
|
};
|
|
416
|
-
EventFactory.prototype.generateSignatureEvent = function (status, chainId, address, message,
|
|
416
|
+
EventFactory.prototype.generateSignatureEvent = function (status, chainId, address, message, properties, context) {
|
|
417
417
|
return __awaiter(this, void 0, void 0, function () {
|
|
418
418
|
var signatureEvent;
|
|
419
419
|
return __generator(this, function (_a) {
|
|
420
420
|
signatureEvent = {
|
|
421
|
-
properties: __assign(
|
|
421
|
+
properties: __assign({ status: status, chainId: chainId, message: message }, properties),
|
|
422
422
|
address: address,
|
|
423
423
|
type: "signature",
|
|
424
424
|
};
|
|
@@ -506,7 +506,7 @@ var EventFactory = /** @class */ (function () {
|
|
|
506
506
|
case 12:
|
|
507
507
|
formoEvent = _b.sent();
|
|
508
508
|
return [3 /*break*/, 19];
|
|
509
|
-
case 13: return [4 /*yield*/, this.generateSignatureEvent(event.status, event.chainId, event.address, event.message, event.
|
|
509
|
+
case 13: return [4 /*yield*/, this.generateSignatureEvent(event.status, event.chainId, event.address, event.message, event.properties, event.context)];
|
|
510
510
|
case 14:
|
|
511
511
|
formoEvent = _b.sent();
|
|
512
512
|
return [3 /*break*/, 19];
|