receive_sharing_intent icon indicating copy to clipboard operation
receive_sharing_intent copied to clipboard

In iOS, receive_sharing_intent hijacks all deep linking for the app and nothing else can use deep links

Open acoutts opened this issue 4 years ago • 11 comments

I understand the way this works is the share extension (a separate process) writes the shared files into a temporary location (the shared group), and then sends a deep link containing the path to the shared file (or just the url string if a url was shared) through a deep link to the plugin code, where it picks it up in these handlers: https://github.com/KasemJaffer/receive_sharing_intent/blob/master/ios/Classes/SwiftReceiveSharingIntentPlugin.swift#L53-L74

Then it sends a stream event so that the dart code can read the shared file path.

But the problem is, with the way it is currently implemented, it hijacks all deep links shared to the app, even if they weren't intended for this plugin. This is unexpected behavior and I just had to spend a lot of time tracing down why uni_links couldn't pickup anything. It's a race condition for which swift code runs first to get the handleUrl hook.

What should be done is a check should be made to make sure the URL is relevant only to us in this context and allowing anything else to pass by to other intent / deep link listeners.

acoutts avatar May 12 '20 17:05 acoutts

On closer look, I think it's working fine, and I have a bug elsewhere.

acoutts avatar May 12 '20 18:05 acoutts

No, there is definitely an isssue.

            } else {
                latestText = url.absoluteString
                if(setInitialData) {
                    initialText = latestText
                }
                eventSinkText?(latestText)
            }

This will send any deep link back through and return true saying it's been handled. It should only handle it if it's relevant and from the share extension.

acoutts avatar May 12 '20 18:05 acoutts

Here's one example. Wrap the inside of handleUrl in SwiftReceiveSharingIntentPlugin.swift with this:

 if (url.absoluteString.contains("\(Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String).ShareExtension")) {

This will only handle the url if it contains bundleIdentifier.ShareExtension, and then in ShareViewController.swift inside your own project, modify redirectToHostApp to look like this:

        let url = URL(string: "\(Bundle.main.infoDictionary!["CFBundleIdentifier"] as! String)://dataUrl=\(sharedKey)#\(type)")

Then make sure your project's Info.plist has this scheme registered:

      <dict>
        <key>CFBundleURLName</key>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        <key>CFBundleURLSchemes</key>
        <array>
          <string>$(PRODUCT_BUNDLE_IDENTIFIER).ShareExtension</string>
        </array>
      </dict>

That should ensure only relevant links are handled from the plugin.

acoutts avatar May 12 '20 18:05 acoutts

@KasemJaffer @acoutts Can we get a PR for this?

britannio avatar May 28 '20 13:05 britannio

When I dug into this more, it's a much harder problem to solve to make it interoperate with unil_inks. In the end I removed unilinks and just use this lib to handle all of my deep linking and share intent now. It's tough because deep linking is so similar to this. It probably makes sense to just use this for everything if you intent to handle share intent and deep linking, and use uni_links if you don't need to handle share intent.

acoutts avatar May 28 '20 13:05 acoutts

My concern is if I want to add firebase_dynamic_links in the future.

I'm not an IOS developer but based on this comment https://github.com/KasemJaffer/receive_sharing_intent/issues/36#issuecomment-595130324 it seems like different url schemes can be defined here? My app uses google_sign_in and part of the IOS integration requires defining a separate url scheme.

britannio avatar May 28 '20 14:05 britannio

That's generally how the share extension communicates with the host app, via deep link. But the problem is, if you launch the app from an exited state by sharing a URL to it, then that also comes in on the didFinishLaunchingWithOptions hook. And if someone else handles that link then the other libs won't see it. It is a race condition for who gets the didFinishLaunchingWithOptions hook first.

acoutts avatar May 28 '20 14:05 acoutts

In the end I think the best solution here is to make didFinishLaunchingWithOptions always return true, but just call handleUrl like normal. This way we never make a decision about if the app can handle the URL or not because some other plugin in the app might handle it.

This is how I see it done in firebase_messaging and uni_links.

In the plugin, update it like this:

public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [AnyHashable : Any] = [:]) -> Bool {
    
    if let url = launchOptions[UIApplication.LaunchOptionsKey.url] as? URL {
        // Handle a single URL shared in
        let _ = handleUrl(url: url, setInitialData: true)
    } else if let activityDictionary = launchOptions[UIApplication.LaunchOptionsKey.userActivityDictionary] as? [AnyHashable: Any] {
        // Handle multiple URLs shared in
        for key in activityDictionary.keys {
            if let userActivity = activityDictionary[key] as? NSUserActivity {
                if let url = userActivity.webpageURL {
                    let _ = handleUrl(url: url, setInitialData: true)
                }
            }
        }
    }
    return true
}

acoutts avatar Aug 27 '20 22:08 acoutts

Made a PR that should resolve the issue of not propagating events to other modules. Not an iOS expert so feel free to offer suggestions.

danReynolds avatar Dec 23 '20 04:12 danReynolds

@danReynolds still that fix of yours didn't work for me. I'm using receive_sharing_intent: ^1.4.3 and firebase_messaging: ^7.0.0 but firebase deep linking does not work for me. Any help on how to fix it?

abhishek199-dhn avatar Mar 16 '21 12:03 abhishek199-dhn

Any updates on this @KasemJaffer @danReynolds ?

harnit-bakshi avatar Mar 19 '21 02:03 harnit-bakshi