cordova-plugin-oauth 4.0.1 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +20 -3
- package/package.json +35 -15
- package/plugin.xml +5 -2
- package/src/android/OAuthPlugin.java +84 -122
- package/src/ios/OAuthPlugin.swift +73 -18
- package/www/oauth.js +107 -2
package/README.md
CHANGED
|
@@ -38,11 +38,11 @@ Installation
|
|
|
38
38
|
------------
|
|
39
39
|
|
|
40
40
|
```
|
|
41
|
-
cordova plugin add cordova-plugin-oauth [--variable URL_SCHEME=mycoolapp]
|
|
41
|
+
cordova plugin add cordova-plugin-oauth [--variable URL_SCHEME=mycoolapp] [--variable URL_HOSTNAME=callback]
|
|
42
42
|
```
|
|
43
43
|
|
|
44
44
|
By default, the plugin registers the app ID as a scheme to be used as the
|
|
45
|
-
OAuth callback URL, and expects a host of `oauth_callback` (i.e., if your
|
|
45
|
+
OAuth callback URL, and expects a default host of `oauth_callback` (i.e., if your
|
|
46
46
|
app's ID is `com.example.foo`, your OAuth redirect URL should be
|
|
47
47
|
`com.example.foo://oauth_callback`).
|
|
48
48
|
|
|
@@ -50,6 +50,9 @@ The scheme for the OAuth callback URL can be changed by providing a
|
|
|
50
50
|
`URL_SCHEME` variable when installing. If your `URL_SCHEME` is `mycoolapp`,
|
|
51
51
|
then your OAuth redirect URL should be `mycoolapp://oauth_callback`.
|
|
52
52
|
|
|
53
|
+
The hostname endpoint can be changed by providing a `URL_HOSTNAME` variable
|
|
54
|
+
when installing.
|
|
55
|
+
|
|
53
56
|
|
|
54
57
|
Supported Platforms
|
|
55
58
|
-------------------
|
|
@@ -69,13 +72,22 @@ Usage
|
|
|
69
72
|
window.open(endpoint, 'oauth:google', '');
|
|
70
73
|
```
|
|
71
74
|
|
|
75
|
+
Alternatively, you can include `oauth=yes` in the window features string:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
var endpoint = 'https://accounts.google.com/o/oauth2/v2/auth?....';
|
|
79
|
+
window.open(endpoint, '_self', 'oauth=yes');
|
|
80
|
+
```
|
|
81
|
+
|
|
72
82
|
2. The plugin will open the OAuth login page in a new browser window.
|
|
73
83
|
|
|
74
84
|
3. When the OAuth process is complete and it redirects to your app scheme, the
|
|
75
85
|
plugin will send a message to the Cordova app.
|
|
76
86
|
|
|
77
87
|
The message begins with `oauth::` and is followed by a JSON object
|
|
78
|
-
containing all of the key/value pairs from the OAuth redirect query string
|
|
88
|
+
containing all of the key/value pairs from the OAuth redirect query string
|
|
89
|
+
or fragment parameters. The complete OAuth callback URL is available in a
|
|
90
|
+
`oauth_callback_url` property.
|
|
79
91
|
|
|
80
92
|
```javascript
|
|
81
93
|
// Called from a callback URL like
|
|
@@ -113,4 +125,9 @@ Licence
|
|
|
113
125
|
Released under the Apache 2.0 Licence.
|
|
114
126
|
Copyright © 2020-2022 Ayogo Health Inc.
|
|
115
127
|
|
|
128
|
+
This project includes the [EventTarget polyfill][etpoly], copyright © 2018,
|
|
129
|
+
Andrea Giammarchi under the [ISC Licence][isc].
|
|
130
|
+
|
|
116
131
|
[coc]: https://github.com/AyogoHealth/cordova-plugin-oauth/blob/main/CODE_OF_CONDUCT.md
|
|
132
|
+
[etpoly]: https://github.com/ungap/event-target
|
|
133
|
+
[isc]: https://github.com/ungap/event-target/blob/master/LICENSE
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cordova-plugin-oauth",
|
|
3
|
-
"version": "4.0
|
|
3
|
+
"version": "4.1.0",
|
|
4
4
|
"author": "Ayogo Health Inc. <info@ayogo.com>",
|
|
5
5
|
"contributors": [
|
|
6
6
|
"Darryl Pogue <darryl@dpogue.ca>",
|
|
@@ -9,10 +9,7 @@
|
|
|
9
9
|
],
|
|
10
10
|
"description": "Cordova plugin for performing OAuth login flows.",
|
|
11
11
|
"license": "Apache-2.0",
|
|
12
|
-
"repository":
|
|
13
|
-
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/AyogoHealth/cordova-plugin-oauth.git"
|
|
15
|
-
},
|
|
12
|
+
"repository": "github:AyogoHealth/cordova-plugin-oauth.git",
|
|
16
13
|
"keywords": [
|
|
17
14
|
"cordova",
|
|
18
15
|
"ecosystem:cordova",
|
|
@@ -24,21 +21,44 @@
|
|
|
24
21
|
"www",
|
|
25
22
|
"plugin.xml"
|
|
26
23
|
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"version": "sed -i -e \"s/\\(<plugin [^>]*id=\\\"$npm_package_name\\\" [^>]*version=\\\"\\)[^\\\"]*\\\"/\\1$npm_package_version\\\"/\" plugin.xml && git add plugin.xml"
|
|
26
|
+
},
|
|
27
27
|
"cordova": {
|
|
28
|
+
"id": "cordova-plugin-oauth",
|
|
28
29
|
"platforms": [
|
|
29
30
|
"ios",
|
|
30
31
|
"android"
|
|
31
32
|
]
|
|
32
33
|
},
|
|
33
|
-
"engines":
|
|
34
|
-
{
|
|
35
|
-
"
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
"
|
|
40
|
-
|
|
34
|
+
"engines": {
|
|
35
|
+
"cordovaDependencies": {
|
|
36
|
+
"1.0.0": {
|
|
37
|
+
"cordova-android": ">= 8.0.0",
|
|
38
|
+
"cordova-ios": ">= 5.0.0"
|
|
39
|
+
},
|
|
40
|
+
"2.0.0": {
|
|
41
|
+
"cordova-android": ">= 8.0.0",
|
|
42
|
+
"cordova-ios": ">= 5.0.0"
|
|
43
|
+
},
|
|
44
|
+
"3.0.0": {
|
|
45
|
+
"cordova-android": ">= 8.0.0",
|
|
46
|
+
"cordova-ios": ">= 6.1.0"
|
|
47
|
+
},
|
|
48
|
+
"4.0.0": {
|
|
49
|
+
"cordova-android": ">= 9.0.0",
|
|
50
|
+
"cordova-ios": ">= 6.1.0"
|
|
51
|
+
},
|
|
52
|
+
"5.0.0": {
|
|
53
|
+
"cordova": ">100"
|
|
54
|
+
}
|
|
41
55
|
}
|
|
42
|
-
|
|
43
|
-
"dependencies": {}
|
|
56
|
+
},
|
|
57
|
+
"dependencies": {},
|
|
58
|
+
"devEngines": {
|
|
59
|
+
"packageManager": {
|
|
60
|
+
"name": "npm",
|
|
61
|
+
"onFail": "ignore"
|
|
62
|
+
}
|
|
63
|
+
}
|
|
44
64
|
}
|
package/plugin.xml
CHANGED
|
@@ -14,7 +14,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
14
14
|
See the License for the specific language governing permissions and
|
|
15
15
|
limitations under the License.
|
|
16
16
|
-->
|
|
17
|
-
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-oauth" version="4.0
|
|
17
|
+
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android" id="cordova-plugin-oauth" version="4.1.0">
|
|
18
18
|
<name>cordova-plugin-oauth</name>
|
|
19
19
|
<description>Cordova plugin for performing OAuth login flows.</description>
|
|
20
20
|
<keywords>cordova,ios,android,oauth</keywords>
|
|
@@ -23,6 +23,7 @@ limitations under the License.
|
|
|
23
23
|
<issues>https://github.com/AyogoHealth/cordova-plugin-oauth/issues</issues>
|
|
24
24
|
|
|
25
25
|
<preference name="URL_SCHEME" default="$PACKAGE_NAME" />
|
|
26
|
+
<preference name="URL_HOSTNAME" default="oauth_callback" />
|
|
26
27
|
|
|
27
28
|
<engines>
|
|
28
29
|
<engine name="cordova-ios" version=">= 6.1.0" />
|
|
@@ -35,6 +36,7 @@ limitations under the License.
|
|
|
35
36
|
|
|
36
37
|
<config-file target="config.xml" parent="/*">
|
|
37
38
|
<preference name="OAuthScheme" value="$URL_SCHEME"/>
|
|
39
|
+
<preference name="OAuthHostname" value="$URL_HOSTNAME"/>
|
|
38
40
|
</config-file>
|
|
39
41
|
|
|
40
42
|
<platform name="ios">
|
|
@@ -66,6 +68,7 @@ limitations under the License.
|
|
|
66
68
|
<config-file target="res/xml/config.xml" parent="/*">
|
|
67
69
|
<feature name="OAuth">
|
|
68
70
|
<param name="android-package" value="com.ayogo.cordova.oauth.OAuthPlugin" />
|
|
71
|
+
<param name="onload" value="true" />
|
|
69
72
|
</feature>
|
|
70
73
|
</config-file>
|
|
71
74
|
|
|
@@ -76,7 +79,7 @@ limitations under the License.
|
|
|
76
79
|
<category android:name="android.intent.category.DEFAULT" />
|
|
77
80
|
<category android:name="android.intent.category.BROWSABLE" />
|
|
78
81
|
|
|
79
|
-
<data android:scheme="$URL_SCHEME" android:host="
|
|
82
|
+
<data android:scheme="$URL_SCHEME" android:host="$URL_HOSTNAME" />
|
|
80
83
|
</intent-filter>
|
|
81
84
|
</config-file>
|
|
82
85
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2019 Ayogo Health Inc.
|
|
2
|
+
* Copyright 2019 - 2022 Ayogo Health Inc.
|
|
3
3
|
*
|
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
* you may not use this file except in compliance with the License.
|
|
@@ -16,20 +16,17 @@
|
|
|
16
16
|
|
|
17
17
|
package com.ayogo.cordova.oauth;
|
|
18
18
|
|
|
19
|
-
import android.app.Activity;
|
|
20
|
-
import android.content.Context;
|
|
21
19
|
import android.content.Intent;
|
|
22
|
-
import android.content.IntentFilter;
|
|
23
|
-
import android.content.pm.PackageManager;
|
|
24
|
-
import android.content.pm.ResolveInfo;
|
|
25
20
|
import android.net.Uri;
|
|
26
21
|
import androidx.browser.customtabs.CustomTabsIntent;
|
|
27
22
|
import android.text.TextUtils;
|
|
23
|
+
import java.net.URLDecoder;
|
|
28
24
|
|
|
29
25
|
import java.util.ArrayList;
|
|
30
26
|
import java.util.List;
|
|
31
27
|
|
|
32
28
|
import org.apache.cordova.CallbackContext;
|
|
29
|
+
import org.apache.cordova.CordovaArgs;
|
|
33
30
|
import org.apache.cordova.CordovaInterface;
|
|
34
31
|
import org.apache.cordova.CordovaPlugin;
|
|
35
32
|
import org.apache.cordova.CordovaWebView;
|
|
@@ -37,29 +34,15 @@ import org.apache.cordova.CordovaWebViewEngine;
|
|
|
37
34
|
import org.apache.cordova.LOG;
|
|
38
35
|
import org.apache.cordova.PluginResult;
|
|
39
36
|
|
|
40
|
-
import org.json.JSONArray;
|
|
41
37
|
import org.json.JSONException;
|
|
42
38
|
import org.json.JSONObject;
|
|
43
39
|
|
|
44
40
|
|
|
45
41
|
public class OAuthPlugin extends CordovaPlugin {
|
|
46
42
|
private final String TAG = "OAuthPlugin";
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
static final String STABLE_PACKAGE = "com.android.chrome";
|
|
51
|
-
static final String BETA_PACKAGE = "com.chrome.beta";
|
|
52
|
-
static final String DEV_PACKAGE = "com.chrome.dev";
|
|
53
|
-
static final String LOCAL_PACKAGE = "com.google.android.apps.chrome";
|
|
54
|
-
private static final String EXTRA_CUSTOM_TABS_KEEP_ALIVE = "android.support.customtabs.extra.KEEP_ALIVE";
|
|
55
|
-
private static final String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* The name of the package to use for the custom tab service.
|
|
60
|
-
*/
|
|
61
|
-
private String tabProvider = null;
|
|
62
|
-
|
|
43
|
+
private CallbackContext oauthCallback = null;
|
|
44
|
+
private boolean didFinishLoading = false;
|
|
45
|
+
private String lastOAuthResult = null;
|
|
63
46
|
|
|
64
47
|
/**
|
|
65
48
|
* Executes the request.
|
|
@@ -76,14 +59,14 @@ public class OAuthPlugin extends CordovaPlugin {
|
|
|
76
59
|
* @return Whether the action was valid.
|
|
77
60
|
*/
|
|
78
61
|
@Override
|
|
79
|
-
public boolean execute(String action,
|
|
62
|
+
public boolean execute(String action, CordovaArgs args, CallbackContext callbackContext) {
|
|
80
63
|
if ("startOAuth".equals(action)) {
|
|
81
64
|
try {
|
|
82
65
|
String authEndpoint = args.getString(0);
|
|
66
|
+
oauthCallback = callbackContext;
|
|
83
67
|
|
|
84
68
|
this.startOAuth(authEndpoint);
|
|
85
69
|
|
|
86
|
-
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.OK));
|
|
87
70
|
return true;
|
|
88
71
|
} catch (JSONException e) {
|
|
89
72
|
callbackContext.sendPluginResult(new PluginResult(PluginResult.Status.ERROR));
|
|
@@ -96,6 +79,14 @@ public class OAuthPlugin extends CordovaPlugin {
|
|
|
96
79
|
}
|
|
97
80
|
|
|
98
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Called when the activity is becoming visible to the user.
|
|
84
|
+
*/
|
|
85
|
+
@Override
|
|
86
|
+
public void onStart() {
|
|
87
|
+
onNewIntent(cordova.getActivity().getIntent());
|
|
88
|
+
}
|
|
89
|
+
|
|
99
90
|
|
|
100
91
|
/**
|
|
101
92
|
* Called when the activity receives a new intent.
|
|
@@ -110,26 +101,40 @@ public class OAuthPlugin extends CordovaPlugin {
|
|
|
110
101
|
}
|
|
111
102
|
|
|
112
103
|
final Uri uri = intent.getData();
|
|
104
|
+
String callbackHost = preferences.getString("oauthhostname", "oauth_callback");
|
|
113
105
|
|
|
114
|
-
if (uri.getHost().equals(
|
|
106
|
+
if (uri.getHost().equals(callbackHost)) {
|
|
115
107
|
LOG.i(TAG, "OAuth called back with parameters.");
|
|
116
108
|
|
|
117
109
|
try {
|
|
118
110
|
JSONObject jsobj = new JSONObject();
|
|
111
|
+
jsobj.put("oauth_callback_url", uri.toString());
|
|
112
|
+
|
|
113
|
+
// Parse fragment parameters
|
|
114
|
+
if (uri.getFragment() != null) {
|
|
115
|
+
String fragment = uri.getFragment();
|
|
116
|
+
String[] pairs = fragment.split("&");
|
|
117
|
+
for (String pair : pairs) {
|
|
118
|
+
String[] keyValue = pair.split("=");
|
|
119
|
+
if (keyValue.length == 2) {
|
|
120
|
+
// Decode the fragment parameter before adding it to the JSONObject
|
|
121
|
+
String key = keyValue[0];
|
|
122
|
+
String value = keyValue[1];
|
|
123
|
+
jsobj.put(key, value);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
119
127
|
|
|
128
|
+
// Parse query parameters
|
|
120
129
|
for (String queryKey : uri.getQueryParameterNames()) {
|
|
121
130
|
jsobj.put(queryKey, uri.getQueryParameter(queryKey));
|
|
122
131
|
}
|
|
123
132
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
final String jsCode = "window.dispatchEvent(new MessageEvent('message', { data: 'oauth::" + msg + "' }));";
|
|
127
|
-
if (engine != null) {
|
|
128
|
-
engine.evaluateJavascript(jsCode, null);
|
|
133
|
+
if (this.didFinishLoading) {
|
|
134
|
+
dispatchOAuthMessage(jsobj.toString());
|
|
129
135
|
} else {
|
|
130
|
-
this.
|
|
136
|
+
this.lastOAuthResult = jsobj.toString();
|
|
131
137
|
}
|
|
132
|
-
|
|
133
138
|
} catch (JSONException e) {
|
|
134
139
|
LOG.e(TAG, "JSON Serialization failed");
|
|
135
140
|
e.printStackTrace();
|
|
@@ -137,119 +142,76 @@ public class OAuthPlugin extends CordovaPlugin {
|
|
|
137
142
|
}
|
|
138
143
|
}
|
|
139
144
|
|
|
140
|
-
|
|
141
145
|
/**
|
|
142
|
-
*
|
|
146
|
+
* Called when the activity will start interacting with the user.
|
|
143
147
|
*
|
|
144
|
-
*
|
|
148
|
+
* We use this method to indicate to the JavaScript side that the OAuth
|
|
149
|
+
* window has closed (regardless of login status).
|
|
145
150
|
*/
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
|
150
|
-
CustomTabsIntent customTabsIntent = builder.build();
|
|
151
|
+
@Override
|
|
152
|
+
public void onResume(boolean multitasking) {
|
|
153
|
+
super.onResume(multitasking);
|
|
151
154
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
if (oauthCallback != null) {
|
|
156
|
+
oauthCallback.sendPluginResult(new PluginResult(PluginResult.Status.OK));
|
|
157
|
+
oauthCallback = null;
|
|
155
158
|
}
|
|
156
|
-
|
|
157
|
-
customTabsIntent.launchUrl(this.cordova.getActivity(), Uri.parse(url));
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
|
|
161
162
|
/**
|
|
162
|
-
*
|
|
163
|
+
* Called when a message is sent to plugin.
|
|
163
164
|
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
* This is <strong>not</strong> threadsafe.
|
|
168
|
-
*
|
|
169
|
-
* @return The package name recommended to use for connecting to custom
|
|
170
|
-
* tabs related components.
|
|
165
|
+
* @param id The message id
|
|
166
|
+
* @param data The message data
|
|
167
|
+
* @return Object to stop propagation or null
|
|
171
168
|
*/
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
PackageManager pm = this.cordova.getActivity().getPackageManager();
|
|
178
|
-
|
|
179
|
-
// Get default VIEW intent handler.
|
|
180
|
-
Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.example.com"));
|
|
181
|
-
ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
|
|
182
|
-
String defaultViewHandlerPackageName = null;
|
|
183
|
-
|
|
184
|
-
if (defaultViewHandlerInfo != null) {
|
|
185
|
-
defaultViewHandlerPackageName = defaultViewHandlerInfo.activityInfo.packageName;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// Get all apps that can handle VIEW intents.
|
|
190
|
-
List<ResolveInfo> resolvedActivityList = pm.queryIntentActivities(activityIntent, PackageManager.MATCH_ALL);
|
|
191
|
-
List<String> packagesSupportingCustomTabs = new ArrayList<>();
|
|
192
|
-
|
|
193
|
-
for (ResolveInfo info : resolvedActivityList) {
|
|
194
|
-
Intent serviceIntent = new Intent();
|
|
195
|
-
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
|
|
196
|
-
serviceIntent.setPackage(info.activityInfo.packageName);
|
|
169
|
+
@Override
|
|
170
|
+
public Object onMessage(String id, Object data) {
|
|
171
|
+
if (id.equals("onPageFinished")) {
|
|
172
|
+
this.didFinishLoading = true;
|
|
197
173
|
|
|
198
|
-
if (
|
|
199
|
-
|
|
174
|
+
if (this.lastOAuthResult != null) {
|
|
175
|
+
this.dispatchOAuthMessage(this.lastOAuthResult);
|
|
176
|
+
this.lastOAuthResult = null;
|
|
200
177
|
}
|
|
201
178
|
}
|
|
202
|
-
|
|
203
|
-
// Now packagesSupportingCustomTabs contains all apps that can handle both VIEW intents
|
|
204
|
-
// and service calls.
|
|
205
|
-
if (packagesSupportingCustomTabs.isEmpty()) {
|
|
206
|
-
this.tabProvider = null;
|
|
207
|
-
} else if (packagesSupportingCustomTabs.size() == 1) {
|
|
208
|
-
this.tabProvider = packagesSupportingCustomTabs.get(0);
|
|
209
|
-
} else if (!TextUtils.isEmpty(defaultViewHandlerPackageName) && !this.hasSpecializedHandlerIntents(activityIntent) && packagesSupportingCustomTabs.contains(defaultViewHandlerPackageName)) {
|
|
210
|
-
this.tabProvider = defaultViewHandlerPackageName;
|
|
211
|
-
} else if (packagesSupportingCustomTabs.contains(STABLE_PACKAGE)) {
|
|
212
|
-
this.tabProvider = STABLE_PACKAGE;
|
|
213
|
-
} else if (packagesSupportingCustomTabs.contains(BETA_PACKAGE)) {
|
|
214
|
-
this.tabProvider = BETA_PACKAGE;
|
|
215
|
-
} else if (packagesSupportingCustomTabs.contains(DEV_PACKAGE)) {
|
|
216
|
-
this.tabProvider = DEV_PACKAGE;
|
|
217
|
-
} else if (packagesSupportingCustomTabs.contains(LOCAL_PACKAGE)) {
|
|
218
|
-
this.tabProvider = LOCAL_PACKAGE;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return this.tabProvider;
|
|
179
|
+
return null;
|
|
222
180
|
}
|
|
223
181
|
|
|
224
182
|
|
|
225
183
|
/**
|
|
226
|
-
*
|
|
184
|
+
* Launches the custom tab with the OAuth endpoint URL.
|
|
227
185
|
*
|
|
228
|
-
* @param
|
|
229
|
-
* @return Whether there is a specialized handler for the given intent.
|
|
186
|
+
* @param url The URL of the OAuth endpoint.
|
|
230
187
|
*/
|
|
231
|
-
private
|
|
232
|
-
|
|
233
|
-
PackageManager pm = this.cordova.getActivity().getPackageManager();
|
|
234
|
-
List<ResolveInfo> handlers = pm.queryIntentActivities(intent, PackageManager.GET_RESOLVED_FILTER);
|
|
188
|
+
private void startOAuth(String url) {
|
|
189
|
+
CustomTabsIntent.Builder builder = new CustomTabsIntent.Builder();
|
|
235
190
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
191
|
+
CustomTabsIntent customTabsIntent = builder.build();
|
|
192
|
+
customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
|
193
|
+
customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
|
194
|
+
customTabsIntent.intent.putExtra("android.support.customtabs.extra.ENABLE_URLBAR_HIDING", true);
|
|
195
|
+
customTabsIntent.intent.putExtra("android.support.customtabs.extra.EXTRA_ENABLE_INSTANT_APPS", false);
|
|
196
|
+
customTabsIntent.intent.putExtra("android.support.customtabs.extra.SEND_TO_EXTERNAL_HANDLER", false);
|
|
197
|
+
customTabsIntent.intent.putExtra("androidx.browser.customtabs.extra.SHARE_STATE", 2);
|
|
198
|
+
customTabsIntent.intent.putExtra("androidx.browser.customtabs.extra.DISABLE_BACKGROUND_INTERACTION", false);
|
|
199
|
+
customTabsIntent.intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_DOWNLOAD_BUTTON", true);
|
|
200
|
+
customTabsIntent.intent.putExtra("org.chromium.chrome.browser.customtabs.EXTRA_DISABLE_STAR_BUTTON", true);
|
|
239
201
|
|
|
240
|
-
|
|
241
|
-
|
|
202
|
+
customTabsIntent.launchUrl(this.cordova.getActivity(), Uri.parse(url));
|
|
203
|
+
}
|
|
242
204
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
205
|
+
@SuppressWarnings("deprecation")
|
|
206
|
+
private void dispatchOAuthMessage(final String msg) {
|
|
207
|
+
final String msgData = msg.replace("'", "\\'").replace("\n", "\\n").replace("\r", "\\r").replace("\t", "\\t");
|
|
208
|
+
final String jsCode = "window.dispatchEvent(new MessageEvent('message', { data: 'oauth::" + msgData + "' }));";
|
|
246
209
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
210
|
+
CordovaWebViewEngine engine = this.webView.getEngine();
|
|
211
|
+
if (engine != null) {
|
|
212
|
+
engine.evaluateJavascript(jsCode, null);
|
|
213
|
+
} else {
|
|
214
|
+
this.webView.sendJavascript(jsCode);
|
|
251
215
|
}
|
|
252
|
-
|
|
253
|
-
return false;
|
|
254
216
|
}
|
|
255
217
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Copyright 2019 Ayogo Health Inc.
|
|
2
|
+
* Copyright 2019 - 2022 Ayogo Health Inc.
|
|
3
3
|
*
|
|
4
4
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
5
|
* you may not use this file except in compliance with the License.
|
|
@@ -14,11 +14,19 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
#if canImport(Cordova)
|
|
18
|
+
import Cordova
|
|
19
|
+
#endif
|
|
20
|
+
|
|
17
21
|
import os.log
|
|
18
22
|
import Foundation
|
|
19
23
|
import AuthenticationServices
|
|
20
24
|
import SafariServices
|
|
21
25
|
|
|
26
|
+
extension NSNotification.Name {
|
|
27
|
+
static let CDVPluginOAuthCancelled = NSNotification.Name("CDVPluginOAuthCancelledNotification");
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
@objc protocol OAuthSessionProvider {
|
|
23
31
|
init(_ endpoint : URL, callbackScheme : String)
|
|
24
32
|
func start() -> Void
|
|
@@ -37,6 +45,8 @@ class ASWebAuthenticationSessionOAuthSessionProvider : OAuthSessionProvider {
|
|
|
37
45
|
self.aswas = ASWebAuthenticationSession(url: endpoint, callbackURLScheme: callbackURLScheme, completionHandler: { (callBack:URL?, error:Error?) in
|
|
38
46
|
if let incomingUrl = callBack {
|
|
39
47
|
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: incomingUrl)
|
|
48
|
+
} else {
|
|
49
|
+
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginOAuthCancelled, object: nil)
|
|
40
50
|
}
|
|
41
51
|
})
|
|
42
52
|
}
|
|
@@ -64,6 +74,8 @@ class SFAuthenticationSessionOAuthSessionProvider : OAuthSessionProvider {
|
|
|
64
74
|
self.sfas = SFAuthenticationSession(url: endpoint, callbackURLScheme: callbackScheme, completionHandler: { (callBack:URL?, error:Error?) in
|
|
65
75
|
if let incomingUrl = callBack {
|
|
66
76
|
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginHandleOpenURL, object: incomingUrl)
|
|
77
|
+
} else {
|
|
78
|
+
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginOAuthCancelled, object: nil)
|
|
67
79
|
}
|
|
68
80
|
})
|
|
69
81
|
}
|
|
@@ -86,6 +98,9 @@ class SFSafariViewControllerOAuthSessionProvider : OAuthSessionProvider {
|
|
|
86
98
|
|
|
87
99
|
required init(_ endpoint : URL, callbackScheme : String) {
|
|
88
100
|
self.sfvc = SFSafariViewController(url: endpoint)
|
|
101
|
+
if #available(iOS 11.0, *) {
|
|
102
|
+
self.sfvc.dismissButtonStyle = .cancel
|
|
103
|
+
}
|
|
89
104
|
}
|
|
90
105
|
|
|
91
106
|
func start() {
|
|
@@ -119,14 +134,19 @@ class SafariAppOAuthSessionProvider : OAuthSessionProvider {
|
|
|
119
134
|
|
|
120
135
|
@objc(CDVOAuthPlugin)
|
|
121
136
|
class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticationPresentationContextProviding {
|
|
137
|
+
/** This exists for testing purposes */
|
|
138
|
+
static var forcedVersion : UInt32 = UInt32.max
|
|
139
|
+
|
|
122
140
|
var authSystem : OAuthSessionProvider?
|
|
141
|
+
var closeCallbackId : String?
|
|
123
142
|
var callbackScheme : String?
|
|
124
143
|
var logger : OSLog?
|
|
125
144
|
|
|
126
145
|
override func pluginInitialize() {
|
|
127
146
|
let urlScheme = self.commandDelegate.settings["oauthscheme"] as! String
|
|
147
|
+
let urlHostname = self.commandDelegate.settings["oauthhostname"] as? String ?? "oauth_callback";
|
|
128
148
|
|
|
129
|
-
self.callbackScheme = "\(urlScheme)
|
|
149
|
+
self.callbackScheme = "\(urlScheme)://\(urlHostname)"
|
|
130
150
|
if #available(iOS 10.0, *) {
|
|
131
151
|
self.logger = OSLog(subsystem: urlScheme, category: "Cordova")
|
|
132
152
|
}
|
|
@@ -135,6 +155,11 @@ class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticati
|
|
|
135
155
|
selector: #selector(OAuthPlugin._handleOpenURL(_:)),
|
|
136
156
|
name: NSNotification.Name.CDVPluginHandleOpenURL,
|
|
137
157
|
object: nil)
|
|
158
|
+
|
|
159
|
+
NotificationCenter.default.addObserver(self,
|
|
160
|
+
selector: #selector(OAuthPlugin._handleCancel(_:)),
|
|
161
|
+
name: NSNotification.Name.CDVPluginOAuthCancelled,
|
|
162
|
+
object: nil)
|
|
138
163
|
}
|
|
139
164
|
|
|
140
165
|
|
|
@@ -149,7 +174,9 @@ class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticati
|
|
|
149
174
|
return
|
|
150
175
|
}
|
|
151
176
|
|
|
152
|
-
|
|
177
|
+
self.closeCallbackId = command.callbackId
|
|
178
|
+
|
|
179
|
+
if OAuthPlugin.forcedVersion >= 12, #available(iOS 12.0, *) {
|
|
153
180
|
self.authSystem = ASWebAuthenticationSessionOAuthSessionProvider(url, callbackScheme:self.callbackScheme!)
|
|
154
181
|
|
|
155
182
|
if #available(iOS 13.0, *) {
|
|
@@ -157,9 +184,9 @@ class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticati
|
|
|
157
184
|
aswas.delegate = self
|
|
158
185
|
}
|
|
159
186
|
}
|
|
160
|
-
} else if #available(iOS 11.0, *) {
|
|
187
|
+
} else if OAuthPlugin.forcedVersion >= 11, #available(iOS 11.0, *) {
|
|
161
188
|
self.authSystem = SFAuthenticationSessionOAuthSessionProvider(url, callbackScheme:self.callbackScheme!)
|
|
162
|
-
} else if #available(iOS 9.0, *) {
|
|
189
|
+
} else if OAuthPlugin.forcedVersion >= 9, #available(iOS 9.0, *) {
|
|
163
190
|
self.authSystem = SFSafariViewControllerOAuthSessionProvider(url, callbackScheme:self.callbackScheme!)
|
|
164
191
|
|
|
165
192
|
if let sfvc = self.authSystem as? SFSafariViewControllerOAuthSessionProvider {
|
|
@@ -172,7 +199,6 @@ class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticati
|
|
|
172
199
|
|
|
173
200
|
self.authSystem?.start()
|
|
174
201
|
|
|
175
|
-
self.commandDelegate.send(CDVPluginResult(status: .ok), callbackId: command.callbackId)
|
|
176
202
|
return
|
|
177
203
|
}
|
|
178
204
|
|
|
@@ -181,24 +207,38 @@ class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticati
|
|
|
181
207
|
self.authSystem?.cancel()
|
|
182
208
|
self.authSystem = nil
|
|
183
209
|
|
|
184
|
-
var jsobj : [String : String] = [:]
|
|
210
|
+
var jsobj : [String : String] = ["oauth_callback_url": url.absoluteString]
|
|
185
211
|
let queryItems = URLComponents(url: url, resolvingAgainstBaseURL: false)?.queryItems
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
212
|
+
let fragment = url.fragment
|
|
213
|
+
|
|
214
|
+
// Parse fragment parameters
|
|
215
|
+
if let fragment = fragment {
|
|
216
|
+
let pairs = fragment.split(separator: "&")
|
|
217
|
+
for pair in pairs {
|
|
218
|
+
let keyValue = pair.split(separator: "=")
|
|
219
|
+
if keyValue.count == 2,
|
|
220
|
+
let key = keyValue[0].removingPercentEncoding,
|
|
221
|
+
let value = keyValue[1].removingPercentEncoding {
|
|
222
|
+
jsobj[String(key)] = String(value)
|
|
223
|
+
}
|
|
224
|
+
}
|
|
189
225
|
}
|
|
190
226
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
} else {
|
|
194
|
-
NSLog("OAuth called back with parameters.")
|
|
227
|
+
queryItems?.forEach {
|
|
228
|
+
jsobj[$0.name] = $0.value
|
|
195
229
|
}
|
|
196
230
|
|
|
197
231
|
do {
|
|
198
232
|
let data = try JSONSerialization.data(withJSONObject: jsobj)
|
|
199
|
-
let msg = String(data: data, encoding: .utf8)!
|
|
200
233
|
|
|
201
|
-
|
|
234
|
+
if let msg = String(data: data, encoding: .utf8) {
|
|
235
|
+
let jsMsg = msg.replacingOccurrences(of: "'", with: "\\'")
|
|
236
|
+
.replacingOccurrences(of: "\n", with: "\\n")
|
|
237
|
+
.replacingOccurrences(of: "\r", with: "\\r")
|
|
238
|
+
.replacingOccurrences(of: "\t", with: "\\t")
|
|
239
|
+
|
|
240
|
+
self.webViewEngine.evaluateJavaScript("window.dispatchEvent(new MessageEvent('message', { data: 'oauth::\(jsMsg)' }));", completionHandler: nil)
|
|
241
|
+
}
|
|
202
242
|
} catch {
|
|
203
243
|
let errStr = "JSON Serialization failed: \(error)"
|
|
204
244
|
if #available(iOS 10.0, *) {
|
|
@@ -220,13 +260,28 @@ class OAuthPlugin : CDVPlugin, SFSafariViewControllerDelegate, ASWebAuthenticati
|
|
|
220
260
|
}
|
|
221
261
|
|
|
222
262
|
self.parseToken(from: url)
|
|
263
|
+
|
|
264
|
+
if let cb = self.closeCallbackId {
|
|
265
|
+
self.commandDelegate.send(CDVPluginResult(status: .ok), callbackId: cb)
|
|
266
|
+
}
|
|
267
|
+
self.closeCallbackId = nil
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
@objc internal func _handleCancel(_ notification : NSNotification) {
|
|
272
|
+
if let cb = self.closeCallbackId {
|
|
273
|
+
self.commandDelegate.send(CDVPluginResult(status: .ok), callbackId: cb)
|
|
274
|
+
}
|
|
275
|
+
self.closeCallbackId = nil
|
|
223
276
|
}
|
|
224
277
|
|
|
225
278
|
|
|
226
279
|
@available(iOS 9.0, *)
|
|
227
280
|
func safariViewControllerDidFinish(_ controller: SFSafariViewController) {
|
|
228
|
-
|
|
229
|
-
|
|
281
|
+
self.authSystem?.cancel()
|
|
282
|
+
self.authSystem = nil
|
|
283
|
+
|
|
284
|
+
NotificationCenter.default.post(name: NSNotification.Name.CDVPluginOAuthCancelled, object: nil)
|
|
230
285
|
}
|
|
231
286
|
|
|
232
287
|
@available(iOS 13.0, *)
|
package/www/oauth.js
CHANGED
|
@@ -18,10 +18,115 @@ var exec = require('cordova/exec');
|
|
|
18
18
|
var modulemapper = require('cordova/modulemapper');
|
|
19
19
|
var noop = function() { };
|
|
20
20
|
|
|
21
|
+
// https://github.com/ungap/event-target
|
|
22
|
+
/**
|
|
23
|
+
* Copyright (c) 2018, Andrea Giammarchi, @WebReflection
|
|
24
|
+
*
|
|
25
|
+
* Permission to use, copy, modify, and/or distribute this software for any
|
|
26
|
+
* purpose with or without fee is hereby granted, provided that the above
|
|
27
|
+
* copyright notice and this permission notice appear in all copies.
|
|
28
|
+
*
|
|
29
|
+
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
30
|
+
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
31
|
+
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
32
|
+
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
33
|
+
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
|
|
34
|
+
* OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
35
|
+
* PERFORMANCE OF THIS SOFTWARE.
|
|
36
|
+
*/
|
|
37
|
+
var EventTargetPolyfill = (function(Object, wm) {
|
|
38
|
+
var create = Object.create;
|
|
39
|
+
var defineProperty = Object.defineProperty;
|
|
40
|
+
var proto = EventTarget.prototype;
|
|
41
|
+
define(proto, 'addEventListener', function (type, listener, options) {
|
|
42
|
+
for (var
|
|
43
|
+
secret = wm.get(this),
|
|
44
|
+
listeners = secret[type] || (secret[type] = []),
|
|
45
|
+
i = 0, length = listeners.length; i < length; i++
|
|
46
|
+
) {
|
|
47
|
+
if (listeners[i].listener === listener)
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
listeners.push({target: this, listener: listener, options: options});
|
|
51
|
+
});
|
|
52
|
+
define(proto, 'dispatchEvent', function (event) {
|
|
53
|
+
var secret = wm.get(this);
|
|
54
|
+
var listeners = secret[event.type];
|
|
55
|
+
if (listeners) {
|
|
56
|
+
define(event, 'target', this);
|
|
57
|
+
define(event, 'currentTarget', this);
|
|
58
|
+
listeners.slice(0).some(dispatch, event);
|
|
59
|
+
delete event.currentTarget;
|
|
60
|
+
delete event.target;
|
|
61
|
+
}
|
|
62
|
+
return true;
|
|
63
|
+
});
|
|
64
|
+
define(proto, 'removeEventListener', function (type, listener) {
|
|
65
|
+
for (var
|
|
66
|
+
secret = wm.get(this),
|
|
67
|
+
/* istanbul ignore next */
|
|
68
|
+
listeners = secret[type] || (secret[type] = []),
|
|
69
|
+
i = 0, length = listeners.length; i < length; i++
|
|
70
|
+
) {
|
|
71
|
+
if (listeners[i].listener === listener) {
|
|
72
|
+
listeners.splice(i, 1);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return EventTarget;
|
|
78
|
+
|
|
79
|
+
function EventTarget() {'use strict';
|
|
80
|
+
wm.set(this, create(null));
|
|
81
|
+
}
|
|
82
|
+
function define(target, name, value) {
|
|
83
|
+
defineProperty(
|
|
84
|
+
target,
|
|
85
|
+
name,
|
|
86
|
+
{
|
|
87
|
+
configurable: true,
|
|
88
|
+
writable: true,
|
|
89
|
+
value: value
|
|
90
|
+
}
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
function dispatch(info) {
|
|
94
|
+
var options = info.options;
|
|
95
|
+
if (options && options.once)
|
|
96
|
+
info.target.removeEventListener(this.type, info.listener);
|
|
97
|
+
if (typeof info.listener === 'function')
|
|
98
|
+
info.listener.call(info.target, this);
|
|
99
|
+
else
|
|
100
|
+
info.listener.handleEvent(this);
|
|
101
|
+
return this._stopImmediatePropagationFlag;
|
|
102
|
+
}
|
|
103
|
+
})(Object, new WeakMap());
|
|
104
|
+
|
|
21
105
|
|
|
22
106
|
module.exports = function(url, name, features) {
|
|
23
|
-
|
|
24
|
-
|
|
107
|
+
var nameMatch = name && name.match && name.match(/^oauth:/);
|
|
108
|
+
var featureMatch = features && features.match && features.match(/^(?:.+,)?(oauth)(?:[=,].*)?$/i);
|
|
109
|
+
|
|
110
|
+
if (nameMatch || featureMatch) {
|
|
111
|
+
var wnd = null;
|
|
112
|
+
if (window.EventTarget) {
|
|
113
|
+
wnd = new EventTarget();
|
|
114
|
+
} else {
|
|
115
|
+
wnd = new EventTargetPolyfill();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function success() {
|
|
119
|
+
if (wnd) {
|
|
120
|
+
if (wnd.onclose) {
|
|
121
|
+
wnd.onclose();
|
|
122
|
+
}
|
|
123
|
+
wnd.dispatchEvent(new Event('close'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
cordova.exec(success, noop, 'OAuth', 'startOAuth', [url]);
|
|
128
|
+
|
|
129
|
+
return wnd;
|
|
25
130
|
} else {
|
|
26
131
|
var originalWindowOpen = modulemapper.getOriginalSymbol(window, 'open');
|
|
27
132
|
return originalWindowOpen.apply(window, arguments);
|