Giter Site home page Giter Site logo

customerio / customerio-ios Goto Github PK

View Code? Open in Web Editor NEW
26.0 26.0 16.0 4.25 MB

Official Customer.io SDK for iOS. Track customers and send messages to your iOS app.

Home Page: https://customer.io/docs/sdk/ios/

License: MIT License

Swift 96.43% Shell 0.32% Makefile 0.44% Ruby 2.59% JavaScript 0.17% Objective-C 0.04%
customerio ios sdk swift

customerio-ios's People

Contributors

ahmed-ali avatar ahmed-cio avatar ami-aman avatar ami-ci avatar bernardgatt avatar dependabot[bot] avatar glosier avatar hownowstephen avatar joepurdy avatar levibostian avatar lj-cio avatar matt-frizzell avatar mrehan27 avatar nunofgs avatar semantic-release-bot avatar shahroz16 avatar stephen-pope-customer-io avatar xtreem88 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

customerio-ios's Issues

Could you please update FirebaseMessaging and FirebaseCore dependency to the latest version?

In my project I'm using FirebaseMessaging v9.6.0 but you are 8.10.0. So I have cocoapods config.

[!] CocoaPods could not find compatible versions for pod "FirebaseMessaging":
  In Podfile:
    customer_io (from `.symlinks/plugins/customer_io/ios`) was resolved to 0.0.1, which depends on
      CustomerIOMessagingPushFCM (= 1.2.0-beta.3) was resolved to 1.2.0-beta.3, which depends on
        FirebaseMessaging (~> 8.10.0)

    customer_io (from `.symlinks/plugins/customer_io/ios`) was resolved to 0.0.1, which depends on
      FirebaseMessaging (~> 9.6.0)

Could you please update it in your .podspec file?

In-App Message action iOS/Android Deep Link opens preview page in Safari

Version(s) of SDK bug appears: 1.2.0

Current Behavior

We use iOS/Android Deep Link action in the In-App message, but when the app is opened, and we get the in-app message, after pressing Yes! button which triggers the action, the app first opens the preview page in Safari.

Steps to Reproduce

Screen Shot 2022-10-14 at 1 11 01 PM

Screen Shot 2022-10-14 at 1 17 32 PM

Expected Behavior

The app shouldn't open the preview page in Safari but should handle dynamic links in the app immediately.

Any other info?
Also, we noticed some strange issue in the Customer IO console when creating an In-App message. When you try to save iOS/Android Deep Link action option for some item, it's not saved correctly, because when you refresh the page the action Navigate to a web page (New Tab) is displayed. I'm not sure if this is somehow related to the issue described above.

We also tried to turn the option Skip the app preview page for a dynamic link that we use in the Firebase console, but it's still not working, because then it opens the link in Safari which is meant for non-mobile devices.

Memory Leak

With Customer.io 2.5.0, enabled this way:

    CustomerIO.initialize(siteId: Constants.ThirdPartyConstants.cioSiteID,
                          apiKey: Constants.ThirdPartyConstants.cioAppKey,
                          region: .US) {
        $0.autoTrackScreenViews = true
    }

The memory is constantly growing - even while leaving the app without any user activity, and no UI updates.
image
While without Customer.io SDK enabled - the memory stays as expected:
image

https://derrickho328.medium.com/swift-try-memory-leak-b02983c47f01

Maybe related to this part of code.
image

Carthage support

Hello,

Does the SDK support installation via Carthage?
If no, would there any possiblity to add it?

InApp Messaging does not provide single instance usage

Hi,

Looking at version 1.12.0-beta1, I am wondering why there is no way to initialise a single instance of MessagingInApp (not using a singleton).
If you look at the documentation, it is possible to use a custom customerIO instance and for In App messaging it's not possible to pass the customerIO instance.

 customerIO = CustomerIO(
            siteId: "<mysiteId>",
            apiKey: "<apiKey>"
        )
        messagingPush = MessagingPush(customerIO: customerIO)

For InApp messaging, only singleton is possible:

        CustomerIO.initialize(siteId: "<mysiteId>", apiKey: "<apiKey>")
            
        CustomerIO.config {
            $0.logLevel = .debug
            $0.autoTrackScreenViews = true
        }
        MessagingInApp.shared.initialize(organizationId: "organizationId")

Rich push notifications not working with FCM.

I am not able to make the rich push notifications work with the documentation details provided here: https://customer.io/docs/sdk/ios/rich-push/

First the current stable version 1.0.3, is lacking methods when using FCM for rich methods. I've seen this is fixed in latest beta.

But still the following code is not showing me the image in the notification (only the standard title and body are shown):

Messaging.shared.didReceive(request, withContentHandler: contentHandler)

Note: the service is well started as I am able to modify the title or body manually

Notification payload:

