@yz-social/civildefense.io 4.5.29 → 5.0.3
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.
- package/index.js +1 -0
- package/package.json +4 -2
- package/public/about/add-topic-autocomplete.png +0 -0
- package/public/about/add-topic.png +0 -0
- package/public/about/can-happen.jpg +0 -0
- package/public/about/en.html +72 -30
- package/public/about/identity.html +48 -0
- package/public/about/index.html +1 -1
- package/public/about/join.webp +0 -0
- package/public/about/notification.html +71 -0
- package/public/about/posters.jpeg +0 -0
- package/public/about/pwa.html +61 -0
- package/public/about/style.css +15 -2
- package/public/about/template.html +28 -0
- package/public/about/topics.png +0 -0
- package/public/about/train-radio-button.png +0 -0
- package/public/about/train.png +0 -0
- package/public/about/ukraine-fires.jpeg +0 -0
- package/public/about/ukraine-fires.png +0 -0
- package/public/index.html +17 -9
- package/public/javascripts/alert.js +203 -119
- package/public/javascripts/browser-push.js +56 -0
- package/public/javascripts/conversation.js +6 -3
- package/public/javascripts/display.js +2 -1
- package/public/javascripts/hashtags.js +3 -1
- package/public/javascripts/main.js +102 -64
- package/public/javascripts/map.js +13 -6
- package/public/javascripts/p2pWebNetwork.js +37 -4
- package/public/javascripts/protocol.js +3 -3
- package/public/javascripts/pubsub.js +107 -98
- package/public/javascripts/push.js +28 -0
- package/public/javascripts/s2.js +24 -2
- package/public/javascripts/service-manager.js +11 -7
- package/public/javascripts/store-memory.js +74 -0
- package/public/javascripts/store-redis.js +101 -0
- package/public/javascripts/translations.js +6 -6
- package/public/service-worker.js +50 -5
- package/public/stylesheets/style.css +50 -17
- package/server/websocket.js +12 -5
package/public/service-worker.js
CHANGED
|
@@ -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 = '
|
|
3
|
+
const serviceVersion = '5.0.3';
|
|
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
|
-
|
|
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('
|
|
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('
|
|
283
|
+
console.log('notificationclick opening client', notification);
|
|
239
284
|
return clients.openWindow(data.url);
|
|
240
285
|
}),
|
|
241
286
|
);
|
|
@@ -253,8 +253,8 @@ button, .leaflet-control-zoom > a, .leaflet-popup-close-button, md-outlined-icon
|
|
|
253
253
|
}
|
|
254
254
|
.alert-marker {
|
|
255
255
|
text-shadow: 8px -3px 10px rgb(0 0 0 / 60%);
|
|
256
|
-
animation:
|
|
257
|
-
transition:
|
|
256
|
+
animation: pulse08 ease 0.8s 1;
|
|
257
|
+
transition: opacity 0.4s;
|
|
258
258
|
}
|
|
259
259
|
.alert-commented {
|
|
260
260
|
display: none;
|
|
@@ -288,7 +288,7 @@ button, .leaflet-control-zoom > a, .leaflet-popup-close-button, md-outlined-icon
|
|
|
288
288
|
left: -5px;
|
|
289
289
|
background-image: radial-gradient(transparent 30%, var(--md-sys-color-primary), transparent 70%);
|
|
290
290
|
transition: opacity 0.8s;
|
|
291
|
-
animation:
|
|
291
|
+
animation: pulse08 ease 0.8s 1;
|
|
292
292
|
}
|
|
293
293
|
.aggregate.alert-pin.starting {
|
|
294
294
|
font-size: 0px;
|
|
@@ -296,19 +296,43 @@ button, .leaflet-control-zoom > a, .leaflet-popup-close-button, md-outlined-icon
|
|
|
296
296
|
.aggregate.alert-pin.starting:after {
|
|
297
297
|
opacity: 0;
|
|
298
298
|
}
|
|
299
|
-
@keyframes
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
100% { filter: saturate(1); }
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
299
|
+
@keyframes pulse08 {
|
|
300
|
+
0% { filter: saturate(1); background-color: rgb(189, 46, 47, 0%); border-radius: 8px; }
|
|
301
|
+
25% { filter: saturate(4); background-color: rgb(189, 46, 47, 25%); }
|
|
302
|
+
50% { filter: saturate(1); background-color: rgb(189, 46, 47, 40%); }
|
|
303
|
+
75% { filter: saturate(0); background-color: rgb(189, 46, 47, 25%); }
|
|
304
|
+
100% { filter: saturate(1); background-color: rgb(189, 46, 47, 0%); border-radius: 8px;}
|
|
305
|
+
}
|
|
306
|
+
/* 0.2s 0.2s */
|
|
307
|
+
/* <-0.8s-> <-0.8s-> */
|
|
308
|
+
/* 4 4 4 */
|
|
309
|
+
/* 1 1 1 1 1 1 */
|
|
310
|
+
/* 0 0 */
|
|
311
|
+
@keyframes pulse20 {
|
|
312
|
+
0% { filter: saturate(1); background-color: rgb(189, 46, 47, 0%); border-radius: 8px; }
|
|
313
|
+
10% { filter: saturate(4); background-color: rgb(189, 46, 47, 25%); }
|
|
314
|
+
20% { filter: saturate(1); background-color: rgb(189, 46, 47, 50%); }
|
|
315
|
+
30% { filter: saturate(0); }
|
|
316
|
+
40% { filter: saturate(1); }
|
|
317
|
+
50% { filter: saturate(4); }
|
|
318
|
+
60% { filter: saturate(1); }
|
|
319
|
+
70% { filter: saturate(0); }
|
|
320
|
+
80% { filter: saturate(1); background-color: rgb(189, 46, 47, 50%); }
|
|
321
|
+
90% { filter: saturate(4); background-color: rgb(189, 46, 47, 25%); }
|
|
322
|
+
100% { filter: saturate(1); background-color: rgb(189, 46, 47, 0%); border-radius: 8px; }
|
|
323
|
+
}
|
|
324
|
+
@keyframes pulse20border {
|
|
325
|
+
0% { filter: saturate(1); background-color: rgb(189, 46, 47, 0%); border-radius: 8px; border: solid thin rgb(189, 46, 47, 0);}
|
|
326
|
+
10% { filter: saturate(4); background-color: rgb(189, 46, 47, 25%); border: solid thick rgb(189, 46, 47, 100%); }
|
|
327
|
+
20% { filter: saturate(1); background-color: rgb(189, 46, 47, 50%); }
|
|
328
|
+
30% { filter: saturate(0); }
|
|
329
|
+
40% { filter: saturate(1); }
|
|
330
|
+
50% { filter: saturate(4); }
|
|
331
|
+
60% { filter: saturate(1); }
|
|
332
|
+
70% { filter: saturate(0); }
|
|
333
|
+
80% { filter: saturate(1); background-color: rgb(189, 46, 47, 50%); }
|
|
334
|
+
90% { filter: saturate(4); background-color: rgb(189, 46, 47, 25%); border: solid thick rgb(189, 46, 47, 100%); }
|
|
335
|
+
100% { filter: saturate(1); background-color: rgb(189, 46, 47, 0%); border-radius: 8px; border: solid thin rgb(189, 46, 47, 0); }
|
|
312
336
|
}
|
|
313
337
|
|
|
314
338
|
#checkForUpdates, #downloadUpdates, #updateContainer md-elevated-button {
|
|
@@ -320,7 +344,7 @@ button, .leaflet-control-zoom > a, .leaflet-popup-close-button, md-outlined-icon
|
|
|
320
344
|
gap: 10px;
|
|
321
345
|
}
|
|
322
346
|
.pub {
|
|
323
|
-
animation:
|
|
347
|
+
animation: pulse08 ease 0.8s 1;
|
|
324
348
|
--_label-text-weight: 800;
|
|
325
349
|
--_selected-label-text-color: var(--md-sys-color-secondary);
|
|
326
350
|
--md-filter-chip-selected-leading-icon-color: var(--md-sys-color-secondary);
|
|
@@ -627,6 +651,15 @@ input[type="file"] {
|
|
|
627
651
|
padding: 0;
|
|
628
652
|
min-width: unset;
|
|
629
653
|
}
|
|
654
|
+
.pwa {
|
|
655
|
+
display: flex;
|
|
656
|
+
align-items: center;
|
|
657
|
+
gap: 5px;
|
|
658
|
+
}
|
|
659
|
+
#learnNotification:after, #learnIdentity:after {
|
|
660
|
+
content: "";
|
|
661
|
+
margin-right: 5px;
|
|
662
|
+
}
|
|
630
663
|
.describe {
|
|
631
664
|
margin-bottom: 5px;
|
|
632
665
|
}
|
package/server/websocket.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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;
|
|
@@ -38,7 +42,10 @@ export function configureWebsocket(server) {
|
|
|
38
42
|
|
|
39
43
|
const interval = setInterval(function ping() { // Keep-alive ping/pong on interval
|
|
40
44
|
wss.clients.forEach(function each(ws) {
|
|
41
|
-
if (!ws.isAlive)
|
|
45
|
+
if (!ws.isAlive) {
|
|
46
|
+
console.log('heartbeat failure');
|
|
47
|
+
return ws.terminate();
|
|
48
|
+
}
|
|
42
49
|
if (ws.readyState == WebSocket.OPEN) {
|
|
43
50
|
ws.isAlive = false;
|
|
44
51
|
ws.ping();
|