summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/utils
diff options
context:
space:
mode:
authorAdam <[email protected]>2026-02-11 19:05:15 -0600
committerGitHub <[email protected]>2026-02-12 01:05:15 +0000
commit81ca2df6ad57085b895caafc386e4ac4ab9098a6 (patch)
tree3a78f5125aff9ebf84e98275a72f5fdd13d930ff /packages/app/src/utils
parentaea68c386a4f64cf718c3eeee9dffec8409ee6b0 (diff)
downloadopencode-81ca2df6ad57085b895caafc386e4ac4ab9098a6.tar.gz
opencode-81ca2df6ad57085b895caafc386e4ac4ab9098a6.zip
fix(app): guard randomUUID in insecure browser contexts (#13237)
Co-authored-by: Selim <[email protected]>
Diffstat (limited to 'packages/app/src/utils')
-rw-r--r--packages/app/src/utils/perf.ts8
-rw-r--r--packages/app/src/utils/uuid.test.ts78
-rw-r--r--packages/app/src/utils/uuid.ts12
3 files changed, 94 insertions, 4 deletions
diff --git a/packages/app/src/utils/perf.ts b/packages/app/src/utils/perf.ts
index 0ecbc33ff..105d02a82 100644
--- a/packages/app/src/utils/perf.ts
+++ b/packages/app/src/utils/perf.ts
@@ -1,3 +1,5 @@
+import { uuid } from "@/utils/uuid"
+
type Nav = {
id: string
dir?: string
@@ -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>()
@@ -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
@@ -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" })
diff --git a/packages/app/src/utils/uuid.test.ts b/packages/app/src/utils/uuid.test.ts
new file mode 100644
index 000000000..e6b4e2824
--- /dev/null
+++ b/packages/app/src/utils/uuid.test.ts
@@ -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")
+ })
+})
diff --git a/packages/app/src/utils/uuid.ts b/packages/app/src/utils/uuid.ts
new file mode 100644
index 000000000..7b964068c
--- /dev/null
+++ b/packages/app/src/utils/uuid.ts
@@ -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()
+ }
+}