diff --git a/.gitignore b/.gitignore index e932326..bd64ae8 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,10 @@ yarn-error.log* # Production build (if you pack the app later) dist/ build/ + +# Linting +lint_output.txt +final_lint.txt +*.log +.eslintcache + diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..cb225bd --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,22 @@ +import js from "@eslint/js"; +import globals from "globals"; + +export default [ + js.configs.recommended, + { + languageOptions: { + ecmaVersion: 2022, + sourceType: "module", + globals: { + ...globals.browser, + ...globals.node, + ...globals.es2021 + } + }, + rules: { + "no-unused-vars": "warn", + "no-console": "off", + "no-undef": "warn" + } + } +]; diff --git a/export.bat b/export.bat new file mode 100644 index 0000000..584f692 --- /dev/null +++ b/export.bat @@ -0,0 +1,15 @@ +@echo off +echo ========================================== +echo Exporting Account Data... +echo ========================================== +echo. + +node export.js + +echo. +echo ========================================== +echo EXPORT COMPLETE! +echo Check file: accounts_export.txt +echo ========================================== +echo. +pause diff --git a/一键安装环境.bat b/install.bat similarity index 100% rename from 一键安装环境.bat rename to install.bat diff --git a/main.js b/main.js index 43dfa19..4bbfd07 100644 --- a/main.js +++ b/main.js @@ -1,16 +1,74 @@ -const { app, BrowserWindow, ipcMain, clipboard, Menu } = require('electron'); +const { app, BrowserWindow, ipcMain, clipboard, Menu, desktopCapturer, screen } = require('electron'); const path = require('path'); +const fs = require('fs'); const Database = require('./src/db/database'); -// 强制设置用户数据存储位置为项目根目录下的 data 文件夹 -// 这样 Electron 的缓存、Localstorage、日志等都不会写到 C 盘 +// Force userData path to project root/data folder +// Prevents Electron cache/localStorage/logs from writing to C drive app.setPath('userData', path.join(process.cwd(), 'data')); let mainWindow; let db; +let backupInterval; + +// Auto backup feature +function startAutoBackup() { + const backupDir = path.join(process.cwd(), 'DB'); + const dbPath = path.join(process.cwd(), 'accounts.db'); + + // Ensure backup directory exists + if (!fs.existsSync(backupDir)) { + fs.mkdirSync(backupDir, { recursive: true }); + } + + // Backup every 30 minutes + backupInterval = setInterval(() => { + try { + if (fs.existsSync(dbPath)) { + const now = new Date(); + const timestamp = now.getFullYear().toString() + + (now.getMonth() + 1).toString().padStart(2, '0') + + now.getDate().toString().padStart(2, '0') + '_' + + now.getHours().toString().padStart(2, '0') + + now.getMinutes().toString().padStart(2, '0') + + now.getSeconds().toString().padStart(2, '0'); + + const backupPath = path.join(backupDir, `accounts_${timestamp}.db`); + fs.copyFileSync(dbPath, backupPath); + console.log(`[Backup] Auto backup success: ${backupPath}`); + + // Clean old backups, keep last 10 + cleanOldBackups(backupDir, 10); + } + } catch (e) { + console.error('[Backup] Backup failed:', e); + } + }, 30 * 60 * 1000); // 30 minutes + + console.log('[Backup] Auto backup started, interval 30 minutes'); +} + +// Clean old backups +function cleanOldBackups(backupDir, keepCount) { + try { + const files = fs.readdirSync(backupDir) + .filter(f => f.startsWith('accounts_') && f.endsWith('.db')) + .map(f => ({ name: f, path: path.join(backupDir, f), time: fs.statSync(path.join(backupDir, f)).mtime })) + .sort((a, b) => b.time - a.time); + + if (files.length > keepCount) { + files.slice(keepCount).forEach(f => { + fs.unlinkSync(f.path); + console.log(`[Backup] Deleted old backup: ${f.name}`); + }); + } + } catch (e) { + console.error('[Backup] Clean old backups failed:', e); + } +} function createWindow() { - // 移除应用菜单栏 + // Remove application menu bar Menu.setApplicationMenu(null); mainWindow = new BrowserWindow({ @@ -21,7 +79,8 @@ function createWindow() { webPreferences: { preload: path.join(__dirname, 'preload.js'), contextIsolation: true, - nodeIntegration: false + nodeIntegration: false, + sandbox: false }, backgroundColor: '#0a0a0b', titleBarStyle: 'default', @@ -34,13 +93,16 @@ function createWindow() { mainWindow.show(); }); - // 开发时打开DevTools - // mainWindow.webContents.openDevTools(); + // DevTools: opt-in only (prevents auto-opening on startup) + if (process.env.ELECTRON_DEVTOOLS === '1') { + mainWindow.webContents.openDevTools(); + } } app.whenReady().then(() => { db = new Database(); createWindow(); + startAutoBackup(); app.on('activate', () => { if (BrowserWindow.getAllWindows().length === 0) { @@ -50,6 +112,9 @@ app.whenReady().then(() => { }); app.on('window-all-closed', () => { + if (backupInterval) { + clearInterval(backupInterval); + } if (process.platform !== 'darwin') { app.quit(); } @@ -105,3 +170,31 @@ ipcMain.handle('clipboard:write', async (event, text) => { clipboard.writeText(text); return true; }); + +// Screen capture feature +ipcMain.handle('screen:capture', async () => { + try { + const primaryDisplay = screen.getPrimaryDisplay(); + const { width, height } = primaryDisplay.size; + const scaleFactor = primaryDisplay.scaleFactor; + + const sources = await desktopCapturer.getSources({ + types: ['screen'], + thumbnailSize: { width: width * scaleFactor, height: height * scaleFactor } + }); + + if (sources.length > 0) { + const thumbnail = sources[0].thumbnail; + return { + success: true, + image: thumbnail.toDataURL(), + width: thumbnail.getSize().width, + height: thumbnail.getSize().height + }; + } + return { success: false, error: 'Failed to capture screen' }; + } catch (e) { + console.error('Screen capture failed:', e); + return { success: false, error: e.message }; + } +}); diff --git a/package-lock.json b/package-lock.json index 5926005..a565eb5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,12 @@ "license": "MIT", "dependencies": { "crypto-js": "^4.2.0", + "jsqr": "^1.4.0", "sql.js": "^1.10.0" }, "devDependencies": { - "electron": "^28.0.0" + "electron": "^28.0.0", + "eslint": "^9.39.2" } }, "node_modules/@electron/get": { @@ -38,6 +40,202 @@ "global-agent": "^3.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", @@ -77,6 +275,13 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/http-cache-semantics": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", @@ -84,6 +289,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/keyv": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", @@ -125,6 +337,76 @@ "@types/node": "*" } }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/boolean": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz", @@ -134,6 +416,17 @@ "license": "MIT", "optional": true }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", @@ -173,6 +466,33 @@ "node": ">=8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/clone-response": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", @@ -186,6 +506,48 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/crypto-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", @@ -239,6 +601,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/defer-to-connect": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", @@ -370,7 +739,6 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", - "optional": true, "engines": { "node": ">=10" }, @@ -378,6 +746,160 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -399,6 +921,27 @@ "@types/yauzl": "^2.9.1" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -409,6 +952,57 @@ "pend": "~1.2.0" } }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, "node_modules/fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -440,6 +1034,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/global-agent": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz", @@ -473,6 +1080,19 @@ "node": ">=10" } }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -538,6 +1158,16 @@ "dev": true, "license": "ISC" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-property-descriptors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", @@ -573,6 +1203,86 @@ "node": ">=10.19.0" } }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -580,6 +1290,20 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -598,6 +1322,12 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsqr": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsqr/-/jsqr-1.4.0.tgz", + "integrity": "sha512-dxLob7q65Xg2DvstYkRpkYtmKm2sPJ9oFhrhmudT1dZvNFFTlroai3AWSpLey/w5vMcLBXRgOJsbXpdN9HzU/A==", + "license": "Apache-2.0" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -608,6 +1338,43 @@ "json-buffer": "3.0.1" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -642,6 +1409,19 @@ "node": ">=4" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -649,6 +1429,13 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, "node_modules/normalize-url": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", @@ -683,6 +1470,24 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -693,6 +1498,71 @@ "node": ">=8" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -700,6 +1570,16 @@ "dev": true, "license": "MIT" }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/progress": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", @@ -721,6 +1601,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -741,6 +1631,16 @@ "dev": true, "license": "MIT" }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -808,6 +1708,29 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/sprintf-js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", @@ -822,6 +1745,19 @@ "integrity": "sha512-RJbVP1HRDlUUXahJ7VMTcu9Rm1Nzw+EBpoPr94vnbD4LwR715F3CcxE2G2k45PewcaZ57pjetYa+LoSJLAASgA==", "license": "MIT" }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/sumchecker": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz", @@ -835,6 +1771,32 @@ "node": ">= 8.0" } }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz", @@ -866,6 +1828,42 @@ "node": ">= 4.0.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -883,6 +1881,19 @@ "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index 63f6a83..fa70346 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "本地2FA账号密码管理工具", "main": "main.js", "scripts": { - "start": "electron ." + "start": "electron .", + "lint": "eslint ." }, "keywords": [ "2fa", @@ -14,10 +15,12 @@ "author": "", "license": "MIT", "devDependencies": { - "electron": "^28.0.0" + "electron": "^28.0.0", + "eslint": "^9.39.2" }, "dependencies": { "crypto-js": "^4.2.0", + "jsqr": "^1.4.0", "sql.js": "^1.10.0" } } \ No newline at end of file diff --git a/preload.js b/preload.js index a5cd302..a4bd15a 100644 --- a/preload.js +++ b/preload.js @@ -1,5 +1,17 @@ const { contextBridge, ipcRenderer } = require('electron'); +// 加载jsQR库 +let jsQRLib = null; +try { + // 直接require jsqr模块 + const jsqrModule = require('jsqr'); + // 处理ES module的default export + jsQRLib = jsqrModule.default || jsqrModule; + console.log('jsQR loaded successfully:', typeof jsQRLib); +} catch (e) { + console.error('Failed to load jsQR:', e); +} + contextBridge.exposeInMainWorld('api', { // Vault operations getVaults: () => ipcRenderer.invoke('db:getVaults'), @@ -17,5 +29,39 @@ contextBridge.exposeInMainWorld('api', { searchAccounts: (query, vaultId) => ipcRenderer.invoke('db:searchAccounts', query, vaultId), // Clipboard - copyToClipboard: (text) => ipcRenderer.invoke('clipboard:write', text) + copyToClipboard: (text) => ipcRenderer.invoke('clipboard:write', text), + + + // Screenshot + captureScreen: () => ipcRenderer.invoke('screen:capture'), + + // QR Code parsing + decodeQR: (imageData, width, height) => { + if (!jsQRLib) { + console.error('jsQR not loaded'); + return null; + } + try { + // 确保是Uint8ClampedArray格式 + const data = imageData instanceof Uint8ClampedArray + ? imageData + : new Uint8ClampedArray(imageData); + + console.log('Decoding QR, image size:', width, 'x', height, 'data length:', data.length); + const result = jsQRLib(data, width, height); + + if (result) { + console.log('QR code found:', result.data); + return result.data; + } else { + console.log('No QR code found in image'); + return null; + } + } catch (e) { + console.error('QR decode error:', e); + return null; + } + }, + hasJsQR: () => !!jsQRLib, + }); diff --git a/src/db/database.js b/src/db/database.js index 714ae39..38dae53 100644 --- a/src/db/database.js +++ b/src/db/database.js @@ -1,37 +1,37 @@ const initSqlJs = require('sql.js'); const path = require('path'); const fs = require('fs'); -const { app } = require('electron'); +// const { app } = require('electron'); const CryptoJS = require('crypto-js'); const ENCRYPTION_KEY = 'your-secret-key-2fa-manager-v1'; class AccountDatabase { - constructor() { - this.db = null; - this.dbPath = null; - this.ready = this.init(); + constructor() { + this.db = null; + this.dbPath = null; + this.ready = this.init(); + } + + async init() { + const SQL = await initSqlJs(); + + // const userDataPath = app.getPath('userData'); + // this.dbPath = path.join(userDataPath, 'accounts.db'); + + // 便携版:将数据库放在项目根目录下 + this.dbPath = path.join(process.cwd(), 'accounts.db'); + + // 尝试加载现有数据库 + if (fs.existsSync(this.dbPath)) { + const buffer = fs.readFileSync(this.dbPath); + this.db = new SQL.Database(buffer); + } else { + this.db = new SQL.Database(); } - async init() { - const SQL = await initSqlJs(); - - // const userDataPath = app.getPath('userData'); - // this.dbPath = path.join(userDataPath, 'accounts.db'); - - // 便携版:将数据库放在项目根目录下 - this.dbPath = path.join(process.cwd(), 'accounts.db'); - - // 尝试加载现有数据库 - if (fs.existsSync(this.dbPath)) { - const buffer = fs.readFileSync(this.dbPath); - this.db = new SQL.Database(buffer); - } else { - this.db = new SQL.Database(); - } - - // 创建资料库表 - this.db.run(` + // 创建资料库表 + this.db.run(` CREATE TABLE IF NOT EXISTS vaults ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL UNIQUE, @@ -41,8 +41,8 @@ class AccountDatabase { ) `); - // 创建账号表 - this.db.run(` + // 创建账号表 + this.db.run(` CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, vault_id INTEGER DEFAULT NULL, @@ -60,54 +60,54 @@ class AccountDatabase { ) `); - // 迁移: 添加 tags 和 vault_id 和 proxy 和 browser_id 列 (如果不存在) - try { this.db.run(`ALTER TABLE accounts ADD COLUMN tags TEXT`); } catch (e) { } - try { this.db.run(`ALTER TABLE accounts ADD COLUMN vault_id INTEGER`); } catch (e) { } - try { this.db.run(`ALTER TABLE accounts ADD COLUMN proxy TEXT`); } catch (e) { } - try { this.db.run(`ALTER TABLE accounts ADD COLUMN browser_id TEXT`); } catch (e) { } + // 迁移: 添加 tags 和 vault_id 和 proxy 和 browser_id 列 (如果不存在) + try { this.db.run(`ALTER TABLE accounts ADD COLUMN tags TEXT`); } catch { /* ignore */ } + try { this.db.run(`ALTER TABLE accounts ADD COLUMN vault_id INTEGER`); } catch { /* ignore */ } + try { this.db.run(`ALTER TABLE accounts ADD COLUMN proxy TEXT`); } catch { /* ignore */ } + try { this.db.run(`ALTER TABLE accounts ADD COLUMN browser_id TEXT`); } catch { /* ignore */ } - // 创建默认资料库 (如果不存在) - const defaultVault = this.db.exec("SELECT id FROM vaults WHERE name = '默认'"); - if (!defaultVault.length || !defaultVault[0].values.length) { - this.db.run("INSERT INTO vaults (name, icon) VALUES ('默认', '🏠')"); - } - - // 创建索引 - this.db.run(`CREATE INDEX IF NOT EXISTS idx_accounts_name ON accounts(name)`); - this.db.run(`CREATE INDEX IF NOT EXISTS idx_accounts_username ON accounts(username)`); - this.db.run(`CREATE INDEX IF NOT EXISTS idx_accounts_vault ON accounts(vault_id)`); - - this.save(); - return true; + // 创建默认资料库 (如果不存在) + const defaultVault = this.db.exec("SELECT id FROM vaults WHERE name = '默认'"); + if (!defaultVault.length || !defaultVault[0].values.length) { + this.db.run("INSERT INTO vaults (name, icon) VALUES ('默认', '🏠')"); } - save() { - if (!this.db || !this.dbPath) return; - const data = this.db.export(); - const buffer = Buffer.from(data); - fs.writeFileSync(this.dbPath, buffer); + // 创建索引 + this.db.run(`CREATE INDEX IF NOT EXISTS idx_accounts_name ON accounts(name)`); + this.db.run(`CREATE INDEX IF NOT EXISTS idx_accounts_username ON accounts(username)`); + this.db.run(`CREATE INDEX IF NOT EXISTS idx_accounts_vault ON accounts(vault_id)`); + + this.save(); + return true; + } + + save() { + if (!this.db || !this.dbPath) return; + const data = this.db.export(); + const buffer = Buffer.from(data); + fs.writeFileSync(this.dbPath, buffer); + } + + encrypt(text) { + if (!text) return ''; + return CryptoJS.AES.encrypt(text, ENCRYPTION_KEY).toString(); + } + + decrypt(ciphertext) { + if (!ciphertext) return ''; + try { + const bytes = CryptoJS.AES.decrypt(ciphertext, ENCRYPTION_KEY); + return bytes.toString(CryptoJS.enc.Utf8); + } catch { + return ciphertext; } + } - encrypt(text) { - if (!text) return ''; - return CryptoJS.AES.encrypt(text, ENCRYPTION_KEY).toString(); - } + // ==================== 资料库操作 ==================== - decrypt(ciphertext) { - if (!ciphertext) return ''; - try { - const bytes = CryptoJS.AES.decrypt(ciphertext, ENCRYPTION_KEY); - return bytes.toString(CryptoJS.enc.Utf8); - } catch (e) { - return ciphertext; - } - } - - // ==================== 资料库操作 ==================== - - async getVaults() { - await this.ready; - const result = this.db.exec(` + async getVaults() { + await this.ready; + const result = this.db.exec(` SELECT v.*, COUNT(a.id) as account_count FROM vaults v LEFT JOIN accounts a ON a.vault_id = v.id @@ -115,211 +115,211 @@ class AccountDatabase { ORDER BY v.created_at ASC `); - if (!result.length) return []; + if (!result.length) return []; - const columns = result[0].columns; - return result[0].values.map(row => { - const obj = {}; - columns.forEach((col, i) => obj[col] = row[i]); - return obj; - }); + const columns = result[0].columns; + return result[0].values.map(row => { + const obj = {}; + columns.forEach((col, i) => obj[col] = row[i]); + return obj; + }); + } + + async addVault(vault) { + await this.ready; + this.db.run(`INSERT INTO vaults (name, icon, color) VALUES (?, ?, ?)`, [ + vault.name, + vault.icon || '📁', + vault.color || '#3b82f6' + ]); + this.save(); + + const result = this.db.exec('SELECT last_insert_rowid() as id'); + return { id: result[0]?.values[0][0], ...vault }; + } + + async updateVault(id, vault) { + await this.ready; + this.db.run(`UPDATE vaults SET name = ?, icon = ?, color = ? WHERE id = ?`, [ + vault.name, + vault.icon || '📁', + vault.color || '#3b82f6', + id + ]); + this.save(); + return { id, ...vault }; + } + + async deleteVault(id) { + await this.ready; + // 将该资料库中的账号移到默认资料库 + const defaultVault = this.db.exec("SELECT id FROM vaults WHERE name = '默认'"); + const defaultId = defaultVault[0]?.values[0]?.[0] || null; + + this.db.run('UPDATE accounts SET vault_id = ? WHERE vault_id = ?', [defaultId, id]); + this.db.run('DELETE FROM vaults WHERE id = ? AND name != ?', [id, '默认']); + this.save(); + return true; + } + + // ==================== 账号操作 ==================== + + async getAccounts(page = 1, limit = 10, vaultId = null) { + await this.ready; + const offset = (page - 1) * limit; + + let sql = `SELECT * FROM accounts`; + const params = []; + + if (vaultId !== null) { + sql += ` WHERE vault_id = ?`; + params.push(vaultId); } - async addVault(vault) { - await this.ready; - this.db.run(`INSERT INTO vaults (name, icon, color) VALUES (?, ?, ?)`, [ - vault.name, - vault.icon || '📁', - vault.color || '#3b82f6' - ]); - this.save(); + sql += ` ORDER BY updated_at DESC LIMIT ? OFFSET ?`; + params.push(limit, offset); - const result = this.db.exec('SELECT last_insert_rowid() as id'); - return { id: result[0]?.values[0][0], ...vault }; + const stmt = this.db.prepare(sql); + stmt.bind(params); + + const accounts = []; + while (stmt.step()) { + const row = stmt.getAsObject(); + accounts.push({ + ...row, + password: this.decrypt(row.password), + totp_secret: this.decrypt(row.totp_secret) + }); + } + stmt.free(); + + return accounts; + } + + async getAccountCount(vaultId = null) { + await this.ready; + let sql = 'SELECT COUNT(*) as count FROM accounts'; + const params = []; + + if (vaultId !== null) { + sql += ' WHERE vault_id = ?'; + params.push(vaultId); } - async updateVault(id, vault) { - await this.ready; - this.db.run(`UPDATE vaults SET name = ?, icon = ?, color = ? WHERE id = ?`, [ - vault.name, - vault.icon || '📁', - vault.color || '#3b82f6', - id - ]); - this.save(); - return { id, ...vault }; - } + const stmt = this.db.prepare(sql); + stmt.bind(params); + stmt.step(); + const result = stmt.getAsObject(); + stmt.free(); - async deleteVault(id) { - await this.ready; - // 将该资料库中的账号移到默认资料库 - const defaultVault = this.db.exec("SELECT id FROM vaults WHERE name = '默认'"); - const defaultId = defaultVault[0]?.values[0]?.[0] || null; + return result.count || 0; + } - this.db.run('UPDATE accounts SET vault_id = ? WHERE vault_id = ?', [defaultId, id]); - this.db.run('DELETE FROM vaults WHERE id = ? AND name != ?', [id, '默认']); - this.save(); - return true; - } + async addAccount(account) { + await this.ready; - // ==================== 账号操作 ==================== - - async getAccounts(page = 1, limit = 10, vaultId = null) { - await this.ready; - const offset = (page - 1) * limit; - - let sql = `SELECT * FROM accounts`; - const params = []; - - if (vaultId !== null) { - sql += ` WHERE vault_id = ?`; - params.push(vaultId); - } - - sql += ` ORDER BY updated_at DESC LIMIT ? OFFSET ?`; - params.push(limit, offset); - - const stmt = this.db.prepare(sql); - stmt.bind(params); - - const accounts = []; - while (stmt.step()) { - const row = stmt.getAsObject(); - accounts.push({ - ...row, - password: this.decrypt(row.password), - totp_secret: this.decrypt(row.totp_secret) - }); - } - stmt.free(); - - return accounts; - } - - async getAccountCount(vaultId = null) { - await this.ready; - let sql = 'SELECT COUNT(*) as count FROM accounts'; - const params = []; - - if (vaultId !== null) { - sql += ' WHERE vault_id = ?'; - params.push(vaultId); - } - - const stmt = this.db.prepare(sql); - stmt.bind(params); - stmt.step(); - const result = stmt.getAsObject(); - stmt.free(); - - return result.count || 0; - } - - async addAccount(account) { - await this.ready; - - this.db.run(` + this.db.run(` INSERT INTO accounts (vault_id, name, username, password, totp_secret, tags, email, proxy, notes, browser_id) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) `, [ - account.vault_id || null, - account.name, - account.username, - this.encrypt(account.password), - this.encrypt(account.totp_secret), - account.tags || '', - account.email, - account.proxy || '', - account.notes, - account.browser_id || '' - ]); + account.vault_id || null, + account.name, + account.username, + this.encrypt(account.password), + this.encrypt(account.totp_secret), + account.tags || '', + account.email, + account.proxy || '', + account.notes, + account.browser_id || '' + ]); - this.save(); + this.save(); - const result = this.db.exec('SELECT last_insert_rowid() as id'); - const id = result[0]?.values[0][0]; + const result = this.db.exec('SELECT last_insert_rowid() as id'); + const id = result[0]?.values[0][0]; - return { id, ...account }; - } + return { id, ...account }; + } - async updateAccount(id, account) { - await this.ready; + async updateAccount(id, account) { + await this.ready; - this.db.run(` + this.db.run(` UPDATE accounts SET vault_id = ?, name = ?, username = ?, password = ?, totp_secret = ?, tags = ?, email = ?, proxy = ?, notes = ?, browser_id = ?, updated_at = datetime('now') WHERE id = ? `, [ - account.vault_id || null, - account.name, - account.username, - this.encrypt(account.password), - this.encrypt(account.totp_secret), - account.tags || '', - account.email, - account.proxy || '', - account.notes, - account.browser_id || '', - id - ]); + account.vault_id || null, + account.name, + account.username, + this.encrypt(account.password), + this.encrypt(account.totp_secret), + account.tags || '', + account.email, + account.proxy || '', + account.notes, + account.browser_id || '', + id + ]); - this.save(); - return { id, ...account }; + this.save(); + return { id, ...account }; + } + + async moveAccountToVault(accountId, vaultId) { + await this.ready; + this.db.run('UPDATE accounts SET vault_id = ?, updated_at = datetime("now") WHERE id = ?', + [vaultId, accountId]); + this.save(); + return true; + } + + async deleteAccount(id) { + await this.ready; + this.db.run('DELETE FROM accounts WHERE id = ?', [id]); + this.save(); + return true; + } + + async searchAccounts(query, vaultId = null) { + await this.ready; + const searchTerm = `%${query}%`; + + let sql = `SELECT * FROM accounts WHERE (name LIKE ? OR username LIKE ? OR email LIKE ? OR tags LIKE ? OR proxy LIKE ?)`; + const params = [searchTerm, searchTerm, searchTerm, searchTerm, searchTerm]; + + if (vaultId !== null) { + sql += ` AND vault_id = ?`; + params.push(vaultId); } - async moveAccountToVault(accountId, vaultId) { - await this.ready; - this.db.run('UPDATE accounts SET vault_id = ?, updated_at = datetime("now") WHERE id = ?', - [vaultId, accountId]); - this.save(); - return true; + sql += ` ORDER BY updated_at DESC LIMIT 50`; + + const stmt = this.db.prepare(sql); + stmt.bind(params); + + const accounts = []; + while (stmt.step()) { + const row = stmt.getAsObject(); + accounts.push({ + ...row, + password: this.decrypt(row.password), + totp_secret: this.decrypt(row.totp_secret) + }); } + stmt.free(); - async deleteAccount(id) { - await this.ready; - this.db.run('DELETE FROM accounts WHERE id = ?', [id]); - this.save(); - return true; - } - - async searchAccounts(query, vaultId = null) { - await this.ready; - const searchTerm = `%${query}%`; - - let sql = `SELECT * FROM accounts WHERE (name LIKE ? OR username LIKE ? OR email LIKE ? OR tags LIKE ? OR proxy LIKE ?)`; - const params = [searchTerm, searchTerm, searchTerm, searchTerm, searchTerm]; - - if (vaultId !== null) { - sql += ` AND vault_id = ?`; - params.push(vaultId); - } - - sql += ` ORDER BY updated_at DESC LIMIT 50`; - - const stmt = this.db.prepare(sql); - stmt.bind(params); - - const accounts = []; - while (stmt.step()) { - const row = stmt.getAsObject(); - accounts.push({ - ...row, - password: this.decrypt(row.password), - totp_secret: this.decrypt(row.totp_secret) - }); - } - stmt.free(); - - return accounts; - } - - close() { - if (this.db) { - this.save(); - this.db.close(); - } + return accounts; + } + + close() { + if (this.db) { + this.save(); + this.db.close(); } + } } module.exports = AccountDatabase; diff --git a/src/index.html b/src/index.html index 6cbf63a..fb7edf9 100644 --- a/src/index.html +++ b/src/index.html @@ -5,7 +5,7 @@ + content="default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src 'self' data: https://cdnjs.cloudflare.com; img-src 'self' data:; connect-src 'self' http://localhost:12138 http://127.0.0.1:12138"> 2FA 账号管理器 @@ -16,10 +16,30 @@
-

账号列表

- @@ -31,6 +51,12 @@ 资料库
+ + +
全部 @@ -95,7 +121,12 @@
- +
+ + +
@@ -164,7 +195,7 @@ 0-9