@@ -19,7 +19,8 @@ const rmRF = (p: string) => fs.rm(p, { recursive: true, force: true });
1919import windowStateKeeper from 'electron-window-state' ;
2020import { getSystemProxy } from 'os-proxy-config' ;
2121import registerContextMenu = require( 'electron-context-menu' ) ;
22- import { getDeferred , delay } from '@httptoolkit/util' ;
22+ import * as sudoPrompt from '@expo/sudo-prompt' ;
23+ import { getDeferred , delay , ErrorLike } from '@httptoolkit/util' ;
2324
2425import { getMenu , shouldAutoHideMenu } from './menu' ;
2526import { ContextMenuDefinition , openContextMenu } from './context-menu' ;
@@ -659,3 +660,33 @@ ipcMain.handle('restart-app', ipcHandler(() => {
659660 app . relaunch ( ) ;
660661 app . quit ( ) ;
661662} ) ) ;
663+
664+ // Prompt the user to authorize a command to run as root, for system setup we can't do
665+ // without elevated permissions. We try to avoid this, but for system config changes
666+ // it's required, and much better to do per-command than running the whole app as root.
667+ ipcMain . handle ( 'sudo' , ipcHandler ( async ( options : {
668+ command : string
669+ } ) => {
670+ if ( typeof options ?. command !== 'string' ) {
671+ throw new Error ( "Invalid command for sudo" ) ;
672+ }
673+
674+ const result = getDeferred < { success : boolean , error ?: ErrorLike , stdout : string , stderr : string } > ( ) ;
675+
676+ sudoPrompt . exec ( options . command , {
677+ name : 'HTTP Toolkit'
678+ } , ( error : unknown | null , stdout : string , stderr : string ) => {
679+ // Types suggest this could be a buffer, but we want to
680+ // enforce simpler string output:
681+ stdout = stdout ?. toString ( ) ?? '' ;
682+ stderr = stderr ?. toString ( ) ?? '' ;
683+
684+ if ( error ) {
685+ result . resolve ( { success : false , error, stdout, stderr } ) ;
686+ } else {
687+ result . resolve ( { success : true , stdout, stderr } ) ;
688+ }
689+ } ) ;
690+
691+ return result . promise ;
692+ } ) ) ;
0 commit comments