TestNewMuxDebugEndpoints function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	t.Setenv("RFW_DEVTOOLS", "1")
	mux := NewMux(t.TempDir())
	ts := httptest.NewServer(mux)
	defer ts.Close()

	resp, err := http.Get(ts.URL + "/debug/vars")
	if err != nil {
		t.Fatalf("vars request failed: %v", err)
	}
	resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}

	resp, err = http.Get(ts.URL + "/debug/pprof/")
	if err != nil {
		t.Fatalf("pprof request failed: %v", err)
	}
	resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		t.Fatalf("expected 200, got %d", resp.StatusCode)
	}
}

TestNewMuxServesBrotliWasmWithEncoding function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	t.Setenv("RFW_DEVTOOLS", "")
	root := t.TempDir()
	clientDir := filepath.Join(root, "client")
	if err := os.MkdirAll(clientDir, 0o755); err != nil {
		t.Fatalf("failed to create client dir: %v", err)
	}
	wasmPath := filepath.Join(clientDir, "app.wasm.br")
	if err := os.WriteFile(wasmPath, []byte("compressed"), 0o644); err != nil {
		t.Fatalf("failed to write wasm: %v", err)
	}
	if err := os.WriteFile(filepath.Join(clientDir, "index.html"), []byte("<html></html>"), 0o644); err != nil {
		t.Fatalf("failed to write index: %v", err)
	}

	mux := NewMux(clientDir)
	srv := httptest.NewServer(mux)
	defer srv.Close()

	resp, err := http.Get(srv.URL + "/app.wasm.br")
	if err != nil {
		t.Fatalf("failed to get wasm: %v", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		t.Fatalf("unexpected status: %d", resp.StatusCode)
	}
	if enc := resp.Header.Get("Content-Encoding"); enc != "br" {
		t.Fatalf("expected Content-Encoding br, got %q", enc)
	}
	if ct := resp.Header.Get("Content-Type"); ct != "application/wasm" {
		t.Fatalf("expected Content-Type application/wasm, got %q", ct)
	}
	if vary := resp.Header.Get("Vary"); vary != "Accept-Encoding" && vary != "Accept-Encoding, Accept-Encoding" {
		// Allow duplicated value as Go's header may append values depending on environment.
		t.Fatalf("unexpected Vary header: %q", vary)
	}
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		t.Fatalf("failed to read body: %v", err)
	}
	if string(body) != "compressed" {
		t.Fatalf("unexpected body: %q", string(body))
	}
}

readPort function

Returns:

  • int
Show/Hide Function Body
{
	if override := strings.TrimSpace(os.Getenv("RFW_HOST_PORT")); override != "" {
		if p, err := strconv.Atoi(override); err == nil && p > 0 {
			return p
		}
	}
	var manifest struct {
		Port int `json:"port"`
	}
	data, err := os.ReadFile("rfw.json")
	if err != nil {
		return 8080
	}
	if err := json.Unmarshal(data, &manifest); err != nil {
		return 8080
	}
	if manifest.Port == 0 {
		return 8080
	}
	return manifest.Port
}

Start function

Start launches HTTP and HTTPS servers serving files from root.

The HTTPS port is the HTTP port + 1.

Parameters:

  • root string

Returns:

  • error
Show/Hide Function Body
{
	port := readPort()
	httpsPort := port + 1

	go func() {
		addr := fmt.Sprintf(":%d", port)
		if err := ListenAndServe(addr, root); err != nil {
			logger.Error("HTTP server error", "err", err)
		}
	}()

	httpsAddr := fmt.Sprintf(":%d", httpsPort)
	return ListenAndServeTLS(httpsAddr, root)
}

logLevel function

Returns:

  • slog.Level
Show/Hide Function Body
{
	switch strings.ToLower(os.Getenv("RFW_LOG_LEVEL")) {
	case "debug":
		return slog.LevelDebug
	case "warn":
		return slog.LevelWarn
	case "error":
		return slog.LevelError
	default:
		return slog.LevelInfo
	}
}

