From 54126eba6ba6ae5cf6739497df42890a62d03940 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Mon, 27 Jun 2022 23:03:27 +0100 Subject: [PATCH] APNS push environments (#756) --- apps/ios/Shared/AppDelegate.swift | 7 ++-- apps/ios/Shared/Model/ChatModel.swift | 2 +- apps/ios/Shared/Model/PushEnvironment.swift | 41 ++++++++++++++++++ apps/ios/Shared/Model/SimpleXAPI.swift | 10 ++--- .../Views/UserSettings/SettingsView.swift | 4 +- apps/ios/SimpleX.xcodeproj/project.pbxproj | 4 ++ apps/ios/SimpleXChat/APITypes.swift | 42 +++++++++++++++---- cabal.project | 2 +- scripts/nix/sha256map.nix | 2 +- src/Simplex/Chat.hs | 2 +- src/Simplex/Chat/Controller.hs | 2 +- stack.yaml | 2 +- 12 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 apps/ios/Shared/Model/PushEnvironment.swift diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index ec8a19cde6..c58912dd71 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -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 { diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index 78fc66bf1c..be3e1fe446 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -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 diff --git a/apps/ios/Shared/Model/PushEnvironment.swift b/apps/ios/Shared/Model/PushEnvironment.swift new file mode 100644 index 0000000000..7fa7ef0b99 --- /dev/null +++ b/apps/ios/Shared/Model/PushEnvironment.swift @@ -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(""), + let data = (plistString + "").data(using: .isoLatin1) + else { + return nil + } + + return try PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] +} diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index e353ebf1ed..a40e5c0e48 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -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)) } diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index 47dff51fe1..cc48759c4e 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -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."), diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index c408623643..4edc81a91b 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -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 = ""; }; 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SuspendChat.swift; sourceTree = ""; }; 5CB346E62868D76D001FD2EF /* NotificationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationsView.swift; sourceTree = ""; }; + 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushEnvironment.swift; sourceTree = ""; }; 5CB924D327A853F100ACCCDD /* SettingsButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsButton.swift; sourceTree = ""; }; 5CB924D627A8563F00ACCCDD /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 5CB924E027A867BA00ACCCDD /* UserProfile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserProfile.swift; sourceTree = ""; }; @@ -410,6 +412,7 @@ 5C35CFC727B2782E00FB6C6D /* BGManager.swift */, 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */, 5CB346E42868AA7F001FD2EF /* SuspendChat.swift */, + 5CB346E82869E8BA001FD2EF /* PushEnvironment.swift */, ); path = Model; sourceTree = ""; @@ -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 */, diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f5474623f3..7b07940416 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -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" diff --git a/cabal.project b/cabal.project index 3b8c5538a1..7af4430249 100644 --- a/cabal.project +++ b/cabal.project @@ -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 diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index fcc28b64f2..bec8b11529 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -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"; diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 8a99e2deb5..b1d6ff4fd5 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -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 diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 0b12fb7bae..1a3980cc93 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -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 diff --git a/stack.yaml b/stack.yaml index 63b756b335..89564af995 100644 --- a/stack.yaml +++ b/stack.yaml @@ -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