wails/v3/pkg/application/browser_window.go
Lea Anthony 9a363d7be5
feat(v3): add server mode for headless HTTP deployment (#4903)
* feat(v3): add server mode for headless HTTP deployment

Server mode allows Wails applications to run as pure HTTP servers
without native GUI dependencies. Enable with `-tags server` build tag.

Features:
- HTTP server with configurable host/port via ServerOptions
- WAILS_SERVER_HOST and WAILS_SERVER_PORT env var overrides
- WebSocket event broadcasting to connected browsers
- Browser clients represented as BrowserWindow (Window interface)
- Health check endpoint at /health
- Graceful shutdown with configurable timeout
- Docker support with Dockerfile.server template and tasks

Build and run:
  wails3 task build:server
  wails3 task run:server
  wails3 task build:docker
  wails3 task run:docker

Documentation at docs/guides/server-build.mdx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* feat(v3): add server mode for headless HTTP deployment

Server mode allows Wails applications to run as pure HTTP servers
without native GUI dependencies. Enable with `-tags server` build tag.

Features:
- HTTP server with configurable host/port via ServerOptions
- WAILS_SERVER_HOST and WAILS_SERVER_PORT env var overrides
- WebSocket event broadcasting to connected browsers
- Browser clients represented as BrowserWindow (Window interface)
- Health check endpoint at /health
- Graceful shutdown with configurable timeout
- Docker support with Dockerfile.server template and tasks

Build and run:
  wails3 task build:server
  wails3 task run:server
  wails3 task build:docker
  wails3 task run:docker

Documentation at docs/guides/server-build.mdx

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address CodeRabbit review comments

- Fix corrupted test file with embedded terminal output
- Fix module name mismatch in gin-routing (was gin-example)
- Fix replace directive version mismatch in gin-service
- Fix placeholder module name in ios example (was changeme)
- Fix Dockerfile COPY path to work from both build contexts
- Fix bare URL in README (MD034 compliance)
- Fix comment accuracy in getScreens (returns error, not empty slice)
- Remove deprecated docker-compose version field
- Add port documentation in Taskfile template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* fix: address CodeRabbit review comments

- Add note about healthcheck wget not being available in distroless images
- Add !server build constraint to menu_windows.go and menu_darwin.go
- Downgrade window-visibility-test go.mod from 1.25 to 1.24 to match CI

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 14:33:44 +11:00

149 lines
8 KiB
Go

//go:build server
package application
import (
"fmt"
"unsafe"
"github.com/wailsapp/wails/v3/pkg/events"
)
// BrowserWindow represents a browser client connection in server mode.
// It implements the Window interface so browser clients can be treated
// uniformly with native windows throughout the codebase.
type BrowserWindow struct {
id uint
name string
clientID string // The runtime's nanoid for this client
}
// NewBrowserWindow creates a new browser window with the given ID.
func NewBrowserWindow(id uint, clientID string) *BrowserWindow {
return &BrowserWindow{
id: id,
name: fmt.Sprintf("browser-%d", id),
clientID: clientID,
}
}
// Core identification methods
func (b *BrowserWindow) ID() uint { return b.id }
func (b *BrowserWindow) Name() string { return b.name }
func (b *BrowserWindow) ClientID() string { return b.clientID }
// Event methods - these are meaningful for browser windows
func (b *BrowserWindow) DispatchWailsEvent(event *CustomEvent) {
// Events are dispatched via WebSocket broadcast, not per-window
}
func (b *BrowserWindow) EmitEvent(name string, data ...any) bool {
return globalApplication.Event.Emit(name, data...)
}
// Logging methods
func (b *BrowserWindow) Error(message string, args ...any) {
globalApplication.error(message, args...)
}
func (b *BrowserWindow) Info(message string, args ...any) {
globalApplication.info(message, args...)
}
// No-op methods - these don't apply to browser windows
func (b *BrowserWindow) Center() {}
func (b *BrowserWindow) Close() {}
func (b *BrowserWindow) DisableSizeConstraints() {}
func (b *BrowserWindow) EnableSizeConstraints() {}
func (b *BrowserWindow) ExecJS(js string) {}
func (b *BrowserWindow) Focus() {}
func (b *BrowserWindow) ForceReload() {}
func (b *BrowserWindow) Fullscreen() Window { return b }
func (b *BrowserWindow) GetBorderSizes() *LRTB { return nil }
func (b *BrowserWindow) GetScreen() (*Screen, error) { return nil, nil }
func (b *BrowserWindow) GetZoom() float64 { return 1.0 }
func (b *BrowserWindow) handleDragAndDropMessage(filenames []string, dropTarget *DropTargetDetails) {}
func (b *BrowserWindow) InitiateFrontendDropProcessing(filenames []string, x int, y int) {}
func (b *BrowserWindow) HandleMessage(message string) {}
func (b *BrowserWindow) HandleWindowEvent(id uint) {}
func (b *BrowserWindow) Height() int { return 0 }
func (b *BrowserWindow) Hide() Window { return b }
func (b *BrowserWindow) HideMenuBar() {}
func (b *BrowserWindow) IsFocused() bool { return false }
func (b *BrowserWindow) IsFullscreen() bool { return false }
func (b *BrowserWindow) IsIgnoreMouseEvents() bool { return false }
func (b *BrowserWindow) IsMaximised() bool { return false }
func (b *BrowserWindow) IsMinimised() bool { return false }
func (b *BrowserWindow) HandleKeyEvent(acceleratorString string) {}
func (b *BrowserWindow) Maximise() Window { return b }
func (b *BrowserWindow) Minimise() Window { return b }
func (b *BrowserWindow) OnWindowEvent(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
return func() {}
}
func (b *BrowserWindow) OpenContextMenu(data *ContextMenuData) {}
func (b *BrowserWindow) Position() (int, int) { return 0, 0 }
func (b *BrowserWindow) RelativePosition() (int, int) { return 0, 0 }
func (b *BrowserWindow) Reload() {}
func (b *BrowserWindow) Resizable() bool { return false }
func (b *BrowserWindow) Restore() {}
func (b *BrowserWindow) Run() {}
func (b *BrowserWindow) SetPosition(x, y int) {}
func (b *BrowserWindow) SetAlwaysOnTop(b2 bool) Window { return b }
func (b *BrowserWindow) SetBackgroundColour(colour RGBA) Window { return b }
func (b *BrowserWindow) SetFrameless(frameless bool) Window { return b }
func (b *BrowserWindow) SetHTML(html string) Window { return b }
func (b *BrowserWindow) SetMinimiseButtonState(state ButtonState) Window { return b }
func (b *BrowserWindow) SetMaximiseButtonState(state ButtonState) Window { return b }
func (b *BrowserWindow) SetCloseButtonState(state ButtonState) Window { return b }
func (b *BrowserWindow) SetMaxSize(maxWidth, maxHeight int) Window { return b }
func (b *BrowserWindow) SetMinSize(minWidth, minHeight int) Window { return b }
func (b *BrowserWindow) SetRelativePosition(x, y int) Window { return b }
func (b *BrowserWindow) SetResizable(b2 bool) Window { return b }
func (b *BrowserWindow) SetIgnoreMouseEvents(ignore bool) Window { return b }
func (b *BrowserWindow) SetSize(width, height int) Window { return b }
func (b *BrowserWindow) SetTitle(title string) Window { return b }
func (b *BrowserWindow) SetURL(s string) Window { return b }
func (b *BrowserWindow) SetZoom(magnification float64) Window { return b }
func (b *BrowserWindow) Show() Window { return b }
func (b *BrowserWindow) ShowMenuBar() {}
func (b *BrowserWindow) Size() (width int, height int) { return 0, 0 }
func (b *BrowserWindow) OpenDevTools() {}
func (b *BrowserWindow) ToggleFullscreen() {}
func (b *BrowserWindow) ToggleMaximise() {}
func (b *BrowserWindow) ToggleMenuBar() {}
func (b *BrowserWindow) ToggleFrameless() {}
func (b *BrowserWindow) UnFullscreen() {}
func (b *BrowserWindow) UnMaximise() {}
func (b *BrowserWindow) UnMinimise() {}
func (b *BrowserWindow) Width() int { return 0 }
func (b *BrowserWindow) IsVisible() bool { return true }
func (b *BrowserWindow) Bounds() Rect { return Rect{} }
func (b *BrowserWindow) SetBounds(bounds Rect) {}
func (b *BrowserWindow) Zoom() {}
func (b *BrowserWindow) ZoomIn() {}
func (b *BrowserWindow) ZoomOut() {}
func (b *BrowserWindow) ZoomReset() Window { return b }
func (b *BrowserWindow) SetMenu(menu *Menu) {}
func (b *BrowserWindow) SnapAssist() {}
func (b *BrowserWindow) SetContentProtection(protection bool) Window { return b }
func (b *BrowserWindow) NativeWindow() unsafe.Pointer { return nil }
func (b *BrowserWindow) SetEnabled(enabled bool) {}
func (b *BrowserWindow) Flash(enabled bool) {}
func (b *BrowserWindow) Print() error { return nil }
func (b *BrowserWindow) RegisterHook(eventType events.WindowEventType, callback func(event *WindowEvent)) func() {
return func() {}
}
func (b *BrowserWindow) shouldUnconditionallyClose() bool { return true }
// Editing methods
func (b *BrowserWindow) cut() {}
func (b *BrowserWindow) copy() {}
func (b *BrowserWindow) paste() {}
func (b *BrowserWindow) undo() {}
func (b *BrowserWindow) redo() {}
func (b *BrowserWindow) delete() {}
func (b *BrowserWindow) selectAll() {}