Skip to content

Commit

Permalink
Merge pull request #37615 from VickyStash/ts-migration/desktop-files
Browse files Browse the repository at this point in the history
[TS migration] Migrate 'Desktop' files to TypeScript
  • Loading branch information
mountiny authored Mar 15, 2024
2 parents d17b4df + ec0efd0 commit cdd93d1
Show file tree
Hide file tree
Showing 10 changed files with 318 additions and 617 deletions.
4 changes: 2 additions & 2 deletions config/webpack/webpack.desktop.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ module.exports = (env) => {
name: 'desktop-main',
target: 'electron-main',
entry: {
main: './desktop/main.js',
contextBridge: './desktop/contextBridge.js',
main: './desktop/main.ts',
contextBridge: './desktop/contextBridge.ts',
},
output: {
filename: '[name].js',
Expand Down
4 changes: 2 additions & 2 deletions desktop/ELECTRON_EVENTS.js → desktop/ELECTRON_EVENTS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ const ELECTRON_EVENTS = {
KEYBOARD_SHORTCUTS_PAGE: 'keyboard-shortcuts-page',
START_UPDATE: 'start-update',
UPDATE_DOWNLOADED: 'update-downloaded',
};
} as const;

module.exports = ELECTRON_EVENTS;
export default ELECTRON_EVENTS;
8 changes: 4 additions & 4 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ The New Expensify desktop app is built using [Electron.js](https://www.electronj
The desktop app is organized in three pieces:

1. The Electron main process
- Implemented in https://github.com/Expensify/App/blob/main/desktop/main.js.
- Implemented in https://github.com/Expensify/App/blob/main/desktop/main.ts.
- This file has access to the full set of Electron and Node.JS APIs.
2. The Electron renderer process
- This is the webpack-bundled version of our react-native-web app (except using `index.desktop.js` files instead of `index.website.js`, where applicable)
- This is _very_ similar to our web app, and code in this process should assume it will be run in the context of a browser (no access to `require`, Electron, or Node.js APis)
3. The context bridge
- Implemented in https://github.com/Expensify/App/blob/main/desktop/contextBridge.js
- Implemented in https://github.com/Expensify/App/blob/main/desktop/contextBridge.ts
- The context bridge enables communication between the main and renderer processes. For example, if the renderer process needs to make use of a Node.js or Electron API it must:
1. Define an event in https://github.com/Expensify/App/blob/main/desktop/ELECTRON_EVENTS.js
1. Define an event in https://github.com/Expensify/App/blob/main/desktop/ELECTRON_EVENTS.ts
2. Add that event to the whitelist defined in the context bridge
3. Set up a handler for the event in the main process that can respond to the renderer process back through the bridge, if necessary.

Expand Down Expand Up @@ -131,7 +131,7 @@ The root [package.json](../package.json) serves for `devDependencies` and shared
The [desktop/package.json](./package.json) serves for desktop (electron-main) specific dependencies
We use Webpack with a [desktop specific config](../config/webpack/webpack.desktop.js) to bundle our js code
Half of the config takes care of packaging root package dependencies - everything related to rendering App in the Electron window. Packaged under `dist/www`
The other half is about bundling the `main.js` script which initializes Electron and renders `www`
The other half is about bundling the `main.ts` script which initializes Electron and renders `www`

## See what is getting packaged in the app
If you suspect unnecessary items might be getting packaged you can inspect the package content in `desktop-build/`
Expand Down
98 changes: 0 additions & 98 deletions desktop/contextBridge.js

This file was deleted.

82 changes: 82 additions & 0 deletions desktop/contextBridge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {contextBridge, ipcRenderer} from 'electron';
import ELECTRON_EVENTS from './ELECTRON_EVENTS';

type ContextBridgeApi = {
send: (channel: string, data?: unknown) => void;
sendSync: (channel: string, data?: unknown) => unknown;
invoke: (channel: string, ...args: unknown[]) => Promise<unknown>;
on: (channel: string, func: (...args: unknown[]) => void) => void;
removeAllListeners: (channel: string) => void;
};

const WHITELIST_CHANNELS_RENDERER_TO_MAIN = [
ELECTRON_EVENTS.REQUEST_DEVICE_ID,
ELECTRON_EVENTS.REQUEST_FOCUS_APP,
ELECTRON_EVENTS.REQUEST_UPDATE_BADGE_COUNT,
ELECTRON_EVENTS.REQUEST_VISIBILITY,
ELECTRON_EVENTS.START_UPDATE,
ELECTRON_EVENTS.LOCALE_UPDATED,
] as const;

const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR] as const;

const getErrorMessage = (channel: string): string => `Electron context bridge cannot be used with channel '${channel}'`;

/**
* The following methods will be available in the renderer process under `window.electron`.
*/
contextBridge.exposeInMainWorld('electron', {
/**
* Send data asynchronously from renderer process to main process.
* Note that this is a one-way channel – main will not respond. In order to get a response from main, either:
*
* - Use `sendSync`
* - Or implement `invoke` if you want to maintain asynchronous communication: https://www.electronjs.org/docs/latest/api/ipc-renderer#ipcrendererinvokechannel-args
*/
send: (channel: string, data: unknown) => {
if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) {
throw new Error(getErrorMessage(channel));
}

ipcRenderer.send(channel, data);
},

/** Send data synchronously from renderer process to main process. Main process may return a result. */
sendSync: (channel: string, data: unknown): unknown => {
if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) {
throw new Error(getErrorMessage(channel));
}

return ipcRenderer.sendSync(channel, data);
},

/** Execute a function in the main process and return a promise that resolves with its response. */
invoke: (channel: string, ...args: unknown[]): Promise<unknown> => {
if (!WHITELIST_CHANNELS_RENDERER_TO_MAIN.some((whitelistChannel) => whitelistChannel === channel)) {
throw new Error(getErrorMessage(channel));
}

return ipcRenderer.invoke(channel, ...args);
},

/** Set up a listener for events emitted from the main process and sent to the renderer process. */
on: (channel: string, func: (...args: unknown[]) => void) => {
if (!WHITELIST_CHANNELS_MAIN_TO_RENDERER.some((whitelistChannel) => whitelistChannel === channel)) {
throw new Error(getErrorMessage(channel));
}

// Deliberately strip event as it includes `sender`
ipcRenderer.on(channel, (event, ...args) => func(...args));
},

/** Remove listeners for a single channel from the main process and sent to the renderer process. */
removeAllListeners: (channel: string) => {
if (!WHITELIST_CHANNELS_MAIN_TO_RENDERER.some((whitelistChannel) => whitelistChannel === channel)) {
throw new Error(getErrorMessage(channel));
}

ipcRenderer.removeAllListeners(channel);
},
});

export default ContextBridgeApi;
Loading

0 comments on commit cdd93d1

Please sign in to comment.