[NEW] E2E Encryption push (iOS) (#2463)

* link pods to notification service

* push encryption poc

* decrypt room key poc

* read user key from mmkv and cast into a pkcs

* push decrypt poc (iOS)

* expose needed watermelon methods

* watermelon -> database

* indent & simple-crypto update

* string extensions

* storage

* toBase64 -> toData

* remove a forced unwrap

* remove unused import

* database driver

* improvement

* folder structure & watermelon bridge

* more improvement stuff

* watermelon -> database

* reuse database instance

* improvement

* database fix: bypass watermelon cache

* some code improvements

* encryption instances

* start api stuff

* network layer

* improve notification service

* improve folder structure

* watermelon patch

* retry fetch logic

* rocketchat class

* fix try to decrypt without a roomKey

* fallback to original content that is translated

* some fixes to rocketchat logic

* merge develop

* remove unnecessary extension

* [CHORE] Improve reply notification code (iOS)

* undo sign changes

* remove mocked value

* import direct from library

* send message request

* reply notification with encrypted message working properly

* revert apple sign

* fix api onerror

* trick to display sender name on group notifications

* revert data.host change

* fix some multithread issues

* use sendername sent by server

* small improvement

* Bump crypto lib

* Update ios/NotificationService/NotificationService.swift

* add experimental string

* remove trailing slash

* remove trailing slash on reply

* fix decrypt messages

Co-authored-by: Diego Mello <diegolmello@gmail.com>
This commit is contained in:
Djorkaeff Alexandre 2020-09-24 15:34:13 -03:00 committed by GitHub
parent f30c405de3
commit 60dc128c63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
68 changed files with 35806 additions and 26141 deletions

View File

@ -43,7 +43,8 @@ class Encryption {
}
// Initialize Encryption client
initialize = () => {
initialize = (userId) => {
this.userId = userId;
this.roomInstances = {};
// Don't await these promises
@ -69,6 +70,7 @@ class Encryption {
// Stop Encryption client
stop = () => {
this.userId = null;
this.privateKey = null;
this.roomInstances = {};
// Cancel ongoing encryption/decryption requests
@ -199,7 +201,7 @@ class Encryption {
// If doesn't have a instance of this room
if (!this.roomInstances[rid]) {
this.roomInstances[rid] = new EncryptionRoom(rid);
this.roomInstances[rid] = new EncryptionRoom(rid, this.userId);
}
const roomE2E = this.roomInstances[rid];

View File

@ -21,9 +21,10 @@ import database from '../database';
import log from '../../utils/log';
export default class EncryptionRoom {
constructor(roomId) {
constructor(roomId, userId) {
this.ready = false;
this.roomId = roomId;
this.userId = userId;
this.establishing = false;
this.readyPromise = new Deferred();
this.readyPromise.then(() => {

View File

@ -79,7 +79,7 @@ const handleEncryptionInit = function* handleEncryptionInit() {
}
// Decrypt all pending messages/subscriptions
Encryption.initialize();
Encryption.initialize(user.id);
} catch (e) {
log(e);
}
@ -109,7 +109,7 @@ const handleEncryptionDecodeKey = function* handleEncryptionDecodeKey({ password
yield Encryption.persistKeys(server, publicKey, privateKey);
// Decrypt all pending messages/subscriptions
Encryption.initialize();
Encryption.initialize(user.id);
// Hide encryption banner
yield put(encryptionSetBanner());

View File

@ -2,5 +2,8 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "SecureStorage.h"
#import <MMKVAppExtension/MMKV.h>
#import <MMKV/MMKV.h>
#import <react-native-mmkv-storage/SecureStorage.h>
#import <react-native-simple-crypto/Aes.h>
#import <react-native-simple-crypto/Rsa.h>
#import <react-native-simple-crypto/Shared.h>

View File

@ -1,46 +1,10 @@
import CoreLocation
import UserNotifications
struct PushResponse: Decodable {
let success: Bool
let data: Data
struct Data: Decodable {
let notification: Notification
struct Notification: Decodable {
let notId: Int
let title: String
let text: String
let payload: Payload
struct Payload: Decodable, Encodable {
let host: String
let rid: String?
let type: String?
let sender: Sender?
let messageId: String
let notificationType: String?
let name: String?
let messageType: String?
struct Sender: Decodable, Encodable {
let _id: String
let username: String
let name: String?
}
}
}
}
}
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
var retryCount = 0
var retryTimeout = [1.0, 3.0, 5.0, 10.0]
var rocketchat: RocketChat?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
@ -48,130 +12,44 @@ class NotificationService: UNNotificationServiceExtension {
if let bestAttemptContent = bestAttemptContent {
let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)!
guard let data = try? (JSONDecoder().decode(PushResponse.Data.Notification.Payload.self, from: ejson)) else {
guard let data = try? (JSONDecoder().decode(Payload.self, from: ejson)) else {
return
}
let notificationType = data.notificationType ?? ""
rocketchat = RocketChat.instanceForServer(server: data.host.removeTrailingSlash())
// If the notification have the content at her payload, show it
if notificationType != "message-id-only" {
contentHandler(bestAttemptContent)
// If the notification has the content on the payload, show it
if data.notificationType != .messageIdOnly {
self.processPayload(payload: data)
return
}
let mmapID = "default"
let instanceID = "com.MMKV.\(mmapID)"
let secureStorage = SecureStorage()
var cryptKey: Data = Data()
// get mmkv instance password from keychain
secureStorage.getSecureKey(instanceID.toHex()) { (response) -> () in
guard let password = response?[1] as? String else {
// kill the process and show notification as it came from APN
exit(0)
// Request the content from server
rocketchat?.getPushWithId(data.messageId) { notification in
if let notification = notification {
self.bestAttemptContent?.title = notification.title
self.bestAttemptContent?.body = notification.text
self.processPayload(payload: notification.payload)
}
cryptKey = password.data(using: .utf8)!
}
// Get App Group directory
let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else {
return
}
// Set App Group dir
MMKV.initialize(rootDir: nil, groupDir: directory.path, logLevel: MMKVLogLevel.none)
guard let mmkv = MMKV(mmapID: mmapID, cryptKey: cryptKey, mode: MMKVMode.multiProcess) else {
return
}
var server = data.host
if (server.last == "/") {
server.removeLast()
}
let msgId = data.messageId
let userId = mmkv.string(forKey: "reactnativemeteor_usertoken-\(server)") ?? ""
let token = mmkv.string(forKey: "reactnativemeteor_usertoken-\(userId)") ?? ""
if userId.isEmpty || token.isEmpty {
contentHandler(bestAttemptContent)
return
}
var urlComponents = URLComponents(string: "\(server)/api/v1/push.get")!
let queryItems = [URLQueryItem(name: "id", value: msgId)]
urlComponents.queryItems = queryItems
var request = URLRequest(url: urlComponents.url!)
request.httpMethod = "GET"
request.addValue(userId, forHTTPHeaderField: "x-user-id")
request.addValue(token, forHTTPHeaderField: "x-auth-token")
runRequest(request: request, bestAttemptContent: bestAttemptContent, contentHandler: contentHandler)
}
}
func runRequest(request: URLRequest, bestAttemptContent: UNMutableNotificationContent, contentHandler: @escaping (UNNotificationContent) -> Void) {
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
func retryRequest() {
// if we can try again
if self.retryCount < self.retryTimeout.count {
// Try again after X seconds
DispatchQueue.main.asyncAfter(deadline: .now() + self.retryTimeout[self.retryCount], execute: {
self.runRequest(request: request, bestAttemptContent: bestAttemptContent, contentHandler: contentHandler)
self.retryCount += 1
})
}
}
// If some error happened
if error != nil {
retryRequest()
// Check if the request did successfully
} else if let response = response as? HTTPURLResponse {
// if it not was successfully
if response.statusCode != 200 {
retryRequest()
// If the response status is 200
} else {
// Process data
if let data = data {
// Parse data of response
let push = try? (JSONDecoder().decode(PushResponse.self, from: data))
if let push = push {
if push.success {
bestAttemptContent.title = push.data.notification.title
bestAttemptContent.body = push.data.notification.text
let payload = try? (JSONEncoder().encode(push.data.notification.payload))
if let payload = payload {
bestAttemptContent.userInfo["ejson"] = String(data: payload, encoding: .utf8) ?? "{}"
}
// Show notification with the content modified
contentHandler(bestAttemptContent)
return
}
}
}
retryRequest()
}
}
}
task.resume()
func processPayload(payload: Payload) {
// If is a encrypted message
if payload.messageType == .e2e {
if let message = payload.msg, let rid = payload.rid {
if let decryptedMessage = rocketchat?.decryptMessage(rid: rid, message: message) {
bestAttemptContent?.body = decryptedMessage
if let roomType = payload.type, roomType == .group, let sender = payload.senderName {
bestAttemptContent?.body = "\(sender): \(decryptedMessage)"
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
if let bestAttemptContent = bestAttemptContent {
contentHandler?(bestAttemptContent)
}
}
}

View File

@ -1,48 +0,0 @@
//
// SecureStorage.h
// RocketChatRN
//
// https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/ios/SecureStorage.h
//
#if __has_include("RCTBridgeModule.h")
#import "RCTBridgeModule.h"
#else
#import <React/RCTBridgeModule.h>
#endif
#import <Foundation/Foundation.h>
@interface SecureStorage: NSObject
- (void) setSecureKey: (nonnull NSString *)key value:(nonnull NSString *)value
options: (nonnull NSDictionary *)options
callback:(nullable RCTResponseSenderBlock)callback;
- (nullable NSString *) getSecureKey:(nonnull NSString *)key
callback:(nullable RCTResponseSenderBlock)callback;
- (bool) secureKeyExists:(nonnull NSString *)key
callback:(nullable RCTResponseSenderBlock)callback;
- (void) removeSecureKey:(nonnull NSString *)key
callback:(nullable RCTResponseSenderBlock)callback;
- (BOOL)searchKeychainCopyMatchingExists:(nonnull NSString *)identifier;
- (nonnull NSString *)searchKeychainCopyMatching:(nonnull NSString *)identifier;
- (nonnull NSMutableDictionary *)newSearchDictionary:(nonnull NSString *)identifier;
- (BOOL)createKeychainValue:(nonnull NSString *)value forIdentifier:(nonnull NSString *)identifier options: (NSDictionary * __nullable)options;
- (BOOL)updateKeychainValue:(nonnull NSString *)password forIdentifier:(nonnull NSString *)identifier options:(NSDictionary * __nullable)options;
- (BOOL)deleteKeychainValue:(nonnull NSString *)identifier;
- (void)clearSecureKeyStore;
- (void)handleAppUninstallation;
@end

View File

@ -1,287 +0,0 @@
//
// SecureStorage.m
// NotificationService
//
// https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/ios/SecureStorage.m
// Refer to /patches/react-native-mmkv-storage+0.3.5.patch
#if __has_include("RCTBridgeModule.h")
#import "RCTBridgeModule.h"
#else
#import <React/RCTBridgeModule.h>
#endif
#import "SecureStorage.h"
@implementation SecureStorage : NSObject
NSString *serviceName;
- (void) setSecureKey: (NSString *)key value:(NSString *)value
options: (NSDictionary *)options
callback:(RCTResponseSenderBlock)callback
{
@try {
[self handleAppUninstallation];
BOOL status = [self createKeychainValue: value forIdentifier: key options: options];
if (status) {
callback(@[[NSNull null],@"Key updated successfully" ]);
} else {
BOOL status = [self updateKeychainValue: value forIdentifier: key options: options];
if (status) {
callback(@[[NSNull null],@"Key updated successfully" ]);
} else {
callback(@[@"An error occurred", [NSNull null]]);
}
}
}
@catch (NSException *exception) {
callback(@[exception.reason, [NSNull null]]);
}
}
- (NSString *) getSecureKey:(NSString *)key
callback:(RCTResponseSenderBlock)callback
{
@try {
[self handleAppUninstallation];
NSString *value = [self searchKeychainCopyMatching:key];
if (value == nil) {
NSString* errorMessage = @"key does not present";
if (callback != NULL) {
callback(@[errorMessage, [NSNull null]]);
}
return NULL;
} else {
if (callback != NULL) {
callback(@[[NSNull null], value]);
}
return value;
}
}
@catch (NSException *exception) {
if (callback != NULL) {
callback(@[exception.reason, [NSNull null]]);
}
return NULL;
}
}
- (bool) secureKeyExists:(NSString *)key
callback:(RCTResponseSenderBlock)callback
{
@try {
[self handleAppUninstallation];
BOOL exists = [self searchKeychainCopyMatchingExists:key];
if (exists) {
if (callback != NULL) {
callback(@[[NSNull null], @true]);
}
return true;
} else {
if (callback != NULL) {
callback(@[[NSNull null], @false]);
}
return false;
}
}
@catch(NSException *exception) {
if (callback != NULL) {
callback(@[exception.reason, [NSNull null]]);
}
return NULL;
}
}
- (void) removeSecureKey:(NSString *)key
callback:(RCTResponseSenderBlock)callback
{
@try {
BOOL status = [self deleteKeychainValue:key];
if (status) {
callback(@[[NSNull null], @"key removed successfully"]);
} else {
NSString* errorMessage = @"Could not find the key to delete.";
callback(@[errorMessage, [NSNull null]]);
}
}
@catch(NSException *exception) {
callback(@[exception.reason, [NSNull null]]);
}
}
- (NSMutableDictionary *)newSearchDictionary:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [[NSMutableDictionary alloc] init];
// this value is shared by main app and extensions, so, is the best to be used here
serviceName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"];
[searchDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrGeneric];
[searchDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
[searchDictionary setObject:serviceName forKey:(id)kSecAttrService];
NSString *keychainGroup = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"KeychainGroup"];
[searchDictionary setObject:keychainGroup forKey:(id)kSecAttrAccessGroup];
return searchDictionary;
}
- (NSString *)searchKeychainCopyMatching:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
// Add search attributes
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
// Add search return types
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
NSDictionary *found = nil;
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
(CFTypeRef *)&result);
NSString *value = nil;
found = (__bridge NSDictionary*)(result);
if (found) {
value = [[NSString alloc] initWithData:found encoding:NSUTF8StringEncoding];
}
return value;
}
- (BOOL)searchKeychainCopyMatchingExists:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
// Add search attributes
[searchDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
// Add search return types
[searchDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((CFDictionaryRef)searchDictionary,
(CFTypeRef *)&result);
if (status != errSecItemNotFound) {
return YES;
}
return NO;
}
- (BOOL)createKeychainValue:(NSString *)value forIdentifier:(NSString *)identifier options: (NSDictionary * __nullable)options {
CFStringRef accessible = accessibleValue(options);
NSMutableDictionary *dictionary = [self newSearchDictionary:identifier];
NSData *valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
[dictionary setObject:valueData forKey:(id)kSecValueData];
dictionary[(__bridge NSString *)kSecAttrAccessible] = (__bridge id)accessible;
OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (BOOL)updateKeychainValue:(NSString *)password forIdentifier:(NSString *)identifier options:(NSDictionary * __nullable)options {
CFStringRef accessible = accessibleValue(options);
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
NSMutableDictionary *updateDictionary = [[NSMutableDictionary alloc] init];
NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
[updateDictionary setObject:passwordData forKey:(id)kSecValueData];
updateDictionary[(__bridge NSString *)kSecAttrAccessible] = (__bridge id)accessible;
OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
(CFDictionaryRef)updateDictionary);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (BOOL)deleteKeychainValue:(NSString *)identifier {
NSMutableDictionary *searchDictionary = [self newSearchDictionary:identifier];
OSStatus status = SecItemDelete((CFDictionaryRef)searchDictionary);
if (status == errSecSuccess) {
return YES;
}
return NO;
}
- (void)clearSecureKeyStore
{
NSArray *secItemClasses = @[(__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrGeneric,
(__bridge id)kSecAttrAccount,
(__bridge id)kSecClassKey,
(__bridge id)kSecAttrService];
for (id secItemClass in secItemClasses) {
NSDictionary *spec = @{(__bridge id)kSecClass: secItemClass};
SecItemDelete((__bridge CFDictionaryRef)spec);
}
}
- (void)handleAppUninstallation
{
// use app group user defaults to prevent clear when it's share extension
NSString *suiteName = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"];
NSUserDefaults *userDefaults = [[NSUserDefaults alloc] initWithSuiteName:suiteName];
if (![userDefaults boolForKey:@"RnSksIsAppInstalled"]) {
[self clearSecureKeyStore];
[userDefaults setBool:YES forKey:@"RnSksIsAppInstalled"];
[userDefaults synchronize];
}
}
NSError * secureKeyStoreError(NSString *errMsg)
{
NSError *error = [NSError errorWithDomain:serviceName code:200 userInfo:@{@"reason": errMsg}];
return error;
}
CFStringRef accessibleValue(NSDictionary *options)
{
if (options && options[@"accessible"] != nil) {
NSDictionary *keyMap = @{
@"AccessibleWhenUnlocked": (__bridge NSString *)kSecAttrAccessibleWhenUnlocked,
@"AccessibleAfterFirstUnlock": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlock,
@"AccessibleAlways": (__bridge NSString *)kSecAttrAccessibleAlways,
@"AccessibleWhenPasscodeSetThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly,
@"AccessibleWhenUnlockedThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
@"AccessibleAfterFirstUnlockThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
@"AccessibleAlwaysThisDeviceOnly": (__bridge NSString *)kSecAttrAccessibleAlwaysThisDeviceOnly
};
NSString *result = keyMap[options[@"accessible"]];
if (result) {
return (__bridge CFStringRef)result;
}
}
return kSecAttrAccessibleAfterFirstUnlock;
}
@end

View File

@ -1,13 +0,0 @@
//
// String+Hex.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 8/6/20.
// Copyright © 2020 Facebook. All rights reserved.
//
extension String {
func toHex() -> String {
return unicodeScalars.map{ .init($0.value, radix: 16, uppercase: false) }.joined()
}
}

View File

@ -20,7 +20,7 @@ end
# used to get user credentials
target 'NotificationService' do
pod 'MMKVAppExtension'
all_pods
end
post_install do |installer|

View File

@ -186,8 +186,6 @@ PODS:
- libwebp/webp (1.1.0)
- MMKV (1.2.1):
- MMKVCore (~> 1.2.1)
- MMKVAppExtension (1.2.1):
- MMKVCore (~> 1.2.1)
- MMKVCore (1.2.1)
- nanopb (1.30905.0):
- nanopb/decode (= 1.30905.0)
@ -386,7 +384,7 @@ PODS:
- React
- react-native-safe-area-context (3.1.1):
- React
- react-native-simple-crypto (0.3.1):
- react-native-simple-crypto (0.4.0):
- OpenSSL-Universal
- React
- react-native-slider (3.0.2):
@ -578,7 +576,6 @@ DEPENDENCIES:
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- KeyCommands (from `../node_modules/react-native-keycommands`)
- MMKVAppExtension
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
@ -676,7 +673,6 @@ SPEC REPOS:
- JitsiMeetSDK
- libwebp
- MMKV
- MMKVAppExtension
- MMKVCore
- nanopb
- OpenSSL-Universal
@ -904,7 +900,6 @@ SPEC CHECKSUMS:
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
MMKVAppExtension: d792aa7bd301285e2c3100c5ce15aa44fa26456f
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
@ -929,7 +924,7 @@ SPEC CHECKSUMS:
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
react-native-simple-crypto: 5e4f2877f71675d95baabf5823cd0cc0c379d0e6
react-native-simple-crypto: 564740fd8124827d82e9e8ded4a0de8c695c8a4d
react-native-slider: 0221b417686c5957f6e77cd9ac22c1478a165355
react-native-webview: 679b6f400176e2ea8a785acf7ae16cf282e7d1eb
React-RCTActionSheet: 1702a1a85e550b5c36e2e03cb2bd3adea053de95
@ -983,6 +978,6 @@ SPEC CHECKSUMS:
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 4916aa46fbfaed764171540e6ed76fb07a8a95e7
PODFILE CHECKSUM: 19c78b598c807d2c6b988fd11b24544ac1895a35
COCOAPODS: 1.9.3

View File

@ -1 +0,0 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h

View File

@ -1 +0,0 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h

View File

@ -1 +0,0 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h

View File

@ -1 +0,0 @@
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h

View File

@ -1,6 +1,6 @@
{
"name": "react-native-simple-crypto",
"version": "0.3.1",
"version": "0.4.0",
"summary": "A simpler React-Native crypto library",
"authors": "Gary Button <gary.button.public@gmail.com>",
"license": "MIT",

View File

@ -1,189 +0,0 @@
Tencent is pleased to support the open source community by making MMKV available.
Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved.
If you have downloaded a copy of the MMKV binary from Tencent, please note that the MMKV binary is licensed under the BSD 3-Clause License.
If you have downloaded a copy of the MMKV source code from Tencent, please note that MMKV source code is licensed under the BSD 3-Clause License, except for the third-party components listed below which are subject to different license terms. Your integration of MMKV into your own projects may require compliance with the BSD 3-Clause License, as well as the other licenses applicable to the third-party components included within MMKV.
A copy of the BSD 3-Clause License is included in this file.
Other dependencies and licenses:
Open Source Software Licensed Under the OpenSSL License:
----------------------------------------------------------------------------------------
1. OpenSSL 1.1.0i
Copyright (c) 1998-2018 The OpenSSL Project.
All rights reserved.
Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
All rights reserved.
Terms of the OpenSSL License:
---------------------------------------------------
LICENSE ISSUES:
--------------------------------------------------------------------
The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit.
See below for the actual license texts.
OpenSSL License:
--------------------------------------------------------------------
Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgment:
"This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to endorse or promote products derived from this software without prior written permission. For written permission, please contact openssl-core@openssl.org.
5. Products derived from this software may not be called "OpenSSL" nor may "OpenSSL" appear in their names without prior written permission of the OpenSSL Project.
6. Redistributions of any form whatsoever must retain the following acknowledgment: "This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit (http://www.openssl.org/)"
THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
====================================================================
* This product includes cryptographic software written by Eric Young (eay@cryptsoft.com). This product includes software written by Tim Hudson (tjh@cryptsoft.com).
Original SSLeay License:
--------------------------------------------------------------------
Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
All rights reserved.
This package is an SSL implementation written by Eric Young (eay@cryptsoft.com).
The implementation was written so as to conform with Netscapes SSL.
This library is free for commercial and non-commercial use as long as the following conditions are aheared to. The following conditions apply to all code found in this distribution, be it the RC4, RSA, lhash, DES, etc., code; not just the SSL code. The SSL documentation included with this distribution is covered by the same copyright terms except that the holder is Tim Hudson (tjh@cryptsoft.com).
Copyright remains Eric Young's, and as such any Copyright notices in the code are not to be removed. If this package is used in a product, Eric Young should be given attribution as the author of the parts of the library used. This can be in the form of a textual message at program startup or in documentation (online or textual) provided with the package.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgement:" This product includes cryptographic software written by Eric Young (eay@cryptsoft.com)" The word 'cryptographic' can be left out if the rouines from the library being used are not cryptographic related :-).
4. If you include any Windows specific code (or a derivative thereof) from the apps directory (application code) you must include an acknowledgement: "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
The licence and distribution terms for any publically available version or derivative of this code cannot be changed. i.e. this code cannot simply be copied and put under another distribution licence [including the GNU Public Licence.]
Open Source Software Licensed Under the Apache License, Version 2.0:
The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2018 THL A29 Limited.
----------------------------------------------------------------------------------------
1. MultiprocessSharedPreferences v1.0
Copyright (C) 2014 seven456@gmail.com
Terms of the Apache License, Version 2.0:
--------------------------------------------------------------------
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
“License” shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
“Licensor” shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
“Legal Entity” shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, “control” means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
“You” (or “Your”) shall mean an individual or Legal Entity exercising permissions granted by this License.
“Source” form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
“Object” form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
“Work” shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
“Derivative Works” shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
“Contribution” shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, “submitted” means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as “Not a Contribution.”
“Contributor” shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
a) You must give any other recipients of the Work or Derivative Works a copy of this License; and
b) You must cause any modified files to carry prominent notices stating that You changed the files; and
c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
d) If the Work includes a “NOTICE” text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Open Source Software Licensed Under the zlib License:
The below software in this distribution may have been modified by THL A29 Limited (“Tencent Modifications”). All Tencent Modifications are Copyright (C) 2018 THL A29 Limited.
----------------------------------------------------------------------------------------
1. zlib v1.2.11
Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler
Terms of the zlib License:
--------------------------------------------------------------------
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Terms of the BSD 3-Clause License:
--------------------------------------------------------------------
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
Neither the name of [copyright holder] nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,287 +0,0 @@
[![license](https://img.shields.io/badge/license-BSD_3-brightgreen.svg?style=flat)](https://github.com/Tencent/MMKV/blob/master/LICENSE.TXT)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/Tencent/MMKV/pulls)
[![Release Version](https://img.shields.io/badge/release-1.2.1-brightgreen.svg)](https://github.com/Tencent/MMKV/releases)
[![Platform](https://img.shields.io/badge/Platform-%20Android%20%7C%20iOS%2FmacOS%20%7C%20Win32%20%7C%20POSIX-brightgreen.svg)](https://github.com/Tencent/MMKV/wiki/home)
中文版本请参看[这里](./readme_cn.md)
MMKV is an **efficient**, **small**, **easy-to-use** mobile key-value storage framework used in the WeChat application. It's currently available on **Android**, **iOS/macOS**, **Win32** and **POSIX**.
# MMKV for Android
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of Android to achieve best performance.
* **Multi-Process concurrency**: MMKV supports concurrent read-read and read-write access between processes.
* **Easy-to-use**. You can use MMKV as you go. All changes are saved immediately, no `sync`, no `apply` calls needed.
* **Small**.
* **A handful of files**: MMKV contains process locks, encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **About 50K in binary size**: MMKV adds about 50K per architecture on App size, and much less when zipped (apk).
## Getting Started
### Installation Via Maven
Add the following lines to `build.gradle` on your app module:
```gradle
dependencies {
implementation 'com.tencent:mmkv-static:1.2.1'
// replace "1.2.1" with any available version
}
```
For other installation options, see [Android Setup](https://github.com/Tencent/MMKV/wiki/android_setup).
### Quick Tutorial
You can use MMKV as you go. All changes are saved immediately, no `sync`, no `apply` calls needed.
Setup MMKV on App startup, say your `Application` class, add these lines:
```Java
public void onCreate() {
super.onCreate();
String rootDir = MMKV.initialize(this);
System.out.println("mmkv root: " + rootDir);
//……
}
```
MMKV has a global instance, that can be used directly:
```Java
import com.tencent.mmkv.MMKV;
MMKV kv = MMKV.defaultMMKV();
kv.encode("bool", true);
boolean bValue = kv.decodeBool("bool");
kv.encode("int", Integer.MIN_VALUE);
int iValue = kv.decodeInt("int");
kv.encode("string", "Hello from mmkv");
String str = kv.decodeString("string");
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found here [Android Tutorial](https://github.com/Tencent/MMKV/wiki/android_tutorial).
## Performance
Writing random `int` for 1000 times, we get this chart:
![](https://github.com/Tencent/MMKV/wiki/assets/profile_android_mini.png)
For more benchmark data, please refer to [our benchmark](https://github.com/Tencent/MMKV/wiki/android_benchmark).
# MMKV for iOS/macOS
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of iOS/macOS to achieve best performance.
* **Easy-to-use**. You can use MMKV as you go, no configurations needed. All changes are saved immediately, no `synchronize` calls needed.
* **Small**.
* **A handful of files**: MMKV contains encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **Less than 30K in binary size**: MMKV adds less than 30K per architecture on App size, and much less when zipped (ipa).
## Getting Started
### Installation Via CocoaPods:
1. Install [CocoaPods](https://guides.CocoaPods.org/using/getting-started.html);
2. Open terminal, `cd` to your project directory, run `pod repo update` to make CocoaPods aware of the latest available MMKV versions;
3. Edit your Podfile, add `pod 'MMKV'` to your app target;
4. Run `pod install`;
5. Open the `.xcworkspace` file generated by CocoaPods;
6. Add `#import <MMKV/MMKV.h>` to your source file and we are done.
For other installation options, see [iOS/macOS Setup](https://github.com/Tencent/MMKV/wiki/iOS_setup).
### Quick Tutorial
You can use MMKV as you go, no configurations needed. All changes are saved immediately, no `synchronize` calls needed.
Setup MMKV on App startup, in your `-[MyApp application: didFinishLaunchingWithOptions:]`, add these lines:
```objective-c
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// init MMKV in the main thread
[MMKV initializeMMKV:nil];
//...
return YES;
}
```
MMKV has a global instance, that can be used directly:
```objective-c
MMKV *mmkv = [MMKV defaultMMKV];
[mmkv setBool:YES forKey:@"bool"];
BOOL bValue = [mmkv getBoolForKey:@"bool"];
[mmkv setInt32:-1024 forKey:@"int32"];
int32_t iValue = [mmkv getInt32ForKey:@"int32"];
[mmkv setString:@"hello, mmkv" forKey:@"string"];
NSString *str = [mmkv getStringForKey:@"string"];
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found [here](https://github.com/Tencent/MMKV/wiki/iOS_tutorial).
## Performance
Writing random `int` for 10000 times, we get this chart:
![](https://github.com/Tencent/MMKV/wiki/assets/profile_mini.png)
For more benchmark data, please refer to [our benchmark](https://github.com/Tencent/MMKV/wiki/iOS_benchmark).
# MMKV for Win32
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of Windows to achieve best performance.
* **Multi-Process concurrency**: MMKV supports concurrent read-read and read-write access between processes.
* **Easy-to-use**. You can use MMKV as you go. All changes are saved immediately, no `save`, no `sync` calls needed.
* **Small**.
* **A handful of files**: MMKV contains process locks, encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **About 10K in binary size**: MMKV adds about 10K on application size, and much less when zipped.
## Getting Started
### Installation Via Source
1. Getting source code from git repository:
```
git clone https://github.com/Tencent/MMKV.git
```
2. Add `Win32/MMKV/MMKV.vcxproj` to your solution;
3. Add `MMKV` project to your project's dependencies;
4. Add `$(OutDir)include` to your project's `C/C++` -> `General` -> `Additional Include Directories`;
5. Add `$(OutDir)` to your project's `Linker` -> `General` -> `Additional Library Directories`;
6. Add `MMKV.lib` to your project's `Linker` -> `Input` -> `Additional Dependencies`;
7. Add `#include <MMKV/MMKV.h>` to your source file and we are done.
note:
1. MMKV is compiled with `MT/MTd` runtime by default. If your project uses `MD/MDd`, you should change MMKV's setting to match your project's (`C/C++` -> `Code Generation` -> `Runtime Library`), or vise versa.
2. MMKV is developed with Visual Studio 2017, change the `Platform Toolset` if you use a different version of Visual Studio.
For other installation options, see [Win32 Setup](https://github.com/Tencent/MMKV/wiki/windows_setup).
### Quick Tutorial
You can use MMKV as you go. All changes are saved immediately, no `sync`, no `save` calls needed.
Setup MMKV on App startup, say in your `main()`, add these lines:
```C++
#include <MMKV/MMKV.h>
int main() {
std::wstring rootDir = getYourAppDocumentDir();
MMKV::initializeMMKV(rootDir);
//...
}
```
MMKV has a global instance, that can be used directly:
```C++
auto mmkv = MMKV::defaultMMKV();
mmkv->set(true, "bool");
std::cout << "bool = " << mmkv->getBool("bool") << std::endl;
mmkv->set(1024, "int32");
std::cout << "int32 = " << mmkv->getInt32("int32") << std::endl;
mmkv->set("Hello, MMKV for Win32", "string");
std::string result;
mmkv->getString("string", result);
std::cout << "string = " << result << std::endl;
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found here [Win32 Tutorial](https://github.com/Tencent/MMKV/wiki/windows_tutorial).
# MMKV for POSIX
## Features
* **Efficient**. MMKV uses mmap to keep memory synced with file, and protobuf to encode/decode values, making the most of POSIX to achieve best performance.
* **Multi-Process concurrency**: MMKV supports concurrent read-read and read-write access between processes.
* **Easy-to-use**. You can use MMKV as you go. All changes are saved immediately, no `save`, no `sync` calls needed.
* **Small**.
* **A handful of files**: MMKV contains process locks, encode/decode helpers and mmap logics and nothing more. It's really tidy.
* **About 7K in binary size**: MMKV adds about 7K on application size, and much less when zipped.
## Getting Started
### Installation Via CMake
1. Getting source code from git repository:
```
git clone https://github.com/Tencent/MMKV.git
```
2. Edit your `CMakeLists.txt`, add those lines:
```cmake
add_subdirectory(mmkv/POSIX/src mmkv)
target_link_libraries(MyApp
mmkv)
```
3. Add `#include "MMKV.h"` to your source file and we are done.
For other installation options, see [POSIX Setup](https://github.com/Tencent/MMKV/wiki/posix_setup).
### Quick Tutorial
You can use MMKV as you go. All changes are saved immediately, no `sync`, no `save` calls needed.
Setup MMKV on App startup, say in your `main()`, add these lines:
```C++
#include "MMKV.h"
int main() {
std::string rootDir = getYourAppDocumentDir();
MMKV::initializeMMKV(rootDir);
//...
}
```
MMKV has a global instance, that can be used directly:
```C++
auto mmkv = MMKV::defaultMMKV();
mmkv->set(true, "bool");
std::cout << "bool = " << mmkv->getBool("bool") << std::endl;
mmkv->set(1024, "int32");
std::cout << "int32 = " << mmkv->getInt32("int32") << std::endl;
mmkv->set("Hello, MMKV for Win32", "string");
std::string result;
mmkv->getString("string", result);
std::cout << "string = " << result << std::endl;
```
MMKV also supports **Multi-Process Access**. Full tutorials can be found here [POSIX Tutorial](https://github.com/Tencent/MMKV/wiki/posix_tutorial).
## License
MMKV is published under the BSD 3-Clause license. For details check out the [LICENSE.TXT](./LICENSE.TXT).
## Change Log
Check out the [CHANGELOG.md](./CHANGELOG.md) for details of change history.
## Contributing
If you are interested in contributing, check out the [CONTRIBUTING.md](./CONTRIBUTING.md), also join our [Tencent OpenSource Plan](https://opensource.tencent.com/contribution).
To give clarity of what is expected of our members, MMKV has adopted the code of conduct defined by the Contributor Covenant, which is widely used. And we think it articulates our values well. For more, check out the [Code of Conduct](./CODE_OF_CONDUCT.md).
## FAQ & Feedback
Check out the [FAQ](https://github.com/Tencent/MMKV/wiki/FAQ) first. Should there be any questions, don't hesitate to create [issues](https://github.com/Tencent/MMKV/issues).

View File

@ -1,222 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2018 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MMKVHandler.h"
typedef NS_ENUM(NSUInteger, MMKVMode) {
MMKVSingleProcess = 0x1,
MMKVMultiProcess = 0x2,
};
@interface MMKV : NSObject
NS_ASSUME_NONNULL_BEGIN
/// call this in main thread, before calling any other MMKV methods
/// @param rootDir the root dir of MMKV, passing nil defaults to {NSDocumentDirectory}/mmkv
/// @return root dir of MMKV
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir NS_SWIFT_NAME(initialize(rootDir:));
/// call this in main thread, before calling any other MMKV methods
/// @param rootDir the root dir of MMKV, passing nil defaults to {NSDocumentDirectory}/mmkv
/// @param logLevel MMKVLogInfo by default, MMKVLogNone to disable all logging
/// @return root dir of MMKV
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir logLevel:(MMKVLogLevel)logLevel NS_SWIFT_NAME(initialize(rootDir:logLevel:));
/// call this in main thread, before calling any other MMKV methods
/// @param rootDir the root dir of MMKV, passing nil defaults to {NSDocumentDirectory}/mmkv
/// @param groupDir the root dir of multi-process MMKV, MMKV with MMKVMultiProcess mode will be stored in groupDir/mmkv
/// @param logLevel MMKVLogInfo by default, MMKVLogNone to disable all logging
/// @return root dir of MMKV
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir groupDir:(NSString *)groupDir logLevel:(MMKVLogLevel)logLevel NS_SWIFT_NAME(initialize(rootDir:groupDir:logLevel:));
/// a generic purpose instance (in MMKVSingleProcess mode)
+ (nullable instancetype)defaultMMKV;
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID NS_SWIFT_NAME(init(mmapID:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param mode MMKVMultiProcess for multi-process MMKV
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode NS_SWIFT_NAME(init(mmapID:mode:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey NS_SWIFT_NAME(init(mmapID:cryptKey:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
/// @param mode MMKVMultiProcess for multi-process MMKV
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode NS_SWIFT_NAME(init(mmapID:cryptKey:mode:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param relativePath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID relativePath:(nullable NSString *)relativePath NS_SWIFT_NAME(init(mmapID:relativePath:)) __attribute__((deprecated("use +mmkvWithID:rootPath: instead")));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param rootPath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath NS_SWIFT_NAME(init(mmapID:rootPath:));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
/// @param relativePath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey relativePath:(nullable NSString *)relativePath NS_SWIFT_NAME(init(mmapID:cryptKey:relativePath:)) __attribute__((deprecated("use +mmkvWithID:cryptKey:rootPath: instead")));
/// @param mmapID any unique ID (com.tencent.xin.pay, etc), if you want a per-user mmkv, you could merge user-id within mmapID
/// @param cryptKey 16 bytes at most
/// @param rootPath custom path of the file, `NSDocumentDirectory/mmkv` by default
+ (nullable instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey rootPath:(nullable NSString *)rootPath NS_SWIFT_NAME(init(mmapID:cryptKey:rootPath:));
// you can call this on applicationWillTerminate, it's totally fine if you don't call
+ (void)onAppTerminate;
+ (NSString *)mmkvBasePath;
// if you want to change the base path, do it BEFORE getting any MMKV instance
// otherwise the behavior is undefined
+ (void)setMMKVBasePath:(NSString *)basePath __attribute__((deprecated("use +initializeMMKV: instead", "+initializeMMKV:")));
// transform plain text into encrypted text, or vice versa by passing newKey = nil
// you can change existing crypt key with different key
- (BOOL)reKey:(nullable NSData *)newKey NS_SWIFT_NAME(reset(cryptKey:));
- (nullable NSData *)cryptKey;
// just reset cryptKey (will not encrypt or decrypt anything)
// usually you should call this method after other process reKey() the multi-process mmkv
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey NS_SWIFT_NAME(checkReSet(cryptKey:));
- (BOOL)setObject:(nullable NSObject<NSCoding> *)object forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setBool:(BOOL)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setInt32:(int32_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setUInt32:(uint32_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setInt64:(int64_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setUInt64:(uint64_t)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setFloat:(float)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setDouble:(double)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setString:(NSString *)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setDate:(NSDate *)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (BOOL)setData:(NSData *)value forKey:(NSString *)key NS_SWIFT_NAME(set(_:forKey:));
- (nullable id)getObjectOfClass:(Class)cls forKey:(NSString *)key NS_SWIFT_NAME(object(of:forKey:));
- (BOOL)getBoolForKey:(NSString *)key __attribute__((swift_name("bool(forKey:)")));
- (BOOL)getBoolForKey:(NSString *)key defaultValue:(BOOL)defaultValue __attribute__((swift_name("bool(forKey:defaultValue:)")));
- (int32_t)getInt32ForKey:(NSString *)key NS_SWIFT_NAME(int32(forKey:));
- (int32_t)getInt32ForKey:(NSString *)key defaultValue:(int32_t)defaultValue NS_SWIFT_NAME(int32(forKey:defaultValue:));
- (uint32_t)getUInt32ForKey:(NSString *)key NS_SWIFT_NAME(uint32(forKey:));
- (uint32_t)getUInt32ForKey:(NSString *)key defaultValue:(uint32_t)defaultValue NS_SWIFT_NAME(uint32(forKey:defaultValue:));
- (int64_t)getInt64ForKey:(NSString *)key NS_SWIFT_NAME(int64(forKey:));
- (int64_t)getInt64ForKey:(NSString *)key defaultValue:(int64_t)defaultValue NS_SWIFT_NAME(int64(forKey:defaultValue:));
- (uint64_t)getUInt64ForKey:(NSString *)key NS_SWIFT_NAME(uint64(forKey:));
- (uint64_t)getUInt64ForKey:(NSString *)key defaultValue:(uint64_t)defaultValue NS_SWIFT_NAME(uint64(forKey:defaultValue:));
- (float)getFloatForKey:(NSString *)key NS_SWIFT_NAME(float(forKey:));
- (float)getFloatForKey:(NSString *)key defaultValue:(float)defaultValue NS_SWIFT_NAME(float(forKey:defaultValue:));
- (double)getDoubleForKey:(NSString *)key NS_SWIFT_NAME(double(forKey:));
- (double)getDoubleForKey:(NSString *)key defaultValue:(double)defaultValue NS_SWIFT_NAME(double(forKey:defaultValue:));
- (nullable NSString *)getStringForKey:(NSString *)key NS_SWIFT_NAME(string(forKey:));
- (nullable NSString *)getStringForKey:(NSString *)key defaultValue:(nullable NSString *)defaultValue NS_SWIFT_NAME(string(forKey:defaultValue:));
- (nullable NSDate *)getDateForKey:(NSString *)key NS_SWIFT_NAME(date(forKey:));
- (nullable NSDate *)getDateForKey:(NSString *)key defaultValue:(nullable NSDate *)defaultValue NS_SWIFT_NAME(date(forKey:defaultValue:));
- (nullable NSData *)getDataForKey:(NSString *)key NS_SWIFT_NAME(data(forKey:));
- (nullable NSData *)getDataForKey:(NSString *)key defaultValue:(nullable NSData *)defaultValue NS_SWIFT_NAME(data(forKey:defaultValue:));
// return the actual size consumption of the key's value
// Note: might be a little bigger than value's length
- (size_t)getValueSizeForKey:(NSString *)key NS_SWIFT_NAME(valueSize(forKey:));
// return size written into buffer
// return -1 on any error
- (int32_t)writeValueForKey:(NSString *)key toBuffer:(NSMutableData *)buffer NS_SWIFT_NAME(writeValue(forKey:buffer:));
- (BOOL)containsKey:(NSString *)key NS_SWIFT_NAME(contains(key:));
- (size_t)count;
- (size_t)totalSize;
- (size_t)actualSize;
- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block;
- (NSArray *)allKeys;
- (void)removeValueForKey:(NSString *)key NS_SWIFT_NAME(removeValue(forKey:));
- (void)removeValuesForKeys:(NSArray<NSString *> *)arrKeys NS_SWIFT_NAME(removeValues(forKeys:));
- (void)clearAll;
// MMKV's size won't reduce after deleting key-values
// call this method after lots of deleting if you care about disk usage
// note that `clearAll` has the similar effect of `trim`
- (void)trim;
// call this method if the instance is no longer needed in the near future
// any subsequent call to the instance is undefined behavior
- (void)close;
// call this method if you are facing memory-warning
// any subsequent call to the instance will load all key-values from file again
- (void)clearMemoryCache;
// you don't need to call this, really, I mean it
// unless you worry about running out of battery
- (void)sync;
- (void)async;
// check if content changed by other process
- (void)checkContentChanged;
+ (void)registerHandler:(id<MMKVHandler>)handler;
+ (void)unregiserHandler;
// MMKVLogInfo by default
// MMKVLogNone to disable all logging
+ (void)setLogLevel:(MMKVLogLevel)logLevel __attribute__((deprecated("use +initializeMMKV:logLevel: instead", "initializeMMKV:nil logLevel")));
// Migrate NSUserDefault data to MMKV
// return imported count of key-values
- (uint32_t)migrateFromUserDefaults:(NSUserDefaults *)userDaults NS_SWIFT_NAME(migrateFrom(userDefaults:));
// for CrashProtected Only
+ (BOOL)isFileValid:(NSString *)mmapID NS_SWIFT_NAME(isFileValid(for:));
+ (BOOL)isFileValid:(NSString *)mmapID rootPath:(nullable NSString *)path NS_SWIFT_NAME(isFileValid(for:rootPath:));
NS_ASSUME_NONNULL_END
@end

View File

@ -1,60 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2018 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef MMKVHandler_h
#define MMKVHandler_h
#import <Foundation/Foundation.h>
typedef NS_ENUM(NSUInteger, MMKVRecoverStrategic) {
MMKVOnErrorDiscard = 0,
MMKVOnErrorRecover,
};
typedef NS_ENUM(NSUInteger, MMKVLogLevel) {
MMKVLogDebug = 0, // not available for release/product build
MMKVLogInfo = 1, // default level
MMKVLogWarning,
MMKVLogError,
MMKVLogNone, // special level used to disable all log messages
};
// callback is called on the operating thread of the MMKV instance
@protocol MMKVHandler <NSObject>
@optional
// by default MMKV will discard all datas on crc32-check failure
// return `MMKVOnErrorRecover` to recover any data on the file
- (MMKVRecoverStrategic)onMMKVCRCCheckFail:(NSString *)mmapID;
// by default MMKV will discard all datas on file length mismatch
// return `MMKVOnErrorRecover` to recover any data on the file
- (MMKVRecoverStrategic)onMMKVFileLengthError:(NSString *)mmapID;
// by default MMKV will print log using NSLog
// implement this method to redirect MMKV's log
- (void)mmkvLogWithLevel:(MMKVLogLevel)level file:(const char *)file line:(int)line func:(const char *)funcname message:(NSString *)message;
// called when content is changed by other process
// doesn't guarantee real-time notification
- (void)onMMKVContentChange:(NSString *)mmapID;
@end
#endif /* MMKVHandler_h */

View File

@ -1,699 +0,0 @@
/*
* Tencent is pleased to support the open source community by making
* MMKV available.
*
* Copyright (C) 2020 THL A29 Limited, a Tencent company.
* All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use
* this file except in compliance with the License. You may obtain a copy of
* the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "MMKV.h"
#import <MMKVCore/MMKV.h>
#import <MMKVCore/MMKVLog.h>
#import <MMKVCore/ScopedLock.hpp>
#import <MMKVCore/ThreadLock.h>
#import <MMKVCore/openssl_md5.h>
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
#import <UIKit/UIKit.h>
#endif
using namespace std;
static NSMutableDictionary *g_instanceDic = nil;
static mmkv::ThreadLock *g_lock;
static id<MMKVHandler> g_callbackHandler = nil;
static bool g_isLogRedirecting = false;
static NSString *g_basePath = nil;
static NSString *g_groupPath = nil;
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
static BOOL g_isRunningInAppExtension = NO;
#endif
static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message);
static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType);
static void ContentChangeHandler(const string &mmapID);
@implementation MMKV {
NSString *m_mmapID;
NSString *m_mmapKey;
mmkv::MMKV *m_mmkv;
}
#pragma mark - init
// protect from some old code that don't call +initializeMMKV:
+ (void)initialize {
if (self == MMKV.class) {
g_instanceDic = [[NSMutableDictionary alloc] init];
g_lock = new mmkv::ThreadLock();
g_lock->initialize();
mmkv::MMKV::minimalInit([self mmkvBasePath].UTF8String);
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
// just in case someone forget to set the MMKV_IOS_EXTENSION macro
if ([[[NSBundle mainBundle] bundlePath] hasSuffix:@".appex"]) {
g_isRunningInAppExtension = YES;
}
if (!g_isRunningInAppExtension) {
auto appState = [UIApplication sharedApplication].applicationState;
auto isInBackground = (appState == UIApplicationStateBackground);
mmkv::MMKV::setIsInBackground(isInBackground);
MMKVInfo("appState:%ld", (long) appState);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didEnterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil];
}
#endif
}
}
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir {
return [MMKV initializeMMKV:rootDir logLevel:MMKVLogInfo];
}
static BOOL g_hasCalledInitializeMMKV = NO;
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir logLevel:(MMKVLogLevel)logLevel {
if (g_hasCalledInitializeMMKV) {
MMKVWarning("already called +initializeMMKV before, ignore this request");
return [self mmkvBasePath];
}
g_hasCalledInitializeMMKV = YES;
g_basePath = (rootDir != nil) ? rootDir : [self mmkvBasePath];
mmkv::MMKV::initializeMMKV(g_basePath.UTF8String, (mmkv::MMKVLogLevel) logLevel);
return [self mmkvBasePath];
}
+ (NSString *)initializeMMKV:(nullable NSString *)rootDir groupDir:(NSString *)groupDir logLevel:(MMKVLogLevel)logLevel {
auto ret = [MMKV initializeMMKV:rootDir logLevel:logLevel];
g_groupPath = [groupDir stringByAppendingPathComponent:@"mmkv"];
MMKVInfo("groupDir: %@", g_groupPath);
return ret;
}
// a generic purpose instance
+ (instancetype)defaultMMKV {
return [MMKV mmkvWithID:(@"" DEFAULT_MMAP_ID) cryptKey:nil rootPath:nil mode:MMKVSingleProcess];
}
// any unique ID (com.tencent.xin.pay, etc)
+ (instancetype)mmkvWithID:(NSString *)mmapID {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:nil mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID mode:(MMKVMode)mode {
auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath;
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:mode];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:nil mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey mode:(MMKVMode)mode {
auto rootPath = (mode == MMKVSingleProcess) ? nil : g_groupPath;
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:rootPath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID relativePath:(nullable NSString *)relativePath {
return [MMKV mmkvWithID:mmapID cryptKey:nil rootPath:relativePath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(nullable NSString *)rootPath {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:rootPath mode:MMKVSingleProcess];
}
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(nullable NSData *)cryptKey relativePath:(nullable NSString *)relativePath {
return [MMKV mmkvWithID:mmapID cryptKey:cryptKey rootPath:relativePath mode:MMKVSingleProcess];
}
// relatePath and MMKVMultiProcess mode can't be set at the same time, so we hide this method from public
+ (instancetype)mmkvWithID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(nullable NSString *)rootPath mode:(MMKVMode)mode {
if (!g_hasCalledInitializeMMKV) {
MMKVError("MMKV not initialized properly, must call +initializeMMKV: in main thread before calling any other MMKV methods");
}
if (mmapID.length <= 0) {
return nil;
}
SCOPED_LOCK(g_lock);
if (mode == MMKVMultiProcess) {
if (!rootPath) {
rootPath = g_groupPath;
}
if (!rootPath) {
MMKVError("Getting a multi-process MMKV [%@] without setting groupDir makes no sense", mmapID);
MMKV_ASSERT(0);
}
}
NSString *kvKey = [MMKV mmapKeyWithMMapID:mmapID rootPath:rootPath];
MMKV *kv = [g_instanceDic objectForKey:kvKey];
if (kv == nil) {
kv = [[MMKV alloc] initWithMMapID:mmapID cryptKey:cryptKey rootPath:rootPath mode:mode];
if (!kv->m_mmkv) {
return nil;
}
kv->m_mmapKey = kvKey;
[g_instanceDic setObject:kv forKey:kvKey];
}
return kv;
}
- (instancetype)initWithMMapID:(NSString *)mmapID cryptKey:(NSData *)cryptKey rootPath:(NSString *)rootPath mode:(MMKVMode)mode {
if (self = [super init]) {
string pathTmp;
if (rootPath.length > 0) {
pathTmp = rootPath.UTF8String;
}
string cryptKeyTmp;
if (cryptKey.length > 0) {
cryptKeyTmp = string((char *) cryptKey.bytes, cryptKey.length);
}
string *rootPathPtr = pathTmp.empty() ? nullptr : &pathTmp;
string *cryptKeyPtr = cryptKeyTmp.empty() ? nullptr : &cryptKeyTmp;
m_mmkv = mmkv::MMKV::mmkvWithID(mmapID.UTF8String, (mmkv::MMKVMode) mode, cryptKeyPtr, rootPathPtr);
if (!m_mmkv) {
return self;
}
m_mmapID = [NSString stringWithUTF8String:m_mmkv->mmapID().c_str()];
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
if (!g_isRunningInAppExtension) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(onMemoryWarning)
name:UIApplicationDidReceiveMemoryWarningNotification
object:nil];
}
#endif
}
return self;
}
- (void)dealloc {
[self clearMemoryCache];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#pragma mark - Application state
#if defined(MMKV_IOS) && !defined(MMKV_IOS_EXTENSION)
- (void)onMemoryWarning {
MMKVInfo("cleaning on memory warning %@", m_mmapID);
[self clearMemoryCache];
}
+ (void)didEnterBackground {
mmkv::MMKV::setIsInBackground(true);
MMKVInfo("isInBackground:%d", true);
}
+ (void)didBecomeActive {
mmkv::MMKV::setIsInBackground(false);
MMKVInfo("isInBackground:%d", false);
}
#endif
- (void)clearAll {
m_mmkv->clearAll();
}
- (void)clearMemoryCache {
if (m_mmkv) {
m_mmkv->clearMemoryCache();
}
}
- (void)close {
SCOPED_LOCK(g_lock);
MMKVInfo("closing %@", m_mmapID);
m_mmkv->close();
m_mmkv = nullptr;
[g_instanceDic removeObjectForKey:m_mmapKey];
}
- (void)trim {
m_mmkv->trim();
}
#pragma mark - encryption & decryption
#ifndef MMKV_DISABLE_CRYPT
- (nullable NSData *)cryptKey {
auto str = m_mmkv->cryptKey();
if (str.length() > 0) {
return [NSData dataWithBytes:str.data() length:str.length()];
}
return nil;
}
- (BOOL)reKey:(nullable NSData *)newKey {
string key;
if (newKey.length > 0) {
key = string((char *) newKey.bytes, newKey.length);
}
return m_mmkv->reKey(key);
}
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey {
if (cryptKey.length > 0) {
string key = string((char *) cryptKey.bytes, cryptKey.length);
m_mmkv->checkReSetCryptKey(&key);
} else {
m_mmkv->checkReSetCryptKey(nullptr);
}
}
#else
- (nullable NSData *)cryptKey {
return nil;
}
- (BOOL)reKey:(nullable NSData *)newKey {
return NO;
}
- (void)checkReSetCryptKey:(nullable NSData *)cryptKey {
}
#endif // MMKV_DISABLE_CRYPT
#pragma mark - set & get
- (BOOL)setObject:(nullable NSObject<NSCoding> *)object forKey:(NSString *)key {
return m_mmkv->set(object, key);
}
- (BOOL)setBool:(BOOL)value forKey:(NSString *)key {
return m_mmkv->set((bool) value, key);
}
- (BOOL)setInt32:(int32_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setUInt32:(uint32_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setInt64:(int64_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setUInt64:(uint64_t)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setFloat:(float)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setDouble:(double)value forKey:(NSString *)key {
return m_mmkv->set(value, key);
}
- (BOOL)setString:(NSString *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (BOOL)setDate:(NSDate *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (BOOL)setData:(NSData *)value forKey:(NSString *)key {
return [self setObject:value forKey:key];
}
- (id)getObjectOfClass:(Class)cls forKey:(NSString *)key {
return m_mmkv->getObject(key, cls);
}
- (BOOL)getBoolForKey:(NSString *)key {
return [self getBoolForKey:key defaultValue:FALSE];
}
- (BOOL)getBoolForKey:(NSString *)key defaultValue:(BOOL)defaultValue {
return m_mmkv->getBool(key, defaultValue);
}
- (int32_t)getInt32ForKey:(NSString *)key {
return [self getInt32ForKey:key defaultValue:0];
}
- (int32_t)getInt32ForKey:(NSString *)key defaultValue:(int32_t)defaultValue {
return m_mmkv->getInt32(key, defaultValue);
}
- (uint32_t)getUInt32ForKey:(NSString *)key {
return [self getUInt32ForKey:key defaultValue:0];
}
- (uint32_t)getUInt32ForKey:(NSString *)key defaultValue:(uint32_t)defaultValue {
return m_mmkv->getUInt32(key, defaultValue);
}
- (int64_t)getInt64ForKey:(NSString *)key {
return [self getInt64ForKey:key defaultValue:0];
}
- (int64_t)getInt64ForKey:(NSString *)key defaultValue:(int64_t)defaultValue {
return m_mmkv->getInt64(key, defaultValue);
}
- (uint64_t)getUInt64ForKey:(NSString *)key {
return [self getUInt64ForKey:key defaultValue:0];
}
- (uint64_t)getUInt64ForKey:(NSString *)key defaultValue:(uint64_t)defaultValue {
return m_mmkv->getUInt64(key, defaultValue);
}
- (float)getFloatForKey:(NSString *)key {
return [self getFloatForKey:key defaultValue:0];
}
- (float)getFloatForKey:(NSString *)key defaultValue:(float)defaultValue {
return m_mmkv->getFloat(key, defaultValue);
}
- (double)getDoubleForKey:(NSString *)key {
return [self getDoubleForKey:key defaultValue:0];
}
- (double)getDoubleForKey:(NSString *)key defaultValue:(double)defaultValue {
return m_mmkv->getDouble(key, defaultValue);
}
- (nullable NSString *)getStringForKey:(NSString *)key {
return [self getStringForKey:key defaultValue:nil];
}
- (nullable NSString *)getStringForKey:(NSString *)key defaultValue:(nullable NSString *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSString *valueString = [self getObjectOfClass:NSString.class forKey:key];
if (!valueString) {
valueString = defaultValue;
}
return valueString;
}
- (nullable NSDate *)getDateForKey:(NSString *)key {
return [self getDateForKey:key defaultValue:nil];
}
- (nullable NSDate *)getDateForKey:(NSString *)key defaultValue:(nullable NSDate *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSDate *valueDate = [self getObjectOfClass:NSDate.class forKey:key];
if (!valueDate) {
valueDate = defaultValue;
}
return valueDate;
}
- (nullable NSData *)getDataForKey:(NSString *)key {
return [self getDataForKey:key defaultValue:nil];
}
- (nullable NSData *)getDataForKey:(NSString *)key defaultValue:(nullable NSData *)defaultValue {
if (key.length <= 0) {
return defaultValue;
}
NSData *valueData = [self getObjectOfClass:NSData.class forKey:key];
if (!valueData) {
valueData = defaultValue;
}
return valueData;
}
- (size_t)getValueSizeForKey:(NSString *)key {
return m_mmkv->getValueSize(key, false);
}
- (int32_t)writeValueForKey:(NSString *)key toBuffer:(NSMutableData *)buffer {
return m_mmkv->writeValueToBuffer(key, buffer.mutableBytes, static_cast<int32_t>(buffer.length));
}
#pragma mark - enumerate
- (BOOL)containsKey:(NSString *)key {
return m_mmkv->containsKey(key);
}
- (size_t)count {
return m_mmkv->count();
}
- (size_t)totalSize {
return m_mmkv->totalSize();
}
- (size_t)actualSize {
return m_mmkv->actualSize();
}
- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block {
m_mmkv->enumerateKeys(block);
}
- (NSArray *)allKeys {
return m_mmkv->allKeys();
}
- (void)removeValueForKey:(NSString *)key {
m_mmkv->removeValueForKey(key);
}
- (void)removeValuesForKeys:(NSArray *)arrKeys {
m_mmkv->removeValuesForKeys(arrKeys);
}
#pragma mark - Boring stuff
- (void)sync {
m_mmkv->sync(mmkv::MMKV_SYNC);
}
- (void)async {
m_mmkv->sync(mmkv::MMKV_ASYNC);
}
- (void)checkContentChanged {
m_mmkv->checkContentChanged();
}
+ (void)onAppTerminate {
SCOPED_LOCK(g_lock);
[g_instanceDic removeAllObjects];
mmkv::MMKV::onExit();
}
+ (NSString *)mmkvBasePath {
if (g_basePath.length > 0) {
return g_basePath;
}
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentPath = (NSString *) [paths firstObject];
if ([documentPath length] > 0) {
g_basePath = [documentPath stringByAppendingPathComponent:@"mmkv"];
return g_basePath;
} else {
return @"";
}
}
+ (void)setMMKVBasePath:(NSString *)basePath {
if (basePath.length > 0) {
g_basePath = basePath;
[MMKV initializeMMKV:basePath];
// still warn about it
g_hasCalledInitializeMMKV = NO;
MMKVInfo("set MMKV base path to: %@", g_basePath);
}
}
static NSString *md5(NSString *value) {
uint8_t md[MD5_DIGEST_LENGTH] = {};
char tmp[3] = {}, buf[33] = {};
auto data = [value dataUsingEncoding:NSUTF8StringEncoding];
openssl::MD5((uint8_t *) data.bytes, data.length, md);
for (auto ch : md) {
snprintf(tmp, sizeof(tmp), "%2.2x", ch);
strcat(buf, tmp);
}
return [NSString stringWithCString:buf encoding:NSASCIIStringEncoding];
}
+ (NSString *)mmapKeyWithMMapID:(NSString *)mmapID rootPath:(nullable NSString *)rootPath {
NSString *string = nil;
if ([rootPath length] > 0 && [rootPath isEqualToString:[MMKV mmkvBasePath]] == NO) {
string = md5([rootPath stringByAppendingPathComponent:mmapID]);
} else {
string = mmapID;
}
MMKVDebug("mmapKey: %@", string);
return string;
}
+ (BOOL)isFileValid:(NSString *)mmapID {
return [self isFileValid:mmapID rootPath:nil];
}
+ (BOOL)isFileValid:(NSString *)mmapID rootPath:(nullable NSString *)path {
if (mmapID.length > 0) {
if (path.length > 0) {
string rootPath(path.UTF8String);
return mmkv::MMKV::isFileValid(mmapID.UTF8String, &rootPath);
} else {
return mmkv::MMKV::isFileValid(mmapID.UTF8String, nullptr);
}
}
return NO;
}
+ (void)registerHandler:(id<MMKVHandler>)handler {
SCOPED_LOCK(g_lock);
g_callbackHandler = handler;
if ([g_callbackHandler respondsToSelector:@selector(mmkvLogWithLevel:file:line:func:message:)]) {
g_isLogRedirecting = true;
mmkv::MMKV::registerLogHandler(LogHandler);
}
if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)] ||
[g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
mmkv::MMKV::registerErrorHandler(ErrorHandler);
}
if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) {
mmkv::MMKV::registerContentChangeHandler(ContentChangeHandler);
}
}
+ (void)unregiserHandler {
SCOPED_LOCK(g_lock);
g_isLogRedirecting = false;
g_callbackHandler = nil;
mmkv::MMKV::unRegisterLogHandler();
mmkv::MMKV::unRegisterErrorHandler();
mmkv::MMKV::unRegisterContentChangeHandler();
}
+ (void)setLogLevel:(MMKVLogLevel)logLevel {
mmkv::MMKV::setLogLevel((mmkv::MMKVLogLevel) logLevel);
}
- (uint32_t)migrateFromUserDefaults:(NSUserDefaults *)userDaults {
NSDictionary *dic = [userDaults dictionaryRepresentation];
if (dic.count <= 0) {
MMKVInfo("migrate data fail, userDaults is nil or empty");
return 0;
}
__block uint32_t count = 0;
[dic enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL *_Nonnull stop) {
if ([key isKindOfClass:[NSString class]]) {
NSString *stringKey = key;
if ([MMKV tranlateData:obj key:stringKey kv:self]) {
count++;
}
} else {
MMKVWarning("unknown type of key:%@", key);
}
}];
return count;
}
+ (BOOL)tranlateData:(id)obj key:(NSString *)key kv:(MMKV *)kv {
if ([obj isKindOfClass:[NSString class]]) {
return [kv setString:obj forKey:key];
} else if ([obj isKindOfClass:[NSData class]]) {
return [kv setData:obj forKey:key];
} else if ([obj isKindOfClass:[NSDate class]]) {
return [kv setDate:obj forKey:key];
} else if ([obj isKindOfClass:[NSNumber class]]) {
NSNumber *num = obj;
CFNumberType numberType = CFNumberGetType((CFNumberRef) obj);
switch (numberType) {
case kCFNumberCharType:
case kCFNumberSInt8Type:
case kCFNumberSInt16Type:
case kCFNumberSInt32Type:
case kCFNumberIntType:
case kCFNumberShortType:
return [kv setInt32:num.intValue forKey:key];
case kCFNumberSInt64Type:
case kCFNumberLongType:
case kCFNumberNSIntegerType:
case kCFNumberLongLongType:
return [kv setInt64:num.longLongValue forKey:key];
case kCFNumberFloat32Type:
return [kv setFloat:num.floatValue forKey:key];
case kCFNumberFloat64Type:
case kCFNumberDoubleType:
return [kv setDouble:num.doubleValue forKey:key];
default:
MMKVWarning("unknown number type:%ld, key:%@", (long) numberType, key);
return NO;
}
} else if ([obj isKindOfClass:[NSArray class]] || [obj isKindOfClass:[NSDictionary class]]) {
return [kv setObject:obj forKey:key];
} else {
MMKVWarning("unknown type of key:%@", key);
}
return NO;
}
@end
#pragma makr - callbacks
static void LogHandler(mmkv::MMKVLogLevel level, const char *file, int line, const char *function, NSString *message) {
[g_callbackHandler mmkvLogWithLevel:(MMKVLogLevel) level file:file line:line func:function message:message];
}
static mmkv::MMKVRecoverStrategic ErrorHandler(const string &mmapID, mmkv::MMKVErrorType errorType) {
if (errorType == mmkv::MMKVCRCCheckFail) {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVCRCCheckFail:)]) {
auto ret = [g_callbackHandler onMMKVCRCCheckFail:[NSString stringWithUTF8String:mmapID.c_str()]];
return (mmkv::MMKVRecoverStrategic) ret;
}
} else {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVFileLengthError:)]) {
auto ret = [g_callbackHandler onMMKVFileLengthError:[NSString stringWithUTF8String:mmapID.c_str()]];
return (mmkv::MMKVRecoverStrategic) ret;
}
}
return mmkv::OnErrorDiscard;
}
static void ContentChangeHandler(const string &mmapID) {
if ([g_callbackHandler respondsToSelector:@selector(onMMKVContentChange:)]) {
[g_callbackHandler onMMKVContentChange:[NSString stringWithUTF8String:mmapID.c_str()]];
}
}

11
ios/Pods/Manifest.lock generated
View File

@ -186,8 +186,6 @@ PODS:
- libwebp/webp (1.1.0)
- MMKV (1.2.1):
- MMKVCore (~> 1.2.1)
- MMKVAppExtension (1.2.1):
- MMKVCore (~> 1.2.1)
- MMKVCore (1.2.1)
- nanopb (1.30905.0):
- nanopb/decode (= 1.30905.0)
@ -386,7 +384,7 @@ PODS:
- React
- react-native-safe-area-context (3.1.1):
- React
- react-native-simple-crypto (0.3.1):
- react-native-simple-crypto (0.4.0):
- OpenSSL-Universal
- React
- react-native-slider (3.0.2):
@ -578,7 +576,6 @@ DEPENDENCIES:
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
- KeyCommands (from `../node_modules/react-native-keycommands`)
- MMKVAppExtension
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
- React (from `../node_modules/react-native/`)
@ -676,7 +673,6 @@ SPEC REPOS:
- JitsiMeetSDK
- libwebp
- MMKV
- MMKVAppExtension
- MMKVCore
- nanopb
- OpenSSL-Universal
@ -904,7 +900,6 @@ SPEC CHECKSUMS:
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
MMKVAppExtension: d792aa7bd301285e2c3100c5ce15aa44fa26456f
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
@ -929,7 +924,7 @@ SPEC CHECKSUMS:
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
react-native-simple-crypto: 5e4f2877f71675d95baabf5823cd0cc0c379d0e6
react-native-simple-crypto: 564740fd8124827d82e9e8ded4a0de8c695c8a4d
react-native-slider: 0221b417686c5957f6e77cd9ac22c1478a165355
react-native-webview: 679b6f400176e2ea8a785acf7ae16cf282e7d1eb
React-RCTActionSheet: 1702a1a85e550b5c36e2e03cb2bd3adea053de95
@ -983,6 +978,6 @@ SPEC CHECKSUMS:
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
PODFILE CHECKSUM: 4916aa46fbfaed764171540e6ed76fb07a8a95e7
PODFILE CHECKSUM: 19c78b598c807d2c6b988fd11b24544ac1895a35
COCOAPODS: 1.9.3

File diff suppressed because it is too large Load Diff

View File

@ -1,5 +0,0 @@
#import <Foundation/Foundation.h>
@interface PodsDummy_MMKVAppExtension : NSObject
@end
@implementation PodsDummy_MMKVAppExtension
@end

View File

@ -1,14 +0,0 @@
APPLICATION_EXTENSION_API_ONLY = YES
CLANG_CXX_LANGUAGE_STANDARD = gnu++17
CLANG_CXX_LIBRARY = libc++
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MMKVAppExtension
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 MMKV_IOS_EXTENSION
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/MMKVAppExtension" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/MMKVAppExtension" "${PODS_ROOT}/Headers/Public/MMKVCore"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/MMKVAppExtension
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -1,14 +0,0 @@
APPLICATION_EXTENSION_API_ONLY = YES
CLANG_CXX_LANGUAGE_STANDARD = gnu++17
CLANG_CXX_LIBRARY = libc++
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = NO
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/MMKVAppExtension
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 MMKV_IOS_EXTENSION
HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Private" "${PODS_ROOT}/Headers/Private/MMKVAppExtension" "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/MMKVAppExtension" "${PODS_ROOT}/Headers/Public/MMKVCore"
PODS_BUILD_DIR = ${BUILD_DIR}
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
PODS_ROOT = ${SRCROOT}
PODS_TARGET_SRCROOT = ${PODS_ROOT}/MMKVAppExtension
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
SKIP_INSTALL = YES
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

View File

@ -0,0 +1,165 @@
#!/bin/sh
set -e
set -u
set -o pipefail
function on_error {
echo "$(realpath -mq "${0}"):$1: error: Unexpected failure"
}
trap 'on_error $LINENO' ERR
if [ -z ${UNLOCALIZED_RESOURCES_FOLDER_PATH+x} ]; then
# If UNLOCALIZED_RESOURCES_FOLDER_PATH is not set, then there's nowhere for us to copy
# resources to, so exit 0 (signalling the script phase was successful).
exit 0
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
RESOURCES_TO_COPY=${PODS_ROOT}/resources-to-copy-${TARGETNAME}.txt
> "$RESOURCES_TO_COPY"
XCASSET_FILES=()
# This protects against multiple targets copying the same framework dependency at the same time. The solution
# was originally proposed here: https://lists.samba.org/archive/rsync/2008-February/020158.html
RSYNC_PROTECT_TMP_FILES=(--filter "P .*.??????")
case "${TARGETED_DEVICE_FAMILY:-}" in
1,2)
TARGET_DEVICE_ARGS="--target-device ipad --target-device iphone"
;;
1)
TARGET_DEVICE_ARGS="--target-device iphone"
;;
2)
TARGET_DEVICE_ARGS="--target-device ipad"
;;
3)
TARGET_DEVICE_ARGS="--target-device tv"
;;
4)
TARGET_DEVICE_ARGS="--target-device watch"
;;
*)
TARGET_DEVICE_ARGS="--target-device mac"
;;
esac
install_resource()
{
if [[ "$1" = /* ]] ; then
RESOURCE_PATH="$1"
else
RESOURCE_PATH="${PODS_ROOT}/$1"
fi
if [[ ! -e "$RESOURCE_PATH" ]] ; then
cat << EOM
error: Resource "$RESOURCE_PATH" not found. Run 'pod install' to update the copy resources script.
EOM
exit 1
fi
case $RESOURCE_PATH in
*.storyboard)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .storyboard`.storyboardc" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.xib)
echo "ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile ${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib $RESOURCE_PATH --sdk ${SDKROOT} ${TARGET_DEVICE_ARGS}" || true
ibtool --reference-external-strings-file --errors --warnings --notices --minimum-deployment-target ${!DEPLOYMENT_TARGET_SETTING_NAME} --output-format human-readable-text --compile "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename \"$RESOURCE_PATH\" .xib`.nib" "$RESOURCE_PATH" --sdk "${SDKROOT}" ${TARGET_DEVICE_ARGS}
;;
*.framework)
echo "mkdir -p ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
mkdir -p "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
echo "rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" $RESOURCE_PATH ${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}" || true
rsync --delete -av "${RSYNC_PROTECT_TMP_FILES[@]}" "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}"
;;
*.xcdatamodel)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH"`.mom\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodel`.mom"
;;
*.xcdatamodeld)
echo "xcrun momc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd\"" || true
xcrun momc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcdatamodeld`.momd"
;;
*.xcmappingmodel)
echo "xcrun mapc \"$RESOURCE_PATH\" \"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm\"" || true
xcrun mapc "$RESOURCE_PATH" "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/`basename "$RESOURCE_PATH" .xcmappingmodel`.cdm"
;;
*.xcassets)
ABSOLUTE_XCASSET_FILE="$RESOURCE_PATH"
XCASSET_FILES+=("$ABSOLUTE_XCASSET_FILE")
;;
*)
echo "$RESOURCE_PATH" || true
echo "$RESOURCE_PATH" >> "$RESOURCES_TO_COPY"
;;
esac
}
if [[ "$CONFIGURATION" == "Debug" ]]; then
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle"
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle"
fi
if [[ "$CONFIGURATION" == "Release" ]]; then
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/RNImageCropPicker/QBImagePicker.bundle"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/AntDesign.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/EvilIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Feather.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Brands.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Regular.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/FontAwesome5_Solid.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Fontisto.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Foundation.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Ionicons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/MaterialIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Octicons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/SimpleLineIcons.ttf"
install_resource "${PODS_ROOT}/../../node_modules/react-native-vector-icons/Fonts/Zocial.ttf"
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/React-Core/AccessibilityResources.bundle"
install_resource "${PODS_CONFIGURATION_BUILD_DIR}/TOCropViewController/TOCropViewControllerBundle.bundle"
fi
mkdir -p "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
if [[ "${ACTION}" == "install" ]] && [[ "${SKIP_INSTALL}" == "NO" ]]; then
mkdir -p "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
rsync -avr --copy-links --no-relative --exclude '*/.svn/*' --files-from="$RESOURCES_TO_COPY" / "${INSTALL_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
fi
rm -f "$RESOURCES_TO_COPY"
if [[ -n "${WRAPPER_EXTENSION}" ]] && [ "`xcrun --find actool`" ] && [ -n "${XCASSET_FILES:-}" ]
then
# Find all other xcassets (this unfortunately includes those of path pods and other targets).
OTHER_XCASSETS=$(find -L "$PWD" -iname "*.xcassets" -type d)
while read line; do
if [[ $line != "${PODS_ROOT}*" ]]; then
XCASSET_FILES+=("$line")
fi
done <<<"$OTHER_XCASSETS"
if [ -z ${ASSETCATALOG_COMPILER_APPICON_NAME+x} ]; then
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}"
else
printf "%s\0" "${XCASSET_FILES[@]}" | xargs -0 xcrun actool --output-format human-readable-text --notices --warnings --platform "${PLATFORM_NAME}" --minimum-deployment-target "${!DEPLOYMENT_TARGET_SETTING_NAME}" ${TARGET_DEVICE_ARGS} --compress-pngs --compile "${BUILT_PRODUCTS_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}" --app-icon "${ASSETCATALOG_COMPILER_APPICON_NAME}" --output-partial-info-plist "${TARGET_TEMP_DIR}/assetcatalog_generated_info_cocoapods.plist"
fi
fi

View File

@ -10,3 +10,7 @@
#endif
#endif
FOUNDATION_EXPORT double Pods_NotificationServiceVersionNumber;
FOUNDATION_EXPORT const unsigned char Pods_NotificationServiceVersionString[];

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,6 @@
module Pods_NotificationService {
umbrella header "Pods-NotificationService-umbrella.h"
export *
module * { export * }
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,53 @@
//
// ReplyNotification.swift
// RocketChatRN
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/17/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
import UserNotifications
@objc(ReplyNotification)
class ReplyNotification: RNNotificationEventHandler {
private static let dispatchOnce: Void = {
let instance: AnyClass! = object_getClass(ReplyNotification())
let originalMethod = class_getInstanceMethod(instance, #selector(didReceive))
let swizzledMethod = class_getInstanceMethod(instance, #selector(replyNotification_didReceiveNotificationResponse))
if let originalMethod = originalMethod, let swizzledMethod = swizzledMethod {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}()
@objc
public static func configure() {
_ = self.dispatchOnce
}
@objc
func replyNotification_didReceiveNotificationResponse(_ response: UNNotificationResponse, completionHandler: @escaping(() -> Void)) {
if response.actionIdentifier == "REPLY_ACTION" {
if let notification = RCTConvert.unNotificationPayload(response.notification) {
if let data = (notification["ejson"] as? String)?.data(using: .utf8) {
if let payload = try? JSONDecoder().decode(Payload.self, from: data), let rid = payload.rid {
if let msg = (response as? UNTextInputNotificationResponse)?.userText {
let rocketchat = RocketChat.instanceForServer(server: payload.host.removeTrailingSlash())
let backgroundTask = UIApplication.shared.beginBackgroundTask(expirationHandler: nil)
rocketchat.sendMessage(rid: rid, message: msg) { response in
guard let response = response, response.success else {
let content = UNMutableNotificationContent()
content.body = "Failed to reply message."
let request = UNNotificationRequest(identifier: "replyFailure", content: content, trigger: nil)
UNUserNotificationCenter.current().add(request, withCompletionHandler: nil)
return
}
UIApplication.shared.endBackgroundTask(backgroundTask)
}
}
}
}
}
}
}
}

View File

@ -2,3 +2,11 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import <MMKV/MMKV.h>
#import <react-native-mmkv-storage/SecureStorage.h>
#import <react-native-notifications/RNNotificationEventHandler.h>
#import <react-native-notifications/RNNotificationCenter.h>
#import <react-native-notifications/RCTConvert+RNNotifications.h>
#import <react-native-simple-crypto/Aes.h>
#import <react-native-simple-crypto/Rsa.h>
#import <react-native-simple-crypto/Shared.h>

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,7 @@
#import <UIKit/UIKit.h>
#import <React/RCTBridgeDelegate.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import "RocketChatRN-Swift.h"
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>

View File

@ -64,6 +64,7 @@ static void InitializeFlipper(UIApplication *application) {
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
[RNNotifications startMonitorNotifications];
[ReplyNotification configure];
// AppGroup MMKV
NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path;

View File

@ -0,0 +1,23 @@
//
// Data+Extensions.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/18/20.
// Copyright © 2020 Facebook. All rights reserved.
//
import Foundation
extension Data {
static func randomBytes(length: Int) -> Data {
let bytes = [UInt32](repeating: 0, count: length).map { _ in arc4random() }
let data = Data(bytes: bytes, count: length)
return data
}
static func join(vector: Data, data: Data) -> Data {
var v = vector
v.append(data)
return v
}
}

View File

@ -0,0 +1,15 @@
//
// Date+Extensions.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/18/20.
// Copyright © 2020 Facebook. All rights reserved.
//
import Foundation
extension Date {
func currentTimeMillis() -> Int64 {
return Int64(self.timeIntervalSince1970 * 1000)
}
}

View File

@ -0,0 +1,37 @@
//
// String+Extensions.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 8/6/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
extension String {
func toHex() -> String {
return unicodeScalars.map{ .init($0.value, radix: 16, uppercase: false) }.joined()
}
func toData() -> Data? {
// Add padding if needed
var base64Encoded = self.padding(toLength: ((self.count + 3) / 4) * 4, withPad: "=", startingAt: 0)
// Decode URL safe encoded base64
base64Encoded = base64Encoded.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
return Data(base64Encoded: base64Encoded, options: .ignoreUnknownCharacters)
}
func removeTrailingSlash() -> String {
var url = self
if (url.last == "/") {
url.removeLast()
}
return url
}
static func random(length: Int) -> String {
let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
return String((0..<length).map{ _ in letters.randomElement()! })
}
}

View File

@ -0,0 +1,23 @@
//
// URL+Extensions.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
extension URL {
var domain: String? {
if let host = self.host {
if let port = self.port {
return "\(host):\(port)"
}
return host
}
return nil
}
}

View File

@ -0,0 +1,16 @@
//
// Message.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Message: Codable {
let _id: String
let text: String
let userId: String
let ts: Int64 = Date().currentTimeMillis()
}

View File

@ -0,0 +1,13 @@
//
// MessageType.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
enum MessageType: String, Codable {
case e2e
}

View File

@ -0,0 +1,16 @@
//
// Notification.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Notification: Decodable {
let notId: Int
let title: String
let text: String
let payload: Payload
}

View File

@ -0,0 +1,14 @@
//
// NotificationType.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
enum NotificationType: String, Codable {
case message = "message"
case messageIdOnly = "message-id-only"
}

View File

@ -0,0 +1,22 @@
//
// Payload.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Payload: Codable {
let host: String
let rid: String?
let type: RoomType?
let sender: Sender?
let messageId: String
let notificationType: NotificationType?
let name: String?
let messageType: MessageType?
let msg: String?
let senderName: String?
}

View File

@ -0,0 +1,18 @@
//
// PushResponse.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct PushResponse: Response {
let success: Bool
let data: Data
struct Data: Decodable {
let notification: Notification
}
}

View File

@ -0,0 +1,13 @@
//
// RoomKey.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct RoomKey: Decodable {
let k: String
}

View File

@ -0,0 +1,14 @@
//
// RoomType.swift
// RocketChatRN
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/22/20.
// Copyright © 2020 Facebook. All rights reserved.
//
import Foundation
enum RoomType: String, Codable {
case direct = "d"
case group = "p"
}

View File

@ -0,0 +1,15 @@
//
// Sender.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Sender: Codable {
let _id: String
let username: String
let name: String?
}

View File

@ -0,0 +1,91 @@
//
// API.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Retry {
let retries: Int
let retryTimeout = [10.0, 5.0, 3.0, 1.0]
init(retries: Int) {
if retries < 0 {
self.retries = 0
} else if retries > retryTimeout.count {
self.retries = retryTimeout.count
} else {
self.retries = retries
}
}
var timeout: Double {
return retryTimeout[retries - 1]
}
}
final class API {
typealias Server = String
final let server: URL
final let credentials: Credentials?
final let decoder = JSONDecoder()
static var instances: [Server: API] = [:]
convenience init?(server: Server) {
guard let server = URL(string: server.removeTrailingSlash()) else {
return nil
}
self.init(server: server)
}
init(server: URL) {
self.server = server
self.credentials = Storage.shared.getCredentials(server: server.absoluteString)
}
func fetch<T: Request>(request: T, retry: Retry? = nil, completion: @escaping((APIResponse<T.ResponseType>) -> Void)) {
func onError() {
if let retry = retry, retry.retries > 0 {
DispatchQueue.main.asyncAfter(deadline: .now() + retry.timeout, execute: {
self.fetch(request: request, retry: Retry(retries: retry.retries - 1), completion: completion)
})
return
}
completion(.error)
}
guard let request = request.request(for: self) else {
completion(.error)
return
}
let task = URLSession.shared.dataTask(with: request) {(data, _, error) in
if let _ = error as NSError? {
onError()
return
}
guard let data = data else {
onError()
return
}
if let response = try? self.decoder.decode(T.ResponseType.self, from: data), response.success {
completion(.resource(response))
return
}
onError()
}
task.resume()
}
}

View File

@ -0,0 +1,14 @@
//
// HTTPMethod.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
}

View File

@ -0,0 +1,68 @@
//
// Request.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
protocol Request {
associatedtype ResponseType: Response
var path: String { get }
var method: HTTPMethod { get }
var contentType: String { get }
var query: String? { get }
func body() -> Data?
func request(for api: API) -> URLRequest?
}
extension Request {
var method: HTTPMethod {
return .get
}
var contentType: String {
return "application/json"
}
var path: String {
return ""
}
var query: String? {
return nil
}
func body() -> Data? {
return nil
}
func request(for api: API) -> URLRequest? {
var components = URLComponents(url: api.server, resolvingAgainstBaseURL: false)
components?.path += path
components?.query = query
guard let url = components?.url else {
return nil
}
var request = URLRequest(url: url)
request.httpMethod = method.rawValue
request.httpBody = body()
request.addValue(contentType, forHTTPHeaderField: "Content-Type")
if let userId = api.credentials?.userId {
request.addValue(userId, forHTTPHeaderField: "x-user-id")
}
if let userToken = api.credentials?.userToken {
request.addValue(userToken, forHTTPHeaderField: "x-auth-token")
}
return request
}
}

View File

@ -0,0 +1,21 @@
//
// Push.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
final class PushRequest: Request {
var query: String?
typealias ResponseType = PushResponse
let path = "/api/v1/push.get"
init(msgId: String) {
self.query = "id=\(msgId)"
}
}

View File

@ -0,0 +1,51 @@
//
// SendMessage.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/18/20.
// Copyright © 2020 Facebook. All rights reserved.
//
import Foundation
struct MessageBody: Codable {
let message: Message
struct Message: Codable {
let _id: String
let msg: String
let rid: String
let tmid: String?
let t: MessageType?
}
}
struct MessageResponse: Response {
var success: Bool
}
final class SendMessageRequest: Request {
typealias ResponseType = MessageResponse
let method: HTTPMethod = .post
let path = "/api/v1/chat.sendMessage"
let id: String
let roomId: String
let text: String
let messageType: MessageType?
let threadIdentifier: String?
init(id: String, roomId: String, text: String, threadIdentifier: String? = nil, messageType: MessageType? = nil) {
self.id = id
self.roomId = roomId
self.text = text
self.messageType = messageType
self.threadIdentifier = threadIdentifier
}
func body() -> Data? {
return try? JSONEncoder().encode(MessageBody(message: MessageBody.Message(_id: id, msg: text, rid: roomId, tmid: threadIdentifier, t: messageType)))
}
}

View File

@ -0,0 +1,18 @@
//
// Response.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/16/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
protocol Response: Decodable {
var success: Bool { get }
}
enum APIResponse<T: Response> {
case resource(T)
case error
}

View File

@ -0,0 +1,68 @@
//
// Database.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/14/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
import WatermelonDB
final class Database {
private final var database: WatermelonDB.Database? = nil
private var directory: String? {
if let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as? String {
if let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) {
return directory.path
}
}
return nil
}
init(server: String) {
if let url = URL(string: server) {
if let domain = url.domain, let directory = directory {
self.database = WatermelonDB.Database(path: "\(directory)/\(domain)-experimental.db")
}
}
}
func readRoomEncryptionKey(rid: String) -> String? {
if let database = database {
if let results = try? database.queryRaw("select * from subscriptions where id == ? limit 1", [rid]) {
guard let record = results.next() else {
return nil
}
if let room = record.resultDictionary as? [String: Any] {
if let e2eKey = room["e2e_key"] as? String {
return e2eKey
}
}
}
}
return nil
}
func readRoomEncrypted(rid: String) -> Bool {
if let database = database {
if let results = try? database.queryRaw("select * from subscriptions where id == ? limit 1", [rid]) {
guard let record = results.next() else {
return false
}
if let room = record.resultDictionary as? [String: Any] {
if let encrypted = room["encrypted"] as? Bool {
return encrypted
}
}
}
}
return false
}
}

View File

@ -0,0 +1,103 @@
//
// Encryption.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/11/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
import CommonCrypto
import class react_native_simple_crypto.RCTRsaUtils
final class Encryption {
final var roomKey: String? = nil
final var keyId: String? = nil
private let privateKey: String?
private let credentials: Credentials?
private let server: String
private let rid: String
private var userKey: String? {
if let userKey = self.privateKey {
guard let json = try? JSONSerialization.jsonObject(with: userKey.data(using: .utf8)!, options: []) as? [String: Any] else {
return nil
}
let utils = RCTRsaUtils()
let k = NSMutableDictionary(dictionary: json)
return utils.importKey(jwk: k)
}
return nil
}
private final let encoder = JSONEncoder()
init(server: String, rid: String) {
self.privateKey = Storage.shared.getPrivateKey(server: server)
self.credentials = Storage.shared.getCredentials(server: server)
self.server = server
self.rid = rid
if let E2EKey = Database(server: server).readRoomEncryptionKey(rid: rid) {
self.roomKey = decryptRoomKey(E2EKey: E2EKey)
}
}
func decryptRoomKey(E2EKey: String) -> String? {
if let userKey = userKey {
let index = E2EKey.index(E2EKey.startIndex, offsetBy: 12)
let roomKey = String(E2EKey[index...])
keyId = String(E2EKey[..<index])
let rsa = Rsa()
rsa.privateKey = userKey
let message = rsa.decrypt(roomKey)
if let message = message?.data(using: .utf8) {
if let key = try? (JSONDecoder().decode(RoomKey.self, from: message)) {
if let base64Encoded = key.k.toData() {
return Shared.toHex(base64Encoded)
}
}
}
}
return nil
}
func decryptMessage(message: String) -> String? {
if let roomKey = self.roomKey {
let index = message.index(message.startIndex, offsetBy: 12)
let msg = String(message[index...])
if let data = msg.toData() {
let iv = data.subdata(in: 0..<kCCBlockSizeAES128)
let cypher = data.subdata(in: kCCBlockSizeAES128..<data.count)
if let decrypted = Aes.aes128CBC("decrypt", data: cypher, key: roomKey, iv: Shared.toHex(iv)) {
if let m = try? (JSONDecoder().decode(Message.self, from: decrypted)) {
return m.text
}
}
}
}
return nil
}
func encryptMessage(id: String, message: String) -> String {
if let userId = credentials?.userId, let roomKey = roomKey {
let m = Message(_id: id, text: message, userId: userId)
let iv = Data.randomBytes(length: kCCBlockSizeAES128)
let cypher = try? encoder.encode(m)
if let keyId = keyId, let cypher = cypher, let data = Aes.aes128CBC("encrypt", data: cypher, key: roomKey, iv: Shared.toHex(iv)) {
let joined = Data.join(vector: iv, data: data)
return keyId + joined.base64EncodedString()
}
}
return message
}
}

View File

@ -0,0 +1,99 @@
//
// RocketChat.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/17/20.
// Copyright © 2020 Facebook. All rights reserved.
//
import Foundation
final class RocketChat {
typealias Server = String
typealias RoomId = String
let server: Server
let api: API?
static var instances: [Server: RocketChat] = [:]
var encryptionInstances: [RoomId: Encryption] = [:]
static private var queue = DispatchQueue(label: "chat.rocket.instanceQueue")
private var encryptionQueue = DispatchQueue(label: "chat.rocket.encryptionQueue")
init(server: Server) {
self.server = server
self.api = API(server: server)
}
static func instanceForServer(server: Server) -> RocketChat {
queue.sync {
if let rocketchat = instances[server] {
return rocketchat
}
let rocketchat = RocketChat(server: server)
instances[server] = rocketchat
return rocketchat
}
}
func getPushWithId(_ msgId: String, completion: @escaping((Notification?) -> Void)) {
api?.fetch(request: PushRequest(msgId: msgId), retry: Retry(retries: 4)) { response in
switch response {
case .resource(let response):
let notification = response.data.notification
completion(notification)
case .error:
completion(nil)
break
}
}
}
func sendMessage(rid: String, message: String, completion: @escaping((MessageResponse?) -> Void)) {
let id = String.random(length: 17)
var msg = message
let encrypted = Database(server: server).readRoomEncrypted(rid: rid)
if encrypted {
msg = encryptMessage(rid: rid, id: id, message: message)
}
api?.fetch(request: SendMessageRequest(id: id, roomId: rid, text: msg, messageType: encrypted ? .e2e : nil )) { response in
switch response {
case .resource(let response):
completion(response)
case .error:
completion(nil)
break
}
}
}
func decryptMessage(rid: String, message: String) -> String? {
encryptionQueue.sync {
if let encryption = encryptionInstances[rid] {
return encryption.decryptMessage(message: message)
}
let encryption = Encryption(server: server, rid: rid)
encryptionInstances[rid] = encryption
return encryption.decryptMessage(message: message)
}
}
func encryptMessage(rid: String, id: String, message: String) -> String {
encryptionQueue.sync {
if let encryption = encryptionInstances[rid] {
return encryption.encryptMessage(id: id, message: message)
}
let encryption = Encryption(server: server, rid: rid)
encryptionInstances[rid] = encryption
return encryption.encryptMessage(id: id, message: message)
}
}
}

View File

@ -0,0 +1,62 @@
//
// Storage.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Credentials {
let userId: String
let userToken: String
}
class Storage {
static let shared = Storage()
final var mmkv: MMKV? = nil
init() {
let mmapID = "default"
let instanceID = "com.MMKV.\(mmapID)"
let secureStorage = SecureStorage()
// get mmkv instance password from keychain
var key: Data?
secureStorage.getSecureKey(instanceID.toHex()) { (response) -> () in
if let password = response?[1] as? String {
key = password.data(using: .utf8)
}
}
guard let cryptKey = key else {
return
}
// Get App Group directory
let suiteName = Bundle.main.object(forInfoDictionaryKey: "AppGroup") as! String
guard let directory = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else {
return
}
// Set App Group dir
MMKV.initialize(rootDir: nil, groupDir: directory.path, logLevel: MMKVLogLevel.none)
self.mmkv = MMKV(mmapID: mmapID, cryptKey: cryptKey, mode: MMKVMode.multiProcess)
}
func getCredentials(server: String) -> Credentials? {
if let userId = self.mmkv?.string(forKey: "reactnativemeteor_usertoken-\(server)") {
if let userToken = self.mmkv?.string(forKey: "reactnativemeteor_usertoken-\(userId)") {
return Credentials(userId: userId, userToken: userToken)
}
}
return nil
}
func getPrivateKey(server: String) -> String? {
return self.mmkv?.string(forKey: "\(server)-RC_E2E_PRIVATE_KEY")
}
}

View File

@ -113,7 +113,7 @@
"react-redux": "7.2.0",
"reactotron-react-native": "5.0.0",
"redux": "4.0.5",
"redux-immutable-state-invariant": "^2.1.0",
"redux-immutable-state-invariant": "2.1.0",
"redux-saga": "1.1.3",
"remove-markdown": "^0.3.0",
"reselect": "^4.0.0",

View File

@ -1,3 +1,36 @@
diff --git a/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift b/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift
index 43f2c9c..e24a24f 100644
--- a/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift
+++ b/node_modules/@nozbe/watermelondb/native/ios/WatermelonDB/Database.swift
@@ -1,14 +1,14 @@
import Foundation
-class Database {
- typealias SQL = String
- typealias TableName = String
- typealias QueryArgs = [Any]
+public class Database {
+ public typealias SQL = String
+ public typealias TableName = String
+ public typealias QueryArgs = [Any]
private let fmdb: FMDatabase
private let path: String
- init(path: String) {
+ public init(path: String) {
self.path = path
fmdb = FMDatabase(path: path)
open()
@@ -53,7 +53,7 @@ class Database {
}
}
- func queryRaw(_ query: SQL, _ args: QueryArgs = []) throws -> AnyIterator<FMResultSet> {
+ public func queryRaw(_ query: SQL, _ args: QueryArgs = []) throws -> AnyIterator<FMResultSet> {
let resultSet = try fmdb.executeQuery(query, values: args)
return AnyIterator {
diff --git a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt b/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
index 2217222..5b2eb73 100644
--- a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt

View File

@ -1,159 +1,3 @@
diff --git a/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m b/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m
index edc4fd4..694f01f 100644
--- a/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m
+++ b/node_modules/react-native-notifications/RNNotifications/RNNotificationEventHandler.m
@@ -3,6 +3,8 @@
#import "RNNotificationUtils.h"
#import "RCTConvert+RNNotifications.h"
#import "RNNotificationParser.h"
+#import <MMKV/MMKV.h>
+#import "SecureStorage.h"
@implementation RNNotificationEventHandler {
RNNotificationsStore* _store;
@@ -28,9 +30,140 @@ - (void)didReceiveForegroundNotification:(UNNotification *)notification withComp
[RNEventEmitter sendEvent:RNNotificationReceivedForeground body:[RNNotificationParser parseNotification:notification]];
}
+/*
+ * Generate a random alphanumeric string to message id
+*/
+-(NSString *)random:(int)len {
+ NSString *letters = @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+ NSMutableString *randomString = [NSMutableString stringWithCapacity:len];
+
+ for (int i=0; i<len; i++) {
+ [randomString appendFormat: @"%C", [letters characterAtIndex: arc4random_uniform([letters length])]];
+ }
+
+ return randomString;
+}
+
+/*
+ * Remove trailing slash on server url from notification
+*/
+- (NSString *)serverURL:(NSString *)host {
+ if ([host length] > 0) {
+ unichar last = [host characterAtIndex:[host length] - 1];
+ if (last == '/') {
+ host = [host substringToIndex:[host length] - 1];
+ }
+ }
+ return host;
+}
+
+- (NSString *)stringToHex:(NSString *)string
+{
+ char *utf8 = (char *)[string UTF8String];
+ NSMutableString *hex = [NSMutableString string];
+ while (*utf8) [hex appendFormat:@"%02X", *utf8++ & 0x00FF];
+
+ return [[NSString stringWithFormat:@"%@", hex] lowercaseString];
+}
+
+- (NSDictionary *) retrieveCredentialsForServer:(NSString *)serverURL {
+ // AppGroup MMKV
+ NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path;
+
+ // Make sure MMKV is using the group directory
+ [MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogNone];
+
+ // Start the MMKV container
+ __block MMKV *mmkv;
+ SecureStorage *secureStorage = [[SecureStorage alloc] init];
+
+ // https://github.com/ammarahm-ed/react-native-mmkv-storage/blob/master/src/loader.js#L31
+ [secureStorage getSecureKey:[self stringToHex:@"com.MMKV.default"] callback:^(NSArray *response) {
+ // error handling
+ if ([response objectAtIndex:0] != [NSNull null]) {
+ return;
+ }
+ NSString *key = [response objectAtIndex:1];
+ NSData *cryptKey = [key dataUsingEncoding:NSUTF8StringEncoding];
+ mmkv = [MMKV mmkvWithID:@"default" cryptKey:cryptKey mode:MMKVMultiProcess];
+ }];
+
+ if (!mmkv) {
+ return @{
+ @"userId": @"",
+ @"userToken": @""
+ };
+ }
+ // Get credentials
+ NSString *TOKEN_KEY = @"reactnativemeteor_usertoken";
+ NSString *userId = [mmkv getStringForKey:[NSString stringWithFormat:@"%@-%@", TOKEN_KEY, serverURL]];
+ NSString *userToken = [mmkv getStringForKey:[NSString stringWithFormat:@"%@-%@", TOKEN_KEY, userId]];
+
+ // Retrive a NSDictionary with the credentials
+ return @{
+ @"userId": userId,
+ @"userToken": userToken
+ };
+}
+
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(void))completionHandler {
- [_store setActionCompletionHandler:completionHandler withCompletionKey:response.notification.request.identifier];
- [RNEventEmitter sendEvent:RNNotificationOpened body:[RNNotificationParser parseNotificationResponse:response]];
+ // if notification response is a REPLY_ACTION
+ if ([response.actionIdentifier isEqualToString:@"REPLY_ACTION"]) {
+ // convert notification data to dictionary payload
+ NSDictionary *notification = [RCTConvert UNNotificationPayload:response.notification];
+
+ // parse ejson from notification
+ NSData *ejsonData = [[notification valueForKey:@"ejson"] dataUsingEncoding:NSUTF8StringEncoding];
+ NSError *error;
+ NSDictionary *ejson = [NSJSONSerialization JSONObjectWithData:ejsonData options:kNilOptions error:&error];
+
+ // data from notification
+ NSString *host = [ejson valueForKey:@"host"];
+ NSString *rid = [ejson valueForKey:@"rid"];
+
+ // msg on textinput of notification
+ NSString *msg = [(UNTextInputNotificationResponse *)response userText];
+
+ // get credentials
+ NSDictionary *credentials = [self retrieveCredentialsForServer:[self serverURL:host]];
+ NSString *userId = [credentials valueForKey:@"userId"];
+ NSString *token = [credentials valueForKey:@"userToken"];
+
+ // background task - we need this because fetch doesn't work if app is closed/killed
+ UIApplication *app = [UIApplication sharedApplication];
+ __block UIBackgroundTaskIdentifier task = [app beginBackgroundTaskWithExpirationHandler:^{
+ [app endBackgroundTask:task];
+ task = UIBackgroundTaskInvalid;
+ }];
+ // we use global queue to make requests with app closed/killed
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ // we make a synchronous request to post new message
+ NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"%@/api/v1/chat.sendMessage", [self serverURL:host]]]];
+
+ NSString *message = [NSString stringWithFormat:@"{ \"message\": { \"_id\": \"%@\", \"msg\": \"%@\", \"rid\": \"%@\" } }", [self random:17], msg, rid];
+
+ [request setHTTPMethod:@"POST"];
+ [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
+ [request addValue:userId forHTTPHeaderField:@"x-user-id"];
+ [request addValue:token forHTTPHeaderField:@"x-auth-token"];
+ [request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding]];
+
+ NSURLResponse *response = nil;
+ NSError *error = nil;
+ NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
+
+ // end background task
+ [app endBackgroundTask:task];
+ task = UIBackgroundTaskInvalid;
+
+ // complete notification response
+ completionHandler();
+ });
+ } else {
+ // We only set initial notification and emit event to JS when not is a reply action
+ [_store setActionCompletionHandler:completionHandler withCompletionKey:response.notification.request.identifier];
+ [RNEventEmitter sendEvent:RNNotificationOpened body:[RNNotificationParser parseNotificationResponse:response]];
+ }
}
@end
diff --git a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java b/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
index 524ff07..70f22d5 100644
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java

View File

@ -13341,7 +13341,7 @@ redent@^1.0.0:
indent-string "^2.1.0"
strip-indent "^1.0.1"
redux-immutable-state-invariant@^2.1.0:
redux-immutable-state-invariant@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1"
integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg==