fix(web): add secure-context-safe clientTurnId helper
Provide RFC 4122 v4 fallback via getRandomValues for LAN HTTP pages where randomUUID is unavailable. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { createClientTurnId, isUuidV4 } from './createClientTurnId';
|
||||
|
||||
const originalCrypto = globalThis.crypto;
|
||||
|
||||
function restoreCrypto() {
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
configurable: true,
|
||||
value: originalCrypto,
|
||||
});
|
||||
}
|
||||
|
||||
test('createClientTurnId uses randomUUID when available', () => {
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
configurable: true,
|
||||
value: {
|
||||
randomUUID: () => '11111111-1111-4111-8111-111111111111',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
assert.equal(createClientTurnId(), '11111111-1111-4111-8111-111111111111');
|
||||
} finally {
|
||||
restoreCrypto();
|
||||
}
|
||||
});
|
||||
|
||||
test('createClientTurnId falls back to getRandomValues UUID v4', () => {
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
configurable: true,
|
||||
value: {
|
||||
getRandomValues: (bytes: Uint8Array) => {
|
||||
for (let i = 0; i < bytes.length; i += 1) {
|
||||
bytes[i] = i;
|
||||
}
|
||||
return bytes;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const id = createClientTurnId();
|
||||
assert.equal(isUuidV4(id), true);
|
||||
assert.match(id, /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
|
||||
} finally {
|
||||
restoreCrypto();
|
||||
}
|
||||
});
|
||||
|
||||
test('createClientTurnId throws when no secure random API exists', () => {
|
||||
Object.defineProperty(globalThis, 'crypto', {
|
||||
configurable: true,
|
||||
value: {},
|
||||
});
|
||||
|
||||
try {
|
||||
assert.throws(
|
||||
() => createClientTurnId(),
|
||||
/当前浏览器不支持安全随机标识/
|
||||
);
|
||||
} finally {
|
||||
restoreCrypto();
|
||||
}
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
const UUID_V4_REGEX =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||
|
||||
function formatUuidV4(bytes: Uint8Array): string {
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
|
||||
const hex = Array.from(bytes, (value) => value.toString(16).padStart(2, '0'));
|
||||
|
||||
return [
|
||||
hex.slice(0, 4).join(''),
|
||||
hex.slice(4, 6).join(''),
|
||||
hex.slice(6, 8).join(''),
|
||||
hex.slice(8, 10).join(''),
|
||||
hex.slice(10, 16).join(''),
|
||||
].join('-');
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成客户端 turn 标识,兼容非 Secure Context(如局域网 HTTP)。
|
||||
* 优先 randomUUID;否则用 getRandomValues 生成 RFC 4122 v4 UUID。
|
||||
*/
|
||||
export function createClientTurnId(): string {
|
||||
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
||||
return globalThis.crypto.randomUUID();
|
||||
}
|
||||
|
||||
if (typeof globalThis.crypto?.getRandomValues === 'function') {
|
||||
const bytes = new Uint8Array(16);
|
||||
globalThis.crypto.getRandomValues(bytes);
|
||||
return formatUuidV4(bytes);
|
||||
}
|
||||
|
||||
throw new Error('当前浏览器不支持安全随机标识,请升级浏览器或通过 HTTPS 访问');
|
||||
}
|
||||
|
||||
export function isUuidV4(value: string): boolean {
|
||||
return UUID_V4_REGEX.test(value);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export type SendMessageResult = {
|
||||
accepted: boolean;
|
||||
error?: string;
|
||||
};
|
||||
Reference in New Issue
Block a user