diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index a164a0ad..7a07de03 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}`; } /** @@ -4856,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,