@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.
Files changed (41) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +226 -0
  3. package/build/angular/index.d.ts +1 -0
  4. package/build/angular/index.js +4 -0
  5. package/build/core/crypto.d.ts +37 -0
  6. package/build/core/crypto.js +132 -0
  7. package/build/core/index.d.ts +2 -0
  8. package/build/core/index.js +2 -0
  9. package/build/core/native-module.d.ts +9 -0
  10. package/build/core/native-module.js +3 -0
  11. package/build/core/types.d.ts +24 -0
  12. package/build/core/types.js +18 -0
  13. package/build/react/index.d.ts +1 -0
  14. package/build/react/index.js +5 -0
  15. package/build/vue/index.d.ts +1 -0
  16. package/build/vue/index.js +4 -0
  17. package/build-ngc/angular/index.d.ts +1 -0
  18. package/build-ngc/angular/index.js +5 -0
  19. package/build-ngc/angular/index.js.map +1 -0
  20. package/build-ngc/core/crypto.d.ts +37 -0
  21. package/build-ngc/core/crypto.js +133 -0
  22. package/build-ngc/core/crypto.js.map +1 -0
  23. package/build-ngc/core/index.d.ts +2 -0
  24. package/build-ngc/core/index.js +3 -0
  25. package/build-ngc/core/index.js.map +1 -0
  26. package/build-ngc/core/native-module.d.ts +9 -0
  27. package/build-ngc/core/native-module.js +4 -0
  28. package/build-ngc/core/native-module.js.map +1 -0
  29. package/build-ngc/core/types.d.ts +24 -0
  30. package/build-ngc/core/types.js +19 -0
  31. package/build-ngc/core/types.js.map +1 -0
  32. package/native-link.json +12 -0
  33. package/package.json +107 -0
  34. package/src/angular/index.ts +4 -0
  35. package/src/core/crypto.test.ts +245 -0
  36. package/src/core/crypto.ts +165 -0
  37. package/src/core/index.ts +18 -0
  38. package/src/core/native-module.ts +26 -0
  39. package/src/core/types.ts +32 -0
  40. package/src/react/index.ts +5 -0
  41. package/src/vue/index.ts +4 -0
