refactor frontend storage access through safeStorage

This commit is contained in:
Dariusz L
2026-08-09 00:08:51 +02:00
parent 551dae9df7
commit 215f4e391e
4 changed files with 50 additions and 49 deletions
+17
View File
@@ -0,0 +1,17 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
const STORAGE_MODULES = [
'web/resolver/model_resolver.js',
'web/resolver/actions/queue_storage_methods.js',
'web/resolver/actions/queue_methods.js',
];
test('resolver storage modules use safeStorage instead of direct localStorage access', async () => {
for (const relativePath of STORAGE_MODULES) {
const source = await readFile(new URL(`../${relativePath}`, import.meta.url), 'utf8');
assert.match(source, /safeStorage\.(?:getItem|setItem|removeItem)/, relativePath);
assert.doesNotMatch(source, /\blocalStorage\s*\./, relativePath);
}
});
+12 -15
View File
@@ -4,6 +4,7 @@ import { getSvgIcon } from "../../utils/icon_utils.js";
import { startSplitterDrag } from "../utils/splitter_drag.js";
import { syncElementAttributes } from "../utils/dom_patch_utils.js";
import { bindDownloadActionHandlers } from "../utils/download_action_handlers.js";
import { safeStorage } from "../utils/html_utils.js";
const FOOTER_VERSION_REFRESH_INTERVAL_MS = 6 * 60 * 60 * 1000;
const FOOTER_VERSION_FAILURE_REFRESH_INTERVAL_MS = 5 * 60 * 1000;
@@ -36,18 +37,16 @@ export const queueMethods = {
this.observeQueueSplitContainer(body);
// Restore saved queue width and wire splitter
try {
const savedSplit = localStorage.getItem('model_resolver_split_w');
if (savedSplit) {
const w = parseInt(savedSplit, 10);
if (!isNaN(w) && w > 0) {
this.queueElement.style.width = `${w}px`;
this.queueElement.style.flexBasis = `${w}px`;
this.queueElement.style.flexGrow = '0';
this.queueElement.style.flexShrink = '0';
}
const savedSplit = safeStorage.getItem('model_resolver_split_w');
if (savedSplit) {
const w = parseInt(savedSplit, 10);
if (!isNaN(w) && w > 0) {
this.queueElement.style.width = `${w}px`;
this.queueElement.style.flexBasis = `${w}px`;
this.queueElement.style.flexGrow = '0';
this.queueElement.style.flexShrink = '0';
}
} catch (_e) { }
}
try {
const onSplitMouseDown = (e) => this.startSplitDrag(e);
@@ -72,10 +71,8 @@ export const queueMethods = {
} catch (_e) { }
// Restore queue collapsed state
try {
const col = localStorage.getItem('model_resolver_queue_collapsed');
if (col === '1') this.setQueueCollapsed(true, { persist: false, updatePanel: false });
} catch (_e) { }
const col = safeStorage.getItem('model_resolver_queue_collapsed');
if (col === '1') this.setQueueCollapsed(true, { persist: false, updatePanel: false });
return body;
},
+14 -24
View File
@@ -1,3 +1,5 @@
import { safeStorage } from '../utils/html_utils.js';
export const queueStorageMethods = {
getActiveDownloadRecoveryStorageKey() {
return this.activeDownloadsStorageKey || 'model_resolver_active_downloads';
@@ -10,7 +12,7 @@ export const queueStorageMethods = {
this._activeDownloadRecoveryLoaded = true;
try {
const raw = localStorage.getItem(this.getActiveDownloadRecoveryStorageKey());
const raw = safeStorage.getItem(this.getActiveDownloadRecoveryStorageKey());
const parsed = raw ? JSON.parse(raw) : {};
this._activeDownloadRecovery = parsed && typeof parsed === 'object' && !Array.isArray(parsed)
? parsed
@@ -24,14 +26,10 @@ export const queueStorageMethods = {
saveActiveDownloadRecovery() {
const recovery = this.loadActiveDownloadRecovery();
try {
localStorage.setItem(
this.getActiveDownloadRecoveryStorageKey(),
JSON.stringify(recovery)
);
} catch (error) {
console.warn('Model Resolver: failed to save active download recovery', error);
}
safeStorage.setItem(
this.getActiveDownloadRecoveryStorageKey(),
JSON.stringify(recovery)
);
},
persistActiveDownloadRecovery(downloadId, info = {}) {
@@ -88,9 +86,7 @@ export const queueStorageMethods = {
}
const writeState = () => {
this._queueCollapsedPersistTimer = null;
try {
localStorage.setItem('model_resolver_queue_collapsed', collapsed ? '1' : '0');
} catch (_e) { }
safeStorage.setItem('model_resolver_queue_collapsed', collapsed ? '1' : '0');
};
const delay = Math.max(0, Number(delayMs) || 0);
if (delay > 0) {
@@ -109,9 +105,7 @@ export const queueStorageMethods = {
}
const writeWidth = () => {
this._queueSplitPersistTimer = null;
try {
localStorage.setItem('model_resolver_split_w', String(nextWidth));
} catch (_e) { }
safeStorage.setItem('model_resolver_split_w', String(nextWidth));
};
const delay = Math.max(0, Number(delayMs) || 0);
if (delay > 0) {
@@ -125,7 +119,7 @@ export const queueStorageMethods = {
if (this._downloadHistoryLoaded) return this.downloadHistory || [];
this._downloadHistoryLoaded = true;
try {
const raw = localStorage.getItem(this.downloadHistoryStorageKey || 'model_resolver_download_history');
const raw = safeStorage.getItem(this.downloadHistoryStorageKey || 'model_resolver_download_history');
const parsed = raw ? JSON.parse(raw) : [];
this.downloadHistory = Array.isArray(parsed)
? parsed.filter(item => item && typeof item === 'object')
@@ -147,14 +141,10 @@ export const queueStorageMethods = {
saveDownloadHistory() {
const history = this.getDownloadHistory().slice(0, this.downloadHistoryLimit || 200);
this.downloadHistory = history;
try {
localStorage.setItem(
this.downloadHistoryStorageKey || 'model_resolver_download_history',
JSON.stringify(history)
);
} catch (error) {
console.warn('Model Resolver: failed to save download history', error);
}
safeStorage.setItem(
this.downloadHistoryStorageKey || 'model_resolver_download_history',
JSON.stringify(history)
);
},
getDownloadHistoryIdentity(entry = {}) {
+7 -10
View File
@@ -8,6 +8,7 @@ import { isSidebarButtonActive } from "./utils/dom_patch_utils.js";
import { fetchJson as fetchJsonRequest } from "./utils/api_client.js";
import { ResolverManagerDialog } from "./resolver_dialog.js";
import { showNotification } from "../utils/notification_utils.js";
import { safeStorage } from "./utils/html_utils.js";
import {
serializeKeybindingCombo,
keybindingsEqual,
@@ -60,11 +61,11 @@ function getKeybindingSetting(id) {
}
function applyStoredFrontendLoggingPreference() {
const stored = localStorage.getItem('ModelResolver.frontendLogsEnabled');
const stored = safeStorage.getItem('ModelResolver.frontendLogsEnabled');
if (stored !== null) {
frontendLogger.setEnabled(stored !== 'false');
}
const storedLevel = localStorage.getItem('ModelResolver.frontendLogLevel');
const storedLevel = safeStorage.getItem('ModelResolver.frontendLogLevel');
if (storedLevel) {
frontendLogger.setGlobalAndModuleLevel(frontendLogger.normalizeLevel(storedLevel));
}
@@ -716,11 +717,7 @@ export class ModelResolver {
this.dialog.showResolvedModels = true;
this.dialog.missingModelsTypeFilter = 'all';
this.dialog.missingModelsTypeFilterMenuOpen = false;
try {
localStorage.setItem(this.dialog.showResolvedModelsStorageKey, '1');
} catch (error) {
log.debug('Model Resolver: failed to persist resolved-model visibility.', error);
}
safeStorage.setItem(this.dialog.showResolvedModelsStorageKey, '1');
const selectionRequest = this.dialog.queueWorkflowModelReferenceSelection?.(reference);
if (!wasVisible) {
@@ -961,11 +958,11 @@ export class ModelResolver {
}
isWorkflowHashMetadataEnabled() {
return localStorage.getItem('ModelResolver.workflowHashMetadataEnabled') !== 'false';
return safeStorage.getItem('ModelResolver.workflowHashMetadataEnabled') !== 'false';
}
isWorkflowDependencyMarkerEnabled() {
return localStorage.getItem('ModelResolver.workflowDependencyMarkerEnabled') === 'true';
return safeStorage.getItem('ModelResolver.workflowDependencyMarkerEnabled') === 'true';
}
setupWorkflowHashMetadataInjection() {
@@ -1605,7 +1602,7 @@ export class ModelResolver {
* Check if auto-open preference is enabled
*/
isAutoOpenEnabled() {
return localStorage.getItem('ModelResolver.autoOpenOnMissing') === 'true';
return safeStorage.getItem('ModelResolver.autoOpenOnMissing') === 'true';
}
openResolverForDetectedMissingModels() {