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

feat: tracking push metrics from js #152

Merged
merged 15 commits into from
Jul 5, 2023
2 changes: 1 addition & 1 deletion ios/CustomerioInAppMessaging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class CustomerioInAppMessaging: RCTEventEmitter {
* We are combining in-app events against single name so only one event is added.
*/
open override func supportedEvents() -> [String]! {
return [ "InAppEventListener" ]
return [CustomerioConstants.inAppEventListener]
}

/**
Expand Down
9 changes: 9 additions & 0 deletions ios/CustomerioPushMessaging.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#import <React/RCTBridgeModule.h>

@interface RCT_EXTERN_MODULE(CustomerioPushMessaging, NSObject)

RCT_EXTERN_METHOD(trackNotificationResponseReceived : (nonnull NSDictionary *) payload])

RCT_EXTERN_METHOD(trackNotificationReceived : (nonnull NSDictionary *) payload])

@end
32 changes: 32 additions & 0 deletions ios/CustomerioPushMessaging.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import CioInternalCommon
import CioMessagingPush


@objc(CustomerioPushMessaging)
class CustomerioPushMessaging: NSObject {

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

// Tracks `opened` push metrics when a push notification is interacted with.
@objc(trackNotificationResponseReceived:)
func trackNotificationResponseReceived(payload: NSDictionary) {
trackPushMetrics(payload: payload, event: .opened)
}

// Tracks `delivered` push metrics when a push notification is received.
@objc(trackNotificationReceived:)
func trackNotificationReceived(payload: NSDictionary) {

trackPushMetrics(payload: payload, event: .delivered)
}

private func trackPushMetrics(payload: NSDictionary, event : Metric) {
guard let deliveryId = payload[CustomerioConstants.CioDeliveryId] as? String, let deviceToken = payload[CustomerioConstants.CioDeliveryToken] as? String else
{return}

MessagingPush.shared.trackMetric(deliveryID: deliveryId, event: event, deviceToken: deviceToken)
}
}
5 changes: 5 additions & 0 deletions ios/CustomerioReactnative.m
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,9 @@ @interface RCT_EXTERN_MODULE(CustomerioReactnative, NSObject)

RCT_EXTERN_METHOD(getPushPermissionStatus: (RCTPromiseResolveBlock) resolver
rejecter:(RCTPromiseRejectBlock)rejecter)

RCT_EXTERN_METHOD(trackNotificationResponseReceived : (nonnull NSDictionary *) payload])

RCT_EXTERN_METHOD(trackNotificationReceived : (nonnull NSDictionary *) payload])

@end
49 changes: 25 additions & 24 deletions ios/CustomerioReactnative.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import CioTracking
import CioInternalCommon
import CioMessagingInApp
import UserNotifications
import CioMessagingPush

enum PushPermissionStatus: String, CaseIterable {
case denied
Expand All @@ -27,31 +28,31 @@ class CustomerioReactnative: NSObject {
@objc(initialize:configData:packageConfig:)
func initialize(env: Dictionary<String, AnyHashable>, configData: Dictionary<String, AnyHashable>, packageConfig: Dictionary<String, AnyHashable>) -> Void {

guard let siteId = env["siteId"] as? String, let apiKey = env["apiKey"] as? String, let region = env["region"] as? String, let organizationId = env["organizationId"] as? String else {
guard let siteId = env[CustomerioConstants.siteId] as? String, let apiKey = env[CustomerioConstants.apiKey] as? String, let region = env[CustomerioConstants.region] as? String else {
return
}

guard let pversion = packageConfig["version"] as? String, let source = packageConfig["source"] as? String else {
guard let pversion = packageConfig[CustomerioConstants.version] as? String, let source = packageConfig[CustomerioConstants.source] as? String else {
return
}

var sdkSource = SdkWrapperConfig.Source.reactNative
if source.lowercased() == "expo" {
if source.lowercased() == CustomerioConstants.expo {
sdkSource = SdkWrapperConfig.Source.expo
}

CustomerIO.initialize(siteId: siteId, apiKey: apiKey, region: Region.getLocation(from: region)) { config in
config._sdkWrapperConfig = SdkWrapperConfig(source: sdkSource, version: pversion )
config.autoTrackDeviceAttributes = configData["autoTrackDeviceAttributes"] as! Bool
config.logLevel = CioLogLevel.getLogValue(for: configData["logLevel"] as! Int)
config.autoTrackPushEvents = configData["autoTrackPushEvents"] as! Bool
config.backgroundQueueMinNumberOfTasks = configData["backgroundQueueMinNumberOfTasks"] as! Int
config.backgroundQueueSecondsDelay = configData["backgroundQueueSecondsDelay"] as! Seconds
if let trackingApiUrl = configData["trackingApiUrl"] as? String, !trackingApiUrl.isEmpty {
config.autoTrackDeviceAttributes = configData[CustomerioConstants.autoTrackDeviceAttributes] as! Bool
config.logLevel = CioLogLevel.getLogValue(for: configData[CustomerioConstants.logLevel] as! Int)
config.autoTrackPushEvents = configData[CustomerioConstants.autoTrackPushEvents] as! Bool
config.backgroundQueueMinNumberOfTasks = configData[CustomerioConstants.bgQMinTasks] as! Int
config.backgroundQueueSecondsDelay = configData[CustomerioConstants.bgQSecondsDelay] as! Seconds
if let trackingApiUrl = configData[CustomerioConstants.trackingApiUrl] as? String, !trackingApiUrl.isEmpty {
config.trackingApiUrl = trackingApiUrl
}
}
if let isEnableInApp = configData["enableInApp"] as? Bool, isEnableInApp {
if let isEnableInApp = configData[CustomerioConstants.enableInApp] as? Bool, isEnableInApp {
initializeInApp()
}

Expand Down Expand Up @@ -154,7 +155,7 @@ class CustomerioReactnative: NSObject {
self.requestPushAuthorization(options: options) { permissionStatus in

guard let status = permissionStatus as? Bool else {
reject("[CIO]", "Error requesting push notification permission.", permissionStatus as? Error)
reject(CustomerioConstants.cioTag, CustomerioConstants.showPromptFailureError, permissionStatus as? Error)
return
}
resolve(status ? PushPermissionStatus.granted.value : PushPermissionStatus.denied.value)
Expand All @@ -179,12 +180,12 @@ class CustomerioReactnative: NSObject {
) {
let current = UNUserNotificationCenter.current()
var notificationOptions : UNAuthorizationOptions = [.alert]
if let ios = options["ios"] as? [String: Any] {
if let ios = options[CustomerioConstants.platformiOS] as? [String: Any] {

if let soundOption = ios["sound"] as? Bool, soundOption {
if let soundOption = ios[CustomerioConstants.sound] as? Bool, soundOption {
notificationOptions.insert(.sound)
}
if let bagdeOption = ios["badge"] as? Bool, bagdeOption {
if let bagdeOption = ios[CustomerioConstants.badge] as? Bool, bagdeOption {
notificationOptions.insert(.badge)
}
}
Expand Down Expand Up @@ -227,35 +228,35 @@ class CustomerioReactnative: NSObject {
extension CustomerioReactnative: InAppEventListener {
private func sendEvent(eventType: String, message: InAppMessage, actionValue: String? = nil, actionName: String? = nil) {
var body = [
"eventType": eventType,
"messageId": message.messageId,
"deliveryId": message.deliveryId
CustomerioConstants.eventType: eventType,
CustomerioConstants.messageId: message.messageId,
CustomerioConstants.deliveryId: message.deliveryId
]
if let actionValue = actionValue {
body["actionValue"] = actionValue
body[CustomerioConstants.actionValue] = actionValue
}
if let actionName = actionName {
body["actionName"] = actionName
body[CustomerioConstants.actionName] = actionName
}
CustomerioInAppMessaging.shared?.sendEvent(
withName: "InAppEventListener",
withName: CustomerioConstants.inAppEventListener,
body: body
)
}

func messageShown(message: InAppMessage) {
sendEvent(eventType: "messageShown", message: message)
sendEvent(eventType: CustomerioConstants.messageShown, message: message)
}

func messageDismissed(message: InAppMessage) {
sendEvent(eventType: "messageDismissed", message: message)
sendEvent(eventType: CustomerioConstants.messageDismissed, message: message)
}

func errorWithMessage(message: InAppMessage) {
sendEvent(eventType: "errorWithMessage", message: message)
sendEvent(eventType: CustomerioConstants.errorWithMessage, message: message)
}

func messageActionTaken(message: InAppMessage, actionValue: String, actionName: String) {
sendEvent(eventType: "messageActionTaken", message: message, actionValue: actionValue, actionName: actionName)
sendEvent(eventType: CustomerioConstants.messageActionTaken, message: message, actionValue: actionValue, actionName: actionName)
}
}
41 changes: 41 additions & 0 deletions ios/constants/CustomerioConstants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
struct CustomerioConstants {
// InApp Messaging
static let inAppEventListener = "InAppEventListener"
static let eventType = "eventType"
static let messageId = "messageId"
static let deliveryId = "deliveryId"
static let actionValue = "actionValue"
static let actionName = "actionName"
static let messageShown = "messageShown"
static let messageDismissed = "messageDismissed"
static let errorWithMessage = "errorWithMessage"
static let messageActionTaken = "messageActionTaken"

// Push Messaging
static let CioDeliveryId = "CIO-Delivery-ID"
static let CioDeliveryToken = "CIO-Delivery-Token"

// Tracking
static let siteId = "siteId"
static let apiKey = "apiKey"
static let region = "region"
static let version = "version"
static let source = "source"
static let expo = "expo"

static let autoTrackDeviceAttributes = "autoTrackDeviceAttributes"
static let logLevel = "logLevel"
static let autoTrackPushEvents = "autoTrackPushEvents"
static let bgQMinTasks = "backgroundQueueMinNumberOfTasks"
static let bgQSecondsDelay = "backgroundQueueSecondsDelay"
static let trackingApiUrl = "trackingApiUrl"
static let enableInApp = "enableInApp"

static let cioTag = "[CIO]"
static let showPromptFailureError = "Error requesting push notification permission."

static let platformiOS = "ios"
static let sound = "sound"
static let badge = "badge"

}
34 changes: 34 additions & 0 deletions src/CustomerIOPushMessaging.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,40 @@ class CustomerIOPushMessaging {
onBackgroundMessageReceived(message: any): Promise<boolean> {
return this.onMessageReceived(message, !message.notification);
}

/**
* Track push notifications metrics using this method.
* Call this method when a user interacts and taps open the push notification.
* @param payload Customer.io payload as received from the push notification
*/
trackNotificationResponseReceived(payload: Object) {
// Tracking push notification metrics on Android is handled automatically
// through the Google Services API, so there is no need to make a specific call for it.
// This method is specific to iOS and works as expected on Android without any additional intervention.
if (payload == null || this.isAndroid()) {
return
}
PushMessagingNative.trackNotificationResponseReceived(payload)
}

/**
* Track push notifications metrics using this method.
* Call this method when a push notification is received.
* @param payload Customer.io payload as received from the push notification
*/
trackNotificationReceived(payload: Object) {
// Tracking push notification metrics on Android is handled automatically
// through the Google Services API, so there is no need to make a specific call for it.
// This method is specific to iOS and works as expected on Android without any additional intervention.
if (payload == null || this.isAndroid()) {
return
}
PushMessagingNative.trackNotificationReceived(payload)
}

isAndroid() : boolean {
return Platform.OS == "android"
}
}

export { CustomerIOPushMessaging };