diff --git a/CONFIGURATION.md b/CONFIGURATION.md index 62d173996..4d7e99009 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -1,103 +1,11 @@ # Configuration This document outlines configuration options which aren't exposed via N1's -preferences interface, but may be useful. +preferences interface but may be useful. ## Running Against Open Source Sync Engine -N1 needs to fetch mail from a running instance of the [Nylas Sync -Engine](https://github.com/nylas/sync-engine). The Sync Engine is what -abstracts away IMAP, POP, and SMTP to serve your email on any provider -through a modern, RESTful API. - -By default the N1 source points to our hosted version of the sync-engine; -however, the Sync Engine is open source and you can run it yourself. - -1. Install the Nylas Sync Engine in a Vagrant virtual machine by following the - [installation and setup](https://github.com/nylas/sync-engine#installation-and-setup) - instructions. - -2. Once you've installed the sync engine, add accounts by running the inbox-auth - script. For Gmail accounts, the syntax is simple: `bin/inbox-auth you@gmail.com` - -3. Start the sync engine by running `bin/inbox-start` and the API via `bin/inbox-api`. - -4. After you've linked accounts to the Sync Engine, open or create a file at - `~/.nylas/config.json`. This is the config file that N1 reads at launch. - - Replace `env: "production"` with `env: "local"` at the top level of the config. - This tells N1 to look at `localhost:5555` for the sync engine. If you've deployed - the sync engine elsewhere, add the following block beneath `env: "local"`: - - ```javascript - "syncEngine": { - "APIRoot": "http://mysite.com:5555" - }, - ``` - - NOTE: If you are using a custom network layout and your sync engine is not on - `localhost:5555`, use `env: custom` instead along with your alternate IP for the - API Root, for example `192.168.1.00:5555` - - ```javascript - { - "env": "custom", - "syncEngine": { - "APIRoot": "http://192.168.1.100:5555" - }, - ``` - - Copy the JSON array of accounts returned from the Sync Engine's `/accounts` - endpoint (ex. `http://localhost:5555/accounts`) into the config file at the - path `*.nylas.accounts`. - - N1 will look for access tokens for these accounts under `*.nylas.accountTokens`, - but the open source version of the sync engine does not provide access tokens. - When you make requests to the open source API, you provide an account - ID in the HTTP Basic Auth username field instead of an account token. - - For each account you've created, add an entry to `*.nylas.accountTokens` - with the account ID as both the key and value. - - The final `config.json` file should look something like this: - ```javascript - { - "*": { - "env": "local", - "nylas": { - "accounts": [ - { - "server_id": "{ACCOUNT_ID_1}", - "object": "account", - "account_id": "{ACCOUNT_ID_1}", - "name": "{YOUR NAME}", - "provider": "{PROVIDER_NAME}", - "email_address": "{YOUR_EMAIL_ADDRESS}", - "organization_unit": "{folder or label}", - "id": "{ACCOUNT_ID_1}" - }, - { - "server_id": "{ACCOUNT_ID_2}", - "object": "account", - "account_id": "{ACCOUNT_ID_2}", - "name": "{YOUR_NAME}", - "provider": "{PROVIDER_NAME}", - "email_address": "{YOUR_EMAIL_ADDRESS}", - "organization_unit": "{folder or label}", - "id": "{ACCOUNT_ID_2}" - } - ], - "accountTokens": { - "{ACCOUNT_ID_1}": "{ACCOUNT_ID_1}", - "{ACCOUNT_ID_2}": "{ACCOUNT_ID_2}" - } - } - } - } - ``` -Note: `{ACCOUNT_ID_1}` refers to the database ID of the `Account` object -you create when setting up the Sync Engine. The JSON above should match -fairly closely with the Sync Engine `Account` object. +If you want to point N1 to your self-hosted sync engine, select "Hosting your own sync engine?" under the "Get Started" button on the welcome screen. There, follow the instructions for creating your own instance of the sync engine and enter the URL and port number where you have it running. ## Other Config Options diff --git a/README.md b/README.md index eec0e3f9d..72b38a055 100644 --- a/README.md +++ b/README.md @@ -68,8 +68,8 @@ Great starting points for creating your own plugins! - [Website Launcher](https://github.com/adriangrantdotorg/nylas-n1-background-webpage)—Opens a URL in separate window - In Development: [Cypher](https://github.com/mbilker/cypher) (PGP Encryption) -# Running Locally -By default, the N1 source points to our hosted version of the Nylas Sync Engine—however, the Sync Engine is open source, and you can [run it yourself](https://github.com/nylas/N1/blob/master/CONFIGURATION.md). +# Configuration +You can configure N1 in a few ways—for instance, pointing it to your self-hosted instance of the sync engine or changing the interface zoom level. [Learn more about how.](https://github.com/nylas/N1/blob/master/CONFIGURATION.md). # Feature Requests / Plugin Ideas diff --git a/internal_packages/onboarding/lib/onboarding-actions.es6 b/internal_packages/onboarding/lib/onboarding-actions.es6 index c2602e76f..f47195cb5 100644 --- a/internal_packages/onboarding/lib/onboarding-actions.es6 +++ b/internal_packages/onboarding/lib/onboarding-actions.es6 @@ -7,6 +7,7 @@ const OnboardingActions = Reflux.createActions([ "moveToPage", "authenticationJSONReceived", "accountJSONReceived", + "accountsAddedLocally", ]); for (const key of Object.keys(OnboardingActions)) { diff --git a/internal_packages/onboarding/lib/onboarding-root.jsx b/internal_packages/onboarding/lib/onboarding-root.jsx index d964a46b5..9a786b984 100644 --- a/internal_packages/onboarding/lib/onboarding-root.jsx +++ b/internal_packages/onboarding/lib/onboarding-root.jsx @@ -5,6 +5,8 @@ import PageTopBar from './page-top-bar'; import WelcomePage from './page-welcome'; import TutorialPage from './page-tutorial'; +import SelfHostingSetupPage from './page-self-hosting-setup'; +import SelfHostingConfigPage from './page-self-hosting-config'; import AuthenticatePage from './page-authenticate'; import AccountChoosePage from './page-account-choose'; import AccountSettingsPage from './page-account-settings'; @@ -16,6 +18,8 @@ import InitialPreferencesPage from './page-initial-preferences'; const PageComponents = { "welcome": WelcomePage, "tutorial": TutorialPage, + "self-hosting-setup": SelfHostingSetupPage, + "self-hosting-config": SelfHostingConfigPage, "authenticate": AuthenticatePage, "account-choose": AccountChoosePage, "account-settings": AccountSettingsPage, diff --git a/internal_packages/onboarding/lib/onboarding-store.es6 b/internal_packages/onboarding/lib/onboarding-store.es6 index ed6dc40a3..e3b5698eb 100644 --- a/internal_packages/onboarding/lib/onboarding-store.es6 +++ b/internal_packages/onboarding/lib/onboarding-store.es6 @@ -24,6 +24,7 @@ class OnboardingStore extends NylasStore { this.listenTo(OnboardingActions.moveToPreviousPage, this._onMoveToPreviousPage) this.listenTo(OnboardingActions.moveToPage, this._onMoveToPage) this.listenTo(OnboardingActions.accountJSONReceived, this._onAccountJSONReceived) + this.listenTo(OnboardingActions.accountsAddedLocally, this._onAccountsAddedLocally) this.listenTo(OnboardingActions.authenticationJSONReceived, this._onAuthenticationJSONReceived) this.listenTo(OnboardingActions.setAccountInfo, this._onSetAccountInfo); this.listenTo(OnboardingActions.setAccountType, this._onSetAccountType); @@ -177,6 +178,29 @@ class OnboardingStore extends NylasStore { } } + _onAccountsAddedLocally = (accounts) => { + try { + const isFirstAccount = AccountStore.accounts().length === 0 + + for (const account of accounts) { + account.auth_token = account.id + AccountStore.addAccountFromJSON(account) + } + + ipcRenderer.send('new-account-added') + NylasEnv.displayWindow() + + if (isFirstAccount) { + this._onMoveToPage('initial-preferences') + } else { + this._onOnboardingComplete(); + } + } catch (e) { + NylasEnv.reportError(e) + NylasEnv.showErrorDialog("Unable to Connect Accounts", "Sorry, something went wrong on your instance of the sync engine. Please try again.") + } + } + page() { return this._pageStack[this._pageStack.length - 1]; } diff --git a/internal_packages/onboarding/lib/page-account-choose.jsx b/internal_packages/onboarding/lib/page-account-choose.jsx index 6c607e60a..21e033e22 100644 --- a/internal_packages/onboarding/lib/page-account-choose.jsx +++ b/internal_packages/onboarding/lib/page-account-choose.jsx @@ -2,6 +2,7 @@ import React from 'react'; import {RetinaImg} from 'nylas-component-kit'; import OnboardingActions from './onboarding-actions'; import AccountTypes from './account-types'; +import SelfHostingConfigPage from './page-self-hosting-config' export default class AccountChoosePage extends React.Component { static displayName = "AccountChoosePage"; @@ -30,6 +31,11 @@ export default class AccountChoosePage extends React.Component { } render() { + if (NylasEnv.config.get('env', 'custom') || + NylasEnv.config.get('env', 'local')) { + return () + } + return (

