feat: add cross-environment crypto adapter
- Create getCryptoRandomValues() utility - Support both window.crypto (browser/Electron) and crypto.webcrypto (Node.js) - Add comprehensive test coverage for adapter functionality
This commit is contained in:
36
src/utils/crypto-adapter.ts
Normal file
36
src/utils/crypto-adapter.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Cross-environment crypto adapter
|
||||
* Provides unified access to cryptographically secure random number generation
|
||||
* Works in both browser/Electron (window.crypto) and Node.js (crypto.webcrypto)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Gets the appropriate Crypto interface for the current environment
|
||||
* @returns Crypto interface with getRandomValues method
|
||||
* @throws Error if no crypto API is available
|
||||
*/
|
||||
function getCrypto(): Crypto {
|
||||
// Browser/Electron environment
|
||||
if (typeof window !== 'undefined' && window.crypto) {
|
||||
return window.crypto;
|
||||
}
|
||||
|
||||
// Node.js environment (15+) - uses Web Crypto API standard
|
||||
if (typeof global !== 'undefined') {
|
||||
const nodeCrypto = require('crypto');
|
||||
if (nodeCrypto.webcrypto) {
|
||||
return nodeCrypto.webcrypto;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('No Web Crypto API available in this environment');
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills a typed array with cryptographically secure random values
|
||||
* @param array TypedArray to fill with random values
|
||||
* @returns The same array filled with random values
|
||||
*/
|
||||
export function getCryptoRandomValues<T extends ArrayBufferView>(array: T): T {
|
||||
return getCrypto().getRandomValues(array);
|
||||
}
|
||||
56
tests/crypto-adapter.test.ts
Normal file
56
tests/crypto-adapter.test.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { getCryptoRandomValues } from '../src/utils/crypto-adapter';
|
||||
|
||||
describe('crypto-adapter', () => {
|
||||
describe('getCryptoRandomValues', () => {
|
||||
it('should fill Uint8Array with random values', () => {
|
||||
const array = new Uint8Array(32);
|
||||
const result = getCryptoRandomValues(array);
|
||||
|
||||
expect(result).toBe(array);
|
||||
expect(result.length).toBe(32);
|
||||
// Verify not all zeros (extremely unlikely with true random)
|
||||
const hasNonZero = Array.from(result).some(val => val !== 0);
|
||||
expect(hasNonZero).toBe(true);
|
||||
});
|
||||
|
||||
it('should produce different values on subsequent calls', () => {
|
||||
const array1 = new Uint8Array(32);
|
||||
const array2 = new Uint8Array(32);
|
||||
|
||||
getCryptoRandomValues(array1);
|
||||
getCryptoRandomValues(array2);
|
||||
|
||||
// Arrays should be different (extremely unlikely to be identical)
|
||||
const identical = Array.from(array1).every((val, idx) => val === array2[idx]);
|
||||
expect(identical).toBe(false);
|
||||
});
|
||||
|
||||
it('should preserve array type', () => {
|
||||
const uint8 = new Uint8Array(16);
|
||||
const uint16 = new Uint16Array(8);
|
||||
const uint32 = new Uint32Array(4);
|
||||
|
||||
const result8 = getCryptoRandomValues(uint8);
|
||||
const result16 = getCryptoRandomValues(uint16);
|
||||
const result32 = getCryptoRandomValues(uint32);
|
||||
|
||||
expect(result8).toBeInstanceOf(Uint8Array);
|
||||
expect(result16).toBeInstanceOf(Uint16Array);
|
||||
expect(result32).toBeInstanceOf(Uint32Array);
|
||||
});
|
||||
|
||||
it('should work with different array lengths', () => {
|
||||
const small = new Uint8Array(8);
|
||||
const medium = new Uint8Array(32);
|
||||
const large = new Uint8Array(128);
|
||||
|
||||
getCryptoRandomValues(small);
|
||||
getCryptoRandomValues(medium);
|
||||
getCryptoRandomValues(large);
|
||||
|
||||
expect(small.every(val => val >= 0 && val <= 255)).toBe(true);
|
||||
expect(medium.every(val => val >= 0 && val <= 255)).toBe(true);
|
||||
expect(large.every(val => val >= 0 && val <= 255)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user