Skip to content

Commit

Permalink
feat: tracking push metrics from js (#152)
Browse files Browse the repository at this point in the history
  • Loading branch information
ami-aman committed Jul 5, 2023
1 parent f0a0a82 commit 6f51703
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 25 deletions.
2 changes: 1 addition & 1 deletion ios/CustomerioInAppMessaging.swift
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
@@ -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
@@ -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
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
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
@@ -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
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"

Check warning on line 95 in src/CustomerIOPushMessaging.tsx

View workflow job for this annotation

GitHub Actions / eslint results

src/CustomerIOPushMessaging.tsx#L95

[eqeqeq] Expected '===' and instead saw '=='.
}
}

export { CustomerIOPushMessaging };

0 comments on commit 6f51703

Please sign in to comment.