FRE-600: Fix code review blockers

- Consolidated duplicate UndoManagers to single instance
- Fixed connection promise to only resolve on 'connected' status
- Fixed WebSocketProvider import (WebsocketProvider)
- Added proper doc.destroy() cleanup
- Renamed isPresenceInitialized property to avoid conflict

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-25 00:08:01 -04:00
parent 65b552bb08
commit 7c684a42cc
48450 changed files with 5679671 additions and 383 deletions

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2015-present, Facebook, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,52 @@
# React Native Async Storage
An asynchronous, unencrypted, persistent, key-value storage system for React
Native.
## Supported platforms
- Android
- iOS
- [macOS](https://github.com/react-native-async-storage/async-storage/releases/tag/v1.8.1)
- [Web](https://github.com/react-native-async-storage/async-storage/releases/tag/v1.9.0)
- [Windows](https://github.com/react-native-async-storage/async-storage/releases/tag/v1.10.0)
## Getting Started
Head over to the
[documentation](https://react-native-async-storage.github.io/async-storage/docs/install)
to learn more.
## Running E2E locally
### Android
1. Create and start Android Emulator with Play services, API level 29
2. Build app and run tests
```shell
yarn bundle:android
yarn build:e2e:android
yarn test:e2e:android
```
### iOS
1. Create and start iPhone 14 simulator with iOS version 16.4
2. Build app and run tests
```shell
yarn bundle:ios
pod install --project-directory=example/ios
yarn build:e2e:ios
yarn test:e2e:ios
```
## Contribution
Pull requests are welcome. Please open an issue first to discuss what you would
like to change.
See the [CONTRIBUTING](.github/CONTRIBUTING.md) file for more information.
## License
MIT

View File

@@ -0,0 +1,45 @@
require 'json'
package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
Pod::Spec.new do |s|
s.name = "RNCAsyncStorage"
s.version = package['version']
s.summary = package['description']
s.license = package['license']
s.authors = package['author']
s.homepage = package['homepage']
s.source = { :git => "https://github.com/react-native-async-storage/async-storage.git", :tag => "v#{s.version}" }
s.source_files = "ios/**/*.{h,m,mm}"
s.resource_bundles = { "RNCAsyncStorage_resources" => "ios/PrivacyInfo.xcprivacy" }
if fabric_enabled
folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
s.pod_target_xcconfig = {
'HEADER_SEARCH_PATHS' => '"$(PODS_ROOT)/boost" "$(PODS_ROOT)/boost-for-react-native" "$(PODS_ROOT)/RCT-Folly"',
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
}
s.platforms = { ios: '13.4', tvos: '11.0', :osx => "10.15", :visionos => "1.0" }
s.compiler_flags = folly_compiler_flags + ' -DRCT_NEW_ARCH_ENABLED=1'
if respond_to?(:install_modules_dependencies, true)
install_modules_dependencies(s)
else
s.dependency "React-Core"
s.dependency "React-Codegen"
s.dependency "RCT-Folly"
s.dependency "RCTRequired"
s.dependency "RCTTypeSafety"
s.dependency "ReactCommon/turbomodule/core"
end
else
s.platforms = { :ios => "9.0", :tvos => "9.2", :osx => "10.14", :visionos => "1.0" }
s.dependency "React-Core"
end
end

View File

@@ -0,0 +1,124 @@
configurations {
compileClasspath
}
buildscript {
apply from: "config.gradle"
def kotlinVersion = ext.AsyncStorageConfig.kotlinVersion
def kspVersion = ext.AsyncStorageConfig.kspVersion
repositories {
mavenCentral()
google()
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:$kspVersion"
}
}
apply plugin: 'com.android.library'
apply from: 'config.gradle'
boolean isNewArchitectureEnabled = ext.AsyncStorageConfig.isNewArchitectureEnabled
boolean useNextStorage = ext.AsyncStorageConfig.useNextStorage
logger.info("[AsyncStorage] Config used: {}", ext.AsyncStorageConfig)
if (useNextStorage) {
apply plugin: 'com.google.devtools.ksp'
apply plugin: 'kotlin-android'
apply from: './testresults.gradle'
}
if (isNewArchitectureEnabled) {
apply plugin: "com.facebook.react"
}
android {
def agpVersion = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION
if (agpVersion.tokenize('.')[0].toInteger() >= 7) {
namespace "com.reactnativecommunity.asyncstorage"
buildFeatures {
buildConfig true
}
}
compileSdkVersion project.ext.AsyncStorageConfig.compileSdkVersion
// Used to override the NDK path/version by allowing users to customize
// the NDK path/version from their root project (e.g. for M1 support)
if (rootProject.hasProperty("ndkPath")) {
ndkPath rootProject.ext.ndkPath
}
if (rootProject.hasProperty("ndkVersion")) {
ndkVersion rootProject.ext.ndkVersion
}
defaultConfig {
minSdkVersion project.ext.AsyncStorageConfig.minSdkVersion
targetSdkVersion project.ext.AsyncStorageConfig.targetSdkVersion
buildConfigField "Long", "AsyncStorage_db_size", "${project.ext.AsyncStorageConfig.databaseSizeMB}L"
buildConfigField "boolean", "AsyncStorage_useDedicatedExecutor", "${project.ext.AsyncStorageConfig.useDedicatedExecutor}"
buildConfigField "boolean", "AsyncStorage_useNextStorage", "${useNextStorage}"
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "${isNewArchitectureEnabled}"
}
lintOptions {
abortOnError false
}
if (useNextStorage) {
testOptions {
unitTests {
returnDefaultValues = true
includeAndroidResources = true
}
}
}
sourceSets.main {
java {
if (useNextStorage) {
srcDirs += 'src/kotlinPackage/java'
} else {
srcDirs += 'src/javaPackage/java'
}
if (!isNewArchitectureEnabled) {
srcDirs += 'src/oldarch/java'
}
}
}
}
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "${project.ext.resolveModulePath("react-native")}/android"
}
google()
mavenCentral()
}
dependencies {
if (useNextStorage) {
def room_version = project.ext.AsyncStorageConfig.roomVersion
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
ksp "androidx.room:room-compiler:$room_version"
implementation project.ext.AsyncStorageLibs.coroutines
testImplementation project.ext.AsyncStorageLibs.testCoroutines
testImplementation project.ext.AsyncStorageLibs.testJunit
testImplementation project.ext.AsyncStorageLibs.testExtJunit
testImplementation project.ext.AsyncStorageLibs.testRunner
testImplementation project.ext.AsyncStorageLibs.testRules
testImplementation project.ext.AsyncStorageLibs.testRobolectric
testImplementation project.ext.AsyncStorageLibs.testTruth
}
implementation 'com.facebook.react:react-native:+' // from node_modules
}

View File

@@ -0,0 +1,125 @@
import java.nio.file.Paths
def DEFAULT_KOTLIN_VERSION = "1.9.24"
def DEFAULT_ROOM_VERSION = "2.6.1"
def kotlinVersion = getKotlinVersion(DEFAULT_KOTLIN_VERSION)
project.ext.AsyncStorageConfig = [
kotlinVersion : kotlinVersion,
kspVersion : getKspVersion(kotlinVersion),
roomVersion : getPropertyOfDefault('AsyncStorage_next_roomVersion', DEFAULT_ROOM_VERSION),
minSdkVersion : safeExtGet('minSdkVersion', 23),
targetSdkVersion : safeExtGet('targetSdkVersion', 32),
compileSdkVersion : safeExtGet('compileSdkVersion', 32),
useNextStorage : getFlagOrDefault("AsyncStorage_useNextStorage", false),
databaseSizeMB : getDatabaseSize(),
isNewArchitectureEnabled: isNewArchitectureEnabled(),
useDedicatedExecutor : getFlagOrDefault('AsyncStorage_dedicatedExecutor', false),
]
project.ext.AsyncStorageLibs = [
coroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0",
testCoroutines : "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0",
testJunit : "junit:junit:4.13.2",
testRunner : "androidx.test:runner:1.4.0",
testRules : "androidx.test:rules:1.4.0",
testExtJunit : "androidx.test.ext:junit:1.1.3",
testRobolectric: "org.robolectric:robolectric:4.11.1",
testTruth : "com.google.truth:truth:1.1.3",
]
def getKotlinVersion(String defaultVersion) {
return rootProject.ext.has('kotlinVersion')
? rootProject.ext['kotlinVersion']
: rootProject.hasProperty('AsyncStorage_kotlinVersion')
? rootProject.properties['AsyncStorage_kotlinVersion']
: defaultVersion
}
def isNewArchitectureEnabled() {
// To opt-in for the New Architecture, you can either:
// - Set `newArchEnabled` to true inside the `gradle.properties` file
// - Invoke gradle with `-newArchEnabled=true`
// - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
}
String getKspVersion(String kotlinVersion) {
String overriddenKspVersion = getPropertyOfDefault("AsyncStorage_next_kspVersion", null)
if (overriddenKspVersion != null) {
return overriddenKspVersion
}
// https://github.com/google/ksp/releases
def kspVersions = [
"1.9.24-1.0.20",
"1.9.23-1.0.20",
"1.9.22-1.0.17",
"1.9.21-1.0.16",
"1.9.20-1.0.14",
"1.9.10-1.0.13",
"1.9.0-1.0.13",
"1.8.22-1.0.11",
"1.8.21-1.0.11",
"1.8.20-1.0.11",
"1.8.10-1.0.9",
"1.8.0-1.0.9",
"1.7.22-1.0.8",
"1.7.21-1.0.8",
"1.7.20-1.0.8",
"1.7.10-1.0.6",
"1.7.0-1.0.6",
"1.6.21-1.0.6",
"1.6.20-1.0.5",
"1.6.10-1.0.4",
"1.6.0-1.0.2",
"1.5.31-1.0.1",
"1.5.30-1.0.0",
]
return kspVersions.find { it.startsWith(kotlinVersion) } ?: kspVersions.first()
}
// AsyncStorage has default size of 6MB.
// This is a sane limit to protect the user from the app storing too much data in the database.
// This also protects the database from filling up the disk cache and becoming malformed.
// If you really need bigger size, please keep in mind the potential consequences.
long getDatabaseSize() {
long dbSizeInMB = 6L
def newDbSize = getPropertyOfDefault('AsyncStorage_db_size_in_MB', null)
if (newDbSize != null && newDbSize.isLong()) {
dbSizeInMB = newDbSize.toLong()
}
return dbSizeInMB
}
def safeExtGet(prop, fallback) {
rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
}
def getFlagOrDefault(flagName, defaultValue) {
rootProject.hasProperty(flagName) ? rootProject.properties[flagName] == "true" : defaultValue
}
def getPropertyOfDefault(String flagName, String defaultVersion) {
rootProject.hasProperty(flagName) ? rootProject.properties[flagName] : defaultVersion
}
ext.resolveModulePath = { packageName ->
def basePath = rootDir.toPath().normalize()
// Node's module resolution algorithm searches up to the root directory,
// after which the base path will be null
while (basePath) {
def candidatePath = Paths.get(basePath.toString(), 'node_modules', packageName)
if (candidatePath.toFile().exists()) {
return candidatePath.toString()
}
basePath = basePath.getParent()
}
return null
}

View File

@@ -0,0 +1 @@
android.useAndroidX=true

View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.ModuleSpec;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import com.facebook.react.uimanager.ViewManager;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
@ReactModuleList(
nativeModules = {
AsyncStorageModule.class,
}
)
public class AsyncStoragePackage extends TurboReactPackage {
@Override
protected List<ModuleSpec> getViewManagers(ReactApplicationContext reactContext) {
return null;
}
@Override
public NativeModule getModule(String name, @Nonnull ReactApplicationContext reactContext) {
switch (name) {
case AsyncStorageModule.NAME:
return new AsyncStorageModule(reactContext);
default:
return null;
}
}
@Override
public ReactModuleInfoProvider getReactModuleInfoProvider() {
try {
Class<?> reactModuleInfoProviderClass =
Class.forName("com.reactnativecommunity.asyncstorage.AsyncStoragePackage$$ReactModuleInfoProvider");
return (ReactModuleInfoProvider) reactModuleInfoProviderClass.newInstance();
} catch (ClassNotFoundException e) {
// ReactModuleSpecProcessor does not run at build-time. Create this ReactModuleInfoProvider by
// hand.
return new ReactModuleInfoProvider() {
@Override
public Map<String, ReactModuleInfo> getReactModuleInfos() {
final Map<String, ReactModuleInfo> reactModuleInfoMap = new HashMap<>();
boolean isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
Class<? extends NativeModule>[] moduleList =
new Class[] {
AsyncStorageModule.class,
};
for (Class<? extends NativeModule> moduleClass : moduleList) {
ReactModule reactModule = moduleClass.getAnnotation(ReactModule.class);
reactModuleInfoMap.put(
reactModule.name(),
new ReactModuleInfo(
reactModule.name(),
moduleClass.getName(),
reactModule.canOverrideExistingModule(),
reactModule.needsEagerInit(),
reactModule.hasConstants(),
reactModule.isCxxModule(),
isTurboModule));
}
return reactModuleInfoMap;
}
};
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(
"No ReactModuleInfoProvider for com.reactnativecommunity.asyncstorage.AsyncStoragePackage$$ReactModuleInfoProvider", e);
}
}
@Override
@SuppressWarnings("rawtypes")
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}

View File

@@ -0,0 +1,55 @@
package com.reactnativecommunity.asyncstorage
import com.facebook.react.TurboReactPackage
import com.facebook.react.bridge.ModuleSpec
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.module.annotations.ReactModuleList
import com.facebook.react.module.model.ReactModuleInfo
import com.facebook.react.module.model.ReactModuleInfoProvider
import com.reactnativecommunity.asyncstorage.next.StorageModule
@ReactModuleList(
nativeModules = [
StorageModule::class
]
)
class AsyncStoragePackage : TurboReactPackage() {
override fun getModule(name: String, context: ReactApplicationContext): NativeModule? = when (name) {
StorageModule.NAME -> StorageModule(context)
else -> null
}
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
try {
val reactModuleInfoProviderClass =
Class.forName("com.reactnativecommunity.asyncstorage.AsyncStoragePackage$\$ReactModuleInfoProvider")
return reactModuleInfoProviderClass.newInstance() as ReactModuleInfoProvider
} catch (e: ClassNotFoundException) {
return ReactModuleInfoProvider {
val isTurboModule = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
val reactModule: ReactModule = StorageModule::class.java.getAnnotation(
ReactModule::class.java)!!
mapOf(
StorageModule.NAME to ReactModuleInfo(
reactModule.name,
StorageModule::class.java.name,
reactModule.canOverrideExistingModule,
reactModule.needsEagerInit,
reactModule.hasConstants,
reactModule.isCxxModule,
isTurboModule
)
)
}
} catch (e: InstantiationException) {
throw RuntimeException("No ReactModuleInfoProvider for AsyncStoragePackage$\$ReactModuleInfoProvider", e)
} catch (e: IllegalAccessException) {
throw RuntimeException("No ReactModuleInfoProvider for AsyncStoragePackage$\$ReactModuleInfoProvider", e)
}
}
override fun getViewManagers(reactContext: ReactApplicationContext?): MutableList<ModuleSpec>? = null
}

View File

@@ -0,0 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnativecommunity.asyncstorage">
</manifest>

View File

@@ -0,0 +1,178 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import javax.annotation.Nullable;
import java.io.File;
import java.util.Arrays;
import java.util.Iterator;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import com.facebook.react.bridge.ReadableArray;
import org.json.JSONException;
import org.json.JSONObject;
import static com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier.KEY_COLUMN;
import static com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier.TABLE_CATALYST;
import static com.reactnativecommunity.asyncstorage.ReactDatabaseSupplier.VALUE_COLUMN;
/**
* Helper for database operations.
*/
public class AsyncLocalStorageUtil {
/**
* Build the String required for an SQL select statement:
* WHERE key IN (?, ?, ..., ?)
* without 'WHERE' and with selectionCount '?'
*/
/* package */ static String buildKeySelection(int selectionCount) {
String[] list = new String[selectionCount];
Arrays.fill(list, "?");
return KEY_COLUMN + " IN (" + TextUtils.join(", ", list) + ")";
}
/**
* Build the String[] arguments needed for an SQL selection, i.e.:
* {a, b, c}
* to be used in the SQL select statement: WHERE key in (?, ?, ?)
*/
/* package */ static String[] buildKeySelectionArgs(ReadableArray keys, int start, int count) {
String[] selectionArgs = new String[count];
for (int keyIndex = 0; keyIndex < count; keyIndex++) {
selectionArgs[keyIndex] = keys.getString(start + keyIndex);
}
return selectionArgs;
}
/**
* Returns the value of the given key, or null if not found.
*/
public static @Nullable String getItemImpl(SQLiteDatabase db, String key) {
String[] columns = {VALUE_COLUMN};
String[] selectionArgs = {key};
Cursor cursor = db.query(
TABLE_CATALYST,
columns,
KEY_COLUMN + "=?",
selectionArgs,
null,
null,
null);
try {
if (!cursor.moveToFirst()) {
return null;
} else {
return cursor.getString(0);
}
} finally {
cursor.close();
}
}
/**
* Sets the value for the key given, returns true if successful, false otherwise.
*/
/* package */ static boolean setItemImpl(SQLiteDatabase db, String key, String value) {
ContentValues contentValues = new ContentValues();
contentValues.put(KEY_COLUMN, key);
contentValues.put(VALUE_COLUMN, value);
long inserted = db.insertWithOnConflict(
TABLE_CATALYST,
null,
contentValues,
SQLiteDatabase.CONFLICT_REPLACE);
return (-1 != inserted);
}
/**
* Does the actual merge of the (key, value) pair with the value stored in the database.
* NB: This assumes that a database lock is already in effect!
* @return the errorCode of the operation
*/
/* package */ static boolean mergeImpl(SQLiteDatabase db, String key, String value)
throws JSONException {
String oldValue = getItemImpl(db, key);
String newValue;
if (oldValue == null) {
newValue = value;
} else {
JSONObject oldJSON = new JSONObject(oldValue);
JSONObject newJSON = new JSONObject(value);
deepMergeInto(oldJSON, newJSON);
newValue = oldJSON.toString();
}
return setItemImpl(db, key, newValue);
}
/**
* Merges two {@link JSONObject}s. The newJSON object will be merged with the oldJSON object by
* either overriding its values, or merging them (if the values of the same key in both objects
* are of type {@link JSONObject}). oldJSON will contain the result of this merge.
*/
private static void deepMergeInto(JSONObject oldJSON, JSONObject newJSON)
throws JSONException {
Iterator<?> keys = newJSON.keys();
while (keys.hasNext()) {
String key = (String) keys.next();
JSONObject newJSONObject = newJSON.optJSONObject(key);
JSONObject oldJSONObject = oldJSON.optJSONObject(key);
if (newJSONObject != null && oldJSONObject != null) {
deepMergeInto(oldJSONObject, newJSONObject);
oldJSON.put(key, oldJSONObject);
} else {
oldJSON.put(key, newJSON.get(key));
}
}
}
/**
* From Pie and up, Android started to use Write-ahead logging (WAL), instead of journal rollback
* for atomic commits and rollbacks.
* Basically, WAL does not write directly to the database file, rather to the supporting WAL file.
* Because of that, migration to the next storage might not be successful, because the content of
* RKStorage might be still in WAL file instead. Committing all data from WAL to db file is called
* a "checkpoint" and is done automatically (by default) when the WAL file reaches a threshold
* size of 1000 pages.
* More here: https://sqlite.org/wal.html
*
* This helper will force checkpoint on RKStorage, if Next storage file does not exists yet.
*/
public static void verifyAndForceSqliteCheckpoint(Context ctx) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
Log.i("AsyncStorage_Next", "SQLite checkpoint not required on this API version.");
}
File nextStorageFile = ctx.getDatabasePath("AsyncStorage");
File currentStorageFile = ctx.getDatabasePath(ReactDatabaseSupplier.DATABASE_NAME);
boolean isCheckpointRequired = !nextStorageFile.exists() && currentStorageFile.exists();
if (!isCheckpointRequired) {
Log.i("AsyncStorage_Next", "SQLite checkpoint not required.");
return;
}
try {
ReactDatabaseSupplier supplier = ReactDatabaseSupplier.getInstance(ctx);
supplier.get().rawQuery("PRAGMA wal_checkpoint", null).close();
supplier.closeDatabase();
Log.i("AsyncStorage_Next", "Forcing SQLite checkpoint successful.");
} catch (Exception e) {
Log.w("AsyncStorage_Next", "Could not force checkpoint on RKStorage, the Next storage might not migrate the data properly: " + e.getMessage());
}
}
}

View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import javax.annotation.Nullable;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.WritableMap;
/**
* Helper class for database errors.
*/
public class AsyncStorageErrorUtil {
/**
* Create Error object to be passed back to the JS callback.
*/
/* package */ static WritableMap getError(@Nullable String key, String errorMessage) {
WritableMap errorMap = Arguments.createMap();
errorMap.putString("message", errorMessage);
if (key != null) {
errorMap.putString("key", key);
}
return errorMap;
}
/* package */ static WritableMap getInvalidKeyError(@Nullable String key) {
return getError(key, "Invalid key");
}
/* package */ static WritableMap getInvalidValueError(@Nullable String key) {
return getError(key, "Invalid Value");
}
/* package */ static WritableMap getDBError(@Nullable String key) {
return getError(key, "Database Error");
}
}

View File

@@ -0,0 +1,154 @@
package com.reactnativecommunity.asyncstorage;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.Files;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
// A utility class that migrates a scoped AsyncStorage database to RKStorage.
// This utility only runs if the RKStorage file has not been created yet.
public class AsyncStorageExpoMigration {
static final String LOG_TAG = "AsyncStorageExpoMigration";
public static void migrate(Context context) {
// Only migrate if the default async storage file does not exist.
if (isAsyncStorageDatabaseCreated(context)) {
return;
}
ArrayList<File> expoDatabases = getExpoDatabases(context);
File expoDatabase = getLastModifiedFile(expoDatabases);
if (expoDatabase == null) {
Log.v(LOG_TAG, "No scoped database found");
return;
}
try {
// Create the storage file
ReactDatabaseSupplier.getInstance(context).get();
copyFile(new FileInputStream(expoDatabase), new FileOutputStream(context.getDatabasePath(ReactDatabaseSupplier.DATABASE_NAME)));
Log.v(LOG_TAG, "Migrated most recently modified database " + expoDatabase.getName() + " to RKStorage");
} catch (Exception e) {
Log.v(LOG_TAG, "Failed to migrate scoped database " + expoDatabase.getName());
e.printStackTrace();
return;
}
try {
for (File file : expoDatabases) {
if (file.delete()) {
Log.v(LOG_TAG, "Deleted scoped database " + file.getName());
} else {
Log.v(LOG_TAG, "Failed to delete scoped database " + file.getName());
}
}
} catch (Exception e) {
e.printStackTrace();
}
Log.v(LOG_TAG, "Completed the scoped AsyncStorage migration");
}
private static boolean isAsyncStorageDatabaseCreated(Context context) {
return context.getDatabasePath(ReactDatabaseSupplier.DATABASE_NAME).exists();
}
// Find all database files that the user may have created while using Expo.
private static ArrayList<File> getExpoDatabases(Context context) {
ArrayList<File> scopedDatabases = new ArrayList<>();
try {
File databaseDirectory = context.getDatabasePath("noop").getParentFile();
File[] directoryListing = databaseDirectory.listFiles();
if (directoryListing != null) {
for (File child : directoryListing) {
// Find all databases matching the Expo scoped key, and skip any database journals.
if (child.getName().startsWith("RKStorage-scoped-experience-") && !child.getName().endsWith("-journal")) {
scopedDatabases.add(child);
}
}
}
} catch (Exception e) {
// Just in case anything happens catch and print, file system rules can tend to be different across vendors.
e.printStackTrace();
}
return scopedDatabases;
}
// Returns the most recently modified file.
// If a user publishes an app with Expo, then changes the slug
// and publishes again, a new database will be created.
// We want to select the most recent database and migrate it to RKStorage.
private static File getLastModifiedFile(ArrayList<File> files) {
if (files.size() == 0) {
return null;
}
long lastMod = -1;
File lastModFile = null;
for (File child : files) {
long modTime = getLastModifiedTimeInMillis(child);
if (modTime > lastMod) {
lastMod = modTime;
lastModFile = child;
}
}
if (lastModFile != null) {
return lastModFile;
}
return files.get(0);
}
private static long getLastModifiedTimeInMillis(File file) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
return getLastModifiedTimeFromBasicFileAttrs(file);
} else {
return file.lastModified();
}
} catch (Exception e) {
e.printStackTrace();
return -1;
}
}
@RequiresApi(Build.VERSION_CODES.O)
private static long getLastModifiedTimeFromBasicFileAttrs(File file) {
try {
return Files.readAttributes(file.toPath(), BasicFileAttributes.class).creationTime().toMillis();
} catch (Exception e) {
return -1;
}
}
private static void copyFile(FileInputStream fromFile, FileOutputStream toFile) throws IOException {
FileChannel fromChannel = null;
FileChannel toChannel = null;
try {
fromChannel = fromFile.getChannel();
toChannel = toFile.getChannel();
fromChannel.transferTo(0, fromChannel.size(), toChannel);
} finally {
try {
if (fromChannel != null) {
fromChannel.close();
}
} finally {
if (toChannel != null) {
toChannel.close();
}
}
}
}
}

