logstream

package
v0.5.0 Latest Latest
Warning

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

Go to latest
Published: Sep 1, 2026 License: MIT Imports: 15 Imported by: 0

Documentation

Overview

Package logstream is the GoFastr log-tail plugin: xterm.js inside an opaque-origin sandboxed iframe, fed by a line source the HOST pushes across the postMessage bridge without ever being asked.

Why this shape: every other plugin in this repo is turn-based — load a document, save a document; even the datagrid, which moves 100,000 rows, moves them one request at a time in answer to a question the frame asked. A log tail is the opposite: open-ended, host-initiated, and produced faster than it can be rendered. This plugin exists to prove the bridge carries that, and to make the answer to "what happens when it cannot keep up" EXPLICIT instead of silent:

  • The host adapter (host/adapter.js) keeps at most 4 unacknowledged batches in flight; the frame acks each rendered batch with a streamAck event carrying the last sequence number it rendered.
  • When the producer outruns the frame, the host drops from the OLDEST end of its bounded line buffer and counts; the count travels with the next batch and the frame renders a visible "N lines dropped" marker. A gap the user cannot see is worse than a gap labelled "1,432 lines dropped".
  • The frame's scrollback is bounded (10,000 lines, published in every ack next to the live depth) and its consumption is paced at one batch per ~16 ms tick, so a burst cannot monopolise its main thread.

Read-only by design: no PTY, no shell, no command input, no writes, no uploads. The host supplies a line source (WithSource); the frame renders it. A terminal that can send input is a different plugin with a different security review.

Capabilities: stream:read + theme:read, nothing else. The single route, GET /stream, is the producer's side of the bridge and gates on stream:read. pluginhost.Allow is a capability gate, NOT authentication: it passes for anonymous callers, so a host exposing a sensitive source must check the session in its own SourceFunc. See docs/logstream.md.

Index

Constants

View Source
const (
	Name             = "logstream"
	Version          = "0.1.0"
	RoutePrefix      = "/__gofastr/plugin/logstream"
	TermHTMLURL      = RoutePrefix + "/term.html"
	TermJSURL        = RoutePrefix + "/term.js"
	TermCSSURL       = RoutePrefix + "/term.css"
	AdapterScriptURL = RoutePrefix + "/adapter.js"
	StreamURL        = RoutePrefix + "/stream"
	DemoURL          = "/logstream"
	SchemaVersion    = "logstream-v1"

	// CapStreamRead gates GET StreamURL — the producer side of the bridge.
	// It is the plugin's only route: logstream has no write surface at all.
	CapStreamRead = "stream:read"
)

Identity and route constants. Both this plugin and host/adapter.js hard-code these exactly (protocol-v1.md §2/§10). The demo lives at /logstream.

These mirror the logstream row in plugins.json; internal/registry tests pin Name + RoutePrefix against that row, so they MUST NOT drift.

Variables

This section is empty.

Functions

func DefaultCapabilities

func DefaultCapabilities() []string

DefaultCapabilities is the grant set advertised to the frame: reading the stream and bridging theme tokens. There are deliberately no optional capabilities — the plugin is read-only by construction, so there is no handler-vs-grant cross-check to fail loud about (the datagrid pattern exists for plugins with optional WRITE surface).

func Mount

func Mount(cfg MountConfig) render.HTML

Mount renders the generic mount marker. A log tail has no canonical doc and no hidden form field — nothing to round-trip on submit; the marker is the whole mount. All interpolated values are HTML-escaped inside pluginhost.MountMarker.

func UIHostOption

func UIHostOption() uihost.Option

UIHostOption injects the platform broker and this plugin's adapter (in that order — the adapter registers with the registry the former defines).

Types

type Line

type Line struct {
	Seq  uint64 `json:"seq"`
	Text string `json:"text"`
}

Line is one log line crossing the stream: a sequence number assigned by the host's source plus the raw text with ANSI escapes INTACT — the frame interprets colour, the host never does. Seq must be strictly increasing within a stream (the ack/drop accounting is seq-ordered).

