完整接入指南:從 VAPID 訂閱到服務(wù)端下發(fā))
前端Web框架WebAssembly【免費(fèi)下載鏈接】go-appA package to build progressive web apps with Go programming language and WebAssembly.項(xiàng)目地址https://gitcode.com/gh_mirrors/go/go-app點(diǎn)擊查看免費(fèi)下載導(dǎo)讀推送通知Push Notifications是由遠(yuǎn)程服務(wù)器主動(dòng)發(fā)送、無(wú)論應(yīng)用處于運(yùn)行還是關(guān)閉狀態(tài)都能展示的系統(tǒng)級(jí)通知。在 go-app 構(gòu)建的 PWA漸進(jìn)式 Web 應(yīng)用中接入推送通知需要完成獲取訂閱 → 注冊(cè)到推送服務(wù)器 → 服務(wù)端下發(fā) → 客戶(hù)端展示與跳轉(zhuǎn)四個(gè)環(huán)節(jié)。讀完本文你將掌握基于 Context.Notifications() 的訂閱 API、VAPID 公鑰配置、HTTP 服務(wù)端注冊(cè)流程以及基于 webpush-go 的服務(wù)端下發(fā)實(shí)現(xiàn)并理解 go-app 內(nèi)置 Service Worker 對(duì)推送消息和點(diǎn)擊跳轉(zhuǎn)的處理原理。前置知識(shí)本文以 go-app 的通知權(quán)限模型為前提建議先閱讀 通知啟用與本地通知 一文掌握Permission()、RequestPermission()與Notification.Path的用法。一、推送通知的核心流程推送通知與本地通知Local Notification最大的區(qū)別在于本地通知由應(yīng)用自身在瀏覽器中創(chuàng)建并展示NotificationService.New而推送通知由遠(yuǎn)程服務(wù)器發(fā)起即便應(yīng)用關(guān)閉也能送達(dá)。go-app 的推送鏈路共四步獲取訂閱客戶(hù)端調(diào)用Context.Notifications().Subscribe(VAPIDPublicKey)向?yàn)g覽器的 Push API 申請(qǐng)一個(gè)攜帶加密密鑰的訂閱對(duì)象NotificationSubscription注冊(cè)訂閱客戶(hù)端把訂閱對(duì)象以 JSON 形式 POST 到自建的推送服務(wù)器服務(wù)器持久化存儲(chǔ)服務(wù)端下發(fā)推送服務(wù)器通過(guò) Web Push 協(xié)議如 webpush-go 庫(kù)向各訂閱端點(diǎn)發(fā)送 JSON 編碼的 Notification 結(jié)構(gòu)客戶(hù)端展示go-app 內(nèi)置的 Service Worker 監(jiān)聽(tīng)push事件解析消息并調(diào)用registration.showNotification展示用戶(hù)點(diǎn)擊通知后自動(dòng)導(dǎo)航到Notification.Path指定的頁(yè)面。從源碼結(jié)構(gòu)看訂閱與展示的關(guān)鍵實(shí)現(xiàn)分別位于 pkg/app/notification.goGo 側(cè) API與 pkg/app/gen/app.js瀏覽器側(cè) JS 橋接下文將逐環(huán)節(jié)展開(kāi)。二、獲取通知訂閱客戶(hù)端2.1 Subscribe API 與 VAPID 公鑰推送訂閱通過(guò) Context.Notifications().Subscribe() 獲取它需要傳入VAPID 公鑰VAPID Public Key。VAPIDVoluntary Application Server Identification是 Web Push 協(xié)議中用于標(biāo)識(shí)應(yīng)用服務(wù)器身份的機(jī)制公鑰用于瀏覽器端訂閱加密私鑰留在服務(wù)器端用于簽名下發(fā)請(qǐng)求。func (f *foo) enableNotifications(ctx app.Context, e app.Event) { f.notificationPermission ctx.Notifications().RequestPermission() if f.notificationPermission app.NotificationGranted { f.registerNotificationSubscription(ctx) } } func (f *foo) registerNotificationSubscription(ctx app.Context) { sub, err : ctx.Notifications().Subscribe(MY_VAPID_PUBLIC_KEY) if err ! nil { log.Println(subscribing to push notifications failed:, err) return } }Subscribe的底層行為pkg/app/notification.go#L152-L177公鑰為空時(shí)直接報(bào)錯(cuò)vapid public key is empty因此必須傳入真實(shí)有效的 VAPID 公鑰內(nèi)部調(diào)用瀏覽器橋接函數(shù)goappSubscribePushNotificationspkg/app/gen/app.js#L143-L155其本質(zhì)是pushManager.subscribe({ userVisibleOnly: true, applicationServerKey: vapIDpublicKey })userVisibleOnly: true表示每條推送都必須展示通知Web Push 的強(qiáng)制約束若瀏覽器不支持推送或訂閱失敗返回錯(cuò)誤push notifications are not supported by the browser或解碼失敗錯(cuò)誤。2.2 訂閱前的權(quán)限檢查訂閱前必須確保用戶(hù)已授予通知權(quán)限。推薦的完整流程是先請(qǐng)求權(quán)限僅在app.NotificationGranted時(shí)繼續(xù)訂閱見(jiàn)上文enableNotifications。通知權(quán)限共有四種取值pkg/app/notification.go#L93-L111權(quán)限常量說(shuō)明app.NotificationDefault用戶(hù)尚未表態(tài)瀏覽器按拒絕處理app.NotificationGranted用戶(hù)已允許顯示通知app.NotificationDenied用戶(hù)已拒絕app.NotificationNotSupported瀏覽器不支持通知權(quán)限查詢(xún)與請(qǐng)求分別對(duì)應(yīng)Context.Notifications().Permission()pkg/app/notification.go#L117-L124與RequestPermission()pkg/app/notification.go#L127-L141前者讀window.Notification.permission后者觸發(fā)瀏覽器彈窗詢(xún)問(wèn)并等待用戶(hù)選擇。三、注冊(cè)訂閱到推送服務(wù)器客戶(hù)端獲取到訂閱對(duì)象后必須把它注冊(cè)到自建的推送通知服務(wù)器否則服務(wù)器無(wú)從得知向誰(shuí)下發(fā)。這一步在 go-app 中就是一次普通的 HTTP POST 請(qǐng)求。因?yàn)榫W(wǎng)絡(luò) I/O 不應(yīng)阻塞 UI 線(xiàn)程官方推薦將注冊(cè)邏輯放入 ctx.Async() 異步回調(diào)中執(zhí)行func (f *foo) registerNotificationSubscription(ctx app.Context) { sub, err : ctx.Notifications().Subscribe(MY_VAPID_PUBLIC_KEY) if err ! nil { log.Println(subscribing to push notifications failed:, err) return } ctx.Async(func() { var body bytes.Buffer if err : json.NewEncoder(body).Encode(sub); err ! nil { log.Println(encoding notification subscription failed:, err) return } res, err : http.Post(/PUSH_SERVER_ENDPOINT, application/json, body) if err ! nil { log.Println(registering notification subscription failed:, err) return } defer res.Body.Close() }) }要點(diǎn)說(shuō)明序列化后的訂閱對(duì)象即為 NotificationSubscription 的 JSON 形態(tài)endpoint推送服務(wù)端點(diǎn) URL與keys.auth、keys.p256dh用于消息加密的密鑰對(duì)/PUSH_SERVER_ENDPOINT替換為你推送服務(wù)器上的注冊(cè)接口地址訂閱對(duì)象可能在推送服務(wù)輪換時(shí)失效正式產(chǎn)品中建議在應(yīng)用啟動(dòng)時(shí)重新訂閱并同步注冊(cè)。四、服務(wù)端接收并存儲(chǔ)訂閱推送服務(wù)器可以用任意語(yǔ)言實(shí)現(xiàn)go-app 對(duì)服務(wù)端的唯一硬性要求是下發(fā)的通知消息必須是 JSON 編碼的Notification結(jié)構(gòu)。這里以 Go 官方推薦的 http.Handler webpush-go 為例演示完整服務(wù)端實(shí)現(xiàn)。首先注冊(cè)路由并定義處理器結(jié)構(gòu)處理器持有 VAPID 密鑰對(duì)和內(nèi)存中的訂閱表func main() { // ... http.Handle(/test/notifications/, notificationHandler{ VAPIDPrivateKey: MY_VAPID_PRIVATE_KEY, VAPIDPublicKey: MY_VAPID_PUBLIC_KEY, }) // ... } type notificationHandler struct { VAPIDPrivateKey string VAPIDPublicKey string mutex sync.Mutex subscriptions map[string]webpush.Subscription } func (h *notificationHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { switch path : path.Base(r.URL.Path); path { case register: h.handleRegistrations(w, r) case test: h.handleTests(w, r) } }注意這里同時(shí)持有VAPID 私鑰僅存服務(wù)器用于簽名下發(fā)請(qǐng)求與公鑰分發(fā)給客戶(hù)端用于訂閱私鑰絕不能下發(fā)到瀏覽器端。示例中使用map[string]webpush.Subscription按endpoint去重存儲(chǔ)生產(chǎn)環(huán)境應(yīng)替換為數(shù)據(jù)庫(kù)等持久化存儲(chǔ)并考慮訂閱過(guò)期清理。接收訂閱的處理函數(shù)從請(qǐng)求體解碼webpush.Subscription在互斥鎖保護(hù)下寫(xiě)入訂閱表func (h *notificationHandler) handleRegistrations(w http.ResponseWriter, r *http.Request) { var sub webpush.Subscription if err : json.NewDecoder(r.Body).Decode(sub); err ! nil { w.WriteHeader(http.StatusBadRequest) return } h.mutex.Lock() defer h.mutex.Unlock() if h.subscriptions nil { h.subscriptions make(map[string]webpush.Subscription) } h.subscriptions[sub.Endpoint] sub }五、服務(wù)端下發(fā) JSON 編碼的 Notification下發(fā)處理函數(shù)遍歷全部已注冊(cè)訂閱為每個(gè)訂閱并發(fā)調(diào)用webpush.SendNotification消息體是json.Marshal(app.Notification{...})的結(jié)果// handleTests creates and sends a push notification for all the registered // subscriptions. func (h *notificationHandler) handleTests(w http.ResponseWriter, r *http.Request) { h.mutex.Lock() defer h.mutex.Unlock() for _, sub : range h.subscriptions { go func(sub webpush.Subscription) { body, _ : json.Marshal(app.Notification{ Title: Push test from server, Body: go-app push notification number, Path: /mypage }) res, err : webpush.SendNotification(body, sub, webpush.Options{ VAPIDPrivateKey: h.VAPIDPrivateKey, VAPIDPublicKey: h.VAPIDPublicKey, TTL: 30, }) if err ! nil { app.Log(err) return } defer res.Body.Close() }(sub) } }關(guān)鍵點(diǎn)消息體必須為 JSON 編碼的app.Notification這是 go-app 端 Service Worker 解析推送的前提TTL: 30表示推送在推送服務(wù)上最多保留 30 秒超時(shí)未送達(dá)則丟棄適合測(cè)試場(chǎng)景生產(chǎn)環(huán)境按時(shí)效需求調(diào)整并發(fā)go func下發(fā)可提升吞吐注意h.mutex的鎖定范圍只覆蓋遍歷讀取安全示例中的app.Notification來(lái)自 pkg/app/notification.go字段完全對(duì)齊 Web Notifications API見(jiàn)下文。六、Notification 結(jié)構(gòu)與客戶(hù)端展示原理6.1 Notification 字段全景go-app 的 Notification 是對(duì) Web Notifications API 的 Go 封裝字段如下字段JSON key必填說(shuō)明Titletitle是通知頂部醒目標(biāo)題Pathpath否點(diǎn)擊通知后應(yīng)用導(dǎo)航到的 URL 路徑Langlang否ISO 兩位語(yǔ)言代碼Badgebadge否空間有限時(shí)代表通知的圖片 URLBodybody否標(biāo)題下方的主內(nèi)容Tagtag否通知唯一標(biāo)識(shí)用于替換同標(biāo)簽舊通知Iconicon否通知中顯示的圖標(biāo) URLImageimage否通知中展示的大圖 URLDatadata否任意附加數(shù)據(jù)注意goapp鍵被 go-app 保留Renotifyrenotify否新通知替換舊通知時(shí)是否再次提醒用戶(hù)RequireInteractionrequireInteraction否通知保持激活直到用戶(hù)操作不自動(dòng)關(guān)閉Silentsilent否靜默通知抑制聲音與振動(dòng)Vibratevibrate否設(shè)備振動(dòng)模式數(shù)字?jǐn)?shù)組Actionsactions否通知內(nèi)展示的可操作按鈕列表Actions的元素是 NotificationActionAction唯一 ID、Title按鈕文案、Icon圖標(biāo) URL、Path點(diǎn)擊該動(dòng)作后的導(dǎo)航路徑。注意Data中的goapp鍵為 go-app 保留源碼注釋明確標(biāo)注用于內(nèi)部傳遞path與actions信息請(qǐng)勿占用。6.2 Service Worker 展示與點(diǎn)擊跳轉(zhuǎn)原理推送消息到達(dá)后go-app 內(nèi)置的 Service Workerpkg/app/gen/app-worker.js完成展示與跳轉(zhuǎn)push事件pkg/app/gen/app-worker.js#L63-L79解析event.data.json()得到通知對(duì)象交給showNotification展示解析失敗則靜默丟棄showNotificationpkg/app/gen/app-worker.js#L92-L117把Notification字段映射為registration.showNotification(title, { body, icon, badge, actions, data })并將path與動(dòng)作路徑寫(xiě)入data.goapp供點(diǎn)擊時(shí)取用notificationclickpkg/app/gen/app-worker.js#L119-L158關(guān)閉通知后讀取data.goapp.path若點(diǎn)擊的是某個(gè)動(dòng)作按鈕則改用對(duì)應(yīng)動(dòng)作的Path隨后優(yōu)先聚焦已打開(kāi)的窗口并postMessage通知 go-app 導(dǎo)航否則clients.openWindow(path)打開(kāi)新窗口。因此Notification.Path必須是應(yīng)用內(nèi)頁(yè)面的 URL 路徑點(diǎn)擊通知時(shí)應(yīng)用會(huì)自動(dòng)導(dǎo)航到該路徑如/mypage。這正是推送通知從展示到業(yè)務(wù)落地的關(guān)鍵銜接點(diǎn)。6.3 與本地通知的統(tǒng)一客戶(hù)端本地通知Context.Notifications().New(app.Notification{...})與推送通知復(fù)用同一 Notification 結(jié)構(gòu)與goappNewNotification橋接pkg/app/gen/app.js#L157-L179展示路徑一致只是來(lái)源不同本地 vs 遠(yuǎn)程。若 Service Worker 不可用本地通知會(huì)退化為new Notification(...)并手動(dòng)處理onclick導(dǎo)航。七、端到端驗(yàn)證步驟將上述代碼組裝后可按以下步驟驗(yàn)證整條鏈路配置 VAPID 密鑰對(duì)生成 VAPID 公鑰/私鑰webpush-go 提供了密鑰生成工具公鑰填到客戶(hù)端Subscribe()私鑰與公鑰填到服務(wù)端處理器客戶(hù)端請(qǐng)求權(quán)限在組件渲染中根據(jù)notificationPermission狀態(tài)展示啟用通知按鈕參考 notifications.md 的app.If分支寫(xiě)法點(diǎn)擊后RequestPermission()并訂閱服務(wù)端注冊(cè)客戶(hù)端把訂閱 JSON POST 到服務(wù)端/test/notifications/register服務(wù)端下發(fā)訪(fǎng)問(wèn)服務(wù)端/test/notifications/test觸發(fā)對(duì)所有已注冊(cè)訂閱的推送驗(yàn)證展示與跳轉(zhuǎn)確認(rèn)應(yīng)用關(guān)閉時(shí)也能收到通知點(diǎn)擊通知正文跳轉(zhuǎn)/mypage如有動(dòng)作按鈕點(diǎn)擊動(dòng)作跳轉(zhuǎn)對(duì)應(yīng)路徑。整個(gè)過(guò)程無(wú)需客戶(hù)端輪詢(xún)服務(wù)端通過(guò) Web Push 協(xié)議直接推送這正是 PWA 離線(xiàn)可用體驗(yàn)的重要組成部分。結(jié)語(yǔ)go-app 將 Web Push 的復(fù)雜度收斂為三個(gè)動(dòng)作客戶(hù)端Subscribe()拿訂閱、HTTP 注冊(cè)到自有服務(wù)器、服務(wù)端下發(fā) JSON 編碼的Notification。只要服務(wù)端最終下發(fā)的是符合 Notification 結(jié)構(gòu)的 JSON 消息推送服務(wù)器無(wú)論用 Go、Node.js 還是其他語(yǔ)言實(shí)現(xiàn)都可以與 go-app 無(wú)縫對(duì)接。建議結(jié)合實(shí)際項(xiàng)目將訂閱存儲(chǔ)、VAPID 密鑰管理和訂閱過(guò)期重訂閱納入工程化設(shè)計(jì)。贊分享前端Web框架WebAssembly【免費(fèi)下載鏈接】go-appA package to build progressive web apps with Go programming language and WebAssembly.項(xiàng)目地址https://gitcode.com/gh_mirrors/go/go-app點(diǎn)擊查看免費(fèi)下載相關(guān)推薦Django-Push-Notifications 實(shí)現(xiàn) Web 推送通知完整指南Django Push Notifications 實(shí)現(xiàn) Web 推送通知完整指南 前言 在現(xiàn)代 Web 應(yīng)用中推送通知已成為提升用戶(hù)參與度和留存率的重要功能后端webpush-go 實(shí)戰(zhàn)指南基于 VAPID 與 aes128gcm 的 Web Push 服務(wù)端推送加密webpush go 實(shí)戰(zhàn)指南基于 VAPID 與 aes128gcm 的 Web Push 服務(wù)端推送加密 導(dǎo)讀 webpush go 是一個(gè)專(zhuān)為 Go 語(yǔ)網(wǎng)絡(luò)安全推送通知神器 - Push Notifications推送通知神器 Push Notifications 在移動(dòng)應(yīng)用開(kāi)發(fā)中推送通知是與用戶(hù)保持連接的關(guān)鍵工具。有了這個(gè)開(kāi)源項(xiàng)目你將擁有一款跨平臺(tái)的測(cè)試?yán)鳌狿u上一篇Vant Weapp Switch 開(kāi)關(guān)組件完全指南狀態(tài)管理、事件機(jī)制與源碼實(shí)現(xiàn)解析下一篇10分鐘搭建企業(yè)級(jí)溝通平臺(tái)OpenIM Server低代碼集成指南創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考