Help us shape the future of Go! Take the 2026 Go Developer Survey

push

package
v0.40.6 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2026 License: MIT Imports: 28 Imported by: 0

Documentation

Overview

Package push handles Web Push subscriptions and notification delivery. Subscriptions and the singleton VAPID keypair live in the shared Postgres database.

Index

Constants

View Source
const (
	EnvSandbox    = "sandbox"
	EnvProduction = "production"
)

Device environments.

View Source
const (
	LiveUpdate = "update"
	LiveEnd    = "end"
)

Live Activity events.

View Source
const ActivityTTL = 12 * time.Hour

ActivityTTL is the longest a registration lives: an activity runs 8 hours (12 on screen), so a row older than this is stale whatever the app said.

View Source
const LiveInterval = time.Minute

LiveInterval is how often the worker looks for charging trucks that have a running Live Activity. A send still waits for LiveMinInterval per activity.

View Source
const LiveMinInterval = 5 * time.Minute

LiveMinInterval is the least time between two routine updates to one activity (Apple budgets Live Activity updates; at most 12 an hour per activity). The start and the end are not routine and bypass it.

View Source
const NotificationTTL = 24 * time.Hour

NotificationTTL is how long a notification's text is kept for the app to fetch.

Variables

View Source
var ErrEndpoint = errors.New("push: endpoint is not an accepted push service")

ErrEndpoint is returned for an endpoint that is not an accepted push service.

View Source
var ErrLiveOff = errors.New("push: live charging is off for this device")

ErrLiveOff is "this iPhone's Live Activity switch is off".

View Source
var ErrNotFound = errors.New("push: not found")

ErrNotFound is "no such notification (for this user, still fresh)".

View Source
var LiveTestState = LiveState{SoCPct: 50, PowerKW: 7.2, MinutesToTarget: 60}

LiveTestState is what the user's own "test" shows on the lock screen.

Functions

func CandidateOther added in v0.33.2

func CandidateOther(primary string, names []string) string

CandidateOther picks the second distinct network from detection evidence candidates (names in nearest-first order); "" when none.

func EndpointHost added in v0.40.5

func EndpointHost(raw string) string

EndpointHost is the lower-case host of a push endpoint and nothing else: no scheme, port, path, query or credentials. The path of a push endpoint is a capability (whoever holds it can push to the device), so a log line about a refused endpoint names the host only. "" for anything that does not parse; capped, so a long junk host cannot flood a log.

func JoinClosures added in v0.38.26

func JoinClosures(kinds []string) string

JoinClosures names the open closures: "the frunk", "the frunk and a door" (in the order of closureWords). Unknown kinds are ignored; with none left it is "something".

func PurgeActivities added in v0.38.15

func PurgeActivities(ctx context.Context, db *sql.DB) (int64, error)

PurgeActivities deletes every registration older than ActivityTTL (all users). Run hourly.

func PurgeNotifications added in v0.38.11

func PurgeNotifications(ctx context.Context, db *sql.DB) (int64, error)

PurgeNotifications deletes every notification past NotificationTTL (all users). Run hourly.

func SafeNotificationURL added in v0.38.11

func SafeNotificationURL(u string) string

SafeNotificationURL keeps a notification's tap target inside the app: a same-site relative path, else "/" (an external link, such as a release-notes page, is dropped).

func ValidActivityToken added in v0.38.15

func ValidActivityToken(t string) bool

ValidActivityToken reports whether t has the shape of an activity push token.

func ValidDeviceToken added in v0.38.11

func ValidDeviceToken(t string) bool

ValidDeviceToken reports whether t has the shape of an APNs device token.

func ValidEndpoint added in v0.37.5

func ValidEndpoint(raw string) error

ValidEndpoint reports whether raw is an https URL on an accepted push service: no credentials, the default port, a named host (never an IP literal) on the list above.

Types

type APNs added in v0.38.11

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

APNs sends pushes to Apple. Safe for concurrent use.

func NewAPNs added in v0.38.11

func NewAPNs(cfg APNsConfig) (*APNs, error)

NewAPNs parses the key and returns a sender. The key is a PKCS#8 P-256 PEM (the .p8 file). It errors, naming nothing from the key, when the config is incomplete or the key is not usable.

func (*APNs) Send added in v0.38.11

func (a *APNs) Send(ctx context.Context, env, deviceToken, notificationID string) error

