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