Skip to content

Commit f45bde6

Browse files
committed
fix: preserve user identity across iOS prewarm launches (#1669)
Merges main's #1669 (iOS prewarm identity fix) into identity_verification_beta. Brings in OSResilientStorage, the OneSignalConfig protected-data gate, and OneSignalIdentifiers' unencrypted-mirror fallback for storedAppId/subscriptionId. Conflict resolution: request OSRequest* kept the branch's JWT/alias logic, which already routed appId reads through OneSignalIdentifiers.currentAppId (per #1667), so the incoming change was a no-op there. OneSignalConfig/OneSignalIdentifiers took the incoming superset. Migrated the two updateUserJwt readiness gates and the executor test setup off the removed OneSignalConfigManager APIs onto OneSignalConfig/OneSignalIdentifiers, matching #1669's sweep.
1 parent e4cb30e commit f45bde6

22 files changed

Lines changed: 777 additions & 38 deletions

iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@
7171
3C14E3A12AFAE461006ED053 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3C14E3A02AFAE461006ED053 /* PrivacyInfo.xcprivacy */; };
7272
3C14E3A42AFAE54C006ED053 /* OneSignalSwiftInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEC08AFF2947D4E900C81DA3 /* OneSignalSwiftInterface.swift */; };
7373
3C19C6322E919F0C00D6731E /* OSRequestLiveActivityClicked.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C19C6312E919F0C00D6731E /* OSRequestLiveActivityClicked.swift */; };
74+
3C23A21B2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C23A21A2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift */; };
75+
3C23A21D2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C23A21C2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift */; };
76+
3C23A21F2FCE0AA1001D32E3 /* OSResilientStorageTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C23A21E2FCE0AA1001D32E3 /* OSResilientStorageTests.swift */; };
7477
3C24B0EC2BD09D7A0052E771 /* OneSignalCoreObjCTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3C24B0EB2BD09D7A0052E771 /* OneSignalCoreObjCTests.m */; };
7578
3C277D7E2BD76E0000857606 /* OSIdentityModelRepo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C277D7D2BD76E0000857606 /* OSIdentityModelRepo.swift */; };
7679
3C2C7DC8288F3C020020F9AE /* OSSubscriptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C2C7DC7288F3C020020F9AE /* OSSubscriptionModel.swift */; };
@@ -186,6 +189,7 @@
186189
3CC890352C5BF9A7002CB4CC /* UserConcurrencyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC890342C5BF9A7002CB4CC /* UserConcurrencyTests.swift */; };
187190
3CC9A6342AFA1FDE008F68FD /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3CC9A6332AFA1FDD008F68FD /* PrivacyInfo.xcprivacy */; };
188191
3CC9A6362AFA26E7008F68FD /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 3CC9A6352AFA26E7008F68FD /* PrivacyInfo.xcprivacy */; };
192+
3CCC48042FCD619400D77E94 /* OSResilientStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCC48032FCD619400D77E94 /* OSResilientStorage.swift */; };
189193
3CCF44BE299B17290021964D /* OneSignalWrapper.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CCF44BC299B17290021964D /* OneSignalWrapper.h */; settings = {ATTRIBUTES = (Public, ); }; };
190194
3CCF44BF299B17290021964D /* OneSignalWrapper.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CCF44BD299B17290021964D /* OneSignalWrapper.m */; };
191195
3CDE664C2BFC2A56006DA114 /* OneSignalUserObjcTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3CDE664B2BFC2A56006DA114 /* OneSignalUserObjcTests.m */; };
@@ -1346,6 +1350,9 @@
13461350
3C14E39E2AFAE39B006ED053 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
13471351
3C14E3A02AFAE461006ED053 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
13481352
3C19C6312E919F0C00D6731E /* OSRequestLiveActivityClicked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestLiveActivityClicked.swift; sourceTree = "<group>"; };
1353+
3C23A21A2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalIdentifiersFallbackTests.swift; sourceTree = "<group>"; };
1354+
3C23A21C2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSModelStoreRefreshTests.swift; sourceTree = "<group>"; };
1355+
3C23A21E2FCE0AA1001D32E3 /* OSResilientStorageTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSResilientStorageTests.swift; sourceTree = "<group>"; };
13491356
3C24B0EA2BD09D790052E771 /* OneSignalCoreTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OneSignalCoreTests-Bridging-Header.h"; sourceTree = "<group>"; };
13501357
3C24B0EB2BD09D7A0052E771 /* OneSignalCoreObjCTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalCoreObjCTests.m; sourceTree = "<group>"; };
13511358
3C277D7D2BD76E0000857606 /* OSIdentityModelRepo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSIdentityModelRepo.swift; sourceTree = "<group>"; };
@@ -1435,6 +1442,7 @@
14351442
3CC890342C5BF9A7002CB4CC /* UserConcurrencyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserConcurrencyTests.swift; sourceTree = "<group>"; };
14361443
3CC9A6332AFA1FDD008F68FD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
14371444
3CC9A6352AFA26E7008F68FD /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1445+
3CCC48032FCD619400D77E94 /* OSResilientStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSResilientStorage.swift; sourceTree = "<group>"; };
14381446
3CCF44BC299B17290021964D /* OneSignalWrapper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OneSignalWrapper.h; sourceTree = "<group>"; };
14391447
3CCF44BD299B17290021964D /* OneSignalWrapper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OneSignalWrapper.m; sourceTree = "<group>"; };
14401448
3CDE664A2BFC2A55006DA114 /* OneSignalUserTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "OneSignalUserTests-Bridging-Header.h"; sourceTree = "<group>"; };
@@ -2258,6 +2266,7 @@
22582266
3C2FF9CE2C5FCD590081293B /* Jwt */,
22592267
3C115188289ADEA300565C41 /* OSModelStore.swift */,
22602268
3C5C6FFB2FCB8DED00102E2C /* OneSignalIdentifiers.swift */,
2269+
3CCC48032FCD619400D77E94 /* OSResilientStorage.swift */,
22612270
3C115186289ADE7700565C41 /* OSModelStoreListener.swift */,
22622271
3C115184289ADE4F00565C41 /* OSModel.swift */,
22632272
3CF1A5622C669EA40056B3AA /* OSNewRecordsState.swift */,
@@ -2580,6 +2589,9 @@
25802589
isa = PBXGroup;
25812590
children = (
25822591
5BC1DE672C90C23E00CA8807 /* OSConsistencyManagerTests.swift */,
2592+
3C23A21A2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift */,
2593+
3C23A21E2FCE0AA1001D32E3 /* OSResilientStorageTests.swift */,
2594+
3C23A21C2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift */,
25832595
);
25842596
path = OneSignalOSCoreTests;
25852597
sourceTree = "<group>";
@@ -4446,6 +4458,7 @@
44464458
5BC1DE5C2C90B7E600CA8807 /* OSConsistencyManager.swift in Sources */,
44474459
3C5C6FFC2FCB8DED00102E2C /* OneSignalIdentifiers.swift in Sources */,
44484460
3C115189289ADEA300565C41 /* OSModelStore.swift in Sources */,
4461+
3CCC48042FCD619400D77E94 /* OSResilientStorage.swift in Sources */,
44494462
3C115185289ADE4F00565C41 /* OSModel.swift in Sources */,
44504463
3CF1A5632C669EA40056B3AA /* OSNewRecordsState.swift in Sources */,
44514464
3C448BA22936B474002F96BC /* OSBackgroundTaskManager.swift in Sources */,
@@ -4598,6 +4611,9 @@
45984611
buildActionMask = 2147483647;
45994612
files = (
46004613
5B053FC32CAE0843002F30C4 /* OSConsistencyManagerTests.swift in Sources */,
4614+
3C23A21F2FCE0AA1001D32E3 /* OSResilientStorageTests.swift in Sources */,
4615+
3C23A21D2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift in Sources */,
4616+
3C23A21B2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift in Sources */,
46014617
);
46024618
runOnlyForDeploymentPostprocessing = 0;
46034619
};

