server

package
v0.0.1 Latest Latest
Warning

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

Go to latest
Published: Feb 24, 2026 License: MIT Imports: 46 Imported by: 0

Documentation

Overview

Package server provides the server-side runtime for Vango's server-driven architecture.

The server package manages WebSocket connections, component state, event handling, and patch generation. It is the integration layer that brings together the reactive system (pkg/vango), virtual DOM (pkg/vdom), and binary protocol (pkg/protocol).

Architecture

The server runtime consists of several key components:

  • Session: Per-connection state container managing component tree, handlers, and reactive ownership
  • SessionManager: Manages all active sessions with cleanup and lifecycle hooks
  • ComponentInstance: A mounted component with its reactive state and render capability
  • Handler: Event handler functions that respond to client events
  • Server: HTTP/WebSocket server with handshake and graceful shutdown

Session Lifecycle

Each WebSocket connection creates a Session that manages:

  • Component tree and hydration IDs
  • Event handler registry (HID -> handler mapping)
  • Reactive ownership for signals and effects
  • Sequence numbers for reliable delivery

The session runs three goroutines:

  • ReadLoop: Receives WebSocket frames, decodes events, queues for processing
  • EventLoop: Processes events, runs handlers, generates patches
  • WriteLoop: Sends heartbeat pings

Event Processing

When a client sends an event:

  1. ReadLoop decodes the binary event frame
  2. Event is queued for the EventLoop
  3. Handler is found by HID and executed
  4. Pending effects are run
  5. Dirty components are re-rendered
  6. Diff generates patches
  7. Patches are encoded and sent to client

Example Usage

server := server.MustNew(&server.ServerConfig{
    Address: ":8080",
})

server.HandleFunc("/", func(ctx server.Ctx) *vdom.VNode {
    count := vango.NewIntSignal(0)
    return vdom.Div(
        vdom.H1(vdom.Textf("Count: %d", count.Get())),
        vdom.Button(
            vdom.OnClick(func() { count.Inc() }),
            vdom.Text("+"),
        ),
    )
})

server.Run()

Thread Safety

The server package is designed for concurrent access:

  • Session.mu protects WebSocket writes
  • Events channel serializes event processing
  • Signal access uses Phase 1 synchronization
  • SessionManager uses RWMutex for session map

Performance Targets

  • Memory per session: < 3MB default budget
  • Concurrent sessions per GB: 5,000+
  • Event processing latency: < 10ms
  • WebSocket reconnect: < 500ms

Session Resume and Authentication

When a WebSocket connection drops and reconnects within ResumeWindow, Vango attempts to resume the existing session. For authenticated sessions, Vango enforces auth revalidation by default via AuthResumePolicy.

## Auth Resume Policy

AuthResumePolicyStrict (default): Requires authFunc or OnSessionResume to be configured for authenticated sessions. Without these, authenticated session resume is rejected and the client receives a HandshakeNotAuthorized error, triggering re-authentication. This prevents session IDs from acting as bearer tokens that could be exploited if leaked via XSS, logging, etc.

AuthResumePolicyTrustSessionID: Allows authenticated sessions to resume using only the session ID for in-memory sessions. Use this only if you understand the security implications: a leaked session ID grants authenticated access. Note: Persisted sessions (Redis, NATS) still require auth rehydration because user objects are not serialized.

## Configuring Auth Revalidation

The recommended approach is to configure Server.SetAuthFunc():

server.SetAuthFunc(func(r *http.Request) (any, error) {
    return myauth.ValidateRequest(r)
})

This validates the auth cookie/token on every resume and automatically rehydrates the user in the session. If authFunc returns (nil, nil), the resume is rejected for previously authenticated sessions.

Alternatively, configure OnSessionResume for custom rehydration:

cfg.OnSessionResume = func(ctx context.Context, session *Session) error {
    user, err := myauth.ValidateFromContext(ctx)
    if err != nil {
        return err
    }
    if user != nil {
        auth.Set(session, user) // REQUIRED: rehydrate user
    }
    return nil
}

IMPORTANT: A no-op OnSessionResume that just returns nil does NOT provide security. In strict mode (without authFunc), resume clears runtime auth projection before this hook runs, so previously authenticated sessions are rejected unless the hook actually revalidates and rehydrates auth.

Index

Constants

View Source
const CSPHeaderName = "Content-Security-Policy"

CSPHeaderName is the header for Content Security Policy.

View Source
const CSPReportOnlyHeaderName = "Content-Security-Policy-Report-Only"

CSPReportOnlyHeaderName is the header for report-only CSP.

View Source
const CSRFCookieName = "__vango_csrf"

CSRFCookieName is the name of the CSRF cookie.

View Source
const CSRFHeaderName = "X-CSRF-Token"

CSRFHeaderName is the default header used for CSRF tokens on HTTP requests.

View Source
const DefaultAuthSessionKey = auth.SessionKey

DefaultAuthSessionKey is the conventional session key for an authenticated user. The vango/pkg/auth helpers use this key, and server.Ctx.User() falls back to it.

Variables

View Source
var (
	// ErrSessionClosed is returned when an operation is attempted on a closed session.
	ErrSessionClosed = errors.New("server: session closed")

	// ErrSessionNotFound is returned when a session ID does not exist.
	ErrSessionNotFound = errors.New("server: session not found")

	// ErrHandlerNotFound is returned when no handler is registered for an HID.
	ErrHandlerNotFound = errors.New("server: handler not found")

	// ErrEventQueueFull is returned when the event queue is full and an event is dropped.
	ErrEventQueueFull = errors.New("server: event queue full")

	// ErrInvalidHandshake is returned when the WebSocket handshake fails.
	ErrInvalidHandshake = errors.New("server: invalid handshake")

	// ErrMaxSessionsReached is returned when the maximum number of sessions is reached.
	ErrMaxSessionsReached = errors.New("server: max sessions reached")

	// ErrTooManySessionsFromIP is returned when the per-IP session limit is exceeded.
	ErrTooManySessionsFromIP = errors.New("server: too many sessions from this IP address")

	// ErrTooManyConcurrentHandshakes is returned when the global pre-auth handshake
	// concurrency limit is exceeded.
	ErrTooManyConcurrentHandshakes = errors.New("server: too many concurrent handshakes")

	// ErrTooManyConcurrentHandshakesFromIP is returned when the per-IP pre-auth
	// handshake concurrency limit is exceeded.
	ErrTooManyConcurrentHandshakesFromIP = errors.New("server: too many concurrent handshakes from this IP address")

	// ErrInvalidCSRF is returned when CSRF token validation fails.
	ErrInvalidCSRF = errors.New("server: invalid CSRF token")

	// ErrSecureCookiesRequired is returned when secure cookies are required but the request is not secure.
	ErrSecureCookiesRequired = errors.New("server: secure cookies require HTTPS or trusted proxy headers")

	// ErrSessionExpired is returned when a session has expired due to inactivity.
	ErrSessionExpired = errors.New("server: session expired")

	// ErrConnectionClosed is returned when the WebSocket connection is closed.
	ErrConnectionClosed = errors.New("server: connection closed")

	// ErrWriteTimeout is returned when a write operation times out.
	ErrWriteTimeout = errors.New("server: write timeout")

	// ErrReadTimeout is returned when a read operation times out.
	ErrReadTimeout = errors.New("server: read timeout")

	// ErrAuthCheckPanicked is returned when an AuthCheck provider panics.
	// The panic is recovered and surfaced as an error so it cannot terminate
	// the server process.
	ErrAuthCheckPanicked = errors.New("server: auth check panicked")

	// ErrNoConnection is returned when attempting to send on a nil connection.
	ErrNoConnection = errors.New("server: no connection")
)

Sentinel errors for common session and server error conditions.

View Source
var ErrForbidden = auth.ErrForbidden

ErrForbidden is returned when authentication is present but insufficient. This is defined in the auth package and re-exported here for convenience.

View Source
var ErrUnauthorized = auth.ErrUnauthorized

ErrUnauthorized is returned when authentication is required but not present. This is defined in the auth package and re-exported here for convenience.

Functions

func AllowAllOriginsCheck

func AllowAllOriginsCheck(*http.Request) bool

AllowAllOriginsCheck allows any origin. Use only in development.

func AllowedOriginsCheck

func AllowedOriginsCheck(allowedOrigins []string) func(*http.Request) bool

AllowedOriginsCheck validates that the request Origin matches an allowlist. Origins are normalized by scheme + lowercase host + effective port (default port omitted/explicit forms are equivalent) and must not include path, query, or fragment. Requests without an Origin header are allowed.

func BuildCSP

func BuildCSP(opts CSPOptions) string

BuildCSP constructs a policy string from options.

func CSRFCtxToken

func CSRFCtxToken(ctx Ctx) string

CSRFCtxToken retrieves the CSRF token from the request-scoped context values.

func CanonicalizePath

func CanonicalizePath(path string) (canonPath, query string, changed bool, err error)

CanonicalizePath normalizes a URL path for navigation.

func CurrentMemoryUsage

func CurrentMemoryUsage() int64

CurrentMemoryUsage returns the current heap memory usage in bytes.

func EstimateAnyMemory

func EstimateAnyMemory(value any) int64

EstimateAnyMemory approximates the memory usage of a value. It is depth-limited to avoid runaway recursion on complex graphs.

func EstimateMapMemory

func EstimateMapMemory(length, keySize, valueSize int) int64

EstimateMapMemory estimates memory usage of a map.

func EstimateSliceMemory

func EstimateSliceMemory(length, elementSize int) int64

EstimateSliceMemory estimates memory usage of a slice.

func EstimateStringMemory

func EstimateStringMemory(s string) int64

EstimateStringMemory estimates memory usage of a string.

func InspectState

func InspectState(session *Session) vruntime.StateInspection

InspectState returns inspection data for a session.

func IsAuthError

func IsAuthError(err error) bool

IsAuthError returns true if the error is an authentication or authorization error.

func LoggerForEvent

func LoggerForEvent(ctx Ctx, base *slog.Logger) *slog.Logger

LoggerForEvent returns a context-rich logger for an event. If ctx.Logger() is present, it is returned as-is; otherwise, it is enriched with available session/user/event details.

func NormalizeAuthCheckConfig

func NormalizeAuthCheckConfig(cfg *AuthCheckConfig)

NormalizeAuthCheckConfig applies defaults to an auth check config in-place.

func RunRouteMiddleware

func RunRouteMiddleware(ctx Ctx, middleware []RouteMiddleware, final func() error) (ranFinal bool, err error)

RunRouteMiddleware executes a route middleware chain and then calls final.

Middleware can short-circuit by returning nil without calling next. In that case ranFinal will be false and err will be nil.

func SameOriginCheck

func SameOriginCheck(r *http.Request) bool

SameOriginCheck validates that the WebSocket request origin matches the host. This is the secure default for CheckOrigin. SECURITY: Uses strict URL/authority parsing, case-insensitive host comparison, and default-port normalization while keeping scheme checks strict (http != https).

func SetCSRFCtxToken

func SetCSRFCtxToken(ctx Ctx, token string)

SetCSRFCtxToken stores the CSRF token in the request-scoped context values. This allows SSR handlers to render CSRF-protected forms.

func SetGlobalPrefetchLimit

func SetGlobalPrefetchLimit(limit int)

SetGlobalPrefetchLimit sets the global prefetch concurrency limit. Should be called at server startup before any prefetch operations.

func TotalSystemMemory

func TotalSystemMemory() int64

TotalSystemMemory returns the total system memory seen by the Go runtime.

func UserFromContext

func UserFromContext(ctx context.Context) any

UserFromContext retrieves the authenticated user stored by WithUser.

func ValidateExternalRedirectURL

func ValidateExternalRedirectURL(rawURL string, allowedHosts []string) (string, bool)

ValidateExternalRedirectURL validates an absolute redirect URL against an allowlist. Returns the canonical URL and true when allowed; otherwise returns ("", false).

func WithUser

func WithUser(ctx context.Context, user any) context.Context

WithUser stores an authenticated user in a standard library context. This is useful for bridging from HTTP middleware into Vango SSR and OnSessionStart.

Types

type AppliedNavigateOptions

type AppliedNavigateOptions struct {
	Replace bool
	Params  map[string]any
	Scroll  bool
}

AppliedNavigateOptions is the concrete, inspected result of applying NavigateOption closures. This is useful in contexts outside server.ctx (e.g. SSR adapters) where NavigateOption is opaque.

func ApplyNavigateOptions

func ApplyNavigateOptions(opts ...NavigateOption) AppliedNavigateOptions

ApplyNavigateOptions applies NavigateOption closures to the default option set.

func BuildNavigateURL

func BuildNavigateURL(path string, opts ...NavigateOption) (fullPath string, applied AppliedNavigateOptions)

BuildNavigateURL builds the final navigation URL by applying NavigateOption params and validating the resulting path against the navigation security rules.

Returns an empty string if the path is invalid (e.g. absolute URL).

type AuthCheckConfig

type AuthCheckConfig struct {
	// Interval between active checks (0 disables periodic checks).
	Interval time.Duration

	// Timeout caps how long a Check may take.
	// Default: 5 seconds.
	Timeout time.Duration

	// FailureMode controls transient failure handling.
	// Default: FailOpenWithGrace.
	FailureMode AuthFailureMode

	// MaxStale caps how long a session may go without a successful active check
	// when FailureMode is fail-open.
	// Default: 15 minutes.
	MaxStale time.Duration

	// Check is called periodically and on-demand.
	// This runs off the session loop; do not mutate session state directly.
	// Panics are recovered and treated as check failures.
	Check func(ctx context.Context, p auth.Principal) error

	// OnExpired defines what happens when auth fails.
	OnExpired AuthExpiredConfig
}

AuthCheckConfig configures periodic active revalidation.

type AuthExpiredAction

type AuthExpiredAction int

AuthExpiredAction defines what happens when auth expires.

const (
	// ForceReload triggers browser reload (re-enters HTTP pipeline).
	ForceReload AuthExpiredAction = iota

	// NavigateTo performs a hard navigation to a path (re-enters HTTP pipeline).
	NavigateTo

	// Custom invokes a caller-provided handler.
	Custom
)

type AuthExpiredConfig

type AuthExpiredConfig struct {
	Action  AuthExpiredAction
	Path    string
	Reason  AuthExpiredReason
	Handler func(s *Session)
}

AuthExpiredConfig defines behavior when auth expires.

type AuthExpiredReason

type AuthExpiredReason int

AuthExpiredReason provides structured context for auth expiry.

const (
	AuthExpiredUnknown AuthExpiredReason = iota
	AuthExpiredPassiveExpiry
	AuthExpiredResumeRehydrateFailed
	AuthExpiredActiveRevalidateFailed
	AuthExpiredOnDemandRevalidateFailed
	AuthExpiredNoRevalidationHooks // No authFunc/OnSessionResume configured for authenticated session
	AuthExpiredResumeIdentityMismatch
)

