Merge branch 'main' into main

This commit is contained in:
Mustafa Ateş UZUN
2023-12-12 10:58:43 +03:00
committed by GitHub
1116 changed files with 96089 additions and 39238 deletions
+1
View File
@@ -247,6 +247,7 @@ function getIcon(unreadCount: number) {
case 'darwin':
iconSize = '16';
break;
case 'linux':
case 'win32':
iconSize = '32';
break;
+1 -1
View File
@@ -117,7 +117,7 @@ export function setup(getLogger: () => LoggerType, forceEnable = false): void {
prefix: 'desktop-crash-',
});
logger.info('crashReports: upload complete');
logger.info(`crashReports: upload complete, ${url}`);
clipboard.writeText(url);
} finally {
await eraseDumps(logger, pendingDumps);
+26 -2
View File
@@ -23,14 +23,28 @@ function getLocaleMessages(locale: string): LocaleMessagesType {
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
export type LocaleDisplayNames = Record<string, Record<string, string>>;
function getLocaleDisplayNames(): LocaleDisplayNames {
const targetFile = join(
__dirname,
'..',
'build',
'locale-display-names.json'
);
return JSON.parse(readFileSync(targetFile, 'utf-8'));
}
export type LocaleDirection = 'ltr' | 'rtl';
export type LocaleType = {
availableLocales: Array<string>;
i18n: LocalizerType;
name: string;
direction: LocaleDirection;
messages: LocaleMessagesType;
hourCyclePreference: HourCyclePreference;
localeDisplayNames: LocaleDisplayNames;
};
function getLocaleDirection(
@@ -65,10 +79,12 @@ function getLocaleDirection(
}
function finalize(
availableLocales: Array<string>,
messages: LocaleMessagesType,
backupMessages: LocaleMessagesType,
localeName: string,
hourCyclePreference: HourCyclePreference,
localeDisplayNames: LocaleDisplayNames,
logger: LoggerType
): LocaleType {
// We start with english, then overwrite that with anything present in locale
@@ -80,11 +96,13 @@ function finalize(
logger.info(`locale: Text info direction for ${localeName}: ${direction}`);
return {
availableLocales,
i18n,
name: localeName,
direction,
messages: finalMessages,
hourCyclePreference,
localeDisplayNames,
};
}
@@ -99,10 +117,12 @@ export function _getAvailableLocales(): Array<string> {
export function load({
preferredSystemLocales,
localeOverride,
hourCyclePreference,
logger,
}: {
preferredSystemLocales: Array<string>;
localeOverride: string | null;
hourCyclePreference: HourCyclePreference;
logger: LoggerType;
}): LocaleType {
@@ -117,10 +137,11 @@ export function load({
const availableLocales = _getAvailableLocales();
logger.info('locale: Supported locales:', availableLocales.join(', '));
logger.info('locale: Preferred locales: ', preferredSystemLocales.join(', '));
logger.info('locale: Preferred locales:', preferredSystemLocales.join(', '));
logger.info('locale: Locale Override:', localeOverride);
const matchedLocale = LocaleMatcher.match(
preferredSystemLocales,
localeOverride != null ? [localeOverride] : preferredSystemLocales,
availableLocales,
'en',
{ algorithm: 'best fit' }
@@ -130,12 +151,15 @@ export function load({
const matchedLocaleMessages = getLocaleMessages(matchedLocale);
const englishMessages = getLocaleMessages('en');
const languageDisplayNames = getLocaleDisplayNames();
return finalize(
availableLocales,
matchedLocaleMessages,
englishMessages,
matchedLocale,
hourCyclePreference,
languageDisplayNames,
logger
);
}
+328 -172
View File
@@ -100,15 +100,6 @@ import { createTemplate } from './menu';
import { installFileHandler, installWebHandler } from './protocol_filter';
import OS from '../ts/util/os/osMain';
import { isProduction } from '../ts/util/version';
import {
isSgnlHref,
isCaptchaHref,
isSignalHttpsLink,
parseSgnlHref,
parseCaptchaHref,
parseSignalHttpsLink,
rewriteSignalHrefsIfNecessary,
} from '../ts/util/sgnlHref';
import { clearTimeoutIfNecessary } from '../ts/util/clearTimeoutIfNecessary';
import { toggleMaximizedBrowserWindow } from '../ts/util/toggleMaximizedBrowserWindow';
import { ChallengeMainHandler } from '../ts/main/challengeMain';
@@ -123,6 +114,9 @@ import { load as loadLocale } from './locale';
import type { LoggerType } from '../ts/types/Logging';
import { HourCyclePreference } from '../ts/types/I18N';
import { DBVersionFromFutureError } from '../ts/sql/migrations';
import type { ParsedSignalRoute } from '../ts/util/signalRoutes';
import { parseSignalRoute } from '../ts/util/signalRoutes';
const STICKER_CREATOR_PARTITION = 'sticker-creator';
@@ -149,6 +143,7 @@ const consoleLogger = createBufferedConsoleLogger();
// These will be set after app fires the 'ready' event
let logger: LoggerType | undefined;
let preferredSystemLocales: Array<string> | undefined;
let localeOverride: string | null | undefined;
let resolvedTranslationsLocale: LocaleType | undefined;
let settingsChannel: SettingsChannel | undefined;
@@ -194,7 +189,11 @@ const defaultWebPrefs = {
getEnvironment() !== Environment.Production ||
!isProduction(app.getVersion()),
spellcheck: false,
enableBlinkFeatures: 'CSSPseudoDir,CSSLogical',
// https://chromium.googlesource.com/chromium/src/+/main/third_party/blink/renderer/platform/runtime_enabled_features.json5
enableBlinkFeatures: [
'CSSPseudoDir', // status=experimental, needed for RTL (ex: :dir(rtl))
'CSSLogical', // status=experimental, needed for RTL (ex: margin-inline-start)
].join(','),
};
const DISABLE_GPU =
@@ -265,18 +264,10 @@ if (!process.mas) {
return;
}
const incomingCaptchaHref = getIncomingCaptchaHref(argv);
if (incomingCaptchaHref) {
const { captcha } = parseCaptchaHref(incomingCaptchaHref, getLogger());
challengeHandler.handleCaptcha(captcha);
return true;
const route = maybeGetIncomingSignalRoute(argv);
if (route != null) {
handleSignalRoute(route);
}
// Are they trying to open a sgnl:// href?
const incomingHref = getIncomingHref(argv);
if (incomingHref) {
handleSgnlHref(incomingHref);
}
// Handled
return true;
});
}
@@ -315,30 +306,32 @@ type GetThemeSettingOptionsType = Readonly<{
async function getThemeSetting({
ephemeralOnly = false,
}: GetThemeSettingOptionsType = {}): Promise<ThemeSettingType> {
let result: unknown;
const fastValue = ephemeralConfig.get('theme-setting');
if (fastValue !== undefined) {
getLogger().info('got fast theme-setting value', fastValue);
return fastValue as ThemeSettingType;
}
if (ephemeralOnly) {
result = fastValue;
} else if (ephemeralOnly) {
return 'system';
}
} else {
const json = await sql.sqlCall('getItemById', 'theme-setting');
const json = await sql.sqlCall('getItemById', 'theme-setting');
result = json?.value;
}
// Default to `system` if setting doesn't exist or is invalid
const setting: unknown = json?.value;
const slowValue =
setting === 'light' || setting === 'dark' || setting === 'system'
? setting
const validatedResult =
result === 'light' || result === 'dark' || result === 'system'
? result
: 'system';
ephemeralConfig.set('theme-setting', slowValue);
if (fastValue !== validatedResult) {
ephemeralConfig.set('theme-setting', validatedResult);
getLogger().info('got slow theme-setting value', result);
}
getLogger().info('got slow theme-setting value', slowValue);
return slowValue;
return validatedResult;
}
async function getResolvedThemeSetting(
@@ -367,6 +360,26 @@ async function getBackgroundColor(
throw missingCaseError(theme);
}
async function getLocaleOverrideSetting(): Promise<string | null> {
const fastValue = ephemeralConfig.get('localeOverride');
// eslint-disable-next-line eqeqeq -- Checking for null explicitly
if (typeof fastValue === 'string' || fastValue === null) {
getLogger().info('got fast localeOverride setting', fastValue);
return fastValue;
}
const json = await sql.sqlCall('getItemById', 'localeOverride');
// Default to `null` if setting doesn't exist yet
const slowValue = typeof json?.value === 'string' ? json.value : null;
ephemeralConfig.set('localeOverride', slowValue);
getLogger().info('got slow localeOverride setting', slowValue);
return slowValue;
}
let systemTrayService: SystemTrayService | undefined;
const systemTraySettingCache = new SystemTraySettingCache(
sql,
@@ -419,6 +432,13 @@ function getPreferredSystemLocales(): Array<string> {
return preferredSystemLocales;
}
function getLocaleOverride(): string | null {
if (typeof localeOverride === 'undefined') {
throw new Error('getLocaleOverride: Locale not yet initialized!');
}
return localeOverride;
}
function getResolvedMessagesLocale(): LocaleType {
if (!resolvedTranslationsLocale) {
throw new Error('getResolvedMessagesLocale: Locale not yet initialized!');
@@ -468,23 +488,21 @@ async function handleUrl(rawTarget: string) {
return;
}
const target = rewriteSignalHrefsIfNecessary(rawTarget);
const signalRoute = parseSignalRoute(rawTarget);
// We only want to specially handle urls that aren't requesting the dev server
if (signalRoute != null) {
handleSignalRoute(signalRoute);
return;
}
const { protocol, hostname } = parsedUrl;
const isDevServer =
process.env.SIGNAL_ENABLE_HTTP && hostname === 'localhost';
// We only want to specially handle urls that aren't requesting the dev server
if (
isSgnlHref(target, getLogger()) ||
isSignalHttpsLink(target, getLogger())
) {
handleSgnlHref(target);
return;
}
if ((protocol === 'http:' || protocol === 'https:') && !isDevServer) {
try {
await shell.openExternal(target);
await shell.openExternal(rawTarget);
} catch (error) {
getLogger().error(`Failed to open url: ${Errors.toLogFormat(error)}`);
}
@@ -525,7 +543,7 @@ function handleCommonWindowEvents(
const focusInterval = setInterval(setWindowFocus, 10000);
window.on('closed', () => clearInterval(focusInterval));
// Works only for mainWindow because it has `enablePreferredSizeMode`
// Works only for mainWindow and settings because they have `enablePreferredSizeMode`
let lastZoomFactor = window.webContents.getZoomFactor();
const onZoomChanged = () => {
if (
@@ -541,15 +559,38 @@ function handleCommonWindowEvents(
return;
}
drop(
settingsChannel?.invokeCallbackInMainWindow('persistZoomFactor', [
zoomFactor,
])
);
lastZoomFactor = zoomFactor;
if (!mainWindow) {
return;
}
if (window === mainWindow) {
drop(
settingsChannel?.invokeCallbackInMainWindow('persistZoomFactor', [
zoomFactor,
])
);
} else {
mainWindow.webContents.setZoomFactor(zoomFactor);
}
};
window.webContents.on('preferred-size-changed', onZoomChanged);
window.on('show', () => {
// Install handler here after we init zoomFactor otherwise an initial
// preferred-size-changed event emits with an undesired zoomFactor.
window.webContents.on('preferred-size-changed', onZoomChanged);
});
// Workaround to apply zoomFactor because webPreferences does not handle it
// https://github.com/electron/electron/issues/10572
// But main window emits ready-to-show before window.Events is available
// so set main window zoom in background.ts
if (window !== mainWindow) {
window.once('ready-to-show', async () => {
const zoomFactor =
(await settingsChannel?.getSettingFromMainWindow('zoomFactor')) ?? 1;
window.webContents.setZoomFactor(zoomFactor);
});
}
nativeThemeNotifier.addWindow(window);
@@ -751,8 +792,9 @@ async function createWindow() {
}
const startInTray =
isTestEnvironment(getEnvironment()) ||
(await systemTraySettingCache.get()) ===
SystemTraySetting.MinimizeToAndStartInSystemTray;
SystemTraySetting.MinimizeToAndStartInSystemTray;
const visibleOnAnyScreen = some(screen.getAllDisplays(), display => {
if (
@@ -790,7 +832,9 @@ async function createWindow() {
setupSpellChecker(
mainWindow,
getPreferredSystemLocales(),
getResolvedMessagesLocale().i18n
getLocaleOverride(),
getResolvedMessagesLocale().i18n,
getLogger()
);
if (!startInTray && windowConfig && windowConfig.maximized) {
mainWindow.maximize();
@@ -946,6 +990,7 @@ async function createWindow() {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
getLogger().info('main window closed event');
mainWindow = undefined;
if (settingsChannel) {
settingsChannel.setMainWindow(mainWindow);
@@ -956,11 +1001,13 @@ async function createWindow() {
});
mainWindow.on('enter-full-screen', () => {
getLogger().info('mainWindow enter-full-screen event');
if (mainWindow) {
mainWindow.webContents.send('full-screen-change', true);
}
});
mainWindow.on('leave-full-screen', () => {
getLogger().info('mainWindow leave-full-screen event');
if (mainWindow) {
mainWindow.webContents.send('full-screen-change', false);
}
@@ -976,6 +1023,8 @@ async function createWindow() {
return;
}
mainWindow.webContents.send('ci:event', 'db-initialized', {});
const shouldShowWindow =
!app.getLoginItemSettings().wasOpenedAsHidden && !startInTray;
@@ -1093,9 +1142,9 @@ async function readyForUpdates() {
isReadyForUpdates = true;
// First, install requested sticker pack
const incomingHref = getIncomingHref(process.argv);
const incomingHref = maybeGetIncomingSignalRoute(process.argv);
if (incomingHref) {
handleSgnlHref(incomingHref);
handleSignalRoute(incomingHref);
}
// Second, start checking for app updates
@@ -1315,6 +1364,7 @@ async function showSettingsWindow() {
contextIsolation: true,
preload: join(__dirname, '../bundles/settings/preload.js'),
nativeWindowOpen: true,
enablePreferredSizeMode: true,
},
};
@@ -1371,13 +1421,28 @@ async function openArtCreator() {
let debugLogWindow: BrowserWindow | undefined;
async function showDebugLogWindow() {
if (debugLogWindow) {
debugLogWindow.show();
doShowDebugLogWindow();
return;
}
function doShowDebugLogWindow() {
if (debugLogWindow) {
// Electron has [a macOS bug][0] that causes parent windows to become unresponsive
// if it's fullscreen and opens a fullscreen child window. Until that's fixed, we
// only set the parent on MacOS is if the mainWindow is not fullscreen
// [0]: https://github.com/electron/electron/issues/32374
if (OS.isMacOS() && mainWindow?.isFullScreen()) {
debugLogWindow.setParentWindow(null);
} else {
debugLogWindow.setParentWindow(mainWindow ?? null);
}
debugLogWindow.show();
}
}
const titleBarOverlay = await getTitleBarOverlay();
const options = {
const options: Electron.BrowserWindowConstructorOptions = {
width: 700,
height: 500,
resizable: false,
@@ -1394,14 +1459,8 @@ async function showDebugLogWindow() {
sandbox: true,
contextIsolation: true,
preload: join(__dirname, '../bundles/debuglog/preload.js'),
nativeWindowOpen: true,
},
parent: mainWindow,
// Electron has [a macOS bug][0] that causes parent windows to become unresponsive if
// it's fullscreen and opens a fullscreen child window. Until that's fixed, we
// prevent the child window from being fullscreenable, which sidesteps the problem.
// [0]: https://github.com/electron/electron/issues/32374
fullscreenable: !OS.isMacOS(),
};
debugLogWindow = new BrowserWindow(options);
@@ -1414,7 +1473,7 @@ async function showDebugLogWindow() {
debugLogWindow.once('ready-to-show', () => {
if (debugLogWindow) {
debugLogWindow.show();
doShowDebugLogWindow();
// Electron sometimes puts the window in a strange spot until it's shown.
debugLogWindow.center();
@@ -1548,6 +1607,7 @@ async function initializeSQL(
// `sql.sqlCall` will throw an uninitialized error instead of waiting for
// init to finish.
await sql.initialize({
appVersion: app.getVersion(),
configDir: userDataPath,
key,
logger: getLogger(),
@@ -1581,28 +1641,72 @@ const onDatabaseError = async (error: string) => {
}
mainWindow = undefined;
const { i18n } = getResolvedMessagesLocale();
let deleteAllDataButtonIndex: number | undefined;
let messageDetail: string;
const buttons = [i18n('icu:copyErrorAndQuit')];
const copyErrorAndQuitButtonIndex = 0;
const SIGNAL_SUPPORT_LINK = 'https://support.signal.org/error';
if (error.includes(DBVersionFromFutureError.name)) {
// If the DB version is too new, the user likely opened an older version of Signal,
// and they would almost never want to delete their data as a result, so we don't show
// that option
messageDetail = i18n('icu:databaseError__startOldVersion');
} else {
// Otherwise, this is some other kind of DB error, let's give them the option to
// delete.
messageDetail = i18n('icu:databaseError__detail', {
link: SIGNAL_SUPPORT_LINK,
});
buttons.push(i18n('icu:deleteAndRestart'));
deleteAllDataButtonIndex = 1;
}
const buttonIndex = dialog.showMessageBoxSync({
buttons: [
getResolvedMessagesLocale().i18n('icu:deleteAndRestart'),
getResolvedMessagesLocale().i18n('icu:copyErrorAndQuit'),
],
defaultId: 1,
cancelId: 1,
detail: redactAll(error),
message: getResolvedMessagesLocale().i18n('icu:databaseError'),
buttons,
defaultId: copyErrorAndQuitButtonIndex,
cancelId: copyErrorAndQuitButtonIndex,
message: i18n('icu:databaseError'),
detail: messageDetail,
noLink: true,
type: 'error',
});
if (buttonIndex === 1) {
if (buttonIndex === copyErrorAndQuitButtonIndex) {
clipboard.writeText(`Database startup error:\n\n${redactAll(error)}`);
} else {
await sql.removeDB();
userConfig.remove();
getLogger().error(
'onDatabaseError: Requesting immediate restart after quit'
);
app.relaunch();
} else if (
typeof deleteAllDataButtonIndex === 'number' &&
buttonIndex === deleteAllDataButtonIndex
) {
const confirmationButtons = [
i18n('icu:cancel'),
i18n('icu:deleteAndRestart'),
];
const cancelButtonIndex = 0;
const confirmDeleteAllDataButtonIndex = 1;
const confirmationButtonIndex = dialog.showMessageBoxSync({
buttons: confirmationButtons,
defaultId: cancelButtonIndex,
cancelId: cancelButtonIndex,
message: i18n('icu:databaseError__deleteDataConfirmation'),
detail: i18n('icu:databaseError__deleteDataConfirmation__detail'),
noLink: true,
type: 'warning',
});
if (confirmationButtonIndex === confirmDeleteAllDataButtonIndex) {
getLogger().error('onDatabaseError: Deleting all data');
await sql.removeDB();
userConfig.remove();
getLogger().error(
'onDatabaseError: Requesting immediate restart after quit'
);
app.relaunch();
}
}
getLogger().error('onDatabaseError: Quitting application');
@@ -1713,11 +1817,15 @@ app.on('ready', async () => {
// Write buffered information into newly created logger.
consoleLogger.writeBufferInto(logger);
sqlInitPromise = initializeSQL(userDataPath);
if (!resolvedTranslationsLocale) {
preferredSystemLocales = resolveCanonicalLocales(
loadPreferredSystemLocales()
);
localeOverride = await getLocaleOverrideSetting();
const hourCyclePreference = getHourCyclePreference();
logger.info(`app.ready: hour cycle preference: ${hourCyclePreference}`);
@@ -1728,13 +1836,12 @@ app.on('ready', async () => {
);
resolvedTranslationsLocale = loadLocale({
preferredSystemLocales,
localeOverride,
hourCyclePreference,
logger: getLogger(),
});
}
sqlInitPromise = initializeSQL(userDataPath);
// First run: configure Signal to minimize to tray. Additionally, on Windows
// enable auto-start with start-in-tray so that starting from a Desktop icon
// would still show the window.
@@ -2055,16 +2162,42 @@ async function requestShutdown() {
}
}
app.on('before-quit', () => {
function getWindowDebugInfo() {
const windows = BrowserWindow.getAllWindows();
return {
windowCount: windows.length,
mainWindowExists: windows.some(win => win === mainWindow),
mainWindowIsFullScreen: mainWindow?.isFullScreen(),
};
}
app.on('before-quit', e => {
getLogger().info('before-quit event', {
readyForShutdown: windowState.readyForShutdown(),
shouldQuit: windowState.shouldQuit(),
hasEventBeenPrevented: e.defaultPrevented,
...getWindowDebugInfo(),
});
systemTrayService?.markShouldQuit();
windowState.markShouldQuit();
});
app.on('will-quit', e => {
getLogger().info('will-quit event', {
hasEventBeenPrevented: e.defaultPrevented,
...getWindowDebugInfo(),
});
});
app.on('quit', e => {
getLogger().info('quit event', {
hasEventBeenPrevented: e.defaultPrevented,
...getWindowDebugInfo(),
});
});
// Quit when all windows are closed.
app.on('window-all-closed', () => {
getLogger().info('main process handling window-all-closed');
@@ -2113,24 +2246,29 @@ app.on('will-finish-launching', () => {
// https://stackoverflow.com/a/43949291
app.on('open-url', (event, incomingHref) => {
event.preventDefault();
if (isCaptchaHref(incomingHref, getLogger())) {
const { captcha } = parseCaptchaHref(incomingHref, getLogger());
challengeHandler.handleCaptcha(captcha);
// Show window after handling captcha
showWindow();
return;
const route = parseSignalRoute(incomingHref);
if (route != null) {
handleSignalRoute(route);
}
handleSgnlHref(incomingHref);
});
});
ipc.on('set-badge-count', (_event: Electron.Event, count: number) => {
app.badgeCount = count;
});
ipc.on(
'set-badge',
(_event: Electron.Event, badge: number | 'marked-unread') => {
if (badge === 'marked-unread') {
if (process.platform === 'darwin') {
// Will show a ● on macOS when undefined
app.setBadgeCount(undefined);
} else {
// All other OS's need a number
app.setBadgeCount(1);
}
} else {
app.setBadgeCount(badge);
}
}
);
ipc.on('remove-setup-menu-items', () => {
setupMenu();
@@ -2298,10 +2436,12 @@ ipc.on('get-config', async event => {
const parsed = rendererConfigSchema.safeParse({
name: packageJson.productName,
availableLocales: getResolvedMessagesLocale().availableLocales,
resolvedTranslationsLocale: getResolvedMessagesLocale().name,
resolvedTranslationsLocaleDirection: getResolvedMessagesLocale().direction,
hourCyclePreference: getResolvedMessagesLocale().hourCyclePreference,
preferredSystemLocales: getPreferredSystemLocales(),
localeOverride: getLocaleOverride(),
version: app.getVersion(),
buildCreation: config.get<number>('buildCreation'),
buildExpiration: config.get<number>('buildExpiration'),
@@ -2369,6 +2509,12 @@ ipc.on('locale-data', event => {
event.returnValue = getResolvedMessagesLocale().messages;
});
// Ingested in preload.js via a sendSync call
ipc.on('locale-display-names', event => {
// eslint-disable-next-line no-param-reassign
event.returnValue = getResolvedMessagesLocale().localeDisplayNames;
});
// TODO DESKTOP-5241
ipc.on('OS.getHasCustomTitleBar', event => {
// eslint-disable-next-line no-param-reassign
@@ -2389,7 +2535,8 @@ ipc.handle(
process.versions.node,
app.getVersion(),
os.version(),
userAgent
userAgent,
OS.getLinuxName()
);
}
);
@@ -2421,78 +2568,71 @@ ipc.on('preferences-changed', () => {
}
});
function getIncomingHref(argv: Array<string>) {
return argv.find(arg => isSgnlHref(arg, getLogger()));
function maybeGetIncomingSignalRoute(argv: Array<string>) {
for (const arg of argv) {
const route = parseSignalRoute(arg);
if (route != null) {
return route;
}
}
return null;
}
function getIncomingCaptchaHref(argv: Array<string>) {
return argv.find(arg => isCaptchaHref(arg, getLogger()));
}
function handleSignalRoute(route: ParsedSignalRoute) {
const log = getLogger();
function handleSgnlHref(incomingHref: string) {
let command;
let args;
let hash;
if (isSgnlHref(incomingHref, getLogger())) {
({ command, args, hash } = parseSgnlHref(incomingHref, getLogger()));
} else if (isSignalHttpsLink(incomingHref, getLogger())) {
({ command, args, hash } = parseSignalHttpsLink(incomingHref, getLogger()));
if (mainWindow == null || !mainWindow.webContents) {
log.error('handleSignalRoute: mainWindow is null or missing webContents');
return;
}
if (mainWindow && mainWindow.webContents) {
if (command === 'addstickers') {
getLogger().info('Opening sticker pack from sgnl protocol link');
const packId = args?.get('pack_id');
const packKeyHex = args?.get('pack_key');
const packKey = packKeyHex
? Buffer.from(packKeyHex, 'hex').toString('base64')
: '';
mainWindow.webContents.send('show-sticker-pack', { packId, packKey });
} else if (command === 'art-auth') {
const token = args?.get('token');
const pubKeyBase64 = args?.get('pub_key');
log.info('handleSignalRoute: Matched signal route:', route.key);
mainWindow.webContents.send('authorize-art-creator', {
token,
pubKeyBase64,
});
} else if (command === 'signal.group' && hash) {
getLogger().info('Showing group from sgnl protocol link');
mainWindow.webContents.send('show-group-via-link', { hash });
} else if (command === 'signal.me' && hash) {
getLogger().info('Showing conversation from sgnl protocol link');
mainWindow.webContents.send('show-conversation-via-signal.me', { hash });
} else if (
command === 'show-conversation' &&
args &&
args.get('conversationId')
) {
getLogger().info('Showing conversation from notification');
mainWindow.webContents.send('show-conversation-via-notification', {
conversationId: args.get('conversationId'),
messageId: args.get('messageId'),
storyId: args.get('storyId'),
});
} else if (
command === 'start-call-lobby' &&
args &&
args.get('conversationId')
) {
getLogger().info('Starting call lobby from notification');
mainWindow.webContents.send('start-call-lobby', {
conversationId: args.get('conversationId'),
});
} else if (command === 'show-window') {
mainWindow.webContents.send('show-window');
} else if (command === 'set-is-presenting') {
mainWindow.webContents.send('set-is-presenting');
} else {
getLogger().info('Showing warning that we cannot process link');
mainWindow.webContents.send('unknown-sgnl-link');
}
if (route.key === 'artAddStickers') {
mainWindow.webContents.send('show-sticker-pack', {
packId: route.args.packId,
packKey: Buffer.from(route.args.packKey, 'hex').toString('base64'),
});
} else if (route.key === 'artAuth') {
mainWindow.webContents.send('authorize-art-creator', {
token: route.args.token,
pubKeyBase64: route.args.pubKey,
});
} else if (route.key === 'groupInvites') {
mainWindow.webContents.send('show-group-via-link', {
value: route.args.inviteCode,
});
} else if (route.key === 'contactByPhoneNumber') {
mainWindow.webContents.send('show-conversation-via-signal.me', {
kind: 'phoneNumber',
value: route.args.phoneNumber,
});
} else if (route.key === 'contactByEncryptedUsername') {
mainWindow.webContents.send('show-conversation-via-signal.me', {
kind: 'encryptedUsername',
value: route.args.encryptedUsername,
});
} else if (route.key === 'showConversation') {
mainWindow.webContents.send('show-conversation-via-notification', {
conversationId: route.args.conversationId,
messageId: route.args.messageId,
storyId: route.args.storyId,
});
} else if (route.key === 'startCallLobby') {
mainWindow.webContents.send('start-call-lobby', {
conversationId: route.args.conversationId,
});
} else if (route.key === 'showWindow') {
mainWindow.webContents.send('show-window');
} else if (route.key === 'setIsPresenting') {
mainWindow.webContents.send('set-is-presenting');
} else if (route.key === 'captcha') {
challengeHandler.handleCaptcha(route.args.captchaId);
// Show window after handling captcha
showWindow();
} else {
getLogger().error('Unhandled sgnl link');
log.info('handleSignalRoute: Unknown signal route:', route.key);
mainWindow.webContents.send('unknown-sgnl-link');
}
}
@@ -2553,6 +2693,15 @@ async function ensureFilePermissions(onlyFiles?: Array<string>) {
getLogger().info(`Finish ensuring permissions in ${Date.now() - start}ms`);
}
ipc.handle('get-media-access-status', async (_event, value) => {
// This function is not supported on Linux
if (!systemPreferences.getMediaAccessStatus) {
return undefined;
}
return systemPreferences.getMediaAccessStatus(value);
});
ipc.handle('get-auto-launch', async () => {
return app.getLoginItemSettings(await getDefaultLoginItemSettings())
.openAtLogin;
@@ -2601,13 +2750,16 @@ ipc.handle('show-save-dialog', async (_event, { defaultPath }) => {
return { canceled: false, filePath: finalFilePath };
});
ipc.handle('getScreenCaptureSources', async () => {
return desktopCapturer.getSources({
fetchWindowIcons: true,
thumbnailSize: { height: 102, width: 184 },
types: ['window', 'screen'],
});
});
ipc.handle(
'getScreenCaptureSources',
async (_event, types: Array<'screen' | 'window'> = ['screen', 'window']) => {
return desktopCapturer.getSources({
fetchWindowIcons: true,
thumbnailSize: { height: 102, width: 184 },
types,
});
}
);
ipc.handle('executeMenuRole', async ({ sender }, untypedRole) => {
const role = untypedRole as MenuItemConstructorOptions['role'];
@@ -2788,6 +2940,10 @@ async function showStickerCreatorWindow() {
}
if (isTestEnvironment(getEnvironment())) {
ipc.handle('ci:test-electron:debug', async (_event, info) => {
process.stdout.write(`ci:test-electron:debug=${JSON.stringify(info)}\n`);
});
ipc.handle('ci:test-electron:done', async (_event, info) => {
if (!process.env.TEST_QUIT_ON_COMPLETE) {
return;
+1 -2
View File
@@ -42,9 +42,8 @@ export function _urlToPath(
: decoded.slice(options?.isWindows ? 8 : 7);
const withoutQuerystring = _eliminateAllAfterCharacter(withoutScheme, '?');
const withoutHash = _eliminateAllAfterCharacter(withoutQuerystring, '#');
return withoutHash;
return withoutQuerystring;
}
function _createFileHandler({
+16 -12
View File
@@ -8,6 +8,12 @@ import type { WindowsNotificationData } from '../ts/services/notifications';
import { NotificationType } from '../ts/services/notifications';
import { missingCaseError } from '../ts/util/missingCaseError';
import {
setIsPresentingRoute,
showConversationRoute,
showWindowRoute,
startCallLobbyRoute,
} from '../ts/util/signalRoutes';
function pathToUri(path: string) {
return `file:///${encodeURI(path.replace(/\\/g, '/'))}`;
@@ -51,21 +57,19 @@ export function renderWindowsToast({
// 1) this maps to the notify() function in services/notifications.ts
// 2) this also maps to the url-handling in main.ts
if (type === NotificationType.Message || type === NotificationType.Reaction) {
launch = new URL('sgnl://show-conversation');
launch.searchParams.set('conversationId', conversationId);
if (messageId) {
launch.searchParams.set('messageId', messageId);
}
if (storyId) {
launch.searchParams.set('storyId', storyId);
}
launch = showConversationRoute.toAppUrl({
conversationId,
messageId: messageId ?? null,
storyId: storyId ?? null,
});
} else if (type === NotificationType.IncomingGroupCall) {
launch = new URL(`sgnl://start-call-lobby`);
launch.searchParams.set('conversationId', conversationId);
launch = startCallLobbyRoute.toAppUrl({
conversationId,
});
} else if (type === NotificationType.IncomingCall) {
launch = new URL('sgnl://show-window');
launch = showWindowRoute.toAppUrl({});
} else if (type === NotificationType.IsPresenting) {
launch = new URL('sgnl://set-is-presenting');
launch = setIsPresentingRoute.toAppUrl({});
} else {
throw missingCaseError(type);
}
+27 -7
View File
@@ -11,6 +11,7 @@ import { maybeParseUrl } from '../ts/util/url';
import type { MenuListType } from '../ts/types/menu';
import type { LocalizerType } from '../ts/types/Util';
import { strictAssert } from '../ts/util/assert';
import type { LoggerType } from '../ts/types/Logging';
export const FAKE_DEFAULT_LOCALE = 'en-x-ignore'; // -x- is an extension space for attaching other metadata to the locale
@@ -55,16 +56,35 @@ export function getLanguages(
export const setup = (
browserWindow: BrowserWindow,
preferredSystemLocales: ReadonlyArray<string>,
i18n: LocalizerType
localeOverride: string | null,
i18n: LocalizerType,
logger: LoggerType
): void => {
const { session } = browserWindow.webContents;
session.on('spellcheck-dictionary-download-begin', (_event, lang) => {
logger.info('spellcheck: dictionary download begin:', lang);
});
session.on('spellcheck-dictionary-download-failure', (_event, lang) => {
logger.error('spellcheck: dictionary download failure:', lang);
});
session.on('spellcheck-dictionary-download-success', (_event, lang) => {
logger.info('spellcheck: dictionary download success:', lang);
});
session.on('spellcheck-dictionary-initialized', (_event, lang) => {
logger.info('spellcheck: dictionary initialized:', lang);
});
// Locale override should be combined with other preferences rather than
// replace them entirely.
const combinedLocales =
localeOverride != null
? [localeOverride, ...preferredSystemLocales]
: preferredSystemLocales;
const availableLocales = session.availableSpellCheckerLanguages;
const languages = getLanguages(
preferredSystemLocales,
availableLocales,
'en'
);
console.log('spellcheck: user locales:', preferredSystemLocales);
const languages = getLanguages(combinedLocales, availableLocales, 'en');
console.log('spellcheck: user locales:', combinedLocales);
console.log(
'spellcheck: available spellchecker languages:',
availableLocales