@the-polyfills/get-random-values 1.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/GetRandomValues.podspec +20 -0
- package/LICENSE +20 -0
- package/README.md +5 -0
- package/android/build.gradle +55 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/com/thepolyfills/getrandomvalues/GetRandomValuesModule.kt +37 -0
- package/android/src/main/java/com/thepolyfills/getrandomvalues/GetRandomValuesPackage.kt +16 -0
- package/ios/GetRandomValues.h +5 -0
- package/ios/GetRandomValues.mm +35 -0
- package/lib/module/NativeGetRandomValues.js +17 -0
- package/lib/module/NativeGetRandomValues.js.map +1 -0
- package/lib/module/constants.js +54 -0
- package/lib/module/constants.js.map +1 -0
- package/lib/module/index.js +18 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/utils/base64-decode.js +43 -0
- package/lib/module/utils/base64-decode.js.map +1 -0
- package/lib/module/utils/chrome.js +16 -0
- package/lib/module/utils/chrome.js.map +1 -0
- package/lib/module/utils/get-random-values.js +57 -0
- package/lib/module/utils/get-random-values.js.map +1 -0
- package/lib/module/utils/insecure-random-values.js +20 -0
- package/lib/module/utils/insecure-random-values.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/NativeGetRandomValues.d.ts +5 -0
- package/lib/typescript/src/NativeGetRandomValues.d.ts.map +1 -0
- package/lib/typescript/src/constants.d.ts +44 -0
- package/lib/typescript/src/constants.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +6 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/utils/base64-decode.d.ts +2 -0
- package/lib/typescript/src/utils/base64-decode.d.ts.map +1 -0
- package/lib/typescript/src/utils/chrome.d.ts +2 -0
- package/lib/typescript/src/utils/chrome.d.ts.map +1 -0
- package/lib/typescript/src/utils/get-random-values.d.ts +3 -0
- package/lib/typescript/src/utils/get-random-values.d.ts.map +1 -0
- package/lib/typescript/src/utils/insecure-random-values.d.ts +2 -0
- package/lib/typescript/src/utils/insecure-random-values.d.ts.map +1 -0
- package/package.json +128 -0
- package/src/NativeGetRandomValues.ts +21 -0
- package/src/constants.ts +55 -0
- package/src/index.tsx +20 -0
- package/src/utils/base64-decode.ts +59 -0
- package/src/utils/chrome.ts +18 -0
- package/src/utils/get-random-values.ts +81 -0
- package/src/utils/insecure-random-values.ts +25 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
require "json"
|
|
2
|
+
|
|
3
|
+
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
|
|
4
|
+
|
|
5
|
+
Pod::Spec.new do |s|
|
|
6
|
+
s.name = "GetRandomValues"
|
|
7
|
+
s.version = package["version"]
|
|
8
|
+
s.summary = package["description"]
|
|
9
|
+
s.homepage = package["homepage"]
|
|
10
|
+
s.license = package["license"]
|
|
11
|
+
s.authors = package["author"]
|
|
12
|
+
|
|
13
|
+
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
+
s.source = { :git => "https://github.com/doanhtu07/react-native-the-polyfills.git", :tag => "#{s.version}" }
|
|
15
|
+
|
|
16
|
+
s.source_files = "ios/**/*.{h,m,mm,swift,cpp}"
|
|
17
|
+
s.private_header_files = "ios/**/*.h"
|
|
18
|
+
|
|
19
|
+
install_modules_dependencies(s)
|
|
20
|
+
end
|
package/LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anh Tu Do
|
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
6
|
+
in the Software without restriction, including without limitation the rights
|
|
7
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
8
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
9
|
+
furnished to do so, subject to the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
|
12
|
+
copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
16
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
17
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
18
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
19
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
20
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
buildscript {
|
|
2
|
+
ext.GetRandomValues = [
|
|
3
|
+
kotlinVersion: "2.0.21",
|
|
4
|
+
minSdkVersion: 24,
|
|
5
|
+
compileSdkVersion: 36
|
|
6
|
+
]
|
|
7
|
+
|
|
8
|
+
ext.getExtOrDefault = { prop ->
|
|
9
|
+
if (rootProject.ext.has(prop)) {
|
|
10
|
+
return rootProject.ext.get(prop)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return GetRandomValues[prop]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
repositories {
|
|
17
|
+
google()
|
|
18
|
+
mavenCentral()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
dependencies {
|
|
22
|
+
classpath "com.android.tools.build:gradle:8.7.2"
|
|
23
|
+
// noinspection DifferentKotlinGradleVersion
|
|
24
|
+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${getExtOrDefault('kotlinVersion')}"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
apply plugin: "com.android.library"
|
|
30
|
+
|
|
31
|
+
if (project.extensions.findByName("kotlin") == null) {
|
|
32
|
+
apply plugin: "kotlin-android"
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
apply plugin: "com.facebook.react"
|
|
37
|
+
|
|
38
|
+
android {
|
|
39
|
+
namespace "com.thepolyfills.getrandomvalues"
|
|
40
|
+
|
|
41
|
+
compileSdkVersion getExtOrDefault("compileSdkVersion")
|
|
42
|
+
|
|
43
|
+
defaultConfig {
|
|
44
|
+
minSdkVersion getExtOrDefault("minSdkVersion")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
compileOptions {
|
|
48
|
+
sourceCompatibility JavaVersion.VERSION_17
|
|
49
|
+
targetCompatibility JavaVersion.VERSION_17
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
dependencies {
|
|
54
|
+
implementation "com.facebook.react:react-android"
|
|
55
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.thepolyfills.getrandomvalues
|
|
2
|
+
|
|
3
|
+
import android.util.Base64
|
|
4
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
5
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
6
|
+
import com.facebook.react.bridge.ReactMethod
|
|
7
|
+
import java.security.SecureRandom
|
|
8
|
+
|
|
9
|
+
class GetRandomValuesModule(reactContext: ReactApplicationContext) :
|
|
10
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
11
|
+
|
|
12
|
+
override fun getName(): String = NAME
|
|
13
|
+
|
|
14
|
+
// Blocking synchronous method so `getRandomBase64` returns the value
|
|
15
|
+
// directly, matching the synchronous WebCrypto API.
|
|
16
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
17
|
+
fun getRandomBase64(byteLength: Double): String {
|
|
18
|
+
val length = byteLength.toInt()
|
|
19
|
+
|
|
20
|
+
if (length < 0) {
|
|
21
|
+
throw IllegalArgumentException("byteLength must be >= 0")
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (length == 0) {
|
|
25
|
+
return ""
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
val bytes = ByteArray(length)
|
|
29
|
+
SecureRandom().nextBytes(bytes)
|
|
30
|
+
|
|
31
|
+
return Base64.encodeToString(bytes, Base64.NO_WRAP)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
companion object {
|
|
35
|
+
const val NAME = "GetRandomValues"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
package com.thepolyfills.getrandomvalues
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.ReactPackage
|
|
4
|
+
import com.facebook.react.bridge.NativeModule
|
|
5
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
6
|
+
import com.facebook.react.uimanager.ViewManager
|
|
7
|
+
|
|
8
|
+
class GetRandomValuesPackage : ReactPackage {
|
|
9
|
+
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
|
|
10
|
+
return listOf(GetRandomValuesModule(reactContext))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
|
|
14
|
+
return emptyList()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#import "GetRandomValues.h"
|
|
2
|
+
#import <Security/Security.h>
|
|
3
|
+
|
|
4
|
+
@implementation GetRandomValues
|
|
5
|
+
|
|
6
|
+
RCT_EXPORT_MODULE(GetRandomValues)
|
|
7
|
+
|
|
8
|
+
+ (BOOL)requiresMainQueueSetup {
|
|
9
|
+
return NO;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getRandomBase64 : (double)byteLength) {
|
|
13
|
+
NSInteger length = (NSInteger)byteLength;
|
|
14
|
+
|
|
15
|
+
if (length < 0) {
|
|
16
|
+
return nil;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (length == 0) {
|
|
20
|
+
return @"";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
NSMutableData *data = [NSMutableData dataWithLength:(NSUInteger)length];
|
|
24
|
+
|
|
25
|
+
int status = SecRandomCopyBytes(kSecRandomDefault, (size_t)length,
|
|
26
|
+
data.mutableBytes);
|
|
27
|
+
|
|
28
|
+
if (status != errSecSuccess) {
|
|
29
|
+
return nil;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return [data base64EncodedStringWithOptions:0];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { NativeModules } from 'react-native';
|
|
4
|
+
|
|
5
|
+
// Old Architecture (Paper) only: the native module is registered via
|
|
6
|
+
// RCT_EXPORT_MODULE / ReactPackage and looked up through NativeModules.
|
|
7
|
+
// There is intentionally no TurboModuleRegistry usage and no codegen spec
|
|
8
|
+
// in this package.
|
|
9
|
+
|
|
10
|
+
export function getNativeGetRandomValues() {
|
|
11
|
+
const native = NativeModules.GetRandomValues;
|
|
12
|
+
if (native == null || typeof native.getRandomBase64 !== 'function') {
|
|
13
|
+
throw new Error("Native module 'GetRandomValues' not found. Rebuild the app after installing the package (e.g. `expo prebuild --clean`).");
|
|
14
|
+
}
|
|
15
|
+
return native;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=NativeGetRandomValues.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["NativeModules","getNativeGetRandomValues","native","GetRandomValues","getRandomBase64","Error"],"sourceRoot":"../../src","sources":["NativeGetRandomValues.ts"],"mappings":";;AAAA,SAASA,aAAa,QAAQ,cAAc;;AAE5C;AACA;AACA;AACA;;AAKA,OAAO,SAASC,wBAAwBA,CAAA,EAAS;EAC/C,MAAMC,MAAM,GAAGF,aAAa,CAACG,eAAmC;EAEhE,IAAID,MAAM,IAAI,IAAI,IAAI,OAAOA,MAAM,CAACE,eAAe,KAAK,UAAU,EAAE;IAClE,MAAM,IAAIC,KAAK,CACb,yHACF,CAAC;EACH;EAEA,OAAOH,MAAM;AACf","ignoreList":[]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maximum number of bytes `crypto.getRandomValues()` may fill in a single call.
|
|
5
|
+
*
|
|
6
|
+
* The WebCrypto spec sets this quota at 65,536 bytes. Browsers throw a
|
|
7
|
+
* `QuotaExceededError` DOMException when `array.byteLength` exceeds it.
|
|
8
|
+
*
|
|
9
|
+
* Notes for this polyfill:
|
|
10
|
+
* - The check uses `byteLength`, not `length`. For example,
|
|
11
|
+
* `Uint16Array(40_000)` has `length === 40_000` but
|
|
12
|
+
* `byteLength === 80_000`, so it must throw.
|
|
13
|
+
* - Hermes (React Native) has no `DOMException`, so `getRandomValues`
|
|
14
|
+
* throws a plain `Error` with `error.name = 'QuotaExceededError'` instead.
|
|
15
|
+
* Browser-style `catch (e) { e.name === 'QuotaExceededError' }` code keeps
|
|
16
|
+
* working.
|
|
17
|
+
* - A zero-length array short-circuits before any Expo/native call.
|
|
18
|
+
*/
|
|
19
|
+
export const MAX_BYTE_LENGTH = 65_536;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Reverse lookup table for standard base64 decoding.
|
|
23
|
+
*
|
|
24
|
+
* The native module can only return random bytes as a base64 `string` over
|
|
25
|
+
* the synchronous bridge, so JS decodes it back to bytes without an
|
|
26
|
+
* external dependency.
|
|
27
|
+
*
|
|
28
|
+
* How it works:
|
|
29
|
+
* - The base64 alphabet `'A-Za-z0-9+/'` maps index -> 6-bit value
|
|
30
|
+
* (`'A' === 0`, ..., `'/' === 63`).
|
|
31
|
+
* - This table inverts that: `table[charCode] === 6-bit value`, built once
|
|
32
|
+
* at module load via the IIFE below.
|
|
33
|
+
* - The table has 128 slots (one per ASCII code). Slots are pre-filled with
|
|
34
|
+
* `-1` as an "invalid character" marker; only the 64 alphabet codes are
|
|
35
|
+
* overwritten. `'='` padding is handled separately by the decoder (mapped
|
|
36
|
+
* to `0`), and any lookup that still yields `-1` (or `undefined ?? -1`
|
|
37
|
+
* for char codes > 127) makes `base64ToBytes` throw
|
|
38
|
+
* `'Invalid base64 character'`.
|
|
39
|
+
*
|
|
40
|
+
* Decode math (per 4-char block in `base64ToBytes`):
|
|
41
|
+
* - `triplet = (a << 18) | (b << 12) | (c << 6) | d` reassembles 4 x 6 bits
|
|
42
|
+
* into 24 bits, then splits into 3 bytes via `>> 16`, `>> 8`, `& 0xff`.
|
|
43
|
+
* - `outputLength = (clean.length / 4) * 3 - padding` accounts for trailing
|
|
44
|
+
* `'='` / `'=='`, and `outIndex < outputLength` guards drop padding bytes.
|
|
45
|
+
*/
|
|
46
|
+
export const BASE64_LOOKUP = (() => {
|
|
47
|
+
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
|
48
|
+
const table = new Array(128).fill(-1);
|
|
49
|
+
for (let i = 0; i < chars.length; i++) {
|
|
50
|
+
table[chars.charCodeAt(i)] = i;
|
|
51
|
+
}
|
|
52
|
+
return table;
|
|
53
|
+
})();
|
|
54
|
+
//# sourceMappingURL=constants.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["MAX_BYTE_LENGTH","BASE64_LOOKUP","chars","table","Array","fill","i","length","charCodeAt"],"sourceRoot":"../../src","sources":["constants.ts"],"mappings":";;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMA,eAAe,GAAG,MAAM;;AAErC;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,aAAa,GAAG,CAAC,MAAM;EAClC,MAAMC,KAAK,GACT,kEAAkE;EAEpE,MAAMC,KAAK,GAAG,IAAIC,KAAK,CAAS,GAAG,CAAC,CAACC,IAAI,CAAC,CAAC,CAAC,CAAC;EAE7C,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGJ,KAAK,CAACK,MAAM,EAAED,CAAC,EAAE,EAAE;IACrCH,KAAK,CAACD,KAAK,CAACM,UAAU,CAACF,CAAC,CAAC,CAAC,GAAGA,CAAC;EAChC;EAEA,OAAOH,KAAK;AACd,CAAC,EAAE,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { getRandomValues } from "./utils/get-random-values.js";
|
|
4
|
+
export { getRandomValues } from "./utils/get-random-values.js";
|
|
5
|
+
export function installGetRandomValues(options) {
|
|
6
|
+
const g = globalThis;
|
|
7
|
+
if (typeof g.crypto !== 'object' || g.crypto === null) {
|
|
8
|
+
g.crypto = {};
|
|
9
|
+
}
|
|
10
|
+
if (options?.force || typeof g.crypto.getRandomValues !== 'function') {
|
|
11
|
+
g.crypto.getRandomValues = getRandomValues;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Side-effect import installs the polyfill, matching
|
|
16
|
+
// `react-native-get-random-values` behavior.
|
|
17
|
+
installGetRandomValues();
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["getRandomValues","installGetRandomValues","options","g","globalThis","crypto","force"],"sourceRoot":"../../src","sources":["index.tsx"],"mappings":";;AAAA,SAASA,eAAe,QAAQ,8BAA2B;AAE3D,SAASA,eAAe,QAAQ,8BAA2B;AAG3D,OAAO,SAASC,sBAAsBA,CAACC,OAA6B,EAAQ;EAC1E,MAAMC,CAAC,GAAGC,UAAiB;EAE3B,IAAI,OAAOD,CAAC,CAACE,MAAM,KAAK,QAAQ,IAAIF,CAAC,CAACE,MAAM,KAAK,IAAI,EAAE;IACrDF,CAAC,CAACE,MAAM,GAAG,CAAC,CAAC;EACf;EAEA,IAAIH,OAAO,EAAEI,KAAK,IAAI,OAAOH,CAAC,CAACE,MAAM,CAACL,eAAe,KAAK,UAAU,EAAE;IACpEG,CAAC,CAACE,MAAM,CAACL,eAAe,GAAGA,eAAe;EAC5C;AACF;;AAEA;AACA;AACAC,sBAAsB,CAAC,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { BASE64_LOOKUP } from "../constants.js";
|
|
4
|
+
export function base64ToBytes(base64) {
|
|
5
|
+
// Standard base64 (NO_WRAP on Android, no line breaks on iOS), may be padded.
|
|
6
|
+
const clean = base64.replace(/\s/g, '');
|
|
7
|
+
if (clean.length === 0) {
|
|
8
|
+
return new Uint8Array(0);
|
|
9
|
+
}
|
|
10
|
+
if (clean.length % 4 !== 0) {
|
|
11
|
+
throw new Error('Invalid base64 string length');
|
|
12
|
+
}
|
|
13
|
+
let padding = 0;
|
|
14
|
+
if (clean.endsWith('==')) {
|
|
15
|
+
padding = 2;
|
|
16
|
+
} else if (clean.endsWith('=')) {
|
|
17
|
+
padding = 1;
|
|
18
|
+
}
|
|
19
|
+
const outputLength = clean.length / 4 * 3 - padding;
|
|
20
|
+
const output = new Uint8Array(outputLength);
|
|
21
|
+
let outIndex = 0;
|
|
22
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
23
|
+
const a = BASE64_LOOKUP[clean.charCodeAt(i)] ?? -1;
|
|
24
|
+
const b = BASE64_LOOKUP[clean.charCodeAt(i + 1)] ?? -1;
|
|
25
|
+
const c = clean[i + 2] === '=' ? 0 : BASE64_LOOKUP[clean.charCodeAt(i + 2)] ?? -1;
|
|
26
|
+
const d = clean[i + 3] === '=' ? 0 : BASE64_LOOKUP[clean.charCodeAt(i + 3)] ?? -1;
|
|
27
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) {
|
|
28
|
+
throw new Error('Invalid base64 character');
|
|
29
|
+
}
|
|
30
|
+
const triplet = a << 18 | b << 12 | c << 6 | d;
|
|
31
|
+
if (outIndex < outputLength) {
|
|
32
|
+
output[outIndex++] = triplet >> 16 & 0xff;
|
|
33
|
+
}
|
|
34
|
+
if (outIndex < outputLength) {
|
|
35
|
+
output[outIndex++] = triplet >> 8 & 0xff;
|
|
36
|
+
}
|
|
37
|
+
if (outIndex < outputLength) {
|
|
38
|
+
output[outIndex++] = triplet & 0xff;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return output;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=base64-decode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["BASE64_LOOKUP","base64ToBytes","base64","clean","replace","length","Uint8Array","Error","padding","endsWith","outputLength","output","outIndex","i","a","charCodeAt","b","c","d","triplet"],"sourceRoot":"../../../src","sources":["utils/base64-decode.ts"],"mappings":";;AAAA,SAASA,aAAa,QAAQ,iBAAc;AAE5C,OAAO,SAASC,aAAaA,CAACC,MAAc,EAAc;EACxD;EACA,MAAMC,KAAK,GAAGD,MAAM,CAACE,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC;EAEvC,IAAID,KAAK,CAACE,MAAM,KAAK,CAAC,EAAE;IACtB,OAAO,IAAIC,UAAU,CAAC,CAAC,CAAC;EAC1B;EAEA,IAAIH,KAAK,CAACE,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE;IAC1B,MAAM,IAAIE,KAAK,CAAC,8BAA8B,CAAC;EACjD;EAEA,IAAIC,OAAO,GAAG,CAAC;EAEf,IAAIL,KAAK,CAACM,QAAQ,CAAC,IAAI,CAAC,EAAE;IACxBD,OAAO,GAAG,CAAC;EACb,CAAC,MAAM,IAAIL,KAAK,CAACM,QAAQ,CAAC,GAAG,CAAC,EAAE;IAC9BD,OAAO,GAAG,CAAC;EACb;EAEA,MAAME,YAAY,GAAIP,KAAK,CAACE,MAAM,GAAG,CAAC,GAAI,CAAC,GAAGG,OAAO;EACrD,MAAMG,MAAM,GAAG,IAAIL,UAAU,CAACI,YAAY,CAAC;EAE3C,IAAIE,QAAQ,GAAG,CAAC;EAEhB,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGV,KAAK,CAACE,MAAM,EAAEQ,CAAC,IAAI,CAAC,EAAE;IACxC,MAAMC,CAAC,GAAGd,aAAa,CAACG,KAAK,CAACY,UAAU,CAACF,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAElD,MAAMG,CAAC,GAAGhB,aAAa,CAACG,KAAK,CAACY,UAAU,CAACF,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAEtD,MAAMI,CAAC,GACLd,KAAK,CAACU,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAIb,aAAa,CAACG,KAAK,CAACY,UAAU,CAACF,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAE;IAE3E,MAAMK,CAAC,GACLf,KAAK,CAACU,CAAC,GAAG,CAAC,CAAC,KAAK,GAAG,GAAG,CAAC,GAAIb,aAAa,CAACG,KAAK,CAACY,UAAU,CAACF,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAE;IAE3E,IAAIC,CAAC,GAAG,CAAC,IAAIE,CAAC,GAAG,CAAC,IAAIC,CAAC,GAAG,CAAC,IAAIC,CAAC,GAAG,CAAC,EAAE;MACpC,MAAM,IAAIX,KAAK,CAAC,0BAA0B,CAAC;IAC7C;IAEA,MAAMY,OAAO,GAAIL,CAAC,IAAI,EAAE,GAAKE,CAAC,IAAI,EAAG,GAAIC,CAAC,IAAI,CAAE,GAAGC,CAAC;IAEpD,IAAIN,QAAQ,GAAGF,YAAY,EAAE;MAC3BC,MAAM,CAACC,QAAQ,EAAE,CAAC,GAAIO,OAAO,IAAI,EAAE,GAAI,IAAI;IAC7C;IAEA,IAAIP,QAAQ,GAAGF,YAAY,EAAE;MAC3BC,MAAM,CAACC,QAAQ,EAAE,CAAC,GAAIO,OAAO,IAAI,CAAC,GAAI,IAAI;IAC5C;IAEA,IAAIP,QAAQ,GAAGF,YAAY,EAAE;MAC3BC,MAAM,CAACC,QAAQ,EAAE,CAAC,GAAGO,OAAO,GAAG,IAAI;IACrC;EACF;EAEA,OAAOR,MAAM;AACf","ignoreList":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
export function isRemoteDebuggingInChrome() {
|
|
4
|
+
const g = globalThis;
|
|
5
|
+
|
|
6
|
+
// Remote debugging in Chrome is not supported in bridgeless mode.
|
|
7
|
+
if ('RN$Bridgeless' in g && g.RN$Bridgeless === true) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
return __DEV__ !== undefined && __DEV__ && g.nativeCallSyncHook === undefined;
|
|
12
|
+
} catch {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=chrome.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["isRemoteDebuggingInChrome","g","globalThis","RN$Bridgeless","__DEV__","undefined","nativeCallSyncHook"],"sourceRoot":"../../../src","sources":["utils/chrome.ts"],"mappings":";;AAEA,OAAO,SAASA,yBAAyBA,CAAA,EAAY;EACnD,MAAMC,CAAC,GAAGC,UAAiB;;EAE3B;EACA,IAAI,eAAe,IAAID,CAAC,IAAIA,CAAC,CAACE,aAAa,KAAK,IAAI,EAAE;IACpD,OAAO,KAAK;EACd;EAEA,IAAI;IACF,OACEC,OAAO,KAAKC,SAAS,IAAID,OAAO,IAAIH,CAAC,CAACK,kBAAkB,KAAKD,SAAS;EAE1E,CAAC,CAAC,MAAM;IACN,OAAO,KAAK;EACd;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { MAX_BYTE_LENGTH } from "../constants.js";
|
|
4
|
+
import { getNativeGetRandomValues } from "../NativeGetRandomValues.js";
|
|
5
|
+
import { base64ToBytes } from "./base64-decode.js";
|
|
6
|
+
import { isRemoteDebuggingInChrome } from "./chrome.js";
|
|
7
|
+
import { insecureRandomValues } from "./insecure-random-values.js";
|
|
8
|
+
function isAllowedArray(value) {
|
|
9
|
+
if (!ArrayBuffer.isView(value) || value instanceof DataView) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// WebCrypto only allows integer TypedArrays (no float16/32/64).
|
|
14
|
+
return value instanceof Int8Array || value instanceof Uint8Array || value instanceof Uint8ClampedArray || value instanceof Int16Array || value instanceof Uint16Array || value instanceof Int32Array || value instanceof Uint32Array || value instanceof BigInt64Array || value instanceof BigUint64Array;
|
|
15
|
+
}
|
|
16
|
+
export function getRandomValues(array) {
|
|
17
|
+
if (!isAllowedArray(array)) {
|
|
18
|
+
throw new TypeError('crypto.getRandomValues: expected an integer TypedArray (e.g. Uint8Array)');
|
|
19
|
+
}
|
|
20
|
+
if (array.byteLength > MAX_BYTE_LENGTH) {
|
|
21
|
+
// Browsers throw QuotaExceededError (DOMException). RN Hermes may not
|
|
22
|
+
// have DOMException, so use a plain Error with the same name.
|
|
23
|
+
const error = new Error(`crypto.getRandomValues: byteLength ${array.byteLength} exceeds ${MAX_BYTE_LENGTH}`);
|
|
24
|
+
error.name = 'QuotaExceededError';
|
|
25
|
+
throw error;
|
|
26
|
+
}
|
|
27
|
+
if (array.byteLength === 0) {
|
|
28
|
+
return array;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Expo SDK 48+: prefer ExpoCrypto when available so managed workflow
|
|
32
|
+
// apps don't need our native module linked.
|
|
33
|
+
|
|
34
|
+
const expoGetRandomValues = globalThis?.expo?.modules?.ExpoCrypto?.getRandomValues;
|
|
35
|
+
if (typeof expoGetRandomValues === 'function') {
|
|
36
|
+
// ExpoCrypto mutates the array in place and returns void.
|
|
37
|
+
expoGetRandomValues.call(globalThis.expo.modules.ExpoCrypto, array);
|
|
38
|
+
return array;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Remote debugging in Chrome can't call sync native methods
|
|
42
|
+
// ("Calling synchronous methods on native modules is not supported in
|
|
43
|
+
// Chrome"), so fall back to Math.random() there.
|
|
44
|
+
if (isRemoteDebuggingInChrome()) {
|
|
45
|
+
return insecureRandomValues(array);
|
|
46
|
+
}
|
|
47
|
+
const base64 = getNativeGetRandomValues().getRandomBase64(array.byteLength);
|
|
48
|
+
const bytes = base64ToBytes(base64);
|
|
49
|
+
if (bytes.byteLength !== array.byteLength) {
|
|
50
|
+
throw new Error(`crypto.getRandomValues: native returned ${bytes.byteLength} bytes, expected ${array.byteLength}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Write through to the underlying buffer so BigInt arrays work too.
|
|
54
|
+
new Uint8Array(array.buffer, array.byteOffset, array.byteLength).set(bytes);
|
|
55
|
+
return array;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=get-random-values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["MAX_BYTE_LENGTH","getNativeGetRandomValues","base64ToBytes","isRemoteDebuggingInChrome","insecureRandomValues","isAllowedArray","value","ArrayBuffer","isView","DataView","Int8Array","Uint8Array","Uint8ClampedArray","Int16Array","Uint16Array","Int32Array","Uint32Array","BigInt64Array","BigUint64Array","getRandomValues","array","TypeError","byteLength","error","Error","name","expoGetRandomValues","globalThis","expo","modules","ExpoCrypto","call","base64","getRandomBase64","bytes","buffer","byteOffset","set"],"sourceRoot":"../../../src","sources":["utils/get-random-values.ts"],"mappings":";;AAAA,SAASA,eAAe,QAAQ,iBAAc;AAC9C,SAASC,wBAAwB,QAAQ,6BAA0B;AACnE,SAASC,aAAa,QAAQ,oBAAiB;AAC/C,SAASC,yBAAyB,QAAQ,aAAU;AACpD,SAASC,oBAAoB,QAAQ,6BAA0B;AAE/D,SAASC,cAAcA,CAACC,KAAc,EAA4B;EAChE,IAAI,CAACC,WAAW,CAACC,MAAM,CAACF,KAAK,CAAC,IAAIA,KAAK,YAAYG,QAAQ,EAAE;IAC3D,OAAO,KAAK;EACd;;EAEA;EACA,OACEH,KAAK,YAAYI,SAAS,IAC1BJ,KAAK,YAAYK,UAAU,IAC3BL,KAAK,YAAYM,iBAAiB,IAClCN,KAAK,YAAYO,UAAU,IAC3BP,KAAK,YAAYQ,WAAW,IAC5BR,KAAK,YAAYS,UAAU,IAC3BT,KAAK,YAAYU,WAAW,IAC5BV,KAAK,YAAYW,aAAa,IAC9BX,KAAK,YAAYY,cAAc;AAEnC;AAIA,OAAO,SAASC,eAAeA,CAA4BC,KAAQ,EAAK;EACtE,IAAI,CAACf,cAAc,CAACe,KAAK,CAAC,EAAE;IAC1B,MAAM,IAAIC,SAAS,CACjB,0EACF,CAAC;EACH;EAEA,IAAID,KAAK,CAACE,UAAU,GAAGtB,eAAe,EAAE;IACtC;IACA;IACA,MAAMuB,KAAK,GAAG,IAAIC,KAAK,CACrB,sCAAsCJ,KAAK,CAACE,UAAU,YAAYtB,eAAe,EACnF,CAAC;IACDuB,KAAK,CAACE,IAAI,GAAG,oBAAoB;IACjC,MAAMF,KAAK;EACb;EAEA,IAAIH,KAAK,CAACE,UAAU,KAAK,CAAC,EAAE;IAC1B,OAAOF,KAAK;EACd;;EAEA;EACA;;EAEA,MAAMM,mBAAmB,GAAIC,UAAU,EAAUC,IAAI,EAAEC,OAAO,EAAEC,UAAU,EACtEX,eAAiE;EAErE,IAAI,OAAOO,mBAAmB,KAAK,UAAU,EAAE;IAC7C;IACAA,mBAAmB,CAACK,IAAI,CAAEJ,UAAU,CAASC,IAAI,CAACC,OAAO,CAACC,UAAU,EAAEV,KAAK,CAAC;IAC5E,OAAOA,KAAK;EACd;;EAEA;EACA;EACA;EACA,IAAIjB,yBAAyB,CAAC,CAAC,EAAE;IAC/B,OAAOC,oBAAoB,CAACgB,KAAK,CAAC;EACpC;EAEA,MAAMY,MAAM,GAAG/B,wBAAwB,CAAC,CAAC,CAACgC,eAAe,CAACb,KAAK,CAACE,UAAU,CAAC;EAC3E,MAAMY,KAAK,GAAGhC,aAAa,CAAC8B,MAAM,CAAC;EAEnC,IAAIE,KAAK,CAACZ,UAAU,KAAKF,KAAK,CAACE,UAAU,EAAE;IACzC,MAAM,IAAIE,KAAK,CACb,2CAA2CU,KAAK,CAACZ,UAAU,oBAAoBF,KAAK,CAACE,UAAU,EACjG,CAAC;EACH;;EAEA;EACA,IAAIX,UAAU,CAACS,KAAK,CAACe,MAAM,EAAEf,KAAK,CAACgB,UAAU,EAAEhB,KAAK,CAACE,UAAU,CAAC,CAACe,GAAG,CAACH,KAAK,CAAC;EAE3E,OAAOd,KAAK;AACd","ignoreList":[]}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
let insecureWarned = false;
|
|
4
|
+
export function insecureRandomValues(array) {
|
|
5
|
+
if (!insecureWarned) {
|
|
6
|
+
console.warn('crypto.getRandomValues: using an insecure random number generator, ' + 'this should only happen when running in a debugger without support for crypto.getRandomValues');
|
|
7
|
+
insecureWarned = true;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Fill through a Uint8 view so BigInt arrays work too.
|
|
11
|
+
const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength);
|
|
12
|
+
for (let i = 0, r = 0; i < bytes.length; i++) {
|
|
13
|
+
if ((i & 0x03) === 0) {
|
|
14
|
+
r = Math.random() * 0x100000000;
|
|
15
|
+
}
|
|
16
|
+
bytes[i] = r >>> ((i & 0x03) << 3) & 0xff;
|
|
17
|
+
}
|
|
18
|
+
return array;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=insecure-random-values.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["insecureWarned","insecureRandomValues","array","console","warn","bytes","Uint8Array","buffer","byteOffset","byteLength","i","r","length","Math","random"],"sourceRoot":"../../../src","sources":["utils/insecure-random-values.ts"],"mappings":";;AAAA,IAAIA,cAAc,GAAG,KAAK;AAE1B,OAAO,SAASC,oBAAoBA,CAA4BC,KAAQ,EAAK;EAC3E,IAAI,CAACF,cAAc,EAAE;IACnBG,OAAO,CAACC,IAAI,CACV,qEAAqE,GACnE,+FACJ,CAAC;IAEDJ,cAAc,GAAG,IAAI;EACvB;;EAEA;EACA,MAAMK,KAAK,GAAG,IAAIC,UAAU,CAACJ,KAAK,CAACK,MAAM,EAAEL,KAAK,CAACM,UAAU,EAAEN,KAAK,CAACO,UAAU,CAAC;EAE9E,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEC,CAAC,GAAG,CAAC,EAAED,CAAC,GAAGL,KAAK,CAACO,MAAM,EAAEF,CAAC,EAAE,EAAE;IAC5C,IAAI,CAACA,CAAC,GAAG,IAAI,MAAM,CAAC,EAAE;MACpBC,CAAC,GAAGE,IAAI,CAACC,MAAM,CAAC,CAAC,GAAG,WAAW;IACjC;IAEAT,KAAK,CAACK,CAAC,CAAC,GAAIC,CAAC,MAAM,CAACD,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC,GAAI,IAAI;EAC7C;EAEA,OAAOR,KAAK;AACd","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NativeGetRandomValues.d.ts","sourceRoot":"","sources":["../../../src/NativeGetRandomValues.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,IAAI;IACnB,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAAA;CAC5C;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAU/C"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximum number of bytes `crypto.getRandomValues()` may fill in a single call.
|
|
3
|
+
*
|
|
4
|
+
* The WebCrypto spec sets this quota at 65,536 bytes. Browsers throw a
|
|
5
|
+
* `QuotaExceededError` DOMException when `array.byteLength` exceeds it.
|
|
6
|
+
*
|
|
7
|
+
* Notes for this polyfill:
|
|
8
|
+
* - The check uses `byteLength`, not `length`. For example,
|
|
9
|
+
* `Uint16Array(40_000)` has `length === 40_000` but
|
|
10
|
+
* `byteLength === 80_000`, so it must throw.
|
|
11
|
+
* - Hermes (React Native) has no `DOMException`, so `getRandomValues`
|
|
12
|
+
* throws a plain `Error` with `error.name = 'QuotaExceededError'` instead.
|
|
13
|
+
* Browser-style `catch (e) { e.name === 'QuotaExceededError' }` code keeps
|
|
14
|
+
* working.
|
|
15
|
+
* - A zero-length array short-circuits before any Expo/native call.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MAX_BYTE_LENGTH = 65536;
|
|
18
|
+
/**
|
|
19
|
+
* Reverse lookup table for standard base64 decoding.
|
|
20
|
+
*
|
|
21
|
+
* The native module can only return random bytes as a base64 `string` over
|
|
22
|
+
* the synchronous bridge, so JS decodes it back to bytes without an
|
|
23
|
+
* external dependency.
|
|
24
|
+
*
|
|
25
|
+
* How it works:
|
|
26
|
+
* - The base64 alphabet `'A-Za-z0-9+/'` maps index -> 6-bit value
|
|
27
|
+
* (`'A' === 0`, ..., `'/' === 63`).
|
|
28
|
+
* - This table inverts that: `table[charCode] === 6-bit value`, built once
|
|
29
|
+
* at module load via the IIFE below.
|
|
30
|
+
* - The table has 128 slots (one per ASCII code). Slots are pre-filled with
|
|
31
|
+
* `-1` as an "invalid character" marker; only the 64 alphabet codes are
|
|
32
|
+
* overwritten. `'='` padding is handled separately by the decoder (mapped
|
|
33
|
+
* to `0`), and any lookup that still yields `-1` (or `undefined ?? -1`
|
|
34
|
+
* for char codes > 127) makes `base64ToBytes` throw
|
|
35
|
+
* `'Invalid base64 character'`.
|
|
36
|
+
*
|
|
37
|
+
* Decode math (per 4-char block in `base64ToBytes`):
|
|
38
|
+
* - `triplet = (a << 18) | (b << 12) | (c << 6) | d` reassembles 4 x 6 bits
|
|
39
|
+
* into 24 bits, then splits into 3 bytes via `>> 16`, `>> 8`, `& 0xff`.
|
|
40
|
+
* - `outputLength = (clean.length / 4) * 3 - padding` accounts for trailing
|
|
41
|
+
* `'='` / `'=='`, and `outIndex < outputLength` guards drop padding bytes.
|
|
42
|
+
*/
|
|
43
|
+
export declare const BASE64_LOOKUP: number[];
|
|
44
|
+
//# sourceMappingURL=constants.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/constants.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,eAAe,QAAS,CAAA;AAErC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,aAAa,UAWtB,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA2B,CAAA;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,8BAA2B,CAAA;AAElE,wBAAgB,sBAAsB,CAAC,OAAO,CAAC,EAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAU1E"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base64-decode.d.ts","sourceRoot":"","sources":["../../../../src/utils/base64-decode.ts"],"names":[],"mappings":"AAEA,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAwDxD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chrome.d.ts","sourceRoot":"","sources":["../../../../src/utils/chrome.ts"],"names":[],"mappings":"AAEA,wBAAgB,yBAAyB,IAAI,OAAO,CAenD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-random-values.d.ts","sourceRoot":"","sources":["../../../../src/utils/get-random-values.ts"],"names":[],"mappings":"AAyBA,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,SAAS,eAAe,EAAE,KAAK,EAAE,CAAC,KAAK,CAAC,CAAA;AAE1E,wBAAgB,eAAe,CAAC,CAAC,SAAS,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAqDtE"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"insecure-random-values.d.ts","sourceRoot":"","sources":["../../../../src/utils/insecure-random-values.ts"],"names":[],"mappings":"AAEA,wBAAgB,oBAAoB,CAAC,CAAC,SAAS,eAAe,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAsB3E"}
|
package/package.json
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@the-polyfills/get-random-values",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Polyfill for crypto.getRandomValues in React Native",
|
|
5
|
+
"main": "./lib/module/index.js",
|
|
6
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"the-polyfills-get-random-values-source": "./src/index.tsx",
|
|
10
|
+
"types": "./lib/typescript/src/index.d.ts",
|
|
11
|
+
"default": "./lib/module/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"src",
|
|
17
|
+
"lib",
|
|
18
|
+
"android",
|
|
19
|
+
"ios",
|
|
20
|
+
"cpp",
|
|
21
|
+
"*.podspec",
|
|
22
|
+
"react-native.config.js",
|
|
23
|
+
"!ios/build",
|
|
24
|
+
"!android/build",
|
|
25
|
+
"!android/gradle",
|
|
26
|
+
"!android/gradlew",
|
|
27
|
+
"!android/gradlew.bat",
|
|
28
|
+
"!android/local.properties",
|
|
29
|
+
"!**/__tests__",
|
|
30
|
+
"!**/__fixtures__",
|
|
31
|
+
"!**/__mocks__",
|
|
32
|
+
"!**/.*"
|
|
33
|
+
],
|
|
34
|
+
"keywords": [
|
|
35
|
+
"react-native",
|
|
36
|
+
"ios",
|
|
37
|
+
"android"
|
|
38
|
+
],
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "git+https://github.com/doanhtu07/react-native-the-polyfills.git"
|
|
42
|
+
},
|
|
43
|
+
"author": "Anh Tu Do <doanhtu07@gmail.com> (https://github.com/doanhtu07)",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/doanhtu07/react-native-the-polyfills/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/doanhtu07/react-native-the-polyfills#readme",
|
|
49
|
+
"publishConfig": {
|
|
50
|
+
"registry": "https://registry.npmjs.org/"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@eslint/compat": "^2.1.1",
|
|
54
|
+
"@eslint/eslintrc": "^3.3.7",
|
|
55
|
+
"@eslint/js": "^10.0.1",
|
|
56
|
+
"@jest/globals": "^30.5.2",
|
|
57
|
+
"@react-native/babel-preset": "0.87.1",
|
|
58
|
+
"@react-native/eslint-config": "0.87.1",
|
|
59
|
+
"@types/react": "~19.0.14",
|
|
60
|
+
"del-cli": "^7.0.0",
|
|
61
|
+
"eslint": "^10.11.0",
|
|
62
|
+
"eslint-config-prettier": "^10.1.8",
|
|
63
|
+
"eslint-plugin-ft-flow": "^3.0.11",
|
|
64
|
+
"eslint-plugin-prettier": "^5.5.6",
|
|
65
|
+
"jest": "^30.5.2",
|
|
66
|
+
"nodemon": "^3.1.14",
|
|
67
|
+
"prettier": "^3.9.9",
|
|
68
|
+
"react": "19.0.0",
|
|
69
|
+
"react-native": "0.79.6",
|
|
70
|
+
"react-native-builder-bob": "^0.43.1",
|
|
71
|
+
"turbo": "^2.11.3",
|
|
72
|
+
"typescript": "~5.8.3"
|
|
73
|
+
},
|
|
74
|
+
"peerDependencies": {
|
|
75
|
+
"react": "*",
|
|
76
|
+
"react-native": "*"
|
|
77
|
+
},
|
|
78
|
+
"react-native-builder-bob": {
|
|
79
|
+
"source": "src",
|
|
80
|
+
"output": "lib",
|
|
81
|
+
"targets": [
|
|
82
|
+
[
|
|
83
|
+
"module",
|
|
84
|
+
{
|
|
85
|
+
"esm": true
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
[
|
|
89
|
+
"typescript",
|
|
90
|
+
{
|
|
91
|
+
"project": "tsconfig.build.json"
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"jest": {
|
|
97
|
+
"preset": "react-native",
|
|
98
|
+
"testEnvironmentOptions": {
|
|
99
|
+
"customExportConditions": [
|
|
100
|
+
"require",
|
|
101
|
+
"react-native",
|
|
102
|
+
"<%- project.sourceCondition -%>"
|
|
103
|
+
]
|
|
104
|
+
},
|
|
105
|
+
"modulePathIgnorePatterns": [
|
|
106
|
+
"<rootDir>/example/node_modules",
|
|
107
|
+
"<rootDir>/lib/"
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
"create-react-native-library": {
|
|
111
|
+
"type": "turbo-module",
|
|
112
|
+
"languages": "kotlin-objc",
|
|
113
|
+
"tools": [
|
|
114
|
+
"eslint",
|
|
115
|
+
"jest"
|
|
116
|
+
],
|
|
117
|
+
"version": "0.63.1"
|
|
118
|
+
},
|
|
119
|
+
"scripts": {
|
|
120
|
+
"clean": "del-cli android/build example/android/build example/android/app/build example/ios/build lib",
|
|
121
|
+
"dev": "nodemon",
|
|
122
|
+
"typecheck": "tsc",
|
|
123
|
+
"lint": "eslint \"**/*.{js,ts,tsx}\"",
|
|
124
|
+
"format": "prettier",
|
|
125
|
+
"check": "pnpm typecheck && prettier --write . && eslint \"**/*.{js,ts,tsx}\" --fix",
|
|
126
|
+
"test": "jest"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { NativeModules } from 'react-native'
|
|
2
|
+
|
|
3
|
+
// Old Architecture (Paper) only: the native module is registered via
|
|
4
|
+
// RCT_EXPORT_MODULE / ReactPackage and looked up through NativeModules.
|
|
5
|
+
// There is intentionally no TurboModuleRegistry usage and no codegen spec
|
|
6
|
+
// in this package.
|
|
7
|
+
export interface Spec {
|
|
8
|
+
getRandomBase64(byteLength: number): string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getNativeGetRandomValues(): Spec {
|
|
12
|
+
const native = NativeModules.GetRandomValues as Spec | undefined
|
|
13
|
+
|
|
14
|
+
if (native == null || typeof native.getRandomBase64 !== 'function') {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"Native module 'GetRandomValues' not found. Rebuild the app after installing the package (e.g. `expo prebuild --clean`).",
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return native
|
|
21
|
+
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maximum number of bytes `crypto.getRandomValues()` may fill in a single call.
|
|
3
|
+
*
|
|
4
|
+
* The WebCrypto spec sets this quota at 65,536 bytes. Browsers throw a
|
|
5
|
+
* `QuotaExceededError` DOMException when `array.byteLength` exceeds it.
|
|
6
|
+
*
|
|
7
|
+
* Notes for this polyfill:
|
|
8
|
+
* - The check uses `byteLength`, not `length`. For example,
|
|
9
|
+
* `Uint16Array(40_000)` has `length === 40_000` but
|
|
10
|
+
* `byteLength === 80_000`, so it must throw.
|
|
11
|
+
* - Hermes (React Native) has no `DOMException`, so `getRandomValues`
|
|
12
|
+
* throws a plain `Error` with `error.name = 'QuotaExceededError'` instead.
|
|
13
|
+
* Browser-style `catch (e) { e.name === 'QuotaExceededError' }` code keeps
|
|
14
|
+
* working.
|
|
15
|
+
* - A zero-length array short-circuits before any Expo/native call.
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_BYTE_LENGTH = 65_536
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Reverse lookup table for standard base64 decoding.
|
|
21
|
+
*
|
|
22
|
+
* The native module can only return random bytes as a base64 `string` over
|
|
23
|
+
* the synchronous bridge, so JS decodes it back to bytes without an
|
|
24
|
+
* external dependency.
|
|
25
|
+
*
|
|
26
|
+
* How it works:
|
|
27
|
+
* - The base64 alphabet `'A-Za-z0-9+/'` maps index -> 6-bit value
|
|
28
|
+
* (`'A' === 0`, ..., `'/' === 63`).
|
|
29
|
+
* - This table inverts that: `table[charCode] === 6-bit value`, built once
|
|
30
|
+
* at module load via the IIFE below.
|
|
31
|
+
* - The table has 128 slots (one per ASCII code). Slots are pre-filled with
|
|
32
|
+
* `-1` as an "invalid character" marker; only the 64 alphabet codes are
|
|
33
|
+
* overwritten. `'='` padding is handled separately by the decoder (mapped
|
|
34
|
+
* to `0`), and any lookup that still yields `-1` (or `undefined ?? -1`
|
|
35
|
+
* for char codes > 127) makes `base64ToBytes` throw
|
|
36
|
+
* `'Invalid base64 character'`.
|
|
37
|
+
*
|
|
38
|
+
* Decode math (per 4-char block in `base64ToBytes`):
|
|
39
|
+
* - `triplet = (a << 18) | (b << 12) | (c << 6) | d` reassembles 4 x 6 bits
|
|
40
|
+
* into 24 bits, then splits into 3 bytes via `>> 16`, `>> 8`, `& 0xff`.
|
|
41
|
+
* - `outputLength = (clean.length / 4) * 3 - padding` accounts for trailing
|
|
42
|
+
* `'='` / `'=='`, and `outIndex < outputLength` guards drop padding bytes.
|
|
43
|
+
*/
|
|
44
|
+
export const BASE64_LOOKUP = (() => {
|
|
45
|
+
const chars =
|
|
46
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
|
|
47
|
+
|
|
48
|
+
const table = new Array<number>(128).fill(-1)
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < chars.length; i++) {
|
|
51
|
+
table[chars.charCodeAt(i)] = i
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return table
|
|
55
|
+
})()
|
package/src/index.tsx
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { getRandomValues } from './utils/get-random-values'
|
|
2
|
+
|
|
3
|
+
export { getRandomValues } from './utils/get-random-values'
|
|
4
|
+
export type { GetRandomValuesFn } from './utils/get-random-values'
|
|
5
|
+
|
|
6
|
+
export function installGetRandomValues(options?: { force?: boolean }): void {
|
|
7
|
+
const g = globalThis as any
|
|
8
|
+
|
|
9
|
+
if (typeof g.crypto !== 'object' || g.crypto === null) {
|
|
10
|
+
g.crypto = {}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (options?.force || typeof g.crypto.getRandomValues !== 'function') {
|
|
14
|
+
g.crypto.getRandomValues = getRandomValues
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Side-effect import installs the polyfill, matching
|
|
19
|
+
// `react-native-get-random-values` behavior.
|
|
20
|
+
installGetRandomValues()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { BASE64_LOOKUP } from '../constants'
|
|
2
|
+
|
|
3
|
+
export function base64ToBytes(base64: string): Uint8Array {
|
|
4
|
+
// Standard base64 (NO_WRAP on Android, no line breaks on iOS), may be padded.
|
|
5
|
+
const clean = base64.replace(/\s/g, '')
|
|
6
|
+
|
|
7
|
+
if (clean.length === 0) {
|
|
8
|
+
return new Uint8Array(0)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (clean.length % 4 !== 0) {
|
|
12
|
+
throw new Error('Invalid base64 string length')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let padding = 0
|
|
16
|
+
|
|
17
|
+
if (clean.endsWith('==')) {
|
|
18
|
+
padding = 2
|
|
19
|
+
} else if (clean.endsWith('=')) {
|
|
20
|
+
padding = 1
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const outputLength = (clean.length / 4) * 3 - padding
|
|
24
|
+
const output = new Uint8Array(outputLength)
|
|
25
|
+
|
|
26
|
+
let outIndex = 0
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < clean.length; i += 4) {
|
|
29
|
+
const a = BASE64_LOOKUP[clean.charCodeAt(i)] ?? -1
|
|
30
|
+
|
|
31
|
+
const b = BASE64_LOOKUP[clean.charCodeAt(i + 1)] ?? -1
|
|
32
|
+
|
|
33
|
+
const c =
|
|
34
|
+
clean[i + 2] === '=' ? 0 : (BASE64_LOOKUP[clean.charCodeAt(i + 2)] ?? -1)
|
|
35
|
+
|
|
36
|
+
const d =
|
|
37
|
+
clean[i + 3] === '=' ? 0 : (BASE64_LOOKUP[clean.charCodeAt(i + 3)] ?? -1)
|
|
38
|
+
|
|
39
|
+
if (a < 0 || b < 0 || c < 0 || d < 0) {
|
|
40
|
+
throw new Error('Invalid base64 character')
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const triplet = (a << 18) | (b << 12) | (c << 6) | d
|
|
44
|
+
|
|
45
|
+
if (outIndex < outputLength) {
|
|
46
|
+
output[outIndex++] = (triplet >> 16) & 0xff
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (outIndex < outputLength) {
|
|
50
|
+
output[outIndex++] = (triplet >> 8) & 0xff
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (outIndex < outputLength) {
|
|
54
|
+
output[outIndex++] = triplet & 0xff
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return output
|
|
59
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
declare const __DEV__: boolean | undefined
|
|
2
|
+
|
|
3
|
+
export function isRemoteDebuggingInChrome(): boolean {
|
|
4
|
+
const g = globalThis as any
|
|
5
|
+
|
|
6
|
+
// Remote debugging in Chrome is not supported in bridgeless mode.
|
|
7
|
+
if ('RN$Bridgeless' in g && g.RN$Bridgeless === true) {
|
|
8
|
+
return false
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
return (
|
|
13
|
+
__DEV__ !== undefined && __DEV__ && g.nativeCallSyncHook === undefined
|
|
14
|
+
)
|
|
15
|
+
} catch {
|
|
16
|
+
return false
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { MAX_BYTE_LENGTH } from '../constants'
|
|
2
|
+
import { getNativeGetRandomValues } from '../NativeGetRandomValues'
|
|
3
|
+
import { base64ToBytes } from './base64-decode'
|
|
4
|
+
import { isRemoteDebuggingInChrome } from './chrome'
|
|
5
|
+
import { insecureRandomValues } from './insecure-random-values'
|
|
6
|
+
|
|
7
|
+
function isAllowedArray(value: unknown): value is ArrayBufferView {
|
|
8
|
+
if (!ArrayBuffer.isView(value) || value instanceof DataView) {
|
|
9
|
+
return false
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// WebCrypto only allows integer TypedArrays (no float16/32/64).
|
|
13
|
+
return (
|
|
14
|
+
value instanceof Int8Array ||
|
|
15
|
+
value instanceof Uint8Array ||
|
|
16
|
+
value instanceof Uint8ClampedArray ||
|
|
17
|
+
value instanceof Int16Array ||
|
|
18
|
+
value instanceof Uint16Array ||
|
|
19
|
+
value instanceof Int32Array ||
|
|
20
|
+
value instanceof Uint32Array ||
|
|
21
|
+
value instanceof BigInt64Array ||
|
|
22
|
+
value instanceof BigUint64Array
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type GetRandomValuesFn = <T extends ArrayBufferView>(array: T) => T
|
|
27
|
+
|
|
28
|
+
export function getRandomValues<T extends ArrayBufferView>(array: T): T {
|
|
29
|
+
if (!isAllowedArray(array)) {
|
|
30
|
+
throw new TypeError(
|
|
31
|
+
'crypto.getRandomValues: expected an integer TypedArray (e.g. Uint8Array)',
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (array.byteLength > MAX_BYTE_LENGTH) {
|
|
36
|
+
// Browsers throw QuotaExceededError (DOMException). RN Hermes may not
|
|
37
|
+
// have DOMException, so use a plain Error with the same name.
|
|
38
|
+
const error = new Error(
|
|
39
|
+
`crypto.getRandomValues: byteLength ${array.byteLength} exceeds ${MAX_BYTE_LENGTH}`,
|
|
40
|
+
)
|
|
41
|
+
error.name = 'QuotaExceededError'
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (array.byteLength === 0) {
|
|
46
|
+
return array
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Expo SDK 48+: prefer ExpoCrypto when available so managed workflow
|
|
50
|
+
// apps don't need our native module linked.
|
|
51
|
+
|
|
52
|
+
const expoGetRandomValues = (globalThis as any)?.expo?.modules?.ExpoCrypto
|
|
53
|
+
?.getRandomValues as ((array: ArrayBufferView) => void) | undefined
|
|
54
|
+
|
|
55
|
+
if (typeof expoGetRandomValues === 'function') {
|
|
56
|
+
// ExpoCrypto mutates the array in place and returns void.
|
|
57
|
+
expoGetRandomValues.call((globalThis as any).expo.modules.ExpoCrypto, array)
|
|
58
|
+
return array
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Remote debugging in Chrome can't call sync native methods
|
|
62
|
+
// ("Calling synchronous methods on native modules is not supported in
|
|
63
|
+
// Chrome"), so fall back to Math.random() there.
|
|
64
|
+
if (isRemoteDebuggingInChrome()) {
|
|
65
|
+
return insecureRandomValues(array)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const base64 = getNativeGetRandomValues().getRandomBase64(array.byteLength)
|
|
69
|
+
const bytes = base64ToBytes(base64)
|
|
70
|
+
|
|
71
|
+
if (bytes.byteLength !== array.byteLength) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`crypto.getRandomValues: native returned ${bytes.byteLength} bytes, expected ${array.byteLength}`,
|
|
74
|
+
)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Write through to the underlying buffer so BigInt arrays work too.
|
|
78
|
+
new Uint8Array(array.buffer, array.byteOffset, array.byteLength).set(bytes)
|
|
79
|
+
|
|
80
|
+
return array
|
|
81
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
let insecureWarned = false
|
|
2
|
+
|
|
3
|
+
export function insecureRandomValues<T extends ArrayBufferView>(array: T): T {
|
|
4
|
+
if (!insecureWarned) {
|
|
5
|
+
console.warn(
|
|
6
|
+
'crypto.getRandomValues: using an insecure random number generator, ' +
|
|
7
|
+
'this should only happen when running in a debugger without support for crypto.getRandomValues',
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
insecureWarned = true
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Fill through a Uint8 view so BigInt arrays work too.
|
|
14
|
+
const bytes = new Uint8Array(array.buffer, array.byteOffset, array.byteLength)
|
|
15
|
+
|
|
16
|
+
for (let i = 0, r = 0; i < bytes.length; i++) {
|
|
17
|
+
if ((i & 0x03) === 0) {
|
|
18
|
+
r = Math.random() * 0x100000000
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
bytes[i] = (r >>> ((i & 0x03) << 3)) & 0xff
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return array
|
|
25
|
+
}
|