TestHostComponent function

TestHostComponent verifies registration and handler execution.

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	called := false
	hc := NewHostComponent("cmp", func(payload map[string]any) any {
		called = true
		if payload["x"] != 1 {
			t.Fatalf("unexpected payload: %v", payload)
		}
		return "ok"
	})
	Register(hc)
	got, ok := Get("cmp")
	if !ok || got != hc {
		t.Fatalf("component not registered")
	}
	if resp := hc.Handle(map[string]any{"x": 1}); resp != "ok" || !called {
		t.Fatalf("handler not executed or wrong response: %v", resp)
	}
}

TestHostComponentWithSession function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	hc := NewHostComponentWithSession("withSession", func(session *Session, payload map[string]any) any {
		if session == nil {
			t.Fatalf("session should not be nil")
		}
		store := session.StoreManager().NewStore("test")
		store.Set("value", payload["v"])
		return store.Snapshot()
	})

	sess := newSession("abc")
	resp := hc.HandleWithSession(sess, map[string]any{"v": 42})
	snap, ok := resp.(map[string]any)
	if !ok {
		t.Fatalf("unexpected response type %T", resp)
	}
	if snap["value"] != 42 {
		t.Fatalf("unexpected store snapshot: %v", snap)
	}
	if !hc.SessionAware() {
		t.Fatalf("expected session aware component")
	}
	if hc.StoreManager(sess) != sess.StoreManager() {
		t.Fatalf("StoreManager helper mismatch")
	}
}

TestLogLevel function

TestLogLevel checks environment variable parsing.

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	t.Setenv("RFW_LOG_LEVEL", "debug")
	if lvl := logLevel(); lvl.String() != "DEBUG" {
		t.Fatalf("expected DEBUG level, got %s", lvl)
	}
	t.Setenv("RFW_LOG_LEVEL", "warn")
	if lvl := logLevel(); lvl.String() != "WARN" {
		t.Fatalf("expected WARN level, got %s", lvl)
	}
	t.Setenv("RFW_LOG_LEVEL", "")
	if lvl := logLevel(); lvl.String() != "INFO" {
		t.Fatalf("expected INFO level, got %s", lvl)
	}
}

TestGenerateSelfSignedCert function

TestGenerateSelfSignedCert ensures a certificate is generated.

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	cert, err := generateSelfSignedCert()
	if err != nil {
		t.Fatalf("generateSelfSignedCert returned error: %v", err)
	}
	if len(cert.Certificate) == 0 {
		t.Fatalf("expected certificate data")
	}
}

statusRecorder struct

Fields:

  • status (int)

Methods:

WriteHeader


Parameters:
  • code int

Show/Hide Method Body
{
	r.status = code
	r.ResponseWriter.WriteHeader(code)
}

Hijack


Returns:
  • net.Conn
  • *bufio.ReadWriter
  • error

Show/Hide Method Body
{
	if h, ok := r.ResponseWriter.(http.Hijacker); ok {
		return h.Hijack()
	}
	return nil, nil, errors.New("http.Hijacker not supported")
}

loggingMiddleware function

Parameters:

  • next http.Handler

Returns:

  • http.Handler
Show/Hide Function Body
{
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		rec := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
		start := time.Now()
		next.ServeHTTP(rec, r)
		logger.Info("request", "method", r.Method, "path", r.URL.Path, "status", rec.status, "duration", time.Since(start))
	})
}

resolveRoot function

Parameters:

  • root string

Returns:

  • string
Show/Hide Function Body
{
	if _, err := os.Stat(root); err == nil {
		return root
	}
	if exe, err := os.Executable(); err == nil {
		candidate := filepath.Join(filepath.Dir(exe), "..", root)
		if _, err := os.Stat(candidate); err == nil {
			return candidate
		}
	}
	return root
}

NewMux function

NewMux returns an HTTP mux that serves static files from root and the

