diff options
| author | Adam <[email protected]> | 2025-12-18 15:47:16 -0600 |
|---|---|---|
| committer | Adam <[email protected]> | 2025-12-18 15:47:21 -0600 |
| commit | d57b963141a99c1e49271de2bdde2b08b6546f57 (patch) | |
| tree | 1ec82c7cf8929b9613c804b48d67ac2f20bd562b /packages/desktop/src/utils | |
| parent | 0ebcaff92717ba0cb3ca122064cabc7622a2dd68 (diff) | |
| download | opencode-d57b963141a99c1e49271de2bdde2b08b6546f57.tar.gz opencode-d57b963141a99c1e49271de2bdde2b08b6546f57.zip | |
fix: id
Diffstat (limited to 'packages/desktop/src/utils')
| -rw-r--r-- | packages/desktop/src/utils/id.ts | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/packages/desktop/src/utils/id.ts b/packages/desktop/src/utils/id.ts new file mode 100644 index 000000000..fa27cf4c5 --- /dev/null +++ b/packages/desktop/src/utils/id.ts @@ -0,0 +1,99 @@ +import z from "zod" + +const prefixes = { + session: "ses", + message: "msg", + permission: "per", + user: "usr", + part: "prt", + pty: "pty", +} as const + +const LENGTH = 26 +let lastTimestamp = 0 +let counter = 0 + +type Prefix = keyof typeof prefixes +export namespace Identifier { + export function schema(prefix: Prefix) { + return z.string().startsWith(prefixes[prefix]) + } + + export function ascending(prefix: Prefix, given?: string) { + return generateID(prefix, false, given) + } + + export function descending(prefix: Prefix, given?: string) { + return generateID(prefix, true, given) + } +} + +function generateID(prefix: Prefix, descending: boolean, given?: string): string { + if (!given) { + return create(prefix, descending) + } + + if (!given.startsWith(prefixes[prefix])) { + throw new Error(`ID ${given} does not start with ${prefixes[prefix]}`) + } + + return given +} + +function create(prefix: Prefix, descending: boolean, timestamp?: number): string { + const currentTimestamp = timestamp ?? Date.now() + + if (currentTimestamp !== lastTimestamp) { + lastTimestamp = currentTimestamp + counter = 0 + } + + counter += 1 + + let now = BigInt(currentTimestamp) * BigInt(0x1000) + BigInt(counter) + + if (descending) { + now = ~now + } + + const timeBytes = new Uint8Array(6) + for (let i = 0; i < 6; i += 1) { + timeBytes[i] = Number((now >> BigInt(40 - 8 * i)) & BigInt(0xff)) + } + + return prefixes[prefix] + "_" + bytesToHex(timeBytes) + randomBase62(LENGTH - 12) +} + +function bytesToHex(bytes: Uint8Array): string { + let hex = "" + for (let i = 0; i < bytes.length; i += 1) { + hex += bytes[i].toString(16).padStart(2, "0") + } + return hex +} + +function randomBase62(length: number): string { + const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + const bytes = getRandomBytes(length) + let result = "" + for (let i = 0; i < length; i += 1) { + result += chars[bytes[i] % 62] + } + return result +} + +function getRandomBytes(length: number): Uint8Array { + const bytes = new Uint8Array(length) + const cryptoObj = typeof globalThis !== "undefined" ? globalThis.crypto : undefined + + if (cryptoObj && typeof cryptoObj.getRandomValues === "function") { + cryptoObj.getRandomValues(bytes) + return bytes + } + + for (let i = 0; i < length; i += 1) { + bytes[i] = Math.floor(Math.random() * 256) + } + + return bytes +} |
