Skip to content

Commit

Permalink
Merge branch 'v3-alpha' into v3-alpha-bugfix/bindgen-build-flags
Browse files Browse the repository at this point in the history
  • Loading branch information
leaanthony authored Jan 23, 2025
2 parents 3dc549a + 16ce1d3 commit 13e7082
Show file tree
Hide file tree
Showing 16 changed files with 134 additions and 63 deletions.
3 changes: 3 additions & 0 deletions docs/src/content/docs/changelog.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `window-call` example to demonstrate how to know which window is calling a service by [@leaanthony](https://github.com/leaanthony)
- Better panic handling by [@leaanthony](https://github.com/leaanthony)
- New Menu guide by [@leaanthony](https://github.com/leaanthony)
- Add doc comments for Service API by [@fbbdev](https://github.com/fbbdev) in [#4024](https://github.com/wailsapp/wails/pull/4024)
- Add function `application.NewServiceWithOptions` to initialise services with additional configuration by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)

### Fixed

Expand All @@ -53,6 +55,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Removed `application.WindowIDKey` and `application.WindowNameKey` (replaced by `application.WindowKey`) by [@leaanthony](https://github.com/leaanthony)
- In JS/TS bindings, class fields of fixed-length array types are now initialized with their expected length instead of being empty by [@fbbdev](https://github.com/fbbdev) in [#4001](https://github.com/wailsapp/wails/pull/4001)
- ContextMenuData now returns a string instead of any by [@leaanthony](https://github.com/leaanthony)
- `application.NewService` does not accept options as an optional parameter anymore (use `application.NewServiceWithOptions` instead) by [@leaanthony](https://github.com/leaanthony) in [#4024](https://github.com/wailsapp/wails/pull/4024)

## v3.0.0-alpha.9 - 2025-01-13

Expand Down
15 changes: 10 additions & 5 deletions docs/src/content/docs/guides/panic-handling.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -69,16 +69,21 @@ If you want to see the full stack trace, you can use the `FullStackTrace` field.

## Default Panic Handler

If you don't specify a custom panic handler, Wails will use its default handler which outputs error information in a formatted log message. For example:
If you don't specify a custom panic handler, Wails will use its default handler which outputs error information in a formatted log message and then quits.
For example:

```
Jan 16 21:18:05.649 ERR panic error: oh no! something went wrong deep in my service! :(
************************ FATAL ******************************
* There has been a catastrophic failure in your application *
********************* Error Details *************************
panic error: oh no! something went wrong deep in my service! :(
main.(*WindowService).call2
at E:/wails/v3/examples/panic-handling/main.go:24
at E:/wails/v3/examples/panic-handling/main.go:23
main.(*WindowService).call1
at E:/wails/v3/examples/panic-handling/main.go:20
at E:/wails/v3/examples/panic-handling/main.go:19
main.(*WindowService).GeneratePanic
at E:/wails/v3/examples/panic-handling/main.go:16
at E:/wails/v3/examples/panic-handling/main.go:15
*************************************************************
```

## Custom Panic Handler
Expand Down
28 changes: 24 additions & 4 deletions docs/src/content/docs/learn/services.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,24 @@ app := application.New(application.Options{
})
```

This registers the `NewMyService` function as a service with the application.

Services may also be registered with additional options:

```go
app := application.New(application.Options{
Services: []application.Service{
application.NewServiceWithOptions(NewMyService(), application.ServiceOptions{
// ...
})
}
})
```

ServiceOptions has the following fields:
- Name - Specify a custom name for the Service
- Route - A route to bind the Service to the frontend (more on this below)

## Optional Methods

Services can implement optional methods to hook into the application lifecycle.
Expand All @@ -67,8 +85,10 @@ bindings generated for a service, so they are not exposed to your frontend.
func (s *Service) ServiceName() string
```

This method returns the name of the service. It is used for logging purposes
only.
This method returns the name of the service. By default, it will the struct name of the Service but can be
overridden with the `Name` field of the `ServiceOptions`.

It is used for logging purposes only.

### ServiceStartup

Expand Down Expand Up @@ -101,7 +121,7 @@ your service to act as an HTTP handler. The route of the handler is defined in
the service options:

```go
application.NewService(fileserver.New(&fileserver.Config{
application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
RootPath: rootPath,
}), application.ServiceOptions{
Route: "/files",
Expand Down Expand Up @@ -144,7 +164,7 @@ We can now use this service in our application:
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewService(fileserver.New(&fileserver.Config{
application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
RootPath: rootPath,
}), application.ServiceOptions{
Route: "/files",
Expand Down
2 changes: 1 addition & 1 deletion v3/examples/panic-handling/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ func main() {
fmt.Printf("Time: %s\n", panicDetails.Time)
fmt.Printf("Error: %s\n", panicDetails.Error)
fmt.Printf("Stacktrace: %s\n", panicDetails.StackTrace)
application.InfoDialog().SetMessage("There was a panic!").Show()
},
})

Expand All @@ -56,5 +57,4 @@ func main() {
if err != nil {
log.Fatal(err)
}

}
2 changes: 1 addition & 1 deletion v3/examples/services/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func main() {
AutoSave: true,
})),
application.NewService(log.New()),
application.NewService(fileserver.New(&fileserver.Config{
application.NewServiceWithOptions(fileserver.New(&fileserver.Config{
RootPath: rootPath,
}), application.ServiceOptions{
Route: "/files",
Expand Down
5 changes: 5 additions & 0 deletions v3/internal/commands/bindings.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func GenerateBindings(options *flags.GenerateBindingsOptions, patterns []string)
pluralise(stats.NumModels, "Model"),
stats.Elapsed().String(),
)
if spinner != nil {
spinner.Info(resultMessage)
} else {
term.Infofln("%s", resultMessage)
}

// Report output directory.
term.Infof("Output directory: %s", absPath)
Expand Down
8 changes: 4 additions & 4 deletions v3/internal/generator/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@ import (
// ErrNoContextPackage indicates that
// the canonical path for the standard context package
// did not match any actual package.
var ErrNoContextPackage = errors.New("standard context package not found at canonical import path ('context'): is the Wails v3 module properly installed?")
var ErrNoContextPackage = errors.New("standard context package not found at canonical import path ('context'): is the Wails v3 module properly installed? ")

// ErrNoApplicationPackage indicates that
// the canonical path for the Wails application package
// did not match any actual package.
var ErrNoApplicationPackage = errors.New("Wails application package not found at canonical import path ('" + config.WailsAppPkgPath + "'): is the Wails v3 module properly installed?")
var ErrNoApplicationPackage = errors.New("Wails application package not found at canonical import path ('" + config.WailsAppPkgPath + "'): is the Wails v3 module properly installed? ")

// ErrBadApplicationPackage indicates that
// the Wails application package has invalid content.
var ErrBadApplicationPackage = errors.New("package " + config.WailsAppPkgPath + ": function NewService has wrong signature: is the Wails v3 module properly installed?")
var ErrBadApplicationPackage = errors.New("package " + config.WailsAppPkgPath + ": function NewService has wrong signature: is the Wails v3 module properly installed? ")

// ErrNoPackages is returned by [Generator.Generate]
// when [LoadPackages] returns no error and no packages.
Expand All @@ -43,7 +43,7 @@ type ErrorReport struct {
errors map[string]bool
}

// NewError report initialises an ErrorReport instance
// NewErrorReport report initialises an ErrorReport instance
// with the provided Logger implementation.
//
// If logger is nil, messages will be accumulated but not logged.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
".Service10",
".Service11",
".Service12",
"/other.Service13"
".Service13",
"/other.Service14"
]
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package main

import "github.com/wailsapp/wails/v3/pkg/application"

func ServiceInitialiser[T any]() func(*T, ...application.ServiceOptions) application.Service {
func ServiceInitialiser[T any]() func(*T) application.Service {
return application.NewService[T]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type Service9 struct{}
type Service10 struct{}
type Service11 struct{}
type Service12 struct{}
type Service13 struct{}

func main() {
factory := NewFactory[Service1, Service2]()
Expand All @@ -36,6 +37,7 @@ func main() {
ServiceInitialiser[Service6]()(&Service6{}),
other.CustomNewService(Service7{}),
other.ServiceInitialiser[Service8]()(&Service8{}),
application.NewServiceWithOptions(&Service13{}, application.ServiceOptions{Name: "custom name"}),
other.LocalService,
},
CustomNewServices[Service9, Service10]()...),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ func CustomNewService[T any](srv T) application.Service {
return application.NewService(&srv)
}

func ServiceInitialiser[T any]() func(*T, ...application.ServiceOptions) application.Service {
func ServiceInitialiser[T any]() func(*T) application.Service {
return application.NewService[T]
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package other

import "github.com/wailsapp/wails/v3/pkg/application"

type Service13 int
type Service14 int

var LocalService = application.NewService(new(Service13))
var LocalService = application.NewService(new(Service14))
13 changes: 5 additions & 8 deletions v3/pkg/application/application.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"net/http"
"os"
"runtime"
"runtime/debug"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -419,13 +418,11 @@ func (a *App) ResetEvents() {

func (a *App) handleFatalError(err error) {
var buffer strings.Builder
buffer.WriteString("*********************** FATAL ***********************")
buffer.WriteString("There has been a catastrophic failure in your application.")
buffer.WriteString("Please report this error at https://github.com/wailsapp/wails/issues")
buffer.WriteString("******************** Error Details ******************")
buffer.WriteString(fmt.Sprintf("Message: " + err.Error()))
buffer.WriteString(fmt.Sprintf("Stack: " + string(debug.Stack())))
buffer.WriteString("*********************** FATAL ***********************")
buffer.WriteString("\n\n************************ FATAL ******************************\n")
buffer.WriteString("* There has been a catastrophic failure in your application *\n")
buffer.WriteString("********************* Error Details *************************\n")
buffer.WriteString(err.Error())
buffer.WriteString("*************************************************************\n")
a.handleError(fmt.Errorf(buffer.String()))
os.Exit(1)
}
Expand Down
34 changes: 0 additions & 34 deletions v3/pkg/application/application_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,6 @@ import (
"github.com/wailsapp/wails/v3/internal/assetserver"
)

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

type ServiceOptions struct {
// Name can be set to override the name of the service
// This is useful for logging and debugging purposes
Name string
// Route is the path to the assets
Route string
}

var DefaultServiceOptions = ServiceOptions{
Route: "",
}

// NewService returns a Service value wrapping the given pointer.
// If T is not a named type, the returned value is invalid.
// The prefix is used if Service implements a http.Handler only one allowed
func NewService[T any](instance *T, options ...ServiceOptions) Service {
if len(options) == 1 {
return Service{instance, options[0]}
}
return Service{instance, DefaultServiceOptions}
}

func (s Service) Instance() any {
return s.instance
}

// Options contains the options for the application
type Options struct {
// Name is the name of the application (used in the default about box)
Expand Down
2 changes: 1 addition & 1 deletion v3/pkg/application/panic_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,5 +103,5 @@ func processPanic(panicDetails *PanicDetails) {

func defaultPanicHandler(panicDetails *PanicDetails) {
errorMessage := fmt.Sprintf("panic error: %s\n%s", panicDetails.Error.Error(), panicDetails.StackTrace)
globalApplication.error(errorMessage)
globalApplication.fatal(errorMessage)
}
72 changes: 72 additions & 0 deletions v3/pkg/application/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,86 @@ import (
"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
}

// 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.
//
// If the return value is non-nil, it is logged along with the service name,
// the startup process aborts and the application quits.
// 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 the return value is non-nil, it is logged along with the service name.
type ServiceShutdown interface {
ServiceShutdown() error
}
Expand Down

0 comments on commit 13e7082

Please sign in to comment.