WebSocket handler at /ws.

Parameters:

  • root string

Returns:

  • *http.ServeMux
Show/Hide Function Body
{
	root = resolveRoot(root)
	staticRoot := filepath.Join(root, "..", "static")
	mux := http.NewServeMux()
	fs := http.FileServer(http.Dir(root))
	var sfs http.Handler
	if _, err := os.Stat(staticRoot); err == nil {
		sfs = http.FileServer(http.Dir(staticRoot))
	}
	if sfs != nil {
		mux.Handle("/static/", http.StripPrefix("/static", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			setWasmEncodingHeaders(w, r.URL.Path)
			sfs.ServeHTTP(w, r)
		})))
	}
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if sfs != nil {
			spath := filepath.Join(staticRoot, r.URL.Path)
			if st, err := os.Stat(spath); err == nil && !st.IsDir() {
				setWasmEncodingHeaders(w, spath)
				sfs.ServeHTTP(w, r)
				return
			}
		}
		path := filepath.Join(root, r.URL.Path)
		if st, err := os.Stat(path); err == nil && !st.IsDir() {
			setWasmEncodingHeaders(w, path)
			fs.ServeHTTP(w, r)
			return
		}
		http.ServeFile(w, r, filepath.Join(root, "index.html"))
	})
	mux.Handle("/ws", websocket.Handler(wsHandler))
	if os.Getenv("RFW_DEVTOOLS") != "" {
		mux.Handle("/debug/vars", expvar.Handler())
		mux.HandleFunc("/debug/pprof/", pprof.Index)
		mux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline)
		mux.HandleFunc("/debug/pprof/profile", pprof.Profile)
		mux.HandleFunc("/debug/pprof/symbol", pprof.Symbol)
		mux.HandleFunc("/debug/pprof/trace", pprof.Trace)
	}
	return mux
}

ListenAndServe function

ListenAndServe starts an HTTP server using NewMux to serve files and the

WebSocket endpoint.

Parameters:

  • addr string
  • root string

Returns:

  • error
Show/Hide Function Body
{
	logger.Info("serving HTTP", "addr", addr)
	return http.ListenAndServe(addr, loggingMiddleware(NewMux(root)))
}

ListenAndServeWithMux function

ListenAndServeWithMux starts an HTTP server using the provided mux.

Parameters:

  • addr string
  • mux *http.ServeMux

Returns:

  • error
Show/Hide Function Body
{
	logger.Info("serving HTTP", "addr", addr)
	return http.ListenAndServe(addr, loggingMiddleware(mux))
}

ListenAndServeTLS function

ListenAndServeTLS starts an HTTPS server using a self-signed certificate

and NewMux to serve files and the WebSocket endpoint.

Parameters:

  • addr string
  • root string

Returns:

  • error
Show/Hide Function Body
{
	cert, err := generateSelfSignedCert()
	if err != nil {
		return err
	}
	srv := &http.Server{
		Addr:      addr,
		Handler:   loggingMiddleware(NewMux(root)),
		TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
	}
	logger.Info("serving HTTPS", "addr", addr)
	return srv.ListenAndServeTLS("", "")
}

ListenAndServeTLSWithMux function

ListenAndServeTLSWithMux starts an HTTPS server using a self-signed certificate

and the provided mux, preserving any additional routes registered by callers.

Parameters:

  • addr string
  • mux *http.ServeMux

Returns:

  • error
Show/Hide Function Body
{
	cert, err := generateSelfSignedCert()
	if err != nil {
		return err
	}
	srv := &http.Server{
		Addr:      addr,
		Handler:   loggingMiddleware(mux),
		TLSConfig: &tls.Config{Certificates: []tls.Certificate{cert}},
	}
	logger.Info("serving HTTPS", "addr", addr)
	return srv.ListenAndServeTLS("", "")
}

setWasmEncodingHeaders function

Parameters:

  • w http.ResponseWriter
  • path string
