@symbiote-native/standard-web-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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 A. Prokopenko
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,95 @@
1
+ # @symbiote-native/standard-web-crypto
2
+
3
+ A wrapper package for [SymbioteNative](../../README.md) that makes
4
+ [`expo-standard-web-crypto`](https://github.com/expo/expo/tree/main/packages/expo-standard-web-crypto)
5
+ — a partial W3C [Web Crypto API](https://www.w3.org/TR/WebCryptoAPI/) polyfill exposing
6
+ `crypto.getRandomValues` — usable from **every** adapter, React, Vue, and Angular, not just React.
7
+ Unlike this repo's other Expo ports, this package needs **no native module of its own** — upstream
8
+ is a ~15-line pure-JS shim that delegates straight to `expo-crypto`'s own `getRandomValues`; this
9
+ port delegates to [`@symbiote-native/crypto`](../crypto)'s `getRandomValues` instead, since this
10
+ repo already ships that native random source as a sibling package. Like `@symbiote-native/crypto`,
11
+ every export here is a plain function/object with no per-instance state or event stream, so there
12
+ is no hook/composable/service to wrap — the React, Vue, and Angular entry points are plain
13
+ re-exports of the same `core`.
14
+
15
+ ## Install
16
+
17
+ ```bash
18
+ npm install @symbiote-native/standard-web-crypto @symbiote-native/crypto
19
+ ```
20
+
21
+ `@symbiote-native/crypto` comes along as a regular dependency and does the actual native random-byte
22
+ generation — see [its README](../crypto/README.md) for `expo-crypto`'s own native autolinking
23
+ requirements (already satisfied in any app that already wires up `@symbiote-native/crypto` or
24
+ `@symbiote-native/device`).
25
+
26
+ No further native wiring is needed for this package itself — it has no native module of its own.
27
+
28
+ ## Shape
29
+
30
+ ```
31
+ src/core/ web-crypto.ts — the Crypto class + webCrypto singleton (default export) and
32
+ polyfillWebCrypto(), delegating to @symbiote-native/crypto's getRandomValues.
33
+ src/react/ @symbiote-native/standard-web-crypto/react — export * from '../core'
34
+ src/vue/ @symbiote-native/standard-web-crypto/vue — export * from '../core'
35
+ src/angular/ @symbiote-native/standard-web-crypto/angular — export * from '../core'
36
+ ```
37
+
38
+ No per-adapter lifecycle wrapper exists because there's nothing to subscribe to or clean up —
39
+ each adapter entry is a single-file re-export.
40
+
41
+ ## Use it
42
+
43
+ ```ts
44
+ import webCrypto, { polyfillWebCrypto } from '@symbiote-native/standard-web-crypto';
45
+
46
+ // Reach the polyfill (or a real globalThis.crypto, if one already exists) directly:
47
+ const bytes = new Uint8Array(16);
48
+ webCrypto.getRandomValues(bytes);
49
+
50
+ // Or install it as globalThis.crypto for any library that expects the Web Crypto API to
51
+ // already be present — a no-op if globalThis.crypto is already defined:
52
+ polyfillWebCrypto();
53
+ crypto.getRandomValues(bytes);
54
+ ```
55
+
56
+ The React/Vue/Angular entry points re-export the identical surface:
57
+
58
+ ```ts
59
+ import webCrypto, { polyfillWebCrypto } from '@symbiote-native/standard-web-crypto/react';
60
+ import webCrypto, { polyfillWebCrypto } from '@symbiote-native/standard-web-crypto/vue';
61
+ import webCrypto, { polyfillWebCrypto } from '@symbiote-native/standard-web-crypto/angular';
62
+ ```
63
+
64
+ ## API
65
+
66
+ ```ts
67
+ const webCrypto: {
68
+ getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray;
69
+ };
70
+ export default webCrypto;
71
+
72
+ function polyfillWebCrypto(): void;
73
+ ```
74
+
75
+ Plus the `IWebCrypto` type describing the shape above.
76
+
77
+ ## Notes
78
+
79
+ - **`webCrypto` is resolved once, at module-load time**: if `globalThis.crypto` already exists
80
+ (a real Web Crypto implementation), `webCrypto` *is* that object; otherwise it's this package's
81
+ own `Crypto` class instance backed by `@symbiote-native/crypto`. React Native has no reliable
82
+ `window` global, so this port checks/defines `globalThis.crypto` rather than upstream's `window`.
83
+ - **`getRandomValues` only accepts the integer TypedArrays `@symbiote-native/crypto` can hand to
84
+ its native module** (`Int8Array`/`Uint8Array`/`Int16Array`/`Uint16Array`/`Int32Array`/
85
+ `Uint32Array`) — passing any other `ArrayBufferView` (a `DataView`, `Uint8ClampedArray`, a
86
+ `Float32Array`, …) throws a `TypeError`, the same way upstream's own `getRandomValues` rejects an
87
+ unsupported view.
88
+ - `polyfillWebCrypto()` is a no-op when `globalThis.crypto` is already defined — it never
89
+ overwrites an existing implementation.
90
+
91
+ ## Test it
92
+
93
+ Every export here is a pure function/object surface, never a view or per-instance state — no
94
+ Fabric/Descriptor angle at all. Tests mock `@symbiote-native/crypto`'s `getRandomValues` directly
95
+ (`src/core/web-crypto.test.ts`, `vitest`) — no `installFabric()`, no ViewConfig.
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/standard-web-crypto/angular: the Angular entry over the framework-agnostic
2
+ // core. Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in
3
+ // a service, so this is a plain re-export. `export *` never forwards a default export (ES module
4
+ // semantics), so `webCrypto` needs its own explicit named re-export alongside it.
5
+ export * from '../core';
6
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,2 @@
1
+ export { default, polyfillWebCrypto } from './web-crypto';
2
+ export type { IWebCrypto } from './web-crypto';
@@ -0,0 +1 @@
1
+ export { default, polyfillWebCrypto } from './web-crypto';
@@ -0,0 +1,6 @@
1
+ export type IWebCrypto = {
2
+ getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray;
3
+ };
4
+ declare const webCrypto: IWebCrypto;
5
+ export default webCrypto;
6
+ export declare function polyfillWebCrypto(): void;
@@ -0,0 +1,53 @@
1
+ // Ported from expo-standard-web-crypto (upstream: packages/expo-standard-web-crypto/src/index.ts,
2
+ // verified identical on origin/main and origin/sdk-57) — a partial W3C Web Crypto API polyfill
3
+ // exposing `crypto.getRandomValues`. Upstream delegates to expo-crypto's own getRandomValues; this
4
+ // port delegates to @symbiote-native/crypto's getRandomValues instead, which already wraps the
5
+ // same native random source, so there is no native call left to re-wrap here. React Native has no
6
+ // reliable `window` global, so `globalThis` replaces every `window`/bare `crypto` reference
7
+ // upstream used.
8
+ import { getRandomValues as getNativeRandomValues } from '@symbiote-native/crypto';
9
+ // @symbiote-native/crypto's getRandomValues only accepts the integer TypedArrays it can hand to
10
+ // the native module (ITypedArray) — narrower than the W3C spec's ArrayBufferView. This guard
11
+ // narrows the incoming view at the one call site that needs it, so the class below can still
12
+ // expose the full ArrayBufferView-typed method the upstream Crypto class does.
13
+ function isSupportedTypedArray(value) {
14
+ return (value instanceof Int8Array ||
15
+ value instanceof Uint8Array ||
16
+ value instanceof Int16Array ||
17
+ value instanceof Uint16Array ||
18
+ value instanceof Int32Array ||
19
+ value instanceof Uint32Array);
20
+ }
21
+ class Crypto {
22
+ getRandomValues(values) {
23
+ if (!isSupportedTypedArray(values)) {
24
+ throw new TypeError('The provided ArrayBuffer view is not a supported integer-typed array');
25
+ }
26
+ return getNativeRandomValues(values);
27
+ }
28
+ }
29
+ function isWebCrypto(value) {
30
+ if (typeof value !== 'object' || value === null) {
31
+ return false;
32
+ }
33
+ return 'getRandomValues' in value && typeof value.getRandomValues === 'function';
34
+ }
35
+ // No ambient `declare global { var crypto }` here on purpose — it would collide with lib.dom.d.ts's
36
+ // own `crypto` global the moment any consumer's tsconfig pulls in the DOM lib (as this package's
37
+ // own Angular build does), producing a duplicate-declaration error. Reflect.get sidesteps that:
38
+ // it reads an untyped global property without asserting one into existence.
39
+ function readGlobalCrypto() {
40
+ const value = Reflect.get(globalThis, 'crypto');
41
+ return isWebCrypto(value) ? value : undefined;
42
+ }
43
+ const webCrypto = readGlobalCrypto() ?? new Crypto();
44
+ export default webCrypto;
45
+ export function polyfillWebCrypto() {
46
+ if (readGlobalCrypto() === undefined) {
47
+ Object.defineProperty(globalThis, 'crypto', {
48
+ configurable: true,
49
+ enumerable: true,
50
+ get: () => webCrypto,
51
+ });
52
+ }
53
+ }
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/standard-web-crypto/react: the React entry over the framework-agnostic core.
2
+ // The polyfill has no per-instance state or event stream — nothing for a hook to wrap — so this
3
+ // is a plain re-export, mirroring @symbiote-native/device's and @symbiote-native/crypto's React
4
+ // entries. `export *` never forwards a default export (ES module semantics), so `webCrypto` needs
5
+ // its own explicit named re-export alongside it.
6
+ export * from '../core';
7
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/standard-web-crypto/vue: the Vue entry over the framework-agnostic core. Same
2
+ // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
+ // reactivity, so this is a plain re-export. `export *` never forwards a default export (ES module
4
+ // semantics), so `webCrypto` needs its own explicit named re-export alongside it.
5
+ export * from '../core';
6
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,2 @@
1
+ export * from '../core';
2
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/standard-web-crypto/angular: the Angular entry over the framework-agnostic
2
+ // core. Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in
3
+ // a service, so this is a plain re-export. `export *` never forwards a default export (ES module
4
+ // semantics), so `webCrypto` needs its own explicit named re-export alongside it.
5
+ export * from '../core';
6
+ export { default as webCrypto } from '../core';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/angular/index.ts"],"names":[],"mappings":"AAAA,8FAA8F;AAC9F,mGAAmG;AACnG,iGAAiG;AACjG,kFAAkF;AAClF,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,SAAS,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { default, polyfillWebCrypto } from './web-crypto';
2
+ export type { IWebCrypto } from './web-crypto';
@@ -0,0 +1,2 @@
1
+ export { default, polyfillWebCrypto } from './web-crypto';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/core/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC"}
@@ -0,0 +1,6 @@
1
+ export type IWebCrypto = {
2
+ getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray;
3
+ };
4
+ declare const webCrypto: IWebCrypto;
5
+ export default webCrypto;
6
+ export declare function polyfillWebCrypto(): void;
@@ -0,0 +1,54 @@
1
+ // Ported from expo-standard-web-crypto (upstream: packages/expo-standard-web-crypto/src/index.ts,
2
+ // verified identical on origin/main and origin/sdk-57) — a partial W3C Web Crypto API polyfill
3
+ // exposing `crypto.getRandomValues`. Upstream delegates to expo-crypto's own getRandomValues; this
4
+ // port delegates to @symbiote-native/crypto's getRandomValues instead, which already wraps the
5
+ // same native random source, so there is no native call left to re-wrap here. React Native has no
6
+ // reliable `window` global, so `globalThis` replaces every `window`/bare `crypto` reference
7
+ // upstream used.
8
+ import { getRandomValues as getNativeRandomValues } from '@symbiote-native/crypto';
9
+ // @symbiote-native/crypto's getRandomValues only accepts the integer TypedArrays it can hand to
10
+ // the native module (ITypedArray) — narrower than the W3C spec's ArrayBufferView. This guard
11
+ // narrows the incoming view at the one call site that needs it, so the class below can still
12
+ // expose the full ArrayBufferView-typed method the upstream Crypto class does.
13
+ function isSupportedTypedArray(value) {
14
+ return (value instanceof Int8Array ||
15
+ value instanceof Uint8Array ||
16
+ value instanceof Int16Array ||
17
+ value instanceof Uint16Array ||
18
+ value instanceof Int32Array ||
19
+ value instanceof Uint32Array);
20
+ }
21
+ class Crypto {
22
+ getRandomValues(values) {
23
+ if (!isSupportedTypedArray(values)) {
24
+ throw new TypeError('The provided ArrayBuffer view is not a supported integer-typed array');
25
+ }
26
+ return getNativeRandomValues(values);
27
+ }
28
+ }
29
+ function isWebCrypto(value) {
30
+ if (typeof value !== 'object' || value === null) {
31
+ return false;
32
+ }
33
+ return 'getRandomValues' in value && typeof value.getRandomValues === 'function';
34
+ }
35
+ // No ambient `declare global { var crypto }` here on purpose — it would collide with lib.dom.d.ts's
36
+ // own `crypto` global the moment any consumer's tsconfig pulls in the DOM lib (as this package's
37
+ // own Angular build does), producing a duplicate-declaration error. Reflect.get sidesteps that:
38
+ // it reads an untyped global property without asserting one into existence.
39
+ function readGlobalCrypto() {
40
+ const value = Reflect.get(globalThis, 'crypto');
41
+ return isWebCrypto(value) ? value : undefined;
42
+ }
43
+ const webCrypto = readGlobalCrypto() ?? new Crypto();
44
+ export default webCrypto;
45
+ export function polyfillWebCrypto() {
46
+ if (readGlobalCrypto() === undefined) {
47
+ Object.defineProperty(globalThis, 'crypto', {
48
+ configurable: true,
49
+ enumerable: true,
50
+ get: () => webCrypto,
51
+ });
52
+ }
53
+ }
54
+ //# sourceMappingURL=web-crypto.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web-crypto.js","sourceRoot":"","sources":["../../src/core/web-crypto.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,+FAA+F;AAC/F,mGAAmG;AACnG,+FAA+F;AAC/F,kGAAkG;AAClG,4FAA4F;AAC5F,iBAAiB;AACjB,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAOnF,gGAAgG;AAChG,6FAA6F;AAC7F,6FAA6F;AAC7F,+EAA+E;AAC/E,SAAS,qBAAqB,CAAC,KAAsB;IACnD,OAAO,CACL,KAAK,YAAY,SAAS;QAC1B,KAAK,YAAY,UAAU;QAC3B,KAAK,YAAY,UAAU;QAC3B,KAAK,YAAY,WAAW;QAC5B,KAAK,YAAY,UAAU;QAC3B,KAAK,YAAY,WAAW,CAC7B,CAAC;AACJ,CAAC;AAED,MAAM,MAAM;IACV,eAAe,CAAiC,MAAc;QAC5D,IAAI,CAAC,qBAAqB,CAAC,MAAM,CAAC,EAAE,CAAC;YACnC,MAAM,IAAI,SAAS,CAAC,sEAAsE,CAAC,CAAC;QAC9F,CAAC;QACD,OAAO,qBAAqB,CAAC,MAAM,CAAC,CAAC;IACvC,CAAC;CACF;AAED,SAAS,WAAW,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,iBAAiB,IAAI,KAAK,IAAI,OAAO,KAAK,CAAC,eAAe,KAAK,UAAU,CAAC;AACnF,CAAC;AAED,oGAAoG;AACpG,iGAAiG;AACjG,gGAAgG;AAChG,4EAA4E;AAC5E,SAAS,gBAAgB;IACvB,MAAM,KAAK,GAAY,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IACzD,OAAO,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AAChD,CAAC;AAED,MAAM,SAAS,GAAe,gBAAgB,EAAE,IAAI,IAAI,MAAM,EAAE,CAAC;AAEjE,eAAe,SAAS,CAAC;AAEzB,MAAM,UAAU,iBAAiB;IAC/B,IAAI,gBAAgB,EAAE,KAAK,SAAS,EAAE,CAAC;QACrC,MAAM,CAAC,cAAc,CAAC,UAAU,EAAE,QAAQ,EAAE;YAC1C,YAAY,EAAE,IAAI;YAClB,UAAU,EAAE,IAAI;YAChB,GAAG,EAAE,GAAG,EAAE,CAAC,SAAS;SACrB,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,105 @@
1
+ {
2
+ "name": "@symbiote-native/standard-web-crypto",
3
+ "version": "0.0.1",
4
+ "description": "expo-standard-web-crypto ported for SymbioteNative — a Web Crypto API getRandomValues polyfill built on @symbiote-native/crypto's native random source.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/OneEyed1366/symbiote-native.git",
9
+ "directory": "packages/standard-web-crypto"
10
+ },
11
+ "homepage": "https://github.com/OneEyed1366/symbiote-native/tree/master/packages/standard-web-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
+ ],
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "dependencies": {
48
+ "@symbiote-native/crypto": "0.0.1"
49
+ },
50
+ "peerDependencies": {
51
+ "@symbiote-native/engine": ">=0.1.7",
52
+ "@angular/core": ">=20",
53
+ "@vue/runtime-core": "^3.5.13",
54
+ "react": ">=19.0.0",
55
+ "react-native": ">=0.86",
56
+ "vue": ">=3.5.0",
57
+ "@symbiote-native/angular": "0.6.1",
58
+ "@symbiote-native/react": "0.2.8",
59
+ "@symbiote-native/vue": "0.3.8"
60
+ },
61
+ "peerDependenciesMeta": {
62
+ "@symbiote-native/angular": {
63
+ "optional": true
64
+ },
65
+ "@symbiote-native/react": {
66
+ "optional": true
67
+ },
68
+ "@symbiote-native/vue": {
69
+ "optional": true
70
+ },
71
+ "@angular/core": {
72
+ "optional": true
73
+ },
74
+ "@vue/runtime-core": {
75
+ "optional": true
76
+ },
77
+ "react": {
78
+ "optional": true
79
+ },
80
+ "vue": {
81
+ "optional": true
82
+ }
83
+ },
84
+ "devDependencies": {
85
+ "@angular/compiler": "^22",
86
+ "@angular/compiler-cli": "^22",
87
+ "@angular/core": "^22",
88
+ "@types/node": "^26.0.0",
89
+ "@types/react": "^19.2.0",
90
+ "@vue/runtime-core": "^3.5.13",
91
+ "react": "19.2.3",
92
+ "typescript": "~6.0.0",
93
+ "@symbiote-native/test-utils": "0.1.6",
94
+ "@symbiote-native/engine": "0.1.7",
95
+ "@symbiote-native/react": "0.2.8",
96
+ "@symbiote-native/angular": "0.6.1",
97
+ "@symbiote-native/vue": "0.3.8"
98
+ },
99
+ "scripts": {
100
+ "typecheck": "tsc --build",
101
+ "clean": "rm -rf build-ngc",
102
+ "ng:build": "pnpm run clean && ngc -p tsconfig.angular.json",
103
+ "format": "prettier --write \"src/**/*.{ts,tsx}\""
104
+ }
105
+ }
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/standard-web-crypto/angular: the Angular entry over the framework-agnostic
2
+ // core. Same reasoning as the React/Vue entries — no per-instance state or event stream to wrap in
3
+ // a service, so this is a plain re-export. `export *` never forwards a default export (ES module
4
+ // semantics), so `webCrypto` needs its own explicit named re-export alongside it.
5
+ export * from '../core';
6
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,2 @@
1
+ export { default, polyfillWebCrypto } from './web-crypto';
2
+ export type { IWebCrypto } from './web-crypto';
@@ -0,0 +1,91 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2
+
3
+ const getRandomValues = vi.fn((typedArray: Uint8Array) => {
4
+ typedArray.fill(7);
5
+ return typedArray;
6
+ });
7
+
8
+ // @symbiote-native/crypto resolves the real native module via requireNativeModule() at import
9
+ // time, which would throw in this headless test run — faked here in place of the sibling
10
+ // package, the same pattern packages/crypto/src/core/crypto.test.ts uses for expo-modules-core.
11
+ vi.mock('@symbiote-native/crypto', () => ({
12
+ getRandomValues,
13
+ }));
14
+
15
+ // Node (unlike React Native) ships its own native `globalThis.crypto` — deleted before every test
16
+ // so `web-crypto.ts`'s module-load-time feature detection sees the same "no crypto global" state
17
+ // it would on-device, not Node's own WebCrypto implementation.
18
+ beforeEach(() => {
19
+ Reflect.deleteProperty(globalThis, 'crypto');
20
+ });
21
+
22
+ afterEach(() => {
23
+ vi.clearAllMocks();
24
+ Reflect.deleteProperty(globalThis, 'crypto');
25
+ vi.resetModules();
26
+ });
27
+
28
+ describe('webCrypto.getRandomValues', () => {
29
+ it('delegates a supported typed array to @symbiote-native/crypto', async () => {
30
+ const { default: webCrypto } = await import('./web-crypto');
31
+ const typedArray = new Uint8Array(4);
32
+
33
+ const result = webCrypto.getRandomValues(typedArray);
34
+
35
+ expect(result).toBe(typedArray);
36
+ expect(getRandomValues).toHaveBeenCalledWith(typedArray);
37
+ expect(Array.from(typedArray)).toEqual([7, 7, 7, 7]);
38
+ });
39
+
40
+ it('throws a TypeError for a view outside the supported integer TypedArrays', async () => {
41
+ const { default: webCrypto } = await import('./web-crypto');
42
+
43
+ expect(() => webCrypto.getRandomValues(new DataView(new ArrayBuffer(4)))).toThrow(TypeError);
44
+ expect(getRandomValues).not.toHaveBeenCalled();
45
+ });
46
+ });
47
+
48
+ describe('webCrypto singleton', () => {
49
+ it('is the polyfill instance when globalThis.crypto is absent', async () => {
50
+ const { default: webCrypto } = await import('./web-crypto');
51
+
52
+ expect(typeof webCrypto.getRandomValues).toBe('function');
53
+ });
54
+
55
+ it('reuses an existing globalThis.crypto instead of the polyfill', async () => {
56
+ const existingCrypto = { getRandomValues: vi.fn() };
57
+ Object.defineProperty(globalThis, 'crypto', {
58
+ configurable: true,
59
+ value: existingCrypto,
60
+ });
61
+
62
+ const { default: webCrypto } = await import('./web-crypto');
63
+
64
+ expect(webCrypto).toBe(existingCrypto);
65
+ });
66
+ });
67
+
68
+ describe('polyfillWebCrypto', () => {
69
+ it('defines globalThis.crypto when it is absent', async () => {
70
+ const { default: webCrypto, polyfillWebCrypto } = await import('./web-crypto');
71
+
72
+ expect(Reflect.get(globalThis, 'crypto')).toBeUndefined();
73
+
74
+ polyfillWebCrypto();
75
+
76
+ expect(Reflect.get(globalThis, 'crypto')).toBe(webCrypto);
77
+ });
78
+
79
+ it('does not overwrite an existing globalThis.crypto', async () => {
80
+ const existingCrypto = { getRandomValues: vi.fn() };
81
+ Object.defineProperty(globalThis, 'crypto', {
82
+ configurable: true,
83
+ value: existingCrypto,
84
+ });
85
+
86
+ const { polyfillWebCrypto } = await import('./web-crypto');
87
+ polyfillWebCrypto();
88
+
89
+ expect(Reflect.get(globalThis, 'crypto')).toBe(existingCrypto);
90
+ });
91
+ });
@@ -0,0 +1,67 @@
1
+ // Ported from expo-standard-web-crypto (upstream: packages/expo-standard-web-crypto/src/index.ts,
2
+ // verified identical on origin/main and origin/sdk-57) — a partial W3C Web Crypto API polyfill
3
+ // exposing `crypto.getRandomValues`. Upstream delegates to expo-crypto's own getRandomValues; this
4
+ // port delegates to @symbiote-native/crypto's getRandomValues instead, which already wraps the
5
+ // same native random source, so there is no native call left to re-wrap here. React Native has no
6
+ // reliable `window` global, so `globalThis` replaces every `window`/bare `crypto` reference
7
+ // upstream used.
8
+ import { getRandomValues as getNativeRandomValues } from '@symbiote-native/crypto';
9
+ import type { ITypedArray } from '@symbiote-native/crypto';
10
+
11
+ export type IWebCrypto = {
12
+ getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray;
13
+ };
14
+
15
+ // @symbiote-native/crypto's getRandomValues only accepts the integer TypedArrays it can hand to
16
+ // the native module (ITypedArray) — narrower than the W3C spec's ArrayBufferView. This guard
17
+ // narrows the incoming view at the one call site that needs it, so the class below can still
18
+ // expose the full ArrayBufferView-typed method the upstream Crypto class does.
19
+ function isSupportedTypedArray(value: ArrayBufferView): value is ITypedArray {
20
+ return (
21
+ value instanceof Int8Array ||
22
+ value instanceof Uint8Array ||
23
+ value instanceof Int16Array ||
24
+ value instanceof Uint16Array ||
25
+ value instanceof Int32Array ||
26
+ value instanceof Uint32Array
27
+ );
28
+ }
29
+
30
+ class Crypto implements IWebCrypto {
31
+ getRandomValues<TArray extends ArrayBufferView>(values: TArray): TArray {
32
+ if (!isSupportedTypedArray(values)) {
33
+ throw new TypeError('The provided ArrayBuffer view is not a supported integer-typed array');
34
+ }
35
+ return getNativeRandomValues(values);
36
+ }
37
+ }
38
+
39
+ function isWebCrypto(value: unknown): value is IWebCrypto {
40
+ if (typeof value !== 'object' || value === null) {
41
+ return false;
42
+ }
43
+ return 'getRandomValues' in value && typeof value.getRandomValues === 'function';
44
+ }
45
+
46
+ // No ambient `declare global { var crypto }` here on purpose — it would collide with lib.dom.d.ts's
47
+ // own `crypto` global the moment any consumer's tsconfig pulls in the DOM lib (as this package's
48
+ // own Angular build does), producing a duplicate-declaration error. Reflect.get sidesteps that:
49
+ // it reads an untyped global property without asserting one into existence.
50
+ function readGlobalCrypto(): IWebCrypto | undefined {
51
+ const value: unknown = Reflect.get(globalThis, 'crypto');
52
+ return isWebCrypto(value) ? value : undefined;
53
+ }
54
+
55
+ const webCrypto: IWebCrypto = readGlobalCrypto() ?? new Crypto();
56
+
57
+ export default webCrypto;
58
+
59
+ export function polyfillWebCrypto(): void {
60
+ if (readGlobalCrypto() === undefined) {
61
+ Object.defineProperty(globalThis, 'crypto', {
62
+ configurable: true,
63
+ enumerable: true,
64
+ get: () => webCrypto,
65
+ });
66
+ }
67
+ }
@@ -0,0 +1,7 @@
1
+ // @symbiote-native/standard-web-crypto/react: the React entry over the framework-agnostic core.
2
+ // The polyfill has no per-instance state or event stream — nothing for a hook to wrap — so this
3
+ // is a plain re-export, mirroring @symbiote-native/device's and @symbiote-native/crypto's React
4
+ // entries. `export *` never forwards a default export (ES module semantics), so `webCrypto` needs
5
+ // its own explicit named re-export alongside it.
6
+ export * from '../core';
7
+ export { default as webCrypto } from '../core';
@@ -0,0 +1,6 @@
1
+ // @symbiote-native/standard-web-crypto/vue: the Vue entry over the framework-agnostic core. Same
2
+ // reasoning as the React entry — no per-instance state or event stream to wire onto Vue's
3
+ // reactivity, so this is a plain re-export. `export *` never forwards a default export (ES module
4
+ // semantics), so `webCrypto` needs its own explicit named re-export alongside it.
5
+ export * from '../core';
6
+ export { default as webCrypto } from '../core';