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

[MM-55152] Add new Desktop API endpoints, improve preload script, some clean-up #2900

Merged
merged 23 commits into from
Dec 13, 2023

Conversation

devinbinnie
Copy link
Member

@devinbinnie devinbinnie commented Nov 3, 2023

Summary

This PR introduces the concept of a new Desktop App/Web App API, provided by the preload script in the form of methods exposed on the window.desktopAPI object. These are exposed securely by the contextBridge module of Electron that allows us to share some functionality directly to the renderer process, without exposing any modules directly. This way, we can avoid relying on window.postMessage listeners and work with a clean, typed API in the Web App when Desktop App communication is necessary.

In this change we have:

  • Added the contextBridge API and all the current functionality in the form of functions that the web app can call
  • Reworked and organized all of the legacy message-passing code that will still need to exist to support older web app versions for the time being
  • Removed some of the extra code in the preload script that should be in the Main Process to avoid bloating it
  • Converted the preload script to TypeScript and created a types package to be shared with the Web App
  • Some additional changes/clean up to support the changes (see comments below).

We'll be waiting to merge this PR until after it has been tested and integrated properly with the Web App. There are also some concerns about running the new API alongside the old one, so we'll have to see what challenges that presents.

Related PR

mattermost/mattermost#25438: Web App PR utilizing the new API

Ticket Link

https://mattermost.atlassian.net/browse/MM-55152

Reworked and updated the preload script to use an updated and more robust Web App API

@devinbinnie devinbinnie added the Do Not Merge Should not be merged until this label is removed label Nov 3, 2023
@devinbinnie devinbinnie removed the request for review from streamer45 November 3, 2023 19:40
@@ -68,7 +68,7 @@ export class ServerViewState {
}

getCurrentServer = () => {
log.debug('getCurrentServer');
log.silly('getCurrentServer');
Copy link
Member Author

Choose a reason for hiding this comment

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

These logs kept just getting in the way as some of these methods are called very frequently (ie. on every click) so I changed the logging level on them.

@@ -114,7 +113,7 @@ export function handleWelcomeScreenModal() {
}
}

