修复服务器断线时自动刷新误报暂存失败
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
const { test } = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const vm = require('node:vm');
|
||||
|
||||
function harness() {
|
||||
const events = new Map(), storage = new Map(), warnings = [], timers = new Map();
|
||||
let extension, reloads = 0;
|
||||
const api = {
|
||||
addEventListener: (name, fn) => events.set(name, fn),
|
||||
fetchApi: async () => ({ ok: true, json: async () => ({ revision: '200', user_id: 'a' }) }),
|
||||
};
|
||||
const app = {
|
||||
isGraphReady: true,
|
||||
graphToPrompt: async () => ({ workflow: { nodes: [] } }),
|
||||
extensionManager: {
|
||||
workflow: { openWorkflows: [] },
|
||||
toast: { add: item => warnings.push(item) },
|
||||
setting: { settings: {} },
|
||||
},
|
||||
registerExtension: value => { extension = value; },
|
||||
};
|
||||
const sessionStorage = {
|
||||
getItem: key => storage.get(key), setItem: (key, value) => storage.set(key, value),
|
||||
removeItem: key => storage.delete(key),
|
||||
};
|
||||
const source = fs.readFileSync(require.resolve('../web/js/autoRefresh.js'), 'utf8')
|
||||
.replace(/^import .*;\r?\n/gm, '')
|
||||
.replace('import.meta.url', JSON.stringify('http://localhost/autoRefresh.js?am_revision=100'));
|
||||
vm.runInNewContext(source, {
|
||||
app, api, sessionStorage, URL, AbortSignal, console: { error() {} },
|
||||
setTimeout: fn => { const id = {}; timers.set(id, fn); return id; },
|
||||
clearTimeout: id => timers.delete(id),
|
||||
window: { addEventListener() {}, location: { reload: () => reloads++ } },
|
||||
document: { addEventListener() {} },
|
||||
});
|
||||
extension.setup();
|
||||
return { app, api, warnings, storage, sessionStorage, events, reloads: () => reloads };
|
||||
}
|
||||
|
||||
test('server unavailable does not warn or reload', async () => {
|
||||
const h = harness();
|
||||
h.api.fetchApi = async () => { throw new TypeError('Failed to fetch'); };
|
||||
await h.events.get('reconnected')();
|
||||
assert.equal(h.warnings.length, 0);
|
||||
assert.equal(h.reloads(), 0);
|
||||
});
|
||||
|
||||
for (const fail of [true, false]) {
|
||||
test(`disconnect during snapshot (${fail ? 'failure' : 'success'}) waits for reconnect`, async () => {
|
||||
const h = harness();
|
||||
h.app.graphToPrompt = async () => {
|
||||
h.events.get('reconnecting')?.();
|
||||
if (fail) throw new TypeError('Failed to fetch');
|
||||
return { workflow: { nodes: [] } };
|
||||
};
|
||||
await h.events.get('reconnected')();
|
||||
assert.equal(h.warnings.length, 0);
|
||||
assert.equal(h.reloads(), 0);
|
||||
assert.equal(h.storage.has('AccountManager.RefreshRevision'), false);
|
||||
h.app.graphToPrompt = async () => ({ workflow: { nodes: [] } });
|
||||
await h.events.get('reconnected')();
|
||||
assert.equal(h.reloads(), 1);
|
||||
});
|
||||
}
|
||||
|
||||
test('real local storage failure still pauses refresh', async () => {
|
||||
const h = harness();
|
||||
h.sessionStorage.setItem = () => { throw new Error('Quota exceeded'); };
|
||||
await h.events.get('reconnected')();
|
||||
await h.events.get('reconnected')();
|
||||
assert.equal(h.warnings.length, 1);
|
||||
assert.equal(h.reloads(), 0);
|
||||
});
|
||||
|
||||
test('disconnect while version response is pending discards the response', async () => {
|
||||
const h = harness();
|
||||
const fetch = h.api.fetchApi;
|
||||
h.api.fetchApi = async () => {
|
||||
h.events.get('reconnecting')();
|
||||
return fetch();
|
||||
};
|
||||
await h.events.get('reconnected')();
|
||||
assert.equal(h.reloads(), 0);
|
||||
assert.equal(h.warnings.length, 0);
|
||||
h.api.fetchApi = fetch;
|
||||
await h.events.get('reconnected')();
|
||||
assert.equal(h.reloads(), 1);
|
||||
});
|
||||
+21
-2
@@ -8,6 +8,8 @@ let busy = false;
|
||||
let pending = false;
|
||||
let reloading = false;
|
||||
let paused = false;
|
||||
let disconnected = false;
|
||||
let connectionEpoch = 0;
|
||||
let retryTimer;
|
||||
|
||||
function readStored(key) {
|
||||
@@ -80,7 +82,7 @@ async function restoreWorkflows(info) {
|
||||
}
|
||||
|
||||
async function checkForUpdate() {
|
||||
if (reloading || paused) return;
|
||||
if (reloading || paused || disconnected) return;
|
||||
if (busy) {
|
||||
pending = true;
|
||||
return;
|
||||
@@ -91,6 +93,7 @@ async function checkForUpdate() {
|
||||
return;
|
||||
}
|
||||
busy = true;
|
||||
const epoch = connectionEpoch;
|
||||
try {
|
||||
let info;
|
||||
try {
|
||||
@@ -102,13 +105,16 @@ async function checkForUpdate() {
|
||||
} catch {
|
||||
return; // A stopped server or expired login must not trigger a reload.
|
||||
}
|
||||
if (epoch !== connectionEpoch || disconnected) return;
|
||||
if (!/^\d+$/.test(info.revision) || !info.user_id) return;
|
||||
try {
|
||||
await restoreWorkflows(info);
|
||||
} catch (error) {
|
||||
if (epoch !== connectionEpoch || disconnected) return;
|
||||
pauseRefresh("工作流恢复未完成,备份已保留。请先检查并保存当前工作流。", error);
|
||||
return;
|
||||
}
|
||||
if (epoch !== connectionEpoch || disconnected) return;
|
||||
let seen;
|
||||
try {
|
||||
seen = readStored(SEEN_KEY);
|
||||
@@ -125,9 +131,11 @@ async function checkForUpdate() {
|
||||
if (BigInt(info.revision) <= baseline) return;
|
||||
try {
|
||||
const backup = await snapshotWorkflows(info);
|
||||
if (epoch !== connectionEpoch || disconnected) return;
|
||||
sessionStorage.setItem(BACKUP_KEY, JSON.stringify(backup));
|
||||
sessionStorage.setItem(SEEN_KEY, JSON.stringify({ revision: info.revision, user_id: info.user_id }));
|
||||
} catch (error) {
|
||||
if (epoch !== connectionEpoch || disconnected) return;
|
||||
pauseRefresh("无法安全暂存工作流,请先保存后手动刷新。", error);
|
||||
return;
|
||||
}
|
||||
@@ -148,7 +156,18 @@ async function checkForUpdate() {
|
||||
app.registerExtension({
|
||||
name: "ComfyUI.AccountManager.AutoRefresh",
|
||||
setup() {
|
||||
api.addEventListener("reconnected", checkForUpdate);
|
||||
// ComfyUI already displays connection status; do not add a save warning while offline.
|
||||
const onDisconnect = () => {
|
||||
disconnected = true;
|
||||
connectionEpoch++;
|
||||
clearTimeout(retryTimer);
|
||||
};
|
||||
api.addEventListener("reconnecting", onDisconnect);
|
||||
window.addEventListener("offline", onDisconnect);
|
||||
api.addEventListener("reconnected", () => {
|
||||
disconnected = false;
|
||||
return checkForUpdate();
|
||||
});
|
||||
window.addEventListener("online", checkForUpdate);
|
||||
window.addEventListener("pageshow", checkForUpdate);
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
|
||||
Reference in New Issue
Block a user