Show/Hide Function Body
{
	if !strings.HasSuffix(path, ".wasm.br") {
		return
	}
	header := w.Header()
	header.Set("Content-Encoding", "br")
	header.Set("Content-Type", "application/wasm")
	if vary := header.Get("Vary"); vary == "" {
		header.Set("Vary", "Accept-Encoding")
	} else if !strings.Contains(vary, "Accept-Encoding") {
		header.Set("Vary", vary+", Accept-Encoding")
	}
}

generateSelfSignedCert function

Returns:

  • tls.Certificate
  • error
Show/Hide Function Body
{
	priv, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return tls.Certificate{}, err
	}
	tmpl := x509.Certificate{
		SerialNumber: big.NewInt(1),
		NotBefore:    time.Now(),
		NotAfter:     time.Now().Add(365 * 24 * time.Hour),
		KeyUsage:     x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
		ExtKeyUsage:  []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		DNSNames:     []string{"localhost"},
	}
	der, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv)
	if err != nil {
		return tls.Certificate{}, err
	}
	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: der})
	keyPEM := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
	return tls.X509KeyPair(certPEM, keyPEM)
}

Session struct

Session represents per-connection state for a WebSocket client.

It exposes an isolated StoreManager and a context bag for arbitrary data.

Fields:

  • id (string)
  • stores (*state.StoreManager)
  • ctxMu (sync.RWMutex)
  • ctx (map[string]any)

Methods:

ID


Returns:
  • string

Show/Hide Method Body
{ return s.id }

StoreManager


Returns:
  • *state.StoreManager

Show/Hide Method Body
{ return s.stores }

ContextGet

ContextGet retrieves a value from the session context.


Parameters:
  • key string

Returns:
  • any
  • bool

Show/Hide Method Body
{
	s.ctxMu.RLock()
	defer s.ctxMu.RUnlock()
	v, ok := s.ctx[key]
	return v, ok
}

ContextSet

ContextSet stores a value in the session context.


Parameters:
  • key string
  • value any

Show/Hide Method Body
{
	s.ctxMu.Lock()
	s.ctx[key] = value
	s.ctxMu.Unlock()
}

ContextDelete

ContextDelete removes a value from the session context.


Parameters:
  • key string

Show/Hide Method Body
{
	s.ctxMu.Lock()
	delete(s.ctx, key)
	s.ctxMu.Unlock()
}

Snapshot

Snapshot returns a copy of all stores registered in this session.


Returns:
  • map[string]map[string]map[string]any

Show/Hide Method Body
{
	return s.stores.Snapshot()
}

newSession function

Parameters:

  • id string

Returns:

  • *Session
Show/Hide Function Body
{
	return &Session{
		id:     id,
		stores: state.NewStoreManager(),
		ctx:    make(map[string]any),
	}
}

allocateSession function

Returns:

  • *Session
Show/Hide Function Body
{
	id := generateSessionID()
	session := newSession(id)
	sessionMu.Lock()
	sessions[id] = session
	sessionMu.Unlock()
	return session
}

releaseSession function

Parameters:

  • session *Session
Show/Hide Function Body
{
	if session == nil {
		return
	}
	sessionMu.Lock()
	delete(sessions, session.id)
	sessionMu.Unlock()
}

SessionByID function

SessionByID retrieves a session for the given ID.

Parameters:

  • id string

Returns:

  • *Session
  • bool
Show/Hide Function Body
{
	sessionMu.RLock()
	defer sessionMu.RUnlock()
	s, ok := sessions[id]
	return s, ok
}

generateSessionID function

Returns:

  • string
Show/Hide Function Body
{
	buf := make([]byte, 16)
	if _, err := rand.Read(buf); err != nil {
		panic(err)
	}
	return hex.EncodeToString(buf)
}