func (AuthExpiredReason) String

func (r AuthExpiredReason) String() string

type AuthFailureMode

type AuthFailureMode int

AuthFailureMode controls what happens when active checks fail.

const (
	// FailOpenWithGrace keeps the session alive on transient failures.
	// Default: availability-first, bounded by MaxStale.
	FailOpenWithGrace AuthFailureMode = iota

	// FailClosed expires the session on any check failure.
	FailClosed
)

type AuthResumePolicy

type AuthResumePolicy int

AuthResumePolicy controls how authenticated session resumes are validated.

const (
	// AuthResumePolicyStrict (default) requires authFunc or OnSessionResume to be
	// configured for authenticated sessions to resume. If neither is set and the
	// session was previously authenticated, resume is rejected.
	//
	// This is the secure default: session ID alone is NOT sufficient to resume
	// an authenticated session. The original auth (cookie, token, etc.) must be
	// revalidated on every resume.
	//
	// IMPORTANT: Simply configuring OnSessionResume is not sufficient for security.
	// The hook MUST actually revalidate authentication and call auth.Set() to
	// rehydrate the user. In strict mode, resume clears runtime auth projection
	// before OnSessionResume when authFunc is not configured, so a no-op hook
	// is rejected as unauthenticated resume. See documentation for proper implementation.
	AuthResumePolicyStrict AuthResumePolicy = iota

	// AuthResumePolicyTrustSessionID treats the session ID as a credential.
	// Authenticated sessions can resume without revalidation if the session
	// data is still in server memory.
	//
	// IMPORTANT: This policy ONLY applies to in-memory session resume. For sessions
	// restored from persistence (Redis, NATS, etc.), the user object is not serialized,
	// so the ghost auth check will still reject resume unless authFunc or OnSessionResume
	// rehydrates the user. This is by design - persisted sessions should always
	// revalidate auth on restore.
	//
	// SECURITY WARNING: Use this policy ONLY if ALL of the following are true:
	//   - You generate cryptographically strong session IDs (Vango does this)
	//   - Session IDs are NEVER logged, exposed in URLs visible to users, or
	//     accessible to client-side JavaScript beyond the thin client
	//   - You accept the risk that a leaked session ID grants authenticated access
	//   - You have additional protections (short ResumeWindow, IP binding, etc.)
	//
	// This policy is appropriate for:
	//   - Internal tools with trusted networks
	//   - Apps where session continuity UX outweighs security concerns
	//   - Apps with very short ResumeWindow (< 30 seconds)
	//
	// IMPORTANT: This policy weakens "logout elsewhere" and revocation semantics.
	// A detached in-memory session may still resume with only the session ID until
	// ResumeWindow expires or the session is evicted/closed.
	AuthResumePolicyTrustSessionID
)

func (AuthResumePolicy) IsValid

func (p AuthResumePolicy) IsValid() bool

IsValid returns true if the policy is a known valid value.

func (AuthResumePolicy) String

func (p AuthResumePolicy) String() string

String returns the string representation of the auth resume policy.

type BudgetExceededMode

type BudgetExceededMode int

BudgetExceededMode determines behavior when a storm budget is exceeded.

const (
	// BudgetThrottle drops excess operations silently (default).
	// Operations that exceed the budget are not executed.
	BudgetThrottle BudgetExceededMode = iota

	// BudgetTripBreaker pauses effect execution until cleared.
	// Like a circuit breaker, stops all effect processing until reset.
	BudgetTripBreaker
)

type ByteSize

type ByteSize int64

ByteSize represents a byte size with human-readable formatting.

const (
	KB ByteSize = 1 << (10 * iota)
	MB
	GB
	TB
)

func (ByteSize) String

func (b ByteSize) String() string

String returns a human-readable representation of the byte size.

type CSPOptions

type CSPOptions struct {
	ReportOnly         bool
	AllowInlineStyles  bool
	AllowInlineScripts bool
	AllowDataImages    bool
	AllowHTTPSImages   bool
	IncludeWSS         bool
	IncludeWS          bool

	AdditionalConnectSrc []string
	AdditionalScriptSrc  []string
	AdditionalStyleSrc   []string
	AdditionalImgSrc     []string
	AdditionalFontSrc    []string
	AdditionalDirectives []string
}

CSPOptions configures the CSP middleware helper.

func DefaultCSPOptions

func DefaultCSPOptions() CSPOptions

DefaultCSPOptions returns a CSPOptions aligned with the security guide example.

func HardenedCSPOptions

func HardenedCSPOptions() CSPOptions

HardenedCSPOptions returns a stricter CSP profile for production hardening. It disables inline styles while preserving other defaults.

type CSRFError

type CSRFError struct {
	Reason string
}

CSRFError represents an HTTP CSRF validation error.

func (CSRFError) Error

func (e CSRFError) Error() string

func (CSRFError) StatusCode

func (e CSRFError) StatusCode() int

type Component

type Component interface {
	// Render returns the VNode tree for this component.
	Render() *vdom.VNode
}

Component is the interface for renderable components. Components produce VNode trees that represent the UI.

type ComponentInstance

type ComponentInstance struct {
	// InstanceID is the unique instance identifier.
	InstanceID string

	// Component is the component being rendered.
	Component Component

	// HID is the hydration ID of the root element.
	HID string

	// Owner manages signal ownership for this component.
	Owner *vango.Owner

	// Parent is the parent component instance (nil for root).
	Parent *ComponentInstance

	// Children are child component instances.
	Children []*ComponentInstance

	// Props are the current props passed to the component.
	Props map[string]any
	// contains filtered or unexported fields
}

ComponentInstance represents a mounted component with its state. It holds the component's reactive ownership, tracking, and rendering context.

func (*ComponentInstance) AddChild

func (c *ComponentInstance) AddChild(child *ComponentInstance)

AddChild adds a child component instance.

func (*ComponentInstance) ClearDirty

func (c *ComponentInstance) ClearDirty()

ClearDirty clears the dirty flag.

func (*ComponentInstance) Dispose

func (c *ComponentInstance) Dispose()

Dispose disposes the component instance and all its children.

func (*ComponentInstance) GetProp

func (c *ComponentInstance) GetProp(key string) any

GetProp gets a prop value.

func (*ComponentInstance) ID

func (c *ComponentInstance) ID() uint64

ID implements vango.Listener and returns a globally unique identifier.

func (*ComponentInstance) IsDirty

func (c *ComponentInstance) IsDirty() bool

IsDirty returns whether the component needs re-rendering.

func (*ComponentInstance) LastTree

func (c *ComponentInstance) LastTree() *vdom.VNode

LastTree returns the last rendered VNode tree.

func (*ComponentInstance) ListKeyPath

func (c *ComponentInstance) ListKeyPath() []string

ListKeyPath returns a copy of the list key path for this instance.

func (*ComponentInstance) MarkDirty

func (c *ComponentInstance) MarkDirty()

MarkDirty marks the component as needing re-render.

func (*ComponentInstance) MemoryUsage

func (c *ComponentInstance) MemoryUsage() int64

MemoryUsage returns an estimate of memory used by this instance.

func (*ComponentInstance) RemoveChild

func (c *ComponentInstance) RemoveChild(child *ComponentInstance)

RemoveChild removes a child component instance.

func (*ComponentInstance) Render

func (c *ComponentInstance) Render() *vdom.VNode

Render renders the component and returns the VNode tree. It sets up the tracking context so signals are properly tracked, and sets the runtime context so UseCtx() works during render.

func (*ComponentInstance) Session

func (c *ComponentInstance) Session() *Session

Session returns the owning session.

func (*ComponentInstance) SetLastTree

func (c *ComponentInstance) SetLastTree(tree *vdom.VNode)

SetLastTree sets the last rendered tree (used after diffing).

func (*ComponentInstance) SetProp

func (c *ComponentInstance) SetProp(key string, value any)

SetProp sets a prop value.

func (*ComponentInstance) TrackDependency

func (c *ComponentInstance) TrackDependency(dep vango.Dependency)

type CookieOption

type CookieOption func(*CookiePolicyOptions)

CookieOption configures cookie policy application.

func WithCookieHTTPOnly

func WithCookieHTTPOnly(enabled bool) CookieOption

WithCookieHTTPOnly overrides the default HttpOnly behavior for a cookie.

type CookiePolicy

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

CookiePolicy enforces cookie defaults and security requirements.

func (*CookiePolicy) Apply

func (p *CookiePolicy) Apply(r *http.Request, cookie *http.Cookie, opts ...CookieOption) (*http.Cookie, error)

Apply applies cookie defaults and enforces SecureCookies requirements. Returns ErrSecureCookiesRequired if secure cookies are enabled but the request is not secure.

func (*CookiePolicy) ApplyCookiePolicy

func (p *CookiePolicy) ApplyCookiePolicy(r *http.Request, cookie *http.Cookie) (*http.Cookie, error)

ApplyCookiePolicy applies defaults without overrides. This is a compatibility hook for external packages that accept a generic cookie policy interface.

func (*CookiePolicy) IsRequestSecure

func (p *CookiePolicy) IsRequestSecure(r *http.Request) bool

IsRequestSecure reports whether the request is considered secure (HTTPS), honoring trusted proxy headers when configured.

type CookiePolicyOptions

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

CookiePolicyOptions configures optional overrides for cookie policy.

type Ctx

type Ctx interface {

	// Request returns the underlying HTTP request.
	Request() *http.Request

	// Path returns the URL path.
	Path() string

	// Method returns the HTTP method.
	Method() string

	// Query returns the URL query parameters as url.Values.
	Query() url.Values

	// QueryParam returns a single query parameter value by key.
	// Returns an empty string if the key is not present.
	QueryParam(key string) string

	// Param returns a route parameter by key.
	Param(key string) string

	// Header returns a request header value.
	Header(key string) string

	// Cookie returns a cookie by name.
	Cookie(name string) (*http.Cookie, error)

	// Status sets the HTTP response status code.
	Status(code int)

	// Redirect redirects to the given URL with the given status code.
	// Redirects are relative-only and reject absolute URLs.
	Redirect(url string, code int)

	// RedirectExternal redirects to an absolute URL with the given status code.
	// External redirects require an explicit allowlist.
	RedirectExternal(url string, code int)

	// SetHeader sets a response header.
	SetHeader(key, value string)

	// SetCookie sets a response cookie.
	SetCookie(cookie *http.Cookie)

	// SetCookieStrict sets a response cookie with enforced security defaults.
	// Returns ErrSecureCookiesRequired if secure cookies are enabled and the request is not secure.
	SetCookieStrict(cookie *http.Cookie, opts ...CookieOption) error

	// Session returns the WebSocket session (nil for SSR-only requests).
	Session() vango.Session

	// AuthSession returns the WebSocket session as an auth.Session interface.
	// This allows auth helpers to persist to the session without importing server.
	AuthSession() auth.Session

	// User returns the authenticated user (set by auth middleware).
	User() any

	// SetUser sets the authenticated user.
	SetUser(user any)

	// Principal returns the authenticated principal, if any.
	Principal() (auth.Principal, bool)

	// MustPrincipal returns the principal or panics if not set.
	MustPrincipal() auth.Principal

	// RevalidateAuth forces an immediate active check if configured.
	// Returns any error from the check, including ErrAuthCheckPanicked when
	// the configured check panics.
	// If authenticated session projection exists but principal is missing,
	// this fails closed with auth.ErrSessionExpired.
	RevalidateAuth() error

	// BroadcastAuthLogout notifies other tabs that auth was cleared and they should reload.
	BroadcastAuthLogout()

	// Logger returns the request-scoped logger.
	Logger() *slog.Logger

	// Done returns a channel that's closed when the request is canceled.
	Done() <-chan struct{}

	// SetValue stores a request-scoped value.
	// These values are only available for the duration of the current event/request.
	SetValue(key, value any)

	// Value retrieves a request-scoped value.
	Value(key any) any

	// Emit dispatches a custom event to the client.
	// The event will be dispatched as a CustomEvent with the given name and detail.
	// Use this for notifications, toast messages, analytics, etc.
	Emit(name string, data any)

	// Dispatch queues a function to run on the session's event loop.
	// This is safe to call from any goroutine and is the correct way to
	// update signals from asynchronous operations (database calls, timers, etc.).
	//
	// The function will be executed synchronously on the event loop, ensuring
	// signal writes are properly serialized. After the function completes,
	// pending effects will run and dirty components will re-render.
	//
	// Example:
	//
	//     go func() {
	//         user, err := db.Users.FindByID(ctx.StdContext(), id)
	//         ctx.Dispatch(func() {
	//             if err != nil {
	//                 errorSignal.Set(err)
	//             } else {
	//                 userSignal.Set(user)
	//             }
	//         })
	//     }()
	//
	// IMPORTANT: This method MUST be safe to call from any goroutine.
	Dispatch(fn func())

	// Navigate performs a client-side navigation to the given path.
	// Unlike Redirect(), this updates the browser URL and re-renders the page
	// without a full HTTP redirect. The navigation is queued and processed
	// after the current handler completes.
	//
	// Options can be provided to customize behavior:
	//   - WithReplace() - replace current history entry instead of pushing
	//   - WithNavigateParams(map[string]any) - add query parameters to the URL
	//   - WithoutScroll() - disable scrolling to top after navigation
	//
	// Example:
	//
	//     func handleSave(ctx vango.Ctx) {
	//         project := saveProject()
	//         ctx.Navigate("/projects/" + project.ID)
	//     }
	//
	// For HTTP-only requests (SSR without WebSocket), this falls back to
	// an HTTP redirect.
	Navigate(path string, opts ...NavigateOption)

	// StdContext returns the standard library context with trace propagation.
	// Use this when calling external services or database drivers.
	//
	// Example:
	//     row := db.QueryRowContext(ctx.StdContext(), "SELECT * FROM users WHERE id = $1", userID)
	//     req, _ := http.NewRequestWithContext(ctx.StdContext(), "GET", url, nil)
	//
	// The context includes any trace spans injected by middleware (e.g., OpenTelemetry).
	StdContext() context.Context

	// WithStdContext updates the standard context used by StdContext().
	// This is used by middleware to inject trace spans for downstream calls.
	//
	// This is typically called by observability middleware:
	//     spanCtx, span := tracer.Start(ctx.StdContext(), "operation")
	//     defer span.End()
	//     ctx = ctx.WithStdContext(spanCtx)
	//     return next()
	WithStdContext(stdCtx context.Context) Ctx

	// Event returns the current WebSocket event being processed.
	// Returns nil for HTTP-only requests (SSR).
	//
	// Example:
	//     if event := ctx.Event(); event != nil {
	//         log.Printf("Processing %s event on %s", event.Type, event.HID)
	//     }
	Event() *Event

	// PatchCount returns the number of patches sent during this request.
	// This is updated after each render cycle.
	PatchCount() int

	// AddPatchCount increments the patch count for this request.
	// Called internally by the render system.
	AddPatchCount(count int)

	// StormBudget returns the storm budget checker for this session.
	// Used by primitives (Action, Resource, GoLatest) to check rate limits.
	// Returns nil if storm budgets are not configured.
	//
	// See SPEC_ADDENDUM.md §A.4 for storm budget configuration.
	StormBudget() vango.StormBudgetChecker

	// Mode returns the current render mode as int.
	// Returns 0 (ModeNormal) for regular requests, 1 (ModePrefetch) during prefetch.
	//
	// Primitives like Effect, Interval, and signal.Set() check this to
	// enforce read-only behavior during prefetch:
	//   - ModePrefetch (1): Signal writes are dropped, effects are no-ops
	//   - ModeNormal (0): All operations proceed normally
	//
	// This method implements vango.PrefetchModeChecker interface.
	//
	// Example (for primitive implementations):
	//     if ctx := UseCtx(); ctx != nil && ctx.Mode() == 1 {
	//         if DevMode { panic("signal write forbidden in prefetch") }
	//         return // drop in prod
	//     }
	Mode() int

	// Asset resolves a source asset path to its fingerprinted path.
	// Returns the original path if no manifest is configured or the asset is not found.
	//
	// This enables cache-busting via content-hashed filenames while keeping
	// templates simple:
	//
	// Example:
	//
	//     <script src={ctx.Asset("vango.js")}></script>
	//     // In dev: "/public/vango.js"
	//     // In prod: "/public/vango.a1b2c3d4.min.js"
	Asset(source string) string
}

