@bsv/sdk 1.8.2 → 1.8.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsv/sdk",
3
- "version": "1.8.2",
3
+ "version": "1.8.3",
4
4
  "type": "module",
5
5
  "description": "BSV Blockchain Software Development Kit",
6
6
  "main": "dist/cjs/mod.js",
@@ -1,6 +1,25 @@
1
+ /**
2
+ * Random number generator that works across modern JavaScript environments.
3
+ *
4
+ * This implementation uses the Web Crypto API which is available in:
5
+ * - Node.js 6+ via require('crypto').randomBytes()
6
+ * - Node.js 18+ via globalThis.crypto
7
+ * - Modern browsers via globalThis.crypto, self.crypto, or window.crypto
8
+ * - Web Workers and Service Workers via self.crypto
9
+ * - Deno and Bun via globalThis.crypto
10
+ * - React Native (requires react-native-get-random-values polyfill)
11
+ *
12
+ * @throws {Error} If no secure random number generator is available
13
+ */
1
14
  class Rand {
2
15
  _rand: (n: number) => number[] // ✅ Explicit function type
3
16
 
17
+ getRandomValues (obj: any, n: number): number[] {
18
+ const arr = new Uint8Array(n)
19
+ obj.crypto.getRandomValues(arr)
20
+ return Array.from(arr)
21
+ }
22
+
4
23
  constructor () {
5
24
  const noRand = (): never => {
6
25
  throw new Error(
@@ -10,29 +29,79 @@ class Rand {
10
29
 
11
30
  this._rand = noRand // Assign the function
12
31
 
13
- if (typeof self === 'object') {
14
- /* eslint-disable-next-line */
15
- if (self.crypto?.getRandomValues) {
16
- this._rand = (n) => {
17
- const arr = new Uint8Array(n)
18
- /* eslint-disable-next-line */
19
- self.crypto.getRandomValues(arr);
20
- return [...arr]
21
- }
22
- } /* if (typeof window === 'object') */ else {
23
- this._rand = noRand
32
+ // Try globalThis.crypto (works in Node.js 18+, modern browsers, and Deno)
33
+ if (typeof globalThis !== 'undefined' && typeof (globalThis as any).crypto?.getRandomValues === 'function') {
34
+ this._rand = (n) => {
35
+ /* eslint-disable-next-line */
36
+ return this.getRandomValues(globalThis as any, n)
24
37
  }
25
- } else {
38
+ return
39
+ }
40
+
41
+ // Node.js fallback for versions < 18
42
+ if (typeof process !== 'undefined' && process.release?.name === 'node') {
26
43
  try {
27
- /* eslint-disable-next-line */
28
- const crypto = require("crypto");
44
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
45
+ const crypto = require('crypto')
29
46
  if (typeof crypto.randomBytes === 'function') {
30
- this._rand = (n: number) => [...crypto.randomBytes(n)]
47
+ this._rand = (n) => {
48
+ return Array.from(crypto.randomBytes(n))
49
+ }
50
+ return
31
51
  }
32
- } catch {
33
- this._rand = noRand
52
+ } catch (e) {
53
+ // crypto module not available, continue to other checks
34
54
  }
35
55
  }
56
+
57
+ // Try self.crypto (Web Workers and Service Workers)
58
+ if (typeof self !== 'undefined' && typeof self.crypto?.getRandomValues === 'function') {
59
+ this._rand = (n) => {
60
+ /* eslint-disable-next-line */
61
+ return this.getRandomValues(self as any, n)
62
+ }
63
+ return
64
+ }
65
+
66
+ // Try window.crypto (browsers)
67
+ if (typeof window !== 'undefined' && typeof (window as any).crypto?.getRandomValues === 'function') {
68
+ this._rand = (n) => {
69
+ /* eslint-disable-next-line */
70
+ return this.getRandomValues(window as any, n)
71
+ }
72
+ return
73
+ }
74
+
75
+ // React Native support - try to load polyfill
76
+ if (typeof navigator !== 'undefined' && (navigator as any).product === 'ReactNative') {
77
+ try {
78
+ // Try to require the polyfill - this will populate globalThis.crypto
79
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
80
+ require('react-native-get-random-values')
81
+
82
+ if (typeof (globalThis as any).crypto?.getRandomValues === 'function') {
83
+ this._rand = (n) => {
84
+ /* eslint-disable-next-line */
85
+ return this.getRandomValues(globalThis as any, n)
86
+ }
87
+ return
88
+ }
89
+ } catch (e) {
90
+ // Polyfill not available - provide helpful error
91
+ this._rand = (): never => {
92
+ throw new Error(
93
+ 'React Native detected but crypto is not available. ' +
94
+ 'Please install and import "react-native-get-random-values" at the top of your entry file:\n' +
95
+ 'npm install react-native-get-random-values\n' +
96
+ 'Then add: import "react-native-get-random-values" to your index.js/App.js'
97
+ )
98
+ }
99
+ return
100
+ }
101
+ }
102
+
103
+ // No crypto available
104
+ this._rand = noRand
36
105
  }
37
106
 
38
107
  generate (len: number): number[] {
@@ -11,4 +11,25 @@ describe('Random', () => {
11
11
  // I haven't seen it fail yet. If you see it fail, please let me know.
12
12
  expect(Random(32)).not.toEqual(Random(32))
13
13
  })
14
+ it('Produces values in valid byte range (0-255)', () => {
15
+ const bytes = Random(100)
16
+ bytes.forEach(byte => {
17
+ expect(byte).toBeGreaterThanOrEqual(0)
18
+ expect(byte).toBeLessThanOrEqual(255)
19
+ })
20
+ })
21
+ it('Works with various lengths including edge cases', () => {
22
+ expect(Random(1).length).toBe(1)
23
+ expect(Random(16).length).toBe(16)
24
+ expect(Random(32).length).toBe(32)
25
+ expect(Random(64).length).toBe(64)
26
+ expect(Random(256).length).toBe(256)
27
+ })
28
+ it('Returns an array of numbers', () => {
29
+ const bytes = Random(10)
30
+ expect(Array.isArray(bytes)).toBe(true)
31
+ bytes.forEach(byte => {
32
+ expect(typeof byte).toBe('number')
33
+ })
34
+ })
14
35
  })