Skip to content
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
33 changes: 0 additions & 33 deletions OptimoveCore/Sources/Classes/AppGroupsHelper.swift

This file was deleted.

15 changes: 13 additions & 2 deletions OptimoveCore/Sources/Classes/Extension/Bundle+HostApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,27 @@
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
}
}
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"
}
}
59 changes: 0 additions & 59 deletions OptimoveCore/Sources/Classes/KeyValPersistenceHelper.swift

This file was deleted.

12 changes: 9 additions & 3 deletions OptimoveCore/Sources/Classes/MediaHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,27 @@

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)
}
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
}

Expand Down
63 changes: 26 additions & 37 deletions OptimoveCore/Sources/Classes/OptimobileHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand All @@ -27,40 +32,24 @@ 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? {
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 = KeyValPersistenceHelper.object(forKey: OptimobileUserDefaultsKey.BADGE_COUNT.rawValue) as? NSNumber {
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
}
}
5 changes: 1 addition & 4 deletions OptimoveCore/Sources/Classes/OptimobileUserDefaultsKey.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@

import Foundation

public enum OptimobileUserDefaultsKey: String {
public enum OptimobileUserDefaultsKey: String, CaseIterable {
case REGION = "KumulosEventsRegion"
case MEDIA_BASE_URL = "KumulosMediaBaseUrl"
case INSTALL_UUID = "KumulosUUID"
case USER_ID = "KumulosCurrentUserID"
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"
Expand Down
24 changes: 15 additions & 9 deletions OptimoveCore/Sources/Classes/PendingNotificationHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }) {
Expand All @@ -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 }) {
Expand All @@ -23,18 +29,18 @@ 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
}

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 }) {
Expand All @@ -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)
}
}
}
6 changes: 3 additions & 3 deletions OptimoveCore/Sources/Classes/PushNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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)
Expand Down
19 changes: 19 additions & 0 deletions OptimoveCore/Sources/Classes/Storage/FileManager+AppGroup.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// Copyright © 2023 Optimove. All rights reserved.

import Foundation

public extension FileManager {
static func optimoveAppGroupURL() throws -> 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
"""
assertionFailure(message)
throw GuardError.custom(message)
}
return url
}
}
Loading