Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file modified script.sh
100644 → 100755
Empty file.
1 change: 1 addition & 0 deletions swift-sdk/Core/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ enum Const {
static let visitorConsentTimestamp = "itbl_visitor_consent_timestamp"
static let isNotificationsEnabled = "itbl_isNotificationsEnabled"
static let hasStoredNotificationSetting = "itbl_hasStoredNotificationSetting"
static let networkLoggingEnabled = "itbl_network_logging_enabled"

static let attributionInfoExpiration = 24
}
Expand Down
5 changes: 5 additions & 0 deletions swift-sdk/Internal/HealthMonitor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,12 @@ class HealthMonitor {
let currentDate = dateProvider.currentDate
let apiCallRequest = apiCallRequest.addingCreatedAt(currentDate)
if let urlRequest = apiCallRequest.convertToURLRequest(sentAt: currentDate) {
ITBInfo("Attempting to send failed-to-schedule request directly for path: '\(apiCallRequest.getPath())'")
_ = RequestSender.sendRequest(urlRequest, usingSession: networkSession)
} else {
let endpoint = apiCallRequest.endpoint
let path = apiCallRequest.getPath()
ITBError("Failed to convert to URL request in health monitor - endpoint: '\(endpoint)', path: '\(path)'")
}
onError()
}
Expand Down
32 changes: 31 additions & 1 deletion swift-sdk/Internal/InternalIterableAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -964,7 +964,30 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {

private static func setApiEndpoint(apiEndPointOverride: String?, config: IterableConfig) -> String {
let apiEndPoint = config.dataRegion
return apiEndPointOverride ?? apiEndPoint
let endpoint = apiEndPointOverride ?? apiEndPoint

// Sanitize and validate endpoint
let sanitized = endpoint.trimmingCharacters(in: .whitespacesAndNewlines)

// Validate endpoint is a valid URL
if let url = URL(string: sanitized) {
if url.scheme == nil || url.host == nil {
ITBError("Invalid API endpoint - missing scheme or host: '\(sanitized)'")
}
} else {
ITBError("Invalid API endpoint - cannot create URL from: '\(sanitized)'")
}

// Check for common issues
if sanitized != endpoint {
ITBError("API endpoint contained whitespace, trimmed from '\(endpoint)' to '\(sanitized)'")
}

if sanitized.isEmpty {
ITBError("API endpoint is empty after sanitization")
}

return sanitized
}

init(apiKey: String,
Expand All @@ -983,6 +1006,7 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
networkSession = dependencyContainer.networkSession
notificationStateProvider = dependencyContainer.notificationStateProvider
localStorage = dependencyContainer.localStorage
NetworkHelper.isNetworkLoggingEnabled = localStorage.networkLoggingEnabled
inAppDisplayer = dependencyContainer.inAppDisplayer
urlOpener = dependencyContainer.urlOpener
notificationCenter = dependencyContainer.notificationCenter
Expand Down Expand Up @@ -1136,6 +1160,12 @@ final class InternalIterableAPI: NSObject, PushTrackerProtocol, AuthProvider {
requestHandler.getRemoteConfiguration().onSuccess { remoteConfiguration in
self.localStorage.offlineMode = remoteConfiguration.offlineMode
self.requestHandler.offlineMode = remoteConfiguration.offlineMode

if let enableNetworkLogging = remoteConfiguration.enableNetworkLogging {
self.localStorage.networkLoggingEnabled = enableNetworkLogging
NetworkHelper.isNetworkLoggingEnabled = enableNetworkLogging
}

ITBInfo("setting offlineMode: \(self.requestHandler.offlineMode)")
}.onError { error in
let offlineMode = self.requestHandler.offlineMode
Expand Down
40 changes: 36 additions & 4 deletions swift-sdk/Internal/IterableAPICallTaskProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ struct IterableAPICallTaskProcessor: IterableTaskProcessor {
let iterableRequest = decodedIterableRequest.addingCreatedAt(task.scheduledAt)

guard let urlRequest = iterableRequest.convertToURLRequest(sentAt: dateProvider.currentDate, processorType: .offline) else {
return IterableTaskError.createErroredFuture(reason: "could not convert to url request")
let endpoint = decodedIterableRequest.endpoint
let path = decodedIterableRequest.getPath()
let errorMessage = "Could not convert to URL request - endpoint: '\(endpoint)', path: '\(path)'"
ITBError(errorMessage)
return IterableTaskError.createErroredFuture(reason: errorMessage)
}

let result = Fulfill<IterableTaskResult, IterableTaskError>()
Expand All @@ -47,10 +51,38 @@ struct IterableAPICallTaskProcessor: IterableTaskProcessor {
private let dateProvider: DateProviderProtocol

private static func isNetworkUnavailable(sendRequestError: SendRequestError) -> Bool {
// Check for NSURLError codes that indicate network issues
if let nsError = sendRequestError.originalError as? NSError {
if nsError.domain == NSURLErrorDomain {
let networkErrorCodes: Set<Int> = [
NSURLErrorNotConnectedToInternet, // -1009
NSURLErrorNetworkConnectionLost, // -1005
NSURLErrorTimedOut, // -1001
NSURLErrorCannotConnectToHost, // -1004
NSURLErrorDNSLookupFailed, // -1006
NSURLErrorDataNotAllowed, // -1020 (cellular data disabled)
NSURLErrorInternationalRoamingOff // -1018
]
if networkErrorCodes.contains(nsError.code) {
ITBInfo("Network error detected: code=\(nsError.code), description=\(nsError.localizedDescription)")
return true
}
}
}

// Fallback to string check for other network-related errors
if let originalError = sendRequestError.originalError {
return originalError.localizedDescription.lowercased().contains("offline")
} else {
return false
let description = originalError.localizedDescription.lowercased()
let isNetworkError = description.contains("offline") ||
description.contains("network") ||
description.contains("internet") ||
description.contains("connection")
if isNetworkError {
ITBInfo("Network error detected via description: \(originalError.localizedDescription)")
}
return isNetworkError
}

return false
}
}
9 changes: 9 additions & 0 deletions swift-sdk/Internal/IterableRequest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ extension IterableRequest: Codable {
}
}

func getPath() -> String {
switch self {
case .get(let request):
return request.path
case .post(let request):
return request.path
}
}

private static let requestTypeGet = "get"
private static let requestTypePost = "post"
}
Expand Down
3 changes: 3 additions & 0 deletions swift-sdk/Internal/IterableRequestUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ struct IterableRequestUtil {
headers: [String: String]? = nil,
args: [String: String]? = nil) -> URLRequest? {
guard let url = getUrlComponents(forApiEndPoint: apiEndPoint, path: path, args: args)?.url else {
ITBError("Failed to create GET request URL")
return nil
}

Expand Down Expand Up @@ -53,6 +54,7 @@ struct IterableRequestUtil {
args: [String: String]? = nil,
body: Data? = nil) -> URLRequest? {
guard let url = getUrlComponents(forApiEndPoint: apiEndPoint, path: path, args: args)?.url else {
ITBError("Failed to create POST request URL")
return nil
}

Expand Down Expand Up @@ -88,6 +90,7 @@ struct IterableRequestUtil {
let endPointCombined = pathCombine(path1: apiEndPoint, path2: path)

guard var components = URLComponents(string: "\(endPointCombined)") else {
ITBError("Failed to create URLComponents - apiEndPoint: '\(apiEndPoint)', path: '\(path)', combined: '\(endPointCombined)'")
return nil
}

Expand Down
9 changes: 9 additions & 0 deletions swift-sdk/Internal/IterableUserDefaults.swift
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,14 @@ class IterableUserDefaults {
}
}

var networkLoggingEnabled: Bool {
get {
bool(withKey: .networkLoggingEnabled)
} set {
save(bool: newValue, withKey: .networkLoggingEnabled)
}
}

func getAttributionInfo(currentDate: Date) -> IterableAttributionInfo? {
(try? codable(withKey: .attributionInfo, currentDate: currentDate)) ?? nil
}
Expand Down Expand Up @@ -346,6 +354,7 @@ class IterableUserDefaults {

static let isNotificationsEnabled = UserDefaultsKey(value: Const.UserDefault.isNotificationsEnabled)
static let hasStoredNotificationSetting = UserDefaultsKey(value: Const.UserDefault.hasStoredNotificationSetting)
static let networkLoggingEnabled = UserDefaultsKey(value: Const.UserDefault.networkLoggingEnabled)
}
private struct Envelope: Codable {
let payload: Data
Expand Down
1 change: 1 addition & 0 deletions swift-sdk/Internal/Models.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Foundation

struct RemoteConfiguration: Codable, Equatable {
let offlineMode: Bool
let enableNetworkLogging: Bool?
}

struct Criteria: Codable {
Expand Down
52 changes: 52 additions & 0 deletions swift-sdk/Internal/Network/NetworkHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ extension NetworkError: LocalizedError {
struct NetworkHelper {
static let maxRetryCount = 5
static let retryDelaySeconds = 2
static var isNetworkLoggingEnabled = false

static func getData(fromUrl url: URL, usingSession networkSession: NetworkSessionProtocol) -> Pending<Data, Error> {
let fulfill = Fulfill<Data, Error>()
Expand Down Expand Up @@ -57,6 +58,11 @@ struct NetworkHelper {
usingSession networkSession: NetworkSessionProtocol) -> Pending<T, NetworkError> {

let requestId = IterableUtil.generateUUID()

if isNetworkLoggingEnabled {
logRequest(request, requestId: requestId)
}

#if NETWORK_DEBUG
print()
print("====================================================>")
Expand Down Expand Up @@ -94,6 +100,10 @@ struct NetworkHelper {
}

func handleSuccess(requestId: String, value: T) {
if isNetworkLoggingEnabled {
logSuccess(requestId: requestId, value: value)
}

#if NETWORK_DEBUG
print("request with id: \(requestId) successfully sent, response:")
print(value)
Expand All @@ -105,6 +115,10 @@ struct NetworkHelper {
if shouldRetry(error: error, retriesLeft: retriesLeft) {
retryRequest(requestId: requestId, request: request, error: error, retriesLeft: retriesLeft)
} else {
if isNetworkLoggingEnabled {
logFailure(requestId: requestId, error: error)
}

#if NETWORK_DEBUG
print("request with id: \(requestId) errored")
print(error)
Expand All @@ -119,6 +133,10 @@ struct NetworkHelper {
}

func retryRequest(requestId: String, request: URLRequest, error: NetworkError, retriesLeft: Int) {
if isNetworkLoggingEnabled {
logRetry(requestId: requestId, url: request.url?.absoluteString ?? "", attempt: maxRetryCount - retriesLeft + 1, error: error)
}

#if NETWORK_DEBUG
print("retry attempt: \(maxRetryCount-retriesLeft+1) for url: \(request.url?.absoluteString ?? "")")
print(error)
Expand Down Expand Up @@ -203,4 +221,38 @@ struct NetworkHelper {

return .success(data)
}

private static func logRequest(_ request: URLRequest, requestId: String) {
var message = "\n====================================================>\n"
message += "sending request: \(request)\n"
message += "requestId: \(requestId)\n"
if let headers = request.allHTTPHeaderFields {
message += "headers:\n\(headers)\n"
}
if let body = request.httpBody {
if let dict = try? JSONSerialization.jsonObject(with: body, options: []) {
message += "request body:\n\(dict)\n"
}
}
message += "====================================================>\n"
ITBInfo(message)
}

private static func logSuccess<T>(requestId: String, value: T) {
var message = "request with id: \(requestId) successfully sent, response:\n"
message += "\(value)"
ITBInfo(message)
}

private static func logFailure(requestId: String, error: NetworkError) {
var message = "request with id: \(requestId) errored\n"
message += "\(error)"
ITBError(message)
}

private static func logRetry(requestId: String, url: String, attempt: Int, error: NetworkError) {
var message = "retry attempt: \(attempt) for url: \(url)\n"
message += "\(error)"
ITBInfo(message)
}
}
8 changes: 8 additions & 0 deletions swift-sdk/Internal/Utilities/LocalStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,14 @@ struct LocalStorage: LocalStorageProtocol {
}
}

var networkLoggingEnabled: Bool {
get {
iterableUserDefaults.networkLoggingEnabled
} set {
iterableUserDefaults.networkLoggingEnabled = newValue
}
}

func getAttributionInfo(currentDate: Date) -> IterableAttributionInfo? {
iterableUserDefaults.getAttributionInfo(currentDate: currentDate)
}
Expand Down
2 changes: 2 additions & 0 deletions swift-sdk/Internal/Utilities/LocalStorageProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ protocol LocalStorageProtocol {

var hasStoredNotificationSetting: Bool { get set }

var networkLoggingEnabled: Bool { get set }

func getAttributionInfo(currentDate: Date) -> IterableAttributionInfo?

func save(attributionInfo: IterableAttributionInfo?, withExpiration expiration: Date?)
Expand Down
15 changes: 12 additions & 3 deletions swift-sdk/Internal/api-client/ApiClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,23 +84,32 @@ class ApiClient {

func send(iterableRequest: IterableRequest) -> Pending<SendRequestValue, SendRequestError> {
guard let urlRequest = convertToURLRequest(iterableRequest: iterableRequest) else {
return SendRequestError.createErroredFuture()
let path = iterableRequest.getPath()
let errorMessage = "Failed to create URL request for endpoint: '\(endpoint)', path: '\(path)'"
ITBError(errorMessage)
return SendRequestError.createErroredFuture(reason: errorMessage)
}

return RequestSender.sendRequest(urlRequest, usingSession: networkSession)
}

func sendWithoutCreatedAt(iterableRequest: IterableRequest) -> Pending<SendRequestValue, SendRequestError> {
guard let urlRequest = convertToURLRequestWithoutCreatedAt(iterableRequest: iterableRequest) else {
return SendRequestError.createErroredFuture()
let path = iterableRequest.getPath()
let errorMessage = "Failed to create URL request for endpoint: '\(endpoint)', path: '\(path)'"
ITBError(errorMessage)
return SendRequestError.createErroredFuture(reason: errorMessage)
}

return RequestSender.sendRequest(urlRequest, usingSession: networkSession)
}

func send<T>(iterableRequest: IterableRequest) -> Pending<T, SendRequestError> where T: Decodable {
guard let urlRequest = convertToURLRequest(iterableRequest: iterableRequest) else {
return SendRequestError.createErroredFuture()
let path = iterableRequest.getPath()
let errorMessage = "Failed to create URL request for endpoint: '\(endpoint)', path: '\(path)'"
ITBError(errorMessage)
return SendRequestError.createErroredFuture(reason: errorMessage)
}

return RequestSender.sendRequest(urlRequest, usingSession: networkSession)
Expand Down
2 changes: 2 additions & 0 deletions tests/common/MockLocalStorage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ class MockLocalStorage: LocalStorageProtocol {

var hasStoredNotificationSetting: Bool = false

var networkLoggingEnabled: Bool = false

func getAttributionInfo(currentDate: Date) -> IterableAttributionInfo? {
guard !MockLocalStorage.isExpired(expiration: attributionInfoExpiration, currentDate: currentDate) else {
return nil
Expand Down
Loading
Loading