simplify remote api, add ios api (#3213)

This commit is contained in:
Evgeny Poberezkin 2023-10-13 22:35:30 +01:00 committed by GitHub
parent 193361c09a
commit 5e6aaffb09
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 184 additions and 125 deletions

View File

@ -882,6 +882,38 @@ func apiCancelFile(fileId: Int64) async -> AChatItem? {
}
}
func startRemoteCtrl() async throws {
try await sendCommandOkResp(.startRemoteCtrl)
}
func registerRemoteCtrl(_ remoteCtrlOOB: RemoteCtrlOOB) async throws -> Int64 {
let r = await chatSendCmd(.registerRemoteCtrl(remoteCtrlOOB: remoteCtrlOOB))
if case let .remoteCtrlRegistered(rcId) = r { return rcId }
throw r
}
func listRemoteCtrls() async throws -> [RemoteCtrlInfo] {
let r = await chatSendCmd(.listRemoteCtrls)
if case let .remoteCtrlList(rcInfo) = r { return rcInfo }
throw r
}
func acceptRemoteCtrl(_ rcId: Int64) async throws {
try await sendCommandOkResp(.acceptRemoteCtrl(remoteCtrlId: rcId))
}
func rejectRemoteCtrl(_ rcId: Int64) async throws {
try await sendCommandOkResp(.rejectRemoteCtrl(remoteCtrlId: rcId))
}
func stopRemoteCtrl() async throws {
try await sendCommandOkResp(.stopRemoteCtrl)
}
func deleteRemoteCtrl(_ rcId: Int64) async throws {
try await sendCommandOkResp(.deleteRemoteCtrl(remoteCtrlId: rcId))
}
func networkErrorAlert(_ r: ChatResponse) -> Alert? {
switch r {
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))):

View File