View File

@@ -0,0 +1,430 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import android.os.AsyncTask;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.GuardedAsyncTask;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.annotations.VisibleForTesting;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.modules.common.ModuleDataCleaner;
import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ReactModule(name = AsyncStorageModule.NAME)
public final class AsyncStorageModule
extends NativeAsyncStorageModuleSpec implements ModuleDataCleaner.Cleanable, LifecycleEventListener {
// changed name to not conflict with AsyncStorage from RN repo
public static final String NAME = "RNCAsyncStorage";
// SQL variable number limit, defined by SQLITE_LIMIT_VARIABLE_NUMBER:
// https://raw.githubusercontent.com/android/platform_external_sqlite/master/dist/sqlite3.c
private static final int MAX_SQL_KEYS = 999;
private ReactDatabaseSupplier mReactDatabaseSupplier;
private boolean mShuttingDown = false;
private final SerialExecutor executor;
public AsyncStorageModule(ReactApplicationContext reactContext) {
this(
reactContext,
BuildConfig.AsyncStorage_useDedicatedExecutor
? Executors.newSingleThreadExecutor()
: AsyncTask.THREAD_POOL_EXECUTOR
);
}
@VisibleForTesting
AsyncStorageModule(ReactApplicationContext reactContext, Executor executor) {
super(reactContext);
// The migration MUST run before the AsyncStorage database is created for the first time.
AsyncStorageExpoMigration.migrate(reactContext);
this.executor = new SerialExecutor(executor);
reactContext.addLifecycleEventListener(this);
// Creating the database MUST happen after the migration.
mReactDatabaseSupplier = ReactDatabaseSupplier.getInstance(reactContext);
}
@Override
public String getName() {
return NAME;
}
@Override
public void initialize() {
super.initialize();
mShuttingDown = false;
}
@Override
public void onCatalystInstanceDestroy() {
mShuttingDown = true;
}
@Override
public void clearSensitiveData() {
// Clear local storage. If fails, crash, since the app is potentially in a bad state and could
// cause a privacy violation. We're still not recovering from this well, but at least the error
// will be reported to the server.
mReactDatabaseSupplier.clearAndCloseDatabase();
}
@Override
public void onHostResume() {}
@Override
public void onHostPause() {}
@Override
public void onHostDestroy() {
// ensure we close database when activity is destroyed
mReactDatabaseSupplier.closeDatabase();
}
/**
* Given an array of keys, this returns a map of (key, value) pairs for the keys found, and
* (key, null) for the keys that haven't been found.
*/
@ReactMethod
@Override
public void multiGet(final ReadableArray keys, final Callback callback) {
if (keys == null) {
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null), null);
return;
}
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null), null);
return;
}
String[] columns = {ReactDatabaseSupplier.KEY_COLUMN, ReactDatabaseSupplier.VALUE_COLUMN};
HashSet<String> keysRemaining = new HashSet<>();
WritableArray data = Arguments.createArray();
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
Cursor cursor = mReactDatabaseSupplier.get().query(
ReactDatabaseSupplier.TABLE_CATALYST,
columns,
AsyncLocalStorageUtil.buildKeySelection(keyCount),
AsyncLocalStorageUtil.buildKeySelectionArgs(keys, keyStart, keyCount),
null,
null,
null);
keysRemaining.clear();
try {
if (cursor.getCount() != keys.size()) {
// some keys have not been found - insert them with null into the final array
for (int keyIndex = keyStart; keyIndex < keyStart + keyCount; keyIndex++) {
keysRemaining.add(keys.getString(keyIndex));
}
}
if (cursor.moveToFirst()) {
do {
WritableArray row = Arguments.createArray();
row.pushString(cursor.getString(0));
row.pushString(cursor.getString(1));
data.pushArray(row);
keysRemaining.remove(cursor.getString(0));
} while (cursor.moveToNext());
}
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
return;
} finally {
cursor.close();
}
for (String key : keysRemaining) {
WritableArray row = Arguments.createArray();
row.pushString(key);
row.pushNull();
data.pushArray(row);
}
keysRemaining.clear();
}
callback.invoke(null, data);
}
}.executeOnExecutor(executor);
}
/**
* Inserts multiple (key, value) pairs. If one or more of the pairs cannot be inserted, this will
* return AsyncLocalStorageFailure, but all other pairs will have been inserted.
* The insertion will replace conflicting (key, value) pairs.
*/
@ReactMethod
@Override
public void multiSet(final ReadableArray keyValueArray, final Callback callback) {
if (keyValueArray.size() == 0) {
callback.invoke();
return;
}
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
String sql = "INSERT OR REPLACE INTO " + ReactDatabaseSupplier.TABLE_CATALYST + " VALUES (?, ?);";
SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql);
WritableMap error = null;
try {
mReactDatabaseSupplier.get().beginTransaction();
for (int idx=0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
if (keyValueArray.getArray(idx).getString(0) == null) {
error = AsyncStorageErrorUtil.getInvalidKeyError(null);
return;
}
if (keyValueArray.getArray(idx).getString(1) == null) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
statement.clearBindings();
statement.bindString(1, keyValueArray.getArray(idx).getString(0));
statement.bindString(2, keyValueArray.getArray(idx).getString(1));
statement.execute();
}
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally {
try {
mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
}
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke();
}
}
}.executeOnExecutor(executor);
}
/**
* Removes all rows of the keys given.
*/
@ReactMethod
@Override
public void multiRemove(final ReadableArray keys, final Callback callback) {
if (keys.size() == 0) {
callback.invoke();
return;
}
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
WritableMap error = null;
try {
mReactDatabaseSupplier.get().beginTransaction();
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
mReactDatabaseSupplier.get().delete(
ReactDatabaseSupplier.TABLE_CATALYST,
AsyncLocalStorageUtil.buildKeySelection(keyCount),
AsyncLocalStorageUtil.buildKeySelectionArgs(keys, keyStart, keyCount));
}
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally {
try {
mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
}
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke();
}
}
}.executeOnExecutor(executor);
}
/**
* Given an array of (key, value) pairs, this will merge the given values with the stored values
* of the given keys, if they exist.
*/
@ReactMethod
@Override
public void multiMerge(final ReadableArray keyValueArray, final Callback callback) {
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
WritableMap error = null;
try {
mReactDatabaseSupplier.get().beginTransaction();
for (int idx = 0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
if (keyValueArray.getArray(idx).getString(0) == null) {
error = AsyncStorageErrorUtil.getInvalidKeyError(null);
return;
}
if (keyValueArray.getArray(idx).getString(1) == null) {
error = AsyncStorageErrorUtil.getInvalidValueError(null);
return;
}
if (!AsyncLocalStorageUtil.mergeImpl(
mReactDatabaseSupplier.get(),
keyValueArray.getArray(idx).getString(0),
keyValueArray.getArray(idx).getString(1))) {
error = AsyncStorageErrorUtil.getDBError(null);
return;
}
}
mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally {
try {
mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
}
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke();
}
}
}.executeOnExecutor(executor);
}
/**
* Clears the database.
*/
@ReactMethod
@Override
public void clear(final Callback callback) {
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!mReactDatabaseSupplier.ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return;
}
try {
mReactDatabaseSupplier.clear();
callback.invoke();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
}
}
}.executeOnExecutor(executor);
}
/**
* Returns an array with all keys from the database.
*/
@ReactMethod
@Override
public void getAllKeys(final Callback callback) {
new GuardedAsyncTask<Void, Void>(getReactApplicationContext()) {
@Override
protected void doInBackgroundGuarded(Void... params) {
if (!ensureDatabase()) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null), null);
return;
}
WritableArray data = Arguments.createArray();
String[] columns = {ReactDatabaseSupplier.KEY_COLUMN};
Cursor cursor = mReactDatabaseSupplier.get()
.query(ReactDatabaseSupplier.TABLE_CATALYST, columns, null, null, null, null, null);
try {
if (cursor.moveToFirst()) {
do {
data.pushString(cursor.getString(0));
} while (cursor.moveToNext());
}
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
return;
} finally {
cursor.close();
}
callback.invoke(null, data);
}
}.executeOnExecutor(executor);
}
/**
* Verify the database is open for reads and writes.
*/
private boolean ensureDatabase() {
return !mShuttingDown && mReactDatabaseSupplier.ensureDatabase();
}
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
package com.reactnativecommunity.asyncstorage;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import com.facebook.common.logging.FLog;
import com.facebook.react.common.ReactConstants;
import javax.annotation.Nullable;
/**
* Database supplier of the database used by react native. This creates, opens and deletes the
* database as necessary.
*/
public class ReactDatabaseSupplier extends SQLiteOpenHelper {
// VisibleForTesting
public static final String DATABASE_NAME = "RKStorage";
private static final int DATABASE_VERSION = 1;
private static final int SLEEP_TIME_MS = 30;
static final String TABLE_CATALYST = "catalystLocalStorage";
static final String KEY_COLUMN = "key";
static final String VALUE_COLUMN = "value";
static final String VERSION_TABLE_CREATE =
"CREATE TABLE " + TABLE_CATALYST + " (" +
KEY_COLUMN + " TEXT PRIMARY KEY, " +
VALUE_COLUMN + " TEXT NOT NULL" +
")";
private static @Nullable ReactDatabaseSupplier sReactDatabaseSupplierInstance;
private Context mContext;
private @Nullable SQLiteDatabase mDb;
private long mMaximumDatabaseSize = BuildConfig.AsyncStorage_db_size * 1024L * 1024L;
private ReactDatabaseSupplier(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
mContext = context;
}
public static ReactDatabaseSupplier getInstance(Context context) {
if (sReactDatabaseSupplierInstance == null) {
sReactDatabaseSupplierInstance = new ReactDatabaseSupplier(context.getApplicationContext());
}
return sReactDatabaseSupplierInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(VERSION_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
if (oldVersion != newVersion) {
deleteDatabase();
onCreate(db);
}
}
/**
* Verify the database exists and is open.
*/
/* package */ synchronized boolean ensureDatabase() {
if (mDb != null && mDb.isOpen()) {
return true;
}
// Sometimes retrieving the database fails. We do 2 retries: first without database deletion
// and then with deletion.
SQLiteException lastSQLiteException = null;
for (int tries = 0; tries < 2; tries++) {
try {
if (tries > 0) {
deleteDatabase();
}
mDb = getWritableDatabase();
break;
} catch (SQLiteException e) {
lastSQLiteException = e;
}
// Wait before retrying.
try {
Thread.sleep(SLEEP_TIME_MS);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (mDb == null) {
throw lastSQLiteException;
}
// This is a sane limit to protect the user from the app storing too much data in the database.
// This also protects the database from filling up the disk cache and becoming malformed
// (endTransaction() calls will throw an exception, not rollback, and leave the db malformed).
mDb.setMaximumSize(mMaximumDatabaseSize);
return true;
}
/**
* Create and/or open the database.
*/
public synchronized SQLiteDatabase get() {
ensureDatabase();
return mDb;
}
public synchronized void clearAndCloseDatabase() throws RuntimeException {
try {
clear();
closeDatabase();
FLog.d(ReactConstants.TAG, "Cleaned " + DATABASE_NAME);
} catch (Exception e) {
// Clearing the database has failed, delete it instead.
if (deleteDatabase()) {
FLog.d(ReactConstants.TAG, "Deleted Local Database " + DATABASE_NAME);
return;
}
// Everything failed, throw
throw new RuntimeException("Clearing and deleting database " + DATABASE_NAME + " failed");
}
}
/* package */ synchronized void clear() {
get().delete(TABLE_CATALYST, null, null);
}
/**
* Sets the maximum size the database will grow to. The maximum size cannot
* be set below the current size.
*/
public synchronized void setMaximumSize(long size) {
mMaximumDatabaseSize = size;
if (mDb != null) {
mDb.setMaximumSize(mMaximumDatabaseSize);
}
}
private synchronized boolean deleteDatabase() {
closeDatabase();
return mContext.deleteDatabase(DATABASE_NAME);
}
public synchronized void closeDatabase() {
if (mDb != null && mDb.isOpen()) {
mDb.close();
mDb = null;
}
}
// For testing purposes only!
public static void deleteInstance() {
sReactDatabaseSupplierInstance = null;
}
}

View File

@@ -0,0 +1,40 @@
package com.reactnativecommunity.asyncstorage;
import java.util.ArrayDeque;
import java.util.concurrent.Executor;
/**
* Detox is using this implementation detail in its environment setup,
* so in order for Next storage to work, this class has been made public
*
* Adapted from https://android.googlesource.com/platform/frameworks/base.git/+/1488a3a19d4681a41fb45570c15e14d99db1cb66/core/java/android/os/AsyncTask.java#237
*/
public class SerialExecutor implements Executor {
private final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
private Runnable mActive;
private final Executor executor;
public SerialExecutor(Executor executor) {
this.executor = executor;
}
public synchronized void execute(final Runnable r) {
mTasks.offer(new Runnable() {
public void run() {
try {
r.run();
} finally {
scheduleNext();
}
}
});
if (mActive == null) {
scheduleNext();
}
}
synchronized void scheduleNext() {
if ((mActive = mTasks.poll()) != null) {
executor.execute(mActive);
}
}
}

View File

@@ -0,0 +1,86 @@
package com.reactnativecommunity.asyncstorage.next
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import org.json.JSONException
import org.json.JSONObject
fun ReadableArray.toEntryList(): List<Entry> {
val list = mutableListOf<Entry>()
for (keyValue in this.toArrayList()) {
if (keyValue !is ArrayList<*> || keyValue.size != 2) {
throw AsyncStorageError.invalidKeyValueFormat()
}
val key = keyValue[0]
val value = keyValue[1]
if (key !is String) {
when (key) {
null -> throw AsyncStorageError.keyIsNull()
else -> throw AsyncStorageError.keyNotString()
}
}
if (value !is String) {
throw AsyncStorageError.valueNotString(key)
}
list.add(Entry(key, value))
}
return list
}
fun ReadableArray.toKeyList(): List<String> {
val list = this.toArrayList()
for (item in list) {
if (item !is String) {
throw AsyncStorageError.keyNotString()
}
}
return list as List<String>
}
fun List<Entry>.toKeyValueArgument(): ReadableArray {
val args = Arguments.createArray()
for (entry in this) {
val keyValue = Arguments.createArray()
keyValue.pushString(entry.key)
keyValue.pushString(entry.value)
args.pushArray(keyValue)
}
return args
}
fun String?.isValidJson(): Boolean {
if (this == null) return false
return try {
JSONObject(this)
true
} catch (e: JSONException) {
false
}
}
fun JSONObject.mergeWith(newObject: JSONObject): JSONObject {
val keys = newObject.keys()
val mergedObject = JSONObject(this.toString())
while (keys.hasNext()) {
val key = keys.next()
val curValue = this.optJSONObject(key)
val newValue = newObject.optJSONObject(key)
if (curValue != null && newValue != null) {
val merged = curValue.mergeWith(newValue)
mergedObject.put(key, merged)
} else {
mergedObject.put(key, newObject.get(key))
}
}
return mergedObject
}

View File

@@ -0,0 +1,39 @@
package com.reactnativecommunity.asyncstorage.next
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import kotlinx.coroutines.CoroutineExceptionHandler
internal fun createExceptionHandler(cb: Callback): CoroutineExceptionHandler {
return CoroutineExceptionHandler { _, throwable ->
val error = Arguments.createMap()
if (throwable !is AsyncStorageError) {
error.putString(
"message", "Unexpected AsyncStorage error: ${throwable.localizedMessage}"
)
} else {
error.putString("message", throwable.errorMessage)
}
cb(error)
}
}
internal class AsyncStorageError private constructor(val errorMessage: String) :
Throwable(errorMessage) {
companion object {
fun keyIsNull() = AsyncStorageError("Key cannot be null.")
fun keyNotString() = AsyncStorageError("Provided key is not string. Only strings are supported as storage key.")
fun valueNotString(key: String?): AsyncStorageError {
val detail = if (key == null) "Provided value" else "Value for key \"$key\""
return AsyncStorageError("$detail is not a string. Only strings are supported as a value.")
}
fun invalidKeyValueFormat() =
AsyncStorageError("Invalid key-value format. Expected a list of [key, value] list.")
}
}

View File

@@ -0,0 +1,94 @@
package com.reactnativecommunity.asyncstorage.next
import android.content.Context
import androidx.annotation.VisibleForTesting
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.module.annotations.ReactModule
import com.reactnativecommunity.asyncstorage.NativeAsyncStorageModuleSpec
import com.reactnativecommunity.asyncstorage.SerialExecutor
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.asExecutor
import kotlinx.coroutines.launch
@ReactModule(name = StorageModule.NAME)
class StorageModule(reactContext: ReactApplicationContext) : NativeAsyncStorageModuleSpec(reactContext), CoroutineScope {
override fun getName() = NAME
// this executor is not used by the module, but it must exists here due to
// Detox relying on this implementation detail to run
@VisibleForTesting
private val executor = SerialExecutor(Dispatchers.Main.asExecutor())
override val coroutineContext =
Dispatchers.IO + CoroutineName("AsyncStorageScope") + SupervisorJob()
private val storage = StorageSupplier.getInstance(reactContext)
companion object {
const val NAME = "RNCAsyncStorage"
@JvmStatic
fun getStorageInstance(ctx: Context): AsyncStorageAccess {
return StorageSupplier.getInstance(ctx)
}
}
@ReactMethod
override fun multiGet(keys: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
val entries = storage.getValues(keys.toKeyList())
cb(null, entries.toKeyValueArgument())
}
}
@ReactMethod
override fun multiSet(keyValueArray: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
val entries = keyValueArray.toEntryList()
storage.setValues(entries)
cb(null)
}
}
@ReactMethod
override fun multiRemove(keys: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
storage.removeValues(keys.toKeyList())
cb(null)
}
}
@ReactMethod
override fun multiMerge(keyValueArray: ReadableArray, cb: Callback) {
launch(createExceptionHandler(cb)) {
val entries = keyValueArray.toEntryList()
storage.mergeValues(entries)
cb(null)
}
}
@ReactMethod
override fun getAllKeys(cb: Callback) {
launch(createExceptionHandler(cb)) {
val keys = storage.getKeys()
val result = Arguments.createArray()
keys.forEach { result.pushString(it) }
cb.invoke(null, result)
}
}
@ReactMethod
override fun clear(cb: Callback) {
launch(createExceptionHandler(cb)) {
storage.clear()
cb(null)
}
}
}

View File

@@ -0,0 +1,161 @@
package com.reactnativecommunity.asyncstorage.next
import android.content.Context
import android.util.Log
import androidx.room.ColumnInfo
import androidx.room.Dao
import androidx.room.Database
import androidx.room.Entity
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.PrimaryKey
import androidx.room.Query
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.Transaction
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import org.json.JSONObject
private const val DATABASE_VERSION = 2
private const val DATABASE_NAME = "AsyncStorage"
private const val TABLE_NAME = "Storage"
private const val COLUMN_KEY = "key"
private const val COLUMN_VALUE = "value"
@Entity(tableName = TABLE_NAME)
data class Entry(
@PrimaryKey @ColumnInfo(name = COLUMN_KEY) val key: String,
@ColumnInfo(name = COLUMN_VALUE) val value: String?
)
@Dao
internal interface StorageDao {
@Transaction
@Query("SELECT * FROM $TABLE_NAME WHERE `$COLUMN_KEY` IN (:keys)")
suspend fun getValues(keys: List<String>): List<Entry>
@Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setValues(entries: List<Entry>)
@Transaction
@Query("DELETE FROM $TABLE_NAME WHERE `$COLUMN_KEY` in (:keys)")
suspend fun removeValues(keys: List<String>)
@Transaction
suspend fun mergeValues(entries: List<Entry>) {
val currentDbEntries = getValues(entries.map { it.key })
val newEntries = mutableListOf<Entry>()
entries.forEach { newEntry ->
val oldEntry = currentDbEntries.find { it.key == newEntry.key }
if (oldEntry?.value == null) {
newEntries.add(newEntry)
} else if (!oldEntry.value.isValidJson() || !newEntry.value.isValidJson()) {
newEntries.add(newEntry)
} else {
val newValue =
JSONObject(oldEntry.value).mergeWith(JSONObject(newEntry.value)).toString()
newEntries.add(newEntry.copy(value = newValue))
}
}
setValues(newEntries)
}
@Transaction
@Query("SELECT `$COLUMN_KEY` FROM $TABLE_NAME")
suspend fun getKeys(): List<String>
@Transaction
@Query("DELETE FROM $TABLE_NAME")
suspend fun clear()
}
/**
* Previous version of AsyncStorage is violating the SQL standard (based on bug in SQLite),
* where PrimaryKey ('key' column) should never be null (https://www.sqlite.org/lang_createtable.html#the_primary_key).
* Because of that, we cannot reuse the old DB, because ROOM is guarded against that case (won't compile).
*
* In order to work around this, two steps are necessary:
* - Room DB pre-population from the old database file (https://developer.android.com/training/data-storage/room/prepopulate#from-asset)
* - Version migration, so that we can mark 'key' column as NOT-NULL
*
* This migration will happens only once, when developer enable this feature (when DB is still not created).
*/
@Suppress("ClassName")
private object MIGRATION_TO_NEXT : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
val oldTableName = "catalystLocalStorage" // from ReactDatabaseSupplier
database.execSQL("CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`${COLUMN_KEY}` TEXT NOT NULL, `${COLUMN_VALUE}` TEXT, PRIMARY KEY(`${COLUMN_KEY}`));")
// even if the old AsyncStorage has checks for not nullable keys
// make sure we don't copy any, to not fail migration
database.execSQL("DELETE FROM $oldTableName WHERE `${COLUMN_KEY}` IS NULL")
database.execSQL(
"""
INSERT INTO $TABLE_NAME (`${COLUMN_KEY}`, `${COLUMN_VALUE}`)
SELECT `${COLUMN_KEY}`, `${COLUMN_VALUE}`
FROM $oldTableName;
""".trimIndent()
)
Log.e("AsyncStorage_Next", "Migration to Next storage completed.")
}
}
@Database(entities = [Entry::class], version = DATABASE_VERSION, exportSchema = false)
internal abstract class StorageDb : RoomDatabase() {
abstract fun storage(): StorageDao
companion object {
private var instance: StorageDb? = null
fun getDatabase(context: Context): StorageDb {
var inst = instance
if (inst != null) {
return inst
}
synchronized(this) {
val oldDbFile = context.getDatabasePath("RKStorage")
val db = Room.databaseBuilder(
context, StorageDb::class.java, DATABASE_NAME
)
if (oldDbFile.exists()) {
// migrate data from old database, if it exists
db.createFromFile(oldDbFile).addMigrations(MIGRATION_TO_NEXT)
}
inst = db.build()
instance = inst
return instance!!
}
}
}
}
interface AsyncStorageAccess {
suspend fun getValues(keys: List<String>): List<Entry>
suspend fun setValues(entries: List<Entry>)
suspend fun removeValues(keys: List<String>)
suspend fun getKeys(): List<String>
suspend fun clear()
suspend fun mergeValues(entries: List<Entry>)
}
class StorageSupplier internal constructor(db: StorageDb) : AsyncStorageAccess {
companion object {
fun getInstance(ctx: Context): AsyncStorageAccess {
return StorageSupplier(StorageDb.getDatabase(ctx))
}
}
private val access = db.storage()
override suspend fun getValues(keys: List<String>) = access.getValues(keys)
override suspend fun setValues(entries: List<Entry>) = access.setValues(entries)
override suspend fun removeValues(keys: List<String>) = access.removeValues(keys)
override suspend fun mergeValues(entries: List<Entry>) = access.mergeValues(entries)
override suspend fun getKeys() = access.getKeys()
override suspend fun clear() = access.clear()
}

View File

@@ -0,0 +1,47 @@
/**
* This code was generated by [react-native-codegen](https://www.npmjs.com/package/react-native-codegen).
*
* Update this file after changing NativeAsyncStorageModule.ts to match the version generated by codegen.
*/
package com.reactnativecommunity.asyncstorage;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReactModuleWithSpec;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.turbomodule.core.interfaces.TurboModule;
public abstract class NativeAsyncStorageModuleSpec extends ReactContextBaseJavaModule implements ReactModuleWithSpec, TurboModule {
public NativeAsyncStorageModuleSpec(ReactApplicationContext reactContext) {
super(reactContext);
}
@ReactMethod
@DoNotStrip
public abstract void multiGet(ReadableArray keys, Callback callback);
@ReactMethod
@DoNotStrip
public abstract void multiSet(ReadableArray kvPairs, Callback callback);
@ReactMethod
@DoNotStrip
public abstract void multiRemove(ReadableArray keys, Callback callback);
@ReactMethod
@DoNotStrip
public abstract void multiMerge(ReadableArray kvPairs, Callback callback);
@ReactMethod
@DoNotStrip
public abstract void getAllKeys(Callback callback);
@ReactMethod
@DoNotStrip
public abstract void clear(Callback callback);
}