Send delivers one notification (by id) to one device. An *APNsError comes back for Apple's refusals; Dead() says the token should be removed. A 403 ExpiredProviderToken refreshes the provider token and retries once.

func (*APNs) SendLive added in v0.38.15

func (a *APNs) SendLive(ctx context.Context, env, activityToken, event string, st LiveState) error

SendLive delivers one Live Activity update (event LiveUpdate or LiveEnd) to one activity token. Routine updates go at low priority and the end at high, as Apple advises. An *APNsError comes back for Apple's refusals; Dead() says the token is gone.

func (*APNs) Topic added in v0.38.11

func (a *APNs) Topic() string

Topic is the bundle id every push is addressed to (and devices must register with).

type APNsConfig added in v0.38.11

type APNsConfig struct {
	KeyID  string // RIVOLT_APNS_KEY_ID
	TeamID string // RIVOLT_APNS_TEAM_ID
	KeyPEM string // RIVOLT_APNS_KEY: the .p8 file's text
	Topic  string // RIVOLT_APNS_TOPIC: the bundle id
}

APNsConfig is the server's Apple Push identity: the token-auth key (.p8) and the app's bundle id (the apns-topic). All four must be set for APNs to be on.

func (APNsConfig) Enabled added in v0.38.11

func (c APNsConfig) Enabled() bool

Enabled reports whether every field is set.

type APNsDevice added in v0.38.11

type APNsDevice struct {
	ID        uuid.UUID
	SessionID uuid.UUID
	Token     string
	Env       string
	BundleID  string
	Prefs     Preferences
	CreatedAt time.Time
	LastOKAt  *time.Time
	// InstallID is the random id the app keeps in its Keychain, sent on register. Only its hash is stored (install_hash);
	// the raw value is never kept, logged or exported. Empty from an older app.
	InstallID string
}

APNsDevice is an iPhone app install registered for push (push_apns_devices). Token is the raw APNs device token; it is needed to send, is never logged and is not part of the data export (ExportRow).

type APNsError added in v0.38.11

type APNsError struct {
	Status int
	Reason string
}

APNsError is a refusal from Apple. Reason is Apple's code ("BadDeviceToken", "Unregistered", …), never anything from our payload.

func (*APNsError) Dead added in v0.38.11

func (e *APNsError) Dead() bool

Dead reports that the device token will never work again: the row should go.

func (*APNsError) Error added in v0.38.11

func (e *APNsError) Error() string

type APNsExportRow added in v0.38.11

type APNsExportRow struct {
	BundleID  string      `json:"bundle_id"`
	Env       string      `json:"env"`
	CreatedAt time.Time   `json:"created_at"`
	LastOKAt  *time.Time  `json:"last_ok_at"`
	Prefs     Preferences `json:"prefs"`
}

APNsExportRow is a device as the data export lists it: no token.

type Activity added in v0.38.15

type Activity struct {
	ID       uuid.UUID
	Token    string
	Env      string
	BundleID string
}

Activity is a registered running Live Activity, with what a send needs.

type ErrDeviceElsewhere added in v0.39.4

type ErrDeviceElsewhere struct{ Reason string }

ErrDeviceElsewhere is a register refused because the token is registered to another account whose session is live, and the caller did not prove it is the same install (the install id matches). Reason says which: "install_mismatch" or "install_missing" (a fixed list, safe as a metric label).

func (ErrDeviceElsewhere) Error added in v0.39.4

func (e ErrDeviceElsewhere) Error() string

type Factory

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

Factory hands out per-user *Store instances. push.Store wraps (db, userID); the per-user split is for routing, not for per-user-VAPID — the VAPID keypair is global (push_vapid.id = 1) and any *Store can read it.

func NewFactory

func NewFactory(d *sql.DB) *Factory

NewFactory wraps the shared pool.

func NewFactoryWithSystem added in v0.39.7

func NewFactoryWithSystem(d, sys *sql.DB) *Factory

NewFactoryWithSystem is NewFactory for request handling under row-level security: d is the request pool and sys the system pool the stores use for the statements that must see other users' rows (APNs token registration).

func (*Factory) DB

func (f *Factory) DB() *sql.DB

DB exposes the shared pool. Used by main.go to load/generate the global VAPID keypair, which lives at push_vapid.id=1 and isn't scoped to any user.

