5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-06 11:52:04 +08:00
wails/v3/pkg/application/messageprocessor_call.go
2025-02-25 03:33:56 +01:00

196 lines
5.0 KiB
Go

package application
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
)
type contextKey string
const (
CallBinding = 0
WindowKey contextKey = "Window"
)
func (m *MessageProcessor) callErrorCallback(window Window, message string, callID *string, err error) {
m.Error(message, "id", *callID, "error", err)
if cerr := (*CallError)(nil); errors.As(err, &cerr) {
if data, jsonErr := json.Marshal(cerr); jsonErr == nil {
window.CallError(*callID, string(data), true)
return
} else {
m.Error("Unable to convert data to JSON. Please report this to the Wails team!", "id", *callID, "error", jsonErr)
}
}
window.CallError(*callID, err.Error(), false)
}
func (m *MessageProcessor) callCallback(window Window, callID *string, result string) {
window.CallResponse(*callID, result)
}
func (m *MessageProcessor) processCallCancelMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
args, err := params.Args()
if err != nil {
m.httpError(rw, "Invalid binding call:", fmt.Errorf("unable to parse arguments: %w", err))
return
}
callID := args.String("call-id")
if callID == nil || *callID == "" {
m.httpError(rw, "Invalid binding call:", errors.New("missing argument 'call-id'"))
return
}
var cancel func()
func() {
m.l.Lock()
defer m.l.Unlock()
cancel = m.runningCalls[*callID]
}()
if cancel != nil {
cancel()
m.Info("Binding call canceled:", "id", *callID)
}
m.ok(rw)
}
func (m *MessageProcessor) processCallMethod(method int, rw http.ResponseWriter, r *http.Request, window Window, params QueryParams) {
args, err := params.Args()
if err != nil {
m.httpError(rw, "Invalid binding call:", fmt.Errorf("unable to parse arguments: %w", err))
return
}
callID := args.String("call-id")
if callID == nil || *callID == "" {
m.httpError(rw, "Invalid binding call:", errors.New("missing argument 'call-id'"))
return
}
switch method {
case CallBinding:
var options CallOptions
err := params.ToStruct(&options)
if err != nil {
m.httpError(rw, "Invalid binding call:", fmt.Errorf("error parsing call options: %w", err))
return
}
ctx, cancel := context.WithCancel(context.WithoutCancel(r.Context()))
// Schedule cancel in case panics happen before starting the call.
cancelRequired := true
defer func() {
if cancelRequired {
cancel()
}
}()
ambiguousID := false
func() {
m.l.Lock()
defer m.l.Unlock()
if m.runningCalls[*callID] != nil {
ambiguousID = true
} else {
m.runningCalls[*callID] = cancel
}
}()
if ambiguousID {
m.httpError(rw, "Invalid binding call:", fmt.Errorf("ambiguous call id: %s", *callID))
return
}
m.ok(rw) // From now on, failures are reported through the error callback.
// Log call
var methodRef any = options.MethodName
if options.MethodName == "" {
methodRef = options.MethodID
}
m.Info("Binding call started:", "id", *callID, "method", methodRef)
go func() {
defer handlePanic()
defer func() {
m.l.Lock()
defer m.l.Unlock()
delete(m.runningCalls, *callID)
}()
defer cancel()
var boundMethod *BoundMethod
if options.MethodName != "" {
boundMethod = globalApplication.bindings.Get(&options)
if boundMethod == nil {
m.callErrorCallback(window, "Binding call failed:", callID, &CallError{
Kind: ReferenceError,
Message: fmt.Sprintf("unknown bound method name '%s'", options.MethodName),
})
return
}
} else {
boundMethod = globalApplication.bindings.GetByID(options.MethodID)
if boundMethod == nil {
m.callErrorCallback(window, "Binding call failed:", callID, &CallError{
Kind: ReferenceError,
Message: fmt.Sprintf("unknown bound method id %d", options.MethodID),
})
return
}
}
// Prepare args for logging. This should never fail since json.Unmarshal succeeded before.
jsonArgs, _ := json.Marshal(options.Args)
var jsonResult []byte
defer func() {
m.Info("Binding call complete:", "id", *callID, "method", boundMethod, "args", string(jsonArgs), "result", string(jsonResult))
}()
// Set the context values for the window
if window != nil {
ctx = context.WithValue(ctx, WindowKey, window)
}
result, err := boundMethod.Call(ctx, options.Args)
if cerr := (*CallError)(nil); errors.As(err, &cerr) {
switch cerr.Kind {
case ReferenceError, TypeError:
m.callErrorCallback(window, "Binding call failed:", callID, cerr)
case RuntimeError:
m.callErrorCallback(window, "Bound method returned an error:", callID, cerr)
}
return
}
if result != nil {
// convert result to json
jsonResult, err = json.Marshal(result)
if err != nil {
m.callErrorCallback(window, "Binding call failed:", callID, &CallError{
Kind: TypeError,
Message: fmt.Sprintf("error marshaling result: %s", err),
})
return
}
}
m.callCallback(window, callID, string(jsonResult))
}()
cancelRequired = false
default:
m.httpError(rw, "Invalid binding call:", fmt.Errorf("unknown method: %d", method))
return
}
}