Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: in-app messaging fixes and improvements #342

Merged
merged 5 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
8 changes: 6 additions & 2 deletions Apps/APN/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ target 'SampleApp' do
# Flags change depending on the env values.
flags = get_default_flags()

pod 'customerio-reactnative/apn', :path => '../node_modules/customerio-reactnative'
# TODO: Remove this once iOS Native SDK is released and revert to original code
install_non_production_ios_sdk_git_branch(branch_name: 'feature/react-native-cdp', is_app_extension: false, push_service: "apn")
# pod 'customerio-reactnative/apn', :path => '../node_modules/customerio-reactnative'
# install_non_production_ios_sdk_local_path(local_path: '~/code/customerio-ios/', is_app_extension: false, push_service: "apn")
# install_non_production_ios_sdk_git_branch(branch_name: 'main', is_app_extension: false, push_service: "apn")

Expand Down Expand Up @@ -49,7 +51,9 @@ target 'SampleApp' do
end

target 'NotificationServiceExtension' do
pod 'customerio-reactnative-richpush/apn', :path => '../node_modules/customerio-reactnative'
# TODO: Remove this once iOS Native SDK is released and revert to original code
install_non_production_ios_sdk_git_branch(branch_name: 'feature/react-native-cdp', is_app_extension: true, push_service: "apn")
# pod 'customerio-reactnative-richpush/apn', :path => '../node_modules/customerio-reactnative'
# install_non_production_ios_sdk_local_path(local_path: '~/code/customerio-ios/', is_app_extension: true, push_service: "apn")
# install_non_production_ios_sdk_git_branch(branch_name: 'main', is_app_extension: true, push_service: "apn")
end
1 change: 0 additions & 1 deletion Apps/APN/src/services/CustomerIOService.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,6 @@ export const requestPushNotificationsPermission = (options) => {
return CustomerIO.pushMessaging.showPromptForPushNotifications(options);
};

