2
2
3
3
import android .app .Activity ;
4
4
import android .app .PendingIntent ;
5
+ import android .content .ComponentName ;
5
6
import android .content .Context ;
6
7
import android .content .Intent ;
7
8
import android .net .Uri ;
9
+ import android .os .Bundle ;
8
10
import android .support .annotation .Nullable ;
11
+ import android .support .customtabs .CustomTabsCallback ;
12
+ import android .support .customtabs .CustomTabsClient ;
13
+ import android .support .customtabs .CustomTabsServiceConnection ;
14
+ import android .util .Log ;
9
15
10
16
import com .facebook .react .bridge .ActivityEventListener ;
11
17
import com .facebook .react .bridge .ReactApplicationContext ;
37
43
import net .openid .appauth .connectivity .ConnectionBuilder ;
38
44
import net .openid .appauth .connectivity .DefaultConnectionBuilder ;
39
45
46
+ import java .util .Collections ;
40
47
import java .util .HashMap ;
41
48
import java .util .Map ;
49
+ import java .util .concurrent .atomic .AtomicReference ;
50
+ import java .util .concurrent .CountDownLatch ;
42
51
43
52
public class RNAppAuthModule extends ReactContextBaseJavaModule implements ActivityEventListener {
44
53
54
+ public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome" ;
55
+
45
56
private final ReactApplicationContext reactContext ;
46
57
private Promise promise ;
47
58
private Boolean dangerouslyAllowInsecureHttpRequests ;
@@ -50,13 +61,76 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ
50
61
private Map <String , String > tokenRequestHeaders = null ;
51
62
private Map <String , String > additionalParametersMap ;
52
63
private String clientSecret ;
64
+ private final AtomicReference <AuthorizationServiceConfiguration > mServiceConfiguration = new AtomicReference <>();
65
+ private boolean isPrefetched = false ;
53
66
54
67
public RNAppAuthModule (ReactApplicationContext reactContext ) {
55
68
super (reactContext );
56
69
this .reactContext = reactContext ;
57
70
reactContext .addActivityEventListener (this );
58
71
}
59
72
73
+ @ ReactMethod
74
+ public void prefetchConfiguration (
75
+ final Boolean warmAndPrefetchChrome ,
76
+ final String issuer ,
77
+ final String redirectUrl ,
78
+ final String clientId ,
79
+ final ReadableArray scopes ,
80
+ final ReadableMap serviceConfiguration ,
81
+ final Boolean dangerouslyAllowInsecureHttpRequests ,
82
+ final ReadableMap headers ,
83
+ final Promise promise
84
+ ) {
85
+ if (warmAndPrefetchChrome ) {
86
+ warmChromeCustomTab (reactContext , issuer );
87
+ }
88
+
89
+ this .parseHeaderMap (headers );
90
+ final ConnectionBuilder builder = createConnectionBuilder (dangerouslyAllowInsecureHttpRequests , this .authorizationRequestHeaders );
91
+ final CountDownLatch fetchConfigurationLatch = new CountDownLatch (1 );
92
+
93
+ if (!isPrefetched ) {
94
+ if (serviceConfiguration != null && mServiceConfiguration .get () == null ) {
95
+ try {
96
+ mServiceConfiguration .set (createAuthorizationServiceConfiguration (serviceConfiguration ));
97
+ isPrefetched = true ;
98
+ fetchConfigurationLatch .countDown ();
99
+ } catch (Exception e ) {
100
+ promise .reject ("RNAppAuth Error" , "Failed to convert serviceConfiguration" , e );
101
+ }
102
+ } else if (mServiceConfiguration .get () == null ) {
103
+ final Uri issuerUri = Uri .parse (issuer );
104
+ AuthorizationServiceConfiguration .fetchFromUrl (
105
+ buildConfigurationUriFromIssuer (issuerUri ),
106
+ new AuthorizationServiceConfiguration .RetrieveConfigurationCallback () {
107
+ public void onFetchConfigurationCompleted (
108
+ @ Nullable AuthorizationServiceConfiguration fetchedConfiguration ,
109
+ @ Nullable AuthorizationException ex ) {
110
+ if (ex != null ) {
111
+ promise .reject ("RNAppAuth Error" , "Failed to fetch configuration" , ex );
112
+ return ;
113
+ }
114
+ mServiceConfiguration .set (fetchedConfiguration );
115
+ isPrefetched = true ;
116
+ fetchConfigurationLatch .countDown ();
117
+ }
118
+ },
119
+ builder
120
+ );
121
+ }
122
+ } else {
123
+ fetchConfigurationLatch .countDown ();
124
+ }
125
+
126
+ try {
127
+ fetchConfigurationLatch .await ();
128
+ promise .resolve (isPrefetched );
129
+ } catch (Exception e ) {
130
+ promise .reject ("RNAppAuth Error" , "Failed to await fetch configuration" , e );
131
+ }
132
+ }
133
+
60
134
@ ReactMethod
61
135
public void authorize (
62
136
String issuer ,
@@ -89,10 +163,11 @@ public void authorize(
89
163
this .clientAuthMethod = clientAuthMethod ;
90
164
91
165
// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
92
- if (serviceConfiguration != null ) {
166
+ if (serviceConfiguration != null || mServiceConfiguration . get () != null ) {
93
167
try {
168
+ final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration .get () != null ? mServiceConfiguration .get () : createAuthorizationServiceConfiguration (serviceConfiguration );
94
169
authorizeWithConfiguration (
95
- createAuthorizationServiceConfiguration ( serviceConfiguration ) ,
170
+ serviceConfig ,
96
171
appAuthConfiguration ,
97
172
clientId ,
98
173
scopes ,
@@ -116,6 +191,8 @@ public void onFetchConfigurationCompleted(
116
191
return ;
117
192
}
118
193
194
+ mServiceConfiguration .set (fetchedConfiguration );
195
+
119
196
authorizeWithConfiguration (
120
197
fetchedConfiguration ,
121
198
appAuthConfiguration ,
@@ -165,10 +242,11 @@ public void refresh(
165
242
this .additionalParametersMap = additionalParametersMap ;
166
243
167
244
// when serviceConfiguration is provided, we don't need to hit up the OpenID well-known id endpoint
168
- if (serviceConfiguration != null ) {
245
+ if (serviceConfiguration != null || mServiceConfiguration . get () != null ) {
169
246
try {
247
+ final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration .get () != null ? mServiceConfiguration .get () : createAuthorizationServiceConfiguration (serviceConfiguration );
170
248
refreshWithConfiguration (
171
- createAuthorizationServiceConfiguration ( serviceConfiguration ) ,
249
+ serviceConfig ,
172
250
appAuthConfiguration ,
173
251
refreshToken ,
174
252
clientId ,
@@ -184,7 +262,6 @@ public void refresh(
184
262
}
185
263
} else {
186
264
final Uri issuerUri = Uri .parse (issuer );
187
- // @TODO: Refactor to avoid hitting IDP endpoint on refresh, reuse fetchedConfiguration if possible.
188
265
AuthorizationServiceConfiguration .fetchFromUrl (
189
266
buildConfigurationUriFromIssuer (issuerUri ),
190
267
new AuthorizationServiceConfiguration .RetrieveConfigurationCallback () {
@@ -196,6 +273,8 @@ public void onFetchConfigurationCompleted(
196
273
return ;
197
274
}
198
275
276
+ mServiceConfiguration .set (fetchedConfiguration );
277
+
199
278
refreshWithConfiguration (
200
279
fetchedConfiguration ,
201
280
appAuthConfiguration ,
@@ -498,6 +577,21 @@ private AuthorizationServiceConfiguration createAuthorizationServiceConfiguratio
498
577
);
499
578
}
500
579
580
+ private void warmChromeCustomTab (Context context , final String issuer ) {
581
+ CustomTabsServiceConnection connection = new CustomTabsServiceConnection () {
582
+ @ Override
583
+ public void onCustomTabsServiceConnected (ComponentName name , CustomTabsClient client ) {
584
+ client .warmup (0 );
585
+ client .newSession (new CustomTabsCallback ()).mayLaunchUrl (Uri .parse (issuer ), null , Collections .<Bundle >emptyList ());
586
+ }
587
+
588
+ @ Override
589
+ public void onServiceDisconnected (ComponentName name ) {
590
+
591
+ }
592
+ };
593
+ CustomTabsClient .bindCustomTabsService (context , CUSTOM_TAB_PACKAGE_NAME , connection );
594
+ }
501
595
502
596
@ Override
503
597
public void onNewIntent (Intent intent ) {
0 commit comments