diff --git a/frontend/src/api/tools.ts b/frontend/src/api/tools.ts index 966b7bf2..477a46c1 100644 --- a/frontend/src/api/tools.ts +++ b/frontend/src/api/tools.ts @@ -1,4 +1,5 @@ import { ApiError } from './errors'; +import { generateId } from '../lib/utils'; export interface QueryResult { /** Source text of the statement that produced this result, when known. */ @@ -62,7 +63,7 @@ export async function executeTool( }, body: JSON.stringify({ jsonrpc: '2.0', - id: crypto.randomUUID(), + id: generateId(), method: 'tools/call', params: { name: toolName, diff --git a/frontend/src/components/views/ToolDetailView.tsx b/frontend/src/components/views/ToolDetailView.tsx index 2180be05..47a096e7 100644 --- a/frontend/src/components/views/ToolDetailView.tsx +++ b/frontend/src/components/views/ToolDetailView.tsx @@ -3,6 +3,7 @@ import { useParams, Navigate, useSearchParams } from 'react-router-dom'; import { fetchSource } from '../../api/sources'; import { executeTool, type QueryResult } from '../../api/tools'; import { ApiError } from '../../api/errors'; +import { generateId } from '../../lib/utils'; import type { Tool } from '../../types/datasource'; import { SqlEditor, ParameterForm, RunButton, ResultsTabs, type ResultTab, type SqlEditorHandle } from '../tool'; import LockIcon from '../icons/LockIcon'; @@ -230,7 +231,7 @@ export default function ToolDetailView() { // collapsed into a single tab - only the first tab's time reflects the // whole batch's duration, since the others didn't wait separately. const newTabs: ResultTab[] = queryResults.map((result, index) => ({ - id: crypto.randomUUID(), + id: generateId(), // Offset timestamps so tabs from the same run sort stably and get distinct ids/keys. timestamp: new Date(timestamp.getTime() + index), result, @@ -244,7 +245,7 @@ export default function ToolDetailView() { setActiveTabId(newTabs[0].id); } catch (err) { const errorTab: ResultTab = { - id: crypto.randomUUID(), + id: generateId(), timestamp: new Date(), result: null, error: err instanceof Error ? err.message : 'Query failed', diff --git a/frontend/src/lib/utils.ts b/frontend/src/lib/utils.ts index fed2fe91..c283add0 100644 --- a/frontend/src/lib/utils.ts +++ b/frontend/src/lib/utils.ts @@ -4,3 +4,17 @@ import { twMerge } from 'tailwind-merge' export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)) } + +// crypto.randomUUID is only exposed in secure contexts (HTTPS or localhost), +// so fall back to building a v4 UUID from getRandomValues when the workbench +// is served over plain HTTP from a remote host. +export function generateId(): string { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID() + } + const bytes = crypto.getRandomValues(new Uint8Array(16)) + 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)}` +}