Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion examples/demo-app/src/cloud-providers/dropbox-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const KEPLER_DROPBOX_FOLDER_LINK = `//${DOMAIN}/home/Apps`;
const CORS_FREE_DOMAIN = 'dl.dropboxusercontent.com';

export default class DropboxProvider {
constructor(clientId, appName, icon = DropboxIcon) {
constructor(appName, clientId, icon = DropboxIcon) {
// All cloud-providers providers must implement the following properties
this.name = NAME;
this.clientId = clientId;
Expand Down
65 changes: 65 additions & 0 deletions examples/demo-app/src/cloud-providers/google-drive-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* global gapi */
import GoogleProvider from './google-provider';
import GoogleDriveIcon from '../components/icons/google-drive-icon';

// Array of API discovery doc URLs for APIs
const DISCOVERY_DOCS = ["https://www.googleapis.com/discovery/v1/apis/drive/v3/rest"];

const SCOPES = ['https://www.googleapis.com/auth/drive.file']; // Restrict to files from this app

// There is a selection of access scopes for Google Drive, e.g.
// - https://www.googleapis.com/auth/drive See, edit, create, and delete all of your Google Drive files
// - https://www.googleapis.com/auth/drive.appdata View and manage its own configuration data in your Google Drive
// - https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
// See https://developers.google.com/identity/protocols/googlescopes#drivev3

export default class GoogleDriveProvider extends GoogleProvider {
/**
* To access Google Drive, specify it in scopes
* param scopes = ['https://www.googleapis.com/auth/drive.file'];
*/
constructor(appName, clientId, appKey, scopes = SCOPES) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try to make to make this consistent across all cloud providers.
Right now Dropbox provider accepts an icon where here we have different parameters.

I think we can follow a more flexible pattern:

constructor(appName, clientId, options = {})

options can handle all different requirement for each cloud provider

super({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For default parameters I would create a constant and merge it with constructor’s postmasters

clientId,
appKey,
name: 'Google Drive',
appName,
icon: GoogleDriveIcon,
discoveryDocs: DISCOVERY_DOCS,
scopes
});
}

/**
* Uploads a file to Google Drive
* @param blob to upload
* @param name if blob doesn't contain a file name, this field is used
* @param isPublic define whether the file will be available pubblicaly once uploaded
* @returns {Promise<void>}
*/
async uploadFile({blob, name, isPublic = true}) {
const mimeType = 'application/json';

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be defined outside the scope of this method.

Actually it could be defined in a cloud provider constant file since other providers may take advantage of it

// gapi.client.drive doesn't seem to have good stubs for uploading, so we use the Google Drive REST API
const metadata = {
name, // Filename on Google Drive
mimeType, // mimeType on Google Drive
parents: ['root'] // Folder ID at Google Drive

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Constant

};

// The body of the post request will be this form
const form = new FormData();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would encapsulate the logic to generate the body into an helper method

form.append('metadata', new Blob([JSON.stringify(metadata)], {type: mimeType}));
form.append('file', blob);
const accessToken = gapi.auth.getToken().access_token; // Here gapi is used for retrieving the access token.

return fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id', {
method: 'POST',
headers: new Headers({Authorization: `Bearer ${accessToken}`}),
body: form
})
.then(res => res.json())
.catch(error => console.error(val)); // eslint-disable-line

// TODO - implement sharing
}
}
142 changes: 142 additions & 0 deletions examples/demo-app/src/cloud-providers/google-provider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/* global window, gapi */
import {loadScript} from '../utils/load-script';

// We load `gapi` at run-time from this URL
const GOOGLE_API_URL = 'https://apis.google.com/js/api.js';

export default class GoogleProvider {
// Create a new Google Account provider
// To access Google Drive, specify it in scopes
// @param scopes = ['https://www.googleapis.com/auth/drive'];
constructor({clientId, appKey, name, appName, icon, discoveryDocs, scopes = []}) {
// All cloud-providers providers must implement the following properties

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m not clear what the hierarchy is here. My understanding is the following:
GoogleDroveProvider and DropboxProvider are peers and GoogleProvider and DropboxSdk are their respective parents.

But constructors don’t reflect such hierarchy, why google provider have an icon given it is an api layer?

If my first assumptions is wrong, can you clarify in the PR description the role of GoolgeProvider and GoogleCloudProvider?

I only see google provider in it

this.appName = appName;
this.name = name;
this.icon = icon;

this._gapiInitializationOptions = {clientId, appKey, discoveryDocs, scopes};
this._clearUserData();
}

/**
* This method will handle the oauth flow by performing the following steps:
* - Opening a popup window and letting the user select google account
* - If already signed in to the browser, the window will disappear immediately
* @returns {Promise<void>}
*/
async login(onCloudLoginSuccess) {
await this._initializeGoogleApi();

// NOTE: We are not using async/await syntax here because:
// - babel's "heavy-handed" transpilation of `async/await` moves this code into a callback
// - but it needs to run directly in the `onClick` handler since it opens a popup
// - and popups that are not a direct result of the user's actions are typically blocked by browser settings.
return gapi.auth2.getAuthInstance().signIn().then(googleUser => {
this._getUserData(googleUser);
this._onLoginStatusChange();
onCloudLoginSuccess(this.name);
}).catch (error => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you call onCloudLoginFailure?

console.error('GOOGLE API LOGIN ERROR', error); // eslint-disable-line
});
}

/**
* Provides the current auth token.
* @returns {any}
*/
getAccessToken() {
const token = this.accessToken;
return (token || '') !== '' ? token : null;
}

// FOR DERIVED CLASSES

_onLoginStatusChange(googleUser) {
// Redefine in subclass
}

// PRIVATE

// Initialize the Google API (gapi) by loading it dynamically and initializing with appropriate scope
// TODO
// gapi could potentially be initialized by other parts of kepler (in particular, by other cloud providers)
// To fully handle that case we would likely need to use the advanced gapi authentication API,
// as the recommended API we use here only supports one sign-in.
async _initializeGoogleApi() {
// Check if already initialized
if (window.gapi) {
return;
}

const {clientId, appKey, discoveryDocs, scopes} = this._gapiInitializationOptions;

await loadScript(GOOGLE_API_URL);

// gapi.load is supposed return a thenable object, but await does not seem to work on it so wrap in Promise instead
await new Promise(resolve => gapi.load('client', resolve));

// Initialize the JavaScript client library.
// TODO - https://stackoverflow.com/questions/15657983/popup-blocking-the-gdrive-authorization-in-chrome

const gapiOptions = {
discoveryDocs,
scope: scopes.join(" "),
fetch_basic_profile: true,
immediate: false
};
if (clientId) {
gapiOptions.client_id = clientId;
}
if (appKey) {
gapiOptions.appKey = appKey;
}
await gapi.client.init(gapiOptions);

// For APIKEY, no authinstance is provided
const authInstance = gapi.auth2.getAuthInstance();
if (!authInstance) {
return;
}

// LISTEN FOR LOGIN
authInstance.isSignedIn.listen(isSignedIn => {
if (isSignedIn) {
// NOTE: This is only triggered on fresh login. I.e. signIn() does not trigger this if browser already logged in
console.log('GOOGLE API LOGIN DETECTED'); // eslint-disable-line
} else {
console.log('GOOGLE API LOGOUT DETECTED');// eslint-disable-line
this._clearUserData();
this._onLoginChange(null);
}

// Update derived classes
this._onLoginStatusChange();
// TODO - also needs a callback to update the app UI...
});
}

// Extract a minimum of user data from the GoogleUser object
_getUserData(googleUser) {
const userProfile = googleUser.getBasicProfile();
this.email = userProfile.getEmail();
this.domain = googleUser.getHostedDomain();
this.imageUrl = userProfile.getImageUrl();

this.scopes = googleUser.getGrantedScopes().split(' ');

const authResponse = googleUser.getAuthResponse(true);
this.accessToken = authResponse.access_token;
this.idToken = authResponse.id_token;
}

// Clear user data
_clearUserData() {
this.signedIn = false;
this.email = null;
this.domain = null;
this.imageUrl = null;
this.scopes = [];
this.accessToken = null;
this.idToken = null;
}
}
15 changes: 11 additions & 4 deletions examples/demo-app/src/cloud-providers/providers.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,20 @@
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

// Pure cloud storage providers
import DropboxProvider from './dropbox-provider';
import GoogleDriveProvider from './google-drive-provider';

import {AUTH_TOKENS} from '../constants/default-settings';

import DropboxProvider from './dropbox-provider';
const DROPBOX_CLIENT_NAME = AUTH_TOKENS.DROPBOX_CLIENT_NAME;
const DROPBOX_CLIENT_ID = AUTH_TOKENS.DROPBOX_CLIENT_ID;

const {DROPBOX_CLIENT_ID} = AUTH_TOKENS;
const DROPBOX_CLIENT_NAME = 'Kepler.gl%20(managed%20by%20Uber%20Technologies%2C%20Inc.)';
const GOOGLE_DRIVE_CLIENT_NAME = AUTH_TOKENS.GOOGLE_DRIVE_CLIENT_NAME;
const GOOGLE_DRIVE_CLIENT_ID = AUTH_TOKENS.GOOGLE_DRIVE_CLIENT_ID;
const GOOGLE_DRIVE_APPKEY = AUTH_TOKENS.GOOGLE_DRIVE_APPKEY;

export const CLOUD_PROVIDERS = [
new DropboxProvider(DROPBOX_CLIENT_ID, DROPBOX_CLIENT_NAME)
new GoogleDriveProvider(GOOGLE_DRIVE_CLIENT_NAME, GOOGLE_DRIVE_CLIENT_ID, GOOGLE_DRIVE_APPKEY),
new DropboxProvider(DROPBOX_CLIENT_NAME, DROPBOX_CLIENT_ID)
];
Loading