From 4f98bc8b68b77d099c7d98f89f0a424157a85a81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rover=20Release=20Bot=20=F0=9F=A4=96?= Date: Wed, 18 Dec 2024 18:59:42 +0000 Subject: [PATCH] Releasing 4.9.0 --- Sources/AXS/AXSAuthorizer.swift | 16 ++++- Sources/AXS/AXSManager.swift | 67 +++++++++++++------ Sources/Data/Context/ContextManager.swift | 4 +- .../Providers/StaticContextProvider.swift | 2 +- Sources/Data/EventQueue/EventQueue.swift | 4 ++ .../Experiences/Model/ExperienceModel.swift | 5 +- .../Experiences/UI/CarouselState.swift | 17 +++-- .../Experiences/UI/ImageFetcher.swift | 8 +-- .../Experiences/UI/ScreenViewController.swift | 2 +- .../ExperienceStoreService.swift | 3 +- Sources/Foundation/Meta.swift | 2 +- .../NotificationsSyncParticipant.swift | 6 +- 12 files changed, 94 insertions(+), 42 deletions(-) diff --git a/Sources/AXS/AXSAuthorizer.swift b/Sources/AXS/AXSAuthorizer.swift index 1ba27eff..30f08314 100644 --- a/Sources/AXS/AXSAuthorizer.swift +++ b/Sources/AXS/AXSAuthorizer.swift @@ -18,13 +18,23 @@ public protocol AXSAuthorizer { /** Set the user's AXS credentials after a successful sign-in. - - Parameters: - - userId: The value of the `userId` property. + - Parameter userId: The value of the `userID` property. */ + @available(*, deprecated, renamed: "setUserID") func setUserId(_ userId: String) - + + /** + Set the user's AXS credentials after a successful sign-in. If `userID` is nil, then it is treated as a sign out. + + - Parameter userID: The value of the `userID` property. + - Parameter flashMemberID: A Flash Seats Member ID. + - Parameter flashMobileID: A Flash Seats Mobile ID. + */ + func setUserID(_ userID: String?, flashMemberID: String?, flashMobileID: String?) + /** Clear the user's AXS credentials after a successful sign-out. */ func clearCredentials() } + diff --git a/Sources/AXS/AXSManager.swift b/Sources/AXS/AXSManager.swift index ff760e46..af1443ed 100644 --- a/Sources/AXS/AXSManager.swift +++ b/Sources/AXS/AXSManager.swift @@ -23,46 +23,71 @@ class AXSManager: AXSAuthorizer, PrivacyListener { private let privacyService: PrivacyService private var userID = PersistedValue(storageKey: "io.rover.axs") - + private var flashMemberID = PersistedValue(storageKey: "io.rover.axs.flashMemberID") + private var flashMobileID = PersistedValue(storageKey: "io.rover.axs.flashMobileID") + private var axsUserInfo: [String: String]? { - guard let userID = self.userID.value else { - return nil + var dictionary = [String: String]() + + if let userID = self.userID.value { + dictionary["userID"] = userID + } + + if let flashMemberID = self.flashMemberID.value { + dictionary["flashMemberID"] = flashMemberID + } + + if let flashMobileID = self.flashMobileID.value { + dictionary["flashMobileID"] = flashMobileID } - return ["userID": userID] + return dictionary } - + init(userInfoManager: UserInfoManager, privacyService: PrivacyService) { self.userInfoManager = userInfoManager self.privacyService = privacyService } // MARK: AxsAuthorizer - + func setUserId(_ id: String) { + setUserID(id, flashMemberID: nil, flashMobileID: nil) + } + + func setUserID(_ userID: String?, flashMemberID: String?, flashMobileID: String?) { guard privacyService.trackingMode == .default else { return } - self.userID.value = id + guard let userID else { + clearCredentials() + return + } + + self.userID.value = userID + self.flashMemberID.value = flashMemberID + self.flashMobileID.value = flashMobileID + + guard let userInfo = axsUserInfo else { + return + } + + updateUserInfo(userInfo) - if let userInfo = axsUserInfo { - self.userInfoManager.updateUserInfo { - if let existingAxsUserInfo = $0.rawValue["axs"] as? Attributes { - // axs data already exists, just clobber it: - $0.rawValue["axs"] = Attributes(rawValue: existingAxsUserInfo.rawValue.merging(userInfo) { $1 }) - } else { - // axs data does not already exist, so set it: - $0.rawValue["axs"] = Attributes(rawValue: userInfo) - } - } - - os_log("AXSID has been set: %s", log: .general, userID.value!) + os_log("AXS IDs have been set. user ID: %s, flashMemberID: %s, flashMobileID: %s", log: .general, userID, flashMemberID ?? "nil", flashMobileID ?? "nil") + } + + private func updateUserInfo(_ userInfo: [String: String]) { + self.userInfoManager.updateUserInfo { + $0.rawValue["axs"] = Attributes(rawValue: userInfo) } } - + func clearCredentials() { self.userID.value = nil + self.flashMemberID.value = nil + self.flashMobileID.value = nil self.userInfoManager.updateUserInfo { attributes in attributes.rawValue["axs"] = nil } @@ -72,7 +97,7 @@ class AXSManager: AXSAuthorizer, PrivacyListener { func trackingModeDidChange(_ trackingMode: PrivacyService.TrackingMode) { if(trackingMode != .default) { - os_log("Tracking disabled, axs data cleared", log: .axs) + os_log("Tracking disabled, AXS data cleared", log: .axs) clearCredentials() } } diff --git a/Sources/Data/Context/ContextManager.swift b/Sources/Data/Context/ContextManager.swift index 03ebbc0b..7ab7e2df 100644 --- a/Sources/Data/Context/ContextManager.swift +++ b/Sources/Data/Context/ContextManager.swift @@ -160,8 +160,8 @@ extension ContextManager: StaticContextProvider { #endif } - var deviceIdentifier: String { - return UIDevice.current.identifierForVendor!.uuidString + var deviceIdentifier: String? { + return UIDevice.current.identifierForVendor?.uuidString } var deviceManufacturer: String { diff --git a/Sources/Data/Context/Providers/StaticContextProvider.swift b/Sources/Data/Context/Providers/StaticContextProvider.swift index a92afb4c..41ebf20f 100644 --- a/Sources/Data/Context/Providers/StaticContextProvider.swift +++ b/Sources/Data/Context/Providers/StaticContextProvider.swift @@ -21,7 +21,7 @@ public protocol StaticContextProvider: AnyObject { var appIdentifier: String { get } var appVersion: String { get } var buildEnvironment: Context.BuildEnvironment { get } - var deviceIdentifier: String { get } + var deviceIdentifier: String? { get } var deviceManufacturer: String { get } var deviceModel: String { get } var deviceName: String { get } diff --git a/Sources/Data/EventQueue/EventQueue.swift b/Sources/Data/EventQueue/EventQueue.swift index dd5b1396..f2712d9c 100644 --- a/Sources/Data/EventQueue/EventQueue.swift +++ b/Sources/Data/EventQueue/EventQueue.swift @@ -130,6 +130,10 @@ public class EventQueue { public func addEvent(_ info: EventInfo) { let context = self.contextProvider.context + guard context.deviceIdentifier != nil else { + return + } + let event = Event( name: info.name, context: context, diff --git a/Sources/Experiences/Experiences/Model/ExperienceModel.swift b/Sources/Experiences/Experiences/Model/ExperienceModel.swift index d7f00c21..a4361db8 100644 --- a/Sources/Experiences/Experiences/Model/ExperienceModel.swift +++ b/Sources/Experiences/Experiences/Model/ExperienceModel.swift @@ -33,7 +33,7 @@ final class ExperienceModel: Decodable { var gradients = [DocumentGradient]() var localization = StringTable() var fonts = [DocumentFont]() - + /// Font download URLs var fontURLs: [URL] { fonts.flatMap { $0.sources.map {$0.assetUrl} } @@ -54,6 +54,9 @@ final class ExperienceModel: Decodable { return initialScreen!.id } + /// The URL this experience was loaded from. + /// + /// NB. Unlike the other fields, this one is not populated from the contents of the experience JSON. var sourceUrl: URL? /// Initialize Experience from data (JSON) diff --git a/Sources/Experiences/Experiences/UI/CarouselState.swift b/Sources/Experiences/Experiences/UI/CarouselState.swift index 0fd563f7..de063ea3 100644 --- a/Sources/Experiences/Experiences/UI/CarouselState.swift +++ b/Sources/Experiences/Experiences/UI/CarouselState.swift @@ -13,6 +13,7 @@ // IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +import Foundation import Combine import RoverFoundation @@ -22,16 +23,21 @@ final class CarouselState: ObservableObject { @Published var currentNumberOfPagesForCarousel: [ViewID: Int] = [:] @Published var storyStyleStatusForCarousel: [ViewID: Bool] = [:] @Published var currentBarProgressForCarousel: [ViewID: [Double]] = [:] - private let experienceId: String? + private let experienceUrl: String? private let persistedCarouselPositions = PersistedValue<[String: Int]>(storageKey: "io.rover.experience.carouselPositions") - init(experienceId: String?) { - self.experienceId = experienceId + init(experienceUrl: String?) { + self.experienceUrl = experienceUrl + } + + private func persistenceKey(for viewID: ViewID) -> String { + let urlBase64 = experienceUrl.flatMap { $0.data(using: .utf8)?.base64EncodedString() } + return "\(urlBase64 ?? "unknown")-\(viewID.toString())" } func setPersistedPosition(for viewID: ViewID, newValue: Int) { - let carouselIdentifier = "\(experienceId ?? "local")-\(viewID.toString())" + let carouselIdentifier = persistenceKey(for: viewID) guard var carouselPositions = persistedCarouselPositions.value else { persistedCarouselPositions.value = [carouselIdentifier: newValue] @@ -43,8 +49,7 @@ final class CarouselState: ObservableObject { } func getPersistedPosition(for viewID: ViewID) -> Int { - let carouselIdentifier = "\(experienceId ?? "local")-\(viewID.toString())" - + let carouselIdentifier = persistenceKey(for: viewID) guard let carouselPositions = persistedCarouselPositions.value, let value = carouselPositions[carouselIdentifier] else { return 0 diff --git a/Sources/Experiences/Experiences/UI/ImageFetcher.swift b/Sources/Experiences/Experiences/UI/ImageFetcher.swift index 02eacc18..6c9d568c 100644 --- a/Sources/Experiences/Experiences/UI/ImageFetcher.swift +++ b/Sources/Experiences/Experiences/UI/ImageFetcher.swift @@ -39,17 +39,17 @@ struct ImageFetcher: View where Content: View, Placeholder var body: some View { if let uiImage = uiImage { content(uiImage) - .onValueChanged(of: url) { _ in - startFetch() + .onValueChanged(of: url) { url in + startFetch(url: url) } } else { placeholder.onAppear { - startFetch() + startFetch(url: url) } } } - private func startFetch() { + private func startFetch(url: URL) { let experienceManager = Rover.shared.resolve(ExperienceManager.self)! func setState(_ state: FetchState) { diff --git a/Sources/Experiences/Experiences/UI/ScreenViewController.swift b/Sources/Experiences/Experiences/UI/ScreenViewController.swift index 68355b2a..c1c7b965 100644 --- a/Sources/Experiences/Experiences/UI/ScreenViewController.swift +++ b/Sources/Experiences/Experiences/UI/ScreenViewController.swift @@ -35,7 +35,7 @@ class ScreenViewController: UIViewController, UIScrollViewDelegate { self.urlParameters = urlParameters self.userInfo = userInfo self.authorize = authorize - self.carouselState = CarouselState(experienceId: experience.id) + self.carouselState = CarouselState(experienceUrl: experience.sourceUrl?.absoluteString) super.init(nibName: nil, bundle: nil) super.restorationIdentifier = screen.id } diff --git a/Sources/Experiences/Services/ExperienceStore/ExperienceStoreService.swift b/Sources/Experiences/Services/ExperienceStore/ExperienceStoreService.swift index a8e8c985..4c578bc1 100644 --- a/Sources/Experiences/Services/ExperienceStore/ExperienceStoreService.swift +++ b/Sources/Experiences/Services/ExperienceStore/ExperienceStoreService.swift @@ -79,6 +79,7 @@ class ExperienceStoreService: ExperienceStore { if url.isFileURL { do { if let experienceObj = try read(contentsOf: url) { + experienceObj.sourceUrl = url let experience = LoadedExperience.file( experience: experienceObj, urlParameters: experienceObj.urlParameters, @@ -156,7 +157,7 @@ class ExperienceStoreService: ExperienceStore { completionHandler(.failure(.invalidExperienceData(error))) return } - + let key = CacheKey(url: experienceUrl) let value = CacheValue(experience: experience) self.cache.setObject(value, forKey: key) diff --git a/Sources/Foundation/Meta.swift b/Sources/Foundation/Meta.swift index 7ff381b1..053b09b7 100644 --- a/Sources/Foundation/Meta.swift +++ b/Sources/Foundation/Meta.swift @@ -17,5 +17,5 @@ import Foundation public enum Meta { public static let APIVersion: Int = 2 - public static let SDKVersion: String = "4.8.1" + public static let SDKVersion: String = "4.9.0" } diff --git a/Sources/Notifications/Services/NotificationsSyncParticipant.swift b/Sources/Notifications/Services/NotificationsSyncParticipant.swift index ad7b16f6..3b47079a 100644 --- a/Sources/Notifications/Services/NotificationsSyncParticipant.swift +++ b/Sources/Notifications/Services/NotificationsSyncParticipant.swift @@ -26,6 +26,10 @@ class NotificationsSyncParticipant: SyncParticipant { } func initialRequest() -> SyncRequest? { + guard let uuidString = UIDevice.current.identifierForVendor?.uuidString else { + return nil + } + let orderBy: Attributes = [ "field": "CREATED_AT", "direction": "DESC" @@ -36,7 +40,7 @@ class NotificationsSyncParticipant: SyncParticipant { values: [ "last": 500, "orderBy": orderBy, - "deviceIdentifier": UIDevice.current.identifierForVendor!.uuidString + "deviceIdentifier": uuidString ] ) }