@symbiote-native/crypto 0.0.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.
- package/LICENSE +21 -0
- package/README.md +226 -0
- package/build/angular/index.d.ts +1 -0
- package/build/angular/index.js +4 -0
- package/build/core/crypto.d.ts +37 -0
- package/build/core/crypto.js +132 -0
- package/build/core/index.d.ts +2 -0
- package/build/core/index.js +2 -0
- package/build/core/native-module.d.ts +9 -0
- package/build/core/native-module.js +3 -0
- package/build/core/types.d.ts +24 -0
- package/build/core/types.js +18 -0
- package/build/react/index.d.ts +1 -0
- package/build/react/index.js +5 -0
- package/build/vue/index.d.ts +1 -0
- package/build/vue/index.js +4 -0
- package/build-ngc/angular/index.d.ts +1 -0
- package/build-ngc/angular/index.js +5 -0
- package/build-ngc/angular/index.js.map +1 -0
- package/build-ngc/core/crypto.d.ts +37 -0
- package/build-ngc/core/crypto.js +133 -0
- package/build-ngc/core/crypto.js.map +1 -0
- package/build-ngc/core/index.d.ts +2 -0
- package/build-ngc/core/index.js +3 -0
- package/build-ngc/core/index.js.map +1 -0
- package/build-ngc/core/native-module.d.ts +9 -0
- package/build-ngc/core/native-module.js +4 -0
- package/build-ngc/core/native-module.js.map +1 -0
- package/build-ngc/core/types.d.ts +24 -0
- package/build-ngc/core/types.js +19 -0
- package/build-ngc/core/types.js.map +1 -0
- package/native-link.json +12 -0
- package/package.json +107 -0
- package/src/angular/index.ts +4 -0
- package/src/core/crypto.test.ts +245 -0
- package/src/core/crypto.ts +165 -0
- package/src/core/index.ts +18 -0
- package/src/core/native-module.ts +26 -0
- package/src/core/types.ts +32 -0
- package/src/react/index.ts +5 -0
- package/src/vue/index.ts +4 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { UnavailabilityError } from 'expo-modules-core';
|
|
2
|
+
import { expoCrypto } from './native-module';
|
|
3
|
+
import { CryptoDigestAlgorithm, CryptoEncoding } from './types';
|
|
4
|
+
const NATIVE_MODULE_NAME = 'expo-crypto';
|
|
5
|
+
const MIN_BYTE_COUNT = 0;
|
|
6
|
+
const MAX_BYTE_COUNT = 1024;
|
|
7
|
+
export class CryptoError extends TypeError {
|
|
8
|
+
code = 'ERR_CRYPTO';
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(`expo-crypto: ${message}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function assertByteCount(value, methodName) {
|
|
14
|
+
if (typeof value !== 'number' ||
|
|
15
|
+
Number.isNaN(value) ||
|
|
16
|
+
Math.floor(value) < MIN_BYTE_COUNT ||
|
|
17
|
+
Math.floor(value) > MAX_BYTE_COUNT) {
|
|
18
|
+
throw new TypeError(`expo-crypto: ${methodName}(${value}) expected a valid number from range ${MIN_BYTE_COUNT}...${MAX_BYTE_COUNT}`);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function assertAlgorithm(algorithm) {
|
|
22
|
+
if (!Object.values(CryptoDigestAlgorithm).includes(algorithm)) {
|
|
23
|
+
throw new CryptoError(`Invalid algorithm provided. Expected one of: CryptoDigestAlgorithm.${Object.keys(CryptoDigestAlgorithm).join(', CryptoDigestAlgorithm.')}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function assertData(data) {
|
|
27
|
+
if (typeof data !== 'string') {
|
|
28
|
+
throw new CryptoError('Invalid data provided. Expected a string.');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function assertEncoding(encoding) {
|
|
32
|
+
if (!Object.values(CryptoEncoding).includes(encoding)) {
|
|
33
|
+
throw new CryptoError(`Invalid encoding provided. Expected one of: CryptoEncoding.${Object.keys(CryptoEncoding).join(', CryptoEncoding.')}`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generates completely random bytes using native implementations. `byteCount` must be within
|
|
38
|
+
* `0`-`1024` (inclusive), anything else throws a `TypeError`.
|
|
39
|
+
*/
|
|
40
|
+
export function getRandomBytes(byteCount) {
|
|
41
|
+
assertByteCount(byteCount, 'getRandomBytes');
|
|
42
|
+
const validByteCount = Math.floor(byteCount);
|
|
43
|
+
if (!expoCrypto.getRandomValues) {
|
|
44
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getRandomBytes');
|
|
45
|
+
}
|
|
46
|
+
const byteArray = new Uint8Array(validByteCount);
|
|
47
|
+
expoCrypto.getRandomValues(byteArray);
|
|
48
|
+
return byteArray;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Generates completely random bytes using native implementations. `byteCount` must be within
|
|
52
|
+
* `0`-`1024` (inclusive), anything else throws a `TypeError`.
|
|
53
|
+
*/
|
|
54
|
+
export async function getRandomBytesAsync(byteCount) {
|
|
55
|
+
assertByteCount(byteCount, 'getRandomBytesAsync');
|
|
56
|
+
const validByteCount = Math.floor(byteCount);
|
|
57
|
+
if (!expoCrypto.getRandomValues) {
|
|
58
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getRandomBytesAsync');
|
|
59
|
+
}
|
|
60
|
+
const byteArray = new Uint8Array(validByteCount);
|
|
61
|
+
expoCrypto.getRandomValues(byteArray);
|
|
62
|
+
return byteArray;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Fills the provided `TypedArray` with cryptographically secure random values in place, and
|
|
66
|
+
* returns it.
|
|
67
|
+
*/
|
|
68
|
+
export function getRandomValues(typedArray) {
|
|
69
|
+
if (!expoCrypto.getRandomValues) {
|
|
70
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getRandomValues');
|
|
71
|
+
}
|
|
72
|
+
expoCrypto.getRandomValues(typedArray);
|
|
73
|
+
return typedArray;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Returns a unique identifier based on the V4 UUID spec (RFC4122), using cryptographically
|
|
77
|
+
* secure random values.
|
|
78
|
+
*/
|
|
79
|
+
export function randomUUID() {
|
|
80
|
+
if (!expoCrypto.randomUUID) {
|
|
81
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'randomUUID');
|
|
82
|
+
}
|
|
83
|
+
return expoCrypto.randomUUID();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Generates a digest of the supplied `data` string with the provided digest `algorithm`,
|
|
87
|
+
* formatted as one of `CryptoEncoding` (defaults to `HEX`).
|
|
88
|
+
*/
|
|
89
|
+
export async function digestStringAsync(algorithm, data, options = { encoding: CryptoEncoding.HEX }) {
|
|
90
|
+
if (!expoCrypto.digestStringAsync) {
|
|
91
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'digestStringAsync');
|
|
92
|
+
}
|
|
93
|
+
assertAlgorithm(algorithm);
|
|
94
|
+
assertData(data);
|
|
95
|
+
assertEncoding(options.encoding);
|
|
96
|
+
return expoCrypto.digestStringAsync(algorithm, data, options);
|
|
97
|
+
}
|
|
98
|
+
// Fixed output length (in bytes) per digest algorithm — used only as the sync fallback's
|
|
99
|
+
// preallocated output buffer size when the native module has no digestAsync.
|
|
100
|
+
const digestLengths = {
|
|
101
|
+
[CryptoDigestAlgorithm.SHA1]: 20,
|
|
102
|
+
[CryptoDigestAlgorithm.SHA256]: 32,
|
|
103
|
+
[CryptoDigestAlgorithm.SHA384]: 48,
|
|
104
|
+
[CryptoDigestAlgorithm.SHA512]: 64,
|
|
105
|
+
[CryptoDigestAlgorithm.MD2]: 16,
|
|
106
|
+
[CryptoDigestAlgorithm.MD4]: 16,
|
|
107
|
+
[CryptoDigestAlgorithm.MD5]: 16,
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* Generates a digest of the supplied `TypedArray`/`ArrayBuffer` of bytes with the provided digest
|
|
111
|
+
* `algorithm`. Prefers the native async `digestAsync` when present; otherwise falls back to
|
|
112
|
+
* allocating a fixed-size output buffer (sized via `digestLengths`) and the sync `digest`.
|
|
113
|
+
*/
|
|
114
|
+
export function digest(algorithm, data) {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
try {
|
|
117
|
+
if (typeof expoCrypto.digestAsync === 'function') {
|
|
118
|
+
resolve(expoCrypto.digestAsync(algorithm, data));
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (!expoCrypto.digest) {
|
|
122
|
+
throw new UnavailabilityError(NATIVE_MODULE_NAME, 'digest');
|
|
123
|
+
}
|
|
124
|
+
const output = new Uint8Array(digestLengths[algorithm]);
|
|
125
|
+
expoCrypto.digest(algorithm, output, data);
|
|
126
|
+
resolve(output.buffer);
|
|
127
|
+
}
|
|
128
|
+
catch (error) {
|
|
129
|
+
reject(error);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=crypto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crypto.js","sourceRoot":"","sources":["../../src/core/crypto.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { CryptoError, getRandomBytes, getRandomBytesAsync, getRandomValues, randomUUID, digestStringAsync, digest, } from './crypto';
|
|
2
|
+
export { CryptoDigestAlgorithm, CryptoEncoding, type ICryptoDigestOptions, type IDigest, type IUintBasedTypedArray, type IIntBasedTypedArray, type ITypedArray, } from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,cAAc,EACd,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,iBAAiB,EACjB,MAAM,GACP,MAAM,UAAU,CAAC;AAClB,OAAO,EACL,qBAAqB,EACrB,cAAc,GAMf,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { CryptoDigestAlgorithm, ICryptoDigestOptions, IDigest, ITypedArray } from './types';
|
|
2
|
+
export type INativeCryptoModule = {
|
|
3
|
+
getRandomValues(typedArray: ITypedArray): void;
|
|
4
|
+
randomUUID(): string;
|
|
5
|
+
digest(algorithm: CryptoDigestAlgorithm, output: Uint8Array, data: BufferSource): void;
|
|
6
|
+
digestStringAsync?(algorithm: CryptoDigestAlgorithm, data: string, options: ICryptoDigestOptions): Promise<IDigest>;
|
|
7
|
+
digestAsync?(algorithm: CryptoDigestAlgorithm, data: BufferSource): Promise<ArrayBuffer>;
|
|
8
|
+
};
|
|
9
|
+
export declare const expoCrypto: INativeCryptoModule;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"native-module.js","sourceRoot":"","sources":["../../src/core/native-module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAIxD,MAAM,uBAAuB,GAAG,YAAY,CAAC;AAqB7C,MAAM,CAAC,MAAM,UAAU,GAAG,mBAAmB,CAAsB,uBAAuB,CAAC,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare enum CryptoDigestAlgorithm {
|
|
2
|
+
SHA1 = "SHA-1",
|
|
3
|
+
SHA256 = "SHA-256",
|
|
4
|
+
SHA384 = "SHA-384",
|
|
5
|
+
SHA512 = "SHA-512",
|
|
6
|
+
/** @platform ios */
|
|
7
|
+
MD2 = "MD2",
|
|
8
|
+
/** @platform ios */
|
|
9
|
+
MD4 = "MD4",
|
|
10
|
+
MD5 = "MD5"
|
|
11
|
+
}
|
|
12
|
+
export declare enum CryptoEncoding {
|
|
13
|
+
HEX = "hex",
|
|
14
|
+
/** Has trailing padding. Does not wrap lines. Does not have a trailing newline. */
|
|
15
|
+
BASE64 = "base64"
|
|
16
|
+
}
|
|
17
|
+
export type ICryptoDigestOptions = {
|
|
18
|
+
/** Format the digest is returned in. */
|
|
19
|
+
encoding: CryptoEncoding;
|
|
20
|
+
};
|
|
21
|
+
export type IDigest = string;
|
|
22
|
+
export type IUintBasedTypedArray = Uint8Array | Uint16Array | Uint32Array;
|
|
23
|
+
export type IIntBasedTypedArray = Int8Array | Int16Array | Int32Array;
|
|
24
|
+
export type ITypedArray = IUintBasedTypedArray | IIntBasedTypedArray;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export var CryptoDigestAlgorithm;
|
|
2
|
+
(function (CryptoDigestAlgorithm) {
|
|
3
|
+
CryptoDigestAlgorithm["SHA1"] = "SHA-1";
|
|
4
|
+
CryptoDigestAlgorithm["SHA256"] = "SHA-256";
|
|
5
|
+
CryptoDigestAlgorithm["SHA384"] = "SHA-384";
|
|
6
|
+
CryptoDigestAlgorithm["SHA512"] = "SHA-512";
|
|
7
|
+
/** @platform ios */
|
|
8
|
+
CryptoDigestAlgorithm["MD2"] = "MD2";
|
|
9
|
+
/** @platform ios */
|
|
10
|
+
CryptoDigestAlgorithm["MD4"] = "MD4";
|
|
11
|
+
CryptoDigestAlgorithm["MD5"] = "MD5";
|
|
12
|
+
})(CryptoDigestAlgorithm || (CryptoDigestAlgorithm = {}));
|
|
13
|
+
export var CryptoEncoding;
|
|
14
|
+
(function (CryptoEncoding) {
|
|
15
|
+
CryptoEncoding["HEX"] = "hex";
|
|
16
|
+
/** Has trailing padding. Does not wrap lines. Does not have a trailing newline. */
|
|
17
|
+
CryptoEncoding["BASE64"] = "base64";
|
|
18
|
+
})(CryptoEncoding || (CryptoEncoding = {}));
|
|
19
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,qBAUX;AAVD,WAAY,qBAAqB;IAC/B,uCAAc,CAAA;IACd,2CAAkB,CAAA;IAClB,2CAAkB,CAAA;IAClB,2CAAkB,CAAA;IAClB,oBAAoB;IACpB,oCAAW,CAAA;IACX,oBAAoB;IACpB,oCAAW,CAAA;IACX,oCAAW,CAAA;AACb,CAAC,EAVW,qBAAqB,KAArB,qBAAqB,QAUhC;AAED,MAAM,CAAN,IAAY,cAIX;AAJD,WAAY,cAAc;IACxB,6BAAW,CAAA;IACX,mFAAmF;IACnF,mCAAiB,CAAA;AACnB,CAAC,EAJW,cAAc,KAAd,cAAc,QAIzB"}
|
package/native-link.json
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@symbiote-native/crypto",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "expo-crypto wrapped for SymbioteNative — one framework-agnostic core, built once and reachable from the React, Vue, and Angular adapters. Cryptographically secure random bytes, randomUUID, and string/buffer digest hashing (SHA-1/256/384/512, MD2/MD4/MD5).",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/OneEyed1366/symbiote-native.git",
|
|
9
|
+
"directory": "packages/crypto"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/crypto#readme",
|
|
12
|
+
"bugs": {
|
|
13
|
+
"url": "https://github.com/OneEyed1366/symbiote-native/issues"
|
|
14
|
+
},
|
|
15
|
+
"author": "Andrey Prokopenko <psevdoproger@gmail.com>",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./build/core/index.js",
|
|
18
|
+
"module": "./build/core/index.js",
|
|
19
|
+
"types": "./build/core/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./build/core/index.d.ts",
|
|
23
|
+
"default": "./build/core/index.js"
|
|
24
|
+
},
|
|
25
|
+
"./vue": {
|
|
26
|
+
"types": "./build/vue/index.d.ts",
|
|
27
|
+
"default": "./build/vue/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./react": {
|
|
30
|
+
"types": "./build/react/index.d.ts",
|
|
31
|
+
"default": "./build/react/index.js"
|
|
32
|
+
},
|
|
33
|
+
"./angular": {
|
|
34
|
+
"types": "./build-ngc/angular/index.d.ts",
|
|
35
|
+
"react-native": "./build-ngc/angular/index.js",
|
|
36
|
+
"default": "./src/angular/index.ts"
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
"files": [
|
|
40
|
+
"src",
|
|
41
|
+
"build",
|
|
42
|
+
"build-ngc",
|
|
43
|
+
"native-link.json"
|
|
44
|
+
],
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"access": "public"
|
|
47
|
+
},
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"expo-crypto": "57.0.1",
|
|
50
|
+
"expo-modules-core": "57.0.5"
|
|
51
|
+
},
|
|
52
|
+
"peerDependencies": {
|
|
53
|
+
"@symbiote-native/engine": ">=0.1.7",
|
|
54
|
+
"@angular/core": ">=20",
|
|
55
|
+
"@vue/runtime-core": "^3.5.13",
|
|
56
|
+
"react": ">=19.0.0",
|
|
57
|
+
"react-native": ">=0.86",
|
|
58
|
+
"vue": ">=3.5.0",
|
|
59
|
+
"@symbiote-native/angular": "0.6.1",
|
|
60
|
+
"@symbiote-native/react": "0.2.8",
|
|
61
|
+
"@symbiote-native/vue": "0.3.8"
|
|
62
|
+
},
|
|
63
|
+
"peerDependenciesMeta": {
|
|
64
|
+
"@symbiote-native/angular": {
|
|
65
|
+
"optional": true
|
|
66
|
+
},
|
|
67
|
+
"@symbiote-native/react": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
70
|
+
"@symbiote-native/vue": {
|
|
71
|
+
"optional": true
|
|
72
|
+
},
|
|
73
|
+
"@angular/core": {
|
|
74
|
+
"optional": true
|
|
75
|
+
},
|
|
76
|
+
"@vue/runtime-core": {
|
|
77
|
+
"optional": true
|
|
78
|
+
},
|
|
79
|
+
"react": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"vue": {
|
|
83
|
+
"optional": true
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"devDependencies": {
|
|
87
|
+
"@angular/compiler": "^22",
|
|
88
|
+
"@angular/compiler-cli": "^22",
|
|
89
|
+
"@angular/core": "^22",
|
|
90
|
+
"@types/node": "^26.0.0",
|
|
91
|
+
"@types/react": "^19.2.0",
|
|
92
|
+
"@vue/runtime-core": "^3.5.13",
|
|
93
|
+
"react": "19.2.3",
|
|
94
|
+
"typescript": "~6.0.0",
|
|
95
|
+
"@symbiote-native/angular": "0.6.1",
|
|
96
|
+
"@symbiote-native/react": "0.2.8",
|
|
97
|
+
"@symbiote-native/vue": "0.3.8",
|
|
98
|
+
"@symbiote-native/test-utils": "0.1.6",
|
|
99
|
+
"@symbiote-native/engine": "0.1.7"
|
|
100
|
+
},
|
|
101
|
+
"scripts": {
|
|
102
|
+
"typecheck": "tsc --build",
|
|
103
|
+
"clean": "rm -rf build-ngc",
|
|
104
|
+
"ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
|
|
105
|
+
"format": "prettier --write \"src/**/*.{ts,tsx}\""
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
const FAKE_NATIVE_CRYPTO = {
|
|
4
|
+
getRandomValues: vi.fn((typedArray: Uint8Array) => {
|
|
5
|
+
typedArray.fill(7);
|
|
6
|
+
}),
|
|
7
|
+
randomUUID: vi.fn(() => '00000000-0000-4000-8000-000000000000'),
|
|
8
|
+
digestStringAsync: vi.fn(async () => 'fake-digest-hex'),
|
|
9
|
+
digestAsync: vi.fn(async () => new ArrayBuffer(32)),
|
|
10
|
+
digest: vi.fn((_algorithm: string, output: Uint8Array) => {
|
|
11
|
+
output.fill(9);
|
|
12
|
+
}),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// The real ExpoCrypto native module only exists on device — resolving it via
|
|
16
|
+
// requireNativeModule() at import time would throw in this headless test run, so the
|
|
17
|
+
// module-lookup file is faked in place of expo-modules-core's runtime resolution, the same
|
|
18
|
+
// pattern packages/local-auth/src/core/local-authentication.test.ts uses.
|
|
19
|
+
vi.mock('./native-module', () => ({
|
|
20
|
+
expoCrypto: FAKE_NATIVE_CRYPTO,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
// expo-modules-core's real entry transitively imports 'react-native' for Platform/
|
|
24
|
+
// TurboModuleRegistry, whose Flow-typed source Vitest's Oxc transform can't parse — same fake
|
|
25
|
+
// packages/local-auth/src/core/local-authentication.test.ts uses.
|
|
26
|
+
vi.mock('expo-modules-core', () => ({
|
|
27
|
+
UnavailabilityError: class UnavailabilityError extends Error {
|
|
28
|
+
constructor(moduleName: string, propertyName: string) {
|
|
29
|
+
super(`${propertyName} is not available on ${moduleName}`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
const {
|
|
35
|
+
getRandomBytes,
|
|
36
|
+
getRandomBytesAsync,
|
|
37
|
+
getRandomValues,
|
|
38
|
+
randomUUID,
|
|
39
|
+
digestStringAsync,
|
|
40
|
+
digest,
|
|
41
|
+
} = await import('./crypto');
|
|
42
|
+
const { CryptoDigestAlgorithm, CryptoEncoding } = await import('./types');
|
|
43
|
+
|
|
44
|
+
afterEach(() => {
|
|
45
|
+
vi.clearAllMocks();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
describe('getRandomBytes', () => {
|
|
49
|
+
it('accepts the lower bound (0)', () => {
|
|
50
|
+
const result = getRandomBytes(0);
|
|
51
|
+
expect(result).toHaveLength(0);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('accepts the upper bound (1024)', () => {
|
|
55
|
+
const result = getRandomBytes(1024);
|
|
56
|
+
expect(result).toHaveLength(1024);
|
|
57
|
+
expect(FAKE_NATIVE_CRYPTO.getRandomValues).toHaveBeenCalledTimes(1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('floors a fractional byteCount', () => {
|
|
61
|
+
const result = getRandomBytes(4.9);
|
|
62
|
+
expect(result).toHaveLength(4);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('rejects a negative byteCount', () => {
|
|
66
|
+
expect(() => getRandomBytes(-1)).toThrow(TypeError);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects a byteCount above 1024', () => {
|
|
70
|
+
expect(() => getRandomBytes(1025)).toThrow(TypeError);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it('rejects a non-number byteCount', () => {
|
|
74
|
+
// @ts-expect-error -- exercising the runtime guard against a caller ignoring the types
|
|
75
|
+
expect(() => getRandomBytes('16')).toThrow(TypeError);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('rejects NaN', () => {
|
|
79
|
+
expect(() => getRandomBytes(NaN)).toThrow(TypeError);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('throws an UnavailabilityError-shaped error when the native method is absent', () => {
|
|
83
|
+
const { getRandomValues: native } = FAKE_NATIVE_CRYPTO;
|
|
84
|
+
// @ts-expect-error -- simulating a platform where the native module has no such method
|
|
85
|
+
FAKE_NATIVE_CRYPTO.getRandomValues = undefined;
|
|
86
|
+
|
|
87
|
+
expect(() => getRandomBytes(4)).toThrow('getRandomBytes is not available on expo-crypto');
|
|
88
|
+
|
|
89
|
+
FAKE_NATIVE_CRYPTO.getRandomValues = native;
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('getRandomBytesAsync', () => {
|
|
94
|
+
it('accepts the lower and upper bound', async () => {
|
|
95
|
+
await expect(getRandomBytesAsync(0)).resolves.toHaveLength(0);
|
|
96
|
+
await expect(getRandomBytesAsync(1024)).resolves.toHaveLength(1024);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('rejects a negative byteCount', async () => {
|
|
100
|
+
await expect(getRandomBytesAsync(-1)).rejects.toThrow(TypeError);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it('rejects a byteCount above 1024', async () => {
|
|
104
|
+
await expect(getRandomBytesAsync(1025)).rejects.toThrow(TypeError);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('throws an UnavailabilityError-shaped error when the native method is absent', async () => {
|
|
108
|
+
const { getRandomValues: native } = FAKE_NATIVE_CRYPTO;
|
|
109
|
+
// @ts-expect-error -- simulating a platform where the native module has no such method
|
|
110
|
+
FAKE_NATIVE_CRYPTO.getRandomValues = undefined;
|
|
111
|
+
|
|
112
|
+
await expect(getRandomBytesAsync(4)).rejects.toThrow(
|
|
113
|
+
'getRandomBytesAsync is not available on expo-crypto',
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
FAKE_NATIVE_CRYPTO.getRandomValues = native;
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('getRandomValues', () => {
|
|
121
|
+
it('fills the provided typed array in place and returns it', () => {
|
|
122
|
+
const typedArray = new Uint8Array(4);
|
|
123
|
+
const result = getRandomValues(typedArray);
|
|
124
|
+
|
|
125
|
+
expect(result).toBe(typedArray);
|
|
126
|
+
expect(FAKE_NATIVE_CRYPTO.getRandomValues).toHaveBeenCalledWith(typedArray);
|
|
127
|
+
expect(Array.from(typedArray)).toEqual([7, 7, 7, 7]);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('randomUUID', () => {
|
|
132
|
+
it('returns the native UUID directly', () => {
|
|
133
|
+
expect(randomUUID()).toBe('00000000-0000-4000-8000-000000000000');
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
describe('digestStringAsync', () => {
|
|
138
|
+
it('defaults to HEX encoding when options are omitted', async () => {
|
|
139
|
+
await digestStringAsync(CryptoDigestAlgorithm.SHA256, 'hello');
|
|
140
|
+
|
|
141
|
+
expect(FAKE_NATIVE_CRYPTO.digestStringAsync).toHaveBeenCalledWith(
|
|
142
|
+
CryptoDigestAlgorithm.SHA256,
|
|
143
|
+
'hello',
|
|
144
|
+
{ encoding: CryptoEncoding.HEX },
|
|
145
|
+
);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('passes through a custom encoding', async () => {
|
|
149
|
+
await digestStringAsync(CryptoDigestAlgorithm.SHA1, 'hello', {
|
|
150
|
+
encoding: CryptoEncoding.BASE64,
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
expect(FAKE_NATIVE_CRYPTO.digestStringAsync).toHaveBeenCalledWith(
|
|
154
|
+
CryptoDigestAlgorithm.SHA1,
|
|
155
|
+
'hello',
|
|
156
|
+
{ encoding: CryptoEncoding.BASE64 },
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('resolves with the native digest', async () => {
|
|
161
|
+
await expect(digestStringAsync(CryptoDigestAlgorithm.MD5, 'hello')).resolves.toBe(
|
|
162
|
+
'fake-digest-hex',
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('rejects an invalid algorithm', async () => {
|
|
167
|
+
// @ts-expect-error -- exercising the runtime guard against an invalid algorithm value
|
|
168
|
+
await expect(digestStringAsync('NOT-A-REAL-ALGORITHM', 'hello')).rejects.toThrow(
|
|
169
|
+
'Invalid algorithm provided',
|
|
170
|
+
);
|
|
171
|
+
expect(FAKE_NATIVE_CRYPTO.digestStringAsync).not.toHaveBeenCalled();
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('rejects non-string data', async () => {
|
|
175
|
+
// @ts-expect-error -- exercising the runtime guard against non-string data
|
|
176
|
+
await expect(digestStringAsync(CryptoDigestAlgorithm.SHA256, 12345)).rejects.toThrow(
|
|
177
|
+
'Invalid data provided',
|
|
178
|
+
);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it('rejects an invalid encoding', async () => {
|
|
182
|
+
await expect(
|
|
183
|
+
// @ts-expect-error -- exercising the runtime guard against an invalid encoding value
|
|
184
|
+
digestStringAsync(CryptoDigestAlgorithm.SHA256, 'hello', { encoding: 'not-an-encoding' }),
|
|
185
|
+
).rejects.toThrow('Invalid encoding provided');
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('throws an UnavailabilityError-shaped error when the native method is absent', async () => {
|
|
189
|
+
const { digestStringAsync: native } = FAKE_NATIVE_CRYPTO;
|
|
190
|
+
// @ts-expect-error -- simulating a platform where the native module has no such method
|
|
191
|
+
FAKE_NATIVE_CRYPTO.digestStringAsync = undefined;
|
|
192
|
+
|
|
193
|
+
await expect(digestStringAsync(CryptoDigestAlgorithm.SHA256, 'hello')).rejects.toThrow(
|
|
194
|
+
'digestStringAsync is not available on expo-crypto',
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
FAKE_NATIVE_CRYPTO.digestStringAsync = native;
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe('digest', () => {
|
|
202
|
+
it('prefers digestAsync when the native module implements it', async () => {
|
|
203
|
+
const data = new Uint8Array([1, 2, 3]);
|
|
204
|
+
const result = await digest(CryptoDigestAlgorithm.SHA256, data);
|
|
205
|
+
|
|
206
|
+
expect(FAKE_NATIVE_CRYPTO.digestAsync).toHaveBeenCalledWith(CryptoDigestAlgorithm.SHA256, data);
|
|
207
|
+
expect(FAKE_NATIVE_CRYPTO.digest).not.toHaveBeenCalled();
|
|
208
|
+
expect(result).toBeInstanceOf(ArrayBuffer);
|
|
209
|
+
expect(result.byteLength).toBe(32);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it('falls back to the sync digest + digestLengths lookup table when digestAsync is absent', async () => {
|
|
213
|
+
const { digestAsync: native } = FAKE_NATIVE_CRYPTO;
|
|
214
|
+
// @ts-expect-error -- simulating a platform where the native module has no digestAsync
|
|
215
|
+
FAKE_NATIVE_CRYPTO.digestAsync = undefined;
|
|
216
|
+
|
|
217
|
+
const data = new Uint8Array([1, 2, 3]);
|
|
218
|
+
const result = await digest(CryptoDigestAlgorithm.SHA1, data);
|
|
219
|
+
|
|
220
|
+
expect(FAKE_NATIVE_CRYPTO.digest).toHaveBeenCalledTimes(1);
|
|
221
|
+
const [algorithm, output, passedData] = FAKE_NATIVE_CRYPTO.digest.mock.calls[0];
|
|
222
|
+
expect(algorithm).toBe(CryptoDigestAlgorithm.SHA1);
|
|
223
|
+
expect(output).toHaveLength(20); // SHA1's entry in digestLengths
|
|
224
|
+
expect(passedData).toBe(data);
|
|
225
|
+
expect(result.byteLength).toBe(20);
|
|
226
|
+
expect(Array.from(new Uint8Array(result))).toEqual(new Array(20).fill(9));
|
|
227
|
+
|
|
228
|
+
FAKE_NATIVE_CRYPTO.digestAsync = native;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('rejects when neither digestAsync nor digest is available', async () => {
|
|
232
|
+
const { digestAsync: nativeAsync, digest: nativeSync } = FAKE_NATIVE_CRYPTO;
|
|
233
|
+
// @ts-expect-error -- simulating a platform where the native module has neither method
|
|
234
|
+
FAKE_NATIVE_CRYPTO.digestAsync = undefined;
|
|
235
|
+
// @ts-expect-error -- simulating a platform where the native module has neither method
|
|
236
|
+
FAKE_NATIVE_CRYPTO.digest = undefined;
|
|
237
|
+
|
|
238
|
+
await expect(digest(CryptoDigestAlgorithm.SHA256, new Uint8Array([1]))).rejects.toThrow(
|
|
239
|
+
'digest is not available on expo-crypto',
|
|
240
|
+
);
|
|
241
|
+
|
|
242
|
+
FAKE_NATIVE_CRYPTO.digestAsync = nativeAsync;
|
|
243
|
+
FAKE_NATIVE_CRYPTO.digest = nativeSync;
|
|
244
|
+
});
|
|
245
|
+
});
|