diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..074d9209 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,26 @@ +# Changelog + +## 1.1.1 (2026-02-28) + +### Fixes + +- **Email viewer**: Show/hide details toggle now stays in place when expanded instead of jumping to the bottom of the details section (#18) +- **Email viewer**: Details toggle text is now properly translated (was hardcoded in English) +- **Instrumentation**: Resolve Edge Runtime warnings by splitting Node.js-only code into a separate module +- **Security**: Patch minimatch ReDoS vulnerability (CVE-2026-27903) — upgrade 9.0.6→9.0.9 and 3.1.3→3.1.5 + +## 1.1.0 (2026-02-28) + +- Server-side version update check on startup (logs when a newer release is available) + +## 1.0.2 (2026-02-27) + +- Fix 4 CVEs in production Docker image (removed npm, upgraded Alpine packages) + +## 1.0.1 (2026-02-26) + +- Remove stale references, clean up README + +## 1.0.0 (2026-02-25) + +- Initial public release diff --git a/VERSION b/VERSION index 9084fa2f..524cb552 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.1.0 +1.1.1 diff --git a/components/email/email-viewer.tsx b/components/email/email-viewer.tsx index c13014e3..0c8846fd 100644 --- a/components/email/email-viewer.tsx +++ b/components/email/email-viewer.tsx @@ -972,9 +972,27 @@ export function EmailViewer({ )} - {/* Modern Expandable Details */} + {/* Details toggle - stays in place when expanded */} + + + {/* Expandable Details */} {showFullHeaders && ( -
+
{/* Security & Authentication Section */} {(email.authenticationResults || email.spamScore !== undefined) && (
@@ -1254,22 +1272,6 @@ export function EmailViewer({
)} -
diff --git a/instrumentation.node.ts b/instrumentation.node.ts new file mode 100644 index 00000000..f2985a30 --- /dev/null +++ b/instrumentation.node.ts @@ -0,0 +1,44 @@ +import { readFileSync } from "fs"; + +const VERSION_CHECK_URL = + "https://raw.githubusercontent.com/root-fr/jmap-webmail/main/VERSION"; + +const SEMVER_RE = /^\d+\.\d+\.\d+$/; + +function compareVersions(current: string, remote: string): number { + const a = current.split(".").map(Number); + const b = remote.split(".").map(Number); + for (let i = 0; i < 3; i++) { + if ((b[i] ?? 0) > (a[i] ?? 0)) return 1; + if ((b[i] ?? 0) < (a[i] ?? 0)) return -1; + } + return 0; +} + +const pkg = JSON.parse( + readFileSync(`${process.cwd()}/package.json`, "utf-8") +); +const current: string = pkg.version ?? "0.0.0"; +console.info(`JMAP Webmail v${current}`); + +if (process.env.NODE_ENV === "production") { + fetch(VERSION_CHECK_URL, { + cache: "no-store", + signal: AbortSignal.timeout(5000), + }) + .then((res) => { + if (!res.ok) return; + return res.text(); + }) + .then((text) => { + if (!text) return; + const remote = text.trim(); + if (!SEMVER_RE.test(remote)) return; + if (compareVersions(current, remote) > 0) { + console.info( + `Update available: v${remote} — https://github.com/root-fr/jmap-webmail` + ); + } + }) + .catch(() => {}); +} diff --git a/instrumentation.ts b/instrumentation.ts index 92fa0ce4..41630756 100644 --- a/instrumentation.ts +++ b/instrumentation.ts @@ -1,43 +1,5 @@ -import { readFileSync } from "fs"; - -const VERSION_CHECK_URL = - "https://raw.githubusercontent.com/root-fr/jmap-webmail/main/VERSION"; - -const SEMVER_RE = /^\d+\.\d+\.\d+$/; - -function compareVersions(current: string, remote: string): number { - const a = current.split(".").map(Number); - const b = remote.split(".").map(Number); - for (let i = 0; i < 3; i++) { - if ((b[i] ?? 0) > (a[i] ?? 0)) return 1; - if ((b[i] ?? 0) < (a[i] ?? 0)) return -1; - } - return 0; -} - export async function register() { - const pkg = JSON.parse( - readFileSync(`${process.cwd()}/package.json`, "utf-8") - ); - const current: string = pkg.version ?? "0.0.0"; - console.info(`JMAP Webmail v${current}`); - - if (process.env.NODE_ENV !== "production") return; - - try { - const res = await fetch(VERSION_CHECK_URL, { - cache: "no-store", - signal: AbortSignal.timeout(5000), - }); - if (!res.ok) return; - const remote = (await res.text()).trim(); - if (!SEMVER_RE.test(remote)) return; - if (compareVersions(current, remote) > 0) { - console.info( - `Update available: v${remote} — https://github.com/root-fr/jmap-webmail` - ); - } - } catch { - // Network unavailable — not critical + if (process.env.NEXT_RUNTIME === "nodejs") { + await import("./instrumentation.node"); } } diff --git a/package-lock.json b/package-lock.json index 3b521771..bf6a4ccb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "jmap-webmail", - "version": "0.1.0", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "jmap-webmail", - "version": "0.1.0", + "version": "1.1.0", "license": "MIT", "dependencies": { "@tanstack/react-virtual": "^3.13.18", @@ -1108,13 +1108,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array/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/@eslint/config-array/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1127,9 +1120,9 @@ } }, "node_modules/@eslint/config-array/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -1189,13 +1182,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/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/@eslint/eslintrc/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -1231,9 +1217,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -4117,14 +4103,11 @@ } }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "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", - "engines": { - "node": "18 || 20 || >=22" - } + "license": "MIT" }, "node_modules/baseline-browser-mapping": { "version": "2.10.0", @@ -4149,16 +4132,13 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.3.tgz", - "integrity": "sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0" } }, "node_modules/browserslist": { @@ -5026,13 +5006,6 @@ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, - "node_modules/eslint-plugin-react/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/eslint-plugin-react/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5045,9 +5018,9 @@ } }, "node_modules/eslint-plugin-react/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5121,13 +5094,6 @@ "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/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/eslint/node_modules/brace-expansion": { "version": "1.1.12", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", @@ -5163,9 +5129,9 @@ } }, "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.3.tgz", - "integrity": "sha512-M2GCs7Vk83NxkUyQV1bkABc4yxgz9kILhHImZiBPAZ9ybuvCb0/H7lEl5XvIg3g+9d4eNotkZA5IWwYl0tibaA==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -6762,13 +6728,13 @@ } }, "node_modules/minimatch": { - "version": "9.0.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.6.tgz", - "integrity": "sha512-kQAVowdR33euIqeA0+VZTDqU+qo1IeVY+hrKYtZMio3Pg0P0vuh/kwRylLUddJhB6pf3q/botcOvRtx4IN1wqQ==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^2.0.2" }, "engines": { "node": ">=16 || 14 >=14.17" diff --git a/package.json b/package.json index 732fa945..3655e633 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jmap-webmail", - "version": "1.1.0", + "version": "1.1.1", "description": "A modern JMAP webmail client built for Stalwart Mail Server", "author": "Matthieu MALVACHE ", "license": "MIT",