TestSessionIsolation function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	t.Helper()

	registry = make(map[string]*HostComponent)

	const componentName = "SessionHost"
	Register(NewHostComponentWithSession(componentName, func(session *Session, payload map[string]any) any {
		const storeKey = "counter"
		storeVal, ok := session.ContextGet(storeKey)
		var store *state.Store
		if ok {
			store = storeVal.(*state.Store)
		} else {
			store = session.StoreManager().NewStore("counter")
			store.Set("value", 0)
			session.ContextSet(storeKey, store)
		}
		if inc, ok := payload["increment"].(bool); ok && inc {
			current, _ := store.Get("value").(int)
			store.Set("value", current+1)
		}
		return map[string]any{"value": store.Get("value")}
	}))

	root := t.TempDir()
	// Ensure an index exists so NewMux can serve fallback responses without error.
	if err := os.WriteFile(filepath.Join(root, "index.html"), []byte("ok"), 0o644); err != nil {
		t.Fatalf("write index: %v", err)
	}

	srv := httptest.NewServer(loggingMiddleware(NewMux(root)))
	defer srv.Close()

	wsURL := "ws" + strings.TrimPrefix(srv.URL, "http") + "/ws"

	type sessionConn struct {
		ws  *websocket.Conn
		id  string
		idx int
	}

	dial := func(idx int) sessionConn {
		ws, err := websocket.Dial(wsURL, "", srv.URL)
		if err != nil {
			t.Fatalf("dial %d: %v", idx, err)
		}
		init := map[string]any{
			"component": componentName,
			"payload":   map[string]any{"init": true},
		}
		raw, err := json.Marshal(init)
		if err != nil {
			t.Fatalf("marshal init %d: %v", idx, err)
		}
		if err := websocket.Message.Send(ws, raw); err != nil {
			t.Fatalf("send init %d: %v", idx, err)
		}
		var respRaw []byte
		if err := websocket.Message.Receive(ws, &respRaw); err != nil {
			t.Fatalf("recv init %d: %v", idx, err)
		}
		var resp struct {
			Component string         `json:"component"`
			Payload   map[string]any `json:"payload"`
			Session   string         `json:"session"`
		}
		if err := json.Unmarshal(respRaw, &resp); err != nil {
			t.Fatalf("unmarshal init %d: %v", idx, err)
		}
		if resp.Session == "" {
			t.Fatalf("session id missing for conn %d", idx)
		}
		if resp.Component != componentName {
			t.Fatalf("unexpected component %s", resp.Component)
		}
		if val, ok := resp.Payload["value"].(float64); !ok || val != 0 {
			t.Fatalf("unexpected init value for conn %d: %v", idx, resp.Payload)
		}
		return sessionConn{ws: ws, id: resp.Session, idx: idx}
	}

	sessions := []sessionConn{dial(0), dial(1)}
	defer func() {
		for _, sc := range sessions {
			sc.ws.Close()
		}
	}()

	counts := []int{5, 2}
	if len(counts) != len(sessions) {
		t.Fatalf("mismatched counts")
	}

	errCh := make(chan error, len(sessions))
	var wg sync.WaitGroup
	for i, sc := range sessions {
		wg.Add(1)
		count := counts[i]
		go func(sc sessionConn, target int) {
			defer wg.Done()
			for j := 0; j < target; j++ {
				payload := map[string]any{
					"component": componentName,
					"payload":   map[string]any{"increment": true},
				}
				raw, err := json.Marshal(payload)
				if err != nil {
					errCh <- fmt.Errorf("marshal increment idx=%d: %w", sc.idx, err)
					return
				}
				if err := websocket.Message.Send(sc.ws, raw); err != nil {
					errCh <- fmt.Errorf("send increment idx=%d: %w", sc.idx, err)
					return
				}
				var respRaw []byte
				if err := websocket.Message.Receive(sc.ws, &respRaw); err != nil {
					errCh <- fmt.Errorf("recv increment idx=%d: %w", sc.idx, err)
					return
				}
				var resp struct {
					Component string         `json:"component"`
					Payload   map[string]any `json:"payload"`
					Session   string         `json:"session"`
				}
				if err := json.Unmarshal(respRaw, &resp); err != nil {
					errCh <- fmt.Errorf("unmarshal increment idx=%d: %w", sc.idx, err)
					return
				}
				if resp.Session != sc.id {
					errCh <- fmt.Errorf("response session mismatch idx=%d: got %s want %s", sc.idx, resp.Session, sc.id)
					return
				}
			}
			errCh <- nil
		}(sc, count)
	}

	wg.Wait()
	close(errCh)
	for err := range errCh {
		if err != nil {
			t.Fatal(err)
		}
	}

	for i, sc := range sessions {
		sess, ok := SessionByID(sc.id)
		if !ok {
			t.Fatalf("session %d not found", i)
		}
		snap := sess.Snapshot()
		module := snap["default"]
		if module == nil {
			t.Fatalf("session %d missing default module snapshot", i)
		}
		counter := module["counter"]
		if counter == nil {
			t.Fatalf("session %d missing counter store", i)
		}
		val, ok := counter["value"].(int)
		if !ok {
			t.Fatalf("session %d missing value entry: %v", i, counter)
		}
		if val != counts[i] {
			t.Fatalf("session %d got value %d want %d", i, val, counts[i])
		}
	}

	if sessions[0].id == sessions[1].id {
		t.Fatal("session ids should differ")
	}
}

