updater

package
v3.0.0-alpha2.113 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 4, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

Package updater provides an in-app self-update facility for Wails v3 applications.

The package exposes a single Updater that is reachable as `app.Updater`. Configure it once via Init, then call Check / DownloadAndInstall / CheckAndInstall to drive the update flow.

Update sources are pluggable through the Provider interface. The Updater owns verification, atomic writes, the binary swap and the default window; providers only describe how to look up and stream a release.

Subscribe to lifecycle events through the standard Wails event system — both Go and JavaScript subscribe the same way:

app.Event.On(updater.EventDownloadProgress, func(e *application.CustomEvent) {
    var p updater.Progress
    _ = json.Unmarshal(e.JSON(), &p)
})

wails.Events.On("wails:updater:download-progress", (e) => { /* ... */ })

Index

Constants

View Source
const (
	// EventCheckStarted fires before a Check round trip. Payload: nil.
	EventCheckStarted = "wails:updater:check-started"

	// EventUpdateAvailable fires when Check returns a newer release. Payload: *Release.
	EventUpdateAvailable = "wails:updater:update-available"

	// EventNoUpdate fires when Check confirms the caller is up to date. Payload: nil.
	EventNoUpdate = "wails:updater:no-update"

	// EventDownloadStarted fires when the Updater begins streaming bytes from
	// a provider. Payload: *Release.
	EventDownloadStarted = "wails:updater:download-started"

	// EventDownloadProgress fires periodically during download (~10/sec). Payload: Progress.
	EventDownloadProgress = "wails:updater:download-progress"

	// EventDownloadComplete fires once all bytes are on disk and the file has
	// been closed, but BEFORE verification. Payload: *Release.
	EventDownloadComplete = "wails:updater:download-complete"

	// EventVerifying fires when the Updater begins verifying the downloaded
	// artifact. Payload: *Release.
	EventVerifying = "wails:updater:verifying"

	// EventInstalling fires when the Updater begins swapping the binary.
	// Payload: *Release.
	EventInstalling = "wails:updater:installing"

	// EventUpdateReady fires when an update is installed and a restart is
	// pending. Payload: *Release.
	EventUpdateReady = "wails:updater:update-ready"

	// EventError fires whenever any stage fails. Payload: ErrorInfo.
	EventError = "wails:updater:error"

	// EventMeta fires once per session before the first state-snapshot
	// replay, carrying host-side context the page can't derive from any
	// Release: the version currently running, and the version the user
	// has marked skipped (or "" if none). Payload: Meta.
	EventMeta = "wails:updater:meta"
)

Event names emitted by the Updater. Subscribe in Go via app.Event.On(name, ...) or in JavaScript via wails.Events.On(name, ...). Payload types are documented inline next to each constant.

View Source
const (
	EventWindowReady = "wails:updater:window:ready"
	EventUserInstall = "wails:updater:user:install"
	EventUserSkip    = "wails:updater:user:skip"
	EventUserRemind  = "wails:updater:user:remind"
	EventUserCancel  = "wails:updater:user:cancel"
	EventUserRestart = "wails:updater:user:restart"
)

User-action event names emitted by the default window and any custom template that follows the same contract. The Updater listens to these to drive the flow without ever calling into the window directly.

Variables

View Source
var (
	ErrAlreadyConfigured  = errors.New("updater: Init already called")
	ErrNotConfigured      = errors.New("updater: Init has not been called")
	ErrNoPendingRelease   = errors.New("updater: no pending release (call Check first)")
	ErrDownloadInProgress = errors.New("updater: download already in progress")
)
View Source
var (
	// ErrNotReady is returned by Restart when there is no installed update
	// staged for launch.
	ErrNotReady = errors.New("updater: nothing to restart into (call DownloadAndInstall first)")
)

Functions

func HandleHelperMode

func HandleHelperMode()

HandleHelperMode returns immediately when the current process was not spawned as an updater helper. When it WAS spawned as a helper it performs the swap, relaunches the application, and calls os.Exit — it never returns in that case.

The Wails application package calls this from application.New so that `app.Updater.Restart` works without users wiring anything by hand.

Types

type Artifact

type Artifact struct {
	Filename string `json:"filename"`
	Filetype string `json:"filetype,omitempty"`
	Size     int64  `json:"size,omitempty"`
	Platform string `json:"platform,omitempty"`
	Arch     string `json:"arch,omitempty"`
}

Artifact describes the file to download for a Release on the running platform.

type BuiltinWindow

