fix(electron): align desktop update restart with updater flow
This commit is contained in:
+69
-14
@@ -126,6 +126,7 @@ const state = {
|
|||||||
quitRequested: false,
|
quitRequested: false,
|
||||||
quitConfirmed: false,
|
quitConfirmed: false,
|
||||||
quitConfirmationPending: false,
|
quitConfirmationPending: false,
|
||||||
|
installingUpdate: false,
|
||||||
quitRiskPollerStarted: false,
|
quitRiskPollerStarted: false,
|
||||||
pendingUpdate: null,
|
pendingUpdate: null,
|
||||||
unreachableHosts: new Set(),
|
unreachableHosts: new Set(),
|
||||||
@@ -167,10 +168,11 @@ const quitConfirmationMessage = () => {
|
|||||||
return `OpenChamber detected ${reasons.join(', ')}. Quitting now will stop sidecar/background processes and may interrupt pending work.`;
|
return `OpenChamber detected ${reasons.join(', ')}. Quitting now will stop sidecar/background processes and may interrupt pending work.`;
|
||||||
};
|
};
|
||||||
|
|
||||||
const performConfirmedQuit = () => {
|
const prepareForQuit = ({ installingUpdate = false } = {}) => {
|
||||||
if (state.quitConfirmed) return;
|
|
||||||
state.quitConfirmed = true;
|
|
||||||
state.quitRequested = true;
|
state.quitRequested = true;
|
||||||
|
state.quitConfirmed = true;
|
||||||
|
state.installingUpdate = installingUpdate;
|
||||||
|
state.quitConfirmationPending = false;
|
||||||
|
|
||||||
if (state.mainWindow && !state.mainWindow.isDestroyed()) {
|
if (state.mainWindow && !state.mainWindow.isDestroyed()) {
|
||||||
try {
|
try {
|
||||||
@@ -179,11 +181,18 @@ const performConfirmedQuit = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
if (!installingUpdate) {
|
||||||
killSidecar();
|
try {
|
||||||
} catch {
|
killSidecar();
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
void sshManager.shutdownAll().catch(() => {});
|
||||||
}
|
}
|
||||||
void sshManager.shutdownAll().catch(() => {});
|
};
|
||||||
|
|
||||||
|
const performConfirmedQuit = () => {
|
||||||
|
if (state.quitConfirmed) return;
|
||||||
|
prepareForQuit();
|
||||||
|
|
||||||
// Safety net: force-exit if normal quit sequence stalls (e.g. background
|
// Safety net: force-exit if normal quit sequence stalls (e.g. background
|
||||||
// handles in electron-updater / fetch refs) after a short grace period.
|
// handles in electron-updater / fetch refs) after a short grace period.
|
||||||
@@ -1180,7 +1189,9 @@ const createBrowserWindow = ({ label, restoreGeometry, url }) => {
|
|||||||
state.mainWindow = null;
|
state.mainWindow = null;
|
||||||
}
|
}
|
||||||
if (BrowserWindow.getAllWindows().length === 0) {
|
if (BrowserWindow.getAllWindows().length === 0) {
|
||||||
killSidecar();
|
if (!state.installingUpdate) {
|
||||||
|
killSidecar();
|
||||||
|
}
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
@@ -1947,8 +1958,26 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
|||||||
if (!state.pendingUpdate.electronUpdate) {
|
if (!state.pendingUpdate.electronUpdate) {
|
||||||
throw new Error('Electron updater metadata is not available for this build');
|
throw new Error('Electron updater metadata is not available for this build');
|
||||||
}
|
}
|
||||||
await autoUpdater.downloadUpdate();
|
if (!state.pendingUpdate.downloaded) {
|
||||||
state.pendingUpdate.downloaded = true;
|
await new Promise((resolve, reject) => {
|
||||||
|
let settled = false;
|
||||||
|
const cleanup = () => {
|
||||||
|
autoUpdater.off('update-downloaded', onDownloaded);
|
||||||
|
autoUpdater.off('error', onError);
|
||||||
|
};
|
||||||
|
const finish = (callback, value) => {
|
||||||
|
if (settled) return;
|
||||||
|
settled = true;
|
||||||
|
cleanup();
|
||||||
|
callback(value);
|
||||||
|
};
|
||||||
|
const onDownloaded = () => finish(resolve, null);
|
||||||
|
const onError = (error) => finish(reject, error);
|
||||||
|
autoUpdater.on('update-downloaded', onDownloaded);
|
||||||
|
autoUpdater.on('error', onError);
|
||||||
|
Promise.resolve(autoUpdater.downloadUpdate()).catch((error) => finish(reject, error));
|
||||||
|
});
|
||||||
|
}
|
||||||
emitToAllWindows('openchamber:update-progress', mapUpdaterProgressEvent({
|
emitToAllWindows('openchamber:update-progress', mapUpdaterProgressEvent({
|
||||||
event: 'Finished',
|
event: 'Finished',
|
||||||
data: {},
|
data: {},
|
||||||
@@ -1958,13 +1987,37 @@ const handleInvoke = async (browserWindow, command, args = {}) => {
|
|||||||
case 'desktop_restart': {
|
case 'desktop_restart': {
|
||||||
const applyUpdate = Boolean(state.pendingUpdate?.downloaded && app.isPackaged);
|
const applyUpdate = Boolean(state.pendingUpdate?.downloaded && app.isPackaged);
|
||||||
log.info(`[electron] desktop_restart applyUpdate=${applyUpdate} packaged=${app.isPackaged}`);
|
log.info(`[electron] desktop_restart applyUpdate=${applyUpdate} packaged=${app.isPackaged}`);
|
||||||
|
if (applyUpdate && process.platform === 'darwin' && typeof app.isInApplicationsFolder === 'function') {
|
||||||
|
try {
|
||||||
|
if (!app.isInApplicationsFolder()) {
|
||||||
|
throw new Error('Desktop update requires OpenChamber.app to be installed in /Applications');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
log.warn('[electron] desktop_restart blocked', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (applyUpdate) {
|
||||||
|
// Match the working updater pattern closely: only bypass the macOS
|
||||||
|
// hide-on-close / quit-confirmation guards, leave the rest of the
|
||||||
|
// updater-driven quit/install sequence alone.
|
||||||
|
state.quitRequested = true;
|
||||||
|
state.installingUpdate = true;
|
||||||
|
state.quitConfirmationPending = false;
|
||||||
|
if (state.mainWindow && !state.mainWindow.isDestroyed()) {
|
||||||
|
try {
|
||||||
|
debounceWindowStatePersist(state.mainWindow, true);
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Defer so the IPC reply flushes before the app starts shutting down.
|
// Defer so the IPC reply flushes before the app starts shutting down.
|
||||||
// Without this, quitAndInstall() can race with the renderer's pending
|
// Without this, quitAndInstall() can race with the renderer's pending
|
||||||
// invoke and the restart appears to do nothing from the UI side.
|
// invoke and the restart appears to do nothing from the UI side.
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
try {
|
try {
|
||||||
if (applyUpdate) {
|
if (applyUpdate) {
|
||||||
autoUpdater.quitAndInstall(false, true);
|
autoUpdater.quitAndInstall();
|
||||||
} else {
|
} else {
|
||||||
app.relaunch();
|
app.relaunch();
|
||||||
app.exit(0);
|
app.exit(0);
|
||||||
@@ -2237,15 +2290,17 @@ app.on('window-all-closed', () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
killSidecar();
|
if (!state.installingUpdate) {
|
||||||
void sshManager.shutdownAll();
|
killSidecar();
|
||||||
|
void sshManager.shutdownAll();
|
||||||
|
}
|
||||||
if (process.platform !== 'darwin') {
|
if (process.platform !== 'darwin') {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('before-quit', (event) => {
|
app.on('before-quit', (event) => {
|
||||||
if (state.quitConfirmed || process.platform !== 'darwin') {
|
if (state.quitConfirmed || state.installingUpdate || process.platform !== 'darwin') {
|
||||||
state.quitRequested = true;
|
state.quitRequested = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user