Ctx provides access to request data within components. It is passed to route handlers and can be used to access the request, session, and response control methods.

func NewTestContext

func NewTestContext(s *Session) Ctx

NewTestContext creates a context for testing with the given session. This allows testing components that require a valid context.

func NewTestContextWithDebug

func NewTestContextWithDebug(s *Session, debugMode bool) Ctx

NewTestContextWithDebug creates a test context with an explicit debug flag.

type Event

type Event struct {
	// Seq is the sequence number of the event.
	Seq uint64

	// Type is the type of event (click, input, submit, etc.).
	Type protocol.EventType

	// HID is the hydration ID of the target element.
	HID string

	// Payload contains type-specific event data.
	Payload any

	// Session is the session that received the event.
	Session *Session

	// Time is when the event was received by the server.
	Time time.Time
	// contains filtered or unexported fields
}

Event represents a decoded event from the client with runtime context.

func (*Event) TypeString

func (e *Event) TypeString() string

TypeString returns the string representation of the event type. Used for logging and tracing.

type EventRateLimiter

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

EventRateLimiter implements token bucket rate limiting for events.

func (*EventRateLimiter) Allow

func (r *EventRateLimiter) Allow() bool

Allow returns true if the event is allowed.

type FormData

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

FormData represents submitted form data.

func (FormData) All

func (f FormData) All() map[string]string

All returns all form fields.

func (FormData) Get

func (f FormData) Get(key string) string

Get returns the value for a form field.

func (FormData) Has

func (f FormData) Has(key string) bool

Has returns whether a form field exists.

type FuncComponent

type FuncComponent func() *vdom.VNode

FuncComponent wraps a render function as a Component.

func (FuncComponent) Render

func (f FuncComponent) Render() *vdom.VNode

Render calls the wrapped function.

type Handler

type Handler func(event *Event)

Handler is the internal event handler function type. It receives a decoded event and processes it.

type HandlerError

type HandlerError struct {
	SessionID string
	HID       string
	EventType string
	Panic     any
	Stack     []byte
}

HandlerError wraps a panic that occurred in an event handler.

func NewHandlerError

func NewHandlerError(sessionID, hid, eventType string, panicVal any, stack []byte) *HandlerError

NewHandlerError creates a new HandlerError.

func (*HandlerError) Error

func (e *HandlerError) Error() string

Error returns the error message.

type HookEvent

type HookEvent struct {
	Name string
	Data map[string]any
}

HookEvent represents a client hook event.

func (HookEvent) Get

func (h HookEvent) Get(key string) any

Get returns a value from the hook data.

func (HookEvent) GetBool

func (h HookEvent) GetBool(key string) bool

GetBool returns a bool value from the hook data.

func (HookEvent) GetInt

func (h HookEvent) GetInt(key string) int

GetInt returns an int value from the hook data.

func (HookEvent) GetString

func (h HookEvent) GetString(key string) string

GetString returns a string value from the hook data.

type InstancePath

type InstancePath struct {
	RouteID  string
	KeyPath  []string
	StableID string
}

InstancePath uniquely identifies a primitive instance.

func ComputeInstancePath

func ComputeInstancePath(session *Session, component *ComponentInstance, stableID string) InstancePath

ComputeInstancePath builds the full path for a primitive.

func (InstancePath) String

func (p InstancePath) String() string

type KeyboardEvent

type KeyboardEvent struct {
	Key      string
	CtrlKey  bool
	ShiftKey bool
	AltKey   bool
	MetaKey  bool
}

KeyboardEvent represents a keyboard event with key and modifiers.

type LayoutHandler

type LayoutHandler func(ctx Ctx, children vango.Slot) *vdom.VNode

LayoutHandler wraps child content in a layout.

type ManagerStats

type ManagerStats struct {
	Active       int
	TotalCreated uint64
	TotalClosed  uint64
	Peak         int
	TotalMemory  int64
}

ManagerStats contains aggregated session manager statistics.

type MemoryMonitor

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

MemoryMonitor monitors system memory and triggers actions when thresholds are exceeded.

func NewMemoryMonitor

func NewMemoryMonitor(config *MemoryMonitorConfig) *MemoryMonitor

NewMemoryMonitor creates a new memory monitor.

func (*MemoryMonitor) ForceCheck

func (m *MemoryMonitor) ForceCheck()

ForceCheck performs an immediate memory check.

func (*MemoryMonitor) Pause

func (m *MemoryMonitor) Pause()

Pause temporarily pauses memory checking.

func (*MemoryMonitor) Resume

func (m *MemoryMonitor) Resume()

Resume resumes memory checking.

func (*MemoryMonitor) SetOnHardLimit

func (m *MemoryMonitor) SetOnHardLimit(fn func(current, limit int64))

SetOnHardLimit sets the callback for hard limit breach.

func (*MemoryMonitor) SetOnSoftLimit

func (m *MemoryMonitor) SetOnSoftLimit(fn func(current, limit int64))

SetOnSoftLimit sets the callback for soft limit breach.

func (*MemoryMonitor) Start

func (m *MemoryMonitor) Start()

Start begins monitoring memory.

func (*MemoryMonitor) Stop

func (m *MemoryMonitor) Stop()

Stop stops the memory monitor.

type MemoryMonitorConfig

type MemoryMonitorConfig struct {
	SoftLimit     int64         // Bytes at which to start evicting (default: 80% of system memory)
	HardLimit     int64         // Bytes at which to aggressively evict (default: 90% of system memory)
	CheckInterval time.Duration // How often to check memory (default: 10s)
	GCCooldown    time.Duration // Minimum time between forced GCs (default: 30s)
}

MemoryMonitorConfig configures the memory monitor.

func DefaultMemoryMonitorConfig

func DefaultMemoryMonitorConfig() *MemoryMonitorConfig

DefaultMemoryMonitorConfig returns sensible defaults.

type MemoryPressureLevel

type MemoryPressureLevel int

MemoryPressureLevel indicates the current memory pressure.

const (
	MemoryPressureNone     MemoryPressureLevel = iota // Below soft limit
	MemoryPressureLow                                 // Between soft and hard limit
	MemoryPressureHigh                                // Above hard limit
	MemoryPressureCritical                            // Near OOM
)

func GetMemoryPressureLevel

func GetMemoryPressureLevel(softLimit, hardLimit int64) MemoryPressureLevel

GetMemoryPressureLevel returns the current memory pressure level.

func (MemoryPressureLevel) String

func (l MemoryPressureLevel) String() string

String returns the string representation of the pressure level.

type MemoryStats

type MemoryStats struct {
	HeapAlloc    int64         // Bytes allocated on heap
	HeapSys      int64         // Bytes obtained from OS for heap
	HeapIdle     int64         // Bytes in idle spans
	HeapInuse    int64         // Bytes in non-idle spans
	HeapReleased int64         // Bytes released to OS
	StackInuse   int64         // Bytes used by stack
	NumGC        uint32        // Number of completed GC cycles
	LastGC       time.Time     // Time of last GC
	GCPauseTotal time.Duration // Total GC pause time
}

MemoryStats returns detailed memory statistics.

func GetMemoryStats

func GetMemoryStats() *MemoryStats

GetMemoryStats returns current memory statistics.

type MetricsCollector

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

MetricsCollector collects and aggregates metrics over time.

func NewMetricsCollector

func NewMetricsCollector() *MetricsCollector

NewMetricsCollector creates a new MetricsCollector.

func (*MetricsCollector) RecordActionDuration

func (m *MetricsCollector) RecordActionDuration(action string, ms int64)

RecordActionDuration records action duration.

func (*MetricsCollector) RecordActionError

func (m *MetricsCollector) RecordActionError(action string)

RecordActionError records an action error.

func (*MetricsCollector) RecordBytesReceived

func (m *MetricsCollector) RecordBytesReceived(n int)

RecordBytesReceived records bytes received.

func (*MetricsCollector) RecordBytesSent

func (m *MetricsCollector) RecordBytesSent(n int)

RecordBytesSent records bytes sent.

func (*MetricsCollector) RecordColdDeployAck

func (m *MetricsCollector) RecordColdDeployAck(source string)

RecordColdDeployAck records a cold deploy acknowledgment.

func (*MetricsCollector) RecordEventDropped

func (m *MetricsCollector) RecordEventDropped()

RecordEventDropped records an event dropped.

func (*MetricsCollector) RecordEventLatency

func (m *MetricsCollector) RecordEventLatency(latencyUs int64)

RecordEventLatency records event processing latency in microseconds.

func (*MetricsCollector) RecordEventProcessed

func (m *MetricsCollector) RecordEventProcessed()

RecordEventProcessed records an event processed.

func (*MetricsCollector) RecordEventReceived

func (m *MetricsCollector) RecordEventReceived()

RecordEventReceived records an event received.

func (*MetricsCollector) RecordHandlerPanic

func (m *MetricsCollector) RecordHandlerPanic()

RecordHandlerPanic records a handler panic.

func (*MetricsCollector) RecordPatchBytes

func (m *MetricsCollector) RecordPatchBytes(route string, bytes int)

RecordPatchBytes records patch bytes by route.

func (*MetricsCollector) RecordPatchMismatch

func (m *MetricsCollector) RecordPatchMismatch(reason string)

RecordPatchMismatch records a patch mismatch by reason.

func (*MetricsCollector) RecordPatchesSent

func (m *MetricsCollector) RecordPatchesSent(count int, bytes int)

RecordPatchesSent records patches sent.

func (*MetricsCollector) RecordPersistBytes

func (m *MetricsCollector) RecordPersistBytes(stableID string, bytes int64)

RecordPersistBytes records bytes written by stable ID.

func (*MetricsCollector) RecordPersistBytesWritten

func (m *MetricsCollector) RecordPersistBytesWritten(primitiveID string, bytes int64)

RecordPersistBytesWritten records bytes successfully written.

func (*MetricsCollector) RecordPersistWriteRejected

func (m *MetricsCollector) RecordPersistWriteRejected(reason string, primitiveID string)

RecordPersistWriteRejected records a rejected persist write.

func (*MetricsCollector) RecordReadError

func (m *MetricsCollector) RecordReadError()

RecordReadError records a read error.

func (*MetricsCollector) RecordResourceDuration

func (m *MetricsCollector) RecordResourceDuration(resource string, ms int64)

RecordResourceDuration records resource duration.

func (*MetricsCollector) RecordResourceError

func (m *MetricsCollector) RecordResourceError(resource string)

RecordResourceError records a resource error.

func (*MetricsCollector) RecordResumeFailure

func (m *MetricsCollector) RecordResumeFailure(reason string)

RecordResumeFailure records a failed resume with a reason.

func (*MetricsCollector) RecordResumeSuccess

func (m *MetricsCollector) RecordResumeSuccess()

RecordResumeSuccess records a successful resume.

func (*MetricsCollector) RecordSchemaMismatch

func (m *MetricsCollector) RecordSchemaMismatch(reason string)

RecordSchemaMismatch records a schema mismatch event.

func (*MetricsCollector) RecordSessionActive

func (m *MetricsCollector) RecordSessionActive(delta int)

RecordSessionActive records an active session delta.

func (*MetricsCollector) RecordSessionDetached

func (m *MetricsCollector) RecordSessionDetached(delta int)

RecordSessionDetached records a detached session delta.

func (*MetricsCollector) RecordSessionPersistBytes

func (m *MetricsCollector) RecordSessionPersistBytes(sessionID string, bytes int64)

RecordSessionPersistBytes records total persisted bytes per session.

func (*MetricsCollector) RecordSignalWriteViolation

func (m *MetricsCollector) RecordSignalWriteViolation(operation, reason string)

RecordSignalWriteViolation records an invalid signal write attempt.

func (*MetricsCollector) RecordWriteError

func (m *MetricsCollector) RecordWriteError()

RecordWriteError records a write error.

func (*MetricsCollector) Reset

func (m *MetricsCollector) Reset()

Reset resets all counters.

func (*MetricsCollector) Snapshot

func (m *MetricsCollector) Snapshot() *ServerMetrics

Snapshot returns current metrics.

type Middleware

type Middleware func(http.Handler) http.Handler

Middleware is a function that wraps an HTTP handler.

func CSPHeaderMiddleware

func CSPHeaderMiddleware(policy string, reportOnly bool) Middleware

CSPHeaderMiddleware sets a CSP header with the provided policy string.

func CSPMiddleware

func CSPMiddleware(opts CSPOptions) Middleware

CSPMiddleware sets a Content-Security-Policy header using a default policy with optional tweaks from CSPOptions.

type MouseEvent

type MouseEvent struct {
	ClientX  int
	ClientY  int
	Button   int
	CtrlKey  bool
	ShiftKey bool
	AltKey   bool
	MetaKey  bool
}

MouseEvent represents a mouse event with position and modifiers.

type NavigateEvent struct {
	Path    string
	Replace bool
}

NavigateEvent represents a navigation request.

type NavigateOption func(*navigateOptions)

NavigateOption is a functional option for Navigate.

func WithNavigateParams

func WithNavigateParams(params map[string]any) NavigateOption

WithNavigateParams adds query parameters to the navigation URL.

func WithReplace

func WithReplace() NavigateOption

WithReplace replaces the current history entry instead of pushing.

func WithoutScroll

func WithoutScroll() NavigateOption

WithoutScroll disables scrolling to top after navigation.