{
    "message": {
        "apns": {
            "payload": {
                "CIO": {
                    "push": {
                        "link": "remote-habits://deep?message=hello&message2=world",
                        "image": "https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fpawsindoorssouthend.com%2Fwp-content%2Fuploads%2F2020%2F04%2Ftips-for-choosing-doggy-day-care.jpg"
                    }
                },
                "aps": {
                    "mutable-content": 1,
                    "alert": {
                        "title": "Title of your push goes here!",
                        "body": "Body of your push goes here!"
                    }
                }
            }
        }
    }
}

I am wondering if I need to initialise the CustomerIO instance within the service as well as it is independent from the app?

Any help appreciated.

Background queue not running on 30 second timer as intended.

SDK version: 1.0.0-alpha.26

Expected behavior

  1. If the background queue intends to start a timer, the queue starts the timer.
  2. When the background queue timer is finished, the background queue runs.

Actual behavior

  1. The background queue timer gets scheduled once. But after it gets scheduled once, it will not schedule again until the app is terminated for a fresh start.
  2. The background queue timer says it's scheduled for 30 seconds but 30 seconds later it does not run.

Crash DIGraph_SdkConfigStore_singleton_access

SDK version: 1.2.4

Environment: Production

Are logs available?
No, because we can not reproduce a crash in a debug area

Describe the bug
In our crashlytics we can see a lot of crashes from the customer io library.
Crash:

Crashed: DIGraph_SdkConfigStore_singleton_access
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000000

Now the app crashes on any screen, any user flow, we can see it in the crashlytics.

To Reproduce
Can not reproduce from our side

Expected behavior
An app should not crash.

Screenshots
Screenshot 2022-11-23 at 2 52 13 PM

issue_ca7686a2dbc4478891e440a0b082755c_crash_session_4192f76bfb2e45a8be413c7800cba7e2_DNE_0_v2_stacktrace.txt

The app crashes because of Customer IO Source Code

SDK version: 1.2.1

Environment: Development

Are logs available?
No

Describe the bug
Launching and killing the app several times - the app crashes appears.

To Reproduce
Launch and kill app with Customer IO multiple times.

Expected behavior
The app should not crash.

Screenshots
image

image

Additional context
Firebase, Netfox integrated.

Push metrics not being reported to the API. No HTTP request being made.

Version: 1.0.0-alpha.25

Reproduce:
Send yourself a rich push and click on it. Wait until background queue runs. You will see a stacktrace which says

Decode key not found. Key: CodingKeys(stringValue: "delivery_id", intValue: nil),
Json path: [], json: {"delivery_id":"RO7nBgAFAX5PajCwhzxlKAfAzMvgNA==","device_id":"c2b2c3398e803626d3bd565e24c9c07208bb0ad5907a1c6ab350fc7cc5545628","event":"opened","timestamp":1642009883}

Memory/CPU Hang in 2.0.2

SDK version: 2.0.2

Environment: Production

Are logs available?
In logs I see just theese

[CIO] (siteid:6cbe9) ℹ️ Customer.io SDK 2.0.2 initialized and ready to use for site id: xxx
[CIO] (siteid:6cbe9) ℹ️ registering device token xxx
[CIO] (siteid:6cbe9) ℹ️ adding queue task registerPushToken
[CIO] (siteid:6cbe9) ℹ️ queue met criteria to run automatically

Describe the bug
SDK is taking all available memory and CPU resources in version 2.0.2. App is then unusable. This is not happening in 2.0.1

To Reproduce
For me is enough just to launch app on device. In stack trace it seems to be failing when trying to update push token with customer.io

Here is stack trace I see on client.
Screenshot 2023-01-10 at 14 35 42

Opened event has never tracked

Hi,

I am using the 1.2.0-beta.1. Implemented and get Rich Notification work successfully. As the document, it said "If you already configured rich push notifications, the SDK will automatically track opened and delivered events for push notifications originating from Customer.io.". However, I have never gotten the Opened event from Broadcast > Newsletters reports.

My NotificationService

import UserNotifications
import CioTracking
import CioMessagingPush

class NotificationService: UNNotificationServiceExtension {
    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        CustomerIO.initialize(siteId: "", apiKey: "")
        let handled = MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
        if !handled {
            contentHandler(request.content)
        }
    }
    
    override func serviceExtensionTimeWillExpire() {
        MessagingPush.shared.serviceExtensionTimeWillExpire()
    }
}

Images in Rich Push Notifications still not working in FCM?

Hello,
I am trying to implement rich notifications with images but it seems it's still not working in latest alpha 1.2.0-alpha.4 release. I added Notification Service Extension with this implementation:

import CioMessagingPush
import UserNotifications

class NotificationService: UNNotificationServiceExtension {

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
    }
    
    override func serviceExtensionTimeWillExpire() {
        MessagingPush.shared.serviceExtensionTimeWillExpire()
    }
}

