Add WatchConnectivity

This commit is contained in:
Djorkaeff Alexandre 2024-01-15 15:31:38 -03:00
parent 96901bba39
commit 31c1181f91
16 changed files with 429 additions and 86 deletions

View File

@ -1,6 +1,24 @@
import SwiftUI
final class ContentViewModel: ObservableObject {
private let connection: Connection
init(connection: Connection) {
self.connection = connection
}
func onAppear() {
connection.sendMessage { result in
print(result)
}
}
}
struct ContentView: View {
@StateObject var viewModel = ContentViewModel(
connection: WatchConnection()
)
var body: some View {
VStack {
Image(systemName: "globe")
@ -9,6 +27,9 @@ struct ContentView: View {
Text("Hello, world!")
}
.padding()
.onAppear {
viewModel.onAppear()
}
}
}

View File

@ -0,0 +1,68 @@
import Foundation
import WatchConnectivity
enum ConnectionError: Error {
case needsUnlock
case decoding(Error)
}
protocol Connection {
func sendMessage(completionHandler: @escaping (Result<WatchMessage, ConnectionError>) -> Void)
}
final class WatchConnection: NSObject {
private let session: WCSession
init(session: WCSession = .default) {
self.session = session
super.init()
session.delegate = self
session.activate()
}
private func scheduledSendMessage(completionHandler: @escaping (Result<WatchMessage, ConnectionError>) -> Void) {
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) { [weak self] _ in
self?.sendMessage(completionHandler: completionHandler)
}
}
}
// MARK: - WCSessionDelegate
extension WatchConnection: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
}
// MARK: - Connection
extension WatchConnection: Connection {
func sendMessage(completionHandler: @escaping (Result<WatchMessage, ConnectionError>) -> Void) {
guard session.activationState == .activated else {
scheduledSendMessage(completionHandler: completionHandler)
return
}
guard !session.iOSDeviceNeedsUnlockAfterRebootForReachability else {
completionHandler(.failure(.needsUnlock))
return
}
guard session.isReachable else {
scheduledSendMessage(completionHandler: completionHandler)
return
}
session.sendMessage([:]) { dictionary in
do {
let data = try JSONSerialization.data(withJSONObject: dictionary)
let message = try JSONDecoder().decode(WatchMessage.self, from: data)
completionHandler(.success(message))
} catch {
completionHandler(.failure(.decoding(error)))
}
}
}
}

View File

