React-native-branch-deep-linking-attribution: Have access to the raw URL clicked by user

Created on 16 Jan 2020  路  31Comments  路  Source: BranchMetrics/react-native-branch-deep-linking-attribution

We have a react native app with which we are having significant reliability issues when it comes to deep linking.

We use branch links to pass invite tokens to the app, if the app cannot retreive the invite token the user is blocked and cannot advance.

We have a significant number of users who follow a branch link when the app is already installed, the are never redirected.

We have the following code called in the componentDidMount of our root component:

  branchSubscribe = branch.subscribe(({ error, params }) => {
    // `+non_branch_link` handles incoming standard deeplinks and universal links.
    const url = params["+non_branch_link"] || params["~referring_link"];
    if (url) {
      redirectFromUrl(url);
    }

    if (error) {
      logWarning("Error from Branch: " + error);
      return;
    }
  });
};

The callback passed to branch.subscribe is never called when the device has zero network connectivity. Even in cases where we have low connectivity, this callback is called unreliably.

The raw link is cached on the native side, but it does not seem that this is exposed to RN through any channel that is not triggered by the response to the Branch Servers

getLatestReferringParams seems a bit more reliable, but the data it returns still seems to be set after a request to the branch servers.

Is there any way to always have reliable access to the raw link that the user clicked ?

All 31 comments

I too am experiencing this using email verification tokens. Additionally, even if the app has network connectivity, it may still timeout trying to connect to branch servers.

One solution would be for the branch.subscribe() callback to always return the native layer referring link on network errors. That would allow the deep link account verification flow to continue.

Adding a ticket for this one.

I think it could make sense to give a separate listener to pass the referral_link directly to the app when wanted. The case described above, we are perfectly happy to let branch function in the background, but we aren't relying on anything from branch for the app to function.