This I have in podfile:

target 'xxx do
  use_frameworks!

  ...
  pod 'CustomerIOTracking', '1.2.0-alpha.4'
  pod 'CustomerIOMessagingPushFCM', '1.2.0-alpha.4'

end

target 'NotificationService' do
  use_frameworks!

  pod 'CustomerIOTracking', '1.2.0-alpha.4'
  pod 'CustomerIOMessagingPushFCM', '1.2.0-alpha.4'
end

I am trying example payload from documentation and notifications are coming with title and body but no image. I tried to change didReceive method to add text to title and it was working. So it seems notifications are handled by CustomerIO in extension but image is not processed. How can I fix it? What am I doing wrong?

Thanks for help

Register APN device token for push notifications

In order to send a push notification to a mobile device from CIO, you need to:

  1. Setup the push notification backend service (APN/FCM) with your mobile app. FCM and APN has steps that they require to setup your app.
  2. FCM and APN then give the mobile app a device token. CIO needs this device token. A customer can use Segment or our API.

This task here has to do with item 2 above and customers who need to use our API.

Why are we doing this for our customers?

Today, our customers send push notification device tokens to CIO via (1) 3rd party services like Segment or (2) they write custom code in their apps to send the token to CIO via our API. This task is to do item (2) for our customers.

Sending a push notification device token to CIO will now just be 1 line of code:

Customers.deviceToken = "XXXXXXXXXXXXX"

...and that's it!

Along with sending a device token, this task will also set the platform and last_used properties automatically.

How do we test that this change is successful?

This feature will be tested with automated tests + manual testing with a sample app.

How are we building this?

  • Create a separate swift package just for APN push messaging.

  • Create a class MessagingPush

public class MessagingPush {
  // convenient shared singleton instance 
  public static private(set) var instance: MessagingPushAPN = MessagingPushAPN(CustomerIO.instance)

  // needs customerIO so MessagingPushAPN SDK features can get credentials and config
  init(customerIO: CustomerIO) {}
}

The idea is that this 1 class is where all the functionality of Push messaging exists. When you add a new SDK, it will extend MessagingPush.

  • Create extensions to MessagingPush to add APN specific functionality:
public extension MessagingPush {
  public func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data, onComplete: @escaping (Result<String, Error>) -> Void) {
    // perform API call to send device token up to API. 
    // if successful, save device token in key/value storage 
    // if successful or error, send result to onComplete()
  }

  func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
    // Print to XCode logs the error 
  }
}
  • When a customer is identified, after performing a HTTP request to add/update a customer, check if a device token exists in key/value storage. If one does, perform a HTTP request to register the device token with the customer's profile.

  • Update the function Tracking.identifyStop():

func identifyStop(onComplete: (Result<Void, Error>) -> Void, removeDeviceFromProfile: Bool = true) {
    if removeDeviceFromProfile {
        delete device token from profile in API: https://customer.io/docs/api/#operation/delete_device
    }
}

Setup dependency injection

Setup dependency injection in project to make testing easier to do.

Why are we doing this for our customers?

Automated tests are important to feel confident our SDK is stable. Dependency injection helps make automated tests able to test more code in the project.

How do we test that this change is successful?

This is a chore task and therefore QA is not needed. The engineers know the task is done when a dependency graph is created with ability to...

  • Create singletons and non-singletons
  • Override injections for testing purposes
  • Do dependency injection in a slim way.

How are we building this?

  • Install Sourcery CLI. Setup with configuration files.
  • Install DI template for Sourcery.
  • Setup dev tools to run sourcery.

[Feature Request] Add a new function to InAppEventListener that would allow customization of message action

I have the same issue reported in #205, which is Universal Links when triggered from In-App won't open the app but the system's browser - and I completely understood the technical reasons behind this, as explained there.

That said, I thought about an option that would allow the message action event to be intercepted and then customized if needed.

My suggestion to address this is to add a new method to the InAppEventListener delegate - messageActionWillExecute, like this:

public func messageActionWillExecute(message: CioMessagingInApp.InAppMessage, actionValue: String, actionName: String) -> Bool {
    // Having this delegate method would allow message to be inspected beforehand then we could choose to:
    // - Let it continue,
    // - Cancel its execution, 
    // - Or perform some customized action

    // Returning true -> The message action will execute
    // Returning false -> The message action won't execute
}

And example usage scenario: processing universal links from In-App:

public func messageActionWillExecute(message: CioMessagingInApp.InAppMessage, actionValue: String, actionName: String) -> Bool {
    // Run a custom deep linking handling mechanism, cancelling default message action behavior
    if let url = URL(string: actionValue) {
        aCustomDeepLinkingHandlerInstance.handle(url: url)
        return false
    }

    // Resume with the default message action behavior
    return true
}

Let me know if you need additional information.

Setup linting and formatting

