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
- Variables
- func CandidateOther(primary string, names []string) string
- func EndpointHost(raw string) string
- func JoinClosures(kinds []string) string
- func PurgeActivities(ctx context.Context, db *sql.DB) (int64, error)
- func PurgeNotifications(ctx context.Context, db *sql.DB) (int64, error)
- func SafeNotificationURL(u string) string
- func ValidActivityToken(t string) bool
- func ValidDeviceToken(t string) bool
- func ValidEndpoint(raw string) error
- type APNs
- type APNsConfig
- type APNsDevice
- type APNsError
- type APNsExportRow
- type Activity
- type ErrDeviceElsewhere
- type Factory
- type LiveSource
- type LiveSourceFunc
- type LiveState
- type NetworkConfirm
- type Notification
- type Payload
- type Preferences
- type Sender
- type Service
- func (s *Service) APNsEnabled() bool
- func (s *Service) APNsTopic() string
- func (s *Service) DeliverLeftOpen(ctx context.Context, store *Store, model string, kinds []string, minutes int) (int, error)
- func (s *Service) DeliverMilestone(ctx context.Context, store *Store, title, body string) (int, error)
- func (s *Service) DeliverPlugInReminder(ctx context.Context, store *Store, soc, threshold int) (int, error)
- func (s *Service) EndLive(ctx context.Context, store *Store, vehicleID uuid.UUID, st LiveState) int
- func (s *Service) EndLiveForCharge(ctx context.Context, f *Factory, userID uuid.UUID, rivianVehicleID string, ...) int
- func (s *Service) LiveEnabled() bool
- func (s *Service) NotifyAnomaly(store *Store, title, body, deepLink string)
- func (s *Service) NotifyChargingDone(store *Store, sessionID, summary string)
- func (s *Service) NotifyNetworkConfirm(store *Store, userKey string, e NetworkConfirm, pending int)
- func (s *Service) NotifySoftwareUpdate(store *Store, vehicle, version string, installed bool)
- func (s *Service) PublicKey() string
- func (s *Service) RunLive(ctx context.Context, db *sql.DB, f *Factory, src LiveSource, ...)
- func (s *Service) SendAPNsTest(ctx context.Context, store *Store) int
- func (s *Service) SendTest(ctx context.Context, store *Store, endpoint string) error
- func (s *Service) TestLive(ctx context.Context, store *Store) int
- func (s *Service) TickLive(ctx context.Context, db *sql.DB, f *Factory, src LiveSource) int
- func (s *Service) UpdateLive(ctx context.Context, store *Store, vehicleID uuid.UUID, st LiveState) int
- func (s *Service) WithAPNs(a *APNs) *Service
- func (s *Service) WithLive(on bool) *Service
- type Store
- func (s *Store) APNsLive(ctx context.Context, token string) (on, ok bool, err error)
- func (s *Store) APNsPrefs(ctx context.Context, token string) (Preferences, bool, error)
- func (s *Store) ActivityCount(ctx context.Context) (int, error)
- func (s *Store) Close() error
- func (s *Store) Count(ctx context.Context) (int, error)
- func (s *Store) CreateNotification(ctx context.Context, kind, title, body, url string) (uuid.UUID, error)
- func (s *Store) Delete(ctx context.Context, endpoint string) error
- func (s *Store) DeleteAPNsDevice(ctx context.Context, token string) error
- func (s *Store) EndActivity(ctx context.Context, activityToken string) error
- func (s *Store) ExportAPNsDevices(ctx context.Context) ([]APNsExportRow, error)
- func (s *Store) Get(ctx context.Context, endpoint string) (*Subscription, error)
- func (s *Store) GetNotification(ctx context.Context, id uuid.UUID) (Notification, error)
- func (s *Store) GetVAPID(ctx context.Context) (*VAPID, error)
- func (s *Store) List(ctx context.Context) ([]Subscription, error)
- func (s *Store) ListLiveAPNsDevices(ctx context.Context) ([]APNsDevice, error)
- func (s *Store) ListLiveActivities(ctx context.Context, vehicleID uuid.UUID) ([]Activity, error)
- func (s *Store) ListUserLiveActivities(ctx context.Context) ([]Activity, error)
- func (s *Store) RegisterActivity(ctx context.Context, deviceToken, activityToken string, vehicleID uuid.UUID) error
- func (s *Store) SaveVAPID(ctx context.Context, v VAPID) error
- func (s *Store) SetAPNsLeftOpen(ctx context.Context, token string, on bool) (bool, error)
- func (s *Store) SetAPNsLive(ctx context.Context, token string, on bool) (bool, error)
- func (s *Store) SetAPNsOptIns(ctx context.Context, token string, plugIn, milestones *bool) (bool, error)
- func (s *Store) SetAPNsPrefs(ctx context.Context, token string, chargingDone, anomaly, softwareUpdate *bool) (bool, error)
- func (s *Store) SetLeftOpen(ctx context.Context, endpoint string, on bool) (bool, error)
- func (s *Store) SetOptIns(ctx context.Context, endpoint string, plugIn, milestones *bool) (bool, error)
- func (s *Store) SwitchOffAll(ctx context.Context, kind string) (int64, error)
- func (s *Store) Upsert(ctx context.Context, sub Subscription) error
- func (s *Store) UpsertAPNsDevice(ctx context.Context, sessionID uuid.UUID, d APNsDevice) error
- type Subscription
- type VAPID
Constants ¶
const ( EnvSandbox = "sandbox" EnvProduction = "production" )
Device environments.
const ( LiveUpdate = "update" LiveEnd = "end" )
Live Activity events.
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.
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.
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.
const NotificationTTL = 24 * time.Hour
NotificationTTL is how long a notification's text is kept for the app to fetch.
Variables ¶
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.
var ErrLiveOff = errors.New("push: live charging is off for this device")
ErrLiveOff is "this iPhone's Live Activity switch is off".
var ErrNotFound = errors.New("push: not found")
ErrNotFound is "no such notification (for this user, still fresh)".
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
CandidateOther picks the second distinct network from detection evidence candidates (names in nearest-first order); "" when none.
func EndpointHost ¶ added in v0.40.5
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
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
PurgeActivities deletes every registration older than ActivityTTL (all users). Run hourly.
func PurgeNotifications ¶ added in v0.38.11
PurgeNotifications deletes every notification past NotificationTTL (all users). Run hourly.
func SafeNotificationURL ¶ added in v0.38.11
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
ValidActivityToken reports whether t has the shape of an activity push token.
func ValidDeviceToken ¶ added in v0.38.11
ValidDeviceToken reports whether t has the shape of an APNs device token.
func ValidEndpoint ¶ added in v0.37.5
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
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.
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
APNsError is a refusal from Apple. Reason is Apple's code ("BadDeviceToken", "Unregistered", …), never anything from our payload.
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
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 NewFactoryWithSystem ¶ added in v0.39.7
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).
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
LiveSourceFunc adapts a function to 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
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
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
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 ¶
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
APNsEnabled reports whether the iPhone app's push is on.
func (*Service) APNsTopic ¶ added in v0.38.12
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
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
LiveEnabled reports whether Live Activity updates are sent.
func (*Service) NotifyAnomaly ¶
NotifyAnomaly fires when an anomaly is detected for the user. Scoped to the user via store.
func (*Service) NotifyChargingDone ¶
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
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 ¶
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
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 ¶
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
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
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.
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 ¶
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 (*Store) APNsLive ¶ added in v0.38.15
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
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
ActivityCount is how many activities the user has registered (tests, and the export's count if it ever wants one).
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) DeleteAPNsDevice ¶ added in v0.38.11
DeleteAPNsDevice removes one of the user's devices by token. No-op if absent.
func (*Store) EndActivity ¶ added in v0.38.15
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
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
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 ¶
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
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
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) SetAPNsLeftOpen ¶ added in v0.38.26
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
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
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
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
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 ¶
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.