From dba2c2c1cb0db739135fac9feb57bfb4cb282ff0 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 09:48:19 +0200 Subject: [PATCH 01/30] feat: add push_notification decodable --- .../Sources/PushNotification.swift | 62 +++++++++++++++++++ .../Resources/notification-background.json | 20 ++++++ .../Tests/Resources/notification-badge.json | 20 ++++++ .../Tests/Resources/notification-buttons.json | 40 ++++++++++++ .../Resources/notification-deeplink.json | 24 +++++++ .../Tests/Resources/notification-image.json | 21 +++++++ .../Tests/Resources/notification-message.json | 18 ++++++ .../Tests/Sources/PushNotificationTests.swift | 59 ++++++++++++++++++ Package.swift | 10 ++- 9 files changed, 272 insertions(+), 2 deletions(-) create mode 100644 OptimoveNotificationServiceExtension/Sources/PushNotification.swift create mode 100644 OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json create mode 100644 OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json create mode 100644 OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json create mode 100644 OptimoveNotificationServiceExtension/Tests/Resources/notification-deeplink.json create mode 100644 OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json create mode 100644 OptimoveNotificationServiceExtension/Tests/Resources/notification-message.json create mode 100644 OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift diff --git a/OptimoveNotificationServiceExtension/Sources/PushNotification.swift b/OptimoveNotificationServiceExtension/Sources/PushNotification.swift new file mode 100644 index 00000000..19e5cfd4 --- /dev/null +++ b/OptimoveNotificationServiceExtension/Sources/PushNotification.swift @@ -0,0 +1,62 @@ +// Copyright © 2023 Optimove. All rights reserved. + +/// Represents a push notification received from the server. +struct PushNotification: Decodable { + struct Button: Decodable { + struct Icon: Decodable { + enum IconType: String, Decodable { + case custom + case system + } + + let id: String + let type: IconType + } + + let id: String + let icon: Icon? + let text: String + } + + let id: Int + let badge: Int? + let buttons: [Button]? + let isBackground: Bool + let picturePath: String? + + private enum CodingKeys: String, CodingKey { + case a + case aps + case attachments + case badge = "badge_inc" + case buttons = "k.buttons" + case custom + case data + case deeplink = "k.deepLink" + case id + case isBackground = "content-available" + case message = "k.message" + case pictureUrl + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let custom = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .custom) + self.badge = try custom.decodeIfPresent(Int.self, forKey: .badge) + + let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) + self.buttons = try a.decodeIfPresent([Button].self, forKey: .buttons) + + let message = try a.nestedContainer(keyedBy: CodingKeys.self, forKey: .message) + let data = try message.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) + self.id = try data.decode(Int.self, forKey: .id) + + let aps = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .aps) + let isBackground = try aps.decodeIfPresent(Int.self, forKey: .isBackground) + self.isBackground = isBackground == 1 ? true : false + + let attachments = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: .attachments) + self.picturePath = try attachments?.decodeIfPresent(String.self, forKey: .pictureUrl) + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json new file mode 100644 index 00000000..451dc22a --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json @@ -0,0 +1,20 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + }, + "content-available": 1, + "mutable-content": 1 + }, + "custom": { + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + } + } + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json new file mode 100644 index 00000000..c60dbe8d --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json @@ -0,0 +1,20 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + }, + "badge": 42 + }, + "custom": { + "badge_inc": 42, + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + } + } + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json new file mode 100644 index 00000000..4871228d --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json @@ -0,0 +1,40 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + } + }, + "custom": { + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + }, + "k.buttons": [ + { + "id": "1", + "text": "action_1" + }, + { + "id": "2", + "text": "action_2", + "icon": { + "type": "system", + "id": "sys_icon_id" + } + }, + { + "id": "3", + "text": "action_3", + "icon": { + "type": "custom", + "id": "custom_icon_id" + } + } + ] + } + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-deeplink.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-deeplink.json new file mode 100644 index 00000000..dbdd2a5e --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-deeplink.json @@ -0,0 +1,24 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + } + }, + "custom": { + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + }, + "k.deepLink": { + "type": 2, + "data": { + "id": 1 + } + } + } + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json new file mode 100644 index 00000000..5800a09e --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json @@ -0,0 +1,21 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + } + }, + "attachments": { + "pictureUrl": "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg" + }, + "custom": { + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + } + } + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-message.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-message.json new file mode 100644 index 00000000..1bc8d635 --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-message.json @@ -0,0 +1,18 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + } + }, + "custom": { + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + } + } + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift b/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift new file mode 100644 index 00000000..829a83bc --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift @@ -0,0 +1,59 @@ +// Copyright © 2023 Optimove. All rights reserved. + +@testable import OptimoveNotificationServiceExtension +import OptimoveTest +import XCTest + +final class PushNotificationTests: XCTestCase, FileAccessible { + var fileName: String = "" + + func test_decode_id() throws { + fileName = "notification-message.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + XCTAssertEqual(notification.id, 1) + } + + func test_decode_badge() throws { + fileName = "notification-badge.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + XCTAssertEqual(notification.badge, 42) + } + + func test_decode_buttons() throws { + fileName = "notification-buttons.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + + XCTAssertEqual(notification.buttons?.count, 3) + XCTAssertEqual(notification.buttons?[0].id, "1") + XCTAssertEqual(notification.buttons?[0].text, "action_1") + + XCTAssertEqual(notification.buttons?[1].id, "2") + XCTAssertEqual(notification.buttons?[1].text, "action_2") + XCTAssertEqual(notification.buttons?[1].icon?.id, "sys_icon_id") + XCTAssertEqual(notification.buttons?[1].icon?.type, .system) + + XCTAssertEqual(notification.buttons?[2].id, "3") + XCTAssertEqual(notification.buttons?[2].text, "action_3") + XCTAssertEqual(notification.buttons?[2].icon?.id, "custom_icon_id") + XCTAssertEqual(notification.buttons?[2].icon?.type, .custom) + } + + func test_decode_image() throws { + fileName = "notification-image.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + + XCTAssertEqual(notification.picturePath, "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg") + } + + func test_decode_background() throws { + fileName = "notification-background.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + + XCTAssertEqual(notification.isBackground, true) + } +} diff --git a/Package.swift b/Package.swift index 4342fc11..a4e642b1 100644 --- a/Package.swift +++ b/Package.swift @@ -78,8 +78,14 @@ let package = Package( ), .testTarget( name: "OptimoveNotificationServiceExtensionTests", - dependencies: ["OptimoveNotificationServiceExtension"], - path: "OptimoveNotificationServiceExtension/Tests" + dependencies: [ + "OptimoveNotificationServiceExtension", + "OptimoveTest", + ], + path: "OptimoveNotificationServiceExtension/Tests", + resources: [ + .process("Resources"), + ] ), ], swiftLanguageVersions: [.v5] From 04f8fe5bdc7303843a2924d0d443b94c9e822ac3 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 11:46:20 +0200 Subject: [PATCH 02/30] refactor: notification attachments --- OptimobileShared/MediaHelper.swift | 27 +- OptimobileShared/PendingNotification.swift | 6 + .../Sources/OptimoveNotificationService.swift | 246 ++++++------------ ...oveNotificationServiceExtensionTests.swift | 54 ++-- .../Classes/Optimobile/OptimoveInApp.swift | 5 +- Package.swift | 3 +- 6 files changed, 155 insertions(+), 186 deletions(-) diff --git a/OptimobileShared/MediaHelper.swift b/OptimobileShared/MediaHelper.swift index 066077b3..bbffa4d2 100644 --- a/OptimobileShared/MediaHelper.swift +++ b/OptimobileShared/MediaHelper.swift @@ -3,17 +3,28 @@ import Foundation enum MediaHelper { - /// Use ``Region.US`` as fallback region. - static let mediaResizerBaseUrl: String = "https://i-us-east-1.app.delivery" + enum Error: LocalizedError { + case noMediaUrlFound + case invalidPictureUrl(String) + } + + static func getCompletePictureUrl(pictureUrlString: String, width: UInt) throws -> URL { + if pictureUrlString.hasPrefix("https://") || pictureUrlString.hasPrefix("http://") { + guard let url = URL(string: pictureUrlString) else { + throw Error.invalidPictureUrl(pictureUrlString) + } + return url + } - static func getCompletePictureUrl(pictureUrl: String, width: UInt) -> URL? { - if pictureUrl.hasPrefix("https://") || pictureUrl.hasPrefix("http://") { - return URL(string: pictureUrl) + guard let mediaUrl = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) as? String else { + throw Error.noMediaUrlFound } - let baseUrl = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) as? String ?? mediaResizerBaseUrl + let urlString = "\(mediaUrl)/\(width)x/\(pictureUrlString)" + guard let url = URL(string: urlString) else { + throw Error.invalidPictureUrl(urlString) + } - let completeString = "\(baseUrl)/\(width)x/\(pictureUrl)" - return URL(string: completeString) + return url } } diff --git a/OptimobileShared/PendingNotification.swift b/OptimobileShared/PendingNotification.swift index b4003d74..ffe747e1 100644 --- a/OptimobileShared/PendingNotification.swift +++ b/OptimobileShared/PendingNotification.swift @@ -6,4 +6,10 @@ struct PendingNotification: Codable { let id: Int let deliveredAt: Date let identifier: String + + init(id: Int, deliveredAt: Date = .init(), identifier: String) { + self.id = id + self.deliveredAt = deliveredAt + self.identifier = identifier + } } diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 22a29e03..36af5920 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -5,193 +5,128 @@ import UIKit import UserNotifications public class OptimoveNotificationService { - private static let syncBarrier = DispatchSemaphore(value: 0) + enum Error: String, LocalizedError { + case noBestAttemptContent + case userInfoNotValid + } - public class func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { - let bestAttemptContent = (request.content.mutableCopy() as! UNMutableNotificationContent) - let userInfo = request.content.userInfo + public class func didReceive( + _ request: UNNotificationRequest, + withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void + ) { + Task { + do { + let bestAttemptContent = try await didReceive(request) + contentHandler(bestAttemptContent) + } catch { + assertionFailure(error.localizedDescription) + contentHandler(request.content) + } + } + } - if !validateUserInfo(userInfo: userInfo) { - return + class func didReceive(_ request: UNNotificationRequest) async throws -> UNNotificationContent { + guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else { + throw Error.noBestAttemptContent } - let custom = userInfo["custom"] as! [AnyHashable: Any] - let data = custom["a"] as! [AnyHashable: Any] + let userInfo = request.content.userInfo + guard JSONSerialization.isValidJSONObject(userInfo) else { + throw Error.userInfoNotValid + } - let msg = data["k.message"] as! [AnyHashable: Any] - let msgData = msg["data"] as! [AnyHashable: Any] - let id = msgData["id"] as! Int + let data = try JSONSerialization.data(withJSONObject: userInfo) + let notification = try JSONDecoder().decode(PushNotification.self, from: data) if bestAttemptContent.categoryIdentifier == "" { - let actionButtons = getButtons(userInfo: userInfo, bestAttemptContent: bestAttemptContent) - - addCategory(bestAttemptContent: bestAttemptContent, actionArray: actionButtons, id: id) + bestAttemptContent.categoryIdentifier = buildCategorIdentifier(notification: notification) } - let dispatchGroup = DispatchGroup() - - maybeAddImageAttachment(dispatchGroup: dispatchGroup, userInfo: userInfo, bestAttemptContent: bestAttemptContent) - if AppGroupsHelper.isKumulosAppGroupDefined() { - maybeSetBadge(bestAttemptContent: bestAttemptContent, userInfo: userInfo) - PendingNotificationHelper.add(notification: PendingNotification(id: id, deliveredAt: Date(), identifier: request.identifier)) - } - - dispatchGroup.notify(queue: .main) { - contentHandler(bestAttemptContent) - } - } - - private class func validateUserInfo(userInfo: [AnyHashable: Any]) -> Bool { - var dict: [AnyHashable: Any] = userInfo - let keysInOrder = ["custom", "a", "k.message", "data"] - - for key in keysInOrder { - if dict[key] == nil { - return false + if let attachment = try await maybeGetAttachment(notification: notification) { + bestAttemptContent.attachments = [attachment] } - - dict = dict[key] as! [AnyHashable: Any] - } - - if dict["id"] == nil { - return false + maybeSetBadge(bestAttemptContent: bestAttemptContent, userInfo: userInfo) + PendingNotificationHelper.add( + notification: PendingNotification( + id: notification.id, + identifier: request.identifier + ) + ) } - return true + return bestAttemptContent } - private class func getButtons(userInfo: [AnyHashable: Any], bestAttemptContent _: UNMutableNotificationContent) -> NSMutableArray { - let actionArray = NSMutableArray() - - let custom = userInfo["custom"] as! [AnyHashable: Any] - let data = custom["a"] as! [AnyHashable: Any] - - let buttons = data["k.buttons"] as? NSArray - - if buttons == nil || buttons!.count == 0 { - return actionArray - } - - for button in buttons! { - let buttonDict = button as! [AnyHashable: Any] - - let id = buttonDict["id"] as! String - let text = buttonDict["text"] as! String - + class func buildActions(notification: PushNotification) -> [UNNotificationAction] { + return notification.buttons?.map { button in if #available(iOS 15.0, *) { - let icon = getButtonIcon(button: buttonDict) - let action = UNNotificationAction(identifier: id, title: text, options: .foreground, icon: icon) - actionArray.add(action) + return UNNotificationAction( + identifier: button.id, + title: button.text, + options: .foreground, + icon: buildIcon(button: button) + ) } else { - let action = UNNotificationAction(identifier: id, title: text, options: .foreground) - actionArray.add(action) + return UNNotificationAction( + identifier: button.id, + title: button.text, + options: .foreground + ) } - } - - return actionArray + } ?? [] } @available(iOS 15.0, *) - private class func getButtonIcon(button: [AnyHashable: Any]) -> UNNotificationActionIcon? { - guard let icon = button["icon"] as? [String: String], let iconType = icon["type"], let iconId = icon["id"] else { - return nil + class func buildIcon(button: PushNotification.Button) -> UNNotificationActionIcon? { + guard let icon = button.icon else { return nil } + switch icon.type { + case .custom: + return UNNotificationActionIcon(templateImageName: icon.id) + case .system: + return UNNotificationActionIcon(systemImageName: icon.id) } - - if iconType == "custom" { - // TODO: - What if this doesnt exist? Catch exception -> return nil? - return UNNotificationActionIcon(templateImageName: iconId) - } - - return UNNotificationActionIcon(systemImageName: iconId) } - private class func addCategory(bestAttemptContent: UNMutableNotificationContent, actionArray: NSMutableArray, id: Int) { - let categoryIdentifier = CategoryManager.getCategoryIdForMessageId(messageId: id) + class func buildCategorIdentifier(notification: PushNotification) -> String { + let categoryIdentifier = CategoryManager.getCategoryIdForMessageId(messageId: notification.id) - let category = UNNotificationCategory(identifier: categoryIdentifier, actions: actionArray as! [UNNotificationAction], intentIdentifiers: [], options: .customDismissAction) + let category = UNNotificationCategory( + identifier: categoryIdentifier, + actions: buildActions(notification: notification), + intentIdentifiers: [], + options: .customDismissAction + ) CategoryManager.registerCategory(category: category) - bestAttemptContent.categoryIdentifier = categoryIdentifier + return categoryIdentifier } - private class func maybeAddImageAttachment(dispatchGroup: DispatchGroup, userInfo: [AnyHashable: Any], bestAttemptContent: UNMutableNotificationContent) { - let attachments = userInfo["attachments"] as? [AnyHashable: Any] - let pictureUrl = attachments?["pictureUrl"] as? String - - guard let picUrlNonNull = pictureUrl else { return } - - let picExtension = getPictureExtension(picUrlNonNull) - let url = MediaHelper.getCompletePictureUrl(pictureUrl: picUrlNonNull as String, width: UInt(floor(UIScreen.main.bounds.size.width))) - - dispatchGroup.enter() - - loadAttachment(url!, withExtension: picExtension, completionHandler: { attachment in - if attachment != nil { - bestAttemptContent.attachments = [attachment!] - } - dispatchGroup.leave() - }) - } + class func maybeGetAttachment(notification: PushNotification) async throws -> UNNotificationAttachment? { + guard let picturePath = notification.picturePath else { return nil } - private class func getPictureExtension(_ pictureUrl: String?) -> String? { - if pictureUrl == nil { - return nil - } - let pictureExtension = URL(fileURLWithPath: pictureUrl!).pathExtension - if pictureExtension == "" { - return nil - } + let url = try await MediaHelper.getCompletePictureUrl( + pictureUrlString: picturePath, + width: UInt(floor(UIScreen.main.bounds.size.width)) + ) - return "." + pictureExtension + return try await downloadAttachment(url: url) } - private class func loadAttachment(_ url: URL, withExtension pictureExtension: String?, completionHandler: @escaping (UNNotificationAttachment?) -> Void) { - let session = URLSession(configuration: URLSessionConfiguration.default) - - (session.downloadTask(with: url, completionHandler: { temporaryFileLocation, response, error in - if error != nil { - print("NotificationServiceExtension: \(error!.localizedDescription)") - completionHandler(nil) - return - } - - var finalExt = pictureExtension - if finalExt == nil { - finalExt = self.getPictureExtension(response?.suggestedFilename) - if finalExt == nil { - completionHandler(nil) - return - } - } - - if temporaryFileLocation == nil { - completionHandler(nil) - return - } - - let fileManager = FileManager.default - let localURL = URL(fileURLWithPath: temporaryFileLocation!.path + finalExt!) - do { - try fileManager.moveItem(at: temporaryFileLocation!, to: localURL) - } catch { - completionHandler(nil) - return - } - - var attachment: UNNotificationAttachment? - do { - attachment = try UNNotificationAttachment(identifier: "", url: localURL, options: nil) - } catch { - print("NotificationServiceExtension: attachment error: \(error.localizedDescription)") - } - - completionHandler(attachment) - })).resume() + class func downloadAttachment(url: URL) async throws -> UNNotificationAttachment { + let (data, response) = try await URLSession.shared.data(from: url) + let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()) + .appendingPathComponent(UUID().uuidString) + .appendingPathExtension((response.url ?? url).pathExtension) + try data.write(to: tempURL) + return try UNNotificationAttachment( + identifier: "", + url: tempURL + ) } - private class func maybeSetBadge(bestAttemptContent: UNMutableNotificationContent, userInfo: [AnyHashable: Any]) { + class func maybeSetBadge(bestAttemptContent: UNMutableNotificationContent, userInfo: [AnyHashable: Any]) { let aps = userInfo["aps"] as! [AnyHashable: Any] if let contentAvailable = aps["content-available"] as? Int, contentAvailable == 1 { return @@ -205,13 +140,4 @@ public class OptimoveNotificationService { bestAttemptContent.badge = newBadge KeyValPersistenceHelper.set(newBadge, forKey: OptimobileUserDefaultsKey.BADGE_COUNT.rawValue) } - - private class func isBackgroundPush(userInfo: [AnyHashable: Any]) -> Bool { - let aps = userInfo["aps"] as! [AnyHashable: Any] - if let contentAvailable = aps["content-available"] as? Int, contentAvailable == 1 { - return true - } - - return false - } } diff --git a/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift b/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift index ca1d81cc..b9ee619d 100644 --- a/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift +++ b/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift @@ -1,28 +1,52 @@ // Copyright © 2023 Optimove. All rights reserved. +@testable import OptimoveNotificationServiceExtension import XCTest final class OptimoveNotificationServiceExtensionTests: XCTestCase { - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. + func test_download_attachment() async throws { + let url = URL(string: "https://picsum.photos/200")! + let attachment = try await OptimoveNotificationService.downloadAttachment(url: url) + XCTAssertNotNil(attachment) + XCTAssertTrue( + FileManager.default.fileExists(atPath: attachment.url.path), + "File should exist at path: \(attachment.url.path)" + ) } - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. + func test_download_attachment_in_the_process() async throws { + let url = URL(string: "https://picsum.photos/200")! + for _ in 0 ... 10 { + let attachment = try await OptimoveNotificationService.downloadAttachment(url: url) + XCTAssertNotNil(attachment) + XCTAssertTrue( + FileManager.default.fileExists(atPath: attachment.url.path), + "File should exist at path: \(attachment.url.path)" + ) + } } - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. + func test_download_attachment_withInvalidUrl() async throws { + let url = URL(string: "https://picsum.photos/invalid")! + do { + _ = try await OptimoveNotificationService.downloadAttachment(url: url) + XCTFail("Should throw an error") + } catch { + XCTAssertNotNil(error) + } } - func testPerformanceExample() throws { - // This is an example of a performance test case. - self.measure { - // Put the code you want to measure the time of here. - } + func test_getCompletePictureUrl() throws { + let pictureUrlString = "https://www.optimove.com/wp-content/uploads/2018/12/optimove-logo.png" + let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) + XCTAssertEqual(url.absoluteString, pictureUrlString) + } + + func test_getCompletePictureUrl_withMediaUrl() throws { + let pictureUrlString = "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg" + let mediaUrl = "https://www.optimove.com" + KeyValPersistenceHelper.set(mediaUrl, forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) + let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) + XCTAssertEqual(url.absoluteString, "\(mediaUrl)/100x/\(pictureUrlString)") } } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index 68f9f556..7c5cc9d3 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -60,7 +60,10 @@ public class InAppInboxItem { public func getImageUrl(width: UInt) -> URL? { if let imagePathNotNil = imagePath { - return MediaHelper.getCompletePictureUrl(pictureUrl: imagePathNotNil, width: width) + return try? MediaHelper.getCompletePictureUrl( + pictureUrlString: imagePathNotNil, + width: width + ) } return nil diff --git a/Package.swift b/Package.swift index a4e642b1..38d46b81 100644 --- a/Package.swift +++ b/Package.swift @@ -1,12 +1,11 @@ // swift-tools-version:5.3 -// The swift-tools-version declares the minimum version of Swift required to build this package. import PackageDescription let package = Package( name: "Optimove", platforms: [ - .iOS(.v10), + .iOS(.v13), .macOS(.v10_14), ], products: [ From ae3dacaa9be56860f4d212bb148346117911c9ac Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 13:10:16 +0200 Subject: [PATCH 03/30] chore: add mutable-content flag --- .../Tests/Resources/notification-buttons.json | 3 ++- .../Tests/Resources/notification-image.json | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json index 4871228d..0abe1aab 100644 --- a/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json @@ -3,7 +3,8 @@ "alert": { "title": "title", "body": "body" - } + }, + "mutable-content": 1 }, "custom": { "a": { diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json index 5800a09e..3f3f004c 100644 --- a/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json @@ -3,7 +3,8 @@ "alert": { "title": "title", "body": "body" - } + }, + "mutable-content": 1 }, "attachments": { "pictureUrl": "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg" From e6ea51b0224cf5e5d6e648b5d5fbea52b6f7914d Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 13:24:02 +0200 Subject: [PATCH 04/30] refactor: use static instead of class --- .../Sources/OptimoveNotificationService.swift | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 36af5920..14f025d9 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -4,13 +4,13 @@ import Foundation import UIKit import UserNotifications -public class OptimoveNotificationService { +public enum OptimoveNotificationService { enum Error: String, LocalizedError { case noBestAttemptContent case userInfoNotValid } - public class func didReceive( + public static func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { @@ -25,7 +25,7 @@ public class OptimoveNotificationService { } } - class func didReceive(_ request: UNNotificationRequest) async throws -> UNNotificationContent { + static func didReceive(_ request: UNNotificationRequest) async throws -> UNNotificationContent { guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else { throw Error.noBestAttemptContent } @@ -58,7 +58,7 @@ public class OptimoveNotificationService { return bestAttemptContent } - class func buildActions(notification: PushNotification) -> [UNNotificationAction] { + static func buildActions(notification: PushNotification) -> [UNNotificationAction] { return notification.buttons?.map { button in if #available(iOS 15.0, *) { return UNNotificationAction( @@ -78,7 +78,7 @@ public class OptimoveNotificationService { } @available(iOS 15.0, *) - class func buildIcon(button: PushNotification.Button) -> UNNotificationActionIcon? { + static func buildIcon(button: PushNotification.Button) -> UNNotificationActionIcon? { guard let icon = button.icon else { return nil } switch icon.type { case .custom: @@ -88,7 +88,7 @@ public class OptimoveNotificationService { } } - class func buildCategorIdentifier(notification: PushNotification) -> String { + static func buildCategorIdentifier(notification: PushNotification) -> String { let categoryIdentifier = CategoryManager.getCategoryIdForMessageId(messageId: notification.id) let category = UNNotificationCategory( @@ -103,7 +103,7 @@ public class OptimoveNotificationService { return categoryIdentifier } - class func maybeGetAttachment(notification: PushNotification) async throws -> UNNotificationAttachment? { + static func maybeGetAttachment(notification: PushNotification) async throws -> UNNotificationAttachment? { guard let picturePath = notification.picturePath else { return nil } let url = try await MediaHelper.getCompletePictureUrl( @@ -114,11 +114,11 @@ public class OptimoveNotificationService { return try await downloadAttachment(url: url) } - class func downloadAttachment(url: URL) async throws -> UNNotificationAttachment { + static func downloadAttachment(url: URL) async throws -> UNNotificationAttachment { let (data, response) = try await URLSession.shared.data(from: url) let tempURL = URL(fileURLWithPath: NSTemporaryDirectory()) - .appendingPathComponent(UUID().uuidString) - .appendingPathExtension((response.url ?? url).pathExtension) + .appendingPathComponent(UUID().uuidString) + .appendingPathExtension((response.url ?? url).pathExtension) try data.write(to: tempURL) return try UNNotificationAttachment( identifier: "", @@ -126,7 +126,7 @@ public class OptimoveNotificationService { ) } - class func maybeSetBadge(bestAttemptContent: UNMutableNotificationContent, userInfo: [AnyHashable: Any]) { + static func maybeSetBadge(bestAttemptContent: UNMutableNotificationContent, userInfo: [AnyHashable: Any]) { let aps = userInfo["aps"] as! [AnyHashable: Any] if let contentAvailable = aps["content-available"] as? Int, contentAvailable == 1 { return From f8088bf1cc55295e957caa1d18fda639ee0e97c2 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 13:46:14 +0200 Subject: [PATCH 05/30] refactor: category manager --- .../Sources/CategoryManager.swift | 101 ++++++------------ .../Sources/OptimoveNotificationService.swift | 17 ++- .../Tests/Sources/CategoryManagerTests.swift | 65 +++++++++++ 3 files changed, 103 insertions(+), 80 deletions(-) create mode 100644 OptimoveNotificationServiceExtension/Tests/Sources/CategoryManagerTests.swift diff --git a/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift b/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift index def072a2..19db26c6 100644 --- a/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift +++ b/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift @@ -1,92 +1,55 @@ // Copyright © 2022 Optimove. All rights reserved. -import Foundation import UserNotifications -let MAX_DYNAMIC_CATEGORIES = 128 -let DYNAMIC_CATEGORY_IDENTIFIER = "__kumulos_category_%d__" - -@available(iOS 10.0, *) -class CategoryManager { - let categoryReadLock = DispatchSemaphore(value: 0) - let dynamicCategoryLock = DispatchSemaphore(value: 1) - - fileprivate static var instance: CategoryManager? - - static var sharedInstance: CategoryManager { - if instance == nil { - instance = CategoryManager() - } - - return instance! +enum CategoryManager { + enum Constants { + static let MAX_DYNAMIC_CATEGORIES = 128 + static let DYNAMIC_CATEGORY = OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue } - static func getCategoryIdForMessageId(messageId: Int) -> String { - return String(format: DYNAMIC_CATEGORY_IDENTIFIER, messageId) + static func getCategoryId(messageId: Int) -> String { + return "__kumulos_category_\(messageId)__" } - static func registerCategory(category: UNNotificationCategory) { - var categorySet = sharedInstance.getExistingCategories() - var storedDynamicCategories = sharedInstance.getExistingDynamicCategoriesList() + static func registerCategory(_ category: UNNotificationCategory) async { + var systemCategories = await UNUserNotificationCenter.current().notificationCategories() + var storedCategoryIds = readCategoryIds() + + systemCategories.insert(category) + storedCategoryIds.insert(category.identifier) - categorySet.insert(category) - storedDynamicCategories.append(category.identifier) + let (categories, categoryIds) = maybePruneCategories(categories: systemCategories, categoryIds: storedCategoryIds) - sharedInstance.pruneCategoriesAndSave(categories: categorySet, dynamicCategories: storedDynamicCategories) + UNUserNotificationCenter.current().setNotificationCategories(categories) + writeCategoryIds(categoryIds) // Force a reload of the categories - _ = sharedInstance.getExistingCategories() + await UNUserNotificationCenter.current().notificationCategories() } - private func getExistingCategories() -> Set { - var returnedCategories = Set() - - UNUserNotificationCenter.current().getNotificationCategories { (categories: Set) in - returnedCategories = Set(categories) - - self.categoryReadLock.signal() - } - - _ = categoryReadLock.wait(timeout: DispatchTime.now() + DispatchTimeInterval.seconds(5)) - - return returnedCategories + static func readCategoryIds() -> Set { + let array = UserDefaults.standard.object(forKey: Constants.DYNAMIC_CATEGORY) as? [String] ?? [] + return Set(array) } - private func getExistingDynamicCategoriesList() -> [String] { - dynamicCategoryLock.wait() - defer { - dynamicCategoryLock.signal() - } - - if let existingArray = UserDefaults.standard.object(forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) { - return existingArray as! [String] - } - - let newArray = [String]() - - UserDefaults.standard.set(newArray, forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) - - return newArray + static func writeCategoryIds(_ ids: Set) { + UserDefaults.standard.set(Array(ids), forKey: Constants.DYNAMIC_CATEGORY) } - private func pruneCategoriesAndSave(categories: Set, dynamicCategories: [String]) { - if dynamicCategories.count <= MAX_DYNAMIC_CATEGORIES { - UNUserNotificationCenter.current().setNotificationCategories(categories) - UserDefaults.standard.set(dynamicCategories, forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) - return + static func maybePruneCategories( + categories: Set, + categoryIds: Set, + limit: Int = Constants.MAX_DYNAMIC_CATEGORIES + ) -> (categories: Set, categoryIds: Set) { + if categoryIds.count <= limit, categories.count <= limit { + return (categories: categories, categoryIds: categoryIds) } - let categoriesToRemove = dynamicCategories.prefix(dynamicCategories.count - MAX_DYNAMIC_CATEGORIES) - - let prunedCategories = categories.filter { category -> Bool in - categoriesToRemove.firstIndex(of: category.identifier) == nil - } - - let prunedDynamicCategories = dynamicCategories.filter { cat -> Bool in - categoriesToRemove.firstIndex(of: cat) == nil - } + let categoriesToRemove = categoryIds.prefix(categoryIds.count - limit) + let prunedCategories = categories.filter { !categoriesToRemove.contains($0.identifier) } + let prunedCategoryIds = categoryIds.subtracting(categoriesToRemove) - UNUserNotificationCenter.current().setNotificationCategories(prunedCategories) - UserDefaults.standard.set(prunedDynamicCategories, forKey: OptimobileUserDefaultsKey.DYNAMIC_CATEGORY.rawValue) + return (categories: prunedCategories, categoryIds: prunedCategoryIds) } } diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 14f025d9..4235fcbd 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -29,19 +29,15 @@ public enum OptimoveNotificationService { guard let bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent else { throw Error.noBestAttemptContent } - let userInfo = request.content.userInfo guard JSONSerialization.isValidJSONObject(userInfo) else { throw Error.userInfoNotValid } - let data = try JSONSerialization.data(withJSONObject: userInfo) let notification = try JSONDecoder().decode(PushNotification.self, from: data) - - if bestAttemptContent.categoryIdentifier == "" { - bestAttemptContent.categoryIdentifier = buildCategorIdentifier(notification: notification) + if bestAttemptContent.categoryIdentifier.isEmpty { + bestAttemptContent.categoryIdentifier = await buildCategory(notification: notification) } - if AppGroupsHelper.isKumulosAppGroupDefined() { if let attachment = try await maybeGetAttachment(notification: notification) { bestAttemptContent.attachments = [attachment] @@ -88,17 +84,15 @@ public enum OptimoveNotificationService { } } - static func buildCategorIdentifier(notification: PushNotification) -> String { - let categoryIdentifier = CategoryManager.getCategoryIdForMessageId(messageId: notification.id) - + static func buildCategory(notification: PushNotification) async -> String { + let categoryIdentifier = CategoryManager.getCategoryId(messageId: notification.id) let category = UNNotificationCategory( identifier: categoryIdentifier, actions: buildActions(notification: notification), intentIdentifiers: [], options: .customDismissAction ) - - CategoryManager.registerCategory(category: category) + await CategoryManager.registerCategory(category) return categoryIdentifier } @@ -120,6 +114,7 @@ public enum OptimoveNotificationService { .appendingPathComponent(UUID().uuidString) .appendingPathExtension((response.url ?? url).pathExtension) try data.write(to: tempURL) + return try UNNotificationAttachment( identifier: "", url: tempURL diff --git a/OptimoveNotificationServiceExtension/Tests/Sources/CategoryManagerTests.swift b/OptimoveNotificationServiceExtension/Tests/Sources/CategoryManagerTests.swift new file mode 100644 index 00000000..6d7e543a --- /dev/null +++ b/OptimoveNotificationServiceExtension/Tests/Sources/CategoryManagerTests.swift @@ -0,0 +1,65 @@ +// Copyright © 2023 Optimove. All rights reserved. + +@testable import OptimoveNotificationServiceExtension +import XCTest + +final class CategoryManagerTests: XCTestCase { + override func tearDown() async throws { + UserDefaults.standard.removeObject(forKey: CategoryManager.Constants.DYNAMIC_CATEGORY) + } + + func test_category_id() { + let id = CategoryManager.getCategoryId(messageId: 123) + XCTAssertEqual(id, "__kumulos_category_123__") + } + + func test_read_dynamic_categories() async throws { + let categories = CategoryManager.readCategoryIds() + XCTAssertEqual(categories.count, 0) + } + + func test_write_dynamic_categories() async throws { + let categories = CategoryManager.readCategoryIds() + XCTAssertEqual(categories.count, 0) + + CategoryManager.writeCategoryIds(["category1", "category2"]) + + let newCategories = CategoryManager.readCategoryIds() + XCTAssertEqual(newCategories.count, 2) + } + + func test_filter_pruned_categories() async throws { + let categories: Set = [ + UNNotificationCategory(identifier: "category1", actions: [], intentIdentifiers: [], options: []), + UNNotificationCategory(identifier: "category2", actions: [], intentIdentifiers: [], options: []), + UNNotificationCategory(identifier: "category3", actions: [], intentIdentifiers: [], options: []), + ] + let categoryIds = Set(["category1", "category2", "category3"]) + + let (prunedCategories, prunedCategoryIds) = CategoryManager.maybePruneCategories( + categories: categories, + categoryIds: categoryIds + ) + + XCTAssertEqual(prunedCategories.count, 3) + XCTAssertEqual(prunedCategoryIds.count, 3) + } + + func test_filter_pruned_categories_with_limit() async throws { + let categories: Set = [ + UNNotificationCategory(identifier: "category1", actions: [], intentIdentifiers: [], options: []), + UNNotificationCategory(identifier: "category2", actions: [], intentIdentifiers: [], options: []), + UNNotificationCategory(identifier: "category3", actions: [], intentIdentifiers: [], options: []), + ] + let categoryIds = Set(["category1", "category2", "category3"]) + + let (prunedCategories, prunedCategoryIds) = CategoryManager.maybePruneCategories( + categories: categories, + categoryIds: categoryIds, + limit: 2 + ) + + XCTAssertEqual(prunedCategories.count, 2) + XCTAssertEqual(prunedCategoryIds.count, 2) + } +} From 20a8284ed59acf7e97f7c85817b181d8da8e7af2 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 14:16:45 +0200 Subject: [PATCH 06/30] chore: change notification fixtures --- .../Tests/Resources/notification-background.json | 3 +-- .../Tests/Resources/notification-badge.json | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json index 451dc22a..4c85cdf2 100644 --- a/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json @@ -4,8 +4,7 @@ "title": "title", "body": "body" }, - "content-available": 1, - "mutable-content": 1 + "content-available": 1 }, "custom": { "a": { diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json b/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json index c60dbe8d..f59e75df 100644 --- a/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json +++ b/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json @@ -4,7 +4,8 @@ "title": "title", "body": "body" }, - "badge": 42 + "badge": 42, + "mutable-content": 1 }, "custom": { "badge_inc": 42, From a062657fbabdcf8a998499e4cea0968f81d529a3 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 18:46:11 +0200 Subject: [PATCH 07/30] refactor: add optimobile core --- .../Sources}/AppGroupsHelper.swift | 6 +++--- OptimobileCore/Sources/Credentials.swift | 13 ++++++++++++ .../Sources}/Extensions/Notifications.swift | 2 +- .../Sources}/KeyValPersistenceHelper.swift | 10 +++++----- .../Sources}/MediaHelper.swift | 4 ++-- .../Sources}/OptimobileEvent.swift | 2 +- .../Sources}/OptimobileHelper.swift | 12 +++++------ .../Sources}/OptimobileUserDefaultsKey.swift | 2 +- .../Sources/PendingNotification.swift | 15 ++++++++++++++ .../Sources}/PendingNotificationHelper.swift | 12 +++++------ OptimobileCore/Tests/MediaHelperTests.swift | 20 +++++++++++++++++++ OptimobileShared/Credentials.swift | 8 -------- OptimobileShared/PendingNotification.swift | 15 -------------- .../Sources/CategoryManager.swift | 1 + .../Sources/OptimobileShared | 1 - .../Sources/OptimoveNotificationService.swift | 1 + ...oveNotificationServiceExtensionTests.swift | 14 ------------- .../Classes/Optimobile/AnalyticsHelper.swift | 1 + .../Optimobile/InApp/InAppManager.swift | 1 + .../Optimobile/InApp/InAppPresenter.swift | 1 + .../Network/AuthorizationMediator.swift | 1 + .../Optimobile/Network/UrlBuilder.swift | 1 + .../Optimobile/Optimobile+Analytics.swift | 1 + .../Optimobile/Optimobile+DeepLinking.swift | 1 + .../Optimobile/Optimobile+Location.swift | 1 + .../Classes/Optimobile/Optimobile+Push.swift | 1 + .../Classes/Optimobile/Optimobile+Stats.swift | 1 + .../Classes/Optimobile/Optimobile.swift | 1 + .../Classes/Optimobile/OptimoveInApp.swift | 1 + .../Classes/Optimobile/SessionHelper.swift | 1 + OptimoveSDK/Sources/Classes/OptimobileShared | 1 - .../Sources/Classes/OptimoveConfig.swift | 1 + .../Sources/OptimoveConfigBuilderTests.swift | 1 + Package.swift | 15 ++++++++++++++ 34 files changed, 105 insertions(+), 64 deletions(-) rename {OptimobileShared => OptimobileCore/Sources}/AppGroupsHelper.swift (86%) create mode 100644 OptimobileCore/Sources/Credentials.swift rename {OptimobileShared => OptimobileCore/Sources}/Extensions/Notifications.swift (82%) rename {OptimobileShared => OptimobileCore/Sources}/KeyValPersistenceHelper.swift (86%) rename {OptimobileShared => OptimobileCore/Sources}/MediaHelper.swift (87%) rename {OptimobileShared => OptimobileCore/Sources}/OptimobileEvent.swift (95%) rename {OptimobileShared => OptimobileCore/Sources}/OptimobileHelper.swift (85%) rename {OptimobileShared => OptimobileCore/Sources}/OptimobileUserDefaultsKey.swift (95%) create mode 100644 OptimobileCore/Sources/PendingNotification.swift rename {OptimobileShared => OptimobileCore/Sources}/PendingNotificationHelper.swift (81%) create mode 100644 OptimobileCore/Tests/MediaHelperTests.swift delete mode 100644 OptimobileShared/Credentials.swift delete mode 100644 OptimobileShared/PendingNotification.swift delete mode 120000 OptimoveNotificationServiceExtension/Sources/OptimobileShared delete mode 120000 OptimoveSDK/Sources/Classes/OptimobileShared diff --git a/OptimobileShared/AppGroupsHelper.swift b/OptimobileCore/Sources/AppGroupsHelper.swift similarity index 86% rename from OptimobileShared/AppGroupsHelper.swift rename to OptimobileCore/Sources/AppGroupsHelper.swift index 521bbd66..24dbc808 100644 --- a/OptimobileShared/AppGroupsHelper.swift +++ b/OptimobileCore/Sources/AppGroupsHelper.swift @@ -6,14 +6,14 @@ public enum AppGroupConfig { public static var suffix: String = ".optimove" } -enum AppGroupsHelper { - static func isKumulosAppGroupDefined() -> Bool { +public enum AppGroupsHelper { + public static func isKumulosAppGroupDefined() -> Bool { let containerUrl = getSharedContainerPath() return containerUrl != nil } - static func getSharedContainerPath() -> URL? { + public static func getSharedContainerPath() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: getKumulosGroupName()) } diff --git a/OptimobileCore/Sources/Credentials.swift b/OptimobileCore/Sources/Credentials.swift new file mode 100644 index 00000000..6b6774c2 --- /dev/null +++ b/OptimobileCore/Sources/Credentials.swift @@ -0,0 +1,13 @@ +// Copyright © 2023 Optimove. All rights reserved. + +import Foundation + +public struct OptimobileCredentials: Codable { + public let apiKey: String + public let secretKey: String + + public init(apiKey: String, secretKey: String) { + self.apiKey = apiKey + self.secretKey = secretKey + } +} diff --git a/OptimobileShared/Extensions/Notifications.swift b/OptimobileCore/Sources/Extensions/Notifications.swift similarity index 82% rename from OptimobileShared/Extensions/Notifications.swift rename to OptimobileCore/Sources/Extensions/Notifications.swift index dfd89739..c0699e8f 100644 --- a/OptimobileShared/Extensions/Notifications.swift +++ b/OptimobileCore/Sources/Extensions/Notifications.swift @@ -2,6 +2,6 @@ import Foundation -extension Notification.Name { +public extension Notification.Name { static let optimobileInializationFinished = Notification.Name("optimobileInializationFinished") } diff --git a/OptimobileShared/KeyValPersistenceHelper.swift b/OptimobileCore/Sources/KeyValPersistenceHelper.swift similarity index 86% rename from OptimobileShared/KeyValPersistenceHelper.swift rename to OptimobileCore/Sources/KeyValPersistenceHelper.swift index fe1365d2..e3091138 100644 --- a/OptimobileShared/KeyValPersistenceHelper.swift +++ b/OptimobileCore/Sources/KeyValPersistenceHelper.swift @@ -8,8 +8,8 @@ protocol KeyValPersistent { static func removeObject(forKey: String) } -enum KeyValPersistenceHelper { - static func maybeMigrateUserDefaultsToAppGroups() { +public enum KeyValPersistenceHelper { + public static func maybeMigrateUserDefaultsToAppGroups() { let standardDefaults = UserDefaults.standard let haveMigratedKey: String = OptimobileUserDefaultsKey.MIGRATED_TO_GROUPS.rawValue if !AppGroupsHelper.isKumulosAppGroupDefined() { @@ -45,15 +45,15 @@ enum KeyValPersistenceHelper { } extension KeyValPersistenceHelper: KeyValPersistent { - static func set(_ value: Any?, forKey: String) { + public static func set(_ value: Any?, forKey: String) { getUserDefaults().set(value, forKey: forKey) } - static func object(forKey: String) -> Any? { + public static func object(forKey: String) -> Any? { return getUserDefaults().object(forKey: forKey) } - static func removeObject(forKey: String) { + public static func removeObject(forKey: String) { getUserDefaults().removeObject(forKey: forKey) } } diff --git a/OptimobileShared/MediaHelper.swift b/OptimobileCore/Sources/MediaHelper.swift similarity index 87% rename from OptimobileShared/MediaHelper.swift rename to OptimobileCore/Sources/MediaHelper.swift index bbffa4d2..f9a1e575 100644 --- a/OptimobileShared/MediaHelper.swift +++ b/OptimobileCore/Sources/MediaHelper.swift @@ -2,13 +2,13 @@ import Foundation -enum MediaHelper { +public enum MediaHelper { enum Error: LocalizedError { case noMediaUrlFound case invalidPictureUrl(String) } - static func getCompletePictureUrl(pictureUrlString: String, width: UInt) throws -> URL { + public static func getCompletePictureUrl(pictureUrlString: String, width: UInt) throws -> URL { if pictureUrlString.hasPrefix("https://") || pictureUrlString.hasPrefix("http://") { guard let url = URL(string: pictureUrlString) else { throw Error.invalidPictureUrl(pictureUrlString) diff --git a/OptimobileShared/OptimobileEvent.swift b/OptimobileCore/Sources/OptimobileEvent.swift similarity index 95% rename from OptimobileShared/OptimobileEvent.swift rename to OptimobileCore/Sources/OptimobileEvent.swift index 076455cf..77d3555e 100644 --- a/OptimobileShared/OptimobileEvent.swift +++ b/OptimobileCore/Sources/OptimobileEvent.swift @@ -1,6 +1,6 @@ // Copyright © 2023 Optimove. All rights reserved. -enum OptimobileEvent: String, Codable { +public enum OptimobileEvent: String, Codable { case DEEP_LINK_MATCHED = "k.deepLink.matched" case DEVICE_UNSUBSCRIBED = "k.push.deviceUnsubscribed" case ENGAGE_BEACON_ENTERED_PROXIMITY = "k.engage.beaconEnteredProximity" diff --git a/OptimobileShared/OptimobileHelper.swift b/OptimobileCore/Sources/OptimobileHelper.swift similarity index 85% rename from OptimobileShared/OptimobileHelper.swift rename to OptimobileCore/Sources/OptimobileHelper.swift index 11594019..7932235b 100644 --- a/OptimobileShared/OptimobileHelper.swift +++ b/OptimobileCore/Sources/OptimobileHelper.swift @@ -2,13 +2,13 @@ import Foundation -let KS_MESSAGE_TYPE_PUSH = 1 +public let KS_MESSAGE_TYPE_PUSH = 1 -enum OptimobileHelper { +public enum OptimobileHelper { private static let installIdLock = DispatchSemaphore(value: 1) - static let userIdLock = DispatchSemaphore(value: 1) + public static let userIdLock = DispatchSemaphore(value: 1) - static var installId: String { + public static var installId: String { installIdLock.wait() defer { installIdLock.signal() @@ -29,7 +29,7 @@ enum OptimobileHelper { If no user is associated, it returns the Kumulos installation ID */ - static var currentUserIdentifier: String { + public static var currentUserIdentifier: String { userIdLock.wait() defer { userIdLock.signal() } if let userId = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.USER_ID.rawValue) as! String? { @@ -39,7 +39,7 @@ enum OptimobileHelper { return OptimobileHelper.installId } - static func getBadgeFromUserInfo(userInfo: [AnyHashable: Any]) -> NSNumber? { + public static func getBadgeFromUserInfo(userInfo: [AnyHashable: Any]) -> NSNumber? { let custom = userInfo["custom"] as? [AnyHashable: Any] let aps = userInfo["aps"] as? [AnyHashable: Any] diff --git a/OptimobileShared/OptimobileUserDefaultsKey.swift b/OptimobileCore/Sources/OptimobileUserDefaultsKey.swift similarity index 95% rename from OptimobileShared/OptimobileUserDefaultsKey.swift rename to OptimobileCore/Sources/OptimobileUserDefaultsKey.swift index f26e7889..4c290b0a 100644 --- a/OptimobileShared/OptimobileUserDefaultsKey.swift +++ b/OptimobileCore/Sources/OptimobileUserDefaultsKey.swift @@ -2,7 +2,7 @@ import Foundation -enum OptimobileUserDefaultsKey: String { +public enum OptimobileUserDefaultsKey: String { case REGION = "KumulosEventsRegion" case MEDIA_BASE_URL = "KumulosMediaBaseUrl" case INSTALL_UUID = "KumulosUUID" diff --git a/OptimobileCore/Sources/PendingNotification.swift b/OptimobileCore/Sources/PendingNotification.swift new file mode 100644 index 00000000..4afa9cfe --- /dev/null +++ b/OptimobileCore/Sources/PendingNotification.swift @@ -0,0 +1,15 @@ +// Copyright © 2022 Optimove. All rights reserved. + +import Foundation + +public struct PendingNotification: Codable { + public let id: Int + public let deliveredAt: Date + public let identifier: String + + public init(id: Int, deliveredAt: Date = .init(), identifier: String) { + self.id = id + self.deliveredAt = deliveredAt + self.identifier = identifier + } +} diff --git a/OptimobileShared/PendingNotificationHelper.swift b/OptimobileCore/Sources/PendingNotificationHelper.swift similarity index 81% rename from OptimobileShared/PendingNotificationHelper.swift rename to OptimobileCore/Sources/PendingNotificationHelper.swift index dce70fe8..f55356bf 100644 --- a/OptimobileShared/PendingNotificationHelper.swift +++ b/OptimobileCore/Sources/PendingNotificationHelper.swift @@ -2,8 +2,8 @@ import Foundation -enum PendingNotificationHelper { - static func remove(id: Int) { +public enum PendingNotificationHelper { + public static func remove(id: Int) { var pendingNotifications = readAll() if let i = pendingNotifications.firstIndex(where: { $0.id == id }) { @@ -13,7 +13,7 @@ enum PendingNotificationHelper { } } - static func remove(identifier: String) { + public static func remove(identifier: String) { var pendingNotifications = readAll() if let i = pendingNotifications.firstIndex(where: { $0.identifier == identifier }) { @@ -23,7 +23,7 @@ enum PendingNotificationHelper { } } - static func readAll() -> [PendingNotification] { + public static func readAll() -> [PendingNotification] { var pendingNotifications = [PendingNotification]() if let data = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.PENDING_NOTIFICATIONS.rawValue), let decoded = try? JSONDecoder().decode([PendingNotification].self, from: data as! Data) @@ -34,7 +34,7 @@ enum PendingNotificationHelper { return pendingNotifications } - static func add(notification: PendingNotification) { + public static func add(notification: PendingNotification) { var pendingNotifications = readAll() if let _ = pendingNotifications.firstIndex(where: { $0.id == notification.id }) { @@ -46,7 +46,7 @@ enum PendingNotificationHelper { save(pendingNotifications: pendingNotifications) } - fileprivate static func save(pendingNotifications: [PendingNotification]) { + static func save(pendingNotifications: [PendingNotification]) { if let data = try? JSONEncoder().encode(pendingNotifications) { KeyValPersistenceHelper.set(data, forKey: OptimobileUserDefaultsKey.PENDING_NOTIFICATIONS.rawValue) } diff --git a/OptimobileCore/Tests/MediaHelperTests.swift b/OptimobileCore/Tests/MediaHelperTests.swift new file mode 100644 index 00000000..a4dcfbe8 --- /dev/null +++ b/OptimobileCore/Tests/MediaHelperTests.swift @@ -0,0 +1,20 @@ +// Copyright © 2023 Optimove. All rights reserved. + +@testable import OptimobileCore +import XCTest + +final class MediaHelperTests: XCTestCase { + func test_getCompletePictureUrl() throws { + let pictureUrlString = "https://www.optimove.com/wp-content/uploads/2018/12/optimove-logo.png" + let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) + XCTAssertEqual(url.absoluteString, pictureUrlString) + } + + func test_getCompletePictureUrl_withMediaUrl() throws { + let pictureUrlString = "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg" + let mediaUrl = "https://www.optimove.com" + KeyValPersistenceHelper.set(mediaUrl, forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) + let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) + XCTAssertEqual(url.absoluteString, "\(mediaUrl)/100x/\(pictureUrlString)") + } +} diff --git a/OptimobileShared/Credentials.swift b/OptimobileShared/Credentials.swift deleted file mode 100644 index 4e71a33c..00000000 --- a/OptimobileShared/Credentials.swift +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © 2023 Optimove. All rights reserved. - -import Foundation - -struct OptimobileCredentials: Codable { - let apiKey: String - let secretKey: String -} diff --git a/OptimobileShared/PendingNotification.swift b/OptimobileShared/PendingNotification.swift deleted file mode 100644 index ffe747e1..00000000 --- a/OptimobileShared/PendingNotification.swift +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright © 2022 Optimove. All rights reserved. - -import Foundation - -struct PendingNotification: Codable { - let id: Int - let deliveredAt: Date - let identifier: String - - init(id: Int, deliveredAt: Date = .init(), identifier: String) { - self.id = id - self.deliveredAt = deliveredAt - self.identifier = identifier - } -} diff --git a/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift b/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift index 19db26c6..4c1354ee 100644 --- a/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift +++ b/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift @@ -1,5 +1,6 @@ // Copyright © 2022 Optimove. All rights reserved. +import OptimobileCore import UserNotifications enum CategoryManager { diff --git a/OptimoveNotificationServiceExtension/Sources/OptimobileShared b/OptimoveNotificationServiceExtension/Sources/OptimobileShared deleted file mode 120000 index 75755936..00000000 --- a/OptimoveNotificationServiceExtension/Sources/OptimobileShared +++ /dev/null @@ -1 +0,0 @@ -../../OptimobileShared \ No newline at end of file diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 4235fcbd..7baac8c3 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore import UIKit import UserNotifications diff --git a/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift b/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift index b9ee619d..fb3d7d02 100644 --- a/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift +++ b/OptimoveNotificationServiceExtension/Tests/Sources/OptimoveNotificationServiceExtensionTests.swift @@ -35,18 +35,4 @@ final class OptimoveNotificationServiceExtensionTests: XCTestCase { XCTAssertNotNil(error) } } - - func test_getCompletePictureUrl() throws { - let pictureUrlString = "https://www.optimove.com/wp-content/uploads/2018/12/optimove-logo.png" - let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) - XCTAssertEqual(url.absoluteString, pictureUrlString) - } - - func test_getCompletePictureUrl_withMediaUrl() throws { - let pictureUrlString = "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg" - let mediaUrl = "https://www.optimove.com" - KeyValPersistenceHelper.set(mediaUrl, forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) - let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) - XCTAssertEqual(url.absoluteString, "\(mediaUrl)/100x/\(pictureUrlString)") - } } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift index 4817ca19..52726a6d 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift @@ -2,6 +2,7 @@ import CoreData import Foundation +import OptimobileCore class KSEventModel: NSManagedObject { @NSManaged var uuid: String diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift index 02f12b31..d2f54f56 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift @@ -2,6 +2,7 @@ import CoreData import Foundation +import OptimobileCore import UIKit public enum InAppMessagePresentationResult: String { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift index ff18a6cf..3cd6318b 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift @@ -1,5 +1,6 @@ // Copyright © 2022 Optimove. All rights reserved. +import OptimobileCore import StoreKit import UIKit import UserNotifications diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift b/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift index c3fbbae2..8a795fa9 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift @@ -1,6 +1,7 @@ // Copyright © 2023 Optimove. All rights reserved. import Foundation +import OptimobileCore enum AuthorizationStrategy { case basic diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift b/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift index 503577a3..48a3d16a 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore public class UrlBuilder { enum Error: LocalizedError { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift index 9a78b74d..66ed0ead 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore extension Optimobile { static func trackEvent(eventType: OptimobileEvent, properties: [String: Any]?, immediateFlush: Bool = false) { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift index f47b984b..3c9d97f7 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift @@ -2,6 +2,7 @@ import Foundation import UIKit +import OptimobileCore public struct DeepLinkContent { public let title: String? diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift index 5f26cf3d..ec954731 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift @@ -1,5 +1,6 @@ import CoreLocation import Foundation +import OptimobileCore extension Optimobile { static func sendLocationUpdate(location: CLLocation) { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index d5279f64..3cf3d3df 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -2,6 +2,7 @@ import Foundation import ObjectiveC.runtime +import OptimobileCore import UIKit import UserNotifications diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift index 19b0f52f..bd8deee3 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore import OptimoveCore import UserNotifications diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift index 5f0b44c4..c5ff7c08 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore import UserNotifications public typealias InAppDeepLinkHandlerBlock = (InAppButtonPress) -> Void diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index 7c5cc9d3..e4f5e331 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -2,6 +2,7 @@ import CoreData import Foundation +import OptimobileCore public class InAppInboxItem { public internal(set) var id: Int64 diff --git a/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift b/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift index d22fb931..983a48bd 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore import UIKit class SessionIdleTimer { diff --git a/OptimoveSDK/Sources/Classes/OptimobileShared b/OptimoveSDK/Sources/Classes/OptimobileShared deleted file mode 120000 index f70d4ed1..00000000 --- a/OptimoveSDK/Sources/Classes/OptimobileShared +++ /dev/null @@ -1 +0,0 @@ -../../../OptimobileShared \ No newline at end of file diff --git a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift index 80d599c3..031ce28a 100644 --- a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift +++ b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore /// A set of options for configuring the SDK. /// - Note: The SDK can be configured to support multiple features. diff --git a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift index 80e418bf..6cf358ee 100644 --- a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift +++ b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift @@ -1,5 +1,6 @@ // Copyright © 2023 Optimove. All rights reserved. +import OptimobileCore @testable import OptimoveSDK import XCTest diff --git a/Package.swift b/Package.swift index 38d46b81..19419730 100644 --- a/Package.swift +++ b/Package.swift @@ -29,6 +29,7 @@ let package = Package( .target( name: "OptimoveSDK", dependencies: [ + "OptimobileCore", "OptimoveCore", ], path: "OptimoveSDK/Sources" @@ -37,8 +38,15 @@ let package = Package( name: "OptimoveCore", path: "OptimoveCore/Sources" ), + .target( + name: "OptimobileCore", + path: "OptimobileCore/Sources" + ), .target( name: "OptimoveNotificationServiceExtension", + dependencies: [ + "OptimobileCore", + ], path: "OptimoveNotificationServiceExtension/Sources" ), .target( @@ -75,6 +83,13 @@ let package = Package( .process("Resources"), ] ), + .testTarget( + name: "OptimobileCoreTests", + dependencies: [ + "OptimobileCore", + ], + path: "OptimobileCore/Tests" + ), .testTarget( name: "OptimoveNotificationServiceExtensionTests", dependencies: [ From 64ead6fda80801bea6cee1929ae7ff3cbc332e3e Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Thu, 7 Dec 2023 21:24:56 +0200 Subject: [PATCH 08/30] chore: move push notification to mobile core --- OptimobileCore/Sources/PushNotification.swift | 92 +++++++ .../Tests}/PushNotificationTests.swift | 18 +- .../Resources/notification-background.json | 0 .../Tests/Resources/notification-badge.json | 0 .../Tests/Resources/notification-buttons.json | 0 .../Resources/notification-deeplink.json | 0 .../Tests/Resources/notification-image.json | 0 .../Tests/Resources/notification-message.json | 0 .../Tests/Resources/notification-url.json | 19 ++ .../Sources/PushNotification.swift | 62 ----- .../Optimobile/InApp/InAppManager.swift | 10 +- .../Classes/Optimobile/Optimobile+Push.swift | 258 +++++++----------- ...timoveUserNotificationCenterDelegate.swift | 29 +- Package.swift | 11 +- 14 files changed, 261 insertions(+), 238 deletions(-) create mode 100644 OptimobileCore/Sources/PushNotification.swift rename {OptimoveNotificationServiceExtension/Tests/Sources => OptimobileCore/Tests}/PushNotificationTests.swift (79%) rename {OptimoveNotificationServiceExtension => OptimobileCore}/Tests/Resources/notification-background.json (100%) rename {OptimoveNotificationServiceExtension => OptimobileCore}/Tests/Resources/notification-badge.json (100%) rename {OptimoveNotificationServiceExtension => OptimobileCore}/Tests/Resources/notification-buttons.json (100%) rename {OptimoveNotificationServiceExtension => OptimobileCore}/Tests/Resources/notification-deeplink.json (100%) rename {OptimoveNotificationServiceExtension => OptimobileCore}/Tests/Resources/notification-image.json (100%) rename {OptimoveNotificationServiceExtension => OptimobileCore}/Tests/Resources/notification-message.json (100%) create mode 100644 OptimobileCore/Tests/Resources/notification-url.json delete mode 100644 OptimoveNotificationServiceExtension/Sources/PushNotification.swift diff --git a/OptimobileCore/Sources/PushNotification.swift b/OptimobileCore/Sources/PushNotification.swift new file mode 100644 index 00000000..37c4bcaa --- /dev/null +++ b/OptimobileCore/Sources/PushNotification.swift @@ -0,0 +1,92 @@ +// Copyright © 2023 Optimove. All rights reserved. + +import Foundation + +/// Represents a push notification received from the server. +public struct PushNotification: Decodable { + public struct Data: Decodable { + public let id: Int + + private enum CodingKeys: String, CodingKey { + case id + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) + self.id = try data.decode(Int.self, forKey: CodingKeys.id) + } + } + + public struct Button: Decodable { + public struct Icon: Decodable { + public enum IconType: String, Decodable { + case custom + case system + } + + public let id: String + public let type: IconType + } + + public let id: String + public let icon: Icon? + public let text: String + } + + public let id: Int + public let deeplink: PushNotification.Data? + public let badge: Int? + public let buttons: [Button]? + public let isBackground: Bool + public let picturePath: String? + public let url: URL? + + private enum CodingKeys: String, CodingKey { + case a + case aps + case attachments + case badge = "badge_inc" + case buttons = "k.buttons" + case custom + case data + case deeplink = "k.deepLink" + case id + case isBackground = "content-available" + case message = "k.message" + case pictureUrl + case u + } + + public init(userInfo: [AnyHashable: Any]) throws { + let data = try JSONSerialization.data(withJSONObject: userInfo) + let decoder = JSONDecoder() + self = try decoder.decode(PushNotification.self, from: data) + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + let custom = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .custom) + self.badge = try custom.decodeIfPresent(Int.self, forKey: .badge) + + let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) + self.buttons = try a.decodeIfPresent([Button].self, forKey: .buttons) + + let message = try a.nestedContainer(keyedBy: CodingKeys.self, forKey: .message) + let messageData = try message.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) + self.id = try messageData.decode(Int.self, forKey: .id) + + let aps = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .aps) + let isBackground = try aps.decodeIfPresent(Int.self, forKey: .isBackground) + self.isBackground = isBackground == 1 ? true : false + + let attachments = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: .attachments) + self.picturePath = try attachments?.decodeIfPresent(String.self, forKey: .pictureUrl) + + self.url = try custom.decodeIfPresent(URL.self, forKey: .u) + + self.deeplink = try a.decodeIfPresent(PushNotification.Data.self, forKey: .deeplink) + } +} diff --git a/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift b/OptimobileCore/Tests/PushNotificationTests.swift similarity index 79% rename from OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift rename to OptimobileCore/Tests/PushNotificationTests.swift index 829a83bc..db862832 100644 --- a/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift +++ b/OptimobileCore/Tests/PushNotificationTests.swift @@ -1,6 +1,6 @@ // Copyright © 2023 Optimove. All rights reserved. -@testable import OptimoveNotificationServiceExtension +import OptimobileCore import OptimoveTest import XCTest @@ -56,4 +56,20 @@ final class PushNotificationTests: XCTestCase, FileAccessible { XCTAssertEqual(notification.isBackground, true) } + + func test_decode_url() throws { + fileName = "notification-url.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + + XCTAssertEqual(notification.url?.absoluteString, "https://www.optimove.com") + } + + func test_decode_deeplink() throws { + fileName = "notification-deeplink.json" + let decoder = JSONDecoder() + let notification = try decoder.decode(PushNotification.self, from: data) + + XCTAssertEqual(notification.deeplink?.id, 1) + } } diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json b/OptimobileCore/Tests/Resources/notification-background.json similarity index 100% rename from OptimoveNotificationServiceExtension/Tests/Resources/notification-background.json rename to OptimobileCore/Tests/Resources/notification-background.json diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json b/OptimobileCore/Tests/Resources/notification-badge.json similarity index 100% rename from OptimoveNotificationServiceExtension/Tests/Resources/notification-badge.json rename to OptimobileCore/Tests/Resources/notification-badge.json diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json b/OptimobileCore/Tests/Resources/notification-buttons.json similarity index 100% rename from OptimoveNotificationServiceExtension/Tests/Resources/notification-buttons.json rename to OptimobileCore/Tests/Resources/notification-buttons.json diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-deeplink.json b/OptimobileCore/Tests/Resources/notification-deeplink.json similarity index 100% rename from OptimoveNotificationServiceExtension/Tests/Resources/notification-deeplink.json rename to OptimobileCore/Tests/Resources/notification-deeplink.json diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json b/OptimobileCore/Tests/Resources/notification-image.json similarity index 100% rename from OptimoveNotificationServiceExtension/Tests/Resources/notification-image.json rename to OptimobileCore/Tests/Resources/notification-image.json diff --git a/OptimoveNotificationServiceExtension/Tests/Resources/notification-message.json b/OptimobileCore/Tests/Resources/notification-message.json similarity index 100% rename from OptimoveNotificationServiceExtension/Tests/Resources/notification-message.json rename to OptimobileCore/Tests/Resources/notification-message.json diff --git a/OptimobileCore/Tests/Resources/notification-url.json b/OptimobileCore/Tests/Resources/notification-url.json new file mode 100644 index 00000000..e57f0c19 --- /dev/null +++ b/OptimobileCore/Tests/Resources/notification-url.json @@ -0,0 +1,19 @@ +{ + "aps": { + "alert": { + "title": "title", + "body": "body" + } + }, + "custom": { + "u": "https://www.optimove.com", + "a": { + "k.message": { + "type": 1, + "data": { + "id": 1 + } + } + } + } +} diff --git a/OptimoveNotificationServiceExtension/Sources/PushNotification.swift b/OptimoveNotificationServiceExtension/Sources/PushNotification.swift deleted file mode 100644 index 19e5cfd4..00000000 --- a/OptimoveNotificationServiceExtension/Sources/PushNotification.swift +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright © 2023 Optimove. All rights reserved. - -/// Represents a push notification received from the server. -struct PushNotification: Decodable { - struct Button: Decodable { - struct Icon: Decodable { - enum IconType: String, Decodable { - case custom - case system - } - - let id: String - let type: IconType - } - - let id: String - let icon: Icon? - let text: String - } - - let id: Int - let badge: Int? - let buttons: [Button]? - let isBackground: Bool - let picturePath: String? - - private enum CodingKeys: String, CodingKey { - case a - case aps - case attachments - case badge = "badge_inc" - case buttons = "k.buttons" - case custom - case data - case deeplink = "k.deepLink" - case id - case isBackground = "content-available" - case message = "k.message" - case pictureUrl - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - let custom = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .custom) - self.badge = try custom.decodeIfPresent(Int.self, forKey: .badge) - - let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) - self.buttons = try a.decodeIfPresent([Button].self, forKey: .buttons) - - let message = try a.nestedContainer(keyedBy: CodingKeys.self, forKey: .message) - let data = try message.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) - self.id = try data.decode(Int.self, forKey: .id) - - let aps = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .aps) - let isBackground = try aps.decodeIfPresent(Int.self, forKey: .isBackground) - self.isBackground = isBackground == 1 ? true : false - - let attachments = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: .attachments) - self.picturePath = try attachments?.decodeIfPresent(String.self, forKey: .pictureUrl) - } -} diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift index d2f54f56..573331de 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift @@ -671,24 +671,20 @@ class InAppManager { } func handlePushOpen(notification: PushNotification) { - let deepLink: [AnyHashable: Any]? = notification.inAppDeepLink() - if !inAppEnabled() || deepLink == nil { + guard let deepLink = notification.deeplink, !inAppEnabled() else { return } DispatchQueue.global(qos: .default).async { - let data = deepLink!["data"] as! [AnyHashable: Any] - let inAppPartId: Int = data["id"] as! Int - objc_sync_enter(self.pendingTickleIds) defer { objc_sync_exit(self.pendingTickleIds) } - self.pendingTickleIds.add(inAppPartId) + self.pendingTickleIds.add(deepLink.id) let messagesToPresent = self.getMessagesToPresent([]) let tickleMessageFound = messagesToPresent.contains(where: { message -> Bool in - message.id == inAppPartId + message.id == deepLink.id }) if !tickleMessageFound { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index 3cf3d3df..6856ebcb 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -6,79 +6,6 @@ import OptimobileCore import UIKit import UserNotifications -public class PushNotification: NSObject { - static let DeepLinkTypeInApp: Int = 1 - - public internal(set) var id: Int - public internal(set) var aps: [AnyHashable: Any] - public internal(set) var data: [AnyHashable: Any] - public internal(set) var url: URL? - public internal(set) var actionIdentifier: String? - - init(userInfo: [AnyHashable: Any]?) { - id = 0 - self.aps = [:] - self.data = [:] - - guard let userInfo = userInfo else { - return - } - - guard let aps = userInfo["aps"] as? [AnyHashable: Any] else { - return - } - - self.aps = aps - - guard let custom = userInfo["custom"] as? [AnyHashable: Any] else { - return - } - - guard let data = custom["a"] as? [AnyHashable: Any] else { - return - } - - self.data = data - - guard let msg = data["k.message"] as? [AnyHashable: Any] else { - return - } - - let msgData = msg["data"] as! [AnyHashable: Any] - - id = msgData["id"] as! Int - - if let urlStr = custom["u"] as? String { - url = URL(string: urlStr) - } else { - url = nil - } - } - - @available(iOS 10.0, *) - convenience init(userInfo: [AnyHashable: Any]?, response: UNNotificationResponse?) { - self.init(userInfo: userInfo) - - if let notificationResponse = response { - if notificationResponse.actionIdentifier != UNNotificationDefaultActionIdentifier { - actionIdentifier = notificationResponse.actionIdentifier - } - } - } - - public func inAppDeepLink() -> [AnyHashable: Any]? { - guard let deepLink = data["k.deepLink"] as? [AnyHashable: Any] else { - return nil - } - - if deepLink["type"] as? Int != PushNotification.DeepLinkTypeInApp { - return nil - } - - return deepLink - } -} - @available(iOS 10.0, *) public typealias OptimoveUNAuthorizationCheckedHandler = (UNAuthorizationStatus, Error?) -> Void @@ -209,35 +136,44 @@ extension Optimobile { - notification: The notification which triggered the action */ static func pushTrackOpen(notification: PushNotification) { - if notification.id == 0 { - Logger.warn(""" - Ignoring push notification open. - Reason: Invalid notification id (== 0). - Payload: \(notification). - """) - } let params = ["type": KS_MESSAGE_TYPE_PUSH, "id": notification.id] Optimobile.trackEvent(eventType: OptimobileEvent.MESSAGE_OPENED, properties: params) } static func pushTrackOpen(userInfo: [AnyHashable: Any]) { - let notification = PushNotification(userInfo: userInfo) - Optimobile.pushTrackOpen(notification: notification) + do { + let notification = try PushNotification(userInfo: userInfo) + Optimobile.pushTrackOpen(notification: notification) + } catch { + Logger.error( + """ + Ignoring push notification open. + Reason: Invalid notification payload. + Payload: \(userInfo). + Error: \(error.localizedDescription). + """ + ) + } } @available(iOS 10.0, *) - func pushHandleOpen(withUserInfo: [AnyHashable: Any]?, response: UNNotificationResponse?) -> Bool { - let notification = PushNotification(userInfo: withUserInfo, response: response) - - if notification.id == 0 { + func pushHandleOpen(withUserInfo userInfo: [AnyHashable: Any]) -> Bool { + do { + let notification = try PushNotification(userInfo: userInfo) + pushHandleOpen(notification: notification) + PendingNotificationHelper.remove(id: notification.id) + return true + } catch { + Logger.error( + """ + Ignoring push notification open. + Reason: Invalid notification payload. + Payload: \(userInfo). + Error: \(error.localizedDescription). + """ + ) return false } - - pushHandleOpen(notification: notification) - - PendingNotificationHelper.remove(id: notification.id) - - return true } private func pushHandleOpen(notification: PushNotification) { @@ -269,16 +205,24 @@ extension Optimobile { // MARK: Dismissed handling @available(iOS 10.0, *) - func pushHandleDismissed(withUserInfo: [AnyHashable: Any]?, response: UNNotificationResponse?) -> Bool { - let notification = PushNotification(userInfo: withUserInfo, response: response) - - if notification.id == 0 { + func pushHandleDismissed(withUserInfo userInfo: [AnyHashable: Any]) -> Bool { + do { + let data = try JSONSerialization.data(withJSONObject: userInfo) + let notification = try JSONDecoder().decode(PushNotification.self, from: data) + pushHandleDismissed(notificationId: notification.id) + PendingNotificationHelper.remove(id: notification.id) + return true + } catch { + Logger.error( + """ + Ignoring push notification dismissed. + Reason: Invalid notification payload. + Payload: \(userInfo). + Error: \(error.localizedDescription). + """ + ) return false } - - pushHandleDismissed(notificationId: notification.id) - - return true } @available(iOS 10.0, *) @@ -303,23 +247,24 @@ extension Optimobile { if !AppGroupsHelper.isKumulosAppGroupDefined() { return } - - UNUserNotificationCenter.current().getDeliveredNotifications { (notifications: [UNNotification]) in - var actualPendingNotificationIds: [Int] = [] - for notification in notifications { - let notification = PushNotification(userInfo: notification.request.content.userInfo) - if notification.id == 0 { - continue + Task { + do { + let notifications = await UNUserNotificationCenter.current().deliveredNotifications() + var actualPendingNotificationIds: [Int] = [] + for notification in notifications { + let notification = try PushNotification(userInfo: notification.request.content.userInfo) + + actualPendingNotificationIds.append(notification.id) } - actualPendingNotificationIds.append(notification.id) - } + let recordedPendingNotifications = PendingNotificationHelper.readAll() - let recordedPendingNotifications = PendingNotificationHelper.readAll() - - let deletions = recordedPendingNotifications.filter { !actualPendingNotificationIds.contains($0.id) } - for deletion in deletions { - self.pushHandleDismissed(notificationId: deletion.id, dismissedAt: deletion.deliveredAt) + let deletions = recordedPendingNotifications.filter { !actualPendingNotificationIds.contains($0.id) } + for deletion in deletions { + pushHandleDismissed(notificationId: deletion.id, dismissedAt: deletion.deliveredAt) + } + } catch { + Logger.error("Failed to track push dismissed events: \(error.localizedDescription)") } } } @@ -400,58 +345,63 @@ class PushHelper { let didReceiveSelector = #selector(UIApplicationDelegate.application(_:didReceiveRemoteNotification:fetchCompletionHandler:)) let receiveType = NSString(string: "v@:@@@?").utf8String let didReceive: didReceiveBlock = { (obj: Any, _ application: UIApplication, userInfo: [AnyHashable: Any], completionHandler: @escaping (UIBackgroundFetchResult) -> Void) in - let notification = PushNotification(userInfo: userInfo) - let hasInApp = notification.inAppDeepLink() != nil + do { + let notification = try PushNotification(userInfo: userInfo) + let hasInApp = notification.deeplink != nil - self.setBadge(userInfo: userInfo) - self.trackPushDelivery(notification: notification) + self.setBadge(userInfo: userInfo) + self.trackPushDelivery(notification: notification) - if existingDidReceive == nil, !hasInApp { - // Nothing to do - completionHandler(.noData) - return - } else if existingDidReceive != nil, !hasInApp { - // Only existing delegate work to do - unsafeBitCast(existingDidReceive, to: kumulos_applicationDidReceiveRemoteNotificationFetchCompletionHandler.self)(obj, didReceiveSelector, application, userInfo, completionHandler) - return - } + if existingDidReceive == nil, !hasInApp { + // Nothing to do + completionHandler(.noData) + return + } else if existingDidReceive != nil, !hasInApp { + // Only existing delegate work to do + unsafeBitCast(existingDidReceive, to: kumulos_applicationDidReceiveRemoteNotificationFetchCompletionHandler.self)(obj, didReceiveSelector, application, userInfo, completionHandler) + return + } - var fetchResult: UIBackgroundFetchResult = .noData - let group = DispatchGroup() + var fetchResult: UIBackgroundFetchResult = .noData + let group = DispatchGroup() - if existingDidReceive != nil { - group.enter() - DispatchQueue.main.async { - unsafeBitCast(existingDidReceive, to: kumulos_applicationDidReceiveRemoteNotificationFetchCompletionHandler.self)(obj, didReceiveSelector, application, userInfo, { (result: UIBackgroundFetchResult) in + if existingDidReceive != nil { + group.enter() + DispatchQueue.main.async { + unsafeBitCast(existingDidReceive, to: kumulos_applicationDidReceiveRemoteNotificationFetchCompletionHandler.self)(obj, didReceiveSelector, application, userInfo, { (result: UIBackgroundFetchResult) in + DispatchQueue.main.async { + if fetchResult == .noData { + fetchResult = result + } + + group.leave() + } + }) + } + } + + if hasInApp { + group.enter() + Optimobile.sharedInstance.inAppManager.sync { (result: Int) in DispatchQueue.main.async { - if fetchResult == .noData { - fetchResult = result + if result < 0 { + fetchResult = .failed + } else if result > 0 { + fetchResult = .newData } + // No data case is default, allow override from other handler group.leave() } - }) - } - } - - if hasInApp { - group.enter() - Optimobile.sharedInstance.inAppManager.sync { (result: Int) in - DispatchQueue.main.async { - if result < 0 { - fetchResult = .failed - } else if result > 0 { - fetchResult = .newData - } - // No data case is default, allow override from other handler - - group.leave() } } - } - group.notify(queue: .main) { - completionHandler(fetchResult) + group.notify(queue: .main) { + completionHandler(fetchResult) + } + } catch { + Logger.error("Failed to parse push notification: \(error.localizedDescription)") + completionHandler(.failed) } } let kumulosDidReceive = imp_implementationWithBlock(unsafeBitCast(didReceive, to: AnyObject.self)) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift index fb87420f..cf75a93d 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift @@ -1,6 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimobileCore import UserNotifications @available(iOS 10.0, *) @@ -12,19 +13,23 @@ class OptimoveUserNotificationCenterDelegate: NSObject, UNUserNotificationCenter } func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { - let push = PushNotification(userInfo: notification.request.content.userInfo, response: nil) + do { + let push = try PushNotification(userInfo: notification.request.content.userInfo) - if push.id == 0 { - chainCenter(center, willPresent: notification, with: completionHandler) - return - } + if push.id == 0 { + chainCenter(center, willPresent: notification, with: completionHandler) + return + } - if Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock == nil { - completionHandler(.alert) - return - } + if Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock == nil { + completionHandler(.alert) + return + } - Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock?(push, completionHandler) + Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock?(push, completionHandler) + } catch { + chainCenter(center, willPresent: notification, with: completionHandler) + } } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { @@ -36,7 +41,7 @@ class OptimoveUserNotificationCenterDelegate: NSObject, UNUserNotificationCenter } if response.actionIdentifier == UNNotificationDismissActionIdentifier { - let handled = Optimobile.sharedInstance.pushHandleDismissed(withUserInfo: userInfo, response: response) + let handled = Optimobile.sharedInstance.pushHandleDismissed(withUserInfo: userInfo) if !handled { chainCenter(center, didReceive: response, with: completionHandler) return @@ -46,7 +51,7 @@ class OptimoveUserNotificationCenterDelegate: NSObject, UNUserNotificationCenter return } - let handled = Optimobile.sharedInstance.pushHandleOpen(withUserInfo: userInfo, response: response) + let handled = Optimobile.sharedInstance.pushHandleOpen(withUserInfo: userInfo) if !handled { chainCenter(center, didReceive: response, with: completionHandler) diff --git a/Package.swift b/Package.swift index 19419730..fa4253a0 100644 --- a/Package.swift +++ b/Package.swift @@ -40,7 +40,10 @@ let package = Package( ), .target( name: "OptimobileCore", - path: "OptimobileCore/Sources" + path: "OptimobileCore/Sources", + resources: [ + .process("Resources"), + ] ), .target( name: "OptimoveNotificationServiceExtension", @@ -87,8 +90,12 @@ let package = Package( name: "OptimobileCoreTests", dependencies: [ "OptimobileCore", + "OptimoveTest", ], - path: "OptimobileCore/Tests" + path: "OptimobileCore/Tests", + resources: [ + .process("Resources"), + ] ), .testTarget( name: "OptimoveNotificationServiceExtensionTests", From d6e697fd59b70d270a26e38a36901905c60db54d Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 11:44:19 +0200 Subject: [PATCH 09/30] chore: keep message struct in notification --- OptimobileCore/Sources/PushNotification.swift | 10 +++------- OptimobileCore/Tests/PushNotificationTests.swift | 4 ++-- .../Sources/OptimoveNotificationService.swift | 4 ++-- .../Classes/Optimobile/Optimobile+Push.swift | 16 ++++++---------- .../OptimoveUserNotificationCenterDelegate.swift | 5 ----- 5 files changed, 13 insertions(+), 26 deletions(-) diff --git a/OptimobileCore/Sources/PushNotification.swift b/OptimobileCore/Sources/PushNotification.swift index 37c4bcaa..d0d0e8ea 100644 --- a/OptimobileCore/Sources/PushNotification.swift +++ b/OptimobileCore/Sources/PushNotification.swift @@ -35,8 +35,8 @@ public struct PushNotification: Decodable { public let text: String } - public let id: Int public let deeplink: PushNotification.Data? + public let message: PushNotification.Data public let badge: Int? public let buttons: [Button]? public let isBackground: Bool @@ -73,10 +73,8 @@ public struct PushNotification: Decodable { let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) self.buttons = try a.decodeIfPresent([Button].self, forKey: .buttons) - - let message = try a.nestedContainer(keyedBy: CodingKeys.self, forKey: .message) - let messageData = try message.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) - self.id = try messageData.decode(Int.self, forKey: .id) + self.deeplink = try a.decodeIfPresent(PushNotification.Data.self, forKey: .deeplink) + self.message = try a.decode(PushNotification.Data.self, forKey: .message) let aps = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .aps) let isBackground = try aps.decodeIfPresent(Int.self, forKey: .isBackground) @@ -86,7 +84,5 @@ public struct PushNotification: Decodable { self.picturePath = try attachments?.decodeIfPresent(String.self, forKey: .pictureUrl) self.url = try custom.decodeIfPresent(URL.self, forKey: .u) - - self.deeplink = try a.decodeIfPresent(PushNotification.Data.self, forKey: .deeplink) } } diff --git a/OptimobileCore/Tests/PushNotificationTests.swift b/OptimobileCore/Tests/PushNotificationTests.swift index db862832..9d158895 100644 --- a/OptimobileCore/Tests/PushNotificationTests.swift +++ b/OptimobileCore/Tests/PushNotificationTests.swift @@ -7,11 +7,11 @@ import XCTest final class PushNotificationTests: XCTestCase, FileAccessible { var fileName: String = "" - func test_decode_id() throws { + func test_decode_message() throws { fileName = "notification-message.json" let decoder = JSONDecoder() let notification = try decoder.decode(PushNotification.self, from: data) - XCTAssertEqual(notification.id, 1) + XCTAssertEqual(notification.message.id, 1) } func test_decode_badge() throws { diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 7baac8c3..e3383170 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -46,7 +46,7 @@ public enum OptimoveNotificationService { maybeSetBadge(bestAttemptContent: bestAttemptContent, userInfo: userInfo) PendingNotificationHelper.add( notification: PendingNotification( - id: notification.id, + id: notification.message.id, identifier: request.identifier ) ) @@ -86,7 +86,7 @@ public enum OptimoveNotificationService { } static func buildCategory(notification: PushNotification) async -> String { - let categoryIdentifier = CategoryManager.getCategoryId(messageId: notification.id) + let categoryIdentifier = CategoryManager.getCategoryId(messageId: notification.message.id) let category = UNNotificationCategory( identifier: categoryIdentifier, actions: buildActions(notification: notification), diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index 6856ebcb..7c5b5fc4 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -136,7 +136,7 @@ extension Optimobile { - notification: The notification which triggered the action */ static func pushTrackOpen(notification: PushNotification) { - let params = ["type": KS_MESSAGE_TYPE_PUSH, "id": notification.id] + let params = ["type": KS_MESSAGE_TYPE_PUSH, "id": notification.message.id] Optimobile.trackEvent(eventType: OptimobileEvent.MESSAGE_OPENED, properties: params) } @@ -161,7 +161,7 @@ extension Optimobile { do { let notification = try PushNotification(userInfo: userInfo) pushHandleOpen(notification: notification) - PendingNotificationHelper.remove(id: notification.id) + PendingNotificationHelper.remove(id: notification.message.id) return true } catch { Logger.error( @@ -209,8 +209,8 @@ extension Optimobile { do { let data = try JSONSerialization.data(withJSONObject: userInfo) let notification = try JSONDecoder().decode(PushNotification.self, from: data) - pushHandleDismissed(notificationId: notification.id) - PendingNotificationHelper.remove(id: notification.id) + pushHandleDismissed(notificationId: notification.message.id) + PendingNotificationHelper.remove(id: notification.message.id) return true } catch { Logger.error( @@ -254,7 +254,7 @@ extension Optimobile { for notification in notifications { let notification = try PushNotification(userInfo: notification.request.content.userInfo) - actualPendingNotificationIds.append(notification.id) + actualPendingNotificationIds.append(notification.message.id) } let recordedPendingNotifications = PendingNotificationHelper.readAll() @@ -422,11 +422,7 @@ class PushHelper { } private func trackPushDelivery(notification: PushNotification) { - if notification.id == 0 { - return - } - - let props: [String: Any] = ["type": KS_MESSAGE_TYPE_PUSH, "id": notification.id] + let props: [String: Any] = ["type": KS_MESSAGE_TYPE_PUSH, "id": notification.message.id] Optimobile.trackEvent(eventType: OptimobileEvent.MESSAGE_DELIVERED, properties: props, immediateFlush: true) } } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift index cf75a93d..101f90d9 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift @@ -16,11 +16,6 @@ class OptimoveUserNotificationCenterDelegate: NSObject, UNUserNotificationCenter do { let push = try PushNotification(userInfo: notification.request.content.userInfo) - if push.id == 0 { - chainCenter(center, willPresent: notification, with: completionHandler) - return - } - if Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock == nil { completionHandler(.alert) return From b67069eb9cca50c2e3d6ff2d4557ace6e52a62cc Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 11:50:12 +0200 Subject: [PATCH 10/30] chore: wrap attachments --- OptimobileCore/Sources/PushNotification.swift | 17 ++++++++--------- .../Tests/PushNotificationTests.swift | 2 +- .../Sources/OptimoveNotificationService.swift | 2 +- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/OptimobileCore/Sources/PushNotification.swift b/OptimobileCore/Sources/PushNotification.swift index d0d0e8ea..fe124e29 100644 --- a/OptimobileCore/Sources/PushNotification.swift +++ b/OptimobileCore/Sources/PushNotification.swift @@ -19,6 +19,10 @@ public struct PushNotification: Decodable { } } + public struct Attachment: Decodable { + public let pictureUrl: String? + } + public struct Button: Decodable { public struct Icon: Decodable { public enum IconType: String, Decodable { @@ -35,12 +39,12 @@ public struct PushNotification: Decodable { public let text: String } + public let attachment: PushNotification.Attachment? + public let badge: Int? + public let buttons: [PushNotification.Button]? public let deeplink: PushNotification.Data? public let message: PushNotification.Data - public let badge: Int? - public let buttons: [Button]? public let isBackground: Bool - public let picturePath: String? public let url: URL? private enum CodingKeys: String, CodingKey { @@ -50,12 +54,9 @@ public struct PushNotification: Decodable { case badge = "badge_inc" case buttons = "k.buttons" case custom - case data case deeplink = "k.deepLink" - case id case isBackground = "content-available" case message = "k.message" - case pictureUrl case u } @@ -69,6 +70,7 @@ public struct PushNotification: Decodable { let container = try decoder.container(keyedBy: CodingKeys.self) let custom = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .custom) + self.attachment = try container.decodeIfPresent(Attachment.self, forKey: .attachments) self.badge = try custom.decodeIfPresent(Int.self, forKey: .badge) let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) @@ -80,9 +82,6 @@ public struct PushNotification: Decodable { let isBackground = try aps.decodeIfPresent(Int.self, forKey: .isBackground) self.isBackground = isBackground == 1 ? true : false - let attachments = try? container.nestedContainer(keyedBy: CodingKeys.self, forKey: .attachments) - self.picturePath = try attachments?.decodeIfPresent(String.self, forKey: .pictureUrl) - self.url = try custom.decodeIfPresent(URL.self, forKey: .u) } } diff --git a/OptimobileCore/Tests/PushNotificationTests.swift b/OptimobileCore/Tests/PushNotificationTests.swift index 9d158895..c486b034 100644 --- a/OptimobileCore/Tests/PushNotificationTests.swift +++ b/OptimobileCore/Tests/PushNotificationTests.swift @@ -46,7 +46,7 @@ final class PushNotificationTests: XCTestCase, FileAccessible { let decoder = JSONDecoder() let notification = try decoder.decode(PushNotification.self, from: data) - XCTAssertEqual(notification.picturePath, "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg") + XCTAssertEqual(notification.attachment?.pictureUrl, "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg") } func test_decode_background() throws { diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index e3383170..10a13472 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -99,7 +99,7 @@ public enum OptimoveNotificationService { } static func maybeGetAttachment(notification: PushNotification) async throws -> UNNotificationAttachment? { - guard let picturePath = notification.picturePath else { return nil } + guard let picturePath = notification.attachment?.pictureUrl else { return nil } let url = try await MediaHelper.getCompletePictureUrl( pictureUrlString: picturePath, From 7cd93c38c2d0df7cca8faf0cf53b40cdd746f6c7 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 12:08:03 +0200 Subject: [PATCH 11/30] refactor: notification struct --- OptimobileCore/Sources/PushNotification.swift | 60 ++++++++++++++----- .../Tests/PushNotificationTests.swift | 2 +- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/OptimobileCore/Sources/PushNotification.swift b/OptimobileCore/Sources/PushNotification.swift index fe124e29..39d5a366 100644 --- a/OptimobileCore/Sources/PushNotification.swift +++ b/OptimobileCore/Sources/PushNotification.swift @@ -4,18 +4,37 @@ import Foundation /// Represents a push notification received from the server. public struct PushNotification: Decodable { - public struct Data: Decodable { - public let id: Int + public struct Aps: Decodable { + public struct Alert: Decodable { + public let title: String? + public let body: String? + } + + public let alert: Alert? + public let badge: Int? + public let sound: String? + /// The background notification flag. To perform a silent background update, specify the value 1 and don’t include the alert, badge, or sound keys in your payload. If this key is present with a value of 1, the system attempts to initialize your app in the background so that it can make updates to its user interface. If the app is already running in the foreground, this key has no effect. + public let isBackground: Bool + /// The notification service app extension flag. If the value is 1, the system passes the notification to your notification service app extension before delivery. Use your extension to modify the notification’s content. + public let isExtension: Bool private enum CodingKeys: String, CodingKey { - case id - case data + case alert + case badge + case sound + case isBackground = "content-available" + case isExtension = "mutable-content" } public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) - self.id = try data.decode(Int.self, forKey: CodingKeys.id) + self.alert = try container.decodeIfPresent(Alert.self, forKey: .alert) + self.badge = try container.decodeIfPresent(Int.self, forKey: .badge) + self.sound = try container.decodeIfPresent(String.self, forKey: .sound) + let isBackground = try container.decodeIfPresent(Int.self, forKey: .isBackground) + self.isBackground = isBackground == 1 + let isExtension = try container.decodeIfPresent(Int.self, forKey: .isExtension) + self.isExtension = isExtension == 1 } } @@ -39,12 +58,28 @@ public struct PushNotification: Decodable { public let text: String } + public struct Data: Decodable { + public let id: Int + + private enum CodingKeys: String, CodingKey { + case id + case data + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let data = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data) + self.id = try data.decode(Int.self, forKey: CodingKeys.id) + } + } + + public let aps: Aps public let attachment: PushNotification.Attachment? + /// Optimove badge public let badge: Int? public let buttons: [PushNotification.Button]? public let deeplink: PushNotification.Data? public let message: PushNotification.Data - public let isBackground: Bool public let url: URL? private enum CodingKeys: String, CodingKey { @@ -55,7 +90,6 @@ public struct PushNotification: Decodable { case buttons = "k.buttons" case custom case deeplink = "k.deepLink" - case isBackground = "content-available" case message = "k.message" case u } @@ -68,20 +102,16 @@ public struct PushNotification: Decodable { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) + self.aps = try container.decode(Aps.self, forKey: .aps) + self.attachment = try container.decodeIfPresent(Attachment.self, forKey: .attachments) let custom = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .custom) - self.attachment = try container.decodeIfPresent(Attachment.self, forKey: .attachments) self.badge = try custom.decodeIfPresent(Int.self, forKey: .badge) + self.url = try custom.decodeIfPresent(URL.self, forKey: .u) let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) self.buttons = try a.decodeIfPresent([Button].self, forKey: .buttons) self.deeplink = try a.decodeIfPresent(PushNotification.Data.self, forKey: .deeplink) self.message = try a.decode(PushNotification.Data.self, forKey: .message) - - let aps = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .aps) - let isBackground = try aps.decodeIfPresent(Int.self, forKey: .isBackground) - self.isBackground = isBackground == 1 ? true : false - - self.url = try custom.decodeIfPresent(URL.self, forKey: .u) } } diff --git a/OptimobileCore/Tests/PushNotificationTests.swift b/OptimobileCore/Tests/PushNotificationTests.swift index c486b034..c8d068e5 100644 --- a/OptimobileCore/Tests/PushNotificationTests.swift +++ b/OptimobileCore/Tests/PushNotificationTests.swift @@ -54,7 +54,7 @@ final class PushNotificationTests: XCTestCase, FileAccessible { let decoder = JSONDecoder() let notification = try decoder.decode(PushNotification.self, from: data) - XCTAssertEqual(notification.isBackground, true) + XCTAssertEqual(notification.aps.isBackground, true) } func test_decode_url() throws { From 0a66d7ea56eb066f31a3960044bcc4c1a30b22b0 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 12:33:05 +0200 Subject: [PATCH 12/30] refactor: move legacy optimove core to sdk --- OptimoveCore/Sources/Assets/.gitkeep | 0 .../Sources/Classes/Coding/RuntimeCodingKey.swift | 0 .../Sources/Classes/Components/OptiTrack/OptiTrack.swift | 4 ---- .../Sources/Classes/Configuration}/Configuration.swift | 0 .../Sources/Classes/Configuration}/ConfigurationBuilder.swift | 0 .../Sources/Classes/Configuration}/Global/GlobalConfig.swift | 0 .../Sources/Classes/Configuration}/Tenant/EventConfig.swift | 0 .../Classes/Configuration}/Tenant/OptitrackMetaData.swift | 0 .../Sources/Classes/Configuration}/Tenant/Parameter.swift | 0 .../Classes/Configuration}/Tenant/RealtimeMetaData.swift | 0 .../Sources/Classes/Configuration}/Tenant/TenantConfig.swift | 0 .../Sources/Classes/Events/Event.swift | 0 OptimoveSDK/Sources/Classes/Factories/MainFactory.swift | 3 --- .../Sources/Classes/JSON/Initialization.swift | 0 {OptimoveCore => OptimoveSDK}/Sources/Classes/JSON/JSON.swift | 0 .../Sources/Classes/JSON/Merging.swift | 0 .../Sources/Classes/JSON/Querying.swift | 0 .../Sources/Classes/NetworkClient/NetworkClient.swift | 0 .../Sources/Classes/NetworkClient/NetworkError.swift | 0 .../Sources/Classes/NetworkClient/NetworkRequest.swift | 0 .../Sources/Classes/NetworkClient/NetworkResponse.swift | 0 .../Sources/Classes/NetworkClient/NetworkResult.swift | 0 .../Sources/Classes/Operations/AsyncOperation.swift | 0 OptimoveSDK/Sources/Classes/Optimove.swift | 1 - .../Sources/Classes/Optistream/OptistreamEvent.swift | 1 + .../Sources/Classes/Optistream/OptistreamEventBuilder.swift | 0 .../Sources/Classes/Optistream/OptistreamNetworking.swift | 0 .../Sources/Classes/Repository/ConfigurationRepository.swift | 0 .../Sources/Classes/Storage/FileManager+Optimove.swift | 0 .../Sources/Classes/Storage/FileStorage.swift | 1 + .../Sources/Classes/Storage/StorageFacade.swift | 1 + .../Sources/Classes/Storage/UserDefaults+Optimove.swift | 1 + {OptimoveCore => OptimoveSDK}/Tests/Resources/configs.json | 0 .../Tests/Resources/dev.tid.107.optipush.json | 0 .../Sources/Configurations/ConfigurationBuilderTests.swift | 2 +- .../Sources/Configurations/Global/GlobalConfigTests.swift | 2 +- .../Sources/Configurations/Tenant/TenantConfigTests.swift | 2 +- .../Tests/Sources/Mocks/MockConfigurationRepository.swift | 1 + .../Tests/Sources/NetworkClientTests.swift | 2 +- .../Tests/Sources/Storage/KeyValueStorageTests.swift | 3 ++- .../Tests/Sources/Storage/OptimoveFileManagerTests.swift | 2 +- .../Tests/Sources/Storage/OptimoveStorageFacadeTests.swift | 2 +- Package.swift | 1 + Shared/Sources/Fixture/ConfigurationFixture.swift | 2 +- Shared/Sources/Fixture/GlobalConfigFixture.swift | 2 +- Shared/Sources/Fixture/TenantConfigFixture.swift | 2 +- Shared/Sources/Mocks/MockOptimoveStorage.swift | 1 + Shared/Sources/Mocks/OptistreamNetworkingMock.swift | 2 +- Shared/Sources/StubEvent.swift | 2 +- 49 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 OptimoveCore/Sources/Assets/.gitkeep rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Coding/RuntimeCodingKey.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Configuration.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/ConfigurationBuilder.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Global/GlobalConfig.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Tenant/EventConfig.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Tenant/OptitrackMetaData.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Tenant/Parameter.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Tenant/RealtimeMetaData.swift (100%) rename {OptimoveCore/Sources/Classes/Configurations => OptimoveSDK/Sources/Classes/Configuration}/Tenant/TenantConfig.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Events/Event.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/JSON/Initialization.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/JSON/JSON.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/JSON/Merging.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/JSON/Querying.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/NetworkClient/NetworkClient.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/NetworkClient/NetworkError.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/NetworkClient/NetworkRequest.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/NetworkClient/NetworkResponse.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/NetworkClient/NetworkResult.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Operations/AsyncOperation.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Optistream/OptistreamEvent.swift (99%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Optistream/OptistreamEventBuilder.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Optistream/OptistreamNetworking.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Repository/ConfigurationRepository.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Storage/FileManager+Optimove.swift (100%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Storage/FileStorage.swift (99%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Storage/StorageFacade.swift (99%) rename {OptimoveCore => OptimoveSDK}/Sources/Classes/Storage/UserDefaults+Optimove.swift (96%) rename {OptimoveCore => OptimoveSDK}/Tests/Resources/configs.json (100%) rename {OptimoveCore => OptimoveSDK}/Tests/Resources/dev.tid.107.optipush.json (100%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/Configurations/ConfigurationBuilderTests.swift (96%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/Configurations/Global/GlobalConfigTests.swift (97%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift (97%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/NetworkClientTests.swift (98%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/Storage/KeyValueStorageTests.swift (99%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/Storage/OptimoveFileManagerTests.swift (98%) rename {OptimoveCore => OptimoveSDK}/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift (97%) diff --git a/OptimoveCore/Sources/Assets/.gitkeep b/OptimoveCore/Sources/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/OptimoveCore/Sources/Classes/Coding/RuntimeCodingKey.swift b/OptimoveSDK/Sources/Classes/Coding/RuntimeCodingKey.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Coding/RuntimeCodingKey.swift rename to OptimoveSDK/Sources/Classes/Coding/RuntimeCodingKey.swift diff --git a/OptimoveSDK/Sources/Classes/Components/OptiTrack/OptiTrack.swift b/OptimoveSDK/Sources/Classes/Components/OptiTrack/OptiTrack.swift index d470555a..ad683523 100644 --- a/OptimoveSDK/Sources/Classes/Components/OptiTrack/OptiTrack.swift +++ b/OptimoveSDK/Sources/Classes/Components/OptiTrack/OptiTrack.swift @@ -4,10 +4,6 @@ import Foundation import OptimoveCore import UIKit -typealias OptistreamEvent = OptimoveCore.OptistreamEvent -typealias OptistreamEventBuilder = OptimoveCore.OptistreamEventBuilder -typealias OptistreamNetworking = OptimoveCore.OptistreamNetworking - final class OptiTrack { enum Constants { static let eventBatchLimit = 50 diff --git a/OptimoveCore/Sources/Classes/Configurations/Configuration.swift b/OptimoveSDK/Sources/Classes/Configuration/Configuration.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Configuration.swift rename to OptimoveSDK/Sources/Classes/Configuration/Configuration.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/ConfigurationBuilder.swift b/OptimoveSDK/Sources/Classes/Configuration/ConfigurationBuilder.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/ConfigurationBuilder.swift rename to OptimoveSDK/Sources/Classes/Configuration/ConfigurationBuilder.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/Global/GlobalConfig.swift b/OptimoveSDK/Sources/Classes/Configuration/Global/GlobalConfig.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Global/GlobalConfig.swift rename to OptimoveSDK/Sources/Classes/Configuration/Global/GlobalConfig.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/Tenant/EventConfig.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/EventConfig.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Tenant/EventConfig.swift rename to OptimoveSDK/Sources/Classes/Configuration/Tenant/EventConfig.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/Tenant/OptitrackMetaData.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/OptitrackMetaData.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Tenant/OptitrackMetaData.swift rename to OptimoveSDK/Sources/Classes/Configuration/Tenant/OptitrackMetaData.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/Tenant/Parameter.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/Parameter.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Tenant/Parameter.swift rename to OptimoveSDK/Sources/Classes/Configuration/Tenant/Parameter.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/Tenant/RealtimeMetaData.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/RealtimeMetaData.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Tenant/RealtimeMetaData.swift rename to OptimoveSDK/Sources/Classes/Configuration/Tenant/RealtimeMetaData.swift diff --git a/OptimoveCore/Sources/Classes/Configurations/Tenant/TenantConfig.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/TenantConfig.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Configurations/Tenant/TenantConfig.swift rename to OptimoveSDK/Sources/Classes/Configuration/Tenant/TenantConfig.swift diff --git a/OptimoveCore/Sources/Classes/Events/Event.swift b/OptimoveSDK/Sources/Classes/Events/Event.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Events/Event.swift rename to OptimoveSDK/Sources/Classes/Events/Event.swift diff --git a/OptimoveSDK/Sources/Classes/Factories/MainFactory.swift b/OptimoveSDK/Sources/Classes/Factories/MainFactory.swift index 273503c1..3d62ff87 100644 --- a/OptimoveSDK/Sources/Classes/Factories/MainFactory.swift +++ b/OptimoveSDK/Sources/Classes/Factories/MainFactory.swift @@ -3,9 +3,6 @@ import Foundation import OptimoveCore -typealias NetworkClient = OptimoveCore.NetworkClient -typealias NetworkClientImpl = OptimoveCore.NetworkClientImpl - final class MainFactory { private let serviceLocator: ServiceLocator diff --git a/OptimoveCore/Sources/Classes/JSON/Initialization.swift b/OptimoveSDK/Sources/Classes/JSON/Initialization.swift similarity index 100% rename from OptimoveCore/Sources/Classes/JSON/Initialization.swift rename to OptimoveSDK/Sources/Classes/JSON/Initialization.swift diff --git a/OptimoveCore/Sources/Classes/JSON/JSON.swift b/OptimoveSDK/Sources/Classes/JSON/JSON.swift similarity index 100% rename from OptimoveCore/Sources/Classes/JSON/JSON.swift rename to OptimoveSDK/Sources/Classes/JSON/JSON.swift diff --git a/OptimoveCore/Sources/Classes/JSON/Merging.swift b/OptimoveSDK/Sources/Classes/JSON/Merging.swift similarity index 100% rename from OptimoveCore/Sources/Classes/JSON/Merging.swift rename to OptimoveSDK/Sources/Classes/JSON/Merging.swift diff --git a/OptimoveCore/Sources/Classes/JSON/Querying.swift b/OptimoveSDK/Sources/Classes/JSON/Querying.swift similarity index 100% rename from OptimoveCore/Sources/Classes/JSON/Querying.swift rename to OptimoveSDK/Sources/Classes/JSON/Querying.swift diff --git a/OptimoveCore/Sources/Classes/NetworkClient/NetworkClient.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkClient.swift similarity index 100% rename from OptimoveCore/Sources/Classes/NetworkClient/NetworkClient.swift rename to OptimoveSDK/Sources/Classes/NetworkClient/NetworkClient.swift diff --git a/OptimoveCore/Sources/Classes/NetworkClient/NetworkError.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkError.swift similarity index 100% rename from OptimoveCore/Sources/Classes/NetworkClient/NetworkError.swift rename to OptimoveSDK/Sources/Classes/NetworkClient/NetworkError.swift diff --git a/OptimoveCore/Sources/Classes/NetworkClient/NetworkRequest.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkRequest.swift similarity index 100% rename from OptimoveCore/Sources/Classes/NetworkClient/NetworkRequest.swift rename to OptimoveSDK/Sources/Classes/NetworkClient/NetworkRequest.swift diff --git a/OptimoveCore/Sources/Classes/NetworkClient/NetworkResponse.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResponse.swift similarity index 100% rename from OptimoveCore/Sources/Classes/NetworkClient/NetworkResponse.swift rename to OptimoveSDK/Sources/Classes/NetworkClient/NetworkResponse.swift diff --git a/OptimoveCore/Sources/Classes/NetworkClient/NetworkResult.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResult.swift similarity index 100% rename from OptimoveCore/Sources/Classes/NetworkClient/NetworkResult.swift rename to OptimoveSDK/Sources/Classes/NetworkClient/NetworkResult.swift diff --git a/OptimoveCore/Sources/Classes/Operations/AsyncOperation.swift b/OptimoveSDK/Sources/Classes/Operations/AsyncOperation.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Operations/AsyncOperation.swift rename to OptimoveSDK/Sources/Classes/Operations/AsyncOperation.swift diff --git a/OptimoveSDK/Sources/Classes/Optimove.swift b/OptimoveSDK/Sources/Classes/Optimove.swift index f8ffccfb..09912db6 100644 --- a/OptimoveSDK/Sources/Classes/Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Optimove.swift @@ -5,7 +5,6 @@ import OptimoveCore import UIKit.UIApplication import UserNotifications -public typealias Event = OptimoveCore.Event typealias Logger = OptimoveCore.Logger /// The Optimove SDK for iOS - a realtime customer data platform. diff --git a/OptimoveCore/Sources/Classes/Optistream/OptistreamEvent.swift b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift similarity index 99% rename from OptimoveCore/Sources/Classes/Optistream/OptistreamEvent.swift rename to OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift index 48d8a283..2b9154a5 100644 --- a/OptimoveCore/Sources/Classes/Optistream/OptistreamEvent.swift +++ b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift @@ -1,6 +1,7 @@ // Copyright © 2020 Optimove. All rights reserved. import Foundation +import OptimoveCore public struct OptistreamEvent: Codable { public let tenant: Int diff --git a/OptimoveCore/Sources/Classes/Optistream/OptistreamEventBuilder.swift b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEventBuilder.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Optistream/OptistreamEventBuilder.swift rename to OptimoveSDK/Sources/Classes/Optistream/OptistreamEventBuilder.swift diff --git a/OptimoveCore/Sources/Classes/Optistream/OptistreamNetworking.swift b/OptimoveSDK/Sources/Classes/Optistream/OptistreamNetworking.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Optistream/OptistreamNetworking.swift rename to OptimoveSDK/Sources/Classes/Optistream/OptistreamNetworking.swift diff --git a/OptimoveCore/Sources/Classes/Repository/ConfigurationRepository.swift b/OptimoveSDK/Sources/Classes/Repository/ConfigurationRepository.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Repository/ConfigurationRepository.swift rename to OptimoveSDK/Sources/Classes/Repository/ConfigurationRepository.swift diff --git a/OptimoveCore/Sources/Classes/Storage/FileManager+Optimove.swift b/OptimoveSDK/Sources/Classes/Storage/FileManager+Optimove.swift similarity index 100% rename from OptimoveCore/Sources/Classes/Storage/FileManager+Optimove.swift rename to OptimoveSDK/Sources/Classes/Storage/FileManager+Optimove.swift diff --git a/OptimoveCore/Sources/Classes/Storage/FileStorage.swift b/OptimoveSDK/Sources/Classes/Storage/FileStorage.swift similarity index 99% rename from OptimoveCore/Sources/Classes/Storage/FileStorage.swift rename to OptimoveSDK/Sources/Classes/Storage/FileStorage.swift index b615ddb3..2d5d24b8 100644 --- a/OptimoveCore/Sources/Classes/Storage/FileStorage.swift +++ b/OptimoveSDK/Sources/Classes/Storage/FileStorage.swift @@ -1,6 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation +import OptimoveCore public protocol FileStorage { /// Check file if exist. diff --git a/OptimoveCore/Sources/Classes/Storage/StorageFacade.swift b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift similarity index 99% rename from OptimoveCore/Sources/Classes/Storage/StorageFacade.swift rename to OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift index 863ae2d0..0c4b9509 100644 --- a/OptimoveCore/Sources/Classes/Storage/StorageFacade.swift +++ b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift @@ -1,6 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation +import OptimoveCore /// Combined protocol for a convenince access to stored values and files. public typealias OptimoveStorage = FileStorage & KeyValueStorage & StorageValue diff --git a/OptimoveCore/Sources/Classes/Storage/UserDefaults+Optimove.swift b/OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift similarity index 96% rename from OptimoveCore/Sources/Classes/Storage/UserDefaults+Optimove.swift rename to OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift index 7cf7fd5a..42f28bee 100644 --- a/OptimoveCore/Sources/Classes/Storage/UserDefaults+Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift @@ -1,6 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation +import OptimoveCore public extension UserDefaults { enum Constants { diff --git a/OptimoveCore/Tests/Resources/configs.json b/OptimoveSDK/Tests/Resources/configs.json similarity index 100% rename from OptimoveCore/Tests/Resources/configs.json rename to OptimoveSDK/Tests/Resources/configs.json diff --git a/OptimoveCore/Tests/Resources/dev.tid.107.optipush.json b/OptimoveSDK/Tests/Resources/dev.tid.107.optipush.json similarity index 100% rename from OptimoveCore/Tests/Resources/dev.tid.107.optipush.json rename to OptimoveSDK/Tests/Resources/dev.tid.107.optipush.json diff --git a/OptimoveCore/Tests/Sources/Configurations/ConfigurationBuilderTests.swift b/OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift similarity index 96% rename from OptimoveCore/Tests/Sources/Configurations/ConfigurationBuilderTests.swift rename to OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift index 4bce5f2c..6dd661f3 100644 --- a/OptimoveCore/Tests/Sources/Configurations/ConfigurationBuilderTests.swift +++ b/OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -@testable import OptimoveCore +import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveCore/Tests/Sources/Configurations/Global/GlobalConfigTests.swift b/OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift similarity index 97% rename from OptimoveCore/Tests/Sources/Configurations/Global/GlobalConfigTests.swift rename to OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift index b406aa7f..8fca81cc 100644 --- a/OptimoveCore/Tests/Sources/Configurations/Global/GlobalConfigTests.swift +++ b/OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -@testable import OptimoveCore +import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveCore/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift b/OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift similarity index 97% rename from OptimoveCore/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift rename to OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift index 63831896..73656568 100644 --- a/OptimoveCore/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift +++ b/OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -@testable import OptimoveCore +import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift b/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift index 6616468d..54dd34ee 100644 --- a/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift +++ b/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift @@ -1,5 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. +import OptimoveSDK import OptimoveCore final class MockConfigurationRepository: ConfigurationRepository { diff --git a/OptimoveCore/Tests/Sources/NetworkClientTests.swift b/OptimoveSDK/Tests/Sources/NetworkClientTests.swift similarity index 98% rename from OptimoveCore/Tests/Sources/NetworkClientTests.swift rename to OptimoveSDK/Tests/Sources/NetworkClientTests.swift index 15ebdd62..455f765e 100644 --- a/OptimoveCore/Tests/Sources/NetworkClientTests.swift +++ b/OptimoveSDK/Tests/Sources/NetworkClientTests.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Mocker -@testable import OptimoveCore +import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveCore/Tests/Sources/Storage/KeyValueStorageTests.swift b/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift similarity index 99% rename from OptimoveCore/Tests/Sources/Storage/KeyValueStorageTests.swift rename to OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift index c9872600..6fe47d36 100644 --- a/OptimoveCore/Tests/Sources/Storage/KeyValueStorageTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift @@ -1,6 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. -@testable import OptimoveCore +import OptimoveCore +import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveCore/Tests/Sources/Storage/OptimoveFileManagerTests.swift b/OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift similarity index 98% rename from OptimoveCore/Tests/Sources/Storage/OptimoveFileManagerTests.swift rename to OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift index 3cff64a3..5c622f95 100644 --- a/OptimoveCore/Tests/Sources/Storage/OptimoveFileManagerTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -@testable import OptimoveCore +import OptimoveSDK import XCTest final class MockedFileManager: FileManager { diff --git a/OptimoveCore/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift b/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift similarity index 97% rename from OptimoveCore/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift rename to OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift index 27a3745a..4ea35af2 100644 --- a/OptimoveCore/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -@testable import OptimoveCore +@testable import OptimoveSDK import XCTest class OptimoveStorageFacadeTests: XCTestCase { diff --git a/Package.swift b/Package.swift index fa4253a0..639dd44f 100644 --- a/Package.swift +++ b/Package.swift @@ -56,6 +56,7 @@ let package = Package( name: "OptimoveTest", dependencies: [ "OptimoveCore", + "OptimoveSDK", ], path: "Shared", resources: [ diff --git a/Shared/Sources/Fixture/ConfigurationFixture.swift b/Shared/Sources/Fixture/ConfigurationFixture.swift index 53b7545c..4e572bb6 100644 --- a/Shared/Sources/Fixture/ConfigurationFixture.swift +++ b/Shared/Sources/Fixture/ConfigurationFixture.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation -@testable import OptimoveCore +@testable import OptimoveSDK public struct Options { public let isEnableRealtime: Bool diff --git a/Shared/Sources/Fixture/GlobalConfigFixture.swift b/Shared/Sources/Fixture/GlobalConfigFixture.swift index 4c1b7a2a..435ea587 100644 --- a/Shared/Sources/Fixture/GlobalConfigFixture.swift +++ b/Shared/Sources/Fixture/GlobalConfigFixture.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation -@testable import OptimoveCore +@testable import OptimoveSDK public final class GlobalConfigFixture: FileAccessible { public let fileName: String = "core_events.json" diff --git a/Shared/Sources/Fixture/TenantConfigFixture.swift b/Shared/Sources/Fixture/TenantConfigFixture.swift index 7a7e0af7..b03b8200 100644 --- a/Shared/Sources/Fixture/TenantConfigFixture.swift +++ b/Shared/Sources/Fixture/TenantConfigFixture.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation -import OptimoveCore +import OptimoveSDK public final class TenantConfigFixture { public init() {} diff --git a/Shared/Sources/Mocks/MockOptimoveStorage.swift b/Shared/Sources/Mocks/MockOptimoveStorage.swift index d98e7cae..7ad92a82 100644 --- a/Shared/Sources/Mocks/MockOptimoveStorage.swift +++ b/Shared/Sources/Mocks/MockOptimoveStorage.swift @@ -2,6 +2,7 @@ import Foundation import OptimoveCore +import OptimoveSDK public final class MockOptimoveStorage: OptimoveStorage { public init() {} diff --git a/Shared/Sources/Mocks/OptistreamNetworkingMock.swift b/Shared/Sources/Mocks/OptistreamNetworkingMock.swift index 64d1b169..5aa116ad 100644 --- a/Shared/Sources/Mocks/OptistreamNetworkingMock.swift +++ b/Shared/Sources/Mocks/OptistreamNetworkingMock.swift @@ -1,6 +1,6 @@ // Copyright © 2020 Optimove. All rights reserved. -import OptimoveCore +import OptimoveSDK public final class OptistreamNetworkingMock: OptistreamNetworking { public init() {} diff --git a/Shared/Sources/StubEvent.swift b/Shared/Sources/StubEvent.swift index 446bc530..439d3a5c 100644 --- a/Shared/Sources/StubEvent.swift +++ b/Shared/Sources/StubEvent.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation -@testable import OptimoveCore +@testable import OptimoveSDK public final class StubEvent: Event { public enum Constnats { From e741854e36395dbd21feebb7e34c78d71867d8ee Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 12:44:11 +0200 Subject: [PATCH 13/30] chore: merge mobile core with optimove core --- .../Sources/Classes}/AppGroupsHelper.swift | 0 .../Sources/Classes}/Credentials.swift | 0 .../Classes}/Extensions/Notifications.swift | 0 .../Classes}/KeyValPersistenceHelper.swift | 0 .../Sources/Classes}/MediaHelper.swift | 0 .../Sources/Classes}/OptimobileEvent.swift | 0 .../Sources/Classes}/OptimobileHelper.swift | 0 .../Classes}/OptimobileUserDefaultsKey.swift | 0 .../Classes}/PendingNotification.swift | 0 .../Classes}/PendingNotificationHelper.swift | 0 .../Sources/Classes}/PushNotification.swift | 0 .../Resources/notification-background.json | 0 .../Tests/Resources/notification-badge.json | 0 .../Tests/Resources/notification-buttons.json | 0 .../Resources/notification-deeplink.json | 0 .../Tests/Resources/notification-image.json | 0 .../Tests/Resources/notification-message.json | 0 .../Tests/Resources/notification-url.json | 0 .../Tests/Sources}/MediaHelperTests.swift | 2 +- .../Sources}/PushNotificationTests.swift | 2 +- .../Sources/CategoryManager.swift | 2 +- .../Sources/OptimoveNotificationService.swift | 2 +- OptimoveSDK/Sources/Assets/.gitkeep | 0 .../Classes/Optimobile/AnalyticsHelper.swift | 2 +- .../Optimobile/InApp/InAppManager.swift | 2 +- .../Optimobile/InApp/InAppPresenter.swift | 2 +- .../Network/AuthorizationMediator.swift | 2 +- .../Optimobile/Network/UrlBuilder.swift | 2 +- .../Optimobile/Optimobile+Analytics.swift | 2 +- .../Optimobile/Optimobile+DeepLinking.swift | 2 +- .../Optimobile/Optimobile+Location.swift | 2 +- .../Classes/Optimobile/Optimobile+Push.swift | 2 +- .../Classes/Optimobile/Optimobile+Stats.swift | 1 - .../Classes/Optimobile/Optimobile.swift | 2 +- .../Classes/Optimobile/OptimoveInApp.swift | 2 +- ...timoveUserNotificationCenterDelegate.swift | 2 +- .../Classes/Optimobile/SessionHelper.swift | 2 +- .../Sources/Classes/OptimoveConfig.swift | 2 +- .../Sources/OptimoveConfigBuilderTests.swift | 2 +- Package.swift | 27 ++----------------- 40 files changed, 21 insertions(+), 45 deletions(-) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/AppGroupsHelper.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/Credentials.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/Extensions/Notifications.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/KeyValPersistenceHelper.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/MediaHelper.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/OptimobileEvent.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/OptimobileHelper.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/OptimobileUserDefaultsKey.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/PendingNotification.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/PendingNotificationHelper.swift (100%) rename {OptimobileCore/Sources => OptimoveCore/Sources/Classes}/PushNotification.swift (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-background.json (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-badge.json (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-buttons.json (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-deeplink.json (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-image.json (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-message.json (100%) rename {OptimobileCore => OptimoveCore}/Tests/Resources/notification-url.json (100%) rename {OptimobileCore/Tests => OptimoveCore/Tests/Sources}/MediaHelperTests.swift (96%) rename {OptimobileCore/Tests => OptimoveCore/Tests/Sources}/PushNotificationTests.swift (99%) delete mode 100644 OptimoveSDK/Sources/Assets/.gitkeep diff --git a/OptimobileCore/Sources/AppGroupsHelper.swift b/OptimoveCore/Sources/Classes/AppGroupsHelper.swift similarity index 100% rename from OptimobileCore/Sources/AppGroupsHelper.swift rename to OptimoveCore/Sources/Classes/AppGroupsHelper.swift diff --git a/OptimobileCore/Sources/Credentials.swift b/OptimoveCore/Sources/Classes/Credentials.swift similarity index 100% rename from OptimobileCore/Sources/Credentials.swift rename to OptimoveCore/Sources/Classes/Credentials.swift diff --git a/OptimobileCore/Sources/Extensions/Notifications.swift b/OptimoveCore/Sources/Classes/Extensions/Notifications.swift similarity index 100% rename from OptimobileCore/Sources/Extensions/Notifications.swift rename to OptimoveCore/Sources/Classes/Extensions/Notifications.swift diff --git a/OptimobileCore/Sources/KeyValPersistenceHelper.swift b/OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift similarity index 100% rename from OptimobileCore/Sources/KeyValPersistenceHelper.swift rename to OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift diff --git a/OptimobileCore/Sources/MediaHelper.swift b/OptimoveCore/Sources/Classes/MediaHelper.swift similarity index 100% rename from OptimobileCore/Sources/MediaHelper.swift rename to OptimoveCore/Sources/Classes/MediaHelper.swift diff --git a/OptimobileCore/Sources/OptimobileEvent.swift b/OptimoveCore/Sources/Classes/OptimobileEvent.swift similarity index 100% rename from OptimobileCore/Sources/OptimobileEvent.swift rename to OptimoveCore/Sources/Classes/OptimobileEvent.swift diff --git a/OptimobileCore/Sources/OptimobileHelper.swift b/OptimoveCore/Sources/Classes/OptimobileHelper.swift similarity index 100% rename from OptimobileCore/Sources/OptimobileHelper.swift rename to OptimoveCore/Sources/Classes/OptimobileHelper.swift diff --git a/OptimobileCore/Sources/OptimobileUserDefaultsKey.swift b/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift similarity index 100% rename from OptimobileCore/Sources/OptimobileUserDefaultsKey.swift rename to OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift diff --git a/OptimobileCore/Sources/PendingNotification.swift b/OptimoveCore/Sources/Classes/PendingNotification.swift similarity index 100% rename from OptimobileCore/Sources/PendingNotification.swift rename to OptimoveCore/Sources/Classes/PendingNotification.swift diff --git a/OptimobileCore/Sources/PendingNotificationHelper.swift b/OptimoveCore/Sources/Classes/PendingNotificationHelper.swift similarity index 100% rename from OptimobileCore/Sources/PendingNotificationHelper.swift rename to OptimoveCore/Sources/Classes/PendingNotificationHelper.swift diff --git a/OptimobileCore/Sources/PushNotification.swift b/OptimoveCore/Sources/Classes/PushNotification.swift similarity index 100% rename from OptimobileCore/Sources/PushNotification.swift rename to OptimoveCore/Sources/Classes/PushNotification.swift diff --git a/OptimobileCore/Tests/Resources/notification-background.json b/OptimoveCore/Tests/Resources/notification-background.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-background.json rename to OptimoveCore/Tests/Resources/notification-background.json diff --git a/OptimobileCore/Tests/Resources/notification-badge.json b/OptimoveCore/Tests/Resources/notification-badge.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-badge.json rename to OptimoveCore/Tests/Resources/notification-badge.json diff --git a/OptimobileCore/Tests/Resources/notification-buttons.json b/OptimoveCore/Tests/Resources/notification-buttons.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-buttons.json rename to OptimoveCore/Tests/Resources/notification-buttons.json diff --git a/OptimobileCore/Tests/Resources/notification-deeplink.json b/OptimoveCore/Tests/Resources/notification-deeplink.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-deeplink.json rename to OptimoveCore/Tests/Resources/notification-deeplink.json diff --git a/OptimobileCore/Tests/Resources/notification-image.json b/OptimoveCore/Tests/Resources/notification-image.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-image.json rename to OptimoveCore/Tests/Resources/notification-image.json diff --git a/OptimobileCore/Tests/Resources/notification-message.json b/OptimoveCore/Tests/Resources/notification-message.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-message.json rename to OptimoveCore/Tests/Resources/notification-message.json diff --git a/OptimobileCore/Tests/Resources/notification-url.json b/OptimoveCore/Tests/Resources/notification-url.json similarity index 100% rename from OptimobileCore/Tests/Resources/notification-url.json rename to OptimoveCore/Tests/Resources/notification-url.json diff --git a/OptimobileCore/Tests/MediaHelperTests.swift b/OptimoveCore/Tests/Sources/MediaHelperTests.swift similarity index 96% rename from OptimobileCore/Tests/MediaHelperTests.swift rename to OptimoveCore/Tests/Sources/MediaHelperTests.swift index a4dcfbe8..580e1006 100644 --- a/OptimobileCore/Tests/MediaHelperTests.swift +++ b/OptimoveCore/Tests/Sources/MediaHelperTests.swift @@ -1,6 +1,6 @@ // Copyright © 2023 Optimove. All rights reserved. -@testable import OptimobileCore +import OptimoveCore import XCTest final class MediaHelperTests: XCTestCase { diff --git a/OptimobileCore/Tests/PushNotificationTests.swift b/OptimoveCore/Tests/Sources/PushNotificationTests.swift similarity index 99% rename from OptimobileCore/Tests/PushNotificationTests.swift rename to OptimoveCore/Tests/Sources/PushNotificationTests.swift index c8d068e5..8f0cac7f 100644 --- a/OptimobileCore/Tests/PushNotificationTests.swift +++ b/OptimoveCore/Tests/Sources/PushNotificationTests.swift @@ -1,6 +1,6 @@ // Copyright © 2023 Optimove. All rights reserved. -import OptimobileCore +import OptimoveCore import OptimoveTest import XCTest diff --git a/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift b/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift index 4c1354ee..995a145e 100644 --- a/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift +++ b/OptimoveNotificationServiceExtension/Sources/CategoryManager.swift @@ -1,6 +1,6 @@ // Copyright © 2022 Optimove. All rights reserved. -import OptimobileCore +import OptimoveCore import UserNotifications enum CategoryManager { diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 10a13472..9c79734c 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore import UIKit import UserNotifications diff --git a/OptimoveSDK/Sources/Assets/.gitkeep b/OptimoveSDK/Sources/Assets/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift index 52726a6d..3c34e005 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift @@ -2,7 +2,7 @@ import CoreData import Foundation -import OptimobileCore +import OptimoveCore class KSEventModel: NSManagedObject { @NSManaged var uuid: String diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift index 573331de..db390962 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift @@ -2,7 +2,7 @@ import CoreData import Foundation -import OptimobileCore +import OptimoveCore import UIKit public enum InAppMessagePresentationResult: String { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift index 3cd6318b..ffac42dd 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift @@ -1,6 +1,6 @@ // Copyright © 2022 Optimove. All rights reserved. -import OptimobileCore +import OptimoveCore import StoreKit import UIKit import UserNotifications diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift b/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift index 8a795fa9..f3873bb1 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Network/AuthorizationMediator.swift @@ -1,7 +1,7 @@ // Copyright © 2023 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore enum AuthorizationStrategy { case basic diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift b/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift index 48a3d16a..84bcd4dd 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore public class UrlBuilder { enum Error: LocalizedError { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift index 66ed0ead..72b184f5 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore extension Optimobile { static func trackEvent(eventType: OptimobileEvent, properties: [String: Any]?, immediateFlush: Bool = false) { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift index 3c9d97f7..0cc1bd48 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift @@ -1,8 +1,8 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation +import OptimoveCore import UIKit -import OptimobileCore public struct DeepLinkContent { public let title: String? diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift index ec954731..ae60fd29 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Location.swift @@ -1,6 +1,6 @@ import CoreLocation import Foundation -import OptimobileCore +import OptimoveCore extension Optimobile { static func sendLocationUpdate(location: CLLocation) { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index 7c5b5fc4..10db4778 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -2,7 +2,7 @@ import Foundation import ObjectiveC.runtime -import OptimobileCore +import OptimoveCore import UIKit import UserNotifications diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift index bd8deee3..19b0f52f 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift @@ -1,7 +1,6 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore import OptimoveCore import UserNotifications diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift index c5ff7c08..d86d9c6c 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore import UserNotifications public typealias InAppDeepLinkHandlerBlock = (InAppButtonPress) -> Void diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index e4f5e331..e4a3f252 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -2,7 +2,7 @@ import CoreData import Foundation -import OptimobileCore +import OptimoveCore public class InAppInboxItem { public internal(set) var id: Int64 diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift index 101f90d9..b091d137 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore import UserNotifications @available(iOS 10.0, *) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift b/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift index 983a48bd..3da83b7d 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/SessionHelper.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore import UIKit class SessionIdleTimer { diff --git a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift index 031ce28a..6cfdc426 100644 --- a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift +++ b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift @@ -1,7 +1,7 @@ // Copyright © 2022 Optimove. All rights reserved. import Foundation -import OptimobileCore +import OptimoveCore /// A set of options for configuring the SDK. /// - Note: The SDK can be configured to support multiple features. diff --git a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift index 6cf358ee..e4dfb222 100644 --- a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift +++ b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift @@ -1,6 +1,6 @@ // Copyright © 2023 Optimove. All rights reserved. -import OptimobileCore +import OptimoveCore @testable import OptimoveSDK import XCTest diff --git a/Package.swift b/Package.swift index 639dd44f..73b21dea 100644 --- a/Package.swift +++ b/Package.swift @@ -29,7 +29,6 @@ let package = Package( .target( name: "OptimoveSDK", dependencies: [ - "OptimobileCore", "OptimoveCore", ], path: "OptimoveSDK/Sources" @@ -38,24 +37,16 @@ let package = Package( name: "OptimoveCore", path: "OptimoveCore/Sources" ), - .target( - name: "OptimobileCore", - path: "OptimobileCore/Sources", - resources: [ - .process("Resources"), - ] - ), .target( name: "OptimoveNotificationServiceExtension", dependencies: [ - "OptimobileCore", + "OptimoveCore", ], path: "OptimoveNotificationServiceExtension/Sources" ), .target( name: "OptimoveTest", dependencies: [ - "OptimoveCore", "OptimoveSDK", ], path: "Shared", @@ -87,27 +78,13 @@ let package = Package( .process("Resources"), ] ), - .testTarget( - name: "OptimobileCoreTests", - dependencies: [ - "OptimobileCore", - "OptimoveTest", - ], - path: "OptimobileCore/Tests", - resources: [ - .process("Resources"), - ] - ), .testTarget( name: "OptimoveNotificationServiceExtensionTests", dependencies: [ "OptimoveNotificationServiceExtension", "OptimoveTest", ], - path: "OptimoveNotificationServiceExtension/Tests", - resources: [ - .process("Resources"), - ] + path: "OptimoveNotificationServiceExtension/Tests" ), ], swiftLanguageVersions: [.v5] From 85adee62dd786afca1ac4554488ebb795981678a Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 13:36:58 +0200 Subject: [PATCH 14/30] refactor: review access level --- .../Sources/Classes/AppGroupsHelper.swift | 4 +- .../Sources/Classes/Credentials.swift | 13 ---- .../Classes/Extension/Bundle+HostApp.swift | 2 +- .../Classes/Extension/Result+Successful.swift | 12 ---- .../Extension/utsname+DeviceModel.swift | 2 +- .../Classes/Extensions/Notifications.swift | 7 --- .../Sources/Classes/OptimobileHelper.swift | 2 - .../Classes/Coding/RuntimeCodingKey.swift | 10 ++-- .../Classes/Configuration/Configuration.swift | 60 +++++++++---------- .../Configuration/ConfigurationBuilder.swift | 16 ++--- .../Configuration/Global/GlobalConfig.swift | 30 +++++----- .../RemoteConfigurationRequestBuilder.swift | 8 +-- .../Configuration/Tenant/EventConfig.swift | 12 ++-- .../Tenant/OptitrackMetaData.swift | 8 +-- .../Configuration/Tenant/Parameter.swift | 10 ++-- .../Tenant/RealtimeMetaData.swift | 6 +- .../Configuration/Tenant/TenantConfig.swift | 22 +++---- .../Classes/DataStructs/RingBuffer.swift | 14 ++--- .../Sources/Classes/Events/Event.swift | 20 +++---- .../Extensions}/Bundle+AppVersion.swift | 2 +- .../Classes/Extensions/Notification.swift | 3 +- .../Sources/Classes/JSON/Initialization.swift | 18 +++--- OptimoveSDK/Sources/Classes/JSON/JSON.swift | 8 +-- .../Sources/Classes/JSON/Merging.swift | 2 +- .../Sources/Classes/JSON/Querying.swift | 2 +- .../Classes/NetworkClient/NetworkClient.swift | 10 ++-- .../Classes/NetworkClient/NetworkError.swift | 4 +- .../NetworkClient/NetworkRequest.swift | 54 ++++++++--------- .../NetworkClient/NetworkResponse.swift | 4 +- .../Classes/NetworkClient/NetworkResult.swift | 2 +- .../Classes/Operations/AsyncOperation.swift | 14 ++--- .../Classes/Optimobile/Credentials.swift | 11 ++++ .../Classes/Optimobile/CwlSysctl.swift | 38 ++++++------ .../Optimobile/InApp/InAppModels.swift | 24 ++++---- .../Optimobile/Optimobile+DeepLinking.swift | 4 +- .../Classes/Optimobile/Optimobile+Push.swift | 2 + .../Classes/Optimobile/OptimoveInApp.swift | 58 +++++++++--------- .../Sources/Classes/OptimoveConfig.swift | 8 +-- .../Classes/Optistream/OptistreamEvent.swift | 40 ++++++------- .../Optistream/OptistreamEventBuilder.swift | 6 +- .../Optistream/OptistreamNetworking.swift | 10 ++-- .../Repository/ConfigurationRepository.swift | 18 +++--- .../Storage/FileManager+Optimove.swift | 2 +- .../Sources/Classes/Storage/FileStorage.swift | 20 +++---- .../Classes/Storage/StorageFacade.swift | 38 ++++++------ .../Storage/UserDefaults+Optimove.swift | 2 +- .../ConfigurationBuilderTests.swift | 2 +- .../Global/GlobalConfigTests.swift | 2 +- .../Tenant/TenantConfigTests.swift | 2 +- .../Mocks/MockConfigurationRepository.swift | 2 +- .../Tests/Sources/NetworkClientTests.swift | 2 +- .../Storage/KeyValueStorageTests.swift | 2 +- .../Storage/OptimoveFileManagerTests.swift | 2 +- .../Sources/Fixture/TenantConfigFixture.swift | 2 +- .../Sources/Mocks/MockOptimoveStorage.swift | 2 +- .../Mocks/OptistreamNetworkingMock.swift | 2 +- Shared/Sources/OptimoveTestCase.swift | 1 + 57 files changed, 332 insertions(+), 351 deletions(-) delete mode 100644 OptimoveCore/Sources/Classes/Credentials.swift delete mode 100644 OptimoveCore/Sources/Classes/Extension/Result+Successful.swift delete mode 100644 OptimoveCore/Sources/Classes/Extensions/Notifications.swift rename {OptimoveCore/Sources/Classes/Extension => OptimoveSDK/Sources/Classes/Extensions}/Bundle+AppVersion.swift (88%) create mode 100644 OptimoveSDK/Sources/Classes/Optimobile/Credentials.swift diff --git a/OptimoveCore/Sources/Classes/AppGroupsHelper.swift b/OptimoveCore/Sources/Classes/AppGroupsHelper.swift index 24dbc808..af813509 100644 --- a/OptimoveCore/Sources/Classes/AppGroupsHelper.swift +++ b/OptimoveCore/Sources/Classes/AppGroupsHelper.swift @@ -2,8 +2,8 @@ import Foundation -public enum AppGroupConfig { - public static var suffix: String = ".optimove" +enum AppGroupConfig { + static var suffix: String = ".optimove" } public enum AppGroupsHelper { diff --git a/OptimoveCore/Sources/Classes/Credentials.swift b/OptimoveCore/Sources/Classes/Credentials.swift deleted file mode 100644 index 6b6774c2..00000000 --- a/OptimoveCore/Sources/Classes/Credentials.swift +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright © 2023 Optimove. All rights reserved. - -import Foundation - -public struct OptimobileCredentials: Codable { - public let apiKey: String - public let secretKey: String - - public init(apiKey: String, secretKey: String) { - self.apiKey = apiKey - self.secretKey = secretKey - } -} diff --git a/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift b/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift index 9d3548e2..6f0d73de 100644 --- a/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift +++ b/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift @@ -2,7 +2,7 @@ import Foundation -public extension Bundle { +extension Bundle { /// https://stackoverflow.com/a/27849695 static func hostAppBundle() -> Bundle? { let mainBundle = Bundle.main diff --git a/OptimoveCore/Sources/Classes/Extension/Result+Successful.swift b/OptimoveCore/Sources/Classes/Extension/Result+Successful.swift deleted file mode 100644 index af15ed09..00000000 --- a/OptimoveCore/Sources/Classes/Extension/Result+Successful.swift +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2019 Optimove. All rights reserved. - -public extension Result { - var isSuccessful: Bool { - do { - _ = try get() - return true - } catch { - return false - } - } -} diff --git a/OptimoveCore/Sources/Classes/Extension/utsname+DeviceModel.swift b/OptimoveCore/Sources/Classes/Extension/utsname+DeviceModel.swift index 282b89b4..f0d34ae4 100644 --- a/OptimoveCore/Sources/Classes/Extension/utsname+DeviceModel.swift +++ b/OptimoveCore/Sources/Classes/Extension/utsname+DeviceModel.swift @@ -2,7 +2,7 @@ import Foundation -public extension utsname { +extension utsname { var deviceModel: String { var systemInfo = self uname(&systemInfo) diff --git a/OptimoveCore/Sources/Classes/Extensions/Notifications.swift b/OptimoveCore/Sources/Classes/Extensions/Notifications.swift deleted file mode 100644 index c0699e8f..00000000 --- a/OptimoveCore/Sources/Classes/Extensions/Notifications.swift +++ /dev/null @@ -1,7 +0,0 @@ -// Copyright © 2023 Optimove. All rights reserved. - -import Foundation - -public extension Notification.Name { - static let optimobileInializationFinished = Notification.Name("optimobileInializationFinished") -} diff --git a/OptimoveCore/Sources/Classes/OptimobileHelper.swift b/OptimoveCore/Sources/Classes/OptimobileHelper.swift index 7932235b..13dc6448 100644 --- a/OptimoveCore/Sources/Classes/OptimobileHelper.swift +++ b/OptimoveCore/Sources/Classes/OptimobileHelper.swift @@ -2,8 +2,6 @@ import Foundation -public let KS_MESSAGE_TYPE_PUSH = 1 - public enum OptimobileHelper { private static let installIdLock = DispatchSemaphore(value: 1) public static let userIdLock = DispatchSemaphore(value: 1) diff --git a/OptimoveSDK/Sources/Classes/Coding/RuntimeCodingKey.swift b/OptimoveSDK/Sources/Classes/Coding/RuntimeCodingKey.swift index 95a87773..5657cab5 100644 --- a/OptimoveSDK/Sources/Classes/Coding/RuntimeCodingKey.swift +++ b/OptimoveSDK/Sources/Classes/Coding/RuntimeCodingKey.swift @@ -2,16 +2,16 @@ import Foundation -public final class RuntimeCodingKey: CodingKey { - public var stringValue: String +final class RuntimeCodingKey: CodingKey { + var stringValue: String - public init?(stringValue: String) { + init?(stringValue: String) { self.stringValue = stringValue } - public var intValue: Int? + var intValue: Int? - public init?(intValue: Int) { + init?(intValue: Int) { self.intValue = intValue stringValue = String(intValue) } diff --git a/OptimoveSDK/Sources/Classes/Configuration/Configuration.swift b/OptimoveSDK/Sources/Classes/Configuration/Configuration.swift index eddca072..25c7dea8 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Configuration.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Configuration.swift @@ -2,16 +2,16 @@ import Foundation -public struct Configuration: Codable, TenantInfo, EventInfo { - public let tenantID: Int - public let logger: LoggerConfig - public let realtime: RealtimeConfig - public let optitrack: OptitrackConfig - public let events: [String: EventsConfig] - public let isEnableRealtime: Bool - public let isSupportedAirship: Bool +struct Configuration: Codable, TenantInfo, EventInfo { + let tenantID: Int + let logger: LoggerConfig + let realtime: RealtimeConfig + let optitrack: OptitrackConfig + let events: [String: EventsConfig] + let isEnableRealtime: Bool + let isSupportedAirship: Bool - public init( + init( tenantID: Int, logger: LoggerConfig, realtime: RealtimeConfig, @@ -30,12 +30,12 @@ public struct Configuration: Codable, TenantInfo, EventInfo { } } -public struct LoggerConfig: Codable, TenantInfo { - public let tenantID: Int - public let logServiceEndpoint: URL - public let isProductionLogsEnabled: Bool +struct LoggerConfig: Codable, TenantInfo { + let tenantID: Int + let logServiceEndpoint: URL + let isProductionLogsEnabled: Bool - public init( + init( tenantID: Int, logServiceEndpoint: URL, isProductionLogsEnabled: Bool @@ -46,13 +46,13 @@ public struct LoggerConfig: Codable, TenantInfo { } } -public struct RealtimeConfig: Codable, TenantInfo, EventInfo { - public let tenantID: Int - public let realtimeGateway: URL - public let events: [String: EventsConfig] - public let isEnableRealtimeThroughOptistream: Bool +struct RealtimeConfig: Codable, TenantInfo, EventInfo { + let tenantID: Int + let realtimeGateway: URL + let events: [String: EventsConfig] + let isEnableRealtimeThroughOptistream: Bool - public init( + init( tenantID: Int, realtimeGateway: URL, events: [String: EventsConfig], @@ -65,15 +65,15 @@ public struct RealtimeConfig: Codable, TenantInfo, EventInfo { } } -public struct OptitrackConfig: Codable, TenantInfo, EventInfo { - public let tenantID: Int - public let optitrackEndpoint: URL - public let enableAdvertisingIdReport: Bool - public let eventCategoryName: String - public let events: [String: EventsConfig] - public let isEnableRealtime: Bool +struct OptitrackConfig: Codable, TenantInfo, EventInfo { + let tenantID: Int + let optitrackEndpoint: URL + let enableAdvertisingIdReport: Bool + let eventCategoryName: String + let events: [String: EventsConfig] + let isEnableRealtime: Bool - public init( + init( tenantID: Int, optitrackEndpoint: URL, enableAdvertisingIdReport: Bool, @@ -90,10 +90,10 @@ public struct OptitrackConfig: Codable, TenantInfo, EventInfo { } } -public protocol TenantInfo { +protocol TenantInfo { var tenantID: Int { get } } -public protocol EventInfo { +protocol EventInfo { var events: [String: EventsConfig] { get } } diff --git a/OptimoveSDK/Sources/Classes/Configuration/ConfigurationBuilder.swift b/OptimoveSDK/Sources/Classes/Configuration/ConfigurationBuilder.swift index ea588f53..8cea0bc9 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/ConfigurationBuilder.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/ConfigurationBuilder.swift @@ -2,13 +2,13 @@ import Foundation -public final class ConfigurationBuilder { - private let globalConfig: GlobalConfig - private let tenantConfig: TenantConfig - private let events: [String: EventsConfig] +final class ConfigurationBuilder { + let globalConfig: GlobalConfig + let tenantConfig: TenantConfig + let events: [String: EventsConfig] - public init(globalConfig: GlobalConfig, - tenantConfig: TenantConfig) + init(globalConfig: GlobalConfig, + tenantConfig: TenantConfig) { self.globalConfig = globalConfig self.tenantConfig = tenantConfig @@ -18,7 +18,7 @@ public final class ConfigurationBuilder { ) } - public func build() -> Configuration { + func build() -> Configuration { return Configuration( tenantID: tenantConfig.optitrack.siteId, logger: buildLoggerConfig(), @@ -31,7 +31,7 @@ public final class ConfigurationBuilder { } } -private extension ConfigurationBuilder { +extension ConfigurationBuilder { func buildLoggerConfig() -> LoggerConfig { return LoggerConfig( tenantID: tenantConfig.optitrack.siteId, diff --git a/OptimoveSDK/Sources/Classes/Configuration/Global/GlobalConfig.swift b/OptimoveSDK/Sources/Classes/Configuration/Global/GlobalConfig.swift index 96e87ec6..24df81da 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Global/GlobalConfig.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Global/GlobalConfig.swift @@ -4,14 +4,14 @@ import Foundation // MARK: - GlobalConfig -public struct GlobalConfig: Codable, Equatable { - public let general: GlobalGeneralConfig - public let optitrack: GlobalOptitrackConfig - public let coreEvents: [String: EventsConfig] +struct GlobalConfig: Codable, Equatable { + let general: GlobalGeneralConfig + let optitrack: GlobalOptitrackConfig + let coreEvents: [String: EventsConfig] - public init(general: GlobalGeneralConfig, - optitrack: GlobalOptitrackConfig, - coreEvents: [String: EventsConfig]) + init(general: GlobalGeneralConfig, + optitrack: GlobalOptitrackConfig, + coreEvents: [String: EventsConfig]) { self.general = general self.optitrack = optitrack @@ -24,14 +24,14 @@ public struct GlobalConfig: Codable, Equatable { case coreEvents = "core_events" } - public init(from decoder: Decoder) throws { + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) general = try container.decode(GlobalGeneralConfig.self, forKey: .general) optitrack = try container.decode(GlobalOptitrackConfig.self, forKey: .optitrack) coreEvents = try container.decode([String: EventsConfig].self, forKey: .coreEvents) } - public func encode(to encoder: Encoder) throws { + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(general, forKey: .general) try container.encode(optitrack, forKey: .optitrack) @@ -41,10 +41,10 @@ public struct GlobalConfig: Codable, Equatable { // MARK: - General -public struct GlobalGeneralConfig: Codable, Equatable { - public let logsServiceEndpoint: URL +struct GlobalGeneralConfig: Codable, Equatable { + let logsServiceEndpoint: URL - public init(logsServiceEndpoint: URL) { + init(logsServiceEndpoint: URL) { self.logsServiceEndpoint = logsServiceEndpoint } @@ -55,10 +55,10 @@ public struct GlobalGeneralConfig: Codable, Equatable { // MARK: - Optitrack -public struct GlobalOptitrackConfig: Codable, Equatable { - public let eventCategoryName: String +struct GlobalOptitrackConfig: Codable, Equatable { + let eventCategoryName: String - public init(eventCategoryName: String) { + init(eventCategoryName: String) { self.eventCategoryName = eventCategoryName } diff --git a/OptimoveSDK/Sources/Classes/Configuration/RemoteConfiguration/RemoteConfigurationRequestBuilder.swift b/OptimoveSDK/Sources/Classes/Configuration/RemoteConfiguration/RemoteConfigurationRequestBuilder.swift index 35b57f68..8ceaaf4c 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/RemoteConfiguration/RemoteConfigurationRequestBuilder.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/RemoteConfiguration/RemoteConfigurationRequestBuilder.swift @@ -3,7 +3,7 @@ import Foundation import OptimoveCore -public final class RemoteConfigurationRequestBuilder { +final class RemoteConfigurationRequestBuilder { enum Error: LocalizedError { case failedToCreateTenantConfigurationRequest(Swift.Error) @@ -21,11 +21,11 @@ public final class RemoteConfigurationRequestBuilder { private let storage: OptimoveStorage - public init(storage: OptimoveStorage) { + init(storage: OptimoveStorage) { self.storage = storage } - public func createTenantConfigurationsRequest() throws -> NetworkRequest { + func createTenantConfigurationsRequest() throws -> NetworkRequest { do { let tenantToken = try storage.getTenantToken() let version = try storage.getVersion() @@ -41,7 +41,7 @@ public final class RemoteConfigurationRequestBuilder { } } - public func createGlobalConfigurationsRequest() -> NetworkRequest { + func createGlobalConfigurationsRequest() -> NetworkRequest { let url = Endpoints.Remote.GlobalConfig.url Logger.debug("Connect to \(url.absoluteString) to retreive global file.") return NetworkRequest(method: .get, baseURL: url, timeoutInterval: Constants.timeout) diff --git a/OptimoveSDK/Sources/Classes/Configuration/Tenant/EventConfig.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/EventConfig.swift index ff264943..07f4954e 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Tenant/EventConfig.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Tenant/EventConfig.swift @@ -1,12 +1,12 @@ // Copyright © 2017 Optimove. All rights reserved. -public struct EventsConfig: Codable, Equatable { - public let id: Int - public let supportedOnOptitrack: Bool - public let supportedOnRealTime: Bool - public let parameters: [String: Parameter] +struct EventsConfig: Codable, Equatable { + let id: Int + let supportedOnOptitrack: Bool + let supportedOnRealTime: Bool + let parameters: [String: Parameter] - public init( + init( id: Int, supportedOnOptitrack: Bool, supportedOnRealTime: Bool, diff --git a/OptimoveSDK/Sources/Classes/Configuration/Tenant/OptitrackMetaData.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/OptitrackMetaData.swift index ae08a7bf..5f2898fd 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Tenant/OptitrackMetaData.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Tenant/OptitrackMetaData.swift @@ -2,11 +2,11 @@ import Foundation -public struct TenantOptitrackConfig: Codable, Equatable { - public var optitrackEndpoint: URL - public var siteId: Int +struct TenantOptitrackConfig: Codable, Equatable { + var optitrackEndpoint: URL + var siteId: Int - public init( + init( optitrackEndpoint: URL, siteId: Int ) { diff --git a/OptimoveSDK/Sources/Classes/Configuration/Tenant/Parameter.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/Parameter.swift index 5d842c5e..c90f5149 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Tenant/Parameter.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Tenant/Parameter.swift @@ -1,10 +1,10 @@ // Copyright © 2017 Optimove. All rights reserved. -public struct Parameter: Codable, Equatable { - public let type: String - public let optional: Bool +struct Parameter: Codable, Equatable { + let type: String + let optional: Bool - public init( + init( type: String, optional: Bool ) { @@ -12,7 +12,7 @@ public struct Parameter: Codable, Equatable { self.optional = optional } - public var mandatory: Bool { + var mandatory: Bool { return !optional } diff --git a/OptimoveSDK/Sources/Classes/Configuration/Tenant/RealtimeMetaData.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/RealtimeMetaData.swift index e473dcf1..8126d6af 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Tenant/RealtimeMetaData.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Tenant/RealtimeMetaData.swift @@ -2,10 +2,10 @@ import Foundation -public struct TenantRealtimeConfig: Codable, Equatable { - public var realtimeGateway: URL +struct TenantRealtimeConfig: Codable, Equatable { + var realtimeGateway: URL - public init(realtimeGateway: URL) { + init(realtimeGateway: URL) { self.realtimeGateway = realtimeGateway } } diff --git a/OptimoveSDK/Sources/Classes/Configuration/Tenant/TenantConfig.swift b/OptimoveSDK/Sources/Classes/Configuration/Tenant/TenantConfig.swift index 88b251f8..1cd5a2ec 100644 --- a/OptimoveSDK/Sources/Classes/Configuration/Tenant/TenantConfig.swift +++ b/OptimoveSDK/Sources/Classes/Configuration/Tenant/TenantConfig.swift @@ -1,15 +1,15 @@ // Copyright © 2017 Optimove. All rights reserved. -public struct TenantConfig: Codable, Equatable { - public let isSupportedAirship: Bool - public let isEnableRealtime: Bool - public let isEnableRealtimeThroughOptistream: Bool - public let isProductionLogsEnabled: Bool - public let realtime: TenantRealtimeConfig - public var optitrack: TenantOptitrackConfig - public let events: [String: EventsConfig] +struct TenantConfig: Codable, Equatable { + let isSupportedAirship: Bool + let isEnableRealtime: Bool + let isEnableRealtimeThroughOptistream: Bool + let isProductionLogsEnabled: Bool + let realtime: TenantRealtimeConfig + var optitrack: TenantOptitrackConfig + let events: [String: EventsConfig] - public init( + init( realtime: TenantRealtimeConfig, optitrack: TenantOptitrackConfig, events: [String: EventsConfig], @@ -27,7 +27,7 @@ public struct TenantConfig: Codable, Equatable { self.isProductionLogsEnabled = isProductionLogsEnabled } - public init(from decoder: Decoder) throws { + init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) isSupportedAirship = try container.decodeIfPresent(Bool.self, forKey: .supportAirship) ?? false isEnableRealtime = try container.decode(Bool.self, forKey: .enableRealtime) @@ -38,7 +38,7 @@ public struct TenantConfig: Codable, Equatable { events = try container.decode([String: EventsConfig].self, forKey: .events) } - public func encode(to encoder: Encoder) throws { + func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encodeIfPresent(isSupportedAirship, forKey: .supportAirship) try container.encodeIfPresent(isProductionLogsEnabled, forKey: .prodLogsEnabled) diff --git a/OptimoveSDK/Sources/Classes/DataStructs/RingBuffer.swift b/OptimoveSDK/Sources/Classes/DataStructs/RingBuffer.swift index 530c7241..55ad7e7f 100644 --- a/OptimoveSDK/Sources/Classes/DataStructs/RingBuffer.swift +++ b/OptimoveSDK/Sources/Classes/DataStructs/RingBuffer.swift @@ -2,18 +2,18 @@ import Foundation -public struct RingBuffer { +struct RingBuffer { private var array: [T?] private var readIndex = 0 private var writeIndex = 0 - public init(count: Int) { + init(count: Int) { array = [T?](repeating: nil, count: count) } /* Returns false if out of space. */ @discardableResult - public mutating func write(_ element: T) -> Bool { + mutating func write(_ element: T) -> Bool { guard !isFull else { return false } defer { writeIndex += 1 @@ -23,7 +23,7 @@ public struct RingBuffer { } /* Returns nil if the buffer is empty. */ - public mutating func read() -> T? { + mutating func read() -> T? { guard !isEmpty else { return nil } defer { array[wrapped: readIndex] = nil @@ -36,7 +36,7 @@ public struct RingBuffer { return writeIndex - readIndex } - public var isEmpty: Bool { + var isEmpty: Bool { return availableSpaceForReading == 0 } @@ -44,13 +44,13 @@ public struct RingBuffer { return array.count - availableSpaceForReading } - public var isFull: Bool { + var isFull: Bool { return availableSpaceForWriting == 0 } } extension RingBuffer: Sequence { - public func makeIterator() -> AnyIterator { + func makeIterator() -> AnyIterator { var index = readIndex return AnyIterator { guard index < self.writeIndex else { return nil } diff --git a/OptimoveSDK/Sources/Classes/Events/Event.swift b/OptimoveSDK/Sources/Classes/Events/Event.swift index cfb11bd2..dfa0ce9a 100644 --- a/OptimoveSDK/Sources/Classes/Events/Event.swift +++ b/OptimoveSDK/Sources/Classes/Events/Event.swift @@ -2,18 +2,18 @@ import Foundation -open class Event { - public static let category = "track" +class Event { + static let category = "track" - public let eventId: UUID - public let requestId: String - public let name: String - public let timestamp: Date - public let category: String - public var context: [String: Any] - public var isRealtime: Bool + let eventId: UUID + let requestId: String + let name: String + let timestamp: Date + let category: String + var context: [String: Any] + var isRealtime: Bool - public init( + init( eventId: UUID? = nil, requestId: String? = nil, name: String, diff --git a/OptimoveCore/Sources/Classes/Extension/Bundle+AppVersion.swift b/OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift similarity index 88% rename from OptimoveCore/Sources/Classes/Extension/Bundle+AppVersion.swift rename to OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift index 199b21ef..6970cb20 100644 --- a/OptimoveCore/Sources/Classes/Extension/Bundle+AppVersion.swift +++ b/OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift @@ -2,7 +2,7 @@ import Foundation -public extension Bundle { +extension Bundle { var appVersion: String { return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "undefined" } diff --git a/OptimoveSDK/Sources/Classes/Extensions/Notification.swift b/OptimoveSDK/Sources/Classes/Extensions/Notification.swift index 3e4ff19f..10adc460 100644 --- a/OptimoveSDK/Sources/Classes/Extensions/Notification.swift +++ b/OptimoveSDK/Sources/Classes/Extensions/Notification.swift @@ -2,6 +2,7 @@ import Foundation -public extension Notification.Name { +extension Notification.Name { static let internetStatusChanged = Notification.Name("internetStatusChanged") + static let optimobileInializationFinished = Notification.Name("optimobileInializationFinished") } diff --git a/OptimoveSDK/Sources/Classes/JSON/Initialization.swift b/OptimoveSDK/Sources/Classes/JSON/Initialization.swift index 653ab1ae..ed11c86c 100644 --- a/OptimoveSDK/Sources/Classes/JSON/Initialization.swift +++ b/OptimoveSDK/Sources/Classes/JSON/Initialization.swift @@ -26,7 +26,7 @@ import Foundation private struct InitializationError: Error {} -public extension JSON { +extension JSON { /// Create a JSON value from anything. /// /// Argument has to be a valid JSON structure: A `Double`, `Int`, `String`, @@ -59,7 +59,7 @@ public extension JSON { } } -public extension JSON { +extension JSON { /// Create a JSON value from an `Encodable`. This will give you access to the “raw” /// encoded JSON value the `Encodable` is serialized into. init(encodable: T) throws { @@ -69,25 +69,25 @@ public extension JSON { } extension JSON: ExpressibleByBooleanLiteral { - public init(booleanLiteral value: Bool) { + init(booleanLiteral value: Bool) { self = .bool(value) } } extension JSON: ExpressibleByNilLiteral { - public init(nilLiteral _: ()) { + init(nilLiteral _: ()) { self = .null } } extension JSON: ExpressibleByArrayLiteral { - public init(arrayLiteral elements: JSON...) { + init(arrayLiteral elements: JSON...) { self = .array(elements) } } extension JSON: ExpressibleByDictionaryLiteral { - public init(dictionaryLiteral elements: (String, JSON)...) { + init(dictionaryLiteral elements: (String, JSON)...) { var object: [String: JSON] = [:] for (k, v) in elements { object[k] = v @@ -97,19 +97,19 @@ extension JSON: ExpressibleByDictionaryLiteral { } extension JSON: ExpressibleByFloatLiteral { - public init(floatLiteral value: Double) { + init(floatLiteral value: Double) { self = .number(value) } } extension JSON: ExpressibleByIntegerLiteral { - public init(integerLiteral value: Int) { + init(integerLiteral value: Int) { self = .number(Double(value)) } } extension JSON: ExpressibleByStringLiteral { - public init(stringLiteral value: String) { + init(stringLiteral value: String) { self = .string(value) } } diff --git a/OptimoveSDK/Sources/Classes/JSON/JSON.swift b/OptimoveSDK/Sources/Classes/JSON/JSON.swift index 9fd3ef33..5cd4fb5b 100644 --- a/OptimoveSDK/Sources/Classes/JSON/JSON.swift +++ b/OptimoveSDK/Sources/Classes/JSON/JSON.swift @@ -28,7 +28,7 @@ import Foundation /// for JSON values, since it makes sure only valid JSON values are present & supports `Equatable` /// and `Codable`, so that you can compare values for equality and code and decode them into data /// or strings. -@dynamicMemberLookup public enum JSON: Equatable { +@dynamicMemberLookup enum JSON: Equatable { case string(String) case number(Double) case object([String: JSON]) @@ -38,7 +38,7 @@ import Foundation } extension JSON: Codable { - public func encode(to encoder: Encoder) throws { + func encode(to encoder: Encoder) throws { var container = encoder.singleValueContainer() switch self { @@ -57,7 +57,7 @@ extension JSON: Codable { } } - public init(from decoder: Decoder) throws { + init(from decoder: Decoder) throws { let container = try decoder.singleValueContainer() if let object = try? container.decode([String: JSON].self) { @@ -81,7 +81,7 @@ extension JSON: Codable { } extension JSON: CustomDebugStringConvertible { - public var debugDescription: String { + var debugDescription: String { switch self { case let .string(str): return str.debugDescription diff --git a/OptimoveSDK/Sources/Classes/JSON/Merging.swift b/OptimoveSDK/Sources/Classes/JSON/Merging.swift index 48fa473f..a3ed3c79 100644 --- a/OptimoveSDK/Sources/Classes/JSON/Merging.swift +++ b/OptimoveSDK/Sources/Classes/JSON/Merging.swift @@ -24,7 +24,7 @@ import Foundation -public extension JSON { +extension JSON { /// Return a new JSON value by merging two other ones /// /// If we call the current JSON value `old` and the incoming JSON value diff --git a/OptimoveSDK/Sources/Classes/JSON/Querying.swift b/OptimoveSDK/Sources/Classes/JSON/Querying.swift index 3464dde5..0d6d49d1 100644 --- a/OptimoveSDK/Sources/Classes/JSON/Querying.swift +++ b/OptimoveSDK/Sources/Classes/JSON/Querying.swift @@ -24,7 +24,7 @@ import Foundation -public extension JSON { +extension JSON { /// Return the string value if this is a `.string`, otherwise `nil` var stringValue: String? { if case let .string(value) = self { diff --git a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkClient.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkClient.swift index 1448e65c..42ba1fb8 100644 --- a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkClient.swift +++ b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkClient.swift @@ -2,22 +2,22 @@ import Foundation -public typealias NetworkServiceCompletion = (Result, NetworkError>) -> Void +typealias NetworkServiceCompletion = (Result, NetworkError>) -> Void -public protocol NetworkClient { +protocol NetworkClient { func perform(_ request: NetworkRequest, _ completion: @escaping NetworkServiceCompletion) } -public struct NetworkClientImpl { +struct NetworkClientImpl { let session: URLSession - public init(configuration: URLSessionConfiguration = URLSessionConfiguration.default) { + init(configuration: URLSessionConfiguration = URLSessionConfiguration.default) { session = URLSession(configuration: configuration) } } extension NetworkClientImpl: NetworkClient { - public func perform(_ request: NetworkRequest, _ completion: @escaping NetworkServiceCompletion) { + func perform(_ request: NetworkRequest, _ completion: @escaping NetworkServiceCompletion) { let baseURL: URL = request.baseURL var urlComponents = URLComponents() diff --git a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkError.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkError.swift index a60e7cee..dec3f5a9 100644 --- a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkError.swift +++ b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkError.swift @@ -2,14 +2,14 @@ import Foundation -public enum NetworkError: LocalizedError { +enum NetworkError: LocalizedError { case error(Error) case noData case invalidURL case requestInvalid(Data?) case requestFailed - public var errorDescription: String? { + var errorDescription: String? { return "NetworkError: " + { switch self { case let .error(error): diff --git a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkRequest.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkRequest.swift index a525c872..97703acf 100644 --- a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkRequest.swift +++ b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkRequest.swift @@ -2,26 +2,26 @@ import Foundation -public final class NetworkRequest { - public enum DefaultValue { - public static let path: String? = nil - public static let headers: [HTTPHeader] = [] - public static let queryItems: [URLQueryItem]? = nil - public static let httpBody: Data? = nil - public static let timeoutInterval: TimeInterval = 60 - public static let keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys +final class NetworkRequest { + enum DefaultValue { + static let path: String? = nil + static let headers: [HTTPHeader] = [] + static let queryItems: [URLQueryItem]? = nil + static let httpBody: Data? = nil + static let timeoutInterval: TimeInterval = 60 + static let keyEncodingStrategy: KeyEncodingStrategy = .useDefaultKeys } - public let method: HTTPMethod - public let baseURL: URL - public let path: String? - public let headers: [HTTPHeader]? - public let queryItems: [URLQueryItem]? - public let httpBody: Data? - public let timeoutInterval: TimeInterval - public let keyEncodingStrategy: KeyEncodingStrategy + let method: HTTPMethod + let baseURL: URL + let path: String? + let headers: [HTTPHeader]? + let queryItems: [URLQueryItem]? + let httpBody: Data? + let timeoutInterval: TimeInterval + let keyEncodingStrategy: KeyEncodingStrategy - public required init( + required init( method: HTTPMethod, baseURL: URL, path: String? = DefaultValue.path, @@ -41,7 +41,7 @@ public final class NetworkRequest { self.keyEncodingStrategy = keyEncodingStrategy } - public convenience init( + convenience init( method: HTTPMethod, baseURL: URL, path: String? = DefaultValue.path, @@ -66,7 +66,7 @@ public final class NetworkRequest { } } -public enum HTTPMethod: String { +enum HTTPMethod: String { case get = "GET" case put = "PUT" case post = "POST" @@ -78,12 +78,12 @@ public enum HTTPMethod: String { case patch = "PATCH" } -public struct HTTPHeader { - public let field: String - public let value: String +struct HTTPHeader { + let field: String + let value: String } -public extension HTTPHeader { +extension HTTPHeader { enum Fields: String { case contentType = "Content-Type" case userAgent = "User-Agent" @@ -94,7 +94,7 @@ public extension HTTPHeader { } } -public extension HTTPHeader { +extension HTTPHeader { init(field: Fields, value: Values) { self.field = field.rawValue self.value = value.rawValue @@ -102,13 +102,13 @@ public extension HTTPHeader { } extension HTTPHeader: CustomStringConvertible { - public var description: String { + var description: String { return "key: \(field), value: \(value)" } } extension NetworkRequest: CustomStringConvertible { - public var description: String { + var description: String { return """ [Method]: \(method.rawValue) [URL]: \(baseURL.absoluteString) @@ -121,7 +121,7 @@ extension NetworkRequest: CustomStringConvertible { } } -public enum KeyEncodingStrategy { +enum KeyEncodingStrategy { case useDefaultKeys case convertToSnakeCase case custom(([CodingKey]) -> CodingKey) diff --git a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResponse.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResponse.swift index 61e0cb0b..6140e54b 100644 --- a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResponse.swift +++ b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResponse.swift @@ -2,12 +2,12 @@ import Foundation -public struct NetworkResponse { +struct NetworkResponse { let statusCode: Int let body: Body } -public extension NetworkResponse where Body == Data? { +extension NetworkResponse where Body == Data? { func decode(to _: BodyType.Type) throws -> BodyType { let data = try unwrap() return try JSONDecoder().decode(BodyType.self, from: data) diff --git a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResult.swift b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResult.swift index 761b8c3f..330f6a27 100644 --- a/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResult.swift +++ b/OptimoveSDK/Sources/Classes/NetworkClient/NetworkResult.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -public enum NetworkResult { +enum NetworkResult { case success(NetworkResponse) case failure(NetworkError) } diff --git a/OptimoveSDK/Sources/Classes/Operations/AsyncOperation.swift b/OptimoveSDK/Sources/Classes/Operations/AsyncOperation.swift index 2fdeb3fe..3c845159 100644 --- a/OptimoveSDK/Sources/Classes/Operations/AsyncOperation.swift +++ b/OptimoveSDK/Sources/Classes/Operations/AsyncOperation.swift @@ -2,8 +2,8 @@ import Foundation -open class AsyncOperation: Operation { - public enum State: String { +class AsyncOperation: Operation { + enum State: String { case waiting = "isWaiting" case ready = "isReady" case executing = "isExecuting" @@ -11,7 +11,7 @@ open class AsyncOperation: Operation { case cancelled = "isCancelled" } - public var state = State.waiting { + var state = State.waiting { willSet { willChangeValue(forKey: State.ready.rawValue) willChangeValue(forKey: State.executing.rawValue) @@ -79,12 +79,12 @@ open class AsyncOperation: Operation { } } -open class AsyncBlockOperation: AsyncOperation { - public typealias Closure = (AsyncBlockOperation) -> Void +class AsyncBlockOperation: AsyncOperation { + typealias Closure = (AsyncBlockOperation) -> Void - public let closure: Closure + let closure: Closure - public init(closure: @escaping Closure) { + init(closure: @escaping Closure) { self.closure = closure } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Credentials.swift b/OptimoveSDK/Sources/Classes/Optimobile/Credentials.swift new file mode 100644 index 00000000..77801e59 --- /dev/null +++ b/OptimoveSDK/Sources/Classes/Optimobile/Credentials.swift @@ -0,0 +1,11 @@ +// Copyright © 2023 Optimove. All rights reserved. + +struct OptimobileCredentials: Codable { + let apiKey: String + let secretKey: String + + init(apiKey: String, secretKey: String) { + self.apiKey = apiKey + self.secretKey = secretKey + } +} diff --git a/OptimoveSDK/Sources/Classes/Optimobile/CwlSysctl.swift b/OptimoveSDK/Sources/Classes/Optimobile/CwlSysctl.swift index 1f535974..9bca2d77 100755 --- a/OptimoveSDK/Sources/Classes/Optimobile/CwlSysctl.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/CwlSysctl.swift @@ -20,7 +20,7 @@ import Foundation -public enum SysctlError: Error { +enum SysctlError: Error { case unknown case malformedUTF8 case invalidSize @@ -28,7 +28,7 @@ public enum SysctlError: Error { } /// Wrapper around `sysctl` that preflights and allocates an [Int8] for the result and throws a Swift error if anything goes wrong. -public func sysctl(levels: [Int32]) throws -> [Int8] { +func sysctl(levels: [Int32]) throws -> [Int8] { return try levels.withUnsafeBufferPointer { levelsPointer throws -> [Int8] in // Preflight the request to get the required data size var requiredSize = 0 @@ -51,7 +51,7 @@ public func sysctl(levels: [Int32]) throws -> [Int8] { } /// Generate an array of name levels (as can be used with the previous sysctl function) from a sysctl name string. -public func sysctlLevels(fromName: String) throws -> [Int32] { +func sysctlLevels(fromName: String) throws -> [Int32] { var levelsBufferSize = Int(CTL_MAXNAME) var levelsBuffer = [Int32](repeating: 0, count: levelsBufferSize) try levelsBuffer.withUnsafeMutableBufferPointer { (lbp: inout UnsafeMutableBufferPointer) throws in @@ -89,33 +89,33 @@ private func stringFromSysctl(levels: [Int32]) throws -> String { } /// Get an arbitrary sysctl value and interpret the bytes as a UTF8 string -public func sysctlString(levels: Int32...) throws -> String { +func sysctlString(levels: Int32...) throws -> String { return try stringFromSysctl(levels: levels) } /// Get an arbitrary sysctl value and interpret the bytes as a UTF8 string -public func sysctlString(name: String) throws -> String { +func sysctlString(name: String) throws -> String { return try stringFromSysctl(levels: sysctlLevels(fromName: name)) } /// Get an arbitrary sysctl value and cast it to an Int64 -public func sysctlInt(levels: Int32...) throws -> Int64 { +func sysctlInt(levels: Int32...) throws -> Int64 { return try intFromSysctl(levels: levels) } /// Get an arbitrary sysctl value and cast it to an Int64 -public func sysctlInt(name: String) throws -> Int64 { +func sysctlInt(name: String) throws -> Int64 { return try intFromSysctl(levels: sysctlLevels(fromName: name)) } -public enum Sysctl { +enum Sysctl { /// e.g. "MyComputer.local" (from System Preferences -> Sharing -> Computer Name) or /// "My-Name-iPhone" (from Settings -> General -> About -> Name) - public static var hostName: String { return try! sysctlString(levels: CTL_KERN, KERN_HOSTNAME) } + static var hostName: String { return try! sysctlString(levels: CTL_KERN, KERN_HOSTNAME) } /// e.g. "x86_64" or "N71mAP" /// NOTE: this is *corrected* on iOS devices to fetch hw.model - public static var machine: String { + static var machine: String { #if os(iOS) && !arch(x86_64) && !arch(i386) return try! sysctlString(levels: CTL_HW, HW_MODEL) #else @@ -125,7 +125,7 @@ public enum Sysctl { /// e.g. "MacPro4,1" or "iPhone8,1" /// NOTE: this is *corrected* on iOS devices to fetch hw.machine - public static var model: String { + static var model: String { #if os(iOS) && !arch(x86_64) && !arch(i386) return try! sysctlString(levels: CTL_HW, HW_MACHINE) #else @@ -134,29 +134,29 @@ public enum Sysctl { } /// e.g. "8" or "2" - public static var activeCPUs: Int64 { return try! sysctlInt(levels: CTL_HW, HW_AVAILCPU) } + static var activeCPUs: Int64 { return try! sysctlInt(levels: CTL_HW, HW_AVAILCPU) } /// e.g. "15.3.0" or "15.0.0" - public static var osRelease: String { return try! sysctlString(levels: CTL_KERN, KERN_OSRELEASE) } + static var osRelease: String { return try! sysctlString(levels: CTL_KERN, KERN_OSRELEASE) } /// e.g. 199506 or 199506 - public static var osRev: Int64 { return try! sysctlInt(levels: CTL_KERN, KERN_OSREV) } + static var osRev: Int64 { return try! sysctlInt(levels: CTL_KERN, KERN_OSREV) } /// e.g. "Darwin" or "Darwin" - public static var osType: String { return try! sysctlString(levels: CTL_KERN, KERN_OSTYPE) } + static var osType: String { return try! sysctlString(levels: CTL_KERN, KERN_OSTYPE) } /// e.g. "15D21" or "13D20" - public static var osVersion: String { return try! sysctlString(levels: CTL_KERN, KERN_OSVERSION) } + static var osVersion: String { return try! sysctlString(levels: CTL_KERN, KERN_OSVERSION) } /// e.g. "Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64" or /// "Darwin Kernel Version 15.0.0: Wed Dec 9 22:19:38 PST 2015; root:xnu-3248.31.3~2/RELEASE_ARM64_S8000" - public static var version: String { return try! sysctlString(levels: CTL_KERN, KERN_VERSION) } + static var version: String { return try! sysctlString(levels: CTL_KERN, KERN_VERSION) } #if os(OSX) /// e.g. 2659000000 (not available on iOS) - public static var cpuFreq: Int64 { return try! sysctlInt(name: "hw.cpufrequency") } + static var cpuFreq: Int64 { return try! sysctlInt(name: "hw.cpufrequency") } /// e.g. 25769803776 (not available on iOS) - public static var memSize: Int64 { return try! sysctlInt(levels: CTL_HW, HW_MEMSIZE) } + static var memSize: Int64 { return try! sysctlInt(levels: CTL_HW, HW_MEMSIZE) } #endif } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppModels.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppModels.swift index a2df1185..ec1525d8 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppModels.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppModels.swift @@ -3,13 +3,13 @@ import CoreData import Foundation -public enum InAppPresented: String { +enum InAppPresented: String { case IMMEDIATELY = "immediately" case NEXT_OPEN = "next-open" case NEVER = "never" } -class InAppMessageEntity: NSManagedObject { +final class InAppMessageEntity: NSManagedObject { @NSManaged var id: Int64 @NSManaged var updatedAt: NSDate @NSManaged var presentedWhen: String @@ -38,16 +38,16 @@ class InAppMessageEntity: NSManagedObject { } } -class InAppMessage: NSObject { - public internal(set) var id: Int64 - public internal(set) var updatedAt: NSDate - public internal(set) var content: NSDictionary - public internal(set) var data: NSDictionary? - public internal(set) var badgeConfig: NSDictionary? - public internal(set) var inboxConfig: NSDictionary? - public internal(set) var dismissedAt: NSDate? - public internal(set) var readAt: NSDate? - public internal(set) var sentAt: NSDate? +final class InAppMessage: NSObject { + private(set) var id: Int64 + private(set) var updatedAt: NSDate + private(set) var content: NSDictionary + private(set) var data: NSDictionary? + private(set) var badgeConfig: NSDictionary? + private(set) var inboxConfig: NSDictionary? + private(set) var dismissedAt: NSDate? + private(set) var readAt: NSDate? + private(set) var sentAt: NSDate? init(entity: InAppMessageEntity) { id = Int64(entity.id) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift index 0cc1bd48..4b2ee5a4 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift @@ -5,8 +5,8 @@ import OptimoveCore import UIKit public struct DeepLinkContent { - public let title: String? - public let description: String? + let title: String? + let description: String? } public struct DeepLink { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index 10db4778..15f124b7 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -9,6 +9,8 @@ import UserNotifications @available(iOS 10.0, *) public typealias OptimoveUNAuthorizationCheckedHandler = (UNAuthorizationStatus, Error?) -> Void +let KS_MESSAGE_TYPE_PUSH = 1 + extension Optimobile { /** Helper method for requesting the device token with alert, badge and sound permissions. diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index e4a3f252..8a5ea8b1 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -4,15 +4,15 @@ import CoreData import Foundation import OptimoveCore -public class InAppInboxItem { - public internal(set) var id: Int64 - public internal(set) var title: String - public internal(set) var subtitle: String - public internal(set) var availableFrom: Date? - public internal(set) var availableTo: Date? - public internal(set) var dismissedAt: Date? - public internal(set) var sentAt: Date - public internal(set) var data: NSDictionary? +class InAppInboxItem { + private(set) var id: Int64 + private(set) var title: String + private(set) var subtitle: String + private(set) var availableFrom: Date? + private(set) var availableTo: Date? + private(set) var dismissedAt: Date? + private(set) var sentAt: Date + private(set) var data: NSDictionary? private var readAt: Date? private var imagePath: String? @@ -41,7 +41,7 @@ public class InAppInboxItem { imagePath = inboxConfig["imagePath"] as? String } - public func isAvailable() -> Bool { + func isAvailable() -> Bool { if availableFrom != nil, availableFrom!.timeIntervalSinceNow > 0 { return false } else if availableTo != nil, availableTo!.timeIntervalSinceNow < 0 { @@ -51,15 +51,15 @@ public class InAppInboxItem { return true } - public func isRead() -> Bool { + func isRead() -> Bool { return readAt != nil } - public func getImageUrl() -> URL? { + func getImageUrl() -> URL? { return getImageUrl(width: InAppInboxItem.defaultImageWidth) } - public func getImageUrl(width: UInt) -> URL? { + func getImageUrl(width: UInt) -> URL? { if let imagePathNotNil = imagePath { return try? MediaHelper.getCompletePictureUrl( pictureUrlString: imagePathNotNil, @@ -71,18 +71,18 @@ public class InAppInboxItem { } } -public struct InAppInboxSummary { - public let totalCount: Int64 - public let unreadCount: Int64 +struct InAppInboxSummary { + let totalCount: Int64 + let unreadCount: Int64 } -public typealias InboxUpdatedHandlerBlock = () -> Void -public typealias InboxSummaryBlock = (InAppInboxSummary?) -> Void +typealias InboxUpdatedHandlerBlock = () -> Void +typealias InboxSummaryBlock = (InAppInboxSummary?) -> Void -public enum OptimoveInApp { +enum OptimoveInApp { private static var _inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock? - public static func updateConsent(forUser consentGiven: Bool) { + static func updateConsent(forUser consentGiven: Bool) { if Optimobile.inAppConsentStrategy != InAppConsentStrategy.explicitByUser { NSException(name: NSExceptionName(rawValue: "Optimobile: Invalid In-app consent strategy"), reason: "You can only manage in-app messaging consent when the feature is enabled and strategy is set to InAppConsentStrategyExplicitByUser", userInfo: nil).raise() @@ -92,15 +92,15 @@ public enum OptimoveInApp { Optimobile.sharedInstance.inAppManager.updateUserConsent(consentGiven: consentGiven) } - public static func setDisplayMode(mode: InAppDisplayMode) { + static func setDisplayMode(mode: InAppDisplayMode) { Optimobile.sharedInstance.inAppManager.presenter.setDisplayMode(mode) } - public static func getDisplayMode() -> InAppDisplayMode { + static func getDisplayMode() -> InAppDisplayMode { return Optimobile.sharedInstance.inAppManager.presenter.getDisplayMode() } - public static func getInboxItems() -> [InAppInboxItem] { + static func getInboxItems() -> [InAppInboxItem] { guard let context = Optimobile.sharedInstance.inAppManager.messagesContext else { return [] } @@ -140,7 +140,7 @@ public enum OptimoveInApp { return results } - public static func presentInboxMessage(item: InAppInboxItem) -> InAppMessagePresentationResult { + static func presentInboxMessage(item: InAppInboxItem) -> InAppMessagePresentationResult { if getDisplayMode() == .paused { return .PAUSED } @@ -154,11 +154,11 @@ public enum OptimoveInApp { return result ? InAppMessagePresentationResult.PRESENTED : InAppMessagePresentationResult.FAILED } - public static func deleteMessageFromInbox(item: InAppInboxItem) -> Bool { + static func deleteMessageFromInbox(item: InAppInboxItem) -> Bool { return Optimobile.sharedInstance.inAppManager.deleteMessageFromInbox(withId: item.id) } - public static func markAsRead(item: InAppInboxItem) -> Bool { + static func markAsRead(item: InAppInboxItem) -> Bool { if item.isRead() { return false } @@ -168,15 +168,15 @@ public enum OptimoveInApp { return res } - public static func markAllInboxItemsAsRead() -> Bool { + static func markAllInboxItemsAsRead() -> Bool { return Optimobile.sharedInstance.inAppManager.markAllInboxItemsAsRead() } - public static func setOnInboxUpdated(inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock?) { + static func setOnInboxUpdated(inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock?) { _inboxUpdatedHandlerBlock = inboxUpdatedHandlerBlock } - public static func getInboxSummaryAsync(inboxSummaryBlock: @escaping InboxSummaryBlock) { + static func getInboxSummaryAsync(inboxSummaryBlock: @escaping InboxSummaryBlock) { Optimobile.sharedInstance.inAppManager.readInboxSummary(inboxSummaryBlock: inboxSummaryBlock) } diff --git a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift index 6cfdc426..b3ece124 100644 --- a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift +++ b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift @@ -50,11 +50,11 @@ public struct OptimoveConfig { } } -@objc public class OptimoveTenantInfo: NSObject { - @objc public var tenantToken: String - @objc public var configName: String +class OptimoveTenantInfo: NSObject { + var tenantToken: String + var configName: String - @objc public init(tenantToken: String, configName: String) { + init(tenantToken: String, configName: String) { self.tenantToken = tenantToken self.configName = configName } diff --git a/OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift index 2b9154a5..d4bff2f0 100644 --- a/OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift +++ b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEvent.swift @@ -3,24 +3,24 @@ import Foundation import OptimoveCore -public struct OptistreamEvent: Codable { - public let tenant: Int - public let category: String - public let event: String - public let origin: String - public let customer: String? - public let visitor: String - public let timestamp: String - public let context: JSON - public var metadata: Metadata +struct OptistreamEvent: Codable { + let tenant: Int + let category: String + let event: String + let origin: String + let customer: String? + let visitor: String + let timestamp: String + let context: JSON + var metadata: Metadata - public struct Metadata: Codable, Hashable { - public var realtime: Bool - public var firstVisitorDate: Int64? - public let eventId: String - public let requestId: String - public let platform: String = "ios" - public let version: String = SDKVersion + struct Metadata: Codable, Hashable { + var realtime: Bool + var firstVisitorDate: Int64? + let eventId: String + let requestId: String + let platform: String = "ios" + let version: String = SDKVersion enum CodingKeys: String, CodingKey { case realtime @@ -31,7 +31,7 @@ public struct OptistreamEvent: Codable { case version = "sdk_version" } - public init( + init( realtime: Bool, firstVisitorDate: Int64?, eventId: String, @@ -44,7 +44,7 @@ public struct OptistreamEvent: Codable { } } - public init( + init( tenant: Int, category: String, event: String, @@ -68,7 +68,7 @@ public struct OptistreamEvent: Codable { } extension OptistreamEvent: Equatable { - public static func == (lhs: OptistreamEvent, rhs: OptistreamEvent) -> Bool { + static func == (lhs: OptistreamEvent, rhs: OptistreamEvent) -> Bool { return lhs.metadata.eventId == rhs.metadata.eventId } } diff --git a/OptimoveSDK/Sources/Classes/Optistream/OptistreamEventBuilder.swift b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEventBuilder.swift index f8fefb83..56454ca1 100644 --- a/OptimoveSDK/Sources/Classes/Optistream/OptistreamEventBuilder.swift +++ b/OptimoveSDK/Sources/Classes/Optistream/OptistreamEventBuilder.swift @@ -4,7 +4,7 @@ import Foundation /// Builds an Optistream event from internal event type. /// The `delivery_event` do not use this class in reason of memory consuption under Notification Service Extention. -public final class OptistreamEventBuilder { +final class OptistreamEventBuilder { enum Constants { enum Values { static let origin = "sdk" @@ -14,7 +14,7 @@ public final class OptistreamEventBuilder { private let tenantID: Int private let storage: OptimoveStorage - public init( + init( tenantID: Int, storage: OptimoveStorage ) { @@ -22,7 +22,7 @@ public final class OptistreamEventBuilder { self.storage = storage } - public func build(event: Event) throws -> OptistreamEvent { + func build(event: Event) throws -> OptistreamEvent { return try OptistreamEvent( tenant: tenantID, category: event.category, diff --git a/OptimoveSDK/Sources/Classes/Optistream/OptistreamNetworking.swift b/OptimoveSDK/Sources/Classes/Optistream/OptistreamNetworking.swift index 84173a50..b552612e 100644 --- a/OptimoveSDK/Sources/Classes/Optistream/OptistreamNetworking.swift +++ b/OptimoveSDK/Sources/Classes/Optistream/OptistreamNetworking.swift @@ -2,7 +2,7 @@ import Foundation -public protocol OptistreamNetworking { +protocol OptistreamNetworking { func send( events: [OptistreamEvent], path: String, @@ -15,11 +15,11 @@ public protocol OptistreamNetworking { ) } -public final class OptistreamNetworkingImpl { +final class OptistreamNetworkingImpl { private let networkClient: NetworkClient private let endpoint: URL - public init( + init( networkClient: NetworkClient, endpoint: URL ) { @@ -49,7 +49,7 @@ public final class OptistreamNetworkingImpl { } extension OptistreamNetworkingImpl: OptistreamNetworking { - public func send( + func send( events: [OptistreamEvent], path: String, completion: @escaping (Result) -> Void @@ -57,7 +57,7 @@ extension OptistreamNetworkingImpl: OptistreamNetworking { _send(events: events, path: path, completion: completion) } - public func send( + func send( events: [OptistreamEvent], completion: @escaping (Result) -> Void ) { diff --git a/OptimoveSDK/Sources/Classes/Repository/ConfigurationRepository.swift b/OptimoveSDK/Sources/Classes/Repository/ConfigurationRepository.swift index af951e62..79ebf762 100644 --- a/OptimoveSDK/Sources/Classes/Repository/ConfigurationRepository.swift +++ b/OptimoveSDK/Sources/Classes/Repository/ConfigurationRepository.swift @@ -2,7 +2,7 @@ import Foundation -public protocol ConfigurationRepository { +protocol ConfigurationRepository { func getConfiguration() throws -> Configuration func setConfiguration(_: Configuration) throws @@ -13,7 +13,7 @@ public protocol ConfigurationRepository { func saveTenant(_: TenantConfig) throws } -public final class ConfigurationRepositoryImpl { +final class ConfigurationRepositoryImpl { private enum Constants { static let fileExtension = ".json" enum Global { @@ -32,35 +32,35 @@ public final class ConfigurationRepositoryImpl { private let storage: OptimoveStorage - public init(storage: OptimoveStorage) { + init(storage: OptimoveStorage) { self.storage = storage } } extension ConfigurationRepositoryImpl: ConfigurationRepository { - public func getConfiguration() throws -> Configuration { + func getConfiguration() throws -> Configuration { return try storage.load(fileName: Constants.Configuration.fileName, isTemporary: true) } - public func setConfiguration(_ config: Configuration) throws { + func setConfiguration(_ config: Configuration) throws { try storage.save(data: config, toFileName: Constants.Configuration.fileName, isTemporary: true) } - public func getGlobal() throws -> GlobalConfig { + func getGlobal() throws -> GlobalConfig { return try storage.load(fileName: Constants.Global.fileName) } - public func saveGlobal(_ config: GlobalConfig) throws { + func saveGlobal(_ config: GlobalConfig) throws { try storage.save(data: config, toFileName: Constants.Global.fileName) } - public func getTenant() throws -> TenantConfig { + func getTenant() throws -> TenantConfig { let version = try storage.getVersion() let fileName = version + Constants.fileExtension return try storage.load(fileName: fileName, isTemporary: true) } - public func saveTenant(_ config: TenantConfig) throws { + func saveTenant(_ config: TenantConfig) throws { let version = try storage.getVersion() let fileName = version + Constants.fileExtension try storage.save(data: config, toFileName: fileName, isTemporary: true) diff --git a/OptimoveSDK/Sources/Classes/Storage/FileManager+Optimove.swift b/OptimoveSDK/Sources/Classes/Storage/FileManager+Optimove.swift index c0f6c3c2..d286f3d8 100644 --- a/OptimoveSDK/Sources/Classes/Storage/FileManager+Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Storage/FileManager+Optimove.swift @@ -4,7 +4,7 @@ import Foundation private var temporaryDirectoryURL: URL? -public extension FileManager { +extension FileManager { static func optimoveURL() throws -> URL { return try FileManager.default.url( for: .applicationSupportDirectory, diff --git a/OptimoveSDK/Sources/Classes/Storage/FileStorage.swift b/OptimoveSDK/Sources/Classes/Storage/FileStorage.swift index 2d5d24b8..8842d701 100644 --- a/OptimoveSDK/Sources/Classes/Storage/FileStorage.swift +++ b/OptimoveSDK/Sources/Classes/Storage/FileStorage.swift @@ -3,7 +3,7 @@ import Foundation import OptimoveCore -public protocol FileStorage { +protocol FileStorage { /// Check file if exist. /// /// - Parameters: @@ -61,7 +61,7 @@ public protocol FileStorage { func delete(fileName: String, isTemporary: Bool) throws } -public extension FileStorage { +extension FileStorage { func isExist(fileName: String) -> Bool { return isExist(fileName: fileName, isTemporary: false) } @@ -87,7 +87,7 @@ public extension FileStorage { } } -public final class FileStorageImpl { +final class FileStorageImpl { enum FileStorageError: Error { case unableToCreateDirectory case unableToSaveFile @@ -103,7 +103,7 @@ public final class FileStorageImpl { let persistentStorageURL: URL let temporaryStorageURL: URL - public init(persistentStorageURL: URL, temporaryStorageURL: URL) throws { + init(persistentStorageURL: URL, temporaryStorageURL: URL) throws { fileManager = FileManager.default self.persistentStorageURL = persistentStorageURL self.temporaryStorageURL = temporaryStorageURL @@ -125,13 +125,13 @@ public final class FileStorageImpl { } extension FileStorageImpl: FileStorage { - public func isExist(fileName: String, isTemporary: Bool) -> Bool { + func isExist(fileName: String, isTemporary: Bool) -> Bool { let url = getDirectory(isTemporary: isTemporary) let fileUrl = url.appendingPathComponent(fileName) return fileManager.fileExists(atPath: fileUrl.path) } - public func loadData(fileName: String, isTemporary: Bool) throws -> Data { + func loadData(fileName: String, isTemporary: Bool) throws -> Data { let fileUrl = getDirectory(isTemporary: isTemporary).appendingPathComponent(fileName) do { let contents = try unwrap(fileManager.contents(atPath: fileUrl.path)) @@ -143,17 +143,17 @@ extension FileStorageImpl: FileStorage { } } - public func load(fileName: String, isTemporary: Bool) throws -> T where T: Decodable, T: Encodable { + func load(fileName: String, isTemporary: Bool) throws -> T where T: Decodable, T: Encodable { let data = try loadData(fileName: fileName, isTemporary: isTemporary) return try JSONDecoder().decode(T.self, from: data) } - public func save(data: T, toFileName fileName: String, isTemporary: Bool) throws { + func save(data: T, toFileName fileName: String, isTemporary: Bool) throws { let data = try JSONEncoder().encode(data) try saveData(data: data, toFileName: fileName, isTemporary: isTemporary) } - public func saveData(data: Data, toFileName fileName: String, isTemporary: Bool) throws { + func saveData(data: Data, toFileName fileName: String, isTemporary: Bool) throws { do { let url = getDirectory(isTemporary: isTemporary) try fileManager.createDirectory(at: url, withIntermediateDirectories: true) @@ -169,7 +169,7 @@ extension FileStorageImpl: FileStorage { } } - public func delete(fileName: String, isTemporary: Bool) throws { + func delete(fileName: String, isTemporary: Bool) throws { do { let fileUrl = getDirectory(isTemporary: isTemporary).appendingPathComponent(fileName) try fileManager.removeItem(at: fileUrl) diff --git a/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift index 0c4b9509..ab63d8ff 100644 --- a/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift +++ b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift @@ -4,13 +4,13 @@ import Foundation import OptimoveCore /// Combined protocol for a convenince access to stored values and files. -public typealias OptimoveStorage = FileStorage & KeyValueStorage & StorageValue +typealias OptimoveStorage = FileStorage & KeyValueStorage & StorageValue // MARK: - StorageCase // MARK: - StorageKey -public enum StorageKey: String, CaseIterable { +enum StorageKey: String, CaseIterable { case installationID case customerID case configurationEndPoint @@ -38,7 +38,7 @@ public enum StorageKey: String, CaseIterable { // MARK: - StorageValue /// The protocol used as convenience accessor to storage values. -public protocol StorageValue { +protocol StorageValue { var installationID: String? { get set } var customerID: String? { get set } var configurationEndPoint: URL? { get set } @@ -77,22 +77,22 @@ public protocol StorageValue { } /// The protocol used for convenience implementation of any storage technology below this protocol. -public protocol KeyValueStorage { +protocol KeyValueStorage { func set(value: Any?, key: StorageKey) func value(for: StorageKey) -> Any? subscript(_: StorageKey) -> T? { get set } } extension UserDefaults: KeyValueStorage { - public func set(value: Any?, key: StorageKey) { + func set(value: Any?, key: StorageKey) { set(value, forKey: key.rawValue) } - public func value(for key: StorageKey) -> Any? { + func value(for key: StorageKey) -> Any? { return value(forKey: key.rawValue) } - public subscript(key: StorageKey) -> T? { + subscript(key: StorageKey) -> T? { get { return value(for: key) as? T } @@ -102,19 +102,19 @@ extension UserDefaults: KeyValueStorage { } } -public final class InMemoryStorage: KeyValueStorage { +final class InMemoryStorage: KeyValueStorage { private var storage = [StorageKey: Any]() private let queue = DispatchQueue(label: "com.optimove.sdk.inmemorystorage", attributes: .concurrent) - public init() {} + init() {} - public func set(value: Any?, key: StorageKey) { + func set(value: Any?, key: StorageKey) { queue.async(flags: .barrier) { [self] in storage[key] = value } } - public subscript(key: StorageKey) -> T? { + subscript(key: StorageKey) -> T? { get { var result: T? queue.sync { @@ -129,7 +129,7 @@ public final class InMemoryStorage: KeyValueStorage { } } - public func value(for key: StorageKey) -> Any? { + func value(for key: StorageKey) -> Any? { var result: Any? queue.sync { result = storage[key] @@ -138,10 +138,10 @@ public final class InMemoryStorage: KeyValueStorage { } } -public enum StorageError: LocalizedError { +enum StorageError: LocalizedError { case noValue(StorageKey) - public var errorDescription: String? { + var errorDescription: String? { switch self { case let .noValue(key): return "StorageError: No value for key \(key.rawValue)" @@ -150,12 +150,12 @@ public enum StorageError: LocalizedError { } /// Class implements the Façade pattern for hiding complexity of the OptimoveStorage protocol. -public final class StorageFacade: OptimoveStorage { +final class StorageFacade: OptimoveStorage { private let persistantStorage: KeyValueStorage private let inMemoryStorage: KeyValueStorage private let fileStorage: FileStorage - public init( + init( persistantStorage: KeyValueStorage, inMemoryStorage: KeyValueStorage, fileStorage: FileStorage @@ -175,7 +175,7 @@ public final class StorageFacade: OptimoveStorage { // MARK: - KeyValueStorage -public extension StorageFacade { +extension StorageFacade { /// Use `storage.key` instead. /// Some variable have formatters, implemented in own setters. Set unformatted value could cause an issue. func set(value: Any?, key: StorageKey) { @@ -207,7 +207,7 @@ public extension StorageFacade { // MARK: - FileStorage -public extension StorageFacade { +extension StorageFacade { func isExist(fileName: String, isTemporary: Bool) -> Bool { return fileStorage.isExist(fileName: fileName, isTemporary: isTemporary) } @@ -235,7 +235,7 @@ public extension StorageFacade { // MARK: - StorageValue -public extension KeyValueStorage where Self: StorageValue { +extension KeyValueStorage where Self: StorageValue { var installationID: String? { get { return self[.installationID] diff --git a/OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift b/OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift index 42f28bee..43e30cf9 100644 --- a/OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Storage/UserDefaults+Optimove.swift @@ -3,7 +3,7 @@ import Foundation import OptimoveCore -public extension UserDefaults { +extension UserDefaults { enum Constants { static let suiteName: String = "com.optimove.sdk" } diff --git a/OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift b/OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift index 6dd661f3..9bf0a3a8 100644 --- a/OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift +++ b/OptimoveSDK/Tests/Sources/Configurations/ConfigurationBuilderTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -import OptimoveSDK +@testable import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift b/OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift index 8fca81cc..a9d31e8d 100644 --- a/OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift +++ b/OptimoveSDK/Tests/Sources/Configurations/Global/GlobalConfigTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -import OptimoveSDK +@testable import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift b/OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift index 73656568..987f9dcf 100644 --- a/OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift +++ b/OptimoveSDK/Tests/Sources/Configurations/Tenant/TenantConfigTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -import OptimoveSDK +@testable import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift b/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift index 54dd34ee..f70ca926 100644 --- a/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift +++ b/OptimoveSDK/Tests/Sources/Mocks/MockConfigurationRepository.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -import OptimoveSDK +@testable import OptimoveSDK import OptimoveCore final class MockConfigurationRepository: ConfigurationRepository { diff --git a/OptimoveSDK/Tests/Sources/NetworkClientTests.swift b/OptimoveSDK/Tests/Sources/NetworkClientTests.swift index 455f765e..423f1f0a 100644 --- a/OptimoveSDK/Tests/Sources/NetworkClientTests.swift +++ b/OptimoveSDK/Tests/Sources/NetworkClientTests.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Mocker -import OptimoveSDK +@testable import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift b/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift index 6fe47d36..50b8721c 100644 --- a/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import OptimoveCore -import OptimoveSDK +@testable import OptimoveSDK import OptimoveTest import XCTest diff --git a/OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift b/OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift index 5c622f95..f41640fe 100644 --- a/OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/OptimoveFileManagerTests.swift @@ -1,6 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. -import OptimoveSDK +@testable import OptimoveSDK import XCTest final class MockedFileManager: FileManager { diff --git a/Shared/Sources/Fixture/TenantConfigFixture.swift b/Shared/Sources/Fixture/TenantConfigFixture.swift index b03b8200..ab2d1b6e 100644 --- a/Shared/Sources/Fixture/TenantConfigFixture.swift +++ b/Shared/Sources/Fixture/TenantConfigFixture.swift @@ -1,7 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import Foundation -import OptimoveSDK +@testable import OptimoveSDK public final class TenantConfigFixture { public init() {} diff --git a/Shared/Sources/Mocks/MockOptimoveStorage.swift b/Shared/Sources/Mocks/MockOptimoveStorage.swift index 7ad92a82..d0fa7a71 100644 --- a/Shared/Sources/Mocks/MockOptimoveStorage.swift +++ b/Shared/Sources/Mocks/MockOptimoveStorage.swift @@ -2,7 +2,7 @@ import Foundation import OptimoveCore -import OptimoveSDK +@testable import OptimoveSDK public final class MockOptimoveStorage: OptimoveStorage { public init() {} diff --git a/Shared/Sources/Mocks/OptistreamNetworkingMock.swift b/Shared/Sources/Mocks/OptistreamNetworkingMock.swift index 5aa116ad..ac76aa88 100644 --- a/Shared/Sources/Mocks/OptistreamNetworkingMock.swift +++ b/Shared/Sources/Mocks/OptistreamNetworkingMock.swift @@ -1,6 +1,6 @@ // Copyright © 2020 Optimove. All rights reserved. -import OptimoveSDK +@testable import OptimoveSDK public final class OptistreamNetworkingMock: OptistreamNetworking { public init() {} diff --git a/Shared/Sources/OptimoveTestCase.swift b/Shared/Sources/OptimoveTestCase.swift index 2fdb61aa..b4385b48 100644 --- a/Shared/Sources/OptimoveTestCase.swift +++ b/Shared/Sources/OptimoveTestCase.swift @@ -1,6 +1,7 @@ // Copyright © 2019 Optimove. All rights reserved. import OptimoveCore +@testable import OptimoveSDK import XCTest public let defaultTimeout: TimeInterval = 0.8 From 9a121c69be51c83449c9f965728c613f7d39a002 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 14:14:39 +0200 Subject: [PATCH 15/30] refactor: split storage facade to files --- .../Classes/Storage/KeyValueStorage.swift | 145 ++++++++++++++++++ .../Classes/Storage/OptimoveStorage.swift | 4 + .../Classes/Storage/StorageFacade.swift | 135 ---------------- 3 files changed, 149 insertions(+), 135 deletions(-) create mode 100644 OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift create mode 100644 OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift diff --git a/OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift b/OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift new file mode 100644 index 00000000..f43528ab --- /dev/null +++ b/OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift @@ -0,0 +1,145 @@ +// Copyright © 2023 Optimove. All rights reserved. + +import Foundation + +/// The enum used as keys for storage values. +enum StorageKey: String, CaseIterable { + case installationID + case customerID + case configurationEndPoint + case initialVisitorId + case tenantToken + case visitorID + case version + case userAgent + case deviceResolutionWidth + case deviceResolutionHeight + case advertisingIdentifier + case migrationVersions /// For storing a migration history + case firstRunTimestamp + case pushNotificationChannels + case optitrackEndpoint + case tenantID + case userEmail + case siteID /// Legacy: See tenantID + case settingUserSuccess + case firstVisitTimestamp /// Legacy + /// Kumulos + case region + case mediaURL + case installUUID + case userID + case badgeCount + case pendingNotifications + case pendingAnaltics + case inAppLastSyncedAt + case inAppMostRecentUpdateAt + case inAppConsented + case dynamicCategory + + static let inMemoryValues: Set = [.tenantToken, .version] + static let appGroup: Set = [.mediaURL, .dynamicCategory] +} + +/// The protocol used as convenience accessor to storage values. +protocol StorageValue { + var installationID: String? { get set } + var customerID: String? { get set } + var configurationEndPoint: URL? { get set } + var initialVisitorId: String? { get set } + var tenantToken: String? { get set } + var visitorID: String? { get set } + var version: String? { get set } + var userAgent: String? { get set } + var deviceResolutionWidth: Float? { get set } + var deviceResolutionHeight: Float? { get set } + var advertisingIdentifier: String? { get set } + var optitrackEndpoint: URL? { get set } + var tenantID: Int? { get set } + var userEmail: String? { get set } + /// Legacy: See tenantID + var siteID: Int? { get set } + var isSettingUserSuccess: Bool? { get set } + /// Legacy. Use `firstRunTimestamp` instead + var firstVisitTimestamp: Int64? { get set } + + func getConfigurationEndPoint() throws -> URL + func getCustomerID() throws -> String + func getInitialVisitorId() throws -> String + func getTenantToken() throws -> String + func getVisitorID() throws -> String + func getVersion() throws -> String + func getUserAgent() throws -> String + func getDeviceResolutionWidth() throws -> Float + func getDeviceResolutionHeight() throws -> Float + /// Called when a migration is finished for the version. + mutating func finishedMigration(to version: String) + /// Use for checking if a migration was applied for the version. + func isAlreadyMigrated(to version: String) -> Bool + func getUserEmail() throws -> String + func getSiteID() throws -> Int +} + +/// The protocol used for convenience implementation of any storage technology below this protocol. +protocol KeyValueStorage { + func set(value: Any?, key: StorageKey) + func value(for: StorageKey) -> Any? + subscript(_: StorageKey) -> T? { get set } +} + +/// ``UserDefaults`` uses as persistent ``KeyValueStorage``. +extension UserDefaults: KeyValueStorage { + func set(value: Any?, key: StorageKey) { + set(value, forKey: key.rawValue) + } + + func value(for key: StorageKey) -> Any? { + return value(forKey: key.rawValue) + } + + subscript(key: StorageKey) -> T? { + get { + return value(for: key) as? T + } + set { + set(value: newValue, key: key) + } + } +} + +/// ``InMemoryStorage`` uses as in-memory ``KeyValueStorage``. +final class InMemoryStorage: KeyValueStorage { + private var storage = [StorageKey: Any]() + private let queue = DispatchQueue(label: "com.optimove.sdk.inmemorystorage", attributes: .concurrent) + + init() {} + + func set(value: Any?, key: StorageKey) { + queue.async(flags: .barrier) { [self] in + storage[key] = value + } + } + + subscript(key: StorageKey) -> T? { + get { + var result: T? + queue.sync { + result = storage[key] as? T + } + return result + } + set { + queue.async(flags: .barrier) { [self] in + storage[key] = newValue + } + } + } + + func value(for key: StorageKey) -> Any? { + var result: Any? + queue.sync { + result = storage[key] + } + return result + } +} diff --git a/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift b/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift new file mode 100644 index 00000000..51107fcf --- /dev/null +++ b/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift @@ -0,0 +1,4 @@ +// Copyright © 2023 Optimove. All rights reserved. + +/// Combined protocol for a convenince access to stored values and files. +typealias OptimoveStorage = FileStorage & KeyValueStorage & StorageValue diff --git a/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift index ab63d8ff..750c5eea 100644 --- a/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift +++ b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift @@ -3,141 +3,6 @@ import Foundation import OptimoveCore -/// Combined protocol for a convenince access to stored values and files. -typealias OptimoveStorage = FileStorage & KeyValueStorage & StorageValue - -// MARK: - StorageCase - -// MARK: - StorageKey - -enum StorageKey: String, CaseIterable { - case installationID - case customerID - case configurationEndPoint - case initialVisitorId - case tenantToken - case visitorID - case version - case userAgent - case deviceResolutionWidth - case deviceResolutionHeight - case advertisingIdentifier - case migrationVersions /// For storing a migration history - case firstRunTimestamp - case pushNotificationChannels - case optitrackEndpoint - case tenantID - case userEmail - case siteID /// Legacy: See tenantID - case settingUserSuccess - case firstVisitTimestamp /// Legacy - - static let inMemoryValues: Set = [.tenantToken, .version] -} - -// MARK: - StorageValue - -/// The protocol used as convenience accessor to storage values. -protocol StorageValue { - var installationID: String? { get set } - var customerID: String? { get set } - var configurationEndPoint: URL? { get set } - var initialVisitorId: String? { get set } - var tenantToken: String? { get set } - var visitorID: String? { get set } - var version: String? { get set } - var userAgent: String? { get set } - var deviceResolutionWidth: Float? { get set } - var deviceResolutionHeight: Float? { get set } - var advertisingIdentifier: String? { get set } - var optitrackEndpoint: URL? { get set } - var tenantID: Int? { get set } - var userEmail: String? { get set } - /// Legacy: See tenantID - var siteID: Int? { get set } - var isSettingUserSuccess: Bool? { get set } - /// Legacy. Use `firstRunTimestamp` instead - var firstVisitTimestamp: Int64? { get set } - - func getConfigurationEndPoint() throws -> URL - func getCustomerID() throws -> String - func getInitialVisitorId() throws -> String - func getTenantToken() throws -> String - func getVisitorID() throws -> String - func getVersion() throws -> String - func getUserAgent() throws -> String - func getDeviceResolutionWidth() throws -> Float - func getDeviceResolutionHeight() throws -> Float - /// Called when a migration is finished for the version. - mutating func finishedMigration(to version: String) - /// Use for checking if a migration was applied for the version. - func isAlreadyMigrated(to version: String) -> Bool - func getUserEmail() throws -> String - func getSiteID() throws -> Int -} - -/// The protocol used for convenience implementation of any storage technology below this protocol. -protocol KeyValueStorage { - func set(value: Any?, key: StorageKey) - func value(for: StorageKey) -> Any? - subscript(_: StorageKey) -> T? { get set } -} - -extension UserDefaults: KeyValueStorage { - func set(value: Any?, key: StorageKey) { - set(value, forKey: key.rawValue) - } - - func value(for key: StorageKey) -> Any? { - return value(forKey: key.rawValue) - } - - subscript(key: StorageKey) -> T? { - get { - return value(for: key) as? T - } - set { - set(value: newValue, key: key) - } - } -} - -final class InMemoryStorage: KeyValueStorage { - private var storage = [StorageKey: Any]() - private let queue = DispatchQueue(label: "com.optimove.sdk.inmemorystorage", attributes: .concurrent) - - init() {} - - func set(value: Any?, key: StorageKey) { - queue.async(flags: .barrier) { [self] in - storage[key] = value - } - } - - subscript(key: StorageKey) -> T? { - get { - var result: T? - queue.sync { - result = storage[key] as? T - } - return result - } - set { - queue.async(flags: .barrier) { [self] in - storage[key] = newValue - } - } - } - - func value(for key: StorageKey) -> Any? { - var result: Any? - queue.sync { - result = storage[key] - } - return result - } -} - enum StorageError: LocalizedError { case noValue(StorageKey) From 615864e1316760d0fdeecabebc2573ef0cd54457 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 14:14:50 +0200 Subject: [PATCH 16/30] feat: add migration 5.7.0 --- .../Classes/OptimobileUserDefaultsKey.swift | 2 +- OptimoveSDK/Sources/Classes/DI/Assembly.swift | 1 + .../Classes/Migration/MigrationWork.swift | 62 ++++++++++++++++--- .../Sources/Classes/Migration/Version.swift | 1 + 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift b/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift index 4c290b0a..3fab605c 100644 --- a/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift +++ b/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift @@ -2,7 +2,7 @@ import Foundation -public enum OptimobileUserDefaultsKey: String { +public enum OptimobileUserDefaultsKey: String, CaseIterable { case REGION = "KumulosEventsRegion" case MEDIA_BASE_URL = "KumulosMediaBaseUrl" case INSTALL_UUID = "KumulosUUID" diff --git a/OptimoveSDK/Sources/Classes/DI/Assembly.swift b/OptimoveSDK/Sources/Classes/DI/Assembly.swift index 5b85aa56..777bf0ff 100644 --- a/OptimoveSDK/Sources/Classes/DI/Assembly.swift +++ b/OptimoveSDK/Sources/Classes/DI/Assembly.swift @@ -33,6 +33,7 @@ final class Assembly { private func migrate() { let migrations: [MigrationWork] = [ MigrationWork_3_3_0(), + MigrationWork_5_7_0(), ] migrations .filter { $0.isAllowToMiragte(SDKVersion) } diff --git a/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift b/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift index 4deddd58..573eafec 100644 --- a/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift +++ b/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift @@ -8,6 +8,10 @@ protocol MigrationWork { func runMigration() } +private protocol Replacer { + func replace() +} + class MigrationWorker: MigrationWork { let version: Version @@ -57,9 +61,10 @@ class MigrationWorkerWithStorage: MigrationWorker { final class MigrationWork_2_10_0: MigrationWorkerWithStorage { private let synchronizer: Pipeline - init(synchronizer: Pipeline, - storage: OptimoveStorage) - { + init( + synchronizer: Pipeline, + storage: OptimoveStorage + ) { self.synchronizer = synchronizer super.init(storage: storage, newVersion: .v_2_10_0) } @@ -108,10 +113,6 @@ final class MigrationWork_3_3_0: MigrationWorker { } } -private protocol Replacer { - func replace() -} - extension MigrationWork_3_3_0 { final class AppGroupReplacer: Replacer { func replace() { @@ -229,3 +230,50 @@ extension MigrationWork_3_3_0 { } } } + +/// Migration from Kumulos UserDefaults to Optimove UserDefaults. +final class MigrationWork_5_7_0: MigrationWorker { + init() { + super.init(newVersion: .v_5_7_0) + } + + override func isAllowToMiragte(_: String) -> Bool { + guard let storage = try? UserDefaults.optimove() else { + return true + } + let key = StorageKey.migrationVersions + let versions = storage.object(forKey: key.rawValue) as? [String] ?? [] + return !versions.contains(version.rawValue) + } + + override func runMigration() { + Logger.info("Migration from Kumulos UserDefaults to Optimove UserDefaults started") + let keyMapping: [OptimobileUserDefaultsKey: StorageKey] = [ + .REGION: .region, + .MEDIA_BASE_URL: .mediaURL, + .INSTALL_UUID: .installUUID, + .USER_ID: .userID, + .BADGE_COUNT: .badgeCount, + .PENDING_NOTIFICATIONS: .pendingNotifications, + .PENDING_ANALYTICS: .pendingAnaltics, + .IN_APP_LAST_SYNCED_AT: .inAppLastSyncedAt, + .IN_APP_MOST_RECENT_UPDATED_AT: .inAppMostRecentUpdateAt, + .IN_APP_CONSENTED: .inAppConsented, + .DYNAMIC_CATEGORY: .dynamicCategory, + // TODO: MIGRATED_TO_GROUPS + ] + /// Move values from Kumulos UserDefaults to Optimove UserDefaults. + let kumulosStorage = UserDefaults.standard + let optimoveStorage = try! UserDefaults.optimove() + OptimobileUserDefaultsKey.allCases.forEach { key in + if let value = kumulosStorage.object(forKey: key.rawValue), + let storageKey = keyMapping[key] + { + optimoveStorage.set(value: value, key: storageKey) + kumulosStorage.removeObject(forKey: key.rawValue) + } + } + Logger.info("Migration from Kumulos UserDefaults to Optimove UserDefaults completed") + super.runMigration() + } +} diff --git a/OptimoveSDK/Sources/Classes/Migration/Version.swift b/OptimoveSDK/Sources/Classes/Migration/Version.swift index e55b4653..c1c172ca 100644 --- a/OptimoveSDK/Sources/Classes/Migration/Version.swift +++ b/OptimoveSDK/Sources/Classes/Migration/Version.swift @@ -4,4 +4,5 @@ enum Version: String { case v_2_10_0 = "2.10.0" case v_3_0_0 = "3.0.0" case v_3_3_0 = "3.3.0" + case v_5_7_0 = "5.7.0" } From b4f1c8b7c9bf456ccbb43be4c5c5043011cd4287 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 16:51:47 +0200 Subject: [PATCH 17/30] feat: remove kumulos key-value storage --- .../Classes/Extension/Bundle+HostApp.swift | 15 +- .../Classes/KeyValPersistenceHelper.swift | 59 --- .../Sources/Classes/MediaHelper.swift | 12 +- .../Sources/Classes/OptimobileHelper.swift | 38 +- .../Classes/OptimobileUserDefaultsKey.swift | 3 - .../Classes/PendingNotificationHelper.swift | 24 +- .../Storage/FileManager+AppGroup.swift | 18 + .../Classes/Storage/KeyValueStorage.swift | 34 +- .../Storage/UserDefaults+AppGroup.swift | 18 + .../Tests/Sources/MediaHelperTests.swift | 15 +- .../Sources/OptimoveNotificationService.swift | 37 +- .../Classes/Migration/MigrationWork.swift | 1 - .../Classes/Optimobile/AnalyticsHelper.swift | 11 +- .../Optimobile/InApp/InAppManager.swift | 30 +- .../Optimobile/InApp/InAppPresenter.swift | 11 +- .../Optimobile/Network/UrlBuilder.swift | 6 +- .../Optimobile/Optimobile+Analytics.swift | 49 ++- .../Optimobile/Optimobile+DeepLinking.swift | 10 +- .../Classes/Optimobile/Optimobile+Push.swift | 18 +- .../Classes/Optimobile/Optimobile+Stats.swift | 2 +- .../Classes/Optimobile/Optimobile.swift | 121 +++--- .../Classes/Optimobile/OptimoveInApp.swift | 13 +- OptimoveSDK/Sources/Classes/Optimove.swift | 69 +-- .../Sources/Classes/OptimoveConfig.swift | 2 +- .../Classes/Storage/OptimoveStorage.swift | 398 +++++++++++++++++ .../Classes/Storage/StorageFacade.swift | 399 ------------------ .../Sources/OptimoveConfigBuilderTests.swift | 2 +- .../Storage/OptimoveStorageFacadeTests.swift | 1 + 28 files changed, 758 insertions(+), 658 deletions(-) delete mode 100644 OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift create mode 100644 OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift rename {OptimoveSDK => OptimoveCore}/Sources/Classes/Storage/KeyValueStorage.swift (82%) create mode 100644 OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift delete mode 100644 OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift diff --git a/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift b/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift index 6f0d73de..3f96df9b 100644 --- a/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift +++ b/OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift @@ -3,11 +3,12 @@ import Foundation extension Bundle { + /// Returns the bundle containing the host app. /// https://stackoverflow.com/a/27849695 - static func hostAppBundle() -> Bundle? { + static func hostAppBundle() -> Bundle { let mainBundle = Bundle.main if mainBundle.bundleURL.pathExtension == "appex" { - // Peel off two directory levels - SOME_APP.app/PlugIns/SOME_APP_EXTENSION.appex + // Peel off two directory levels - APP.app/PlugIns/APP_EXTENSION.appex let url = mainBundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent() if let hostBundle = Bundle(url: url) { return hostBundle @@ -15,4 +16,14 @@ extension Bundle { } return mainBundle } + + /// Returns the bundle identifier of the host app. + static var hostAppBundleIdentifier: String { + return hostAppBundle().bundleIdentifier! + } + + /// Returns the app group identifier for the SDK app. + static var optimoveAppGroupIdentifier: String { + return "group.\(hostAppBundleIdentifier).optimove" + } } diff --git a/OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift b/OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift deleted file mode 100644 index e3091138..00000000 --- a/OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright © 2022 Optimove. All rights reserved. - -import Foundation - -protocol KeyValPersistent { - static func set(_ value: Any?, forKey: String) - static func object(forKey: String) -> Any? - static func removeObject(forKey: String) -} - -public enum KeyValPersistenceHelper { - public static func maybeMigrateUserDefaultsToAppGroups() { - let standardDefaults = UserDefaults.standard - let haveMigratedKey: String = OptimobileUserDefaultsKey.MIGRATED_TO_GROUPS.rawValue - if !AppGroupsHelper.isKumulosAppGroupDefined() { - standardDefaults.set(false, forKey: haveMigratedKey) - return - } - - guard let groupDefaults = UserDefaults(suiteName: AppGroupsHelper.getKumulosGroupName()) else { return } - if groupDefaults.bool(forKey: haveMigratedKey), standardDefaults.bool(forKey: haveMigratedKey) { - return - } - - let defaultsAsDict: [String: Any] = standardDefaults.dictionaryRepresentation() - for key in OptimobileUserDefaultsKey.sharedKeys { - groupDefaults.set(defaultsAsDict[key.rawValue], forKey: key.rawValue) - } - - standardDefaults.set(true, forKey: haveMigratedKey) - groupDefaults.set(true, forKey: haveMigratedKey) - } - - fileprivate static func getUserDefaults() -> UserDefaults { - if !AppGroupsHelper.isKumulosAppGroupDefined() { - return UserDefaults.standard - } - - if let suiteUserDefaults = UserDefaults(suiteName: AppGroupsHelper.getKumulosGroupName()) { - return suiteUserDefaults - } - - return UserDefaults.standard - } -} - -extension KeyValPersistenceHelper: KeyValPersistent { - public static func set(_ value: Any?, forKey: String) { - getUserDefaults().set(value, forKey: forKey) - } - - public static func object(forKey: String) -> Any? { - return getUserDefaults().object(forKey: forKey) - } - - public static func removeObject(forKey: String) { - getUserDefaults().removeObject(forKey: forKey) - } -} diff --git a/OptimoveCore/Sources/Classes/MediaHelper.swift b/OptimoveCore/Sources/Classes/MediaHelper.swift index f9a1e575..161ec0cd 100644 --- a/OptimoveCore/Sources/Classes/MediaHelper.swift +++ b/OptimoveCore/Sources/Classes/MediaHelper.swift @@ -2,13 +2,19 @@ import Foundation -public enum MediaHelper { +public struct MediaHelper { enum Error: LocalizedError { case noMediaUrlFound case invalidPictureUrl(String) } - public static func getCompletePictureUrl(pictureUrlString: String, width: UInt) throws -> URL { + let storage: KeyValueStorage + + public init(storage: KeyValueStorage) { + self.storage = storage + } + + public func getCompletePictureUrl(pictureUrlString: String, width: UInt) throws -> URL { if pictureUrlString.hasPrefix("https://") || pictureUrlString.hasPrefix("http://") { guard let url = URL(string: pictureUrlString) else { throw Error.invalidPictureUrl(pictureUrlString) @@ -16,7 +22,7 @@ public enum MediaHelper { return url } - guard let mediaUrl = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) as? String else { + guard let mediaUrl: String = storage[.mediaURL] else { throw Error.noMediaUrlFound } diff --git a/OptimoveCore/Sources/Classes/OptimobileHelper.swift b/OptimoveCore/Sources/Classes/OptimobileHelper.swift index 13dc6448..f94a5f6c 100644 --- a/OptimoveCore/Sources/Classes/OptimobileHelper.swift +++ b/OptimoveCore/Sources/Classes/OptimobileHelper.swift @@ -2,23 +2,28 @@ import Foundation -public enum OptimobileHelper { - private static let installIdLock = DispatchSemaphore(value: 1) +public struct OptimobileHelper { + static let installIdLock = DispatchSemaphore(value: 1) public static let userIdLock = DispatchSemaphore(value: 1) - public static var installId: String { - installIdLock.wait() + let storage: KeyValueStorage + + public init(storage: KeyValueStorage) { + self.storage = storage + } + + public func installId() -> String { + OptimobileHelper.installIdLock.wait() defer { - installIdLock.signal() + OptimobileHelper.installIdLock.signal() } - if let existingID = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.INSTALL_UUID.rawValue) { - return existingID as! String + if let existingID: String = storage[.installUUID] { + return existingID } let newID = UUID().uuidString - KeyValPersistenceHelper.set(newID, forKey: OptimobileUserDefaultsKey.INSTALL_UUID.rawValue) - + storage.set(value: newID, key: .installUUID) return newID } @@ -27,17 +32,18 @@ public enum OptimobileHelper { If no user is associated, it returns the Kumulos installation ID */ - public static var currentUserIdentifier: String { - userIdLock.wait() - defer { userIdLock.signal() } - if let userId = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.USER_ID.rawValue) as! String? { + public func currentUserIdentifier() -> String { + OptimobileHelper.userIdLock.wait() + defer { OptimobileHelper.userIdLock.signal() } + if let userId: String = storage[.userID] { return userId } - return OptimobileHelper.installId + return installId() } - public static func getBadgeFromUserInfo(userInfo: [AnyHashable: Any]) -> NSNumber? { + // FIXME: Use PushNotifcation + public func getBadgeFromUserInfo(userInfo: [AnyHashable: Any]) -> NSNumber? { let custom = userInfo["custom"] as? [AnyHashable: Any] let aps = userInfo["aps"] as? [AnyHashable: Any] @@ -53,7 +59,7 @@ public enum OptimobileHelper { } var newBadge: NSNumber? = badge - if let incrementBy = incrementBy, let currentVal = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.BADGE_COUNT.rawValue) as? NSNumber { + if let incrementBy = incrementBy, let currentVal: NSNumber = storage[.badgeCount] { newBadge = NSNumber(value: currentVal.intValue + incrementBy.intValue) if newBadge!.intValue < 0 { diff --git a/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift b/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift index 3fab605c..824bc186 100644 --- a/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift +++ b/OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift @@ -10,9 +10,6 @@ public enum OptimobileUserDefaultsKey: String, CaseIterable { case BADGE_COUNT = "KumulosBadgeCount" case PENDING_NOTIFICATIONS = "KumulosPendingNotifications" case PENDING_ANALYTICS = "KumulosPendingAnalytics" - - // exist only in standard defaults for app - case MIGRATED_TO_GROUPS = "KumulosDidMigrateToAppGroups" case IN_APP_LAST_SYNCED_AT = "KumulosMessagesLastSyncedAt" case IN_APP_MOST_RECENT_UPDATED_AT = "KumulosInAppMostRecentUpdatedAt" case IN_APP_CONSENTED = "KumulosInAppConsented" diff --git a/OptimoveCore/Sources/Classes/PendingNotificationHelper.swift b/OptimoveCore/Sources/Classes/PendingNotificationHelper.swift index f55356bf..405de69c 100644 --- a/OptimoveCore/Sources/Classes/PendingNotificationHelper.swift +++ b/OptimoveCore/Sources/Classes/PendingNotificationHelper.swift @@ -2,8 +2,14 @@ import Foundation -public enum PendingNotificationHelper { - public static func remove(id: Int) { +public struct PendingNotificationHelper { + let storage: KeyValueStorage + + public init(storage: KeyValueStorage) { + self.storage = storage + } + + public func remove(id: Int) { var pendingNotifications = readAll() if let i = pendingNotifications.firstIndex(where: { $0.id == id }) { @@ -13,7 +19,7 @@ public enum PendingNotificationHelper { } } - public static func remove(identifier: String) { + public func remove(identifier: String) { var pendingNotifications = readAll() if let i = pendingNotifications.firstIndex(where: { $0.identifier == identifier }) { @@ -23,10 +29,10 @@ public enum PendingNotificationHelper { } } - public static func readAll() -> [PendingNotification] { + public func readAll() -> [PendingNotification] { var pendingNotifications = [PendingNotification]() - if let data = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.PENDING_NOTIFICATIONS.rawValue), - let decoded = try? JSONDecoder().decode([PendingNotification].self, from: data as! Data) + if let data: Data = storage[.pendingNotifications], + let decoded = try? JSONDecoder().decode([PendingNotification].self, from: data) { pendingNotifications = decoded } @@ -34,7 +40,7 @@ public enum PendingNotificationHelper { return pendingNotifications } - public static func add(notification: PendingNotification) { + public func add(notification: PendingNotification) { var pendingNotifications = readAll() if let _ = pendingNotifications.firstIndex(where: { $0.id == notification.id }) { @@ -46,9 +52,9 @@ public enum PendingNotificationHelper { save(pendingNotifications: pendingNotifications) } - static func save(pendingNotifications: [PendingNotification]) { + func save(pendingNotifications: [PendingNotification]) { if let data = try? JSONEncoder().encode(pendingNotifications) { - KeyValPersistenceHelper.set(data, forKey: OptimobileUserDefaultsKey.PENDING_NOTIFICATIONS.rawValue) + storage.set(value: data, key: .pendingNotifications) } } } diff --git a/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift b/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift new file mode 100644 index 00000000..116c9c9d --- /dev/null +++ b/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift @@ -0,0 +1,18 @@ +// Copyright © 2023 Optimove. All rights reserved. + +import Foundation + +public extension FileManager { + static func optimoveAppGroupURL() -> URL { + let suiteName = Bundle.optimoveAppGroupIdentifier + guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else { + let message = """ + Unable to initialize UserDefault with suit name "\(suiteName)". + Highly possible that the client forgot to add the app group as described in the documentation. + Link: https://github.com/optimove-tech/Optimove-SDK-iOS/wiki/SDK-Setup-Capabilities + """ + fatalError(message) + } + return url + } +} diff --git a/OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift b/OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift similarity index 82% rename from OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift rename to OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift index f43528ab..de9fca90 100644 --- a/OptimoveSDK/Sources/Classes/Storage/KeyValueStorage.swift +++ b/OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift @@ -3,7 +3,7 @@ import Foundation /// The enum used as keys for storage values. -enum StorageKey: String, CaseIterable { +public enum StorageKey: String, CaseIterable { case installationID case customerID case configurationEndPoint @@ -36,13 +36,21 @@ enum StorageKey: String, CaseIterable { case inAppMostRecentUpdateAt case inAppConsented case dynamicCategory + case deferredLinkChecked = "KUMULOS_DDL_CHECKED" - static let inMemoryValues: Set = [.tenantToken, .version] - static let appGroup: Set = [.mediaURL, .dynamicCategory] + public static let inMemoryValues: Set = [.tenantToken, .version] + public static let appGroup: Set = [ + .badgeCount, + .dynamicCategory, + .installUUID, + .mediaURL, + .pendingNotifications, + .userID, + ] } /// The protocol used as convenience accessor to storage values. -protocol StorageValue { +public protocol StorageValue { var installationID: String? { get set } var customerID: String? { get set } var configurationEndPoint: URL? { get set } @@ -81,7 +89,7 @@ protocol StorageValue { } /// The protocol used for convenience implementation of any storage technology below this protocol. -protocol KeyValueStorage { +public protocol KeyValueStorage { func set(value: Any?, key: StorageKey) func value(for: StorageKey) -> Any? subscript(_: StorageKey) -> T? { get set } @@ -89,15 +97,15 @@ protocol KeyValueStorage { /// ``UserDefaults`` uses as persistent ``KeyValueStorage``. extension UserDefaults: KeyValueStorage { - func set(value: Any?, key: StorageKey) { + public func set(value: Any?, key: StorageKey) { set(value, forKey: key.rawValue) } - func value(for key: StorageKey) -> Any? { + public func value(for key: StorageKey) -> Any? { return value(forKey: key.rawValue) } - subscript(key: StorageKey) -> T? { + public subscript(key: StorageKey) -> T? { get { return value(for: key) as? T } @@ -108,19 +116,19 @@ extension UserDefaults: KeyValueStorage { } /// ``InMemoryStorage`` uses as in-memory ``KeyValueStorage``. -final class InMemoryStorage: KeyValueStorage { +public final class InMemoryStorage: KeyValueStorage { private var storage = [StorageKey: Any]() private let queue = DispatchQueue(label: "com.optimove.sdk.inmemorystorage", attributes: .concurrent) - init() {} + public init() {} - func set(value: Any?, key: StorageKey) { + public func set(value: Any?, key: StorageKey) { queue.async(flags: .barrier) { [self] in storage[key] = value } } - subscript(key: StorageKey) -> T? { + public subscript(key: StorageKey) -> T? { get { var result: T? queue.sync { @@ -135,7 +143,7 @@ final class InMemoryStorage: KeyValueStorage { } } - func value(for key: StorageKey) -> Any? { + public func value(for key: StorageKey) -> Any? { var result: Any? queue.sync { result = storage[key] diff --git a/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift b/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift new file mode 100644 index 00000000..74096c35 --- /dev/null +++ b/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift @@ -0,0 +1,18 @@ +// Copyright © 2023 Optimove. All rights reserved. + +import Foundation + +public extension UserDefaults { + static func optimoveAppGroup() -> UserDefaults { + let suiteName = Bundle.optimoveAppGroupIdentifier + guard let userDefaults = UserDefaults(suiteName: suiteName) else { + let message = """ + Unable to initialize UserDefault with suit name "\(suiteName)". + Highly possible that the client forgot to add the app group as described in the documentation. + Link: https://github.com/optimove-tech/Optimove-SDK-iOS/wiki/SDK-Setup-Capabilities + """ + fatalError(message) + } + return userDefaults + } +} diff --git a/OptimoveCore/Tests/Sources/MediaHelperTests.swift b/OptimoveCore/Tests/Sources/MediaHelperTests.swift index 580e1006..ba33d984 100644 --- a/OptimoveCore/Tests/Sources/MediaHelperTests.swift +++ b/OptimoveCore/Tests/Sources/MediaHelperTests.swift @@ -1,20 +1,29 @@ // Copyright © 2023 Optimove. All rights reserved. import OptimoveCore +import OptimoveTest import XCTest final class MediaHelperTests: XCTestCase { + var mediaHelper: MediaHelper! + var storage: KeyValueStorage! + + override func setUp() { + storage = MockOptimoveStorage() + mediaHelper = MediaHelper(storage: storage) + } + func test_getCompletePictureUrl() throws { let pictureUrlString = "https://www.optimove.com/wp-content/uploads/2018/12/optimove-logo.png" - let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) + let url = try mediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) XCTAssertEqual(url.absoluteString, pictureUrlString) } func test_getCompletePictureUrl_withMediaUrl() throws { let pictureUrlString = "B04wM4Y7/b2f69e254879d69b58c7418468213762.jpeg" let mediaUrl = "https://www.optimove.com" - KeyValPersistenceHelper.set(mediaUrl, forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) - let url = try MediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) + storage.set(value: mediaUrl, key: .mediaURL) + let url = try mediaHelper.getCompletePictureUrl(pictureUrlString: pictureUrlString, width: 100) XCTAssertEqual(url.absoluteString, "\(mediaUrl)/100x/\(pictureUrlString)") } } diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 9c79734c..cc77fb91 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -39,12 +39,22 @@ public enum OptimoveNotificationService { if bestAttemptContent.categoryIdentifier.isEmpty { bestAttemptContent.categoryIdentifier = await buildCategory(notification: notification) } - if AppGroupsHelper.isKumulosAppGroupDefined() { - if let attachment = try await maybeGetAttachment(notification: notification) { + if AppGroupsHelper.isAppGroupDefined() { + let storage = UserDefaults.optimoveAppGroup() + let mediaHelper = MediaHelper(storage: storage) + if let attachment = try await maybeGetAttachment( + notification: notification, + mediaHelper: mediaHelper + ) { bestAttemptContent.attachments = [attachment] } - maybeSetBadge(bestAttemptContent: bestAttemptContent, userInfo: userInfo) - PendingNotificationHelper.add( + let optimobileHelper = OptimobileHelper(storage: storage) + if let badge = maybeSetBadge(userInfo: userInfo, optimobileHelper: optimobileHelper) { + storage.set(value: badge, key: .badgeCount) + bestAttemptContent.badge = badge + } + let pendingNoticationHelper = PendingNotificationHelper(storage: storage) + pendingNoticationHelper.add( notification: PendingNotification( id: notification.message.id, identifier: request.identifier @@ -98,10 +108,10 @@ public enum OptimoveNotificationService { return categoryIdentifier } - static func maybeGetAttachment(notification: PushNotification) async throws -> UNNotificationAttachment? { + static func maybeGetAttachment(notification: PushNotification, mediaHelper: MediaHelper) async throws -> UNNotificationAttachment? { guard let picturePath = notification.attachment?.pictureUrl else { return nil } - let url = try await MediaHelper.getCompletePictureUrl( + let url = try await mediaHelper.getCompletePictureUrl( pictureUrlString: picturePath, width: UInt(floor(UIScreen.main.bounds.size.width)) ) @@ -122,18 +132,19 @@ public enum OptimoveNotificationService { ) } - static func maybeSetBadge(bestAttemptContent: UNMutableNotificationContent, userInfo: [AnyHashable: Any]) { + static func maybeSetBadge( + userInfo: [AnyHashable: Any], + optimobileHelper: OptimobileHelper + ) -> NSNumber? { let aps = userInfo["aps"] as! [AnyHashable: Any] if let contentAvailable = aps["content-available"] as? Int, contentAvailable == 1 { - return + return nil } - let newBadge: NSNumber? = OptimobileHelper.getBadgeFromUserInfo(userInfo: userInfo) + let newBadge: NSNumber? = optimobileHelper.getBadgeFromUserInfo(userInfo: userInfo) if newBadge == nil { - return + return nil } - - bestAttemptContent.badge = newBadge - KeyValPersistenceHelper.set(newBadge, forKey: OptimobileUserDefaultsKey.BADGE_COUNT.rawValue) + return newBadge } } diff --git a/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift b/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift index 573eafec..f58a501a 100644 --- a/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift +++ b/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift @@ -260,7 +260,6 @@ final class MigrationWork_5_7_0: MigrationWorker { .IN_APP_MOST_RECENT_UPDATED_AT: .inAppMostRecentUpdateAt, .IN_APP_CONSENTED: .inAppConsented, .DYNAMIC_CATEGORY: .dynamicCategory, - // TODO: MIGRATED_TO_GROUPS ] /// Move values from Kumulos UserDefaults to Optimove UserDefaults. let kumulosStorage = UserDefaults.standard diff --git a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift index 3c34e005..99c72b05 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift @@ -16,17 +16,19 @@ typealias SyncCompletedBlock = (Error?) -> Void final class AnalyticsHelper { let eventsHttpClient: KSHttpClient + let optimobileHelper: OptimobileHelper private var analyticsContext: NSManagedObjectContext? private var migrationAnalyticsContext: NSManagedObjectContext? private var finishedInitializationToken: NSObjectProtocol? // MARK: Initialization - init(httpClient: KSHttpClient) { + init(httpClient: KSHttpClient, optimobileHelper: OptimobileHelper) { analyticsContext = nil migrationAnalyticsContext = nil eventsHttpClient = httpClient + self.optimobileHelper = optimobileHelper initContext() @@ -67,7 +69,7 @@ final class AnalyticsHelper { } private func getSharedDbUrl() -> URL? { - let sharedContainerPath: URL? = AppGroupsHelper.getSharedContainerPath() + let sharedContainerPath = try? FileManager.optimoveAppGroupURL() if sharedContainerPath == nil { return nil } @@ -78,7 +80,7 @@ final class AnalyticsHelper { private func initContext() { let appDbUrl = getAppDbUrl() let appDbExists = appDbUrl == nil ? false : FileManager.default.fileExists(atPath: appDbUrl!.path) - let appGroupExists = AppGroupsHelper.isKumulosAppGroupDefined() + let appGroupExists = AppGroupsHelper.isAppGroupDefined() let storeUrl = getMainStoreUrl(appGroupExists: appGroupExists) @@ -121,6 +123,7 @@ final class AnalyticsHelper { return } + let currentUserIdentifier = optimobileHelper.currentUserIdentifier() let work = { guard let context = self.analyticsContext else { print("No context, aborting") @@ -137,7 +140,7 @@ final class AnalyticsHelper { event.uuid = UUID().uuidString.lowercased() event.happenedAt = NSNumber(value: Int64(atTime.timeIntervalSince1970 * 1000)) event.eventType = eventType - event.userIdentifier = OptimobileHelper.currentUserIdentifier + event.userIdentifier = currentUserIdentifier if properties != nil { let propsJson = try? JSONSerialization.data(withJSONObject: properties as Any, options: JSONSerialization.WritingOptions(rawValue: 0)) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift index db390962..19d748c5 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift @@ -19,7 +19,10 @@ private var ks_existingBackgroundFetchDelegate: IMP? typealias InAppSyncCompletionHandler = (_ result: Int) -> Void class InAppManager { - private let httpClient: KSHttpClient + let httpClient: KSHttpClient + let storage: OptimoveStorage + let pendingNoticationHelper: PendingNotificationHelper + let optimobileHelper: OptimobileHelper private(set) var presenter: InAppPresenter private var pendingTickleIds = NSMutableOrderedSet(capacity: 1) @@ -35,9 +38,23 @@ class InAppManager { // MARK: Initialization - init(_ config: OptimobileConfig, httpClient: KSHttpClient, urlBuilder: UrlBuilder) { + init( + _ config: OptimobileConfig, + httpClient: KSHttpClient, + urlBuilder: UrlBuilder, + storage: OptimoveStorage, + pendingNoticationHelper: PendingNotificationHelper, + optimobileHelper: OptimobileHelper + ) { self.httpClient = httpClient - presenter = InAppPresenter(displayMode: config.inAppDefaultDisplayMode, urlBuilder: urlBuilder) + self.storage = storage + self.pendingNoticationHelper = pendingNoticationHelper + self.optimobileHelper = optimobileHelper + presenter = InAppPresenter( + displayMode: config.inAppDefaultDisplayMode, + urlBuilder: urlBuilder, + pendingNoticationHelper: pendingNoticationHelper + ) syncQueue = DispatchQueue(label: "com.optimove.inapp.sync") finishedInitializationToken = NotificationCenter.default @@ -262,6 +279,7 @@ class InAppManager { } func sync(_ onComplete: InAppSyncCompletionHandler? = nil) { + let currentUserIdentifier = optimobileHelper.currentUserIdentifier() syncQueue.async { let syncBarrier = DispatchSemaphore(value: 0) @@ -277,7 +295,7 @@ class InAppManager { after = "?after=\(KSHttpUtil.urlEncode(formatter.string(from: mostRecentUpdate as Date))!)" } - let encodedIdentifier = KSHttpUtil.urlEncode(OptimobileHelper.currentUserIdentifier) + let encodedIdentifier = KSHttpUtil.urlEncode(currentUserIdentifier) let path = "/v1/users/\(encodedIdentifier!)/messages\(after)" self.httpClient.sendRequest(.GET, toPath: path, data: nil, onSuccess: { _, decodedBody in @@ -446,7 +464,7 @@ class InAppManager { if #available(iOS 10, *) { let tickleNotificationId = "k-in-app-message:\(id)" UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [tickleNotificationId]) - PendingNotificationHelper.remove(identifier: tickleNotificationId) + pendingNoticationHelper.remove(identifier: tickleNotificationId) } } @@ -808,7 +826,7 @@ class InAppManager { func markAllInboxItemsAsRead() -> Bool { var result = true - let inboxItems = OptimoveInApp.getInboxItems() + let inboxItems = OptimoveInApp.getInboxItems(storage: storage) var inboxNeedsUpdate = false for item in inboxItems { if item.isRead() { diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift index ffac42dd..21926bac 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppPresenter.swift @@ -32,9 +32,16 @@ final class InAppPresenter: NSObject, WKScriptMessageHandler, WKNavigationDelega private var displayMode: InAppDisplayMode private var currentMessage: InAppMessage? + let pendingNoticationHelper: PendingNotificationHelper + let urlBuilder: UrlBuilder - init(displayMode: InAppDisplayMode, urlBuilder: UrlBuilder) { + init( + displayMode: InAppDisplayMode, + urlBuilder: UrlBuilder, + pendingNoticationHelper: PendingNotificationHelper + ) { + self.pendingNoticationHelper = pendingNoticationHelper messageQueue = NSMutableOrderedSet(capacity: 5) pendingTickleIds = NSMutableOrderedSet(capacity: 2) currentMessage = nil @@ -176,7 +183,7 @@ final class InAppPresenter: NSObject, WKScriptMessageHandler, WKNavigationDelega let tickleNotificationId = "k-in-app-message:\(message.id)" UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: [tickleNotificationId]) - PendingNotificationHelper.remove(identifier: tickleNotificationId) + pendingNoticationHelper.remove(identifier: tickleNotificationId) } messageQueueLock.wait() diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift b/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift index 84bcd4dd..c5554d0d 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Network/UrlBuilder.swift @@ -29,7 +29,7 @@ public class UrlBuilder { public typealias ServiceUrlMap = [Service: String] - let storage: KeyValPersistenceHelper.Type + let storage: KeyValueStorage // Overrided urls var runtimeUrlsMap: ServiceUrlMap? @@ -46,14 +46,14 @@ public class UrlBuilder { var region: String { get throws { - if let regionString = storage.object(forKey: OptimobileUserDefaultsKey.REGION.rawValue) as? String { + if let regionString: String = storage[.region] { return regionString } throw Error.regionNotSet } } - required init(storage: KeyValPersistenceHelper.Type, runtimeUrlsMap: ServiceUrlMap? = nil) { + required init(storage: KeyValueStorage, runtimeUrlsMap: ServiceUrlMap? = nil) { self.storage = storage if let runtimeUrlsMap = runtimeUrlsMap, isValidateUrlMap(urlsMap: runtimeUrlsMap) { self.runtimeUrlsMap = runtimeUrlsMap diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift index 72b184f5..b46cb719 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Analytics.swift @@ -40,8 +40,15 @@ extension Optimobile { Parameters: - userIdentifier: Unique identifier for the current user */ - static func associateUserWithInstall(userIdentifier: String) { - associateUserWithInstallImpl(userIdentifier: userIdentifier, attributes: nil) + static func associateUserWithInstall( + userIdentifier: String, + storage: OptimoveStorage + ) { + associateUserWithInstallImpl( + userIdentifier: userIdentifier, + attributes: nil, + storage: storage + ) } /** @@ -51,16 +58,16 @@ extension Optimobile { - userIdentifier: Unique identifier for the current user - attributes: JSON encodable dictionary of attributes to store for the user */ - static func associateUserWithInstall(userIdentifier: String, attributes: [String: AnyObject]) { - associateUserWithInstallImpl(userIdentifier: userIdentifier, attributes: attributes) - } - - /** - Returns the identifier for the user currently associated with the Kumulos installation record - If no user is associated, it returns the Kumulos installation ID - */ - static var currentUserIdentifier: String { - return OptimobileHelper.currentUserIdentifier + static func associateUserWithInstall( + userIdentifier: String, + attributes: [String: AnyObject], + storage: OptimoveStorage + ) { + associateUserWithInstallImpl( + userIdentifier: userIdentifier, + attributes: attributes, + storage: storage + ) } /** @@ -68,23 +75,27 @@ extension Optimobile { See associateUserWithInstall and currentUserIdentifier for further information. */ - static func clearUserAssociation() { + static func clearUserAssociation(storage: OptimoveStorage) { OptimobileHelper.userIdLock.wait() - let currentUserId = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.USER_ID.rawValue) as! String? + let currentUserId: String? = storage[.userID] OptimobileHelper.userIdLock.signal() Optimobile.trackEvent(eventType: OptimobileEvent.STATS_USER_ASSOCIATION_CLEARED, properties: ["oldUserIdentifier": currentUserId ?? NSNull()]) OptimobileHelper.userIdLock.wait() - KeyValPersistenceHelper.removeObject(forKey: OptimobileUserDefaultsKey.USER_ID.rawValue) + storage.set(value: nil, key: .userID) OptimobileHelper.userIdLock.signal() - if currentUserId != nil, currentUserId != Optimobile.installId { + if currentUserId != nil, currentUserId != Optimobile.sharedInstance.installId() { getInstance().inAppManager.handleAssociatedUserChange() } } - fileprivate static func associateUserWithInstallImpl(userIdentifier: String, attributes: [String: AnyObject]?) { + fileprivate static func associateUserWithInstallImpl( + userIdentifier: String, + attributes: [String: AnyObject]?, + storage: OptimoveStorage + ) { if userIdentifier == "" { print("User identifier cannot be empty, aborting!") return @@ -98,8 +109,8 @@ extension Optimobile { } OptimobileHelper.userIdLock.wait() - let currentUserId = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.USER_ID.rawValue) as! String? - KeyValPersistenceHelper.set(userIdentifier, forKey: OptimobileUserDefaultsKey.USER_ID.rawValue) + let currentUserId: String? = storage[.userID] + storage.set(value: userIdentifier, key: .userID) OptimobileHelper.userIdLock.signal() Optimobile.trackEvent(eventType: OptimobileEvent.STATS_ASSOCIATE_USER, properties: params, immediateFlush: true) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift index 4b2ee5a4..25344eb4 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+DeepLinking.swift @@ -44,18 +44,18 @@ final class DeepLinkHelper { let wasDeferred: Bool } - fileprivate static let deferredLinkCheckedKey = "KUMULOS_DDL_CHECKED" - let config: OptimobileConfig let httpClient: KSHttpClient + let storage: OptimoveStorage var anyContinuationHandled: Bool var cachedLink: CachedLink? var cachedFingerprintComponents: [String: String]? var finishedInitializationToken: NSObjectProtocol? - init(_ config: OptimobileConfig, httpClient: KSHttpClient) { + init(_ config: OptimobileConfig, httpClient: KSHttpClient, storage: OptimoveStorage) { self.config = config self.httpClient = httpClient + self.storage = storage anyContinuationHandled = false finishedInitializationToken = NotificationCenter.default @@ -97,7 +97,7 @@ final class DeepLinkHelper { private func checkForDeferredLinkOnClipboard() -> Bool { var handled = false - if let checked = KeyValPersistenceHelper.object(forKey: DeepLinkHelper.deferredLinkCheckedKey) as? Bool, checked == true { + if let checked: Bool? = storage[.deferredLinkChecked], checked == true { return handled } @@ -114,7 +114,7 @@ final class DeepLinkHelper { handled = true } - KeyValPersistenceHelper.set(true, forKey: DeepLinkHelper.deferredLinkCheckedKey) + storage.set(value: true, key: .deferredLinkChecked) return handled } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index 15f124b7..6bf0336e 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -163,7 +163,7 @@ extension Optimobile { do { let notification = try PushNotification(userInfo: userInfo) pushHandleOpen(notification: notification) - PendingNotificationHelper.remove(id: notification.message.id) + pendingNoticationHelper.remove(id: notification.message.id) return true } catch { Logger.error( @@ -212,7 +212,7 @@ extension Optimobile { let data = try JSONSerialization.data(withJSONObject: userInfo) let notification = try JSONDecoder().decode(PushNotification.self, from: data) pushHandleDismissed(notificationId: notification.message.id) - PendingNotificationHelper.remove(id: notification.message.id) + pendingNoticationHelper.remove(id: notification.message.id) return true } catch { Logger.error( @@ -229,7 +229,7 @@ extension Optimobile { @available(iOS 10.0, *) private func pushHandleDismissed(notificationId: Int, dismissedAt: Date? = nil) { - PendingNotificationHelper.remove(id: notificationId) + pendingNoticationHelper.remove(id: notificationId) pushTrackDismissed(notificationId: notificationId, dismissedAt: dismissedAt) } @@ -246,7 +246,7 @@ extension Optimobile { @available(iOS 10.0, *) func maybeTrackPushDismissedEvents() { - if !AppGroupsHelper.isKumulosAppGroupDefined() { + if !AppGroupsHelper.isAppGroupDefined() { return } Task { @@ -259,7 +259,7 @@ extension Optimobile { actualPendingNotificationIds.append(notification.message.id) } - let recordedPendingNotifications = PendingNotificationHelper.readAll() + let recordedPendingNotifications = pendingNoticationHelper.readAll() let deletions = recordedPendingNotifications.filter { !actualPendingNotificationIds.contains($0.id) } for deletion in deletions { @@ -416,8 +416,14 @@ class PushHelper { } }() + let optimobileHelper: OptimobileHelper + + init(optimobileHelper: OptimobileHelper) { + self.optimobileHelper = optimobileHelper + } + private func setBadge(userInfo: [AnyHashable: Any]) { - let badge: NSNumber? = OptimobileHelper.getBadgeFromUserInfo(userInfo: userInfo) + let badge: NSNumber? = optimobileHelper.getBadgeFromUserInfo(userInfo: userInfo) if let newBadge = badge { UIApplication.shared.applicationIconBadgeNumber = newBadge.intValue } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift index 19b0f52f..bb173bd4 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift @@ -159,7 +159,7 @@ extension Optimobile { } return [ - "hasGroup": AppGroupsHelper.isKumulosAppGroupDefined(), + "hasGroup": AppGroupsHelper.isAppGroupDefined(), "push": push, ] } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift index d86d9c6c..367df907 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile.swift @@ -21,8 +21,6 @@ public enum InAppDisplayMode: String { case paused } -// MARK: class - final class Optimobile { enum Error: LocalizedError { case alreadyInitialized @@ -43,12 +41,21 @@ final class Optimobile { let pushNotificationDeviceType = 1 let pushNotificationProductionTokenType: Int = 1 - let sdkType: Int = 101 - fileprivate static var instance: Optimobile? - var notificationCenter: Any? + private(set) var config: OptimobileConfig + private(set) var inAppConsentStrategy = InAppConsentStrategy.notEnabled + private(set) var inAppManager: InAppManager + private(set) var analyticsHelper: AnalyticsHelper + private(set) var sessionHelper: SessionHelper + private(set) var badgeObserver: OptimobileBadgeObserver + private var pushHelper: PushHelper + private(set) var deepLinkHelper: DeepLinkHelper? + private let networkFactory: NetworkFactory + private var credentials: OptimobileCredentials? + let pendingNoticationHelper: PendingNotificationHelper + let optimobileHelper: OptimobileHelper static var sharedInstance: Optimobile { if isInitialized() == false { @@ -62,33 +69,17 @@ final class Optimobile { return sharedInstance } - private(set) var config: OptimobileConfig - private(set) var inAppConsentStrategy = InAppConsentStrategy.notEnabled - static var inAppConsentStrategy: InAppConsentStrategy { return sharedInstance.inAppConsentStrategy } - private(set) var inAppManager: InAppManager - - private(set) var analyticsHelper: AnalyticsHelper - private(set) var sessionHelper: SessionHelper - private(set) var badgeObserver: OptimobileBadgeObserver - - private var pushHelper: PushHelper - - private(set) var deepLinkHelper: DeepLinkHelper? - - private let networkFactory: NetworkFactory - private var credentials: OptimobileCredentials? - /** The unique installation Id of the current app - Returns: String - UUID */ - static var installId: String { - return OptimobileHelper.installId + func installId() -> String { + return optimobileHelper.installId() } static func isInitialized() -> Bool { @@ -102,9 +93,12 @@ final class Optimobile { /** Initialize the Optimobile SDK. */ - static func initialize(config optimoveConfig: OptimoveConfig, initialVisitorId: String, initialUserId: String?) throws { + static func initialize( + optimoveConfig: OptimoveConfig, + storage: OptimoveStorage + ) throws { if instance !== nil, optimoveConfig.features.contains(.delayedConfiguration) { - try completeDelayedConfiguration(config: optimoveConfig.optimobileConfig!) + try completeDelayedConfiguration(config: optimoveConfig.optimobileConfig!, storage: storage) return } @@ -117,9 +111,9 @@ final class Optimobile { throw Error.configurationIsMissing } - writeDefaultsKeys(config: config, initialVisitorId: initialVisitorId) + try writeDefaultsKeys(config: config, storage: storage) - instance = Optimobile(config: config) + instance = Optimobile(config: config, storage: storage) instance!.initializeHelpers() @@ -133,19 +127,19 @@ final class Optimobile { instance!.sendDeviceInformation(config: config) } - maybeAlignUserAssociation(initialUserId: initialUserId) + instance!.maybeAlignUserAssociation(storage: storage) if !optimoveConfig.features.contains(.delayedConfiguration) { NotificationCenter.default.post(name: .optimobileInializationFinished, object: nil) } } - static func completeDelayedConfiguration(config: OptimobileConfig) throws { + static func completeDelayedConfiguration(config: OptimobileConfig, storage: OptimoveStorage) throws { guard let credentials = config.credentials else { throw Error.noCredentialsProvidedForDelayedConfigurationCompletion } Logger.info("Completing delayed configuration with credentials: \(credentials)") - updateStorageValues(config) + updateStorageValues(config, storage: storage) setCredentials(credentials) NotificationCenter.default.post(name: .optimobileInializationFinished, object: nil) } @@ -154,16 +148,18 @@ final class Optimobile { Optimobile.instance?.credentials = credentials } - static func updateStorageValues(_ config: OptimobileConfig) { - KeyValPersistenceHelper.set(config.region.rawValue, forKey: OptimobileUserDefaultsKey.REGION.rawValue) + static func updateStorageValues(_ config: OptimobileConfig, storage: OptimoveStorage) { + storage.set(value: config.region.rawValue, key: .region) let baseUrlMap = UrlBuilder.defaultMapping(for: config.region.rawValue) - KeyValPersistenceHelper.set(baseUrlMap[.media], forKey: OptimobileUserDefaultsKey.MEDIA_BASE_URL.rawValue) + storage.set(value: baseUrlMap[.media], key: .mediaURL) } - fileprivate static func writeDefaultsKeys(config: OptimobileConfig, initialVisitorId: String) { - KeyValPersistenceHelper.maybeMigrateUserDefaultsToAppGroups() - - let existingInstallId = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.INSTALL_UUID.rawValue) as? String + fileprivate static func writeDefaultsKeys( + config: OptimobileConfig, + storage: OptimoveStorage + ) throws { + let existingInstallId: String? = storage[.installUUID] + let initialVisitorId = try storage.getInitialVisitorId() // This block handles upgrades from Kumulos SDK users to Optimove SDK users // In the case where a user was auto-enrolled into in-app messaging on the K SDK, they would not become auto-enrolled // on the new Optimove SDK installation. @@ -172,34 +168,34 @@ final class Optimobile { // we're a new install. Note comparing to `nil` isn't enough because we may have a value depending if previous storage used // app groups or not. if existingInstallId != initialVisitorId, - let _ = UserDefaults.standard.object(forKey: OptimobileUserDefaultsKey.IN_APP_CONSENTED.rawValue) + storage[.inAppConsented] != nil { - UserDefaults.standard.removeObject(forKey: OptimobileUserDefaultsKey.IN_APP_CONSENTED.rawValue) + storage.set(value: nil, key: .inAppConsented) } - KeyValPersistenceHelper.set(initialVisitorId, forKey: OptimobileUserDefaultsKey.INSTALL_UUID.rawValue) + storage.set(value: initialVisitorId, key: .installUUID) if let credentials = config.credentials { setCredentials(credentials) } - updateStorageValues(config) + updateStorageValues(config, storage: storage) } - fileprivate static func maybeAlignUserAssociation(initialUserId: String?) { - if initialUserId == nil { + fileprivate func maybeAlignUserAssociation(storage: OptimoveStorage) { + guard let initialUserId: String = storage[.customerID] else { return } - let optimobileUserId = OptimobileHelper.currentUserIdentifier + let optimobileUserId = optimobileHelper.currentUserIdentifier() if optimobileUserId == initialUserId { return } - Optimobile.associateUserWithInstall(userIdentifier: initialUserId!) + Optimobile.associateUserWithInstall(userIdentifier: initialUserId, storage: storage) } - private init(config: OptimobileConfig) { + private init(config: OptimobileConfig, storage: OptimoveStorage) { self.config = config - let urlBuilder = UrlBuilder(storage: KeyValPersistenceHelper.self) + let urlBuilder = UrlBuilder(storage: storage) networkFactory = NetworkFactory( urlBuilder: urlBuilder, authorization: AuthorizationMediator(provider: { @@ -207,20 +203,39 @@ final class Optimobile { }) ) inAppConsentStrategy = config.inAppConsentStrategy - + optimobileHelper = OptimobileHelper( + storage: storage + ) analyticsHelper = AnalyticsHelper( - httpClient: networkFactory.build(for: .events) + httpClient: networkFactory.build(for: .events), + optimobileHelper: optimobileHelper ) sessionHelper = SessionHelper(sessionIdleTimeout: config.sessionIdleTimeout) - inAppManager = InAppManager(config, httpClient: networkFactory.build(for: .push), urlBuilder: urlBuilder) - pushHelper = PushHelper() + pendingNoticationHelper = PendingNotificationHelper( + storage: storage + ) + inAppManager = InAppManager( + config, + httpClient: networkFactory.build(for: .push), + urlBuilder: urlBuilder, + storage: storage, + pendingNoticationHelper: pendingNoticationHelper, + optimobileHelper: optimobileHelper + ) + pushHelper = PushHelper( + optimobileHelper: optimobileHelper + ) badgeObserver = OptimobileBadgeObserver(callback: { newBadgeCount in - KeyValPersistenceHelper.set(newBadgeCount, forKey: OptimobileUserDefaultsKey.BADGE_COUNT.rawValue) + storage.set(value: newBadgeCount, key: .badgeCount) }) if config.deepLinkHandler != nil { - deepLinkHelper = DeepLinkHelper(config, httpClient: networkFactory.build(for: .ddl)) + deepLinkHelper = DeepLinkHelper( + config, + httpClient: networkFactory.build(for: .ddl), + storage: storage + ) } Logger.debug("Optimobile SDK was initialized with \(config)") diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index 8a5ea8b1..e2f4f9db 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -17,9 +17,11 @@ class InAppInboxItem { private var imagePath: String? private static let defaultImageWidth: UInt = 300 + let mediaHelper: MediaHelper - init(entity: InAppMessageEntity) { + init(entity: InAppMessageEntity, mediaHelper: MediaHelper) { id = Int64(entity.id) + self.mediaHelper = mediaHelper let inboxConfig = entity.inboxConfig?.copy() as! [String: Any] @@ -61,7 +63,7 @@ class InAppInboxItem { func getImageUrl(width: UInt) -> URL? { if let imagePathNotNil = imagePath { - return try? MediaHelper.getCompletePictureUrl( + return try? mediaHelper.getCompletePictureUrl( pictureUrlString: imagePathNotNil, width: width ) @@ -100,7 +102,7 @@ enum OptimoveInApp { return Optimobile.sharedInstance.inAppManager.presenter.getDisplayMode() } - static func getInboxItems() -> [InAppInboxItem] { + static func getInboxItems(storage: OptimoveStorage) -> [InAppInboxItem] { guard let context = Optimobile.sharedInstance.inAppManager.messagesContext else { return [] } @@ -127,7 +129,10 @@ enum OptimoveInApp { } for item in items { - let inboxItem = InAppInboxItem(entity: item) + let inboxItem = InAppInboxItem( + entity: item, + mediaHelper: MediaHelper(storage: storage) + ) if inboxItem.isAvailable() == false { continue diff --git a/OptimoveSDK/Sources/Classes/Optimove.swift b/OptimoveSDK/Sources/Classes/Optimove.swift index 09912db6..73efa480 100644 --- a/OptimoveSDK/Sources/Classes/Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Optimove.swift @@ -52,10 +52,10 @@ typealias Logger = OptimoveCore.Logger if config.isOptimobileConfigured() { shared.container.resolve { serviceLocator in do { - let visitorId = try serviceLocator.storage().getInitialVisitorId() - let userId = try? serviceLocator.storage().getCustomerID() - - try Optimobile.initialize(config: config, initialVisitorId: visitorId, initialUserId: userId) + try Optimobile.initialize( + optimoveConfig: config, + storage: serviceLocator.storage() + ) } catch { throw GuardError.custom("Failed on OptimobileSDK initialization. Reason: \(error.localizedDescription)") } @@ -170,22 +170,24 @@ public extension Optimove { /// - email: The user email. @objc func registerUser(sdkId userID: String, email: String) { if config.isOptimoveConfigured() { - let function: (ServiceLocator) -> Void = { serviceLocator in - tryCatch { - let user = User(userID: userID) - let setUserIdEvent = try self._setUser(user, serviceLocator) - let setUserEmailEvent: Event = try self._setUserEmail(email, serviceLocator) - serviceLocator.pipeline().deliver(.report(events: [setUserIdEvent, setUserEmailEvent])) - if UserValidator(storage: serviceLocator.storage()).validateNewUser(user) == .valid { - serviceLocator.pipeline().deliver(.setInstallation) - } + container.resolve { serviceLocator in + let user = User(userID: userID) + let setUserIdEvent = try self._setUser(user, serviceLocator) + let setUserEmailEvent: Event = try self._setUserEmail(email, serviceLocator) + serviceLocator.pipeline().deliver(.report(events: [setUserIdEvent, setUserEmailEvent])) + if UserValidator(storage: serviceLocator.storage()).validateNewUser(user) == .valid { + serviceLocator.pipeline().deliver(.setInstallation) } } - container.resolve(function) } if config.isOptimobileConfigured() { - Optimobile.associateUserWithInstall(userIdentifier: userID) + container.resolve { serviceLocator in + Optimobile.associateUserWithInstall( + userIdentifier: userID, + storage: serviceLocator.storage() + ) + } } } @@ -203,21 +205,23 @@ public extension Optimove { /// - Parameter userID: The user unique identifier. @objc func setUserId(_ userID: String) { if config.isOptimoveConfigured() { - let function: (ServiceLocator) -> Void = { serviceLocator in - tryCatch { - let user = User(userID: userID) - let event = try self._setUser(user, serviceLocator) - serviceLocator.pipeline().deliver(.report(events: [event])) - if UserValidator(storage: serviceLocator.storage()).validateNewUser(user) == .valid { - serviceLocator.pipeline().deliver(.setInstallation) - } + container.resolve { serviceLocator in + let user = User(userID: userID) + let event = try self._setUser(user, serviceLocator) + serviceLocator.pipeline().deliver(.report(events: [event])) + if UserValidator(storage: serviceLocator.storage()).validateNewUser(user) == .valid { + serviceLocator.pipeline().deliver(.setInstallation) } } - container.resolve(function) } if config.isOptimobileConfigured() { - Optimobile.associateUserWithInstall(userIdentifier: userID) + container.resolve { serviceLocator in + Optimobile.associateUserWithInstall( + userIdentifier: userID, + storage: serviceLocator.storage() + ) + } } } @@ -294,17 +298,18 @@ public extension Optimove { /// Call this function to unset the customerID and revert to an anonymous visitor func signOutUser() { if config.isOptimoveConfigured() { - let function: (ServiceLocator) -> Void = { serviceLocator in - tryCatch { - serviceLocator.storage().set(value: nil, key: StorageKey.customerID) - serviceLocator.storage().set(value: serviceLocator.storage().initialVisitorId, key: StorageKey.visitorID) - } + container.resolve { serviceLocator in + serviceLocator.storage().set(value: nil, key: StorageKey.customerID) + serviceLocator.storage().set(value: serviceLocator.storage().initialVisitorId, key: StorageKey.visitorID) } - container.resolve(function) } if config.isOptimobileConfigured() { - Optimobile.clearUserAssociation() + container.resolve { serviceLocator in + Optimobile.clearUserAssociation( + storage: serviceLocator.storage() + ) + } } } } diff --git a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift index b3ece124..3f836113 100644 --- a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift +++ b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift @@ -150,7 +150,7 @@ open class OptimoveConfigBuilder: NSObject { override public required init() { features = [] - urlBuilder = UrlBuilder(storage: KeyValPersistenceHelper.self) + urlBuilder = UrlBuilder(storage: UserDefaults.optimoveAppGroup()) super.init() } diff --git a/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift b/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift index 51107fcf..6af833f1 100644 --- a/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift +++ b/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift @@ -1,4 +1,402 @@ // Copyright © 2023 Optimove. All rights reserved. +import Foundation +import OptimoveCore + /// Combined protocol for a convenince access to stored values and files. typealias OptimoveStorage = FileStorage & KeyValueStorage & StorageValue + +enum StorageError: LocalizedError { + case noValue(StorageKey) + + var errorDescription: String? { + switch self { + case let .noValue(key): + return "StorageError: No value for key \(key.rawValue)" + } + } +} + +/// Class implements the Façade pattern for hiding complexity of the OptimoveStorage protocol. +final class StorageFacade: OptimoveStorage { + private let persistantStorage: KeyValueStorage + private let inMemoryStorage: KeyValueStorage + private let fileStorage: FileStorage + + init( + persistantStorage: KeyValueStorage, + inMemoryStorage: KeyValueStorage, + fileStorage: FileStorage + ) { + self.fileStorage = fileStorage + self.inMemoryStorage = inMemoryStorage + self.persistantStorage = persistantStorage + } + + func getStorage(for key: StorageKey) -> KeyValueStorage { + if StorageKey.inMemoryValues.contains(key) { + return inMemoryStorage + } + return persistantStorage + } +} + +// MARK: - KeyValueStorage + +extension StorageFacade { + /// Use `storage.key` instead. + /// Some variable have formatters, implemented in own setters. Set unformatted value could cause an issue. + func set(value: Any?, key: StorageKey) { + getStorage(for: key).set(value: value, key: key) + } + + func value(for key: StorageKey) -> Any? { + return getStorage(for: key).value(for: key) + } + + subscript(key: StorageKey) -> T? { + get { + return getStorage(for: key).value(for: key) as? T + } + set { + getStorage(for: key).set(value: newValue, key: key) + } + } + + subscript(key: StorageKey) -> () throws -> T { + get { + { try cast(self.getStorage(for: key).value(for: key)) } + } + set { + getStorage(for: key).set(value: newValue, key: key) + } + } +} + +// MARK: - FileStorage + +extension StorageFacade { + func isExist(fileName: String, isTemporary: Bool) -> Bool { + return fileStorage.isExist(fileName: fileName, isTemporary: isTemporary) + } + + func save(data: T, toFileName: String, isTemporary: Bool) throws { + try fileStorage.save(data: data, toFileName: toFileName, isTemporary: isTemporary) + } + + func saveData(data: Data, toFileName: String, isTemporary: Bool) throws { + try fileStorage.saveData(data: data, toFileName: toFileName, isTemporary: isTemporary) + } + + func load(fileName: String, isTemporary: Bool) throws -> T { + return try unwrap(fileStorage.load(fileName: fileName, isTemporary: isTemporary)) + } + + func loadData(fileName: String, isTemporary: Bool) throws -> Data { + return try unwrap(fileStorage.loadData(fileName: fileName, isTemporary: isTemporary)) + } + + func delete(fileName: String, isTemporary: Bool) throws { + try fileStorage.delete(fileName: fileName, isTemporary: isTemporary) + } +} + +// MARK: - StorageValue + +extension KeyValueStorage where Self: StorageValue { + var installationID: String? { + get { + return self[.installationID] + } + set { + self[.installationID] = newValue + } + } + + var customerID: String? { + get { + return self[.customerID] + } + set { + self[.customerID] = newValue + } + } + + var visitorID: String? { + get { + return self[.visitorID] + } + set { + self[.visitorID] = newValue?.lowercased() + } + } + + var initialVisitorId: String? { + get { + return self[.initialVisitorId] + } + set { + self[.initialVisitorId] = newValue?.lowercased() + } + } + + var configurationEndPoint: URL? { + get { + do { + return try URL(string: unwrap(self[.configurationEndPoint])) + } catch { + return nil + } + } + set { + self[.configurationEndPoint] = newValue?.absoluteString + } + } + + var tenantToken: String? { + get { + return self[.tenantToken] + } + set { + self[.tenantToken] = newValue + } + } + + var version: String? { + get { + return self[.version] + } + set { + self[.version] = newValue + } + } + + var userAgent: String? { + get { + return self[.userAgent] + } + set { + self[.userAgent] = newValue + } + } + + var deviceResolutionWidth: Float? { + get { + return self[.deviceResolutionWidth] + } + set { + self[.deviceResolutionWidth] = newValue + } + } + + var deviceResolutionHeight: Float? { + get { + return self[.deviceResolutionHeight] + } + set { + self[.deviceResolutionHeight] = newValue + } + } + + var advertisingIdentifier: String? { + get { + return self[.advertisingIdentifier] + } + set { + self[.advertisingIdentifier] = newValue + } + } + + var migrationVersions: [String] { + get { + return self[.migrationVersions] ?? [] + } + set { + self[.migrationVersions] = newValue + } + } + + var firstRunTimestamp: Int64? { + get { + return self[.firstRunTimestamp] + } + set { + self[.firstRunTimestamp] = newValue + } + } + + var pushNotificationChannels: [String]? { + get { + return self[.pushNotificationChannels] + } + set { + self[.pushNotificationChannels] = newValue + } + } + + var optitrackEndpoint: URL? { + get { + do { + return try URL(string: unwrap(self[.optitrackEndpoint])) + } catch { + return nil + } + } + set { + self[.optitrackEndpoint] = newValue?.absoluteString + } + } + + var tenantID: Int? { + get { + return self[.tenantID] + } + set { + self[.tenantID] = newValue + } + } + + var userEmail: String? { + get { + return self[.userEmail] + } + set { + self[.userEmail] = newValue + } + } + + var siteID: Int? { + get { + return self[.siteID] + } + set { + self[.siteID] = newValue + } + } + + var isSettingUserSuccess: Bool? { + get { + return self[.settingUserSuccess] + } + set { + return self[.settingUserSuccess] = newValue + } + } + + var firstVisitTimestamp: Int64? { + get { + return self[.firstVisitTimestamp] + } + set { + self[.firstVisitTimestamp] = newValue + } + } + + func getConfigurationEndPoint() throws -> URL { + guard let value = configurationEndPoint else { + throw StorageError.noValue(.configurationEndPoint) + } + return value + } + + func getInstallationID() throws -> String { + guard let value = installationID else { + throw StorageError.noValue(.installationID) + } + return value + } + + func getCustomerID() throws -> String { + guard let value = customerID else { + throw StorageError.noValue(.customerID) + } + return value + } + + func getInitialVisitorId() throws -> String { + guard let value = initialVisitorId else { + throw StorageError.noValue(.initialVisitorId) + } + return value + } + + func getTenantToken() throws -> String { + guard let value = tenantToken else { + throw StorageError.noValue(.tenantToken) + } + return value + } + + func getVisitorID() throws -> String { + guard let value = visitorID else { + throw StorageError.noValue(.visitorID) + } + return value + } + + func getVersion() throws -> String { + guard let value = version else { + throw StorageError.noValue(.version) + } + return value + } + + func getUserAgent() throws -> String { + guard let value = userAgent else { + throw StorageError.noValue(.userAgent) + } + return value + } + + func getDeviceResolutionWidth() throws -> Float { + guard let value = deviceResolutionWidth else { + throw StorageError.noValue(.deviceResolutionWidth) + } + return value + } + + func getDeviceResolutionHeight() throws -> Float { + guard let value = deviceResolutionHeight else { + throw StorageError.noValue(.deviceResolutionHeight) + } + return value + } + + func getFirstRunTimestamp() throws -> Int64 { + guard let value = firstRunTimestamp else { + throw StorageError.noValue(.firstRunTimestamp) + } + return value + } + + func getTenantID() throws -> Int { + guard let value = tenantID else { + throw StorageError.noValue(.tenantID) + } + return value + } + + mutating func finishedMigration(to version: String) { + var versions = migrationVersions + versions.append(version) + migrationVersions = versions + } + + func isAlreadyMigrated(to version: String) -> Bool { + return migrationVersions.contains(version) + } + + func getUserEmail() throws -> String { + guard let value = userEmail else { + throw StorageError.noValue(.userEmail) + } + return value + } + + func getSiteID() throws -> Int { + guard let value = siteID else { + throw StorageError.noValue(.siteID) + } + return value + } +} diff --git a/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift b/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift deleted file mode 100644 index 750c5eea..00000000 --- a/OptimoveSDK/Sources/Classes/Storage/StorageFacade.swift +++ /dev/null @@ -1,399 +0,0 @@ -// Copyright © 2019 Optimove. All rights reserved. - -import Foundation -import OptimoveCore - -enum StorageError: LocalizedError { - case noValue(StorageKey) - - var errorDescription: String? { - switch self { - case let .noValue(key): - return "StorageError: No value for key \(key.rawValue)" - } - } -} - -/// Class implements the Façade pattern for hiding complexity of the OptimoveStorage protocol. -final class StorageFacade: OptimoveStorage { - private let persistantStorage: KeyValueStorage - private let inMemoryStorage: KeyValueStorage - private let fileStorage: FileStorage - - init( - persistantStorage: KeyValueStorage, - inMemoryStorage: KeyValueStorage, - fileStorage: FileStorage - ) { - self.fileStorage = fileStorage - self.inMemoryStorage = inMemoryStorage - self.persistantStorage = persistantStorage - } - - func getStorage(for key: StorageKey) -> KeyValueStorage { - if StorageKey.inMemoryValues.contains(key) { - return inMemoryStorage - } - return persistantStorage - } -} - -// MARK: - KeyValueStorage - -extension StorageFacade { - /// Use `storage.key` instead. - /// Some variable have formatters, implemented in own setters. Set unformatted value could cause an issue. - func set(value: Any?, key: StorageKey) { - getStorage(for: key).set(value: value, key: key) - } - - func value(for key: StorageKey) -> Any? { - return getStorage(for: key).value(for: key) - } - - subscript(key: StorageKey) -> T? { - get { - return getStorage(for: key).value(for: key) as? T - } - set { - getStorage(for: key).set(value: newValue, key: key) - } - } - - subscript(key: StorageKey) -> () throws -> T { - get { - { try cast(self.getStorage(for: key).value(for: key)) } - } - set { - getStorage(for: key).set(value: newValue, key: key) - } - } -} - -// MARK: - FileStorage - -extension StorageFacade { - func isExist(fileName: String, isTemporary: Bool) -> Bool { - return fileStorage.isExist(fileName: fileName, isTemporary: isTemporary) - } - - func save(data: T, toFileName: String, isTemporary: Bool) throws { - try fileStorage.save(data: data, toFileName: toFileName, isTemporary: isTemporary) - } - - func saveData(data: Data, toFileName: String, isTemporary: Bool) throws { - try fileStorage.saveData(data: data, toFileName: toFileName, isTemporary: isTemporary) - } - - func load(fileName: String, isTemporary: Bool) throws -> T { - return try unwrap(fileStorage.load(fileName: fileName, isTemporary: isTemporary)) - } - - func loadData(fileName: String, isTemporary: Bool) throws -> Data { - return try unwrap(fileStorage.loadData(fileName: fileName, isTemporary: isTemporary)) - } - - func delete(fileName: String, isTemporary: Bool) throws { - try fileStorage.delete(fileName: fileName, isTemporary: isTemporary) - } -} - -// MARK: - StorageValue - -extension KeyValueStorage where Self: StorageValue { - var installationID: String? { - get { - return self[.installationID] - } - set { - self[.installationID] = newValue - } - } - - var customerID: String? { - get { - return self[.customerID] - } - set { - self[.customerID] = newValue - } - } - - var visitorID: String? { - get { - return self[.visitorID] - } - set { - self[.visitorID] = newValue?.lowercased() - } - } - - var initialVisitorId: String? { - get { - return self[.initialVisitorId] - } - set { - self[.initialVisitorId] = newValue?.lowercased() - } - } - - var configurationEndPoint: URL? { - get { - do { - return try URL(string: unwrap(self[.configurationEndPoint])) - } catch { - return nil - } - } - set { - self[.configurationEndPoint] = newValue?.absoluteString - } - } - - var tenantToken: String? { - get { - return self[.tenantToken] - } - set { - self[.tenantToken] = newValue - } - } - - var version: String? { - get { - return self[.version] - } - set { - self[.version] = newValue - } - } - - var userAgent: String? { - get { - return self[.userAgent] - } - set { - self[.userAgent] = newValue - } - } - - var deviceResolutionWidth: Float? { - get { - return self[.deviceResolutionWidth] - } - set { - self[.deviceResolutionWidth] = newValue - } - } - - var deviceResolutionHeight: Float? { - get { - return self[.deviceResolutionHeight] - } - set { - self[.deviceResolutionHeight] = newValue - } - } - - var advertisingIdentifier: String? { - get { - return self[.advertisingIdentifier] - } - set { - self[.advertisingIdentifier] = newValue - } - } - - var migrationVersions: [String] { - get { - return self[.migrationVersions] ?? [] - } - set { - self[.migrationVersions] = newValue - } - } - - var firstRunTimestamp: Int64? { - get { - return self[.firstRunTimestamp] - } - set { - self[.firstRunTimestamp] = newValue - } - } - - var pushNotificationChannels: [String]? { - get { - return self[.pushNotificationChannels] - } - set { - self[.pushNotificationChannels] = newValue - } - } - - var optitrackEndpoint: URL? { - get { - do { - return try URL(string: unwrap(self[.optitrackEndpoint])) - } catch { - return nil - } - } - set { - self[.optitrackEndpoint] = newValue?.absoluteString - } - } - - var tenantID: Int? { - get { - return self[.tenantID] - } - set { - self[.tenantID] = newValue - } - } - - var userEmail: String? { - get { - return self[.userEmail] - } - set { - self[.userEmail] = newValue - } - } - - var siteID: Int? { - get { - return self[.siteID] - } - set { - self[.siteID] = newValue - } - } - - var isSettingUserSuccess: Bool? { - get { - return self[.settingUserSuccess] - } - set { - return self[.settingUserSuccess] = newValue - } - } - - var firstVisitTimestamp: Int64? { - get { - return self[.firstVisitTimestamp] - } - set { - self[.firstVisitTimestamp] = newValue - } - } - - func getConfigurationEndPoint() throws -> URL { - guard let value = configurationEndPoint else { - throw StorageError.noValue(.configurationEndPoint) - } - return value - } - - func getInstallationID() throws -> String { - guard let value = installationID else { - throw StorageError.noValue(.installationID) - } - return value - } - - func getCustomerID() throws -> String { - guard let value = customerID else { - throw StorageError.noValue(.customerID) - } - return value - } - - func getInitialVisitorId() throws -> String { - guard let value = initialVisitorId else { - throw StorageError.noValue(.initialVisitorId) - } - return value - } - - func getTenantToken() throws -> String { - guard let value = tenantToken else { - throw StorageError.noValue(.tenantToken) - } - return value - } - - func getVisitorID() throws -> String { - guard let value = visitorID else { - throw StorageError.noValue(.visitorID) - } - return value - } - - func getVersion() throws -> String { - guard let value = version else { - throw StorageError.noValue(.version) - } - return value - } - - func getUserAgent() throws -> String { - guard let value = userAgent else { - throw StorageError.noValue(.userAgent) - } - return value - } - - func getDeviceResolutionWidth() throws -> Float { - guard let value = deviceResolutionWidth else { - throw StorageError.noValue(.deviceResolutionWidth) - } - return value - } - - func getDeviceResolutionHeight() throws -> Float { - guard let value = deviceResolutionHeight else { - throw StorageError.noValue(.deviceResolutionHeight) - } - return value - } - - func getFirstRunTimestamp() throws -> Int64 { - guard let value = firstRunTimestamp else { - throw StorageError.noValue(.firstRunTimestamp) - } - return value - } - - func getTenantID() throws -> Int { - guard let value = tenantID else { - throw StorageError.noValue(.tenantID) - } - return value - } - - mutating func finishedMigration(to version: String) { - var versions = migrationVersions - versions.append(version) - migrationVersions = versions - } - - func isAlreadyMigrated(to version: String) -> Bool { - return migrationVersions.contains(version) - } - - func getUserEmail() throws -> String { - guard let value = userEmail else { - throw StorageError.noValue(.userEmail) - } - return value - } - - func getSiteID() throws -> Int { - guard let value = siteID else { - throw StorageError.noValue(.siteID) - } - return value - } -} diff --git a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift index e4dfb222..9a9237f3 100644 --- a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift +++ b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift @@ -42,7 +42,7 @@ final class OptimoveConfigBuilderTests: XCTestCase { } func testBaseUrlMapping() throws { - KeyValPersistenceHelper.set(Region.DEV.rawValue, forKey: OptimobileUserDefaultsKey.REGION.rawValue) + UserDefaults.optimoveAppGroup().set(value: Region.DEV.rawValue, key: .region) let urlsMap = UrlBuilder.defaultMapping(for: Region.DEV.rawValue) let config = optimoveConfigBuilder.build() try UrlBuilder.Service.allCases.forEach { service in diff --git a/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift b/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift index 4ea35af2..2d765e50 100644 --- a/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift @@ -1,5 +1,6 @@ // Copyright © 2019 Optimove. All rights reserved. +import OptimoveCore @testable import OptimoveSDK import XCTest From f9d268a382a80478d65e09dd77a06ae2c5c98e5d Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 16:58:49 +0200 Subject: [PATCH 18/30] feat: add appgroup to storge facade --- .../Sources/Classes/Storage/KeyValueStorage.swift | 2 +- OptimoveSDK/Sources/Classes/DI/Assembly.swift | 6 +++--- .../Sources/Classes/Storage/OptimoveStorage.swift | 15 +++++++++++---- .../Sources/Storage/KeyValueStorageTests.swift | 3 ++- .../Storage/OptimoveStorageFacadeTests.swift | 3 ++- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift b/OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift index de9fca90..3529df0e 100644 --- a/OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift +++ b/OptimoveCore/Sources/Classes/Storage/KeyValueStorage.swift @@ -39,7 +39,7 @@ public enum StorageKey: String, CaseIterable { case deferredLinkChecked = "KUMULOS_DDL_CHECKED" public static let inMemoryValues: Set = [.tenantToken, .version] - public static let appGroup: Set = [ + public static let appGroupValues: Set = [ .badgeCount, .dynamicCategory, .installUUID, diff --git a/OptimoveSDK/Sources/Classes/DI/Assembly.swift b/OptimoveSDK/Sources/Classes/DI/Assembly.swift index 777bf0ff..32349912 100644 --- a/OptimoveSDK/Sources/Classes/DI/Assembly.swift +++ b/OptimoveSDK/Sources/Classes/DI/Assembly.swift @@ -12,14 +12,14 @@ final class Assembly { /// A special storage migration, before an actual storage going to be in use. migrate() do { - let keyValureStorage = try UserDefaults.optimove() let fileStorage = try FileStorageImpl( persistentStorageURL: FileManager.optimoveURL(), temporaryStorageURL: FileManager.temporaryURL() ) - return ServiceLocator( + return try ServiceLocator( storageFacade: StorageFacade( - persistantStorage: keyValureStorage, + standardStorage: UserDefaults.optimove(), + appGroupStorage: UserDefaults.optimoveAppGroup(), inMemoryStorage: InMemoryStorage(), fileStorage: fileStorage ) diff --git a/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift b/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift index 6af833f1..72479c1b 100644 --- a/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift +++ b/OptimoveSDK/Sources/Classes/Storage/OptimoveStorage.swift @@ -19,25 +19,32 @@ enum StorageError: LocalizedError { /// Class implements the Façade pattern for hiding complexity of the OptimoveStorage protocol. final class StorageFacade: OptimoveStorage { - private let persistantStorage: KeyValueStorage + // FIXME: - Split persistance storage to AppGroup and Standart + private let standardStorage: KeyValueStorage + private let appGroupStorage: KeyValueStorage private let inMemoryStorage: KeyValueStorage private let fileStorage: FileStorage init( - persistantStorage: KeyValueStorage, + standardStorage: KeyValueStorage, + appGroupStorage: KeyValueStorage, inMemoryStorage: KeyValueStorage, fileStorage: FileStorage ) { self.fileStorage = fileStorage self.inMemoryStorage = inMemoryStorage - self.persistantStorage = persistantStorage + self.appGroupStorage = appGroupStorage + self.standardStorage = standardStorage } func getStorage(for key: StorageKey) -> KeyValueStorage { if StorageKey.inMemoryValues.contains(key) { return inMemoryStorage } - return persistantStorage + if StorageKey.appGroupValues.contains(key) { + return appGroupStorage + } + return standardStorage } } diff --git a/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift b/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift index 50b8721c..7e4544a6 100644 --- a/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/KeyValueStorageTests.swift @@ -62,7 +62,8 @@ class KeyValueStorageTests: XCTestCase { override func setUp() { storage = StorageFacade( - persistantStorage: MockKeyValueStorage(), + standardStorage: MockKeyValueStorage(), + appGroupStorage: MockKeyValueStorage(), inMemoryStorage: MockKeyValueStorage(), fileStorage: MockFileStorage() ) diff --git a/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift b/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift index 2d765e50..20406a0c 100644 --- a/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift +++ b/OptimoveSDK/Tests/Sources/Storage/OptimoveStorageFacadeTests.swift @@ -9,7 +9,8 @@ class OptimoveStorageFacadeTests: XCTestCase { override func setUp() { storage = StorageFacade( - persistantStorage: MockKeyValueStorage(), + standardStorage: MockKeyValueStorage(), + appGroupStorage: MockKeyValueStorage(), inMemoryStorage: MockKeyValueStorage(), fileStorage: MockFileStorage() ) From e24e830d520fada388f14d6801eaa80af97ba87d Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 17:05:44 +0200 Subject: [PATCH 19/30] chore: remove app group helper --- .../Sources/Classes/AppGroupsHelper.swift | 33 ------------------- .../Storage/FileManager+AppGroup.swift | 5 +-- .../Storage/UserDefaults+AppGroup.swift | 5 +-- .../Sources/OptimoveNotificationService.swift | 3 +- .../Classes/Optimobile/AnalyticsHelper.swift | 2 +- .../Classes/Optimobile/Optimobile+Push.swift | 3 -- .../Classes/Optimobile/Optimobile+Stats.swift | 2 +- .../Sources/Classes/OptimoveConfig.swift | 11 +++++-- .../Sources/OptimoveConfigBuilderTests.swift | 2 +- 9 files changed, 18 insertions(+), 48 deletions(-) delete mode 100644 OptimoveCore/Sources/Classes/AppGroupsHelper.swift diff --git a/OptimoveCore/Sources/Classes/AppGroupsHelper.swift b/OptimoveCore/Sources/Classes/AppGroupsHelper.swift deleted file mode 100644 index af813509..00000000 --- a/OptimoveCore/Sources/Classes/AppGroupsHelper.swift +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright © 2022 Optimove. All rights reserved. - -import Foundation - -enum AppGroupConfig { - static var suffix: String = ".optimove" -} - -public enum AppGroupsHelper { - public static func isKumulosAppGroupDefined() -> Bool { - let containerUrl = getSharedContainerPath() - - return containerUrl != nil - } - - public static func getSharedContainerPath() -> URL? { - return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: getKumulosGroupName()) - } - - static func getKumulosGroupName() -> String { - var targetBundle = Bundle.main - if targetBundle.bundleURL.pathExtension == "appex" { - let url = targetBundle.bundleURL.deletingLastPathComponent().deletingLastPathComponent() - if let mainBundle = Bundle(url: url) { - targetBundle = mainBundle - } else { - print("AppGroupsHelper: Error, could not obtain main bundle from extension!") - } - } - - return "group.\(targetBundle.bundleIdentifier!)\(AppGroupConfig.suffix)" - } -} diff --git a/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift b/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift index 116c9c9d..21947591 100644 --- a/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift +++ b/OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift @@ -3,7 +3,7 @@ import Foundation public extension FileManager { - static func optimoveAppGroupURL() -> URL { + static func optimoveAppGroupURL() throws -> URL { let suiteName = Bundle.optimoveAppGroupIdentifier guard let url = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: suiteName) else { let message = """ @@ -11,7 +11,8 @@ public extension FileManager { Highly possible that the client forgot to add the app group as described in the documentation. Link: https://github.com/optimove-tech/Optimove-SDK-iOS/wiki/SDK-Setup-Capabilities """ - fatalError(message) + assertionFailure(message) + throw GuardError.custom(message) } return url } diff --git a/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift b/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift index 74096c35..aaf6f3d6 100644 --- a/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift +++ b/OptimoveCore/Sources/Classes/Storage/UserDefaults+AppGroup.swift @@ -3,7 +3,7 @@ import Foundation public extension UserDefaults { - static func optimoveAppGroup() -> UserDefaults { + static func optimoveAppGroup() throws -> UserDefaults { let suiteName = Bundle.optimoveAppGroupIdentifier guard let userDefaults = UserDefaults(suiteName: suiteName) else { let message = """ @@ -11,7 +11,8 @@ public extension UserDefaults { Highly possible that the client forgot to add the app group as described in the documentation. Link: https://github.com/optimove-tech/Optimove-SDK-iOS/wiki/SDK-Setup-Capabilities """ - fatalError(message) + assertionFailure(message) + throw GuardError.custom(message) } return userDefaults } diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index cc77fb91..5deb96c9 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -39,8 +39,7 @@ public enum OptimoveNotificationService { if bestAttemptContent.categoryIdentifier.isEmpty { bestAttemptContent.categoryIdentifier = await buildCategory(notification: notification) } - if AppGroupsHelper.isAppGroupDefined() { - let storage = UserDefaults.optimoveAppGroup() + if let storage = try? UserDefaults.optimoveAppGroup() { let mediaHelper = MediaHelper(storage: storage) if let attachment = try await maybeGetAttachment( notification: notification, diff --git a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift index 99c72b05..b548ee3f 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/AnalyticsHelper.swift @@ -80,7 +80,7 @@ final class AnalyticsHelper { private func initContext() { let appDbUrl = getAppDbUrl() let appDbExists = appDbUrl == nil ? false : FileManager.default.fileExists(atPath: appDbUrl!.path) - let appGroupExists = AppGroupsHelper.isAppGroupDefined() + let appGroupExists = true let storeUrl = getMainStoreUrl(appGroupExists: appGroupExists) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index 6bf0336e..d2e88bf3 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -246,9 +246,6 @@ extension Optimobile { @available(iOS 10.0, *) func maybeTrackPushDismissedEvents() { - if !AppGroupsHelper.isAppGroupDefined() { - return - } Task { do { let notifications = await UNUserNotificationCenter.current().deliveredNotifications() diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift index bb173bd4..f17792f2 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Stats.swift @@ -159,7 +159,7 @@ extension Optimobile { } return [ - "hasGroup": AppGroupsHelper.isAppGroupDefined(), + "hasGroup": true, "push": push, ] } diff --git a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift index 3f836113..4ebba084 100644 --- a/OptimoveSDK/Sources/Classes/OptimoveConfig.swift +++ b/OptimoveSDK/Sources/Classes/OptimoveConfig.swift @@ -149,9 +149,14 @@ open class OptimoveConfigBuilder: NSObject { } override public required init() { - features = [] - urlBuilder = UrlBuilder(storage: UserDefaults.optimoveAppGroup()) - super.init() + do { + features = [] + // FIXME: Move UrlBuilder out of OptimoveConfigBuilder + urlBuilder = try UrlBuilder(storage: UserDefaults.optimoveAppGroup()) + super.init() + } catch { + fatalError(error.localizedDescription) + } } @discardableResult public func setCredentials(optimoveCredentials: String?, optimobileCredentials: String?) -> OptimoveConfigBuilder { diff --git a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift index 9a9237f3..991cc2c2 100644 --- a/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift +++ b/OptimoveSDK/Tests/Sources/OptimoveConfigBuilderTests.swift @@ -42,7 +42,7 @@ final class OptimoveConfigBuilderTests: XCTestCase { } func testBaseUrlMapping() throws { - UserDefaults.optimoveAppGroup().set(value: Region.DEV.rawValue, key: .region) + try UserDefaults.optimoveAppGroup().set(value: Region.DEV.rawValue, key: .region) let urlsMap = UrlBuilder.defaultMapping(for: Region.DEV.rawValue) let config = optimoveConfigBuilder.build() try UrlBuilder.Service.allCases.forEach { service in From 78c4929970d3bad0dca3ee851a575c6c42a2e214 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Fri, 8 Dec 2023 17:26:12 +0200 Subject: [PATCH 20/30] refactor: badge arithmetic --- .../Sources/Classes/OptimobileHelper.swift | 31 +++++-------------- .../Sources/Classes/PushNotification.swift | 6 ++-- .../Tests/Sources/PushNotificationTests.swift | 2 +- .../Sources/OptimoveNotificationService.swift | 20 ++---------- .../Classes/Optimobile/Optimobile+Push.swift | 9 +++--- 5 files changed, 17 insertions(+), 51 deletions(-) diff --git a/OptimoveCore/Sources/Classes/OptimobileHelper.swift b/OptimoveCore/Sources/Classes/OptimobileHelper.swift index f94a5f6c..2e90014d 100644 --- a/OptimoveCore/Sources/Classes/OptimobileHelper.swift +++ b/OptimoveCore/Sources/Classes/OptimobileHelper.swift @@ -42,31 +42,14 @@ public struct OptimobileHelper { return installId() } - // FIXME: Use PushNotifcation - public func getBadgeFromUserInfo(userInfo: [AnyHashable: Any]) -> NSNumber? { - let custom = userInfo["custom"] as? [AnyHashable: Any] - let aps = userInfo["aps"] as? [AnyHashable: Any] - - if custom == nil || aps == nil { - return nil - } - - let incrementBy: NSNumber? = custom!["badge_inc"] as? NSNumber - let badge: NSNumber? = aps!["badge"] as? NSNumber - - if badge == nil { - return nil - } - - var newBadge: NSNumber? = badge - if let incrementBy = incrementBy, let currentVal: NSNumber = storage[.badgeCount] { - newBadge = NSNumber(value: currentVal.intValue + incrementBy.intValue) - - if newBadge!.intValue < 0 { - newBadge = 0 - } + public func getBadge(notification: PushNotification) -> Int? { + if let incrementBy = notification.badgeIncrement, + let current: Int = storage[.badgeCount] + { + let badge = current + incrementBy + return badge < 0 ? 0 : badge } - return newBadge + return notification.aps.badge } } diff --git a/OptimoveCore/Sources/Classes/PushNotification.swift b/OptimoveCore/Sources/Classes/PushNotification.swift index 39d5a366..03507d62 100644 --- a/OptimoveCore/Sources/Classes/PushNotification.swift +++ b/OptimoveCore/Sources/Classes/PushNotification.swift @@ -76,7 +76,7 @@ public struct PushNotification: Decodable { public let aps: Aps public let attachment: PushNotification.Attachment? /// Optimove badge - public let badge: Int? + public let badgeIncrement: Int? public let buttons: [PushNotification.Button]? public let deeplink: PushNotification.Data? public let message: PushNotification.Data @@ -86,7 +86,7 @@ public struct PushNotification: Decodable { case a case aps case attachments - case badge = "badge_inc" + case badgeIncrement = "badge_inc" case buttons = "k.buttons" case custom case deeplink = "k.deepLink" @@ -106,7 +106,7 @@ public struct PushNotification: Decodable { self.attachment = try container.decodeIfPresent(Attachment.self, forKey: .attachments) let custom = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .custom) - self.badge = try custom.decodeIfPresent(Int.self, forKey: .badge) + self.badgeIncrement = try custom.decodeIfPresent(Int.self, forKey: .badgeIncrement) self.url = try custom.decodeIfPresent(URL.self, forKey: .u) let a = try custom.nestedContainer(keyedBy: CodingKeys.self, forKey: .a) diff --git a/OptimoveCore/Tests/Sources/PushNotificationTests.swift b/OptimoveCore/Tests/Sources/PushNotificationTests.swift index 8f0cac7f..fbea0fa0 100644 --- a/OptimoveCore/Tests/Sources/PushNotificationTests.swift +++ b/OptimoveCore/Tests/Sources/PushNotificationTests.swift @@ -18,7 +18,7 @@ final class PushNotificationTests: XCTestCase, FileAccessible { fileName = "notification-badge.json" let decoder = JSONDecoder() let notification = try decoder.decode(PushNotification.self, from: data) - XCTAssertEqual(notification.badge, 42) + XCTAssertEqual(notification.badgeIncrement, 42) } func test_decode_buttons() throws { diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 5deb96c9..6c775a02 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -48,9 +48,9 @@ public enum OptimoveNotificationService { bestAttemptContent.attachments = [attachment] } let optimobileHelper = OptimobileHelper(storage: storage) - if let badge = maybeSetBadge(userInfo: userInfo, optimobileHelper: optimobileHelper) { + if let badge = optimobileHelper.getBadge(notification: notification) { storage.set(value: badge, key: .badgeCount) - bestAttemptContent.badge = badge + bestAttemptContent.badge = NSNumber(integerLiteral: badge) } let pendingNoticationHelper = PendingNotificationHelper(storage: storage) pendingNoticationHelper.add( @@ -130,20 +130,4 @@ public enum OptimoveNotificationService { url: tempURL ) } - - static func maybeSetBadge( - userInfo: [AnyHashable: Any], - optimobileHelper: OptimobileHelper - ) -> NSNumber? { - let aps = userInfo["aps"] as! [AnyHashable: Any] - if let contentAvailable = aps["content-available"] as? Int, contentAvailable == 1 { - return nil - } - - let newBadge: NSNumber? = optimobileHelper.getBadgeFromUserInfo(userInfo: userInfo) - if newBadge == nil { - return nil - } - return newBadge - } } diff --git a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift index d2e88bf3..c5eea925 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -348,7 +348,7 @@ class PushHelper { let notification = try PushNotification(userInfo: userInfo) let hasInApp = notification.deeplink != nil - self.setBadge(userInfo: userInfo) + self.setBadge(notification: notification) self.trackPushDelivery(notification: notification) if existingDidReceive == nil, !hasInApp { @@ -419,10 +419,9 @@ class PushHelper { self.optimobileHelper = optimobileHelper } - private func setBadge(userInfo: [AnyHashable: Any]) { - let badge: NSNumber? = optimobileHelper.getBadgeFromUserInfo(userInfo: userInfo) - if let newBadge = badge { - UIApplication.shared.applicationIconBadgeNumber = newBadge.intValue + private func setBadge(notification: PushNotification) { + if let badge = optimobileHelper.getBadge(notification: notification) { + UIApplication.shared.applicationIconBadgeNumber = badge } } From 8eaf717f465d88855afdbf22ed81d7fa597400db Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 6 Feb 2024 09:45:23 +0200 Subject: [PATCH 21/30] chore: migration version 6.0.0 --- OptimoveSDK/Sources/Classes/DI/Assembly.swift | 2 +- OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift | 4 ++-- OptimoveSDK/Sources/Classes/Migration/Version.swift | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/OptimoveSDK/Sources/Classes/DI/Assembly.swift b/OptimoveSDK/Sources/Classes/DI/Assembly.swift index 32349912..7a87c0a6 100644 --- a/OptimoveSDK/Sources/Classes/DI/Assembly.swift +++ b/OptimoveSDK/Sources/Classes/DI/Assembly.swift @@ -33,7 +33,7 @@ final class Assembly { private func migrate() { let migrations: [MigrationWork] = [ MigrationWork_3_3_0(), - MigrationWork_5_7_0(), + MigrationWork_6_0_0(), ] migrations .filter { $0.isAllowToMiragte(SDKVersion) } diff --git a/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift b/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift index f58a501a..4decf8de 100644 --- a/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift +++ b/OptimoveSDK/Sources/Classes/Migration/MigrationWork.swift @@ -232,9 +232,9 @@ extension MigrationWork_3_3_0 { } /// Migration from Kumulos UserDefaults to Optimove UserDefaults. -final class MigrationWork_5_7_0: MigrationWorker { +final class MigrationWork_6_0_0: MigrationWorker { init() { - super.init(newVersion: .v_5_7_0) + super.init(newVersion: .v_6_0_0) } override func isAllowToMiragte(_: String) -> Bool { diff --git a/OptimoveSDK/Sources/Classes/Migration/Version.swift b/OptimoveSDK/Sources/Classes/Migration/Version.swift index c1c172ca..d35ae1f4 100644 --- a/OptimoveSDK/Sources/Classes/Migration/Version.swift +++ b/OptimoveSDK/Sources/Classes/Migration/Version.swift @@ -4,5 +4,5 @@ enum Version: String { case v_2_10_0 = "2.10.0" case v_3_0_0 = "3.0.0" case v_3_3_0 = "3.3.0" - case v_5_7_0 = "5.7.0" + case v_6_0_0 = "6.0.0" } From 6d2fe1ea2f86f0d894586d8cbcac3650d5970077 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 6 Feb 2024 11:12:21 +0200 Subject: [PATCH 22/30] chore: add debug product --- OptimoveDebug/Sources/ApplicationInfo.swift | 9 +++ OptimoveDebug/Sources/StateService.swift | 68 +++++++++++++++++++++ Package.swift | 11 ++++ 3 files changed, 88 insertions(+) create mode 100644 OptimoveDebug/Sources/ApplicationInfo.swift create mode 100644 OptimoveDebug/Sources/StateService.swift diff --git a/OptimoveDebug/Sources/ApplicationInfo.swift b/OptimoveDebug/Sources/ApplicationInfo.swift new file mode 100644 index 00000000..9d789498 --- /dev/null +++ b/OptimoveDebug/Sources/ApplicationInfo.swift @@ -0,0 +1,9 @@ +// Copyright © 2024 Optimove. All rights reserved. + +import Foundation + +public enum ApplicationInfo { + public static let bundleIdentifier = Bundle.main.bundleIdentifier! + public static let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String + public static let build = Bundle.main.infoDictionary!["CFBundleVersion"] as! String +} diff --git a/OptimoveDebug/Sources/StateService.swift b/OptimoveDebug/Sources/StateService.swift new file mode 100644 index 00000000..f097ada4 --- /dev/null +++ b/OptimoveDebug/Sources/StateService.swift @@ -0,0 +1,68 @@ +// Copyright © 2024 Optimove. All rights reserved. + +import Foundation +import OptimoveCore +@testable import OptimoveSDK + +public final class StateService { + public enum State { + case app_version + case sdk_version + case installation + case tenant + case initial_visitor + case customer + case update_visitor + case email + } + + private enum Constants { + static let undefined = "" + } + + public static var shared: StateService = .init() + + private let storage: any OptimoveStorage + + private init() { + do { + storage = try StorageFacade( + standardStorage: UserDefaults.optimove(), + appGroupStorage: UserDefaults.optimoveAppGroup(), + inMemoryStorage: InMemoryStorage(), + fileStorage: FileStorageImpl( + persistentStorageURL: FileManager.optimoveURL(), + temporaryStorageURL: FileManager.temporaryURL() + ) + ) + } catch { + fatalError(error.localizedDescription) + } + } + + public func getState(_ state: State) -> String { + switch state { + case .app_version: + return "\(ApplicationInfo.version) build: \(ApplicationInfo.build)" + case .sdk_version: + return Optimove.version + case .installation: + return storage.installationID ?? Constants.undefined + case .tenant: + guard let tenantID = storage.tenantID else { return Constants.undefined } + return String(tenantID) + case .initial_visitor: + return storage.initialVisitorId ?? Constants.undefined + case .customer: + return storage.customerID ?? Constants.undefined + case .email: + return storage.userEmail ?? Constants.undefined + case .update_visitor: + return storage.visitorID ?? Constants.undefined + } + } + + public subscript(state: State) -> String { + return StateService.shared.getState(state) + } +} diff --git a/Package.swift b/Package.swift index 73b21dea..d0394087 100644 --- a/Package.swift +++ b/Package.swift @@ -21,6 +21,10 @@ let package = Package( name: "OptimoveNotificationServiceExtension", targets: ["OptimoveNotificationServiceExtension"] ), + .library( + name: "OptimoveDebug", + targets: ["OptimoveDebug"] + ), ], dependencies: [ .package(url: "https://github.com/WeTransfer/Mocker", from: "3.0.1"), @@ -54,6 +58,13 @@ let package = Package( .process("Resources"), ] ), + .target( + name: "OptimoveDebug", + dependencies: [ + "OptimoveSDK", + ], + path: "OptimoveDebug/Sources" + ), .testTarget( name: "OptimoveSDKTests", dependencies: [ From a64a40a08b9b0eb954e3adcd5b50433c60eedff8 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 6 Feb 2024 13:25:25 +0200 Subject: [PATCH 23/30] feat: expose sdk state --- OptimoveDebug/Sources/ApplicationInfo.swift | 9 --- OptimoveDebug/Sources/StateService.swift | 68 ------------------- .../Extensions/Bundle+AppVersion.swift | 4 ++ OptimoveSDK/Sources/Classes/Optimove.swift | 24 ++++++- .../Sources/Classes/States/SdkState.swift | 25 +++++++ Package.swift | 11 --- 6 files changed, 52 insertions(+), 89 deletions(-) delete mode 100644 OptimoveDebug/Sources/ApplicationInfo.swift delete mode 100644 OptimoveDebug/Sources/StateService.swift create mode 100644 OptimoveSDK/Sources/Classes/States/SdkState.swift diff --git a/OptimoveDebug/Sources/ApplicationInfo.swift b/OptimoveDebug/Sources/ApplicationInfo.swift deleted file mode 100644 index 9d789498..00000000 --- a/OptimoveDebug/Sources/ApplicationInfo.swift +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © 2024 Optimove. All rights reserved. - -import Foundation - -public enum ApplicationInfo { - public static let bundleIdentifier = Bundle.main.bundleIdentifier! - public static let version = Bundle.main.infoDictionary!["CFBundleShortVersionString"] as! String - public static let build = Bundle.main.infoDictionary!["CFBundleVersion"] as! String -} diff --git a/OptimoveDebug/Sources/StateService.swift b/OptimoveDebug/Sources/StateService.swift deleted file mode 100644 index f097ada4..00000000 --- a/OptimoveDebug/Sources/StateService.swift +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright © 2024 Optimove. All rights reserved. - -import Foundation -import OptimoveCore -@testable import OptimoveSDK - -public final class StateService { - public enum State { - case app_version - case sdk_version - case installation - case tenant - case initial_visitor - case customer - case update_visitor - case email - } - - private enum Constants { - static let undefined = "" - } - - public static var shared: StateService = .init() - - private let storage: any OptimoveStorage - - private init() { - do { - storage = try StorageFacade( - standardStorage: UserDefaults.optimove(), - appGroupStorage: UserDefaults.optimoveAppGroup(), - inMemoryStorage: InMemoryStorage(), - fileStorage: FileStorageImpl( - persistentStorageURL: FileManager.optimoveURL(), - temporaryStorageURL: FileManager.temporaryURL() - ) - ) - } catch { - fatalError(error.localizedDescription) - } - } - - public func getState(_ state: State) -> String { - switch state { - case .app_version: - return "\(ApplicationInfo.version) build: \(ApplicationInfo.build)" - case .sdk_version: - return Optimove.version - case .installation: - return storage.installationID ?? Constants.undefined - case .tenant: - guard let tenantID = storage.tenantID else { return Constants.undefined } - return String(tenantID) - case .initial_visitor: - return storage.initialVisitorId ?? Constants.undefined - case .customer: - return storage.customerID ?? Constants.undefined - case .email: - return storage.userEmail ?? Constants.undefined - case .update_visitor: - return storage.visitorID ?? Constants.undefined - } - } - - public subscript(state: State) -> String { - return StateService.shared.getState(state) - } -} diff --git a/OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift b/OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift index 6970cb20..8fefc29f 100644 --- a/OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift +++ b/OptimoveSDK/Sources/Classes/Extensions/Bundle+AppVersion.swift @@ -6,4 +6,8 @@ extension Bundle { var appVersion: String { return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "undefined" } + + var buildVersion: String { + return Bundle.main.infoDictionary?["CFBundleVersion"] as? String ?? "undefined" + } } diff --git a/OptimoveSDK/Sources/Classes/Optimove.swift b/OptimoveSDK/Sources/Classes/Optimove.swift index 73efa480..57380eb8 100644 --- a/OptimoveSDK/Sources/Classes/Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Optimove.swift @@ -18,7 +18,7 @@ typealias Logger = OptimoveCore.Logger /// The shared instance of Optimove SDK. @objc public static let shared: Optimove = .init() - private let container: Container + let container: Container private var config: OptimoveConfig! override private init() { @@ -312,6 +312,28 @@ public extension Optimove { } } } + + enum Debug { + enum Constants { + static let undefined = "" + } + + public static var state: SdkState { + return Optimove.shared.container.resolve { locator in + let storage = locator.storage() + return SdkState( + appVersion: "\(Bundle.main.appVersion) build: \(Bundle.main.buildVersion)", + sdkVersion: Optimove.version, + installation: storage.installationID ?? Constants.undefined, + tenant: storage.tenantID?.description ?? Constants.undefined, + initialVisitor: storage.initialVisitorId ?? Constants.undefined, + customer: storage.customerID ?? Constants.undefined, + email: storage.userEmail ?? Constants.undefined, + updateVisitor: storage.visitorID ?? Constants.undefined + ) + } ?? SdkState.empty + } + } } // MARK: - Optimobile APIs diff --git a/OptimoveSDK/Sources/Classes/States/SdkState.swift b/OptimoveSDK/Sources/Classes/States/SdkState.swift new file mode 100644 index 00000000..7952588e --- /dev/null +++ b/OptimoveSDK/Sources/Classes/States/SdkState.swift @@ -0,0 +1,25 @@ +// Copyright © 2024 Optimove. All rights reserved. + +import Foundation + +public struct SdkState { + public let appVersion: String + public let sdkVersion: String + public let installation: String + public let tenant: String + public let initialVisitor: String + public let customer: String + public let email: String + public let updateVisitor: String + + static let empty = SdkState( + appVersion: "", + sdkVersion: "", + installation: "", + tenant: "", + initialVisitor: "", + customer: "", + email: "", + updateVisitor: "" + ) +} diff --git a/Package.swift b/Package.swift index d0394087..73b21dea 100644 --- a/Package.swift +++ b/Package.swift @@ -21,10 +21,6 @@ let package = Package( name: "OptimoveNotificationServiceExtension", targets: ["OptimoveNotificationServiceExtension"] ), - .library( - name: "OptimoveDebug", - targets: ["OptimoveDebug"] - ), ], dependencies: [ .package(url: "https://github.com/WeTransfer/Mocker", from: "3.0.1"), @@ -58,13 +54,6 @@ let package = Package( .process("Resources"), ] ), - .target( - name: "OptimoveDebug", - dependencies: [ - "OptimoveSDK", - ], - path: "OptimoveDebug/Sources" - ), .testTarget( name: "OptimoveSDKTests", dependencies: [ From 1f677e8530109ccb365962eb2cc854e9048df8fa Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 6 Feb 2024 13:26:42 +0200 Subject: [PATCH 24/30] fix: return back access level of OptimoveInApp --- OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index e2f4f9db..a530f6e6 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -81,7 +81,7 @@ struct InAppInboxSummary { typealias InboxUpdatedHandlerBlock = () -> Void typealias InboxSummaryBlock = (InAppInboxSummary?) -> Void -enum OptimoveInApp { +public enum OptimoveInApp { private static var _inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock? static func updateConsent(forUser consentGiven: Bool) { From c8fb2997e7d0ef80ee316d0e3f437ab242338f6d Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 6 Feb 2024 13:27:30 +0200 Subject: [PATCH 25/30] refactor: rename method --- .../Sources/OptimoveNotificationService.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift index 6c775a02..0140c4d7 100644 --- a/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift +++ b/OptimoveNotificationServiceExtension/Sources/OptimoveNotificationService.swift @@ -37,7 +37,7 @@ public enum OptimoveNotificationService { let data = try JSONSerialization.data(withJSONObject: userInfo) let notification = try JSONDecoder().decode(PushNotification.self, from: data) if bestAttemptContent.categoryIdentifier.isEmpty { - bestAttemptContent.categoryIdentifier = await buildCategory(notification: notification) + bestAttemptContent.categoryIdentifier = await registerCategory(notification: notification) } if let storage = try? UserDefaults.optimoveAppGroup() { let mediaHelper = MediaHelper(storage: storage) @@ -94,7 +94,7 @@ public enum OptimoveNotificationService { } } - static func buildCategory(notification: PushNotification) async -> String { + static func registerCategory(notification: PushNotification) async -> String { let categoryIdentifier = CategoryManager.getCategoryId(messageId: notification.message.id) let category = UNNotificationCategory( identifier: categoryIdentifier, From 332bb01b9c795ba8f044b1e9eec8af69afbd3381 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 6 Feb 2024 14:08:31 +0200 Subject: [PATCH 26/30] docs: update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06d748d2..e51ffdff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## 6.0.0 + +- Minimum supported iOS version is now 13.0. + ## 5.6.0 - Support the delayed configuration for SDK. Add new public APIs: From 1f2427de5fa3a8e1c0f9736947f7c49cfdf119ba Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Wed, 7 Feb 2024 12:54:34 +0200 Subject: [PATCH 27/30] chore: move back access level of in-app --- .../Classes/Optimobile/OptimoveInApp.swift | 61 +++++++++++-------- 1 file changed, 34 insertions(+), 27 deletions(-) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index a530f6e6..b233a8d4 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -4,15 +4,15 @@ import CoreData import Foundation import OptimoveCore -class InAppInboxItem { - private(set) var id: Int64 - private(set) var title: String - private(set) var subtitle: String - private(set) var availableFrom: Date? - private(set) var availableTo: Date? - private(set) var dismissedAt: Date? - private(set) var sentAt: Date - private(set) var data: NSDictionary? +public class InAppInboxItem { + public private(set) var id: Int64 + public private(set) var title: String + public private(set) var subtitle: String + public private(set) var availableFrom: Date? + public private(set) var availableTo: Date? + public private(set) var dismissedAt: Date? + public private(set) var sentAt: Date + public private(set) var data: NSDictionary? private var readAt: Date? private var imagePath: String? @@ -43,7 +43,7 @@ class InAppInboxItem { imagePath = inboxConfig["imagePath"] as? String } - func isAvailable() -> Bool { + public func isAvailable() -> Bool { if availableFrom != nil, availableFrom!.timeIntervalSinceNow > 0 { return false } else if availableTo != nil, availableTo!.timeIntervalSinceNow < 0 { @@ -53,15 +53,15 @@ class InAppInboxItem { return true } - func isRead() -> Bool { + public func isRead() -> Bool { return readAt != nil } - func getImageUrl() -> URL? { + public func getImageUrl() -> URL? { return getImageUrl(width: InAppInboxItem.defaultImageWidth) } - func getImageUrl(width: UInt) -> URL? { + public func getImageUrl(width: UInt) -> URL? { if let imagePathNotNil = imagePath { return try? mediaHelper.getCompletePictureUrl( pictureUrlString: imagePathNotNil, @@ -73,18 +73,18 @@ class InAppInboxItem { } } -struct InAppInboxSummary { - let totalCount: Int64 - let unreadCount: Int64 +public struct InAppInboxSummary { + public let totalCount: Int64 + public let unreadCount: Int64 } -typealias InboxUpdatedHandlerBlock = () -> Void -typealias InboxSummaryBlock = (InAppInboxSummary?) -> Void +public typealias InboxUpdatedHandlerBlock = () -> Void +public typealias InboxSummaryBlock = (InAppInboxSummary?) -> Void public enum OptimoveInApp { private static var _inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock? - static func updateConsent(forUser consentGiven: Bool) { + public static func updateConsent(forUser consentGiven: Bool) { if Optimobile.inAppConsentStrategy != InAppConsentStrategy.explicitByUser { NSException(name: NSExceptionName(rawValue: "Optimobile: Invalid In-app consent strategy"), reason: "You can only manage in-app messaging consent when the feature is enabled and strategy is set to InAppConsentStrategyExplicitByUser", userInfo: nil).raise() @@ -94,14 +94,21 @@ public enum OptimoveInApp { Optimobile.sharedInstance.inAppManager.updateUserConsent(consentGiven: consentGiven) } - static func setDisplayMode(mode: InAppDisplayMode) { + public static func setDisplayMode(mode: InAppDisplayMode) { Optimobile.sharedInstance.inAppManager.presenter.setDisplayMode(mode) } - static func getDisplayMode() -> InAppDisplayMode { + public static func getDisplayMode() -> InAppDisplayMode { return Optimobile.sharedInstance.inAppManager.presenter.getDisplayMode() } + public static func getInboxItems() -> [InAppInboxItem] { + return Optimove.shared.container.resolve { locator in + let storage = locator.storage() + return getInboxItems(storage: storage) + } ?? [] + } + static func getInboxItems(storage: OptimoveStorage) -> [InAppInboxItem] { guard let context = Optimobile.sharedInstance.inAppManager.messagesContext else { return [] @@ -145,7 +152,7 @@ public enum OptimoveInApp { return results } - static func presentInboxMessage(item: InAppInboxItem) -> InAppMessagePresentationResult { + public static func presentInboxMessage(item: InAppInboxItem) -> InAppMessagePresentationResult { if getDisplayMode() == .paused { return .PAUSED } @@ -159,11 +166,11 @@ public enum OptimoveInApp { return result ? InAppMessagePresentationResult.PRESENTED : InAppMessagePresentationResult.FAILED } - static func deleteMessageFromInbox(item: InAppInboxItem) -> Bool { + public static func deleteMessageFromInbox(item: InAppInboxItem) -> Bool { return Optimobile.sharedInstance.inAppManager.deleteMessageFromInbox(withId: item.id) } - static func markAsRead(item: InAppInboxItem) -> Bool { + public static func markAsRead(item: InAppInboxItem) -> Bool { if item.isRead() { return false } @@ -173,15 +180,15 @@ public enum OptimoveInApp { return res } - static func markAllInboxItemsAsRead() -> Bool { + public static func markAllInboxItemsAsRead() -> Bool { return Optimobile.sharedInstance.inAppManager.markAllInboxItemsAsRead() } - static func setOnInboxUpdated(inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock?) { + public static func setOnInboxUpdated(inboxUpdatedHandlerBlock: InboxUpdatedHandlerBlock?) { _inboxUpdatedHandlerBlock = inboxUpdatedHandlerBlock } - static func getInboxSummaryAsync(inboxSummaryBlock: @escaping InboxSummaryBlock) { + public static func getInboxSummaryAsync(inboxSummaryBlock: @escaping InboxSummaryBlock) { Optimobile.sharedInstance.inAppManager.readInboxSummary(inboxSummaryBlock: inboxSummaryBlock) } From 6efe532d2b7b87d34d87b04f875f2898ae88a0e9 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Wed, 7 Feb 2024 12:55:08 +0200 Subject: [PATCH 28/30] chore: add in-app into optimove class --- OptimoveSDK/Sources/Classes/Optimove.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/OptimoveSDK/Sources/Classes/Optimove.swift b/OptimoveSDK/Sources/Classes/Optimove.swift index 57380eb8..daa193b6 100644 --- a/OptimoveSDK/Sources/Classes/Optimove.swift +++ b/OptimoveSDK/Sources/Classes/Optimove.swift @@ -12,6 +12,7 @@ typealias Logger = OptimoveCore.Logger /// - WARNING: /// To initialize and configure SDK using `Optimove.configure(for:)` first. @objc public final class Optimove: NSObject { + public typealias InApp = OptimoveInApp /// The current OptimoveSDK version string value. public static let version = OptimoveCore.SDKVersion From 4323989a8bb8d8069d8f54aab40f0ecbc7ca4b30 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Wed, 7 Feb 2024 17:22:38 +0200 Subject: [PATCH 29/30] fix: switch in-app manager to optimove storage --- .../Optimobile/InApp/InAppManager.swift | 45 ++++++++++--------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift index 19d748c5..7b6d6059 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/InApp/InAppManager.swift @@ -173,7 +173,7 @@ class InAppManager { func userConsented() -> Bool { // Note if this implementation is changed there is a usage in the main Optimobile initialisation path // that should be considered. - return UserDefaults.standard.bool(forKey: OptimobileUserDefaultsKey.IN_APP_CONSENTED.rawValue) + return storage[.inAppConsented] ?? false } func updateUserConsent(consentGiven: Bool) { @@ -182,7 +182,7 @@ class InAppManager { Optimobile.trackEventImmediately(eventType: OptimobileEvent.IN_APP_CONSENT_CHANGED.rawValue, properties: props) if consentGiven { - UserDefaults.standard.set(consentGiven, forKey: OptimobileUserDefaultsKey.IN_APP_CONSENTED.rawValue) + storage.set(value: consentGiven, key: .inAppConsented) handleEnrollmentAndSyncSetup() } else { DispatchQueue.global(qos: .default).async { @@ -237,9 +237,9 @@ class InAppManager { } NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) - UserDefaults.standard.removeObject(forKey: OptimobileUserDefaultsKey.IN_APP_CONSENTED.rawValue) - UserDefaults.standard.removeObject(forKey: OptimobileUserDefaultsKey.IN_APP_LAST_SYNCED_AT.rawValue) - UserDefaults.standard.removeObject(forKey: OptimobileUserDefaultsKey.IN_APP_MOST_RECENT_UPDATED_AT.rawValue) + storage.set(value: nil, key: .inAppConsented) + storage.set(value: nil, key: .inAppLastSyncedAt) + storage.set(value: nil, key: .inAppMostRecentUpdateAt) context.performAndWait { let fetchRequest: NSFetchRequest = NSFetchRequest(entityName: "Message") @@ -267,23 +267,23 @@ class InAppManager { // MARK: Message management func syncDebounced(_ onComplete: InAppSyncCompletionHandler? = nil) { - syncQueue.async { - let lastSyncedAt = UserDefaults.standard.object(forKey: OptimobileUserDefaultsKey.IN_APP_LAST_SYNCED_AT.rawValue) as? Date ?? Date(timeIntervalSince1970: 0) - - if lastSyncedAt.timeIntervalSinceNow < self.SYNC_DEBOUNCE_SECONDS { + syncQueue.async { [unowned self] in + let lastSyncedAt = storage.value(for: .inAppLastSyncedAt) as? Date ?? Date(timeIntervalSince1970: 0) + if lastSyncedAt.timeIntervalSinceNow < SYNC_DEBOUNCE_SECONDS { return } - self.sync(onComplete) + sync(onComplete) } } func sync(_ onComplete: InAppSyncCompletionHandler? = nil) { let currentUserIdentifier = optimobileHelper.currentUserIdentifier() - syncQueue.async { + syncQueue.async { [unowned self] in let syncBarrier = DispatchSemaphore(value: 0) - let mostRecentUpdate = UserDefaults.standard.object(forKey: OptimobileUserDefaultsKey.IN_APP_MOST_RECENT_UPDATED_AT.rawValue) as? NSDate + let mostRecentUpdate = storage.value(for: .inAppMostRecentUpdateAt) as? Date + var after = "" if let mostRecentUpdate = mostRecentUpdate { @@ -298,9 +298,9 @@ class InAppManager { let encodedIdentifier = KSHttpUtil.urlEncode(currentUserIdentifier) let path = "/v1/users/\(encodedIdentifier!)/messages\(after)" - self.httpClient.sendRequest(.GET, toPath: path, data: nil, onSuccess: { _, decodedBody in + httpClient.sendRequest(.GET, toPath: path, data: nil, onSuccess: { [weak self] _, decodedBody in defer { - UserDefaults.standard.set(Date(), forKey: OptimobileUserDefaultsKey.IN_APP_LAST_SYNCED_AT.rawValue) + self?.storage.set(value: Date(), key: .inAppLastSyncedAt) syncBarrier.signal() } @@ -310,7 +310,7 @@ class InAppManager { return } - self.persistInAppMessages(messages: messagesToPersist!) + self?.persistInAppMessages(messages: messagesToPersist!) onComplete?(1) DispatchQueue.main.async { @@ -318,9 +318,15 @@ class InAppManager { return } - DispatchQueue.global(qos: .default).async { - let messagesToPresent = self.getMessagesToPresent([InAppPresented.IMMEDIATELY.rawValue]) - self.presenter.queueMessagesForPresentation(messages: messagesToPresent, tickleIds: self.pendingTickleIds) + DispatchQueue.global(qos: .default).async { [weak self] in + if let messagesToPresent = self?.getMessagesToPresent([InAppPresented.IMMEDIATELY.rawValue]), + let pendingTickleIds = self?.pendingTickleIds + { + self?.presenter.queueMessagesForPresentation( + messages: messagesToPresent, + tickleIds: pendingTickleIds + ) + } } } }, onFailure: { _, _, _ in @@ -447,8 +453,7 @@ class InAppManager { removeNotificationTickle(id: idEvicted) } - UserDefaults.standard.set(mostRecentUpdate, forKey: OptimobileUserDefaultsKey.IN_APP_MOST_RECENT_UPDATED_AT.rawValue) - + storage.set(value: mostRecentUpdate, key: .inAppMostRecentUpdateAt) trackMessageDelivery(messages: messages) let inboxUpdated = fetchedWithInbox || evictedWithInbox || evictedExceedersWithInbox From a6278a4278855bf37a8d92c2f4be9778232414b1 Mon Sep 17 00:00:00 2001 From: Eli Gutovsky Date: Tue, 26 Mar 2024 21:19:14 +0200 Subject: [PATCH 30/30] add identifiable --- OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift index b233a8d4..39fa95f2 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveInApp.swift @@ -4,7 +4,7 @@ import CoreData import Foundation import OptimoveCore -public class InAppInboxItem { +public class InAppInboxItem: Identifiable { public private(set) var id: Int64 public private(set) var title: String public private(set) var subtitle: String @@ -103,8 +103,8 @@ public enum OptimoveInApp { } public static func getInboxItems() -> [InAppInboxItem] { - return Optimove.shared.container.resolve { locator in - let storage = locator.storage() + return Optimove.shared.container.resolve { container in + let storage = container.storage() return getInboxItems(storage: storage) } ?? [] }