Setup swift format and swiftlint using git hooks to format and lint code.

Why are we doing this for our customers?

Linting and formatting tools help the development team to be more productive. They help with communication and to catch bugs that could creep up. This task helps to make the app easier for customers to read the source code and makes the app more stable.

How do we test that this change is successful?

This is a chore task to QA is not needed.

How are we building this?

External libraries are being added with the latest SDK version.

New external libraries are being added with the latest SDK version using SPM.

SDK version: 2.7.8

Describe the issue

I am wondering if this is expected that all the versions after 2.6.1 are by default including FCM. Haven't found any notes regarding this so I would appreciate it if you could provide more context. Or if there is any way to avoid adding FCM when is not needed.

Screenshots

Screenshot 2023-08-15 at 15 20 39

Thanks

Setup ability to mock in automated tests

Setup ability to mock in automated tests.

Why are we doing this for our customers?

Automated tests are important to feel confident our SDK is stable. Mocking helps make automated tests able to test more code in the project.

How do we test that this change is successful?

This is a chore task and therefore QA is not needed. The engineers know the task is done when a unit test is created in the project successfully using a mock.

How are we building this?

  • Install sourcery template to generate mocks.
  • Generate mock of a random class (lets make it a random Repository). Use it in a random ViewModel.
  • Create a unit test of the ViewModel that uses the mocked Repository.

fix: screen view tracking for scene delegates

From iOS 13.0+ an app uses scene delegates along with app delegate where the responsibilities of app delegate is distributed amongst these. Current implementation provides support only for App delegates and gets UIWindow from App Delegate whereas from iOS 13.0+ onwards, UIWindow is available in Scene Delegate.

Requirement - add support for iOS 13.0+

Setup ability for SDK to be used in an iOS project

Setup the project with Swift Package Manager. We will then be able to install the SDK into the Remote Habits iOS app.

Why are we doing this for our customers?

Our customers need a way to install the SDK into their iOS project. Swift Package Manager is the modern iOS tool to do this. We need to setup our project to be able to be installed by this tool.

How do we test that this change is successful?

Once the Remote Habits iOS app can install this code base and call a random piece of code in the SDK, we know that this is setup correctly.

This includes the Remote Habits app able to build on a CI server.

How are we building this?

  • In SDK, create a small public class with public function so the SDK has something an iOS app can call.
  • On a new git branch, create a Package.swift file following this example project. Push code to GitHub. We will install the swift package using the branch you are on.
  • Setup semantic-release to create git tag, github release. git tags are all we need in order for the SPM to be deployed 😄 .

Deep links are not working for 2.0.0 when the app has been closed.

SDK version: 2.0.0

Environment: Development and Production

Are logs available?
No, due to the app is closed

Describe the bug
Precondition: The app was closed ("killed"). When a user receives a rich push notification that contains a deep link and he taps on it, the user is not redirected to the target screen (based on a deep link), the user is just on the first screen of the app.
When the app is in foreground/background - the rich push notification with deep link works as expected.

To Reproduce
Enable Rich Push notifications on the app per the developer instructions
Kill the app
In the Rich Push Notification UI interface on Customer.io, send a test notification with a deep link.

Expected behavior
the user is redirected by deep link correctly

Screenshots
no

Additional context
I've checked the same behavior for 1.2.7 - it works as expected when the app was killed. It means when the user taps on rich push notification with deep link the user is redirected to the target screen without any issues.

{ "CIO": { "push": { "link": "myscheme://il/testclient/mymail", } }, "aps": { "mutable-content": 1, "alert": { "title": "Message!", "body": "Open it!" } } }

MessagingInApp initialisation leading to crash

Hello ,
Added Inapp messaging SDK via cocoa pods like ,
pod 'CustomerIO/Tracking','~> 1.2.0-beta.1'
pod 'CustomerIO/MessagingPushAPN','~> 1.2.0-beta.1'
pod 'CustomerIOMessagingInApp','~> 1.2.0-beta.1'

And Initialise in AppDelegate like ,
let customerIO = CustomerIO( siteId: customerSiteID , apiKey: customerAPIKey , region: Region.US)
customerIO.config {
$0.autoTrackDeviceAttributes = true
$0.autoTrackPushEvents = true
$0.logLevel = .debug
$0.autoTrackScreenViews = true
}
MessagingInApp.shared.initialize(organizationId: customerOrgnizationID)

But when we try to run the app , its leading to crash on SDK
File Name - MessagingInApp.swift
private var diGraph: DIGraph {
return diGraphOverride ?? DIGraph.getInstance(siteId: siteId)
}
Here SiteId coming nil , still its set on above CustomerIO instance.

Handling rich push

