@formo/analytics 1.38.2 → 1.40.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.
Files changed (74) hide show
  1. package/dist/cjs/src/FormoAnalytics.d.ts +4 -1
  2. package/dist/cjs/src/FormoAnalytics.js +56 -36
  3. package/dist/cjs/src/FormoAnalyticsProvider.js +20 -0
  4. package/dist/cjs/src/event/EventFactory.d.ts +9 -1
  5. package/dist/cjs/src/event/EventFactory.js +31 -16
  6. package/dist/cjs/src/event/EventManager.d.ts +3 -1
  7. package/dist/cjs/src/event/EventManager.js +33 -4
  8. package/dist/cjs/src/event/cancellation.d.ts +4 -0
  9. package/dist/cjs/src/event/cancellation.js +7 -0
  10. package/dist/cjs/src/event/type.d.ts +1 -0
  11. package/dist/cjs/src/event/utils.d.ts +5 -1
  12. package/dist/cjs/src/event/utils.js +57 -2
  13. package/dist/cjs/src/evm/EvmEventTracker.d.ts +7 -0
  14. package/dist/cjs/src/evm/EvmEventTracker.js +31 -5
  15. package/dist/cjs/src/queue/EventQueue.d.ts +1 -0
  16. package/dist/cjs/src/queue/EventQueue.js +11 -6
  17. package/dist/cjs/src/solana/SolanaManager.d.ts +37 -9
  18. package/dist/cjs/src/solana/SolanaManager.js +135 -20
  19. package/dist/cjs/src/solana/SolanaStoreHandler.d.ts +6 -0
  20. package/dist/cjs/src/solana/SolanaStoreHandler.js +17 -8
  21. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  22. package/dist/cjs/src/solana/SolanaWalletStandardRegistry.js +447 -0
  23. package/dist/cjs/src/solana/index.d.ts +10 -5
  24. package/dist/cjs/src/solana/index.js +12 -6
  25. package/dist/cjs/src/solana/storeTypes.d.ts +20 -1
  26. package/dist/cjs/src/solana/types.d.ts +32 -9
  27. package/dist/cjs/src/solana/types.js +15 -0
  28. package/dist/cjs/src/solana/walletStandardTypes.d.ts +54 -0
  29. package/dist/cjs/src/solana/walletStandardTypes.js +21 -0
  30. package/dist/cjs/src/storage/StorageManager.d.ts +2 -0
  31. package/dist/cjs/src/storage/StorageManager.js +6 -0
  32. package/dist/cjs/src/storage/index.d.ts +1 -0
  33. package/dist/cjs/src/storage/index.js +7 -1
  34. package/dist/cjs/src/types/base.d.ts +15 -5
  35. package/dist/cjs/src/version.d.ts +1 -1
  36. package/dist/cjs/src/version.js +1 -1
  37. package/dist/esm/src/FormoAnalytics.d.ts +4 -1
  38. package/dist/esm/src/FormoAnalytics.js +54 -34
  39. package/dist/esm/src/FormoAnalyticsProvider.js +20 -0
  40. package/dist/esm/src/event/EventFactory.d.ts +9 -1
  41. package/dist/esm/src/event/EventFactory.js +31 -16
  42. package/dist/esm/src/event/EventManager.d.ts +3 -1
  43. package/dist/esm/src/event/EventManager.js +33 -4
  44. package/dist/esm/src/event/cancellation.d.ts +4 -0
  45. package/dist/esm/src/event/cancellation.js +4 -0
  46. package/dist/esm/src/event/type.d.ts +1 -0
  47. package/dist/esm/src/event/utils.d.ts +5 -1
  48. package/dist/esm/src/event/utils.js +56 -3
  49. package/dist/esm/src/evm/EvmEventTracker.d.ts +7 -0
  50. package/dist/esm/src/evm/EvmEventTracker.js +31 -5
  51. package/dist/esm/src/queue/EventQueue.d.ts +1 -0
  52. package/dist/esm/src/queue/EventQueue.js +11 -6
  53. package/dist/esm/src/solana/SolanaManager.d.ts +37 -9
  54. package/dist/esm/src/solana/SolanaManager.js +135 -20
  55. package/dist/esm/src/solana/SolanaStoreHandler.d.ts +6 -0
  56. package/dist/esm/src/solana/SolanaStoreHandler.js +18 -9
  57. package/dist/esm/src/solana/SolanaWalletStandardRegistry.d.ts +163 -0
  58. package/dist/esm/src/solana/SolanaWalletStandardRegistry.js +444 -0
  59. package/dist/esm/src/solana/index.d.ts +10 -5
  60. package/dist/esm/src/solana/index.js +10 -5
  61. package/dist/esm/src/solana/storeTypes.d.ts +20 -1
  62. package/dist/esm/src/solana/types.d.ts +32 -9
  63. package/dist/esm/src/solana/types.js +14 -0
  64. package/dist/esm/src/solana/walletStandardTypes.d.ts +54 -0
  65. package/dist/esm/src/solana/walletStandardTypes.js +18 -0
  66. package/dist/esm/src/storage/StorageManager.d.ts +2 -0
  67. package/dist/esm/src/storage/StorageManager.js +6 -0
  68. package/dist/esm/src/storage/index.d.ts +1 -0
  69. package/dist/esm/src/storage/index.js +5 -0
  70. package/dist/esm/src/types/base.d.ts +15 -5
  71. package/dist/esm/src/version.d.ts +1 -1
  72. package/dist/esm/src/version.js +1 -1
  73. package/dist/index.umd.min.js +1 -1
  74. package/package.json +2 -2
