jaws

package module
v0.304.1 Latest Latest
Warning

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

Go to latest
Published: Apr 13, 2026 License: MIT Imports: 37 Imported by: 8

README

build coverage goreport Docs

JaWS

Javascript and WebSockets used to create responsive webpages.

JaWS embraces a "server holds the truth" philosophy and keeps the complexity of modern browser applications on the backend. The client-side script becomes a thin transport layer that faithfully relays events and DOM updates.

Features

  • Moves web application state fully to the server.
  • Keeps the browser intentionally dumb – no implicit trust in JavaScript logic running on the client.
  • Binds application data to UI elements using user-defined tags and type-aware binders.
  • Integrates with the standard library as well as third-party routers such as Echo.
  • Ships with a small standard library of UI widgets and helper types that can be extended through interfaces.

There is a demo application with plenty of comments to use as a tutorial.

Installation

JaWS is distributed as a standard Go module. To add it to an existing project use the go get command:

go get github.com/linkdata/jaws

After the dependency is added, your Go module will be able to import and use JaWS as demonstrated below.

For widget authoring guidance see lib/ui/README.md.

Quick start

The following minimal program renders a single range input whose value is kept on the server. Copy the snippet into a new module, run go mod tidy, and start it with go run .. Visiting http://localhost:8080/ demonstrates the full request lifecycle.

Usage

import (
	"html/template"
	"log/slog"
	"net/http"
	"sync"

	"github.com/linkdata/jaws"
	"github.com/linkdata/jaws/lib/bind"
	"github.com/linkdata/jaws/lib/ui"
)

const indexhtml = `
<html>
<head>{{$.HeadHTML}}</head>
<body>{{$.Range .Dot}}</body>
</html>
`

func main() {
	jw, err := jaws.New() // create a default JaWS instance
	if err != nil {
		panic(err)
	}
	defer jw.Close()           // ensure we clean up
	jw.Logger = slog.Default() // optionally set the logger to use

	// parse our template and inform JaWS about it
	templates := template.Must(template.New("index").Parse(indexhtml))
	jw.AddTemplateLookuper(templates)

	go jw.Serve()                             // start the JaWS processing loop
	http.DefaultServeMux.Handle("GET /jaws/", jw) // ensure the JaWS routes are handled

	var mu sync.Mutex
	var f float64

	http.DefaultServeMux.Handle("GET /", ui.Handler(jw, "index", bind.New(&mu, &f)))
	slog.Error(http.ListenAndServe("localhost:8080", nil).Error())
}

Next steps when building a real application typically include:

  1. Adding more templates and wiring them with AddTemplateLookuper.
  2. Creating types that implement JawsRender and JawsUpdate so they can be reused as widgets.
  3. Introducing sessions (see below) to keep track of user state.
Creating HTML entities

When JawsRender() is called for a UI object, it can call NewElement() to create new Elements while writing their initial HTML code to the web page. Each Element is a unique instance of a UI object bound to a specific Request, and will have a unique HTML id.

If a HTML entity is not registered in a Request, JaWS will not forward events from it, nor perform DOM manipulations for it.

Dynamic updates of HTML entities is done using the different methods on the Element object when the JawsUpdate() method is called.

Javascript events

Supported Javascript events are sent to the server and are handled by the Element's UI type. If that didn't handle the event, any extra objects added to the Element are invoked (in order) until one handles the event. If none handle the event, it is ignored.

The generic event handler is JawsEvent. An event handler should return ErrEventUnhandled if it didn't handle the event or wants to pass it to the next handler.

  • onclick invokes JawsClick if present, otherwise JawsEvent with what.Click
  • oninput invokes JawsEvent with what.Input

Technical notes

WebSocket wire format notes

JaWS websocket messages are line-based and field-delimited: What<TAB>Jid<TAB>Data<LF>. Keep these invariants in mind when changing client/server protocol code:

  • The browser is not trusted. Incoming frames are validated (What, Jid, framing, quoting) and invalid frames are ignored/dropped.
  • what.Remove means remove child element(s). For browser-originated Remove messages, the websocket Jid must be a valid JaWS element ID for the managed parent/container and Data carries removed managed child IDs. Remove frames from non-managed containers are expected to be invalid and therefore dropped.
  • what.Replace replaces the target element HTML and carries plain HTML in Data.
  • what.Call/what.Set use path + "=" + json inside Data. Embedded tabs or newlines in JSON break message framing; Jaws.JsCall compacts valid JSON before sending.
  • jawsVar(name, ...) resolves properties from window, but websocket routing uses the top-level symbol name only. Register JaWS JsVar names as top-level identifiers (example: app), and use dotted suffixes as the JSON path (example: jawsVar("app.state", value) sends path state).
HTTP request flow and associating the WebSocket

When a new HTTP request is received, create a JaWS Request using the JaWS object's NewRequest() method, and then use the Request's HeadHTML() method to get the HTML code needed in the <head> section of the HTML page.

