Update project structure and dependencies; add architecture documentation and improve build scripts. Introduce new versioning and permissions for enhanced functionality.

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent c8380de521
commit 0371a8b802
113 changed files with 15944 additions and 6556 deletions
+17
View File
@@ -0,0 +1,17 @@
export class ExtensionError extends Error {
constructor(
public readonly code: string,
message: string,
) {
super(message);
this.name = 'ExtensionError';
}
}
export function errorCode(error: unknown): string {
return error instanceof ExtensionError ? error.code : 'request_failed';
}
export function isDeniedErrorCode(code: string): boolean {
return ['permission_denied', 'grant_expired', 'target_denied', 'origin_changed', 'stale_document'].includes(code);
}
+26
View File
@@ -0,0 +1,26 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { createOpaqueId } from './id';
describe('createOpaqueId', () => {
afterEach(() => vi.unstubAllGlobals());
it('uses getRandomValues without requiring randomUUID', () => {
vi.stubGlobal('crypto', {
getRandomValues<T extends ArrayBufferView>(value: T): T {
new Uint8Array(value.buffer, value.byteOffset, value.byteLength).fill(0xab);
return value;
},
});
expect(createOpaqueId('request')).toBe(`request-${'ab'.repeat(16)}`);
});
it('remains unique when the crypto implementation is unavailable', () => {
vi.stubGlobal('crypto', { getRandomValues: () => { throw new Error('unavailable'); } });
const first = createOpaqueId('request');
const second = createOpaqueId('request');
expect(first).not.toBe(second);
expect(first).toMatch(/^request-[a-z0-9]+-[a-z0-9]+$/);
});
});
+20
View File
@@ -0,0 +1,20 @@
let fallbackSequence = 0;
function randomHex(byteLength: number): string | undefined {
try {
const cryptoApi = globalThis.crypto;
if (!cryptoApi || typeof cryptoApi.getRandomValues !== 'function') return undefined;
const bytes = cryptoApi.getRandomValues(new Uint8Array(byteLength));
return Array.from(bytes, (value) => value.toString(16).padStart(2, '0')).join('');
} catch {
return undefined;
}
}
export function createOpaqueId(prefix: string): string {
const entropy = randomHex(16);
if (entropy) return `${prefix}-${entropy}`;
fallbackSequence = (fallbackSequence + 1) % Number.MAX_SAFE_INTEGER;
return `${prefix}-${Date.now().toString(36)}-${fallbackSequence.toString(36)}`;
}