@oxyhq/core 12.5.0 → 12.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,14 +4,26 @@
4
4
  * Ensures Buffer and crypto.getRandomValues are available
5
5
  * across all platforms (Node.js, Browser, React Native).
6
6
  *
7
- * - Browser/Node.js: Uses native crypto
8
- * - React Native: Uses expo-crypto (statically imported via the
9
- * per-platform `platform/crypto` module in `@oxyhq/protocol` — see that
10
- * file's doc-comment for how platform routing works).
7
+ * Guard order when installing a `getRandomValues` shim (see bottom of file and
8
+ * {@link cryptoPolyfill}):
9
+ *
10
+ * 1. A REAL `globalThis.crypto.getRandomValues` used as-is (browser, Node
11
+ * >= 20, modern Hermes). The shim below is only installed when the host is
12
+ * missing it, so this branch is the install-time gate.
13
+ * 2. Node — backed by the built-in `node:crypto` module (`webcrypto`, else
14
+ * `randomFillSync`). This is what a Node runtime WITHOUT a global WebCrypto
15
+ * (Node 18 script entrypoints, some embedded hosts) falls back to.
16
+ * 3. React Native — `expo-crypto.getRandomBytes` (statically imported via the
17
+ * per-platform `platform/crypto` module in `@oxyhq/protocol`).
18
+ *
19
+ * Historically step (2) delegated to `@oxyhq/protocol`'s RN-only
20
+ * `getRandomBytesRN`, which THROWS on Node — so any Node host lacking a global
21
+ * WebCrypto crashed here instead of getting randomness. It is now a proper
22
+ * Node-backed implementation.
11
23
  */
12
24
  import _cjs_buffer from 'buffer';
13
25
  const { Buffer } = _cjs_buffer;
14
- import { getRandomBytesRN } from '@oxyhq/protocol';
26
+ import { getRandomBytesRN, isNodeJS } from '@oxyhq/protocol';
15
27
  const getGlobalObject = () => {
16
28
  if (typeof globalThis !== 'undefined')
17
29
  return globalThis;
@@ -29,23 +41,79 @@ if (!globalObject.Buffer) {
29
41
  globalObject.Buffer = Buffer;
30
42
  }
31
43
  /**
32
- * Synchronous random-bytes shim. On RN, this delegates to
33
- * `expo-crypto.getRandomBytes` (statically imported by the RN variant of
34
- * `@oxyhq/protocol`'s `platform/crypto`, so available without any async
35
- * warm-up). On Node /
36
- * browser, this throws — but is never called there because both platforms
37
- * already provide `globalThis.crypto.getRandomValues` natively.
44
+ * Lazily-resolved `node:crypto` module, cached after the first attempt.
45
+ * `undefined` = not tried yet; `null` = tried and unavailable (non-Node host).
46
+ */
47
+ let cachedNodeCrypto;
48
+ /**
49
+ * Synchronously load `node:crypto` on a Node runtime, or `null` elsewhere.
50
+ *
51
+ * Uses a guarded, Node-only `require`. Every runtime that actually reaches this
52
+ * branch has a working CommonJS `require`: `@oxyhq/core` publishes no
53
+ * `"type": "module"`, so Node loads it as CommonJS and the `require` free
54
+ * variable is present. Browsers never reach here (they own `globalThis.crypto`,
55
+ * so this polyfill is never installed) and React Native takes the
56
+ * `getRandomBytesRN` branch — so the `node:crypto` reference is dead code in
57
+ * those bundles, and Expo's Metro resolver shims `node:*` builtins, keeping
58
+ * web/native bundles green.
59
+ */
60
+ function loadNodeCryptoSync() {
61
+ if (cachedNodeCrypto !== undefined) {
62
+ return cachedNodeCrypto;
63
+ }
64
+ if (typeof require !== 'function') {
65
+ cachedNodeCrypto = null;
66
+ return cachedNodeCrypto;
67
+ }
68
+ try {
69
+ cachedNodeCrypto = require('node:crypto');
70
+ }
71
+ catch {
72
+ // No Node crypto (unexpected on a real Node host) — degrade to the next
73
+ // mechanism rather than crash.
74
+ cachedNodeCrypto = null;
75
+ }
76
+ return cachedNodeCrypto;
77
+ }
78
+ /**
79
+ * Fill `array` with cryptographically-secure random bytes from `node:crypto`.
80
+ * Prefers `webcrypto.getRandomValues`; falls back to `randomFillSync`. Returns
81
+ * `false` when Node crypto is unavailable so the caller can try the next
82
+ * mechanism.
38
83
  */
39
- function getRandomBytesSync(byteCount) {
40
- // `getRandomBytesRN` throws on non-RN platforms. That's fine: this
41
- // function is only ever called as a fallback when the native
42
- // `globalThis.crypto.getRandomValues` is missing, which on a normal
43
- // Node/browser host never happens.
44
- return getRandomBytesRN(byteCount);
84
+ function fillFromNodeCrypto(array) {
85
+ const nodeCrypto = loadNodeCryptoSync();
86
+ if (!nodeCrypto) {
87
+ return false;
88
+ }
89
+ const webcrypto = nodeCrypto.webcrypto;
90
+ if (webcrypto && typeof webcrypto.getRandomValues === 'function') {
91
+ try {
92
+ webcrypto.getRandomValues(array);
93
+ return true;
94
+ }
95
+ catch {
96
+ // `webcrypto.getRandomValues` rejects non-integer views (Float*Array,
97
+ // DataView); fall through to `randomFillSync`, which accepts any view.
98
+ }
99
+ }
100
+ if (typeof nodeCrypto.randomFillSync === 'function') {
101
+ nodeCrypto.randomFillSync(array);
102
+ return true;
103
+ }
104
+ return false;
45
105
  }
46
106
  const cryptoPolyfill = {
47
107
  getRandomValues(array) {
48
- const bytes = getRandomBytesSync(array.byteLength);
108
+ // Node: back the CSPRNG with `node:crypto`. This is the path that matters on
109
+ // Node runtimes shipping WITHOUT a global WebCrypto — where delegating to
110
+ // the RN-only expo-crypto stub would throw.
111
+ if (isNodeJS() && fillFromNodeCrypto(array)) {
112
+ return array;
113
+ }
114
+ // React Native (and any non-Node host without WebCrypto): synchronous
115
+ // expo-crypto via @oxyhq/protocol's RN `platform/crypto` variant.
116
+ const bytes = getRandomBytesRN(array.byteLength);
49
117
  const uint8View = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
50
118
  uint8View.set(bytes);
51
119
  return array;