core: update api (#3221)

This commit is contained in:
Evgeny Poberezkin 2023-10-15 00:18:04 +01:00 committed by GitHub
parent f5e9bd4f8b
commit 41b86e07f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 199 additions and 126 deletions

View File

@ -886,9 +886,9 @@ func startRemoteCtrl() async throws {
try await sendCommandOkResp(.startRemoteCtrl) try await sendCommandOkResp(.startRemoteCtrl)
} }
func registerRemoteCtrl(_ remoteCtrlOOB: RemoteCtrlOOB) async throws -> Int64 { func registerRemoteCtrl(_ remoteCtrlOOB: RemoteCtrlOOB) async throws -> RemoteCtrlInfo {
let r = await chatSendCmd(.registerRemoteCtrl(remoteCtrlOOB: remoteCtrlOOB)) let r = await chatSendCmd(.registerRemoteCtrl(remoteCtrlOOB: remoteCtrlOOB))
if case let .remoteCtrlRegistered(rcId) = r { return rcId } if case let .remoteCtrlRegistered(rcInfo) = r { return rcInfo }
throw r throw r
} }

View File

@ -117,6 +117,7 @@ public enum ChatCommand {
case receiveFile(fileId: Int64, encrypted: Bool, inline: Bool?) case receiveFile(fileId: Int64, encrypted: Bool, inline: Bool?)
case setFileToReceive(fileId: Int64, encrypted: Bool) case setFileToReceive(fileId: Int64, encrypted: Bool)
case cancelFile(fileId: Int64) case cancelFile(fileId: Int64)
case setLocalDeviceName(displayName: String)
case startRemoteCtrl case startRemoteCtrl
case registerRemoteCtrl(remoteCtrlOOB: RemoteCtrlOOB) case registerRemoteCtrl(remoteCtrlOOB: RemoteCtrlOOB)
case listRemoteCtrls case listRemoteCtrls
@ -262,6 +263,7 @@ public enum ChatCommand {
return s return s
case let .setFileToReceive(fileId, encrypted): return "/_set_file_to_receive \(fileId) encrypt=\(onOff(encrypted))" case let .setFileToReceive(fileId, encrypted): return "/_set_file_to_receive \(fileId) encrypt=\(onOff(encrypted))"
case let .cancelFile(fileId): return "/fcancel \(fileId)" case let .cancelFile(fileId): return "/fcancel \(fileId)"
case let .setLocalDeviceName(displayName): return "/set device name \(displayName)"
case .startRemoteCtrl: return "/start remote ctrl" case .startRemoteCtrl: return "/start remote ctrl"
case let .registerRemoteCtrl(oob): return "/register remote ctrl \(oob.caFingerprint)" case let .registerRemoteCtrl(oob): return "/register remote ctrl \(oob.caFingerprint)"
case let .acceptRemoteCtrl(rcId): return "/accept remote ctrl \(rcId)" case let .acceptRemoteCtrl(rcId): return "/accept remote ctrl \(rcId)"
@ -381,6 +383,7 @@ public enum ChatCommand {
case .receiveFile: return "receiveFile" case .receiveFile: return "receiveFile"
case .setFileToReceive: return "setFileToReceive" case .setFileToReceive: return "setFileToReceive"
case .cancelFile: return "cancelFile" case .cancelFile: return "cancelFile"
case .setLocalDeviceName: return "setLocalDeviceName"
case .startRemoteCtrl: return "startRemoteCtrl" case .startRemoteCtrl: return "startRemoteCtrl"
case .registerRemoteCtrl: return "registerRemoteCtrl" case .registerRemoteCtrl: return "registerRemoteCtrl"
case .listRemoteCtrls: return "listRemoteCtrls" case .listRemoteCtrls: return "listRemoteCtrls"
@ -585,11 +588,11 @@ public enum ChatResponse: Decodable, Error {
case newContactConnection(user: UserRef, connection: PendingContactConnection) case newContactConnection(user: UserRef, connection: PendingContactConnection)
case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection)
case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo]) case remoteCtrlList(remoteCtrls: [RemoteCtrlInfo])
case remoteCtrlRegistered(remoteCtrlId: Int64) case remoteCtrlRegistered(remoteCtrl: RemoteCtrlInfo)
case remoteCtrlAnnounce(fingerprint: String) case remoteCtrlAnnounce(fingerprint: String)
case remoteCtrlFound(remoteCtrl: RemoteCtrl) case remoteCtrlFound(remoteCtrl: RemoteCtrlInfo)
case remoteCtrlConnecting(remoteCtrlId: Int64, displayName: String) case remoteCtrlConnecting(remoteCtrl: RemoteCtrlInfo)
case remoteCtrlConnected(remoteCtrlId: Int64, displayName: String) case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo)
case remoteCtrlStopped case remoteCtrlStopped
case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration])
case cmdOk(user: UserRef?) case cmdOk(user: UserRef?)
@ -874,11 +877,11 @@ public enum ChatResponse: Decodable, Error {
case let .newContactConnection(u, connection): return withUser(u, String(describing: connection)) case let .newContactConnection(u, connection): return withUser(u, String(describing: connection))
case let .contactConnectionDeleted(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 .remoteCtrlList(remoteCtrls): return String(describing: remoteCtrls)
case let .remoteCtrlRegistered(rcId): return "remote ctrl ID: \(rcId)" case let .remoteCtrlRegistered(remoteCtrl): return String(describing: remoteCtrl)
case let .remoteCtrlAnnounce(fingerprint): return "fingerprint: \(fingerprint)" case let .remoteCtrlAnnounce(fingerprint): return "fingerprint: \(fingerprint)"
case let .remoteCtrlFound(remoteCtrl): return "remote ctrl: \(String(describing: remoteCtrl))" case let .remoteCtrlFound(remoteCtrl): return String(describing: remoteCtrl)
case let .remoteCtrlConnecting(rcId, displayName): return "remote ctrl ID: \(rcId)\nhost displayName: \(displayName)" case let .remoteCtrlConnecting(remoteCtrl): return String(describing: remoteCtrl)
case let .remoteCtrlConnected(rcId, displayName): return "remote ctrl ID: \(rcId)\nhost displayName: \(displayName)" case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl)
case .remoteCtrlStopped: return noDetails 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 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 .cmdOk: return noDetails

View File

@ -166,6 +166,7 @@ class AppPreferences {
val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null)
val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0) val lastMigratedVersionCode = mkIntPreference(SHARED_PREFS_LAST_MIGRATED_VERSION_CODE, 0)
val customDisappearingMessageTime = mkIntPreference(SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME, 300) val customDisappearingMessageTime = mkIntPreference(SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME, 300)
val deviceNameForRemoteAccess = mkStrPreference(SHARED_PREFS_DEVICE_NAME_FOR_REMOTE_ACCESS, "Desktop")
private fun mkIntPreference(prefName: String, default: Int) = private fun mkIntPreference(prefName: String, default: Int) =
SharedPreference( SharedPreference(
@ -306,6 +307,7 @@ class AppPreferences {
private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion"
private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode" private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode"
private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime" private const val SHARED_PREFS_CUSTOM_DISAPPEARING_MESSAGE_TIME = "CustomDisappearingMessageTime"
private const val SHARED_PREFS_DEVICE_NAME_FOR_REMOTE_ACCESS = "DeviceNameForRemoteAccess"
} }
} }
@ -342,6 +344,11 @@ object ChatController {
val users = listUsers() val users = listUsers()
chatModel.users.clear() chatModel.users.clear()
chatModel.users.addAll(users) chatModel.users.addAll(users)
val remoteHosts = listRemoteHosts()
if (remoteHosts != null) {
chatModel.remoteHosts.clear()
chatModel.remoteHosts.addAll(remoteHosts)
}
if (justStarted) { if (justStarted) {
chatModel.currentUser.value = user chatModel.currentUser.value = user
chatModel.userCreated.value = true chatModel.userCreated.value = true
@ -432,15 +439,16 @@ object ChatController {
} }
} }
private fun recvMsg(ctrl: ChatCtrl): CR? { private fun recvMsg(ctrl: ChatCtrl): APIResponse? {
val json = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT) val json = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT)
return if (json == "") { return if (json == "") {
null null
} else { } else {
val r = APIResponse.decodeStr(json).resp val apiResp = APIResponse.decodeStr(json)
val r = apiResp.resp
Log.d(TAG, "chatRecvMsg: ${r.responseType}") Log.d(TAG, "chatRecvMsg: ${r.responseType}")
if (r is CR.Response || r is CR.Invalid) Log.d(TAG, "chatRecvMsg json: $json") if (r is CR.Response || r is CR.Invalid) Log.d(TAG, "chatRecvMsg json: $json")
r apiResp
} }
} }
@ -1327,6 +1335,59 @@ object ChatController {
} }
} }
suspend fun setLocalDeviceName(displayName: String): Boolean = sendCommandOkResp(CC.SetLocalDeviceName(displayName))
suspend fun createRemoteHost(): RemoteHostInfo? {
val r = sendCmd(CC.CreateRemoteHost())
if (r is CR.RemoteHostCreated) return r.remoteHost
apiErrorAlert("createRemoteHost", generalGetString(MR.strings.error), r)
return null
}
suspend fun listRemoteHosts(): List<RemoteHostInfo>? {
val r = sendCmd(CC.ListRemoteHosts())
if (r is CR.RemoteHostList) return r.remoteHosts
apiErrorAlert("listRemoteHosts", generalGetString(MR.strings.error), r)
return null
}
suspend fun startRemoteHost(rhId: Long): Boolean = sendCommandOkResp(CC.StartRemoteHost(rhId))
suspend fun registerRemoteCtrl(oob: RemoteCtrlOOB): RemoteCtrlInfo? {
val r = sendCmd(CC.RegisterRemoteCtrl(oob))
if (r is CR.RemoteCtrlRegistered) return r.remoteCtrl
apiErrorAlert("registerRemoteCtrl", generalGetString(MR.strings.error), r)
return null
}
suspend fun listRemoteCtrls(): List<RemoteCtrlInfo>? {
val r = sendCmd(CC.ListRemoteCtrls())
if (r is CR.RemoteCtrlList) return r.remoteCtrls
apiErrorAlert("listRemoteCtrls", generalGetString(MR.strings.error), r)
return null
}
suspend fun stopRemoteHost(rhId: Long): Boolean = sendCommandOkResp(CC.StopRemoteHost(rhId))
suspend fun deleteRemoteHost(rhId: Long): Boolean = sendCommandOkResp(CC.DeleteRemoteHost(rhId))
suspend fun startRemoteCtrl(): Boolean = sendCommandOkResp(CC.StartRemoteCtrl())
suspend fun acceptRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(CC.AcceptRemoteCtrl(rcId))
suspend fun rejectRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(CC.RejectRemoteCtrl(rcId))
suspend fun stopRemoteCtrl(): Boolean = sendCommandOkResp(CC.StopRemoteCtrl())
suspend fun deleteRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(CC.DeleteRemoteCtrl(rcId))
private suspend fun sendCommandOkResp(cmd: CC): Boolean {
val r = sendCmd(cmd)
val ok = r is CR.CmdOk
if (!ok) apiErrorAlert(cmd.cmdType, generalGetString(MR.strings.error), r)
return ok
}
suspend fun apiGetVersion(): CoreVersionInfo? { suspend fun apiGetVersion(): CoreVersionInfo? {
val r = sendCmd(CC.ShowVersion()) val r = sendCmd(CC.ShowVersion())
return if (r is CR.VersionInfo) { return if (r is CR.VersionInfo) {
@ -1361,14 +1422,15 @@ object ChatController {
} }
} }
fun apiErrorAlert(method: String, title: String, r: CR) { private fun apiErrorAlert(method: String, title: String, r: CR) {
val errMsg = "${r.responseType}: ${r.details}" val errMsg = "${r.responseType}: ${r.details}"
Log.e(TAG, "$method bad response: $errMsg") Log.e(TAG, "$method bad response: $errMsg")
AlertManager.shared.showAlertMsg(title, errMsg) AlertManager.shared.showAlertMsg(title, errMsg)
} }
suspend fun processReceivedMsg(r: CR) { private suspend fun processReceivedMsg(apiResp: APIResponse) {
lastMsgReceivedTimestamp = System.currentTimeMillis() lastMsgReceivedTimestamp = System.currentTimeMillis()
val r = apiResp.resp
chatModel.addTerminalItem(TerminalItem.resp(r)) chatModel.addTerminalItem(TerminalItem.resp(r))
when (r) { when (r) {
is CR.NewContactConnection -> { is CR.NewContactConnection -> {
@ -1674,6 +1736,13 @@ object ChatController {
chatModel.updateContactConnectionStats(r.contact, r.ratchetSyncProgress.connectionStats) chatModel.updateContactConnectionStats(r.contact, r.ratchetSyncProgress.connectionStats)
is CR.GroupMemberRatchetSync -> is CR.GroupMemberRatchetSync ->
chatModel.updateGroupMemberConnectionStats(r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats) chatModel.updateGroupMemberConnectionStats(r.groupInfo, r.member, r.ratchetSyncProgress.connectionStats)
is CR.RemoteHostConnected -> {
// update
chatModel.connectingRemoteHost.value = r.remoteHost
}
is CR.RemoteHostStopped -> {
//
}
else -> else ->
Log.d(TAG , "unsupported event: ${r.responseType}") Log.d(TAG , "unsupported event: ${r.responseType}")
} }
@ -1933,6 +2002,7 @@ sealed class CC {
class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC() class ApiChatUnread(val type: ChatType, val id: Long, val unreadChat: Boolean): CC()
class ReceiveFile(val fileId: Long, val encrypted: Boolean, val inline: Boolean?): CC() class ReceiveFile(val fileId: Long, val encrypted: Boolean, val inline: Boolean?): CC()
class CancelFile(val fileId: Long): CC() class CancelFile(val fileId: Long): CC()
class SetLocalDeviceName(val displayName: String): CC()
class CreateRemoteHost(): CC() class CreateRemoteHost(): CC()
class ListRemoteHosts(): CC() class ListRemoteHosts(): CC()
class StartRemoteHost(val remoteHostId: Long): CC() class StartRemoteHost(val remoteHostId: Long): CC()
@ -2053,13 +2123,14 @@ sealed class CC {
is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}" is ApiChatUnread -> "/_unread chat ${chatRef(type, id)} ${onOff(unreadChat)}"
is ReceiveFile -> "/freceive $fileId encrypt=${onOff(encrypted)}" + (if (inline == null) "" else " inline=${onOff(inline)}") is ReceiveFile -> "/freceive $fileId encrypt=${onOff(encrypted)}" + (if (inline == null) "" else " inline=${onOff(inline)}")
is CancelFile -> "/fcancel $fileId" is CancelFile -> "/fcancel $fileId"
is SetLocalDeviceName -> "/set device name $displayName"
is CreateRemoteHost -> "/create remote host" is CreateRemoteHost -> "/create remote host"
is ListRemoteHosts -> "/list remote hosts" is ListRemoteHosts -> "/list remote hosts"
is StartRemoteHost -> "/start remote host $remoteHostId" is StartRemoteHost -> "/start remote host $remoteHostId"
is StopRemoteHost -> "/stop remote host $remoteHostId" is StopRemoteHost -> "/stop remote host $remoteHostId"
is DeleteRemoteHost -> "/delete remote host $remoteHostId" is DeleteRemoteHost -> "/delete remote host $remoteHostId"
is StartRemoteCtrl -> "/start remote ctrl" is StartRemoteCtrl -> "/start remote ctrl"
is RegisterRemoteCtrl -> "/register remote ctrl ${remoteCtrlOOB.caFingerprint}" is RegisterRemoteCtrl -> "/register remote ctrl ${remoteCtrlOOB.fingerprint}"
is AcceptRemoteCtrl -> "/accept remote ctrl $remoteCtrlId" is AcceptRemoteCtrl -> "/accept remote ctrl $remoteCtrlId"
is RejectRemoteCtrl -> "/reject remote ctrl $remoteCtrlId" is RejectRemoteCtrl -> "/reject remote ctrl $remoteCtrlId"
is ListRemoteCtrls -> "/list remote ctrls" is ListRemoteCtrls -> "/list remote ctrls"
@ -2162,6 +2233,7 @@ sealed class CC {
is ApiChatUnread -> "apiChatUnread" is ApiChatUnread -> "apiChatUnread"
is ReceiveFile -> "receiveFile" is ReceiveFile -> "receiveFile"
is CancelFile -> "cancelFile" is CancelFile -> "cancelFile"
is SetLocalDeviceName -> "setLocalDeviceName"
is CreateRemoteHost -> "createRemoteHost" is CreateRemoteHost -> "createRemoteHost"
is ListRemoteHosts -> "listRemoteHosts" is ListRemoteHosts -> "listRemoteHosts"
is StartRemoteHost -> "startRemoteHost" is StartRemoteHost -> "startRemoteHost"
@ -3246,7 +3318,8 @@ data class RemoteCtrl (
@Serializable @Serializable
data class RemoteCtrlOOB ( data class RemoteCtrlOOB (
val caFingerprint: String val fingerprint: String,
val displayName: String
) )
@Serializable @Serializable
@ -3261,6 +3334,7 @@ data class RemoteHostInfo (
val remoteHostId: Long, val remoteHostId: Long,
val storePath: String, val storePath: String,
val displayName: String, val displayName: String,
val remoteCtrlOOB: RemoteCtrlOOB,
val sessionActive: Boolean val sessionActive: Boolean
) )
@ -3277,7 +3351,7 @@ val yaml = Yaml(configuration = YamlConfiguration(
)) ))
@Serializable @Serializable
class APIResponse(val resp: CR, val corr: String? = null) { class APIResponse(val resp: CR, val remoteHostId: Long?, val corr: String? = null) {
companion object { companion object {
fun decodeStr(str: String): APIResponse { fun decodeStr(str: String): APIResponse {
return try { return try {
@ -3287,48 +3361,35 @@ class APIResponse(val resp: CR, val corr: String? = null) {
Log.d(TAG, e.localizedMessage ?: "") Log.d(TAG, e.localizedMessage ?: "")
val data = json.parseToJsonElement(str).jsonObject val data = json.parseToJsonElement(str).jsonObject
val resp = data["resp"]!!.jsonObject val resp = data["resp"]!!.jsonObject
val type = resp["type"]?.jsonPrimitive?.content ?: "invalid" val type = resp["type"]?.jsonPrimitive?.contentOrNull ?: "invalid"
val corr = data["corr"]?.toString()
val remoteHostId = data["remoteHostId"]?.jsonPrimitive?.longOrNull
try { try {
if (type == "apiChats") { if (type == "apiChats") {
val user: UserRef = json.decodeFromJsonElement(resp["user"]!!.jsonObject) val user: UserRef = json.decodeFromJsonElement(resp["user"]!!.jsonObject)
val chats: List<Chat> = resp["chats"]!!.jsonArray.map { val chats: List<Chat> = resp["chats"]!!.jsonArray.map {
parseChatData(it) parseChatData(it)
} }
return APIResponse( return APIResponse(CR.ApiChats(user, chats), remoteHostId, corr)
resp = CR.ApiChats(user, chats),
corr = data["corr"]?.toString()
)
} else if (type == "apiChat") { } else if (type == "apiChat") {
val user: UserRef = json.decodeFromJsonElement(resp["user"]!!.jsonObject) val user: UserRef = json.decodeFromJsonElement(resp["user"]!!.jsonObject)
val chat = parseChatData(resp["chat"]!!) val chat = parseChatData(resp["chat"]!!)
return APIResponse( return APIResponse(CR.ApiChat(user, chat), remoteHostId, corr)
resp = CR.ApiChat(user, chat),
corr = data["corr"]?.toString()
)
} else if (type == "chatCmdError") { } else if (type == "chatCmdError") {
val userObject = resp["user_"]?.jsonObject val userObject = resp["user_"]?.jsonObject
val user = runCatching<UserRef?> { json.decodeFromJsonElement(userObject!!) }.getOrNull() val user = runCatching<UserRef?> { json.decodeFromJsonElement(userObject!!) }.getOrNull()
return APIResponse( return APIResponse(CR.ChatCmdError(user, ChatError.ChatErrorInvalidJSON(json.encodeToString(resp["chatError"]))), remoteHostId, corr)
resp = CR.ChatCmdError(user, ChatError.ChatErrorInvalidJSON(json.encodeToString(resp["chatError"]))),
corr = data["corr"]?.toString()
)
} else if (type == "chatError") { } else if (type == "chatError") {
val userObject = resp["user_"]?.jsonObject val userObject = resp["user_"]?.jsonObject
val user = runCatching<UserRef?> { json.decodeFromJsonElement(userObject!!) }.getOrNull() val user = runCatching<UserRef?> { json.decodeFromJsonElement(userObject!!) }.getOrNull()
return APIResponse( return APIResponse(CR.ChatRespError(user, ChatError.ChatErrorInvalidJSON(json.encodeToString(resp["chatError"]))), remoteHostId, corr)
resp = CR.ChatRespError(user, ChatError.ChatErrorInvalidJSON(json.encodeToString(resp["chatError"]))),
corr = data["corr"]?.toString()
)
} }
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error while parsing chat(s): " + e.stackTraceToString()) Log.e(TAG, "Error while parsing chat(s): " + e.stackTraceToString())
} }
APIResponse( APIResponse(CR.Response(type, json.encodeToString(data)), remoteHostId, corr)
resp = CR.Response(type, json.encodeToString(data)),
corr = data["corr"]?.toString()
)
} catch(e: Exception) { } catch(e: Exception) {
APIResponse(CR.Invalid(str)) APIResponse(CR.Invalid(str), remoteHostId = null)
} }
} }
} }
@ -3484,17 +3545,17 @@ sealed class CR {
@Serializable @SerialName("newContactConnection") class NewContactConnection(val user: UserRef, val connection: PendingContactConnection): 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() @Serializable @SerialName("contactConnectionDeleted") class ContactConnectionDeleted(val user: UserRef, val connection: PendingContactConnection): CR()
// remote events (desktop) // remote events (desktop)
@Serializable @SerialName("remoteHostCreated") class RemoteHostCreated(val remoteHostId: Long, val oobData: RemoteCtrlOOB): CR() @Serializable @SerialName("remoteHostCreated") class RemoteHostCreated(val remoteHost: RemoteHostInfo): CR()
@Serializable @SerialName("remoteHostList") class RemoteHostList(val remoteHosts: List<RemoteHostInfo>): CR() @Serializable @SerialName("remoteHostList") class RemoteHostList(val remoteHosts: List<RemoteHostInfo>): CR()
@Serializable @SerialName("remoteHostConnected") class RemoteHostConnected(val remoteHostId: Long): CR() @Serializable @SerialName("remoteHostConnected") class RemoteHostConnected(val remoteHost: RemoteHostInfo): CR()
@Serializable @SerialName("remoteHostStopped") class RemoteHostStopped(val remoteHostId: Long): CR() @Serializable @SerialName("remoteHostStopped") class RemoteHostStopped(val remoteHostId: Long): CR()
// remote events (mobile) // remote events (mobile)
@Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List<RemoteCtrlInfo>): CR() @Serializable @SerialName("remoteCtrlList") class RemoteCtrlList(val remoteCtrls: List<RemoteCtrlInfo>): CR()
@Serializable @SerialName("remoteCtrlRegistered") class RemoteCtrlRegistered(val remoteCtrlId: Long): CR() @Serializable @SerialName("remoteCtrlRegistered") class RemoteCtrlRegistered(val remoteCtrl: RemoteCtrlInfo): CR()
@Serializable @SerialName("remoteCtrlAnnounce") class RemoteCtrlAnnounce(val fingerprint: String): CR() @Serializable @SerialName("remoteCtrlAnnounce") class RemoteCtrlAnnounce(val fingerprint: String): CR()
@Serializable @SerialName("remoteCtrlFound") class RemoteCtrlFound(val remoteCtrl: RemoteCtrl): CR() @Serializable @SerialName("remoteCtrlFound") class RemoteCtrlFound(val remoteCtrl: RemoteCtrlInfo): CR()
@Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrlId: Long, val displayName: String): CR() @Serializable @SerialName("remoteCtrlConnecting") class RemoteCtrlConnecting(val remoteCtrl: RemoteCtrlInfo): CR()
@Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrlId: Long, val displayName: String): CR() @Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrl: RemoteCtrlInfo): CR()
@Serializable @SerialName("remoteCtrlStopped") class RemoteCtrlStopped(): 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("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("cmdOk") class CmdOk(val user: UserRef?): CR()
@ -3767,17 +3828,17 @@ sealed class CR {
is CallEnded -> withUser(user, "contact: ${contact.id}") is CallEnded -> withUser(user, "contact: ${contact.id}")
is NewContactConnection -> withUser(user, json.encodeToString(connection)) is NewContactConnection -> withUser(user, json.encodeToString(connection))
is ContactConnectionDeleted -> withUser(user, json.encodeToString(connection)) is ContactConnectionDeleted -> withUser(user, json.encodeToString(connection))
is RemoteHostCreated -> "remote host ID: $remoteHostId\noobData ${json.encodeToString(oobData)}" is RemoteHostCreated -> json.encodeToString(remoteHost)
is RemoteHostList -> "remote hosts: ${json.encodeToString(remoteHosts)}" is RemoteHostList -> json.encodeToString(remoteHosts)
is RemoteHostConnected -> "remote host ID: $remoteHostId" is RemoteHostConnected -> json.encodeToString(remoteHost)
is RemoteHostStopped -> "remote host ID: $remoteHostId" is RemoteHostStopped -> "remote host ID: $remoteHostId"
is RemoteCtrlList -> json.encodeToString(remoteCtrls) is RemoteCtrlList -> json.encodeToString(remoteCtrls)
is RemoteCtrlRegistered -> "remote ctrl ID: $remoteCtrlId" is RemoteCtrlRegistered -> json.encodeToString(remoteCtrl)
is RemoteCtrlAnnounce -> "fingerprint: $fingerprint" is RemoteCtrlAnnounce -> "fingerprint: $fingerprint"
is RemoteCtrlFound -> "remote ctrl: ${json.encodeToString(remoteCtrl)}" is RemoteCtrlFound -> json.encodeToString(remoteCtrl)
is RemoteCtrlConnecting -> "remote ctrl ID: $remoteCtrlId\nhost displayName: $displayName" is RemoteCtrlConnecting -> json.encodeToString(remoteCtrl)
is RemoteCtrlConnected -> "remote ctrl ID: $remoteCtrlId\nhost displayName: $displayName" is RemoteCtrlConnected -> json.encodeToString(remoteCtrl)
is RemoteCtrlStopped -> "" is RemoteCtrlStopped -> noDetails()
is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" + is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" +
"chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" + "chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" +
"agent migrations: ${json.encodeToString(agentMigrations.map { it.upName })}" "agent migrations: ${json.encodeToString(agentMigrations.map { it.upName })}"

View File

@ -70,6 +70,7 @@ import Simplex.Chat.Store.Files
import Simplex.Chat.Store.Groups import Simplex.Chat.Store.Groups
import Simplex.Chat.Store.Messages import Simplex.Chat.Store.Messages
import Simplex.Chat.Store.Profiles import Simplex.Chat.Store.Profiles
import Simplex.Chat.Store.Remote
import Simplex.Chat.Store.Shared import Simplex.Chat.Store.Shared
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
@ -1900,7 +1901,7 @@ processChatCommand = \case
StopRemoteHost rh -> closeRemoteHostSession rh >> ok_ StopRemoteHost rh -> closeRemoteHostSession rh >> ok_
DeleteRemoteHost rh -> deleteRemoteHost rh >> ok_ DeleteRemoteHost rh -> deleteRemoteHost rh >> ok_
StartRemoteCtrl -> startRemoteCtrl (execChatCommand Nothing) >> ok_ StartRemoteCtrl -> startRemoteCtrl (execChatCommand Nothing) >> ok_
RegisterRemoteCtrl oob -> CRRemoteCtrlRegistered <$> registerRemoteCtrl oob RegisterRemoteCtrl oob -> CRRemoteCtrlRegistered <$> withStore' (`insertRemoteCtrl` oob)
AcceptRemoteCtrl rc -> acceptRemoteCtrl rc >> ok_ AcceptRemoteCtrl rc -> acceptRemoteCtrl rc >> ok_
RejectRemoteCtrl rc -> rejectRemoteCtrl rc >> ok_ RejectRemoteCtrl rc -> rejectRemoteCtrl rc >> ok_
StopRemoteCtrl -> stopRemoteCtrl >> ok_ StopRemoteCtrl -> stopRemoteCtrl >> ok_

View File

@ -633,14 +633,14 @@ data ChatResponse
| CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection}
| CRRemoteHostCreated {remoteHost :: RemoteHostInfo} | CRRemoteHostCreated {remoteHost :: RemoteHostInfo}
| CRRemoteHostList {remoteHosts :: [RemoteHostInfo]} | CRRemoteHostList {remoteHosts :: [RemoteHostInfo]}
| CRRemoteHostConnected {remoteHostId :: RemoteHostId} -- TODO add displayName | CRRemoteHostConnected {remoteHost :: RemoteHostInfo}
| CRRemoteHostStopped {remoteHostId :: RemoteHostId} | CRRemoteHostStopped {remoteHostId :: RemoteHostId}
| CRRemoteCtrlList {remoteCtrls :: [RemoteCtrlInfo]} | CRRemoteCtrlList {remoteCtrls :: [RemoteCtrlInfo]}
| CRRemoteCtrlRegistered {remoteCtrlId :: RemoteCtrlId} | CRRemoteCtrlRegistered {remoteCtrl :: RemoteCtrlInfo}
| CRRemoteCtrlAnnounce {fingerprint :: C.KeyHash} -- unregistered fingerprint, needs confirmation | CRRemoteCtrlAnnounce {fingerprint :: C.KeyHash} -- unregistered fingerprint, needs confirmation
| CRRemoteCtrlFound {remoteCtrl :: RemoteCtrl} -- registered fingerprint, may connect | CRRemoteCtrlFound {remoteCtrl :: RemoteCtrlInfo} -- registered fingerprint, may connect
| CRRemoteCtrlConnecting {remoteCtrlId :: RemoteCtrlId, displayName :: Text} | CRRemoteCtrlConnecting {remoteCtrl :: RemoteCtrlInfo}
| CRRemoteCtrlConnected {remoteCtrlId :: RemoteCtrlId, displayName :: Text} | CRRemoteCtrlConnected {remoteCtrl :: RemoteCtrlInfo}
| CRRemoteCtrlStopped | CRRemoteCtrlStopped
| CRSQLResult {rows :: [Text]} | CRSQLResult {rows :: [Text]}
| CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]}
@ -693,34 +693,6 @@ logResponseToFile = \case
CRMessageError {} -> True CRMessageError {} -> True
_ -> False _ -> False
data RemoteCtrlOOB = RemoteCtrlOOB
{ caFingerprint :: C.KeyHash,
displayName :: Text
}
deriving (Show, Generic, FromJSON)
instance ToJSON RemoteCtrlOOB where toEncoding = J.genericToEncoding J.defaultOptions
data RemoteHostInfo = RemoteHostInfo
{ remoteHostId :: RemoteHostId,
storePath :: FilePath,
displayName :: Text,
remoteCtrlOOB :: RemoteCtrlOOB,
sessionActive :: Bool
}
deriving (Show, Generic, FromJSON)
instance ToJSON RemoteHostInfo where toEncoding = J.genericToEncoding J.defaultOptions
data RemoteCtrlInfo = RemoteCtrlInfo
{ remoteCtrlId :: RemoteCtrlId,
displayName :: Text,
sessionActive :: Bool
}
deriving (Eq, Show, Generic, FromJSON)
instance ToJSON RemoteCtrlInfo where toEncoding = J.genericToEncoding J.defaultOptions
data ConnectionPlan data ConnectionPlan
= CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan} = CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan}
| CPContactAddress {contactAddressPlan :: ContactAddressPlan} | CPContactAddress {contactAddressPlan :: ContactAddressPlan}

View File

@ -33,6 +33,7 @@ import Data.Int (Int64)
import Data.List.NonEmpty (NonEmpty (..)) import Data.List.NonEmpty (NonEmpty (..))
import qualified Data.Map.Strict as M import qualified Data.Map.Strict as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8, encodeUtf8) import Data.Text.Encoding (decodeUtf8, encodeUtf8)
import qualified Network.HTTP.Types as HTTP import qualified Network.HTTP.Types as HTTP
@ -93,7 +94,7 @@ startRemoteHost remoteHostId = do
M.lookup remoteHostId <$> chatReadVar remoteHostSessions >>= \case M.lookup remoteHostId <$> chatReadVar remoteHostSessions >>= \case
Nothing -> logInfo $ "Session already closed for remote host " <> tshow remoteHostId Nothing -> logInfo $ "Session already closed for remote host " <> tshow remoteHostId
Just _ -> closeRemoteHostSession remoteHostId >> toView (CRRemoteHostStopped remoteHostId) Just _ -> closeRemoteHostSession remoteHostId >> toView (CRRemoteHostStopped remoteHostId)
run RemoteHost {storePath, caKey, caCert} = do run rh@RemoteHost {storePath, caKey, caCert} = do
finished <- newTVarIO False finished <- newTVarIO False
let parent = (C.signatureKeyPair caKey, caCert) let parent = (C.signatureKeyPair caKey, caCert)
sessionCreds <- liftIO $ genCredentials (Just parent) (0, 24) "Session" sessionCreds <- liftIO $ genCredentials (Just parent) (0, 24) "Session"
@ -120,7 +121,9 @@ startRemoteHost remoteHostId = do
Nothing -> toViewRemote chatResponse Nothing -> toViewRemote chatResponse
Just localFile -> toViewRemote CRRcvFileComplete {user = ru, chatItem = AChatItem c d i ci {file = Just localFile}} Just localFile -> toViewRemote CRRcvFileComplete {user = ru, chatItem = AChatItem c d i ci {file = Just localFile}}
_ -> toViewRemote chatResponse _ -> toViewRemote chatResponse
toView CRRemoteHostConnected {remoteHostId} rcName <- chatReadVar localDeviceName
-- TODO what sets session active?
toView CRRemoteHostConnected {remoteHost = remoteHostInfo rh True rcName}
sendHello :: (ChatMonad m) => HTTP2Client -> m (Either HTTP2.HTTP2ClientError HTTP2.HTTP2Response) sendHello :: (ChatMonad m) => HTTP2Client -> m (Either HTTP2.HTTP2ClientError HTTP2.HTTP2Response)
sendHello http = liftIO (HTTP2.sendRequestDirect http req Nothing) sendHello http = liftIO (HTTP2.sendRequestDirect http req Nothing)
@ -155,13 +158,13 @@ cancelRemoteHostSession = \case
createRemoteHost :: (ChatMonad m) => m RemoteHostInfo createRemoteHost :: (ChatMonad m) => m RemoteHostInfo
createRemoteHost = do createRemoteHost = do
let hostDisplayName = "TODO" -- you don't have remote host name here, it will be passed from remote host let rhName = "TODO" -- you don't have remote host name here, it will be passed from remote host
((_, caKey), caCert) <- liftIO $ genCredentials Nothing (-25, 24 * 365) hostDisplayName ((_, caKey), caCert) <- liftIO $ genCredentials Nothing (-25, 24 * 365) rhName
storePath <- liftIO randomStorePath storePath <- liftIO randomStorePath
remoteHostId <- withStore' $ \db -> insertRemoteHost db storePath hostDisplayName caKey caCert remoteHostId <- withStore' $ \db -> insertRemoteHost db storePath rhName caKey caCert
displayName <- chatReadVar localDeviceName rcName <- chatReadVar localDeviceName
let remoteCtrlOOB = RemoteCtrlOOB {caFingerprint = C.certificateFingerprint caCert, displayName} let remoteCtrlOOB = RemoteCtrlOOB {fingerprint = C.certificateFingerprint caCert, displayName = rcName}
pure RemoteHostInfo {remoteHostId, storePath, displayName, remoteCtrlOOB, sessionActive = False} pure RemoteHostInfo {remoteHostId, storePath, displayName = rhName, remoteCtrlOOB, sessionActive = False}
-- | Generate a random 16-char filepath without / in it by using base64url encoding. -- | Generate a random 16-char filepath without / in it by using base64url encoding.
randomStorePath :: IO FilePath randomStorePath :: IO FilePath
@ -173,10 +176,13 @@ listRemoteHosts = do
rcName <- chatReadVar localDeviceName rcName <- chatReadVar localDeviceName
map (rhInfo active rcName) <$> withStore' getRemoteHosts map (rhInfo active rcName) <$> withStore' getRemoteHosts
where where
rhInfo active rcName RemoteHost {remoteHostId, storePath, displayName, caCert} = rhInfo active rcName rh@RemoteHost {remoteHostId} =
let sessionActive = M.member remoteHostId active remoteHostInfo rh (M.member remoteHostId active) rcName
remoteCtrlOOB = RemoteCtrlOOB {caFingerprint = C.certificateFingerprint caCert, displayName = rcName}
in RemoteHostInfo {remoteHostId, storePath, displayName, remoteCtrlOOB, sessionActive} remoteHostInfo :: RemoteHost -> Bool -> Text -> RemoteHostInfo
remoteHostInfo RemoteHost {remoteHostId, storePath, displayName, caCert} sessionActive rcName =
let remoteCtrlOOB = RemoteCtrlOOB {fingerprint = C.certificateFingerprint caCert, displayName = rcName}
in RemoteHostInfo {remoteHostId, storePath, displayName, remoteCtrlOOB, sessionActive}
deleteRemoteHost :: (ChatMonad m) => RemoteHostId -> m () deleteRemoteHost :: (ChatMonad m) => RemoteHostId -> m ()
deleteRemoteHost remoteHostId = withRemoteHost remoteHostId $ \RemoteHost {storePath} -> do deleteRemoteHost remoteHostId = withRemoteHost remoteHostId $ \RemoteHost {storePath} -> do
@ -405,13 +411,13 @@ startRemoteCtrl execChatCommand =
accepted <- newEmptyTMVarIO accepted <- newEmptyTMVarIO
supervisor <- async $ do supervisor <- async $ do
remoteCtrlId <- atomically (readTMVar accepted) remoteCtrlId <- atomically (readTMVar accepted)
withRemoteCtrl remoteCtrlId $ \RemoteCtrl {displayName, fingerprint} -> do withRemoteCtrl remoteCtrlId $ \rc@RemoteCtrl {fingerprint} -> do
source <- atomically $ TM.lookup fingerprint discovered >>= maybe retry pure source <- atomically $ TM.lookup fingerprint discovered >>= maybe retry pure
toView $ CRRemoteCtrlConnecting {remoteCtrlId, displayName} toView $ CRRemoteCtrlConnecting $ remoteCtrlInfo rc False
atomically $ writeTVar discovered mempty -- flush unused sources atomically $ writeTVar discovered mempty -- flush unused sources
server <- async $ Discovery.connectRevHTTP2 source fingerprint (processControllerRequest execChatCommand) server <- async $ Discovery.connectRevHTTP2 source fingerprint (processControllerRequest execChatCommand)
chatModifyVar remoteCtrlSession $ fmap $ \s -> s {hostServer = Just server} chatModifyVar remoteCtrlSession $ fmap $ \s -> s {hostServer = Just server}
toView $ CRRemoteCtrlConnected {remoteCtrlId, displayName} toView $ CRRemoteCtrlConnected $ remoteCtrlInfo rc True
_ <- waitCatch server _ <- waitCatch server
chatWriteVar remoteCtrlSession Nothing chatWriteVar remoteCtrlSession Nothing
toView CRRemoteCtrlStopped toView CRRemoteCtrlStopped
@ -436,7 +442,7 @@ discoverRemoteCtrls discovered = Discovery.withListener go
withStore' (`getRemoteCtrlByFingerprint` fingerprint) >>= \case withStore' (`getRemoteCtrlByFingerprint` fingerprint) >>= \case
Nothing -> toView $ CRRemoteCtrlAnnounce fingerprint -- unknown controller, ui "register" action required Nothing -> toView $ CRRemoteCtrlAnnounce fingerprint -- unknown controller, ui "register" action required
Just found@RemoteCtrl {remoteCtrlId, accepted = storedChoice} -> case storedChoice of Just found@RemoteCtrl {remoteCtrlId, accepted = storedChoice} -> case storedChoice of
Nothing -> toView $ CRRemoteCtrlFound found -- first-time controller, ui "accept" action required Nothing -> toView $ CRRemoteCtrlFound $ remoteCtrlInfo found False -- first-time controller, ui "accept" action required
Just False -> pure () -- skipping a rejected item Just False -> pure () -- skipping a rejected item
Just True -> Just True ->
chatReadVar remoteCtrlSession >>= \case chatReadVar remoteCtrlSession >>= \case
@ -444,11 +450,6 @@ discoverRemoteCtrls discovered = Discovery.withListener go
Just RemoteCtrlSession {accepted} -> atomically $ void $ tryPutTMVar accepted remoteCtrlId -- previously accepted controller, connect automatically Just RemoteCtrlSession {accepted} -> atomically $ void $ tryPutTMVar accepted remoteCtrlId -- previously accepted controller, connect automatically
_nonV4 -> go sock _nonV4 -> go sock
registerRemoteCtrl :: (ChatMonad m) => RemoteCtrlOOB -> m RemoteCtrlId
registerRemoteCtrl RemoteCtrlOOB {caFingerprint, displayName} = do
remoteCtrlId <- withStore' $ \db -> insertRemoteCtrl db displayName caFingerprint
pure remoteCtrlId
listRemoteCtrls :: (ChatMonad m) => m [RemoteCtrlInfo] listRemoteCtrls :: (ChatMonad m) => m [RemoteCtrlInfo]
listRemoteCtrls = do listRemoteCtrls = do
active <- active <-
@ -456,9 +457,12 @@ listRemoteCtrls = do
$>>= \RemoteCtrlSession {accepted} -> atomically $ tryReadTMVar accepted $>>= \RemoteCtrlSession {accepted} -> atomically $ tryReadTMVar accepted
map (rcInfo active) <$> withStore' getRemoteCtrls map (rcInfo active) <$> withStore' getRemoteCtrls
where where
rcInfo active RemoteCtrl {remoteCtrlId, displayName} = rcInfo active rc@RemoteCtrl {remoteCtrlId} =
let sessionActive = active == Just remoteCtrlId remoteCtrlInfo rc $ active == Just remoteCtrlId
in RemoteCtrlInfo {remoteCtrlId, displayName, sessionActive}
remoteCtrlInfo :: RemoteCtrl -> Bool -> RemoteCtrlInfo
remoteCtrlInfo RemoteCtrl {remoteCtrlId, displayName, fingerprint, accepted} sessionActive =
RemoteCtrlInfo {remoteCtrlId, displayName, fingerprint, accepted, sessionActive}
acceptRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m () acceptRemoteCtrl :: (ChatMonad m) => RemoteCtrlId -> m ()
acceptRemoteCtrl remoteCtrlId = do acceptRemoteCtrl remoteCtrlId = do

View File

@ -24,6 +24,25 @@ data RemoteHost = RemoteHost
} }
deriving (Show) deriving (Show)
data RemoteCtrlOOB = RemoteCtrlOOB
{ fingerprint :: C.KeyHash,
displayName :: Text
}
deriving (Show)
$(J.deriveJSON J.defaultOptions ''RemoteCtrlOOB)
data RemoteHostInfo = RemoteHostInfo
{ remoteHostId :: RemoteHostId,
storePath :: FilePath,
displayName :: Text,
remoteCtrlOOB :: RemoteCtrlOOB,
sessionActive :: Bool
}
deriving (Show)
$(J.deriveJSON J.defaultOptions ''RemoteHostInfo)
type RemoteCtrlId = Int64 type RemoteCtrlId = Int64
data RemoteCtrl = RemoteCtrl data RemoteCtrl = RemoteCtrl
@ -34,4 +53,15 @@ data RemoteCtrl = RemoteCtrl
} }
deriving (Show) deriving (Show)
$(J.deriveJSON J.defaultOptions ''RemoteCtrl) $(J.deriveJSON J.defaultOptions {J.omitNothingFields = True} ''RemoteCtrl)
data RemoteCtrlInfo = RemoteCtrlInfo
{ remoteCtrlId :: RemoteCtrlId,
displayName :: Text,
fingerprint :: C.KeyHash,
accepted :: Maybe Bool,
sessionActive :: Bool
}
deriving (Show)
$(J.deriveJSON J.defaultOptions {J.omitNothingFields = True} ''RemoteCtrlInfo)

