Skip to main content

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: localStorage or sessionStorage
  • React Native: AsyncStorage or react-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: bonjour or mdns packages
  • 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:

  1. ESPAsyncStorage - AsyncStorage-based storage adapter
  2. ESPProvAdapter - BLE/WiFi provisioning using native modules
  3. ESPDiscoveryAdapter - mDNS discovery using native modules
  4. ESPLocalControlAdapter - Local control implementation
  5. ESPNotificationAdapter - Push notification handling
  6. ESPOauthAdapter - OAuth authentication flow
  7. 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);
tip

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);
}
}