mirror of
https://github.com/nimbold/Firelink.git
synced 2026-08-22 17:06:40 +00:00
fix(properties): harden resume lifecycle
This commit is contained in:
@@ -14,6 +14,7 @@ import {
|
||||
PROPERTIES_WINDOW_ACTION_RESULT,
|
||||
PROPERTIES_WINDOW_REMOVED,
|
||||
PROPERTIES_WINDOW_SNAPSHOT,
|
||||
attachAsyncPropertiesListener,
|
||||
getPropertiesLifecycleAction,
|
||||
sendPropertiesActionRequest,
|
||||
sendPropertiesReady,
|
||||
@@ -190,7 +191,7 @@ export const PropertiesWindowApp = () => {
|
||||
const id = await invoke('get_properties_window_download_id');
|
||||
if (cancelled) return;
|
||||
setDownloadId(id);
|
||||
unlistenSnapshot = await listen<PropertiesSnapshotEvent>(PROPERTIES_WINDOW_SNAPSHOT, async event => {
|
||||
const snapshotListener = await listen<PropertiesSnapshotEvent>(PROPERTIES_WINDOW_SNAPSHOT, async event => {
|
||||
if (event.payload.windowLabel !== windowLabel
|
||||
|| event.payload.downloadId !== id
|
||||
|| event.payload.sessionId !== sessionId) return;
|
||||
@@ -220,7 +221,12 @@ export const PropertiesWindowApp = () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
unlistenResult = await listen<PropertiesActionResult>(PROPERTIES_WINDOW_ACTION_RESULT, event => {
|
||||
if (cancelled) {
|
||||
snapshotListener();
|
||||
return;
|
||||
}
|
||||
unlistenSnapshot = snapshotListener;
|
||||
const resultListener = await listen<PropertiesActionResult>(PROPERTIES_WINDOW_ACTION_RESULT, event => {
|
||||
if (event.payload.windowLabel !== windowLabel
|
||||
|| event.payload.downloadId !== id
|
||||
|| event.payload.sessionId !== sessionId) return;
|
||||
@@ -248,7 +254,12 @@ export const PropertiesWindowApp = () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
unlistenRemoved = await listen<{ windowLabel: string; downloadId: string }>(PROPERTIES_WINDOW_REMOVED, event => {
|
||||
if (cancelled) {
|
||||
resultListener();
|
||||
return;
|
||||
}
|
||||
unlistenResult = resultListener;
|
||||
const removedListener = await listen<{ windowLabel: string; downloadId: string }>(PROPERTIES_WINDOW_REMOVED, event => {
|
||||
if (event.payload.windowLabel === windowLabel && event.payload.downloadId === id) {
|
||||
if (readyRetryTimer !== undefined) {
|
||||
window.clearInterval(readyRetryTimer);
|
||||
@@ -258,6 +269,11 @@ export const PropertiesWindowApp = () => {
|
||||
setNotice(t($ => $.downloadTable.noDownloads));
|
||||
}
|
||||
});
|
||||
if (cancelled) {
|
||||
removedListener();
|
||||
return;
|
||||
}
|
||||
unlistenRemoved = removedListener;
|
||||
await sendPropertiesReady(sessionId);
|
||||
if (cancelled) return;
|
||||
// Tauri event listeners are registered asynchronously. If the main
|
||||
@@ -315,12 +331,16 @@ export const PropertiesWindowApp = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDirty) return;
|
||||
let disposed = false;
|
||||
let unlisten: UnlistenFn | undefined;
|
||||
void currentWindow.onCloseRequested(event => {
|
||||
attachAsyncPropertiesListener(currentWindow.onCloseRequested(event => {
|
||||
event.preventDefault();
|
||||
setClosePrompt(true);
|
||||
}).then(value => { unlisten = value; });
|
||||
return () => unlisten?.();
|
||||
}), () => disposed, value => { unlisten = value; });
|
||||
return () => {
|
||||
disposed = true;
|
||||
unlisten?.();
|
||||
};
|
||||
}, [currentWindow, isDirty]);
|
||||
|
||||
const requestAction = useCallback(async (
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
PROPERTIES_WINDOW_CLOSED,
|
||||
PROPERTIES_WINDOW_READY,
|
||||
applySecretPatch,
|
||||
attachAsyncPropertiesListener,
|
||||
beginExclusivePropertiesAction,
|
||||
createFrameCoalescer,
|
||||
enqueuePropertiesAction,
|
||||
@@ -176,8 +177,10 @@ export const PropertiesWindowBridgeHost = () => {
|
||||
};
|
||||
|
||||
const handleReady = async (payload: PropertiesWindowReady) => {
|
||||
if (disposed) return;
|
||||
try {
|
||||
await invoke('validate_properties_window_request', payload);
|
||||
if (disposed) return;
|
||||
const item = useDownloadStore.getState().downloads.find(download => download.id === payload.downloadId);
|
||||
if (!item) {
|
||||
await sendPropertiesRemoved(payload.windowLabel, payload.downloadId);
|
||||
@@ -192,12 +195,14 @@ export const PropertiesWindowBridgeHost = () => {
|
||||
};
|
||||
|
||||
const processAction = async (request: PropertiesActionRequest) => {
|
||||
if (disposed) return;
|
||||
let ok = false;
|
||||
let error: string | undefined;
|
||||
const actionKey = `${request.windowLabel}:${request.downloadId}`;
|
||||
let releaseAction: (() => void) | undefined;
|
||||
try {
|
||||
await invoke('validate_properties_window_request', request);
|
||||
if (disposed) return;
|
||||
const registration = windows.get(request.windowLabel);
|
||||
if (!registration
|
||||
|| registration.downloadId !== request.downloadId
|
||||
@@ -302,6 +307,7 @@ export const PropertiesWindowBridgeHost = () => {
|
||||
} finally {
|
||||
releaseAction?.();
|
||||
}
|
||||
if (disposed) return;
|
||||
if (ok) {
|
||||
try {
|
||||
await sendFor(request.windowLabel, request.downloadId);
|
||||
@@ -325,12 +331,14 @@ export const PropertiesWindowBridgeHost = () => {
|
||||
};
|
||||
|
||||
const handleAction = async (request: PropertiesActionRequest) => {
|
||||
if (disposed) return;
|
||||
const actionKey = `${request.windowLabel}:${request.downloadId}`;
|
||||
try {
|
||||
// The native command validates the caller, download binding, and
|
||||
// renderer session. If a ready event is delayed or lost, this valid
|
||||
// action can also establish the main-window registration.
|
||||
await invoke('validate_properties_window_request', request);
|
||||
if (disposed) return;
|
||||
synchronizeRegistration(request.windowLabel, request.downloadId, request.sessionId);
|
||||
} catch {
|
||||
// Stale renderer actions are deliberately ignored. The current child
|
||||
@@ -349,15 +357,32 @@ export const PropertiesWindowBridgeHost = () => {
|
||||
await enqueuePropertiesAction(actionChains, actionKey, () => processAction(request));
|
||||
};
|
||||
|
||||
void listen<PropertiesWindowReady>(PROPERTIES_WINDOW_READY, event => void handleReady(event.payload)).then(value => { unlistenReady = value; });
|
||||
void listen<PropertiesActionRequest>(PROPERTIES_WINDOW_ACTION_REQUEST, event => void handleAction(event.payload)).then(value => { unlistenAction = value; });
|
||||
void listen<string>(PROPERTIES_WINDOW_CLOSED, event => {
|
||||
const registration = windows.get(event.payload);
|
||||
windows.delete(event.payload);
|
||||
snapshotRevisions.delete(event.payload);
|
||||
snapshotCoalescer.cancel(event.payload);
|
||||
if (registration) actionsInFlight.delete(`${event.payload}:${registration.downloadId}`);
|
||||
}).then(value => { unlistenClosed = value; });
|
||||
attachAsyncPropertiesListener(
|
||||
listen<PropertiesWindowReady>(PROPERTIES_WINDOW_READY, event => {
|
||||
if (!disposed) void handleReady(event.payload);
|
||||
}),
|
||||
() => disposed,
|
||||
value => { unlistenReady = value; },
|
||||
);
|
||||
attachAsyncPropertiesListener(
|
||||
listen<PropertiesActionRequest>(PROPERTIES_WINDOW_ACTION_REQUEST, event => {
|
||||
if (!disposed) void handleAction(event.payload);
|
||||
}),
|
||||
() => disposed,
|
||||
value => { unlistenAction = value; },
|
||||
);
|
||||
attachAsyncPropertiesListener(
|
||||
listen<string>(PROPERTIES_WINDOW_CLOSED, event => {
|
||||
if (disposed) return;
|
||||
const registration = windows.get(event.payload);
|
||||
windows.delete(event.payload);
|
||||
snapshotRevisions.delete(event.payload);
|
||||
snapshotCoalescer.cancel(event.payload);
|
||||
if (registration) actionsInFlight.delete(`${event.payload}:${registration.downloadId}`);
|
||||
}),
|
||||
() => disposed,
|
||||
value => { unlistenClosed = value; },
|
||||
);
|
||||
|
||||
const unsubscribeStore = useDownloadStore.subscribe((state, previous) => {
|
||||
for (const [windowLabel, registration] of windows) {
|
||||
|
||||
Reference in New Issue
Block a user