@formo/analytics 1.33.1 → 1.34.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.
Files changed (45) hide show
  1. package/README.md +3 -0
  2. package/dist/cjs/src/FormoAnalytics.d.ts +65 -19
  3. package/dist/cjs/src/FormoAnalytics.js +188 -94
  4. package/dist/cjs/src/event/EventFactory.d.ts +1 -1
  5. package/dist/cjs/src/event/EventFactory.js +32 -16
  6. package/dist/cjs/src/event/sanitize.d.ts +13 -0
  7. package/dist/cjs/src/event/sanitize.js +94 -0
  8. package/dist/cjs/src/privy/index.d.ts +8 -2
  9. package/dist/cjs/src/privy/index.js +8 -2
  10. package/dist/cjs/src/privy/types.d.ts +25 -2
  11. package/dist/cjs/src/privy/utils.d.ts +100 -0
  12. package/dist/cjs/src/privy/utils.js +375 -16
  13. package/dist/cjs/src/session/index.d.ts +73 -6
  14. package/dist/cjs/src/session/index.js +309 -12
  15. package/dist/cjs/src/solana/SolanaManager.d.ts +1 -1
  16. package/dist/cjs/src/solana/SolanaManager.js +1 -1
  17. package/dist/cjs/src/solana/storeTypes.d.ts +1 -1
  18. package/dist/cjs/src/solana/storeTypes.js +1 -1
  19. package/dist/cjs/src/solana/types.d.ts +2 -2
  20. package/dist/cjs/src/types/base.d.ts +17 -1
  21. package/dist/cjs/src/version.d.ts +1 -1
  22. package/dist/cjs/src/version.js +1 -1
  23. package/dist/esm/src/FormoAnalytics.d.ts +65 -19
  24. package/dist/esm/src/FormoAnalytics.js +188 -94
  25. package/dist/esm/src/event/EventFactory.d.ts +1 -1
  26. package/dist/esm/src/event/EventFactory.js +32 -16
  27. package/dist/esm/src/event/sanitize.d.ts +13 -0
  28. package/dist/esm/src/event/sanitize.js +88 -0
  29. package/dist/esm/src/privy/index.d.ts +8 -2
  30. package/dist/esm/src/privy/index.js +8 -2
  31. package/dist/esm/src/privy/types.d.ts +25 -2
  32. package/dist/esm/src/privy/utils.d.ts +100 -0
  33. package/dist/esm/src/privy/utils.js +374 -16
  34. package/dist/esm/src/session/index.d.ts +73 -6
  35. package/dist/esm/src/session/index.js +309 -12
  36. package/dist/esm/src/solana/SolanaManager.d.ts +1 -1
  37. package/dist/esm/src/solana/SolanaManager.js +1 -1
  38. package/dist/esm/src/solana/storeTypes.d.ts +1 -1
  39. package/dist/esm/src/solana/storeTypes.js +1 -1
  40. package/dist/esm/src/solana/types.d.ts +2 -2
  41. package/dist/esm/src/types/base.d.ts +17 -1
  42. package/dist/esm/src/version.d.ts +1 -1
  43. package/dist/esm/src/version.js +1 -1
  44. package/dist/index.umd.min.js +1 -1
  45. package/package.json +7 -7
package/README.md CHANGED
@@ -29,6 +29,9 @@ You can install Formo on:
29
29
 
30
30
  Visit Formo's [Developer Docs](https://docs.formo.so) for detailed guides on local testing, debugging, and consent management.
31
31
 
32
+ Using [Privy](./docs/PRIVY_INTEGRATION.md)? `identify(user)`
33
+ clusters all of a Privy user's linked wallets under a single identity.
34
+
32
35
  ## Methodology
33
36
 
