@symbiote-native/crypto 0.0.1 → 0.1.0

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.
@@ -1,4 +1,4 @@
1
1
  // @symbiote-native/crypto/angular: the Angular entry over the framework-agnostic core. Same
2
2
  // reasoning as the React/Vue entries — no per-instance state or event stream to wrap in a
3
3
  // service, so this is a plain re-export.
4
- export * from '../core';
4
+ export * from '../core/index.js';
@@ -1,6 +1,6 @@
1
1
  import { UnavailabilityError } from 'expo-modules-core';
2
- import { expoCrypto } from './native-module';
3
- import { CryptoDigestAlgorithm, CryptoEncoding } from './types';
2
+ import { expoCrypto } from './native-module.js';
3
+ import { CryptoDigestAlgorithm, CryptoEncoding } from './types.js';
4
4
  const NATIVE_MODULE_NAME = 'expo-crypto';
5
5
  const MIN_BYTE_COUNT = 0;
6
6
  const MAX_BYTE_COUNT = 1024;
@@ -1,2 +1,2 @@
1
- export { CryptoError, getRandomBytes, getRandomBytesAsync, getRandomValues, randomUUID, digestStringAsync, digest, } from './crypto';
2
- export { CryptoDigestAlgorithm, CryptoEncoding, } from './types';
1
+ export { CryptoError, getRandomBytes, getRandomBytesAsync, getRandomValues, randomUUID, digestStringAsync, digest, } from './crypto.js';
2
+ export { CryptoDigestAlgorithm, CryptoEncoding, } from './types.js';
@@ -2,4 +2,4 @@
2
2
  // Upstream ships free sync/async functions and two enums, no per-instance state and no event
3
3
  // stream — there is nothing for a hook to wrap, so this is a plain re-export, mirroring
4
4
  // packages/local-auth/src/react/index.ts.
5
- export * from '../core';
5
+ export * from '../core/index.js';
@@ -1,4 +1,4 @@
1
1
  // @symbiote-native/crypto/vue: the Vue entry over the framework-agnostic core. Same reasoning
2
2
  // as the React entry — no per-instance state or event stream to wire onto Vue's reactivity, so
3
3
  // this is a plain re-export.
4
- export * from '../core';
4
+ export * from '../core/index.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbiote-native/crypto",
3
- "version": "0.0.1",
3
+ "version": "0.1.0",
4
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
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,7 +40,10 @@
40
40
  "src",
41
41
  "build",
42
42
  "build-ngc",
43
- "native-link.json"
43
+ "native-link.json",
44
+ "!src/**/*.test.*",
45
+ "!src/**/*.spec.*",
46
+ "!src/**/*.detox.*"
44
47
  ],
45
48
  "publishConfig": {
46
49
  "access": "public"
@@ -56,7 +59,7 @@
56
59
  "react": ">=19.0.0",
57
60
  "react-native": ">=0.86",
58
61
  "vue": ">=3.5.0",
59
- "@symbiote-native/angular": "0.6.1",
62
+ "@symbiote-native/angular": "0.6.2",
60
63
  "@symbiote-native/react": "0.2.8",
61
64
  "@symbiote-native/vue": "0.3.8"
62
65
  },
@@ -92,11 +95,11 @@
92
95
  "@vue/runtime-core": "^3.5.13",
93
96
  "react": "19.2.3",
94
97
  "typescript": "~6.0.0",
95
- "@symbiote-native/angular": "0.6.1",
98
+ "@symbiote-native/angular": "0.6.2",
99
+ "@symbiote-native/engine": "0.1.7",
96
100
  "@symbiote-native/react": "0.2.8",
97
- "@symbiote-native/vue": "0.3.8",
98
101
  "@symbiote-native/test-utils": "0.1.6",
99
- "@symbiote-native/engine": "0.1.7"
102
+ "@symbiote-native/vue": "0.3.8"
100
103
  },
101
104
  "scripts": {
102
105
  "typecheck": "tsc --build",
@@ -1,245 +0,0 @@
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
- });