KasemJaffer / receive_sharing_intent

A Flutter plugin that enables flutter apps to receive sharing photos, text and url from other apps.
Apache License 2.0
334 stars 390 forks source link

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

Open acoutts opened 4 years ago

acoutts commented 4 years ago

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 commented 4 years ago

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

acoutts commented 4 years ago

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 commented 4 years ago

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.

britannio commented 4 years ago

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

acoutts commented 4 years ago

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.

britannio commented 4 years ago

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.

acoutts commented 4 years ago

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 commented 4 years ago

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
}
danReynolds commented 3 years ago

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.

abhishek199-dhn commented 3 years ago

@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?

harnit-bakshi commented 3 years ago

Any updates on this @KasemJaffer @danReynolds ?