Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(notifications): use Pinghub websocket #46070

Merged
merged 22 commits into from
Oct 31, 2020
Merged

Conversation

nsakaimbo
Copy link
Contributor

@nsakaimbo nsakaimbo commented Sep 30, 2020

Description

Uses direct Pinghub websocket connection to source realtime notification events.

Context

  • HMTL5 PushManager service workers are unsupported by Electron
  • Calypso/renderer websocket connection cannot work in the Desktop app, because it sends the Origin header to the backend. Unfortunately, a bug in Electron's Chromium implementation prevents us from modifying headers of websocket connection requests made in the renderer process. 💩
  • To enable timely notifications, we have to directly connect to Pinghub (via OAuth) from Electron's Node process

Implementation Overview

  • Use npm package ws for websocket connection
  • Use npm package keytar to persist sensitive user credentials to the keychain (OAuth token) (cross-platform keychain support for Windows, Linux and Mac)
  • Use Automattic npm package wpcom-xhr-request to interface with other notification APIs (fetch note, mark-note-as-read)

Limitations/To-Dos

  • Cannot sync notifications with badge count (requires dedicated server-side support). We might be able to infer this from Calypso, except:
    • Calypso only tracks the unseen, not unread count. Unseen count = all notifications received while the notifications panel is hidden (dot icon), zeroed out when bell is clicked (even though notifications may be unread).
  • Trying to avoid using window events and instead use redux state to force a notifications panel refresh. Not sure if possible? Solved.

@matticbot
Copy link
Contributor

@matticbot matticbot added the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Sep 30, 2020
@matticbot
Copy link
Contributor

matticbot commented Sep 30, 2020

Here is how your PR affects size of JS and CSS bundles shipped to the user's browser:

App Entrypoints (~72 bytes removed 📉 [gzipped])

name        parsed_size           gzip_size
entry-main       -283 B  (-0.0%)      -72 B  (-0.0%)

Common code that is always downloaded and parsed every time the app is loaded, no matter which route is used.

Sections (~88 bytes removed 📉 [gzipped])

name              parsed_size           gzip_size
security               -103 B  (-0.0%)      -46 B  (-0.0%)
gutenberg-editor       -103 B  (-0.0%)      -42 B  (-0.0%)

Sections contain code specific for a given set of routes. Is downloaded and parsed only when a particular route is navigated to.

Async-loaded Components (~278 bytes removed 📉 [gzipped])

name                                           parsed_size           gzip_size
async-load-automattic-notifications-index-jsx       -841 B  (-0.7%)     -278 B  (-0.8%)

React components that are loaded lazily, when a certain part of UI is displayed for the first time.

Legend

What is parsed and gzip size?

Parsed Size: Uncompressed size of the JS and CSS files. This much code needs to be parsed and stored in memory.
Gzip Size: Compressed size of the JS and CSS files. This much data needs to be downloaded over network.

Generated by performance advisor bot at iscalypsofastyet.com.

@nsakaimbo nsakaimbo force-pushed the desktop-notifications branch 2 times, most recently from 59df8b8 to 0e12e20 Compare October 1, 2020 15:51
@@ -279,10 +274,11 @@ export class Notifications extends Component {
export default connect(
( state ) => ( {
currentLocaleSlug: getCurrentLocaleVariant( state ) || getCurrentLocaleSlug( state ),
forceRefresh: shouldForceRefresh( state ),
Copy link
Contributor Author

@nsakaimbo nsakaimbo Oct 1, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: Confirmed that the Redux event is firing in Calypso, but is not being picked up here (i.e. doesn't trigger a re-render of the component). I'm not quite sure how the existing getCurrentLocaleVariant getter (above) works in this case.

notifications-refresh-calypso

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: finally got this working thanks to corrections proposed by @tyxla (d7aec3a). Works like a charm now - thank you for your help!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad it worked, @nsakaimbo 🎉

@nsakaimbo nsakaimbo changed the base branch from master to release/6.2.0 October 8, 2020 16:55
@nsakaimbo nsakaimbo requested review from a team as code owners October 8, 2020 16:55
@kwight
Copy link
Contributor

kwight commented Oct 8, 2020

@nsakaimbo Is the idea to split implementation for the web (master) and the desktop app going forward? Or are you proposing this be merged to master too, just after the 6.2 release?

@kwight
Copy link
Contributor

kwight commented Oct 9, 2020

@nsakaimbo Are there updated testing instructions anywhere for testing PRs like this? I didn't realize wp-desktop was no more, and fully in Calypso (but I don't see a readme explaining know to get it all up and running for testing).

Looking through, it seems you're mostly removing special desktop handling, so that the desktop app uses pinghub the same way Calypso does (but with allowances for Origin issues). Would this change anything for Calypso on web?

@nsakaimbo
Copy link
Contributor Author

Thanks for taking a peek, @kwight! Yes, the desktop app is fully in Calypso now (pbwngY-7c-p2).

Apologies for the lack of documentation, but this is something that will be landing fairly shortly! (Developer docs in #45916).

@nsakaimbo
Copy link
Contributor Author

nsakaimbo commented Oct 9, 2020

Looking through, it seems you're mostly removing special desktop handling, so that the desktop app uses pinghub the same way Calypso does (but with allowances for Origin issues). Would this change anything for Calypso on web?

@kwight This is correct. The special desktop handling was added fairly recently (by me 😅 ) in #45793, and while this mostly worked we discovered that Calypso's existing Pinghub connection does not work in the Desktop app (due to a low-level limitation in Electron that we can't even work around) - see pbwngY-7U-p2. For more robust/timely notification support, we're using the OAuth token to connect Electron's Node process to Pinghub directly.

Would this change anything for Calypso on web?

Since we're reverting specialized logic that was only recently added to Calypso (i.e. now the Desktop/Electron will do the heavy lifting w.r.t. Pinghub), Calypso's behavior should remain unchanged (reverting to Calypso's behavior before the initial changes were made to apps/notifications).

Copy link
Contributor

@belcherj belcherj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some small things. Looks good. Great Work!!

client/desktop/lib/debounce/index.js Outdated Show resolved Hide resolved
client/desktop/lib/keychain/index.js Outdated Show resolved Hide resolved
client/desktop/lib/notifications/api/notes.js Outdated Show resolved Hide resolved
client/desktop/lib/window-manager/index.js Show resolved Hide resolved
Base automatically changed from release/6.2.0 to master October 20, 2020 00:15
Copy link
Contributor

@belcherj belcherj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thank you for the changes.

}, 1000 );
}
onNotificationsPanelRefresh: function () {
this.store.dispatch( forceNotificationsRefresh( true ) );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should simply import the refreshNotes method from the notifications module and call it. Adding the Redux action and reducer framework around it should be completely unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree this seems a little convoluted but I can't find a prior example of the client code importing helpers from apps/ directory. I gave this approach a try in 497823d3b9b119fbaa29379d61a1974f24f5e476 but ultimately reverted due to a couple issues:

  • for some reason, it didn't work to refresh the notifications panel (confirmed by logging activity from the notifications rest-client). Not sure why.
  • the linter also complained about the import in client/lib/desktop/index.js:
import { refreshNotes } from '../../../apps/notifications/src/panel/notifications';
// error: file is not under 'rootDir'. `rootDir` is expected to contain all source files.

I can't find a prior example of client code importing something from the apps directory, but happy to revisit this as a refactor, though.

client/desktop/lib/cookie-auth/index.js Outdated Show resolved Hide resolved
client/desktop/lib/keychain/index.js Show resolved Hide resolved

module.exports = {
fetchNote: ( noteId ) => promiseTimeout( 300, fetchNote( noteId ) ),
markReadStatus: ( noteId, isRead ) => promiseTimeout( 1000, markReadStatus( noteId, isRead ) ), // response time of this endpoint is very slow!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is there only a 0.3s timeout for fetchNote? That's very unfriendly for users on slow connections. And why do we need hard timeouts at all?

Copy link
Contributor Author

@nsakaimbo nsakaimbo Oct 30, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I went ahead and bumped the timeouts to a more forgiving value.

IMO it seems having timeout is useful from a user responsiveness perspective - at the very least, this gives us the opportunity to display some sort of error message, rather than leave them potentially hanging indefinitely (or for too long).

However if there's some sort of "natural timeout" that happens anyway and adding a hard timeout is truly redundant (either from the server or perhaps even the network request itself) then I'd be happy to take this out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the fetchNote or markReadStatus request takes a lot of time, it doesn't block the UI. It's only that the incoming WebSocket notifications take long to appear in the UI, or that marking a notification as read takes a long time or is never really performed on server. The user can continue to use the UI normally.

Nowhere else in Calypso do we put extra timeouts on REST APIs. And even the 2 seconds still are way too short for, e.g., users in Australia or South Africa who are connecting to US servers.

I'd remove them everywhere except the initial boot sequence, which has a potential to brick the UI completely.

},
( error, body ) => {
if ( error ) {
reject( error );
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The callback should return after rejecting.

client/desktop/lib/notifications/viewmodel/index.js Outdated Show resolved Hide resolved
client/desktop/window-handlers/notifications/index.js Outdated Show resolved Hide resolved
@nsakaimbo nsakaimbo merged commit 7c41344 into master Oct 31, 2020
@nsakaimbo nsakaimbo deleted the desktop-notifications branch October 31, 2020 01:35
@matticbot matticbot removed the [Status] Needs Review The PR is ready for review. This also triggers e2e canary tests and wp-desktop tests automatically. label Oct 31, 2020
@nsakaimbo nsakaimbo mentioned this pull request Jan 4, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants