163 lines
4.2 KiB
Go
163 lines
4.2 KiB
Go
//go:build js && wasm
|
|
|
|
package wasmdb
|
|
|
|
import (
|
|
"syscall/js"
|
|
|
|
"github.com/hack-pad/safejs"
|
|
)
|
|
|
|
// safeValueToBytes converts a safejs.Value to a []byte
|
|
// This handles Uint8Array, ArrayBuffer, and strings from IndexedDB
|
|
func safeValueToBytes(val safejs.Value) []byte {
|
|
if val.IsUndefined() || val.IsNull() {
|
|
return nil
|
|
}
|
|
|
|
// Get global Uint8Array and ArrayBuffer constructors
|
|
uint8ArrayType := safejs.MustGetGlobal("Uint8Array")
|
|
arrayBufferType := safejs.MustGetGlobal("ArrayBuffer")
|
|
|
|
// Check if it's a Uint8Array
|
|
isUint8Array, _ := val.InstanceOf(uint8ArrayType)
|
|
if isUint8Array {
|
|
length, err := val.Length()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
buf := make([]byte, length)
|
|
// Copy bytes - we need to iterate since safejs doesn't have CopyBytesToGo
|
|
for i := 0; i < length; i++ {
|
|
elem, err := val.Index(i)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
intVal, err := elem.Int()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
buf[i] = byte(intVal)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// Check if it's an ArrayBuffer
|
|
isArrayBuffer, _ := val.InstanceOf(arrayBufferType)
|
|
if isArrayBuffer {
|
|
// Create a Uint8Array view of the ArrayBuffer
|
|
uint8Array, err := uint8ArrayType.New(val)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return safeValueToBytes(uint8Array)
|
|
}
|
|
|
|
// Try to treat it as a typed array-like object
|
|
length, err := val.Length()
|
|
if err == nil && length > 0 {
|
|
buf := make([]byte, length)
|
|
for i := 0; i < length; i++ {
|
|
elem, err := val.Index(i)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
intVal, err := elem.Int()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
buf[i] = byte(intVal)
|
|
}
|
|
return buf
|
|
}
|
|
|
|
// Last resort: check if it's a string (for string keys in IndexedDB)
|
|
if val.Type() == safejs.TypeString {
|
|
str, err := val.String()
|
|
if err == nil {
|
|
return []byte(str)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// bytesToSafeValue converts a []byte to a safejs.Value (Uint8Array)
|
|
func bytesToSafeValue(buf []byte) safejs.Value {
|
|
if buf == nil {
|
|
return safejs.Null()
|
|
}
|
|
|
|
uint8Array := js.Global().Get("Uint8Array").New(len(buf))
|
|
js.CopyBytesToJS(uint8Array, buf)
|
|
return safejs.Safe(uint8Array)
|
|
}
|
|
|
|
// cryptoRandom fills the provided byte slice with cryptographically secure random bytes
|
|
// using the Web Crypto API (crypto.getRandomValues) or Node.js crypto.randomFillSync
|
|
func cryptoRandom(buf []byte) error {
|
|
if len(buf) == 0 {
|
|
return nil
|
|
}
|
|
|
|
// First try browser's crypto.getRandomValues
|
|
crypto := js.Global().Get("crypto")
|
|
if crypto.IsUndefined() {
|
|
// Fallback to msCrypto for older IE
|
|
crypto = js.Global().Get("msCrypto")
|
|
}
|
|
|
|
if !crypto.IsUndefined() {
|
|
// Try getRandomValues (browser API)
|
|
getRandomValues := crypto.Get("getRandomValues")
|
|
if !getRandomValues.IsUndefined() && getRandomValues.Type() == js.TypeFunction {
|
|
// Create a Uint8Array to receive random bytes
|
|
uint8Array := js.Global().Get("Uint8Array").New(len(buf))
|
|
|
|
// Call crypto.getRandomValues - may throw in Node.js
|
|
defer func() {
|
|
// Recover from panic if this method doesn't work
|
|
recover()
|
|
}()
|
|
getRandomValues.Invoke(uint8Array)
|
|
|
|
// Copy the random bytes to our Go slice
|
|
js.CopyBytesToGo(buf, uint8Array)
|
|
return nil
|
|
}
|
|
|
|
// Try randomFillSync (Node.js API)
|
|
randomFillSync := crypto.Get("randomFillSync")
|
|
if !randomFillSync.IsUndefined() && randomFillSync.Type() == js.TypeFunction {
|
|
uint8Array := js.Global().Get("Uint8Array").New(len(buf))
|
|
randomFillSync.Invoke(uint8Array)
|
|
js.CopyBytesToGo(buf, uint8Array)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// Try to load Node.js crypto module via require
|
|
requireFunc := js.Global().Get("require")
|
|
if !requireFunc.IsUndefined() && requireFunc.Type() == js.TypeFunction {
|
|
nodeCrypto := requireFunc.Invoke("crypto")
|
|
if !nodeCrypto.IsUndefined() {
|
|
randomFillSync := nodeCrypto.Get("randomFillSync")
|
|
if !randomFillSync.IsUndefined() && randomFillSync.Type() == js.TypeFunction {
|
|
uint8Array := js.Global().Get("Uint8Array").New(len(buf))
|
|
randomFillSync.Invoke(uint8Array)
|
|
js.CopyBytesToGo(buf, uint8Array)
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return errNoCryptoAPI
|
|
}
|
|
|
|
// errNoCryptoAPI is returned when the Web Crypto API is not available
|
|
type cryptoAPIError struct{}
|
|
|
|
func (cryptoAPIError) Error() string { return "Web Crypto API not available" }
|
|
|
|
var errNoCryptoAPI = cryptoAPIError{}
|