Notifications
Enable Notifications
Enabling notifications requires the user to give permission to display them.
Current Permission
The current notifications permission is retrieved by calling Context.Notifications().Permission():
type foo struct {
app.Compo
notificationPermission app.NotificationPermission
}
func (f *foo) OnMount(ctx app.Context) {
f.notificationPermission = ctx.Notifications().Permission()
}
Request Permission
The Notification permission is given by requesting the user permission with Context.Notifications().RequestPermission():
func (f *foo) Render() app.UI {
return app.Div().Body(
app.If(f.notificationPermission == app.NotificationDefault,
app.Button().
Text("Enable Notifications").
OnClick(f.enableNotifications),
).ElseIf(f.notificationPermission == app.NotificationDenied,
app.Text("Notification permission is denied"),
).ElseIf(f.notificationPermission == app.NotificationGranted,
app.Text("Notification permission is already granted"),
).Else(
app.Text("Notification are not supported"),
),
)
}
func (f *foo) enableNotifications(ctx app.Context, e app.Event) {
// Triggers a browser popup that asks for user permission.
f.notificationPermission = ctx.Notifications().RequestPermission()
}
Display Local Notifications
A local notification is a notification created in the app with Context.Notifications().New():
func (f *foo) Render() app.UI {
return app.Div().Body(
app.If(f.notificationPermission == app.NotificationDefault,
app.Button().
Text("Enable Notifications").
OnClick(f.enableNotifications),
).ElseIf(f.notificationPermission == app.NotificationDenied,
app.Text("Notification permission is denied"),
).ElseIf(f.notificationPermission == app.NotificationGranted,
app.Button().
Text("Test Notification").
OnClick(f.enableNotifications),
).Else(
app.Text("Notification are not supported"),
),
)
}
func (f *foo) testNotification(ctx app.Context, e app.Event) {
ctx.Notifications().New(app.Notification{
Title: "Test",
Body: "A test notification",
Path: "/mypage",
})
}
Notification.Path is a URL path that targets a page in the app. When a notification is clicked, the app will be navigated on this URL path.
Example
Push Notifications
Push notifications are notifications that are sent by a remote server and that can be displayed whether the app is running or closed. Setting them up requires sending a subscription to a push notification server.
Getting Notification Subscription
The push notification subscription is obtained with Context.Notifications().Subscribe():
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
}
}
Registering Notification Subscription
Once the subscription is obtained, it has to be registered on a push notification server. This is done with a classic HTTP request:
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()
})
}
Sending Push Notification
Sending a push notification is done on the server side by using the subscription previously created. Here is an http.Handler implementation based on the webpush-go package.
func main() {
// ...
http.Handle("/test/notifications/", ¬ificationHandler{
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)
}
}
The first step is to receive and store the previously created subscription:
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
}
Then create and send a JSON encoded notification:
// 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)
}
}
Push servers can be implemented in various programming languages. The requirement to receive a push notification with go-app is that the notification message is a JSON encoded Notification struct.