Skip to content

Commit

Permalink
create MmkvHostObject.h
Browse files Browse the repository at this point in the history
  • Loading branch information
mrousavy committed Mar 25, 2024
1 parent e0edd0e commit f113a18
Show file tree
Hide file tree
Showing 4 changed files with 352 additions and 8 deletions.
290 changes: 290 additions & 0 deletions cpp/MmkvHostObject.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,290 @@
//
// MmkvHostObject.cpp
// Mmkv
//
// Created by Marc Rousavy on 03.09.21.
// Copyright © 2021 Facebook. All rights reserved.
//

#include "MmkvHostObject.h"
#include "TypedArray.h"
#include <MMKV.h>
#include <android/log.h>
#include <string>
#include <vector>

MmkvHostObject::MmkvHostObject(const std::string& instanceId, std::string path,
std::string cryptKey) {
bool hasEncryptionKey = cryptKey.size() > 0;
__android_log_print(ANDROID_LOG_INFO, "RNMMKV",
"Creating MMKV instance \"%s\"... (Path: %s, Encrypted: %b)",
instanceId.c_str(), path.c_str(), hasEncryptionKey);
std::string* pathPtr = path.size() > 0 ? &path : nullptr;
std::string* cryptKeyPtr = cryptKey.size() > 0 ? &cryptKey : nullptr;
instance = MMKV::mmkvWithID(instanceId, mmkv::DEFAULT_MMAP_SIZE, MMKV_SINGLE_PROCESS, cryptKeyPtr,
pathPtr);

if (instance == nullptr) {
// Check if instanceId is invalid
if (instanceId.empty()) {
throw std::runtime_error("Failed to create MMKV instance! `id` cannot be empty!");
}

// Check if encryptionKey is invalid
if (cryptKey.size() > 16) {
throw std::runtime_error(
"Failed to create MMKV instance! `encryptionKey` cannot be longer than 16 bytes!");
}

throw std::runtime_error("Failed to create MMKV instance!");
}
}

std::vector<jsi::PropNameID> MmkvHostObject::getPropertyNames(jsi::Runtime& rt) {
std::vector<jsi::PropNameID> result;
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("set")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("getBoolean")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("getBuffer")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("getString")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("getNumber")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("contains")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("delete")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("getAllKeys")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("deleteAll")));
result.push_back(jsi::PropNameID::forUtf8(rt, std::string("recrypt")));
return result;
}

jsi::Value MmkvHostObject::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) {
auto propName = propNameId.utf8(runtime);
auto funcName = "MMKV." + propName;

if (propName == "set") {
// MMKV.set(key: string, value: string | number | bool | Uint8Array)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
2, // key, value
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime,
"MMKV::set: First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);

if (arguments[1].isBool()) {
// bool
instance->set(arguments[1].getBool(), keyName);
} else if (arguments[1].isNumber()) {
// number
instance->set(arguments[1].getNumber(), keyName);
} else if (arguments[1].isString()) {
// string
auto stringValue = arguments[1].getString(runtime).utf8(runtime);
instance->set(stringValue, keyName);
} else if (arguments[1].isObject()) {
// object
auto object = arguments[1].asObject(runtime);
if (isTypedArray(runtime, object)) {
// Uint8Array
auto typedArray = getTypedArray(runtime, object);
auto bufferValue = typedArray.getBuffer(runtime);
mmkv::MMBuffer buffer(bufferValue.data(runtime), bufferValue.size(runtime),
mmkv::MMBufferCopyFlag::MMBufferNoCopy);
instance->set(buffer, keyName);
} else {
// unknown object
throw jsi::JSError(
runtime, "MMKV::set: 'value' argument is an object, but not of type Uint8Array!");
}
} else {
// unknown type
throw jsi::JSError(
runtime,
"MMKV::set: 'value' argument is not of type bool, number, string or buffer!");
}

return jsi::Value::undefined();
});
}

if (propName == "getBoolean") {
// MMKV.getBoolean(key: string)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // key
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);
bool hasValue;
auto value = instance->getBool(keyName, false, &hasValue);
if (hasValue) {
return jsi::Value(value);
} else {
return jsi::Value::undefined();
}
});
}

if (propName == "getString") {
// MMKV.getString(key: string)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // key
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);
std::string result;
bool hasValue = instance->getString(keyName, result);
if (hasValue) {
return jsi::Value(runtime, jsi::String::createFromUtf8(runtime, result));
} else {
return jsi::Value::undefined();
}
});
}

if (propName == "getNumber") {
// MMKV.getNumber(key: string)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // key
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);
bool hasValue;
auto value = instance->getDouble(keyName, 0.0, &hasValue);
if (hasValue) {
return jsi::Value(value);
} else {
return jsi::Value::undefined();
}
});
}

