Skip to content

Commit e9d43f6

Browse files
wsszhsupEItanya
authored
fix(ui): use feature detection for crypto.randomUUID to support HTTP deployments (#1868)
**Summary** - The UI uses `crypto.randomUUID()` which requires a [Secure Context](https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID) (HTTPS or localhost). Deployments served over plain HTTP crash on `/agents/new` with `Uncaught TypeError: crypto.randomUUID is not a function`. - Introduces a `generateId()` utility in `ui/src/lib/utils.ts` that detects `crypto.randomUUID` availability at runtime and falls back to the `uuid` package (already a dependency, v14) when unavailable. - Replaces all 8 `crypto.randomUUID()` call sites across 4 source files. **Changes** | File | Change | |------|--------| | `ui/src/lib/utils.ts` | Add `generateId()` with runtime feature detection | | `ui/src/lib/promptSourceRow.ts` | `crypto.randomUUID()` → `generateId()` | | `ui/src/lib/openClawSandboxForm.ts` | `crypto.randomUUID()` → `generateId()` | | `ui/src/components/prompts/FragmentEntriesEditor.tsx` | 4× `crypto.randomUUID()` → `generateId()` | | `ui/src/app/agents/new/page.tsx` | 2× `crypto.randomUUID()` → `generateId()` | **Context** `crypto.randomUUID()` is a Web Crypto API gated behind Secure Context. Any deployment accessed via `http://` (common in internal/dev clusters without TLS termination) crashes when rendering the agent creation form. The `uuid` npm package (v14, already in `package.json`) uses `crypto.getRandomValues()` which works in all contexts. The fix uses runtime feature detection (`typeof crypto.randomUUID === "function"`) so it works with the pre-built Docker image regardless of deployment configuration — no Helm or environment variable changes needed. `ui/public/mockServiceWorker.js` also calls `crypto.randomUUID()` but is auto-generated by MSW and runs in a Service Worker context (always secure), so it is not modified. **Test plan** - [ ] `cd ui && npm run build` passes - [ ] `cd ui && npm run lint` passes - [ ] Deploy over HTTP → `/agents/new` → "Skip wizard" → form loads without crash - [ ] Deploy over HTTPS → same flow works using native `crypto.randomUUID()` - [ ] `grep -r 'crypto.randomUUID' ui/src/` returns only `utils.ts` feature-detection branch (and `mockServiceWorker.js`) Signed-off-by: wsszh <wsszh@users.noreply.github.com> Signed-off-by: sup <sup@supdeMacBook-Pro.local> Co-authored-by: sup <sup@supdeMacBook-Pro.local> Co-authored-by: Eitan Yarmush <eitan.yarmush@solo.io>
1 parent 88a4bc7 commit e9d43f6

5 files changed

Lines changed: 21 additions & 8 deletions

File tree

ui/src/app/agents/new/page.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { formAgentTypeFromApi, formUsesByoSections, formUsesDeclarativeSections
88
import { ModelConfig, AgentType, ContextConfig, type DeclarativeRuntime } from "@/types";
99
import { SystemPromptSection } from "@/components/create/SystemPromptSection";
1010
import { newPromptSourceRow, type PromptSourceRow } from "@/lib/promptSourceRow";
11+
import { generateId } from "@/lib/utils";
1112
import { ModelSelectionSection } from "@/components/create/ModelSelectionSection";
1213
import { ToolsSection } from "@/components/create/ToolsSection";
1314
import { MemorySection } from "@/components/create/MemorySection";
@@ -162,7 +163,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
162163
return {
163164
...prev,
164165
errors: { ...prev.errors, promptSources: undefined },
165-
promptSourceRows: [...nonEmpty, { id: crypto.randomUUID(), name: t, alias: "" }],
166+
promptSourceRows: [...nonEmpty, { id: generateId(), name: t, alias: "" }],
166167
};
167168
});
168169
}, []);
@@ -207,7 +208,7 @@ function AgentPageContent({ isEditMode, agentName, agentNamespace }: AgentPageCo
207208
const pt = decl?.promptTemplate;
208209
const srcRows: PromptSourceRow[] =
209210
pt?.dataSources?.map((ds) => ({
210-
id: crypto.randomUUID(),
211+
id: generateId(),
211212
name: ds.name || "",
212213
alias: ds.alias || "",
213214
})) ?? [newPromptSourceRow()];

ui/src/components/prompts/FragmentEntriesEditor.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Input } from "@/components/ui/input";
66
import { Textarea } from "@/components/ui/textarea";
77
import { Label } from "@/components/ui/label";
88
import { Plus, Trash2 } from "lucide-react";
9+
import { generateId } from "@/lib/utils";
910

