diff --git a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj index 10f512fa6..658d2d80d 100644 --- a/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj +++ b/iOS_SDK/OneSignalSDK/OneSignal.xcodeproj/project.pbxproj @@ -279,6 +279,7 @@ 475F474A2B8E3B4600EC05B3 /* OneSignalLiveActivities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 475F471E2B8E398D00EC05B3 /* OneSignalLiveActivities.framework */; platformFilter = ios; }; 475F474F2B8E3B5400EC05B3 /* OneSignalLiveActivities.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 475F471E2B8E398D00EC05B3 /* OneSignalLiveActivities.framework */; }; 475F47502B8E3B5400EC05B3 /* OneSignalLiveActivities.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 475F471E2B8E398D00EC05B3 /* OneSignalLiveActivities.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 50C4F7CFEC46836131011004 /* XCTestCase+WaitUntil.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A81CCA8DFA43C8AEA738B8 /* XCTestCase+WaitUntil.swift */; }; 5B053FBC2CAE07EB002F30C4 /* OneSignalOSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C115161289A259500565C41 /* OneSignalOSCore.framework */; }; 5B053FC32CAE0843002F30C4 /* OSConsistencyManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC1DE672C90C23E00CA8807 /* OSConsistencyManagerTests.swift */; }; 5B58E4F8237CE7B4009401E0 /* UIDeviceOverrider.m in Sources */ = {isa = PBXBuildFile; fileRef = 5B58E4F6237CE7B4009401E0 /* UIDeviceOverrider.m */; }; @@ -394,6 +395,7 @@ CACBAAAA218A65AE000ACAA5 /* InAppMessagingTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CACBAAA9218A65AE000ACAA5 /* InAppMessagingTests.m */; }; CACBAAAC218A662B000ACAA5 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CACBAAAB218A662B000ACAA5 /* WebKit.framework */; }; CACBAAB4218A7113000ACAA5 /* WebKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CACBAAAB218A662B000ACAA5 /* WebKit.framework */; }; + DD2A89A8052E2D1912B0038B /* OSIamFetchReadyConditionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FF4B19D1EC31C0750F13065A /* OSIamFetchReadyConditionTests.swift */; }; DE16C14424D3724700670EFA /* OneSignalLifecycleObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = DE16C14324D3724700670EFA /* OneSignalLifecycleObserver.m */; }; DE16C14524D3724700670EFA /* OneSignalLifecycleObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = DE16C14324D3724700670EFA /* OneSignalLifecycleObserver.m */; }; DE16C14724D3727200670EFA /* OneSignalLifecycleObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = DE16C14624D3727200670EFA /* OneSignalLifecycleObserver.h */; }; @@ -1319,6 +1321,7 @@ 03866CBC2378A33B0009C1D8 /* OutcomeIntegrationTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OutcomeIntegrationTests.m; sourceTree = ""; }; 03866CBF2378A67B0009C1D8 /* RestClientAsserts.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RestClientAsserts.h; sourceTree = ""; }; 03866CC02378A67B0009C1D8 /* RestClientAsserts.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RestClientAsserts.m; sourceTree = ""; }; + 03A81CCA8DFA43C8AEA738B8 /* XCTestCase+WaitUntil.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = "XCTestCase+WaitUntil.swift"; sourceTree = ""; }; 03CCCC7D2835D8CC004BF794 /* OneSignalUNUserNotificationCenterSwizzlingTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OneSignalUNUserNotificationCenterSwizzlingTest.m; sourceTree = ""; }; 03CCCC812835D90F004BF794 /* OneSignalUNUserNotificationCenterHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OneSignalUNUserNotificationCenterHelper.m; sourceTree = ""; }; 03CCCC822835D90F004BF794 /* OneSignalUNUserNotificationCenterHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OneSignalUNUserNotificationCenterHelper.h; sourceTree = ""; }; @@ -1344,6 +1347,12 @@ 3C11518C289AF5E800565C41 /* OSModelChangedHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSModelChangedHandler.swift; sourceTree = ""; }; 3C14E39E2AFAE39B006ED053 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; 3C14E3A02AFAE461006ED053 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = ""; }; + 3C14E3A92FAE54C006ED053 /* IOSLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOSLogger.swift; sourceTree = ""; }; + 3C14E3AA2FAE54C006ED053 /* OneSignalLogHttpSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalLogHttpSender.swift; sourceTree = ""; }; + 3C14E3AB2FAE54C006ED053 /* FileLogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogStore.swift; sourceTree = ""; }; + 3C14E3AC2FAE54C006ED053 /* OSLoggerPlatformProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLoggerPlatformProvider.swift; sourceTree = ""; }; + 3C14E3AD2FAE54C006ED053 /* KotlinByteArray+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KotlinByteArray+Data.swift"; sourceTree = ""; }; + 3C14E3AE2FAE54C006ED053 /* OSLoggerAdaptersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLoggerAdaptersTests.swift; sourceTree = ""; }; 3C19C6312E919F0C00D6731E /* OSRequestLiveActivityClicked.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSRequestLiveActivityClicked.swift; sourceTree = ""; }; 3C23A21A2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalIdentifiersFallbackTests.swift; sourceTree = ""; }; 3C23A21C2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSModelStoreRefreshTests.swift; sourceTree = ""; }; @@ -1783,12 +1792,6 @@ DEBAAEB62A4381AE00BF2C1C /* OSInAppMessageMigrationController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OSInAppMessageMigrationController.h; sourceTree = ""; }; DEBAAEB72A4381AE00BF2C1C /* OSInAppMessageMigrationController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OSInAppMessageMigrationController.m; sourceTree = ""; }; DEC08AFF2947D4E900C81DA3 /* OneSignalSwiftInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalSwiftInterface.swift; sourceTree = ""; }; - 3C14E3A92FAE54C006ED053 /* IOSLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOSLogger.swift; sourceTree = ""; }; - 3C14E3AA2FAE54C006ED053 /* OneSignalLogHttpSender.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OneSignalLogHttpSender.swift; sourceTree = ""; }; - 3C14E3AB2FAE54C006ED053 /* FileLogStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileLogStore.swift; sourceTree = ""; }; - 3C14E3AC2FAE54C006ED053 /* OSLoggerPlatformProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLoggerPlatformProvider.swift; sourceTree = ""; }; - 3C14E3AD2FAE54C006ED053 /* KotlinByteArray+Data.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KotlinByteArray+Data.swift"; sourceTree = ""; }; - 3C14E3AE2FAE54C006ED053 /* OSLoggerAdaptersTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLoggerAdaptersTests.swift; sourceTree = ""; }; DEF5CCF12539321A0003E9CC /* UnitTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = UnitTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; DEF5CCF32539321A0003E9CC /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; DEF5CCF42539321A0003E9CC /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; @@ -1834,6 +1837,7 @@ DEFB3E622BB731BD00E65DAD /* ActivityKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ActivityKit.framework; path = System/Library/Frameworks/ActivityKit.framework; sourceTree = SDKROOT; }; DEFB3E642BB7346D00E65DAD /* OSLiveActivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSLiveActivities.swift; sourceTree = ""; }; DEFB3E662BB735B500E65DAD /* OSStubLiveActivities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OSStubLiveActivities.swift; sourceTree = ""; }; + FF4B19D1EC31C0750F13065A /* OSIamFetchReadyConditionTests.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = OSIamFetchReadyConditionTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -2560,6 +2564,8 @@ 3C23A21A2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift */, 3C23A21E2FCE0AA1001D32E3 /* OSResilientStorageTests.swift */, 3C23A21C2FCE0A83001D32E3 /* OSModelStoreRefreshTests.swift */, + FF4B19D1EC31C0750F13065A /* OSIamFetchReadyConditionTests.swift */, + 03A81CCA8DFA43C8AEA738B8 /* XCTestCase+WaitUntil.swift */, ); path = OneSignalOSCoreTests; sourceTree = ""; @@ -4585,6 +4591,8 @@ 3C427AC9301BB28A0059B8B7 /* OSOperationRepoFlushTests.swift in Sources */, 3C14E3B52FAE54C006ED053 /* OSLoggerAdaptersTests.swift in Sources */, 3C23A21B2FCE0A52001D32E3 /* OneSignalIdentifiersFallbackTests.swift in Sources */, + DD2A89A8052E2D1912B0038B /* OSIamFetchReadyConditionTests.swift in Sources */, + 50C4F7CFEC46836131011004 /* XCTestCase+WaitUntil.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -5410,14 +5418,6 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -5445,6 +5445,14 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_C_LANGUAGE_STANDARD = gnu11; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OneSignalOSCoreFramework/Info.plist; @@ -5478,14 +5486,6 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -5514,6 +5514,14 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -6976,14 +6984,6 @@ buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; BUNDLE_LOADER = "$(TEST_HOST)"; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -7008,6 +7008,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_USER_SCRIPT_SANDBOXING = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -7030,14 +7038,6 @@ buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; BUNDLE_LOADER = "$(TEST_HOST)"; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -7062,6 +7062,14 @@ DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_TESTABILITY = YES; ENABLE_USER_SCRIPT_SANDBOXING = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_C_LANGUAGE_STANDARD = gnu17; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -7092,14 +7100,6 @@ buildSettings = { ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; BUNDLE_LOADER = "$(TEST_HOST)"; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; @@ -7124,6 +7124,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_USER_SCRIPT_SANDBOXING = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 11.0; @@ -7191,14 +7199,6 @@ CA2951B72167F4120064227A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_GCD_PERFORMANCE = YES; CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ENABLE_CODE_COVERAGE = NO; @@ -7208,6 +7208,14 @@ "$(inherited)", "$(PROJECT_DIR)", ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7225,14 +7233,6 @@ CA2951B82167F4120064227A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_CODE_COVERAGE = NO; @@ -7249,6 +7249,14 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; INFOPLIST_FILE = OneSignalFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -7278,14 +7286,6 @@ CA2951B92167F4120064227A /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; @@ -7297,6 +7297,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = OS_TEST; HEADER_SEARCH_PATHS = $CONFIGURATION_TEMP_DIR/UnitTests.build/DerivedSources; @@ -7395,14 +7403,6 @@ CA2951C32167FB950064227A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_GCD_PERFORMANCE = YES; CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ENABLE_CODE_COVERAGE = NO; @@ -7412,6 +7412,14 @@ "$(inherited)", "$(PROJECT_DIR)", ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7429,14 +7437,6 @@ CA2951C42167FB950064227A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_CODE_COVERAGE = YES; @@ -7454,6 +7454,14 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; INFOPLIST_FILE = OneSignalFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -7484,14 +7492,6 @@ CA2951C52167FB950064227A /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; @@ -7503,6 +7503,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = OS_TEST; HEADER_SEARCH_PATHS = $CONFIGURATION_TEMP_DIR/UnitTests.build/DerivedSources; @@ -7601,14 +7609,6 @@ DE3D8F3928C15839008C2BBF /* Test */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ANALYZER_GCD_PERFORMANCE = YES; CLANG_ANALYZER_SECURITY_FLOATLOOPCOUNTER = YES; CLANG_ENABLE_CODE_COVERAGE = NO; @@ -7618,6 +7618,14 @@ "$(inherited)", "$(PROJECT_DIR)", ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); IPHONEOS_DEPLOYMENT_TARGET = 9.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -7635,14 +7643,6 @@ DE3D8F3A28C15839008C2BBF /* Test */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_CODE_COVERAGE = YES; @@ -7660,6 +7660,14 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_WARN_64_TO_32_BIT_CONVERSION = NO; INFOPLIST_FILE = OneSignalFramework/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; @@ -7690,14 +7698,6 @@ DE3D8F3B28C15839008C2BBF /* Test */ = { isa = XCBuildConfiguration; buildSettings = { - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_ANALYZER_NONNULL = YES; CLANG_ENABLE_MODULES = YES; @@ -7709,6 +7709,14 @@ DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 99SW8E36CT; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = OS_TEST; HEADER_SEARCH_PATHS = $CONFIGURATION_TEMP_DIR/UnitTests.build/DerivedSources; @@ -9205,14 +9213,6 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; - "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", - ); - "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( - "$(inherited)", - "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", - ); CLANG_ENABLE_MODULES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -9222,6 +9222,14 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; ENABLE_TESTABILITY = YES; + "FRAMEWORK_SEARCH_PATHS[sdk=iphoneos*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64", + ); + "FRAMEWORK_SEARCH_PATHS[sdk=iphonesimulator*]" = ( + "$(inherited)", + "$(SRCROOT)/../../OneSignal-KMP-SDK/kmp/build/XCFrameworks/release/OneSignalKMP.xcframework/ios-arm64_x86_64-simulator", + ); GCC_PREPROCESSOR_DEFINITIONS = OS_TEST; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = OneSignalOSCoreFramework/Info.plist; diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/EarlyTriggerTrackingTests.swift b/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/EarlyTriggerTrackingTests.swift index bedfe909e..d916c1f49 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/EarlyTriggerTrackingTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/EarlyTriggerTrackingTests.swift @@ -49,7 +49,7 @@ final class EarlyTriggerTrackingTests: XCTestCase { override func setUpWithError() throws { OneSignalCoreMocks.clearUserDefaults() OneSignalUserMocks.reset() - OSConsistencyManager.shared.reset() + ConsistencyManagerTestHelpers.reset() OSMessagingController.removeInstance() // Set up basic configuration diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/IAMIntegrationTests.swift b/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/IAMIntegrationTests.swift index 9efcb6f51..f21259a50 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/IAMIntegrationTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/IAMIntegrationTests.swift @@ -41,7 +41,7 @@ final class IAMIntegrationTests: XCTestCase { override func setUpWithError() throws { OneSignalCoreMocks.clearUserDefaults() OneSignalUserMocks.reset() - OSConsistencyManager.shared.reset() + ConsistencyManagerTestHelpers.reset() // Temp. logging to help debug during testing OneSignalLog.setLogLevel(.LL_VERBOSE) } diff --git a/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/OSMessagingControllerUserStateTests.swift b/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/OSMessagingControllerUserStateTests.swift index 8d7ada7cb..9c73924d6 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/OSMessagingControllerUserStateTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalInAppMessagesTests/OSMessagingControllerUserStateTests.swift @@ -50,7 +50,7 @@ final class OSMessagingControllerUserStateTests: XCTestCase { override func setUpWithError() throws { OneSignalCoreMocks.clearUserDefaults() OneSignalUserMocks.reset() - OSConsistencyManager.shared.reset() + ConsistencyManagerTestHelpers.reset() OSMessagingController.removeInstance() // Set up basic configuration diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/IamFetch/OSIamFetchReadyCondition.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/IamFetch/OSIamFetchReadyCondition.swift index 66751e3a0..3075e9cbb 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/IamFetch/OSIamFetchReadyCondition.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/IamFetch/OSIamFetchReadyCondition.swift @@ -28,17 +28,31 @@ @objc public class OSIamFetchReadyCondition: NSObject, OSCondition { // the id used to index the token map (e.g. onesignalId) private let id: String + + private let stateLock = NSLock() private var hasSubscriptionUpdatePending: Bool = false - // Singleton shared instance initialized with default empty id - private static var instance: OSIamFetchReadyCondition? + private static let instancesLock = NSLock() + private static var instances: [String: OSIamFetchReadyCondition] = [:] - // Method to get or initialize the shared instance + /** + One condition per id, so a fetch waits on the same object the subscription listener armed, and a + fetch for a user who just switched in is not answered by the previous user's tokens. + */ @objc public static func sharedInstance(withId id: String) -> OSIamFetchReadyCondition { - if instance == nil { - instance = OSIamFetchReadyCondition(id: id) + return instancesLock.withLock { + if let existing = instances[id] { + return existing + } + let condition = OSIamFetchReadyCondition(id: id) + instances[id] = condition + return condition } - return instance! + } + + /// Test seam; the instances otherwise live as long as the process. + @objc public static func reset() { + instancesLock.withLock { instances = [:] } } // Private initializer to prevent external instantiation @@ -53,8 +67,16 @@ return OSIamFetchReadyCondition.CONDITIONID } + /// Raises the bar for the next fetch: an in-session subscription change is only readable once its + /// own token arrives, so waiting on the user token alone would fetch before the server can see it. public func setSubscriptionUpdatePending(value: Bool) { - hasSubscriptionUpdatePending = value + stateLock.withLock { hasSubscriptionUpdatePending = value } + } + + /// The fetch this was raised for has been released, so later fetches stop waiting on a subscription + /// token that has no update behind it. + @objc public func onConditionSatisfied() { + setSubscriptionUpdatePending(value: false) } public func isMet(indexedTokens: [String: [NSNumber: OSReadYourWriteData]]) -> Bool { @@ -71,7 +93,7 @@ return true } - if hasSubscriptionUpdatePending { + if stateLock.withLock({ hasSubscriptionUpdatePending }) { return userUpdateTokenSet && subscriptionTokenSet } return userUpdateTokenSet diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSCondition.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSCondition.swift index c63807819..ffef386ee 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSCondition.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSCondition.swift @@ -32,4 +32,8 @@ import Foundation var conditionId: String { get } func isMet(indexedTokens: [String: [NSNumber: OSReadYourWriteData]]) -> Bool func getNewestToken(indexedTokens: [String: [NSNumber: OSReadYourWriteData]]) -> OSReadYourWriteData? + + /// Called once a waiter on this condition has been released, so a condition that raised its own bar + /// for that wait can lower it again instead of holding every later waiter to it. + @objc optional func onConditionSatisfied() } diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSConsistencyManager.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSConsistencyManager.swift index ac7129de8..50de9c001 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSConsistencyManager.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCore/Source/Consistency/OSConsistencyManager.swift @@ -32,17 +32,30 @@ import OneSignalCore // Singleton instance @objc public static let shared = OSConsistencyManager() - private let queue = DispatchQueue(label: "com.consistencyManager.queue") + // Serial, and the only place `indexedTokens` and `indexedConditions` may be touched. + // Non-private so test helpers can synchronize with it. + let queue = DispatchQueue(label: "com.consistencyManager.queue") private var indexedTokens: [String: [NSNumber: OSReadYourWriteData]] = [:] - private var indexedConditions: [String: [(OSCondition, DispatchSemaphore)]] = [:] // Index conditions by condition id + // Waiters, indexed by the id passed to getRywTokenFromAwaitableCondition. Non-private for tests. + var indexedConditions: [String: [(OSCondition, DispatchSemaphore)]] = [:] + + /** + How long a waiter blocks before proceeding with whatever token it has. A response that never arrives — + the device is offline, or the endpoint stopped returning `ryw_token` and has no call that resolves the + condition — would otherwise hold the calling thread for the life of the process. + Non-private so tests can shorten it. + */ + static var waitTimeout: DispatchTimeInterval = .seconds(30) // Private initializer to prevent multiple instances private override init() {} // Used for testing public func reset() { - indexedTokens = [:] - indexedConditions = [:] + queue.sync { + self.indexedTokens = [:] + self.indexedConditions = [:] + } } // Function to set the token in a thread-safe manner @@ -57,52 +70,67 @@ import OneSignalCore } } - // Register a condition and block the caller until the condition is met + /// Blocks the caller until the condition is met or `waitTimeout` elapses, then returns the newest + /// token the condition accepts, which is nil when it was released without one. @objc public func getRywTokenFromAwaitableCondition(_ condition: OSCondition, forId id: String) -> OSReadYourWriteData? { let semaphore = DispatchSemaphore(value: 0) queue.sync { - if self.indexedConditions[id] == nil { - self.indexedConditions[id] = [] - } - self.indexedConditions[id]?.append((condition, semaphore)) + self.indexedConditions[id, default: []].append((condition, semaphore)) self.checkConditionsAndComplete(forId: id) } - semaphore.wait() // Block until the condition is met + if semaphore.wait(timeout: .now() + OSConsistencyManager.waitTimeout) == .timedOut { + OneSignalLog.onesignalLog(.LL_WARN, message: "OSConsistencyManager timed out waiting on \(condition.conditionId) for id: \(id)") + queue.sync { + // Skip if a met-path release already removed this waiter. + guard self.indexedConditions[id]?.contains(where: { $0.1 === semaphore }) == true else { + return + } + // Clear so later fetches for this id are not held to a subscription token that never arrives. + condition.onConditionSatisfied?() + self.indexedConditions[id]?.removeAll { $0.1 === semaphore } + } + } return queue.sync { return condition.getNewestToken(indexedTokens: self.indexedTokens) } } - // Method to resolve conditions by condition ID (e.g. OSIamFetchReadyCondition.ID) - @objc public func resolveConditionsWithID(id: String) { - guard let conditionList = indexedConditions[id] else { return } - var completedConditions: [(OSCondition, DispatchSemaphore)] = [] - for (condition, semaphore) in conditionList { - if condition.conditionId == id { - semaphore.signal() - completedConditions.append((condition, semaphore)) + /** + Releases waiters on `conditionId` registered under `id` (e.g. onesignalId). Used when that user's + response carried no `ryw_token`, so those waiters have nothing left to wait for. + */ + @objc(resolveConditionsWithConditionId:forId:) + public func resolveConditions(conditionId: String, forId id: String) { + queue.sync { + guard let waiters = self.indexedConditions[id] else { + return } - } - indexedConditions[id]?.removeAll { condition, semaphore in - completedConditions.contains(where: { $0.0 === condition && $0.1 == semaphore }) + for (condition, semaphore) in waiters where condition.conditionId == conditionId { + OneSignalLog.onesignalLog(.LL_INFO, message: "Condition \(conditionId) resolved for id: \(id)") + self.release(condition, semaphore) + } + self.indexedConditions[id] = waiters.filter { $0.0.conditionId != conditionId } } } // Private method to check conditions for a specific id (unique ID like onesignalId) private func checkConditionsAndComplete(forId id: String) { - guard let conditionList = indexedConditions[id] else { return } - var completedConditions: [(OSCondition, DispatchSemaphore)] = [] - for (condition, semaphore) in conditionList { + guard let waiters = indexedConditions[id] else { return } + var stillWaiting: [(OSCondition, DispatchSemaphore)] = [] + for (condition, semaphore) in waiters { if condition.isMet(indexedTokens: indexedTokens) { OneSignalLog.onesignalLog(.LL_INFO, message: "Condition met for id: \(id)") - semaphore.signal() - completedConditions.append((condition, semaphore)) + release(condition, semaphore) } else { OneSignalLog.onesignalLog(.LL_INFO, message: "Condition not met for id: \(id)") + stillWaiting.append((condition, semaphore)) } } - indexedConditions[id]?.removeAll { condition, semaphore in - completedConditions.contains(where: { $0.0 === condition && $0.1 == semaphore }) - } + indexedConditions[id] = stillWaiting + } + + private func release(_ condition: OSCondition, _ semaphore: DispatchSemaphore) { + condition.onConditionSatisfied?() + semaphore.signal() } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCoreMocks/ConsistencyManagerTestHelpers.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCoreMocks/ConsistencyManagerTestHelpers.swift index 53d105943..8649a1a07 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCoreMocks/ConsistencyManagerTestHelpers.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCoreMocks/ConsistencyManagerTestHelpers.swift @@ -28,6 +28,13 @@ import OneSignalOSCore public class ConsistencyManagerTestHelpers { + /// Clears both halves of the read-your-write state: the manager's tokens and waiters, and the + /// per-id conditions, which otherwise carry a raised subscription bar into the next test. + public static func reset() { + OSConsistencyManager.shared.reset() + OSIamFetchReadyCondition.reset() + } + /// Unblocks the Consistency Manager, which allows fetching of IAMs for example. public static func setDefaultRywToken(id: String) { let key = OSIamFetchOffsetKey.userUpdate diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSConsistencyManagerTests.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSConsistencyManagerTests.swift index 9a686d644..2db1a1378 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSConsistencyManagerTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSConsistencyManagerTests.swift @@ -8,19 +8,23 @@ import Foundation import XCTest -import OneSignalOSCore +@testable import OneSignalOSCore class OSConsistencyManagerTests: XCTestCase { var consistencyManager: OSConsistencyManager! + private var defaultWaitTimeout: DispatchTimeInterval! override func setUp() { super.setUp() // Use the shared instance of OSConsistencyManager consistencyManager = OSConsistencyManager.shared + defaultWaitTimeout = OSConsistencyManager.waitTimeout } override func tearDown() { + OSConsistencyManager.waitTimeout = defaultWaitTimeout consistencyManager.reset() + OSIamFetchReadyCondition.reset() super.tearDown() } @@ -92,35 +96,28 @@ class OSConsistencyManagerTests: XCTestCase { // Test: registerCondition does not complete when condition is not met func testRegisterConditionDoesNotCompleteWhenConditionIsNotMet() { - // Given a condition that will never be met - let condition = TestUnmetCondition() + OSConsistencyManager.waitTimeout = .milliseconds(500) let id = "test_id" - let rywDelay = 500 as NSNumber - - // Start on a background queue to simulate async behavior - DispatchQueue.global().async { - // Register the condition asynchronously - let rywData = self.consistencyManager.getRywTokenFromAwaitableCondition(condition, forId: id) + let returned = expectation(description: "waiter returned") + var rywData: OSReadYourWriteData? - // Since the condition will never be met, rywToken should remain nil - XCTAssertNil(rywData) + DispatchQueue.global().async { + rywData = self.consistencyManager.getRywTokenFromAwaitableCondition(TestUnmetCondition(), forId: id) + returned.fulfill() + } + waitUntil("waiter registered") { self.consistencyManager.waiterCount == 1 } - // Set an unrelated token to verify that the unmet condition still doesn't complete - self.consistencyManager.setRywTokenAndDelay( + // A token for another id must not release this waiter + consistencyManager.setRywTokenAndDelay( id: "unrelated_id", key: OSIamFetchOffsetKey.userUpdate, - value: OSReadYourWriteData(rywToken: "unrelated", rywDelay: rywDelay) - ) - - // newest token should still be nil as the condition is not met - XCTAssertNil(rywData) - } + value: OSReadYourWriteData(rywToken: "unrelated", rywDelay: 500) + ) + XCTAssertEqual(consistencyManager.waiterCount, 1) - // Use a short delay to let the async behavior complete without waiting indefinitely - DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) { - XCTAssertTrue(true) // Simulate some async action completing without hanging - } - } + wait(for: [returned], timeout: 2.0) + XCTAssertNil(rywData) + } func testSetRywTokenWithoutAnyCondition() { // Given @@ -292,6 +289,87 @@ class OSConsistencyManagerTests: XCTestCase { waitForExpectations(timeout: 2.0, handler: nil) } + + // MARK: - Releasing waiters + + /// A response with no `ryw_token` releases waiters for that user only. + func testResolvingByConditionIdReleasesWaitersForThatId() { + let returned = expectation(description: "waiter returned") + DispatchQueue.global().async { + _ = self.consistencyManager.getRywTokenFromAwaitableCondition(TestUnmetCondition(), forId: "onesignal-id") + returned.fulfill() + } + waitUntil("waiter registered") { self.consistencyManager.waiterCount == 1 } + + consistencyManager.resolveConditions(conditionId: TestUnmetCondition.CONDITIONID, forId: "onesignal-id") + + wait(for: [returned], timeout: 2.0) + XCTAssertEqual(consistencyManager.waiterCount, 0) + } + + /// Resolving user B leaves user A's waiter registered. + func testResolvingOneIdLeavesAnotherIdsWaiterWaiting() { + OSConsistencyManager.waitTimeout = .milliseconds(200) + let userA = "onesignal-id-a" + let userB = "onesignal-id-b" + + let aReturned = expectation(description: "user A waiter returned") + let bReturned = expectation(description: "user B waiter returned") + DispatchQueue.global().async { + _ = self.consistencyManager.getRywTokenFromAwaitableCondition(TestUnmetCondition(), forId: userA) + aReturned.fulfill() + } + DispatchQueue.global().async { + _ = self.consistencyManager.getRywTokenFromAwaitableCondition(TestUnmetCondition(), forId: userB) + bReturned.fulfill() + } + waitUntil("both waiters registered") { self.consistencyManager.waiterCount == 2 } + + consistencyManager.resolveConditions(conditionId: TestUnmetCondition.CONDITIONID, forId: userB) + + wait(for: [bReturned], timeout: 2.0) + XCTAssertEqual(consistencyManager.waiterCount, 1, "user A's waiter must still be registered") + // Drain A's timeout so the thread is not left blocked after the test. + wait(for: [aReturned], timeout: 2.0) + } + + func testResolvingADifferentConditionLeavesTheWaiterWaiting() { + OSConsistencyManager.waitTimeout = .milliseconds(200) + let returned = expectation(description: "waiter returned") + DispatchQueue.global().async { + _ = self.consistencyManager.getRywTokenFromAwaitableCondition(TestUnmetCondition(), forId: "onesignal-id") + returned.fulfill() + } + waitUntil("waiter registered") { self.consistencyManager.waiterCount == 1 } + + consistencyManager.resolveConditions(conditionId: "SomeOtherCondition", forId: "onesignal-id") + + XCTAssertEqual(consistencyManager.waiterCount, 1) + // Drain the timeout so the thread is not left blocked after the test. + wait(for: [returned], timeout: 2.0) + } + + /// A condition nothing ever meets must not hold its thread for the life of the process. + func testAWaiterGivesUpWhenItsConditionIsNeverMet() { + OSConsistencyManager.waitTimeout = .milliseconds(200) + let returned = expectation(description: "waiter returned") + var rywData: OSReadYourWriteData? + DispatchQueue.global().async { + rywData = self.consistencyManager.getRywTokenFromAwaitableCondition(TestUnmetCondition(), forId: "onesignal-id") + returned.fulfill() + } + + wait(for: [returned], timeout: 2.0) + XCTAssertNil(rywData) + XCTAssertEqual(consistencyManager.waiterCount, 0, "a waiter that gave up has to deregister") + } +} + +extension OSConsistencyManager { + /// Waiters registered right now. Tests poll it while the manager mutates on its own queue. + var waiterCount: Int { + return queue.sync { indexedConditions.values.reduce(0) { $0 + $1.count } } + } } // Mock implementation of OSCondition that simulates a condition that isn't met diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSIamFetchReadyConditionTests.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSIamFetchReadyConditionTests.swift new file mode 100644 index 000000000..68ea0b19e --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSIamFetchReadyConditionTests.swift @@ -0,0 +1,208 @@ +/* + Modified MIT License + + Copyright 2026 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER 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 XCTest +@testable import OneSignalOSCore + +/// Covers what an IAM fetch waits for, and for whom. +final class OSIamFetchReadyConditionTests: XCTestCase { + + private let userA = "onesignal-id-a" + private let userB = "onesignal-id-b" + private var defaultWaitTimeout: DispatchTimeInterval! + + override func setUp() { + super.setUp() + defaultWaitTimeout = OSConsistencyManager.waitTimeout + OSConsistencyManager.shared.reset() + OSIamFetchReadyCondition.reset() + } + + override func tearDown() { + OSConsistencyManager.waitTimeout = defaultWaitTimeout + OSConsistencyManager.shared.reset() + OSIamFetchReadyCondition.reset() + super.tearDown() + } + + // MARK: - One condition per id + + func testTheSameIdGetsTheSameCondition() { + let first = OSIamFetchReadyCondition.sharedInstance(withId: userA) + let second = OSIamFetchReadyCondition.sharedInstance(withId: userA) + + XCTAssertTrue(first === second, "the fetch has to wait on the object the listener armed") + } + + /// A user switch must not be answered by the previous user's condition, which reads their tokens. + func testADifferentIdGetsItsOwnCondition() { + let forUserA = OSIamFetchReadyCondition.sharedInstance(withId: userA) + let forUserB = OSIamFetchReadyCondition.sharedInstance(withId: userB) + + XCTAssertFalse(forUserA === forUserB) + XCTAssertTrue(forUserA.isMet(indexedTokens: [userA: userCreateToken()])) + XCTAssertFalse(forUserB.isMet(indexedTokens: [userA: userCreateToken()]), + "the new user's fetch must not be released by the previous user's token") + } + + // MARK: - What the condition waits for + + func testAUserUpdateTokenIsEnoughWithNoSubscriptionUpdatePending() { + let condition = OSIamFetchReadyCondition.sharedInstance(withId: userA) + + XCTAssertTrue(condition.isMet(indexedTokens: [userA: userUpdateToken()])) + } + + func testAPendingSubscriptionUpdateAlsoWaitsForItsToken() { + let condition = OSIamFetchReadyCondition.sharedInstance(withId: userA) + condition.setSubscriptionUpdatePending(value: true) + + XCTAssertFalse(condition.isMet(indexedTokens: [userA: userUpdateToken()])) + XCTAssertTrue(condition.isMet(indexedTokens: [userA: userUpdateToken().merging(subscriptionToken()) { current, _ in current }])) + } + + /// The raised bar belongs to the fetch it was raised for; leaving it up makes every later fetch + /// wait on a subscription token that has no update behind it. + func testTheSubscriptionBarComesBackDownOnceTheFetchIsReleased() { + let condition = OSIamFetchReadyCondition.sharedInstance(withId: userA) + condition.setSubscriptionUpdatePending(value: true) + + condition.onConditionSatisfied() + + XCTAssertTrue(condition.isMet(indexedTokens: [userA: userUpdateToken()])) + } + + // MARK: - Through the Consistency Manager + + func testTheManagerLowersTheBarWhenItReleasesTheWaiter() { + let manager = OSConsistencyManager.shared + OSIamFetchReadyCondition.sharedInstance(withId: userA).setSubscriptionUpdatePending(value: true) + + let firstReturned = expectation(description: "first fetch released") + DispatchQueue.global().async { + _ = manager.getRywTokenFromAwaitableCondition(OSIamFetchReadyCondition.sharedInstance(withId: self.userA), forId: self.userA) + firstReturned.fulfill() + } + waitUntil("first fetch waiting") { manager.waiterCount == 1 } + + manager.setRywTokenAndDelay(id: userA, key: OSIamFetchOffsetKey.userUpdate, value: token("100")) + XCTAssertEqual(manager.waiterCount, 1, "a pending subscription update still owes a token") + + manager.setRywTokenAndDelay(id: userA, key: OSIamFetchOffsetKey.subscriptionUpdate, value: token("200")) + wait(for: [firstReturned], timeout: 2.0) + + // The next fetch has no subscription update behind it, so the user token alone releases it. + let secondReturned = expectation(description: "second fetch released") + DispatchQueue.global().async { + _ = manager.getRywTokenFromAwaitableCondition(OSIamFetchReadyCondition.sharedInstance(withId: self.userA), forId: self.userA) + secondReturned.fulfill() + } + wait(for: [secondReturned], timeout: 2.0) + } + + /// The `ryw_token`-missing fallback the executors call for that user. + func testResolvingTheConditionReleasesTheFetch() { + let manager = OSConsistencyManager.shared + let returned = expectation(description: "fetch released") + DispatchQueue.global().async { + _ = manager.getRywTokenFromAwaitableCondition(OSIamFetchReadyCondition.sharedInstance(withId: self.userA), forId: self.userA) + returned.fulfill() + } + waitUntil("fetch waiting") { manager.waiterCount == 1 } + + manager.resolveConditions(conditionId: OSIamFetchReadyCondition.CONDITIONID, forId: userA) + + wait(for: [returned], timeout: 2.0) + } + + /// Resolving user B leaves user A's subscription bar raised. + func testResolvingOneUserDoesNotLowerAnotherUsersSubscriptionBar() { + let manager = OSConsistencyManager.shared + let conditionA = OSIamFetchReadyCondition.sharedInstance(withId: userA) + let conditionB = OSIamFetchReadyCondition.sharedInstance(withId: userB) + conditionA.setSubscriptionUpdatePending(value: true) + conditionB.setSubscriptionUpdatePending(value: true) + + let bReturned = expectation(description: "user B fetch released") + DispatchQueue.global().async { + _ = manager.getRywTokenFromAwaitableCondition(conditionB, forId: self.userB) + bReturned.fulfill() + } + waitUntil("user B waiting") { manager.waiterCount == 1 } + + manager.resolveConditions(conditionId: OSIamFetchReadyCondition.CONDITIONID, forId: userB) + wait(for: [bReturned], timeout: 2.0) + + XCTAssertFalse(conditionA.isMet(indexedTokens: [userA: userUpdateToken()]), + "user A's subscription bar must still be up") + XCTAssertTrue(conditionB.isMet(indexedTokens: [userB: userUpdateToken()]), + "user B's bar comes down with its own resolve") + } + + /// After a timeout, a later fetch for the same id is released by the user token alone. + func testTimingOutLowersTheSubscriptionBar() { + OSConsistencyManager.waitTimeout = .milliseconds(200) + let manager = OSConsistencyManager.shared + let condition = OSIamFetchReadyCondition.sharedInstance(withId: userA) + condition.setSubscriptionUpdatePending(value: true) + + let firstReturned = expectation(description: "first fetch timed out") + DispatchQueue.global().async { + _ = manager.getRywTokenFromAwaitableCondition(condition, forId: self.userA) + firstReturned.fulfill() + } + wait(for: [firstReturned], timeout: 2.0) + + manager.setRywTokenAndDelay(id: userA, key: OSIamFetchOffsetKey.userUpdate, value: token("100")) + + let secondReturned = expectation(description: "second fetch released by user token alone") + DispatchQueue.global().async { + _ = manager.getRywTokenFromAwaitableCondition(condition, forId: self.userA) + secondReturned.fulfill() + } + wait(for: [secondReturned], timeout: 2.0) + } + + // MARK: - Helpers + + private func token(_ value: String) -> OSReadYourWriteData { + return OSReadYourWriteData(rywToken: value, rywDelay: 0) + } + + private func userCreateToken() -> [NSNumber: OSReadYourWriteData] { + return [NSNumber(value: OSIamFetchOffsetKey.userCreate.rawValue): token("create")] + } + + private func userUpdateToken() -> [NSNumber: OSReadYourWriteData] { + return [NSNumber(value: OSIamFetchOffsetKey.userUpdate.rawValue): token("update")] + } + + private func subscriptionToken() -> [NSNumber: OSReadYourWriteData] { + return [NSNumber(value: OSIamFetchOffsetKey.subscriptionUpdate.rawValue): token("subscription")] + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSOperationRepoFlushTests.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSOperationRepoFlushTests.swift index e8519205d..875a26ffb 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSOperationRepoFlushTests.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/OSOperationRepoFlushTests.swift @@ -138,25 +138,6 @@ final class OSOperationRepoFlushTests: XCTestCase { value: property ) } - - private func waitUntil( - _ description: String, - timeout: TimeInterval = 2.0, - file: StaticString = #filePath, - line: UInt = #line, - _ condition: @escaping () -> Bool - ) { - let exp = expectation(description: description) - let timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in - if condition() { - timer.invalidate() - exp.fulfill() - } - } - let result = XCTWaiter.wait(for: [exp], timeout: timeout) - timer.invalidate() - XCTAssertEqual(result, .completed, "Timed out waiting for: \(description)", file: file, line: line) - } } private final class MockOperationExecutor: OSOperationExecutor { diff --git a/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/XCTestCase+WaitUntil.swift b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/XCTestCase+WaitUntil.swift new file mode 100644 index 000000000..629c7d95e --- /dev/null +++ b/iOS_SDK/OneSignalSDK/OneSignalOSCoreTests/XCTestCase+WaitUntil.swift @@ -0,0 +1,51 @@ +/* + Modified MIT License + + Copyright 2025 OneSignal + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + 1. The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + 2. All copies of substantial portions of the Software may only be used in connection + with services provided by OneSignal. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER 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 XCTest + +extension XCTestCase { + /// Polls: the work runs on a private queue with no completion to hook. + func waitUntil( + _ description: String, + timeout: TimeInterval = 2.0, + file: StaticString = #filePath, + line: UInt = #line, + _ condition: @escaping () -> Bool + ) { + let exp = expectation(description: description) + let timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { timer in + if condition() { + timer.invalidate() + exp.fulfill() + } + } + let result = XCTWaiter.wait(for: [exp], timeout: timeout) + timer.invalidate() + XCTAssertEqual(result, .completed, "Timed out waiting for: \(description)", file: file, line: line) + } +} diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift index 88f29af73..0df165df2 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSPropertyOperationExecutor.swift @@ -276,7 +276,7 @@ class OSPropertyOperationExecutor: OSOperationExecutor { ) } else { // handle a potential regression where ryw_token is no longer returned by API - OSConsistencyManager.shared.resolveConditionsWithID(id: OSIamFetchReadyCondition.CONDITIONID) + OSConsistencyManager.shared.resolveConditions(conditionId: OSIamFetchReadyCondition.CONDITIONID, forId: onesignalId) } } } onFailure: { error in diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift index f100bbfd9..f8c985cce 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSSubscriptionOperationExecutor.swift @@ -311,7 +311,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { ) } else { // handle a potential regression where ryw_token is no longer returned by API - OSConsistencyManager.shared.resolveConditionsWithID(id: OSIamFetchReadyCondition.CONDITIONID) + OSConsistencyManager.shared.resolveConditions(conditionId: OSIamFetchReadyCondition.CONDITIONID, forId: onesignalId) } } @@ -431,7 +431,7 @@ class OSSubscriptionOperationExecutor: OSOperationExecutor { ) } else { // handle a potential regression where ryw_token is no longer returned by API - OSConsistencyManager.shared.resolveConditionsWithID(id: OSIamFetchReadyCondition.CONDITIONID) + OSConsistencyManager.shared.resolveConditions(conditionId: OSIamFetchReadyCondition.CONDITIONID, forId: onesignalId) } } diff --git a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift index 1b8d0e5b3..477f11540 100644 --- a/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift +++ b/iOS_SDK/OneSignalSDK/OneSignalUser/Source/Executors/OSUserExecutor.swift @@ -271,7 +271,7 @@ extension OSUserExecutor { ) } else { // handle a potential regression where ryw_token is no longer returned by API - OSConsistencyManager.shared.resolveConditionsWithID(id: OSIamFetchReadyCondition.CONDITIONID) + OSConsistencyManager.shared.resolveConditions(conditionId: OSIamFetchReadyCondition.CONDITIONID, forId: onesignalId) } } }