Skip to content

Commit

Permalink
Merge branch 'main' of github.com:customerio/customerio-ios
Browse files Browse the repository at this point in the history
  • Loading branch information
scotttwittrockcio committed Nov 13, 2024
2 parents 49c7e02 + c8e1a44 commit d76eb0a
Show file tree
Hide file tree
Showing 20 changed files with 234 additions and 109 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [3.6.0](https://github.com/customerio/customerio-ios/compare/3.5.1...3.6.0) (2024-11-13)

### Features

* support for large in-app messages ([#831](https://github.com/customerio/customerio-ios/issues/831)) ([3731f68](https://github.com/customerio/customerio-ios/commit/3731f685a19bd1e9840c53ab3bcd70e37deac182))

## [3.5.1](https://github.com/customerio/customerio-ios/compare/3.5.0...3.5.1) (2024-10-25)

### Bug Fixes
Expand Down
2 changes: 1 addition & 1 deletion CustomerIO.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

Pod::Spec.new do |spec|
spec.name = "CustomerIO"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIOCommon.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIOCommon"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIODataPipelines.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIODataPipelines"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIOMessagingInApp.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIOMessagingInApp"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIOMessagingPush.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIOMessagingPush"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIOMessagingPushAPN.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIOMessagingPushAPN"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIOMessagingPushFCM.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIOMessagingPushFCM"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion CustomerIOTrackingMigration.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |spec|
spec.name = "CustomerIOTrackingMigration"
spec.version = "3.5.1" # Don't modify this line - it's automatically updated
spec.version = "3.6.0" # Don't modify this line - it's automatically updated
spec.summary = "Official Customer.io SDK for iOS."
spec.homepage = "https://github.com/customerio/customerio-ios"
spec.documentation_url = 'https://customer.io/docs/sdk/ios/'
Expand Down
2 changes: 1 addition & 1 deletion Sources/Common/Version.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ import Foundation
Keep this file as small as possible to make the automated updating script stable and easy to use.
*/
public enum SdkVersion {
public static let version: String = "3.5.1"
public static let version: String = "3.6.0"
}
114 changes: 68 additions & 46 deletions Sources/MessagingInApp/Gist/EngineWeb/EngineWeb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,55 +34,60 @@ public class EngineWeb: NSObject, EngineWebInstance {
webView
}

private let currentConfiguration: EngineWebConfiguration

public private(set) var currentRoute: String {
get {
_currentRoute
}
set {
_currentRoute = newValue
}
get { _currentRoute }
set { _currentRoute = newValue }
}

/// Initializes the EngineWeb instance with the given configuration, state, and message.
init(configuration: EngineWebConfiguration, state: InAppMessageState, message: Message) {
self.currentMessage = message
self.currentConfiguration = configuration

super.init()

_elapsedTimer.start(title: "Engine render for message: \(configuration.messageId)")
setupWebView()
injectJavaScriptListener()
loadMessage(with: state)
}

/// Sets up the properties and appearance of the WKWebView.
private func setupWebView() {
_elapsedTimer.start(title: "Engine render for message: \(currentConfiguration.messageId)")

webView.translatesAutoresizingMaskIntoConstraints = false
webView.navigationDelegate = self
webView.isOpaque = false
webView.backgroundColor = UIColor.clear
webView.scrollView.backgroundColor = UIColor.clear
webView.backgroundColor = .clear
webView.scrollView.backgroundColor = .clear
}

let js = "window.parent.postMessage = function(message) {webkit.messageHandlers.gist.postMessage(message)}"
/// Injects a JavaScript listener to handle messages from the web content.
private func injectJavaScriptListener() {
let js = """
window.addEventListener('message', function(event) {
webkit.messageHandlers.gist.postMessage(event.data);
});
"""
let messageHandlerScript = WKUserScript(source: js, injectionTime: .atDocumentEnd, forMainFrameOnly: false)

webView.configuration.userContentController.add(self, name: "gist")
webView.configuration.userContentController.addUserScript(messageHandlerScript)
}

if #available(iOS 11.0, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never
}
private func loadMessage(with state: InAppMessageState) {
let messageUrl = "\(state.environment.networkSettings.renderer)/index.html"
logger.logWithModuleTag("Rendering message with URL: \(messageUrl)", level: .debug)

if let jsonData = try? JSONEncoder().encode(configuration),
let jsonString = String(data: jsonData, encoding: .utf8),
let options = jsonString.data(using: .utf8)?.base64EncodedString()
.addingPercentEncoding(withAllowedCharacters: .alphanumerics) {
let url = "\(state.environment.networkSettings.renderer)/index.html?options=\(options)"
logger.logWithModuleTag("Loading URL: \(url)", level: .info)
if let link = URL(string: url) {
self._timeoutTimer = Timer.scheduledTimer(
timeInterval: 5.0,
target: self,
selector: #selector(forcedTimeout),
userInfo: nil,
repeats: false
)
let request = URLRequest(url: link)
webView.load(request)
}
if let url = URL(string: messageUrl) {
_timeoutTimer?.invalidate()
_timeoutTimer = Timer.scheduledTimer(timeInterval: 5.0, target: self, selector: #selector(forcedTimeout), userInfo: nil, repeats: false)
webView.load(URLRequest(url: url))
} else {
logger.logWithModuleTag("Invalid URL: \(messageUrl)", level: .error)
delegate?.error()
}
}

Expand All @@ -104,10 +109,7 @@ public class EngineWeb: NSObject, EngineWebInstance {

// swiftlint:disable cyclomatic_complexity
extension EngineWeb: WKScriptMessageHandler {
public func userContentController(
_ userContentController: WKUserContentController,
didReceive message: WKScriptMessage
) {
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
guard let dict = message.body as? [String: AnyObject],
let eventProperties = dict["gist"] as? [String: AnyObject],
let method = eventProperties["method"] as? String,
Expand All @@ -116,6 +118,10 @@ extension EngineWeb: WKScriptMessageHandler {
return
}

handleEngineEvent(engineEventMethod, eventProperties: eventProperties)
}

private func handleEngineEvent(_ engineEventMethod: EngineEvent, eventProperties: [String: AnyObject]) {
switch engineEventMethod {
case .bootstrapped:
_timeoutTimer?.invalidate()
Expand Down Expand Up @@ -150,27 +156,43 @@ extension EngineWeb: WKScriptMessageHandler {
}

// swiftlint:enable cyclomatic_complexity

extension EngineWeb: WKNavigationDelegate {
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {}
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
injectConfiguration(currentConfiguration)
}

private func injectConfiguration(_ configuration: EngineWebConfiguration) {
do {
let jsonData = try JSONEncoder().encode(["options": configuration])
guard let jsonString = String(data: jsonData, encoding: .utf8) else {
throw NSError(domain: "EngineWeb", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to create JSON string"])
}

let js = "window.postMessage(\(jsonString), '*');"

webView.evaluateJavaScript(js) { [weak self] _, error in
if let error = error {
self?.logger.logWithModuleTag("JavaScript execution error: \(error)", level: .error)
self?.delegate?.error()
} else {
self?.logger.logWithModuleTag("Configuration injected successfully", level: .error)
}
}
} catch {
logger.logWithModuleTag("Failed to encode configuration: \(error)", level: .error)
delegate?.error()
}
}

public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {
delegate?.error()
}

public func webView(
_ webView: WKWebView,
didFail navigation: WKNavigation!,
withError error: Error
) {
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
delegate?.error()
}

public func webView(
_ webView: WKWebView,
didFailProvisionalNavigation navigation: WKNavigation!,
withError error: Error
) {
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
delegate?.error()
}
}
4 changes: 2 additions & 2 deletions Sources/MessagingInApp/Gist/Network/NetworkSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ protocol NetworkSettings {
struct NetworkSettingsProduction: NetworkSettings {
let queueAPI = "https://gist-queue-consumer-api.cloud.gist.build"
let engineAPI = "https://engine.api.gist.build"
let renderer = "https://renderer.gist.build/2.0"
let renderer = "https://renderer.gist.build/3.0"
}

struct NetworkSettingsDevelopment: NetworkSettings {
let queueAPI = "https://gist-queue-consumer-api.cloud.dev.gist.build"
let engineAPI = "https://engine.api.dev.gist.build"
let renderer = "https://renderer.gist.build/2.0"
let renderer = "https://renderer.gist.build/3.0"
}

struct NetworkSettingsLocal: NetworkSettings {
Expand Down
17 changes: 17 additions & 0 deletions Sources/MessagingPush/Config/MessagingPushConfigBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public class MessagingPushConfigBuilder {

// configuration options for MessagingPushConfigOptions
private let cdpApiKey: String
private var region: Region = .US
private var autoFetchDeviceToken: Bool = true
private var autoTrackPushEvents: Bool = true
private var showPushAppInForeground: Bool = true
Expand All @@ -54,6 +55,17 @@ public class MessagingPushConfigBuilder {
self.cdpApiKey = cdpApiKey
}

/// Configures the region for NotificationServiceExtension for metric tracking
@discardableResult
@available(iOS, unavailable)
@available(visionOS, unavailable)
@available(iOSApplicationExtension, introduced: 13.0)
@available(visionOSApplicationExtension, introduced: 1.0)
public func region(_ region: Region) -> MessagingPushConfigBuilder {
self.region = region
return self
}

/// Configures the log level for NotificationServiceExtension, allowing customization of SDK log
/// verbosity to help setup and debugging
@discardableResult
Expand Down Expand Up @@ -94,6 +106,7 @@ public class MessagingPushConfigBuilder {
let configOptions = MessagingPushConfigOptions(
logLevel: logLevel,
cdpApiKey: cdpApiKey,
region: region,
autoFetchDeviceToken: autoFetchDeviceToken,
autoTrackPushEvents: autoTrackPushEvents,
showPushAppInForeground: showPushAppInForeground
Expand All @@ -106,6 +119,7 @@ public class MessagingPushConfigBuilder {
public extension MessagingPushConfigBuilder {
/// Constants used to map each of the options in MessagingPushConfigOptions.
enum Keys: String {
case region
case autoFetchDeviceToken
case autoTrackPushEvents
case showPushAppInForeground
Expand All @@ -116,6 +130,9 @@ public extension MessagingPushConfigBuilder {
static func build(from dictionary: [String: Any]) -> MessagingPushConfigOptions {
let builder = MessagingPushConfigBuilder()

if let region = dictionary[Keys.region.rawValue] as? String {
builder.region = Region.getRegion(from: region)
}
if let autoFetchDeviceToken = dictionary[Keys.autoFetchDeviceToken.rawValue] as? Bool {
builder.autoFetchDeviceToken(autoFetchDeviceToken)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import CioInternalCommon
public struct MessagingPushConfigOptions {
public let logLevel: CioLogLevel
public let cdpApiKey: String
public let region: Region
public let autoFetchDeviceToken: Bool
public let autoTrackPushEvents: Bool
public let showPushAppInForeground: Bool
Expand Down
4 changes: 3 additions & 1 deletion Sources/MessagingPush/RichPush/RichPushDeliveryTracker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import Foundation
class RichPushDeliveryTracker {
let httpClient: HttpClient
let logger: Logger
let region: Region

init(httpClient: HttpClient, logger: Logger) {
self.httpClient = httpClient
self.logger = logger
self.region = MessagingPush.moduleConfig.region
}

func trackMetric(token: String, event: Metric, deliveryId: String, timestamp: String? = nil, onComplete: @escaping (Result<Void, HttpRequestError>) -> Void) {
Expand All @@ -25,7 +27,7 @@ class RichPushDeliveryTracker {
let endpoint: CIOApiEndpoint = .trackPushMetricsCdp
guard let httpParams = HttpRequestParams(
endpoint: endpoint,
baseUrl: RichPushHttpClient.defaultAPIHost,
baseUrl: RichPushHttpClient.getDefaultApiHost(region: region),
headers: nil,
body: try? JSONSerialization.data(withJSONObject: properties)
) else {
Expand Down
15 changes: 12 additions & 3 deletions Sources/MessagingPush/RichPush/RichPushHttpClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ public class RichPushHttpClient: HttpClient {
[cioApiSession, publicSession]
}

private let region: Region

public func request(_ params: CioInternalCommon.HttpRequestParams, onComplete: @escaping (Result<Data, CioInternalCommon.HttpRequestError>) -> Void) {
httpRequestRunner
.request(
Expand Down Expand Up @@ -57,7 +59,7 @@ public class RichPushHttpClient: HttpClient {
}

func getSessionForRequest(url: URL) -> URLSession {
let cioApiHostname = URL(string: Self.defaultAPIHost)!.host
let cioApiHostname = URL(string: Self.getDefaultApiHost(region: region))!.host
let requestHostname = url.host
let isRequestToCIOApi = cioApiHostname == requestHostname

Expand Down Expand Up @@ -123,7 +125,7 @@ public class RichPushHttpClient: HttpClient {
self.httpRequestRunner = httpRequestRunner
self.jsonAdapter = jsonAdapter
self.logger = logger

self.region = MessagingPush.moduleConfig.region
self.publicSession = Self.getBasicSession()
self.cioApiSession = Self.getCIOApiSession(
key: MessagingPush.moduleConfig.cdpApiKey,
Expand All @@ -137,7 +139,14 @@ public class RichPushHttpClient: HttpClient {
}

extension RichPushHttpClient {
public static let defaultAPIHost = "https://cdp.customer.io/v1"
static func getDefaultApiHost(region: Region) -> String {
switch region {
case .US:
"https://cdp.customer.io/v1"
case .EU:
"https://cdp-eu.customer.io/v1"
}
}

static func authorizationHeaderForCdpApiKey(_ key: String) -> String {
var returnHeader = "\(key):"
Expand Down
Loading

0 comments on commit d76eb0a

Please sign in to comment.