From c30f828d2c8753dcac413f237a76391694ff6c31 Mon Sep 17 00:00:00 2001 From: KodekaTeam Date: Fri, 5 Jun 2026 14:19:45 +0700 Subject: [PATCH] fix(update): randomUUID crypto change is not working after being implemented on the online server. --- .../components/NotificationsSettingsPanel.tsx | 6 +- .../components/terminal/TerminalTabPanel.tsx | 85 +++++++++++-------- src/app/terminal/page.tsx | 3 +- src/lib/api.ts | 19 +---- src/lib/git-provider-callback-intent.ts | 6 +- src/lib/random-id.ts | 38 +++++++++ 6 files changed, 96 insertions(+), 61 deletions(-) create mode 100644 src/lib/random-id.ts diff --git a/src/app/notifications/components/NotificationsSettingsPanel.tsx b/src/app/notifications/components/NotificationsSettingsPanel.tsx index 3fadd6c..1fe30e1 100644 --- a/src/app/notifications/components/NotificationsSettingsPanel.tsx +++ b/src/app/notifications/components/NotificationsSettingsPanel.tsx @@ -31,6 +31,7 @@ import { normalizeNotificationActionKeys, NOTIFICATION_ACTION_OPTIONS, } from "@/app/notifications/components/notification-actions"; +import { createClientId } from "@/lib/random-id"; import type { EditableNotificationSettings, NotificationProviderItem, @@ -153,10 +154,7 @@ function createProvider( type: NotificationProviderKind, ): NotificationProviderItem { return { - id: - typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" - ? crypto.randomUUID() - : `provider-${Date.now()}-${Math.random().toString(16).slice(2)}`, + id: createClientId("provider"), type, name: PROVIDER_OPTIONS.find((item) => item.type === type)?.label || diff --git a/src/app/projects/[projectId]/environments/[environmentId]/containers/[containerId]/components/terminal/TerminalTabPanel.tsx b/src/app/projects/[projectId]/environments/[environmentId]/containers/[containerId]/components/terminal/TerminalTabPanel.tsx index bd3b4ba..5c7799d 100644 --- a/src/app/projects/[projectId]/environments/[environmentId]/containers/[containerId]/components/terminal/TerminalTabPanel.tsx +++ b/src/app/projects/[projectId]/environments/[environmentId]/containers/[containerId]/components/terminal/TerminalTabPanel.tsx @@ -11,6 +11,7 @@ import { TerminalSquare, } from "lucide-react"; import { terminal as terminalApi } from "@/lib/api"; +import { createClientId } from "@/lib/random-id"; import { sanitizeTerminalStreamChunk } from "@/lib/terminal-output"; import type { TerminalCommandPreset, @@ -23,7 +24,12 @@ interface TerminalTabPanelProps { terminal: TerminalTabData; } -type TerminalStatus = "idle" | "connecting" | "connected" | "disconnected" | "error"; +type TerminalStatus = + | "idle" + | "connecting" + | "connected" + | "disconnected" + | "error"; type TerminalSender = (data: string) => boolean; type StoredTerminalSession = { id: string; @@ -46,17 +52,17 @@ const summaryToneColor: Record< cyan: "var(--accent-cyan)", }; -const historyClass: Record = - { - Completed: "badge-online", - Running: "badge-warning", - Failed: "badge-danger", - }; +const historyClass: Record< + TerminalTabData["history"][number]["status"], + string +> = { + Completed: "badge-online", + Running: "badge-warning", + Failed: "badge-danger", +}; function presetTone(preset: TerminalCommandPreset) { - return preset.tone === "warning" - ? "rgba(245,158,11,0.1)" - : "var(--bg-input)"; + return preset.tone === "warning" ? "rgba(245,158,11,0.1)" : "var(--bg-input)"; } function formatTime(date: Date) { @@ -75,12 +81,8 @@ function createSessionId(terminal: TerminalTabData) { const safeSessionTarget = terminal.execTarget .replace(/[^a-zA-Z0-9_-]/g, "") .slice(0, 72); - const suffix = - typeof crypto !== "undefined" && "randomUUID" in crypto - ? crypto.randomUUID() - : `${Date.now()}-${Math.random().toString(36).slice(2)}`; - return `container-${safeSessionTarget}-${suffix}`.slice(0, 128); + return `container-${safeSessionTarget}-${createClientId()}`.slice(0, 128); } function getStorageId(terminal: TerminalTabData) { @@ -93,7 +95,9 @@ function readStoredSessions(): Record { try { const raw = localStorage.getItem(STORAGE_KEY); const parsed = raw ? JSON.parse(raw) : {}; - return typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) + return typeof parsed === "object" && + parsed !== null && + !Array.isArray(parsed) ? (parsed as Record) : {}; } catch { @@ -348,9 +352,7 @@ function ContainerTerminalPane({ ws.onopen = () => { onStatusChange("connected"); onSenderChange(sendInput); - enqueueTerminalOutput( - "\x1b[32mSSH connection established\x1b[0m\r\n", - ); + enqueueTerminalOutput("\x1b[32mSSH connection established\x1b[0m\r\n"); enqueueTerminalOutput( `\x1b[36mOpening container shell: ${terminal.execTarget}\x1b[0m\r\n`, ); @@ -489,9 +491,12 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) { const [sessionHistory, setSessionHistory] = useState( terminal.history, ); - const handleSenderChange = useCallback((nextSender: TerminalSender | null) => { - setSender(() => nextSender); - }, []); + const handleSenderChange = useCallback( + (nextSender: TerminalSender | null) => { + setSender(() => nextSender); + }, + [], + ); const handleSessionInitialized = useCallback(() => { writeStoredSession(storageId, { ...session, initialized: true }); }, [session, storageId]); @@ -609,13 +614,15 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) { title="Container Terminal" action={
- + {status === "connecting" ? ( ) : null} @@ -667,10 +674,14 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) { padding: 24, }} > - + Container terminal is unavailable - Start this container first, then reconnect to open an interactive shell. + Start this container first, then reconnect to open an + interactive shell.
)} @@ -705,7 +716,10 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) { borderRadius: 7, background: presetTone(preset), padding: 11, - cursor: sender && status === "connected" ? "pointer" : "not-allowed", + cursor: + sender && status === "connected" + ? "pointer" + : "not-allowed", opacity: sender && status === "connected" ? 1 : 0.62, }} > @@ -772,9 +786,7 @@ export default function TerminalTabPanel({ terminal }: TerminalTabPanelProps) { fontSize: 12, }} > - - {item.label} - + {item.label} - Preset commands executed in this page session will appear here. + Preset commands executed in this page session will appear + here. ) : null} diff --git a/src/app/terminal/page.tsx b/src/app/terminal/page.tsx index f5aaf9c..94eb1ea 100644 --- a/src/app/terminal/page.tsx +++ b/src/app/terminal/page.tsx @@ -15,6 +15,7 @@ import { terminal as terminalApi, Server as ServerType, } from "@/lib/api"; +import { createClientId } from "@/lib/random-id"; import { sanitizeTerminalStreamChunk } from "@/lib/terminal-output"; import { useSearchParams } from "next/navigation"; @@ -488,7 +489,7 @@ function TerminalPageContent() { const id = `${serverId}-${Date.now()}`; const tab: Tab = { id, - sessionId: crypto.randomUUID(), + sessionId: createClientId(), serverId, serverName: name, serverIp: ip, diff --git a/src/lib/api.ts b/src/lib/api.ts index 70c637f..bc448ac 100644 --- a/src/lib/api.ts +++ b/src/lib/api.ts @@ -35,16 +35,7 @@ function resolveRuntimeOrigin(protocol: "http" | "ws") { return null; } - const scheme = - protocol === "ws" - ? window.location.protocol === "https:" - ? "wss" - : "ws" - : window.location.protocol === "https:" - ? "https" - : "http"; - - return `${scheme}://${window.location.hostname}:${DEFAULT_API_PORT}`; + return `ws://${window.location.hostname}:${DEFAULT_API_PORT}`; } export function getApiBaseUrl() { @@ -54,12 +45,8 @@ export function getApiBaseUrl() { ); } -function getWsBaseUrl() { - return ( - process.env.NEXT_PUBLIC_WS_URL || - resolveRuntimeOrigin("ws") || - `ws://localhost:${DEFAULT_API_PORT}` - ); +function getWsBaseUrl(): string | undefined { + return process.env.NEXT_PUBLIC_WS_URL || undefined; } export function getHealthUrl() { diff --git a/src/lib/git-provider-callback-intent.ts b/src/lib/git-provider-callback-intent.ts index 9879604..d90accc 100644 --- a/src/lib/git-provider-callback-intent.ts +++ b/src/lib/git-provider-callback-intent.ts @@ -1,4 +1,5 @@ import type { GitProviderType } from "@/lib/api"; +import { createClientId } from "@/lib/random-id"; const CALLBACK_INTENT_TTL_SECONDS = 15 * 60; @@ -25,10 +26,7 @@ export function markGitProviderCallbackIntent(provider: GitProviderType): void { return; } - const nonce = - typeof crypto !== "undefined" && typeof crypto.randomUUID === "function" - ? crypto.randomUUID() - : `${Date.now()}-${Math.random().toString(36).slice(2, 10)}`; + const nonce = createClientId(); document.cookie = `${getCallbackIntentCookieName(provider)}=${encodeURIComponent(nonce)}; ${buildCookieAttributes(provider).join("; ")}`; } diff --git a/src/lib/random-id.ts b/src/lib/random-id.ts new file mode 100644 index 0000000..e160573 --- /dev/null +++ b/src/lib/random-id.ts @@ -0,0 +1,38 @@ +function createUuidFromRandomValues(cryptoApi: Crypto): string | null { + if (typeof cryptoApi.getRandomValues !== "function") { + return null; + } + + const bytes = new Uint8Array(16); + cryptoApi.getRandomValues(bytes); + + bytes[6] = (bytes[6] & 0x0f) | 0x40; + bytes[8] = (bytes[8] & 0x3f) | 0x80; + + const hex = Array.from(bytes, (byte) => + byte.toString(16).padStart(2, "0"), + ); + + return [ + hex.slice(0, 4).join(""), + hex.slice(4, 6).join(""), + hex.slice(6, 8).join(""), + hex.slice(8, 10).join(""), + hex.slice(10, 16).join(""), + ].join("-"); +} + +export function createClientId(prefix?: string): string { + const cryptoApi = + typeof globalThis !== "undefined" ? globalThis.crypto : undefined; + const suffix = + typeof cryptoApi?.randomUUID === "function" + ? cryptoApi.randomUUID() + : cryptoApi + ? createUuidFromRandomValues(cryptoApi) + : null; + const id = + suffix || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`; + + return prefix ? `${prefix}-${id}` : id; +}