@ -117,6 +117,13 @@ public enum ChatCommand {
case receiveFile(fileId: Int64, encrypted: Bool, inline: Bool?)
case setFileToReceive(fileId: Int64, encrypted: Bool)
case cancelFile(fileId: Int64)
case startRemoteCtrl
case registerRemoteCtrl(remoteCtrlOOB: RemoteCtrlOOB)
case listRemoteCtrls
case acceptRemoteCtrl(remoteCtrlId: Int64)
case rejectRemoteCtrl(remoteCtrlId: Int64)
case stopRemoteCtrl
case deleteRemoteCtrl(remoteCtrlId: Int64)
case showVersion
case string(String)
@ -255,6 +262,13 @@ public enum ChatCommand {
return s
case let .setFileToReceive(fileId, encrypted): return "/_set_file_to_receive \(fileId) encrypt=\(onOff(encrypted))"
case let .cancelFile(fileId): return "/fcancel \(fileId)"
case .startRemoteCtrl: return "/start remote ctrl"
case let .registerRemoteCtrl(oob): return "/register remote ctrl \(oob.caFingerprint)"
case let .acceptRemoteCtrl(rcId): return "/accept remote ctrl \(rcId)"
case let .rejectRemoteCtrl(rcId): return "/reject remote ctrl \(rcId)"
case .listRemoteCtrls: return "/list remote ctrls"
case .stopRemoteCtrl: return "/stop remote ctrl"
case let .deleteRemoteCtrl(rcId): return "/delete remote ctrl \(rcId)"
case .showVersion: return "/version"
case let .string(str): return str
}
@ -367,6 +381,13 @@ public enum ChatCommand {
case .receiveFile: return "receiveFile"
case .setFileToReceive: return "setFileToReceive"
case .cancelFile: return "cancelFile"
case .startRemoteCtrl: return "startRemoteCtrl"
case .registerRemoteCtrl: return "registerRemoteCtrl"
case .listRemoteCtrls: return "listRemoteCtrls"
case .acceptRemoteCtrl: return "acceptRemoteCtrl"
case .rejectRemoteCtrl: return "rejectRemoteCtrl"
case .stopRemoteCtrl: return "stopRemoteCtrl"
case .deleteRemoteCtrl: return "deleteRemoteCtrl"
case .showVersion: return "showVersion"
case .string: return "console command"
}
@ -563,6 +584,13 @@ public enum ChatResponse: Decodable, Error {
case ntfMessages(user_: User?, connEntity: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo])
case newContactConnection(user: UserRef, connection: PendingContactConnection)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo])
case remoteCtrlRegistered(remoteCtrlId: Int64)
case remoteCtrlAnnounce(fingerprint: String)
case remoteCtrlFound(remoteCtrl: RemoteCtrl)
case remoteCtrlConnecting(remoteCtrlId: Int64, displayName: String)
case remoteCtrlConnected(remoteCtrlId: Int64, displayName: String)
case remoteCtrlStopped
case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration])
case cmdOk(user: UserRef?)
case chatCmdError(user_: UserRef?, chatError: ChatError)
@ -699,6 +727,13 @@ public enum ChatResponse: Decodable, Error {
case .ntfMessages: return "ntfMessages"
case .newContactConnection: return "newContactConnection"
case .contactConnectionDeleted: return "contactConnectionDeleted"
case .remoteCtrlList: return "remoteCtrlList"
case .remoteCtrlRegistered: return "remoteCtrlRegistered"
case .remoteCtrlAnnounce: return "remoteCtrlAnnounce"
case .remoteCtrlFound: return "remoteCtrlFound"
case .remoteCtrlConnecting: return "remoteCtrlConnecting"
case .remoteCtrlConnected: return "remoteCtrlConnected"
case .remoteCtrlStopped: return "remoteCtrlStopped"
case .versionInfo: return "versionInfo"
case .cmdOk: return "cmdOk"
case .chatCmdError: return "chatCmdError"
@ -838,6 +873,13 @@ public enum ChatResponse: Decodable, Error {
case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))")
case let .newContactConnection(u, connection): return withUser(u, String(describing: connection))
case let .contactConnectionDeleted(u, connection): return withUser(u, String(describing: connection))
case let .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls)
case let .remoteCtrlRegistered(rcId): return "remote ctrl ID: \(rcId)"
case let .remoteCtrlAnnounce(fingerprint): return "fingerprint: \(fingerprint)"
case let .remoteCtrlFound(remoteCtrl): return "remote ctrl: \(String(describing: remoteCtrl))"
case let .remoteCtrlConnecting(rcId, displayName): return "remote ctrl ID: \(rcId)\nhost displayName: \(displayName)"
case let .remoteCtrlConnected(rcId, displayName): return "remote ctrl ID: \(rcId)\nhost displayName: \(displayName)"
case .remoteCtrlStopped: return noDetails
case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))"
case .cmdOk: return noDetails
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
@ -1461,6 +1503,23 @@ public enum NotificationPreviewMode: String, SelectableItem {
public static var values: [NotificationPreviewMode] = [.message, .contact, .hidden]
}
public struct RemoteCtrlOOB {
public var caFingerprint: String
}
public struct RemoteCtrlInfo: Decodable {
public var remoteCtrlId: Int64
public var displayName: String
public var sessionActive: Bool
}
public struct RemoteCtrl: Decodable {
var remoteCtrlId: Int64
var displayName: String
var fingerprint: String
var accepted: Bool?
}
public struct CoreVersionInfo: Decodable {
public var version: String
public var simplexmqVersion: String
@ -1488,6 +1547,7 @@ public enum ChatError: Decodable {
case errorAgent(agentError: AgentErrorType)
case errorStore(storeError: StoreError)
case errorDatabase(databaseError: DatabaseError)
case errorRemoteCtrl(remoteCtrlError: RemoteCtrlError)
case invalidJSON(json: String)
}
@ -1739,3 +1799,15 @@ public enum ArchiveError: Decodable {
case `import`(chatError: ChatError)
case importFile(file: String, chatError: ChatError)
}
public enum RemoteCtrlError: Decodable {
case missing(remoteCtrlId: Int64)
case inactive
case busy
case timeout
case disconnected(remoteCtrlId: Int64, reason: String)
case connectionLost(remoteCtrlId: Int64, reason: String)
case certificateExpired(remoteCtrlId: Int64)
case certificateUntrusted(remoteCtrlId: Int64)
case badFingerprint
}

View File

@ -1938,8 +1938,8 @@ sealed class CC {
class StartRemoteHost(val remoteHostId: Long): CC()
class StopRemoteHost(val remoteHostId: Long): CC()
class DeleteRemoteHost(val remoteHostId: Long): CC()
class RegisterRemoteCtrl(val remoteCtrlOOB: RemoteCtrlOOB): CC()
class StartRemoteCtrl(): CC()
class RegisterRemoteCtrl(val remoteCtrlOOB: RemoteCtrlOOB): CC()
class ListRemoteCtrls(): CC()
class AcceptRemoteCtrl(val remoteCtrlId: Long): CC()
class RejectRemoteCtrl(val remoteCtrlId: Long): CC()
@ -2167,8 +2167,8 @@ sealed class CC {
is StartRemoteHost -> "startRemoteHost"
is StopRemoteHost -> "stopRemoteHost"
is DeleteRemoteHost -> "deleteRemoteHost"
is RegisterRemoteCtrl -> "registerRemoteCtrl"
is StartRemoteCtrl -> "startRemoteCtrl"
is RegisterRemoteCtrl -> "registerRemoteCtrl"
is ListRemoteCtrls -> "listRemoteCtrls"
is AcceptRemoteCtrl -> "acceptRemoteCtrl"
is RejectRemoteCtrl -> "rejectRemoteCtrl"
@ -3483,30 +3483,24 @@ sealed class CR {
@Serializable @SerialName("callEnded") class CallEnded(val user: UserRef, val contact: Contact): CR()
@Serializable @SerialName("newContactConnection") class NewContactConnection(val user: UserRef, val connection: PendingContactConnection): CR()
@Serializable @SerialName("contactConnectionDeleted") class ContactConnectionDeleted(val user: UserRef, val connection: PendingContactConnection): CR()
// remote events (desktop)
@Serializable @SerialName("remoteHostCreated") class RemoteHostCreated(val remoteHostId: Long, val oobData: RemoteCtrlOOB): CR()
@Serializable @SerialName("remoteHostList") class RemoteHostList(val remoteHosts: List<RemoteHostInfo>): CR()
@Serializable @SerialName("remoteHostConnected") class RemoteHostConnected(val remoteHostId: Long): CR()
@Serializable @SerialName("remoteHostStopped") class RemoteHostStopped(val remoteHostId: Long): CR()
// remote events (mobile)
@Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List<RemoteCtrlInfo>): CR()
@Serializable @SerialName("remoteCtrlRegistered") class RemoteCtrlRegistered(val remoteCtrlId: Long): CR()
@Serializable @SerialName("remoteCtrlAnnounce") class RemoteCtrlAnnounce(val fingerprint: String): CR()
@Serializable @SerialName("remoteCtrlFound") class RemoteCtrlFound(val remoteCtrl: RemoteCtrl): CR()
@Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrlId: Long, val displayName: String): CR()
@Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrlId: Long, val displayName: String): CR()
@Serializable @SerialName("remoteCtrlStopped") class RemoteCtrlStopped(): CR()
@Serializable @SerialName("versionInfo") class VersionInfo(val versionInfo: CoreVersionInfo, val chatMigrations: List<UpMigration>, val agentMigrations: List<UpMigration>): CR()
@Serializable @SerialName("cmdOk") class CmdOk(val user: UserRef?): CR()
@Serializable @SerialName("chatCmdError") class ChatCmdError(val user_: UserRef?, val chatError: ChatError): CR()
@Serializable @SerialName("chatError") class ChatRespError(val user_: UserRef?, val chatError: ChatError): CR()
@Serializable @SerialName("archiveImported") class ArchiveImported(val archiveErrors: List<ArchiveError>): CR()
// remote events (desktop)
@Serializable @SerialName("remoteHostCreated") class RemoteHostCreated(val remoteHostId: Long, val oobData: RemoteCtrlOOB): CR()
@Serializable @SerialName("remoteHostList") class RemoteHostList(val remoteHosts: List<RemoteHostInfo>): CR()
@Serializable @SerialName("remoteHostStarted") class RemoteHostStarted(val remoteHostId: Long): CR()
@Serializable @SerialName("remoteHostConnected") class RemoteHostConnected(val remoteHostId: Long): CR()
@Serializable @SerialName("remoteHostStopped") class RemoteHostStopped(val remoteHostId: Long): CR()
@Serializable @SerialName("remoteHostDeleted") class RemoteHostDeleted(val remoteHostId: Long): CR()
// remote events (mobile)
@Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List<RemoteCtrlInfo>): CR()
@Serializable @SerialName("remoteCtrlRegistered") class RemoteCtrlRegistered(val remoteCtrlId: Long): CR()
@Serializable @SerialName("remoteCtrlStarted") class RemoteCtrlStarted(): CR()
@Serializable @SerialName("remoteCtrlAnnounce") class RemoteCtrlAnnounce(val fingerprint: String): CR()
@Serializable @SerialName("remoteCtrlFound") class RemoteCtrlFound(val remoteCtrl: RemoteCtrl): CR()
@Serializable @SerialName("remoteCtrlAccepted") class RemoteCtrlAccepted(val remoteCtrlId: Long): CR()
@Serializable @SerialName("remoteCtrlRejected") class RemoteCtrlRejected(val remoteCtrlId: Long): CR()
@Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrlId: Long, val displayName: String): CR()
@Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrlId: Long, val displayName: String): CR()
@Serializable @SerialName("remoteCtrlStopped") class RemoteCtrlStopped(): CR()
@Serializable @SerialName("remoteCtrlDeleted") class RemoteCtrlDeleted(val remoteCtrlId: Long): CR()
// general
@Serializable class Response(val type: String, val json: String): CR()
@Serializable class Invalid(val str: String): CR()
@ -3632,28 +3626,22 @@ sealed class CR {
is CallEnded -> "callEnded"
is NewContactConnection -> "newContactConnection"
is ContactConnectionDeleted -> "contactConnectionDeleted"
is RemoteHostCreated -> "remoteHostCreated"
is RemoteHostList -> "remoteHostList"
is RemoteHostConnected -> "remoteHostConnected"
is RemoteHostStopped -> "remoteHostStopped"
is RemoteCtrlList -> "remoteCtrlList"
is RemoteCtrlRegistered -> "remoteCtrlRegistered"
is RemoteCtrlAnnounce -> "remoteCtrlAnnounce"
is RemoteCtrlFound -> "remoteCtrlFound"
is RemoteCtrlConnecting -> "remoteCtrlConnecting"
is RemoteCtrlConnected -> "remoteCtrlConnected"
is RemoteCtrlStopped -> "remoteCtrlStopped"
is VersionInfo -> "versionInfo"
is CmdOk -> "cmdOk"
is ChatCmdError -> "chatCmdError"
is ChatRespError -> "chatError"
is ArchiveImported -> "archiveImported"
is RemoteHostCreated -> "remoteHostCreated"
is RemoteHostList -> "remoteHostList"
is RemoteHostStarted -> "remoteHostStarted"
is RemoteHostConnected -> "remoteHostConnected"
is RemoteHostStopped -> "remoteHostStopped"
is RemoteHostDeleted -> "remoteHostDeleted"
is RemoteCtrlList -> "remoteCtrlList"
is RemoteCtrlRegistered -> "remoteCtrlRegistered"
is RemoteCtrlStarted -> "remoteCtrlStarted"
is RemoteCtrlAnnounce -> "remoteCtrlAnnounce"
is RemoteCtrlFound -> "remoteCtrlFound"
is RemoteCtrlAccepted -> "remoteCtrlAccepted"
is RemoteCtrlRejected -> "remoteCtrlRejected"
is RemoteCtrlConnecting -> "remoteCtrlConnecting"
is RemoteCtrlConnected -> "remoteCtrlConnected"
is RemoteCtrlStopped -> "remoteCtrlStopped"
is RemoteCtrlDeleted -> "remoteCtrlDeleted"
is Response -> "* $type"
is Invalid -> "* invalid json"
}
@ -3779,6 +3767,17 @@ sealed class CR {
is CallEnded -> withUser(user, "contact: ${contact.id}")
is NewContactConnection -> withUser(user, json.encodeToString(connection))
is ContactConnectionDeleted -> withUser(user, json.encodeToString(connection))
is RemoteHostCreated -> "remote host ID: $remoteHostId\noobData ${json.encodeToString(oobData)}"
is RemoteHostList -> "remote hosts: ${json.encodeToString(remoteHosts)}"
is RemoteHostConnected -> "remote host ID: $remoteHostId"
is RemoteHostStopped -> "remote host ID: $remoteHostId"
is RemoteCtrlList -> json.encodeToString(remoteCtrls)
is RemoteCtrlRegistered -> "remote ctrl ID: $remoteCtrlId"
is RemoteCtrlAnnounce -> "fingerprint: $fingerprint"
is RemoteCtrlFound -> "remote ctrl: ${json.encodeToString(remoteCtrl)}"
is RemoteCtrlConnecting -> "remote ctrl ID: $remoteCtrlId\nhost displayName: $displayName"
is RemoteCtrlConnected -> "remote ctrl ID: $remoteCtrlId\nhost displayName: $displayName"
is RemoteCtrlStopped -> ""
is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" +
"chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" +
"agent migrations: ${json.encodeToString(agentMigrations.map { it.upName })}"
@ -3786,23 +3785,6 @@ sealed class CR {
is ChatCmdError -> withUser(user_, chatError.string)
is ChatRespError -> withUser(user_, chatError.string)
is ArchiveImported -> "${archiveErrors.map { it.string } }"
is RemoteHostCreated -> "remote host ID: $remoteHostId\noobData ${json.encodeToString(oobData)}"
is RemoteHostList -> "remote hosts: ${json.encodeToString(remoteHosts)}"
is RemoteHostStarted -> "remote host $remoteHostId"
is RemoteHostConnected -> "remote host ID: $remoteHostId"
is RemoteHostStopped -> "remote host ID: $remoteHostId"
is RemoteHostDeleted -> "remote host ID: $remoteHostId"
is RemoteCtrlList -> json.encodeToString(remoteCtrls)
is RemoteCtrlRegistered -> "remote ctrl ID: $remoteCtrlId"
is RemoteCtrlStarted -> ""
is RemoteCtrlAnnounce -> "fingerprint: $fingerprint"
is RemoteCtrlFound -> "remote ctrl: ${json.encodeToString(remoteCtrl)}"
is RemoteCtrlAccepted -> "remote ctrl ID: $remoteCtrlId"
is RemoteCtrlRejected -> "remote ctrl ID: $remoteCtrlId"
is RemoteCtrlConnecting -> "remote ctrl ID: $remoteCtrlId\nhost displayName: $displayName"
is RemoteCtrlConnected -> "remote ctrl ID: $remoteCtrlId\nhost displayName: $displayName"
is RemoteCtrlStopped -> ""
is RemoteCtrlDeleted -> "remote ctrl ID: $remoteCtrlId"
is Response -> json
is Invalid -> str
}
@ -3948,16 +3930,16 @@ sealed class ChatError {
is ChatErrorAgent -> "agent ${agentError.string}"
is ChatErrorStore -> "store ${storeError.string}"
is ChatErrorDatabase -> "database ${databaseError.string}"
is ChatErrorRemoteCtrl -> "remoteCtrl ${remoteCtrlError.string}"
is ChatErrorRemoteHost -> "remoteHost ${remoteHostError.string}"
is ChatErrorRemoteCtrl -> "remoteCtrl ${remoteCtrlError.string}"
is ChatErrorInvalidJSON -> "invalid json ${json}"
}
@Serializable @SerialName("error") class ChatErrorChat(val errorType: ChatErrorType): ChatError()
@Serializable @SerialName("errorAgent") class ChatErrorAgent(val agentError: AgentErrorType): ChatError()
@Serializable @SerialName("errorStore") class ChatErrorStore(val storeError: StoreError): ChatError()
@Serializable @SerialName("errorDatabase") class ChatErrorDatabase(val databaseError: DatabaseError): ChatError()
@Serializable @SerialName("errorRemoteCtrl") class ChatErrorRemoteCtrl(val remoteCtrlError: RemoteCtrlError): ChatError()
@Serializable @SerialName("errorRemoteHost") class ChatErrorRemoteHost(val remoteHostError: RemoteHostError): ChatError()
@Serializable @SerialName("errorRemoteCtrl") class ChatErrorRemoteCtrl(val remoteCtrlError: RemoteCtrlError): ChatError()
@Serializable @SerialName("invalidJSON") class ChatErrorInvalidJSON(val json: String): ChatError()
}

