[photos-desktop] Update to latest Electron (#1213)

- And update to latest everything else too
- Fix the duplication in preload
- ... and many more cleanup changes (see commit titles for details)

Should not have major functional impact, and whatever IPC actions got
broken by the switch to context-isolation will still not be working.
This is just an intermediate step, and more PRs will continue fixing the
IPC APIs.
This commit is contained in:
Manav Rathi 2024-03-26 21:54:18 +05:30 committed by GitHub
commit a78e2892cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
39 changed files with 839 additions and 827 deletions

View file

@ -1,50 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ente Photos</title>
</head>
<body style="background-color: black">
<div
style="
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 90vh;
"
>
<div style="width: 64px">
<svg
version="1.1"
id="L9"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
x="0px"
y="0px"
viewBox="0 0 100 100"
enable-background="new 0 0 0 0"
xml:space="preserve"
>
<path
fill="#2dc262"
d="M73,50c0-12.7-10.3-23-23-23S27,37.3,27,50 M30.9,50c0-10.5,8.5-19.1,19.1-19.1S69.1,39.5,69.1,50"
>
<animateTransform
attributeName="transform"
attributeType="XML"
type="rotate"
dur="1s"
from="0 50 50"
to="360 50 50"
repeatCount="indefinite"
/>
</path>
</svg>
</div>
</div>
</body>
</html>

View file

@ -27,7 +27,7 @@ There is also a third environment that gets temporarily created:
- The [preload script](../src/preload.ts) acts as a gateway between the _main_
and the _renderer_ process. It runs in its own isolated environment.
### electron-builder
### Packaging
[Electron Builder](https://www.electron.build) is used for packaging the app for
distribution.
@ -36,15 +36,44 @@ During the build it uses
[electron-builder-notarize](https://github.com/karaggeorge/electron-builder-notarize)
to notarize the macOS binary.
### Updates
[electron-updater](https://www.electron.build/auto-update#debugging), while a
separate package, is also a part of Electron Builder. It provides an alternative
to Electron's built in auto updater, with a more flexible API. It supports auto
updates for the DMG, AppImage, DEB, RPM and NSIS packages.
[compare-versions](https://github.com/omichelsen/compare-versions) is used for
semver comparisons when we decide when to trigger updates.
### Logging
[electron-log](https://github.com/megahertz/electron-log) is used for logging.
Specifically, it allows us to log to a file (in addition to the console of the
Node.js process), and also handles log rotation and limiting the size of the log
files.
### next-electron-server
This spins up a server for serving files using a protocol handler inside our
Electron process. This allows us to directly use the output produced by
`next build` for loading into our renderer process.
## DX
### Others
See [web/docs/dependencies#DX](../../web/docs/dependencies.md#dx) for the
* [any-shell-escape](https://github.com/boazy/any-shell-escape) is for
escaping shell commands before we execute them (e.g. say when invoking the
embedded ffmpeg CLI).
* [auto-launch](https://github.com/Teamwork/node-auto-launch) is for
automatically starting our app on login, if the user so wishes.
* [electron-store](https://github.com/sindresorhus/electron-store) is used for
persisting user preferences and other arbitrary data.
## Dev
See [web/docs/dependencies#DX](../../web/docs/dependencies.md#dev) for the
general development experience related dependencies like TypeScript etc, which
are similar to that in the web code.
@ -52,6 +81,43 @@ Some extra ones specific to the code here are:
* [concurrently](https://github.com/open-cli-tools/concurrently) for spawning
parallel tasks when we do `yarn dev`.
* [shx](https://github.com/shelljs/shx) for providing a portable way to use
Unix commands in scripts. This allows us to use the same commands across
different platforms like Linux and Windows.
* [shx](https://github.com/shelljs/shx) for providing a portable way to use Unix
commands in our `package.json` scripts. This allows us to use the same
commands (like `ln`) across different platforms like Linux and Windows.
## Functionality
### Conversion
The main tool we use is for arbitrary conversions is FFMPEG. To bundle a
(platform specific) static binary of ffmpeg with our app, we use
[ffmpeg-static](https://github.com/eugeneware/ffmpeg-static).
> There is a significant (~20x) speed difference between using the compiled
> FFMPEG binary and using the WASM one (that our renderer process already has).
> Which is why we bundle it to speed up operations on the desktop app.
In addition, we also bundle a static Linux binary of imagemagick in our extra
resources (`build`) folder. This is used for thumbnail generation on Linux.
On macOS, we use the `sips` CLI tool for conversion, but that is already
available on the host machine, and is not bundled with our app.
### Watch Folders
[chokidar](https://github.com/paulmillr/chokidar) is used as a file system
watcher for the watch folders functionality.
### AI/ML
* [onnxruntime-node](https://github.com/Microsoft/onnxruntime)
* html-entities is used by the bundled clip-bpe-ts.
* GGML binaries are bundled
* We also use [jpeg-js](https://github.com/jpeg-js/jpeg-js#readme) for
conversion of all images to JPEG before processing.
## ZIP
[node-stream-zip](https://github.com/antelle/node-stream-zip) is used for
reading of large ZIP files (e.g. during imports of Google Takeout ZIPs).

View file

@ -8,12 +8,7 @@ Launch the app in development mode:
- Transpiles the files in `src/` and starts the main process.
- Runs a development server for the renderer (with hot module reload).
- Starts tsc in watch mode to recompile the JS files used by the main process.
Note that the main process is not restarted on changes automatically, you'll
still need to restart the app manually  running tsc in watch mode is still
useful to notice any errors.
- Runs a development server for the renderer, with hot module reload.
### yarn build

View file

@ -11,43 +11,42 @@
"build-main:quick": "tsc && electron-builder --dir --config.compression=store --config.mac.identity=null",
"build-renderer": "cd ../web && yarn install && yarn build:photos && cd ../desktop && shx rm -f out && shx ln -sf ../web/apps/photos/out out",
"build:quick": "yarn build-renderer && yarn build-main:quick",
"dev": "concurrently --names 'main,rndr,tscw' \"yarn dev-main\" \"yarn dev-renderer\" \"yarn dev-main-watch\"",
"dev": "concurrently --names 'main,rndr' \"yarn dev-main\" \"yarn dev-renderer\"",
"dev-main": "tsc && electron app/main.js",
"dev-main-watch": "tsc --watch --preserveWatchOutput",
"dev-renderer": "cd ../web && yarn install && yarn dev:photos",
"postinstall": "electron-builder install-app-deps",
"lint": "yarn prettier --check . && eslint --ext .ts src",
"lint-fix": "yarn prettier --write . && eslint --fix --ext .ts src"
},
"dependencies": {
"any-shell-escape": "^0.1.1",
"auto-launch": "^5.0.5",
"chokidar": "^3.5.3",
"compare-versions": "^6.1.0",
"electron-log": "^4.3.5",
"electron-store": "^8.0.1",
"electron-updater": "^4.3.8",
"ffmpeg-static": "^5.1.0",
"html-entities": "^2.4.0",
"jpeg-js": "^0.4.4",
"any-shell-escape": "^0.1",
"auto-launch": "^5.0",
"chokidar": "^3.6",
"compare-versions": "^6.1",
"electron-log": "^5.1",
"electron-store": "^8.2",
"electron-updater": "^6.1",
"ffmpeg-static": "^5.2",
"html-entities": "^2.5",
"jpeg-js": "^0.4",
"next-electron-server": "^1",
"node-stream-zip": "^1.15.0",
"onnxruntime-node": "^1.16.3"
"node-stream-zip": "^1.15",
"onnxruntime-node": "^1.17"
},
"devDependencies": {
"@types/auto-launch": "^5.0.2",
"@types/ffmpeg-static": "^3.0.1",
"@types/auto-launch": "^5.0",
"@types/ffmpeg-static": "^3.0",
"@typescript-eslint/eslint-plugin": "^7",
"@typescript-eslint/parser": "^7",
"concurrently": "^8",
"electron": "^25.8.4",
"electron-builder": "^24.6.4",
"electron-builder-notarize": "^1.2.0",
"electron": "^29",
"electron-builder": "^24",
"electron-builder-notarize": "^1.5",
"eslint": "^8",
"prettier": "^3",
"prettier-plugin-organize-imports": "^3.2",
"prettier-plugin-packagejson": "^2.4",
"shx": "^0.3.4",
"shx": "^0.3",
"typescript": "^5"
},
"productName": "ente"

View file

@ -1,10 +1,10 @@
import { getElectronFile } from "../services/fs";
import {
getElectronFilesFromGoogleZip,
getSavedFilePaths,
} from "../services/upload";
import { uploadStatusStore } from "../stores/upload.store";
import { ElectronFile, FILE_PATH_TYPE } from "../types";
import { getElectronFile } from "./../services/fs";
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
export const getPendingUploads = async () => {
const filePaths = getSavedFilePaths(FILE_PATH_TYPE.FILES);

View file

@ -8,19 +8,16 @@
*
* https://www.electronjs.org/docs/latest/tutorial/process-model#the-main-process
*/
import * as log from "electron-log";
import log from "electron-log";
import { app, BrowserWindow } from "electron/main";
import serveNextAt from "next-electron-server";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import fs from "node:fs/promises";
import path from "node:path";
import { isDev } from "./main/general";
import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc";
import { logErrorSentry, setupLogging } from "./main/log";
import { initWatcher } from "./services/chokidar";
import { addAllowOriginHeader } from "./utils/cors";
import { createWindow } from "./utils/createWindow";
import {
addAllowOriginHeader,
createWindow,
handleDockIconHideOnAutoLaunch,
handleDownloads,
handleExternalLinks,
@ -29,7 +26,10 @@ import {
setupMacWindowOnDockIconClick,
setupMainMenu,
setupTrayItem,
} from "./utils/main";
} from "./main/init";
import { attachFSWatchIPCHandlers, attachIPCHandlers } from "./main/ipc";
import { logErrorSentry, setupLogging } from "./main/log";
import { initWatcher } from "./services/chokidar";
let appIsQuitting = false;
@ -100,8 +100,9 @@ const increaseDiskCache = () => {
/**
* Older versions of our app used to maintain a cache dir using the main
* process. This has been deprecated in favor of using a normal web cache (See:
* [Note: Increased disk cache for the desktop app]).
* process. This has been deprecated in favor of using a normal web cache.
*
* See [Note: Increased disk cache for the desktop app]
*
* Delete the old cache dir if it exists. This code was added March 2024, and
* can be removed after some time once most people have upgraded to newer

View file

@ -1,8 +1,8 @@
import { dialog } from "electron/main";
import * as path from "node:path";
import path from "node:path";
import { getDirFilePaths, getElectronFile } from "../services/fs";
import { getElectronFilesFromGoogleZip } from "../services/upload";
import type { ElectronFile } from "../types";
import type { ElectronFile } from "../types/ipc";
export const selectDirectory = async () => {
const result = await dialog.showOpenDialog({

View file

@ -2,8 +2,8 @@
* @file file system related functions exposed over the context bridge.
*/
import { createWriteStream, existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import fs from "node:fs/promises";
import path from "node:path";
import { Readable } from "node:stream";
import { logError } from "./log";

View file

@ -1,6 +1,6 @@
import { shell } from "electron"; /* TODO(MR): Why is this not in /main? */
import { app } from "electron/main";
import * as path from "node:path";
import path from "node:path";
/** `true` if the app is running in development mode. */
export const isDev = !app.isPackaged;

View file

@ -4,15 +4,94 @@ import { existsSync } from "node:fs";
import os from "os";
import path from "path";
import util from "util";
import { rendererURL } from "../main";
import { isDev } from "../main/general";
import { isAppQuitting, rendererURL } from "../main";
import { setupAutoUpdater } from "../services/appUpdater";
import autoLauncher from "../services/autoLauncher";
import { getHideDockIconPreference } from "../services/userPreference";
import { isPlatform } from "./common/platform";
import { buildContextMenu, buildMenuBar } from "./menu";
import { isPlatform } from "../utils/common/platform";
import { buildContextMenu, buildMenuBar } from "../utils/menu";
import { isDev } from "./general";
import { logErrorSentry } from "./log";
const execAsync = util.promisify(require("child_process").exec);
/**
* Create an return the {@link BrowserWindow} that will form our app's UI.
*
* This window will show the HTML served from {@link rendererURL}.
*/
export const createWindow = async () => {
const appImgPath = isDev
? "../build/window-icon.png"
: path.join(process.resourcesPath, "window-icon.png");
const appIcon = nativeImage.createFromPath(appImgPath);
// Create the main window. This'll show our web content.
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(app.getAppPath(), "preload.js"),
},
icon: appIcon,
// The color to show in the window until the web content gets loaded.
// See: https://www.electronjs.org/docs/latest/api/browser-window#setting-the-backgroundcolor-property
backgroundColor: "black",
// We'll show conditionally depending on `wasAutoLaunched` later
show: false,
});
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
if (wasAutoLaunched) {
// Keep the macOS dock icon hidden if we were auto launched.
if (process.platform == "darwin") app.dock.hide();
} else {
// Show our window (maximizing it) if this is not an auto-launch on
// login.
mainWindow.maximize();
}
mainWindow.loadURL(rendererURL);
// Open the DevTools automatically when running in dev mode
if (isDev) mainWindow.webContents.openDevTools();
mainWindow.webContents.on("render-process-gone", (event, details) => {
mainWindow.webContents.reload();
logErrorSentry(
Error("render-process-gone"),
"webContents event render-process-gone",
{ details },
);
ElectronLog.log("webContents event render-process-gone", details);
});
mainWindow.webContents.on("unresponsive", () => {
mainWindow.webContents.forcefullyCrashRenderer();
ElectronLog.log("webContents event unresponsive");
});
mainWindow.on("close", function (event) {
if (!isAppQuitting()) {
event.preventDefault();
mainWindow.hide();
}
return false;
});
mainWindow.on("hide", () => {
// On macOS, also hide the app's icon in the dock if the user has
// selected the Settings > Hide dock icon checkbox.
const shouldHideDockIcon = getHideDockIconPreference();
if (process.platform == "darwin" && shouldHideDockIcon) {
app.dock.hide();
}
});
mainWindow.on("show", () => {
if (process.platform == "darwin") app.dock.show();
});
return mainWindow;
};
export async function handleUpdates(mainWindow: BrowserWindow) {
const isInstalledViaBrew = await checkIfInstalledViaBrew();
if (!isDev && !isInstalledViaBrew) {
@ -116,3 +195,23 @@ export async function checkIfInstalledViaBrew() {
return false;
}
}
function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
const headers: Record<string, string[]> = {};
for (const key of Object.keys(responseHeaders)) {
headers[key.toLowerCase()] = responseHeaders[key];
}
return headers;
}
export function addAllowOriginHeader(mainWindow: BrowserWindow) {
mainWindow.webContents.session.webRequest.onHeadersReceived(
(details, callback) => {
details.responseHeaders = lowerCaseHeaders(details.responseHeaders);
details.responseHeaders["access-control-allow-origin"] = ["*"];
callback({
responseHeaders: details.responseHeaders,
});
},
);
}

View file

@ -4,6 +4,8 @@
*
* This file is meant as a sibling to `preload.ts`, but this one runs in the
* context of the main process, and can import other files from `src/`.
*
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
*/
import type { FSWatcher } from "chokidar";
@ -44,7 +46,7 @@ import type {
FILE_PATH_TYPE,
Model,
WatchMapping,
} from "../types";
} from "../types/ipc";
import {
selectDirectory,
showUploadDirsDialog,
@ -95,7 +97,7 @@ export const attachIPCHandlers = () => {
ipcMain.handle("openLogDirectory", (_) => openLogDirectory());
// See: [Note: Catching exception during .send/.on]
// See [Note: Catching exception during .send/.on]
ipcMain.on("logToDisk", (_, message) => logToDisk(message));
ipcMain.on("clear-electron-store", (_) => {

View file

@ -24,47 +24,43 @@
* file. However, since this is just boilerplate code providing a bridge between
* the main and renderer, we avoid introducing another moving part into the mix
* and just keep the entire preload setup in this single file.
*
* [Note: types.ts <-> preload.ts <-> ipc.ts]
*
* The following three files are boilerplatish linkage of the same functions,
* and when changing one of them, remember to see if the other two also need
* changing:
*
* - [renderer] web/packages/shared/electron/types.ts contains docs
* - [preload] desktop/src/preload.ts
* - [main] desktop/src/main/ipc.ts contains impl
*/
import { contextBridge, ipcRenderer } from "electron";
import type { ElectronFile } from "./types";
import { contextBridge, ipcRenderer } from "electron/renderer";
// TODO (MR): Uncomment and FIXME once preload is getting loaded.
// import { setupLogging } from "./main/log";
// setupLogging();
// While we can't import other code, we can import types since they're just
// needed when compiling and will not be needed / looked around for at runtime.
import type {
AppUpdateInfo,
ElectronFile,
FILE_PATH_TYPE,
Model,
WatchMapping,
} from "./types/ipc";
// - General
/** Return the version of the desktop app. */
const appVersion = (): Promise<string> => ipcRenderer.invoke("appVersion");
/**
* Open the given {@link dirPath} in the system's folder viewer.
*
* For example, on macOS this'll open {@link dirPath} in Finder.
*/
const openDirectory = (dirPath: string): Promise<void> =>
ipcRenderer.invoke("openDirectory");
/**
* Open the app's log directory in the system's folder viewer.
*
* @see {@link openDirectory}
*/
const openLogDirectory = (): Promise<void> =>
ipcRenderer.invoke("openLogDirectory");
/**
* Log the given {@link message} to the on-disk log file maintained by the
* desktop app.
*/
const logToDisk = (message: string): void =>
ipcRenderer.send("logToDisk", message);
/**
* Return true if there is a file or directory at the given
* {@link path}.
*/
const fsExists = (path: string): Promise<boolean> =>
ipcRenderer.invoke("fsExists", path);
@ -89,12 +85,6 @@ const getEncryptionKey = (): Promise<string> =>
// - App update
/* preload: duplicated */
interface AppUpdateInfo {
autoUpdatable: boolean;
version: string;
}
const registerUpdateEventListener = (
showUpdateDialog: (updateInfo: AppUpdateInfo) => void,
) => {
@ -152,12 +142,6 @@ const runFFmpegCmd = (
// - ML
/* preload: duplicated Model */
enum Model {
GGML_CLIP = "ggml-clip",
ONNX_CLIP = "onnx-clip",
}
const computeImageEmbedding = (
model: Model,
imageData: Uint8Array,
@ -222,22 +206,6 @@ const addWatchMapping = (
const removeWatchMapping = (folderPath: string): Promise<void> =>
ipcRenderer.invoke("removeWatchMapping", folderPath);
/* preload: duplicated WatchMappingSyncedFile */
interface WatchMappingSyncedFile {
path: string;
uploadedFileID: number;
collectionID: number;
}
/* preload: duplicated WatchMapping */
interface WatchMapping {
rootFolderName: string;
uploadStrategy: number;
folderPath: string;
syncedFiles: WatchMappingSyncedFile[];
ignoredFiles: string[];
}
const getWatchMappings = (): Promise<WatchMapping[]> =>
ipcRenderer.invoke("getWatchMappings");
@ -292,12 +260,6 @@ const getPendingUploads = (): Promise<{
type: string;
}> => ipcRenderer.invoke("getPendingUploads");
/* preload: duplicated FILE_PATH_TYPE */
enum FILE_PATH_TYPE {
FILES = "files",
ZIPS = "zips",
}
const setToUploadFiles = (
type: FILE_PATH_TYPE,
filePaths: string[],
@ -331,7 +293,7 @@ const getDirFiles = (dirPath: string): Promise<ElectronFile[]> =>
// Algorithm to serialize objects passed between processes.
// https://www.electronjs.org/docs/latest/tutorial/ipc#object-serialization
//
// In particular, both ArrayBuffer is eligible for structured cloning.
// In particular, ArrayBuffer is eligible for structured cloning.
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm
//
// Also, ArrayBuffer is "transferable", which means it is a zero-copy operation

View file

@ -4,7 +4,7 @@ import { default as ElectronLog, default as log } from "electron-log";
import { autoUpdater } from "electron-updater";
import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
import { logErrorSentry } from "../main/log";
import { AppUpdateInfo } from "../types";
import { AppUpdateInfo } from "../types/ipc";
import {
clearMuteUpdateNotificationVersion,
clearSkipAppVersion,

View file

@ -1,4 +1,4 @@
import { AutoLauncherClient } from "../types/autoLauncher";
import { AutoLauncherClient } from "../types/main";
import { isPlatform } from "../utils/common/platform";
import linuxAndWinAutoLauncher from "./autoLauncherClients/linuxAndWinAutoLauncher";
import macAutoLauncher from "./autoLauncherClients/macAutoLauncher";

View file

@ -1,6 +1,6 @@
import AutoLaunch from "auto-launch";
import { app } from "electron";
import { AutoLauncherClient } from "../../types/autoLauncher";
import { AutoLauncherClient } from "../../types/main";
const LAUNCHED_AS_HIDDEN_FLAG = "hidden";

View file

@ -1,5 +1,5 @@
import { app } from "electron";
import { AutoLauncherClient } from "../../types/autoLauncher";
import { AutoLauncherClient } from "../../types/main";
class MacAutoLauncher implements AutoLauncherClient {
async isEnabled() {

View file

@ -1,6 +1,6 @@
import chokidar from "chokidar";
import { BrowserWindow } from "electron";
import * as path from "path";
import path from "path";
import { logError } from "../main/log";
import { getWatchMappings } from "../services/watch";
import { getElectronFile } from "./fs";

View file

@ -1,14 +1,14 @@
import * as log from "electron-log";
import log from "electron-log";
import { app, net } from "electron/main";
import { existsSync } from "fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import fs from "node:fs/promises";
import path from "node:path";
import util from "util";
import { CustomErrors } from "../constants/errors";
import { writeStream } from "../main/fs";
import { isDev } from "../main/general";
import { logErrorSentry } from "../main/log";
import { Model } from "../types";
import { Model } from "../types/ipc";
import Tokenizer from "../utils/clip-bpe-ts/mod";
import { getPlatform } from "../utils/common/platform";
import { generateTempFilePath } from "../utils/temp";

View file

@ -1,12 +1,12 @@
import log from "electron-log";
import pathToFfmpeg from "ffmpeg-static";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import fs from "node:fs/promises";
import util from "util";
import { CustomErrors } from "../constants/errors";
import { writeStream } from "../main/fs";
import { logError, logErrorSentry } from "../main/log";
import { ElectronFile } from "../types";
import { ElectronFile } from "../types/ipc";
import { generateTempFilePath, getTempDirPath } from "../utils/temp";
const shellescape = require("any-shell-escape");

View file

@ -1,9 +1,9 @@
import StreamZip from "node-stream-zip";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import fs from "node:fs/promises";
import path from "node:path";
import { logError } from "../main/log";
import { ElectronFile } from "../types";
import { ElectronFile } from "../types/ipc";
const FILE_STREAM_CHUNK_SIZE: number = 4 * 1024 * 1024;

View file

@ -1,14 +1,14 @@
import { exec } from "child_process";
import log from "electron-log";
import { existsSync } from "fs";
import * as fs from "node:fs/promises";
import fs from "node:fs/promises";
import path from "path";
import util from "util";
import { CustomErrors } from "../constants/errors";
import { writeStream } from "../main/fs";
import { isDev } from "../main/general";
import { logError, logErrorSentry } from "../main/log";
import { ElectronFile } from "../types";
import { ElectronFile } from "../types/ipc";
import { isPlatform } from "../utils/common/platform";
import { generateTempFilePath } from "../utils/temp";
import { deleteTempFile } from "./ffmpeg";

View file

@ -1,7 +1,8 @@
import StreamZip from "node-stream-zip";
import path from "path";
import { uploadStatusStore } from "../stores/upload.store";
import { ElectronFile, FILE_PATH_KEYS, FILE_PATH_TYPE } from "../types";
import { ElectronFile, FILE_PATH_TYPE } from "../types/ipc";
import { FILE_PATH_KEYS } from "../types/main";
import { getValidPaths, getZipFileStream } from "./fs";
export const getSavedFilePaths = (type: FILE_PATH_TYPE) => {

View file

@ -1,7 +1,7 @@
import type { FSWatcher } from "chokidar";
import ElectronLog from "electron-log";
import { watchStore } from "../stores/watch.store";
import { WatchMapping, WatchStoreType } from "../types";
import { WatchMapping, WatchStoreType } from "../types/ipc";
import { isMappingPresent } from "../utils/watch";
export const addWatchMapping = async (

View file

@ -1,5 +1,5 @@
import Store, { Schema } from "electron-store";
import { KeysStoreType } from "../types";
import type { KeysStoreType } from "../types/main";
const keysStoreSchema: Schema<KeysStoreType> = {
AnonymizeUserID: {

View file

@ -1,5 +1,5 @@
import Store, { Schema } from "electron-store";
import { SafeStorageStoreType } from "../types";
import type { SafeStorageStoreType } from "../types/main";
const safeStorageSchema: Schema<SafeStorageStoreType> = {
encryptionKey: {

View file

@ -1,5 +1,5 @@
import Store, { Schema } from "electron-store";
import { UploadStoreType } from "../types";
import type { UploadStoreType } from "../types/main";
const uploadStoreSchema: Schema<UploadStoreType> = {
filePaths: {

View file

@ -1,5 +1,5 @@
import Store, { Schema } from "electron-store";
import { UserPreferencesType } from "../types";
import type { UserPreferencesType } from "../types/main";
const userPreferencesSchema: Schema<UserPreferencesType> = {
hideDockIcon: {

View file

@ -1,5 +1,5 @@
import Store, { Schema } from "electron-store";
import { WatchStoreType } from "../types";
import { WatchStoreType } from "../types/ipc";
const watchStoreSchema: Schema<WatchStoreType> = {
mappings: {

View file

@ -1,5 +0,0 @@
export interface AutoLauncherClient {
isEnabled: () => Promise<boolean>;
toggleAutoLaunch: () => Promise<void>;
wasAutoLaunched: () => Promise<boolean>;
}

View file

@ -1,3 +1,9 @@
/**
* @file types that are shared across the IPC boundary with the renderer process
*
* This file is manually kept in sync with the renderer code.
* See [Note: types.ts <-> preload.ts <-> ipc.ts]
*/
/**
* Deprecated - Use File + webUtils.getPathForFile instead
*
@ -20,18 +26,6 @@ export interface ElectronFile {
arrayBuffer: () => Promise<Uint8Array>;
}
export interface UploadStoreType {
filePaths: string[];
zipPaths: string[];
collectionName: string;
}
export interface KeysStoreType {
AnonymizeUserID: {
id: string;
};
}
interface WatchMappingSyncedFile {
path: string;
uploadedFileID: number;
@ -55,23 +49,6 @@ export enum FILE_PATH_TYPE {
ZIPS = "zips",
}
export const FILE_PATH_KEYS: {
[k in FILE_PATH_TYPE]: keyof UploadStoreType;
} = {
[FILE_PATH_TYPE.ZIPS]: "zipPaths",
[FILE_PATH_TYPE.FILES]: "filePaths",
};
export interface SafeStorageStoreType {
encryptionKey: string;
}
export interface UserPreferencesType {
hideDockIcon: boolean;
skipAppVersion: string;
muteUpdateNotificationVersion: string;
}
export interface AppUpdateInfo {
autoUpdatable: boolean;
version: string;

36
desktop/src/types/main.ts Normal file
View file

@ -0,0 +1,36 @@
import { FILE_PATH_TYPE } from "./ipc";
export interface AutoLauncherClient {
isEnabled: () => Promise<boolean>;
toggleAutoLaunch: () => Promise<void>;
wasAutoLaunched: () => Promise<boolean>;
}
export interface UploadStoreType {
filePaths: string[];
zipPaths: string[];
collectionName: string;
}
export interface KeysStoreType {
AnonymizeUserID: {
id: string;
};
}
export const FILE_PATH_KEYS: {
[k in FILE_PATH_TYPE]: keyof UploadStoreType;
} = {
[FILE_PATH_TYPE.ZIPS]: "zipPaths",
[FILE_PATH_TYPE.FILES]: "filePaths",
};
export interface SafeStorageStoreType {
encryptionKey: string;
}
export interface UserPreferencesType {
hideDockIcon: boolean;
skipAppVersion: string;
muteUpdateNotificationVersion: string;
}

View file

@ -1,21 +0,0 @@
import { BrowserWindow } from "electron";
function lowerCaseHeaders(responseHeaders: Record<string, string[]>) {
const headers: Record<string, string[]> = {};
for (const key of Object.keys(responseHeaders)) {
headers[key.toLowerCase()] = responseHeaders[key];
}
return headers;
}
export function addAllowOriginHeader(mainWindow: BrowserWindow) {
mainWindow.webContents.session.webRequest.onHeadersReceived(
(details, callback) => {
details.responseHeaders = lowerCaseHeaders(details.responseHeaders);
details.responseHeaders["access-control-allow-origin"] = ["*"];
callback({
responseHeaders: details.responseHeaders,
});
},
);
}

View file

@ -1,110 +0,0 @@
import { app, BrowserWindow, nativeImage } from "electron";
import ElectronLog from "electron-log";
import * as path from "path";
import { isAppQuitting, rendererURL } from "../main";
import autoLauncher from "../services/autoLauncher";
import { logErrorSentry } from "../main/log";
import { getHideDockIconPreference } from "../services/userPreference";
import { isDev } from "../main/general";
import { isPlatform } from "./common/platform";
/**
* Create an return the {@link BrowserWindow} that will form our app's UI.
*
* This window will show the HTML served from {@link rendererURL}.
*/
export const createWindow = async () => {
const appImgPath = isDev
? "resources/window-icon.png"
: path.join(process.resourcesPath, "window-icon.png");
const appIcon = nativeImage.createFromPath(appImgPath);
// Create the browser window.
const mainWindow = new BrowserWindow({
webPreferences: {
preload: path.join(__dirname, "../preload.js"),
},
icon: appIcon,
show: false, // don't show the main window on load,
});
const wasAutoLaunched = await autoLauncher.wasAutoLaunched();
ElectronLog.log("wasAutoLaunched", wasAutoLaunched);
const splash = new BrowserWindow({
transparent: true,
show: false,
});
if (isPlatform("mac") && wasAutoLaunched) {
app.dock.hide();
}
if (!wasAutoLaunched) {
splash.maximize();
splash.show();
}
if (isDev) {
splash.loadFile(`../resources/splash.html`);
mainWindow.loadURL(rendererURL);
// Open the DevTools.
mainWindow.webContents.openDevTools();
} else {
splash.loadURL(
`file://${path.join(process.resourcesPath, "splash.html")}`,
);
mainWindow.loadURL(rendererURL);
}
mainWindow.once("ready-to-show", async () => {
try {
splash.destroy();
if (!wasAutoLaunched) {
mainWindow.maximize();
mainWindow.show();
}
} catch (e) {
// ignore
}
});
mainWindow.webContents.on("render-process-gone", (event, details) => {
mainWindow.webContents.reload();
logErrorSentry(
Error("render-process-gone"),
"webContents event render-process-gone",
{ details },
);
ElectronLog.log("webContents event render-process-gone", details);
});
mainWindow.webContents.on("unresponsive", () => {
mainWindow.webContents.forcefullyCrashRenderer();
ElectronLog.log("webContents event unresponsive");
});
setTimeout(() => {
try {
splash.destroy();
if (!wasAutoLaunched) {
mainWindow.maximize();
mainWindow.show();
}
} catch (e) {
// ignore
}
}, 2000);
mainWindow.on("close", function (event) {
if (!isAppQuitting()) {
event.preventDefault();
mainWindow.hide();
}
return false;
});
mainWindow.on("hide", () => {
const shouldHideDockIcon = getHideDockIconPreference();
if (isPlatform("mac") && shouldHideDockIcon) {
app.dock.hide();
}
});
mainWindow.on("show", () => {
if (isPlatform("mac")) {
app.dock.show();
}
});
return mainWindow;
};

View file

@ -1,6 +1,6 @@
import { app } from "electron/main";
import { existsSync } from "node:fs";
import * as fs from "node:fs/promises";
import fs from "node:fs/promises";
import path from "path";
const CHARACTERS =

View file

@ -1,4 +1,4 @@
import { WatchMapping } from "../types";
import { WatchMapping } from "../types/ipc";
export function isMappingPresent(
watchMappings: WatchMapping[],

View file

@ -48,14 +48,6 @@
/* Emit the generated JS into `app/` */
"outDir": "app",
/* Generate source maps */
"sourceMap": true,
/* Allow absolute imports starting with src as root */
"baseUrl": "src",
/* Allow imports of paths from node_modules */
"paths": {
"*": ["node_modules/*"]
},
/* Temporary overrides to get things to compile with the older config */
"strict": false,

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
# Dependencies
## DX
## Dev
These are some global dev dependencies in the root `package.json`. These set the
baseline for how our code be in all the workspaces in this (yarn) monorepo.

View file

@ -1,5 +1,10 @@
import { ElectronFile } from "@ente/shared/upload/types";
import { WatchMapping } from "@ente/shared/watchFolder/types";
// Following are types shared with the Electron process. This list is manually
// kept in sync with `desktop/src/types/ipc.ts`.
//
// See [Note: types.ts <-> preload.ts <-> ipc.ts]
import type { ElectronFile } from "@ente/shared/upload/types";
import type { WatchMapping } from "@ente/shared/watchFolder/types";
export interface AppUpdateInfo {
autoUpdatable: boolean;
@ -26,7 +31,8 @@ export enum PICKED_UPLOAD_TYPE {
* Extra APIs provided by the Node.js layer when our code is running in Electron
*
* This list is manually kept in sync with `desktop/src/preload.ts`. In case of
* a mismatch, the types may lie.
* a mismatch, the types may lie. See also: [Note: types.ts <-> preload.ts <->
* ipc.ts]
*
* These extra objects and functions will only be available when our code is
* running as the renderer process in Electron. So something in the code path
@ -57,7 +63,7 @@ export interface ElectronAPIsType {
* desktop app.
*
* Note: Unlike the other functions exposed over the Electron bridge,
* logToDisk is fire-and-forge and does not return a promise.
* logToDisk is fire-and-forget and does not return a promise.
*/
logToDisk: (message: string) => void;