34
37
  Learn how Formo handles [onchain attribution](https://docs.formo.so/data/attribution) and [data collection](https://docs.formo.so/data/what-we-collect).
@@ -1,6 +1,7 @@
1
1
  import { EIP6963ProviderDetail } from "mipd";
2
2
  import { Address, ChainID, Config, EIP1193Provider, IFormoAnalytics, IFormoEventContext, IFormoEventProperties, Options, SignatureStatus, TransactionStatus } from "./types";
3
3
  import { SolanaManager } from "./solana/SolanaManager";
4
+ import type { PrivyUser } from "./privy";
4
5
  export declare class FormoAnalytics implements IFormoAnalytics {
5
6
  readonly writeKey: string;
6
7
  options: Options;
@@ -189,23 +190,50 @@ export declare class FormoAnalytics implements IFormoAnalytics {
189
190
  * // Basic identify
190
191
  * formo.identify({ address: '0x...', userId: 'user123' });
191
192
  *
192
- * // With Privy user
193
- * import { parsePrivyProperties } from '@formo/analytics';
193
+ * // Privy: pass the usePrivy() user to identify every linked wallet under
194
+ * // the user's DID in one call. Attribution stays on the already-connected
195
+ * // wallet when there is one, else Privy's primary (user.wallet); pass
196
+ * // `activeAddress` to pin a specific wallet.
194
197
  * const { user } = usePrivy();
195
- * if (user) {
196
- * const { properties, wallets } = parsePrivyProperties(user);
197
- * for (const wallet of wallets) {
198
- * formo.identify({ address: wallet.address, userId: user.id }, properties);
199
- * }
200
- * }
198
+ * if (user) formo.identify(user);
201
199
  * ```
202
200
  */
201
+ identify(user: PrivyUser, options?: {
202
+ activeAddress?: string;
203
+ properties?: IFormoEventProperties;
204
+ }): Promise<void>;
203
205
  identify(params?: {
204
206
  address: Address;
205
207
  providerName?: string;
206
208
  userId?: string;
207
209
  rdns?: string;
208
210
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
211
+ /**
212
+ * Reconcile currentChainId with a newly-activated Privy wallet's chain
213
+ * namespace. identify() sets currentAddress but never touches the chain id
214
+ * (that comes from connect()/chain()/wagmi), so activating e.g. a Solana
215
+ * wallet while an EVM chain id is current would leave the address paired with
216
+ * a mismatched chain in events, excludeChains, and the active-wallet cookie.
217
+ *
218
+ * We can't infer the wallet's specific chain id from Privy's chainType, so on
219
+ * a namespace mismatch we clear the chain id rather than assert a wrong one; a
220
+ * real wallet connect will set the correct chain. Same-namespace activations
221
+ * (and wallets whose namespace can't be determined) leave the chain id alone.
222
+ *
223
+ * Privy doesn't always supply `chainType`: a `smart_wallet` entry is
224
+ * `{ type, address, smartWalletType }`, and `cross_app` wallets are bare
225
+ * `{ address }`. A `0x`-prefixed 20-byte address is unambiguously EVM though,
226
+ * so fall back to the address shape - otherwise activating an EVM smart
227
+ * wallet while a Solana chain id is current would leave the address paired
228
+ * with the wrong chain, and an `excludeChains` gate could drop the identify
229
+ * after it was already dedup-marked.
230
+ *
231
+ * @internal Not part of the public IFormoAnalytics contract - invoked by
232
+ * `identifyPrivyUser` (via a structural cast) before it emits, so both the
233
+ * `identify(user,{privy:true})` and direct `identifyPrivyUser()` paths
234
+ * reconcile the chain.
235
+ */
236
+ syncPrivyActiveChain(chainType?: string, address?: string): void;
209
237
  /**
210
238
  * Emits a detect wallet event with current wallet provider info.
211
239
  * @param {string} params.providerName
@@ -286,20 +314,38 @@ export declare class FormoAnalytics implements IFormoAnalytics {
286
314
  * Visitor-level tracking suppression.
287
315
  *
288
316
  * Returns true when the SDK must not persist any identity/session/chain
289
- * state or send any events for this visitor i.e. an explicit opt-out or a
317
+ * state or send any events for this visitor - i.e. an explicit opt-out or a
290
318
  * jurisdiction/timezone exclusion. Public entry points that write state
291
319
  * before reaching the `shouldTrack()` event gate (identify/connect/detect)
292
320
  * check this first so suppressed visitors leave no cookies or session state.
293
321
  * @returns {boolean} True if all tracking and persistence must be suppressed
322
+ * @internal Also read by `identifyPrivyUser` (via a structural cast) so the
323
+ * Privy sync skips chain reconciliation and emission for suppressed visitors.
324
+ */
325
+ isTrackingSuppressed(): boolean;
326
+ /**
327
+ * Whether the current chain id is in `tracking.excludeChains`.
328
+ *
329
+ * Split out from `shouldTrack()` so `identify()` can check it *before*
330
+ * mutating identity state. `trackEvent()` drops an excluded event silently
331
+ * and returns void, but `identify()` marks the wallet as identified first, so
332
+ * without this guard an identify on an excluded chain is dedup-marked and
333
+ * then discarded, and the wallet never re-emits for the rest of the session
334
+ * even after switching to an allowed chain. On the Privy path that loses the
335
+ * user's whole cluster at once rather than a single wallet.
336
+ *
337
+ * Mirrors the chain rule in `shouldTrack()`: only applies when `tracking` is
338
+ * an options object with `excludeChains` set, and only once a chain id is
339
+ * known.
294
340
  */
295
- private isTrackingSuppressed;
341
+ private isCurrentChainExcluded;
296
342
  /**
297
- * Whether the current environment is excluded from tracking the visitor's
343
+ * Whether the current environment is excluded from tracking - the visitor's
298
344
  * timezone, the current hostname, or the current pathname matches a
299
345
  * configured exclusion.
300
346
  *
301
347
  * Timezone is visitor/session-level (stable for the session); host/path are
302
- * current-page-level and transient if a SPA navigates to an allowed path,
348
+ * current-page-level and transient - if a SPA navigates to an allowed path,
303
349
  * tracking resumes for future actions. Used as the "do not write identity or
304
350
  * send events" gate at every entry point that would persist state before the
305
351
  * `shouldTrack()` event gate.
@@ -308,19 +354,19 @@ export declare class FormoAnalytics implements IFormoAnalytics {
308
354
  private isCurrentEnvironmentExcluded;
309
355
  /**
310
356
  * Whether the current hostname matches a configured `tracking.excludeHosts`
311
- * entry (exact match). Current-page-level see isCurrentEnvironmentExcluded.
357
+ * entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
312
358
  * @returns {boolean} True if the current hostname is excluded
313
359
  */
314
360
  private isHostExcluded;
315
361
  /**
316
362
  * Whether the current pathname matches a configured `tracking.excludePaths`
317
- * entry (exact match). Current-page-level see isCurrentEnvironmentExcluded.
363
+ * entry (exact match). Current-page-level - see isCurrentEnvironmentExcluded.
318
364
  * @returns {boolean} True if the current pathname is excluded
319
365
  */
320
366
  private isPathExcluded;
321
367
  /**
322
- * Whether the current call is in a visitor-level suppression state opt-out
323
- * or excluded timezone for which any persisted identity cookie should be
368
+ * Whether the current call is in a visitor-level suppression state - opt-out
369
+ * or excluded timezone - for which any persisted identity cookie should be
324
370
  * actively purged (not merely skipped). Host/path exclusions are
325
371
  * deliberately excluded here: they are transient current-page states, so a
326
372
  * cookie legitimately written on an allowed page must survive a visit to an
@@ -331,7 +377,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
331
377
  /**
332
378
  * Whether the visitor's browser-resolved timezone matches a configured
333
379
  * `tracking.excludeTimezones` entry (case-insensitive). Client-side and
334
- * best-effort see TrackingOptions.excludeTimezones.
380
+ * best-effort - see TrackingOptions.excludeTimezones.
335
381
  * @returns {boolean} True if the current timezone is excluded
336
382
  */
337
383
  private isTimezoneExcluded;
@@ -445,7 +491,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
445
491
  * WITHOUT emitting an event.
446
492
  *
447
493
  * Integrations (e.g. the wagmi handler) must call this on every
448
- * connect / chain-change / disconnect even when the corresponding
494
+ * connect / chain-change / disconnect - even when the corresponding
449
495
  * autocapture event is disabled. Otherwise `currentChainId` stays
450
496
  * stale/undefined and `shouldTrack()`'s `tracking.excludeChains`
451
497
  * check (which keys off `currentChainId`, not the event payload) can
@@ -467,7 +513,7 @@ export declare class FormoAnalytics implements IFormoAnalytics {
467
513
  /**
468
514
  * Persist (or clear) the current wallet snapshot in a cookie so that the
469
515
  * SDK can repopulate `currentAddress`/`currentChainId` at init on the next
470
- * page load closing the gap between page-show and wagmi/EIP-1193
516
+ * page load - closing the gap between page-show and wagmi/EIP-1193
471
517
  * reconnection during which track()/page() events would otherwise ship
472
518
  * with an empty address.
473
519
  */