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
3 changes: 2 additions & 1 deletion packages/app/src/components/prompt-input/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { onCleanup, onMount } from "solid-js"
import { showToast } from "@opencode-ai/ui/toast"
import { usePrompt, type ContentPart, type ImageAttachmentPart } from "@/context/prompt"
import { useLanguage } from "@/context/language"
import { uuid } from "@/utils/uuid"
import { getCursorPosition } from "./editor-dom"

export const ACCEPTED_IMAGE_TYPES = ["image/png", "image/jpeg", "image/gif", "image/webp"]
Expand Down Expand Up @@ -31,7 +32,7 @@ export function createPromptAttachments(input: PromptAttachmentsInput) {
const dataUrl = reader.result as string
const attachment: ImageAttachmentPart = {
type: "image",
id: crypto.randomUUID?.() ?? Math.random().toString(16).slice(2),
id: uuid(),
filename: file.name,
mime: file.type,
dataUrl,
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/context/comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { createSimpleContext } from "@opencode-ai/ui/context"
import { useParams } from "@solidjs/router"
import { Persist, persisted } from "@/utils/persist"
import { createScopedCache } from "@/utils/scoped-cache"
import { uuid } from "@/utils/uuid"
import type { SelectedLineRange } from "@/context/file"

export type LineComment = {
Expand Down Expand Up @@ -53,7 +54,7 @@ function createCommentSessionState(store: Store<CommentStore>, setStore: SetStor

const add = (input: Omit<LineComment, "id" | "time">) => {
const next: LineComment = {
id: crypto.randomUUID?.() ?? Math.random().toString(16).slice(2),
id: uuid(),
time: Date.now(),
...input,
}
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/utils/perf.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { uuid } from "@/utils/uuid"

type Nav = {
id: string
dir?: string
Expand All @@ -16,8 +18,6 @@ const key = (dir: string | undefined, to: string) => `${dir ?? ""}:${to}`

const now = () => performance.now()

const uid = () => crypto.randomUUID?.() ?? Math.random().toString(16).slice(2)

const navs = new Map<string, Nav>()
const pending = new Map<string, string>()
const active = new Map<string, string>()
Expand Down Expand Up @@ -94,7 +94,7 @@ function ensure(id: string, data: Omit<Nav, "marks" | "logged" | "timer">) {
export function navStart(input: { dir?: string; from?: string; to: string; trigger?: string }) {
if (!dev) return

const id = uid()
const id = uuid()
const start = now()
const nav = ensure(id, { ...input, id, start })
nav.marks["navigate:start"] = start
Expand All @@ -109,7 +109,7 @@ export function navParams(input: { dir?: string; from?: string; to: string }) {
const k = key(input.dir, input.to)
const pendingId = pending.get(k)
if (pendingId) pending.delete(k)
const id = pendingId ?? uid()
const id = pendingId ?? uuid()

const start = now()
const nav = ensure(id, { ...input, id, start, trigger: pendingId ? "key" : "route" })
Expand Down
78 changes: 78 additions & 0 deletions packages/app/src/utils/uuid.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { afterEach, describe, expect, test } from "bun:test"
import { uuid } from "./uuid"

const cryptoDescriptor = Object.getOwnPropertyDescriptor(globalThis, "crypto")
const secureDescriptor = Object.getOwnPropertyDescriptor(globalThis, "isSecureContext")
const randomDescriptor = Object.getOwnPropertyDescriptor(Math, "random")

const setCrypto = (value: Partial<Crypto>) => {
Object.defineProperty(globalThis, "crypto", {
configurable: true,
value: value as Crypto,
})
}

const setSecure = (value: boolean) => {
Object.defineProperty(globalThis, "isSecureContext", {
configurable: true,
value,
})
}

const setRandom = (value: () => number) => {
Object.defineProperty(Math, "random", {
configurable: true,
value,
})
}

afterEach(() => {
if (cryptoDescriptor) {
Object.defineProperty(globalThis, "crypto", cryptoDescriptor)
}

if (secureDescriptor) {
Object.defineProperty(globalThis, "isSecureContext", secureDescriptor)
}

if (!secureDescriptor) {
delete (globalThis as { isSecureContext?: boolean }).isSecureContext
}

if (randomDescriptor) {
Object.defineProperty(Math, "random", randomDescriptor)
}
})

describe("uuid", () => {
test("uses randomUUID in secure contexts", () => {
setCrypto({ randomUUID: () => "00000000-0000-0000-0000-000000000000" })
setSecure(true)
expect(uuid()).toBe("00000000-0000-0000-0000-000000000000")
})

test("falls back in insecure contexts", () => {
setCrypto({ randomUUID: () => "00000000-0000-0000-0000-000000000000" })
setSecure(false)
setRandom(() => 0.5)
expect(uuid()).toBe("8")
})

test("falls back when randomUUID throws", () => {
setCrypto({
randomUUID: () => {
throw new DOMException("Failed", "OperationError")
},
})
setSecure(true)
setRandom(() => 0.5)
expect(uuid()).toBe("8")
})

test("falls back when randomUUID is unavailable", () => {
setCrypto({})
setSecure(true)
setRandom(() => 0.5)
expect(uuid()).toBe("8")
})
})
12 changes: 12 additions & 0 deletions packages/app/src/utils/uuid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const fallback = () => Math.random().toString(16).slice(2)

export function uuid() {
const c = globalThis.crypto
if (!c || typeof c.randomUUID !== "function") return fallback()
if (typeof globalThis.isSecureContext === "boolean" && !globalThis.isSecureContext) return fallback()
try {
return c.randomUUID()
} catch {
return fallback()
}
}
Loading