BranchMetrics / ios-branch-deep-linking-attribution

The Branch iOS SDK for deep linking and attribution. Branch helps mobile apps grow with deep links / deeplinks that power paid acquisition and re-engagement campaigns, referral programs, content sharing, deep linked emails, smart banners, custom user onboarding, and more.
https://help.branch.io/developers-hub/docs/ios-sdk-overview
MIT License
728 stars 229 forks source link

Failure to get callbacks when upgrading from 3.0.0 to 3.3.0 #1364

Closed Splash04 closed 5 months ago

Splash04 commented 6 months ago

Describe the bug

Faced with issue after updating SDK version. Old app contains SDK v3.0.0; new app version: v3.3.0.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        Branch.setUseTestBranchKey(true)
        Branch.getInstance().initSession(launchOptions: launchOptions, automaticallyDisplayDeepLinkController: false, deepLinkHandler: handleDeepLink)
}

func handleDeepLink(params: [AnyHashable: Any]?, error: Error?) {}

The main issue: After app update, deepLinkHandler is never called. App restart not help. The only solution is to delete app and install from scratch. But such case is NOT acceptable for app users.

Network finish operation https://api3.branch.io/v1/open 0.944s. Status 200 error (null).

Expected result: deepLinkHandler call just after app launch.

Log file: Branch_io_log.txt

The same issue as users described here: https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/issues/760#issuecomment-1640572758 https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/issues/914#issue-434226742 https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/issues/532#issue-554558963 And a lot of threads at stackoverflow.

Steps to reproduce

1. 2. 3.

Expected behavior

deepLinkHandler call after app launch.

SDK Version

3.3.0

XCode Version

15.2

Device

iPhone 13 pro

OS

15.7.1

Additional Information/Context

No response

Splash04 commented 6 months ago
Screenshot 2024-03-07 at 00 01 40 lose_callback

Well, looks like SDK on findExistingInstallOrOpen take BranchOpenRequest with empty callback. And at the same time urlString is nil as well, so if and else cases just NOT save initSessionCallback and we lose it

echo-branch commented 6 months ago

@Splash04 Mind opening a support ticket for this? The upgrade scenario should work.

Now the status 200, is actually the http status which is ok. That's just informational and not an error. So I'm going to rename this ticket to correctly describe it as a potential upgrade issue.

echo-branch commented 6 months ago

Looking at the logs and screenshots, the behavior is as expected. So I think a deeper analysis is necessary.

The else block is to handle a very specific lifecycle situation. Most of the time we actually do NOT want to create a new request. The reason it exists is if we have two lifecycle events come in very rapidly and the second lifecycle has link data, we cannot ignore like we normally do. We must make an attempt to resolve that one as well.

Splash04 commented 6 months ago

I'm still have one phone with this issue. So I will try to provide some additional details that maybe help:

  1. On App launch by some reason we load from file "BNCServerRequestQueue" just one BranchOpenRequest with empty fields: empty_request

  2. Init branch single with this data: init_branch_singletone

  3. Checking request [self.requestQueue findExistingInstallOrOpen] and not saving initSessionCallback: checking_request_in_queue

  4. Processing empty BranchOpenRequest: processing_empty_request_from_queue

  5. Send open event to server: send_open_event_to_server

  6. on processResponse we don't have errors and callback is null, so just load data and [BranchOpenRequest releaseOpenResponseLock] releaseOpenResponseLock

  7. Callback null, so do nothing here: empty_callback

  8. processNextQueueItem processNextQueueItem

So no callback call here.

When I compare this flow with clear setup flow, I had seen that findExistingInstallOrOpen don't return anything. So we're creating BranchOpenRequest and saving initSessionCallback. And initSessionCallback call after the end. fresh_install

Splash04 commented 6 months ago

For developers who faced with this issue. This actions should help.

  1. We should create timer to catch BranchIO freeze and force start the app. I had selected 15 sec timeout.
  2. We should clear the cache file for branch serverRequestQueue to avoid freeze on the next launch.
@main
class AppDelegate: UIResponder, UIApplicationDelegate {

    private var branchIOLaunchTimer: Timer?

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // code
        startBranchTimeoutTimer()
        Branch.getInstance().initSession(launchOptions: launchOptions, automaticallyDisplayDeepLinkController: false, deepLinkHandler: handleDeepLink)
    }

    func handleDeepLink(params: [AnyHashable: Any]?, error: Error?) {
        stopBranchTimeoutTimer()
        // handle DeepLink
    }
}

// ******************************* MARK: - Branch iOS workaround

extension AppDelegate {
    @objc
    private func onBranchTimerTimeout() {
        logError("Branch IO app launch Timeout")
        // Just additional safety (Queue should be already empty), basically do nothing for our issue
        Branch.getInstance().clearNetworkQueue()

        /*
         The main issue, that file for serverRequestQueue cache contains invalid event
         so we need to get path to this file and clear it
         */

        /* persistImmediately will not clean up the file, because it contains checking count != 0
            if let serverRequestQueue = (BranchSDK.BNCServerRequestQueue.getInstance() as? BranchSDK.BNCServerRequestQueue) {
                print("serverRequestQueue: \(serverRequestQueue.queueDepth())")
                serverRequestQueue.persistImmediately()
            }
         */

        // Load serverRequestQueue file path from ObjecitiveC code
        if let fileUrl = ObjectiveCUtils.serverRequestQueueURL() {
            logDebug("Remove cache file for branch serverRequestQueue", data: ["fileUrl" : fileUrl])
            removeFile(fileUrl: fileUrl)
        }

        // Force start the app
        handleDeepLink(params: [:], error: nil)
    }

    func removeFile(fileUrl: URL) {
        if FileManager.default.fileExists(atPath: fileUrl.path) {
            do {
                try FileManager.default.removeItem(atPath: fileUrl.path)
                print("File deleted: \(fileUrl)")
            } catch {
                print("Could not delete file, probably read-only filesystem")
            }
        } else {
            print("File not exist")
        }
    }

    fileprivate func startBranchTimeoutTimer() {
        if let _ = self.branchIOLaunchTimer { return }
        self.branchIOLaunchTimer = Timer.scheduledTimer(timeInterval: 10.0,
                                                        target: self,
                                                        selector: #selector(onBranchTimerTimeout),
                                                        userInfo: nil,
                                                        repeats: false)
    }

    private func stopBranchTimeoutTimer() {
        self.branchIOLaunchTimer?.invalidate()
        self.branchIOLaunchTimer = nil
    }
}

The main problem here that serverRequestQueue cache file path is a private method in objectiveC class. So, we have to use Objective-C Runtime Utilities function to get selector by name (NSSelectorFromString). Please, check that function name is not changed: function_name

How to get this path:

  1. Create ObjectiveCUtils.h:
    
    #import <Foundation/Foundation.h>

@interface ObjectiveCUtils : NSObject

@end

2. Accept to create -Bridging-Header.h when XCode asks you. It should contain just one line:
`#import "ObjectiveCUtils.h"`
3. Create ObjectiveCUtils.m

import "ObjectiveCUtils.h"

import "Branch.h"

@implementation ObjectiveCUtils

@end

echo-branch commented 6 months ago

@Splash04 Thanks for the detailed logs and your workaround. Looking to see if I can reproduce this on my own test devices. Either way, I think an additional safety check for invalid events in storage is necessary in the next patch.

echo-branch commented 5 months ago

A error recovery safety check similar to the one described here was added in the latest release. https://github.com/BranchMetrics/ios-branch-deep-linking-attribution/releases/tag/3.4.1