Show error toast on database errors
This commit is contained in:
parent
c38e871ca9
commit
60f55f1749
7 changed files with 76 additions and 15 deletions
|
@ -804,6 +804,10 @@
|
||||||
"messageformat": "Failed to send message with endorsements",
|
"messageformat": "Failed to send message with endorsements",
|
||||||
"description": "An error popup when we attempted and failed to send a message using endorsements, only for internal users."
|
"description": "An error popup when we attempted and failed to send a message using endorsements, only for internal users."
|
||||||
},
|
},
|
||||||
|
"icu:Toast--SQLError": {
|
||||||
|
"messageformat": "Database error: please share your logs.",
|
||||||
|
"description": "An error popup when a database method fails, for internal & beta users"
|
||||||
|
},
|
||||||
"icu:Toast--InvalidStorageServiceHeaders": {
|
"icu:Toast--InvalidStorageServiceHeaders": {
|
||||||
"messageformat": "Received invalid response from storage service. Please share your logs.",
|
"messageformat": "Received invalid response from storage service. Please share your logs.",
|
||||||
"description": "[Only shown to internal/beta users] An error popup when we noticed an invalid response (i.e. a web request response) from one of our servers"
|
"description": "[Only shown to internal/beta users] An error popup when we noticed an invalid response (i.e. a web request response) from one of our servers"
|
||||||
|
|
25
app/main.ts
25
app/main.ts
|
@ -1595,7 +1595,7 @@ const runSQLCorruptionHandler = async () => {
|
||||||
// This is a glorified event handler. Normally, this promise never resolves,
|
// This is a glorified event handler. Normally, this promise never resolves,
|
||||||
// but if there is a corruption error triggered by any query that we run
|
// but if there is a corruption error triggered by any query that we run
|
||||||
// against the database - the promise will resolve and we will call
|
// against the database - the promise will resolve and we will call
|
||||||
// `onDatabaseError`.
|
// `onDatabaseInitializationError`.
|
||||||
const error = await sql.whenCorrupted();
|
const error = await sql.whenCorrupted();
|
||||||
|
|
||||||
getLogger().error(
|
getLogger().error(
|
||||||
|
@ -1603,14 +1603,14 @@ const runSQLCorruptionHandler = async () => {
|
||||||
`Restarting the application immediately. Error: ${error.message}`
|
`Restarting the application immediately. Error: ${error.message}`
|
||||||
);
|
);
|
||||||
|
|
||||||
await onDatabaseError(error);
|
await onDatabaseInitializationError(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
const runSQLReadonlyHandler = async () => {
|
const runSQLReadonlyHandler = async () => {
|
||||||
// This is a glorified event handler. Normally, this promise never resolves,
|
// This is a glorified event handler. Normally, this promise never resolves,
|
||||||
// but if there is a corruption error triggered by any query that we run
|
// but if there is a corruption error triggered by any query that we run
|
||||||
// against the database - the promise will resolve and we will call
|
// against the database - the promise will resolve and we will call
|
||||||
// `onDatabaseError`.
|
// `onDatabaseInitializationError`.
|
||||||
const error = await sql.whenReadonly();
|
const error = await sql.whenReadonly();
|
||||||
|
|
||||||
getLogger().error(
|
getLogger().error(
|
||||||
|
@ -1779,10 +1779,19 @@ async function initializeSQL(
|
||||||
drop(runSQLCorruptionHandler());
|
drop(runSQLCorruptionHandler());
|
||||||
drop(runSQLReadonlyHandler());
|
drop(runSQLReadonlyHandler());
|
||||||
|
|
||||||
|
sql.onUnknownSqlError(onUnknownSqlError);
|
||||||
|
|
||||||
return { ok: true, error: undefined };
|
return { ok: true, error: undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
const onDatabaseError = async (error: Error) => {
|
function onUnknownSqlError(error: Error) {
|
||||||
|
getLogger().error('Unknown SQL Error:', Errors.toLogFormat(error));
|
||||||
|
if (mainWindow) {
|
||||||
|
mainWindow.webContents.send('sql-error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const onDatabaseInitializationError = async (error: Error) => {
|
||||||
// Prevent window from re-opening
|
// Prevent window from re-opening
|
||||||
ready = false;
|
ready = false;
|
||||||
|
|
||||||
|
@ -1885,11 +1894,11 @@ const onDatabaseError = async (error: Error) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
if (confirmationButtonIndex === confirmDeleteAllDataButtonIndex) {
|
if (confirmationButtonIndex === confirmDeleteAllDataButtonIndex) {
|
||||||
getLogger().error('onDatabaseError: Deleting all data');
|
getLogger().error('onDatabaseInitializationError: Deleting all data');
|
||||||
await sql.removeDB();
|
await sql.removeDB();
|
||||||
userConfig.remove();
|
userConfig.remove();
|
||||||
getLogger().error(
|
getLogger().error(
|
||||||
'onDatabaseError: Requesting immediate restart after quit'
|
'onDatabaseInitializationError: Requesting immediate restart after quit'
|
||||||
);
|
);
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
}
|
}
|
||||||
|
@ -1901,7 +1910,7 @@ const onDatabaseError = async (error: Error) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLogger().error('onDatabaseError: Quitting application');
|
getLogger().error('onDatabaseInitializationError: Quitting application');
|
||||||
app.exit(1);
|
app.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2278,7 +2287,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);
|
await onDatabaseInitializationError(sqlError);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,6 +152,8 @@ function getToast(toastType: ToastType): AnyToast {
|
||||||
return { toastType: ToastType.ReportedSpam };
|
return { toastType: ToastType.ReportedSpam };
|
||||||
case ToastType.ReportedSpamAndBlocked:
|
case ToastType.ReportedSpamAndBlocked:
|
||||||
return { toastType: ToastType.ReportedSpamAndBlocked };
|
return { toastType: ToastType.ReportedSpamAndBlocked };
|
||||||
|
case ToastType.SQLError:
|
||||||
|
return { toastType: ToastType.SQLError };
|
||||||
case ToastType.StickerPackInstallFailed:
|
case ToastType.StickerPackInstallFailed:
|
||||||
return { toastType: ToastType.StickerPackInstallFailed };
|
return { toastType: ToastType.StickerPackInstallFailed };
|
||||||
case ToastType.StoryMuted:
|
case ToastType.StoryMuted:
|
||||||
|
|
|
@ -506,6 +506,20 @@ export function renderToast({
|
||||||
</Toast>
|
</Toast>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (toastType === ToastType.SQLError) {
|
||||||
|
return (
|
||||||
|
<Toast
|
||||||
|
onClose={hideToast}
|
||||||
|
toastAction={{
|
||||||
|
label: i18n('icu:Toast__ActionLabel--SubmitLog'),
|
||||||
|
onClick: onShowDebugLog,
|
||||||
|
}}
|
||||||
|
autoDismissDisabled
|
||||||
|
>
|
||||||
|
{i18n('icu:Toast--SQLError')}
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (toastType === ToastType.StoryMuted) {
|
if (toastType === ToastType.StoryMuted) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -92,6 +92,7 @@ type ResponseEntry<T> = {
|
||||||
type KnownErrorResolverType = Readonly<{
|
type KnownErrorResolverType = Readonly<{
|
||||||
kind: SqliteErrorKind;
|
kind: SqliteErrorKind;
|
||||||
resolve: (err: Error) => void;
|
resolve: (err: Error) => void;
|
||||||
|
once?: boolean;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
type CreateWorkerResultType = Readonly<{
|
type CreateWorkerResultType = Readonly<{
|
||||||
|
@ -210,16 +211,31 @@ export class MainSQL {
|
||||||
|
|
||||||
public whenCorrupted(): Promise<Error> {
|
public whenCorrupted(): Promise<Error> {
|
||||||
const { promise, resolve } = explodePromise<Error>();
|
const { promise, resolve } = explodePromise<Error>();
|
||||||
this.#errorResolvers.push({ kind: SqliteErrorKind.Corrupted, resolve });
|
this.#errorResolvers.push({
|
||||||
|
kind: SqliteErrorKind.Corrupted,
|
||||||
|
resolve,
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
public whenReadonly(): Promise<Error> {
|
public whenReadonly(): Promise<Error> {
|
||||||
const { promise, resolve } = explodePromise<Error>();
|
const { promise, resolve } = explodePromise<Error>();
|
||||||
this.#errorResolvers.push({ kind: SqliteErrorKind.Readonly, resolve });
|
this.#errorResolvers.push({
|
||||||
|
kind: SqliteErrorKind.Readonly,
|
||||||
|
resolve,
|
||||||
|
once: true,
|
||||||
|
});
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public onUnknownSqlError(callback: (error: Error) => void): void {
|
||||||
|
this.#errorResolvers.push({
|
||||||
|
kind: SqliteErrorKind.Unknown,
|
||||||
|
resolve: callback,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async close(): Promise<void> {
|
public async close(): Promise<void> {
|
||||||
if (this.#onReady) {
|
if (this.#onReady) {
|
||||||
try {
|
try {
|
||||||
|
@ -375,16 +391,14 @@ export class MainSQL {
|
||||||
}
|
}
|
||||||
|
|
||||||
#onError(errorKind: SqliteErrorKind, error: Error): void {
|
#onError(errorKind: SqliteErrorKind, error: Error): void {
|
||||||
if (errorKind === SqliteErrorKind.Unknown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolvers = new Array<(error: Error) => void>();
|
const resolvers = new Array<(error: Error) => void>();
|
||||||
this.#errorResolvers = this.#errorResolvers.filter(entry => {
|
this.#errorResolvers = this.#errorResolvers.filter(entry => {
|
||||||
if (entry.kind === errorKind) {
|
if (entry.kind === errorKind) {
|
||||||
resolvers.push(entry.resolve);
|
resolvers.push(entry.resolve);
|
||||||
|
if (entry.once) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -54,6 +54,7 @@ export enum ToastType {
|
||||||
ReactionFailed = 'ReactionFailed',
|
ReactionFailed = 'ReactionFailed',
|
||||||
ReportedSpam = 'ReportedSpam',
|
ReportedSpam = 'ReportedSpam',
|
||||||
ReportedSpamAndBlocked = 'ReportedSpamAndBlocked',
|
ReportedSpamAndBlocked = 'ReportedSpamAndBlocked',
|
||||||
|
SQLError = 'SQLError',
|
||||||
StickerPackInstallFailed = 'StickerPackInstallFailed',
|
StickerPackInstallFailed = 'StickerPackInstallFailed',
|
||||||
StoryMuted = 'StoryMuted',
|
StoryMuted = 'StoryMuted',
|
||||||
StoryReact = 'StoryReact',
|
StoryReact = 'StoryReact',
|
||||||
|
@ -153,6 +154,7 @@ export type AnyToast =
|
||||||
| { toastType: ToastType.ReportedSpam }
|
| { toastType: ToastType.ReportedSpam }
|
||||||
| { toastType: ToastType.ReportedSpamAndBlocked }
|
| { toastType: ToastType.ReportedSpamAndBlocked }
|
||||||
| { toastType: ToastType.StickerPackInstallFailed }
|
| { toastType: ToastType.StickerPackInstallFailed }
|
||||||
|
| { toastType: ToastType.SQLError }
|
||||||
| { toastType: ToastType.StoryMuted }
|
| { toastType: ToastType.StoryMuted }
|
||||||
| { toastType: ToastType.StoryReact }
|
| { toastType: ToastType.StoryReact }
|
||||||
| { toastType: ToastType.StoryReply }
|
| { toastType: ToastType.StoryReply }
|
||||||
|
|
|
@ -22,6 +22,8 @@ import { DataReader } from '../../sql/Client';
|
||||||
import type { WindowsNotificationData } from '../../services/notifications';
|
import type { WindowsNotificationData } from '../../services/notifications';
|
||||||
import { AggregatedStats } from '../../textsecure/WebsocketResources';
|
import { AggregatedStats } from '../../textsecure/WebsocketResources';
|
||||||
import { UNAUTHENTICATED_CHANNEL_NAME } from '../../textsecure/SocketManager';
|
import { UNAUTHENTICATED_CHANNEL_NAME } from '../../textsecure/SocketManager';
|
||||||
|
import { isProduction } from '../../util/version';
|
||||||
|
import { ToastType } from '../../types/Toast';
|
||||||
|
|
||||||
// It is important to call this as early as possible
|
// It is important to call this as early as possible
|
||||||
window.i18n = SignalContext.i18n;
|
window.i18n = SignalContext.i18n;
|
||||||
|
@ -437,6 +439,20 @@ ipc.on('show-release-notes', () => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipc.on('sql-error', () => {
|
||||||
|
if (!window.reduxActions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isProduction(window.getVersion())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
window.reduxActions.toast.showToast({
|
||||||
|
toastType: ToastType.SQLError,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
ipc.on(
|
ipc.on(
|
||||||
'art-creator:uploadStickerPack',
|
'art-creator:uploadStickerPack',
|
||||||
async (
|
async (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue