[photos-desktop] Document a bit as I find my way around disabling nodeIntegration (#1170)

* Also includes an unrelated change to update the support email.
* See corresponding commit messages for more details about why specific
bits of code were removed.
This commit is contained in:
Manav Rathi 2024-03-21 12:19:24 +05:30 committed by GitHub
commit 4c33030f28
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 106 additions and 96 deletions

View file

@ -10,6 +10,12 @@ To know more about Ente, see [our main README](../README.md) or visit
## Building from source
> [!CAUTION]
>
> We're improving the security of the desktop app further by migrating to
> Electron's sandboxing and contextIsolation. These updates are still WIP and
> meanwhile the instructions below might not fully work on the main branch.
Fetch submodules
```sh
@ -28,11 +34,6 @@ Run in development mode (with hot reload)
yarn dev
```
> [!CAUTION]
>
> `yarn dev` is currently not working (we'll fix soon). If you just want to
> build from source and use the generated binary, use `yarn build`.
Or create a binary for your platform
```sh

View file

@ -1,24 +0,0 @@
<!doctype html>
<html>
<head>
<title>Electron Updater Example</title>
</head>
<body>
Current version: <span id="version">vX.Y.Z</span>
<div id="messages"></div>
<script>
// Display the current version
let version = window.location.hash.substring(1);
document.getElementById("version").innerText = version;
// Listen for messages
const { ipcRenderer } = require("electron");
ipcRenderer.on("message", function (event, text) {
var container = document.getElementById("messages");
var message = document.createElement("div");
message.innerHTML = text;
container.appendChild(message);
});
</script>
</body>
</html>

View file

@ -5,36 +5,23 @@ nsis:
linux:
target:
- target: AppImage
arch:
- x64
- arm64
arch: [x64, arm64]
- target: deb
arch:
- x64
- arm64
arch: [x64, arm64]
- target: rpm
arch:
- x64
- arm64
arch: [x64, arm64]
- target: pacman
arch:
- x64
- arm64
arch: [x64, arm64]
icon: ./resources/icon.icns
category: Photography
mac:
target:
target: default
arch:
- universal
arch: [universal]
category: public.app-category.photography
hardenedRuntime: true
x64ArchFiles: Contents/Resources/ggmlclip-mac
afterSign: electron-builder-notarize
asarUnpack:
- node_modules/ffmpeg-static/bin/${os}/${arch}/ffmpeg
- node_modules/ffmpeg-static/index.js
- node_modules/ffmpeg-static/package.json
extraFiles:
- from: build
to: resources

View file

@ -370,7 +370,45 @@ setupLogging();
// These objects exposed here will become available to the JS code in our
// renderer (the web/ code) as `window.ElectronAPIs.*`
//
// https://www.electronjs.org/docs/latest/tutorial/tutorial-preload
// - Introduction
// https://www.electronjs.org/docs/latest/tutorial/tutorial-preload
//
// There are a few related concepts at play here, and it might be worthwhile to
// read their (excellent) documentation to get an understanding;
//
// - ContextIsolation:
// https://www.electronjs.org/docs/latest/tutorial/context-isolation
//
// - IPC https://www.electronjs.org/docs/latest/tutorial/ipc
//
//
// [Note: Transferring large amount of data over IPC]
//
// Electron's IPC implementation uses the HTML standard Structured Clone
// Algorithm to serialize objects passed between processes.
// https://www.electronjs.org/docs/latest/tutorial/ipc#object-serialization
//
// In particular, both ArrayBuffer and the web File types are 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
// operation when it happens across threads.
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Transferable_objects
//
// In our case though, we're not dealing with threads but separate processes,
// and it seems like there is a copy involved since the documentation for
// contextBridge explicitly calls out that "parameters, errors and return values
// are **copied** when they're sent over the bridge".
// https://www.electronjs.org/docs/latest/api/context-bridge#methods
//
// Related is a note from one of Electron's committers stating that even with
// copying, the IPC should be fast enough for even moderately large data:
// https://github.com/electron/electron/issues/1948#issuecomment-864191345
//
// The main problem with transfering large amounts of data is potentially
// running out of memory, causing the app to crash as it copies it over across
// the processes.
contextBridge.exposeInMainWorld("ElectronAPIs", {
exists,
checkExistsAndCreateDir,

View file

@ -1,6 +1,5 @@
import crypto from "crypto";
import path from "path";
import { existsSync, rename, stat, unlink } from "promise-fs";
import { existsSync, stat, unlink } from "promise-fs";
import DiskLRUService from "../services/diskLRU";
import { LimitedCache } from "../types/cache";
import { getFileStream, writeStream } from "./fs";
@ -44,28 +43,6 @@ export class DiskCache implements LimitedCache {
DiskLRUService.touch(cachePath);
return new Response(await getFileStream(cachePath));
} else {
// add fallback for old cache keys
const oldCachePath = getOldAssetCachePath(
this.cacheBucketDir,
cacheKey,
);
if (existsSync(oldCachePath)) {
const fileStats = await stat(oldCachePath);
if (sizeInBytes && fileStats.size !== sizeInBytes) {
logError(
Error(),
"Old cache key exists but size does not match. Deleting cache key.",
);
unlink(oldCachePath).catch((e) => {
if (e.code === "ENOENT") return;
logError(e, "Failed to delete cache key");
});
return undefined;
}
const match = new Response(await getFileStream(oldCachePath));
void migrateOldCacheKey(oldCachePath, cachePath);
return match;
}
return undefined;
}
}
@ -79,20 +56,3 @@ export class DiskCache implements LimitedCache {
}
}
}
function getOldAssetCachePath(cacheDir: string, cacheKey: string) {
// hashing the key to prevent illegal filenames
const cacheKeyHash = crypto
.createHash("sha256")
.update(cacheKey)
.digest("hex");
return path.join(cacheDir, cacheKeyHash);
}
async function migrateOldCacheKey(oldCacheKey: string, newCacheKey: string) {
try {
await rename(oldCacheKey, newCacheKey);
} catch (e) {
logError(e, "Failed to move cache key to new cache key");
}
}

View file

@ -16,10 +16,33 @@ const INPUT_PATH_PLACEHOLDER = "INPUT";
const FFMPEG_PLACEHOLDER = "FFMPEG";
const OUTPUT_PATH_PLACEHOLDER = "OUTPUT";
function getFFmpegStaticPath() {
return pathToFfmpeg.replace("app.asar", "app.asar.unpacked");
}
/**
* Run a ffmpeg command
*
* [Note: FFMPEG in Electron]
*
* There is a wasm build of FFMPEG, but that is currently 10-20 times slower
* that the native build. That is slow enough to be unusable for our purposes.
* https://ffmpegwasm.netlify.app/docs/performance
*
* So the alternative is to bundle a ffmpeg binary with our app. e.g.
*
* yarn add fluent-ffmpeg ffmpeg-static ffprobe-static
*
* (we only use ffmpeg-static, the rest are mentioned for completeness' sake).
*
* Interestingly, Electron already bundles an ffmpeg library (it comes from the
* ffmpeg fork maintained by Chromium).
* https://chromium.googlesource.com/chromium/third_party/ffmpeg
* https://stackoverflow.com/questions/53963672/what-version-of-ffmpeg-is-bundled-inside-electron
*
* This can be found in (e.g. on macOS) at
*
* $ file ente.app/Contents/Frameworks/Electron\ Framework.framework/Versions/Current/Libraries/libffmpeg.dylib
* .../libffmpeg.dylib: Mach-O 64-bit dynamically linked shared library arm64
*
* I'm not sure if our code is supposed to be able to use it, and how.
*/
export async function runFFmpegCmd(
cmd: string[],
inputFilePath: string,
@ -32,7 +55,7 @@ export async function runFFmpegCmd(
cmd = cmd.map((cmdPart) => {
if (cmdPart === FFMPEG_PLACEHOLDER) {
return getFFmpegStaticPath();
return ffmpegBinaryPath();
} else if (cmdPart === INPUT_PATH_PLACEHOLDER) {
return inputFilePath;
} else if (cmdPart === OUTPUT_PATH_PLACEHOLDER) {
@ -76,6 +99,19 @@ export async function runFFmpegCmd(
}
}
/**
* Return the path to the `ffmpeg` binary.
*
* At runtime, the ffmpeg binary is present in a path like (macOS example):
* `ente.app/Contents/Resources/app.asar.unpacked/node_modules/ffmpeg-static/ffmpeg`
*/
const ffmpegBinaryPath = () => {
// This substitution of app.asar by app.asar.unpacked is suggested by the
// ffmpeg-static library author themselves:
// https://github.com/eugeneware/ffmpeg-static/issues/16
return pathToFfmpeg.replace("app.asar", "app.asar.unpacked");
};
export async function writeTempFile(fileStream: Uint8Array, fileName: string) {
const tempFilePath = await generateTempFilePath(fileName);
await writeFile(tempFilePath, fileStream);

View file

@ -1,3 +1,15 @@
/**
* Deprecated - Use File + webUtils.getPathForFile instead
*
* Electron used to augment the standard web
* [File](https://developer.mozilla.org/en-US/docs/Web/API/File) object with an
* additional `path` property. This is now deprecated, and will be removed in a
* future release.
* https://www.electronjs.org/docs/latest/api/file-object
*
* The alternative to the `path` property is to use `webUtils.getPathForFile`
* https://www.electronjs.org/docs/latest/api/web-utils
*/
export interface ElectronFile {
name: string;
path: string;

View file

@ -46,9 +46,9 @@ export default function HelpSection() {
variant="secondary"
/>
<EnteMenuItem
onClick={() => openLink("mailto:contact@ente.io", true)}
onClick={() => openLink("mailto:support@ente.io", true)}
labelComponent={
<NoStyleAnchor href="mailto:contact@ente.io">
<NoStyleAnchor href="mailto:support@ente.io">
<Typography fontWeight={"bold"}>
{t("SUPPORT")}
</Typography>