5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-06 10:50:45 +08:00
wails/v3/pkg/application/services.go
Fabio Massaioli e7c134de4e
[v3] Late service registration and error handling overhaul (#4066)
* Add service registration method

* Fix error handling and formatting in messageprocessor

* Add configurable error handling

* Improve error strings

* Fix service shutdown on macOS

* Add post shutdown hook

* Better fatal errors

* Add startup/shutdown sequence tests

* Improve debug messages

* Update JS runtime

* Update docs

* Update changelog

* Fix log message in clipboard message processor

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Remove panic in RegisterService

* Fix linux tests (hopefully)

* Fix error formatting everywhere

* Fix typo in windows webview

* Tidy example mods

* Set application name in tests

* Fix ubuntu test workflow

* Cleanup template test pipeline

* Fix dev build detection on Go 1.24

* Update template go.mod/sum to Go 1.24

* Remove redundant caching in template tests

* Final format string cleanup

* Fix wails3 tool references

* Fix legacy log calls

* Remove formatJS and simplify format strings

* Fix indirect import

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2025-02-19 09:27:41 +01:00

135 lines
4.8 KiB
Go

package application
import (
"context"
"reflect"
)
// Service wraps a bound type instance.
// The zero value of Service is invalid.
// Valid values may only be obtained by calling [NewService].
type Service struct {
instance any
options ServiceOptions
}
// ServiceOptions provides optional parameters for calls to [NewService].
type ServiceOptions struct {
// Name can be set to override the name of the service
// for logging and debugging purposes.
//
// If empty, it will default
// either to the value obtained through the [ServiceName] interface,
// or to the type name.
Name string
// If the service instance implements [http.Handler],
// it will be mounted on the internal asset server
// at the prefix specified by Route.
Route string
// MarshalError will be called if non-nil
// to marshal to JSON the error values returned by this service's methods.
//
// MarshalError is not allowed to fail,
// but it may return a nil slice to fall back
// to the globally configured error handler.
//
// If the returned slice is not nil, it must contain valid JSON.
MarshalError func(error) []byte
}
// DefaultServiceOptions specifies the default values of service options,
// used when no [ServiceOptions] instance is provided to [NewService].
var DefaultServiceOptions = ServiceOptions{}
// NewService returns a Service value wrapping the given pointer.
// If T is not a concrete named type, the returned value is invalid.
func NewService[T any](instance *T) Service {
return Service{instance, DefaultServiceOptions}
}
// NewServiceWithOptions returns a Service value wrapping the given pointer
// and specifying the given service options.
// If T is not a concrete named type, the returned value is invalid.
func NewServiceWithOptions[T any](instance *T, options ServiceOptions) Service {
service := NewService(instance) // Delegate to NewService so that the static analyser may detect T. Do not remove this call.
service.options = options
return service
}
// Instance returns the service instance provided to [NewService].
func (s Service) Instance() any {
return s.instance
}
// ServiceName returns the name of the service
//
// This is an *optional* method that may be implemented by service instances.
// It is used for logging and debugging purposes.
//
// If a non-empty name is provided with [ServiceOptions],
// it takes precedence over the one returned by the ServiceName method.
type ServiceName interface {
ServiceName() string
}
// ServiceStartup is an *optional* method that may be implemented by service instances.
//
// This method will be called during application startup and will receive a copy of the options
// specified at creation time. It can be used for initialising resources.
//
// The context will be valid as long as the application is running,
// and will be canceled right before shutdown.
//
// Services are guaranteed to receive the startup notification
// in the exact order in which they were either
// listed in [Options.Services] or registered with [App.RegisterService],
// with those from [Options.Services] coming first.
//
// If the return value is non-nil, the startup process aborts
// and [App.Run] returns the error wrapped with [fmt.Errorf]
// in a user-friendly message comprising the service name.
// The original error can be retrieved either by calling the Unwrap method
// or through the [errors.As] API.
//
// When that happens, service instances that have been already initialised
// receive a shutdown notification.
type ServiceStartup interface {
ServiceStartup(ctx context.Context, options ServiceOptions) error
}
// ServiceShutdown is an *optional* method that may be implemented by service instances.
//
// This method will be called during application shutdown. It can be used for cleaning up resources.
// If a service has received a startup notification,
// then it is guaranteed to receive a shutdown notification too,
// except in case of unhandled panics during shutdown.
//
// Services receive shutdown notifications in reverse registration order,
// after all user-provided shutdown hooks have run (see [App.OnShutdown]).
//
// If the return value is non-nil, it is passed to the application's
// configured error handler at [Options.ErrorHandler],
// wrapped with [fmt.Errorf] in a user-friendly message comprising the service name.
// The default behaviour is to log the error along with the service name.
// The original error can be retrieved either by calling the Unwrap method
// or through the [errors.As] API.
type ServiceShutdown interface {
ServiceShutdown() error
}
func getServiceName(service Service) string {
if service.options.Name != "" {
return service.options.Name
}
// Check if the service implements the ServiceName interface
if s, ok := service.Instance().(ServiceName); ok {
return s.ServiceName()
}
// Finally, get the name from the type.
return reflect.TypeOf(service.Instance()).Elem().String()
}