@@ -0,0 +1,165 @@
1
+ import { UnavailabilityError } from 'expo-modules-core';
2
+
3
+ import { expoCrypto } from './native-module';
4
+ import { CryptoDigestAlgorithm, CryptoEncoding } from './types';
5
+ import type { ICryptoDigestOptions, IDigest, ITypedArray } from './types';
6
+
7
+ const NATIVE_MODULE_NAME = 'expo-crypto';
8
+ const MIN_BYTE_COUNT = 0;
9
+ const MAX_BYTE_COUNT = 1024;
10
+
11
+ export class CryptoError extends TypeError {
12
+ code = 'ERR_CRYPTO';
13
+
14
+ constructor(message: string) {
15
+ super(`expo-crypto: ${message}`);
16
+ }
17
+ }
18
+
19
+ function assertByteCount(value: number, methodName: string): void {
20
+ if (
21
+ typeof value !== 'number' ||
22
+ Number.isNaN(value) ||
23
+ Math.floor(value) < MIN_BYTE_COUNT ||
24
+ Math.floor(value) > MAX_BYTE_COUNT
25
+ ) {
26
+ throw new TypeError(
27
+ `expo-crypto: ${methodName}(${value}) expected a valid number from range ${MIN_BYTE_COUNT}...${MAX_BYTE_COUNT}`,
28
+ );
29
+ }
30
+ }
31
+
32
+ function assertAlgorithm(algorithm: CryptoDigestAlgorithm): void {
33
+ if (!Object.values(CryptoDigestAlgorithm).includes(algorithm)) {
34
+ throw new CryptoError(
35
+ `Invalid algorithm provided. Expected one of: CryptoDigestAlgorithm.${Object.keys(
36
+ CryptoDigestAlgorithm,
37
+ ).join(', CryptoDigestAlgorithm.')}`,
38
+ );
39
+ }
40
+ }
41
+
42
+ function assertData(data: string): void {
43
+ if (typeof data !== 'string') {
44
+ throw new CryptoError('Invalid data provided. Expected a string.');
45
+ }
46
+ }
47
+
48
+ function assertEncoding(encoding: CryptoEncoding): void {
49
+ if (!Object.values(CryptoEncoding).includes(encoding)) {
50
+ throw new CryptoError(
51
+ `Invalid encoding provided. Expected one of: CryptoEncoding.${Object.keys(
52
+ CryptoEncoding,
53
+ ).join(', CryptoEncoding.')}`,
54
+ );
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Generates completely random bytes using native implementations. `byteCount` must be within
60
+ * `0`-`1024` (inclusive), anything else throws a `TypeError`.
61
+ */
62
+ export function getRandomBytes(byteCount: number): Uint8Array {
63
+ assertByteCount(byteCount, 'getRandomBytes');
64
+ const validByteCount = Math.floor(byteCount);
65
+ if (!expoCrypto.getRandomValues) {
66
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getRandomBytes');
67
+ }
68
+ const byteArray = new Uint8Array(validByteCount);
69
+ expoCrypto.getRandomValues(byteArray);
70
+ return byteArray;
71
+ }
72
+
73
+ /**
74
+ * Generates completely random bytes using native implementations. `byteCount` must be within
75
+ * `0`-`1024` (inclusive), anything else throws a `TypeError`.
76
+ */
77
+ export async function getRandomBytesAsync(byteCount: number): Promise<Uint8Array> {
78
+ assertByteCount(byteCount, 'getRandomBytesAsync');
79
+ const validByteCount = Math.floor(byteCount);
80
+ if (!expoCrypto.getRandomValues) {
81
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getRandomBytesAsync');
82
+ }
83
+ const byteArray = new Uint8Array(validByteCount);
84
+ expoCrypto.getRandomValues(byteArray);
85
+ return byteArray;
86
+ }
87
+
88
+ /**
89
+ * Fills the provided `TypedArray` with cryptographically secure random values in place, and
90
+ * returns it.
91
+ */
92
+ export function getRandomValues<T extends ITypedArray>(typedArray: T): T {
93
+ if (!expoCrypto.getRandomValues) {
94
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'getRandomValues');
95
+ }
96
+ expoCrypto.getRandomValues(typedArray);
97
+ return typedArray;
98
+ }
99
+
100
+ /**
101
+ * Returns a unique identifier based on the V4 UUID spec (RFC4122), using cryptographically
102
+ * secure random values.
103
+ */
104
+ export function randomUUID(): string {
105
+ if (!expoCrypto.randomUUID) {
106
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'randomUUID');
107
+ }
108
+ return expoCrypto.randomUUID();
109
+ }
110
+
111
+ /**
112
+ * Generates a digest of the supplied `data` string with the provided digest `algorithm`,
113
+ * formatted as one of `CryptoEncoding` (defaults to `HEX`).
114
+ */
115
+ export async function digestStringAsync(
116
+ algorithm: CryptoDigestAlgorithm,
117
+ data: string,
118
+ options: ICryptoDigestOptions = { encoding: CryptoEncoding.HEX },
119
+ ): Promise<IDigest> {
120
+ if (!expoCrypto.digestStringAsync) {
121
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'digestStringAsync');
122
+ }
123
+
124
+ assertAlgorithm(algorithm);
125
+ assertData(data);
126
+ assertEncoding(options.encoding);
127
+
128
+ return expoCrypto.digestStringAsync(algorithm, data, options);
129
+ }
130
+
131
+ // Fixed output length (in bytes) per digest algorithm — used only as the sync fallback's
132
+ // preallocated output buffer size when the native module has no digestAsync.
133
+ const digestLengths: Record<CryptoDigestAlgorithm, number> = {
134
+ [CryptoDigestAlgorithm.SHA1]: 20,
135
+ [CryptoDigestAlgorithm.SHA256]: 32,
136
+ [CryptoDigestAlgorithm.SHA384]: 48,
137
+ [CryptoDigestAlgorithm.SHA512]: 64,
138
+ [CryptoDigestAlgorithm.MD2]: 16,
139
+ [CryptoDigestAlgorithm.MD4]: 16,
140
+ [CryptoDigestAlgorithm.MD5]: 16,
141
+ };
142
+
143
+ /**
144
+ * Generates a digest of the supplied `TypedArray`/`ArrayBuffer` of bytes with the provided digest
145
+ * `algorithm`. Prefers the native async `digestAsync` when present; otherwise falls back to
146
+ * allocating a fixed-size output buffer (sized via `digestLengths`) and the sync `digest`.
147
+ */
148
+ export function digest(algorithm: CryptoDigestAlgorithm, data: BufferSource): Promise<ArrayBuffer> {
149
+ return new Promise((resolve, reject) => {
150
+ try {
151
+ if (typeof expoCrypto.digestAsync === 'function') {
152
+ resolve(expoCrypto.digestAsync(algorithm, data));
153
+ return;
154
+ }
155
+ if (!expoCrypto.digest) {
156
+ throw new UnavailabilityError(NATIVE_MODULE_NAME, 'digest');
157
+ }
158
+ const output = new Uint8Array(digestLengths[algorithm]);
159
+ expoCrypto.digest(algorithm, output, data);
160
+ resolve(output.buffer);
161
+ } catch (error) {
162
+ reject(error);
163
+ }
164
+ });
165
+ }
@@ -0,0 +1,18 @@
1
+ export {
2
+ CryptoError,
3
+ getRandomBytes,
4
+ getRandomBytesAsync,
5
+ getRandomValues,
6
+ randomUUID,
7
+ digestStringAsync,
8
+ digest,
9
+ } from './crypto';
10
+ export {
11
+ CryptoDigestAlgorithm,
12
+ CryptoEncoding,
13
+ type ICryptoDigestOptions,
14
+ type IDigest,
15
+ type IUintBasedTypedArray,
16
+ type IIntBasedTypedArray,
17
+ type ITypedArray,
18
+ } from './types';
@@ -0,0 +1,26 @@
1
+ import { requireNativeModule } from 'expo-modules-core';
2
+
3
+ import type { CryptoDigestAlgorithm, ICryptoDigestOptions, IDigest, ITypedArray } from './types';
4
+
5
+ const EXPO_CRYPTO_MODULE_NAME = 'ExpoCrypto';
6
+
7
+ // getRandomValues/randomUUID/digest are unconditionally implemented on both iOS and Android
8
+ // (verified against .vendors/expo's CryptoModule.swift/CryptoModule.kt at sdk-57 — both
9
+ // register the same `Name("ExpoCrypto")` with those three, plus digestStringAsync). digestAsync
10
+ // is the one method genuinely absent from the native side today — upstream's own `digest()`
11
+ // feature-detects it (`typeof ExpoCrypto.digestAsync === 'function'`) and falls back to the sync
12
+ // `digest` otherwise, so only it (and digestStringAsync, guarded the same way upstream guards it)
13
+ // stay optional here.
14
+ export type INativeCryptoModule = {
15
+ getRandomValues(typedArray: ITypedArray): void;
16
+ randomUUID(): string;
17
+ digest(algorithm: CryptoDigestAlgorithm, output: Uint8Array, data: BufferSource): void;
18
+ digestStringAsync?(
19
+ algorithm: CryptoDigestAlgorithm,
20
+ data: string,
21
+ options: ICryptoDigestOptions,
22
+ ): Promise<IDigest>;
23
+ digestAsync?(algorithm: CryptoDigestAlgorithm, data: BufferSource): Promise<ArrayBuffer>;
24
+ };
25
+
26
+ export const expoCrypto = requireNativeModule<INativeCryptoModule>(EXPO_CRYPTO_MODULE_NAME);
@@ -0,0 +1,32 @@
1
+ export 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
+
13
+ export enum CryptoEncoding {
14
+ HEX = 'hex',
15
+ /** Has trailing padding. Does not wrap lines. Does not have a trailing newline. */
16
+ BASE64 = 'base64',
17
+ }
18
+
19
+ export type ICryptoDigestOptions = {
20
+ /** Format the digest is returned in. */
21
+ encoding: CryptoEncoding;
22
+ };
23
+
24
+ export type IDigest = string;
25
+
26
+ // expo-modules-core exports the equivalent constraint as `UintBasedTypedArray | IntBasedTypedArray`
27
+ // off its own internal typed-array module; we never depend on the `expo` meta-package's re-export
28
+ // path for it (see the symbiote-expo-native-module skill), so this is our own precise union over
29
+ // the standard integer TypedArrays instead.
30
+ export type IUintBasedTypedArray = Uint8Array | Uint16Array | Uint32Array;
31
+ export type IIntBasedTypedArray = Int8Array | Int16Array | Int32Array;
32
+ export type ITypedArray = IUintBasedTypedArray | IIntBasedTypedArray;
@@ -0,0 +1,5 @@
1
+ // @symbiote-native/crypto/react: the React entry over the framework-agnostic core.
2
+ // Upstream ships free sync/async functions and two enums, no per-instance state and no event
3
+ // stream — there is nothing for a hook to wrap, so this is a plain re-export, mirroring
4
+ // packages/local-auth/src/react/index.ts.
5
+ export * from '../core';
@@ -0,0 +1,4 @@
1
+ // @symbiote-native/crypto/vue: the Vue entry over the framework-agnostic core. Same reasoning
2
+ // as the React entry — no per-instance state or event stream to wire onto Vue's reactivity, so
3
+ // this is a plain re-export.
4
+ export * from '../core';