Skip to content

Commit

Permalink
Better handling for android auth (npomfret#9)
Browse files Browse the repository at this point in the history
* Handle being already signed in better

When already signed in, unnecessary auth calls are made to Google API. Already being signed in is now handled better.

This also gets rid of a weird flash when already signed in and making calls to `loginIfNeeded()`

Also api setup issues were hard to debug because it was not logging the exception code from google. This is also fixed.

* Update README.md

Add usage example for android

* Update README.md
  • Loading branch information
coopermaruyama authored May 31, 2021
1 parent 356f08f commit cadc9f5
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 44 deletions.
49 changes: 49 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,52 @@ RNCloudFs.listFiles({targetPath: path, scope: scope})
_targetPath_: a path representing a folder to list files from

_scope_: a string to specify if the files are the user-visible documents (`visible`) or the app-visible documents (`hidden`)


## Android

After following the instructions in [Getting started](./docs/getting-started.md) I recommend using [react-native-google-signin](https://github.com/react-native-google-signin/google-signin) to authenticate the user, especially if you need additional scopes besides `auth/drive.file` which is the only scope this package requests, and let that package handle auth.

Example of usage on Android:

```js
import { Platform } from 'react-native';
import { GoogleSignin, statusCodes } from '@react-native-community/google-signin';
import RNCloudFS from 'react-native-cloud-fs';

if (Platform.OS === 'android') {
GoogleSignin.configure({
scopes: [
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
'https://www.googleapis.com/auth/drive.file',
// other scopes your app needs
],
webClientId: '...' // you may not need this depending on scopes,
offlineAccess: true,
forceConsentPrompt: true,
});

// Ensure play services exist. Cannot sign in otherwise. If unavailable, this
// will show a modal asking the user to get play services before continuing.
await GoogleSignin.hasPlayServices({
showPlayServicesUpdateDialog: true
});

// If user is already signed in, don't need to call signIn again
const isSignedIn = await GoogleSignin.isSignedIn();
if (!isSignedIn) {
await GoogleSignin.signIn();
}

// Syncs signed in state to RNCloudFS
await RNCloudFS.loginIfNeeded();

// Now you can copy to cloud
await RNCloudFS.copyToCloud({
sourcePath: { path: 'some/path.txt' },
targetPath: `some/path.txt`,
scope: 'visible'
});
}
```
107 changes: 63 additions & 44 deletions android/src/main/java/org/rncloudfs/RNCloudFsModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,12 @@
import com.facebook.react.bridge.WritableNativeMap;
import com.google.android.gms.auth.api.signin.GoogleSignInOptions;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.ApiException;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.common.api.Scope;

import com.google.android.gms.auth.api.signin.GoogleSignIn;
import com.google.android.gms.auth.api.signin.GoogleSignInAccount;
import com.google.android.gms.auth.api.signin.GoogleSignInClient;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
Expand Down Expand Up @@ -132,8 +134,32 @@ public void deleteFromCloud(ReadableMap options, final Promise promise) {
@ReactMethod
public void loginIfNeeded(final Promise promise){
if (mDriveServiceHelper == null) {
this.signInPromise = promise;
requestSignIn();
// Check for already logged in account
GoogleSignInAccount account = GoogleSignIn.getLastSignedInAccount(this.reactContext);

// Not signed in - request sign in
if (account == null) {
this.signInPromise = promise;
requestSignIn();
} else {
// Restore mDriveServiceHelper
// Use the authenticated account to sign in to the Drive service.
GoogleAccountCredential credential =
GoogleAccountCredential.usingOAuth2(
this.reactContext, Collections.singleton(DriveScopes.DRIVE_APPDATA));
credential.setSelectedAccount(account.getAccount());
Drive googleDriveService =
new Drive.Builder(
AndroidHttp.newCompatibleTransport(),
new GsonFactory(),
credential)
.build();

// The DriveServiceHelper encapsulates all REST API and SAF functionality.
// Its instantiation is required before handling any onClick actions.
mDriveServiceHelper = new DriveServiceHelper(googleDriveService);
promise.resolve(true);
}
} else {
promise.resolve(true);
}
Expand Down Expand Up @@ -302,11 +328,13 @@ public void clearPendingOperations(){

@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
// super.onActivityResult(requestCode, resultCode, resultData);
switch (requestCode) {
case REQUEST_CODE_SIGN_IN:
if (resultCode == Activity.RESULT_OK && data != null) {
handleSignInResult(data);
}
// The Task returned from this call is always completed, no need to attach
// a listener.
Task<GoogleSignInAccount> task = GoogleSignIn.getSignedInAccountFromIntent(data);
handleSignInResult(task);
break;
case REQUEST_AUTHORIZATION:
if (resultCode == Activity.RESULT_OK && data != null) {
Expand Down Expand Up @@ -338,7 +366,6 @@ public void onActivityResult(Activity activity, int requestCode, int resultCode,
}
}

//super.onActivityResult(requestCode, resultCode, resultData);
}

@Override
Expand Down Expand Up @@ -393,44 +420,36 @@ public void logout(Promise promise) {
* Handles the {@code result} of a completed sign-in activity initiated from {@link
* #requestSignIn()} ()}.
*/
private void handleSignInResult(Intent result) {
GoogleSignIn.getSignedInAccountFromIntent(result)
.addOnSuccessListener(googleAccount -> {
Log.d(TAG, "Signed in as " + googleAccount.getEmail());

// Use the authenticated account to sign in to the Drive service.
GoogleAccountCredential credential =
GoogleAccountCredential.usingOAuth2(
this.reactContext, Collections.singleton(DriveScopes.DRIVE_APPDATA));
credential.setSelectedAccount(googleAccount.getAccount());
Drive googleDriveService =
new Drive.Builder(
AndroidHttp.newCompatibleTransport(),
new GsonFactory(),
credential)
.build();

// The DriveServiceHelper encapsulates all REST API and SAF functionality.
// Its instantiation is required before handling any onClick actions.
mDriveServiceHelper = new DriveServiceHelper(googleDriveService);

if(this.signInPromise != null){
this.signInPromise.resolve(true);
this.signInPromise = null;
}
})
.addOnFailureListener(exception -> {
try{
UserRecoverableAuthIOException e = (UserRecoverableAuthIOException)exception;
this.reactContext.startActivityForResult(e.getIntent(), REQUEST_AUTHORIZATION, null);
} catch(Exception e){
Log.e(TAG, "Unable to sign in.", exception);
if(this.signInPromise != null){
this.signInPromise.reject(exception);
this.signInPromise = null;
}
}
});
private void handleSignInResult(Task<GoogleSignInAccount> completedTask) {
try {
GoogleSignInAccount googleAccount = completedTask.getResult(ApiException.class);
Log.d(TAG, "Signed in as " + googleAccount.getEmail());

// Use the authenticated account to sign in to the Drive service.
GoogleAccountCredential credential =
GoogleAccountCredential.usingOAuth2(
this.reactContext, Collections.singleton(DriveScopes.DRIVE_APPDATA));
credential.setSelectedAccount(googleAccount.getAccount());
Drive googleDriveService =
new Drive.Builder(
AndroidHttp.newCompatibleTransport(),
new GsonFactory(),
credential)
.build();

// The DriveServiceHelper encapsulates all REST API and SAF functionality.
// Its instantiation is required before handling any onClick actions.
mDriveServiceHelper = new DriveServiceHelper(googleDriveService);

if(this.signInPromise != null){
this.signInPromise.resolve(true);
this.signInPromise = null;
}
} catch (ApiException e) {
// The ApiException status code indicates the detailed failure reason.
// Please refer to the GoogleSignInStatusCodes class reference for more information.
Log.w(TAG, "signInResult:failed code=" + e.getStatusCode());
}
}

/**
Expand Down

0 comments on commit cadc9f5

Please sign in to comment.