helpers.go raw
1 //go:build js && wasm
2
3 package wasmdb
4
5 import (
6 "syscall/js"
7
8 "github.com/hack-pad/safejs"
9 )
10
11 // safeValueToBytes converts a safejs.Value to a []byte
12 // This handles Uint8Array, ArrayBuffer, and strings from IndexedDB
13 func safeValueToBytes(val safejs.Value) []byte {
14 if val.IsUndefined() || val.IsNull() {
15 return nil
16 }
17
18 // Get global Uint8Array and ArrayBuffer constructors
19 uint8ArrayType := safejs.MustGetGlobal("Uint8Array")
20 arrayBufferType := safejs.MustGetGlobal("ArrayBuffer")
21
22 // Check if it's a Uint8Array
23 isUint8Array, _ := val.InstanceOf(uint8ArrayType)
24 if isUint8Array {
25 length, err := val.Length()
26 if err != nil {
27 return nil
28 }
29 buf := make([]byte, length)
30 // Copy bytes - we need to iterate since safejs doesn't have CopyBytesToGo
31 for i := 0; i < length; i++ {
32 elem, err := val.Index(i)
33 if err != nil {
34 return nil
35 }
36 intVal, err := elem.Int()
37 if err != nil {
38 return nil
39 }
40 buf[i] = byte(intVal)
41 }
42 return buf
43 }
44
45 // Check if it's an ArrayBuffer
46 isArrayBuffer, _ := val.InstanceOf(arrayBufferType)
47 if isArrayBuffer {
48 // Create a Uint8Array view of the ArrayBuffer
49 uint8Array, err := uint8ArrayType.New(val)
50 if err != nil {
51 return nil
52 }
53 return safeValueToBytes(uint8Array)
54 }
55
56 // Try to treat it as a typed array-like object
57 length, err := val.Length()
58 if err == nil && length > 0 {
59 buf := make([]byte, length)
60 for i := 0; i < length; i++ {
61 elem, err := val.Index(i)
62 if err != nil {
63 return nil
64 }
65 intVal, err := elem.Int()
66 if err != nil {
67 return nil
68 }
69 buf[i] = byte(intVal)
70 }
71 return buf
72 }
73
74 // Last resort: check if it's a string (for string keys in IndexedDB)
75 if val.Type() == safejs.TypeString {
76 str, err := val.String()
77 if err == nil {
78 return []byte(str)
79 }
80 }
81
82 return nil
83 }
84
85 // bytesToSafeValue converts a []byte to a safejs.Value (Uint8Array)
86 func bytesToSafeValue(buf []byte) safejs.Value {
87 if buf == nil {
88 return safejs.Null()
89 }
90
91 uint8Array := js.Global().Get("Uint8Array").New(len(buf))
92 js.CopyBytesToJS(uint8Array, buf)
93 return safejs.Safe(uint8Array)
94 }
95
96 // cryptoRandom fills the provided byte slice with cryptographically secure random bytes
97 // using the Web Crypto API (crypto.getRandomValues) or Node.js crypto.randomFillSync
98 func cryptoRandom(buf []byte) error {
99 if len(buf) == 0 {
100 return nil
101 }
102
103 // First try browser's crypto.getRandomValues
104 crypto := js.Global().Get("crypto")
105 if crypto.IsUndefined() {
106 // Fallback to msCrypto for older IE
107 crypto = js.Global().Get("msCrypto")
108 }
109
110 if !crypto.IsUndefined() {
111 // Try getRandomValues (browser API)
112 getRandomValues := crypto.Get("getRandomValues")
113 if !getRandomValues.IsUndefined() && getRandomValues.Type() == js.TypeFunction {
114 // Create a Uint8Array to receive random bytes
115 uint8Array := js.Global().Get("Uint8Array").New(len(buf))
116
117 // Call crypto.getRandomValues - may throw in Node.js
118 defer func() {
119 // Recover from panic if this method doesn't work
120 recover()
121 }()
122 getRandomValues.Invoke(uint8Array)
123
124 // Copy the random bytes to our Go slice
125 js.CopyBytesToGo(buf, uint8Array)
126 return nil
127 }
128
129 // Try randomFillSync (Node.js API)
130 randomFillSync := crypto.Get("randomFillSync")
131 if !randomFillSync.IsUndefined() && randomFillSync.Type() == js.TypeFunction {
132 uint8Array := js.Global().Get("Uint8Array").New(len(buf))
133 randomFillSync.Invoke(uint8Array)
134 js.CopyBytesToGo(buf, uint8Array)
135 return nil
136 }
137 }
138
139 // Try to load Node.js crypto module via require
140 requireFunc := js.Global().Get("require")
141 if !requireFunc.IsUndefined() && requireFunc.Type() == js.TypeFunction {
142 nodeCrypto := requireFunc.Invoke("crypto")
143 if !nodeCrypto.IsUndefined() {
144 randomFillSync := nodeCrypto.Get("randomFillSync")
145 if !randomFillSync.IsUndefined() && randomFillSync.Type() == js.TypeFunction {
146 uint8Array := js.Global().Get("Uint8Array").New(len(buf))
147 randomFillSync.Invoke(uint8Array)
148 js.CopyBytesToGo(buf, uint8Array)
149 return nil
150 }
151 }
152 }
153
154 return errNoCryptoAPI
155 }
156
157 // errNoCryptoAPI is returned when the Web Crypto API is not available
158 type cryptoAPIError struct{}
159
160 func (cryptoAPIError) Error() string { return "Web Crypto API not available" }
161
162 var errNoCryptoAPI = cryptoAPIError{}
163