mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-15 21:56:18 -08:00
refactor(deps): drop uuid for a shared crypto-based id helper (#1266)
Supersedes #1252 and #872. uuid 14 is ESM-only, apps/web/jest.config.ts only kept v9 working by mapping `^uuid$` at a `wrapper.mjs` that v14 no longer ships, and the specifier also has to be synced in libs/shared/m3u-utils/package.json or @nx/dependency-checks fails lint. All four call sites only used `v4()`, so the dependency goes away instead. `createRandomId()` prefers `crypto.randomUUID()` and falls back to building the same v4 shape from `crypto.getRandomValues()` — that fallback is load-bearing, because randomUUID is only exposed in secure contexts and the self-hosted PWA is regularly served over plain http on a LAN address. getRandomValues stays available there, and it is what uuid's own v4 used. `@types/uuid` goes too; it only existed for the untyped v9 package.
This commit is contained in:
1 parent
23512411a0
commit
1ab82b04a1
11 files changed
+143
-39
No files matched your search
@@ -30,6 +30,7 @@ export * from './lib/playlist.interface';
|
||||
export * from './lib/portal-activity-item.interface';
|
||||
export * from './lib/portal-debug.interface';
|
||||
export * from './lib/portal-playback.interface';
|
||||
export * from './lib/random-id.util';
|
||||
export * from './lib/security-policy-error.utils';
|
||||
export * from './lib/settings.interface';
|
||||
export * from './lib/stalker-portal-actions.enum';
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
import { createRandomId } from './random-id.util';
|
||||
|
||||
const UUID_V4 =
|
||||
/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
|
||||
|
||||
describe('createRandomId', () => {
|
||||
const nativeRandomUUID = crypto.randomUUID;
|
||||
|
||||
afterEach(() => {
|
||||
Object.defineProperty(crypto, 'randomUUID', {
|
||||
configurable: true,
|
||||
value: nativeRandomUUID,
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a v4 uuid', () => {
|
||||
expect(createRandomId()).toMatch(UUID_V4);
|
||||
});
|
||||
|
||||
it('returns unique ids', () => {
|
||||
const ids = new Set(
|
||||
Array.from({ length: 500 }, () => createRandomId())
|
||||
);
|
||||
|
||||
expect(ids.size).toBe(500);
|
||||
});
|
||||
|
||||
it('uses the native randomUUID when the context exposes it', () => {
|
||||
const randomUUID = jest
|
||||
.fn()
|
||||
.mockReturnValue('11111111-1111-4111-8111-111111111111');
|
||||
Object.defineProperty(crypto, 'randomUUID', {
|
||||
configurable: true,
|
||||
value: randomUUID,
|
||||
});
|
||||
|
||||
expect(createRandomId()).toBe('11111111-1111-4111-8111-111111111111');
|
||||
expect(randomUUID).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
describe('without randomUUID (insecure context)', () => {
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(crypto, 'randomUUID', {
|
||||
configurable: true,
|
||||
value: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it('still returns a v4 uuid from getRandomValues', () => {
|
||||
expect(createRandomId()).toMatch(UUID_V4);
|
||||
});
|
||||
|
||||
it('returns unique ids', () => {
|
||||
const ids = new Set(
|
||||
Array.from({ length: 500 }, () => createRandomId())
|
||||
);
|
||||
|
||||
expect(ids.size).toBe(500);
|
||||
});
|
||||
|
||||
it('sets the version and variant bits on all-zero randomness', () => {
|
||||
jest.spyOn(crypto, 'getRandomValues').mockImplementation(
|
||||
(buffer) => buffer
|
||||
);
|
||||
|
||||
expect(createRandomId()).toBe(
|
||||
'00000000-0000-4000-8000-000000000000'
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps the remaining bits from getRandomValues', () => {
|
||||
jest.spyOn(crypto, 'getRandomValues').mockImplementation(
|
||||
(buffer) => {
|
||||
(buffer as Uint8Array).fill(0xff);
|
||||
return buffer;
|
||||
}
|
||||
);
|
||||
|
||||
expect(createRandomId()).toBe(
|
||||
'ffffffff-ffff-4fff-bfff-ffffffffffff'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Random RFC 4122 v4 identifier used for playlist `_id` values, shared by the
|
||||
* renderer, the Electron backend and the web backend. Pure function — no
|
||||
* Angular/Node dependencies.
|
||||
*
|
||||
* `crypto.randomUUID()` is only exposed in secure contexts, and the
|
||||
* self-hosted PWA is regularly served over plain http on a LAN address. So the
|
||||
* fallback builds the same v4 shape from `crypto.getRandomValues()`, which
|
||||
* insecure contexts do expose. This mirrors what `uuid`'s `v4()` did before it
|
||||
* was dropped as a dependency.
|
||||
*/
|
||||
|
||||
const HEX_BY_BYTE = Array.from({ length: 256 }, (_unused, byte) =>
|
||||
byte.toString(16).padStart(2, '0')
|
||||
);
|
||||
|
||||
function randomUuidFromBytes(): string {
|
||||
const bytes = new Uint8Array(16);
|
||||
crypto.getRandomValues(bytes);
|
||||
|
||||
// Version 4 and the RFC 4122 variant bits.
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
|
||||
const hex = Array.from(bytes, (byte) => HEX_BY_BYTE[byte]);
|
||||
|
||||
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('-');
|
||||
}
|
||||
|
||||
/** Returns a lowercase RFC 4122 v4 UUID. */
|
||||
export function createRandomId(): string {
|
||||
if (typeof crypto.randomUUID === 'function') {
|
||||
return crypto.randomUUID();
|
||||
}
|
||||
|
||||
return randomUuidFromBytes();
|
||||
}
|
||||
Reference in new issue
Block a user