type NavigateResult struct {
	// Path is the canonicalized path that was navigated to
	Path string

	// Matched indicates if a route was matched
	Matched bool

	// Patches contains the DOM patches from the navigation
	Patches []vdom.Patch

	// NavPatch is the NAV_PUSH or NAV_REPLACE patch
	NavPatch protocol.Patch

	// Error contains any error that occurred
	Error error
}

NavigateResult contains the result of a navigation operation.

type OriginPolicyMode

type OriginPolicyMode int

OriginPolicyMode describes how WebSocket origins are validated.

const (
	OriginPolicyUnset OriginPolicyMode = iota
	OriginPolicySameOrigin
	OriginPolicyAllowedOrigins
	OriginPolicyCustom
	OriginPolicyAllowAll
)

type PageHandler

type PageHandler func(ctx Ctx, params any) Component

PageHandler handles a page request, returning a component to render.

type PatchHistory

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

PatchHistory is a thread-safe ring buffer for storing sent patch frames. It supports:

  • Fast insertion at head
  • Lookup by sequence range for resync
  • Garbage collection based on acknowledged sequence

The ring buffer overwrites oldest entries when full, maintaining a sliding window of recent patches that can be replayed if a client misses some.

func NewPatchHistory

func NewPatchHistory(capacity int) *PatchHistory

NewPatchHistory creates a new patch history ring buffer with the given capacity.

func (*PatchHistory) Add

func (h *PatchHistory) Add(seq uint64, frame []byte)

Add stores a patch frame in the buffer. This should be called ONLY after a successful write to the WebSocket. The frame bytes are copied to prevent issues with buffer reuse.

func (*PatchHistory) CanRecover

func (h *PatchHistory) CanRecover(lastSeq uint64) bool

CanRecover checks if the buffer can provide frames to recover from lastSeq. Returns true if all sequences from lastSeq+1 to maxSeq are available.

func (*PatchHistory) Clear

func (h *PatchHistory) Clear()

Clear removes all entries from the buffer. This is useful during session resume when starting fresh.

func (*PatchHistory) Count

func (h *PatchHistory) Count() int

Count returns the number of entries in the buffer.

func (*PatchHistory) GarbageCollect

func (h *PatchHistory) GarbageCollect(ackSeq uint64)

GarbageCollect updates minSeq based on the acknowledged sequence. Entries with sequence <= ackSeq are no longer needed for resync and will be overwritten naturally by the ring buffer.

func (*PatchHistory) GetFrames

func (h *PatchHistory) GetFrames(afterSeq, toSeq uint64) [][]byte

GetFrames returns frames for sequences (afterSeq, toSeq]. Returns nil if any sequence in the requested range is not available. The returned frames are in sequence order, ready to be replayed.

func (*PatchHistory) MaxSeq

func (h *PatchHistory) MaxSeq() uint64

MaxSeq returns the maximum sequence in buffer.

func (*PatchHistory) MemoryUsage

func (h *PatchHistory) MemoryUsage() int64

MemoryUsage estimates the memory used by the patch history, including frame bytes.

func (*PatchHistory) MinSeq

func (h *PatchHistory) MinSeq() uint64

MinSeq returns the minimum recoverable sequence.

type PatchHistoryEntry

type PatchHistoryEntry struct {
	Seq    uint64    // Patch sequence number
	Frame  []byte    // Pre-encoded FramePatches for fast replay
	SentAt time.Time // When the frame was sent
}

PatchHistoryEntry stores a sent patch frame for potential replay.

type PrefetchCache

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

PrefetchCache is an LRU cache for prefetched route renders. Each session has its own cache instance.

Per Section 8.2:

  • Keyed by canonical path
  • TTL: 30 seconds (configurable)
  • Max entries: 10 (LRU eviction)

func NewPrefetchCache

func NewPrefetchCache(config *PrefetchConfig) *PrefetchCache

NewPrefetchCache creates a new prefetch cache.

func (*PrefetchCache) Clear

func (c *PrefetchCache) Clear()

Clear removes all cached entries.

func (*PrefetchCache) Delete

func (c *PrefetchCache) Delete(path string)

Delete removes a cached entry.

func (*PrefetchCache) Get

func (c *PrefetchCache) Get(path string) *PrefetchCacheEntry

Get retrieves a cached prefetch result. Returns nil if not found or expired.

func (*PrefetchCache) Len

func (c *PrefetchCache) Len() int

Len returns the number of cached entries.

func (*PrefetchCache) MemoryUsage

func (c *PrefetchCache) MemoryUsage() int64

MemoryUsage estimates the memory used by the prefetch cache.

func (*PrefetchCache) Set

func (c *PrefetchCache) Set(path string, tree *vdom.VNode)

Set stores a prefetch result in the cache. If the cache is full, the least recently used entry is evicted.

type PrefetchCacheEntry

type PrefetchCacheEntry struct {
	// Tree is the rendered VNode tree
	Tree *vdom.VNode

	// CreatedAt is when this entry was created
	CreatedAt time.Time

	// ExpiresAt is when this entry expires
	ExpiresAt time.Time
}

PrefetchCacheEntry holds a cached prefetch result.

func (*PrefetchCacheEntry) IsExpired

func (e *PrefetchCacheEntry) IsExpired() bool

IsExpired returns true if the entry has expired.

type PrefetchConfig

type PrefetchConfig struct {
	// TTL is how long a prefetched result is valid.
	// Default: 30 seconds
	TTL time.Duration

	// MaxEntries is the maximum number of cached entries per session.
	// Uses LRU eviction when exceeded.
	// Default: 10
	MaxEntries int

	// Timeout is the maximum time to wait for a prefetch to complete.
	// If exceeded, the prefetch is aborted and result is not cached.
	// Default: 100ms
	Timeout time.Duration

	// RateLimit is the maximum prefetch requests per second per session.
	// Excess requests are silently dropped.
	// Default: 5
	RateLimit float64

	// SessionConcurrency is the max simultaneous prefetch evaluations per session.
	// Default: 2
	SessionConcurrency int

	// GlobalConcurrency is the max simultaneous prefetch evaluations globally.
	// Default: 50
	GlobalConcurrency int
}

PrefetchConfig holds configuration for the prefetch system. These defaults are per Section 8.2-8.3 of the Routing Spec.

func DefaultPrefetchConfig

func DefaultPrefetchConfig() *PrefetchConfig

DefaultPrefetchConfig returns the default prefetch configuration. Per Section 8.2 and 8.3.3 of the Routing Spec.

type PrefetchRateLimiter

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

PrefetchRateLimiter implements token bucket rate limiting for prefetch requests. Per Section 8.5:

  • Max 5 prefetch requests per second per session
  • Excess requests are silently dropped

func NewPrefetchRateLimiter

func NewPrefetchRateLimiter(ratePerSecond float64) *PrefetchRateLimiter

NewPrefetchRateLimiter creates a new rate limiter.

func (*PrefetchRateLimiter) Allow

func (r *PrefetchRateLimiter) Allow() bool

Allow returns true if a prefetch request is allowed. Returns false if rate limit is exceeded (request should be dropped).

type PrefetchSemaphore

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

PrefetchSemaphore limits concurrent prefetch operations. Used both per-session and globally.

func GlobalPrefetchSemaphore

func GlobalPrefetchSemaphore() *PrefetchSemaphore

GlobalPrefetchSemaphore returns the global prefetch semaphore. Used by sessions to check global concurrency limits.

func NewPrefetchSemaphore

func NewPrefetchSemaphore(limit int) *PrefetchSemaphore

NewPrefetchSemaphore creates a new semaphore with the given limit.

func (*PrefetchSemaphore) Acquire

func (s *PrefetchSemaphore) Acquire() bool

Acquire tries to acquire a slot. Returns true if successful. If the semaphore is full, returns false immediately (non-blocking).

func (*PrefetchSemaphore) Release

func (s *PrefetchSemaphore) Release()

Release releases a slot.

type PrimitiveInfo

type PrimitiveInfo struct {
	StableID     string
	DebugName    string
	AnchorKey    string
	Kind         string
	Class        string
	Persisted    bool
	InstancePath string
	Primitive    any
}

PrimitiveInfo contains inspection data for a registered primitive.

type ProtocolError

type ProtocolError struct {
	SessionID string
	Op        string
	Message   string
}

ProtocolError represents an error in the binary protocol.

func NewProtocolError

func NewProtocolError(sessionID, op, message string) *ProtocolError

NewProtocolError creates a new ProtocolError.

func (*ProtocolError) Error

func (e *ProtocolError) Error() string

Error returns the error message.

type ReconnectConfig

type ReconnectConfig struct {
	// ToastOnReconnect shows a toast notification when connection is restored.
	// Default: false.
	ToastOnReconnect bool

	// ToastMessage is the message shown in the reconnection toast.
	// Default: "Connection restored".
	ToastMessage string

	// MaxRetries is the maximum number of reconnection attempts before giving up.
	// Default: 10.
	MaxRetries int

	// BaseDelay is the initial delay between reconnection attempts (milliseconds).
	// Uses exponential backoff: delay = min(baseDelay * 2^attempt, maxDelay).
	// Default: 1000 (1 second).
	BaseDelay int

	// MaxDelay is the maximum delay between reconnection attempts (milliseconds).
	// Default: 30000 (30 seconds).
	MaxDelay int
}

ReconnectConfig configures client-side reconnection behavior. These values are sent to the client during handshake and control how the thin client handles connection interruptions.

func DefaultReconnectConfig

func DefaultReconnectConfig() *ReconnectConfig

DefaultReconnectConfig returns a ReconnectConfig with sensible defaults.

type RenderMode

type RenderMode int

RenderMode indicates the rendering context for a request. This is used by the prefetch system to enforce read-only behavior.

const (
	// ModeNormal is the default rendering mode for regular requests.
	// All operations are allowed.
	ModeNormal RenderMode = iota

	// ModePrefetch is used when prefetching a route for caching.
	// In this mode, side effects are forbidden:
	//   - Signal.Set() panics in dev / drops in prod
	//   - Effect/Interval/Timeout panic in dev / no-op in prod
	//   - SetUser() panics
	//   - Navigate() is ignored
	//
	// Per Routing Spec Section 8.3.1, prefetch uses "bounded I/O":
	// synchronous work is allowed, but no async work may outlive the prefetch.
	ModePrefetch
)

func (RenderMode) String

func (m RenderMode) String() string

String returns a human-readable name for the render mode.

type ResizeEvent

type ResizeEvent struct {
	Width  int
	Height int
}

ResizeEvent represents a resize event with dimensions.

type RouteMatch

type RouteMatch interface {
	// GetParams returns the extracted route parameters.
	GetParams() map[string]string

	// GetPageHandler returns the page handler, if any.
	GetPageHandler() PageHandler

	// GetLayoutHandlers returns the layout handlers in order (root to leaf).
	GetLayoutHandlers() []LayoutHandler

	// GetMiddleware returns the middleware chain.
	GetMiddleware() []RouteMiddleware
}

RouteMatch contains the result of matching a path against the router. This interface is implemented by router.MatchResult.

type RouteMiddleware

type RouteMiddleware interface {
	Handle(ctx Ctx, next func() error) error
}

RouteMiddleware processes requests before they reach the handler. This is different from HTTP middleware - it operates on the routing level.

type RouteNavigator

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

RouteNavigator handles route-based navigation for sessions. It is responsible for matching routes, invoking page handlers, and managing the page component lifecycle during navigation.

func NewRouteNavigator

func NewRouteNavigator(session *Session, r Router) *RouteNavigator

NewRouteNavigator creates a new route navigator for a session.

func (*RouteNavigator) CurrentParams

func (rn *RouteNavigator) CurrentParams() map[string]string

CurrentParams returns the current route parameters.

func (*RouteNavigator) CurrentPath

func (rn *RouteNavigator) CurrentPath() string

CurrentPath returns the current route path.

func (*RouteNavigator) Navigate

func (rn *RouteNavigator) Navigate(path string, replace bool) *NavigateResult

Navigate handles navigation to a new path. This is called when:

  • ctx.Navigate() is called and pending navigation is processed
  • EventNavigate is received from the client

The navigation process:

  1. Canonicalize the path
  2. Determine if NAV_PUSH or NAV_REPLACE should be used
  3. Match the route
  4. Create the new page component
  5. Mount and render the new page
  6. Diff against the old tree
  7. Return patches

Per Section 4.4 (Programmatic Navigation), this is ONE transaction - NAV_* patch and DOM patches are returned together.

type Router

type Router interface {
	// Match finds the handler for a path.
	// Returns the match result and whether a match was found.
	Match(method, path string) (RouteMatch, bool)

	// NotFound returns the 404 handler, if configured.
	NotFound() PageHandler
}

Router defines the interface for route matching. This interface is implemented by router.Router.

type SchemaError

type SchemaError struct {
	Reason        string
	Message       string
	Reasons       []string
	IsCold        bool
	SchemaHash    string
	SchemaVersion uint16
}

SchemaError represents a schema-related resume failure.

func (*SchemaError) Error

func (e *SchemaError) Error() string

type ScrollEvent

type ScrollEvent struct {
	ScrollTop  int
	ScrollLeft int
}

ScrollEvent represents a scroll event with position.

type Server

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

Server is the main HTTP/WebSocket server for Vango.

func MustNew

func MustNew(config *ServerConfig) *Server

MustNew creates a new Server and panics if the configuration is invalid.

func New

func New(config *ServerConfig) (*Server, error)

New creates a new Server with the given configuration. Returns an error if the configuration is invalid for non-DevMode environments.

func (*Server) CSRFMiddleware

func (s *Server) CSRFMiddleware() Middleware

CSRFMiddleware enforces CSRF for state-changing HTTP requests.

func (*Server) Config

func (s *Server) Config() *ServerConfig

Config returns the server configuration.

func (*Server) CookiePolicy

func (s *Server) CookiePolicy() *CookiePolicy

CookiePolicy returns the server cookie policy helper.

func (*Server) EnsureCSRFCookie

func (s *Server) EnsureCSRFCookie(w http.ResponseWriter, r *http.Request) (string, error)

EnsureCSRFCookie ensures a valid CSRF cookie exists for the request. Returns the token value when present or generated.

func (*Server) GenerateCSRFToken

func (s *Server) GenerateCSRFToken() string

GenerateCSRFToken generates a new cryptographically secure CSRF token. If CSRFSecret is set, the token is HMAC-signed for additional security. This should be: 1. Set as a cookie with path=/, SameSite from config, Secure when required 2. Embedded in the initial HTML page for the client to send in handshake

func (*Server) HandleWebSocket

func (s *Server) HandleWebSocket(w http.ResponseWriter, r *http.Request)

HandleWebSocket handles WebSocket upgrade and connection.

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns an http.Handler for mounting in external routers. This is the primary integration point for ecosystem compatibility with Chi, Gorilla, Echo, stdlib mux, etc.