View File

@@ -0,0 +1,93 @@
package com.reactnativecommunity.asyncstorage.next
import com.facebook.react.bridge.JavaOnlyArray
import com.facebook.react.bridge.ReadableArray
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.BlockJUnit4ClassRunner
@RunWith(BlockJUnit4ClassRunner::class)
class ArgumentHelpersTest {
@Test
fun transformsArgumentsToEntryList() {
val args = JavaOnlyArray.of(
arrayListOf("key1", "value1"),
arrayListOf("key2", "value2"),
arrayListOf("key3", "value3")
)
assertThat(args.toEntryList()).isEqualTo(
listOf(
Entry("key1", "value1"),
Entry("key2", "value2"),
Entry("key3", "value3"),
)
)
}
@Test
fun transfersArgumentsToKeyList() {
val keyList = listOf("key1", "key2", "key3")
val args = keyList.toReadableArray()
assertThat(args.toKeyList()).isEqualTo(keyList)
}
@Test
fun throwsIfArgumentsNotValidFormat() {
val invalid = arrayListOf("invalid")
val args = JavaOnlyArray.of(invalid)
val error = assertThrows(AsyncStorageError::class.java) {
args.toEntryList()
}
assertThat(error is AsyncStorageError).isTrue()
assertThat(error).hasMessageThat()
.isEqualTo("Invalid key-value format. Expected a list of [key, value] list.")
}
@Test
fun throwsIfArgumentKeyIsNullOrNotString() {
val argsInvalidNull = JavaOnlyArray.of(arrayListOf(null, "invalid"))
val errorArgsInvalidNull = assertThrows(AsyncStorageError::class.java) {
argsInvalidNull.toEntryList()
}
assertThat(errorArgsInvalidNull is AsyncStorageError).isTrue()
assertThat(errorArgsInvalidNull).hasMessageThat().isEqualTo("Key cannot be null.")
val notStringArgs = JavaOnlyArray.of(arrayListOf(123, "invalid"))
val errorNotString = assertThrows(AsyncStorageError::class.java) {
notStringArgs.toEntryList()
}
assertThat(errorNotString is AsyncStorageError).isTrue()
assertThat(errorNotString).hasMessageThat()
.isEqualTo("Provided key is not string. Only strings are supported as storage key.")
}
@Test
fun throwsIfArgumentValueNotString() {
val invalidArgs = JavaOnlyArray.of(arrayListOf("my_key", 666))
val error = assertThrows(AsyncStorageError::class.java) {
invalidArgs.toEntryList()
}
assertThat(error is AsyncStorageError).isTrue()
assertThat(error).hasMessageThat()
.isEqualTo("Value for key \"my_key\" is not a string. Only strings are supported as a value.")
}
}
fun List<Any?>.toReadableArray(): ReadableArray {
val arr = JavaOnlyArray()
forEach {
when (it) {
null -> arr.pushNull()
is Boolean -> arr.pushBoolean(it)
is Double -> arr.pushDouble(it)
is Int -> arr.pushInt(it)
is String -> arr.pushString(it)
else -> throw NotImplementedError()
}
}
return arr
}

View File

@@ -0,0 +1,141 @@
package com.reactnativecommunity.asyncstorage.next
import androidx.room.Room
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import org.json.JSONObject
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import kotlin.random.Random
@ExperimentalCoroutinesApi
@RunWith(AndroidJUnit4::class)
class AsyncStorageAccessTest {
private lateinit var asyncStorage: AsyncStorageAccess
private lateinit var database: StorageDb
@Before
fun setup() {
database = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context, StorageDb::class.java
).allowMainThreadQueries().build()
asyncStorage = StorageSupplier(database)
}
@After
fun tearDown() {
database.close()
}
@Test
fun performsBasicGetSetRemoveOperations() = runBlocking {
val entriesCount = 10
val entries = createRandomEntries(entriesCount)
val keys = entries.map { it.key }
assertThat(asyncStorage.getValues(keys)).hasSize(0)
asyncStorage.setValues(entries)
assertThat(asyncStorage.getValues(keys)).hasSize(entriesCount)
val indicesToRemove = (1..4).map { Random.nextInt(0, entriesCount) }.distinct()
val toRemove = entries.filterIndexed { index, _ -> indicesToRemove.contains(index) }
asyncStorage.removeValues(toRemove.map { it.key })
val currentEntries = asyncStorage.getValues(keys)
assertThat(currentEntries).hasSize(entriesCount - toRemove.size)
}
@Test
fun readsAllKeysAndClearsDb() = runBlocking {
val entries = createRandomEntries(8)
val keys = entries.map { it.key }
asyncStorage.setValues(entries)
val dbKeys = asyncStorage.getKeys()
assertThat(dbKeys).isEqualTo(keys)
asyncStorage.clear()
assertThat(asyncStorage.getValues(keys)).hasSize(0)
}
@Test
fun mergesDeeplyTwoValues() = runBlocking {
val initialEntry = Entry("key", VALUE_INITIAL)
val overrideEntry = Entry("key", VALUE_OVERRIDES)
asyncStorage.setValues(listOf(initialEntry))
asyncStorage.mergeValues(listOf(overrideEntry))
val current = asyncStorage.getValues(listOf("key"))[0]
assertThat(current.value).isEqualTo(VALUE_MERGED)
}
@Test
fun updatesExistingValues() = runBlocking {
val key = "test_key"
val value = "test_value"
val entries = listOf(Entry(key, value))
assertThat(asyncStorage.getValues(listOf(key))).hasSize(0)
asyncStorage.setValues(entries)
assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(entries)
val modifiedEntries = listOf(Entry(key, "updatedValue"))
asyncStorage.setValues(modifiedEntries)
assertThat(asyncStorage.getValues(listOf(key))).isEqualTo(modifiedEntries)
}
// Test Helpers
private fun createRandomEntries(count: Int = Random.nextInt(10)): List<Entry> {
val entries = mutableListOf<Entry>()
for (i in 0 until count) {
entries.add(Entry("key$i", "value$i"))
}
return entries
}
private val VALUE_INITIAL = JSONObject(
"""
{
"key":"value",
"key2":"override",
"key3":{
"key4":"value4",
"key6":{
"key7":"value7",
"key8":"override"
}
}
}
""".trimMargin()
).toString()
private val VALUE_OVERRIDES = JSONObject(
"""
{
"key2":"value2",
"key3":{
"key5":"value5",
"key6":{
"key8":"value8"
}
}
}
"""
).toString()
private val VALUE_MERGED = JSONObject(
"""
{
"key":"value",
"key2":"value2",
"key3":{
"key4":"value4",
"key6":{
"key7":"value7",
"key8":"value8"
},
"key5":"value5"
}
}
""".trimMargin()
).toString()
}

View File

