Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
* Utility functions for notification triggers.
*/

import { generateUUID } from '@renderer/utils/uuid';

import type { NotificationTrigger, TriggerContentType, TriggerMode } from '@renderer/types/data';

/**
* Generates a UUID v4 for new triggers.
*/
export function generateId(): string {
return crypto.randomUUID();
return generateUUID();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { useCallback, useEffect, useState } from 'react';
import { api } from '@renderer/api';
import { confirm } from '@renderer/components/common/ConfirmDialog';
import { useStore } from '@renderer/store';
import { generateUUID } from '@renderer/utils/uuid';
import { Edit2, Loader2, Plus, Save, Server, Trash2, X } from 'lucide-react';

import { SettingsSectionHeader } from '../components/SettingsSectionHeader';
Expand Down Expand Up @@ -102,7 +103,7 @@ export const WorkspaceSection = (): React.JSX.Element => {

const handleAdd = async (): Promise<void> => {
const newProfile: SshConnectionProfile = {
id: crypto.randomUUID(),
id: generateUUID(),
name: formName.trim(),
host: formHost.trim(),
port: parseInt(formPort, 10) || 22,
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/store/slices/paneSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { MAX_PANES } from '@renderer/types/panes';
import { generateUUID } from '@renderer/utils/uuid';

import {
createEmptyPane,
Expand Down Expand Up @@ -140,7 +141,7 @@ export const createPaneSlice: StateCreator<AppState, [], [], PaneSlice> = (set,
};

// Create new pane with the tab
const newPaneId = crypto.randomUUID();
const newPaneId = generateUUID();
const newPane = {
...createEmptyPane(newPaneId),
tabs: [tab],
Expand Down Expand Up @@ -277,7 +278,7 @@ export const createPaneSlice: StateCreator<AppState, [], [], PaneSlice> = (set,
newSourceActiveTabId = newSourceTabs[oldIndex]?.id ?? newSourceTabs[oldIndex - 1]?.id ?? null;
}

const newPaneId = crypto.randomUUID();
const newPaneId = generateUUID();
const newPane = {
...createEmptyPane(newPaneId),
tabs: [tab],
Expand Down
5 changes: 3 additions & 2 deletions src/renderer/store/slices/tabSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
findTabBySessionAndProject,
truncateLabel,
} from '@renderer/types/tabs';
import { generateUUID } from '@renderer/utils/uuid';

import {
findPane,
Expand Down Expand Up @@ -172,7 +173,7 @@ export const createTabSlice: StateCreator<AppState, [], [], TabSlice> = (set, ge
// Create new tab with generated id and timestamp
const newTab: Tab = {
...tab,
id: crypto.randomUUID(),
id: generateUUID(),
label: truncateLabel(tab.label),
createdAt: Date.now(),
};
Expand Down Expand Up @@ -365,7 +366,7 @@ export const createTabSlice: StateCreator<AppState, [], [], TabSlice> = (set, ge
if (!focusedPane) return;

const newTab: Tab = {
id: crypto.randomUUID(),
id: generateUUID(),
type: 'dashboard',
label: 'Dashboard',
createdAt: Date.now(),
Expand Down
8 changes: 5 additions & 3 deletions src/renderer/types/tabs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Based on specs/001-tabbed-layout-dashboard/contracts/tab-state.ts
*/

import { generateUUID } from '@renderer/utils/uuid';

import type { Session } from './data';
import type { TriggerColor } from '@shared/constants/triggerColors';

Expand Down Expand Up @@ -52,7 +54,7 @@ export interface SearchNavigationPayload {
* The nonce ensures repeated clicks produce new navigations.
*/
export interface TabNavigationRequest {
/** Unique nonce per click/action (crypto.randomUUID) */
/** Unique nonce per click/action (generateUUID) */
id: string;
/** Kind of navigation */
kind: 'error' | 'search' | 'autoBottom';
Expand Down Expand Up @@ -194,7 +196,7 @@ export function createErrorNavigationRequest(
highlightColor?: TriggerColor
): TabNavigationRequest {
return {
id: crypto.randomUUID(),
id: generateUUID(),
kind: 'error',
source,
highlight: highlightColor ?? 'red',
Expand All @@ -209,7 +211,7 @@ export function createSearchNavigationRequest(
payload: SearchNavigationPayload
): TabNavigationRequest {
return {
id: crypto.randomUUID(),
id: generateUUID(),
kind: 'search',
source: 'commandPalette',
highlight: 'yellow',
Expand Down
25 changes: 25 additions & 0 deletions src/renderer/utils/uuid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* Generate a UUID v4 string.
*
* `crypto.randomUUID()` is only available in **secure contexts** (HTTPS or
* localhost). When the app is served over plain HTTP on a LAN IP (e.g.
* Docker accessed from another machine), the browser will not expose
* `randomUUID`. This helper falls back to `crypto.getRandomValues()` which
* is available in all modern browsers regardless of secure context.
*
* @see https://github.com/matt1398/claude-devtools/issues/132
*/
export function generateUUID(): string {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}

// Fallback: construct a v4 UUID from getRandomValues
const bytes = crypto.getRandomValues(new Uint8Array(16));
Comment on lines +13 to +18
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved robustness and consistency, it's better to use globalThis.crypto. The optional chaining (?.) on randomUUID prevents a ReferenceError in environments where crypto might not be defined. Using globalThis is also more portable across different JavaScript environments (e.g. window, worker).

This also makes the usage consistent with the fallback path which will rely on globalThis.crypto.

Suggested change
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
// Fallback: construct a v4 UUID from getRandomValues
const bytes = crypto.getRandomValues(new Uint8Array(16));
if (globalThis.crypto?.randomUUID) {
return globalThis.crypto.randomUUID();
}
// Fallback: construct a v4 UUID from getRandomValues
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave this as-is. This code only runs in browser renderers where crypto is always defined (it's been a web standard since IE 11). The typeof crypto.randomUUID === 'function' check already guards the missing method safely.

The globalThis.crypto?. suggestion is also inconsistent — it uses optional chaining on the check but not on the fallback (globalThis.crypto.getRandomValues()), so if crypto were somehow undefined, the fallback would throw instead of the feature-detect.

// Set version (4) and variant (RFC 4122)
bytes[6] = (bytes[6] & 0x0f) | 0x40;
bytes[8] = (bytes[8] & 0x3f) | 0x80;

const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
Loading