fix: replace unguarded crypto.randomUUID() with safe generateUUID() utility
This commit is contained in:
@@ -18,7 +18,7 @@ import { useContactStore, getContactDisplayName } from "@/stores/contact-store";
|
||||
import { useAuthStore, redirectToLogin } from "@/stores/auth-store";
|
||||
import { useEmailStore } from "@/stores/email-store";
|
||||
import { toast } from "@/stores/toast-store";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn, generateUUID } from "@/lib/utils";
|
||||
import { NavigationRail } from "@/components/layout/navigation-rail";
|
||||
import { SidebarAppsModal } from "@/components/layout/sidebar-apps-modal";
|
||||
import { InlineAppView } from "@/components/layout/inline-app-view";
|
||||
@@ -274,7 +274,7 @@ export default function ContactsPage() {
|
||||
toast.success(t("toast.created"));
|
||||
} else {
|
||||
const localContact: ContactCard = {
|
||||
id: `local-${crypto.randomUUID()}`,
|
||||
id: `local-${generateUUID()}`,
|
||||
addressBookIds: {},
|
||||
...data,
|
||||
};
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
} from "@/lib/calendar-participants";
|
||||
import { PluginSlot } from "@/components/plugins/plugin-slot";
|
||||
import { useSettingsStore } from "@/stores/settings-store";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
|
||||
export interface PendingEventPreview {
|
||||
start: Date;
|
||||
@@ -402,9 +403,7 @@ export function EventModal({
|
||||
if (!event || !onDuplicate) return;
|
||||
const start = getEventStartDate(event);
|
||||
const newStart = addDays(start, 1);
|
||||
const newUid = typeof crypto !== 'undefined' && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
||||
const newUid = generateUUID();
|
||||
const data: Partial<CalendarEvent> = {
|
||||
uid: newUid,
|
||||
title: event.title,
|
||||
|
||||
@@ -8,7 +8,7 @@ import { EMAIL_SANITIZE_CONFIG, collapseBlockedImageContainers } from "@/lib/ema
|
||||
import { hasMeaningfulHtmlBody } from "@/lib/signature-utils";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Avatar } from "@/components/ui/avatar";
|
||||
import { formatFileSize, cn, buildMailboxTree, MailboxNode, formatDateTime } from "@/lib/utils";
|
||||
import { formatFileSize, cn, buildMailboxTree, MailboxNode, formatDateTime, generateUUID } from "@/lib/utils";
|
||||
import { getSecurityStatus, extractListHeaders } from "@/lib/email-headers";
|
||||
import {
|
||||
Reply,
|
||||
@@ -4874,7 +4874,7 @@ export function EmailViewer({
|
||||
if (client && supportsSync) {
|
||||
createContact(client, contactData).then(() => toast.success('Contact added'));
|
||||
} else {
|
||||
addLocalContact({ id: `local-${crypto.randomUUID()}`, addressBookIds: {}, ...contactData } as ContactCard);
|
||||
addLocalContact({ id: `local-${generateUUID()}`, addressBookIds: {}, ...contactData } as ContactCard);
|
||||
toast.success('Contact added');
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -16,7 +16,7 @@ import type {
|
||||
FilterActionType,
|
||||
} from "@/lib/jmap/sieve-types";
|
||||
import type { Mailbox } from "@/lib/jmap/types";
|
||||
import { buildMailboxTree, flattenMailboxTree, type MailboxNode } from "@/lib/utils";
|
||||
import { buildMailboxTree, flattenMailboxTree, type MailboxNode, generateUUID } from "@/lib/utils";
|
||||
|
||||
interface FilterRuleModalProps {
|
||||
rule?: FilterRule;
|
||||
@@ -109,7 +109,7 @@ export function FilterRuleModal({
|
||||
}
|
||||
|
||||
onSave({
|
||||
id: rule?.id || crypto.randomUUID(),
|
||||
id: rule?.id || generateUUID(),
|
||||
name: trimmedName,
|
||||
enabled: rule?.enabled ?? true,
|
||||
matchType,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { CalendarEvent, CalendarParticipant } from '@/lib/jmap/types';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
export interface ParticipantInfo {
|
||||
id: string;
|
||||
@@ -104,9 +105,7 @@ export function buildParticipantMap(
|
||||
): Record<string, Partial<CalendarParticipant>> {
|
||||
const participants: Record<string, Partial<CalendarParticipant>> = {};
|
||||
|
||||
const generateId = () => typeof crypto !== 'undefined' && crypto.randomUUID
|
||||
? crypto.randomUUID()
|
||||
: `p-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
||||
const generateId = () => generateUUID();
|
||||
|
||||
participants[generateId()] = {
|
||||
'@type': 'Participant',
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
* All line endings are CRLF per RFC 5322.
|
||||
*/
|
||||
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
const CRLF = '\r\n';
|
||||
|
||||
export interface MimeAttachment {
|
||||
@@ -43,7 +45,7 @@ export function buildMimeMessage(input: MimeMessageInput): Uint8Array {
|
||||
// BCC is intentionally omitted from the MIME headers per RFC 5322
|
||||
lines.push(formatHeader('Subject', encodeHeaderValue(input.subject)));
|
||||
lines.push(formatHeader('Date', formatDate(input.date ?? new Date())));
|
||||
lines.push(formatHeader('Message-ID', input.messageId ?? `<${crypto.randomUUID()}@smime.local>`));
|
||||
lines.push(formatHeader('Message-ID', input.messageId ?? `<${generateUUID()}@smime.local>`));
|
||||
if (input.inReplyTo) {
|
||||
lines.push(formatHeader('In-Reply-To', input.inReplyTo));
|
||||
}
|
||||
@@ -245,7 +247,7 @@ export function wrapCmsAsSmimeMessage(cmsBlob: Blob | ArrayBuffer | Uint8Array,
|
||||
}
|
||||
lines.push(formatHeader('Subject', encodeHeaderValue(input.subject)));
|
||||
lines.push(formatHeader('Date', formatDate(input.date ?? new Date())));
|
||||
lines.push(formatHeader('Message-ID', input.messageId ?? `<${crypto.randomUUID()}@smime.local>`));
|
||||
lines.push(formatHeader('Message-ID', input.messageId ?? `<${generateUUID()}@smime.local>`));
|
||||
if (input.inReplyTo) {
|
||||
lines.push(formatHeader('In-Reply-To', input.inReplyTo));
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as asn1js from 'asn1js';
|
||||
import * as pkijs from 'pkijs';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
import {
|
||||
extractCertificateInfo,
|
||||
classifyCapabilities,
|
||||
@@ -154,7 +155,7 @@ export async function importPkcs12(
|
||||
const email = certInfo.emailAddresses[0] ?? '';
|
||||
|
||||
const keyRecord: SmimeKeyRecord = {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
email: email.toLowerCase(),
|
||||
certificate: leafCertDer,
|
||||
certificateChain: chainCertsDer,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import type { EmailTemplate } from './template-types';
|
||||
import { BUILT_IN_PLACEHOLDERS } from './template-types';
|
||||
import { generateUUID } from './utils';
|
||||
|
||||
const PLACEHOLDER_REGEX = /\{\{(\w+)\}\}/g;
|
||||
const MAX_TEMPLATE_NAME_LENGTH = 200;
|
||||
@@ -149,7 +150,7 @@ export function importTemplates(json: string): ImportResult {
|
||||
const recipients = t.defaultRecipients as Record<string, unknown> | undefined;
|
||||
|
||||
templates.push({
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
name: sanitizeText(t.name),
|
||||
subject: sanitizeText(t.subject),
|
||||
body: sanitizeText(t.body),
|
||||
|
||||
@@ -7,6 +7,13 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function generateUUID(): string {
|
||||
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
return `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
|
||||
}
|
||||
|
||||
export function formatDate(date: Date | string): string {
|
||||
const d = typeof date === "string" ? new Date(date) : date;
|
||||
const now = new Date();
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import type { ContactCard, NameComponent, ContactOnlineService, AnniversaryDate, PartialDate } from "@/lib/jmap/types";
|
||||
import { generateUUID } from "@/lib/utils";
|
||||
|
||||
// Convert RFC 9553 AnniversaryDate (PartialDate|Timestamp|string) to vCard date string
|
||||
function anniversaryDateToVcardString(date: AnniversaryDate): string {
|
||||
@@ -153,7 +154,7 @@ export function parseVCard(vcfString: string): ContactCard[] {
|
||||
}
|
||||
|
||||
function buildContact(raw: Record<string, string[]>): ContactCard | null {
|
||||
const id = `import-${crypto.randomUUID()}`;
|
||||
const id = `import-${generateUUID()}`;
|
||||
const card: ContactCard = { id, addressBookIds: {} };
|
||||
|
||||
for (const [fullKey, values] of Object.entries(raw)) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import { debug } from '@/lib/debug';
|
||||
import { normalizeAllDayDuration } from '@/lib/calendar-utils';
|
||||
import { sanitizeOutgoingCalendarEventData } from '@/lib/calendar-event-normalization';
|
||||
import { expandRecurringEvents } from '@/lib/recurrence-expansion';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
export type CalendarViewMode = 'month' | 'week' | 'day' | 'agenda' | 'tasks';
|
||||
|
||||
@@ -722,9 +723,7 @@ export const useCalendarStore = create<CalendarStore>()(
|
||||
if (!calendar) throw new Error('Failed to create calendar');
|
||||
|
||||
const subscription: ICalSubscription = {
|
||||
id: typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
|
||||
? crypto.randomUUID()
|
||||
: `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
|
||||
id: generateUUID(),
|
||||
url,
|
||||
calendarId: calendar.id,
|
||||
name,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { ContactCard, AddressBook, ContactName } from '@/lib/jmap/types';
|
||||
import type { IJMAPClient } from '@/lib/jmap/client-interface';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
export function getContactDisplayName(contact: ContactCard): string {
|
||||
if (contact.name?.components) {
|
||||
@@ -378,7 +379,7 @@ export const useContactStore = create<ContactStore>()(
|
||||
set((state) => ({ contacts: [...state.contacts, created] }));
|
||||
} else {
|
||||
const localGroup: ContactCard = {
|
||||
id: `local-${crypto.randomUUID()}`,
|
||||
id: `local-${generateUUID()}`,
|
||||
addressBookIds: {},
|
||||
...groupData,
|
||||
} as ContactCard;
|
||||
@@ -617,7 +618,7 @@ export const useContactStore = create<ContactStore>()(
|
||||
} else {
|
||||
const localContact: ContactCard = {
|
||||
...contact,
|
||||
id: `local-${crypto.randomUUID()}`,
|
||||
id: `local-${generateUUID()}`,
|
||||
};
|
||||
set((state) => ({ contacts: [...state.contacts, localContact] }));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { SmimeKeyRecord, SmimePublicCert } from '@/lib/smime/types';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
import {
|
||||
saveKeyRecord,
|
||||
listKeyRecords,
|
||||
@@ -287,7 +288,7 @@ export const useSmimeStore = create<SmimeStore>()(
|
||||
const email = info.emailAddresses[0] ?? '';
|
||||
|
||||
const publicCert: SmimePublicCert = {
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
accountId: get().currentAccountId ?? undefined,
|
||||
email: email.toLowerCase(),
|
||||
certificate: der,
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
importTemplates as importUtil,
|
||||
filterTemplates,
|
||||
} from '@/lib/template-utils';
|
||||
import { generateUUID } from '@/lib/utils';
|
||||
|
||||
const MAX_RECENT = 5;
|
||||
|
||||
@@ -39,7 +40,7 @@ export const useTemplateStore = create<TemplateStore>()(
|
||||
const now = new Date().toISOString();
|
||||
const template: EmailTemplate = {
|
||||
...data,
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
@@ -73,8 +74,9 @@ export const useTemplateStore = create<TemplateStore>()(
|
||||
const now = new Date().toISOString();
|
||||
const duplicate: EmailTemplate = {
|
||||
...original,
|
||||
id: crypto.randomUUID(),
|
||||
id: generateUUID(),
|
||||
name: `${original.name} ${nameSuffix || '(copy)'}`,
|
||||
|
||||
isFavorite: false,
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
||||
Reference in New Issue
Block a user