From f6737dcf4a495fff15815548b8501ff04791c03d Mon Sep 17 00:00:00 2001 From: Bradley Ayers Date: Sun, 1 Mar 2026 09:44:44 +1100 Subject: [PATCH] fix expo-crypto support in API routes (#43405) This fixes support on API routes for using `getRandomBytes`, `getRandomBytesAsync`, `getRandomValues`, `randomUUID`, `digestAsync`. These were previously broken and only `digestStringAsync` would work because it used the global `crypto` instead of getting it from `getCrypto()`. # Why # How # Test Plan # Checklist - [ ] I added a `changelog.md` entry and rebuilt the package sources according to [this short guide](https://github.com/expo/expo/blob/main/CONTRIBUTING.md#-before-submitting) - [ ] This diff will work correctly for `npx expo prebuild` & EAS Build (eg: updated a module plugin). - [ ] Conforms with the [Documentation Writing Style Guide](https://github.com/expo/expo/blob/main/guides/Expo%20Documentation%20Writing%20Style%20Guide.md) --------- Co-authored-by: Phil Pluckthun --- packages/expo-crypto/CHANGELOG.md | 2 ++ packages/expo-crypto/build/ExpoCrypto.web.d.ts.map | 2 +- packages/expo-crypto/build/ExpoCrypto.web.js | 13 ++++++++++++- packages/expo-crypto/build/ExpoCrypto.web.js.map | 2 +- packages/expo-crypto/src/ExpoCrypto.web.ts | 11 ++++++++++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/expo-crypto/CHANGELOG.md b/packages/expo-crypto/CHANGELOG.md index 161620d4cc2393..89146b8827bc89 100644 --- a/packages/expo-crypto/CHANGELOG.md +++ b/packages/expo-crypto/CHANGELOG.md @@ -8,6 +8,8 @@ ### 🐛 Bug fixes +- Access `crypto` on `globalThis` rather than just `window.crypto` ([#43405](https://github.com/expo/expo/pull/43405) by [@bradleyayers](https://github.com/bradleyayers)) + ### 💡 Others ## 55.0.8 — 2026-02-25 diff --git a/packages/expo-crypto/build/ExpoCrypto.web.d.ts.map b/packages/expo-crypto/build/ExpoCrypto.web.d.ts.map index 6ead886bf97f1c..faaedc1d13e724 100644 --- a/packages/expo-crypto/build/ExpoCrypto.web.d.ts.map +++ b/packages/expo-crypto/build/ExpoCrypto.web.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"ExpoCrypto.web.d.ts","sourceRoot":"","sources":["../src/ExpoCrypto.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,qBAAqB,EAAkB,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;;iCAM7E,qBAAqB,QAC1B,MAAM,WACH,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC;2BAiBK,MAAM,GAAG,UAAU;gCAIR,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;gCAIlC,UAAU;;2BAMf,mBAAmB,QAAQ,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;;AApCtF,wBAuCE"} \ No newline at end of file +{"version":3,"file":"ExpoCrypto.web.d.ts","sourceRoot":"","sources":["../src/ExpoCrypto.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAE,qBAAqB,EAAkB,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;;iCAc7E,qBAAqB,QAC1B,MAAM,WACH,mBAAmB,GAC3B,OAAO,CAAC,MAAM,CAAC;2BAkBK,MAAM,GAAG,UAAU;gCAIR,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC;gCAIlC,UAAU;;2BAMf,mBAAmB,QAAQ,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;;AArCtF,wBAwCE"} \ No newline at end of file diff --git a/packages/expo-crypto/build/ExpoCrypto.web.js b/packages/expo-crypto/build/ExpoCrypto.web.js index 1c282fd6a4d620..84e9e321457f3a 100644 --- a/packages/expo-crypto/build/ExpoCrypto.web.js +++ b/packages/expo-crypto/build/ExpoCrypto.web.js @@ -1,8 +1,19 @@ import { CodedError } from 'expo-modules-core'; import { CryptoEncoding } from './Crypto.types'; -const getCrypto = () => window.crypto ?? window.msCrypto; +const getCrypto = () => { + if (typeof globalThis.crypto !== 'undefined') { + return globalThis.crypto; + } + else if (typeof window !== 'undefined') { + return window.crypto ?? window.msCrypto; + } + else { + return crypto; + } +}; export default { async digestStringAsync(algorithm, data, options) { + const crypto = getCrypto(); if (!crypto.subtle) { throw new CodedError('ERR_CRYPTO_UNAVAILABLE', 'Access to the WebCrypto API is restricted to secure origins (localhost/https).'); } diff --git a/packages/expo-crypto/build/ExpoCrypto.web.js.map b/packages/expo-crypto/build/ExpoCrypto.web.js.map index 51d9c3f91728ad..740f50b87bcd43 100644 --- a/packages/expo-crypto/build/ExpoCrypto.web.js.map +++ b/packages/expo-crypto/build/ExpoCrypto.web.js.map @@ -1 +1 @@ -{"version":3,"file":"ExpoCrypto.web.js","sourceRoot":"","sources":["../src/ExpoCrypto.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAyB,cAAc,EAAuB,MAAM,gBAAgB,CAAC;AAE5F,MAAM,SAAS,GAAG,GAAW,EAAE,CAAC,MAAM,CAAC,MAAM,IAAK,MAAc,CAAC,QAAQ,CAAC;AAE1E,eAAe;IACb,KAAK,CAAC,iBAAiB,CACrB,SAAgC,EAChC,IAAY,EACZ,OAA4B;QAE5B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,UAAU,CAClB,wBAAwB,EACxB,gFAAgF,CACjF,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,QAAQ,KAAK,cAAc,CAAC,GAAG,EAAE,CAAC;YAC5C,OAAO,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,cAAc,CAAC,MAAM,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,UAAU,CAAC,mBAAmB,EAAE,iCAAiC,CAAC,CAAC;IAC/E,CAAC;IACD,cAAc,CAAC,MAAc;QAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,SAAS,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,CAAC,mBAAmB,CAAC,MAAc;QACtC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,SAAS,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,eAAe,CAAC,UAAsB;QACpC,OAAO,SAAS,EAAE,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACjD,CAAC;IACD,UAAU;QACR,OAAO,SAAS,EAAE,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;IACD,WAAW,CAAC,SAA8B,EAAE,IAAiB;QAC3D,OAAO,SAAS,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;CACF,CAAC;AAEF,SAAS,SAAS,CAAC,MAAmB;IACpC,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAEzC,MAAM,QAAQ,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/C,OAAO,aAAa,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["import { CodedError, TypedArray } from 'expo-modules-core';\n\nimport { CryptoDigestAlgorithm, CryptoEncoding, CryptoDigestOptions } from './Crypto.types';\n\nconst getCrypto = (): Crypto => window.crypto ?? (window as any).msCrypto;\n\nexport default {\n async digestStringAsync(\n algorithm: CryptoDigestAlgorithm,\n data: string,\n options: CryptoDigestOptions\n ): Promise {\n if (!crypto.subtle) {\n throw new CodedError(\n 'ERR_CRYPTO_UNAVAILABLE',\n 'Access to the WebCrypto API is restricted to secure origins (localhost/https).'\n );\n }\n const encoder = new TextEncoder();\n const buffer = encoder.encode(data);\n const hashedData = await crypto.subtle.digest(algorithm, buffer);\n if (options.encoding === CryptoEncoding.HEX) {\n return hexString(hashedData);\n } else if (options.encoding === CryptoEncoding.BASE64) {\n return btoa(String.fromCharCode(...new Uint8Array(hashedData)));\n }\n throw new CodedError('ERR_CRYPTO_DIGEST', 'Invalid encoding type provided.');\n },\n getRandomBytes(length: number): Uint8Array {\n const array = new Uint8Array(length);\n return getCrypto().getRandomValues(array);\n },\n async getRandomBytesAsync(length: number): Promise {\n const array = new Uint8Array(length);\n return getCrypto().getRandomValues(array);\n },\n getRandomValues(typedArray: TypedArray) {\n return getCrypto().getRandomValues(typedArray);\n },\n randomUUID() {\n return getCrypto().randomUUID();\n },\n digestAsync(algorithm: AlgorithmIdentifier, data: ArrayBuffer): Promise {\n return getCrypto().subtle.digest(algorithm, data);\n },\n};\n\nfunction hexString(buffer: ArrayBuffer): string {\n const byteArray = new Uint8Array(buffer);\n\n const hexCodes = [...byteArray].map((value) => {\n const hexCode = value.toString(16);\n const paddedHexCode = hexCode.padStart(2, '0');\n return paddedHexCode;\n });\n\n return hexCodes.join('');\n}\n"]} \ No newline at end of file +{"version":3,"file":"ExpoCrypto.web.js","sourceRoot":"","sources":["../src/ExpoCrypto.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAc,MAAM,mBAAmB,CAAC;AAE3D,OAAO,EAAyB,cAAc,EAAuB,MAAM,gBAAgB,CAAC;AAE5F,MAAM,SAAS,GAAG,GAAW,EAAE;IAC7B,IAAI,OAAO,UAAU,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QAC7C,OAAO,UAAU,CAAC,MAAM,CAAC;IAC3B,CAAC;SAAM,IAAI,OAAO,MAAM,KAAK,WAAW,EAAE,CAAC;QACzC,OAAO,MAAM,CAAC,MAAM,IAAK,MAAc,CAAC,QAAQ,CAAC;IACnD,CAAC;SAAM,CAAC;QACN,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC,CAAC;AAEF,eAAe;IACb,KAAK,CAAC,iBAAiB,CACrB,SAAgC,EAChC,IAAY,EACZ,OAA4B;QAE5B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,IAAI,UAAU,CAClB,wBAAwB,EACxB,gFAAgF,CACjF,CAAC;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,WAAW,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACpC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACjE,IAAI,OAAO,CAAC,QAAQ,KAAK,cAAc,CAAC,GAAG,EAAE,CAAC;YAC5C,OAAO,SAAS,CAAC,UAAU,CAAC,CAAC;QAC/B,CAAC;aAAM,IAAI,OAAO,CAAC,QAAQ,KAAK,cAAc,CAAC,MAAM,EAAE,CAAC;YACtD,OAAO,IAAI,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,IAAI,UAAU,CAAC,mBAAmB,EAAE,iCAAiC,CAAC,CAAC;IAC/E,CAAC;IACD,cAAc,CAAC,MAAc;QAC3B,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,SAAS,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,KAAK,CAAC,mBAAmB,CAAC,MAAc;QACtC,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;QACrC,OAAO,SAAS,EAAE,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC5C,CAAC;IACD,eAAe,CAAC,UAAsB;QACpC,OAAO,SAAS,EAAE,CAAC,eAAe,CAAC,UAAU,CAAC,CAAC;IACjD,CAAC;IACD,UAAU;QACR,OAAO,SAAS,EAAE,CAAC,UAAU,EAAE,CAAC;IAClC,CAAC;IACD,WAAW,CAAC,SAA8B,EAAE,IAAiB;QAC3D,OAAO,SAAS,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACpD,CAAC;CACF,CAAC;AAEF,SAAS,SAAS,CAAC,MAAmB;IACpC,MAAM,SAAS,GAAG,IAAI,UAAU,CAAC,MAAM,CAAC,CAAC;IAEzC,MAAM,QAAQ,GAAG,CAAC,GAAG,SAAS,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;QAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC;QACnC,MAAM,aAAa,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAC/C,OAAO,aAAa,CAAC;IACvB,CAAC,CAAC,CAAC;IAEH,OAAO,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC3B,CAAC","sourcesContent":["import { CodedError, TypedArray } from 'expo-modules-core';\n\nimport { CryptoDigestAlgorithm, CryptoEncoding, CryptoDigestOptions } from './Crypto.types';\n\nconst getCrypto = (): Crypto => {\n if (typeof globalThis.crypto !== 'undefined') {\n return globalThis.crypto;\n } else if (typeof window !== 'undefined') {\n return window.crypto ?? (window as any).msCrypto;\n } else {\n return crypto;\n }\n};\n\nexport default {\n async digestStringAsync(\n algorithm: CryptoDigestAlgorithm,\n data: string,\n options: CryptoDigestOptions\n ): Promise {\n const crypto = getCrypto();\n if (!crypto.subtle) {\n throw new CodedError(\n 'ERR_CRYPTO_UNAVAILABLE',\n 'Access to the WebCrypto API is restricted to secure origins (localhost/https).'\n );\n }\n const encoder = new TextEncoder();\n const buffer = encoder.encode(data);\n const hashedData = await crypto.subtle.digest(algorithm, buffer);\n if (options.encoding === CryptoEncoding.HEX) {\n return hexString(hashedData);\n } else if (options.encoding === CryptoEncoding.BASE64) {\n return btoa(String.fromCharCode(...new Uint8Array(hashedData)));\n }\n throw new CodedError('ERR_CRYPTO_DIGEST', 'Invalid encoding type provided.');\n },\n getRandomBytes(length: number): Uint8Array {\n const array = new Uint8Array(length);\n return getCrypto().getRandomValues(array);\n },\n async getRandomBytesAsync(length: number): Promise {\n const array = new Uint8Array(length);\n return getCrypto().getRandomValues(array);\n },\n getRandomValues(typedArray: TypedArray) {\n return getCrypto().getRandomValues(typedArray);\n },\n randomUUID() {\n return getCrypto().randomUUID();\n },\n digestAsync(algorithm: AlgorithmIdentifier, data: ArrayBuffer): Promise {\n return getCrypto().subtle.digest(algorithm, data);\n },\n};\n\nfunction hexString(buffer: ArrayBuffer): string {\n const byteArray = new Uint8Array(buffer);\n\n const hexCodes = [...byteArray].map((value) => {\n const hexCode = value.toString(16);\n const paddedHexCode = hexCode.padStart(2, '0');\n return paddedHexCode;\n });\n\n return hexCodes.join('');\n}\n"]} \ No newline at end of file diff --git a/packages/expo-crypto/src/ExpoCrypto.web.ts b/packages/expo-crypto/src/ExpoCrypto.web.ts index 6be365a0e2a7dc..7cfd9f3f64353a 100644 --- a/packages/expo-crypto/src/ExpoCrypto.web.ts +++ b/packages/expo-crypto/src/ExpoCrypto.web.ts @@ -2,7 +2,15 @@ import { CodedError, TypedArray } from 'expo-modules-core'; import { CryptoDigestAlgorithm, CryptoEncoding, CryptoDigestOptions } from './Crypto.types'; -const getCrypto = (): Crypto => window.crypto ?? (window as any).msCrypto; +const getCrypto = (): Crypto => { + if (typeof globalThis.crypto !== 'undefined') { + return globalThis.crypto; + } else if (typeof window !== 'undefined') { + return window.crypto ?? (window as any).msCrypto; + } else { + return crypto; + } +}; export default { async digestStringAsync( @@ -10,6 +18,7 @@ export default { data: string, options: CryptoDigestOptions ): Promise { + const crypto = getCrypto(); if (!crypto.subtle) { throw new CodedError( 'ERR_CRYPTO_UNAVAILABLE',