diff --git a/app/config.ts b/app/config.ts
index f38515e1c..512f1b981 100644
--- a/app/config.ts
+++ b/app/config.ts
@@ -38,6 +38,7 @@ if (getEnvironment() === Environment.PackagedApp) {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '';
process.env.SIGNAL_ENABLE_HTTP = '';
process.env.SIGNAL_CI_CONFIG = '';
+ process.env.GENERATE_PRELOAD_CACHE = '';
}
// We load config after we've made our modifications to NODE_ENV
diff --git a/app/main.ts b/app/main.ts
index 587fd87c9..67d649188 100644
--- a/app/main.ts
+++ b/app/main.ts
@@ -2567,6 +2567,9 @@ ipc.on('restart', () => {
app.quit();
});
ipc.on('shutdown', () => {
+ if (process.env.GENERATE_PRELOAD_CACHE) {
+ windowState.markReadyForShutdown();
+ }
app.quit();
});
diff --git a/package.json b/package.json
index 540cc597a..b3448225c 100644
--- a/package.json
+++ b/package.json
@@ -85,7 +85,7 @@
"build:esbuild:prod": "node scripts/esbuild.js --prod",
"build:electron": "electron-builder --config.extraMetadata.environment=$SIGNAL_ENV",
"build:release": "cross-env SIGNAL_ENV=production npm run build:electron -- --config.directories.output=release",
- "build:preload-cache": "electron --js-args=\"--predictable --random-seed 1\" ts/scripts/generate-preload-cache.js",
+ "build:preload-cache": "node ts/scripts/generate-preload-cache.js",
"verify": "run-p --print-label verify:*",
"verify:ts": "tsc --noEmit",
"electron:install-app-deps": "electron-builder install-app-deps"
diff --git a/preload.wrapper.ts b/preload.wrapper.ts
index 7773a54ce..d7e83ea9e 100644
--- a/preload.wrapper.ts
+++ b/preload.wrapper.ts
@@ -5,9 +5,10 @@
// https://github.com/zertosh/v8-compile-cache/blob/b6bc035d337fbda0e6e3ec7936499048fc9deafc/v8-compile-cache.js
import { Module } from 'node:module';
-import { readFileSync } from 'node:fs';
+import { readFileSync, writeFileSync } from 'node:fs';
import { join, dirname } from 'node:path';
import { Script } from 'node:vm';
+import { ipcRenderer } from 'electron';
const srcPath = join(__dirname, 'preload.bundle.js');
const cachePath = join(__dirname, 'preload.bundle.cache');
@@ -22,6 +23,8 @@ try {
}
}
+let script: Script | undefined;
+
function compile(
filename: string,
content: string
@@ -31,7 +34,7 @@ function compile(
// create wrapper function
const wrapper = Module.wrap(content);
- const script = new Script(wrapper, {
+ script = new Script(wrapper, {
filename,
lineOffset: 0,
cachedData,
@@ -124,3 +127,9 @@ ModuleInternals.prototype._compile = function _compile(
// eslint-disable-next-line import/no-dynamic-require
require(srcPath);
+
+// See `ts/scripts/generate-preload-cache.ts`
+if (script && process.env.GENERATE_PRELOAD_CACHE) {
+ writeFileSync(cachePath, script.createCachedData());
+ ipcRenderer.send('shutdown');
+}
diff --git a/ts/scripts/generate-preload-cache.html b/ts/scripts/generate-preload-cache.html
deleted file mode 100644
index 9e17b3325..000000000
--- a/ts/scripts/generate-preload-cache.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
- Generating cache...
-
-
- Generating cache...
-
-
diff --git a/ts/scripts/generate-preload-cache.preload.ts b/ts/scripts/generate-preload-cache.preload.ts
deleted file mode 100644
index 51122245f..000000000
--- a/ts/scripts/generate-preload-cache.preload.ts
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright 2024 Signal Messenger, LLC
-// SPDX-License-Identifier: AGPL-3.0-only
-
-import { Module } from 'node:module';
-import { readFile, writeFile } from 'node:fs/promises';
-import { join } from 'node:path';
-import { Script } from 'node:vm';
-import { ipcRenderer } from 'electron';
-
-ipcRenderer.on('compile', async () => {
- try {
- const sourceFile = join(__dirname, '..', '..', 'preload.bundle.js');
- const outFile = sourceFile.replace(/\.js$/, '');
-
- const source = await readFile(sourceFile, 'utf8');
- const script = new Script(Module.wrap(source), {
- filename: 'preload.bundle.js',
- produceCachedData: true,
- });
- if (!script.cachedDataProduced || !script.cachedData) {
- throw new Error('Cached data not produced');
- }
-
- await writeFile(`${outFile}.cache`, script.cachedData);
- await ipcRenderer.invoke('done');
- } catch (error) {
- await ipcRenderer.invoke('error', error);
- }
-});
diff --git a/ts/scripts/generate-preload-cache.ts b/ts/scripts/generate-preload-cache.ts
index fdd25fde9..93adb6b66 100644
--- a/ts/scripts/generate-preload-cache.ts
+++ b/ts/scripts/generate-preload-cache.ts
@@ -1,35 +1,51 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
-import { pathToFileURL } from 'node:url';
+import { spawnSync } from 'node:child_process';
import { join } from 'node:path';
-import { app, BrowserWindow, ipcMain } from 'electron';
+import { tmpdir } from 'node:os';
+import { mkdtemp, rm } from 'node:fs/promises';
-app.on('ready', async () => {
- ipcMain.handle('done', () => {
- app.quit();
- });
+const ROOT_DIR = join(__dirname, '..', '..');
- ipcMain.handle('error', (_event, err) => {
- console.error(err);
- process.exit(1);
- });
+const ELECTRON = join(
+ ROOT_DIR,
+ 'node_modules',
+ '.bin',
+ process.platform === 'win32' ? 'electron.cmd' : 'electron'
+);
- const window = new BrowserWindow({
- show: false,
- webPreferences: {
- devTools: true,
- nodeIntegration: false,
- sandbox: false,
- contextIsolation: true,
- preload: join(__dirname, 'generate-preload-cache.preload.js'),
- },
- });
+async function main(): Promise {
+ const storagePath = await mkdtemp(join(tmpdir(), 'signal-preload-cache-'));
- await window.loadURL(
- pathToFileURL(join(__dirname, 'generate-preload-cache.html')).toString()
- );
+ let status: number | null;
+ try {
+ ({ status } = spawnSync(
+ ELECTRON,
+ ['--js-args="--predictable --random-seed 1"', 'ci.js'],
+ {
+ cwd: ROOT_DIR,
+ env: {
+ ...process.env,
+ GENERATE_PRELOAD_CACHE: 'on',
+ SIGNAL_CI_CONFIG: JSON.stringify({
+ storagePath,
+ }),
+ },
+ // Since we run `.cmd` file on Windows - use shell
+ shell: process.platform === 'win32',
+ }
+ ));
+ } finally {
+ await rm(storagePath, { recursive: true });
+ }
- window.webContents.openDevTools();
- window.webContents.send('compile', process.argv[2], process.argv[3]);
+ if (status !== 0) {
+ throw new Error(`Exit code: ${status}`);
+ }
+}
+
+main().catch(error => {
+ console.error(error);
+ process.exit(1);
});