Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Share Extension for iOS not working #97

Open
KevinToala opened this issue Nov 21, 2024 · 2 comments
Open

Share Extension for iOS not working #97

KevinToala opened this issue Nov 21, 2024 · 2 comments

Comments

@KevinToala
Copy link

KevinToala commented Nov 21, 2024

Hi everyone 👋

I am testing the plugin and can configure everything without any issues. Our app appears as an option to share information, but when selecting the app, there’s only a screen effect, and the event never reaches the app. We are unsure what we might be missing or doing wrong, as we have followed all the steps exactly as outlined.

We are using Capacitor 6 with Angular, and the goal of integrating this plugin is to enable sharing of locations, similar to how it works in WhatsApp when you share a location with a contact. For now, we are just testing to see if we receive any data. However, neither a web link shared from Safari nor images or other data seem to reach the app.

What could we be missing?

Grabacion.de.pantalla.2024-11-21.a.las.4.53.34.p.m.mov
Here are the logs we have:
KeyboardPlugin: resize mode - native
nw_connection_copy_connected_local_endpoint_block_invoke [C1] Connection has no local endpoint
nw_connection_copy_connected_local_endpoint_block_invoke [C1] Connection has no local endpoint
⚡️  Loading app at capacitor://localhost...
nw_connection_copy_connected_local_endpoint_block_invoke [C3] Connection has no local endpoint
nw_connection_copy_connected_local_endpoint_block_invoke [C3] Connection has no local endpoint
⚡️  [log] - onscript loading complete
⚡️  WebView loaded
⚡️  To Native ->  App getInfo 131431287
⚡️  To Native ->  App addListener 131431288
⚡️  TO JS {"id":"com.myapp.store","build":"1.15.0","name":"Asociado","version":"1.15.0"}
⚡️  To Native ->  SplashScreen hide 131431289
⚡️  To Native ->  StatusBar hide ⚡️  TO JS 131431290
undefined
⚡️  TO JS undefined
⚡️  To Native ->  SendIntent checkSendIntentReceived 131431291
⚡️  TO JS {"description":"","type":"","url":"","additionalItems":[],"title":""}
To Native Cordova ->  OneSignalPush init OneSignalPush1803229918 ["options": [5437d3df-6466-4819-b264-6570a41e4862]]
To Native Cordova ->  OneSignalPush login OneSignalPush1803229919 ["options": [5d39e2fb-1001-4880-843e-b205d275e247]]
⚡️  To Native ->  LocalNotifications checkPermissions 131431292
⚡️  To Native ->  Device getInfo 131431293
To Native Cordova ->  OneSignalPush getPushSubscriptionId OneSignalPush1803229923 ["options": []]
To Native Cordova ->  OneSignalPush getPushSubscriptionToken OneSignalPush1803229924 ["options": []]
To Native Cordova ->  OneSignalPush getPushSubscriptionOptedIn OneSignalPush1803229925 ["options": []]
⚡️  TO JS {"isVirtual":true,"diskTotal":994662584320,"realDiskFree":400601702400,"model":"iPhone17,2","realDiskTotal":994662584320,"name":"iPhone 16 Pro Max","osVersion":"18.0","platform":"ios","iOSVersion":180000,"diskFree":400601702400,"memUsed":295043072,"operati
⚡️  [log] - SendIntent received
⚡️  [log] - {"description":"","type":"","url":"","additionalItems":[],"title":""}
To Native Cordova ->  OneSignalPush addPushSubscriptionObserver OneSignalPush1803229926 ["options": []]
⚡️  TO JS {"display":"granted"}
To Native Cordova ->  OneSignalPush getPermissionInternal OneSignalPush1803229927 ["options": []]
To Native Cordova ->  OneSignalPush addPermissionObserver OneSignalPush1803229928 ["options": []]
⚡️  [log] - OneSignal: SDK is not compatible with this browser. To support iOS please install as a Web App. See the OneSignal guide https://documentation.onesignal.com/docs/safari-web-push-for-ios
⚡️  To Native ->  Keyboard getResizeMode 131431294
⚡️  TO JS {"mode":"native"}
⚡️  To Native ->  Keyboard getResizeMode 131431295
⚡️  TO JS {"mode":"native"}
⚡️  To Native ->  App addListener 131431296
⚡️  To Native ->  Keyboard getResizeMode 131431297
⚡️  TO JS {"mode":"native"}
⚡️  To Native ->  App getInfo 131431298
⚡️  TO JS {"name":"Asociado","id":"com.myapp.store","version":"1.15.0","build":"1.15.0"}
ERROR: Unabled to parse response to create subscription request
ERROR: Unabled to parse response to create subscription request

I can only see the following in the logs:
{"description":"","type":"","url":"","additionalItems":[],"title":""}

This is displayed when opening the app.
I’m also sharing the code for iOS exactly as we currently have it. We have created the Share Extension target and the group, and our URL scheme is correctly configured as well.

import UIKit
import SendIntent
import Capacitor

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    let store = ShareStore.store

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        return true
    }

    func applicationWillResignActive(_ application: UIApplication) {
        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
        // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
    }

    func applicationDidEnterBackground(_ application: UIApplication) {
        // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
        // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
    }

    func applicationWillEnterForeground(_ application: UIApplication) {
        // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
    }

    func applicationDidBecomeActive(_ application: UIApplication) {
        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    }

    func applicationWillTerminate(_ application: UIApplication) {
        // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
    }

    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
        
        var success = true
        if CAPBridge.handleOpenUrl(url, options) {
            success = ApplicationDelegateProxy.shared.application(app, open: url, options: options)
        }
        
        guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true),
              let params = components.queryItems else {
                  return false
              }
        let titles = params.filter { $0.name == "title" }
        let descriptions = params.filter { $0.name == "description" }
        let types = params.filter { $0.name == "type" }
        let urls = params.filter { $0.name == "url" }
        
        store.shareItems.removeAll()
    
        if(titles.count > 0){
            for index in 0...titles.count-1 {
                var shareItem: JSObject = JSObject()
                shareItem["title"] = titles[index].value!
                shareItem["description"] = descriptions[index].value!
                shareItem["type"] = types[index].value!
                shareItem["url"] = urls[index].value!
                store.shareItems.append(shareItem)
            }
        }
        
        store.processed = false
        let nc = NotificationCenter.default
        nc.post(name: Notification.Name("triggerSendIntent"), object: nil )
        
        return success
    }

    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
        // Called when the app was launched with an activity, including Universal Links.
        // Feel free to add additional processing here, but if you want the App API to support
        // tracking app url opens, make sure to keep this call
        return ApplicationDelegateProxy.shared.application(application, continue: userActivity, restorationHandler: restorationHandler)
    }


}
//
//  ShareViewController.swift
//  mindlib
//
//  Created by Carsten Klaffke on 05.07.20.
//