1011
export interface FragmentRow {
1112
id: string;
@@ -16,10 +17,10 @@ export interface FragmentRow {
1617
export function rowsFromData(data: Record<string, string>): FragmentRow[] {
1718
const keys = Object.keys(data);
1819
if (keys.length === 0) {
19-
return [{ id: crypto.randomUUID(), key: "", value: "" }];
20+
return [{ id: generateId(), key: "", value: "" }];
2021
}
2122
return keys.map((key) => ({
22-
id: crypto.randomUUID(),
23+
id: generateId(),
2324
key,
2425
value: data[key] ?? "",
2526
}));
@@ -51,11 +52,11 @@ export function FragmentEntriesEditor({
5152

5253
const removeRow = (id: string) => {
5354
const next = rows.filter((r) => r.id !== id);
54-
onRowsChange(next.length > 0 ? next : [{ id: crypto.randomUUID(), key: "", value: "" }]);
55+
onRowsChange(next.length > 0 ? next : [{ id: generateId(), key: "", value: "" }]);
5556
};
5657

5758
const addRow = () => {
58-
onRowsChange([...rows, { id: crypto.randomUUID(), key: "", value: "" }]);
59+
onRowsChange([...rows, { id: generateId(), key: "", value: "" }]);
5960
};
6061

6162
return (

ui/src/lib/openClawSandboxForm.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ValueSource } from "@/types";
22
import { k8sRefUtils } from "@/lib/k8sUtils";
3+
import { generateId } from "@/lib/utils";
34

45
/** Default Sandbox CR backend when the harness form does not specify one. */
56
const SANDBOX_BACKEND_OPENCLAW = "openclaw" as const;
@@ -39,7 +40,7 @@ export interface OpenClawChannelRow {
3940

4041
export function newOpenClawChannelRow(): OpenClawChannelRow {
4142
return {
42-
id: crypto.randomUUID(),
43+
id: generateId(),
4344
name: "",
4445
channelType: "telegram",
4546
botTokenSource: "inline",

ui/src/lib/promptSourceRow.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1+
import { generateId } from "@/lib/utils";
2+
13
export interface PromptSourceRow {
24
id: string;
35
name: string;
46
alias: string;
57
}
68

79
export function newPromptSourceRow(): PromptSourceRow {
8-
return { id: crypto.randomUUID(), name: "", alias: "" };
10+
return { id: generateId(), name: "", alias: "" };
911
}

ui/src/lib/utils.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { clsx, type ClassValue } from "clsx";
22
import { twMerge } from "tailwind-merge";
3+
import { v4 as uuidv4 } from "uuid";
34
import { Message as A2AMessage, Task as A2ATask, TaskStatusUpdateEvent as A2ATaskStatusUpdateEvent, TaskArtifactUpdateEvent as A2ATaskArtifactUpdateEvent } from "@a2a-js/sdk";
45

56
export function cn(...inputs: ClassValue[]) {
@@ -36,6 +37,13 @@ export function getBackendUrl() {
3637
return "http://localhost:8083/api";
3738
}
3839

40+
export function generateId(): string {
41+
if (typeof crypto.randomUUID === "function") {
42+
return crypto.randomUUID();
43+
}
44+
return uuidv4();
45+
}
46+
3947
export function getRelativeTimeString(date: string | number | Date): string {
4048
const now = new Date();
4149
const past = new Date(date);

0 commit comments

Comments
 (0)