import 'react-native-get-random-values'; import { v4 as uuidv4 } from 'uuid'; import * as Keychain from 'react-native-keychain'; const DEVICE_SERVICE = 'com.bankcbs.mobile.deviceId'; /** * Kept in memory as well so the many calls that need it during one flow do not * each hit the Keystore. */ let cachedDeviceId: string | null = null; let inflight: Promise | null = null; const persist = async (deviceId: string) => { await Keychain.setGenericPassword('deviceId', deviceId, { service: DEVICE_SERVICE, accessible: Keychain.ACCESSIBLE.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY, securityLevel: Keychain.SECURITY_LEVEL.SECURE_SOFTWARE, }); }; const load = async (): Promise => { const stored = await Keychain.getGenericPassword({ service: DEVICE_SERVICE }); if (stored && stored.password) { return stored.password; } const created = uuidv4(); await persist(created); return created; }; /** * The profile the customer registers is bound to this value. Registration and * every later sign-in must send the *same* one — a different value comes back * as `DEVICE_NOT_BOUND`, which reads like a server fault but is a client bug. * So it is generated exactly once, on first launch, and never regenerated * except by an explicit `reset()`. * * `AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY` rather than `WHEN_UNLOCKED_*`: the id * is not a secret, and sign-in must read it before the user has authenticated. */ export const deviceIdentity = { async get(): Promise { if (cachedDeviceId) { return cachedDeviceId; } // Concurrent callers on a cold start would otherwise each mint a UUID and // the last write would win, silently unbinding the device. inflight = inflight ?? load(); try { cachedDeviceId = await inflight; return cachedDeviceId; } finally { inflight = null; } }, /** In-memory peek for code that cannot await. Null before the first `get()`. */ peek() { return cachedDeviceId; }, /** * Mints a fresh id, unbinding this install from its registered profile. Only * for a deliberate "Register again" action — the customer will * have to go through registration once more. */ async reset(): Promise { const next = uuidv4(); await persist(next); cachedDeviceId = next; return next; }, };