Improve error handling during group sends

This commit is contained in:
Fedor Indutny 2022-11-22 10:43:43 -08:00 committed by GitHub
parent f0a3735ca2
commit 991580a1ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
58 changed files with 299 additions and 324 deletions

View file

@ -13,27 +13,28 @@ let quitText = 'Quit';
let copyErrorAndQuitText = 'Copy error and quit'; let copyErrorAndQuitText = 'Copy error and quit';
function handleError(prefix: string, error: Error): void { function handleError(prefix: string, error: Error): void {
const formattedError = Errors.toLogFormat(error);
if (console._error) { if (console._error) {
console._error(`${prefix}:`, Errors.toLogFormat(error)); console._error(`${prefix}:`, formattedError);
} }
console.error(`${prefix}:`, Errors.toLogFormat(error)); console.error(`${prefix}:`, formattedError);
if (app.isReady()) { if (app.isReady()) {
// title field is not shown on macOS, so we don't use it // title field is not shown on macOS, so we don't use it
const buttonIndex = dialog.showMessageBoxSync({ const buttonIndex = dialog.showMessageBoxSync({
buttons: [quitText, copyErrorAndQuitText], buttons: [quitText, copyErrorAndQuitText],
defaultId: 0, defaultId: 0,
detail: redactAll(error.stack || ''), detail: redactAll(formattedError),
message: prefix, message: prefix,
noLink: true, noLink: true,
type: 'error', type: 'error',
}); });
if (buttonIndex === 1) { if (buttonIndex === 1) {
clipboard.writeText(`${prefix}\n\n${redactAll(error.stack || '')}`); clipboard.writeText(`${prefix}\n\n${redactAll(formattedError)}`);
} }
} else { } else {
dialog.showErrorBox(prefix, error.stack || ''); dialog.showErrorBox(prefix, formattedError);
} }
app.exit(1); app.exit(1);

View file

@ -46,6 +46,7 @@ import { strictAssert } from '../ts/util/assert';
import { consoleLogger } from '../ts/util/consoleLogger'; import { consoleLogger } from '../ts/util/consoleLogger';
import type { ThemeSettingType } from '../ts/types/StorageUIKeys'; import type { ThemeSettingType } from '../ts/types/StorageUIKeys';
import { ThemeType } from '../ts/types/Util'; import { ThemeType } from '../ts/types/Util';
import * as Errors from '../ts/types/errors';
import './startup_config'; import './startup_config';
@ -471,7 +472,7 @@ async function handleUrl(event: Electron.Event, rawTarget: string) {
try { try {
await shell.openExternal(target); await shell.openExternal(target);
} catch (error) { } catch (error) {
getLogger().error(`Failed to open url: ${error.stack}`); getLogger().error(`Failed to open url: ${Errors.toLogFormat(error)}`);
} }
} }
} }
@ -938,7 +939,7 @@ ipc.handle('database-ready', async () => {
if (error) { if (error) {
getLogger().error( getLogger().error(
'database-ready requested, but got sql error', 'database-ready requested, but got sql error',
error && error.stack Errors.toLogFormat(error)
); );
return; return;
} }
@ -1029,7 +1030,7 @@ async function readyForUpdates() {
} catch (error) { } catch (error) {
getLogger().error( getLogger().error(
'Error starting update checks:', 'Error starting update checks:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -1039,10 +1040,7 @@ async function forceUpdate() {
getLogger().info('starting force update'); getLogger().info('starting force update');
await updater.force(); await updater.force();
} catch (error) { } catch (error) {
getLogger().error( getLogger().error('Error during force update:', Errors.toLogFormat(error));
'Error during force update:',
error && error.stack ? error.stack : error
);
} }
} }
@ -1477,7 +1475,7 @@ const runSQLCorruptionHandler = async () => {
`Restarting the application immediately. Error: ${error.message}` `Restarting the application immediately. Error: ${error.message}`
); );
await onDatabaseError(error.stack || error.message); await onDatabaseError(Errors.toLogFormat(error));
}; };
async function initializeSQL( async function initializeSQL(
@ -1796,7 +1794,7 @@ app.on('ready', async () => {
} catch (err) { } catch (err) {
logger.error( logger.error(
'main/ready: Error deleting temp dir:', 'main/ready: Error deleting temp dir:',
err && err.stack ? err.stack : err Errors.toLogFormat(err)
); );
} }
@ -1823,7 +1821,7 @@ app.on('ready', async () => {
if (sqlError) { if (sqlError) {
getLogger().error('sql.initialize was unsuccessful; returning early'); getLogger().error('sql.initialize was unsuccessful; returning early');
await onDatabaseError(sqlError.stack || sqlError.message); await onDatabaseError(Errors.toLogFormat(sqlError));
return; return;
} }
@ -1840,7 +1838,7 @@ app.on('ready', async () => {
} catch (err) { } catch (err) {
getLogger().error( getLogger().error(
'(ready event handler) error deleting IndexedDB:', '(ready event handler) error deleting IndexedDB:',
err && err.stack ? err.stack : err Errors.toLogFormat(err)
); );
} }
@ -1949,10 +1947,7 @@ async function requestShutdown() {
try { try {
await request; await request;
} catch (error) { } catch (error) {
getLogger().error( getLogger().error('requestShutdown error:', Errors.toLogFormat(error));
'requestShutdown error:',
error && error.stack ? error.stack : error
);
} }
} }
@ -2157,7 +2152,7 @@ ipc.handle(
} catch (error) { } catch (error) {
getLogger().error( getLogger().error(
'show-permissions-popup error:', 'show-permissions-popup error:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -2200,7 +2195,10 @@ ipc.on('get-built-in-images', async () => {
if (mainWindow && mainWindow.webContents) { if (mainWindow && mainWindow.webContents) {
mainWindow.webContents.send('get-success-built-in-images', error.message); mainWindow.webContents.send('get-success-built-in-images', error.message);
} else { } else {
getLogger().error('Error handling get-built-in-images:', error.stack); getLogger().error(
'Error handling get-built-in-images:',
Errors.toLogFormat(error)
);
} }
} }
}); });

View file

@ -7,6 +7,7 @@ import { app } from 'electron';
import { start } from './base_config'; import { start } from './base_config';
import config from './config'; import config from './config';
import * as Errors from '../ts/types/errors';
let userData: string | undefined; let userData: string | undefined;
// Use separate data directory for benchmarks & development // Use separate data directory for benchmarks & development
@ -23,7 +24,7 @@ if (userData !== undefined) {
try { try {
mkdirSync(userData, { recursive: true }); mkdirSync(userData, { recursive: true });
} catch (error) { } catch (error) {
console.error('Failed to create userData', error?.stack || String(error)); console.error('Failed to create userData', Errors.toLogFormat(error));
} }
app.setPath('userData', userData); app.setPath('userData', userData);

View file

@ -13,7 +13,9 @@ import type { Props as DropZoneProps } from '../elements/DropZone';
import { DropZone } from '../elements/DropZone'; import { DropZone } from '../elements/DropZone';
import { processStickerImage } from '../util/preload'; import { processStickerImage } from '../util/preload';
import { useI18n } from '../util/i18n'; import { useI18n } from '../util/i18n';
import { ProcessStickerImageError } from '../errors';
import { MINUTE } from '../../ts/util/durations'; import { MINUTE } from '../../ts/util/durations';
import * as Errors from '../../ts/types/errors';
const queue = new PQueue({ concurrency: 3, timeout: MINUTE * 30 }); const queue = new PQueue({ concurrency: 3, timeout: MINUTE * 30 });
@ -64,13 +66,16 @@ const InnerGrid = SortableContainer(
} catch (e) { } catch (e) {
window.SignalContext.log.error( window.SignalContext.log.error(
'Error processing image:', 'Error processing image:',
e?.stack ? e.stack : String(e) Errors.toLogFormat(e)
); );
actions.removeSticker(path); actions.removeSticker(path);
const key =
e instanceof ProcessStickerImageError
? e.errorMessageI18nKey
: 'StickerCreator--Toasts--errorProcessing';
actions.addToast({ actions.addToast({
key: key,
(e || {}).errorMessageI18nKey ||
'StickerCreator--Toasts--errorProcessing',
}); });
} }
}); });

View file

@ -0,0 +1,8 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
export class ProcessStickerImageError extends Error {
constructor(message: string, public readonly errorMessageI18nKey: string) {
super(message);
}
}

View file

@ -18,6 +18,7 @@ import { initialize as initializeWebAPI } from '../../ts/textsecure/WebAPI';
import { SignalContext } from '../../ts/windows/context'; import { SignalContext } from '../../ts/windows/context';
import { getAnimatedPngDataIfExists } from '../../ts/util/getAnimatedPngDataIfExists'; import { getAnimatedPngDataIfExists } from '../../ts/util/getAnimatedPngDataIfExists';
import { ProcessStickerImageError } from '../errors';
const STICKER_SIZE = 512; const STICKER_SIZE = 512;
const MIN_STICKER_DIMENSION = 10; const MIN_STICKER_DIMENSION = 10;
@ -42,12 +43,6 @@ const WebAPI = initializeWebAPI({
version: config.version, version: config.version,
}); });
function processStickerError(message: string, i18nKey: string): Error {
const result = new Error(message);
result.errorMessageI18nKey = i18nKey;
return result;
}
window.processStickerImage = async (path: string | undefined) => { window.processStickerImage = async (path: string | undefined) => {
if (!path) { if (!path) {
throw new Error(`Path ${path} is not valid!`); throw new Error(`Path ${path} is not valid!`);
@ -59,7 +54,7 @@ window.processStickerImage = async (path: string | undefined) => {
const { width, height } = meta; const { width, height } = meta;
if (!width || !height) { if (!width || !height) {
throw processStickerError( throw new ProcessStickerImageError(
'Sticker height or width were falsy', 'Sticker height or width were falsy',
'StickerCreator--Toasts--errorProcessing' 'StickerCreator--Toasts--errorProcessing'
); );
@ -75,31 +70,31 @@ window.processStickerImage = async (path: string | undefined) => {
const animatedPngDataIfExists = getAnimatedPngDataIfExists(imgBuffer); const animatedPngDataIfExists = getAnimatedPngDataIfExists(imgBuffer);
if (animatedPngDataIfExists) { if (animatedPngDataIfExists) {
if (imgBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) { if (imgBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) {
throw processStickerError( throw new ProcessStickerImageError(
'Sticker file was too large', 'Sticker file was too large',
'StickerCreator--Toasts--tooLarge' 'StickerCreator--Toasts--tooLarge'
); );
} }
if (width !== height) { if (width !== height) {
throw processStickerError( throw new ProcessStickerImageError(
'Sticker must be square', 'Sticker must be square',
'StickerCreator--Toasts--APNG--notSquare' 'StickerCreator--Toasts--APNG--notSquare'
); );
} }
if (width > MAX_STICKER_DIMENSION) { if (width > MAX_STICKER_DIMENSION) {
throw processStickerError( throw new ProcessStickerImageError(
'Sticker dimensions are too large', 'Sticker dimensions are too large',
'StickerCreator--Toasts--APNG--dimensionsTooLarge' 'StickerCreator--Toasts--APNG--dimensionsTooLarge'
); );
} }
if (width < MIN_STICKER_DIMENSION) { if (width < MIN_STICKER_DIMENSION) {
throw processStickerError( throw new ProcessStickerImageError(
'Sticker dimensions are too small', 'Sticker dimensions are too small',
'StickerCreator--Toasts--APNG--dimensionsTooSmall' 'StickerCreator--Toasts--APNG--dimensionsTooSmall'
); );
} }
if (animatedPngDataIfExists.numPlays !== Infinity) { if (animatedPngDataIfExists.numPlays !== Infinity) {
throw processStickerError( throw new ProcessStickerImageError(
'Animated stickers must loop forever', 'Animated stickers must loop forever',
'StickerCreator--Toasts--mustLoopForever' 'StickerCreator--Toasts--mustLoopForever'
); );
@ -118,7 +113,7 @@ window.processStickerImage = async (path: string | undefined) => {
.webp() .webp()
.toBuffer(); .toBuffer();
if (processedBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) { if (processedBuffer.byteLength > MAX_STICKER_BYTE_LENGTH) {
throw processStickerError( throw new ProcessStickerImageError(
'Sticker file was too large', 'Sticker file was too large',
'StickerCreator--Toasts--tooLarge' 'StickerCreator--Toasts--tooLarge'
); );

View file

@ -299,7 +299,7 @@ export class ConversationController {
log.error( log.error(
'Contact is not valid. Not saving, but adding to collection:', 'Contact is not valid. Not saving, but adding to collection:',
conversation.idForLogging(), conversation.idForLogging(),
validationError.stack Errors.toLogFormat(validationError)
); );
return conversation; return conversation;
@ -316,7 +316,7 @@ export class ConversationController {
identifier, identifier,
type, type,
'Error:', 'Error:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
throw error; throw error;
} }
@ -1247,7 +1247,7 @@ export class ConversationController {
} catch (error) { } catch (error) {
log.error( log.error(
'ConversationController.load/map: Failed to prepare a conversation', 'ConversationController.load/map: Failed to prepare a conversation',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}) })
@ -1256,7 +1256,7 @@ export class ConversationController {
} catch (error) { } catch (error) {
log.error( log.error(
'ConversationController: initial fetch failed', 'ConversationController: initial fetch failed',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
throw error; throw error;
} }

View file

@ -392,7 +392,7 @@ export class SignalProtocolStore extends EventEmitter {
} catch (error) { } catch (error) {
log.error( log.error(
'removePreKey error triggering removePreKey:', 'removePreKey error triggering removePreKey:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -604,7 +604,7 @@ export class SignalProtocolStore extends EventEmitter {
await this.commitZoneChanges('saveSenderKey'); await this.commitZoneChanges('saveSenderKey');
} }
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error; const errorString = Errors.toLogFormat(error);
log.error( log.error(
`saveSenderKey: failed to save senderKey ${senderId}/${distributionId}: ${errorString}` `saveSenderKey: failed to save senderKey ${senderId}/${distributionId}: ${errorString}`
); );
@ -653,7 +653,7 @@ export class SignalProtocolStore extends EventEmitter {
log.info('Successfully fetched sender key(cache miss):', id); log.info('Successfully fetched sender key(cache miss):', id);
return item; return item;
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error; const errorString = Errors.toLogFormat(error);
log.error( log.error(
`getSenderKey: failed to load sender key ${senderId}/${distributionId}: ${errorString}` `getSenderKey: failed to load sender key ${senderId}/${distributionId}: ${errorString}`
); );
@ -679,7 +679,7 @@ export class SignalProtocolStore extends EventEmitter {
this.senderKeys.delete(id); this.senderKeys.delete(id);
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error; const errorString = Errors.toLogFormat(error);
log.error( log.error(
`removeSenderKey: failed to remove senderKey ${senderId}/${distributionId}: ${errorString}` `removeSenderKey: failed to remove senderKey ${senderId}/${distributionId}: ${errorString}`
); );
@ -860,7 +860,7 @@ export class SignalProtocolStore extends EventEmitter {
`pending sender keys size ${this.pendingSenderKeys.size}, ` + `pending sender keys size ${this.pendingSenderKeys.size}, ` +
`pending sessions size ${this.pendingSessions.size}, ` + `pending sessions size ${this.pendingSessions.size}, ` +
`pending unprocessed size ${this.pendingUnprocessed.size}`, `pending unprocessed size ${this.pendingUnprocessed.size}`,
error && error.stack Errors.toLogFormat(error)
); );
this.pendingSenderKeys.clear(); this.pendingSenderKeys.clear();
this.pendingSessions.clear(); this.pendingSessions.clear();
@ -961,7 +961,7 @@ export class SignalProtocolStore extends EventEmitter {
// and save it to the database. // and save it to the database.
return await this._maybeMigrateSession(entry.fromDB, { zone }); return await this._maybeMigrateSession(entry.fromDB, { zone });
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error; const errorString = Errors.toLogFormat(error);
log.error(`loadSession: failed to load session ${id}: ${errorString}`); log.error(`loadSession: failed to load session ${id}: ${errorString}`);
return undefined; return undefined;
} }
@ -1095,7 +1095,7 @@ export class SignalProtocolStore extends EventEmitter {
await this.commitZoneChanges('storeSession'); await this.commitZoneChanges('storeSession');
} }
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error; const errorString = Errors.toLogFormat(error);
log.error(`storeSession: Save failed for ${id}: ${errorString}`); log.error(`storeSession: Save failed for ${id}: ${errorString}`);
throw error; throw error;
} }
@ -1189,7 +1189,7 @@ export class SignalProtocolStore extends EventEmitter {
} catch (error) { } catch (error) {
log.error( log.error(
'getOpenDevices: Failed to get devices', 'getOpenDevices: Failed to get devices',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
throw error; throw error;
} }
@ -1692,7 +1692,7 @@ export class SignalProtocolStore extends EventEmitter {
} catch (error) { } catch (error) {
log.error( log.error(
'saveIdentity: error triggering keychange:', 'saveIdentity: error triggering keychange:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }

View file

@ -609,7 +609,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.info( log.info(
'User chose not to delete old data. Shutting down.', 'User chose not to delete old data. Shutting down.',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
window.shutdown(); window.shutdown();
return; return;
@ -627,7 +627,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'Failed to remove IndexedDB file or remove SQL data:', 'Failed to remove IndexedDB file or remove SQL data:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -859,7 +859,7 @@ export async function startApp(): Promise<void> {
try { try {
await window.Signal.Data.startInRendererProcess(); await window.Signal.Data.startInRendererProcess();
} catch (err) { } catch (err) {
log.error('SQL failed to initialize', err && err.stack ? err.stack : err); log.error('SQL failed to initialize', Errors.toLogFormat(err));
} }
setAppLoadingScreenMessage(window.i18n('loading'), window.i18n); setAppLoadingScreenMessage(window.i18n('loading'), window.i18n);
@ -950,7 +950,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.warn( log.warn(
'background/setInterval: Failed to parse integer from desktop.retryRespondMaxAge feature flag', 'background/setInterval: Failed to parse integer from desktop.retryRespondMaxAge feature flag',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -961,7 +961,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'background/onready/setInterval: Error deleting sent protos: ', 'background/onready/setInterval: Error deleting sent protos: ',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -991,7 +991,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'background/onready/setInterval: Error getting expired retry placeholders: ', 'background/onready/setInterval: Error getting expired retry placeholders: ',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}, FIVE_MINUTES); }, FIVE_MINUTES);
@ -1038,7 +1038,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'background.js: ConversationController failed to load:', 'background.js: ConversationController failed to load:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} finally { } finally {
initializeRedux({ mainWindowStats, menuOptions }); initializeRedux({ mainWindowStats, menuOptions });
@ -2140,7 +2140,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'connect: Error refreshing remote config:', 'connect: Error refreshing remote config:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -2226,7 +2226,7 @@ export async function startApp(): Promise<void> {
} catch (e) { } catch (e) {
log.error( log.error(
'Problem with account manager updates after starting new version: ', 'Problem with account manager updates after starting new version: ',
e && e.stack ? e.stack : e Errors.toLogFormat(e)
); );
} }
} }
@ -2239,7 +2239,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'Error: Unable to register for unauthenticated delivery support.', 'Error: Unable to register for unauthenticated delivery support.',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -2270,7 +2270,7 @@ export async function startApp(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'Error: Unable to register our capabilities.', 'Error: Unable to register our capabilities.',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -2859,7 +2859,10 @@ export async function startApp(): Promise<void> {
return; return;
} }
} catch (error) { } catch (error) {
log.error('respondWithProfileKeyBatcher error', error && error.stack); log.error(
'respondWithProfileKeyBatcher error',
Errors.toLogFormat(error)
);
} }
sender.queueJob('sendProfileKeyUpdate', () => sender.queueJob('sendProfileKeyUpdate', () =>
@ -3521,7 +3524,7 @@ export async function startApp(): Promise<void> {
log.error( log.error(
'unlinkAndDisconnect: Something went wrong clearing ' + 'unlinkAndDisconnect: Something went wrong clearing ' +
'local configuration', 'local configuration',
eraseError && eraseError.stack ? eraseError.stack : eraseError Errors.toLogFormat(eraseError)
); );
} finally { } finally {
window.Signal.Util.Registration.markEverDone(); window.Signal.Util.Registration.markEverDone();

View file

@ -15,6 +15,7 @@ import type { ExecuteMenuRoleType } from './TitleBarContainer';
import { ToastLoadingFullLogs } from './ToastLoadingFullLogs'; import { ToastLoadingFullLogs } from './ToastLoadingFullLogs';
import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser'; import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser';
import { createSupportUrl } from '../util/createSupportUrl'; import { createSupportUrl } from '../util/createSupportUrl';
import * as Errors from '../types/errors';
import { useEscapeHandling } from '../hooks/useEscapeHandling'; import { useEscapeHandling } from '../hooks/useEscapeHandling';
import { useTheme } from '../hooks/useTheme'; import { useTheme } from '../hooks/useTheme';
@ -107,10 +108,7 @@ export function DebugLogWindow({
const publishedLogURL = await uploadLogs(text); const publishedLogURL = await uploadLogs(text);
setPublicLogURL(publishedLogURL); setPublicLogURL(publishedLogURL);
} catch (error) { } catch (error) {
log.error( log.error('DebugLogWindow error:', Errors.toLogFormat(error));
'DebugLogWindow error:',
error && error.stack ? error.stack : error
);
setLoadState(LoadState.Loaded); setLoadState(LoadState.Loaded);
setToastType(ToastType.Error); setToastType(ToastType.Error);
} }

View file

@ -14,6 +14,7 @@ import {
getImageDimensions, getImageDimensions,
defaultBlurHash, defaultBlurHash,
} from '../../types/Attachment'; } from '../../types/Attachment';
import * as Errors from '../../types/errors';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
const MAX_GIF_REPEAT = 4; const MAX_GIF_REPEAT = 4;
@ -90,7 +91,7 @@ export function GIF(props: Props): JSX.Element {
video.play().catch(error => { video.play().catch(error => {
log.info( log.info(
"Failed to match GIF playback to window's state", "Failed to match GIF playback to window's state",
(error && error.stack) || error Errors.toLogFormat(error)
); );
}); });
} else { } else {

View file

@ -453,7 +453,10 @@ async function uploadAvatar(
key, key,
}; };
} catch (error) { } catch (error) {
log.warn(`uploadAvatar/${logId} Failed to upload avatar`, error.stack); log.warn(
`uploadAvatar/${logId} Failed to upload avatar`,
Errors.toLogFormat(error)
);
throw error; throw error;
} }
} }
@ -2391,7 +2394,7 @@ export async function initiateMigrationToGroupV2(
} catch (error) { } catch (error) {
log.error( log.error(
`initiateMigrationToGroupV2/${logId}: Error creating group:`, `initiateMigrationToGroupV2/${logId}: Error creating group:`,
error.stack Errors.toLogFormat(error)
); );
throw error; throw error;
@ -2473,7 +2476,7 @@ export async function waitThenRespondToGroupV2Migration(
} catch (error) { } catch (error) {
log.error( log.error(
`waitThenRespondToGroupV2Migration/${conversation.idForLogging()}: respondToGroupV2Migration failure:`, `waitThenRespondToGroupV2Migration/${conversation.idForLogging()}: respondToGroupV2Migration failure:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}); });
@ -2946,7 +2949,7 @@ export async function waitThenMaybeUpdateGroup(
} catch (error) { } catch (error) {
log.error( log.error(
`waitThenMaybeUpdateGroup/${conversation.idForLogging()}: maybeUpdateGroup failure:`, `waitThenMaybeUpdateGroup/${conversation.idForLogging()}: maybeUpdateGroup failure:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}); });
@ -2984,7 +2987,7 @@ export async function maybeUpdateGroup(
} catch (error) { } catch (error) {
log.error( log.error(
`maybeUpdateGroup/${logId}: Failed to update group:`, `maybeUpdateGroup/${logId}: Failed to update group:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
throw error; throw error;
} }
@ -3910,7 +3913,7 @@ async function integrateGroupChanges({
} catch (error) { } catch (error) {
log.error( log.error(
`integrateGroupChanges/${logId}: Failed to apply change log, continuing to apply remaining change logs.`, `integrateGroupChanges/${logId}: Failed to apply change log, continuing to apply remaining change logs.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -5287,7 +5290,7 @@ export async function applyNewAvatar(
} catch (error) { } catch (error) {
log.warn( log.warn(
`applyNewAvatar/${logId} Failed to handle avatar, clearing it`, `applyNewAvatar/${logId} Failed to handle avatar, clearing it`,
error.stack Errors.toLogFormat(error)
); );
if (result.avatar && result.avatar.path) { if (result.avatar && result.avatar.path) {
await window.Signal.Migrations.deleteAttachmentData(result.avatar.path); await window.Signal.Migrations.deleteAttachmentData(result.avatar.path);
@ -5622,7 +5625,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt sourceUuid.`, `decryptGroupChange/${logId}: Unable to decrypt sourceUuid.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -5677,7 +5680,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt deleteMembers.deletedUserId. Dropping member.`, `decryptGroupChange/${logId}: Unable to decrypt deleteMembers.deletedUserId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return null; return null;
} }
@ -5711,7 +5714,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt modifyMemberRole.userId. Dropping member.`, `decryptGroupChange/${logId}: Unable to decrypt modifyMemberRole.userId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return null; return null;
} }
@ -5844,7 +5847,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt deletePendingMembers.deletedUserId. Dropping member.`, `decryptGroupChange/${logId}: Unable to decrypt deletePendingMembers.deletedUserId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return null; return null;
} }
@ -6022,7 +6025,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt modifyTitle.title`, `decryptGroupChange/${logId}: Unable to decrypt modifyTitle.title`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} else { } else {
@ -6049,7 +6052,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt modifyDisappearingMessagesTimer.timer`, `decryptGroupChange/${logId}: Unable to decrypt modifyDisappearingMessagesTimer.timer`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} else { } else {
@ -6152,7 +6155,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt deletePendingApproval.deletedUserId. Dropping member.`, `decryptGroupChange/${logId}: Unable to decrypt deletePendingApproval.deletedUserId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return null; return null;
} }
@ -6190,7 +6193,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt promoteAdminApproval.userId. Dropping member.`, `decryptGroupChange/${logId}: Unable to decrypt promoteAdminApproval.userId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return null; return null;
} }
@ -6232,7 +6235,7 @@ function decryptGroupChange(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupChange/${logId}: Unable to decrypt modifyDescription.descriptionBytes`, `decryptGroupChange/${logId}: Unable to decrypt modifyDescription.descriptionBytes`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} else { } else {
@ -6365,7 +6368,7 @@ function decryptGroupState(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupState/${logId}: Unable to decrypt title. Clearing it.`, `decryptGroupState/${logId}: Unable to decrypt title. Clearing it.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -6388,7 +6391,7 @@ function decryptGroupState(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupState/${logId}: Unable to decrypt disappearing message timer. Clearing it.`, `decryptGroupState/${logId}: Unable to decrypt disappearing message timer. Clearing it.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -6472,7 +6475,7 @@ function decryptGroupState(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptGroupState/${logId}: Unable to decrypt descriptionBytes. Clearing it.`, `decryptGroupState/${logId}: Unable to decrypt descriptionBytes. Clearing it.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -6537,7 +6540,7 @@ function decryptMember(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptMember/${logId}: Unable to decrypt member userid. Dropping member.`, `decryptMember/${logId}: Unable to decrypt member userid. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return undefined; return undefined;
} }
@ -6608,7 +6611,7 @@ function decryptMemberPendingProfileKey(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptMemberPendingProfileKey/${logId}: Unable to decrypt pending member addedByUserId. Dropping member.`, `decryptMemberPendingProfileKey/${logId}: Unable to decrypt pending member addedByUserId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return undefined; return undefined;
} }
@ -6648,7 +6651,7 @@ function decryptMemberPendingProfileKey(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptMemberPendingProfileKey/${logId}: Unable to decrypt pending member userId. Dropping member.`, `decryptMemberPendingProfileKey/${logId}: Unable to decrypt pending member userId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return undefined; return undefined;
} }
@ -6673,7 +6676,7 @@ function decryptMemberPendingProfileKey(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptMemberPendingProfileKey/${logId}: Unable to decrypt pending member profileKey. Dropping profileKey.`, `decryptMemberPendingProfileKey/${logId}: Unable to decrypt pending member profileKey. Dropping profileKey.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -6735,7 +6738,7 @@ function decryptMemberPendingAdminApproval(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptMemberPendingAdminApproval/${logId}: Unable to decrypt pending member userId. Dropping member.`, `decryptMemberPendingAdminApproval/${logId}: Unable to decrypt pending member userId. Dropping member.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return undefined; return undefined;
} }
@ -6760,7 +6763,7 @@ function decryptMemberPendingAdminApproval(
} catch (error) { } catch (error) {
log.warn( log.warn(
`decryptMemberPendingAdminApproval/${logId}: Unable to decrypt profileKey. Dropping profileKey.`, `decryptMemberPendingAdminApproval/${logId}: Unable to decrypt profileKey. Dropping profileKey.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }

View file

@ -19,6 +19,7 @@ import { read as readLastLines } from 'read-last-lines';
import rimraf from 'rimraf'; import rimraf from 'rimraf';
import type { LoggerType } from '../types/Logging'; import type { LoggerType } from '../types/Logging';
import * as Errors from '../types/errors';
import * as durations from '../util/durations'; import * as durations from '../util/durations';
import { createRotatingPinoDest } from '../util/rotatingPinoDest'; import { createRotatingPinoDest } from '../util/rotatingPinoDest';
@ -67,7 +68,9 @@ export async function initialize(
try { try {
await cleanupLogs(logPath); await cleanupLogs(logPath);
} catch (error) { } catch (error) {
const errorString = `Failed to clean logs; deleting all. Error: ${error.stack}`; const errorString =
'Failed to clean logs; deleting all. ' +
`Error: ${Errors.toLogFormat(error)}`;
console.error(errorString); console.error(errorString);
await deleteAllLogs(logPath); await deleteAllLogs(logPath);
mkdirp.sync(logPath); mkdirp.sync(logPath);
@ -136,7 +139,7 @@ export async function initialize(
...rest, ...rest,
}; };
} catch (error) { } catch (error) {
logger.error(`Problem loading log data: ${error.stack}`); logger.error(`Problem loading log data: ${Errors.toLogFormat(error)}`);
return; return;
} }
@ -151,7 +154,7 @@ export async function initialize(
try { try {
await deleteAllLogs(logPath); await deleteAllLogs(logPath);
} catch (error) { } catch (error) {
logger.error(`Problem deleting all logs: ${error.stack}`); logger.error(`Problem deleting all logs: ${Errors.toLogFormat(error)}`);
} }
}); });
@ -196,7 +199,7 @@ async function cleanupLogs(logPath: string) {
} catch (error) { } catch (error) {
console.error( console.error(
'Error cleaning logs; deleting and starting over from scratch.', 'Error cleaning logs; deleting and starting over from scratch.',
error.stack Errors.toLogFormat(error)
); );
// delete and re-create the log directory // delete and re-create the log directory
@ -215,7 +218,7 @@ export function isLineAfterDate(line: string, date: Readonly<Date>): boolean {
const data = JSON.parse(line); const data = JSON.parse(line);
return new Date(data.time).getTime() > date.getTime(); return new Date(data.time).getTime() > date.getTime();
} catch (e) { } catch (e) {
console.log('error parsing log line', e.stack, line); console.log('error parsing log line', Errors.toLogFormat(e), line);
return false; return false;
} }
} }