export function handleMentionNotification(event: IpcMainEvent, title: string, body: string, channel: {id: string}, teamId: string, url: string, silent: boolean, data: MentionData) {
export function handleMentionNotification(event: IpcMainEvent, title: string, body: string, channel: {id: string}, teamId: string, url: string, silent: boolean, data: {soundName: string}) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Some types, like MentionData and some of the calls types weren't as necessary since we have more control over what is sent up and down through the API, so I've removed most of the extra ones.

@@ -1,94 +0,0 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
Copy link
Member Author

Choose a reason for hiding this comment

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

There was no need for a separate calls widget script since we can assume that everything that is exposed is accessible remotely, and it was easier to just manage one for all use cases.

@@ -89,6 +91,12 @@ export class MattermostBrowserView extends EventEmitter {
if (process.platform !== 'darwin') {
this.browserView.webContents.on('before-input-event', this.handleInputEvents);
}
this.browserView.webContents.on('input-event', (_, inputEvent) => {
Copy link
Member Author

Choose a reason for hiding this comment

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

This was transferred over from the preload script, that checks when the user clicks on the main view to close any of the dropdowns.


if (!this.isAllowedEvent(ev)) {
log.warn('Disallowed calls event');
if (this.mainView?.webContentsId !== ev.sender.id) {
Copy link
Member Author

Choose a reason for hiding this comment

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

I decided that it made sense to be more deliberately restrictive on some of these calls methods, so I've removed and reworked all the checks for one-way message passing between the two. It also was necessary to do to merge the preload scripts as the message passing code was getting confused.

@devinbinnie devinbinnie added 2: Dev Review Requires review by a core committer and removed Do Not Merge Should not be merged until this label is removed labels Nov 14, 2023
@devinbinnie devinbinnie added this to the v5.7.0 milestone Nov 14, 2023
@@ -25,7 +25,7 @@ export class AppState extends EventEmitter {
updateExpired = (viewId: string, expired: boolean) => {
ServerManager.getViewLog(viewId, 'AppState').silly('updateExpired', expired);

this.unreads.set(viewId, expired);
this.expired.set(viewId, expired);
Copy link
Member Author

Choose a reason for hiding this comment

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

This was just wrong :P

export function handleMentionNotification(event: IpcMainEvent, title: string, body: string, channel: {id: string}, teamId: string, url: string, silent: boolean, data: MentionData) {
log.debug('handleMentionNotification', {title, body, channel, teamId, url, silent, data});
NotificationManager.displayMention(title, body, channel, teamId, url, silent, event.sender, data);
export function handleMentionNotification(event: IpcMainEvent, title: string, body: string, channelId: string, teamId: string, url: string, silent: boolean, soundName: string) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Simplified this since we should be more specific about what these calls expect.

Copy link
Member

Choose a reason for hiding this comment

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

Does changing this cause any issues for old web apps, or is it this just called from the desktop app itself?

Copy link
Member Author

Choose a reason for hiding this comment

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

Just called from the Desktop itself, I do some "magic" in the preload script to account for the legacy case so that we could fix it properly here.

@@ -75,7 +75,7 @@ export class ServerDropdownView {

private init = () => {
log.info('init');
const preload = getLocalPreload('desktopAPI.js');
const preload = getLocalPreload('internalAPI.js');
Copy link
Member

Choose a reason for hiding this comment

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

I didn't realize all the BrowserViews that make up the desktop app also needed this preload script. I guess it makes sense since it's also how they access the desktop API

@devinbinnie
Copy link
Member Author

@streamer45 Gentle ping to review :)

@streamer45
Copy link
Contributor

@devinbinnie Could you confirm whether this is supposed to be a backwards compatible change? What I mean is, is the expectation that running this Desktop branch against a non-updated webapp (or plugin) should work as before?

Copy link
Contributor

@streamer45 streamer45 left a comment

Choose a reason for hiding this comment

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

Gave this a quick pass, mostly focusing on Calls related changes. Overall it looks great, left a couple of comments.


if (!this.isCallsWidget(ev.sender.id)) {
log.debug('onJoinedCall', 'blocked on wrong webContentsId');
return;
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose that case shouldn't really happen in a sane environment but no objections with properly rejecting.

src/main/preload/externalAPI.ts Outdated Show resolved Hide resolved
src/main/preload/externalAPI.ts Outdated Show resolved Hide resolved
Copy link
Contributor

@streamer45 streamer45 left a comment

Choose a reason for hiding this comment

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

Great stuff, thank you for being patient with me 🤝

@cpoile Would be good if you could do some smoketesting purely for Calls ringing functionality on top of this PR. Not blocking, just something we may want to cover in case I missed anything.

@devinbinnie
Copy link
Member Author

Great stuff, thank you for being patient with me 🤝

@cpoile Would be good if you could do some smoketesting purely for Calls ringing functionality on top of this PR. Not blocking, just something we may want to cover in case I missed anything.

I did some preliminary testing, made sure that all the calls functionality worked as I saw, but yeah feel free to do another pass :)

@devinbinnie devinbinnie removed the 2: Dev Review Requires review by a core committer label Nov 23, 2023
@yasserfaraazkhan yasserfaraazkhan added Run Desktop E2E Tests This label will trigger the workflow that runs e2e automation tests and removed Run Desktop E2E Tests This label will trigger the workflow that runs e2e automation tests labels Nov 26, 2023
@devinbinnie devinbinnie added the 3: Security Review Review requested from Security Team label Nov 30, 2023
Copy link
Contributor

@yasserfaraazkhan yasserfaraazkhan left a comment

Choose a reason for hiding this comment

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

@devinbinnie
Tested this on Mac and windows (5.7 version from the artifacts)

  • inviting user, license upload, custom emoji, slash command, desktop notification on mac and windows, link preview, creating GM, converting gm to private, draft counts, GM Unread message count, creating teams.

@devinbinnie devinbinnie removed the 3: QA Review Requires review by a QA tester label Dec 4, 2023
@@ -143,6 +143,7 @@
"@electron/fuses": "1.6.0",
"@electron/universal": "1.3.1",
"@mattermost/compass-icons": "0.1.32",
"@mattermost/desktop-api": "*",
Copy link
Member

Choose a reason for hiding this comment

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

Can we depend on a version number here? Not a big deal, but would make it easier to see which version we were actually using in a specific release, if we ever need to figure that out in the future. The lockfile has all that info too, but it's not exactly easy to read.

Copy link
Member Author

Choose a reason for hiding this comment

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

We actually do this same thing here for the webapp, I believe we do that to make sure we use the local package instead of the remote one while developing.

We want the web app to depend on a specific version so we know how the compatibility works, but for the Desktop App it should always use whatever types are available within the same commit as the app was built.

LEGACY_OFF,
} from 'common/communication';

const createListener: ExternalAPI['createListener'] = (channel: string, listener: (...args: never[]) => void) => {
Copy link
Member

@jupenur jupenur Dec 13, 2023

Choose a reason for hiding this comment

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

This is really nicely handled. Something to be extremely careful with is accidentally returning privileged objects to the webapp context. IpcRendererEvent is one such object and this covers that case well.

Copy link
Member Author

Choose a reason for hiding this comment

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

FWIW, I believe Electron will not send anything that isn't serializable, so eg. we can't send a function callback by accident.

Copy link
Member

Choose a reason for hiding this comment

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

It'll serialize all objects and proxy all functions. Prototypes chains will be ignored though.

@devinbinnie devinbinnie added 4: Reviews Complete All reviewers have approved the pull request and removed 3: Security Review Review requested from Security Team labels Dec 13, 2023
@devinbinnie devinbinnie merged commit 0cab09b into mattermost:master Dec 13, 2023
21 of 23 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4: Reviews Complete All reviewers have approved the pull request kind/refactor release-note
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants