Remove use of __dirname from main process

This commit is contained in:
Fedor Indutny
2026-03-27 10:55:37 -07:00
committed by GitHub
parent 70f111e868
commit a048f83dbc
39 changed files with 222 additions and 337 deletions

View File

@@ -11,8 +11,9 @@ import type { OptionalResourceService } from './OptionalResourceService.main.js'
import { SignalService as Proto } from '../ts/protobuf/index.std.js';
import { parseUnknown } from '../ts/util/schemas.std.js';
import { utf16ToEmoji } from '../ts/util/utf16ToEmoji.node.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
const MANIFEST_PATH = join(__dirname, '..', 'build', 'jumbomoji.json');
const MANIFEST_PATH = join(getAppRootDir(), 'build', 'jumbomoji.json');
const manifestSchema = z.record(z.string(), z.string().array());

View File

@@ -15,15 +15,15 @@ import type {
} from '../ts/types/OptionalResource.std.js';
import { OptionalResourcesDictSchema } from '../ts/types/OptionalResource.std.js';
import { createLogger } from '../ts/logging/log.std.js';
import { getGotOptions } from '../ts/updater/got.node.js';
import { getGotOptions } from '../ts/updater/got.main.js';
import { drop } from '../ts/util/drop.std.js';
import { parseUnknown } from '../ts/util/schemas.std.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
const log = createLogger('OptionalResourceService');
const RESOURCES_DICT_PATH = join(
__dirname,
'..',
getAppRootDir(),
'build',
'optional-resources.json'
);

View File