View file

@ -22,6 +22,7 @@ import {
} from './shared'; } from './shared';
import * as log from './log'; import * as log from './log';
import { Environment, getEnvironment } from '../environment'; import { Environment, getEnvironment } from '../environment';
import * as Errors from '../types/errors';
import { createRotatingPinoDest } from '../util/rotatingPinoDest'; import { createRotatingPinoDest } from '../util/rotatingPinoDest';
// Backwards-compatible logging, simple strings and no level (defaulted to INFO) // Backwards-compatible logging, simple strings and no level (defaulted to INFO)
@ -114,14 +115,13 @@ window.SignalContext.log = {
}; };
window.onerror = (_message, _script, _line, _col, error) => { window.onerror = (_message, _script, _line, _col, error) => {
const errorInfo = error && error.stack ? error.stack : JSON.stringify(error); const errorInfo = Errors.toLogFormat(error);
log.error(`Top-level unhandled error: ${errorInfo}`); log.error(`Top-level unhandled error: ${errorInfo}`);
}; };
window.addEventListener('unhandledrejection', rejectionEvent => { window.addEventListener('unhandledrejection', rejectionEvent => {
const error = rejectionEvent.reason; const error = rejectionEvent.reason;
const errorString = const errorString = Errors.toLogFormat(error);
error && error.stack ? error.stack : JSON.stringify(error);
log.error(`Top-level unhandled promise rejection: ${errorString}`); log.error(`Top-level unhandled promise rejection: ${errorString}`);
}); });

