ios: receive message in NSE (#742)

This commit is contained in:
Evgeny Poberezkin
2022-06-19 19:49:39 +01:00
committed by GitHub
parent c5c65f813b
commit 291096d87f
8 changed files with 132 additions and 181 deletions

View File

@@ -15,6 +15,8 @@ public enum ChatCommand {
case showActiveUser
case createActiveUser(profile: Profile)
case startChat
case apiStopChat
case apiSetAppPhase(appPhase: AgentPhase)
case setFilesFolder(filesFolder: String)
case apiGetChats
case apiGetChat(type: ChatType, id: Int64)
@@ -25,6 +27,7 @@ public enum ChatCommand {
case apiVerifyToken(token: String, code: String, nonce: String)
case apiIntervalNofication(token: String, interval: Int)
case apiDeleteToken(token: String)
case apiGetNtfMessage(nonce: String, encNtfInfo: String)
case getUserSMPServers
case setUserSMPServers(smpServers: [String])
case addContact
@@ -56,6 +59,8 @@ public enum ChatCommand {
case .showActiveUser: return "/u"
case let .createActiveUser(profile): return "/u \(profile.displayName) \(profile.fullName)"
case .startChat: return "/_start"
case .apiStopChat: return "/_stop"
case let .apiSetAppPhase(appPhase): return "/_app phase \(appPhase)"
case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)"
case .apiGetChats: return "/_get chats pcc=on"
case let .apiGetChat(type, id): return "/_get chat \(ref(type, id)) count=100"
@@ -68,6 +73,7 @@ public enum ChatCommand {
case let .apiVerifyToken(token, code, nonce): return "/_ntf verify apns \(token) \(code) \(nonce)"
case let .apiIntervalNofication(token, interval): return "/_ntf interval apns \(token) \(interval)"
case let .apiDeleteToken(token): return "/_ntf delete apns \(token)"
case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)"
case .getUserSMPServers: return "/smp_servers"
case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))"
case .addContact: return "/connect"
@@ -101,6 +107,8 @@ public enum ChatCommand {
case .showActiveUser: return "showActiveUser"
case .createActiveUser: return "createActiveUser"
case .startChat: return "startChat"
case .apiStopChat: return "apiStopChat"
case .apiSetAppPhase: return "apiSetAppPhase"
case .setFilesFolder: return "setFilesFolder"
case .apiGetChats: return "apiGetChats"
case .apiGetChat: return "apiGetChat"
@@ -111,6 +119,7 @@ public enum ChatCommand {
case .apiVerifyToken: return "apiVerifyToken"
case .apiIntervalNofication: return "apiIntervalNofication"
case .apiDeleteToken: return "apiDeleteToken"
case .apiGetNtfMessage: return "apiGetNtfMessage"
case .getUserSMPServers: return "getUserSMPServers"
case .setUserSMPServers: return "setUserSMPServers"
case .addContact: return "addContact"
@@ -156,6 +165,8 @@ public enum ChatResponse: Decodable, Error {
case activeUser(user: User)
case chatStarted
case chatRunning
case chatStopped
case appPhase(appPhase: AgentPhase)
case apiChats(chats: [ChatData])
case apiChat(chat: ChatData)
case userSMPServers(smpServers: [String])
@@ -205,6 +216,7 @@ public enum ChatResponse: Decodable, Error {
case callExtraInfo(contact: Contact, extraInfo: WebRTCExtraInfo)
case callEnded(contact: Contact)
case ntfTokenStatus(status: NtfTknStatus)
case ntfMessages(connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
case newContactConnection(connection: PendingContactConnection)
case contactConnectionDeleted(connection: PendingContactConnection)
case cmdOk
@@ -218,6 +230,8 @@ public enum ChatResponse: Decodable, Error {
case .activeUser: return "activeUser"
case .chatStarted: return "chatStarted"
case .chatRunning: return "chatRunning"
case .chatStopped: return "chatStopped"
case .appPhase: return "appPhase"
case .apiChats: return "apiChats"
case .apiChat: return "apiChat"
case .userSMPServers: return "userSMPServers"
@@ -265,6 +279,7 @@ public enum ChatResponse: Decodable, Error {
case .callExtraInfo: return "callExtraInfo"
case .callEnded: return "callEnded"
case .ntfTokenStatus: return "ntfTokenStatus"
case .ntfMessages: return "ntfMessages"
case .newContactConnection: return "newContactConnection"
case .contactConnectionDeleted: return "contactConnectionDeleted"
case .cmdOk: return "cmdOk"
@@ -281,6 +296,8 @@ public enum ChatResponse: Decodable, Error {
case let .activeUser(user): return String(describing: user)
case .chatStarted: return noDetails
case .chatRunning: return noDetails
case .chatStopped: return noDetails
case let .appPhase(appPhase): return appPhase.rawValue
case let .apiChats(chats): return String(describing: chats)
case let .apiChat(chat): return String(describing: chat)
case let .userSMPServers(smpServers): return String(describing: smpServers)
@@ -328,6 +345,7 @@ public enum ChatResponse: Decodable, Error {
case let .callExtraInfo(contact, extraInfo): return "contact: \(contact.id)\nextraInfo: \(String(describing: extraInfo))"
case let .callEnded(contact): return "contact: \(contact.id)"
case let .ntfTokenStatus(status): return String(describing: status)
case let .ntfMessages(connEntity, msgTs, ntfMessages): return "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))"
case let .newContactConnection(connection): return String(describing: connection)
case let .contactConnectionDeleted(connection): return String(describing: connection)
case .cmdOk: return noDetails
@@ -346,6 +364,12 @@ struct ComposedMessage: Encodable {
var msgContent: MsgContent
}
public enum AgentPhase: String, Codable {
case active = "ACTIVE"
case paused = "PAUSED"
case suspended = "SUSPENDED"
}
public func decodeJSON<T: Decodable>(_ json: String) -> T? {
if let data = json.data(using: .utf8) {
return try? jsonDecoder.decode(T.self, from: data)

View File

@@ -9,169 +9,51 @@
import Foundation
import SwiftUI
let GROUP_DEFAULT_APP_IN_BACKGROUND = "appInBackground"
let GROUP_DEFAULT_APP_STATE = "appState"
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) {
public enum AppState: String {
case active
case pausing
case paused
case suspending
case suspended
public init(appPhase: AgentPhase) {
switch appPhase {
case .active: self = .active
case .paused: self = .paused
case .suspended: self = .suspended
}
}
public var running: Bool {
switch self {
case .paused: return false
case .suspending: return false
case .suspended: return false
default: return true
}
}
}
public func setAppState(_ state: AppState) {
if let defaults = getGroupDefaults() {
defaults.set(phase == .background, forKey: GROUP_DEFAULT_APP_IN_BACKGROUND)
defaults.set(state.rawValue, forKey: GROUP_DEFAULT_APP_STATE)
defaults.synchronize()
}
}
public func getAppState() -> ScenePhase {
if let defaults = getGroupDefaults() {
if defaults.bool(forKey: GROUP_DEFAULT_APP_IN_BACKGROUND) {
return .background
}
public func getAppState() -> AppState {
if let defaults = getGroupDefaults(),
let rawValue = defaults.string(forKey: GROUP_DEFAULT_APP_STATE),
let state = AppState(rawValue: rawValue) {
return state
}
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)
}
}

View File

@@ -264,6 +264,9 @@ public struct Connection: Decodable {
)
}
public struct UserContact: Decodable {
}
public struct UserContactRequest: Decodable, NamedChat {
var contactRequestId: Int64
var localDisplayName: ContactName
@@ -437,6 +440,18 @@ public struct MemberSubError: Decodable {
var memberError: ChatError
}
public enum ConnectionEntity: Decodable {
case rcvDirectMsgConnection(entityConnection: Connection, contact: Contact?)
case rcvGroupMsgConnection(entityConnection: Connection, groupInfo: GroupInfo, groupMember: GroupMember)
case sndFileConnection(entityConnection: Connection, sndFileTransfer: SndFileTransfer)
case rcvFileConnection(entityConnection: Connection, rcvFileTransfer: RcvFileTransfer)
case userContactConnection(entityConnection: Connection, userContact: UserContact)
}
public struct NtfMsgInfo: Decodable {
}
public struct AChatItem: Decodable {
public var chatInfo: ChatInfo
public var chatItem: ChatItem
@@ -901,6 +916,10 @@ public struct SndFileTransfer: Decodable {
}
public struct RcvFileTransfer: Decodable {
}
public struct FileTransferMeta: Decodable {
}