According to the documentation for setting up rich push notifications, the following must be called after the notification was tapped:
let handled = MessagingPush.shared.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler)
The problem I'm having is if I call this method then CustomerIO handles the deep link by opening it in a browser when instead I should handle the deep link myself. If I don't call this method then the open and delivered events don't get tracked so I have to manually call trackMetric.

If we shouldn't be calling let handled = MessagingPush.shared.userNotificationCenter(center, didReceive: response, withCompletionHandler: completionHandler) when handling our own push then please state that in the documentation.

Crash in 1.2.8, MessagingInApp

SDK version:
1.2.8

Environment: Development or Production
Production

Are logs available?

CioMessagingInApp/MessagingInApp.swift:21: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
CioMessagingInApp/MessagingInApp.swift:21: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

Describe the bug

Upgraded from 1.2.7 to 1.2.8, and am seeing a crash when initializing the logger.debug. In particular DIGraph.getInstance(siteId: siteId) is returning nil and throwing the error

To Reproduce

Upgrade to 1.2.8
Turn on debug logging
Start app and hit the debug logging

Expected behavior

Initialization should work

Screenshots

No screenshot but stack trace is

#4	0x0000000106047e34 in MessagingInApp.diGraph.getter at SourcePackages/checkouts/customerio-ios/Sources/MessagingInApp/MessagingInApp.swift:21
#5	0x0000000106047f1c in MessagingInApp.logger.getter at SourcePackages/checkouts/customerio-ios/Sources/MessagingInApp/MessagingInApp.swift:33
#6	0x000000010604836c in MessagingInApp.initialize(organizationId:) at SourcePackages/checkouts/customerio-ios/Sources/MessagingInApp/MessagingInApp.swift:61

Additional context

After clearIdentify() SDK still sends something to server with old identity

SDK version: 1.2.0-beta.4

Environment: Development and Production

Describe the bug
After calling clearIdentify() method SDK still sends some data to server.

To Reproduce

  • identify() user via SDK - this will create new user on customer.io
  • delete user manually on customer.io
  • run clearIdentify() and do not exit the app
  • in 10-20 seconds user will reappear on customer.io

Expected behavior
We are trying to build such logic in our app:
When user clicks on "Delete profile" inside our app we need to clean all the data of the user.
First I run clearIdentify()
Then I send request to our server to clean the data and our server removes user from customer.io
And I expect that user is not present on customer.io any more.
But after some time (10-20 seconds) I see that user is recreated again.
I assume that SDK on iOS making data saves in background and it looks like even after clearIdentify() was invoked it still saves (may be for the last time) some data which recreates user.

Identify a customer

Add to our mobile SDK the ability to identify a person. This is very similar to adding/updating a customer with the CIO API, but with a different API unique to the mobile SDK. This task is useful for customers who have mobile apps with user accounts and they want to identify the customers in CIO workspaces.

Why are we doing this for our customers?

  1. Not require our customers to manually write code in their mobile app to add/update a customer in CIO via our API.
  2. Go a step further and create a developer friendly API in the SDK to make updating an existing customer more convenient.
  • If CIO makes changes to workspaces (like recent change of being able to use email as identifier), the SDK can adapt to make our customers not need to make any code changes in their apps. Or, at least, make it minimal changes.

How do we test that this change is successful?

We will use automated tests + manually running an integration test that calls the live track API.

How are we building this?

  • Create a separate swift package just for tracking.

  • Create a new class Tracking that is the basis for tracking in Customer.io

public class Tracking {
  // convenient shared singleton instance 
  public static private(set) var instance: Tracking = Tracking(CustomerIO.instance)

  // needs customerIO so Tracking SDK features can get credentials and config
  init(customerIO: CustomerIO) {}
}
  • Add identify functions to Tracking
Tracking.identify(id: String, onComplete: @escaping (Result<Void, Error>) -> Void, email: String? = null, createdAt: Date = Date())

Note: onComplete is only included here because of the SDK not having a background queue. The parameter will be removed in the future when a queue is put in place.

  • Perform a HTTP network request to identify a customer
    • If error, return error.
    • If success, save the identification information to key/value storage.
  • Add ability to stop identifying a customer to Tracking
Tracking.identifyStop()
  • Remove identification information from key/value storage. This is prep for event tracking so that events will not be registered to the identified user anymore.

Won't compile when installed via CocoaPods and using Notification Extension.

Hi,

When I add a Notification Service Extension to handle Rich Pushes, I need to include there CioMessagingPushAPN pod.

The problem is, it won't compile, since there is a code in SDK that is not available to App Extensions in general.

As an example, UIApplication.shared is not available in extensions. Probably these methods that are not needed from within extension, should be marked with @available(iOSApplicationExtension, unavailable).

Zrzut ekranu 2022-07-15 o 10 10 52

When SDK is installed via SPM - it works fine.

Could you please check and fix this, so that it's possible to use the SDK via CocoaPods ? Thank you!

Possibility of custom attributes not being successfully sent to the API.

