mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-09 22:45:16 +08:00
During onboarding, test Gmail accounts for the All Mail folder
This commit is contained in:
parent
d6c336f070
commit
71f537ede0
6 changed files with 217 additions and 8 deletions
209
app/internal_packages/onboarding/lib/oauth-signin-page.jsx
Normal file
209
app/internal_packages/onboarding/lib/oauth-signin-page.jsx
Normal file
|
@ -0,0 +1,209 @@
|
|||
import { ipcRenderer, shell, clipboard } from 'electron';
|
||||
import { React, PropTypes, Actions } from 'mailspring-exports';
|
||||
import { RetinaImg } from 'mailspring-component-kit';
|
||||
import FormErrorMessage from './form-error-message';
|
||||
|
||||
export default class OAuthSignInPage extends React.Component {
|
||||
static displayName = 'OAuthSignInPage';
|
||||
|
||||
static propTypes = {
|
||||
/**
|
||||
* Step 1: Open a webpage in the user's browser letting them login on
|
||||
* the native provider's website. We pass along a key and a redirect
|
||||
* url to a Nylas-owned server
|
||||
*/
|
||||
providerAuthPageUrl: PropTypes.string,
|
||||
|
||||
/**
|
||||
* Step 2: Poll a Nylas server with this function looking for the key.
|
||||
* Once users complete the auth successfully, Nylas servers will get
|
||||
* the token and vend it back to us via this url. We need to poll
|
||||
* since we don't know how long it'll take users to log in on their
|
||||
* provider's website.
|
||||
*/
|
||||
tokenRequestPollFn: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Once we have the token, we can use that to retrieve the full
|
||||
* account credentials or establish a direct connection ourselves.
|
||||
* Some Nylas backends vend all account credentials along with the
|
||||
* token making this function unnecessary and a no-op. Mailspring
|
||||
* local sync needs to use the returned OAuth token to establish an
|
||||
* IMAP connection directly that may have its own set of failure
|
||||
* cases.
|
||||
*/
|
||||
accountFromTokenFn: PropTypes.func,
|
||||
|
||||
/**
|
||||
* Called once we have successfully received the account data from
|
||||
* `accountFromTokenFn`
|
||||
*/
|
||||
onSuccess: PropTypes.func,
|
||||
|
||||
onTryAgain: PropTypes.func,
|
||||
iconName: PropTypes.string,
|
||||
sessionKey: PropTypes.string,
|
||||
serviceName: PropTypes.string,
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
authStage: 'initial',
|
||||
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.providerAuthPageUrl);
|
||||
this.startPollingForResponse();
|
||||
}, 600);
|
||||
setTimeout(() => {
|
||||
this.setState({ showAlternative: true });
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this._startTimer) clearTimeout(this._startTimer);
|
||||
if (this._pollTimer) clearTimeout(this._pollTimer);
|
||||
}
|
||||
|
||||
_handleError(err) {
|
||||
this.setState({ authStage: 'error', errorMessage: err.message });
|
||||
AppEnv.reportError(err);
|
||||
Actions.recordUserEvent('Email Account Auth Failed', {
|
||||
errorMessage: err.message,
|
||||
provider: 'gmail',
|
||||
});
|
||||
}
|
||||
|
||||
startPollingForResponse() {
|
||||
let delay = 1000;
|
||||
let onWindowFocused = null;
|
||||
let poll = null;
|
||||
this.setState({ authStage: 'polling' });
|
||||
|
||||
onWindowFocused = () => {
|
||||
delay = 1000;
|
||||
if (this._pollTimer) {
|
||||
clearTimeout(this._pollTimer);
|
||||
this._pollTimer = setTimeout(poll, delay);
|
||||
}
|
||||
};
|
||||
|
||||
poll = async () => {
|
||||
clearTimeout(this._pollTimer);
|
||||
try {
|
||||
const tokenData = await this.props.tokenRequestPollFn(this.props.sessionKey);
|
||||
ipcRenderer.removeListener('browser-window-focus', onWindowFocused);
|
||||
this.fetchAccountDataWithToken(tokenData);
|
||||
} catch (err) {
|
||||
if (err.statusCode === 404) {
|
||||
delay = Math.min(delay * 1.1, 3000);
|
||||
this._pollTimer = setTimeout(poll, delay);
|
||||
} else {
|
||||
ipcRenderer.removeListener('browser-window-focus', onWindowFocused);
|
||||
this._handleError(err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ipcRenderer.on('browser-window-focus', onWindowFocused);
|
||||
this._pollTimer = setTimeout(poll, 3000);
|
||||
}
|
||||
|
||||
async fetchAccountDataWithToken(tokenData) {
|
||||
try {
|
||||
this.setState({ authStage: 'fetchingAccount' });
|
||||
const accountData = await this.props.accountFromTokenFn(tokenData);
|
||||
this.props.onSuccess(accountData);
|
||||
this.setState({ authStage: 'accountSuccess' });
|
||||
} catch (err) {
|
||||
this._handleError(err);
|
||||
}
|
||||
}
|
||||
|
||||
_renderHeader() {
|
||||
const authStage = this.state.authStage;
|
||||
if (authStage === 'initial' || authStage === 'polling') {
|
||||
return (
|
||||
<h2>
|
||||
Sign in with {this.props.serviceName} in<br />your browser.
|
||||
</h2>
|
||||
);
|
||||
} else if (authStage === 'fetchingAccount') {
|
||||
return <h2>Connecting to {this.props.serviceName}…</h2>;
|
||||
} else if (authStage === 'accountSuccess') {
|
||||
return (
|
||||
<div>
|
||||
<h2>Successfully connected to {this.props.serviceName}!</h2>
|
||||
<h3>Adding your account to Mailspring…</h3>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Error
|
||||
return (
|
||||
<div>
|
||||
<h2>Sorry, we had trouble logging you in</h2>
|
||||
<div className="error-region">
|
||||
<FormErrorMessage message={this.state.errorMessage} />
|
||||
<div className="btn" style={{ marginTop: 20 }} onClick={this.props.onTryAgain}>
|
||||
Try Again
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
_renderAlternative() {
|
||||
let classnames = 'input hidden';
|
||||
if (this.state.authStage === 'polling' && this.state.showAlternative) {
|
||||
classnames += ' fadein';
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="alternative-auth">
|
||||
<div className={classnames}>
|
||||
<div style={{ marginTop: 40 }}>
|
||||
Page didn't open? Paste this URL into your browser:
|
||||
</div>
|
||||
<input
|
||||
type="url"
|
||||
className="url-copy-target"
|
||||
value={this.props.providerAuthPageUrl}
|
||||
readOnly
|
||||
/>
|
||||
<div
|
||||
className="copy-to-clipboard"
|
||||
onClick={() => clipboard.writeText(this.props.providerAuthPageUrl)}
|
||||
onMouseDown={() => this.setState({ pressed: true })}
|
||||
onMouseUp={() => this.setState({ pressed: false })}
|
||||
>
|
||||
<RetinaImg name="icon-copytoclipboard.png" mode={RetinaImg.Mode.ContentIsMask} />
|
||||
</div>
|
||||
</div>
|
||||
</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>
|
||||
{this._renderHeader()}
|
||||
{this._renderAlternative()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -99,10 +99,6 @@ export function makeGmailOAuthRequest(sessionKey) {
|
|||
}
|
||||
|
||||
export async function buildGmailAccountFromToken(serverTokenResponse) {
|
||||
// At this point, the Mailspring server has retrieved the Gmail token,
|
||||
// created an account object in the database and tested it. All we
|
||||
// need to do is save it locally, since we're confident Gmail will be
|
||||
// accessible from the local sync worker.
|
||||
const { name, emailAddress, refreshToken } = serverTokenResponse;
|
||||
|
||||
const account = expandAccountWithCommonSettings(
|
||||
|
@ -118,6 +114,10 @@ export async function buildGmailAccountFromToken(serverTokenResponse) {
|
|||
|
||||
account.id = idForAccount(emailAddress, account.settings);
|
||||
|
||||
// test the account locally to ensure the All Mail folder is enabled
|
||||
// and the refresh token can be exchanged for an account token.
|
||||
await finalizeAndValidateAccount(account);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { OAuthSignInPage } from 'mailspring-component-kit';
|
||||
|
||||
import {
|
||||
makeGmailOAuthRequest,
|
||||
|
@ -9,6 +8,7 @@ import {
|
|||
buildGmailAuthURL,
|
||||
} from './onboarding-helpers';
|
||||
|
||||
import OAuthSignInPage from './oauth-signin-page';
|
||||
import OnboardingActions from './onboarding-actions';
|
||||
import AccountProviders from './account-providers';
|
||||
|
||||
|
|
|
@ -95,7 +95,6 @@ lazyLoad('UndoToast', 'undo-toast');
|
|||
lazyLoad('LazyRenderedList', 'lazy-rendered-list');
|
||||
lazyLoad('OverlaidComponents', 'overlaid-components/overlaid-components');
|
||||
lazyLoad('OverlaidComposerExtension', 'overlaid-components/overlaid-composer-extension');
|
||||
lazyLoad('OAuthSignInPage', 'oauth-signin-page');
|
||||
lazyLoadFrom('AttachmentItem', 'attachment-items');
|
||||
lazyLoadFrom('ImageAttachmentItem', 'attachment-items');
|
||||
lazyLoad('CodeSnippet', 'code-snippet');
|
||||
|
|
|
@ -13,7 +13,8 @@ let Utils = null;
|
|||
|
||||
export const LocalizedErrorStrings = {
|
||||
ErrorConnection: 'Connection Error - Unable to connect to the server / port you provided.',
|
||||
ErrorInvalidAccount: 'This account is invalid, or does not have an inbox or all folder.',
|
||||
ErrorInvalidAccount:
|
||||
'This account is invalid or Mailspring could not find the Inbox or All Mail folder. http://support.getmailspring.com/hc/en-us/articles/115001881912',
|
||||
ErrorTLSNotAvailable: 'TLS Not Available',
|
||||
ErrorParse: 'Parsing Error',
|
||||
ErrorCertificate: 'Certificate Error',
|
||||
|
|
2
mailsync
2
mailsync
|
@ -1 +1 @@
|
|||
Subproject commit 6024cf3744aca7448e623e1ec3f9820e1fe5a3d9
|
||||
Subproject commit bbf196e106cbf858015748b5b04b2ca537253d5f
|
Loading…
Reference in a new issue