11# ESPWebPush
22
3- ESPWebPush is an ** async-first** Web Push sender for ESP32 firmware. It handles VAPID JWT signing, Web Push AES-GCM payload encryption, and HTTP delivery so your devices can notify browsers without extra glue code.
3+ ESPWebPush is an async-first Web Push sender for ESP32 firmware. It handles VAPID JWT signing, RFC 8291 ` aes128gcm ` payload encryption, and HTTP delivery so devices can notify browsers without custom glue code.
44
5- ArduinoJson v7+ is a required dependency for the structured payload API.
5+ ArduinoJson v7+ is required for the structured payload API.
66
77## CI / Release / License
88[ ![ CI] ( https://github.com/ESPToolKit/esp-webPush/actions/workflows/ci.yml/badge.svg )] ( https://github.com/ESPToolKit/esp-webPush/actions/workflows/ci.yml )
99[ ![ Release] ( https://img.shields.io/github/v/release/ESPToolKit/esp-webPush?sort=semver )] ( https://github.com/ESPToolKit/esp-webPush/releases )
10- [ ![ License: MIT] ( https://img.shields.io/badge/License-MIT-yellow.svg )] ( LICENSE.md )
10+ [ ![ License: MIT] ( https://img.shields.io/github/license/ESPToolKit/esp-webPush )] ( LICENSE.md )
1111
1212## Features
13- - VAPID JWT signing (ES256) from base64url private key .
14- - Web Push AES-GCM payload encryption.
13+ - RFC 8292 VAPID JWT signing with ` mailto: ` or ` https:// ` subjects .
14+ - RFC 8291 / RFC 8188 ` aes128gcm ` Web Push encryption.
1515- Async queue + worker task via native FreeRTOS APIs.
16- - Optional synchronous ` send() ` API.
16+ - Bounded shutdown via ` requestStop() ` , ` join(timeoutMs) ` , and ` deinit(timeoutMs) ` .
17+ - Sync ` send() ` API plus async ` send() ` overloads that return ` WebPushEnqueueResult ` .
1718- Strict ` PushPayload ` validation for browser notification fields.
18- - ArduinoJson v7+ overloads for validated ` JsonDocument ` / ` JsonVariantConst ` payloads.
19- - Configurable queue length, memory caps (internal vs PSRAM), stack, priority, retries, and timeouts.
20- - Optional application-provided network validator callback.
21- - Uses the standard Web Push headers (` Authorization ` , ` Crypto-Key ` , ` Encryption ` , ` TTL ` ).
19+ - Payload-size guard with the RFC-safe default limit of 3993 bytes.
20+ - Small per-origin JWT cache to avoid re-signing every message.
21+ - Configurable queue length, memory caps, retries, timeouts, and worker task settings.
2222
2323## Quick Start
2424
@@ -31,33 +31,35 @@ ESPWebPush webPush;
3131void setup () {
3232 Serial.begin(115200);
3333
34+ WebPushVapidConfig vapid;
35+ vapid.subject = "mailto:notify@example.com";
36+ vapid.publicKeyBase64 = "BAvapidPublicKeyBase64Url...";
37+ vapid.privateKeyBase64 = "vapidPrivateKeyBase64Url...";
38+
3439 WebPushConfig cfg;
3540 cfg.queueLength = 16;
3641 cfg.queueMemory = WebPushQueueMemory::Psram;
3742 cfg.worker.stackSizeBytes = 16 * 1024;
3843 cfg.worker.priority = 3;
3944 cfg.worker.name = "webpush";
45+ cfg.maxPayloadBytes = 3993;
4046 cfg.networkValidator = []() { return true; };
4147
42- webPush.init(
43- "notify@example.com",
44- "BAvapidPublicKeyBase64Url...",
45- "vapidPrivateKeyBase64Url...",
46- cfg);
48+ webPush.init(vapid, cfg);
4749}
4850
4951void loop () {}
5052```
5153
5254## Usage
5355
54- ### Subscription / Structured Payload
56+ ### WebPushSubscription / Structured Payload
5557
5658``` cpp
57- Subscription sub ;
58- sub .endpoint = " https://fcm.googleapis.com/fcm/send/..." ;
59- sub .p256dh = " BME..." ; // base64url from browser subscription
60- sub .auth = " nsa..." ; // base64url from browser subscription
59+ WebPushSubscription subscription ;
60+ subscription .endpoint = " https://fcm.googleapis.com/fcm/send/..." ;
61+ subscription .p256dh = " BME..." ;
62+ subscription .auth = " nsa..." ;
6163
6264PushPayload payload;
6365payload.title = " Hello" ;
@@ -69,7 +71,7 @@ payload.icon = "https://example.com/icon.png";
6971### Async Send
7072
7173``` cpp
72- bool started = webPush.send(sub , payload, [](WebPushResult result) {
74+ WebPushEnqueueResult enqueue = webPush.send(subscription , payload, [](WebPushResult result) {
7375 if (!result.ok()) {
7476 ESP_LOGE ("WEBPUSH", "Push failed: %s (status %d)",
7577 result.message, result.statusCode);
@@ -78,11 +80,13 @@ bool started = webPush.send(sub, payload, [](WebPushResult result) {
7880 ESP_LOGI("WEBPUSH", "Push OK (status %d)", result.statusCode);
7981});
8082
81- if (!started ) {
82- ESP_LOGW ("WEBPUSH", "Queue full or not initialized" );
83+ if (!enqueue.queued() ) {
84+ ESP_LOGW ("WEBPUSH", "Enqueue failed: %s", enqueue.message );
8385}
8486```
8587
88+ Async preflight failures are returned through ` WebPushEnqueueResult ` . The callback only runs for messages that were actually queued.
89+
8690### ArduinoJson v7+ Send
8791
8892``` cpp
@@ -91,13 +95,13 @@ doc["title"] = "Hello";
9195doc[" body" ] = " ESP32" ;
9296doc[" tag" ] = " demo" ;
9397
94- WebPushResult result = webPush.send(sub , doc);
98+ WebPushResult result = webPush.send(subscription , doc);
9599```
96100
97101### Sync Send
98102
99103``` cpp
100- WebPushResult result = webPush.send(sub , payload);
104+ WebPushResult result = webPush.send(subscription , payload);
101105if (!result.ok()) {
102106 ESP_LOGW ("WEBPUSH", "Sync push failed: %s", result.message);
103107}
@@ -107,7 +111,7 @@ if (!result.ok()) {
107111
108112``` cpp
109113PushMessage msg;
110- msg.sub = sub ;
114+ msg.subscription = subscription ;
111115msg.payload = " {\" title\" :\" Hello\" ,\" body\" :\" ESP32\" }" ;
112116
113117// Raw payload strings remain supported, but they are not schema-validated.
@@ -118,60 +122,73 @@ WebPushResult result = webPush.send(msg);
118122
119123``` cpp
120124if (webPush.isInitialized()) {
121- webPush.deinit();
125+ WebPushJoinStatus stopStatus = webPush.deinit();
126+ if (stopStatus == WebPushJoinStatus::Timeout) {
127+ ESP_LOGW ("WEBPUSH", "Worker did not stop within the timeout");
128+ }
122129}
123130```
124131
132+ ` requestStop() ` marks shutdown and wakes the worker without blocking. ` join(timeoutMs) ` waits for the worker to exit and finalizes shutdown when the stop completes in time. ` deinit(timeoutMs) ` is the convenience wrapper that performs both in one call.
133+
125134## Configuration
126135
127136` WebPushConfig ` lets you tune the worker and queue:
128137
129- - ` queueLength ` – number of queued messages.
130- - ` queueMemory ` – ` Internal ` , ` Psram ` , or ` Any ` .
131- - ` worker ` – stack size, priority, core id, PSRAM stack usage.
132- - ` requestTimeoutMs ` – HTTP timeout.
133- - ` ttlSeconds ` – Web Push TTL header.
134- - ` maxRetries ` , ` retryBaseDelayMs ` , ` retryMaxDelayMs ` – retry/backoff controls.
135- - ` networkValidator ` – optional callback for application-defined network readiness checks.
138+ - ` queueLength ` - number of queued messages.
139+ - ` queueMemory ` - ` Internal ` , ` Psram ` , or ` Any ` .
140+ - ` worker ` - stack size, priority, core id, and task name.
141+ - ` requestTimeoutMs ` - HTTP timeout.
142+ - ` ttlSeconds ` - Web Push TTL header.
143+ - ` maxRetries ` , ` retryBaseDelayMs ` , ` retryMaxDelayMs ` - retry/backoff controls.
144+ - ` maxPayloadBytes ` - plaintext payload size guard. The default is 3993 bytes; use ` 0 ` to disable.
145+ - ` networkValidator ` - optional callback for application-defined network readiness checks.
136146
137147## Gotchas
138- - ** System time is required** for VAPID JWT expiration. Ensure SNTP is synced.
139- - Web Push endpoints require TLS; ` esp_http_client ` must be built with TLS support.
140- - ` aesgcm ` content encoding is used to match existing Web Push payloads.
141- - Structured payload inputs reject unknown top-level keys and invalid field types.
142-
143- ## API Reference (Core)
144-
145- - ` bool init(contactEmail, publicKeyBase64, privateKeyBase64, config) `
146- - ` bool send(const PushMessage&, WebPushResultCB cb) ` (async)
147- - ` WebPushResult send(const PushMessage&) ` (sync)
148- - ` bool send(const Subscription&, const PushPayload&, WebPushResultCB cb) ` / ` WebPushResult send(const Subscription&, const PushPayload&) `
149- - ` bool send(const Subscription&, const JsonDocument&, WebPushResultCB cb) ` / ` WebPushResult send(const Subscription&, const JsonDocument&) `
150- - ` bool send(const Subscription&, JsonVariantConst, WebPushResultCB cb) ` / ` WebPushResult send(const Subscription&, JsonVariantConst) `
148+ - System time is required for VAPID JWT expiration.
149+ - Web Push endpoints require TLS-capable ` esp_http_client ` .
150+ - Only ` aes128gcm ` is generated. Legacy ` aesgcm ` is intentionally not supported in v2.
151+ - ` subject ` must start with ` mailto: ` or ` https:// ` .
152+ - The configured VAPID public key must match the private key.
153+
154+ ## API Reference
155+
156+ - ` bool init(const WebPushVapidConfig&, const WebPushConfig& = {}) `
157+ - ` WebPushEnqueueResult send(const PushMessage&, WebPushResultCB cb) `
158+ - ` WebPushResult send(const PushMessage&) `
159+ - ` WebPushEnqueueResult send(const WebPushSubscription&, const PushPayload&, WebPushResultCB cb) `
160+ - ` WebPushResult send(const WebPushSubscription&, const PushPayload&) `
161+ - ` WebPushEnqueueResult send(const WebPushSubscription&, const JsonDocument&, WebPushResultCB cb) `
162+ - ` WebPushResult send(const WebPushSubscription&, const JsonDocument&) `
163+ - ` WebPushEnqueueResult send(const WebPushSubscription&, JsonVariantConst, WebPushResultCB cb) `
164+ - ` WebPushResult send(const WebPushSubscription&, JsonVariantConst) `
165+ - ` void requestStop() `
166+ - ` WebPushJoinStatus join(uint32_t timeoutMs) `
151167- ` void setNetworkValidator(WebPushNetworkValidator) `
152- - ` void deinit()` / ` bool isInitialized() const `
153- - ` const char* errorToString(WebPushError) `
168+ - ` WebPushJoinStatus deinit(uint32_t timeoutMs = 10000 )` / ` bool isInitialized() const `
169+ - ` const char * errorToString(WebPushError) `
154170
155- ## Restrictions
156- - ESP32-class targets only (Arduino + ESP-IDF).
171+ ## Compatibility
172+ - ESP32-class targets only.
173+ - Arduino and ESP-IDF frameworks are supported.
157174- Requires C++17, ArduinoJson v7+, and mbedTLS.
158175- Do not call from ISR context.
159176
160177## Tests
161- Host-side tests are disabled. Use the ` examples/ ` sketches with PlatformIO or Arduino CLI.
178+ - On-device Unity tests live in ` test/test_esp_webPush ` .
179+ - CI builds Arduino examples and includes an ESP-IDF compile smoke build.
162180
163181## Formatting Baseline
164182
165183This repository follows the firmware formatting baseline from ` esptoolkit-template ` :
166184- ` .clang-format ` is the source of truth for C/C++/INO layout.
167185- ` .editorconfig ` enforces tabs (` tab_width = 4 ` ), LF endings, and final newline.
168- - Format all tracked firmware sources with ` bash scripts/format_cpp.sh ` .
186+ - Format tracked firmware sources with ` bash scripts/format_cpp.sh ` .
169187
170188## License
171- MIT — see [ LICENSE.md] ( LICENSE.md ) .
189+ MIT - see [ LICENSE.md] ( LICENSE.md ) .
172190
173191## ESPToolKit
174192- Check out other libraries: < https://github.com/orgs/ESPToolKit/repositories >
175- - Hang out on Discord: < https://discord.gg/WG8sSqAy >
176193- Support the project: < https://ko-fi.com/esptoolkit >
177194- Visit the website: < https://www.esptoolkit.hu/ >
0 commit comments