// TODO: Implement this method when inapp feature is added
export const registerInAppEventListener = () => {
const logInAppEvent = (name, params) => {
console.log(`in-app message: ${name}, params: `, params);
Expand Down
8 changes: 6 additions & 2 deletions Apps/FCM/ios/Podfile
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,9 @@ target 'FCMSampleApp' do
:app_path => "#{Pod::Config.instance.installation_root}/.."
)

pod 'customerio-reactnative/fcm', :path => '../node_modules/customerio-reactnative'
# TODO: Remove this once iOS Native SDK is released and revert to original code
install_non_production_ios_sdk_git_branch(branch_name: 'feature/react-native-cdp', is_app_extension: false, push_service: "fcm")
# pod 'customerio-reactnative/fcm', :path => '../node_modules/customerio-reactnative'
# install_non_production_ios_sdk_local_path(local_path: '~/code/customerio-ios/', is_app_extension: false, push_service: "fcm")
# install_non_production_ios_sdk_git_branch(branch_name: 'levi/v2-multiple-push-handlers', is_app_extension: false, push_service: "fcm")

Expand All @@ -61,7 +63,9 @@ target 'FCMSampleApp' do
end

target 'NotificationServiceExtension' do
pod 'customerio-reactnative-richpush/fcm', :path => '../node_modules/customerio-reactnative'
# TODO: Remove this once iOS Native SDK is released and revert to original code
install_non_production_ios_sdk_git_branch(branch_name: 'feature/react-native-cdp', is_app_extension: true, push_service: "fcm")
# pod 'customerio-reactnative-richpush/fcm', :path => '../node_modules/customerio-reactnative'
# install_non_production_ios_sdk_local_path(local_path: '~/code/customerio-ios/', is_app_extension: true, push_service: "fcm")
# install_non_production_ios_sdk_git_branch(branch_name: 'levi/v2-multiple-push-handlers', is_app_extension: true, push_service: "fcm")
end
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import io.customer.sdk.data.model.Region
class RNCIOInAppMessaging(
private val reactContext: ReactApplicationContext,
) : ReactContextBaseJavaModule(reactContext), InAppEventListener {
override fun getName(): String = "CustomerioInAppMessaging"
override fun getName(): String = "CioRctInAppMessaging"

private val inAppMessagingModule: ModuleMessagingInApp?
get() = kotlin.runCatching { CustomerIO.instance().inAppMessaging() }.getOrNull()
Expand Down
34 changes: 16 additions & 18 deletions ios/wrappers/CioRctWrapper.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import CioInternalCommon
import CioDataPipelines
import CioMessagingInApp
import CioMessagingPush
Expand All @@ -8,26 +9,26 @@ import React
class CioRctWrapper: NSObject {

@objc var moduleRegistry: RCTModuleRegistry!
private var logger: CioLogger!
private let logger: CioInternalCommon.Logger = DIGraphShared.shared.logger

@objc
func initialize(_ configJson: AnyObject, logLevel: String) {
do {
logger = CioLoggerWrapper.getInstance(moduleRegistry: moduleRegistry, logLevel: CioLogLevel(rawValue: logLevel) ?? .none)
guard let sdkConfig = configJson as? [String: Any?] else {
logger.error("Initializing Customer.io SDK failed with error: Invalid config format")
return
}

logger.debug("Initializing Customer.io SDK with config: \(configJson)")
let sdkConfigBuilder = try SDKConfigBuilder.create(from: sdkConfig)
CustomerIO.initialize(withConfig: sdkConfigBuilder.build())

logger.debug("\(CustomerioConstants.initializeWithConfigMessage) \(configJson)")
let rtcConfig = try RCTCioConfig.from(configJson)
let cioInitConfig = cioInitializeConfig(from: rtcConfig, logLevel: logLevel)
CustomerIO.initialize(withConfig: cioInitConfig.cdp)
if let inAppConfig = cioInitConfig.inApp {
// In app initializes UIView(s) which would crash if run from non-UI queues
DispatchQueue.main.async {
MessagingInApp.initialize(withConfig: inAppConfig)
MessagingInApp.shared.setEventListener(self)
}
if let inAppConfig = try? MessagingInAppConfigBuilder.build(from: sdkConfig) {
MessagingInApp.initialize(withConfig: inAppConfig)
MessagingInApp.shared.setEventListener(self)
}
} catch {
logger.error("\(CustomerioConstants.initializationFailedError) \(error)")
logger.error("Initializing Customer.io SDK failed with error: \(error)")
}
}

Expand All @@ -40,7 +41,7 @@ class CioRctWrapper: NSObject {
} else if codableTraits != nil {
CustomerIO.shared.identify(traits: codableTraits!)
} else {
logger.error(CustomerioConstants.noIdOrTraitError)
logger.error("Provide id or traits to identify a user profile.")
}
}

Expand Down Expand Up @@ -93,13 +94,10 @@ extension CioRctWrapper: InAppEventListener {
if let actionName = actionName {
body[CustomerioConstants.actionName] = actionName
}
// TODO: Add when inapp feature is implemented
/*
CustomerioInAppMessaging.shared?.sendEvent(
CioRctInAppMessaging.shared?.sendEvent(
withName: CustomerioConstants.inAppEventListener,
body: body
)
*/
}

func messageShown(message: InAppMessage) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#import <React/RCTEventEmitter.h>

@interface RCT_EXTERN_MODULE(CustomerioInAppMessaging, RCTEventEmitter)
@interface RCT_EXTERN_REMAP_MODULE(CioRctInAppMessaging, CioRctInAppMessaging, RCTEventEmitter)

RCT_EXTERN_METHOD(supportedEvents)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,19 @@ import Foundation
import React
import CioMessagingInApp

@objc(CustomerioInAppMessaging)
class CustomerioInAppMessaging: RCTEventEmitter {
@objc(CioRctInAppMessaging)
class CioRctInAppMessaging: RCTEventEmitter {
// React Native modules should be initialized late as they will be created and initialized automatically
public static var shared: CustomerioInAppMessaging?
public static var shared: CioRctInAppMessaging?

@objc override static func requiresMainQueueSetup() -> Bool {
false /// false because our native module's initialization does not require access to UIKit
}

override init() {
super.init()
CustomerioInAppMessaging.shared = self

Self.shared = self
}

/**
Expand Down
78 changes: 20 additions & 58 deletions ios/wrappers/logging/CioLoggingEmitter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
import React
import CioInternalCommon

typealias CioLogger = CioInternalCommon.Logger

@objc(CioLoggingEmitter)
class CioLoggingEmitter: RCTEventEmitter {

Expand All @@ -16,6 +14,22 @@ class CioLoggingEmitter: RCTEventEmitter {
@objc static override func requiresMainQueueSetup() -> Bool {
return true // Return true if the module must be initialized on the main queue
}

override init() {
super.init()

DIGraphShared.shared.logger.setLogDispatcher { [weak self] level, message in
guard let self else { return }

emit(level: level, message: message)
}
}

deinit {
// Clear log dispatcher if the emitter has been deallocated
DIGraphShared.shared.logger.setLogDispatcher(nil)
}

override func startObserving() {
hasObservers = true
}
Expand All @@ -31,66 +45,14 @@ class CioLoggingEmitter: RCTEventEmitter {
override var methodQueue: dispatch_queue_t! {
DispatchQueue(label: Self.moduleName())
}
}


class CioLoggerWrapper: CioLogger {
private(set) var logLevel: CioLogLevel
private weak var moduleRegistry: RCTModuleRegistry?

static func getInstance (moduleRegistry: RCTModuleRegistry, logLevel: CioLogLevel) -> CioLogger {
if let wrapper = DIGraphShared.shared.logger as? Self {
wrapper.logLevel = logLevel
wrapper.moduleRegistry = moduleRegistry
return wrapper
}

let wrapper = CioLoggerWrapper(moduleRegistry: moduleRegistry, logLevel: logLevel)
DIGraphShared.shared.override(value: wrapper, forType: CioLogger.self)
return wrapper
}

private init(moduleRegistry: RCTModuleRegistry, logLevel: CioLogLevel?) {
self.logLevel = logLevel ?? .none
self.moduleRegistry = moduleRegistry
}

func setLogLevel(_ level: CioLogLevel) {
logLevel = level
private var emitter: CioLoggingEmitter? {
moduleRegistry?.module(forName: "CioLoggingEmitter") as? CioLoggingEmitter
}

func debug(_ message: String) {
emit(message, level: .debug)
}

func info(_ message: String) {
emit(message, level: .info)
}

func error(_ message: String) {
emit(message, level: .error)
}

private func emit(_ message: String, level: CioLogLevel) {
if shouldEmit(level: level), let emitter, emitter.hasObservers {
private func emit(level: CioLogLevel, message: String) {
if let emitter, emitter.hasObservers {
emitter.sendEvent(withName: CioLoggingEmitter.eventName, body: ["logLevel": level.rawValue, "message": message])
}
}

private func shouldEmit(level: CioLogLevel) -> Bool {
switch self.logLevel {
case .none: return false
case .error:
return level == .error
case .info:
return level == .error || level == .info
case .debug:
return true
}
}

private var emitter: CioLoggingEmitter? {
moduleRegistry?.module(forName: "CioLoggingEmitter") as? CioLoggingEmitter
}

}
58 changes: 58 additions & 0 deletions ios/wrappers/utils/AnyCodable.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// To convert dictionary to Codable
struct AnyCodable: Codable {
mrehan27 marked this conversation as resolved.
Show resolved Hide resolved
let value: Any

init(_ value: Any) {
self.value = value
}

init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()

if let value = try? container.decode(String.self) {
self.value = value
} else if let value = try? container.decode(Double.self) {
self.value = value
} else if let value = try? container.decode(Int.self) {
self.value = value
} else if let value = try? container.decode(Bool.self) {
self.value = value
} else if let value = try? container.decode([String: AnyCodable].self) {
self.value = value.mapValues { $0.value }
} else if let value = try? container.decode([AnyCodable].self) {
self.value = value.map { $0.value }
} else if container.decodeNil() {
self.value = NSNull()
} else {
throw DecodingError.typeMismatch(AnyCodable.self, DecodingError.Context(
codingPath: decoder.codingPath,
debugDescription: "Unsupported type"
))
}
}

func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()

if let value = value as? String {
try container.encode(value)
} else if let value = value as? Int {
try container.encode(value)
} else if let value = value as? Double {
try container.encode(value)
} else if let value = value as? Bool {
try container.encode(value)
} else if let value = value as? [String: Any] {
try container.encode(value.mapValues { AnyCodable($0) })
} else if let value = value as? [Any] {
try container.encode(value.map { AnyCodable($0) })
} else if value is NSNull {
try container.encodeNil()
} else {
throw EncodingError.invalidValue(value, EncodingError.Context(
codingPath: encoder.codingPath,
debugDescription: "Unsupported type"
))
}
}
mrehan27 marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading