Files
iris/auth/http_wasm.go
Hugo Nijhuis 00d98879d3
Some checks failed
CI / build (push) Failing after 36s
Initial iris repository structure
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>
2026-01-08 19:23:49 +01:00

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)
}