version 1.0.0-alpha.25

Expected behavior

Public SDK functions that allow adding user attributes (such as CustomerIO.identify()) allow an optional JSONEncoder to be passed in. The main use case here is that if a customer wants user attributes to show up as "firstName" in fly.customer.io for example, they can. We want to give you the ability to modify the keys of the JSON.

Actual behavior

Customers providing their own JSONEncoder not only can modify the keys but also the values of JSON. This is a bad thing because our API requires dates to be in a certain format, for example. If a customer provides a custom JSONEncoder that encodes Date in a way that is not compatible with our API, there is a chance that value in the JSON will be ignored by the API.

Setup automated running of tests on code push, PR

Setup project to run our automated tests during development.

Why are we doing this for our customers?

Our automated tests help to make us feel more confident our SDK is stable. By running these automated tests on a CI server, we can make sure that our tests all pass before we mark a task as complete.

How do we test that this change is successful?

  • Our unit and integration tests are executed on CI server on every push and pull request.
  • After the tests run, have the CI server parse the junit report from the tests to make the rest results more useful.

How are we building this?

  • Setup GitHub Actions to run unit and integration tests with swift command on every push and pull request.
  • Parse junit report on the CI server.
  • Upload test-results directory to CI for assets to download by dev.
  • Add test-results to .gitignore.

Universal links are not properly supported on push notifications

SDK version: 2.0.4

Environment: Development or Production

Are logs available?
N/A

Describe the bug
When tapping on a push notification with a universal link as the deep link URL, the app opens but then the browser opens right afterwards.

To Reproduce

  • Send a universal link as a deep link in a push notification
  • Tap on the notification
  • Observe the app opens and then the browser opens right afterwards

Expected behavior
When tapping on a notification with a universal link, the app opens and the browser doesn't. Universal links work great outside of notifications, so my expectation is that they should work in notifications as well so we can use the same URLs everywhere instead of having to use a custom scheme for the deep links we send in notifications.

The current documentation isn't very clear about this, but based on what I see here it seems like our only option might be to use a custom scheme?

Screen Shot 2023-02-02 at 10 01 28 AM

HTTPS URLs work great on Android, so it would be nice if they work well on iOS as well. And based on what your docs say here, it really seems like universal links were supposed to be supported.

Screenshots
N/A

Additional context
According to Apple's docs (referenced by you here as well):

It’s important to understand that if your app uses openURL: to open a universal link to your website, the link does not open in your app. In this scenario, iOS recognizes that the call originates from your app and therefore should not be handled as a universal link by your app.

However, it seems that's what the SDK is doing here:

if let deepLinkurl = pushContent.deepLink {
    UIApplication.shared.open(url: deepLinkurl)
}

Is there anything more sophisticated the SDK could do to make sure universal links will be handled properly (e.g. something like this)? Or anything we can do on our end?

Need to unistall and install again if the previuos version of app has errors on coding implementing customer

SDK version: 2.0.4

Environment: Production

Are logs available?
yes

Describe the bug
I release the version of my app 2.0.1 with errors in code implementing customer IO. After some emails with the staff, they recommend doing a fresh install in XCode and implementing it the right way. I did that and now everything is working. When I send the version to test flight, the code was not working. After some tests, I decided to uninstall and install it again the APP. And then, everything starts working. The conclusion is that something is saving in the cache, but is no option to ask all my clients to uninstall and then install again the app. When the code is updated and the app is updated also, everything needs to work as expected with the new version.

To Reproduce
1 install a version with error
2 correct the errors and install the new version
3 starts working with a fresh install of app

Expected behavior
take the changes of the new version of the app

Screenshots
NA

Additional context

Rich push notifications not showing media files with APNS.

Rich push notification is working with APNS
I have followed the documentation provided: here

SDK version: 2.6.1

Describe the issue
Rich push notification is not showing media file with APNS integration only contents are visible like normal notifications.

Added UNNotificationServiceExtension

Import UserNotifications
import CioMessagingPushAPN
import CioTracking

class NotificationService: UNNotificationServiceExtension {
    
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
   CustomerIO.initialize(siteId: CustomerIOEnvironment.customerIOSiteId, apiKey: CustomerIOEnvironment.customerIOApiKey, region: Region.US) { config in
      config.autoTrackPushEvents = true
   }
   MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)
}
   
override func serviceExtensionTimeWillExpire() {
   MessagingPush.shared.serviceExtensionTimeWillExpire()
 }
}

Screenshots
Payload -

Screenshot 2023-06-22 at 10 53 54 AM

Notification view without image -

IMG_4219

Team - Can you please help me here to identify the issue ?

Thanks

Improve dependency injection graph to allow integration tests and easier use