func (*Factory) For

func (f *Factory) For(userID uuid.UUID) *Store

For returns the *Store for userID, constructing one on first sight.

type LiveSource added in v0.38.15

type LiveSource interface {
	Charging(uid uuid.UUID, rivianVehicleID string) (socPct, powerKW, minutesToTarget float64, ok bool)
}

LiveSource is where a charging truck's figures come from: the recorder's latest vehicle state. Charging reports the charge in progress (charge percent, power in kW, minutes to the charge target) and ok=false when the truck is not charging or the figures are stale.

type LiveSourceFunc added in v0.38.15

type LiveSourceFunc func(uid uuid.UUID, rivianVehicleID string) (float64, float64, float64, bool)

LiveSourceFunc adapts a function to LiveSource.

func (LiveSourceFunc) Charging added in v0.38.15

func (f LiveSourceFunc) Charging(uid uuid.UUID, rivianVehicleID string) (float64, float64, float64, bool)

Charging implements LiveSource.

type LiveState added in v0.38.15

type LiveState struct {
	SoCPct          int     `json:"soc_pct"`
	PowerKW         float64 `json:"power_kw"`
	MinutesToTarget int     `json:"minutes_to_target"`
}

LiveState is the whole content of a Live Activity update.

func NewLiveState added in v0.38.15

func NewLiveState(socPct, powerKW, minutesToTarget float64) LiveState

NewLiveState clamps the figures to what the lock screen can show: charge 0-100, power at least 0 with one decimal, minutes at least 0.

type NetworkConfirm added in v0.33.2

type NetworkConfirm struct {
	ChargeID string
	// Network is the detected network's display name ("" if unmatched).
	Network string
	// Other is a second, co-located network, if the evidence has one.
	Other string
	Cost  float64
	Rate  float64
	KWh   float64
	// Matched is false when nothing was within range (no price).
	Matched bool
	// State is "estimated" or "needs_confirmation".
	State string
}

NetworkConfirm is what the close hook knows about an auto-priced charge. Cost/Rate are zero when nothing matched.

type Notification added in v0.38.11

type Notification struct {
	ID        uuid.UUID `json:"id"`
	Kind      string    `json:"kind"`
	Title     string    `json:"title"`
	Body      string    `json:"body"`
	URL       string    `json:"url"`
	CreatedAt time.Time `json:"created_at"`
}

Notification is the text of a push, fetched by the app after Apple delivers the generic one.

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.

func LeftOpenPayload added in v0.38.26

func LeftOpenPayload(model string, kinds []string, minutes int) Payload

LeftOpenPayload is the left-open alert. The text is fixed apart from the vehicle's model (never the user's own nickname for it: it shows on the lock screen) and which closures are open (a door, the frunk, the liftgate, the tailgate, the tonneau). It names no place and says nothing about the truck being safe or unsafe: a missing alert is never a promise that nothing is open. The tap opens the app.

func NetworkConfirmPayload added in v0.33.2

func NetworkConfirmPayload(e NetworkConfirm) Payload

NetworkConfirmPayload builds the notification for e, using the contract's three wordings.

func PlugInReminderPayload added in v0.37.5

func PlugInReminderPayload(soc, threshold int) Payload

PlugInReminderPayload is the plug-in reminder: functional, SoC and the user's own threshold, no place and no promotion.

type Preferences

type Preferences struct {
	OnChargingDone   bool `json:"on_charging_done"`
	OnPlugInReminder bool `json:"on_plug_in_reminder"`
	OnAnomaly        bool `json:"on_anomaly"`
	OnSoftwareUpdate bool `json:"on_software_update"`
	OnMilestones     bool `json:"on_milestones"`
	OnLeftOpen       bool `json:"on_left_open"`
}

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) APNsEnabled added in v0.38.11

func (s *Service) APNsEnabled() bool

APNsEnabled reports whether the iPhone app's push is on.

func (*Service) APNsTopic added in v0.38.12

func (s *Service) APNsTopic() string

APNsTopic is the app bundle id notifications are sent for ("" when off).

func (*Service) DeliverLeftOpen added in v0.38.26

func (s *Service) DeliverLeftOpen(ctx context.Context, store *Store, model string, kinds []string, minutes int) (int, error)

DeliverLeftOpen sends the left-open alert to every device of the store's user that has it on, and waits. It reports how many sends succeeded; an error means none did.