iOS_SDK/OneSignalSDK/OneSignalExtension/OneSignalReceiveReceiptsController.m

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,15 @@
3535
@implementation OneSignalReceiveReceiptsController
3636

3737
- (BOOL)isReceiveReceiptsEnabled {
38-
return [OneSignalUserDefaults.initShared getSavedBoolForKey:OSUD_RECEIVE_RECEIPTS_ENABLED defaultValue:NO];
38+
BOOL enabled = [OneSignalUserDefaults.initShared getSavedBoolForKey:OSUD_RECEIVE_RECEIPTS_ENABLED defaultValue:NO];
39+
if (enabled) {
40+
return YES;
41+
}
42+
// UserDefaults can return NO for two reasons: the flag is genuinely off, or the shared
43+
// UserDefaults file isn't readable right now (NSE running while device is locked under
44+
// NSFileProtectionCompleteUntilFirstUserAuthentication). Fall back to the unencrypted cache.
45+
NSString *cached = [OSResilientStorage stringForKey:OSResilientStorage.keyReceiveReceiptsEnabled];
46+
return [cached isEqualToString:@"1"];
3947
}
4048

4149
- (void)sendReceiveReceiptWithNotificationId:(NSString *)notificationId {

iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityClicked.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ class OSRequestLiveActivityClicked: OneSignalRequest, OSLiveActivityRequest {
4545
return false
4646
}
4747

48-
guard let subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId else {
48+
guard let subscriptionId = OneSignalIdentifiers.subscriptionId else {
4949
OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the OSRequestLiveActivityClicked due to null subscription ID.")
5050
return false
5151
}

iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestLiveActivityReceiveReceipts.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class OSRequestLiveActivityReceiveReceipts: OneSignalRequest, OSLiveActivityRequ
4444
return false
4545
}
4646

47-
guard let subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId else {
47+
guard let subscriptionId = OneSignalIdentifiers.subscriptionId else {
4848
OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the OSRequestLiveActivityReceiveReceipts due to null subscription ID.")
4949
return false
5050
}

iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestRemoveStartToken.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class OSRequestRemoveStartToken: OneSignalRequest, OSLiveActivityRequest, OSLive
4242
return false
4343
}
4444

45-
guard let subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId else {
45+
guard let subscriptionId = OneSignalIdentifiers.subscriptionId else {
4646
OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the remove start token request due to null subscription ID.")
4747
return false
4848
}

iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestRemoveUpdateToken.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class OSRequestRemoveUpdateToken: OneSignalRequest, OSLiveActivityRequest, OSLiv
4242
return false
4343
}
4444

45-
guard let subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId else {
45+
guard let subscriptionId = OneSignalIdentifiers.subscriptionId else {
4646
OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the remove update token request due to null subscription ID.")
4747
return false
4848
}

iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestSetStartToken.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class OSRequestSetStartToken: OneSignalRequest, OSLiveActivityRequest, OSLiveAct
4444
return false
4545
}
4646

47-
guard let subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId else {
47+
guard let subscriptionId = OneSignalIdentifiers.subscriptionId else {
4848
OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the set start token request due to null subscription ID.")
4949
return false
5050
}

iOS_SDK/OneSignalSDK/OneSignalLiveActivities/Source/Requests/OSRequestSetUpdateToken.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class OSRequestSetUpdateToken: OneSignalRequest, OSLiveActivityRequest, OSLiveAc
4343
return false
4444
}
4545

46-
guard let subscriptionId = OneSignalUserManagerImpl.sharedInstance.pushSubscriptionId else {
46+
guard let subscriptionId = OneSignalIdentifiers.subscriptionId else {
4747
OneSignalLog.onesignalLog(.LL_DEBUG, message: "Cannot generate the set update token request due to null subscription ID.")
4848
return false
4949
}

iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/OSModelStore.swift

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,20 @@ open class OSModelStore<TModel: OSModel>: NSObject {
3636
public init(changeSubscription: OSEventProducer<OSModelStoreChangedHandler>, storeKey: String) {
3737
self.storeKey = storeKey
3838
self.changeSubscription = changeSubscription
39-
40-
// read models from cache, if any
41-
if let models = OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: self.storeKey, defaultValue: [:]) as? [String: TModel] {
42-
self.models = models
43-
} else {
44-
// log error
45-
self.models = [:]
46-
}
39+
self.models = OSModelStore.loadModelsFromUserDefaults(storeKey: storeKey)
4740
super.init()
41+
subscribeToOwnedModels()
42+
}
43+
44+
/// Reads the archived `[String: TModel]` dict for `storeKey` from shared UserDefaults.
45+
/// Returns an empty dict if the entry is missing or the unarchive fails.
46+
private static func loadModelsFromUserDefaults(storeKey: String) -> [String: TModel] {
47+
return (OneSignalUserDefaults.initShared().getSavedCodeableData(forKey: storeKey, defaultValue: [:]) as? [String: TModel]) ?? [:]
48+
}
4849

49-
// listen for changes to the models
50+
/// Subscribes this store as a change observer on every model currently in `models`.
51+
/// Callers must hold `lock` if invoking after init.
52+
private func subscribeToOwnedModels() {
5053
for model in self.models.values {
5154
model.changeNotifier.subscribe(self, key: storeKey)
5255
}
@@ -93,6 +96,25 @@ open class OSModelStore<TModel: OSModel>: NSObject {
9396
}
9497
}
9598

99+
/// Re-read this store's backing UserDefaults entry and hydrate `models` from disk.
100+
/// No-op when `models` is already non-empty — we never clobber in-memory state.
101+
///
102+
/// Motivation: model stores load their `models` dict once in `init()` from shared UserDefaults.
103+
/// If `init()` runs while protected data is unavailable (iOS app prewarm, NSE before first
104+
/// unlock), that read returns nil and the dict stays empty for the lifetime of the singleton —
105+
/// it is never re-read. After protected data becomes available, callers can call `refresh()`
106+
/// so the store reflects what's actually on disk. Does not fire listener events.
107+
public func refresh() {
108+
lock.withLock {
109+
guard models.isEmpty else { return }
110+
let stored = OSModelStore.loadModelsFromUserDefaults(storeKey: self.storeKey)
111+
guard !stored.isEmpty else { return }
112+
OneSignalLog.onesignalLog(.LL_DEBUG, message: "OSModelStore[\(self.storeKey)] refresh hydrated \(stored.count) model(s) from UserDefaults")
113+
self.models = stored
114+
subscribeToOwnedModels()
115+
}
116+
}
117+
96118
public func add(id: String, model: TModel, hydrating: Bool) {
97119
// TODO: Check if we are adding the same model? Do we replace?
98120
// For example, calling addEmail multiple times with the same email

0 commit comments

Comments
 (0)