TestReadPortOverride function

Parameters:

  • t *testing.T
Show/Hide Function Body
{
	t.Setenv("RFW_HOST_PORT", "9095")
	if got := readPort(); got != 9095 {
		t.Fatalf("expected override port 9095, got %d", got)
	}
}

inbound struct

Fields:

  • Component (string) - json:"component"
  • Payload (map[string]any) - json:"payload"

outbound struct

Fields:

  • Component (string) - json:"component"
  • Payload (any) - json:"payload,omitempty"
  • Session (string) - json:"session"

broadcastOptions struct

Fields:

  • session (string)

BroadcastOption type

BroadcastOption configures a broadcast call.

Type Definition:

func(*broadcastOptions)

WithSessionTarget function

WithSessionTarget limits a broadcast to a specific session ID.

Parameters:

  • sessionID string

Returns:

  • BroadcastOption

References:

Show/Hide Function Body
{
	return func(opts *broadcastOptions) {
		opts.session = sessionID
	}
}

wsHandler function

Parameters:

  • ws *websocket.Conn
Show/Hide Function Body
{
	session := allocateSession()
	var subscribed []string
	defer func() {
		connMu.Lock()
		for _, name := range subscribed {
			if set, ok := connections[name]; ok {
				delete(set, ws)
				if len(set) == 0 {
					delete(connections, name)
				}
			}
		}
		connMu.Unlock()
		releaseSession(session)
		ws.Close()
	}()
	for {
		var raw []byte
		if err := websocket.Message.Receive(ws, &raw); err != nil {
			if err == io.EOF {
				break
			}
			log.Printf("recv: %v", err)
			return
		}
		var msg inbound
		if err := json.Unmarshal(raw, &msg); err != nil {
			log.Printf("unmarshal: %v", err)
			continue
		}
		if hc, ok := Get(msg.Component); ok {
			connMu.Lock()
			if _, ok := connections[msg.Component]; !ok {
				connections[msg.Component] = make(map[*websocket.Conn]*Session)
			}
			if _, tracked := connections[msg.Component][ws]; !tracked {
				connections[msg.Component][ws] = session
				subscribed = append(subscribed, msg.Component)
			}
			connMu.Unlock()
			resp := hc.HandleWithSession(session, msg.Payload)
			if resp != nil {
				switch v := resp.(type) {
				case *InitSnapshot:
					if v != nil {
						sendToConn(ws, outbound{Component: msg.Component, Payload: map[string]any{"initSnapshot": v}, Session: session.ID()})
					}
					continue
				case InitSnapshot:
					sendToConn(ws, outbound{Component: msg.Component, Payload: map[string]any{"initSnapshot": v}, Session: session.ID()})
					continue
				default:
					sendToConn(ws, outbound{Component: msg.Component, Payload: resp, Session: session.ID()})
					continue
				}
			}
			if msg.Payload != nil && msg.Payload["init"] == true {
				sendToConn(ws, outbound{
					Component: msg.Component,
					Session:   session.ID(),
					Payload:   map[string]any{"session": session.ID()},
				})
			}
		}
	}
}

