Skip to content

Commit

Permalink
Experimental AsyncStorage support for react-native (#983)
Browse files Browse the repository at this point in the history
* Experimental AsyncStorage support for react-native

Use @react-native-async-storage/async-storage to implement an AsyncStorage based storage.

* Finalize

Use in react native env

Also downgrades chai-as-promise because the esm only has effects on running tests locally

* Merge

* fix package-lock and ignore strange types

* fix package-lock
  • Loading branch information
Apollon77 authored Jul 1, 2024
1 parent f3858f7 commit df4ae8f
Show file tree
Hide file tree
Showing 8 changed files with 19,813 additions and 14,171 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ The main work (all changes without a GitHub username in brackets in the below li
- Deprecation: We've deprecated the hand-generated device type definitions used by the pre-0.8.0 API in DeviceTypes.ts. These device type definitions remain at Matter 1.1.
- Removal: We removed old Scenes cluster implementation which was never fully implemented or used by any Matter controller
- matter.js-react-native:
- Feature: Introduces new package that provides a React Native compatible platform Implementations for Matter.js. This package is still in development and should be considered experimental for now! Currently supports UDP, BLE and Crypto platform features. A In-memory storage is used for now because a react-native persisting Storage backend is missing currently.
- Feature: Introduces new package that provides a React Native compatible platform Implementations for Matter.js. This package is still in development and should be considered experimental for now! Currently supports UDP, BLE, AsyncStorage and Crypto platform features.
- matter.js chip and python Testing:
- Includes updates and infrastructure improvements for Matter.js use of tests defined in [connectedhomeip](https://github.com/project-chip/connectedhomeip)

Expand Down
33,819 changes: 19,654 additions & 14,165 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/matter-node.js/src/storage/StorageBackendDisk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ export class StorageBackendDisk extends SyncStorage {
}

contexts(contexts: string[]) {
const contextKey = contexts.length ? this.getContextBaseKey(contexts) : "";
const contextKey = this.getContextBaseKey(contexts, true);
const startContextKey = contextKey.length ? `${contextKey}.` : "";
const foundContexts = new Array<string>();
for (const key of Object.keys(this.localStorage)) {
Expand Down
1 change: 1 addition & 0 deletions packages/matter.js-react-native/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This package uses the following react-native libraries to provide the needed fun
- react-native-quick-crypto
- react-native-ble-plx
- react-native-udp
- @react-native-async-storage/async-storage
Please check these projects if special preparations need to be done for your developed app.


Expand Down
1 change: 1 addition & 0 deletions packages/matter.js-react-native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"dependencies": {
"@project-chip/matter-node.js": "0.10.0-alpha.0-20240630-77b4749e",
"@project-chip/matter.js": "0.10.0-alpha.0-20240630-77b4749e",
"@react-native-async-storage/async-storage": "^1.23.1",
"react-native-ble-plx": "^3.2.0",
"react-native-quick-crypto": "^0.7.0-rc.9",
"react-native-udp": "^4.1.7"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

import { Environment, StorageService, VariableService } from "@project-chip/matter.js/environment";
import { Network } from "@project-chip/matter.js/net";
import { StorageBackendMemory } from "@project-chip/matter.js/storage";
import { NetworkReactNative } from "../net/NetworkReactNative.js";
import { StorageBackendAsyncStorage } from "../storage/StorageBackendAsyncStorage.js";

/**
* This is the default environment implementation for React-native:
*
* - Uses memory storage for now, so nothing is persisted!
* - Uses AsyncStorage for storage
*/
export function ReactNativeEnvironment() {
const env = new Environment("default");
Expand All @@ -38,7 +38,7 @@ function configureStorage(env: Environment) {
service.location = "Memory";
});

service.factory = _namespace => new StorageBackendMemory(); // TODO implement real persistence
service.factory = namespace => new StorageBackendAsyncStorage(namespace);
}

function configureNetwork(env: Environment) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
/**
* @license
* Copyright 2022-2024 Matter.js Authors
* SPDX-License-Identifier: Apache-2.0
*/

import {
MaybeAsyncStorage,
StorageError,
SupportedStorageTypes,
fromJson,
toJson,
} from "@project-chip/matter.js/storage";
import AsyncStorage from "@react-native-async-storage/async-storage";

export class StorageBackendAsyncStorage extends MaybeAsyncStorage {
#namespace: string;
protected isInitialized = false;

/**
* Creates a new instance of the AsyncStorage storage backend. In a "namespace" is provided then the keys will be
* prefixed with the namespace (separated with a # which is normally not used in matter.js keys).
*/
constructor(namespace?: string) {
super();
this.#namespace = namespace ?? "";
}

get initialized() {
return this.isInitialized;
}

initialize() {
this.isInitialized = true;
}

close() {
this.isInitialized = false;
}

clear() {
// @ts-expect-error AsyncStorage types are not correct
return AsyncStorage.clear();
}

getContextBaseKey(contexts: string[], allowEmptyContext = false) {
const contextKey = contexts.join(".");
if (
(!contextKey.length && !allowEmptyContext) ||
contextKey.includes("..") ||
contextKey.startsWith(".") ||
contextKey.endsWith(".")
)
throw new StorageError("Context must not be an empty and not contain dots.");
return `${this.#namespace.length ? `${this.#namespace}#` : ""}${contextKey}`;
}

buildStorageKey(contexts: string[], key: string) {
if (!key.length) {
throw new StorageError("Key must not be an empty string.");
}
const contextKey = this.getContextBaseKey(contexts);
return `${contextKey}.${key}`;
}

async get<T extends SupportedStorageTypes>(contexts: string[], key: string): Promise<T | undefined> {
// @ts-expect-error AsyncStorage types are not correct
const value = await AsyncStorage.getItem(this.buildStorageKey(contexts, key));
if (value === null) return undefined;
return fromJson(value) as T;
}

set(contexts: string[], key: string, value: SupportedStorageTypes): Promise<void>;
set(contexts: string[], values: Record<string, SupportedStorageTypes>): Promise<void>;
async set(
contexts: string[],
keyOrValues: string | Record<string, SupportedStorageTypes>,
value?: SupportedStorageTypes,
) {
if (typeof keyOrValues === "string") {
// @ts-expect-error AsyncStorage types are not correct
await AsyncStorage.setItem(this.buildStorageKey(contexts, keyOrValues), toJson(value));
} else {
for (const [key, value] of Object.entries(keyOrValues)) {
// @ts-expect-error AsyncStorage types are not correct
await AsyncStorage.setItem(this.buildStorageKey(contexts, key), toJson(value));
}
}
}

delete(contexts: string[], key: string) {
// @ts-expect-error AsyncStorage types are not correct
return AsyncStorage.removeItem(this.buildStorageKey(contexts, key));
}

/** Returns all keys of a storage context without keys of sub-contexts */
async keys(contexts: string[]) {
const contextKey = this.getContextBaseKey(contexts);
const keys = [];
const contextKeyStart = `${contextKey}.`;
// @ts-expect-error AsyncStorage types are not correct
const allKeys = await AsyncStorage.getAllKeys();
for (const key of allKeys) {
if (key.startsWith(contextKeyStart) && key.indexOf(".", contextKeyStart.length) === -1) {
keys.push(key.substring(contextKeyStart.length));
}
}
return keys;
}

async values(contexts: string[]) {
// Initialize and context checks are done by keys method
const values = {} as Record<string, SupportedStorageTypes>;
for (const key of await this.keys(contexts)) {
values[key] = await this.get(contexts, key);
}
return values;
}

async contexts(contexts: string[]) {
const contextKey = this.getContextBaseKey(contexts, true);
const startContextKey = contextKey.length ? `${contextKey}.` : "";
const foundContexts = new Array<string>();
// @ts-expect-error AsyncStorage types are not correct
const allKeys = await AsyncStorage.getAllKeys();
for (const key of allKeys) {
if (key.startsWith(startContextKey)) {
const subKeys = key.substring(startContextKey.length).split(".");
if (subKeys.length === 1) continue; // found leaf key
const context = subKeys[0];
if (!foundContexts.includes(context)) {
foundContexts.push(context);
}
}
}
return foundContexts;
}

async clearAll(contexts: string[]) {
const contextKey = this.getContextBaseKey(contexts, true);
const startContextKey = contextKey.length ? `${contextKey}.` : "";
// @ts-expect-error AsyncStorage types are not correct
const allKeys = await AsyncStorage.getAllKeys();
for (const key of allKeys) {
if (key.startsWith(startContextKey)) {
// @ts-expect-error AsyncStorage types are not correct
await AsyncStorage.removeItem(key);
}
}
}
}
2 changes: 1 addition & 1 deletion packages/matter.js-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
"ansi-colors": "^4.1.3",
"c8": "^10.1.2",
"chai": "^4.4.1",
"chai-as-promised": "^8.0.0",
"chai-as-promised": "^7.1.2",
"esbuild": "^0.21.5",
"express": "^4.19.2",
"glob": "^10.4.1",
Expand Down

0 comments on commit df4ae8f

Please sign in to comment.