1import { getRandomValues as expoCryptoGetRandomValues } from 'expo-crypto'; 2 3const MAX_RANDOM_BYTES = 65536; 4 5type IntegerArray = 6 | Int8Array 7 | Uint8Array 8 | Int16Array 9 | Uint16Array 10 | Int32Array 11 | Uint32Array 12 | Uint8ClampedArray; 13 14/** 15 * An implementation of Crypto.getRandomValues that uses expo-random's secure random generator if 16 * available and falls back to Math.random (cryptographically insecure) when synchronous bridged 17 * methods are unavailable. 18 * 19 * See https://www.w3.org/TR/WebCryptoAPI/#Crypto-method-getRandomValues 20 */ 21export default function getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray { 22 if (arguments.length < 1) { 23 throw new TypeError( 24 `An ArrayBuffer view must be specified as the destination for the random values` 25 ); 26 } 27 28 if ( 29 !(values instanceof Int8Array) && 30 !(values instanceof Uint8Array) && 31 !(values instanceof Int16Array) && 32 !(values instanceof Uint16Array) && 33 !(values instanceof Int32Array) && 34 !(values instanceof Uint32Array) && 35 !(values instanceof Uint8ClampedArray) 36 ) { 37 throw new TypeError(`The provided ArrayBuffer view is not an integer-typed array`); 38 } 39 40 if (values.byteLength > MAX_RANDOM_BYTES) { 41 throw new QuotaExceededError( 42 `The ArrayBuffer view's byte length (${values.byteLength}) exceeds the number of bytes of entropy available via this API (${MAX_RANDOM_BYTES})` 43 ); 44 } 45 46 try { 47 // NOTE: Consider implementing `fillRandomBytes` to populate the given TypedArray directly 48 expoCryptoGetRandomValues(values); 49 } catch { 50 // TODO: rethrow the error if it's not due to a lack of synchronous methods 51 console.warn(`Random.getRandomBytes is not supported; falling back to insecure Math.random`); 52 return getRandomValuesInsecure(values); 53 } 54 55 return values; 56} 57 58export function getRandomValuesInsecure<TArray extends IntegerArray>(values: TArray): TArray { 59 // Write random bytes to the given TypedArray's underlying ArrayBuffer 60 const byteView = new Uint8Array(values.buffer, values.byteOffset, values.byteLength); 61 for (let i = 0; i < byteView.length; i++) { 62 // The range of Math.random() is [0, 1) and the ToUint8 abstract operation rounds down 63 byteView[i] = Math.random() * 256; 64 } 65 return values; 66} 67 68class QuotaExceededError extends Error { 69 name = 'QuotaExceededError'; 70 code = 22; // QUOTA_EXCEEDED_ERR 71} 72