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/Sources/PushNotification.swift b/OptimobileCore/Sources/PushNotification.swift new file mode 100644 index 00000000..39d5a366 --- /dev/null +++ b/OptimobileCore/Sources/PushNotification.swift @@ -0,0 +1,117 @@ +// Copyright © 2023 Optimove. All rights reserved. + +import Foundation + +/// Represents a push notification received from the server. +public struct PushNotification: Decodable { + 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 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) + 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 + } + } + + public struct Attachment: Decodable { + public let pictureUrl: String? + } + + 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 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 url: URL? + + private enum CodingKeys: String, CodingKey { + case a + case aps + case attachments + case badge = "badge_inc" + case buttons = "k.buttons" + case custom + case deeplink = "k.deepLink" + case message = "k.message" + 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) + 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.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) + } +} 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/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift b/OptimobileCore/Tests/PushNotificationTests.swift similarity index 70% rename from OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift rename to OptimobileCore/Tests/PushNotificationTests.swift index 829a83bc..c8d068e5 100644 --- a/OptimoveNotificationServiceExtension/Tests/Sources/PushNotificationTests.swift +++ b/OptimobileCore/Tests/PushNotificationTests.swift @@ -1,17 +1,17 @@ // Copyright © 2023 Optimove. All rights reserved. -@testable import OptimoveNotificationServiceExtension +import OptimobileCore import OptimoveTest 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 { @@ -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 { @@ -54,6 +54,22 @@ 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 { + 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/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..10a13472 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 @@ -45,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 ) ) @@ -85,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), @@ -98,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, 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/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..573331de 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 { @@ -670,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/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..7c5b5fc4 100644 --- a/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift +++ b/OptimoveSDK/Sources/Classes/Optimobile/Optimobile+Push.swift @@ -2,82 +2,10 @@ import Foundation import ObjectiveC.runtime +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 @@ -208,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] + let params = ["type": KS_MESSAGE_TYPE_PUSH, "id": notification.message.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.message.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) { @@ -268,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.message.id) + PendingNotificationHelper.remove(id: notification.message.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, *) @@ -302,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.message.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)") } } } @@ -399,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)) @@ -471,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/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/OptimoveUserNotificationCenterDelegate.swift b/OptimoveSDK/Sources/Classes/Optimobile/OptimoveUserNotificationCenterDelegate.swift index fb87420f..101f90d9 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,18 @@ 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 Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock == nil { + completionHandler(.alert) + return + } - if Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock == nil { - completionHandler(.alert) - return + Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock?(push, completionHandler) + } catch { + chainCenter(center, willPresent: notification, with: completionHandler) } - - Optimobile.sharedInstance.config.pushReceivedInForegroundHandlerBlock?(push, completionHandler) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { @@ -36,7 +36,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 +46,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/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..fa4253a0 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,18 @@ let package = Package( name: "OptimoveCore", path: "OptimoveCore/Sources" ), + .target( + name: "OptimobileCore", + path: "OptimobileCore/Sources", + resources: [ + .process("Resources"), + ] + ), .target( name: "OptimoveNotificationServiceExtension", + dependencies: [ + "OptimobileCore", + ], path: "OptimoveNotificationServiceExtension/Sources" ), .target( @@ -75,6 +86,17 @@ let package = Package( .process("Resources"), ] ), + .testTarget( + name: "OptimobileCoreTests", + dependencies: [ + "OptimobileCore", + "OptimoveTest", + ], + path: "OptimobileCore/Tests", + resources: [ + .process("Resources"), + ] + ), .testTarget( name: "OptimoveNotificationServiceExtensionTests", dependencies: [