From 1f0322637837a221a9f492c670e978f9af65d660 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Tue, 14 Jul 2026 20:56:25 +0800 Subject: [PATCH] fix: localize Secrets operation errors --- plugins/secrets/frontend/src/index.js | 46 +++++++++++++++++++++++---- plugins/secrets/locales/en.json | 10 ++++++ plugins/secrets/locales/ru.json | 10 ++++++ scripts/smoke-secrets-plugin.js | 29 +++++++++++++++-- 4 files changed, 86 insertions(+), 9 deletions(-) diff --git a/plugins/secrets/frontend/src/index.js b/plugins/secrets/frontend/src/index.js index fe226e3..27752d6 100644 --- a/plugins/secrets/frontend/src/index.js +++ b/plugins/secrets/frontend/src/index.js @@ -167,6 +167,38 @@ render(); } + function userFacingError(action, err) { + var raw = (err && err.message) ? err.message : String(err || ''); + if (typeof console !== 'undefined' && typeof console.error === 'function') { + console.error('[verstak.secrets] ' + action + ' failed', err); + } + if (action === 'unlock') { + var minimumLength = raw.match(/master password must be at least (\d+) characters/i); + if (minimumLength) { + return tr('ui.masterPasswordMinLength', { count: minimumLength[1] }, 'Master password must be at least ' + minimumLength[1] + ' characters.'); + } + if (/master password is empty/i.test(raw)) { + return tr('ui.masterPasswordRequired', null, 'Enter a master password.'); + } + if (/invalid master password/i.test(raw)) { + return tr('ui.masterPasswordInvalid', null, 'The master password is incorrect.'); + } + } + return tr('ui.' + action + 'Error', null, { + status: 'Could not check the secret storage. Please try again.', + load: 'Could not load secrets. Please try again.', + read: 'Could not open this secret. Please try again.', + save: 'Could not save the secret. Please try again.', + delete: 'Could not delete the secret. Please try again.', + copyLink: 'Could not copy the secret link. Please try again.', + unlock: 'Could not unlock secrets. Check the master password and try again.' + }[action] || 'Could not complete this action. Please try again.'); + } + + function setUserFacingError(action, err) { + setStatus(userFacingError(action, err), true); + } + function recordWorkspaceRoot(record) { var scope = record && record.scope || {}; return scope.kind === ScopeWorkspace ? cleanWorkspace(scope.workspaceRootPath) : ''; @@ -249,7 +281,7 @@ return loadRecords(); }).catch(function (err) { unlockBtn.disabled = false; - setStatus((err && err.message) ? err.message : String(err), true); + setUserFacingError('unlock', err); }); } }, [tr('ui.unlock', null, 'Unlock')]); @@ -458,7 +490,7 @@ selectedValue = ''; return loadRecords(); }).catch(function (err) { - setStatus((err && err.message) ? err.message : String(err), true); + setUserFacingError('save', err); }); } }, [tr('ui.save', null, 'Save')]), @@ -505,6 +537,8 @@ selectedValue = ''; mode = 'selected'; render(); + }).catch(function (err) { + setUserFacingError('load', err); }); } @@ -522,7 +556,7 @@ render(); }).catch(function (err) { if (disposed) return; - setStatus((err && err.message) ? err.message : String(err), true); + setUserFacingError('read', err); }); } @@ -549,7 +583,7 @@ selectedValue = ''; return loadRecords(); }).catch(function (err) { - setStatus((err && err.message) ? err.message : String(err), true); + setUserFacingError('delete', err); }); } @@ -559,7 +593,7 @@ setStatus(tr('ui.linkCopied', null, 'Secret link copied'), false); }); }).catch(function (err) { - setStatus((err && err.message) ? err.message : String(err), true); + setUserFacingError('copyLink', err); }); } @@ -569,7 +603,7 @@ if (unlocked) return loadWorkspaceOptions().then(loadRecords); render(); }).catch(function (err) { - statusText = (err && err.message) ? err.message : String(err); + statusText = userFacingError('status', err); statusError = true; renderLocked(); }); diff --git a/plugins/secrets/locales/en.json b/plugins/secrets/locales/en.json index 31dd6ef..ca2be0b 100644 --- a/plugins/secrets/locales/en.json +++ b/plugins/secrets/locales/en.json @@ -9,6 +9,9 @@ "ui.masterPassword": "Master password", "ui.repeatPassword": "Repeat master password", "ui.passwordMismatch": "Master passwords do not match", + "ui.masterPasswordMinLength": "Master password must be at least {count} characters.", + "ui.masterPasswordRequired": "Enter a master password.", + "ui.masterPasswordInvalid": "The master password is incorrect.", "ui.unlock": "Unlock", "ui.createMaster": "Create master password", "ui.password": "Password", @@ -38,6 +41,13 @@ "ui.search": "Search secrets", "ui.chooseWorkspace": "Choose a Deal", "ui.workspaceRequired": "Choose a Deal for this secret.", + "ui.statusError": "Could not check the secret storage. Please try again.", + "ui.loadError": "Could not load secrets. Please try again.", + "ui.readError": "Could not open this secret. Please try again.", + "ui.saveError": "Could not save the secret. Please try again.", + "ui.deleteError": "Could not delete the secret. Please try again.", + "ui.copyLinkError": "Could not copy the secret link. Please try again.", + "ui.unlockError": "Could not unlock secrets. Check the master password and try again.", "ui.value": "Value", "ui.save": "Save", "ui.cancel": "Cancel", diff --git a/plugins/secrets/locales/ru.json b/plugins/secrets/locales/ru.json index 043c678..54513e9 100644 --- a/plugins/secrets/locales/ru.json +++ b/plugins/secrets/locales/ru.json @@ -9,6 +9,9 @@ "ui.masterPassword": "Мастер-пароль", "ui.repeatPassword": "Повторите мастер-пароль", "ui.passwordMismatch": "Мастер-пароли не совпадают", + "ui.masterPasswordMinLength": "Мастер-пароль должен содержать не менее {count} символов.", + "ui.masterPasswordRequired": "Введите мастер-пароль.", + "ui.masterPasswordInvalid": "Мастер-пароль указан неверно.", "ui.unlock": "Разблокировать", "ui.createMaster": "Создать мастер-пароль", "ui.password": "Пароль", @@ -38,6 +41,13 @@ "ui.search": "Поиск секретов", "ui.chooseWorkspace": "Выберите дело", "ui.workspaceRequired": "Выберите дело для этого секрета.", + "ui.statusError": "Не удалось проверить хранилище секретов. Повторите попытку.", + "ui.loadError": "Не удалось загрузить секреты. Повторите попытку.", + "ui.readError": "Не удалось открыть секрет. Повторите попытку.", + "ui.saveError": "Не удалось сохранить секрет. Повторите попытку.", + "ui.deleteError": "Не удалось удалить секрет. Повторите попытку.", + "ui.copyLinkError": "Не удалось скопировать ссылку на секрет. Повторите попытку.", + "ui.unlockError": "Не удалось разблокировать секреты. Проверьте мастер-пароль и повторите попытку.", "ui.value": "Значение", "ui.save": "Сохранить", "ui.cancel": "Отмена", diff --git a/scripts/smoke-secrets-plugin.js b/scripts/smoke-secrets-plugin.js index 3a18c07..2676ff6 100755 --- a/scripts/smoke-secrets-plugin.js +++ b/scripts/smoke-secrets-plugin.js @@ -96,10 +96,15 @@ function makeDocument() { }; } -function loadComponent(document) { +function loadComponent(document, errorLog) { const registry = {}; vm.runInNewContext(source, { - console, + console: { + ...console, + error(...args) { + errorLog.push(args.map((value) => String(value)).join(' ')); + }, + }, document, window: { VerstakPluginRegister(pluginId, bundle) { @@ -138,13 +143,15 @@ async function flush() { if (!(manifest.contributes.settingsPanels || []).some((item) => item.component === 'SecretsView')) throw new Error('secrets settings panel missing'); const document = makeDocument(); - const component = loadComponent(document); + const errorLog = []; + const component = loadComponent(document, errorLog); const records = [ { id: 'global.server', title: 'Global Server', username: 'root', scope: { kind: 'global' }, updatedAt: '2026-06-29T00:00:00Z' }, { id: 'client-a.db', title: 'Client A DB', username: 'app', scope: { kind: 'workspace', workspaceRootPath: 'ClientA' }, updatedAt: '2026-06-29T00:00:00Z' }, ]; let initialized = false; let unlocked = false; + let unlockError = ''; const readCalls = []; const copied = []; const deleted = []; @@ -152,6 +159,7 @@ async function flush() { secrets: { status: async () => ({ initialized, unlocked }), unlock: async (password) => { + if (unlockError) throw new Error(unlockError); if (password !== 'master-password') throw new Error('bad password'); initialized = true; unlocked = true; @@ -197,6 +205,21 @@ async function flush() { const confirmInput = walk(container, (node) => node.getAttribute && node.getAttribute('data-secret-master-password-confirm') === ''); const unlockButton = walk(container, (node) => node.getAttribute && node.getAttribute('data-secret-unlock') === ''); if (!passwordInput || !confirmInput || !unlockButton) throw new Error('setup controls missing'); + unlockError = '[plugin:verstak.secrets] secrets.unlock failed: master password must be at least 8 characters'; + passwordInput.value = 'short'; + confirmInput.value = 'short'; + unlockButton.click(); + await flush(); + if (!container.textContent.includes('Master password must be at least 8 characters')) { + throw new Error('weak master password error was not explained clearly'); + } + if (container.textContent.includes('[plugin:') || container.textContent.includes('secrets.unlock')) { + throw new Error('technical unlock details leaked into the Secrets UI'); + } + if (!errorLog.some((entry) => entry.includes('[plugin:verstak.secrets] secrets.unlock failed'))) { + throw new Error('technical unlock details were not retained in the console log'); + } + unlockError = ''; passwordInput.value = 'master-password'; confirmInput.value = 'master-password'; unlockButton.click();