Broadcast function

Broadcast sends the given payload to all connections subscribed to the component name.

Parameters:

  • name string
  • payload any
  • opts ...BroadcastOption
Show/Hide Function Body
{
	var options broadcastOptions
	for _, opt := range opts {
		opt(&options)
	}

	connMu.RLock()
	conns := connections[name]
	connMu.RUnlock()
	if len(conns) == 0 {
		return
	}

	for ws, session := range conns {
		if options.session != "" && session.ID() != options.session {
			continue
		}
		sendToConn(ws, outbound{Component: name, Payload: payload, Session: session.ID()})
	}
}

sendToConn function

Parameters:

  • ws *websocket.Conn
  • out outbound

References:

Show/Hide Function Body
{
	b, err := json.Marshal(out)
	if err != nil {
		return
	}
	if err := websocket.Message.Send(ws, b); err != nil {
		log.Printf("send: %v", err)
	}
}

Handler type

Handler processes inbound payloads for a HostComponent and returns a

response payload to send back to the wasm runtime. Returning nil results in

no message being sent.

Type Definition:

func(payload map[string]any) any

HandlerWithSession type

HandlerWithSession processes inbound payloads with the associated Session.

Type Definition:

func(*Session, map[string]any) any

HostComponent struct

HostComponent represents server-side logic backing an HTML component.

Fields:

  • name (string)
  • handler (Handler)
  • sessionHandler (HandlerWithSession)
  • initSnapshot (func(*Session, map[string]any) *InitSnapshot)

Methods:

WithInitSnapshot

WithInitSnapshot registers a callback that produces an InitSnapshot when a resync is requested.


Parameters:
  • fn func(*Session, map[string]any) *InitSnapshot

Returns:
  • *HostComponent

Show/Hide Method Body
{
	hc.initSnapshot = fn
	return hc
}

Name


Returns:
  • string

Show/Hide Method Body
{ return hc.name }

Handle

Handle executes the component's handler.


Parameters:
  • payload map[string]any

Returns:
  • any

Show/Hide Method Body
{
	if hc.handler == nil {
		return nil
	}
	return hc.handler(payload)
}

HandleWithSession

HandleWithSession executes the session-aware handler when available.


Parameters:
  • session *Session
  • payload map[string]any

Returns:
  • any

Show/Hide Method Body
{
	if payload != nil {
		if _, ok := payload["resync"]; ok && hc.initSnapshot != nil {
			if snap := hc.initSnapshot(session, payload); snap != nil {
				return snap
			}
		}
	}
	if hc.sessionHandler != nil {
		return hc.sessionHandler(session, payload)
	}
	if hc.handler != nil {
		return hc.handler(payload)
	}
	return nil
}

SessionAware

SessionAware reports whether the component registered a session handler.


Returns:
  • bool

Show/Hide Method Body
{ return hc.sessionHandler != nil }

StoreManager

StoreManager returns the session-specific store manager when available.

If session is nil a reference to the global manager is returned for

backward compatibility with legacy handlers.


Parameters:
  • session *Session

Returns:
  • *state.StoreManager

Show/Hide Method Body
{
	if session != nil {
		return session.StoreManager()
	}
	return state.GlobalStoreManager
}

InitSnapshot struct

InitSnapshot represents markup the host can send to force the client to repaint a fragment.

Fields:

  • HTML (string) - json:"html"
  • Vars ([]string) - json:"vars,omitempty"

NewHostComponent function

NewHostComponent registers a handler for the given component name.