View File

@ -1891,18 +1891,18 @@ processChatCommand = \case
let pref = uncurry TimedMessagesGroupPreference $ maybe (FEOff, Just 86400) (\ttl -> (FEOn, Just ttl)) ttl_
updateGroupProfileByName gName $ \p ->
p {groupPreferences = Just . setGroupPreference' SGFTimedMessages pref $ groupPreferences p}
CreateRemoteHost -> createRemoteHost
ListRemoteHosts -> listRemoteHosts
StartRemoteHost rh -> startRemoteHost rh
StopRemoteHost rh -> closeRemoteHostSession rh
DeleteRemoteHost rh -> deleteRemoteHost rh
StartRemoteCtrl -> startRemoteCtrl (execChatCommand Nothing)
AcceptRemoteCtrl rc -> acceptRemoteCtrl rc
RejectRemoteCtrl rc -> rejectRemoteCtrl rc
StopRemoteCtrl -> stopRemoteCtrl
RegisterRemoteCtrl oob -> registerRemoteCtrl oob
ListRemoteCtrls -> listRemoteCtrls
DeleteRemoteCtrl rc -> deleteRemoteCtrl rc
CreateRemoteHost -> uncurry CRRemoteHostCreated <$> createRemoteHost
ListRemoteHosts -> CRRemoteHostList <$> listRemoteHosts
StartRemoteHost rh -> startRemoteHost rh >> ok_
StopRemoteHost rh -> closeRemoteHostSession rh >> ok_
DeleteRemoteHost rh -> deleteRemoteHost rh >> ok_
StartRemoteCtrl -> startRemoteCtrl (execChatCommand Nothing) >> ok_
AcceptRemoteCtrl rc -> acceptRemoteCtrl rc >> ok_
RejectRemoteCtrl rc -> rejectRemoteCtrl rc >> ok_
StopRemoteCtrl -> stopRemoteCtrl >> ok_
RegisterRemoteCtrl oob -> CRRemoteCtrlRegistered <$> registerRemoteCtrl oob
ListRemoteCtrls -> CRRemoteCtrlList <$> listRemoteCtrls
DeleteRemoteCtrl rc -> deleteRemoteCtrl rc >> ok_
QuitChat -> liftIO exitSuccess
ShowVersion -> do
let versionInfo = coreVersionInfo $(simplexmqCommitQ)

View File

@ -425,8 +425,8 @@ data ChatCommand
-- | SwitchRemoteHost (Maybe RemoteHostId) -- ^ Switch current remote host
| StopRemoteHost RemoteHostId -- ^ Shut down a running session
| DeleteRemoteHost RemoteHostId -- ^ Unregister remote host and remove its data
| RegisterRemoteCtrl RemoteCtrlOOB -- ^ Register OOB data for satellite discovery and handshake
| StartRemoteCtrl -- ^ Start listening for announcements from all registered controllers
| RegisterRemoteCtrl RemoteCtrlOOB -- ^ Register OOB data for satellite discovery and handshake
| ListRemoteCtrls
| AcceptRemoteCtrl RemoteCtrlId -- ^ Accept discovered data and store confirmation
| RejectRemoteCtrl RemoteCtrlId -- ^ Reject and blacklist discovered data
@ -631,21 +631,15 @@ data ChatResponse
| CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection}
| CRRemoteHostCreated {remoteHostId :: RemoteHostId, oobData :: RemoteCtrlOOB}
| CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} -- XXX: RemoteHostInfo is mostly concerned with session setup
| CRRemoteHostStarted {remoteHostId :: RemoteHostId}
| CRRemoteHostConnected {remoteHostId :: RemoteHostId}
| CRRemoteHostStopped {remoteHostId :: RemoteHostId}
| CRRemoteHostDeleted {remoteHostId :: RemoteHostId}
| CRRemoteCtrlList {remoteCtrls :: [RemoteCtrlInfo]}
| CRRemoteCtrlRegistered {remoteCtrlId :: RemoteCtrlId}
| CRRemoteCtrlStarted
| CRRemoteCtrlAnnounce {fingerprint :: C.KeyHash} -- unregistered fingerprint, needs confirmation
| CRRemoteCtrlFound {remoteCtrl :: RemoteCtrl} -- registered fingerprint, may connect
| CRRemoteCtrlAccepted {remoteCtrlId :: RemoteCtrlId}
| CRRemoteCtrlRejected {remoteCtrlId :: RemoteCtrlId}
| CRRemoteCtrlConnecting {remoteCtrlId :: RemoteCtrlId, displayName :: Text}
| CRRemoteCtrlConnected {remoteCtrlId :: RemoteCtrlId, displayName :: Text}
| CRRemoteCtrlStopped
| CRRemoteCtrlDeleted {remoteCtrlId :: RemoteCtrlId}
| CRSQLResult {rows :: [Text]}
| CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]}
| CRDebugLocks {chatLockName :: Maybe String, agentLocks :: AgentLocks}
@ -667,21 +661,15 @@ allowRemoteEvent :: ChatResponse -> Bool
allowRemoteEvent = \case
CRRemoteHostCreated {} -> False
CRRemoteHostList {} -> False
CRRemoteHostStarted {} -> False
CRRemoteHostConnected {} -> False
CRRemoteHostStopped {} -> False
CRRemoteHostDeleted {} -> False
CRRemoteCtrlList {} -> False
CRRemoteCtrlRegistered {} -> False
CRRemoteCtrlStarted {} -> False
CRRemoteCtrlAnnounce {} -> False
CRRemoteCtrlFound {} -> False
CRRemoteCtrlAccepted {} -> False
CRRemoteCtrlRejected {} -> False
CRRemoteCtrlConnecting {} -> False
CRRemoteCtrlConnected {} -> False
CRRemoteCtrlStopped {} -> False
CRRemoteCtrlDeleted {} -> False
_ -> True
logResponseToFile :: ChatResponse -> Bool