When the client has finished loading the document and parsed the scripts, the JaWS JavaScript will request a WebSocket connection on /jaws/*, with the * being the encoded Request.JawsKey value.

On receiving the WebSocket HTTP request, decode the key parameter from the URL and call the JaWS object's UseRequest() method to retrieve the Request created in the first step. Then call its ServeHTTP() method to start up the WebSocket and begin processing JavaScript events and DOM updates.

WebSocket keepalive ping

JaWS can periodically ping active WebSocket connections to detect peers that disappeared without a close handshake.

Set Jaws.WebSocketPingInterval to control this. The default is jaws.DefaultWebSocketPingInterval (1 minute). Set it to 0 or a negative value to disable keepalive pings.

Safe to call before Serve()

The following APIs are safe to call before starting the JaWS processing loop (Serve() or ServeWithTimeout()):

  • Construction and lifecycle: jaws.New(), (*Jaws).Close(), (*Jaws).Done().
  • Configuration: (*Jaws).AddTemplateLookuper(), (*Jaws).RemoveTemplateLookuper(), (*Jaws).LookupTemplate(), (*Jaws).GenerateHeadHTML(), (*Jaws).Setup(), (*Jaws).FaviconURL().
  • Inspection and logging helpers: (*Jaws).RequestCount(), (*Jaws).Pending(), (*Jaws).SessionCount(), (*Jaws).Sessions(), (*Jaws).Log(), (*Jaws).MustLog().
  • Static/ping JaWS endpoints via (*Jaws).ServeHTTP(): /jaws/.ping, /jaws/.jaws.<hash>.js, /jaws/.jaws.<hash>.css.

Broadcasting APIs are not safe before the processing loop starts. In particular, (*Jaws).Broadcast() (and helpers that call it), (*Session).Broadcast(), (*Session).Reload() and (*Session).Close() may block before Serve() or ServeWithTimeout() is running.

Secure Response Headers

Use (*Jaws).SecureHeadersMiddleware(next) to wrap page handlers with a security-header baseline and a Content-Security-Policy that matches the resources currently configured for JaWS.

The baseline headers come from github.com/linkdata/secureheaders.

The middleware snapshots secureheaders.DefaultHeaders, replaces Content-Security-Policy with jw.ContentSecurityPolicy(), and does not trust forwarded HTTPS headers.

page := ui.Handler(jw, "index", bind.New(&mu, &f))
http.DefaultServeMux.Handle("GET /", jw.SecureHeadersMiddleware(page))
Routing

JaWS doesn't enforce any particular router, but it does require several endpoints to be registered in whichever router you choose to use. All of the endpoints start with "/jaws/", and Jaws.ServeHTTP() will handle all of them.

  • /jaws/.jaws.<hash>.css

    Serves the built-in JaWS stylesheet.

    The response should be cached indefinitely.

  • /jaws/.jaws.<hash>.js

    Serves the built-in JaWS client-side JavaScript.

    The response should be cached indefinitely.

  • /jaws/[0-9a-z]+ (and /jaws/[0-9a-z]+/noscript)

    The WebSocket endpoint. The trailing string must be decoded using assets.JawsKeyValue() (github.com/linkdata/jaws/lib/assets) and then the matching JaWS Request retrieved using the JaWS object's UseRequest() method.

    If the Request is not found, return a 404 Not Found, otherwise call the Request ServeHTTP() method to start the WebSocket and begin processing events and updates.

  • /jaws/.ping

    This endpoint is called by the Javascript while waiting for the server to come online. This is done in order to not spam the WebSocket endpoint with connection requests, and browsers are better at handling XHR requests failing.

    If you don't have a JaWS object, or if its completion channel is closed (see Jaws.Done()), return 503 Service Unavailable. If you're ready to serve requests, return 204 No Content.

    The response should not be cached.

Handling the routes with the standard library's http.DefaultServeMux:

jw, err := jaws.New()
if err != nil {
  panic(err)
}
defer jw.Close()
go jw.Serve()
http.DefaultServeMux.Handle("GET /jaws/", jw)

Handling the routes with Echo:

jw, err := jaws.New()
if err != nil {
  panic(err)
}
defer jw.Close()
go jw.Serve()
router := echo.New()
router.GET("/jaws/*", func(c echo.Context) error {
  jw.ServeHTTP(c.Response().Writer, c.Request())
  return nil
})
HTML rendering

HTML output elements (e.g. ui.RequestWriter.Div()) require a bind.HTMLGetter or something that can be made into one using bind.MakeHTMLGetter().

In order of precedence, this can be:

  • bind.HTMLGetter: JawsGetHTML(*Element) template.HTML to be used as-is.
  • bind.Getter[string]: JawsGet(*Element) string that will be escaped using html.EscapeString.
  • bind.Formatter: Format("%v") string that will be escaped using html.EscapeString.
  • fmt.Stringer: String() string that will be escaped using html.EscapeString.
  • a static template.HTML or string to be used as-is with no HTML escaping.
  • everything else is rendered using fmt.Sprint() and escaped using html.EscapeString.

You can use bind.New(...).FormatHTML(), bind.HTMLGetterFunc() or bind.StringGetterFunc() to build a custom renderer for trivial rendering tasks, or define a custom type implementing HTMLGetter.

Data binding

HTML input elements (e.g. ui.RequestWriter.Range()) require bi-directional data flow between the server and the browser. The first argument to these is usually a bind.Setter[T] where T is one of string, float64, bool or time.Time. It can also be a bind.Getter[T], in which case the HTML element should be made read-only.

Since all data access need to be protected with locks, you will usually use bind.New() to create a bind.Binder[T] that combines a (RW)Locker and a pointer to a value of type T. It also allows you to add chained setters, getters and on-success handlers.

Session handling

JaWS has non-persistent session handling integrated. Sessions won't be persisted across restarts and must have an expiry time.

Use one of these patterns:

  • Wrap page handlers with Jaws.Session(handler) to ensure a session exists.
  • Call Jaws.NewSession(w, r) explicitly to create and attach a fresh session cookie.

When subsequent Requests are created with NewRequest(), if the HTTP request has the cookie set and comes from the correct IP, the new Request will have access to that Session.

Session key-value pairs can be accessed using Request.Set() and Request.Get(), or directly using a Session object. It's safe to do this if there is no session; Get() will return nil, and Set() will be a no-op.

Sessions are bound to the client IP. Attempting to access an existing session from a new IP will fail.

No data is stored in the client browser except the randomly generated session cookie. You can set the cookie name in Jaws.CookieName, the default is derived from the executable name and falls back to jaws.

A note on the Context

The Request object embeds a context.Context inside its struct, contrary to recommended Go practice.

The reason is that there is no unbroken call chain from the time the Request object is created when the initial HTTP request comes in and when it is requested during the Javascript WebSocket HTTP request.

Security of the WebSocket callback

Each JaWS request gets a unique 64-bit random value assigned to it when you create the Request object. This value is written to the HTML output so it can be read by the Javascript, and used to construct the WebSocket callback URL.

Once the WebSocket call comes in, the value is consumed by that request, and is no longer valid until, theoretically, another Request gets the same random value. And that's fine, since JaWS guarantees that no two Requests waiting for WebSocket calls can have the same value at the same time.

In addition to this, Requests that are not claimed by a WebSocket call get cleaned up at regular intervals. By default an unclaimed Request is removed after 10 seconds.

In order to guess (and thus hijack) a WebSocket you'd have to make on the order of 2^63 requests before the genuine request comes in, or 10 seconds pass assuming you can reliably prevent the genuine WebSocket request.

Dependencies

We try to minimize dependencies outside of the standard library.

Learn more

Documentation

Overview

package jaws provides a mechanism to create dynamic webpages using Javascript and WebSockets.

It integrates well with Go's html/template package, but can be used without it. It can be used with any router that supports the standard ServeHTTP interface.

Index

Constants

View Source
const (
	DefaultUpdateInterval        = time.Millisecond * 100 // Default browser update interval
	DefaultWebSocketPingInterval = time.Minute            // Default WebSocket keepalive ping interval
	DefaultWebSocketTimeout      = time.Second * 10       // WebSocket must connect and respond within this interval
)

Variables

View Source
var (
	ErrWebsocketOriginMissing     = errors.New("websocket request missing Origin header")
	ErrWebsocketOriginWrongScheme = errors.New("websocket Origin not http or https")
	ErrWebsocketOriginWrongHost   = errors.New("websocket Origin host mismatch")
	ErrRequestAlreadyClaimed      = errors.New("request already claimed")
	ErrJavascriptDisabled         = errors.New("javascript is disabled")
)
View Source
var ErrEventHandlerPanic errEventHandlerPanic

ErrEventHandlerPanic is returned when an event handler panics.

View Source
var ErrEventUnhandled = errEventUnhandled{}

ErrEventUnhandled returned by JawsEvent() or JawsClick() causes the next available handler to be invoked.

View Source
var ErrNoWebSocketRequest errNoWebSocketRequest

ErrNoWebSocketRequest is returned when the WebSocket callback was not received within the timeout period. Most common reason is that client is not using Javascript.

View Source
var ErrRequestCancelled errRequestCancelled

ErrRequestCancelled indicates a Request was cancelled. Use Unwrap() to see the underlying cause.

View Source
var ErrValueUnchanged = errors.New("value unchanged")

ErrValueUnchanged can be returned from JawsSet[Type] functions to indicate that while there was no error, the underlying value was already the desired value.

Functions

func AppendID added in v0.31.0

func AppendID(b []byte) []byte

AppendID appends the result of NextID() in text form to the given slice.

func CallEventHandlers added in v0.300.0

func CallEventHandlers(ui any, e *Element, wht what.What, val string) (err error)

CallEventHandlers calls the event handlers for the given Element. Recovers from panics in user-provided handlers, returning them as errors.

func MakeID added in v0.24.0

func MakeID() string

MakeID returns a string in the form 'jaws.X' where X is a unique string within lifetime of the program.

func NextID added in v0.31.0

func NextID() int64

NextID returns an int64 unique within lifetime of the program.

Types

type Auth added in v0.85.0

type Auth interface {
	Data() map[string]any // returns authenticated user data, or nil
	Email() string        // returns authenticated user email, or an empty string
	IsAdmin() bool        // return true if admins are defined and current user is one, or if no admins are defined
}

type ClickHandler added in v0.31.0

type ClickHandler interface {
	// JawsClick is called when an Element's HTML element or something within it
	// is clicked in the browser.
	//
	// The name parameter is taken from the first 'name' HTML attribute or HTML
	// 'button' textContent found when traversing the DOM. It may be empty.
	JawsClick(e *Element, name string) (err error)
}

type ConnectFn

type ConnectFn = func(rq *Request) error

ConnectFn can be used to interact with a Request before message processing starts. Returning an error causes the Request to abort, and the WebSocket connection to close.

type Container added in v0.31.0

type Container interface {
	// JawsContains must return a slice of hashable UI objects. The slice contents must not be modified after returning it.
	JawsContains(e *Element) (contents []UI)
}

type DefaultAuth added in v0.300.0

type DefaultAuth struct{}

func (DefaultAuth) Data added in v0.300.0

func (DefaultAuth) Data() map[string]any

func (DefaultAuth) Email added in v0.300.0

func (DefaultAuth) Email() string

func (DefaultAuth) IsAdmin added in v0.300.0

func (DefaultAuth) IsAdmin() bool

type Element added in v0.31.0

type Element struct {
	*Request // (read-only) the Request the Element belongs to
	// contains filtered or unexported fields
}

An Element is an instance of a *Request, an UI object and a Jid.

func (*Element) AddHandlers added in v0.300.0

func (e *Element) AddHandlers(h ...EventHandler)

AddHandler adds the given handlers to the Element.

func (*Element) Append added in v0.31.0

func (e *Element) Append(htmlCode template.HTML)

Append appends a new HTML element as a child to the current one.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) ApplyGetter added in v0.75.0

func (e *Element) ApplyGetter(getter any) (tag any, err error)

ApplyGetter examines getter, and if it's not nil, either adds it as a Tag, or, if it is a TagGetter, adds the result of that as a Tag.

If getter is a ClickHandler or an EventHandler, it's added to the list of handlers for the Element.

Finally, if getter is an InitHandler, it's JawsInit() function is called.

Returns the Tag(s) added, or nil if getter was nil, along with any error returned from JawsInit() if it was called.

func (*Element) ApplyParams added in v0.60.0

func (e *Element) ApplyParams(params []any) (retv []template.HTMLAttr)

ApplyParams parses the parameters passed to UI() when creating a new Element, adding UI tags, adding any additional event handlers found.

Returns the list of HTML attributes found, if any.

func (*Element) HasTag added in v0.31.0

func (e *Element) HasTag(tag any) bool

HasTag returns true if this Element has the given tag.

func (*Element) JawsRender added in v0.55.0

func (e *Element) JawsRender(w io.Writer, params []any) (err error)

JawsRender calls Ui().JawsRender() for this Element.

Do not call this yourself unless it's from within another JawsRender implementation.

func (*Element) JawsUpdate added in v0.55.0

func (e *Element) JawsUpdate()

JawsUpdate calls Ui().JawsUpdate() for this Element.

Do not call this yourself unless it's from within another JawsUpdate implementation.

func (*Element) Jid added in v0.31.0

func (e *Element) Jid() jid.Jid

Jid returns the JaWS ID for this Element, unique within it's Request.

func (*Element) Order added in v0.31.0

func (e *Element) Order(jidList []jid.Jid)

Order reorders the HTML elements.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) Remove added in v0.31.0

func (e *Element) Remove(htmlId string)

Remove requests that the HTML child with the given HTML ID of this Element is removed from the Request and it's HTML element from the browser.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) RemoveAttr added in v0.31.0

func (e *Element) RemoveAttr(attr string)

RemoveAttr queues sending a request to remove an attribute to the browser for the Element with the given JaWS ID in this Request.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) RemoveClass added in v0.31.0

func (e *Element) RemoveClass(cls string)

RemoveClass queues sending a request to remove a class to the browser for the Element with the given JaWS ID in this Request.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) Replace added in v0.31.0

func (e *Element) Replace(htmlCode template.HTML)

Replace replaces the elements entire HTML DOM node with new HTML code. If the HTML code doesn't seem to contain correct HTML ID, it panics.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) SetAttr added in v0.31.0

func (e *Element) SetAttr(attr, val string)

SetAttr queues sending a new attribute value to the browser for the Element with the given JaWS ID in this Request.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) SetClass added in v0.31.0

func (e *Element) SetClass(cls string)

SetClass a queues sending a class to the browser for the Element with the given JaWS ID in this Request.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) SetInner added in v0.31.0

func (e *Element) SetInner(innerHTML template.HTML)

SetInner queues sending a new inner HTML content to the browser for the Element.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) SetValue added in v0.31.0

func (e *Element) SetValue(val string)

SetValue queues sending a new current input value in textual form to the browser for the Element with the given JaWS ID in this Request.

Call this only during JawsRender() or JawsUpdate() processing.

func (*Element) String added in v0.31.0

func (e *Element) String() string

func (*Element) Tag added in v0.31.0

func (e *Element) Tag(tags ...any)

Tag adds the given tags to the Element.

func (*Element) Ui added in v0.31.0

func (e *Element) Ui() UI

Ui returns the UI object.

type EventFn

type EventFn = func(e *Element, wht what.What, val string) (err error)

EventFn is the signature of a event handling function to be called when JaWS receives an event message from the Javascript via the WebSocket connection.

type EventHandler added in v0.31.0

type EventHandler interface {
	JawsEvent(e *Element, wht what.What, val string) (err error)
}

func ParseParams added in v0.60.0

func ParseParams(params []any) (tags []any, handlers []EventHandler, attrs []string)

ParseParams parses the parameters passed to UI() when creating a new Element, returning UI tags, event handlers and HTML attributes.

type HandleFunc added in v0.111.6

type HandleFunc = func(pattern string, handler http.Handler)

HandleFunc matches the signature of http.ServeMux.Handle().

type InitHandler added in v0.110.0

type InitHandler interface {
	JawsInit(e *Element) (err error)
}

InitHandler allows initializing UI getters and setters before their use.

You can of course initialize them in the call from the template engine, but at that point you don't have access to the Element, Element.Context or Element.Session.

type Jaws

type Jaws struct {
	CookieName            string          // Name for session cookies, defaults to "jaws"
	Logger                Logger          // Optional logger to use
	Debug                 bool            // Set to true to enable debug info in generated HTML code
	MakeAuth              MakeAuthFn      // Optional function to create With.Auth for Templates
	BaseContext           context.Context // Non-nil base context for Requests, set to context.Background() in New()
	WebSocketPingInterval time.Duration   // Interval between keepalive pings on active WebSocket connections. Defaults to DefaultWebSocketPingInterval. Set <=0 to disable keepalive pings.
	// contains filtered or unexported fields
}

Jaws holds the server-side state and configuration for a JaWS instance.

A single Jaws value coordinates template lookup, session handling and the request lifecycle that keeps the browser and backend synchronized via WebSockets. The zero value is not ready for use; construct instances with New to ensure the helper goroutines and static assets are prepared.

func New

func New() (jw *Jaws, err error)

New allocates a JaWS instance with the default configuration.

The returned Jaws value is ready for use: static assets are embedded, internal goroutines are configured and the request pool is primed. Call Close when the instance is no longer needed to free associated resources.

func (*Jaws) AddTemplateLookuper added in v0.45.0

func (jw *Jaws) AddTemplateLookuper(tl TemplateLookuper) (err error)

AddTemplateLookuper adds an object that can resolve strings to *template.Template.

func (*Jaws) Alert

func (jw *Jaws) Alert(lvl, msg string)

Alert sends an alert to all Requests. The lvl argument should be one of Bootstraps alert levels: primary, secondary, success, danger, warning, info, light or dark.

func (*Jaws) Append

func (jw *Jaws) Append(target any, html template.HTML)

Append calls the Javascript 'appendChild()' method on all HTML elements matching target.

func (*Jaws) Broadcast

func (jw *Jaws) Broadcast(msg wire.Message)

Broadcast sends a message to all Requests.

It must not be called before the JaWS processing loop (`Serve()` or `ServeWithTimeout()`) is running. Otherwise this call may block once the internal broadcast channel fills.

All convenience helpers on Jaws that call Broadcast inherit this requirement.

func (*Jaws) Close

func (jw *Jaws) Close()

Close frees resources associated with the JaWS object, and closes the completion channel if the JaWS was created with New(). Once the completion channel is closed, broadcasts and sends may be discarded. Subsequent calls to Close() have no effect.

func (*Jaws) ContentSecurityPolicy added in v0.300.0

func (jw *Jaws) ContentSecurityPolicy() (s string)

ContentSecurityPolicy returns the generated Content-Security-Policy header value.

func (*Jaws) Delete added in v0.31.0

func (jw *Jaws) Delete(target any)

Delete removes the HTML element(s) matching target.

func (*Jaws) Dirty added in v0.31.0

func (jw *Jaws) Dirty(dirtyTags ...any)

Dirty marks all Elements that have one or more of the given tags as dirty.

Note that if any of the tags are a TagGetter, it will be called with a nil Request. Prefer using Request.Dirty() which avoids this.

func (*Jaws) Done

func (jw *Jaws) Done() <-chan struct{}

Done returns the channel that is closed when Close has been called.

func (*Jaws) FaviconURL added in v0.111.6

func (jw *Jaws) FaviconURL() (s string)

func (*Jaws) GenerateHeadHTML added in v0.5.0

func (jw *Jaws) GenerateHeadHTML(extra ...string) (err error)

GenerateHeadHTML (re-)generates the HTML code that goes in the HEAD section, ensuring that the provided URL resources in `extra` are loaded, along with the JaWS javascript. If one of the resources is named "favicon", it's URL will be stored and can be retrieved using FaviconURL().

You only need to call this if you add your own images, scripts and stylesheets.

func (*Jaws) GetSession added in v0.11.0

func (jw *Jaws) GetSession(hr *http.Request) (sess *Session)

GetSession returns the Session associated with the given *http.Request, or nil.

func (*Jaws) Insert

func (jw *Jaws) Insert(target any, where, html string)

Insert calls the Javascript 'insertBefore()' method on all HTML elements matching target.

The position parameter 'where' may be either a HTML ID, an child index or the text 'null'.

func (*Jaws) JsCall added in v0.114.0

func (jw *Jaws) JsCall(tag any, jsfunc, jsonstr string)

JsCall calls the Javascript function 'jsfunc' with the argument 'jsonstr' on all Requests that have the target UI tag.

func (*Jaws) Log

func (jw *Jaws) Log(err error) error

Log sends an error to the Logger set in the Jaws. Has no effect if the err is nil or the Logger is nil. Returns err.

func (*Jaws) LookupTemplate added in v0.66.0

func (jw *Jaws) LookupTemplate(name string) *template.Template

LookupTemplate queries the known TemplateLookupers in the order they were added and returns the first found.

func (*Jaws) MustLog added in v0.1.1

func (jw *Jaws) MustLog(err error)

MustLog sends an error to the Logger set in the Jaws or panics with the given error if no Logger is set. Has no effect if the err is nil.

func (*Jaws) NewRequest

func (jw *Jaws) NewRequest(hr *http.Request) (rq *Request)

NewRequest returns a new pending JaWS request.

Call this as soon as you start processing a HTML request, and store the returned Request pointer so it can be used while constructing the HTML response in order to register the JaWS id's you use in the response, and use it's Key attribute when sending the Javascript portion of the reply.

Automatic timeout handling is performed by ServeWithTimeout. The default Serve() helper uses a 10-second timeout.

func (*Jaws) NewSession added in v0.26.0

func (jw *Jaws) NewSession(w http.ResponseWriter, hr *http.Request) (sess *Session)

NewSession creates a new Session.

Any pre-existing Session will be cleared and closed. This may call Session.Close() on an existing session and therefore requires the JaWS processing loop (`Serve()` or `ServeWithTimeout()`) to be running.

Subsequent Requests created with `NewRequest()` that have the cookie set and originates from the same IP will be able to access the Session.

func (*Jaws) Pending

func (jw *Jaws) Pending() (n int)

Pending returns the number of requests waiting for their WebSocket callbacks.

func (*Jaws) Redirect

func (jw *Jaws) Redirect(url string)

Redirect requests all Requests to navigate to the given URL.

func (*Jaws) Reload

func (jw *Jaws) Reload()

Reload requests all Requests to reload their current page.

func (*Jaws) RemoveAttr

func (jw *Jaws) RemoveAttr(target any, attr string)

RemoveAttr sends a request to remove the given attribute from all HTML elements matching target.

func (*Jaws) RemoveClass added in v0.31.0

func (jw *Jaws) RemoveClass(target any, cls string)

RemoveClass sends a request to remove the given class from all HTML elements matching target.

func (*Jaws) RemoveTemplateLookuper added in v0.45.0

func (jw *Jaws) RemoveTemplateLookuper(tl TemplateLookuper) (err error)

RemoveTemplateLookuper removes the given object from the list of TemplateLookupers.

func (*Jaws) Replace

func (jw *Jaws) Replace(target any, html string)

Replace replaces HTML on all HTML elements matching target.

func (*Jaws) RequestCount added in v0.25.0

func (jw *Jaws) RequestCount() (n int)

RequestCount returns the number of Requests.

The count includes all Requests, including those being rendered, those waiting for the WebSocket callback and those active.

func (*Jaws) SecureHeadersMiddleware added in v0.300.0

func (jw *Jaws) SecureHeadersMiddleware(next http.Handler) http.Handler

SecureHeadersMiddleware wraps next with security headers that match the current JaWS configuration.

It snapshots secureheaders.DefaultHeaders, replacing the Content-Security-Policy value with ContentSecurityPolicy so responses allow the resources configured by GenerateHeadHTML.

The returned middleware does not trust forwarded HTTPS headers. The next handler must be non-nil.

func (*Jaws) Serve

func (jw *Jaws) Serve()

Serve calls ServeWithTimeout(DefaultWebSocketTimeout). It is intended to run on it's own goroutine. It returns when Close is called.

func (*Jaws) ServeHTTP added in v0.19.0

func (jw *Jaws) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP can handle the required JaWS endpoints, which all start with "/jaws/".

func (*Jaws) ServeWithTimeout

func (jw *Jaws) ServeWithTimeout(requestTimeout time.Duration)

ServeWithTimeout begins processing requests with the given timeout. It is intended to run on it's own goroutine. It returns when Close is called.

func (*Jaws) Session added in v0.77.0

func (jw *Jaws) Session(h http.Handler) http.Handler

Session returns a http.Handler that ensures a JaWS Session exists before invoking h.

func (*Jaws) SessionCount added in v0.11.0

func (jw *Jaws) SessionCount() (n int)

SessionCount returns the number of active sessions.

func (*Jaws) Sessions added in v0.11.0

func (jw *Jaws) Sessions() (sl []*Session)

Sessions returns a list of all active sessions, which may be nil.

func (*Jaws) SetAttr

func (jw *Jaws) SetAttr(target any, attr, val string)

SetAttr sends a request to replace the given attribute value in all HTML elements matching target.

func (*Jaws) SetClass added in v0.31.0

func (jw *Jaws) SetClass(target any, cls string)

SetClass sends a request to set the given class in all HTML elements matching target.

func (*Jaws) SetInner

func (jw *Jaws) SetInner(target any, innerHTML template.HTML)

SetInner sends a request to replace the inner HTML of all HTML elements matching target.

func (*Jaws) SetValue

func (jw *Jaws) SetValue(target any, val string)

SetValue sends a request to set the HTML "value" attribute of all HTML elements matching target.

func (*Jaws) Setup added in v0.111.6

func (jw *Jaws) Setup(handleFn HandleFunc, prefix string, extras ...any) (err error)

Setup configures Jaws with extra functionality and resources.

The list of extras can be strings, *url.URL or *staticserve.StaticServe (URL resources) or a setup function matching SetupFunc such as jawsboot.Setup.

It calls GenerateHeadHTML with the final list of URLs, with any relative URL paths prefixed with prefix.

func (*Jaws) UseRequest

func (jw *Jaws) UseRequest(jawsKey uint64, hr *http.Request) (rq *Request)

UseRequest extracts the JaWS request with the given key from the request map if it exists and the HTTP request remote IP matches.

Call it when receiving the WebSocket connection on '/jaws/:key' to get the associated Request, and then call it's ServeHTTP method to process the WebSocket messages.

Returns nil if the key was not found or the IP doesn't match, in which case you should return a HTTP "404 Not Found" status.

type Jid added in v0.31.0

type Jid = jid.Jid // convenience alias

Jid is the identifier type used for HTML elements managed by JaWS.

It is provided as a convenience alias to the value defined in the jid subpackage so applications do not have to import that package directly when working with element IDs.

var NextJid Jid

NextJid is the next Jid that should be used. Used when testing. Do not modify it outside of tests.

type Logger added in v0.110.1

type Logger interface {
	Info(msg string, args ...any)
	Warn(msg string, args ...any)
	Error(msg string, args ...any)
}

Logger matches the log/slog.Logger interface.

type MakeAuthFn added in v0.85.0

type MakeAuthFn func(*Request) Auth

type Renderer added in v0.60.0

type Renderer interface {
	// JawsRender is called once per Element when rendering the initial webpage.
	// Do not call this yourself unless it's from within another JawsRender implementation.
	JawsRender(e *Element, w io.Writer, params []any) error
}

type Request

type Request struct {
	Jaws    *Jaws  // (read-only) the JaWS instance the Request belongs to
	JawsKey uint64 // (read-only) a random number used in the WebSocket URI to identify this Request

	Rendering atomic.Bool // set to true by RequestWriter.Write()
	// contains filtered or unexported fields
}

Request maintains the state for a JaWS WebSocket connection, and handles processing of events and broadcasts.

Note that we have to store the context inside the struct because there is no call chain between the Request being created and it being used once the WebSocket is created.

func (*Request) Alert

func (rq *Request) Alert(lvl, msg string)

Alert attempts to show an alert message on the current request webpage if it has an HTML element with the id 'jaws-alert'. The lvl argument should be one of Bootstraps alert levels: primary, secondary, success, danger, warning, info, light or dark.

The default JaWS javascript only supports Bootstrap.js dismissable alerts. See Jaws.Broadcast for processing-loop requirements.

func (*Request) AlertError

func (rq *Request) AlertError(err error)

AlertError calls Alert if the given error is not nil.

func (*Request) Context

func (rq *Request) Context() (ctx context.Context)

Context returns the Request's Context, which is by default derived from jaws.BaseContext.

func (*Request) DeleteElement added in v0.300.0

func (rq *Request) DeleteElement(elem *Element)

DeleteElement removes elem from the Request element registry.

This is primarily intended for UI implementations that manage dynamic child element sets and need to drop stale elements after issuing a corresponding DOM remove operation.

func (*Request) Dirty added in v0.31.0

func (rq *Request) Dirty(dirtyTags ...any)

Dirty marks all Elements that have one or more of the given tags as dirty.

func (*Request) Get added in v0.11.0

func (rq *Request) Get(key string) any

Get is shorthand for `Session().Get()` and returns the session value associated with the key, or nil. It no session is associated with the Request, returns nil.

func (*Request) GetConnectFn added in v0.7.0

func (rq *Request) GetConnectFn() (fn ConnectFn)

GetConnectFn returns the currently set ConnectFn. That function will be called before starting the WebSocket tunnel if not nil.

func (*Request) GetElementByJid added in v0.300.0

func (rq *Request) GetElementByJid(jid Jid) (e *Element)

func (*Request) GetElements added in v0.31.0

func (rq *Request) GetElements(tagitem any) (elems []*Element)

GetElements returns a list of the UI elements in the Request that have the given tag(s).

func (*Request) HasTag added in v0.31.0

func (rq *Request) HasTag(elem *Element, tag any) (yes bool)

func (*Request) HeadHTML

func (rq *Request) HeadHTML(w io.Writer) (err error)

HeadHTML writes the HTML code needed in the HTML page's HEAD section.

func (*Request) Initial added in v0.8.0

func (rq *Request) Initial() (r *http.Request)

Initial returns the Request's initial HTTP request, or nil.

func (*Request) JawsKeyString

func (rq *Request) JawsKeyString() string

func (*Request) Log added in v0.300.0

func (rq *Request) Log(err error) error

Log sends an error to the Logger set in the Jaws. Has no effect if the err is nil or the Logger is nil. Returns err.

func (*Request) MustLog added in v0.300.0

func (rq *Request) MustLog(err error)

MustLog sends an error to the Logger set in the Jaws or panics with the given error if no Logger is set. Has no effect if the err is nil.

func (*Request) NewElement added in v0.31.0

func (rq *Request) NewElement(ui UI) *Element

NewElement creates a new Element using the given UI object.

Panics if the build tag "debug" is set and the UI object doesn't satisfy all requirements.

func (*Request) Redirect

func (rq *Request) Redirect(url string)

Redirect requests the current Request to navigate to the given URL. See Jaws.Broadcast for processing-loop requirements.

func (*Request) ServeHTTP

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

ServeHTTP implements http.HanderFunc.

Requires UseRequest() have been successfully called for the Request.

func (*Request) Session added in v0.14.0

func (rq *Request) Session() (sess *Session)

Session returns the Request's Session, or nil.

func (*Request) Set added in v0.11.0

func (rq *Request) Set(key string, val any)

Set is shorthand for `Session().Set()` and sets a session value to be associated with the key. If value is nil, the key is removed from the session. Does nothing if there is no session is associated with the Request.

func (*Request) SetConnectFn added in v0.7.0

func (rq *Request) SetConnectFn(fn ConnectFn)

SetConnectFn sets ConnectFn. That function will be called before starting the WebSocket tunnel if not nil.

func (*Request) SetContext added in v0.110.0

func (rq *Request) SetContext(fn func(oldctx context.Context) (newctx context.Context))

SetContext atomically replaces the Request's context with the function return value. The function is given the current context and must return a non-nil context. The returned context must be derived from oldctx so cancellation and deadlines continue to propagate to Request.Context().

func (*Request) String

func (rq *Request) String() string

func (*Request) Tag added in v0.31.0

func (rq *Request) Tag(elem *Element, tagItems ...any)

Tag adds the given tags to the given Element.

func (*Request) TagExpanded added in v0.300.0

func (rq *Request) TagExpanded(elem *Element, expandedtags []any)

Tag adds the given tags to the given Element.

func (*Request) TagsOf added in v0.31.0

func (rq *Request) TagsOf(elem *Element) (tags []any)

func (*Request) TailHTML added in v0.79.0

func (rq *Request) TailHTML(w io.Writer) (err error)

TailHTML writes optional HTML code at the end of the page's BODY section that will immediately apply HTML attribute and class updates made during initial rendering, which minimizes flicker without having to write the correct value in templates or during JawsRender().

It also adds a <noscript> tag that warns of reduces functionality.

type Session added in v0.11.0

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

func (*Session) Broadcast added in v0.26.0

func (sess *Session) Broadcast(msg wire.Message)

Broadcast attempts to send a message to all Requests using this session.

It must not be called before the JaWS processing loop (`Serve()` or `ServeWithTimeout()`) is running. Otherwise this call may block. It is safe to call on a nil Session.

func (*Session) Clear added in v0.16.0

func (sess *Session) Clear()

Clear removes all key/value pairs from the session. It is safe to call on a nil Session.

func (*Session) Close added in v0.17.0

func (sess *Session) Close() (cookie *http.Cookie)

Close invalidates and expires the Session. Future Requests won't be able to associate with it, and Cookie() will return a deletion cookie.

Existing Requests already associated with the Session will ask the browser to reload the pages. Key/value pairs in the Session are left unmodified, you can use `Session.Clear()` to remove all of them.

It must not be called before the JaWS processing loop (`Serve()` or `ServeWithTimeout()`) is running, because reload broadcasts may block.

Returns a cookie to be sent to the client browser that will delete the browser cookie. Returns nil if the session was not found. It is safe to call on a nil Session.

func (*Session) Cookie added in v0.11.0

func (sess *Session) Cookie() (cookie *http.Cookie)

Cookie returns a cookie for the Session. Returns a delete cookie if the Session is expired. It is safe to call on a nil Session, in which case it returns nil.

func (*Session) CookieValue added in v0.11.0

func (sess *Session) CookieValue() (s string)

CookieValue returns the session cookie value. It is safe to call on a nil Session, in which case it returns an empty string.

func (*Session) Get added in v0.11.0

func (sess *Session) Get(key string) (val any)

Get returns the value associated with the key, or nil. It is safe to call on a nil Session.

func (*Session) ID added in v0.11.0

func (sess *Session) ID() (id uint64)

ID returns the session ID, a 64-bit random value. It is safe to call on a nil Session, in which case it returns zero.

func (*Session) IP added in v0.11.0

func (sess *Session) IP() (ip netip.Addr)

IP returns the remote IP the session is bound to (which may be nil). It is safe to call on a nil Session, in which case it returns nil.

func (*Session) Jaws added in v0.81.0

func (sess *Session) Jaws() (jw *Jaws)

Jaws returns the Jaws instance of the Session, or nil. It is safe to call on a nil Session.

func (*Session) Reload added in v0.17.0

func (sess *Session) Reload()

Reload calls Broadcast with a message asking browsers to reload the page. See Broadcast for the processing-loop requirement.

func (*Session) Requests added in v0.37.0

func (sess *Session) Requests() (rl []*Request)

Requests returns a list of the Requests using this Session. It is safe to call on a nil Session.

func (*Session) Set added in v0.11.0

func (sess *Session) Set(key string, val any)

Set sets a value to be associated with the key. If value is nil, the key is removed from the session. It is safe to call on a nil Session.

type SetupFunc added in v0.111.6

type SetupFunc = func(jw *Jaws, handleFn HandleFunc, prefix string) (urls []*url.URL, err error)

SetupFunc is called by Setup and allows setting up addons for JaWS.

The urls returned will be used in a call to GenerateHeadHTML.

type TemplateLookuper added in v0.45.0

type TemplateLookuper interface {
	Lookup(name string) *template.Template
}

TemplateLookuper resolves a name to a *template.Template.

type TestRequest added in v0.300.0

type TestRequest struct {
	*Request
	// contains filtered or unexported fields
}

TestRequest is a request harness intended for tests.

func NewTestRequest added in v0.300.0

func NewTestRequest(jw *Jaws, hr *http.Request) (tr *TestRequest)

NewTestRequest creates a TestRequest for use when testing. Passing nil for hr creates a GET / request with no body.

func (TestRequest) BodyHTML added in v0.300.0

func (rh TestRequest) BodyHTML() template.HTML

func (TestRequest) BodyString added in v0.300.0

func (rh TestRequest) BodyString() string

func (TestRequest) Close added in v0.300.0

func (rh TestRequest) Close()

type UI added in v0.31.0

type UI interface {
	Renderer
	Updater
}

UI defines the required methods on JaWS UI objects. In addition, all UI objects must be comparable so they can be used as map keys.

type Updater added in v0.60.0

type Updater interface {
	// JawsUpdate is called for an Element that has been marked dirty to update it's HTML.
	// Do not call this yourself unless it's from within another JawsUpdate implementation.
	JawsUpdate(e *Element)
}

Directories

Path Synopsis
lib
jid
ui
Package ui contains the standard JaWS widget implementations.
Package ui contains the standard JaWS widget implementations.

Jump to

Keyboard shortcuts

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