ios: push notifications (#482)
* ios: get device token for push notifications * ios: receive messages when background notification is received * add notifications API, update simplexmq * chat API to register and verify notification token * update AppDelegate to recognize different notification types, update simplexmq * core: api to enable periodic background notifications * update simplexmq * chat API to delete device notification token * use base64url encoding in verification code * update simplexmq for notifications
This commit is contained in:
parent
03b8cdea8d
commit
f594774579
2
apps/ios/.gitignore
vendored
2
apps/ios/.gitignore
vendored
@ -67,3 +67,5 @@ iOSInjectionProject/
|
|||||||
Libraries/
|
Libraries/
|
||||||
|
|
||||||
Shared/MyPlayground.playground/*
|
Shared/MyPlayground.playground/*
|
||||||
|
|
||||||
|
testpush.sh
|
||||||
|
59
apps/ios/Shared/AppDelegate.swift
Normal file
59
apps/ios/Shared/AppDelegate.swift
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
//
|
||||||
|
// AppDelegate.swift
|
||||||
|
// SimpleX
|
||||||
|
//
|
||||||
|
// Created by Evgeny on 30/03/2022.
|
||||||
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class AppDelegate: NSObject, UIApplicationDelegate {
|
||||||
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
|
||||||
|
logger.debug("AppDelegate: didFinishLaunchingWithOptions")
|
||||||
|
application.registerForRemoteNotifications()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
|
||||||
|
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
|
||||||
|
ChatModel.shared.deviceToken = token
|
||||||
|
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
|
||||||
|
logger.error("AppDelegate: didFailToRegisterForRemoteNotificationsWithError \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
|
||||||
|
func application(_ application: UIApplication,
|
||||||
|
didReceiveRemoteNotification userInfo: [AnyHashable : Any],
|
||||||
|
fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||||
|
logger.debug("AppDelegate: didReceiveRemoteNotification")
|
||||||
|
print(userInfo)
|
||||||
|
if let ntfData = userInfo["notificationData"] as? [AnyHashable : Any] {
|
||||||
|
if let verification = ntfData["verification"] as? String {
|
||||||
|
logger.debug("AppDelegate: didReceiveRemoteNotification: verification, confirming \(verification)")
|
||||||
|
// TODO send to chat
|
||||||
|
completionHandler(.newData)
|
||||||
|
} else if let checkMessages = ntfData["checkMessages"] as? Bool, checkMessages {
|
||||||
|
// TODO check if app in background
|
||||||
|
logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessages")
|
||||||
|
receiveMessages(completionHandler)
|
||||||
|
} else if let smpQueue = ntfData["checkMessage"] as? String {
|
||||||
|
// TODO check if app in background
|
||||||
|
logger.debug("AppDelegate: didReceiveRemoteNotification: checkMessage \(smpQueue)")
|
||||||
|
receiveMessages(completionHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func receiveMessages(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
|
||||||
|
let complete = BGManager.shared.completionHandler {
|
||||||
|
logger.debug("AppDelegate: completed BGManager.receiveMessages")
|
||||||
|
completionHandler(.newData)
|
||||||
|
}
|
||||||
|
|
||||||
|
BGManager.shared.receiveMessages(complete)
|
||||||
|
}
|
||||||
|
}
|
@ -20,7 +20,7 @@ class BGManager {
|
|||||||
static let shared = BGManager()
|
static let shared = BGManager()
|
||||||
var chatReceiver: ChatReceiver?
|
var chatReceiver: ChatReceiver?
|
||||||
var bgTimer: Timer?
|
var bgTimer: Timer?
|
||||||
var completed = false
|
var completed = true
|
||||||
|
|
||||||
func register() {
|
func register() {
|
||||||
logger.debug("BGManager.register")
|
logger.debug("BGManager.register")
|
||||||
@ -43,36 +43,48 @@ class BGManager {
|
|||||||
private func handleRefresh(_ task: BGAppRefreshTask) {
|
private func handleRefresh(_ task: BGAppRefreshTask) {
|
||||||
logger.debug("BGManager.handleRefresh")
|
logger.debug("BGManager.handleRefresh")
|
||||||
schedule()
|
schedule()
|
||||||
self.completed = false
|
let completeRefresh = completionHandler {
|
||||||
|
task.setTaskCompleted(success: true)
|
||||||
|
}
|
||||||
|
task.expirationHandler = { completeRefresh("expirationHandler") }
|
||||||
|
receiveMessages(completeRefresh)
|
||||||
|
}
|
||||||
|
|
||||||
let completeTask: (String) -> Void = { reason in
|
func completionHandler(_ complete: @escaping () -> Void) -> ((String) -> Void) {
|
||||||
logger.debug("BGManager.handleRefresh completeTask: \(reason)")
|
{ reason in
|
||||||
|
logger.debug("BGManager.completionHandler: \(reason)")
|
||||||
if !self.completed {
|
if !self.completed {
|
||||||
self.completed = true
|
self.completed = true
|
||||||
self.chatReceiver?.stop()
|
self.chatReceiver?.stop()
|
||||||
self.chatReceiver = nil
|
self.chatReceiver = nil
|
||||||
self.bgTimer?.invalidate()
|
self.bgTimer?.invalidate()
|
||||||
self.bgTimer = nil
|
self.bgTimer = nil
|
||||||
task.setTaskCompleted(success: true)
|
complete()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
task.expirationHandler = { completeTask("expirationHandler") }
|
func receiveMessages(_ completeReceiving: @escaping (String) -> Void) {
|
||||||
|
if (!self.completed) {
|
||||||
|
logger.debug("BGManager.receiveMessages: in progress, exiting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
self.completed = false
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
initializeChat()
|
initializeChat()
|
||||||
if ChatModel.shared.currentUser == nil {
|
if ChatModel.shared.currentUser == nil {
|
||||||
completeTask("no current user")
|
completeReceiving("no current user")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.debug("BGManager.handleRefresh: starting chat")
|
logger.debug("BGManager.receiveMessages: starting chat")
|
||||||
let cr = ChatReceiver()
|
let cr = ChatReceiver()
|
||||||
self.chatReceiver = cr
|
self.chatReceiver = cr
|
||||||
cr.start()
|
cr.start()
|
||||||
RunLoop.current.add(Timer(timeInterval: 2, repeats: true) { timer in
|
RunLoop.current.add(Timer(timeInterval: 2, repeats: true) { timer in
|
||||||
logger.debug("BGManager.handleRefresh: timer")
|
logger.debug("BGManager.receiveMessages: timer")
|
||||||
self.bgTimer = timer
|
self.bgTimer = timer
|
||||||
if cr.lastMsgTime.distance(to: Date.now) >= waitForMessages {
|
if cr.lastMsgTime.distance(to: Date.now) >= waitForMessages {
|
||||||
completeTask("timer (no messages after \(waitForMessages) seconds)")
|
completeReceiving("timer (no messages after \(waitForMessages) seconds)")
|
||||||
}
|
}
|
||||||
}, forMode: .default)
|
}, forMode: .default)
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,7 @@ final class ChatModel: ObservableObject {
|
|||||||
@Published var userAddress: String?
|
@Published var userAddress: String?
|
||||||
@Published var userSMPServers: [String]?
|
@Published var userSMPServers: [String]?
|
||||||
@Published var appOpenUrl: URL?
|
@Published var appOpenUrl: URL?
|
||||||
|
@Published var deviceToken: String?
|
||||||
|
|
||||||
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
|
var messageDelivery: Dictionary<Int64, () -> Void> = [:]
|
||||||
|
|
||||||
|
@ -25,6 +25,8 @@ enum ChatCommand {
|
|||||||
case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent)
|
case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent)
|
||||||
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent)
|
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent)
|
||||||
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
||||||
|
case apiRegisterToken(token: String)
|
||||||
|
case apiVerifyToken(token: String, code: String)
|
||||||
case getUserSMPServers
|
case getUserSMPServers
|
||||||
case setUserSMPServers(smpServers: [String])
|
case setUserSMPServers(smpServers: [String])
|
||||||
case addContact
|
case addContact
|
||||||
@ -59,6 +61,8 @@ enum ChatCommand {
|
|||||||
}
|
}
|
||||||
case let .apiUpdateChatItem(type, id, itemId, mc): return "/_update item \(ref(type, id)) \(itemId) \(mc.cmdString)"
|
case let .apiUpdateChatItem(type, id, itemId, mc): return "/_update item \(ref(type, id)) \(itemId) \(mc.cmdString)"
|
||||||
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
|
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
|
||||||
|
case let .apiRegisterToken(token): return "/_ntf register apn \(token)"
|
||||||
|
case let .apiVerifyToken(token, code): return "/_ntf verify apn \(token) \(code)"
|
||||||
case .getUserSMPServers: return "/smp_servers"
|
case .getUserSMPServers: return "/smp_servers"
|
||||||
case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))"
|
case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))"
|
||||||
case .addContact: return "/connect"
|
case .addContact: return "/connect"
|
||||||
@ -90,6 +94,8 @@ enum ChatCommand {
|
|||||||
case .apiSendMessage: return "apiSendMessage"
|
case .apiSendMessage: return "apiSendMessage"
|
||||||
case .apiUpdateChatItem: return "apiUpdateChatItem"
|
case .apiUpdateChatItem: return "apiUpdateChatItem"
|
||||||
case .apiDeleteChatItem: return "apiDeleteChatItem"
|
case .apiDeleteChatItem: return "apiDeleteChatItem"
|
||||||
|
case .apiRegisterToken: return "apiRegisterToken"
|
||||||
|
case .apiVerifyToken: return "apiRegisterToken"
|
||||||
case .getUserSMPServers: return "getUserSMPServers"
|
case .getUserSMPServers: return "getUserSMPServers"
|
||||||
case .setUserSMPServers: return "setUserSMPServers"
|
case .setUserSMPServers: return "setUserSMPServers"
|
||||||
case .addContact: return "addContact"
|
case .addContact: return "addContact"
|
||||||
@ -444,6 +450,18 @@ func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteM
|
|||||||
throw r
|
throw r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func apiRegisterToken(token: String) async throws {
|
||||||
|
let r = await chatSendCmd(.apiRegisterToken(token: token))
|
||||||
|
if case .cmdOk = r { return }
|
||||||
|
throw r
|
||||||
|
}
|
||||||
|
|
||||||
|
func apiVerifyToken(token: String, code: String) async throws {
|
||||||
|
let r = await chatSendCmd(.apiVerifyToken(token: token, code: code))
|
||||||
|
if case .cmdOk = r { return }
|
||||||
|
throw r
|
||||||
|
}
|
||||||
|
|
||||||
func getUserSMPServers() throws -> [String] {
|
func getUserSMPServers() throws -> [String] {
|
||||||
let r = chatSendCmdSync(.getUserSMPServers)
|
let r = chatSendCmdSync(.getUserSMPServers)
|
||||||
if case let .userSMPServers(smpServers) = r { return smpServers }
|
if case let .userSMPServers(smpServers) = r { return smpServers }
|
||||||
|
@ -12,6 +12,7 @@ let logger = Logger()
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct SimpleXApp: App {
|
struct SimpleXApp: App {
|
||||||
|
@UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||||
@StateObject private var chatModel = ChatModel.shared
|
@StateObject private var chatModel = ChatModel.shared
|
||||||
@Environment(\.scenePhase) var scenePhase
|
@Environment(\.scenePhase) var scenePhase
|
||||||
|
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
<plist version="1.0">
|
<plist version="1.0">
|
||||||
<dict>
|
<dict>
|
||||||
|
<key>aps-environment</key>
|
||||||
|
<string>development</string>
|
||||||
<key>com.apple.developer.associated-domains</key>
|
<key>com.apple.developer.associated-domains</key>
|
||||||
<array>
|
<array>
|
||||||
<string>applinks:simplex.chat</string>
|
<string>applinks:simplex.chat</string>
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
<key>UIBackgroundModes</key>
|
<key>UIBackgroundModes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>fetch</string>
|
<string>fetch</string>
|
||||||
|
<string>remote-notification</string>
|
||||||
</array>
|
</array>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E261127A30FEA00F70299 /* TerminalView.swift */; };
|
5C2E261227A30FEA00F70299 /* TerminalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C2E261127A30FEA00F70299 /* TerminalView.swift */; };
|
||||||
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; };
|
5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; };
|
||||||
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
|
5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
|
||||||
|
5C35CFCC27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; };
|
||||||
|
5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; };
|
||||||
|
5C36027427F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; };
|
||||||
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
|
5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; };
|
||||||
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
|
5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; };
|
||||||
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C5346A727B59A6A004DF848 /* ChatHelp.swift */; };
|
||||||
@ -94,6 +97,7 @@
|
|||||||
5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
|
5C2E261127A30FEA00F70299 /* TerminalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalView.swift; sourceTree = "<group>"; };
|
||||||
5C35CFC727B2782E00FB6C6D /* BGManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGManager.swift; sourceTree = "<group>"; };
|
5C35CFC727B2782E00FB6C6D /* BGManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGManager.swift; sourceTree = "<group>"; };
|
||||||
5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfManager.swift; sourceTree = "<group>"; };
|
5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfManager.swift; sourceTree = "<group>"; };
|
||||||
|
5C36027227F47AD5009F19D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = "<group>"; };
|
5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = "<group>"; };
|
||||||
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = "<group>"; };
|
5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.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>"; };
|
||||||
@ -274,6 +278,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
5CA059C3279559F40002BEB4 /* SimpleXApp.swift */,
|
5CA059C3279559F40002BEB4 /* SimpleXApp.swift */,
|
||||||
|
5C36027227F47AD5009F19D9 /* AppDelegate.swift */,
|
||||||
5CA059C4279559F40002BEB4 /* ContentView.swift */,
|
5CA059C4279559F40002BEB4 /* ContentView.swift */,
|
||||||
64DAE1502809D9F5000DA960 /* FileUtils.swift */,
|
64DAE1502809D9F5000DA960 /* FileUtils.swift */,
|
||||||
5C764E87279CBC8E000C6508 /* Model */,
|
5C764E87279CBC8E000C6508 /* Model */,
|
||||||
@ -475,6 +480,7 @@
|
|||||||
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */,
|
5C6AD81327A834E300348BD7 /* NewChatButton.swift in Sources */,
|
||||||
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
|
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */,
|
||||||
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */,
|
5CEACCE327DE9246000BD591 /* ComposeView.swift in Sources */,
|
||||||
|
5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */,
|
||||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */,
|
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */,
|
||||||
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */,
|
5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */,
|
||||||
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */,
|
5C5346A827B59A6A004DF848 /* ChatHelp.swift in Sources */,
|
||||||
|
@ -3,7 +3,7 @@ packages: .
|
|||||||
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: d38303d5f1d55a90dd95d951ba8d798e17699269
|
tag: 7774fc327173ac86f5b94bc16742296f1d1fb85f
|
||||||
|
|
||||||
source-repository-package
|
source-repository-package
|
||||||
type: git
|
type: git
|
||||||
|
@ -30,7 +30,7 @@ dependencies:
|
|||||||
- optparse-applicative >= 0.15 && < 0.17
|
- optparse-applicative >= 0.15 && < 0.17
|
||||||
- process == 1.6.*
|
- process == 1.6.*
|
||||||
- simple-logger == 0.1.*
|
- simple-logger == 0.1.*
|
||||||
- simplexmq >= 1.0 && < 1.1
|
- simplexmq >= 1.1 && < 3.0
|
||||||
- sqlite-simple == 0.4.*
|
- sqlite-simple == 0.4.*
|
||||||
- stm == 2.5.*
|
- stm == 2.5.*
|
||||||
- terminal == 0.2.*
|
- terminal == 0.2.*
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"https://github.com/simplex-chat/simplexmq.git"."d38303d5f1d55a90dd95d951ba8d798e17699269" = "0gx9wlk0v8ml6aid9hzm1nqy357plk8sbnhfcnchna12dgm8v0sd";
|
"https://github.com/simplex-chat/simplexmq.git"."7774fc327173ac86f5b94bc16742296f1d1fb85f" = "0cj4hywxmsiljgfv20syyn8cbhp56pxr5s0hfmik37l4ii8pj8rs";
|
||||||
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
|
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
|
||||||
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
||||||
"https://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97";
|
"https://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97";
|
||||||
|
@ -72,7 +72,7 @@ library
|
|||||||
, optparse-applicative >=0.15 && <0.17
|
, optparse-applicative >=0.15 && <0.17
|
||||||
, process ==1.6.*
|
, process ==1.6.*
|
||||||
, simple-logger ==0.1.*
|
, simple-logger ==0.1.*
|
||||||
, simplexmq ==1.0.*
|
, simplexmq >=1.1 && <3.0
|
||||||
, sqlite-simple ==0.4.*
|
, sqlite-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
@ -109,7 +109,7 @@ executable simplex-bot
|
|||||||
, process ==1.6.*
|
, process ==1.6.*
|
||||||
, simple-logger ==0.1.*
|
, simple-logger ==0.1.*
|
||||||
, simplex-chat
|
, simplex-chat
|
||||||
, simplexmq ==1.0.*
|
, simplexmq >=1.1 && <3.0
|
||||||
, sqlite-simple ==0.4.*
|
, sqlite-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
@ -146,7 +146,7 @@ executable simplex-bot-advanced
|
|||||||
, process ==1.6.*
|
, process ==1.6.*
|
||||||
, simple-logger ==0.1.*
|
, simple-logger ==0.1.*
|
||||||
, simplex-chat
|
, simplex-chat
|
||||||
, simplexmq ==1.0.*
|
, simplexmq >=1.1 && <3.0
|
||||||
, sqlite-simple ==0.4.*
|
, sqlite-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
@ -183,7 +183,7 @@ executable simplex-chat
|
|||||||
, process ==1.6.*
|
, process ==1.6.*
|
||||||
, simple-logger ==0.1.*
|
, simple-logger ==0.1.*
|
||||||
, simplex-chat
|
, simplex-chat
|
||||||
, simplexmq ==1.0.*
|
, simplexmq >=1.1 && <3.0
|
||||||
, sqlite-simple ==0.4.*
|
, sqlite-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
@ -229,7 +229,7 @@ test-suite simplex-chat-test
|
|||||||
, process ==1.6.*
|
, process ==1.6.*
|
||||||
, simple-logger ==0.1.*
|
, simple-logger ==0.1.*
|
||||||
, simplex-chat
|
, simplex-chat
|
||||||
, simplexmq ==1.0.*
|
, simplexmq >=1.1 && <3.0
|
||||||
, sqlite-simple ==0.4.*
|
, sqlite-simple ==0.4.*
|
||||||
, stm ==2.5.*
|
, stm ==2.5.*
|
||||||
, terminal ==0.2.*
|
, terminal ==0.2.*
|
||||||
|
@ -49,11 +49,12 @@ import Simplex.Chat.Store
|
|||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Util (ifM, safeDecodeUtf8, unlessM, whenM)
|
import Simplex.Chat.Util (ifM, safeDecodeUtf8, unlessM, whenM)
|
||||||
import Simplex.Messaging.Agent
|
import Simplex.Messaging.Agent
|
||||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), defaultAgentConfig)
|
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), defaultAgentConfig)
|
||||||
import Simplex.Messaging.Agent.Protocol
|
import Simplex.Messaging.Agent.Protocol
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding
|
import Simplex.Messaging.Encoding
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
|
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), PushProvider (..))
|
||||||
import Simplex.Messaging.Parsers (base64P, parseAll)
|
import Simplex.Messaging.Parsers (base64P, parseAll)
|
||||||
import Simplex.Messaging.Protocol (ErrorType (..), MsgBody)
|
import Simplex.Messaging.Protocol (ErrorType (..), MsgBody)
|
||||||
import qualified Simplex.Messaging.Protocol as SMP
|
import qualified Simplex.Messaging.Protocol as SMP
|
||||||
@ -75,8 +76,7 @@ defaultChatConfig =
|
|||||||
{ agentConfig =
|
{ agentConfig =
|
||||||
defaultAgentConfig
|
defaultAgentConfig
|
||||||
{ tcpPort = undefined, -- agent does not listen to TCP
|
{ tcpPort = undefined, -- agent does not listen to TCP
|
||||||
initialSMPServers = undefined, -- filled in newChatController
|
dbFile = "simplex_v1",
|
||||||
dbFile = undefined, -- filled in newChatController
|
|
||||||
dbPoolSize = 1,
|
dbPoolSize = 1,
|
||||||
yesToMigrations = False
|
yesToMigrations = False
|
||||||
},
|
},
|
||||||
@ -109,7 +109,8 @@ newChatController chatStore user cfg@ChatConfig {agentConfig = aCfg, tbqSize} Ch
|
|||||||
firstTime <- not <$> doesFileExist f
|
firstTime <- not <$> doesFileExist f
|
||||||
currentUser <- newTVarIO user
|
currentUser <- newTVarIO user
|
||||||
initialSMPServers <- resolveServers
|
initialSMPServers <- resolveServers
|
||||||
smpAgent <- getSMPAgentClient aCfg {dbFile = dbFilePrefix <> "_agent.db", initialSMPServers}
|
let servers = InitialAgentServers {smp = initialSMPServers, ntf = ["smp://smAc80rtvJKA02nysCCmiDzMUmcGnYA3gujwKU1NT30=@127.0.0.1:443"]}
|
||||||
|
smpAgent <- getSMPAgentClient aCfg {dbFile = dbFilePrefix <> "_agent.db"} servers
|
||||||
agentAsync <- newTVarIO Nothing
|
agentAsync <- newTVarIO Nothing
|
||||||
idsDrg <- newTVarIO =<< drgNew
|
idsDrg <- newTVarIO =<< drgNew
|
||||||
inputQ <- newTBQueueIO tbqSize
|
inputQ <- newTBQueueIO tbqSize
|
||||||
@ -369,6 +370,10 @@ processChatCommand = \case
|
|||||||
pure $ CRContactRequestRejected cReq
|
pure $ CRContactRequestRejected cReq
|
||||||
APIUpdateProfile profile -> withUser (`updateProfile` profile)
|
APIUpdateProfile profile -> withUser (`updateProfile` profile)
|
||||||
APIParseMarkdown text -> pure . CRApiParsedMarkdown $ parseMaybeMarkdownList text
|
APIParseMarkdown text -> pure . CRApiParsedMarkdown $ parseMaybeMarkdownList text
|
||||||
|
APIRegisterToken token -> withUser $ \_ -> withAgent (`registerNtfToken` token) $> CRCmdOk
|
||||||
|
APIVerifyToken token code nonce -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token code nonce) $> CRCmdOk
|
||||||
|
APIIntervalNofication token interval -> withUser $ \_ -> withAgent (\a -> enableNtfCron a token interval) $> CRCmdOk
|
||||||
|
APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) $> CRCmdOk
|
||||||
GetUserSMPServers -> CRUserSMPServers <$> withUser (\user -> withStore (`getSMPServers` user))
|
GetUserSMPServers -> CRUserSMPServers <$> withUser (\user -> withStore (`getSMPServers` user))
|
||||||
SetUserSMPServers smpServers -> withUser $ \user -> withChatLock $ do
|
SetUserSMPServers smpServers -> withUser $ \user -> withChatLock $ do
|
||||||
withStore $ \st -> overwriteSMPServers st user smpServers
|
withStore $ \st -> overwriteSMPServers st user smpServers
|
||||||
@ -1893,6 +1898,10 @@ chatCommandP =
|
|||||||
<|> "/_reject " *> (APIRejectContact <$> A.decimal)
|
<|> "/_reject " *> (APIRejectContact <$> A.decimal)
|
||||||
<|> "/_profile " *> (APIUpdateProfile <$> jsonP)
|
<|> "/_profile " *> (APIUpdateProfile <$> jsonP)
|
||||||
<|> "/_parse " *> (APIParseMarkdown . safeDecodeUtf8 <$> A.takeByteString)
|
<|> "/_parse " *> (APIParseMarkdown . safeDecodeUtf8 <$> A.takeByteString)
|
||||||
|
<|> "/_ntf register " *> (APIRegisterToken <$> tokenP)
|
||||||
|
<|> "/_ntf verify " *> (APIVerifyToken <$> tokenP <* A.space <*> strP <* A.space <*> strP)
|
||||||
|
<|> "/_ntf interval " *> (APIIntervalNofication <$> tokenP <* A.space <*> A.decimal)
|
||||||
|
<|> "/_ntf delete " *> (APIDeleteToken <$> tokenP)
|
||||||
<|> "/smp_servers default" $> SetUserSMPServers []
|
<|> "/smp_servers default" $> SetUserSMPServers []
|
||||||
<|> "/smp_servers " *> (SetUserSMPServers <$> smpServersP)
|
<|> "/smp_servers " *> (SetUserSMPServers <$> smpServersP)
|
||||||
<|> "/smp_servers" $> GetUserSMPServers
|
<|> "/smp_servers" $> GetUserSMPServers
|
||||||
@ -1958,6 +1967,10 @@ chatCommandP =
|
|||||||
"text " *> (MCText . safeDecodeUtf8 <$> A.takeByteString)
|
"text " *> (MCText . safeDecodeUtf8 <$> A.takeByteString)
|
||||||
<|> "json " *> jsonP
|
<|> "json " *> jsonP
|
||||||
ciDeleteMode = "broadcast" $> CIDMBroadcast <|> "internal" $> CIDMInternal
|
ciDeleteMode = "broadcast" $> CIDMBroadcast <|> "internal" $> CIDMInternal
|
||||||
|
tokenP = "apns " *> (DeviceToken PPApns <$> hexStringP)
|
||||||
|
hexStringP =
|
||||||
|
A.takeWhile (\c -> (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')) >>= \s ->
|
||||||
|
if even (B.length s) then pure s else fail "odd number of hex characters"
|
||||||
displayName = safeDecodeUtf8 <$> (B.cons <$> A.satisfy refChar <*> A.takeTill (== ' '))
|
displayName = safeDecodeUtf8 <$> (B.cons <$> A.satisfy refChar <*> A.takeTill (== ' '))
|
||||||
sendMsgQuote msgDir = SendMessageQuote <$> displayName <* A.space <*> pure msgDir <*> quotedMsg <*> A.takeByteString
|
sendMsgQuote msgDir = SendMessageQuote <$> displayName <* A.space <*> pure msgDir <*> quotedMsg <*> A.takeByteString
|
||||||
quotedMsg = A.char '(' *> A.takeTill (== ')') <* A.char ')' <* optional A.space
|
quotedMsg = A.char '(' *> A.takeTill (== ')') <* A.char ')' <* optional A.space
|
||||||
|
@ -22,6 +22,7 @@ import Data.Map.Strict (Map)
|
|||||||
import Data.Text (Text)
|
import Data.Text (Text)
|
||||||
import Data.Time (ZonedTime)
|
import Data.Time (ZonedTime)
|
||||||
import Data.Version (showVersion)
|
import Data.Version (showVersion)
|
||||||
|
import Data.Word (Word16)
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Numeric.Natural
|
import Numeric.Natural
|
||||||
import qualified Paths_simplex_chat as SC
|
import qualified Paths_simplex_chat as SC
|
||||||
@ -34,6 +35,8 @@ import Simplex.Messaging.Agent (AgentClient)
|
|||||||
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig)
|
import Simplex.Messaging.Agent.Env.SQLite (AgentConfig)
|
||||||
import Simplex.Messaging.Agent.Protocol
|
import Simplex.Messaging.Agent.Protocol
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore)
|
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore)
|
||||||
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
|
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..))
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, sumTypeJSON)
|
||||||
import Simplex.Messaging.Protocol (CorrId)
|
import Simplex.Messaging.Protocol (CorrId)
|
||||||
import System.IO (Handle)
|
import System.IO (Handle)
|
||||||
@ -106,6 +109,10 @@ data ChatCommand
|
|||||||
| APIRejectContact Int64
|
| APIRejectContact Int64
|
||||||
| APIUpdateProfile Profile
|
| APIUpdateProfile Profile
|
||||||
| APIParseMarkdown Text
|
| APIParseMarkdown Text
|
||||||
|
| APIRegisterToken DeviceToken
|
||||||
|
| APIVerifyToken DeviceToken ByteString C.CbNonce
|
||||||
|
| APIIntervalNofication DeviceToken Word16
|
||||||
|
| APIDeleteToken DeviceToken
|
||||||
| GetUserSMPServers
|
| GetUserSMPServers
|
||||||
| SetUserSMPServers [SMPServer]
|
| SetUserSMPServers [SMPServer]
|
||||||
| ChatHelp HelpSection
|
| ChatHelp HelpSection
|
||||||
|
@ -33,9 +33,8 @@ import Simplex.Chat.Protocol
|
|||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Util (eitherToMaybe, safeDecodeUtf8)
|
import Simplex.Chat.Util (eitherToMaybe, safeDecodeUtf8)
|
||||||
import Simplex.Messaging.Agent.Protocol (AgentErrorType, AgentMsgId, MsgMeta (..))
|
import Simplex.Messaging.Agent.Protocol (AgentErrorType, AgentMsgId, MsgMeta (..))
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (fromTextField_)
|
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, singleFieldJSON, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, singleFieldJSON, sumTypeJSON)
|
||||||
import Simplex.Messaging.Protocol (MsgBody)
|
import Simplex.Messaging.Protocol (MsgBody)
|
||||||
import Simplex.Messaging.Util ((<$?>))
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import qualified Data.Attoparsec.ByteString.Char8 as A
|
|||||||
import qualified Data.ByteString.Char8 as B
|
import qualified Data.ByteString.Char8 as B
|
||||||
import Options.Applicative
|
import Options.Applicative
|
||||||
import Simplex.Chat.Controller (updateStr, versionStr)
|
import Simplex.Chat.Controller (updateStr, versionStr)
|
||||||
import Simplex.Messaging.Agent.Protocol (SMPServer (..))
|
import Simplex.Messaging.Agent.Protocol (SMPServer)
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (parseAll)
|
import Simplex.Messaging.Parsers (parseAll)
|
||||||
import System.FilePath (combine)
|
import System.FilePath (combine)
|
||||||
|
@ -31,8 +31,8 @@ import Database.SQLite.Simple.ToField (ToField (..))
|
|||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Util (eitherToMaybe, safeDecodeUtf8)
|
import Simplex.Chat.Util (eitherToMaybe, safeDecodeUtf8)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (fromTextField_)
|
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
|
import Simplex.Messaging.Parsers (fromTextField_)
|
||||||
import Simplex.Messaging.Util ((<$?>))
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
|
||||||
data ConnectionEntity
|
data ConnectionEntity
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
{-# LANGUAGE PatternSynonyms #-}
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
{-# LANGUAGE ScopedTypeVariables #-}
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
@ -195,12 +196,13 @@ import Simplex.Chat.Migrations.M20220404_files_status_fields
|
|||||||
import Simplex.Chat.Protocol
|
import Simplex.Chat.Protocol
|
||||||
import Simplex.Chat.Types
|
import Simplex.Chat.Types
|
||||||
import Simplex.Chat.Util (eitherToMaybe)
|
import Simplex.Chat.Util (eitherToMaybe)
|
||||||
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, InvitationId, MsgMeta (..), SMPServer (..))
|
import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, InvitationId, MsgMeta (..))
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore (..), createSQLiteStore, firstRow, withTransaction)
|
import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore (..), createSQLiteStore, firstRow, withTransaction)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String (StrEncoding (strEncode))
|
import Simplex.Messaging.Encoding.String (StrEncoding (strEncode))
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
||||||
|
import Simplex.Messaging.Protocol (ProtocolServer (..), SMPServer, pattern SMPServer)
|
||||||
import Simplex.Messaging.Util (liftIOEither, (<$$>))
|
import Simplex.Messaging.Util (liftIOEither, (<$$>))
|
||||||
import System.FilePath (takeFileName)
|
import System.FilePath (takeFileName)
|
||||||
import UnliftIO.STM
|
import UnliftIO.STM
|
||||||
@ -3552,7 +3554,7 @@ overwriteSMPServers st User {userId} smpServers = do
|
|||||||
liftIOEither . checkConstraint SEUniqueID . withTransaction st $ \db -> do
|
liftIOEither . checkConstraint SEUniqueID . withTransaction st $ \db -> do
|
||||||
currentTs <- getCurrentTime
|
currentTs <- getCurrentTime
|
||||||
DB.execute db "DELETE FROM smp_servers WHERE user_id = ?" (Only userId)
|
DB.execute db "DELETE FROM smp_servers WHERE user_id = ?" (Only userId)
|
||||||
forM_ smpServers $ \SMPServer {host, port, keyHash} ->
|
forM_ smpServers $ \ProtocolServer {host, port, keyHash} ->
|
||||||
DB.execute
|
DB.execute
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
|
@ -32,9 +32,8 @@ import Database.SQLite.Simple.Ok (Ok (Ok))
|
|||||||
import Database.SQLite.Simple.ToField (ToField (..))
|
import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Messaging.Agent.Protocol (ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId)
|
import Simplex.Messaging.Agent.Protocol (ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId)
|
||||||
import Simplex.Messaging.Agent.Store.SQLite (fromTextField_)
|
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON)
|
||||||
import Simplex.Messaging.Util ((<$?>))
|
import Simplex.Messaging.Util ((<$?>))
|
||||||
|
|
||||||
class IsContact a where
|
class IsContact a where
|
||||||
@ -398,25 +397,25 @@ data GroupMemberCategory
|
|||||||
| GCPostMember -- member who joined after the user to whom the user was introduced (user receives x.grp.mem.new announcing these members and then x.grp.mem.fwd with invitation from these members)
|
| GCPostMember -- member who joined after the user to whom the user was introduced (user receives x.grp.mem.new announcing these members and then x.grp.mem.fwd with invitation from these members)
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance FromField GroupMemberCategory where fromField = fromTextField_ decodeText
|
instance FromField GroupMemberCategory where fromField = fromTextField_ textDecode
|
||||||
|
|
||||||
instance ToField GroupMemberCategory where toField = toField . encodeText
|
instance ToField GroupMemberCategory where toField = toField . textEncode
|
||||||
|
|
||||||
instance FromJSON GroupMemberCategory where parseJSON = textParseJSON "GroupMemberCategory"
|
instance FromJSON GroupMemberCategory where parseJSON = textParseJSON "GroupMemberCategory"
|
||||||
|
|
||||||
instance ToJSON GroupMemberCategory where
|
instance ToJSON GroupMemberCategory where
|
||||||
toJSON = J.String . encodeText
|
toJSON = J.String . textEncode
|
||||||
toEncoding = JE.text . encodeText
|
toEncoding = JE.text . textEncode
|
||||||
|
|
||||||
instance TextEncoding GroupMemberCategory where
|
instance TextEncoding GroupMemberCategory where
|
||||||
decodeText = \case
|
textDecode = \case
|
||||||
"user" -> Just GCUserMember
|
"user" -> Just GCUserMember
|
||||||
"invitee" -> Just GCInviteeMember
|
"invitee" -> Just GCInviteeMember
|
||||||
"host" -> Just GCHostMember
|
"host" -> Just GCHostMember
|
||||||
"pre" -> Just GCPreMember
|
"pre" -> Just GCPreMember
|
||||||
"post" -> Just GCPostMember
|
"post" -> Just GCPostMember
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
encodeText = \case
|
textEncode = \case
|
||||||
GCUserMember -> "user"
|
GCUserMember -> "user"
|
||||||
GCInviteeMember -> "invitee"
|
GCInviteeMember -> "invitee"
|
||||||
GCHostMember -> "host"
|
GCHostMember -> "host"
|
||||||
@ -437,15 +436,15 @@ data GroupMemberStatus
|
|||||||
| GSMemCreator -- user member that created the group (only GCUserMember)
|
| GSMemCreator -- user member that created the group (only GCUserMember)
|
||||||
deriving (Eq, Show, Ord)
|
deriving (Eq, Show, Ord)
|
||||||
|
|
||||||
instance FromField GroupMemberStatus where fromField = fromTextField_ decodeText
|
instance FromField GroupMemberStatus where fromField = fromTextField_ textDecode
|
||||||
|
|
||||||
instance ToField GroupMemberStatus where toField = toField . encodeText
|
instance ToField GroupMemberStatus where toField = toField . textEncode
|
||||||
|
|
||||||
instance FromJSON GroupMemberStatus where parseJSON = textParseJSON "GroupMemberStatus"
|
instance FromJSON GroupMemberStatus where parseJSON = textParseJSON "GroupMemberStatus"
|
||||||
|
|
||||||
instance ToJSON GroupMemberStatus where
|
instance ToJSON GroupMemberStatus where
|
||||||
toJSON = J.String . encodeText
|
toJSON = J.String . textEncode
|
||||||
toEncoding = JE.text . encodeText
|
toEncoding = JE.text . textEncode
|
||||||
|
|
||||||
memberActive :: GroupMember -> Bool
|
memberActive :: GroupMember -> Bool
|
||||||
memberActive m = case memberStatus m of
|
memberActive m = case memberStatus m of
|
||||||
@ -476,7 +475,7 @@ memberCurrent m = case memberStatus m of
|
|||||||
GSMemCreator -> True
|
GSMemCreator -> True
|
||||||
|
|
||||||
instance TextEncoding GroupMemberStatus where
|
instance TextEncoding GroupMemberStatus where
|
||||||
decodeText = \case
|
textDecode = \case
|
||||||
"removed" -> Just GSMemRemoved
|
"removed" -> Just GSMemRemoved
|
||||||
"left" -> Just GSMemLeft
|
"left" -> Just GSMemLeft
|
||||||
"deleted" -> Just GSMemGroupDeleted
|
"deleted" -> Just GSMemGroupDeleted
|
||||||
@ -489,7 +488,7 @@ instance TextEncoding GroupMemberStatus where
|
|||||||
"complete" -> Just GSMemComplete
|
"complete" -> Just GSMemComplete
|
||||||
"creator" -> Just GSMemCreator
|
"creator" -> Just GSMemCreator
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
encodeText = \case
|
textEncode = \case
|
||||||
GSMemRemoved -> "removed"
|
GSMemRemoved -> "removed"
|
||||||
GSMemLeft -> "left"
|
GSMemLeft -> "left"
|
||||||
GSMemGroupDeleted -> "deleted"
|
GSMemGroupDeleted -> "deleted"
|
||||||
@ -633,25 +632,25 @@ fileTransferCancelled (FTRcv RcvFileTransfer {cancelled}) = cancelled
|
|||||||
|
|
||||||
data FileStatus = FSNew | FSAccepted | FSConnected | FSComplete | FSCancelled deriving (Eq, Ord, Show)
|
data FileStatus = FSNew | FSAccepted | FSConnected | FSComplete | FSCancelled deriving (Eq, Ord, Show)
|
||||||
|
|
||||||
instance FromField FileStatus where fromField = fromTextField_ decodeText
|
instance FromField FileStatus where fromField = fromTextField_ textDecode
|
||||||
|
|
||||||
instance ToField FileStatus where toField = toField . encodeText
|
instance ToField FileStatus where toField = toField . textEncode
|
||||||
|
|
||||||
instance FromJSON FileStatus where parseJSON = textParseJSON "FileStatus"
|
instance FromJSON FileStatus where parseJSON = textParseJSON "FileStatus"
|
||||||
|
|
||||||
instance ToJSON FileStatus where
|
instance ToJSON FileStatus where
|
||||||
toJSON = J.String . encodeText
|
toJSON = J.String . textEncode
|
||||||
toEncoding = JE.text . encodeText
|
toEncoding = JE.text . textEncode
|
||||||
|
|
||||||
instance TextEncoding FileStatus where
|
instance TextEncoding FileStatus where
|
||||||
decodeText = \case
|
textDecode = \case
|
||||||
"new" -> Just FSNew
|
"new" -> Just FSNew
|
||||||
"accepted" -> Just FSAccepted
|
"accepted" -> Just FSAccepted
|
||||||
"connected" -> Just FSConnected
|
"connected" -> Just FSConnected
|
||||||
"complete" -> Just FSComplete
|
"complete" -> Just FSComplete
|
||||||
"cancelled" -> Just FSCancelled
|
"cancelled" -> Just FSCancelled
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
encodeText = \case
|
textEncode = \case
|
||||||
FSNew -> "new"
|
FSNew -> "new"
|
||||||
FSAccepted -> "accepted"
|
FSAccepted -> "accepted"
|
||||||
FSConnected -> "connected"
|
FSConnected -> "connected"
|
||||||
@ -701,18 +700,18 @@ data ConnStatus
|
|||||||
ConnDeleted
|
ConnDeleted
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance FromField ConnStatus where fromField = fromTextField_ decodeText
|
instance FromField ConnStatus where fromField = fromTextField_ textDecode
|
||||||
|
|
||||||
instance ToField ConnStatus where toField = toField . encodeText
|
instance ToField ConnStatus where toField = toField . textEncode
|
||||||
|
|
||||||
instance FromJSON ConnStatus where parseJSON = textParseJSON "ConnStatus"
|
instance FromJSON ConnStatus where parseJSON = textParseJSON "ConnStatus"
|
||||||
|
|
||||||
instance ToJSON ConnStatus where
|
instance ToJSON ConnStatus where
|
||||||
toJSON = J.String . encodeText
|
toJSON = J.String . textEncode
|
||||||
toEncoding = JE.text . encodeText
|
toEncoding = JE.text . textEncode
|
||||||
|
|
||||||
instance TextEncoding ConnStatus where
|
instance TextEncoding ConnStatus where
|
||||||
decodeText = \case
|
textDecode = \case
|
||||||
"new" -> Just ConnNew
|
"new" -> Just ConnNew
|
||||||
"joined" -> Just ConnJoined
|
"joined" -> Just ConnJoined
|
||||||
"requested" -> Just ConnRequested
|
"requested" -> Just ConnRequested
|
||||||
@ -721,7 +720,7 @@ instance TextEncoding ConnStatus where
|
|||||||
"ready" -> Just ConnReady
|
"ready" -> Just ConnReady
|
||||||
"deleted" -> Just ConnDeleted
|
"deleted" -> Just ConnDeleted
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
encodeText = \case
|
textEncode = \case
|
||||||
ConnNew -> "new"
|
ConnNew -> "new"
|
||||||
ConnJoined -> "joined"
|
ConnJoined -> "joined"
|
||||||
ConnRequested -> "requested"
|
ConnRequested -> "requested"
|
||||||
@ -733,25 +732,25 @@ instance TextEncoding ConnStatus where
|
|||||||
data ConnType = ConnContact | ConnMember | ConnSndFile | ConnRcvFile | ConnUserContact
|
data ConnType = ConnContact | ConnMember | ConnSndFile | ConnRcvFile | ConnUserContact
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
|
||||||
instance FromField ConnType where fromField = fromTextField_ decodeText
|
instance FromField ConnType where fromField = fromTextField_ textDecode
|
||||||
|
|
||||||
instance ToField ConnType where toField = toField . encodeText
|
instance ToField ConnType where toField = toField . textEncode
|
||||||
|
|
||||||
instance FromJSON ConnType where parseJSON = textParseJSON "ConnType"
|
instance FromJSON ConnType where parseJSON = textParseJSON "ConnType"
|
||||||
|
|
||||||
instance ToJSON ConnType where
|
instance ToJSON ConnType where
|
||||||
toJSON = J.String . encodeText
|
toJSON = J.String . textEncode
|
||||||
toEncoding = JE.text . encodeText
|
toEncoding = JE.text . textEncode
|
||||||
|
|
||||||
instance TextEncoding ConnType where
|
instance TextEncoding ConnType where
|
||||||
decodeText = \case
|
textDecode = \case
|
||||||
"contact" -> Just ConnContact
|
"contact" -> Just ConnContact
|
||||||
"member" -> Just ConnMember
|
"member" -> Just ConnMember
|
||||||
"snd_file" -> Just ConnSndFile
|
"snd_file" -> Just ConnSndFile
|
||||||
"rcv_file" -> Just ConnRcvFile
|
"rcv_file" -> Just ConnRcvFile
|
||||||
"user_contact" -> Just ConnUserContact
|
"user_contact" -> Just ConnUserContact
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
encodeText = \case
|
textEncode = \case
|
||||||
ConnContact -> "contact"
|
ConnContact -> "contact"
|
||||||
ConnMember -> "member"
|
ConnMember -> "member"
|
||||||
ConnSndFile -> "snd_file"
|
ConnSndFile -> "snd_file"
|
||||||
@ -812,9 +811,5 @@ data Notification = Notification {title :: Text, text :: Text}
|
|||||||
|
|
||||||
type JSONString = String
|
type JSONString = String
|
||||||
|
|
||||||
class TextEncoding a where
|
|
||||||
encodeText :: a -> Text
|
|
||||||
decodeText :: Text -> Maybe a
|
|
||||||
|
|
||||||
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
|
textParseJSON :: TextEncoding a => String -> J.Value -> JT.Parser a
|
||||||
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . decodeText
|
textParseJSON name = J.withText name $ maybe (fail $ "bad " <> name) pure . textDecode
|
||||||
|
@ -49,7 +49,7 @@ extra-deps:
|
|||||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||||
# - ../simplexmq
|
# - ../simplexmq
|
||||||
- github: simplex-chat/simplexmq
|
- github: simplex-chat/simplexmq
|
||||||
commit: d38303d5f1d55a90dd95d951ba8d798e17699269
|
commit: 7774fc327173ac86f5b94bc16742296f1d1fb85f
|
||||||
# - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
|
# - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
|
||||||
- github: simplex-chat/aeson
|
- github: simplex-chat/aeson
|
||||||
commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7
|
commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
{-# LANGUAGE DuplicateRecordFields #-}
|
{-# LANGUAGE DuplicateRecordFields #-}
|
||||||
{-# LANGUAGE LambdaCase #-}
|
{-# LANGUAGE LambdaCase #-}
|
||||||
{-# LANGUAGE NamedFieldPuns #-}
|
{-# LANGUAGE NamedFieldPuns #-}
|
||||||
|
{-# LANGUAGE NumericUnderscores #-}
|
||||||
{-# LANGUAGE OverloadedLists #-}
|
{-# LANGUAGE OverloadedLists #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE TypeApplications #-}
|
{-# LANGUAGE TypeApplications #-}
|
||||||
@ -182,7 +183,10 @@ serverCfg =
|
|||||||
msgQueueQuota = 4,
|
msgQueueQuota = 4,
|
||||||
queueIdBytes = 12,
|
queueIdBytes = 12,
|
||||||
msgIdBytes = 6,
|
msgIdBytes = 6,
|
||||||
storeLog = Nothing,
|
storeLogFile = Nothing,
|
||||||
|
allowNewQueues = True,
|
||||||
|
messageTTL = Just $ 7 * 86400, -- 7 days
|
||||||
|
expireMessagesInterval = Just 21600_000000, -- microseconds, 6 hours
|
||||||
caCertificateFile = "tests/fixtures/tls/ca.crt",
|
caCertificateFile = "tests/fixtures/tls/ca.crt",
|
||||||
privateKeyFile = "tests/fixtures/tls/server.key",
|
privateKeyFile = "tests/fixtures/tls/server.key",
|
||||||
certificateFile = "tests/fixtures/tls/server.crt"
|
certificateFile = "tests/fixtures/tls/server.crt"
|
||||||
|
@ -15,7 +15,7 @@ import qualified Simplex.Messaging.Crypto as C
|
|||||||
import Simplex.Messaging.Crypto.Ratchet
|
import Simplex.Messaging.Crypto.Ratchet
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (parseAll)
|
import Simplex.Messaging.Parsers (parseAll)
|
||||||
import Simplex.Messaging.Protocol (smpClientVRange)
|
import Simplex.Messaging.Protocol (ProtocolServer (..), smpClientVRange)
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
protocolTests :: Spec
|
protocolTests :: Spec
|
||||||
@ -23,7 +23,7 @@ protocolTests = decodeChatMessageTest
|
|||||||
|
|
||||||
srv :: SMPServer
|
srv :: SMPServer
|
||||||
srv =
|
srv =
|
||||||
SMPServer
|
ProtocolServer
|
||||||
{ host = "smp.simplex.im",
|
{ host = "smp.simplex.im",
|
||||||
port = "5223",
|
port = "5223",
|
||||||
keyHash = C.KeyHash "\215m\248\251"
|
keyHash = C.KeyHash "\215m\248\251"
|
||||||
|
Loading…
Reference in New Issue
Block a user