func (*Service) DeliverMilestone added in v0.37.5

func (s *Service) DeliverMilestone(ctx context.Context, store *Store, title, body string) (int, error)

DeliverMilestone sends a milestone push to every subscription of the store's user that has milestones on, and waits. It reports how many sends succeeded; an error means none did.

func (*Service) DeliverPlugInReminder added in v0.37.5

func (s *Service) DeliverPlugInReminder(ctx context.Context, store *Store, soc, threshold int) (int, error)

DeliverPlugInReminder sends the plug-in reminder to every subscription of the store's user that has it on, and waits. It reports how many sends succeeded; an error means none did, or the subscriptions could not be read. Zero subscriptions with the reminder on is an error too: nothing was delivered.

func (*Service) EndLive added in v0.38.15

func (s *Service) EndLive(ctx context.Context, store *Store, vehicleID uuid.UUID, st LiveState) int

EndLive sends the final state with the end event to the vehicle's running activities and forgets them, whatever Apple answers (the charge is over).

func (*Service) EndLiveForCharge added in v0.38.15

func (s *Service) EndLiveForCharge(ctx context.Context, f *Factory, userID uuid.UUID, rivianVehicleID string, endSoCPct float64) int

EndLiveForCharge is the charge-closed hook: it sends the final state with the end event to the truck's running activities and forgets them. The figures are the charge's end percent, no power and no time left.

func (*Service) LiveEnabled added in v0.38.15

func (s *Service) LiveEnabled() bool

LiveEnabled reports whether Live Activity updates are sent.

func (*Service) NotifyAnomaly

func (s *Service) NotifyAnomaly(store *Store, title, body, deepLink string)

NotifyAnomaly fires when an anomaly is detected for the user. Scoped to the user via store.

func (*Service) NotifyChargingDone

func (s *Service) NotifyChargingDone(store *Store, sessionID, summary string)

NotifyChargingDone fires when a charge session reaches its terminal state. The provided store must be scoped to the owning user.

func (*Service) NotifyNetworkConfirm added in v0.33.2

func (s *Service) NotifyNetworkConfirm(store *Store, userKey string, e NetworkConfirm, pending int)

NotifyNetworkConfirm sends the confirm-or-change push for an auto-priced DC fast charge, rate limited per user. userKey scopes the limiter (the user id); pending is how many charges the user has open for confirmation, used in the collapsed summary. The limiter is per-process: with several pods a user can see up to confirmPerWindow per pod.

func (*Service) NotifySoftwareUpdate added in v0.36.10

func (s *Service) NotifySoftwareUpdate(store *Store, vehicle, version string, installed bool)

NotifySoftwareUpdate fires when a vehicle is offered a new software version (installed=false) or its installed version changed (installed=true). Scoped to the user via store. The tap opens the app: Rivian's release-notes link is a one-hour signed URL, so it is never put in a payload.

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) RunLive added in v0.38.15

func (s *Service) RunLive(ctx context.Context, db *sql.DB, f *Factory, src LiveSource, every time.Duration)

RunLive calls TickLive every `every` until ctx ends.

func (*Service) SendAPNsTest added in v0.38.14

func (s *Service) SendAPNsTest(ctx context.Context, store *Store) int

SendAPNsTest sends the user's own test notification to each of their signed-in iPhones, whatever its switches say, and returns how many Apple accepted. The words are fixed here, so nothing a caller sends reaches Apple or the phone.

func (*Service) SendTest

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

SendTest delivers a single test notification to one endpoint. The store must be scoped to the requesting user so we don't leak test pings across tenants.

func (*Service) TestLive added in v0.38.15

func (s *Service) TestLive(ctx context.Context, store *Store) int

TestLive sends fixed figures to every running activity of the user's (any vehicle, ignoring the update interval) and, after a short delay, the end. It returns how many activities the update reached; the end follows in the background. For trying the Live Activity where no real charge runs.

func (*Service) TickLive added in v0.38.15

func (s *Service) TickLive(ctx context.Context, db *sql.DB, f *Factory, src LiveSource) int

TickLive sends one round of routine updates: for each truck with a running activity that is charging now, the current figures go to its activities (each at most once per LiveMinInterval). It returns how many Apple accepted.

func (*Service) UpdateLive added in v0.38.15

