Mailspring/app/internal_packages/onboarding/lib/oauth-signin-page.tsx
Ben Gotow 149b389508
Replace Babel with TypeScript compiler, switch entire app to TypeScript 🎉 (#1404)
* Switch to using Typescript instead of Babel

* Switch all es6 / jsx file extensions to ts / tsx

* Convert Utils to a TS module from module.exports style module

* Move everything from module.exports to typescript exports

* Define .d.ts files for mailspring-exports and component kit… Yes it seems this is the best option :(

* Load up on those @types

* Synthesize TS types from PropTypes for standard components

* Add types to Model classes and move constructor constants to instance vars

* 9800 => 7700 TS errors

* 7700 => 5600 TS errors

* 5600 => 5330 TS errors

* 5330 => 4866 TS errors

* 4866 => 4426 TS errors

* 4426 => 2411 TS errors

* 2411 > 1598 TS errors

* 1598 > 769 TS errors

* 769 > 129 TS errors

* 129 > 22 TS errors

* Fix runtime errors

* More runtime error fixes

* Remove support for custom .es6 file extension

* Remove a few odd remaining references to Nylas

* Don’t ship Typescript support in the compiled app for now

* Fix issues in compiled app - module resolution in TS is case sensitive?

* README updates

* Fix a few more TS errors

* Make “No Signature” option clickable + selectable

* Remove flicker when saving file and reloading keymaps

* Fix mail rule item height in preferences

* Fix missing spacing in thread sharing popover

* Fix scrollbar ticks being nested incorrectly

* Add Japanese as a manually reviewed language

* Prevent the thread list from “sticking”

* Re-use Sheet when switching root tabs, prevent sidebar from resetting

* Ensure specs run

* Update package configuration to avoid shpping types

* Turn eslint back on - we will opt-in to the TS rules one by one
2019-03-04 11:03:12 -08:00

213 lines
6.2 KiB
TypeScript

import { shell, clipboard } from 'electron';
import React from 'react';
import { localized, localizedReactFragment, PropTypes, Account } from 'mailspring-exports';
import { RetinaImg } from 'mailspring-component-kit';
import http from 'http';
import url from 'url';
import FormErrorMessage from './form-error-message';
import { LOCAL_SERVER_PORT } from './onboarding-helpers';
interface OAuthSignInPageProps {
providerAuthPageUrl: string;
buildAccountFromAuthResponse: (rep: any) => Account | Promise<Account>;
onSuccess: (account: Account) => void;
onTryAgain: () => void;
iconName: string;
serviceName: string;
}
interface OAuthSignInPageState {
authStage: string;
showAlternative: boolean;
errorMessage?: string;
pressed?: boolean;
}
export default class OAuthSignInPage extends React.Component<
OAuthSignInPageProps,
OAuthSignInPageState
> {
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 Mailspring-owned server
*/
providerAuthPageUrl: PropTypes.string,
buildAccountFromAuthResponse: PropTypes.func,
onSuccess: PropTypes.func,
onTryAgain: PropTypes.func,
iconName: PropTypes.string,
serviceName: PropTypes.string,
};
_server?: http.Server;
_startTimer: NodeJS.Timeout;
_warnTimer: NodeJS.Timeout;
_mounted: boolean = false;
state: OAuthSignInPageState = {
authStage: 'initial',
showAlternative: false,
};
componentDidMount() {
// Show the "Sign in to ..." prompt for a moment before bouncing
// to URL. (400msec animation + 200msec to read)
this._mounted = true;
this._startTimer = setTimeout(() => {
if (!this._mounted) return;
shell.openExternal(this.props.providerAuthPageUrl);
}, 600);
this._warnTimer = setTimeout(() => {
if (!this._mounted) return;
this.setState({ showAlternative: true });
}, 1500);
// launch a web server
this._server = http.createServer((request, response) => {
if (!this._mounted) return;
const { query } = url.parse(request.url, true);
if (query.code) {
this._onReceivedCode(query.code);
response.writeHead(302, { Location: 'https://id.getmailspring.com/oauth/finished' });
response.end();
} else {
response.end('Unknown Request');
}
});
this._server.listen(LOCAL_SERVER_PORT, err => {
if (err) {
AppEnv.showErrorDialog({
title: localized('Unable to Start Local Server'),
message: localized(
`To listen for the Gmail Oauth response, Mailspring needs to start a webserver on port ${LOCAL_SERVER_PORT}. Please go back and try linking your account again. If this error persists, use the IMAP/SMTP option with a Gmail App Password.\n\n%@`,
err
),
});
return;
}
});
}
componentWillUnmount() {
this._mounted = false;
if (this._startTimer) clearTimeout(this._startTimer);
if (this._warnTimer) clearTimeout(this._warnTimer);
if (this._server) this._server.close();
}
_onError(err) {
this.setState({ authStage: 'error', errorMessage: err.message });
AppEnv.reportError(err);
}
async _onReceivedCode(code) {
if (!this._mounted) return;
AppEnv.focus();
this.setState({ authStage: 'buildingAccount' });
let account = null;
try {
account = await this.props.buildAccountFromAuthResponse(code);
} catch (err) {
if (!this._mounted) return;
this._onError(err);
return;
}
if (!this._mounted) return;
this.setState({ authStage: 'accountSuccess' });
setTimeout(() => {
if (!this._mounted) return;
this.props.onSuccess(account);
}, 400);
}
_renderHeader() {
const authStage = this.state.authStage;
if (authStage === 'initial') {
return (
<h2>
{localizedReactFragment(
'Sign in with %@ in %@ your browser.',
this.props.serviceName,
<br />
)}
</h2>
);
}
if (authStage === 'buildingAccount') {
return <h2>{localized('Connecting to %@…', this.props.serviceName)}</h2>;
}
if (authStage === 'accountSuccess') {
return (
<div>
<h2>{localized('Successfully connected to %@!', this.props.serviceName)}</h2>
<h3>{localized('Adding your account to Mailspring…')}</h3>
</div>
);
}
// Error
return (
<div>
<h2>{localized('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}>
{localized('Try Again')}
</div>
</div>
</div>
);
}
_renderAlternative() {
let classnames = 'input hidden';
if (this.state.showAlternative) {
classnames += ' fadein';
}
return (
<div className="alternative-auth">
<div className={classnames}>
<div style={{ marginTop: 40 }}>
{localized(`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>
);
}
}