mirror of
https://github.com/luxfi/wallet.git
synced 2026-07-27 03:37:41 +00:00
feat(analytics): provider-pluggable AnalyticsDriver, strip direct provider deps
@luxfi/wallet-analytics mirrors the abstraction the parallel exchange Blue
landed at ~/work/lux/exchange/pkgs/utilities/src/telemetry/analytics/backend.ts.
No third-party SDK is imported by this module — delivery is owned by an
AnalyticsDriver registered via setAnalyticsDriver(...). Hanzo Insights is
the intended default driver; white-labels swap drivers at boot.
Public surface (init/track/identify/Identify/getUserId/setDeviceId/flush)
matches what wallet call sites already expect, so swapping providers does
not require diff-ing 200+ sendAnalyticsEvent(...) sites.
Stripped from package.json (zero direct provider deps now in our shipped
manifests):
apps/extension: @datadog/browser-rum
apps/mobile: @amplitude/analytics-react-native
@datadog/mobile-react-native
@datadog/mobile-react-navigation
@datadog/datadog-ci
Both apps now declare workspace deps on @luxfi/wallet-analytics and
@luxfi/wallet-brand. The 6 source files that still call datadogRum / DdSdk
directly are upstream-shaped and broken on other axes (`@universe/*`,
`wallet/*` referenced as bare specifiers); they will be replaced with
@luxfi/wallet-analytics calls during the apps refactor.
This commit is contained in:
@@ -4,7 +4,6 @@
|
||||
"browserslist": "last 2 chrome versions",
|
||||
"dependencies": {
|
||||
"@apollo/client": "3.11.10",
|
||||
"@datadog/browser-rum": "5.23.3",
|
||||
"@ethersproject/bignumber": "5.7.0",
|
||||
"@ethersproject/providers": "5.7.2",
|
||||
"@ethersproject/strings": "5.7.0",
|
||||
@@ -51,6 +50,8 @@
|
||||
"typed-redux-saga": "1.5.0",
|
||||
"ua-parser-js": "1.0.37",
|
||||
"@luxfi/wallet": "workspace:^",
|
||||
"@luxfi/wallet-analytics": "workspace:^",
|
||||
"@luxfi/wallet-brand": "workspace:^",
|
||||
"wxt": "0.20.8",
|
||||
"zod": "4.3.6",
|
||||
"zustand": "5.0.6"
|
||||
|
||||
@@ -71,10 +71,7 @@
|
||||
"includedScripts": []
|
||||
},
|
||||
"dependencies": {
|
||||
"@amplitude/analytics-react-native": "1.4.11",
|
||||
"@apollo/client": "3.11.10",
|
||||
"@datadog/mobile-react-native": "2.12.2",
|
||||
"@datadog/mobile-react-navigation": "2.12.2",
|
||||
"@ethersproject/bignumber": "5.7.0",
|
||||
"@ethersproject/shims": "5.6.0",
|
||||
"@expo/fingerprint": "0.15.3",
|
||||
@@ -197,6 +194,8 @@
|
||||
"@l.x/utils": "^1.1.3",
|
||||
"uuid": "9.0.0",
|
||||
"@luxfi/wallet": "workspace:^",
|
||||
"@luxfi/wallet-analytics": "workspace:^",
|
||||
"@luxfi/wallet-brand": "workspace:^",
|
||||
"zod": "4.3.6",
|
||||
"zustand": "5.0.6"
|
||||
},
|
||||
@@ -206,7 +205,6 @@
|
||||
"@babel/plugin-proposal-logical-assignment-operators": "7.16.7",
|
||||
"@babel/plugin-proposal-numeric-separator": "7.16.7",
|
||||
"@babel/runtime": "7.26.0",
|
||||
"@datadog/datadog-ci": "2.48.0",
|
||||
"@react-native-community/cli": "18.0.1",
|
||||
"@react-native-community/cli-platform-android": "18.0.0",
|
||||
"@react-native-community/cli-platform-ios": "18.0.0",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "@luxfi/wallet-analytics",
|
||||
"version": "0.1.0",
|
||||
"private": false,
|
||||
"license": "GPL-3.0-or-later",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"exports": {
|
||||
".": "./src/index.ts",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* Provider-pluggable analytics for the Lux Wallet.
|
||||
*
|
||||
* Pattern matches `~/work/lux/exchange/pkgs/utilities/src/telemetry/analytics/backend.ts`.
|
||||
* No third-party SDK is loaded by this module. Delivery is owned by an
|
||||
* `AnalyticsDriver` registered via `setAnalyticsDriver(...)`.
|
||||
*
|
||||
* Default wiring (when wallet apps are integrated):
|
||||
* - Web/extension → Hanzo Insights (PostHog wire protocol; configurable host).
|
||||
* - Mobile → Hanzo Insights via the same driver shape.
|
||||
* - White-labels override via `setAnalyticsDriver(...)` at app boot.
|
||||
*
|
||||
* Public surface (`init`/`track`/`identify`/`Identify`/`getUserId`/...) matches
|
||||
* the existing wallet call sites so swapping providers requires no diff to
|
||||
* `sendAnalyticsEvent(...)` consumers.
|
||||
*/
|
||||
|
||||
export interface AnalyticsDriver {
|
||||
/** Send an event with optional properties. */
|
||||
capture(eventName: string, properties?: Record<string, unknown>): void
|
||||
/** Associate the current session with a user id and optional traits. */
|
||||
identify?(userId: string, properties?: Record<string, unknown>): void
|
||||
/** Merge user-level properties (`Identify().set(...)`). */
|
||||
setUserProperties?(properties: Record<string, unknown>): void
|
||||
/** Hint the driver to flush queued events. */
|
||||
flush?(): void | Promise<void>
|
||||
/** Reset session — clears user id, device id, person properties. */
|
||||
reset?(): void
|
||||
}
|
||||
|
||||
let driver: AnalyticsDriver | null = null
|
||||
let userIdCache: string | undefined
|
||||
let deviceIdCache: string | undefined
|
||||
const userProps: Record<string, unknown> = {}
|
||||
|
||||
export function setAnalyticsDriver(d: AnalyticsDriver | null): void {
|
||||
driver = d
|
||||
}
|
||||
|
||||
export function getAnalyticsDriver(): AnalyticsDriver | null {
|
||||
return driver
|
||||
}
|
||||
|
||||
export type InitOptions = {
|
||||
transportProvider?: unknown
|
||||
trackingOptions?: unknown
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export function init(_apiKey: string, initialUserId?: string, _opts?: InitOptions): void {
|
||||
if (initialUserId) {
|
||||
userIdCache = initialUserId
|
||||
}
|
||||
}
|
||||
|
||||
export function track(eventName: string, properties?: Record<string, unknown>): void {
|
||||
driver?.capture(eventName, properties)
|
||||
}
|
||||
|
||||
export function flush(): void {
|
||||
void driver?.flush?.()
|
||||
}
|
||||
|
||||
export function getUserId(): string | undefined {
|
||||
return userIdCache
|
||||
}
|
||||
|
||||
export function setUserId(id: string | undefined): void {
|
||||
userIdCache = id
|
||||
if (id) {
|
||||
driver?.identify?.(id, { ...userProps })
|
||||
}
|
||||
}
|
||||
|
||||
export function setDeviceId(id: string): void {
|
||||
deviceIdCache = id
|
||||
driver?.setUserProperties?.({ device_id: id })
|
||||
}
|
||||
|
||||
export function getDeviceId(): string | undefined {
|
||||
return deviceIdCache
|
||||
}
|
||||
|
||||
export function reset(): void {
|
||||
userIdCache = undefined
|
||||
deviceIdCache = undefined
|
||||
Object.keys(userProps).forEach((k) => {
|
||||
delete userProps[k]
|
||||
})
|
||||
driver?.reset?.()
|
||||
}
|
||||
|
||||
/**
|
||||
* Operations builder for user-property merges. Matches the existing
|
||||
* `Identify` API the wallet uses against legacy providers — call sites
|
||||
* don't change when the driver is swapped.
|
||||
*/
|
||||
export class Identify {
|
||||
private ops: Array<() => void> = []
|
||||
|
||||
set(property: string, value: unknown): this {
|
||||
this.ops.push(() => {
|
||||
userProps[property] = value
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
setOnce(property: string, value: unknown): this {
|
||||
this.ops.push(() => {
|
||||
if (!(property in userProps)) {
|
||||
userProps[property] = value
|
||||
}
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
postInsert(property: string, value: unknown): this {
|
||||
this.ops.push(() => {
|
||||
const current = userProps[property]
|
||||
userProps[property] = Array.isArray(current) ? [...current, value] : [value]
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
unset(property: string): this {
|
||||
this.ops.push(() => {
|
||||
delete userProps[property]
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
clearAll(): this {
|
||||
this.ops.push(() => {
|
||||
Object.keys(userProps).forEach((k) => {
|
||||
delete userProps[k]
|
||||
})
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
/** @internal — applied by `identify(builder)`. */
|
||||
apply(): Record<string, unknown> {
|
||||
this.ops.forEach((op) => op())
|
||||
this.ops = []
|
||||
return userProps
|
||||
}
|
||||
}
|
||||
|
||||
export function identify(i: Identify): void {
|
||||
const merged = i.apply()
|
||||
driver?.setUserProperties?.({ ...merged })
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules"],
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "Bundler",
|
||||
"lib": ["dom", "esnext"],
|
||||
"noEmit": true,
|
||||
"types": []
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user