@yz-social/civildefense.io 4.5.28 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  const { Request, Response, URL, clients} = self;
2
2
  // Little point in trying to automatically pull this through package.json, as we need a byte change in THIS file to trigger a new worker.
3
- const serviceVersion = '4.5.28';
3
+ const serviceVersion = '5.0.0';
4
4
 
5
5
  const cacheList = [ // The files we need.
6
6
  "/",
@@ -203,8 +203,46 @@ async function cacheSource(version, event) { // Cache source in the given versio
203
203
  return version;
204
204
  }
205
205
 
206
+ const issued = new Set();
207
+ function showNotification({lat, lng, issuedTime, hashtag, alert, body = '', force = false}) { // Promise to show a platform notification. There are two paths to here:
208
+ // 1. The app handles data from network connections, and determines that it should alert the user.
209
+ // In this case, the app sends the data to this service worker.
210
+ // 2. An upstream node would like to send the data over the network, but finds that we are not connected,
211
+ // and so pushes the the network envelope directly to the service worker.
212
+ // Abstractly, this function could be in either app or service worker, but the app might not be running
213
+ // in path 2, so it has to be here if we want to have the code in just one place.
214
+ // Any filtering (e.g., do not show notifications for one's own alerts) happens upstream of here. (We don't know the Agent.current and the sender isn't in the notification.)
215
+ const seenKey = alert + body; // If we click on a notification for an off-screen alert, we may process the alert again and try to notify again.
216
+ if (!force && issued.has(seenKey)) return null;
217
+ issued.add(seenKey);
218
+ const base = location.href;
219
+ const timestamp = issuedTime;
220
+ const icon = new URL('./images/civil-defense-192.png', base).href;
221
+ const queryString = `./?tags=${encodeURIComponent(hashtag)}&lat=${lat}&lng=${lng}&alert=${alert}`;
222
+ const url = new URL(queryString, base).href; // For opening page when it has been closed.
223
+ const data = {lat, lng, url};
224
+ const options = {icon, timestamp, body, data, tag: alert, renotify: true};
225
+ console.log('showNotification', {seenKey, hashtag, options});
226
+ return self.registration.showNotification(hashtag, options);
227
+ }
228
+
229
+ function showNotificationFromEnvelope({deleted, message, msgId}) { // We get the generic, application-independent envelope.
230
+ //console.log('envelope', message, msgId);
231
+ if (deleted) return cancelNotification(msgId);
232
+ const {issuedTime, hashtag, payload, alert = msgId} = message;
233
+ let {lat, lng, message:body, name = ''} = payload;
234
+ body ||= name;
235
+ return showNotification({alert, lat, lng, issuedTime, hashtag, body});
236
+ }
206
237
 
207
- self.addEventListener('message', async event => {
238
+ async function cancelNotification(tag, body = '') { // Cancel those that match tag, and if body, then only those also matching body.
239
+ for (const notification of await self.registration.getNotifications({tag})) {
240
+ console.log('cancel', {tag, body, notification});
241
+ if (!body || (body === notification.body)) notification.close();
242
+ }
243
+ }
244
+
245
+ self.addEventListener('message', event => {
208
246
  const {method, params} = event.data;
209
247
  switch (method) {
210
248
  case 'version':
@@ -214,11 +252,19 @@ self.addEventListener('message', async event => {
214
252
  event.waitUntil(cacheSource(params, event)
215
253
  .then(version => event.source.postMessage({method: 'cached', params: version})));
216
254
  break;
255
+ case 'notify':
256
+ event.waitUntil(showNotification(params));
257
+ break;
258
+ case 'cancelNotification':
259
+ event.waitUntil(cancelNotification(...params));
260
+ break;
217
261
  default:
218
262
  console.warn(`Unrecognized service worker message: "${event.data}".`);
219
263
  }
220
264
  });
221
265
 
266
+ self.addEventListener('push', event => event.waitUntil(showNotificationFromEnvelope(event.data.json())));
267
+
222
268
  self.addEventListener('notificationclick', event => {
223
269
  const {notification} = event;
224
270
  const {title, body, tag, data} = notification;
@@ -229,13 +275,12 @@ self.addEventListener('notificationclick', event => {
229
275
  clients
230
276
  .matchAll({type: 'window', includeUncontrolled: true})
231
277
  .then(async clientList => {
232
- console.log('notification', {title, body, tag, data, clientList});
233
278
  for (const client of clientList) {
234
- console.log('notification click found client');
279
+ console.log('notificationclick found client', notification, clientList);
235
280
  return client.focus().then(() => client.postMessage({method: 'go', params: {alert: tag, ...data}}));
236
281
  }
237
282
  // Client has been closed. Open one.
238
- console.log('notification click opening client', data.url);
283
+ console.log('notificationclick opening client', notification);
239
284
  return clients.openWindow(data.url);
240
285
  }),
241
286
  );
@@ -254,7 +254,7 @@ button, .leaflet-control-zoom > a, .leaflet-popup-close-button, md-outlined-icon
254
254
  .alert-marker {
255
255
  text-shadow: 8px -3px 10px rgb(0 0 0 / 60%);
256
256
  animation: pulse ease 0.8s 1;
257
- transition: transform 0.8s, opacity 0.4s;
257
+ transition: opacity 0.4s;
258
258
  }
259
259
  .alert-commented {
260
260
  display: none;
@@ -627,6 +627,15 @@ input[type="file"] {
627
627
  padding: 0;
628
628
  min-width: unset;
629
629
  }
630
+ .pwa {
631
+ display: flex;
632
+ align-items: center;
633
+ gap: 5px;
634
+ }
635
+ #learnNotification:after, #learnIdentity:after {
636
+ content: "";
637
+ margin-right: 5px;
638
+ }
630
639
  .describe {
631
640
  margin-bottom: 5px;
632
641
  }
@@ -7,10 +7,10 @@ import { resolve } from './dirname.js';
7
7
  const filename = './location.json';
8
8
  export const data = await import(filename, {with: { type: 'json' }})
9
9
  .catch(async error => {
10
- console.log(error);
10
+ console.log("Unable to get location.json");
11
11
  const response = await fetch('https://ipinfo.io/json');
12
12
  const string = await response.text();
13
- console.log('Estimating location as', string);
13
+ console.log("Estimating location as", string);
14
14
  await fs.writeFile(resolve(filename), string, 'utf8');
15
15
  return {default: JSON.parse(string)};
16
16
  });
@@ -5,7 +5,7 @@ import * as operator from '../public/javascripts/pubsub.js';
5
5
  const sockets = {};
6
6
  operator.setReceiver((nodeId, id, envelope) => {
7
7
  const socket = sockets[nodeId];
8
- socket?.send(JSON.stringify([id, envelope]));
8
+ socket.send(JSON.stringify([id, envelope])); // We want an error if socket is gone, closed, etc.
9
9
  });
10
10
 
11
11
  function heartbeat() {
@@ -20,9 +20,13 @@ export function configureWebsocket(server) {
20
20
  sockets[nodeTag] = ws;
21
21
 
22
22
  ws.on('message', async message => {
23
- const [id, methodName, ...rest] = JSON.parse(message);
24
- const result = await operator[methodName](...rest);
25
- if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify([id, result]));
23
+ try {
24
+ const [id, methodName, ...rest] = JSON.parse(message);
25
+ const result = await operator[methodName](...rest);
26
+ if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify([id, result]));
27
+ } catch (error) {
28
+ console.error(`Incomming ${error.message}: ${message}\n${error.stack}`);
29
+ }
26
30
  });
27
31
 
28
32
  ws.isAlive = true;