notify

package
v0.3.3 Latest Latest
Warning

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

Go to latest
Published: Nov 4, 2026 License: GPL-3.0 Imports: 39 Imported by: 0

Documentation

Overview

Package notify provides durable, rule-filtered owner notifications. Channel secrets never appear in API responses, delivery logs, or transport errors.

Index

Constants

View Source
const Redacted = "[configured]"

Variables

View Source
var Kinds = eventKindIDs()

Kinds lists every event kind Ether accepts. Definitions live in events.go.

Functions

func ChannelKindDescriptors added in v0.3.3

func ChannelKindDescriptors() []extend.Descriptor

ChannelKindDescriptors returns the built-in channelKind descriptors for the extend registry.

func EventKindDescriptors added in v0.3.3

func EventKindDescriptors() []extend.Descriptor

EventKindDescriptors returns the built-in eventKind descriptors for the extend registry.

func PurgeGrantTx added in v0.3.0

func PurgeGrantTx(ctx context.Context, tx *sql.Tx, viewerID int64, grantHash string) error

func PurgeViewerTx added in v0.3.0

func PurgeViewerTx(ctx context.Context, tx *sql.Tx, viewerID int64) error

PurgeViewerTx and PurgeGrantTx participate in the same transaction that deactivates membership or revokes a device grant.

func RunMonitor added in v0.2.4

func RunMonitor(input io.Reader) error

RunMonitor is used only by the executable's notification-monitor entry point. A killed host, network outage, or supervisor killing the whole process group also stops this watcher. An external uptime monitor is required for those.

Types

type Channel

type Channel struct {
	ID            string            `json:"id"`
	Name          string            `json:"name"`
	Kind          string            `json:"kind"`
	Enabled       bool              `json:"enabled"`
	Events        []string          `json:"events"`
	Settings      map[string]string `json:"settings,omitempty"`
	Secrets       map[string]string `json:"secrets,omitempty"`
	PerMinute     int               `json:"perMinute"`
	DigestMinutes int               `json:"digestMinutes"`
}

Channel is one notification destination. Kind-specific values live in Settings (plain) and Secrets (sealed at rest, redacted by the owner API), keyed by the field names of the kind's schema (kinds.go).

func (Channel) Secret added in v0.3.3

func (c Channel) Secret(key string) string

Secret returns a secret field value, or "".

func (*Channel) SetSubscription added in v0.3.3

func (c *Channel) SetSubscription(p *PushSubscription)

SetSubscription stores p as the channel's subscription secret.

func (Channel) Setting added in v0.3.3

func (c Channel) Setting(key string) string

Setting returns a non-secret field value, or "".

func (Channel) Subscription added in v0.2.4

func (c Channel) Subscription() (*PushSubscription, error)

Subscription decodes the browser push subscription held in Secrets.

type ChannelKind added in v0.3.3

type ChannelKind struct {
	ID    string
	Owner string // extend.Builtin or a plugin id
	Label extend.Text
	Hint  extend.Text
	Icon  string
	Order int

	Fields     []extend.SettingsField
	SendTest   bool
	Selectable bool
	Formats    []string
	MaxLength  int

	Validate func(c Channel) error
	Deliver  func(ctx context.Context, s *Service, c Channel, e Event) error
}

ChannelKind is one way notifications can be delivered: a settings schema the console renders, validation, and delivery. Built-ins and (through Service.RegisterChannelKind) plugin kinds use the same shape; each kind becomes a channelKind descriptor in the extend registry.

func BuiltinChannelKinds added in v0.3.3

func BuiltinChannelKinds() []ChannelKind

BuiltinChannelKinds returns Ether's own channel kinds, ordered as in the console picker.

func (ChannelKind) Descriptor added in v0.3.3

func (k ChannelKind) Descriptor() extend.Descriptor

Descriptor returns the kind as a channelKind descriptor.

type Event

type Event struct {
	HTML    string    `json:"html,omitempty"`
	Kind    string    `json:"kind"`
	Title   string    `json:"title"`
	Message string    `json:"message"`
	At      time.Time `json:"at"`
}

type FavoriteEpisodeDetector added in v0.3.0

type FavoriteEpisodeDetector struct {
	Preferences *ViewerAlerts
	// Episodes must return only visible, currently favorited shows and episodes.
	Episodes func(context.Context, *core.User) ([]core.Title, error)
	Now      func() time.Time
}

FavoriteEpisodeDetector scans only currently authorized favorites. The caller runs it under the cluster leader lease. The database uniqueness constraint also protects against overlap during a leader handoff.

func (*FavoriteEpisodeDetector) Scan added in v0.3.0

