@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
@@ -18,6 +18,172 @@ var __assign = (this && this.__assign) || function () {
18
18
  import { cookie } from "../storage";
19
19
  import { getIdentityCookieSecurity } from "../storage/cookiePolicy";
20
20
  import { logger } from "../logger";
21
+ /**
22
+ * Serialize a value so that equal values always produce the same string,
23
+ * regardless of object key insertion order.
24
+ *
25
+ * `JSON.stringify` preserves insertion order, so `{a:1,b:2}` and `{b:2,a:1}`
26
+ * would hash differently and re-emit an identify that carries identical
27
+ * properties. Object keys are therefore sorted; arrays keep their order, since
28
+ * order is meaningful there.
29
+ *
30
+ * **This function must never throw.** It runs inside `identify()` *after* the
31
+ * active address/user have been updated, so a throw would leave the SDK with
32
+ * mutated identity state and no emitted event. `JSON.stringify` throws on
33
+ * circular references and on `BigInt` - both realistic here, since a web3 app
34
+ * can easily pass a token balance (`123n`) or a wallet object with a back
35
+ * reference in `properties`. Every such case is therefore handled explicitly
36
+ * and degrades to a stable marker instead of an exception.
37
+ *
38
+ * Totality here is only about *dedup*: it guarantees the fingerprint step can't
39
+ * be what loses an identify. It does **not** make such values sendable - the
40
+ * event queue still serializes with native `JSON.stringify`, so a `BigInt` or
41
+ * circular value in `properties` fails downstream exactly as it does for
42
+ * `track()`. That is a separate, pre-existing SDK-wide limitation.
43
+ *
44
+ * It mirrors `JSON.stringify`'s own value semantics so the fingerprint tracks
45
+ * **what actually goes on the wire**, not what happens to be in the object:
46
+ *
47
+ * - `undefined`, functions, and symbols are *omitted* as object properties and
48
+ * become `null` as array elements, exactly as JSON does. Two property sets
49
+ * that serialize identically must fingerprint identically, or dedup emits a
50
+ * second identify carrying a byte-identical payload.
51
+ * - `null` stays `null`, so a real value → `null` update is never mistaken for
52
+ * an omitted key.
53
+ * - `NaN`/`Infinity` become `null`, because that is what is sent.
54
+ * - `toJSON()` is honored, so `Date` and `URL` reflect their serialized form
55
+ * rather than their (often empty) enumerable keys.
56
+ * - `Map`/`Set` have no enumerable own properties and no `toJSON`, so they send
57
+ * as `{}` - and therefore canonicalize as `{}`.
58
+ *
59
+ * The exceptions are the two things JSON *cannot* represent: `BigInt` and
60
+ * circular references both make `JSON.stringify` throw. They get distinct
61
+ * markers instead, because this function must never throw - it runs inside
62
+ * `identify()` *after* the active address/user have been updated, so a throw
63
+ * would leave the SDK with mutated identity state and no emitted event.
64
+ *
65
+ * Totality here is only about *dedup*: it guarantees the fingerprint step can't
66
+ * be what loses an identify. It does **not** make such values sendable - the
67
+ * event queue still serializes with native `JSON.stringify`, so a `BigInt` or
68
+ * circular value in `properties` fails downstream exactly as it does for
69
+ * `track()`. That is a separate, pre-existing SDK-wide limitation.
70
+ *
71
+ * Returns `undefined` when JSON would omit the value entirely.
72
+ */
73
+ function stableStringify(value, seen) {
74
+ if (seen === void 0) { seen = new Set(); }
75
+ // JSON omits these as object properties; array elements are mapped to "null"
76
+ // by the caller below.
77
+ if (value === undefined)
78
+ return undefined;
79
+ if (typeof value === "function")
80
+ return undefined;
81
+ if (typeof value === "symbol")
82
+ return undefined;
83
+ if (value === null)
84
+ return "null";
85
+ // JSON.stringify throws on BigInt, so it has no wire form to mirror.
86
+ if (typeof value === "bigint")
87
+ return "bigint:".concat(value.toString());
88
+ if (typeof value === "number") {
89
+ // NaN and Infinity are sent as null.
90
+ return Number.isFinite(value) ? JSON.stringify(value) : "null";
91
+ }
92
+ if (typeof value !== "object") {
93
+ // Remaining primitives: string, boolean.
94
+ return JSON.stringify(value);
95
+ }
96
+ // Cycle guard: a repeated reference within the current path is replaced by a
97
+ // marker rather than recursing forever. JSON.stringify would throw here.
98
+ if (seen.has(value))
99
+ return '"[circular]"';
100
+ seen.add(value);
101
+ try {
102
+ // JSON calls toJSON() before inspecting the value, so do it first. This is
103
+ // what makes an invalid Date canonicalize as null (Date.prototype.toJSON
104
+ // returns null rather than throwing) and a URL reflect its href.
105
+ var maybeToJSON = value.toJSON;
106
+ if (typeof maybeToJSON === "function") {
107
+ return stableStringify(maybeToJSON.call(value), seen);
108
+ }
109
+ if (Array.isArray(value)) {
110
+ return "[".concat(value
111
+ .map(function (item) { var _a; return (_a = stableStringify(item, seen)) !== null && _a !== void 0 ? _a : "null"; })
112
+ .join(","), "]");
113
+ }
114
+ // Everything else, Map and Set included, serializes from its enumerable own
115
+ // properties. Keys are sorted so insertion order can't change the result.
116
+ var record = value;
117
+ var parts = [];
118
+ for (var _i = 0, _a = Object.keys(record).sort(); _i < _a.length; _i++) {
119
+ var key = _a[_i];
120
+ var encoded = stableStringify(record[key], seen);
121
+ if (encoded === undefined)
122
+ continue; // JSON omits this property
123
+ parts.push("".concat(JSON.stringify(key), ":").concat(encoded));
124
+ }
125
+ return "{".concat(parts.join(","), "}");
126
+ }
127
+ finally {
128
+ // Only guard against cycles, not repeated siblings: the same object used
129
+ // twice in one payload must serialize the same both times.
130
+ seen.delete(value);
131
+ }
132
+ }
133
+ /**
134
+ * Short, stable fingerprint of an identify's properties (two FNV-1a lanes,
135
+ * base36).
136
+ *
137
+ * Folded into the dedup key so that re-identifying a known wallet with *changed*
138
+ * properties re-emits, while an unchanged repeat still dedupes. Kept short
139
+ * because every key is stored in a size-bounded cookie.
140
+ *
141
+ * Two independent lanes (different offset bases, combined into ~64 bits) rather
142
+ * than one: a single 32-bit lane collides readily - `{"x":"xefn1fnkq0"}` and
143
+ * `{"x":"filot3n704"}` both hash to `1mgjpo5` - and a collision here silently
144
+ * suppresses a legitimately changed profile for the rest of the session. At 64
145
+ * bits that is no longer a practical concern, for six more characters per key.
146
+ */
147
+ function fingerprintProperties(properties) {
148
+ var _a;
149
+ if (!properties)
150
+ return undefined;
151
+ var serialized;
152
+ try {
153
+ // Canonicalize first, then decide emptiness from the result. Anything that
154
+ // serializes to `{}` - a literal `{}`, or an object whose every value JSON
155
+ // omits - carries no wire payload, so it keeps the legacy no-hash key shape
156
+ // rather than getting a hash of "{}". Doing this inside the guard matters:
157
+ // reading properties can run user code (a Proxy with a throwing ownKeys
158
+ // trap), and this whole function must be total.
159
+ var canonical = stableStringify(properties);
160
+ if (canonical === undefined || canonical === "{}")
161
+ return undefined;
162
+ serialized = canonical;
163
+ }
164
+ catch (error) {
165
+ // stableStringify handles every value type it knows about, but reading a
166
+ // property can still run arbitrary user code (a throwing getter, an exotic
167
+ // Proxy). Identity state has already been updated by the time we get here,
168
+ // so failing closed to a constant is the safe outcome: dedup degrades to
169
+ // the pre-fingerprint behavior (identify once per session for this wallet)
170
+ // instead of the whole identify being swallowed by the catch in identify().
171
+ (_a = logger.warn) === null || _a === void 0 ? void 0 : _a.call(logger, "Session: failed to fingerprint identify properties", error);
172
+ return "nohash";
173
+ }
174
+ var lane1 = 0x811c9dc5;
175
+ var lane2 = 0x01000193;
176
+ for (var i = 0; i < serialized.length; i++) {
177
+ var code = serialized.charCodeAt(i);
178
+ lane1 ^= code;
179
+ lane1 = Math.imul(lane1, 0x01000193);
180
+ // A second lane with a different seed and multiplier, fed the position as
181
+ // well as the character, so the two lanes don't move together.
182
+ lane2 ^= code + i;
183
+ lane2 = Math.imul(lane2, 0x85ebca6b);
184
+ }
185
+ return "".concat((lane1 >>> 0).toString(36)).concat((lane2 >>> 0).toString(36));
186
+ }
21
187
  /**
22
188
  * Cookie keys for session tracking
23
189
  * NOTE: These values must match the original constants in constants/base.ts
@@ -35,20 +201,130 @@ export var SESSION_WALLET_IDENTIFIED_KEY = "wallet-identified";
35
201
  * Session data expires at end of day (86400 seconds).
36
202
  */
37
203
  var MAX_SESSION_ENTRIES = 20;
204
+ /**
205
+ * Byte budget for the identified-wallet cookie, measured on the value as the
206
+ * browser actually stores it.
207
+ *
208
+ * A Privy user can identify far more than 20 wallets in one session (an 8+
209
+ * wallet user is the motivating case), so a fixed entry count would evict
210
+ * `(wallet, userId)` keys and let a later sync re-emit them. Instead we bound
211
+ * the store by serialized size and evict oldest only when it would overflow the
212
+ * cookie - so every identity that fits is retained.
213
+ *
214
+ * The budget must be applied to the **encoded** length. Key components are
215
+ * already percent-encoded, and `CookieStorage.set()` then encodes the whole
216
+ * joined value again, so `%3A` becomes `%253A` and each `,` separator becomes
217
+ * `%2C`. Measuring the raw string underestimates what is written: 37 realistic
218
+ * DID-bearing keys measure 3500 raw but 3956 encoded, and a non-ASCII external
219
+ * user id inflates far more than that. Overflowing makes the browser reject the
220
+ * write outright, so nothing is persisted and every identify re-emits for the
221
+ * rest of the session - the exact failure the store exists to prevent.
222
+ */
223
+ var MAX_COOKIE_BYTES = 4096;
224
+ /** Reserve for the cookie name plus path/expires/SameSite/Secure attributes. */
225
+ var COOKIE_OVERHEAD_RESERVE = 512;
226
+ var MAX_IDENTIFIED_ENCODED_BYTES = MAX_COOKIE_BYTES - COOKIE_OVERHEAD_RESERVE;
227
+ /** Length of a cookie value as written, i.e. after CookieStorage encodes it. */
228
+ function encodedCookieLength(value) {
229
+ return encodeURIComponent(value).length;
230
+ }
38
231
  var FormoAnalyticsSession = /** @class */ (function () {
39
232
  function FormoAnalyticsSession() {
40
233
  }
41
234
  /**
42
- * Generate a unique key for wallet identification tracking
43
- * Combines address and RDNS to track specific wallet-address combinations
235
+ * Generate a unique key for wallet identification tracking.
236
+ *
237
+ * Combines address, RDNS, and (optionally) the external user ID and a
238
+ * fingerprint of the identify's properties, so the key identifies a specific
239
+ * wallet-user-profile combination rather than just an address.
240
+ *
241
+ * Folding the user ID in means the same wallet identified first anonymously
242
+ * and later with a user ID (e.g. after a Privy login attaches a DID) produces
243
+ * two distinct keys, so the second identify is not deduped.
244
+ *
245
+ * Folding the properties hash in means a *changed* profile re-emits. This
246
+ * matters for account linking: a Privy user who links a Google account keeps
247
+ * the same wallets and the same DID, so without the hash every already-seen
248
+ * wallet would dedupe and the new `google` property would never reach Formo
249
+ * until the session expired. An identify repeated with identical properties
250
+ * still dedupes, so this does not turn a re-render into an event.
251
+ *
252
+ * Key shapes, by component count - each is unambiguous, so they cannot
253
+ * collide with one another:
254
+ *
255
+ * | Components | Shape | When |
256
+ * | --- | --- | --- |
257
+ * | 1 | `address` | no rdns, no userId, no properties |
258
+ * | 2 | `address:rdns` | rdns only |
259
+ * | 3 | `address:rdns:userId` | userId, no properties |
260
+ * | 4 | `address:rdns:userId:hash` | properties present |
261
+ *
262
+ * Shapes 1 and 2 are unchanged from before user IDs and property hashes
263
+ * existed, so keys already stored in browsers still match (backward
264
+ * compatible). An identify that carries properties moves to shape 4, so the
265
+ * first identify after an upgrade re-emits once per wallet - a one-off, and
266
+ * the correct outcome, since those properties were never recorded under the
267
+ * new key.
44
268
  *
45
269
  * @param address The wallet address
46
270
  * @param rdns The reverse domain name of the wallet provider
271
+ * @param userId Optional external user ID (e.g. a Privy DID)
272
+ * @param properties Optional identify properties, fingerprinted into the key
47
273
  * @returns A unique identification key
48
274
  */
49
- FormoAnalyticsSession.prototype.generateIdentificationKey = function (address, rdns) {
50
- // If rdns is missing, use address-only key as fallback for empty identifies
51
- return rdns ? "".concat(address, ":").concat(rdns) : address;
275
+ FormoAnalyticsSession.prototype.generateIdentificationKey = function (address, rdns, userId, properties) {
276
+ return this.buildIdentificationKey(address, rdns, userId, properties).key;
277
+ };
278
+ /**
279
+ * Build the dedup key plus the **identity prefix** it belongs to.
280
+ *
281
+ * The identity prefix is the key with the properties hash stripped -
282
+ * `address:rdns:userId` - i.e. *which wallet-user this is*, independent of
283
+ * *what profile it last had*. `markWalletIdentified` uses it to drop that
284
+ * identity's previous state before storing the new one, which matters twice:
285
+ *
286
+ * - **Reversion.** Keeping every state seen would make dedup mean "have I
287
+ * ever seen this exact profile", so a profile that goes A → B → A (link
288
+ * then unlink an account) would find the old A key and emit nothing. Dedup
289
+ * should mean "is this the same as this wallet's *last* identify", so only
290
+ * the current state is retained.
291
+ * - **Growth.** Otherwise each profile change adds a key per wallet, and an
292
+ * 8-wallet user linking a few accounts would push the cookie into eviction.
293
+ * Superseding keeps it at one entry per wallet-user.
294
+ *
295
+ * The prefix is only defined for keys that have a userId and/or a properties
296
+ * hash (3+ components). The legacy 1- and 2-component shapes are the whole
297
+ * identity already, so there is nothing to supersede.
298
+ */
299
+ FormoAnalyticsSession.prototype.buildIdentificationKey = function (address, rdns, userId, properties) {
300
+ // Percent-encode each component before joining. The identified-wallet list
301
+ // is persisted comma-joined in a cookie and later split on commas, so a raw
302
+ // comma in an arbitrary external userId would corrupt the key and defeat
303
+ // dedup (the same identify would re-emit on every call). Encoding also keeps
304
+ // the ":" separator unambiguous. Addresses and RDNS contain no reserved
305
+ // characters, so their encoded form is unchanged - existing stored keys
306
+ // still match (backward compatible).
307
+ // An identify with no properties keeps the pre-hash key shape, so the
308
+ // common `identify({ address })` call is unaffected.
309
+ var propertiesHash = fingerprintProperties(properties);
310
+ var parts = [encodeURIComponent(address)];
311
+ if (userId || propertiesHash) {
312
+ // Once any later slot is set, always emit the intervening slots (even when
313
+ // empty) so the tuple has a fixed shape. Otherwise a userId that happens to
314
+ // equal a provider RDNS (e.g. "io.metamask") would produce the same key as
315
+ // an anonymous `address:rdns` identify and be wrongly deduped. userId and
316
+ // hash keys are new, so this shape has no backward-compat cost.
317
+ parts.push(encodeURIComponent(rdns || ""));
318
+ parts.push(encodeURIComponent(userId || ""));
319
+ var identityPrefix = parts.join(":");
320
+ if (propertiesHash)
321
+ parts.push(propertiesHash);
322
+ return { key: parts.join(":"), identityPrefix: identityPrefix };
323
+ }
324
+ if (rdns) {
325
+ parts.push(encodeURIComponent(rdns));
326
+ }
327
+ return { key: parts.join(":") };
52
328
  };
53
329
  /**
54
330
  * Check if a wallet provider has been detected in this session
@@ -87,8 +363,8 @@ var FormoAnalyticsSession = /** @class */ (function () {
87
363
  * @param rdns The reverse domain name of the wallet provider
88
364
  * @returns true if this wallet-address pair has been identified
89
365
  */
90
- FormoAnalyticsSession.prototype.isWalletIdentified = function (address, rdns) {
91
- var identifiedKey = this.generateIdentificationKey(address, rdns);
366
+ FormoAnalyticsSession.prototype.isWalletIdentified = function (address, rdns, userId, properties) {
367
+ var identifiedKey = this.generateIdentificationKey(address, rdns, userId, properties);
92
368
  var cookieValue = cookie().get(SESSION_WALLET_IDENTIFIED_KEY);
93
369
  var identifiedWallets = (cookieValue === null || cookieValue === void 0 ? void 0 : cookieValue.split(",")) || [];
94
370
  var isIdentified = identifiedWallets.includes(identifiedKey);
@@ -106,17 +382,38 @@ var FormoAnalyticsSession = /** @class */ (function () {
106
382
  * @param address The wallet address
107
383
  * @param rdns The reverse domain name of the wallet provider
108
384
  */
109
- FormoAnalyticsSession.prototype.markWalletIdentified = function (address, rdns) {
385
+ FormoAnalyticsSession.prototype.markWalletIdentified = function (address, rdns, userId, properties) {
110
386
  var _a;
111
- var identifiedKey = this.generateIdentificationKey(address, rdns);
387
+ var _b = this.buildIdentificationKey(address, rdns, userId, properties), identifiedKey = _b.key, identityPrefix = _b.identityPrefix;
112
388
  var identifiedWallets = ((_a = cookie().get(SESSION_WALLET_IDENTIFIED_KEY)) === null || _a === void 0 ? void 0 : _a.split(",")) || [];
113
389
  var alreadyExists = identifiedWallets.includes(identifiedKey);
114
390
  if (!alreadyExists) {
115
- identifiedWallets.push(identifiedKey);
116
- if (identifiedWallets.length > MAX_SESSION_ENTRIES) {
117
- identifiedWallets.splice(0, identifiedWallets.length - MAX_SESSION_ENTRIES);
391
+ // Supersede this wallet-user's previous profile state rather than
392
+ // accumulating one key per state. Without this, a profile that reverts to
393
+ // an earlier value (link then unlink an account) would match the stale key
394
+ // and emit nothing, and every profile change would grow the cookie.
395
+ if (identityPrefix) {
396
+ identifiedWallets = identifiedWallets.filter(function (entry) {
397
+ return entry !== identityPrefix &&
398
+ !entry.startsWith("".concat(identityPrefix, ":"));
399
+ });
118
400
  }
401
+ identifiedWallets.push(identifiedKey);
402
+ // Bound the stored list by serialized size (not a fixed entry count) so a
403
+ // many-wallet Privy user's identities all persist, evicting oldest only if
404
+ // the value would overflow the cookie.
405
+ //
406
+ // `shift()` drops the oldest from the front while the new key was pushed
407
+ // to the back, and the loop stops at one entry, so the just-added key can
408
+ // never be evicted. A single key is bounded by its components (address +
409
+ // rdns + external user id + a 13-char hash) and cannot on its own approach
410
+ // the budget, so there is no oversized-single-entry case to handle.
119
411
  var newValue = identifiedWallets.join(",");
412
+ while (identifiedWallets.length > 1 &&
413
+ encodedCookieLength(newValue) > MAX_IDENTIFIED_ENCODED_BYTES) {
414
+ identifiedWallets.shift();
415
+ newValue = identifiedWallets.join(",");
416
+ }
120
417
  cookie().set(SESSION_WALLET_IDENTIFIED_KEY, newValue, __assign({
121
418
  // Expires by the end of the day
122
419
  expires: new Date(Date.now() + 86400 * 1000).toUTCString(), path: "/" }, getIdentityCookieSecurity()));
@@ -29,7 +29,7 @@ export declare class SolanaManager {
29
29
  *
30
30
  * @example
31
31
  * ```tsx
32
- * import { createClient } from '@solana-foundation/framework-kit';
32
+ * import { createClient, autoDiscover } from '@solana/client';
33
33
  *
34
34
  * const client = createClient({ endpoint: '...', walletConnectors: autoDiscover() });
35
35
  * formo.solana.setStore(client.store);
@@ -37,7 +37,7 @@ var SolanaManager = /** @class */ (function () {
37
37
  *
38
38
  * @example
39
39
  * ```tsx
40
- * import { createClient } from '@solana-foundation/framework-kit';
40
+ * import { createClient, autoDiscover } from '@solana/client';
41
41
  *
42
42
  * const client = createClient({ endpoint: '...', walletConnectors: autoDiscover() });
43
43
  * formo.solana.setStore(client.store);
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Type definitions for framework-kit's zustand store integration.
3
3
  *
4
- * These types mirror the state shape of @solana-foundation/framework-kit's
4
+ * These types mirror the state shape of framework-kit's
5
5
  * vanilla zustand store, allowing the SDK to subscribe to wallet and
6
6
  * transaction state changes without wrapping any wallet methods.
7
7
  *
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Type definitions for framework-kit's zustand store integration.
3
3
  *
4
- * These types mirror the state shape of @solana-foundation/framework-kit's
4
+ * These types mirror the state shape of framework-kit's
5
5
  * vanilla zustand store, allowing the SDK to subscribe to wallet and
6
6
  * transaction state changes without wrapping any wallet methods.
7
7
  *
@@ -54,11 +54,11 @@ export interface SolanaOptions {
54
54
  * When provided, wallet connect/disconnect and transaction events are tracked
55
55
  * automatically by subscribing to zustand store state changes.
56
56
  *
57
- * This is the recommended approach for apps using @solana-foundation/framework-kit.
57
+ * This is the recommended approach for apps using framework-kit.
58
58
  *
59
59
  * @example
60
60
  * ```tsx
61
- * import { createClient } from '@solana-foundation/framework-kit';
61
+ * import { createClient, autoDiscover } from '@solana/client';
62
62
  * const client = createClient({ endpoint, walletConnectors: autoDiscover() });
63
63
  * const formo = await Formo.init(writeKey, { solana: { store: client.store } });
64
64
  * ```
@@ -2,6 +2,7 @@ import { LogLevel } from "../logger";
2
2
  import { IFormoEventContext, IFormoEventProperties, SignatureStatus, TransactionStatus } from "./events";
3
3
  import { EIP1193Provider } from "./provider";
4
4
  import { SolanaOptions } from "../solana/types";
5
+ import type { PrivyUser } from "../privy/types";
5
6
  export type Nullable<T> = T | null;
6
7
  export type ChainID = number;
7
8
  export type Address = string;
@@ -51,6 +52,21 @@ export interface IFormoAnalytics {
51
52
  function_name?: string;
52
53
  function_args?: Record<string, unknown>;
53
54
  }, properties?: IFormoEventProperties, context?: IFormoEventContext, callback?: (...args: unknown[]) => void): Promise<void>;
55
+ /**
56
+ * Privy form. Pass the `usePrivy()` user directly and the SDK identifies
57
+ * every wallet linked to that Privy account under the user's DID in a single
58
+ * call, forwarding each wallet's metadata. Only the active wallet (explicit
59
+ * `activeAddress`, else the already-connected wallet, else Privy's
60
+ * `user.wallet`) takes over event attribution - the rest are recorded purely
61
+ * for identity clustering.
62
+ *
63
+ * Recognized by shape: a Privy user has a string `id` and no `address`,
64
+ * while an address-keyed identify always has an `address`.
65
+ */
66
+ identify(user: PrivyUser, options?: {
67
+ activeAddress?: string;
68
+ properties?: IFormoEventProperties;
69
+ }): Promise<void>;
54
70
  identify(params: {
55
71
  address: Address;
56
72
  providerName?: string;
@@ -75,7 +91,7 @@ export interface TrackingOptions {
75
91
  /**
76
92
  * IANA timezone names to opt out of tracking entirely. When the visitor's
77
93
  * resolved timezone (via `Intl.DateTimeFormat().resolvedOptions().timeZone`)
78
- * matches one of these, no events are enqueued or sent including `identify`
94
+ * matches one of these, no events are enqueued or sent - including `identify`
79
95
  * and `connect`. Matched case-insensitively against the full timezone string.
80
96
  *
81
97
  * Note: this is client-side, timezone-derived geolocation. It is best-effort
@@ -1,2 +1,2 @@
1
- export declare const version = "1.30.1";
1
+ export declare const version = "1.34.1";
2
2
  //# sourceMappingURL=version.d.ts.map
@@ -1,4 +1,4 @@
1
1
  // This file is auto-generated by scripts/update-version.js during npm version
2
2
  // Do not edit manually - it will be overwritten
3
- export var version = '1.30.1';
3
+ export var version = '1.34.1';
4
4
  //# sourceMappingURL=version.js.map