Implementation of x-callback-url (Inter app communication) in swift

Overview

CallbackURLKit - Inter app communication

License Platform Language Issues Cocoapod

Become a Patron! Buy me a coffee

Starting to integrate URL scheme in an app, why not be compliant with x-callback-url.

CallbackURLKit.register(action: "play") { parameters, ... in
  self.player.play()
}

Want to interact with one of the numerous other applications which implement already x-callback-url, you can also use this framework.

CallbackURLKit.perform(action: "open", urlScheme: "googlechrome-x-callback",
                             parameters: ["url": "http://www.google.com"])

Usage

Perform action on other applications

Anywhere in your code after imported CallbackURLKit you can call

try CallbackURLKit.perform(action: "actionName", urlScheme: "applicationName",
    parameters: ["key1": "value1"])

You can also use a new Manager or the shared instance

try Manager.shared.perform(action: "actionName", urlScheme: "application-name",
    parameters: ["key1": "value1"])

Declare targeted applications URL schemes in iOS9

You must whitelist any URL schemes your app wants to query in Info.plist under the LSApplicationQueriesSchemes key (an array of strings)

xcode-white-list

Create a client class

Alternatively you can create a new Client object where you can define the targeted app URL scheme.

let client = Client(urlScheme: "application-url-scheme")
try client.perform(action:(..)

or create a new Client class to add some utility methods which hide implementation details and allow to make some parameters check.

class GoogleChrome: Client {
  init() {
    super.init(urlScheme:"googlechrome-x-callback")
  }
  func open(url: String, ...) {
    self.perform(action: "open", parameters: ["url": url], ...)
  }
}

see full sample into Clients directory. (You can PR to add new Client classes)

You can check that an application respond to the URL scheme using client.appInstalled

Use callback closure

Callbacks allow you to receive data from the target applications, but before doing this you need to add x-callback-url support to your app.

Then you can specify one of the 3 x-callbacks: success, failure and cancel.

try client.perform(action: "actionName",
    onSuccess: { parameters in
    },

onFailure: { error in
    },
    onCancel: {
    }
)

Add x-callback-url support to your app

Register your URL scheme

Apps can declare any custom URL schemes they support. Use the URL Types section of the Info tab to specify the custom URL schemes that your app handles. xcode

Or register your URL Scheme directly into Info.plist file:

  • Add URL Types, then within Item 0
  • Set Document Role to Editor.
  • URL Identifier to your app bundle ID.
  • Create a URL Schemes field and set Item 0 to the URL Scheme you want to use.

Apple documentation: Using URL Schemes to Communicate with Apps.

Setup framework with URL scheme

Then you must set the scheme to this framework manager

let manager =  Manager.shared
manager.callbackURLScheme = "my-application-scheme"
// or get the first from Info.plist using utility method
manager.callbackURLScheme = Manager.urlSchemes?.first

Handle incoming URLs

And finally to handle incoming URLs, your application delegate should implement the following methods below.

On iOS

func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
    manager.handleOpen(url: url)
    return true
}

On OSX if you have no other need with URL events you can let manager do all the job by calling into applicationDidFinishLaunching the method Manager.instance.registerToURLEvent()

Add new action

The client application will interact with your application using the following URL Structure.

[url-scheme]://x-callback-url/[action]?[x-callback parameters]&[action parameters]

An action is defined by its name (the url path), and optional action parameters.

manager["myActionName"] = { parameters, success, failure, cancel in
  // action code, using or not the parameters
  ...
  // notify the client app with one of the callback
  success(nil) // or failure(exception)
}

You can also register an action on shared Manager instance using

CallbackURLKit.register(action: "myActionName") { parameters, success, failure, cancel in

}

Installation

Using CocoaPods

CocoaPods is a centralized dependency manager for Objective-C and Swift. Go here to learn more.

  1. Add the project to your Podfile.

    use_frameworks!
    
    pod 'CallbackURLKit'
  2. Run pod install and open the .xcworkspace file to launch Xcode.

