import type { IDeviceAdapter, ScreenInfo } from '@namiml/sdk-core'; import type { DevicePayload, TDevice } from '@namiml/sdk-core'; import { NAMI_SDK_PACKAGE_VERSION } from '@namiml/sdk-core'; import { Dimensions, Platform } from 'react-native'; function getExpoDevice() { return require('expo-device'); } function getExpoConstants() { return require('expo-constants').default; } function getExpoLocalization() { return require('expo-localization'); } const SDK_CLIENT = 'expo'; const TV_SCALE_FACTORS: Record = { 720: 0.67, 1080: 1, 1440: 1.5, 2160: 2, }; export class ExpoDeviceAdapter implements IDeviceAdapter { getDeviceData(): DevicePayload { let osName = Platform.OS; let osVersion = String(Platform.Version); let deviceModel: string | undefined; try { const device = getExpoDevice(); if (device) { osName = device.osName ?? osName; osVersion = device.osVersion ?? osVersion; deviceModel = device.modelName ?? device.modelId; } } catch { /* expo-device unavailable */ } return { os_name: osName, os_version: osVersion, browser_name: '', browser_version: '', sdk_client: SDK_CLIENT, sdk_version: NAMI_SDK_PACKAGE_VERSION, language: this.getLanguage(), ...(deviceModel ? { device_model: deviceModel } : {}), extended_platform: 'expo', extended_platform_version: this.getExpoVersion(), }; } private getExpoVersion(): string { try { const constants = getExpoConstants(); return constants?.expoConfig?.sdkVersion ?? ''; } catch { return ''; } } getDeviceFormFactor(): TDevice { try { const device = getExpoDevice(); const deviceType = device?.deviceType; const DeviceType = device?.DeviceType; if (DeviceType && deviceType === DeviceType.TV) return 'television'; if (DeviceType && deviceType === DeviceType.TABLET) return 'tablet'; } catch { // expo-device may not be available (e.g., Expo Go without native modules) } return 'phone'; } getDeviceScaleFactor(formFactor?: string): number { const ff = formFactor ?? this.getDeviceFormFactor(); if (ff !== 'television') return 1; const { height } = Dimensions.get('window'); const closest = Object.keys(TV_SCALE_FACTORS) .map(Number) .reduce((prev, curr) => Math.abs(curr - height) < Math.abs(prev - height) ? curr : prev ); return TV_SCALE_FACTORS[closest] ?? 1; } generateUUID(): string { return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { const r = (Math.random() * 16) | 0; const v = c === 'x' ? r : (r & 0x3) | 0x8; return v.toString(16); }); } getScreenInfo(): ScreenInfo { const { width, height, scale } = Dimensions.get('window'); return { width, height, scale }; } getLanguage(): string { try { const { getLocales } = getExpoLocalization(); const locales = getLocales(); return locales?.[0]?.languageCode ?? 'en'; } catch { return 'en'; } } }