import MobileCoreServices
import Social
import UIKit

class ShareItem {
    
    public var title: String?
    public var type: String?
    public var url: String?
}

class ShareViewController: UIViewController {
    
    private var shareItems: [ShareItem] = []
    
    override public func viewDidAppear(_ animated: Bool) {
       super.viewDidAppear(animated)
       self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
    }
    
    private func sendData() {
        let queryItems = shareItems.map {
            [
                URLQueryItem(
                    name: "title",
                    value: $0.title?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
                URLQueryItem(name: "description", value: ""),
                URLQueryItem(
                    name: "type",
                    value: $0.type?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
                URLQueryItem(
                    name: "url",
                    value: $0.url?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) ?? ""),
            ]
        }.flatMap({ $0 })
        var urlComps = URLComponents(string: "com.myapp.store://")!
        urlComps.queryItems = queryItems
        openURL(urlComps.url!)
    }
    
    fileprivate func createSharedFileUrl(_ url: URL?) -> String {
        let fileManager = FileManager.default
        
        let copyFileUrl =
        fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.myapp.store.ShareExtensionGroup")!
            .absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! + url!
            .lastPathComponent.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        try? Data(contentsOf: url!).write(to: URL(string: copyFileUrl)!)
        
        return copyFileUrl
    }
    
    func saveScreenshot(_ image: UIImage, _ index: Int) -> String {
        let fileManager = FileManager.default
        
        let copyFileUrl =
        fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.com.myapp.store.ShareExtensionGroup")!
            .absoluteString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
        + "/screenshot_\(index).png"
        do {
            try image.pngData()?.write(to: URL(string: copyFileUrl)!)
            return copyFileUrl
        } catch {
            print(error.localizedDescription)
            return ""
        }
    }
    
    fileprivate func handleTypeUrl(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil)
        let url = results as! URL?
        let shareItem: ShareItem = ShareItem()
        
        if url!.isFileURL {
            shareItem.title = url!.lastPathComponent
            shareItem.type = "application/" + url!.pathExtension.lowercased()
            shareItem.url = createSharedFileUrl(url)
        } else {
            shareItem.title = url!.absoluteString
            shareItem.url = url!.absoluteString
            shareItem.type = "text/plain"
        }
        
        return shareItem
    }
    