@ -79,6 +79,28 @@
1ED038952B507B4D00C007D4 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038942B507B4D00C007D4 /* Assets.xcassets */; };
1ED038982B507B4D00C007D4 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */; };
1ED0389B2B507B4D00C007D4 /* Rocket.Chat.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 1ED0388E2B507B4B00C007D4 /* Rocket.Chat.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
1ED038A12B508FE700C007D4 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */; };
1ED038A22B508FE700C007D4 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */; };
1ED038A32B508FE700C007D4 /* FileManager+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */; };
1ED038A52B50900800C007D4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */; };
1ED038A62B50900800C007D4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */; };
1ED038A72B50900800C007D4 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */; };
1ED038A92B5090AD00C007D4 /* MMKV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A82B5090AD00C007D4 /* MMKV.swift */; };
1ED038AA2B5090AD00C007D4 /* MMKV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A82B5090AD00C007D4 /* MMKV.swift */; };
1ED038AB2B5090AD00C007D4 /* MMKV.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038A82B5090AD00C007D4 /* MMKV.swift */; };
1ED038AD2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */; };
1ED038AE2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */; };
1ED038AF2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */; };
1ED038BA2B50A1B800C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038B92B50A1B800C007D4 /* WatchConnection.swift */; };
1ED038BB2B50A1B800C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038B92B50A1B800C007D4 /* WatchConnection.swift */; };
1ED038BE2B50A1D400C007D4 /* DBServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038BD2B50A1D400C007D4 /* DBServer.swift */; };
1ED038BF2B50A1D400C007D4 /* DBServer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038BD2B50A1D400C007D4 /* DBServer.swift */; };
1ED038C12B50A1E400C007D4 /* DBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C02B50A1E400C007D4 /* DBUser.swift */; };
1ED038C22B50A1E400C007D4 /* DBUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C02B50A1E400C007D4 /* DBUser.swift */; };
1ED038C42B50A1F500C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; };
1ED038C52B50A1F500C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; };
1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C32B50A1F500C007D4 /* WatchMessage.swift */; };
1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1ED038C92B50A58400C007D4 /* WatchConnection.swift */; };
1ED59D4C22CBA77D00C54289 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */; };
1EF5FBD1250C109E00614FEA /* Encryption.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EF5FBD0250C109E00614FEA /* Encryption.swift */; };
1EFEB5982493B6640072EDC0 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1EFEB5972493B6640072EDC0 /* NotificationService.swift */; };
@ -287,6 +309,15 @@
1ED038922B507B4C00C007D4 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
1ED038942B507B4D00C007D4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1ED038972B507B4D00C007D4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileManager+Extensions.swift"; sourceTree = "<group>"; };
1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = "<group>"; };
1ED038A82B5090AD00C007D4 /* MMKV.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MMKV.swift; sourceTree = "<group>"; };
1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WatermelonDB+Extensions.swift"; sourceTree = "<group>"; };
1ED038B92B50A1B800C007D4 /* WatchConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnection.swift; sourceTree = "<group>"; };
1ED038BD2B50A1D400C007D4 /* DBServer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBServer.swift; sourceTree = "<group>"; };
1ED038C02B50A1E400C007D4 /* DBUser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBUser.swift; sourceTree = "<group>"; };
1ED038C32B50A1F500C007D4 /* WatchMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchMessage.swift; sourceTree = "<group>"; };
1ED038C92B50A58400C007D4 /* WatchConnection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchConnection.swift; sourceTree = "<group>"; };
1ED59D4B22CBA77D00C54289 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
1EF5FBD0250C109E00614FEA /* Encryption.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Encryption.swift; sourceTree = "<group>"; };
1EFEB5952493B6640072EDC0 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
@ -462,6 +493,7 @@
1E1C2F7F250FCB69005DCE7D /* Database.swift */,
1E470E822513A71E00E3DD1D /* RocketChat.swift */,
1EB8EF712510F1EE00F352B7 /* Storage.swift */,
1ED038A82B5090AD00C007D4 /* MMKV.swift */,
);
path = RocketChat;
sourceTree = "<group>";
@ -473,6 +505,9 @@
1E598AE32515057D002BDFBD /* Date+Extensions.swift */,
1E598AE625150660002BDFBD /* Data+Extensions.swift */,
1E67380324DC529B0009E081 /* String+Extensions.swift */,
1ED038A02B508FE700C007D4 /* FileManager+Extensions.swift */,
1ED038A42B50900800C007D4 /* Bundle+Extensions.swift */,
1ED038AC2B50927B00C007D4 /* WatermelonDB+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
@ -512,6 +547,7 @@
1ED038922B507B4C00C007D4 /* ContentView.swift */,
1ED038942B507B4D00C007D4 /* Assets.xcassets */,
1ED038962B507B4D00C007D4 /* Preview Content */,
1ED038C92B50A58400C007D4 /* WatchConnection.swift */,
);
path = "RocketChat Watch App";
sourceTree = "<group>";
@ -524,6 +560,25 @@
path = "Preview Content";
sourceTree = "<group>";
};
1ED038B82B50A1A500C007D4 /* Watch */ = {
isa = PBXGroup;
children = (
1ED038BC2B50A1C700C007D4 /* Database */,
1ED038B92B50A1B800C007D4 /* WatchConnection.swift */,
1ED038C32B50A1F500C007D4 /* WatchMessage.swift */,
);
path = Watch;
sourceTree = "<group>";
};
1ED038BC2B50A1C700C007D4 /* Database */ = {
isa = PBXGroup;
children = (
1ED038BD2B50A1D400C007D4 /* DBServer.swift */,
1ED038C02B50A1E400C007D4 /* DBUser.swift */,
);
path = Database;
sourceTree = "<group>";
};
1EFEB5962493B6640072EDC0 /* NotificationService */ = {
isa = PBXGroup;
children = (
@ -579,6 +634,7 @@
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
1ED038B82B50A1A500C007D4 /* Watch */,
1E76CBC425152A7F0067298C /* Shared */,
1E068CFB24FD2DAF00A0FFC1 /* AppGroup */,
13B07FAE1A68108700A75B9A /* RocketChatRN */,
@ -1438,6 +1494,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1ED038A92B5090AD00C007D4 /* MMKV.swift in Sources */,
1E76CBCB25152C250067298C /* Sender.swift in Sources */,
1E76CBD825152C870067298C /* Request.swift in Sources */,
1ED00BB12513E04400A1331F /* ReplyNotification.swift in Sources */,
@ -1454,11 +1511,18 @@
1E76CBD225152C730067298C /* Data+Extensions.swift in Sources */,
1E76CBD125152C710067298C /* Date+Extensions.swift in Sources */,
1E76CBD425152C790067298C /* Database.swift in Sources */,
1ED038AD2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */,
1ED038A52B50900800C007D4 /* Bundle+Extensions.swift in Sources */,
1E76CBC325152A460067298C /* String+Extensions.swift in Sources */,
1ED038BA2B50A1B800C007D4 /* WatchConnection.swift in Sources */,
1ED038A12B508FE700C007D4 /* FileManager+Extensions.swift in Sources */,
1E76CBCA25152C220067298C /* Notification.swift in Sources */,
1ED038C12B50A1E400C007D4 /* DBUser.swift in Sources */,
1E76CBD525152C7F0067298C /* API.swift in Sources */,
1E76CBD625152C820067298C /* Response.swift in Sources */,
1ED038BE2B50A1D400C007D4 /* DBServer.swift in Sources */,
1E068D0124FD2E0500A0FFC1 /* AppGroup.m in Sources */,
1ED038C42B50A1F500C007D4 /* WatchMessage.swift in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
1E76CBD025152C6E0067298C /* URL+Extensions.swift in Sources */,
1E068CFE24FD2DC700A0FFC1 /* AppGroup.swift in Sources */,
@ -1486,6 +1550,8 @@
buildActionMask = 2147483647;
files = (
1ED038932B507B4C00C007D4 /* ContentView.swift in Sources */,
1ED038CA2B50A58400C007D4 /* WatchConnection.swift in Sources */,
1ED038C62B50A21800C007D4 /* WatchMessage.swift in Sources */,
1ED038912B507B4C00C007D4 /* RocketChatApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -1497,6 +1563,8 @@
1E51D965251263D600DC95DE /* NotificationType.swift in Sources */,
1EF5FBD1250C109E00614FEA /* Encryption.swift in Sources */,
1E598AE42515057D002BDFBD /* Date+Extensions.swift in Sources */,
1ED038A22B508FE700C007D4 /* FileManager+Extensions.swift in Sources */,
1ED038AA2B5090AD00C007D4 /* MMKV.swift in Sources */,
1E01C81C2511208400FEF824 /* URL+Extensions.swift in Sources */,
1E470E832513A71E00E3DD1D /* RocketChat.swift in Sources */,
1E2F615D25128FA300871711 /* Response.swift in Sources */,
@ -1505,6 +1573,8 @@
1E0426E7251A54B4008F022C /* RoomType.swift in Sources */,
1E1C2F80250FCB69005DCE7D /* Database.swift in Sources */,
1E67380424DC529B0009E081 /* String+Extensions.swift in Sources */,
1ED038AE2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */,
1ED038A62B50900800C007D4 /* Bundle+Extensions.swift in Sources */,
1E01C8292511304100FEF824 /* Sender.swift in Sources */,
1E51D962251263CD00DC95DE /* MessageType.swift in Sources */,
1E01C82B2511335A00FEF824 /* Message.swift in Sources */,
@ -1526,6 +1596,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1ED038AB2B5090AD00C007D4 /* MMKV.swift in Sources */,
7AAB3E15257E6A6E00707CF6 /* Sender.swift in Sources */,
7AAB3E16257E6A6E00707CF6 /* Request.swift in Sources */,
7AAB3E17257E6A6E00707CF6 /* ReplyNotification.swift in Sources */,
@ -1542,11 +1613,18 @@
7AAB3E23257E6A6E00707CF6 /* Data+Extensions.swift in Sources */,
7AAB3E24257E6A6E00707CF6 /* Date+Extensions.swift in Sources */,
7AAB3E25257E6A6E00707CF6 /* Database.swift in Sources */,
1ED038AF2B50927B00C007D4 /* WatermelonDB+Extensions.swift in Sources */,
1ED038A72B50900800C007D4 /* Bundle+Extensions.swift in Sources */,
7AAB3E26257E6A6E00707CF6 /* String+Extensions.swift in Sources */,
1ED038BB2B50A1B800C007D4 /* WatchConnection.swift in Sources */,
1ED038A32B508FE700C007D4 /* FileManager+Extensions.swift in Sources */,
7AAB3E27257E6A6E00707CF6 /* Notification.swift in Sources */,
1ED038C22B50A1E400C007D4 /* DBUser.swift in Sources */,
7AAB3E28257E6A6E00707CF6 /* API.swift in Sources */,
7AAB3E29257E6A6E00707CF6 /* Response.swift in Sources */,
1ED038BF2B50A1D400C007D4 /* DBServer.swift in Sources */,
7AAB3E2A257E6A6E00707CF6 /* AppGroup.m in Sources */,
1ED038C52B50A1F500C007D4 /* WatchMessage.swift in Sources */,
7AAB3E2B257E6A6E00707CF6 /* main.m in Sources */,
7AAB3E2C257E6A6E00707CF6 /* URL+Extensions.swift in Sources */,
7AAB3E2D257E6A6E00707CF6 /* AppGroup.swift in Sources */,

View File

@ -15,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1ED0388D2B507B4B00C007D4"
BuildableName = "RocketChat Watch App.app"
BuildableName = "Rocket.Chat.app"
BlueprintName = "RocketChat Watch App"
ReferencedContainer = "container:RocketChatRN.xcodeproj">
</BuildableReference>
@ -58,7 +58,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1ED0388D2B507B4B00C007D4"
BuildableName = "RocketChat Watch App.app"
BuildableName = "Rocket.Chat.app"
BlueprintName = "RocketChat Watch App"
ReferencedContainer = "container:RocketChatRN.xcodeproj">
</BuildableReference>
@ -75,7 +75,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "1ED0388D2B507B4B00C007D4"
BuildableName = "RocketChat Watch App.app"
BuildableName = "Rocket.Chat.app"
BlueprintName = "RocketChat Watch App"
ReferencedContainer = "container:RocketChatRN.xcodeproj">
</BuildableReference>

View File

@ -10,6 +10,7 @@
#import <UIKit/UIKit.h>
#import <React/RCTBridgeDelegate.h>
#import <Expo/Expo.h>
#import <WatchConnectivity/WatchConnectivity.h>
// https://github.com/expo/expo/issues/17705#issuecomment-1196251146
#import "ExpoModulesCore-Swift.h"
#import "RocketChatRN-Swift.h"
@ -17,5 +18,6 @@
@interface AppDelegate : EXAppDelegateWrapper <UIApplicationDelegate, RCTBridgeDelegate>
@property (nonatomic, strong) UIWindow *window;
@property (nonatomic, strong) WatchConnection *watchConnection;
@end

View File

@ -69,6 +69,8 @@
[MMKV initializeMMKV:nil groupDir:groupDir logLevel:MMKVLogInfo];
[RNBootSplash initWithStoryboard:@"LaunchScreen" rootView:rootView];
self.watchConnection = [[WatchConnection alloc] initWithSession:[WCSession defaultSession]];
return YES;
}

View File

@ -0,0 +1,15 @@
import Foundation
extension Bundle {
func bool(forKey key: String) -> Bool {
object(forInfoDictionaryKey: key) as? Bool ?? false
}
func string(forKey key: String) -> String {
guard let string = object(forInfoDictionaryKey: key) as? String else {
fatalError("Could not locate string for key \(key).")
}
return string
}
}

View File

@ -0,0 +1,13 @@
import Foundation
extension FileManager {
func groupDir() -> String {
let applicationGroupIdentifier = Bundle.main.string(forKey: "AppGroup")
guard let path = containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier)?.path else {
return ""
}
return path
}
}

View File

@ -0,0 +1,33 @@
import WatermelonDB
extension WatermelonDB.Database {
convenience init(name: String) {
let isOfficial = Bundle.main.bool(forKey: "IS_OFFICIAL")
let groupDir = FileManager.default.groupDir()
let path = "\(groupDir)/\(name)\(isOfficial ? "" : "-experimental").db"
self.init(path: path)
}
func query<T: Codable>(raw: SQL, _ args: QueryArgs = []) -> [T] {
guard let results = try? queryRaw(raw, args) else {
return []
}
return results.compactMap { result in
guard let dictionary = result.resultDictionary else {
return nil
}
guard let data = try? JSONSerialization.data(withJSONObject: dictionary) else {
return nil
}
guard let item = try? JSONDecoder().decode(T.self, from: data) else {
return nil
}
return item
}
}
}

View File

@ -10,38 +10,21 @@ 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
}
private let database: WatermelonDB.Database
init(server: String) {
if let url = URL(string: server) {
if let domain = url.domain, let directory = directory {
let isOfficial = Bundle.main.object(forInfoDictionaryKey: "IS_OFFICIAL") as? Bool ?? false
self.database = WatermelonDB.Database(path: "\(directory)/\(domain)\(isOfficial ? "" : "-experimental").db")
}
}
database = .init(name: server)
}
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
}
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
}
}
}
@ -50,16 +33,14 @@ final class Database {
}
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
}
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
}
}
}