The approach we are taking in the short term is to user React Native Linking in parallel to branch:

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity
 restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> *restorableObjects))restorationHandler
{
  BOOL continueActivity = [RNBranch continueUserActivity:userActivity];

  // if branch isn't handling this, maybe our universal link manager should
  if (!continueActivity) {
    continueActivity = [RCTLinkingManager application:application
                                 continueUserActivity:userActivity
                                   restorationHandler:restorationHandler];
  }

  return continueActivity;

But it would be much nicer if branch handled this case.

For android we would like to do something similar

Does your app use Branch generated short urls (e.g. for sharing links)?

Interested in how you would achieve concurrent RCTLinkingManager and branch.subscribe usage in that case.

Hi all, wanted to follow up here. Seems like the main issue is that the callback is never called when there are connectivity issues. Is that the case?

If so, would the best solution be configurable retries and timeouts, after which we invoke the callback passed via branch.subscribe?

For critical universal link flows, there should not be a hard dependency on 3rd party server availability. @zyskowsk already summed this up in his original comment. Having a fallback to the raw universal link from the native layer is the best way forward in my opinion.

Oh, got it. Apologies for not fully understanding the above, I'm very new to React Native.

It seems there there are a few possible approaches (if I'm understanding correctly):

  1. You examine the Universal Link URL by going down into the native layer, specifically the continueUserActivity method(s)
  2. If there is an error with connecting to Branch's servers, we return the error, and also return the full Universal Link URL inside the params object (assuming we're talking about branch.subscribe(({ error, params }) => { });)
  3. The Branch SDK implements a separate method for retrieving the latest Universal Link URL at any time.

Each approach has pros and cons.

Pros of (1): you have complete control and can implement now
Cons of (1): requires you to interact with the native layer

Pros of (2): perhaps the simplest to implement
Cons of (2): not yet available; could confuse developers if they see ~referring_link and assume everything is working, but then there are missing attributed installs, opens, etc within Branch

Pros of (3): avoids the main cons of (1) and (2)
Cons of (3): requires both a Branch iOS SDK change and a Branch RN SDK change; unclear when Branch should wipe a previous Universal Link URL from the cache, so then a developer could repeatedly retrieve the same Universal Link URL over and over when in a failure mode (aka trouble connecting to Branch's servers)

Could you provide feedback about the above? Any other thoughts outside of these 3 approaches?

It's really up to you guys. Option 2 satisfies my reqs. You could still add a native module getter for universalLinkUrl at a later date.

The cons to Option 2 are pretty significant though. All attribution and analytics will be missing from Branch (Dashboard visualizations, webhooks, exports), and in-app we won't have other keys that typically accompany ~ referring_link. So while it satisfies your specific use case, it creates other edge cases that can be pretty nasty.

Still thinking through the best way to build this. @jdee can you take a look at this as well and let us know your thoughts?

There can be a problem with passing the link that opened the app. We deliberately removed or deprecated this somewhere around 2.x/3.x. There are weird edge cases that don't work or are hard to handle such as forcing the app open with a URI redirect on Android, in which case the link that opens the app is not the one you want, but ~referring_link is the right thing.

Adding this also makes this SDK unlike the other Branch SDKs. It might not be a bad idea to support this at the native level eventually and make it more transparent. If the issue is lack of connectivity to resolve the link, that may not be specific to RN.

The good news is I'm now full-time at Branch and have passed through the gantlet of onboarding. :tada: I'll be able to provide a higher level of support from now on. I'm working on getting a release out for RN < 0.60 by EOD. Once that's out, I'll review this in more depth and help come up with a solution we can support in the long term. Sorry for the long delay in responding, but I'll be spending most of my time on the RN SDK for a bit to come.

I haven't forgotten about this. Sorry for the delay. I have one other task on my plate first and then can hopefully give you a good solution probably next week.

OK, at long last we have a plan. Essentially we will provide a notification option when a link is first opened and then in addition pass the original URI in the current callback.

The API will be something like this:

import branch from 'react-native-branch';

branch.subscribe({
  onOpenStart: (uri) => {
    // This function is called before the Branch API open call.
    console.log('Opening URI: ' + uri);
  },
  onOpenComplete: ({ params, error, uri })=> {
    // This is the same as the current callback, with the uri passed as a third
    // named (destructured) option.
  }),
});

The current API will still be supported, since we can differentiate by argument type. This also paves the way to things like passing Branch keys in the call to subscribe. You will also get the uri in the current API if you declare it:

branch.subscribe({ params, error, uri } => {
  console.log('Branch open complete for ' + uri);
});

The uri parameter will always be present, even in case of error, except in the case of a deferred deep link (through an app store install), when there's no URI to report beyond the ~referring_link.

This will all work for both Branch links and non-Branch links.

I should have a PR up for this early next week or maybe tomorrow. Please feel free to comment here or there. Thanks.

@zyskowsk and @jamesholcomb will you both review this and ensure it meets your needs? If not, Jimmy can tweak his approach. Thanks!

You will also get the uri in the current API if you declare it:

And uri = raw uri from the native layer? I assume subscribe is still called with respect to the configured network error/retry flow? In other words, assuming you have no network connectivity or Branch servers are unresponsive, subscribe won't be invoked until the final retry or timeout has occurred.

@jamesholcomb Yes uri is the raw URI that opened the app. And yes, the subscribe callback (or onOpenComplete) is only called when the native SDK fires its callback. Anything else would require native SDK changes. There's no way for the RN module to do that on its own. If you need to avoid a lengthy delay, you can make use of the onOpenStart callback to set a timer, which roughly accomplishes the same thing.

LGTM

Hey @jdee @derrick-branch thank you so much for digging into the issue and sorry for my absence.

What is important for me is to be able to either process the raw URL _or_ the ~referring_link and to be able to set preference for which one I take given the situation.

I would like to be able to use branch.subscribe as the source of truth for all link ingestion within the app, but if my app knows how to process the raw URI, I prefer to process it instead of waiting in certain critical cases for branch to return.

If i understand correctly, onOpenStart is called and is passed the raw URI before any network communication occurs, is this correct ?

@zyskowsk Yes, that is correct. The onOpenStart callback amounts to the same thing as using RN Linking to grab links immediately on open.

looks good to me, it also creates a nice separation for the preferential logic I was discribing.

@zyskowsk, @jamesholcomb Please see PR #564, now merged into the staging branch. This is what we plan to release this week. Any feedback on how this addresses your needs is welcome. Basically you can now do:

branch.subscribe({
  onOpenStart: ({uri}) => { ... },
  onOpenComplete: ({params, error, uri}) => { ... },
})

You can also create a BranchSubscriber object directly if you prefer this API:

import { BranchSubscriber } from 'react-native-branch'

const subscriber = new BranchSubscriber({
  onOpenStart: ({uri}) => { ... },
  onOpenComplete: ({params, error, uri}) => { ... },
})
subscriber.subscribe()

This is just what branch.subscribe does under the covers. Also see src/Subscribe.js in the testbed_simple example app in the staging branch for a working example.

Note that the uri reported in onOpenStart can be null in some cases, particularly in the case of a deferred deep link (through an AppStore install, e.g.), when the app is not launched by a link. You'll still get onOpenStart({uri: null}) before you get onOpenComplete.

Thanks!

Thank you for your work on the topic @jdee !

This was released to NPM in 5.0.0-beta.1. Please see https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/releases/tag/v5.0.0-beta.1 for details. Closing this issue. If you have trouble with this new feature, please feel free to open a new issue or contact [email protected]. Thanks!

Our app is on RN 0.59.10. Is a release coming for that specific version?

@jamesholcomb Sorry. I should have checked with you and @zyskowsk first. We can provide support for 0.59. I'd prefer not to maintain two parallel betas, especially for a legacy release, so I'm hoping to get a bit of feedback on the 5.0.0-beta.1 release. But nothing prevents this being done in 3.x.

Maybe you can help us with a bit of insight though. RN 0.60 was released last July. I understand the pain of upgrading. I go through it with the example apps here. Even 0.60 -> 0.62 was kind of painful. But eventually I suppose you'll have to do it. Do you have any plans to upgrade in the foreseeable future? And I don't mind if I can help out with advice on how to do it if you run into snags. I ask because we'd love to be able to maintain one release instead of two at a time, and I will be glad to discontinue 3.x whenever possible. Obviously that's not just you, but any insight you can provide on your own plans might help us plan better. Thanks!

PS - This is a useful resource for upgrading RN apps:

https://github.com/react-native-community/rn-diff-purge

Perhaps a bad assumption on my part. I was not aware of end of life for RNBranch 3.X so I had assumed new features would land there.

No definitive plans to upgrade to 0.60+ in the near term but it's def on the slate. That may preclude me from assisting in testing 5.0 I'm afraid.

Our app is on RN 61.4. We will remove our current code to retrieve the raw URL from the native layer, replacing it with the new pattern and get back to you if we have any issues !

@zyskowsk We've added a section to the docs for the new feature: https://help.branch.io/developers-hub/docs/react-native#section-receive-notification-before-a-link-is-opened.

I'd love any feedback on the API, whether you prefer BranchSubscriber or branch.subscribe or otherwise how it suits your needs. I didn't add BranchSubscriber to the docs yet, trying to keep it fairly simple. That could end up remaining an internal utility class.

https://github.com/BranchMetrics/react-native-branch-deep-linking-attribution/blob/a252504084f583ccbb56c38802c72423be1d22fc/src/BranchSubscriber.js#L65-L80

Can you add a parameter to onOpenStart indicating if a cached value was returned?

Something like

this.options.onOpenStart({ uri: result.uri, cachedEvent: true })

or perhaps the entire result object?

@jamesholcomb There is a +rn_cached_initial_event that should be present in the params. Does that cover this use case?

Perhaps. I use onOpenStart to synchronously parse and route all non_branch_links irrespective of branch server availability (the crux of this issue). These raw links contain verification codes and do not have dependencies on branch.io per se. It's much simpler to have the info about the cached initial event at that time.

I have been using a locally patched version of 5.0 beta 1 that passes the entire result object in onOpenStart (line 76 above). It works but I am interested if this is not recommended due to issues in the native layer.

Was this page helpful?
0 / 5 - 0 ratings