    fileprivate func handleTypeText(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeText as String, options: nil)
        let shareItem: ShareItem = ShareItem()
        let text = results as! String
        shareItem.title = text
        shareItem.type = "text/plain"
        return shareItem
    }
    
    fileprivate func handleTypeMovie(_ attachment: NSItemProvider)
    async throws -> ShareItem
    {
        let results = try await attachment.loadItem(forTypeIdentifier: kUTTypeMovie as String, options: nil)
        let shareItem: ShareItem = ShareItem()
        
        let url = results as! URL?
        shareItem.title = url!.lastPathComponent
        shareItem.type = "video/" + url!.pathExtension.lowercased()
        shareItem.url = createSharedFileUrl(url)
        return shareItem
    }
    
    fileprivate func handleTypeImage(_ attachment: NSItemProvider, _ index: Int)
    async throws -> ShareItem
    {
        let data = try await attachment.loadItem(forTypeIdentifier: kUTTypeImage as String, options: nil)
        
        let shareItem: ShareItem = ShareItem()
            switch data {
                case let image as UIImage:
                    shareItem.title = "screenshot_\(index)"
                    shareItem.type = "image/png"
                    shareItem.url = self.saveScreenshot(image, index)
                case let url as URL:
                    shareItem.title = url.lastPathComponent
                    shareItem.type = "image/" + url.pathExtension.lowercased()
                    shareItem.url = self.createSharedFileUrl(url)
                default:
                    print("Unexpected image data:", type(of: data))
        }
        return shareItem
    }
    
    override public func viewDidLoad() {
        super.viewDidLoad()
        
        shareItems.removeAll()
        
        let extensionItem = extensionContext?.inputItems[0] as! NSExtensionItem
        Task {
            try await withThrowingTaskGroup(
                of: ShareItem.self,
                body: { taskGroup in
                    
                    for (index, attachment) in extensionItem.attachments!.enumerated() {
                        if attachment.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeUrl(attachment)
                            }
                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeText as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeText(attachment)
                            }
                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeMovie as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeMovie(attachment)
                            }
                        } else if attachment.hasItemConformingToTypeIdentifier(kUTTypeImage as String) {
                            taskGroup.addTask {
                                return try await self.handleTypeImage(attachment, index)
                            }
                        }
                    }
                    
                    for try await item in taskGroup {
                        self.shareItems.append(item)
                    }
                })
            
            self.sendData()
            
        }
    }
    
    @objc func openURL(_ url: URL) -> Bool {
        var responder: UIResponder? = self
        while responder != nil {
            if let application = responder as? UIApplication {
                return application.perform(#selector(openURL(_:)), with: url) != nil
            }
            responder = responder?.next
        }
        return false
    }
    
}
export class AppComponent {

  constructor(
  ) {
    window.addEventListener("sendIntentReceived", () => {
      console.log("sendIntent received");
      Plugins.SendIntent.checkSendIntentReceived().then((result) => {
        console.log("get SendIntent received");
        console.log(JSON.stringify(result));
      });
    });
    SendIntent.checkSendIntentReceived()
      .then((result) => {
        console.log("SendIntent received");
        console.log(JSON.stringify(result));
      })
      .catch((err) => console.error(err));
  }
}
@tobeagram
Copy link
Contributor

Hi @KevinToala, I pushed an open PR that fixes this. Hope that helps :)

@KevinToala
Copy link
Author

Hi @tobeagram

thank you so much
Your PR was helpful.
I'm leaving here the info.plist configuration so that it only supports sharing locations from Google Maps in case anyone else needs it

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>NSExtension</key>
	<dict>
		<key>NSExtensionAttributes</key>
    <dict>
      <key>NSExtensionActivationRule</key>
      <string>
          SUBQUERY(
              extensionItems,
              $extensionItem,
              SUBQUERY(
                  $extensionItem.attachments,
                  $attachment,
                  SUBQUERY(
                      $attachment.registeredTypeIdentifiers, $uti, $uti UTI-CONFORMS-TO "public.url"
                      AND NOT $uti UTI-CONFORMS-TO "public.file-url"
                  ).@count == $extensionItem.attachments.@count
              ).@count >= 1
          ).@count >= 1
          OR
          SUBQUERY(
              extensionItems,
              $extensionItem,
              SUBQUERY(
                  $extensionItem.attachments,
                  $attachment,
                  ANY $attachment.registeredTypeIdentifiers UTI-CONFORMS-TO "com.apple.mapkit.map-item"
              ).@count >= 1
          ).@count >= 1
      </string>
    </dict>
		<key>NSExtensionMainStoryboard</key>
		<string>MainInterface</string>
		<key>NSExtensionPointIdentifier</key>
		<string>com.apple.share-services</string>
	</dict>
</dict>
</plist>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants