Giter Club home page Giter Club logo

callbackurlkit's Introduction

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

callbackurlkit's People

Contributors

andreasluca avatar dispatchqueue avatar e-marchand avatar herrjemand avatar midzdotdev avatar phimage avatar pvieito avatar readmecritic avatar ssrlive avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

callbackurlkit's Issues

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)!

Release

Are you planning to release a new official version?

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?

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.

response path should be prefixed with "/" when building callback URLs

The x-success, x-failure and x-cancel callback URLs are not being constructed correctly; they are always empty strings. Thus, callbacks do not function properly.

In Manager.swift, line 199, it should be changed from:

xcuComponents.path = kResponse

to:

xcuComponents.path = "/" + kResponse

See the documentation for the NSURLComponents.URL property:

If the components object has an authority component (user, password, host, or port) and a path component, then the path must either begin with "/" or be an empty string. Otherwise, this property contains nil.

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.

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.

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)" )
	}

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.

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

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 for CallbackURLKitfrom../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

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.