Opener is an app for iOS that allows people to open web links in native apps instead. It does so by transforming web links within an engine powered by a rule set. This repo is the public version of that rule set.
The apps top level key in the manifest contains an ordered list of dictionaries, each representing an app supported by Opener. Each app contains the following fields
identifierstring: A human-readable identifier for this app, used elsewhere in the manifest.namestring: The user-facing name for this app within Opener.storeIdentifiernumber as string: The identifier of the app on the App Store.schemeURL as string: A URL containing only the scheme that will open this app.newbool: Indicates whether or not this app will be include in the "New Apps" group in Opener. Evaluates tofalseif unspecified.platformstring: Specifies if this app should only show up on iPhone/iPod Touch (value=phone) or on iPad (value=pad), shows on both if unspecified. (Opener 1.0.1 and above)
For example, if Opener were to include itself as an app
{
"identifier": "opener",
"storeIdentifier": "989565871",
"name": "Opener",
"scheme": "opener://",
"new": true
}
The actions top level key in the manifest contains a list of dictionaries, each corresponding to a web URL-to-native URL rule. There's a many-to-one relationship between the values in actions and apps.
titlestring: The user-facing title for this action.regexstring: A regular expression string that the input URL is matched against. If this regex is matched by Opener for a given input, this action will appear in the list of available opening options.includeHeadersbool: Indicates if headers should be included in the string thatregexis matched with. Iftrue, the headers are included in the input as a JSON encoded string separated from the input URL by a newline. (Opener 1.0.2 and above)formatsarray of dictionaries: Specifies the apps that an action can be opened in (see below).
Because an action could taken in multiple apps, there's an array within each action dictionary named formats. Each entry in this array matches the input URL with an app-specific output for the given action. Each of these contains the following keys.
appIdentifierstring: The identifier of the app that this action applies to. Should match theidentifierof an app.formatstring: The regex template applied to the input. Mutually exclusive withscript.
Some app native URLs can't be generated using simple regex templating, they require lookups or encoding of some sort. To do this, action formats can provide Javascript methods that are executed to convert input URLs to app native action URLs.
scriptJavascript string: Mutually exclusive withformat.
This script must contain a Javascript function named process that takes two inputs, a URL and an anonymous function to be called upon completion. Once complete, the completion handler should be called passing the result or null on failure.
For example
function process(url, completionHandler) {
// do something with URL...
url = rot13(url);
completionHandler(url);
}
Opener enforces a timeout of 15 seconds if completionHandler isn't called.
If you're planning on using the Twitter Cards tags embedded on a page, you should isolate the app's URL scheme and do something like this, which is used for Swarm.
function process(url, completionHandler) { var xmlhttp = new XMLHttpRequest(); xmlhttp.onreadystatechange = function() { if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { var res = xmlhttp.responseText; var regex = RegExp('.*(swarm:\/\/.*?)\".*'); var match = regex.exec(res)[1]; completionHandler(match); } }; xmlhttp.open('GET', url, true); xmlhttp.send(); }
If you need to URL encode something, here's an example for Overcast.
function process(url, completionHandler) { completionHandler('overcast://x-callback-url/add?url=' + encodeURIComponent(url)); }
If you need to pick a component out of the URL passed in, you can use the Javascript RegExp class.
To keep Opener maintainable, tests for actions can and should be provided.
At the action level:
testInputsarray of strings: An array of test inputs that will be run againstregexthen each action.
At the format level:
testResultsarray of strings or null entries: An array of expected results for this format for each of the test inputs.nullshould be used to specify that a test input should not match.
For example
{
...
"regex": "http(?:s)?://(?:www\\.)?foo\.bar/(\\d+).*$",
"testInputs": [
"https://foo.bar/1234"
"http://www.foo.bar/wat"
],
"formats": [
{
...
"format": "foo-app://entry/$1",
"testResults": [
"foo-app://entry/1234",
null
]
},
{
...
"script": "function process(url, completion) { completion('bar-app://' + encodeURIComponent(url)); }",
"testResults": [
"bar-app://https%3A%2F%2Ffoo.bar%2F1234",
null
]
}
]
}
Testing formats that have includeHeaders is not currently possible.
Pull requests are welcome! Because Opener is a closed source app with an experience that I'd like to keep great, I'm going to be pedantic about these requests. I will likely manipulate the order of the apps and actions that are added, and handle the new flag for them.