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

[TS migration] Migrate 'Desktop' files to TypeScript #37615

Merged
merged 8 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
4 changes: 2 additions & 2 deletions desktop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ 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)
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,
];
VickyStash marked this conversation as resolved.
Show resolved Hide resolved

const WHITELIST_CHANNELS_MAIN_TO_RENDERER = [ELECTRON_EVENTS.KEYBOARD_SHORTCUTS_PAGE, ELECTRON_EVENTS.UPDATE_DOWNLOADED, ELECTRON_EVENTS.FOCUS, ELECTRON_EVENTS.BLUR];
VickyStash marked this conversation as resolved.
Show resolved Hide resolved

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
Loading