Adapters
The @espressif/rainmaker-base-sdk follows an adapter pattern to provide a flexible, platform-agnostic architecture. This design allows the SDK to work across different JavaScript frameworks and platforms (React Native, web, Electron, etc.) by implementing platform-specific functionality through adapters.
Why Adapters?
Adapters bridge the gap between the SDK's core TypeScript logic and platform-specific implementations. Different platforms have different capabilities:
- React Native requires native modules for Bluetooth, storage, and notifications
- Web applications use browser APIs like localStorage and Web Bluetooth
- Electron apps may use Node.js filesystem APIs
By using adapters, the SDK can:
- Remain platform-agnostic
- Enable consistent API across all platforms
- Allow developers to provide custom implementations for their specific needs
- Support multiple UI frameworks (React Native, Vue, Angular, etc.)
Available Adapters
The SDK provides interfaces for seven different adapters, each serving a specific purpose:
1. Storage Adapter
Purpose: Manages persistent data storage for user tokens, configurations, and SDK state.
Interface: ESPRMStorageAdapterInterface
Required Methods:
interface ESPRMStorageAdapterInterface {
setItem(name: string, value: string): Promise<void>;
getItem(name: string): Promise<string | null>;
removeItem(name: string): Promise<void>;
clear(): Promise<void>;
}
When to Use: Always required. The SDK needs persistent storage to maintain user sessions, cache data, and store configurations.
Common Implementations:
- Web:
localStorageorsessionStorage - React Native:
AsyncStorageorreact-native-mmkv - Node.js: File system or database storage
2. Provisioning Adapter
Purpose: Enables device provisioning functionality, allowing users to onboard new ESP devices to their WiFi network.
Interface: ESPProvisionAdapterInterface
Required Methods:
interface ESPProvisionAdapterInterface {
searchESPDevices(
devicePrefix: string,
transport: ESPTransport
): Promise<ESPDeviceInterface[]>;
stopESPDevicesSearch(): Promise<void>;
createESPDevice(
name: string,
transport: string,
security?: number,
proofOfPossession?: string,
softAPPassword?: string,
username?: string
): Promise<ESPDeviceInterface>;
connect(deviceName: string): Promise<ESPConnectStatus>;
getDeviceCapabilities(deviceName: string): Promise<string[]>;
getDeviceVersionInfo(deviceName: string): Promise<{ [key: string]: any }>;
setProofOfPossession(
deviceName: string,
proofOfPossession: string
): Promise<boolean>;
initializeSession(deviceName: string): Promise<boolean>;
scanWifiList(deviceName: string): Promise<ESPWifiList[]>;
sendData(deviceName: string, endPoint: string, data: string): Promise<string>;
provision(
deviceName: string,
ssid: string,
passphrase: string
): Promise<ESPProvisionStatus>;
disconnect(deviceName: string): Promise<void>;
}
When to Use: Required if your app needs to provision devices. This adapter handles communication with ESP devices during the onboarding process.
Common Implementations:
- React Native: Native modules for BLE/WiFi provisioning
- Web: Web Bluetooth API (for BLE provisioning)
3. Local Discovery Adapter
Purpose: Discovers ESP RainMaker devices on the local network using mDNS/Bonjour.
Interface: ESPLocalDiscoveryAdapterInterface
Required Methods:
interface ESPLocalDiscoveryAdapterInterface {
startDiscovery(callback: Function, params: DiscoveryParamsInterface): void;
stopDiscovery(): void;
}
interface DiscoveryParamsInterface {
serviceType: string; // e.g., "_esp_local_ctrl._tcp"
domain: string; // e.g., "local."
}
When to Use: Optional. Required if your app supports local control of devices (controlling devices without cloud connectivity).
Common Implementations:
- React Native: Native modules using platform mDNS/Bonjour APIs
- Node.js:
bonjourormdnspackages - Web: Limited support (no standard browser API)
4. Local Control Adapter
Purpose: Enables direct communication with devices on the local network without going through the cloud.
Interface: ESPLocalControlAdapterInterface
Required Methods:
interface ESPLocalControlAdapterInterface {
isConnected(nodeId: string): Promise<boolean>;
connect(
nodeId: string,
baseUrl: string,
securityType: number,
pop?: string,
username?: string
): Promise<Record<string, any>>;
sendData(nodeId: string, path: string, data: string): Promise<string>;
}
When to Use: Optional. Required if your app supports local control features for faster, more reliable device control when on the same network.
Common Implementations:
- React Native: Native modules for secure local communication
- Web/Node.js: HTTP/HTTPS clients with security protocol implementation
5. Notification Adapter
Purpose: Handles push notifications from the ESP RainMaker platform.
Interface: ESPNotificationAdapterInterface
Required Methods:
interface ESPNotificationAdapterInterface {
addNotificationListener(callback: Function): void;
}
When to Use: Optional. Required if your app needs to receive and handle push notifications about device events, alerts, or automation triggers.
Common Implementations:
- React Native: Firebase Cloud Messaging (FCM) or Apple Push Notification Service (APNS)
- Web: Web Push API or Firebase Cloud Messaging
6. OAuth Adapter
Purpose: Enables third-party authentication (Google, Apple, GitHub, etc.) for user login.
Interface: ESPOauthAdapterInterface
Required Methods:
interface ESPOauthAdapterInterface {
getCode(requestURL: string): Promise<string>;
}
When to Use: Optional. Required only if your app supports third-party login methods. The adapter opens an OAuth flow and returns the authorization code.
Common Implementations:
- React Native: In-app browser or native OAuth modules
- Web: Popup window or redirect-based OAuth flow
7. App Utility Adapter
Purpose: Provides platform-specific utility functions for permission checks and app features.
Interface: ESPAppUtilityAdapterInterface
Required Methods:
interface ESPAppUtilityAdapterInterface {
isBlePermissionGranted(): Promise<boolean>;
isLocationPermissionGranted(): Promise<boolean>;
}
When to Use: Optional. Useful for checking required permissions before attempting device provisioning or discovery.
Common Implementations:
- React Native: Native modules checking platform permissions
- Web: Browser permission APIs
Implementing Adapters
Basic Example: Storage Adapter
Here's a simple example of implementing a storage adapter for a web application:
import { ESPRMStorageAdapterInterface } from "@espressif/rainmaker-base-sdk";
class WebStorageAdapter implements ESPRMStorageAdapterInterface {
async setItem(name: string, value: string): Promise<void> {
window.localStorage.setItem(name, value);
}
async getItem(name: string): Promise<string | null> {
return window.localStorage.getItem(name);
}
async removeItem(name: string): Promise<void> {
window.localStorage.removeItem(name);
}
async clear(): Promise<void> {
window.localStorage.clear();
}
}
export default new WebStorageAdapter();
React Native Example: AsyncStorage Adapter
import AsyncStorage from "@react-native-async-storage/async-storage";
import { ESPRMStorageAdapterInterface } from "@espressif/rainmaker-base-sdk";
class AsyncStorageAdapter implements ESPRMStorageAdapterInterface {
async setItem(name: string, value: string): Promise<void> {
await AsyncStorage.setItem(name, value);
}
async getItem(name: string): Promise<string | null> {
return await AsyncStorage.getItem(name);
}
async removeItem(name: string): Promise<void> {
await AsyncStorage.removeItem(name);
}
async clear(): Promise<void> {
await AsyncStorage.clear();
}
}
export default new AsyncStorageAdapter();
Reference Implementation for React Native
A complete reference implementation of all adapters for React Native is available in the ESP RainMaker Home repository:
Repository: esp-rainmaker-home/adaptors
This implementation includes:
- ESPAsyncStorage - AsyncStorage-based storage adapter
- ESPProvAdapter - BLE/WiFi provisioning using native modules
- ESPDiscoveryAdapter - mDNS discovery using native modules
- ESPLocalControlAdapter - Local control implementation
- ESPNotificationAdapter - Push notification handling
- ESPOauthAdapter - OAuth authentication flow
- ESPAppUtilityAdapter - Permission checks and utilities
You can use these as a starting point for your React Native application or as a reference for implementing adapters for other frameworks.
Configuring Adapters
Once you've implemented your adapters, configure them with the SDK:
import { ESPRMBase } from "@espressif/rainmaker-base-sdk";
import storageAdapter from "./adapters/storage";
import provisionAdapter from "./adapters/provision";
import localDiscoveryAdapter from "./adapters/localDiscovery";
import localControlAdapter from "./adapters/localControl";
import notificationAdapter from "./adapters/notification";
import oauthAdapter from "./adapters/oauth";
import appUtilityAdapter from "./adapters/appUtility";
const config = {
baseUrl: "https://api.rainmaker.espressif.com",
version: "v1",
// Required adapter
customStorageAdapter: storageAdapter,
// Optional adapters (provide only what your app needs)
provisionAdapter: provisionAdapter,
localDiscoveryAdapter: localDiscoveryAdapter,
localControlAdapter: localControlAdapter,
notificationAdapter: notificationAdapter,
oauthAdapter: oauthAdapter,
appUtilityAdapter: appUtilityAdapter,
};
ESPRMBase.configure(config);
You only need to provide adapters for features your app uses. If you don't need provisioning or local control, you can omit those adapters.
Adapter Implementation for Different Frameworks
Vue.js / Nuxt
For Vue.js applications, you can implement adapters similar to React:
// Storage adapter using localStorage
export const vueStorageAdapter = {
async setItem(name: string, value: string): Promise<void> {
localStorage.setItem(name, value);
},
async getItem(name: string): Promise<string | null> {
return localStorage.getItem(name);
},
async removeItem(name: string): Promise<void> {
localStorage.removeItem(name);
},
async clear(): Promise<void> {
localStorage.clear();
},
};
Angular
Angular services can encapsulate adapter implementations:
import { Injectable } from "@angular/core";
import { ESPRMStorageAdapterInterface } from "@espressif/rainmaker-base-sdk";
@Injectable({
providedIn: "root",
})
export class StorageAdapterService implements ESPRMStorageAdapterInterface {
async setItem(name: string, value: string): Promise<void> {
localStorage.setItem(name, value);
}
async getItem(name: string): Promise<string | null> {
return localStorage.getItem(name);
}
async removeItem(name: string): Promise<void> {
localStorage.removeItem(name);
}
async clear(): Promise<void> {
localStorage.clear();
}
}
Electron
For Electron apps, you might want to use Node.js APIs:
import Store from "electron-store";
import { ESPRMStorageAdapterInterface } from "@espressif/rainmaker-base-sdk";
const store = new Store();
export const electronStorageAdapter: ESPRMStorageAdapterInterface = {
async setItem(name: string, value: string): Promise<void> {
store.set(name, value);
},
async getItem(name: string): Promise<string | null> {
return store.get(name) as string | null;
},
async removeItem(name: string): Promise<void> {
store.delete(name);
},
async clear(): Promise<void> {
store.clear();
},
};
Best Practices
1. Error Handling
Always implement proper error handling in your adapters:
async setItem(name: string, value: string): Promise<void> {
try {
await AsyncStorage.setItem(name, value);
} catch (error) {
console.error('Failed to save item:', error);
throw error;
}
}
2. Type Safety
Use TypeScript's type system to ensure your adapters implement all required methods:
import { ESPRMStorageAdapterInterface } from "@espressif/rainmaker-base-sdk";
// TypeScript will enforce that all methods are implemented
class MyStorageAdapter implements ESPRMStorageAdapterInterface {
// ... implementation
}
3. Platform Detection
For universal apps, detect the platform and provide appropriate adapters:
import webStorageAdapter from "./adapters/web-storage";
import reactNativeStorageAdapter from "./adapters/rn-storage";
const storageAdapter =
Platform.OS === "web" ? webStorageAdapter : reactNativeStorageAdapter;
ESPRMBase.configure({
// ...
customStorageAdapter: storageAdapter,
});
Troubleshooting
Missing Adapter Error
If you see an error about a missing adapter, ensure you've provided it in the configuration:
// ❌ Missing storage adapter
ESPRMBase.configure({
baseUrl: "https://api.rainmaker.espressif.com",
});
// ✅ Storage adapter provided
ESPRMBase.configure({
baseUrl: "https://api.rainmaker.espressif.com",
customStorageAdapter: storageAdapter,
});
Interface Mismatch
If TypeScript reports that your adapter doesn't match the interface, ensure all methods are implemented with correct signatures:
// ❌ Missing async/await
class BadAdapter implements ESPRMStorageAdapterInterface {
setItem(name: string, value: string): void {
// Missing Promise return type
localStorage.setItem(name, value);
}
}
// ✅ Correct implementation
class GoodAdapter implements ESPRMStorageAdapterInterface {
async setItem(name: string, value: string): Promise<void> {
localStorage.setItem(name, value);
}
}