mirror of
https://github.com/t8y2/dbx.git
synced 2026-10-19 16:16:45 +00:00
@@ -32,6 +32,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Database, Table, Columns, Loader2, Save, Upload, Settings, ChevronRight, X, AlertCircle, ArrowUp, ArrowDown } from "@lucide/vue";
|
||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
const { t } = useI18n();
|
||||
const store = useConnectionStore();
|
||||
@@ -752,7 +753,7 @@ async function startInsert() {
|
||||
execute: async () => {
|
||||
executing.value = true;
|
||||
insertCancelled.value = false;
|
||||
const executionId = crypto.randomUUID();
|
||||
const executionId = uuid();
|
||||
activeExecutionId = executionId;
|
||||
const startedAt = performance.now();
|
||||
const perTable: TableResult[] = [];
|
||||
|
||||
@@ -7,6 +7,7 @@ import type { UpdateDownloadSource as SettingsUpdateDownloadSource } from "@/sto
|
||||
import type { UpdateDownloadProgress } from "@/lib/backend/tauri";
|
||||
import { currentLocale } from "@/i18n";
|
||||
import { shouldBlockAppUpdate } from "@/lib/app/appUpdateTaskGuard";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
interface UseAppUpdaterOptions {
|
||||
getActiveTaskCount?: () => number;
|
||||
@@ -266,7 +267,7 @@ export function useAppUpdater(options: UseAppUpdaterOptions = {}) {
|
||||
automaticDownload = automatic;
|
||||
const version = updateInfo.value!.latest_version;
|
||||
const token = ++generation;
|
||||
const attemptId = crypto.randomUUID();
|
||||
const attemptId = uuid();
|
||||
phase.value = "downloading";
|
||||
downloadProgress.value = null;
|
||||
clearError();
|
||||
|
||||
@@ -2,6 +2,7 @@ import { computed, ref, toValue, watch, type MaybeRefOrGetter } from "vue";
|
||||
import { filterModeNeedsValue, filterModeUsesRange } from "@/lib/dataGrid/dataGridColumnFilter";
|
||||
import type { DataGridContextFilterMode } from "@/lib/dataGrid/dataGridSql";
|
||||
import { matchesIdentifierSearch } from "@/lib/sql/identifierSearch";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
export type DataGridStructuredFilterRule = {
|
||||
id: string;
|
||||
@@ -80,7 +81,7 @@ export function useDataGridFilterBuilder(options: UseDataGridFilterBuilderOption
|
||||
const activeCount = computed(() => rules.value.filter((rule) => !rule.disabled && rule.columnName && options.isComplete(rule)).length);
|
||||
|
||||
function defaultRule(): DataGridStructuredFilterRule {
|
||||
return { id: options.createId?.() ?? crypto.randomUUID(), columnName: "", mode: "equals", rawValue: "", rawEndValue: "", conjunction: "AND" };
|
||||
return { id: options.createId?.() ?? uuid(), columnName: "", mode: "equals", rawValue: "", rawEndValue: "", conjunction: "AND" };
|
||||
}
|
||||
function ensureRule() {
|
||||
if (!rules.value.length && toValue(options.columns).length) rules.value = [defaultRule()];
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { reactive, computed } from "vue";
|
||||
import * as api from "@/lib/backend/api";
|
||||
import { isTerminalTransferProgress } from "@/lib/backend/transferProgress";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
export type BackgroundTaskKind = "table-export" | "database-export" | "sql-file" | "data-transfer" | "multi-db-execution" | "schema-diff" | "data-compare";
|
||||
export type BackgroundTaskStatus = "Running" | "Writing" | "Cancelling" | "Done" | "Error" | "Cancelled";
|
||||
@@ -387,20 +388,8 @@ export function useExportTracker() {
|
||||
|
||||
const hasActive = computed(() => activeCount.value > 0);
|
||||
|
||||
function generateUUID() {
|
||||
if (typeof crypto !== "undefined" && crypto.randomUUID) {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
let buffer = new Uint8Array(16);
|
||||
crypto.getRandomValues(buffer);
|
||||
buffer[6] = (buffer[6] & 0x0f) | 0x40;
|
||||
return Array.from(buffer, (b) => b.toString(16).padStart(2, "0"))
|
||||
.join("")
|
||||
.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
||||
}
|
||||
|
||||
function addTask(tableName: string, format: string, filePath: string, exportId?: string): ExportTask {
|
||||
const id = exportId ?? generateUUID();
|
||||
const id = exportId ?? uuid();
|
||||
const task = reactive<ExportTask>({
|
||||
exportId: id,
|
||||
kind: "table-export",
|
||||
|
||||
@@ -15,6 +15,7 @@ import { qualifiedTableKey } from "./docsKeys";
|
||||
import type { DocsRoute } from "./docsRoute";
|
||||
import { groupBySchema, groupByTableGroup } from "./docsIndex";
|
||||
import type { AnnotationFile, DocsEdit, GroupAnnotation, SchemaSnapshot } from "./types";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -144,7 +145,7 @@ function home(): void {
|
||||
*/
|
||||
function createGroupFor(tableKey: string): void {
|
||||
const group: GroupAnnotation = {
|
||||
id: crypto.randomUUID(),
|
||||
id: uuid(),
|
||||
// Reuses the picker's own "New group" string rather than adding a locale
|
||||
// key for a placeholder the user renames immediately in GroupEditor.
|
||||
name: props.translate("docs.newGroup"),
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { onScopeDispose } from "vue";
|
||||
import i18n from "@/i18n";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
const blockers = new Set<() => string | undefined>();
|
||||
let barrierDepth = 0;
|
||||
@@ -127,7 +128,7 @@ export async function setupUpdatePreparation(participant: UpdatePreparationParti
|
||||
return {
|
||||
async prepare() {
|
||||
if (held.size) throw new Error(message("preparationBusy", "Update preparation already in progress"));
|
||||
const id = crypto.randomUUID();
|
||||
const id = uuid();
|
||||
const members = (await getAllWebviewWindows()).map((item) => item.label).sort();
|
||||
const expected = new Set(members.filter((item) => item !== label));
|
||||
let timer: ReturnType<typeof setTimeout> | undefined;
|
||||
|
||||
@@ -278,6 +278,7 @@ import { appendDebugLog, isDebugLoggingEnabled } from "@/lib/backend/debugLog";
|
||||
import { collectBrowserSupportInfo } from "@/lib/app/supportInfo";
|
||||
import { normalizeConnectionTestResult } from "@/lib/connection/connectionDatabaseInfo";
|
||||
import type { AnnotationFile, SchemaSnapshot } from "@/docs/types";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
@@ -1412,7 +1413,7 @@ export async function executeMultiWithProgress(
|
||||
executionId?: string;
|
||||
},
|
||||
): Promise<QueryResult[]> {
|
||||
const executionId = options?.executionId ?? crypto.randomUUID();
|
||||
const executionId = options?.executionId ?? uuid();
|
||||
const { executionId: _executionId, ...executeOptions } = options ?? {};
|
||||
const results = await executeMulti(connectionId, database, sql, schema, executionId, executeOptions);
|
||||
const total = results.length;
|
||||
|
||||
@@ -778,6 +778,7 @@ export async function saveMaxRetries(maxRetries: number): Promise<void> {
|
||||
|
||||
export type { OpenTabsStatePayload, PersistedEditorGroup } from "@/lib/app/openTabsPersistence";
|
||||
import type { OpenTabsStatePayload } from "@/lib/app/openTabsPersistence";
|
||||
import { uuid } from "@/lib/common/utils";
|
||||
|
||||
export async function loadEditorSettings(): Promise<unknown | null> {
|
||||
return invoke("load_editor_settings");
|
||||
@@ -1609,7 +1610,7 @@ export async function executeMultiWithProgress(
|
||||
executionId?: string;
|
||||
},
|
||||
): Promise<QueryResult[]> {
|
||||
const executionId = options?.executionId ?? crypto.randomUUID();
|
||||
const executionId = options?.executionId ?? uuid();
|
||||
const { executionId: _executionId, ...invokeOptions } = options ?? {};
|
||||
const unlisten = await listen<ExecuteMultiProgress>("query-batch-progress", (event) => {
|
||||
if (event.payload.executionId === executionId) onProgress(event.payload);
|
||||
|
||||
@@ -19,4 +19,14 @@ describe("uuid", () => {
|
||||
|
||||
expect(uuid()).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
|
||||
});
|
||||
|
||||
it("keeps working without crypto.randomUUID, the state of plain-HTTP deployments", () => {
|
||||
// Issue #9306: the Docker build served over HTTP has crypto.getRandomValues but no
|
||||
// crypto.randomUUID, and every bare crypto.randomUUID() call threw there.
|
||||
const getRandomValues = vi.fn((buffer: Uint8Array) => buffer.fill(0xab));
|
||||
vi.stubGlobal("crypto", { getRandomValues });
|
||||
|
||||
expect(uuid()).toBe("abababab-abab-4bab-abab-abababababab");
|
||||
expect(getRandomValues).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import { readFileSync, readdirSync, statSync } from "node:fs";
|
||||
import { join, relative, sep } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
/**
|
||||
* `crypto.randomUUID` exists only in secure contexts, so every bare call in the
|
||||
* desktop/web bundle crashes under the plain-HTTP Docker deployment (issue #9306).
|
||||
* The guard-compatible forms — `crypto?.randomUUID?.()`, `globalThis.crypto?.randomUUID?.()`
|
||||
* — are allowed, as is the shared helper that owns the fallback.
|
||||
*/
|
||||
const SOURCE_ROOT = fileURLToPath(new URL("../../..", import.meta.url));
|
||||
const HELPER_PATH = join("lib", "common", "utils.ts");
|
||||
const BARE_CALL = /(?<!\?)crypto\s*\.\s*randomUUID\s*\(/;
|
||||
|
||||
function sourceFiles(directory: string): string[] {
|
||||
return readdirSync(directory).flatMap((entry) => {
|
||||
const path = join(directory, entry);
|
||||
if (statSync(path).isDirectory()) return entry === "__tests__" ? [] : sourceFiles(path);
|
||||
return entry.endsWith(".ts") || entry.endsWith(".vue") ? [path] : [];
|
||||
});
|
||||
}
|
||||
|
||||
describe("crypto.randomUUID call sites", () => {
|
||||
it("recognizes direct calls but not guard-compatible ones", () => {
|
||||
expect(BARE_CALL.test("const id = crypto.randomUUID();")).toBe(true);
|
||||
expect(BARE_CALL.test("globalThis.crypto.randomUUID()")).toBe(true);
|
||||
expect(BARE_CALL.test("crypto?.randomUUID?.() || fallback")).toBe(false);
|
||||
expect(BARE_CALL.test("globalThis.crypto?.randomUUID?.() ?? fallback")).toBe(false);
|
||||
});
|
||||
|
||||
it("routes every UUID through the shared helper instead of calling crypto.randomUUID directly", () => {
|
||||
const offenders = sourceFiles(SOURCE_ROOT)
|
||||
.map((path) => ({ path: relative(SOURCE_ROOT, path).split(sep).join("/"), text: readFileSync(path, "utf8") }))
|
||||
.filter(({ path }) => path !== HELPER_PATH.split(sep).join("/"))
|
||||
.filter(({ text }) => BARE_CALL.test(text))
|
||||
.map(({ path }) => path);
|
||||
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
@@ -6,10 +6,27 @@ export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a random UUID (v4).
|
||||
*
|
||||
* `crypto.randomUUID` is exposed only in secure contexts, so the Docker/Web build
|
||||
* served over plain HTTP on a LAN address sees it as `undefined` while
|
||||
* `crypto.getRandomValues` keeps working (issue #9306). Call this helper instead of
|
||||
* `crypto.randomUUID()` directly; the generated value only has to be unique, so the
|
||||
* non-secure fallbacks are acceptable.
|
||||
*/
|
||||
export function uuid(): string {
|
||||
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
|
||||
const bytes = crypto.getRandomValues(new Uint8Array(16));
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0"))
|
||||
.join("")
|
||||
.replace(/^(.{8})(.{4})(.{4})(.{4})(.{12})$/, "$1-$2-$3-$4-$5");
|
||||
}
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
|
||||
const r = (Math.random() * 16) | 0;
|
||||
return (c === "x" ? r : (r & 0x3) | 0x8).toString(16);
|
||||
|
||||
@@ -3527,7 +3527,7 @@ export const useQueryStore = defineStore("query", () => {
|
||||
connectionId,
|
||||
providerId,
|
||||
connectionType: connection.plugin_connection_type,
|
||||
workbenchId: crypto.randomUUID(),
|
||||
workbenchId: uuid(),
|
||||
connection: {
|
||||
id: connection.id,
|
||||
name: connection.name,
|
||||
|
||||
Generated
+1
-1
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
||||
"apps/desktop/src/docs-export/exportPayload.ts": "d2355903843aa8a2e37b83827de9c4df2187d6df92fd514e3ab2fd89390e4b6c",
|
||||
"apps/desktop/src/docs-export/exportTranslate.ts": "2e01bf9ad20fb38f3dfab96769ed87c44a24e5eb8d0aa352f4b398606fb77e3b",
|
||||
"apps/desktop/src/docs-export/main.ts": "08c541f19fa6e368ea42fd096320da4312624f1961cd2b59387aa618288fc397",
|
||||
"apps/desktop/src/docs/DocsApp.vue": "60fa8c3f434efb39622688f8ad65dcad77eedd99bde9ffb9103d711519aa29d3",
|
||||
"apps/desktop/src/docs/DocsApp.vue": "5b7ad3b550a77432563a72e8452643bc044f0bbaad858b3f8a5909c46bf78503",
|
||||
"apps/desktop/src/docs/components/ColumnTable.vue": "a025b78a96eb194d0768afc8ecb7653e5066dcb2af05bbbec925f7b7e5ced1e6",
|
||||
"apps/desktop/src/docs/components/DocsSearch.vue": "163d585dc3400b30d7ff37b4efec7ce646933699131e11e4120979b2d3286c78",
|
||||
"apps/desktop/src/docs/components/DocsSidebar.vue": "cf4a394b9b1ceff18e463c3ba5366921b61033d6ccf530373a9b300b23f0baf8",
|
||||
@@ -37,6 +37,7 @@
|
||||
"apps/desktop/src/i18n/locales/docs/zh-CN.ts": "2092c39277f2a6774405124d1f167bbcbc5f633d8f82bbd2b28ff5e12d78f3f7",
|
||||
"apps/desktop/src/i18n/locales/docs/zh-TW.ts": "691cf502248dbd09358c6c276990945013b26c52fb682bcd56b26f378daea29c",
|
||||
"apps/desktop/src/lib/app/localeOptions.ts": "2c19ecb7c91050d5f1c84c381606087ada11870d3ee95029362f84f34de23239",
|
||||
"apps/desktop/src/lib/common/utils.ts": "76c4b2af1e03edeb8db389d9b2c1b56b72a57a1b5198d4e224671c1445c527da",
|
||||
"apps/desktop/src/lib/diagram/erDiagram.ts": "e28aa2b5fbba001ae78bc0c4218b11f93b0e14ab9f54562f14254386b98bad11",
|
||||
"apps/desktop/src/styles/tokens.css": "b8852ce50865ad98ebc992971f7a06670c5d830ca82efcee956f8d3eb7c2e9a3",
|
||||
"apps/desktop/tsconfig.json": "5ad521941835ad8529e9b81ba79c11650e5f020f0e376b7a9f17e22a9306fa76",
|
||||
|
||||
Reference in New Issue
Block a user