@@ -67,11 +67,18 @@ import { CHANNEL, CLICK_ID_PARAMS, DEFAULT_EXCLUDED_QUERY_PARAMS, DEFAULT_REFERR
67
67
  import { sanitizeTrafficSources } from "./sanitize";
68
68
  import { generateAnonymousId } from "./utils";
69
69
  import { detectBrowser } from "../browser/browsers";
70
+ import { EVENT_CREATION_CANCELLED } from "./cancellation";
70
71
  var ISO_3166_ALPHA_2_REGEX = /^[A-Z]{2}$/;
71
72
  var EventFactory = /** @class */ (function () {
72
- function EventFactory(options) {
73
+ function EventFactory(options,
74
+ /** Consulted once per event, after the only await in creation. */
75
+ canCreate) {
76
+ if (canCreate === void 0) { canCreate = function () { return true; }; }
73
77
  var _this = this;
74
78
  var _a, _b;
79
+ this.canCreate = canCreate;
80
+ /** Bumped by invalidate(); an event created across a bump is dropped. */
81
+ this.generation = 0;
75
82
  this.extractUTMParameters = function (url) {
76
83
  var result = {
77
84
  utm_campaign: "",
@@ -404,6 +411,10 @@ var EventFactory = /** @class */ (function () {
404
411
  return safeDefaults;
405
412
  }
406
413
  };
414
+ /** Drop every event still being created. Called on clear() and close(). */
415
+ EventFactory.prototype.invalidate = function () {
416
+ this.generation++;
417
+ };
407
418
  // Contextual fields that are automatically collected and populated by the Formo SDK
408
419
  EventFactory.prototype.generateContext = function (context) {
409
420
  return __awaiter(this, void 0, void 0, function () {
@@ -427,24 +438,28 @@ var EventFactory = /** @class */ (function () {
427
438
  };
428
439
  EventFactory.prototype.getEnrichedEvent = function (formoEvent, context) {
429
440
  return __awaiter(this, void 0, void 0, function () {
430
- var commonEventData, eventChainId, validAddress, processedEvent;
431
- var _a;
432
- var _b, _c;
433
- return __generator(this, function (_d) {
434
- switch (_d.label) {
441
+ var generation, enrichedContext, commonEventData, eventChainId, validAddress, processedEvent;
442
+ var _a, _b;
443
+ return __generator(this, function (_c) {
444
+ switch (_c.label) {
435
445
  case 0:
436
- _a = {};
446
+ generation = this.generation;
437
447
  return [4 /*yield*/, this.generateContext(context)];
438
448
  case 1:
439
- commonEventData = (_a.context = _d.sent(),
440
- _a.original_timestamp = getCurrentTimeFormatted(),
441
- _a.user_id = formoEvent.user_id,
442
- _a.type = formoEvent.type,
443
- _a.channel = CHANNEL,
444
- _a.version = VERSION,
445
- _a);
446
- commonEventData.anonymous_id = generateAnonymousId(LOCAL_ANONYMOUS_ID_KEY, (_b = this.options) === null || _b === void 0 ? void 0 : _b.crossSubdomainCookies);
447
- eventChainId = (_c = formoEvent.properties) === null || _c === void 0 ? void 0 : _c.chainId;
449
+ enrichedContext = _c.sent();
450
+ if (generation !== this.generation || !this.canCreate()) {
451
+ throw EVENT_CREATION_CANCELLED;
452
+ }
453
+ commonEventData = {
454
+ context: enrichedContext,
455
+ original_timestamp: getCurrentTimeFormatted(),
456
+ user_id: formoEvent.user_id,
457
+ type: formoEvent.type,
458
+ channel: CHANNEL,
459
+ version: VERSION,
460
+ };
461
+ commonEventData.anonymous_id = generateAnonymousId(LOCAL_ANONYMOUS_ID_KEY, (_a = this.options) === null || _a === void 0 ? void 0 : _a.crossSubdomainCookies);
462
+ eventChainId = (_b = formoEvent.properties) === null || _b === void 0 ? void 0 : _b.chainId;
448
463
  validAddress = this.validateEventAddress(formoEvent.address, eventChainId);
449
464
  commonEventData.address = validAddress;
450
465
  processedEvent = mergeDeepRight(formoEvent, commonEventData);
@@ -5,14 +5,16 @@ import { IEventFactory, IEventManager } from "./type";
5
5
  * A service to generate valid event payloads and queue them for processing
6
6
  */
7
7
  declare class EventManager implements IEventManager {
8
+ private readonly canAcceptEvent;
8
9
  eventQueue: IEventQueue;
9
10
  eventFactory: IEventFactory;
11
+ private generation;
10
12
  /**
11
13
  *
12
14
  * @param eventQueue Event queue instance
13
15
  * @param options Optional configuration (referral parsing, etc.)
14
16
  */
15
- constructor(eventQueue: IEventQueue, options?: Options);
17
+ constructor(eventQueue: IEventQueue, options?: Options, canAcceptEvent?: () => boolean);
16
18
  /**
17
19
  * Consumes a new incoming event
18
20
  * @param event Incoming event data
@@ -47,6 +47,7 @@ var __rest = (this && this.__rest) || function (s, e) {
47
47
  };
48
48
  import { logger } from "../logger";
49
49
  import { EventFactory } from "./EventFactory";
50
+ import { EVENT_CREATION_CANCELLED } from "./cancellation";
50
51
  import { isBlockedAddress } from "../utils/address";
51
52
  /**
52
53
  * A service to generate valid event payloads and queue them for processing
@@ -57,9 +58,13 @@ var EventManager = /** @class */ (function () {
57
58
  * @param eventQueue Event queue instance
58
59
  * @param options Optional configuration (referral parsing, etc.)
59
60
  */
60
- function EventManager(eventQueue, options) {
61
+ function EventManager(eventQueue, options, canAcceptEvent) {
62
+ if (canAcceptEvent === void 0) { canAcceptEvent = function () { return true; }; }
63
+ var _this = this;
64
+ this.canAcceptEvent = canAcceptEvent;
65
+ this.generation = 0;
61
66
  this.eventQueue = eventQueue;
62
- this.eventFactory = new EventFactory(options);
67
+ this.eventFactory = new EventFactory(options, function () { return _this.canAcceptEvent(); });
63
68
  }
64
69
  /**
65
70
  * Consumes a new incoming event
@@ -67,14 +72,34 @@ var EventManager = /** @class */ (function () {
67
72
  */
68
73
  EventManager.prototype.addEvent = function (event, address, userId) {
69
74
  return __awaiter(this, void 0, void 0, function () {
70
- var callback, _event, formoEvent;
75
+ var callback, _event, generation, shouldContinue, formoEvent, error_1;
76
+ var _this = this;
71
77
  return __generator(this, function (_a) {
72
78
  switch (_a.label) {
73
79
  case 0:
74
80
  callback = event.callback, _event = __rest(event, ["callback"]);
75
- return [4 /*yield*/, this.eventFactory.create(_event, address, userId)];
81
+ generation = this.generation;
82
+ shouldContinue = function () {
83
+ return generation === _this.generation && _this.canAcceptEvent();
84
+ };
85
+ if (!shouldContinue())
86
+ return [2 /*return*/];
87
+ _a.label = 1;
76
88
  case 1:
89
+ _a.trys.push([1, 3, , 4]);
90
+ return [4 /*yield*/, this.eventFactory.create(_event, address, userId)];
91
+ case 2:
77
92
  formoEvent = _a.sent();
93
+ return [3 /*break*/, 4];
94
+ case 3:
95
+ error_1 = _a.sent();
96
+ if (error_1 === EVENT_CREATION_CANCELLED)
97
+ return [2 /*return*/];
98
+ throw error_1;
99
+ case 4:
100
+ // Reject work invalidated while enrichment was pending.
101
+ if (!shouldContinue())
102
+ return [2 /*return*/];
78
103
  // Check if the final event has a blocked address - don't queue it
79
104
  if (formoEvent.address && isBlockedAddress(formoEvent.address)) {
80
105
  logger.warn("Event blocked: Address ".concat(formoEvent.address, " is in the blocked list and cannot emit events"));
@@ -95,10 +120,14 @@ var EventManager = /** @class */ (function () {
95
120
  };
96
121
  /** Drop any buffered events (consent withdrawal). Recoverable. */
97
122
  EventManager.prototype.clear = function () {
123
+ this.generation++;
124
+ this.eventFactory.invalidate();
98
125
  this.eventQueue.clear();
99
126
  };
100
127
  /** Terminal shutdown on teardown: nothing can be sent after this. */
101
128
  EventManager.prototype.close = function () {
129
+ this.generation++;
130
+ this.eventFactory.invalidate();
102
131
  this.eventQueue.close();
103
132
  };
104
133
  return EventManager;
@@ -0,0 +1,4 @@
1
+ /** Internal control flow: an event invalidated by consent withdrawal. */
2
+ declare const EVENT_CREATION_CANCELLED: unique symbol;
3
+ export { EVENT_CREATION_CANCELLED };
4
+ //# sourceMappingURL=cancellation.d.ts.map
@@ -0,0 +1,4 @@
1
+ /** Internal control flow: an event invalidated by consent withdrawal. */
2
+ var EVENT_CREATION_CANCELLED = Symbol();
3
+ export { EVENT_CREATION_CANCELLED };
4
+ //# sourceMappingURL=cancellation.js.map
@@ -5,6 +5,7 @@ export interface IEventManager {
5
5
  close(): void;
6
6
  }
7
7
  export interface IEventFactory {
8
+ invalidate(): void;
8
9
  create(event: APIEvent, address?: Address, userId?: string): Promise<IFormoEvent>;
9
10
  }
10
11
  //# sourceMappingURL=type.d.ts.map
@@ -1,4 +1,8 @@
1
1
  import { AnonymousID } from "../types";
2
2
  declare const generateAnonymousId: (key: string, crossSubdomainCookies?: boolean) => AnonymousID;
3
- export { generateAnonymousId };
3
+ /** Clear the anonymous id from every storage layer. */
4
+ declare const clearAnonymousId: (key: string) => void;
5
+ /** Test hook: forget the page-lifetime memory, as a new page load would. */
6
+ declare const __resetAnonymousIdMemory: () => void;
7
+ export { generateAnonymousId, clearAnonymousId, __resetAnonymousIdMemory };
4
8
  //# sourceMappingURL=utils.d.ts.map
@@ -10,13 +10,26 @@ var __assign = (this && this.__assign) || function () {
10
10
  return __assign.apply(this, arguments);
11
11
  };
12
12
  import { generateNativeUUID } from "../utils";
13
- import { cookie } from "../storage";
13
+ import { cookie, local, usesCookieStorage } from "../storage";
14
14
  import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "../storage/cookiePolicy";
15
+ /** Page-lifetime fallback when persistent storage is unavailable. */
16
+ var volatileAnonymousId;
17
+ /** Persistent fallback when cookies are unavailable. */
18
+ var readLocal = function (key) {
19
+ try {
20
+ var value = local().get(key);
21
+ return typeof value === "string" && value ? value : undefined;
22
+ }
23
+ catch (_a) {
24
+ return undefined;
25
+ }
26
+ };
15
27
  var generateAnonymousId = function (key, crossSubdomainCookies) {
28
+ var _a, _b;
16
29
  var storedAnonymousId = cookie().get(key);
17
30
  var anonymousId = (storedAnonymousId && typeof storedAnonymousId === "string"
18
31
  ? storedAnonymousId
19
- : generateNativeUUID());
32
+ : (_b = (_a = readLocal(key)) !== null && _a !== void 0 ? _a : volatileAnonymousId) !== null && _b !== void 0 ? _b : generateNativeUUID());
20
33
  var domain = getIdentityCookieDomain(crossSubdomainCookies);
21
34
  // Re-set the cookie with the configured scope. When crossSubdomainCookies
22
35
  // is true, this migrates legacy host-only cookies on the current host to the apex
@@ -24,7 +37,47 @@ var generateAnonymousId = function (key, crossSubdomainCookies) {
24
37
  // example.com is not visible from app.example.com) cannot be migrated
25
38
  // until the user revisits that host.
26
39
  cookie().set(key, anonymousId, __assign(__assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
40
+ // Retry host-only when a domain-scoped write is rejected.
41
+ if (domain && cookie().get(key) !== anonymousId) {
42
+ cookie().set(key, anonymousId, __assign({ expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 365).toUTCString(), path: "/" }, getIdentityCookieSecurity()));
43
+ }
44
+ if (cookie().get(key) === anonymousId) {
45
+ // Prefer the cookie and discard the fallback copy.
46
+ volatileAnonymousId = undefined;
47
+ if (usesCookieStorage() && readLocal(key)) {
48
+ try {
49
+ local().remove(key);
50
+ }
51
+ catch (_c) {
52
+ // Storage is unavailable.
53
+ }
54
+ }
55
+ return anonymousId;
56
+ }
57
+ // Persist across embedded page loads when cookies are blocked.
58
+ try {
59
+ local().set(key, anonymousId);
60
+ }
61
+ catch (_d) {
62
+ // Memory remains the last fallback.
63
+ }
64
+ volatileAnonymousId = anonymousId;
27
65
  return anonymousId;
28
66
  };
29
- export { generateAnonymousId };
67
+ /** Clear the anonymous id from every storage layer. */
68
+ var clearAnonymousId = function (key) {
69
+ cookie().remove(key);
70
+ try {
71
+ local().remove(key);
72
+ }
73
+ catch (_a) {
74
+ // Storage is unavailable.
75
+ }
76
+ volatileAnonymousId = undefined;
77
+ };
78
+ /** Test hook: forget the page-lifetime memory, as a new page load would. */
79
+ var __resetAnonymousIdMemory = function () {
80
+ volatileAnonymousId = undefined;
81
+ };
82
+ export { generateAnonymousId, clearAnonymousId, __resetAnonymousIdMemory };
30
83
  //# sourceMappingURL=utils.js.map
@@ -11,6 +11,8 @@ export interface EvmEventTrackerDeps {
11
11
  isTrackingSuppressed(): boolean;
12
12
  /** Whether an event on this chain would actually be sent. */
13
13
  willTrackEvent(chainId?: ChainID): boolean;
14
+ /** Retry detect events after the active chain changes. */
15
+ retryDetection(): void;
14
16
  /** In wagmi mode the SDK does not wrap providers itself. */
15
17
  isWagmiMode(): boolean;
16
18
  /** Emission. The tracker decides WHEN; the SDK owns the event API. */
@@ -152,6 +154,8 @@ export declare class EvmEventTracker {
152
154
  * had already replaced.
153
155
  */
154
156
  private unsubscribeDiscovery?;
157
+ /** Discovery store retained for complete listener cleanup. */
158
+ private discoveryStore?;
155
159
  constructor(wallet: WalletStateStore, registry: EvmProviderRegistry, deps: EvmEventTrackerDeps);
156
160
  /** Stop listening for wallet announcements. Called from SDK teardown. */
157
161
  cleanup(): void;
@@ -259,8 +263,11 @@ export declare class EvmEventTracker {
259
263
  private registerConnectListener;
260
264
  private registerDisconnectListener;
261
265
  private onConnected;
266
+ /** Whether Formo owns provider lifecycle tracking. */
267
+ private tracksDiscovered;
262
268
  getProviders(): Promise<readonly EIP6963ProviderDetail[]>;
263
269
  detectWallets(providers: readonly EIP6963ProviderDetail[]): Promise<void>;
270
+ detectableProviders(): readonly EIP6963ProviderDetail[];
264
271
  /**
265
272
  * Seed a provider's chain from whatever it already exposes synchronously.
266
273
  *
@@ -267,7 +267,7 @@ var EvmEventTracker = /** @class */ (function () {
267
267
  };
268
268
  /** Stop listening for wallet announcements. Called from SDK teardown. */
269
269
  EvmEventTracker.prototype.cleanup = function () {
270
- var _a;
270
+ var _a, _b;
271
271
  // A replay in flight (awaiting the active wallet's accounts) must not
272
272
  // resume into a torn-down instance and commit a session there.
273
273
  this.disposed = true;
@@ -277,11 +277,13 @@ var EvmEventTracker = /** @class */ (function () {
277
277
  this.retryRequested = false;
278
278
  try {
279
279
  (_a = this.unsubscribeDiscovery) === null || _a === void 0 ? void 0 : _a.call(this);
280
+ (_b = this.discoveryStore) === null || _b === void 0 ? void 0 : _b.destroy();
280
281
  }
281
282
  catch (e) {
282
283
  logger.warn("Failed to unsubscribe from provider discovery", e);
283
284
  }
284
285
  this.unsubscribeDiscovery = undefined;
286
+ this.discoveryStore = undefined;
285
287
  };
286
288
  EvmEventTracker.prototype.sessionGeneration = function (provider) {
287
289
  var _a;
@@ -1015,6 +1017,7 @@ var EvmEventTracker = /** @class */ (function () {
1015
1017
  this.wallet.provider = provider;
1016
1018
  }
1017
1019
  this.wallet.set('evm', { chainId: nextChainId });
1020
+ this.deps.retryDetection();
1018
1021
  _a.label = 1;
1019
1022
  case 1:
1020
1023
  _a.trys.push([1, 5, , 6]);
@@ -1332,12 +1335,17 @@ var EvmEventTracker = /** @class */ (function () {
1332
1335
  });
1333
1336
  });
1334
1337
  };
1338
+ /** Whether Formo owns provider lifecycle tracking. */
1339
+ EvmEventTracker.prototype.tracksDiscovered = function () {
1340
+ return !this.deps.isWagmiMode();
1341
+ };
1335
1342
  EvmEventTracker.prototype.getProviders = function () {
1336
1343
  return __awaiter(this, void 0, void 0, function () {
1337
1344
  var store, providers, injected, injectedProviderInfo, injectedDetail, uniqueProviders, _i, uniqueProviders_1, detail;
1338
1345
  var _this = this;
1339
1346
  return __generator(this, function (_a) {
1340
1347
  store = createStore();
1348
+ this.discoveryStore = store;
1341
1349
  providers = store.getProviders();
1342
1350
  this.unsubscribeDiscovery = store.subscribe(function (providerDetails) {
1343
1351
  providers = providerDetails;
@@ -1356,8 +1364,12 @@ var EvmEventTracker = /** @class */ (function () {
1356
1364
  var p = detail === null || detail === void 0 ? void 0 : detail.provider;
1357
1365
  return !!p && !_this.registry.isTracked(p);
1358
1366
  });
1359
- if (newDetails.length > 0) {
1367
+ // Wagmi detects only providers added by this announcement.
1368
+ var toDetect = _this.tracksDiscovered() ? newDetails : newlyAddedDetails;
1369
+ if (_this.tracksDiscovered() && newDetails.length > 0) {
1360
1370
  _this.trackProviders(newDetails);
1371
+ }
1372
+ if (toDetect.length > 0) {
1361
1373
  // Detect newly discovered wallets (session de-dupes) with error handling
1362
1374
  (function () { return __awaiter(_this, void 0, void 0, function () {
1363
1375
  var e_3;
@@ -1365,7 +1377,7 @@ var EvmEventTracker = /** @class */ (function () {
1365
1377
  switch (_a.label) {
1366
1378
  case 0:
1367
1379
  _a.trys.push([0, 2, , 3]);
1368
- return [4 /*yield*/, this.detectWallets(newDetails)];
1380
+ return [4 /*yield*/, this.detectWallets(toDetect)];
1369
1381
  case 1:
1370
1382
  _a.sent();
1371
1383
  return [3 /*break*/, 3];
@@ -1391,7 +1403,7 @@ var EvmEventTracker = /** @class */ (function () {
1391
1403
  if (this.registry.injected &&
1392
1404
  this.registry.injected.provider === injected) {
1393
1405
  // Ensure it's tracked
1394
- if (!this.registry.isTracked(injected)) {
1406
+ if (this.tracksDiscovered() && !this.registry.isTracked(injected)) {
1395
1407
  this.trackEIP1193Provider(injected);
1396
1408
  }
1397
1409
  // Merge with existing providers instead of overwriting
@@ -1399,7 +1411,7 @@ var EvmEventTracker = /** @class */ (function () {
1399
1411
  return [2 /*return*/, this.registry.all];
1400
1412
  }
1401
1413
  // Re-check if the injected provider is already tracked just before tracking
1402
- if (!this.registry.isTracked(injected)) {
1414
+ if (this.tracksDiscovered() && !this.registry.isTracked(injected)) {
1403
1415
  this.trackEIP1193Provider(injected);
1404
1416
  }
1405
1417
  injectedProviderInfo = detectInjectedProviderInfo(injected);
@@ -1459,6 +1471,20 @@ var EvmEventTracker = /** @class */ (function () {
1459
1471
  });
1460
1472
  });
1461
1473
  };
1474
+ EvmEventTracker.prototype.detectableProviders = function () {
1475
+ var _a;
1476
+ var available = new Set();
1477
+ (_a = this.discoveryStore) === null || _a === void 0 ? void 0 : _a.getProviders().forEach(function (detail) {
1478
+ return available.add(detail.provider);
1479
+ });
1480
+ var injected = typeof window !== "undefined" ? window.ethereum : undefined;
1481
+ if (injected)
1482
+ available.add(injected);
1483
+ this.externallyRegistered.forEach(function (provider) { return available.add(provider); });
1484
+ return this.registry.all.filter(function (detail) {
1485
+ return available.has(detail.provider);
1486
+ });
1487
+ };
1462
1488
  /**
1463
1489
  * Seed a provider's chain from whatever it already exposes synchronously.
1464
1490
  *
@@ -28,6 +28,7 @@ export declare class EventQueue implements IEventQueue {
28
28
  private pendingFlush;
29
29
  private payloadHashes;
30
30
  private canSend?;
31
+ private generation;
31
32
  private closed;
32
33
  private disposePageLeave;
33
34
  constructor(writeKey: string, options: Options);
@@ -83,6 +83,7 @@ var EventQueue = /** @class */ (function () {
83
83
  this.queue = [];
84
84
  this.queueByteSize = 0; // running total of queued items' byteSize
85
85
  this.payloadHashes = new Set();
86
+ this.generation = 0;
86
87
  // Terminal shutdown flag. Once set, enqueue() and flush() are no-ops for
87
88
  // the rest of this instance's life. See close().
88
89
  this.closed = false;
@@ -216,6 +217,7 @@ var EventQueue = /** @class */ (function () {
216
217
  * withdrawal / SDK teardown so nothing buffered can be sent later.
217
218
  */
218
219
  EventQueue.prototype.clear = function () {
220
+ this.generation++;
219
221
  if (this.timer) {
220
222
  clearTimeout(this.timer);
221
223
  this.timer = null;
@@ -223,6 +225,8 @@ var EventQueue = /** @class */ (function () {
223
225
  this.queue = [];
224
226
  this.queueByteSize = 0;
225
227
  this.payloadHashes.clear();
228
+ this.flushed = false;
229
+ this.pendingFlush = null;
226
230
  };
227
231
  /**
228
232
  * Terminal shutdown. Unlike clear(), which only empties the buffer and can
@@ -259,11 +263,12 @@ var EventQueue = /** @class */ (function () {
259
263
  });
260
264
  EventQueue.prototype.enqueue = function (event, callback) {
261
265
  return __awaiter(this, void 0, void 0, function () {
262
- var message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
266
+ var generation, message_id, queueItem, hasReachedFlushAt, hasReachedQueueSize;
263
267
  return __generator(this, function (_a) {
264
268
  switch (_a.label) {
265
269
  case 0:
266
270
  callback = callback || noop;
271
+ generation = this.generation;
267
272
  // A torn-down instance must never buffer, however late the caller
268
273
  // arrives. See close().
269
274
  if (this.closed)
@@ -276,12 +281,12 @@ var EventQueue = /** @class */ (function () {
276
281
  return [4 /*yield*/, this.generateMessageId(event)];
277
282
  case 1:
278
283
  message_id = _a.sent();
279
- // Re-check after the await. A caller that entered before close() is
280
- // suspended here, and on a queue that has not flushed yet its event
281
- // would push and flush immediately - the exact shape of the bug close()
282
- // exists to stop.
283
- if (this.closed)
284
+ if (this.closed || generation !== this.generation)
284
285
  return [2 /*return*/];
286
+ if (this.canSend && !this.canSend()) {
287
+ this.clear();
288
+ return [2 /*return*/];
289
+ }
285
290
  // check if the message already exists
286
291
  if (this.isDuplicate(message_id)) {
287
292
  logger.warn("Event already enqueued, try again after ".concat(millisecondsToSecond(this.flushIntervalMs), " seconds."));
@@ -1,14 +1,27 @@
1
1
  /**
2
2
  * SolanaManager
3
3
  *
4
- * Manages the lifecycle of the Solana store integration.
5
- * Subscribes to framework-kit's zustand store for automatic event capture
6
- * of wallet connect/disconnect and transaction lifecycle events.
4
+ * Owns the two ways the SDK learns about Solana wallets:
7
5
  *
8
- * For signMessage/signTransaction tracking (not captured by the store),
6
+ * 1. `SolanaWalletStandardRegistry`: discovers wallets through the Wallet
7
+ * Standard and reports detect / connect / disconnect. On by default, so
8
+ * compatible wallets registered by Solana Kit, wallet-adapter,
9
+ * framework-kit, or another host are covered with no configuration,
10
+ * exactly like EVM wallets through EIP-6963. `solana: false` turns it off.
11
+ * 2. `SolanaStoreHandler`: subscribes to framework-kit's zustand store for
12
+ * connect / disconnect / cluster changes AND the transaction lifecycle.
13
+ * Opt-in through `solana: { store }` or `formo.solana.setStore()`.
14
+ *
15
+ * Both observe the same Wallet Standard connection when a framework-kit app
16
+ * connects. A store supplied at initialization owns wallet events; a store
17
+ * attached later takes ownership when it observes its first connection and
18
+ * adopts any connect the registry already reported. One connect per
19
+ * connection, whichever path an app is on.
20
+ *
21
+ * For signMessage/signTransaction tracking (not captured by either path),
9
22
  * use formo.signature() directly with the address and chainId.
10
23
  *
11
- * For manual event tracking without the store, use the core API directly:
24
+ * For manual event tracking, use the core API directly:
12
25
  * formo.transaction(), formo.signature(), formo.connect(), formo.disconnect().
13
26
  */
14
27
  import { FormoAnalytics } from "../FormoAnalytics";
@@ -16,9 +29,19 @@ import { SolanaCluster, SolanaOptions } from "./types";
16
29
  import { SolanaClientStore } from "./storeTypes";
17
30
  export declare class SolanaManager {
18
31
  private formo;
32
+ private readonly enabled;
19
33
  private storeHandler?;
34
+ private registry?;
20
35
  private pendingCluster?;
21
- constructor(formo: FormoAnalytics, options?: SolanaOptions);
36
+ private storeOwnsWalletEvents;
37
+ /**
38
+ * @param formo - The SDK instance events are reported to.
39
+ * @param options - `options.solana` as passed to the SDK, if an object.
40
+ * @param enabled - Whether Solana tracking is enabled. False only when the
41
+ * host app passed `solana: false`; both discovery and stores then stay off.
42
+ */
43
+ constructor(formo: FormoAnalytics, options?: SolanaOptions, enabled?: boolean);
44
+ private attachStore;
22
45
  /**
23
46
  * Set the framework-kit zustand store for automatic event tracking.
24
47
  * This enables autocapture mode — connect/disconnect and transaction events
@@ -39,11 +62,16 @@ export declare class SolanaManager {
39
62
  cluster?: SolanaCluster;
40
63
  }): void;
41
64
  /**
42
- * Update the cluster/network. Only needed if the store endpoint doesn't
43
- * contain a recognizable cluster name (e.g. custom RPC URLs).
44
- * In most cases, the cluster is auto-detected from the store's endpoint.
65
+ * Update the cluster/network.
66
+ *
67
+ * With a framework-kit store, only needed if the store endpoint doesn't
68
+ * contain a recognizable cluster name (e.g. custom RPC URLs). Without one,
69
+ * this is how a non-mainnet app tells the SDK which cluster its Wallet
70
+ * Standard connections are on, since the standard itself cannot say.
45
71
  */
46
72
  setCluster(cluster: SolanaCluster): void;
73
+ /** Names of the Wallet Standard wallets discovered so far. */
74
+ get discoveredWallets(): string[];
47
75
  cleanup(): void;
48
76
  }
49
77
  //# sourceMappingURL=SolanaManager.d.ts.map