Frontend Runtime
This page covers the client-side integration for frontend codepush and experiments.
The runtime precedence is:
- selected experiment
- installed codepush
- embedded frontend
What clients need
Your client app needs three pieces:
- a
wailsupdate.Runtime runtime.AssetFS(...)in the Wails asset handler- a frontend listener for
update:frontend-reload-requiredthat callswindow.location.reload()
Preferred runtime bootstrap
Create one wailsupdate.Runtime and reuse it for both the updater service and asset serving:
runtime, err := wailsupdate.NewRuntime(wailsupdate.RuntimeOptions{
AppID: "com.example.myapp",
CurrentVersion: appVersion,
Channel: "stable",
NativeCompat: nativeCompat,
Source: wailsupdate.RuntimeSource{
BaseURL: "https://releases.example.com",
},
Frontend: wailsupdate.RuntimeFrontend{
CatalogPublicKey: os.Getenv("FRONTEND_CATALOG_PUBLIC_KEY"),
},
Client: &http.Client{Timeout: 45 * time.Second},
})
if err != nil {
return err
}
AppID must match the signed frontend catalog app_id.
Runtime asset handler
Replace the embedded-only asset handler with the runtime wrapper:
app := application.New(application.Options{
Assets: application.AssetOptions{
Handler: application.AssetFileServerFS(runtime.AssetFS(assets)),
},
})
runtime.AssetFS(...) resolves requests against one consistent frontend layer per request, so concurrent installs and reloads do not mix files from different bundles.
Updater service
Register the runtime-backed updater service:
app := application.New(application.Options{
Services: []application.Service{
application.NewService(runtime.Service()),
},
})
Rules:
FrontendCatalogURLis pinned in app config. Do not learn it from the native release manifest.- The catalog URL must be HTTPS.
FrontendCatalogPublicKeymust be the Ed25519 public key for the signed catalog response.- When
BaseURLis set,NewRuntime(...)derives/manifestautomatically and derives/frontend/catalogonly when frontend runtime is enabled. - GitHub repository mode derives the native manifest URL, but frontend catalog URLs still need an explicit pinned override.
Advanced manual wiring
wailsupdate.NewService(...) is still the low-level escape hatch when you need to wire the client, URLs, or frontend.BundleManager yourself.
Register events
Register the normal updater events once:
func init() {
wailsupdate.RegisterEvents("update")
}
That now includes update:frontend-reload-required.
Frontend reload handling
Listen for the reload event in the web app:
import { Events } from "@wailsio/runtime";
Events.On("update:frontend-reload-required", () => {
window.location.reload();
});
Use this same event path for:
- manual codepush apply
- forced codepush auto-apply
- experiment switch
- reset from experiment back to codepush or embedded
Service methods
The frontend runtime uses these wailsupdate.Service methods:
RefreshFrontendCatalog()GetFrontendState()ApplyCodepush()SwitchExperiment(name string)ResetFrontend()
Typical app-level wrappers look like:
type UpdateService struct {
service *wailsupdate.Service
}
func (s *UpdateService) GetFrontendState() wailsupdate.FrontendState {
return s.service.GetFrontendState()
}
func (s *UpdateService) RefreshFrontendCatalog() wailsupdate.FrontendState {
return s.service.RefreshFrontendCatalog()
}
func (s *UpdateService) ApplyCodepush() wailsupdate.ActionResponse {
return s.service.ApplyCodepush()
}
func (s *UpdateService) SwitchExperiment(name string) wailsupdate.ActionResponse {
return s.service.SwitchExperiment(name)
}
func (s *UpdateService) ResetFrontend() wailsupdate.ActionResponse {
return s.service.ResetFrontend()
}
Runtime behavior
Codepush
- The client fetches the signed catalog.
- It chooses the newest compatible codepush by
PublishedAt, then semantic version. - If the catalog entry is
force: true, the client attempts one automatic install for that bundle version. - If that forced install fails, the client records one local failure for that version and does not loop forever.
- Manual
ApplyCodepush()is still allowed later.
Experiments
- The client lists all compatible experiments returned by the signed catalog.
SwitchExperiment(name)downloads the bundle if needed, installs it, updates selection, and emits the reload event.ResetFrontend()clears the experiment selection and falls back to installed codepush or embedded assets.
Failure handling
- If catalog fetch fails, the client keeps the current local state.
- Installed codepush and experiment bundles are not removed on catalog failure.
- If a selected experiment is removed, incompatible, or tampered locally, the client clears the selection and falls back.
Proxy and catalog contract
Clients expect:
GET /frontend/catalog- HTTPS
- a signed catalog response
- codepush and experiment asset URLs already rewritten by the proxy if needed
The client verifies the final signed catalog it receives. That means proxy URL rewriting must happen before signing.
Security checks on the client
The client runtime enforces:
- strict experiment and codepush names: lowercase
[a-z0-9-_]+ - strict bundle checksums:
sha256:<64 hex> - catalog signature verification
- bundle compat checks
- installed bundle checksum verification before activation
- trusted install records for codepush and experiments
- selection validation before activating an experiment
Minimal integration checklist
- Pin
FrontendCatalogURLin app config. - Ship the Ed25519 catalog public key with the app.
- Create one
wailsupdate.Runtime. - Serve assets with
runtime.AssetFS(assets). - Register
runtime.Service()with Wails. - Expose the frontend service methods to the webview.
- Reload on
update:frontend-reload-required.