push

package
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Apr 22, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package push handles Web Push subscriptions and notification delivery for self-hosted Caffeine installs. It persists browser push subscriptions in the same SQLite database the rest of the app uses and fans out notifications on "shot finished" and "analysis ready" events.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Payload

type Payload struct {
	Title string `json:"title"`
	Body  string `json:"body"`
	// Tag collapses multiple notifications in the same category into a
	// single entry on the device (so a run of back-to-back shots doesn't
	// spam the notification center).
	Tag string `json:"tag,omitempty"`
	// URL is the path the PWA should navigate to when the user taps the
	// notification. Must be same-origin; the SW uses clients.openWindow.
	URL string `json:"url,omitempty"`
	// Kind lets the SW / UI disambiguate without string-matching titles.
	Kind string `json:"kind,omitempty"`
}

Payload is the JSON body we deliver to the service worker. The SW parses it in its 'push' handler and turns it into a Notification.

type Preferences

type Preferences struct {
	OnShotFinished  bool `json:"on_shot_finished"`
	OnAnalysisReady bool `json:"on_analysis_ready"`
}

Preferences captures the per-subscription event filters.

type Sender

type Sender interface {
	SendNotification(message []byte, sub *webpush.Subscription, options *webpush.Options) (*http.Response, error)
}

Sender is the subset of webpush used by Service. Extracted so tests can feed a fake without spinning up a push service.

type Service

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

Service sends push notifications to every stored subscription that opted in for the given event. All public methods are non-blocking: they hand the work off to a background goroutine so they can be called from latency-sensitive spots (recorder flush, analysis completion) without holding anything up.

func NewService

func NewService(store *Store, vapid VAPID, logger *slog.Logger) *Service

NewService builds a Service. Callers should only construct one per process; it's safe for concurrent use.

func (*Service) NotifyAnalysisReady

func (s *Service) NotifyAnalysisReady(shotID, model string)

NotifyAnalysisReady fires when the auto-analyzer has saved a critique.

func (*Service) NotifyShotFinished

func (s *Service) NotifyShotFinished(shotID, name string)

NotifyShotFinished fires when a live shot has been persisted. Runs in its own goroutine — callers need not block.

func (*Service) PublicKey

func (s *Service) PublicKey() string

PublicKey exposes the VAPID public key so the API layer can return it to the frontend for pushManager.subscribe().

func (*Service) SendTest

func (s *Service) SendTest(ctx context.Context, endpoint string) error

SendTest delivers a single test notification to one endpoint. Used by the "send test" button in Settings so operators can verify the pipe without having to actually pull a shot.

type Store

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

Store wraps access to push_subscriptions + push_vapid.

func OpenStore

func OpenStore(path string) (*Store, error)

OpenStore opens the push store at path. Safe to point at the same file other internal/* packages use; tables don't collide.

func (*Store) Close

func (s *Store) Close() error

Close releases the underlying database.

func (*Store) Count

func (s *Store) Count(ctx context.Context) (int, error)

Count returns the number of stored subscriptions. Handy for the settings UI status line.

func (*Store) Delete

func (s *Store) Delete(ctx context.Context, endpoint string) error

Delete removes a subscription. No-op if absent.

func (*Store) GetVAPID

func (s *Store) GetVAPID(ctx context.Context) (*VAPID, error)

GetVAPID returns the persisted VAPID keypair or (nil, nil) if none is stored yet.

func (*Store) List

func (s *Store) List(ctx context.Context) ([]Subscription, error)

List returns every subscription. Order is stable (oldest first) so the caller can reason about fan-out timing if it cares.

func (*Store) SaveVAPID

func (s *Store) SaveVAPID(ctx context.Context, v VAPID) error

SaveVAPID persists the keypair. The table is pinned to a single row (id=1) so this is effectively a replace.

func (*Store) Upsert

func (s *Store) Upsert(ctx context.Context, sub Subscription) error

Upsert inserts-or-updates a subscription keyed on endpoint. The endpoint URL is effectively the browser's stable identifier for this (origin, installation) pair; reusing it lets a browser that re-subscribes — say after a permission toggle — overwrite the old row instead of piling up orphans.

type Subscription

type Subscription struct {
	Endpoint        string
	P256dh          string
	Auth            string
	OnShotFinished  bool
	OnAnalysisReady bool
	UserAgent       string
	CreatedAt       time.Time
	UpdatedAt       time.Time
}

Subscription is a stored browser push subscription.

type VAPID

type VAPID struct {
	PublicKey  string
	PrivateKey string
	Subject    string
}

VAPID is the server identity used to sign outbound push requests.

func LoadOrGenerateVAPID

func LoadOrGenerateVAPID(ctx context.Context, store *Store, envPub, envPriv, subject string) (VAPID, error)

LoadOrGenerateVAPID returns the persisted VAPID keypair, generating and persisting a fresh one on first run. Env-supplied values (if both publicKey and privateKey are non-empty) always win and are written back into the store so subsequent restarts stay stable if the env var is removed.

subject is the "Subscriber" claim embedded in VAPID-signed requests. Push services use it to reach out to the server owner if something misbehaves; mailto: URLs are the canonical form but any URL works.

Jump to

Keyboard shortcuts

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