func (s *Service) UpdateLive(ctx context.Context, store *Store, vehicleID uuid.UUID, st LiveState) int

UpdateLive sends a routine update to the user's running activities on the vehicle whose device switch is on, at most one per LiveMinInterval each, and returns how many Apple accepted. A dead token is forgotten.

func (*Service) WithAPNs added in v0.38.11

func (s *Service) WithAPNs(a *APNs) *Service

WithAPNs turns on delivery to the iPhone app's installs. Chainable; a nil sender leaves it off.

func (*Service) WithLive added in v0.38.15

func (s *Service) WithLive(on bool) *Service

WithLive turns on Live Activity updates (RIVOLT_APNS_LIVE). Off by default; it needs APNs to be on as well. Chainable.

type Store

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

Store wraps access to push_subscriptions + push_vapid. Scoped to a single user_id; the VAPID keypair is global (push_vapid.id = 1).

func NewServerStore

func NewServerStore(db *sql.DB) *Store

NewServerStore returns a *Store usable only for VAPID Get/Save. Subscription ops will fail (userID is zero); that's by design — VAPID is the only push state that's truly server-wide.

func OpenStore

func OpenStore(db *sql.DB, userID uuid.UUID) (*Store, error)

OpenStore binds a pooled connection to a user_id. The pool is owned by the caller.

func (*Store) APNsLive added in v0.38.15

func (s *Store) APNsLive(ctx context.Context, token string) (on, ok bool, err error)

APNsLive reports the Live Activity switch of one of the user's devices; ok is false when the token is not the user's.

func (*Store) APNsPrefs added in v0.38.12

func (s *Store) APNsPrefs(ctx context.Context, token string) (Preferences, bool, error)

APNsPrefs returns the five switches of one of the user's devices; ok is false when the token is not the user's.

func (*Store) ActivityCount added in v0.38.15

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

