diff --git a/Example/CreativeUITest/CreativeUITest.swift b/Example/CreativeUITest/CreativeUITest.swift index 8af0c3f..0a1dc6c 100644 --- a/Example/CreativeUITest/CreativeUITest.swift +++ b/Example/CreativeUITest/CreativeUITest.swift @@ -26,7 +26,9 @@ final class CreativeUITest: XCTestCase, BaseXCTestCase { } func testLoadCreative_clickClose_closesCreative() { - launch(with: .production) + launch(with: .production, extras: [ + "SKIP_FATIGUE_ON_CREATIVE": "true" + ]) HomePage .tapOnPushMeToCreative() diff --git a/Example/CreativeUITest/Protocols/BaseXCTestCase.swift b/Example/CreativeUITest/Protocols/BaseXCTestCase.swift index 3b9f2c1..a9bc462 100644 --- a/Example/CreativeUITest/Protocols/BaseXCTestCase.swift +++ b/Example/CreativeUITest/Protocols/BaseXCTestCase.swift @@ -33,12 +33,21 @@ extension BaseXCTestCase { } func launch(with mode: Mode) { + launch(with: mode, extras: [:]) + } + + func launch(with mode: Mode, extras: [String: String] = [:]) { let app = XCUIApplication() app.launchEnvironment = [ "COM_ATTENTIVE_EXAMPLE_DOMAIN" : "mobileapps", "COM_ATTENTIVE_EXAMPLE_MODE" : mode.rawValue, "COM_ATTENTIVE_EXAMPLE_IS_UI_TEST" : "YES", ] + + if !extras.isEmpty { + app.launchEnvironment.merge(extras) { _, new in new } + } + app.launch() } diff --git a/README.md b/README.md index 49f6718..e51d761 100644 --- a/README.md +++ b/README.md @@ -242,6 +242,28 @@ ATTNSDK *sdk = [[ATTNSDK alloc] initWithDomain:@"domain"]; [sdk updateDomain: @"differentDomain"]; ``` +### Skip Fatigue on Creative + +Determinates if fatigue rules evaluation will be skipped for Creative. Default value is `false`. + +#### Swift + +```swift +let sdk = ATTNSDK(domain: "domain") +sdk.skipFatigueOnCreative = true +``` + +#### Objective-C + +```objective-c +ATTNSDK *sdk = [[ATTNSDK alloc] initWithDomain:@"domain"]; +sdk.skipFatigueOnCreative = YES; +``` + +Alternatively, `SKIP_FATIGUE_ON_CREATIVE` can be added as an environment value in the project scheme or even included in CI files. + +Environment value can be a string with value `"true"` or `"false"`. + ### Clear the current user If the user logs out then the current user identifiers should be deleted: diff --git a/Sources/ATTNConstants.swift b/Sources/ATTNConstants.swift index 5722bae..3ad804d 100644 --- a/Sources/ATTNConstants.swift +++ b/Sources/ATTNConstants.swift @@ -8,5 +8,8 @@ import Foundation struct ATTNConstants { + private init() { } + static let sdkVersion = "0.6.0" + static let skipFatigueEnvKey = "SKIP_FATIGUE_ON_CREATIVE" } diff --git a/Sources/Helpers/Extension/ATTNSDK+Extension.swift b/Sources/Helpers/Extension/ATTNSDK+Extension.swift new file mode 100644 index 0000000..795b9a6 --- /dev/null +++ b/Sources/Helpers/Extension/ATTNSDK+Extension.swift @@ -0,0 +1,22 @@ +// +// ATTNSDK+Extension.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-06-17. +// + +import Foundation + +extension ATTNSDK { + func send(event: ATTNEvent) { + api.send(event: event, userIdentity: userIdentity) + } + + func initializeSkipFatigueOnCreatives() { + if let skipFatigueValue = ProcessInfo.processInfo.environment[ATTNConstants.skipFatigueEnvKey] { + self.skipFatigueOnCreative = skipFatigueValue.booleanValue + } else { + self.skipFatigueOnCreative = false + } + } +} diff --git a/Sources/Helpers/Extension/Boolean+Extension.swift b/Sources/Helpers/Extension/Boolean+Extension.swift new file mode 100644 index 0000000..a529ec0 --- /dev/null +++ b/Sources/Helpers/Extension/Boolean+Extension.swift @@ -0,0 +1,14 @@ +// +// Boolean+Extension.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-06-17. +// + +import Foundation + +extension Bool { + var stringValue: String { + self ? "true" : "false" + } +} diff --git a/Sources/Helpers/Extension/String+Extension.swift b/Sources/Helpers/Extension/String+Extension.swift new file mode 100644 index 0000000..6338da4 --- /dev/null +++ b/Sources/Helpers/Extension/String+Extension.swift @@ -0,0 +1,14 @@ +// +// String+Extension.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-06-17. +// + +import Foundation + +extension String { + var booleanValue: Bool { + self == "true" + } +} diff --git a/Sources/Public/SDK/ATTNSDK.swift b/Sources/Public/SDK/ATTNSDK.swift index dffc4a7..64ae80a 100644 --- a/Sources/Public/SDK/ATTNSDK.swift +++ b/Sources/Public/SDK/ATTNSDK.swift @@ -51,6 +51,9 @@ public final class ATTNSDK: NSObject { private var mode: ATTNSDKMode private var urlBuilder: ATTNCreativeUrlProviding = ATTNCreativeUrlProvider() + /// Determinates if fatigue rules evaluation will be skipped for Creative. Default value is false. + @objc public var skipFatigueOnCreative: Bool = false + public init(domain: String, mode: ATTNSDKMode) { NSLog("init attentive_ios_sdk v%@", ATTNConstants.sdkVersion) self.domain = domain @@ -62,6 +65,7 @@ public final class ATTNSDK: NSObject { super.init() self.sendInfoEvent() + self.initializeSkipFatigueOnCreatives() } @objc(initWithDomain:) @@ -84,11 +88,57 @@ public final class ATTNSDK: NSObject { @objc(trigger:) public func trigger(_ view: UIView) { - trigger(view, handler: nil) + launchCreative(parentView: view) } @objc(trigger:handler:) public func trigger(_ view: UIView, handler: ATTNCreativeTriggerCompletionHandler?) { + launchCreative(parentView: view, handler: handler) + } + + @objc(trigger:creativeId:) + public func trigger(_ view: UIView, creativeId: String) { + launchCreative(parentView: view, creativeId: creativeId, handler: nil) + } + + @objc(trigger:creativeId:handler:) + public func trigger(_ view: UIView, creativeId: String, handler: ATTNCreativeTriggerCompletionHandler?) { + launchCreative(parentView: view, creativeId: creativeId, handler: handler) + } + + @objc(clearUser) + public func clearUser() { + userIdentity.clearUser() + } + + @objc(updateDomain:) + public func update(domain: String) { + guard self.domain != domain else { return } + self.domain = domain + api.update(domain: domain) + api.send(userIdentity: userIdentity) + } +} + +// MARK: Private Helpers +fileprivate extension ATTNSDK { + func sendInfoEvent() { + api.send(event: ATTNInfoEvent(), userIdentity: userIdentity) + } + + func closeCreative() { + webView?.removeFromSuperview() + webView = nil + ATTNSDK.isCreativeOpen = false + triggerHandler?(ATTNCreativeTriggerStatus.closed) + NSLog("Successfully closed creative") + } + + func launchCreative( + parentView view: UIView, + creativeId: String? = nil, + handler: ATTNCreativeTriggerCompletionHandler? = nil + ) { parentView = view triggerHandler = handler @@ -107,9 +157,13 @@ public final class ATTNSDK: NSObject { NSLog("The iOS version is new enough, continuing to show the Attentive creative.") let creativePageUrl = urlBuilder.buildCompanyCreativeUrl( - forDomain: domain, - mode: mode.rawValue, - userIdentity: userIdentity + configuration: ATTNCreativeUrlConfig( + domain: domain, + creativeId: creativeId, + skipFatigue: skipFatigueOnCreative, + mode: mode.rawValue, + userIdentity: userIdentity + ) ) NSLog("Requesting creative page url: %@", creativePageUrl) @@ -142,34 +196,6 @@ public final class ATTNSDK: NSObject { webView.backgroundColor = .clear } } - - @objc(clearUser) - public func clearUser() { - userIdentity.clearUser() - } - - @objc(updateDomain:) - public func update(domain: String) { - guard self.domain != domain else { return } - self.domain = domain - api.update(domain: domain) - api.send(userIdentity: userIdentity) - } -} - -// MARK: Private Helpers -fileprivate extension ATTNSDK { - func sendInfoEvent() { - api.send(event: ATTNInfoEvent(), userIdentity: userIdentity) - } - - func closeCreative() { - webView?.removeFromSuperview() - webView = nil - ATTNSDK.isCreativeOpen = false - triggerHandler?(ATTNCreativeTriggerStatus.closed) - NSLog("Successfully closed creative") - } } // MARK: WKScriptMessageHandler @@ -270,10 +296,6 @@ extension ATTNSDK: WKNavigationDelegate { // MARK: Internal Helpers extension ATTNSDK { - func send(event: ATTNEvent) { - api.send(event: event, userIdentity: userIdentity) - } - convenience init(domain: String, mode: ATTNSDKMode, urlBuilder: ATTNCreativeUrlProviding) { self.init(domain: domain, mode: mode) self.urlBuilder = urlBuilder diff --git a/Sources/URLProviders/ATTNCreativeUrlProvider.swift b/Sources/URLProviders/ATTNCreativeUrlProvider.swift index c7307ee..8c7f28c 100644 --- a/Sources/URLProviders/ATTNCreativeUrlProvider.swift +++ b/Sources/URLProviders/ATTNCreativeUrlProvider.swift @@ -8,11 +8,7 @@ import Foundation protocol ATTNCreativeUrlProviding { - func buildCompanyCreativeUrl( - forDomain domain: String, - mode: String, - userIdentity: ATTNUserIdentity - ) -> String + func buildCompanyCreativeUrl(configuration: ATTNCreativeUrlConfig) -> String } struct ATTNCreativeUrlProvider: ATTNCreativeUrlProviding { @@ -28,47 +24,43 @@ struct ATTNCreativeUrlProvider: ATTNCreativeUrlProviding { self.appInfo = appInfo } - func buildCompanyCreativeUrl( - forDomain domain: String, - mode: String, - userIdentity: ATTNUserIdentity - ) -> String { + func buildCompanyCreativeUrl(configuration: ATTNCreativeUrlConfig) -> String { var components = URLComponents() components.scheme = Constants.scheme components.host = Constants.host components.path = Constants.path var queryItems = [ - URLQueryItem(name: "domain", value: domain) + URLQueryItem(name: "domain", value: configuration.domain) ] - if mode == "debug" { - queryItems.append(URLQueryItem(name: mode, value: "matter-trip-grass-symbol")) + if configuration.mode == "debug" { + queryItems.append(URLQueryItem(name: configuration.mode, value: "matter-trip-grass-symbol")) } - queryItems.append(URLQueryItem(name: "vid", value: userIdentity.visitorId)) + queryItems.append(URLQueryItem(name: "vid", value: configuration.userIdentity.visitorId)) - if let clientUserId = userIdentity.identifiers[ATTNIdentifierType.clientUserId] as? String { + if let clientUserId = configuration.userIdentity.identifiers[ATTNIdentifierType.clientUserId] as? String { queryItems.append(URLQueryItem(name: "cuid", value: clientUserId)) } - if let phone = userIdentity.identifiers[ATTNIdentifierType.phone] as? String { + if let phone = configuration.userIdentity.identifiers[ATTNIdentifierType.phone] as? String { queryItems.append(URLQueryItem(name: "p", value: phone)) } - if let email = userIdentity.identifiers[ATTNIdentifierType.email] as? String { + if let email = configuration.userIdentity.identifiers[ATTNIdentifierType.email] as? String { queryItems.append(URLQueryItem(name: "e", value: email)) } - if let klaviyoId = userIdentity.identifiers[ATTNIdentifierType.klaviyoId] as? String { + if let klaviyoId = configuration.userIdentity.identifiers[ATTNIdentifierType.klaviyoId] as? String { queryItems.append(URLQueryItem(name: "kid", value: klaviyoId)) } - if let shopifyId = userIdentity.identifiers[ATTNIdentifierType.shopifyId] as? String { + if let shopifyId = configuration.userIdentity.identifiers[ATTNIdentifierType.shopifyId] as? String { queryItems.append(URLQueryItem(name: "sid", value: shopifyId)) } - if let customIdentifiersJson = getCustomIdentifiersJson(userIdentity: userIdentity) { + if let customIdentifiersJson = getCustomIdentifiersJson(userIdentity: configuration.userIdentity) { queryItems.append(URLQueryItem(name: "cstm", value: customIdentifiersJson)) } @@ -76,6 +68,14 @@ struct ATTNCreativeUrlProvider: ATTNCreativeUrlProviding { queryItems.append(URLQueryItem(name: "sdkVersion", value: appInfo.getSdkVersion())) queryItems.append(URLQueryItem(name: "sdkName", value: appInfo.getSdkName())) + if configuration.skipFatigue { + queryItems.append(URLQueryItem(name: "skipFatigue", value: configuration.skipFatigue.stringValue)) + } + + if let creativeId = configuration.creativeId { + queryItems.append(URLQueryItem(name: "attn_creative_id", value: creativeId)) + } + components.queryItems = queryItems return components.string ?? "" diff --git a/Sources/URLProviders/Configs/ATTNCreativeUrlConfig.swift b/Sources/URLProviders/Configs/ATTNCreativeUrlConfig.swift new file mode 100644 index 0000000..eab66c5 --- /dev/null +++ b/Sources/URLProviders/Configs/ATTNCreativeUrlConfig.swift @@ -0,0 +1,16 @@ +// +// ATTNCreativeUrlConfig.swift +// attentive-ios-sdk-framework +// +// Created by Vladimir - Work on 2024-06-14. +// + +import Foundation + +struct ATTNCreativeUrlConfig { + let domain: String + let creativeId: String? + let skipFatigue: Bool + let mode: String + let userIdentity: ATTNUserIdentity +} diff --git a/Tests/Doubles/Spies/ATTNCreativeUrlProviderSpy.swift b/Tests/Doubles/Spies/ATTNCreativeUrlProviderSpy.swift index eea8460..9dfee68 100644 --- a/Tests/Doubles/Spies/ATTNCreativeUrlProviderSpy.swift +++ b/Tests/Doubles/Spies/ATTNCreativeUrlProviderSpy.swift @@ -11,10 +11,12 @@ import Foundation final class ATTNCreativeUrlProviderSpy: ATTNCreativeUrlProviding { private(set) var buildCompanyCreativeUrlWasCalled = false private(set) var usedDomain: String? + private(set) var usedCreativeId: String? - func buildCompanyCreativeUrl(forDomain domain: String, mode: String, userIdentity: ATTNSDKFramework.ATTNUserIdentity) -> String { + func buildCompanyCreativeUrl(configuration: ATTNSDKFramework.ATTNCreativeUrlConfig) -> String { buildCompanyCreativeUrlWasCalled = true - usedDomain = domain + usedDomain = configuration.domain + usedCreativeId = configuration.creativeId return "" } } diff --git a/Tests/Extensions/ProcessInfo+Extension.swift b/Tests/Extensions/ProcessInfo+Extension.swift new file mode 100644 index 0000000..a92704b --- /dev/null +++ b/Tests/Extensions/ProcessInfo+Extension.swift @@ -0,0 +1,36 @@ +// +// ProcessInfo+Extension.swift +// attentive-ios-sdk Tests +// +// Created by Vladimir - Work on 2024-06-17. +// + +import Foundation + +extension ProcessInfo { + private static func swizzleEnvironmentImplementation(needsReset: Bool = false) { + let originalSelector = #selector(getter: ProcessInfo.environment) + let swizzledSelector = #selector(getter: ProcessInfo.mock_environment) + + guard let originalMethod = class_getInstanceMethod(ProcessInfo.self, originalSelector), + let swizzledMethod = class_getInstanceMethod(ProcessInfo.self, swizzledSelector) else { return } + + if needsReset { + method_exchangeImplementations(swizzledMethod, originalMethod) + } else { + method_exchangeImplementations(originalMethod, swizzledMethod) + } + } + + @objc var mock_environment: [String: String] { + return ["SKIP_FATIGUE_ON_CREATIVE": "true"] + } + + static func swizzleEnvironment() { + self.swizzleEnvironmentImplementation() + } + + static func restoreOriginalEnvironment() { + self.swizzleEnvironmentImplementation(needsReset: true) + } +} diff --git a/Tests/TestCases/ATTNCreativeUrlFormatterTests.swift b/Tests/TestCases/ATTNCreativeUrlFormatterTests.swift index 40fd3fe..7834b7d 100644 --- a/Tests/TestCases/ATTNCreativeUrlFormatterTests.swift +++ b/Tests/TestCases/ATTNCreativeUrlFormatterTests.swift @@ -9,7 +9,7 @@ import XCTest @testable import ATTNSDKFramework final class ATTNCreativeUrlFormatterTests: XCTestCase { - private let TEST_DOMAIN = "testDomain" + private let testDomain = "testDomain" private var sut: ATTNCreativeUrlProvider! private var appInfo: ATTNAppInfo! @@ -27,7 +27,15 @@ final class ATTNCreativeUrlFormatterTests: XCTestCase { func testBuildCompanyCreativeUrlForDomain_productionMode_buildsProdUrl() { let userIdentity = ATTNUserIdentity(identifiers: [:]) - let url = sut.buildCompanyCreativeUrl(forDomain: TEST_DOMAIN, mode: "production", userIdentity: userIdentity) + let config = ATTNCreativeUrlConfig( + domain: testDomain, + creativeId: nil, + skipFatigue: false, + mode: "production", + userIdentity: userIdentity + ) + + let url = sut.buildCompanyCreativeUrl(configuration: config) let expectedUrl = "https://creatives.attn.tv/mobile-apps/index.html?domain=testDomain&vid=\(userIdentity.visitorId)&sdkVersion=\(appInfo.getSdkVersion())&sdkName=attentive-ios-sdk" @@ -36,7 +44,15 @@ final class ATTNCreativeUrlFormatterTests: XCTestCase { func testBuildCompanyCreativeUrlForDomain_productionMode_buildsDebugUrl() { let userIdentity = ATTNUserIdentity(identifiers: [:]) - let url = sut.buildCompanyCreativeUrl(forDomain: TEST_DOMAIN, mode: "debug", userIdentity: userIdentity) + let config = ATTNCreativeUrlConfig( + domain: testDomain, + creativeId: nil, + skipFatigue: false, + mode: "debug", + userIdentity: userIdentity + ) + + let url = sut.buildCompanyCreativeUrl(configuration: config) let expectedUrl = "https://creatives.attn.tv/mobile-apps/index.html?domain=testDomain&debug=matter-trip-grass-symbol&vid=\(userIdentity.visitorId)&sdkVersion=\(appInfo.getSdkVersion())&sdkName=attentive-ios-sdk" @@ -45,7 +61,15 @@ final class ATTNCreativeUrlFormatterTests: XCTestCase { func testBuildCompanyCreativeUrlForDomain_withUserIdentifiers_buildsUrlWithIdentifierQueryParams() { let userIdentity = ATTNTestEventUtils.buildUserIdentity() - let url = sut.buildCompanyCreativeUrl(forDomain: TEST_DOMAIN, mode: "production", userIdentity: userIdentity) + let config = ATTNCreativeUrlConfig( + domain: testDomain, + creativeId: nil, + skipFatigue: false, + mode: "production", + userIdentity: userIdentity + ) + + let url = sut.buildCompanyCreativeUrl(configuration: config) let expectedUrl = "https://creatives.attn.tv/mobile-apps/index.html?domain=testDomain&vid=\(userIdentity.visitorId)&cuid=someClientUserId&p=+14156667777&e=someEmail@email.com&kid=someKlaviyoId&sid=someShopifyId&cstm=%7B%22customId%22:%22customIdValue%22%7D&sdkVersion=\(appInfo.getSdkVersion())&sdkName=attentive-ios-sdk" @@ -55,10 +79,54 @@ final class ATTNCreativeUrlFormatterTests: XCTestCase { func testBuildCompanyCreativeUrlForDomain_productionMode_buildsUrlWithSdkDetails() { let userIdentity = ATTNUserIdentity(identifiers: [:]) - let url = sut.buildCompanyCreativeUrl(forDomain: TEST_DOMAIN, mode: "production", userIdentity: userIdentity) + let config = ATTNCreativeUrlConfig( + domain: testDomain, + creativeId: nil, + skipFatigue: false, + mode: "production", + userIdentity: userIdentity + ) + + let url = sut.buildCompanyCreativeUrl(configuration: config) let expectedUrl = "https://creatives.attn.tv/mobile-apps/index.html?domain=testDomain&vid=\(userIdentity.visitorId)&sdkVersion=\(ATTNConstants.sdkVersion)&sdkName=attentive-ios-sdk" XCTAssertEqual(expectedUrl, url) } + + func testBuildCompanyCreativeUrlForDomain_withSkipFatigue_buildsUrlWithSkipFatigue() { + let userIdentity = ATTNUserIdentity(identifiers: [:]) + + let config = ATTNCreativeUrlConfig( + domain: testDomain, + creativeId: nil, + skipFatigue: true, + mode: "production", + userIdentity: userIdentity + ) + + let url = sut.buildCompanyCreativeUrl(configuration: config) + + let expectedUrl = "https://creatives.attn.tv/mobile-apps/index.html?domain=testDomain&vid=\(userIdentity.visitorId)&sdkVersion=\(ATTNConstants.sdkVersion)&sdkName=attentive-ios-sdk&skipFatigue=true" + + XCTAssertEqual(expectedUrl, url) + } + + func testBuildCompanyCreativeUrlForDomain_withCreativeId_buildsUrlWithCreativeId() { + let userIdentity = ATTNUserIdentity(identifiers: [:]) + + let config = ATTNCreativeUrlConfig( + domain: testDomain, + creativeId: "1234567", + skipFatigue: false, + mode: "production", + userIdentity: userIdentity + ) + + let url = sut.buildCompanyCreativeUrl(configuration: config) + + let expectedUrl = "https://creatives.attn.tv/mobile-apps/index.html?domain=testDomain&vid=\(userIdentity.visitorId)&sdkVersion=\(ATTNConstants.sdkVersion)&sdkName=attentive-ios-sdk&attn_creative_id=1234567" + + XCTAssertEqual(expectedUrl, url) + } } diff --git a/Tests/TestCases/ATTNSDKTests.swift b/Tests/TestCases/ATTNSDKTests.swift index 151674b..0e3c6b2 100644 --- a/Tests/TestCases/ATTNSDKTests.swift +++ b/Tests/TestCases/ATTNSDKTests.swift @@ -26,6 +26,8 @@ final class ATTNSDKTests: XCTestCase { override func tearDown() { ATTNEventTracker.destroy() + ProcessInfo.restoreOriginalEnvironment() + creativeUrlProviderSpy = nil sut = nil apiSpy = nil @@ -85,5 +87,26 @@ final class ATTNSDKTests: XCTestCase { XCTAssertEqual(sdk?.getDomain(), newDomain) } + + func testSkipFatigue_whenTrue_willUpdateUrl() { + let creativeId = "123456" + sut.skipFatigueOnCreative = true + + sut.trigger(UIView(), creativeId: creativeId, handler: nil) + + XCTAssertTrue(creativeUrlProviderSpy.buildCompanyCreativeUrlWasCalled) + XCTAssertEqual(creativeUrlProviderSpy.usedCreativeId, creativeId) + } + + func testSkipFatigue_whenEnvValueIsPassed_ShouldBeTrue() { + ProcessInfo.swizzleEnvironment() + let creativeId = "123456" + sut = ATTNSDK(api: apiSpy, urlBuilder: creativeUrlProviderSpy) + + sut.trigger(UIView(), creativeId: creativeId) + + XCTAssertTrue(creativeUrlProviderSpy.buildCompanyCreativeUrlWasCalled) + XCTAssertEqual(creativeUrlProviderSpy.usedCreativeId, creativeId) + } } diff --git a/attentive-ios-sdk.xcodeproj/project.pbxproj b/attentive-ios-sdk.xcodeproj/project.pbxproj index 8a3d068..7f277f7 100644 --- a/attentive-ios-sdk.xcodeproj/project.pbxproj +++ b/attentive-ios-sdk.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ 58D52E1D292EC52B00CF32DE /* ATTNSDKFramework.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 58332EC3292EC18800B1ECF3 /* ATTNSDKFramework.framework */; }; FB080C712C1C755A00834BAA /* ATTNAPISpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB65536D2C1B7747008DB3B1 /* ATTNAPISpy.swift */; }; FB080C722C1C755F00834BAA /* ATTNCreativeUrlProviderSpy.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB080C6F2C1C752E00834BAA /* ATTNCreativeUrlProviderSpy.swift */; }; + FB080C7F2C1CC9E500834BAA /* ATTNCreativeUrlConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB080C7E2C1CC9E500834BAA /* ATTNCreativeUrlConfig.swift */; }; FB2983E42C0F73990039759C /* ATTNAppInfoMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB2983D02C0F73990039759C /* ATTNAppInfoMock.swift */; }; FB2983E52C0F73990039759C /* ATTNUserAgentBuilderMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB2983D12C0F73990039759C /* ATTNUserAgentBuilderMock.swift */; }; FB2983F42C0F759D0039759C /* ATTNVisitorServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB2983F32C0F759D0039759C /* ATTNVisitorServiceTests.swift */; }; @@ -42,6 +43,10 @@ FB35C1972C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C1962C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift */; }; FB35C1992C0E5365009FA048 /* ATTNInfoEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */; }; FB35C19B2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */; }; + FB56D4DA2C208BAD00AF7530 /* ATTNSDK+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */; }; + FB56D4DC2C208D6100AF7530 /* Boolean+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */; }; + FB56D4DE2C208DC100AF7530 /* String+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4DD2C208DC100AF7530 /* String+Extension.swift */; }; + FB56D4E12C20925500AF7530 /* ProcessInfo+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB56D4E02C20925500AF7530 /* ProcessInfo+Extension.swift */; }; FB60AF0B2C1211C700C61537 /* ATTNEventURLProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB60AF0A2C1211C700C61537 /* ATTNEventURLProvider.swift */; }; FB65536A2C1B72D4008DB3B1 /* ATTNSDKTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */; }; FB65536C2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FB65536B2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift */; }; @@ -108,6 +113,7 @@ 58B01AC4294D44140001CEBF /* OCMArg.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OCMArg.h; sourceTree = ""; }; 58D52E19292EC52B00CF32DE /* attentive-ios-sdk Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "attentive-ios-sdk Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; FB080C6F2C1C752E00834BAA /* ATTNCreativeUrlProviderSpy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNCreativeUrlProviderSpy.swift; sourceTree = ""; }; + FB080C7E2C1CC9E500834BAA /* ATTNCreativeUrlConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNCreativeUrlConfig.swift; sourceTree = ""; }; FB0E49E52BFBB1900025E281 /* Package.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Package.swift; sourceTree = ""; }; FB2983D02C0F73990039759C /* ATTNAppInfoMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ATTNAppInfoMock.swift; sourceTree = ""; }; FB2983D12C0F73990039759C /* ATTNUserAgentBuilderMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ATTNUserAgentBuilderMock.swift; sourceTree = ""; }; @@ -137,6 +143,10 @@ FB35C1962C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNProductViewEvent+Extension.swift"; sourceTree = ""; }; FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNInfoEvent+Extension.swift"; sourceTree = ""; }; FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNCustomEvent+Extension.swift"; sourceTree = ""; }; + FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ATTNSDK+Extension.swift"; sourceTree = ""; }; + FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Boolean+Extension.swift"; sourceTree = ""; }; + FB56D4DD2C208DC100AF7530 /* String+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "String+Extension.swift"; sourceTree = ""; }; + FB56D4E02C20925500AF7530 /* ProcessInfo+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProcessInfo+Extension.swift"; sourceTree = ""; }; FB60AF0A2C1211C700C61537 /* ATTNEventURLProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNEventURLProvider.swift; sourceTree = ""; }; FB6553692C1B72D4008DB3B1 /* ATTNSDKTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNSDKTests.swift; sourceTree = ""; }; FB65536B2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ATTNAPIProtocol.swift; sourceTree = ""; }; @@ -266,6 +276,14 @@ path = OCMock; sourceTree = ""; }; + FB080C7D2C1CC9C000834BAA /* Configs */ = { + isa = PBXGroup; + children = ( + FB080C7E2C1CC9E500834BAA /* ATTNCreativeUrlConfig.swift */, + ); + path = Configs; + sourceTree = ""; + }; FB2983D22C0F73990039759C /* Mocks */ = { isa = PBXGroup; children = ( @@ -292,6 +310,7 @@ FB2983F22C0F75600039759C /* TestCases */, FB2983D32C0F73990039759C /* Doubles */, FB2984032C0F9D650039759C /* ATTNTestEventUtils.swift */, + FB56D4DF2C20923800AF7530 /* Extensions */, ); path = Tests; sourceTree = SOURCE_ROOT; @@ -332,9 +351,18 @@ path = Protocols; sourceTree = ""; }; + FB56D4DF2C20923800AF7530 /* Extensions */ = { + isa = PBXGroup; + children = ( + FB56D4E02C20925500AF7530 /* ProcessInfo+Extension.swift */, + ); + path = Extensions; + sourceTree = ""; + }; FB60AF092C12119F00C61537 /* URLProviders */ = { isa = PBXGroup; children = ( + FB080C7D2C1CC9C000834BAA /* Configs */, FBA9F9EA2C0A77AB00C65024 /* ATTNCreativeUrlProvider.swift */, FB60AF0A2C1211C700C61537 /* ATTNEventURLProvider.swift */, ); @@ -374,6 +402,8 @@ FBA9F9E82C0A77AB00C65024 /* Extension */ = { isa = PBXGroup; children = ( + FB56D4DD2C208DC100AF7530 /* String+Extension.swift */, + FB56D4DB2C208D6100AF7530 /* Boolean+Extension.swift */, FBA9F9E72C0A77AB00C65024 /* Dictionary+Extension.swift */, FB35C1822C0E1FD7009FA048 /* URLSession+Extension.swift */, FB35C18A2C0E3F27009FA048 /* ATTNEvent+Extension.swift */, @@ -383,6 +413,7 @@ FB35C1962C0E52F3009FA048 /* ATTNProductViewEvent+Extension.swift */, FB35C1982C0E5365009FA048 /* ATTNInfoEvent+Extension.swift */, FB35C19A2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift */, + FB56D4D92C208BAD00AF7530 /* ATTNSDK+Extension.swift */, ); path = Extension; sourceTree = ""; @@ -602,6 +633,7 @@ FBA9FA122C0A77AB00C65024 /* ATTNCustomEvent.swift in Sources */, FB35C1992C0E5365009FA048 /* ATTNInfoEvent+Extension.swift in Sources */, FB35C1892C0E3AF8009FA048 /* ATTNExternalVendorTypes.swift in Sources */, + FB080C7F2C1CC9E500834BAA /* ATTNCreativeUrlConfig.swift in Sources */, FBA9FA092C0A77AB00C65024 /* ATTNAppInfo.swift in Sources */, FB35C1792C0E030E009FA048 /* ATTNCreativeTriggerStatus.swift in Sources */, FBA9FA1B2C0A77AB00C65024 /* ATTNSDKMode.swift in Sources */, @@ -626,6 +658,8 @@ FBA9FA0D2C0A77AB00C65024 /* ATTNPersistentStorage.swift in Sources */, FBA9FA0B2C0A77AB00C65024 /* ATTNInfoEvent.swift in Sources */, FBA9FA0F2C0A77AB00C65024 /* ATTNVisitorService.swift in Sources */, + FB56D4DE2C208DC100AF7530 /* String+Extension.swift in Sources */, + FB56D4DA2C208BAD00AF7530 /* ATTNSDK+Extension.swift in Sources */, FBA9FA172C0A77AB00C65024 /* ATTNProductViewEvent.swift in Sources */, FB35C19B2C0E53F9009FA048 /* ATTNCustomEvent+Extension.swift in Sources */, FB35C1952C0E5292009FA048 /* ATTNAddToCartEvent+Extension.swift in Sources */, @@ -635,6 +669,7 @@ FBA9FA0A2C0A77AB00C65024 /* ATTNCreativeUrlProvider.swift in Sources */, FB65536C2C1B74A9008DB3B1 /* ATTNAPIProtocol.swift in Sources */, FB35C17D2C0E039E009FA048 /* ATTNConstants.swift in Sources */, + FB56D4DC2C208D6100AF7530 /* Boolean+Extension.swift in Sources */, FBA9FA082C0A77AB00C65024 /* Dictionary+Extension.swift in Sources */, FBA9FA152C0A77AB00C65024 /* ATTNOrder.swift in Sources */, FBA9FA1A2C0A77AB00C65024 /* ATTNSDK.swift in Sources */, @@ -647,6 +682,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + FB56D4E12C20925500AF7530 /* ProcessInfo+Extension.swift in Sources */, FB080C712C1C755A00834BAA /* ATTNAPISpy.swift in Sources */, FB90EF0B2C109CB4004DFC4A /* ATTNAPIITTests.swift in Sources */, FB90EF0F2C10A81E004DFC4A /* NSURLSessionMock.swift in Sources */,