[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:
parent
f30c405de3
commit
60dc128c63
|
@ -43,7 +43,8 @@ class Encryption {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize Encryption client
|
// Initialize Encryption client
|
||||||
initialize = () => {
|
initialize = (userId) => {
|
||||||
|
this.userId = userId;
|
||||||
this.roomInstances = {};
|
this.roomInstances = {};
|
||||||
|
|
||||||
// Don't await these promises
|
// Don't await these promises
|
||||||
|
@ -69,6 +70,7 @@ class Encryption {
|
||||||
|
|
||||||
// Stop Encryption client
|
// Stop Encryption client
|
||||||
stop = () => {
|
stop = () => {
|
||||||
|
this.userId = null;
|
||||||
this.privateKey = null;
|
this.privateKey = null;
|
||||||
this.roomInstances = {};
|
this.roomInstances = {};
|
||||||
// Cancel ongoing encryption/decryption requests
|
// Cancel ongoing encryption/decryption requests
|
||||||
|
@ -199,7 +201,7 @@ class Encryption {
|
||||||
|
|
||||||
// If doesn't have a instance of this room
|
// If doesn't have a instance of this room
|
||||||
if (!this.roomInstances[rid]) {
|
if (!this.roomInstances[rid]) {
|
||||||
this.roomInstances[rid] = new EncryptionRoom(rid);
|
this.roomInstances[rid] = new EncryptionRoom(rid, this.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const roomE2E = this.roomInstances[rid];
|
const roomE2E = this.roomInstances[rid];
|
||||||
|
|
|
@ -21,9 +21,10 @@ import database from '../database';
|
||||||
import log from '../../utils/log';
|
import log from '../../utils/log';
|
||||||
|
|
||||||
export default class EncryptionRoom {
|
export default class EncryptionRoom {
|
||||||
constructor(roomId) {
|
constructor(roomId, userId) {
|
||||||
this.ready = false;
|
this.ready = false;
|
||||||
this.roomId = roomId;
|
this.roomId = roomId;
|
||||||
|
this.userId = userId;
|
||||||
this.establishing = false;
|
this.establishing = false;
|
||||||
this.readyPromise = new Deferred();
|
this.readyPromise = new Deferred();
|
||||||
this.readyPromise.then(() => {
|
this.readyPromise.then(() => {
|
||||||
|
|
|
@ -79,7 +79,7 @@ const handleEncryptionInit = function* handleEncryptionInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt all pending messages/subscriptions
|
// Decrypt all pending messages/subscriptions
|
||||||
Encryption.initialize();
|
Encryption.initialize(user.id);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e);
|
log(e);
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ const handleEncryptionDecodeKey = function* handleEncryptionDecodeKey({ password
|
||||||
yield Encryption.persistKeys(server, publicKey, privateKey);
|
yield Encryption.persistKeys(server, publicKey, privateKey);
|
||||||
|
|
||||||
// Decrypt all pending messages/subscriptions
|
// Decrypt all pending messages/subscriptions
|
||||||
Encryption.initialize();
|
Encryption.initialize(user.id);
|
||||||
|
|
||||||
// Hide encryption banner
|
// Hide encryption banner
|
||||||
yield put(encryptionSetBanner());
|
yield put(encryptionSetBanner());
|
||||||
|
|
|
@ -2,5 +2,8 @@
|
||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "SecureStorage.h"
|
#import <MMKV/MMKV.h>
|
||||||
#import <MMKVAppExtension/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>
|
||||||
|
|
|
@ -1,177 +1,55 @@
|
||||||
import CoreLocation
|
|
||||||
import UserNotifications
|
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 {
|
class NotificationService: UNNotificationServiceExtension {
|
||||||
|
|
||||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
|
||||||
var bestAttemptContent: UNMutableNotificationContent?
|
|
||||||
|
|
||||||
var retryCount = 0
|
|
||||||
var retryTimeout = [1.0, 3.0, 5.0, 10.0]
|
|
||||||
|
|
||||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
|
||||||
self.contentHandler = contentHandler
|
|
||||||
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let notificationType = data.notificationType ?? ""
|
|
||||||
|
|
||||||
// If the notification have the content at her payload, show it
|
|
||||||
if notificationType != "message-id-only" {
|
|
||||||
contentHandler(bestAttemptContent)
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
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) {
|
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||||
let task = URLSession.shared.dataTask(with: request) {(data, response, error) in
|
var bestAttemptContent: UNMutableNotificationContent?
|
||||||
|
var rocketchat: RocketChat?
|
||||||
func retryRequest() {
|
|
||||||
// if we can try again
|
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
if self.retryCount < self.retryTimeout.count {
|
self.contentHandler = contentHandler
|
||||||
// Try again after X seconds
|
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + self.retryTimeout[self.retryCount], execute: {
|
|
||||||
self.runRequest(request: request, bestAttemptContent: bestAttemptContent, contentHandler: contentHandler)
|
if let bestAttemptContent = bestAttemptContent {
|
||||||
self.retryCount += 1
|
let ejson = (bestAttemptContent.userInfo["ejson"] as? String ?? "").data(using: .utf8)!
|
||||||
})
|
guard let data = try? (JSONDecoder().decode(Payload.self, from: ejson)) else {
|
||||||
}
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// If some error happened
|
rocketchat = RocketChat.instanceForServer(server: data.host.removeTrailingSlash())
|
||||||
if error != nil {
|
|
||||||
retryRequest()
|
// If the notification has the content on the payload, show it
|
||||||
|
if data.notificationType != .messageIdOnly {
|
||||||
// Check if the request did successfully
|
self.processPayload(payload: data)
|
||||||
} else if let response = response as? HTTPURLResponse {
|
return
|
||||||
// if it not was successfully
|
}
|
||||||
if response.statusCode != 200 {
|
|
||||||
retryRequest()
|
// Request the content from server
|
||||||
|
rocketchat?.getPushWithId(data.messageId) { notification in
|
||||||
// If the response status is 200
|
if let notification = notification {
|
||||||
} else {
|
self.bestAttemptContent?.title = notification.title
|
||||||
// Process data
|
self.bestAttemptContent?.body = notification.text
|
||||||
if let data = data {
|
self.processPayload(payload: notification.payload)
|
||||||
// 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
|
func processPayload(payload: Payload) {
|
||||||
|
// If is a encrypted message
|
||||||
let payload = try? (JSONEncoder().encode(push.data.notification.payload))
|
if payload.messageType == .e2e {
|
||||||
if let payload = payload {
|
if let message = payload.msg, let rid = payload.rid {
|
||||||
bestAttemptContent.userInfo["ejson"] = String(data: payload, encoding: .utf8) ?? "{}"
|
if let decryptedMessage = rocketchat?.decryptMessage(rid: rid, message: message) {
|
||||||
}
|
bestAttemptContent?.body = decryptedMessage
|
||||||
|
if let roomType = payload.type, roomType == .group, let sender = payload.senderName {
|
||||||
// Show notification with the content modified
|
bestAttemptContent?.body = "\(sender): \(decryptedMessage)"
|
||||||
contentHandler(bestAttemptContent)
|
}
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
retryRequest()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task.resume()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override func serviceExtensionTimeWillExpire() {
|
if let bestAttemptContent = bestAttemptContent {
|
||||||
// Called just before the extension will be terminated by the system.
|
contentHandler?(bestAttemptContent)
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
|
@ -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
|
|
||||||
|
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -20,7 +20,7 @@ end
|
||||||
|
|
||||||
# used to get user credentials
|
# used to get user credentials
|
||||||
target 'NotificationService' do
|
target 'NotificationService' do
|
||||||
pod 'MMKVAppExtension'
|
all_pods
|
||||||
end
|
end
|
||||||
|
|
||||||
post_install do |installer|
|
post_install do |installer|
|
||||||
|
|
|
@ -186,8 +186,6 @@ PODS:
|
||||||
- libwebp/webp (1.1.0)
|
- libwebp/webp (1.1.0)
|
||||||
- MMKV (1.2.1):
|
- MMKV (1.2.1):
|
||||||
- MMKVCore (~> 1.2.1)
|
- MMKVCore (~> 1.2.1)
|
||||||
- MMKVAppExtension (1.2.1):
|
|
||||||
- MMKVCore (~> 1.2.1)
|
|
||||||
- MMKVCore (1.2.1)
|
- MMKVCore (1.2.1)
|
||||||
- nanopb (1.30905.0):
|
- nanopb (1.30905.0):
|
||||||
- nanopb/decode (= 1.30905.0)
|
- nanopb/decode (= 1.30905.0)
|
||||||
|
@ -386,7 +384,7 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- react-native-safe-area-context (3.1.1):
|
- react-native-safe-area-context (3.1.1):
|
||||||
- React
|
- React
|
||||||
- react-native-simple-crypto (0.3.1):
|
- react-native-simple-crypto (0.4.0):
|
||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
- React
|
- React
|
||||||
- react-native-slider (3.0.2):
|
- react-native-slider (3.0.2):
|
||||||
|
@ -578,7 +576,6 @@ DEPENDENCIES:
|
||||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||||
- KeyCommands (from `../node_modules/react-native-keycommands`)
|
- KeyCommands (from `../node_modules/react-native-keycommands`)
|
||||||
- MMKVAppExtension
|
|
||||||
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
||||||
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
||||||
- React (from `../node_modules/react-native/`)
|
- React (from `../node_modules/react-native/`)
|
||||||
|
@ -676,7 +673,6 @@ SPEC REPOS:
|
||||||
- JitsiMeetSDK
|
- JitsiMeetSDK
|
||||||
- libwebp
|
- libwebp
|
||||||
- MMKV
|
- MMKV
|
||||||
- MMKVAppExtension
|
|
||||||
- MMKVCore
|
- MMKVCore
|
||||||
- nanopb
|
- nanopb
|
||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
|
@ -904,7 +900,6 @@ SPEC CHECKSUMS:
|
||||||
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
|
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
|
||||||
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
||||||
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
|
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
|
||||||
MMKVAppExtension: d792aa7bd301285e2c3100c5ce15aa44fa26456f
|
|
||||||
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
|
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
|
||||||
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
|
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
|
||||||
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
||||||
|
@ -929,7 +924,7 @@ SPEC CHECKSUMS:
|
||||||
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
|
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
|
||||||
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
|
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
|
||||||
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
|
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
|
||||||
react-native-simple-crypto: 5e4f2877f71675d95baabf5823cd0cc0c379d0e6
|
react-native-simple-crypto: 564740fd8124827d82e9e8ded4a0de8c695c8a4d
|
||||||
react-native-slider: 0221b417686c5957f6e77cd9ac22c1478a165355
|
react-native-slider: 0221b417686c5957f6e77cd9ac22c1478a165355
|
||||||
react-native-webview: 679b6f400176e2ea8a785acf7ae16cf282e7d1eb
|
react-native-webview: 679b6f400176e2ea8a785acf7ae16cf282e7d1eb
|
||||||
React-RCTActionSheet: 1702a1a85e550b5c36e2e03cb2bd3adea053de95
|
React-RCTActionSheet: 1702a1a85e550b5c36e2e03cb2bd3adea053de95
|
||||||
|
@ -983,6 +978,6 @@ SPEC CHECKSUMS:
|
||||||
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
|
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
|
||||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||||
|
|
||||||
PODFILE CHECKSUM: 4916aa46fbfaed764171540e6ed76fb07a8a95e7
|
PODFILE CHECKSUM: 19c78b598c807d2c6b988fd11b24544ac1895a35
|
||||||
|
|
||||||
COCOAPODS: 1.9.3
|
COCOAPODS: 1.9.3
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h
|
|
|
@ -1 +0,0 @@
|
||||||
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h
|
|
|
@ -1 +0,0 @@
|
||||||
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKV.h
|
|
|
@ -1 +0,0 @@
|
||||||
../../../MMKVAppExtension/iOS/MMKV/MMKV/MMKVHandler.h
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "react-native-simple-crypto",
|
"name": "react-native-simple-crypto",
|
||||||
"version": "0.3.1",
|
"version": "0.4.0",
|
||||||
"summary": "A simpler React-Native crypto library",
|
"summary": "A simpler React-Native crypto library",
|
||||||
"authors": "Gary Button <gary.button.public@gmail.com>",
|
"authors": "Gary Button <gary.button.public@gmail.com>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
|
@ -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.
|
|
|
@ -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).
|
|
|
@ -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
|
|
|
@ -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 */
|
|
|
@ -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()]];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -186,8 +186,6 @@ PODS:
|
||||||
- libwebp/webp (1.1.0)
|
- libwebp/webp (1.1.0)
|
||||||
- MMKV (1.2.1):
|
- MMKV (1.2.1):
|
||||||
- MMKVCore (~> 1.2.1)
|
- MMKVCore (~> 1.2.1)
|
||||||
- MMKVAppExtension (1.2.1):
|
|
||||||
- MMKVCore (~> 1.2.1)
|
|
||||||
- MMKVCore (1.2.1)
|
- MMKVCore (1.2.1)
|
||||||
- nanopb (1.30905.0):
|
- nanopb (1.30905.0):
|
||||||
- nanopb/decode (= 1.30905.0)
|
- nanopb/decode (= 1.30905.0)
|
||||||
|
@ -386,7 +384,7 @@ PODS:
|
||||||
- React
|
- React
|
||||||
- react-native-safe-area-context (3.1.1):
|
- react-native-safe-area-context (3.1.1):
|
||||||
- React
|
- React
|
||||||
- react-native-simple-crypto (0.3.1):
|
- react-native-simple-crypto (0.4.0):
|
||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
- React
|
- React
|
||||||
- react-native-slider (3.0.2):
|
- react-native-slider (3.0.2):
|
||||||
|
@ -578,7 +576,6 @@ DEPENDENCIES:
|
||||||
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
- Folly (from `../node_modules/react-native/third-party-podspecs/Folly.podspec`)
|
||||||
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
- glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`)
|
||||||
- KeyCommands (from `../node_modules/react-native-keycommands`)
|
- KeyCommands (from `../node_modules/react-native-keycommands`)
|
||||||
- MMKVAppExtension
|
|
||||||
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
- RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`)
|
||||||
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
- RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`)
|
||||||
- React (from `../node_modules/react-native/`)
|
- React (from `../node_modules/react-native/`)
|
||||||
|
@ -676,7 +673,6 @@ SPEC REPOS:
|
||||||
- JitsiMeetSDK
|
- JitsiMeetSDK
|
||||||
- libwebp
|
- libwebp
|
||||||
- MMKV
|
- MMKV
|
||||||
- MMKVAppExtension
|
|
||||||
- MMKVCore
|
- MMKVCore
|
||||||
- nanopb
|
- nanopb
|
||||||
- OpenSSL-Universal
|
- OpenSSL-Universal
|
||||||
|
@ -904,7 +900,6 @@ SPEC CHECKSUMS:
|
||||||
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
|
KeyCommands: f66c535f698ed14b3d3a4e58859d79a827ea907e
|
||||||
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
libwebp: 946cb3063cea9236285f7e9a8505d806d30e07f3
|
||||||
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
|
MMKV: 67253edee25a34edf332f91d73fa94a9e038b971
|
||||||
MMKVAppExtension: d792aa7bd301285e2c3100c5ce15aa44fa26456f
|
|
||||||
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
|
MMKVCore: fe398984acac1fa33f92795d1b5fd0a334c944af
|
||||||
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
|
nanopb: c43f40fadfe79e8b8db116583945847910cbabc9
|
||||||
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
OpenSSL-Universal: 8b48cc0d10c1b2923617dfe5c178aa9ed2689355
|
||||||
|
@ -929,7 +924,7 @@ SPEC CHECKSUMS:
|
||||||
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
|
react-native-notifications: ee8fd739853e72694f3af8b374c8ccb106b7b227
|
||||||
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
|
react-native-orientation-locker: f0ca1a8e5031dab6b74bfb4ab33a17ed2c2fcb0d
|
||||||
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
|
react-native-safe-area-context: 344b969c45af3d8464d36e8dea264942992ef033
|
||||||
react-native-simple-crypto: 5e4f2877f71675d95baabf5823cd0cc0c379d0e6
|
react-native-simple-crypto: 564740fd8124827d82e9e8ded4a0de8c695c8a4d
|
||||||
react-native-slider: 0221b417686c5957f6e77cd9ac22c1478a165355
|
react-native-slider: 0221b417686c5957f6e77cd9ac22c1478a165355
|
||||||
react-native-webview: 679b6f400176e2ea8a785acf7ae16cf282e7d1eb
|
react-native-webview: 679b6f400176e2ea8a785acf7ae16cf282e7d1eb
|
||||||
React-RCTActionSheet: 1702a1a85e550b5c36e2e03cb2bd3adea053de95
|
React-RCTActionSheet: 1702a1a85e550b5c36e2e03cb2bd3adea053de95
|
||||||
|
@ -983,6 +978,6 @@ SPEC CHECKSUMS:
|
||||||
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
|
Yoga: d5bd05a2b6b94c52323745c2c2b64557c8c66f64
|
||||||
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
|
||||||
|
|
||||||
PODFILE CHECKSUM: 4916aa46fbfaed764171540e6ed76fb07a8a95e7
|
PODFILE CHECKSUM: 19c78b598c807d2c6b988fd11b24544ac1895a35
|
||||||
|
|
||||||
COCOAPODS: 1.9.3
|
COCOAPODS: 1.9.3
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,5 +0,0 @@
|
||||||
#import <Foundation/Foundation.h>
|
|
||||||
@interface PodsDummy_MMKVAppExtension : NSObject
|
|
||||||
@end
|
|
||||||
@implementation PodsDummy_MMKVAppExtension
|
|
||||||
@end
|
|
|
@ -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
|
|
|
@ -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
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
165
ios/Pods/Target Support Files/Pods-NotificationService/Pods-NotificationService-resources.sh
generated
Executable file
165
ios/Pods/Target Support Files/Pods-NotificationService/Pods-NotificationService-resources.sh
generated
Executable 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
|
|
@ -10,3 +10,7 @@
|
||||||
#endif
|
#endif
|
||||||
#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
6
ios/Pods/Target Support Files/Pods-NotificationService/Pods-NotificationService.modulemap
generated
Normal file
6
ios/Pods/Target Support Files/Pods-NotificationService/Pods-NotificationService.modulemap
generated
Normal 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
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,3 +2,11 @@
|
||||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
// 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
|
@ -10,6 +10,7 @@
|
||||||
#import <UIKit/UIKit.h>
|
#import <UIKit/UIKit.h>
|
||||||
#import <React/RCTBridgeDelegate.h>
|
#import <React/RCTBridgeDelegate.h>
|
||||||
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
|
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
|
||||||
|
#import "RocketChatRN-Swift.h"
|
||||||
|
|
||||||
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
|
@interface AppDelegate : UIResponder <UIApplicationDelegate, RCTBridgeDelegate>
|
||||||
|
|
||||||
|
|
|
@ -64,6 +64,7 @@ static void InitializeFlipper(UIApplication *application) {
|
||||||
self.window.rootViewController = rootViewController;
|
self.window.rootViewController = rootViewController;
|
||||||
[self.window makeKeyAndVisible];
|
[self.window makeKeyAndVisible];
|
||||||
[RNNotifications startMonitorNotifications];
|
[RNNotifications startMonitorNotifications];
|
||||||
|
[ReplyNotification configure];
|
||||||
|
|
||||||
// AppGroup MMKV
|
// AppGroup MMKV
|
||||||
NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path;
|
NSString *groupDir = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:[[NSBundle mainBundle] objectForInfoDictionaryKey:@"AppGroup"]].path;
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()! })
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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?
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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?
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
||||||
|
}
|
|
@ -113,7 +113,7 @@
|
||||||
"react-redux": "7.2.0",
|
"react-redux": "7.2.0",
|
||||||
"reactotron-react-native": "5.0.0",
|
"reactotron-react-native": "5.0.0",
|
||||||
"redux": "4.0.5",
|
"redux": "4.0.5",
|
||||||
"redux-immutable-state-invariant": "^2.1.0",
|
"redux-immutable-state-invariant": "2.1.0",
|
||||||
"redux-saga": "1.1.3",
|
"redux-saga": "1.1.3",
|
||||||
"remove-markdown": "^0.3.0",
|
"remove-markdown": "^0.3.0",
|
||||||
"reselect": "^4.0.0",
|
"reselect": "^4.0.0",
|
||||||
|
|
|
@ -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
|
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
|
index 2217222..5b2eb73 100644
|
||||||
--- a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
|
--- a/node_modules/@nozbe/watermelondb/native/android/src/main/java/com/nozbe/watermelondb/Database.kt
|
||||||
|
|
|
@ -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
|
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
|
index 524ff07..70f22d5 100644
|
||||||
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
|
--- a/node_modules/react-native-notifications/android/app/src/main/java/com/wix/reactnativenotifications/core/notification/PushNotification.java
|
||||||
|
|
|
@ -13341,7 +13341,7 @@ redent@^1.0.0:
|
||||||
indent-string "^2.1.0"
|
indent-string "^2.1.0"
|
||||||
strip-indent "^1.0.1"
|
strip-indent "^1.0.1"
|
||||||
|
|
||||||
redux-immutable-state-invariant@^2.1.0:
|
redux-immutable-state-invariant@2.1.0:
|
||||||
version "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"
|
resolved "https://registry.yarnpkg.com/redux-immutable-state-invariant/-/redux-immutable-state-invariant-2.1.0.tgz#308fd3cc7415a0e7f11f51ec997b6379c7055ce1"
|
||||||
integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg==
|
integrity sha512-3czbDKs35FwiBRsx/3KabUk5zSOoTXC+cgVofGkpBNv3jQcqIe5JrHcF5AmVt7B/4hyJ8MijBIpCJ8cife6yJg==
|
||||||
|
|
Loading…
Reference in New Issue