From 07b2f47bcef864225e84ed953cd4b6e96d4b7d22 Mon Sep 17 00:00:00 2001 From: Sergey Korney Date: Fri, 28 Jun 2024 17:21:38 +0500 Subject: [PATCH] 1.6.33 --- .github/workflows/framework.yml | 14 +- AffiseAttributionLib.podspec | 2 +- AffiseAttributionLib/Classes/Affise.swift | 46 +++++- .../validate/DebugValidateUseCaseImpl.swift | 3 +- .../Classes/deeplink/DeeplinkManager.swift | 4 +- .../deeplink/DeeplinkManagerImpl.swift | 4 +- .../Classes/deeplink/DeeplinkValue.swift | 81 ++++++++++ .../Classes/modules/AffiseModuleApi.swift | 3 + .../Classes/modules/AffiseModuleManager.swift | 4 + .../Classes/modules/AffiseModules.swift | 2 + .../Classes/modules/link/AffiseLink.swift | 33 ++++ .../Classes/modules/link/AffiseLinkApi.swift | 5 + .../modules/link/AffiseLinkCallback.swift | 3 + .../subscription/AffiseSubscription.swift | 41 +++++ .../subscription/AffiseSubscriptionApi.swift | 6 +- .../Classes/network/CloudRepositoryImpl.swift | 3 +- .../Classes/network/HttpResponse.swift | 4 +- .../Classes/network/NetworkService.swift | 3 +- .../Classes/network/NetworkServiceImpl.swift | 17 +- .../Classes/network/NoRedirect.swift | 39 +++++ .../init/AffSDKVersionProvider.swift | 2 +- .../Classes/utils/HttpUtils.swift | 37 +++++ .../Classes/utils/StringUtils.swift | 10 ++ AffiseInternal.podspec | 2 +- AffiseInternal/Classes/AffiseApiMethod.swift | 27 +++- AffiseInternal/Classes/AffiseApiWrapper.swift | 147 +++++++++++------- AffiseModule.podspec | 16 +- AffiseModule/Link/Classes/LinkModule.swift | 24 +++ .../Classes/usecase/LinkResolveUseCase.swift | 7 + .../usecase/LinkResolveUseCaseImpl.swift | 59 +++++++ .../usecase/CheckStatusUseCaseImpl.swift | 3 +- .../Subscription/Classes/AffiseExt.swift | 14 -- .../Classes/SubscriptionModule.swift | 19 +-- AffiseSKAdNetwork.podspec | 2 +- CHANGELOG.md | 16 +- Package.swift | 7 + README.md | 105 +++++++++---- example/app/app/AppDelegate.swift | 29 +++- example/app/app/StoreView.swift | 38 +++-- 39 files changed, 707 insertions(+), 174 deletions(-) create mode 100644 AffiseAttributionLib/Classes/deeplink/DeeplinkValue.swift create mode 100644 AffiseAttributionLib/Classes/modules/AffiseModuleApi.swift create mode 100644 AffiseAttributionLib/Classes/modules/link/AffiseLink.swift create mode 100644 AffiseAttributionLib/Classes/modules/link/AffiseLinkApi.swift create mode 100644 AffiseAttributionLib/Classes/modules/link/AffiseLinkCallback.swift create mode 100644 AffiseAttributionLib/Classes/modules/subscription/AffiseSubscription.swift create mode 100644 AffiseAttributionLib/Classes/network/NoRedirect.swift create mode 100644 AffiseModule/Link/Classes/LinkModule.swift create mode 100644 AffiseModule/Link/Classes/usecase/LinkResolveUseCase.swift create mode 100644 AffiseModule/Link/Classes/usecase/LinkResolveUseCaseImpl.swift delete mode 100644 AffiseModule/Subscription/Classes/AffiseExt.swift diff --git a/.github/workflows/framework.yml b/.github/workflows/framework.yml index 4786166..07217d3 100644 --- a/.github/workflows/framework.yml +++ b/.github/workflows/framework.yml @@ -41,7 +41,7 @@ jobs: - name: Create AffiseAttributionLib XCFramework uses: unsignedapps/swift-create-xcframework@v2.3.0 with: - target: "AffiseAttributionLib, AffiseModuleAdvertising, AffiseModuleStatus, AffiseModuleSubscription" + target: "AffiseAttributionLib, AffiseModuleAdvertising, AffiseModuleLink, AffiseModuleStatus, AffiseModuleSubscription" # Release - name: Release @@ -68,6 +68,18 @@ jobs: asset_name: AffiseAttributionLib-${{ steps.version_tag.outputs.VERSION }}.xcframework.zip asset_content_type: application/zip + # Upload AffiseModuleLink + - name: Upload Release AffiseModuleLink Framework πŸ—³ + if: ${{ success() }} + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./AffiseModuleLink.zip + asset_name: AffiseModuleLink-${{ steps.version_tag.outputs.VERSION }}.xcframework.zip + asset_content_type: application/zip + # Upload AffiseModuleStatus - name: Upload Release AffiseModuleStatus Framework πŸ—³ if: ${{ success() }} diff --git a/AffiseAttributionLib.podspec b/AffiseAttributionLib.podspec index f464009..95f8856 100644 --- a/AffiseAttributionLib.podspec +++ b/AffiseAttributionLib.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |spec| spec.name = "AffiseAttributionLib" - spec.version = ENV['LIB_VERSION'] || "1.6.32" + spec.version = ENV['LIB_VERSION'] || "1.6.33" spec.summary = "Affise Attribution iOS library" spec.description = "Affise SDK is a software you can use to collect app usage statistics, device identifiers, deeplink usage, track install referrer." spec.homepage = "https://github.com/affise/sdk-ios" diff --git a/AffiseAttributionLib/Classes/Affise.swift b/AffiseAttributionLib/Classes/Affise.swift index 4e0596d..3079092 100644 --- a/AffiseAttributionLib/Classes/Affise.swift +++ b/AffiseAttributionLib/Classes/Affise.swift @@ -89,7 +89,7 @@ public final class Affise: NSObject { * Register [callback] for deeplink */ @objc - public static func registerDeeplinkCallback(_ callback: @escaping OnDeeplinkCallback) { + public static func registerDeeplinkCallback(_ callback: OnDeeplinkCallback?) { api?.deeplinkManager.setDeeplinkCallback(callback: callback) } @@ -216,6 +216,7 @@ public final class Affise: NSObject { /** * Get module status */ + @available(*, deprecated, message: "Method moved to Affise.Module", renamed: "Module.getStatus") @objc public static func getStatus(_ module: AffiseModules, _ onComplete: @escaping OnKeyValueCallback) { api?.moduleManager.status(module, onComplete) @@ -224,6 +225,7 @@ public final class Affise: NSObject { /** * Manual module start */ + @available(*, deprecated, message: "Method moved to Affise.Module", renamed: "Module.moduleStart") @objc @discardableResult public static func moduleStart(_ module: AffiseModules) -> Bool { @@ -233,6 +235,7 @@ public final class Affise: NSObject { /** * Get installed modules */ + @available(*, deprecated, message: "Method moved to Affise.Module", renamed: "Module.getModulesInstalledObjc") @objc public static func getModulesInstalledObjc() -> [String] { return api?.moduleManager.getModules().map { $0.description } ?? [] @@ -241,6 +244,7 @@ public final class Affise: NSObject { /** * Get installed modules */ + @available(*, deprecated, message: "Method moved to Affise.Module", renamed: "Module.getModulesInstalled") public static func getModulesInstalled() -> [AffiseModules] { return api?.moduleManager.getModules() ?? [] } @@ -256,7 +260,7 @@ public final class Affise: NSObject { internal static func getApi() -> AffiseApi? { return api } - + /** * Store internal send */ @@ -265,6 +269,44 @@ public final class Affise: NSObject { api?.storeInternalEventUseCase.storeInternalEvent(event: event) } + public class Module { + /** + * Get module status + */ + @objc + public static func getStatus(_ module: AffiseModules, _ onComplete: @escaping OnKeyValueCallback) { + api?.moduleManager.status(module, onComplete) + } + + /** + * Manual module start + */ + @objc + @discardableResult + public static func moduleStart(_ module: AffiseModules) -> Bool { + return api?.moduleManager.manualStart(module) ?? false + } + + /** + * Get installed modules + */ + @objc + public static func getModulesInstalledObjc() -> [String] { + return api?.moduleManager.getModules().map { $0.description } ?? [] + } + + /** + * Get installed modules + */ + public static func getModulesInstalled() -> [AffiseModules] { + return api?.moduleManager.getModules() ?? [] + } + + internal static func getApi(_ module: AffiseModules) -> AffiseModuleApi? { + return api?.moduleManager.api(module) + } + } + public class Debug { /** * Won't work on Production diff --git a/AffiseAttributionLib/Classes/debug/validate/DebugValidateUseCaseImpl.swift b/AffiseAttributionLib/Classes/debug/validate/DebugValidateUseCaseImpl.swift index 250a275..563c464 100644 --- a/AffiseAttributionLib/Classes/debug/validate/DebugValidateUseCaseImpl.swift +++ b/AffiseAttributionLib/Classes/debug/validate/DebugValidateUseCaseImpl.swift @@ -46,7 +46,8 @@ class DebugValidateUseCaseImpl { method: .POST, data: converter.convert(from: providers).toData(), timeout: TIMEOUT_SEND, - headers: CloudConfig.headers + headers: CloudConfig.headers, + redirect: true ) } diff --git a/AffiseAttributionLib/Classes/deeplink/DeeplinkManager.swift b/AffiseAttributionLib/Classes/deeplink/DeeplinkManager.swift index 639d85b..e9cb8c8 100644 --- a/AffiseAttributionLib/Classes/deeplink/DeeplinkManager.swift +++ b/AffiseAttributionLib/Classes/deeplink/DeeplinkManager.swift @@ -5,7 +5,7 @@ import UIKit * Interface describing callback that is going to be triggered when deeplink is received by application * Triggered when new deeplink [uri] is received by application with */ -public typealias OnDeeplinkCallback = (_ url: URL?) -> Void +public typealias OnDeeplinkCallback = (_ value: DeeplinkValue) -> Void /** * Manager that coordinates deeplink related tasks @@ -19,7 +19,7 @@ protocol DeeplinkManager { /** * Sets [callback] to invoke when app receives deeplink */ - func setDeeplinkCallback(callback: @escaping OnDeeplinkCallback) + func setDeeplinkCallback(callback: OnDeeplinkCallback?) /** * Process [uri] as deeplink diff --git a/AffiseAttributionLib/Classes/deeplink/DeeplinkManagerImpl.swift b/AffiseAttributionLib/Classes/deeplink/DeeplinkManagerImpl.swift index 0610968..500d334 100644 --- a/AffiseAttributionLib/Classes/deeplink/DeeplinkManagerImpl.swift +++ b/AffiseAttributionLib/Classes/deeplink/DeeplinkManagerImpl.swift @@ -52,13 +52,13 @@ internal class DeeplinkManagerImpl : DeeplinkManager { } } - func setDeeplinkCallback(callback: @escaping OnDeeplinkCallback) { + func setDeeplinkCallback(callback: OnDeeplinkCallback?) { deeplinkCallback = callback } func handleDeeplink(url: URL?) { isDeeplinkRepository.setDeeplinkClick(isDeeplink: true) isDeeplinkRepository.setDeeplink(deeplink: url?.absoluteString ?? "") - deeplinkCallback?(url) + deeplinkCallback?(url.toDeeplinkValue()) } } diff --git a/AffiseAttributionLib/Classes/deeplink/DeeplinkValue.swift b/AffiseAttributionLib/Classes/deeplink/DeeplinkValue.swift new file mode 100644 index 0000000..32e5022 --- /dev/null +++ b/AffiseAttributionLib/Classes/deeplink/DeeplinkValue.swift @@ -0,0 +1,81 @@ +import Foundation + + +@objc +public class DeeplinkValue: NSObject { + + public let deeplink: String + public let scheme: String? + public let host: String? + public let path: String? + public let parameters: [String:[String]] + + init( + deeplink: String, + scheme: String?, + host: String?, + path: String?, + parameters: [String:[String]] + ) { + self.deeplink = deeplink + self.scheme = scheme + self.host = host + self.path = path + self.parameters = parameters + } +} + +extension DeeplinkValue { + public override var description: String { "DeeplinkValue(scheme=\"\(self.scheme ?? "")\", host=\"\(self.host ?? "")\", path=\"\(self.path ?? "")\", parameters=\(self.parameters))" } +} + + +extension Optional where Wrapped == URL { + + func toDeeplinkValue() -> DeeplinkValue { + guard let self = self else { + return DeeplinkValue( + deeplink: "", + scheme: nil, + host: nil, + path: nil, + parameters: [:] + ) + } + + var host: String? + var path: String? + var parameters: [String:[String]] = [:] + + if #available(iOS 16.0, *) { +#if swift(>=5.7.1) + host = self.host(percentEncoded: false) + path = self.path(percentEncoded: false) +#else + host = self.host + path = self.path +#endif + } else { + host = self.host + path = self.path + } + + let component = URLComponents(string: self.absoluteString) + for item in component?.queryItems ?? [] { + let value = item.value?.removingPercentEncoding + if parameters[item.name] != nil { + parameters[item.name]?.append(value ?? "") + } else { + parameters[item.name] = [value ?? ""] + } + } + + return DeeplinkValue( + deeplink: self.absoluteString, + scheme: self.scheme, + host: host, + path: path, + parameters: parameters + ) + } +} diff --git a/AffiseAttributionLib/Classes/modules/AffiseModuleApi.swift b/AffiseAttributionLib/Classes/modules/AffiseModuleApi.swift new file mode 100644 index 0000000..e873de5 --- /dev/null +++ b/AffiseAttributionLib/Classes/modules/AffiseModuleApi.swift @@ -0,0 +1,3 @@ +public protocol AffiseModuleApi { + +} diff --git a/AffiseAttributionLib/Classes/modules/AffiseModuleManager.swift b/AffiseAttributionLib/Classes/modules/AffiseModuleManager.swift index 4f9bb7e..1b46618 100644 --- a/AffiseAttributionLib/Classes/modules/AffiseModuleManager.swift +++ b/AffiseAttributionLib/Classes/modules/AffiseModuleManager.swift @@ -78,4 +78,8 @@ internal class AffiseModuleManager { callback(module) } } + + func api(_ module: AffiseModules) -> AffiseModuleApi? { + return getModule(module) as? AffiseModuleApi + } } diff --git a/AffiseAttributionLib/Classes/modules/AffiseModules.swift b/AffiseAttributionLib/Classes/modules/AffiseModules.swift index 1602306..e137a65 100644 --- a/AffiseAttributionLib/Classes/modules/AffiseModules.swift +++ b/AffiseAttributionLib/Classes/modules/AffiseModules.swift @@ -4,12 +4,14 @@ import Foundation @objc public enum AffiseModules: Int { case Advertising + case Link case Status case Subscription internal var enumValue: String { switch self { case .Advertising: return "Advertising" + case .Link: return "Link" case .Status: return "Status" case .Subscription: return "Subscription" } diff --git a/AffiseAttributionLib/Classes/modules/link/AffiseLink.swift b/AffiseAttributionLib/Classes/modules/link/AffiseLink.swift new file mode 100644 index 0000000..af9717e --- /dev/null +++ b/AffiseAttributionLib/Classes/modules/link/AffiseLink.swift @@ -0,0 +1,33 @@ +import Foundation + +class AffiseLink { + private static var api: AffiseLinkApi? { + get { + if module != nil { + return module + } else { + module = Affise.Module.getApi(.Link) as? AffiseLinkApi + } + return module + } + } + + private static var module: AffiseLinkApi? = nil + + /** + * Module Link url Resolve + */ + public static func linkResolve(_ url: String, _ callback: @escaping AffiseLinkCallback) { + api?.linkResolve(url, callback) ?? callback("") + } +} + +extension Affise.Module { + + /** + * Module Link url Resolve + */ + public static func linkResolve(_ url: String, _ callback: @escaping AffiseLinkCallback) { + AffiseLink.linkResolve(url, callback) + } +} diff --git a/AffiseAttributionLib/Classes/modules/link/AffiseLinkApi.swift b/AffiseAttributionLib/Classes/modules/link/AffiseLinkApi.swift new file mode 100644 index 0000000..67e1a8a --- /dev/null +++ b/AffiseAttributionLib/Classes/modules/link/AffiseLinkApi.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol AffiseLinkApi: AffiseModuleApi { + func linkResolve(_ url: String, _ callback: @escaping AffiseLinkCallback) +} diff --git a/AffiseAttributionLib/Classes/modules/link/AffiseLinkCallback.swift b/AffiseAttributionLib/Classes/modules/link/AffiseLinkCallback.swift new file mode 100644 index 0000000..de99895 --- /dev/null +++ b/AffiseAttributionLib/Classes/modules/link/AffiseLinkCallback.swift @@ -0,0 +1,3 @@ +import Foundation + +public typealias AffiseLinkCallback = (_ result: String) -> Void \ No newline at end of file diff --git a/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscription.swift b/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscription.swift new file mode 100644 index 0000000..c8c7d19 --- /dev/null +++ b/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscription.swift @@ -0,0 +1,41 @@ +class AffiseSubscription { + private static var api: AffiseSubscriptionApi? { + get { + if module != nil { + return module + } else { + module = Affise.Module.getApi(.Subscription) as? AffiseSubscriptionApi + } + return module + } + } + + private static var module: AffiseSubscriptionApi? = nil + + public static func hasSubscriptionModule() -> Bool { + return api != nil + } + + public static func fetchProducts(_ productsIds: [String], _ callback: @escaping AffiseResultCallback) { + api?.fetchProducts(productsIds, callback) + } + + public static func purchase(_ product: AffiseProduct, _ type: AffiseProductType?, _ callback: @escaping AffiseResultCallback) { + api?.purchase(product, type, callback) + } +} + + +extension Affise.Module { + public static func fetchProducts(_ productsIds: [String], _ callback: @escaping AffiseResultCallback) { + AffiseSubscription.fetchProducts(productsIds, callback) + } + + public static func purchase(_ product: AffiseProduct, _ type: AffiseProductType?, _ callback: @escaping AffiseResultCallback) { + AffiseSubscription.purchase(product, type, callback) + } + + public static func hasSubscriptionModule() -> Bool { + return AffiseSubscription.hasSubscriptionModule() + } +} diff --git a/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscriptionApi.swift b/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscriptionApi.swift index b837f22..25c964f 100644 --- a/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscriptionApi.swift +++ b/AffiseAttributionLib/Classes/modules/subscription/AffiseSubscriptionApi.swift @@ -1,8 +1,8 @@ import Foundation -public protocol AffiseSubscriptionApi { - static func fetchProducts(_ productsIds: [String], _ callback: @escaping AffiseResultCallback) +public protocol AffiseSubscriptionApi : AffiseModuleApi { + func fetchProducts(_ productsIds: [String], _ callback: @escaping AffiseResultCallback) - static func purchase(_ product: AffiseProduct, _ type: AffiseProductType?, _ callback: @escaping AffiseResultCallback) + func purchase(_ product: AffiseProduct, _ type: AffiseProductType?, _ callback: @escaping AffiseResultCallback) } diff --git a/AffiseAttributionLib/Classes/network/CloudRepositoryImpl.swift b/AffiseAttributionLib/Classes/network/CloudRepositoryImpl.swift index 21a42d9..b1d40cc 100644 --- a/AffiseAttributionLib/Classes/network/CloudRepositoryImpl.swift +++ b/AffiseAttributionLib/Classes/network/CloudRepositoryImpl.swift @@ -75,7 +75,8 @@ extension CloudRepositoryImpl: CloudRepository { method: .POST, data: converter.convert(from: data).toData(), timeout: TIMEOUT_SEND, - headers: createHeaders() + headers: createHeaders(), + redirect: true ) } } diff --git a/AffiseAttributionLib/Classes/network/HttpResponse.swift b/AffiseAttributionLib/Classes/network/HttpResponse.swift index 47a4981..eeb7f9c 100644 --- a/AffiseAttributionLib/Classes/network/HttpResponse.swift +++ b/AffiseAttributionLib/Classes/network/HttpResponse.swift @@ -5,11 +5,13 @@ public class HttpResponse: NSObject { public let code: Int public let message: String public let body: String? + public let headers: [String:[String]] - public init(_ code: Int, _ message: String, _ body: String? = nil) { + public init(_ code: Int, _ message: String, _ body: String? = nil, _ headers: [String:[String]] = [:]) { self.code = code self.message = message self.body = body + self.headers = headers } public override var description: String { diff --git a/AffiseAttributionLib/Classes/network/NetworkService.swift b/AffiseAttributionLib/Classes/network/NetworkService.swift index d0210d0..89c37f4 100644 --- a/AffiseAttributionLib/Classes/network/NetworkService.swift +++ b/AffiseAttributionLib/Classes/network/NetworkService.swift @@ -23,6 +23,7 @@ public protocol NetworkService { method: NetworkServiceMethod, data: Data?, timeout: TimeInterval, - headers: [String:String] + headers: [String:String], + redirect: Bool ) -> HttpResponse } diff --git a/AffiseAttributionLib/Classes/network/NetworkServiceImpl.swift b/AffiseAttributionLib/Classes/network/NetworkServiceImpl.swift index e9d1a9b..0799d4a 100644 --- a/AffiseAttributionLib/Classes/network/NetworkServiceImpl.swift +++ b/AffiseAttributionLib/Classes/network/NetworkServiceImpl.swift @@ -20,11 +20,13 @@ extension NetworkServiceImpl: NetworkService { method: NetworkServiceMethod, data: Data?, timeout: TimeInterval, - headers: [String:String] + headers: [String:String], + redirect: Bool = true ) -> HttpResponse { var responseCode: Int = 0 var responseMessage: String = "" var responseBody: String? = nil + var responseHeaders: [String:[String]] = [:] var request = URLRequest.init(url: httpsUrl) request.httpMethod = method.rawValue @@ -41,20 +43,23 @@ extension NetworkServiceImpl: NetworkService { var complete: Bool = false - let task = urlSession.dataTask(with: request) { responseData, response, error in + let task = urlSession.dataTask(redirect: redirect, with: request) { responseData, response, error in + let httpResponse = (response as? HTTPURLResponse) + responseBody = responseData?.toString() - responseCode = (response as? HTTPURLResponse)?.statusCode ?? 0 + responseCode = httpResponse?.statusCode ?? 0 responseMessage = error?.localizedDescription ?? "" - + responseHeaders = httpResponse?.allHeaders() ?? [:] + complete = true } task.resume() - + while !complete { Thread.sleep(forTimeInterval: 1) } - let httpResponse = HttpResponse(responseCode, responseMessage, responseBody) + let httpResponse = HttpResponse(responseCode, responseMessage, responseBody, responseHeaders) if let debugRequest = debugRequest { debugRequest(HttpRequest(httpsUrl, method, headers, data?.toString()), httpResponse) } diff --git a/AffiseAttributionLib/Classes/network/NoRedirect.swift b/AffiseAttributionLib/Classes/network/NoRedirect.swift new file mode 100644 index 0000000..a4f02a7 --- /dev/null +++ b/AffiseAttributionLib/Classes/network/NoRedirect.swift @@ -0,0 +1,39 @@ +import Foundation + + +internal class NoRedirect: NSObject, URLSessionTaskDelegate, @unchecked Sendable { + + private static let shared: NoRedirect = NoRedirect() + + static let urlSession: URLSession = shared.session + + private var session: URLSession = URLSession.shared + + private override init() { + super.init() + self.session = URLSession(configuration: .default, delegate: self, delegateQueue: nil) + } + + // URLSessionTaskDelegate method to handle redirection + func urlSession( + _ session: URLSession, + task: URLSessionTask, + willPerformHTTPRedirection response: HTTPURLResponse, + newRequest request: URLRequest, + completionHandler: @escaping (URLRequest?) -> Void + ) { + // Call the completion handler with nil to prevent the redirection from happening + completionHandler(nil) + } +} + + +internal extension URLSession { + func dataTask(redirect: Bool, with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionDataTask { + if redirect == true { + return self.dataTask(with: request, completionHandler: completionHandler) + } else { + return NoRedirect.urlSession.dataTask(with: request, completionHandler: completionHandler) + } + } +} diff --git a/AffiseAttributionLib/Classes/parameters/providers/init/AffSDKVersionProvider.swift b/AffiseAttributionLib/Classes/parameters/providers/init/AffSDKVersionProvider.swift index 3b4752a..f23fdfe 100644 --- a/AffiseAttributionLib/Classes/parameters/providers/init/AffSDKVersionProvider.swift +++ b/AffiseAttributionLib/Classes/parameters/providers/init/AffSDKVersionProvider.swift @@ -6,7 +6,7 @@ import Foundation class AffSDKVersionProvider: StringPropertyProvider { override func provide() -> String? { - return "1.6.32" + return "1.6.33" } public override func getOrder() -> Float { diff --git a/AffiseAttributionLib/Classes/utils/HttpUtils.swift b/AffiseAttributionLib/Classes/utils/HttpUtils.swift index b6f8dd2..0a9aadf 100644 --- a/AffiseAttributionLib/Classes/utils/HttpUtils.swift +++ b/AffiseAttributionLib/Classes/utils/HttpUtils.swift @@ -1,3 +1,40 @@ +import Foundation + + public func isHttpValid(_ responseCode: Int) -> Bool { return (200...399).contains(responseCode) } + + +public func isRedirect(_ responseCode: Int) -> Bool { + return (300...399).contains(responseCode) +} + + +extension HttpResponse { + public func isHttpValid() -> Bool { + return AffiseAttributionLib.isHttpValid(self.code) + } + + + public func isRedirect() -> Bool { + return AffiseAttributionLib.isRedirect(self.code) + } +} + + +extension HTTPURLResponse { + func allHeaders() -> [String: [String]] { + return Dictionary(uniqueKeysWithValues: self.allHeaderFields.map { k, v in + let key = String(describing: k) + var value = "" + if #available(iOS 13.0, *) { + value = self.value(forHTTPHeaderField: key) ?? "" + } else { + value = String(describing: v) + } + return ( key, value.components(separatedBy: "; ") ) + }) + } +} + diff --git a/AffiseAttributionLib/Classes/utils/StringUtils.swift b/AffiseAttributionLib/Classes/utils/StringUtils.swift index 691c14c..a7421b6 100644 --- a/AffiseAttributionLib/Classes/utils/StringUtils.swift +++ b/AffiseAttributionLib/Classes/utils/StringUtils.swift @@ -30,3 +30,13 @@ public extension String { return allSatisfy { $0.isWhitespace } } } + + +extension Optional where Wrapped == String { + + public var isNullOrBlank: Bool { + get { + return self?.isBlank ?? true + } + } +} diff --git a/AffiseInternal.podspec b/AffiseInternal.podspec index b58a7c6..5290f10 100644 --- a/AffiseInternal.podspec +++ b/AffiseInternal.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |spec| spec.name = "AffiseInternal" - spec.version = ENV['LIB_VERSION'] || "1.6.32" + spec.version = ENV['LIB_VERSION'] || "1.6.33" spec.summary = "Affise Internal library" spec.description = "Affise Internal wrapper library for crossplatform" spec.homepage = "https://github.com/affise/sdk-ios" diff --git a/AffiseInternal/Classes/AffiseApiMethod.swift b/AffiseInternal/Classes/AffiseApiMethod.swift index 77dacc3..6329d1b 100644 --- a/AffiseInternal/Classes/AffiseApiMethod.swift +++ b/AffiseInternal/Classes/AffiseApiMethod.swift @@ -24,13 +24,10 @@ public enum AffiseApiMethod: Int { case GET_RANDOM_USER_ID case GET_RANDOM_DEVICE_ID case GET_PROVIDERS - case MODULE_START - case GET_MODULES_INSTALLED case IS_FIRST_RUN // callback case GET_REFERRER_CALLBACK case GET_REFERRER_VALUE_CALLBACK - case GET_STATUS_CALLBACK case REGISTER_DEEPLINK_CALLBACK case SKAD_REGISTER_ERROR_CALLBACK case SKAD_POSTBACK_ERROR_CALLBACK @@ -39,6 +36,17 @@ public enum AffiseApiMethod: Int { case DEBUG_NETWORK_CALLBACK // builder case AFFISE_BUILDER + //////////////////////////////////////// + // modules + //////////////////////////////////////// + case MODULE_START + case GET_MODULES_INSTALLED + case GET_STATUS_CALLBACK + // Link Module + case MODULE_LINK_LINK_RESOLVE_CALLBACK + //////////////////////////////////////// + // modules + //////////////////////////////////////// var value: String { switch self { @@ -63,19 +71,26 @@ public enum AffiseApiMethod: Int { case .GET_RANDOM_USER_ID: return "get_random_user_id" case .GET_RANDOM_DEVICE_ID: return "get_random_device_id" case .GET_PROVIDERS: return "get_providers" - case .MODULE_START: return "module_start" - case .GET_MODULES_INSTALLED: return "get_modules_installed" case .IS_FIRST_RUN: return "is_first_run" // callback case .GET_REFERRER_CALLBACK: return "get_referrer_callback" case .GET_REFERRER_VALUE_CALLBACK: return "get_referrer_value_callback" - case .GET_STATUS_CALLBACK: return "get_status_callback" case .REGISTER_DEEPLINK_CALLBACK: return "register_deeplink_callback" case .SKAD_REGISTER_ERROR_CALLBACK: return "skad_register_error_callback" case .SKAD_POSTBACK_ERROR_CALLBACK: return "skad_postback_error_callback" case .DEBUG_VALIDATE_CALLBACK: return "debug_validate_callback" case .DEBUG_NETWORK_CALLBACK: return "debug_network_callback" case .AFFISE_BUILDER: return "affise_builder" + //////////////////////////////////////// + // modules + //////////////////////////////////////// + case .MODULE_START: return "module_start" + case .GET_MODULES_INSTALLED: return "get_modules_installed" + case .GET_STATUS_CALLBACK: return "get_status_callback" + case .MODULE_LINK_LINK_RESOLVE_CALLBACK: return "module_link_link_resolve_callback" + //////////////////////////////////////// + // modules + //////////////////////////////////////// } } } diff --git a/AffiseInternal/Classes/AffiseApiWrapper.swift b/AffiseInternal/Classes/AffiseApiWrapper.swift index 77d3704..3f8e82f 100644 --- a/AffiseInternal/Classes/AffiseApiWrapper.swift +++ b/AffiseInternal/Classes/AffiseApiWrapper.swift @@ -86,12 +86,9 @@ public class AffiseApiWrapper: NSObject { case .GET_RANDOM_USER_ID: callGetRandomUserId(api, map: map, result: result) case .GET_RANDOM_DEVICE_ID: callGetRandomDeviceId(api, map: map, result: result) case .GET_PROVIDERS: callGetProviders(api, map: map, result: result) - case .MODULE_START: callModuleStart(api, map: map, result: result) - case .GET_MODULES_INSTALLED: callGetModulesInstalled(api, map: map, result: result) case .IS_FIRST_RUN: callIsFirstRun(api, map: map, result: result) case .GET_REFERRER_CALLBACK: callGetReferrer(api, map: map, result: result) case .GET_REFERRER_VALUE_CALLBACK: callGetReferrerValue(api, map: map, result: result) - case .GET_STATUS_CALLBACK: callGetStatusCallback(api, map: map, result: result) case .REGISTER_DEEPLINK_CALLBACK: callRegisterDeeplinkCallback(api, map: map, result: result) case .SKAD_REGISTER_ERROR_CALLBACK: callSkadRegisterErrorCallback(api, map: map, result: result) case .SKAD_POSTBACK_ERROR_CALLBACK: callSkadPostbackErrorCallback(api, map: map, result: result) @@ -99,6 +96,16 @@ public class AffiseApiWrapper: NSObject { case .DEBUG_NETWORK_CALLBACK: callDebugNetworkCallback(api, map: map, result: result) case .AFFISE_BUILDER: callAffiseBuilder(api, map: map, result: result) + //////////////////////////////////////// + // modules + //////////////////////////////////////// + case .MODULE_START: callModuleStart(api, map: map, result: result) + case .GET_MODULES_INSTALLED: callGetModulesInstalled(api, map: map, result: result) + case .GET_STATUS_CALLBACK: callGetStatusCallback(api, map: map, result: result) + case .MODULE_LINK_LINK_RESOLVE_CALLBACK: callModuleLinkLinkResolveCallback(api, map: map, result: result) + //////////////////////////////////////// + // modules + //////////////////////////////////////// default: result?.notImplemented() } @@ -293,25 +300,6 @@ public class AffiseApiWrapper: NSObject { result?.success(data) } - private func callModuleStart(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { - guard let name: String = map.opt(api) else { - result?.error("api [\(api.method)]: value not set") - return - } - - guard let module: AffiseModules = AffiseModules.from(name) else { - result?.error("api [\(api.method)]: no valid AffiseModules") - return - } - - result?.success(Affise.moduleStart(module)) - } - - private func callGetModulesInstalled(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { - let data: [String] = Affise.getModulesInstalled().map { $0.description } - result?.success(data) - } - private func callIsFirstRun(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { result?.success(Affise.isFirstRun()) } @@ -360,42 +348,16 @@ public class AffiseApiWrapper: NSObject { result?.success(nil) } - private func callGetStatusCallback(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { - guard let uuid: String = map.opt(UUID) else { - result?.error("api [\(api.method)]: no valid Callback UUID") - return - } - - guard let name: String = map.opt(api) else { - result?.error("api [\(api.method)]: value not set") - return - } - - guard let module: AffiseModules = AffiseModules.from(name) else { - result?.error("api [\(api.method)]: no valid AffiseModules") - return - } - - Affise.getStatus(module) { status in - let data: [String: Any?] = [ - self.UUID: uuid, - api.method: status.toListOfMap(), - ] - self.callback?(api.method, data) - } - result?.success(nil) - } - private func callRegisterDeeplinkCallback(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { - guard let uuid: String = map.opt(UUID) else { - result?.error("api [\(api.method)]: no valid Callback UUID") - return - } - - Affise.registerDeeplinkCallback { uri in + Affise.registerDeeplinkCallback { value in let data: [String: Any?] = [ - self.UUID: uuid, - api.method: uri?.absoluteString ?? "", + api.method: [ + "deeplink": value.deeplink, + "scheme": value.scheme as Any, + "host": value.host as Any, + "path": value.path as Any, + "parameters": value.parameters, + ], ] self.callback?(api.method, data) } @@ -483,4 +445,77 @@ public class AffiseApiWrapper: NSObject { private func callAffiseBuilder(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { affiseBuilder.call(api, map, result) } + + //////////////////////////////////////// + // modules + //////////////////////////////////////// + private func callModuleStart(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { + guard let name: String = map.opt(api) else { + result?.error("api [\(api.method)]: value not set") + return + } + + guard let module: AffiseModules = AffiseModules.from(name) else { + result?.error("api [\(api.method)]: no valid AffiseModules") + return + } + + result?.success(Affise.Module.moduleStart(module)) + } + + private func callGetModulesInstalled(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { + let data: [String] = Affise.Module.getModulesInstalled().map { $0.description } + result?.success(data) + } + + private func callGetStatusCallback(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { + guard let uuid: String = map.opt(UUID) else { + result?.error("api [\(api.method)]: no valid Callback UUID") + return + } + + guard let name: String = map.opt(api) else { + result?.error("api [\(api.method)]: value not set") + return + } + + guard let module: AffiseModules = AffiseModules.from(name) else { + result?.error("api [\(api.method)]: no valid AffiseModules") + return + } + + Affise.Module.getStatus(module) { status in + let data: [String: Any?] = [ + self.UUID: uuid, + api.method: status.toListOfMap(), + ] + self.callback?(api.method, data) + } + result?.success(nil) + } + + // Link Module + private func callModuleLinkLinkResolveCallback(_ api: AffiseApiMethod, map: [String: Any?], result: AffiseResult?) { + guard let uuid: String = map.opt(UUID) else { + result?.error("api [\(api.method)]: no valid Callback UUID") + return + } + + guard let url: String = map.opt(api) else { + result?.error("api [\(api.method)]: value not set") + return + } + + Affise.Module.linkResolve(url) { redirectUrl in + let data: [String: Any?] = [ + self.UUID: uuid, + api.method: redirectUrl, + ] + self.callback?(api.method, data) + } + result?.success(nil) + } + //////////////////////////////////////// + // modules + //////////////////////////////////////// } diff --git a/AffiseModule.podspec b/AffiseModule.podspec index e2ac950..9716759 100644 --- a/AffiseModule.podspec +++ b/AffiseModule.podspec @@ -8,7 +8,7 @@ Pod::Spec.new do |s| s.name = "AffiseModule" - s.version = ENV["LIB_VERSION"] || "1.6.32" + s.version = ENV["LIB_VERSION"] || "1.6.33" s.summary = "Affise Modules" s.description = "Affise module collection" s.homepage = "https://github.com/affise/sdk-ios" @@ -25,14 +25,18 @@ Pod::Spec.new do |s| s.dependency "AffiseAttributionLib", "#{s.version}" - s.subspec "Status" do |sub| - sub.source_files = "AffiseModule/Status/Classes/**/*.{swift}" - end - s.subspec "Advertising" do |sub| sub.source_files = "AffiseModule/Advertising/Classes/**/*.{swift}" end - + + s.subspec "Link" do |sub| + sub.source_files = "AffiseModule/Link/Classes/**/*.{swift}" + end + + s.subspec "Status" do |sub| + sub.source_files = "AffiseModule/Status/Classes/**/*.{swift}" + end + s.subspec "Subscription" do |sub| sub.source_files = "AffiseModule/Subscription/Classes/**/*.{swift}" sub.framework = "StoreKit" diff --git a/AffiseModule/Link/Classes/LinkModule.swift b/AffiseModule/Link/Classes/LinkModule.swift new file mode 100644 index 0000000..5399812 --- /dev/null +++ b/AffiseModule/Link/Classes/LinkModule.swift @@ -0,0 +1,24 @@ +import Foundation +import UIKit +import AffiseAttributionLib + +@objc(AffiseLinkModule) +public final class LinkModule: AffiseModule { + + private var useCase: LinkResolveUseCase? = nil + + public override func start() { + guard let networkService: NetworkService = get() else { return } + + useCase = LinkResolveUseCaseImpl( + networkService: networkService + ) + } +} + +extension LinkModule : AffiseLinkApi { + + public func linkResolve(_ url: String, _ callback: @escaping AffiseLinkCallback) { + useCase?.linkResolve(url, callback) ?? callback("") + } +} diff --git a/AffiseModule/Link/Classes/usecase/LinkResolveUseCase.swift b/AffiseModule/Link/Classes/usecase/LinkResolveUseCase.swift new file mode 100644 index 0000000..5aa6df6 --- /dev/null +++ b/AffiseModule/Link/Classes/usecase/LinkResolveUseCase.swift @@ -0,0 +1,7 @@ +import Foundation +import AffiseAttributionLib + + +protocol LinkResolveUseCase { + func linkResolve(_ url: String, _ callback: @escaping AffiseLinkCallback) +} diff --git a/AffiseModule/Link/Classes/usecase/LinkResolveUseCaseImpl.swift b/AffiseModule/Link/Classes/usecase/LinkResolveUseCaseImpl.swift new file mode 100644 index 0000000..8e554ef --- /dev/null +++ b/AffiseModule/Link/Classes/usecase/LinkResolveUseCaseImpl.swift @@ -0,0 +1,59 @@ +import AffiseAttributionLib +import Foundation + +class LinkResolveUseCaseImpl { + + private let MAX_REDIRECT_COUNT: Int = 10 + private let HEADER_LOCATION: String = "Location" + + let networkService: NetworkService + + init( + networkService: NetworkService + ) { + self.networkService = networkService + } + + private func resolve(_ url: String, _ callback: @escaping AffiseLinkCallback, _ maxSteps: Int) { + //Create request + let response = createRequest(url) + //Has redirect status + if (response.isRedirect() && maxSteps > 0) { + //Get first non blank location url + let redirectUrl = response.headers[HEADER_LOCATION]?.first { !$0.isBlank } + if !redirectUrl.isNullOrBlank { + //Resolve redirect url + resolve(redirectUrl ?? url, callback, maxSteps - 1) + } else { + //Return final url + callback(url) + } + } else { + //Return final url + callback(url) + } + } + + + private func createRequest(_ url: String) -> HttpResponse { + guard let httpsUrl = url.toURL() else { return HttpResponse(0, "") } + // Create request + return networkService.executeRequest( + httpsUrl: httpsUrl, + method: .GET, + data: nil, + timeout: 0, + headers: [:], + redirect: false + ) + } +} + +extension LinkResolveUseCaseImpl: LinkResolveUseCase { + func linkResolve(_ url: String, _ callback: @escaping AffiseLinkCallback) { + DispatchQueue.global(qos: .background).async { [weak self] in + guard let self = self else { return } + self.resolve(url, callback, self.MAX_REDIRECT_COUNT) + } + } +} diff --git a/AffiseModule/Status/Classes/usecase/CheckStatusUseCaseImpl.swift b/AffiseModule/Status/Classes/usecase/CheckStatusUseCaseImpl.swift index dda0418..cc18c00 100644 --- a/AffiseModule/Status/Classes/usecase/CheckStatusUseCaseImpl.swift +++ b/AffiseModule/Status/Classes/usecase/CheckStatusUseCaseImpl.swift @@ -41,7 +41,8 @@ internal class CheckStatusUseCaseImpl { method: .POST, data: converter.convert(from: providers).toData(), timeout: TIMEOUT_SEND, - headers: CloudConfig.headers + headers: CloudConfig.headers, + redirect: true ) } } diff --git a/AffiseModule/Subscription/Classes/AffiseExt.swift b/AffiseModule/Subscription/Classes/AffiseExt.swift deleted file mode 100644 index 5503559..0000000 --- a/AffiseModule/Subscription/Classes/AffiseExt.swift +++ /dev/null @@ -1,14 +0,0 @@ -import Foundation -import AffiseAttributionLib - - -extension Affise: AffiseSubscriptionApi { - - public static func fetchProducts(_ ids: [String], _ callback: @escaping AffiseResultCallback) { - SubscriptionModule.fetchProducts(ids, callback) - } - - public static func purchase(_ product: AffiseProduct, _ type: AffiseProductType? = nil, _ callback: @escaping AffiseResultCallback) { - SubscriptionModule.purchase(product, type, callback) - } -} diff --git a/AffiseModule/Subscription/Classes/SubscriptionModule.swift b/AffiseModule/Subscription/Classes/SubscriptionModule.swift index 229ebb3..1e3cd7d 100644 --- a/AffiseModule/Subscription/Classes/SubscriptionModule.swift +++ b/AffiseModule/Subscription/Classes/SubscriptionModule.swift @@ -5,30 +5,21 @@ import AffiseAttributionLib @objc(AffiseSubscriptionModule) public final class SubscriptionModule: AffiseModule { - - private(set) static var instance: SubscriptionModule? = nil lazy var storeManager: StoreManager = StoreManager() - override public func start() { - SubscriptionModule.instance = self + override public func start() { } } extension SubscriptionModule: AffiseSubscriptionApi { - public static func fetchProducts(_ productsIds: [String], _ callback: @escaping AffiseResultCallback) { - guard let module = instance else { - return callback(.failure(AffiseSubscriptionError.notInitialized)) - } - module.storeManager.fetchProducts(productsIds, callback) + public func fetchProducts(_ productsIds: [String], _ callback: @escaping AffiseResultCallback) { + storeManager.fetchProducts(productsIds, callback) } - public static func purchase(_ product: AffiseProduct, _ type: AffiseProductType? = nil, _ callback: @escaping AffiseResultCallback) { - guard let module = instance else { - return callback(.failure(AffiseSubscriptionError.notInitialized)) - } - module.storeManager.purchase(product, type, callback) + public func purchase(_ product: AffiseProduct, _ type: AffiseProductType? = nil, _ callback: @escaping AffiseResultCallback) { + storeManager.purchase(product, type, callback) } } diff --git a/AffiseSKAdNetwork.podspec b/AffiseSKAdNetwork.podspec index 14725d6..0772cb2 100644 --- a/AffiseSKAdNetwork.podspec +++ b/AffiseSKAdNetwork.podspec @@ -5,7 +5,7 @@ Pod::Spec.new do |spec| spec.name = "AffiseSKAdNetwork" - spec.version = ENV['LIB_VERSION'] || "1.6.32" + spec.version = ENV['LIB_VERSION'] || "1.6.33" spec.summary = "AffiseSKAdNetwork iOS library" spec.description = "Affise library for StoreKit Ad Network (SKAdNetwork)" spec.homepage = "https://github.com/affise/sdk-ios" diff --git a/CHANGELOG.md b/CHANGELOG.md index 29c8332..1aaec81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,23 @@ # Changelog +## [1.6.33] - 2024-06-28 + +### Added + +- New module `Link`. + +### Changed + +- Update `registerDeeplinkCallback` change uri to convenient values. +- Update `Affise.getStatus` moved to `Affise.Module.getStatus`. +- Update `Affise.moduleStart` moved to `Affise.Module.moduleStart`. +- Update `Affise.getModulesInstalled` moved to `Affise.Module.getModulesInstalled`. + ## [1.6.32] - 2024-05-28 ### Removed -- Result for failed `sendEvents`. +- Result for failed `sendNow`. ### Changed @@ -49,6 +62,7 @@ - Improve `AffiseModuleManager` - Update for demo app +[1.6.33]: https://github.com/affise/sdk-ios/compare/1.6.32...1.6.33 [1.6.32]: https://github.com/affise/sdk-ios/compare/1.6.31...1.6.32 [1.6.31]: https://github.com/affise/sdk-ios/compare/1.6.30...1.6.31 [1.6.30]: https://github.com/affise/sdk-ios/compare/1.6.29...1.6.30 diff --git a/Package.swift b/Package.swift index 7cd8f1c..02ed205 100644 --- a/Package.swift +++ b/Package.swift @@ -11,6 +11,7 @@ let package = Package( products: [ .library(name: "AffiseAttributionLib", targets: ["AffiseAttributionLib"]), .library(name: "AffiseModuleAdvertising", targets: ["AffiseModuleAdvertising"]), + .library(name: "AffiseModuleLink", targets: ["AffiseModuleLink"]), .library(name: "AffiseModuleStatus", targets: ["AffiseModuleStatus"]), .library(name: "AffiseModuleSubscription", targets: ["AffiseModuleSubscription"]), .library(name: "AffiseSKAdNetwork", targets: ["AffiseSKAdNetwork", "AffiseInternalWrapperObjC"]), @@ -37,6 +38,12 @@ let package = Package( path: "AffiseModule/Advertising", sources: [ "Classes" ] ), + .target( + name: "AffiseModuleLink", + dependencies: ["AffiseAttributionLib"], + path: "AffiseModule/Link", + sources: [ "Classes" ] + ), .target( name: "AffiseModuleStatus", dependencies: ["AffiseAttributionLib"], diff --git a/README.md b/README.md index 4b259bb..c8492d6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,14 @@ # Affise Attribution iOS Library +[Change Log](CHANGELOG.md) + | Pod | Version | | ---- |:-------:| -| `AffiseAttributionLib` | [`1.6.32`](https://github.com/CocoaPods/Specs/tree/master/Specs/a/9/3/AffiseAttributionLib) | -| `AffiseSKAdNetwork` | [`1.6.32`](https://github.com/CocoaPods/Specs/tree/master/Specs/3/6/f/AffiseSKAdNetwork) | -| `AffiseModule/Advertising` | [`1.6.32`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | -| `AffiseModule/Status` | [`1.6.32`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | +| `AffiseAttributionLib` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/a/9/3/AffiseAttributionLib) | +| `AffiseSKAdNetwork` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/3/6/f/AffiseSKAdNetwork) | +| `AffiseModule/Advertising` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | +| `AffiseModule/Link` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | +| `AffiseModule/Status` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | - [Affise Attribution iOS Library](#affise-attribution-ios-library) - [Description](#description) @@ -16,7 +19,8 @@ - [Initialize](#initialize) - [Domain](#domain) - [Modules](#modules) - - [Advertising](#advertising) + - [Module Advertising](#module-advertising) + - [Module Link](#module-link) - [Requirements](#requirements) - [StoreKit Ad Network](#storekit-ad-network) - [Features](#features) @@ -76,20 +80,22 @@ To add the SDK using Cocoapods, specify the version you want to use in your Podf ```ruby # Affise SDK library -pod 'AffiseAttributionLib', '~> 1.6.32' +pod 'AffiseAttributionLib', '~> 1.6.33' # Affise modules -pod 'AffiseModule/Advertising', '~> 1.6.32' -pod 'AffiseModule/Status', '~> 1.6.32' +pod 'AffiseModule/Advertising', '~> 1.6.33' +pod 'AffiseModule/Link', '~> 1.6.33' +pod 'AffiseModule/Status', '~> 1.6.33' ``` Get source directly from GitHub ```ruby # Affise SDK library -pod 'AffiseAttributionLib', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.32' +pod 'AffiseAttributionLib', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.33' # Affise modules -pod 'AffiseModule/Advertising', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.32' -pod 'AffiseModule/Status', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.32' +pod 'AffiseModule/Advertising', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.33' +pod 'AffiseModule/Link', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.33' +pod 'AffiseModule/Status', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.33' ``` ### Integrate as Swift Package Manager @@ -134,7 +140,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { } ``` -For `objective-c` use: +For `objective-c` use: > Demo app [AppDelegate.m](example/app-obj-c/app-obj-c/AppDelegate.m) @@ -191,37 +197,50 @@ Affise | Module | Version | Start | | ------------- |:------------------------------------------------------------------------------------:|----------| -| `Advertising` | [`1.6.32`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | `Manual` | -| `Status` | [`1.6.32`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | `Auto` | +| `Advertising` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | `Manual` | +| `Link` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | `Auto` | +| `Status` | [`1.6.33`](https://github.com/CocoaPods/Specs/tree/master/Specs/0/3/d/AffiseModule/) | `Auto` | If module start type is `manual`, then call: ```swift -Affise.moduleStart(.Advertising) +Affise.Module.moduleStart(.Advertising) ``` Get list of installed modules: ```swift -Affise.getModulesInstalled() +Affise.Module.getModulesInstalled() ``` -#### Advertising +#### Module Advertising This module required to Use [`IDFA`](https://developer.apple.com/documentation/adsupport/asidentifiermanager/advertisingidentifier) (Identifier for advertisers) > **Warning** -> +> > πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯ > > Module Advertising requires `NSUserTrackingUsageDescription` key in `info.plist` > -> Application will crash if key not present -> +> Application **will crash** if key not present +> > πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯πŸŸ₯ Open `info.plist` and add key `NSUserTrackingUsageDescription` with string value. For more information [read requirements](#requirements) +#### Module Link + +Return last url in chan of redirection + +πŸŸ₯Support MAX 10 redirectionsπŸŸ₯ + +```swift +Affise.Module.linkResolve("SITE_WITH_REDIRECTION") { redirectUrl in + // handle redirect url +} +``` + ### Requirements Affise Advertising module uses `AppTrackingTransparency` framework to get `advertisingIdentifier` @@ -244,14 +263,14 @@ To add the SDK using Cocoapods, specify the version you want to use in your Podf ```ruby # Wrapper for StoreKit Ad Network -pod 'AffiseSKAdNetwork', '~> 1.6.32' +pod 'AffiseSKAdNetwork', '~> 1.6.33' ``` Get source directly from GitHub ```ruby # Wrapper for StoreKit Ad Network -pod 'AffiseSKAdNetwork', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.32' +pod 'AffiseSKAdNetwork', :git => 'https://github.com/affise/sdk-ios.git', :tag => '1.6.33' ``` For `swift` use: @@ -445,7 +464,7 @@ AddToCartEvent() // handle event send success }) { errorResponse in // handle event send failed - // Warning: event is NOT cached for later send + // πŸŸ₯WarningπŸŸ₯: event is NOT cached for later send } ``` @@ -790,9 +809,16 @@ To integrate deeplink support you need: ```swift Affise.settings(affiseAppId:affiseAppId, secretKey:secretKey).start(app:app, launchOptions: launchOptions) Affise.registerDeeplinkCallback { url in - let component = URLComponents(string: url?.absoluteString ?? "") - let screen = component?.queryItems?.first(where: {$0.name == ""})?.value - if let screen = screen, screen == "" { + // full uri "scheme://host/path?parameters" + let deeplink = value.deeplink + + // separated for convenience + let scheme = value.scheme + let host = value.host + let path = value.path + let queryParametersMap = value.parameters + + if queryParametersMap[""].contains("") == true { // handle value } } @@ -833,6 +859,12 @@ Example: `YOUR_SCHEME://YOUR_DOMAIN` (`myapp://mydomain.com`) ``` +Test DeepLink via terminal command: + +```terminal +xcrun simctl openurl booted "YOUR_SCHEME://YOUR_DOMAIN/somepath?param=1&list=some&list=other&list=1" +``` + ## AppLinks > **Warning** @@ -858,9 +890,16 @@ To integrate applink support you need: ```swift Affise.settings(affiseAppId:affiseAppId, secretKey:secretKey).start(app:app, launchOptions: launchOptions) Affise.registerDeeplinkCallback { url in - let component = URLComponents(string: url?.absoluteString ?? "") - let screen = component?.queryItems?.first(where: {$0.name == ""})?.value - if let screen = screen, screen == "" { + // full uri "scheme://host/path?parameters" + let deeplink = value.deeplink + + // separated for convenience + let scheme = value.scheme + let host = value.host + let path = value.path + let queryParametersMap = value.parameters + + if queryParametersMap[""].contains("") == true { // handle value } } @@ -895,6 +934,12 @@ Example: `https://YOUR_DOMAIN` (`https://mydomain.com`) ``` +Test DeepLink via terminal command: + +```terminal +xcrun simctl openurl booted "https://YOUR_DOMAIN/somepath?param=1&list=some&list=other&list=1" +``` + ## Get random user Id ```swift @@ -994,7 +1039,7 @@ In examples above `ReferrerKey.CLICK_ID` is used, but many others is available: ## Get module state ```swift -Affise.getStatus(AffiseModules.STATUS) { response in +Affise.Module.getStatus(AffiseModules.STATUS) { response in // handle status response }; ``` diff --git a/example/app/app/AppDelegate.swift b/example/app/app/AppDelegate.swift index b23edeb..d2d4db3 100644 --- a/example/app/app/AppDelegate.swift +++ b/example/app/app/AppDelegate.swift @@ -20,10 +20,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { .start(app: application, launchOptions: launchOptions) // Start Affise SDK // Deeplinks https://github.com/affise/sdk-ios#deeplinks - Affise.registerDeeplinkCallback { url in - let component = URLComponents(string: url?.absoluteString ?? "") - let screen = component?.queryItems?.first(where: {$0.name == "screen"})?.value - if let screen = screen, screen == "special_offer" { + Affise.registerDeeplinkCallback { [weak self] value in + self?.showAlert( + title: "Deeplink", + message: "\"\(value.deeplink)\"\n\n" + + "scheme: \"\(value.scheme ?? "")\"\n\n" + + "host: \"\(value.host ?? "")\"\n\n" + + "path: \"\(value.path ?? "")\"\n\n" + + "parameters: \(value.parameters)" + ) + + if value.parameters["screen"]?.contains("special_offer") == true { // handle value } } @@ -47,7 +54,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { // Affise.Debug.validate { status in // debugPrint("Affise: validate = \(status)") // } - + // Debug: network request/response Affise.Debug.network { (request, response) in // debugPrint("Affise: \(request)") @@ -86,5 +93,17 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func applicationWillTerminate(_ application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } + + func showAlert(title: String, message: String) { + guard let rootViewController = self.window?.rootViewController else { + return + } + + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) + let action = UIAlertAction(title: "OK", style: .default, handler: nil) + alertController.addAction(action) + + rootViewController.present(alertController, animated: true, completion: nil) + } } diff --git a/example/app/app/StoreView.swift b/example/app/app/StoreView.swift index fd844c1..c992d08 100644 --- a/example/app/app/StoreView.swift +++ b/example/app/app/StoreView.swift @@ -1,12 +1,5 @@ import SwiftUI import AffiseAttributionLib -#if canImport(AffiseModule) -// Cocopods module -import AffiseModule -#elseif canImport(AffiseModuleSubscription) -// SwiftPM or XCFramework module -import AffiseModuleSubscription -#endif import StoreKit @@ -56,6 +49,22 @@ func titleByType(_ type: AffiseProductType) -> String { @available(iOS 13.0, *) struct StoreView: View { + @State var hasSubscriptionModule: Bool = Affise.Module.hasSubscriptionModule() + + var body: some View { + VStack { + if hasSubscriptionModule { + Products() + } else { + Text("No Subscription Module") + } + } + } +} + +@available(iOS 13.0, *) +struct Products: View { + @State var products: [AffiseProduct] = [] func typeMatch(_ product: AffiseProduct, _ type: AffiseProductType) -> Bool { @@ -93,8 +102,7 @@ struct StoreView: View { } func initProducts() { - #if canImport(AffiseModule) || canImport(AffiseModuleSubscription) - Affise.fetchProducts(Product.allIds) { result in + Affise.Module.fetchProducts(Product.allIds) { result in switch result { case .failure(let error): print("\(error)") @@ -102,14 +110,12 @@ struct StoreView: View { products = result.products .sorted { $0.price ?? 0 < $1.price ?? 0 } - print("invalid ids: [\(result.invalidIds.joined(separator: ", "))]") + // print("invalid ids: [\(result.invalidIds.joined(separator: ", "))]") } } - #endif } } - @available(iOS 13.0, *) struct ProductRowView: View { @@ -155,8 +161,7 @@ struct ProductRowView: View { } func purchase(_ product: AffiseProduct) { - #if canImport(AffiseModule) || canImport(AffiseModuleSubscription) - Affise.purchase(product, Product.getType(product.productId)) { result in + Affise.Module.purchase(product, Product.getType(product.productId)) { result in switch result { case .failure(let error): print("\(error)") @@ -164,13 +169,12 @@ struct ProductRowView: View { print("\(purchasedInfo)") } } - #endif } } #if targetEnvironment(simulator) @available(iOS 13.0, *) -struct StoreView_Previews: PreviewProvider { +struct Products_Previews: PreviewProvider { static let products: [AffiseProduct] = [ AffiseProduct("Preview product 1", 0.01, "Test"), @@ -178,7 +182,7 @@ struct StoreView_Previews: PreviewProvider { ] static var previews: some View { - StoreView(products: products) + Products(products: products) } }