48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
import {useSyncExternalStore} from 'react'
|
|
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
|
|
}
|
|
|
|
function subscribeToDeviceId(onChange: () => void) {
|
|
const sub = device.addOnValueChangedListener(['deviceId'], onChange)
|
|
return () => sub.remove()
|
|
}
|
|
|
|
/**
|
|
* Reads the device ID for use during render. The app awaits
|
|
* `getAndMigrateDeviceId` before booting (see `setupDeviceId` in
|
|
* `analytics/index.tsx`), so this is normally set on first read.
|
|
*
|
|
* Subscribing rather than reading storage directly means a late write - a
|
|
* caller that mounts before the migration resolves - still propagates, instead
|
|
* of leaving consumers pinned to `undefined` for the lifetime of the component.
|
|
* `useSyncExternalStore` re-evaluates the snapshot every render, so there's no
|
|
* gap between the initial read and the subscription.
|
|
*/
|
|
export function useDeviceId() {
|
|
return useSyncExternalStore(subscribeToDeviceId, getDeviceId)
|
|
}
|