ActivityCount is how many activities the user has registered (tests, and the export's count if it ever wants one).

func (*Store) Close

func (s *Store) Close() error

Close is a no-op; the pool is managed by main.

func (*Store) Count

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

Count returns the number of stored subscriptions for this user.

func (*Store) CreateNotification added in v0.38.11

func (s *Store) CreateNotification(ctx context.Context, kind, title, body, url string) (uuid.UUID, error)

CreateNotification stores a notification's text for NotificationTTL and returns its random id. Only the known kinds are stored.

func (*Store) Delete

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

Delete removes a subscription. No-op if absent.

func (*Store) DeleteAPNsDevice added in v0.38.11

func (s *Store) DeleteAPNsDevice(ctx context.Context, token string) error

DeleteAPNsDevice removes one of the user's devices by token. No-op if absent.

func (*Store) EndActivity added in v0.38.15

func (s *Store) EndActivity(ctx context.Context, activityToken string) error

EndActivity forgets one of the user's activity tokens. No-op if absent.

func (*Store) ExportAPNsDevices added in v0.38.11

func (s *Store) ExportAPNsDevices(ctx context.Context) ([]APNsExportRow, error)

ExportAPNsDevices lists the user's devices for the data export, without tokens.

func (*Store) Get added in v0.37.2

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

Get returns the user's subscription for endpoint, or (nil, nil) when the endpoint is not one of theirs.

func (*Store) GetNotification added in v0.38.11

func (s *Store) GetNotification(ctx context.Context, id uuid.UUID) (Notification, error)

GetNotification returns one of the user's notifications that is under NotificationTTL old. Another user's id, an unknown id and an expired one are all ErrNotFound.

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. The keypair is shared across all users of this install.

func (*Store) List

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

List returns every subscription for this user, oldest first.

func (*Store) ListLiveAPNsDevices added in v0.38.11

func (s *Store) ListLiveAPNsDevices(ctx context.Context) ([]APNsDevice, error)

ListLiveAPNsDevices returns the user's devices whose app session is still live: not revoked and not expired. A phone that signed out stops receiving pushes whether or not it unregistered.

func (*Store) ListLiveActivities added in v0.38.15

func (s *Store) ListLiveActivities(ctx context.Context, vehicleID uuid.UUID) ([]Activity, error)

ListLiveActivities returns the user's running activities on one vehicle whose device has the Live Activity switch on and a live (not revoked, not expired) app session, and which are not older than ActivityTTL.

func (*Store) ListUserLiveActivities added in v0.38.15

func (s *Store) ListUserLiveActivities(ctx context.Context) ([]Activity, error)

ListUserLiveActivities is ListLiveActivities across all of the user's vehicles.

func (*Store) RegisterActivity added in v0.38.15

func (s *Store) RegisterActivity(ctx context.Context, deviceToken, activityToken string, vehicleID uuid.UUID) error

RegisterActivity records a running Live Activity's push token for one of the user's devices and vehicles. ErrNotFound when the device or vehicle is not the user's; ErrLiveOff when the device's switch is off. Idempotent: the same token again re-points it at the device and vehicle but keeps its start time.

func (*Store) SaveVAPID

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

SaveVAPID persists the keypair at id=1 (singleton row).

func (*Store) SetAPNsLeftOpen added in v0.38.26

func (s *Store) SetAPNsLeftOpen(ctx context.Context, token string, on bool) (bool, error)

SetAPNsLeftOpen sets the left-open switch (off until the user's own control turns it on) on one of the user's devices. Reports whether the token is the user's.

func (*Store) SetAPNsLive added in v0.38.15

func (s *Store) SetAPNsLive(ctx context.Context, token string, on bool) (bool, error)

SetAPNsLive sets the Live Activity switch of one of the user's devices. Turning it off also forgets the device's registered activities. Reports whether the token is the user's.

func (*Store) SetAPNsOptIns added in v0.38.11

func (s *Store) SetAPNsOptIns(ctx context.Context, token string, plugIn, milestones *bool) (bool, error)

SetAPNsOptIns sets the plug-in reminder and/or milestones switch (the two that start off) on one of the user's devices; nil leaves a switch alone.

func (*Store) SetAPNsPrefs added in v0.38.11

func (s *Store) SetAPNsPrefs(ctx context.Context, token string, chargingDone, anomaly, softwareUpdate *bool) (bool, error)

SetAPNsPrefs sets the three always-available switches on one of the user's devices; nil leaves a switch alone. Reports whether the token is the user's.

func (*Store) SetLeftOpen added in v0.38.26

func (s *Store) SetLeftOpen(ctx context.Context, endpoint string, on bool) (bool, error)

SetLeftOpen sets the left-open switch (off until the user's own control turns it on) on one of the user's subscriptions. Reports whether the endpoint is the user's.

func (*Store) SetOptIns added in v0.37.2

func (s *Store) SetOptIns(ctx context.Context, endpoint string, plugIn, milestones *bool) (bool, error)

SetOptIns sets the plug-in reminder and/or milestones switch on one of the user's subscriptions (nil leaves a switch alone) and reports whether the endpoint is the user's.

func (*Store) SwitchOffAll added in v0.37.2

func (s *Store) SwitchOffAll(ctx context.Context, kind string) (int64, error)

SwitchOffAll turns one opt-in kind (plug_in_reminder, milestones or left_open) off on every one of the user's subscriptions and reports how many changed.

func (*Store) Upsert

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

Upsert inserts or updates a subscription keyed on (user_id, endpoint). It never sets the two opt-in switches (plug-in reminder, milestones) on: a new row starts with both off and a re-subscribe leaves what is stored. Only SetOptIns, reached from the Settings screen's own control, turns them on, so a stale tab or an old pod posting its local defaults cannot opt anyone in.

func (*Store) UpsertAPNsDevice added in v0.38.11

func (s *Store) UpsertAPNsDevice(ctx context.Context, sessionID uuid.UUID, d APNsDevice) error

UpsertAPNsDevice registers (or refreshes) a device for the app session it signed in with. A new row starts with the web's defaults: charging done, anomaly and software update on, plug-in reminder and milestones off. Re-registering keeps what is stored for every switch (only SetAPNsOptIns/SetAPNsPrefs change them), so a stale app posting its local defaults cannot opt anyone in.

type Subscription

type Subscription struct {
	Endpoint         string
	P256dh           string
	Auth             string
	OnChargingDone   bool
	OnPlugInReminder bool
	OnAnomaly        bool
	OnSoftwareUpdate bool
	// OnMilestones is the opt-in push for milestone moments (default off).
	OnMilestones bool
	// OnLeftOpen is the opt-in push for a door, the frunk, the liftgate, the tailgate or the tonneau left open (default off).
	OnLeftOpen 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