View file

@ -7,6 +7,7 @@ import { Collection, Model } from 'backbone';
import type { MessageModel } from '../models/messages'; import type { MessageModel } from '../models/messages';
import { getContactId } from '../messages/helpers'; import { getContactId } from '../messages/helpers';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
import { deleteForEveryone } from '../util/deleteForEveryone'; import { deleteForEveryone } from '../util/deleteForEveryone';
export type DeleteAttributesType = { export type DeleteAttributesType = {
@ -97,10 +98,7 @@ export class Deletes extends Collection<DeleteModel> {
this.remove(del); this.remove(del);
}); });
} catch (error) { } catch (error) {
log.error( log.error('Deletes.onDelete error:', Errors.toLogFormat(error));
'Deletes.onDelete error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -15,6 +15,7 @@ import { getOwn } from '../util/getOwn';
import { missingCaseError } from '../util/missingCaseError'; import { missingCaseError } from '../util/missingCaseError';
import { createWaitBatcher } from '../util/waitBatcher'; import { createWaitBatcher } from '../util/waitBatcher';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID';
import * as Errors from '../types/errors';
import { import {
SendActionType, SendActionType,
SendStatus, SendStatus,
@ -331,10 +332,7 @@ export class MessageReceipts extends Collection<MessageReceiptModel> {
this.remove(receipt); this.remove(receipt);
} catch (error) { } catch (error) {
log.error( log.error('MessageReceipts.onReceipt error:', Errors.toLogFormat(error));
'MessageReceipts.onReceipt error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -6,6 +6,7 @@
import { Collection, Model } from 'backbone'; import { Collection, Model } from 'backbone';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
export type MessageRequestAttributesType = { export type MessageRequestAttributesType = {
threadE164?: string; threadE164?: string;
@ -122,10 +123,7 @@ export class MessageRequests extends Collection<MessageRequestModel> {
this.remove(sync); this.remove(sync);
} catch (error) { } catch (error) {
log.error( log.error('MessageRequests.onResponse error:', Errors.toLogFormat(error));
'MessageRequests.onResponse error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -10,6 +10,7 @@ import type {
MessageAttributesType, MessageAttributesType,
ReactionAttributesType, ReactionAttributesType,
} from '../model-types.d'; } from '../model-types.d';
import * as Errors from '../types/errors';
import * as log from '../logging/log'; import * as log from '../logging/log';
import { getContactId, getContact } from '../messages/helpers'; import { getContactId, getContact } from '../messages/helpers';
import { isDirectConversation, isMe } from '../util/whatTypeOfConversation'; import { isDirectConversation, isMe } from '../util/whatTypeOfConversation';
@ -213,10 +214,7 @@ export class Reactions extends Collection<ReactionModel> {
this.remove(reaction); this.remove(reaction);
}); });
} catch (error) { } catch (error) {
log.error( log.error('Reactions.onReaction error:', Errors.toLogFormat(error));
'Reactions.onReaction error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -10,6 +10,7 @@ import { isIncoming } from '../state/selectors/message';
import { isMessageUnread } from '../util/isMessageUnread'; import { isMessageUnread } from '../util/isMessageUnread';
import { notificationService } from '../services/notifications'; import { notificationService } from '../services/notifications';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
export type ReadSyncAttributesType = { export type ReadSyncAttributesType = {
senderId: string; senderId: string;
@ -142,10 +143,7 @@ export class ReadSyncs extends Collection {
this.remove(sync); this.remove(sync);
} catch (error) { } catch (error) {
log.error( log.error('ReadSyncs.onSync error:', Errors.toLogFormat(error));
'ReadSyncs.onSync error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -6,6 +6,7 @@
import { Collection, Model } from 'backbone'; import { Collection, Model } from 'backbone';
import type { MessageModel } from '../models/messages'; import type { MessageModel } from '../models/messages';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
export type ViewOnceOpenSyncAttributesType = { export type ViewOnceOpenSyncAttributesType = {
source?: string; source?: string;
@ -94,10 +95,7 @@ export class ViewOnceOpenSyncs extends Collection<ViewOnceOpenSyncModel> {
this.remove(sync); this.remove(sync);
} catch (error) { } catch (error) {
log.error( log.error('ViewOnceOpenSyncs.onSync error:', Errors.toLogFormat(error));
'ViewOnceOpenSyncs.onSync error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -9,6 +9,7 @@ import type { MessageModel } from '../models/messages';
import { ReadStatus } from '../messages/MessageReadStatus'; import { ReadStatus } from '../messages/MessageReadStatus';
import { markViewed } from '../services/MessageUpdater'; import { markViewed } from '../services/MessageUpdater';
import { isDownloaded } from '../types/Attachment'; import { isDownloaded } from '../types/Attachment';
import * as Errors from '../types/errors';
import { isIncoming } from '../state/selectors/message'; import { isIncoming } from '../state/selectors/message';
import { notificationService } from '../services/notifications'; import { notificationService } from '../services/notifications';
import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads'; import { queueAttachmentDownloads } from '../util/queueAttachmentDownloads';
@ -111,10 +112,7 @@ export class ViewSyncs extends Collection {
this.remove(sync); this.remove(sync);
} catch (error) { } catch (error) {
log.error( log.error('ViewSyncs.onSync error:', Errors.toLogFormat(error));
'ViewSyncs.onSync error:',
error && error.stack ? error.stack : error
);
} }
} }
} }

View file

@ -1242,7 +1242,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
} catch (error) { } catch (error) {
log.error( log.error(
`Error erasing data for message ${this.idForLogging()}:`, `Error erasing data for message ${this.idForLogging()}:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -1358,11 +1358,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
} }
errors.forEach(e => { errors.forEach(e => {
log.error( log.error('Message.saveErrors:', Errors.toLogFormat(e));
'Message.saveErrors:',
e && e.reason ? e.reason : null,
e && e.stack ? e.stack : e
);
}); });
errors = errors.map(e => { errors = errors.map(e => {
// Note: in our environment, instanceof can be scary, so we have a backup check // Note: in our environment, instanceof can be scary, so we have a backup check
@ -2415,7 +2411,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
sentAt: message.get('sent_at'), sentAt: message.get('sent_at'),
}); });
} catch (error) { } catch (error) {
const errorText = error && error.stack ? error.stack : error; const errorText = Errors.toLogFormat(error);
log.error( log.error(
`${idLog}: Failed to process group update as part of message ${message.idForLogging()}: ${errorText}` `${idLog}: Failed to process group update as part of message ${message.idForLogging()}: ${errorText}`
); );
@ -3050,7 +3046,7 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
log.info(`${idLog}: Batching save`); log.info(`${idLog}: Batching save`);
this.saveAndNotify(conversation, confirm); this.saveAndNotify(conversation, confirm);
} catch (error) { } catch (error) {
const errorForLog = error && error.stack ? error.stack : error; const errorForLog = Errors.toLogFormat(error);
log.error(`${idLog}: error:`, errorForLog); log.error(`${idLog}: error:`, errorForLog);
throw error; throw error;
} }

View file

@ -144,7 +144,7 @@ export async function routineProfileRefresh({
} catch (err) { } catch (err) {
log.error( log.error(
`${logId}: refreshed profile for ${conversation.idForLogging()}`, `${logId}: refreshed profile for ${conversation.idForLogging()}`,
err?.stack || err Errors.toLogFormat(err)
); );
} }
} }

View file

@ -11,6 +11,7 @@ import type {
MaybeGrabLinkPreviewOptionsType, MaybeGrabLinkPreviewOptionsType,
AddLinkPreviewOptionsType, AddLinkPreviewOptionsType,
} from '../types/LinkPreview'; } from '../types/LinkPreview';
import * as Errors from '../types/errors';
import type { StickerPackType as StickerPackDBType } from '../sql/Interface'; import type { StickerPackType as StickerPackDBType } from '../sql/Interface';
import type { MIMEType } from '../types/MIME'; import type { MIMEType } from '../types/MIME';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
@ -216,7 +217,7 @@ export async function addLinkPreview(
} catch (error) { } catch (error) {
log.error( log.error(
'Problem loading link preview, disabling.', 'Problem loading link preview, disabling.',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
disableLinkPreviews = true; disableLinkPreviews = true;
removeLinkPreview(); removeLinkPreview();
@ -455,10 +456,7 @@ async function getStickerPackPreview(
url, url,
}; };
} catch (error) { } catch (error) {
log.error( log.error('getStickerPackPreview error:', Errors.toLogFormat(error));
'getStickerPackPreview error:',
error && error.stack ? error.stack : error
);
return null; return null;
} finally { } finally {
if (id) { if (id) {
@ -530,7 +528,7 @@ async function getGroupPreview(
), ),
}; };
} catch (error) { } catch (error) {
const errorString = error && error.stack ? error.stack : error; const errorString = Errors.toLogFormat(error);
log.error( log.error(
`getGroupPreview/${logId}: Failed to fetch avatar ${errorString}` `getGroupPreview/${logId}: Failed to fetch avatar ${errorString}`
); );

View file

@ -4,6 +4,7 @@
import { requestMicrophonePermissions } from '../util/requestMicrophonePermissions'; import { requestMicrophonePermissions } from '../util/requestMicrophonePermissions';
import * as log from '../logging/log'; import * as log from '../logging/log';
import type { WebAudioRecorderClass } from '../window.d'; import type { WebAudioRecorderClass } from '../window.d';
import * as Errors from '../types/errors';
export class RecorderClass { export class RecorderClass {
private context?: AudioContext; private context?: AudioContext;
@ -84,10 +85,7 @@ export class RecorderClass {
this.source.connect(this.input); this.source.connect(this.input);
this.stream = stream; this.stream = stream;
} catch (err) { } catch (err) {
log.error( log.error('Recorder.onGetUserMediaError:', Errors.toLogFormat(err));
'Recorder.onGetUserMediaError:',
err && err.stack ? err.stack : err
);
this.clear(); this.clear();
throw err; throw err;
} }
@ -135,7 +133,7 @@ export class RecorderClass {
this.clear(); this.clear();
log.error('Recorder/onError:', error && error.stack ? error.stack : error); log.error('Recorder/onError:', Errors.toLogFormat(error));
} }
getBlob(): Blob { getBlob(): Blob {

View file

@ -67,6 +67,7 @@ import {
} from '../calling/findBestMatchingDevice'; } from '../calling/findBestMatchingDevice';
import type { LocalizerType } from '../types/Util'; import type { LocalizerType } from '../types/Util';
import { UUID, UUIDKind } from '../types/UUID'; import { UUID, UUIDKind } from '../types/UUID';
import * as Errors from '../types/errors';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
import * as Bytes from '../Bytes'; import * as Bytes from '../Bytes';
import { uuidToBytes, bytesToUuid } from '../Crypto'; import { uuidToBytes, bytesToUuid } from '../Crypto';
@ -1061,10 +1062,7 @@ export class CallingClass {
sendType: 'callingMessage', sendType: 'callingMessage',
timestamp, timestamp,
}).catch(err => { }).catch(err => {
log.error( log.error('Failed to send group call update:', Errors.toLogFormat(err));
'Failed to send group call update:',
err && err.stack ? err.stack : err
);
}); });
} }
@ -1877,7 +1875,7 @@ export class CallingClass {
return await this.getCallSettings(conversation); return await this.getCallSettings(conversation);
} catch (err) { } catch (err) {
log.error(`Ignoring incoming call: ${err.stack}`); log.error(`Ignoring incoming call: ${Errors.toLogFormat(err)}`);
this.addCallHistoryForFailedIncomingCall( this.addCallHistoryForFailedIncomingCall(
conversation, conversation,
call.isVideoCall, call.isVideoCall,

View file

@ -7,6 +7,7 @@ import type { MessageModel } from '../models/messages';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { sleep } from '../util/sleep'; import { sleep } from '../util/sleep';
import { SECOND } from '../util/durations'; import { SECOND } from '../util/durations';
import * as Errors from '../types/errors';
class ExpiringMessagesDeletionService { class ExpiringMessagesDeletionService {
public update: typeof this.checkExpiringMessages; public update: typeof this.checkExpiringMessages;
@ -65,7 +66,7 @@ class ExpiringMessagesDeletionService {
} catch (error) { } catch (error) {
window.SignalContext.log.error( window.SignalContext.log.error(
'destroyExpiredMessages: Error deleting expired messages', 'destroyExpiredMessages: Error deleting expired messages',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
window.SignalContext.log.info( window.SignalContext.log.info(
'destroyExpiredMessages: Waiting 30 seconds before trying again' 'destroyExpiredMessages: Waiting 30 seconds before trying again'

View file

@ -12,6 +12,7 @@ import { missingCaseError } from '../util/missingCaseError';
import { waitForOnline } from '../util/waitForOnline'; import { waitForOnline } from '../util/waitForOnline';
import * as log from '../logging/log'; import * as log from '../logging/log';
import type { StorageInterface } from '../types/Storage.d'; import type { StorageInterface } from '../types/Storage.d';
import * as Errors from '../types/errors';
import type { WebAPIType } from '../textsecure/WebAPI'; import type { WebAPIType } from '../textsecure/WebAPI';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
@ -171,7 +172,7 @@ export class SenderCertificateService {
`Sender certificate service could not fetch a ${modeToLogString( `Sender certificate service could not fetch a ${modeToLogString(
mode mode
)} certificate. Returning undefined`, )} certificate. Returning undefined`,
err && err.stack ? err.stack : err Errors.toLogFormat(err)
); );
return undefined; return undefined;
} }

View file

@ -4,6 +4,7 @@
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from '../util/clearTimeoutIfNecessary';
import { DAY } from '../util/durations'; import { DAY } from '../util/durations';
import * as Errors from '../types/errors';
async function eraseTapToViewMessages() { async function eraseTapToViewMessages() {
try { try {
@ -30,7 +31,7 @@ async function eraseTapToViewMessages() {
} catch (error) { } catch (error) {
window.SignalContext.log.error( window.SignalContext.log.error(
'eraseTapToViewMessages: Error erasing messages', 'eraseTapToViewMessages: Error erasing messages',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }

View file

@ -3,6 +3,7 @@
import * as log from '../logging/log'; import * as log from '../logging/log';
import { deleteAllLogs } from '../util/deleteAllLogs'; import { deleteAllLogs } from '../util/deleteAllLogs';
import * as Errors from '../types/errors';
export async function deleteAllData(): Promise<void> { export async function deleteAllData(): Promise<void> {
try { try {
@ -28,7 +29,7 @@ export async function deleteAllData(): Promise<void> {
} catch (error) { } catch (error) {
log.error( log.error(
'Something went wrong deleting all data:', 'Something went wrong deleting all data:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
window.restart(); window.restart();

View file

@ -4,6 +4,7 @@
import { parentPort } from 'worker_threads'; import { parentPort } from 'worker_threads';
import type { LoggerType } from '../types/Logging'; import type { LoggerType } from '../types/Logging';
import * as Errors from '../types/errors';
import type { import type {
WrappedWorkerRequest, WrappedWorkerRequest,
WrappedWorkerResponse, WrappedWorkerResponse,
@ -22,7 +23,7 @@ function respond(seq: number, error: Error | undefined, response?: any) {
const wrappedResponse: WrappedWorkerResponse = { const wrappedResponse: WrappedWorkerResponse = {
type: 'response', type: 'response',
seq, seq,
error: error?.stack, error: error === undefined ? undefined : Errors.toLogFormat(error),
response, response,
}; };
port.postMessage(wrappedResponse); port.postMessage(wrappedResponse);

View file

@ -55,6 +55,7 @@ import { CallMode } from '../../types/Calling';
import type { MediaItemType } from '../../types/MediaItem'; import type { MediaItemType } from '../../types/MediaItem';
import type { UUIDStringType } from '../../types/UUID'; import type { UUIDStringType } from '../../types/UUID';
import { MY_STORY_ID, StorySendMode } from '../../types/Stories'; import { MY_STORY_ID, StorySendMode } from '../../types/Stories';
import * as Errors from '../../types/errors';
import { import {
getGroupSizeRecommendedLimit, getGroupSizeRecommendedLimit,
getGroupSizeHardLimit, getGroupSizeHardLimit,
@ -1206,7 +1207,7 @@ function myProfileChanged(
payload: null, payload: null,
}); });
} catch (err) { } catch (err) {
log.error('myProfileChanged', err && err.stack ? err.stack : err); log.error('myProfileChanged', Errors.toLogFormat(err));
dispatch({ type: TOGGLE_PROFILE_EDITOR_ERROR }); dispatch({ type: TOGGLE_PROFILE_EDITOR_ERROR });
} }
}; };
@ -1606,7 +1607,7 @@ function createGroup(
}) })
); );
} catch (err) { } catch (err) {
log.error('Failed to create group', err && err.stack ? err.stack : err); log.error('Failed to create group', Errors.toLogFormat(err));
dispatch({ type: 'CREATE_GROUP_REJECTED' }); dispatch({ type: 'CREATE_GROUP_REJECTED' });
} }
}; };

View file

@ -8,6 +8,7 @@ import {
toggleVerification, toggleVerification,
} from '../../shims/contactVerification'; } from '../../shims/contactVerification';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import * as Errors from '../../types/errors';
export type SafetyNumberContactType = { export type SafetyNumberContactType = {
safetyNumber: string; safetyNumber: string;
@ -108,10 +109,7 @@ async function alterVerification(contact: ConversationType): Promise<void> {
if (result.name === 'OutgoingIdentityKeyError') { if (result.name === 'OutgoingIdentityKeyError') {
throw result; throw result;
} else { } else {
log.error( log.error('failed to toggle verified:', Errors.toLogFormat(result));
'failed to toggle verified:',
result && result.stack ? result.stack : result
);
} }
} else { } else {
const keyError = result.errors.find( const keyError = result.errors.find(
@ -121,10 +119,7 @@ async function alterVerification(contact: ConversationType): Promise<void> {
throw keyError; throw keyError;
} else { } else {
result.errors.forEach((error: Error) => { result.errors.forEach((error: Error) => {
log.error( log.error('failed to toggle verified:', Errors.toLogFormat(error));
'failed to toggle verified:',
error && error.stack ? error.stack : error
);
}); });
} }
} }

View file

@ -9,6 +9,7 @@ import type { StateType } from '../reducer';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import { ForwardMessageModal } from '../../components/ForwardMessageModal'; import { ForwardMessageModal } from '../../components/ForwardMessageModal';
import { LinkPreviewSourceType } from '../../types/LinkPreview'; import { LinkPreviewSourceType } from '../../types/LinkPreview';
import * as Errors from '../../types/errors';
import type { GetConversationByIdType } from '../selectors/conversations'; import type { GetConversationByIdType } from '../selectors/conversations';
import { import {
getAllComposableConversations, getAllComposableConversations,
@ -107,7 +108,7 @@ export function SmartForwardMessageModal(): JSX.Element | null {
closeModal(); closeModal();
} }
} catch (err) { } catch (err) {
log.warn('doForwardMessage', err && err.stack ? err.stack : err); log.warn('doForwardMessage', Errors.toLogFormat(err));
} }
}} }}
getPreferredBadge={getPreferredBadge} getPreferredBadge={getPreferredBadge}

View file

@ -3,6 +3,7 @@
import { assert } from 'chai'; import { assert } from 'chai';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { LibSignalErrorBase } from '@signalapp/libsignal-client';
import { import {
_analyzeSenderKeyDevices, _analyzeSenderKeyDevices,
@ -172,7 +173,11 @@ describe('sendToGroup', () => {
}); });
it("returns true for any error with 'untrusted' identity", async () => { it("returns true for any error with 'untrusted' identity", async () => {
const error = new Error('This was an untrusted identity.'); const error = new LibSignalErrorBase(
'untrusted identity',
'UntrustedIdentity',
'ignored'
);
assert.isTrue(_shouldFailSend(error, 'logId')); assert.isTrue(_shouldFailSend(error, 'logId'));
}); });

View file

@ -575,7 +575,7 @@ export default class AccountManager extends EventTarget {
} catch (error) { } catch (error) {
log.error( log.error(
'Something went wrong deleting data from previous number', 'Something went wrong deleting data from previous number',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} else { } else {

View file

@ -8,6 +8,7 @@ import protobuf from '../protobuf/wrap';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import { normalizeUuid } from '../util/normalizeUuid'; import { normalizeUuid } from '../util/normalizeUuid';
import { DurationInSeconds } from '../util/durations'; import { DurationInSeconds } from '../util/durations';
import * as Errors from '../types/errors';
import * as log from '../logging/log'; import * as log from '../logging/log';
import Avatar = Proto.ContactDetails.IAvatar; import Avatar = Proto.ContactDetails.IAvatar;
@ -90,10 +91,7 @@ abstract class ParserBase<
expireTimer, expireTimer,
}; };
} catch (error) { } catch (error) {
log.error( log.error('ProtoParser.next error:', Errors.toLogFormat(error));
'ProtoParser.next error:',
error && error.stack ? error.stack : error
);
return undefined; return undefined;
} }
} }

View file

@ -54,8 +54,9 @@ export class ReplayableError extends Error {
name?: string; name?: string;
message: string; message: string;
functionCode?: number; functionCode?: number;
cause?: unknown;
}) { }) {
super(options.message); super(options.message, { cause: options.cause });
this.name = options.name || 'ReplayableError'; this.name = options.name || 'ReplayableError';
this.message = options.message; this.message = options.message;
@ -71,7 +72,7 @@ export class ReplayableError extends Error {
} }
export class OutgoingIdentityKeyError extends ReplayableError { export class OutgoingIdentityKeyError extends ReplayableError {
identifier: string; public readonly identifier: string;
// Note: Data to resend message is no longer captured // Note: Data to resend message is no longer captured
constructor(incomingIdentifier: string) { constructor(incomingIdentifier: string) {
@ -162,6 +163,7 @@ export class SendMessageChallengeError extends ReplayableError {
super({ super({
name: 'SendMessageChallengeError', name: 'SendMessageChallengeError',
message: httpError.message, message: httpError.message,
cause: httpError,
}); });
[this.identifier] = identifier.split('.'); [this.identifier] = identifier.split('.');
@ -237,9 +239,7 @@ export class SendMessageProtoError extends Error implements CallbackResultType {
return 'No errors'; return 'No errors';
} }
return errors return errors.map(error => error.toString()).join(', ');
.map(error => (error.stackForLog ? error.stackForLog : error.toString()))
.join(', ');
} }
} }

View file

@ -13,6 +13,8 @@ import type {
PlaintextContent, PlaintextContent,
} from '@signalapp/libsignal-client'; } from '@signalapp/libsignal-client';
import { import {
ErrorCode,
LibSignalErrorBase,
CiphertextMessageType, CiphertextMessageType,
ProtocolAddress, ProtocolAddress,
sealedSenderEncrypt, sealedSenderEncrypt,
@ -34,6 +36,7 @@ import {
import type { CallbackResultType, CustomError } from './Types.d'; import type { CallbackResultType, CustomError } from './Types.d';
import { isValidNumber } from '../types/PhoneNumber'; import { isValidNumber } from '../types/PhoneNumber';
import { Address } from '../types/Address'; import { Address } from '../types/Address';
import * as Errors from '../types/errors';
import { QualifiedAddress } from '../types/QualifiedAddress'; import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID, isValidUuid } from '../types/UUID'; import { UUID, isValidUuid } from '../types/UUID';
import { Sessions, IdentityKeys } from '../LibSignalStores'; import { Sessions, IdentityKeys } from '../LibSignalStores';
@ -244,8 +247,7 @@ export default class OutgoingMessage {
} }
} }
error.reason = reason; error.cause = reason;
error.stackForLog = providedError ? providedError.stack : undefined;
this.errors[this.errors.length] = error; this.errors[this.errors.length] = error;
this.numberCompleted(); this.numberCompleted();
@ -284,7 +286,6 @@ export default class OutgoingMessage {
: { accessKey: undefined }; : { accessKey: undefined };
const { accessKey } = info; const { accessKey } = info;
try {
const { accessKeyFailed } = await getKeysForIdentifier( const { accessKeyFailed } = await getKeysForIdentifier(
identifier, identifier,
this.server, this.server,
@ -294,12 +295,6 @@ export default class OutgoingMessage {
if (accessKeyFailed && !this.failoverIdentifiers.includes(identifier)) { if (accessKeyFailed && !this.failoverIdentifiers.includes(identifier)) {
this.failoverIdentifiers.push(identifier); this.failoverIdentifiers.push(identifier);
} }
} catch (error) {
if (error?.message?.includes('untrusted identity for address')) {
error.timestamp = this.timestamp;
}
throw error;
}
} }
async transmitMessage( async transmitMessage(
@ -626,8 +621,13 @@ export default class OutgoingMessage {
); );
}); });
} }
if (error?.message?.includes('untrusted identity for address')) {
error.timestamp = this.timestamp; let newError = error;
if (
error instanceof LibSignalErrorBase &&
error.code === ErrorCode.UntrustedIdentity
) {
newError = new OutgoingIdentityKeyError(identifier);
log.error( log.error(
'Got "key changed" error from encrypt - no identityKey for application layer', 'Got "key changed" error from encrypt - no identityKey for application layer',
identifier, identifier,
@ -643,7 +643,8 @@ export default class OutgoingMessage {
}, },
innerError => { innerError => {
log.error( log.error(
`doSendMessage: Error closing sessions: ${innerError.stack}` 'doSendMessage: Error closing sessions: ' +
`${Errors.toLogFormat(innerError)}`
); );
throw error; throw error;
} }
@ -653,7 +654,7 @@ export default class OutgoingMessage {
this.registerError( this.registerError(
identifier, identifier,
'Failed to create or send message', 'Failed to create or send message',
error newError
); );
return undefined; return undefined;
@ -712,7 +713,7 @@ export default class OutgoingMessage {
} catch (error) { } catch (error) {
log.error( log.error(
`sendToIdentifier: Failed to fetch UUID for identifier ${identifier}`, `sendToIdentifier: Failed to fetch UUID for identifier ${identifier}`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} else { } else {
@ -731,7 +732,10 @@ export default class OutgoingMessage {
} }
await this.reloadDevicesAndSend(identifier, true)(); await this.reloadDevicesAndSend(identifier, true)();
} catch (error) { } catch (error) {
if (error?.message?.includes('untrusted identity for address')) { if (
error instanceof LibSignalErrorBase &&
error.code === ErrorCode.UntrustedIdentity
) {
const newError = new OutgoingIdentityKeyError(identifier); const newError = new OutgoingIdentityKeyError(identifier);
this.registerError(identifier, 'Untrusted identity', newError); this.registerError(identifier, 'Untrusted identity', newError);
} else { } else {

View file

@ -2,6 +2,8 @@
// SPDX-License-Identifier: AGPL-3.0-only // SPDX-License-Identifier: AGPL-3.0-only
import { import {
ErrorCode,
LibSignalErrorBase,
PreKeyBundle, PreKeyBundle,
processPreKeyBundle, processPreKeyBundle,
ProtocolAddress, ProtocolAddress,
@ -9,9 +11,9 @@ import {
} from '@signalapp/libsignal-client'; } from '@signalapp/libsignal-client';
import { import {
OutgoingIdentityKeyError,
UnregisteredUserError, UnregisteredUserError,
HTTPError, HTTPError,
OutgoingIdentityKeyError,
} from './Errors'; } from './Errors';
import { Sessions, IdentityKeys } from '../LibSignalStores'; import { Sessions, IdentityKeys } from '../LibSignalStores';
import { Address } from '../types/Address'; import { Address } from '../types/Address';
@ -72,13 +74,6 @@ async function getServerKeys(
}), }),
}; };
} catch (error: unknown) { } catch (error: unknown) {
if (
error instanceof Error &&
error.message.includes('untrusted identity')
) {
throw new OutgoingIdentityKeyError(identifier);
}
if ( if (
accessKey && accessKey &&
isRecord(error) && isRecord(error) &&
@ -155,22 +150,27 @@ async function handleServerKeys(
ourUuid, ourUuid,
new Address(theirUuid, deviceId) new Address(theirUuid, deviceId)
); );
await window.textsecure.storage.protocol
.enqueueSessionJob(address, () => try {
await window.textsecure.storage.protocol.enqueueSessionJob(
address,
() =>
processPreKeyBundle( processPreKeyBundle(
preKeyBundle, preKeyBundle,
protocolAddress, protocolAddress,
sessionStore, sessionStore,
identityKeyStore identityKeyStore
) )
) );
.catch(error => { } catch (error) {
if (error?.message?.includes('untrusted identity for address')) { if (
// eslint-disable-next-line no-param-reassign error instanceof LibSignalErrorBase &&
error.identityKey = response.identityKey; error.code === ErrorCode.UntrustedIdentity
) {
throw new OutgoingIdentityKeyError(identifier);
} }
throw error; throw error;
}); }
}) })
); );
} }

View file

@ -529,7 +529,7 @@ export async function downloadEphemeralPack(
} }
log.error( log.error(
`Ephemeral download error for sticker pack ${redactPackId(packId)}:`, `Ephemeral download error for sticker pack ${redactPackId(packId)}:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -554,7 +554,7 @@ export async function downloadStickerPack(
} catch (error) { } catch (error) {
log.error( log.error(
'doDownloadStickerPack threw an error:', 'doDownloadStickerPack threw an error:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
}); });
@ -696,7 +696,7 @@ async function doDownloadStickerPack(
} catch (error) { } catch (error) {
log.error( log.error(
`Error downloading manifest for sticker pack ${redactPackId(packId)}:`, `Error downloading manifest for sticker pack ${redactPackId(packId)}:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
const pack = { const pack = {
@ -779,7 +779,7 @@ async function doDownloadStickerPack(
} catch (error) { } catch (error) {
log.error( log.error(
`Error downloading stickers for sticker pack ${redactPackId(packId)}:`, `Error downloading stickers for sticker pack ${redactPackId(packId)}:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
const errorStatus = 'error'; const errorStatus = 'error';

View file

@ -4,15 +4,20 @@
import { get, has } from 'lodash'; import { get, has } from 'lodash';
export function toLogFormat(error: unknown): string { export function toLogFormat(error: unknown): string {
let result = '';
if (error instanceof Error && error.stack) { if (error instanceof Error && error.stack) {
return error.stack; result = error.stack;
} else if (has(error, 'message')) {
result = get(error, 'message');
} else {
result = String(error);
} }
if (has(error, 'message')) { if (has(error, 'cause')) {
return get(error, 'message'); result += `\nCaused by: ${String(get(error, 'cause'))}`;
} }
return String(error); return result;
} }
export class ProfileDecryptError extends Error {} export class ProfileDecryptError extends Error {}

View file

@ -3,6 +3,7 @@
import { getEnvironment, Environment } from '../environment'; import { getEnvironment, Environment } from '../environment';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
/** /**
* In production and beta, logs a warning and continues. For development it * In production and beta, logs a warning and continues. For development it
@ -15,7 +16,7 @@ export function softAssert(condition: unknown, message: string): void {
} }
const err = new Error(message); const err = new Error(message);
log.warn('softAssert failure:', err && err.stack ? err.stack : err); log.warn('softAssert failure:', Errors.toLogFormat(err));
} }
} }
@ -34,7 +35,7 @@ export function assertDev(
} }
throw err; throw err;
} }
log.error('assert failure:', err && err.stack ? err.stack : err); log.error('assert failure:', Errors.toLogFormat(err));
} }
} }

View file

@ -8,11 +8,11 @@ import { getValue } from '../RemoteConfig';
import { parseIntOrThrow } from './parseIntOrThrow'; import { parseIntOrThrow } from './parseIntOrThrow';
import { scaleImageToLevel } from './scaleImageToLevel'; import { scaleImageToLevel } from './scaleImageToLevel';
import { isRecord } from './isRecord';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentType } from '../types/Attachment';
import { canBeTranscoded } from '../types/Attachment'; import { canBeTranscoded } from '../types/Attachment';
import type { LoggerType } from '../types/Logging'; import type { LoggerType } from '../types/Logging';
import * as MIME from '../types/MIME'; import * as MIME from '../types/MIME';
import * as Errors from '../types/errors';
const MEBIBYTE = 1024 * 1024; const MEBIBYTE = 1024 * 1024;
const DEFAULT_MAX = 100 * MEBIBYTE; const DEFAULT_MAX = 100 * MEBIBYTE;
@ -87,8 +87,7 @@ export async function autoOrientJPEG(
return xcodedAttachment; return xcodedAttachment;
} catch (error: unknown) { } catch (error: unknown) {
const errorString = const errorString = Errors.toLogFormat(error);
isRecord(error) && 'stack' in error ? error.stack : error;
logger.error( logger.error(
'autoOrientJPEG: Failed to rotate/scale attachment', 'autoOrientJPEG: Failed to rotate/scale attachment',
errorString errorString

View file

@ -14,6 +14,7 @@ import type {
DefaultConversationColorType, DefaultConversationColorType,
} from '../types/Colors'; } from '../types/Colors';
import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors'; import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors';
import * as Errors from '../types/errors';
import * as Stickers from '../types/Stickers'; import * as Stickers from '../types/Stickers';
import type { SystemTraySetting } from '../types/SystemTraySetting'; import type { SystemTraySetting } from '../types/SystemTraySetting';
import { parseSystemTraySetting } from '../types/SystemTraySetting'; import { parseSystemTraySetting } from '../types/SystemTraySetting';
@ -453,7 +454,7 @@ export function createIPCEvents(
window.isShowingModal = false; window.isShowingModal = false;
log.error( log.error(
'showStickerPack: Ran into an error!', 'showStickerPack: Ran into an error!',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
const errorView = new ReactWrapperView({ const errorView = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',
@ -483,7 +484,7 @@ export function createIPCEvents(
} catch (error) { } catch (error) {
log.error( log.error(
'showGroupViaLink: Ran into an error!', 'showGroupViaLink: Ran into an error!',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
const errorView = new ReactWrapperView({ const errorView = new ReactWrapperView({
className: 'error-modal-wrapper', className: 'error-modal-wrapper',

View file

@ -102,7 +102,7 @@ export async function onRetryRequest(event: RetryRequestEvent): Promise<void> {
} catch (error) { } catch (error) {
log.warn( log.warn(
`onRetryRequest/${logId}: Failed to parse integer from desktop.retryRespondMaxAge feature flag`, `onRetryRequest/${logId}: Failed to parse integer from desktop.retryRespondMaxAge feature flag`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
@ -349,7 +349,7 @@ async function sendDistributionMessageOrNullMessage(
} catch (error) { } catch (error) {
log.error( log.error(
`sendDistributionMessageOrNullMessage/${logId}: Failed to send sender key distribution message`, `sendDistributionMessageOrNullMessage/${logId}: Failed to send sender key distribution message`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -651,7 +651,7 @@ async function requestResend(decryptionError: DecryptionErrorEventData) {
} catch (error) { } catch (error) {
log.error( log.error(
`requestResend/${logId}: Failed to send retry request, failing over to automatic reset`, `requestResend/${logId}: Failed to send retry request, failing over to automatic reset`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
startAutomaticSessionReset(decryptionError); startAutomaticSessionReset(decryptionError);
return; return;

View file

@ -4,6 +4,7 @@
import { Environment, getEnvironment } from '../environment'; import { Environment, getEnvironment } from '../environment';
import { isInPast } from './timestamp'; import { isInPast } from './timestamp';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
const ONE_DAY_MS = 86400 * 1000; const ONE_DAY_MS = 86400 * 1000;
const NINETY_ONE_DAYS = 91 * ONE_DAY_MS; const NINETY_ONE_DAYS = 91 * ONE_DAY_MS;
@ -18,7 +19,7 @@ export function hasExpired(): boolean {
log.info('Build expires: ', new Date(buildExpiration).toISOString()); log.info('Build expires: ', new Date(buildExpiration).toISOString());
} }
} catch (e) { } catch (e) {
log.error('Error retrieving build expiration date', e.stack); log.error('Error retrieving build expiration date', Errors.toLogFormat(e));
return true; return true;
} }

View file

@ -13,9 +13,9 @@ const loadImageData = async (input: Input): Promise<ImageData> => {
canvasOrError => { canvasOrError => {
if (canvasOrError instanceof Event && canvasOrError.type === 'error') { if (canvasOrError instanceof Event && canvasOrError.type === 'error') {
const processError = new Error( const processError = new Error(
'imageToBlurHash: Failed to process image' 'imageToBlurHash: Failed to process image',
{ cause: canvasOrError }
); );
processError.originalError = canvasOrError;
reject(processError); reject(processError);
return; return;
} }

View file

@ -6,6 +6,7 @@ import { ReactWrapperView } from '../views/ReactWrapperView';
import { ErrorModal } from '../components/ErrorModal'; import { ErrorModal } from '../components/ErrorModal';
import { ProgressModal } from '../components/ProgressModal'; import { ProgressModal } from '../components/ProgressModal';
import * as log from '../logging/log'; import * as log from '../logging/log';
import * as Errors from '../types/errors';
import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary'; import { clearTimeoutIfNecessary } from './clearTimeoutIfNecessary';
export async function longRunningTaskWrapper<T>({ export async function longRunningTaskWrapper<T>({
@ -62,7 +63,7 @@ export async function longRunningTaskWrapper<T>({
} catch (error) { } catch (error) {
log.error( log.error(
`longRunningTaskWrapper/${idLog}: Error!`, `longRunningTaskWrapper/${idLog}: Error!`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
clearTimeoutIfNecessary(progressTimeout); clearTimeoutIfNecessary(progressTimeout);

View file

@ -4,6 +4,7 @@
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { strictAssert } from './assert'; import { strictAssert } from './assert';
import * as Errors from '../types/errors';
import type { UnwrapPromise } from '../types/Util'; import type { UnwrapPromise } from '../types/Util';
import type { import type {
IPCEventsValuesType, IPCEventsValuesType,
@ -104,11 +105,7 @@ export function installSetting(
try { try {
ipcRenderer.send('settings:response', seq, null, await getFn()); ipcRenderer.send('settings:response', seq, null, await getFn());
} catch (error) { } catch (error) {
ipcRenderer.send( ipcRenderer.send('settings:response', seq, Errors.toLogFormat(error));
'settings:response',
seq,
error && error.stack ? error.stack : error
);
} }
}); });
} }
@ -132,11 +129,7 @@ export function installSetting(
await setFn(value); await setFn(value);
ipcRenderer.send('settings:response', seq, null); ipcRenderer.send('settings:response', seq, null);
} catch (error) { } catch (error) {
ipcRenderer.send( ipcRenderer.send('settings:response', seq, Errors.toLogFormat(error));
'settings:response',
seq,
error && error.stack ? error.stack : error
);
} }
}); });
} }
@ -152,11 +145,7 @@ export function installCallback<Name extends keyof IPCEventsCallbacksType>(
try { try {
ipcRenderer.send('settings:response', seq, null, await hook(...args)); ipcRenderer.send('settings:response', seq, null, await hook(...args));
} catch (error) { } catch (error) {
ipcRenderer.send( ipcRenderer.send('settings:response', seq, Errors.toLogFormat(error));
'settings:response',
seq,
error && error.stack ? error.stack : error
);
} }
}); });
} }

View file

@ -8,6 +8,7 @@ import type {
AttachmentDraftType, AttachmentDraftType,
InMemoryAttachmentDraftType, InMemoryAttachmentDraftType,
} from '../types/Attachment'; } from '../types/Attachment';
import * as Errors from '../types/errors';
import { getMaximumAttachmentSize } from './attachments'; import { getMaximumAttachmentSize } from './attachments';
import { AttachmentToastType } from '../types/AttachmentToastType'; import { AttachmentToastType } from '../types/AttachmentToastType';
import { fileToBytes } from './fileToBytes'; import { fileToBytes } from './fileToBytes';
@ -105,7 +106,7 @@ export async function processAttachment(
} catch (e) { } catch (e) {
log.error( log.error(
`Was unable to generate thumbnail for fileType ${fileType}`, `Was unable to generate thumbnail for fileType ${fileType}`,
e && e.stack ? e.stack : e Errors.toLogFormat(e)
); );
const data = await fileToBytes(file); const data = await fileToBytes(file);
attachment = { attachment = {
@ -125,7 +126,7 @@ export async function processAttachment(
} catch (error) { } catch (error) {
log.error( log.error(
'Error ensuring that image is properly sized:', 'Error ensuring that image is properly sized:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
throw error; throw error;

View file

@ -19,6 +19,7 @@ import type {
MessageAttributesType, MessageAttributesType,
QuotedMessageType, QuotedMessageType,
} from '../model-types.d'; } from '../model-types.d';
import * as Errors from '../types/errors';
import type { StickerType } from '../types/Stickers'; import type { StickerType } from '../types/Stickers';
import type { LinkPreviewType } from '../types/message/LinkPreviews'; import type { LinkPreviewType } from '../types/message/LinkPreviews';
@ -222,7 +223,7 @@ export async function queueAttachmentDownloads(
} catch (error) { } catch (error) {
log.error( log.error(
`Problem copying sticker (${packId}, ${stickerId}) to attachments:`, `Problem copying sticker (${packId}, ${stickerId}) to attachments:`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }

View file

@ -8,7 +8,6 @@ import { IMAGE_JPEG } from '../types/MIME';
import { canvasToBlob } from './canvasToBlob'; import { canvasToBlob } from './canvasToBlob';
import { getValue } from '../RemoteConfig'; import { getValue } from '../RemoteConfig';
import { parseNumber } from './libphonenumberUtil'; import { parseNumber } from './libphonenumberUtil';
import { isRecord } from './isRecord';
enum MediaQualityLevels { enum MediaQualityLevels {
One = 1, One = 1,
@ -126,13 +125,10 @@ export async function scaleImageToLevel(
throw new Error('image not a canvas'); throw new Error('image not a canvas');
} }
({ image } = data); ({ image } = data);
} catch (err) { } catch (cause) {
const errorString = isRecord(err) && 'stack' in err ? err.stack : err; const error = new Error('scaleImageToLevel: Failed to process image', {
const error = new Error( cause,
'scaleImageToLevel: Failed to process image', });
errorString
);
error.originalError = err;
throw error; throw error;
} }

View file

@ -5,6 +5,7 @@ import { differenceWith, omit, partition } from 'lodash';
import { import {
ErrorCode, ErrorCode,
LibSignalErrorBase,
groupEncrypt, groupEncrypt,
ProtocolAddress, ProtocolAddress,
sealedSenderMultiRecipientEncrypt, sealedSenderMultiRecipientEncrypt,
@ -21,6 +22,7 @@ import {
import { Address } from '../types/Address'; import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress'; import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID'; import { UUID } from '../types/UUID';
import * as Errors from '../types/errors';
import { getValue, isEnabled } from '../RemoteConfig'; import { getValue, isEnabled } from '../RemoteConfig';
import type { UUIDStringType } from '../types/UUID'; import type { UUIDStringType } from '../types/UUID';
import { isRecord } from './isRecord'; import { isRecord } from './isRecord';
@ -216,7 +218,7 @@ export async function sendContentMessageToGroup({
log.error( log.error(
`sendToGroup/${logId}: Sender Key send failed, logging, proceeding to normal send`, `sendToGroup/${logId}: Sender Key send failed, logging, proceeding to normal send`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} }
} }
@ -581,7 +583,10 @@ export async function sendToGroupViaSenderKey(options: {
recursionCount: recursionCount + 1, recursionCount: recursionCount + 1,
}); });
} }
if (error.code === ErrorCode.InvalidRegistrationId && error.addr) { if (
error instanceof LibSignalErrorBase &&
error.code === ErrorCode.InvalidRegistrationId
) {
const address = error.addr as ProtocolAddress; const address = error.addr as ProtocolAddress;
const name = address.name(); const name = address.name();
@ -742,7 +747,7 @@ function getSenderKeyExpireDuration(): number {
} catch (error) { } catch (error) {
log.warn( log.warn(
`getSenderKeyExpireDuration: Failed to parse integer. Using default of ${MAX_SENDER_KEY_EXPIRE_DURATION}.`, `getSenderKeyExpireDuration: Failed to parse integer. Using default of ${MAX_SENDER_KEY_EXPIRE_DURATION}.`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return MAX_SENDER_KEY_EXPIRE_DURATION; return MAX_SENDER_KEY_EXPIRE_DURATION;
} }
@ -753,7 +758,10 @@ export function _shouldFailSend(error: unknown, logId: string): boolean {
log.error(`_shouldFailSend/${logId}: ${message}`); log.error(`_shouldFailSend/${logId}: ${message}`);
}; };
if (error instanceof Error && error.message.includes('untrusted identity')) { if (
error instanceof LibSignalErrorBase &&
error.code === ErrorCode.UntrustedIdentity
) {
logError("'untrusted identity' error, failing."); logError("'untrusted identity' error, failing.");
return true; return true;
} }
@ -1271,7 +1279,7 @@ async function fetchKeysForIdentifiers(
} catch (error) { } catch (error) {
log.error( log.error(
'fetchKeysForIdentifiers: Failed to fetch keys:', 'fetchKeysForIdentifiers: Failed to fetch keys:',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
throw error; throw error;
} }

View file

@ -12,6 +12,7 @@ import { render } from 'mustache';
import type { AttachmentType } from '../types/Attachment'; import type { AttachmentType } from '../types/Attachment';
import { isGIF } from '../types/Attachment'; import { isGIF } from '../types/Attachment';
import * as Stickers from '../types/Stickers'; import * as Stickers from '../types/Stickers';
import * as Errors from '../types/errors';
import type { DraftBodyRangesType } from '../types/Util'; import type { DraftBodyRangesType } from '../types/Util';
import type { MIMEType } from '../types/MIME'; import type { MIMEType } from '../types/MIME';
import type { ConversationModel } from '../models/conversations'; import type { ConversationModel } from '../models/conversations';
@ -1693,7 +1694,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} catch (error) { } catch (error) {
log.error( log.error(
'Error sending delete-for-everyone', 'Error sending delete-for-everyone',
error && error.stack, Errors.toLogFormat(error),
messageId messageId
); );
showToast(ToastDeleteForEveryoneFailed); showToast(ToastDeleteForEveryoneFailed);
@ -2375,7 +2376,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
const { packId, stickerId } = options; const { packId, stickerId } = options;
this.model.sendStickerMessage(packId, stickerId); this.model.sendStickerMessage(packId, stickerId);
} catch (error) { } catch (error) {
log.error('clickSend error:', error && error.stack ? error.stack : error); log.error('clickSend error:', Errors.toLogFormat(error));
} }
} }
@ -2531,10 +2532,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} }
} catch (error) { } catch (error) {
this.enableMessageField(); this.enableMessageField();
log.error( log.error('sendMessage error:', Errors.toLogFormat(error));
'sendMessage error:',
error && error.stack ? error.stack : error
);
return; return;
} }
@ -2596,7 +2594,7 @@ export class ConversationView extends window.Backbone.View<ConversationModel> {
} catch (error) { } catch (error) {
log.error( log.error(
'Error pulling attached files before send', 'Error pulling attached files before send',
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
} finally { } finally {
this.enableMessageField(); this.enableMessageField();

25
ts/window.d.ts vendored
View file

@ -3,13 +3,10 @@
// Captures the globals put in place by preload.js, background.js and others // Captures the globals put in place by preload.js, background.js and others
/* eslint-disable max-classes-per-file */
import type { Store } from 'redux'; import type { Store } from 'redux';
import type * as Backbone from 'backbone'; import type * as Backbone from 'backbone';
import type * as Underscore from 'underscore'; import type * as Underscore from 'underscore';
import type PQueue from 'p-queue/dist'; import type PQueue from 'p-queue/dist';
import type { Ref } from 'react';
import type { assert } from 'chai'; import type { assert } from 'chai';
import type * as Mustache from 'mustache'; import type * as Mustache from 'mustache';
@ -358,15 +355,6 @@ declare global {
}; };
} }
interface Error {
originalError?: Event;
reason?: unknown;
stackForLog?: string;
// Used in sticker creator to attach messages to errors
errorMessageI18nKey?: string;
}
interface Element { interface Element {
// WebKit-specific // WebKit-specific
scrollIntoViewIfNeeded: (bringToCenter?: boolean) => void; scrollIntoViewIfNeeded: (bringToCenter?: boolean) => void;
@ -387,19 +375,6 @@ declare global {
} }
} }
export class GumVideoCapturer {
constructor(
maxWidth: number,
maxHeight: number,
maxFramerate: number,
localPreview: Ref<HTMLVideoElement>
);
}
export class CanvasVideoRenderer {
constructor(canvas: Ref<HTMLCanvasElement>);
}
export type WhisperType = { export type WhisperType = {
Conversation: typeof ConversationModel; Conversation: typeof ConversationModel;
ConversationCollection: typeof ConversationModelCollectionType; ConversationCollection: typeof ConversationModelCollectionType;

View file

@ -11,6 +11,7 @@ import { ThemeType } from '../../types/Util';
import { getEnvironment, Environment } from '../../environment'; import { getEnvironment, Environment } from '../../environment';
import { SignalContext } from '../context'; import { SignalContext } from '../context';
import * as log from '../../logging/log'; import * as log from '../../logging/log';
import * as Errors from '../../types/errors';
import { strictAssert } from '../../util/assert'; import { strictAssert } from '../../util/assert';
@ -90,7 +91,7 @@ window.isBeforeVersion = (toCheck, baseVersion) => {
} catch (error) { } catch (error) {
log.error( log.error(
`isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`, `isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return true; return true;
} }
@ -101,7 +102,7 @@ window.isAfterVersion = (toCheck, baseVersion) => {
} catch (error) { } catch (error) {
log.error( log.error(
`isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`, `isBeforeVersion error: toCheck: ${toCheck}, baseVersion: ${baseVersion}`,
error && error.stack ? error.stack : error Errors.toLogFormat(error)
); );
return true; return true;
} }
@ -305,7 +306,7 @@ ipc.on('delete-all-data', async () => {
try { try {
await deleteAllData(); await deleteAllData();
} catch (error) { } catch (error) {
log.error('delete-all-data: error', error && error.stack); log.error('delete-all-data: error', Errors.toLogFormat(error));
} }
}); });
@ -362,10 +363,7 @@ ipc.on('get-ready-for-shutdown', async () => {
await shutdown(); await shutdown();
ipc.send('now-ready-for-shutdown'); ipc.send('now-ready-for-shutdown');
} catch (error) { } catch (error) {
ipc.send( ipc.send('now-ready-for-shutdown', Errors.toLogFormat(error));
'now-ready-for-shutdown',
error && error.stack ? error.stack : error
);
} }
}); });

View file

@ -18,7 +18,7 @@ const port = parentPort;
function respond(uuid: string, error: Error | undefined, response?: File) { function respond(uuid: string, error: Error | undefined, response?: File) {
const wrappedResponse: WrappedWorkerResponse = { const wrappedResponse: WrappedWorkerResponse = {
uuid, uuid,
error: error ? error.stack : undefined, error: error?.stack,
response, response,
}; };
port.postMessage(wrappedResponse); port.postMessage(wrappedResponse);