feat(oauth): Add new component for OAuth sign-in

Summary: Future services that require OAuth get a cute new component that lets them connect more easily.

Test Plan: Tested manually.

Reviewers: juan, evan

Reviewed By: evan

Differential Revision: https://phab.nylas.com/D3186
This commit is contained in:
Jackie Luo 2016-08-25 10:44:23 -07:00
parent 7406c1a94f
commit 1f15026a5f
4 changed files with 137 additions and 93 deletions

View file

@ -18,7 +18,7 @@ function base64url(inBuffer) {
.replace(/\//g, '_'); // Convert '/' to '_'
}
export function pollForGmailAccount(sessionKey, callback) {
export function makeGmailOAuthRequest(sessionKey, callback) {
EdgehillAPI.makeRequest({
path: `/oauth/google/token?key=${sessionKey}`,
method: "GET",

View file

@ -1,9 +1,8 @@
import React from 'react';
import {ipcRenderer, shell} from 'electron';
import {RetinaImg} from 'nylas-component-kit';
import {OAuthSignInPage} from 'nylas-component-kit';
import {
pollForGmailAccount,
makeGmailOAuthRequest,
buildGmailSessionKey,
buildGmailAuthURL,
} from './onboarding-helpers';
@ -11,7 +10,6 @@ import {
import OnboardingActions from './onboarding-actions';
import AccountTypes from './account-types';
const clipboard = require('electron').clipboard
export default class AccountSettingsPageGmail extends React.Component {
static displayName = "AccountSettingsPageGmail";
@ -22,105 +20,27 @@ export default class AccountSettingsPageGmail extends React.Component {
constructor() {
super()
this.state = {
showAlternative: false,
}
}
componentDidMount() {
// Show the "Sign in to Gmail" prompt for a moment before actually bouncing
// to Gmail. (400msec animation + 200msec to read)
this._sessionKey = buildGmailSessionKey();
this._pollTimer = null;
this._gmailAuthUrl = buildGmailAuthURL(this._sessionKey)
this._startTimer = setTimeout(() => {
shell.openExternal(this._gmailAuthUrl);
this.startPollingForResponse();
}, 600);
setTimeout(() => {
this.setState({showAlternative: true})
}, 1500);
}
componentWillUnmount() {
if (this._startTimer) { clearTimeout(this._startTimer); }
if (this._pollTimer) { clearTimeout(this._pollTimer); }
onSuccess(account) {
OnboardingActions.accountJSONReceived(account);
}
startPollingForResponse() {
let delay = 1000;
let onWindowFocused = null;
let poll = null;
onWindowFocused = () => {
delay = 1000;
if (this._pollTimer) {
clearTimeout(this._pollTimer);
this._pollTimer = setTimeout(poll, delay);
}
};
poll = () => {
pollForGmailAccount(this._sessionKey, (err, account) => {
clearTimeout(this._pollTimer);
if (account) {
ipcRenderer.removeListener('browser-window-focus', onWindowFocused);
OnboardingActions.accountJSONReceived(account);
} else {
delay = Math.min(delay * 1.2, 10000);
this._pollTimer = setTimeout(poll, delay);
}
});
}
ipcRenderer.on('browser-window-focus', onWindowFocused);
this._pollTimer = setTimeout(poll, 5000);
}
_renderAlternative() {
let classnames = "input hidden"
if (this.state.showAlternative) {
classnames += " fadein"
}
return (
<div className={classnames}>
<p> Page didn't open?</p>
<p>Paste into your browser:
<input type="url" className="url-copy-target" value={this._gmailAuthUrl} />
<div className="copy-to-clipboard" onClick={() => clipboard.writeText(this._gmailAuthUrl)} onMouseDown={() => this.setState({pressed: true})} onMouseUp={() => this.setState({pressed: false})}>
<RetinaImg
name="icon-copytoclipboard.png"
mode={RetinaImg.Mode.ContentIsMask}
/>
</div>
</p>
</div>
)
}
render() {
const {accountInfo} = this.props;
const iconName = AccountTypes.find(a => a.type === accountInfo.type).headerIcon;
return (
<div className="page account-setup gmail">
<div className="logo-container">
<RetinaImg
name={iconName}
mode={RetinaImg.Mode.ContentPreserve}
className="logo"
/>
</div>
<h2>
Sign in to Google in<br />your browser.
</h2>
<div className="alternative-auth">
{this._renderAlternative()}
</div>
</div>
<OAuthSignInPage
authUrl={this._gmailAuthUrl}
iconName={iconName}
makeRequest={makeGmailOAuthRequest}
onSuccess={this.onSuccess}
serviceName="Gmail"
sessionKey={this._sessionKey}
/>
);
}
}

View file

@ -0,0 +1,123 @@
import React from 'react';
import {ipcRenderer, shell} from 'electron';
import {RetinaImg} from 'nylas-component-kit';
const clipboard = require('electron').clipboard
export default class OAuthSignInPage extends React.Component {
static displayName = "OAuthSignInPage";
static propTypes = {
authUrl: React.PropTypes.string,
iconName: React.PropTypes.string,
makeRequest: React.PropTypes.func,
onSuccess: React.PropTypes.func,
serviceName: React.PropTypes.string,
sessionKey: React.PropTypes.string,
};
constructor() {
super()
this.state = {
showAlternative: false,
}
}
componentDidMount() {
// Show the "Sign in to ..." prompt for a moment before bouncing
// to URL. (400msec animation + 200msec to read)
this._pollTimer = null;
this._startTimer = setTimeout(() => {
shell.openExternal(this.props.authUrl);
this.startPollingForResponse();
}, 600);
setTimeout(() => {
this.setState({showAlternative: true})
}, 1500);
}
componentWillUnmount() {
if (this._startTimer) clearTimeout(this._startTimer);
if (this._pollTimer) clearTimeout(this._pollTimer);
}
startPollingForResponse() {
let delay = 1000;
let onWindowFocused = null;
let poll = null;
onWindowFocused = () => {
delay = 1000;
if (this._pollTimer) {
clearTimeout(this._pollTimer);
this._pollTimer = setTimeout(poll, delay);
}
};
poll = () => {
this.props.makeRequest(this.props.sessionKey, (err, json) => {
clearTimeout(this._pollTimer);
if (json) {
ipcRenderer.removeListener('browser-window-focus', onWindowFocused);
this.props.onSuccess(json);
} else {
delay = Math.min(delay * 1.2, 10000);
this._pollTimer = setTimeout(poll, delay);
}
});
}
ipcRenderer.on('browser-window-focus', onWindowFocused);
this._pollTimer = setTimeout(poll, 5000);
}
_renderAlternative() {
let classnames = "input hidden"
if (this.state.showAlternative) {
classnames += " fadein"
}
return (
<div className={classnames}>
<p>Page didn't open?</p>
<p>Paste into your browser:
<input type="url" className="url-copy-target" value={this.props.authUrl} />
<div
className="copy-to-clipboard"
onClick={() => clipboard.writeText(this.props.authUrl)}
onMouseDown={() => this.setState({pressed: true})}
onMouseUp={() => this.setState({pressed: false})}
>
<RetinaImg
name="icon-copytoclipboard.png"
mode={RetinaImg.Mode.ContentIsMask}
/>
</div>
</p>
</div>
)
}
render() {
return (
<div className={`page account-setup ${this.props.serviceName.toLowerCase()}`}>
<div className="logo-container">
<RetinaImg
name={this.props.iconName}
mode={RetinaImg.Mode.ContentPreserve}
className="logo"
/>
</div>
<h2>
Sign in to {this.props.serviceName} in<br />your browser.
</h2>
<div className="alternative-auth">
{this._renderAlternative()}
</div>
</div>
);
}
}

View file

@ -72,6 +72,7 @@ class NylasComponentKit
@load "LazyRenderedList", "lazy-rendered-list"
@load "OverlaidComponents", "overlaid-components/overlaid-components"
@load "OverlaidComposerExtension", "overlaid-components/overlaid-composer-extension"
@load "OAuthSignInPage", "oauth-signin-page"
@load "ScrollRegion", 'scroll-region'
@load "ResizableRegion", 'resizable-region'