@@ -8,6 +8,7 @@ import { join } from 'node:path';
import { readFileSync } from 'node:fs';
import { createLogger } from '../ts/logging/log.std.js';
import type { LocalizerType } from '../ts/types/I18N.std.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
const log = createLogger('SystemTrayService');
@@ -285,8 +286,7 @@ function getTrayIconImagePath(size: number, unreadCount: number): string {
}
const iconPath = join(
__dirname,
'..',
getAppRootDir(),
'images',
'tray-icons',
dirName,

View File

@@ -1,7 +1,7 @@
// Copyright 2017 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { ipcMain as ipc, app } from 'electron';
import { ipcMain as ipc } from 'electron';
import type { IpcMainInvokeEvent } from 'electron';
import { join } from 'node:path';
import { Worker } from 'node:worker_threads';
@@ -15,6 +15,7 @@ import type {
import { WindowsNotificationDataSchema } from '../ts/types/notifications.std.js';
import OS from '../ts/util/os/osMain.node.js';
import { createLogger } from '../ts/logging/log.std.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
const log = createLogger('WindowsNotifications');
@@ -22,7 +23,7 @@ let worker: Worker | undefined;
if (OS.isWindows()) {
const scriptPath = join(
app.getAppPath(),
getAppRootDir(),
'app',
'WindowsNotificationsWorker.node.js'
);

View File

@@ -13,6 +13,7 @@ import {
parseEnvironment,
} from '../ts/environment.std.js';
import { createLogger } from '../ts/logging/log.std.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
const log = createLogger('config');
@@ -28,7 +29,6 @@ if (app.isPackaged) {
// Set environment vars to configure node-config before requiring it
process.env.NODE_ENV = getEnvironment();
process.env.NODE_CONFIG_DIR = join(__dirname, '..', 'config');
if (getEnvironment() === Environment.PackagedApp) {
// harden production config against the local env
@@ -43,8 +43,12 @@ if (getEnvironment() === Environment.PackagedApp) {
process.env.SIGNAL_CI_CONFIG = '';
process.env.GENERATE_PRELOAD_CACHE = '';
process.env.REACT_DEVTOOLS = '';
process.env.IS_BUNDLED = '1';
}
// Call `getAppRootDir()` after hardening since it relies on env variables
process.env.NODE_CONFIG_DIR = join(getAppRootDir(), 'config');
// We load config after we've made our modifications to NODE_ENV
// Note: we use `require()` because esbuild moves the imports to the top of
// the module regardless of their actual placement in the file.

View File

@@ -6,6 +6,7 @@ import { readFile } from 'node:fs/promises';
import { DNSFallbackSchema } from '../ts/types/DNSFallback.std.js';
import type { DNSFallbackType } from '../ts/types/DNSFallback.std.js';
import { parseUnknown } from '../ts/util/schemas.std.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
import { createLogger } from '../ts/logging/log.std.js';
const log = createLogger('dns-fallback');
@@ -17,7 +18,7 @@ export async function getDNSFallback(): Promise<DNSFallbackType> {
return cached;
}
const configPath = join(__dirname, '..', 'build', 'dns-fallback.json');
const configPath = join(getAppRootDir(), 'build', 'dns-fallback.json');
let str: string;
try {
str = await readFile(configPath, 'utf8');

View File

@@ -20,19 +20,25 @@ const { merge } = lodash;
type CompactLocaleMessagesType = ReadonlyArray<string | null>;
type CompactLocaleKeysType = ReadonlyArray<string>;
function getLocaleMessages(locale: string): LocaleMessagesType {
const targetFile = join(__dirname, '..', '_locales', locale, 'messages.json');
function getLocaleMessages(
rootDir: string,
locale: string
): LocaleMessagesType {
const targetFile = join(rootDir, '_locales', locale, 'messages.json');
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
function getCompactLocaleKeys(): CompactLocaleKeysType {
const targetFile = join(__dirname, '..', '_locales', 'keys.json');
function getCompactLocaleKeys(rootDir: string): CompactLocaleKeysType {
const targetFile = join(rootDir, '_locales', 'keys.json');
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
function getCompactLocaleValues(locale: string): CompactLocaleMessagesType {
const targetFile = join(__dirname, '..', '_locales', locale, 'values.json');
function getCompactLocaleValues(
rootDir: string,
locale: string
): CompactLocaleMessagesType {
const targetFile = join(rootDir, '_locales', locale, 'values.json');
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
@@ -40,23 +46,13 @@ function getCompactLocaleValues(locale: string): CompactLocaleMessagesType {
export type LocaleDisplayNames = Record<string, Record<string, string>>;
export type CountryDisplayNames = Record<string, Record<string, string>>;
function getLocaleDisplayNames(): LocaleDisplayNames {
const targetFile = join(
__dirname,
'..',
'build',
'locale-display-names.json'
);
function getLocaleDisplayNames(rootDir: string): LocaleDisplayNames {
const targetFile = join(rootDir, 'build', 'locale-display-names.json');
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
function getCountryDisplayNames(): CountryDisplayNames {
const targetFile = join(
__dirname,
'..',
'build',
'country-display-names.json'
);
function getCountryDisplayNames(rootDir: string): CountryDisplayNames {
const targetFile = join(rootDir, 'build', 'country-display-names.json');
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
@@ -77,16 +73,14 @@ function getLocaleDirection(localeName: string): LocaleDirection {
return new Intl.Locale(localeName).getTextInfo().direction ?? 'ltr';
}
export function _getAvailableLocales(): Array<string> {
export function _getAvailableLocales(rootDir: string): Array<string> {
return JSON.parse(
readFileSync(
join(__dirname, '..', 'build', 'available-locales.json'),
'utf-8'
)
readFileSync(join(rootDir, 'build', 'available-locales.json'), 'utf-8')
) as Array<string>;
}
export function load({
rootDir,
hourCyclePreference,
isPackaged,
localeDirectionTestingOverride,
@@ -94,6 +88,7 @@ export function load({
logger,
preferredSystemLocales,
}: {
rootDir: string;
hourCyclePreference: HourCyclePreference;
isPackaged: boolean;
localeDirectionTestingOverride: LocaleDirection | null;
@@ -109,7 +104,7 @@ export function load({
logger.warn('locale: `preferredSystemLocales` was empty');
}
const availableLocales = _getAvailableLocales();
const availableLocales = _getAvailableLocales(rootDir);
logger.info('locale: Supported locales:', availableLocales.join(', '));
logger.info('locale: Preferred locales:', preferredSystemLocales.join(', '));
@@ -124,14 +119,17 @@ export function load({
logger.info(`locale: Matched locale: ${matchedLocale}`);
const localeDisplayNames = getLocaleDisplayNames();
const countryDisplayNames = getCountryDisplayNames();
const localeDisplayNames = getLocaleDisplayNames(rootDir);
const countryDisplayNames = getCountryDisplayNames(rootDir);
let finalMessages: LocaleMessagesType;
if (isPackaged) {
const matchedLocaleMessages = getCompactLocaleValues(matchedLocale);
const englishMessages = getCompactLocaleValues('en');
const keys = getCompactLocaleKeys();
const matchedLocaleMessages = getCompactLocaleValues(
rootDir,
matchedLocale
);
const englishMessages = getCompactLocaleValues(rootDir, 'en');
const keys = getCompactLocaleKeys(rootDir);
if (matchedLocaleMessages.length !== keys.length) {
throw new Error(
`Invalid "${matchedLocale}" entry count, ` +
@@ -153,8 +151,8 @@ export function load({
};
}
} else {
const matchedLocaleMessages = getLocaleMessages(matchedLocale);
const englishMessages = getLocaleMessages('en');
const matchedLocaleMessages = getLocaleMessages(rootDir, matchedLocale);
const englishMessages = getLocaleMessages(rootDir, 'en');
// We start with english, then overwrite that with anything present in locale
finalMessages = merge(englishMessages, matchedLocaleMessages);

View File

@@ -32,15 +32,12 @@ import {
import type { MenuItemConstructorOptions, Settings } from 'electron';
import { z } from 'zod';
import {
version as packageVersion,
productName,
} from '../ts/util/packageJson.node.js';
import { packageJson } from '../ts/util/packageJson.main.js';
import * as GlobalErrors from './global_errors.main.js';
import { setup as setupCrashReports } from './crashReports.main.js';
import { setup as setupSpellChecker } from './spell_check.main.js';
import { getDNSFallback } from './dns-fallback.node.js';
import { redactAll, addSensitivePath } from '../ts/util/privacy.node.js';
import { getDNSFallback } from './dns-fallback.main.js';
import { redactAll, addSensitivePath } from './privacy.main.js';
import { createSupportUrl } from '../ts/util/createSupportUrl.std.js';
import { missingCaseError } from '../ts/util/missingCaseError.std.js';
import { strictAssert } from '../ts/util/assert.std.js';
@@ -137,6 +134,7 @@ import { safeParseLoose, safeParseUnknown } from '../ts/util/schemas.std.js';
import { getAppErrorIcon } from '../ts/util/getAppErrorIcon.node.js';
import { promptOSAuth } from '../ts/util/os/promptOSAuthMain.main.js';
import { appRelaunch } from '../ts/util/relaunch.main.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
import {
sendDummyKeystroke,
show as showWindowsNotification,
@@ -626,13 +624,14 @@ function isVisible(window: BoundsType, bounds: BoundsType) {
}
let windowIcon: string;
const rootDir = getAppRootDir();
if (OS.isWindows()) {
windowIcon = join(__dirname, '../build/icons/win/icon.ico');
windowIcon = join(rootDir, 'build', 'icons', 'win', 'icon.ico');
} else if (OS.isLinux()) {
windowIcon = join(__dirname, '../images/signal-logo-desktop-linux.png');
windowIcon = join(rootDir, 'images', 'signal-logo-desktop-linux.png');
} else {
windowIcon = join(__dirname, '../build/icons/png/512x512.png');
windowIcon = join(rootDir, 'build', 'icons', 'png', '512x512.png');
}
// The titlebar is hidden on:
@@ -722,10 +721,10 @@ async function createWindow() {
sandbox: false,
contextIsolation: !isTestEnvironment(getEnvironment()),
preload: join(
__dirname,
usePreloadBundle
? '../preload.wrapper.js'
: '../ts/windows/main/preload.preload.js'
rootDir,
...(usePreloadBundle
? ['preload.wrapper.js']
: ['ts', 'windows', 'main', 'preload.preload.js'])
),
spellcheck,
},
@@ -1056,8 +1055,8 @@ async function createWindow() {
await safeLoadURL(
mainWindow,
getEnvironment() === Environment.Test
? await prepareFileUrl([__dirname, '../test/index.html'])
: await prepareFileUrl([__dirname, '../background.html'])
? await prepareFileUrl([rootDir, 'test', 'index.html'])
: await prepareFileUrl([rootDir, 'background.html'])
);
}
@@ -1304,7 +1303,7 @@ async function showScreenShareWindow(sourceName: string | undefined) {
nodeIntegrationInWorker: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/screenShare/preload.preload.js'),
preload: join(rootDir, 'bundles', 'screenShare', 'preload.preload.js'),
},
x: Math.floor(display.size.width / 2) - width / 2,
y: 24,
@@ -1326,7 +1325,7 @@ async function showScreenShareWindow(sourceName: string | undefined) {
await safeLoadURL(
screenShareWindow,
await prepareFileUrl([__dirname, '../screenShare.html'], { sourceName })
await prepareFileUrl([rootDir, 'screenShare.html'], { sourceName })
);
}
@@ -1352,7 +1351,7 @@ async function showAbout() {
nodeIntegrationInWorker: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/about/preload.preload.js'),
preload: join(rootDir, 'bundles', 'about', 'preload.preload.js'),
nativeWindowOpen: true,
},
};
@@ -1371,10 +1370,7 @@ async function showAbout() {
}
});
await safeLoadURL(
aboutWindow,
await prepareFileUrl([__dirname, '../about.html'])
);
await safeLoadURL(aboutWindow, await prepareFileUrl([rootDir, 'about.html']));
}
async function getIsLinked() {
@@ -1416,7 +1412,7 @@ async function showDebugLogWindow(options: DebugLogWindowOptions = {}) {
if (debugLogWindow) {
if (debugLogCurrentMode !== newMode) {
debugLogCurrentMode = newMode;
const url = pathToFileURL(join(__dirname, '../debug_log.html'));
const url = pathToFileURL(join(rootDir, 'debug_log.html'));
url.searchParams.set('mode', newMode);
await safeLoadURL(debugLogWindow, url.href);
}
@@ -1457,7 +1453,7 @@ async function showDebugLogWindow(options: DebugLogWindowOptions = {}) {
nodeIntegrationInWorker: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/debuglog/preload.preload.js'),
preload: join(rootDir, 'bundles', 'debuglog', 'preload.preload.js'),
},
parent: mainWindow,
};
@@ -1480,7 +1476,7 @@ async function showDebugLogWindow(options: DebugLogWindowOptions = {}) {
}
});
const url = pathToFileURL(join(__dirname, '../debug_log.html'));
const url = pathToFileURL(join(rootDir, 'debug_log.html'));
if (options.mode) {
url.searchParams.set('mode', options.mode);
}
@@ -1527,7 +1523,7 @@ async function showCallDiagnosticWindow() {
nodeIntegrationInWorker: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/calldiagnostic/preload.preload.js'),
preload: join(rootDir, 'bundles', 'calldiagnostic', 'preload.preload.js'),
},
parent: mainWindow,
};
@@ -1549,7 +1545,7 @@ async function showCallDiagnosticWindow() {
}
});
const url = pathToFileURL(join(__dirname, '../call_diagnostic.html'));
const url = pathToFileURL(join(rootDir, 'call_diagnostic.html'));
await safeLoadURL(callDiagnosticWindow, url.href);
}
@@ -1586,7 +1582,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
nodeIntegrationInWorker: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/permissions/preload.preload.js'),
preload: join(rootDir, 'bundles', 'permissions', 'preload.preload.js'),
nativeWindowOpen: true,
},
parent: mainWindow,
@@ -1612,7 +1608,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
await safeLoadURL(
permissionsPopupWindow,
await prepareFileUrl([__dirname, '../permissions_popup.html'], {
await prepareFileUrl([rootDir, 'permissions_popup.html'], {
forCalling,
forCamera,
})
@@ -1790,7 +1786,7 @@ function handleSafeStorageDecryptionError(): 'continue' | 'quit' {
cancelId: copyErrorAndQuitIndex,
message,
detail,
icon: getAppErrorIcon(),
icon: getAppErrorIcon(rootDir),
noLink: true,
});
if (resultIndex === copyErrorAndQuitIndex) {
@@ -1951,7 +1947,7 @@ const onDatabaseInitializationError = async (error: Error) => {
cancelId: copyErrorAndQuitButtonIndex,
message: messageTitle,
detail: messageDetail,
icon: getAppErrorIcon(),
icon: getAppErrorIcon(rootDir),
noLink: true,
});
@@ -1977,7 +1973,7 @@ const onDatabaseInitializationError = async (error: Error) => {
cancelId: cancelButtonIndex,
message: i18n('icu:databaseError__deleteDataConfirmation'),
detail: i18n('icu:databaseError__deleteDataConfirmation__detail'),
icon: getAppErrorIcon(),
icon: getAppErrorIcon(rootDir),
noLink: true,
});
@@ -2080,7 +2076,7 @@ app.on('ready', async () => {
const [userDataPath, crashDumpsPath, installPath] = await Promise.all([
realpath(app.getPath('userData')),
realpath(app.getPath('crashDumps')),
realpath(app.getAppPath()),
realpath(rootDir),
]);
updateDefaultSession(session.defaultSession, log);
@@ -2120,6 +2116,7 @@ app.on('ready', async () => {
log.info('app.ready: preferred system locales:', preferredSystemLocales);
resolvedTranslationsLocale = loadLocale({
rootDir,
hourCyclePreference,
isPackaged: app.isPackaged,
localeDirectionTestingOverride,
@@ -2233,7 +2230,7 @@ app.on('ready', async () => {
}
log.info('app ready');
log.info(`starting version ${packageVersion}`);
log.info(`starting version ${packageJson.version}`);
// This logging helps us debug user reports about broken devices.
{
@@ -2292,7 +2289,7 @@ app.on('ready', async () => {
nodeIntegration: false,
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/loading/preload.preload.js'),
preload: join(rootDir, 'bundles', 'loading', 'preload.preload.js'),
},
icon: windowIcon,
});
@@ -2310,7 +2307,7 @@ app.on('ready', async () => {
await safeLoadURL(
loadingWindow,
await prepareFileUrl([__dirname, '../loading.html'])
await prepareFileUrl([rootDir, 'loading.html'])
);
})
);
@@ -2863,7 +2860,7 @@ ipc.on('get-config', async event => {
}
const parsed = safeParseLoose(rendererConfigSchema, {
name: productName,
name: packageJson.productName,
availableLocales: getResolvedMessagesLocale().availableLocales,
resolvedTranslationsLocale: getResolvedMessagesLocale().name,
resolvedTranslationsLocaleDirection: getResolvedMessagesLocale().direction,
@@ -2915,7 +2912,7 @@ ipc.on('get-config', async event => {
// paths
crashDumpsPath: app.getPath('crashDumps'),
homePath: app.getPath('home'),
installPath: app.getAppPath(),
installPath: rootDir,
userDataPath: app.getPath('userData'),
directoryConfig: directoryConfig.data,
@@ -3411,8 +3408,11 @@ async function showStickerCreatorWindow() {
sandbox: true,
contextIsolation: true,
preload: join(
__dirname,
'../ts/windows/sticker-creator/preload.preload.js'
rootDir,
'ts',
'windows',
'sticker-creator',
'preload.preload.js'
),
nativeWindowOpen: true,
},
@@ -3432,7 +3432,7 @@ async function showStickerCreatorWindow() {
await safeLoadURL(
stickerCreatorWindow,
await prepareFileUrl([__dirname, '../sticker-creator/dist/index.html'])
await prepareFileUrl([rootDir, 'sticker-creator', 'dist', 'index.html'])
);
}

9
app/privacy.main.ts Normal file
View File

@@ -0,0 +1,9 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { redactAll, addSensitivePath } from '../ts/util/privacy.node.js';
import { getAppRootDir } from '../ts/util/appRootDir.main.js';
addSensitivePath(getAppRootDir());
export { redactAll, addSensitivePath };

View File

@@ -3,7 +3,7 @@
import { app } from 'electron';
import { name } from '../ts/util/packageJson.node.js';
import { packageJson } from '../ts/util/packageJson.main.js';
import { createLogger } from '../ts/logging/log.std.js';
import * as GlobalErrors from './global_errors.main.js';
@@ -15,7 +15,7 @@ GlobalErrors.addHandler();
// set such that only we have read access to our files
process.umask(0o077);
export const AUMID = `org.whispersystems.${name}`;
export const AUMID = `org.whispersystems.${packageJson.name}`;
log.info('Set Windows Application User Model ID (AUMID)', {
AUMID,
});

View File

@@ -23,7 +23,6 @@
"generate:phase-1": "run-p --aggregate-output --print-label generate:phase-1:bundle build:icu-types build:compact-locales build:styles get-expire-time copy-components build:policy-files",
"generate:phase-1:bundle": "run-s build:protobuf build:esbuild:bundle",
"build-release": "pnpm run build",
"sign-release": "node ts/updater/generateSignature.js",
"notarize": "echo 'No longer necessary'",
"get-strings": "ts-node ts/scripts/get-strings.node.ts && ts-node ts/scripts/gen-nsis-script.node.ts && ts-node ts/scripts/gen-locales-config.node.ts && run-p get-strings:locales get-strings:countries get-strings:emoji mark-unusued-strings-deleted && run-p build:compact-locales",
"get-strings:locales": "ts-node ./ts/scripts/build-localized-display-names.node.ts locales ts/scripts/locale-data/locale-display-names.csv build/locale-display-names.json",

View File

@@ -7,7 +7,7 @@ const chaiAsPromised = require('chai-as-promised');
const { Crypto } = require('../ts/context/Crypto.node.js');
const { setEnvironment, Environment } = require('../ts/environment.std.js');
const { HourCyclePreference } = require('../ts/types/I18N.std.js');
const { default: package } = require('../ts/util/packageJson.node.js');
const { packageJson } = require('../ts/util/packageJson.node.js');
chai.use(chaiAsPromised);
@@ -20,14 +20,14 @@ global.window = {
SignalContext: {
i18n: key => `i18n(${key})`,
getPath: () => '/tmp',
getVersion: () => package.version,
getVersion: () => packageJson.version,
config: {
serverUrl: 'https://127.0.0.1:9',
storageUrl: 'https://127.0.0.1:9',
updatesUrl: 'https://127.0.0.1:9',
resourcesUrl: 'https://127.0.0.1:9',
certificateAuthority: package.certificateAuthority,
version: package.version,
certificateAuthority: packageJson.certificateAuthority,
version: packageJson.version,
},
crypto: new Crypto(),
getResolvedMessagesLocale: () => 'en',

View File

@@ -3,6 +3,7 @@
import { parse } from 'csv-parse';
import fs from 'node:fs/promises';
import { join } from 'node:path';
import { z } from 'zod';
import { _getAvailableLocales } from '../../app/locale.node.js';
import { parseUnknown } from '../util/schemas.std.js';
@@ -21,8 +22,9 @@ if (!process.argv[4]) {
throw new Error('Missing third argument: output json file');
}
const localeDisplayNamesBuildPath = process.argv[4];
const rootDir = join(__dirname, '..', '..');
const availableLocales = _getAvailableLocales();
const availableLocales = _getAvailableLocales(rootDir);
const LocaleString = z.string().refine(arg => {
try {

View File

@@ -14,7 +14,7 @@ import { Format, NtExecutable } from 'pe-library';
import ELECTRON_BINARY from 'electron';
import { drop } from '../util/drop.std.js';
import packageJson from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.node.js';
const { ImageDosHeader, ImageNtHeaders, ImageDirectoryEntry } = Format;

View File

@@ -6,7 +6,7 @@ import { execSync } from 'node:child_process';
import { writeFileSync } from 'node:fs';
import { DAY } from '../util/durations/index.std.js';
import { version } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.node.js';
import { isNotUpdatable } from '../util/version.std.js';
const unixTimestamp = parseInt(
@@ -18,7 +18,7 @@ const buildCreation = unixTimestamp * 1000;
// NB: Build expirations are also determined via users' auto-update settings; see
// getExpirationTimestamp
const validDuration = isNotUpdatable(version) ? DAY * 30 : DAY * 90;
const validDuration = isNotUpdatable(packageJson.version) ? DAY * 30 : DAY * 90;
const buildExpiration = buildCreation + validDuration;
const localProductionPath = join(
@@ -29,7 +29,7 @@ const localProductionPath = join(
const localProductionConfig = {
buildCreation,
buildExpiration,
...(isNotUpdatable(version) ? { updatesEnabled: false } : {}),
...(isNotUpdatable(packageJson.version) ? { updatesEnabled: false } : {}),
};
writeFileSync(

View File

@@ -5,7 +5,7 @@ import type { BuildResult } from 'electron-builder';
import { notarize } from '@electron/notarize';
import { build } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.node.js';
export async function afterAllArtifactBuild({
platformToTargets,
@@ -24,7 +24,7 @@ export async function afterAllArtifactBuild({
return;
}
const appBundleId = build.appId;
const appBundleId = packageJson.build.appId;
if (!appBundleId) {
throw new Error(
'appBundleId must be provided in package.json: build.appId'

View File

@@ -6,7 +6,7 @@ import type { AfterPackContext } from 'electron-builder';
import { notarize } from '@electron/notarize';
import { build } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.node.js';
export async function afterSign({
appOutDir,
@@ -22,7 +22,7 @@ export async function afterSign({
const appPath = path.join(appOutDir, `${productFilename}.app`);
const appBundleId = build.appId;
const appBundleId = packageJson.build.appId;
if (!appBundleId) {
throw new Error(
'appBundleId must be provided in package.json: build.appId'

View File

@@ -4,7 +4,10 @@
import { stat } from 'node:fs/promises';
import { join } from 'node:path';
import { name as NAME, version as VERSION } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.node.js';
const NAME = packageJson.name;
const VERSION = packageJson.version;
const SUPPORT_CONFIG = new Set([
'linux',

View File

@@ -9,7 +9,7 @@ import { mkdtemp, cp } from 'node:fs/promises';
import { constants as fsConstants } from 'node:fs';
import { _electron as electron } from 'playwright';
import { productName, name } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.node.js';
import { gracefulRmRecursive } from '../util/gracefulFs.node.js';
import { consoleLogger } from '../util/consoleLogger.std.js';
@@ -21,24 +21,24 @@ let exe: string;
if (process.platform === 'darwin') {
archive = join(
'mac-arm64',
`${productName}.app`,
`${packageJson.productName}.app`,
'Contents',
'Resources',
'app.asar'
);
exe = join(
'mac-arm64',
`${productName}.app`,
`${packageJson.productName}.app`,
'Contents',
'MacOS',
productName
packageJson.productName
);
} else if (process.platform === 'win32') {
archive = join('win-unpacked', 'resources', 'app.asar');
exe = join('win-unpacked', `${productName}.exe`);
exe = join('win-unpacked', `${packageJson.productName}.exe`);
} else if (process.platform === 'linux') {
archive = join('linux-unpacked', 'resources', 'app.asar');
exe = join('linux-unpacked', name);
exe = join('linux-unpacked', packageJson.name);
} else {
throw new Error(`Unsupported platform: ${process.platform}`);
}
@@ -87,7 +87,7 @@ const main = async () => {
);
console.log('Checking window title');
assert.strictEqual(await window.title(), productName);
assert.strictEqual(await window.title(), packageJson.productName);
await app.close();
} finally {

View File

@@ -8,6 +8,7 @@ import { app } from 'electron';
import { strictAssert } from '../util/assert.std.js';
import { explodePromise } from '../util/explodePromise.std.js';
import { getAppRootDir } from '../util/appRootDir.main.js';
import type { LoggerType } from '../types/Logging.std.js';
import * as Errors from '../types/errors.std.js';
import { SqliteErrorKind } from './errors.std.js';
@@ -551,12 +552,7 @@ export class MainSQL {
}
#createWorker(): CreateWorkerResultType {
const scriptPath = join(
app.getAppPath(),
'ts',
'sql',
'mainWorker.node.js'
);
const scriptPath = join(getAppRootDir(), 'ts', 'sql', 'mainWorker.node.js');
const worker = new Worker(scriptPath);

View File

@@ -1,6 +1,7 @@
// Copyright 2021 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { join } from 'node:path';
import { assert } from 'chai';
import { stub } from 'sinon';
import * as LocaleMatcher from '@formatjs/intl-localematcher';
@@ -8,6 +9,8 @@ import { load, _getAvailableLocales } from '../../../app/locale.node.js';
import { FAKE_DEFAULT_LOCALE } from '../../../app/spell_check.main.js';
import { HourCyclePreference } from '../../types/I18N.std.js';
const rootDir = join(__dirname, '..', '..', '..');
describe('locale', async () => {
describe('load', () => {
it('resolves expected locales correctly', async () => {
@@ -26,6 +29,7 @@ describe('locale', async () => {
expectedLocale: string
) {
const actualLocale = await load({
rootDir,
hourCyclePreference: HourCyclePreference.UnknownPreference,
isPackaged: false,
localeDirectionTestingOverride: null,
@@ -150,7 +154,7 @@ describe('locale', async () => {
'VE',
];
const availableLocales = _getAvailableLocales();
const availableLocales = _getAvailableLocales(rootDir);
for (const locale of SINGLE_REGION_LOCALES) {
const { language } = new Intl.Locale(locale);

View File

@@ -1,6 +1,7 @@
// Copyright 2018 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { join } from 'node:path';
import { assert } from 'chai';
import { stub } from 'sinon';
import type { MenuItemConstructorOptions } from 'electron';
@@ -214,6 +215,7 @@ describe('createTemplate', () => {
};
const { i18n } = loadLocale({
rootDir: join(__dirname, '..', '..', '..'),
hourCyclePreference: HourCyclePreference.UnknownPreference,
isPackaged: false,
localeDirectionTestingOverride: null,

View File

@@ -9,14 +9,14 @@ import { tmpdir } from 'node:os';
import { strictAssert } from '../../util/assert.std.js';
import * as durations from '../../util/durations/index.std.js';
import { getGotOptions } from '../../updater/got.node.js';
import { getGotOptions } from '../../updater/got.main.js';
import {
computeDiff,
getBlockMapFileName,
prepareDownload,
isValidPreparedData,
download,
} from '../../updater/differential.node.js';
} from '../../updater/differential.main.js';
const FIXTURES = path.join(__dirname, '..', '..', '..', 'fixtures');
const CRLF = '\r\n';

View File

@@ -4,10 +4,12 @@
import { assert } from 'chai';
import * as Privacy from '../../util/privacy.node.js';
import { APP_ROOT_PATH } from '../../util/privacy.node.js';
Privacy.addSensitivePath('sensitive-path');
const APP_ROOT_PATH = __dirname;
Privacy.addSensitivePath(APP_ROOT_PATH);
describe('Privacy', () => {
describe('redactCardNumbers', () => {
it('should redact anything that looks like a credit card', () => {

24
ts/types/packageJson.d.ts vendored Normal file
View File

@@ -0,0 +1,24 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import type { ReadonlyDeep } from 'type-fest';
export type PackageJsonType = ReadonlyDeep<{
name: string;
version: string;
productName: string;
build: {
appId: string;
mac: {
releaseInfo: {
vendor: {
minOSVersion: string;
};
};
};
deb: {
depends: Array<string>;
};
files: Array<string | Record<string, unknown>>;
};
}>;

View File

@@ -1,7 +1,6 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-console */
import { createWriteStream } from 'node:fs';
import fsExtra from 'fs-extra';
import { mkdir, readdir, stat, writeFile } from 'node:fs/promises';
@@ -10,8 +9,6 @@ import { release as osRelease, tmpdir } from 'node:os';
import { extname, join, normalize } from 'node:path';
import config from 'config';
import type { ParserConfiguration } from 'dashdash';
import { createParser } from 'dashdash';
import { FAILSAFE_SCHEMA, load as loadYaml } from 'js-yaml';
import { gt, gte, lt } from 'semver';
import got from 'got';
@@ -39,7 +36,7 @@ import {
} from '../util/version.std.js';
import { isPathInside } from '../util/isPathInside.node.js';
import { version as packageVersion } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.main.js';
import {
getSignatureFileName,
@@ -51,8 +48,8 @@ import {
getBlockMapFileName,
isValidPreparedData as isValidDifferentialData,
prepareDownload as prepareDifferentialDownload,
} from './differential.node.js';
import { getGotOptions } from './got.node.js';
} from './differential.main.js';
import { getGotOptions } from './got.main.js';
import { checkIntegrity, isTimeToUpdate } from './util.node.js';
import {
gracefulRename,
@@ -60,7 +57,7 @@ import {
} from '../util/gracefulFs.node.js';
import type { LoggerType } from '../types/Logging.std.js';
import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential.node.js';
import type { PrepareDownloadResultType as DifferentialDownloadDataType } from './differential.main.js';
import type { MainSQL } from '../sql/main.main.js';
const { pathExists } = fsExtra;
@@ -578,7 +575,7 @@ export abstract class Updater {
async #checkForUpdates(
checkType: CheckType
): Promise<UpdateInformationType | undefined> {
if (isNotUpdatable(packageVersion)) {
if (isNotUpdatable(packageJson.version)) {
this.logger.info(
'checkForUpdates: not checking for updates, this is not an updatable build'
);
@@ -605,7 +602,7 @@ export abstract class Updater {
if (checkType === CheckType.Normal && !isVersionNewer(version)) {
this.logger.info(
`checkForUpdates: ${version} is not newer than ${packageVersion}; ` +
`checkForUpdates: ${version} is not newer than ${packageJson.version}; ` +
'no new update available'
);
@@ -1007,27 +1004,27 @@ export function getUpdatesFileName(): string {
}
function getChannel(): string {
if (isNotUpdatable(packageVersion)) {
if (isNotUpdatable(packageJson.version)) {
// we don't want ad hoc versions to update
return packageVersion;
return packageJson.version;
}
if (isStaging(packageVersion)) {
if (isStaging(packageJson.version)) {
return 'staging';
}
if (isAlpha(packageVersion)) {
if (isAlpha(packageJson.version)) {
return 'alpha';
}
if (isAxolotl(packageVersion)) {
if (isAxolotl(packageJson.version)) {
return 'axolotl';
}
if (isBeta(packageVersion)) {
if (isBeta(packageJson.version)) {
return 'beta';
}
return 'latest';
}
function isVersionNewer(newVersion: string): boolean {
return gt(newVersion, packageVersion);
return gt(newVersion, packageJson.version);
}
export function getVersion(info: JSONUpdateSchema): string | null {
@@ -1179,16 +1176,3 @@ export async function deleteTempDir(
await gracefulRmRecursive(logger, targetDir);
}
export function getCliOptions<T>(options: ParserConfiguration['options']): T {
const parser = createParser({ options });
const cliOptions = parser.parse(process.argv);
if (cliOptions.help) {
const help = parser.help().trimRight();
console.log(help);
process.exit(0);
}
return cliOptions as unknown as T;
}

View File

@@ -15,8 +15,8 @@ import Dicer from '@indutny/dicer';
import { strictAssert } from '../util/assert.std.js';
import { wrapEventEmitterOnce } from '../util/wrapEventEmitterOnce.node.js';
import type { LoggerType } from '../types/Logging.std.js';
import { getGotOptions } from './got.node.js';
import type { GotOptions } from './got.node.js';
import { getGotOptions } from './got.main.js';
import type { GotOptions } from './got.main.js';
import { checkIntegrity } from './util.node.js';
const { chunk: lodashChunk, noop } = lodash;

View File

@@ -1,48 +0,0 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-console */
import * as Errors from '../types/errors.std.js';
import { getCliOptions } from './common.main.js';
import { keyPair } from './curve.node.js';
import { writeHexToPath } from './signature.node.js';
const OPTIONS = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.',
},
{
names: ['key', 'k'],
type: 'string',
help: 'Path where public key will go',
default: 'public.key',
},
{
names: ['private', 'p'],
type: 'string',
help: 'Path where private key will go',
default: 'private.key',
},
];
type OptionsType = {
key: string;
private: string;
};
const cliOptions = getCliOptions<OptionsType>(OPTIONS);
go(cliOptions).catch(error => {
console.error('Something went wrong!', Errors.toLogFormat(error));
});
async function go(options: OptionsType) {
const { key: publicKeyPath, private: privateKeyPath } = options;
const { publicKey, privateKey } = keyPair();
await Promise.all([
writeHexToPath(publicKeyPath, publicKey),
writeHexToPath(privateKeyPath, privateKey),
]);
}

View File

@@ -1,95 +0,0 @@
// Copyright 2019 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable no-console */
import { join, resolve } from 'node:path';
import { readdir as readdirCallback } from 'node:fs';
import pify from 'pify';
import * as Errors from '../types/errors.std.js';
import { getCliOptions } from './common.main.js';
import { writeSignature } from './signature.node.js';
import { version as packageVersion } from '../util/packageJson.node.js';
const readdir = pify(readdirCallback);
const OPTIONS = [
{
names: ['help', 'h'],
type: 'bool',
help: 'Print this help and exit.',
},
{
names: ['private', 'p'],
type: 'string',
help: 'Path to private key file (default: ./private.key)',
default: 'private.key',
},
{
names: ['update', 'u'],
type: 'string',
help: 'Path to the update package (default: the .exe or .zip in ./release)',
},
{
names: ['version', 'v'],
type: 'string',
help: `Version number of this package (default: ${packageVersion})`,
default: packageVersion,
},
];
type OptionsType = {
private: string;
update: string;
version: string;
};
const cliOptions = getCliOptions<OptionsType>(OPTIONS);
go(cliOptions).catch(error => {
console.error('Something went wrong!', Errors.toLogFormat(error));
});
async function go(options: OptionsType) {
const { private: privateKeyPath, version } = options;
let updatePaths: Array<string>;
if (options.update) {
updatePaths = [options.update];
} else {
updatePaths = await findUpdatePaths();
}
await Promise.all(
updatePaths.map(async updatePath => {
console.log('Signing with...');
console.log(` version: ${version}`);
console.log(` update file: ${updatePath}`);
console.log(` private key file: ${privateKeyPath}`);
await writeSignature(updatePath, version, privateKeyPath);
})
);
}
const IS_EXE = /\.exe$/;
const IS_ZIP = /\.zip$/;
async function findUpdatePaths(): Promise<Array<string>> {
const releaseDir = resolve('release');
const files: Array<string> = await readdir(releaseDir);
const results = new Array<string>();
for (const file of files) {
const fullPath = join(releaseDir, file);
if (IS_EXE.test(file) || IS_ZIP.test(file)) {
results.push(fullPath);
}
}
if (results.length === 0) {
throw new Error("No suitable file found in 'release' folder!");
}
return results;
}

View File

@@ -5,7 +5,7 @@ import type { StrictOptions as GotOptions } from 'got';
import config from 'config';
import { Agent as HTTPAgent } from 'node:http';
import { version } from '../util/packageJson.node.js';
import { packageJson } from '../util/packageJson.main.js';
import { getUserAgent } from '../util/getUserAgent.node.js';
import * as durations from '../util/durations/index.std.js';
import { createHTTPSAgent } from '../util/createHTTPSAgent.node.js';
@@ -46,7 +46,7 @@ export async function getGotOptions(): Promise<GotOptions> {
},
headers: {
'Cache-Control': 'no-cache',
'User-Agent': getUserAgent(version),
'User-Agent': getUserAgent(packageJson.version),
},
timeout: {
connect: GOT_CONNECT_TIMEOUT,

View File

@@ -14,6 +14,7 @@ import { DialogType } from '../types/Dialogs.std.js';
import * as Errors from '../types/errors.std.js';
import type { UpdaterOptionsType } from './common.main.js';
import { appRelaunch } from '../util/relaunch.main.js';
import { getAppRootDir } from '../util/appRootDir.main.js';
const MIN_UBUNTU_VERSION = '22.04';
@@ -69,7 +70,7 @@ export function initLinux({ logger, getMainWindow }: UpdaterOptionsType): void {
// See our patch for app-builder-lib.
//
// /opt/Signal/resources/app.asar
const asarPath = join(__dirname, '..', '..');
const asarPath = getAppRootDir();
if (!asarPath.endsWith('.asar')) {
throw new Error('updater/linux: not running from ASAR');
}

View File

@@ -9,6 +9,7 @@ import { readdir as readdirCallback, unlink as unlinkCallback } from 'node:fs';
import { app } from 'electron';
import pify from 'pify';
import { getAppRootDir } from '../util/appRootDir.main.js';
import { Updater } from './common.main.js';
const readdir = pify(readdirCallback);
@@ -112,7 +113,7 @@ export class WindowsUpdater extends Updater {
// Helpers
function getElevatePath() {
const installPath = app.getAppPath();
const installPath = getAppRootDir();
return join(installPath, 'resources', 'elevate.exe');
}

View File

@@ -0,0 +1,14 @@
// Copyright 2026 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { app } from 'electron';
import { join } from 'node:path';
export function getAppRootDir(): string {
// We have this logic because test-node runs under `electron-mocha` that has
// `app.getAppPath()` pointing within `electron-mocha`'s folder.
if (app.isPackaged || process.env.IS_BUNDLED) {
return app.getAppPath();
}
return join(__dirname, '..', '..');
}

View File

@@ -5,13 +5,7 @@ import { nativeImage } from 'electron';
import type { NativeImage } from 'electron';
import { join } from 'node:path';
export function getAppErrorIcon(): NativeImage {
const iconPath = join(
__dirname,
'..',
'..',
'images',
'app-icon-with-error.png'
);
export function getAppErrorIcon(rootDir: string): NativeImage {
const iconPath = join(rootDir, 'images', 'app-icon-with-error.png');
return nativeImage.createFromPath(iconPath);
}

View File

@@ -0,0 +1,13 @@
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { PackageJsonType } from '../types/packageJson.d.ts';
import { getAppRootDir } from './appRootDir.main.js';
const PACKAGE_JSON_PATH = join(getAppRootDir(), 'package.json');
export const packageJson: PackageJsonType = JSON.parse(
readFileSync(PACKAGE_JSON_PATH, 'utf8')
);

View File

@@ -3,32 +3,10 @@
import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { PackageJsonType } from '../types/packageJson.d.ts';
const PACKAGE_JSON_PATH = join(__dirname, '..', '..', 'package.json');
const json: {
name: string;
version: string;
productName: string;
build: {
appId: string;
mac: {
releaseInfo: {
vendor: {
minOSVersion: string;
};
};
};
deb: {
depends: Array<string>;
};
files: Array<string | Record<string, unknown>>;
};
} = JSON.parse(readFileSync(PACKAGE_JSON_PATH, 'utf8'));
export default json;
export const { name } = json;
export const { version } = json;
export const { productName } = json;
export const { build } = json;
export const packageJson: PackageJsonType = JSON.parse(
readFileSync(PACKAGE_JSON_PATH, 'utf8')
);

View File

@@ -11,7 +11,6 @@ import lodash from 'lodash';
import type { ExtendedStorageID } from '../types/StorageService.d.ts';
import type { ConversationModel } from '../models/conversations.preload.js';
export const APP_ROOT_PATH = path.join(__dirname, '..', '..');
const { escapeRegExp, isString, isRegExp } = lodash;
const { compose } = lodashFp;
@@ -232,8 +231,6 @@ export const addSensitivePath = (filePath: string): void => {
redactSensitivePaths = createRedactSensitivePaths(sensitivePaths);
};
addSensitivePath(APP_ROOT_PATH);
export const redactAll: RedactFunction = text => {
let result = text;

View File

@@ -1,7 +1,7 @@
// Copyright 2024 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { version } from './packageJson.node.js';
import { packageJson } from './packageJson.node.js';
import { getUserAgent } from './getUserAgent.node.js';
type AuthenticateOptionsType = Readonly<{
@@ -36,6 +36,6 @@ export async function authenticate({
return new Headers({
authorization: `${auth.tokenType} ${auth.accessToken}`,
'user-agent': getUserAgent(version),
'user-agent': getUserAgent(packageJson.version),
});
}

View File

@@ -3,7 +3,7 @@
import { join } from 'node:path';
import { Worker } from 'node:worker_threads';
import { app } from 'electron';
import { getAppRootDir } from '../util/appRootDir.main.js';
export type WrappedWorkerRequest = {
readonly uuid: string;
@@ -21,7 +21,7 @@ export function getHeicConverter(): (
data: Uint8Array<ArrayBuffer>
) => Promise<WrappedWorkerResponse> {
const scriptDir = join(
app.getAppPath(),
getAppRootDir(),
'ts',
'workers',
'heicConverterWorker.node.js'