2022-05-03 08:20:19 +01:00
|
|
|
//
|
|
|
|
// API.swift
|
|
|
|
// SimpleX NSE
|
|
|
|
//
|
|
|
|
// Created by Evgeny on 26/04/2022.
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
|
|
|
private var chatController: chat_ctrl?
|
|
|
|
|
2022-09-23 12:51:40 +01:00
|
|
|
private var migrationResult: (Bool, DBMigrationResult)?
|
|
|
|
|
2022-09-07 12:49:41 +01:00
|
|
|
public func getChatCtrl(_ useKey: String? = nil) -> chat_ctrl {
|
2022-05-03 08:20:19 +01:00
|
|
|
if let controller = chatController { return controller }
|
2022-09-24 09:28:22 +01:00
|
|
|
fatalError("chat controller not initialized")
|
2022-05-03 08:20:19 +01:00
|
|
|
}
|
|
|
|
|
2022-09-24 09:28:22 +01:00
|
|
|
public func chatMigrateInit(_ useKey: String? = nil) -> (Bool, DBMigrationResult) {
|
2022-09-23 12:51:40 +01:00
|
|
|
if let res = migrationResult { return res }
|
2022-09-24 09:28:22 +01:00
|
|
|
logger.debug("chatMigrateInit \(storeDBPassphraseGroupDefault.get())")
|
2022-09-07 12:49:41 +01:00
|
|
|
let dbPath = getAppDatabasePath().path
|
|
|
|
var dbKey = ""
|
|
|
|
let useKeychain = storeDBPassphraseGroupDefault.get()
|
|
|
|
if let key = useKey {
|
|
|
|
dbKey = key
|
|
|
|
} else if useKeychain {
|
|
|
|
if !hasDatabase() {
|
|
|
|
dbKey = randomDatabasePassword()
|
|
|
|
initialRandomDBPassphraseGroupDefault.set(true)
|
|
|
|
} else if let key = getDatabaseKey() {
|
|
|
|
dbKey = key
|
|
|
|
}
|
|
|
|
}
|
2022-09-24 09:28:22 +01:00
|
|
|
logger.debug("chatMigrateInit DB path: \(dbPath)")
|
2022-09-25 13:17:04 +01:00
|
|
|
// logger.debug("chatMigrateInit DB key: \(dbKey)")
|
2022-09-07 12:49:41 +01:00
|
|
|
var cPath = dbPath.cString(using: .utf8)!
|
|
|
|
var cKey = dbKey.cString(using: .utf8)!
|
2022-09-24 09:28:22 +01:00
|
|
|
// the last parameter of chat_migrate_init is used to return the pointer to chat controller
|
|
|
|
let cjson = chat_migrate_init(&cPath, &cKey, &chatController)!
|
2022-09-23 12:51:40 +01:00
|
|
|
let dbRes = dbMigrationResult(fromCString(cjson))
|
2022-09-07 12:49:41 +01:00
|
|
|
let encrypted = dbKey != ""
|
2022-09-23 12:51:40 +01:00
|
|
|
let keychainErr = dbRes == .ok && useKeychain && encrypted && !setDatabaseKey(dbKey)
|
|
|
|
let result = (encrypted, keychainErr ? .errorKeychain : dbRes)
|
|
|
|
migrationResult = result
|
|
|
|
return result
|
2022-09-07 12:49:41 +01:00
|
|
|
}
|
|
|
|
|
2022-06-24 13:52:20 +01:00
|
|
|
public func resetChatCtrl() {
|
|
|
|
chatController = nil
|
2022-09-23 12:51:40 +01:00
|
|
|
migrationResult = nil
|
2022-06-24 13:52:20 +01:00
|
|
|
}
|
|
|
|
|
2022-05-31 07:55:13 +01:00
|
|
|
public func sendSimpleXCmd(_ cmd: ChatCommand) -> ChatResponse {
|
2022-05-03 08:20:19 +01:00
|
|
|
var c = cmd.cmdString.cString(using: .utf8)!
|
2022-06-24 13:52:20 +01:00
|
|
|
let cjson = chat_send_cmd(getChatCtrl(), &c)!
|
|
|
|
return chatResponse(fromCString(cjson))
|
|
|
|
}
|
|
|
|
|
|
|
|
// in microseconds
|
|
|
|
let MESSAGE_TIMEOUT: Int32 = 15_000_000
|
|
|
|
|
|
|
|
public func recvSimpleXMsg() -> ChatResponse? {
|
|
|
|
if let cjson = chat_recv_msg_wait(getChatCtrl(), MESSAGE_TIMEOUT) {
|
|
|
|
let s = fromCString(cjson)
|
|
|
|
return s == "" ? nil : chatResponse(s)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
public func parseSimpleXMarkdown(_ s: String) -> [FormattedText]? {
|
|
|
|
var c = s.cString(using: .utf8)!
|
|
|
|
if let cjson = chat_parse_markdown(&c) {
|
|
|
|
if let d = fromCString(cjson).data(using: .utf8) {
|
|
|
|
do {
|
|
|
|
let r = try jsonDecoder.decode(ParsedMarkdown.self, from: d)
|
|
|
|
return r.formattedText
|
|
|
|
} catch {
|
|
|
|
logger.error("parseSimpleXMarkdown jsonDecoder.decode error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ParsedMarkdown: Decodable {
|
|
|
|
var formattedText: [FormattedText]?
|
|
|
|
}
|
|
|
|
|
|
|
|
private func fromCString(_ c: UnsafeMutablePointer<CChar>) -> String {
|
|
|
|
let s = String.init(cString: c)
|
|
|
|
free(c)
|
|
|
|
return s
|
2022-05-03 08:20:19 +01:00
|
|
|
}
|
|
|
|
|
2022-06-24 13:52:20 +01:00
|
|
|
public func chatResponse(_ s: String) -> ChatResponse {
|
2022-05-03 08:20:19 +01:00
|
|
|
let d = s.data(using: .utf8)!
|
|
|
|
// TODO is there a way to do it without copying the data? e.g:
|
|
|
|
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
|
|
|
|
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
|
|
|
|
do {
|
|
|
|
let r = try jsonDecoder.decode(APIResponse.self, from: d)
|
|
|
|
return r.resp
|
|
|
|
} catch {
|
|
|
|
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
|
|
|
|
var type: String?
|
|
|
|
var json: String?
|
|
|
|
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
|
|
|
|
if let j1 = j["resp"] as? NSDictionary, j1.count == 1 {
|
|
|
|
type = j1.allKeys[0] as? String
|
|
|
|
}
|
|
|
|
json = prettyJSON(j)
|
|
|
|
}
|
|
|
|
return ChatResponse.response(type: type ?? "invalid", json: json ?? s)
|
|
|
|
}
|
|
|
|
|
|
|
|
func prettyJSON(_ obj: NSDictionary) -> String? {
|
|
|
|
if let d = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) {
|
|
|
|
return String(decoding: d, as: UTF8.self)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2022-05-31 07:55:13 +01:00
|
|
|
public func responseError(_ err: Error) -> String {
|
2022-05-03 08:20:19 +01:00
|
|
|
if let r = err as? ChatResponse {
|
|
|
|
return String(describing: r)
|
|
|
|
} else {
|
|
|
|
return err.localizedDescription
|
|
|
|
}
|
|
|
|
}
|
2022-09-07 12:49:41 +01:00
|
|
|
|
|
|
|
public enum DBMigrationResult: Decodable, Equatable {
|
|
|
|
case ok
|
|
|
|
case errorNotADatabase(dbFile: String)
|
|
|
|
case error(dbFile: String, migrationError: String)
|
|
|
|
case errorKeychain
|
|
|
|
case unknown(json: String)
|
|
|
|
}
|
|
|
|
|
|
|
|
func dbMigrationResult(_ s: String) -> DBMigrationResult {
|
|
|
|
let d = s.data(using: .utf8)!
|
|
|
|
// TODO is there a way to do it without copying the data? e.g:
|
|
|
|
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
|
|
|
|
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
|
|
|
|
do {
|
|
|
|
return try jsonDecoder.decode(DBMigrationResult.self, from: d)
|
|
|
|
} catch let error {
|
|
|
|
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
|
|
|
|
return .unknown(json: s)
|
|
|
|
}
|
|
|
|
}
|