22
33import android .app .Activity ;
44import android .app .PendingIntent ;
5+ import android .content .ComponentName ;
56import android .content .Context ;
67import android .content .Intent ;
78import android .net .Uri ;
9+ import android .os .Bundle ;
810import 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 ;
915
1016import com .facebook .react .bridge .ActivityEventListener ;
1117import com .facebook .react .bridge .ReactApplicationContext ;
3743import net .openid .appauth .connectivity .ConnectionBuilder ;
3844import net .openid .appauth .connectivity .DefaultConnectionBuilder ;
3945
46+ import java .util .Collections ;
4047import java .util .HashMap ;
4148import java .util .Map ;
49+ import java .util .concurrent .atomic .AtomicReference ;
50+ import java .util .concurrent .CountDownLatch ;
4251
4352public class RNAppAuthModule extends ReactContextBaseJavaModule implements ActivityEventListener {
4453
54+ public static final String CUSTOM_TAB_PACKAGE_NAME = "com.android.chrome" ;
55+
4556 private final ReactApplicationContext reactContext ;
4657 private Promise promise ;
4758 private Boolean dangerouslyAllowInsecureHttpRequests ;
@@ -50,13 +61,76 @@ public class RNAppAuthModule extends ReactContextBaseJavaModule implements Activ
5061 private Map <String , String > tokenRequestHeaders = null ;
5162 private Map <String , String > additionalParametersMap ;
5263 private String clientSecret ;
64+ private final AtomicReference <AuthorizationServiceConfiguration > mServiceConfiguration = new AtomicReference <>();
65+ private boolean isPrefetched = false ;
5366
5467 public RNAppAuthModule (ReactApplicationContext reactContext ) {
5568 super (reactContext );
5669 this .reactContext = reactContext ;
5770 reactContext .addActivityEventListener (this );
5871 }
5972
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+
60134 @ ReactMethod
61135 public void authorize (
62136 String issuer ,
@@ -89,10 +163,11 @@ public void authorize(
89163 this .clientAuthMethod = clientAuthMethod ;
90164
91165 // 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 ) {
93167 try {
168+ final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration .get () != null ? mServiceConfiguration .get () : createAuthorizationServiceConfiguration (serviceConfiguration );
94169 authorizeWithConfiguration (
95- createAuthorizationServiceConfiguration ( serviceConfiguration ) ,
170+ serviceConfig ,
96171 appAuthConfiguration ,
97172 clientId ,
98173 scopes ,
@@ -116,6 +191,8 @@ public void onFetchConfigurationCompleted(
116191 return ;
117192 }
118193
194+ mServiceConfiguration .set (fetchedConfiguration );
195+
119196 authorizeWithConfiguration (
120197 fetchedConfiguration ,
121198 appAuthConfiguration ,
@@ -165,10 +242,11 @@ public void refresh(
165242 this .additionalParametersMap = additionalParametersMap ;
166243
167244 // 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 ) {
169246 try {
247+ final AuthorizationServiceConfiguration serviceConfig = mServiceConfiguration .get () != null ? mServiceConfiguration .get () : createAuthorizationServiceConfiguration (serviceConfiguration );
170248 refreshWithConfiguration (
171- createAuthorizationServiceConfiguration ( serviceConfiguration ) ,
249+ serviceConfig ,
172250 appAuthConfiguration ,
173251 refreshToken ,
174252 clientId ,
@@ -184,7 +262,6 @@ public void refresh(
184262 }
185263 } else {
186264 final Uri issuerUri = Uri .parse (issuer );
187- // @TODO: Refactor to avoid hitting IDP endpoint on refresh, reuse fetchedConfiguration if possible.
188265 AuthorizationServiceConfiguration .fetchFromUrl (
189266 buildConfigurationUriFromIssuer (issuerUri ),
190267 new AuthorizationServiceConfiguration .RetrieveConfigurationCallback () {
@@ -196,6 +273,8 @@ public void onFetchConfigurationCompleted(
196273 return ;
197274 }
198275
276+ mServiceConfiguration .set (fetchedConfiguration );
277+
199278 refreshWithConfiguration (
200279 fetchedConfiguration ,
201280 appAuthConfiguration ,
@@ -498,6 +577,21 @@ private AuthorizationServiceConfiguration createAuthorizationServiceConfiguratio
498577 );
499578 }
500579
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+ }
501595
502596 @ Override
503597 public void onNewIntent (Intent intent ) {
0 commit comments