Skip to content

Commit

Permalink
Merge release 1.10 into master (#1688)
Browse files Browse the repository at this point in the history
* Update dependency concurrently to v5 (#1631)

* Update dependency electron-rebuild to v1.8.6 (#1533)

* Update dependency highlight.js to v9.15.10 (#1545)

* Update dependency autoprefixer to v9.6.4 (#1628)

* Update dependency react-overlays to v2 (#1620)

* Update dependency react-dropzone to v10.1.10 (#1630)

* Update dependency eslint-config-prettier to v6.4.0 (#1629)

* Update dependency eslint-plugin-react to v7.16.0 (#1623)

* Update react monorepo to v16.10.2 (#1621)

* Update dependency electron to v4.2.11 (#1483)

* Refactor tag operations to stop directly mutating tag objects (#1638)

See #1614

As part of a broader effort to resolve data-flow issues in the app this PR is a
first step in removing direct mutation where transactional atomic updates
should be occurring.

It's not clear if the existing code is the source of existing defects in the software
and this is part of why the code is problematic; we have created inherent
concurrency flaws that open up extremely-difficult-to-reproduce bugs.

Resolving this may or may not resolve any existing bugs but it will definitely
help guard us from introducing new ones.

---

Previously we have been directly mutating note and tag objects when
editing those tags. This mutation can lead to concurrency defects which
expose themselves as inconsistent UI state. This breaks our Redux model
which assumes that all UI updates happen atomically.

In this patch we're building new note objects and tag objects when we
make these updates in order to maintain our consistency.

---

There should be no significant visual or behavioral changes with this PR. We
are changing code related to removing tags, renaming tags, and
reordering tags.

In testing verify that with separate sessions the updates appear as expected.
Add, reorder, and remove tags to make sure the changes synchronize.

* Refactor updating note content to stop directly mutating note object (#1634)

See #1614

As part of a broader effort to resolve data-flow issues in the app this PR is a
first step in removing direct mutation where transactional atomic updates
should be occurring.

It's not clear if the existing code is the source of existing defects in the software
and this is part of why the code is problematic; we have created inherent
concurrency flaws that open up extremely-difficult-to-reproduce bugs.

Resolving this may or may not resolve any existing bugs but it will definitely
help guard us from introducing new ones.

---

Previously we have been directly mutating the note object when updating
its content. This may have been an attempt to work around confusing
data-flow issues that thankfully don't exist anymore. We have also been
performing inline checks to make sure that we update the editor's
contents if we receive these updates.

This mutation can lead to concurrency defects which expose themselves as
inconsistent UI state. This breaks our Redux model which assumes that
all UI updates happen atomically.

In this patch we're building a new note object when we update a note
in order to maintain our consistency. In light of #1598 we're also
removing some work-around code that attempted to force consistency when
it didn't exist; that consistency now exists since we're tracking the
underlying Simperium data closely now vs. storing it in separate
places.

When updating checklist items we're forcing a sync so that those changes
will propagate immediately. We don't have a need to debounce those
clicks.

* Refactor note tag operation to stop directly mutating note object (#1639)

See #1614

As part of a broader effort to resolve data-flow issues in the app this PR is a
first step in removing direct mutation where transactional atomic updates
should be occurring.

It's not clear if the existing code is the source of existing defects in the software
and this is part of why the code is problematic; we have created inherent
concurrency flaws that open up extremely-difficult-to-reproduce bugs.

Resolving this may or may not resolve any existing bugs but it will definitely
help guard us from introducing new ones.

---

Previously we have been directly mutating note objects when editing
their tags. This mutation can lead to concurrency defects which expose
themselves as inconsistent UI state. This breaks our Redux model which
assumes that all UI updates happen atomically.

In this patch we're building new note objects when we make these updates
in order to maintain our consistency.

---

There should be no significant visual or behavioral changes with this PR. We
are changing code related to removing tags, renaming tags, and
reordering tags.

In testing verify that with separate sessions the updates appear as expected.
Add, reorder, and remove tags to make sure the changes synchronize.

* Fix: Broken oAuth flow (#1627)

We have been experiencing problems when trying to login with the
WordPress.com signin. Something appears to have changed in Electron such
that the older versions of the app still work but newer versions are
failing.

In this patch we're rewriting the authentication flow to simplify it and
prepare ourselves for better handling of the failure cases.

In production we are seeing strange behaviors on failure and some on
success: unending re-requests to `simplenote://auth` which trigger full
CPU load; and no response after authentication.

After this patch we should be able to wrangle in errors and add a
timeout to better communicate when things are failing.

Additionally, the unending loop should be closed due to a replacement of
the old network intercept code with a single simplified model.

We have also been sharing sessions between the main window and the auth
window and also sharing sessions between teach time the auth window
appears.

This leads to leaked cookies and can result in confusing flows, largely
because of the shared cookies.

In this patch we're creating a new `Session` for the auth window every
time we open it. By not including `persist:` in the "partition" name
we're making sure it only exists in memory. By introducing randomness
into its name we're making sure we don't share the same session from
one auth attempt to the next. By freeing the window after close we're
making sure we don't leak memory.

Previously we were able to open the auth window after closing it and
instead of logging in again it would open to the "Accept/Deny" view.
After this change it requires logging in on every attempt. This will
likely be more frustrating but much safer than the previous behavior.

* Make system setting the default theme preference (#1581)

* Make system setting the default theme preference

* Update Release Notes

* Make system the first item in the list

* Update system theme logic

* Add option to hide menu bar (#1215)

Closes #293
Based on #1216

This adds an option to auto-hide the menu bar on Windows/Linux Electron. The option will be in Settings ▸ Display.

screen shot 2019-02-21 at 23 26 17

We have to be careful not to get the user stuck in a situation where they can't bring back the menu bar, so we'll show the Settings button and footer links in the Navigation Bar when auto-hide is enabled.

* Support the unicode bullet as a list item bullet in unordered lists (#1551)

Support the unicode bullet character • as a list item bullet because lists copied from HTML or word documents may contain this character.

* Add release note adding bullet char to lists (#1646)

* Note list: Distinguish loading from empty states (#1650)

Alternative idea to #1649

There are a few times when we boot the app and our list of notes is
empty because we haven't received udpates from the server yet. This
happens on intial app boot, immediately after authorizing, and when
we lose our local copy of the notes from `IndexedDB`.

During these times we're showing that there are no notes in the account
which is misleading. In this patch we're starting with a `null` value
for the notes so that we can distinguish between "there are no notes
in the account" and "we haven yet to determine which notes are in the
account."

In comparison to #1649 we're using a tri-state value for `notes` instead
of introducing an additional flag which must be kept in sync with
`notes`.

* Deps: Update simperium to fix infinite duplication bug

Update `simperium` library to incorporate changes that fixed a defect
where we were holding open old WebSocket connections after signing-out
and signing-in. This defect produced an infinite duplication of changes.

* Update the link to the Release Notes in the updater config (#1675)

Update the link to the Release Notes in the updater config

In (#1582) CHANGELOG.md was renamed to RELEASE-NOTES.txt. This caused the link to the release notes to break.

* In Dev mode, open CHrome Deve Tools in detached mode (#1660)

* Update version in package.json for 1.10.0 release

Added the beta version for 1.10.0-beta1 release

* Fix/only run notes loaded when notes loaded (#1680)

* Only run notesLoaded when notes are indeed loaded

After querying the noteBucket we run notes loaded with an empty notes array. This causes havoc because we rely on notes being null until notes are loaded. This commit adds a check to ensure there is at least one note before running notesLoaded

* Add release notes

* Update RELEASE-NOTES.txt

* Update signing certificate (#1682)

Props to @loremattei for creating these changes

* Bump version to v1.10.0-beta2 (#1684)

* Add version for release 1.10.0
  • Loading branch information
loremattei authored and belcherj committed Nov 4, 2019
1 parent d5dbaeb commit b82a9ea
Show file tree
Hide file tree
Showing 25 changed files with 513 additions and 435 deletions.
17 changes: 16 additions & 1 deletion RELEASE-NOTES.txt
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
# Changelog

## [v1.10.0]

### Enhancements

- Add ability to select system as a theme option and make it the default
- Added support for the unicode bullet • in list items
- Display a notice that notes are loading when notes are loading
- In dev mode open Chrome Dev Tools in a separate window

### Fixes

- Rework WordPress.com signin to prevent infinite looping and login failures [#1627](https://github.com/Automattic/simplenote-electron/pull/1627)
- Update link to release-notes in updater config: CHANGELOG -> RELEASE_NOTES
- Stop showing that there are no notes when initially loading notes from the server. [#1680](https://github.com/Automattic/simplenote-electron/pull/1680)

## [v1.9.1]

### Fixes

- Prevent ulimited duplication of changes after signing out and signing in [#1666](https://github.com/Automattic/simplenote-electron/pull/1666)
- Prevent ulimited duplication of changes after signing out and signing in [#1664](https://github.com/Automattic/simplenote-electron/pull/1664)

## [v1.9.0]

Expand Down
1 change: 0 additions & 1 deletion after_sign_hook.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ module.exports = async function(params) {
if (process.platform !== 'darwin') {
return;
}
console.log('afterSign hook triggered', params);

if (!process.env.CIRCLE_TAG || process.env.CIRCLE_TAG.length === 0) {
console.log('Not on a tag. Skipping notarization');
Expand Down
7 changes: 6 additions & 1 deletion desktop/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ module.exports = function main() {
}

if (isDev || process.argv.includes('--devtools')) {
mainWindow.openDevTools();
mainWindow.openDevTools({ mode: 'detach' });
}

// Configure and set the application menu
Expand Down Expand Up @@ -109,6 +109,11 @@ module.exports = function main() {
});
});

ipcMain.on('setAutoHideMenuBar', function(event, autoHideMenuBar) {
mainWindow.setAutoHideMenuBar(autoHideMenuBar || false);
mainWindow.setMenuBarVisibility(!autoHideMenuBar);
});

mainWindowState.manage(mainWindow);

mainWindow.webContents.on('new-window', function(event, linkUrl) {
Expand Down
2 changes: 1 addition & 1 deletion desktop/config-updater.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"updater": {
"downloadUrl": "https://github.com/Automattic/simplenote-electron/releases/latest",
"changelogUrl": "https://github.com/Automattic/simplenote-electron/blob/master/CHANGELOG.md",
"changelogUrl": "https://github.com/Automattic/simplenote-electron/blob/master/RELEASE-NOTES.txt",
"apiUrl": "https://api.github.com/repos/automattic/simplenote-electron/releases/latest",
"delay": 2000,
"interval": 600000
Expand Down
51 changes: 42 additions & 9 deletions lib/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function mapDispatchToProps(dispatch, { noteBucket }) {
'setNoteDisplay',
'setMarkdown',
'setAccountName',
'toggleAutoHideMenuBar',
'toggleFocusMode',
'toggleSpellCheck',
]),
Expand Down Expand Up @@ -145,6 +146,7 @@ export const App = connect(

componentDidMount() {
ipc.on('appCommand', this.onAppCommand);
ipc.send('setAutoHideMenuBar', this.props.settings.autoHideMenuBar);
ipc.send('settingsUpdate', this.props.settings);

this.props.noteBucket
Expand Down Expand Up @@ -309,8 +311,20 @@ export const App = connect(
preferencesBucket: this.props.preferencesBucket,
});

getSystemColorMode = () =>
window.matchMedia('(prefers-color-scheme: dark)').matches
? 'dark'
: 'light';

getTheme = () => {
const {
settings: { theme },
} = this.props;
return 'system' === theme ? this.getSystemColorMode() : theme;
};

initializeElectron = () => {
const remote = __non_webpack_require__('electron').remote; // eslint-disable-line no-undef
const { remote } = __non_webpack_require__('electron'); // eslint-disable-line no-undef

this.setState({
electron: {
Expand All @@ -320,12 +334,28 @@ export const App = connect(
});
};

onUpdateContent = (note, content) =>
this.props.actions.updateNoteContent({
noteBucket: this.props.noteBucket,
note,
content,
});
onUpdateContent = (note, content) => {
if (!note) {
return;
}

const updatedNote = {
...note,
data: {
...note.data,
content,
modificationDate: Math.floor(Date.now() / 1000),
},
};

// update the bucket but don't force sync right away
// as this happens per keystroke when the user is editing
// a note. The NoteEditor will notify via props when
// it's time to sync via Simperium
const { noteBucket } = this.props;

noteBucket.update(note.id, updatedNote.data, {}, { sync: false });
};

syncNote = noteId => {
this.props.noteBucket.touch(noteId);
Expand Down Expand Up @@ -388,7 +418,7 @@ export const App = connect(
} = this.props;
const isMacApp = isElectronMac();

const themeClass = `theme-${settings.theme}`;
const themeClass = `theme-${this.getTheme()}`;

const appClasses = classNames('app', themeClass, {
'is-line-length-full': settings.lineLength === 'full',
Expand All @@ -407,7 +437,9 @@ export const App = connect(
{isDevConfig && <DevBadge />}
{isAuthorized ? (
<div className={mainClasses}>
{state.showNavigation && <NavigationBar />}
{state.showNavigation && (
<NavigationBar isElectron={isElectron()} />
)}
<AppLayout
isFocusMode={settings.focusModeEnabled}
isNavigationOpen={state.showNavigation}
Expand Down Expand Up @@ -442,6 +474,7 @@ export const App = connect(
closeDialog={this.props.actions.closeDialog}
dialogs={this.props.appState.dialogs}
isElectron={isElectron()}
isMacApp={isMacApp}
/>
</div>
);
Expand Down
121 changes: 55 additions & 66 deletions lib/auth/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,84 +216,73 @@ export class Auth extends Component {
};

setupAuthWindow = () => {
const remote = __non_webpack_require__('electron').remote; // eslint-disable-line no-undef
const BrowserWindow = remote.BrowserWindow;
const protocol = remote.protocol;
// eslint-disable-next-line no-undef
const { BrowserWindow, session } = __non_webpack_require__(
'electron'
).remote;

this.authWindow = new BrowserWindow({
width: 640,
height: 640,
show: false,
webPreferences: {
nodeIntegration: false,
session: session.fromPartition(`fresh-session-${Math.random()}`),
},
});

// Register simplenote:// protocol
protocol.registerHttpProtocol('simplenote', req => {
this.authWindow.loadURL(req.url);
this.authWindow.on('closed', () => {
// make sure to release this from memory
this.authWindow = null;
});

this.authWindow.webContents.on('will-navigate', (event, url) =>
this.onBrowserNavigate(url)
);

this.authWindow.webContents.on(
'did-get-redirect-request',
(event, oldUrl, newUrl) => this.onBrowserNavigate(newUrl)
);
};

onBrowserNavigate = url => {
try {
this.authenticateWithUrl(new URL(url));
} catch (error) {
// Do nothing if Url was invalid
}
};

authenticateWithUrl = url => {
// Bail out if the url is not the simplenote protocol
if (url.protocol !== 'simplenote:') {
return;
}

const { authorizeUserWithToken, saveWPToken } = this.props;
const params = url.searchParams;

// Display an error message if authorization failed.
if (params.get('error')) {
switch (params.get('code')) {
case '1':
return this.authError(
'Please activate your WordPress.com account via email and try again.'
);
default:
return this.authError('An error was encountered while signing in.');
this.authWindow.webContents.session.protocol.registerHttpProtocol(
'simplenote',
(req, callback) => {
const { searchParams } = new URL(req.url);

// cancel the request by running callback() with no parameters
// we're going to close the window and continue processing the
// information we received in args of the simplenote://auth URL
callback();

const errorCode = searchParams.get('error')
? searchParams.get('code')
: false;
const authState = searchParams.get('state');
const userEmail = searchParams.get('user');
const simpToken = searchParams.get('token');
const wpccToken = searchParams.get('wp_token');

// Display an error message if authorization failed.
switch (errorCode) {
case false:
break;
case '1':
return this.authError(
'Please activate your WordPress.com account via email and try again.'
);
case '2':
return this.authError(
'Please confirm your account with the confirmation email before signing in to Simplenote.'
);
default:
return this.authError('An error was encountered while signing in.');
}

this.closeAuthWindow();

if (authState !== this.authState) {
return;
}

const { authorizeUserWithToken, saveWPToken } = this.props;
authorizeUserWithToken(userEmail, simpToken);
if (wpccToken) {
saveWPToken(wpccToken);
}
}
}

const userEmail = params.get('user');
const spToken = params.get('token');
const state = params.get('state');

// Sanity check on params
if (!(spToken && userEmail && state)) {
return this.closeAuthWindow();
}

// Verify that the state strings match
if (state !== this.authState) {
return;
}

authorizeUserWithToken(userEmail, spToken);

const wpToken = params.get('wp_token');
if (wpToken) {
saveWPToken(wpToken);
}

this.closeAuthWindow();
);
};

authError = errorMessage => {
Expand Down
3 changes: 3 additions & 0 deletions lib/dialog-renderer/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export const DialogRenderer = props => {
closeDialog,
dialogs,
isElectron,
isMacApp,
} = props;

const renderDialog = dialog => {
Expand Down Expand Up @@ -40,6 +41,7 @@ export const DialogRenderer = props => {
dialog={dialog}
requestClose={closeThisDialog}
isElectron={isElectron}
isMacApp={isMacApp}
{...appProps}
/>
</Modal>
Expand All @@ -56,6 +58,7 @@ DialogRenderer.propTypes = {
closeDialog: PropTypes.func.isRequired,
dialogs: PropTypes.array.isRequired,
isElectron: PropTypes.bool.isRequired,
isMacApp: PropTypes.bool.isRequired,
};

export default DialogRenderer;
4 changes: 3 additions & 1 deletion lib/dialogs/settings-group.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,13 @@ export const SettingsGroup = ({
</a>
);

const childElements = React.Children.toArray(children).filter(o => o);

return (
<div className="settings-group">
<PanelTitle headingLevel="3">{groupTitle}</PanelTitle>
<div className="settings-items theme-color-border">
{React.Children.map(children, ({ props: { slug, title } }) => (
{childElements.map(({ props: { slug, title } }) => (
<label
className="settings-item theme-color-border"
htmlFor={`settings-field-${groupSlug}-${slug}`}
Expand Down
16 changes: 14 additions & 2 deletions lib/dialogs/settings/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class SettingsDialog extends Component {
dialog: PropTypes.shape({ title: PropTypes.string.isRequired }),
onSignOut: PropTypes.func.isRequired,
isElectron: PropTypes.bool.isRequired,
isMacApp: PropTypes.bool.isRequired,
onSetWPToken: PropTypes.func.isRequired,
requestClose: PropTypes.func.isRequired,
settings: PropTypes.object.isRequired,
Expand Down Expand Up @@ -118,7 +119,14 @@ export class SettingsDialog extends Component {
};

render() {
const { buckets, dialog, requestClose, settings } = this.props;
const {
buckets,
dialog,
isElectron,
isMacApp,
requestClose,
settings,
} = this.props;
const { analyticsEnabled } = this.props.appState.preferences;

return (
Expand All @@ -132,7 +140,11 @@ export class SettingsDialog extends Component {
this.onToggleShareAnalyticsPreference
}
/>
<DisplayPanel buckets={buckets} />
<DisplayPanel
buckets={buckets}
isElectron={isElectron}
isMacApp={isMacApp}
/>
<ToolsPanel />
</TabPanels>
</Dialog>
Expand Down
Loading

0 comments on commit b82a9ea

Please sign in to comment.