Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package core

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

// ActionServiceStartup is a message sent when the application's services are starting up.
// This provides a hook for services to perform initialization tasks.
type ActionServiceStartup struct{}

// ActionServiceShutdown is a message sent when the application is shutting down.
// This allows services to perform cleanup tasks, such as saving state or closing resources.
type ActionServiceShutdown struct{}

// ActionDisplayOpenWindow is a structured message for requesting a new window.
Expand Down
43 changes: 43 additions & 0 deletions core.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ import (
)

// New initialises a Core instance using the provided options and performs the necessary setup.
// It is the primary entry point for creating a new Core application.
//
// Example:
//
// core, err := core.New(
// core.WithService(&MyService{}),
// core.WithAssets(assets),
// )
func New(opts ...Option) (*Core, error) {
c := &Core{
services: make(map[string]any),
Expand All @@ -37,6 +45,20 @@ func New(opts ...Option) (*Core, error) {
// WithService creates an Option that registers a service. It automatically discovers
// the service name from its package path and registers its IPC handler if it
// implements a method named `HandleIPCEvents`.
//
// Example:
//
// // In myapp/services/calculator.go
// package services
//
// type Calculator struct{}
//
// func (s *Calculator) Add(a, b int) int { return a + b }
//
// // In main.go
// import "myapp/services"
//
// core.New(core.WithService(services.NewCalculator))
func WithService(factory func(*Core) (any, error)) Option {
return func(c *Core) error {
serviceInstance, err := factory(c)
Expand Down Expand Up @@ -83,20 +105,27 @@ func WithName(name string, factory func(*Core) (any, error)) Option {
}
}

// WithWails creates an Option that injects the Wails application instance into the Core.
// This is essential for services that need to interact with the Wails runtime.
func WithWails(app *application.App) Option {
return func(c *Core) error {
c.App = app
return nil
}
}

// WithAssets creates an Option that registers the application's embedded assets.
// This is necessary for the application to be able to serve its frontend.
func WithAssets(fs embed.FS) Option {
return func(c *Core) error {
c.assets = fs
return nil
}
}

// WithServiceLock creates an Option that prevents any further services from being
// registered after the Core has been initialized. This is a security measure to
// prevent late-binding of services that could have unintended consequences.
func WithServiceLock() Option {
return func(c *Core) error {
c.serviceLock = true
Expand All @@ -106,14 +135,20 @@ func WithServiceLock() Option {

// --- Core Methods ---

// ServiceStartup is the entry point for the Core service's startup lifecycle.
// It is called by Wails when the application starts.
func (c *Core) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
return c.ACTION(ActionServiceStartup{})
}

// ServiceShutdown is the entry point for the Core service's shutdown lifecycle.
// It is called by Wails when the application shuts down.
func (c *Core) ServiceShutdown(ctx context.Context) error {
return c.ACTION(ActionServiceShutdown{})
}

// ACTION dispatches a message to all registered IPC handlers.
// This is the primary mechanism for services to communicate with each other.
func (c *Core) ACTION(msg Message) error {
c.ipcMu.RLock()
handlers := append([]func(*Core, Message) error(nil), c.ipcHandlers...)
Expand All @@ -128,18 +163,21 @@ func (c *Core) ACTION(msg Message) error {
return agg
}

// RegisterAction adds a new IPC handler to the Core.
func (c *Core) RegisterAction(handler func(*Core, Message) error) {
c.ipcMu.Lock()
c.ipcHandlers = append(c.ipcHandlers, handler)
c.ipcMu.Unlock()
}

// RegisterActions adds multiple IPC handlers to the Core.
func (c *Core) RegisterActions(handlers ...func(*Core, Message) error) {
c.ipcMu.Lock()
c.ipcHandlers = append(c.ipcHandlers, handlers...)
c.ipcMu.Unlock()
}

// RegisterService adds a new service to the Core.
func (c *Core) RegisterService(name string, api any) error {
if c.servicesLocked {
return fmt.Errorf("core: service %q is not permitted by the serviceLock setting", name)
Expand All @@ -156,6 +194,8 @@ func (c *Core) RegisterService(name string, api any) error {
return nil
}

// Service retrieves a registered service by name.
// It returns nil if the service is not found.
func (c *Core) Service(name string) any {
c.serviceMu.RLock()
api, ok := c.services[name]
Expand Down Expand Up @@ -191,6 +231,7 @@ func MustServiceFor[T any](c *Core, name string) T {
}

// App returns the global application instance.
// It panics if the Core has not been initialized.
func App() *application.App {
if instance == nil {
panic("core.App() called before core.Setup() was successfully initialized")
Expand All @@ -210,8 +251,10 @@ func (c *Core) Display() Display {
return display
}

// Core returns the Core instance itself.
func (c *Core) Core() *Core { return c }

// Assets returns the embedded filesystem containing the application's assets.
func (c *Core) Assets() embed.FS {
return c.assets
}
28 changes: 27 additions & 1 deletion interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ import (
// in the Core framework. Services depend on these interfaces, not on
// concrete implementations.

// Contract specifies the operational guarantees that the Core and its services must adhere to.
// This is used for configuring panic handling and other resilience features.
type Contract struct {
DontPanic bool
// DontPanic, if true, instructs the Core to recover from panics and return an error instead.
DontPanic bool
// DisableLogging, if true, disables all logging from the Core and its services.
DisableLogging bool
}

// Features provides a way to check if a feature is enabled.
// This is used for feature flagging and conditional logic.
type Features struct {
// Flags is a list of enabled feature flags.
Flags []string
}

Expand All @@ -31,8 +37,16 @@ func (f *Features) IsEnabled(feature string) bool {
}
return false
}

// Option is a function that configures the Core.
// This is used to apply settings and register services during initialization.
type Option func(*Core) error

// Message is the interface for all messages that can be sent through the Core's IPC system.
// Any struct can be a message, allowing for structured data to be passed between services.
type Message interface{}

// Core is the central application object that manages services, assets, and communication.
type Core struct {
once sync.Once
initErr error
Expand All @@ -51,7 +65,9 @@ var instance *Core

// Config provides access to application configuration.
type Config interface {
// Get retrieves a configuration value by key and stores it in the 'out' variable.
Get(key string, out any) error
// Set stores a configuration value by key.
Set(key string, v any) error
}

Expand All @@ -66,23 +82,29 @@ type WindowConfig struct {

// WindowOption configures window creation.
type WindowOption interface {
// Apply applies the window option to the given configuration.
Apply(*WindowConfig)
}

// Display manages windows and UI.
type Display interface {
// OpenWindow creates and displays a new window with the given options.
OpenWindow(opts ...WindowOption) error
}

// Help manages the in-app documentation and help system.
type Help interface {
// Show displays the main help topic.
Show() error
// ShowAt displays the help topic for the given anchor.
ShowAt(anchor string) error
}

// Crypt provides cryptographic functions.
type Crypt interface {
// EncryptPGP encrypts data using PGP and writes the result to the given writer.
EncryptPGP(writer io.Writer, recipientPath, data string, signerPath, signerPassphrase *string) (string, error)
// DecryptPGP decrypts a PGP message.
DecryptPGP(recipientPath, message, passphrase string, signerPath *string) (string, error)
}

Expand All @@ -96,8 +118,12 @@ type I18n interface {

// Workspace manages user workspaces.
type Workspace interface {
// CreateWorkspace creates a new workspace with the given identifier and password.
CreateWorkspace(identifier, password string) (string, error)
// SwitchWorkspace changes the active workspace.
SwitchWorkspace(name string) error
// WorkspaceFileGet retrieves the content of a file from the current workspace.
WorkspaceFileGet(filename string) (string, error)
// WorkspaceFileSet writes content to a file in the current workspace.
WorkspaceFileSet(filename, content string) error
}
5 changes: 4 additions & 1 deletion runtime.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
package core

// ServiceRuntime is a helper struct embedded in services to provide access to the core application.
// It is generic and can be parameterized with a service-specific options struct.
type ServiceRuntime[T any] struct {
core *Core
opts T
}

// NewServiceRuntime creates a new ServiceRuntime instance for a service.
// This is typically called by a service's constructor.
func NewServiceRuntime[T any](c *Core, opts T) *ServiceRuntime[T] {
return &ServiceRuntime[T]{
core: c,
opts: opts,
}
}

// Core returns the central core instance.
// Core returns the central core instance, providing access to all registered services.
func (r *ServiceRuntime[T]) Core() *Core {
return r.core
}

// Config returns the registered Config service from the core application.
// This is a convenience method for accessing the application's configuration.
func (r *ServiceRuntime[T]) Config() Config {
return r.core.Config()
}
8 changes: 8 additions & 0 deletions runtime_pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,19 @@ import (

// Runtime is the container that holds all instantiated services.
// Its fields are the concrete types, allowing Wails to bind them directly.
// This struct is the primary entry point for the Wails application.
type Runtime struct {
app *application.App
Core *Core
}

// ServiceFactory defines a function that creates a service instance.
// This is used to decouple the service creation from the runtime initialization.
type ServiceFactory func() (any, error)

// NewWithFactories creates a new Runtime instance using the provided service factories.
// This is the most flexible way to create a new Runtime, as it allows for
// the registration of any number of services.
func NewWithFactories(app *application.App, factories map[string]ServiceFactory) (*Runtime, error) {
services := make(map[string]any)
coreOpts := []Option{
Expand Down Expand Up @@ -58,6 +62,8 @@ func NewWithFactories(app *application.App, factories map[string]ServiceFactory)
}

// NewRuntime creates and wires together all application services.
// This is the simplest way to create a new Runtime, but it does not allow for
// the registration of any custom services.
func NewRuntime(app *application.App) (*Runtime, error) {
return NewWithFactories(app, map[string]ServiceFactory{})
}
Expand All @@ -68,11 +74,13 @@ func (r *Runtime) ServiceName() string {
}

// ServiceStartup is called by Wails at application startup.
// This is where the Core's startup lifecycle is initiated.
func (r *Runtime) ServiceStartup(ctx context.Context, options application.ServiceOptions) {
r.Core.ServiceStartup(ctx, options)
}

// ServiceShutdown is called by Wails at application shutdown.
// This is where the Core's shutdown lifecycle is initiated.
func (r *Runtime) ServiceShutdown(ctx context.Context) {
if r.Core != nil {
r.Core.ServiceShutdown(ctx)
Expand Down
Loading