Documentation
¶
Overview ¶
Package webpushpush sends encrypted browser notifications using Web Push, RFC 8291 encryption and RFC 8292 VAPID authentication. It implements arkoren.dev/core/service/push.Driver without lifecycle I/O or retries.
Construct a driver with the application's persistent key.Signing identity and contact URI, then pass it to push.New:
identity, err := key.LoadSigning("vapid-private.pem")
if err != nil {
return err
}
driver, err := webpushpush.New(identity, "mailto:push@example.com")
if err != nil {
return err
}
_ = push.New(driver)
The plaintext is flat JSON: {"title":"...","body":"...","data":{...}}. Empty body and data are omitted. Encoded plaintext may contain at most 3993 bytes, including JSON syntax and escaping, for a 4096-byte encrypted record. TTL is rounded down to whole seconds; zero explicitly forbids storage. Only HTTP 201 confirms acceptance, not display or delivery. Receipt IDs are empty: capability URLs in provider headers are neither returned nor followed. Unexpected responses never trigger a retry.
Defaults permit Google's fcm.googleapis.com, Mozilla's updates.push.services.mozilla.com, and subdomains of push.apple.com. AllowedOrigins replaces these defaults with exact HTTPS origins. All connections still require public DNS addresses, verified TLS and port 443. Environment proxies, redirects, connection reuse and transparent retries are disabled. An operation has a finite ten-second default timeout.
VAPID authorizations are reused per origin until five minutes before their twelve-hour expiration, including concurrent sends. At most 256 origins with unexpired authorizations are retained; additional origins fail closed. Reuse a long-lived driver rather than constructing one for each send. Authentication state is not shared across processes or driver instances. Keep constructor pointers instead of copying synchronized drivers. Formatting the pointer emits a fixed redaction marker for owned credentials without traversing caller objects. Error text is bounded, but explicitly unwrapped native causes retain identity and may contain confidential transport data. Applications own subscription authorization, persistence, service workers, visible notification display and reconciliation of uncertain outcomes.
Index ¶
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Builder ¶
type Builder struct {
// contains filtered or unexported fields
}
Builder holds construction-only settings. New detaches its owned slices, so retaining a draft cannot change a published driver.
Example:
_, err := webpushpush.New(identity, "mailto:push@example.com", func(connection *webpushpush.Builder) {
connection.Timeout(5 * time.Second)
})
_ = err
func (*Builder) AllowedOrigins ¶
AllowedOrigins replaces the curated provider policy with exact HTTPS origins, without paths, queries or wildcards. Empty input denies all. This is trusted deployment configuration, never subscription input. Addresses resolved at connection time must still be public.
Example:
_, err := webpushpush.New(identity, "mailto:push@example.com", func(connection *webpushpush.Builder) {
connection.AllowedOrigins("https://fcm.googleapis.com")
})
_ = err
func (*Builder) Timeout ¶
Timeout bounds the complete send. Zero restores ten seconds; negative values fail New. A shorter caller context deadline always wins.
Example:
_, err := webpushpush.New(identity, "mailto:push@example.com", func(connection *webpushpush.Builder) {
connection.Timeout(3 * time.Second)
})
_ = err
type BuilderFunc ¶
type BuilderFunc = func(*Builder)
BuilderFunc edits one fresh construction draft, synchronously and in order. Nil callbacks panic; empty callback slices leave defaults unchanged.
Example:
var policy webpushpush.BuilderFunc = func(connection *webpushpush.Builder) {
connection.Timeout(time.Second)
}
_, err := webpushpush.New(identity, "mailto:push@example.com", policy)
_ = err
type Driver ¶
type Driver struct {
// contains filtered or unexported fields
}
Driver sends one encrypted request per Send and never retries a request. Its private credentials and settings are detached from construction inputs; concurrent calls share a synchronized per-origin authorization cache. Keep New's pointer: the mutex must not be copied. Pointer formatting emits a fixed redaction marker without traversing owned credentials or caller objects.
Example:
driver, err := webpushpush.New(identity, "mailto:push@example.com")
if err != nil {
return err
}
_, err = driver.Send(ctx, push.NewMessage(subscription, "Ready"), push.SendConfig{})
return err
func New ¶
New validates configuration and snapshots key material without network I/O. identity must be a validated key.Signing value; contact must be a mailto address or an HTTPS URI. Invalid settings return a redacted error. Callbacks run once in order even for invalid required inputs. The returned driver is safe for concurrent use and requires no shutdown operation.
Example:
identity, err := key.LoadSigning("vapid-private.pem")
if err != nil {
return err
}
driver, err := webpushpush.New(identity, "mailto:push@example.com")
if err != nil {
return err
}
_ = driver
Example ¶
ExampleNew constructs a ready sender without network I/O. Production applications load a persistent key matching their browser subscriptions.
package main
import (
"fmt"
"time"
"arkoren.dev/core/service/push"
"arkoren.dev/driver/webpush/webpushpush"
"arkoren.dev/foundation/key"
)
func main() {
identity, err := key.GenerateSigning()
if err != nil {
fmt.Println("key generation failed")
return
}
driver, err := webpushpush.New(identity, "mailto:push@example.com", func(connection *webpushpush.Builder) {
connection.Timeout(5 * time.Second)
})
if err != nil {
fmt.Println("configuration failed")
return
}
sender := push.New(driver)
fmt.Println(sender != nil)
}
Output: true
func (*Driver) Format ¶
Format redacts the owned signing scalar and cached authorizations without traversing cache state, transports or caller objects. Keep the constructor's pointer: Driver contains a mutex and must not be copied.
Example:
text := fmt.Sprintf("%#v", driver)
_ = text // [REDACTED Web Push driver]
func (*Driver) Send ¶
func (driver *Driver) Send(ctx context.Context, message push.Message, config push.SendConfig) (push.Receipt, error)
Send validates the message, endpoint policy, actual encoded size and TTL before dispatch. Only value push.Subscription recipients are supported. TTL is truncated to seconds and must not exceed 2147483647 seconds. Failures return a zero receipt. A 201 response returns an empty receipt ID even if optional headers are absent or cleanup fails. Transport errors conservatively wrap push.ErrOutcomeUnknown and preserve native/context error identity. Top-level diagnostics never contain endpoint URLs, keys or payload text.
Example:
message := push.NewMessage(subscription, "Your export is ready")
_, err := driver.Send(ctx, message, push.SendConfig{TTL: time.Hour})
if errors.Is(err, push.ErrNotFound) {
invalidateSubmittedVersion()
}
return err
type ProviderError ¶
type ProviderError struct {
// StatusCode is the authoritative HTTP response code, not a delivery result.
// Example: if rejected.StatusCode == 410 { invalidateSubmittedVersion() }
StatusCode int
// RetryAfter is the nonnegative provider delay, or zero when absent,
// invalid, in the past or too large for time.Duration. No retry is made.
// Example: nextAttemptAt := time.Now().Add(rejected.RetryAfter)
RetryAfter time.Duration
// contains filtered or unexported fields
}
ProviderError records a rejected provider response without retaining its body, endpoint, headers or capability URL. Errors.As exposes safe metadata; errors.Is follows its portable push classification through Unwrap.
Example:
var rejected *webpushpush.ProviderError
if errors.As(err, &rejected) {
log.Printf("push rejected: status=%d retry_after=%s", rejected.StatusCode, rejected.RetryAfter)
}
func (*ProviderError) Error ¶
func (rejected *ProviderError) Error() string
Error produces safe diagnostic text containing only the numeric status. It never includes provider-controlled body text or endpoint capabilities.
Example:
var rejected *webpushpush.ProviderError
if errors.As(err, &rejected) { log.Print(rejected.Error()) }
func (*ProviderError) Unwrap ¶
func (rejected *ProviderError) Unwrap() error
Unwrap returns the portable push classification for errors.Is checks, or nil when no contract sentinel describes the rejection. Authentication failures, redirects and other unclassified rejections remain typed provider errors; they do not mean the recipient is unsupported or its subscription expired.
Example:
if errors.Is(err, push.ErrNotFound) { invalidateSubmittedVersion() }