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