@@ -0,0 +1,38 @@
// pretty print test results
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
import org.gradle.api.tasks.testing.logging.TestLogEvent
tasks.withType(Test) {
testLogging {
events TestLogEvent.FAILED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.STANDARD_OUT
exceptionFormat TestExceptionFormat.FULL
showExceptions true
showCauses true
showStackTraces true
debug {
events TestLogEvent.STARTED,
TestLogEvent.FAILED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.STANDARD_ERROR,
TestLogEvent.STANDARD_OUT
exceptionFormat TestExceptionFormat.FULL
}
info.events = debug.events
info.exceptionFormat = debug.exceptionFormat
afterSuite { desc, result ->
if (!desc.parent) { // will match the outermost suite
def output = "Results: ${result.resultType} (${result.testCount} tests, ${result.successfulTestCount} passed, ${result.failedTestCount} failed, ${result.skippedTestCount} skipped)"
def startItem = '| ', endItem = ' |'
def repeatLength = startItem.length() + output.length() + endItem.length()
println('\n' + ('-' * repeatLength) + '\n' + startItem + output + endItem + '\n' + ('-' * repeatLength))
}
}
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
#import <React/RCTBridgeModule.h>
#import <React/RCTInvalidating.h>
#ifdef RCT_NEW_ARCH_ENABLED
#import <rnasyncstorage/rnasyncstorage.h>
#endif
#import "RNCAsyncStorageDelegate.h"
/**
* A simple, asynchronous, persistent, key-value storage system designed as a
* backend to the AsyncStorage JS module, which is modeled after LocalStorage.
*
* Current implementation stores small values in serialized dictionary and
* larger values in separate files. Since we use a serial file queue
* `RKFileQueue`, reading/writing from multiple threads should be perceived as
* being atomic, unless someone bypasses the `RNCAsyncStorage` API.
*
* Keys and values must always be strings or an error is returned.
*/
@interface RNCAsyncStorage : NSObject <
#ifdef RCT_NEW_ARCH_ENABLED
NativeAsyncStorageModuleSpec
#else
RCTBridgeModule
#endif
,
RCTInvalidating>
@property (nonatomic, weak, nullable) id<RNCAsyncStorageDelegate> delegate;
@property (nonatomic, assign) BOOL clearOnInvalidate;
@property (nonatomic, readonly, getter=isValid) BOOL valid;
// Clear the RNCAsyncStorage data from native code
- (void)clearAllData;
// For clearing data when the bridge may not exist, e.g. when logging out.
+ (void)clearAllData;
// Grab data from the cache. ResponseBlock result array will have an error at position 0, and an
// array of arrays at position 1.
- (void)multiGet:(NSArray<NSString *> *)keys callback:(RCTResponseSenderBlock)callback;
// Add multiple key value pairs to the cache.
- (void)multiSet:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback;
// Interface for natively fetching all the keys from the storage data.
- (void)getAllKeys:(RCTResponseSenderBlock)callback;
@end

View File

@@ -0,0 +1,909 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import "RNCAsyncStorage.h"
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonDigest.h>
#import <React/RCTConvert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
static NSString *const RCTStorageDirectory = @"RCTAsyncLocalStorage_V1";
static NSString *const RCTOldStorageDirectory = @"RNCAsyncLocalStorage_V1";
static NSString *const RCTExpoStorageDirectory = @"RCTAsyncLocalStorage";
static NSString *const RCTManifestFileName = @"manifest.json";
static const NSUInteger RCTInlineValueThreshold = 1024;
#pragma mark - Static helper functions
static NSDictionary *RCTErrorForKey(NSString *key)
{
if (![key isKindOfClass:[NSString class]]) {
return RCTMakeAndLogError(@"Invalid key - must be a string. Key: ", key, @{@"key": key});
} else if (key.length < 1) {
return RCTMakeAndLogError(
@"Invalid key - must be at least one character. Key: ", key, @{@"key": key});
} else {
return nil;
}
}
static BOOL RCTAsyncStorageSetExcludedFromBackup(NSString *path, NSNumber *isExcluded)
{
NSFileManager *fileManager = [[NSFileManager alloc] init];
BOOL isDir;
BOOL exists = [fileManager fileExistsAtPath:path isDirectory:&isDir];
BOOL success = false;
if (isDir && exists) {
NSURL *pathUrl = [NSURL fileURLWithPath:path];
NSError *error = nil;
success = [pathUrl setResourceValue:isExcluded
forKey:NSURLIsExcludedFromBackupKey
error:&error];
if (!success) {
NSLog(@"Could not exclude AsyncStorage dir from backup %@", error);
}
}
return success;
}
static void RCTAppendError(NSDictionary *error, NSMutableArray<NSDictionary *> **errors)
{
if (error && errors) {
if (!*errors) {
*errors = [NSMutableArray new];
}
[*errors addObject:error];
}
}
static NSArray<NSDictionary *> *RCTMakeErrors(NSArray<id<NSObject>> *results)
{
NSMutableArray<NSDictionary *> *errors;
for (id object in results) {
if ([object isKindOfClass:[NSError class]]) {
NSError *error = (NSError *)object;
NSDictionary *keyError = RCTMakeError(error.localizedDescription, error, nil);
RCTAppendError(keyError, &errors);
}
}
return errors;
}
static NSString *RCTReadFile(NSString *filePath, NSString *key, NSDictionary **errorOut)
{
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
NSError *error;
NSStringEncoding encoding;
NSString *entryString = [NSString stringWithContentsOfFile:filePath
usedEncoding:&encoding
error:&error];
NSDictionary *extraData = @{@"key": RCTNullIfNil(key)};
if (error) {
if (errorOut) {
*errorOut = RCTMakeError(@"Failed to read storage file.", error, extraData);
}
return nil;
}
if (encoding != NSUTF8StringEncoding) {
if (errorOut) {
*errorOut =
RCTMakeError(@"Incorrect encoding of storage file: ", @(encoding), extraData);
}
return nil;
}
return entryString;
}
return nil;
}
// DO NOT USE
// This is used internally to migrate data from the old file location to the new one.
// Please use `RCTCreateStorageDirectoryPath` instead
static NSString *RCTCreateStorageDirectoryPath_deprecated(NSString *storageDir)
{
NSString *storageDirectoryPath;
#if TARGET_OS_TV
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
#else
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES).firstObject;
#endif
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir];
return storageDirectoryPath;
}
static NSString *RCTCreateStorageDirectoryPath(NSString *storageDir)
{
NSString *storageDirectoryPath = @"";
#if TARGET_OS_TV
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
#else
storageDirectoryPath =
NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES)
.firstObject;
// We should use the "Application Support/[bundleID]" folder for persistent data storage that's
// hidden from users
storageDirectoryPath = [storageDirectoryPath
stringByAppendingPathComponent:[[NSBundle mainBundle] bundleIdentifier]];
#endif
// Per Apple's docs, all app content in Application Support must be within a subdirectory of the
// app's bundle identifier
storageDirectoryPath = [storageDirectoryPath stringByAppendingPathComponent:storageDir];
return storageDirectoryPath;
}
static NSString *RCTGetStorageDirectory()
{
static NSString *storageDirectory = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
#if TARGET_OS_TV
RCTLogWarn(
@"Persistent storage is not supported on tvOS, your data may be removed at any point.");
#endif
storageDirectory = RCTCreateStorageDirectoryPath(RCTStorageDirectory);
});
return storageDirectory;
}
static NSString *RCTCreateManifestFilePath(NSString *storageDirectory)
{
return [storageDirectory stringByAppendingPathComponent:RCTManifestFileName];
}
static NSString *RCTGetManifestFilePath()
{
static NSString *manifestFilePath = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manifestFilePath = RCTCreateManifestFilePath(RCTStorageDirectory);
});
return manifestFilePath;
}
// Only merges objects - all other types are just clobbered (including arrays)
static BOOL RCTMergeRecursive(NSMutableDictionary *destination, NSDictionary *source)
{
BOOL modified = NO;
for (NSString *key in source) {
id sourceValue = source[key];
id destinationValue = destination[key];
if ([sourceValue isKindOfClass:[NSDictionary class]]) {
if ([destinationValue isKindOfClass:[NSDictionary class]]) {
if ([destinationValue classForCoder] != [NSMutableDictionary class]) {
destinationValue = [destinationValue mutableCopy];
}
if (RCTMergeRecursive(destinationValue, sourceValue)) {
destination[key] = destinationValue;
modified = YES;
}
} else {
destination[key] = [sourceValue copy];
modified = YES;
}
} else if (![source isEqual:destinationValue]) {
destination[key] = [sourceValue copy];
modified = YES;
}
}
return modified;
}
static dispatch_queue_t RCTGetMethodQueue()
{
// We want all instances to share the same queue since they will be reading/writing the same
// files.
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue =
dispatch_queue_create("com.facebook.react.AsyncLocalStorageQueue", DISPATCH_QUEUE_SERIAL);
});
return queue;
}
static NSCache *RCTGetCache()
{
// We want all instances to share the same cache since they will be reading/writing the same
// files.
static NSCache *cache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cache = [NSCache new];
cache.totalCostLimit = 2 * 1024 * 1024; // 2MB
#if !TARGET_OS_OSX
// Clear cache in the event of a memory warning
[[NSNotificationCenter defaultCenter]
addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
object:nil
queue:nil
usingBlock:^(__unused NSNotification *note) {
[cache removeAllObjects];
}];
#endif // !TARGET_OS_OSX
});
return cache;
}
static BOOL RCTHasCreatedStorageDirectory = NO;
static NSDictionary *RCTDeleteStorageDirectory()
{
NSError *error;
[[NSFileManager defaultManager] removeItemAtPath:RCTGetStorageDirectory() error:&error];
RCTHasCreatedStorageDirectory = NO;
if (error && error.code != NSFileNoSuchFileError) {
return RCTMakeError(@"Failed to delete storage directory.", error, nil);
}
return nil;
}
static NSDate *RCTManifestModificationDate(NSString *manifestFilePath)
{
NSDictionary *attributes =
[[NSFileManager defaultManager] attributesOfItemAtPath:manifestFilePath error:nil];
return [attributes fileModificationDate];
}
/**
* Creates an NSException used during Storage Directory Migration.
*/
static void RCTStorageDirectoryMigrationLogError(NSString *reason, NSError *error)
{
RCTLogWarn(@"%@: %@", reason, error ? error.description : @"");
}
static void RCTStorageDirectoryCleanupOld(NSString *oldDirectoryPath)
{
NSError *error;
if (![[NSFileManager defaultManager] removeItemAtPath:oldDirectoryPath error:&error]) {
RCTStorageDirectoryMigrationLogError(
@"Failed to remove old storage directory during migration", error);
}
}
static void _createStorageDirectory(NSString *storageDirectory, NSError **error)
{
[[NSFileManager defaultManager] createDirectoryAtPath:storageDirectory
withIntermediateDirectories:YES
attributes:nil
error:error];
}
static void RCTStorageDirectoryMigrate(NSString *oldDirectoryPath,
NSString *newDirectoryPath,
BOOL shouldCleanupOldDirectory)
{
NSError *error;
// Migrate data by copying old storage directory to new storage directory location
if (![[NSFileManager defaultManager] copyItemAtPath:oldDirectoryPath
toPath:newDirectoryPath
error:&error]) {
// the new storage directory "Application Support/[bundleID]/RCTAsyncLocalStorage_V1" seems
// unable to migrate because folder "Application Support/[bundleID]" doesn't exist.. create
// this folder and attempt folder copying again
if (error != nil && error.code == 4 &&
[newDirectoryPath isEqualToString:RCTGetStorageDirectory()]) {
NSError *error = nil;
_createStorageDirectory(RCTCreateStorageDirectoryPath(@""), &error);
if (error == nil) {
RCTStorageDirectoryMigrate(
oldDirectoryPath, newDirectoryPath, shouldCleanupOldDirectory);
} else {
RCTStorageDirectoryMigrationLogError(
@"Failed to create storage directory during migration.", error);
}
} else {
RCTStorageDirectoryMigrationLogError(
@"Failed to copy old storage directory to new storage directory location during "
@"migration",
error);
}
} else if (shouldCleanupOldDirectory) {
// If copying succeeds, remove old storage directory
RCTStorageDirectoryCleanupOld(oldDirectoryPath);
}
}
/**
* Determine which of RCTOldStorageDirectory or RCTExpoStorageDirectory needs to migrated.
* If both exist, we remove the least recently modified and return the most recently modified.
* Otherwise, this will return the path to whichever directory exists.
* If no directory exists, then return nil.
*/
static NSString *RCTGetStoragePathForMigration()
{
BOOL isDir;
NSString *oldStoragePath = RCTCreateStorageDirectoryPath_deprecated(RCTOldStorageDirectory);
NSString *expoStoragePath = RCTCreateStorageDirectoryPath_deprecated(RCTExpoStorageDirectory);
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL oldStorageDirectoryExists =
[fileManager fileExistsAtPath:oldStoragePath isDirectory:&isDir] && isDir;
BOOL expoStorageDirectoryExists =
[fileManager fileExistsAtPath:expoStoragePath isDirectory:&isDir] && isDir;
// Check if both the old storage directory and Expo storage directory exist
if (oldStorageDirectoryExists && expoStorageDirectoryExists) {
// If the old storage has been modified more recently than Expo storage, then clear Expo
// storage. Otherwise, clear the old storage.
if ([RCTManifestModificationDate(RCTCreateManifestFilePath(oldStoragePath))
compare:RCTManifestModificationDate(RCTCreateManifestFilePath(expoStoragePath))] ==
NSOrderedDescending) {
RCTStorageDirectoryCleanupOld(expoStoragePath);
return oldStoragePath;
} else {
RCTStorageDirectoryCleanupOld(oldStoragePath);
return expoStoragePath;
}
} else if (oldStorageDirectoryExists) {
return oldStoragePath;
} else if (expoStorageDirectoryExists) {
return expoStoragePath;
} else {
return nil;
}
}
/**
* This check is added to make sure that anyone coming from pre-1.2.2 does not lose cached data.
* Check that data is migrated from the old location to the new location
* fromStorageDirectory: the directory where the older data lives
* toStorageDirectory: the directory where the new data should live
* shouldCleanupOldDirectoryAndOverwriteNewDirectory: YES if we should delete the old directory's
* contents and overwrite the new directory's contents during the migration to the new directory
*/
static void
RCTStorageDirectoryMigrationCheck(NSString *fromStorageDirectory,
NSString *toStorageDirectory,
BOOL shouldCleanupOldDirectoryAndOverwriteNewDirectory)
{
NSError *error;
BOOL isDir;
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the old directory exists, it means we may need to migrate old data to the new directory
if ([fileManager fileExistsAtPath:fromStorageDirectory isDirectory:&isDir] && isDir) {
// Check if the new storage directory location already exists
if ([fileManager fileExistsAtPath:toStorageDirectory]) {
// If new storage location exists, check if the new storage has been modified sooner in
// which case we may want to cleanup the old location
if ([RCTManifestModificationDate(RCTCreateManifestFilePath(toStorageDirectory))
compare:RCTManifestModificationDate(
RCTCreateManifestFilePath(fromStorageDirectory))] == 1) {
// If new location has been modified more recently, simply clean out old data
if (shouldCleanupOldDirectoryAndOverwriteNewDirectory) {
RCTStorageDirectoryCleanupOld(fromStorageDirectory);
}
} else if (shouldCleanupOldDirectoryAndOverwriteNewDirectory) {
// If old location has been modified more recently, remove new storage and migrate
if (![fileManager removeItemAtPath:toStorageDirectory error:&error]) {
RCTStorageDirectoryMigrationLogError(
@"Failed to remove new storage directory during migration", error);
} else {
RCTStorageDirectoryMigrate(fromStorageDirectory,
toStorageDirectory,
shouldCleanupOldDirectoryAndOverwriteNewDirectory);
}
}
} else {
// If new storage location doesn't exist, migrate data
RCTStorageDirectoryMigrate(fromStorageDirectory,
toStorageDirectory,
shouldCleanupOldDirectoryAndOverwriteNewDirectory);
}
}
}
#pragma mark - RNCAsyncStorage
@implementation RNCAsyncStorage {
BOOL _haveSetup;
// The manifest is a dictionary of all keys with small values inlined. Null values indicate
// values that are stored in separate files (as opposed to nil values which don't exist). The
// manifest is read off disk at startup, and written to disk after all mutations.
NSMutableDictionary<NSString *, NSString *> *_manifest;
}
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
// Get the path to any old storage directory that needs to be migrated. If multiple exist,
// the oldest are removed and the most recently modified is returned.
NSString *oldStoragePath = RCTGetStoragePathForMigration();
if (oldStoragePath != nil) {
// Migrate our deprecated path "Documents/.../RNCAsyncLocalStorage_V1" or
// "Documents/.../RCTAsyncLocalStorage" to "Documents/.../RCTAsyncLocalStorage_V1"
RCTStorageDirectoryMigrationCheck(
oldStoragePath, RCTCreateStorageDirectoryPath_deprecated(RCTStorageDirectory), YES);
}
// Migrate what's in "Documents/.../RCTAsyncLocalStorage_V1" to
// "Application Support/[bundleID]/RCTAsyncLocalStorage_V1"
RCTStorageDirectoryMigrationCheck(RCTCreateStorageDirectoryPath_deprecated(RCTStorageDirectory),
RCTCreateStorageDirectoryPath(RCTStorageDirectory),
NO);
return self;
}
RCT_EXPORT_MODULE()
- (dispatch_queue_t)methodQueue
{
return RCTGetMethodQueue();
}
- (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
[self->_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
}
+ (void)clearAllData
{
dispatch_async(RCTGetMethodQueue(), ^{
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
});
}
- (void)invalidate
{
if (_clearOnInvalidate) {
[RCTGetCache() removeAllObjects];
RCTDeleteStorageDirectory();
}
_clearOnInvalidate = NO;
[_manifest removeAllObjects];
_haveSetup = NO;
}
- (BOOL)isValid
{
return _haveSetup;
}
- (void)dealloc
{
[self invalidate];
}
- (NSString *)_filePathForKey:(NSString *)key
{
NSString *safeFileName = RCTMD5Hash(key);
return [RCTGetStorageDirectory() stringByAppendingPathComponent:safeFileName];
}
- (NSDictionary *)_ensureSetup
{
RCTAssertThread(RCTGetMethodQueue(), @"Must be executed on storage thread");
NSError *error = nil;
if (!RCTHasCreatedStorageDirectory) {
_createStorageDirectory(RCTGetStorageDirectory(), &error);
if (error) {
return RCTMakeError(@"Failed to create storage directory.", error, nil);
}
RCTHasCreatedStorageDirectory = YES;
}
if (!_haveSetup) {
// iCloud backup exclusion
NSNumber *isExcludedFromBackup =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTAsyncStorageExcludeFromBackup"];
if (isExcludedFromBackup == nil) {
// by default, we want to exclude AsyncStorage data from backup
isExcludedFromBackup = @YES;
}
RCTAsyncStorageSetExcludedFromBackup(RCTCreateStorageDirectoryPath(RCTStorageDirectory),
isExcludedFromBackup);
NSDictionary *errorOut = nil;
NSString *serialized = RCTReadFile(RCTCreateStorageDirectoryPath(RCTGetManifestFilePath()),
RCTManifestFileName,
&errorOut);
if (!serialized) {
if (errorOut) {
// We cannot simply create a new manifest in case the file does exist but we have no
// access to it. This can happen when data protection is enabled for the app and we
// are trying to read the manifest while the device is locked. (The app can be
// started by the system even if the device is locked due to e.g. a geofence event.)
RCTLogError(
@"Could not open the existing manifest, perhaps data protection is "
@"enabled?\n\n%@",
errorOut);
return errorOut;
} else {
// We can get nil without errors only when the file does not exist.
RCTLogTrace(@"Manifest does not exist - creating a new one.\n\n%@", errorOut);
_manifest = [NSMutableDictionary new];
}
} else {
_manifest = RCTJSONParseMutable(serialized, &error);
if (!_manifest) {
RCTLogError(@"Failed to parse manifest - creating a new one.\n\n%@", error);
_manifest = [NSMutableDictionary new];
}
}
_haveSetup = YES;
}
return nil;
}
- (NSDictionary *)_writeManifest:(NSMutableArray<NSDictionary *> **)errors
{
NSError *error;
NSString *serialized = RCTJSONStringify(_manifest, &error);
[serialized writeToFile:RCTCreateStorageDirectoryPath(RCTGetManifestFilePath())
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
NSDictionary *errorOut;
if (error) {
errorOut = RCTMakeError(@"Failed to write manifest file.", error, nil);
RCTAppendError(errorOut, errors);
}
return errorOut;
}
- (NSDictionary *)_appendItemForKey:(NSString *)key
toArray:(NSMutableArray<NSArray<NSString *> *> *)result
{
NSDictionary *errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
NSString *value = [self _getValueForKey:key errorOut:&errorOut];
[result addObject:@[key, RCTNullIfNil(value)]]; // Insert null if missing or failure.
return errorOut;
}
- (NSString *)_getValueForKey:(NSString *)key errorOut:(NSDictionary **)errorOut
{
NSString *value =
_manifest[key]; // nil means missing, null means there may be a data file, else: NSString
if (value == (id)kCFNull) {
value = [RCTGetCache() objectForKey:key];
if (!value) {
NSString *filePath = [self _filePathForKey:key];
value = RCTReadFile(filePath, key, errorOut);
if (value) {
[RCTGetCache() setObject:value forKey:key cost:value.length];
} else {
// file does not exist after all, so remove from manifest (no need to save
// manifest immediately though, as cost of checking again next time is negligible)
[_manifest removeObjectForKey:key];
}
}
}
return value;
}
- (NSDictionary *)_writeEntry:(NSArray<NSString *> *)entry changedManifest:(BOOL *)changedManifest
{
if (entry.count != 2) {
return RCTMakeAndLogError(
@"Entries must be arrays of the form [key: string, value: string], got: ", entry, nil);
}
NSString *key = entry[0];
NSDictionary *errorOut = RCTErrorForKey(key);
if (errorOut) {
return errorOut;
}
NSString *value = entry[1];
NSString *filePath = [self _filePathForKey:key];
NSError *error;
if (value.length <= RCTInlineValueThreshold) {
if (_manifest[key] == (id)kCFNull) {
// If the value already existed but wasn't inlined, remove the old file.
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
}
*changedManifest = YES;
_manifest[key] = value;
return nil;
}
[value writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:&error];
[RCTGetCache() setObject:value forKey:key cost:value.length];
if (error) {
errorOut = RCTMakeError(@"Failed to write value.", error, @{@"key": key});
} else if (_manifest[key] != (id)kCFNull) {
*changedManifest = YES;
_manifest[key] = (id)kCFNull;
}
return errorOut;
}
- (void)_multiGet:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback
getter:(NSString * (^)(NSUInteger i, NSString *key, NSDictionary **errorOut))getValue
{
NSMutableArray<NSDictionary *> *errors;
NSMutableArray<NSArray<NSString *> *> *result = [NSMutableArray arrayWithCapacity:keys.count];
for (NSUInteger i = 0; i < keys.count; ++i) {
NSString *key = keys[i];
id keyError;
id value = getValue(i, key, &keyError);
[result addObject:@[key, RCTNullIfNil(value)]];
RCTAppendError(keyError, &errors);
}
callback(@[RCTNullIfNil(errors), result]);
}
- (BOOL)_passthroughDelegate
{
return
[self.delegate respondsToSelector:@selector(isPassthrough)] && self.delegate.isPassthrough;
}
#pragma mark - Exported JS Functions
// clang-format off
RCT_EXPORT_METHOD(multiGet:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate
valuesForKeys:keys
completion:^(NSArray<id<NSObject>> *valuesOrErrors) {
[self _multiGet:keys
callback:callback
getter:^NSString *(NSUInteger i, NSString *key, NSDictionary **errorOut) {
id valueOrError = valuesOrErrors[i];
if ([valueOrError isKindOfClass:[NSError class]]) {
NSError *error = (NSError *)valueOrError;
NSDictionary *extraData = @{@"key": RCTNullIfNil(key)};
*errorOut =
RCTMakeError(error.localizedDescription, error, extraData);
return nil;
} else {
return [valueOrError isKindOfClass:[NSString class]]
? (NSString *)valueOrError
: nil;
}
}];
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut], (id)kCFNull]);
return;
}
[self _multiGet:keys
callback:callback
getter:^(NSUInteger i, NSString *key, NSDictionary **errorOut) {
return [self _getValueForKey:key errorOut:errorOut];
}];
}
// clang-format off
RCT_EXPORT_METHOD(multiSet:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
NSMutableArray<NSString *> *keys = [NSMutableArray arrayWithCapacity:kvPairs.count];
NSMutableArray<NSString *> *values = [NSMutableArray arrayWithCapacity:kvPairs.count];
for (NSArray<NSString *> *entry in kvPairs) {
[keys addObject:entry[0]];
[values addObject:entry[1]];
}
[self.delegate setValues:values
forKeys:keys
completion:^(NSArray<id<NSObject>> *results) {
NSArray<NSDictionary *> *errors = RCTMakeErrors(results);
callback(@[RCTNullIfNil(errors)]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
BOOL changedManifest = NO;
NSMutableArray<NSDictionary *> *errors;
for (NSArray<NSString *> *entry in kvPairs) {
NSDictionary *keyError = [self _writeEntry:entry changedManifest:&changedManifest];
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}
// clang-format off
RCT_EXPORT_METHOD(multiMerge:(NSArray<NSArray<NSString *> *> *)kvPairs
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
NSMutableArray<NSString *> *keys = [NSMutableArray arrayWithCapacity:kvPairs.count];
NSMutableArray<NSString *> *values = [NSMutableArray arrayWithCapacity:kvPairs.count];
for (NSArray<NSString *> *entry in kvPairs) {
[keys addObject:entry[0]];
[values addObject:entry[1]];
}
[self.delegate mergeValues:values
forKeys:keys
completion:^(NSArray<id<NSObject>> *results) {
NSArray<NSDictionary *> *errors = RCTMakeErrors(results);
callback(@[RCTNullIfNil(errors)]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
BOOL changedManifest = NO;
NSMutableArray<NSDictionary *> *errors;
for (__strong NSArray<NSString *> *entry in kvPairs) {
NSDictionary *keyError;
NSString *value = [self _getValueForKey:entry[0] errorOut:&keyError];
if (!keyError) {
if (value) {
NSError *jsonError;
NSMutableDictionary *mergedVal = RCTJSONParseMutable(value, &jsonError);
NSDictionary *mergingValue = RCTJSONParse(entry[1], &jsonError);
if (!mergingValue.count || RCTMergeRecursive(mergedVal, mergingValue)) {
entry = @[entry[0], RCTNullIfNil(RCTJSONStringify(mergedVal, NULL))];
}
if (jsonError) {
keyError = RCTJSErrorFromNSError(jsonError);
}
}
if (!keyError) {
keyError = [self _writeEntry:entry changedManifest:&changedManifest];
}
}
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}
// clang-format off
RCT_EXPORT_METHOD(multiRemove:(NSArray<NSString *> *)keys
callback:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate removeValuesForKeys:keys
completion:^(NSArray<id<NSObject>> *results) {
NSArray<NSDictionary *> *errors = RCTMakeErrors(results);
callback(@[RCTNullIfNil(errors)]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[@[errorOut]]);
return;
}
NSMutableArray<NSDictionary *> *errors;
BOOL changedManifest = NO;
for (NSString *key in keys) {
NSDictionary *keyError = RCTErrorForKey(key);
if (!keyError) {
if (_manifest[key] == (id)kCFNull) {
NSString *filePath = [self _filePathForKey:key];
[[NSFileManager defaultManager] removeItemAtPath:filePath error:nil];
[RCTGetCache() removeObjectForKey:key];
}
if (_manifest[key]) {
changedManifest = YES;
[_manifest removeObjectForKey:key];
}
}
RCTAppendError(keyError, &errors);
}
if (changedManifest) {
[self _writeManifest:&errors];
}
callback(@[RCTNullIfNil(errors)]);
}
// clang-format off
RCT_EXPORT_METHOD(clear:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate removeAllValues:^(NSError *error) {
NSDictionary *result = nil;
if (error != nil) {
result = RCTMakeError(error.localizedDescription, error, nil);
}
callback(@[RCTNullIfNil(result)]);
}];
return;
}
[_manifest removeAllObjects];
[RCTGetCache() removeAllObjects];
NSDictionary *error = RCTDeleteStorageDirectory();
callback(@[RCTNullIfNil(error)]);
}
// clang-format off
RCT_EXPORT_METHOD(getAllKeys:(RCTResponseSenderBlock)callback)
// clang-format on
{
if (self.delegate != nil) {
[self.delegate allKeys:^(NSArray<id<NSObject>> *keys) {
callback(@[(id)kCFNull, keys]);
}];
if (![self _passthroughDelegate]) {
return;
}
}
NSDictionary *errorOut = [self _ensureSetup];
if (errorOut) {
callback(@[errorOut, (id)kCFNull]);
} else {
callback(@[(id)kCFNull, _manifest.allKeys]);
}
}
#if RCT_NEW_ARCH_ENABLED
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeAsyncStorageModuleSpecJSI>(params);
}
#endif
@end

View File

@@ -0,0 +1,285 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
747102F0243FFB7400D4F466 /* RNCAsyncStorage.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
747102F1243FFB7400D4F466 /* RNCAsyncStorageDelegate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
E29A30832BA9DF6000E66900 /* RNCAsyncStorage.mm in Sources */ = {isa = PBXBuildFile; fileRef = E29A30822BA9DF6000E66900 /* RNCAsyncStorage.mm */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
747102F0243FFB7400D4F466 /* RNCAsyncStorage.h in CopyFiles */,
747102F1243FFB7400D4F466 /* RNCAsyncStorageDelegate.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCAsyncStorage.a; sourceTree = BUILT_PRODUCTS_DIR; };
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCAsyncStorageDelegate.h; sourceTree = "<group>"; };
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RNCAsyncStorage.h; sourceTree = "<group>"; };
E29A30822BA9DF6000E66900 /* RNCAsyncStorage.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = RNCAsyncStorage.mm; sourceTree = "<group>"; };
E29A30842BA9E0E600E66900 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
E29A30842BA9E0E600E66900 /* PrivacyInfo.xcprivacy */,
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */,
E29A30822BA9DF6000E66900 /* RNCAsyncStorage.mm */,
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */,
134814211AA4EA7D00B7C361 /* Products */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
19F94B1D2239A948006921A9 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */,
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */;
buildPhases = (
19F94B1D2239A948006921A9 /* Headers */,
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNCAsyncStorage;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E29A30832BA9DF6000E66900 /* RNCAsyncStorage.mm in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void (^RNCAsyncStorageCompletion)(NSError *_Nullable error);
typedef void (^RNCAsyncStorageResultCallback)(NSArray<id<NSObject>> *valuesOrErrors);
@protocol RNCAsyncStorageDelegate <NSObject>
/*!
* Returns all keys currently stored. If none, an empty array is returned.
* @param block Block to call with result.
*/
- (void)allKeys:(RNCAsyncStorageResultCallback)block;
/*!
* Merges values with the corresponding values stored at specified keys.
* @param values Values to merge.
* @param keys Keys to the values that should be merged with.
* @param block Block to call with merged result.
*/
- (void)mergeValues:(NSArray<NSString *> *)values
forKeys:(NSArray<NSString *> *)keys
completion:(RNCAsyncStorageResultCallback)block;
/*!
* Removes all values from the store.
* @param block Block to call with result.
*/
- (void)removeAllValues:(RNCAsyncStorageCompletion)block;
/*!
* Removes all values associated with specified keys.
* @param keys Keys of values to remove.
* @param block Block to call with result.
*/
- (void)removeValuesForKeys:(NSArray<NSString *> *)keys
completion:(RNCAsyncStorageResultCallback)block;
/*!
* Sets specified key-value pairs.
* @param values Values to set.
* @param keys Keys of specified values to set.
* @param block Block to call with result.
*/
- (void)setValues:(NSArray<NSString *> *)values
forKeys:(NSArray<NSString *> *)keys
completion:(RNCAsyncStorageResultCallback)block;
/*!
* Returns values associated with specified keys.
* @param keys Keys of values to return.
* @param block Block to call with result.
*/
- (void)valuesForKeys:(NSArray<NSString *> *)keys completion:(RNCAsyncStorageResultCallback)block;
@optional
/*!
* Returns whether the delegate should be treated as a passthrough.
*/
@property (nonatomic, readonly, getter=isPassthrough) BOOL passthrough;
@end
NS_ASSUME_NONNULL_END

View File

@@ -0,0 +1,9 @@
import type {
AsyncStorageHook,
AsyncStorageStatic,
} from "../lib/typescript/types";
export function useAsyncStorage(key: string): AsyncStorageHook;
declare const AsyncStorage: AsyncStorageStatic;
export default AsyncStorage;

View File

@@ -0,0 +1,107 @@
/* eslint-disable no-undef */
const merge = require("merge-options").bind({
concatArrays: true,
ignoreUndefined: true,
});
const asMock = {
__INTERNAL_MOCK_STORAGE__: {},
setItem: jest.fn(async (key, value, callback) => {
const setResult = await asMock.multiSet([[key, value]], undefined);
callback && callback(setResult);
return setResult;
}),
getItem: jest.fn(async (key, callback) => {
const getResult = await asMock.multiGet([key], undefined);
const result = getResult[0] ? getResult[0][1] : null;
callback && callback(null, result);
return result;
}),
removeItem: jest.fn((key, callback) => asMock.multiRemove([key], callback)),
mergeItem: jest.fn((key, value, callback) =>
asMock.multiMerge([[key, value]], callback)
),
clear: jest.fn(_clear),
getAllKeys: jest.fn(_getAllKeys),
flushGetRequests: jest.fn(),
multiGet: jest.fn(_multiGet),
multiSet: jest.fn(_multiSet),
multiRemove: jest.fn(_multiRemove),
multiMerge: jest.fn(_multiMerge),
useAsyncStorage: jest.fn((key) => {
return {
getItem: (...args) => asMock.getItem(key, ...args),
setItem: (...args) => asMock.setItem(key, ...args),
mergeItem: (...args) => asMock.mergeItem(key, ...args),
removeItem: (...args) => asMock.removeItem(key, ...args),
};
}),
};
async function _multiSet(keyValuePairs, callback) {
keyValuePairs.forEach((keyValue) => {
const key = keyValue[0];
asMock.__INTERNAL_MOCK_STORAGE__[key] = keyValue[1];
});
callback && callback(null);
return null;
}
async function _multiGet(keys, callback) {
const values = keys.map((key) => [
key,
asMock.__INTERNAL_MOCK_STORAGE__[key] || null,
]);
callback && callback(null, values);
return values;
}
async function _multiRemove(keys, callback) {
keys.forEach((key) => {
if (asMock.__INTERNAL_MOCK_STORAGE__[key]) {
delete asMock.__INTERNAL_MOCK_STORAGE__[key];
}
});
callback && callback(null);
return null;
}
async function _clear(callback) {
asMock.__INTERNAL_MOCK_STORAGE__ = {};
callback && callback(null);
return null;
}
async function _getAllKeys() {
return Object.keys(asMock.__INTERNAL_MOCK_STORAGE__);
}
async function _multiMerge(keyValuePairs, callback) {
keyValuePairs.forEach((keyValue) => {
const [key, value] = keyValue;
const oldValue = asMock.__INTERNAL_MOCK_STORAGE__[key];
asMock.__INTERNAL_MOCK_STORAGE__[key] =
oldValue != null
? JSON.stringify(merge(JSON.parse(oldValue), JSON.parse(value)))
: value;
});
callback && callback(null);
return null;
}
module.exports = asMock;

View File

@@ -0,0 +1,146 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _mergeOptions = _interopRequireDefault(require("merge-options"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
// eslint-disable-next-line @typescript-eslint/ban-types
// eslint-disable-next-line @typescript-eslint/ban-types
const merge = _mergeOptions.default.bind({
concatArrays: true,
ignoreUndefined: true
});
function mergeLocalStorageItem(key, value) {
const oldValue = window.localStorage.getItem(key);
if (oldValue) {
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify(merge(oldObject, newObject));
window.localStorage.setItem(key, nextValue);
} else {
window.localStorage.setItem(key, value);
}
}
function createPromise(getValue, callback) {
return new Promise((resolve, reject) => {
try {
const value = getValue();
callback === null || callback === void 0 ? void 0 : callback(null, value);
resolve(value);
} catch (err) {
callback === null || callback === void 0 ? void 0 : callback(err);
reject(err);
}
});
}
function createPromiseAll(promises, callback, processResult) {
return Promise.all(promises).then(result => {
const value = (processResult === null || processResult === void 0 ? void 0 : processResult(result)) ?? null;
callback === null || callback === void 0 ? void 0 : callback(null, value);
return Promise.resolve(value);
}, errors => {
callback === null || callback === void 0 ? void 0 : callback(errors);
return Promise.reject(errors);
});
}
const AsyncStorage = {
/**
* Fetches `key` value.
*/
getItem: (key, callback) => {
return createPromise(() => window.localStorage.getItem(key), callback);
},
/**
* Sets `value` for `key`.
*/
setItem: (key, value, callback) => {
return createPromise(() => window.localStorage.setItem(key, value), callback);
},
/**
* Removes a `key`
*/
removeItem: (key, callback) => {
return createPromise(() => window.localStorage.removeItem(key), callback);
},
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
mergeItem: (key, value, callback) => {
return createPromise(() => mergeLocalStorageItem(key, value), callback);
},
/**
* Erases *all* AsyncStorage for the domain.
*/
clear: callback => {
return createPromise(() => window.localStorage.clear(), callback);
},
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
getAllKeys: callback => {
return createPromise(() => {
const numberOfKeys = window.localStorage.length;
const keys = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i) || "";
keys.push(key);
}
return keys;
}, callback);
},
/**
* (stub) Flushes any pending requests using a single batch call to get the data.
*/
flushGetRequests: () => undefined,
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
multiGet: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.getItem(key));
const processResult = result => result.map((value, i) => [keys[i], value]);
return createPromiseAll(promises, callback, processResult);
},
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
multiSet: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.setItem(item[0], item[1]));
return createPromiseAll(promises, callback);
},
/**
* Delete all the keys in the `keys` array.
*/
multiRemove: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.removeItem(key));
return createPromiseAll(promises, callback);
},
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
multiMerge: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.mergeItem(item[0], item[1]));
return createPromiseAll(promises, callback);
}
};
var _default = AsyncStorage;
exports.default = _default;
//# sourceMappingURL=AsyncStorage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,324 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _helpers = require("./helpers");
var _RCTAsyncStorage = _interopRequireDefault(require("./RCTAsyncStorage"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
if (!_RCTAsyncStorage.default) {
throw new Error(`[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
• Uninstall, rebuild and restart the app.
• Run the packager with \`--reset-cache\` flag.
• If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory, then rebuild and re-run the app.
• Make sure your project's \`package.json\` depends on \`@react-native-async-storage/async-storage\`, even if you only depend on it indirectly through other dependencies. CLI only autolinks native modules found in your \`package.json\`.
• If this happens while testing with Jest, check out how to integrate AsyncStorage here: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the GitHub repository: https://github.com/react-native-async-storage/async-storage/issues
`);
}
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
const AsyncStorage = (() => {
let _getRequests = [];
let _getKeys = [];
let _immediate = null;
return {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key);
_RCTAsyncStorage.default.multiGet([key], (errors, result) => {
var _result$;
// Unpack result to get value from [[key,value]]
const value = result !== null && result !== void 0 && (_result$ = result[0]) !== null && _result$ !== void 0 && _result$[1] ? result[0][1] : null;
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0], value);
if (errs) {
reject(errs[0]);
} else {
resolve(value);
}
});
});
},
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key, value);
_RCTAsyncStorage.default.multiSet([[key, value]], errors => {
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key);
_RCTAsyncStorage.default.multiRemove([key], errors => {
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
(0, _helpers.checkValidInput)(key, value);
_RCTAsyncStorage.default.multiMerge([[key, value]], errors => {
const errs = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: callback => {
return new Promise((resolve, reject) => {
_RCTAsyncStorage.default.clear(error => {
const err = (0, _helpers.convertError)(error);
callback === null || callback === void 0 ? void 0 : callback(err);
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: callback => {
return new Promise((resolve, reject) => {
_RCTAsyncStorage.default.getAllKeys((error, keys) => {
const err = (0, _helpers.convertError)(error);
callback === null || callback === void 0 ? void 0 : callback(err, keys);
if (keys) {
resolve(keys);
} else {
reject(err);
}
});
});
},
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => {
const getRequests = _getRequests;
const getKeys = _getKeys;
_getRequests = [];
_getKeys = [];
_RCTAsyncStorage.default.multiGet(getKeys, (errors, result) => {
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
// it's much, much faster in practice for the data sets we deal with (we avoid
// allocating result pair arrays). This was heavily benchmarked.
//
// Is there a way to avoid using the map but fix the bug in this breaking test?
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
const map = {};
result === null || result === void 0 ? void 0 : result.forEach(([key, value]) => {
map[key] = value;
return value;
});
const reqLength = getRequests.length;
/**
* As mentioned few lines above, this method could be called with the array of potential error,
* in case of anything goes wrong. The problem is, if any of the batched calls fails
* the rest of them would fail too, but the error would be consumed by just one. The rest
* would simply return `undefined` as their result, rendering false negatives.
*
* In order to avoid this situation, in case of any call failing,
* the rest of them will be rejected as well (with the same error).
*/
const errorList = (0, _helpers.convertErrors)(errors);
const error = errorList !== null && errorList !== void 0 && errorList.length ? errorList[0] : null;
for (let i = 0; i < reqLength; i++) {
var _request$callback2, _request$resolve;
const request = getRequests[i];
if (error) {
var _request$callback, _request$reject;
(_request$callback = request.callback) === null || _request$callback === void 0 ? void 0 : _request$callback.call(request, errorList);
(_request$reject = request.reject) === null || _request$reject === void 0 ? void 0 : _request$reject.call(request, error);
continue;
}
const requestResult = request.keys.map(key => [key, map[key]]);
(_request$callback2 = request.callback) === null || _request$callback2 === void 0 ? void 0 : _request$callback2.call(request, null, requestResult);
(_request$resolve = request.resolve) === null || _request$resolve === void 0 ? void 0 : _request$resolve.call(request, requestResult);
}
});
},
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys, callback) => {
if (!_immediate) {
_immediate = setImmediate(() => {
_immediate = null;
AsyncStorage.flushGetRequests();
});
}
const getRequest = {
keys: keys,
callback: callback,
// do we need this?
keyIndex: _getKeys.length
};
const promiseResult = new Promise((resolve, reject) => {
getRequest.resolve = resolve;
getRequest.reject = reject;
});
_getRequests.push(getRequest);
// avoid fetching duplicates
keys.forEach(key => {
if (_getKeys.indexOf(key) === -1) {
_getKeys.push(key);
}
});
return promiseResult;
},
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs, callback) => {
(0, _helpers.checkValidArgs)(keyValuePairs, callback);
return new Promise((resolve, reject) => {
keyValuePairs.forEach(([key, value]) => {
(0, _helpers.checkValidInput)(key, value);
});
_RCTAsyncStorage.default.multiSet(keyValuePairs, errors => {
const error = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys, callback) => {
return new Promise((resolve, reject) => {
keys.forEach(key => (0, _helpers.checkValidInput)(key));
_RCTAsyncStorage.default.multiRemove(keys, errors => {
const error = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs, callback) => {
return new Promise((resolve, reject) => {
_RCTAsyncStorage.default.multiMerge(keyValuePairs, errors => {
const error = (0, _helpers.convertErrors)(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
};
})();
var _default = AsyncStorage;
exports.default = _default;
//# sourceMappingURL=AsyncStorage.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _reactNative = require("react-native");
var _default = _reactNative.TurboModuleRegistry.get("RNCAsyncStorage");
exports.default = _default;
//# sourceMappingURL=NativeAsyncStorageModule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["_reactNative","require","_default","TurboModuleRegistry","get","exports","default"],"sources":["NativeAsyncStorageModule.ts"],"sourcesContent":["import type { TurboModule } from \"react-native\";\nimport { TurboModuleRegistry } from \"react-native\";\nimport type { ErrorLike } from \"./types\";\n\nexport interface Spec extends TurboModule {\n multiGet: (\n keys: string[],\n callback: (error?: ErrorLike[], result?: [string, string][]) => void\n ) => void;\n multiSet: (\n kvPairs: [string, string][],\n callback: (error?: ErrorLike[]) => void\n ) => void;\n multiRemove: (\n keys: readonly string[],\n callback: (error?: ErrorLike[]) => void\n ) => void;\n multiMerge: (\n kvPairs: [string, string][],\n callback: (error?: ErrorLike[]) => void\n ) => void;\n getAllKeys: (\n callback: (error?: ErrorLike[], result?: [string, string][]) => void\n ) => void;\n clear: (callback: (error?: ErrorLike[]) => void) => void;\n}\n\nexport default TurboModuleRegistry.get<Spec>(\"RNCAsyncStorage\");\n"],"mappings":";;;;;;AACA,IAAAA,YAAA,GAAAC,OAAA;AAAmD,IAAAC,QAAA,GA0BpCC,gCAAmB,CAACC,GAAG,CAAO,iBAAiB,CAAC;AAAAC,OAAA,CAAAC,OAAA,GAAAJ,QAAA"}

View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
var _reactNative = require("react-native");
var _shouldFallbackToLegacyNativeModule = require("./shouldFallbackToLegacyNativeModule");
// TurboModuleRegistry falls back to NativeModules so we don't have to try go
// assign NativeModules' counterparts if TurboModuleRegistry would resolve
// with undefined.
let RCTAsyncStorage = _reactNative.TurboModuleRegistry ? _reactNative.TurboModuleRegistry.get("PlatformLocalStorage") ||
// Support for external modules, like react-native-windows
_reactNative.TurboModuleRegistry.get("RNC_AsyncSQLiteDBStorage") || _reactNative.TurboModuleRegistry.get("RNCAsyncStorage") : _reactNative.NativeModules["PlatformLocalStorage"] ||
// Support for external modules, like react-native-windows
_reactNative.NativeModules["RNC_AsyncSQLiteDBStorage"] || _reactNative.NativeModules["RNCAsyncStorage"];
if (!RCTAsyncStorage && (0, _shouldFallbackToLegacyNativeModule.shouldFallbackToLegacyNativeModule)()) {
if (_reactNative.TurboModuleRegistry) {
RCTAsyncStorage = _reactNative.TurboModuleRegistry.get("AsyncSQLiteDBStorage") || _reactNative.TurboModuleRegistry.get("AsyncLocalStorage");
} else {
RCTAsyncStorage = _reactNative.NativeModules["AsyncSQLiteDBStorage"] || _reactNative.NativeModules["AsyncLocalStorage"];
}
}
var _default = RCTAsyncStorage;
exports.default = _default;
//# sourceMappingURL=RCTAsyncStorage.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["_reactNative","require","_shouldFallbackToLegacyNativeModule","RCTAsyncStorage","TurboModuleRegistry","get","NativeModules","shouldFallbackToLegacyNativeModule","_default","exports","default"],"sources":["RCTAsyncStorage.ts"],"sourcesContent":["import { NativeModules, TurboModuleRegistry } from \"react-native\";\nimport { shouldFallbackToLegacyNativeModule } from \"./shouldFallbackToLegacyNativeModule\";\n\n// TurboModuleRegistry falls back to NativeModules so we don't have to try go\n// assign NativeModules' counterparts if TurboModuleRegistry would resolve\n// with undefined.\nlet RCTAsyncStorage = TurboModuleRegistry\n ? TurboModuleRegistry.get(\"PlatformLocalStorage\") || // Support for external modules, like react-native-windows\n TurboModuleRegistry.get(\"RNC_AsyncSQLiteDBStorage\") ||\n TurboModuleRegistry.get(\"RNCAsyncStorage\")\n : NativeModules[\"PlatformLocalStorage\"] || // Support for external modules, like react-native-windows\n NativeModules[\"RNC_AsyncSQLiteDBStorage\"] ||\n NativeModules[\"RNCAsyncStorage\"];\n\nif (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {\n if (TurboModuleRegistry) {\n RCTAsyncStorage =\n TurboModuleRegistry.get(\"AsyncSQLiteDBStorage\") ||\n TurboModuleRegistry.get(\"AsyncLocalStorage\");\n } else {\n RCTAsyncStorage =\n NativeModules[\"AsyncSQLiteDBStorage\"] ||\n NativeModules[\"AsyncLocalStorage\"];\n }\n}\n\nexport default RCTAsyncStorage;\n"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AACA,IAAAC,mCAAA,GAAAD,OAAA;AAEA;AACA;AACA;AACA,IAAIE,eAAe,GAAGC,gCAAmB,GACrCA,gCAAmB,CAACC,GAAG,CAAC,sBAAsB,CAAC;AAAI;AACnDD,gCAAmB,CAACC,GAAG,CAAC,0BAA0B,CAAC,IACnDD,gCAAmB,CAACC,GAAG,CAAC,iBAAiB,CAAC,GAC1CC,0BAAa,CAAC,sBAAsB,CAAC;AAAI;AACzCA,0BAAa,CAAC,0BAA0B,CAAC,IACzCA,0BAAa,CAAC,iBAAiB,CAAC;AAEpC,IAAI,CAACH,eAAe,IAAI,IAAAI,sEAAkC,EAAC,CAAC,EAAE;EAC5D,IAAIH,gCAAmB,EAAE;IACvBD,eAAe,GACbC,gCAAmB,CAACC,GAAG,CAAC,sBAAsB,CAAC,IAC/CD,gCAAmB,CAACC,GAAG,CAAC,mBAAmB,CAAC;EAChD,CAAC,MAAM;IACLF,eAAe,GACbG,0BAAa,CAAC,sBAAsB,CAAC,IACrCA,0BAAa,CAAC,mBAAmB,CAAC;EACtC;AACF;AAAC,IAAAE,QAAA,GAEcL,eAAe;AAAAM,OAAA,CAAAC,OAAA,GAAAF,QAAA"}

View File

@@ -0,0 +1,57 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.checkValidArgs = checkValidArgs;
exports.checkValidInput = checkValidInput;
exports.convertError = convertError;
exports.convertErrors = convertErrors;
function checkValidArgs(keyValuePairs, callback) {
if (!Array.isArray(keyValuePairs) || keyValuePairs.length === 0 || !Array.isArray(keyValuePairs[0])) {
throw new Error("[AsyncStorage] Expected array of key-value pairs as first argument to multiSet");
}
if (callback && typeof callback !== "function") {
if (Array.isArray(callback)) {
throw new Error("[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?");
}
throw new Error("[AsyncStorage] Expected function as second argument to multiSet");
}
}
function checkValidInput(...input) {
const [key, value] = input;
if (typeof key !== "string") {
// eslint-disable-next-line no-console
console.warn(`[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${key}\n`);
}
if (input.length > 1 && typeof value !== "string") {
if (value == null) {
throw new Error(`[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\nPassed value: ${value}\nPassed key: ${key}\n`);
} else {
// eslint-disable-next-line no-console
console.warn(`[AsyncStorage] The value for key "${key}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${key}\n`);
}
}
}
function convertError(error) {
if (!error) {
return null;
}
const out = new Error(error.message);
out["key"] = error.key;
return out;
}
function convertErrors(errs) {
const errors = ensureArray(errs);
return errors ? errors.map(e => convertError(e)) : null;
}
function ensureArray(e) {
if (Array.isArray(e)) {
return e.length === 0 ? null : e;
} else if (e) {
return [e];
} else {
return null;
}
}
//# sourceMappingURL=helpers.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["checkValidArgs","keyValuePairs","callback","Array","isArray","length","Error","checkValidInput","input","key","value","console","warn","convertError","error","out","message","convertErrors","errs","errors","ensureArray","map","e"],"sources":["helpers.ts"],"sourcesContent":["import type { ErrorLike } from \"./types\";\n\nexport function checkValidArgs(\n keyValuePairs: readonly unknown[],\n callback: unknown\n) {\n if (\n !Array.isArray(keyValuePairs) ||\n keyValuePairs.length === 0 ||\n !Array.isArray(keyValuePairs[0])\n ) {\n throw new Error(\n \"[AsyncStorage] Expected array of key-value pairs as first argument to multiSet\"\n );\n }\n\n if (callback && typeof callback !== \"function\") {\n if (Array.isArray(callback)) {\n throw new Error(\n \"[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?\"\n );\n }\n\n throw new Error(\n \"[AsyncStorage] Expected function as second argument to multiSet\"\n );\n }\n}\n\nexport function checkValidInput(...input: unknown[]) {\n const [key, value] = input;\n\n if (typeof key !== \"string\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\\nKey passed: ${key}\\n`\n );\n }\n\n if (input.length > 1 && typeof value !== \"string\") {\n if (value == null) {\n throw new Error(\n `[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n } else {\n // eslint-disable-next-line no-console\n console.warn(\n `[AsyncStorage] The value for key \"${key}\" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n }\n }\n}\n\nexport function convertError(error?: ErrorLike): Error | null {\n if (!error) {\n return null;\n }\n\n const out = new Error(error.message) as Error & ErrorLike;\n out[\"key\"] = error.key;\n return out;\n}\n\nexport function convertErrors(\n errs?: ErrorLike[]\n): ReadonlyArray<Error | null> | null {\n const errors = ensureArray(errs);\n return errors ? errors.map((e) => convertError(e)) : null;\n}\n\nfunction ensureArray(e?: ErrorLike | ErrorLike[]): ErrorLike[] | null {\n if (Array.isArray(e)) {\n return e.length === 0 ? null : e;\n } else if (e) {\n return [e];\n } else {\n return null;\n }\n}\n"],"mappings":";;;;;;;;;AAEO,SAASA,cAAcA,CAC5BC,aAAiC,EACjCC,QAAiB,EACjB;EACA,IACE,CAACC,KAAK,CAACC,OAAO,CAACH,aAAa,CAAC,IAC7BA,aAAa,CAACI,MAAM,KAAK,CAAC,IAC1B,CAACF,KAAK,CAACC,OAAO,CAACH,aAAa,CAAC,CAAC,CAAC,CAAC,EAChC;IACA,MAAM,IAAIK,KAAK,CACb,gFACF,CAAC;EACH;EAEA,IAAIJ,QAAQ,IAAI,OAAOA,QAAQ,KAAK,UAAU,EAAE;IAC9C,IAAIC,KAAK,CAACC,OAAO,CAACF,QAAQ,CAAC,EAAE;MAC3B,MAAM,IAAII,KAAK,CACb,6IACF,CAAC;IACH;IAEA,MAAM,IAAIA,KAAK,CACb,iEACF,CAAC;EACH;AACF;AAEO,SAASC,eAAeA,CAAC,GAAGC,KAAgB,EAAE;EACnD,MAAM,CAACC,GAAG,EAAEC,KAAK,CAAC,GAAGF,KAAK;EAE1B,IAAI,OAAOC,GAAG,KAAK,QAAQ,EAAE;IAC3B;IACAE,OAAO,CAACC,IAAI,CACT,wBAAuB,OAAOH,GAAI,iHAAgHA,GAAI,IACzJ,CAAC;EACH;EAEA,IAAID,KAAK,CAACH,MAAM,GAAG,CAAC,IAAI,OAAOK,KAAK,KAAK,QAAQ,EAAE;IACjD,IAAIA,KAAK,IAAI,IAAI,EAAE;MACjB,MAAM,IAAIJ,KAAK,CACZ,gJAA+II,KAAM,iBAAgBD,GAAI,IAC5K,CAAC;IACH,CAAC,MAAM;MACL;MACAE,OAAO,CAACC,IAAI,CACT,qCAAoCH,GAAI,4GAA2GC,KAAM,iBAAgBD,GAAI,IAChL,CAAC;IACH;EACF;AACF;AAEO,SAASI,YAAYA,CAACC,KAAiB,EAAgB;EAC5D,IAAI,CAACA,KAAK,EAAE;IACV,OAAO,IAAI;EACb;EAEA,MAAMC,GAAG,GAAG,IAAIT,KAAK,CAACQ,KAAK,CAACE,OAAO,CAAsB;EACzDD,GAAG,CAAC,KAAK,CAAC,GAAGD,KAAK,CAACL,GAAG;EACtB,OAAOM,GAAG;AACZ;AAEO,SAASE,aAAaA,CAC3BC,IAAkB,EACkB;EACpC,MAAMC,MAAM,GAAGC,WAAW,CAACF,IAAI,CAAC;EAChC,OAAOC,MAAM,GAAGA,MAAM,CAACE,GAAG,CAAEC,CAAC,IAAKT,YAAY,CAACS,CAAC,CAAC,CAAC,GAAG,IAAI;AAC3D;AAEA,SAASF,WAAWA,CAACE,CAA2B,EAAsB;EACpE,IAAInB,KAAK,CAACC,OAAO,CAACkB,CAAC,CAAC,EAAE;IACpB,OAAOA,CAAC,CAACjB,MAAM,KAAK,CAAC,GAAG,IAAI,GAAGiB,CAAC;EAClC,CAAC,MAAM,IAAIA,CAAC,EAAE;IACZ,OAAO,CAACA,CAAC,CAAC;EACZ,CAAC,MAAM;IACL,OAAO,IAAI;EACb;AACF"}

View File

@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.useAsyncStorage = useAsyncStorage;
var _AsyncStorage = _interopRequireDefault(require("./AsyncStorage"));
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
function useAsyncStorage(key) {
return {
getItem: (...args) => _AsyncStorage.default.getItem(key, ...args),
setItem: (...args) => _AsyncStorage.default.setItem(key, ...args),
mergeItem: (...args) => _AsyncStorage.default.mergeItem(key, ...args),
removeItem: (...args) => _AsyncStorage.default.removeItem(key, ...args)
};
}
//# sourceMappingURL=hooks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["_AsyncStorage","_interopRequireDefault","require","obj","__esModule","default","useAsyncStorage","key","getItem","args","AsyncStorage","setItem","mergeItem","removeItem"],"sources":["hooks.ts"],"sourcesContent":["import AsyncStorage from \"./AsyncStorage\";\nimport type { AsyncStorageHook } from \"./types\";\n\nexport function useAsyncStorage(key: string): AsyncStorageHook {\n return {\n getItem: (...args) => AsyncStorage.getItem(key, ...args),\n setItem: (...args) => AsyncStorage.setItem(key, ...args),\n mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),\n removeItem: (...args) => AsyncStorage.removeItem(key, ...args),\n };\n}\n"],"mappings":";;;;;;AAAA,IAAAA,aAAA,GAAAC,sBAAA,CAAAC,OAAA;AAA0C,SAAAD,uBAAAE,GAAA,WAAAA,GAAA,IAAAA,GAAA,CAAAC,UAAA,GAAAD,GAAA,KAAAE,OAAA,EAAAF,GAAA;AAGnC,SAASG,eAAeA,CAACC,GAAW,EAAoB;EAC7D,OAAO;IACLC,OAAO,EAAEA,CAAC,GAAGC,IAAI,KAAKC,qBAAY,CAACF,OAAO,CAACD,GAAG,EAAE,GAAGE,IAAI,CAAC;IACxDE,OAAO,EAAEA,CAAC,GAAGF,IAAI,KAAKC,qBAAY,CAACC,OAAO,CAACJ,GAAG,EAAE,GAAGE,IAAI,CAAC;IACxDG,SAAS,EAAEA,CAAC,GAAGH,IAAI,KAAKC,qBAAY,CAACE,SAAS,CAACL,GAAG,EAAE,GAAGE,IAAI,CAAC;IAC5DI,UAAU,EAAEA,CAAC,GAAGJ,IAAI,KAAKC,qBAAY,CAACG,UAAU,CAACN,GAAG,EAAE,GAAGE,IAAI;EAC/D,CAAC;AACH"}

View File

@@ -0,0 +1,18 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = void 0;
Object.defineProperty(exports, "useAsyncStorage", {
enumerable: true,
get: function () {
return _hooks.useAsyncStorage;
}
});
var _AsyncStorage = _interopRequireDefault(require("./AsyncStorage"));
var _hooks = require("./hooks");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var _default = _AsyncStorage.default;
exports.default = _default;
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["_AsyncStorage","_interopRequireDefault","require","_hooks","obj","__esModule","default","_default","AsyncStorage","exports"],"sources":["index.ts"],"sourcesContent":["import AsyncStorage from \"./AsyncStorage\";\n\nexport { useAsyncStorage } from \"./hooks\";\n\nexport type { AsyncStorageStatic } from \"./types\";\n\nexport default AsyncStorage;\n"],"mappings":";;;;;;;;;;;;AAAA,IAAAA,aAAA,GAAAC,sBAAA,CAAAC,OAAA;AAEA,IAAAC,MAAA,GAAAD,OAAA;AAA0C,SAAAD,uBAAAG,GAAA,WAAAA,GAAA,IAAAA,GAAA,CAAAC,UAAA,GAAAD,GAAA,KAAAE,OAAA,EAAAF,GAAA;AAAA,IAAAG,QAAA,GAI3BC,qBAAY;AAAAC,OAAA,CAAAH,OAAA,GAAAC,QAAA"}

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.shouldFallbackToLegacyNativeModule = shouldFallbackToLegacyNativeModule;
var _reactNative = require("react-native");
function shouldFallbackToLegacyNativeModule() {
var _NativeModules$Native;
const expoConstants = (_NativeModules$Native = _reactNative.NativeModules["NativeUnimoduleProxy"]) === null || _NativeModules$Native === void 0 || (_NativeModules$Native = _NativeModules$Native.modulesConstants) === null || _NativeModules$Native === void 0 ? void 0 : _NativeModules$Native.ExponentConstants;
if (expoConstants) {
/**
* In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.
* In bare React Native apps using expo-constants, appOwnership is never defined, so
* isLegacySdkVersion will be false in that context.
*/
const isLegacySdkVersion = expoConstants.appOwnership && !expoConstants.executionEnvironment;
/**
* Expo managed apps don't include the @react-native-async-storage/async-storage
* native modules yet, but the API interface is the same, so we can use the version
* exported from React Native still.
*
* If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this
* will likely not be valid anymore, and the package will need to be included in the Expo SDK
* to continue to work.
*/
if (isLegacySdkVersion || ["storeClient", "standalone"].includes(expoConstants.executionEnvironment)) {
return true;
}
}
return false;
}
//# sourceMappingURL=shouldFallbackToLegacyNativeModule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["_reactNative","require","shouldFallbackToLegacyNativeModule","_NativeModules$Native","expoConstants","NativeModules","modulesConstants","ExponentConstants","isLegacySdkVersion","appOwnership","executionEnvironment","includes"],"sources":["shouldFallbackToLegacyNativeModule.ts"],"sourcesContent":["import { NativeModules } from \"react-native\";\n\nexport function shouldFallbackToLegacyNativeModule(): boolean {\n const expoConstants =\n NativeModules[\"NativeUnimoduleProxy\"]?.modulesConstants?.ExponentConstants;\n\n if (expoConstants) {\n /**\n * In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.\n * In bare React Native apps using expo-constants, appOwnership is never defined, so\n * isLegacySdkVersion will be false in that context.\n */\n const isLegacySdkVersion =\n expoConstants.appOwnership && !expoConstants.executionEnvironment;\n\n /**\n * Expo managed apps don't include the @react-native-async-storage/async-storage\n * native modules yet, but the API interface is the same, so we can use the version\n * exported from React Native still.\n *\n * If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this\n * will likely not be valid anymore, and the package will need to be included in the Expo SDK\n * to continue to work.\n */\n if (\n isLegacySdkVersion ||\n [\"storeClient\", \"standalone\"].includes(expoConstants.executionEnvironment)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":";;;;;;AAAA,IAAAA,YAAA,GAAAC,OAAA;AAEO,SAASC,kCAAkCA,CAAA,EAAY;EAAA,IAAAC,qBAAA;EAC5D,MAAMC,aAAa,IAAAD,qBAAA,GACjBE,0BAAa,CAAC,sBAAsB,CAAC,cAAAF,qBAAA,gBAAAA,qBAAA,GAArCA,qBAAA,CAAuCG,gBAAgB,cAAAH,qBAAA,uBAAvDA,qBAAA,CAAyDI,iBAAiB;EAE5E,IAAIH,aAAa,EAAE;IACjB;AACJ;AACA;AACA;AACA;IACI,MAAMI,kBAAkB,GACtBJ,aAAa,CAACK,YAAY,IAAI,CAACL,aAAa,CAACM,oBAAoB;;IAEnE;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACI,IACEF,kBAAkB,IAClB,CAAC,aAAa,EAAE,YAAY,CAAC,CAACG,QAAQ,CAACP,aAAa,CAACM,oBAAoB,CAAC,EAC1E;MACA,OAAO,IAAI;IACb;EACF;EAEA,OAAO,KAAK;AACd"}

View File

@@ -0,0 +1,2 @@
"use strict";
//# sourceMappingURL=types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,139 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import mergeOptions from "merge-options";
// eslint-disable-next-line @typescript-eslint/ban-types
// eslint-disable-next-line @typescript-eslint/ban-types
const merge = mergeOptions.bind({
concatArrays: true,
ignoreUndefined: true
});
function mergeLocalStorageItem(key, value) {
const oldValue = window.localStorage.getItem(key);
if (oldValue) {
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify(merge(oldObject, newObject));
window.localStorage.setItem(key, nextValue);
} else {
window.localStorage.setItem(key, value);
}
}
function createPromise(getValue, callback) {
return new Promise((resolve, reject) => {
try {
const value = getValue();
callback === null || callback === void 0 ? void 0 : callback(null, value);
resolve(value);
} catch (err) {
callback === null || callback === void 0 ? void 0 : callback(err);
reject(err);
}
});
}
function createPromiseAll(promises, callback, processResult) {
return Promise.all(promises).then(result => {
const value = (processResult === null || processResult === void 0 ? void 0 : processResult(result)) ?? null;
callback === null || callback === void 0 ? void 0 : callback(null, value);
return Promise.resolve(value);
}, errors => {
callback === null || callback === void 0 ? void 0 : callback(errors);
return Promise.reject(errors);
});
}
const AsyncStorage = {
/**
* Fetches `key` value.
*/
getItem: (key, callback) => {
return createPromise(() => window.localStorage.getItem(key), callback);
},
/**
* Sets `value` for `key`.
*/
setItem: (key, value, callback) => {
return createPromise(() => window.localStorage.setItem(key, value), callback);
},
/**
* Removes a `key`
*/
removeItem: (key, callback) => {
return createPromise(() => window.localStorage.removeItem(key), callback);
},
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
mergeItem: (key, value, callback) => {
return createPromise(() => mergeLocalStorageItem(key, value), callback);
},
/**
* Erases *all* AsyncStorage for the domain.
*/
clear: callback => {
return createPromise(() => window.localStorage.clear(), callback);
},
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
getAllKeys: callback => {
return createPromise(() => {
const numberOfKeys = window.localStorage.length;
const keys = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i) || "";
keys.push(key);
}
return keys;
}, callback);
},
/**
* (stub) Flushes any pending requests using a single batch call to get the data.
*/
flushGetRequests: () => undefined,
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
multiGet: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.getItem(key));
const processResult = result => result.map((value, i) => [keys[i], value]);
return createPromiseAll(promises, callback, processResult);
},
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
multiSet: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.setItem(item[0], item[1]));
return createPromiseAll(promises, callback);
},
/**
* Delete all the keys in the `keys` array.
*/
multiRemove: (keys, callback) => {
const promises = keys.map(key => AsyncStorage.removeItem(key));
return createPromiseAll(promises, callback);
},
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
multiMerge: (keyValuePairs, callback) => {
const promises = keyValuePairs.map(item => AsyncStorage.mergeItem(item[0], item[1]));
return createPromiseAll(promises, callback);
}
};
export default AsyncStorage;
//# sourceMappingURL=AsyncStorage.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,316 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import { checkValidArgs, checkValidInput, convertError, convertErrors } from "./helpers";
import RCTAsyncStorage from "./RCTAsyncStorage";
if (!RCTAsyncStorage) {
throw new Error(`[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
• Uninstall, rebuild and restart the app.
• Run the packager with \`--reset-cache\` flag.
• If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory, then rebuild and re-run the app.
• Make sure your project's \`package.json\` depends on \`@react-native-async-storage/async-storage\`, even if you only depend on it indirectly through other dependencies. CLI only autolinks native modules found in your \`package.json\`.
• If this happens while testing with Jest, check out how to integrate AsyncStorage here: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the GitHub repository: https://github.com/react-native-async-storage/async-storage/issues
`);
}
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
const AsyncStorage = (() => {
let _getRequests = [];
let _getKeys = [];
let _immediate = null;
return {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiGet([key], (errors, result) => {
var _result$;
// Unpack result to get value from [[key,value]]
const value = result !== null && result !== void 0 && (_result$ = result[0]) !== null && _result$ !== void 0 && _result$[1] ? result[0][1] : null;
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0], value);
if (errs) {
reject(errs[0]);
} else {
resolve(value);
}
});
});
},
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiSet([[key, value]], errors => {
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiRemove([key], errors => {
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiMerge([[key, value]], errors => {
const errs = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(errs === null || errs === void 0 ? void 0 : errs[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: callback => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.clear(error => {
const err = convertError(error);
callback === null || callback === void 0 ? void 0 : callback(err);
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: callback => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.getAllKeys((error, keys) => {
const err = convertError(error);
callback === null || callback === void 0 ? void 0 : callback(err, keys);
if (keys) {
resolve(keys);
} else {
reject(err);
}
});
});
},
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => {
const getRequests = _getRequests;
const getKeys = _getKeys;
_getRequests = [];
_getKeys = [];
RCTAsyncStorage.multiGet(getKeys, (errors, result) => {
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
// it's much, much faster in practice for the data sets we deal with (we avoid
// allocating result pair arrays). This was heavily benchmarked.
//
// Is there a way to avoid using the map but fix the bug in this breaking test?
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
const map = {};
result === null || result === void 0 ? void 0 : result.forEach(([key, value]) => {
map[key] = value;
return value;
});
const reqLength = getRequests.length;
/**
* As mentioned few lines above, this method could be called with the array of potential error,
* in case of anything goes wrong. The problem is, if any of the batched calls fails
* the rest of them would fail too, but the error would be consumed by just one. The rest
* would simply return `undefined` as their result, rendering false negatives.
*
* In order to avoid this situation, in case of any call failing,
* the rest of them will be rejected as well (with the same error).
*/
const errorList = convertErrors(errors);
const error = errorList !== null && errorList !== void 0 && errorList.length ? errorList[0] : null;
for (let i = 0; i < reqLength; i++) {
var _request$callback2, _request$resolve;
const request = getRequests[i];
if (error) {
var _request$callback, _request$reject;
(_request$callback = request.callback) === null || _request$callback === void 0 ? void 0 : _request$callback.call(request, errorList);
(_request$reject = request.reject) === null || _request$reject === void 0 ? void 0 : _request$reject.call(request, error);
continue;
}
const requestResult = request.keys.map(key => [key, map[key]]);
(_request$callback2 = request.callback) === null || _request$callback2 === void 0 ? void 0 : _request$callback2.call(request, null, requestResult);
(_request$resolve = request.resolve) === null || _request$resolve === void 0 ? void 0 : _request$resolve.call(request, requestResult);
}
});
},
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys, callback) => {
if (!_immediate) {
_immediate = setImmediate(() => {
_immediate = null;
AsyncStorage.flushGetRequests();
});
}
const getRequest = {
keys: keys,
callback: callback,
// do we need this?
keyIndex: _getKeys.length
};
const promiseResult = new Promise((resolve, reject) => {
getRequest.resolve = resolve;
getRequest.reject = reject;
});
_getRequests.push(getRequest);
// avoid fetching duplicates
keys.forEach(key => {
if (_getKeys.indexOf(key) === -1) {
_getKeys.push(key);
}
});
return promiseResult;
},
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs, callback) => {
checkValidArgs(keyValuePairs, callback);
return new Promise((resolve, reject) => {
keyValuePairs.forEach(([key, value]) => {
checkValidInput(key, value);
});
RCTAsyncStorage.multiSet(keyValuePairs, errors => {
const error = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys, callback) => {
return new Promise((resolve, reject) => {
keys.forEach(key => checkValidInput(key));
RCTAsyncStorage.multiRemove(keys, errors => {
const error = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs, callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge(keyValuePairs, errors => {
const error = convertErrors(errors);
callback === null || callback === void 0 ? void 0 : callback(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
}
};
})();
export default AsyncStorage;
//# sourceMappingURL=AsyncStorage.native.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,3 @@
import { TurboModuleRegistry } from "react-native";
export default TurboModuleRegistry.get("RNCAsyncStorage");
//# sourceMappingURL=NativeAsyncStorageModule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["TurboModuleRegistry","get"],"sources":["NativeAsyncStorageModule.ts"],"sourcesContent":["import type { TurboModule } from \"react-native\";\nimport { TurboModuleRegistry } from \"react-native\";\nimport type { ErrorLike } from \"./types\";\n\nexport interface Spec extends TurboModule {\n multiGet: (\n keys: string[],\n callback: (error?: ErrorLike[], result?: [string, string][]) => void\n ) => void;\n multiSet: (\n kvPairs: [string, string][],\n callback: (error?: ErrorLike[]) => void\n ) => void;\n multiRemove: (\n keys: readonly string[],\n callback: (error?: ErrorLike[]) => void\n ) => void;\n multiMerge: (\n kvPairs: [string, string][],\n callback: (error?: ErrorLike[]) => void\n ) => void;\n getAllKeys: (\n callback: (error?: ErrorLike[], result?: [string, string][]) => void\n ) => void;\n clear: (callback: (error?: ErrorLike[]) => void) => void;\n}\n\nexport default TurboModuleRegistry.get<Spec>(\"RNCAsyncStorage\");\n"],"mappings":"AACA,SAASA,mBAAmB,QAAQ,cAAc;AA0BlD,eAAeA,mBAAmB,CAACC,GAAG,CAAO,iBAAiB,CAAC"}

View File

@@ -0,0 +1,20 @@
import { NativeModules, TurboModuleRegistry } from "react-native";
import { shouldFallbackToLegacyNativeModule } from "./shouldFallbackToLegacyNativeModule";
// TurboModuleRegistry falls back to NativeModules so we don't have to try go
// assign NativeModules' counterparts if TurboModuleRegistry would resolve
// with undefined.
let RCTAsyncStorage = TurboModuleRegistry ? TurboModuleRegistry.get("PlatformLocalStorage") ||
// Support for external modules, like react-native-windows
TurboModuleRegistry.get("RNC_AsyncSQLiteDBStorage") || TurboModuleRegistry.get("RNCAsyncStorage") : NativeModules["PlatformLocalStorage"] ||
// Support for external modules, like react-native-windows
NativeModules["RNC_AsyncSQLiteDBStorage"] || NativeModules["RNCAsyncStorage"];
if (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {
if (TurboModuleRegistry) {
RCTAsyncStorage = TurboModuleRegistry.get("AsyncSQLiteDBStorage") || TurboModuleRegistry.get("AsyncLocalStorage");
} else {
RCTAsyncStorage = NativeModules["AsyncSQLiteDBStorage"] || NativeModules["AsyncLocalStorage"];
}
}
export default RCTAsyncStorage;
//# sourceMappingURL=RCTAsyncStorage.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["NativeModules","TurboModuleRegistry","shouldFallbackToLegacyNativeModule","RCTAsyncStorage","get"],"sources":["RCTAsyncStorage.ts"],"sourcesContent":["import { NativeModules, TurboModuleRegistry } from \"react-native\";\nimport { shouldFallbackToLegacyNativeModule } from \"./shouldFallbackToLegacyNativeModule\";\n\n// TurboModuleRegistry falls back to NativeModules so we don't have to try go\n// assign NativeModules' counterparts if TurboModuleRegistry would resolve\n// with undefined.\nlet RCTAsyncStorage = TurboModuleRegistry\n ? TurboModuleRegistry.get(\"PlatformLocalStorage\") || // Support for external modules, like react-native-windows\n TurboModuleRegistry.get(\"RNC_AsyncSQLiteDBStorage\") ||\n TurboModuleRegistry.get(\"RNCAsyncStorage\")\n : NativeModules[\"PlatformLocalStorage\"] || // Support for external modules, like react-native-windows\n NativeModules[\"RNC_AsyncSQLiteDBStorage\"] ||\n NativeModules[\"RNCAsyncStorage\"];\n\nif (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {\n if (TurboModuleRegistry) {\n RCTAsyncStorage =\n TurboModuleRegistry.get(\"AsyncSQLiteDBStorage\") ||\n TurboModuleRegistry.get(\"AsyncLocalStorage\");\n } else {\n RCTAsyncStorage =\n NativeModules[\"AsyncSQLiteDBStorage\"] ||\n NativeModules[\"AsyncLocalStorage\"];\n }\n}\n\nexport default RCTAsyncStorage;\n"],"mappings":"AAAA,SAASA,aAAa,EAAEC,mBAAmB,QAAQ,cAAc;AACjE,SAASC,kCAAkC,QAAQ,sCAAsC;;AAEzF;AACA;AACA;AACA,IAAIC,eAAe,GAAGF,mBAAmB,GACrCA,mBAAmB,CAACG,GAAG,CAAC,sBAAsB,CAAC;AAAI;AACnDH,mBAAmB,CAACG,GAAG,CAAC,0BAA0B,CAAC,IACnDH,mBAAmB,CAACG,GAAG,CAAC,iBAAiB,CAAC,GAC1CJ,aAAa,CAAC,sBAAsB,CAAC;AAAI;AACzCA,aAAa,CAAC,0BAA0B,CAAC,IACzCA,aAAa,CAAC,iBAAiB,CAAC;AAEpC,IAAI,CAACG,eAAe,IAAID,kCAAkC,CAAC,CAAC,EAAE;EAC5D,IAAID,mBAAmB,EAAE;IACvBE,eAAe,GACbF,mBAAmB,CAACG,GAAG,CAAC,sBAAsB,CAAC,IAC/CH,mBAAmB,CAACG,GAAG,CAAC,mBAAmB,CAAC;EAChD,CAAC,MAAM;IACLD,eAAe,GACbH,aAAa,CAAC,sBAAsB,CAAC,IACrCA,aAAa,CAAC,mBAAmB,CAAC;EACtC;AACF;AAEA,eAAeG,eAAe"}

View File

@@ -0,0 +1,48 @@
export function checkValidArgs(keyValuePairs, callback) {
if (!Array.isArray(keyValuePairs) || keyValuePairs.length === 0 || !Array.isArray(keyValuePairs[0])) {
throw new Error("[AsyncStorage] Expected array of key-value pairs as first argument to multiSet");
}
if (callback && typeof callback !== "function") {
if (Array.isArray(callback)) {
throw new Error("[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?");
}
throw new Error("[AsyncStorage] Expected function as second argument to multiSet");
}
}
export function checkValidInput(...input) {
const [key, value] = input;
if (typeof key !== "string") {
// eslint-disable-next-line no-console
console.warn(`[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${key}\n`);
}
if (input.length > 1 && typeof value !== "string") {
if (value == null) {
throw new Error(`[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\nPassed value: ${value}\nPassed key: ${key}\n`);
} else {
// eslint-disable-next-line no-console
console.warn(`[AsyncStorage] The value for key "${key}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${key}\n`);
}
}
}
export function convertError(error) {
if (!error) {
return null;
}
const out = new Error(error.message);
out["key"] = error.key;
return out;
}
export function convertErrors(errs) {
const errors = ensureArray(errs);
return errors ? errors.map(e => convertError(e)) : null;
}
function ensureArray(e) {
if (Array.isArray(e)) {
return e.length === 0 ? null : e;
} else if (e) {
return [e];
} else {
return null;
}
}
//# sourceMappingURL=helpers.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["checkValidArgs","keyValuePairs","callback","Array","isArray","length","Error","checkValidInput","input","key","value","console","warn","convertError","error","out","message","convertErrors","errs","errors","ensureArray","map","e"],"sources":["helpers.ts"],"sourcesContent":["import type { ErrorLike } from \"./types\";\n\nexport function checkValidArgs(\n keyValuePairs: readonly unknown[],\n callback: unknown\n) {\n if (\n !Array.isArray(keyValuePairs) ||\n keyValuePairs.length === 0 ||\n !Array.isArray(keyValuePairs[0])\n ) {\n throw new Error(\n \"[AsyncStorage] Expected array of key-value pairs as first argument to multiSet\"\n );\n }\n\n if (callback && typeof callback !== \"function\") {\n if (Array.isArray(callback)) {\n throw new Error(\n \"[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?\"\n );\n }\n\n throw new Error(\n \"[AsyncStorage] Expected function as second argument to multiSet\"\n );\n }\n}\n\nexport function checkValidInput(...input: unknown[]) {\n const [key, value] = input;\n\n if (typeof key !== \"string\") {\n // eslint-disable-next-line no-console\n console.warn(\n `[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\\nKey passed: ${key}\\n`\n );\n }\n\n if (input.length > 1 && typeof value !== \"string\") {\n if (value == null) {\n throw new Error(\n `[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n } else {\n // eslint-disable-next-line no-console\n console.warn(\n `[AsyncStorage] The value for key \"${key}\" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\\nPassed value: ${value}\\nPassed key: ${key}\\n`\n );\n }\n }\n}\n\nexport function convertError(error?: ErrorLike): Error | null {\n if (!error) {\n return null;\n }\n\n const out = new Error(error.message) as Error & ErrorLike;\n out[\"key\"] = error.key;\n return out;\n}\n\nexport function convertErrors(\n errs?: ErrorLike[]\n): ReadonlyArray<Error | null> | null {\n const errors = ensureArray(errs);\n return errors ? errors.map((e) => convertError(e)) : null;\n}\n\nfunction ensureArray(e?: ErrorLike | ErrorLike[]): ErrorLike[] | null {\n if (Array.isArray(e)) {\n return e.length === 0 ? null : e;\n } else if (e) {\n return [e];\n } else {\n return null;\n }\n}\n"],"mappings":"AAEA,OAAO,SAASA,cAAcA,CAC5BC,aAAiC,EACjCC,QAAiB,EACjB;EACA,IACE,CAACC,KAAK,CAACC,OAAO,CAACH,aAAa,CAAC,IAC7BA,aAAa,CAACI,MAAM,KAAK,CAAC,IAC1B,CAACF,KAAK,CAACC,OAAO,CAACH,aAAa,CAAC,CAAC,CAAC,CAAC,EAChC;IACA,MAAM,IAAIK,KAAK,CACb,gFACF,CAAC;EACH;EAEA,IAAIJ,QAAQ,IAAI,OAAOA,QAAQ,KAAK,UAAU,EAAE;IAC9C,IAAIC,KAAK,CAACC,OAAO,CAACF,QAAQ,CAAC,EAAE;MAC3B,MAAM,IAAII,KAAK,CACb,6IACF,CAAC;IACH;IAEA,MAAM,IAAIA,KAAK,CACb,iEACF,CAAC;EACH;AACF;AAEA,OAAO,SAASC,eAAeA,CAAC,GAAGC,KAAgB,EAAE;EACnD,MAAM,CAACC,GAAG,EAAEC,KAAK,CAAC,GAAGF,KAAK;EAE1B,IAAI,OAAOC,GAAG,KAAK,QAAQ,EAAE;IAC3B;IACAE,OAAO,CAACC,IAAI,CACT,wBAAuB,OAAOH,GAAI,iHAAgHA,GAAI,IACzJ,CAAC;EACH;EAEA,IAAID,KAAK,CAACH,MAAM,GAAG,CAAC,IAAI,OAAOK,KAAK,KAAK,QAAQ,EAAE;IACjD,IAAIA,KAAK,IAAI,IAAI,EAAE;MACjB,MAAM,IAAIJ,KAAK,CACZ,gJAA+II,KAAM,iBAAgBD,GAAI,IAC5K,CAAC;IACH,CAAC,MAAM;MACL;MACAE,OAAO,CAACC,IAAI,CACT,qCAAoCH,GAAI,4GAA2GC,KAAM,iBAAgBD,GAAI,IAChL,CAAC;IACH;EACF;AACF;AAEA,OAAO,SAASI,YAAYA,CAACC,KAAiB,EAAgB;EAC5D,IAAI,CAACA,KAAK,EAAE;IACV,OAAO,IAAI;EACb;EAEA,MAAMC,GAAG,GAAG,IAAIT,KAAK,CAACQ,KAAK,CAACE,OAAO,CAAsB;EACzDD,GAAG,CAAC,KAAK,CAAC,GAAGD,KAAK,CAACL,GAAG;EACtB,OAAOM,GAAG;AACZ;AAEA,OAAO,SAASE,aAAaA,CAC3BC,IAAkB,EACkB;EACpC,MAAMC,MAAM,GAAGC,WAAW,CAACF,IAAI,CAAC;EAChC,OAAOC,MAAM,GAAGA,MAAM,CAACE,GAAG,CAAEC,CAAC,IAAKT,YAAY,CAACS,CAAC,CAAC,CAAC,GAAG,IAAI;AAC3D;AAEA,SAASF,WAAWA,CAACE,CAA2B,EAAsB;EACpE,IAAInB,KAAK,CAACC,OAAO,CAACkB,CAAC,CAAC,EAAE;IACpB,OAAOA,CAAC,CAACjB,MAAM,KAAK,CAAC,GAAG,IAAI,GAAGiB,CAAC;EAClC,CAAC,MAAM,IAAIA,CAAC,EAAE;IACZ,OAAO,CAACA,CAAC,CAAC;EACZ,CAAC,MAAM;IACL,OAAO,IAAI;EACb;AACF"}

View File

@@ -0,0 +1,10 @@
import AsyncStorage from "./AsyncStorage";
export function useAsyncStorage(key) {
return {
getItem: (...args) => AsyncStorage.getItem(key, ...args),
setItem: (...args) => AsyncStorage.setItem(key, ...args),
mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),
removeItem: (...args) => AsyncStorage.removeItem(key, ...args)
};
}
//# sourceMappingURL=hooks.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["AsyncStorage","useAsyncStorage","key","getItem","args","setItem","mergeItem","removeItem"],"sources":["hooks.ts"],"sourcesContent":["import AsyncStorage from \"./AsyncStorage\";\nimport type { AsyncStorageHook } from \"./types\";\n\nexport function useAsyncStorage(key: string): AsyncStorageHook {\n return {\n getItem: (...args) => AsyncStorage.getItem(key, ...args),\n setItem: (...args) => AsyncStorage.setItem(key, ...args),\n mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),\n removeItem: (...args) => AsyncStorage.removeItem(key, ...args),\n };\n}\n"],"mappings":"AAAA,OAAOA,YAAY,MAAM,gBAAgB;AAGzC,OAAO,SAASC,eAAeA,CAACC,GAAW,EAAoB;EAC7D,OAAO;IACLC,OAAO,EAAEA,CAAC,GAAGC,IAAI,KAAKJ,YAAY,CAACG,OAAO,CAACD,GAAG,EAAE,GAAGE,IAAI,CAAC;IACxDC,OAAO,EAAEA,CAAC,GAAGD,IAAI,KAAKJ,YAAY,CAACK,OAAO,CAACH,GAAG,EAAE,GAAGE,IAAI,CAAC;IACxDE,SAAS,EAAEA,CAAC,GAAGF,IAAI,KAAKJ,YAAY,CAACM,SAAS,CAACJ,GAAG,EAAE,GAAGE,IAAI,CAAC;IAC5DG,UAAU,EAAEA,CAAC,GAAGH,IAAI,KAAKJ,YAAY,CAACO,UAAU,CAACL,GAAG,EAAE,GAAGE,IAAI;EAC/D,CAAC;AACH"}

View File

@@ -0,0 +1,4 @@
import AsyncStorage from "./AsyncStorage";
export { useAsyncStorage } from "./hooks";
export default AsyncStorage;
//# sourceMappingURL=index.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["AsyncStorage","useAsyncStorage"],"sources":["index.ts"],"sourcesContent":["import AsyncStorage from \"./AsyncStorage\";\n\nexport { useAsyncStorage } from \"./hooks\";\n\nexport type { AsyncStorageStatic } from \"./types\";\n\nexport default AsyncStorage;\n"],"mappings":"AAAA,OAAOA,YAAY,MAAM,gBAAgB;AAEzC,SAASC,eAAe,QAAQ,SAAS;AAIzC,eAAeD,YAAY"}

View File

@@ -0,0 +1,28 @@
import { NativeModules } from "react-native";
export function shouldFallbackToLegacyNativeModule() {
var _NativeModules$Native;
const expoConstants = (_NativeModules$Native = NativeModules["NativeUnimoduleProxy"]) === null || _NativeModules$Native === void 0 || (_NativeModules$Native = _NativeModules$Native.modulesConstants) === null || _NativeModules$Native === void 0 ? void 0 : _NativeModules$Native.ExponentConstants;
if (expoConstants) {
/**
* In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.
* In bare React Native apps using expo-constants, appOwnership is never defined, so
* isLegacySdkVersion will be false in that context.
*/
const isLegacySdkVersion = expoConstants.appOwnership && !expoConstants.executionEnvironment;
/**
* Expo managed apps don't include the @react-native-async-storage/async-storage
* native modules yet, but the API interface is the same, so we can use the version
* exported from React Native still.
*
* If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this
* will likely not be valid anymore, and the package will need to be included in the Expo SDK
* to continue to work.
*/
if (isLegacySdkVersion || ["storeClient", "standalone"].includes(expoConstants.executionEnvironment)) {
return true;
}
}
return false;
}
//# sourceMappingURL=shouldFallbackToLegacyNativeModule.js.map

View File

@@ -0,0 +1 @@
{"version":3,"names":["NativeModules","shouldFallbackToLegacyNativeModule","_NativeModules$Native","expoConstants","modulesConstants","ExponentConstants","isLegacySdkVersion","appOwnership","executionEnvironment","includes"],"sources":["shouldFallbackToLegacyNativeModule.ts"],"sourcesContent":["import { NativeModules } from \"react-native\";\n\nexport function shouldFallbackToLegacyNativeModule(): boolean {\n const expoConstants =\n NativeModules[\"NativeUnimoduleProxy\"]?.modulesConstants?.ExponentConstants;\n\n if (expoConstants) {\n /**\n * In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.\n * In bare React Native apps using expo-constants, appOwnership is never defined, so\n * isLegacySdkVersion will be false in that context.\n */\n const isLegacySdkVersion =\n expoConstants.appOwnership && !expoConstants.executionEnvironment;\n\n /**\n * Expo managed apps don't include the @react-native-async-storage/async-storage\n * native modules yet, but the API interface is the same, so we can use the version\n * exported from React Native still.\n *\n * If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this\n * will likely not be valid anymore, and the package will need to be included in the Expo SDK\n * to continue to work.\n */\n if (\n isLegacySdkVersion ||\n [\"storeClient\", \"standalone\"].includes(expoConstants.executionEnvironment)\n ) {\n return true;\n }\n }\n\n return false;\n}\n"],"mappings":"AAAA,SAASA,aAAa,QAAQ,cAAc;AAE5C,OAAO,SAASC,kCAAkCA,CAAA,EAAY;EAAA,IAAAC,qBAAA;EAC5D,MAAMC,aAAa,IAAAD,qBAAA,GACjBF,aAAa,CAAC,sBAAsB,CAAC,cAAAE,qBAAA,gBAAAA,qBAAA,GAArCA,qBAAA,CAAuCE,gBAAgB,cAAAF,qBAAA,uBAAvDA,qBAAA,CAAyDG,iBAAiB;EAE5E,IAAIF,aAAa,EAAE;IACjB;AACJ;AACA;AACA;AACA;IACI,MAAMG,kBAAkB,GACtBH,aAAa,CAACI,YAAY,IAAI,CAACJ,aAAa,CAACK,oBAAoB;;IAEnE;AACJ;AACA;AACA;AACA;AACA;AACA;AACA;AACA;IACI,IACEF,kBAAkB,IAClB,CAAC,aAAa,EAAE,YAAY,CAAC,CAACG,QAAQ,CAACN,aAAa,CAACK,oBAAoB,CAAC,EAC1E;MACA,OAAO,IAAI;IACb;EACF;EAEA,OAAO,KAAK;AACd"}

View File

@@ -0,0 +1,2 @@
//# sourceMappingURL=types.js.map

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { AsyncStorageStatic } from "./types";
declare const AsyncStorage: AsyncStorageStatic;
export default AsyncStorage;
//# sourceMappingURL=AsyncStorage.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AsyncStorage.d.ts","sourceRoot":"","sources":["../../src/AsyncStorage.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EACV,kBAAkB,EAGnB,MAAM,SAAS,CAAC;AA8DjB,QAAA,MAAM,YAAY,EAAE,kBAuGnB,CAAC;AAEF,eAAe,YAAY,CAAC"}

View File

@@ -0,0 +1,17 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import type { AsyncStorageStatic } from "./types";
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
declare const AsyncStorage: AsyncStorageStatic;
export default AsyncStorage;
//# sourceMappingURL=AsyncStorage.native.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"AsyncStorage.native.d.ts","sourceRoot":"","sources":["../../src/AsyncStorage.native.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EACV,kBAAkB,EAInB,MAAM,SAAS,CAAC;AAqBjB;;;;;;GAMG;AACH,QAAA,MAAM,YAAY,oBAkTd,CAAC;AAEL,eAAe,YAAY,CAAC"}

View File

@@ -0,0 +1,13 @@
import type { TurboModule } from "react-native";
import type { ErrorLike } from "./types";
export interface Spec extends TurboModule {
multiGet: (keys: string[], callback: (error?: ErrorLike[], result?: [string, string][]) => void) => void;
multiSet: (kvPairs: [string, string][], callback: (error?: ErrorLike[]) => void) => void;
multiRemove: (keys: readonly string[], callback: (error?: ErrorLike[]) => void) => void;
multiMerge: (kvPairs: [string, string][], callback: (error?: ErrorLike[]) => void) => void;
getAllKeys: (callback: (error?: ErrorLike[], result?: [string, string][]) => void) => void;
clear: (callback: (error?: ErrorLike[]) => void) => void;
}
declare const _default: Spec | null;
export default _default;
//# sourceMappingURL=NativeAsyncStorageModule.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"NativeAsyncStorageModule.d.ts","sourceRoot":"","sources":["../../src/NativeAsyncStorageModule.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,MAAM,WAAW,IAAK,SAAQ,WAAW;IACvC,QAAQ,EAAE,CACR,IAAI,EAAE,MAAM,EAAE,EACd,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,IAAI,KACjE,IAAI,CAAC;IACV,QAAQ,EAAE,CACR,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC3B,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,IAAI,KACpC,IAAI,CAAC;IACV,WAAW,EAAE,CACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,IAAI,KACpC,IAAI,CAAC;IACV,UAAU,EAAE,CACV,OAAO,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EAC3B,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,IAAI,KACpC,IAAI,CAAC;IACV,UAAU,EAAE,CACV,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,KAAK,IAAI,KACjE,IAAI,CAAC;IACV,KAAK,EAAE,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,EAAE,KAAK,IAAI,KAAK,IAAI,CAAC;CAC1D;;AAED,wBAAgE"}

View File

@@ -0,0 +1,3 @@
declare let RCTAsyncStorage: any;
export default RCTAsyncStorage;
//# sourceMappingURL=RCTAsyncStorage.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"RCTAsyncStorage.d.ts","sourceRoot":"","sources":["../../src/RCTAsyncStorage.ts"],"names":[],"mappings":"AAMA,QAAA,IAAI,eAAe,KAMiB,CAAC;AAcrC,eAAe,eAAe,CAAC"}

View File

@@ -0,0 +1,6 @@
import type { ErrorLike } from "./types";
export declare function checkValidArgs(keyValuePairs: readonly unknown[], callback: unknown): void;
export declare function checkValidInput(...input: unknown[]): void;
export declare function convertError(error?: ErrorLike): Error | null;
export declare function convertErrors(errs?: ErrorLike[]): ReadonlyArray<Error | null> | null;
//# sourceMappingURL=helpers.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/helpers.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAEzC,wBAAgB,cAAc,CAC5B,aAAa,EAAE,SAAS,OAAO,EAAE,EACjC,QAAQ,EAAE,OAAO,QAuBlB;AAED,wBAAgB,eAAe,CAAC,GAAG,KAAK,EAAE,OAAO,EAAE,QAsBlD;AAED,wBAAgB,YAAY,CAAC,KAAK,CAAC,EAAE,SAAS,GAAG,KAAK,GAAG,IAAI,CAQ5D;AAED,wBAAgB,aAAa,CAC3B,IAAI,CAAC,EAAE,SAAS,EAAE,GACjB,aAAa,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,IAAI,CAGpC"}

View File

@@ -0,0 +1,3 @@
import type { AsyncStorageHook } from "./types";
export declare function useAsyncStorage(key: string): AsyncStorageHook;
//# sourceMappingURL=hooks.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAEhD,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAO7D"}

View File

@@ -0,0 +1,5 @@
import AsyncStorage from "./AsyncStorage";
export { useAsyncStorage } from "./hooks";
export type { AsyncStorageStatic } from "./types";
export default AsyncStorage;
//# sourceMappingURL=index.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,YAAY,MAAM,gBAAgB,CAAC;AAE1C,OAAO,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE1C,YAAY,EAAE,kBAAkB,EAAE,MAAM,SAAS,CAAC;AAElD,eAAe,YAAY,CAAC"}

View File

@@ -0,0 +1,2 @@
export declare function shouldFallbackToLegacyNativeModule(): boolean;
//# sourceMappingURL=shouldFallbackToLegacyNativeModule.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"shouldFallbackToLegacyNativeModule.d.ts","sourceRoot":"","sources":["../../src/shouldFallbackToLegacyNativeModule.ts"],"names":[],"mappings":"AAEA,wBAAgB,kCAAkC,IAAI,OAAO,CA+B5D"}

View File

@@ -0,0 +1,114 @@
export type ErrorLike = {
message: string;
key?: string;
};
export type Callback = (error?: Error | null) => void;
export type CallbackWithResult<T> = (error?: Error | null, result?: T | null) => void;
export type KeyValuePair = [string, string | null];
export type MultiCallback = (errors?: readonly (Error | null)[] | null) => void;
export type MultiGetCallback = (errors?: readonly (Error | null)[] | null, result?: readonly KeyValuePair[]) => void;
export type MultiRequest = {
keys: readonly string[];
callback?: MultiGetCallback;
keyIndex: number;
resolve?: (result: readonly KeyValuePair[]) => void;
reject?: (error?: ErrorLike) => void;
};
export type AsyncStorageHook = {
getItem: (callback?: CallbackWithResult<string>) => Promise<string | null>;
setItem: (value: string, callback?: Callback) => Promise<void>;
mergeItem: (value: string, callback?: Callback) => Promise<void>;
removeItem: (callback?: Callback) => Promise<void>;
};
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
export type AsyncStorageStatic = {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key: string, callback?: CallbackWithResult<string>) => Promise<string | null>;
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key: string, callback?: Callback) => Promise<void>;
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: (callback?: Callback) => Promise<void>;
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: (callback?: CallbackWithResult<readonly string[]>) => Promise<readonly string[]>;
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => void;
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys: readonly string[], callback?: MultiGetCallback) => Promise<readonly KeyValuePair[]>;
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs: ReadonlyArray<readonly [string, string]>, callback?: MultiCallback) => Promise<void>;
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys: readonly string[], callback?: MultiCallback) => Promise<void>;
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs: [string, string][], callback?: MultiCallback) => Promise<void>;
};
//# sourceMappingURL=types.d.ts.map

View File

@@ -0,0 +1 @@
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GAAG;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,KAAK,IAAI,CAAC;AAEtD,MAAM,MAAM,kBAAkB,CAAC,CAAC,IAAI,CAClC,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,EACpB,MAAM,CAAC,EAAE,CAAC,GAAG,IAAI,KACd,IAAI,CAAC;AAEV,MAAM,MAAM,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,CAAC;AAEnD,MAAM,MAAM,aAAa,GAAG,CAAC,MAAM,CAAC,EAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,KAAK,IAAI,CAAC;AAEhF,MAAM,MAAM,gBAAgB,GAAG,CAC7B,MAAM,CAAC,EAAE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,GAAG,IAAI,EACzC,MAAM,CAAC,EAAE,SAAS,YAAY,EAAE,KAC7B,IAAI,CAAC;AAEV,MAAM,MAAM,YAAY,GAAG;IACzB,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,SAAS,YAAY,EAAE,KAAK,IAAI,CAAC;IACpD,MAAM,CAAC,EAAE,CAAC,KAAK,CAAC,EAAE,SAAS,KAAK,IAAI,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,OAAO,EAAE,CAAC,QAAQ,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC3E,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,SAAS,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjE,UAAU,EAAE,CAAC,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,GAAG;IAC/B;;;;OAIG;IACH,OAAO,EAAE,CACP,GAAG,EAAE,MAAM,EACX,QAAQ,CAAC,EAAE,kBAAkB,CAAC,MAAM,CAAC,KAClC,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAE5B;;;;OAIG;IACH,OAAO,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5E;;;;OAIG;IACH,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhE;;;;;OAKG;IACH,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E;;;;;;OAMG;IACH,KAAK,EAAE,CAAC,QAAQ,CAAC,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9C;;;;OAIG;IACH,UAAU,EAAE,CACV,QAAQ,CAAC,EAAE,kBAAkB,CAAC,SAAS,MAAM,EAAE,CAAC,KAC7C,OAAO,CAAC,SAAS,MAAM,EAAE,CAAC,CAAC;IAEhC;;;;;;;;OAQG;IAEH;;;;SAIK;IACL,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAE7B;;;;;;OAMG;IACH,QAAQ,EAAE,CACR,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,CAAC,EAAE,gBAAgB,KACxB,OAAO,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IAEtC;;;;;OAKG;IACH,QAAQ,EAAE,CACR,aAAa,EAAE,aAAa,CAAC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACvD,QAAQ,CAAC,EAAE,aAAa,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB;;;;OAIG;IACH,WAAW,EAAE,CACX,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,CAAC,EAAE,aAAa,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;IAEnB;;;;;OAKG;IACH,UAAU,EAAE,CACV,aAAa,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,EACjC,QAAQ,CAAC,EAAE,aAAa,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;CACpB,CAAC"}

View File

@@ -0,0 +1,385 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
3893A2E123C509D1009200E3 /* RNCAsyncStorage.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
3893A2E223C509D1009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
3893A2E523C50AFE009200E3 /* RNCAsyncStorage.h in Headers */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
3893A2E623C50AFE009200E3 /* RNCAsyncStorageDelegate.h in Headers */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
3893A2E823C50AFE009200E3 /* RNCAsyncStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */; };
3893A2EB23C50AFE009200E3 /* RNCAsyncStorage.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */; };
3893A2EC23C50AFE009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */; };
B3E7B58A1CC2AC0600A0062D /* RNCAsyncStorage.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
3893A2EA23C50AFE009200E3 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = include/RNCAsyncStorage;
dstSubfolderSpec = 16;
files = (
3893A2EB23C50AFE009200E3 /* RNCAsyncStorage.h in CopyFiles */,
3893A2EC23C50AFE009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D91A9E6C8500147676 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
3893A2E123C509D1009200E3 /* RNCAsyncStorage.h in CopyFiles */,
3893A2E223C509D1009200E3 /* RNCAsyncStorageDelegate.h in CopyFiles */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRNCAsyncStorage.a; sourceTree = BUILT_PRODUCTS_DIR; };
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCAsyncStorageDelegate.h; path = ../ios/RNCAsyncStorageDelegate.h; sourceTree = "<group>"; };
3893A2F023C50AFE009200E3 /* libRNCAsyncStorage-macOS.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libRNCAsyncStorage-macOS.a"; sourceTree = BUILT_PRODUCTS_DIR; };
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RNCAsyncStorage.h; path = ../ios/RNCAsyncStorage.h; sourceTree = "<group>"; };
B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RNCAsyncStorage.m; path = ../ios/RNCAsyncStorage.m; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
3893A2E923C50AFE009200E3 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D81A9E6C8500147676 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
134814211AA4EA7D00B7C361 /* Products */ = {
isa = PBXGroup;
children = (
134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */,
);
name = Products;
sourceTree = "<group>";
};
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
B3E7B5881CC2AC0600A0062D /* RNCAsyncStorage.h */,
B3E7B5891CC2AC0600A0062D /* RNCAsyncStorage.m */,
1990B9402233FE3A009E5EA1 /* RNCAsyncStorageDelegate.h */,
134814211AA4EA7D00B7C361 /* Products */,
3893A2F023C50AFE009200E3 /* libRNCAsyncStorage-macOS.a */,
);
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXHeadersBuildPhase section */
19F94B1D2239A948006921A9 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
1990B97A223993B0009E5EA1 /* RNCAsyncStorage.h in Headers */,
1990B97B223993B0009E5EA1 /* RNCAsyncStorageDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
3893A2E423C50AFE009200E3 /* Headers */ = {
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
3893A2E523C50AFE009200E3 /* RNCAsyncStorage.h in Headers */,
3893A2E623C50AFE009200E3 /* RNCAsyncStorageDelegate.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXHeadersBuildPhase section */
/* Begin PBXNativeTarget section */
3893A2E323C50AFE009200E3 /* RNCAsyncStorage-macOS */ = {
isa = PBXNativeTarget;
buildConfigurationList = 3893A2ED23C50AFE009200E3 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage-macOS" */;
buildPhases = (
3893A2E423C50AFE009200E3 /* Headers */,
3893A2E723C50AFE009200E3 /* Sources */,
3893A2E923C50AFE009200E3 /* Frameworks */,
3893A2EA23C50AFE009200E3 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = "RNCAsyncStorage-macOS";
productName = RCTDataManager;
productReference = 3893A2F023C50AFE009200E3 /* libRNCAsyncStorage-macOS.a */;
productType = "com.apple.product-type.library.static";
};
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */ = {
isa = PBXNativeTarget;
buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */;
buildPhases = (
19F94B1D2239A948006921A9 /* Headers */,
58B511D71A9E6C8500147676 /* Sources */,
58B511D81A9E6C8500147676 /* Frameworks */,
58B511D91A9E6C8500147676 /* CopyFiles */,
);
buildRules = (
);
dependencies = (
);
name = RNCAsyncStorage;
productName = RCTDataManager;
productReference = 134814201AA4EA6300B7C361 /* libRNCAsyncStorage.a */;
productType = "com.apple.product-type.library.static";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
58B511D31A9E6C8500147676 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = Facebook;
TargetAttributes = {
58B511DA1A9E6C8500147676 = {
CreatedOnToolsVersion = 6.1.1;
};
};
};
buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = English;
hasScannedForEncodings = 0;
knownRegions = (
English,
en,
);
mainGroup = 58B511D21A9E6C8500147676;
productRefGroup = 58B511D21A9E6C8500147676;
projectDirPath = "";
projectRoot = "";
targets = (
58B511DA1A9E6C8500147676 /* RNCAsyncStorage */,
3893A2E323C50AFE009200E3 /* RNCAsyncStorage-macOS */,
);
};
/* End PBXProject section */
/* Begin PBXSourcesBuildPhase section */
3893A2E723C50AFE009200E3 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
3893A2E823C50AFE009200E3 /* RNCAsyncStorage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
58B511D71A9E6C8500147676 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
B3E7B58A1CC2AC0600A0062D /* RNCAsyncStorage.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
3893A2EE23C50AFE009200E3 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Debug;
};
3893A2EF23C50AFE009200E3 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
"$(SRCROOT)/../node_modules/react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
MACOSX_DEPLOYMENT_TARGET = 10.10;
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = "$(TARGET_NAME)";
SDKROOT = macosx;
SKIP_INSTALL = YES;
};
name = Release;
};
58B511ED1A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
58B511EE1A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
58B511F01A9E6C8500147676 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Debug;
};
58B511F11A9E6C8500147676 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
HEADER_SEARCH_PATHS = (
"$(inherited)",
/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
"$(SRCROOT)/../../../React/**",
"$(SRCROOT)/../../react-native/React/**",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
OTHER_LDFLAGS = "-ObjC";
PRODUCT_NAME = RNCAsyncStorage;
SKIP_INSTALL = YES;
SUPPORTED_PLATFORMS = "iphonesimulator iphoneos appletvsimulator appletvos";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
3893A2ED23C50AFE009200E3 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage-macOS" */ = {
isa = XCConfigurationList;
buildConfigurations = (
3893A2EE23C50AFE009200E3 /* Debug */,
3893A2EF23C50AFE009200E3 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511ED1A9E6C8500147676 /* Debug */,
58B511EE1A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RNCAsyncStorage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
58B511F01A9E6C8500147676 /* Debug */,
58B511F11A9E6C8500147676 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 58B511D31A9E6C8500147676 /* Project object */;
}

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3893A2E323C50AFE009200E3"
BuildableName = "libRNCAsyncStorage-macOS.a"
BlueprintName = "RNCAsyncStorage-macOS"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "3893A2E323C50AFE009200E3"
BuildableName = "libRNCAsyncStorage-macOS.a"
BlueprintName = "RNCAsyncStorage-macOS"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1120"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
BuildableName = "libRNCAsyncStorage.a"
BlueprintName = "RNCAsyncStorage"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "58B511DA1A9E6C8500147676"
BuildableName = "libRNCAsyncStorage.a"
BlueprintName = "RNCAsyncStorage"
ReferencedContainer = "container:RNCAsyncStorage.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,127 @@
{
"name": "@react-native-async-storage/async-storage",
"version": "1.24.0",
"description": "Asynchronous, persistent, key-value storage system for React Native.",
"main": "lib/commonjs/index.js",
"module": "lib/module/index.js",
"react-native": "src/index.ts",
"types": "lib/typescript/index.d.ts",
"files": [
"RNCAsyncStorage.podspec",
"android/",
"!android/.gradle",
"!android/build",
"ios/",
"jest/",
"lib/",
"macos/",
"src/",
"windows/"
],
"author": "Krzysztof Borowy <contact@kborowy.com>",
"contributors": [
"Evan Bacon <bacon@expo.io> (https://github.com/evanbacon)",
"Tommy Nguyen <4123478+tido64@users.noreply.github.com> (https://github.com/tido64)"
],
"homepage": "https://github.com/react-native-async-storage/async-storage#readme",
"license": "MIT",
"keywords": [
"react-native",
"react native",
"async storage",
"asyncstorage",
"storage"
],
"repository": {
"type": "git",
"url": "https://github.com/react-native-async-storage/async-storage.git",
"directory": "packages/default-storage-backend"
},
"scripts": {
"prepack": "yarn build",
"build": "bob build",
"start": "react-native start",
"start:android": "react-native run-android",
"start:ios": "react-native run-ios",
"start:macos": "react-native run-macos --project-path example/macos --scheme AsyncStorageExample",
"start:web": "expo start --web",
"start:windows": "install-windows-test-app -p example/windows && react-native run-windows --root example --logging --no-packager --no-telemetry",
"build:e2e:android": "scripts/android_e2e.sh 'build'",
"build:e2e:ios": "scripts/ios_e2e.sh 'build'",
"build:e2e:macos": "scripts/macos_e2e.sh 'build'",
"bundle:android": "scripts/android_e2e.sh 'bundle'",
"bundle:ios": "scripts/ios_e2e.sh 'bundle'",
"bundle:macos": "react-native bundle --entry-file index.ts --platform macos --dev false --bundle-output example/index.macos.jsbundle",
"test": "concurrently -n lint,ts yarn:test:lint yarn:test:ts",
"test:lint": "eslint $(git ls-files '*.js' '*.ts' '*.tsx')",
"test:ts": "tsc",
"test:e2e:android": "scripts/android_e2e.sh 'test'",
"test:e2e:ios": "scripts/ios_e2e.sh 'test'",
"test:e2e:macos": "scripts/macos_e2e.sh 'test'"
},
"installConfig": {
"hoistingLimits": "workspaces"
},
"dependencies": {
"merge-options": "^3.0.4"
},
"peerDependencies": {
"react-native": "^0.0.0-0 || >=0.60 <1.0"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@react-native/babel-preset": "^0.73.19",
"@react-native/metro-config": "^0.73.3",
"@rnx-kit/metro-config": "^1.3.15",
"@types/lodash": "^4.14.184",
"@types/mocha": "^10.0.1",
"@types/react": "^18.0.0",
"@wdio/appium-service": "^8.24.0",
"@wdio/cli": "^8.24.0",
"@wdio/local-runner": "^8.24.0",
"@wdio/mocha-framework": "^8.11.0",
"@wdio/spec-reporter": "^8.24.0",
"appium": "2.2.2",
"appium-uiautomator2-driver": "^2.34.1",
"appium-xcuitest-driver": "^5.9.1",
"concurrently": "^8.2.2",
"eslint": "^8.54.0",
"expo": "^48.0.0",
"lodash": "^4.17.21",
"prettier": "2.8.8",
"react": "18.2.0",
"react-dom": "^18.2.0",
"react-native": "^0.73.0",
"react-native-builder-bob": "^0.18.0",
"react-native-macos": "^0.73.0",
"react-native-test-app": "^3.7.3",
"react-native-web": "~0.18.10",
"react-native-windows": "^0.73.0",
"react-test-renderer": "18.2.0",
"typescript": "^5.3.0",
"webdriverio": "^8.24.0"
},
"react-native-builder-bob": {
"source": "src",
"output": "lib",
"targets": [
"commonjs",
"module",
[
"typescript",
{
"project": "tsconfig.build.json"
}
]
]
},
"codegenConfig": {
"name": "rnasyncstorage",
"type": "modules",
"jsSrcsDir": "./src",
"android": {
"javaPackageName": "com.reactnativecommunity.asyncstorage"
}
}
}

View File

@@ -0,0 +1,356 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {
checkValidArgs,
checkValidInput,
convertError,
convertErrors,
} from "./helpers";
import RCTAsyncStorage from "./RCTAsyncStorage";
import type {
AsyncStorageStatic,
ErrorLike,
KeyValuePair,
MultiRequest,
} from "./types";
if (!RCTAsyncStorage) {
throw new Error(`[@RNC/AsyncStorage]: NativeModule: AsyncStorage is null.
To fix this issue try these steps:
• Uninstall, rebuild and restart the app.
• Run the packager with \`--reset-cache\` flag.
• If you are using CocoaPods on iOS, run \`pod install\` in the \`ios\` directory, then rebuild and re-run the app.
• Make sure your project's \`package.json\` depends on \`@react-native-async-storage/async-storage\`, even if you only depend on it indirectly through other dependencies. CLI only autolinks native modules found in your \`package.json\`.
• If this happens while testing with Jest, check out how to integrate AsyncStorage here: https://react-native-async-storage.github.io/async-storage/docs/advanced/jest
If none of these fix the issue, please open an issue on the GitHub repository: https://github.com/react-native-async-storage/async-storage/issues
`);
}
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
const AsyncStorage = ((): AsyncStorageStatic => {
let _getRequests: MultiRequest[] = [];
let _getKeys: string[] = [];
let _immediate: ReturnType<typeof setImmediate> | null = null;
return {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiGet(
[key],
(errors?: ErrorLike[], result?: string[][]) => {
// Unpack result to get value from [[key,value]]
const value = result?.[0]?.[1] ? result[0][1] : null;
const errs = convertErrors(errors);
callback?.(errs?.[0], value);
if (errs) {
reject(errs[0]);
} else {
resolve(value);
}
}
);
});
},
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiSet([[key, value]], (errors?: ErrorLike[]) => {
const errs = convertErrors(errors);
callback?.(errs?.[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key);
RCTAsyncStorage.multiRemove([key], (errors?: ErrorLike[]) => {
const errs = convertErrors(errors);
callback?.(errs?.[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key, value, callback) => {
return new Promise((resolve, reject) => {
checkValidInput(key, value);
RCTAsyncStorage.multiMerge([[key, value]], (errors?: ErrorLike[]) => {
const errs = convertErrors(errors);
callback?.(errs?.[0]);
if (errs) {
reject(errs[0]);
} else {
resolve();
}
});
});
},
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: (callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.clear((error?: ErrorLike) => {
const err = convertError(error);
callback?.(err);
if (err) {
reject(err);
} else {
resolve();
}
});
});
},
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: (callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.getAllKeys((error?: ErrorLike, keys?: string[]) => {
const err = convertError(error);
callback?.(err, keys);
if (keys) {
resolve(keys);
} else {
reject(err);
}
});
});
},
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => {
const getRequests = _getRequests;
const getKeys = _getKeys;
_getRequests = [];
_getKeys = [];
RCTAsyncStorage.multiGet(
getKeys,
(errors?: ErrorLike[], result?: string[][]) => {
// Even though the runtime complexity of this is theoretically worse vs if we used a map,
// it's much, much faster in practice for the data sets we deal with (we avoid
// allocating result pair arrays). This was heavily benchmarked.
//
// Is there a way to avoid using the map but fix the bug in this breaking test?
// https://github.com/facebook/react-native/commit/8dd8ad76579d7feef34c014d387bf02065692264
const map: Record<string, string> = {};
result?.forEach(([key, value]) => {
map[key] = value;
return value;
});
const reqLength = getRequests.length;
/**
* As mentioned few lines above, this method could be called with the array of potential error,
* in case of anything goes wrong. The problem is, if any of the batched calls fails
* the rest of them would fail too, but the error would be consumed by just one. The rest
* would simply return `undefined` as their result, rendering false negatives.
*
* In order to avoid this situation, in case of any call failing,
* the rest of them will be rejected as well (with the same error).
*/
const errorList = convertErrors(errors);
const error = errorList?.length ? errorList[0] : null;
for (let i = 0; i < reqLength; i++) {
const request = getRequests[i];
if (error) {
request.callback?.(errorList);
request.reject?.(error);
continue;
}
const requestResult = request.keys.map<KeyValuePair>((key) => [
key,
map[key],
]);
request.callback?.(null, requestResult);
request.resolve?.(requestResult);
}
}
);
},
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (keys, callback) => {
if (!_immediate) {
_immediate = setImmediate(() => {
_immediate = null;
AsyncStorage.flushGetRequests();
});
}
const getRequest: MultiRequest = {
keys: keys,
callback: callback,
// do we need this?
keyIndex: _getKeys.length,
};
const promiseResult = new Promise<readonly KeyValuePair[]>(
(resolve, reject) => {
getRequest.resolve = resolve;
getRequest.reject = reject;
}
);
_getRequests.push(getRequest);
// avoid fetching duplicates
keys.forEach((key) => {
if (_getKeys.indexOf(key) === -1) {
_getKeys.push(key);
}
});
return promiseResult;
},
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (keyValuePairs, callback) => {
checkValidArgs(keyValuePairs, callback);
return new Promise((resolve, reject) => {
keyValuePairs.forEach(([key, value]) => {
checkValidInput(key, value);
});
RCTAsyncStorage.multiSet(keyValuePairs, (errors?: ErrorLike[]) => {
const error = convertErrors(errors);
callback?.(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (keys, callback) => {
return new Promise((resolve, reject) => {
keys.forEach((key) => checkValidInput(key));
RCTAsyncStorage.multiRemove(keys, (errors?: ErrorLike[]) => {
const error = convertErrors(errors);
callback?.(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (keyValuePairs, callback) => {
return new Promise((resolve, reject) => {
RCTAsyncStorage.multiMerge(keyValuePairs, (errors?: ErrorLike[]) => {
const error = convertErrors(errors);
callback?.(error);
if (error) {
reject(error);
} else {
resolve();
}
});
});
},
};
})();
export default AsyncStorage;

View File

@@ -0,0 +1,181 @@
/**
* Copyright (c) Nicolas Gallagher.
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import mergeOptions from "merge-options";
import type {
AsyncStorageStatic,
MultiCallback,
MultiGetCallback,
} from "./types";
// eslint-disable-next-line @typescript-eslint/ban-types
type OnMultiResult = Function;
// eslint-disable-next-line @typescript-eslint/ban-types
type OnResult = Function;
const merge = mergeOptions.bind({
concatArrays: true,
ignoreUndefined: true,
});
function mergeLocalStorageItem(key: string, value: string) {
const oldValue = window.localStorage.getItem(key);
if (oldValue) {
const oldObject = JSON.parse(oldValue);
const newObject = JSON.parse(value);
const nextValue = JSON.stringify(merge(oldObject, newObject));
window.localStorage.setItem(key, nextValue);
} else {
window.localStorage.setItem(key, value);
}
}
function createPromise<Result, Callback extends OnResult>(
getValue: () => Result,
callback?: Callback
): Promise<Result> {
return new Promise((resolve, reject) => {
try {
const value = getValue();
callback?.(null, value);
resolve(value);
} catch (err) {
callback?.(err);
reject(err);
}
});
}
function createPromiseAll<
ReturnType,
Result,
ResultProcessor extends OnMultiResult
>(
promises: Promise<Result>[],
callback?: MultiCallback | MultiGetCallback,
processResult?: ResultProcessor
): Promise<ReturnType> {
return Promise.all(promises).then(
(result) => {
const value = processResult?.(result) ?? null;
callback?.(null, value);
return Promise.resolve(value);
},
(errors) => {
callback?.(errors);
return Promise.reject(errors);
}
);
}
const AsyncStorage: AsyncStorageStatic = {
/**
* Fetches `key` value.
*/
getItem: (key, callback) => {
return createPromise(() => window.localStorage.getItem(key), callback);
},
/**
* Sets `value` for `key`.
*/
setItem: (key, value, callback) => {
return createPromise(
() => window.localStorage.setItem(key, value),
callback
);
},
/**
* Removes a `key`
*/
removeItem: (key, callback) => {
return createPromise(() => window.localStorage.removeItem(key), callback);
},
/**
* Merges existing value with input value, assuming they are stringified JSON.
*/
mergeItem: (key, value, callback) => {
return createPromise(() => mergeLocalStorageItem(key, value), callback);
},
/**
* Erases *all* AsyncStorage for the domain.
*/
clear: (callback) => {
return createPromise(() => window.localStorage.clear(), callback);
},
/**
* Gets *all* keys known to the app, for all callers, libraries, etc.
*/
getAllKeys: (callback) => {
return createPromise(() => {
const numberOfKeys = window.localStorage.length;
const keys: string[] = [];
for (let i = 0; i < numberOfKeys; i += 1) {
const key = window.localStorage.key(i) || "";
keys.push(key);
}
return keys;
}, callback);
},
/**
* (stub) Flushes any pending requests using a single batch call to get the data.
*/
flushGetRequests: () => undefined,
/**
* multiGet resolves to an array of key-value pair arrays that matches the
* input format of multiSet.
*
* multiGet(['k1', 'k2']) -> [['k1', 'val1'], ['k2', 'val2']]
*/
multiGet: (keys, callback) => {
const promises = keys.map((key) => AsyncStorage.getItem(key));
const processResult = (result: string[]) =>
result.map((value, i) => [keys[i], value]);
return createPromiseAll(promises, callback, processResult);
},
/**
* Takes an array of key-value array pairs.
* multiSet([['k1', 'val1'], ['k2', 'val2']])
*/
multiSet: (keyValuePairs, callback) => {
const promises = keyValuePairs.map((item) =>
AsyncStorage.setItem(item[0], item[1])
);
return createPromiseAll(promises, callback);
},
/**
* Delete all the keys in the `keys` array.
*/
multiRemove: (keys, callback) => {
const promises = keys.map((key) => AsyncStorage.removeItem(key));
return createPromiseAll(promises, callback);
},
/**
* Takes an array of key-value array pairs and merges them with existing
* values, assuming they are stringified JSON.
*
* multiMerge([['k1', 'val1'], ['k2', 'val2']])
*/
multiMerge: (keyValuePairs, callback) => {
const promises = keyValuePairs.map((item) =>
AsyncStorage.mergeItem(item[0], item[1])
);
return createPromiseAll(promises, callback);
},
};
export default AsyncStorage;

View File

@@ -0,0 +1,28 @@
import type { TurboModule } from "react-native";
import { TurboModuleRegistry } from "react-native";
import type { ErrorLike } from "./types";
export interface Spec extends TurboModule {
multiGet: (
keys: string[],
callback: (error?: ErrorLike[], result?: [string, string][]) => void
) => void;
multiSet: (
kvPairs: [string, string][],
callback: (error?: ErrorLike[]) => void
) => void;
multiRemove: (
keys: readonly string[],
callback: (error?: ErrorLike[]) => void
) => void;
multiMerge: (
kvPairs: [string, string][],
callback: (error?: ErrorLike[]) => void
) => void;
getAllKeys: (
callback: (error?: ErrorLike[], result?: [string, string][]) => void
) => void;
clear: (callback: (error?: ErrorLike[]) => void) => void;
}
export default TurboModuleRegistry.get<Spec>("RNCAsyncStorage");

View File

@@ -0,0 +1,27 @@
import { NativeModules, TurboModuleRegistry } from "react-native";
import { shouldFallbackToLegacyNativeModule } from "./shouldFallbackToLegacyNativeModule";
// TurboModuleRegistry falls back to NativeModules so we don't have to try go
// assign NativeModules' counterparts if TurboModuleRegistry would resolve
// with undefined.
let RCTAsyncStorage = TurboModuleRegistry
? TurboModuleRegistry.get("PlatformLocalStorage") || // Support for external modules, like react-native-windows
TurboModuleRegistry.get("RNC_AsyncSQLiteDBStorage") ||
TurboModuleRegistry.get("RNCAsyncStorage")
: NativeModules["PlatformLocalStorage"] || // Support for external modules, like react-native-windows
NativeModules["RNC_AsyncSQLiteDBStorage"] ||
NativeModules["RNCAsyncStorage"];
if (!RCTAsyncStorage && shouldFallbackToLegacyNativeModule()) {
if (TurboModuleRegistry) {
RCTAsyncStorage =
TurboModuleRegistry.get("AsyncSQLiteDBStorage") ||
TurboModuleRegistry.get("AsyncLocalStorage");
} else {
RCTAsyncStorage =
NativeModules["AsyncSQLiteDBStorage"] ||
NativeModules["AsyncLocalStorage"];
}
}
export default RCTAsyncStorage;

View File

@@ -0,0 +1,79 @@
import type { ErrorLike } from "./types";
export function checkValidArgs(
keyValuePairs: readonly unknown[],
callback: unknown
) {
if (
!Array.isArray(keyValuePairs) ||
keyValuePairs.length === 0 ||
!Array.isArray(keyValuePairs[0])
) {
throw new Error(
"[AsyncStorage] Expected array of key-value pairs as first argument to multiSet"
);
}
if (callback && typeof callback !== "function") {
if (Array.isArray(callback)) {
throw new Error(
"[AsyncStorage] Expected function as second argument to multiSet. Did you forget to wrap key-value pairs in an array for the first argument?"
);
}
throw new Error(
"[AsyncStorage] Expected function as second argument to multiSet"
);
}
}
export function checkValidInput(...input: unknown[]) {
const [key, value] = input;
if (typeof key !== "string") {
// eslint-disable-next-line no-console
console.warn(
`[AsyncStorage] Using ${typeof key} type for key is not supported. This can lead to unexpected behavior/errors. Use string instead.\nKey passed: ${key}\n`
);
}
if (input.length > 1 && typeof value !== "string") {
if (value == null) {
throw new Error(
`[AsyncStorage] Passing null/undefined as value is not supported. If you want to remove value, Use .removeItem method instead.\nPassed value: ${value}\nPassed key: ${key}\n`
);
} else {
// eslint-disable-next-line no-console
console.warn(
`[AsyncStorage] The value for key "${key}" is not a string. This can lead to unexpected behavior/errors. Consider stringifying it.\nPassed value: ${value}\nPassed key: ${key}\n`
);
}
}
}
export function convertError(error?: ErrorLike): Error | null {
if (!error) {
return null;
}
const out = new Error(error.message) as Error & ErrorLike;
out["key"] = error.key;
return out;
}
export function convertErrors(
errs?: ErrorLike[]
): ReadonlyArray<Error | null> | null {
const errors = ensureArray(errs);
return errors ? errors.map((e) => convertError(e)) : null;
}
function ensureArray(e?: ErrorLike | ErrorLike[]): ErrorLike[] | null {
if (Array.isArray(e)) {
return e.length === 0 ? null : e;
} else if (e) {
return [e];
} else {
return null;
}
}

View File

@@ -0,0 +1,11 @@
import AsyncStorage from "./AsyncStorage";
import type { AsyncStorageHook } from "./types";
export function useAsyncStorage(key: string): AsyncStorageHook {
return {
getItem: (...args) => AsyncStorage.getItem(key, ...args),
setItem: (...args) => AsyncStorage.setItem(key, ...args),
mergeItem: (...args) => AsyncStorage.mergeItem(key, ...args),
removeItem: (...args) => AsyncStorage.removeItem(key, ...args),
};
}

View File

@@ -0,0 +1,7 @@
import AsyncStorage from "./AsyncStorage";
export { useAsyncStorage } from "./hooks";
export type { AsyncStorageStatic } from "./types";
export default AsyncStorage;

View File

@@ -0,0 +1,34 @@
import { NativeModules } from "react-native";
export function shouldFallbackToLegacyNativeModule(): boolean {
const expoConstants =
NativeModules["NativeUnimoduleProxy"]?.modulesConstants?.ExponentConstants;
if (expoConstants) {
/**
* In SDK <= 39, appOwnership is defined in managed apps but executionEnvironment is not.
* In bare React Native apps using expo-constants, appOwnership is never defined, so
* isLegacySdkVersion will be false in that context.
*/
const isLegacySdkVersion =
expoConstants.appOwnership && !expoConstants.executionEnvironment;
/**
* Expo managed apps don't include the @react-native-async-storage/async-storage
* native modules yet, but the API interface is the same, so we can use the version
* exported from React Native still.
*
* If in future releases (eg: @react-native-async-storage/async-storage >= 2.0.0) this
* will likely not be valid anymore, and the package will need to be included in the Expo SDK
* to continue to work.
*/
if (
isLegacySdkVersion ||
["storeClient", "standalone"].includes(expoConstants.executionEnvironment)
) {
return true;
}
}
return false;
}

View File

@@ -0,0 +1,155 @@
export type ErrorLike = {
message: string;
key?: string;
};
export type Callback = (error?: Error | null) => void;
export type CallbackWithResult<T> = (
error?: Error | null,
result?: T | null
) => void;
export type KeyValuePair = [string, string | null];
export type MultiCallback = (errors?: readonly (Error | null)[] | null) => void;
export type MultiGetCallback = (
errors?: readonly (Error | null)[] | null,
result?: readonly KeyValuePair[]
) => void;
export type MultiRequest = {
keys: readonly string[];
callback?: MultiGetCallback;
keyIndex: number;
resolve?: (result: readonly KeyValuePair[]) => void;
reject?: (error?: ErrorLike) => void;
};
export type AsyncStorageHook = {
getItem: (callback?: CallbackWithResult<string>) => Promise<string | null>;
setItem: (value: string, callback?: Callback) => Promise<void>;
mergeItem: (value: string, callback?: Callback) => Promise<void>;
removeItem: (callback?: Callback) => Promise<void>;
};
/**
* `AsyncStorage` is a simple, unencrypted, asynchronous, persistent, key-value
* storage system that is global to the app. It should be used instead of
* LocalStorage.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api
*/
export type AsyncStorageStatic = {
/**
* Fetches an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getitem
*/
getItem: (
key: string,
callback?: CallbackWithResult<string>
) => Promise<string | null>;
/**
* Sets the value for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#setitem
*/
setItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Removes an item for a `key` and invokes a callback upon completion.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#removeitem
*/
removeItem: (key: string, callback?: Callback) => Promise<void>;
/**
* Merges an existing `key` value with an input value, assuming both values
* are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#mergeitem
*/
mergeItem: (key: string, value: string, callback?: Callback) => Promise<void>;
/**
* Erases *all* `AsyncStorage` for all clients, libraries, etc. You probably
* don't want to call this; use `removeItem` or `multiRemove` to clear only
* your app's keys.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#clear
*/
clear: (callback?: Callback) => Promise<void>;
/**
* Gets *all* keys known to your app; for all callers, libraries, etc.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#getallkeys
*/
getAllKeys: (
callback?: CallbackWithResult<readonly string[]>
) => Promise<readonly string[]>;
/**
* The following batched functions are useful for executing a lot of
* operations at once, allowing for native optimizations and provide the
* convenience of a single callback after all operations are complete.
*
* These functions return arrays of errors, potentially one for every key.
* For key-specific errors, the Error object will have a key property to
* indicate which key caused the error.
*/
/**
* Flushes any pending requests using a single batch call to get the data.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#flushgetrequests
* */
flushGetRequests: () => void;
/**
* This allows you to batch the fetching of items given an array of `key`
* inputs. Your callback will be invoked with an array of corresponding
* key-value pairs found.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiget
*/
multiGet: (
keys: readonly string[],
callback?: MultiGetCallback
) => Promise<readonly KeyValuePair[]>;
/**
* Use this as a batch operation for storing multiple key-value pairs. When
* the operation completes you'll get a single callback with any errors.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiset
*/
multiSet: (
keyValuePairs: ReadonlyArray<readonly [string, string]>,
callback?: MultiCallback
) => Promise<void>;
/**
* Call this to batch the deletion of all keys in the `keys` array.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multiremove
*/
multiRemove: (
keys: readonly string[],
callback?: MultiCallback
) => Promise<void>;
/**
* Batch operation to merge in existing and new values for a given set of
* keys. This assumes that the values are stringified JSON.
*
* See https://react-native-async-storage.github.io/async-storage/docs/api#multimerge
*/
multiMerge: (
keyValuePairs: [string, string][],
callback?: MultiCallback
) => Promise<void>;
};

View File

@@ -0,0 +1,172 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.30114.105
MinimumVisualStudioVersion = 10.0.40219.1
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactNativeAsyncStorage", "ReactNativeAsyncStorage\ReactNativeAsyncStorage.vcxproj", "{4855D892-E16C-404D-8286-0089E0F7F9C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReactNative", "ReactNative", "{4F6E56C3-12C5-4457-9239-0ACF0B7150A8}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "..\node_modules\react-native-windows\Common\Common.vcxproj", "{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Folly", "..\node_modules\react-native-windows\Folly\Folly.vcxproj", "{A990658C-CE31-4BCC-976F-0FC6B1AF693D}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Universal", "..\node_modules\react-native-windows\JSI\Universal\JSI.Universal.vcxproj", "{A62D504A-16B8-41D2-9F19-E2E86019E5E4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "JSI.Shared", "..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems", "{0CC28589-39E4-4288-B162-97B959F8B843}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Chakra", "..\node_modules\react-native-windows\Chakra\Chakra.vcxitems", "{C38970C0-5FBF-4D69-90D8-CBAC225AE895}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative", "..\node_modules\react-native-windows\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj", "{F7D32BD0-2749-483E-9A0D-1635EF7E3136}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mso", "..\node_modules\react-native-windows\Mso\Mso.vcxitems", "{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ReactCommon", "..\node_modules\react-native-windows\ReactCommon\ReactCommon.vcxproj", "{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Cxx", "..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems", "{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microsoft.ReactNative.Shared", "..\node_modules\react-native-windows\Shared\Shared.vcxitems", "{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Include", "..\node_modules\react-native-windows\include\Include.vcxitems", "{EF074BA1-2D54-4D49-A28E-5E040B47CD2E}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{0cc28589-39e4-4288-b162-97b959f8b843}*SharedItemsImports = 9
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{2049dbe9-8d13-42c9-ae4b-413ae38fffd0}*SharedItemsImports = 9
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{84e05bfa-cbaf-4f0d-bfb6-4ce85742a57e}*SharedItemsImports = 9
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{a62d504a-16b8-41d2-9f19-e2e86019e5e4}*SharedItemsImports = 4
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{c38970c0-5fbf-4d69-90d8-cbac225ae895}*SharedItemsImports = 9
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{da8b35b3-da00-4b02-bde6-6a397b3fd46b}*SharedItemsImports = 9
..\node_modules\react-native-windows\include\Include.vcxitems*{ef074ba1-2d54-4d49-a28e-5e040b47cd2e}*SharedItemsImports = 9
..\node_modules\react-native-windows\Chakra\Chakra.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\JSI\Shared\JSI.Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Mso\Mso.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
..\node_modules\react-native-windows\Shared\Shared.vcxitems*{f7d32bd0-2749-483e-9a0d-1635ef7e3136}*SharedItemsImports = 4
EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM = Debug|ARM
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM = Release|ARM
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.ActiveCfg = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM.Build.0 = Debug|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|ARM64.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.ActiveCfg = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x64.Build.0 = Debug|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.ActiveCfg = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Debug|x86.Build.0 = Debug|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.ActiveCfg = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM.Build.0 = Release|ARM
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|ARM64.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.ActiveCfg = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x64.Build.0 = Release|x64
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.ActiveCfg = Release|Win32
{4855D892-E16C-404D-8286-0089E0F7F9C4}.Release|x86.Build.0 = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.ActiveCfg = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM.Build.0 = Debug|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|ARM64.Build.0 = Debug|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.ActiveCfg = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x64.Build.0 = Debug|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.ActiveCfg = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Debug|x86.Build.0 = Debug|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.ActiveCfg = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM.Build.0 = Release|ARM
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.ActiveCfg = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|ARM64.Build.0 = Release|ARM64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.ActiveCfg = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x64.Build.0 = Release|x64
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.ActiveCfg = Release|Win32
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D}.Release|x86.Build.0 = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.ActiveCfg = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM.Build.0 = Debug|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|ARM64.Build.0 = Debug|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.ActiveCfg = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x64.Build.0 = Debug|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.ActiveCfg = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Debug|x86.Build.0 = Debug|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.ActiveCfg = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM.Build.0 = Release|ARM
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.ActiveCfg = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|ARM64.Build.0 = Release|ARM64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.ActiveCfg = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x64.Build.0 = Release|x64
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.ActiveCfg = Release|Win32
{A990658C-CE31-4BCC-976F-0FC6B1AF693D}.Release|x86.Build.0 = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.ActiveCfg = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM.Build.0 = Debug|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|ARM64.Build.0 = Debug|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.ActiveCfg = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x64.Build.0 = Debug|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.ActiveCfg = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Debug|x86.Build.0 = Debug|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.ActiveCfg = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM.Build.0 = Release|ARM
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.ActiveCfg = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|ARM64.Build.0 = Release|ARM64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.ActiveCfg = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x64.Build.0 = Release|x64
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.ActiveCfg = Release|Win32
{A62D504A-16B8-41D2-9F19-E2E86019E5E4}.Release|x86.Build.0 = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.ActiveCfg = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM.Build.0 = Debug|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.ActiveCfg = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|ARM64.Build.0 = Debug|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.ActiveCfg = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x64.Build.0 = Debug|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.ActiveCfg = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Debug|x86.Build.0 = Debug|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.ActiveCfg = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM.Build.0 = Release|ARM
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.ActiveCfg = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|ARM64.Build.0 = Release|ARM64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.ActiveCfg = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x64.Build.0 = Release|x64
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.ActiveCfg = Release|Win32
{F7D32BD0-2749-483E-9A0D-1635EF7E3136}.Release|x86.Build.0 = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.ActiveCfg = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM.Build.0 = Debug|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.ActiveCfg = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|ARM64.Build.0 = Debug|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.ActiveCfg = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x64.Build.0 = Debug|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.ActiveCfg = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Debug|x86.Build.0 = Debug|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.ActiveCfg = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM.Build.0 = Release|ARM
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.ActiveCfg = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|ARM64.Build.0 = Release|ARM64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.ActiveCfg = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x64.Build.0 = Release|x64
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.ActiveCfg = Release|Win32
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD}.Release|x86.Build.0 = Release|Win32
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{FCA38F3C-7C73-4C47-BE4E-32F77FA8538D} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A990658C-CE31-4BCC-976F-0FC6B1AF693D} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A62D504A-16B8-41D2-9F19-E2E86019E5E4} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{0CC28589-39E4-4288-B162-97B959F8B843} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{C38970C0-5FBF-4D69-90D8-CBAC225AE895} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{F7D32BD0-2749-483E-9A0D-1635EF7E3136} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{84E05BFA-CBAF-4F0D-BFB6-4CE85742A57E} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{A9D95A91-4DB7-4F72-BEB6-FE8A5C89BFBD} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{DA8B35B3-DA00-4B02-BDE6-6A397B3FD46B} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{2049DBE9-8D13-42C9-AE4B-413AE38FFFD0} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
{EF074BA1-2D54-4D49-A28E-5E040B47CD2E} = {4F6E56C3-12C5-4457-9239-0ACF0B7150A8}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1F02BFA9-97C8-4ACF-A348-B3166C3BC7EA}
EndGlobalSection
EndGlobal

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>

View File

@@ -0,0 +1,172 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<MinimalCoreWin>true</MinimalCoreWin>
<ProjectGuid>{4855D892-E16C-404D-8286-0089E0F7F9C4}</ProjectGuid>
<ProjectName>ReactNativeAsyncStorage</ProjectName>
<RootNamespace>ReactNativeAsyncStorage</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<MinimumVisualStudioVersion>16.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion Condition=" '$(WindowsTargetPlatformMinVersion)' == '' ">10.0.17763.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="ReactNativeWindowsProps">
<ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(SolutionDir), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<ImportGroup Label="ReactNativeWindowsPropertySheets">
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props')" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
<!--Temporarily disable cppwinrt heap enforcement to work around xaml compiler generated std::shared_ptr use -->
<AdditionalOptions Condition="'$(CppWinRTHeapEnforcement)'==''">/DWINRT_NO_MAKE_DETECTION %(AdditionalOptions)</AdditionalOptions>
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>true</GenerateWindowsMetadata>
<ModuleDefinitionFile>..\code\ReactNativeAsyncStorage.def</ModuleDefinitionFile>
<AdditionalDependencies>winsqlite3.lib;%(AdditionalDependencies)</AdditionalDependencies>
<DelayLoadDLLs>winsqlite3.dll;%(DelayLoadDLLs)</DelayLoadDLLs>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="..\code\pch.h" />
<ClInclude Include="..\code\ReactPackageProvider.h">
<DependentUpon>..\code\ReactPackageProvider.idl</DependentUpon>
</ClInclude>
<ClInclude Include="..\code\DBStorage.h" />
<ClInclude Include="..\code\RNCAsyncStorage.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="..\code\pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="..\code\ReactPackageProvider.cpp">
<DependentUpon>..\code\ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="..\code\DBStorage.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="..\code\ReactPackageProvider.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="..\code\ReactNativeAsyncStorage.def" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ReactNativeWindowsDir)\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj">
<Project>{f7d32bd0-2749-483e-9a0d-1635ef7e3136}</Project>
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ReactNativeWindowsTargets">
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets')" />
</ImportGroup>
<Target Name="EnsureReactNativeWindowsTargets" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references targets in your node_modules\react-native-windows folder. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props')" Text="$([System.String]::Format('$(ErrorText)', '$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props'))" />
<Error Condition="!Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets'))" />
</Target>
<ImportGroup Label="ExtensionTargets">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
</ImportGroup>
<Target Name="Deploy" />
<Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props'))" />
<Error Condition="!Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets'))" />
</Target>
</Project>

Some files were not shown because too many files have changed in this diff Show More