Repository: ionic-team/ionic-plugin-deeplinks
Branch: master
Commit: bbbbf108c046
Files: 12
Total size: 40.6 KB
Directory structure:
gitextract_glkpx5o0/
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── package.json
├── plugin.xml
├── src/
│ ├── android/
│ │ └── io/
│ │ └── ionic/
│ │ └── links/
│ │ └── IonicDeeplink.java
│ ├── browser/
│ │ └── DeeplinkProxy.js
│ └── ios/
│ ├── AppDelegate+IonicDeeplink.m
│ ├── IonicDeeplinkPlugin.h
│ └── IonicDeeplinkPlugin.m
└── www/
└── deeplink.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
node_modules/
package-lock.json
================================================
FILE: .npmrc
================================================
registry=https://registry.npmjs.org/
================================================
FILE: LICENSE
================================================
Copyright 2016-present Drifty Co.
http://drifty.com/
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
================================================
FILE: README.md
================================================
# Community Maintained
This plugin is being maintained by the Ionic community. Interested in helping? Message `max` on ionic worldwide slack.
Another great solution for deep links for Ionic is the Branch Metrics plugin: [https://github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking](https://github.com/BranchMetrics/cordova-ionic-phonegap-branch-deep-linking)
If you used to handle URI schemes with the help of this plugin and have migrated to Branch Metrics, you can make use of a plugin such as [https://github.com/EddyVerbruggen/Custom-URL-scheme](https://github.com/EddyVerbruggen/Custom-URL-scheme) to facilitate custom URL schemes.
Ionic Deeplinks Plugin
======
This plugin makes it easy to respond to deeplinks through custom URL schemes
and Universal/App Links on iOS and Android.
For example, you can have your app open through a link to https://yoursite.com/product/cool-beans and then navigate
to display the Cool Beans in your app (cool beans!).
Additionally, on Android iOS, your app can be opened through a custom URL scheme, like `coolbeans://product/cool-beans`.
Since Custom URL scheme behavior has changed quite a bit in iOS 9.2 for the case where the app isn't installed, you'll want to start using [Universal Links](#ios-configuration) as it's clear custom URL schemes are on the way out.
*Note: this plugin may clash with existing Custom URL Scheme and Universal Links Plugins. Please let
us know if you encounter compatibility issues. Also, try removing them and using this one on its own.*
Thank you to the [Cordova Universal Links Plugin](https://github.com/nordnet/cordova-universal-links-plugin) and the [Custom URL Scheme](https://github.com/EddyVerbruggen/Custom-URL-scheme) plugin that this plugin is inspired and borrows from.
## Installation
```bash
cordova plugin add ionic-plugin-deeplinks
--variable URL_SCHEME=myapp --variable DEEPLINK_SCHEME=https --variable DEEPLINK_HOST=example.com
--variable ANDROID_PATH_PREFIX=/
```
Fill in the appropriate values as shown below:
* `URL_SCHEME` - the custom URL scheme you'd like to use for your app. This lets your app respond to links like `myapp://blah`
* `DEEPLINK_SCHEME` - the scheme to use for universal/app links. Defaults to 'https' in 1.0.13. 99% of the time you'll use `https` here as iOS and Android require SSL for app links domains.
* `DEEPLINK_HOST` - the host that will respond to deeplinks. For example, if we want `example.com/product/cool-beans` to open in our app, we'd use `example.com` here.
* `ANDROID_PATH_PREFIX` - (optional): specify which path prefix our Android app should open from [more info](https://developer.android.com/guide/topics/manifest/data-element.html)
(New in 1.0.13): If you'd like to support multiple hosts for Android, you can also set the variables `DEEPLINK_2_SCHEME`, `DEEPLINK_2_HOST`, `ANDROID_2_PATH_PREFIX` and optionally substitue `2` with 3, 4, and 5 to set more.
## Handling Deeplinks in JavaScript
#### Ionic/Angular 2
*note: make sure to call IonicDeeplink from a platform.ready or `deviceready` event*
Using [Ionic Native](https://github.com/ionic-team/ionic-native) (available in 1.2.4 or greater):
```javascript
import { Platform, NavController } from 'ionic-angular';
import { Deeplinks } from '@ionic-native/deeplinks/ngx';
export class MyApp {
constructor(
protected platform: Platform
, protected navController: NavController
, protected deeplinks: Deeplinks
) {
this.platform.ready().then(() => {
this.deeplinks.route({
'/about-us': HomePage,
'/products/:productId': HelpPage
}).subscribe((match) => {
// match.$route - the route we matched, which is the matched entry from the arguments to route()
// match.$args - the args passed in the link
// match.$link - the full link data
console.log('Successfully matched route', match);
},
(nomatch) => {
// nomatch.$link - the full link data
console.error('Got a deeplink that didn\'t match', nomatch);
});
});
}
}
// Note: routeWithNavController returns an observable from Ionic Native so it *must* be subscribed to first in order to trigger.
```
If you're using Ionic 2, there is a convenience method to route automatically (see the simple [Ionic 2 Deeplinks](https://github.com/ionic-team/ionic2-deeplinks-demo/blob/master/app/app.ts) demo for an example):
```javascript
import { Platform, NavController } from 'ionic-angular';
import { Deeplinks } from '@ionic-native/deeplinks/ngx';
export class MyApp {
constructor(
protected platform: Platform
, protected navController: NavController
, protected deeplinks: Deeplinks
) {
this.platform.ready().then(() => {
this.deeplinks.routeWithNavController(this.navController, {
'/about-us': HomePage,
'/products/:productId': HelpPage
}).subscribe((match) => {
// match.$route - the route we matched, which is the matched entry from the arguments to route()
// match.$args - the args passed in the link
// match.$link - the full link data
console.log('Successfully matched route', match);
},
(nomatch) => {
// nomatch.$link - the full link data
console.error('Got a deeplink that didn\'t match', nomatch);
});
});
}
}
// Note: routeWithNavController returns an observable from Ionic Native so it *must* be subscribed to first in order to trigger.
```
#### Ionic/Angular 1
For Ionic 1 and Angular 1 apps using Ionic Native, there are many ways we can handle deeplinks. However,
we need to make sure we set up a history stack for the user, we can't navigate directly to our page
because Ionic 1's navigation system won't properly build the navigation stack (to show a back button, for example).
This is all fine because deeplinks should provide the user with a designed experience for what the back button
should do, as we are putting them deep into the app and need to provide a natural way back to the main flow:
(See a simple [demo](https://github.com/ionic-team/ionic1-deeplinks-demo) of v1 deeplinking).
```javascript
angular.module('myApp', ['ionic', 'ionic.native'])
.run(['$ionicPlatform', '$cordovaDeeplinks', '$state', '$timeout', function($ionicPlatform, $cordovaDeeplinks, $state, $timeout) {
$ionicPlatform.ready(function() {
// Note: route's first argument can take any kind of object as its data,
// and will send along the matching object if the route matches the deeplink
$cordovaDeeplinks.route({
'/product/:productId': {
target: 'product',
parent: 'products'
}
}).subscribe(function(match) {
// One of our routes matched, we will quickly navigate to our parent
// view to give the user a natural back button flow
$timeout(function() {
$state.go(match.$route.parent, match.$args);
// Finally, we will navigate to the deeplink page. Now the user has
// the 'product' view visibile, and the back button goes back to the
// 'products' view.
$timeout(function() {
$state.go(match.$route.target, match.$args);
}, 800);
}, 100); // Timeouts can be tweaked to customize the feel of the deeplink
}, function(nomatch) {
console.warn('No match', nomatch);
});
});
}])
```
#### Non-Ionic/angular
Ionic Native works with non-Ionic/Angular projects and can be accessed at `window.IonicNative` if imported.
If you don't want to use Ionic Native, the plugin is available on `window.IonicDeeplink` with a similar API minus the observable callback:
```javascript
window.addEventListener('deviceready', function() {
IonicDeeplink.route({
'/product/:productId': {
target: 'product',
parent: 'products'
}
}, function(match) {
}, function(nomatch) {
});
})
```
## iOS Configuration
As of iOS 9.2, Universal Links *must* be enabled in order to deep link to your app. Custom URL schemes are no longer supported.
Follow the official [Universal Links](https://developer.apple.com/library/ios/documentation/General/Conceptual/AppSearch/UniversalLinks.html) guide on the Apple Developer docs
to set up your domain to allow Universal Links.
### How to set up top-level domains (TLD's)
#### Set up Associated Domains
First you must enable the `Associated Domains` capability in your [provisioning profile](https://developer.apple.com/account/resources/profiles/list).
After that you must enable it in the Xcode project, too.
For automated builds you can do it easily by adding this to your `config.xml`.
<config-file target="*-Debug.plist" parent="com.apple.developer.associated-domains">
<array>
<string>applinks:example.org</string>
</array>
</config-file>
<config-file target="*-Release.plist" parent="com.apple.developer.associated-domains">
<array>
<string>applinks:example.org</string>
</array>
</config-file>
Instead of `applinks` only you could use `<string>webcredentials:example.org</string>` or `<string>activitycontinuation:example.org</string>`, too.
#### Set up Apple App Site Association (AASA)
Your website (i.e. `example.org`) must provide this both files.
* /apple-app-site-association
* /.well-known/apple-app-site-association
The content should contain your app.
{
"applinks": {
"apps": [],
"details": [
{
"appID": "1A234BCD56.org.example",
"paths": [
"NOT \/api\/*",
"NOT \/",
"*"
]
}
]
}
}
This means that all your requests - except /api and / - will be redirected to your app.
Please replace `1A234BCD56` with your TEAM ID and `org.example` with your Bundle-ID. (the `id=""` of your `<widget />`)
## Android Configuration
Android supports Custom URL Scheme links, and as of Android 6.0 supports a similar feature to iOS' Universal Links called App Links.
Follow the App Links documentation on [Declaring Website Associations](https://developer.android.com/training/app-links/index.html#web-assoc) to enable your domain to
deeplink to your Android app.
To prevent Android from creating multiple app instances when opening deeplinks, you can add the following preference in Cordova config.xml file:
```xml
<preference name="AndroidLaunchMode" value="singleTask" />
```
### How to set up top-level domains (TLD's)
#### Set up [Android App Links](https://developer.android.com/training/app-links)
Your website (i.e. `example.org`) must provide this file.
* /.well-known/assetlinks.json
The content should contain your app.
[
{
"relation": [
"delegate_permission\/common.handle_all_urls"
],
"target": {
"namespace": "android_app",
"package_name": "org.example",
"sha256_cert_fingerprints": [
"12:A3:BC:D4:56:E7:89:F0:12:34:5A:B6:78:90:C1:23:45:DE:67:FA:89:01:2B:C3:45:67:8D:9E:0F:1A:2B:C3"
]
}
}
]
Replace `org.example` with your app package. (the `id=""` of your `<widget />`)
The fingerprints you can get via `$ keytool -list -v -keystore my-release-key.keystore`.
You can test it via https://developers.google.com/digital-asset-links/tools/generator.
================================================
FILE: package.json
================================================
{
"name": "ionic-plugin-deeplinks",
"version": "1.0.24",
"cordova": {
"id": "ionic-plugin-deeplinks",
"platforms": [
"android",
"ios"
]
},
"description": "Ionic Deeplinks Plugin",
"repository": "https://github.com/ionic-team/ionic-plugin-deeplinks.git",
"issue": "https://github.com/ionic-team/ionic-plugin-deeplinks/issues",
"keywords": [
"ionic",
"cordova",
"deeplink",
"deeplinks",
"mobile",
"hybrid",
"ecosystem:cordova",
"cordova-android",
"cordova-ios"
],
"dependencies": {
"mkpath": ">=1.0.0",
"xml2js": ">=0.4",
"node-version-compare": ">=1.0.1",
"plist": ">=1.2.0"
},
"author": "Max Lynch <max@ionic.io>",
"license": "MIT"
}
================================================
FILE: plugin.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="ionic-plugin-deeplinks" version="1.0.22">
<name>Ionic Deeplink Plugin</name>
<description>Ionic Deeplink Plugin</description>
<license>MIT</license>
<keywords>Ionic,deeplinks,deeplinking</keywords>
<repo>https://github.com/ionic-team/ionic-plugin-deeplink.git</repo>
<issue>https://github.com/ionic-team/ionic-plugin-deeplink/issues</issue>
<preference name="URL_SCHEME" />
<preference name="DEEPLINK_SCHEME" default="https" />
<preference name="DEEPLINK_HOST" default="" />
<!-- android -->
<platform name="android">
<preference name="ANDROID_PATH_PREFIX" default="/" />
<preference name="ANDROID_2_PATH_PREFIX" default="/" />
<preference name="ANDROID_3_PATH_PREFIX" default="/" />
<preference name="ANDROID_4_PATH_PREFIX" default="/" />
<preference name="ANDROID_5_PATH_PREFIX" default="/" />
<preference name="DEEPLINK_2_SCHEME" default=" " />
<preference name="DEEPLINK_2_HOST" default=" " />
<preference name="DEEPLINK_3_SCHEME" default=" " />
<preference name="DEEPLINK_3_HOST" default=" " />
<preference name="DEEPLINK_4_SCHEME" default=" " />
<preference name="DEEPLINK_4_HOST" default=" " />
<preference name="DEEPLINK_5_SCHEME" default=" " />
<preference name="DEEPLINK_5_HOST" default=" " />
<js-module src="www/deeplink.js" name="deeplink">
<runs/>
<clobbers target="IonicDeeplink" />
</js-module>
<config-file target="res/xml/config.xml" parent="/*">
<feature name="IonicDeeplinkPlugin">
<param name="android-package" value="io.ionic.links.IonicDeeplink" />
<param name="onload" value="true" />
</feature>
</config-file>
<config-file target="AndroidManifest.xml" parent="/manifest/application/activity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="$URL_SCHEME" />
</intent-filter>
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="$DEEPLINK_SCHEME" android:host="$DEEPLINK_HOST" android:pathPrefix="$ANDROID_PATH_PREFIX" />
<data android:scheme="$DEEPLINK_2_SCHEME" android:host="$DEEPLINK_2_HOST" android:pathPrefix="$ANDROID_2_PATH_PREFIX" />
<data android:scheme="$DEEPLINK_3_SCHEME" android:host="$DEEPLINK_3_HOST" android:pathPrefix="$ANDROID_3_PATH_PREFIX" />
<data android:scheme="$DEEPLINK_4_SCHEME" android:host="$DEEPLINK_4_HOST" android:pathPrefix="$ANDROID_4_PATH_PREFIX" />
<data android:scheme="$DEEPLINK_5_SCHEME" android:host="$DEEPLINK_5_HOST" android:pathPrefix="$ANDROID_5_PATH_PREFIX" />
</intent-filter>
</config-file>
<source-file src="src/android/io/ionic/links/IonicDeeplink.java" target-dir="src/io/ionic/deeplink" />
</platform>
<!-- ios -->
<platform name="ios">
<js-module src="www/deeplink.js" name="deeplink">
<runs/>
<clobbers target="IonicDeeplink" />
</js-module>
<config-file target="config.xml" parent="/*">
<feature name="IonicDeeplinkPlugin">
<param name="ios-package" value="IonicDeeplinkPlugin" onload="true" />
</feature>
<preference name="URL_SCHEME" value="$URL_SCHEME" />
<preference name="DEEPLINK_SCHEME" value="$DEEPLINK_SCHEME" />
<preference name="DEEPLINK_HOST" value="$DEEPLINK_HOST" />
</config-file>
<config-file target="*-Info.plist" parent="CFBundleURLTypes">
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>$URL_SCHEME</string>
</array>
</dict>
</array>
</config-file>
<source-file src="src/ios/AppDelegate+IonicDeeplink.m" />
<header-file src="src/ios/IonicDeeplinkPlugin.h" />
<source-file src="src/ios/IonicDeeplinkPlugin.m" />
</platform>
<platform name="browser">
<js-module src="www/deeplink.js" name="deeplink">
<runs/>
<clobbers target="IonicDeeplink" />
</js-module>
<js-module src="src/browser/DeeplinkProxy.js" name="IonicDeeplinkProxy">
<runs />
</js-module>
</platform>
</plugin>
================================================
FILE: src/android/io/ionic/links/IonicDeeplink.java
================================================
/**
* Ionic Deeplinks Plugin.
* License: MIT
*
* Thanks to Eddy Verbruggen and nordnet for the great custom URl scheme and
* unviversal links plugins this plugin was inspired by.
*
* https://github.com/EddyVerbruggen/Custom-URL-scheme
* https://github.com/nordnet/cordova-universal-links-plugin
*/
package io.ionic.links;
import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaInterface;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CordovaWebView;
import org.apache.cordova.PluginResult;
import org.apache.cordova.PluginResult.Status;
import org.json.JSONObject;
import org.json.JSONArray;
import org.json.JSONException;
import java.util.Date;
import java.util.TimeZone;
import android.util.Log;
import android.content.Intent;
import android.content.Context;
import android.graphics.Rect;
import android.util.DisplayMetrics;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.inputmethod.InputMethodManager;
import android.telephony.TelephonyManager;
import android.net.Uri;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.provider.Settings;
import java.util.ArrayList;
import java.util.Set;
import java.util.Collection;
import java.util.Map;
public class IonicDeeplink extends CordovaPlugin {
private static final String TAG = "IonicDeeplinkPlugin";
private JSONObject lastEvent;
private ArrayList<CallbackContext> _handlers = new ArrayList<CallbackContext>();
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
Log.d(TAG, "IonicDeepLinkPlugin: firing up...");
handleIntent(cordova.getActivity().getIntent());
}
@Override
public void onNewIntent(Intent intent) {
handleIntent(intent);
}
public void handleIntent(Intent intent) {
final String intentString = intent.getDataString();
// read intent
String action = intent.getAction();
Uri url = intent.getData();
JSONObject bundleData = this._bundleToJson(intent.getExtras());
Log.d(TAG, "Got a new intent: " + intentString + " " + intent.getScheme() + " " + action + " " + url);
// if app was not launched by the url - ignore
if (!Intent.ACTION_VIEW.equals(action) || url == null) {
return;
}
// store message and try to consume it
try {
lastEvent = new JSONObject();
lastEvent.put("url", url.toString());
lastEvent.put("path", url.getPath());
lastEvent.put("queryString", url.getQuery());
lastEvent.put("scheme", url.getScheme());
lastEvent.put("host", url.getHost());
lastEvent.put("fragment", url.getFragment());
lastEvent.put("extra", bundleData);
consumeEvents();
} catch(JSONException ex) {
Log.e(TAG, "Unable to process URL scheme deeplink", ex);
}
}
public boolean execute(String action, JSONArray args, final CallbackContext callbackContext) throws JSONException {
if(action.equals("onDeepLink")) {
addHandler(args, callbackContext);
} else if(action.equals("canOpenApp")) {
String uri = args.getString(0);
canOpenApp(uri, callbackContext);
} else if(action.equals("getHardwareInfo")) {
getHardwareInfo(args, callbackContext);
}
return true;
}
/**
* Try to consume any waiting intent events by sending them to our plugin
* handlers. We will only do this if we have active handlers so the message isn't lost.
*/
private void consumeEvents() {
if(this._handlers.size() == 0 || lastEvent == null) {
return;
}
for(CallbackContext callback : this._handlers) {
sendToJs(lastEvent, callback);
}
lastEvent = null;
}
private void sendToJs(JSONObject event, CallbackContext callback) {
final PluginResult result = new PluginResult(PluginResult.Status.OK, event);
result.setKeepCallback(true);
callback.sendPluginResult(result);
}
private void addHandler(JSONArray args, final CallbackContext callbackContext) {
this._handlers.add(callbackContext);
this.consumeEvents();
}
private JSONObject _bundleToJson(Bundle bundle) {
if(bundle == null) {
return new JSONObject();
}
JSONObject j = new JSONObject();
Set<String> keys = bundle.keySet();
for(String key : keys) {
try {
Class<?> jsonClass = j.getClass();
Class[] cArg = new Class[1];
cArg[0] = String.class;
//Workaround for API < 19
try{
if(jsonClass.getDeclaredMethod("wrap", cArg) != null){
j.put(key, JSONObject.wrap(bundle.get(key)));
}
}
catch(NoSuchMethodException e) {
j.put(key, this._wrap(bundle.get(key)));
}
} catch(JSONException ex) {}
}
return j;
}
//Wrap method not available in JSONObject API < 19
private Object _wrap(Object o){
if (o == null) {
return null;
}
if (o instanceof JSONArray || o instanceof JSONObject) {
return o;
}
if (o.equals(null)) {
return o;
}
try {
if (o instanceof Collection) {
return new JSONArray((Collection) o);
} else if (o.getClass().isArray()) {
return new JSONArray(o);
}
if (o instanceof Map) {
return new JSONObject((Map) o);
}
if (o instanceof Boolean ||
o instanceof Byte ||
o instanceof Character ||
o instanceof Double ||
o instanceof Float ||
o instanceof Integer ||
o instanceof Long ||
o instanceof Short ||
o instanceof String) {
return o;
}
if (o.getClass().getPackage().getName().startsWith("java.")) {
return o.toString();
}
} catch (Exception ignored) {}
return null;
}
/**
* Check if we can open an app with a given URI scheme.
*
* Thanks to https://github.com/ohh2ahh/AppAvailability/blob/master/src/android/AppAvailability.java
*/
private void canOpenApp(String uri, final CallbackContext callbackContext) {
Context ctx = this.cordova.getActivity().getApplicationContext();
final PackageManager pm = ctx.getPackageManager();
try {
pm.getPackageInfo(uri, PackageManager.GET_ACTIVITIES);
callbackContext.success();
} catch(PackageManager.NameNotFoundException e) {}
callbackContext.error("");
}
private void getHardwareInfo(JSONArray args, final CallbackContext callbackContext) {
String uuid = Settings.Secure.getString(this.cordova.getActivity().getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);
JSONObject j = new JSONObject();
try {
j.put("uuid", uuid);
j.put("platform", this.getPlatform());
j.put("tz", this.getTimeZoneID());
j.put("tz_offset", this.getTimeZoneOffset());
j.put("os_version", this.getOSVersion());
j.put("sdk_version", this.getSDKVersion());
} catch(JSONException ex) {}
final PluginResult result = new PluginResult(PluginResult.Status.OK, j);
callbackContext.sendPluginResult(result);
}
private boolean isAmazonDevice() {
if (android.os.Build.MANUFACTURER.equals("Amazon")) {
return true;
}
return false;
}
private String getTimeZoneID() {
TimeZone tz = TimeZone.getDefault();
return (tz.getID());
}
private int getTimeZoneOffset() {
TimeZone tz = TimeZone.getDefault();
return tz.getOffset(new Date().getTime()) / 1000 / 60;
}
private String getSDKVersion() {
@SuppressWarnings("deprecation")
String sdkversion = android.os.Build.VERSION.SDK;
return sdkversion;
}
private String getOSVersion() {
String osversion = android.os.Build.VERSION.RELEASE;
return osversion;
}
private String getPlatform() {
String platform;
if (isAmazonDevice()) {
platform = "amazon-fireos";
} else {
platform = "android";
}
return platform;
}
}
================================================
FILE: src/browser/DeeplinkProxy.js
================================================
function parseSchemeFromUrl (url) {
var _sep = url.indexOf(':');
if (_sep > -1) {
return url.slice(0, _sep + 1);
}
return undefined;
}
function parseQueryStringFromUrl(url) {
var qs = url.indexOf('?');
if (qs > -1) {
return url.slice(qs + 1);
}
return undefined;
}
function locationToData(l) {
return {
url: l.href,
path: l.pathname,
host: l.hostname,
fragment: l.hash,
scheme: parseSchemeFromUrl(l.href),
queryString: parseQueryStringFromUrl(l.href)
}
}
module.exports = {
canOpenApp: function() {
// We can't infer this from the browser environment
return false;
},
onDeepLink: function(callback) {
// Try the first deeplink route
setTimeout(function() {
callback && callback(locationToData(window.location), {
keepCallback: true
});
})
return window.addEventListener('hashchange', function(e) {
callback && callback(locationToData(window.location), {
keepCallback: true
});
}, false);
},
getHardwareInfo: function(callback) {
return {};
}
};
require("cordova/exec/proxy").add("IonicDeeplinkPlugin", module.exports);
================================================
FILE: src/ios/AppDelegate+IonicDeeplink.m
================================================
#import "AppDelegate.h"
#import "IonicDeeplinkPlugin.h"
static NSString *const PLUGIN_NAME = @"IonicDeeplinkPlugin";
/**
* Category for the AppDelegate that overrides application:continueUserActivity:restorationHandler method,
* so we could handle application launch when user clicks on the link in the browser.
*/
@interface AppDelegate (IonicDeeplinkPlugin)
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options;
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation;
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler;
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
@end
@implementation AppDelegate (IonicDeeplinkPlugin)
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
NSMutableString *sourceApp = [[NSMutableString alloc] init];
NSMutableString *annotation = [[NSMutableString alloc] init];
if([options objectForKey:UIApplicationOpenURLOptionsSourceApplicationKey]) {
sourceApp = [options objectForKey:UIApplicationOpenURLOptionsSourceApplicationKey];
}
if([options objectForKey:UIApplicationOpenURLOptionsAnnotationKey]) {
annotation = [options objectForKey:UIApplicationOpenURLOptionsAnnotationKey];
}
return [self application:app openURL:url sourceApplication:sourceApp annotation:annotation];
}
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
IonicDeeplinkPlugin *plugin = [self.viewController getCommandInstance:PLUGIN_NAME];
if(plugin == nil) {
NSLog(@"Unable to get instance of command plugin");
return NO;
}
BOOL handled = [plugin handleLink:url];
if(!handled) {
// Pass event through to Cordova
NSMutableDictionary * openURLData = [[NSMutableDictionary alloc] init];
[openURLData setValue:url forKey:@"url"];
if (sourceApplication) {
[openURLData setValue:sourceApplication forKey:@"sourceApplication"];
}
if (annotation) {
[openURLData setValue:annotation forKey:@"annotation"];
}
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLNotification object:url]];
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:CDVPluginHandleOpenURLWithAppSourceAndAnnotationNotification object:openURLData]];
// Send notice to the rest of our plugin that we didn't handle this URL
[[NSNotificationCenter defaultCenter] postNotification:[NSNotification notificationWithName:@"IonicLinksUnhandledURL" object:[url absoluteString]]];
}
return YES;
}
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray *restorableObjects))restorationHandler {
// Pass it off to our plugin
IonicDeeplinkPlugin *plugin = [self.viewController getCommandInstance:PLUGIN_NAME];
if(plugin == nil) {
return NO;
}
BOOL handled = [plugin handleContinueUserActivity:userActivity];
if(!handled) {
// Continue sending the openURL request through
}
return YES;
}
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
// Pass the push notification to the plugin
if([userInfo objectForKey:@"uri"] == nil) {
return;
}
if(application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground) {
IonicDeeplinkPlugin *plugin = [self.viewController getCommandInstance:PLUGIN_NAME];
if(plugin == nil) {
NSLog(@"Unable to get instance of command plugin");
return;
}
NSURL *url = [NSURL URLWithString:[userInfo objectForKey:@"uri"]];
[plugin handleLink:url];
}
}
@end
================================================
FILE: src/ios/IonicDeeplinkPlugin.h
================================================
#import <Cordova/CDVPlugin.h>
@interface IonicDeeplinkPlugin : CDVPlugin {
// Handlers for URL events
NSMutableArray *_handlers;
CDVPluginResult *_lastEvent;
}
// User-plugin command handler
- (void)canOpenApp:(CDVInvokedUrlCommand *)command;
- (void)onDeepLink:(CDVInvokedUrlCommand *)command;
- (void)getHardwareInfo:(CDVInvokedUrlCommand *)command;
// Internal deeplink and CUA handlers
- (BOOL)handleLink:(NSURL *)url;
- (BOOL)handleContinueUserActivity:(NSUserActivity *)userActivity;
- (void)sendToJs;
- (CDVPluginResult*)createResult:(NSURL *)url;
@end
================================================
FILE: src/ios/IonicDeeplinkPlugin.m
================================================
#import "IonicDeeplinkPlugin.h"
#import <Cordova/CDVAvailability.h>
@implementation IonicDeeplinkPlugin
- (void)pluginInitialize {
_handlers = [[NSMutableArray alloc] init];
}
/* ------------------------------------------------------------- */
- (void)onAppTerminate {
_handlers = nil;
[super onAppTerminate];
}
- (void)canOpenApp:(CDVInvokedUrlCommand *)command {
CDVPluginResult* result = nil;
NSString* scheme = [command.arguments objectAtIndex:0];
if ([[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:scheme]]) {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsBool:(true)];
} else {
result = [CDVPluginResult resultWithStatus:CDVCommandStatus_ERROR messageAsBool:(false)];
}
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
- (void)onDeepLink:(CDVInvokedUrlCommand *)command {
[_handlers addObject:command.callbackId];
// Try to consume any events we got before we were listening
[self sendToJs];
}
- (BOOL)handleLink:(NSURL *)url {
NSLog(@"IonicDeepLinkPlugin: Handle link (internal) %@", url);
if(![self checkUrl:url]) {
return NO;
}
_lastEvent = [self createResult:url];
[self sendToJs];
return YES;
}
- (BOOL)checkUrl:(NSURL *)url {
if(url == nil) return NO;
NSString* urlScheme = [[self.commandDelegate settings] objectForKey:@"url_scheme"];
if(urlScheme == nil) return NO;
NSLog(@"url scheme:%@",[url scheme]);
NSLog(@"url host:%@",[url host]);
if([[url scheme] isEqualToString:urlScheme]) {
return YES;
}
NSString* deeplinkScheme = [[self.commandDelegate settings] objectForKey:@"deeplink_scheme"];
NSString* deeplinkHost = [[self.commandDelegate settings] objectForKey:@"deeplink_host"];
if(deeplinkScheme!=nil && deeplinkHost != nil) {
if([[url scheme] isEqualToString:deeplinkScheme]&&[[url host] isEqualToString:deeplinkHost]) {
return YES;
}
}
return NO;
}
- (BOOL)handleContinueUserActivity:(NSUserActivity *)userActivity {
if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] || userActivity.webpageURL == nil) {
return NO;
}
NSURL *url = userActivity.webpageURL;
_lastEvent = [self createResult:url];
NSLog(@"IonicDeepLinkPlugin: Handle continueUserActivity (internal) %@", url);
[self sendToJs];
return NO;
}
- (void) sendToJs {
// Send the last event to JS if we have one
if (_handlers.count == 0 || _lastEvent == nil) {
return;
}
// Iterate our handlers and send the event
for (id callbackID in _handlers) {
[self.commandDelegate sendPluginResult:_lastEvent callbackId:callbackID];
}
// Clear out the last event
_lastEvent = nil;
}
- (CDVPluginResult *)createResult:(NSURL *)url {
NSDictionary* data = @{
@"url": [url absoluteString] ?: @"",
@"path": [url path] ?: @"",
@"queryString": [url query] ?: @"",
@"scheme": [url scheme] ?: @"",
@"host": [url host] ?: @"",
@"fragment": [url fragment] ?: @""
};
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:data];
[result setKeepCallbackAsBool:YES];
return result;
}
- (void)getHardwareInfo:(CDVInvokedUrlCommand *)command {
NSMutableDictionary *info = [[NSMutableDictionary alloc] init];
// Removing part where advertisingIdentifier is being used to keep the functional part working.
NSString *uuid = [[UIDevice currentDevice].identifierForVendor UUIDString];
if(uuid && [uuid length] > 0) {
[info setObject:uuid forKey:@"uuid"];
}
CDVPluginResult *result = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:info];
[self.commandDelegate sendPluginResult:result callbackId:command.callbackId];
}
@end
================================================
FILE: www/deeplink.js
================================================
var argscheck = require('cordova/argscheck'),
utils = require('cordova/utils'),
exec = require('cordova/exec');
var PLUGIN_NAME = 'IonicDeeplinkPlugin';
var extend = function (out) {
out = out || {};
for (var i = 1; i < arguments.length; i++) {
if (!arguments[i]) {
continue;
}
for (var key in arguments[i]) {
if (arguments[i].hasOwnProperty(key)) {
out[key] = arguments[i][key];
}
}
}
return out;
};
var IonicDeeplink = {
/**
* How long to wait after a deeplink match before navigating.
* Default is 800ms which gives the app time to get back and then
* smoothly animate.
*/
NAVIGATION_DELAY: 800,
canOpenApp: function (app, cb) {
exec(cb, null, PLUGIN_NAME, 'canOpenApp', []);
},
route: function (paths, success, error) {
var self = this;
this.paths = paths;
this.onDeepLink(function (data) {
var realPath = self._getRealPath(data);
var args = self._queryToObject(data.queryString);
var matched = false;
var finalArgs;
var pathData;
for (var targetPath in paths) {
pathData = paths[targetPath];
var matchedParams = self.routeMatch(targetPath, realPath);
if (matchedParams !== false) {
matched = true;
finalArgs = extend({}, matchedParams, args);
break;
}
}
if (matched === true) {
console.log('Match found', realPath);
if (typeof (success) === 'function') {
success({
$route: pathData,
$args: finalArgs,
$link: data,
});
}
return;
}
if (typeof (error) === 'function') {
console.log('No Match found');
error({ $link: data });
}
})
},
routeWithNavController: function (navController, paths, options, success, error) {
var self = this;
var defaultOptions = {
root: false,
};
if (typeof options !== 'function') {
options = extend(defaultOptions, options);
} else {
success = options;
error = success;
options = defaultOptions;
}
this.route(paths, function (match) {
// Defer this to ensure animations run
setTimeout(function () {
if (options.root === true) {
navController.setRoot(match.$route, match.$args);
} else {
navController.push(match.$route, match.$args);
}
}, self.NAVIGATION_DELAY);
if (typeof (success) === 'function') {
success(match);
}
}, function (nomatch) {
if (typeof (error) === 'function') {
error(nomatch);
}
});
},
/**
* Check if the path matches the route.
*/
routeMatch: function (route, path) {
if (route === path) {
return {};
}
var parts = path.split('/');
var routeParts = route.split('/');
// Our aggregated route params that matched our route path.
// This is used for things like /post/:id
var routeParams = {};
if (parts.length !== routeParts.length) {
// Can't possibly match if the lengths are different
return false;
}
// Otherwise, we need to check each part
var rp,
pp;
for (var i = 0; i < parts.length; i++) {
pp = parts[i];
rp = routeParts[i];
if (rp[0] == ':') {
// We have a route param, store it in our
// route params without the colon
routeParams[rp.slice(1)] = pp;
} else if (pp !== rp) {
return false;
}
}
return routeParams;
},
_queryToObject: function (q) {
if (!q) return {};
var qIndex = q.indexOf('?');
if (qIndex > -1) {
q = q.slice(qIndex + 1);
}
var i = 0,
retObj = {},
pair = null,
qArr = q.split('&');
for (; i < qArr.length; i++) {
if (!qArr[i]) {
continue;
}
pair = qArr[i].split('=');
retObj[pair[0]] = pair[1];
}
return retObj;
},
_stripFragmentLeadingHash: function (fragment) {
var hs = fragment.indexOf('#');
if (hs > -1) {
fragment.slice(0, hs);
}
return fragment;
},
/**
* We're fairly flexible when it comes to matching a URL. We support
* host-less custom URL scheme matches like ionic://camera?blah but also support
* and match against fragments.
*
* This method tries to infer what the proper "path" is from the URL
*/
_getRealPath: function (data) {
// 1. Let's just do the obvious and return the parsed 'path' first, if available.
if (!!data.path && data.path !== "") {
return data.path;
}
// 2. Now, are we using a non-standard scheme?
var isCustomScheme = data.scheme.indexOf('http') === -1;
// 3. Nope so we'll go fragment first if available as that should be what comes after
if (!isCustomScheme) {
if (!!data.fragment) {
return this._stripFragmentLeadingHash(data.fragment);
}
}
// 4. Now fall back to host / authority
if (!!data.host) {
if (data.host.charAt(0) != '/') {
data.host = '/' + data.host;
}
return data.host;
}
// 5. We'll use fragment next if we're in a custom scheme, though this might need a little more thought
if (isCustomScheme && !!data.fragment) {
return this._stripFragmentLeadingHash(data.fragment);
}
// 6. Last resort - no obvious path, fragment or host, so we
// slice out the scheme and any query string or fragment from the full url.
var restOfUrl = data.url;
var separator = data.url.indexOf('://');
if (separator !== -1) {
restOfUrl = data.url.slice(separator + 3);
} else {
separator = data.url.indexOf(':/');
if (separator !== -1) {
restOfUrl = data.url.slice(separator + 2);
}
}
var qs = restOfUrl.indexOf('?');
if (qs > -1) {
restOfUrl = restOfUrl.slice(0, qs);
}
var hs = restOfUrl.indexOf('#');
if (hs > -1) {
restOfUrl = restOfUrl.slice(0, hs);
}
return restOfUrl;
},
onDeepLink: function (callback) {
var innerCB = function (data) {
callback(data);
};
exec(innerCB, null, PLUGIN_NAME, 'onDeepLink', []);
},
getHardwareInfo: function (callback) {
exec(callback, null, PLUGIN_NAME, 'getHardwareInfo', []);
},
};
module.exports = IonicDeeplink;
gitextract_glkpx5o0/
├── .gitignore
├── .npmrc
├── LICENSE
├── README.md
├── package.json
├── plugin.xml
├── src/
│ ├── android/
│ │ └── io/
│ │ └── ionic/
│ │ └── links/
│ │ └── IonicDeeplink.java
│ ├── browser/
│ │ └── DeeplinkProxy.js
│ └── ios/
│ ├── AppDelegate+IonicDeeplink.m
│ ├── IonicDeeplinkPlugin.h
│ └── IonicDeeplinkPlugin.m
└── www/
└── deeplink.js
SYMBOL INDEX (22 symbols across 3 files)
FILE: src/android/io/ionic/links/IonicDeeplink.java
class IonicDeeplink (line 45) | public class IonicDeeplink extends CordovaPlugin {
method initialize (line 52) | public void initialize(CordovaInterface cordova, CordovaWebView webVie...
method onNewIntent (line 59) | @Override
method handleIntent (line 64) | public void handleIntent(Intent intent) {
method execute (line 94) | public boolean execute(String action, JSONArray args, final CallbackCo...
method consumeEvents (line 110) | private void consumeEvents() {
method sendToJs (line 121) | private void sendToJs(JSONObject event, CallbackContext callback) {
method addHandler (line 127) | private void addHandler(JSONArray args, final CallbackContext callback...
method _bundleToJson (line 132) | private JSONObject _bundleToJson(Bundle bundle) {
method _wrap (line 159) | private Object _wrap(Object o){
method canOpenApp (line 201) | private void canOpenApp(String uri, final CallbackContext callbackCont...
method getHardwareInfo (line 213) | private void getHardwareInfo(JSONArray args, final CallbackContext cal...
method isAmazonDevice (line 230) | private boolean isAmazonDevice() {
method getTimeZoneID (line 236) | private String getTimeZoneID() {
method getTimeZoneOffset (line 241) | private int getTimeZoneOffset() {
method getSDKVersion (line 246) | private String getSDKVersion() {
method getOSVersion (line 251) | private String getOSVersion() {
method getPlatform (line 255) | private String getPlatform() {
FILE: src/browser/DeeplinkProxy.js
function parseSchemeFromUrl (line 1) | function parseSchemeFromUrl (url) {
function parseQueryStringFromUrl (line 10) | function parseQueryStringFromUrl(url) {
function locationToData (line 20) | function locationToData(l) {
FILE: src/ios/IonicDeeplinkPlugin.h
function interface (line 3) | interface IonicDeeplinkPlugin : CDVPlugin {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (44K chars).
[
{
"path": ".gitignore",
"chars": 32,
"preview": "node_modules/\npackage-lock.json\n"
},
{
"path": ".npmrc",
"chars": 37,
"preview": "registry=https://registry.npmjs.org/\n"
},
{
"path": "LICENSE",
"chars": 1090,
"preview": "Copyright 2016-present Drifty Co.\nhttp://drifty.com/\n\nMIT License\n\nPermission is hereby granted, free of charge, to any "
},
{
"path": "README.md",
"chars": 11311,
"preview": "# Community Maintained\n\nThis plugin is being maintained by the Ionic community. Interested in helping? Message `max` on "
},
{
"path": "package.json",
"chars": 736,
"preview": "{\n \"name\": \"ionic-plugin-deeplinks\",\n \"version\": \"1.0.24\",\n \"cordova\": {\n \"id\": \"ionic-plugin-deeplinks\",\n \"pla"
},
{
"path": "plugin.xml",
"chars": 4277,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n<plugin xmlns=\"http://apache.org/cordova/ns/plugins/1.0\" id=\"ionic-plugin-deepli"
},
{
"path": "src/android/io/ionic/links/IonicDeeplink.java",
"chars": 7952,
"preview": "/**\n * Ionic Deeplinks Plugin.\n * License: MIT\n *\n * Thanks to Eddy Verbruggen and nordnet for the great custom URl sche"
},
{
"path": "src/browser/DeeplinkProxy.js",
"chars": 1149,
"preview": "function parseSchemeFromUrl (url) {\n\tvar _sep = url.indexOf(':');\n\tif (_sep > -1) {\n\t\treturn url.slice(0, _sep + 1);\n\t}\n"
},
{
"path": "src/ios/AppDelegate+IonicDeeplink.m",
"chars": 4264,
"preview": "#import \"AppDelegate.h\"\n#import \"IonicDeeplinkPlugin.h\"\n\nstatic NSString *const PLUGIN_NAME = @\"IonicDeeplinkPlugin\";\n\n/"
},
{
"path": "src/ios/IonicDeeplinkPlugin.h",
"chars": 572,
"preview": "#import <Cordova/CDVPlugin.h>\n\n@interface IonicDeeplinkPlugin : CDVPlugin {\n // Handlers for URL events\n NSMutableArra"
},
{
"path": "src/ios/IonicDeeplinkPlugin.m",
"chars": 3792,
"preview": "#import \"IonicDeeplinkPlugin.h\"\n\n#import <Cordova/CDVAvailability.h>\n\n@implementation IonicDeeplinkPlugin\n\n- (void)plugi"
},
{
"path": "www/deeplink.js",
"chars": 6360,
"preview": "var argscheck = require('cordova/argscheck'),\n utils = require('cordova/utils'),\n exec = require('cordova/exec');\n\nvar"
}
]
About this extraction
This page contains the full source code of the ionic-team/ionic-plugin-deeplinks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (40.6 KB), approximately 10.7k tokens, and a symbol index with 22 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.