diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a1157f7..d5044eb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,7 +2,7 @@ name: Build on: push: branches: - - gmsapi + - main pull_request: types: [opened, synchronize, reopened] jobs: diff --git a/PushSDK.podspec b/PushSDK.podspec index 2da89f9..39d71ca 100644 --- a/PushSDK.podspec +++ b/PushSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "PushSDK" - s.version = "1.0.0.45" + s.version = "1.1.1" s.summary = "SDK for sending push messages to iOS devices." s.homepage = "https://github.com/GlobalMessageServices/BCS-GMS-SDK-IOS" diff --git a/PushSDK.xcodeproj/project.pbxproj b/PushSDK.xcodeproj/project.pbxproj index f85c025..b0302c5 100644 --- a/PushSDK.xcodeproj/project.pbxproj +++ b/PushSDK.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 016F280528D2063D00F7D1A1 /* Notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 016F280428D2063D00F7D1A1 /* Notifications.swift */; }; 01FCADDA28C0C15E00D77FA4 /* PushSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01FCADD128C0C15E00D77FA4 /* PushSDK.framework */; }; 01FCADDF28C0C15E00D77FA4 /* PushSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01FCADDE28C0C15E00D77FA4 /* PushSDKTests.swift */; }; 01FCADE028C0C15E00D77FA4 /* PushSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = 01FCADD428C0C15E00D77FA4 /* PushSDK.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -36,6 +37,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 016F280428D2063D00F7D1A1 /* Notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Notifications.swift; sourceTree = ""; }; 01FCADD128C0C15E00D77FA4 /* PushSDK.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = PushSDK.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 01FCADD428C0C15E00D77FA4 /* PushSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PushSDK.h; sourceTree = ""; }; 01FCADD928C0C15E00D77FA4 /* PushSDKTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PushSDKTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -147,6 +149,7 @@ 01FCADF128C0C27D00D77FA4 /* DataSaver.swift */, 01FCADF228C0C27D00D77FA4 /* Parser.swift */, 01FCADF328C0C27D00D77FA4 /* JsonParser.swift */, + 016F280428D2063D00F7D1A1 /* Notifications.swift */, ); path = core; sourceTree = ""; @@ -364,6 +367,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 016F280528D2063D00F7D1A1 /* Notifications.swift in Sources */, 01FCAE0128C0C27D00D77FA4 /* DataStructures.swift in Sources */, 01FCADFB28C0C27D00D77FA4 /* PushConstants.swift in Sources */, 01FCADFD28C0C27D00D77FA4 /* AnswerBuilder.swift in Sources */, diff --git a/PushSDK.xcworkspace/xcuserdata/o.korniienko.xcuserdatad/UserInterfaceState.xcuserstate b/PushSDK.xcworkspace/xcuserdata/o.korniienko.xcuserdatad/UserInterfaceState.xcuserstate index a7c9b99..2f2b355 100644 Binary files a/PushSDK.xcworkspace/xcuserdata/o.korniienko.xcuserdatad/UserInterfaceState.xcuserstate and b/PushSDK.xcworkspace/xcuserdata/o.korniienko.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/PushSDK/PushSDK.swift b/PushSDK/PushSDK.swift index 8a31c64..e952cb9 100644 --- a/PushSDK/PushSDK.swift +++ b/PushSDK/PushSDK.swift @@ -37,7 +37,7 @@ public class PushSDK { private let log = SwiftyBeaver.self private let parserClassAdapter = PusherKParser.init() private let pushRestServer = PushAPI.init() - //private let funNotificator = PushNotification.init() + private let funNotificator = PushNotification.init() let answerBuilder = AnswerBuilder.init() @@ -303,4 +303,60 @@ public class PushSDK { } + //check if notification permitted (Sync procedure) + public func areNotificationsEnabled() -> Bool { + + let semaphore = DispatchSemaphore(value: 0) + funNotificator.areNotificationsEnabled { (notificationStatus) in + debugPrint(notificationStatus) + PushKConstants.notificationPermission = notificationStatus + semaphore.signal() + } + semaphore.wait() + return PushKConstants.notificationPermission + } + + //check if notification permitted (Async procedure) + public func areNotificationsEnabled(completion:@escaping (Bool)->Swift.Void) { + var notificationStatus: Bool = false + let current = UNUserNotificationCenter.current() + current.getNotificationSettings(completionHandler: { permission in + switch permission.authorizationStatus { + case .authorized: + PushKConstants.logger.debug("User granted permission for notification") + notificationStatus = true + completion(notificationStatus) + break + case .denied: + PushKConstants.logger.debug("User denied notification permission") + notificationStatus = false + completion(notificationStatus) + break + case .notDetermined: + PushKConstants.logger.debug("Notification permission haven't been asked yet") + notificationStatus = false + completion(notificationStatus) + break + case .provisional: + // @available(iOS 12.0, *) + PushKConstants.logger.debug("The application is authorized to post non-interruptive user notifications.") + notificationStatus = true + completion(notificationStatus) + break + case .ephemeral: + // @available(iOS 14.0, *) + PushKConstants.logger.debug("The application is temporarily authorized to post notifications. Only available to app clips.") + notificationStatus = false + completion(notificationStatus) + break + @unknown default: + PushKConstants.logger.debug("Unknow Status") + notificationStatus = false + completion(notificationStatus) + break + } + }) + } + + } diff --git a/PushSDK/PushSDKFirebase.swift b/PushSDK/PushSDKFirebase.swift index b88a4fa..74b6b5f 100644 --- a/PushSDK/PushSDKFirebase.swift +++ b/PushSDK/PushSDKFirebase.swift @@ -15,7 +15,7 @@ import FirebaseCore public class PushSDKFirebase: UIResponder, UIApplicationDelegate { let pushParser = PusherKParser.init() - //let manualNotificator = PushNotification.init() + let manualNotificator = PushNotification.init() let answerAdapter = PushServerAnswParser.init() let pushAdapter = PushSDK.init(basePushURL: PushKConstants.basePushURLactive) let gcmMessageIDKey = "gcm.message_id" @@ -43,7 +43,6 @@ public class PushSDKFirebase: UIResponder, UIApplicationDelegate { public func fbInitApplication(didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?){ - // Override point for customization after application launch. //fbInitApplication0 PushKConstants.logger.debug("Call fbInitApplication: fbInitApplication0") @@ -78,14 +77,13 @@ public class PushSDKFirebase: UIResponder, UIApplicationDelegate { //fbInitApplication2 PushKConstants.logger.debug("Call fbInitApplication: fbInitApplication2") - // If you are receiving a notification message while your app is in the background, - // this callback will not be fired till the user taps on the notification launching the application. + // With swizzling disabled you must let Messaging know about the message, for Analytics - // Messaging.messaging().appDidReceiveMessage(userInfo) - // Print message ID. - if let messageID = userInfo[gcmMessageIDKey] { - PushKConstants.logger.debug("Message ID: \(messageID)") + Messaging.messaging().appDidReceiveMessage(userInfo) + + if let gcmMessageID = userInfo[gcmMessageIDKey] { + PushKConstants.logger.debug("gcm essage ID: \(gcmMessageID)") } // Print full message. @@ -93,7 +91,67 @@ public class PushSDKFirebase: UIResponder, UIApplicationDelegate { } + public func fbInitApplication(didReceiveRemoteNotification userInfo: [AnyHashable: Any], + fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { + //fbInitApplication3 + PushKConstants.logger.debug("Call fbInitApplication: fbInitApplication3") + + + // With swizzling disabled you must let Messaging know about the message, for Analytics + // Messaging.messaging().appDidReceiveMessage(userInfo) + + + if let gcmMessageID = userInfo[gcmMessageIDKey] { + PushKConstants.logger.debug("gcm message ID: \(gcmMessageID)") + + } + + // Print full message. + PushKConstants.logger.debug("userInfo: \(userInfo)") + + + guard let jsonData = try? JSONSerialization.data(withJSONObject: userInfo, options: []) else { return } + let jsonString = String(data: jsonData, encoding: .utf8) + PushKConstants.logger.debug("jsonString: \(jsonString ?? "empty")") + let newString = String(jsonString ?? "").replacingOccurrences(of: "\\", with: "", options: .literal, range: nil) + PushKConstants.logger.debug("newString: \(newString)") + let parsedMessage = PushServerAnswParser.messageIncomingJson(strResp: newString) + PushKConstants.logger.debug("parsedMessage: \(parsedMessage)") + + if (PushKConstants.enableNotificationFlag == true) { + manualNotificator.preparePushNotification( + imageUrl: String(parsedMessage.message.image?.url ?? ""), + contentTitle: String(parsedMessage.message.title ?? ""), + contentBody: String(parsedMessage.message.body ?? ""), + userInfo: userInfo) + + } + + //here is delivery report sending + let messageId = parsedMessage.message.messageId + PushKConstants.logger.debug("messageId: \(messageId ?? "messageId error")") + + if (PushKConstants.enableDeliveryReportAutoFlag == true && PushKConstants.enableNotificationFlag == true) { + if (PushKConstants.deliveryReportLogicFlag == 1) { + let notificationStatus = pushAdapter.areNotificationsEnabled() + + if (notificationStatus == true) { + let drAnswer = self.pushAdapter.pushMessageDeliveryReport(messageId: messageId ?? "") + PushKConstants.logger.debug("delivery report answer: \(drAnswer)") + } + + } else if (PushKConstants.deliveryReportLogicFlag == 2) + { + let drAnswer = self.pushAdapter.pushMessageDeliveryReport(messageId: messageId ?? "") + PushKConstants.logger.debug("delivery report answer: \(drAnswer)") + } + } + + NotificationCenter.default.post(name: .receivePushKData, object: nil, userInfo: userInfo) + + completionHandler(UIBackgroundFetchResult.newData) + } public func fbInitApplication(didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { //fbInitApplication4 @@ -111,24 +169,31 @@ extension PushSDKFirebase: UNUserNotificationCenterDelegate{ willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { let userInfo = notification.request.content.userInfo - + PushKConstants.logger.debug("userInfo: \(userInfo)") // With swizzling disabled you must let Messaging know about the message, for Analytics - // Messaging.messaging().appDidReceiveMessage(userInfo) - // Print message ID. - if let messageID = userInfo[gcmMessageIDKey] { - PushKConstants.logger.debug("Message ID: \(messageID)") + Messaging.messaging().appDidReceiveMessage(userInfo) + + if let gcmMessageID = userInfo[gcmMessageIDKey] { + PushKConstants.logger.debug("gcm message ID: \(gcmMessageID)") + } + + if #available(iOS 14.0, *){ + completionHandler([.banner, .list, .sound, .badge]) + }else{ + completionHandler([.alert, .sound, .badge]) } - completionHandler([.alert, .sound, .badge]) } public func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { let userInfo = response.notification.request.content.userInfo - // Print message ID. - if let messageID = userInfo[gcmMessageIDKey] { - PushKConstants.logger.debug("Message ID: \(messageID)") + PushKConstants.logger.debug("userInfo: \(userInfo)") + + if let gcmMessageID = userInfo[gcmMessageIDKey] { + PushKConstants.logger.debug("gcm message ID: \(gcmMessageID)") } + completionHandler() } } @@ -140,4 +205,59 @@ extension PushSDKFirebase { NotificationCenter.default.post(name: NSNotification.Name(rawValue: mySpecialNotificationKey), object: self) } + public func fb_remote_messaging(remoteMessage: NSDictionary) { + let fdf = remoteMessage as NSDictionary as? [String: AnyObject] + guard let jsonData = (try? JSONSerialization.data(withJSONObject: fdf ?? "", options: [])) else { return } + let jsonString = String(data: jsonData, encoding: .utf8) + let parsedMessage = PushServerAnswParser.messageIncomingJson(strResp: jsonString ?? "") + PushKConstants.logger.debug(parsedMessage) + let parsedMessageUserData = pushParser.messIdParser(messageFromPushServer: jsonString ?? "") + PushKConstants.logger.debug("parsedMessageUserData: \(parsedMessageUserData)") + + + if (PushKConstants.enableNotificationFlag == true) { + manualNotificator.preparePushNotification( + imageUrl: String(parsedMessage.message.image?.url ?? ""), + contentTitle: String(parsedMessage.message.title ?? ""), + contentBody: String(parsedMessage.message.body ?? ""), + userInfo: fdf ?? [:]) + } + + switch UIApplication.shared.applicationState { + case .active: + PushKConstants.logger.debug("active") + case .background: + PushKConstants.logger.debug("App is backgrounded.") + PushKConstants.logger.debug("Background time remaining = " + + "\(UIApplication.shared.backgroundTimeRemaining) seconds") + case .inactive: + PushKConstants.logger.debug("App is inactive.") + @unknown default: + PushKConstants.logger.debug("Fatal application error for UIApplication.shared.applicationState") + } + + if (PushKConstants.enableDeliveryReportAutoFlag == true && PushKConstants.enableNotificationFlag == true) { + if (PushKConstants.deliveryReportLogicFlag == 1) { + let notificationStatus = pushAdapter.areNotificationsEnabled() + + if (notificationStatus == true) { + let drAnswer = self.pushAdapter.pushMessageDeliveryReport(messageId: parsedMessageUserData) + PushKConstants.logger.debug("delivery report answer: \(drAnswer)") + } + + } else if (PushKConstants.deliveryReportLogicFlag == 2) + { + let drAnswer = self.pushAdapter.pushMessageDeliveryReport(messageId: parsedMessageUserData) + PushKConstants.logger.debug("delivery report answer: \(drAnswer)") + } + } + + NotificationCenter.default.post(name: .receivePushKData, object: nil, userInfo: fdf) + } + public func fb_token_messaging(didReceiveRegistrationToken fcmToken: String) { + PushKConstants.logger.debug("Firebase registration token: \(fcmToken)") + let dataDict:[String: String] = ["token": fcmToken] + NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict) + } + } diff --git a/PushSDK/core/JsonParser.swift b/PushSDK/core/JsonParser.swift index a1c980e..3684805 100644 --- a/PushSDK/core/JsonParser.swift +++ b/PushSDK/core/JsonParser.swift @@ -10,48 +10,17 @@ import Foundation class PushServerAnswParser { func registerJParse(strResp: String) -> RegisterJsonParse { - struct RegisterSession: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - let token: String - } - - struct RegisterDevice: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - let deviceId: Int - } - - struct RegisterProfile: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - let userId: Int - let userPhone: String - let createdAt: String - } - - struct FullRegister: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - let session: RegisterSession - let profile: RegisterProfile - let device: RegisterDevice - } - + PushKConstants.logger.debug("registerJParse start: strResp: \(strResp)") - guard let jsonData = strResp.data(using: .utf8) else { return RegisterJsonParse(deviceId: "", token: "", userId: 0, userPhone: "", createdAt: "")} - //let jsonData = JSON.self(using: .utf8)! do { + let jsonData = Data(strResp.utf8) let parsedJson: FullRegister = try JSONDecoder().decode(FullRegister.self, from: jsonData) PushKConstants.logger.debug(parsedJson.session.token) let res = RegisterJsonParse.init(deviceId: String(parsedJson.device.deviceId), token: parsedJson.session.token, userId: parsedJson.profile.userId, userPhone: parsedJson.profile.userPhone, createdAt: parsedJson.profile.createdAt) return res } catch { + PushKConstants.logger.debug("registerJParse error: \(error.localizedDescription)") let res = RegisterJsonParse.init(deviceId: "unknown", token: "unknown_token", userId: 0, userPhone: "0", createdAt: "") return res } @@ -59,126 +28,58 @@ class PushServerAnswParser { - func updateregistrationJParse(strResp: String) -> UpdateRegJsonParse - { - struct RegisterUpdate: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - let deviceId: Int - } + func updateregistrationJParse(strResp: String) -> UpdateRegJsonParse { - guard let jsonData = strResp.data(using: .utf8) else { return UpdateRegJsonParse(deviceId: "")} - //let jsonData = JSON.self(using: .utf8)! + PushKConstants.logger.debug("updateregistrationJParse start: strResp: \(strResp)") do { + let jsonData = Data(strResp.utf8) let parsedJson: RegisterUpdate = try JSONDecoder().decode(RegisterUpdate.self, from: jsonData) let res = UpdateRegJsonParse.init(deviceId: String(parsedJson.deviceId)) return res } catch { + PushKConstants.logger.debug("updateregistrationJParse error: \(error.localizedDescription)") let res = UpdateRegJsonParse.init(deviceId: "unknown") return res } } - func getDeviceListJson(strResp: String) -> PushKGetDeviceList - { + func getDeviceListJson(strResp: String) -> PushKGetDeviceList { - struct PushKGetDeviceListParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var id : Int - var osType: String - var osVersion: String - var deviceType: String - var deviceName: String - var sdkVersion: String - var createdAt: String - var updatedAt: String - } - - struct DevListRespAll: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - let devices: [PushKGetDeviceListParse] - } + PushKConstants.logger.debug("getDeviceListJson start: strResp: \(strResp)") - guard let jsonData = strResp.data(using: .utf8) else { return PushKGetDeviceList(devices: [])} do { - let parsedJson: DevListRespAll = try JSONDecoder().decode(DevListRespAll.self, from: jsonData) + let jsonData = Data(strResp.utf8) + let parsedJson: DevListRespAll = try JSONDecoder().decode(DevListRespAll.self, from: jsonData) - var devPushK: [PushKGetDevice] = [] + var devPushK: [PushKGetDevice] = [] - for i in parsedJson.devices - { - let dev1: PushKGetDevice = PushKGetDevice.init(id: i.id, osType: i.osType, osVersion: i.osVersion, deviceType: i.deviceType, deviceName: i.deviceName, sdkVersion: i.sdkVersion, createdAt: i.createdAt, updatedAt: i.updatedAt) - devPushK.append(dev1) - } + for i in parsedJson.devices{ + let dev1: PushKGetDevice = PushKGetDevice.init(id: i.id, osType: i.osType, osVersion: i.osVersion, deviceType: i.deviceType, deviceName: i.deviceName, sdkVersion: i.sdkVersion, createdAt: i.createdAt, updatedAt: i.updatedAt) + devPushK.append(dev1) + } - let res = PushKGetDeviceList.init(devices: devPushK) - return res + let res = PushKGetDeviceList.init(devices: devPushK) + return res } catch { + PushKConstants.logger.debug("getDeviceListJson error: \(error.localizedDescription)") let res = PushKGetDeviceList.init(devices: []) return res } } - func getMessageHistoryJson(strResp: String) -> MessagesListResponse - { - - struct ImageResponseParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var url: String?=nil - } - - struct ButtonResponseParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var text: String?=nil - var url: String?=nil - } - - struct PushKMessageListParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var phone: String?=nil - var messageId: String?=nil - var title: String?=nil - var body: String?=nil - var image: ImageResponseParse?=nil - var button: ButtonResponseParse?=nil - var time: String?=nil - var partner: String?=nil - } + func getMessageHistoryJson(strResp: String) -> MessagesListResponse{ - struct MessagesListRespAll: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var limitDays: Int?=nil - var limitMessages: Int?=nil - var lastTime: Int?=nil - var messages: [PushKMessageListParse] - } - - - - guard let jsonData = strResp.data(using: .utf8) else { return MessagesListResponse(limitDays: 0, limitMessages: 0, lastTime: 0, messages: [])} + PushKConstants.logger.debug("getMessageHistoryJson start: strResp: \(strResp)") do { + let jsonData = Data(strResp.utf8) let parsedJson: MessagesListRespAll = try JSONDecoder().decode(MessagesListRespAll.self, from: jsonData) var messListPushK: [MessagesResponseStr] = [] - for i in parsedJson.messages - { + for i in parsedJson.messages{ let elem3: ImageResponse = ImageResponse.init(url: i.image?.url) let elem2: ButtonResponse = ButtonResponse.init(text: i.button?.text, url: i.button?.url) let elem1: MessagesResponseStr = MessagesResponseStr.init(phone: i.phone, messageId: i.messageId, title: i.title, body: i.body, image: elem3, button: elem2, time: i.time, partner: i.partner) @@ -188,7 +89,7 @@ class PushServerAnswParser { let res = MessagesListResponse.init(limitDays: parsedJson.limitDays, limitMessages: parsedJson.limitMessages, lastTime: parsedJson.lastTime, messages: messListPushK) return res } catch { - //handle error + PushKConstants.logger.debug("getMessageHistoryJson error: \(error.localizedDescription)") let res = MessagesListResponse.init(limitDays: 0, limitMessages: 0, lastTime: 0, messages: []) return res } @@ -200,75 +101,13 @@ class PushServerAnswParser { { PushKConstants.logger.debug("messageIncomingJson start: strResp: \(strResp)") - let strRespTransform = strResp.replacingOccurrences(of: "\"{", with: "{", options: .literal, range: nil).replacingOccurrences(of: "}\"", with: "}", options: .literal, range: nil) - - - struct ButtonResponseParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var text: String?=nil - var url: String?=nil - } - - struct MessApsData: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - enum CodingKeys: String, CodingKey { - case contentavailable = "content-available" - } - var contentavailable: Int? = 0 - } - - struct ImageResponseParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var url: String?=nil - } - - struct PushKMessageListParse: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - var image: ImageResponseParse?=nil - var button: ButtonResponseParse?=nil - var partner: String?=nil - var phone: String?=nil - var messageId: String?=nil - var time: String?=nil - var body: String?=nil - var title: String?=nil - } - - struct FullFirebaseMessage: Decodable { - enum Category: String, Decodable { - case swift, combine, debugging, xcode - } - enum CodingKeys: String, CodingKey { - case gcmmessageid = "gcm.message_id" - case message = "message" - case googlecsenderid = "google.c.sender.id" - case aps = "aps" - case source = "source" - } - var aps: MessApsData?=nil - var googlecsenderid: String? = "" - var message: PushKMessageListParse?=nil - var gcmmessageid: String? = "" - var source: String? = "" - } - - - PushKConstants.logger.debug("messageIncomingJson before decoding: strRespTransform: \(strRespTransform)") - - - guard let jsonData = strRespTransform.data(using: .utf8) else { return FullFirebaseMessageStr(aps: MessApsDataStr(contentAvailable: 0), message: MessagesResponseStr(phone: "", messageId: "", title: "", body: "", image: ImageResponse(url: ""), button: ButtonResponse(text: "", url: ""), time: "", partner: ""),googleCSenderId: "", gcmMessageId: "")} - - PushKConstants.logger.debug("messageIncomingJson transformed to data") - do { + let strRespReplaced = strResp.replacingOccurrences(of: "\"{", with: "{").replacingOccurrences(of: "}\"", with: "}") + PushKConstants.logger.debug("messageIncomingJson before decoding: strRespTransform: \(strRespReplaced)") + + let jsonData = Data(strRespReplaced.utf8) + PushKConstants.logger.debug("messageIncomingJson transformed to data") + let parsedJson: FullFirebaseMessage = try JSONDecoder().decode(FullFirebaseMessage.self, from: jsonData) PushKConstants.logger.debug("messageIncomingJson parsedJson: \(parsedJson)") @@ -287,7 +126,7 @@ class PushServerAnswParser { return res } catch { - //handle error + PushKConstants.logger.debug("getMessageHistoryJson error: \(error.localizedDescription)") let res = FullFirebaseMessageStr.init(aps: MessApsDataStr(contentAvailable: 0), message: MessagesResponseStr(phone: "", messageId: "", title: "", body: "", image: ImageResponse(url: ""), button: ButtonResponse(text: "", url: ""), time: "", partner: ""), googleCSenderId: "", @@ -297,7 +136,141 @@ class PushServerAnswParser { } +} - + +// DECODALE SRUCTURS + +// related to message parsing +struct ButtonResponseParse: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + var text: String?=nil + var url: String?=nil +} + +struct MessApsData: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + enum CodingKeys: String, CodingKey { + case contentavailable = "content-available" + } + var contentavailable: Int? = 0 +} + +struct ImageResponseParse: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + var url: String?=nil +} + +struct PushKMessageListParse: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + var image: ImageResponseParse?=nil + var button: ButtonResponseParse?=nil + var partner: String?=nil + var phone: String?=nil + var messageId: String?=nil + var time: String?=nil + var body: String?=nil + var title: String?=nil +} + +struct FullFirebaseMessage: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + enum CodingKeys: String, CodingKey { + case gcmmessageid = "gcm.message_id" + case message = "message" + case googlecsenderid = "google.c.sender.id" + case aps = "aps" + case source = "source" + } + var aps: MessApsData?=nil + var googlecsenderid: String? = "" + var message: PushKMessageListParse?=nil + var gcmmessageid: String? = "" + var source: String? = "" +} + + +struct MessagesListRespAll: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + var limitDays: Int?=nil + var limitMessages: Int?=nil + var lastTime: Int?=nil + var messages: [PushKMessageListParse] +} + + +// registration parsing +struct RegisterSession: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + let token: String +} + +struct RegisterDevice: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + let deviceId: Int +} + +struct RegisterProfile: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + let userId: Int + let userPhone: String + let createdAt: String +} + +struct FullRegister: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + let session: RegisterSession + let profile: RegisterProfile + let device: RegisterDevice +} + +//registration update parsing +struct RegisterUpdate: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + let deviceId: Int +} + +//get devices parsing +struct PushKGetDeviceListParse: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + var id : Int + var osType: String + var osVersion: String + var deviceType: String + var deviceName: String + var sdkVersion: String + var createdAt: String + var updatedAt: String +} + +struct DevListRespAll: Decodable { + enum Category: String, Decodable { + case swift, combine, debugging, xcode + } + let devices: [PushKGetDeviceListParse] } diff --git a/PushSDK/core/Notifications.swift b/PushSDK/core/Notifications.swift new file mode 100644 index 0000000..4d25b90 --- /dev/null +++ b/PushSDK/core/Notifications.swift @@ -0,0 +1,137 @@ +// +// Notifications.swift +// PushSDK +// +// Created by o.korniienko on 14.09.22. +// + +import Foundation +import UserNotifications + + + +class PushNotification { + + func preparePushNotification(imageUrl: String = "", + timeInterval: TimeInterval = 1.7, + contentTitle: String = "", + contentSubtitle: String = "", + contentBody: String, + userInfo: [AnyHashable: Any] + ) { + PushKConstants.logger.debug("makePushNotification input: imageUrl: \(imageUrl), timeInterval: \(timeInterval), contentTitle: \(contentTitle), contentSubtitle: \(contentSubtitle), contentBody: \(contentBody)") + + let content = UNMutableNotificationContent() + content.userInfo = userInfo + + if(contentBody != ""){ + content.body = contentBody + } + if(contentTitle != ""){ + content.title = contentTitle + } + if(contentSubtitle != ""){ + content.subtitle = contentSubtitle + } + content.sound = UNNotificationSound.default + content.categoryIdentifier = "pushKActionCategory" + + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: false) + + if(imageUrl != ""){ + if let url = URL(string: imageUrl){ + Task{ + guard let data = NSData(contentsOf: url) else{ + PushKConstants.logger.debug("image getting error with url: \(url)") + makeNotification(content: content, trigger: trigger) + PushKConstants.logger.debug("notification without image was made") + return + } + + let fileIdentifier = ProcessInfo.processInfo.globallyUniqueString + let target = FileManager.default.temporaryDirectory.appendingPathComponent(fileIdentifier).appendingPathExtension(url.pathExtension) + + do{ + try data.write(to: target) + let attachment = try UNNotificationAttachment(identifier: fileIdentifier, url: target, options: nil) + content.attachments.append(attachment) + + makeNotification(content: content, trigger: trigger) + PushKConstants.logger.debug("notification with image was made") + + }catch{ + PushKConstants.logger.debug("error: \(error.localizedDescription)") + makeNotification(content: content, trigger: trigger) + PushKConstants.logger.debug("notification without image was made") + } + } + + }else{ + makeNotification(content: content, trigger: trigger) + PushKConstants.logger.debug("notification without image was made - url error") + } + + }else{ + + makeNotification(content: content, trigger: trigger) + PushKConstants.logger.debug("notification without image was made") + + } + + + } + + func makeNotification(content: UNMutableNotificationContent, trigger: UNTimeIntervalNotificationTrigger){ + let request = UNNotificationRequest(identifier: Date().description, content: content, trigger: trigger) + + UNUserNotificationCenter.current().add(request, withCompletionHandler: { (error) in + if let error = error { + PushKConstants.logger.error("UNUserNotificationCenter error: \(error.localizedDescription)") + } + }) + } + + func areNotificationsEnabled(completion:@escaping (Bool)->Swift.Void) { + var notificationStatus: Bool = false + let current = UNUserNotificationCenter.current() + current.getNotificationSettings(completionHandler: { permission in + switch permission.authorizationStatus { + case .authorized: + PushKConstants.logger.debug("User granted permission for notification") + notificationStatus = true + completion(notificationStatus) + break + case .denied: + PushKConstants.logger.debug("User denied notification permission") + notificationStatus = false + completion(notificationStatus) + break + case .notDetermined: + PushKConstants.logger.debug("Notification permission haven't been asked yet") + notificationStatus = false + completion(notificationStatus) + break + case .provisional: + // @available(iOS 12.0, *) + PushKConstants.logger.debug("The application is authorized to post non-interruptive user notifications.") + notificationStatus = true + completion(notificationStatus) + break + case .ephemeral: + // @available(iOS 14.0, *) + PushKConstants.logger.debug("The application is temporarily authorized to post notifications. Only available to app clips.") + notificationStatus = false + completion(notificationStatus) + break + @unknown default: + PushKConstants.logger.debug("Unknow Status") + notificationStatus = false + completion(notificationStatus) + break + } + }) + } +} + + + diff --git a/PushSDK/firebase/FireBaseHelpers.swift b/PushSDK/firebase/FireBaseHelpers.swift index 741fcc2..e4bf741 100644 --- a/PushSDK/firebase/FireBaseHelpers.swift +++ b/PushSDK/firebase/FireBaseHelpers.swift @@ -26,7 +26,7 @@ internal class PushSdkFirHelpers { }) - PushKConstants.logger.debug("answToken token: \(PushKConstants.firebaseRegistrationToken ?? "")") + PushKConstants.logger.debug("old token: \(PushKConstants.firebaseRegistrationToken ?? "")") let tokenFcm = String(Messaging.messaging().fcmToken ?? "") if (tokenFcm != "") { diff --git a/PushSDK/settings/PushConstants.swift b/PushSDK/settings/PushConstants.swift index 6402f01..06acdd4 100644 --- a/PushSDK/settings/PushConstants.swift +++ b/PushSDK/settings/PushConstants.swift @@ -28,7 +28,7 @@ public struct PushKConstants { let kOSType = "ios" static let serverSdkVersion = "2.3" - static let sdkVersion = "1.0.0.45" + static let sdkVersion = "1.1.1" static let devOSVersion = UIDevice.current.systemVersion static let deviceType = "\(UIDevice.current.model)" static let deviceType2 = "\(UIDevice.current.batteryLevel)" @@ -39,7 +39,7 @@ public struct PushKConstants { static let kPushClientAPIKey = UserDefaults.standard.string(forKey:"clientApiKey") static let FCMToken = UserDefaults.standard.string(forKey: "fcmToken") - static let branch = "master" + static let branch = "main" public static var messageBuffer = "" as String public static var basePushURLactive = "" as String diff --git a/README.md b/README.md index 7de999d..4625213 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,183 @@ # IOS PushSDK *** -Install and init Cocoapods.
+### Install and init Cocoapods.
Open a terminal window and run $ sudo gem install cocoapods to instal Cocoapods.
Then $ cd into your project directory and run $ pod init to create a Podfile. Important ! Before run $ pod init your project should be closed.
To update dependencies in the Podfile run - $ pod update.
To open your project run $ open ProjectName.xcworkspace
More about Cocoapods and Podfile here - https://cocoapods.org, https://guides.cocoapods.org/using/the-podfile.html and https://guides.cocoapods.org/using/using-cocoapods.html. -last actual SDK version: 1.0.0.45
+### Add sdk to your project. +Last actual SDK version: 1.1.1
To integrate PushSDK to your project with COCOAPODS (https://guides.cocoapods.org/using/the-podfile.html) add the next line in Podfile.
pod 'PushSDK', :git => 'https://github.com/GlobalMessageServices/BCS-GMS-SDK-IOS', :branch => 'gmsapi' *** +Important ! Before start using SDK, configure firebase project first and create App Id and APNS key. -* Example of PushSDK initializing +*** +## Add Firebase cloud messaging to your project +* Create a project at https://console.firebase.google.com/ (or use an existing one).
+![image](https://user-images.githubusercontent.com/46021248/190978818-899f7023-9855-4c79-ad03-efb933874e6f.png) +* Add new IOS app.
+![image](https://user-images.githubusercontent.com/46021248/190979252-99141b74-649e-4a46-a492-f3506c6976a1.png) +* Do not forget to download config file GoogleService-Info.plist and add it into the root of your project.
+![image](https://user-images.githubusercontent.com/46021248/190979329-1776b8b2-00e9-4202-94ff-c1c46f613405.png) +* Step 3 should be skipped.
+![image](https://user-images.githubusercontent.com/46021248/190979384-5b94a475-78af-49c0-8328-212ccef8c8cf.png) +* After that you need to create App Id and APNS key by following the [instruction](https://github.com/GlobalMessageServices/BCS-GMS-SDK-IOS/wiki/Creating-App-Id-and-APNS-key). + +# Using SDK +## Initialize firebase helper functions into your AppDelegate.swift: +```swift + import UIKit + import PushSDK + import SwiftyBeaver + import UserNotifications + + @UIApplicationMain + public class AppDelegate: UIResponder, UIApplicationDelegate{ + let fb_ad = PushSDKFirebase.init() + public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Override point for customization after application launch. + FirebaseApp.configure() + fb_ad.fbInitApplication(didFinishLaunchingWithOptions: launchOptions) + application.registerForRemoteNotifications() + fb_ad.registerForPushNotifications() + UNUserNotificationCenter.current().delegate = self + + return true + } + + + func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { + fb_ad.fbInitApplication(didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) + } + + public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { + fb_ad.fbInitApplication(didReceiveRemoteNotification: userInfo) + } + + public func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void){ + fb_ad.fbInitApplication(didReceiveRemoteNotification: userInfo, fetchCompletionHandler: completionHandler) + } + + public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + fb_ad.fbInitApplication(didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) + } +} +``` + +## Configure processing incoming messages in ViewController.swift: +```swift +import UIKit +import PushSDK +import UserNotifications +import Alamofire +import JSON + +class ViewController: UIViewController { + + @IBOutlet var textOutput : UITextView! + +//PushSDK initialization + let pushAdapterSdk = PushSDK.init(basePushURL: "https://example.com/push/", enableNotification: true, enableDeliveryReportAuto: true, deliveryReportLogic: 1) + + override func viewDidLoad() { + super.viewDidLoad() + //register in notification center + NotificationCenter.default.addObserver(self, selector: #selector(onReceiveFromPushServer(_:)), name: .receivePushKData, object: nil) + UNUserNotificationCenter.current().delegate = self + } +} + +extension ViewController: UNUserNotificationCenterDelegate { + + //for displaying notification when app is in foreground + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + //If you don't want to show notification when app is open, do something here else and make a return here. + //If you don't implement this delegate method, you will not see the notification on the specified controller. So, you have to implement this delegate and make sure the below line execute. i.e. completionHandler. + completionHandler([.alert, .badge, .sound]) + } + + // For handling tap and user actions + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + completionHandler() + //UIApplication.shared.applicationIconBadgeNumber = 0 + } + + //processing incoming data message + @objc func onReceiveFromPushServer(_ notification: Notification) { + let incomMessage = PushSDK.parseIncomingPush(message: notification).messageFir + textOutput.text = textOutput.text + "\n" + incomMessage.message.toString() + } +} + +extension AppDelegate: UNUserNotificationCenterDelegate{ + public func userNotificationCenter(_ center: UNUserNotificationCenter, + willPresent notification: UNNotification, + withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.alert, .sound, .badge]) + } + + public func userNotificationCenter(_ center: UNUserNotificationCenter, + didReceive response: UNNotificationResponse, + withCompletionHandler completionHandler: @escaping () -> Void) { + completionHandler() + } +} +``` + +## An example of registration a device: +* Init pPushSDK +```swift +let pushAdapterSdk = PushSDK.init(basePushURL: "https://push.example.com/api/") +``` +* Make registration. +```swift +let registrator: PushKFunAnswerRegister = pushAdapterSdk.pushRegisterNew(user_phone: "375291234567", user_password: "1", x_push_sesion_id: PushKConstants.firebase_registration_token ?? "", x_push_ios_bundle_id: "12345678", X_Push_Client_API_Key: "test_key") +``` +* IMPORTANT. Then update registration. +```swift +pushAdapterSdk.pushUpdateRegistration() +``` + +### Some another example of PushSDK initializing ```swift let pushAdapterSdk = PushSDK.init(basePushURL: "https://push.example.com/api/", enableNotification: true, enableDeliveryReportAuto: true, deliveryReportLogic:1) ``` -*** *** -Notification/delivery reports control -You can enable/disable displaying notification and sending delivery reports with the following optional parameters in PushSDK: +## Notification/delivery reports control +### You can enable/disable displaying notification and sending delivery reports with the following optional parameters in PushSDK: - enableNotification: Bool - enable/disable display notification. Default is true (enabled) - enableDeliveryReportAuto: Bool - enable/disable sending delivery report. Default is true (enabled) - deliveryReportLogic: Int - working if enableNotification is true and enableDeliveryReportAuto is true. Options of deliveryReportLogic:
1 - if notification permitted in application settings, then send delivery report. Else not send report
2 - always send delivery report if receive message +### You can check notification permission with areNotificationsEnabled() public function. You can use it in synchronous and asynchronous ways.
+* synchronous: +```swift +let permission = pushAdapterSdk.areNotificationsEnabled() +``` +* asynchronous: +```swift +pushAdapterSdk.areNotificationsEnabled { (notificationStatus) in + debugPrint(notificationStatus) +} + +``` *** -#SDK functions description +# SDK functions description All this functions are available from PushSDK class For using it, create this class new instance first *** -* New device registration. Register your device on push server +* new device registration. Register your device on push server ```swift public func pushRegisterNew(userPhone: String, userPassword: String, xPushSessionId: String, xPushIOSBundleId: String, xPushClientAPIKey: String) -> PushKFunAnswerRegister @@ -45,7 +185,7 @@ public func pushRegisterNew(userPhone: String, userPassword: String, xPushIOSBun ``` *** -* Clear local device on server. This function clear on push server only locally saved device id +* clear local device on server. This function clear on push server only locally saved device id ```swift public func pushClearCurrentDevice()->PushKGeneralAnswerStruct ``` @@ -57,13 +197,13 @@ public func pushGetMessageHistory(periodInSeconds: Int) -> PushKFunAnswerGetMess ``` *** -* Check message queue +* check message queue ```swift public func pushCheckQueue() -> PushKFunAnswerGeneral ``` *** -* Get all devices from server +* get all devices from server ```swift public func pushGetDeviceAllFromServer() -> PushKFunAnswerGetDeviceList ``` @@ -81,26 +221,41 @@ public func pushSendMessageCallback(messageId: String, callbackText: String) -> ``` *** -* Send delivery report to server +* send delivery report to server ```swift public func pushMessageDeliveryReport(messageId: String) -> PushKGeneralAnswerStruct ``` *** -* Clear all devices registered with current msisdn +* clear all devices registered with current msisdn ```swift public func pushClearAllDevice()->PushKGeneralAnswerStruct ``` *** -* Parse incoming notification +* parse incoming notification ```swift public static func parseIncomingPush(message: Notification) -> PushKMess ``` *** -* Parse incoming notification UserInfo +* parse incoming notification UserInfo ```swift public static func parseIncomingPush(message: [AnyHashable : Any]) -> PushKMess ``` *** + +# SDK answers.
+200 - Ok. All processed successfully
+ +## Answers from remote server.
+401 - HTTP code (Client error) authentication error
+400 - HTTP code (Client error) request validation error
+500 - HTTP code (Server error)
+ + +## SDK errors
+700 - internal SDK error
+701 - incorrect input
+704 - not registered
+707 - registration already exists