Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
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
2 changes: 0 additions & 2 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@
"ua-parser-js": "1.0.41",
"ufuzzy": "npm:@leeoniya/[email protected]",
"unique-names-generator": "4.7.1",
"uuid": "14.0.0",
"websocket-ts": "2.3.0",
"yup": "1.7.1"
},
Expand Down Expand Up @@ -150,7 +149,6 @@
"@types/semver": "7.7.1",
"@types/ssh2": "1.15.5",
"@types/ua-parser-js": "0.7.36",
"@types/uuid": "9.0.2",
"@vitejs/plugin-react": "6.0.5",
"@vitest/browser-playwright": "4.1.10",
"autoprefixer": "10.5.4",
Expand Down
11 changes: 0 additions & 11 deletions site/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions site/src/pages/AgentsPage/AgentChatPageView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
} from "react";
import { useQueryClient } from "react-query";
import type { UrlTransform } from "streamdown";
import { v4 as uuidv4 } from "uuid";
import { invalidateChatDiffContents } from "#/api/queries/chats";
import type * as TypesGen from "#/api/typesGenerated";
import type {
Expand All @@ -28,6 +27,7 @@ import { findWorkspaceAppWithAgent } from "#/modules/apps/workspaceApps";
import { useDashboard } from "#/modules/dashboard/useDashboard";
import { cn } from "#/utils/cn";
import { pageTitle } from "#/utils/page";
import { generateUUID } from "#/utils/random";
import { findWorkspaceAgent } from "#/utils/workspace";
import {
AgentChatInput,
Expand Down Expand Up @@ -592,7 +592,7 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
const createUserRightPanelTabId = (
kind: UserRightPanelTab["kind"],
): string => {
return `${kind}-${uuidv4()}`;
return `${kind}-${generateUUID()}`;
};

const handleAddTerminalTab = () => {
Expand All @@ -611,7 +611,7 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
{
id: tabId,
kind: "terminal",
reconnectionToken: uuidv4(),
reconnectionToken: generateUUID(),
},
]);
startPendingTab(tabId);
Expand Down Expand Up @@ -657,7 +657,7 @@ export const AgentChatPageView: FC<AgentChatPageViewProps> = ({
id: createUserRightPanelTabId("terminal"),
kind: "terminal",
label: app.display_name ?? app.slug,
reconnectionToken: uuidv4(),
reconnectionToken: generateUUID(),
initialCommand: app.command,
sourceAppId: app.id,
};
Expand Down
19 changes: 2 additions & 17 deletions site/src/pages/AgentsPage/hooks/useChatDraftAttachments.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useEffect, useRef, useState } from "react";
import { API } from "#/api/api";
import { MaxChatFileSizeBytes } from "#/api/typesGenerated";
import { generateUUID } from "#/utils/random";
import type { UploadState } from "../components/AgentChatInput";
import {
getChatFileURL,
Expand Down Expand Up @@ -82,8 +83,6 @@ type UploadRegistryEntry = {
// write storage or notify UI again.
const activeDraftUploads = new Map<string, UploadRegistryEntry>();

let fallbackClientIdCounter = 0;

const isTerminalRegistryStatus = (entry: UploadRegistryEntry) =>
entry.status === "uploaded" || entry.status === "error";

Expand All @@ -93,20 +92,7 @@ const pruneTerminalRegistryEntry = (entry: UploadRegistryEntry) => {
}
};

const createClientId = () => {
const cryptoObject =
typeof globalThis.crypto !== "undefined" ? globalThis.crypto : undefined;
if (cryptoObject?.randomUUID) {
return cryptoObject.randomUUID();
}
if (cryptoObject?.getRandomValues) {
const values = new Uint32Array(2);
cryptoObject.getRandomValues(values);
return `draft-${Date.now()}-${Array.from(values, (value) => value.toString(36)).join("-")}`;
}
fallbackClientIdCounter += 1;
return `draft-${Date.now()}-${fallbackClientIdCounter}`;
};
const createClientId = () => generateUUID();

const createBlobPreview = (file: File): string | undefined => {
if (file.type === "text/plain" || typeof URL.createObjectURL !== "function") {
Expand Down Expand Up @@ -884,5 +870,4 @@ export const resetChatDraftAttachmentRegistryForTest = () => {
notifySubscribers(entry);
}
activeDraftUploads.clear();
fallbackClientIdCounter = 0;
};
3 changes: 2 additions & 1 deletion site/src/pages/TaskPage/TaskApps.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
MockWorkspaceProxies,
} from "#/testHelpers/entities";
import { withAuthProvider, withProxyProvider } from "#/testHelpers/storybook";
import { generateUUID } from "#/utils/random";
import { TaskApps } from "./TaskApps";

const mockExternalApp: WorkspaceApp = {
Expand Down Expand Up @@ -123,7 +124,7 @@ export const WithManyEmbeddedApps: Story = {
function mockEmbeddedApp(name = MockWorkspaceApp.display_name): WorkspaceApp {
return {
...MockWorkspaceApp,
id: crypto.randomUUID(),
id: generateUUID(),
slug: kebabCase(name),
display_name: name,
external: false,
Expand Down
4 changes: 2 additions & 2 deletions site/src/pages/TemplateBuilder/TemplateBuilderPage.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { type FC, useCallback, useEffect, useMemo, useState } from "react";
import { useMutation, useQuery } from "react-query";
import { Navigate, useNavigate, useSearchParams } from "react-router";
import { v4 as uuidv4 } from "uuid";
import { deploymentConfig } from "#/api/queries/deployment";
import {
createTemplateFromBuilder,
Expand All @@ -12,6 +11,7 @@ import { Loader } from "#/components/Loader/Loader";
import { useAuthenticated } from "#/hooks/useAuthenticated";
import { linkToTemplate, useLinks } from "#/modules/navigation";
import { pageTitle } from "#/utils/page";
import { generateUUID } from "#/utils/random";
import { TemplateBuilderPageView } from "./TemplateBuilderPageView";
import type {
SelectedBaseMeta,
Expand All @@ -30,7 +30,7 @@ const TemplateBuilderPage: FC = () => {

// Stable session ID for the lifetime of this page mount, shared
// across wizard_entry and compose_completion telemetry events.
const sessionId = useMemo(() => uuidv4(), []);
const sessionId = useMemo(() => generateUUID(), []);

const builderDisabled = data?.config?.template_builder?.disabled ?? false;
const wizardReady =
Expand Down
4 changes: 2 additions & 2 deletions site/src/pages/TerminalPage/TerminalPage.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import TerminalPage from "./TerminalPage";

const reconnectToken = "terminal-page-test-reconnect-token";

vi.mock("uuid", () => ({
v4: () => "terminal-page-test-reconnect-token",
vi.mock("#/utils/random", () => ({
generateUUID: () => "terminal-page-test-reconnect-token",
}));
vi.stubGlobal("jest", vi);
await import("jest-canvas-mock");
Expand Down
4 changes: 2 additions & 2 deletions site/src/pages/TerminalPage/TerminalPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
} from "react";
import { useQuery } from "react-query";
import { useNavigate, useParams, useSearchParams } from "react-router";
import { v4 as uuidv4 } from "uuid";
import { deploymentConfig } from "#/api/queries/deployment";
import { appearanceSettings } from "#/api/queries/users";
import {
Expand All @@ -28,6 +27,7 @@ import { WorkspaceTerminalAlerts } from "#/modules/terminal/WorkspaceTerminalAle
import themes from "#/theme";
import { pageTitle } from "#/utils/page";
import { openMaybePortForwardedURL } from "#/utils/portForward";
import { generateUUID } from "#/utils/random";
import { getMatchingAgentOrFirst } from "#/utils/workspace";
import { TerminalCommandConsentDialog } from "./TerminalCommandConsentDialog";

Expand All @@ -49,7 +49,7 @@ const TerminalPage: FC = () => {
// The reconnection token is a unique token that identifies
// a terminal session. It's generated by the client to reduce
// a round-trip, and must be a UUIDv4.
const reconnectionToken = searchParams.get("reconnect") ?? uuidv4();
const reconnectionToken = searchParams.get("reconnect") ?? generateUUID();
const command = searchParams.get("command") || undefined;
const appSlug = searchParams.get("app") || undefined;
const containerName = searchParams.get("container") || undefined;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { expect, screen, userEvent, waitFor } from "storybook/test";
import type { SharedWorkspaceActor } from "#/api/typesGenerated";
import { generateUUID } from "#/utils/random";
import { WorkspaceSharingIndicator } from "./WorkspaceSharingIndicator";

const mockUser = (
name: string,
roles: SharedWorkspaceActor["roles"] = ["use"],
): SharedWorkspaceActor => ({
id: crypto.randomUUID(),
id: generateUUID(),
actor_type: "user",
name,
roles,
Expand All @@ -17,7 +18,7 @@ const mockGroup = (
name: string,
roles: SharedWorkspaceActor["roles"] = ["use"],
): SharedWorkspaceActor => ({
id: crypto.randomUUID(),
id: generateUUID(),
actor_type: "group",
name,
roles,
Expand Down
30 changes: 28 additions & 2 deletions site/src/utils/random.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
import { describe, expect, it } from "vitest";
import { generateConnectionSessionId } from "#/utils/random";
import { afterEach, describe, expect, it, vi } from "vitest";
import { generateConnectionSessionId, generateUUID } from "#/utils/random";
import { isUUID } from "#/utils/uuid";

describe("generateUUID", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

it("outputs a valid UUID", () => {
expect(isUUID(generateUUID())).toBe(true);
});

it("generates unique values across calls", () => {
const numValues = 1000;
const ids = new Set(
Array.from({ length: numValues }, () => generateUUID()),
);
expect(ids.size).toBe(numValues);
});

it("produces a valid UUID without crypto.randomUUID (insecure context)", () => {
vi.stubGlobal("crypto", {
getRandomValues: crypto.getRandomValues.bind(crypto),
});
expect(isUUID(generateUUID())).toBe(true);
});
});

describe("generateConnectionSessionId", () => {
it("is 32 characters long", () => {
Expand Down
22 changes: 22 additions & 0 deletions site/src/utils/random.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
/**
* Generate a random UUID (version 4). `crypto.randomUUID` only exists in
* secure contexts, so fall back to `crypto.getRandomValues` (which works over
* plain HTTP) when it is missing.
*/
export const generateUUID = (): string => {
if (typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
const bytes = crypto.getRandomValues(new Uint8Array(16));
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
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("-");
};

/**
* Generate a random hexadecimal string from the specified number of bytes.
*/
Expand Down
Loading