type MountConfig

type MountConfig struct {
	// DocID is the stream identity for this mount (logging/debug key; a
	// log tail has no persisted doc).
	DocID string
	// MinHeight is the terminal viewport height. Defaults to 560px.
	MinHeight string
	// Capabilities is an optional CSV grant override.
	Capabilities string
}

MountConfig configures Mount.

type Option

type Option func(*Plugin)

Option configures a Plugin.

func WithCapabilities

func WithCapabilities(caps ...string) Option

WithCapabilities overrides the grant set advertised to the frame. Default: DefaultCapabilities. There is nothing to expand into — the plugin has no optional capabilities — but the override exists for hosts that mint scoped tokens ("stream:*" implies stream:read under the framework's wildcard grammar, and the runtime gate matches it).

func WithDemoControlURL

func WithDemoControlURL(url string) Option

WithDemoControlURL points the demo page's rate switch at a host-app route (POST {"rate":"calm"|"fast"}). The plugin is read-only — no capability exists for changing a producer's rate — so the control belongs to the app that owns the producer, typically the same one that wired WithSource. Unset (the default), the demo page renders without the rate switch.

func WithDemoPage

func WithDemoPage() Option

WithDemoPage registers the self-contained themed demo page at DemoURL.

func WithDemoRoute

func WithDemoRoute(path string) Option

WithDemoRoute overrides where WithDemoPage mounts the demo (default DemoURL, "/logstream").

func WithDevGrantAll

func WithDevGrantAll() Option

WithDevGrantAll short-circuits the capability gate (Phase-0 demo / tests). It bypasses BOTH gate sides, so the stream route behind it still serves anyone — which is exactly why a real host must keep authentication in its own SourceFunc.

func WithSource

func WithSource(fn SourceFunc) Option

WithSource installs the line producer behind GET StreamURL. There is NO default: like datagrid's rows source, a log viewer without a source is a blank rectangle, so New panics without this option.

type Plugin

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

Plugin is the log-stream plugin. It implements framework.Plugin and mirrors the datagrid/pdf shape: opaque-origin sandboxed iframe, protocol v1 over postMessage, go:embed'd frame bundle, capability-gated route. The difference is the traffic profile — an open-ended push, host-initiated, with backpressure carried by frame acks.

func New

func New(opts ...Option) *Plugin

New constructs a Plugin. The platform manifest is built and Validate()'d here so a bad isolation/sandbox config aborts construction rather than silently de-opaquing the frame at runtime.

func (*Plugin) Capabilities

func (p *Plugin) Capabilities() []string

Capabilities returns the grant set this plugin advertises to the frame.

func (*Plugin) Init

func (p *Plugin) Init(app *framework.App) error

Init registers every asset and the stream route on the app's router.

func (*Plugin) Manifest

func (p *Plugin) Manifest() pluginhost.Manifest

func (*Plugin) Name

func (p *Plugin) Name() string

type SourceFunc

type SourceFunc func(ctx context.Context, after uint64, yield func(Line) error) error

SourceFunc is the host-supplied line producer behind GET StreamURL. It is the streaming twin of datagrid's WithRowsSource: instead of answering a pull, it PUSHES lines to yield until the consumer goes away.

Contract:

  • It runs once per connected stream consumer, with a context cancelled on disconnect; it MUST return promptly after ctx is done or yield returns an error.
  • Only lines with Seq > after may be yielded (reconnects resume from the last sequence number the frame acknowledged).
  • yield blocks until the line is on the wire; a stalled consumer therefore backpressures the source rather than being silently overrun. Dropping is the ADAPTER's job (visible, counted); the Go side stays lossless or loud.
  • AUTHENTICATION is the source's own responsibility: Allow passes for anonymous callers, so a host exposing a sensitive log must check the session here before yielding anything.

Jump to

Keyboard shortcuts

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