Parameters:

  • name string
  • handler Handler

Returns:

  • *HostComponent

References:

Show/Hide Function Body
{
	hc := &HostComponent{name: name, handler: handler}
	if handler != nil {
		hc.sessionHandler = func(_ *Session, payload map[string]any) any {
			return handler(payload)
		}
	}
	return hc
}

NewHostComponentWithSession function

NewHostComponentWithSession registers a session-aware handler.

Parameters:

  • name string
  • handler HandlerWithSession

Returns:

  • *HostComponent

References:

Show/Hide Function Body
{
	return &HostComponent{name: name, sessionHandler: handler}
}

Register function

Register adds a HostComponent to the global registry so incoming messages

can be routed to it.

Parameters:

  • hc *HostComponent
Show/Hide Function Body
{ registry[hc.name] = hc }

Get function

Get returns a registered HostComponent by name.

Parameters:

  • name string

Returns:

  • *HostComponent
  • bool
Show/Hide Function Body
{ hc, ok := registry[name]; return hc, ok }

net/http import

Import example:

import "net/http"

net/http/httptest import

Import example:

import "net/http/httptest"

testing import

Import example:

import "testing"

io import

Import example:

import "io"

net/http import

Import example:

import "net/http"

net/http/httptest import

Import example:

import "net/http/httptest"

os import

Import example:

import "os"

path/filepath import

Import example:

import "path/filepath"

testing import

Import example:

import "testing"

encoding/json import

Import example:

import "encoding/json"

fmt import

Import example:

import "fmt"

os import

Import example:

import "os"

strconv import

Import example:

import "strconv"

strings import

Import example:

import "strings"

log/slog import

Import example:

import "log/slog"

os import

Import example:

import "os"

strings import

Import example:

import "strings"

testing import

Import example:

import "testing"

bufio import

Import example:

import "bufio"

errors import

Import example:

import "errors"

net import

Import example:

import "net"

net/http import

Import example:

import "net/http"

time import

Import example:

import "time"

crypto/rand import

Import example:

import "crypto/rand"

crypto/rsa import

Import example:

import "crypto/rsa"

crypto/tls import

Import example:

import "crypto/tls"

crypto/x509 import

Import example:

import "crypto/x509"

encoding/pem import

Import example:

import "encoding/pem"

expvar import

Import example:

import "expvar"

math/big import

Import example:

import "math/big"

net/http import

Import example:

import "net/http"

net/http/pprof import

Import example:

import "net/http/pprof"

os import

Import example:

import "os"

path/filepath import

Import example:

import "path/filepath"

strings import

Import example:

import "strings"

time import

Import example:

import "time"

golang.org/x/net/websocket import

Import example:

import "golang.org/x/net/websocket"

crypto/rand import

Import example:

import "crypto/rand"

encoding/hex import

Import example:

import "encoding/hex"

sync import

Import example:

import "sync"

github.com/rfwlab/rfw/v1/state import

Import example:

import "github.com/rfwlab/rfw/v1/state"

encoding/json import

Import example:

import "encoding/json"

fmt import

Import example:

import "fmt"

net/http/httptest import

Import example:

import "net/http/httptest"

os import

Import example:

import "os"

path/filepath import

Import example:

import "path/filepath"

strings import

Import example:

import "strings"

sync import

Import example:

import "sync"

testing import

Import example:

import "testing"

golang.org/x/net/websocket import

Import example:

import "golang.org/x/net/websocket"

github.com/rfwlab/rfw/v1/state import

Import example:

import "github.com/rfwlab/rfw/v1/state"

testing import

Import example:

import "testing"

encoding/json import

Import example:

import "encoding/json"

io import

Import example:

import "io"

log import

Import example:

import "log"

sync import

Import example:

import "sync"

golang.org/x/net/websocket import

Import example:

import "golang.org/x/net/websocket"

github.com/rfwlab/rfw/v1/state import

Import example:

import "github.com/rfwlab/rfw/v1/state"