fix: replace crypto.randomUUID with polyfill for HTTP contexts

crypto.randomUUID() is only available in secure contexts (HTTPS/localhost).
Add uuid() utility with fallback for HTTP deployments.
This commit is contained in:
t8y2
2026-05-05 23:51:07 +08:00
parent 4e5a7cb40a
commit 0aa562f6ee
16 changed files with 48 additions and 23 deletions
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, nextTick, ref, watch } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
@@ -400,7 +401,7 @@ async function testConnection() {
isTesting.value = true;
testResult.value = null;
try {
const config: ConnectionConfig = { ...form.value, id: editingId.value || crypto.randomUUID() };
const config: ConnectionConfig = { ...form.value, id: editingId.value || uuid() };
const msg = await api.testConnection(config);
if (runId !== testRunId) return;
testResult.value = { ok: true, message: msg };
@@ -465,7 +466,7 @@ async function save() {
await store.updateConnection(updated);
store.stopEditing();
} else {
const config: ConnectionConfig = { ...form.value, id: crypto.randomUUID() };
const config: ConnectionConfig = { ...form.value, id: uuid() };
await store.addConnection(config);
open.value = false;
await nextTick();
+3 -2
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, nextTick, onMounted, ref } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import {
ArrowUp,
@@ -276,7 +277,7 @@ async function send() {
isGenerating.value = true;
messages.value.push({ role: "assistant", content: "" });
const assistantIdx = messages.value.length - 1;
const sessionId = crypto.randomUUID();
const sessionId = uuid();
currentSessionId.value = sessionId;
try {
const context = await buildAiContext(props.tab, props.connection);
@@ -344,7 +345,7 @@ function clearMessages() {
async function persistConversation() {
if (!messages.value.length || !props.connection) return;
if (!conversationId.value) conversationId.value = crypto.randomUUID();
if (!conversationId.value) conversationId.value = uuid();
const first = messages.value.find((m) => m.role === "user");
await saveAiConversation({
id: conversationId.value,
+2 -1
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import { Dialog, DialogHeader, DialogTitle, DialogFooter, DialogScrollContent } from "@/components/ui/dialog";
@@ -165,7 +166,7 @@ async function startImport() {
running.value = true;
cancelling.value = false;
errorMessage.value = "";
importId.value = crypto.randomUUID();
importId.value = uuid();
progress.value = {
importId: importId.value,
status: "running",
+2 -1
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { Plus, Trash2 } from "lucide-vue-next";
import { Button } from "@/components/ui/button";
@@ -67,7 +68,7 @@ function fieldValueTone(value: string): string {
function createBlankNode(keyName: string, readonlyKey: boolean): EditNode {
return {
key: crypto.randomUUID(),
key: uuid(),
keyName,
kind: "value",
valueText: "",
+4 -3
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref, onMounted } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { RefreshCw, Trash2, Plus, Save, ChevronLeft, ChevronRight } from "lucide-vue-next";
import { Button } from "@/components/ui/button";
@@ -130,7 +131,7 @@ function cancelEdit() {
function createEditNode(keyName: string, value: unknown, readonlyKey: boolean, readonlyValue: boolean): EditNode {
if (Array.isArray(value)) {
return {
key: crypto.randomUUID(),
key: uuid(),
keyName,
kind: "array",
valueText: "",
@@ -142,7 +143,7 @@ function createEditNode(keyName: string, value: unknown, readonlyKey: boolean, r
if (value && typeof value === "object") {
return {
key: crypto.randomUUID(),
key: uuid(),
keyName,
kind: "object",
valueText: "",
@@ -155,7 +156,7 @@ function createEditNode(keyName: string, value: unknown, readonlyKey: boolean, r
}
return {
key: crypto.randomUUID(),
key: uuid(),
keyName,
kind: "value",
valueText: formatForEdit(value),
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { AlertCircle, Loader2, Search, Square, Table2 } from "lucide-vue-next";
import { Badge } from "@/components/ui/badge";
@@ -129,7 +130,7 @@ function filterSearchSchemas(schemas: string[]): string[] {
}
function makeExecutionId(): string {
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return `database-search-${crypto.randomUUID()}`;
if (typeof crypto !== "undefined" && "randomUUID" in crypto) return `database-search-${uuid()}`;
return `database-search-${Date.now()}-${Math.random().toString(16).slice(2)}`;
}
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { isTauriRuntime } from "@/lib/tauriRuntime";
import { Dialog, DialogFooter, DialogHeader, DialogScrollContent, DialogTitle } from "@/components/ui/dialog";
@@ -234,7 +235,7 @@ async function selectFile() {
async function startExecution() {
if (!canStart.value || !preview.value) return;
const id = crypto.randomUUID();
const id = uuid();
executionId.value = id;
running.value = true;
cancelling.value = false;
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { Dialog, DialogFooter, DialogHeader, DialogScrollContent, DialogTitle } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
@@ -167,7 +168,7 @@ async function loadStructure() {
function addColumn() {
columns.value.push({
id: `new:${crypto.randomUUID()}`,
id: `new:${uuid()}`,
name: "",
dataType: "varchar(255)",
isNullable: true,
@@ -189,7 +190,7 @@ function toggleDropColumn(column: EditableStructureColumn) {
function addIndex() {
indexes.value.push({
id: `new:${crypto.randomUUID()}`,
id: `new:${uuid()}`,
name: "",
columns: [],
isUnique: false,
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { uuid } from "@/lib/utils";
import { useI18n } from "vue-i18n";
import { Dialog, DialogHeader, DialogTitle, DialogFooter, DialogScrollContent } from "@/components/ui/dialog";
import { Button } from "@/components/ui/button";
@@ -204,7 +205,7 @@ async function startTransfer() {
overallCancelled.value = false;
transferProgress.value.clear();
transferId.value = crypto.randomUUID();
transferId.value = uuid();
// Auto-detect target schema
const targetConfig = store.getConfig(targetConnectionId.value);
+3 -2
View File
@@ -1,4 +1,5 @@
import { useI18n } from "vue-i18n";
import { uuid } from "@/lib/utils";
import { useConnectionStore } from "@/stores/connectionStore";
import { useQueryStore } from "@/stores/queryStore";
import { useToast } from "@/composables/useToast";
@@ -41,7 +42,7 @@ export function useFileDrop() {
const dataQuery = getDataFileQuery(path);
if (dataQuery) {
const config: ConnectionConfig = {
id: crypto.randomUUID(),
id: uuid(),
name: `[Preview] ${name}`,
db_type: "duckdb",
driver_profile: "duckdb",
@@ -64,7 +65,7 @@ export function useFileDrop() {
const dbType = getDbType(path);
if (!dbType) continue;
const config: ConnectionConfig = {
id: crypto.randomUUID(),
id: uuid(),
name,
db_type: dbType,
driver_profile: dbType,
+2 -1
View File
@@ -1,4 +1,5 @@
import type { AiConfig } from "@/stores/settingsStore";
import { uuid } from "@/lib/utils";
import type {
ColumnInfo,
ConnectionConfig,
@@ -111,7 +112,7 @@ export async function runAiStream(
const messages: api.AiMessage[] = [...(history || []), { role: "user", content: userPrompt }];
const sid = sessionId || crypto.randomUUID();
const sid = sessionId || uuid();
const params = actionParams(input.action);
await api.aiStream(
+2 -1
View File
@@ -1,4 +1,5 @@
import type { ConnectionConfig, ConnectionGroup, SidebarLayout, SidebarOrderEntry, TreeNode } from "@/types/database";
import { uuid } from "@/lib/utils";
export function emptyLayout(): SidebarLayout {
return { groups: [], order: [] };
@@ -234,7 +235,7 @@ function reorderConnection(
}
export function createGroup(layout: SidebarLayout, name: string): { layout: SidebarLayout; groupId: string } {
const groupId = crypto.randomUUID();
const groupId = uuid();
const group: ConnectionGroup = { id: groupId, name, collapsed: false };
return {
groupId,
+10
View File
@@ -5,3 +5,13 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export function uuid(): string {
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
}
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);
});
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { defineStore } from "pinia";
import { uuid } from "@/lib/utils";
import { ref, watch } from "vue";
import type { ColumnInfo, ConnectionConfig, SidebarLayout, TreeNode } from "@/types/database";
import { orderPinnedFirst } from "@/lib/pinnedItems";
@@ -866,7 +867,7 @@ export const useConnectionStore = defineStore("connection", () => {
(c) => c.name === config.name && c.host === config.host && c.port === config.port,
);
if (!duplicate) {
config.id = crypto.randomUUID();
config.id = uuid();
const normalized = normalizeConnection(config);
await addConnection(normalized);
count++;
+2 -1
View File
@@ -1,4 +1,5 @@
import { defineStore } from "pinia";
import { uuid } from "@/lib/utils";
import { ref } from "vue";
import * as api from "@/lib/api";
import type { HistoryEntry } from "@/lib/api";
@@ -19,7 +20,7 @@ export const useHistoryStore = defineStore("history", () => {
async function add(entry: Omit<HistoryEntry, "id" | "executed_at">) {
const full: HistoryEntry = {
...entry,
id: crypto.randomUUID(),
id: uuid(),
executed_at: new Date().toISOString(),
};
await api.saveHistory(full);
+4 -3
View File
@@ -1,4 +1,5 @@
import { defineStore } from "pinia";
import { uuid } from "@/lib/utils";
import { ref, watch } from "vue";
import type { DatabaseType, QueryTab } from "@/types/database";
import { orderPinnedFirst } from "@/lib/pinnedItems";
@@ -87,7 +88,7 @@ export const useQueryStore = defineStore("query", () => {
}
}
const id = crypto.randomUUID();
const id = uuid();
const tab: QueryTab = {
id,
title: title || `Query ${tabs.value.length + 1}`,
@@ -222,7 +223,7 @@ export const useQueryStore = defineStore("query", () => {
const tab = tabs.value.find((t) => t.id === id);
if (!tab || !sql.trim()) return;
const executionId = crypto.randomUUID();
const executionId = uuid();
tab.isExecuting = true;
tab.isCancelling = false;
tab.executionId = executionId;
@@ -270,7 +271,7 @@ export const useQueryStore = defineStore("query", () => {
return built;
}
const executionId = crypto.randomUUID();
const executionId = uuid();
tab.isExplaining = true;
tab.explainExecutionId = executionId;
tab.explainError = undefined;