type BuiltinWindow struct {
	// HTML, if non-empty, replaces the default template entirely. The
	// replacement is expected to listen to `updater:*` events and emit
	// `updater:user:*` events using the standard Wails runtime — exactly
	// what the default template does.
	HTML string

	// CSS, if non-empty, is appended to the default template inside a final
	// <style> tag. Ignored when HTML overrides the template entirely.
	CSS string

	// Options overrides the chrome of the built-in window. Zero values fall
	// back to the framework's sensible defaults (small, centred, resizable).
	Options WindowOptions
}

BuiltinWindow customises the framework's default update window. Embed it in Config.Window when you want to override the HTML, layer extra CSS, or change the window chrome (frameless, size, etc.).

type CheckRequest

type CheckRequest struct {
	CurrentVersion string
	Platform       string
	Arch           string
}

CheckRequest carries platform context to a Provider's Check method. The Updater fills in defaults from runtime.GOOS / runtime.GOARCH when the user does not supply them via Config.

type Config

type Config struct {
	// CurrentVersion is the version currently running. Required.
	// Pass the same string you use to tag releases (e.g. "1.2.3" — no "v" prefix).
	CurrentVersion string

	// Providers are the update sources, tried in order. The first to return
	// a release is used; if a provider returns (nil, nil) the Updater treats
	// the application as up to date and stops walking the chain (fallback is
	// for "primary unreachable", not "providers disagree"). Required.
	Providers []Provider

	// PublicKey is the trust root used to verify release signatures. It is
	// the ONLY trust anchor for signature verification — the release source
	// cannot substitute its own key, since the whole point of pinning a key
	// here is to bind verification to a value the application developer set
	// at build time and the release feed cannot influence.
	//
	// When unset, releases that carry only a Digest still install (the digest
	// is checked against the streaming hash), but any release that carries a
	// Signature is rejected. Strongly recommended for any app whose
	// distribution channel might be compromised or proxied.
	PublicKey []byte

	// CheckInterval, when non-zero, makes the Updater poll providers on a
	// timer in the background. Pop-up-on-found behaviour is the same as a
	// manual Check finding an update.
	CheckInterval time.Duration

	// Platform / Arch / Channel override the per-platform defaults passed to
	// each Provider's Check. Leave empty to use runtime.GOOS / runtime.GOARCH
	// and the provider's default channel.
	Platform string
	Arch     string
	Channel  string

	// Window controls how the update UI is rendered. Not yet wired in v1 of
	// the package; see the upcoming Window option types.
	Window WindowOption
}

Config configures the Updater. Pass it to Updater.Init.

type ErrorInfo

type ErrorInfo struct {
	Stage    Stage  `json:"stage"`
	Message  string `json:"message"`
	Provider string `json:"provider,omitempty"`
}

ErrorInfo is the payload of EventError.

type Host

type Host interface {
	// Emit a custom event with the supplied data.
	Emit(name string, data ...any) bool

	// OnEvent registers a callback for a custom event. The returned function
	// removes the listener.
	OnEvent(name string, callback func(payload any)) func()

	// OpenWindow creates and shows an update window. Implementations build
	// a real Wails webview window from opts; tests return a recorder.
	OpenWindow(opts WindowOptions) WindowHandle

	// Quit asks the host application to begin its normal shutdown sequence.
	// Called by Restart after the helper has been spawned so the helper's
	// "wait for parent to exit" step actually completes.
	Quit()
}

Host is the minimal surface the Updater needs from the application that owns it. The application package implements this via an adapter; tests stub it.

type Meta

type Meta struct {
	CurrentVersion string `json:"currentVersion"`
	SkippedVersion string `json:"skippedVersion,omitempty"`
}

Meta is the payload of EventMeta — host-side context the default window template uses to render the "from" version in the update pill and the "v1.2.3 · This is the latest version" pill in the up-to-date state.

type Progress

type Progress struct {
	Written  int64   `json:"written"`
	Total    int64   `json:"total"`
	Rate     float64 `json:"rate"` // bytes/sec smoothed over the last ~1s
	Provider string  `json:"provider,omitempty"`
}

Progress is the payload of EventDownloadProgress.

type Provider

type Provider interface {
	// Name identifies the provider in logs and in the `provider` field of
	// progress / error event payloads. Should be stable and short
	// (e.g. "github", "keygen.sh", "appcast").
	Name() string

	// Check returns the available upgrade for the running platform. Return
	// (nil, nil) when the source confirms the caller is already up to date.
	// Errors here put the Updater into the fallback chain.
	Check(ctx context.Context, req CheckRequest) (*Release, error)

	// Download streams the artifact bytes for r to dst. Providers should
	// invoke onProgress periodically; it is never nil.
	Download(ctx context.Context, r *Release, dst io.Writer, onProgress func(written, total int64)) error
}

Provider abstracts an update source. Implementations are typically tiny: resolve the next release for the running platform, then stream the bytes. Everything else (verification, atomic write, swap, restart, events) is the Updater's job.

type Release

