Expo native module for integrating Meta Wearables DAT (Ray-Ban Meta smart glasses) into React Native apps. Provides device registration, permissions, session-based camera streaming, photo capture, and a React hook — bridged from the official Meta Wearables DAT SDK 0.6 on both iOS and Android.
Official SDK docs: Meta Wearables DAT — Developer Documentation
You must register your app in the Meta Wearables Developer Center to obtain your App ID and Client Token.
Disclaimer: This project is not affiliated with, endorsed by, or sponsored by Meta Platforms, Inc. It is an independent, community-maintained wrapper around the publicly available Meta Wearables DAT SDK.
- Background streaming — the SDK doesn't support it
- Expo Go — requires a development build (native code)
- Device registration / unregistration via Meta AI app
- Permission management (camera)
- Device discovery and link state monitoring
- Session-based camera streaming with native view
- Compressed HEVC video streaming (Android)
- Photo capture (JPEG / HEIC)
-
useMetaWearablesReact hook with full state management - Mock device simulation for testing (debug builds) with permission mocking and phone camera feed
- Expo config plugin (auto-configures Info.plist, AndroidManifest, URL schemes, deployment target)
| Requirement | Version |
|---|---|
| React Native | 0.76+ |
| Expo SDK | 52+ |
| iOS | 16.0+ |
| Android | API 31+ |
| Xcode | 16+ |
| Swift | 5.9+ |
| DAT SDK | 0.6 |
| New Architecture | Untested |
- Ray-Ban Meta (verified)
- Ray-Ban Meta Optics (untested)
- Meta Ray-Ban Display (untested)
- Oakley Meta HSTN / Vanguard (untested)
npx expo install expo-meta-wearables-datOr manually:
# pnpm
pnpm add expo-meta-wearables-dat
# yarn
yarn add expo-meta-wearables-dat
# npm
npm install expo-meta-wearables-datAdd the plugin to your app.json / app.config.js:
{
"plugins": [
[
"expo-meta-wearables-dat",
{
"urlScheme": "myapp",
"metaAppId": "YOUR_META_APP_ID",
"clientToken": "YOUR_CLIENT_TOKEN",
"bluetoothUsageDescription": "This app uses Bluetooth to connect to Meta Wearables."
}
]
]
}| Prop | Required | Description |
|---|---|---|
urlScheme |
Yes | URL scheme for Meta AI app callback (e.g. "myapp"). Do not include :// — only the scheme name |
metaAppId |
No | Meta App ID from Wearables Developer Center. Omit for Developer Mode |
clientToken |
No | Client Token from Wearables Developer Center |
bluetoothUsageDescription |
No | Custom Bluetooth usage description (iOS only) |
githubToken |
No | GitHub token for Maven packages (Android). Falls back to GITHUB_TOKEN env var |
The plugin automatically configures:
-
CFBundleURLTypes(URL scheme) -
LSApplicationQueriesSchemes(fb-viewapp) -
UISupportedExternalAccessoryProtocols(com.meta.ar.wearable) -
UIBackgroundModes(bluetooth-peripheral,external-accessory) NSBluetoothAlwaysUsageDescription-
MWDATconfiguration dictionary (includingTeamIDauto-resolved from Xcode'sDEVELOPMENT_TEAMsigning setting) - iOS deployment target to 16.0
- Embeds MWDATCamera & MWDATCore dynamic frameworks
Note: The native Meta Wearables DAT iOS SDK states iOS 17.0+ as its minimum. The podspec targets 16.0 and builds successfully, but runtime behavior on iOS 16 devices is unverified. We recommend iOS 17.0+ for production use.
The plugin automatically configures:
-
<meta-data>entries forAPPLICATION_IDandCLIENT_TOKENin AndroidManifest.xml - Deep link
<intent-filter>on MainActivity with the configured URL scheme - Bluetooth permissions (
BLUETOOTH,BLUETOOTH_CONNECT)
The Android SDK dependencies are resolved via Maven from GitHub Packages. The config plugin injects the Maven repository automatically. You need either:
-
GITHUB_ACTORandGITHUB_TOKENenvironment variables set, or - The
githubTokenplugin prop configured
After adding the plugin, generate the native projects:
npx expo prebuildIf you change plugin configuration later, regenerate with --clean to ensure native projects are fully updated:
npx expo prebuild --clean- The user must have the Meta AI app installed and paired with their glasses
- A physical device is required (no simulator/emulator support)
- iOS: Xcode 16+ with a valid signing team
- Android: Android Studio with SDK installed, minSdk 31 (Android 12+)
import { View, Button, Text } from "react-native";
import { useMetaWearables, EMWDATStreamView } from "expo-meta-wearables-dat";
import { useState } from "react";
export default function App() {
const [sessionId, setSessionId] = useState<string | null>(null);
const {
isConfigured,
registrationState,
devices,
startRegistration,
createSession,
startSession,
stopSession,
addStreamToSession,
capturePhoto,
} = useMetaWearables({
onPhotoCaptured: (photo) => console.log("Photo saved:", photo.filePath),
onStreamStateChange: (state) => console.log("Stream:", state),
});
const handleStartStream = async () => {
const id = await createSession();
setSessionId(id);
await startSession(id);
await addStreamToSession(id, { resolution: "medium", frameRate: 24 });
};
const handleStopStream = async () => {
if (sessionId) {
await stopSession(sessionId);
setSessionId(null);
}
};
return (
<View style={{ flex: 1, padding: 20, paddingTop: 60, gap: 10 }}>
<Text>Configured: {String(isConfigured)}</Text>
<Text>Registration: {registrationState}</Text>
<Text>Devices: {devices.length}</Text>
<Button title="Register" onPress={() => startRegistration()} />
<Button title="Start Stream" onPress={handleStartStream} />
<Button title="Stop Stream" onPress={handleStopStream} />
<Button title="Capture Photo" onPress={() => capturePhoto("jpeg")} />
<EMWDATStreamView isActive={!!sessionId} resizeMode="contain" style={{ flex: 1 }} />
</View>
);
}React hook that manages the full lifecycle of Meta Wearables integration.
Options (UseMetaWearablesOptions):
| Option | Type | Default | Description |
|---|---|---|---|
autoConfig |
boolean |
true |
Auto-call configure() on mount |
logLevel |
LogLevel |
"info" |
Initial log level |
onRegistrationStateChange |
(state) => void |
— | Registration state changed |
onDevicesChange |
(devices) => void |
— | Device list updated |
onLinkStateChange |
(deviceId, linkState) => void |
— | Device connection changed |
onStreamStateChange |
(state) => void |
— | Stream state changed |
onVideoFrame |
(metadata) => void |
— | Video frame received |
onPhotoCaptured |
(photo) => void |
— | Photo captured |
onStreamError |
(error) => void |
— | Stream error occurred |
onPermissionStatusChange |
(permission, status) => void |
— | Permission status changed |
onCompatibilityChange |
(deviceId, compatibility) => void |
— | Device compatibility changed |
onDeviceSessionStateChange |
(sessionId, state) => void |
— | Device session state changed |
onDeviceSessionError |
(sessionId, error, message?) => void |
— | Device session error |
onCapabilityStateChange |
(sessionId, state) => void |
— | Capability state changed |
Returned state:
| Field | Type | Description |
|---|---|---|
isConfigured |
boolean |
SDK configured |
isConfiguring |
boolean |
true while configuring |
configError |
Error | null |
Error from last configure
|
registrationState |
RegistrationState |
Registration lifecycle state |
permissionStatus |
PermissionStatus |
"granted" | "denied"
|
devices |
Device[] |
Connected devices |
deviceSessionStates |
Record<string, DeviceSessionState> |
Per-session states |
deviceSessionErrors |
Record<string, { error, message? }> |
Per-session errors |
capabilityStates |
Record<string, CapabilityState> |
Per-session capability state |
Returned actions:
| Action | Signature | Description |
|---|---|---|
configure |
() => Promise<void> |
Initialize SDK |
setLogLevel |
(level: LogLevel) => void |
Change log level |
startRegistration |
() => Promise<void> |
Open Meta AI app for registration |
startUnregistration |
() => Promise<void> |
Unregister from Meta AI |
checkPermissionStatus |
(permission) => Promise<PermissionStatus> |
Check permission |
requestPermission |
(permission) => Promise<PermissionStatus> |
Request permission |
getDevice |
(id) => Promise<Device | null> |
Get device by identifier |
refreshDevices |
() => Promise<Device[]> |
Refresh device list |
createSession |
(deviceId?) => Promise<string> |
Create a device session |
startSession |
(sessionId) => Promise<void> |
Start a session |
stopSession |
(sessionId) => Promise<void> |
Stop a session (terminal) |
addStreamToSession |
(sessionId, config?) => Promise<void> |
Attach camera stream capability |
removeStreamFromSession |
(sessionId) => Promise<void> |
Remove stream capability |
capturePhoto |
(format?) => Promise<void> |
Capture photo |
enableMockDeviceKit |
(config?) => Promise<void> |
Enable mock device kit |
disableMockDeviceKit |
() => Promise<void> |
Disable mock device kit |
isMockDeviceKitEnabled |
() => Promise<boolean> |
Check if mock kit is enabled |
pairMockDevice |
() => Promise<string> |
Pair a mock device |
unpairMockDevice |
(deviceId) => Promise<void> |
Unpair a mock device |
mockSetPermissionStatus |
(permission, status) => Promise<void> |
Set mock permission status |
mockSetPermissionRequestResult |
(permission, result) => Promise<void> |
Set mock permission request result |
mockDeviceSetCameraFeedFromCamera |
(id, facing) => Promise<void> |
Set mock camera from phone camera |
These can be imported directly for lower-level control:
import {
EMWDATModule,
configure,
setLogLevel,
startRegistration,
startUnregistration,
handleUrl,
checkPermissionStatus,
requestPermission,
getDevices,
getDevice,
getRegistrationState,
getRegistrationStateAsync,
createSession,
startSession,
stopSession,
addStreamToSession,
removeStreamFromSession,
capturePhoto,
addListener,
// Mock device kit
enableMockDeviceKit,
disableMockDeviceKit,
isMockDeviceKitEnabled,
pairMockDevice,
unpairMockDevice,
getMockDevices,
mockDevicePowerOn,
mockDevicePowerOff,
mockDeviceDon,
mockDeviceDoff,
mockDeviceFold,
mockDeviceUnfold,
mockDeviceSetCameraFeed,
mockDeviceSetCapturedImage,
mockDeviceSetCameraFeedFromCamera,
mockSetPermissionStatus,
mockSetPermissionRequestResult,
} from "expo-meta-wearables-dat";Subscribe via addListener or hook callbacks:
| Event | Payload |
|---|---|
onRegistrationStateChange |
{ state: RegistrationState } |
onDevicesChange |
{ devices: Device[] } |
onLinkStateChange |
{ deviceId: string, linkState: LinkState } |
onStreamStateChange |
{ state: StreamSessionState } |
onVideoFrame |
{ timestamp, width, height, isCompressed? } |
onPhotoCaptured |
{ filePath, format, timestamp, width?, height?, base64? } |
onStreamError |
StreamSessionError (discriminated union) |
onPermissionStatusChange |
{ permission: Permission, status: PermissionStatus } |
onCompatibilityChange |
{ deviceId: string, compatibility: Compatibility } |
onDeviceSessionStateChange |
{ sessionId: string, state: DeviceSessionState } |
onDeviceSessionError |
{ sessionId: string, error: DeviceSessionErrorCode, message?: string } |
onCapabilityStateChange |
{ sessionId: string, state: CapabilityState } |
Native view component for rendering the camera stream.
| Prop | Type | Default | Description |
|---|---|---|---|
isActive |
boolean |
false |
Whether to render frames |
resizeMode |
"contain" | "cover" | "stretch"
|
"contain" |
How frames fit the view |
style |
ViewStyle |
— | Standard React Native style |
Key types exported from the package:
-
LogLevel—"debug"|"info"|"warn"|"error"|"none" -
RegistrationState—"unavailable"|"available"|"registering"|"registered" -
Permission—"camera" -
PermissionStatus—"granted"|"denied" -
Device—{ identifier, name, linkState, deviceType, compatibility } -
DeviceType—"rayBanMeta"|"oakleyMetaHSTN"|"oakleyMetaVanguard"|"metaRayBanDisplay"|"rayBanMetaOptics"|"unknown" -
LinkState—"connected"|"disconnected"|"connecting" -
Compatibility—"compatible"|"undefined"|"deviceUpdateRequired"|"sdkUpdateRequired" -
DeviceSessionState—"idle"|"starting"|"started"|"paused"|"stopping"|"stopped" -
DeviceSessionErrorCode—"noEligibleDevice"|"sessionAlreadyStopped"|"sessionAlreadyExists"|"sessionIdle"|"capabilityAlreadyActive"|"capabilityNotFound"|"unexpectedError" -
CapabilityState—"active"|"stopped" -
StreamSessionConfig—{ videoCodec, resolution, frameRate, deviceId?, compressVideo?, skipAppLaunch? } -
StreamSessionState—"stopped"|"waitingForDevice"|"starting"|"streaming"|"paused"|"stopping" -
StreamSessionError— Discriminated union:internalError|deviceNotFound|deviceNotConnected|timeout|videoStreamingError|permissionDenied|hingesClosed|thermalCritical -
PhotoData—{ filePath, format, timestamp, width?, height?, base64? } -
PhotoCaptureFormat—"jpeg"|"heic" -
VideoFrameMetadata—{ timestamp, width, height, isCompressed? } -
StreamingResolution—"high"|"medium"|"low" -
VideoCodec—"raw"|"hvc1" -
CameraFacing—"front"|"back" -
MockDeviceKitConfig—{ initiallyRegistered?, initialPermissionsGranted? } -
CaptureError—"deviceDisconnected"|"notStreaming"|"captureInProgress"|"captureFailed" -
StreamViewResizeMode—"contain"|"cover"|"stretch" -
EMWDATPluginProps— Config plugin options - Error code types:
WearablesErrorCode,RegistrationErrorCode,UnregistrationErrorCode,PermissionErrorCode,DecoderError
See src/EMWDAT.types.ts for the full list.
Functions for simulating Meta Wearables devices during development using the SDK's mock device framework. Only available in debug builds.
import {
// Kit lifecycle
enableMockDeviceKit,
disableMockDeviceKit,
isMockDeviceKitEnabled,
// Device pairing
pairMockDevice,
unpairMockDevice,
getMockDevices,
// Device simulation
mockDevicePowerOn,
mockDevicePowerOff,
mockDeviceDon,
mockDeviceDoff,
mockDeviceFold,
mockDeviceUnfold,
mockDeviceSetCameraFeed,
mockDeviceSetCapturedImage,
mockDeviceSetCameraFeedFromCamera,
// Permission mocking
mockSetPermissionStatus,
mockSetPermissionRequestResult,
} from "expo-meta-wearables-dat";| Function | Signature | Description |
|---|---|---|
enableMockDeviceKit |
(config?: MockDeviceKitConfig) => Promise<void> |
Enable mock kit with optional config |
disableMockDeviceKit |
() => Promise<void> |
Disable mock kit and remove fakes |
isMockDeviceKitEnabled |
() => Promise<boolean> |
Check if mock kit is enabled |
pairMockDevice |
() => Promise<string> |
Pair a mock Ray-Ban Meta, returns ID |
unpairMockDevice |
(id: string) => Promise<void> |
Unpair a mock device |
getMockDevices |
() => Promise<string[]> |
List active mock device IDs |
mockDevicePowerOn |
(id: string) => Promise<void> |
Power on |
mockDevicePowerOff |
(id: string) => Promise<void> |
Power off |
mockDeviceDon |
(id: string) => Promise<void> |
Simulate putting glasses on |
mockDeviceDoff |
(id: string) => Promise<void> |
Simulate taking glasses off |
mockDeviceFold |
(id: string) => Promise<void> |
Fold hinges |
mockDeviceUnfold |
(id: string) => Promise<void> |
Unfold hinges |
mockDeviceSetCameraFeed |
(id: string, fileUrl: string) => Promise<void> |
Set camera feed from local video file |
mockDeviceSetCapturedImage |
(id: string, fileUrl: string) => Promise<void> |
Set captured image from local file |
mockDeviceSetCameraFeedFromCamera |
(id: string, facing: CameraFacing) => Promise<void> |
Use phone camera as mock feed |
mockSetPermissionStatus |
(permission, status) => Promise<void> |
Set mock permission check result |
mockSetPermissionRequestResult |
(permission, result) => Promise<void> |
Set mock permission request result |
The example/ directory contains a full demo app:
-
Copy the example credentials and fill in your values:
cd exampleEdit
app.jsonand replace the placeholders:-
YOUR_APPLE_TEAM_ID— your Apple Developer Team ID -
YOUR_META_APP_ID— from the Meta Wearables Developer Center -
YOUR_CLIENT_TOKEN— from the same Developer Center page
-
-
Build and run:
npx expo prebuild --clean npx expo run:ios --device # or npx expo run:android --device
Requires a physical device with a paired Meta Wearables device.
1.2.0 migrates to Meta Wearables DAT SDK 0.6, introducing a session-based streaming model.
Deprecated (removed):
-
startStream(config?)— usecreateSession()→startSession(id)→addStreamToSession(id, config) -
stopStream()— usestopSession(sessionId) -
getStreamState()— observe stream state viaonStreamStateChangeevent -
streamStateandlastErrorfrom hook return — use events anddeviceSessionStates/deviceSessionErrors -
SessionStatetype — replaced byDeviceSessionState -
createMockDevice()/removeMockDevice()— useenableMockDeviceKit()+pairMockDevice()/unpairMockDevice()
Added:
- Session management:
createSession,startSession,stopSession,addStreamToSession,removeStreamFromSession -
DeviceSessionState,DeviceSessionErrorCode,CapabilityStatetypes -
compressVideoandskipAppLaunchinStreamSessionConfig -
isCompressedinVideoFrameMetadata -
rayBanMetaOpticsdevice type - Mock device kit lifecycle:
enableMockDeviceKit,disableMockDeviceKit,isMockDeviceKitEnabled - Mock permissions:
mockSetPermissionStatus,mockSetPermissionRequestResult - Mock phone camera:
mockDeviceSetCameraFeedFromCamerawithCameraFacingtype - New events:
onDeviceSessionError,onCapabilityStateChange
Ensure iOS deployment target is 16.0. The config plugin sets this automatically, but if you ran expo prebuild --clean, check that ios/Podfile.properties.json contains:
{ "ios.deploymentTarget": "16.0" }The config plugin adds a build phase to embed these dynamic frameworks. Run npx expo prebuild --clean to regenerate the Xcode project.
Verify your urlScheme matches the one registered in the Meta Wearables Developer Center, and that CFBundleURLTypes in Info.plist contains it. The config plugin handles this, but double-check after prebuild.
Ensure the glasses hinges are open and the device is connected (linkState: "connected"). Check onStreamError for hingesClosed or deviceNotConnected errors.
This wipes Podfile.properties.json. Re-run prebuild (the config plugin will re-inject the deployment target) and then pod install.
The mock video feed must be HEVC (H.265) encoded. The SDK requests video/hevc mime type and rejects H.264 (AVC) videos. Resolution does not matter — only the codec.
- The library does not store, persist, or log personally identifiable information
- No network requests are made beyond what the Meta Wearables DAT SDK itself performs
-
Debug logging is disabled by default (
logLevel: "info") — logs stay on the device console - Photos are saved to a local file path and never uploaded by the library
- Video frames are rendered on-device and not transmitted or stored
See SECURITY.md for the vulnerability reporting process.
- Background streaming (pending SDK support)
- New Architecture validation