diff --git a/Iterable-iOS-SDK.podspec b/Iterable-iOS-SDK.podspec
index 7524de10d..a8fb91229 100644
--- a/Iterable-iOS-SDK.podspec
+++ b/Iterable-iOS-SDK.podspec
@@ -18,7 +18,7 @@ Pod::Spec.new do |s|
s.source = { :git => "https://github.com/Iterable/swift-sdk.git", :tag => s.version }
s.source_files = "swift-sdk/**/*.{h,m,swift}"
- s.resource_bundles = {'Iterable-iOS-SDK' => 'swift-sdk/Resources/**/*.{storyboard,xib,xcassets}' }
+ s.resource_bundles = {'Iterable-iOS-SDK' => 'swift-sdk/Resources/**/*.{storyboard,xib,xcassets,xcdatamodeld}' }
s.pod_target_xcconfig = {
'SWIFT_VERSION' => '5.2'
diff --git a/Package.swift b/Package.swift
index 2c3daaddc..adfd65f05 100644
--- a/Package.swift
+++ b/Package.swift
@@ -1,4 +1,4 @@
-// swift-tools-version:5.1
+// swift-tools-version:5.3
import PackageDescription
@@ -19,7 +19,10 @@ let package = Package(
],
targets: [
.target(name: "IterableSDK",
- path: "swift-sdk"),
+ path: "swift-sdk",
+ resources: [
+ .process("Resources"),
+ ]),
.target(name: "IterableAppExtensions",
path: "notification-extension"),
]
diff --git a/host-app/Info.plist b/host-app/Info.plist
index 10f85c241..dffa2f8b6 100644
--- a/host-app/Info.plist
+++ b/host-app/Info.plist
@@ -5,7 +5,7 @@
CFBundleDevelopmentRegion
$(DEVELOPMENT_LANGUAGE)
CFBundleDisplayName
- UITestApp
+ $(PRODUCT_NAME)
CFBundleExecutable
$(EXECUTABLE_NAME)
CFBundleIdentifier
diff --git a/swift-sdk.xcodeproj/project.pbxproj b/swift-sdk.xcodeproj/project.pbxproj
index 2407920de..1beada15e 100644
--- a/swift-sdk.xcodeproj/project.pbxproj
+++ b/swift-sdk.xcodeproj/project.pbxproj
@@ -40,6 +40,8 @@
AC19520D231D9AC600CD5B61 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA8D1A42196309C001B1332 /* Common.swift */; };
AC19520E231DAB7B00CD5B61 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC72A0BE20CF4CB8004D7997 /* Constants.swift */; };
AC195210231DAD6B00CD5B61 /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00CB31B4210960C4004ACDEC /* TestUtils.swift */; };
+ AC1AA1C624EBB2DC00F29C6B /* IterableTaskRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1AA1C524EBB2DC00F29C6B /* IterableTaskRunner.swift */; };
+ AC1AA1C924EBB3C300F29C6B /* IterableNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1AA1C824EBB3C300F29C6B /* IterableNotifications.swift */; };
AC1BED9523F1D4C700FDD75F /* MiscInboxClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC1BED9423F1D4C700FDD75F /* MiscInboxClasses.swift */; };
AC219C49225FD7EB00B98631 /* IterableInboxViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC219C48225FD7EB00B98631 /* IterableInboxViewController.swift */; };
AC219C4D225FE4C000B98631 /* InboxMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC219C4C225FE4C000B98631 /* InboxMessageViewModel.swift */; };
@@ -47,12 +49,15 @@
AC219C51225FEDBD00B98631 /* SampleInboxCell.xib in Resources */ = {isa = PBXBuildFile; fileRef = AC219C4F225FEDBD00B98631 /* SampleInboxCell.xib */; };
AC219C532260006600B98631 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = AC219C522260006600B98631 /* Assets.xcassets */; };
AC2263F020CF49B8009800EB /* IterableSDK.h in Headers */ = {isa = PBXBuildFile; fileRef = AC2263E220CF49B8009800EB /* IterableSDK.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ AC241C2224F5757C00F8F9CC /* Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2C668320D3370600D46CC9 /* Mocks.swift */; };
AC28480A24AA44C600C1FC7F /* EndpointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC28480924AA44C600C1FC7F /* EndpointTests.swift */; };
AC28480C24AA44C600C1FC7F /* IterableSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2263DF20CF49B8009800EB /* IterableSDK.framework */; };
AC29D05C24B5A7E000A9E019 /* CI.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC29D05B24B5A7E000A9E019 /* CI.swift */; };
AC2A2986231A7CFF0070A9C3 /* TestInAppPayloadGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC87172521A4E47E00FEA369 /* TestInAppPayloadGenerator.swift */; };
AC2A2988231CFAC40070A9C3 /* NetworkTableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2A2987231CFAC40070A9C3 /* NetworkTableViewController.swift */; };
AC2A298A231D44C00070A9C3 /* NetworkDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2A2989231D44C00070A9C3 /* NetworkDetailViewController.swift */; };
+ AC2AED4224EBC60C000EE5F3 /* TaskRunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2AED4124EBC60C000EE5F3 /* TaskRunnerTests.swift */; };
+ AC2AED4424EBC905000EE5F3 /* IterableTaskScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2AED4324EBC905000EE5F3 /* IterableTaskScheduler.swift */; };
AC2B79F721E6A38900A59080 /* NotificationHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2B79F621E6A38900A59080 /* NotificationHelper.swift */; };
AC2C667E20D3111900D46CC9 /* DateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2C667D20D3111900D46CC9 /* DateProvider.swift */; };
AC2C668020D31B1F00D46CC9 /* IterableNotificationResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC2C667F20D31B1F00D46CC9 /* IterableNotificationResponseTests.swift */; };
@@ -64,8 +69,10 @@
AC32E16821DD55B900BD4F83 /* OrderedDictionary.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC32E16721DD55B900BD4F83 /* OrderedDictionary.swift */; };
AC347B5C20E5A7E1003449CF /* APNSTypeChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC347B5B20E5A7E1003449CF /* APNSTypeChecker.swift */; };
AC347B6720E699FA003449CF /* IterableAppExtensions.h in Headers */ = {isa = PBXBuildFile; fileRef = AC347B6620E699D8003449CF /* IterableAppExtensions.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ AC3A336D24F65579008225BA /* RequestProcessorUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3A336C24F65579008225BA /* RequestProcessorUtil.swift */; };
AC3C10F9213F46A900A9B839 /* IterableLogging.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3C10F8213F46A900A9B839 /* IterableLogging.swift */; };
AC3DD9C82142F3650046F886 /* ClassExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3DD9C72142F3650046F886 /* ClassExtensions.swift */; };
+ AC3EFFF02510B8FB007F1330 /* TaskSchedulerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC3EFFEF2510B8FB007F1330 /* TaskSchedulerTests.swift */; };
AC4095A422B18B9D006EF67C /* InboxViewControllerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4095A322B18B9D006EF67C /* InboxViewControllerViewModel.swift */; };
AC426226238C27DD00164121 /* IterableInboxCell+Layout.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC426225238C27DD00164121 /* IterableInboxCell+Layout.swift */; };
AC426CC4211B5497002EDBE8 /* ServerResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC426CC3211B5497002EDBE8 /* ServerResponse.swift */; };
@@ -73,7 +80,15 @@
AC4B039622A8743F0043185B /* InAppManager+Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4B039322A8743F0043185B /* InAppManager+Functions.swift */; };
AC4BA00224163D8F007359F1 /* IterableHtmlMessageViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4BA00124163D8F007359F1 /* IterableHtmlMessageViewControllerTests.swift */; };
AC4BAE0A240BAF0E00D9121F /* OHHTTPStubs in Frameworks */ = {isa = PBXBuildFile; productRef = AC4BAE09240BAF0E00D9121F /* OHHTTPStubs */; };
+ AC50865424C60172001DC132 /* IterableDataModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = AC50865224C60172001DC132 /* IterableDataModel.xcdatamodeld */; };
+ AC50865624C603AC001DC132 /* IterablePersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC50865524C603AC001DC132 /* IterablePersistence.swift */; };
+ AC50865824C60426001DC132 /* IterableTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC50865724C60426001DC132 /* IterableTask.swift */; };
+ AC50865A24C60572001DC132 /* IterableCoreDataPersistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC50865924C60572001DC132 /* IterableCoreDataPersistence.swift */; };
+ AC5812F624F3A90F007E6D36 /* OfflineRequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5812F524F3A90F007E6D36 /* OfflineRequestProcessor.swift */; };
+ AC5812F824F3AE8D007E6D36 /* RequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5812F724F3AE8D007E6D36 /* RequestProcessor.swift */; };
+ AC5E888924E1B7CE00752321 /* OnlineRequestProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC5E888824E1B7CE00752321 /* OnlineRequestProcessor.swift */; };
AC64626B2140AACF0046E1BD /* IterableAPIResponseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC64626A2140AACF0046E1BD /* IterableAPIResponseTests.swift */; };
+ AC67AF982507481200C1E974 /* NetworkConnectivityCheckerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC67AF972507481200C1E974 /* NetworkConnectivityCheckerTests.swift */; };
AC684A86222EF75C00F29749 /* InAppMessageParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC684A85222EF75C00F29749 /* InAppMessageParser.swift */; };
AC684A88222F4FDD00F29749 /* InAppDisplayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC684A87222F4FDD00F29749 /* InAppDisplayer.swift */; };
AC6FDD8820F4372E005D811E /* IterableAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC6FDD8720F4372E005D811E /* IterableAPI.swift */; };
@@ -116,10 +131,12 @@
AC8874AA22178BD80075B54B /* InAppContentParser.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8874A922178BD80075B54B /* InAppContentParser.swift */; };
AC89661E2124FBCE0051A6CD /* IterableAutoRegistrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC89661D2124FBCE0051A6CD /* IterableAutoRegistrationTests.swift */; };
AC8A058924AB1FE1002C1103 /* Environment.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8A058824AB1FE1002C1103 /* Environment.swift */; };
+ AC8E7CA524C7555E0039605F /* CoreDataUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8E7CA424C7555E0039605F /* CoreDataUtil.swift */; };
AC8E9268246284F800BEB68E /* DataFieldsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8E9267246284F800BEB68E /* DataFieldsHelper.swift */; };
AC8F35A2239806B500302994 /* InboxViewControllerViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC8F35A1239806B500302994 /* InboxViewControllerViewModelTests.swift */; };
AC90C4CD20D8632E00EECA5D /* IterableAppExtensions.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC90C4C420D8632D00EECA5D /* IterableAppExtensions.framework */; };
AC90C4E220D8639E00EECA5D /* ITBNotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC90C4E120D8639E00EECA5D /* ITBNotificationServiceExtension.swift */; };
+ AC978D3E24FF953C00372B8C /* NetworkConnectivityChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC978D3D24FF953C00372B8C /* NetworkConnectivityChecker.swift */; };
AC995F992166EE490099A184 /* CommonMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F942166EC880099A184 /* CommonMocks.swift */; };
AC995F9A2166EEB50099A184 /* CommonMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F942166EC880099A184 /* CommonMocks.swift */; };
AC995F9D2167E9FD0099A184 /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F9C2167E9FD0099A184 /* CommonExtensions.swift */; };
@@ -141,6 +158,18 @@
ACB37AB124026C1E0093A8EA /* SampleInboxViewDelegateImplementations.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB37AAF240268A60093A8EA /* SampleInboxViewDelegateImplementations.swift */; };
ACB8273F22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB8273E22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift */; };
ACBDDE5C23C4EDEC0008CC4D /* InboxCustomizationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACBDDE5B23C4EDEC0008CC4D /* InboxCustomizationTests.swift */; };
+ ACC362B624D16D91002C67BA /* IterableRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362B524D16D91002C67BA /* IterableRequest.swift */; };
+ ACC362B824D17005002C67BA /* IterableRequestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362B724D17005002C67BA /* IterableRequestTests.swift */; };
+ ACC362BA24D20BBB002C67BA /* IterableAPICallRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362B924D20BBB002C67BA /* IterableAPICallRequest.swift */; };
+ ACC362BD24D21172002C67BA /* IterableAPICallTaskProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362BC24D21172002C67BA /* IterableAPICallTaskProcessor.swift */; };
+ ACC362BF24D21192002C67BA /* IterableTaskProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362BE24D21192002C67BA /* IterableTaskProcessor.swift */; };
+ ACC362C124D21272002C67BA /* IterableTaskResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362C024D21272002C67BA /* IterableTaskResult.swift */; };
+ ACC362C324D21332002C67BA /* IterableTaskError.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362C224D21332002C67BA /* IterableTaskError.swift */; };
+ ACC362C524D2C190002C67BA /* TaskProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC362C424D2C190002C67BA /* TaskProcessorTests.swift */; };
+ ACC362C624D2C334002C67BA /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F9C2167E9FD0099A184 /* CommonExtensions.swift */; };
+ ACC362C724D2C647002C67BA /* CommonMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F942166EC880099A184 /* CommonMocks.swift */; };
+ ACC362C824D2C7C9002C67BA /* TestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 00CB31B4210960C4004ACDEC /* TestUtils.swift */; };
+ ACC362C924D2CA8C002C67BA /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACA8D1A42196309C001B1332 /* Common.swift */; };
ACC51A6B22A879070095E81F /* EmptyInAppManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC4B039122A8743F0043185B /* EmptyInAppManager.swift */; };
ACC6A84F2323910D003CC4BE /* UITestsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC6A84E2323910D003CC4BE /* UITestsHelper.swift */; };
ACC6A8502323910D003CC4BE /* UITestsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC6A84E2323910D003CC4BE /* UITestsHelper.swift */; };
@@ -148,6 +177,7 @@
ACC87766215C20B50097E29B /* UITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACC87765215C20B50097E29B /* UITests.swift */; };
ACC8776D215C23CC0097E29B /* IterableSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2263DF20CF49B8009800EB /* IterableSDK.framework */; };
ACC8776E215C23CC0097E29B /* IterableSDK.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = AC2263DF20CF49B8009800EB /* IterableSDK.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
+ ACCF274C24F40C85004862D5 /* RequestProcessorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACCF274B24F40C85004862D5 /* RequestProcessorTests.swift */; };
ACD6116C2107D004003E7F6B /* NetworkHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD6116B2107D004003E7F6B /* NetworkHelper.swift */; };
ACD6116E21080564003E7F6B /* IterableAPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACD6116D21080564003E7F6B /* IterableAPITests.swift */; };
ACDA975C23159C37004C412E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACDA975B23159C37004C412E /* AppDelegate.swift */; };
@@ -165,11 +195,19 @@
ACED4C01213F50B30055A497 /* LoggingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACED4C00213F50B30055A497 /* LoggingTests.swift */; };
ACEDF41D2183C2EC000B9BFE /* Promise.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACEDF41C2183C2EC000B9BFE /* Promise.swift */; };
ACEDF41F2183C436000B9BFE /* PromiseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACEDF41E2183C436000B9BFE /* PromiseTests.swift */; };
+ ACF32BDB24E3EA7C0072E2CC /* RequestProcessorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF32BDA24E3EA7C0072E2CC /* RequestProcessorProtocol.swift */; };
+ ACF40621250781F1005FD775 /* NetworkConnectivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF40620250781F1005FD775 /* NetworkConnectivityManager.swift */; };
+ ACF406232507BC72005FD775 /* NetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF406222507BC72005FD775 /* NetworkMonitor.swift */; };
+ ACF406252507F90F005FD775 /* NetworkConnectivityManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF406242507F90F005FD775 /* NetworkConnectivityManagerTests.swift */; };
ACF560D620E443BF000AAC23 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF560D520E443BF000AAC23 /* AppDelegate.swift */; };
ACF560DB20E443BF000AAC23 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ACF560D920E443BF000AAC23 /* Main.storyboard */; };
ACF560DD20E443C0000AAC23 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = ACF560DC20E443C0000AAC23 /* Assets.xcassets */; };
ACF560E020E443C0000AAC23 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = ACF560DE20E443C0000AAC23 /* LaunchScreen.storyboard */; };
ACF560E820E55A6B000AAC23 /* IterableActionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACF560E720E55A6B000AAC23 /* IterableActionContext.swift */; };
+ ACFD5AB324C8179D008E497A /* PersistenceHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFD5AB224C8179D008E497A /* PersistenceHelper.swift */; };
+ ACFD5ABD24C8200C008E497A /* IterableSDK.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AC2263DF20CF49B8009800EB /* IterableSDK.framework */; };
+ ACFD5AC624C8216A008E497A /* TasksCRUDTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFD5AC524C8216A008E497A /* TasksCRUDTests.swift */; };
+ ACFD5AC824C8290E008E497A /* IterableTaskManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACFD5AC724C8290E008E497A /* IterableTaskManagedObject.swift */; };
ACFF4287246569D300FDF10D /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F9C2167E9FD0099A184 /* CommonExtensions.swift */; };
ACFF428824656A2000FDF10D /* CommonMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F942166EC880099A184 /* CommonMocks.swift */; };
ACFF428F24656BDF00FDF10D /* CommonExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = AC995F9C2167E9FD0099A184 /* CommonExtensions.swift */; };
@@ -241,6 +279,20 @@
remoteGlobalIDString = ACF560D220E443BF000AAC23;
remoteInfo = "host-app";
};
+ ACFD5ABE24C8200C008E497A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = AC2263D620CF49B8009800EB /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = AC2263DE20CF49B8009800EB;
+ remoteInfo = "swift-sdk";
+ };
+ ACFD5AC324C82013008E497A /* PBXContainerItemProxy */ = {
+ isa = PBXContainerItemProxy;
+ containerPortal = AC2263D620CF49B8009800EB /* Project object */;
+ proxyType = 1;
+ remoteGlobalIDString = ACF560D220E443BF000AAC23;
+ remoteInfo = "host-app";
+ };
ACFF428B24656BDF00FDF10D /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = AC2263D620CF49B8009800EB /* Project object */;
@@ -326,6 +378,8 @@
AC0A45372179300D0040394F /* host-app.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "host-app.entitlements"; sourceTree = ""; };
AC1670CC2230A91C00989F8E /* InboxTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxTests.swift; sourceTree = ""; };
AC1712882416AEF400F2BB0E /* WebViewProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebViewProtocol.swift; sourceTree = ""; };
+ AC1AA1C524EBB2DC00F29C6B /* IterableTaskRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTaskRunner.swift; sourceTree = ""; };
+ AC1AA1C824EBB3C300F29C6B /* IterableNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableNotifications.swift; sourceTree = ""; };
AC1BED9423F1D4C700FDD75F /* MiscInboxClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiscInboxClasses.swift; sourceTree = ""; };
AC219C48225FD7EB00B98631 /* IterableInboxViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableInboxViewController.swift; sourceTree = ""; };
AC219C4C225FE4C000B98631 /* InboxMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxMessageViewModel.swift; sourceTree = ""; };
@@ -341,6 +395,8 @@
AC29D05B24B5A7E000A9E019 /* CI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CI.swift; sourceTree = ""; };
AC2A2987231CFAC40070A9C3 /* NetworkTableViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkTableViewController.swift; sourceTree = ""; };
AC2A2989231D44C00070A9C3 /* NetworkDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkDetailViewController.swift; sourceTree = ""; };
+ AC2AED4124EBC60C000EE5F3 /* TaskRunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskRunnerTests.swift; sourceTree = ""; };
+ AC2AED4324EBC905000EE5F3 /* IterableTaskScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTaskScheduler.swift; sourceTree = ""; };
AC2B79F621E6A38900A59080 /* NotificationHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationHelper.swift; sourceTree = ""; };
AC2C667D20D3111900D46CC9 /* DateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateProvider.swift; sourceTree = ""; };
AC2C667F20D31B1F00D46CC9 /* IterableNotificationResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableNotificationResponseTests.swift; sourceTree = ""; };
@@ -352,15 +408,25 @@
AC32E16721DD55B900BD4F83 /* OrderedDictionary.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OrderedDictionary.swift; sourceTree = ""; };
AC347B5B20E5A7E1003449CF /* APNSTypeChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNSTypeChecker.swift; sourceTree = ""; };
AC347B6620E699D8003449CF /* IterableAppExtensions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = IterableAppExtensions.h; sourceTree = ""; };
+ AC3A336C24F65579008225BA /* RequestProcessorUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorUtil.swift; sourceTree = ""; };
AC3C10F8213F46A900A9B839 /* IterableLogging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableLogging.swift; sourceTree = ""; };
AC3DD9C72142F3650046F886 /* ClassExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClassExtensions.swift; sourceTree = ""; };
+ AC3EFFEF2510B8FB007F1330 /* TaskSchedulerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSchedulerTests.swift; sourceTree = ""; };
AC4095A322B18B9D006EF67C /* InboxViewControllerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxViewControllerViewModel.swift; sourceTree = ""; };
AC426225238C27DD00164121 /* IterableInboxCell+Layout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "IterableInboxCell+Layout.swift"; sourceTree = ""; };
AC426CC3211B5497002EDBE8 /* ServerResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerResponse.swift; sourceTree = ""; };
AC4B039122A8743F0043185B /* EmptyInAppManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyInAppManager.swift; sourceTree = ""; };
AC4B039322A8743F0043185B /* InAppManager+Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "InAppManager+Functions.swift"; sourceTree = ""; };
AC4BA00124163D8F007359F1 /* IterableHtmlMessageViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableHtmlMessageViewControllerTests.swift; sourceTree = ""; };
+ AC50865324C60172001DC132 /* IterableDataModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = IterableDataModel.xcdatamodel; sourceTree = ""; };
+ AC50865524C603AC001DC132 /* IterablePersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterablePersistence.swift; sourceTree = ""; };
+ AC50865724C60426001DC132 /* IterableTask.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTask.swift; sourceTree = ""; };
+ AC50865924C60572001DC132 /* IterableCoreDataPersistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableCoreDataPersistence.swift; sourceTree = ""; };
+ AC5812F524F3A90F007E6D36 /* OfflineRequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineRequestProcessor.swift; sourceTree = ""; };
+ AC5812F724F3AE8D007E6D36 /* RequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessor.swift; sourceTree = ""; };
+ AC5E888824E1B7CE00752321 /* OnlineRequestProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnlineRequestProcessor.swift; sourceTree = ""; };
AC64626A2140AACF0046E1BD /* IterableAPIResponseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPIResponseTests.swift; sourceTree = ""; };
+ AC67AF972507481200C1E974 /* NetworkConnectivityCheckerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectivityCheckerTests.swift; sourceTree = ""; };
AC684A85222EF75C00F29749 /* InAppMessageParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageParser.swift; sourceTree = ""; };
AC684A87222F4FDD00F29749 /* InAppDisplayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppDisplayer.swift; sourceTree = ""; };
AC6FDD8720F4372E005D811E /* IterableAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPI.swift; sourceTree = ""; };
@@ -402,6 +468,7 @@
AC8874A922178BD80075B54B /* InAppContentParser.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppContentParser.swift; sourceTree = ""; };
AC89661D2124FBCE0051A6CD /* IterableAutoRegistrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAutoRegistrationTests.swift; sourceTree = ""; };
AC8A058824AB1FE1002C1103 /* Environment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Environment.swift; sourceTree = ""; };
+ AC8E7CA424C7555E0039605F /* CoreDataUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataUtil.swift; sourceTree = ""; };
AC8E9267246284F800BEB68E /* DataFieldsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataFieldsHelper.swift; sourceTree = ""; };
AC8F35A1239806B500302994 /* InboxViewControllerViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxViewControllerViewModelTests.swift; sourceTree = ""; };
AC90C4C420D8632D00EECA5D /* IterableAppExtensions.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = IterableAppExtensions.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -409,6 +476,7 @@
AC90C4CC20D8632E00EECA5D /* notification-extension-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "notification-extension-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
AC90C4D520D8632E00EECA5D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
AC90C4E120D8639E00EECA5D /* ITBNotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ITBNotificationServiceExtension.swift; sourceTree = ""; };
+ AC978D3D24FF953C00372B8C /* NetworkConnectivityChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectivityChecker.swift; sourceTree = ""; };
AC98294A20D9D65E00796DAA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
AC995F942166EC880099A184 /* CommonMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonMocks.swift; sourceTree = ""; };
AC995F9C2167E9FD0099A184 /* CommonExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonExtensions.swift; sourceTree = ""; };
@@ -423,11 +491,20 @@
ACB37AAF240268A60093A8EA /* SampleInboxViewDelegateImplementations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SampleInboxViewDelegateImplementations.swift; sourceTree = ""; };
ACB8273E22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableHtmlMessageViewController.swift; sourceTree = ""; };
ACBDDE5B23C4EDEC0008CC4D /* InboxCustomizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxCustomizationTests.swift; sourceTree = ""; };
+ ACC362B524D16D91002C67BA /* IterableRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableRequest.swift; sourceTree = ""; };
+ ACC362B724D17005002C67BA /* IterableRequestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableRequestTests.swift; sourceTree = ""; };
+ ACC362B924D20BBB002C67BA /* IterableAPICallRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPICallRequest.swift; sourceTree = ""; };
+ ACC362BC24D21172002C67BA /* IterableAPICallTaskProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPICallTaskProcessor.swift; sourceTree = ""; };
+ ACC362BE24D21192002C67BA /* IterableTaskProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTaskProcessor.swift; sourceTree = ""; };
+ ACC362C024D21272002C67BA /* IterableTaskResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTaskResult.swift; sourceTree = ""; };
+ ACC362C224D21332002C67BA /* IterableTaskError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTaskError.swift; sourceTree = ""; };
+ ACC362C424D2C190002C67BA /* TaskProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskProcessorTests.swift; sourceTree = ""; };
ACC6A84E2323910D003CC4BE /* UITestsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsHelper.swift; sourceTree = ""; };
ACC6A851232407B5003CC4BE /* InboxUITestsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InboxUITestsHelper.swift; sourceTree = ""; };
ACC87763215C20B50097E29B /* ui-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "ui-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
ACC87765215C20B50097E29B /* UITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITests.swift; sourceTree = ""; };
ACC87767215C20B50097E29B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ ACCF274B24F40C85004862D5 /* RequestProcessorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorTests.swift; sourceTree = ""; };
ACD6116B2107D004003E7F6B /* NetworkHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkHelper.swift; sourceTree = ""; };
ACD6116D21080564003E7F6B /* IterableAPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableAPITests.swift; sourceTree = ""; };
ACDA975923159C36004C412E /* inbox-ui-tests-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "inbox-ui-tests-app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -447,6 +524,10 @@
ACED4C00213F50B30055A497 /* LoggingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingTests.swift; sourceTree = ""; };
ACEDF41C2183C2EC000B9BFE /* Promise.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Promise.swift; sourceTree = ""; };
ACEDF41E2183C436000B9BFE /* PromiseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromiseTests.swift; sourceTree = ""; };
+ ACF32BDA24E3EA7C0072E2CC /* RequestProcessorProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestProcessorProtocol.swift; sourceTree = ""; };
+ ACF40620250781F1005FD775 /* NetworkConnectivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectivityManager.swift; sourceTree = ""; };
+ ACF406222507BC72005FD775 /* NetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkMonitor.swift; sourceTree = ""; };
+ ACF406242507F90F005FD775 /* NetworkConnectivityManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NetworkConnectivityManagerTests.swift; sourceTree = ""; };
ACF560D320E443BF000AAC23 /* host-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "host-app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
ACF560D520E443BF000AAC23 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
ACF560DA20E443BF000AAC23 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
@@ -454,8 +535,12 @@
ACF560DF20E443C0000AAC23 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
ACF560E120E443C0000AAC23 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
ACF560E720E55A6B000AAC23 /* IterableActionContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableActionContext.swift; sourceTree = ""; };
+ ACFD5AB224C8179D008E497A /* PersistenceHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceHelper.swift; sourceTree = ""; };
+ ACFD5AB824C8200C008E497A /* offline-events-tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "offline-events-tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+ ACFD5ABC24C8200C008E497A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
+ ACFD5AC524C8216A008E497A /* TasksCRUDTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksCRUDTests.swift; sourceTree = ""; };
+ ACFD5AC724C8290E008E497A /* IterableTaskManagedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IterableTaskManagedObject.swift; sourceTree = ""; };
ACFF429E24656BDF00FDF10D /* ui-tests-app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ui-tests-app.app"; sourceTree = BUILT_PRODUCTS_DIR; };
- ACFF429F24656BDF00FDF10D /* host-app copy-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "host-app copy-Info.plist"; path = "/Users/tapash.majumder/work/iterable/mobile/ios/swift-sdk/host-app copy-Info.plist"; sourceTree = ""; };
ACFF42A324656CA100FDF10D /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
ACFF42A624656D2600FDF10D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
ACFF42A824656D8E00FDF10D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
@@ -535,6 +620,14 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ ACFD5AB524C8200C008E497A /* Frameworks */ = {
+ isa = PBXFrameworksBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ ACFD5ABD24C8200C008E497A /* IterableSDK.framework in Frameworks */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
ACFF429224656BDF00FDF10D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -587,6 +680,14 @@
name = "Helper Files";
sourceTree = "";
};
+ AC1AA1C724EBB39500F29C6B /* Notification Center */ = {
+ isa = PBXGroup;
+ children = (
+ AC1AA1C824EBB3C300F29C6B /* IterableNotifications.swift */,
+ );
+ name = "Notification Center";
+ sourceTree = "";
+ };
AC219C4A225FD7F900B98631 /* Inbox UI */ = {
isa = PBXGroup;
children = (
@@ -615,7 +716,6 @@
ACDA975A23159C37004C412E /* inbox-ui-tests-app */,
ACFCA72920EB02DB00BFB277 /* tests */,
5550F22324217CFC0014456A /* misc */,
- ACFF429F24656BDF00FDF10D /* host-app copy-Info.plist */,
);
sourceTree = "";
};
@@ -632,6 +732,7 @@
ACDA976C23159C39004C412E /* inbox-ui-tests-app-ui-tests.xctest */,
ACFF429E24656BDF00FDF10D /* ui-tests-app.app */,
AC28480724AA44C600C1FC7F /* endpoint-tests.xctest */,
+ ACFD5AB824C8200C008E497A /* offline-events-tests.xctest */,
);
name = Products;
path = ../..;
@@ -741,10 +842,35 @@
children = (
AC219C522260006600B98631 /* Assets.xcassets */,
AC219C4F225FEDBD00B98631 /* SampleInboxCell.xib */,
+ AC50865224C60172001DC132 /* IterableDataModel.xcdatamodeld */,
);
path = Resources;
sourceTree = "";
};
+ AC50865124C60133001DC132 /* Persistence */ = {
+ isa = PBXGroup;
+ children = (
+ AC50865524C603AC001DC132 /* IterablePersistence.swift */,
+ AC50865924C60572001DC132 /* IterableCoreDataPersistence.swift */,
+ AC8E7CA424C7555E0039605F /* CoreDataUtil.swift */,
+ ACFD5AB224C8179D008E497A /* PersistenceHelper.swift */,
+ ACFD5AC724C8290E008E497A /* IterableTaskManagedObject.swift */,
+ );
+ name = Persistence;
+ sourceTree = "";
+ };
+ AC5E888724E1B7AD00752321 /* Request Processing */ = {
+ isa = PBXGroup;
+ children = (
+ ACF32BDA24E3EA7C0072E2CC /* RequestProcessorProtocol.swift */,
+ AC5E888824E1B7CE00752321 /* OnlineRequestProcessor.swift */,
+ AC5812F524F3A90F007E6D36 /* OfflineRequestProcessor.swift */,
+ AC5812F724F3AE8D007E6D36 /* RequestProcessor.swift */,
+ AC3A336C24F65579008225BA /* RequestProcessorUtil.swift */,
+ );
+ name = "Request Processing";
+ sourceTree = "";
+ };
AC72A0AC20CF4C08004D7997 /* Util */ = {
isa = PBXGroup;
children = (
@@ -760,6 +886,11 @@
AC72A0BB20CF4C8C004D7997 /* Internal */ = {
isa = PBXGroup;
children = (
+ ACF4061F25078186005FD775 /* Network */,
+ AC1AA1C724EBB39500F29C6B /* Notification Center */,
+ AC5E888724E1B7AD00752321 /* Request Processing */,
+ ACC362BB24D21153002C67BA /* Task Processing */,
+ AC50865124C60133001DC132 /* Persistence */,
AC845105228DF5360052BB8F /* API Client */,
AC0248062279132400495FB9 /* Dwifft */,
AC426CC5211B5527002EDBE8 /* Fp */,
@@ -776,7 +907,6 @@
AC72A0C520CF4CB9004D7997 /* IterableAPIInternal.swift */,
AC2C668120D32F2800D46CC9 /* IterableAppIntegrationInternal.swift */,
AC72A0BD20CF4C98004D7997 /* IterableDeepLinkManager.swift */,
- ACD6116B2107D004003E7F6B /* NetworkHelper.swift */,
AC2B79F621E6A38900A59080 /* NotificationHelper.swift */,
ACEDF41C2183C2EC000B9BFE /* Promise.swift */,
);
@@ -820,6 +950,7 @@
AC4BA00124163D8F007359F1 /* IterableHtmlMessageViewControllerTests.swift */,
5585DF8E22A73390000A32B9 /* IterableInboxViewControllerTests.swift */,
AC2C667F20D31B1F00D46CC9 /* IterableNotificationResponseTests.swift */,
+ ACC362B724D17005002C67BA /* IterableRequestTests.swift */,
AC776DA3211A17C700C27C27 /* IterableRequestUtilTests.swift */,
ACE34AB4213776CB00691224 /* LocalStorageTests.swift */,
ACED4C00213F50B30055A497 /* LoggingTests.swift */,
@@ -859,6 +990,7 @@
55B9F15224B6625D00E8198A /* ApiClientProtocol.swift */,
AC8E9267246284F800BEB68E /* DataFieldsHelper.swift */,
AC84510822910A0C0052BB8F /* RequestCreator.swift */,
+ ACC362B524D16D91002C67BA /* IterableRequest.swift */,
);
name = "API Client";
sourceTree = "";
@@ -914,6 +1046,21 @@
path = common;
sourceTree = "";
};
+ ACC362BB24D21153002C67BA /* Task Processing */ = {
+ isa = PBXGroup;
+ children = (
+ ACC362B924D20BBB002C67BA /* IterableAPICallRequest.swift */,
+ ACC362BE24D21192002C67BA /* IterableTaskProcessor.swift */,
+ ACC362BC24D21172002C67BA /* IterableAPICallTaskProcessor.swift */,
+ AC50865724C60426001DC132 /* IterableTask.swift */,
+ ACC362C024D21272002C67BA /* IterableTaskResult.swift */,
+ ACC362C224D21332002C67BA /* IterableTaskError.swift */,
+ AC1AA1C524EBB2DC00F29C6B /* IterableTaskRunner.swift */,
+ AC2AED4324EBC905000EE5F3 /* IterableTaskScheduler.swift */,
+ );
+ name = "Task Processing";
+ sourceTree = "";
+ };
ACC87764215C20B50097E29B /* ui-tests */ = {
isa = PBXGroup;
children = (
@@ -963,6 +1110,17 @@
name = "Local Storage";
sourceTree = "";
};
+ ACF4061F25078186005FD775 /* Network */ = {
+ isa = PBXGroup;
+ children = (
+ ACD6116B2107D004003E7F6B /* NetworkHelper.swift */,
+ AC978D3D24FF953C00372B8C /* NetworkConnectivityChecker.swift */,
+ ACF40620250781F1005FD775 /* NetworkConnectivityManager.swift */,
+ ACF406222507BC72005FD775 /* NetworkMonitor.swift */,
+ );
+ name = Network;
+ sourceTree = "";
+ };
ACF560D420E443BF000AAC23 /* host-app */ = {
isa = PBXGroup;
children = (
@@ -985,10 +1143,26 @@
AC90C4D220D8632E00EECA5D /* notification-extension-tests */,
AC7B142C20D02CE200877BFE /* swift-sdk-swift-tests */,
ACC87764215C20B50097E29B /* ui-tests */,
+ ACFD5AB924C8200C008E497A /* offline-events-tests */,
);
path = tests;
sourceTree = "";
};
+ ACFD5AB924C8200C008E497A /* offline-events-tests */ = {
+ isa = PBXGroup;
+ children = (
+ ACFD5ABC24C8200C008E497A /* Info.plist */,
+ ACFD5AC524C8216A008E497A /* TasksCRUDTests.swift */,
+ ACC362C424D2C190002C67BA /* TaskProcessorTests.swift */,
+ AC2AED4124EBC60C000EE5F3 /* TaskRunnerTests.swift */,
+ ACCF274B24F40C85004862D5 /* RequestProcessorTests.swift */,
+ AC67AF972507481200C1E974 /* NetworkConnectivityCheckerTests.swift */,
+ ACF406242507F90F005FD775 /* NetworkConnectivityManagerTests.swift */,
+ AC3EFFEF2510B8FB007F1330 /* TaskSchedulerTests.swift */,
+ );
+ path = "offline-events-tests";
+ sourceTree = "";
+ };
ACFF42A224656C6200FDF10D /* ui-tests-app */ = {
isa = PBXGroup;
children = (
@@ -1205,6 +1379,25 @@
productReference = ACF560D320E443BF000AAC23 /* host-app.app */;
productType = "com.apple.product-type.application";
};
+ ACFD5AB724C8200C008E497A /* offline-events-tests */ = {
+ isa = PBXNativeTarget;
+ buildConfigurationList = ACFD5AC224C8200C008E497A /* Build configuration list for PBXNativeTarget "offline-events-tests" */;
+ buildPhases = (
+ ACFD5AB424C8200C008E497A /* Sources */,
+ ACFD5AB524C8200C008E497A /* Frameworks */,
+ ACFD5AB624C8200C008E497A /* Resources */,
+ );
+ buildRules = (
+ );
+ dependencies = (
+ ACFD5ABF24C8200C008E497A /* PBXTargetDependency */,
+ ACFD5AC424C82013008E497A /* PBXTargetDependency */,
+ );
+ name = "offline-events-tests";
+ productName = "offline-events-tests";
+ productReference = ACFD5AB824C8200C008E497A /* offline-events-tests.xctest */;
+ productType = "com.apple.product-type.bundle.unit-test";
+ };
ACFF428924656BDF00FDF10D /* ui-tests-app */ = {
isa = PBXNativeTarget;
buildConfigurationList = ACFF429B24656BDF00FDF10D /* Build configuration list for PBXNativeTarget "ui-tests-app" */;
@@ -1306,6 +1499,7 @@
ACDA975823159C36004C412E /* inbox-ui-tests-app */,
ACDA976B23159C39004C412E /* inbox-ui-tests-app-ui-tests */,
AC28480624AA44C600C1FC7F /* endpoint-tests */,
+ ACFD5AB724C8200C008E497A /* offline-events-tests */,
);
};
/* End PBXProject section */
@@ -1390,6 +1584,13 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ ACFD5AB624C8200C008E497A /* Resources */ = {
+ isa = PBXResourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
ACFF429424656BDF00FDF10D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1409,39 +1610,53 @@
files = (
AC31B042232AB53500BE25EB /* InboxImpressionTracker.swift in Sources */,
55D54656239AE5750093ED1E /* LoggingInternal.swift in Sources */,
+ ACF32BDB24E3EA7C0072E2CC /* RequestProcessorProtocol.swift in Sources */,
AC426CC4211B5497002EDBE8 /* ServerResponse.swift in Sources */,
AC219C49225FD7EB00B98631 /* IterableInboxViewController.swift in Sources */,
AC3DD9C82142F3650046F886 /* ClassExtensions.swift in Sources */,
AC219C50225FEDBD00B98631 /* IterableInboxCell.swift in Sources */,
ACE6888D2228B86C00A95E5E /* InAppInternal.swift in Sources */,
+ AC1AA1C924EBB3C300F29C6B /* IterableNotifications.swift in Sources */,
+ ACFD5AB324C8179D008E497A /* PersistenceHelper.swift in Sources */,
AC72A0CD20CF4CE2004D7997 /* IterableAppIntegration.swift in Sources */,
AC72A0CE20CF4CE2004D7997 /* IterableAttributionInfo.swift in Sources */,
+ AC50865424C60172001DC132 /* IterableDataModel.xcdatamodeld in Sources */,
ACA8D1A321910C66001B1332 /* IterableMessaging.swift in Sources */,
+ ACF40621250781F1005FD775 /* NetworkConnectivityManager.swift in Sources */,
AC4B039622A8743F0043185B /* InAppManager+Functions.swift in Sources */,
AC72A0D420CF4D19004D7997 /* IterableDeepLinkManager.swift in Sources */,
ACC51A6B22A879070095E81F /* EmptyInAppManager.swift in Sources */,
+ ACC362BF24D21192002C67BA /* IterableTaskProcessor.swift in Sources */,
AC684A86222EF75C00F29749 /* InAppMessageParser.swift in Sources */,
AC72A0C920CF4CE2004D7997 /* IterableAction.swift in Sources */,
AC72A0CA20CF4CE2004D7997 /* IterableActionRunner.swift in Sources */,
ACD6116C2107D004003E7F6B /* NetworkHelper.swift in Sources */,
+ ACC362B624D16D91002C67BA /* IterableRequest.swift in Sources */,
+ ACC362BD24D21172002C67BA /* IterableAPICallTaskProcessor.swift in Sources */,
AC84510922910A0C0052BB8F /* RequestCreator.swift in Sources */,
ACB8273F22372A5C00DB17D3 /* IterableHtmlMessageViewController.swift in Sources */,
+ AC5812F624F3A90F007E6D36 /* OfflineRequestProcessor.swift in Sources */,
AC81918A22713A400014955E /* AbstractDiffCalculator.swift in Sources */,
AC684A88222F4FDD00F29749 /* InAppDisplayer.swift in Sources */,
AC72A0D220CF4D12004D7997 /* IterableUtil.swift in Sources */,
AC32E16821DD55B900BD4F83 /* OrderedDictionary.swift in Sources */,
+ ACF406232507BC72005FD775 /* NetworkMonitor.swift in Sources */,
AC1712892416AEF400F2BB0E /* WebViewProtocol.swift in Sources */,
AC3C10F9213F46A900A9B839 /* IterableLogging.swift in Sources */,
ACE34AB72139D70B00691224 /* LocalStorageProtocol.swift in Sources */,
AC819186227139230014955E /* SectionedValues.swift in Sources */,
AC6FDD8820F4372E005D811E /* IterableAPI.swift in Sources */,
ACF560E820E55A6B000AAC23 /* IterableActionContext.swift in Sources */,
+ AC5812F824F3AE8D007E6D36 /* RequestProcessor.swift in Sources */,
AC2C668220D32F2800D46CC9 /* IterableAppIntegrationInternal.swift in Sources */,
AC1BED9523F1D4C700FDD75F /* MiscInboxClasses.swift in Sources */,
556FB1EA244FAF6A00EDF6BD /* InAppPresenter.swift in Sources */,
+ AC2AED4424EBC905000EE5F3 /* IterableTaskScheduler.swift in Sources */,
55B9F15324B6625D00E8198A /* ApiClientProtocol.swift in Sources */,
AC219C4D225FE4C000B98631 /* InboxMessageViewModel.swift in Sources */,
+ AC50865A24C60572001DC132 /* IterableCoreDataPersistence.swift in Sources */,
AC72A0C820CF4CE2004D7997 /* Constants.swift in Sources */,
+ AC50865824C60426001DC132 /* IterableTask.swift in Sources */,
AC2B79F721E6A38900A59080 /* NotificationHelper.swift in Sources */,
AC4095A422B18B9D006EF67C /* InboxViewControllerViewModel.swift in Sources */,
AC7A5261227BB9D10064D67E /* DependencyContainer.swift in Sources */,
@@ -1455,6 +1670,8 @@
AC845107228DF54E0052BB8F /* ApiClient.swift in Sources */,
AC72A0D120CF4D0B004D7997 /* InAppHelper.swift in Sources */,
AC8874AA22178BD80075B54B /* InAppContentParser.swift in Sources */,
+ AC50865624C603AC001DC132 /* IterablePersistence.swift in Sources */,
+ ACC362BA24D20BBB002C67BA /* IterableAPICallRequest.swift in Sources */,
AC776DA22118B86600C27C27 /* DeviceInfo.swift in Sources */,
55B3119B251015CF0056E4FC /* AuthManager.swift in Sources */,
AC72A0CB20CF4CE2004D7997 /* IterableAPIInternal.swift in Sources */,
@@ -1462,11 +1679,19 @@
AC31B040232AB42100BE25EB /* InboxSessionManager.swift in Sources */,
ACE34AB321376B1000691224 /* UserDefaultsLocalStorage.swift in Sources */,
AC8E9268246284F800BEB68E /* DataFieldsHelper.swift in Sources */,
+ AC5E888924E1B7CE00752321 /* OnlineRequestProcessor.swift in Sources */,
+ AC978D3E24FF953C00372B8C /* NetworkConnectivityChecker.swift in Sources */,
+ ACC362C324D21332002C67BA /* IterableTaskError.swift in Sources */,
+ ACC362C124D21272002C67BA /* IterableTaskResult.swift in Sources */,
AC2C667E20D3111900D46CC9 /* DateProvider.swift in Sources */,
ACA8D1AB21966555001B1332 /* InAppManager.swift in Sources */,
AC81918822713A110014955E /* Dwifft+UIKit.swift in Sources */,
AC426226238C27DD00164121 /* IterableInboxCell+Layout.swift in Sources */,
AC347B5C20E5A7E1003449CF /* APNSTypeChecker.swift in Sources */,
+ AC3A336D24F65579008225BA /* RequestProcessorUtil.swift in Sources */,
+ AC8E7CA524C7555E0039605F /* CoreDataUtil.swift in Sources */,
+ AC1AA1C624EBB2DC00F29C6B /* IterableTaskRunner.swift in Sources */,
+ ACFD5AC824C8290E008E497A /* IterableTaskManagedObject.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1490,6 +1715,7 @@
buildActionMask = 2147483647;
files = (
ACA8D1A62196309C001B1332 /* Common.swift in Sources */,
+ ACC362B824D17005002C67BA /* IterableRequestTests.swift in Sources */,
AC2C668720D3435700D46CC9 /* IterableActionRunnerTests.swift in Sources */,
00CB31B621096129004ACDEC /* TestUtils.swift in Sources */,
55E6F460238E066400808BCE /* DeferredDeepLinkTests.swift in Sources */,
@@ -1605,6 +1831,25 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
+ ACFD5AB424C8200C008E497A /* Sources */ = {
+ isa = PBXSourcesBuildPhase;
+ buildActionMask = 2147483647;
+ files = (
+ AC3EFFF02510B8FB007F1330 /* TaskSchedulerTests.swift in Sources */,
+ AC67AF982507481200C1E974 /* NetworkConnectivityCheckerTests.swift in Sources */,
+ ACFD5AC624C8216A008E497A /* TasksCRUDTests.swift in Sources */,
+ ACC362C624D2C334002C67BA /* CommonExtensions.swift in Sources */,
+ ACF406252507F90F005FD775 /* NetworkConnectivityManagerTests.swift in Sources */,
+ ACCF274C24F40C85004862D5 /* RequestProcessorTests.swift in Sources */,
+ ACC362C724D2C647002C67BA /* CommonMocks.swift in Sources */,
+ ACC362C824D2C7C9002C67BA /* TestUtils.swift in Sources */,
+ AC241C2224F5757C00F8F9CC /* Mocks.swift in Sources */,
+ ACC362C924D2CA8C002C67BA /* Common.swift in Sources */,
+ ACC362C524D2C190002C67BA /* TaskProcessorTests.swift in Sources */,
+ AC2AED4224EBC60C000EE5F3 /* TaskRunnerTests.swift in Sources */,
+ );
+ runOnlyForDeploymentPostprocessing = 0;
+ };
ACFF428C24656BDF00FDF10D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -1660,6 +1905,16 @@
target = ACF560D220E443BF000AAC23 /* host-app */;
targetProxy = ACFCA72320EAB2B900BFB277 /* PBXContainerItemProxy */;
};
+ ACFD5ABF24C8200C008E497A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = AC2263DE20CF49B8009800EB /* swift-sdk */;
+ targetProxy = ACFD5ABE24C8200C008E497A /* PBXContainerItemProxy */;
+ };
+ ACFD5AC424C82013008E497A /* PBXTargetDependency */ = {
+ isa = PBXTargetDependency;
+ target = ACF560D220E443BF000AAC23 /* host-app */;
+ targetProxy = ACFD5AC324C82013008E497A /* PBXContainerItemProxy */;
+ };
ACFF428A24656BDF00FDF10D /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = AC2263DE20CF49B8009800EB /* swift-sdk */;
@@ -1913,7 +2168,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = JZ52G3H3Z6;
+ DEVELOPMENT_TEAM = BP98Z28R86;
INFOPLIST_FILE = "tests/endpoint-tests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.5;
LD_RUNPATH_SEARCH_PATHS = (
@@ -1934,7 +2189,7 @@
isa = XCBuildConfiguration;
buildSettings = {
CODE_SIGN_STYLE = Automatic;
- DEVELOPMENT_TEAM = JZ52G3H3Z6;
+ DEVELOPMENT_TEAM = BP98Z28R86;
INFOPLIST_FILE = "tests/endpoint-tests/Info.plist";
IPHONEOS_DEPLOYMENT_TARGET = 13.5;
LD_RUNPATH_SEARCH_PATHS = (
@@ -2239,7 +2494,7 @@
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "iterable.host-app";
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = "host-app";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -2260,8 +2515,51 @@
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "iterable.host-app";
+ PRODUCT_NAME = "host-app";
+ TARGETED_DEVICE_FAMILY = "1,2";
+ };
+ name = Release;
+ };
+ ACFD5AC024C8200C008E497A /* Debug */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = BP98Z28R86;
+ INFOPLIST_FILE = "tests/offline-events-tests/Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.6;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.iterable.offline-events-tests";
+ PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
+ TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/host-app.app/host-app";
+ };
+ name = Debug;
+ };
+ ACFD5AC124C8200C008E497A /* Release */ = {
+ isa = XCBuildConfiguration;
+ buildSettings = {
+ CODE_SIGN_STYLE = Automatic;
+ DEVELOPMENT_TEAM = BP98Z28R86;
+ INFOPLIST_FILE = "tests/offline-events-tests/Info.plist";
+ IPHONEOS_DEPLOYMENT_TARGET = 13.6;
+ LD_RUNPATH_SEARCH_PATHS = (
+ "$(inherited)",
+ "@executable_path/Frameworks",
+ "@loader_path/Frameworks",
+ );
+ MTL_FAST_MATH = YES;
+ PRODUCT_BUNDLE_IDENTIFIER = "com.iterable.offline-events-tests";
PRODUCT_NAME = "$(TARGET_NAME)";
+ SWIFT_VERSION = 5.0;
TARGETED_DEVICE_FAMILY = "1,2";
+ TEST_HOST = "$(BUILT_PRODUCTS_DIR)/host-app.app/host-app";
};
name = Release;
};
@@ -2281,7 +2579,7 @@
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "iterable.ui-tests-app";
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = "ui-tests-app";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
@@ -2302,7 +2600,7 @@
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = "iterable.ui-tests-app";
- PRODUCT_NAME = "$(TARGET_NAME)";
+ PRODUCT_NAME = "ui-tests-app";
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Release;
@@ -2400,6 +2698,15 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
+ ACFD5AC224C8200C008E497A /* Build configuration list for PBXNativeTarget "offline-events-tests" */ = {
+ isa = XCConfigurationList;
+ buildConfigurations = (
+ ACFD5AC024C8200C008E497A /* Debug */,
+ ACFD5AC124C8200C008E497A /* Release */,
+ );
+ defaultConfigurationIsVisible = 0;
+ defaultConfigurationName = Release;
+ };
ACFF429B24656BDF00FDF10D /* Build configuration list for PBXNativeTarget "ui-tests-app" */ = {
isa = XCConfigurationList;
buildConfigurations = (
@@ -2429,6 +2736,19 @@
productName = OHHTTPStubs;
};
/* End XCSwiftPackageProductDependency section */
+
+/* Begin XCVersionGroup section */
+ AC50865224C60172001DC132 /* IterableDataModel.xcdatamodeld */ = {
+ isa = XCVersionGroup;
+ children = (
+ AC50865324C60172001DC132 /* IterableDataModel.xcdatamodel */,
+ );
+ currentVersion = AC50865324C60172001DC132 /* IterableDataModel.xcdatamodel */;
+ path = IterableDataModel.xcdatamodeld;
+ sourceTree = "";
+ versionGroupType = wrapper.xcdatamodel;
+ };
+/* End XCVersionGroup section */
};
rootObject = AC2263D620CF49B8009800EB /* Project object */;
}
diff --git a/swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme b/swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme
index 4c8bc650f..878afa34a 100644
--- a/swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme
+++ b/swift-sdk.xcodeproj/xcshareddata/xcschemes/swift-sdk.xcscheme
@@ -62,6 +62,20 @@
ReferencedContainer = "container:swift-sdk.xcodeproj">
+
+
+
+
+
+
+
+
RequestCreator {
+ private func createRequestCreator() -> Result {
guard let authProvider = authProvider else {
- fatalError("authProvider is missing")
+ return .failure(IterableError.general(description: "authProvider is missing"))
}
- return RequestCreator(apiKey: apiKey, auth: authProvider.auth, deviceMetadata: deviceMetadata)
+ return .success(RequestCreator(apiKey: apiKey, auth: authProvider.auth, deviceMetadata: deviceMetadata))
}
+
private func createIterableHeaders() -> [String: String] {
var headers = [JsonKey.contentType.jsonKey: JsonValue.applicationJson.jsonStringValue,
@@ -85,52 +86,53 @@ class ApiClient {
// MARK: - API REQUEST CALLS
extension ApiClient: ApiClientProtocol {
- func register(hexToken: String,
- appName: String,
- deviceId: String,
- sdkVersion: String?,
- deviceAttributes: [String: String],
- pushServicePlatform: String,
- notificationsEnabled: Bool) -> Future {
- send(iterableRequestResult: createRequestCreator().createRegisterTokenRequest(hexToken: hexToken,
- appName: appName,
- deviceId: deviceId,
- sdkVersion: sdkVersion,
- deviceAttributes: deviceAttributes,
- pushServicePlatform: pushServicePlatform,
- notificationsEnabled: notificationsEnabled))
+ func register(registerTokenInfo: RegisterTokenInfo, notificationsEnabled: Bool) -> Future {
+ let result = createRequestCreator().flatMap { $0.createRegisterTokenRequest(registerTokenInfo: registerTokenInfo,
+ notificationsEnabled: notificationsEnabled) }
+ return send(iterableRequestResult: result)
}
func updateUser(_ dataFields: [AnyHashable: Any], mergeNestedObjects: Bool) -> Future {
- send(iterableRequestResult: createRequestCreator().createUpdateUserRequest(dataFields: dataFields, mergeNestedObjects: mergeNestedObjects))
+ let result = createRequestCreator().flatMap { $0.createUpdateUserRequest(dataFields: dataFields,
+ mergeNestedObjects: mergeNestedObjects) }
+ return send(iterableRequestResult: result)
}
func updateEmail(newEmail: String) -> Future {
- send(iterableRequestResult: createRequestCreator().createUpdateEmailRequest(newEmail: newEmail))
+ let result = createRequestCreator().flatMap { $0.createUpdateEmailRequest(newEmail: newEmail) }
+ return send(iterableRequestResult: result)
}
func getInAppMessages(_ count: NSNumber) -> Future {
- send(iterableRequestResult: createRequestCreator().createGetInAppMessagesRequest(count))
+ let result = createRequestCreator().flatMap { $0.createGetInAppMessagesRequest(count) }
+ return send(iterableRequestResult: result)
}
func disableDevice(forAllUsers allUsers: Bool, hexToken: String) -> Future {
- send(iterableRequestResult: createRequestCreator().createDisableDeviceRequest(forAllUsers: allUsers, hexToken: hexToken))
+ let result = createRequestCreator().flatMap { $0.createDisableDeviceRequest(forAllUsers: allUsers,
+ hexToken: hexToken) }
+ return send(iterableRequestResult: result)
}
func track(purchase total: NSNumber, items: [CommerceItem], dataFields: [AnyHashable: Any]?) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackPurchaseRequest(total, items: items, dataFields: dataFields))
+ let result = createRequestCreator().flatMap { $0.createTrackPurchaseRequest(total, items: items,
+ dataFields: dataFields) }
+ return send(iterableRequestResult: result)
}
func track(pushOpen campaignId: NSNumber, templateId: NSNumber?, messageId: String, appAlreadyRunning: Bool, dataFields: [AnyHashable: Any]?) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackPushOpenRequest(campaignId,
- templateId: templateId,
- messageId: messageId,
- appAlreadyRunning: appAlreadyRunning,
- dataFields: dataFields))
+ let result = createRequestCreator().flatMap { $0.createTrackPushOpenRequest(campaignId,
+ templateId: templateId,
+ messageId: messageId,
+ appAlreadyRunning: appAlreadyRunning,
+ dataFields: dataFields) }
+ return send(iterableRequestResult: result)
}
func track(event eventName: String, dataFields: [AnyHashable: Any]?) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackEventRequest(eventName, dataFields: dataFields))
+ let result = createRequestCreator().flatMap { $0.createTrackEventRequest(eventName,
+ dataFields: dataFields) }
+ return send(iterableRequestResult: result)
}
func updateSubscriptions(_ emailListIds: [NSNumber]? = nil,
@@ -139,40 +141,52 @@ extension ApiClient: ApiClientProtocol {
subscribedMessageTypeIds: [NSNumber]? = nil,
campaignId: NSNumber? = nil,
templateId: NSNumber? = nil) -> Future {
- send(iterableRequestResult: createRequestCreator().createUpdateSubscriptionsRequest(emailListIds,
- unsubscribedChannelIds: unsubscribedChannelIds,
- unsubscribedMessageTypeIds: unsubscribedMessageTypeIds,
- subscribedMessageTypeIds: subscribedMessageTypeIds,
- campaignId: campaignId,
- templateId: templateId))
+ let result = createRequestCreator().flatMap { $0.createUpdateSubscriptionsRequest(emailListIds,
+ unsubscribedChannelIds: unsubscribedChannelIds,
+ unsubscribedMessageTypeIds: unsubscribedMessageTypeIds,
+ subscribedMessageTypeIds: subscribedMessageTypeIds,
+ campaignId: campaignId,
+ templateId: templateId) }
+ return send(iterableRequestResult: result)
}
func track(inAppOpen inAppMessageContext: InAppMessageContext) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppOpenRequest(inAppMessageContext: inAppMessageContext))
+ let result = createRequestCreator().flatMap { $0.createTrackInAppOpenRequest(inAppMessageContext: inAppMessageContext) }
+ return send(iterableRequestResult: result)
}
func track(inAppClick inAppMessageContext: InAppMessageContext, clickedUrl: String) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppClickRequest(inAppMessageContext: inAppMessageContext, clickedUrl: clickedUrl))
+ let result = createRequestCreator().flatMap { $0.createTrackInAppClickRequest(inAppMessageContext: inAppMessageContext,
+ clickedUrl: clickedUrl) }
+ return send(iterableRequestResult: result)
}
func track(inAppClose inAppMessageContext: InAppMessageContext, source: InAppCloseSource?, clickedUrl: String?) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppCloseRequest(inAppMessageContext: inAppMessageContext, source: source, clickedUrl: clickedUrl))
+ let result = createRequestCreator().flatMap { $0.createTrackInAppCloseRequest(inAppMessageContext: inAppMessageContext,
+ source: source,
+ clickedUrl: clickedUrl) }
+ return send(iterableRequestResult: result)
}
func track(inAppDelivery inAppMessageContext: InAppMessageContext) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppDeliveryRequest(inAppMessageContext: inAppMessageContext))
+ let result = createRequestCreator().flatMap { $0.createTrackInAppDeliveryRequest(inAppMessageContext: inAppMessageContext) }
+ return send(iterableRequestResult: result)
}
func track(inboxSession: IterableInboxSession) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInboxSessionRequest(inboxSession: inboxSession))
+ let result = createRequestCreator().flatMap { $0.createTrackInboxSessionRequest(inboxSession: inboxSession) }
+ return send(iterableRequestResult: result)
}
func inAppConsume(messageId: String) -> Future {
- send(iterableRequestResult: createRequestCreator().createInAppConsumeRequest(messageId))
+ let result = createRequestCreator().flatMap { $0.createInAppConsumeRequest(messageId) }
+ return send(iterableRequestResult: result)
}
func inAppConsume(inAppMessageContext: InAppMessageContext, source: InAppDeleteSource?) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppConsumeRequest(inAppMessageContext: inAppMessageContext, source: source))
+ let result = createRequestCreator().flatMap { $0.createTrackInAppConsumeRequest(inAppMessageContext: inAppMessageContext,
+ source: source) }
+ return send(iterableRequestResult: result)
}
}
@@ -181,11 +195,14 @@ extension ApiClient: ApiClientProtocol {
extension ApiClient {
// deprecated - will be removed in version 6.3.x or above
func track(inAppOpen messageId: String) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppOpenRequest(messageId))
+ let value = createRequestCreator().flatMap { $0.createTrackInAppOpenRequest(messageId) }
+ return send(iterableRequestResult: value)
}
// deprecated - will be removed in version 6.3.x or above
func track(inAppClick messageId: String, clickedUrl: String) -> Future {
- send(iterableRequestResult: createRequestCreator().createTrackInAppClickRequest(messageId, clickedUrl: clickedUrl))
+ let result = createRequestCreator().flatMap { $0.createTrackInAppClickRequest(messageId,
+ clickedUrl: clickedUrl) }
+ return send(iterableRequestResult: result)
}
}
diff --git a/swift-sdk/Internal/ApiClientProtocol.swift b/swift-sdk/Internal/ApiClientProtocol.swift
index 92c9225da..e3a3a8548 100644
--- a/swift-sdk/Internal/ApiClientProtocol.swift
+++ b/swift-sdk/Internal/ApiClientProtocol.swift
@@ -6,13 +6,7 @@
import Foundation
protocol ApiClientProtocol: AnyObject {
- func register(hexToken: String,
- appName: String,
- deviceId: String,
- sdkVersion: String?,
- deviceAttributes: [String: String],
- pushServicePlatform: String,
- notificationsEnabled: Bool) -> Future
+ func register(registerTokenInfo: RegisterTokenInfo, notificationsEnabled: Bool) -> Future
func updateUser(_ dataFields: [AnyHashable: Any], mergeNestedObjects: Bool) -> Future
diff --git a/swift-sdk/Internal/Auth.swift b/swift-sdk/Internal/Auth.swift
index 86539d900..faaf182a4 100644
--- a/swift-sdk/Internal/Auth.swift
+++ b/swift-sdk/Internal/Auth.swift
@@ -30,3 +30,5 @@ struct Auth {
case none
}
}
+
+extension Auth: Codable {}
diff --git a/swift-sdk/Internal/ClassExtensions.swift b/swift-sdk/Internal/ClassExtensions.swift
index 515f8d7be..b17e00ba8 100644
--- a/swift-sdk/Internal/ClassExtensions.swift
+++ b/swift-sdk/Internal/ClassExtensions.swift
@@ -14,6 +14,16 @@ extension Array {
}
}
+extension Array where Element: Comparable {
+ func isAscending() -> Bool {
+ return zip(self, self.dropFirst()).allSatisfy(<=)
+ }
+
+ func isDescending() -> Bool {
+ return zip(self, self.dropFirst()).allSatisfy(>=)
+ }
+}
+
extension Dictionary where Key == AnyHashable, Value == Any {
func getValue(for key: JsonKey) -> Any? {
self[key.jsonKey]
diff --git a/swift-sdk/Internal/CoreDataUtil.swift b/swift-sdk/Internal/CoreDataUtil.swift
new file mode 100644
index 000000000..e0e8891fd
--- /dev/null
+++ b/swift-sdk/Internal/CoreDataUtil.swift
@@ -0,0 +1,68 @@
+//
+// Created by Tapash Majumder on 7/21/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+// This file should contain general CoreData helper methods.
+// This should not be dependent on Iterable classes.
+
+import CoreData
+import Foundation
+
+struct CoreDataUtil {
+ static func create(context: NSManagedObjectContext, entity: String) -> T? {
+ NSEntityDescription.insertNewObject(forEntityName: entity, into: context) as? T
+ }
+
+ static func findEntitiyByColumn(context: NSManagedObjectContext,
+ entity: String,
+ columnName: String,
+ columnValue: Any) throws -> T? {
+ try findEntitiesByColumns(context: context, entity: entity, columns: [columnName: columnValue]).first
+ }
+
+ static func findAll(context: NSManagedObjectContext, entity: String) throws -> [T] {
+ let request = NSFetchRequest(entityName: entity)
+ return try context.fetch(request)
+ }
+
+ static func findEntitiesByColumns(context: NSManagedObjectContext, entity: String, columns: [String: Any]) throws -> [T] {
+ let request = NSFetchRequest(entityName: entity)
+ request.predicate = createColumnsPredicate(columns: columns)
+ return try context.fetch(request)
+ }
+
+ static func findSortedEntities(context: NSManagedObjectContext,
+ entity: String,
+ column: String,
+ ascending: Bool,
+ limit: Int) throws -> [T] {
+
+ let sortDescriptor = NSSortDescriptor(key: column, ascending: ascending)
+ let request = NSFetchRequest(entityName: entity)
+ request.sortDescriptors = [sortDescriptor]
+ request.fetchLimit = limit
+
+ return try context.fetch(request)
+ }
+
+ private static func createColumnsPredicate(columns: [String: Any]) -> NSPredicate {
+ var subPredicates = [NSPredicate]()
+ for (columnName, columnValue) in columns {
+ subPredicates.append(createColumnPredicate(columnName: columnName, columnValue: columnValue))
+ }
+
+ return NSCompoundPredicate(andPredicateWithSubpredicates: subPredicates)
+ }
+
+ private static func createColumnPredicate(columnName: String, columnValue: Any) -> NSPredicate {
+ if let stringValue = columnValue as? String {
+ return NSPredicate(format: "%K ==[c] %@", columnName, stringValue)
+ } else if let intValue = columnValue as? Int {
+ return NSPredicate(format: "%K == %d", columnName, intValue)
+ } else if let boolValue = columnValue as? Bool {
+ return NSPredicate(format: "%K == %@", columnName, NSNumber(value: boolValue))
+ } else {
+ fatalError("unsuppored value: \(columnValue)")
+ }
+ }
+}
diff --git a/swift-sdk/Internal/DataFieldsHelper.swift b/swift-sdk/Internal/DataFieldsHelper.swift
index c04305f8e..381c48071 100644
--- a/swift-sdk/Internal/DataFieldsHelper.swift
+++ b/swift-sdk/Internal/DataFieldsHelper.swift
@@ -60,7 +60,7 @@ struct DataFieldsHelper {
fields[JsonKey.Device.systemName] = device.systemName
fields[JsonKey.Device.systemVersion] = device.systemVersion
fields[JsonKey.Device.model] = device.model
-
+
if let identifierForVendor = device.identifierForVendor?.uuidString {
fields[JsonKey.Device.vendorId] = identifierForVendor
}
diff --git a/swift-sdk/Internal/DependencyContainer.swift b/swift-sdk/Internal/DependencyContainer.swift
index df69bc09e..d7a1c2d82 100644
--- a/swift-sdk/Internal/DependencyContainer.swift
+++ b/swift-sdk/Internal/DependencyContainer.swift
@@ -20,6 +20,7 @@ protocol DependencyContainerProtocol {
var apnsTypeChecker: APNSTypeCheckerProtocol { get }
func createInAppFetcher(apiClient: ApiClientProtocol) -> InAppFetcherProtocol
+ func createPersistenceContextProvider() -> IterablePersistenceContextProvider?
}
extension DependencyContainerProtocol {
@@ -47,6 +48,66 @@ extension DependencyContainerProtocol {
localStorage: localStorage,
dateProvider: dateProvider)
}
+
+ func createRequestProcessor(apiKey: String,
+ config: IterableConfig,
+ authProvider: AuthProvider?,
+ authManager: IterableInternalAuthManagerProtocol,
+ deviceMetadata: DeviceMetadata) -> RequestProcessorProtocol {
+ if #available(iOS 10.0, *) {
+ return RequestProcessor(onlineCreator: { [weak authProvider] in
+ OnlineRequestProcessor(apiKey: apiKey,
+ authProvider: authProvider,
+ authManager: authManager,
+ endPoint: config.apiEndpoint,
+ networkSession: networkSession,
+ deviceMetadata: deviceMetadata) },
+ offlineCreator: { [weak authProvider] in
+ guard let persistenceContextProvider = createPersistenceContextProvider() else {
+ return nil
+ }
+
+ return OfflineRequestProcessor(apiKey: apiKey,
+ authProvider: authProvider,
+ authManager: authManager,
+ endPoint: config.apiEndpoint,
+ deviceMetadata: deviceMetadata,
+ taskScheduler: createTaskScheduler(persistenceContextProvider: persistenceContextProvider),
+ taskRunner: createTaskRunner(persistenceContextProvider: persistenceContextProvider),
+ notificationCenter: notificationCenter) },
+ strategy: DefaultRequestProcessorStrategy(selectOffline: config.enableOfflineMode))
+ } else {
+ return OnlineRequestProcessor(apiKey: apiKey,
+ authProvider: authProvider,
+ authManager: authManager,
+ endPoint: config.apiEndpoint,
+ networkSession: networkSession,
+ deviceMetadata: deviceMetadata)
+ }
+ }
+
+ func createPersistenceContextProvider() -> IterablePersistenceContextProvider? {
+ if #available(iOS 10.0, *) {
+ return CoreDataPersistenceContextProvider(dateProvider: dateProvider)
+ } else {
+ fatalError("Unable to create persistence container for iOS < 10")
+ }
+ }
+
+ @available(iOS 10.0, *)
+ private func createTaskScheduler(persistenceContextProvider: IterablePersistenceContextProvider) -> IterableTaskScheduler {
+ IterableTaskScheduler(persistenceContextProvider: persistenceContextProvider,
+ notificationCenter: notificationCenter,
+ dateProvider: dateProvider)
+ }
+
+ @available(iOS 10.0, *)
+ private func createTaskRunner(persistenceContextProvider: IterablePersistenceContextProvider) -> IterableTaskRunner {
+ IterableTaskRunner(networkSession: networkSession,
+ persistenceContextProvider: persistenceContextProvider,
+ notificationCenter: notificationCenter,
+ connectivityManager: NetworkConnectivityManager())
+ }
}
struct DependencyContainer: DependencyContainerProtocol {
diff --git a/swift-sdk/Internal/InAppInternal.swift b/swift-sdk/Internal/InAppInternal.swift
index 6e606d0a7..2eb3b0c59 100644
--- a/swift-sdk/Internal/InAppInternal.swift
+++ b/swift-sdk/Internal/InAppInternal.swift
@@ -10,7 +10,7 @@ protocol InAppFetcherProtocol {
}
/// For callbacks when silent push notifications arrive
-protocol InAppNotifiable {
+protocol InAppNotifiable: AnyObject {
func scheduleSync() -> Future
func onInAppRemoved(messageId: String)
func reset() -> Future
diff --git a/swift-sdk/Internal/InAppManager.swift b/swift-sdk/Internal/InAppManager.swift
index 8fc017d92..80bb9939d 100644
--- a/swift-sdk/Internal/InAppManager.swift
+++ b/swift-sdk/Internal/InAppManager.swift
@@ -6,14 +6,6 @@
import Foundation
import UIKit
-protocol NotificationCenterProtocol {
- func addObserver(_ observer: Any, selector: Selector, name: Notification.Name?, object: Any?)
- func removeObserver(_ observer: Any)
- func post(name: Notification.Name, object: Any?, userInfo: [AnyHashable: Any]?)
-}
-
-extension NotificationCenter: NotificationCenterProtocol {}
-
protocol InAppDisplayChecker {
func isOkToShowNow(message: IterableInAppMessage) -> Bool
}
diff --git a/swift-sdk/Internal/IterableAPICallRequest.swift b/swift-sdk/Internal/IterableAPICallRequest.swift
new file mode 100644
index 000000000..1674a234e
--- /dev/null
+++ b/swift-sdk/Internal/IterableAPICallRequest.swift
@@ -0,0 +1,49 @@
+//
+// Created by Tapash Majumder on 7/30/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+/// This struct encapsulates all the data that needs to be sent to Iterable backend.
+/// This struct must be `Codable`.
+struct IterableAPICallRequest {
+ let apiKey: String
+ let endPoint: String
+ let auth: Auth
+ let deviceMetadata: DeviceMetadata
+ let iterableRequest: IterableRequest
+
+ func convertToURLRequest() -> URLRequest? {
+ switch iterableRequest {
+ case let .get(getRequest):
+ return IterableRequestUtil.createGetRequest(forApiEndPoint: endPoint, path: getRequest.path, headers: createIterableHeaders(), args: getRequest.args)
+ case let .post(postRequest):
+ return IterableRequestUtil.createPostRequest(forApiEndPoint: endPoint, path: postRequest.path, headers: createIterableHeaders(), args: postRequest.args, body: postRequest.body)
+ }
+ }
+
+ func getPath() -> String {
+ switch iterableRequest {
+ case .get(let request):
+ return request.path
+ case .post(let request):
+ return request.path
+ }
+ }
+
+ private func createIterableHeaders() -> [String: String] {
+ var headers = [JsonKey.contentType.jsonKey: JsonValue.applicationJson.jsonStringValue,
+ JsonKey.Header.sdkPlatform: JsonValue.iOS.jsonStringValue,
+ JsonKey.Header.sdkVersion: IterableAPI.sdkVersion,
+ JsonKey.Header.apiKey: apiKey]
+
+ if let authToken = auth.authToken {
+ headers[JsonKey.Header.authorization] = "Bearer \(authToken)"
+ }
+
+ return headers
+ }
+}
+
+extension IterableAPICallRequest: Codable {}
diff --git a/swift-sdk/Internal/IterableAPICallTaskProcessor.swift b/swift-sdk/Internal/IterableAPICallTaskProcessor.swift
new file mode 100644
index 000000000..eaf7b2271
--- /dev/null
+++ b/swift-sdk/Internal/IterableAPICallTaskProcessor.swift
@@ -0,0 +1,48 @@
+//
+// Created by Tapash Majumder on 7/30/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+struct IterableAPICallTaskProcessor: IterableTaskProcessor {
+ let networkSession: NetworkSessionProtocol
+
+ func process(task: IterableTask) throws -> Future {
+ ITBInfo()
+ guard let data = task.data else {
+ return IterableTaskError.createErroredFuture(reason: "expecting data")
+ }
+
+ let iterableRequest = try JSONDecoder().decode(IterableAPICallRequest.self, from: data)
+ guard let urlRequest = iterableRequest.convertToURLRequest() else {
+ return IterableTaskError.createErroredFuture(reason: "could not convert to url request")
+ }
+
+ let result = Promise()
+ NetworkHelper.sendRequest(urlRequest, usingSession: networkSession)
+ .onSuccess { sendRequestValue in
+ ITBInfo("Task finished successfully")
+ result.resolve(with: .success(detail: sendRequestValue))
+ }
+ .onError { sendRequestError in
+ if IterableAPICallTaskProcessor.isNetworkUnavailable(sendRequestError: sendRequestError) {
+ ITBInfo("Network is unavailable")
+ result.resolve(with: .failureWithRetry(retryAfter: nil, detail: sendRequestError))
+ } else {
+ ITBInfo("Unrecoverable error")
+ result.resolve(with: .failureWithNoRetry(detail: sendRequestError))
+ }
+ }
+
+ return result
+ }
+
+ private static func isNetworkUnavailable(sendRequestError: SendRequestError) -> Bool {
+ if let originalError = sendRequestError.originalError {
+ return originalError.localizedDescription.lowercased().contains("offline")
+ } else {
+ return false
+ }
+ }
+}
diff --git a/swift-sdk/Internal/IterableAPIInternal.swift b/swift-sdk/Internal/IterableAPIInternal.swift
index 3f646aa31..f656231b8 100644
--- a/swift-sdk/Internal/IterableAPIInternal.swift
+++ b/swift-sdk/Internal/IterableAPIInternal.swift
@@ -91,29 +91,6 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
deviceAttributes.removeValue(forKey: name)
}
- static func defaultOnSuccess(_ identifier: String) -> OnSuccessHandler {
- { data in
- if let data = data {
- ITBInfo("\(identifier) succeeded, got response: \(data)")
- } else {
- ITBInfo("\(identifier) succeeded.")
- }
- }
- }
-
- static func defaultOnFailure(_ identifier: String) -> OnFailureHandler {
- { reason, data in
- var toLog = "\(identifier) failed:"
- if let reason = reason {
- toLog += ", \(reason)"
- }
- if let data = data {
- toLog += ", got response \(String(data: data, encoding: .utf8) ?? "nil")"
- }
- ITBError(toLog)
- }
- }
-
func setEmail(_ email: String?) {
if email != _email {
logoutPreviousUser()
@@ -154,148 +131,165 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
// MARK: - API Request Calls
+ @discardableResult
func register(token: Data,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("registerToken"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("registerToken")) {
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
guard let appName = pushIntegrationName else {
- ITBError("registerToken: appName is nil")
- onFailure?("Not registering device token - appName must not be nil", nil)
- return
+ let errorMessage = "Not registering device token - appName must not be nil"
+ ITBError(errorMessage)
+ onFailure?(errorMessage, nil)
+ return SendRequestError.createErroredFuture(reason: errorMessage)
}
- self.register(token: token,
- appName: appName,
- pushServicePlatform: self.config.pushPlatform,
- notificationsEnabled: notificationStateProvider.notificationsEnabled,
- onSuccess: onSuccess,
- onFailure: onFailure)
+ hexToken = token.hexString()
+ let registerTokenInfo = RegisterTokenInfo(hexToken: token.hexString(),
+ appName: appName,
+ pushServicePlatform: config.pushPlatform,
+ apnsType: dependencyContainer.apnsTypeChecker.apnsType,
+ deviceId: deviceId,
+ deviceAttributes: deviceAttributes,
+ sdkVersion: localStorage.sdkVersion)
+ return requestProcessor.register(registerTokenInfo: registerTokenInfo,
+ notificationStateProvider: notificationStateProvider,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
}
- func disableDeviceForCurrentUser(withOnSuccess onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("disableDevice"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("disableDevice")) {
- disableDevice(forAllUsers: false, onSuccess: onSuccess, onFailure: onFailure)
+ @discardableResult
+ func disableDeviceForCurrentUser(withOnSuccess onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ guard let hexToken = hexToken else {
+ let errorMessage = "no token present"
+ onFailure?(errorMessage, nil)
+ return SendRequestError.createErroredFuture(reason: errorMessage)
+ }
+ guard userId != nil || email != nil else {
+ let errorMessage = "either userId or email must be present"
+ onFailure?(errorMessage, nil)
+ return SendRequestError.createErroredFuture(reason: errorMessage)
+ }
+
+ return requestProcessor.disableDeviceForCurrentUser(hexToken: hexToken, withOnSuccess: onSuccess, onFailure: onFailure)
}
- func disableDeviceForAllUsers(withOnSuccess onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("disableDevice"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("disableDevice")) {
- disableDevice(forAllUsers: true, onSuccess: onSuccess, onFailure: onFailure)
+ @discardableResult
+ func disableDeviceForAllUsers(withOnSuccess onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ guard let hexToken = hexToken else {
+ let errorMessage = "no token present"
+ onFailure?(errorMessage, nil)
+ return SendRequestError.createErroredFuture(reason: errorMessage)
+ }
+ return requestProcessor.disableDeviceForAllUsers(hexToken: hexToken, withOnSuccess: onSuccess, onFailure: onFailure)
}
+ @discardableResult
func updateUser(_ dataFields: [AnyHashable: Any],
mergeNestedObjects: Bool,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("updateUser"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("updateUser")) {
- IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.updateUser(dataFields, mergeNestedObjects: mergeNestedObjects))
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.updateUser(dataFields, mergeNestedObjects: mergeNestedObjects, onSuccess: onSuccess, onFailure: onFailure)
}
+ @discardableResult
func updateEmail(_ newEmail: String,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("updateEmail"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("updateEmail")) {
- IterableAPIInternal.call(
- successHandler: { json in
- if self.email != nil {
- self.setEmail(newEmail)
- }
-
- onSuccess?(json)
- },
- andFailureHandler: { reason, data in
- onFailure?(reason, data)
- },
- andAuthManager: authManager,
- forResult: apiClient.updateEmail(newEmail: newEmail))
+ withToken token: String? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.updateEmail(newEmail, onSuccess: nil, onFailure: nil).onSuccess { json in
+ if self.email != nil {
+ self.setEmail(newEmail)
+ }
+ onSuccess?(json)
+ }.onError { error in
+ onFailure?(error.reason, error.data)
+ }
}
+ @discardableResult
func trackPurchase(_ total: NSNumber,
items: [CommerceItem],
dataFields: [AnyHashable: Any]? = nil,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackPurchase"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackPurchase")) {
- IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.track(purchase: total, items: items, dataFields: dataFields))
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackPurchase(total, items: items, dataFields: dataFields, onSuccess: onSuccess, onFailure: onFailure)
}
+ @discardableResult
func trackPushOpen(_ userInfo: [AnyHashable: Any],
dataFields: [AnyHashable: Any]? = nil,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackPushOpen"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackPushOpen")) {
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
save(pushPayload: userInfo)
if let metadata = IterablePushNotificationMetadata.metadata(fromLaunchOptions: userInfo), metadata.isRealCampaignNotification() {
- trackPushOpen(metadata.campaignId,
- templateId: metadata.templateId,
- messageId: metadata.messageId,
- appAlreadyRunning: false,
- dataFields: dataFields,
- onSuccess: onSuccess,
- onFailure: onFailure)
+ return trackPushOpen(metadata.campaignId,
+ templateId: metadata.templateId,
+ messageId: metadata.messageId,
+ appAlreadyRunning: false,
+ dataFields: dataFields,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
} else {
- onFailure?("Not tracking push open - payload is not an Iterable notification, or is a test/proof/ghost push", nil)
+ return SendRequestError.createErroredFuture(reason: "Not tracking push open - payload is not an Iterable notification, or is a test/proof/ghost push")
}
}
+ @discardableResult
func trackPushOpen(_ campaignId: NSNumber,
templateId: NSNumber?,
messageId: String,
appAlreadyRunning: Bool,
dataFields: [AnyHashable: Any]? = nil,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackPushOpen"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackPushOpen")) {
- IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.track(pushOpen: campaignId,
- templateId: templateId,
- messageId: messageId,
- appAlreadyRunning: appAlreadyRunning,
- dataFields: dataFields))
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackPushOpen(campaignId,
+ templateId: templateId,
+ messageId: messageId,
+ appAlreadyRunning: appAlreadyRunning,
+ dataFields: dataFields,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
}
+ @discardableResult
func track(_ eventName: String,
dataFields: [AnyHashable: Any]? = nil,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackEvent"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackEvent")) {
- IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.track(event: eventName, dataFields: dataFields))
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.track(event: eventName, dataFields: dataFields, onSuccess: onSuccess, onFailure: onFailure)
}
+ @discardableResult
func updateSubscriptions(_ emailListIds: [NSNumber]?,
unsubscribedChannelIds: [NSNumber]?,
unsubscribedMessageTypeIds: [NSNumber]?,
subscribedMessageTypeIds: [NSNumber]?,
campaignId: NSNumber?,
templateId: NSNumber?,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("updateSubscriptions"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("updateSubscriptions")) {
- IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.updateSubscriptions(emailListIds,
- unsubscribedChannelIds: unsubscribedChannelIds,
- unsubscribedMessageTypeIds: unsubscribedMessageTypeIds,
- subscribedMessageTypeIds: subscribedMessageTypeIds,
- campaignId: campaignId,
- templateId: templateId))
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let updateSubscriptionsInfo = UpdateSubscriptionsInfo(emailListIds: emailListIds,
+ unsubscribedChannelIds: unsubscribedChannelIds,
+ unsubscribedMessageTypeIds: unsubscribedMessageTypeIds,
+ subscribedMessageTypeIds: subscribedMessageTypeIds,
+ campaignId: campaignId,
+ templateId: templateId)
+ return requestProcessor.updateSubscriptions(info: updateSubscriptionsInfo, onSuccess: onSuccess, onFailure: onFailure)
}
@discardableResult
func trackInAppOpen(_ message: IterableInAppMessage,
location: InAppLocation,
inboxSessionId: String? = nil,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackInAppOpen"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackInAppOpen")) -> Future {
- let result = apiClient.track(inAppOpen: InAppMessageContext.from(message: message, location: location, inboxSessionId: inboxSessionId))
- return IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: result)
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackInAppOpen(message,
+ location: location,
+ inboxSessionId: inboxSessionId,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
}
@discardableResult
@@ -303,14 +297,13 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
location: InAppLocation = .inApp,
inboxSessionId: String? = nil,
clickedUrl: String,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackInAppClick"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackInAppClick")) -> Future {
- let result = apiClient.track(inAppClick: InAppMessageContext.from(message: message, location: location, inboxSessionId: inboxSessionId),
- clickedUrl: clickedUrl)
- return IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: result)
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackInAppClick(message, location: location,
+ inboxSessionId: inboxSessionId,
+ clickedUrl: clickedUrl,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
}
@discardableResult
@@ -319,50 +312,49 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
inboxSessionId: String? = nil,
source: InAppCloseSource? = nil,
clickedUrl: String? = nil,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackInAppClose"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackInAppClose")) -> Future {
- let result = apiClient.track(inAppClose: InAppMessageContext.from(message: message, location: location, inboxSessionId: inboxSessionId),
- source: source,
- clickedUrl: clickedUrl)
- return IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: result)
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackInAppClose(message,
+ location: location,
+ inboxSessionId: inboxSessionId,
+ source: source,
+ clickedUrl: clickedUrl,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
}
@discardableResult
func track(inboxSession: IterableInboxSession,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("trackInboxSession"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("trackInboxSession")) -> Future {
- let result = apiClient.track(inboxSession: inboxSession)
-
- return IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: result)
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.track(inboxSession: inboxSession, onSuccess: onSuccess, onFailure: onFailure)
}
- func track(inAppDelivery message: IterableInAppMessage) {
- IterableAPIInternal.call(successHandler: IterableAPIInternal.defaultOnSuccess("trackInAppDelivery"),
- andFailureHandler: IterableAPIInternal.defaultOnFailure("trackInAppDelivery"),
- andAuthManager: authManager,
- forResult: apiClient.track(inAppDelivery: InAppMessageContext.from(message: message, location: nil)))
+ @discardableResult
+ func track(inAppDelivery message: IterableInAppMessage,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.track(inAppDelivery: message, onSuccess: onSuccess, onFailure: onFailure)
}
- func inAppConsume(_ messageId: String) {
- IterableAPIInternal.call(successHandler: IterableAPIInternal.defaultOnSuccess("inAppConsume"),
- andFailureHandler: IterableAPIInternal.defaultOnFailure("inAppConsume"),
- andAuthManager: authManager,
- forResult: apiClient.inAppConsume(messageId: messageId))
+ @discardableResult
+ func inAppConsume(_ messageId: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.inAppConsume(messageId, onSuccess: onSuccess, onFailure: onFailure)
}
- func inAppConsume(message: IterableInAppMessage, location: InAppLocation = .inApp, source: InAppDeleteSource? = nil) {
- let result = apiClient.inAppConsume(inAppMessageContext: InAppMessageContext.from(message: message, location: location),
- source: source)
- IterableAPIInternal.call(successHandler: IterableAPIInternal.defaultOnSuccess("inAppConsumeWithSource"),
- andFailureHandler: IterableAPIInternal.defaultOnFailure("inAppConsumeWithSource"),
- andAuthManager: authManager,
- forResult: result)
+ @discardableResult
+ func inAppConsume(message: IterableInAppMessage,
+ location: InAppLocation = .inApp,
+ source: InAppDeleteSource? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.inAppConsume(message: message,
+ location: location,
+ source: source,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
}
// MARK: - Private/Internal
@@ -387,7 +379,7 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
private var launchOptions: [UIApplication.LaunchOptionsKey: Any]?
- lazy var apiClient: ApiClient = {
+ lazy var apiClient: ApiClientProtocol = {
ApiClient(apiKey: apiKey,
authProvider: self,
endPoint: config.apiEndpoint,
@@ -395,6 +387,14 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
deviceMetadata: deviceMetadata)
}()
+ private lazy var requestProcessor: RequestProcessorProtocol = {
+ dependencyContainer.createRequestProcessor(apiKey: apiKey,
+ config: config,
+ authProvider: self,
+ authManager: authManager,
+ deviceMetadata: deviceMetadata)
+ }()
+
private var deviceAttributes = [String: String]()
private var pushIntegrationName: String? {
@@ -453,17 +453,6 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
_ = inAppManager.scheduleSync()
}
- private static func pushServicePlatformToString(_ pushServicePlatform: PushServicePlatform, apnsType: APNSType) -> String {
- switch pushServicePlatform {
- case .production:
- return JsonValue.apnsProduction.jsonStringValue
- case .sandbox:
- return JsonValue.apnsSandbox.jsonStringValue
- case .auto:
- return apnsType == .sandbox ? JsonValue.apnsSandbox.jsonStringValue : JsonValue.apnsProduction.jsonStringValue
- }
- }
-
private func storeIdentifierData() {
localStorage.email = _email
localStorage.userId = _userId
@@ -474,29 +463,6 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
_userId = localStorage.userId
}
- @discardableResult
- private func register(token: Data,
- appName: String,
- pushServicePlatform: PushServicePlatform,
- notificationsEnabled: Bool,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("registerToken"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("registerToken")) -> Future {
- hexToken = token.hexString()
-
- let pushServicePlatformString = IterableAPIInternal.pushServicePlatformToString(pushServicePlatform, apnsType: dependencyContainer.apnsTypeChecker.apnsType)
-
- return IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.register(hexToken: hexToken!,
- appName: appName,
- deviceId: deviceId,
- sdkVersion: localStorage.sdkVersion,
- deviceAttributes: deviceAttributes,
- pushServicePlatform: pushServicePlatformString,
- notificationsEnabled: notificationsEnabled))
- }
-
private func save(pushPayload payload: [AnyHashable: Any]) {
let expiration = Calendar.current.date(byAdding: .hour,
value: Const.UserDefaults.payloadExpiration,
@@ -510,50 +476,6 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
}
}
- private func disableDevice(forAllUsers allUsers: Bool,
- onSuccess: OnSuccessHandler? = IterableAPIInternal.defaultOnSuccess("disableDevice"),
- onFailure: OnFailureHandler? = IterableAPIInternal.defaultOnFailure("disableDevice")) {
- guard let hexToken = hexToken else {
- ITBError("Device not registered.")
- onFailure?("Device not registered.", nil)
- return
- }
-
- guard !(allUsers == false && email == nil && userId == nil) else {
- ITBError("Emal or userId must be set.")
- onFailure?("Email or userId must be set.", nil)
- return
- }
-
- IterableAPIInternal.call(successHandler: onSuccess,
- andFailureHandler: onFailure,
- andAuthManager: authManager,
- forResult: apiClient.disableDevice(forAllUsers: allUsers, hexToken: hexToken))
- }
-
- @discardableResult
- private static func call(successHandler onSuccess: OnSuccessHandler? = nil,
- andFailureHandler onFailure: OnFailureHandler? = nil,
- andAuthManager authManager: IterableInternalAuthManagerProtocol? = nil,
- forResult result: Future
- ) -> Future {
- result.onSuccess { json in
- authManager?.resetFailedAuthCount()
- onSuccess?(json)
- }.onError { error in
- if error.httpStatusCode == 401, error.iterableCode == JsonValue.Code.invalidJwtPayload {
- ITBError(error.reason)
- authManager?.requestNewAuthToken(hasFailedPriorAuth: true, onSuccess: nil)
- } else if error.httpStatusCode == 401, error.iterableCode == JsonValue.Code.badApiKey {
- ITBError(error.reason)
- }
-
- onFailure?(error.reason, error.data)
- }
-
- return result
- }
-
// package private method. Do not call this directly.
init(apiKey: String,
launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil,
@@ -596,6 +518,8 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
handle(launchOptions: launchOptions)
+ requestProcessor.start()
+
return inAppManager.start()
}
@@ -682,6 +606,7 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
deinit {
ITBInfo()
+ requestProcessor.stop()
}
}
@@ -689,17 +614,20 @@ final class IterableAPIInternal: NSObject, PushTrackerProtocol, AuthProvider {
extension IterableAPIInternal {
// deprecated - will be removed in version 6.3.x or above
- func trackInAppOpen(_ messageId: String) {
- IterableAPIInternal.call(successHandler: IterableAPIInternal.defaultOnSuccess("trackInAppOpen"),
- andFailureHandler: IterableAPIInternal.defaultOnFailure("trackInAppOpen"),
- forResult: apiClient.track(inAppOpen: messageId))
+ @discardableResult
+ func trackInAppOpen(_ messageId: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackInAppOpen(messageId, onSuccess: onSuccess, onFailure: onFailure)
}
// deprecated - will be removed in version 6.3.x or above
- func trackInAppClick(_ messageId: String, clickedUrl: String) {
- IterableAPIInternal.call(successHandler: IterableAPIInternal.defaultOnSuccess("trackInAppClick"),
- andFailureHandler: IterableAPIInternal.defaultOnFailure("trackInAppClick"),
- forResult: apiClient.track(inAppClick: messageId, clickedUrl: clickedUrl))
+ @discardableResult
+ func trackInAppClick(_ messageId: String,
+ clickedUrl: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ requestProcessor.trackInAppClick(messageId, clickedUrl: clickedUrl, onSuccess: onSuccess, onFailure: onFailure)
}
// deprecated - will be removed in version 6.3.x or above
diff --git a/swift-sdk/Internal/IterableAppIntegrationInternal.swift b/swift-sdk/Internal/IterableAppIntegrationInternal.swift
index d9ec55cd4..107fa9267 100644
--- a/swift-sdk/Internal/IterableAppIntegrationInternal.swift
+++ b/swift-sdk/Internal/IterableAppIntegrationInternal.swift
@@ -78,21 +78,23 @@ struct UserNotificationResponse: NotificationResponseProtocol {
}
/// Abstraction of push tracking
-public protocol PushTrackerProtocol: AnyObject {
+protocol PushTrackerProtocol: AnyObject {
var lastPushPayload: [AnyHashable: Any]? { get }
+ @discardableResult
func trackPushOpen(_ userInfo: [AnyHashable: Any],
dataFields: [AnyHashable: Any]?,
onSuccess: OnSuccessHandler?,
- onFailure: OnFailureHandler?)
+ onFailure: OnFailureHandler?) -> Future
+ @discardableResult
func trackPushOpen(_ campaignId: NSNumber,
templateId: NSNumber?,
messageId: String,
appAlreadyRunning: Bool,
dataFields: [AnyHashable: Any]?,
onSuccess: OnSuccessHandler?,
- onFailure: OnFailureHandler?)
+ onFailure: OnFailureHandler?) -> Future
}
extension PushTrackerProtocol {
@@ -100,8 +102,8 @@ extension PushTrackerProtocol {
dataFields: [AnyHashable: Any]? = nil) {
trackPushOpen(userInfo,
dataFields: dataFields,
- onSuccess: IterableAPIInternal.defaultOnSuccess("trackPushOpen"),
- onFailure: IterableAPIInternal.defaultOnFailure("trackPushOpen"))
+ onSuccess: nil,
+ onFailure: nil)
}
func trackPushOpen(_ campaignId: NSNumber,
@@ -114,8 +116,8 @@ extension PushTrackerProtocol {
messageId: messageId,
appAlreadyRunning: appAlreadyRunning,
dataFields: dataFields,
- onSuccess: IterableAPIInternal.defaultOnSuccess("trackPushOpen"),
- onFailure: IterableAPIInternal.defaultOnFailure("trackPushOpen"))
+ onSuccess: nil,
+ onFailure: nil)
}
}
@@ -127,11 +129,11 @@ extension PushTrackerProtocol {
extension UIApplication: ApplicationStateProviderProtocol {}
struct IterableAppIntegrationInternal {
- private let tracker: PushTrackerProtocol
+ private weak var tracker: PushTrackerProtocol?
private let urlDelegate: IterableURLDelegate?
private let customActionDelegate: IterableCustomActionDelegate?
private let urlOpener: UrlOpenerProtocol?
- private let inAppNotifiable: InAppNotifiable
+ private weak var inAppNotifiable: InAppNotifiable?
init(tracker: PushTrackerProtocol,
urlDelegate: IterableURLDelegate? = nil,
@@ -160,10 +162,10 @@ struct IterableAppIntegrationInternal {
if case let NotificationInfo.silentPush(silentPush) = NotificationHelper.inspect(notification: userInfo) {
switch silentPush.notificationType {
case .update:
- _ = inAppNotifiable.scheduleSync()
+ _ = inAppNotifiable?.scheduleSync()
case .remove:
if let messageId = silentPush.messageId {
- inAppNotifiable.onInAppRemoved(messageId: messageId)
+ inAppNotifiable?.onInAppRemoved(messageId: messageId)
} else {
ITBError("messageId not found in 'remove' silent push")
}
@@ -220,7 +222,7 @@ struct IterableAppIntegrationInternal {
// Track push open
if let _ = dataFields[JsonKey.actionIdentifier.jsonKey] { // i.e., if action is not dismiss
- tracker.trackPushOpen(userInfo, dataFields: dataFields)
+ tracker?.trackPushOpen(userInfo, dataFields: dataFields)
}
// Execute the action
@@ -312,7 +314,7 @@ struct IterableAppIntegrationInternal {
// Track push open
let dataFields = [JsonKey.actionIdentifier.jsonKey: JsonValue.ActionIdentifier.pushOpenDefault]
- tracker.trackPushOpen(userInfo, dataFields: dataFields)
+ tracker?.trackPushOpen(userInfo, dataFields: dataFields)
guard let itbl = IterableAppIntegrationInternal.itblValue(fromUserInfo: userInfo) else {
return
@@ -331,7 +333,7 @@ struct IterableAppIntegrationInternal {
}
private func alreadyTracked(userInfo: [AnyHashable: Any]) -> Bool {
- guard let lastPushPayload = tracker.lastPushPayload else {
+ guard let lastPushPayload = tracker?.lastPushPayload else {
return false
}
diff --git a/swift-sdk/Internal/IterableCoreDataPersistence.swift b/swift-sdk/Internal/IterableCoreDataPersistence.swift
new file mode 100644
index 000000000..010c0be05
--- /dev/null
+++ b/swift-sdk/Internal/IterableCoreDataPersistence.swift
@@ -0,0 +1,197 @@
+//
+// Created by Tapash Majumder on 7/20/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import CoreData
+import Foundation
+
+enum PersistenceConst {
+ static let dataModelFileName = "IterableDataModel"
+ static let dataModelExtension = "momd"
+
+ enum Entity {
+ enum Task {
+ static let name = "IterableTaskManagedObject"
+
+ enum Column {
+ static let id = "id"
+ static let scheduledAt = "scheduledAt"
+ }
+ }
+ }
+}
+
+/// `Bundle.current` is used to find url path for core data model file.
+/// This is a temporary fix until we can use `Bundle.module` in IterableSDK.
+import class Foundation.Bundle
+private class BundleFinder {}
+extension Foundation.Bundle {
+ /// Returns the resource bundle associated with the current Swift module.
+ static var current: Bundle = {
+ // This is your `target.path` (located in your `Package.swift`) by replacing all the `/` by the `_`.
+ let bundleName = "IterableSDK_IterableSDK"
+ let candidates = [
+ // Bundle should be present here when the package is linked into an App.
+ Bundle.main.resourceURL,
+ // Bundle should be present here when the package is linked into a framework.
+ Bundle(for: BundleFinder.self).resourceURL,
+ // For command-line tools.
+ Bundle.main.bundleURL,
+ ]
+ for candidate in candidates {
+ let bundlePath = candidate?.appendingPathComponent(bundleName + ".bundle")
+ if let bundle = bundlePath.flatMap(Bundle.init(url:)) {
+ return bundle
+ }
+ }
+
+ return Bundle(for: BundleFinder.self)
+ }()
+}
+
+@available(iOS 10.0, *)
+class PersistentContainer: NSPersistentContainer {
+ static let shared: PersistentContainer? = {
+ guard let url = Bundle.current.url(forResource: PersistenceConst.dataModelFileName, withExtension: PersistenceConst.dataModelExtension) else {
+ ITBError("Could not find \(PersistenceConst.dataModelFileName) in bundle")
+ return nil
+ }
+ guard let managedObjectModel = NSManagedObjectModel(contentsOf: url) else {
+ ITBError("Could not initialize managed object model")
+ return nil
+ }
+
+ let container = PersistentContainer(name: PersistenceConst.dataModelFileName, managedObjectModel: managedObjectModel)
+ container.loadPersistentStores { desc, error in
+ if let error = error {
+ fatalError("Unresolved error \(error)")
+ }
+
+ ITBInfo("Successfully loaded persistent store at: \(desc.url?.description ?? "nil")")
+ }
+
+ container.viewContext.automaticallyMergesChangesFromParent = true
+ container.viewContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
+
+ return container
+ }()
+
+ override func newBackgroundContext() -> NSManagedObjectContext {
+ let backgroundContext = super.newBackgroundContext()
+ backgroundContext.automaticallyMergesChangesFromParent = true
+ backgroundContext.mergePolicy = NSMergePolicy(merge: NSMergePolicyType.mergeByPropertyStoreTrumpMergePolicyType)
+ return backgroundContext
+ }
+}
+
+@available(iOS 10.0, *)
+struct CoreDataPersistenceContextProvider: IterablePersistenceContextProvider {
+ init?(dateProvider: DateProviderProtocol = SystemDateProvider()) {
+ guard let persistentContainer = PersistentContainer.shared else {
+ return nil
+ }
+ self.persistentContainer = persistentContainer
+ self.dateProvider = dateProvider
+ }
+
+ func newBackgroundContext() -> IterablePersistenceContext {
+ return CoreDataPersistenceContext(managedObjectContext: persistentContainer.newBackgroundContext(), dateProvider: dateProvider)
+ }
+
+ func mainQueueContext() -> IterablePersistenceContext {
+ return CoreDataPersistenceContext(managedObjectContext: persistentContainer.viewContext, dateProvider: dateProvider)
+ }
+
+ private let persistentContainer: PersistentContainer
+ private let dateProvider: DateProviderProtocol
+}
+
+@available(iOS 10.0, *)
+struct CoreDataPersistenceContext: IterablePersistenceContext {
+ init(managedObjectContext: NSManagedObjectContext, dateProvider: DateProviderProtocol) {
+ self.managedObjectContext = managedObjectContext
+ self.dateProvider = dateProvider
+ }
+
+ func create(task: IterableTask) throws -> IterableTask {
+ guard let taskManagedObject = createTaskManagedObject() else {
+ throw IterableDBError.general("Could not create task managed object")
+ }
+
+ PersistenceHelper.copy(from: task, to: taskManagedObject)
+ taskManagedObject.createdAt = dateProvider.currentDate
+ return PersistenceHelper.task(from: taskManagedObject)
+ }
+
+ func update(task: IterableTask) throws -> IterableTask {
+ guard let taskManagedObject = try findTaskManagedObject(id: task.id) else {
+ throw IterableDBError.general("Could not find task to update")
+ }
+
+ PersistenceHelper.copy(from: task, to: taskManagedObject)
+ taskManagedObject.modifiedAt = dateProvider.currentDate
+ return PersistenceHelper.task(from: taskManagedObject)
+ }
+
+ func delete(task: IterableTask) throws {
+ try deleteTask(withId: task.id)
+ }
+
+ func nextTask() throws -> IterableTask? {
+ let taskManagedObjects: [IterableTaskManagedObject] = try CoreDataUtil.findSortedEntities(context: managedObjectContext,
+ entity: PersistenceConst.Entity.Task.name,
+ column: PersistenceConst.Entity.Task.Column.scheduledAt,
+ ascending: true,
+ limit: 1)
+ return taskManagedObjects.first.map(PersistenceHelper.task(from:))
+ }
+
+ func findTask(withId id: String) throws -> IterableTask? {
+ guard let taskManagedObject = try findTaskManagedObject(id: id) else {
+ return nil
+ }
+ return PersistenceHelper.task(from: taskManagedObject)
+ }
+
+ func deleteTask(withId id: String) throws {
+ guard let taskManagedObject = try findTaskManagedObject(id: id) else {
+ return
+ }
+ managedObjectContext.delete(taskManagedObject)
+ }
+
+ func findAllTasks() throws -> [IterableTask] {
+ let taskManagedObjects: [IterableTaskManagedObject] = try CoreDataUtil.findAll(context: managedObjectContext, entity: PersistenceConst.Entity.Task.name)
+
+ return taskManagedObjects.map(PersistenceHelper.task(from:))
+ }
+
+ func deleteAllTasks() throws {
+ let taskManagedObjects: [IterableTaskManagedObject] = try CoreDataUtil.findAll(context: managedObjectContext, entity: PersistenceConst.Entity.Task.name)
+ taskManagedObjects.forEach { managedObjectContext.delete($0) }
+ }
+
+ func save() throws {
+ try managedObjectContext.save()
+ }
+
+ func perform(_ block: @escaping () -> Void) {
+ managedObjectContext.perform(block)
+ }
+
+ func performAndWait(_ block: () -> Void) {
+ managedObjectContext.performAndWait(block)
+ }
+
+ private let managedObjectContext: NSManagedObjectContext
+ private let dateProvider: DateProviderProtocol
+
+ private func findTaskManagedObject(id: String) throws -> IterableTaskManagedObject? {
+ try CoreDataUtil.findEntitiyByColumn(context: managedObjectContext, entity: PersistenceConst.Entity.Task.name, columnName: PersistenceConst.Entity.Task.Column.id, columnValue: id)
+ }
+
+ private func createTaskManagedObject() -> IterableTaskManagedObject? {
+ CoreDataUtil.create(context: managedObjectContext, entity: PersistenceConst.Entity.Task.name)
+ }
+}
diff --git a/swift-sdk/Internal/IterableNotifications.swift b/swift-sdk/Internal/IterableNotifications.swift
new file mode 100644
index 000000000..e3a9b3744
--- /dev/null
+++ b/swift-sdk/Internal/IterableNotifications.swift
@@ -0,0 +1,84 @@
+//
+// Created by Tapash Majumder on 8/18/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+protocol NotificationCenterProtocol {
+ func addObserver(_ observer: Any, selector: Selector, name: Notification.Name?, object: Any?)
+ func removeObserver(_ observer: Any)
+ func post(name: Notification.Name, object: Any?, userInfo: [AnyHashable: Any]?)
+}
+
+extension NotificationCenter: NotificationCenterProtocol {}
+
+extension Notification.Name {
+ static let iterableTaskScheduled = Notification.Name(rawValue: "itbl_task_scheduled")
+ static let iterableTaskFinishedWithSuccess = Notification.Name(rawValue: "itbl_task_finished_with_success")
+ static let iterableTaskFinishedWithRetry = Notification.Name(rawValue: "itbl_task_finished_with_retry")
+ static let iterableTaskFinishedWithNoRetry = Notification.Name(rawValue: "itbl_task_finished_with_no_retry")
+ static let iterableNetworkOffline = Notification.Name(rawValue: "itbl_network_offline")
+ static let iterableNetworkOnline = Notification.Name(rawValue: "itbl_network_online")
+}
+
+struct TaskSendRequestValue {
+ let taskId: String
+ let sendRequestValue: SendRequestValue
+}
+
+struct TaskSendRequestError {
+ let taskId: String
+ let sendRequestError: SendRequestError
+}
+
+struct IterableNotificationUtil {
+ static func sendRequestValueToUserInfo(_ sendRequestValue: SendRequestValue, taskId: String) -> [AnyHashable: Any] {
+ var userInfo = [AnyHashable: Any]()
+ userInfo[Key.taskId] = taskId
+ userInfo[Key.sendRequestValue] = sendRequestValue
+ return userInfo
+ }
+
+ static func sendRequestErrorToUserInfo(_ sendRequestError: SendRequestError, taskId: String) -> [AnyHashable: Any] {
+ var userInfo = [AnyHashable: Any]()
+ userInfo[Key.taskId] = taskId
+ userInfo[Key.sendRequestError] = sendRequestError
+ return userInfo
+ }
+
+ static func notificationToTaskSendRequestValue(_ notification: Notification) -> TaskSendRequestValue? {
+ guard let userInfo = notification.userInfo else {
+ return nil
+ }
+ guard let taskId = userInfo[Key.taskId] as? String else {
+ return nil
+ }
+ guard let sendRequestValue = userInfo[Key.sendRequestValue] as? SendRequestValue else {
+ return nil
+ }
+
+ return TaskSendRequestValue(taskId: taskId, sendRequestValue: sendRequestValue)
+ }
+
+ static func notificationToTaskSendRequestError(_ notification: Notification) -> TaskSendRequestError? {
+ guard let userInfo = notification.userInfo else {
+ return nil
+ }
+ guard let taskId = userInfo[Key.taskId] as? String else {
+ return nil
+ }
+ guard let sendRequestError = userInfo[Key.sendRequestError] as? SendRequestError else {
+ return nil
+ }
+
+ return TaskSendRequestError(taskId: taskId, sendRequestError: sendRequestError)
+ }
+
+ private enum Key {
+ static let taskId = "taskId"
+ static let sendRequestValue = "sendRequestValue"
+ static let sendRequestError = "sendRequestError"
+ }
+
+}
diff --git a/swift-sdk/Internal/IterablePersistence.swift b/swift-sdk/Internal/IterablePersistence.swift
new file mode 100644
index 000000000..df46d3f44
--- /dev/null
+++ b/swift-sdk/Internal/IterablePersistence.swift
@@ -0,0 +1,52 @@
+//
+// Created by Tapash Majumder on 7/20/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+// This defines persistence contracts for Iterable.
+// This should not be dependent on Coredata
+
+import Foundation
+
+enum IterableDBError: Error {
+ case general(String)
+}
+
+extension IterableDBError: LocalizedError {
+ var errorDescription: String? {
+ switch self {
+ case let .general(description):
+ return description
+ }
+ }
+}
+
+protocol IterablePersistenceContext {
+ @discardableResult
+ func create(task: IterableTask) throws -> IterableTask
+
+ @discardableResult
+ func update(task: IterableTask) throws -> IterableTask
+
+ func delete(task: IterableTask) throws
+
+ func findTask(withId id: String) throws -> IterableTask?
+
+ func deleteTask(withId id: String) throws
+
+ func nextTask() throws -> IterableTask?
+
+ func findAllTasks() throws -> [IterableTask]
+
+ func deleteAllTasks() throws
+
+ func save() throws
+
+ func perform(_ block: @escaping () -> Void)
+
+ func performAndWait(_ block: () -> Void)
+}
+
+protocol IterablePersistenceContextProvider {
+ func newBackgroundContext() -> IterablePersistenceContext
+ func mainQueueContext() -> IterablePersistenceContext
+}
diff --git a/swift-sdk/Internal/IterableRequest.swift b/swift-sdk/Internal/IterableRequest.swift
new file mode 100644
index 000000000..b2de3c2a5
--- /dev/null
+++ b/swift-sdk/Internal/IterableRequest.swift
@@ -0,0 +1,95 @@
+//
+// Created by Tapash Majumder on 7/29/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+// These are Iterable specific Request items.
+// They don't have Api endpoint and request endpoint defined yet.
+enum IterableRequest {
+ case get(GetRequest)
+ case post(PostRequest)
+}
+
+extension IterableRequest: Codable {
+ enum CodingKeys: String, CodingKey {
+ case type
+ case value
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let type = try container.decode(String.self, forKey: .type)
+ switch type {
+ case IterableRequest.requestTypeGet:
+ let request = try container.decode(GetRequest.self, forKey: .value)
+ self = .get(request)
+ case IterableRequest.requestTypePost:
+ let request = try container.decode(PostRequest.self, forKey: .value)
+ self = .post(request)
+ default:
+ throw IterableError.general(description: "Unknown request type: \(type)")
+ }
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ switch self {
+ case let .get(request):
+ try container.encode(IterableRequest.requestTypeGet, forKey: .type)
+ try container.encode(request, forKey: .value)
+ case let .post(request):
+ try container.encode(IterableRequest.requestTypePost, forKey: .type)
+ try container.encode(request, forKey: .value)
+ }
+ }
+
+ private static let requestTypeGet = "get"
+ private static let requestTypePost = "post"
+}
+
+struct GetRequest: Codable {
+ let path: String
+ let args: [String: String]?
+}
+
+struct PostRequest {
+ let path: String
+ let args: [String: String]?
+ let body: [AnyHashable: Any]?
+}
+
+extension PostRequest: Codable {
+ enum CodingKeys: String, CodingKey {
+ case path
+ case args
+ case body
+ }
+
+ init(from decoder: Decoder) throws {
+ let container = try decoder.container(keyedBy: CodingKeys.self)
+ let path = try container.decode(String.self, forKey: .path)
+ let args = try container.decode([String: String]?.self, forKey: .args)
+ let body: [AnyHashable: Any]?
+ if let bodyData = try container.decode(Data?.self, forKey: .body) {
+ body = try JSONSerialization.jsonObject(with: bodyData, options: []) as? [AnyHashable: Any]
+ } else {
+ body = nil
+ }
+ self.path = path
+ self.args = args
+ self.body = body
+ }
+
+ func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(path, forKey: .path)
+ try container.encode(args, forKey: .args)
+ var bodyData: Data?
+ if let body = self.body, JSONSerialization.isValidJSONObject(body) {
+ bodyData = try JSONSerialization.data(withJSONObject: body, options: [])
+ }
+ try container.encode(bodyData, forKey: .body)
+ }
+}
diff --git a/swift-sdk/Internal/IterableTask.swift b/swift-sdk/Internal/IterableTask.swift
new file mode 100644
index 000000000..1c15cd192
--- /dev/null
+++ b/swift-sdk/Internal/IterableTask.swift
@@ -0,0 +1,82 @@
+//
+// Created by Tapash Majumder on 7/20/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+struct IterableTask {
+ static let currentVersion = 1
+
+ let id: String
+ let name: String?
+ let version: Int
+ let createdAt: Date?
+ let modifiedAt: Date?
+ let type: IterableTaskType
+ let attempts: Int
+ let lastAttemptedAt: Date?
+ let processing: Bool
+ let scheduledAt: Date
+ let data: Data?
+ let failed: Bool
+ let blocking: Bool
+ let requestedAt: Date
+ let taskFailureData: Data?
+
+ init(id: String,
+ name: String? = nil,
+ version: Int = IterableTask.currentVersion,
+ createdAt: Date? = nil,
+ modifiedAt: Date? = nil,
+ type: IterableTaskType,
+ attempts: Int = 0,
+ lastAttemptedAt: Date? = nil,
+ processing: Bool = false,
+ scheduledAt: Date,
+ data: Data? = nil,
+ failed: Bool = false,
+ blocking: Bool = true,
+ requestedAt: Date,
+ taskFailureData: Data? = nil) {
+ self.id = id
+ self.name = name
+ self.version = version
+ self.createdAt = createdAt
+ self.modifiedAt = modifiedAt
+ self.type = type
+ self.attempts = attempts
+ self.lastAttemptedAt = lastAttemptedAt
+ self.processing = processing
+ self.scheduledAt = scheduledAt
+ self.data = data
+ self.failed = failed
+ self.blocking = blocking
+ self.requestedAt = requestedAt
+ self.taskFailureData = taskFailureData
+ }
+
+ func updated(attempts: Int? = nil,
+ lastAttemptedAt: Date? = nil,
+ processing: Bool? = nil,
+ scheduledAt: Date? = nil,
+ data: Data? = nil,
+ failed: Bool? = nil,
+ taskFailureData: Data? = nil) -> IterableTask {
+ IterableTask(id: id,
+ name: name,
+ version: version,
+ createdAt: createdAt,
+ modifiedAt: modifiedAt,
+ type: type,
+ attempts: attempts ?? self.attempts,
+ lastAttemptedAt: lastAttemptedAt ?? self.lastAttemptedAt,
+ processing: processing ?? self.processing,
+ scheduledAt: scheduledAt ?? self.scheduledAt,
+ data: data ?? self.data,
+ failed: failed ?? false,
+ blocking: blocking,
+ requestedAt: requestedAt,
+ taskFailureData: taskFailureData ?? self.taskFailureData)
+ }
+}
diff --git a/swift-sdk/Internal/IterableTaskError.swift b/swift-sdk/Internal/IterableTaskError.swift
new file mode 100644
index 000000000..9c73bf2fc
--- /dev/null
+++ b/swift-sdk/Internal/IterableTaskError.swift
@@ -0,0 +1,23 @@
+//
+// Created by Tapash Majumder on 7/30/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+enum IterableTaskError: Error {
+ case general(String?)
+
+ static func createErroredFuture(reason: String? = nil) -> Future {
+ Promise(error: IterableTaskError.general(reason))
+ }
+}
+
+extension IterableTaskError: LocalizedError {
+ var errorDescription: String? {
+ switch self {
+ case let .general(description):
+ return description ?? "general error"
+ }
+ }
+}
diff --git a/swift-sdk/Internal/IterableTaskManagedObject.swift b/swift-sdk/Internal/IterableTaskManagedObject.swift
new file mode 100644
index 000000000..86eb0dd55
--- /dev/null
+++ b/swift-sdk/Internal/IterableTaskManagedObject.swift
@@ -0,0 +1,34 @@
+//
+// Created by Tapash Majumder on 7/22/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+/// This class mirrors what is automatically generated by Xcode.
+
+import CoreData
+import Foundation
+
+@objc(IterableTaskManagedObject)
+public class IterableTaskManagedObject: NSManagedObject {}
+
+extension IterableTaskManagedObject {
+ @nonobjc public class func fetchRequest() -> NSFetchRequest {
+ NSFetchRequest(entityName: PersistenceConst.Entity.Task.name)
+ }
+
+ @NSManaged public var attempts: Int64
+ @NSManaged public var createdAt: Date?
+ @NSManaged public var modifiedAt: Date?
+ @NSManaged public var data: Data?
+ @NSManaged public var id: String
+ @NSManaged public var name: String?
+ @NSManaged public var lastAttemptedAt: Date?
+ @NSManaged public var processing: Bool
+ @NSManaged public var type: String
+ @NSManaged public var scheduledAt: Date
+ @NSManaged public var failed: Bool
+ @NSManaged public var blocking: Bool
+ @NSManaged public var requestedAt: Date
+ @NSManaged public var taskFailureData: Data?
+ @NSManaged public var version: Int64
+}
diff --git a/swift-sdk/Internal/IterableTaskProcessor.swift b/swift-sdk/Internal/IterableTaskProcessor.swift
new file mode 100644
index 000000000..88b6ced2c
--- /dev/null
+++ b/swift-sdk/Internal/IterableTaskProcessor.swift
@@ -0,0 +1,18 @@
+//
+// Created by Tapash Majumder on 7/30/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+enum IterableTaskType: String {
+ case apiCall
+}
+
+struct IterableTaskContext {
+ let blocking: Bool
+}
+
+protocol IterableTaskProcessor {
+ func process(task: IterableTask) throws -> Future
+}
diff --git a/swift-sdk/Internal/IterableTaskResult.swift b/swift-sdk/Internal/IterableTaskResult.swift
new file mode 100644
index 000000000..a5b3dcea3
--- /dev/null
+++ b/swift-sdk/Internal/IterableTaskResult.swift
@@ -0,0 +1,20 @@
+//
+// Created by Tapash Majumder on 7/30/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+enum IterableTaskResult {
+ case success(detail: TaskSuccessDetail?)
+ case failureWithRetry(retryAfter: TimeInterval?, detail: TaskFailureDetail?)
+ case failureWithNoRetry(detail: TaskFailureDetail?)
+}
+
+protocol TaskSuccessDetail {}
+
+extension SendRequestValue: TaskSuccessDetail {}
+
+protocol TaskFailureDetail {}
+
+extension SendRequestError: TaskFailureDetail {}
diff --git a/swift-sdk/Internal/IterableTaskRunner.swift b/swift-sdk/Internal/IterableTaskRunner.swift
new file mode 100644
index 000000000..85901b087
--- /dev/null
+++ b/swift-sdk/Internal/IterableTaskRunner.swift
@@ -0,0 +1,264 @@
+//
+// Created by Tapash Majumder on 8/18/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+import UIKit
+
+
+@available(iOS 10.0, *)
+class IterableTaskRunner: NSObject {
+ init(networkSession: NetworkSessionProtocol = URLSession(configuration: .default),
+ persistenceContextProvider: IterablePersistenceContextProvider,
+ notificationCenter: NotificationCenterProtocol = NotificationCenter.default,
+ timeInterval: TimeInterval = 1.0 * 60,
+ connectivityManager: NetworkConnectivityManager = NetworkConnectivityManager()) {
+ ITBInfo()
+ self.networkSession = networkSession
+ self.persistenceContextProvider = persistenceContextProvider
+ self.notificationCenter = notificationCenter
+ self.timeInterval = timeInterval
+ self.connectivityManager = connectivityManager
+
+ super.init()
+
+ self.notificationCenter.addObserver(self,
+ selector: #selector(onTaskScheduled(notification:)),
+ name: .iterableTaskScheduled,
+ object: nil)
+ self.notificationCenter.addObserver(self,
+ selector: #selector(onAppWillEnterForeground(notification:)),
+ name: UIApplication.willEnterForegroundNotification,
+ object: nil)
+ self.notificationCenter.addObserver(self,
+ selector: #selector(onAppDidEnterBackground(notification:)),
+ name: UIApplication.didEnterBackgroundNotification,
+ object: nil)
+ self.connectivityManager.connectivityChangedCallback = { [weak self] in self?.onConnectivityChanged(connected: $0) }
+ }
+
+ func start() {
+ ITBInfo()
+ paused = false
+ run()
+ connectivityManager.start()
+ }
+
+ func stop() {
+ ITBInfo()
+ paused = true
+ timer?.invalidate()
+ timer = nil
+ connectivityManager.stop()
+ }
+
+ @objc
+ private func onTaskScheduled(notification: Notification) {
+ ITBInfo()
+ if !running && !paused {
+ runNow()
+ }
+ }
+
+ @objc
+ private func onAppWillEnterForeground(notification _: Notification) {
+ ITBInfo()
+ start()
+ }
+
+ @objc
+ private func onAppDidEnterBackground(notification _: Notification) {
+ ITBInfo()
+ stop()
+ }
+
+ private func runNow() {
+ timer?.invalidate()
+ timer = nil
+ run()
+ }
+
+ private func onConnectivityChanged(connected: Bool) {
+ ITBInfo()
+ if connected {
+ if paused {
+ paused = false
+ if !running {
+ runNow()
+ }
+ }
+ } else {
+ if !paused {
+ paused = true
+ }
+ }
+ }
+
+ private func run() {
+ ITBInfo()
+ guard !paused else {
+ ITBInfo("Cannot run when paused")
+ return
+ }
+ guard !running else {
+ ITBInfo("Already running")
+ return
+ }
+
+ persistenceContext.perform {
+ self.processTasks().onSuccess { _ in
+ ITBInfo("Done processing tasks")
+ self.running = false
+ self.scheduleNext()
+ }
+ }
+ }
+
+ private func scheduleNext() {
+ ITBInfo()
+ guard !paused else {
+ ITBInfo("Paused")
+ return
+ }
+
+ DispatchQueue.global().async { [weak self] in
+ ITBInfo("Scheduling timer")
+ guard let timeInterval = self?.timeInterval else {
+ return
+ }
+
+ let timer = Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] _ in
+ self?.run()
+ }
+ self?.timer = timer
+ RunLoop.current.add(timer, forMode: .default)
+ RunLoop.current.run()
+ }
+ }
+
+ @discardableResult
+ private func processTasks() -> Future {
+ ITBInfo()
+ running = true
+
+ /// This is a recursive function.
+ /// Check whether we were stopped in the middle of running tasks
+ guard !paused else {
+ ITBInfo("Tasks paused before finishing processTasks()")
+ return Promise(value: ())
+ }
+
+ if let task = try? persistenceContext.nextTask() {
+ return execute(task: task).flatMap { executionResult in
+ switch executionResult {
+ case .success, .failure, .error:
+ self.deleteTask(task: task)
+ return self.processTasks()
+ case .processing, .retry:
+ return Promise(value: ())
+ }
+ }
+ } else {
+ ITBInfo("No tasks to execute")
+ return Promise(value: ())
+ }
+ }
+
+ @discardableResult
+ private func execute(task: IterableTask) -> Future {
+ ITBInfo("Executing taskId: \(task.id), name: \(task.name ?? "nil")")
+ guard task.processing == false else {
+ return Promise(value: .processing)
+ }
+
+ switch task.type {
+ case .apiCall:
+ let processor = IterableAPICallTaskProcessor(networkSession: networkSession)
+ return processAPICallTask(processor: processor, task: task)
+ }
+ }
+
+ private func processAPICallTask(processor: IterableAPICallTaskProcessor,
+ task: IterableTask) -> Future {
+ ITBInfo()
+ let result = Promise()
+ let processor = IterableAPICallTaskProcessor(networkSession: networkSession)
+ do {
+ try processor.process(task: task).onSuccess { taskResult in
+ switch taskResult {
+ case let .success(detail: detail):
+ ITBInfo("task: \(task.id) succeeded")
+ if let successDetail = detail as? SendRequestValue {
+ let userInfo = IterableNotificationUtil.sendRequestValueToUserInfo(successDetail, taskId: task.id)
+ self.notificationCenter.post(name: .iterableTaskFinishedWithSuccess,
+ object: self,
+ userInfo: userInfo)
+ }
+ result.resolve(with: .success)
+ case let .failureWithNoRetry(detail: detail):
+ ITBInfo("task: \(task.id) failed with no retry.")
+ if let failureDetail = detail as? SendRequestError {
+ let userInfo = IterableNotificationUtil.sendRequestErrorToUserInfo(failureDetail, taskId: task.id)
+ self.notificationCenter.post(name: .iterableTaskFinishedWithNoRetry,
+ object: self,
+ userInfo: userInfo)
+ }
+ result.resolve(with: .failure)
+ case let .failureWithRetry(_, detail: detail):
+ ITBInfo("task: \(task.id) processed with retry")
+ if let failureDetail = detail as? SendRequestError {
+ let userInfo = IterableNotificationUtil.sendRequestErrorToUserInfo(failureDetail, taskId: task.id)
+ self.notificationCenter.post(name: .iterableTaskFinishedWithRetry,
+ object: self,
+ userInfo: userInfo)
+ }
+ result.resolve(with: .retry)
+ }
+ }.onError { error in
+ ITBError("task processing error: \(error.localizedDescription)")
+ result.resolve(with: .failure)
+ }
+ } catch let error {
+ ITBError("Error proessing task: \(task.id), message: \(error.localizedDescription)")
+ result.resolve(with: .error)
+ }
+ return result
+ }
+
+ deinit {
+ ITBInfo()
+ stop()
+ notificationCenter.removeObserver(self)
+ }
+
+ private func deleteTask(task: IterableTask) {
+ do {
+ try persistenceContext.delete(task: task)
+ try persistenceContext.save()
+ } catch let error {
+ ITBError(error.localizedDescription)
+ }
+ }
+
+ private enum TaskExecutionResult {
+ case processing
+ case success
+ case failure
+ case retry
+ case error
+ }
+
+ private var paused = false
+ private let networkSession: NetworkSessionProtocol
+ private let persistenceContextProvider: IterablePersistenceContextProvider
+ private let notificationCenter: NotificationCenterProtocol
+ private let timeInterval: TimeInterval
+ private let connectivityManager: NetworkConnectivityManager
+ private weak var timer: Timer?
+ private var running = false
+
+ private lazy var persistenceContext: IterablePersistenceContext = {
+ return persistenceContextProvider.newBackgroundContext()
+ }()
+}
diff --git a/swift-sdk/Internal/IterableTaskScheduler.swift b/swift-sdk/Internal/IterableTaskScheduler.swift
new file mode 100644
index 000000000..74667a5e2
--- /dev/null
+++ b/swift-sdk/Internal/IterableTaskScheduler.swift
@@ -0,0 +1,48 @@
+//
+// Created by Tapash Majumder on 8/18/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+@available(iOS 10.0, *)
+class IterableTaskScheduler {
+ init(persistenceContextProvider: IterablePersistenceContextProvider,
+ notificationCenter: NotificationCenterProtocol = NotificationCenter.default,
+ dateProvider: DateProviderProtocol = SystemDateProvider()) {
+ self.persistenceContextProvider = persistenceContextProvider
+ self.notificationCenter = notificationCenter
+ self.dateProvider = dateProvider
+ }
+
+ func schedule(apiCallRequest: IterableAPICallRequest,
+ context: IterableTaskContext = IterableTaskContext(blocking: true),
+ scheduledAt: Date? = nil) -> Result {
+ ITBInfo()
+ let taskId = IterableUtil.generateUUID()
+ do {
+ let data = try JSONEncoder().encode(apiCallRequest)
+
+ try persistenceContext.create(task: IterableTask(id: taskId,
+ name: apiCallRequest.getPath(),
+ type: .apiCall,
+ scheduledAt: scheduledAt ?? dateProvider.currentDate,
+ data: data,
+ requestedAt: dateProvider.currentDate))
+ try persistenceContext.save()
+
+ notificationCenter.post(name: .iterableTaskScheduled, object: self, userInfo: nil)
+ } catch let error {
+ return Result.failure(IterableTaskError.general("schedule taskId: \(taskId) failed with error: \(error.localizedDescription)"))
+ }
+ return Result.success(taskId)
+ }
+
+ private let persistenceContextProvider: IterablePersistenceContextProvider
+ private let notificationCenter: NotificationCenterProtocol
+ private let dateProvider: DateProviderProtocol
+
+ private lazy var persistenceContext: IterablePersistenceContext = {
+ return persistenceContextProvider.newBackgroundContext()
+ }()
+}
diff --git a/swift-sdk/Internal/NetworkConnectivityChecker.swift b/swift-sdk/Internal/NetworkConnectivityChecker.swift
new file mode 100644
index 000000000..f3ac21059
--- /dev/null
+++ b/swift-sdk/Internal/NetworkConnectivityChecker.swift
@@ -0,0 +1,113 @@
+//
+// Created by Tapash Majumder on 9/2/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+struct NetworkConnectivityChecker {
+ /// first argument is successfulChecks,
+ /// second argument is totalChecks
+ /// Should return true if threshold is met
+ typealias IsThresholdMet = (Int, Int) -> Bool
+
+ init(networkSession: NetworkSessionProtocol? = nil,
+ isThresholdMet: IsThresholdMet? = nil) {
+ self.networkSession = networkSession ?? URLSession(configuration: Self.urlSessionConfiguration)
+ self.isThresholdMet = isThresholdMet ?? Self.defaultIsThresholdMet
+ }
+
+ @discardableResult
+ func checkConnectivity() -> Future {
+ let result = Promise()
+ let dispatchGroup = DispatchGroup()
+ var tasks: [DataTaskProtocol] = []
+ var successfulChecks: Int = 0, failedChecks: Int = 0
+ let totalChecks = Self.urlsToCheck.count
+
+ let completionHandlerForUrl: (URL) -> NetworkSessionProtocol.CompletionHandler = { url in
+ return { data, response, error in
+ let success = self.checkSucceeded(for: url, data: data, response: response, error: error)
+ success ? (successfulChecks += 1) : (failedChecks += 1)
+ dispatchGroup.leave()
+
+ // If enough tasks have finished successfully abort early
+ self.cancelCheck(
+ pendingTasks: tasks,
+ successfulChecks: successfulChecks,
+ totalChecks: totalChecks
+ )
+ }
+ }
+
+ tasks = Self.urlsToCheck.map {
+ return networkSession.createDataTask(with: $0, completionHandler: completionHandlerForUrl($0))
+ }
+
+ tasks.forEach { task in
+ dispatchGroup.enter()
+ tasksQueue.async {
+ task.resume()
+ }
+ }
+
+ dispatchGroup.notify(queue: updateQueue) { [self] in
+ let online = self.isThresholdMet(successfulChecks, totalChecks)
+ result.resolve(with: online)
+ }
+
+ return result
+ }
+
+ private func checkSucceeded(for url: URL, data: Data?, response: URLResponse?, error: Error?) -> Bool {
+ if let error = error {
+ ITBError("error checking status, error: \(error.localizedDescription)")
+ return false
+ }
+ guard let response = response as? HTTPURLResponse else {
+ ITBError("No response")
+ return false
+ }
+
+ return (200..<300).contains(response.statusCode)
+ }
+
+ private func cancelCheck(
+ pendingTasks: [DataTaskProtocol],
+ successfulChecks: Int,
+ totalChecks: Int) {
+ let connected = isThresholdMet(successfulChecks, totalChecks)
+ guard connected else { return }
+
+ cancelPendingTasks(pendingTasks)
+ }
+
+ private func cancelPendingTasks(_ tasks: [DataTaskProtocol]) {
+ for task in tasks where [.running, .suspended].contains(task.state) {
+ task.cancel()
+ }
+ }
+
+ private static let urlSessionConfiguration: URLSessionConfiguration = {
+ let sessionConfiguration = URLSessionConfiguration.default
+ sessionConfiguration.requestCachePolicy = .reloadIgnoringCacheData
+ sessionConfiguration.timeoutIntervalForRequest = 5.0
+ sessionConfiguration.timeoutIntervalForResource = 5.0
+ return sessionConfiguration
+ }()
+
+ private static let urlsToCheck: [URL] = [
+ "https://www.apple.com/library/test/success.html",
+ "https://www.google.com"
+ ].compactMap { URL(string: $0) }
+
+ private static let defaultIsThresholdMet: (Int, Int) -> Bool = { value, outOf in
+ (Double(value) / Double(outOf)) * 100.0 >= 50.0
+ }
+
+ private var networkSession: NetworkSessionProtocol
+ private var isThresholdMet: IsThresholdMet
+
+ private let tasksQueue = DispatchQueue(label: "tasksQueue")
+ private var updateQueue = DispatchQueue(label: "updateQueue")
+}
diff --git a/swift-sdk/Internal/NetworkConnectivityManager.swift b/swift-sdk/Internal/NetworkConnectivityManager.swift
new file mode 100644
index 000000000..3443a55fc
--- /dev/null
+++ b/swift-sdk/Internal/NetworkConnectivityManager.swift
@@ -0,0 +1,126 @@
+//
+// Created by Tapash Majumder on 9/8/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+class NetworkConnectivityManager: NSObject {
+ init(networkMonitor: NetworkMonitorProtocol? = nil,
+ connectivityChecker: NetworkConnectivityChecker = NetworkConnectivityChecker(),
+ notificationCenter: NotificationCenterProtocol = NotificationCenter.default,
+ offlineModePollingInterval: TimeInterval? = nil,
+ onlineModePollingInterval: TimeInterval? = nil) {
+ ITBInfo()
+ self.networkMonitor = networkMonitor ?? Self.createNetworkMonitor()
+ self.connectivityChecker = connectivityChecker
+ self.notificationCenter = notificationCenter
+ self.offlineModePollingInterval = offlineModePollingInterval ?? Self.defaultOfflineModePollingInterval
+ self.onlineModePollingInterval = onlineModePollingInterval ?? Self.defaultOnlineModePollingInterval
+ super.init()
+ notificationCenter.addObserver(self,
+ selector: #selector(onNetworkOnline(notification:)),
+ name: .iterableNetworkOnline,
+ object: nil)
+ notificationCenter.addObserver(self,
+ selector: #selector(onNetworkOffline(notification:)),
+ name: .iterableNetworkOffline,
+ object: nil)
+ }
+
+ deinit {
+ ITBInfo()
+ notificationCenter.removeObserver(self)
+ stop()
+ }
+
+ var isOnline: Bool {
+ online
+ }
+
+ var connectivityChangedCallback: ((Bool) -> Void)?
+
+ func start() {
+ ITBInfo()
+ networkMonitor.statusUpdatedCallback = { [weak self] in self?.updateStatus() }
+ networkMonitor.start()
+ startTimer()
+ }
+
+ func stop() {
+ ITBInfo()
+ networkMonitor.stop()
+ stopTimer()
+ }
+
+ private func startTimer() {
+ ITBInfo()
+ let interval = online ? onlineModePollingInterval : offlineModePollingInterval
+ if #available(iOS 10.0, *) {
+ timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true, block: { [weak self] _ in
+ ITBInfo("timer called")
+ self?.updateStatus()
+ })
+ }
+ }
+
+ private func stopTimer() {
+ ITBInfo()
+ timer?.invalidate()
+ timer = nil
+ }
+
+ private func resetTimer() {
+ ITBInfo()
+ stopTimer()
+ startTimer()
+ }
+
+ private func updateStatus() {
+ ITBInfo()
+ connectivityChecker.checkConnectivity().onSuccess { connected in
+ self.online = connected
+ }
+ }
+
+ @objc
+ private func onNetworkOnline(notification _: Notification) {
+ ITBInfo()
+ online = true
+ }
+
+ @objc
+ private func onNetworkOffline(notification _: Notification) {
+ ITBInfo()
+ online = false
+ }
+
+ private static func createNetworkMonitor() -> NetworkMonitorProtocol {
+ if #available(iOS 12, *) {
+ return NetworkMonitor()
+ } else {
+ return PollingNetworkMonitor()
+ }
+ }
+
+ private let notificationCenter: NotificationCenterProtocol
+ private var networkMonitor: NetworkMonitorProtocol
+ private let connectivityChecker: NetworkConnectivityChecker
+ private var timer: Timer?
+ private let offlineModePollingInterval: TimeInterval
+ private let onlineModePollingInterval: TimeInterval
+ private static let defaultOfflineModePollingInterval: TimeInterval = 1 * 60.0
+ private static let defaultOnlineModePollingInterval: TimeInterval = 10 * 60
+
+ private var online = true {
+ didSet {
+ ITBInfo("online: \(online)")
+ if online != oldValue {
+ ITBInfo("connectivity changed")
+ connectivityChangedCallback?(online)
+ resetTimer()
+ }
+ }
+ }
+}
+
diff --git a/swift-sdk/Internal/NetworkHelper.swift b/swift-sdk/Internal/NetworkHelper.swift
index 2751531a0..c69bacaee 100644
--- a/swift-sdk/Internal/NetworkHelper.swift
+++ b/swift-sdk/Internal/NetworkHelper.swift
@@ -33,18 +33,60 @@ struct SendRequestError: Error {
static func from(error: Error) -> SendRequestError {
SendRequestError(reason: error.localizedDescription)
}
+
+ static func from(networkError: NetworkError,
+ reason: String? = nil,
+ iterableCode: String? = nil) -> SendRequestError {
+ SendRequestError(reason: reason ?? networkError.reason,
+ data: networkError.data,
+ httpStatusCode: networkError.httpStatusCode,
+ iterableCode: iterableCode,
+ originalError: networkError.originalError)
+ }
}
extension SendRequestError: LocalizedError {
- var localizedDescription: String {
- reason ?? ""
+ var errorDescription: String? {
+ reason
+ }
+}
+
+struct NetworkError: Error {
+ let reason: String?
+ let data: Data?
+ let httpStatusCode: Int?
+ let originalError: Error?
+
+ init(reason: String? = nil,
+ data: Data? = nil,
+ httpStatusCode: Int? = nil,
+ originalError: Error? = nil) {
+ self.reason = reason
+ self.data = data
+ self.httpStatusCode = httpStatusCode
+ self.originalError = originalError
+ }
+}
+
+extension NetworkError: LocalizedError {
+ var errorDescription: String? {
+ reason
}
}
+protocol DataTaskProtocol {
+ var state: URLSessionDataTask.State { get }
+ func resume()
+ func cancel()
+}
+
+extension URLSessionDataTask: DataTaskProtocol {}
+
protocol NetworkSessionProtocol {
typealias CompletionHandler = (Data?, URLResponse?, Error?) -> Void
func makeRequest(_ request: URLRequest, completionHandler: @escaping CompletionHandler)
func makeDataRequest(with url: URL, completionHandler: @escaping CompletionHandler)
+ func createDataTask(with url: URL, completionHandler: @escaping CompletionHandler) -> DataTaskProtocol
}
extension URLSession: NetworkSessionProtocol {
@@ -63,6 +105,10 @@ extension URLSession: NetworkSessionProtocol {
task.resume()
}
+
+ func createDataTask(with url: URL, completionHandler: @escaping CompletionHandler) -> DataTaskProtocol {
+ dataTask(with: url, completionHandler: completionHandler)
+ }
}
struct NetworkHelper {
@@ -87,8 +133,9 @@ struct NetworkHelper {
return promise
}
- static func sendRequest(_ request: URLRequest,
- usingSession networkSession: NetworkSessionProtocol) -> Future {
+ static func sendRequest(_ request: URLRequest,
+ converter: @escaping (Data) throws -> T?,
+ usingSession networkSession: NetworkSessionProtocol) -> Future {
#if NETWORK_DEBUG
let requestId = IterableUtil.generateUUID()
print()
@@ -109,10 +156,13 @@ struct NetworkHelper {
print()
#endif
- let promise = Promise()
+ let promise = Promise()
networkSession.makeRequest(request) { data, response, error in
- let result = createResultFromNetworkResponse(data: data, response: response, error: error)
+ let result = createResultFromNetworkResponse(data: data,
+ converter: converter,
+ response: response,
+ error: error)
switch result {
case let .success(value):
@@ -131,83 +181,111 @@ struct NetworkHelper {
return promise
}
- static func createResultFromNetworkResponse(data: Data?,
- response: URLResponse?,
- error: Error?) -> Result {
+ static func sendRequest(_ request: URLRequest,
+ usingSession networkSession: NetworkSessionProtocol) -> Future {
+ let converter: (Data) throws -> SendRequestValue? = { data in
+ try JSONSerialization.jsonObject(with: data, options: []) as? [AnyHashable: Any]
+ }
+
+ return sendRequest(request,
+ converter: converter,
+ usingSession: networkSession)
+ .mapFailure(convertNetworkErrorToSendRequestError(_:))
+ }
+
+ private static func createResultFromNetworkResponse(data: Data?,
+ converter: (Data) throws -> T?,
+ response: URLResponse?,
+ error: Error?) -> Result {
if let error = error {
- return .failure(SendRequestError(reason: "\(error.localizedDescription)", data: data, originalError: error))
+ return .failure(NetworkError(reason: "\(error.localizedDescription)", data: data, originalError: error))
}
guard let response = response as? HTTPURLResponse else {
- return .failure(SendRequestError(reason: "No response", data: nil))
+ return .failure(NetworkError(reason: "No response", data: nil))
}
- let responseCode = response.statusCode
+ let httpStatusCode = response.statusCode
- let json: Any?
- var jsonError: Error?
+ if httpStatusCode >= 500 {
+ return .failure(NetworkError(reason: "Internal Server Error", data: data, httpStatusCode: httpStatusCode))
+ } else if httpStatusCode >= 400 {
+ return .failure(NetworkError(reason: "Invalid Request", data: data, httpStatusCode: httpStatusCode))
+ } else if httpStatusCode == 200 {
+ if let data = data, data.count > 0 {
+ return convertData(data: data, converter: converter)
+ } else {
+ return .failure(NetworkError(reason: "No data received", data: data, httpStatusCode: httpStatusCode))
+ }
+ } else {
+ return .failure(NetworkError(reason: "Received non-200 response: \(httpStatusCode)", data: data, httpStatusCode: httpStatusCode))
+ }
+ }
+
+ private static func convertData(data: Data, converter: (Data) throws -> T?) -> Result {
+ do {
+ if let responseObj = try converter(data) {
+ return .success(responseObj)
+ } else {
+ return .failure(NetworkError(reason: "Wrong response type", data: data, httpStatusCode: 200))
+ }
+ } catch {
+ var reason = "Could not convert data, error: \(error.localizedDescription)"
+ if let stringValue = String(data: data, encoding: .utf8) {
+ reason = "Could not convert data: \(stringValue), error: \(error.localizedDescription)"
+ }
+ return .failure(NetworkError(reason: reason, data: data, httpStatusCode: 200))
+ }
+ }
+
+ private static func createDataResultFromNetworkResponse(data: Data?,
+ response _: URLResponse?,
+ error: Error?) -> Result {
+ if let error = error {
+ return .failure(SendRequestError(reason: "\(error.localizedDescription)"))
+ }
+
+ guard let data = data else {
+ return .failure(SendRequestError(reason: "No data"))
+ }
+
+ return .success(data)
+ }
+
+ private static func convertNetworkErrorToSendRequestError(_ networkError: NetworkError) -> SendRequestError {
+ guard let httpStatusCode = networkError.httpStatusCode else {
+ return SendRequestError.from(networkError: networkError)
+ }
- if let data = data, data.count > 0 {
+ let json: Any?
+ if let data = networkError.data, data.count > 0 {
do {
json = try JSONSerialization.jsonObject(with: data, options: [])
} catch {
- jsonError = error
json = nil
}
} else {
json = nil
}
- if responseCode == 401 {
+ if httpStatusCode == 401 {
var iterableCode: String? = nil
-
if let jsonDict = json as? [AnyHashable: Any] {
iterableCode = jsonDict[JsonKey.Response.iterableCode] as? String
}
- return .failure(SendRequestError(reason: "Invalid API Key", data: data, httpStatusCode: responseCode, iterableCode: iterableCode))
- } else if responseCode >= 400 {
+ return SendRequestError.from(networkError: networkError, reason: "Invalid API Key", iterableCode: iterableCode)
+ } else if httpStatusCode >= 400 {
var reason = "Invalid Request"
if let jsonDict = json as? [AnyHashable: Any], let msgFromDict = jsonDict["msg"] as? String {
reason = msgFromDict
- } else if responseCode >= 500 {
+ } else if httpStatusCode >= 500 {
reason = "Internal Server Error"
}
- return .failure(SendRequestError(reason: reason, data: data, httpStatusCode: responseCode))
- } else if responseCode == 200 {
- if let data = data, data.count > 0 {
- if let jsonError = jsonError {
- var reason = "Could not parse json, error: \(jsonError.localizedDescription)"
- if let stringValue = String(data: data, encoding: .utf8) {
- reason = "Could not parse json: \(stringValue), error: \(jsonError.localizedDescription)"
- }
-
- return .failure(SendRequestError(reason: reason, data: data, httpStatusCode: responseCode))
- } else if let json = json as? [AnyHashable: Any] {
- return .success(json)
- } else {
- return .failure(SendRequestError(reason: "Response is not a dictionary", data: data, httpStatusCode: responseCode))
- }
- } else {
- return .failure(SendRequestError(reason: "No data received", data: data, httpStatusCode: responseCode))
- }
- } else {
- return .failure(SendRequestError(reason: "Received non-200 response: \(responseCode)", data: data, httpStatusCode: responseCode))
- }
- }
-
- static func createDataResultFromNetworkResponse(data: Data?,
- response _: URLResponse?,
- error: Error?) -> Result {
- if let error = error {
- return .failure(SendRequestError(reason: "\(error.localizedDescription)"))
+ return SendRequestError.from(networkError: networkError, reason: reason)
}
- guard let data = data else {
- return .failure(SendRequestError(reason: "No data"))
- }
-
- return .success(data)
+ return SendRequestError.from(networkError: networkError)
}
}
diff --git a/swift-sdk/Internal/NetworkMonitor.swift b/swift-sdk/Internal/NetworkMonitor.swift
new file mode 100644
index 000000000..e0692deea
--- /dev/null
+++ b/swift-sdk/Internal/NetworkMonitor.swift
@@ -0,0 +1,90 @@
+//
+// Created by Tapash Majumder on 9/8/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+#if canImport(Network)
+import Network
+#endif
+
+/// Listens to network interface to detect status change.
+/// It only knows that the status has changed.
+/// It does not know if the network is online or not.
+protocol NetworkMonitorProtocol {
+ func start()
+ func stop()
+ var statusUpdatedCallback: (() -> Void)? { get set }
+}
+
+@available(iOS 12.0, *)
+class NetworkMonitor: NetworkMonitorProtocol {
+ init() {
+ ITBInfo()
+ }
+
+ deinit {
+ ITBInfo()
+ stop()
+ }
+
+ var statusUpdatedCallback: (() -> Void)?
+
+ func start() {
+ ITBInfo()
+ let networkMonitor = NWPathMonitor()
+ networkMonitor.pathUpdateHandler = { path in
+ ITBInfo("networkMonitor.pathUpdateHandler, path: \(path.debugDescription), status: \(path.status)")
+ self.statusUpdatedCallback?()
+ }
+
+ networkMonitor.start(queue: queue)
+ self.networkMonitor = networkMonitor
+ }
+
+ func stop() {
+ ITBInfo()
+ networkMonitor?.cancel()
+ networkMonitor = nil
+ }
+
+ private weak var networkMonitor: NWPathMonitor?
+ private let queue = DispatchQueue(label: "NetworkMonitor")
+}
+
+/// This is used for pre-iOS 12.0 because `NWPathMonitor` is not available.
+class PollingNetworkMonitor: NetworkMonitorProtocol {
+ init(pollingInterval: TimeInterval? = nil) {
+ ITBInfo()
+ self.pollingInterval = pollingInterval ?? Self.defaultPollingInterval
+ }
+
+ deinit {
+ ITBInfo()
+ }
+
+ var statusUpdatedCallback: (() -> Void)?
+
+ func start() {
+ ITBInfo()
+ timer?.invalidate()
+ if #available(iOS 10.0, *) {
+ self.timer = Timer.scheduledTimer(withTimeInterval: pollingInterval, repeats: true) { timer in
+ ITBInfo("Called timer")
+ self.statusUpdatedCallback?()
+ }
+ }
+ }
+
+ func stop() {
+ ITBInfo()
+ timer?.invalidate()
+ timer = nil
+ }
+
+ private var pollingInterval: TimeInterval
+ private static let defaultPollingInterval: TimeInterval = 5 * 60
+
+ private var timer: Timer?
+}
diff --git a/swift-sdk/Internal/OfflineRequestProcessor.swift b/swift-sdk/Internal/OfflineRequestProcessor.swift
new file mode 100644
index 000000000..296faff15
--- /dev/null
+++ b/swift-sdk/Internal/OfflineRequestProcessor.swift
@@ -0,0 +1,447 @@
+//
+// Created by Tapash Majumder on 8/24/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+@available(iOS 10.0, *)
+struct OfflineRequestProcessor: RequestProcessorProtocol {
+ init(apiKey: String,
+ authProvider: AuthProvider?,
+ authManager: IterableInternalAuthManagerProtocol?,
+ endPoint: String,
+ deviceMetadata: DeviceMetadata,
+ taskScheduler: IterableTaskScheduler,
+ taskRunner: IterableTaskRunner,
+ notificationCenter: NotificationCenterProtocol
+ ) {
+ ITBInfo()
+ self.apiKey = apiKey
+ self.authProvider = authProvider
+ self.authManager = authManager
+ self.endPoint = endPoint
+ self.deviceMetadata = deviceMetadata
+ self.taskScheduler = taskScheduler
+ self.taskRunner = taskRunner
+ notificationListener = NotificationListener(notificationCenter: notificationCenter)
+ }
+
+ func start() {
+ ITBInfo()
+ taskRunner.start()
+ }
+
+ func stop(){
+ ITBInfo()
+ taskRunner.stop()
+ }
+
+ @discardableResult
+ func register(registerTokenInfo: RegisterTokenInfo,
+ notificationStateProvider: NotificationStateProviderProtocol,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createRegisterTokenRequest(registerTokenInfo: registerTokenInfo,
+ notificationsEnabled: true)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func disableDeviceForCurrentUser(hexToken: String,
+ withOnSuccess onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createDisableDeviceRequest(forAllUsers: false, hexToken: hexToken)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func disableDeviceForAllUsers(hexToken: String,
+ withOnSuccess onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createDisableDeviceRequest(forAllUsers: true, hexToken: hexToken)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func updateUser(_ dataFields: [AnyHashable: Any],
+ mergeNestedObjects: Bool,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createUpdateUserRequest(dataFields: dataFields, mergeNestedObjects: mergeNestedObjects)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func updateEmail(_ newEmail: String,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createUpdateEmailRequest(newEmail: newEmail)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func trackPurchase(_ total: NSNumber,
+ items: [CommerceItem],
+ dataFields: [AnyHashable: Any]?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackPurchaseRequest(total,
+ items: items,
+ dataFields: dataFields)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func trackPushOpen(_ campaignId: NSNumber,
+ templateId: NSNumber?,
+ messageId: String,
+ appAlreadyRunning: Bool,
+ dataFields: [AnyHashable: Any]?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackPushOpenRequest(campaignId,
+ templateId: templateId,
+ messageId: messageId,
+ appAlreadyRunning: appAlreadyRunning,
+ dataFields: dataFields)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func track(event: String,
+ dataFields: [AnyHashable: Any]?,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ ITBInfo()
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackEventRequest(event,
+ dataFields: dataFields)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func updateSubscriptions(info: UpdateSubscriptionsInfo,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createUpdateSubscriptionsRequest(info.emailListIds,
+ unsubscribedChannelIds: info.unsubscribedChannelIds,
+ unsubscribedMessageTypeIds: info.unsubscribedMessageTypeIds,
+ subscribedMessageTypeIds: info.subscribedMessageTypeIds,
+ campaignId: info.campaignId,
+ templateId: info.templateId)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func trackInAppOpen(_ message: IterableInAppMessage,
+ location: InAppLocation,
+ inboxSessionId: String?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppOpenRequest(inAppMessageContext: InAppMessageContext.from(message: message,
+ location: location,
+ inboxSessionId: inboxSessionId))
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func trackInAppClick(_ message: IterableInAppMessage,
+ location: InAppLocation,
+ inboxSessionId: String?,
+ clickedUrl: String,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppClickRequest(inAppMessageContext: InAppMessageContext.from(message: message,
+ location: location,
+ inboxSessionId: inboxSessionId),
+ clickedUrl: clickedUrl)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func trackInAppClose(_ message: IterableInAppMessage,
+ location: InAppLocation,
+ inboxSessionId: String?,
+ source: InAppCloseSource?,
+ clickedUrl: String?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppCloseRequest(inAppMessageContext: InAppMessageContext.from(message: message,
+ location: location,
+ inboxSessionId: inboxSessionId),
+ source: source,
+ clickedUrl: clickedUrl)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func track(inboxSession: IterableInboxSession,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInboxSessionRequest(inboxSession: inboxSession)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func track(inAppDelivery message: IterableInAppMessage,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppDeliveryRequest(inAppMessageContext: InAppMessageContext.from(message: message,
+ location: nil))
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func inAppConsume(_ messageId: String,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createInAppConsumeRequest(messageId)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func inAppConsume(message: IterableInAppMessage,
+ location: InAppLocation,
+ source: InAppDeleteSource?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppConsumeRequest(inAppMessageContext: InAppMessageContext.from(message: message, location: location),
+ source: source)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ // MARK: DEPRECATED
+
+ @discardableResult
+ func trackInAppOpen(_ messageId: String,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppOpenRequest(messageId)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ @discardableResult
+ func trackInAppClick(_ messageId: String,
+ clickedUrl: String,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ let requestGenerator = { (requestCreator: RequestCreator) in
+ requestCreator.createTrackInAppClickRequest(messageId, clickedUrl: clickedUrl)
+ }
+
+ return sendIterableRequest(requestGenerator: requestGenerator,
+ successHandler: onSuccess,
+ failureHandler: onFailure,
+ identifier: #function)
+ }
+
+ private let apiKey: String
+ private weak var authProvider: AuthProvider?
+ private weak var authManager: IterableInternalAuthManagerProtocol?
+ private let endPoint: String
+ private let deviceMetadata: DeviceMetadata
+ private let notificationListener: NotificationListener
+ private let taskScheduler: IterableTaskScheduler
+ private let taskRunner: IterableTaskRunner
+
+ private func createRequestCreator(authProvider: AuthProvider) -> RequestCreator {
+ return RequestCreator(apiKey: apiKey, auth: authProvider.auth, deviceMetadata: deviceMetadata)
+ }
+
+ private func sendIterableRequest(requestGenerator: (RequestCreator) -> Result,
+ successHandler onSuccess: OnSuccessHandler?,
+ failureHandler onFailure: OnFailureHandler?,
+ identifier: String) -> Future {
+ guard let authProvider = authProvider else {
+ fatalError("authProvider is missing")
+ }
+
+ let requestCreator = createRequestCreator(authProvider: authProvider)
+ guard case let Result.success(iterableRequest) = requestGenerator(requestCreator) else {
+ return SendRequestError.createErroredFuture(reason: "Could not create request")
+ }
+
+ let apiCallRequest = IterableAPICallRequest(apiKey: apiKey,
+ endPoint: endPoint,
+ auth: authProvider.auth,
+ deviceMetadata: deviceMetadata,
+ iterableRequest: iterableRequest)
+ switch taskScheduler.schedule(apiCallRequest: apiCallRequest, context: IterableTaskContext(blocking: true)) {
+ case .success(let taskId):
+ let result = notificationListener.futureFromTask(withTaskId: taskId)
+ return RequestProcessorUtil.apply(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ andAuthManager: authManager,
+ toResult: result,
+ withIdentifier: identifier)
+ case .failure(let error):
+ ITBError(error.localizedDescription)
+ return SendRequestError.createErroredFuture(reason: error.localizedDescription)
+ }
+ }
+
+ private class NotificationListener: NSObject {
+ init(notificationCenter: NotificationCenterProtocol) {
+ ITBInfo("OfflineRequestProcessor.NotificationListener.init()")
+ self.notificationCenter = notificationCenter
+ super.init()
+ self.notificationCenter.addObserver(self,
+ selector: #selector(onTaskFinishedWithSuccess(notification:)),
+ name: .iterableTaskFinishedWithSuccess, object: nil)
+ self.notificationCenter.addObserver(self,
+ selector: #selector(onTaskFinishedWithNoRetry(notification:)),
+ name: .iterableTaskFinishedWithNoRetry, object: nil)
+ }
+
+ deinit {
+ ITBInfo("OfflineRequestProcessor.NotificationListener.deinit()")
+ self.notificationCenter.removeObserver(self)
+ }
+
+ func futureFromTask(withTaskId taskId: String) -> Future {
+ ITBInfo()
+ let result = Promise()
+ pendingTasksMap[taskId] = result
+ return result
+ }
+
+ @objc
+ private func onTaskFinishedWithSuccess(notification: Notification) {
+ ITBInfo()
+ if let taskSendRequestValue = IterableNotificationUtil.notificationToTaskSendRequestValue(notification) {
+ let taskId = taskSendRequestValue.taskId
+ ITBInfo("task: \(taskId) finished with success")
+ if let promise = pendingTasksMap[taskId] {
+ promise.resolve(with: taskSendRequestValue.sendRequestValue)
+ pendingTasksMap.removeValue(forKey: taskId)
+ } else {
+ ITBError("could not find promise for taskId: \(taskId)")
+ }
+ } else {
+ ITBError("Could not find taskId for notification")
+ }
+ }
+
+ @objc
+ private func onTaskFinishedWithNoRetry(notification: Notification) {
+ ITBInfo()
+ if let taskSendRequestError = IterableNotificationUtil.notificationToTaskSendRequestError(notification) {
+ let taskId = taskSendRequestError.taskId
+ ITBInfo("task: \(taskId) finished with no retry")
+ if let promise = pendingTasksMap[taskId] {
+ promise.reject(with: taskSendRequestError.sendRequestError)
+ pendingTasksMap.removeValue(forKey: taskId)
+ } else {
+ ITBError("could not find promise for taskId: \(taskId)")
+ }
+ } else {
+ ITBError("Could not find taskId for notification")
+ }
+ }
+
+ private let notificationCenter: NotificationCenterProtocol
+ private var pendingTasksMap = [String: Promise]()
+ }
+}
diff --git a/swift-sdk/Internal/OnlineRequestProcessor.swift b/swift-sdk/Internal/OnlineRequestProcessor.swift
new file mode 100644
index 000000000..a45c9c15b
--- /dev/null
+++ b/swift-sdk/Internal/OnlineRequestProcessor.swift
@@ -0,0 +1,285 @@
+//
+// Created by Tapash Majumder on 8/10/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+/// `IterableAPIinternal` will delegate all network related calls to this struct.
+struct OnlineRequestProcessor: RequestProcessorProtocol {
+ init(apiKey: String,
+ authProvider: AuthProvider?,
+ authManager: IterableInternalAuthManagerProtocol?,
+ endPoint: String,
+ networkSession: NetworkSessionProtocol,
+ deviceMetadata: DeviceMetadata) {
+ self.authManager = authManager
+ apiClient = ApiClient(apiKey: apiKey,
+ authProvider: authProvider,
+ endPoint: endPoint,
+ networkSession: networkSession,
+ deviceMetadata: deviceMetadata)
+ }
+
+ func start() {
+ ITBInfo()
+ }
+
+ func stop() {
+ ITBInfo()
+ }
+
+ @discardableResult
+ func register(registerTokenInfo: RegisterTokenInfo,
+ notificationStateProvider: NotificationStateProviderProtocol,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ self.register(registerTokenInfo: registerTokenInfo,
+ notificationsEnabled: notificationStateProvider.notificationsEnabled,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func disableDeviceForCurrentUser(hexToken: String,
+ withOnSuccess onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ disableDevice(forAllUsers: false, hexToken: hexToken, onSuccess: onSuccess, onFailure: onFailure)
+ }
+
+ @discardableResult
+ func disableDeviceForAllUsers(hexToken: String,
+ withOnSuccess onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ disableDevice(forAllUsers: true, hexToken: hexToken, onSuccess: onSuccess, onFailure: onFailure)
+ }
+
+ @discardableResult
+ func updateUser(_ dataFields: [AnyHashable: Any],
+ mergeNestedObjects: Bool,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "updateUser",
+ forResult: apiClient.updateUser(dataFields, mergeNestedObjects: mergeNestedObjects))
+ }
+
+ @discardableResult
+ func updateEmail(_ newEmail: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "updateEmail",
+ forResult: apiClient.updateEmail(newEmail: newEmail))
+ }
+
+ @discardableResult
+ func trackPurchase(_ total: NSNumber,
+ items: [CommerceItem],
+ dataFields: [AnyHashable: Any]? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackPurchase",
+ forResult: apiClient.track(purchase: total, items: items, dataFields: dataFields))
+ }
+
+ @discardableResult
+ func trackPushOpen(_ campaignId: NSNumber,
+ templateId: NSNumber?,
+ messageId: String,
+ appAlreadyRunning: Bool,
+ dataFields: [AnyHashable: Any]? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackPushOpen",
+ forResult: apiClient.track(pushOpen: campaignId,
+ templateId: templateId,
+ messageId: messageId,
+ appAlreadyRunning: appAlreadyRunning,
+ dataFields: dataFields))
+ }
+
+ @discardableResult
+ func track(event: String,
+ dataFields: [AnyHashable: Any]? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackEvent",
+ forResult: apiClient.track(event: event, dataFields: dataFields))
+ }
+
+ @discardableResult
+ func updateSubscriptions(info: UpdateSubscriptionsInfo,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "updateSubscriptions",
+ forResult: apiClient.updateSubscriptions(info.emailListIds,
+ unsubscribedChannelIds: info.unsubscribedChannelIds,
+ unsubscribedMessageTypeIds: info.unsubscribedMessageTypeIds,
+ subscribedMessageTypeIds: info.subscribedMessageTypeIds,
+ campaignId: info.campaignId,
+ templateId: info.templateId))
+ }
+
+ @discardableResult
+ func trackInAppOpen(_ message: IterableInAppMessage,
+ location: InAppLocation,
+ inboxSessionId: String? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let result = apiClient.track(inAppOpen: InAppMessageContext.from(message: message, location: location, inboxSessionId: inboxSessionId))
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInAppOpen",
+ forResult: result)
+ }
+
+ @discardableResult
+ func trackInAppClick(_ message: IterableInAppMessage,
+ location: InAppLocation = .inApp,
+ inboxSessionId: String? = nil,
+ clickedUrl: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let result = apiClient.track(inAppClick: InAppMessageContext.from(message: message, location: location, inboxSessionId: inboxSessionId),
+ clickedUrl: clickedUrl)
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInAppClick",
+ forResult: result)
+ }
+
+ @discardableResult
+ func trackInAppClose(_ message: IterableInAppMessage,
+ location: InAppLocation = .inApp,
+ inboxSessionId: String? = nil,
+ source: InAppCloseSource? = nil,
+ clickedUrl: String? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let result = apiClient.track(inAppClose: InAppMessageContext.from(message: message, location: location, inboxSessionId: inboxSessionId),
+ source: source,
+ clickedUrl: clickedUrl)
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInAppClose",
+ forResult: result)
+ }
+
+ @discardableResult
+ func track(inboxSession: IterableInboxSession,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let result = apiClient.track(inboxSession: inboxSession)
+
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInboxSession",
+ forResult: result)
+ }
+
+ @discardableResult
+ func track(inAppDelivery message: IterableInAppMessage,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInAppDelivery",
+ forResult: apiClient.track(inAppDelivery: InAppMessageContext.from(message: message, location: nil)))
+ }
+
+ @discardableResult
+ func inAppConsume(_ messageId: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "inAppConsume",
+ forResult: apiClient.inAppConsume(messageId: messageId))
+ }
+
+ @discardableResult
+ func inAppConsume(message: IterableInAppMessage,
+ location: InAppLocation = .inApp,
+ source: InAppDeleteSource? = nil,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let result = apiClient.inAppConsume(inAppMessageContext: InAppMessageContext.from(message: message, location: location),
+ source: source)
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "inAppConsumeWithSource",
+ forResult: result)
+ }
+
+ // MARK: DEPRECATED
+
+ @discardableResult
+ func trackInAppOpen(_ messageId: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ let result = apiClient.track(inAppOpen: messageId)
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInAppOpen",
+ forResult: result)
+ }
+
+ @discardableResult
+ func trackInAppClick(_ messageId: String,
+ clickedUrl: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "trackInAppClick",
+ forResult: apiClient.track(inAppClick: messageId, clickedUrl: clickedUrl))
+ }
+
+ private let apiClient: ApiClientProtocol
+ private weak var authManager: IterableInternalAuthManagerProtocol?
+
+ @discardableResult
+ private func register(registerTokenInfo: RegisterTokenInfo,
+ notificationsEnabled: Bool,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ return applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "registerToken",
+ forResult: apiClient.register(registerTokenInfo: registerTokenInfo,
+ notificationsEnabled: notificationsEnabled))
+ }
+
+ @discardableResult
+ private func disableDevice(forAllUsers allUsers: Bool,
+ hexToken: String,
+ onSuccess: OnSuccessHandler? = nil,
+ onFailure: OnFailureHandler? = nil) -> Future {
+ applyCallbacks(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ withIdentifier: "disableDevice",
+ forResult: apiClient.disableDevice(forAllUsers: allUsers, hexToken: hexToken))
+ }
+
+ private func applyCallbacks(successHandler onSuccess: OnSuccessHandler? = nil,
+ andFailureHandler onFailure: OnFailureHandler? = nil,
+ withIdentifier identifier: String,
+ forResult result: Future) -> Future {
+ RequestProcessorUtil.apply(successHandler: onSuccess,
+ andFailureHandler: onFailure,
+ andAuthManager: authManager,
+ toResult: result,
+ withIdentifier: identifier)
+ }
+}
diff --git a/swift-sdk/Internal/PersistenceHelper.swift b/swift-sdk/Internal/PersistenceHelper.swift
new file mode 100644
index 000000000..53def6462
--- /dev/null
+++ b/swift-sdk/Internal/PersistenceHelper.swift
@@ -0,0 +1,44 @@
+//
+// Created by Tapash Majumder on 7/22/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+struct PersistenceHelper {
+ static func task(from: IterableTaskManagedObject) -> IterableTask {
+ IterableTask(id: from.id,
+ name: from.name,
+ version: Int(from.version),
+ createdAt: from.createdAt,
+ modifiedAt: from.modifiedAt,
+ type: IterableTaskType(rawValue: from.type) ?? .apiCall,
+ attempts: Int(from.attempts),
+ lastAttemptedAt: from.lastAttemptedAt,
+ processing: from.processing,
+ scheduledAt: from.scheduledAt,
+ data: from.data,
+ failed: from.failed,
+ blocking: from.blocking,
+ requestedAt: from.requestedAt,
+ taskFailureData: from.taskFailureData)
+ }
+
+ static func copy(from: IterableTask, to: IterableTaskManagedObject) {
+ to.id = from.id
+ to.name = from.name
+ to.version = Int64(from.version)
+ to.createdAt = from.createdAt
+ to.modifiedAt = from.modifiedAt
+ to.type = from.type.rawValue
+ to.attempts = Int64(from.attempts)
+ to.lastAttemptedAt = from.lastAttemptedAt
+ to.processing = from.processing
+ to.scheduledAt = from.scheduledAt
+ to.data = from.data
+ to.failed = from.failed
+ to.blocking = from.blocking
+ to.requestedAt = from.requestedAt
+ to.taskFailureData = from.taskFailureData
+ }
+}
diff --git a/swift-sdk/Internal/Promise.swift b/swift-sdk/Internal/Promise.swift
index 837c440d2..ed4b47988 100644
--- a/swift-sdk/Internal/Promise.swift
+++ b/swift-sdk/Internal/Promise.swift
@@ -10,10 +10,10 @@ enum IterableError: Error {
}
extension IterableError: LocalizedError {
- public var localizedDescription: String {
+ var errorDescription: String? {
switch self {
case let .general(description):
- return description
+ return NSLocalizedString(description, comment: "error description")
}
}
}
@@ -132,6 +132,20 @@ extension Future {
return promise
}
+
+ func replaceError(with defaultForError: Value) -> Future {
+ let promise = Promise()
+
+ onSuccess { value in
+ promise.resolve(with: value)
+ }
+
+ onError { _ in
+ promise.resolve(with: defaultForError)
+ }
+
+ return promise
+ }
}
// This class takes the responsibility of setting value for Future
diff --git a/swift-sdk/Internal/RequestCreator.swift b/swift-sdk/Internal/RequestCreator.swift
index e3fb6733d..5b1f24e13 100644
--- a/swift-sdk/Internal/RequestCreator.swift
+++ b/swift-sdk/Internal/RequestCreator.swift
@@ -6,24 +6,6 @@
import Foundation
import UIKit
-// These are Iterable specific Request items.
-// They don't have Api endpoint and request endpoint defined yet.
-enum IterableRequest {
- case get(GetRequest)
- case post(PostRequest)
-}
-
-struct GetRequest {
- let path: String
- let args: [String: String]?
-}
-
-struct PostRequest {
- let path: String
- let args: [String: String]?
- let body: [AnyHashable: Any]?
-}
-
// This is a stateless pure functional class
// This will create IterableRequest
// The API Endpoint and request endpoint is not defined yet
@@ -49,29 +31,25 @@ struct RequestCreator {
return .success(.post(createPostRequest(path: Const.Path.updateEmail, body: body)))
}
- func createRegisterTokenRequest(hexToken: String,
- appName: String,
- deviceId: String,
- sdkVersion: String?,
- deviceAttributes: [String: String],
- pushServicePlatform: String,
+ func createRegisterTokenRequest(registerTokenInfo: RegisterTokenInfo,
notificationsEnabled: Bool) -> Result {
guard let keyValueForCurrentUser = keyValueForCurrentUser else {
ITBError("Both email and userId are nil")
return .failure(IterableError.general(description: "Both email and userId are nil"))
}
- let dataFields = DataFieldsHelper.createDataFields(sdkVersion: sdkVersion,
- deviceId: deviceId,
+ let dataFields = DataFieldsHelper.createDataFields(sdkVersion: registerTokenInfo.sdkVersion,
+ deviceId: registerTokenInfo.deviceId,
device: UIDevice.current,
bundle: Bundle.main,
notificationsEnabled: notificationsEnabled,
- deviceAttributes: deviceAttributes)
+ deviceAttributes: registerTokenInfo.deviceAttributes)
let deviceDictionary: [String: Any] = [
- JsonKey.token.jsonKey: hexToken,
- JsonKey.platform.jsonKey: pushServicePlatform,
- JsonKey.applicationName.jsonKey: appName,
+ JsonKey.token.jsonKey: registerTokenInfo.hexToken,
+ JsonKey.platform.jsonKey: RequestCreator.pushServicePlatformToString(registerTokenInfo.pushServicePlatform,
+ apnsType: registerTokenInfo.apnsType),
+ JsonKey.applicationName.jsonKey: registerTokenInfo.appName,
JsonKey.dataFields.jsonKey: dataFields,
]
@@ -462,6 +440,17 @@ struct RequestCreator {
return JsonValue.DeviceIdiom.unspecified
}
}
+
+ private static func pushServicePlatformToString(_ pushServicePlatform: PushServicePlatform, apnsType: APNSType) -> String {
+ switch pushServicePlatform {
+ case .production:
+ return JsonValue.apnsProduction.jsonStringValue
+ case .sandbox:
+ return JsonValue.apnsSandbox.jsonStringValue
+ case .auto:
+ return apnsType == .sandbox ? JsonValue.apnsSandbox.jsonStringValue : JsonValue.apnsProduction.jsonStringValue
+ }
+ }
}
// MARK: - DEPRECATED
diff --git a/swift-sdk/Internal/RequestProcessor.swift b/swift-sdk/Internal/RequestProcessor.swift
new file mode 100644
index 000000000..3eb3aefcd
--- /dev/null
+++ b/swift-sdk/Internal/RequestProcessor.swift
@@ -0,0 +1,274 @@
+//
+// Created by Tapash Majumder on 8/24/20.
+// Copyright © 2020 Iterable. All rights reserved.
+//
+
+import Foundation
+
+protocol RequestProcessorStrategy {
+ var chooseOfflineProcessor: Bool { get }
+}
+
+struct DefaultRequestProcessorStrategy: RequestProcessorStrategy {
+ let selectOffline: Bool
+
+ var chooseOfflineProcessor: Bool {
+ selectOffline
+ }
+}
+
+@available(iOS 10.0, *)
+class RequestProcessor: RequestProcessorProtocol {
+ init(onlineCreator: @escaping () -> OnlineRequestProcessor,
+ offlineCreator: @escaping () -> OfflineRequestProcessor?,
+ strategy: RequestProcessorStrategy = DefaultRequestProcessorStrategy(selectOffline: false)) {
+ ITBInfo()
+ self.onlineCreator = onlineCreator
+ self.offlineCreator = offlineCreator
+ self.strategy = strategy
+ }
+
+ deinit {
+ ITBInfo()
+ }
+
+ func start() {
+ ITBInfo()
+ chooseRequestProcessor().start()
+ }
+
+ func stop() {
+ ITBInfo()
+ chooseRequestProcessor().stop()
+ }
+
+ @discardableResult
+ func register(registerTokenInfo: RegisterTokenInfo,
+ notificationStateProvider: NotificationStateProviderProtocol,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().register(registerTokenInfo: registerTokenInfo,
+ notificationStateProvider: notificationStateProvider,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func disableDeviceForCurrentUser(hexToken: String,
+ withOnSuccess onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().disableDeviceForCurrentUser(hexToken: hexToken,
+ withOnSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func disableDeviceForAllUsers(hexToken: String,
+ withOnSuccess onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().disableDeviceForAllUsers(hexToken: hexToken,
+ withOnSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func updateUser(_ dataFields: [AnyHashable: Any],
+ mergeNestedObjects: Bool,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().updateUser(dataFields,
+ mergeNestedObjects: mergeNestedObjects,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func updateEmail(_ newEmail: String,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().updateEmail(newEmail,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func trackPurchase(_ total: NSNumber,
+ items: [CommerceItem],
+ dataFields: [AnyHashable: Any]?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().trackPurchase(total,
+ items: items,
+ dataFields: dataFields,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func trackPushOpen(_ campaignId: NSNumber,
+ templateId: NSNumber?,
+ messageId: String,
+ appAlreadyRunning: Bool,
+ dataFields: [AnyHashable: Any]?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().trackPushOpen(campaignId,
+ templateId: templateId,
+ messageId: messageId,
+ appAlreadyRunning: appAlreadyRunning,
+ dataFields: dataFields,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func track(event: String,
+ dataFields: [AnyHashable: Any]?,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future {
+ chooseRequestProcessor().track(event: event,
+ dataFields: dataFields,
+ onSuccess: onSuccess,
+ onFailure: onFailure)
+ }
+
+ @discardableResult
+ func updateSubscriptions(info: UpdateSubscriptionsInfo,
+ onSuccess: OnSuccessHandler?,
+ onFailure: OnFailureHandler?) -> Future