View File

@ -9,14 +9,15 @@ import Data.Text (Text)
import Database.SQLite.Simple (Only (..)) import Database.SQLite.Simple (Only (..))
import qualified Database.SQLite.Simple as SQL import qualified Database.SQLite.Simple as SQL
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
import Simplex.Chat.Remote.Types (RemoteCtrl (..), RemoteCtrlId, RemoteHost (..), RemoteHostId) import Simplex.Chat.Store.Shared (insertedRowId)
import Simplex.Chat.Remote.Types
import Simplex.Messaging.Agent.Store.SQLite (maybeFirstRow) import Simplex.Messaging.Agent.Store.SQLite (maybeFirstRow)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
insertRemoteHost :: DB.Connection -> FilePath -> Text -> C.APrivateSignKey -> C.SignedCertificate -> IO RemoteHostId insertRemoteHost :: DB.Connection -> FilePath -> Text -> C.APrivateSignKey -> C.SignedCertificate -> IO RemoteHostId
insertRemoteHost db storePath displayName caKey caCert = do insertRemoteHost db storePath displayName caKey caCert = do
DB.execute db "INSERT INTO remote_hosts (store_path, display_name, ca_key, ca_cert) VALUES (?,?,?,?)" (storePath, displayName, caKey, C.SignedObject caCert) DB.execute db "INSERT INTO remote_hosts (store_path, display_name, ca_key, ca_cert) VALUES (?,?,?,?)" (storePath, displayName, caKey, C.SignedObject caCert)
fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()" insertedRowId db
getRemoteHosts :: DB.Connection -> IO [RemoteHost] getRemoteHosts :: DB.Connection -> IO [RemoteHost]
getRemoteHosts db = getRemoteHosts db =
@ -37,10 +38,11 @@ toRemoteHost (remoteHostId, storePath, displayName, caKey, C.SignedObject caCert
deleteRemoteHostRecord :: DB.Connection -> RemoteHostId -> IO () deleteRemoteHostRecord :: DB.Connection -> RemoteHostId -> IO ()
deleteRemoteHostRecord db remoteHostId = DB.execute db "DELETE FROM remote_hosts WHERE remote_host_id = ?" (Only remoteHostId) deleteRemoteHostRecord db remoteHostId = DB.execute db "DELETE FROM remote_hosts WHERE remote_host_id = ?" (Only remoteHostId)
insertRemoteCtrl :: DB.Connection -> Text -> C.KeyHash -> IO RemoteCtrlId insertRemoteCtrl :: DB.Connection -> RemoteCtrlOOB -> IO RemoteCtrlInfo
insertRemoteCtrl db displayName fingerprint = do insertRemoteCtrl db RemoteCtrlOOB {fingerprint, displayName} = do
DB.execute db "INSERT INTO remote_controllers (display_name, fingerprint) VALUES (?,?)" (displayName, fingerprint) DB.execute db "INSERT INTO remote_controllers (display_name, fingerprint) VALUES (?,?)" (displayName, fingerprint)
fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()" remoteCtrlId <- insertedRowId db
pure RemoteCtrlInfo {remoteCtrlId, displayName, fingerprint, accepted = Nothing, sessionActive = False}
getRemoteCtrls :: DB.Connection -> IO [RemoteCtrl] getRemoteCtrls :: DB.Connection -> IO [RemoteCtrl]
getRemoteCtrls db = getRemoteCtrls db =

View File

@ -264,14 +264,14 @@ responseToView (currentRH, user_) ChatConfig {logLevel, showReactions, showRecei
CRNtfMessages {} -> [] CRNtfMessages {} -> []
CRRemoteHostCreated RemoteHostInfo {remoteHostId, remoteCtrlOOB} -> ("remote host " <> sShow remoteHostId <> " created") : viewRemoteCtrlOOBData remoteCtrlOOB CRRemoteHostCreated RemoteHostInfo {remoteHostId, remoteCtrlOOB} -> ("remote host " <> sShow remoteHostId <> " created") : viewRemoteCtrlOOBData remoteCtrlOOB
CRRemoteHostList hs -> viewRemoteHosts hs CRRemoteHostList hs -> viewRemoteHosts hs
CRRemoteHostConnected rhId -> ["remote host " <> sShow rhId <> " connected"] CRRemoteHostConnected RemoteHostInfo {remoteHostId = rhId} -> ["remote host " <> sShow rhId <> " connected"]
CRRemoteHostStopped rhId -> ["remote host " <> sShow rhId <> " stopped"] CRRemoteHostStopped rhId -> ["remote host " <> sShow rhId <> " stopped"]
CRRemoteCtrlList cs -> viewRemoteCtrls cs CRRemoteCtrlList cs -> viewRemoteCtrls cs
CRRemoteCtrlRegistered rcId -> ["remote controller " <> sShow rcId <> " registered"] CRRemoteCtrlRegistered RemoteCtrlInfo {remoteCtrlId = rcId} -> ["remote controller " <> sShow rcId <> " registered"]
CRRemoteCtrlAnnounce fingerprint -> ["remote controller announced", "connection code:", plain $ strEncode fingerprint] CRRemoteCtrlAnnounce fingerprint -> ["remote controller announced", "connection code:", plain $ strEncode fingerprint]
CRRemoteCtrlFound rc -> ["remote controller found:", viewRemoteCtrl rc] CRRemoteCtrlFound rc -> ["remote controller found:", viewRemoteCtrl rc]
CRRemoteCtrlConnecting rcId rcName -> ["remote controller " <> sShow rcId <> " connecting to " <> plain rcName] CRRemoteCtrlConnecting RemoteCtrlInfo {remoteCtrlId = rcId, displayName = rcName} -> ["remote controller " <> sShow rcId <> " connecting to " <> plain rcName]
CRRemoteCtrlConnected rcId rcName -> ["remote controller " <> sShow rcId <> " connected, " <> plain rcName] CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, displayName = rcName} -> ["remote controller " <> sShow rcId <> " connected, " <> plain rcName]
CRRemoteCtrlStopped -> ["remote controller stopped"] CRRemoteCtrlStopped -> ["remote controller stopped"]
CRSQLResult rows -> map plain rows CRSQLResult rows -> map plain rows
CRSlowSQLQueries {chatQueries, agentQueries} -> CRSlowSQLQueries {chatQueries, agentQueries} ->
@ -1633,8 +1633,8 @@ viewVersionInfo logLevel CoreVersionInfo {version, simplexmqVersion, simplexmqCo
parens s = " (" <> s <> ")" parens s = " (" <> s <> ")"
viewRemoteCtrlOOBData :: RemoteCtrlOOB -> [StyledString] viewRemoteCtrlOOBData :: RemoteCtrlOOB -> [StyledString]
viewRemoteCtrlOOBData RemoteCtrlOOB {caFingerprint} = viewRemoteCtrlOOBData RemoteCtrlOOB {fingerprint} =
["connection code:", plain $ strEncode caFingerprint] ["connection code:", plain $ strEncode fingerprint]
viewRemoteHosts :: [RemoteHostInfo] -> [StyledString] viewRemoteHosts :: [RemoteHostInfo] -> [StyledString]
viewRemoteHosts = \case viewRemoteHosts = \case
@ -1653,8 +1653,8 @@ viewRemoteCtrls = \case
plain $ tshow remoteCtrlId <> ". " <> displayName <> if sessionActive then " (active)" else "" plain $ tshow remoteCtrlId <> ". " <> displayName <> if sessionActive then " (active)" else ""
-- TODO fingerprint, accepted? -- TODO fingerprint, accepted?
viewRemoteCtrl :: RemoteCtrl -> StyledString viewRemoteCtrl :: RemoteCtrlInfo -> StyledString
viewRemoteCtrl RemoteCtrl {remoteCtrlId, displayName} = viewRemoteCtrl RemoteCtrlInfo {remoteCtrlId, displayName} =
plain $ tshow remoteCtrlId <> ". " <> displayName plain $ tshow remoteCtrlId <> ". " <> displayName
viewChatError :: ChatLogLevel -> ChatError -> [StyledString] viewChatError :: ChatLogLevel -> ChatError -> [StyledString]