From bca0fa6ca2cf1983a18fa2d934d42cbe9dfa0534 Mon Sep 17 00:00:00 2001 From: Ankmara <118185796+Ankmara@users.noreply.github.com> Date: Wed, 27 Sep 2023 14:21:47 +0200 Subject: [PATCH] iOS push notifications Co-authored-by: Ankmara <> --- .../project.pbxproj | 8 ++ .../Sources/Extensions/Dictionary.swift | 34 +++++ .../Model/BloomreachAsyncMethodType.swift | 9 ++ .../Sources/Model/BloomreachMethodType.swift | 33 +++++ .../Protocol/BloomreachInvokable.swift | 122 ++++++++++++++++++ ...ushNotificationManagerDelegateObject.swift | 29 +++++ 6 files changed, 235 insertions(+) create mode 100644 BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Extensions/Dictionary.swift create mode 100644 BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/PushNotificationManagerDelegateObject.swift diff --git a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS.xcodeproj/project.pbxproj b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS.xcodeproj/project.pbxproj index 0a1adbe..7e1c5db 100644 --- a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS.xcodeproj/project.pbxproj +++ b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS.xcodeproj/project.pbxproj @@ -19,6 +19,8 @@ 313DA6F42A9CC86A00B49508 /* Customer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DA6F32A9CC86A00B49508 /* Customer.swift */; }; 313DA6F62A9CD32700B49508 /* Int.swift in Sources */ = {isa = PBXBuildFile; fileRef = 313DA6F52A9CD32700B49508 /* Int.swift */; }; 315601AF2A9F315900CD8F22 /* BloomreachSDKMauiIOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 315601AE2A9F315900CD8F22 /* BloomreachSDKMauiIOSTests.swift */; }; + 31B6B7752AB877CA00B8BD2C /* Dictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B6B7742AB877CA00B8BD2C /* Dictionary.swift */; }; + 31B6B7772AB8784F00B8BD2C /* PushNotificationManagerDelegateObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 31B6B7762AB8784F00B8BD2C /* PushNotificationManagerDelegateObject.swift */; }; 3D26963D0F8017D117CD24C8 /* Pods_BloomreachSDKMauiIOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3544A38AF78696F520523016 /* Pods_BloomreachSDKMauiIOS.framework */; }; 550893672AB313CD00ABBEF5 /* BloomreachAsyncMethodType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550893652AB313CD00ABBEF5 /* BloomreachAsyncMethodType.swift */; }; 550893682AB313CD00ABBEF5 /* BloomreachMethodType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 550893662AB313CD00ABBEF5 /* BloomreachMethodType.swift */; }; @@ -55,6 +57,8 @@ 313DA6F52A9CD32700B49508 /* Int.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Int.swift; sourceTree = ""; }; 315601AC2A9F315900CD8F22 /* BloomreachSDKMauiIOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BloomreachSDKMauiIOSTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 315601AE2A9F315900CD8F22 /* BloomreachSDKMauiIOSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BloomreachSDKMauiIOSTests.swift; sourceTree = ""; }; + 31B6B7742AB877CA00B8BD2C /* Dictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dictionary.swift; sourceTree = ""; }; + 31B6B7762AB8784F00B8BD2C /* PushNotificationManagerDelegateObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationManagerDelegateObject.swift; sourceTree = ""; }; 3544A38AF78696F520523016 /* Pods_BloomreachSDKMauiIOS.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BloomreachSDKMauiIOS.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 550893652AB313CD00ABBEF5 /* BloomreachAsyncMethodType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomreachAsyncMethodType.swift; sourceTree = ""; }; 550893662AB313CD00ABBEF5 /* BloomreachMethodType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BloomreachMethodType.swift; sourceTree = ""; }; @@ -110,6 +114,7 @@ 313DA6E52A9C9C4600B49508 /* Model */, 313DA6E22A9C9C1E00B49508 /* Extensions */, 312E76D12AA3312D00ECC118 /* Typealiases.swift */, + 31B6B7762AB8784F00B8BD2C /* PushNotificationManagerDelegateObject.swift */, ); path = Sources; sourceTree = ""; @@ -123,6 +128,7 @@ 313DA6EF2A9CA70A00B49508 /* Double.swift */, 313DA6F52A9CD32700B49508 /* Int.swift */, 312E76D32AA334E100ECC118 /* Array+JSON.swift */, + 31B6B7742AB877CA00B8BD2C /* Dictionary.swift */, ); path = Extensions; sourceTree = ""; @@ -402,6 +408,7 @@ 313DA6EC2A9CA5FD00B49508 /* String.swift in Sources */, 313DA6F42A9CC86A00B49508 /* Customer.swift in Sources */, 550893682AB313CD00ABBEF5 /* BloomreachMethodType.swift in Sources */, + 31B6B7752AB877CA00B8BD2C /* Dictionary.swift in Sources */, 550917172A8A57FA00ACED6C /* BloomreachSdkIOS.swift in Sources */, 312E76D42AA334E100ECC118 /* Array+JSON.swift in Sources */, 313DA6F02A9CA70A00B49508 /* Double.swift in Sources */, @@ -411,6 +418,7 @@ 312E76D22AA3312D00ECC118 /* Typealiases.swift in Sources */, 313DA6EA2A9C9C6300B49508 /* BloomreachInvokable.swift in Sources */, 550917192A8A589600ACED6C /* MethodResult.swift in Sources */, + 31B6B7772AB8784F00B8BD2C /* PushNotificationManagerDelegateObject.swift in Sources */, 313DA6EE2A9CA69300B49508 /* Bool.swift in Sources */, 550893672AB313CD00ABBEF5 /* BloomreachAsyncMethodType.swift in Sources */, 5508936A2AB313DA00ABBEF5 /* BloomreachDataError.swift in Sources */, diff --git a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Extensions/Dictionary.swift b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Extensions/Dictionary.swift new file mode 100644 index 0000000..89e3187 --- /dev/null +++ b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Extensions/Dictionary.swift @@ -0,0 +1,34 @@ +// +// Dictionary.swift +// BloomreachSDKMauiIOS +// +// Created by Michal Severín on 18.09.2023. +// + +import Foundation + +extension Dictionary { + var jsonData: String? { + var toReturn: [String: Any] = [:] + for value in self { + let stringKey: String + if let key = value.key as? String { + stringKey = key + } else if let key = value.key as? Int { + stringKey = String(key) + } else if let key = value.key as? Double { + stringKey = String(key) + } else if let key = value.key as? NSNumber { + stringKey = key.stringValue + } else { + stringKey = "\(value.key)" + } + toReturn[stringKey] = value.value + } + if let data = try? JSONSerialization.data(withJSONObject: toReturn, options: []) { + let json = String(data: data, encoding: .utf8) + return json + } + return nil + } +} diff --git a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachAsyncMethodType.swift b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachAsyncMethodType.swift index 8d4ec97..b412a81 100644 --- a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachAsyncMethodType.swift +++ b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachAsyncMethodType.swift @@ -9,6 +9,9 @@ import ExponeaSDK public enum BloomreachAsyncMethodType { case flushData(data: TypeBlock?) + case setReceivedPushCallback(data: TypeBlock?) + case setOpenedPushCallback(data: TypeBlock?) + case requestAuthorization(completion: TypeBlock?) case unsupported init(method: String?, params: String?, asyncBlock: TypeBlock? = nil) { @@ -19,6 +22,12 @@ public enum BloomreachAsyncMethodType { switch methodName.lowercased() { case "flushdata": self = .flushData(data: asyncBlock) + case "setreceivedpushcallback": + self = .setReceivedPushCallback(data: asyncBlock) + case "setopenedpushcallback": + self = .setOpenedPushCallback(data: asyncBlock) + case "requestauthorization": + self = .requestAuthorization(completion: asyncBlock) default: self = .unsupported } diff --git a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachMethodType.swift b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachMethodType.swift index 6fedc21..4ec458c 100644 --- a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachMethodType.swift +++ b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Model/BloomreachMethodType.swift @@ -37,6 +37,17 @@ public enum BloomreachMethodType { case trackEvent(data: [String: Any]) case trackSessionEnd case trackSessionStart + case handlePushNotificationOpened(data: [String: Any]) + case handleCampaignIntent(data: String?) + case handleHmsPushToken + case handlePushToken(data: String?) + case isBloomreachNotification(data: [String: Any]) + case trackClickedPush(data: [String: Any]) + case trackClickedPushWithoutTrackingConsent(data: [String: Any]) + case trackPushToken(data: String?) + case trackDeliveredPush(data: [String: Any]) + case trackDeliveredPushWithoutTrackingConsent(data: [String: Any]) + case trackHmsPushToken case unsupported init(method: String?, params: String?) { @@ -103,6 +114,28 @@ public enum BloomreachMethodType { self = .setLogLevel(data: params) case "setsafemode": self = .setSafeMode(data: JsonDataParser.parseBoolean(params)) + case "handlepushnotificationopened": + self = .handlePushNotificationOpened(data: params.json) + case "handlecampaignintent": + self = .handleCampaignIntent(data: params) + case "handlehmspushtoken": + self = .handleHmsPushToken + case "handlepushtoken": + self = .handlePushToken(data: params) + case "isbloomreachnotification": + self = .isBloomreachNotification(data: params.json) + case "trackclickedpush": + self = .trackClickedPush(data: params.json) + case "trackclickedpushwithouttrackingconsent": + self = .trackClickedPushWithoutTrackingConsent(data: params.json) + case "trackpushtoken": + self = .trackPushToken(data: params) + case "trackdeliveredpush": + self = .trackDeliveredPush(data: params.json) + case "trackdeliveredpushwithouttrackingconsent": + self = .trackDeliveredPushWithoutTrackingConsent(data: params.json) + case "trackhmspushtoken": + self = .trackHmsPushToken default: self = .unsupported } diff --git a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Protocol/BloomreachInvokable.swift b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Protocol/BloomreachInvokable.swift index bc3df37..bce81fa 100644 --- a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Protocol/BloomreachInvokable.swift +++ b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/Protocol/BloomreachInvokable.swift @@ -44,6 +44,23 @@ public protocol BloomreachInvokable { func trackEvent(data: [String: Any], timestamp: Double?) -> MethodResult func trackSessionEnd() -> MethodResult func trackSessionStart() -> MethodResult + + // MARK: - Notification + func handlePushNotificationOpened(data: [String: Any], identifier: String?) -> MethodResult + func handleCampaignIntent(url: String?) -> MethodResult + func handleHmsPushToken() -> MethodResult + func handlePushToken(token: String) -> MethodResult + func isBloomreachNotification(userInfo: [String : Any]) -> MethodResult + func trackClickedPush(data: [String: Any]) -> MethodResult + func trackClickedPushWithoutTrackingConsent(data: [String: Any]) -> MethodResult + func trackPushToken(token: String?) -> MethodResult + func trackDeliveredPush(data: [String: Any]) -> MethodResult + func trackDeliveredPushWithoutTrackingConsent(data: [String: Any]) -> MethodResult + func trackHmsPushToken() -> MethodResult + func setReceivedPushCallback(completion: TypeBlock?) + func setOpenedPushCallback(completion: TypeBlock?) + func requestAuthorization(completion: TypeBlock?) + } public extension BloomreachInvokable { @@ -53,6 +70,12 @@ public extension BloomreachInvokable { flushData(completion: data) case .unsupported: break + case let .setReceivedPushCallback(data): + setReceivedPushCallback(completion: data) + case let .setOpenedPushCallback(data): + setOpenedPushCallback(completion: data) + case let .requestAuthorization(data): + requestAuthorization(completion: data) } } @@ -116,6 +139,28 @@ public extension BloomreachInvokable { return trackSessionEnd() case .trackSessionStart: return trackSessionStart() + case let .handlePushNotificationOpened(data): + return handlePushNotificationOpened(data: data["data"] as! [String: Any], identifier: .assertValueFromDict(data: data, key: "identifier")) + case let .handleCampaignIntent(data): + return handleCampaignIntent(url: data) + case .handleHmsPushToken: + return handleHmsPushToken() + case let .handlePushToken(data): + return handlePushToken(token: data ?? "") + case let .isBloomreachNotification(data): + return isBloomreachNotification(userInfo: data) + case let .trackClickedPush(data): + return trackClickedPush(data: data) + case let .trackClickedPushWithoutTrackingConsent(data): + return trackClickedPushWithoutTrackingConsent(data: data) + case let .trackPushToken(data): + return trackPushToken(token: data) + case let .trackDeliveredPush(data): + return trackDeliveredPush(data: data) + case let .trackDeliveredPushWithoutTrackingConsent(data): + return trackDeliveredPushWithoutTrackingConsent(data: data) + case .trackHmsPushToken: + return trackHmsPushToken() default: return .unsupportedMethod(method ?? "Uknown method") } @@ -466,3 +511,80 @@ public extension BloomreachInvokable { return .success(nil) } } + +// MARK: - Notification +public extension BloomreachInvokable { + func handlePushNotificationOpened(data: [String: Any], identifier: String?) -> MethodResult { + Exponea.shared.handlePushNotificationOpened(userInfo: data, actionIdentifier: identifier) + return .success(nil) + } + + func handleCampaignIntent(url: String?) -> MethodResult { + guard let url = URL(string: url ?? "") else { + return .failure("Incorrect URL") + } + Exponea.shared.trackCampaignClick(url: url, timestamp: Date().timeIntervalSince1970) + return .success(nil) + } + + func handleHmsPushToken() -> MethodResult { + .unsupportedMethod("unsupported method") + } + + func handlePushToken(token: String) -> MethodResult { + Exponea.shared.handlePushNotificationToken(token: token) + return .success(nil) + } + + func isBloomreachNotification(userInfo: [String : Any]) -> MethodResult { + let isExponea = Exponea.isExponeaNotification(userInfo: userInfo) + return .success(isExponea ? "true" : "false") + } + + func trackClickedPush(data: [String: Any]) -> MethodResult { + Exponea.shared.trackPushOpened(with: data) + return .success(nil) + } + + func trackClickedPushWithoutTrackingConsent(data: [String: Any]) -> MethodResult { + Exponea.shared.trackPushOpenedWithoutTrackingConsent(with: data) + return .success(nil) + } + + func trackPushToken(token: String?) -> MethodResult { + Exponea.shared.trackPushToken(token) + return .success(nil) + } + + func trackDeliveredPush(data: [String: Any]) -> MethodResult { + Exponea.shared.trackPushReceived(userInfo: data) + return .success(nil) + } + + func trackDeliveredPushWithoutTrackingConsent(data: [String: Any]) -> MethodResult { + Exponea.shared.trackPushReceivedWithoutTrackingConsent(userInfo: data) + return .success(nil) + } + + func trackHmsPushToken() -> MethodResult { + .unsupportedMethod("unsupported method") + } + + func setOpenedPushCallback(completion: TypeBlock?) { + Exponea.shared.pushNotificationsDelegate = PushNotificationManagerDelegateObject(completion: completion) + } + + func setReceivedPushCallback(completion: TypeBlock?) { + Exponea.shared.pushNotificationsDelegate = PushNotificationManagerDelegateObject(completion: completion) + } + + func requestAuthorization(completion: TypeBlock?) { + UNUserNotificationCenter.current().requestAuthorization { isGranted, error in + if let error { + completion?(.failure(error.localizedDescription)) + } else { + completion?(.success(isGranted ? "true" : "false")) + } + } + } +} diff --git a/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/PushNotificationManagerDelegateObject.swift b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/PushNotificationManagerDelegateObject.swift new file mode 100644 index 0000000..dd1bcd1 --- /dev/null +++ b/BloomreachSDKMauiIOS/BloomreachSDKMauiIOS/Sources/PushNotificationManagerDelegateObject.swift @@ -0,0 +1,29 @@ +// +// PushNotificationManagerDelegateObject.swift +// BloomreachSDKMauiIOS +// +// Created by Michal Severín on 18.09.2023. +// + +import ExponeaSDK + +final class PushNotificationManagerDelegateObject: PushNotificationManagerDelegate { + + let completion: TypeBlock? + + init(completion: TypeBlock? = nil) { + self.completion = completion + } + + func pushNotificationOpened(with action: ExponeaSDK.ExponeaNotificationActionType, value: String?, extraData: [AnyHashable : Any]?) { + var data: [String: Any] = [:] + data["action"] = action.rawValue + data["value"] = value + data["data"] = extraData + completion?(.success(data.jsonData)) + } + + func silentPushNotificationReceived(extraData: [AnyHashable : Any]?) { + completion?(.success(extraData?.jsonData)) + } +}