Detect startup after recent crashes

This commit is contained in:
Fedor Indutny 2022-01-11 12:02:46 -08:00 committed by GitHub
parent 02a732c511
commit 91f1b62bc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 650 additions and 101 deletions

View file

@ -9,6 +9,7 @@ import { actions as badges } from './ducks/badges';
import { actions as calling } from './ducks/calling';
import { actions as composer } from './ducks/composer';
import { actions as conversations } from './ducks/conversations';
import { actions as crashReports } from './ducks/crashReports';
import { actions as emojis } from './ducks/emojis';
import { actions as expiration } from './ducks/expiration';
import { actions as globalModals } from './ducks/globalModals';
@ -31,6 +32,7 @@ export const actionCreators: ReduxActions = {
calling,
composer,
conversations,
crashReports,
emojis,
expiration,
globalModals,
@ -53,6 +55,7 @@ export const mapDispatchToProps = {
...calling,
...composer,
...conversations,
...crashReports,
...emojis,
...expiration,
...globalModals,

View file

@ -0,0 +1,135 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import * as log from '../../logging/log';
import { showToast } from '../../util/showToast';
import * as Errors from '../../types/errors';
import { ToastLinkCopied } from '../../components/ToastLinkCopied';
import { ToastDebugLogError } from '../../components/ToastDebugLogError';
// State
export type CrashReportsStateType = {
count: number;
isPending: boolean;
};
// Actions
const SET_COUNT = 'crashReports/SET_COUNT';
const UPLOAD = 'crashReports/UPLOAD';
const ERASE = 'crashReports/ERASE';
type SetCrashReportCountActionType = {
type: typeof SET_COUNT;
payload: number;
};
type PromiseAction<Type extends string, Payload = void> =
| {
type: Type;
payload: Promise<Payload>;
}
| {
type: `${Type}_PENDING`;
}
| {
type: `${Type}_FULFILLED`;
payload: Payload;
}
| {
type: `${Type}_REJECTED`;
error: true;
payload: Error;
};
type CrashReportsActionType =
| SetCrashReportCountActionType
| PromiseAction<typeof UPLOAD>
| PromiseAction<typeof ERASE>;
// Action Creators
export const actions = {
setCrashReportCount,
uploadCrashReports,
eraseCrashReports,
};
function setCrashReportCount(count: number): SetCrashReportCountActionType {
return { type: SET_COUNT, payload: count };
}
function uploadCrashReports(): PromiseAction<typeof UPLOAD> {
return { type: UPLOAD, payload: window.crashReports.upload() };
}
function eraseCrashReports(): PromiseAction<typeof ERASE> {
return { type: ERASE, payload: window.crashReports.erase() };
}
// Reducer
export function getEmptyState(): CrashReportsStateType {
return {
count: 0,
isPending: false,
};
}
export function reducer(
state: Readonly<CrashReportsStateType> = getEmptyState(),
action: Readonly<CrashReportsActionType>
): CrashReportsStateType {
if (action.type === SET_COUNT) {
return {
...state,
count: action.payload,
};
}
if (
action.type === `${UPLOAD}_PENDING` ||
action.type === `${ERASE}_PENDING`
) {
return {
...state,
isPending: true,
};
}
if (
action.type === `${UPLOAD}_FULFILLED` ||
action.type === `${ERASE}_FULFILLED`
) {
if (action.type === `${UPLOAD}_FULFILLED`) {
showToast(ToastLinkCopied);
}
return {
...state,
count: 0,
isPending: false,
};
}
if (
action.type === (`${UPLOAD}_REJECTED` as const) ||
action.type === (`${ERASE}_REJECTED` as const)
) {
const { error } = action;
log.error(
`Failed to upload crash report due to error ${Errors.toLogFormat(error)}`
);
showToast(ToastDebugLogError);
return {
...state,
count: 0,
isPending: false,
};
}
return state;
}

View file

@ -11,6 +11,7 @@ import { reducer as badges } from './ducks/badges';
import { reducer as calling } from './ducks/calling';
import { reducer as composer } from './ducks/composer';
import { reducer as conversations } from './ducks/conversations';
import { reducer as crashReports } from './ducks/crashReports';
import { reducer as emojis } from './ducks/emojis';
import { reducer as expiration } from './ducks/expiration';
import { reducer as globalModals } from './ducks/globalModals';
@ -33,6 +34,7 @@ export const reducer = combineReducers({
calling,
composer,
conversations,
crashReports,
emojis,
expiration,
globalModals,

View file

@ -0,0 +1,19 @@
// Copyright 2022 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
import { connect } from 'react-redux';
import { mapDispatchToProps } from '../actions';
import { CrashReportDialog } from '../../components/CrashReportDialog';
import type { StateType } from '../reducer';
import { getIntl } from '../selectors/user';
const mapStateToProps = (state: StateType) => {
return {
...state.crashReports,
i18n: getIntl(state),
};
};
const smart = connect(mapStateToProps, mapDispatchToProps);
export const SmartCrashReportDialog = smart(CrashReportDialog);

View file

@ -58,6 +58,7 @@ import { SmartNetworkStatus } from './NetworkStatus';
import { SmartRelinkDialog } from './RelinkDialog';
import { SmartUpdateDialog } from './UpdateDialog';
import { SmartCaptchaDialog } from './CaptchaDialog';
import { SmartCrashReportDialog } from './CrashReportDialog';
function renderExpiredBuildDialog(
props: Readonly<{ containerWidthBreakpoint: WidthBreakpoint }>
@ -88,6 +89,9 @@ function renderUpdateDialog(
function renderCaptchaDialog({ onSkip }: { onSkip(): void }): JSX.Element {
return <SmartCaptchaDialog onSkip={onSkip} />;
}
function renderCrashReportDialog(): JSX.Element {
return <SmartCrashReportDialog />;
}
const getModeSpecificProps = (
state: StateType
@ -185,6 +189,7 @@ const mapStateToProps = (state: StateType) => {
i18n: getIntl(state),
regionCode: getRegionCode(state),
challengeStatus: state.network.challengeStatus,
crashReportCount: state.crashReports.count,
renderExpiredBuildDialog,
renderMainHeader,
renderMessageSearchResult,
@ -192,6 +197,7 @@ const mapStateToProps = (state: StateType) => {
renderRelinkDialog,
renderUpdateDialog,
renderCaptchaDialog,
renderCrashReportDialog,
theme: getTheme(state),
};
};

View file

@ -9,6 +9,7 @@ import type { actions as badges } from './ducks/badges';
import type { actions as calling } from './ducks/calling';
import type { actions as composer } from './ducks/composer';
import type { actions as conversations } from './ducks/conversations';
import type { actions as crashReports } from './ducks/crashReports';
import type { actions as emojis } from './ducks/emojis';
import type { actions as expiration } from './ducks/expiration';
import type { actions as globalModals } from './ducks/globalModals';
@ -30,6 +31,7 @@ export type ReduxActions = {
calling: typeof calling;
composer: typeof composer;
conversations: typeof conversations;
crashReports: typeof crashReports;
emojis: typeof emojis;
expiration: typeof expiration;
globalModals: typeof globalModals;