@formo/analytics 1.35.2 → 1.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/src/FormoAnalytics.d.ts +79 -363
- package/dist/cjs/src/FormoAnalytics.js +368 -2234
- package/dist/cjs/src/event/EventManager.d.ts +3 -1
- package/dist/cjs/src/event/EventManager.js +5 -1
- package/dist/cjs/src/event/type.d.ts +1 -0
- package/dist/cjs/src/evm/EvmEventTracker.d.ts +175 -0
- package/dist/cjs/src/evm/EvmEventTracker.js +1030 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.d.ts +132 -0
- package/dist/cjs/src/evm/EvmProviderRegistry.js +348 -0
- package/dist/cjs/src/evm/EvmRequestTracker.d.ts +120 -0
- package/dist/cjs/src/evm/EvmRequestTracker.js +756 -0
- package/dist/cjs/src/queue/EventQueue.d.ts +28 -0
- package/dist/cjs/src/queue/EventQueue.js +97 -19
- package/dist/cjs/src/queue/type.d.ts +1 -0
- package/dist/cjs/src/tracking/TrackingPolicy.d.ts +146 -0
- package/dist/cjs/src/tracking/TrackingPolicy.js +200 -0
- package/dist/cjs/src/validators/address.d.ts +1 -1
- package/dist/cjs/src/version.d.ts +1 -1
- package/dist/cjs/src/version.js +1 -1
- package/dist/cjs/src/wallet/WalletStateStore.d.ts +223 -0
- package/dist/cjs/src/wallet/WalletStateStore.js +515 -0
- package/dist/esm/src/FormoAnalytics.d.ts +79 -363
- package/dist/esm/src/FormoAnalytics.js +369 -2235
- package/dist/esm/src/event/EventManager.d.ts +3 -1
- package/dist/esm/src/event/EventManager.js +5 -1
- package/dist/esm/src/event/type.d.ts +1 -0
- package/dist/esm/src/evm/EvmEventTracker.d.ts +175 -0
- package/dist/esm/src/evm/EvmEventTracker.js +1027 -0
- package/dist/esm/src/evm/EvmProviderRegistry.d.ts +132 -0
- package/dist/esm/src/evm/EvmProviderRegistry.js +345 -0
- package/dist/esm/src/evm/EvmRequestTracker.d.ts +120 -0
- package/dist/esm/src/evm/EvmRequestTracker.js +753 -0
- package/dist/esm/src/queue/EventQueue.d.ts +28 -0
- package/dist/esm/src/queue/EventQueue.js +97 -19
- package/dist/esm/src/queue/type.d.ts +1 -0
- package/dist/esm/src/tracking/TrackingPolicy.d.ts +146 -0
- package/dist/esm/src/tracking/TrackingPolicy.js +197 -0
- package/dist/esm/src/version.d.ts +1 -1
- package/dist/esm/src/version.js +1 -1
- package/dist/esm/src/wallet/WalletStateStore.d.ts +223 -0
- package/dist/esm/src/wallet/WalletStateStore.js +512 -0
- package/dist/index.umd.min.js +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
var __assign = (this && this.__assign) || function () {
|
|
2
|
+
__assign = Object.assign || function(t) {
|
|
3
|
+
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
4
|
+
s = arguments[i];
|
|
5
|
+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
6
|
+
t[p] = s[p];
|
|
7
|
+
}
|
|
8
|
+
return t;
|
|
9
|
+
};
|
|
10
|
+
return __assign.apply(this, arguments);
|
|
11
|
+
};
|
|
12
|
+
import { ACTIVE_WALLET_KEY, ACTIVE_WALLET_TTL_MS } from "../constants";
|
|
13
|
+
import { cookie } from "../storage";
|
|
14
|
+
import { getIdentityCookieDomain, getIdentityCookieSecurity, } from "../storage/cookiePolicy";
|
|
15
|
+
import { logger } from "../logger";
|
|
16
|
+
import { validateAddress, validateAndChecksumAddress } from "../utils/address";
|
|
17
|
+
import { isSolanaChainId } from "../solana";
|
|
18
|
+
/**
|
|
19
|
+
* Wallet identity and chain state, and the cookie that outlives the page.
|
|
20
|
+
*
|
|
21
|
+
* Split out of `FormoAnalytics` so there is one owner for "which wallet is
|
|
22
|
+
* active, on which chain". Before this, `_chainState`, the derived
|
|
23
|
+
* `currentAddress`/`currentChainId`, the active-wallet cookie and the session
|
|
24
|
+
* generation were manipulated from a dozen places across a 3400-line class,
|
|
25
|
+
* which is what made the ordering races in #341 possible to write.
|
|
26
|
+
*/
|
|
27
|
+
var WalletStateStore = /** @class */ (function () {
|
|
28
|
+
function WalletStateStore(deps) {
|
|
29
|
+
this.deps = deps;
|
|
30
|
+
this.state = {
|
|
31
|
+
evm: {},
|
|
32
|
+
solana: {},
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* Ticket counter for wallet observations.
|
|
36
|
+
*
|
|
37
|
+
* Every signal from a wallet is handled asynchronously: resolving an
|
|
38
|
+
* address, emitting an event. Between the moment a handler decides what to
|
|
39
|
+
* do and the moment it commits, a newer signal can arrive and be fully
|
|
40
|
+
* processed. Whichever handler resumes last then writes its captured data
|
|
41
|
+
* over the newer state.
|
|
42
|
+
*
|
|
43
|
+
* Bespoke guards were added for each case as it was found - a per-provider
|
|
44
|
+
* disconnect count, a per-namespace session generation, a "currently
|
|
45
|
+
* processing" flag - and a fourth review round kept producing new ones,
|
|
46
|
+
* because each guard answers one question and none establishes an order.
|
|
47
|
+
*
|
|
48
|
+
* A ticket does. A handler takes one before its first await; anything it
|
|
49
|
+
* commits afterwards is refused if a newer observation has claimed the
|
|
50
|
+
* namespace since.
|
|
51
|
+
*/
|
|
52
|
+
this.observationSeq = 0;
|
|
53
|
+
this.newestObservation = {
|
|
54
|
+
evm: 0,
|
|
55
|
+
solana: 0,
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* How many disconnects a namespace has begun.
|
|
59
|
+
*
|
|
60
|
+
* Separate from the observation ticket on purpose, because they answer
|
|
61
|
+
* different questions. A ticket asks "is the state I captured still the
|
|
62
|
+
* newest?", which is what a switch or a probe needs. This asks "did the
|
|
63
|
+
* wallet go away after I started?", which is what a connect handler needs.
|
|
64
|
+
*
|
|
65
|
+
* Conflating them loses connects: a connect observation superseded by a
|
|
66
|
+
* NEWER connect must still be reported by somebody, and the ticket cannot
|
|
67
|
+
* tell that apart from being superseded by a disconnect.
|
|
68
|
+
*/
|
|
69
|
+
this.disconnectCount = {
|
|
70
|
+
evm: 0,
|
|
71
|
+
solana: 0,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
Object.defineProperty(WalletStateStore.prototype, "activeNamespace", {
|
|
75
|
+
/** Which namespace currently owns the derived values, if any. */
|
|
76
|
+
get: function () {
|
|
77
|
+
return this._activeNamespace;
|
|
78
|
+
},
|
|
79
|
+
enumerable: false,
|
|
80
|
+
configurable: true
|
|
81
|
+
});
|
|
82
|
+
// ── reads ────────────────────────────────────────────────────────────────
|
|
83
|
+
/** Which namespace a chain id belongs to. */
|
|
84
|
+
WalletStateStore.prototype.namespaceOf = function (chainId) {
|
|
85
|
+
return isSolanaChainId(chainId) ? "solana" : "evm";
|
|
86
|
+
};
|
|
87
|
+
Object.defineProperty(WalletStateStore.prototype, "evmAddress", {
|
|
88
|
+
get: function () {
|
|
89
|
+
return this.state.evm.address;
|
|
90
|
+
},
|
|
91
|
+
enumerable: false,
|
|
92
|
+
configurable: true
|
|
93
|
+
});
|
|
94
|
+
Object.defineProperty(WalletStateStore.prototype, "evmChainId", {
|
|
95
|
+
get: function () {
|
|
96
|
+
return this.state.evm.chainId;
|
|
97
|
+
},
|
|
98
|
+
enumerable: false,
|
|
99
|
+
configurable: true
|
|
100
|
+
});
|
|
101
|
+
Object.defineProperty(WalletStateStore.prototype, "provider", {
|
|
102
|
+
get: function () {
|
|
103
|
+
return this.state.evm.provider;
|
|
104
|
+
},
|
|
105
|
+
// ── writes ───────────────────────────────────────────────────────────────
|
|
106
|
+
set: function (next) {
|
|
107
|
+
this.displaceProvider(this.state.evm.provider, next);
|
|
108
|
+
this.state.evm.provider = next;
|
|
109
|
+
},
|
|
110
|
+
enumerable: false,
|
|
111
|
+
configurable: true
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* Take a ticket for an observation about to be processed.
|
|
115
|
+
*
|
|
116
|
+
* MUST be called before the handler's first await, so the order recorded is
|
|
117
|
+
* the order the signals arrived in, not the order their async work happens
|
|
118
|
+
* to finish in.
|
|
119
|
+
*/
|
|
120
|
+
WalletStateStore.prototype.observe = function (namespace) {
|
|
121
|
+
var id = ++this.observationSeq;
|
|
122
|
+
this.newestObservation[namespace] = id;
|
|
123
|
+
return { id: id, namespace: namespace };
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Record that a namespace's wallet is being torn down.
|
|
127
|
+
*
|
|
128
|
+
* Called at EVERY teardown site before anything is awaited, and whether or
|
|
129
|
+
* not a disconnect event will be emitted: whether the app opted into
|
|
130
|
+
* disconnect autocapture has no bearing on ordering.
|
|
131
|
+
*/
|
|
132
|
+
WalletStateStore.prototype.beginDisconnect = function (namespace) {
|
|
133
|
+
this.disconnectCount[namespace]++;
|
|
134
|
+
};
|
|
135
|
+
/** How many disconnects this namespace has begun so far. */
|
|
136
|
+
WalletStateStore.prototype.disconnectsSoFar = function (namespace) {
|
|
137
|
+
return this.disconnectCount[namespace];
|
|
138
|
+
};
|
|
139
|
+
/**
|
|
140
|
+
* The newest observation for a namespace, for a caller that must NOT
|
|
141
|
+
* supersede its own caller.
|
|
142
|
+
*
|
|
143
|
+
* `disconnect()` is reached both directly by a consumer and from a handler
|
|
144
|
+
* that already holds a ticket. Taking a fresh ticket there would invalidate
|
|
145
|
+
* the handler that called it. Reading the current value and checking it
|
|
146
|
+
* later asks the same question without changing the answer for anyone else.
|
|
147
|
+
*/
|
|
148
|
+
WalletStateStore.prototype.snapshot = function (namespace) {
|
|
149
|
+
return this.newestObservation[namespace];
|
|
150
|
+
};
|
|
151
|
+
/** Whether nothing newer has claimed the namespace since `snapshot`. */
|
|
152
|
+
WalletStateStore.prototype.isUnchangedSince = function (namespace, snapshot) {
|
|
153
|
+
return this.newestObservation[namespace] === snapshot;
|
|
154
|
+
};
|
|
155
|
+
/**
|
|
156
|
+
* The current newest observation, as a ticket, WITHOUT taking a new one.
|
|
157
|
+
*
|
|
158
|
+
* For a handler that must respect the order but has nothing to claim: it
|
|
159
|
+
* still abandons its work if something newer arrives, but it does not
|
|
160
|
+
* supersede whatever is already in flight.
|
|
161
|
+
*/
|
|
162
|
+
WalletStateStore.prototype.currentObservation = function (namespace) {
|
|
163
|
+
return { id: this.newestObservation[namespace], namespace: namespace };
|
|
164
|
+
};
|
|
165
|
+
/**
|
|
166
|
+
* Whether this observation is still the newest for its namespace.
|
|
167
|
+
*
|
|
168
|
+
* False means a newer signal arrived while this handler was suspended, and
|
|
169
|
+
* it must abandon whatever it captured rather than write it over the newer
|
|
170
|
+
* state. Superseding, not dropping: the newer handler is already running.
|
|
171
|
+
*/
|
|
172
|
+
WalletStateStore.prototype.isCurrent = function (observation) {
|
|
173
|
+
return this.newestObservation[observation.namespace] === observation.id;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Update a namespace and re-derive `address`/`chainId`.
|
|
177
|
+
*
|
|
178
|
+
* Accepts a namespace or a chain id. A chain id is also stored as the
|
|
179
|
+
* namespace's chain unless the update names one explicitly.
|
|
180
|
+
*/
|
|
181
|
+
WalletStateStore.prototype.set = function (namespaceOrChainId, update) {
|
|
182
|
+
var namespace = typeof namespaceOrChainId === "string"
|
|
183
|
+
? namespaceOrChainId
|
|
184
|
+
: this.namespaceOf(namespaceOrChainId);
|
|
185
|
+
var ns = this.state[namespace];
|
|
186
|
+
// A plain write. Whether this transition is still the newest is decided
|
|
187
|
+
// by the observation ticket its caller holds, not by comparing addresses
|
|
188
|
+
// here: re-adopting the SAME wallet is a real transition, and no
|
|
189
|
+
// comparison of the values can tell that apart from nothing happening.
|
|
190
|
+
if ("address" in update)
|
|
191
|
+
ns.address = update.address;
|
|
192
|
+
if ("chainId" in update) {
|
|
193
|
+
ns.chainId = update.chainId;
|
|
194
|
+
}
|
|
195
|
+
else if (typeof namespaceOrChainId === "number") {
|
|
196
|
+
ns.chainId = namespaceOrChainId;
|
|
197
|
+
}
|
|
198
|
+
if (namespace === "evm" && "provider" in update) {
|
|
199
|
+
this.displaceProvider(ns.provider, update.provider);
|
|
200
|
+
ns.provider = update.provider;
|
|
201
|
+
}
|
|
202
|
+
this._activeNamespace = namespace;
|
|
203
|
+
this.syncDerived();
|
|
204
|
+
};
|
|
205
|
+
/** Wipe a namespace. Per-namespace so a Solana disconnect spares EVM. */
|
|
206
|
+
WalletStateStore.prototype.clear = function (namespaceOrChainId) {
|
|
207
|
+
var namespace = typeof namespaceOrChainId === "string"
|
|
208
|
+
? namespaceOrChainId
|
|
209
|
+
: this.namespaceOf(namespaceOrChainId);
|
|
210
|
+
if (namespace === "evm") {
|
|
211
|
+
// Wiping the namespace drops the active provider with it.
|
|
212
|
+
this.displaceProvider(this.state.evm.provider, undefined);
|
|
213
|
+
this.state.evm = {};
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
this.state.solana = {};
|
|
217
|
+
}
|
|
218
|
+
this.syncDerived();
|
|
219
|
+
};
|
|
220
|
+
/** Both namespaces, keeping the EVM provider so tracking can resume. */
|
|
221
|
+
WalletStateStore.prototype.reset = function () {
|
|
222
|
+
var evmProvider = this.state.evm.provider;
|
|
223
|
+
this.state = { evm: { provider: evmProvider }, solana: {} };
|
|
224
|
+
this._activeNamespace = undefined;
|
|
225
|
+
this.address = undefined;
|
|
226
|
+
this.chainId = undefined;
|
|
227
|
+
};
|
|
228
|
+
/** Drop the active provider without touching identity. */
|
|
229
|
+
WalletStateStore.prototype.clearProvider = function () {
|
|
230
|
+
this.provider = undefined;
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Repoint the active identity at a wallet, as `identify(setActive)` does,
|
|
234
|
+
* without disturbing per-namespace chain state.
|
|
235
|
+
*
|
|
236
|
+
* Deliberately transient. The next wallet event for either namespace
|
|
237
|
+
* re-derives from namespace state and replaces this, which is the intended
|
|
238
|
+
* order: a wallet that actually connects outranks one merely named by
|
|
239
|
+
* `identify()`. The alternative, making an identify sticky until another
|
|
240
|
+
* identify, would silently mis-attribute every event after a connect.
|
|
241
|
+
*/
|
|
242
|
+
WalletStateStore.prototype.setActiveAddress = function (address) {
|
|
243
|
+
this.address = address;
|
|
244
|
+
this.persist();
|
|
245
|
+
};
|
|
246
|
+
/** Repoint (or forget) the derived chain while keeping the wallet. */
|
|
247
|
+
WalletStateStore.prototype.setActiveChainId = function (chainId) {
|
|
248
|
+
this.chainId = chainId;
|
|
249
|
+
this.persist();
|
|
250
|
+
};
|
|
251
|
+
/** Forget the derived chain while keeping the wallet. */
|
|
252
|
+
WalletStateStore.prototype.clearActiveChainId = function () {
|
|
253
|
+
this.setActiveChainId(undefined);
|
|
254
|
+
};
|
|
255
|
+
// ── higher-level operations ──────────────────────────────────────────────
|
|
256
|
+
/**
|
|
257
|
+
* Record validated wallet/chain state WITHOUT emitting an event.
|
|
258
|
+
*
|
|
259
|
+
* Integrations (the wagmi handler among them) must call this on every
|
|
260
|
+
* connect / chain change / disconnect, even when the matching autocapture
|
|
261
|
+
* event is disabled. Otherwise `chainId` stays stale and the exclusion gate,
|
|
262
|
+
* which keys off it rather than the event payload, can be bypassed.
|
|
263
|
+
*/
|
|
264
|
+
WalletStateStore.prototype.syncWalletState = function (params) {
|
|
265
|
+
var chainId = params.chainId, address = params.address;
|
|
266
|
+
// An integration adopting or dropping a wallet is a wallet signal like
|
|
267
|
+
// any other, so it takes its place in the order. Without this, a
|
|
268
|
+
// disconnect still in flight could not tell that the wagmi handler had
|
|
269
|
+
// already adopted a replacement.
|
|
270
|
+
this.observe(this.namespaceOf(chainId));
|
|
271
|
+
if (this.deps.isTrackingSuppressed()) {
|
|
272
|
+
// While suppressed we must never LEARN a new wallet, but we must still
|
|
273
|
+
// CLEAR a stale one. Otherwise a disconnect or switch observed on a
|
|
274
|
+
// suppressed route leaves the previous address in memory and in the
|
|
275
|
+
// cookie, ready to attach to later allowed-page events.
|
|
276
|
+
if (!address) {
|
|
277
|
+
this.clearForChain(chainId);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (chainId === null || chainId === undefined)
|
|
281
|
+
return;
|
|
282
|
+
var namespace = this.namespaceOf(chainId);
|
|
283
|
+
var known = this.state[namespace].address;
|
|
284
|
+
var incoming = validateAddress(address, chainId);
|
|
285
|
+
if (known && incoming && incoming !== known) {
|
|
286
|
+
// A switch away from the learned wallet invalidates it. Drop the
|
|
287
|
+
// stale one without learning the new address.
|
|
288
|
+
this.clear(chainId);
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!address) {
|
|
293
|
+
this.clearForChain(chainId);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
if (chainId === null || chainId === undefined)
|
|
297
|
+
return;
|
|
298
|
+
var valid = validateAddress(address, chainId);
|
|
299
|
+
if (!valid) {
|
|
300
|
+
logger.warn("syncWalletState: invalid address (\"".concat(address, "\") for chain ").concat(chainId));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this.set(chainId, { address: valid });
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* Learn an EVM wallet observed on an autocaptured signature or transaction,
|
|
307
|
+
* when nothing is known yet.
|
|
308
|
+
*
|
|
309
|
+
* Deliberately conservative: it never overwrites a different wallet, and it
|
|
310
|
+
* corrects only a stale chain for the wallet already known.
|
|
311
|
+
*/
|
|
312
|
+
WalletStateStore.prototype.backfill = function (address, chainId, provider) {
|
|
313
|
+
// Refuse a chain the provider has since moved off.
|
|
314
|
+
//
|
|
315
|
+
// A request captures its chain once and reuses that snapshot for every
|
|
316
|
+
// status it emits, which is right for the payload: a confirmation must
|
|
317
|
+
// not be relabelled mid-flight. Writing that captured value back into
|
|
318
|
+
// central state on a LATER status restored a chain the wallet had already
|
|
319
|
+
// left, and the unscoped events that fall back to it then bypassed an
|
|
320
|
+
// exclusion that should have caught them.
|
|
321
|
+
if (provider && chainId !== undefined && chainId !== 0) {
|
|
322
|
+
var live = this.deps.providerChainId(provider);
|
|
323
|
+
if (live !== undefined && live !== chainId)
|
|
324
|
+
chainId = live;
|
|
325
|
+
}
|
|
326
|
+
// `0` is kept deliberately. It means "could not resolve", and persisting
|
|
327
|
+
// it is what lets the exclusion gate refuse the unscoped events (page,
|
|
328
|
+
// track, identify) that carry no chain of their own. Erasing it to
|
|
329
|
+
// `undefined` looked tidier but removed the only marker distinguishing
|
|
330
|
+
// "unknown" from "no wallet yet".
|
|
331
|
+
// Never learn identity while suppressed. A signature observed on an
|
|
332
|
+
// excluded route must not populate the address for later allowed pages.
|
|
333
|
+
if (this.deps.isTrackingSuppressed())
|
|
334
|
+
return;
|
|
335
|
+
var known = this.evmAddress;
|
|
336
|
+
if (known) {
|
|
337
|
+
// Same wallet, newer chain: correct it rather than returning. A
|
|
338
|
+
// persisted wallet restores a chain from a previous session, and if the
|
|
339
|
+
// provider has since moved, the stale restored chain would stay in the
|
|
340
|
+
// derived value and carry the unscoped events past an exclusion.
|
|
341
|
+
// `0` means "could not resolve". Correcting a stale chain with a real
|
|
342
|
+
// one is the point of this branch; replacing a chain we already know
|
|
343
|
+
// with "unknown" only makes attribution worse, and would trip the
|
|
344
|
+
// exclusion gate's fail-closed rule against a wallet we can place.
|
|
345
|
+
var usable = chainId !== undefined && !(chainId === 0 && this.evmChainId);
|
|
346
|
+
if (usable &&
|
|
347
|
+
known.toLowerCase() === address.toLowerCase() &&
|
|
348
|
+
this.evmChainId !== chainId) {
|
|
349
|
+
this.set("evm", { address: known, chainId: chainId });
|
|
350
|
+
}
|
|
351
|
+
// A DIFFERENT address is another wallet's business; never overwrite.
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
this.set("evm", { address: address, chainId: chainId });
|
|
355
|
+
};
|
|
356
|
+
/**
|
|
357
|
+
* Apply an EVM connect/switch seen while tracking is suppressed: never
|
|
358
|
+
* learn the wallet, but if it is a switch away from one already learned,
|
|
359
|
+
* drop the stale one so it cannot attach to a later allowed-page event.
|
|
360
|
+
*/
|
|
361
|
+
WalletStateStore.prototype.clearStaleEvmWalletOnSwitchWhileSuppressed = function (address) {
|
|
362
|
+
var known = this.state.evm.address;
|
|
363
|
+
var incoming = validateAndChecksumAddress(address);
|
|
364
|
+
if (known && incoming && incoming !== known)
|
|
365
|
+
this.clear("evm");
|
|
366
|
+
};
|
|
367
|
+
// ── persistence ──────────────────────────────────────────────────────────
|
|
368
|
+
/**
|
|
369
|
+
* Persist (or clear) the wallet snapshot, so the next page load can
|
|
370
|
+
* repopulate identity before the wallet reconnects. Without it, every
|
|
371
|
+
* `track()` / `page()` between page-show and reconnection ships with no
|
|
372
|
+
* address.
|
|
373
|
+
*/
|
|
374
|
+
WalletStateStore.prototype.persist = function () {
|
|
375
|
+
try {
|
|
376
|
+
// Visitor-level suppression is stable for the session, so purging is
|
|
377
|
+
// safe and prevents a stale snapshot outliving an opt-out.
|
|
378
|
+
if (this.deps.isPersistedIdentityPurgeRequired()) {
|
|
379
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
if (this.address) {
|
|
383
|
+
// Page-level exclusion: do not write while on an excluded route, but
|
|
384
|
+
// leave any existing cookie alone. One written on an allowed page
|
|
385
|
+
// must survive a transient visit here.
|
|
386
|
+
if (this.deps.isPageExcluded())
|
|
387
|
+
return;
|
|
388
|
+
var value = JSON.stringify(__assign({ address: this.address }, (this.chainId !== undefined && { chainId: this.chainId })));
|
|
389
|
+
var domain = getIdentityCookieDomain(this.deps.crossSubdomainCookies());
|
|
390
|
+
cookie().set(ACTIVE_WALLET_KEY, value, __assign(__assign({ path: "/", expires: new Date(Date.now() + ACTIVE_WALLET_TTL_MS).toUTCString() }, getIdentityCookieSecurity()), (domain ? { domain: domain } : {})));
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
// No active wallet: clear the snapshot. This runs even on an excluded
|
|
394
|
+
// route, so a disconnect observed while suppressed actively removes
|
|
395
|
+
// stale identity rather than leaving it for later.
|
|
396
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
catch (err) {
|
|
400
|
+
logger.warn("Failed to persist current wallet snapshot", err);
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
/** Seed identity from the persisted snapshot, once, during construction. */
|
|
404
|
+
WalletStateStore.prototype.load = function () {
|
|
405
|
+
try {
|
|
406
|
+
if (this.deps.isPersistedIdentityPurgeRequired()) {
|
|
407
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
// Page-level exclusion: don't restore into memory here, but keep the
|
|
411
|
+
// cookie so a later allowed-page load can.
|
|
412
|
+
if (this.deps.isPageExcluded())
|
|
413
|
+
return;
|
|
414
|
+
var raw = cookie().get(ACTIVE_WALLET_KEY);
|
|
415
|
+
if (!raw)
|
|
416
|
+
return;
|
|
417
|
+
var parsed = JSON.parse(raw);
|
|
418
|
+
if (!(parsed === null || parsed === void 0 ? void 0 : parsed.address))
|
|
419
|
+
return;
|
|
420
|
+
// A cookie is attacker-writable and survives across SDK versions, so
|
|
421
|
+
// the chain has to be a real number before anything trusts it. A string
|
|
422
|
+
// "137" restored as-is would never match a numeric exclusion list, so
|
|
423
|
+
// an excluded chain would silently start reporting again.
|
|
424
|
+
//
|
|
425
|
+
// A present-but-unusable chain rejects the WHOLE snapshot rather than
|
|
426
|
+
// being downgraded to "chainless". Downgrading looks harmless and is
|
|
427
|
+
// not: the namespace is derived from the chain, so a Solana wallet with
|
|
428
|
+
// a corrupt chain would be filed under EVM, and a later Solana
|
|
429
|
+
// disconnect would fall through to that phantom entry and keep
|
|
430
|
+
// attributing events to a wallet that had gone.
|
|
431
|
+
var rawChain = parsed.chainId;
|
|
432
|
+
var chainMissing = rawChain === undefined || rawChain === null;
|
|
433
|
+
if (!chainMissing &&
|
|
434
|
+
(typeof rawChain !== "number" || !Number.isFinite(rawChain))) {
|
|
435
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
var chainId = chainMissing ? undefined : rawChain;
|
|
439
|
+
// With no chain to go on, the address shape decides. Defaulting to EVM
|
|
440
|
+
// filed a chainless Solana wallet under EVM, where later EVM handlers
|
|
441
|
+
// read it as `evmAddress` and emitted EVM state for a Solana wallet.
|
|
442
|
+
var namespace = chainId === undefined
|
|
443
|
+
? validateAndChecksumAddress(parsed.address)
|
|
444
|
+
? "evm"
|
|
445
|
+
: "solana"
|
|
446
|
+
: this.namespaceOf(chainId);
|
|
447
|
+
var validated = validateAddress(parsed.address, chainId);
|
|
448
|
+
if (!validated) {
|
|
449
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
var ns = this.state[namespace];
|
|
453
|
+
ns.address = validated;
|
|
454
|
+
if (chainId !== undefined)
|
|
455
|
+
ns.chainId = chainId;
|
|
456
|
+
this._activeNamespace = namespace;
|
|
457
|
+
this.address = validated;
|
|
458
|
+
this.chainId = chainId;
|
|
459
|
+
}
|
|
460
|
+
catch (err) {
|
|
461
|
+
logger.warn("Failed to restore persisted wallet snapshot", err);
|
|
462
|
+
cookie().remove(ACTIVE_WALLET_KEY);
|
|
463
|
+
}
|
|
464
|
+
};
|
|
465
|
+
// ── internals ────────────────────────────────────────────────────────────
|
|
466
|
+
WalletStateStore.prototype.clearForChain = function (chainId) {
|
|
467
|
+
if (chainId !== undefined && chainId !== null) {
|
|
468
|
+
this.clear(chainId);
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
this.clear("evm");
|
|
472
|
+
this.clear("solana");
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
WalletStateStore.prototype.displaceProvider = function (previous, next) {
|
|
476
|
+
if (previous && previous !== next)
|
|
477
|
+
this.deps.onProviderDisplaced(previous);
|
|
478
|
+
};
|
|
479
|
+
/**
|
|
480
|
+
* Re-derive `address`/`chainId` from the active namespace.
|
|
481
|
+
*
|
|
482
|
+
* Last-connected wins, then fall through to the other namespace so a
|
|
483
|
+
* still-connected wallet keeps attribution when its neighbour disconnects.
|
|
484
|
+
*/
|
|
485
|
+
WalletStateStore.prototype.syncDerived = function () {
|
|
486
|
+
var active = this._activeNamespace;
|
|
487
|
+
if (active) {
|
|
488
|
+
var state = this.state[active];
|
|
489
|
+
if (state.address || state.chainId) {
|
|
490
|
+
this.address = state.address;
|
|
491
|
+
this.chainId = state.chainId;
|
|
492
|
+
this.persist();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
var other = active === "evm" ? "solana" : "evm";
|
|
497
|
+
var otherState = this.state[other];
|
|
498
|
+
if (otherState.address || otherState.chainId) {
|
|
499
|
+
this.address = otherState.address;
|
|
500
|
+
this.chainId = otherState.chainId;
|
|
501
|
+
this._activeNamespace = other;
|
|
502
|
+
this.persist();
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
this.address = undefined;
|
|
506
|
+
this.chainId = undefined;
|
|
507
|
+
this.persist();
|
|
508
|
+
};
|
|
509
|
+
return WalletStateStore;
|
|
510
|
+
}());
|
|
511
|
+
export { WalletStateStore };
|
|
512
|
+
//# sourceMappingURL=WalletStateStore.js.map
|