mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8159ae4aea | |||
| 5236e0f201 | |||
| 1eb1ed92f7 | |||
| c25e9e3b72 | |||
| 300223b32e | |||
| 8d7dcb550a | |||
| 045b195483 | |||
| 53414608db | |||
| c7cf206585 | |||
| 6067ac3c93 | |||
| a2f190a6c6 | |||
| 267178dddb | |||
| fadce0c140 | |||
| 58ad97fe6d | |||
| 3ccd9903a7 | |||
| e294999044 | |||
| 2bbc687f4a | |||
| bb61b9c658 | |||
| 575d899f5a | |||
| 825257e898 |
@@ -9,6 +9,7 @@ on:
|
|||||||
tags:
|
tags:
|
||||||
- "v*"
|
- "v*"
|
||||||
- "!*-fdroid"
|
- "!*-fdroid"
|
||||||
|
- "!*-armv7a"
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ private var nseSubscribers: [UUID:NSESubscriber] = [:]
|
|||||||
private let SUSPENDING_TIMEOUT: TimeInterval = 2
|
private let SUSPENDING_TIMEOUT: TimeInterval = 2
|
||||||
|
|
||||||
// timeout should be larger than SUSPENDING_TIMEOUT
|
// timeout should be larger than SUSPENDING_TIMEOUT
|
||||||
func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = DispatchQueue.main, suspended: @escaping (Bool) -> Void) {
|
func waitNSESuspended(timeout: TimeInterval, suspended: @escaping (Bool) -> Void) {
|
||||||
if timeout <= SUSPENDING_TIMEOUT {
|
if timeout <= SUSPENDING_TIMEOUT {
|
||||||
logger.warning("waitNSESuspended: small timeout \(timeout), using \(SUSPENDING_TIMEOUT + 1)")
|
logger.warning("waitNSESuspended: small timeout \(timeout), using \(SUSPENDING_TIMEOUT + 1)")
|
||||||
}
|
}
|
||||||
var state = nseStateGroupDefault.get()
|
var state = nseStateGroupDefault.get()
|
||||||
if case .suspended = state {
|
if case .suspended = state {
|
||||||
dispatchQueue.async { suspended(true) }
|
DispatchQueue.main.async { suspended(true) }
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let id = UUID()
|
let id = UUID()
|
||||||
@@ -45,7 +45,7 @@ func waitNSESuspended(timeout: TimeInterval, dispatchQueue: DispatchQueue = Disp
|
|||||||
logger.debug("waitNSESuspended notifySuspended: calling suspended(\(ok))")
|
logger.debug("waitNSESuspended notifySuspended: calling suspended(\(ok))")
|
||||||
suspendedCalled = true
|
suspendedCalled = true
|
||||||
nseSubscribers.removeValue(forKey: id)
|
nseSubscribers.removeValue(forKey: id)
|
||||||
dispatchQueue.async { suspended(ok) }
|
DispatchQueue.main.async { suspended(ok) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) asyn
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apiStartChat() throws -> Bool {
|
func apiStartChat() throws -> Bool {
|
||||||
let r = chatSendCmdSync(.startChat(subscribe: true, expire: true, xftp: true))
|
let r = chatSendCmdSync(.startChat(mainApp: true))
|
||||||
switch r {
|
switch r {
|
||||||
case .chatStarted: return true
|
case .chatStarted: return true
|
||||||
case .chatRunning: return false
|
case .chatRunning: return false
|
||||||
@@ -403,7 +403,7 @@ func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode) {
|
|||||||
case let .ntfToken(token, status, ntfMode): return (token, status, ntfMode)
|
case let .ntfToken(token, status, ntfMode): return (token, status, ntfMode)
|
||||||
case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off)
|
case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off)
|
||||||
default:
|
default:
|
||||||
logger.debug("apiGetNtfToken response: \(String(describing: r), privacy: .public)")
|
logger.debug("apiGetNtfToken response: \(String(describing: r))")
|
||||||
return (nil, nil, .off)
|
return (nil, nil, .off)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,11 +19,13 @@ let terminationTimeout: Int = 3 // seconds
|
|||||||
|
|
||||||
let activationDelay: TimeInterval = 1.5
|
let activationDelay: TimeInterval = 1.5
|
||||||
|
|
||||||
|
let nseSuspendTimeout: TimeInterval = 5
|
||||||
|
|
||||||
private func _suspendChat(timeout: Int) {
|
private func _suspendChat(timeout: Int) {
|
||||||
// this is a redundant check to prevent logical errors, like the one fixed in this PR
|
// this is a redundant check to prevent logical errors, like the one fixed in this PR
|
||||||
let state = AppChatState.shared.value
|
let state = AppChatState.shared.value
|
||||||
if !state.canSuspend {
|
if !state.canSuspend {
|
||||||
logger.error("_suspendChat called, current state: \(state.rawValue, privacy: .public)")
|
logger.error("_suspendChat called, current state: \(state.rawValue)")
|
||||||
} else if ChatModel.ok {
|
} else if ChatModel.ok {
|
||||||
AppChatState.shared.set(.suspending)
|
AppChatState.shared.set(.suspending)
|
||||||
apiSuspendChat(timeoutMicroseconds: timeout * 1000000)
|
apiSuspendChat(timeoutMicroseconds: timeout * 1000000)
|
||||||
@@ -134,20 +136,33 @@ func initChatAndMigrate(refreshInvitations: Bool = true) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func startChatAndActivate(dispatchQueue: DispatchQueue = DispatchQueue.main, _ completion: @escaping () -> Void) {
|
func startChatForCall() {
|
||||||
|
logger.debug("DEBUGGING: startChatForCall")
|
||||||
|
if ChatModel.shared.chatRunning == true {
|
||||||
|
ChatReceiver.shared.start()
|
||||||
|
logger.debug("DEBUGGING: startChatForCall: after ChatReceiver.shared.start")
|
||||||
|
}
|
||||||
|
if .active != AppChatState.shared.value {
|
||||||
|
logger.debug("DEBUGGING: startChatForCall: before activateChat")
|
||||||
|
activateChat()
|
||||||
|
logger.debug("DEBUGGING: startChatForCall: after activateChat")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func startChatAndActivate(_ completion: @escaping () -> Void) {
|
||||||
logger.debug("DEBUGGING: startChatAndActivate")
|
logger.debug("DEBUGGING: startChatAndActivate")
|
||||||
if ChatModel.shared.chatRunning == true {
|
if ChatModel.shared.chatRunning == true {
|
||||||
ChatReceiver.shared.start()
|
ChatReceiver.shared.start()
|
||||||
logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start")
|
logger.debug("DEBUGGING: startChatAndActivate: after ChatReceiver.shared.start")
|
||||||
}
|
}
|
||||||
if .active == AppChatState.shared.value {
|
if case .active = AppChatState.shared.value {
|
||||||
completion()
|
completion()
|
||||||
} else if nseStateGroupDefault.get().inactive {
|
} else if nseStateGroupDefault.get().inactive {
|
||||||
activate()
|
activate()
|
||||||
} else {
|
} else {
|
||||||
// setting app state to "activating" to notify NSE that it should suspend
|
// setting app state to "activating" to notify NSE that it should suspend
|
||||||
setAppState(.activating)
|
setAppState(.activating)
|
||||||
waitNSESuspended(timeout: 10, dispatchQueue: dispatchQueue) { ok in
|
waitNSESuspended(timeout: nseSuspendTimeout) { ok in
|
||||||
if !ok {
|
if !ok {
|
||||||
// if for some reason NSE failed to suspend,
|
// if for some reason NSE failed to suspend,
|
||||||
// e.g., it crashed previously without setting its state to "suspended",
|
// e.g., it crashed previously without setting its state to "suspended",
|
||||||
|
|||||||
@@ -98,12 +98,12 @@ struct SimpleXApp: App {
|
|||||||
if legacyDatabase, case .documents = dbContainerGroupDefault.get() {
|
if legacyDatabase, case .documents = dbContainerGroupDefault.get() {
|
||||||
dbContainerGroupDefault.set(.documents)
|
dbContainerGroupDefault.set(.documents)
|
||||||
setMigrationState(.offer)
|
setMigrationState(.offer)
|
||||||
logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath(), privacy: .public)*.db")
|
logger.debug("SimpleXApp init: using legacy DB in documents folder: \(getAppDatabasePath())*.db")
|
||||||
} else {
|
} else {
|
||||||
dbContainerGroupDefault.set(.group)
|
dbContainerGroupDefault.set(.group)
|
||||||
setMigrationState(.ready)
|
setMigrationState(.ready)
|
||||||
logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath(), privacy: .public)*.db")
|
logger.debug("SimpleXApp init: using DB in app group container: \(getAppDatabasePath())*.db")
|
||||||
logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not", privacy: .public) present")
|
logger.debug("SimpleXApp init: legacy DB\(legacyDatabase ? "" : " not") present")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,13 +38,13 @@ struct ActiveCallView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onAppear {
|
||||||
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase), privacy: .public), canConnectCall \(canConnectCall)")
|
logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase)), canConnectCall \(canConnectCall)")
|
||||||
AppDelegate.keepScreenOn(true)
|
AppDelegate.keepScreenOn(true)
|
||||||
createWebRTCClient()
|
createWebRTCClient()
|
||||||
dismissAllSheets()
|
dismissAllSheets()
|
||||||
}
|
}
|
||||||
.onChange(of: canConnectCall) { _ in
|
.onChange(of: canConnectCall) { _ in
|
||||||
logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall, privacy: .public)")
|
logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall)")
|
||||||
createWebRTCClient()
|
createWebRTCClient()
|
||||||
}
|
}
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
|||||||
// The delay allows to accept the second call before suspending a chat
|
// The delay allows to accept the second call before suspending a chat
|
||||||
// see `.onChange(of: scenePhase)` in SimpleXApp
|
// see `.onChange(of: scenePhase)` in SimpleXApp
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
|
||||||
logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat), privacy: .public)")
|
logger.debug("CallController: shouldSuspendChat \(String(describing: self?.shouldSuspendChat))")
|
||||||
if ChatModel.shared.activeCall == nil && self?.shouldSuspendChat == true {
|
if ChatModel.shared.activeCall == nil && self?.shouldSuspendChat == true {
|
||||||
self?.shouldSuspendChat = false
|
self?.shouldSuspendChat = false
|
||||||
suspendChat()
|
suspendChat()
|
||||||
@@ -142,45 +142,57 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
|||||||
|
|
||||||
@objc(pushRegistry:didUpdatePushCredentials:forType:)
|
@objc(pushRegistry:didUpdatePushCredentials:forType:)
|
||||||
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
|
func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
|
||||||
logger.debug("CallController: didUpdate push credentials for type \(type.rawValue, privacy: .public)")
|
logger.debug("CallController: didUpdate push credentials for type \(type.rawValue)")
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
|
func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
|
||||||
logger.debug("CallController: did receive push with type \(type.rawValue, privacy: .public)")
|
logger.debug("CallController: did receive push with type \(type.rawValue)")
|
||||||
if type != .voIP {
|
if type != .voIP {
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.debug("CallController: initializing chat")
|
if AppChatState.shared.value == .stopped {
|
||||||
if (!ChatModel.shared.chatInitialized) {
|
self.reportExpiredCall(payload: payload, completion)
|
||||||
initChatAndMigrate(refreshInvitations: false)
|
return
|
||||||
}
|
}
|
||||||
startChatAndActivate(dispatchQueue: DispatchQueue.global()) {
|
if (!ChatModel.shared.chatInitialized) {
|
||||||
self.shouldSuspendChat = true
|
logger.debug("CallController: initializing chat")
|
||||||
// There are no invitations in the model, as it was processed by NSE
|
do {
|
||||||
_ = try? justRefreshCallInvitations()
|
try initializeChat(start: true, refreshInvitations: false)
|
||||||
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
|
} catch let error {
|
||||||
// Extract the call information from the push notification payload
|
logger.error("CallController: initializing chat error: \(error)")
|
||||||
let m = ChatModel.shared
|
self.reportExpiredCall(payload: payload, completion)
|
||||||
if let contactId = payload.dictionaryPayload["contactId"] as? String,
|
return
|
||||||
let invitation = m.callInvitations[contactId] {
|
}
|
||||||
|
}
|
||||||
|
logger.debug("CallController: initialized chat")
|
||||||
|
startChatForCall()
|
||||||
|
logger.debug("CallController: started chat")
|
||||||
|
self.shouldSuspendChat = true
|
||||||
|
// There are no invitations in the model, as it was processed by NSE
|
||||||
|
_ = try? justRefreshCallInvitations()
|
||||||
|
logger.debug("CallController: updated call invitations chat")
|
||||||
|
// logger.debug("CallController justRefreshCallInvitations: \(String(describing: m.callInvitations))")
|
||||||
|
// Extract the call information from the push notification payload
|
||||||
|
let m = ChatModel.shared
|
||||||
|
if let contactId = payload.dictionaryPayload["contactId"] as? String,
|
||||||
|
let invitation = m.callInvitations[contactId] {
|
||||||
|
let update = self.cxCallUpdate(invitation: invitation)
|
||||||
|
if let uuid = invitation.callkitUUID {
|
||||||
|
logger.debug("CallController: report pushkit call via CallKit")
|
||||||
let update = self.cxCallUpdate(invitation: invitation)
|
let update = self.cxCallUpdate(invitation: invitation)
|
||||||
if let uuid = invitation.callkitUUID {
|
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
|
||||||
logger.debug("CallController: report pushkit call via CallKit")
|
if error != nil {
|
||||||
let update = self.cxCallUpdate(invitation: invitation)
|
m.callInvitations.removeValue(forKey: contactId)
|
||||||
self.provider.reportNewIncomingCall(with: uuid, update: update) { error in
|
|
||||||
if error != nil {
|
|
||||||
m.callInvitations.removeValue(forKey: contactId)
|
|
||||||
}
|
|
||||||
// Tell PushKit that the notification is handled.
|
|
||||||
completion()
|
|
||||||
}
|
}
|
||||||
} else {
|
// Tell PushKit that the notification is handled.
|
||||||
self.reportExpiredCall(update: update, completion)
|
completion()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.reportExpiredCall(payload: payload, completion)
|
self.reportExpiredCall(update: update, completion)
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
self.reportExpiredCall(payload: payload, completion)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,7 +223,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) {
|
func reportNewIncomingCall(invitation: RcvCallInvitation, completion: @escaping (Error?) -> Void) {
|
||||||
logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID), privacy: .public)")
|
logger.debug("CallController.reportNewIncomingCall, UUID=\(String(describing: invitation.callkitUUID))")
|
||||||
if CallController.useCallKit(), let uuid = invitation.callkitUUID {
|
if CallController.useCallKit(), let uuid = invitation.callkitUUID {
|
||||||
if invitation.callTs.timeIntervalSinceNow >= -180 {
|
if invitation.callTs.timeIntervalSinceNow >= -180 {
|
||||||
let update = cxCallUpdate(invitation: invitation)
|
let update = cxCallUpdate(invitation: invitation)
|
||||||
@@ -351,7 +363,7 @@ class CallController: NSObject, CXProviderDelegate, PKPushRegistryDelegate, Obse
|
|||||||
private func requestTransaction(with action: CXAction, onSuccess: @escaping () -> Void = {}) {
|
private func requestTransaction(with action: CXAction, onSuccess: @escaping () -> Void = {}) {
|
||||||
controller.request(CXTransaction(action: action)) { error in
|
controller.request(CXTransaction(action: action)) { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription, privacy: .public)")
|
logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)")
|
||||||
} else {
|
} else {
|
||||||
logger.debug("CallController.requestTransaction requested transaction successfully")
|
logger.debug("CallController.requestTransaction requested transaction successfully")
|
||||||
onSuccess()
|
onSuccess()
|
||||||
|
|||||||
@@ -16,9 +16,11 @@ let logger = Logger()
|
|||||||
|
|
||||||
let appSuspendingDelay: UInt64 = 2_500_000_000
|
let appSuspendingDelay: UInt64 = 2_500_000_000
|
||||||
|
|
||||||
let nseSuspendDelay: TimeInterval = 2
|
typealias SuspendSchedule = (delay: TimeInterval, timeout: Int)
|
||||||
|
|
||||||
let nseSuspendTimeout: Int = 5
|
let nseSuspendSchedule: SuspendSchedule = (2, 4)
|
||||||
|
|
||||||
|
let fastNSESuspendSchedule: SuspendSchedule = (1, 1)
|
||||||
|
|
||||||
typealias NtfStream = ConcurrentQueue<NSENotification>
|
typealias NtfStream = ConcurrentQueue<NSENotification>
|
||||||
|
|
||||||
@@ -32,7 +34,7 @@ actor PendingNtfs {
|
|||||||
private var ntfStreams: [String: NtfStream] = [:]
|
private var ntfStreams: [String: NtfStream] = [:]
|
||||||
|
|
||||||
func createStream(_ id: String) async {
|
func createStream(_ id: String) async {
|
||||||
logger.debug("NotificationService PendingNtfs.createStream: \(id, privacy: .public)")
|
logger.debug("NotificationService PendingNtfs.createStream: \(id)")
|
||||||
if ntfStreams[id] == nil {
|
if ntfStreams[id] == nil {
|
||||||
ntfStreams[id] = ConcurrentQueue()
|
ntfStreams[id] = ConcurrentQueue()
|
||||||
logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue")
|
logger.debug("NotificationService PendingNtfs.createStream: created ConcurrentQueue")
|
||||||
@@ -40,14 +42,14 @@ actor PendingNtfs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async {
|
func readStream(_ id: String, for nse: NotificationService, ntfInfo: NtfMessages) async {
|
||||||
logger.debug("NotificationService PendingNtfs.readStream: \(id, privacy: .public) \(ntfInfo.ntfMessages.count, privacy: .public)")
|
logger.debug("NotificationService PendingNtfs.readStream: \(id) \(ntfInfo.ntfMessages.count)")
|
||||||
if !ntfInfo.user.showNotifications {
|
if !ntfInfo.user.showNotifications {
|
||||||
nse.setBestAttemptNtf(.empty)
|
nse.setBestAttemptNtf(.empty)
|
||||||
}
|
}
|
||||||
if let s = ntfStreams[id] {
|
if let s = ntfStreams[id] {
|
||||||
logger.debug("NotificationService PendingNtfs.readStream: has stream")
|
logger.debug("NotificationService PendingNtfs.readStream: has stream")
|
||||||
var expected = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
var expected = Set(ntfInfo.ntfMessages.map { $0.msgId })
|
||||||
logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected, privacy: .public)")
|
logger.debug("NotificationService PendingNtfs.readStream: expecting: \(expected)")
|
||||||
var readCancelled = false
|
var readCancelled = false
|
||||||
var dequeued: DequeueElement<NSENotification>?
|
var dequeued: DequeueElement<NSENotification>?
|
||||||
nse.cancelRead = {
|
nse.cancelRead = {
|
||||||
@@ -66,7 +68,7 @@ actor PendingNtfs {
|
|||||||
} else if case let .msgInfo(info) = ntf {
|
} else if case let .msgInfo(info) = ntf {
|
||||||
let found = expected.remove(info.msgId)
|
let found = expected.remove(info.msgId)
|
||||||
if found != nil {
|
if found != nil {
|
||||||
logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty, privacy: .public)")
|
logger.debug("NotificationService PendingNtfs.readStream: msgInfo, last: \(expected.isEmpty)")
|
||||||
if expected.isEmpty { break }
|
if expected.isEmpty { break }
|
||||||
} else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs {
|
} else if let msgTs = ntfInfo.msgTs, info.msgTs > msgTs {
|
||||||
logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo")
|
logger.debug("NotificationService PendingNtfs.readStream: unexpected msgInfo")
|
||||||
@@ -88,7 +90,7 @@ actor PendingNtfs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeStream(_ id: String, _ ntf: NSENotification) async {
|
func writeStream(_ id: String, _ ntf: NSENotification) async {
|
||||||
logger.debug("NotificationService PendingNtfs.writeStream: \(id, privacy: .public)")
|
logger.debug("NotificationService PendingNtfs.writeStream: \(id)")
|
||||||
if let s = ntfStreams[id] {
|
if let s = ntfStreams[id] {
|
||||||
logger.debug("NotificationService PendingNtfs.writeStream: writing ntf")
|
logger.debug("NotificationService PendingNtfs.writeStream: writing ntf")
|
||||||
s.enqueue(ntf)
|
s.enqueue(ntf)
|
||||||
@@ -208,7 +210,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
self.contentHandler = contentHandler
|
self.contentHandler = contentHandler
|
||||||
registerGroupDefaults()
|
registerGroupDefaults()
|
||||||
let appState = appStateGroupDefault.get()
|
let appState = appStateGroupDefault.get()
|
||||||
logger.debug("NotificationService: app is \(appState.rawValue, privacy: .public)")
|
logger.debug("NotificationService: app is \(appState.rawValue)")
|
||||||
switch appState {
|
switch appState {
|
||||||
case .stopped:
|
case .stopped:
|
||||||
setBadgeCount()
|
setBadgeCount()
|
||||||
@@ -238,7 +240,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
logger.debug("NotificationService: app state is now \(state.rawValue, privacy: .public)")
|
logger.debug("NotificationService: app state is now \(state.rawValue)")
|
||||||
if state.inactive {
|
if state.inactive {
|
||||||
receiveNtfMessages(request, contentHandler)
|
receiveNtfMessages(request, contentHandler)
|
||||||
} else {
|
} else {
|
||||||
@@ -267,7 +269,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
let dbStatus = startChat()
|
let dbStatus = startChat()
|
||||||
if case .ok = dbStatus,
|
if case .ok = dbStatus,
|
||||||
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
|
let ntfInfo = apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo) {
|
||||||
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count), privacy: .public)")
|
logger.debug("NotificationService: receiveNtfMessages: apiGetNtfMessage \(String(describing: ntfInfo.ntfMessages.count))")
|
||||||
if let connEntity = ntfInfo.connEntity_ {
|
if let connEntity = ntfInfo.connEntity_ {
|
||||||
setBestAttemptNtf(
|
setBestAttemptNtf(
|
||||||
ntfInfo.ntfsEnabled
|
ntfInfo.ntfsEnabled
|
||||||
@@ -279,7 +281,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
NtfStreamSemaphores.shared.waitForStream(id)
|
NtfStreamSemaphores.shared.waitForStream(id)
|
||||||
if receiveEntityId != nil {
|
if receiveEntityId != nil {
|
||||||
Task {
|
Task {
|
||||||
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id, privacy: .public)")
|
logger.debug("NotificationService: receiveNtfMessages: in Task, connEntity id \(id)")
|
||||||
await PendingNtfs.shared.createStream(id)
|
await PendingNtfs.shared.createStream(id)
|
||||||
await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo)
|
await PendingNtfs.shared.readStream(id, for: self, ntfInfo: ntfInfo)
|
||||||
deliverBestAttemptNtf()
|
deliverBestAttemptNtf()
|
||||||
@@ -297,7 +299,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
override func serviceExtensionTimeWillExpire() {
|
override func serviceExtensionTimeWillExpire() {
|
||||||
logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire")
|
logger.debug("DEBUGGING: NotificationService.serviceExtensionTimeWillExpire")
|
||||||
deliverBestAttemptNtf()
|
deliverBestAttemptNtf(urgent: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setBadgeCount() {
|
func setBadgeCount() {
|
||||||
@@ -319,7 +321,7 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func deliverBestAttemptNtf() {
|
private func deliverBestAttemptNtf(urgent: Bool = false) {
|
||||||
logger.debug("NotificationService.deliverBestAttemptNtf")
|
logger.debug("NotificationService.deliverBestAttemptNtf")
|
||||||
if let cancel = cancelRead {
|
if let cancel = cancelRead {
|
||||||
cancelRead = nil
|
cancelRead = nil
|
||||||
@@ -329,20 +331,55 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
receiveEntityId = nil
|
receiveEntityId = nil
|
||||||
NtfStreamSemaphores.shared.signalStreamReady(id)
|
NtfStreamSemaphores.shared.signalStreamReady(id)
|
||||||
}
|
}
|
||||||
|
let suspend: Bool
|
||||||
if let t = threadId {
|
if let t = threadId {
|
||||||
threadId = nil
|
threadId = nil
|
||||||
if NSEThreads.shared.endThread(t) {
|
suspend = NSEThreads.shared.endThread(t) && NSEThreads.shared.noThreads
|
||||||
logger.debug("NotificationService.deliverBestAttemptNtf: will suspend")
|
} else {
|
||||||
// suspension is delayed to allow chat core finalise any processing
|
suspend = false
|
||||||
// (e.g., send delivery receipts)
|
}
|
||||||
DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendDelay) {
|
deliverCallkitOrNotification(urgent: urgent, suspend: suspend)
|
||||||
if NSEThreads.shared.noThreads {
|
}
|
||||||
logger.debug("NotificationService.deliverBestAttemptNtf: suspending...")
|
|
||||||
suspendChat(nseSuspendTimeout)
|
private func deliverCallkitOrNotification(urgent: Bool, suspend: Bool = false) {
|
||||||
|
if case .callkit = bestAttemptNtf {
|
||||||
|
logger.debug("NotificationService.deliverCallkitOrNotification: will suspend, callkit")
|
||||||
|
if urgent {
|
||||||
|
// suspending NSE even though there may be other notifications
|
||||||
|
// to allow the app to process callkit call
|
||||||
|
suspendChat(0)
|
||||||
|
deliverNotification()
|
||||||
|
} else {
|
||||||
|
// suspending NSE with delay and delivering after the suspension
|
||||||
|
// because pushkit notification must be processed without delay
|
||||||
|
// to avoid app termination
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + fastNSESuspendSchedule.delay) {
|
||||||
|
suspendChat(fastNSESuspendSchedule.timeout)
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + Double(fastNSESuspendSchedule.timeout)) {
|
||||||
|
self.deliverNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
if suspend {
|
||||||
|
logger.debug("NotificationService.deliverCallkitOrNotification: will suspend")
|
||||||
|
if urgent {
|
||||||
|
suspendChat(0)
|
||||||
|
} else {
|
||||||
|
// suspension is delayed to allow chat core finalise any processing
|
||||||
|
// (e.g., send delivery receipts)
|
||||||
|
DispatchQueue.global().asyncAfter(deadline: .now() + nseSuspendSchedule.delay) {
|
||||||
|
if NSEThreads.shared.noThreads {
|
||||||
|
suspendChat(nseSuspendSchedule.timeout)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
deliverNotification()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func deliverNotification() {
|
||||||
if let handler = contentHandler, let ntf = bestAttemptNtf {
|
if let handler = contentHandler, let ntf = bestAttemptNtf {
|
||||||
contentHandler = nil
|
contentHandler = nil
|
||||||
bestAttemptNtf = nil
|
bestAttemptNtf = nil
|
||||||
@@ -357,17 +394,14 @@ class NotificationService: UNNotificationServiceExtension {
|
|||||||
switch ntf {
|
switch ntf {
|
||||||
case let .nse(content): deliver(content)
|
case let .nse(content): deliver(content)
|
||||||
case let .callkit(invitation):
|
case let .callkit(invitation):
|
||||||
|
logger.debug("NotificationService reportNewIncomingVoIPPushPayload for \(invitation.contact.id)")
|
||||||
CXProvider.reportNewIncomingVoIPPushPayload([
|
CXProvider.reportNewIncomingVoIPPushPayload([
|
||||||
"displayName": invitation.contact.displayName,
|
"displayName": invitation.contact.displayName,
|
||||||
"contactId": invitation.contact.id,
|
"contactId": invitation.contact.id,
|
||||||
"media": invitation.callType.media.rawValue
|
"media": invitation.callType.media.rawValue
|
||||||
]) { error in
|
]) { error in
|
||||||
if error == nil {
|
logger.debug("reportNewIncomingVoIPPushPayload result: \(error)")
|
||||||
deliver(nil)
|
deliver(error == nil ? nil : createCallInvitationNtf(invitation))
|
||||||
} else {
|
|
||||||
logger.debug("NotificationService reportNewIncomingVoIPPushPayload success to CallController for \(invitation.contact.id)")
|
|
||||||
deliver(createCallInvitationNtf(invitation))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet
|
case .empty: deliver(nil) // used to mute notifications that did not unsubscribe yet
|
||||||
case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo
|
case .msgInfo: deliver(nil) // unreachable, the best attempt is never set to msgInfo
|
||||||
@@ -402,14 +436,14 @@ var appSubscriber: AppSubscriber = appStateSubscriber { state in
|
|||||||
logger.debug("NotificationService: appSubscriber")
|
logger.debug("NotificationService: appSubscriber")
|
||||||
if state.running && NSEChatState.shared.value.canSuspend {
|
if state.running && NSEChatState.shared.value.canSuspend {
|
||||||
logger.debug("NotificationService: appSubscriber app state \(state.rawValue), suspending")
|
logger.debug("NotificationService: appSubscriber app state \(state.rawValue), suspending")
|
||||||
suspendChat(nseSuspendTimeout)
|
suspendChat(fastNSESuspendSchedule.timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func appStateSubscriber(onState: @escaping (AppState) -> Void) -> AppSubscriber {
|
func appStateSubscriber(onState: @escaping (AppState) -> Void) -> AppSubscriber {
|
||||||
appMessageSubscriber { msg in
|
appMessageSubscriber { msg in
|
||||||
if case let .state(state) = msg {
|
if case let .state(state) = msg {
|
||||||
logger.debug("NotificationService: appStateSubscriber \(state.rawValue, privacy: .public)")
|
logger.debug("NotificationService: appStateSubscriber \(state.rawValue)")
|
||||||
onState(state)
|
onState(state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,24 +459,33 @@ let xftpConfig: XFTPFileConfig? = getXFTPCfg()
|
|||||||
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
|
// Subsequent calls to didReceive will be waiting on semaphore and won't start chat again, as it will be .active
|
||||||
func startChat() -> DBMigrationResult? {
|
func startChat() -> DBMigrationResult? {
|
||||||
logger.debug("NotificationService: startChat")
|
logger.debug("NotificationService: startChat")
|
||||||
if case .active = NSEChatState.shared.value { return .ok }
|
// only skip creating if there is chat controller
|
||||||
|
if case .active = NSEChatState.shared.value, hasChatCtrl() { return .ok }
|
||||||
|
|
||||||
startLock.wait()
|
startLock.wait()
|
||||||
defer { startLock.signal() }
|
defer { startLock.signal() }
|
||||||
|
|
||||||
return switch NSEChatState.shared.value {
|
if hasChatCtrl() {
|
||||||
case .created: doStartChat()
|
return switch NSEChatState.shared.value {
|
||||||
case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock
|
case .created: doStartChat()
|
||||||
case .active: .ok
|
case .starting: .ok // it should never get to this branch, as it would be waiting for start on startLock
|
||||||
case .suspending: activateChat()
|
case .active: .ok
|
||||||
case .suspended: activateChat()
|
case .suspending: activateChat()
|
||||||
|
case .suspended: activateChat()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ignore state in preference if there is no chat controller.
|
||||||
|
// State in preference may have failed to update e.g. because of a crash.
|
||||||
|
NSEChatState.shared.set(.created)
|
||||||
|
return doStartChat()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func doStartChat() -> DBMigrationResult? {
|
func doStartChat() -> DBMigrationResult? {
|
||||||
logger.debug("NotificationService: doStartChat")
|
logger.debug("NotificationService: doStartChat")
|
||||||
hs_init(0, nil)
|
haskell_init_nse()
|
||||||
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
|
let (_, dbStatus) = chatMigrateInit(confirmMigrations: defaultMigrationConfirmation(), backgroundMode: true)
|
||||||
|
logger.debug("NotificationService: doStartChat \(String(describing: dbStatus))")
|
||||||
if dbStatus != .ok {
|
if dbStatus != .ok {
|
||||||
resetChatCtrl()
|
resetChatCtrl()
|
||||||
NSEChatState.shared.set(.created)
|
NSEChatState.shared.set(.created)
|
||||||
@@ -477,7 +520,7 @@ func doStartChat() -> DBMigrationResult? {
|
|||||||
return .ok
|
return .ok
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("NotificationService startChat error: \(responseError(error), privacy: .public)")
|
logger.error("NotificationService startChat error: \(responseError(error))")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("NotificationService: no active user")
|
logger.debug("NotificationService: no active user")
|
||||||
@@ -504,8 +547,10 @@ func suspendChat(_ timeout: Int) {
|
|||||||
logger.debug("NotificationService: suspendChat")
|
logger.debug("NotificationService: suspendChat")
|
||||||
let state = NSEChatState.shared.value
|
let state = NSEChatState.shared.value
|
||||||
if !state.canSuspend {
|
if !state.canSuspend {
|
||||||
logger.error("NotificationService suspendChat called, current state: \(state.rawValue, privacy: .public)")
|
logger.error("NotificationService suspendChat called, current state: \(state.rawValue)")
|
||||||
} else {
|
} else if hasChatCtrl() {
|
||||||
|
// only suspend if we have chat controller to avoid crashes when suspension is
|
||||||
|
// attempted when chat controller was not created
|
||||||
suspendLock.wait()
|
suspendLock.wait()
|
||||||
defer { suspendLock.signal() }
|
defer { suspendLock.signal() }
|
||||||
|
|
||||||
@@ -571,7 +616,7 @@ private let isInChina = SKStorefront().countryCode == "CHN"
|
|||||||
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
|
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
|
||||||
|
|
||||||
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
||||||
logger.debug("NotificationService receivedMsgNtf: \(res.responseType, privacy: .public)")
|
logger.debug("NotificationService receivedMsgNtf: \(res.responseType)")
|
||||||
switch res {
|
switch res {
|
||||||
case let .contactConnected(user, contact, _):
|
case let .contactConnected(user, contact, _):
|
||||||
return (contact.id, .nse(createContactConnectedNtf(user, contact)))
|
return (contact.id, .nse(createContactConnectedNtf(user, contact)))
|
||||||
@@ -613,6 +658,9 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? {
|
|||||||
case .chatSuspended:
|
case .chatSuspended:
|
||||||
chatSuspended()
|
chatSuspended()
|
||||||
return nil
|
return nil
|
||||||
|
case let .chatError(_, err):
|
||||||
|
logger.error("NotificationService receivedMsgNtf error: \(String(describing: err))")
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)")
|
logger.debug("NotificationService receivedMsgNtf ignored event: \(res.responseType)")
|
||||||
return nil
|
return nil
|
||||||
@@ -627,17 +675,22 @@ func updateNetCfg() {
|
|||||||
try setNetworkConfig(networkConfig)
|
try setNetworkConfig(networkConfig)
|
||||||
networkConfig = newNetConfig
|
networkConfig = newNetConfig
|
||||||
} catch {
|
} catch {
|
||||||
logger.error("NotificationService apply changed network config error: \(responseError(error), privacy: .public)")
|
logger.error("NotificationService apply changed network config error: \(responseError(error))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func apiGetActiveUser() -> User? {
|
func apiGetActiveUser() -> User? {
|
||||||
let r = sendSimpleXCmd(.showActiveUser)
|
let r = sendSimpleXCmd(.showActiveUser)
|
||||||
logger.debug("apiGetActiveUser sendSimpleXCmd response: \(String(describing: r))")
|
logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)")
|
||||||
switch r {
|
switch r {
|
||||||
case let .activeUser(user): return user
|
case let .activeUser(user): return user
|
||||||
case .chatCmdError(_, .error(.noActiveUser)): return nil
|
case .chatCmdError(_, .error(.noActiveUser)):
|
||||||
|
logger.debug("apiGetActiveUser sendSimpleXCmd no active user")
|
||||||
|
return nil
|
||||||
|
case let .chatCmdError(_, err):
|
||||||
|
logger.debug("apiGetActiveUser sendSimpleXCmd error: \(String(describing: err))")
|
||||||
|
return nil
|
||||||
default:
|
default:
|
||||||
logger.error("NotificationService apiGetActiveUser unexpected response: \(String(describing: r))")
|
logger.error("NotificationService apiGetActiveUser unexpected response: \(String(describing: r))")
|
||||||
return nil
|
return nil
|
||||||
@@ -645,7 +698,7 @@ func apiGetActiveUser() -> User? {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func apiStartChat() throws -> Bool {
|
func apiStartChat() throws -> Bool {
|
||||||
let r = sendSimpleXCmd(.startChat(subscribe: false, expire: false, xftp: false))
|
let r = sendSimpleXCmd(.startChat(mainApp: false))
|
||||||
switch r {
|
switch r {
|
||||||
case .chatStarted: return true
|
case .chatStarted: return true
|
||||||
case .chatRunning: return false
|
case .chatRunning: return false
|
||||||
@@ -699,11 +752,12 @@ func apiGetNtfMessage(nonce: String, encNtfInfo: String) -> NtfMessages? {
|
|||||||
}
|
}
|
||||||
let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo))
|
let r = sendSimpleXCmd(.apiGetNtfMessage(nonce: nonce, encNtfInfo: encNtfInfo))
|
||||||
if case let .ntfMessages(user, connEntity_, msgTs, ntfMessages) = r, let user = user {
|
if case let .ntfMessages(user, connEntity_, msgTs, ntfMessages) = r, let user = user {
|
||||||
|
logger.debug("apiGetNtfMessage response ntfMessages: \(ntfMessages.count)")
|
||||||
return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessages: ntfMessages)
|
return NtfMessages(user: user, connEntity_: connEntity_, msgTs: msgTs, ntfMessages: ntfMessages)
|
||||||
} else if case let .chatCmdError(_, error) = r {
|
} else if case let .chatCmdError(_, error) = r {
|
||||||
logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))")
|
logger.debug("apiGetNtfMessage error response: \(String.init(describing: error))")
|
||||||
} else {
|
} else {
|
||||||
logger.debug("apiGetNtfMessage ignored response: \(r.responseType, privacy: .public) \(String.init(describing: r), privacy: .private)")
|
logger.debug("apiGetNtfMessage ignored response: \(r.responseType) \(String.init(describing: r))")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,11 +43,6 @@
|
|||||||
5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; };
|
5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D552842B68D00EC8A82 /* IntegrityErrorItemView.swift */; };
|
||||||
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; };
|
5C3F1D58284363C400EC8A82 /* PrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */; };
|
||||||
5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; };
|
5C4B3B0A285FB130003915F2 /* DatabaseView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4B3B09285FB130003915F2 /* DatabaseView.swift */; };
|
||||||
5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D52B3CCD090080FAE2 /* libgmp.a */; };
|
|
||||||
5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D62B3CCD090080FAE2 /* libffi.a */; };
|
|
||||||
5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */; };
|
|
||||||
5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */; };
|
|
||||||
5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */; };
|
|
||||||
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
||||||
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; };
|
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A91E283AD0E400C4E99E /* CallManager.swift */; };
|
||||||
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
|
5C55A921283CCCB700C4E99E /* IncomingCallView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C55A920283CCCB700C4E99E /* IncomingCallView.swift */; };
|
||||||
@@ -121,6 +116,11 @@
|
|||||||
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
5CC2C0FF2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5CC2C0FD2809BF11000C35E3 /* SimpleX--iOS--InfoPlist.strings */; };
|
||||||
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
5CC868F329EB540C0017BBFD /* CIRcvDecryptionError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */; };
|
||||||
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
5CCB939C297EFCB100399E78 /* NavStackCompat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCB939B297EFCB100399E78 /* NavStackCompat.swift */; };
|
||||||
|
5CCD2C322B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD2C2D2B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb-ghc9.6.3.a */; };
|
||||||
|
5CCD2C332B5C29B400F76440 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD2C2E2B5C29B400F76440 /* libffi.a */; };
|
||||||
|
5CCD2C342B5C29B400F76440 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD2C2F2B5C29B400F76440 /* libgmp.a */; };
|
||||||
|
5CCD2C352B5C29B400F76440 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD2C302B5C29B400F76440 /* libgmpxx.a */; };
|
||||||
|
5CCD2C362B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CCD2C312B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb.a */; };
|
||||||
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
5CCD403427A5F6DF00368C90 /* AddContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403327A5F6DF00368C90 /* AddContactView.swift */; };
|
||||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */; };
|
||||||
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
5CD67B8F2B0E858A00C510B1 /* hs_init.h in Headers */ = {isa = PBXBuildFile; fileRef = 5CD67B8D2B0E858A00C510B1 /* hs_init.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||||
@@ -294,11 +294,6 @@
|
|||||||
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = "<group>"; };
|
5C3F1D57284363C400EC8A82 /* PrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrivacySettings.swift; sourceTree = "<group>"; };
|
||||||
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
|
5C422A7C27A9A6FA0097A1E1 /* SimpleX (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "SimpleX (iOS).entitlements"; sourceTree = "<group>"; };
|
||||||
5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = "<group>"; };
|
5C4B3B09285FB130003915F2 /* DatabaseView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseView.swift; sourceTree = "<group>"; };
|
||||||
5C4E80D52B3CCD090080FAE2 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
|
||||||
5C4E80D62B3CCD090080FAE2 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
|
||||||
5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a"; sourceTree = "<group>"; };
|
|
||||||
5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a"; sourceTree = "<group>"; };
|
|
||||||
5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
|
||||||
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
|
5C5346A727B59A6A004DF848 /* ChatHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatHelp.swift; sourceTree = "<group>"; };
|
||||||
5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
|
5C55A91E283AD0E400C4E99E /* CallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallManager.swift; sourceTree = "<group>"; };
|
||||||
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
|
5C55A920283CCCB700C4E99E /* IncomingCallView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncomingCallView.swift; sourceTree = "<group>"; };
|
||||||
@@ -408,6 +403,11 @@
|
|||||||
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
5CC2C0FE2809BF11000C35E3 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = "ru.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = "<group>"; };
|
||||||
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
5CC868F229EB540C0017BBFD /* CIRcvDecryptionError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIRcvDecryptionError.swift; sourceTree = "<group>"; };
|
||||||
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
5CCB939B297EFCB100399E78 /* NavStackCompat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavStackCompat.swift; sourceTree = "<group>"; };
|
||||||
|
5CCD2C2D2B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb-ghc9.6.3.a"; sourceTree = "<group>"; };
|
||||||
|
5CCD2C2E2B5C29B400F76440 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||||
|
5CCD2C2F2B5C29B400F76440 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||||
|
5CCD2C302B5C29B400F76440 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||||
|
5CCD2C312B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb.a"; sourceTree = "<group>"; };
|
||||||
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
5CCD403327A5F6DF00368C90 /* AddContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddContactView.swift; sourceTree = "<group>"; };
|
||||||
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
5CCD403627A5F9A200368C90 /* ScanToConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScanToConnectView.swift; sourceTree = "<group>"; };
|
||||||
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
5CD67B8D2B0E858A00C510B1 /* hs_init.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = hs_init.h; sourceTree = "<group>"; };
|
||||||
@@ -519,13 +519,13 @@
|
|||||||
isa = PBXFrameworksBuildPhase;
|
isa = PBXFrameworksBuildPhase;
|
||||||
buildActionMask = 2147483647;
|
buildActionMask = 2147483647;
|
||||||
files = (
|
files = (
|
||||||
5C4E80DD2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a in Frameworks */,
|
5CCD2C352B5C29B400F76440 /* libgmpxx.a in Frameworks */,
|
||||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||||
5C4E80DA2B3CCD090080FAE2 /* libgmp.a in Frameworks */,
|
5CCD2C342B5C29B400F76440 /* libgmp.a in Frameworks */,
|
||||||
5C4E80DC2B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a in Frameworks */,
|
5CCD2C322B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb-ghc9.6.3.a in Frameworks */,
|
||||||
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
|
||||||
5C4E80DB2B3CCD090080FAE2 /* libffi.a in Frameworks */,
|
5CCD2C362B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb.a in Frameworks */,
|
||||||
5C4E80DE2B3CCD090080FAE2 /* libgmpxx.a in Frameworks */,
|
5CCD2C332B5C29B400F76440 /* libffi.a in Frameworks */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
@@ -587,11 +587,11 @@
|
|||||||
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
5C764E5C279C70B7000C6508 /* Libraries */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5C4E80D62B3CCD090080FAE2 /* libffi.a */,
|
5CCD2C2E2B5C29B400F76440 /* libffi.a */,
|
||||||
5C4E80D52B3CCD090080FAE2 /* libgmp.a */,
|
5CCD2C2F2B5C29B400F76440 /* libgmp.a */,
|
||||||
5C4E80D92B3CCD090080FAE2 /* libgmpxx.a */,
|
5CCD2C302B5C29B400F76440 /* libgmpxx.a */,
|
||||||
5C4E80D82B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5-ghc9.6.3.a */,
|
5CCD2C2D2B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb-ghc9.6.3.a */,
|
||||||
5C4E80D72B3CCD090080FAE2 /* libHSsimplex-chat-5.4.2.1-FP1oxJSttEYhorN1FRfI5.a */,
|
5CCD2C312B5C29B400F76440 /* libHSsimplex-chat-5.4.4.0-F9Am8s51dKw4ZcRcMkpqsb.a */,
|
||||||
);
|
);
|
||||||
path = Libraries;
|
path = Libraries;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -1518,7 +1518,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 186;
|
CURRENT_PROJECT_VERSION = 190;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1540,7 +1540,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 5.4.2;
|
MARKETING_VERSION = 5.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||||
PRODUCT_NAME = SimpleX;
|
PRODUCT_NAME = SimpleX;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -1561,7 +1561,7 @@
|
|||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 186;
|
CURRENT_PROJECT_VERSION = 190;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
ENABLE_PREVIEWS = YES;
|
ENABLE_PREVIEWS = YES;
|
||||||
@@ -1583,7 +1583,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 5.4.2;
|
MARKETING_VERSION = 5.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app;
|
||||||
PRODUCT_NAME = SimpleX;
|
PRODUCT_NAME = SimpleX;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -1642,7 +1642,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 186;
|
CURRENT_PROJECT_VERSION = 190;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1655,7 +1655,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 5.4.2;
|
MARKETING_VERSION = 5.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -1674,7 +1674,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||||
CODE_SIGN_IDENTITY = "Apple Development";
|
CODE_SIGN_IDENTITY = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 186;
|
CURRENT_PROJECT_VERSION = 190;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
ENABLE_BITCODE = NO;
|
ENABLE_BITCODE = NO;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
@@ -1687,7 +1687,7 @@
|
|||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
"@executable_path/../../Frameworks",
|
"@executable_path/../../Frameworks",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 5.4.2;
|
MARKETING_VERSION = 5.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE";
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||||
@@ -1706,7 +1706,7 @@
|
|||||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 186;
|
CURRENT_PROJECT_VERSION = 190;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
@@ -1730,7 +1730,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Libraries/sim",
|
"$(PROJECT_DIR)/Libraries/sim",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 5.4.2;
|
MARKETING_VERSION = 5.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
@@ -1752,7 +1752,7 @@
|
|||||||
APPLICATION_EXTENSION_API_ONLY = YES;
|
APPLICATION_EXTENSION_API_ONLY = YES;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 186;
|
CURRENT_PROJECT_VERSION = 190;
|
||||||
DEFINES_MODULE = YES;
|
DEFINES_MODULE = YES;
|
||||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||||
@@ -1776,7 +1776,7 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"$(PROJECT_DIR)/Libraries/sim",
|
"$(PROJECT_DIR)/Libraries/sim",
|
||||||
);
|
);
|
||||||
MARKETING_VERSION = 5.4.2;
|
MARKETING_VERSION = 5.4.4;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)";
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
|||||||
@@ -41,7 +41,7 @@
|
|||||||
</Testables>
|
</Testables>
|
||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Release"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<Scheme
|
<Scheme
|
||||||
LastUpgradeVersion = "1400"
|
LastUpgradeVersion = "1400"
|
||||||
wasCreatedForAppExtension = "YES"
|
wasCreatedForAppExtension = "YES"
|
||||||
version = "2.0">
|
version = "1.3">
|
||||||
<BuildAction
|
<BuildAction
|
||||||
parallelizeBuildables = "YES"
|
parallelizeBuildables = "YES"
|
||||||
buildImplicitDependencies = "YES">
|
buildImplicitDependencies = "YES">
|
||||||
@@ -47,16 +47,14 @@
|
|||||||
</TestAction>
|
</TestAction>
|
||||||
<LaunchAction
|
<LaunchAction
|
||||||
buildConfiguration = "Debug"
|
buildConfiguration = "Debug"
|
||||||
selectedDebuggerIdentifier = ""
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
selectedLauncherIdentifier = "Xcode.IDEFoundation.Launcher.PosixSpawn"
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
launchStyle = "0"
|
launchStyle = "0"
|
||||||
askForAppToLaunch = "Yes"
|
|
||||||
useCustomWorkingDirectory = "NO"
|
useCustomWorkingDirectory = "NO"
|
||||||
ignoresPersistentStateOnLaunch = "NO"
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
debugDocumentVersioning = "YES"
|
debugDocumentVersioning = "YES"
|
||||||
debugServiceExtension = "internal"
|
debugServiceExtension = "internal"
|
||||||
allowLocationSimulation = "YES"
|
allowLocationSimulation = "YES">
|
||||||
launchAutomaticallySubstyle = "2">
|
|
||||||
<BuildableProductRunnable
|
<BuildableProductRunnable
|
||||||
runnableDebuggingMode = "0">
|
runnableDebuggingMode = "0">
|
||||||
<BuildableReference
|
<BuildableReference
|
||||||
|
|||||||
@@ -12,7 +12,11 @@ private var chatController: chat_ctrl?
|
|||||||
|
|
||||||
private var migrationResult: (Bool, DBMigrationResult)?
|
private var migrationResult: (Bool, DBMigrationResult)?
|
||||||
|
|
||||||
public func getChatCtrl(_ useKey: String? = nil) -> chat_ctrl {
|
public func hasChatCtrl() -> Bool {
|
||||||
|
chatController != nil
|
||||||
|
}
|
||||||
|
|
||||||
|
public func getChatCtrl() -> chat_ctrl {
|
||||||
if let controller = chatController { return controller }
|
if let controller = chatController { return controller }
|
||||||
fatalError("chat controller not initialized")
|
fatalError("chat controller not initialized")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public enum ChatCommand {
|
|||||||
case apiMuteUser(userId: Int64)
|
case apiMuteUser(userId: Int64)
|
||||||
case apiUnmuteUser(userId: Int64)
|
case apiUnmuteUser(userId: Int64)
|
||||||
case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?)
|
case apiDeleteUser(userId: Int64, delSMPQueues: Bool, viewPwd: String?)
|
||||||
case startChat(subscribe: Bool, expire: Bool, xftp: Bool)
|
case startChat(mainApp: Bool)
|
||||||
case apiStopChat
|
case apiStopChat
|
||||||
case apiActivateChat(restoreChat: Bool)
|
case apiActivateChat(restoreChat: Bool)
|
||||||
case apiSuspendChat(timeoutMicroseconds: Int)
|
case apiSuspendChat(timeoutMicroseconds: Int)
|
||||||
@@ -154,7 +154,7 @@ public enum ChatCommand {
|
|||||||
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
case let .apiMuteUser(userId): return "/_mute user \(userId)"
|
||||||
case let .apiUnmuteUser(userId): return "/_unmute user \(userId)"
|
case let .apiUnmuteUser(userId): return "/_unmute user \(userId)"
|
||||||
case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))"
|
case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return "/_delete user \(userId) del_smp=\(onOff(delSMPQueues))\(maybePwd(viewPwd))"
|
||||||
case let .startChat(subscribe, expire, xftp): return "/_start subscribe=\(onOff(subscribe)) expire=\(onOff(expire)) xftp=\(onOff(xftp))"
|
case let .startChat(mainApp): return "/_start main=\(onOff(mainApp))"
|
||||||
case .apiStopChat: return "/_stop"
|
case .apiStopChat: return "/_stop"
|
||||||
case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))"
|
case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))"
|
||||||
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
|
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
|
||||||
|
|||||||
@@ -23,3 +23,19 @@ void haskell_init(void) {
|
|||||||
char **pargv = argv;
|
char **pargv = argv;
|
||||||
hs_init_with_rtsopts(&argc, &pargv);
|
hs_init_with_rtsopts(&argc, &pargv);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void haskell_init_nse(void) {
|
||||||
|
int argc = 7;
|
||||||
|
char *argv[] = {
|
||||||
|
"simplex",
|
||||||
|
"+RTS", // requires `hs_init_with_rtsopts`
|
||||||
|
"-A1m", // chunk size for new allocations
|
||||||
|
"-H1m", // initial heap size
|
||||||
|
"-F0.5", // heap growth triggering GC
|
||||||
|
"-Fd1", // memory return
|
||||||
|
"-c", // compacting garbage collector
|
||||||
|
0
|
||||||
|
};
|
||||||
|
char **pargv = argv;
|
||||||
|
hs_init_with_rtsopts(&argc, &pargv);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,4 +11,6 @@
|
|||||||
|
|
||||||
void haskell_init(void);
|
void haskell_init(void);
|
||||||
|
|
||||||
|
void haskell_init_nse(void);
|
||||||
|
|
||||||
#endif /* hs_init_h */
|
#endif /* hs_init_h */
|
||||||
|
|||||||
@@ -164,13 +164,14 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
androidAppContext = this
|
androidAppContext = this
|
||||||
APPLICATION_ID = BuildConfig.APPLICATION_ID
|
APPLICATION_ID = BuildConfig.APPLICATION_ID
|
||||||
ntfManager = object : chat.simplex.common.platform.NtfManager() {
|
ntfManager = object : chat.simplex.common.platform.NtfManager() {
|
||||||
override fun notifyCallInvitation(invitation: RcvCallInvitation) = NtfManager.notifyCallInvitation(invitation)
|
override fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean = NtfManager.notifyCallInvitation(invitation)
|
||||||
override fun hasNotificationsForChat(chatId: String): Boolean = NtfManager.hasNotificationsForChat(chatId)
|
override fun hasNotificationsForChat(chatId: String): Boolean = NtfManager.hasNotificationsForChat(chatId)
|
||||||
override fun cancelNotificationsForChat(chatId: String) = NtfManager.cancelNotificationsForChat(chatId)
|
override fun cancelNotificationsForChat(chatId: String) = NtfManager.cancelNotificationsForChat(chatId)
|
||||||
override fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List<Pair<NotificationAction, () -> Unit>>) = NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions.map { it.first })
|
override fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List<Pair<NotificationAction, () -> Unit>>) = NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions.map { it.first })
|
||||||
override fun androidCreateNtfChannelsMaybeShowAlert() = NtfManager.createNtfChannelsMaybeShowAlert()
|
override fun androidCreateNtfChannelsMaybeShowAlert() = NtfManager.createNtfChannelsMaybeShowAlert()
|
||||||
override fun cancelCallNotification() = NtfManager.cancelCallNotification()
|
override fun cancelCallNotification() = NtfManager.cancelCallNotification()
|
||||||
override fun cancelAllNotifications() = NtfManager.cancelAllNotifications()
|
override fun cancelAllNotifications() = NtfManager.cancelAllNotifications()
|
||||||
|
override fun showMessage(title: String, text: String) = NtfManager.showMessage(title, text)
|
||||||
}
|
}
|
||||||
platform = object : PlatformInterface {
|
platform = object : PlatformInterface {
|
||||||
override suspend fun androidServiceStart() {
|
override suspend fun androidServiceStart() {
|
||||||
|
|||||||
+39
-5
@@ -30,7 +30,7 @@ object NtfManager {
|
|||||||
const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS"
|
const val ShowChatsAction: String = "chat.simplex.app.SHOW_CHATS"
|
||||||
|
|
||||||
// DO NOT change notification channel settings / names
|
// DO NOT change notification channel settings / names
|
||||||
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_1"
|
const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_2"
|
||||||
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
|
const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL"
|
||||||
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
|
const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL"
|
||||||
const val CallNotificationId: Int = -1
|
const val CallNotificationId: Int = -1
|
||||||
@@ -59,7 +59,7 @@ object NtfManager {
|
|||||||
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
|
||||||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
.setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
|
||||||
.build()
|
.build()
|
||||||
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
|
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/raw/ring_once")
|
||||||
Log.d(TAG, "callNotificationChannel sound: $soundUri")
|
Log.d(TAG, "callNotificationChannel sound: $soundUri")
|
||||||
callChannel.setSound(soundUri, attrs)
|
callChannel.setSound(soundUri, attrs)
|
||||||
callChannel.enableVibration(true)
|
callChannel.enableVibration(true)
|
||||||
@@ -140,7 +140,7 @@ object NtfManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notifyCallInvitation(invitation: RcvCallInvitation) {
|
fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean {
|
||||||
val keyguardManager = getKeyguardManager(context)
|
val keyguardManager = getKeyguardManager(context)
|
||||||
Log.d(
|
Log.d(
|
||||||
TAG,
|
TAG,
|
||||||
@@ -149,7 +149,7 @@ object NtfManager {
|
|||||||
"callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " +
|
"callOnLockScreen ${appPreferences.callOnLockScreen.get()}, " +
|
||||||
"onForeground ${isAppOnForeground}"
|
"onForeground ${isAppOnForeground}"
|
||||||
)
|
)
|
||||||
if (isAppOnForeground) return
|
if (isAppOnForeground) return false
|
||||||
val contactId = invitation.contact.id
|
val contactId = invitation.contact.id
|
||||||
Log.d(TAG, "notifyCallInvitation $contactId")
|
Log.d(TAG, "notifyCallInvitation $contactId")
|
||||||
val image = invitation.contact.image
|
val image = invitation.contact.image
|
||||||
@@ -163,7 +163,7 @@ object NtfManager {
|
|||||||
.setFullScreenIntent(fullScreenPendingIntent, true)
|
.setFullScreenIntent(fullScreenPendingIntent, true)
|
||||||
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
|
||||||
} else {
|
} else {
|
||||||
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/" + R.raw.ring_once)
|
val soundUri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.packageName + "/raw/ring_once")
|
||||||
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, Intent(), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
|
||||||
NotificationCompat.Builder(context, CallChannel)
|
NotificationCompat.Builder(context, CallChannel)
|
||||||
.setContentIntent(chatPendingIntent(OpenChatAction, invitation.user.userId, invitation.contact.id))
|
.setContentIntent(chatPendingIntent(OpenChatAction, invitation.user.userId, invitation.contact.id))
|
||||||
@@ -206,6 +206,39 @@ object NtfManager {
|
|||||||
notify(CallNotificationId, notification)
|
notify(CallNotificationId, notification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showMessage(title: String, text: String) {
|
||||||
|
val builder = NotificationCompat.Builder(context, MessageChannel)
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setContentText(text)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_HIGH)
|
||||||
|
.setGroup(MessageGroup)
|
||||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||||
|
.setSmallIcon(R.drawable.ntf_icon)
|
||||||
|
.setLargeIcon(null)
|
||||||
|
.setColor(0x88FFFF)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
.setVibrate(null)
|
||||||
|
.setContentIntent(chatPendingIntent(ShowChatsAction, null, null))
|
||||||
|
.setSilent(false)
|
||||||
|
|
||||||
|
val summary = NotificationCompat.Builder(context, MessageChannel)
|
||||||
|
.setSmallIcon(R.drawable.ntf_icon)
|
||||||
|
.setColor(0x88FFFF)
|
||||||
|
.setGroup(MessageGroup)
|
||||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||||
|
.setGroupSummary(true)
|
||||||
|
.setContentIntent(chatPendingIntent(ShowChatsAction, null))
|
||||||
|
.build()
|
||||||
|
|
||||||
|
with(NotificationManagerCompat.from(context)) {
|
||||||
|
if (ActivityCompat.checkSelfPermission(SimplexApp.context, android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
notify("MESSAGE".hashCode(), builder.build())
|
||||||
|
notify(0, summary)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelCallNotification() {
|
fun cancelCallNotification() {
|
||||||
@@ -248,6 +281,7 @@ object NtfManager {
|
|||||||
manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(MR.strings.ntf_channel_calls)))
|
manager.createNotificationChannel(callNotificationChannel(CallChannel, generalGetString(MR.strings.ntf_channel_calls)))
|
||||||
// Remove old channels since they can't be edited
|
// Remove old channels since they can't be edited
|
||||||
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION")
|
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION")
|
||||||
|
manager.deleteNotificationChannel("chat.simplex.app.CALL_NOTIFICATION_1")
|
||||||
manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION")
|
manager.deleteNotificationChannel("chat.simplex.app.LOCK_SCREEN_CALL_NOTIFICATION")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
package chat.simplex.common.views.database
|
||||||
|
|
||||||
|
import chat.simplex.common.views.usersettings.restartApp
|
||||||
|
|
||||||
|
actual fun restartChatOrApp() {
|
||||||
|
restartApp()
|
||||||
|
}
|
||||||
+1
-1
@@ -28,7 +28,7 @@ actual fun SettingsSectionApp(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun restartApp() {
|
fun restartApp() {
|
||||||
ProcessPhoenix.triggerRebirth(androidAppContext)
|
ProcessPhoenix.triggerRebirth(androidAppContext)
|
||||||
shutdownApp()
|
shutdownApp()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,6 +122,9 @@ object ChatModel {
|
|||||||
val remoteHostPairing = mutableStateOf<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
|
val remoteHostPairing = mutableStateOf<Pair<RemoteHostInfo?, RemoteHostSessionState>?>(null)
|
||||||
val remoteCtrlSession = mutableStateOf<RemoteCtrlSession?>(null)
|
val remoteCtrlSession = mutableStateOf<RemoteCtrlSession?>(null)
|
||||||
|
|
||||||
|
val processedCriticalError: ProcessedErrors<AgentErrorType.CRITICAL> = ProcessedErrors(60_000)
|
||||||
|
val processedInternalError: ProcessedErrors<AgentErrorType.INTERNAL> = ProcessedErrors(20_000)
|
||||||
|
|
||||||
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
|
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
|
||||||
currentUser.value
|
currentUser.value
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+15
-3
@@ -108,6 +108,7 @@ class AppPreferences {
|
|||||||
val chatArchiveTime = mkDatePreference(SHARED_PREFS_CHAT_ARCHIVE_TIME, null)
|
val chatArchiveTime = mkDatePreference(SHARED_PREFS_CHAT_ARCHIVE_TIME, null)
|
||||||
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
|
val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null)
|
||||||
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false)
|
||||||
|
val showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, false)
|
||||||
val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
|
val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false)
|
||||||
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false)
|
||||||
val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050")
|
val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050")
|
||||||
@@ -276,6 +277,7 @@ class AppPreferences {
|
|||||||
private const val SHARED_PREFS_ONBOARDING_STAGE = "OnboardingStage"
|
private const val SHARED_PREFS_ONBOARDING_STAGE = "OnboardingStage"
|
||||||
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
|
private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart"
|
||||||
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools"
|
||||||
|
private const val SHARED_PREFS_SHOW_INTERNAL_ERRORS = "ShowInternalErrors"
|
||||||
private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
|
private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible"
|
||||||
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy"
|
||||||
private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort"
|
private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort"
|
||||||
@@ -583,7 +585,7 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun apiStartChat(): Boolean {
|
suspend fun apiStartChat(): Boolean {
|
||||||
val r = sendCmd(null, CC.StartChat(expire = true))
|
val r = sendCmd(null, CC.StartChat(mainApp = true))
|
||||||
when (r) {
|
when (r) {
|
||||||
is CR.ChatStarted -> return true
|
is CR.ChatStarted -> return true
|
||||||
is CR.ChatRunning -> return false
|
is CR.ChatRunning -> return false
|
||||||
@@ -1920,6 +1922,14 @@ object ChatController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is CR.ChatCmdError -> when {
|
||||||
|
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> {
|
||||||
|
chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart)
|
||||||
|
}
|
||||||
|
r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.INTERNAL && appPrefs.showInternalErrors.get() -> {
|
||||||
|
chatModel.processedInternalError.newError(r.chatError.agentError, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
else ->
|
else ->
|
||||||
Log.d(TAG , "unsupported event: ${r.responseType}")
|
Log.d(TAG , "unsupported event: ${r.responseType}")
|
||||||
}
|
}
|
||||||
@@ -2161,7 +2171,7 @@ sealed class CC {
|
|||||||
class ApiMuteUser(val userId: Long): CC()
|
class ApiMuteUser(val userId: Long): CC()
|
||||||
class ApiUnmuteUser(val userId: Long): CC()
|
class ApiUnmuteUser(val userId: Long): CC()
|
||||||
class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC()
|
class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC()
|
||||||
class StartChat(val expire: Boolean): CC()
|
class StartChat(val mainApp: Boolean): CC()
|
||||||
class ApiStopChat: CC()
|
class ApiStopChat: CC()
|
||||||
class SetTempFolder(val tempFolder: String): CC()
|
class SetTempFolder(val tempFolder: String): CC()
|
||||||
class SetFilesFolder(val filesFolder: String): CC()
|
class SetFilesFolder(val filesFolder: String): CC()
|
||||||
@@ -2288,7 +2298,7 @@ sealed class CC {
|
|||||||
is ApiMuteUser -> "/_mute user $userId"
|
is ApiMuteUser -> "/_mute user $userId"
|
||||||
is ApiUnmuteUser -> "/_unmute user $userId"
|
is ApiUnmuteUser -> "/_unmute user $userId"
|
||||||
is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}"
|
is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}"
|
||||||
is StartChat -> "/_start subscribe=on expire=${onOff(expire)} xftp=on"
|
is StartChat -> "/_start main=${onOff(mainApp)}"
|
||||||
is ApiStopChat -> "/_stop"
|
is ApiStopChat -> "/_stop"
|
||||||
is SetTempFolder -> "/_temp_folder $tempFolder"
|
is SetTempFolder -> "/_temp_folder $tempFolder"
|
||||||
is SetFilesFolder -> "/_files_folder $filesFolder"
|
is SetFilesFolder -> "/_files_folder $filesFolder"
|
||||||
@@ -4710,6 +4720,7 @@ sealed class AgentErrorType {
|
|||||||
is AGENT -> "AGENT ${agentErr.string}"
|
is AGENT -> "AGENT ${agentErr.string}"
|
||||||
is INTERNAL -> "INTERNAL $internalErr"
|
is INTERNAL -> "INTERNAL $internalErr"
|
||||||
is INACTIVE -> "INACTIVE"
|
is INACTIVE -> "INACTIVE"
|
||||||
|
is CRITICAL -> "CRITICAL $offerRestart $criticalErr"
|
||||||
}
|
}
|
||||||
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType()
|
@Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType()
|
||||||
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
|
@Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType()
|
||||||
@@ -4721,6 +4732,7 @@ sealed class AgentErrorType {
|
|||||||
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
|
@Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType()
|
||||||
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
|
@Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType()
|
||||||
@Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType()
|
@Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType()
|
||||||
|
@Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|||||||
+2
-1
@@ -93,12 +93,13 @@ abstract class NtfManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract fun notifyCallInvitation(invitation: RcvCallInvitation)
|
abstract fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean
|
||||||
abstract fun hasNotificationsForChat(chatId: String): Boolean
|
abstract fun hasNotificationsForChat(chatId: String): Boolean
|
||||||
abstract fun cancelNotificationsForChat(chatId: String)
|
abstract fun cancelNotificationsForChat(chatId: String)
|
||||||
abstract fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<Pair<NotificationAction, () -> Unit>> = emptyList())
|
abstract fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String? = null, actions: List<Pair<NotificationAction, () -> Unit>> = emptyList())
|
||||||
abstract fun cancelCallNotification()
|
abstract fun cancelCallNotification()
|
||||||
abstract fun cancelAllNotifications()
|
abstract fun cancelAllNotifications()
|
||||||
|
abstract fun showMessage(title: String, text: String)
|
||||||
// Android only
|
// Android only
|
||||||
abstract fun androidCreateNtfChannelsMaybeShowAlert()
|
abstract fun androidCreateNtfChannelsMaybeShowAlert()
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -13,8 +13,8 @@ class CallManager(val chatModel: ChatModel) {
|
|||||||
callInvitations[invitation.contact.id] = invitation
|
callInvitations[invitation.contact.id] = invitation
|
||||||
if (invitation.user.showNotifications) {
|
if (invitation.user.showNotifications) {
|
||||||
if (Clock.System.now() - invitation.callTs <= 3.minutes) {
|
if (Clock.System.now() - invitation.callTs <= 3.minutes) {
|
||||||
|
invitation.sentNotification = ntfManager.notifyCallInvitation(invitation)
|
||||||
activeCallInvitation.value = invitation
|
activeCallInvitation.value = invitation
|
||||||
ntfManager.notifyCallInvitation(invitation)
|
|
||||||
} else {
|
} else {
|
||||||
val contact = invitation.contact
|
val contact = invitation.contact
|
||||||
ntfManager.displayNotification(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText)
|
ntfManager.displayNotification(user = invitation.user, chatId = contact.id, displayName = contact.displayName, msgText = invitation.callTypeText)
|
||||||
|
|||||||
+6
-3
@@ -15,11 +15,10 @@ import dev.icerock.moko.resources.compose.painterResource
|
|||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.ProfileImage
|
import chat.simplex.common.views.helpers.ProfileImage
|
||||||
import chat.simplex.common.views.usersettings.ProfilePreview
|
import chat.simplex.common.views.usersettings.ProfilePreview
|
||||||
import chat.simplex.common.platform.ntfManager
|
|
||||||
import chat.simplex.common.platform.SoundPlayer
|
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
|
|
||||||
@@ -27,7 +26,11 @@ import kotlinx.datetime.Clock
|
|||||||
fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
|
fun IncomingCallAlertView(invitation: RcvCallInvitation, chatModel: ChatModel) {
|
||||||
val cm = chatModel.callManager
|
val cm = chatModel.callManager
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
LaunchedEffect(true) { SoundPlayer.start(scope, sound = !chatModel.showCallView.value) }
|
LaunchedEffect(Unit) {
|
||||||
|
if (chatModel.activeCallInvitation.value?.sentNotification == false || appPlatform.isDesktop) {
|
||||||
|
SoundPlayer.start(scope, sound = !chatModel.showCallView.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
DisposableEffect(true) { onDispose { SoundPlayer.stop() } }
|
DisposableEffect(true) { onDispose { SoundPlayer.stop() } }
|
||||||
IncomingCallAlertLayout(
|
IncomingCallAlertLayout(
|
||||||
invitation,
|
invitation,
|
||||||
|
|||||||
+3
@@ -112,6 +112,9 @@ sealed class WCallResponse {
|
|||||||
CallMediaType.Video -> MR.strings.incoming_video_call
|
CallMediaType.Video -> MR.strings.incoming_video_call
|
||||||
CallMediaType.Audio -> MR.strings.incoming_audio_call
|
CallMediaType.Audio -> MR.strings.incoming_audio_call
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// Shows whether notification was shown or not to prevent playing sound twice in both notification and in-app
|
||||||
|
var sentNotification: Boolean = false
|
||||||
}
|
}
|
||||||
@Serializable data class CallCapabilities(val encryption: Boolean)
|
@Serializable data class CallCapabilities(val encryption: Boolean)
|
||||||
@Serializable data class ConnectionInfo(private val localCandidate: RTCIceCandidate?, private val remoteCandidate: RTCIceCandidate?) {
|
@Serializable data class ConnectionInfo(private val localCandidate: RTCIceCandidate?, private val remoteCandidate: RTCIceCandidate?) {
|
||||||
|
|||||||
+8
-6
@@ -4,7 +4,6 @@ import SectionBottomSpacer
|
|||||||
import SectionDividerSpaced
|
import SectionDividerSpaced
|
||||||
import SectionTextFooter
|
import SectionTextFooter
|
||||||
import SectionItemView
|
import SectionItemView
|
||||||
import SectionSpacer
|
|
||||||
import SectionView
|
import SectionView
|
||||||
import androidx.compose.desktop.ui.tooling.preview.Preview
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
@@ -367,7 +366,7 @@ fun chatArchiveTitle(chatArchiveTime: Instant, chatLastStart: Instant): String {
|
|||||||
return stringResource(if (chatArchiveTime < chatLastStart) MR.strings.old_database_archive else MR.strings.new_database_archive)
|
return stringResource(if (chatArchiveTime < chatLastStart) MR.strings.old_database_archive else MR.strings.new_database_archive)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) {
|
fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged: MutableState<Boolean>) {
|
||||||
withApi {
|
withApi {
|
||||||
try {
|
try {
|
||||||
if (chatDbChanged.value) {
|
if (chatDbChanged.value) {
|
||||||
@@ -407,6 +406,8 @@ private fun stopChatAlert(m: ChatModel) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
expect fun restartChatOrApp()
|
||||||
|
|
||||||
private fun exportProhibitedAlert() {
|
private fun exportProhibitedAlert() {
|
||||||
AlertManager.shared.showAlertMsg(
|
AlertManager.shared.showAlertMsg(
|
||||||
title = generalGetString(MR.strings.set_password_to_export),
|
title = generalGetString(MR.strings.set_password_to_export),
|
||||||
@@ -414,7 +415,7 @@ private fun exportProhibitedAlert() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun authStopChat(m: ChatModel) {
|
fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
|
||||||
if (m.controller.appPrefs.performLA.get()) {
|
if (m.controller.appPrefs.performLA.get()) {
|
||||||
authenticate(
|
authenticate(
|
||||||
generalGetString(MR.strings.auth_stop_chat),
|
generalGetString(MR.strings.auth_stop_chat),
|
||||||
@@ -422,7 +423,7 @@ private fun authStopChat(m: ChatModel) {
|
|||||||
completed = { laResult ->
|
completed = { laResult ->
|
||||||
when (laResult) {
|
when (laResult) {
|
||||||
LAResult.Success, is LAResult.Unavailable -> {
|
LAResult.Success, is LAResult.Unavailable -> {
|
||||||
stopChat(m)
|
stopChat(m, onStop)
|
||||||
}
|
}
|
||||||
is LAResult.Error -> {
|
is LAResult.Error -> {
|
||||||
m.chatRunning.value = true
|
m.chatRunning.value = true
|
||||||
@@ -434,15 +435,16 @@ private fun authStopChat(m: ChatModel) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
stopChat(m)
|
stopChat(m, onStop)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun stopChat(m: ChatModel) {
|
private fun stopChat(m: ChatModel, onStop: (() -> Unit)? = null) {
|
||||||
withApi {
|
withApi {
|
||||||
try {
|
try {
|
||||||
stopChatAsync(m)
|
stopChatAsync(m)
|
||||||
platform.androidChatStopped()
|
platform.androidChatStopped()
|
||||||
|
onStop?.invoke()
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
m.chatRunning.value = true
|
m.chatRunning.value = true
|
||||||
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString())
|
AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString())
|
||||||
|
|||||||
+64
@@ -0,0 +1,64 @@
|
|||||||
|
package chat.simplex.common.views.helpers
|
||||||
|
|
||||||
|
import chat.simplex.common.model.AgentErrorType
|
||||||
|
import chat.simplex.common.platform.Log
|
||||||
|
import chat.simplex.common.platform.TAG
|
||||||
|
import chat.simplex.common.platform.ntfManager
|
||||||
|
import chat.simplex.common.views.database.restartChatOrApp
|
||||||
|
import chat.simplex.res.MR
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
|
class ProcessedErrors <T: AgentErrorType>(val interval: Long) {
|
||||||
|
private var lastShownTimestamp: Long = -1
|
||||||
|
private var lastShownOfferRestart: Boolean = false
|
||||||
|
private var timer: Job = Job()
|
||||||
|
|
||||||
|
fun newError(error: T, offerRestart: Boolean) {
|
||||||
|
timer.cancel()
|
||||||
|
timer = withBGApi {
|
||||||
|
val delayBeforeNext = (lastShownTimestamp + interval) - System.currentTimeMillis()
|
||||||
|
if ((lastShownOfferRestart || !offerRestart) && delayBeforeNext >= 0) {
|
||||||
|
delay(delayBeforeNext)
|
||||||
|
}
|
||||||
|
lastShownTimestamp = System.currentTimeMillis()
|
||||||
|
lastShownOfferRestart = offerRestart
|
||||||
|
AlertManager.shared.hideAllAlerts()
|
||||||
|
showMessage(error, offerRestart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showMessage(error: T, offerRestart: Boolean) {
|
||||||
|
when (error) {
|
||||||
|
is AgentErrorType.CRITICAL -> {
|
||||||
|
val title = generalGetString(MR.strings.agent_critical_error_title)
|
||||||
|
val text = generalGetString(MR.strings.agent_critical_error_desc).format(error.criticalErr)
|
||||||
|
try {
|
||||||
|
ntfManager.showMessage(title, text)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, e.stackTraceToString())
|
||||||
|
}
|
||||||
|
if (offerRestart) {
|
||||||
|
AlertManager.shared.showAlertDialog(
|
||||||
|
title = title,
|
||||||
|
text = text,
|
||||||
|
confirmText = generalGetString(MR.strings.restart_chat_button),
|
||||||
|
onConfirm = {
|
||||||
|
withApi { restartChatOrApp() }
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
title = title,
|
||||||
|
text = text,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is AgentErrorType.INTERNAL -> {
|
||||||
|
AlertManager.shared.showAlertMsg(
|
||||||
|
title = generalGetString(MR.strings.agent_internal_error_title),
|
||||||
|
text = generalGetString(MR.strings.agent_internal_error_desc).format(error.internalErr),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+3
-1
@@ -10,10 +10,11 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalUriHandler
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
|
import chat.simplex.common.model.*
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import dev.icerock.moko.resources.compose.stringResource
|
import dev.icerock.moko.resources.compose.stringResource
|
||||||
import chat.simplex.common.model.ChatModel
|
|
||||||
import chat.simplex.common.platform.appPlatform
|
import chat.simplex.common.platform.appPlatform
|
||||||
|
import chat.simplex.common.platform.appPreferences
|
||||||
import chat.simplex.common.views.TerminalView
|
import chat.simplex.common.views.TerminalView
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
@@ -44,6 +45,7 @@ fun DeveloperView(
|
|||||||
m.controller.appPrefs.terminalAlwaysVisible.set(false)
|
m.controller.appPrefs.terminalAlwaysVisible.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
SettingsPreferenceItem(painterResource(MR.images.ic_report), stringResource(MR.strings.show_internal_errors), appPreferences.showInternalErrors)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SectionTextFooter(
|
SectionTextFooter(
|
||||||
|
|||||||
@@ -662,6 +662,7 @@
|
|||||||
<string name="hide_dev_options">Hide:</string>
|
<string name="hide_dev_options">Hide:</string>
|
||||||
<string name="show_developer_options">Show developer options</string>
|
<string name="show_developer_options">Show developer options</string>
|
||||||
<string name="developer_options">Database IDs and Transport isolation option.</string>
|
<string name="developer_options">Database IDs and Transport isolation option.</string>
|
||||||
|
<string name="show_internal_errors">Show internal errors</string>
|
||||||
<string name="shutdown_alert_question">Shutdown?</string>
|
<string name="shutdown_alert_question">Shutdown?</string>
|
||||||
<string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</string>
|
<string name="shutdown_alert_desc">Notifications will stop working until you re-launch the app</string>
|
||||||
|
|
||||||
@@ -1724,4 +1725,11 @@
|
|||||||
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link.</string>
|
<string name="connect_plan_you_are_already_joining_the_group_via_this_link">You are already joining the group via this link.</string>
|
||||||
<string name="connect_plan_you_are_already_in_group_vName">You are already in group %1$s.</string>
|
<string name="connect_plan_you_are_already_in_group_vName">You are already in group %1$s.</string>
|
||||||
<string name="connect_plan_connect_via_link">Connect via link?</string>
|
<string name="connect_plan_connect_via_link">Connect via link?</string>
|
||||||
|
|
||||||
|
<!-- Errors -->
|
||||||
|
<string name="agent_critical_error_title">Critical error</string>
|
||||||
|
<string name="agent_critical_error_desc">Please report it to the developers: \n%s\n\nIt is recommended to restart the app.</string>
|
||||||
|
<string name="agent_internal_error_title">Internal error</string>
|
||||||
|
<string name="agent_internal_error_desc">Please report it to the developers: \n%s</string>
|
||||||
|
<string name="restart_chat_button">Restart chat</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M479.895-284Q494-284 504-293.895q10-9.894 10-24Q514-332 504.105-342q-9.894-10-24-10Q466-352 456-342.105q-10 9.894-10 24Q446-304 455.895-294q9.894 10 24 10ZM451.5-425H509v-261h-57.5v261ZM332-124.5 124.5-332.176V-628l207.676-207.5H628l207.5 207.676V-332L627.824-124.5H332Zm24.222-57.5h248.243L778-356.222v-248.243L604.242-778H356L182-604.242V-356l174.222 174ZM480-480Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 472 B |
+7
-2
@@ -16,8 +16,8 @@ import javax.imageio.ImageIO
|
|||||||
object NtfManager {
|
object NtfManager {
|
||||||
private val prevNtfs = arrayListOf<Pair<ChatId, Slice>>()
|
private val prevNtfs = arrayListOf<Pair<ChatId, Slice>>()
|
||||||
|
|
||||||
fun notifyCallInvitation(invitation: RcvCallInvitation) {
|
fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean {
|
||||||
if (simplexWindowState.windowFocused.value) return
|
if (simplexWindowState.windowFocused.value) return false
|
||||||
val contactId = invitation.contact.id
|
val contactId = invitation.contact.id
|
||||||
Log.d(TAG, "notifyCallInvitation $contactId")
|
Log.d(TAG, "notifyCallInvitation $contactId")
|
||||||
val image = invitation.contact.image
|
val image = invitation.contact.image
|
||||||
@@ -45,6 +45,11 @@ object NtfManager {
|
|||||||
displayNotificationViaLib(contactId, title, text, prepareIconPath(largeIcon), actions) {
|
displayNotificationViaLib(contactId, title, text, prepareIconPath(largeIcon), actions) {
|
||||||
ntfManager.openChatAction(invitation.user.userId, contactId)
|
ntfManager.openChatAction(invitation.user.userId, contactId)
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showMessage(title: String, text: String) {
|
||||||
|
displayNotificationViaLib("MESSAGE", title, text, null, emptyList()) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun hasNotificationsForChat(chatId: ChatId) = false//prevNtfs.any { it.first == chatId }
|
fun hasNotificationsForChat(chatId: ChatId) = false//prevNtfs.any { it.first == chatId }
|
||||||
|
|||||||
+2
-1
@@ -16,13 +16,14 @@ val defaultLocale: Locale = Locale.getDefault()
|
|||||||
|
|
||||||
fun initApp() {
|
fun initApp() {
|
||||||
ntfManager = object : NtfManager() {
|
ntfManager = object : NtfManager() {
|
||||||
override fun notifyCallInvitation(invitation: RcvCallInvitation) = chat.simplex.common.model.NtfManager.notifyCallInvitation(invitation)
|
override fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean = chat.simplex.common.model.NtfManager.notifyCallInvitation(invitation)
|
||||||
override fun hasNotificationsForChat(chatId: String): Boolean = chat.simplex.common.model.NtfManager.hasNotificationsForChat(chatId)
|
override fun hasNotificationsForChat(chatId: String): Boolean = chat.simplex.common.model.NtfManager.hasNotificationsForChat(chatId)
|
||||||
override fun cancelNotificationsForChat(chatId: String) = chat.simplex.common.model.NtfManager.cancelNotificationsForChat(chatId)
|
override fun cancelNotificationsForChat(chatId: String) = chat.simplex.common.model.NtfManager.cancelNotificationsForChat(chatId)
|
||||||
override fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List<Pair<NotificationAction, () -> Unit>>) = chat.simplex.common.model.NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions)
|
override fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List<Pair<NotificationAction, () -> Unit>>) = chat.simplex.common.model.NtfManager.displayNotification(user, chatId, displayName, msgText, image, actions)
|
||||||
override fun androidCreateNtfChannelsMaybeShowAlert() {}
|
override fun androidCreateNtfChannelsMaybeShowAlert() {}
|
||||||
override fun cancelCallNotification() {}
|
override fun cancelCallNotification() {}
|
||||||
override fun cancelAllNotifications() = chat.simplex.common.model.NtfManager.cancelAllNotifications()
|
override fun cancelAllNotifications() = chat.simplex.common.model.NtfManager.cancelAllNotifications()
|
||||||
|
override fun showMessage(title: String, text: String) = chat.simplex.common.model.NtfManager.showMessage(title, text)
|
||||||
}
|
}
|
||||||
applyAppLocale()
|
applyAppLocale()
|
||||||
withBGApi {
|
withBGApi {
|
||||||
|
|||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
package chat.simplex.common.views.database
|
||||||
|
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import chat.simplex.common.platform.chatModel
|
||||||
|
import chat.simplex.common.views.helpers.withApi
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.datetime.Instant
|
||||||
|
|
||||||
|
actual fun restartChatOrApp() {
|
||||||
|
if (chatModel.chatRunning.value == false) {
|
||||||
|
chatModel.chatDbChanged.value = true
|
||||||
|
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
|
||||||
|
} else {
|
||||||
|
authStopChat(chatModel) {
|
||||||
|
withApi {
|
||||||
|
// adding delay in order to prevent locked database by previous initialization
|
||||||
|
delay(1000)
|
||||||
|
chatModel.chatDbChanged.value = true
|
||||||
|
startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,11 +25,11 @@ android.nonTransitiveRClass=true
|
|||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
kotlin.mpp.androidSourceSetLayoutVersion=2
|
kotlin.mpp.androidSourceSetLayoutVersion=2
|
||||||
|
|
||||||
android.version_name=5.4.2
|
android.version_name=5.4.4
|
||||||
android.version_code=166
|
android.version_code=172
|
||||||
|
|
||||||
desktop.version_name=5.4.2
|
desktop.version_name=5.4.4
|
||||||
desktop.version_code=20
|
desktop.version_code=24
|
||||||
|
|
||||||
kotlin.version=1.8.20
|
kotlin.version=1.8.20
|
||||||
gradle.plugin.version=7.4.2
|
gradle.plugin.version=7.4.2
|
||||||
|
|||||||
+1
-1
@@ -14,7 +14,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
|||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
location: https://github.com/simplex-chat/simplexmq.git
|
location: https://github.com/simplex-chat/simplexmq.git
|
||||||
tag: d0588bd0ac23a459cbfc9a4789633014e91ffa19
|
tag: f6ed4640d407f8879273d104a3e69069806dcb7c
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 5.4.2.1
|
version: 5.4.4.0
|
||||||
#synopsis:
|
#synopsis:
|
||||||
#description:
|
#description:
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ u="$USER"
|
|||||||
tmp="$(mktemp -d -t)"
|
tmp="$(mktemp -d -t)"
|
||||||
folder="$tmp/simplex-chat"
|
folder="$tmp/simplex-chat"
|
||||||
|
|
||||||
nix_ver="nix-2.15.1"
|
nix_ver="nix-2.19.2"
|
||||||
nix_url="https://releases.nixos.org/nix/$nix_ver/install"
|
nix_url="https://releases.nixos.org/nix/$nix_ver/install"
|
||||||
nix_hash="67aa37f0115195d8ddf32b5d6f471f1e60ecca0fdb3e98bcf54bc147c3078640"
|
nix_hash="435f0d7e11f7c7dffeeab0ec9cc55723f6d3c03352379d785633cf4ddb5caf90"
|
||||||
nix_config="sandbox = true
|
nix_config="sandbox = true
|
||||||
max-jobs = auto
|
max-jobs = auto
|
||||||
experimental-features = nix-command flakes"
|
experimental-features = nix-command flakes"
|
||||||
@@ -102,8 +102,19 @@ build() {
|
|||||||
sed -i.bak '/android {/a lint {abortOnError = false}' "$folder/apps/multiplatform/android/build.gradle.kts"
|
sed -i.bak '/android {/a lint {abortOnError = false}' "$folder/apps/multiplatform/android/build.gradle.kts"
|
||||||
|
|
||||||
for arch in $arches; do
|
for arch in $arches; do
|
||||||
android_simplex_lib="${folder}#hydraJobs.${arch}-android:lib:simplex-chat.x86_64-linux"
|
|
||||||
android_support_lib="${folder}#hydraJobs.${arch}-android:lib:support.x86_64-linux"
|
tag_full="$(git tag --points-at HEAD)"
|
||||||
|
tag_version="${tag_full%%-*}"
|
||||||
|
|
||||||
|
if [ "$arch" = "armv7a" ] && [ -n "$tag_full" ] ; then
|
||||||
|
git checkout "${tag_version}-armv7a"
|
||||||
|
android_simplex_lib="${folder}#hydraJobs.${arch}-android:lib:simplex-chat.x86_64-linux"
|
||||||
|
android_support_lib="${folder}#hydraJobs.${arch}-android:lib:support.x86_64-linux"
|
||||||
|
else
|
||||||
|
android_simplex_lib="${folder}#hydraJobs.x86_64-linux.${arch}-android:lib:simplex-chat"
|
||||||
|
android_support_lib="${folder}#hydraJobs.x86_64-linux.${arch}-android:lib:support"
|
||||||
|
fi
|
||||||
|
|
||||||
android_simplex_lib_output="${PWD}/result/pkg-${arch}-android-libsimplex.zip"
|
android_simplex_lib_output="${PWD}/result/pkg-${arch}-android-libsimplex.zip"
|
||||||
android_support_lib_output="${PWD}/result/pkg-${arch}-android-libsupport.zip"
|
android_support_lib_output="${PWD}/result/pkg-${arch}-android-libsupport.zip"
|
||||||
|
|
||||||
@@ -139,6 +150,10 @@ build() {
|
|||||||
zipalign -p -f 4 "$tmp/$android_apk_output_final" "$PWD/$android_apk_output_final"
|
zipalign -p -f 4 "$tmp/$android_apk_output_final" "$PWD/$android_apk_output_final"
|
||||||
|
|
||||||
rm -rf "$libs_folder/$android_arch"
|
rm -rf "$libs_folder/$android_arch"
|
||||||
|
|
||||||
|
if [ "$arch" = "armv7a" ] && [ -n "$tag_full" ] ; then
|
||||||
|
git checkout "${tag_full}"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -37,12 +37,12 @@ for ((i = 0 ; i < ${#arches[@]}; i++)); do
|
|||||||
|
|
||||||
mkdir -p "$output_dir" 2> /dev/null
|
mkdir -p "$output_dir" 2> /dev/null
|
||||||
|
|
||||||
curl --location -o libsupport.zip $job_repo/$arch-android:lib:support.x86_64-linux/latest/download/1 && \
|
curl --tlsv1.2 --location -o libsupport.zip $job_repo/$arch-android:lib:support.x86_64-linux/latest/download/1 && \
|
||||||
unzip -o libsupport.zip && \
|
unzip -o libsupport.zip && \
|
||||||
mv libsupport.so "$output_dir" && \
|
mv libsupport.so "$output_dir" && \
|
||||||
rm libsupport.zip
|
rm libsupport.zip
|
||||||
|
|
||||||
curl --location -o libsimplex.zip "$job_repo"/"$arch"-android:lib:simplex-chat.x86_64-linux/latest/download/1 && \
|
curl --tlsv1.2 --location -o libsimplex.zip "$job_repo"/"$arch"-android:lib:simplex-chat.x86_64-linux/latest/download/1 && \
|
||||||
unzip -o libsimplex.zip && \
|
unzip -o libsimplex.zip && \
|
||||||
mv libsimplex.so "$output_dir" && \
|
mv libsimplex.so "$output_dir" && \
|
||||||
rm libsimplex.zip
|
rm libsimplex.zip
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ mkdir deps 2> /dev/null || true
|
|||||||
cp /tmp/libffi-3.4.4/*-apple-darwin*/.libs/libffi.dylib $BUILD/deps || \
|
cp /tmp/libffi-3.4.4/*-apple-darwin*/.libs/libffi.dylib $BUILD/deps || \
|
||||||
( \
|
( \
|
||||||
cd /tmp && \
|
cd /tmp && \
|
||||||
curl "https://gitlab.haskell.org/ghc/libffi-tarballs/-/raw/libffi-3.4.4/libffi-3.4.4.tar.gz?inline=false" -o libffi.tar.gz && \
|
curl --tlsv1.2 "https://gitlab.haskell.org/ghc/libffi-tarballs/-/raw/libffi-3.4.4/libffi-3.4.4.tar.gz?inline=false" -o libffi.tar.gz && \
|
||||||
tar -xzvf libffi.tar.gz && \
|
tar -xzvf libffi.tar.gz && \
|
||||||
cd "libffi-3.4.4" && \
|
cd "libffi-3.4.4" && \
|
||||||
./configure && \
|
./configure && \
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ cp *imple*.desktop usr/share/applications/
|
|||||||
cp $multiplatform_dir/desktop/src/jvmMain/resources/distribute/*.appdata.xml usr/share/metainfo
|
cp $multiplatform_dir/desktop/src/jvmMain/resources/distribute/*.appdata.xml usr/share/metainfo
|
||||||
|
|
||||||
if [ ! -f ../appimagetool-x86_64.AppImage ]; then
|
if [ ! -f ../appimagetool-x86_64.AppImage ]; then
|
||||||
wget https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O ../appimagetool-x86_64.AppImage
|
wget --secure-protocol=TLSv1_3 https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-x86_64.AppImage -O ../appimagetool-x86_64.AppImage
|
||||||
chmod +x ../appimagetool-x86_64.AppImage
|
chmod +x ../appimagetool-x86_64.AppImage
|
||||||
fi
|
fi
|
||||||
../appimagetool-x86_64.AppImage .
|
../appimagetool-x86_64.AppImage .
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ cd $root_dir
|
|||||||
if [ ! -f dist-newstyle/openssl-1.1.1w/libcrypto-1_1-x64.dll ]; then
|
if [ ! -f dist-newstyle/openssl-1.1.1w/libcrypto-1_1-x64.dll ]; then
|
||||||
mkdir dist-newstyle 2>/dev/null || true
|
mkdir dist-newstyle 2>/dev/null || true
|
||||||
cd dist-newstyle
|
cd dist-newstyle
|
||||||
curl https://www.openssl.org/source/openssl-1.1.1w.tar.gz -o openssl.tar.gz
|
curl --tlsv1.2 https://www.openssl.org/source/openssl-1.1.1w.tar.gz -o openssl.tar.gz
|
||||||
$WINDIR\\System32\\tar.exe -xvzf openssl.tar.gz
|
$WINDIR\\System32\\tar.exe -xvzf openssl.tar.gz
|
||||||
cd openssl-1.1.1w
|
cd openssl-1.1.1w
|
||||||
./Configure mingw64
|
./Configure mingw64
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ mkdir $vlc_dir || exit 0
|
|||||||
cd /tmp
|
cd /tmp
|
||||||
mkdir tmp 2>/dev/null || true
|
mkdir tmp 2>/dev/null || true
|
||||||
cd tmp
|
cd tmp
|
||||||
curl https://github.com/cmatomic/VLCplayer-AppImage/releases/download/3.0.11.1/VLC_media_player-3.0.11.1-x86_64.AppImage -L -o appimage
|
curl --tlsv1.2 https://github.com/cmatomic/VLCplayer-AppImage/releases/download/3.0.11.1/VLC_media_player-3.0.11.1-x86_64.AppImage -L -o appimage
|
||||||
chmod +x appimage
|
chmod +x appimage
|
||||||
./appimage --appimage-extract
|
./appimage --appimage-extract
|
||||||
cp -r squashfs-root/usr/lib/* $vlc_dir
|
cp -r squashfs-root/usr/lib/* $vlc_dir
|
||||||
@@ -28,7 +28,7 @@ cd /tmp
|
|||||||
(
|
(
|
||||||
mkdir tmp
|
mkdir tmp
|
||||||
cd tmp
|
cd tmp
|
||||||
curl http://archive.ubuntu.com/ubuntu/pool/universe/v/vlc/libvlc5_3.0.9.2-1_amd64.deb -o libvlc
|
curl --tlsv1.2 https://archive.ubuntu.com/ubuntu/pool/universe/v/vlc/libvlc5_3.0.9.2-1_amd64.deb -o libvlc
|
||||||
ar p libvlc data.tar.xz > data.tar.xz
|
ar p libvlc data.tar.xz > data.tar.xz
|
||||||
tar -xvf data.tar.xz
|
tar -xvf data.tar.xz
|
||||||
mv usr/lib/x86_64-linux-gnu/libvlc.so{.5,}
|
mv usr/lib/x86_64-linux-gnu/libvlc.so{.5,}
|
||||||
@@ -40,7 +40,7 @@ rm -rf tmp
|
|||||||
(
|
(
|
||||||
mkdir tmp
|
mkdir tmp
|
||||||
cd tmp
|
cd tmp
|
||||||
curl http://archive.ubuntu.com/ubuntu/pool/universe/v/vlc/libvlccore9_3.0.9.2-1_amd64.deb -o libvlccore
|
curl --tlsv1.2 https://archive.ubuntu.com/ubuntu/pool/universe/v/vlc/libvlccore9_3.0.9.2-1_amd64.deb -o libvlccore
|
||||||
ar p libvlccore data.tar.xz > data.tar.xz
|
ar p libvlccore data.tar.xz > data.tar.xz
|
||||||
tar -xvf data.tar.xz
|
tar -xvf data.tar.xz
|
||||||
cp usr/lib/x86_64-linux-gnu/libvlccore.so* $vlc_dir
|
cp usr/lib/x86_64-linux-gnu/libvlccore.so* $vlc_dir
|
||||||
@@ -51,7 +51,7 @@ rm -rf tmp
|
|||||||
(
|
(
|
||||||
mkdir tmp
|
mkdir tmp
|
||||||
cd tmp
|
cd tmp
|
||||||
curl http://mirrors.edge.kernel.org/ubuntu/pool/universe/v/vlc/vlc-plugin-base_3.0.9.2-1_amd64.deb -o plugins
|
curl --tlsv1.2 https://mirrors.edge.kernel.org/ubuntu/pool/universe/v/vlc/vlc-plugin-base_3.0.9.2-1_amd64.deb -o plugins
|
||||||
ar p plugins data.tar.xz > data.tar.xz
|
ar p plugins data.tar.xz > data.tar.xz
|
||||||
tar -xvf data.tar.xz
|
tar -xvf data.tar.xz
|
||||||
find usr/lib/x86_64-linux-gnu/vlc/plugins/ -name "lib*.so*" -exec patchelf --set-rpath '$ORIGIN/../../' {} \;
|
find usr/lib/x86_64-linux-gnu/vlc/plugins/ -name "lib*.so*" -exec patchelf --set-rpath '$ORIGIN/../../' {} \;
|
||||||
@@ -63,7 +63,7 @@ rm -rf tmp
|
|||||||
(
|
(
|
||||||
mkdir tmp
|
mkdir tmp
|
||||||
cd tmp
|
cd tmp
|
||||||
curl http://archive.ubuntu.com/ubuntu/pool/main/libi/libidn/libidn11_1.33-2.2ubuntu2_amd64.deb -o idn
|
curl --tlsv1.2 https://archive.ubuntu.com/ubuntu/pool/main/libi/libidn/libidn11_1.33-2.2ubuntu2_amd64.deb -o idn
|
||||||
ar p idn data.tar.xz > data.tar.xz
|
ar p idn data.tar.xz > data.tar.xz
|
||||||
tar -xvf data.tar.xz
|
tar -xvf data.tar.xz
|
||||||
cp lib/x86_64-linux-gnu/lib* $vlc_dir
|
cp lib/x86_64-linux-gnu/lib* $vlc_dir
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ mkdir -p $vlc_dir/vlc || exit 0
|
|||||||
cd /tmp
|
cd /tmp
|
||||||
mkdir tmp 2>/dev/null || true
|
mkdir tmp 2>/dev/null || true
|
||||||
cd tmp
|
cd tmp
|
||||||
curl https://github.com/simplex-chat/vlc/releases/download/v$vlc_version/vlc-macos-$ARCH.zip -L -o vlc
|
curl --tlsv1.2 https://github.com/simplex-chat/vlc/releases/download/v$vlc_version/vlc-macos-$ARCH.zip -L -o vlc
|
||||||
unzip -oqq vlc
|
unzip -oqq vlc
|
||||||
install_name_tool -add_rpath "@loader_path/VLC.app/Contents/MacOS/lib" vlc-cache-gen
|
install_name_tool -add_rpath "@loader_path/VLC.app/Contents/MacOS/lib" vlc-cache-gen
|
||||||
cd VLC.app/Contents/MacOS/lib
|
cd VLC.app/Contents/MacOS/lib
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ mkdir -p $vlc_dir/vlc || exit 0
|
|||||||
cd /tmp
|
cd /tmp
|
||||||
mkdir tmp 2>/dev/null || true
|
mkdir tmp 2>/dev/null || true
|
||||||
cd tmp
|
cd tmp
|
||||||
curl https://irltoolkit.mm.fcix.net/videolan-ftp/vlc/3.0.18/win64/vlc-3.0.18-win64.zip -L -o vlc
|
curl --tlsv1.2 https://irltoolkit.mm.fcix.net/videolan-ftp/vlc/3.0.18/win64/vlc-3.0.18-win64.zip -L -o vlc
|
||||||
$WINDIR\\System32\\tar.exe -xf vlc
|
$WINDIR\\System32\\tar.exe -xf vlc
|
||||||
cd vlc-*
|
cd vlc-*
|
||||||
# Setting the same date as the date that will be on the file after extraction from JAR to make VLC cache checker happy
|
# Setting the same date as the date that will be on the file after extraction from JAR to make VLC cache checker happy
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ for ((i = 0 ; i < ${#arches[@]}; i++)); do
|
|||||||
output_arch="${output_arches[$i]}"
|
output_arch="${output_arches[$i]}"
|
||||||
output_dir="$HOME/Downloads"
|
output_dir="$HOME/Downloads"
|
||||||
|
|
||||||
curl --location -o "$output_dir"/pkg-ios-"$arch"-swift-json.zip "$job_repo"/"$arch"-darwin-ios:lib:simplex-chat."$arch"-darwin/latest/download/1 && \
|
curl --tlsv1.2 --location -o "$output_dir"/pkg-ios-"$arch"-swift-json.zip "$job_repo"/"$arch"-darwin-ios:lib:simplex-chat."$arch"-darwin/latest/download/1 && \
|
||||||
unzip -o "$output_dir"/pkg-ios-"$output_arch"-swift-json.zip -d ~/Downloads/pkg-ios-"$output_arch"-swift-json
|
unzip -o "$output_dir"/pkg-ios-"$output_arch"-swift-json.zip -d ~/Downloads/pkg-ios-"$output_arch"-swift-json
|
||||||
done
|
done
|
||||||
sh "$root_dir"/scripts/ios/prepare-x86_64.sh
|
sh "$root_dir"/scripts/ios/prepare-x86_64.sh
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."d0588bd0ac23a459cbfc9a4789633014e91ffa19" = "0b17qy74capb0jyli8f3pg1xi4aawhcgpmaz2ykl9g3605png1na";
|
"https://github.com/simplex-chat/simplexmq.git"."f6ed4640d407f8879273d104a3e69069806dcb7c" = "072rakv697f85i8ldjl7bj7jc7vfmzphasx2i4ynwgz3kksydfp5";
|
||||||
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
|
||||||
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d";
|
||||||
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
"https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl";
|
||||||
|
|||||||
+1
-1
@@ -5,7 +5,7 @@ cabal-version: 1.12
|
|||||||
-- see: https://github.com/sol/hpack
|
-- see: https://github.com/sol/hpack
|
||||||
|
|
||||||
name: simplex-chat
|
name: simplex-chat
|
||||||
version: 5.4.2.1
|
version: 5.4.4.0
|
||||||
category: Web, System, Services, Cryptography
|
category: Web, System, Services, Cryptography
|
||||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||||
author: simplex.chat
|
author: simplex.chat
|
||||||
|
|||||||
+37
-26
@@ -29,7 +29,6 @@ import Data.Bifunctor (bimap, first)
|
|||||||
import Data.ByteArray (ScrubbedBytes)
|
import Data.ByteArray (ScrubbedBytes)
|
||||||
import qualified Data.ByteArray as BA
|
import qualified Data.ByteArray as BA
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import Data.ByteString.Builder (toLazyByteString)
|
|
||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
@@ -234,6 +233,7 @@ newChatController
|
|||||||
expireCIFlags <- newTVarIO M.empty
|
expireCIFlags <- newTVarIO M.empty
|
||||||
cleanupManagerAsync <- newTVarIO Nothing
|
cleanupManagerAsync <- newTVarIO Nothing
|
||||||
timedItemThreads <- atomically TM.empty
|
timedItemThreads <- atomically TM.empty
|
||||||
|
chatActivated <- newTVarIO True
|
||||||
showLiveItems <- newTVarIO False
|
showLiveItems <- newTVarIO False
|
||||||
encryptLocalFiles <- newTVarIO False
|
encryptLocalFiles <- newTVarIO False
|
||||||
userXFTPFileConfig <- newTVarIO $ xftpFileConfig cfg
|
userXFTPFileConfig <- newTVarIO $ xftpFileConfig cfg
|
||||||
@@ -269,6 +269,7 @@ newChatController
|
|||||||
expireCIFlags,
|
expireCIFlags,
|
||||||
cleanupManagerAsync,
|
cleanupManagerAsync,
|
||||||
timedItemThreads,
|
timedItemThreads,
|
||||||
|
chatActivated,
|
||||||
showLiveItems,
|
showLiveItems,
|
||||||
encryptLocalFiles,
|
encryptLocalFiles,
|
||||||
userXFTPFileConfig,
|
userXFTPFileConfig,
|
||||||
@@ -311,10 +312,10 @@ cfgServers p s = case p of
|
|||||||
SPSMP -> s.smp
|
SPSMP -> s.smp
|
||||||
SPXFTP -> s.xftp
|
SPXFTP -> s.xftp
|
||||||
|
|
||||||
startChatController :: forall m. ChatMonad' m => Bool -> Bool -> Bool -> m (Async ())
|
startChatController :: forall m. ChatMonad' m => Bool -> m (Async ())
|
||||||
startChatController subConns enableExpireCIs startXFTPWorkers = do
|
startChatController mainApp = do
|
||||||
asks smpAgent >>= resumeAgentClient
|
asks smpAgent >>= resumeAgentClient
|
||||||
unless subConns $
|
unless mainApp $
|
||||||
chatWriteVar subscriptionMode SMOnlyCreate
|
chatWriteVar subscriptionMode SMOnlyCreate
|
||||||
users <- fromRight [] <$> runExceptT (withStoreCtx' (Just "startChatController, getUsers") getUsers)
|
users <- fromRight [] <$> runExceptT (withStoreCtx' (Just "startChatController, getUsers") getUsers)
|
||||||
restoreCalls
|
restoreCalls
|
||||||
@@ -324,15 +325,15 @@ startChatController subConns enableExpireCIs startXFTPWorkers = do
|
|||||||
start s users = do
|
start s users = do
|
||||||
a1 <- async agentSubscriber
|
a1 <- async agentSubscriber
|
||||||
a2 <-
|
a2 <-
|
||||||
if subConns
|
if mainApp
|
||||||
then Just <$> async (subscribeUsers False users)
|
then Just <$> async (subscribeUsers False users)
|
||||||
else pure Nothing
|
else pure Nothing
|
||||||
atomically . writeTVar s $ Just (a1, a2)
|
atomically . writeTVar s $ Just (a1, a2)
|
||||||
when startXFTPWorkers $ do
|
when mainApp $ do
|
||||||
startXFTP
|
startXFTP
|
||||||
void $ forkIO $ startFilesToReceive users
|
void $ forkIO $ startFilesToReceive users
|
||||||
startCleanupManager
|
startCleanupManager
|
||||||
when enableExpireCIs $ startExpireCIs users
|
startExpireCIs users
|
||||||
pure a1
|
pure a1
|
||||||
startXFTP = do
|
startXFTP = do
|
||||||
tmp <- readTVarIO =<< asks tempDirectory
|
tmp <- readTVarIO =<< asks tempDirectory
|
||||||
@@ -544,16 +545,17 @@ processChatCommand' vr = \case
|
|||||||
checkDeleteChatUser user'
|
checkDeleteChatUser user'
|
||||||
withChatLock "deleteUser" . procCmd $ deleteChatUser user' delSMPQueues
|
withChatLock "deleteUser" . procCmd $ deleteChatUser user' delSMPQueues
|
||||||
DeleteUser uName delSMPQueues viewPwd_ -> withUserName uName $ \userId -> APIDeleteUser userId delSMPQueues viewPwd_
|
DeleteUser uName delSMPQueues viewPwd_ -> withUserName uName $ \userId -> APIDeleteUser userId delSMPQueues viewPwd_
|
||||||
StartChat subConns enableExpireCIs startXFTPWorkers -> withUser' $ \_ ->
|
StartChat mainApp -> withUser' $ \_ ->
|
||||||
asks agentAsync >>= readTVarIO >>= \case
|
asks agentAsync >>= readTVarIO >>= \case
|
||||||
Just _ -> pure CRChatRunning
|
Just _ -> pure CRChatRunning
|
||||||
_ -> checkStoreNotChanged $ startChatController subConns enableExpireCIs startXFTPWorkers $> CRChatStarted
|
_ -> checkStoreNotChanged $ startChatController mainApp $> CRChatStarted
|
||||||
APIStopChat -> do
|
APIStopChat -> do
|
||||||
ask >>= stopChatController
|
ask >>= stopChatController
|
||||||
pure CRChatStopped
|
pure CRChatStopped
|
||||||
APIActivateChat restoreChat -> withUser $ \_ -> do
|
APIActivateChat restoreChat -> withUser $ \_ -> do
|
||||||
when restoreChat restoreCalls
|
when restoreChat restoreCalls
|
||||||
withAgent foregroundAgent
|
withAgent foregroundAgent
|
||||||
|
chatWriteVar chatActivated True
|
||||||
when restoreChat $ do
|
when restoreChat $ do
|
||||||
users <- withStoreCtx' (Just "APIActivateChat, getUsers") getUsers
|
users <- withStoreCtx' (Just "APIActivateChat, getUsers") getUsers
|
||||||
void . forkIO $ subscribeUsers True users
|
void . forkIO $ subscribeUsers True users
|
||||||
@@ -561,6 +563,7 @@ processChatCommand' vr = \case
|
|||||||
setAllExpireCIFlags True
|
setAllExpireCIFlags True
|
||||||
ok_
|
ok_
|
||||||
APISuspendChat t -> do
|
APISuspendChat t -> do
|
||||||
|
chatWriteVar chatActivated False
|
||||||
setAllExpireCIFlags False
|
setAllExpireCIFlags False
|
||||||
stopRemoteCtrl
|
stopRemoteCtrl
|
||||||
withAgent (`suspendAgent` t)
|
withAgent (`suspendAgent` t)
|
||||||
@@ -2479,6 +2482,7 @@ startExpireCIThread user@User {userId} = do
|
|||||||
flip catchChatError (toView . CRChatError (Just user)) $ do
|
flip catchChatError (toView . CRChatError (Just user)) $ do
|
||||||
expireFlags <- asks expireCIFlags
|
expireFlags <- asks expireCIFlags
|
||||||
atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry
|
atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry
|
||||||
|
waitChatStartedAndActivated
|
||||||
ttl <- withStoreCtx' (Just "startExpireCIThread, getChatItemTTL") (`getChatItemTTL` user)
|
ttl <- withStoreCtx' (Just "startExpireCIThread, getChatItemTTL") (`getChatItemTTL` user)
|
||||||
forM_ ttl $ \t -> expireChatItems user t False
|
forM_ ttl $ \t -> expireChatItems user t False
|
||||||
liftIO $ threadDelay' interval
|
liftIO $ threadDelay' interval
|
||||||
@@ -2972,7 +2976,7 @@ cleanupManager = do
|
|||||||
stepDelay <- asks (cleanupManagerStepDelay . config)
|
stepDelay <- asks (cleanupManagerStepDelay . config)
|
||||||
forever $ do
|
forever $ do
|
||||||
flip catchChatError (toView . CRChatError Nothing) $ do
|
flip catchChatError (toView . CRChatError Nothing) $ do
|
||||||
waitChatStarted
|
waitChatStartedAndActivated
|
||||||
users <- withStoreCtx' (Just "cleanupManager, getUsers 1") getUsers
|
users <- withStoreCtx' (Just "cleanupManager, getUsers 1") getUsers
|
||||||
let (us, us') = partition activeUser users
|
let (us, us') = partition activeUser users
|
||||||
forM_ us $ cleanupUser interval stepDelay
|
forM_ us $ cleanupUser interval stepDelay
|
||||||
@@ -2982,7 +2986,7 @@ cleanupManager = do
|
|||||||
liftIO $ threadDelay' $ diffToMicroseconds interval
|
liftIO $ threadDelay' $ diffToMicroseconds interval
|
||||||
where
|
where
|
||||||
runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CRChatError Nothing) $ do
|
runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CRChatError Nothing) $ do
|
||||||
waitChatStarted
|
waitChatStartedAndActivated
|
||||||
users <- withStoreCtx' (Just "cleanupManager, getUsers 2") getUsers
|
users <- withStoreCtx' (Just "cleanupManager, getUsers 2") getUsers
|
||||||
let (us, us') = partition activeUser users
|
let (us, us') = partition activeUser users
|
||||||
forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u))
|
forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u))
|
||||||
@@ -3037,7 +3041,7 @@ deleteTimedItem :: ChatMonad m => User -> (ChatRef, ChatItemId) -> UTCTime -> m
|
|||||||
deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do
|
deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do
|
||||||
ts <- liftIO getCurrentTime
|
ts <- liftIO getCurrentTime
|
||||||
liftIO $ threadDelay' $ diffToMicroseconds $ diffUTCTime deleteAt ts
|
liftIO $ threadDelay' $ diffToMicroseconds $ diffUTCTime deleteAt ts
|
||||||
waitChatStarted
|
waitChatStartedAndActivated
|
||||||
vr <- chatVersionRange
|
vr <- chatVersionRange
|
||||||
case cType of
|
case cType of
|
||||||
CTDirect -> do
|
CTDirect -> do
|
||||||
@@ -3063,8 +3067,10 @@ expireChatItems user@User {userId} ttl sync = do
|
|||||||
let expirationDate = addUTCTime (-1 * fromIntegral ttl) currentTs
|
let expirationDate = addUTCTime (-1 * fromIntegral ttl) currentTs
|
||||||
-- this is to keep group messages created during last 12 hours even if they're expired according to item_ts
|
-- this is to keep group messages created during last 12 hours even if they're expired according to item_ts
|
||||||
createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
|
createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs
|
||||||
|
waitChatStartedAndActivated
|
||||||
contacts <- withStoreCtx' (Just "expireChatItems, getUserContacts") (`getUserContacts` user)
|
contacts <- withStoreCtx' (Just "expireChatItems, getUserContacts") (`getUserContacts` user)
|
||||||
loop contacts $ processContact expirationDate
|
loop contacts $ processContact expirationDate
|
||||||
|
waitChatStartedAndActivated
|
||||||
groups <- withStoreCtx' (Just "expireChatItems, getUserGroupDetails") (\db -> getUserGroupDetails db vr user Nothing Nothing)
|
groups <- withStoreCtx' (Just "expireChatItems, getUserGroupDetails") (\db -> getUserGroupDetails db vr user Nothing Nothing)
|
||||||
loop groups $ processGroup expirationDate createdAtCutoff
|
loop groups $ processGroup expirationDate createdAtCutoff
|
||||||
where
|
where
|
||||||
@@ -3083,11 +3089,13 @@ expireChatItems user@User {userId} ttl sync = do
|
|||||||
when (expire == Just True) $ threadDelay 100000 >> a
|
when (expire == Just True) $ threadDelay 100000 >> a
|
||||||
processContact :: UTCTime -> Contact -> m ()
|
processContact :: UTCTime -> Contact -> m ()
|
||||||
processContact expirationDate ct = do
|
processContact expirationDate ct = do
|
||||||
|
waitChatStartedAndActivated
|
||||||
filesInfo <- withStoreCtx' (Just "processContact, getContactExpiredFileInfo") $ \db -> getContactExpiredFileInfo db user ct expirationDate
|
filesInfo <- withStoreCtx' (Just "processContact, getContactExpiredFileInfo") $ \db -> getContactExpiredFileInfo db user ct expirationDate
|
||||||
deleteFilesAndConns user filesInfo
|
deleteFilesAndConns user filesInfo
|
||||||
withStoreCtx' (Just "processContact, deleteContactExpiredCIs") $ \db -> deleteContactExpiredCIs db user ct expirationDate
|
withStoreCtx' (Just "processContact, deleteContactExpiredCIs") $ \db -> deleteContactExpiredCIs db user ct expirationDate
|
||||||
processGroup :: UTCTime -> UTCTime -> GroupInfo -> m ()
|
processGroup :: UTCTime -> UTCTime -> GroupInfo -> m ()
|
||||||
processGroup expirationDate createdAtCutoff gInfo = do
|
processGroup expirationDate createdAtCutoff gInfo = do
|
||||||
|
waitChatStartedAndActivated
|
||||||
filesInfo <- withStoreCtx' (Just "processGroup, getGroupExpiredFileInfo") $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff
|
filesInfo <- withStoreCtx' (Just "processGroup, getGroupExpiredFileInfo") $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff
|
||||||
deleteFilesAndConns user filesInfo
|
deleteFilesAndConns user filesInfo
|
||||||
withStoreCtx' (Just "processGroup, deleteGroupExpiredCIs") $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff
|
withStoreCtx' (Just "processGroup, deleteGroupExpiredCIs") $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff
|
||||||
@@ -5647,8 +5655,7 @@ sendGroupMemberMessages user conn@Connection {connId} events groupId = do
|
|||||||
processBatch batch `catchChatError` (toView . CRChatError (Just user))
|
processBatch batch `catchChatError` (toView . CRChatError (Just user))
|
||||||
where
|
where
|
||||||
processBatch :: MsgBatch -> m ()
|
processBatch :: MsgBatch -> m ()
|
||||||
processBatch (MsgBatch builder sndMsgs) = do
|
processBatch (MsgBatch batchBody sndMsgs) = do
|
||||||
let batchBody = LB.toStrict $ toLazyByteString builder
|
|
||||||
agentMsgId <- withAgent $ \a -> sendMessage a (aConnId conn) MsgFlags {notification = True} batchBody
|
agentMsgId <- withAgent $ \a -> sendMessage a (aConnId conn) MsgFlags {notification = True} batchBody
|
||||||
let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId}
|
let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId}
|
||||||
void . withStoreBatch' $ \db -> map (\SndMessage {msgId} -> createSndMsgDelivery db sndMsgDelivery msgId) sndMsgs
|
void . withStoreBatch' $ \db -> map (\SndMessage {msgId} -> createSndMsgDelivery db sndMsgDelivery msgId) sndMsgs
|
||||||
@@ -5668,28 +5675,28 @@ directMessage chatMsgEvent = do
|
|||||||
chatVRange <- chatVersionRange
|
chatVRange <- chatVersionRange
|
||||||
let r = encodeChatMessage ChatMessage {chatVRange, msgId = Nothing, chatMsgEvent}
|
let r = encodeChatMessage ChatMessage {chatVRange, msgId = Nothing, chatMsgEvent}
|
||||||
case r of
|
case r of
|
||||||
ECMEncoded encodedBody -> pure . LB.toStrict $ encodedBody
|
ECMEncoded encodedBody -> pure encodedBody
|
||||||
ECMLarge -> throwChatError $ CEException "large message"
|
ECMLarge -> throwChatError $ CEException "large message"
|
||||||
|
|
||||||
deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> LazyMsgBody -> MessageId -> m Int64
|
deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> MsgBody -> MessageId -> m Int64
|
||||||
deliverMessage conn cmEventTag msgBody msgId = do
|
deliverMessage conn cmEventTag msgBody msgId = do
|
||||||
let msgFlags = MsgFlags {notification = hasNotification cmEventTag}
|
let msgFlags = MsgFlags {notification = hasNotification cmEventTag}
|
||||||
deliverMessage' conn msgFlags msgBody msgId
|
deliverMessage' conn msgFlags msgBody msgId
|
||||||
|
|
||||||
deliverMessage' :: ChatMonad m => Connection -> MsgFlags -> LazyMsgBody -> MessageId -> m Int64
|
deliverMessage' :: ChatMonad m => Connection -> MsgFlags -> MsgBody -> MessageId -> m Int64
|
||||||
deliverMessage' conn msgFlags msgBody msgId =
|
deliverMessage' conn msgFlags msgBody msgId =
|
||||||
deliverMessages [(conn, msgFlags, msgBody, msgId)] >>= \case
|
deliverMessages [(conn, msgFlags, msgBody, msgId)] >>= \case
|
||||||
[r] -> liftEither r
|
[r] -> liftEither r
|
||||||
rs -> throwChatError $ CEInternalError $ "deliverMessage: expected 1 result, got " <> show (length rs)
|
rs -> throwChatError $ CEInternalError $ "deliverMessage: expected 1 result, got " <> show (length rs)
|
||||||
|
|
||||||
deliverMessages :: ChatMonad' m => [(Connection, MsgFlags, LazyMsgBody, MessageId)] -> m [Either ChatError Int64]
|
deliverMessages :: ChatMonad' m => [(Connection, MsgFlags, MsgBody, MessageId)] -> m [Either ChatError Int64]
|
||||||
deliverMessages msgReqs = do
|
deliverMessages msgReqs = do
|
||||||
sent <- zipWith prepareBatch msgReqs <$> withAgent' (`sendMessages` aReqs)
|
sent <- zipWith prepareBatch msgReqs <$> withAgent' (`sendMessages` aReqs)
|
||||||
withStoreBatch $ \db -> map (bindRight $ createDelivery db) sent
|
withStoreBatch $ \db -> map (bindRight $ createDelivery db) sent
|
||||||
where
|
where
|
||||||
aReqs = map (\(conn, msgFlags, msgBody, _msgId) -> (aConnId conn, msgFlags, LB.toStrict msgBody)) msgReqs
|
aReqs = map (\(conn, msgFlags, msgBody, _msgId) -> (aConnId conn, msgFlags, msgBody)) msgReqs
|
||||||
prepareBatch req = bimap (`ChatErrorAgent` Nothing) (req,)
|
prepareBatch req = bimap (`ChatErrorAgent` Nothing) (req,)
|
||||||
createDelivery :: DB.Connection -> ((Connection, MsgFlags, LazyMsgBody, MessageId), AgentMsgId) -> IO (Either ChatError Int64)
|
createDelivery :: DB.Connection -> ((Connection, MsgFlags, MsgBody, MessageId), AgentMsgId) -> IO (Either ChatError Int64)
|
||||||
createDelivery db ((Connection {connId}, _, _, msgId), agentMsgId) =
|
createDelivery db ((Connection {connId}, _, _, msgId), agentMsgId) =
|
||||||
Right <$> createSndMsgDelivery db (SndMsgDelivery {connId, agentMsgId}) msgId
|
Right <$> createSndMsgDelivery db (SndMsgDelivery {connId, agentMsgId}) msgId
|
||||||
|
|
||||||
@@ -6113,10 +6120,14 @@ checkSameUser userId User {userId = activeUserId} = when (userId /= activeUserId
|
|||||||
chatStarted :: ChatMonad m => m Bool
|
chatStarted :: ChatMonad m => m Bool
|
||||||
chatStarted = fmap isJust . readTVarIO =<< asks agentAsync
|
chatStarted = fmap isJust . readTVarIO =<< asks agentAsync
|
||||||
|
|
||||||
waitChatStarted :: ChatMonad m => m ()
|
waitChatStartedAndActivated :: ChatMonad m => m ()
|
||||||
waitChatStarted = do
|
waitChatStartedAndActivated = do
|
||||||
agentStarted <- asks agentAsync
|
agentStarted <- asks agentAsync
|
||||||
atomically $ readTVar agentStarted >>= \a -> unless (isJust a) retry
|
chatActivated <- asks chatActivated
|
||||||
|
atomically $ do
|
||||||
|
started <- readTVar agentStarted
|
||||||
|
activated <- readTVar chatActivated
|
||||||
|
unless (isJust started && activated) retry
|
||||||
|
|
||||||
chatVersionRange :: ChatMonad' m => m VersionRange
|
chatVersionRange :: ChatMonad' m => m VersionRange
|
||||||
chatVersionRange = do
|
chatVersionRange = do
|
||||||
@@ -6153,8 +6164,8 @@ chatCommandP =
|
|||||||
"/_delete user " *> (APIDeleteUser <$> A.decimal <* " del_smp=" <*> onOffP <*> optional (A.space *> jsonP)),
|
"/_delete user " *> (APIDeleteUser <$> A.decimal <* " del_smp=" <*> onOffP <*> optional (A.space *> jsonP)),
|
||||||
"/delete user " *> (DeleteUser <$> displayName <*> pure True <*> optional (A.space *> pwdP)),
|
"/delete user " *> (DeleteUser <$> displayName <*> pure True <*> optional (A.space *> pwdP)),
|
||||||
("/user" <|> "/u") $> ShowActiveUser,
|
("/user" <|> "/u") $> ShowActiveUser,
|
||||||
"/_start subscribe=" *> (StartChat <$> onOffP <* " expire=" <*> onOffP <* " xftp=" <*> onOffP),
|
"/_start main=" *> (StartChat <$> onOffP),
|
||||||
"/_start" $> StartChat True True True,
|
"/_start" $> StartChat True,
|
||||||
"/_stop" $> APIStopChat,
|
"/_stop" $> APIStopChat,
|
||||||
"/_app activate restore=" *> (APIActivateChat <$> onOffP),
|
"/_app activate restore=" *> (APIActivateChat <$> onOffP),
|
||||||
"/_app activate" $> APIActivateChat True,
|
"/_app activate" $> APIActivateChat True,
|
||||||
|
|||||||
@@ -200,6 +200,7 @@ data ChatController = ChatController
|
|||||||
expireCIThreads :: TMap UserId (Maybe (Async ())),
|
expireCIThreads :: TMap UserId (Maybe (Async ())),
|
||||||
expireCIFlags :: TMap UserId Bool,
|
expireCIFlags :: TMap UserId Bool,
|
||||||
cleanupManagerAsync :: TVar (Maybe (Async ())),
|
cleanupManagerAsync :: TVar (Maybe (Async ())),
|
||||||
|
chatActivated :: TVar Bool,
|
||||||
timedItemThreads :: TMap (ChatRef, ChatItemId) (TVar (Maybe (Weak ThreadId))),
|
timedItemThreads :: TMap (ChatRef, ChatItemId) (TVar (Maybe (Weak ThreadId))),
|
||||||
showLiveItems :: TVar Bool,
|
showLiveItems :: TVar Bool,
|
||||||
encryptLocalFiles :: TVar Bool,
|
encryptLocalFiles :: TVar Bool,
|
||||||
@@ -233,7 +234,7 @@ data ChatCommand
|
|||||||
| UnmuteUser
|
| UnmuteUser
|
||||||
| APIDeleteUser UserId Bool (Maybe UserPwd)
|
| APIDeleteUser UserId Bool (Maybe UserPwd)
|
||||||
| DeleteUser UserName Bool (Maybe UserPwd)
|
| DeleteUser UserName Bool (Maybe UserPwd)
|
||||||
| StartChat {subscribeConnections :: Bool, enableExpireChatItems :: Bool, startXFTPWorkers :: Bool}
|
| StartChat {mainApp :: Bool}
|
||||||
| APIStopChat
|
| APIStopChat
|
||||||
| APIActivateChat {restoreChat :: Bool}
|
| APIActivateChat {restoreChat :: Bool}
|
||||||
| APISuspendChat {suspendTimeout :: Int}
|
| APISuspendChat {suspendTimeout :: Int}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ runSimplexChat :: ChatOpts -> User -> ChatController -> (User -> ChatController
|
|||||||
runSimplexChat ChatOpts {maintenance} u cc chat
|
runSimplexChat ChatOpts {maintenance} u cc chat
|
||||||
| maintenance = wait =<< async (chat u cc)
|
| maintenance = wait =<< async (chat u cc)
|
||||||
| otherwise = do
|
| otherwise = do
|
||||||
a1 <- runReaderT (startChatController True True True) cc
|
a1 <- runReaderT (startChatController True) cc
|
||||||
a2 <- async $ chat u cc
|
a2 <- async $ chat u cc
|
||||||
waitEither_ a1 a2
|
waitEither_ a1 a2
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,6 @@ import qualified Data.Aeson.Encoding as JE
|
|||||||
import qualified Data.Aeson.TH as JQ
|
import qualified Data.Aeson.TH as JQ
|
||||||
import qualified Data.Attoparsec.ByteString.Char8 as A
|
import qualified Data.Attoparsec.ByteString.Char8 as A
|
||||||
import qualified Data.ByteString.Base64 as B64
|
import qualified Data.ByteString.Base64 as B64
|
||||||
import qualified Data.ByteString.Lazy as L
|
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
import Data.Char (isSpace)
|
import Data.Char (isSpace)
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
@@ -764,12 +763,10 @@ checkChatType x = case testEquality (chatTypeI @c) (chatTypeI @c') of
|
|||||||
Just Refl -> Right x
|
Just Refl -> Right x
|
||||||
Nothing -> Left "bad chat type"
|
Nothing -> Left "bad chat type"
|
||||||
|
|
||||||
type LazyMsgBody = L.ByteString
|
|
||||||
|
|
||||||
data SndMessage = SndMessage
|
data SndMessage = SndMessage
|
||||||
{ msgId :: MessageId,
|
{ msgId :: MessageId,
|
||||||
sharedMsgId :: SharedMsgId,
|
sharedMsgId :: SharedMsgId,
|
||||||
msgBody :: LazyMsgBody
|
msgBody :: MsgBody
|
||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
@@ -791,7 +788,7 @@ data RcvMessage = RcvMessage
|
|||||||
data PendingGroupMessage = PendingGroupMessage
|
data PendingGroupMessage = PendingGroupMessage
|
||||||
{ msgId :: MessageId,
|
{ msgId :: MessageId,
|
||||||
cmEventTag :: ACMEventTag,
|
cmEventTag :: ACMEventTag,
|
||||||
msgBody :: LazyMsgBody,
|
msgBody :: MsgBody,
|
||||||
introId_ :: Maybe Int64
|
introId_ :: Maybe Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
module Simplex.Chat.Messages.Batch
|
module Simplex.Chat.Messages.Batch
|
||||||
@@ -9,33 +10,29 @@ module Simplex.Chat.Messages.Batch
|
|||||||
)
|
)
|
||||||
where
|
where
|
||||||
|
|
||||||
import Data.ByteString.Builder (Builder, charUtf8, lazyByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.ByteString.Lazy as LB
|
import qualified Data.ByteString.Char8 as B
|
||||||
import Data.Int (Int64)
|
|
||||||
import Simplex.Chat.Controller (ChatError (..), ChatErrorType (..))
|
import Simplex.Chat.Controller (ChatError (..), ChatErrorType (..))
|
||||||
import Simplex.Chat.Messages
|
import Simplex.Chat.Messages
|
||||||
|
|
||||||
data MsgBatch = MsgBatch Builder [SndMessage]
|
data MsgBatch = MsgBatch ByteString [SndMessage]
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
-- | Batches [SndMessage] into batches of ByteString builders in form of JSON arrays.
|
-- | Batches [SndMessage] into batches of ByteStrings in form of JSON arrays.
|
||||||
-- Does not check if the resulting batch is a valid JSON.
|
-- Does not check if the resulting batch is a valid JSON.
|
||||||
-- If a single element is passed, it is returned as is (a JSON string).
|
-- If a single element is passed, it is returned as is (a JSON string).
|
||||||
-- If an element exceeds maxLen, it is returned as ChatError.
|
-- If an element exceeds maxLen, it is returned as ChatError.
|
||||||
batchMessages :: Int64 -> [SndMessage] -> [Either ChatError MsgBatch]
|
batchMessages :: Int -> [SndMessage] -> [Either ChatError MsgBatch]
|
||||||
batchMessages maxLen msgs =
|
batchMessages maxLen = addBatch . foldr addToBatch ([], [], 0, 0)
|
||||||
let (batches, batch, _, n) = foldr addToBatch ([], [], 0, 0) msgs
|
|
||||||
in if n == 0 then batches else msgBatch batch : batches
|
|
||||||
where
|
where
|
||||||
msgBatch batch = Right (MsgBatch (encodeMessages batch) batch)
|
msgBatch batch = Right (MsgBatch (encodeMessages batch) batch)
|
||||||
addToBatch :: SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int64, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int64, Int)
|
addToBatch :: SndMessage -> ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> ([Either ChatError MsgBatch], [SndMessage], Int, Int)
|
||||||
addToBatch msg@SndMessage {msgBody} (batches, batch, len, n)
|
addToBatch msg@SndMessage {msgBody} acc@(batches, batch, len, n)
|
||||||
| batchLen <= maxLen = (batches, msg : batch, len', n + 1)
|
| batchLen <= maxLen = (batches, msg : batch, len', n + 1)
|
||||||
| msgLen <= maxLen = (batches', [msg], msgLen, 1)
|
| msgLen <= maxLen = (addBatch acc, [msg], msgLen, 1)
|
||||||
| otherwise = (errLarge msg : (if n == 0 then batches else batches'), [], 0, 0)
|
| otherwise = (errLarge msg : addBatch acc, [], 0, 0)
|
||||||
where
|
where
|
||||||
msgLen = LB.length msgBody
|
msgLen = B.length msgBody
|
||||||
batches' = msgBatch batch : batches
|
|
||||||
len'
|
len'
|
||||||
| n == 0 = msgLen
|
| n == 0 = msgLen
|
||||||
| otherwise = msgLen + len + 1 -- 1 accounts for comma
|
| otherwise = msgLen + len + 1 -- 1 accounts for comma
|
||||||
@@ -43,11 +40,11 @@ batchMessages maxLen msgs =
|
|||||||
| n == 0 = len'
|
| n == 0 = len'
|
||||||
| otherwise = len' + 2 -- 2 accounts for opening and closing brackets
|
| otherwise = len' + 2 -- 2 accounts for opening and closing brackets
|
||||||
errLarge SndMessage {msgId} = Left $ ChatError $ CEInternalError ("large message " <> show msgId)
|
errLarge SndMessage {msgId} = Left $ ChatError $ CEInternalError ("large message " <> show msgId)
|
||||||
|
addBatch :: ([Either ChatError MsgBatch], [SndMessage], Int, Int) -> [Either ChatError MsgBatch]
|
||||||
encodeMessages :: [SndMessage] -> Builder
|
addBatch (batches, batch, _, n) = if n == 0 then batches else msgBatch batch : batches
|
||||||
encodeMessages = \case
|
encodeMessages :: [SndMessage] -> ByteString
|
||||||
[] -> mempty
|
encodeMessages = \case
|
||||||
[msg] -> encodeMsg msg
|
[] -> mempty
|
||||||
(msg : msgs) -> charUtf8 '[' <> encodeMsg msg <> mconcat [charUtf8 ',' <> encodeMsg msg' | msg' <- msgs] <> charUtf8 ']'
|
[msg] -> body msg
|
||||||
where
|
msgs -> B.concat ["[", B.intercalate "," (map body msgs), "]"]
|
||||||
encodeMsg SndMessage {msgBody} = lazyByteString msgBody
|
body SndMessage {msgBody} = msgBody
|
||||||
|
|||||||
@@ -29,9 +29,7 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
|||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import Data.ByteString.Internal (c2w, w2c)
|
import Data.ByteString.Internal (c2w, w2c)
|
||||||
import qualified Data.ByteString.Lazy as L
|
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
import qualified Data.ByteString.Lazy.Char8 as LB
|
||||||
import Data.Int (Int64)
|
|
||||||
import Data.Maybe (fromMaybe)
|
import Data.Maybe (fromMaybe)
|
||||||
import Data.String
|
import Data.String
|
||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
@@ -491,20 +489,20 @@ $(JQ.deriveJSON defaultJSON ''QuotedMsg)
|
|||||||
|
|
||||||
-- this limit reserves space for metadata in forwarded messages
|
-- this limit reserves space for metadata in forwarded messages
|
||||||
-- 15780 (limit used for fileChunkSize) - 161 (x.grp.msg.forward overhead) = 15619, round to 15610
|
-- 15780 (limit used for fileChunkSize) - 161 (x.grp.msg.forward overhead) = 15619, round to 15610
|
||||||
maxChatMsgSize :: Int64
|
maxChatMsgSize :: Int
|
||||||
maxChatMsgSize = 15610
|
maxChatMsgSize = 15610
|
||||||
|
|
||||||
data EncodedChatMessage = ECMEncoded L.ByteString | ECMLarge
|
data EncodedChatMessage = ECMEncoded ByteString | ECMLarge
|
||||||
|
|
||||||
encodeChatMessage :: MsgEncodingI e => ChatMessage e -> EncodedChatMessage
|
encodeChatMessage :: MsgEncodingI e => ChatMessage e -> EncodedChatMessage
|
||||||
encodeChatMessage msg = do
|
encodeChatMessage msg = do
|
||||||
case chatToAppMessage msg of
|
case chatToAppMessage msg of
|
||||||
AMJson m -> do
|
AMJson m -> do
|
||||||
let body = J.encode m
|
let body = LB.toStrict $ J.encode m
|
||||||
if LB.length body > maxChatMsgSize
|
if B.length body > maxChatMsgSize
|
||||||
then ECMLarge
|
then ECMLarge
|
||||||
else ECMEncoded body
|
else ECMEncoded body
|
||||||
AMBinary m -> ECMEncoded . LB.fromStrict $ strEncode m
|
AMBinary m -> ECMEncoded $ strEncode m
|
||||||
|
|
||||||
parseChatMessages :: ByteString -> [Either String AChatMessage]
|
parseChatMessages :: ByteString -> [Either String AChatMessage]
|
||||||
parseChatMessages "" = [Left "empty string"]
|
parseChatMessages "" = [Left "empty string"]
|
||||||
|
|||||||
@@ -413,6 +413,7 @@ xftpServerConfig =
|
|||||||
logStatsStartTime = 0,
|
logStatsStartTime = 0,
|
||||||
serverStatsLogFile = "tests/tmp/xftp-server-stats.daily.log",
|
serverStatsLogFile = "tests/tmp/xftp-server-stats.daily.log",
|
||||||
serverStatsBackupFile = Nothing,
|
serverStatsBackupFile = Nothing,
|
||||||
|
controlPort = Nothing,
|
||||||
transportConfig = defaultTransportServerConfig
|
transportConfig = defaultTransportServerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1149,7 +1149,7 @@ testSubscribeAppNSE tmp =
|
|||||||
alice ##> "/_app suspend 1"
|
alice ##> "/_app suspend 1"
|
||||||
alice <## "ok"
|
alice <## "ok"
|
||||||
alice <## "chat suspended"
|
alice <## "chat suspended"
|
||||||
nseAlice ##> "/_start subscribe=off expire=off xftp=off"
|
nseAlice ##> "/_start main=off"
|
||||||
nseAlice <## "chat started"
|
nseAlice <## "chat started"
|
||||||
nseAlice ##> "/ad"
|
nseAlice ##> "/ad"
|
||||||
cLink <- getContactLink nseAlice True
|
cLink <- getContactLink nseAlice True
|
||||||
|
|||||||
@@ -7,8 +7,8 @@
|
|||||||
module MessageBatching (batchingTests) where
|
module MessageBatching (batchingTests) where
|
||||||
|
|
||||||
import Crypto.Number.Serialize (os2ip)
|
import Crypto.Number.Serialize (os2ip)
|
||||||
import Data.ByteString.Builder (toLazyByteString)
|
import Data.ByteString (ByteString)
|
||||||
import qualified Data.ByteString.Lazy as LB
|
import qualified Data.ByteString as B
|
||||||
import Data.Either (partitionEithers)
|
import Data.Either (partitionEithers)
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.String (IsString (..))
|
import Data.String (IsString (..))
|
||||||
@@ -26,7 +26,7 @@ batchingTests = describe "message batching tests" $ do
|
|||||||
it "image x.msg.new and x.msg.file.descr should fit into single batch" testImageFitsSingleBatch
|
it "image x.msg.new and x.msg.file.descr should fit into single batch" testImageFitsSingleBatch
|
||||||
|
|
||||||
instance IsString SndMessage where
|
instance IsString SndMessage where
|
||||||
fromString s = SndMessage {msgId, sharedMsgId = SharedMsgId "", msgBody = LB.fromStrict s'}
|
fromString s = SndMessage {msgId, sharedMsgId = SharedMsgId "", msgBody = s'}
|
||||||
where
|
where
|
||||||
s' = encodeUtf8 $ T.pack s
|
s' = encodeUtf8 $ T.pack s
|
||||||
msgId = fromInteger $ os2ip s'
|
msgId = fromInteger $ os2ip s'
|
||||||
@@ -94,14 +94,14 @@ testImageFitsSingleBatch = do
|
|||||||
-- 261_120 bytes (MAX_IMAGE_SIZE in UI), rounded up, example was 743
|
-- 261_120 bytes (MAX_IMAGE_SIZE in UI), rounded up, example was 743
|
||||||
let descrRoundedSize = 800
|
let descrRoundedSize = 800
|
||||||
|
|
||||||
let xMsgNewStr = LB.replicate xMsgNewRoundedSize 1
|
let xMsgNewStr = B.replicate xMsgNewRoundedSize 1
|
||||||
descrStr = LB.replicate descrRoundedSize 2
|
descrStr = B.replicate descrRoundedSize 2
|
||||||
msg s = SndMessage {msgId = 0, sharedMsgId = SharedMsgId "", msgBody = s}
|
msg s = SndMessage {msgId = 0, sharedMsgId = SharedMsgId "", msgBody = s}
|
||||||
batched = "[" <> xMsgNewStr <> "," <> descrStr <> "]"
|
batched = "[" <> xMsgNewStr <> "," <> descrStr <> "]"
|
||||||
|
|
||||||
runBatcherTest' maxChatMsgSize [msg xMsgNewStr, msg descrStr] [] [batched]
|
runBatcherTest' maxChatMsgSize [msg xMsgNewStr, msg descrStr] [] [batched]
|
||||||
|
|
||||||
runBatcherTest :: Int64 -> [SndMessage] -> [ChatError] -> [LB.ByteString] -> Spec
|
runBatcherTest :: Int -> [SndMessage] -> [ChatError] -> [ByteString] -> Spec
|
||||||
runBatcherTest maxLen msgs expectedErrors expectedBatches =
|
runBatcherTest maxLen msgs expectedErrors expectedBatches =
|
||||||
it
|
it
|
||||||
( (show (map (\SndMessage {msgBody} -> msgBody) msgs) <> ", limit " <> show maxLen <> ": should return ")
|
( (show (map (\SndMessage {msgBody} -> msgBody) msgs) <> ", limit " <> show maxLen <> ": should return ")
|
||||||
@@ -110,10 +110,10 @@ runBatcherTest maxLen msgs expectedErrors expectedBatches =
|
|||||||
)
|
)
|
||||||
(runBatcherTest' maxLen msgs expectedErrors expectedBatches)
|
(runBatcherTest' maxLen msgs expectedErrors expectedBatches)
|
||||||
|
|
||||||
runBatcherTest' :: Int64 -> [SndMessage] -> [ChatError] -> [LB.ByteString] -> IO ()
|
runBatcherTest' :: Int -> [SndMessage] -> [ChatError] -> [ByteString] -> IO ()
|
||||||
runBatcherTest' maxLen msgs expectedErrors expectedBatches = do
|
runBatcherTest' maxLen msgs expectedErrors expectedBatches = do
|
||||||
let (errors, batches) = partitionEithers $ batchMessages maxLen msgs
|
let (errors, batches) = partitionEithers $ batchMessages maxLen msgs
|
||||||
batchedStrs = map (\(MsgBatch builder _) -> toLazyByteString builder) batches
|
batchedStrs = map (\(MsgBatch batchBody _) -> batchBody) batches
|
||||||
testErrors errors `shouldBe` testErrors expectedErrors
|
testErrors errors `shouldBe` testErrors expectedErrors
|
||||||
batchedStrs `shouldBe` expectedBatches
|
batchedStrs `shouldBe` expectedBatches
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ module ProtocolTests where
|
|||||||
|
|
||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import Data.ByteString.Char8 (ByteString)
|
import Data.ByteString.Char8 (ByteString)
|
||||||
import qualified Data.ByteString.Lazy.Char8 as LB
|
|
||||||
import Data.Time.Clock.System (SystemTime (..), systemToUTCTime)
|
import Data.Time.Clock.System (SystemTime (..), systemToUTCTime)
|
||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
@@ -74,7 +73,7 @@ s ##== msg = do
|
|||||||
let r = encodeChatMessage msg
|
let r = encodeChatMessage msg
|
||||||
case r of
|
case r of
|
||||||
ECMEncoded encodedBody ->
|
ECMEncoded encodedBody ->
|
||||||
J.eitherDecodeStrict' (LB.toStrict encodedBody)
|
J.eitherDecodeStrict' encodedBody
|
||||||
`shouldBe` (J.eitherDecodeStrict' s :: Either String J.Value)
|
`shouldBe` (J.eitherDecodeStrict' s :: Either String J.Value)
|
||||||
ECMLarge -> expectationFailure $ "large message"
|
ECMLarge -> expectationFailure $ "large message"
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user