From a92c852cc8e6aac8e0ec1757c5b1ab396202b79f Mon Sep 17 00:00:00 2001 From: texchi2 Date: Sat, 22 Aug 2026 18:09:59 +0800 Subject: [PATCH 1/2] =?UTF-8?q?fix:=20generateMessageId=20crashes=20on=20i?= =?UTF-8?q?nsecure=20origins=20(crypto.randomUUID=20undefined)=20=E2=80=94?= =?UTF-8?q?=20use=20guarded=20generateUUID();=20send=20failed=20after=20dr?= =?UTF-8?q?aft=20save,=20before=20EmailSubmission?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/jmap/client.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index a164a0ad..74224a7d 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -1,3 +1,4 @@ +import { generateUUID } from '@/lib/utils'; import type { Email, Mailbox, StateChange, AccountStates, Thread, Identity, EmailAddress, ContactCard, AddressBook, AddressBookRights, VacationResponse, Calendar, CalendarRights, CalendarEvent, CalendarEventFilter, CalendarTask, FileNode, FileNodeFilter, FileNodeRights, Principal, PushSubscription, EmailSubmission, ScheduledEmail, SendEmailResult, SharedAccount } from "./types"; import type { SieveScript, SieveCapabilities } from "./sieve-types"; import type { IJMAPClient, KeywordDiscoveryResult, KeywordInfo, KeywordMigration } from "./client-interface"; @@ -514,7 +515,11 @@ function stripMessageIdBrackets(id: string): string { function generateMessageId(fromEmail: string): string { const at = fromEmail.lastIndexOf('@'); const domain = at > 0 ? fromEmail.slice(at + 1) : 'localhost'; - return `${Date.now().toString(36)}.${crypto.randomUUID()}@${domain}`; + // FTM fix (2026-08-22): crypto.randomUUID() only exists in SECURE contexts (https/localhost). + // On a plain-http LAN origin it is undefined, so this line threw AFTER the draft was saved and + // BEFORE EmailSubmission/set — send silently failed with drafts piling up. generateUUID() from + // lib/utils falls back to crypto.getRandomValues, which insecure contexts do provide. + return `${Date.now().toString(36)}.${generateUUID()}@${domain}`; } /** From 5e0efff091d1ba53a5101d9b9dbd6d63ec0cc4a4 Mon Sep 17 00:00:00 2001 From: texchi2 Date: Sat, 22 Aug 2026 19:58:18 +0800 Subject: [PATCH 2/2] fix: toast store crashes on insecure origins (crypto.randomUUID undefined), breaking every post-action acknowledgement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit addToast() minted toast ids with crypto.randomUUID(), which only exists in secure contexts (https/localhost). On a plain-http LAN origin every toast.success/toast.error threw a TypeError, so e.g. calendar event Save crashed AFTER a successful CalendarEvent/set — before the dialog-close ran — leaving the dialog open reporting failure while the server had the event; retries created duplicates. Same class as the generateMessageId send bug. Also guards the same-class contact uid fallback in lib/jmap/client.ts (createContact). Both now use the guarded generateUUID() from lib/utils. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MtFkcUA8VM2XAKZKzptpqy --- lib/jmap/client.ts | 2 +- stores/toast-store.ts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index 74224a7d..7a07de03 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -4861,7 +4861,7 @@ export class JMAPClient implements IJMAPClient { "new-contact": { ...contactData, // Stalwart stores the card without one if omitted (#644) - uid: contactData.uid || `urn:uuid:${crypto.randomUUID()}`, + uid: contactData.uid || `urn:uuid:${generateUUID()}`, addressBookIds, } } diff --git a/stores/toast-store.ts b/stores/toast-store.ts index 820e178a..a7a6767a 100644 --- a/stores/toast-store.ts +++ b/stores/toast-store.ts @@ -1,5 +1,6 @@ import { create } from "zustand"; import { Toast, ToastAction } from "@/components/ui/toast"; +import { generateUUID } from "@/lib/utils"; interface ToastStore { toasts: Toast[]; @@ -12,7 +13,11 @@ export const useToastStore = create((set) => ({ toasts: [], addToast: (toast) => { - const id = crypto.randomUUID(); + // FTM fix (2026-08-22): crypto.randomUUID() only exists in SECURE contexts + // (https/localhost). On a plain-http LAN origin it is undefined, so EVERY + // toast crashed -- and took the caller's post-success path down with it + // (calendar save: the success toast threw before the dialog-close ran). + const id = generateUUID(); const newToast: Toast = { ...toast, id,