Shared metadata cache
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import uuid from 'react-native-uuid'
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage'
|
||||
|
||||
import {device} from '#/storage'
|
||||
|
||||
const LEGACY_STABLE_ID = 'STATSIG_LOCAL_STORAGE_STABLE_ID'
|
||||
|
||||
export async function getAndMigrateDeviceId() {
|
||||
const migrated = getDeviceId()
|
||||
if (migrated) return migrated
|
||||
const id = (await AsyncStorage.getItem(LEGACY_STABLE_ID)) || uuid.v4()
|
||||
device.set(['deviceId'], id)
|
||||
return id
|
||||
}
|
||||
|
||||
export function getDeviceId() {
|
||||
return device.get(['deviceId'])
|
||||
}
|
||||
|
||||
export function getDeviceIdOrThrow() {
|
||||
const id = device.get(['deviceId'])
|
||||
if (!id) {
|
||||
throw new Error(`deviceId is not set, call getAndMigrateDeviceId first`)
|
||||
}
|
||||
return id
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import {Platform} from 'react-native'
|
||||
|
||||
import {BSKY_SERVICE} from '#/lib/constants'
|
||||
import {setGrowthBookAttributes} from '#/logger/growthbook'
|
||||
import {getAndMigrateDeviceId, getDeviceId} from '#/logger/metadata/deviceId'
|
||||
import {getSessionId} from '#/logger/metadata/sessionId'
|
||||
import * as persisted from '#/state/persisted'
|
||||
import * as env from '#/env'
|
||||
import {device} from '#/storage'
|
||||
|
||||
export type BaseMetadata = {
|
||||
deviceId: string
|
||||
sessionId: string
|
||||
country: string
|
||||
}
|
||||
|
||||
export type UserMetadata = {
|
||||
did: string
|
||||
isBskyPds: boolean
|
||||
platform: string
|
||||
appVersion: string
|
||||
bundleIdentifier: string
|
||||
bundleDate: number
|
||||
refSrc: string
|
||||
refUrl: string
|
||||
appLanguage: string
|
||||
contentLanguages: string[]
|
||||
}
|
||||
|
||||
export type Metadata = BaseMetadata & Partial<UserMetadata>
|
||||
|
||||
/**
|
||||
* Ensures that deviceId is set and migrated from legacy storage. Handled on
|
||||
* startup in `App.<platform>.tsx`
|
||||
*/
|
||||
export const setupDeviceId = getAndMigrateDeviceId()
|
||||
|
||||
let baseMetadata: BaseMetadata = {
|
||||
deviceId: getDeviceId() || 'unknown',
|
||||
sessionId: getSessionId(),
|
||||
country: device.get(['mergedGeolocation'])?.countryCode || 'unknown',
|
||||
}
|
||||
export function updateBaseMetadata(
|
||||
metadata: Omit<BaseMetadata, 'deviceId' | 'sessionId'>,
|
||||
) {
|
||||
baseMetadata = {
|
||||
deviceId: getDeviceId() || 'unknown',
|
||||
sessionId: getSessionId(),
|
||||
...metadata,
|
||||
}
|
||||
__onMetadataChange()
|
||||
}
|
||||
export function getBaseMetadata() {
|
||||
return {
|
||||
...baseMetadata,
|
||||
sessionId: getSessionId(), // may have changed
|
||||
}
|
||||
}
|
||||
|
||||
let refSrc = ''
|
||||
let refUrl = ''
|
||||
if (env.IS_WEB) {
|
||||
const params = new URLSearchParams(window.location.search)
|
||||
refSrc = params.get('ref_src') ?? ''
|
||||
refUrl = decodeURIComponent(params.get('ref_url') ?? '')
|
||||
}
|
||||
|
||||
let userMetadata: UserMetadata | null = null
|
||||
export function updateUserMetadata(account: persisted.PersistedAccount | null) {
|
||||
if (account === null) {
|
||||
userMetadata = null
|
||||
} else {
|
||||
const languagePrefs = persisted.get('languagePrefs')
|
||||
userMetadata = {
|
||||
did: account.did,
|
||||
isBskyPds: account.service.startsWith(BSKY_SERVICE),
|
||||
platform: Platform.OS,
|
||||
appVersion: env.RELEASE_VERSION,
|
||||
bundleIdentifier: env.BUNDLE_IDENTIFIER,
|
||||
bundleDate: env.BUNDLE_DATE,
|
||||
appLanguage: languagePrefs.appLanguage,
|
||||
contentLanguages: languagePrefs.contentLanguages,
|
||||
refSrc,
|
||||
refUrl,
|
||||
}
|
||||
}
|
||||
__onMetadataChange()
|
||||
}
|
||||
export function getUserMetadata() {
|
||||
return userMetadata
|
||||
}
|
||||
|
||||
export function getMetadata(): Metadata {
|
||||
return {
|
||||
...getBaseMetadata(),
|
||||
...(getUserMetadata() || {}),
|
||||
}
|
||||
}
|
||||
|
||||
function __onMetadataChange() {
|
||||
const metadata = getMetadata()
|
||||
setGrowthBookAttributes(metadata)
|
||||
}
|
||||
__onMetadataChange()
|
||||
@@ -0,0 +1,34 @@
|
||||
import uuid from 'react-native-uuid'
|
||||
|
||||
import {onAppStateChange} from '#/lib/appState'
|
||||
import {device} from '#/storage'
|
||||
|
||||
const TTL = 5 * 60 * 1e3 // 5 min on native
|
||||
function expired(since: number | undefined) {
|
||||
if (since === undefined) return false
|
||||
return Date.now() - since >= TTL
|
||||
}
|
||||
|
||||
let sessionId = (() => {
|
||||
const existing = device.get(['nativeSessionId'])
|
||||
const lastEvent = device.get(['nativeSessionIdLastEventAt'])
|
||||
const id = existing && !expired(lastEvent) ? existing : uuid.v4()
|
||||
device.set(['nativeSessionId'], id)
|
||||
return id
|
||||
})()
|
||||
|
||||
onAppStateChange(state => {
|
||||
if (state === 'active') {
|
||||
const lastEvent = device.get(['nativeSessionIdLastEventAt'])
|
||||
if (expired(lastEvent)) {
|
||||
sessionId = uuid.v4()
|
||||
device.set(['nativeSessionId'], sessionId)
|
||||
}
|
||||
} else {
|
||||
device.set(['nativeSessionIdLastEventAt'], Date.now())
|
||||
}
|
||||
})
|
||||
|
||||
export function getSessionId() {
|
||||
return sessionId
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import uuid from 'react-native-uuid'
|
||||
|
||||
import {onAppStateChange} from '#/lib/appState'
|
||||
|
||||
const TTL = 30 * 60 * 1e3 // 30 min on web
|
||||
const SESSION_ID_KEY = 'bsky_session_id'
|
||||
const LAST_EVENT_KEY = 'bsky_session_id_last_event_at'
|
||||
|
||||
function expired(since: number | undefined) {
|
||||
if (since === undefined) return false
|
||||
return Date.now() - since >= TTL
|
||||
}
|
||||
|
||||
let sessionId = (() => {
|
||||
const existing = window.sessionStorage.getItem(SESSION_ID_KEY)
|
||||
const lastEventStr = window.sessionStorage.getItem(LAST_EVENT_KEY)
|
||||
const lastEvent = lastEventStr ? Number(lastEventStr) : undefined
|
||||
const id = existing && !expired(lastEvent) ? existing : uuid.v4()
|
||||
window.sessionStorage.setItem(SESSION_ID_KEY, id)
|
||||
return id
|
||||
})()
|
||||
|
||||
onAppStateChange(state => {
|
||||
if (state === 'active') {
|
||||
const lastEventStr = window.sessionStorage.getItem(LAST_EVENT_KEY)
|
||||
const lastEvent = lastEventStr ? Number(lastEventStr) : undefined
|
||||
if (expired(lastEvent)) {
|
||||
sessionId = uuid.v4()
|
||||
window.sessionStorage.setItem(SESSION_ID_KEY, sessionId)
|
||||
}
|
||||
} else {
|
||||
window.sessionStorage.setItem(LAST_EVENT_KEY, String(Date.now()))
|
||||
}
|
||||
})
|
||||
|
||||
export function getSessionId() {
|
||||
return sessionId
|
||||
}
|
||||
Reference in New Issue
Block a user