if (propName == "getBuffer") {
// MMKV.getBuffer(key: string)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // key
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);
mmkv::MMBuffer buffer;
bool hasValue = instance->getBytes(keyName, buffer);
if (hasValue) {
auto length = buffer.length();
TypedArray<TypedArrayKind::Uint8Array> array(runtime, length);
auto data = static_cast<const unsigned char*>(buffer.getPtr());
std::vector<unsigned char> vector(length);
vector.assign(data, data + length);
array.update(runtime, vector);
return array;
} else {
return jsi::Value::undefined();
}
});
}

if (propName == "contains") {
// MMKV.contains(key: string)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // key
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);
bool containsKey = instance->containsKey(keyName);
return jsi::Value(containsKey);
});
}

if (propName == "delete") {
// MMKV.delete(key: string)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // key
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (!arguments[0].isString()) {
throw jsi::JSError(runtime, "First argument ('key') has to be of type string!");
}

auto keyName = arguments[0].getString(runtime).utf8(runtime);
instance->removeValueForKey(keyName);
return jsi::Value::undefined();
});
}

if (propName == "getAllKeys") {
// MMKV.getAllKeys()
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName), 0,
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
auto keys = instance->allKeys();
auto array = jsi::Array(runtime, keys.size());
for (int i = 0; i < keys.size(); i++) {
array.setValueAtIndex(runtime, i, keys[i]);
}
return array;
});
}

if (propName == "clearAll") {
// MMKV.clearAll()
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName), 0,
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
instance->clearAll();
return jsi::Value::undefined();
});
}

if (propName == "recrypt") {
// MMKV.recrypt(encryptionKey)
return jsi::Function::createFromHostFunction(
runtime, jsi::PropNameID::forAscii(runtime, funcName),
1, // encryptionKey
[this](jsi::Runtime& runtime, const jsi::Value& thisValue, const jsi::Value* arguments,
size_t count) -> jsi::Value {
if (arguments[0].isUndefined()) {
// reset encryption key to "no encryption"
instance->reKey(std::string());
} else if (arguments[0].isString()) {
// reKey(..) with new encryption-key
auto encryptionKey = arguments[0].getString(runtime).utf8(runtime);
instance->reKey(encryptionKey);
} else {
throw jsi::JSError(
runtime,
"First argument ('encryptionKey') has to be of type string (or undefined)!");
}
return jsi::Value::undefined();
});
}

return jsi::Value::undefined();
}
26 changes: 26 additions & 0 deletions cpp/MmkvHostObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//
// MmkvHostObject.h
// Mmkv
//
// Created by Marc Rousavy on 03.09.21.
// Copyright © 2021 Facebook. All rights reserved.
//

#pragma once

#include <MMKV.h>
#include <jsi/jsi.h>

using namespace facebook;

class JSI_EXPORT MmkvHostObject : public jsi::HostObject {
public:
MmkvHostObject(const std::string& instanceId, std::string path, std::string cryptKey);

public:
jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override;
std::vector<jsi::PropNameID> getPropertyNames(jsi::Runtime& rt) override;

private:
MMKV* instance;
};
42 changes: 36 additions & 6 deletions cpp/react-native-mmkv.h
Original file line number Diff line number Diff line change
@@ -1,8 +1,38 @@
#ifndef MMKV_H
#define MMKV_H
//
// react-native-mmkv.h
// react-native-mmkv
//
// Created by Marc Rousavy on 25.03.2024.
//

namespace mmkv {
double multiply(double a, double b);
}
#pragma once

#if __has_include(<React-Codegen/RNMmkvSpecJSI.h>)
// CocoaPods include (iOS)
#include <React-Codegen/RNMmkvSpecJSI.h>
#elif __has_include("RNMmkvSpecJSI.h")
// CMake include on Android
#include "RNMmkvSpecJSI.h"
#else
#error Cannot find react-native-mmkv spec! Try cleaning your cache and re-running CodeGen!
#endif

#include "MmkvHostObject.h"

namespace facebook::react {

#endif /* MMKV_H */
class NativeMmkvModule : public NativeMmkvCxxSpec<NativeMmkvModule> {
public:
NativeMmkvModule(std::shared_ptr<CallInvoker> jsInvoker);

jsi::Object createMMKV(jsi::Runtime& runtime, jsi::Object config) {
std::string instanceId = bridging::fromJs<std::string>(runtime, config.getProperty(runtime, "id"), jsInvoker_);
std::string path = bridging::fromJs<std::string>(runtime, config.getProperty(runtime, "path"), jsInvoker_);
std::string encryptionKey = bridging::fromJs<std::string>(runtime, config.getProperty(runtime, "encryptionKey"), jsInvoker_);

auto instance = std::make_shared<MmkvHostObject>(instanceId, path, encryptionKey);
return jsi::Object::createFromHostObject(runtime, instance);
}
};

}
2 changes: 0 additions & 2 deletions example/ios/.xcode.env.local

This file was deleted.

0 comments on commit f113a18

Please sign in to comment.