diff --git a/internal_packages/onboarding/lib/page-self-hosting-config.jsx b/internal_packages/onboarding/lib/page-self-hosting-config.jsx new file mode 100644 index 000000000..21886e05b --- /dev/null +++ b/internal_packages/onboarding/lib/page-self-hosting-config.jsx @@ -0,0 +1,159 @@ +import React from 'react' +import {Actions} from 'nylas-exports' +import {Flexbox} from 'nylas-component-kit' +import OnboardingActions from './onboarding-actions' + + +class SelfHostingConfigPage extends React.Component { + static displayName = 'SelfHostingConfigPage' + + static propTypes = { + addAccount: React.PropTypes.bool, + } + + constructor(props) { + super(props) + this.state = { + url: "", + port: "", + error: null, + } + } + + _onChangeUrl = (event) => { + this.setState({ + url: event.target.value, + }) + } + + _onChangePort = (event) => { + this.setState({ + port: event.target.value, + }) + } + + _addAccountJSON = () => { + // Connect to local sync engine's /accounts endpoint and add accounts to N1 + const xmlHttp = new XMLHttpRequest() + xmlHttp.onreadystatechange = () => { + if (xmlHttp.readyState === 4 && xmlHttp.status === 200) { + const accounts = JSON.parse(xmlHttp.responseText) + if (accounts.length === 0) { + this.setState({error: "There are no accounts added to this instance of the sync engine. Make sure you've authed an account."}) + } + OnboardingActions.accountsAddedLocally(accounts) + } + } + xmlHttp.onerror = () => { + this.setState({error: `The request to ${NylasEnv.config.get('syncEngine.APIRoot')}/accounts failed.`}) + } + xmlHttp.open("GET", `${NylasEnv.config.get('syncEngine.APIRoot')}/accounts`) + xmlHttp.send(null) + } + + _onSubmit = () => { + if (this.state.url.length === 0 || this.state.port.length === 0) { + this.setState({error: "Please include both a URL and port number."}) + return + } + NylasEnv.config.set('env', 'custom') + NylasEnv.config.set('syncEngine.APIRoot', `http://${this.state.url}:${this.state.port}`) + Actions.setNylasIdentity({ + token: "SELFHOSTEDSYNCENGINE", + identity: { + firstname: "", + lastname: "", + valid_until: null, + free_until: Number.INT_MAX, + email: "", + id: 1, + seen_welcome_page: true, + }, + }) + this._addAccountJSON() + } + + _onKeyDown = (event) => { + if (['Enter', 'Return'].includes(event.key)) { + this._onSubmit(); + } + } + + _renderInitalConfig() { + return ( +
+

Configure your self-hosted sync engine

+
+ Once you have created your instance of the sync engine, connect it to N1. +
+
+ ) + } + + _renderAdditionalConfig() { + return ( +
+

Connect more email accounts

+
+ To add new accounts, use the instructions for the Sync Engine. For example:
+ bin/inbox-auth you@gmail.com +
+
+ ) + } + + _renderErrorMessage() { + return ( +
+ {this.state.error} +
+ ) + } + + render() { + return ( +
+ {!this.props.addAccount ? this._renderInitalConfig() : this._renderAdditionalConfig()} + {this.state.error ? this._renderErrorMessage() : null} +
+ +
+

{`http://`}

+
+
+ + +
+
+

{`:`}

+
+
+ + +
+
+
+ +
+ ) + } +} + +export default SelfHostingConfigPage diff --git a/internal_packages/onboarding/lib/page-self-hosting-setup.jsx b/internal_packages/onboarding/lib/page-self-hosting-setup.jsx new file mode 100644 index 000000000..2d1659573 --- /dev/null +++ b/internal_packages/onboarding/lib/page-self-hosting-setup.jsx @@ -0,0 +1,42 @@ +import React from 'react' +import OnboardingActions from './onboarding-actions' + + +class SelfHostingSetupPage extends React.Component { + static displayName = 'SelfHostingSetupPage' + + _onContinue = () => { + OnboardingActions.moveToPage("self-hosting-config"); + } + + render() { + return ( +
+

Create your sync engine instance

+
+
+ N1 needs to fetch mail from a running instance of the Nylas Sync Engine. By default, N1 points to our hosted version, but the code is open source so that you can run your own instance. Note that Exchange accounts are not supported and some plugins that rely on our back-end (snoozing, open/link tracking, etc.) will not work. +
+
+ 1. Install the Nylas Sync Engine in a Vagrant virtual machine by following the installation and setup instructions. +
+
+ 2. Add accounts by running the inbox-auth script. For example: bin/inbox-auth you@gmail.com. +
+
+ 3. Start the sync engine by running bin/inbox-start and the API via bin/inbox-api. +
+
+ +
+ ) + } +} + +export default SelfHostingSetupPage diff --git a/internal_packages/onboarding/lib/page-welcome.jsx b/internal_packages/onboarding/lib/page-welcome.jsx index 0e24b0ef3..e83ffe0e9 100644 --- a/internal_packages/onboarding/lib/page-welcome.jsx +++ b/internal_packages/onboarding/lib/page-welcome.jsx @@ -19,6 +19,10 @@ export default class WelcomePage extends React.Component { OnboardingActions.moveToPage("tutorial"); } + _onSelfHosting = () => { + OnboardingActions.moveToPage("self-hosting-setup") + } + _renderContent(isFirstAccount) { if (isFirstAccount) { return ( @@ -32,15 +36,14 @@ export default class WelcomePage extends React.Component { return (

Welcome back!

-

This month we're launching Nylas Pro. As an existing user, you'll receive a coupon for your first year free. Create a Nylas ID to continue using N1, and look out for a coupon email!

+

Since you've been gone, we've launched Nylas Pro, which now requires a paid subscription. Create a Nylas ID to start your trial and continue using N1!

) } render() { - const isFirstAccount = (AccountStore.accounts().length === 0); - + const isFirstAccount = (AccountStore.accounts().length === 0) return (
@@ -48,6 +51,7 @@ export default class WelcomePage extends React.Component {
+
Hosting your own sync engine?
); diff --git a/internal_packages/onboarding/stylesheets/onboarding.less b/internal_packages/onboarding/stylesheets/onboarding.less index 13059a561..88657c577 100644 --- a/internal_packages/onboarding/stylesheets/onboarding.less +++ b/internal_packages/onboarding/stylesheets/onboarding.less @@ -503,6 +503,13 @@ left: 0; pointer-events: none; } + + .btn-self-hosting { + cursor: pointer; + font-weight: 300; + margin-bottom: 20px; + color: white; + } } .page.welcome.is-first-account-false { @@ -536,6 +543,52 @@ } } +.page.self-hosting { + text-align: center; + cursor: default; + + h2 { + margin-top: 70px; + } + + input { + display: inline-block; + width: 100%; + padding: 7px; + margin-bottom: 10px; + background: #FFF; + color: #333; + text-align: left; + border: 1px solid #AAA; + + &:focus { + border: 1px solid @accent-primary; + } + + &.error { + border: 1px solid #A33; + } + } + + .message { + margin-top: 20px; + } + + .self-hosting-container { + width: 400px; + display: block; + margin: 40px auto; + + .section { + margin: 20px 0; + } + + .api-root { + margin: 20px 5px 0 5px; + } + } +} + body.platform-win32 { .page-frame { .alpha-fade-enter { diff --git a/src/browser/application.es6 b/src/browser/application.es6 index 608e75933..4067ab5e4 100644 --- a/src/browser/application.es6 +++ b/src/browser/application.es6 @@ -193,11 +193,8 @@ export default class Application extends EventEmitter { const accounts = this.config.get('nylas.accounts'); const hasAccount = accounts && accounts.length > 0; const hasN1ID = this.config.get('nylas.identity.id'); - const env = this.config.get('env'); - const isLocalOrCustom = (env === 'local' || env === 'custom'); - - if (isLocalOrCustom || (hasAccount && hasN1ID)) { + if (hasAccount && hasN1ID) { this.windowManager.ensureWindow(WindowManager.MAIN_WINDOW); this.windowManager.ensureWindow(WindowManager.WORK_WINDOW); } else { diff --git a/src/flux/nylas-api.coffee b/src/flux/nylas-api.coffee index 7b198f986..66f42f409 100644 --- a/src/flux/nylas-api.coffee +++ b/src/flux/nylas-api.coffee @@ -85,12 +85,10 @@ class NylasAPI @AppID = 'c5dis00do2vki9ib6hngrjs18' @APIRoot = 'https://api-staging-experimental.nylas.com' @pluginsSupported = true - else if env in ['local'] - @AppID = NylasEnv.config.get('syncEngine.AppID') or 'n/a' - @APIRoot = 'http://localhost:5555' - else if env in ['custom'] + else if env in ['custom', 'local'] @AppID = NylasEnv.config.get('syncEngine.AppID') or 'n/a' @APIRoot = NylasEnv.config.get('syncEngine.APIRoot') or 'http://localhost:5555' + @pluginsSupported = false current = {@AppID, @APIRoot, @APITokens}