mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 16:02:32 +00:00
Add hidden admin restart action (#2970)
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
const { handlers, appExitMock, appRelaunchMock } = vi.hoisted(() => ({
|
||||
const { handlers, appExitMock, appQuitMock, appRelaunchMock } = vi.hoisted(() => ({
|
||||
handlers: new Map<string, (_event: unknown, args?: unknown) => unknown>(),
|
||||
appExitMock: vi.fn(),
|
||||
appQuitMock: vi.fn(),
|
||||
appRelaunchMock: vi.fn()
|
||||
}))
|
||||
|
||||
@@ -11,6 +12,7 @@ vi.mock('electron', () => ({
|
||||
exit: appExitMock,
|
||||
getAppPath: vi.fn(() => '/test/app'),
|
||||
isPackaged: false,
|
||||
quit: appQuitMock,
|
||||
relaunch: appRelaunchMock
|
||||
},
|
||||
BrowserWindow: {
|
||||
@@ -37,6 +39,7 @@ describe('registerAppHandlers', () => {
|
||||
vi.useFakeTimers()
|
||||
handlers.clear()
|
||||
appExitMock.mockReset()
|
||||
appQuitMock.mockReset()
|
||||
appRelaunchMock.mockReset()
|
||||
})
|
||||
|
||||
@@ -59,4 +62,22 @@ describe('registerAppHandlers', () => {
|
||||
expect(appRelaunchMock).toHaveBeenCalledTimes(1)
|
||||
expect(appExitMock).toHaveBeenCalledWith(0)
|
||||
})
|
||||
|
||||
it('marks restart as expected shutdown before quitting through the normal pipeline', () => {
|
||||
const onBeforeRelaunch = vi.fn()
|
||||
registerAppHandlers({} as never, { onBeforeRelaunch })
|
||||
|
||||
handlers.get('app:restart')?.(null)
|
||||
|
||||
expect(onBeforeRelaunch).toHaveBeenCalledTimes(1)
|
||||
expect(appRelaunchMock).not.toHaveBeenCalled()
|
||||
expect(appQuitMock).not.toHaveBeenCalled()
|
||||
expect(appExitMock).not.toHaveBeenCalled()
|
||||
|
||||
vi.advanceTimersByTime(150)
|
||||
|
||||
expect(appRelaunchMock).toHaveBeenCalledTimes(1)
|
||||
expect(appQuitMock).toHaveBeenCalledTimes(1)
|
||||
expect(appExitMock).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -178,6 +178,17 @@ export function registerAppHandlers(store: Store, options: RegisterAppHandlersOp
|
||||
}, 150)
|
||||
})
|
||||
|
||||
ipcMain.handle('app:restart', () => {
|
||||
// Why: the hidden admin restart should mirror the update relaunch path:
|
||||
// schedule a new Orca process, then use the normal quit pipeline so daemon
|
||||
// checkpoints, runtime metadata, and telemetry flush before exit.
|
||||
options.onBeforeRelaunch?.()
|
||||
setTimeout(() => {
|
||||
app.relaunch()
|
||||
app.quit()
|
||||
}, 150)
|
||||
})
|
||||
|
||||
ipcMain.handle('app:setUnreadDockBadgeCount', (_event, count: number) => {
|
||||
setUnreadDockBadgeCount(Number.isFinite(count) ? count : 0)
|
||||
})
|
||||
|
||||
@@ -581,6 +581,9 @@ export type AppApi = {
|
||||
* by settings panes that need a full restart to apply changes (e.g. the
|
||||
* terminal-window blur setting in TerminalWindowSection). */
|
||||
relaunch: () => Promise<void>
|
||||
/** Restarts Orca through the normal quit pipeline so daemon-backed terminal
|
||||
* sessions survive and can reattach after the new process starts. */
|
||||
restart: () => Promise<void>
|
||||
/** Reloads the current app renderer through main so expected renderer
|
||||
* teardown can be classified before Electron emits process-gone events. */
|
||||
reload: () => Promise<void>
|
||||
|
||||
+82
-47
@@ -140,6 +140,8 @@ import {
|
||||
type EditorSaveDirtyFilesDetail
|
||||
} from '../shared/editor-save-events'
|
||||
import {
|
||||
ORCA_APP_RESTART_ABORTED_EVENT,
|
||||
ORCA_APP_RESTART_STARTED_EVENT,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT
|
||||
} from '../shared/updater-renderer-events'
|
||||
@@ -169,6 +171,62 @@ type NativeFileDropCallback = (data: NativeFileDropPayload) => void
|
||||
const nativeFileDropCallbacks: NativeFileDropCallback[] = []
|
||||
let nativeFileDropListenerRegistered = false
|
||||
|
||||
type AppRestartPrepOptions = {
|
||||
startedEventName: string
|
||||
abortedEventName: string
|
||||
continueOnSaveFailure: boolean
|
||||
saveFailureLogPrefix: string
|
||||
}
|
||||
|
||||
function requestDirtyEditorFileSave(): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
let claimed = false
|
||||
window.dispatchEvent(
|
||||
new CustomEvent<EditorSaveDirtyFilesDetail>(ORCA_EDITOR_SAVE_DIRTY_FILES_EVENT, {
|
||||
detail: {
|
||||
claim: () => {
|
||||
claimed = true
|
||||
},
|
||||
resolve,
|
||||
reject: (message) => {
|
||||
reject(new Error(message))
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Why: restart paths can run when no editor surface is mounted. When
|
||||
// nothing claims the request there are no in-memory editor buffers to
|
||||
// flush, so proceed with the normal shutdown path immediately.
|
||||
if (!claimed) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function prepareRendererForAppRestart({
|
||||
startedEventName,
|
||||
abortedEventName,
|
||||
continueOnSaveFailure,
|
||||
saveFailureLogPrefix
|
||||
}: AppRestartPrepOptions): Promise<void> {
|
||||
window.dispatchEvent(new Event(startedEventName))
|
||||
|
||||
try {
|
||||
await requestDirtyEditorFileSave()
|
||||
} catch (error) {
|
||||
if (!continueOnSaveFailure) {
|
||||
window.dispatchEvent(new Event(abortedEventName))
|
||||
throw error
|
||||
}
|
||||
console.warn(saveFailureLogPrefix, error)
|
||||
}
|
||||
|
||||
// Dispatch beforeunload now so terminal buffers are captured while panes are
|
||||
// still mounted; update installs later bypass the ordinary close sequence.
|
||||
window.dispatchEvent(new Event('beforeunload'))
|
||||
}
|
||||
|
||||
const onNativeFileDrop = (_event: Electron.IpcRendererEvent, data: NativeFileDropPayload): void => {
|
||||
for (const callback of Array.from(nativeFileDropCallbacks)) {
|
||||
callback(data)
|
||||
@@ -358,6 +416,20 @@ const api = {
|
||||
getFeatureWallAssetBaseUrl: (): Promise<string> =>
|
||||
ipcRenderer.invoke('app:getFeatureWallAssetBaseUrl'),
|
||||
relaunch: (): Promise<void> => ipcRenderer.invoke('app:relaunch'),
|
||||
restart: async (): Promise<void> => {
|
||||
await prepareRendererForAppRestart({
|
||||
startedEventName: ORCA_APP_RESTART_STARTED_EVENT,
|
||||
abortedEventName: ORCA_APP_RESTART_ABORTED_EVENT,
|
||||
continueOnSaveFailure: false,
|
||||
saveFailureLogPrefix: '[app-restart] Saving dirty files before restart failed:'
|
||||
})
|
||||
try {
|
||||
return await ipcRenderer.invoke('app:restart')
|
||||
} catch (error) {
|
||||
window.dispatchEvent(new Event(ORCA_APP_RESTART_ABORTED_EVENT))
|
||||
throw error
|
||||
}
|
||||
},
|
||||
reload: (): Promise<void> => ipcRenderer.invoke('app:reload'),
|
||||
// Why: on macOS this returns AppleCurrentKeyboardLayoutInputSourceID so
|
||||
// the renderer's keyboard-layout probe can distinguish Polish Pro / US
|
||||
@@ -1933,53 +2005,16 @@ const api = {
|
||||
download: (): Promise<void> => ipcRenderer.invoke('updater:download'),
|
||||
dismissNudge: (): Promise<void> => ipcRenderer.invoke('updater:dismissNudge'),
|
||||
quitAndInstall: async (): Promise<void> => {
|
||||
// Why: quitAndInstall closes the BrowserWindow directly from the main
|
||||
// process. Renderer beforeunload guards treat that like a normal window
|
||||
// close unless we mark the updater path explicitly, and #300 introduced
|
||||
// longer-lived editor dirty/autosave state that can otherwise veto the
|
||||
// restart even after the update payload has been downloaded.
|
||||
window.dispatchEvent(new Event(ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT))
|
||||
|
||||
// Why: we wrap the save attempt in try/catch so that a save failure
|
||||
// (e.g., unsupported dirty files or a write error) never silently
|
||||
// prevents the update from installing. The user already clicked
|
||||
// "install update" — proceeding with the restart is better than
|
||||
// leaving them stuck with no feedback.
|
||||
try {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
let claimed = false
|
||||
window.dispatchEvent(
|
||||
new CustomEvent<EditorSaveDirtyFilesDetail>(ORCA_EDITOR_SAVE_DIRTY_FILES_EVENT, {
|
||||
detail: {
|
||||
claim: () => {
|
||||
claimed = true
|
||||
},
|
||||
resolve,
|
||||
reject: (message) => {
|
||||
reject(new Error(message))
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
// Why: updater installs can run when no editor surface is mounted.
|
||||
// When nothing claims the request there are no in-memory editor buffers
|
||||
// to flush, so proceed with the normal shutdown path immediately.
|
||||
if (!claimed) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'[updater] Saving dirty files before quit failed; proceeding with install anyway:',
|
||||
error
|
||||
)
|
||||
}
|
||||
|
||||
// Dispatch beforeunload to trigger terminal buffer capture before the
|
||||
// update process bypasses the normal window close sequence (quitAndInstall
|
||||
// removes close listeners, preventing beforeunload from firing naturally).
|
||||
window.dispatchEvent(new Event('beforeunload'))
|
||||
// Why: update installs must proceed even when a dirty-file auto-save
|
||||
// fails; otherwise a downloaded update can get stuck behind hidden editor
|
||||
// state. Manual app restart uses the same prep but aborts on save failure.
|
||||
await prepareRendererForAppRestart({
|
||||
startedEventName: ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT,
|
||||
abortedEventName: ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT,
|
||||
continueOnSaveFailure: true,
|
||||
saveFailureLogPrefix:
|
||||
'[updater] Saving dirty files before quit failed; proceeding with install anyway:'
|
||||
})
|
||||
try {
|
||||
return await ipcRenderer.invoke('updater:quitAndInstall')
|
||||
} catch (error) {
|
||||
|
||||
@@ -31,7 +31,7 @@ import {
|
||||
type EditorRequestFileCloseDetail,
|
||||
requestEditorSaveQuiesce
|
||||
} from './editor/editor-autosave'
|
||||
import { isUpdaterQuitAndInstallInProgress } from '@/lib/updater-beforeunload'
|
||||
import { isIntentionalAppRestartInProgress } from '@/lib/updater-beforeunload'
|
||||
import EditorAutosaveController from './editor/EditorAutosaveController'
|
||||
import type { TabGroupLayoutNode } from '../../../shared/types'
|
||||
import BrowserPane from './browser-pane/BrowserPane'
|
||||
@@ -1330,10 +1330,9 @@ function Terminal(): React.JSX.Element | null {
|
||||
// Warn on window close if there are unsaved editor files
|
||||
useEffect(() => {
|
||||
const handler = (e: BeforeUnloadEvent): void => {
|
||||
// Why: updater restarts intentionally close the app even if a hidden
|
||||
// editor tab still reports dirty. Let ShipIt replace the bundle instead
|
||||
// of vetoing quitAndInstall and leaving the old version running.
|
||||
if (isUpdaterQuitAndInstallInProgress()) {
|
||||
// Why: update/manual restarts pre-save dirty tabs and then intentionally
|
||||
// close the app. Do not let stale dirty flags veto the relaunch path.
|
||||
if (isIntentionalAppRestartInProgress()) {
|
||||
return
|
||||
}
|
||||
const dirtyFiles = useAppStore.getState().openFiles.filter((f) => f.isDirty)
|
||||
@@ -1350,7 +1349,7 @@ function Terminal(): React.JSX.Element | null {
|
||||
// close here. Explicit destructive terminal actions keep their own confirms.
|
||||
useEffect(() => {
|
||||
return window.api.ui.onWindowCloseRequested(({ isQuitting }) => {
|
||||
if (isUpdaterQuitAndInstallInProgress()) {
|
||||
if (isIntentionalAppRestartInProgress()) {
|
||||
window.api.ui.confirmWindowClose()
|
||||
return
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ import { SettingsSidebar } from './SettingsSidebar'
|
||||
import { ActiveSettingsSectionProvider, SettingsSection } from './SettingsSection'
|
||||
import { matchesSettingsSearch } from './settings-search'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { isIntentionalAppRestartInProgress } from '@/lib/updater-beforeunload'
|
||||
import { checkRuntimeHooks } from '@/runtime/runtime-hooks-client'
|
||||
import { useWindowsTerminalCapabilities } from '@/lib/windows-terminal-capabilities'
|
||||
import { getShortcutPlatform } from '@/lib/shortcut-platform'
|
||||
@@ -295,6 +296,9 @@ function Settings(): React.JSX.Element {
|
||||
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent): void => {
|
||||
if (isIntentionalAppRestartInProgress()) {
|
||||
return
|
||||
}
|
||||
if (!hasUnsavedCommitPromptChanges) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Github,
|
||||
HardDrive,
|
||||
MessageSquareText,
|
||||
RotateCw,
|
||||
School,
|
||||
Settings,
|
||||
Smartphone
|
||||
@@ -19,6 +20,7 @@ import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import {
|
||||
@@ -258,6 +260,9 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
const openSpacePage = useAppStore((s) => s.openSpacePage)
|
||||
const openMobilePage = useAppStore((s) => s.openMobilePage)
|
||||
const [feedbackOpen, setFeedbackOpen] = useState(false)
|
||||
const [helpMenuOpen, setHelpMenuOpen] = useState(false)
|
||||
const [showAdminHelpOptions, setShowAdminHelpOptions] = useState(false)
|
||||
const [isRestartingOrca, setIsRestartingOrca] = useState(false)
|
||||
const lastShowOnboardingAtRef = React.useRef(0)
|
||||
|
||||
const handleShowOnboarding = (): void => {
|
||||
@@ -269,6 +274,34 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
void showOnboardingFromRenderer()
|
||||
}
|
||||
|
||||
const handleHelpMenuOpenChange = (open: boolean): void => {
|
||||
setHelpMenuOpen(open)
|
||||
if (!open) {
|
||||
setShowAdminHelpOptions(false)
|
||||
}
|
||||
}
|
||||
|
||||
const revealAdminHelpOptions = (altKey: boolean): void => {
|
||||
// Why: keep restart off the ordinary Help menu; Alt/Option-click is an
|
||||
// intentional admin affordance for recovering the app without teaching it
|
||||
// as a normal user workflow.
|
||||
setShowAdminHelpOptions(altKey)
|
||||
}
|
||||
|
||||
const handleRestartOrca = (): void => {
|
||||
if (isRestartingOrca) {
|
||||
return
|
||||
}
|
||||
setIsRestartingOrca(true)
|
||||
toast.info('Restarting Orca…')
|
||||
void window.api.app.restart().catch((error) => {
|
||||
setIsRestartingOrca(false)
|
||||
toast.error('Couldn’t restart Orca.', {
|
||||
description: error instanceof Error ? error.message : undefined
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-auto shrink-0">
|
||||
<div className="flex items-center justify-between border-t border-sidebar-border px-2 py-1.5">
|
||||
@@ -324,7 +357,7 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<DropdownMenu modal={false}>
|
||||
<DropdownMenu modal={false} open={helpMenuOpen} onOpenChange={handleHelpMenuOpenChange}>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<DropdownMenuTrigger asChild>
|
||||
@@ -334,6 +367,8 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
type="button"
|
||||
aria-label="Help"
|
||||
className="text-muted-foreground"
|
||||
onPointerDown={(event) => revealAdminHelpOptions(event.altKey)}
|
||||
onClick={(event) => revealAdminHelpOptions(event.altKey)}
|
||||
>
|
||||
<CircleHelp className="size-3.5" />
|
||||
</Button>
|
||||
@@ -360,6 +395,15 @@ const SidebarToolbar = React.memo(function SidebarToolbar() {
|
||||
<ExternalLink className="size-3.5" />
|
||||
Docs
|
||||
</DropdownMenuItem>
|
||||
{showAdminHelpOptions ? (
|
||||
<>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onSelect={handleRestartOrca} disabled={isRestartingOrca}>
|
||||
<RotateCw className="size-3.5" />
|
||||
Restart Orca
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
) : null}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<Tooltip>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useEffectEvent } from 'react'
|
||||
import type { UnifiedTerminalItem } from './useTerminalTabs'
|
||||
import { getNextTabAcrossAllTypes, getNextTabWithinActiveType } from './tab-type-cycle'
|
||||
import { isUpdaterQuitAndInstallInProgress } from '@/lib/updater-beforeunload'
|
||||
import { isIntentionalAppRestartInProgress } from '@/lib/updater-beforeunload'
|
||||
|
||||
type UseTerminalShortcutsParams = {
|
||||
activeWorktreeId: string | null
|
||||
@@ -105,7 +105,7 @@ export function useTerminalShortcuts({
|
||||
})
|
||||
|
||||
const handleBeforeUnload = useEffectEvent((event: BeforeUnloadEvent) => {
|
||||
if (isUpdaterQuitAndInstallInProgress()) {
|
||||
if (isIntentionalAppRestartInProgress()) {
|
||||
return
|
||||
}
|
||||
if (!hasDirtyFiles) {
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
isIntentionalAppRestartInProgress,
|
||||
isUpdaterQuitAndInstallInProgress,
|
||||
registerUpdaterBeforeUnloadBypass
|
||||
} from './updater-beforeunload'
|
||||
import {
|
||||
ORCA_APP_RESTART_ABORTED_EVENT,
|
||||
ORCA_APP_RESTART_STARTED_EVENT,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT
|
||||
} from '../../../shared/updater-renderer-events'
|
||||
@@ -30,9 +33,25 @@ describe('registerUpdaterBeforeUnloadBypass', () => {
|
||||
|
||||
window.dispatchEvent(new Event(ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT))
|
||||
expect(isUpdaterQuitAndInstallInProgress()).toBe(true)
|
||||
expect(isIntentionalAppRestartInProgress()).toBe(true)
|
||||
|
||||
window.dispatchEvent(new Event(ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT))
|
||||
expect(isUpdaterQuitAndInstallInProgress()).toBe(false)
|
||||
expect(isIntentionalAppRestartInProgress()).toBe(false)
|
||||
|
||||
cleanup()
|
||||
})
|
||||
|
||||
it('tracks app restart lifecycle events', () => {
|
||||
const cleanup = registerUpdaterBeforeUnloadBypass()
|
||||
expect(isIntentionalAppRestartInProgress()).toBe(false)
|
||||
|
||||
window.dispatchEvent(new Event(ORCA_APP_RESTART_STARTED_EVENT))
|
||||
expect(isIntentionalAppRestartInProgress()).toBe(true)
|
||||
expect(isUpdaterQuitAndInstallInProgress()).toBe(true)
|
||||
|
||||
window.dispatchEvent(new Event(ORCA_APP_RESTART_ABORTED_EVENT))
|
||||
expect(isIntentionalAppRestartInProgress()).toBe(false)
|
||||
|
||||
cleanup()
|
||||
})
|
||||
|
||||
@@ -1,31 +1,41 @@
|
||||
import {
|
||||
ORCA_APP_RESTART_ABORTED_EVENT,
|
||||
ORCA_APP_RESTART_STARTED_EVENT,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT,
|
||||
ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT
|
||||
} from '../../../shared/updater-renderer-events'
|
||||
|
||||
let updaterQuitAndInstallInProgress = false
|
||||
let intentionalAppRestartInProgress = false
|
||||
|
||||
export function isUpdaterQuitAndInstallInProgress(): boolean {
|
||||
return updaterQuitAndInstallInProgress
|
||||
return isIntentionalAppRestartInProgress()
|
||||
}
|
||||
|
||||
export function isIntentionalAppRestartInProgress(): boolean {
|
||||
return intentionalAppRestartInProgress
|
||||
}
|
||||
|
||||
export function registerUpdaterBeforeUnloadBypass(): () => void {
|
||||
const markInProgress = (): void => {
|
||||
updaterQuitAndInstallInProgress = true
|
||||
intentionalAppRestartInProgress = true
|
||||
}
|
||||
const clearInProgress = (): void => {
|
||||
updaterQuitAndInstallInProgress = false
|
||||
intentionalAppRestartInProgress = false
|
||||
}
|
||||
|
||||
window.addEventListener(ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT, markInProgress)
|
||||
window.addEventListener(ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT, clearInProgress)
|
||||
window.addEventListener(ORCA_APP_RESTART_STARTED_EVENT, markInProgress)
|
||||
window.addEventListener(ORCA_APP_RESTART_ABORTED_EVENT, clearInProgress)
|
||||
|
||||
return () => {
|
||||
window.removeEventListener(ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT, markInProgress)
|
||||
window.removeEventListener(ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT, clearInProgress)
|
||||
window.removeEventListener(ORCA_APP_RESTART_STARTED_EVENT, markInProgress)
|
||||
window.removeEventListener(ORCA_APP_RESTART_ABORTED_EVENT, clearInProgress)
|
||||
// Why: hot reloads can re-register this listener inside the same renderer.
|
||||
// Reset the module flag on cleanup so a failed earlier install attempt
|
||||
// Reset the module flag on cleanup so a failed earlier restart attempt
|
||||
// cannot silently suppress future unsaved-change prompts.
|
||||
updaterQuitAndInstallInProgress = false
|
||||
intentionalAppRestartInProgress = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,6 +298,7 @@ function createWebPreloadApi(): Partial<PreloadApi> {
|
||||
}),
|
||||
getFeatureWallAssetBaseUrl: () => Promise.resolve('/'),
|
||||
relaunch: () => Promise.resolve(window.location.reload()),
|
||||
restart: () => Promise.resolve(window.location.reload()),
|
||||
reload: () => Promise.resolve(window.location.reload()),
|
||||
getKeyboardInputSourceId: () => Promise.resolve(null),
|
||||
setUnreadDockBadgeCount: () => Promise.resolve(),
|
||||
|
||||
@@ -1,2 +1,4 @@
|
||||
export const ORCA_UPDATER_QUIT_AND_INSTALL_STARTED_EVENT = 'orca:updater-quit-and-install-started'
|
||||
export const ORCA_UPDATER_QUIT_AND_INSTALL_ABORTED_EVENT = 'orca:updater-quit-and-install-aborted'
|
||||
export const ORCA_APP_RESTART_STARTED_EVENT = 'orca:app-restart-started'
|
||||
export const ORCA_APP_RESTART_ABORTED_EVENT = 'orca:app-restart-aborted'
|
||||
|
||||
Reference in New Issue
Block a user