@oxy.so/protocol 1.1.0 → 1.1.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.
@@ -123,15 +123,10 @@ export async function loadSecureStore() {
123
123
  export async function loadAsyncStorage() {
124
124
  throw notReactNativeError('@react-native-async-storage/async-storage');
125
125
  }
126
- /**
127
- * Synchronous random-bytes via `expo-crypto.getRandomBytes`. Only available
128
- * in the React Native variant. The default variant throws because Node and
129
- * browsers have their own native CSPRNGs (`crypto.randomBytes` and
130
- * `crypto.getRandomValues` respectively) — callers should use those.
131
- */
132
- export function getRandomBytesRN(_byteCount) {
133
- throw notReactNativeError('expo-crypto.getRandomBytes (sync)');
134
- }
126
+ // Synchronous random bytes live in the dependency-free `./random` module so
127
+ // `@oxy.so/core`'s crypto polyfill can load them through the
128
+ // `@oxy.so/protocol/random` entry without evaluating any crypto library first.
129
+ export { getRandomBytesRN } from './random.js';
135
130
  // ---------------------------------------------------------------------------
136
131
  // Shared identity bridge — `@oxy.so/expo-oxy-identity` (native-only).
137
132
  //
@@ -24,7 +24,7 @@
24
24
  * Those three RN modules are declared OPTIONAL peer dependencies in
25
25
  * `package.json`. A static `import` contradicts that: an optional peer that is
26
26
  * omitted does not degrade, it fails to RESOLVE, and Metro aborts the whole
27
- * bundle. Because `@oxy.so/core`'s `crypto/polyfill` imports `@oxy.so/protocol`
27
+ * bundle. Because `@oxy.so/core` imports `@oxy.so/protocol`
28
28
  * from its root entry, this file is in the eager graph of EVERY React Native
29
29
  * app on `@oxy.so/core` — so a single undeclared optional peer broke the native
30
30
  * bundle of every app that did not happen to install it, with a resolution
@@ -52,14 +52,8 @@
52
52
  * so it stays a plain static import.
53
53
  */
54
54
  import { requireOptionalNativeModule } from 'expo-modules-core';
55
- let expoCryptoModule = null;
56
- let expoCryptoError;
57
- try {
58
- expoCryptoModule = require('expo-crypto');
59
- }
60
- catch (error) {
61
- expoCryptoError = error;
62
- }
55
+ import { missingOptionalPeerError } from './optionalPeer.js';
56
+ import { requireExpoCrypto } from './random.native.js';
63
57
  let secureStoreModule = null;
64
58
  let secureStoreError;
65
59
  try {
@@ -80,22 +74,6 @@ try {
80
74
  catch (error) {
81
75
  asyncStorageError = error;
82
76
  }
83
- /**
84
- * Actionable error for a missing optional peer. Carries the underlying Metro
85
- * resolution message so the failure is never silent — the `catch` above only
86
- * defers the report to the point where the capability is actually needed.
87
- */
88
- function missingOptionalPeerError(packageName, capability, cause) {
89
- const sentences = [
90
- `[oxy.protocol.crypto] '${packageName}' is not installed, so ${capability} is unavailable in this app.`,
91
- 'It is an optional peer dependency of @oxy.so/protocol that the React Native runtime needs —',
92
- `install it with \`npx expo install ${packageName}\`.`,
93
- ];
94
- if (cause instanceof Error) {
95
- sentences.push(`Underlying error: ${cause.message}`);
96
- }
97
- return new Error(sentences.join(' '));
98
- }
99
77
  // ---------------------------------------------------------------------------
100
78
  // Node `crypto` — never available in RN.
101
79
  // ---------------------------------------------------------------------------
@@ -116,10 +94,7 @@ export async function loadNodeCrypto() {
116
94
  // their compilation (see expoTypes.ts).
117
95
  // ---------------------------------------------------------------------------
118
96
  export async function loadExpoCrypto() {
119
- if (!expoCryptoModule) {
120
- throw missingOptionalPeerError('expo-crypto', 'React Native cryptography', expoCryptoError);
121
- }
122
- return expoCryptoModule;
97
+ return requireExpoCrypto('React Native cryptography');
123
98
  }
124
99
  // ---------------------------------------------------------------------------
125
100
  // expo-secure-store — RN keychain / keystore.
@@ -142,20 +117,12 @@ export async function loadAsyncStorage() {
142
117
  // ships ESM or CJS-with-default.
143
118
  return { default: asyncStorageModule };
144
119
  }
145
- /**
146
- * Synchronous random-bytes via `expo-crypto.getRandomBytes`.
147
- *
148
- * Synchronous by contract: `@oxy.so/core`'s crypto polyfill uses this to back
149
- * `globalThis.crypto.getRandomValues`, which cannot await. That is why
150
- * `expo-crypto` is resolved with a synchronous `require` at module scope rather
151
- * than a dynamic `import()`.
152
- */
153
- export function getRandomBytesRN(byteCount) {
154
- if (!expoCryptoModule) {
155
- throw missingOptionalPeerError('expo-crypto', 'the React Native CSPRNG (crypto.getRandomValues)', expoCryptoError);
156
- }
157
- return expoCryptoModule.getRandomBytes(byteCount);
158
- }
120
+ // Synchronous random bytes (and the single `expo-crypto` resolution) live in
121
+ // the dependency-free `./random.native` module, so `@oxy.so/core`'s crypto
122
+ // polyfill can load them through `@oxy.so/protocol/random` without evaluating
123
+ // any crypto library first. The explicit `.native` specifier keeps tsc and
124
+ // Metro pointed at the same file.
125
+ export { getRandomBytesRN } from './random.native.js';
159
126
  // ---------------------------------------------------------------------------
160
127
  // Shared identity bridge — `@oxy.so/expo-oxy-identity` (native-only, OPTIONAL).
161
128
  //
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Actionable error for a missing optional React Native peer.
3
+ *
4
+ * Shared by the React Native platform variants (`crypto.native.ts`,
5
+ * `random.native.ts`). It carries the underlying Metro resolution message so
6
+ * the failure is never silent: the variants' `try { require(...) } catch` only
7
+ * defers the report to the point where the capability is actually used.
8
+ */
9
+ export function missingOptionalPeerError(packageName, capability, cause) {
10
+ const sentences = [
11
+ `[oxy.protocol.crypto] '${packageName}' is not installed, so ${capability} is unavailable in this app.`,
12
+ 'It is an optional peer dependency of @oxy.so/protocol that the React Native runtime needs —',
13
+ `install it with \`npx expo install ${packageName}\`.`,
14
+ ];
15
+ if (cause instanceof Error) {
16
+ sentences.push(`Underlying error: ${cause.message}`);
17
+ }
18
+ return new Error(sentences.join(' '));
19
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Synchronous CSPRNG source — default variant (Node.js, browsers, bundlers).
3
+ *
4
+ * Companion to `./random.native.ts`, which Metro substitutes on iOS / Android.
5
+ * Node and browsers own a native CSPRNG (`node:crypto`, `globalThis.crypto`),
6
+ * so `getRandomBytesRN` only exists here to keep both variants' surfaces
7
+ * identical, and throws if anything reaches it outside React Native.
8
+ *
9
+ * Like its sibling, this module imports nothing: it is part of the
10
+ * `@oxy.so/protocol/random` entry that `@oxy.so/core`'s crypto polyfill loads
11
+ * BEFORE any crypto library is evaluated.
12
+ */
13
+ export function getRandomBytesRN(_byteCount) {
14
+ throw new Error("[oxy.protocol.crypto] Tried to load 'expo-crypto.getRandomBytes (sync)' outside React Native. This module is only available in a React Native runtime; bundling routed this consumer to the default (Node/web) variant. This indicates a missing platform gate (`isReactNative()`) in the calling code.");
15
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Synchronous CSPRNG source — React Native variant.
3
+ *
4
+ * Companion to `./random.ts`; Metro substitutes this file on iOS / Android
5
+ * (see `./crypto.ts` for how the `.native` split works).
6
+ *
7
+ * This module is deliberately tiny and imports NOTHING but `expo-crypto`: it
8
+ * backs `@oxy.so/core`'s `globalThis.crypto.getRandomValues` polyfill, which
9
+ * must be fully installed before any module that captures `globalThis.crypto`
10
+ * at evaluation time (`@noble/hashes` 1.x's `crypto.js` does exactly that) is
11
+ * evaluated. Reaching a crypto library from here would let that library
12
+ * capture the missing global first and throw
13
+ * `crypto.getRandomValues must be defined` for the lifetime of the app.
14
+ *
15
+ * `expo-crypto` is an OPTIONAL peer, so it is resolved with a string-literal
16
+ * `require` inside a `try` (Metro's optional-dependency form; see
17
+ * `./crypto.native.ts`), and the load stays synchronous because
18
+ * `getRandomValues` cannot await.
19
+ */
20
+ import { missingOptionalPeerError } from './optionalPeer.js';
21
+ let expoCryptoModule = null;
22
+ let expoCryptoError;
23
+ try {
24
+ expoCryptoModule = require('expo-crypto');
25
+ }
26
+ catch (error) {
27
+ expoCryptoError = error;
28
+ }
29
+ /**
30
+ * The resolved `expo-crypto` module, or an actionable error naming the missing
31
+ * peer and the capability that needed it. Shared with `./crypto.native.ts` so
32
+ * the optional peer is resolved in exactly one place.
33
+ */
34
+ export function requireExpoCrypto(capability) {
35
+ if (!expoCryptoModule) {
36
+ throw missingOptionalPeerError('expo-crypto', capability, expoCryptoError);
37
+ }
38
+ return expoCryptoModule;
39
+ }
40
+ /**
41
+ * Synchronous random bytes via `expo-crypto.getRandomBytes`.
42
+ *
43
+ * Synchronous by contract: `@oxy.so/core`'s crypto polyfill uses this to back
44
+ * `globalThis.crypto.getRandomValues`, which cannot await.
45
+ */
46
+ export function getRandomBytesRN(byteCount) {
47
+ return requireExpoCrypto('the React Native CSPRNG (crypto.getRandomValues)').getRandomBytes(byteCount);
48
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * `@oxy.so/protocol/random` — the platform randomness source, and nothing else.
3
+ *
4
+ * `@oxy.so/core`'s crypto polyfill installs `globalThis.crypto.getRandomValues`
5
+ * on hosts that lack it (React Native / Hermes). `@noble/hashes` 1.x captures
6
+ * `globalThis.crypto` ONCE, when its `crypto.js` is evaluated, so the polyfill
7
+ * has to be installed before any `@noble/*` module is evaluated. ES imports are
8
+ * evaluated before the importing module's body, so whatever the polyfill
9
+ * imports is evaluated first: importing the ROOT entry (which reaches
10
+ * `@noble/curves` through the envelope signer) let noble capture `undefined`
11
+ * and broke identity creation on Android.
12
+ *
13
+ * This entry therefore reaches no crypto library, no `@oxy.so/*` package and no
14
+ * third-party module other than the optional `expo-crypto` peer (RN variant
15
+ * only). `src/__tests__/randomEntry.test.ts` walks its module graph to keep it
16
+ * that way.
17
+ */
18
+ export { isNodeJS, isReactNative } from './platform/platform.js';
19
+ export { getRandomBytesRN } from './platform/random.js';