ios: mach messages to coordinate database acceess between app & NSE (#717)
This commit is contained in:
committed by
GitHub
parent
b435c0145f
commit
949fb17406
@@ -11,6 +11,13 @@ import SimpleXChat
|
||||
|
||||
let logger = Logger()
|
||||
|
||||
let machMessenger = MachMessenger(APP_MACH_PORT, callback: receivedNSEMachMessage)
|
||||
|
||||
func receivedNSEMachMessage(msgId: Int32, msg: String) -> String? {
|
||||
logger.debug("MachMessenger: receivedNSEMachMessage \"\(msg)\" from NSE, replying")
|
||||
return "reply from App to: \(msg)"
|
||||
}
|
||||
|
||||
@main
|
||||
struct SimpleXApp: App {
|
||||
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
@@ -27,6 +34,7 @@ struct SimpleXApp: App {
|
||||
UserDefaults.standard.register(defaults: appDefaults)
|
||||
BGManager.shared.register()
|
||||
NtfManager.shared.registerCategories()
|
||||
machMessenger.start()
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
@@ -42,14 +50,18 @@ struct SimpleXApp: App {
|
||||
}
|
||||
.onChange(of: scenePhase) { phase in
|
||||
logger.debug("scenePhase \(String(describing: scenePhase))")
|
||||
let res = machMessenger.sendMessageWithReply(NSE_MACH_PORT, msg: "App scenePhase changed to \(String(describing: scenePhase))")
|
||||
logger.debug("MachMessenger \(String(describing: res), privacy: .public)")
|
||||
setAppState(phase)
|
||||
switch (phase) {
|
||||
case .background:
|
||||
BGManager.shared.schedule()
|
||||
doAuthenticate = false
|
||||
enteredBackground = ProcessInfo.processInfo.systemUptime
|
||||
machMessenger.stop()
|
||||
case .active:
|
||||
doAuthenticate = true
|
||||
machMessenger.start()
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
@@ -12,15 +12,20 @@ import SimpleXChat
|
||||
|
||||
let logger = Logger()
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
let machMessenger = MachMessenger(NSE_MACH_PORT, callback: receivedAppMachMessage)
|
||||
|
||||
class NotificationService: UNNotificationServiceExtension {
|
||||
var contentHandler: ((UNNotificationContent) -> Void)?
|
||||
var bestAttemptContent: UNMutableNotificationContent?
|
||||
|
||||
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||
logger.debug("NotificationService.didReceive")
|
||||
machMessenger.start()
|
||||
let res = machMessenger.sendMessageWithReply(APP_MACH_PORT, msg: "starting NSE didReceive")
|
||||
logger.debug("MachMessenger \(String(describing: res), privacy: .public)")
|
||||
if getAppState() != .background {
|
||||
contentHandler(request.content)
|
||||
machMessenger.stop()
|
||||
return
|
||||
}
|
||||
logger.debug("NotificationService: app is in the background")
|
||||
@@ -29,6 +34,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
if let _ = startChat() {
|
||||
let content = receiveMessages()
|
||||
contentHandler (content)
|
||||
machMessenger.stop()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -38,6 +44,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
machMessenger.stop()
|
||||
}
|
||||
|
||||
override func serviceExtensionTimeWillExpire() {
|
||||
@@ -48,7 +55,11 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
contentHandler(bestAttemptContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receivedAppMachMessage(msgId: Int32, msg: String) -> String? {
|
||||
logger.debug("MachMessenger: receivedAppMachMessage \"\(msg)\" from App, replying")
|
||||
return "reply from NSE to: \(msg)"
|
||||
}
|
||||
|
||||
func startChat() -> User? {
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
5CE2BA88284532AD00EC33A6 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A6907F28376BB90076573F /* libgmp.a */; };
|
||||
5CE2BA89284532AD00EC33A6 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 64A6907E28376BB90076573F /* libgmpxx.a */; };
|
||||
5CE2BA8B284533A300EC33A6 /* ChatTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD7228188CFF00503DA2 /* ChatTypes.swift */; };
|
||||
5CE2BA8C284533A300EC33A6 /* GroupDefaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD5228186F9500503DA2 /* GroupDefaults.swift */; };
|
||||
5CE2BA8C284533A300EC33A6 /* AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD5228186F9500503DA2 /* AppGroup.swift */; };
|
||||
5CE2BA8D284533A300EC33A6 /* CallTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5E5D3C282447AB00B0488A /* CallTypes.swift */; };
|
||||
5CE2BA8E284533A300EC33A6 /* API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD7D2818941F00503DA2 /* API.swift */; };
|
||||
5CE2BA8F284533A300EC33A6 /* APITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CDCAD7428188D2900503DA2 /* APITypes.swift */; };
|
||||
@@ -250,7 +250,7 @@
|
||||
5CDCAD472818589900503DA2 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
5CDCAD492818589900503DA2 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX NSE.entitlements"; sourceTree = "<group>"; };
|
||||
5CDCAD5228186F9500503DA2 /* GroupDefaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupDefaults.swift; sourceTree = "<group>"; };
|
||||
5CDCAD5228186F9500503DA2 /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
|
||||
5CDCAD5E28187D4A00503DA2 /* libiconv.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libiconv.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/usr/lib/libiconv.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
5CDCAD6028187D7900503DA2 /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS15.4.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };
|
||||
5CDCAD7228188CFF00503DA2 /* ChatTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTypes.swift; sourceTree = "<group>"; };
|
||||
@@ -398,7 +398,6 @@
|
||||
5C764E87279CBC8E000C6508 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CDCAD7128188CEB00503DA2 /* Shared */,
|
||||
5C764E88279CBCB3000C6508 /* ChatModel.swift */,
|
||||
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */,
|
||||
5C35CFC727B2782E00FB6C6D /* BGManager.swift */,
|
||||
@@ -540,17 +539,10 @@
|
||||
path = "SimpleX NSE";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CDCAD7128188CEB00503DA2 /* Shared */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
);
|
||||
path = Shared;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
5CE2BA692845308900EC33A6 /* SimpleXChat */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
5CDCAD5228186F9500503DA2 /* GroupDefaults.swift */,
|
||||
5CDCAD5228186F9500503DA2 /* AppGroup.swift */,
|
||||
5CDCAD7228188CFF00503DA2 /* ChatTypes.swift */,
|
||||
5CDCAD7428188D2900503DA2 /* APITypes.swift */,
|
||||
5C5E5D3C282447AB00B0488A /* CallTypes.swift */,
|
||||
@@ -887,7 +879,7 @@
|
||||
5CE2BA90284533A300EC33A6 /* JSON.swift in Sources */,
|
||||
5CE2BA8B284533A300EC33A6 /* ChatTypes.swift in Sources */,
|
||||
5CE2BA8F284533A300EC33A6 /* APITypes.swift in Sources */,
|
||||
5CE2BA8C284533A300EC33A6 /* GroupDefaults.swift in Sources */,
|
||||
5CE2BA8C284533A300EC33A6 /* AppGroup.swift in Sources */,
|
||||
5CE2BA8D284533A300EC33A6 /* CallTypes.swift in Sources */,
|
||||
5CE2BA8E284533A300EC33A6 /* API.swift in Sources */,
|
||||
);
|
||||
|
||||
177
apps/ios/SimpleXChat/AppGroup.swift
Normal file
177
apps/ios/SimpleXChat/AppGroup.swift
Normal file
@@ -0,0 +1,177 @@
|
||||
//
|
||||
// GroupDefaults.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 26/04/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
let GROUP_DEFAULT_APP_IN_BACKGROUND = "appInBackground"
|
||||
|
||||
let APP_GROUP_NAME = "group.chat.simplex.app"
|
||||
|
||||
public let NSE_MACH_PORT = "\(APP_GROUP_NAME).nse" as CFString
|
||||
|
||||
public let APP_MACH_PORT = "\(APP_GROUP_NAME).app" as CFString
|
||||
|
||||
func getGroupDefaults() -> UserDefaults? {
|
||||
UserDefaults(suiteName: APP_GROUP_NAME)
|
||||
}
|
||||
|
||||
public func setAppState(_ phase: ScenePhase) {
|
||||
if let defaults = getGroupDefaults() {
|
||||
defaults.set(phase == .background, forKey: GROUP_DEFAULT_APP_IN_BACKGROUND)
|
||||
defaults.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
public func getAppState() -> ScenePhase {
|
||||
if let defaults = getGroupDefaults() {
|
||||
if defaults.bool(forKey: GROUP_DEFAULT_APP_IN_BACKGROUND) {
|
||||
return .background
|
||||
}
|
||||
}
|
||||
return .active
|
||||
}
|
||||
|
||||
let MACH_SEND_TIMEOUT: CFTimeInterval = 1.0
|
||||
let MACH_REPLY_TIMEOUT: CFTimeInterval = 1.0
|
||||
|
||||
public class MachMessenger {
|
||||
public init(_ localPortName: CFString, callback: @escaping Callback) {
|
||||
self.localPortName = localPortName
|
||||
self.callback = callback
|
||||
self.localPort = nil
|
||||
}
|
||||
|
||||
var localPortName: CFString
|
||||
var callback: Callback
|
||||
var localPort: CFMessagePort?
|
||||
|
||||
public enum SendError: Error {
|
||||
case sndTimeout
|
||||
case rcvTimeout
|
||||
case portInvalid
|
||||
case sendError(Int32)
|
||||
case msgError
|
||||
}
|
||||
|
||||
public typealias Callback = (_ msgId: Int32, _ msg: String) -> String?
|
||||
|
||||
class CallbackInfo {
|
||||
internal init(callback: @escaping MachMessenger.Callback) {
|
||||
self.callback = callback
|
||||
}
|
||||
|
||||
var callback: Callback
|
||||
}
|
||||
|
||||
public static func sendError(_ code: Int32) -> SendError? {
|
||||
switch code {
|
||||
case kCFMessagePortSuccess: return nil
|
||||
case kCFMessagePortSendTimeout: return .sndTimeout
|
||||
case kCFMessagePortReceiveTimeout: return .rcvTimeout
|
||||
case kCFMessagePortIsInvalid: return .portInvalid
|
||||
case kCFMessagePortBecameInvalidError: return .portInvalid
|
||||
default: return .sendError(code)
|
||||
}
|
||||
}
|
||||
|
||||
public func start() {
|
||||
logger.debug("MachMessenger.start")
|
||||
localPort = createLocalPort(localPortName, callback: callback)
|
||||
}
|
||||
|
||||
public func stop() {
|
||||
if let port = localPort {
|
||||
logger.debug("MachMessenger.stop")
|
||||
CFMessagePortInvalidate(port)
|
||||
localPort = nil
|
||||
}
|
||||
}
|
||||
|
||||
public func sendMessage(_ remotePortName: CFString, msgId: Int32 = 0, msg: String) -> SendError? {
|
||||
logger.debug("MachMessenger.sendMessage")
|
||||
if let port = createRemotePort(remotePortName) {
|
||||
logger.debug("MachMessenger.sendMessage: sending...")
|
||||
return sendMessage(port, msgId: msgId, msg: msg)
|
||||
} else {
|
||||
logger.debug("MachMessenger.sendMessage: no remote port")
|
||||
return .portInvalid
|
||||
}
|
||||
}
|
||||
|
||||
public func sendMessageWithReply(_ remotePortName: CFString, msgId: Int32 = 0, msg: String) -> Result<String?, SendError> {
|
||||
logger.debug("MachMessenger.sendMessageWithReply")
|
||||
if let port = createRemotePort(remotePortName) {
|
||||
logger.debug("MachMessenger.sendMessageWithReply: sending...")
|
||||
return sendMessageWithReply(port, msgId: msgId, msg: msg)
|
||||
} else {
|
||||
logger.debug("MachMessenger.sendMessageWithReply: no remote port")
|
||||
return .failure(.portInvalid)
|
||||
}
|
||||
}
|
||||
|
||||
private func createLocalPort(_ portName: CFString, callback: @escaping Callback) -> CFMessagePort? {
|
||||
logger.debug("MachMessenger.createLocalPort")
|
||||
if let port = localPort { return port }
|
||||
logger.debug("MachMessenger.createLocalPort: creating...")
|
||||
var context = CFMessagePortContext()
|
||||
context.version = 0
|
||||
context.info = Unmanaged.passRetained(CallbackInfo(callback: callback)).toOpaque()
|
||||
let callout: CFMessagePortCallBack = { port, msgId, msgData, info in
|
||||
logger.debug("MachMessenger CFMessagePortCallBack called")
|
||||
if let data = msgData,
|
||||
let msg = String(data: data as Data, encoding: .utf8),
|
||||
let info = info,
|
||||
let resp = Unmanaged<CallbackInfo>.fromOpaque(info).takeUnretainedValue().callback(msgId, msg),
|
||||
let respData = resp.data(using: .utf8) {
|
||||
return Unmanaged.passRetained(respData as CFData)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return withUnsafeMutablePointer(to: &context) { cxt in
|
||||
let port = CFMessagePortCreateLocal(kCFAllocatorDefault, portName, callout, cxt, nil)
|
||||
CFMessagePortSetDispatchQueue(port, DispatchQueue.main);
|
||||
localPort = port
|
||||
logger.debug("MachMessenger.createLocalPort created: \(portName)")
|
||||
return port
|
||||
}
|
||||
}
|
||||
|
||||
private func createRemotePort(_ portName: CFString) -> CFMessagePort? {
|
||||
CFMessagePortCreateRemote(kCFAllocatorDefault, portName)
|
||||
}
|
||||
|
||||
private func sendMessage(_ remotePort: CFMessagePort, msgId: Int32 = 0, msg: String) -> SendError? {
|
||||
if let data = msg.data(using: .utf8) {
|
||||
logger.debug("MachMessenger sendMessage")
|
||||
let msgData = data as CFData
|
||||
let code = CFMessagePortSendRequest(remotePort, msgId, msgData, MACH_SEND_TIMEOUT, 0, nil, nil)
|
||||
logger.debug("MachMessenger sendMessage \(code)")
|
||||
return MachMessenger.sendError(code)
|
||||
}
|
||||
return .msgError
|
||||
}
|
||||
|
||||
private func sendMessageWithReply(_ remotePort: CFMessagePort, msgId: Int32 = 0, msg: String) -> Result<String?, SendError> {
|
||||
if let data = msg.data(using: .utf8) {
|
||||
let msgData = data as CFData
|
||||
var respData: Unmanaged<CFData>? = nil
|
||||
let code = CFMessagePortSendRequest(remotePort, msgId, msgData, MACH_SEND_TIMEOUT, MACH_REPLY_TIMEOUT, CFRunLoopMode.defaultMode.rawValue, &respData)
|
||||
if let err = MachMessenger.sendError(code) {
|
||||
return .failure(err)
|
||||
} else if let data = respData?.takeUnretainedValue(),
|
||||
let resp = String(data: data as Data, encoding: .utf8) {
|
||||
return .success(resp)
|
||||
} else {
|
||||
return .success(nil)
|
||||
}
|
||||
|
||||
}
|
||||
return .failure(.msgError)
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ public let maxFileSize: Int64 = 8000000
|
||||
|
||||
func getDocumentsDirectory() -> URL {
|
||||
// FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
|
||||
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.chat.simplex.app")!
|
||||
FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: APP_GROUP_NAME)!
|
||||
}
|
||||
|
||||
public func getAppFilesDirectory() -> URL {
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
//
|
||||
// GroupDefaults.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 26/04/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
let GROUP_DEFAULT_APP_IN_BACKGROUND = "appInBackground"
|
||||
|
||||
func getGroupDefaults() -> UserDefaults? {
|
||||
UserDefaults(suiteName: "group.chat.simplex.app")
|
||||
}
|
||||
|
||||
public func setAppState(_ phase: ScenePhase) {
|
||||
if let defaults = getGroupDefaults() {
|
||||
defaults.set(phase == .background, forKey: GROUP_DEFAULT_APP_IN_BACKGROUND)
|
||||
defaults.synchronize()
|
||||
}
|
||||
}
|
||||
|
||||
public func getAppState() -> ScenePhase {
|
||||
if let defaults = getGroupDefaults() {
|
||||
if defaults.bool(forKey: GROUP_DEFAULT_APP_IN_BACKGROUND) {
|
||||
return .background
|
||||
}
|
||||
}
|
||||
return .active
|
||||
}
|
||||
Reference in New Issue
Block a user