mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-29 05:29:08 +00:00
APNS push environments (#756)
This commit is contained in:
@@ -21,12 +21,13 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
let token = deviceToken.map { String(format: "%02hhx", $0) }.joined()
|
||||
logger.debug("AppDelegate: didRegisterForRemoteNotificationsWithDeviceToken \(token)")
|
||||
let m = ChatModel.shared
|
||||
m.deviceToken = token
|
||||
let deviceToken = DeviceToken(env: pushEnvironment, token: token)
|
||||
m.deviceToken = deviceToken
|
||||
let useNotifications = UserDefaults.standard.bool(forKey: "useNotifications")
|
||||
if useNotifications {
|
||||
Task {
|
||||
do {
|
||||
m.tokenStatus = try await apiRegisterToken(token: token, notificationMode: .instant)
|
||||
m.tokenStatus = try await apiRegisterToken(token: deviceToken, notificationMode: .instant)
|
||||
} catch {
|
||||
logger.error("apiRegisterToken error: \(responseError(error))")
|
||||
}
|
||||
@@ -53,7 +54,7 @@ class AppDelegate: NSObject, UIApplicationDelegate {
|
||||
let m = ChatModel.shared
|
||||
do {
|
||||
if case .active = m.tokenStatus {} else { m.tokenStatus = .confirmed }
|
||||
try await apiVerifyToken(token: token, code: verification, nonce: nonce)
|
||||
try await apiVerifyToken(token: token, nonce: nonce, code: verification)
|
||||
m.tokenStatus = .active
|
||||
try await apiIntervalNofication(token: token, interval: 20)
|
||||
} catch {
|
||||
|
||||
@@ -28,7 +28,7 @@ final class ChatModel: ObservableObject {
|
||||
@Published var userAddress: String?
|
||||
@Published var userSMPServers: [String]?
|
||||
@Published var appOpenUrl: URL?
|
||||
@Published var deviceToken: String?
|
||||
@Published var deviceToken: DeviceToken?
|
||||
@Published var tokenStatus = NtfTknStatus.new
|
||||
@Published var notificationMode = NotificationMode.off
|
||||
@Published var notificationPreview: NotificationPreviewMode? = .message
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
//
|
||||
// PushEnvironment.swift
|
||||
// SimpleX (iOS)
|
||||
//
|
||||
// Created by Evgeny on 27/06/2022.
|
||||
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SimpleXChat
|
||||
|
||||
let pushEnvironment: PushEnvironment = {
|
||||
guard let provisioningProfile = try? provisioningProfile(),
|
||||
let entitlements = provisioningProfile["Entitlements"] as? [String: Any],
|
||||
let environment = entitlements["aps-environment"] as? String,
|
||||
let env = PushEnvironment(rawValue: environment)
|
||||
else {
|
||||
logger.warning("pushEnvironment: unknown, assuming production")
|
||||
return .production
|
||||
}
|
||||
logger.debug("pushEnvironment: \(env.rawValue)")
|
||||
return env
|
||||
}()
|
||||
|
||||
private func provisioningProfile() throws -> [String: Any]? {
|
||||
guard let url = Bundle.main.url(forResource: "embedded", withExtension: "mobileprovision") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let binaryString = try String(contentsOf: url, encoding: .isoLatin1)
|
||||
|
||||
let scanner = Scanner(string: binaryString)
|
||||
guard scanner.scanUpToString("<plist") != nil,
|
||||
let plistString = scanner.scanUpToString("</plist>"),
|
||||
let data = (plistString + "</plist>").data(using: .isoLatin1)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any]
|
||||
}
|
||||
@@ -226,21 +226,21 @@ func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteM
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiRegisterToken(token: String, notificationMode: NotificationMode) async throws -> NtfTknStatus {
|
||||
func apiRegisterToken(token: DeviceToken, notificationMode: NotificationMode) async throws -> NtfTknStatus {
|
||||
let r = await chatSendCmd(.apiRegisterToken(token: token, notificationMode: notificationMode))
|
||||
if case let .ntfTokenStatus(status) = r { return status }
|
||||
throw r
|
||||
}
|
||||
|
||||
func apiVerifyToken(token: String, code: String, nonce: String) async throws {
|
||||
try await sendCommandOkResp(.apiVerifyToken(token: token, code: code, nonce: nonce))
|
||||
func apiVerifyToken(token: DeviceToken, nonce: String, code: String) async throws {
|
||||
try await sendCommandOkResp(.apiVerifyToken(token: token, nonce: nonce, code: code))
|
||||
}
|
||||
|
||||
func apiIntervalNofication(token: String, interval: Int) async throws {
|
||||
func apiIntervalNofication(token: DeviceToken, interval: Int) async throws {
|
||||
try await sendCommandOkResp(.apiIntervalNofication(token: token, interval: interval))
|
||||
}
|
||||
|
||||
func apiDeleteToken(token: String) async throws {
|
||||
func apiDeleteToken(token: DeviceToken) async throws {
|
||||
try await sendCommandOkResp(.apiDeleteToken(token: token))
|
||||
}
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ struct SettingsView: View {
|
||||
.foregroundColor(color)
|
||||
}
|
||||
|
||||
private func notificationsToggle(_ token: String) -> some View {
|
||||
private func notificationsToggle(_ token: DeviceToken) -> some View {
|
||||
Toggle("Check messages", isOn: $useNotifications)
|
||||
.onChange(of: useNotifications) { enable in
|
||||
if enable {
|
||||
@@ -269,7 +269,7 @@ struct SettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func enableNotificationsAlert(_ token: String) -> Alert {
|
||||
private func enableNotificationsAlert(_ token: DeviceToken) -> Alert {
|
||||
Alert(
|
||||
title: Text("Enable notifications? (BETA)"),
|
||||
message: Text("The app can receive background notifications every 20 minutes to check the new messages.\n*Please note*: if you confirm, your device token will be sent to SimpleX Chat notifications server."),
|
||||
|
||||
@@ -65,6 +65,7 @@
|
||||
5CB0BA9A2827FD8800B3292C /* HowItWorks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB0BA992827FD8800B3292C /* HowItWorks.swift */; };
|
||||
5CB346E52868AA7F001FD2EF /* SuspendChat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */; };
|
||||
5CB346E72868D76D001FD2EF /* NotificationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E62868D76D001FD2EF /* NotificationsView.swift */; };
|
||||
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */; };
|
||||
5CB924D427A853F100ACCCDD /* SettingsButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D327A853F100ACCCDD /* SettingsButton.swift */; };
|
||||
5CB924D727A8563F00ACCCDD /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924D627A8563F00ACCCDD /* SettingsView.swift */; };
|
||||
5CB924E127A867BA00ACCCDD /* UserProfile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB924E027A867BA00ACCCDD /* UserProfile.swift */; };
|
||||
@@ -240,6 +241,7 @@
|
||||
5CB0BA992827FD8800B3292C /* HowItWorks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HowItWorks.swift; sourceTree = "<group>"; };
|
||||
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = "<group>"; };
|
||||
5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = "<group>"; };
|
||||
5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnvironment.swift; sourceTree = "<group>"; };
|
||||
5CB924D327A853F100ACCCDD /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = "<group>"; };
|
||||
5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = "<group>"; };
|
||||
@@ -410,6 +412,7 @@
|
||||
5C35CFC727B2782E00FB6C6D /* BGManager.swift */,
|
||||
5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */,
|
||||
5CB346E42868AA7F001FD2EF /* SuspendChat.swift */,
|
||||
5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
@@ -850,6 +853,7 @@
|
||||
5C5F2B6D27EBC3FE006A9D5F /* ImagePicker.swift in Sources */,
|
||||
5C577F7D27C83AA10006112D /* MarkdownHelp.swift in Sources */,
|
||||
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
|
||||
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */,
|
||||
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */,
|
||||
5CCD403727A5F9A200368C90 /* ScanToConnectView.swift in Sources */,
|
||||
5CFA59D12864782E00863A68 /* ChatArchiveView.swift in Sources */,
|
||||
|
||||
@@ -28,10 +28,10 @@ public enum ChatCommand {
|
||||
case apiSendMessage(type: ChatType, id: Int64, file: String?, quotedItemId: Int64?, msg: MsgContent)
|
||||
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent)
|
||||
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
||||
case apiRegisterToken(token: String, notificationMode: NotificationMode)
|
||||
case apiVerifyToken(token: String, code: String, nonce: String)
|
||||
case apiIntervalNofication(token: String, interval: Int)
|
||||
case apiDeleteToken(token: String)
|
||||
case apiRegisterToken(token: DeviceToken, notificationMode: NotificationMode)
|
||||
case apiVerifyToken(token: DeviceToken, nonce: String, code: String)
|
||||
case apiIntervalNofication(token: DeviceToken, interval: Int)
|
||||
case apiDeleteToken(token: DeviceToken)
|
||||
case apiGetNtfMessage(nonce: String, encNtfInfo: String)
|
||||
case getUserSMPServers
|
||||
case setUserSMPServers(smpServers: [String])
|
||||
@@ -77,10 +77,10 @@ public enum ChatCommand {
|
||||
return "/_send \(ref(type, id)) json \(msg)"
|
||||
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 .apiRegisterToken(token, notificationMode): return "/_ntf register apns \(token) \(notificationMode.rawValue)"
|
||||
case let .apiVerifyToken(token, code, nonce): return "/_ntf verify apns \(token) \(code) \(nonce)"
|
||||
case let .apiIntervalNofication(token, interval): return "/_ntf interval apns \(token) \(interval)"
|
||||
case let .apiDeleteToken(token): return "/_ntf delete apns \(token)"
|
||||
case let .apiRegisterToken(token, notificationMode): return "/_ntf register \(token.cmdString) \(notificationMode.rawValue)"
|
||||
case let .apiVerifyToken(token, nonce, code): return "/_ntf verify \(token.cmdString) \(nonce) \(code)"
|
||||
case let .apiIntervalNofication(token, interval): return "/_ntf interval \(token.cmdString) \(interval)"
|
||||
case let .apiDeleteToken(token): return "/_ntf delete \(token.cmdString)"
|
||||
case let .apiGetNtfMessage(nonce, encNtfInfo): return "/_ntf message \(nonce) \(encNtfInfo)"
|
||||
case .getUserSMPServers: return "/smp_servers"
|
||||
case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))"
|
||||
@@ -386,6 +386,32 @@ public protocol SelectableItem: Hashable, Identifiable {
|
||||
static var values: [Self] { get }
|
||||
}
|
||||
|
||||
public struct DeviceToken {
|
||||
var env: PushEnvironment
|
||||
var token: String
|
||||
|
||||
public init(env: PushEnvironment, token: String) {
|
||||
self.env = env
|
||||
self.token = token
|
||||
}
|
||||
|
||||
public var cmdString: String {
|
||||
"\(env.pushProvider) \(token)"
|
||||
}
|
||||
}
|
||||
|
||||
public enum PushEnvironment: String {
|
||||
case development
|
||||
case production
|
||||
|
||||
public var pushProvider: String {
|
||||
switch self {
|
||||
case .development: return "apns_dev"
|
||||
case .production: return "apns_prod"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum NotificationMode: String, SelectableItem {
|
||||
case off = "OFF"
|
||||
case periodic = "PERIODIC"
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ constraints: zip +disable-bzip2 +disable-zstd
|
||||
source-repository-package
|
||||
type: git
|
||||
location: https://github.com/simplex-chat/simplexmq.git
|
||||
tag: 51d0b48ce15433d8473770522b1eb814688d2aea
|
||||
tag: ba40d75886c14d70fb6af61c22f917069d7b478f
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."51d0b48ce15433d8473770522b1eb814688d2aea" = "1v1n96lq9k6cvayqhlc7ywr1zwj2qnnsirv4dilqwl507gb6gqyj";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."ba40d75886c14d70fb6af61c22f917069d7b478f" = "1cashma9g0lw1bvaw4fcvfzzjqbpwjxhz42fvl83pi43j8i2plpn";
|
||||
"https://github.com/simplex-chat/aeson.git"."3eb66f9a68f103b5f1489382aad89f5712a64db7" = "0kilkx59fl6c3qy3kjczqvm8c3f4n3p0bdk9biyflf51ljnzp4yp";
|
||||
"https://github.com/simplex-chat/haskell-terminal.git"."f708b00009b54890172068f168bf98508ffcd495" = "0zmq7lmfsk8m340g47g5963yba7i88n4afa6z93sg9px5jv1mijj";
|
||||
"https://github.com/zw3rk/android-support.git"."3c3a5ab0b8b137a072c98d3d0937cbdc96918ddb" = "1r6jyxbim3dsvrmakqfyxbd6ms6miaghpbwyl0sr6dzwpgaprz97";
|
||||
|
||||
+1
-1
@@ -542,7 +542,7 @@ processChatCommand = \case
|
||||
APIParseMarkdown text -> pure . CRApiParsedMarkdown $ parseMaybeMarkdownList text
|
||||
APIGetNtfToken -> withUser $ \_ -> crNtfToken <$> withAgent getNtfToken
|
||||
APIRegisterToken token mode -> CRNtfTokenStatus <$> withUser (\_ -> withAgent $ \a -> registerNtfToken a token mode)
|
||||
APIVerifyToken token code nonce -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token code nonce) $> CRCmdOk
|
||||
APIVerifyToken token nonce code -> withUser $ \_ -> withAgent (\a -> verifyNtfToken a token nonce code) $> CRCmdOk
|
||||
APIDeleteToken token -> withUser $ \_ -> withAgent (`deleteNtfToken` token) $> CRCmdOk
|
||||
APIGetNtfMessage nonce encNtfInfo -> withUser $ \user -> do
|
||||
(NotificationInfo {ntfConnId, ntfMsgMeta}, msgs) <- withAgent $ \a -> getNotificationMessage a nonce encNtfInfo
|
||||
|
||||
@@ -131,7 +131,7 @@ data ChatCommand
|
||||
| APIParseMarkdown Text
|
||||
| APIGetNtfToken
|
||||
| APIRegisterToken DeviceToken NotificationsMode
|
||||
| APIVerifyToken DeviceToken ByteString C.CbNonce
|
||||
| APIVerifyToken DeviceToken C.CbNonce ByteString
|
||||
| APIDeleteToken DeviceToken
|
||||
| APIGetNtfMessage {nonce :: C.CbNonce, encNtfInfo :: ByteString}
|
||||
| GetUserSMPServers
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ extra-deps:
|
||||
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
|
||||
# - ../simplexmq
|
||||
- github: simplex-chat/simplexmq
|
||||
commit: 51d0b48ce15433d8473770522b1eb814688d2aea
|
||||
commit: ba40d75886c14d70fb6af61c22f917069d7b478f
|
||||
# - terminal-0.2.0.0@sha256:de6770ecaae3197c66ac1f0db5a80cf5a5b1d3b64a66a05b50f442de5ad39570,2977
|
||||
- github: simplex-chat/aeson
|
||||
commit: 3eb66f9a68f103b5f1489382aad89f5712a64db7
|
||||
|
||||
Reference in New Issue
Block a user