scanner

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: 13 Imported by: 0

Documentation

Overview

Package scanner is the GoFastr barcode-scanner plugin: @zxing/library inside an opaque-origin sandboxed iframe, fed GRAYSCALE pixels the HOST page captures and pushes across the postMessage bridge.

Why this shape: a camera is unreachable from an opaque-origin frame. getUserMedia fails there with "SecurityError: Invalid security origin" under sandbox="allow-scripts", and adding allow="camera" to the sandbox does not change it (measured — the permission is bound to a real origin the frame cannot have without de-opaquing). So the HOST page owns the MediaStream and the permission prompt appears against the host's origin where a user can reason about it; the frame only ever decodes what it is handed. Pixels cross host→frame; only strings cross back; the frame still cannot open a socket (connect-src 'none' like every plugin here).

The traffic profile is logstream's, inverted: a bounded open-ended push (one scanFrame in flight at a time — the frameDone ack is the flow control) rather than an unbounded one, because decode is the expensive side and stale frames are worthless. There is no document store, no upload path, no save route: a decoded string goes to the host page and nowhere else.

The wire contract v1 (both scanner/assets/scan.js and host/adapter.js implement THIS; see the adapter header for the full table):

host → frame: init (config: {formats, scanRateHz}), scanFrame
               {seq, width, height, gray}, scanSample, teardown
frame → host: scanResult {seq, text, format, decodeMs}, frameDone
               {seq, decoded}, scanStats {framesSeen, decodes,
               lastDecodeMs, lastText}

`gray` is grayscale LUMINANCE, exactly width*height bytes — NOT RGBA. Handing zxing an RGBA buffer fails inside MultiFormatReader with "No MultiFormat Readers were able to detect the code", which reads like a bad image rather than a bad call, so the mistake is invisible until someone measures it. (Measured.) The luminance conversion therefore lives in exactly one place: host/adapter.js, toGray().

Capabilities: scan:decode + theme:read, nothing else. There is no host route to gate — the plugin's only crossings are pixels down and strings up over the broker's source-checked postMessage — so scan:decode is the grant ADVERTISED to the frame (init.capabilities) and the capability a future host-side route would gate on; p.allow enforces it for callers that check. pluginhost.Allow is a capability gate, NOT authentication: it passes for anonymous callers.

Index

Constants

View Source
const (
	Name             = "scanner"
	Version          = "0.1.0"
	RoutePrefix      = "/__gofastr/plugin/scanner"
	FrameHTMLURL     = RoutePrefix + "/scan.html"
	ScanJSURL        = RoutePrefix + "/scan.js"
	ScanCSSURL       = RoutePrefix + "/scan.css"
	AdapterScriptURL = RoutePrefix + "/adapter.js"
	ConfigScriptURL  = RoutePrefix + "/config.js"
	DemoURL          = "/scanner"
	SchemaVersion    = "scanner-v1"

	// CapScanDecode is the decode grant advertised to the frame in
	// init.capabilities. The plugin has no host route to gate today — no
	// store, no upload, no save — so this is the capability a caller must
	// hold before a future host-side decode service would serve it.
	CapScanDecode = "scan:decode"
)

Identity and route constants. Both this plugin and host/adapter.js hard-code these exactly. The demo lives at /scanner.

These mirror the scanner row in plugins.json (added by the coordinator); internal/registry tests pin Name + RoutePrefix against that row, so they MUST NOT drift.

View Source
const ZxingVersion = "0.23.0"

ZxingVersion is the decoder bundled into the frame. It is stated on the demo page, and TestDemoPageStatesTheBundledDecoderVersion requires it to match js/package.json — mermaid's page shipped a version twelve releases stale because nothing checks prose.

Variables

This section is empty.

Functions

func DefaultCapabilities

func DefaultCapabilities() []string

DefaultCapabilities is the grant set advertised to the frame: decoding and bridging theme tokens. There are deliberately no optional capabilities — the plugin has no write surface at all, so there is no handler-vs-grant cross-check to fail loud about.

func DefaultFormats

func DefaultFormats() []string

DefaultFormats returns the default decode set: every supported format. All of them is the least-surprise default for a "barcode scanner"; it costs decode time (MultiFormatReader tries every reader), so a host scanning one symbology should narrow with WithFormats.

func Mount

func Mount(cfg MountConfig) render.HTML

Mount renders the generic mount marker. A scanner 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 SupportedFormats

func SupportedFormats() []string

SupportedFormats returns every format name WithFormats accepts, sorted.

func UIHostOption

func UIHostOption() uihost.Option

UIHostOption injects the platform broker, this plugin's config script, and this plugin's adapter (in that order — the adapter reads the config global the config script publishes, and registers with the broker the former defines).

Types

type MountConfig

type MountConfig struct {
	// DocID is the scanner identity for this mount (logging/debug key; a
	// scanner has no persisted doc).
	DocID string
	// MinHeight is the scanner viewport height. Defaults to 460px.
	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 ("scan:*" implies scan:decode under the framework's wildcard grammar, and the runtime gate matches it).

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, "/scanner").

func WithDevGrantAll

func WithDevGrantAll() Option

WithDevGrantAll short-circuits the capability gate (demo / tests). There is no gated route today, so it only loosens p.allow — kept for API symmetry with the rest of the repo and for whatever host-side decode service lands next.

func WithFormats

func WithFormats(formats ...string) Option

WithFormats narrows the decode set advertised to the frame (default: DefaultFormats, i.e. every supported symbology). Names are zxing BarcodeFormat strings — see SupportedFormats; an unknown name panics in New rather than silently never matching. Fewer formats also decodes faster: MultiFormatReader tries one reader per format.

func WithScanRate

func WithScanRate(hz int) Option

WithScanRate sets the host capture pace in frames per second (default 8). Out-of-range values panic in New: the rate is a pacing bound, not a correctness knob, and a typo of 0 or 5000 should never mount quietly.

type Plugin

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

Plugin is the barcode-scanner plugin. It implements framework.Plugin and mirrors the logstream shape (opaque-origin sandboxed iframe, protocol v1 over postMessage, go:embed'd frame bundle) with the scanner's inversion: the HOST is the producer (camera → grayscale scanFrame events) and the frame is the consumer (zxing decode), flow-controlled one frame in flight by frameDone acks.

func New

func New(opts ...Option) *Plugin

New constructs a Plugin. The platform manifest is built and Validate()'d here, and the instance config (formats, rate) is fail-loud validated, so a bad isolation/sandbox config or a typo'd format aborts construction rather than surfacing as a scanner that never decodes.

func (*Plugin) Capabilities

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

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

func (*Plugin) Formats

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

Formats returns the decode set this instance advertises (sorted copy).

func (*Plugin) Init

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

Init registers every asset on the app's router. There is deliberately no data route: pixels arrive over the bridge from the privileged host adapter, and the frame's CSP (connect-src 'none') is what keeps it that way.

func (*Plugin) Manifest

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

func (*Plugin) Name

func (p *Plugin) Name() string

func (*Plugin) ScanRateHz

func (p *Plugin) ScanRateHz() int

ScanRateHz returns the host capture pace this instance configures.

Jump to

Keyboard shortcuts

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