跳到主要内容

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);
提示

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