mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-16 02:03:42 +08:00
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:
parent
7406c1a94f
commit
1f15026a5f
4 changed files with 137 additions and 93 deletions
|
@ -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",
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
123
src/components/oauth-signin-page.jsx
Normal file
123
src/components/oauth-signin-page.jsx
Normal 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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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'
|
||||
|
|
Loading…
Reference in a new issue