Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Experimental AsyncStorage support for react-native #983

Merged
merged 6 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Apollon77 marked this conversation as resolved.
Show resolved Hide resolved
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