type Release struct {
	Version      string         `json:"version"`
	Channel      string         `json:"channel,omitempty"`
	Name         string         `json:"name,omitempty"`
	Notes        string         `json:"notes,omitempty"`
	PublishedAt  time.Time      `json:"publishedAt,omitempty"`
	Artifact     Artifact       `json:"artifact"`
	Verification *Verification  `json:"verification,omitempty"`
	Metadata     map[string]any `json:"metadata,omitempty"`

	// Provider is set by the Updater after Check; provider implementations
	// should leave it empty. Used to route a follow-up Download back to the
	// same source.
	Provider string `json:"provider,omitempty"`
}

Release is what a Provider returns from Check when there is something newer.

type Stage

type Stage string

Stage describes which phase of the update flow produced an error.

const (
	StageCheck    Stage = "check"
	StageDownload Stage = "download"
	StageVerify   Stage = "verify"
	StageInstall  Stage = "install"
)

type State

type State string

State is the high-level lifecycle phase the Updater is currently in.

const (
	StateUnconfigured State = "unconfigured"
	StateIdle         State = "idle"
	StateChecking     State = "checking"
	StateUpToDate     State = "up-to-date"
	StateAvailable    State = "available"
	StateDownloading  State = "downloading"
	StateVerifying    State = "verifying"
	StateInstalling   State = "installing"
	StateReady        State = "ready"
	StateError        State = "error"
)

type Updater

type Updater struct {
	// contains filtered or unexported fields
}

Updater is the singleton exposed as app.Updater. It is constructed during application initialisation but does nothing useful until Init is called.

func New

func New(host Host) *Updater

New is for internal use by the application package and tests. End users obtain an Updater via app.Updater (or app.Updater.Init).

func (*Updater) Check

func (u *Updater) Check(ctx context.Context) (*Release, error)

Check walks the provider chain looking for an upgrade. Returns:

  • (rel, nil): newer release found; payload of EventUpdateAvailable.
  • (nil, nil): caller is up to date; payload of EventNoUpdate.
  • (nil, err): all providers errored; err wraps every failure.

Fallback semantics: a provider returning (nil, nil) short-circuits to up-to-date — fallback exists for "primary unreachable", not "providers disagree about what's latest." A provider returning an error advances to the next one. If every provider errors, Check returns an error built from the chain.

func (*Updater) CheckAndInstall

func (u *Updater) CheckAndInstall(ctx context.Context) error

CheckAndInstall is the convenience method: it opens the update window (unless Config.Window == WindowNone) and runs Check + DownloadAndInstall. Returns nil with no side effects if the application is already up to date.

The window stays open for the duration of the flow AND across the "up-to-date" / error terminal states — the user dismisses it via the Close button. Opening + immediately closing on the no-update branch produced a perceptible flicker on every check; keeping the window up so the "You're up to date" panel actually renders matches what users expect from system-style updaters.

Apps that want silent background polling should use Config.Window = updater.WindowNone (no window ever opens) or invoke Check() directly and subscribe to EventNoUpdate / EventUpdateAvailable themselves.

func (*Updater) CurrentVersion

func (u *Updater) CurrentVersion() string

CurrentVersion returns the version supplied at Init time, or "" if not configured.

func (*Updater) DownloadAndInstall

func (u *Updater) DownloadAndInstall(ctx context.Context) error

DownloadAndInstall downloads the pending release (set by a previous Check), verifies it, and stages it for swap. Returns ErrNoPendingRelease if Check did not produce one. Returns ErrDownloadInProgress if another download is already running. The actual binary swap is performed in a follow-up commit; v1 of this branch stages the verified file and reports StateReady + EventUpdateReady.

func (*Updater) DownloadedPath

func (u *Updater) DownloadedPath() string

DownloadedPath returns the on-disk path of the last successfully-installed (staged) update, or "" if none.

func (*Updater) Init

func (u *Updater) Init(cfg Config) error

Init configures the Updater. Returns ErrAlreadyConfigured if Init has already been called, or a validation error if cfg is malformed.

func (*Updater) Restart

func (u *Updater) Restart(_ context.Context) error

Restart performs the full restart-into-the-new-version dance: it spawns a helper-mode child (the same binary with sentinel env vars set) and then asks the host application to begin its shutdown sequence via Host.Quit. Once the running process exits, the helper performs the binary swap and relaunches the (now-replaced) application.

Returns ErrNotReady if DownloadAndInstall has not produced an installed artifact yet. If the helper spawn fails the error is surfaced and Quit is not called — the caller's process stays alive on the old binary.

On success Restart returns once the helper has started and Quit has been dispatched. The caller's process will exit asynchronously as the host's normal shutdown unwinds.

func (*Updater) SkipVersion

func (u *Updater) SkipVersion(v string)

