Open galfert opened 6 years ago
Any news on this? I saw you were getting issues with Google Drive in #1099.
I think it's definitely worth looking into.
Android isn't yet stable, which is a concern. Looks good enough though. Chrome custom tabs are considerably faster, too, which is a plus.
I think now might be a good time to consider an API that allows you to implement your own OAuth process (in addition to the default process). I've just had to implement an OAuth function for Electron using electron.remote.BrowserWindow, because window.open doesn't work in Electron. It looks pretty much identical to the code in the Authorise() function aside from opening the window and intercepting the token url (so lots of repetition).
I haven't looked into this one yet. But I managed to solve the Google Drive issue (see https://github.com/remotestorage/remotestorage.js/pull/1099#issuecomment-346601946).
Would you mind sharing the changes you had to make? It would help to determine how a changed API would have to look like.
My solution to this isn't perfect, but here's what I've been doing:
The only thing that really needs to be changed is the implementation of the browser window popup. For example Electron.js cannot use window.open(...'_blank'...) to open up a pop up. Instead it has to use electron.remote.BrowserWindow.
The BrowserWindow class is an abstract
export abstract class BrowserWindow {
protected listeners : any[] = [];
/**
* Creates the browser window, shows it, and handles detecting the correct URL
*/
public abstract show(url : string);
/**
* Closes the browser window
*/
public abstract close();
/**
* Add callback function to the list of listeners
* @param fn Function that will be called with fn(url) on url change
*/
public onUrlChange(fn) {
this.listeners.push(fn);
}
/**
* Emit the changes of the url
*/
protected emitUrlChange(url) {
for (let listener of this.listeners) {
listener(url);
}
}
};
This is then extended for each platform I use.
class ElectronBrowserWindow extends BrowserWindow {
private handle : any;
public show(url : string) {
this.handle = new electron.remote.BrowserWindow({
width: 800,
height: 800,
webPreferences: {
nodeIntegration: false
}
});
this.handle.loadURL(url);
this.handle.webContents.on('did-get-response-details', (e, status, newURL, originalURL, httpResponseCode, requestMethod, referrer, headers, resourceType) => {
console.log(newURL);
if (newURL.indexOf(cordovaRedirectUri) === 0) {
this.emitUrlChange(newURL);
}
});
}
public close() {
this.handle.close();
}
};
class CordovaBrowserWindow extends BrowserWindow {
private handle : InAppBrowserObject;
private iab : InAppBrowser;
constructor() {
super();
this.iab = new InAppBrowser();
}
public show(url : string) {
this.handle = this.iab.create(url);
this.handle.on('loadstart')
.subscribe((event) => {
console.log(event);
if (event.url.indexOf(cordovaRedirectUri) === 0) {
this.emitUrlChange(event.url);
}
});
}
public close() {
this.handle.close();
}
}
This class basically just does what remotestorage.js is already doing, just using my own implementation.
export const cordovaRedirectUri = 'https://YOUR_URL';
/**
* Abstract class for performing RemoteStorage.js OAuth cross-
* platform.
*/
export abstract class OAuthConnector {
private remoteStorage : RemoteStorage;
private services = {
dropbox: {
authUrl: 'https://www.dropbox.com/oauth2/authorize',
scopeUrl: '',
},
googledrive: {
authUrl: 'https://accounts.google.com/o/oauth2/auth',
scopeUrl: 'https://www.googleapis.com/auth/drive'
}
};
constructor(remoteStorage) {
this.remoteStorage = remoteStorage;
}
public async connect(service) {
this.remoteStorage.setBackend(service);
let authWin : BrowserWindow = this.createBrowserWindow();
let url = this.buildUrl(service);
authWin.show(url);
authWin.onUrlChange((newURL) => {
let token = this.getResponseTokenFromUrl(newURL);
this.remoteStorage.remote.configure({
token: token
});
authWin.close();
});
}
protected abstract createBrowserWindow() : BrowserWindow;
/**
* Builds the OAuth URL for the given service
* @param service The service for which to build the OAuth URL
*/
private buildUrl(service) {
let api = this.remoteStorage.apiKeys[service];
let apiKey = api.appKey || api.clientId;
let url = this.services[service].authUrl;
url += '?response_type=token'
url += '&client_id=' + encodeURIComponent(apiKey);
url += '&redirect_uri=' + encodeURIComponent(cordovaRedirectUri);
url += '&scope=' + encodeURIComponent(this.services[service].scopeUrl);
url += '&state';
return url;
}
/**
* Extracts the OAuth response token from the given url
* @param url The URL
*/
private getResponseTokenFromUrl(url) {
// get the GET params part of the URL
let urlParams = url.split('#')[1].split('&');
let responseParams = {};
for (let param of urlParams) {
let paramSplit = param.split('=');
let val = undefined;
if (paramSplit.length == 2) {
val = paramSplit[1];
}
responseParams[paramSplit[0]] = val;
}
return responseParams['access_token'];
}
};
declare let electron;
export class ElectronConnector extends OAuthConnector {
protected createBrowserWindow() : BrowserWindow {
return new ElectronBrowserWindow();
}
};
import { InAppBrowser, InAppBrowserObject } from "@ionic-native/in-app-browser";
export class CordovaConnector extends OAuthConnector {
protected createBrowserWindow() : BrowserWindow {
return new CordovaBrowserWindow();
}
};
This is how it is used inside my "authenticate()" function:
if (this.electronPlatform.isElectron()) {
// add electron connector and connect
let connector = new ElectronConnector(this.remoteStorage);
connector.connect(service);
}
This could definitely be refactored a whole lot so just the BrowserWindow classes are needed and would be injected into a concrete OAuthConnector class, and can definitely make it a bit more DRY, but this is what I'm using at the moment.
I found this article, which suggests to use SafariViewController instead of InAppBrowser.
Unlike the name would suggest, SafariViewController is not for iOS only, but also supports Android, using Chrome custom tabs.
The article lists several reasons not to use InAppBrowser anymore:
I'm not sure though if you can also intercept the redirect to extract the token from the URL, like we are doing it now. The article suggests to use a custom URL scheme for your app, so the redirect page can extract the token and pass it to the app.
What do you think? Is it worth looking into?