View File

@ -79,21 +79,20 @@ withRemoteHost remoteHostId action =
Nothing -> throwError $ ChatErrorRemoteHost remoteHostId RHMissing
Just rh -> action rh
startRemoteHost :: (ChatMonad m) => RemoteHostId -> m ChatResponse
startRemoteHost :: (ChatMonad m) => RemoteHostId -> m ()
startRemoteHost remoteHostId = do
asks remoteHostSessions >>= atomically . TM.lookup remoteHostId >>= \case
Just _ -> throwError $ ChatErrorRemoteHost remoteHostId RHBusy
Nothing -> withRemoteHost remoteHostId $ \rh -> do
announcer <- async $ run rh
chatModifyVar remoteHostSessions $ M.insert remoteHostId RemoteHostSessionStarting {announcer}
pure CRRemoteHostStarted {remoteHostId}
where
cleanup finished = do
logInfo "Remote host http2 client fininshed"
atomically $ writeTVar finished True
M.lookup remoteHostId <$> chatReadVar remoteHostSessions >>= \case
Nothing -> logInfo $ "Session already closed for remote host " <> tshow remoteHostId
Just _ -> closeRemoteHostSession remoteHostId >>= toView
Just _ -> closeRemoteHostSession remoteHostId >> toView (CRRemoteHostStopped remoteHostId)
run RemoteHost {storePath, caKey, caCert} = do
finished <- newTVarIO False
let parent = (C.signatureKeyPair caKey, caCert)
@ -142,42 +141,41 @@ pollRemote finished http path action = loop
readTVarIO finished >>= (`unless` loop)
req = HTTP2Client.requestNoBody "GET" path mempty
closeRemoteHostSession :: (ChatMonad m) => RemoteHostId -> m ChatResponse
closeRemoteHostSession :: (ChatMonad m) => RemoteHostId -> m ()
closeRemoteHostSession remoteHostId = withRemoteHostSession remoteHostId $ \session -> do
logInfo $ "Closing remote host session for " <> tshow remoteHostId
liftIO $ cancelRemoteHostSession session
chatWriteVar currentRemoteHost Nothing
chatModifyVar remoteHostSessions $ M.delete remoteHostId
pure CRRemoteHostStopped {remoteHostId}
cancelRemoteHostSession :: (MonadUnliftIO m) => RemoteHostSession -> m ()
cancelRemoteHostSession = \case
RemoteHostSessionStarting {announcer} -> cancel announcer
RemoteHostSessionStarted {ctrlClient} -> liftIO $ HTTP2.closeHTTP2Client ctrlClient
createRemoteHost :: (ChatMonad m) => m ChatResponse
createRemoteHost :: (ChatMonad m) => m (RemoteHostId, RemoteCtrlOOB)
createRemoteHost = do
let displayName = "TODO" -- you don't have remote host name here, it will be passed from remote host
((_, caKey), caCert) <- liftIO $ genCredentials Nothing (-25, 24 * 365) displayName
storePath <- liftIO randomStorePath
remoteHostId <- withStore' $ \db -> insertRemoteHost db storePath displayName caKey caCert
let oobData = RemoteCtrlOOB {caFingerprint = C.certificateFingerprint caCert}
pure CRRemoteHostCreated {remoteHostId, oobData}
pure (remoteHostId, oobData)
-- | Generate a random 16-char filepath without / in it by using base64url encoding.
randomStorePath :: IO FilePath
randomStorePath = B.unpack . B64U.encode <$> getRandomBytes 12
listRemoteHosts :: (ChatMonad m) => m ChatResponse
listRemoteHosts :: (ChatMonad m) => m [RemoteHostInfo]
listRemoteHosts = do
stored <- withStore' getRemoteHosts
active <- chatReadVar remoteHostSessions
pure $ CRRemoteHostList $ do
pure $ do
RemoteHost {remoteHostId, storePath, displayName} <- stored
let sessionActive = M.member remoteHostId active
pure RemoteHostInfo {remoteHostId, storePath, displayName, sessionActive}
deleteRemoteHost :: (ChatMonad m) => RemoteHostId -> m ChatResponse
deleteRemoteHost :: (ChatMonad m) => RemoteHostId -> m ()
deleteRemoteHost remoteHostId = withRemoteHost remoteHostId $ \RemoteHost {storePath} -> do
chatReadVar filesFolder >>= \case
Just baseDir -> do
@ -185,7 +183,6 @@ deleteRemoteHost remoteHostId = withRemoteHost remoteHostId $ \RemoteHost {store
logError $ "TODO: remove " <> tshow hostStore
Nothing -> logWarn "Local file store not available while deleting remote host"
withStore' $ \db -> deleteRemoteHostRecord db remoteHostId
pure CRRemoteHostDeleted {remoteHostId}
processRemoteCommand :: (ChatMonad m) => RemoteHostSession -> (ByteString, ChatCommand) -> m ChatResponse
processRemoteCommand RemoteHostSessionStarting {} _ = pure . CRChatError Nothing . ChatError $ CEInternalError "sending remote commands before session started"
@ -393,7 +390,7 @@ processControllerRequest execChatCommand HTTP2.HTTP2Request {request, reqBody, s
-- * ChatRequest handlers
startRemoteCtrl :: (ChatMonad m) => (ByteString -> m ChatResponse) -> m ChatResponse
startRemoteCtrl :: (ChatMonad m) => (ByteString -> m ChatResponse) -> m ()
startRemoteCtrl execChatCommand =
chatReadVar remoteCtrlSession >>= \case
Just _busy -> throwError $ ChatErrorRemoteCtrl RCEBusy
@ -416,7 +413,6 @@ startRemoteCtrl execChatCommand =
chatWriteVar remoteCtrlSession Nothing
toView CRRemoteCtrlStopped
chatWriteVar remoteCtrlSession $ Just RemoteCtrlSession {discoverer, supervisor, hostServer = Nothing, discovered, accepted, remoteOutputQ}
pure CRRemoteCtrlStarted
discoverRemoteCtrls :: (ChatMonad m) => TM.TMap C.KeyHash TransportHost -> m ()
discoverRemoteCtrls discovered = Discovery.withListener go
@ -445,33 +441,32 @@ discoverRemoteCtrls discovered = Discovery.withListener go
Just RemoteCtrlSession {accepted} -> atomically $ void $ tryPutTMVar accepted remoteCtrlId -- previously accepted controller, connect automatically
_nonV4 -> go sock
registerRemoteCtrl :: (ChatMonad m) => RemoteCtrlOOB -> m ChatResponse
registerRemoteCtrl :: (ChatMonad m) => RemoteCtrlOOB -> m RemoteCtrlId
registerRemoteCtrl RemoteCtrlOOB {caFingerprint} = do
let displayName = "TODO" -- maybe include into OOB data
remoteCtrlId <- withStore' $ \db -> insertRemoteCtrl db displayName caFingerprint
pure $ CRRemoteCtrlRegistered {remoteCtrlId}
pure remoteCtrlId
listRemoteCtrls :: (ChatMonad m) => m ChatResponse
listRemoteCtrls :: (ChatMonad m) => m [RemoteCtrlInfo]
listRemoteCtrls = do
stored <- withStore' getRemoteCtrls
active <-
chatReadVar remoteCtrlSession >>= \case
Nothing -> pure Nothing
Just RemoteCtrlSession {accepted} -> atomically (tryReadTMVar accepted)
pure $ CRRemoteCtrlList $ do
pure $ do
RemoteCtrl {remoteCtrlId, displayName} <- stored
let sessionActive = active == Just remoteCtrlId
pure RemoteCtrlInfo {remoteCtrlId, displayName, sessionActive}
acceptRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ChatResponse
acceptRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ()
acceptRemoteCtrl remoteCtrlId = do
withStore' $ \db -> markRemoteCtrlResolution db remoteCtrlId True
chatReadVar remoteCtrlSession >>= \case
Nothing -> throwError $ ChatErrorRemoteCtrl RCEInactive
Just RemoteCtrlSession {accepted} -> atomically . void $ tryPutTMVar accepted remoteCtrlId -- the remote host can now proceed with connection
pure $ CRRemoteCtrlAccepted {remoteCtrlId}
rejectRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ChatResponse
rejectRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ()
rejectRemoteCtrl remoteCtrlId = do
withStore' $ \db -> markRemoteCtrlResolution db remoteCtrlId False
chatReadVar remoteCtrlSession >>= \case
@ -479,9 +474,8 @@ rejectRemoteCtrl remoteCtrlId = do
Just RemoteCtrlSession {discoverer, supervisor} -> do
cancel discoverer
cancel supervisor
pure $ CRRemoteCtrlRejected {remoteCtrlId}
stopRemoteCtrl :: (ChatMonad m) => m ChatResponse
stopRemoteCtrl :: (ChatMonad m) => m ()
stopRemoteCtrl =
chatReadVar remoteCtrlSession >>= \case
Nothing -> throwError $ ChatErrorRemoteCtrl RCEInactive
@ -489,7 +483,6 @@ stopRemoteCtrl =
cancelRemoteCtrlSession rcs $ do
chatWriteVar remoteCtrlSession Nothing
toView CRRemoteCtrlStopped
pure $ CRCmdOk Nothing
cancelRemoteCtrlSession_ :: (MonadUnliftIO m) => RemoteCtrlSession -> m ()
cancelRemoteCtrlSession_ rcs = cancelRemoteCtrlSession rcs $ pure ()
@ -503,12 +496,10 @@ cancelRemoteCtrlSession RemoteCtrlSession {discoverer, supervisor, hostServer} c
cancel supervisor -- supervisor is blocked until session progresses
cleanup
deleteRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ChatResponse
deleteRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ()
deleteRemoteCtrl remoteCtrlId =
chatReadVar remoteCtrlSession >>= \case
Nothing -> do
withStore' $ \db -> deleteRemoteCtrlRecord db remoteCtrlId
pure $ CRRemoteCtrlDeleted {remoteCtrlId}
Nothing -> withStore' $ \db -> deleteRemoteCtrlRecord db remoteCtrlId
Just _ -> throwError $ ChatErrorRemoteCtrl RCEBusy
withRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> (RemoteCtrl -> m a) -> m a

View File

@ -264,21 +264,15 @@ responseToView (currentRH, user_) ChatConfig {logLevel, showReactions, showRecei
CRNtfMessages {} -> []
CRRemoteHostCreated rhId oobData -> ("remote host " <> sShow rhId <> " created") : viewRemoteCtrlOOBData oobData
CRRemoteHostList hs -> viewRemoteHosts hs
CRRemoteHostStarted rhId -> ["remote host " <> sShow rhId <> " started"]
CRRemoteHostConnected rhId -> ["remote host " <> sShow rhId <> " connected"]
CRRemoteHostStopped rhId -> ["remote host " <> sShow rhId <> " stopped"]
CRRemoteHostDeleted rhId -> ["remote host " <> sShow rhId <> " deleted"]
CRRemoteCtrlList cs -> viewRemoteCtrls cs
CRRemoteCtrlRegistered rcId -> ["remote controller " <> sShow rcId <> " registered"]
CRRemoteCtrlStarted -> ["remote controller started"]
CRRemoteCtrlAnnounce fingerprint -> ["remote controller announced", "connection code:", plain $ strEncode fingerprint]
CRRemoteCtrlFound rc -> ["remote controller found:", viewRemoteCtrl rc]
CRRemoteCtrlAccepted rcId -> ["remote controller " <> sShow rcId <> " accepted"]
CRRemoteCtrlRejected rcId -> ["remote controller " <> sShow rcId <> " rejected"]
CRRemoteCtrlConnecting rcId rcName -> ["remote controller " <> sShow rcId <> " connecting to " <> plain rcName]
CRRemoteCtrlConnected rcId rcName -> ["remote controller " <> sShow rcId <> " connected, " <> plain rcName]
CRRemoteCtrlStopped -> ["remote controller stopped"]
CRRemoteCtrlDeleted rcId -> ["remote controller " <> sShow rcId <> " deleted"]
CRSQLResult rows -> map plain rows
CRSlowSQLQueries {chatQueries, agentQueries} ->
let viewQuery SlowSQLQuery {query, queryStats = SlowQueryStats {count, timeMax, timeAvg}} =

View File

@ -110,10 +110,10 @@ remoteHandshakeTest = testChat2 aliceProfile bobProfile $ \desktop mobile -> do
desktop <## "Remote hosts:"
desktop <## "1. TODO" -- TODO host name probably should be Maybe, as when host is created there is no name yet
desktop ##> "/start remote host 1"
desktop <## "remote host 1 started"
desktop <## "ok"
mobile ##> "/start remote ctrl"
mobile <## "remote controller started"
mobile <## "ok"
mobile <## "remote controller announced"
mobile <## "connection code:"
fingerprint' <- getTermLine mobile
@ -126,7 +126,7 @@ remoteHandshakeTest = testChat2 aliceProfile bobProfile $ \desktop mobile -> do
mobile <## "Remote controllers:"
mobile <## "1. TODO"
mobile ##> "/accept remote ctrl 1"
mobile <## "remote controller 1 accepted" -- alternative scenario: accepted before controller start
mobile <## "ok" -- alternative scenario: accepted before controller start
mobile <## "remote controller 1 connecting to TODO"
mobile <## "remote controller 1 connected, TODO"
@ -140,9 +140,9 @@ remoteHandshakeTest = testChat2 aliceProfile bobProfile $ \desktop mobile -> do
traceM " - Shutting desktop"
desktop ##> "/stop remote host 1"
desktop <## "remote host 1 stopped"
desktop <## "ok"
desktop ##> "/delete remote host 1"
desktop <## "remote host 1 deleted"
desktop <## "ok"
desktop ##> "/list remote hosts"
desktop <## "No remote hosts"
@ -151,7 +151,7 @@ remoteHandshakeTest = testChat2 aliceProfile bobProfile $ \desktop mobile -> do
mobile <## "ok"
mobile <## "remote controller stopped"
mobile ##> "/delete remote ctrl 1"
mobile <## "remote controller 1 deleted"
mobile <## "ok"
mobile ##> "/list remote ctrls"
mobile <## "No remote controllers"
@ -173,10 +173,10 @@ remoteCommandTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob
fingerprint <- getTermLine desktop
desktop ##> "/start remote host 1"
desktop <## "remote host 1 started"
desktop <## "ok"
mobile ##> "/start remote ctrl"
mobile <## "remote controller started"
mobile <## "ok"
mobile <## "remote controller announced"
mobile <## "connection code:"
fingerprint' <- getTermLine mobile
@ -184,7 +184,7 @@ remoteCommandTest = testChat3 aliceProfile aliceDesktopProfile bobProfile $ \mob
mobile ##> ("/register remote ctrl " <> fingerprint')
mobile <## "remote controller 1 registered"
mobile ##> "/accept remote ctrl 1"
mobile <## "remote controller 1 accepted" -- alternative scenario: accepted before controller start
mobile <## "ok" -- alternative scenario: accepted before controller start
mobile <## "remote controller 1 connecting to TODO"
mobile <## "remote controller 1 connected, TODO"
desktop <## "remote host 1 connected"

View File

@ -33,7 +33,7 @@ main = do
describe "SimpleX chat client" chatTests
xdescribe'' "SimpleX Broadcast bot" broadcastBotTests
xdescribe'' "SimpleX Directory service bot" directoryServiceTests
describe "Remote session" remoteTests
fdescribe "Remote session" remoteTests
where
testBracket test = do
t <- getSystemTime