The handler dispatches based on path:

  • /_vango/ws, /_vango/live → WebSocket upgrade
  • /_vango/* → Internal routes (future: CSRF, assets)
  • /* → Page routes via SSR/handler

Example:

r := chi.NewRouter()
r.Use(middleware.Logger)
r.Use(authMiddleware)
r.Handle("/*", app.Handler())
http.ListenAndServe(":3000", r)

func (*Server) Logger

func (s *Server) Logger() *slog.Logger

Logger returns the server logger.

func (*Server) Metrics

func (s *Server) Metrics() *ServerMetrics

Metrics collects and returns server metrics.

func (*Server) PageHandler

func (s *Server) PageHandler() http.Handler

PageHandler returns an http.Handler for page routes only. Use when you want to handle /_vango/* routes separately.

func (*Server) Router

func (s *Server) Router() Router

Router returns the current router, or nil if none is set.

func (*Server) Run

func (s *Server) Run() error

Run starts the server and blocks until shutdown.

func (*Server) RunContext

func (s *Server) RunContext(ctx context.Context) error

RunContext starts the server and blocks until:

  • the HTTP server returns an error (including unexpected exit), or
  • ctx is canceled (graceful shutdown).

When ctx is canceled, RunContext attempts a graceful shutdown and returns nil unless shutdown fails.

func (*Server) ServeHTTP

func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements http.Handler.

func (*Server) Sessions

func (s *Server) Sessions() *SessionManager

Sessions returns the session manager.

func (*Server) SetAuthFunc

func (s *Server) SetAuthFunc(fn func(*http.Request) (any, error))

SetAuthFunc sets the authentication function for validating requests.

The function is called during:

  • SSR rendering to populate user context
  • WebSocket session resume to revalidate authentication

For session resume, the return values are interpreted as:

  • (user, nil): Auth valid, user is set on session
  • (nil, nil): Auth invalid/absent, resume rejected if session was previously authenticated
  • (nil, err): Auth failed, resume rejected if session was previously authenticated

IMPORTANT: Returning (nil, nil) for a previously authenticated session will reject the resume. This is the expected behavior for sessions where auth is no longer valid (e.g., cookie expired, user logged out elsewhere).

func (*Server) SetCSRFCookie

func (s *Server) SetCSRFCookie(w http.ResponseWriter, r *http.Request, token string) error

SetCSRFCookie sets the CSRF cookie on the response. Call this when rendering the initial page. Uses request TLS or trusted proxy headers to decide the Secure flag. Returns ErrSecureCookiesRequired if secure cookies are enabled and the request is not secure.

func (*Server) SetHandler

func (s *Server) SetHandler(h http.Handler)

SetHandler sets the HTTP handler for non-WebSocket requests.

func (*Server) SetLogger

func (s *Server) SetLogger(logger *slog.Logger)

SetLogger sets the server logger.

func (*Server) SetMetricsSink

func (s *Server) SetMetricsSink(sink vango.MetricsSink)

SetMetricsSink updates the metrics sink used by the server and new sessions. Call this during setup before accepting connections.

func (*Server) SetRootComponent

func (s *Server) SetRootComponent(factory func() Component)

SetRootComponent sets the root component factory.

func (*Server) SetRouter

func (s *Server) SetRouter(r Router)

SetRouter sets the router for route-based navigation. When set, this router is passed to all new sessions to enable:

  • Client-side navigation via EventNavigate (link clicks, popstate)
  • Programmatic navigation via ctx.Navigate()

The router must implement the Router interface (defined in navigation.go). Use router.NewRouterAdapter to wrap a router.Router for use with Server.

Example:

r := router.NewRouter()
r.AddPage("/", index.IndexPage)
r.AddPage("/about", about.AboutPage)
r.AddPage("/projects/:id", projects.ShowPage)
r.SetNotFound(notfound.NotFoundPage)

app.SetRouter(router.NewRouterAdapter(r))

func (*Server) Shutdown

func (s *Server) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down the server.

func (*Server) UploadHandler

func (s *Server) UploadHandler(store upload.Store, config *upload.Config) http.Handler

UploadHandler returns an upload handler wrapped with CSRF middleware. Use this when you need fine-grained control over routing, or when mounting upload handlers on a custom mux.

The handler is automatically wrapped with CSRF middleware when CSRF is enabled. Clients must include the CSRF token in the X-CSRF-Token header.

Example:

mux.Handle("POST /api/upload", srv.UploadHandler(store, nil))

With custom config:

mux.Handle("POST /api/upload", srv.UploadHandler(store, &upload.Config{
    MaxFileSize:  5 * 1024 * 1024,
    AllowedTypes: []string{"image/png", "image/jpeg"},
}))

For most applications, prefer using app.HandleUpload() instead, which handles routing automatically.

func (*Server) Use

func (s *Server) Use(mw Middleware)

Use adds middleware to the server.

func (*Server) ValidateCSRFRequest

func (s *Server) ValidateCSRFRequest(w http.ResponseWriter, r *http.Request) error

ValidateCSRFRequest validates CSRF for an HTTP request. It enforces CSRF for state-changing methods (POST, PUT, PATCH, DELETE).

func (*Server) WebSocketHandler

func (s *Server) WebSocketHandler() http.Handler

WebSocketHandler returns an http.Handler for WebSocket upgrade only. Use when you want fine-grained control over routing.

func (*Server) WebSocketURL

func (s *Server) WebSocketURL(r *http.Request) string

WebSocketURL returns the WebSocket URL for this server and request. It respects TLS and trusted proxy headers when determining scheme.

SECURITY: This function validates the Host header against AllowedHosts to prevent Host header poisoning attacks. If the host is not in the allowlist, it returns an empty string (fail secure).

If CanonicalHost is configured and the request host is valid, the canonical host is used instead of the request host.

type ServerConfig

type ServerConfig struct {
	// Address is the address to listen on (e.g., ":8080" or "localhost:3000").
	// Default: ":8080".
	Address string

	// ReadBufferSize is the WebSocket read buffer size.
	// Default: 4096.
	ReadBufferSize int

	// WriteBufferSize is the WebSocket write buffer size.
	// Default: 4096.
	WriteBufferSize int

	// CheckOrigin is called to validate the request origin.
	// Default: same-origin validation.
	// For allowlist policies, configure via WithAllowedOrigins or AllowedOriginsCheck.
	CheckOrigin func(r *http.Request) bool

	// OriginPolicy describes how WebSocket origin checks are configured.
	// Used for validation and guardrails in production.
	OriginPolicy OriginPolicyMode

	// AllowedOrigins lists domains allowed for WebSocket connections.
	// Entries must be valid origins (scheme + host + optional port) with no path,
	// query, or fragment. Origin matching is normalized by scheme + lowercase host
	// + effective port (default ports may be omitted or explicit).
	// Used for validation when OriginPolicy is OriginPolicyAllowedOrigins.
	AllowedOrigins []string

	// AllowSameOrigin enables same-origin validation.
	// Used for validation when OriginPolicy is OriginPolicySameOrigin.
	AllowSameOrigin bool

	// AllowCustomOriginPolicy explicitly permits custom origin checks in production.
	AllowCustomOriginPolicy bool

	// SessionConfig is the configuration for individual sessions.
	// Default: DefaultSessionConfig().
	SessionConfig *SessionConfig
	// SessionLimits overrides the default session limits when non-nil.
	// Values in this struct are authoritative, including zero values
	// (for example, MaxSessions=0 means unlimited).
	SessionLimits *SessionLimits

	// ShutdownTimeout is the maximum time to wait for graceful shutdown.
	// Default: 30 seconds.
	ShutdownTimeout time.Duration

	// ReadHeaderTimeout is the maximum time to read request headers.
	// Default: 5 seconds.
	ReadHeaderTimeout time.Duration

	// ReadTimeout is the maximum time to read the entire request.
	// Default: 30 seconds.
	ReadTimeout time.Duration

	// WriteTimeout is the maximum time to write the response.
	// Default: 30 seconds.
	WriteTimeout time.Duration

	// IdleTimeout is the maximum time to wait for the next request
	// when keep-alives are enabled.
	// Default: 60 seconds.
	IdleTimeout time.Duration

	// MaxSessions is a legacy convenience override for the global session cap.
	// Used only when SessionLimits is nil.
	// Values > 0 override DefaultSessionLimits().MaxSessions.
	// Values <= 0 leave the default effective cap unchanged.
	// Default: 0 (no legacy override).
	MaxSessions int

	// MaxMemoryPerSession is the approximate memory limit per session.
	// Sessions exceeding this may be evicted under memory pressure.
	// Default: 3MB.
	MaxMemoryPerSession int64

	// MaxConcurrentHandshakes is the maximum number of concurrent WebSocket
	// connections waiting to complete handshake/auth/session admission.
	// Helps bound pre-auth resource usage (FDs/goroutines/memory).
	// 0 means no limit.
	// Default: 1024.
	MaxConcurrentHandshakes int

	// MaxConcurrentHandshakesPerIP is the maximum number of concurrent
	// pre-auth handshakes from a single client IP.
	// Helps prevent per-IP pre-auth socket exhaustion.
	// 0 means no limit.
	// Default: 64.
	MaxConcurrentHandshakesPerIP int

	// CSRFSecret is the secret key for CSRF token generation.
	// If nil and CSRF is enabled in non-DevMode, a random secret is generated
	// at startup (tokens won't survive server restarts).
	// To disable CSRF protection entirely, set CSRFEnabled = false explicitly.
	// For production, prefer a stable 32+ byte secret.
	CSRFSecret []byte

	// CSRFEnabled enables CSRF protection for state-changing HTTP endpoints.
	// Default: true.
	CSRFEnabled bool

	// PreUpgradeCheck runs before upgrading HTTP to WebSocket.
	//
	// Use this as a lightweight pre-upgrade gate (for example:
	// token/header/query/subprotocol checks) to reject unauthenticated
	// or malformed upgrade attempts before a WebSocket socket is allocated.
	//
	// Return nil to allow the upgrade. Return an error to reject with HTTP 403.
	// If the returned error implements `StatusCode() int`, that status code is used
	// when it is in the 4xx/5xx range.
	// Default: nil (disabled).
	PreUpgradeCheck func(r *http.Request) error

	// AllowedRedirectHosts lists hostnames (and optional ports) allowed for external redirects.
	// When empty, external redirects are rejected.
	// Example: []string{"accounts.example.com", "auth.example.com:8443"}
	AllowedRedirectHosts []string

	// AllowedHosts lists hostnames (and optional ports) that are valid for this server.
	// Used to validate the Host header in WebSocketURL() to prevent Host header poisoning.
	// If empty, WebSocketURL() returns an empty string (fail secure).
	// Example: []string{"myapp.com", "www.myapp.com", "myapp.com:8080"}
	AllowedHosts []string

	// CanonicalHost is the preferred hostname to use when generating URLs.
	// If set and the request Host matches any AllowedHosts entry, this value
	// is used instead. Useful for normalizing "www" vs non-"www" hosts.
	// If empty, the validated request host is used as-is.
	CanonicalHost string

	// CleanupInterval is the interval for the session cleanup loop.
	// Default: 30 seconds.
	CleanupInterval time.Duration

	// OnSessionStart is called during WebSocket upgrade, BEFORE the handshake completes.
	// Use this to copy data from the HTTP context (e.g., authenticated user) to the Vango session.
	// This runs SYNCHRONOUSLY before the WebSocket upgrade completes, while r.Context() is still alive.
	// After this callback returns, the HTTP context is dead and cannot be accessed.
	//
	// Example:
	//     OnSessionStart: func(httpCtx context.Context, session *Session) {
	//         if user := myauth.UserFromContext(httpCtx); user != nil {
	//             auth.Set(session, user)  // Use auth.Set to set presence flag
	//         }
	//     }
	OnSessionStart func(httpCtx context.Context, session *Session)

	// OnSessionResume is called during WebSocket session resume, BEFORE the handshake completes.
	// Use this to rehydrate session data from the HTTP context (e.g., re-validate and restore user).
	//
	// Unlike OnSessionStart, this is called when resuming an existing session after disconnect.
	// The session's signal state is preserved, but auth data should be revalidated from cookies/headers.
	//
	// Return nil to allow the resume, or an error to reject it.
	// If an error is returned and the session was previously authenticated (auth.WasAuthenticated),
	// the resume is rejected with HandshakeNotAuthorized. If the session was not authenticated,
	// an error is logged but the resume continues as a guest session.
	//
	// IMPORTANT: Simply returning nil is NOT sufficient for security. The hook MUST
	// actually revalidate authentication and call auth.Set() to rehydrate the user.
	// In strict mode without authFunc, resume clears runtime auth projection before
	// this hook runs, so a no-op OnSessionResume is rejected for previously
	// authenticated sessions.
	//
	// Example:
	//     OnSessionResume: func(httpCtx context.Context, session *Session) error {
	//         user, err := myauth.ValidateFromContext(httpCtx)
	//         if err != nil {
	//             return err  // Reject resume if previously authenticated
	//         }
	//         if user != nil {
	//             auth.Set(session, user)  // REQUIRED: rehydrate user
	//         }
	//         return nil
	//     }
	OnSessionResume func(httpCtx context.Context, session *Session) error

	// AuthResumePolicy controls how authenticated session resumes are validated.
	//
	// Default: AuthResumePolicyStrict (requires authFunc or OnSessionResume for
	// authenticated sessions to resume).
	//
	// SECURITY: The strict default ensures that leaked session IDs cannot be used
	// to hijack authenticated sessions. If a session ID leaks via XSS, logging,
	// or other vectors, the attacker cannot resume the session without also
	// presenting valid authentication credentials.
	//
	// Set to AuthResumePolicyTrustSessionID only if you understand the security
	// implications and have compensating controls in place.
	AuthResumePolicy AuthResumePolicy

	// AllowTrustSessionIDInProduction acknowledges the security tradeoff of
	// AuthResumePolicyTrustSessionID in non-DevMode.
	//
	// When false (default), non-Dev configs that set TrustSessionID are rejected.
	// TrustSessionID also requires ResumeWindow <= 30s in non-DevMode.
	AllowTrustSessionIDInProduction bool

	// TrustedProxies lists trusted reverse proxy IPs for X-Forwarded-* headers.
	// If set, the server will trust forwarded headers (including X-Forwarded-Proto)
	// from these IPs when determining request security.
	// Default: nil (don't trust proxy headers).
	TrustedProxies []string

	// DebugMode enables extra validation and logging for development.
	// When true:
	// - Session.Set() panics on unserializable types (func, chan)
	// - auth.Get() logs warnings on type mismatches
	// Default: false.
	DebugMode bool

	// DevMode disables security checks for local development.
	// SECURITY: NEVER use in production - this disables:
	// - Origin checking (allows all origins)
	// - CSRF validation
	// - Secure cookie requirements
	// Default: false (secure by default)
	DevMode bool

	// SecureCookies enforces Secure flag on cookies set by the server.
	// Should be true when using HTTPS.
	// Default: true
	SecureCookies bool

	// CookieHTTPOnly sets the HttpOnly flag on cookies set by the server.
	// Prevents JavaScript access to cookies that should not be read by JS.
	// Default: true (except for cookies that explicitly opt out like CSRF).
	CookieHTTPOnly bool

	// SameSiteMode sets the SameSite attribute for cookies.
	// Lax is safe for most use cases and allows OAuth flows.
	// Default: http.SameSiteLaxMode
	SameSiteMode http.SameSite

	// CookieDomain sets the Domain attribute for cookies set by the server.
	// Empty string uses the current domain (most secure).
	// Default: "" (current domain)
	CookieDomain string

	// SessionStore is the persistence backend for sessions.
	// If nil, sessions are only stored in memory (lost on restart).
	// Use session.NewMemoryStore(), session.NewRedisStore(), or session.NewSQLStore().
	// Default: nil (in-memory only).
	SessionStore session.SessionStore

	// GlobalSignalPubSub is the broadcast backend for global signals.
	// When nil, global signals update via eventual consistency only.
	GlobalSignalPubSub corevango.GlobalBroadcastBackend

	// GlobalSignalRefreshInterval controls how often global signals refresh
	// from persistence when no broadcast backend is configured.
	// Default: 30 seconds (when GlobalSignalPubSub is nil).
	GlobalSignalRefreshInterval time.Duration

	// ResumeWindow is how long a detached session remains resumable after disconnect.
	// After this window, the session is permanently expired.
	// 0 disables resumption entirely.
	// Default: 5 minutes.
	ResumeWindow time.Duration

	// MaxDetachedSessions is the maximum number of disconnected sessions to keep in memory.
	// When exceeded, the least recently used sessions are evicted (and persisted if store is configured).
	// Default: 10000.
	MaxDetachedSessions int

	// MaxSessionsPerIP is the maximum number of concurrent sessions from a single IP address.
	// Helps prevent DoS attacks from IP exhaustion.
	// 0 means no limit.
	// Default: 100.
	MaxSessionsPerIP int

	// EvictionPolicy determines how detached sessions are evicted when limits are exceeded.
	// Default: EvictionLRU.
	EvictionPolicy SessionEvictionPolicy

	// EvictOnIPLimit controls whether hitting MaxSessionsPerIP evicts the oldest
	// detached session for that IP instead of rejecting the new session.
	// Default: true when MaxSessionsPerIP > 0.
	EvictOnIPLimit bool

	// PersistInterval is how often to persist dirty sessions to the store.
	// Set to 0 to only persist on disconnect.
	// Default: 30 seconds.
	PersistInterval time.Duration

	// ReconnectConfig configures client-side reconnection behavior.
	// Default: ReconnectConfig with sensible defaults.
	ReconnectConfig *ReconnectConfig

	// AssetResolver is the resolver for fingerprinted asset paths.
	// When set, ctx.Asset() will use this resolver to map source paths
	// to their fingerprinted versions (e.g., "vango.js" → "vango.a1b2c3d4.min.js").
	//
	// If nil, ctx.Asset() returns paths unchanged.
	//
	// Example:
	//
	//     manifest, _ := vango.LoadAssetManifest("dist/manifest.json")
	//     config.AssetResolver = vango.NewAssetResolver(manifest, "/public/")
	//
	// For development (no fingerprinting):
	//
	//     config.AssetResolver = vango.NewPassthroughResolver("/public/")
	//
	// Default: nil (passthrough behavior).
	AssetResolver assets.Resolver
	// contains filtered or unexported fields
}

ServerConfig holds configuration for the HTTP/WebSocket server.

func DefaultServerConfig

func DefaultServerConfig() *ServerConfig

DefaultServerConfig returns a ServerConfig with sensible defaults. SECURITY: All security features are ENABLED by default. SECURITY: CheckOrigin enforces same-origin by default to prevent CSWSH. SECURITY: CSRFSecret is nil by default; server.New generates an ephemeral secret in non-DevMode and logs a warning. SECURITY: SecureCookies is true by default for HTTPS environments.

func (*ServerConfig) Clone

func (c *ServerConfig) Clone() *ServerConfig

Clone returns a copy of the ServerConfig.

func (*ServerConfig) EnableToastOnReconnect

func (c *ServerConfig) EnableToastOnReconnect(message string) *ServerConfig

EnableToastOnReconnect enables toast notifications on reconnect. Shorthand for modifying ReconnectConfig.

func (*ServerConfig) GetConfigWarnings

func (c *ServerConfig) GetConfigWarnings() []string

GetConfigWarnings returns a list of configuration warnings. Useful for displaying warnings in a custom way.

func (*ServerConfig) IsSecure

func (c *ServerConfig) IsSecure() bool

IsSecure returns true if the configuration has all security features enabled. Useful for startup checks.

func (*ServerConfig) ValidateConfig

func (c *ServerConfig) ValidateConfig() error

ValidateConfig validates the server configuration. Called automatically by Server.New() and Server.Run(). Returns any fatal configuration errors.

func (*ServerConfig) WithAddress

func (c *ServerConfig) WithAddress(addr string) *ServerConfig

WithAddress sets the server address and returns the config for chaining.

func (*ServerConfig) WithAllowedHosts

func (c *ServerConfig) WithAllowedHosts(hosts ...string) *ServerConfig

WithAllowedHosts sets the allowed hostnames for WebSocketURL validation. This prevents Host header poisoning attacks.

func (*ServerConfig) WithAllowedOrigins

func (c *ServerConfig) WithAllowedOrigins(origins ...string) *ServerConfig

WithAllowedOrigins sets the allowed WebSocket origins and configures the origin policy. This enforces an allowlist-based CheckOrigin in production.

func (*ServerConfig) WithCSRFSecret

func (c *ServerConfig) WithCSRFSecret(secret []byte) *ServerConfig

WithCSRFSecret sets the CSRF secret and returns the config for chaining.

func (*ServerConfig) WithCanonicalHost

func (c *ServerConfig) WithCanonicalHost(host string) *ServerConfig

WithCanonicalHost sets the canonical hostname for URL generation.

func (*ServerConfig) WithCookieDomain

func (c *ServerConfig) WithCookieDomain(domain string) *ServerConfig

WithCookieDomain sets the domain for session cookies.

func (*ServerConfig) WithCookieHTTPOnly

func (c *ServerConfig) WithCookieHTTPOnly(httpOnly bool) *ServerConfig

WithCookieHTTPOnly sets whether cookies should be HttpOnly by default.

func (*ServerConfig) WithDevMode

func (c *ServerConfig) WithDevMode() *ServerConfig

WithDevMode enables development mode which disables security checks. SECURITY WARNING: NEVER use in production. This disables:

  • Origin checking (allows all origins)
  • CSRF validation (disabled)
  • Secure cookie requirements (disabled)

Only use for local development:

config := server.DefaultServerConfig().WithDevMode()

func (*ServerConfig) WithEvictOnIPLimit

func (c *ServerConfig) WithEvictOnIPLimit(evict bool) *ServerConfig

WithEvictOnIPLimit sets whether to evict the oldest detached session on IP limit.

func (*ServerConfig) WithEvictionPolicy

func (c *ServerConfig) WithEvictionPolicy(policy SessionEvictionPolicy) *ServerConfig

WithEvictionPolicy sets the detached session eviction policy.

func (*ServerConfig) WithMaxConcurrentHandshakes

func (c *ServerConfig) WithMaxConcurrentHandshakes(max int) *ServerConfig

WithMaxConcurrentHandshakes sets the global pre-auth concurrent handshake cap.

func (*ServerConfig) WithMaxConcurrentHandshakesPerIP

func (c *ServerConfig) WithMaxConcurrentHandshakesPerIP(max int) *ServerConfig

WithMaxConcurrentHandshakesPerIP sets the per-IP pre-auth concurrent handshake cap.

func (*ServerConfig) WithMaxDetachedSessions

func (c *ServerConfig) WithMaxDetachedSessions(max int) *ServerConfig

WithMaxDetachedSessions sets the maximum detached sessions and returns the config for chaining.

func (*ServerConfig) WithMaxSessions

func (c *ServerConfig) WithMaxSessions(max int) *ServerConfig

WithMaxSessions sets the maximum sessions and returns the config for chaining.

func (*ServerConfig) WithMaxSessionsPerIP

func (c *ServerConfig) WithMaxSessionsPerIP(max int) *ServerConfig

WithMaxSessionsPerIP sets the per-IP session limit and returns the config for chaining.

func (*ServerConfig) WithPersistInterval

func (c *ServerConfig) WithPersistInterval(d time.Duration) *ServerConfig

WithPersistInterval sets the persist interval and returns the config for chaining.

func (*ServerConfig) WithPreUpgradeCheck

func (c *ServerConfig) WithPreUpgradeCheck(check func(r *http.Request) error) *ServerConfig

WithPreUpgradeCheck sets the pre-upgrade check callback.

func (*ServerConfig) WithReconnectConfig

func (c *ServerConfig) WithReconnectConfig(rc *ReconnectConfig) *ServerConfig

WithReconnectConfig sets the reconnect configuration and returns the config for chaining.

func (*ServerConfig) WithResumeWindow

func (c *ServerConfig) WithResumeWindow(d time.Duration) *ServerConfig

WithResumeWindow sets the resume window duration and returns the config for chaining.

func (*ServerConfig) WithSameSiteMode

func (c *ServerConfig) WithSameSiteMode(mode http.SameSite) *ServerConfig

WithSameSiteMode sets the SameSite attribute for cookies.

func (*ServerConfig) WithSecureCookies

func (c *ServerConfig) WithSecureCookies(secure bool) *ServerConfig

WithSecureCookies sets whether cookies should have the Secure flag.

func (*ServerConfig) WithSessionConfig

func (c *ServerConfig) WithSessionConfig(sc *SessionConfig) *ServerConfig

WithSessionConfig sets the session configuration and returns the config for chaining.

func (*ServerConfig) WithSessionStore

func (c *ServerConfig) WithSessionStore(store session.SessionStore) *ServerConfig

WithSessionStore sets the session persistence backend and returns the config for chaining.

type ServerMetrics

type ServerMetrics struct {
	// Sessions
	ActiveSessions int64
	TotalSessions  int64
	SessionCreates int64
	SessionCloses  int64
	PeakSessions   int64

	// Events
	EventsReceived  int64
	EventsProcessed int64
	EventsDropped   int64

	// Patches
	PatchesSent int64
	PatchBytes  int64

	// Network
	BytesSent     int64
	BytesReceived int64

	// Errors
	HandlerPanics int64
	WriteErrors   int64
	ReadErrors    int64
	// Signal write enforcement
	SignalWriteViolationTotal int64

	// Latency (microseconds)
	EventLatencyP50 int64
	EventLatencyP99 int64

	// Memory
	TotalMemory int64

	// Persistence
	PersistWriteRejectedTotal int64
	PersistBytesTotal         int64
	SchemaMismatchTotal       int64

	// Timestamp
	CollectedAt time.Time
}

ServerMetrics aggregates metrics across the server.

type Session

type Session struct {
	// Identity
	ID         string
	UserID     string
	CreatedAt  time.Time
	LastActive time.Time // Guarded by metaMu after initialization.

	// Phase 12: Session persistence fields
	IP           string // Client IP address for per-IP limits
	CurrentRoute string // Current page route for restoration. Guarded by metaMu after initialization.

	DetachedAt time.Time // Time when the session detached (per-IP eviction ordering). Guarded by metaMu after initialization.
	// contains filtered or unexported fields
}

Session represents a single WebSocket connection and its state. Each session has its own component tree, reactive ownership, and handler registry.

func NewMockSession

func NewMockSession() *Session

NewMockSession creates a session without a WebSocket connection for testing. The session has all fields initialized except conn.

func (*Session) BroadcastAuth

func (s *Session) BroadcastAuth(channel, typ string, reason AuthExpiredReason)

BroadcastAuth sends an auth broadcast control message to the client. This should be called on the session loop.

func (*Session) BudgetTracker

func (s *Session) BudgetTracker() *vango.BudgetTracker

BudgetTracker returns the state budget tracker for this session.

func (*Session) BytesReceived

func (s *Session) BytesReceived(n int)

BytesReceived adds to the bytes received counter.

func (*Session) Close

func (s *Session) Close()

Close gracefully closes the session.

func (*Session) Config

func (s *Session) Config() *SessionConfig

Config returns the session configuration.

func (*Session) Conn

func (s *Session) Conn() *websocket.Conn

Conn returns the underlying WebSocket connection. Use with caution - prefer session methods when possible.

func (*Session) Delete

func (s *Session) Delete(key string)

Delete removes a key from session data.

func (*Session) Deserialize

func (s *Session) Deserialize(data []byte) error

Deserialize restores session state from bytes. Phase 7: Validates schema compatibility before restoring.

func (*Session) DeserializeForSessionID

func (s *Session) DeserializeForSessionID(expectedSessionID string, data []byte) error

DeserializeForSessionID restores session state and enforces that the persisted session payload identity matches the requested persistence key.

func (*Session) Dispatch

func (s *Session) Dispatch(fn func())

Dispatch queues a function to run on the session's event loop. This is safe to call from any goroutine and is the correct way to update signals from asynchronous operations (database calls, timers, etc.).

The function will be executed synchronously on the event loop, ensuring signal writes are properly serialized. After the function completes, pending effects will run and dirty components will re-render.

Example:

go func() {
    user, err := db.Users.FindByID(ctx.StdContext(), id)
    ctx.Dispatch(func() {
        if err != nil {
            errorSignal.Set(err)
        } else {
            userSignal.Set(user)
        }
    })
}()

func (*Session) Done

func (s *Session) Done() <-chan struct{}

Done returns a channel that's closed when the session is done.

func (*Session) EventLoop

func (s *Session) EventLoop()

EventLoop processes queued events, dispatch callbacks, and render signals. It runs handlers, schedules effects, and triggers re-renders.

func (*Session) Events

func (s *Session) Events() <-chan *Event

Events returns a best-effort stream of queued events (tests/diagnostics).

func (*Session) Get

func (s *Session) Get(key string) any

Get retrieves a value from session data. Returns nil if key doesn't exist. This is thread-safe and can be called from any goroutine.

func (*Session) GetAllData

func (s *Session) GetAllData() map[string]any

GetAllData returns a copy of all session data. Persistence helpers enforce which keys are durable. Returns nil if no data has been set.

func (*Session) GetInt

func (s *Session) GetInt(key string) int

GetInt is a convenience method that returns value as int. Returns 0 if key doesn't exist or value is not numeric. Handles int, int64, and float64 conversions.

func (*Session) GetString

func (s *Session) GetString(key string) string

GetString is a convenience method that returns value as string. Returns empty string if key doesn't exist or value is not a string.

func (*Session) HandleNavigate

func (s *Session) HandleNavigate(path string, replace bool) error

HandleNavigate processes a navigation request and returns patches. This is called from handleEvent when an EventNavigate is received, or from flush() when ctx.Navigate() was called during event handling.

Per Section 4.4 (Programmatic Navigation), navigation is ONE transaction: NAV_* patch + DOM patches are sent together in a single frame.

Parameters:

  • path: The target path (may include query string)
  • replace: If true, use NAV_REPLACE instead of NAV_PUSH

Returns:

  • error if navigation failed (no route matched and no NotFound handler)

func (*Session) Has

func (s *Session) Has(key string) bool

Has returns whether a key exists in session data.

func (*Session) IsClosed

func (s *Session) IsClosed() bool

IsClosed returns whether the session is closed.

func (*Session) IsDetached

func (s *Session) IsDetached() bool

IsDetached reports whether the session currently has no active WebSocket connection but is still kept in memory for ResumeWindow.

func (*Session) Logger

func (s *Session) Logger() *slog.Logger

Logger returns the session logger.

func (*Session) MemoryUsage

func (s *Session) MemoryUsage() int64

MemoryUsage estimates the memory used by this session.

func (*Session) MountRoot

func (s *Session) MountRoot(component Component)

MountRoot mounts the root component for this session.

func (*Session) Navigator

func (s *Session) Navigator() *RouteNavigator

Navigator returns the route navigator for this session. Returns nil if no router has been set.

func (*Session) NeedsRestart

func (s *Session) NeedsRestart() bool

NeedsRestart returns true if session goroutines need to be restarted. This should be checked after Resume() to decide whether to call Start(). If the session's done channel was closed, goroutines have exited and need to be restarted for the session to function.

func (*Session) Owner

func (s *Session) Owner() *vango.Owner

Owner returns the reactive owner for this session.

func (*Session) PersistErrorHandler

func (s *Session) PersistErrorHandler() func(*vango.PersistBudgetError)

PersistErrorHandler returns the current persist error handler.

func (*Session) PrefetchCache

func (s *Session) PrefetchCache() *PrefetchCache

PrefetchCache returns the prefetch cache for this session. Returns nil if prefetch is not initialized.

func (*Session) PrimitiveRegistry

func (s *Session) PrimitiveRegistry() *SessionPrimitiveRegistry

PrimitiveRegistry returns the session's primitive registry.

func (*Session) QueueEvent

func (s *Session) QueueEvent(event *Event) error

QueueEvent queues an event for processing.

func (*Session) ReadLoop

func (s *Session) ReadLoop()

ReadLoop continuously reads messages from the WebSocket connection. It decodes frames, processes control messages, and queues events. This method blocks until the connection is closed or an error occurs.

func (*Session) RebuildHandlers

func (s *Session) RebuildHandlers() error

RebuildHandlers clears and rebuilds the handler map with fresh HIDs. This is called on session resume to match SSR-rendered DOM.

Unlike a full remount, this preserves:

  • Owner (signals stay alive with their values)
  • Component instances (state preserved)

It regenerates:

  • HID assignments (reset to h1, h2... matching SSR)
  • Handler map (rebuilt from fresh render)
  • Component ownership map

func (*Session) RegisterIslandHandler

func (s *Session) RegisterIslandHandler(id string, handler vango.IslandMessageHandler) func()

RegisterIslandHandler registers a handler for island messages within this session. Returns a cleanup function that unregisters the handler.

func (*Session) RegisterPrimitives

func (s *Session) RegisterPrimitives(regs []vango.PrimitiveRegistration, component *ComponentInstance)

RegisterPrimitives adds primitives from a component's setup.

func (*Session) RegisterWasmHandler

func (s *Session) RegisterWasmHandler(id string, handler vango.WasmMessageHandler) func()

RegisterWasmHandler registers a handler for WASM messages within this session. Returns a cleanup function that unregisters the handler.

func (*Session) RestoreData

func (s *Session) RestoreData(values map[string]any)

RestoreData restores session data from serialized values. This is used during session restoration after server restart or reconnection. Values are merged into existing data (doesn't clear existing keys).

func (*Session) Resume

func (s *Session) Resume(conn *websocket.Conn, lastSeq uint64)

Resume resumes a session after reconnect. It swaps the WebSocket connection, resets sequence numbers, and reinitializes channels if the session was previously closed. Call NeedsRestart() after Resume() to check if Start() should be called to restart goroutines.

func (*Session) RouteID

func (s *Session) RouteID() string

RouteID returns the stable route identifier for this session.

func (*Session) SendClose

func (s *Session) SendClose(reason protocol.CloseReason, message string)

SendClose sends a close control message to the client.

func (*Session) SendHookRevert

func (s *Session) SendHookRevert(hid string)

SendHookRevert requests the client to revert a hook's optimistic UI change. The client will invoke the revert callback registered for the given HID.

func (*Session) SendIslandMessage

func (s *Session) SendIslandMessage(id string, payload any) error

SendIslandMessage sends a message to the client-side island.

func (*Session) SendPatches

func (s *Session) SendPatches(patches []protocol.Patch)

SendPatches is a public wrapper for sendPatches.

func (*Session) SendResyncFull

func (s *Session) SendResyncFull() error

SendResyncFull sends the full HTML tree to the client. This is used as a fallback when HIDs may not align between SSR and remount. The client will replace its entire body content with this HTML.

func (*Session) SendSessionRefresh

func (s *Session) SendSessionRefresh(cmd *protocol.SessionRefreshCommand)

SendSessionRefresh sends a session refresh control message to the client.

func (*Session) SendWasmMessage

func (s *Session) SendWasmMessage(id string, payload any) error

SendWasmMessage sends a message to the client-side WASM component.

func (*Session) Serialize

func (s *Session) Serialize() ([]byte, error)

Serialize converts the session state to bytes for persistence. Phase 7: Uses schema-gated, HMAC-signed blobs with stable IDs.

func (*Session) SessionID

func (s *Session) SessionID() string

SessionID returns the session ID.

func (*Session) Set

func (s *Session) Set(key string, value any)

Set stores a value in session data. Value must be safe to access concurrently (immutable or properly synchronized).

Session.Set stores runtime-only KV by default. Durable session state should use vango.SessionKey[T], setup.SharedSignal, or setup.GlobalSignal.

In debug mode (SessionConfig.DebugMode = true), this panics on obviously unserializable types like func and chan.

func (*Session) SetAssetResolver

func (s *Session) SetAssetResolver(r assets.Resolver)

SetAssetResolver sets the asset resolver for this session. This is called by the server when a session is created or resumed.

func (*Session) SetInitialURL

func (s *Session) SetInitialURL(path string, params map[string]string)

SetInitialURL sets the initial URL parameters from the client handshake. This is called when processing the initial handshake message from the client. URLParam hydration reads this state snapshot during setup.

func (*Session) SetInt

func (s *Session) SetInt(key string, value int)

SetInt stores an int value (always serializable).

func (*Session) SetJSON

func (s *Session) SetJSON(key string, value any)

SetJSON stores runtime-only KV with JSON-oriented intent. This is equivalent to Set(); it does not make the value durable.

func (*Session) SetPersistErrorHandler

func (s *Session) SetPersistErrorHandler(fn func(*vango.PersistBudgetError))

SetPersistErrorHandler registers a persist error handler for this session.

func (*Session) SetRouter

func (s *Session) SetRouter(r Router)

SetRouter sets the router for this session and creates a navigator. This enables route-based navigation via ctx.Navigate() and EventNavigate. The router must implement the Router interface (defined in navigation.go).

func (*Session) SetString

func (s *Session) SetString(key string, value string)

SetString stores a string value (always serializable).

func (*Session) ShowPersistErrorBanner

func (s *Session) ShowPersistErrorBanner(err *vango.PersistBudgetError)

ShowPersistErrorBanner triggers the default persist banner for this session.

func (*Session) Start

func (s *Session) Start()

Start starts all session loops. This should be called after the handshake is complete.

func (*Session) Stats

func (s *Session) Stats() SessionStats

Stats returns session statistics.

func (*Session) StormBudget

func (s *Session) StormBudget() vango.StormBudgetChecker

StormBudget returns the storm budget checker for this session. Returns nil if storm budgets are not configured.

func (*Session) TriggerSchemaRefresh

func (s *Session) TriggerSchemaRefresh(reason protocol.SchemaRefreshReason, debug string)

TriggerSchemaRefresh invalidates the session and forces a client reload. This is used when the running binary detects persisted-state corruption or incompatibility after the session is already mounted (e.g., SharedSignal restore failure).

It is safe to call multiple times; only the first invocation sends the control message.

func (*Session) UnregisterPrimitives

func (s *Session) UnregisterPrimitives(regs []vango.PrimitiveRegistration, component *ComponentInstance)

UnregisterPrimitives removes primitives for a component instance.

func (*Session) UpdateLastActive

func (s *Session) UpdateLastActive()

UpdateLastActive updates the last activity timestamp.

func (*Session) WriteLoop

func (s *Session) WriteLoop()

WriteLoop handles periodic tasks like heartbeats. It runs until the session is closed.

type SessionConfig

type SessionConfig struct {

	// ReadTimeout is the maximum time to wait for a message from the client.
	// Default: 60 seconds.
	ReadTimeout time.Duration

	// WriteTimeout is the maximum time to wait when sending a message.
	// Default: 10 seconds.
	WriteTimeout time.Duration

	// IdleTimeout is the time after which an inactive session is closed.
	// Default: 5 minutes.
	IdleTimeout time.Duration

	// HandshakeTimeout is the maximum time for the initial handshake.
	// Default: 5 seconds.
	HandshakeTimeout time.Duration

	// HeartbeatInterval is the time between heartbeat pings.
	// Default: 30 seconds.
	HeartbeatInterval time.Duration

	// MaxMessageSize is the maximum size of an incoming WebSocket message.
	// Default: 64KB.
	MaxMessageSize int64

	// MaxHandshakePathBytes limits the size of the `?path=` query parameter
	// used during the WebSocket handshake (decoded bytes, includes query string).
	// 0 means no limit (not recommended).
	// Default: 8KB.
	MaxHandshakePathBytes int

	// MaxPatchHistory is the number of recent patches to keep for resync.
	// Default: 100.
	MaxPatchHistory int

	// MaxEventQueue is the maximum number of queued client events per session.
	// When full, new events are dropped and QueueEvent returns ErrEventQueueFull.
	// Default: 256.
	MaxEventQueue int

	// MaxDispatchQueue is the maximum number of queued dispatch callbacks per session.
	//
	// Dispatch callbacks are framework-critical (they apply Resource/Action results and
	// other off-loop work back onto the session loop). They MUST NOT be silently dropped.
	// If the dispatch queue is full and a callback cannot be enqueued, the session will
	// self-heal (fatal error + reload).
	//
	// If zero, it defaults to MaxEventQueue for backwards compatibility.
	// Default: 2048.
	MaxDispatchQueue int

	// MaxDetachedSessions limits the number of detached sessions retained in memory.
	// 0 means unlimited, which is not recommended for production.
	// Default: 10000.
	MaxDetachedSessions int

	// EvictionPolicy controls which detached session is evicted when MaxDetachedSessions is reached.
	// Default: EvictionLRU.
	EvictionPolicy SessionEvictionPolicy

	// EnableCompression enables WebSocket compression.
	// Default: true.
	EnableCompression bool

	// EnableOptimistic enables optimistic updates on the client.
	// Default: true.
	EnableOptimistic bool

	// DebugMode enables extra validation and logging for development.
	// When true:
	// - Session.Set() panics on unserializable types (func, chan)
	// - auth.Get() logs warnings on type mismatches
	// Default: false.
	DebugMode bool

	// URLPolicy controls scheme allowlists for URL-bearing attributes.
	// If zero-valued, defaults are used.
	URLPolicy vdom.URLPolicy

	// StormBudget configures rate limits for effect primitives to prevent
	// amplification bugs (e.g., effect triggers resource refetch triggers effect).
	// See SPEC_ADDENDUM.md §A.4.
	StormBudget *StormBudgetConfig

	// AuthCheck configures authentication freshness checks for the session.
	// When nil, no passive or active auth checks are performed.
	AuthCheck *AuthCheckConfig

	// StateBudget configures persisted state size limits.
	StateBudget *corevango.StateBudgetConfig

	// Observability controls optional logging for mutations.
	Observability corevango.ObservabilityConfig

	// Metrics is the sink for runtime metric emissions.
	// Nil disables metric emission (safe default).
	Metrics corevango.MetricsSink

	// EventMiddleware runs for every WebSocket event before handler execution.
	// Use this for tracing, logging, or rate limiting (e.g., middleware.OpenTelemetry()).
	EventMiddleware []RouteMiddleware

	// PersistenceSecret signs persisted blobs.
	// This includes session persistence and global-signal blobs when
	// global persistence/pubsub paths are enabled.
	// Must be 32+ bytes of cryptographically random data.
	PersistenceSecret []byte

	// PersistenceSecretPrevious allows secret rotation.
	// If set, blobs signed with this secret are accepted on resume.
	PersistenceSecretPrevious []byte

	// MaxBlobAgeMs sets the maximum age in milliseconds for persisted session resume blobs.
	// Blobs older than this are rejected during resume, triggering a fresh session.
	// This applies to session resume only (not global signals).
	// This helps prevent replay attacks with stale session data.
	// 0 means no limit (default).
	MaxBlobAgeMs int64

	// HookEventValidator validates client hook event payloads.
	// Return an error to reject the event.
	// In production, startup warnings are emitted when this is unset.
	HookEventValidator corevango.HookEventValidator

	// DOMEventRateLimit applies per-session rate limiting to standard DOM events
	// (click/input/scroll/etc., including navigation).
	// RatePerSecond <= 0 disables rate limiting.
	DOMEventRateLimit *corevango.EventRateLimitConfig

	// HookEventRateLimit applies per-session rate limiting to hook events.
	// RatePerSecond <= 0 disables rate limiting.
	HookEventRateLimit *corevango.EventRateLimitConfig

	// HookEventMaxPayloadBytes limits JSON size of hook event payloads.
	// 0 means no limit.
	HookEventMaxPayloadBytes int

	// HookEventMaxNameBytes limits hook event name length in bytes.
	// 0 means no limit.
	HookEventMaxNameBytes int

	// IslandMessageValidator validates island message payloads.
	// Return an error to reject the message.
	// In production, startup warnings are emitted when this is unset.
	IslandMessageValidator corevango.IslandMessageValidator

	// IslandEventRateLimit applies per-session rate limiting to island messages.
	// RatePerSecond <= 0 disables rate limiting.
	IslandEventRateLimit *corevango.EventRateLimitConfig

	// IslandEventMaxPayloadBytes limits JSON size of island message payloads.
	// 0 means no limit.
	IslandEventMaxPayloadBytes int

	// IslandEventMaxWrapperBytes limits JSON size of island wrapper payloads.
	// Wrapper payloads are the custom event JSON object containing `id` + `payload`.
	// 0 means no limit.
	IslandEventMaxWrapperBytes int

	// IslandEventMaxJSONDepth limits maximum JSON nesting depth for island
	// wrapper and payload JSON structures.
	// 0 means no limit.
	IslandEventMaxJSONDepth int

	// IslandEventMaxIDBytes limits island identifier length in bytes.
	// 0 means no limit.
	IslandEventMaxIDBytes int

	// WasmMessageValidator validates WASM message payloads.
	// Return an error to reject the message.
	// In production, startup warnings are emitted when this is unset.
	WasmMessageValidator corevango.WasmMessageValidator

	// WasmEventRateLimit applies per-session rate limiting to WASM messages.
	// RatePerSecond <= 0 disables rate limiting.
	WasmEventRateLimit *corevango.EventRateLimitConfig

	// WasmEventMaxPayloadBytes limits JSON size of WASM message payloads.
	// 0 means no limit.
	WasmEventMaxPayloadBytes int

	// WasmEventMaxWrapperBytes limits JSON size of WASM wrapper payloads.
	// Wrapper payloads are the custom event JSON object containing `id` + `payload`.
	// 0 means no limit.
	WasmEventMaxWrapperBytes int

	// WasmEventMaxJSONDepth limits maximum JSON nesting depth for WASM
	// wrapper and payload JSON structures.
	// 0 means no limit.
	WasmEventMaxJSONDepth int

	// WasmEventMaxIDBytes limits WASM identifier length in bytes.
	// 0 means no limit.
	WasmEventMaxIDBytes int

	// AllowRawHTMLInEventPayloads permits raw HTML or executable strings in
	// hook/island/WASM payloads. Default: false.
	// NOTE: Vango's unsafe-payload check is a heuristic substring filter,
	// not a sanitizer. Always validate and/or sanitize untrusted payloads.
	AllowRawHTMLInEventPayloads bool

	// ResyncRateLimitConfig applies per-session rate limiting to resync requests.
	// Resync requests can trigger expensive full HTML renders (SendResyncFull) when
	// the patch history is exhausted, making them a DoS amplification vector.
	//
	// A malicious client can repeatedly send resync requests to:
	//   - Consume CPU (rendering full component tree to HTML)
	//   - Consume bandwidth (sending large HTML payloads)
	//   - Amplify per-session cost across many concurrent sessions
	//
	// Default: 2/sec with burst 5 (conservative, resync should be rare).
	// RatePerSecond <= 0 disables rate limiting (NOT RECOMMENDED in production).
	ResyncRateLimitConfig *corevango.EventRateLimitConfig
}

SessionConfig holds configuration for individual sessions.

func DefaultSessionConfig

func DefaultSessionConfig() *SessionConfig

DefaultSessionConfig returns a SessionConfig with sensible defaults.

func (*SessionConfig) Clone

func (c *SessionConfig) Clone() *SessionConfig

Clone returns a copy of the SessionConfig.

type SessionError

type SessionError struct {
	SessionID string
	Op        string // Operation that failed
	Err       error  // Underlying error
}

SessionError wraps an error with session context for debugging.

func NewSessionError

func NewSessionError(sessionID, op string, err error) *SessionError

NewSessionError creates a new SessionError.

func (*SessionError) Error

func (e *SessionError) Error() string

Error returns the error message with session context.

func (*SessionError) Unwrap

func (e *SessionError) Unwrap() error

Unwrap returns the underlying error for errors.Is/As.

type SessionEvictionPolicy

type SessionEvictionPolicy int

SessionEvictionPolicy determines which detached sessions are evicted first.

const (
	// EvictionLRU evicts the least recently accessed detached sessions first.
	EvictionLRU SessionEvictionPolicy = iota
	// EvictionOldest evicts the oldest sessions first (by creation time).
	EvictionOldest
	// EvictionRandom evicts sessions randomly (faster but less fair).
	EvictionRandom
)

type SessionLimits

type SessionLimits struct {
	// MaxSessions is the maximum number of concurrent sessions.
	MaxSessions int

	// MaxMemoryPerSession is the approximate memory limit per session.
	MaxMemoryPerSession int64

	// MaxTotalMemory is the total memory budget for all sessions.
	// If exceeded, least recently used sessions are evicted.
	MaxTotalMemory int64
}

SessionLimits defines limits for session management.

func DefaultSessionLimits

func DefaultSessionLimits() *SessionLimits

DefaultSessionLimits returns default session limits.

type SessionManager

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

SessionManager manages all active sessions. It handles session creation, lookup, cleanup, and lifecycle callbacks.

func NewSessionManager

func NewSessionManager(config *SessionConfig, limits *SessionLimits, logger *slog.Logger) *SessionManager

NewSessionManager creates a new SessionManager with the given configuration.

func NewSessionManagerWithOptions

func NewSessionManagerWithOptions(config *SessionConfig, limits *SessionLimits, logger *slog.Logger, opts *SessionManagerOptions) *SessionManager

NewSessionManagerWithOptions creates a SessionManager with Phase 12 persistence options.

func (*SessionManager) CheckIPLimit

func (sm *SessionManager) CheckIPLimit(ip string) error

CheckIPLimit checks if the IP has exceeded the session limit. Returns ErrTooManySessionsFromIP if the limit is exceeded. This should be called before creating a new session.

func (*SessionManager) CheckMemoryPressure

func (sm *SessionManager) CheckMemoryPressure()

CheckMemoryPressure checks if memory usage exceeds limits and evicts if needed.

func (*SessionManager) Close

func (sm *SessionManager) Close(id string)

Close closes a session by ID and removes it from the manager.

func (*SessionManager) Count

func (sm *SessionManager) Count() int

Count returns the number of active sessions.

func (*SessionManager) Create

func (sm *SessionManager) Create(conn *websocket.Conn, userID, ip string) (*Session, error)

Create creates a new session for the given WebSocket connection.

func (*SessionManager) DispatchAll

func (sm *SessionManager) DispatchAll(fn func(*Session))

DispatchAll dispatches a function to every active session.

func (*SessionManager) EvictLRU

func (sm *SessionManager) EvictLRU(count int) int

EvictLRU evicts the least recently used sessions. This is called when memory pressure is high.

func (*SessionManager) ForEach

func (sm *SessionManager) ForEach(fn func(*Session) bool)

ForEach iterates over all sessions. The callback should not perform long-running operations as it holds the read lock.

func (*SessionManager) Get

func (sm *SessionManager) Get(id string) *Session

Get retrieves a session by ID.

func (*SessionManager) HasPersistence

func (sm *SessionManager) HasPersistence() bool

HasPersistence returns true if session persistence is configured.

func (*SessionManager) MarkResumed

func (sm *SessionManager) MarkResumed(sessionID string)

MarkResumed removes a session from detached tracking after a successful resume.

func (*SessionManager) OnSessionDisconnect

func (sm *SessionManager) OnSessionDisconnect(sess *Session)

OnSessionDisconnect is called when a WebSocket connection closes. It persists the session state for potential reconnection.

func (*SessionManager) OnSessionReconnect

func (sm *SessionManager) OnSessionReconnect(sessionID string) (*Session, error)

OnSessionReconnect attempts to restore a session after reconnection. Returns ErrSessionNotFound if the session cannot be resumed.

func (*SessionManager) PersistenceManager

func (sm *SessionManager) PersistenceManager() *session.Manager

PersistenceManager returns the underlying persistence manager for advanced use. Returns nil if persistence is not configured.

func (*SessionManager) ResumeWindow

func (sm *SessionManager) ResumeWindow() time.Duration

ResumeWindow returns the configured resume window duration. This is how long detached sessions remain resumable.

func (*SessionManager) SetBudgetTracker

func (sm *SessionManager) SetBudgetTracker(tracker *vango.BudgetTracker)

SetBudgetTracker configures the budget tracker for new sessions.

func (*SessionManager) SetCleanupInterval

func (sm *SessionManager) SetCleanupInterval(d time.Duration)

SetCleanupInterval sets the cleanup interval.

func (*SessionManager) SetMetricsSink

func (sm *SessionManager) SetMetricsSink(sink vango.MetricsSink)

SetMetricsSink updates the metrics sink used for session lifecycle metrics. Call this before creating sessions to ensure they inherit the sink.

func (*SessionManager) SetOnSessionClose

func (sm *SessionManager) SetOnSessionClose(fn func(*Session))

SetOnSessionClose sets the callback for session close.

func (*SessionManager) SetOnSessionCreate

func (sm *SessionManager) SetOnSessionCreate(fn func(*Session))

SetOnSessionCreate sets the callback for session creation.

func (*SessionManager) Shutdown

func (sm *SessionManager) Shutdown()

Shutdown gracefully shuts down all sessions.

func (*SessionManager) ShutdownWithContext

func (sm *SessionManager) ShutdownWithContext(ctx context.Context) error

ShutdownWithContext gracefully shuts down all sessions with context for timeout.

func (*SessionManager) Stats

func (sm *SessionManager) Stats() ManagerStats

Stats returns aggregated session statistics.

func (*SessionManager) TotalMemoryUsage

func (sm *SessionManager) TotalMemoryUsage() int64

TotalMemoryUsage returns the total memory usage of all sessions.

func (*SessionManager) UpdateSessionIP

func (sm *SessionManager) UpdateSessionIP(session *Session, newIP string) error

UpdateSessionIP updates the session's IP address and enforces per-IP limits. Returns ErrTooManySessionsFromIP if the new IP bucket is full.

type SessionManagerOptions

type SessionManagerOptions struct {
	// SessionStore is the persistence backend for sessions.
	SessionStore session.SessionStore

	// ResumeWindow is how long detached sessions remain resumable.
	// nil means use the default (5 minutes).
	// 0 disables resumption entirely.
	ResumeWindow *time.Duration

	// MaxDetachedSessions is the maximum detached sessions before LRU eviction.
	MaxDetachedSessions int

	// MaxSessionsPerIP is the maximum sessions per IP address.
	MaxSessionsPerIP int

	// EvictOnIPLimit controls whether to evict the oldest detached session
	// for a full IP bucket instead of rejecting new sessions.
	EvictOnIPLimit bool

	// EvictionPolicy determines how detached sessions are evicted when limits are exceeded.
	EvictionPolicy SessionEvictionPolicy

	// PersistInterval is how often to persist dirty sessions.
	PersistInterval time.Duration

	// GlobalSignalPubSub is the broadcast backend for global signals.
	GlobalSignalPubSub vango.GlobalBroadcastBackend

	// GlobalSignalRefreshInterval controls the refresh cadence when no pubsub is configured.
	GlobalSignalRefreshInterval time.Duration

	// RedirectAllowlist is the normalized allowlist for external redirects.
	// When nil or empty, external redirects are rejected.
	RedirectAllowlist map[string]struct{}
}

SessionManagerOptions contains optional Phase 12 configuration.

type SessionPrimitiveRegistry

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

SessionPrimitiveRegistry tracks all primitives in a session for inspection.

func NewSessionPrimitiveRegistry

func NewSessionPrimitiveRegistry() *SessionPrimitiveRegistry

NewSessionPrimitiveRegistry creates a new registry.

func (*SessionPrimitiveRegistry) All

All returns all registered primitives.

func (*SessionPrimitiveRegistry) AllPersisted

func (r *SessionPrimitiveRegistry) AllPersisted() []*PrimitiveInfo

AllPersisted returns only persisted primitives.

func (*SessionPrimitiveRegistry) FindByDebugName

func (r *SessionPrimitiveRegistry) FindByDebugName(debugName string) []*PrimitiveInfo

FindByDebugName returns all primitives with the given debug name.

func (*SessionPrimitiveRegistry) FindByNameOrID

func (r *SessionPrimitiveRegistry) FindByNameOrID(nameOrID string) (*PrimitiveInfo, bool)

FindByNameOrID resolves a primitive by debugName, stableID, or anchorKey.

func (*SessionPrimitiveRegistry) FindByStableID

func (r *SessionPrimitiveRegistry) FindByStableID(stableID string) []*PrimitiveInfo

FindByStableID returns all primitives with the given stable ID.

func (*SessionPrimitiveRegistry) Register

func (r *SessionPrimitiveRegistry) Register(reg vango.PrimitiveRegistration, instancePath string)

Register adds a primitive to the registry.

func (*SessionPrimitiveRegistry) SignalValue

func (r *SessionPrimitiveRegistry) SignalValue(nameOrID string) (any, bool)

SignalValue returns the current value of a signal by nameOrID.

func (*SessionPrimitiveRegistry) Unregister

func (r *SessionPrimitiveRegistry) Unregister(instancePath string)

Unregister removes a primitive instance by instance path.

type SessionStats

type SessionStats struct {
	ID             string
	UserID         string
	CreatedAt      time.Time
	LastActive     time.Time
	EventCount     uint64
	PatchCount     uint64
	BytesSent      uint64
	BytesRecv      uint64
	HandlerCount   int
	ComponentCount int
}

SessionStats contains session statistics.

type StormBudgetConfig

type StormBudgetConfig struct {
	// MaxResourceStartsPerSecond limits how many Resource fetches can start per second.
	// 0 means no limit.
	// Default: 50.
	MaxResourceStartsPerSecond int

	// MaxActionStartsPerSecond limits how many Action runs can start per second.
	// 0 means no limit.
	// Default: 100.
	MaxActionStartsPerSecond int

	// MaxGoLatestStartsPerSecond limits how many GoLatest work items can start per second.
	// 0 means no limit.
	// Default: 50.
	MaxGoLatestStartsPerSecond int

	// MaxEffectRunsPerTick limits effect runs within a single event/dispatch tick.
	// Helps catch infinite loops where effects trigger effects.
	// 0 means no limit.
	// Default: 1000.
	MaxEffectRunsPerTick int

	// WindowDuration is the sliding window for per-second limits.
	// Default: 1 second.
	WindowDuration time.Duration

	// OnExceeded determines what happens when a budget is exceeded.
	// Default: BudgetThrottle (drop excess operations).
	OnExceeded BudgetExceededMode
}

StormBudgetConfig configures rate limits for effect primitives. These limits help prevent amplification bugs where effects cascade into more effects, potentially causing performance issues or infinite loops.

func DefaultStormBudgetConfig

func DefaultStormBudgetConfig() *StormBudgetConfig

DefaultStormBudgetConfig returns a StormBudgetConfig with sensible defaults. These defaults are conservative but should handle most applications.

type TouchEvent

type TouchEvent struct {
	Touches []TouchPoint
}

TouchEvent represents a touch event with touch points.

type TouchPoint

type TouchPoint struct {
	ID      int
	ClientX int
	ClientY int
}

TouchPoint represents a single touch point.

Jump to

Keyboard shortcuts

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