fix: v1.5.2 - patch Next.js WebSocket SSRF (CVE-2026-44578)

Security: upgrade Next.js 16.2.4 -> 16.2.6 to patch CVE-2026-44578
(GHSA-c4j6-fc7j-m34r, CVSS 8.6, unauthenticated WebSocket-upgrade
SSRF on the built-in Node server) and eleven other May 2026
advisories bundled in the same release. next-intl patched for
GHSA-4c35-wcg5-mm9h prototype pollution in the experimental
precompile path. React/react-dom rolled forward to 19.2.6.

Fixes:
- bulk delete now honours the "delete to trash" setting instead of
  always hard-deleting
- favicon repaints the base icon on zero unread so the badge clears
This commit is contained in:
Matthieu MALVACHE
2026-05-14 22:32:20 +02:00
committed by Matthieu MALVACHE
parent f3e84a9549
commit 3ea7441f93
8 changed files with 189 additions and 102 deletions
+36
View File
@@ -1,5 +1,41 @@
# Changelog
## 1.5.2 (2026-05-14)
### Security
- **Next.js upgraded to 16.2.6 to patch CVE-2026-44578**
(GHSA-c4j6-fc7j-m34r, CVSS 8.6). The framework's WebSocket upgrade
handler did not apply the safe-rewrite checks used for normal HTTP
requests, so a single unauthenticated HTTP upgrade could cause the
Node server to issue an internal request to any host reachable on
port 80 and return the response to the attacker. Self-hosted
deployments (the only mode this project supports) were exposed to
cloud metadata endpoints, internal APIs, and admin panels. The bump
to 16.2.6 also pulls in eleven other May 2026 advisories covering
middleware/proxy bypass, denial of service, and a React patch.
- **`next-intl` upgraded to patch GHSA-4c35-wcg5-mm9h**, a prototype
pollution issue in the experimental precompile path. This project
doesn't enable that path, but the dependency is patched anyway.
- **Hardening note for self-hosters**: a public PoC for CVE-2026-44578
exists. Redeploy on `rootfr/jmap-webmail:1.5.2` (or `latest`) and,
where you can, keep the container off untrusted networks and block
egress to cloud metadata endpoints (AWS IMDS, GCP metadata).
### Fixes
- **Bulk delete now honours the "delete to trash" setting**: emptying
a selection from the toolbar always performed a hard delete, even
when the user had configured deletes to move to Trash first. The
store path used by the toolbar now routes through the same trash
helper as the single-message delete action, so the behaviour matches
Settings.
- **Favicon clears the unread badge as soon as the inbox empties**:
on `unreadCount === 0` the favicon kept the last-painted badge until
the next refresh because the redraw skipped the zero case. The hook
now repaints the base icon on the zero transition so the badge
disappears immediately.
## 1.5.1 (2026-04-17)
### Fixes
+5
View File
@@ -276,6 +276,11 @@ This document tracks the development status and planned features for JMAP Webmai
- [x] Attachments render for mail originating from providers that stamp a Content-ID on every part (#58)
- [x] Email-to-self delivered instead of being dropped by the MTA's duplicate-message check (#60)
### Release 1.5.2 (2026-05-14)
- [x] Patch CVE-2026-44578 (Next.js WebSocket SSRF, CVSS 8.6) by upgrading to 16.2.6, plus eleven other May 2026 advisories
- [x] Bulk delete honours the "delete to trash" setting instead of always hard-deleting
- [x] Favicon unread badge clears on the zero transition instead of sticking until the next refresh
## Planned Features
### Real-time and PWA (1.6.0)
+1 -1
View File
@@ -1 +1 @@
1.5.1
1.5.2
+27 -10
View File
@@ -34,9 +34,7 @@ export function useFaviconBadge(count: number) {
const img = new Image();
img.onload = () => {
imageRef.current = img;
if (countRef.current > 0) {
applyBadge(canvas, img, countRef.current);
}
renderFavicon(canvas, img, countRef.current);
};
img.src = reader.result as string;
};
@@ -52,17 +50,36 @@ export function useFaviconBadge(count: number) {
}, []);
useEffect(() => {
if (count <= 0) {
clearDynamicFavicon();
return;
}
if (canvasRef.current) {
applyBadge(canvasRef.current, imageRef.current, count);
if (canvasRef.current && imageRef.current) {
renderFavicon(canvasRef.current, imageRef.current, count);
}
}, [count]);
}
function renderFavicon(
canvas: HTMLCanvasElement,
image: HTMLImageElement,
count: number,
) {
if (count > 0) {
applyBadge(canvas, image, count);
} else {
// Paint the plain base icon into the dynamic link instead of removing
// it. Chromium caches the previously-painted badged favicon until a
// new href replaces it; simply removing the link leaves the stale
// badge visible until the tab is reloaded.
drawBase(canvas, image);
}
}
function drawBase(canvas: HTMLCanvasElement, image: HTMLImageElement) {
const ctx = canvas.getContext("2d");
if (!ctx) return;
ctx.clearRect(0, 0, SIZE, SIZE);
ctx.drawImage(image, 0, 0, SIZE, SIZE);
setDynamicFavicon(canvas.toDataURL("image/png"));
}
function applyBadge(
canvas: HTMLCanvasElement,
image: HTMLImageElement | null,
+3 -2
View File
@@ -764,12 +764,13 @@ export class JMAPClient {
};
}
async batchMoveEmails(emailIds: string[], toMailboxId: string): Promise<void> {
async batchMoveEmails(emailIds: string[], toMailboxId: string, accountId?: string): Promise<void> {
if (emailIds.length === 0) return;
const targetAccountId = accountId || this.accountId;
const updates = Object.fromEntries(emailIds.map(id => [id, { mailboxIds: { [toMailboxId]: true } }]));
await this.request([
["Email/set", { accountId: this.accountId, update: updates }, "0"],
["Email/set", { accountId: targetAccountId, update: updates }, "0"],
]);
}
+86 -80
View File
@@ -14,10 +14,10 @@
"date-fns": "^4.1.0",
"dompurify": "^3.4.0",
"lucide-react": "^0.575.0",
"next": "^16.2.4",
"next": "^16.2.6",
"next-intl": "^4.9.1",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"zustand": "^5.0.9"
@@ -1317,18 +1317,18 @@
"license": "MIT"
},
"node_modules/@formatjs/icu-messageformat-parser": {
"version": "3.5.4",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.4.tgz",
"integrity": "sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==",
"version": "3.5.8",
"resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.8.tgz",
"integrity": "sha512-uZLvzLFN7iV2l8cbDdROwgKGtdELeLI4bpnsuz1DnyscHDxn8TdDE0anHzcfjtWK66XYCllGLV3Mi3CYcEPg/g==",
"license": "MIT",
"dependencies": {
"@formatjs/icu-skeleton-parser": "2.1.4"
"@formatjs/icu-skeleton-parser": "2.1.8"
}
},
"node_modules/@formatjs/icu-skeleton-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.4.tgz",
"integrity": "sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==",
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.8.tgz",
"integrity": "sha512-iX5i0O15gPf69l1WqmLFYwn7wq53lauvytvWFnHamIfX/5Ta56gpFj6fdeHRcKTV58IhrKv8QOvWfTYZYm7f+g==",
"license": "MIT"
},
"node_modules/@formatjs/intl-localematcher": {
@@ -1926,15 +1926,15 @@
}
},
"node_modules/@next/env": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz",
"integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.6.tgz",
"integrity": "sha512-gd8HoHN4ufj73WmR3JmVolrpJR47ILK6LouP5xElPglaVxir6e1a7VzvTvDWkOoPXT9rkkTzyCxBu4yeZfZwcw==",
"license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz",
"integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.6.tgz",
"integrity": "sha512-ZJGkkcNfYgrrMkqOdZ7zoLa1TOy0qpcMfk/z4Mh/FKUz40gVO+HNQWqmLxf67Z5WB64DRp0dhEbyHfel+6sJUg==",
"cpu": [
"arm64"
],
@@ -1948,9 +1948,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz",
"integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.6.tgz",
"integrity": "sha512-v/YLBHIY132Ced3puBJ7YJKw1lqsCrgcNo2aRJlCEyQrrCeRJlvGlnmxhPxNQI3KE3N1DN5r9TPNPvka3nq5RQ==",
"cpu": [
"x64"
],
@@ -1964,9 +1964,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz",
"integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.6.tgz",
"integrity": "sha512-RPOvqlYBbcQjkz9VQQDZ2T2bARIjXZV1KFlt+V2Mr6SW/e4I9fcKsaA0hdyf2FHoTlsV2xnBd5Y912rP/1Ce6w==",
"cpu": [
"arm64"
],
@@ -1980,9 +1980,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz",
"integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.6.tgz",
"integrity": "sha512-URUTu1+dMkxJsPFgm+OeEvq9wf5sujw0EvgYy80TDGHTSLTnIHeqb0Eu8A3sC95IRgjejQL+kC4mw+4yPxiAXA==",
"cpu": [
"arm64"
],
@@ -1996,9 +1996,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz",
"integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.6.tgz",
"integrity": "sha512-DOj182mPV8G3UkrayLoREM5YEYI+Dk5wv7Ox9xl1fFibAELEsFD0lDPfHIeILlutMMfdyhlzYPELG3peuKaurw==",
"cpu": [
"x64"
],
@@ -2012,9 +2012,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz",
"integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.6.tgz",
"integrity": "sha512-HKQ5SP/V/ub73UvF7n/zeJlxk2kLmtL7Wzrg4WfmkjmNos5onJ2tKu7yZOPdL18A6Svfn3max29ym+ry7NkK4g==",
"cpu": [
"x64"
],
@@ -2028,9 +2028,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz",
"integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.6.tgz",
"integrity": "sha512-LZXpTlPyS5v7HhSmnvsLGP3iIYgYOBnc8r8ArlT55sGHV89bR2HlDdBjWQ+PY6SJMmk8TuVGFuxalnP3k/0Dwg==",
"cpu": [
"arm64"
],
@@ -2044,9 +2044,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz",
"integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.6.tgz",
"integrity": "sha512-F0+4i0h9J6C4eE3EAPWsoCk7UW/dbzOjyzxY0qnDUOYFu6FFmdZ6l97/XdV3/Nz3VYyO7UWjyEJUXkGqcoXfMA==",
"cpu": [
"x64"
],
@@ -5742,9 +5742,9 @@
}
},
"node_modules/icu-minify": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.1.tgz",
"integrity": "sha512-6NkfF9GHHFouqnz+wuiLjCWQiyxoEyJ5liUv4Jxxo/8wyhV7MY0L0iTEGDAVEa4aAD58WqTxFMa20S5nyMjwNw==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.12.0.tgz",
"integrity": "sha512-zDmM05uav3t3+kxSfRrNlmyXOdj2b+uHA+p04CG32eJabtaHbugXujuL+YfRkwP9joAnf0Uh+RMGCKD5NLa5rQ==",
"funding": [
{
"type": "individual",
@@ -5819,15 +5819,21 @@
}
},
"node_modules/intl-messageformat": {
"version": "11.2.1",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.1.tgz",
"integrity": "sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==",
"version": "11.2.5",
"resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.5.tgz",
"integrity": "sha512-zaROHiUsnlSFXVypU54AsQuAm3DLmmSH8KfDhiUuG1XZ9NTQ4o3xlxIJYVNmeWAklyp3CWg0lhexNUnee8PsYQ==",
"license": "BSD-3-Clause",
"dependencies": {
"@formatjs/fast-memoize": "3.1.2",
"@formatjs/icu-messageformat-parser": "3.5.4"
"@formatjs/fast-memoize": "3.1.5",
"@formatjs/icu-messageformat-parser": "3.5.8"
}
},
"node_modules/intl-messageformat/node_modules/@formatjs/fast-memoize": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.5.tgz",
"integrity": "sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==",
"license": "MIT"
},
"node_modules/is-array-buffer": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
@@ -6837,12 +6843,12 @@
}
},
"node_modules/next": {
"version": "16.2.4",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz",
"integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==",
"version": "16.2.6",
"resolved": "https://registry.npmjs.org/next/-/next-16.2.6.tgz",
"integrity": "sha512-qOVgKJg1+At15NpeUP+eJgCHvTCgXsogweq87Ri/Ix7PkqQHg4sdaXmSFqKlgaIXE4kW0g25LE68W87UANlHtw==",
"license": "MIT",
"dependencies": {
"@next/env": "16.2.4",
"@next/env": "16.2.6",
"@swc/helpers": "0.5.15",
"baseline-browser-mapping": "^2.9.19",
"caniuse-lite": "^1.0.30001579",
@@ -6856,14 +6862,14 @@
"node": ">=20.9.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "16.2.4",
"@next/swc-darwin-x64": "16.2.4",
"@next/swc-linux-arm64-gnu": "16.2.4",
"@next/swc-linux-arm64-musl": "16.2.4",
"@next/swc-linux-x64-gnu": "16.2.4",
"@next/swc-linux-x64-musl": "16.2.4",
"@next/swc-win32-arm64-msvc": "16.2.4",
"@next/swc-win32-x64-msvc": "16.2.4",
"@next/swc-darwin-arm64": "16.2.6",
"@next/swc-darwin-x64": "16.2.6",
"@next/swc-linux-arm64-gnu": "16.2.6",
"@next/swc-linux-arm64-musl": "16.2.6",
"@next/swc-linux-x64-gnu": "16.2.6",
"@next/swc-linux-x64-musl": "16.2.6",
"@next/swc-win32-arm64-msvc": "16.2.6",
"@next/swc-win32-x64-msvc": "16.2.6",
"sharp": "^0.34.5"
},
"peerDependencies": {
@@ -6890,9 +6896,9 @@
}
},
"node_modules/next-intl": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.1.tgz",
"integrity": "sha512-N7ga0CjtYcdxNvaKNIi6eJ2mmatlHK5hp8rt0YO2Omoc1m0gean242/Ukdj6+gJNiReBVcYIjK0HZeNx7CV1ug==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.12.0.tgz",
"integrity": "sha512-v8KpppWG0yLLlChJ3Of6uoPew9LeRDBAtY6vpJmF7YJmBZlHEzzoEL4w1g1dAU+VleEPNoXNm9hg1eEsKWV5hw==",
"funding": [
{
"type": "individual",
@@ -6904,11 +6910,11 @@
"@formatjs/intl-localematcher": "^0.8.1",
"@parcel/watcher": "^2.4.1",
"@swc/core": "^1.15.2",
"icu-minify": "^4.9.1",
"icu-minify": "^4.12.0",
"negotiator": "^1.0.0",
"next-intl-swc-plugin-extractor": "^4.9.1",
"next-intl-swc-plugin-extractor": "^4.12.0",
"po-parser": "^2.1.1",
"use-intl": "^4.9.1"
"use-intl": "^4.12.0"
},
"peerDependencies": {
"next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
@@ -6921,9 +6927,9 @@
}
},
"node_modules/next-intl-swc-plugin-extractor": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.1.tgz",
"integrity": "sha512-8whJJ6oxJz8JqkHarggmmuEDyXgC7nEnaPhZD91CJwEWW4xp0AST3Mw17YxvHyP2vAF3taWfFbs1maD+WWtz3w==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.12.0.tgz",
"integrity": "sha512-jUxVEu1Nryjt4YgaDktSys7ioOgQfcNPF/SF2dbPNxbVb6U+P1INRgHeCVN+EC59H2rnTFIQwbddmOCrUWFr3g==",
"license": "MIT"
},
"node_modules/next-intl/node_modules/@swc/core": {
@@ -7350,9 +7356,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.8",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
"integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
"version": "8.5.14",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz",
"integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==",
"dev": true,
"funding": [
{
@@ -7446,9 +7452,9 @@
}
},
"node_modules/react": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"version": "19.2.6",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.6.tgz",
"integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==",
"license": "MIT",
"peer": true,
"engines": {
@@ -7456,16 +7462,16 @@
}
},
"node_modules/react-dom": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
"integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
"version": "19.2.6",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.6.tgz",
"integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==",
"license": "MIT",
"peer": true,
"dependencies": {
"scheduler": "^0.27.0"
},
"peerDependencies": {
"react": "^19.2.4"
"react": "^19.2.6"
}
},
"node_modules/react-is": {
@@ -8495,9 +8501,9 @@
}
},
"node_modules/use-intl": {
"version": "4.9.1",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.1.tgz",
"integrity": "sha512-iGVV/xFYlhe3btafRlL8RPLD2Jsuet4yqn9DR6LWWbMhULsJnXgLonDkzDmsAIBIwFtk02oJuX/Ox2vwHKF+UQ==",
"version": "4.12.0",
"resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.12.0.tgz",
"integrity": "sha512-r+qVb7UI1+kiOhjYsmsNUCY+jrnjVopwGeFlmMyQj4YInlwZzgMeMSv9n8MqnWWy77HL5BVM8K2WgX50SbtcpA==",
"funding": [
{
"type": "individual",
@@ -8508,7 +8514,7 @@
"dependencies": {
"@formatjs/fast-memoize": "^3.1.0",
"@schummar/icu-type-parser": "1.21.5",
"icu-minify": "^4.9.1",
"icu-minify": "^4.12.0",
"intl-messageformat": "^11.1.0"
},
"peerDependencies": {
+4 -4
View File
@@ -1,6 +1,6 @@
{
"name": "jmap-webmail",
"version": "1.5.1",
"version": "1.5.2",
"description": "A modern JMAP webmail client built for Stalwart Mail Server",
"author": "Matthieu MALVACHE <matthieu@root.cloud>",
"license": "MIT",
@@ -36,10 +36,10 @@
"date-fns": "^4.1.0",
"dompurify": "^3.4.0",
"lucide-react": "^0.575.0",
"next": "^16.2.4",
"next": "^16.2.6",
"next-intl": "^4.9.1",
"react": "^19.2.1",
"react-dom": "^19.2.1",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"sonner": "^2.0.7",
"tailwind-merge": "^3.4.0",
"zustand": "^5.0.9"
+27 -5
View File
@@ -897,19 +897,35 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
},
batchDelete: async (client) => {
const { selectedEmailIds, emails, mailboxes } = get();
const { selectedEmailIds, emails, mailboxes, selectedMailbox } = get();
if (selectedEmailIds.size === 0) return;
set({ error: null });
try {
const emailIdsArray = Array.from(selectedEmailIds);
await client.batchDeleteEmails(emailIdsArray);
const deletedEmails = emails.filter(e => selectedEmailIds.has(e.id));
const deleteAction = useSettingsStore.getState().deleteAction;
const currentMailbox = mailboxes.find(mb => mb.id === selectedMailbox);
const accountId = currentMailbox?.isShared ? currentMailbox.accountId : undefined;
let trashMailbox: typeof mailboxes[number] | undefined;
if (deleteAction === 'trash') {
trashMailbox = mailboxes.find(mb => {
if (accountId) return mb.role === 'trash' && mb.accountId === accountId;
return mb.role === 'trash' && !mb.isShared;
});
}
if (trashMailbox) {
const trashId = trashMailbox.originalId || trashMailbox.id;
await client.batchMoveEmails(emailIdsArray, trashId, accountId);
} else {
await client.batchDeleteEmails(emailIdsArray);
}
// Remove deleted emails from local state
const remainingEmails = emails.filter(e => !selectedEmailIds.has(e.id));
// Update mailbox counters
const deletedEmails = emails.filter(e => selectedEmailIds.has(e.id));
const updatedMailboxes = mailboxes.map(mailbox => {
let deltaTotalEmails = 0;
let deltaUnreadEmails = 0;
@@ -921,6 +937,12 @@ export const useEmailStore = create<EmailStore>((set, get) => ({
deltaUnreadEmails--;
}
}
if (trashMailbox && mailbox.id === trashMailbox.id && !email.mailboxIds?.[mailbox.id]) {
deltaTotalEmails++;
if (!email.keywords?.$seen) {
deltaUnreadEmails++;
}
}
});
return {