Some checks failed
CI / build (push) Failing after 36s
WASM reactive UI framework for Go: - reactive/ - Signal[T], Effect, Runtime - ui/ - Button, Text, Input, View, Canvas, SVG components - navigation/ - Router, guards, history management - auth/ - OIDC client for WASM applications - host/ - Static file server Extracted from arcadia as open-source component. Co-Authored-By: Claude <noreply@anthropic.com>
196 lines
4.8 KiB
Go
196 lines
4.8 KiB
Go
//go:build js && wasm
|
|
|
|
package auth
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"syscall/js"
|
|
)
|
|
|
|
// WASMHTTPClient handles HTTP requests in WASM environment
|
|
type WASMHTTPClient struct{}
|
|
|
|
// HTTPResult represents the result of an HTTP request
|
|
type HTTPResult struct {
|
|
Data []byte
|
|
Error error
|
|
}
|
|
|
|
// HTTPCallback is called when HTTP request completes
|
|
type HTTPCallback func(result HTTPResult)
|
|
|
|
// FetchJSON performs a GET request and unmarshals JSON response
|
|
// This method blocks and should only be called from the main thread
|
|
func (c *WASMHTTPClient) FetchJSON(url string, dest interface{}) error {
|
|
result := make(chan HTTPResult, 1)
|
|
|
|
c.FetchJSONAsync(url, func(r HTTPResult) {
|
|
result <- r
|
|
})
|
|
|
|
r := <-result
|
|
if r.Error != nil {
|
|
return r.Error
|
|
}
|
|
|
|
return json.Unmarshal(r.Data, dest)
|
|
}
|
|
|
|
// FetchJSONAsync performs a GET request asynchronously using callback
|
|
func (c *WASMHTTPClient) FetchJSONAsync(url string, callback HTTPCallback) {
|
|
// Create fetch promise
|
|
promise := js.Global().Call("fetch", url)
|
|
|
|
// Success handler
|
|
var successFunc js.Func
|
|
successFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
response := args[0]
|
|
if !response.Get("ok").Bool() {
|
|
callback(HTTPResult{
|
|
Error: fmt.Errorf("HTTP %d: %s", response.Get("status").Int(), response.Get("statusText").String()),
|
|
})
|
|
successFunc.Release()
|
|
return nil
|
|
}
|
|
|
|
// Get response text
|
|
textPromise := response.Call("text")
|
|
var textFunc js.Func
|
|
textFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
text := args[0].String()
|
|
callback(HTTPResult{
|
|
Data: []byte(text),
|
|
})
|
|
// Cleanup after callback completes
|
|
textFunc.Release()
|
|
successFunc.Release()
|
|
return nil
|
|
})
|
|
|
|
// Error handler for text promise
|
|
var textErrorFunc js.Func
|
|
textErrorFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
callback(HTTPResult{
|
|
Error: fmt.Errorf("failed to read response text: %v", args[0]),
|
|
})
|
|
// Cleanup
|
|
textFunc.Release()
|
|
textErrorFunc.Release()
|
|
successFunc.Release()
|
|
return nil
|
|
})
|
|
|
|
textPromise.Call("then", textFunc).Call("catch", textErrorFunc)
|
|
return nil
|
|
})
|
|
|
|
// Error handler
|
|
var errorFunc js.Func
|
|
errorFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
callback(HTTPResult{
|
|
Error: fmt.Errorf("fetch error: %s", args[0].String()),
|
|
})
|
|
// Cleanup after callback completes
|
|
errorFunc.Release()
|
|
successFunc.Release()
|
|
return nil
|
|
})
|
|
|
|
promise.Call("then", successFunc).Call("catch", errorFunc)
|
|
}
|
|
|
|
// PostForm performs a POST request with form data
|
|
func (c *WASMHTTPClient) PostForm(url string, data url.Values, dest interface{}) error {
|
|
result := make(chan HTTPResult, 1)
|
|
|
|
c.PostFormAsync(url, data, func(r HTTPResult) {
|
|
result <- r
|
|
})
|
|
|
|
r := <-result
|
|
if r.Error != nil {
|
|
return r.Error
|
|
}
|
|
|
|
return json.Unmarshal(r.Data, dest)
|
|
}
|
|
|
|
// PostFormAsync performs a POST request asynchronously using callback
|
|
func (c *WASMHTTPClient) PostFormAsync(url string, data url.Values, callback HTTPCallback) {
|
|
// Prepare fetch options
|
|
headers := map[string]interface{}{
|
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
}
|
|
|
|
options := map[string]interface{}{
|
|
"method": "POST",
|
|
"headers": headers,
|
|
"body": data.Encode(),
|
|
}
|
|
|
|
// Convert options to JS object
|
|
jsOptions := js.ValueOf(options)
|
|
|
|
// Make the request
|
|
promise := js.Global().Call("fetch", url, jsOptions)
|
|
|
|
// Success handler
|
|
var successFunc js.Func
|
|
successFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
response := args[0]
|
|
if !response.Get("ok").Bool() {
|
|
callback(HTTPResult{
|
|
Error: fmt.Errorf("HTTP %d: %s", response.Get("status").Int(), response.Get("statusText").String()),
|
|
})
|
|
successFunc.Release()
|
|
return nil
|
|
}
|
|
|
|
// Get response text
|
|
textPromise := response.Call("text")
|
|
var textFunc js.Func
|
|
textFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
text := args[0].String()
|
|
callback(HTTPResult{
|
|
Data: []byte(text),
|
|
})
|
|
// Cleanup after callback completes
|
|
textFunc.Release()
|
|
successFunc.Release()
|
|
return nil
|
|
})
|
|
|
|
// Error handler for text promise
|
|
var textErrorFunc js.Func
|
|
textErrorFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
callback(HTTPResult{
|
|
Error: fmt.Errorf("failed to read response text: %v", args[0]),
|
|
})
|
|
// Cleanup
|
|
textFunc.Release()
|
|
textErrorFunc.Release()
|
|
successFunc.Release()
|
|
return nil
|
|
})
|
|
|
|
textPromise.Call("then", textFunc).Call("catch", textErrorFunc)
|
|
return nil
|
|
})
|
|
|
|
// Error handler
|
|
var errorFunc js.Func
|
|
errorFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
callback(HTTPResult{
|
|
Error: fmt.Errorf("fetch error: %s", args[0].String()),
|
|
})
|
|
// Cleanup after callback completes
|
|
errorFunc.Release()
|
|
successFunc.Release()
|
|
return nil
|
|
})
|
|
|
|
promise.Call("then", successFunc).Call("catch", errorFunc)
|
|
}
|