Skip to content

Commit dace273

Browse files
committed
Remove subscriptions from server when disabled
1 parent 3e5d9e6 commit dace273

File tree

4 files changed

+127
-10
lines changed

4 files changed

+127
-10
lines changed

internal/feed/feedConfig.go

+17
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,23 @@ func (config *FeedConfig) AddSubscription(s webpush.Subscription) error {
133133
return nil
134134
}
135135

136+
func (config *FeedConfig) DeleteSubscription(s webpush.Subscription) error {
137+
keepSubscriptions := []webpush.Subscription{}
138+
139+
for _, t := range config.Subscriptions {
140+
if s.Endpoint == t.Endpoint && s.Keys.Auth == t.Keys.Auth && s.Keys.P256dh == t.Keys.P256dh {
141+
continue
142+
}
143+
keepSubscriptions = append(keepSubscriptions, t)
144+
}
145+
config.Subscriptions = keepSubscriptions
146+
err := config.Write()
147+
if err != nil {
148+
return err
149+
}
150+
return nil
151+
}
152+
136153
func (p *PIN) IsValid(s string) error {
137154
if p.Expiration.Before(time.Now()) {
138155
code := 401

internal/handlers/handlers.go

+56
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ func (api *ApiHandler) GetServer() *chi.Mux {
167167
r.Delete("/{feedName}/{itemName}", api.feedItemDeleteHandlerFunc)
168168

169169
r.Post("/{feedName}/subscription", api.feedSubscriptionHandlerFunc)
170+
r.Delete("/{feedName}/subscription", api.feedUnsubscribeHandlerFunc)
170171
})
171172
r.Get("/*", RootHandlerFunc)
172173

@@ -352,12 +353,14 @@ func (api *ApiHandler) feedPostHandlerFunc(w http.ResponseWriter, r *http.Reques
352353

353354
// Send push notifications
354355
for _, subscription := range f.Config.Subscriptions {
356+
slog.Info("Sending push notification", slog.String("endpoint", subscription.Endpoint))
355357
resp, _ := webpush.SendNotification([]byte(fmt.Sprintf("New item posted to feed %s", f.Name())), &subscription, &webpush.Options{
356358
Subscriber: "example@example.com", // Do not include "mailto:"
357359
VAPIDPublicKey: api.Config.NotificationSettings.VAPIDPublicKey,
358360
VAPIDPrivateKey: api.Config.NotificationSettings.VAPIDPrivateKey,
359361
TTL: 30,
360362
})
363+
slog.Info("Response", slog.Any("resp", resp))
361364
defer resp.Body.Close()
362365
}
363366

@@ -456,3 +459,56 @@ func (api *ApiHandler) feedSubscriptionHandlerFunc(w http.ResponseWriter, r *htt
456459
return
457460
}
458461
}
462+
463+
func (api *ApiHandler) feedUnsubscribeHandlerFunc(w http.ResponseWriter, r *http.Request) {
464+
465+
slog.Default().WithGroup("http").Debug("Feed subscription request", slog.Any("request", r))
466+
467+
secret, _ := utils.GetSecret(r)
468+
469+
feedName, _ := url.QueryUnescape(chi.URLParam(r, "feedName"))
470+
if feedName == "" {
471+
utils.CloseWithCodeAndMessage(w, 500, "Unable to obtain feed name")
472+
}
473+
474+
f, err := feed.GetFeed(path.Join(api.BasePath, feedName))
475+
476+
if err != nil {
477+
yberr := err.(*feed.FeedError)
478+
utils.CloseWithCodeAndMessage(w, yberr.Code, yberr.Error())
479+
return
480+
}
481+
482+
err = f.IsSecretValid(secret)
483+
484+
if err != nil {
485+
yberr := err.(*feed.FeedError)
486+
utils.CloseWithCodeAndMessage(w, yberr.Code, yberr.Error())
487+
return
488+
}
489+
490+
body, err := io.ReadAll(r.Body)
491+
492+
defer r.Body.Close()
493+
494+
if err != nil {
495+
utils.CloseWithCodeAndMessage(w, 500, "Unable to read subscription")
496+
return
497+
}
498+
499+
var s webpush.Subscription
500+
501+
err = json.Unmarshal(body, &s)
502+
503+
if err != nil {
504+
utils.CloseWithCodeAndMessage(w, 500, "Unable to parse subscription")
505+
return
506+
}
507+
508+
err = f.Config.DeleteSubscription(s)
509+
510+
if err != nil {
511+
utils.CloseWithCodeAndMessage(w, 500, "Unable to add subscription")
512+
return
513+
}
514+
}

web/ui/src/YBFeed/YBNotificationToggle.tsx

+30-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export function NotificationToggle(props:NotificationToggleProps) {
1818
const [notificationsOn,setNotificationsOn] = useState(false)
1919
const [loading,setLoading] = useState(false)
2020
const [canPushNotifications, setCanPushNotification] = useState(false)
21+
2122
async function subscribe(): Promise<Boolean> {
2223
return new Promise((resolve, reject) => {
2324
if (!vapid) {
@@ -46,7 +47,32 @@ export function NotificationToggle(props:NotificationToggleProps) {
4647
.catch(err => console.error(err));
4748
})
4849
}
49-
50+
async function unsubscribe(): Promise<Boolean> {
51+
return new Promise((resolve, reject) => {
52+
if (!vapid) {
53+
reject("VAPID not declared")
54+
}
55+
const connection = new FeedConnector()
56+
57+
navigator.serviceWorker.ready
58+
.then(function(registration) {
59+
return registration.pushManager.getSubscription()
60+
})
61+
.then(function(subscription) {
62+
if (subscription) {
63+
subscription.unsubscribe()
64+
connection.RemoveSubscription(feedName,JSON.stringify(subscription))
65+
.then((r) => {
66+
resolve(true)
67+
})
68+
}
69+
else {
70+
reject("Unable to unsubscribe (empty subscription)")
71+
}
72+
})
73+
.catch(err => console.error(err));
74+
})
75+
}
5076
function urlBase64ToUint8Array(base64String: string) {
5177
const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
5278
const base64 = (base64String + padding)
@@ -66,15 +92,9 @@ export function NotificationToggle(props:NotificationToggleProps) {
6692
.then(function(subscription) {
6793
if (subscription) {
6894
if (notificationsOn) {
69-
subscription.unsubscribe()
70-
.then((b) => {
71-
if (b) {
72-
setNotificationsOn(false)
73-
}
74-
})
75-
.catch((e) => {
76-
console.log(e)
77-
message.error("Error")
95+
unsubscribe()
96+
.then(() => {
97+
setNotificationsOn(false)
7898
})
7999
}
80100
}

web/ui/src/YBFeed/index.ts

+24
Original file line numberDiff line numberDiff line change
@@ -165,4 +165,28 @@ export class FeedConnector {
165165
})
166166
})
167167
}
168+
async RemoveSubscription(feedName: string, subscription: any): Promise<boolean> {
169+
return new Promise((resolve, reject) => {
170+
fetch(this.feedUrl(feedName)+"/subscription",{
171+
method: "DELETE",
172+
credentials: "include",
173+
body: subscription
174+
})
175+
.then((f) => {
176+
if (f.status !== 200) {
177+
f.text().then((b) => {
178+
reject(new YBFeedError(f.status, b))
179+
})
180+
.catch((e) => {
181+
reject(new YBFeedError(f.status, "Server Unavailable"))
182+
})
183+
}
184+
resolve(true)
185+
})
186+
.catch((e) => {
187+
reject(new YBFeedError(e.status, "Server Unavailable"))
188+
})
189+
})
190+
}
191+
168192
}

0 commit comments

Comments
 (0)