Skip to content

Device

@symbiote-native/device wraps expo-device — physical device information: brand/model/OS constants, uptime, max-memory, root/jailbreak detection, side-loading detection, and platform-feature queries — so every SymbioteNative adapter can read it. Like local auth, every export here is either an eagerly-resolved constant or a one-shot async call with no per-instance state — no hook/ composable/service to reach for, unlike sensorsEventEmitter + live-subscription surface.

OS platform Support
iOS ✅ live
Android ✅ live
Framework adapter Support
React ✅ live
Vue ✅ live
Angular ✅ live
Svelte ✅ live
Terminal window
pnpm add @symbiote-native/device

expo-device and expo-modules-core come along as regular dependencies, pinned to exact versions — never install either yourself, and never add the expo meta-package to your project (it bundles its own Metro/Babel pipeline, which conflicts with this project’s own).

expo-device needs no runtime permission on either platform — every constant and function here reads plain system/build information, nothing gated by a permission prompt.

All four adapters — React, Vue, Angular, Svelte — re-export the exact same constants and free functions; there is no per-adapter hook/composable/service to reach for, since nothing here holds live state or a subscription.

import { useEffect, useState } from 'react';
import { Text, View } from '@symbiote-native/react';
import {
brand,
deviceName,
getMaxMemoryAsync,
getUptimeAsync,
isDevice,
modelName,
osName,
osVersion,
} from '@symbiote-native/device/react';
export default function DeviceInfo() {
const [uptime, setUptime] = useState<number | null>(null);
const [maxMemory, setMaxMemory] = useState<number | null>(null);
useEffect(() => {
getUptimeAsync().then(setUptime);
getMaxMemoryAsync().then(setMaxMemory);
}, []);
return (
<View>
<Text>{isDevice ? 'Real device' : 'Simulator/emulator'}</Text>
<Text>{`${brand ?? 'unknown'} ${modelName ?? ''}`}</Text>
<Text>{`${osName ?? 'unknown OS'} ${osVersion ?? ''}`}</Text>
<Text>{deviceName ?? 'unnamed device'}</Text>
<Text>{uptime === null ? 'checking uptime…' : `Uptime: ${uptime}ms`}</Text>
<Text>{maxMemory === null ? 'checking memory…' : `Max memory: ${maxMemory} bytes`}</Text>
</View>
);
}

Resolved once, eagerly, at import time, straight off the native module:

Field Type Description
isDevice boolean true on a real device, false in a simulator/emulator (always true on web)
brand string | null The consumer-visible brand of the hardware, e.g. "google", "Apple"
manufacturer string | null The actual device manufacturer, which may differ from brand
modelId string | null Internal model identifier, e.g. "iPhone7,2". @platform ios
modelName string | null Human-friendly model name, e.g. "Pixel 2", "iPhone XS Max"
designName string | null The industrial design name/code name of the device. @platform android
productName string | null The device’s overall product name. @platform android
deviceType DeviceType | null UNKNOWN/PHONE/TABLET/DESKTOP/TV, determined from screen size on Android
deviceYearClass number | null The device year class of the hardware
totalMemory number | null Total memory accessible to the kernel, in bytes
supportedCpuArchitectures string[] | null Supported processor architecture versions the device expects binaries to target
osName string | null The OS name, e.g. "Android", "iOS", "iPadOS"
osVersion string | null Human-readable OS version string, e.g. "12.3.1"
osBuildId string | null Build ID that more precisely identifies the OS version
osInternalBuildId string | null Internal build ID of the OS
osBuildFingerprint string | null Full build fingerprint string. @platform android
platformApiLevel number | null The Android SDK version currently running. @platform android
deviceName string | null Human-readable device name, may be user-set
Signature Description
getDeviceTypeAsync(): Promise<DeviceType> Same value as the deviceType constant, fetched fresh
getUptimeAsync(): Promise<number> Milliseconds since the device’s last reboot (Android doesn’t count deep-sleep time)
getMaxMemoryAsync(): Promise<number> Maximum memory the Java VM will use, in bytes; the native -1 “no limit” sentinel is normalized to Number.MAX_SAFE_INTEGER. @platform android
isRootedExperimentalAsync(): Promise<boolean> Best-effort root (Android) / jailbreak (iOS) check — bypasses exist on both platforms, so false is not a guarantee
isSideLoadingEnabledAsync(): Promise<boolean> Whether apps can be installed via ACTION_INSTALL_PACKAGE outside the system app store. @platform android
getPlatformFeaturesAsync(): Promise<string[]> Platform-specific feature strings the system reports; resolves [] on iOS/web instead of throwing. @platform android
hasPlatformFeatureAsync(feature: string): Promise<boolean> Whether a specific system feature string is present; resolves false on iOS/web instead of throwing. @platform android
Field Value Description
UNKNOWN 0 An unrecognized device type
PHONE 1 Mobile phone handsets
TABLET 2 Tablet computers
DESKTOP 3 Desktop or laptop computers
TV 4 TV-based interfaces

@symbiote-native/device ships zero React/Vue/Angular logic in expo-device itself — its constants and functions are hand-ported, verbatim, into this package’s own core/, resolving the native module through expo-modules-core’s requireNativeModule rather than the expo meta-package this project never installs:

packages/device/src/
├── core/ # framework-agnostic: every constant + function above, DeviceType; native-module.ts
│ # resolves the native module via expo-modules-core's requireNativeModule
├── react/ # @symbiote-native/device/react — export * from '../core'
├── vue/ # @symbiote-native/device/vue — export * from '../core'
├── svelte/ # @symbiote-native/device/svelte — export * from '../core'
└── angular/ # @symbiote-native/device/angular — export * from '../core'

Same shape as local auth’s three adapter entries: single-file re-exports with no lifecycle code at all, since every export here is either an eagerly-resolved constant or a stateless one-shot call — there is nothing for a hook, composable, or service to subscribe to or clean up. The native code itself is never vendored or copied — expo-modules-autolinking resolves it straight out of node_modules (see the native setup guide).