View File

@ -0,0 +1,40 @@
import Foundation
extension MMKV {
static func build() -> MMKV {
let password = SecureStorage().getSecureKey("com.MMKV.default".toHex())
let groupDir = FileManager.default.groupDir()
MMKV.initialize(rootDir: nil, groupDir: groupDir, logLevel: MMKVLogLevel.none)
guard let mmkv = MMKV(mmapID: "default", cryptKey: password?.data(using: .utf8), mode: MMKVMode.multiProcess) else {
fatalError("Could not initialize MMKV instance.")
}
return mmkv
}
func userToken(for userId: String) -> String {
guard let userToken = string(forKey: "reactnativemeteor_usertoken-\(userId)") else {
fatalError("userToken is nil for userId \(userId)")
}
return userToken
}
func userId(for server: String) -> String {
guard let userId = string(forKey: "reactnativemeteor_usertoken-\(server)") else {
fatalError("userId is nil for server \(server)")
}
return userId
}
func privateKey(for server: String) -> String {
guard let privateKey = string(forKey: "\(server)-RC_E2E_PRIVATE_KEY") else {
fatalError("privateKey is nil for server \(server)")
}
return privateKey
}
}

View File

@ -1,11 +1,3 @@
//
// Storage.swift
// NotificationService
//
// Created by Djorkaeff Alexandre Vilela Pereira on 9/15/20.
// Copyright © 2020 Rocket.Chat. All rights reserved.
//
import Foundation
struct Credentials {
@ -13,48 +5,19 @@ struct Credentials {
let userToken: String
}
class Storage {
final class Storage {
static let shared = Storage()
final var mmkv: MMKV? = nil
private let mmkv = MMKV.build()
init() {
let mmapID = "default"
let instanceID = "com.MMKV.\(mmapID)"
let secureStorage = SecureStorage()
// get mmkv instance password from keychain
var key: Data?
if let password: String = secureStorage.getSecureKey(instanceID.toHex()) {
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 {
let userId = mmkv.userId(for: server)
let userToken = mmkv.userToken(for: userId)
return .init(userId: userId, userToken: userToken)
}
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")
func getPrivateKey(server: String) -> String {
mmkv.privateKey(for: server)
}
}

View File

@ -0,0 +1,19 @@
import Foundation
struct DBServer: Codable {
let url: URL
let name: String
let useRealName: Int
let iconURL: URL
var identifier: String {
url.absoluteString
}
enum CodingKeys: String, CodingKey {
case url = "id"
case name
case useRealName = "use_real_name"
case iconURL = "icon_url"
}
}

View File

@ -0,0 +1,6 @@
import Foundation
struct DBUser: Codable {
let name: String
let username: String
}

View File

@ -0,0 +1,82 @@
import Foundation
import WatermelonDB
import WatchConnectivity
@objc
final class WatchConnection: NSObject {
private let database = WatermelonDB.Database(name: "default")
private let mmkv = MMKV.build()
private let session: WCSession
@objc init(session: WCSession) {
self.session = session
super.init()
if WCSession.isSupported() {
session.delegate = self
session.activate()
}
}
private func getMessage() -> WatchMessage {
let serversQuery = database.query(raw: "select * from servers") as [DBServer]
let servers = serversQuery.compactMap { item -> WatchMessage.Server? in
let userId = mmkv.userId(for: item.identifier)
let userToken = mmkv.userToken(for: userId)
let usersQuery = database.query(raw: "select * from users where token == ? limit 1", [userToken]) as [DBUser]
guard let user = usersQuery.first else {
return nil
}
return WatchMessage.Server(
url: item.url,
name: item.name,
iconURL: item.iconURL,
useRealName: item.useRealName == 1 ? true : false,
loggedUser: .init(
id: userId,
token: userToken,
name: user.name,
username: user.username
)
)
}
return WatchMessage(servers: servers)
}
private func encodedMessage() -> [String: Any] {
do {
let data = try JSONEncoder().encode(getMessage())
guard let dictionary = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] else {
fatalError("Could not serialize message: \(getMessage())")
}
return dictionary
} catch {
fatalError("Could not encode message: \(getMessage())")
}
}
}
extension WatchConnection: WCSessionDelegate {
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
}
func sessionDidBecomeInactive(_ session: WCSession) {
session.activate()
}
func sessionDidDeactivate(_ session: WCSession) {
session.activate()
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: @escaping ([String : Any]) -> Void) {
replyHandler(encodedMessage())
}
}

View File

@ -0,0 +1,20 @@
import Foundation
struct WatchMessage: Codable {
let servers: [Server]
struct Server: Codable {
let url: URL
let name: String
let iconURL: URL
let useRealName: Bool
let loggedUser: LoggedUser
struct LoggedUser: Codable {
let id: String
let token: String
let name: String
let username: String
}
}
}