diff --git a/Airship/AirshipCore/Source/Config.swift b/Airship/AirshipCore/Source/Config.swift index ea995022d..2b529f61f 100644 --- a/Airship/AirshipCore/Source/Config.swift +++ b/Airship/AirshipCore/Source/Config.swift @@ -301,7 +301,15 @@ public class AirshipConfig: NSObject, NSCopying { /// Defaults to `false`. @objc public var useUserPreferredLocale = false - + + /// If set to `true`, Message Center will attempt to be restored between reinstalls. If `false`, + /// the Message Center user will be reset and the Channel will not be able to use the user + /// as an identity hint to recover the past Channel ID. + /// + /// Defaults to `true`. + @objc + public var restoreMessageCenterOnReinstall = true + /// Returns the resolved app key. /// - Returns: The resolved app key or an empty string. @objc @@ -444,6 +452,7 @@ public class AirshipConfig: NSObject, NSCopying { _inProduction = config._inProduction autoPauseInAppAutomationOnLaunch = config.autoPauseInAppAutomationOnLaunch useUserPreferredLocale = config.useUserPreferredLocale + restoreMessageCenterOnReinstall = config.restoreMessageCenterOnReinstall } public func copy(with zone: NSZone? = nil) -> Any { @@ -488,7 +497,8 @@ public class AirshipConfig: NSObject, NSCopying { Site: %ld\n\ Enabled features: %ld\n\ Reset enabled features: %ld\n\ - Use user preferred locale: %d\n + Use user preferred locale: %d\n\ + Restore Message Center on reinstall: %d\n """, inProduction, inProduction, @@ -525,7 +535,8 @@ public class AirshipConfig: NSObject, NSCopying { site.rawValue, enabledFeatures.rawValue, resetEnabledFeatures ? "YES" : "NO", - useUserPreferredLocale + useUserPreferredLocale, + restoreMessageCenterOnReinstall ? "YES" : "NO" ) } diff --git a/Airship/AirshipCore/Source/RuntimeConfig.swift b/Airship/AirshipCore/Source/RuntimeConfig.swift index 9a235b13c..42d21d692 100644 --- a/Airship/AirshipCore/Source/RuntimeConfig.swift +++ b/Airship/AirshipCore/Source/RuntimeConfig.swift @@ -161,8 +161,16 @@ public final class RuntimeConfig: NSObject, @unchecked Sendable { /// /// Defaults to `false`. @objc - public var useUserPreferredLocale = false - + public var useUserPreferredLocale: Bool + + /// If set to `true`, Message Center will attempt to be restored between reinstalls. If `false`, + /// the Message Center user will be reset and the Channel will not be able to use the user + /// as an identity hint to recover the past Channel ID. + /// + /// Defaults to `true`. + @objc + public var restoreMessageCenterOnReinstall: Bool + private let site: CloudSite private let remoteConfigCache: RemoteConfigCache private let notificationCenter: NotificationCenter @@ -316,6 +324,7 @@ public final class RuntimeConfig: NSObject, @unchecked Sendable { self.defaultRemoteDataAPIURL = config.remoteDataAPIURL?.normalizeURLString() } self.useUserPreferredLocale = config.useUserPreferredLocale + self.restoreMessageCenterOnReinstall = config.restoreMessageCenterOnReinstall self.remoteConfigCache = RemoteConfigCache(dataStore: dataStore) self.notificationCenter = notificationCenter super.init() diff --git a/Airship/AirshipMessageCenter/Source/MessageCenterList.swift b/Airship/AirshipMessageCenter/Source/MessageCenterList.swift index 773b04594..dbd235131 100644 --- a/Airship/AirshipMessageCenter/Source/MessageCenterList.swift +++ b/Airship/AirshipMessageCenter/Source/MessageCenterList.swift @@ -111,7 +111,7 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { private let notificationCenter: NotificationCenter private let date: AirshipDateProtocol private let workManager: AirshipWorkManagerProtocol - + private let startUpTask: Task? private let _enabled: AirshipAtomicValue = AirshipAtomicValue(false) var enabled: Bool { get { @@ -221,6 +221,14 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { self.date = date self.workManager = workManager + self.startUpTask = if channel.identifier == nil, !config.restoreMessageCenterOnReinstall { + Task { [weak store] in + await store?.resetUser() + } + } else { + nil + } + super.init() workManager.registerWorker( @@ -251,10 +259,9 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { object: nil, queue: nil ) { [weak self] _ in - self? - .dispatchUpdateWorkRequest( - conflictPolicy: .replace - ) + self?.dispatchUpdateWorkRequest( + conflictPolicy: .replace + ) } Task { @MainActor [weak self] in @@ -269,6 +276,7 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { } self.channel.addRegistrationExtender { [weak self] payload in + await self?.startUpTask?.value guard self?.enabled == true, let user = await self?.store.user else { @@ -287,6 +295,9 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { return payload } + + + } convenience init( @@ -328,6 +339,7 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { return nil } + await self.startUpTask?.value return await self.store.user } @@ -463,6 +475,7 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { guard let user = response.result else { return nil } + await self.store.setUserRequireUpdate(false) await self.store.saveUser(user, channelID: channelID) return user } catch { @@ -479,6 +492,7 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { guard requireUpdate || channelMismatch else { return user } + do { AirshipLogger.debug("Updating Message Center user") let response = try await self.client.updateUser( @@ -493,7 +507,9 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { guard response.isSuccess else { return nil } - await self.store.setUserRequireUpdate(true) + + await self.store.setUserRegisteredChannelID(channelID) + await self.store.setUserRequireUpdate(false) return user } catch { AirshipLogger.info("Failed to update Message Center user: \(error)") @@ -502,6 +518,8 @@ final class MessageCenterInbox: NSObject, MessageCenterInboxProtocol, Sendable { } private func updateInbox() async throws -> AirshipWorkResult { + await self.startUpTask?.value + guard let channelID = channel.identifier else { await self.sendUpdate(.refreshFailed) return .success diff --git a/Airship/AirshipMessageCenter/Tests/MessageCenterListTests.swift b/Airship/AirshipMessageCenter/Tests/MessageCenterListTests.swift index c25f1e6cb..c8cf7b5ba 100644 --- a/Airship/AirshipMessageCenter/Tests/MessageCenterListTests.swift +++ b/Airship/AirshipMessageCenter/Tests/MessageCenterListTests.swift @@ -81,6 +81,87 @@ final class MessageCenterListTest: XCTestCase { XCTAssertNil(resetedUser) } + func testMessageCenterIdenityHint() async throws { + let user = MessageCenterUser( + username: "AnyName", + password: "AnyPassword" + ) + + // Save user + await store.saveUser(user, channelID: "987654433") + + self.inbox.enabled = true + + XCTAssertEqual(1, self.channel.extenders.count) + let payload = await self.channel.channelPayload + XCTAssertEqual(user.username, payload.identityHints?.userID) + } + + func testMessageCenterIdenityHintRestoreMessageCenterDisabled() async throws { + self.channel.extenders.removeAll() + let config = AirshipConfig() + config.restoreMessageCenterOnReinstall = false + + let user = MessageCenterUser( + username: "AnyName", + password: "AnyPassword" + ) + + // Save user + await store.saveUser(user, channelID: "987654433") + + let inbox = MessageCenterInbox( + channel: channel, + client: client, + config: RuntimeConfig( + config: config, + dataStore: dataStore + ), + store: store, + workManager: workManager + ) + + inbox.enabled = true + + XCTAssertEqual(1, self.channel.extenders.count) + let payload = await self.channel.channelPayload + XCTAssertNil(payload.identityHints?.userID) + } + + func testRestoreMessageCenterDisabled() async throws { + self.channel.extenders.removeAll() + let config = AirshipConfig() + config.restoreMessageCenterOnReinstall = false + + let user = MessageCenterUser( + username: "AnyName", + password: "AnyPassword" + ) + + // Save user + await store.saveUser(user, channelID: "987654433") + + let inbox = MessageCenterInbox( + channel: channel, + client: client, + config: RuntimeConfig( + config: config, + dataStore: dataStore + ), + store: store, + workManager: workManager + ) + + inbox.enabled = true + + let fromInbox = await self.inbox.user + let fromStore = await self.store.user + + XCTAssertNil(fromInbox) + XCTAssertNil(fromStore) + } + + func testMessageRetrieve() async throws { self.inbox.enabled = true diff --git a/CHANGELOG.md b/CHANGELOG.md index da191c1ee..46d6caf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,9 @@ Minor release with several minor API additions. - Updated the method `refreshMessages()` to properly cancel if the task is cancelled. - Refreshing messages will no longer block on network connection availability. - Added property `identifierUpdates` on `AirshipChannelProtocol` that provides a stream of updates whenever the channel ID changes. -- Added `resetEnabledFeatures` config option on `AirshipConfig` to reset the `PrivacyManager` enabled features to those specified in config on init. +- Added new `AirshipConfig` properties: + - `resetEnabledFeatures` to reset the `PrivacyManager` enabled features to those specified in config on init. + - `restoreMessageCenterOnReinstall` to control Message Center recovery on reinstall. - Added `quietTime` property on `AirshipPushProtocol` to be able to get/set quiet time start and end time. - Custom event properties will now accept any `Encodable` values and be automatically encoded to JSON. - Added support for attributing a custom event to an in-app message if the event was generated from the message.