type Heartbeat added in v0.2.4

type Heartbeat struct {
	Enabled  bool   `json:"enabled"`
	Endpoint string `json:"endpoint"`
}

type KindRegistry added in v0.3.3

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

KindRegistry holds the channel kinds a service can validate and deliver. It is safe for concurrent use; the plugin host registers plugin kinds here as plugins are installed.

func NewKindRegistry added in v0.3.3

func NewKindRegistry(kinds ...ChannelKind) *KindRegistry

NewKindRegistry returns a registry holding kinds.

func (*KindRegistry) Get added in v0.3.3

func (r *KindRegistry) Get(id string) (ChannelKind, bool)

Get returns one kind.

func (*KindRegistry) List added in v0.3.3

func (r *KindRegistry) List() []ChannelKind

List returns every kind, ordered for pickers.

func (*KindRegistry) Register added in v0.3.3

func (r *KindRegistry) Register(k ChannelKind) error

Register adds a kind. IDs must be unique; plugin kinds are namespaced by their descriptor but share this delivery table by full descriptor id.

func (*KindRegistry) RemovePlugin added in v0.3.3

func (r *KindRegistry) RemovePlugin(id, owner string)

RemovePlugin removes a plugin's channel kind without affecting built-ins.

type Options

type Options struct {
	// NodeID names this node on the queue rows it claims; a random id is
	// used when empty.
	NodeID     string
	Providers  func() []usenet.ProviderStats
	CacheUsage func() (int64, int64)
	Client     *http.Client
	Now        func() time.Time
	Logger     *slog.Logger
	// Sealer protects stored channel secrets and the push identity
	// (secrets.go); nil stores them as given.
	Sealer config.FieldSealer
	// Kinds is the channel-kind registry; nil uses the built-ins.
	Kinds *KindRegistry
}

type PushKeys added in v0.2.4

type PushKeys struct {
	Auth   string `json:"auth"`
	P256dh string `json:"p256dh"`
}

type PushSubscription added in v0.2.4

type PushSubscription struct {
	Endpoint string   `json:"endpoint"`
	Keys     PushKeys `json:"keys"`
}

type Service

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

func New

func New(db *sql.DB, opts Options) *Service

func (*Service) BackupFailed added in v0.2.48

func (s *Service) BackupFailed(ctx context.Context, err error) error

BackupFailed reports a scheduled backup that did not complete.

func (*Service) Channels

func (s *Service) Channels(ctx context.Context) ([]Channel, error)

func (*Service) Drain

func (s *Service) Drain(ctx context.Context) error

func (*Service) Emit

func (s *Service) Emit(ctx context.Context, e Event) error

func (*Service) EmitOnce added in v0.2.48

func (s *Service) EmitOnce(ctx context.Context, key string, e Event) (bool, error)

EmitOnce emits e unless an event was already emitted under key, and reports whether it emitted. The key and the queued deliveries commit in one transaction: a crash or a failed enqueue leaves neither, so a later attempt emits it.

func (*Service) HTTPClient added in v0.3.3

func (s *Service) HTTPClient() *http.Client

HTTPClient is the client channel kinds deliver with.

func (*Service) Handler

func (s *Service) Handler() http.Handler

Handler is an owner-only API; mount with /notifications stripped.

func (*Service) Heartbeat added in v0.2.4

func (s *Service) Heartbeat(ctx context.Context) error

Heartbeat is called only after a successful local HTTP probe. The remote monitor must alert when these one-minute pings stop (allow a three-minute grace).

func (*Service) HeartbeatSettings added in v0.2.4

func (s *Service) HeartbeatSettings(ctx context.Context, redact bool) (Heartbeat, error)

func (*Service) Kinds added in v0.3.3

func (s *Service) Kinds() *KindRegistry

Kinds returns the service's channel-kind registry.

func (*Service) MigrateSecrets added in v0.2.4

func (s *Service) MigrateSecrets(ctx context.Context) (int, error)

MigrateSecrets seals channel secrets stored in plaintext, rewrites flat pre-registry channel rows in the new {settings, secrets} shape, and seals the push identity. It returns how many rows it rewrote. A row is replaced only if it is unchanged since read, so it is safe to rerun on any node; a row that does not open is left untouched and reported in the error.

func (*Service) ObserveIndexer

func (s *Service) ObserveIndexer(e newznab.Event)

ObserveIndexer alerts once per outage, retrying failed queue writes at most once a minute. Database waits never hold the shared health mutex.

func (*Service) ObservePlayback

func (s *Service) ObservePlayback(ev playback.Event)

ObservePlayback only enqueues; network delivery runs separately in Run.

func (*Service) RegisterChannelKind added in v0.3.3

func (s *Service) RegisterChannelKind(k ChannelKind) error

RegisterChannelKind adds a channel kind (plugin kinds; WS9f).

func (*Service) Run

func (s *Service) Run(ctx context.Context)

Run is the single queue consumer. Failed sends are recorded without automatic retry, avoiding duplicate delivery when an upstream timeout is ambiguous.

func (*Service) SampleHealth

func (s *Service) SampleHealth(ctx context.Context) error

SampleHealth emits one alert on entering an unhealthy state, rather than one alert for every sample. Normal recovery allows a later outage to alert again.

func (*Service) Save

func (s *Service) Save(ctx context.Context, c Channel) error

func (*Service) SaveHeartbeat added in v0.2.4

func (s *Service) SaveHeartbeat(ctx context.Context, h Heartbeat) error

func (*Service) Subscribe added in v0.3.3

func (s *Service) Subscribe(fn func(Event)) func()

Subscribe observes successfully emitted events, independent of channel selection.

func (*Service) TitleAvailable

func (s *Service) TitleAvailable(ctx context.Context, title string) error

TitleAvailable lets the discovery/watchlist flow publish availability.

func (*Service) TitleAvailableOnce added in v0.2.48

func (s *Service) TitleAvailableOnce(ctx context.Context, key, title string) error

TitleAvailableOnce publishes availability once per key (an episode, say), however often a caller retries after a failure or crash.

func (*Service) UpdateAvailable added in v0.2.48

func (s *Service) UpdateAvailable(ctx context.Context, current, version string) error

UpdateAvailable announces a release once per version.

func (*Service) WatchProcess added in v0.2.4

func (s *Service) WatchProcess(executable string) (func(bool) error, error)

WatchProcess starts a small independent delivery process. The returned function must be called on normal exit, with clean=false for a fatal run error. A panic must be allowed to terminate the parent so that EOF reaches the watcher.

type ViewerAlerts added in v0.3.0

type ViewerAlerts struct {
	DB     *sql.DB
	Active func(context.Context, int64) (*core.User, error)
	Now    func() time.Time
}

ViewerAlerts keeps viewer notification settings separate from owner channels. Active must consult current membership, not a cached viewer supplied by the caller.

func (*ViewerAlerts) Enabled added in v0.3.0

func (s *ViewerAlerts) Enabled(ctx context.Context, viewer *core.User) (bool, error)

Enabled defaults to false for every viewer without an explicit preference.

func (*ViewerAlerts) SetEnabled added in v0.3.0

func (s *ViewerAlerts) SetEnabled(ctx context.Context, viewer *core.User, enabled bool) error

SetEnabled records the time of each off-to-on transition so detection can suppress episodes already aired before a viewer opted in.

type ViewerPush added in v0.3.0

type ViewerPush struct {
	Notifications *Service
	Preferences   *ViewerAlerts
	Allowed       func(context.Context, *core.User, core.RatingKey) (bool, error)
	Now           func() time.Time
	// Send is optional and permits offline tests; nil uses the web-push transport.
	Send func(context.Context, PushSubscription, Event) error
}

ViewerPush uses viewer-owned destinations, separate from owner channels. Allowed must recheck the favorite show and current episode/show policy.

func (*ViewerPush) Drain added in v0.3.0

func (p *ViewerPush) Drain(ctx context.Context) error

Drain claims recent alerts once per destination across nodes. Claims are intentionally at-most-once: a crash or send failure does not replay a push. Old alerts and alerts preceding subscription or opt-in are never sent.

func (*ViewerPush) PublicKey added in v0.3.0

func (p *ViewerPush) PublicKey(ctx context.Context, viewer *core.User) (string, error)

func (*ViewerPush) PurgeViewer added in v0.3.0

func (p *ViewerPush) PurgeViewer(ctx context.Context, viewerID int64) error

PurgeViewer removes all destinations after membership deactivation or revocation.

func (*ViewerPush) Register added in v0.3.0

func (p *ViewerPush) Register(ctx context.Context, viewer *core.User, subscription PushSubscription, grantHash string) error

Register associates a browser endpoint with its current viewer only.

func (*ViewerPush) Registered added in v0.3.0

func (p *ViewerPush) Registered(ctx context.Context, viewer *core.User, endpoint string) (bool, error)

Registered reports whether this exact browser endpoint belongs to the viewer.

func (*ViewerPush) Unregister added in v0.3.0

func (p *ViewerPush) Unregister(ctx context.Context, viewer *core.User, endpoint string) error

Jump to

Keyboard shortcuts

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