SkipVersion records the supplied version as skipped — subsequent Checks will treat that version as "no update." Used by the default window's Skip This Version button and by callers driving headless flows.

func (*Updater) SkippedVersion

func (u *Updater) SkippedVersion() string

SkippedVersion returns the currently-skipped version, if any.

func (*Updater) State

func (u *Updater) State() State

State returns the Updater's current high-level lifecycle phase.

func (*Updater) StopPeriodicCheck

func (u *Updater) StopPeriodicCheck()

StopPeriodicCheck cancels the timer started by Init when Config.CheckInterval > 0 and blocks until the polling goroutine has returned (so callers can safely inspect provider state afterward). Safe to call when no periodic check was configured.

type Verification

type Verification struct {
	DigestAlgo    string `json:"digestAlgo,omitempty"`    // "sha256", "sha512"
	Digest        []byte `json:"digest,omitempty"`        // raw digest bytes
	SignatureAlgo string `json:"signatureAlgo,omitempty"` // "ed25519", "ed25519ph", "ecdsa-p256"
	Signature     []byte `json:"signature,omitempty"`     // raw signature bytes
}

Verification carries everything the Updater needs to authenticate the downloaded bytes. A Provider populates this from the release source; the Updater verifies against it using its configured trust root.

Either Digest+DigestAlgo, Signature+SignatureAlgo, or both may be present. When both are present, both are checked; either failing fails the update.

Signature verification always uses Config.PublicKey as the trust root. The release source has no say in which key authenticates it — that is the entire point of pinning a key out-of-band at build time. Releases that ship a Signature without a configured Config.PublicKey fail closed.

type WindowHandle

type WindowHandle interface {
	EmitEvent(name string, data ...any) bool
	Show()
	Close()
}

WindowHandle is the minimal API the Updater drives once a window is open. The application adapter satisfies this around a *WebviewWindow.

SetHTML is deliberately omitted — loading HTML after construction puts it on about:blank where the Wails runtime isn't injected (so JS event emits no-op on webkit2gtk and in some WebView2 configurations). Templates are passed via WindowOptions.InitialHTML at construction time instead.

type WindowOption

type WindowOption interface {
	// contains filtered or unexported methods
}

WindowOption is the marker interface for the Window slot on Config. The set of concrete types is small and closed: BuiltinWindow, application.Window (BYO), or the sentinel WindowNone. The full window machinery is implemented in a follow-up commit; the interface is declared here so Config does not change shape between commits.

var WindowNone WindowOption = windowNoneType{}

WindowNone selects headless mode: the Updater drives the flow but never asks the host to open a window. Subscribe to events from your own UI.

func BYOWindow

func BYOWindow(w WindowHandle) WindowOption

BYOWindow wraps a caller-owned WindowHandle (typically an *application.WebviewWindow created outside the updater) so it can be passed via Config.Window. The Updater drives Show / Close / EmitEvent on the wrapped handle instead of creating its own window.

The WindowOption interface has an unexported marker method that prevents arbitrary types from being assigned to Config.Window directly; this constructor is the bridge — call it once at Init time:

myWin := app.Window.NewWithOptions(application.WebviewWindowOptions{...})
app.Updater.Init(updater.Config{
    Window: updater.BYOWindow(myWin),
    ...,
})

type WindowOptions

type WindowOptions struct {
	Title         string
	Width, Height int
	Frameless     bool
	AlwaysOnTop   bool
	DisableResize bool
	InitialHTML   string
}

WindowOptions describes the chrome and starting content for a window the Updater asks the host to open. Maps to (a subset of) application.WebviewWindowOptions on the host side.

type WindowSizer

type WindowSizer interface {
	SetSize(width, height int)
}

WindowSizer is an optional capability a WindowHandle may implement to allow the Updater to resize the window in response to state changes. The default window template uses this to shrink the Up-to-Date state to a compact card; the framework adapter for *application.WebviewWindow implements it transparently. BYO windows can opt in by adding the SetSize method themselves; if they don't, the Updater silently skips the resize and the window stays whatever size the host opened it at.

Directories

Path Synopsis
internal
semver
Package semver wraps golang.org/x/mod/semver with a small, provider-friendly interface.
Package semver wraps golang.org/x/mod/semver with a small, provider-friendly interface.
providers
appcast
Package appcast implements an updater.Provider against a Sparkle-style AppCast RSS feed.
Package appcast implements an updater.Provider against a Sparkle-style AppCast RSS feed.
github
Package github implements an updater.Provider backed by GitHub Releases.
Package github implements an updater.Provider backed by GitHub Releases.
keygen
Package keygen implements an updater.Provider against the keygen.sh REST API.
Package keygen implements an updater.Provider against the keygen.sh REST API.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL