ios: receive message in NSE (#742)
This commit is contained in:
committed by
GitHub
parent
c5c65f813b
commit
291096d87f
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user