@unifold/analytics 0.1.72
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/index.d.mts +329 -0
- package/dist/index.d.ts +329 -0
- package/dist/index.js +326 -0
- package/dist/index.mjs +317 -0
- package/package.json +48 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Well-known event names.
|
|
3
|
+
*
|
|
4
|
+
* Exported as a const object (not an enum) so it tree-shakes cleanly —
|
|
5
|
+
* consumers get autocomplete without pulling in a runtime object they
|
|
6
|
+
* don't use. The `AnalyticsEventName` type still accepts arbitrary
|
|
7
|
+
* strings so new events don't require an SDK update.
|
|
8
|
+
*/
|
|
9
|
+
declare const AnalyticsEvents: {
|
|
10
|
+
/** User viewed a screen / step in the flow. */
|
|
11
|
+
readonly SCREEN_VIEWED: "screen_viewed";
|
|
12
|
+
/** User selected a payment method. */
|
|
13
|
+
readonly PAYMENT_METHOD_SELECTED: "payment_method_selected";
|
|
14
|
+
/**
|
|
15
|
+
* User picked an asset — the token *and* the chain it lives on. One event
|
|
16
|
+
* for both, carrying the full identity, because neither half identifies an
|
|
17
|
+
* asset alone; either dropdown changing re-fires it.
|
|
18
|
+
*/
|
|
19
|
+
readonly TOKEN_SELECTED: "token_selected";
|
|
20
|
+
/** User picked a browser wallet to connect. */
|
|
21
|
+
readonly WALLET_SELECTED: "wallet_selected";
|
|
22
|
+
/** User picked an onramp/exchange provider (card quote, bank, exchange). */
|
|
23
|
+
readonly PROVIDER_SELECTED: "provider_selected";
|
|
24
|
+
/**
|
|
25
|
+
* User picked what to pay with inside a provider's flow — a card, or a
|
|
26
|
+
* wallet like Apple Pay. Distinct from `payment_method_selected`, which is
|
|
27
|
+
* the funding route they chose from the deposit menu.
|
|
28
|
+
*/
|
|
29
|
+
readonly PAYMENT_METHOD_TYPE_SELECTED: "payment_method_type_selected";
|
|
30
|
+
/**
|
|
31
|
+
* User reached a step that asks them to prove something about themselves —
|
|
32
|
+
* identity details, a document, an emailed or texted code, an exchange MFA
|
|
33
|
+
* prompt. `verification_type` says which.
|
|
34
|
+
*/
|
|
35
|
+
readonly VERIFICATION_STARTED: "verification_started";
|
|
36
|
+
/** User handed over what the step asked for. A provider may still be reviewing. */
|
|
37
|
+
readonly VERIFICATION_SUBMITTED: "verification_submitted";
|
|
38
|
+
/** The provider accepted it. */
|
|
39
|
+
readonly VERIFICATION_COMPLETED: "verification_completed";
|
|
40
|
+
/** The provider rejected it, or the user backed out. See `failure_reason`. */
|
|
41
|
+
readonly VERIFICATION_FAILED: "verification_failed";
|
|
42
|
+
/**
|
|
43
|
+
* User began linking an account they hold somewhere else — an exchange, a
|
|
44
|
+
* Stripe Link profile. Distinct from connecting a browser wallet, which the
|
|
45
|
+
* user already controls locally.
|
|
46
|
+
*/
|
|
47
|
+
readonly ACCOUNT_CONNECTION_STARTED: "account_connection_started";
|
|
48
|
+
/** The external account is linked and usable. */
|
|
49
|
+
readonly ACCOUNT_CONNECTED: "account_connected";
|
|
50
|
+
/** Linking the external account failed or was abandoned. */
|
|
51
|
+
readonly ACCOUNT_CONNECTION_FAILED: "account_connection_failed";
|
|
52
|
+
/** User began connecting a browser wallet. */
|
|
53
|
+
readonly WALLET_CONNECTION_STARTED: "wallet_connection_started";
|
|
54
|
+
/** The wallet is connected and usable. */
|
|
55
|
+
readonly WALLET_CONNECTED: "wallet_connected";
|
|
56
|
+
/** The connection failed, or the user declined it in their wallet. */
|
|
57
|
+
readonly WALLET_CONNECTION_FAILED: "wallet_connection_failed";
|
|
58
|
+
/** User navigated back a step. */
|
|
59
|
+
readonly BACK_CLICKED: "back_clicked";
|
|
60
|
+
/** User started a flow (deposit, checkout, withdraw). */
|
|
61
|
+
readonly FLOW_STARTED: "flow_started";
|
|
62
|
+
/** User completed a flow successfully. */
|
|
63
|
+
readonly FLOW_COMPLETED: "flow_completed";
|
|
64
|
+
/** A flow failed with an error. */
|
|
65
|
+
readonly FLOW_FAILED: "flow_failed";
|
|
66
|
+
/** Generic UI interaction (button press, link tap). */
|
|
67
|
+
readonly BUTTON_CLICKED: "button_clicked";
|
|
68
|
+
/** Widget/modal opened. */
|
|
69
|
+
readonly WIDGET_OPENED: "widget_opened";
|
|
70
|
+
/** Widget/modal closed. */
|
|
71
|
+
readonly WIDGET_CLOSED: "widget_closed";
|
|
72
|
+
};
|
|
73
|
+
/** Any known event name, or an arbitrary string for forward-compat. */
|
|
74
|
+
type AnalyticsEventName = (typeof AnalyticsEvents)[keyof typeof AnalyticsEvents] | (string & Record<never, never>);
|
|
75
|
+
/** Flat event-specific properties. Keep flat for easy PostHog filtering. */
|
|
76
|
+
interface AnalyticsProperties {
|
|
77
|
+
/** Payment method (transfer_crypto, card, apple_pay, exchange_connect, etc.) */
|
|
78
|
+
method?: string;
|
|
79
|
+
/** Flow type (deposit, checkout, withdraw). */
|
|
80
|
+
flow_type?: string;
|
|
81
|
+
/** Screen / step name (method_selector, amount_entry, review, processing, success). */
|
|
82
|
+
screen?: string;
|
|
83
|
+
/** Numeric step in the funnel (1-indexed). */
|
|
84
|
+
step?: number;
|
|
85
|
+
/** Chain id (e.g. "mainnet", "8453"). */
|
|
86
|
+
chain?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Slugged network name, paired with `chain` (e.g. "base", "hypercore",
|
|
89
|
+
* "base_sepolia"). Grouping key for charts; `chain` stays the exact id.
|
|
90
|
+
*/
|
|
91
|
+
network?: string;
|
|
92
|
+
/** Chain family (ethereum, solana, …). */
|
|
93
|
+
chain_type?: string;
|
|
94
|
+
/** Token contract address. */
|
|
95
|
+
token_address?: string;
|
|
96
|
+
/** Browser wallet identifier/name (metamask, phantom, …). */
|
|
97
|
+
wallet?: string;
|
|
98
|
+
/** Transaction amount in USD. */
|
|
99
|
+
amount_usd?: string;
|
|
100
|
+
/** Human-readable token amount (standard unit, e.g. "0.5"). */
|
|
101
|
+
amount?: string;
|
|
102
|
+
/** Token amount in base units (smallest denomination). */
|
|
103
|
+
amount_base_unit?: string;
|
|
104
|
+
/** UI element identifier for button_clicked events. */
|
|
105
|
+
element?: string;
|
|
106
|
+
/** Error code for flow_failed events. */
|
|
107
|
+
error_code?: string;
|
|
108
|
+
/** Onramp/exchange provider identifier. */
|
|
109
|
+
provider?: string;
|
|
110
|
+
/**
|
|
111
|
+
* What the user is paying with: `card`, `apple_pay`, `google_pay`. The brand
|
|
112
|
+
* and the last four digits stay out — which wallet, or none, is the part
|
|
113
|
+
* that moves conversion.
|
|
114
|
+
*/
|
|
115
|
+
payment_method_type?: string;
|
|
116
|
+
/** Token symbol as displayed (e.g. "USDC", "USDC.e", "USDC (Perp)"). */
|
|
117
|
+
token?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Slugged token symbol, paired with `token` (e.g. "usdc", "usdc_e",
|
|
120
|
+
* "usdc_perp"). Grouping key for charts; `token` stays the display symbol.
|
|
121
|
+
*/
|
|
122
|
+
currency?: string;
|
|
123
|
+
/**
|
|
124
|
+
* What a `verification_*` event is about: `identity`, `document`, `email`,
|
|
125
|
+
* `phone`, `mfa`, `limit_upgrade`.
|
|
126
|
+
*/
|
|
127
|
+
verification_type?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Provider's own name for the level being verified, when it has tiers —
|
|
130
|
+
* Stripe's `l0` / `l1` / `l2`. Telemetry only.
|
|
131
|
+
*/
|
|
132
|
+
verification_tier?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Provider's raw status verbatim (`pending`, `rejected`, `resubmit`, …). The
|
|
135
|
+
* event name says what happened in our terms; this says what the provider
|
|
136
|
+
* called it, which is what support tickets quote.
|
|
137
|
+
*
|
|
138
|
+
* Telemetry only, like every other `verification_*` property: the host is
|
|
139
|
+
* told a check didn't pass and nothing more.
|
|
140
|
+
*/
|
|
141
|
+
verification_status?: string;
|
|
142
|
+
/** Screen navigated away from (back_clicked). */
|
|
143
|
+
from_screen?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Machine-readable failure reason.
|
|
146
|
+
*
|
|
147
|
+
* Telemetry only for verification and account connections, where the reason
|
|
148
|
+
* names what didn't check out about the user. Wallet connections do forward
|
|
149
|
+
* theirs — declining a prompt is the user's own visible action.
|
|
150
|
+
*/
|
|
151
|
+
failure_reason?: string;
|
|
152
|
+
/** Catch-all for forward-compat. */
|
|
153
|
+
[key: string]: unknown;
|
|
154
|
+
}
|
|
155
|
+
/** Auto-populated SDK/device metadata. Not business data. */
|
|
156
|
+
interface AnalyticsContext {
|
|
157
|
+
library: {
|
|
158
|
+
name: string;
|
|
159
|
+
version: string;
|
|
160
|
+
};
|
|
161
|
+
platform?: string;
|
|
162
|
+
}
|
|
163
|
+
/** Full event payload sent to the telemetry backend. */
|
|
164
|
+
interface AnalyticsEvent {
|
|
165
|
+
event: AnalyticsEventName;
|
|
166
|
+
timestamp: string;
|
|
167
|
+
/**
|
|
168
|
+
* Analytics journey id (`asess_<ksuid>`): one per modal open → close.
|
|
169
|
+
* Present only while a journey is active (after {@link EventTracker.startSession},
|
|
170
|
+
* before {@link EventTracker.endSession}). Serialized as `session_id`.
|
|
171
|
+
*/
|
|
172
|
+
sessionId?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Internal Unifold user.id (`user_<ksuid>`), typically obtained from the
|
|
175
|
+
* deposit-addresses response. Serialized to the wire as `user_id`.
|
|
176
|
+
*/
|
|
177
|
+
userId?: string;
|
|
178
|
+
properties: AnalyticsProperties;
|
|
179
|
+
context: AnalyticsContext;
|
|
180
|
+
}
|
|
181
|
+
interface AnalyticsConfig {
|
|
182
|
+
/** Full telemetry ingest URL. Must be provided by the host (e.g. connect-react reads it from @unifold/core). */
|
|
183
|
+
endpoint?: string;
|
|
184
|
+
/** Publishable key (pk_...) used for auth and project identification. */
|
|
185
|
+
publishableKey: string;
|
|
186
|
+
/** SDK version string (e.g. "0.1.70"). Auto-populated from package version if omitted. */
|
|
187
|
+
sdkVersion?: string;
|
|
188
|
+
/** Platform identifier (e.g. "web", "ios", "react-native"). */
|
|
189
|
+
platform?: string;
|
|
190
|
+
/**
|
|
191
|
+
* Internal Unifold user.id. Optional at construction — usually set later via
|
|
192
|
+
* {@link EventTracker.setUserId} once deposit-addresses returns.
|
|
193
|
+
*/
|
|
194
|
+
userId?: string;
|
|
195
|
+
/** Disable all telemetry. Events are still emitted locally but never sent. */
|
|
196
|
+
disabled?: boolean;
|
|
197
|
+
/** Enable debug logging to console. */
|
|
198
|
+
debug?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Dedup window in milliseconds. If the same event name + properties
|
|
201
|
+
* fires again within this window, the duplicate is dropped.
|
|
202
|
+
* Prevents noise from React re-renders. Defaults to 500ms.
|
|
203
|
+
* Set to 0 to disable dedup.
|
|
204
|
+
*/
|
|
205
|
+
dedupMs?: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Lightweight analytics tracker for the Unifold SDK.
|
|
210
|
+
*
|
|
211
|
+
* Follows the Segment Track spec: event name + flat `properties` bag +
|
|
212
|
+
* auto-populated `context` for SDK metadata.
|
|
213
|
+
*
|
|
214
|
+
* Includes built-in dedup to prevent noise from React re-renders —
|
|
215
|
+
* identical events within the dedup window are silently dropped.
|
|
216
|
+
*
|
|
217
|
+
* ```ts
|
|
218
|
+
* const tracker = createEventTracker({ projectId: 'proj_123', platform: 'web' });
|
|
219
|
+
*
|
|
220
|
+
* // Funnel tracking
|
|
221
|
+
* tracker.track('screen_viewed', { screen: 'method_selector', flow_type: 'deposit', step: 1 });
|
|
222
|
+
* tracker.track('payment_method_selected', { method: 'transfer_crypto', flow_type: 'deposit' });
|
|
223
|
+
* tracker.track('screen_viewed', { screen: 'amount_entry', flow_type: 'deposit', step: 2 });
|
|
224
|
+
* tracker.track('flow_completed', { method: 'transfer_crypto', flow_type: 'deposit', amount_usd: '100' });
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
declare class EventTracker {
|
|
228
|
+
private emitter;
|
|
229
|
+
private config;
|
|
230
|
+
private context;
|
|
231
|
+
private recentEvents;
|
|
232
|
+
private unsubForwarder;
|
|
233
|
+
private userId;
|
|
234
|
+
/** Set only while a journey is active (modal open → close). */
|
|
235
|
+
private sessionId;
|
|
236
|
+
constructor(config: AnalyticsConfig);
|
|
237
|
+
private attachForwarder;
|
|
238
|
+
private detachForwarder;
|
|
239
|
+
/**
|
|
240
|
+
* Track an event. Properties are flat key-value pairs that become
|
|
241
|
+
* directly filterable dimensions in PostHog.
|
|
242
|
+
*
|
|
243
|
+
* Duplicate calls with the same event name + properties within the
|
|
244
|
+
* dedup window are silently dropped.
|
|
245
|
+
*/
|
|
246
|
+
track(eventName: AnalyticsEventName, properties?: AnalyticsProperties): void;
|
|
247
|
+
/**
|
|
248
|
+
* Associate subsequent events with an internal Unifold user.id
|
|
249
|
+
* (`user_<ksuid>`). Pass `null` to clear. Usually called once the
|
|
250
|
+
* deposit-addresses response returns the id.
|
|
251
|
+
*/
|
|
252
|
+
setUserId(userId: string | null): void;
|
|
253
|
+
/**
|
|
254
|
+
* Start a new analytics journey (modal open). Mints an `asess_<ksuid>` and
|
|
255
|
+
* returns it. Every {@link EventTracker.track} call until
|
|
256
|
+
* {@link EventTracker.endSession} carries it.
|
|
257
|
+
*
|
|
258
|
+
* Does not touch `userId`: the internal `user_<ksuid>` is attached separately
|
|
259
|
+
* via {@link EventTracker.setUserId} once the deposit-addresses response
|
|
260
|
+
* returns, and persists until the host replaces it.
|
|
261
|
+
*/
|
|
262
|
+
startSession(): string;
|
|
263
|
+
/**
|
|
264
|
+
* End the current journey (modal close). Clears the session id so any stray
|
|
265
|
+
* events after close are session-less. Call this *after* the terminal
|
|
266
|
+
* `widget_closed` track call.
|
|
267
|
+
*/
|
|
268
|
+
endSession(): void;
|
|
269
|
+
/** Current analytics journey id, or `undefined` when no journey is active. */
|
|
270
|
+
getSessionId(): string | undefined;
|
|
271
|
+
/**
|
|
272
|
+
* Subscribe to a specific event or `'*'` for all events.
|
|
273
|
+
* Returns an unsubscribe function.
|
|
274
|
+
*/
|
|
275
|
+
on(eventName: AnalyticsEventName | '*', handler: (event: AnalyticsEvent) => void): () => void;
|
|
276
|
+
/** Remove all listeners and stop forwarding. */
|
|
277
|
+
destroy(): void;
|
|
278
|
+
/** Enable or disable network forwarding at runtime. Custom listeners are preserved. */
|
|
279
|
+
setDisabled(disabled: boolean): void;
|
|
280
|
+
}
|
|
281
|
+
/** Create a new EventTracker instance. */
|
|
282
|
+
declare function createEventTracker(config: AnalyticsConfig): EventTracker;
|
|
283
|
+
|
|
284
|
+
type EventHandler<T> = (event: T) => void;
|
|
285
|
+
/**
|
|
286
|
+
* Minimal typed event emitter with wildcard support.
|
|
287
|
+
*
|
|
288
|
+
* - `on()` returns an unsubscribe function.
|
|
289
|
+
* - `'*'` subscribes to every event type.
|
|
290
|
+
* - Handler exceptions are caught so a faulty listener never breaks the host app.
|
|
291
|
+
*/
|
|
292
|
+
declare class Emitter<EventMap extends Record<string, unknown>> {
|
|
293
|
+
private handlers;
|
|
294
|
+
on<K extends keyof EventMap>(type: K, handler: EventHandler<EventMap[K]>): () => void;
|
|
295
|
+
on(type: '*', handler: EventHandler<EventMap[keyof EventMap]>): () => void;
|
|
296
|
+
emit<K extends keyof EventMap>(type: K, event: EventMap[K]): void;
|
|
297
|
+
removeAllListeners(): void;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Fire-and-forget POST of an analytics event.
|
|
302
|
+
*
|
|
303
|
+
* - Sends the publishable key via `x-publishable-key` header for auth.
|
|
304
|
+
* - Uses `keepalive: true` so the request completes even during page unload.
|
|
305
|
+
* - Never throws — failures are silently swallowed (optionally logged in debug mode).
|
|
306
|
+
*/
|
|
307
|
+
declare function forwardEvent(event: AnalyticsEvent, publishableKey: string, endpoint?: string, debug?: boolean): void;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Dependency-free KSUID generator.
|
|
311
|
+
*
|
|
312
|
+
* Kept in sync with `@unifold/core`'s implementation (packages/core/src/lib/utils.ts)
|
|
313
|
+
* but duplicated intentionally: `@unifold/analytics` is a zero-dependency leaf
|
|
314
|
+
* package and must not depend on `@unifold/core`.
|
|
315
|
+
*/
|
|
316
|
+
/**
|
|
317
|
+
* Generate a KSUID-like ID without external dependencies.
|
|
318
|
+
* Format: `base62(4-byte-timestamp + 16-random-bytes)` (27 chars).
|
|
319
|
+
*/
|
|
320
|
+
declare function generateKSUID(): string;
|
|
321
|
+
/**
|
|
322
|
+
* Generate a KSUID-like prefixed ID without external dependencies.
|
|
323
|
+
* Format: `{prefix}_{base62(...)}`.
|
|
324
|
+
*/
|
|
325
|
+
declare function generatePrefixedKSUID(prefix: string): string;
|
|
326
|
+
|
|
327
|
+
declare const PACKAGE_VERSION = "0.1.72";
|
|
328
|
+
|
|
329
|
+
export { type AnalyticsConfig, type AnalyticsContext, type AnalyticsEvent, type AnalyticsEventName, AnalyticsEvents, type AnalyticsProperties, Emitter, EventTracker, PACKAGE_VERSION, createEventTracker, forwardEvent, generateKSUID, generatePrefixedKSUID };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Well-known event names.
|
|
3
|
+
*
|
|
4
|
+
* Exported as a const object (not an enum) so it tree-shakes cleanly —
|
|
5
|
+
* consumers get autocomplete without pulling in a runtime object they
|
|
6
|
+
* don't use. The `AnalyticsEventName` type still accepts arbitrary
|
|
7
|
+
* strings so new events don't require an SDK update.
|
|
8
|
+
*/
|
|
9
|
+
declare const AnalyticsEvents: {
|
|
10
|
+
/** User viewed a screen / step in the flow. */
|
|
11
|
+
readonly SCREEN_VIEWED: "screen_viewed";
|
|
12
|
+
/** User selected a payment method. */
|
|
13
|
+
readonly PAYMENT_METHOD_SELECTED: "payment_method_selected";
|
|
14
|
+
/**
|
|
15
|
+
* User picked an asset — the token *and* the chain it lives on. One event
|
|
16
|
+
* for both, carrying the full identity, because neither half identifies an
|
|
17
|
+
* asset alone; either dropdown changing re-fires it.
|
|
18
|
+
*/
|
|
19
|
+
readonly TOKEN_SELECTED: "token_selected";
|
|
20
|
+
/** User picked a browser wallet to connect. */
|
|
21
|
+
readonly WALLET_SELECTED: "wallet_selected";
|
|
22
|
+
/** User picked an onramp/exchange provider (card quote, bank, exchange). */
|
|
23
|
+
readonly PROVIDER_SELECTED: "provider_selected";
|
|
24
|
+
/**
|
|
25
|
+
* User picked what to pay with inside a provider's flow — a card, or a
|
|
26
|
+
* wallet like Apple Pay. Distinct from `payment_method_selected`, which is
|
|
27
|
+
* the funding route they chose from the deposit menu.
|
|
28
|
+
*/
|
|
29
|
+
readonly PAYMENT_METHOD_TYPE_SELECTED: "payment_method_type_selected";
|
|
30
|
+
/**
|
|
31
|
+
* User reached a step that asks them to prove something about themselves —
|
|
32
|
+
* identity details, a document, an emailed or texted code, an exchange MFA
|
|
33
|
+
* prompt. `verification_type` says which.
|
|
34
|
+
*/
|
|
35
|
+
readonly VERIFICATION_STARTED: "verification_started";
|
|
36
|
+
/** User handed over what the step asked for. A provider may still be reviewing. */
|
|
37
|
+
readonly VERIFICATION_SUBMITTED: "verification_submitted";
|
|
38
|
+
/** The provider accepted it. */
|
|
39
|
+
readonly VERIFICATION_COMPLETED: "verification_completed";
|
|
40
|
+
/** The provider rejected it, or the user backed out. See `failure_reason`. */
|
|
41
|
+
readonly VERIFICATION_FAILED: "verification_failed";
|
|
42
|
+
/**
|
|
43
|
+
* User began linking an account they hold somewhere else — an exchange, a
|
|
44
|
+
* Stripe Link profile. Distinct from connecting a browser wallet, which the
|
|
45
|
+
* user already controls locally.
|
|
46
|
+
*/
|
|
47
|
+
readonly ACCOUNT_CONNECTION_STARTED: "account_connection_started";
|
|
48
|
+
/** The external account is linked and usable. */
|
|
49
|
+
readonly ACCOUNT_CONNECTED: "account_connected";
|
|
50
|
+
/** Linking the external account failed or was abandoned. */
|
|
51
|
+
readonly ACCOUNT_CONNECTION_FAILED: "account_connection_failed";
|
|
52
|
+
/** User began connecting a browser wallet. */
|
|
53
|
+
readonly WALLET_CONNECTION_STARTED: "wallet_connection_started";
|
|
54
|
+
/** The wallet is connected and usable. */
|
|
55
|
+
readonly WALLET_CONNECTED: "wallet_connected";
|
|
56
|
+
/** The connection failed, or the user declined it in their wallet. */
|
|
57
|
+
readonly WALLET_CONNECTION_FAILED: "wallet_connection_failed";
|
|
58
|
+
/** User navigated back a step. */
|
|
59
|
+
readonly BACK_CLICKED: "back_clicked";
|
|
60
|
+
/** User started a flow (deposit, checkout, withdraw). */
|
|
61
|
+
readonly FLOW_STARTED: "flow_started";
|
|
62
|
+
/** User completed a flow successfully. */
|
|
63
|
+
readonly FLOW_COMPLETED: "flow_completed";
|
|
64
|
+
/** A flow failed with an error. */
|
|
65
|
+
readonly FLOW_FAILED: "flow_failed";
|
|
66
|
+
/** Generic UI interaction (button press, link tap). */
|
|
67
|
+
readonly BUTTON_CLICKED: "button_clicked";
|
|
68
|
+
/** Widget/modal opened. */
|
|
69
|
+
readonly WIDGET_OPENED: "widget_opened";
|
|
70
|
+
/** Widget/modal closed. */
|
|
71
|
+
readonly WIDGET_CLOSED: "widget_closed";
|
|
72
|
+
};
|
|
73
|
+
/** Any known event name, or an arbitrary string for forward-compat. */
|
|
74
|
+
type AnalyticsEventName = (typeof AnalyticsEvents)[keyof typeof AnalyticsEvents] | (string & Record<never, never>);
|
|
75
|
+
/** Flat event-specific properties. Keep flat for easy PostHog filtering. */
|
|
76
|
+
interface AnalyticsProperties {
|
|
77
|
+
/** Payment method (transfer_crypto, card, apple_pay, exchange_connect, etc.) */
|
|
78
|
+
method?: string;
|
|
79
|
+
/** Flow type (deposit, checkout, withdraw). */
|
|
80
|
+
flow_type?: string;
|
|
81
|
+
/** Screen / step name (method_selector, amount_entry, review, processing, success). */
|
|
82
|
+
screen?: string;
|
|
83
|
+
/** Numeric step in the funnel (1-indexed). */
|
|
84
|
+
step?: number;
|
|
85
|
+
/** Chain id (e.g. "mainnet", "8453"). */
|
|
86
|
+
chain?: string;
|
|
87
|
+
/**
|
|
88
|
+
* Slugged network name, paired with `chain` (e.g. "base", "hypercore",
|
|
89
|
+
* "base_sepolia"). Grouping key for charts; `chain` stays the exact id.
|
|
90
|
+
*/
|
|
91
|
+
network?: string;
|
|
92
|
+
/** Chain family (ethereum, solana, …). */
|
|
93
|
+
chain_type?: string;
|
|
94
|
+
/** Token contract address. */
|
|
95
|
+
token_address?: string;
|
|
96
|
+
/** Browser wallet identifier/name (metamask, phantom, …). */
|
|
97
|
+
wallet?: string;
|
|
98
|
+
/** Transaction amount in USD. */
|
|
99
|
+
amount_usd?: string;
|
|
100
|
+
/** Human-readable token amount (standard unit, e.g. "0.5"). */
|
|
101
|
+
amount?: string;
|
|
102
|
+
/** Token amount in base units (smallest denomination). */
|
|
103
|
+
amount_base_unit?: string;
|
|
104
|
+
/** UI element identifier for button_clicked events. */
|
|
105
|
+
element?: string;
|
|
106
|
+
/** Error code for flow_failed events. */
|
|
107
|
+
error_code?: string;
|
|
108
|
+
/** Onramp/exchange provider identifier. */
|
|
109
|
+
provider?: string;
|
|
110
|
+
/**
|
|
111
|
+
* What the user is paying with: `card`, `apple_pay`, `google_pay`. The brand
|
|
112
|
+
* and the last four digits stay out — which wallet, or none, is the part
|
|
113
|
+
* that moves conversion.
|
|
114
|
+
*/
|
|
115
|
+
payment_method_type?: string;
|
|
116
|
+
/** Token symbol as displayed (e.g. "USDC", "USDC.e", "USDC (Perp)"). */
|
|
117
|
+
token?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Slugged token symbol, paired with `token` (e.g. "usdc", "usdc_e",
|
|
120
|
+
* "usdc_perp"). Grouping key for charts; `token` stays the display symbol.
|
|
121
|
+
*/
|
|
122
|
+
currency?: string;
|
|
123
|
+
/**
|
|
124
|
+
* What a `verification_*` event is about: `identity`, `document`, `email`,
|
|
125
|
+
* `phone`, `mfa`, `limit_upgrade`.
|
|
126
|
+
*/
|
|
127
|
+
verification_type?: string;
|
|
128
|
+
/**
|
|
129
|
+
* Provider's own name for the level being verified, when it has tiers —
|
|
130
|
+
* Stripe's `l0` / `l1` / `l2`. Telemetry only.
|
|
131
|
+
*/
|
|
132
|
+
verification_tier?: string;
|
|
133
|
+
/**
|
|
134
|
+
* Provider's raw status verbatim (`pending`, `rejected`, `resubmit`, …). The
|
|
135
|
+
* event name says what happened in our terms; this says what the provider
|
|
136
|
+
* called it, which is what support tickets quote.
|
|
137
|
+
*
|
|
138
|
+
* Telemetry only, like every other `verification_*` property: the host is
|
|
139
|
+
* told a check didn't pass and nothing more.
|
|
140
|
+
*/
|
|
141
|
+
verification_status?: string;
|
|
142
|
+
/** Screen navigated away from (back_clicked). */
|
|
143
|
+
from_screen?: string;
|
|
144
|
+
/**
|
|
145
|
+
* Machine-readable failure reason.
|
|
146
|
+
*
|
|
147
|
+
* Telemetry only for verification and account connections, where the reason
|
|
148
|
+
* names what didn't check out about the user. Wallet connections do forward
|
|
149
|
+
* theirs — declining a prompt is the user's own visible action.
|
|
150
|
+
*/
|
|
151
|
+
failure_reason?: string;
|
|
152
|
+
/** Catch-all for forward-compat. */
|
|
153
|
+
[key: string]: unknown;
|
|
154
|
+
}
|
|
155
|
+
/** Auto-populated SDK/device metadata. Not business data. */
|
|
156
|
+
interface AnalyticsContext {
|
|
157
|
+
library: {
|
|
158
|
+
name: string;
|
|
159
|
+
version: string;
|
|
160
|
+
};
|
|
161
|
+
platform?: string;
|
|
162
|
+
}
|
|
163
|
+
/** Full event payload sent to the telemetry backend. */
|
|
164
|
+
interface AnalyticsEvent {
|
|
165
|
+
event: AnalyticsEventName;
|
|
166
|
+
timestamp: string;
|
|
167
|
+
/**
|
|
168
|
+
* Analytics journey id (`asess_<ksuid>`): one per modal open → close.
|
|
169
|
+
* Present only while a journey is active (after {@link EventTracker.startSession},
|
|
170
|
+
* before {@link EventTracker.endSession}). Serialized as `session_id`.
|
|
171
|
+
*/
|
|
172
|
+
sessionId?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Internal Unifold user.id (`user_<ksuid>`), typically obtained from the
|
|
175
|
+
* deposit-addresses response. Serialized to the wire as `user_id`.
|
|
176
|
+
*/
|
|
177
|
+
userId?: string;
|
|
178
|
+
properties: AnalyticsProperties;
|
|
179
|
+
context: AnalyticsContext;
|
|
180
|
+
}
|
|
181
|
+
interface AnalyticsConfig {
|
|
182
|
+
/** Full telemetry ingest URL. Must be provided by the host (e.g. connect-react reads it from @unifold/core). */
|
|
183
|
+
endpoint?: string;
|
|
184
|
+
/** Publishable key (pk_...) used for auth and project identification. */
|
|
185
|
+
publishableKey: string;
|
|
186
|
+
/** SDK version string (e.g. "0.1.70"). Auto-populated from package version if omitted. */
|
|
187
|
+
sdkVersion?: string;
|
|
188
|
+
/** Platform identifier (e.g. "web", "ios", "react-native"). */
|
|
189
|
+
platform?: string;
|
|
190
|
+
/**
|
|
191
|
+
* Internal Unifold user.id. Optional at construction — usually set later via
|
|
192
|
+
* {@link EventTracker.setUserId} once deposit-addresses returns.
|
|
193
|
+
*/
|
|
194
|
+
userId?: string;
|
|
195
|
+
/** Disable all telemetry. Events are still emitted locally but never sent. */
|
|
196
|
+
disabled?: boolean;
|
|
197
|
+
/** Enable debug logging to console. */
|
|
198
|
+
debug?: boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Dedup window in milliseconds. If the same event name + properties
|
|
201
|
+
* fires again within this window, the duplicate is dropped.
|
|
202
|
+
* Prevents noise from React re-renders. Defaults to 500ms.
|
|
203
|
+
* Set to 0 to disable dedup.
|
|
204
|
+
*/
|
|
205
|
+
dedupMs?: number;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Lightweight analytics tracker for the Unifold SDK.
|
|
210
|
+
*
|
|
211
|
+
* Follows the Segment Track spec: event name + flat `properties` bag +
|
|
212
|
+
* auto-populated `context` for SDK metadata.
|
|
213
|
+
*
|
|
214
|
+
* Includes built-in dedup to prevent noise from React re-renders —
|
|
215
|
+
* identical events within the dedup window are silently dropped.
|
|
216
|
+
*
|
|
217
|
+
* ```ts
|
|
218
|
+
* const tracker = createEventTracker({ projectId: 'proj_123', platform: 'web' });
|
|
219
|
+
*
|
|
220
|
+
* // Funnel tracking
|
|
221
|
+
* tracker.track('screen_viewed', { screen: 'method_selector', flow_type: 'deposit', step: 1 });
|
|
222
|
+
* tracker.track('payment_method_selected', { method: 'transfer_crypto', flow_type: 'deposit' });
|
|
223
|
+
* tracker.track('screen_viewed', { screen: 'amount_entry', flow_type: 'deposit', step: 2 });
|
|
224
|
+
* tracker.track('flow_completed', { method: 'transfer_crypto', flow_type: 'deposit', amount_usd: '100' });
|
|
225
|
+
* ```
|
|
226
|
+
*/
|
|
227
|
+
declare class EventTracker {
|
|
228
|
+
private emitter;
|
|
229
|
+
private config;
|
|
230
|
+
private context;
|
|
231
|
+
private recentEvents;
|
|
232
|
+
private unsubForwarder;
|
|
233
|
+
private userId;
|
|
234
|
+
/** Set only while a journey is active (modal open → close). */
|
|
235
|
+
private sessionId;
|
|
236
|
+
constructor(config: AnalyticsConfig);
|
|
237
|
+
private attachForwarder;
|
|
238
|
+
private detachForwarder;
|
|
239
|
+
/**
|
|
240
|
+
* Track an event. Properties are flat key-value pairs that become
|
|
241
|
+
* directly filterable dimensions in PostHog.
|
|
242
|
+
*
|
|
243
|
+
* Duplicate calls with the same event name + properties within the
|
|
244
|
+
* dedup window are silently dropped.
|
|
245
|
+
*/
|
|
246
|
+
track(eventName: AnalyticsEventName, properties?: AnalyticsProperties): void;
|
|
247
|
+
/**
|
|
248
|
+
* Associate subsequent events with an internal Unifold user.id
|
|
249
|
+
* (`user_<ksuid>`). Pass `null` to clear. Usually called once the
|
|
250
|
+
* deposit-addresses response returns the id.
|
|
251
|
+
*/
|
|
252
|
+
setUserId(userId: string | null): void;
|
|
253
|
+
/**
|
|
254
|
+
* Start a new analytics journey (modal open). Mints an `asess_<ksuid>` and
|
|
255
|
+
* returns it. Every {@link EventTracker.track} call until
|
|
256
|
+
* {@link EventTracker.endSession} carries it.
|
|
257
|
+
*
|
|
258
|
+
* Does not touch `userId`: the internal `user_<ksuid>` is attached separately
|
|
259
|
+
* via {@link EventTracker.setUserId} once the deposit-addresses response
|
|
260
|
+
* returns, and persists until the host replaces it.
|
|
261
|
+
*/
|
|
262
|
+
startSession(): string;
|
|
263
|
+
/**
|
|
264
|
+
* End the current journey (modal close). Clears the session id so any stray
|
|
265
|
+
* events after close are session-less. Call this *after* the terminal
|
|
266
|
+
* `widget_closed` track call.
|
|
267
|
+
*/
|
|
268
|
+
endSession(): void;
|
|
269
|
+
/** Current analytics journey id, or `undefined` when no journey is active. */
|
|
270
|
+
getSessionId(): string | undefined;
|
|
271
|
+
/**
|
|
272
|
+
* Subscribe to a specific event or `'*'` for all events.
|
|
273
|
+
* Returns an unsubscribe function.
|
|
274
|
+
*/
|
|
275
|
+
on(eventName: AnalyticsEventName | '*', handler: (event: AnalyticsEvent) => void): () => void;
|
|
276
|
+
/** Remove all listeners and stop forwarding. */
|
|
277
|
+
destroy(): void;
|
|
278
|
+
/** Enable or disable network forwarding at runtime. Custom listeners are preserved. */
|
|
279
|
+
setDisabled(disabled: boolean): void;
|
|
280
|
+
}
|
|
281
|
+
/** Create a new EventTracker instance. */
|
|
282
|
+
declare function createEventTracker(config: AnalyticsConfig): EventTracker;
|
|
283
|
+
|
|
284
|
+
type EventHandler<T> = (event: T) => void;
|
|
285
|
+
/**
|
|
286
|
+
* Minimal typed event emitter with wildcard support.
|
|
287
|
+
*
|
|
288
|
+
* - `on()` returns an unsubscribe function.
|
|
289
|
+
* - `'*'` subscribes to every event type.
|
|
290
|
+
* - Handler exceptions are caught so a faulty listener never breaks the host app.
|
|
291
|
+
*/
|
|
292
|
+
declare class Emitter<EventMap extends Record<string, unknown>> {
|
|
293
|
+
private handlers;
|
|
294
|
+
on<K extends keyof EventMap>(type: K, handler: EventHandler<EventMap[K]>): () => void;
|
|
295
|
+
on(type: '*', handler: EventHandler<EventMap[keyof EventMap]>): () => void;
|
|
296
|
+
emit<K extends keyof EventMap>(type: K, event: EventMap[K]): void;
|
|
297
|
+
removeAllListeners(): void;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Fire-and-forget POST of an analytics event.
|
|
302
|
+
*
|
|
303
|
+
* - Sends the publishable key via `x-publishable-key` header for auth.
|
|
304
|
+
* - Uses `keepalive: true` so the request completes even during page unload.
|
|
305
|
+
* - Never throws — failures are silently swallowed (optionally logged in debug mode).
|
|
306
|
+
*/
|
|
307
|
+
declare function forwardEvent(event: AnalyticsEvent, publishableKey: string, endpoint?: string, debug?: boolean): void;
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Dependency-free KSUID generator.
|
|
311
|
+
*
|
|
312
|
+
* Kept in sync with `@unifold/core`'s implementation (packages/core/src/lib/utils.ts)
|
|
313
|
+
* but duplicated intentionally: `@unifold/analytics` is a zero-dependency leaf
|
|
314
|
+
* package and must not depend on `@unifold/core`.
|
|
315
|
+
*/
|
|
316
|
+
/**
|
|
317
|
+
* Generate a KSUID-like ID without external dependencies.
|
|
318
|
+
* Format: `base62(4-byte-timestamp + 16-random-bytes)` (27 chars).
|
|
319
|
+
*/
|
|
320
|
+
declare function generateKSUID(): string;
|
|
321
|
+
/**
|
|
322
|
+
* Generate a KSUID-like prefixed ID without external dependencies.
|
|
323
|
+
* Format: `{prefix}_{base62(...)}`.
|
|
324
|
+
*/
|
|
325
|
+
declare function generatePrefixedKSUID(prefix: string): string;
|
|
326
|
+
|
|
327
|
+
declare const PACKAGE_VERSION = "0.1.72";
|
|
328
|
+
|
|
329
|
+
export { type AnalyticsConfig, type AnalyticsContext, type AnalyticsEvent, type AnalyticsEventName, AnalyticsEvents, type AnalyticsProperties, Emitter, EventTracker, PACKAGE_VERSION, createEventTracker, forwardEvent, generateKSUID, generatePrefixedKSUID };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
5
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
6
|
+
|
|
7
|
+
// src/emitter.ts
|
|
8
|
+
var Emitter = class {
|
|
9
|
+
constructor() {
|
|
10
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
11
|
+
}
|
|
12
|
+
on(type, handler) {
|
|
13
|
+
let set = this.handlers.get(type);
|
|
14
|
+
if (!set) {
|
|
15
|
+
set = /* @__PURE__ */ new Set();
|
|
16
|
+
this.handlers.set(type, set);
|
|
17
|
+
}
|
|
18
|
+
set.add(handler);
|
|
19
|
+
return () => {
|
|
20
|
+
set.delete(handler);
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
emit(type, event) {
|
|
24
|
+
const dispatch = (handler) => {
|
|
25
|
+
try {
|
|
26
|
+
handler(event);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("[unifold/event-tracker] handler threw", err);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
this.handlers.get(type)?.forEach(dispatch);
|
|
32
|
+
this.handlers.get("*")?.forEach(dispatch);
|
|
33
|
+
}
|
|
34
|
+
removeAllListeners() {
|
|
35
|
+
this.handlers.clear();
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// src/forwarder.ts
|
|
40
|
+
var DEFAULT_ENDPOINT = "/v1/public/events";
|
|
41
|
+
function forwardEvent(event, publishableKey, endpoint = DEFAULT_ENDPOINT, debug = false) {
|
|
42
|
+
try {
|
|
43
|
+
if (typeof fetch === "undefined") {
|
|
44
|
+
if (debug) console.debug("[unifold/analytics] fetch unavailable, skipping forward");
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const { sessionId, userId, ...rest } = event;
|
|
48
|
+
const body = {
|
|
49
|
+
...rest,
|
|
50
|
+
// Only attach session_id while a journey is active (modal open → close).
|
|
51
|
+
...sessionId ? { session_id: sessionId } : {},
|
|
52
|
+
...userId ? { user_id: userId } : {}
|
|
53
|
+
};
|
|
54
|
+
fetch(endpoint, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
"x-publishable-key": publishableKey
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify(body),
|
|
61
|
+
keepalive: true
|
|
62
|
+
}).catch((err) => {
|
|
63
|
+
if (debug) console.debug("[unifold/analytics] forward failed", err);
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (debug) console.debug("[unifold/analytics] forward error", err);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/ksuid.ts
|
|
71
|
+
function generateKSUID() {
|
|
72
|
+
const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
73
|
+
const KSUID_EPOCH = 14e8;
|
|
74
|
+
const timestampSeconds = Math.floor(Date.now() / 1e3) - KSUID_EPOCH;
|
|
75
|
+
const payload = new Uint8Array(20);
|
|
76
|
+
payload[0] = timestampSeconds >>> 24 & 255;
|
|
77
|
+
payload[1] = timestampSeconds >>> 16 & 255;
|
|
78
|
+
payload[2] = timestampSeconds >>> 8 & 255;
|
|
79
|
+
payload[3] = timestampSeconds & 255;
|
|
80
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
81
|
+
crypto.getRandomValues(payload.subarray(4));
|
|
82
|
+
} else {
|
|
83
|
+
for (let i = 4; i < 20; i++) {
|
|
84
|
+
payload[i] = Math.floor(Math.random() * 256);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
let value = 0n;
|
|
88
|
+
for (const byte of payload) {
|
|
89
|
+
value = value << 8n | BigInt(byte);
|
|
90
|
+
}
|
|
91
|
+
let encoded = "";
|
|
92
|
+
while (value > 0n) {
|
|
93
|
+
encoded = BASE62[Number(value % 62n)] + encoded;
|
|
94
|
+
value = value / 62n;
|
|
95
|
+
}
|
|
96
|
+
return encoded.padStart(27, "0");
|
|
97
|
+
}
|
|
98
|
+
function generatePrefixedKSUID(prefix) {
|
|
99
|
+
return `${prefix}_${generateKSUID()}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/version.ts
|
|
103
|
+
var PACKAGE_VERSION = "0.1.72";
|
|
104
|
+
|
|
105
|
+
// src/tracker.ts
|
|
106
|
+
var LIBRARY_NAME = "@unifold/analytics";
|
|
107
|
+
var DEFAULT_DEDUP_MS = 500;
|
|
108
|
+
var SESSION_PREFIX = "asess";
|
|
109
|
+
function dedupKey(sessionId, eventName, properties) {
|
|
110
|
+
const sorted = Object.keys(properties).sort().map((k) => `${k}=${String(properties[k])}`).join("&");
|
|
111
|
+
return `${sessionId ?? ""}|${eventName}|${sorted}`;
|
|
112
|
+
}
|
|
113
|
+
var EventTracker = class {
|
|
114
|
+
constructor(config) {
|
|
115
|
+
__publicField(this, "emitter", new Emitter());
|
|
116
|
+
__publicField(this, "config");
|
|
117
|
+
__publicField(this, "context");
|
|
118
|
+
__publicField(this, "recentEvents", /* @__PURE__ */ new Map());
|
|
119
|
+
__publicField(this, "unsubForwarder", null);
|
|
120
|
+
__publicField(this, "userId");
|
|
121
|
+
/** Set only while a journey is active (modal open → close). */
|
|
122
|
+
__publicField(this, "sessionId");
|
|
123
|
+
this.config = {
|
|
124
|
+
endpoint: config.endpoint ?? "/v1/public/events",
|
|
125
|
+
publishableKey: config.publishableKey,
|
|
126
|
+
sdkVersion: config.sdkVersion ?? PACKAGE_VERSION,
|
|
127
|
+
platform: config.platform ?? "web",
|
|
128
|
+
userId: config.userId ?? "",
|
|
129
|
+
disabled: config.disabled ?? false,
|
|
130
|
+
debug: config.debug ?? false,
|
|
131
|
+
dedupMs: config.dedupMs ?? DEFAULT_DEDUP_MS
|
|
132
|
+
};
|
|
133
|
+
this.userId = config.userId || void 0;
|
|
134
|
+
this.context = {
|
|
135
|
+
library: {
|
|
136
|
+
name: LIBRARY_NAME,
|
|
137
|
+
version: this.config.sdkVersion
|
|
138
|
+
},
|
|
139
|
+
platform: this.config.platform
|
|
140
|
+
};
|
|
141
|
+
if (!this.config.disabled) {
|
|
142
|
+
this.attachForwarder();
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
attachForwarder() {
|
|
146
|
+
this.unsubForwarder = this.emitter.on("*", (event) => {
|
|
147
|
+
forwardEvent(event, this.config.publishableKey, this.config.endpoint, this.config.debug);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
detachForwarder() {
|
|
151
|
+
this.unsubForwarder?.();
|
|
152
|
+
this.unsubForwarder = null;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Track an event. Properties are flat key-value pairs that become
|
|
156
|
+
* directly filterable dimensions in PostHog.
|
|
157
|
+
*
|
|
158
|
+
* Duplicate calls with the same event name + properties within the
|
|
159
|
+
* dedup window are silently dropped.
|
|
160
|
+
*/
|
|
161
|
+
track(eventName, properties) {
|
|
162
|
+
const props = properties ?? {};
|
|
163
|
+
if (this.config.dedupMs > 0) {
|
|
164
|
+
const key = dedupKey(this.sessionId, eventName, props);
|
|
165
|
+
const now = Date.now();
|
|
166
|
+
const lastSeen = this.recentEvents.get(key);
|
|
167
|
+
if (lastSeen !== void 0 && now - lastSeen < this.config.dedupMs) {
|
|
168
|
+
if (this.config.debug) {
|
|
169
|
+
console.debug("[unifold/analytics] dedup suppressed", eventName);
|
|
170
|
+
}
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
this.recentEvents.set(key, now);
|
|
174
|
+
if (this.recentEvents.size > 200) {
|
|
175
|
+
const cutoff = now - this.config.dedupMs;
|
|
176
|
+
for (const [k, ts] of this.recentEvents) {
|
|
177
|
+
if (ts < cutoff) this.recentEvents.delete(k);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const event = {
|
|
182
|
+
event: eventName,
|
|
183
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
184
|
+
sessionId: this.sessionId,
|
|
185
|
+
userId: this.userId,
|
|
186
|
+
properties: props,
|
|
187
|
+
context: this.context
|
|
188
|
+
};
|
|
189
|
+
if (this.config.debug) {
|
|
190
|
+
console.debug("[unifold/analytics] track", eventName, event);
|
|
191
|
+
}
|
|
192
|
+
this.emitter.emit(eventName, event);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Associate subsequent events with an internal Unifold user.id
|
|
196
|
+
* (`user_<ksuid>`). Pass `null` to clear. Usually called once the
|
|
197
|
+
* deposit-addresses response returns the id.
|
|
198
|
+
*/
|
|
199
|
+
setUserId(userId) {
|
|
200
|
+
this.userId = userId || void 0;
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Start a new analytics journey (modal open). Mints an `asess_<ksuid>` and
|
|
204
|
+
* returns it. Every {@link EventTracker.track} call until
|
|
205
|
+
* {@link EventTracker.endSession} carries it.
|
|
206
|
+
*
|
|
207
|
+
* Does not touch `userId`: the internal `user_<ksuid>` is attached separately
|
|
208
|
+
* via {@link EventTracker.setUserId} once the deposit-addresses response
|
|
209
|
+
* returns, and persists until the host replaces it.
|
|
210
|
+
*/
|
|
211
|
+
startSession() {
|
|
212
|
+
this.sessionId = generatePrefixedKSUID(SESSION_PREFIX);
|
|
213
|
+
return this.sessionId;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* End the current journey (modal close). Clears the session id so any stray
|
|
217
|
+
* events after close are session-less. Call this *after* the terminal
|
|
218
|
+
* `widget_closed` track call.
|
|
219
|
+
*/
|
|
220
|
+
endSession() {
|
|
221
|
+
this.sessionId = void 0;
|
|
222
|
+
}
|
|
223
|
+
/** Current analytics journey id, or `undefined` when no journey is active. */
|
|
224
|
+
getSessionId() {
|
|
225
|
+
return this.sessionId;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Subscribe to a specific event or `'*'` for all events.
|
|
229
|
+
* Returns an unsubscribe function.
|
|
230
|
+
*/
|
|
231
|
+
on(eventName, handler) {
|
|
232
|
+
return this.emitter.on(eventName, handler);
|
|
233
|
+
}
|
|
234
|
+
/** Remove all listeners and stop forwarding. */
|
|
235
|
+
destroy() {
|
|
236
|
+
this.emitter.removeAllListeners();
|
|
237
|
+
this.recentEvents.clear();
|
|
238
|
+
}
|
|
239
|
+
/** Enable or disable network forwarding at runtime. Custom listeners are preserved. */
|
|
240
|
+
setDisabled(disabled) {
|
|
241
|
+
if (disabled && !this.config.disabled) {
|
|
242
|
+
this.detachForwarder();
|
|
243
|
+
} else if (!disabled && this.config.disabled) {
|
|
244
|
+
this.attachForwarder();
|
|
245
|
+
}
|
|
246
|
+
this.config.disabled = disabled;
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
function createEventTracker(config) {
|
|
250
|
+
return new EventTracker(config);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// src/types.ts
|
|
254
|
+
var AnalyticsEvents = {
|
|
255
|
+
/** User viewed a screen / step in the flow. */
|
|
256
|
+
SCREEN_VIEWED: "screen_viewed",
|
|
257
|
+
/** User selected a payment method. */
|
|
258
|
+
PAYMENT_METHOD_SELECTED: "payment_method_selected",
|
|
259
|
+
/**
|
|
260
|
+
* User picked an asset — the token *and* the chain it lives on. One event
|
|
261
|
+
* for both, carrying the full identity, because neither half identifies an
|
|
262
|
+
* asset alone; either dropdown changing re-fires it.
|
|
263
|
+
*/
|
|
264
|
+
TOKEN_SELECTED: "token_selected",
|
|
265
|
+
/** User picked a browser wallet to connect. */
|
|
266
|
+
WALLET_SELECTED: "wallet_selected",
|
|
267
|
+
/** User picked an onramp/exchange provider (card quote, bank, exchange). */
|
|
268
|
+
PROVIDER_SELECTED: "provider_selected",
|
|
269
|
+
/**
|
|
270
|
+
* User picked what to pay with inside a provider's flow — a card, or a
|
|
271
|
+
* wallet like Apple Pay. Distinct from `payment_method_selected`, which is
|
|
272
|
+
* the funding route they chose from the deposit menu.
|
|
273
|
+
*/
|
|
274
|
+
PAYMENT_METHOD_TYPE_SELECTED: "payment_method_type_selected",
|
|
275
|
+
/**
|
|
276
|
+
* User reached a step that asks them to prove something about themselves —
|
|
277
|
+
* identity details, a document, an emailed or texted code, an exchange MFA
|
|
278
|
+
* prompt. `verification_type` says which.
|
|
279
|
+
*/
|
|
280
|
+
VERIFICATION_STARTED: "verification_started",
|
|
281
|
+
/** User handed over what the step asked for. A provider may still be reviewing. */
|
|
282
|
+
VERIFICATION_SUBMITTED: "verification_submitted",
|
|
283
|
+
/** The provider accepted it. */
|
|
284
|
+
VERIFICATION_COMPLETED: "verification_completed",
|
|
285
|
+
/** The provider rejected it, or the user backed out. See `failure_reason`. */
|
|
286
|
+
VERIFICATION_FAILED: "verification_failed",
|
|
287
|
+
/**
|
|
288
|
+
* User began linking an account they hold somewhere else — an exchange, a
|
|
289
|
+
* Stripe Link profile. Distinct from connecting a browser wallet, which the
|
|
290
|
+
* user already controls locally.
|
|
291
|
+
*/
|
|
292
|
+
ACCOUNT_CONNECTION_STARTED: "account_connection_started",
|
|
293
|
+
/** The external account is linked and usable. */
|
|
294
|
+
ACCOUNT_CONNECTED: "account_connected",
|
|
295
|
+
/** Linking the external account failed or was abandoned. */
|
|
296
|
+
ACCOUNT_CONNECTION_FAILED: "account_connection_failed",
|
|
297
|
+
/** User began connecting a browser wallet. */
|
|
298
|
+
WALLET_CONNECTION_STARTED: "wallet_connection_started",
|
|
299
|
+
/** The wallet is connected and usable. */
|
|
300
|
+
WALLET_CONNECTED: "wallet_connected",
|
|
301
|
+
/** The connection failed, or the user declined it in their wallet. */
|
|
302
|
+
WALLET_CONNECTION_FAILED: "wallet_connection_failed",
|
|
303
|
+
/** User navigated back a step. */
|
|
304
|
+
BACK_CLICKED: "back_clicked",
|
|
305
|
+
/** User started a flow (deposit, checkout, withdraw). */
|
|
306
|
+
FLOW_STARTED: "flow_started",
|
|
307
|
+
/** User completed a flow successfully. */
|
|
308
|
+
FLOW_COMPLETED: "flow_completed",
|
|
309
|
+
/** A flow failed with an error. */
|
|
310
|
+
FLOW_FAILED: "flow_failed",
|
|
311
|
+
/** Generic UI interaction (button press, link tap). */
|
|
312
|
+
BUTTON_CLICKED: "button_clicked",
|
|
313
|
+
/** Widget/modal opened. */
|
|
314
|
+
WIDGET_OPENED: "widget_opened",
|
|
315
|
+
/** Widget/modal closed. */
|
|
316
|
+
WIDGET_CLOSED: "widget_closed"
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
exports.AnalyticsEvents = AnalyticsEvents;
|
|
320
|
+
exports.Emitter = Emitter;
|
|
321
|
+
exports.EventTracker = EventTracker;
|
|
322
|
+
exports.PACKAGE_VERSION = PACKAGE_VERSION;
|
|
323
|
+
exports.createEventTracker = createEventTracker;
|
|
324
|
+
exports.forwardEvent = forwardEvent;
|
|
325
|
+
exports.generateKSUID = generateKSUID;
|
|
326
|
+
exports.generatePrefixedKSUID = generatePrefixedKSUID;
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
|
+
|
|
5
|
+
// src/emitter.ts
|
|
6
|
+
var Emitter = class {
|
|
7
|
+
constructor() {
|
|
8
|
+
__publicField(this, "handlers", /* @__PURE__ */ new Map());
|
|
9
|
+
}
|
|
10
|
+
on(type, handler) {
|
|
11
|
+
let set = this.handlers.get(type);
|
|
12
|
+
if (!set) {
|
|
13
|
+
set = /* @__PURE__ */ new Set();
|
|
14
|
+
this.handlers.set(type, set);
|
|
15
|
+
}
|
|
16
|
+
set.add(handler);
|
|
17
|
+
return () => {
|
|
18
|
+
set.delete(handler);
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
emit(type, event) {
|
|
22
|
+
const dispatch = (handler) => {
|
|
23
|
+
try {
|
|
24
|
+
handler(event);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error("[unifold/event-tracker] handler threw", err);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
this.handlers.get(type)?.forEach(dispatch);
|
|
30
|
+
this.handlers.get("*")?.forEach(dispatch);
|
|
31
|
+
}
|
|
32
|
+
removeAllListeners() {
|
|
33
|
+
this.handlers.clear();
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// src/forwarder.ts
|
|
38
|
+
var DEFAULT_ENDPOINT = "/v1/public/events";
|
|
39
|
+
function forwardEvent(event, publishableKey, endpoint = DEFAULT_ENDPOINT, debug = false) {
|
|
40
|
+
try {
|
|
41
|
+
if (typeof fetch === "undefined") {
|
|
42
|
+
if (debug) console.debug("[unifold/analytics] fetch unavailable, skipping forward");
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const { sessionId, userId, ...rest } = event;
|
|
46
|
+
const body = {
|
|
47
|
+
...rest,
|
|
48
|
+
// Only attach session_id while a journey is active (modal open → close).
|
|
49
|
+
...sessionId ? { session_id: sessionId } : {},
|
|
50
|
+
...userId ? { user_id: userId } : {}
|
|
51
|
+
};
|
|
52
|
+
fetch(endpoint, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
"Content-Type": "application/json",
|
|
56
|
+
"x-publishable-key": publishableKey
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(body),
|
|
59
|
+
keepalive: true
|
|
60
|
+
}).catch((err) => {
|
|
61
|
+
if (debug) console.debug("[unifold/analytics] forward failed", err);
|
|
62
|
+
});
|
|
63
|
+
} catch (err) {
|
|
64
|
+
if (debug) console.debug("[unifold/analytics] forward error", err);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/ksuid.ts
|
|
69
|
+
function generateKSUID() {
|
|
70
|
+
const BASE62 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
|
71
|
+
const KSUID_EPOCH = 14e8;
|
|
72
|
+
const timestampSeconds = Math.floor(Date.now() / 1e3) - KSUID_EPOCH;
|
|
73
|
+
const payload = new Uint8Array(20);
|
|
74
|
+
payload[0] = timestampSeconds >>> 24 & 255;
|
|
75
|
+
payload[1] = timestampSeconds >>> 16 & 255;
|
|
76
|
+
payload[2] = timestampSeconds >>> 8 & 255;
|
|
77
|
+
payload[3] = timestampSeconds & 255;
|
|
78
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
79
|
+
crypto.getRandomValues(payload.subarray(4));
|
|
80
|
+
} else {
|
|
81
|
+
for (let i = 4; i < 20; i++) {
|
|
82
|
+
payload[i] = Math.floor(Math.random() * 256);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
let value = 0n;
|
|
86
|
+
for (const byte of payload) {
|
|
87
|
+
value = value << 8n | BigInt(byte);
|
|
88
|
+
}
|
|
89
|
+
let encoded = "";
|
|
90
|
+
while (value > 0n) {
|
|
91
|
+
encoded = BASE62[Number(value % 62n)] + encoded;
|
|
92
|
+
value = value / 62n;
|
|
93
|
+
}
|
|
94
|
+
return encoded.padStart(27, "0");
|
|
95
|
+
}
|
|
96
|
+
function generatePrefixedKSUID(prefix) {
|
|
97
|
+
return `${prefix}_${generateKSUID()}`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/version.ts
|
|
101
|
+
var PACKAGE_VERSION = "0.1.72";
|
|
102
|
+
|
|
103
|
+
// src/tracker.ts
|
|
104
|
+
var LIBRARY_NAME = "@unifold/analytics";
|
|
105
|
+
var DEFAULT_DEDUP_MS = 500;
|
|
106
|
+
var SESSION_PREFIX = "asess";
|
|
107
|
+
function dedupKey(sessionId, eventName, properties) {
|
|
108
|
+
const sorted = Object.keys(properties).sort().map((k) => `${k}=${String(properties[k])}`).join("&");
|
|
109
|
+
return `${sessionId ?? ""}|${eventName}|${sorted}`;
|
|
110
|
+
}
|
|
111
|
+
var EventTracker = class {
|
|
112
|
+
constructor(config) {
|
|
113
|
+
__publicField(this, "emitter", new Emitter());
|
|
114
|
+
__publicField(this, "config");
|
|
115
|
+
__publicField(this, "context");
|
|
116
|
+
__publicField(this, "recentEvents", /* @__PURE__ */ new Map());
|
|
117
|
+
__publicField(this, "unsubForwarder", null);
|
|
118
|
+
__publicField(this, "userId");
|
|
119
|
+
/** Set only while a journey is active (modal open → close). */
|
|
120
|
+
__publicField(this, "sessionId");
|
|
121
|
+
this.config = {
|
|
122
|
+
endpoint: config.endpoint ?? "/v1/public/events",
|
|
123
|
+
publishableKey: config.publishableKey,
|
|
124
|
+
sdkVersion: config.sdkVersion ?? PACKAGE_VERSION,
|
|
125
|
+
platform: config.platform ?? "web",
|
|
126
|
+
userId: config.userId ?? "",
|
|
127
|
+
disabled: config.disabled ?? false,
|
|
128
|
+
debug: config.debug ?? false,
|
|
129
|
+
dedupMs: config.dedupMs ?? DEFAULT_DEDUP_MS
|
|
130
|
+
};
|
|
131
|
+
this.userId = config.userId || void 0;
|
|
132
|
+
this.context = {
|
|
133
|
+
library: {
|
|
134
|
+
name: LIBRARY_NAME,
|
|
135
|
+
version: this.config.sdkVersion
|
|
136
|
+
},
|
|
137
|
+
platform: this.config.platform
|
|
138
|
+
};
|
|
139
|
+
if (!this.config.disabled) {
|
|
140
|
+
this.attachForwarder();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
attachForwarder() {
|
|
144
|
+
this.unsubForwarder = this.emitter.on("*", (event) => {
|
|
145
|
+
forwardEvent(event, this.config.publishableKey, this.config.endpoint, this.config.debug);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
detachForwarder() {
|
|
149
|
+
this.unsubForwarder?.();
|
|
150
|
+
this.unsubForwarder = null;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Track an event. Properties are flat key-value pairs that become
|
|
154
|
+
* directly filterable dimensions in PostHog.
|
|
155
|
+
*
|
|
156
|
+
* Duplicate calls with the same event name + properties within the
|
|
157
|
+
* dedup window are silently dropped.
|
|
158
|
+
*/
|
|
159
|
+
track(eventName, properties) {
|
|
160
|
+
const props = properties ?? {};
|
|
161
|
+
if (this.config.dedupMs > 0) {
|
|
162
|
+
const key = dedupKey(this.sessionId, eventName, props);
|
|
163
|
+
const now = Date.now();
|
|
164
|
+
const lastSeen = this.recentEvents.get(key);
|
|
165
|
+
if (lastSeen !== void 0 && now - lastSeen < this.config.dedupMs) {
|
|
166
|
+
if (this.config.debug) {
|
|
167
|
+
console.debug("[unifold/analytics] dedup suppressed", eventName);
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
this.recentEvents.set(key, now);
|
|
172
|
+
if (this.recentEvents.size > 200) {
|
|
173
|
+
const cutoff = now - this.config.dedupMs;
|
|
174
|
+
for (const [k, ts] of this.recentEvents) {
|
|
175
|
+
if (ts < cutoff) this.recentEvents.delete(k);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
const event = {
|
|
180
|
+
event: eventName,
|
|
181
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
182
|
+
sessionId: this.sessionId,
|
|
183
|
+
userId: this.userId,
|
|
184
|
+
properties: props,
|
|
185
|
+
context: this.context
|
|
186
|
+
};
|
|
187
|
+
if (this.config.debug) {
|
|
188
|
+
console.debug("[unifold/analytics] track", eventName, event);
|
|
189
|
+
}
|
|
190
|
+
this.emitter.emit(eventName, event);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Associate subsequent events with an internal Unifold user.id
|
|
194
|
+
* (`user_<ksuid>`). Pass `null` to clear. Usually called once the
|
|
195
|
+
* deposit-addresses response returns the id.
|
|
196
|
+
*/
|
|
197
|
+
setUserId(userId) {
|
|
198
|
+
this.userId = userId || void 0;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Start a new analytics journey (modal open). Mints an `asess_<ksuid>` and
|
|
202
|
+
* returns it. Every {@link EventTracker.track} call until
|
|
203
|
+
* {@link EventTracker.endSession} carries it.
|
|
204
|
+
*
|
|
205
|
+
* Does not touch `userId`: the internal `user_<ksuid>` is attached separately
|
|
206
|
+
* via {@link EventTracker.setUserId} once the deposit-addresses response
|
|
207
|
+
* returns, and persists until the host replaces it.
|
|
208
|
+
*/
|
|
209
|
+
startSession() {
|
|
210
|
+
this.sessionId = generatePrefixedKSUID(SESSION_PREFIX);
|
|
211
|
+
return this.sessionId;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* End the current journey (modal close). Clears the session id so any stray
|
|
215
|
+
* events after close are session-less. Call this *after* the terminal
|
|
216
|
+
* `widget_closed` track call.
|
|
217
|
+
*/
|
|
218
|
+
endSession() {
|
|
219
|
+
this.sessionId = void 0;
|
|
220
|
+
}
|
|
221
|
+
/** Current analytics journey id, or `undefined` when no journey is active. */
|
|
222
|
+
getSessionId() {
|
|
223
|
+
return this.sessionId;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Subscribe to a specific event or `'*'` for all events.
|
|
227
|
+
* Returns an unsubscribe function.
|
|
228
|
+
*/
|
|
229
|
+
on(eventName, handler) {
|
|
230
|
+
return this.emitter.on(eventName, handler);
|
|
231
|
+
}
|
|
232
|
+
/** Remove all listeners and stop forwarding. */
|
|
233
|
+
destroy() {
|
|
234
|
+
this.emitter.removeAllListeners();
|
|
235
|
+
this.recentEvents.clear();
|
|
236
|
+
}
|
|
237
|
+
/** Enable or disable network forwarding at runtime. Custom listeners are preserved. */
|
|
238
|
+
setDisabled(disabled) {
|
|
239
|
+
if (disabled && !this.config.disabled) {
|
|
240
|
+
this.detachForwarder();
|
|
241
|
+
} else if (!disabled && this.config.disabled) {
|
|
242
|
+
this.attachForwarder();
|
|
243
|
+
}
|
|
244
|
+
this.config.disabled = disabled;
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
function createEventTracker(config) {
|
|
248
|
+
return new EventTracker(config);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/types.ts
|
|
252
|
+
var AnalyticsEvents = {
|
|
253
|
+
/** User viewed a screen / step in the flow. */
|
|
254
|
+
SCREEN_VIEWED: "screen_viewed",
|
|
255
|
+
/** User selected a payment method. */
|
|
256
|
+
PAYMENT_METHOD_SELECTED: "payment_method_selected",
|
|
257
|
+
/**
|
|
258
|
+
* User picked an asset — the token *and* the chain it lives on. One event
|
|
259
|
+
* for both, carrying the full identity, because neither half identifies an
|
|
260
|
+
* asset alone; either dropdown changing re-fires it.
|
|
261
|
+
*/
|
|
262
|
+
TOKEN_SELECTED: "token_selected",
|
|
263
|
+
/** User picked a browser wallet to connect. */
|
|
264
|
+
WALLET_SELECTED: "wallet_selected",
|
|
265
|
+
/** User picked an onramp/exchange provider (card quote, bank, exchange). */
|
|
266
|
+
PROVIDER_SELECTED: "provider_selected",
|
|
267
|
+
/**
|
|
268
|
+
* User picked what to pay with inside a provider's flow — a card, or a
|
|
269
|
+
* wallet like Apple Pay. Distinct from `payment_method_selected`, which is
|
|
270
|
+
* the funding route they chose from the deposit menu.
|
|
271
|
+
*/
|
|
272
|
+
PAYMENT_METHOD_TYPE_SELECTED: "payment_method_type_selected",
|
|
273
|
+
/**
|
|
274
|
+
* User reached a step that asks them to prove something about themselves —
|
|
275
|
+
* identity details, a document, an emailed or texted code, an exchange MFA
|
|
276
|
+
* prompt. `verification_type` says which.
|
|
277
|
+
*/
|
|
278
|
+
VERIFICATION_STARTED: "verification_started",
|
|
279
|
+
/** User handed over what the step asked for. A provider may still be reviewing. */
|
|
280
|
+
VERIFICATION_SUBMITTED: "verification_submitted",
|
|
281
|
+
/** The provider accepted it. */
|
|
282
|
+
VERIFICATION_COMPLETED: "verification_completed",
|
|
283
|
+
/** The provider rejected it, or the user backed out. See `failure_reason`. */
|
|
284
|
+
VERIFICATION_FAILED: "verification_failed",
|
|
285
|
+
/**
|
|
286
|
+
* User began linking an account they hold somewhere else — an exchange, a
|
|
287
|
+
* Stripe Link profile. Distinct from connecting a browser wallet, which the
|
|
288
|
+
* user already controls locally.
|
|
289
|
+
*/
|
|
290
|
+
ACCOUNT_CONNECTION_STARTED: "account_connection_started",
|
|
291
|
+
/** The external account is linked and usable. */
|
|
292
|
+
ACCOUNT_CONNECTED: "account_connected",
|
|
293
|
+
/** Linking the external account failed or was abandoned. */
|
|
294
|
+
ACCOUNT_CONNECTION_FAILED: "account_connection_failed",
|
|
295
|
+
/** User began connecting a browser wallet. */
|
|
296
|
+
WALLET_CONNECTION_STARTED: "wallet_connection_started",
|
|
297
|
+
/** The wallet is connected and usable. */
|
|
298
|
+
WALLET_CONNECTED: "wallet_connected",
|
|
299
|
+
/** The connection failed, or the user declined it in their wallet. */
|
|
300
|
+
WALLET_CONNECTION_FAILED: "wallet_connection_failed",
|
|
301
|
+
/** User navigated back a step. */
|
|
302
|
+
BACK_CLICKED: "back_clicked",
|
|
303
|
+
/** User started a flow (deposit, checkout, withdraw). */
|
|
304
|
+
FLOW_STARTED: "flow_started",
|
|
305
|
+
/** User completed a flow successfully. */
|
|
306
|
+
FLOW_COMPLETED: "flow_completed",
|
|
307
|
+
/** A flow failed with an error. */
|
|
308
|
+
FLOW_FAILED: "flow_failed",
|
|
309
|
+
/** Generic UI interaction (button press, link tap). */
|
|
310
|
+
BUTTON_CLICKED: "button_clicked",
|
|
311
|
+
/** Widget/modal opened. */
|
|
312
|
+
WIDGET_OPENED: "widget_opened",
|
|
313
|
+
/** Widget/modal closed. */
|
|
314
|
+
WIDGET_CLOSED: "widget_closed"
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
export { AnalyticsEvents, Emitter, EventTracker, PACKAGE_VERSION, createEventTracker, forwardEvent, generateKSUID, generatePrefixedKSUID };
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unifold/analytics",
|
|
3
|
+
"version": "0.1.72",
|
|
4
|
+
"description": "Lightweight internal analytics for the Unifold SDK — zero external dependencies",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@typescript-eslint/eslint-plugin": "^6.21.0",
|
|
20
|
+
"@typescript-eslint/parser": "^6.21.0",
|
|
21
|
+
"eslint": "^8.57.0",
|
|
22
|
+
"eslint-config-prettier": "^9.1.0",
|
|
23
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
24
|
+
"prettier": "^3.2.5",
|
|
25
|
+
"tsup": "^8.0.0",
|
|
26
|
+
"typescript": "^5.0.0",
|
|
27
|
+
"vitest": "^2.1.9"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"unifold",
|
|
31
|
+
"analytics",
|
|
32
|
+
"telemetry",
|
|
33
|
+
"sdk"
|
|
34
|
+
],
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"dev": "tsup --watch",
|
|
39
|
+
"clean": "rm -rf dist",
|
|
40
|
+
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
41
|
+
"lint:fix": "eslint \"src/**/*.{ts,tsx}\" --fix",
|
|
42
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\"",
|
|
43
|
+
"format:check": "prettier --check \"src/**/*.{ts,tsx}\"",
|
|
44
|
+
"type-check": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run",
|
|
46
|
+
"test:watch": "vitest"
|
|
47
|
+
}
|
|
48
|
+
}
|