mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-06 11:52:04 +08:00
196 lines
5.0 KiB
Go
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
|
|
}
|
|
}
|