Skip to content

Commit 3f202af

Browse files
committed
Implement push subscription management and enhance service worker notifications
1 parent 9e40e05 commit 3f202af

5 files changed

Lines changed: 142 additions & 23 deletions

File tree

src/accounthelper/index.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,17 @@ async function uploadPushSubscription(subscription) {
164164
}
165165
}
166166

167+
async function uploadRemovePushSubscription(subscription) {
168+
var response = await fetch(getServerURL() + '/webpush/unsubscribe', {
169+
method: 'POST',
170+
body: JSON.stringify(subscription),
171+
headers: { 'Content-Type': 'application/json' }
172+
});
173+
if (!response.ok) {
174+
throw new Error("Issue when sending to remove subscription. Error: " + await response.text());
175+
}
176+
}
177+
167178
pushNotificationHelper.subscribe = async function (retry = false) {
168179
var subscription = await pushNotificationHelper.getSubscription();
169180
if (subscription && !retry) {
@@ -174,6 +185,15 @@ pushNotificationHelper.subscribe = async function (retry = false) {
174185
await uploadPushSubscription(subscription);
175186
};
176187

188+
pushNotificationHelper.unsubscribe = async function () {
189+
var subscription = await pushNotificationHelper.getSubscription();
190+
if (!subscription) {
191+
return; //No subscription
192+
}
193+
await pushNotificationHelper.__unsubscribe();
194+
await uploadRemovePushSubscription(subscription);
195+
};
196+
177197
module.exports = {
178198
cookieManager,
179199
getServerURL,

src/accounthelper/pushhelper.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//This file goes along with accounthelper.js
2+
//Use accounthelper to get the full functions.
13
var pushNotificationHelper = {};
24
var _registerResolves = [];
35
var _registerRejects = [];
@@ -68,4 +70,17 @@ pushNotificationHelper.__subscribe = async function (publicKey) {
6870

6971
pushNotificationHelper.subscribe = async () => {window.alert("pushNotificationHelper.subscribe needs to be overriden by accounthelper.");};
7072

73+
pushNotificationHelper.__unsubscribe = async function () {
74+
var registration = pushNotificationHelper.registration;
75+
if (!registration) {
76+
return;
77+
}
78+
var successful = await subscription.unsubscribe();
79+
if (!successful) {
80+
throw new Error("Unsubscribe was unsuccessful");
81+
}
82+
};
83+
84+
pushNotificationHelper.unsubscribe = async () => {window.alert("pushNotificationHelper.unsubscribe needs to be overriden by accounthelper.");};
85+
7186
module.exports = pushNotificationHelper;

src/chat/interface/notifications/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ class RealTimeNotifications {
208208
"/notifications",
209209
this.onMessage.bind(this),
210210
);
211+
212+
if ('serviceWorker' in navigator && navigator.serviceWorker.controller) {
213+
navigator.serviceWorker.controller.postMessage({
214+
type: 'HEARTBEAT',
215+
username: accountHelper.getCurrentValidationState()
216+
});
217+
}
211218
}
212219
onMessage(e) {
213220
var data = e.data;

src/serviceworker/index.js

Lines changed: 99 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,7 @@ async function sendPushNotification({title = APP_NAME, body = "", icon = "/image
1818
});
1919
}
2020

21-
async function handlePush(event) {
22-
const clientList = await clients.matchAll({ type: 'window', includeUncontrolled: true });
23-
24-
const isInsideChat = clientList.some(client => {
25-
const url = new URL(client.url);
26-
return url.pathname.startsWith('/chat') && client.visibilityState === 'visible';
27-
});
28-
29-
var origin = ""+self.location.origin;
30-
origin = origin.trim();
31-
if (origin.endsWith("/")) {
32-
origin = origin.slice(0,origin.length-1); //remove the ending slash.
33-
}
34-
35-
// Parse the data sent from your Node.js server
36-
var json = null;
37-
if (event.data) {
38-
json = event.data.json();
39-
} else {
40-
return;
41-
}
42-
21+
async function handleNotification(json) {
4322
if (json.type == "test") {
4423
sendPushNotification({
4524
body: "This is test notification"
@@ -65,6 +44,37 @@ async function handlePush(event) {
6544
}
6645
}
6746

47+
async function handlePush(event) {
48+
const clientList = await clients.matchAll({ type: 'window', includeUncontrolled: true });
49+
50+
const isInsideChat = clientList.some(client => {
51+
const url = new URL(client.url);
52+
return url.pathname.startsWith('/chat') && client.visibilityState === 'visible';
53+
});
54+
55+
var origin = ""+self.location.origin;
56+
origin = origin.trim();
57+
if (origin.endsWith("/")) {
58+
origin = origin.slice(0,origin.length-1); //remove the ending slash.
59+
}
60+
61+
// Parse the data sent from your Node.js server
62+
var json = null;
63+
if (event.data) {
64+
json = event.data.json();
65+
} else {
66+
return;
67+
}
68+
69+
await handleNotification(json);
70+
}
71+
72+
function buildWsNotifyURL() {
73+
var protocol = self.location.protocol === "https:" ? "wss://" : "ws://";
74+
var wsUrl = `${protocol}${self.location.host}/notifications`;
75+
return wsUrl;
76+
}
77+
6878
// 2. Handle Notification Clicks
6979
self.addEventListener('notificationclick', (event) => {
7080
event.notification.close();
@@ -98,4 +108,71 @@ self.addEventListener('install', (event) => {
98108

99109
self.addEventListener('activate', (event) => {
100110
event.waitUntil(clients.claim());
111+
});
112+
113+
async function sendInitialNotifcations() {
114+
var ws = new WebSocket(wsUrl);
115+
116+
setTimeout(() => {
117+
try{
118+
119+
}catch(e){
120+
ws.close();
121+
}
122+
},2000);
123+
124+
ws.onmessage = async function (event) {
125+
var json = JSON.parse(event.data);
126+
127+
if (json.type == "current") {
128+
for (const notif of json.notifications) {
129+
await handleNotification(notif);
130+
}
131+
}
132+
};
133+
}
134+
135+
self.addEventListener('message', async (event) => {
136+
if (event.data && event.data.type === 'HEARTBEAT') {
137+
var cache = await caches.open('rr-meta');
138+
// Save the current timestamp
139+
await cache.put('last-seen-online', new Response(Date.now().toString()));
140+
}
141+
});
142+
143+
async function checkReEngagement() {
144+
var THREE_DAYS = 3 * 24 * 60 * 60 * 1000;
145+
var now = Date.now();
146+
147+
var cache = await caches.open('rr-meta');
148+
var lastSeenResponse = await cache.match('last-seen-online');
149+
150+
if (lastSeenResponse) {
151+
var lastSeen = parseInt(await lastSeenResponse.text());
152+
153+
// If it's been more than 3 days since the last HEARTBEAT
154+
if (now - lastSeen > THREE_DAYS) {
155+
await sendPushNotification({
156+
title: "Missing the chaos?",
157+
body: "Your friends haven't seen you in a while! Jump back into the rants.",
158+
tag: "re-engage",
159+
data: { targetURL: "/chat" }
160+
});
161+
162+
// Update timestamp so we don't nag them again for another 3 days
163+
await cache.put('last-seen-online', new Response(now.toString()));
164+
}
165+
}
166+
}
167+
168+
var hasRunStartupCheck = false;
169+
170+
self.addEventListener('activate', (event) => {
171+
event.waitUntil((async () => {
172+
if (hasRunStartupCheck) return; // Skip if already done this session
173+
hasRunStartupCheck = true;
174+
175+
await sendInitialNotifcations();
176+
await checkReEngagement();
177+
}));
101178
});

wpstatic/version.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"timestamp":"1770734341854"}
1+
{"timestamp":"1770736094584"}

0 commit comments

Comments
 (0)