Today, the dependency injection graph is:

  • One graph for the whole SDK. All instances of the SDK objects no matter what unique siteid and apikey you want to use. This means that if you want to use different site ids in your app, the SDK will not work.
  • Because of the use case defined above with supporting multiple different site ids in the SDK, the dependency injection graph does not truly work as intended today.

Here is an example of what we want:

class HttpClient {
  init(httpRunner: HttpRunner) {
    // httpRunner dependency automatically gets resolved by the DI graph
  }
}

But, what we have today:

class HttpClient {
  init(siteid: String) {
    self.httprunner = DI.shared.httpRunner
  }
}

As you can see, today we are not really using the DI graph as it should be used.

Proposal:

  • Create 1 DI graph per site id.

Some ideas of how to do this:

  • create a new DI graph instance in each top-level class (such as CustomerIO) where all dependencies get resolved from that top-level. You can inject in the constructor a DI graph instance into these top-level classes for integration test purposes.
  • create a singleton manager where you must provide a site id in order to get a shared instance of a DI graph.
  • We can now write integration tests! They are difficult to create today because some classes are not injected into the SDK by use of the DI graph (such as HttpClient). Now that all dependencies are in the graph, we can override them in tests.

Crash in 1.2.9

SDK version:
1.2.9

Environment: Development or Production
Production

Are logs available?

Describe the bug

We have a few users who are hitting a particular crash, and I've run into it too locally. It's within this block

// Use a DispatchQueue to make singleton thread safe. You must create unique dispatchqueues instead of using 1 shared one or you will get a crash when trying 
        // to call DispatchQueue.sync{} while already inside another DispatchQueue.sync{} call. 
        return DispatchQueue(label: "DIGraph_SdkConfigStore_singleton_access").sync {
            if let overridenDep = self.overrides[String(describing: SdkConfigStore.self)] {
                return overridenDep as! SdkConfigStore
            }
            let existingSingletonInstance = self.singletons[String(describing: SdkConfigStore.self)] as? SdkConfigStore
            let instance = existingSingletonInstance ?? _get_sdkConfigStore()
            self.singletons[String(describing: SdkConfigStore.self)] = instance
            return instance    
        }

It's hard to reproduce, it almost seems like a race condition.
To Reproduce
It almost seems like a fairly rare race condition, but we have users who are running into it (and I've seen it locally as well) For what it's worth, I wonder if it's a product of us needing to initialize both in the app and in our notifications extension, but I can't confirm.

Expected behavior

Screenshots

Additional context

Make US region default region

In the initialization of the SDK, make the US region the default.

Why are we doing this for our customers?

  1. Most customers today use the US region.
  2. Making the US region the default is consistent with our other SDKs.
  3. In the future, we may remove the requirement to provide to us a Region anyway (as the SDK will just look up the region that you have set for your workspace). Therefore, set a default to make migration easier in the future.

How do we test that this change is successful?

How are we building this?

  • Change CustomerIO class public init() and static func initialize() to have a default parameter: ..., region: Region = Region.US)

COCOAPOD Missing for INApp messages

Cocoapods available
pod 'CustomerIO/Tracking'
pod 'CustomerIO/MessagingPushAPN'
But its not available for InApp Messages
I tried by using like this pod 'CustomerIO/MessagingInApp' but no luck
I know its in Beta but you can make cocoa pods available by which we can try and implement same

Thank you !

Better way of managing singleton classes

Today in top-level classes like CustomerIO we have to have logic in place to determine if the shared singleton instance of CustomerIO has been initialized or not. Today it's done with conditionals in each public facing SDK function but that can get complex to handle and error prone as the SDK grows in functionality.

This issue is to come up with a better solution to this problem so it's simple to handle using a singleton or not a singleton instance of the top-level classes.

Add timestamp to background queue inventory items

Internally, we have talked about the potential use case of needing to delete stale/outdated tasks in the background queue. For example: if a task has a 403 and it never gets addressed, we would rather delete it then let it fail indefinitely.

In order for us to know what tasks to delete, we need to know how old the task is. When the task was added to the queue. That's what this task addresses.

  • Add a Date to QueueTaskMetadata that gets populated when a task is added to the queue.

Device push_enabled attribute is not updated when notifications get disabled.

Since all attributes of device are send only when a) push token changes b) app manually set custom device attribute, the push_enabled attribute does not represent current status of device when user disable push notifications.

If we want to have campaign where we send a push notification if it's enabled on device, otherwise send an email message - the push_enabled must always represent the most current value.

