push

package
v0.2.16 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: MIT Imports: 26 Imported by: 0

Documentation

Overview

Package push delivers Web Push notifications to subscribed browsers, implementing VAPID (RFC 8292) authorization and the aes128gcm content encoding (RFC 8291 + RFC 8188) on top of the Go standard library — keeping harness-deck dependency-free.

Two long-lived state objects live in the config directory next to config.json:

  • vapid.json the ECDSA P-256 application-server identity keypair, generated once via `harness-deck vapid`.
  • subscriptions.json the list of phones / browsers that asked to be notified.

For every notification we generate a fresh ephemeral ECDH P-256 keypair, derive a content-encryption key + nonce per RFC 8291, encrypt the JSON payload, and POST it to the subscription endpoint with a VAPID-signed JWT in the Authorization header.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Keys

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

Keys holds the application-server VAPID identity — a P-256 ECDSA keypair kept stable across restarts so existing subscriptions remain valid.

func Generate

func Generate() (*Keys, error)

Generate creates a new VAPID keypair.

func Load

func Load(path string) (*Keys, error)

Load reads a keypair previously saved with Save.

func LoadOrMissing

func LoadOrMissing(path string) (*Keys, bool, error)

LoadOrMissing returns the keys if present, or nil, false (no error) when the file simply doesn't exist — push features stay dormant until the user runs `harness-deck vapid`.

func (*Keys) PrivateB64URL

func (k *Keys) PrivateB64URL() string

PrivateB64URL returns the 32-byte private scalar as base64url.

func (*Keys) PublicB64URL

func (k *Keys) PublicB64URL() string

PublicB64URL returns the uncompressed P-256 public key as base64url (no padding) — the form expected by the browser's applicationServerKey and by the VAPID Authorization header's `k=` parameter.

func (*Keys) Save

func (k *Keys) Save(path string) error

Save writes the keypair to path with 0600 permissions.

func (*Keys) SignVAPID

func (k *Keys) SignVAPID(audience, subject string, ttl time.Duration) (string, error)

SignVAPID produces a VAPID JWT (RFC 8292) authorizing one push request: header is fixed {alg:ES256, typ:JWT}; payload has aud (push origin), exp (now+ttl, capped by the spec at 24h), and sub (operator contact).

type Payload

type Payload struct {
	Title   string `json:"title"`
	Body    string `json:"body"`
	Tag     string `json:"tag,omitempty"`
	URL     string `json:"url,omitempty"`
	Project string `json:"project,omitempty"`
	Run     string `json:"run,omitempty"`
}

Payload is the JSON object delivered to the service worker as the notification body. We keep it small and explicit; the service worker renders it as a system notification.

type Sender

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

Sender pushes payloads to subscriptions using a single long-lived VAPID identity. It is safe for concurrent use.

func NewSender

func NewSender(keys *Keys, subject string) *Sender

NewSender builds a sender around an existing VAPID keypair. The `subject` is the contact (mailto: or https:// URL) push services may use to reach the operator about misbehaving notifications.

func (*Sender) Send

func (s *Sender) Send(ctx context.Context, sub Subscription, payload Payload) (int, error)

Send delivers payload to sub. It returns the HTTP status code so the caller can prune subscriptions the push service reports as Gone (404/410).

type Store

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

Store persists the set of push Subscriptions we deliver notifications to. It is safe for concurrent use. One Subscription per push endpoint URL — the same browser/device resubscribing replaces its prior record.

func NewStore

func NewStore(path string) *Store

NewStore opens the JSON-backed subscription store at path. A missing file is fine: it materializes on first Save.

func (*Store) Add

func (s *Store) Add(sub Subscription) error

Add inserts sub, replacing any existing entry with the same endpoint.

func (*Store) All

func (s *Store) All() []Subscription

All returns every stored subscription (snapshot copy).

func (*Store) Count

func (s *Store) Count() int

Count returns the number of stored subscriptions.

func (*Store) Remove

func (s *Store) Remove(endpoint string) error

Remove deletes the subscription with the given endpoint. A missing endpoint is a no-op so unsubscribe is idempotent.

func (*Store) RemoveIfMatches added in v0.2.7

func (s *Store) RemoveIfMatches(endpoint string, keys SubscriptionKeys) error

RemoveIfMatches deletes the subscription only when both the endpoint and the keys match. Pruning a 410-Gone subscription this way avoids deleting a fresh re-subscription that reused the same endpoint URL with new keys. A non-matching endpoint or keys is a no-op.

type Subscription

type Subscription struct {
	Endpoint string           `json:"endpoint"`
	Keys     SubscriptionKeys `json:"keys"`
}

Subscription is the shape the browser's PushSubscription.toJSON() produces — what we receive from /api/push/subscribe and store verbatim.

type SubscriptionKeys

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

SubscriptionKeys carries the UA's P-256 ECDH public key (uncompressed, base64url) and a 16-byte shared auth secret (base64url).

Jump to

Keyboard shortcuts

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