2023-03-01 01:26:13 -03:00
/ *
* Vencord , a modification for Discord ' s desktop app
* Copyright ( c ) 2022 Vendicated and contributors
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
* /
import { showNotification } from "@api/Notifications" ;
2023-05-06 01:36:00 +02:00
import { definePluginSettings } from "@api/Settings" ;
2023-03-01 01:26:13 -03:00
import { Devs } from "@utils/constants" ;
2023-05-06 01:36:00 +02:00
import { Logger } from "@utils/Logger" ;
2023-03-01 01:26:13 -03:00
import { closeAllModals } from "@utils/modal" ;
import definePlugin , { OptionType } from "@utils/types" ;
import { maybePromptToUpdate } from "@utils/updater" ;
2023-11-30 00:43:23 -03:00
import { filters , findBulk , proxyLazyWebpack } from "@webpack" ;
2023-11-29 16:14:05 -03:00
import { FluxDispatcher , NavigationRouter , SelectedChannelStore } from "@webpack/common" ;
2023-03-01 01:26:13 -03:00
const CrashHandlerLogger = new Logger ( "CrashHandler" ) ;
2023-11-30 00:43:23 -03:00
const { ModalStack , DraftManager , DraftType , closeExpressionPicker } = proxyLazyWebpack ( ( ) = > {
const modules = findBulk (
filters . byProps ( "pushLazy" , "popAll" ) ,
filters . byProps ( "clearDraft" , "saveDraft" ) ,
filters . byProps ( "DraftType" ) ,
filters . byProps ( "closeExpressionPicker" , "openExpressionPicker" ) ,
) ;
return {
ModalStack : modules [ 0 ] ,
DraftManager : modules [ 1 ] ,
DraftType : modules [ 2 ] ? . DraftType ,
closeExpressionPicker : modules [ 3 ] ? . closeExpressionPicker ,
} ;
} ) ;
2023-03-01 01:26:13 -03:00
const settings = definePluginSettings ( {
attemptToPreventCrashes : {
type : OptionType . BOOLEAN ,
description : "Whether to attempt to prevent Discord crashes." ,
default : true
} ,
attemptToNavigateToHome : {
type : OptionType . BOOLEAN ,
description : "Whether to attempt to navigate to the home when preventing Discord crashes." ,
default : false
}
} ) ;
2024-02-10 13:42:31 -03:00
let hasCrashedOnce = false ;
2024-02-14 15:00:29 -03:00
let isRecovering = false ;
2024-02-10 13:42:31 -03:00
let shouldAttemptRecover = true ;
2023-03-06 18:54:01 -03:00
2023-03-01 01:26:13 -03:00
export default definePlugin ( {
name : "CrashHandler" ,
2024-02-10 13:42:31 -03:00
description : "Utility plugin for handling and possibly recovering from crashes without a restart" ,
2023-03-01 01:26:13 -03:00
authors : [ Devs . Nuckyz ] ,
enabledByDefault : true ,
settings ,
patches : [
{
find : ".Messages.ERRORS_UNEXPECTED_CRASH" ,
replacement : {
2024-02-14 15:00:29 -03:00
match : /this\.setState\((.+?)\)/ ,
replace : "$self.handleCrash(this,$1);"
2023-03-01 01:26:13 -03:00
}
}
] ,
2024-02-14 15:00:29 -03:00
handleCrash ( _this : any , errorState : any ) {
_this . setState ( errorState ) ;
// Already recovering, prevent error which happens more than once too fast to trigger another recover
if ( isRecovering ) return ;
isRecovering = true ;
2024-02-10 13:42:31 -03:00
// 1 ms timeout to avoid react breaking when re-rendering
setTimeout ( ( ) = > {
2024-02-14 15:00:29 -03:00
try {
// Prevent a crash loop with an error that could not be handled
if ( ! shouldAttemptRecover ) {
try {
showNotification ( {
color : "#eed202" ,
title : "Discord has crashed!" ,
body : "Awn :( Discord has crashed two times rapidly, not attempting to recover." ,
noPersist : true
} ) ;
} catch { }
return ;
}
2023-04-05 00:06:04 -03:00
2024-02-14 15:00:29 -03:00
shouldAttemptRecover = false ;
// This is enough to avoid a crash loop
2024-05-08 18:37:27 -03:00
setTimeout ( ( ) = > shouldAttemptRecover = true , 1000 ) ;
2024-02-14 15:00:29 -03:00
} catch { }
2023-04-05 00:06:04 -03:00
2023-03-06 18:54:01 -03:00
try {
2024-02-10 13:42:31 -03:00
if ( ! hasCrashedOnce ) {
hasCrashedOnce = true ;
maybePromptToUpdate ( "Uh oh, Discord has just crashed... but good news, there is a Vencord update available that might fix this issue! Would you like to update now?" , true ) ;
}
2024-02-14 15:00:29 -03:00
} catch { }
2024-02-10 13:42:31 -03:00
2024-02-14 15:00:29 -03:00
try {
2024-02-10 13:42:31 -03:00
if ( settings . store . attemptToPreventCrashes ) {
this . handlePreventCrash ( _this ) ;
}
} catch ( err ) {
CrashHandlerLogger . error ( "Failed to handle crash" , err ) ;
2023-03-01 01:26:13 -03:00
}
2024-02-10 13:42:31 -03:00
} , 1 ) ;
2023-03-01 01:26:13 -03:00
} ,
2024-02-10 13:42:31 -03:00
handlePreventCrash ( _this : any ) {
try {
showNotification ( {
color : "#eed202" ,
title : "Discord has crashed!" ,
body : "Attempting to recover..." ,
2024-02-14 15:00:29 -03:00
noPersist : true
2024-02-10 13:42:31 -03:00
} ) ;
} catch { }
2023-03-01 01:26:13 -03:00
2023-11-29 16:14:05 -03:00
try {
const channelId = SelectedChannelStore . getChannelId ( ) ;
DraftManager . clearDraft ( channelId , DraftType . ChannelMessage ) ;
DraftManager . clearDraft ( channelId , DraftType . FirstThreadMessage ) ;
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to clear drafts." , err ) ;
}
try {
closeExpressionPicker ( ) ;
}
catch ( err ) {
CrashHandlerLogger . debug ( "Failed to close expression picker." , err ) ;
}
2023-03-01 01:26:13 -03:00
try {
FluxDispatcher . dispatch ( { type : "CONTEXT_MENU_CLOSE" } ) ;
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to close open context menu." , err ) ;
}
try {
2023-11-30 00:43:23 -03:00
ModalStack . popAll ( ) ;
2023-03-01 01:26:13 -03:00
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to close old modals." , err ) ;
}
try {
closeAllModals ( ) ;
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to close all open modals." , err ) ;
}
try {
FluxDispatcher . dispatch ( { type : "USER_PROFILE_MODAL_CLOSE" } ) ;
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to close user popout." , err ) ;
}
try {
FluxDispatcher . dispatch ( { type : "LAYER_POP_ALL" } ) ;
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to pop all layers." , err ) ;
}
if ( settings . store . attemptToNavigateToHome ) {
try {
NavigationRouter . transitionTo ( "/channels/@me" ) ;
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to navigate to home" , err ) ;
}
}
2024-02-14 15:00:29 -03:00
// Set isRecovering to false before setting the state to allow us to handle the next crash error correcty, in case it happens
setImmediate ( ( ) = > isRecovering = false ) ;
2023-03-01 01:26:13 -03:00
try {
2024-02-10 13:42:31 -03:00
_this . setState ( { error : null , info : null } ) ;
2023-03-01 01:26:13 -03:00
} catch ( err ) {
CrashHandlerLogger . debug ( "Failed to update crash handler component." , err ) ;
}
}
} ) ;