Proposed solution would be to always resend the device attributes when app is opened (even if it's coming from background).

Workaround solution I use for now is to set a new custom device attribute every time app is opened, it then triggers to re-evalue the push_enabled attribute value and send it to backend together with other attributes.

Add documentation for setting up APN in XCode project

The current documentation for the SDK does not include any instructions for setting up APN in an XCode iOS project. APN can be confusing to get setup as there are many steps. It is only known by someone who has done this many times before. For many customers, this is their first time setting up APN in a project, ever.

Our docs are good at telling you how to setup the SDK for APN push in your app after you get a APN device token. This issue is to address how to get a APN device token in the first place.

Why are we doing this for our customers?

Help them be successful in getting setup with push in Customer.io by helping them setup APN in their project.

How do we test that this change is successful?

How are we building this?

  • At this time, you will find in the README about getting started with push notifications and getting setup with APN. // TODO add instructions on how to do this. Decide how to provide documentation to the customer on how to setup APN. Linking to an existing doc online? Writing documentation? Linking to remote habits code?

Can't find MessagingPush in scope

image

Followed instructions to get Firebase messaging integration working. Here are the imports:
image

Also, the imports are clearly working because I can call identify.

Any idea how to solve this?

Background queue performs network call but doesn't continue processing queue

SDK version branch

reproduce:

  • add enough items to background queue to make it run automatically
  • make sure one of those events added is registering device token
  • queue should run, get to the task where you register device token, see a debug log statement such as:
queue next task to run: DA3C0, registerPushToken, {"device_token":"XXX","last_used":1638825856,"profile_identifier":"[email protected]"}, QueueTaskRunResults(totalRuns: 0)
  • You will see a HTTP call successfully performed with API to register the device token.
  • No more log statements. The callback in QueueRunRequest runner.runTask(nextTaskToRun) { result in will not get called.

I am assuming this is a garbage collection problem? The hooks are successfully starting HTTP request in MessagingPush module.

Task identifyProfile / trackEvent not handled by any module

Hi,

I just switch from 1.1.1 to 1.2.0-beta.1 to use the In-App Messaging. However, I got the errors and was unable to get the identify / track events anymore:

🛑 task trackEvent not handled by any module
🛑 task identifyProfile not handled by any module

Everything is working well on 1.1.1.

Error logs

Hi, I just integrated the SDK, and have many of the following errors:

 [CIO] 🛑 4xx HTTP status code response.
Probably a bug? Response received, but status code = 400. event data must be a hash

Any clue on what causes this and how to fix it?

No longer seeing images in rich notifications

SDK version: 1.2.7

Environment: Development Development or Production

Are logs available?: Yes

Describe the bug
We were previously able to send images with our rich push notifications and it showed up fine on 1.2.6, but since upgrading, we have not been able to see images with our rich push notifications.

Interestingly, putting a debug point on this line:
let handled = MessagingPush.shared.didReceive(request, withContentHandler: contentHandler)

shows that handled resolves to false, when previously it was resolving to true, which leads me to believe there is either an issue processing it with the new update, or the payload is incorrectly being sent now.

Additionally, downgrading to 1.2.6 does not fix the issue.

To Reproduce
Enable Rich Push notifications on the app per the developer instructions
In the Rich Push Notification UI interface on Customer.io, send a test notification with an image. The test image URL I have been using is (and has been previously working for testing)
https://thumbs.dreamstime.com/b/bee-flower-27533578.jpg

More Debugging:

If i tap the notification,

 let handled = MessagingPush.shared.userNotificationCenter(center, didReceive: response,
                                                                  withCompletionHandler: completionHandler)

returns true for handled.

Expected behavior
Notification is delivered on our side with the image

Screenshots

Additional context

This is the projected payload

{
  "CIO": {
    "push": {
      "image": "https://thumbs.dreamstime.com/b/bee-flower-27533578.jpg"
    }
  },
  "aps": {
    "alert": {
      "title": "Hi",
      "body": "This is a test"
    },
    "mutable-content": 1
  }
}

This is the result of po userInfo in our Notification Extension

▿ 4 elements
  ▿ 0 : 2 elements
    ▿ key : AnyHashable("CIO-Delivery-Token")
      - value : "CIO-Delivery-Token"
    - value : <device token redacted>
  ▿ 1 : 2 elements
    ▿ key : AnyHashable("CIO")
      - value : "CIO"
    ▿ value : 1 element
      ▿ 0 : 2 elements
        - key : push
        ▿ value : 1 element
          ▿ 0 : 2 elements
            - key : image
            - value : https://thumbs.dreamstime.com/b/bee-flower-27533578.jpg
  ▿ 2 : 2 elements
    ▿ key : AnyHashable("CIO-Delivery-ID")
      - value : "CIO-Delivery-ID"
    - value : 
  ▿ 3 : 2 elements
    ▿ key : AnyHashable("aps")
      - value : "aps"
    ▿ value : 2 elements
      ▿ 0 : 2 elements
        - key : alert
        ▿ value : 2 elements
          ▿ 0 : 2 elements
            - key : title
            - value : Hi
          ▿ 1 : 2 elements
            - key : body
            - value : This is a test
      ▿ 1 : 2 elements
        - key : mutable-content
        - value : 1

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.