react-native-nitro-storage 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +447 -0
- package/android/CMakeLists.txt +33 -0
- package/android/build.gradle +96 -0
- package/android/gradle.properties +4 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.cpp +49 -0
- package/android/src/main/cpp/AndroidStorageAdapterCpp.hpp +68 -0
- package/android/src/main/cpp/cpp-adapter.cpp +6 -0
- package/android/src/main/java/com/nitrostorage/AndroidStorageAdapter.kt +94 -0
- package/android/src/main/java/com/nitrostorage/NitroStoragePackage.kt +27 -0
- package/app.plugin.js +106 -0
- package/cpp/bindings/HybridStorage.cpp +144 -0
- package/cpp/bindings/HybridStorage.hpp +52 -0
- package/cpp/core/NativeStorageAdapter.hpp +21 -0
- package/ios/IOSStorageAdapterCpp.hpp +21 -0
- package/ios/IOSStorageAdapterCpp.mm +127 -0
- package/lib/commonjs/Storage.nitro.js +13 -0
- package/lib/commonjs/Storage.nitro.js.map +1 -0
- package/lib/commonjs/index.js +88 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/module/Storage.nitro.js +9 -0
- package/lib/module/Storage.nitro.js.map +1 -0
- package/lib/module/index.js +77 -0
- package/lib/module/index.js.map +1 -0
- package/lib/typescript/Storage.nitro.d.ts +16 -0
- package/lib/typescript/Storage.nitro.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +19 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/nitro.json +15 -0
- package/nitrogen/generated/.gitattributes +1 -0
- package/nitrogen/generated/android/NitroStorage+autolinking.cmake +81 -0
- package/nitrogen/generated/android/NitroStorage+autolinking.gradle +27 -0
- package/nitrogen/generated/android/NitroStorageOnLoad.cpp +44 -0
- package/nitrogen/generated/android/NitroStorageOnLoad.hpp +25 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/com/nitrostorage/NitroStorageOnLoad.kt +35 -0
- package/nitrogen/generated/ios/NitroStorage+autolinking.rb +60 -0
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.cpp +17 -0
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Bridge.hpp +27 -0
- package/nitrogen/generated/ios/NitroStorage-Swift-Cxx-Umbrella.hpp +38 -0
- package/nitrogen/generated/ios/NitroStorageAutolinking.mm +35 -0
- package/nitrogen/generated/ios/NitroStorageAutolinking.swift +12 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.cpp +24 -0
- package/nitrogen/generated/shared/c++/HybridStorageSpec.hpp +67 -0
- package/package.json +117 -0
- package/react-native-nitro-storage.podspec +36 -0
- package/src/Storage.nitro.ts +17 -0
- package/src/index.ts +113 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "NativeStorageAdapter.hpp"
|
|
4
|
+
#include <fbjni/fbjni.h>
|
|
5
|
+
#include <jni.h>
|
|
6
|
+
|
|
7
|
+
namespace NitroStorage {
|
|
8
|
+
|
|
9
|
+
struct JContext : facebook::jni::JavaClass<JContext> {
|
|
10
|
+
static constexpr auto kJavaDescriptor = "Landroid/content/Context;";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
struct AndroidStorageAdapterJava : facebook::jni::JavaClass<AndroidStorageAdapterJava> {
|
|
14
|
+
static constexpr auto kJavaDescriptor = "Lcom/nitrostorage/AndroidStorageAdapter;";
|
|
15
|
+
|
|
16
|
+
static facebook::jni::alias_ref<facebook::jni::JObject> getContext() {
|
|
17
|
+
static auto method = javaClassStatic()->getStaticMethod<facebook::jni::JObject()>("getContext", "()Landroid/content/Context;");
|
|
18
|
+
return method(javaClassStatic());
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
void setDisk(std::string key, std::string value) {
|
|
22
|
+
static auto method = javaClassStatic()->getMethod<void(std::string, std::string)>("setDisk");
|
|
23
|
+
method(self(), key, value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
std::string getDisk(std::string key) {
|
|
27
|
+
static auto method = javaClassStatic()->getMethod<jstring(std::string)>("getDisk");
|
|
28
|
+
auto result = method(self(), key);
|
|
29
|
+
return result ? result->toStdString() : "";
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
void deleteDisk(std::string key) {
|
|
33
|
+
static auto method = javaClassStatic()->getMethod<void(std::string)>("deleteDisk");
|
|
34
|
+
method(self(), key);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void setSecure(std::string key, std::string value) {
|
|
38
|
+
static auto method = javaClassStatic()->getMethod<void(std::string, std::string)>("setSecure");
|
|
39
|
+
method(self(), key, value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
std::string getSecure(std::string key) {
|
|
43
|
+
static auto method = javaClassStatic()->getMethod<jstring(std::string)>("getSecure");
|
|
44
|
+
auto result = method(self(), key);
|
|
45
|
+
return result ? result->toStdString() : "";
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
void deleteSecure(std::string key) {
|
|
49
|
+
static auto method = javaClassStatic()->getMethod<void(std::string)>("deleteSecure");
|
|
50
|
+
method(self(), key);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
class AndroidStorageAdapterCpp : public NativeStorageAdapter {
|
|
55
|
+
public:
|
|
56
|
+
explicit AndroidStorageAdapterCpp(facebook::jni::alias_ref<facebook::jni::JObject> context);
|
|
57
|
+
~AndroidStorageAdapterCpp() override;
|
|
58
|
+
|
|
59
|
+
void setDisk(const std::string& key, const std::string& value) override;
|
|
60
|
+
std::optional<std::string> getDisk(const std::string& key) override;
|
|
61
|
+
void deleteDisk(const std::string& key) override;
|
|
62
|
+
|
|
63
|
+
void setSecure(const std::string& key, const std::string& value) override;
|
|
64
|
+
std::optional<std::string> getSecure(const std::string& key) override;
|
|
65
|
+
void deleteSecure(const std::string& key) override;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
} // namespace NitroStorage
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
package com.nitrostorage
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.SharedPreferences
|
|
5
|
+
import androidx.security.crypto.EncryptedSharedPreferences
|
|
6
|
+
import androidx.security.crypto.MasterKey
|
|
7
|
+
|
|
8
|
+
class AndroidStorageAdapter private constructor(private val context: Context) {
|
|
9
|
+
private val sharedPreferences: SharedPreferences =
|
|
10
|
+
context.getSharedPreferences("NitroStorage", Context.MODE_PRIVATE)
|
|
11
|
+
|
|
12
|
+
private val masterKey: MasterKey = MasterKey.Builder(context)
|
|
13
|
+
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
|
|
14
|
+
.build()
|
|
15
|
+
|
|
16
|
+
private val encryptedPreferences: SharedPreferences = try {
|
|
17
|
+
EncryptedSharedPreferences.create(
|
|
18
|
+
context,
|
|
19
|
+
"NitroStorageSecure",
|
|
20
|
+
masterKey,
|
|
21
|
+
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
|
|
22
|
+
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
|
|
23
|
+
)
|
|
24
|
+
} catch (e: Exception) {
|
|
25
|
+
throw RuntimeException(
|
|
26
|
+
"NitroStorage: Failed to initialize secure storage. " +
|
|
27
|
+
"This may be due to corrupted encryption keys. " +
|
|
28
|
+
"Try clearing app data or reinstalling the app.", e
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
companion object {
|
|
33
|
+
@Volatile
|
|
34
|
+
private var instance: AndroidStorageAdapter? = null
|
|
35
|
+
|
|
36
|
+
@JvmStatic
|
|
37
|
+
fun init(context: Context) {
|
|
38
|
+
if (instance == null) {
|
|
39
|
+
synchronized(this) {
|
|
40
|
+
if (instance == null) {
|
|
41
|
+
instance = AndroidStorageAdapter(context.applicationContext)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
@JvmStatic
|
|
48
|
+
fun getContext(): Context {
|
|
49
|
+
return instance?.context
|
|
50
|
+
?: throw IllegalStateException(
|
|
51
|
+
"NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
|
|
52
|
+
"or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@JvmStatic
|
|
57
|
+
fun setDisk(key: String, value: String) {
|
|
58
|
+
instance?.sharedPreferences?.edit()?.putString(key, value)?.apply()
|
|
59
|
+
?: throw IllegalStateException(
|
|
60
|
+
"NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
|
|
61
|
+
"or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
@JvmStatic
|
|
66
|
+
fun getDisk(key: String): String? {
|
|
67
|
+
return instance?.sharedPreferences?.getString(key, null)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@JvmStatic
|
|
71
|
+
fun deleteDisk(key: String) {
|
|
72
|
+
instance?.sharedPreferences?.edit()?.remove(key)?.apply()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
@JvmStatic
|
|
76
|
+
fun setSecure(key: String, value: String) {
|
|
77
|
+
instance?.encryptedPreferences?.edit()?.putString(key, value)?.apply()
|
|
78
|
+
?: throw IllegalStateException(
|
|
79
|
+
"NitroStorage not initialized. Call AndroidStorageAdapter.init(this) in your MainApplication.onCreate(), " +
|
|
80
|
+
"or add 'react-native-nitro-storage' to your Expo plugins array in app.json."
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@JvmStatic
|
|
85
|
+
fun getSecure(key: String): String? {
|
|
86
|
+
return instance?.encryptedPreferences?.getString(key, null)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@JvmStatic
|
|
90
|
+
fun deleteSecure(key: String) {
|
|
91
|
+
instance?.encryptedPreferences?.edit()?.remove(key)?.apply()
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.nitrostorage
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.TurboReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.module.model.ReactModuleInfo
|
|
7
|
+
import com.facebook.react.module.model.ReactModuleInfoProvider
|
|
8
|
+
import java.util.HashMap
|
|
9
|
+
|
|
10
|
+
class NitroStoragePackage : TurboReactPackage() {
|
|
11
|
+
override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
|
|
16
|
+
return ReactModuleInfoProvider {
|
|
17
|
+
val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
|
|
18
|
+
moduleInfos
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
companion object {
|
|
23
|
+
init {
|
|
24
|
+
System.loadLibrary("NitroStorage")
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
package/app.plugin.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const {
|
|
2
|
+
withInfoPlist,
|
|
3
|
+
withAndroidManifest,
|
|
4
|
+
withMainApplication,
|
|
5
|
+
createRunOncePlugin,
|
|
6
|
+
} = require("@expo/config-plugins");
|
|
7
|
+
|
|
8
|
+
const withNitroStorage = (config, props = {}) => {
|
|
9
|
+
const {
|
|
10
|
+
faceIDPermission = "Allow $(PRODUCT_NAME) to use Face ID for secure authentication",
|
|
11
|
+
} = props;
|
|
12
|
+
|
|
13
|
+
config = withInfoPlist(config, (config) => {
|
|
14
|
+
config.modResults.NSFaceIDUsageDescription =
|
|
15
|
+
faceIDPermission || config.modResults.NSFaceIDUsageDescription;
|
|
16
|
+
return config;
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
config = withAndroidManifest(config, (config) => {
|
|
20
|
+
const mainApplication = config.modResults.manifest.application?.[0];
|
|
21
|
+
if (!mainApplication) {
|
|
22
|
+
return config;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!config.modResults.manifest["uses-permission"]) {
|
|
26
|
+
config.modResults.manifest["uses-permission"] = [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const permissions = config.modResults.manifest["uses-permission"];
|
|
30
|
+
|
|
31
|
+
const biometricPermission = {
|
|
32
|
+
$: { "android:name": "android.permission.USE_BIOMETRIC" },
|
|
33
|
+
};
|
|
34
|
+
const fingerprintPermission = {
|
|
35
|
+
$: { "android:name": "android.permission.USE_FINGERPRINT" },
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const hasBiometric = permissions.some(
|
|
39
|
+
(p) => p.$?.["android:name"] === "android.permission.USE_BIOMETRIC"
|
|
40
|
+
);
|
|
41
|
+
const hasFingerprint = permissions.some(
|
|
42
|
+
(p) => p.$?.["android:name"] === "android.permission.USE_FINGERPRINT"
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
if (!hasBiometric) {
|
|
46
|
+
permissions.push(biometricPermission);
|
|
47
|
+
}
|
|
48
|
+
if (!hasFingerprint) {
|
|
49
|
+
permissions.push(fingerprintPermission);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return config;
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
config = withMainApplication(config, (config) => {
|
|
56
|
+
const { modResults } = config;
|
|
57
|
+
const { language, contents } = modResults;
|
|
58
|
+
|
|
59
|
+
if (language === "java") {
|
|
60
|
+
if (!contents.includes("AndroidStorageAdapter.init")) {
|
|
61
|
+
const importStatement =
|
|
62
|
+
"import com.nitrostorage.AndroidStorageAdapter;";
|
|
63
|
+
const initStatement = " AndroidStorageAdapter.init(this);";
|
|
64
|
+
|
|
65
|
+
if (!contents.includes(importStatement)) {
|
|
66
|
+
modResults.contents = contents.replace(
|
|
67
|
+
/(package .*;\n)/,
|
|
68
|
+
`$1\n${importStatement}\n`
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
modResults.contents = modResults.contents.replace(
|
|
73
|
+
/(super\.onCreate\(\);)/,
|
|
74
|
+
`$1\n${initStatement}`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
} else if (language === "kt") {
|
|
78
|
+
if (!contents.includes("AndroidStorageAdapter.init")) {
|
|
79
|
+
const importStatement = "import com.nitrostorage.AndroidStorageAdapter";
|
|
80
|
+
const initStatement = " AndroidStorageAdapter.init(this)";
|
|
81
|
+
|
|
82
|
+
if (!contents.includes(importStatement)) {
|
|
83
|
+
modResults.contents = contents.replace(
|
|
84
|
+
/(package .*\n)/,
|
|
85
|
+
`$1\n${importStatement}\n`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
modResults.contents = modResults.contents.replace(
|
|
90
|
+
/(super\.onCreate\(\))/,
|
|
91
|
+
`$1\n${initStatement}`
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return config;
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return config;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
module.exports = createRunOncePlugin(
|
|
103
|
+
withNitroStorage,
|
|
104
|
+
"react-native-nitro-storage",
|
|
105
|
+
"1.0.0"
|
|
106
|
+
);
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
#include "HybridStorage.hpp"
|
|
2
|
+
#include <stdexcept>
|
|
3
|
+
|
|
4
|
+
#if __APPLE__
|
|
5
|
+
#include "../../ios/IOSStorageAdapterCpp.hpp"
|
|
6
|
+
#elif __ANDROID__
|
|
7
|
+
#include "../../android/src/main/cpp/AndroidStorageAdapterCpp.hpp"
|
|
8
|
+
#include <fbjni/fbjni.h>
|
|
9
|
+
#endif
|
|
10
|
+
|
|
11
|
+
namespace margelo::nitro::NitroStorage {
|
|
12
|
+
|
|
13
|
+
HybridStorage::HybridStorage()
|
|
14
|
+
: HybridObject(TAG), HybridStorageSpec() {
|
|
15
|
+
#if __APPLE__
|
|
16
|
+
nativeAdapter_ = std::make_shared<::NitroStorage::IOSStorageAdapterCpp>();
|
|
17
|
+
#elif __ANDROID__
|
|
18
|
+
auto context = ::NitroStorage::AndroidStorageAdapterJava::getContext();
|
|
19
|
+
nativeAdapter_ = std::make_shared<::NitroStorage::AndroidStorageAdapterCpp>(context);
|
|
20
|
+
#endif
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
HybridStorage::HybridStorage(std::shared_ptr<::NitroStorage::NativeStorageAdapter> adapter)
|
|
24
|
+
: HybridObject(TAG), HybridStorageSpec(), nativeAdapter_(std::move(adapter)) {}
|
|
25
|
+
|
|
26
|
+
HybridStorage::Scope HybridStorage::toScope(double scopeValue) {
|
|
27
|
+
int intScope = static_cast<int>(scopeValue);
|
|
28
|
+
if (intScope < 0 || intScope > 2) {
|
|
29
|
+
throw std::invalid_argument(
|
|
30
|
+
"Invalid storage scope: " + std::to_string(intScope) +
|
|
31
|
+
". Must be 0 (Memory), 1 (Disk), or 2 (Secure)"
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
return static_cast<Scope>(intScope);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
void HybridStorage::set(const std::string& key, const std::string& value, double scope) {
|
|
38
|
+
Scope s = toScope(scope);
|
|
39
|
+
|
|
40
|
+
switch (s) {
|
|
41
|
+
case Scope::Memory: {
|
|
42
|
+
std::lock_guard<std::mutex> lock(memoryMutex_);
|
|
43
|
+
memoryStore_[key] = value;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
case Scope::Disk:
|
|
47
|
+
nativeAdapter_->setDisk(key, value);
|
|
48
|
+
break;
|
|
49
|
+
case Scope::Secure:
|
|
50
|
+
nativeAdapter_->setSecure(key, value);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
notifyListeners(static_cast<int>(s), key, value);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
std::optional<std::string> HybridStorage::get(const std::string& key, double scope) {
|
|
58
|
+
Scope s = toScope(scope);
|
|
59
|
+
|
|
60
|
+
switch (s) {
|
|
61
|
+
case Scope::Memory: {
|
|
62
|
+
std::lock_guard<std::mutex> lock(memoryMutex_);
|
|
63
|
+
auto it = memoryStore_.find(key);
|
|
64
|
+
if (it != memoryStore_.end()) {
|
|
65
|
+
return it->second;
|
|
66
|
+
}
|
|
67
|
+
return std::nullopt;
|
|
68
|
+
}
|
|
69
|
+
case Scope::Disk:
|
|
70
|
+
return nativeAdapter_->getDisk(key);
|
|
71
|
+
case Scope::Secure:
|
|
72
|
+
return nativeAdapter_->getSecure(key);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return std::nullopt;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void HybridStorage::remove(const std::string& key, double scope) {
|
|
79
|
+
Scope s = toScope(scope);
|
|
80
|
+
|
|
81
|
+
switch (s) {
|
|
82
|
+
case Scope::Memory: {
|
|
83
|
+
std::lock_guard<std::mutex> lock(memoryMutex_);
|
|
84
|
+
memoryStore_.erase(key);
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
case Scope::Disk:
|
|
88
|
+
nativeAdapter_->deleteDisk(key);
|
|
89
|
+
break;
|
|
90
|
+
case Scope::Secure:
|
|
91
|
+
nativeAdapter_->deleteSecure(key);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
notifyListeners(static_cast<int>(s), key, std::nullopt);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
std::function<void()> HybridStorage::addOnChange(
|
|
99
|
+
double scope,
|
|
100
|
+
const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
|
|
101
|
+
) {
|
|
102
|
+
int intScope = static_cast<int>(scope);
|
|
103
|
+
|
|
104
|
+
size_t listenerId;
|
|
105
|
+
{
|
|
106
|
+
std::lock_guard<std::mutex> lock(listenersMutex_);
|
|
107
|
+
listenerId = nextListenerId_++;
|
|
108
|
+
listeners_[intScope].push_back({listenerId, callback});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return [this, intScope, listenerId]() {
|
|
112
|
+
std::lock_guard<std::mutex> lock(listenersMutex_);
|
|
113
|
+
auto& scopeListeners = listeners_[intScope];
|
|
114
|
+
scopeListeners.erase(
|
|
115
|
+
std::remove_if(
|
|
116
|
+
scopeListeners.begin(),
|
|
117
|
+
scopeListeners.end(),
|
|
118
|
+
[listenerId](const Listener& l) { return l.id == listenerId; }
|
|
119
|
+
),
|
|
120
|
+
scopeListeners.end()
|
|
121
|
+
);
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
void HybridStorage::notifyListeners(
|
|
126
|
+
int scope,
|
|
127
|
+
const std::string& key,
|
|
128
|
+
const std::optional<std::string>& value
|
|
129
|
+
) {
|
|
130
|
+
std::vector<Listener> listenersCopy;
|
|
131
|
+
{
|
|
132
|
+
std::lock_guard<std::mutex> lock(listenersMutex_);
|
|
133
|
+
auto it = listeners_.find(scope);
|
|
134
|
+
if (it != listeners_.end()) {
|
|
135
|
+
listenersCopy = it->second;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const auto& listener : listenersCopy) {
|
|
140
|
+
listener.callback(key, value);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
} // namespace margelo::nitro::NitroStorage
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "HybridStorageSpec.hpp"
|
|
4
|
+
#include "../core/NativeStorageAdapter.hpp"
|
|
5
|
+
#include <unordered_map>
|
|
6
|
+
#include <mutex>
|
|
7
|
+
#include <functional>
|
|
8
|
+
#include <memory>
|
|
9
|
+
#include <vector>
|
|
10
|
+
|
|
11
|
+
namespace margelo::nitro::NitroStorage {
|
|
12
|
+
|
|
13
|
+
class HybridStorage : public HybridStorageSpec {
|
|
14
|
+
public:
|
|
15
|
+
HybridStorage();
|
|
16
|
+
explicit HybridStorage(std::shared_ptr<::NitroStorage::NativeStorageAdapter> adapter);
|
|
17
|
+
~HybridStorage() override = default;
|
|
18
|
+
|
|
19
|
+
void set(const std::string& key, const std::string& value, double scope) override;
|
|
20
|
+
std::optional<std::string> get(const std::string& key, double scope) override;
|
|
21
|
+
void remove(const std::string& key, double scope) override;
|
|
22
|
+
std::function<void()> addOnChange(
|
|
23
|
+
double scope,
|
|
24
|
+
const std::function<void(const std::string&, const std::optional<std::string>&)>& callback
|
|
25
|
+
) override;
|
|
26
|
+
|
|
27
|
+
private:
|
|
28
|
+
enum class Scope {
|
|
29
|
+
Memory = 0,
|
|
30
|
+
Disk = 1,
|
|
31
|
+
Secure = 2
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
struct Listener {
|
|
35
|
+
size_t id;
|
|
36
|
+
std::function<void(const std::string&, const std::optional<std::string>&)> callback;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
std::unordered_map<std::string, std::string> memoryStore_;
|
|
40
|
+
std::mutex memoryMutex_;
|
|
41
|
+
|
|
42
|
+
std::shared_ptr<::NitroStorage::NativeStorageAdapter> nativeAdapter_;
|
|
43
|
+
|
|
44
|
+
std::unordered_map<int, std::vector<Listener>> listeners_;
|
|
45
|
+
std::mutex listenersMutex_;
|
|
46
|
+
size_t nextListenerId_ = 0;
|
|
47
|
+
|
|
48
|
+
void notifyListeners(int scope, const std::string& key, const std::optional<std::string>& value);
|
|
49
|
+
Scope toScope(double scopeValue);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
} // namespace margelo::nitro::NitroStorage
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include <string>
|
|
4
|
+
#include <optional>
|
|
5
|
+
|
|
6
|
+
namespace NitroStorage {
|
|
7
|
+
|
|
8
|
+
class NativeStorageAdapter {
|
|
9
|
+
public:
|
|
10
|
+
virtual ~NativeStorageAdapter() = default;
|
|
11
|
+
|
|
12
|
+
virtual void setDisk(const std::string& key, const std::string& value) = 0;
|
|
13
|
+
virtual std::optional<std::string> getDisk(const std::string& key) = 0;
|
|
14
|
+
virtual void deleteDisk(const std::string& key) = 0;
|
|
15
|
+
|
|
16
|
+
virtual void setSecure(const std::string& key, const std::string& value) = 0;
|
|
17
|
+
virtual std::optional<std::string> getSecure(const std::string& key) = 0;
|
|
18
|
+
virtual void deleteSecure(const std::string& key) = 0;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
} // namespace NitroStorage
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#include "../core/NativeStorageAdapter.hpp"
|
|
4
|
+
|
|
5
|
+
namespace NitroStorage {
|
|
6
|
+
|
|
7
|
+
class IOSStorageAdapterCpp : public NativeStorageAdapter {
|
|
8
|
+
public:
|
|
9
|
+
IOSStorageAdapterCpp();
|
|
10
|
+
~IOSStorageAdapterCpp() override;
|
|
11
|
+
|
|
12
|
+
void setDisk(const std::string& key, const std::string& value) override;
|
|
13
|
+
std::optional<std::string> getDisk(const std::string& key) override;
|
|
14
|
+
void deleteDisk(const std::string& key) override;
|
|
15
|
+
|
|
16
|
+
void setSecure(const std::string& key, const std::string& value) override;
|
|
17
|
+
std::optional<std::string> getSecure(const std::string& key) override;
|
|
18
|
+
void deleteSecure(const std::string& key) override;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
} // namespace NitroStorage
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
#import "IOSStorageAdapterCpp.hpp"
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
#import <Security/Security.h>
|
|
4
|
+
|
|
5
|
+
namespace NitroStorage {
|
|
6
|
+
|
|
7
|
+
IOSStorageAdapterCpp::IOSStorageAdapterCpp() {}
|
|
8
|
+
|
|
9
|
+
IOSStorageAdapterCpp::~IOSStorageAdapterCpp() {}
|
|
10
|
+
|
|
11
|
+
void IOSStorageAdapterCpp::setDisk(const std::string& key, const std::string& value) {
|
|
12
|
+
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
13
|
+
NSString* nsValue = [NSString stringWithUTF8String:value.c_str()];
|
|
14
|
+
[[NSUserDefaults standardUserDefaults] setObject:nsValue forKey:nsKey];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
std::optional<std::string> IOSStorageAdapterCpp::getDisk(const std::string& key) {
|
|
18
|
+
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
19
|
+
NSString* result = [[NSUserDefaults standardUserDefaults] stringForKey:nsKey];
|
|
20
|
+
|
|
21
|
+
if (result) {
|
|
22
|
+
const char* utf8String = [result UTF8String];
|
|
23
|
+
if (utf8String) {
|
|
24
|
+
std::string value;
|
|
25
|
+
value.reserve([result lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
|
|
26
|
+
value.assign(utf8String);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return std::nullopt;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
void IOSStorageAdapterCpp::deleteDisk(const std::string& key) {
|
|
34
|
+
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
35
|
+
[[NSUserDefaults standardUserDefaults] removeObjectForKey:nsKey];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
void IOSStorageAdapterCpp::setSecure(const std::string& key, const std::string& value) {
|
|
39
|
+
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
40
|
+
NSString* nsValue = [NSString stringWithUTF8String:value.c_str()];
|
|
41
|
+
NSData* data = [nsValue dataUsingEncoding:NSUTF8StringEncoding];
|
|
42
|
+
|
|
43
|
+
NSString* service = @"com.nitrostorage.keychain";
|
|
44
|
+
|
|
45
|
+
NSDictionary* query = @{
|
|
46
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
47
|
+
(__bridge id)kSecAttrService: service,
|
|
48
|
+
(__bridge id)kSecAttrAccount: nsKey
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
NSDictionary* updateAttributes = @{
|
|
52
|
+
(__bridge id)kSecValueData: data
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Try to update first (atomic operation)
|
|
56
|
+
OSStatus updateStatus = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateAttributes);
|
|
57
|
+
|
|
58
|
+
if (updateStatus == errSecItemNotFound) {
|
|
59
|
+
// Item doesn't exist, add it
|
|
60
|
+
NSDictionary* addQuery = @{
|
|
61
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
62
|
+
(__bridge id)kSecAttrService: service,
|
|
63
|
+
(__bridge id)kSecAttrAccount: nsKey,
|
|
64
|
+
(__bridge id)kSecValueData: data,
|
|
65
|
+
(__bridge id)kSecAttrAccessible: (__bridge id)kSecAttrAccessibleWhenUnlocked
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
OSStatus addStatus = SecItemAdd((__bridge CFDictionaryRef)addQuery, NULL);
|
|
69
|
+
if (addStatus != errSecSuccess) {
|
|
70
|
+
NSLog(@"NitroStorage: Failed to add to Keychain for key '%@'. Error: %d", nsKey, (int)addStatus);
|
|
71
|
+
}
|
|
72
|
+
} else if (updateStatus != errSecSuccess) {
|
|
73
|
+
NSLog(@"NitroStorage: Failed to update Keychain item '%@'. Error: %d", nsKey, (int)updateStatus);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
std::optional<std::string> IOSStorageAdapterCpp::getSecure(const std::string& key) {
|
|
78
|
+
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
79
|
+
NSString* service = @"com.nitrostorage.keychain";
|
|
80
|
+
|
|
81
|
+
NSDictionary* query = @{
|
|
82
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
83
|
+
(__bridge id)kSecAttrService: service,
|
|
84
|
+
(__bridge id)kSecAttrAccount: nsKey,
|
|
85
|
+
(__bridge id)kSecReturnData: @YES,
|
|
86
|
+
(__bridge id)kSecMatchLimit: (__bridge id)kSecMatchLimitOne
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
CFTypeRef result = NULL;
|
|
90
|
+
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
|
|
91
|
+
|
|
92
|
+
if (status == errSecSuccess && result) {
|
|
93
|
+
NSData* data = (__bridge_transfer NSData*)result;
|
|
94
|
+
NSString* nsValue = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
95
|
+
if (nsValue) {
|
|
96
|
+
const char* utf8String = [nsValue UTF8String];
|
|
97
|
+
if (utf8String) {
|
|
98
|
+
std::string value;
|
|
99
|
+
value.reserve([nsValue lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
|
|
100
|
+
value.assign(utf8String);
|
|
101
|
+
return value;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
} else if (status != errSecItemNotFound) {
|
|
105
|
+
NSLog(@"NitroStorage: Failed to read from Keychain for key '%@'. Error: %d", nsKey, (int)status);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return std::nullopt;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
void IOSStorageAdapterCpp::deleteSecure(const std::string& key) {
|
|
112
|
+
NSString* nsKey = [NSString stringWithUTF8String:key.c_str()];
|
|
113
|
+
NSString* service = @"com.nitrostorage.keychain";
|
|
114
|
+
|
|
115
|
+
NSDictionary* query = @{
|
|
116
|
+
(__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
|
|
117
|
+
(__bridge id)kSecAttrService: service,
|
|
118
|
+
(__bridge id)kSecAttrAccount: nsKey
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);
|
|
122
|
+
if (status != errSecSuccess && status != errSecItemNotFound) {
|
|
123
|
+
NSLog(@"NitroStorage: Failed to delete from Keychain for key '%@'. Error: %d", nsKey, (int)status);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
} // namespace NitroStorage
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.StorageScope = void 0;
|
|
7
|
+
let StorageScope = exports.StorageScope = /*#__PURE__*/function (StorageScope) {
|
|
8
|
+
StorageScope[StorageScope["Memory"] = 0] = "Memory";
|
|
9
|
+
StorageScope[StorageScope["Disk"] = 1] = "Disk";
|
|
10
|
+
StorageScope[StorageScope["Secure"] = 2] = "Secure";
|
|
11
|
+
return StorageScope;
|
|
12
|
+
}({});
|
|
13
|
+
//# sourceMappingURL=Storage.nitro.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["StorageScope","exports"],"sourceRoot":"../../src","sources":["Storage.nitro.ts"],"mappings":";;;;;;IAEYA,YAAY,GAAAC,OAAA,CAAAD,YAAA,0BAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EAAZA,YAAY,CAAZA,YAAY;EAAA,OAAZA,YAAY;AAAA","ignoreList":[]}
|