Clients

To get an already implemented Clients use one of the subspec

pod 'CallbackURLKit/GoogleChrome'
pod 'CallbackURLKit/Ulysses' // https://ulyssesapp.com

Links

Comments
  • Unable to install CallbackURLKit via CocoaPods

    Unable to install CallbackURLKit via CocoaPods

    Hello,

    I've tried to run the Demo App but after running: "pod install" I get the following error:

    andreasluca:SampleApp lucaandr$ pod install Analyzing dependencies Fetching podspec forCallbackURLKitfrom../CallbackURLKit.podspec[!] CocoaPods could not find compatible versions for pod "CallbackURLKit/GoogleChrome": In Podfile: CallbackURLKit/GoogleChrome (from../CallbackURLKit.podspec`)

    Specs satisfying the CallbackURLKit/GoogleChrome (from../CallbackURLKit.podspec) dependency were found, but they required a higher minimum deployment target.`

    Do you know why is that?

    Thank you

    opened by andreasluca 12
  • Two way communication

    Two way communication

    How would you implement two way communication described in http://x-callback-url.com/examples/

    Something like:

    targetapp://x-callback-url/translate? x-success=sourceapp://x-callback-url/acceptTranslation& x-source=SourceApp& x-error=sourceapp://x-callback-url/translationError& word=Hello& language=Spanish

    question 
    opened by herrjemand 12
  • client.perform() never reaches onSuccess

    client.perform() never reaches onSuccess

    I've successfully managed to send an action to Bear (MacOS, note taking app) using the code below that appends text to a note but I'm struggling with return parameters. I should be getting not contents back but onSuccess never gets called. Any thoughts on what I may be missing?

    	let manager =  Manager.shared
    	manager.callbackURLScheme = "bear"
    
    	let client = Client(urlScheme: "bear")
    
    	do {
    		try client.perform(
    					action: "add-text",
    					parameters: ["title": "BearNote", "text" : "some text", "mode" : "append" ],
    					onSuccess: { parameters in  
                                                       // never gets here
    						print( "Succeed" )
    					},
    					onFailure: { error in
    						print( "Fail" )
    					},
    					onCancel: {
    						print( "Cancelled" )
    					}
    			)
    	} catch {
    		print( "Error: \(error)" )
    	}
    
    question 
    opened by kimaldis 9
  • Invoking an action on a locked device doesn't work

    Invoking an action on a locked device doesn't work

    I tried to use CallbackURLKit on a Today Extension Widget. When one button on the widget is pressed, it should invoke an action on the main application via a url action.

    try? Manager.perform(action: "playcollection", urlScheme: "bingobongo", parameters: parameters)

    This works when invoking the action from the extension with the device unlocked, but if the device is locked the system doesn't ask to unlock and simply disregards the action.

    The problem comes from Manager.open(url: Foundation.URL) that calls: UIApplication.shared.openURL(url)

    To let the device ask for the unlock code and then invoke the action, there's a similar method on NSExtensionContext: func open(_ URL: URL, completionHandler: ((Bool) -> Void)? = nil)

    The same issue is descibed here:

    http://stackoverflow.com/questions/26909821/ios-8-today-widget-and-locked-device

    It would be useful to inject the current NSExtensionContext in a non-default Manager object, that when extensionContext is not nil will invoke this method instead of the one from UIApplication.

    opened by francosolerio 5
  • macOS 10.13 Issue

    macOS 10.13 Issue

    I'm using your framework in a project running on the 10.13 beta and while it sends x-callbacks without a problem to an installed app (Ulysses, a writing program that has great x-callback support), I don't receive the callback. It's like my registration is ignored.

    There's not much code...

    The Ulysses App Client:

    import Cocoa
    import os.log
    import CallbackURLKit
    
    public class UlyssesClient: Client {
        
        var ulyssesAuthSuccess = ""
        var ulyssesAuthError = ""
        var ulyssesScheme = "ulysses"
        
        init() {
            super.init(urlScheme:"ulysses")
            ulyssesAuthSuccess = "fictivity://x-callback-url/auth-success"//.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
            ulyssesAuthError = "fictivity://x-callback-url/auth-error"//.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!
            //os_log("%@", ulyssesURLString)
            
            CallbackURLKit.register(action: "auth-success") { parameters, success, failure, cancel in
                os_log("auth-success")
                for parameter in parameters {
                    os_log("%@ = %@", parameter.key, parameter.value)
                }
            }
            
            CallbackURLKit.register(action: "auth-error") { parameters, success, failure, cancel in
                os_log("auth-error")
                for parameter in parameters {
                    os_log("%@ = %@", parameter.key, parameter.value)
                }
            }
        }
        
        func authorize(onSuccess: SuccessCallback? = nil, onFailure: FailureCallback? = nil, onCancel: CancelCallback? = nil) throws {
            try self.perform(action: "authorize", parameters: ["appname": "Fictivity", "x-success": ulyssesAuthSuccess, "x-error": ulyssesAuthError], onSuccess: onSuccess, onFailure: onFailure, onCancel: onCancel)
        }
    }
    

    In AppDelegate:

    func applicationDidFinishLaunching(_ aNotification: Notification) {
             let manager =  Manager.shared
            manager.callbackURLScheme = Manager.urlSchemes?.first
            Manager.shared.registerToURLEvent()
        }
    

    I did try changing the url scheme name, and confirmed that Ulysses give an error that there isn't any support for that url scheme. So, Ulysses is replying, the reply is just not propagating.

    Any ideas?

    opened by Radagan 4
  • Ampersands in callback URL query strings should be percent-encoded.

    Ampersands in callback URL query strings should be percent-encoded.

    In Manager.swift, in the Manager.sendRequest(_:) method, the x-success, x-failure and x-cancel parameters to the outgoing URL include their own query strings that include responseType and requestID parameters as well as any response parameters for a given action.

    The ampersand characters (&) that delimit these embedded query strings should be percent-encoded; otherwise, they get parsed as query parameters of the outgoing URL, instead of the response URLs.

    A typical URL that is built by this method looks like this, just before calling Manager.openURL(URL) (in line 228):

    otherapp://x-callback-url/actionName?x-source=MyAppName&
      x-error=myapp://x-callback-url/response?responseType=error&requestID=9CE4D406-EB4E-4242-8ACD-14851A81A588&
      x-cancel=myapp://x-callback-url/response?responseType=cancel&requestID=9CE4D406-EB4E-4242-8ACD-14851A81A588&
      x-success=myapp://x-callback-url/response?responseType=success&requestID=9CE4D406-EB4E-4242-8ACD-14851A81A588
    

    The problem is that the requestID parameter gets stripped from the response URLs, thus the Manager.handleOpenURL(_:) method fails to match up the response to its request (on line 77) and does not call the response handler closures.

    bug 
    opened by cpatterson-lilly 4
  • Swift Package will not build in Xcode 13 (beta 3+)

    Swift Package will not build in Xcode 13 (beta 3+)

    First off, thank you for this excellent package and the amount of ease and sanity it introduces when handling callback URLs!

    It seems that either Apple or the Swift project made a breaking change in the latest Xcode beta release which affects CallbackURLKit. The change is documented here: https://forums.swift.org/t/set-application-extension-api-only-on-a-spm-package/39333/11

    In short, it means that when compiling a Swift Package, it no longer matters if a main app or an extension is being targeted, but instead the code itself needs to declare whether a method is available for extensions or not, on top of everything else. In CallbackURLKit, this breaks at the point where the code tries to use UIApplication.shared.open(url: ...). While I and I'm sure many others probably think of this change as a bit reckless, it may be something that the code in the package needs to deal with.

    The solution, thankfully, is relatively simple. The code needs to have the annotation @available(iOSApplicationExtension, unavailable) around any method that cannot be called from an app extension. In the case of CallbackURLKit, it would just involve "babushka"ing that annotation on the method that opens URLs and the methods that use it in turn.

    Please also see the discussion in the Apple Dev Forums here for reference: https://developer.apple.com/forums/thread/685103

    I hope this is of some help. Thank you once again for the brilliant and super-useful package.

    opened by ptsochantaris 3
  • Explicit handlers for success and error callbacks

    Explicit handlers for success and error callbacks

    This is not so much of a problem, but more of a question (not sure where else to ask). It's my first time writing swift and working with xcode so apologies if this looks trivial:

    Here's my set up: I have added a URL type to my app:

    image

    I created a custom client for the Bear app:

    class BearClient: Client {
        init() {
            super.init(urlScheme: "bear")
        }
        public func search(query: String) {
            let parameters = ["term": query]
            do {
                try self.perform(
                    action: "search",
                    parameters: parameters,
                    onSuccess: { parameters in
                        print(parameters?["notes"])
                    },
                    onFailure: {error in
                        print(error)
                    },
                    onCancel: {
                        print("cancelled")
                    }
                )
            } catch {
    //            NOOP
            }
        }
    }
    

    I have the manager initialized:

    import SwiftUI
    import CallbackURLKit
    
    let manager = Manager.shared
    
    @main
    struct LookBackApp: App {
        
        init() {
            manager.callbackURLScheme = "lookback"
        }
        
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .onOpenURL(perform: { url in
                       _ = manager.handleOpen(url: url)
                    })
            }
        }
    }
    
    

    When I call the client like so:

            let client = BearClient()
            client.search(query: "the")
    

    the client runs (and the Bear app opens with the right search query) but the onSuccess callback is never called.

    What am I missing? I was assuming I don't create any specific handlers for success and error on the manager because that is not documented.

    opened by dshomoye 3
  • Fixed `errorCode` typo

    Fixed `errorCode` typo

    opened by pvieito 1
  • Update Demo App

    Update Demo App

    Hello,

    As a proposal, I'd suggest the following:

    • Updating the Demo App to Swift 4.2
    • Resolving the warnings that appear in Xcode 10
    • To me it does not make a lot of sense the Demo App to have Pod dependencies when all the files are already in the repo. Wouldn't it be better to have the App compiling the local sources instead of having a pod dependency?

    Thanks (it's just a suggestion)!

    opened by andreasluca 1
  • Attempt to fix callback URL query args in issue #3.

    Attempt to fix callback URL query args in issue #3.

    These changes look like they should work, but the resulting callback URL query args are now over-encoded somehow. Instead of converting each "&" to a single "%26", they get converted to "%252526" instead, meaning it's getting percent-encoded one extra time.

    opened by cpatterson-lilly 1
Releases(3.0.0)
Owner
Eric Marchand
🧙🍺🥊🥋🏓
Eric Marchand
A Swift wrapper for URL bookmarks which allow a file to be located regardless of whether it is moved or renamed.

Bookmark A Swift wrapper for URL bookmarks which allow a file to be located regardless of whether it is moved or renamed. This class wraps Swift's URL

Darren Ford 8 Nov 3, 2022
It makes a preview from an URL, grabbing all the information such as title, relevant texts and images.

Link Previewer for iOS, macOS, watchOS and tvOS It makes a preview from an URL, grabbing all the information such as title, relevant texts and images.

Leonardo Cardoso 1.3k Jan 2, 2023
SMAP: Swiss Topo Map URL Generator

smap - Swiss Topo Map URL Generator Usage: smap [-b] <image-file-path> Reads fil

Jean-Nicolas 0 Dec 29, 2021
Swift Server Implementation - RESTful APIs, AWS Lambda Serverless For Swift Runtime amazonlinux: AWS Lambda + API Gateway

Swift Server Implementation - RESTful APIs, AWS Lambda Serverless For Swift Runtime amazonlinux: AWS Lambda + API Gateway deployed on Graviton arm64 build swift:5.6.2-amazonlinux2-docker image

Furqan 2 Aug 16, 2022
Swift implementation of AWS Lambda Events

Swift AWS Lambda Events Overview Swift AWS Lambda Runtime was designed to make building Lambda functions in Swift simple and safe. The library is an i

Swift on Server 29 Dec 19, 2022
SwiftRedux is a Swift implementation of the Redux state container

SwiftRedux is a Swift implementation of the Redux state container. It relies on the same concepts and provides familiar Hooks through property wrappers.

Lucas Lima 8 Feb 1, 2022
Swift implementation of the QOI Format

Swift-QOI Swift implementation of the QOI Format. Contains extensions for AppKit and UIKit to integrate into your projects with ease. Documentation //

Amy While 18 Oct 21, 2022
Swift implementation of the QOI Format

Swift-QOI Swift implementation of the QOI Format. Contains extensions for AppKit and UIKit to integrate into your projects with ease. Documentation //

Amy While 1 Dec 9, 2021
The sample implementation of zip-archived document for a macOS AppKit platform.

The sample implementation of zip-archived document for a macOS AppKit platform. You can implement NSDocument-based I/O of archived document in your application like .sketch or .key.

usagimaru 4 Nov 12, 2022
A utility that reminds your iPhone app's users to review the app written in pure Swift.

SwiftRater SwiftRater is a class that you can drop into any iPhone app that will help remind your users to review your app on the App Store/in your ap

Takeshi Fujiki 289 Dec 12, 2022
A Flutter plugin (platform channel with Swift) to get the current app name and also bring our app to the front.

window_to_front A new flutter plugin project. Getting Started This project is a starting point for a Flutter plug-in package, a specialized package th

Minas Giannekas 1 Nov 13, 2021
TypeStyle is a handy app for iPhone and iPad that generates text using different styles and decorations. It is a native Swift iOS app.

TypeStyle TypeStyle is a handy app for iPhone and iPad that generates text using different styles and decorations. It is a native Swift iOS app. Featu

Eugene Belinski 31 Dec 14, 2022
Mac app to change .ipa file app icons and display names

IPAEdit Mac app to change .ipa file app icon, display name, and app version to avoid updates Compatible with macOS 10.11+ Install To install either cl

Ethan Goodhart 23 Dec 28, 2022
A way to easily add Cocoapod licenses and App Version to your iOS App using the Settings Bundle

EasyAbout Requirements: cocoapods version 1.4.0 or above. Why you should use Well, it is always nice to give credit to the ones who helped you ?? Bonu

João Mourato 54 Apr 6, 2022
This iOS framework allows settings to be in-app in addition to or instead of being in the Settings app.

InAppSettingsKit InAppSettingsKit (IASK) is an open source framework to easily add in-app settings to your iOS or Catalyst apps. Normally iOS apps use

Ortwin Gentz, FutureTap 3.1k Jan 2, 2023
This is a command line tool to extract an app icon. this sample will extract the icon 16x16 from Safari app.

?? X-BundleIcon This is a command line tool to extract an app icon. this sample will extract the icon 16x16 from Safari app. xbi com.apple.Safari 16 /

Rui Aureliano 3 Sep 1, 2022
A Swift app, named 'iPose', for iPhone's pose measurement based on Swift.

iPhone's pose measurement based on Swift. This is a Swift app, named 'iPose', for iPhone's pose measurement based on Swift. This is a side project to

Ghasem Abdi 3 Jul 26, 2022
A simple Pokedex app written in Swift that implements the PokeAPI, using Combine and data driven UI.

SwiftPokedex SwiftPokedex is a simple Pokedex app written by Viktor Gidlöf in Swift that implements the PokeAPI. For full documentation and implementa

Viktor G 26 Dec 14, 2022
This is a app developed in Swift, using Object Oriented Programing, UIKit user interface programmatically, API Request and Kingfisher to load remote images

iOS NOW ⭐ This is a app developed in Swift, using Object Oriented Programing, UIKit user interface programmatically, API Request and Kingfisher to loa

William Tristão de Paula 1 Dec 7, 2021