@yz-social/civildefense.io 4.5.22 → 4.5.28
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/README.md +1 -1
- package/package.json +3 -2
- package/public/about/checkpoint.jpg +0 -0
- package/public/about/en.html +13 -6
- package/public/about/es.html +62 -40
- package/public/about/radio-europe.jpg +0 -0
- package/public/about/streets-of-minneapolis.jpg +0 -0
- package/public/about/style.css +1 -0
- package/public/index.html +11 -0
- package/public/javascripts/alert.js +380 -155
- package/public/javascripts/conversation.js +5 -3
- package/public/javascripts/hashtags.js +16 -18
- package/public/javascripts/main.js +12 -0
- package/public/javascripts/p2pWebNetwork.js +4 -2
- package/public/javascripts/protocol.js +10 -8
- package/public/javascripts/pubsub.js +44 -22
- package/public/javascripts/s2.js +34 -18
- package/public/javascripts/translations.js +11 -7
- package/public/javascripts/versions.js +9 -3
- package/public/service-worker.js +5 -2
- package/public/stylesheets/style.css +109 -0
- package/server/app.js +30 -10
- package/server/location.save.json +1 -1
- package/server/websocket.js +8 -5
|
@@ -254,6 +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
258
|
}
|
|
258
259
|
.alert-commented {
|
|
259
260
|
display: none;
|
|
@@ -271,6 +272,30 @@ button, .leaflet-control-zoom > a, .leaflet-popup-close-button, md-outlined-icon
|
|
|
271
272
|
left: 5px;
|
|
272
273
|
text-align: center;
|
|
273
274
|
}
|
|
275
|
+
.aggregate.alert-pin {
|
|
276
|
+
font-size: 40px;
|
|
277
|
+
line-height: 40px;
|
|
278
|
+
top: 0px;
|
|
279
|
+
left: 0px;
|
|
280
|
+
transition: font-size 0.8s;
|
|
281
|
+
}
|
|
282
|
+
.aggregate.alert-pin:before {
|
|
283
|
+
content: "";
|
|
284
|
+
width: 50px;
|
|
285
|
+
height: 50px;
|
|
286
|
+
position: absolute;
|
|
287
|
+
top: -5px;
|
|
288
|
+
left: -5px;
|
|
289
|
+
background-image: radial-gradient(transparent 30%, var(--md-sys-color-primary), transparent 70%);
|
|
290
|
+
transition: opacity 0.8s;
|
|
291
|
+
animation: pulse ease 0.8s 1;
|
|
292
|
+
}
|
|
293
|
+
.aggregate.alert-pin.starting {
|
|
294
|
+
font-size: 0px;
|
|
295
|
+
}
|
|
296
|
+
.aggregate.alert-pin.starting:after {
|
|
297
|
+
opacity: 0;
|
|
298
|
+
}
|
|
274
299
|
@keyframes pulse {
|
|
275
300
|
0% { filter: saturate(1); }
|
|
276
301
|
25% { filter: saturate(4); }
|
|
@@ -409,9 +434,93 @@ md-menu {
|
|
|
409
434
|
display: flex;
|
|
410
435
|
align-items: center;
|
|
411
436
|
}
|
|
437
|
+
|
|
438
|
+
.teach {
|
|
439
|
+
position: absolute;
|
|
440
|
+
z-index: 1000;
|
|
441
|
+
background: linear-gradient(color-mix(in srgb, var(--md-sys-color-primary), white 40%), var(--md-sys-color-primary));
|
|
442
|
+
color: white;
|
|
443
|
+
padding: 10px;
|
|
444
|
+
border-radius: 12px;
|
|
445
|
+
}
|
|
446
|
+
.changeHashtag {
|
|
447
|
+
anchor-name: --changeHashtag;
|
|
448
|
+
}
|
|
449
|
+
.correspondent.avatar {
|
|
450
|
+
anchor-name: --correspondentAvatar;
|
|
451
|
+
}
|
|
452
|
+
.attribution > .share {
|
|
453
|
+
anchor-name: --share;
|
|
454
|
+
}
|
|
455
|
+
.reply-input [slot="leading-icon"] {
|
|
456
|
+
anchor-name: --attach;
|
|
457
|
+
}
|
|
458
|
+
md-filter-chip[label="🆘 help"], md-filter-chip[label="🆘 ayuda"] {
|
|
459
|
+
anchor-name: --topicToggle;
|
|
460
|
+
}
|
|
461
|
+
.leaflet-popup-content-wrapper {
|
|
462
|
+
anchor-name: --popup;
|
|
463
|
+
}
|
|
464
|
+
body.firstPublish.firstConversation .firstConversation .correspondent,
|
|
465
|
+
body:not(.firstPublish) .firstPublish,
|
|
466
|
+
body:not(.firstConversation) .firstConversation {
|
|
467
|
+
display: none;
|
|
468
|
+
}
|
|
469
|
+
.teach.share {
|
|
470
|
+
position-anchor: --share;
|
|
471
|
+
bottom: calc(anchor(top) + 60px);
|
|
472
|
+
left: anchor(--popup left);
|
|
473
|
+
max-width: min(45vw, 310px);
|
|
474
|
+
}
|
|
475
|
+
.teach.correspondent {
|
|
476
|
+
position-anchor: --correspondentAvatar;
|
|
477
|
+
bottom: calc(anchor(top) + 15px);
|
|
478
|
+
left: anchor(left);
|
|
479
|
+
}
|
|
480
|
+
.teach.changeHashtag {
|
|
481
|
+
position-anchor: --changeHashtag;
|
|
482
|
+
bottom: calc(anchor(top) + 60px);
|
|
483
|
+
right: anchor(--popup right);
|
|
484
|
+
width: min(45vw, 310px);
|
|
485
|
+
}
|
|
486
|
+
.teach.attach {
|
|
487
|
+
position-anchor: --attach;
|
|
488
|
+
top: calc(anchor(bottom) + 15px);
|
|
489
|
+
left: anchor(--popup left);
|
|
490
|
+
background: linear-gradient(var(--md-sys-color-primary), color-mix(in srgb, var(--md-sys-color-primary), white 40%));
|
|
491
|
+
}
|
|
492
|
+
.teach:after {
|
|
493
|
+
content: "";
|
|
494
|
+
position: absolute;
|
|
495
|
+
display: block;
|
|
496
|
+
width: 0;
|
|
497
|
+
border-style: solid;
|
|
498
|
+
border-color: var(--md-sys-color-primary) transparent;
|
|
499
|
+
}
|
|
500
|
+
.teach.share:after {
|
|
501
|
+
bottom: -60px;
|
|
502
|
+
left: 30px;
|
|
503
|
+
border-width: 60px 15px 0;
|
|
504
|
+
}
|
|
505
|
+
.teach.correspondent:after {
|
|
506
|
+
bottom: -13px;
|
|
507
|
+
left: 6px;
|
|
508
|
+
border-width: 15px 15px 0;
|
|
509
|
+
}
|
|
510
|
+
.teach.changeHashtag:after {
|
|
511
|
+
bottom: -60px;
|
|
512
|
+
right: 45px;
|
|
513
|
+
border-width: 60px 15px 0;
|
|
514
|
+
}
|
|
515
|
+
.teach.attach:after {
|
|
516
|
+
top: -15px;
|
|
517
|
+
left: 32px;
|
|
518
|
+
border-width: 0 15px 15px;
|
|
519
|
+
}
|
|
412
520
|
md-outlined-button {
|
|
413
521
|
padding: 10px;
|
|
414
522
|
}
|
|
523
|
+
|
|
415
524
|
/* .attribution > div:last-child > span { */
|
|
416
525
|
/* text-wrap: nowrap; */
|
|
417
526
|
/* } */
|
package/server/app.js
CHANGED
|
@@ -14,18 +14,33 @@ const argv = yargs(hideBin(process.argv))
|
|
|
14
14
|
.option('nPortals', {
|
|
15
15
|
alias: 'p',
|
|
16
16
|
type: 'number',
|
|
17
|
-
default: Math.max(2, logicalCores - 3),
|
|
17
|
+
default: process.env.PORTALS ? parseInt(process.env.PORTALS) : Math.max(2, logicalCores - 3),
|
|
18
18
|
description: "The number of steady nodes that handle initial connections."
|
|
19
19
|
})
|
|
20
|
+
.option('regionStartIndex', {
|
|
21
|
+
type: 'number',
|
|
22
|
+
default: -1,
|
|
23
|
+
description: "If non-negative, place each of the nPortals in successive regions (excluding excludedRegionFromSeries) starting with the region at regionStaartIndex."
|
|
24
|
+
})
|
|
25
|
+
.option('excludedRegionFromSeries', {
|
|
26
|
+
type: 'string', array: true,
|
|
27
|
+
default: ['80', '88', '89'],
|
|
28
|
+
description: "If regionStartIndex is used, the ordered list of regions to occupy is the list of canonical regions less these."
|
|
29
|
+
})
|
|
20
30
|
.option('baseURL', {
|
|
21
31
|
type: 'string',
|
|
22
|
-
default: '
|
|
32
|
+
default: process.env.BRIDGE_URL || 'wss://bridge.axona.net',
|
|
23
33
|
description: "The base URL of the portal server through which to bootstrap."
|
|
24
34
|
})
|
|
25
|
-
.option('externalBaseURL', {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
35
|
+
// .option('externalBaseURL', {
|
|
36
|
+
// type: 'string',
|
|
37
|
+
// default: '',
|
|
38
|
+
// description: "The base URL of the some other portal server to which we should connect ours, if any."
|
|
39
|
+
// })
|
|
40
|
+
.option('port', {
|
|
41
|
+
type: 'number',
|
|
42
|
+
default: 3000,
|
|
43
|
+
description: "Port for the server to listen on."
|
|
29
44
|
})
|
|
30
45
|
.option('announce', {
|
|
31
46
|
type: 'boolean',
|
|
@@ -61,7 +76,6 @@ if (cluster.isPrimary) { // Parent process with portal webserver through which c
|
|
|
61
76
|
const logger = (await import('morgan')).default;
|
|
62
77
|
const { configureWebsocket } = await import('./websocket.js');
|
|
63
78
|
|
|
64
|
-
const port = parseInt((new URL(argv.baseURL)).port || '80');
|
|
65
79
|
process.title = 'yz.social';
|
|
66
80
|
const app = express();
|
|
67
81
|
app.use(logger(':date[iso] :status :method :url :res[content-length] - :response-time ms'));
|
|
@@ -101,8 +115,8 @@ if (cluster.isPrimary) { // Parent process with portal webserver through which c
|
|
|
101
115
|
extensions: ['js'] // Some dependencies refer to .js files as relative pathnames, with the .js missing.
|
|
102
116
|
}));
|
|
103
117
|
|
|
104
|
-
server.listen(port);
|
|
105
|
-
log(`Listening on ${port} and starting ${argv.nPortals} nodes on ${logicalCores} ${cpus()[0].model} logical cores.`);
|
|
118
|
+
server.listen(argv.port);
|
|
119
|
+
log(`Listening on ${argv.port} and starting ${argv.nPortals} nodes on ${logicalCores} ${cpus()[0].model} logical cores.`);
|
|
106
120
|
for (let i = 0; i < argv.nPortals; i++) {
|
|
107
121
|
cluster.fork();
|
|
108
122
|
await delay(); // Number chosen to give an unspikey rise in packets/s.
|
|
@@ -118,8 +132,14 @@ if (cluster.isPrimary) { // Parent process with portal webserver through which c
|
|
|
118
132
|
} else {
|
|
119
133
|
process.title = 'axona-starting';
|
|
120
134
|
const { P2PWebNetwork, location } = await import('../index.js');
|
|
135
|
+
const { MAJORS, geoCellCenter } = await import('@axona/protocol');
|
|
136
|
+
const excluded = argv.excludedRegionFromSeries.map(hex => parseInt(hex, 16).toString());
|
|
137
|
+
const regions = Object.keys(MAJORS).filter(r => !excluded.includes(r));
|
|
138
|
+
const index = (cluster.worker.id - 1 + argv.regionStartIndex) % regions.length;
|
|
139
|
+
const region = (argv.regionStartIndex >= 0) && regions[index];
|
|
140
|
+
const loc = region ? geoCellCenter(parseInt(region)) : location;
|
|
121
141
|
const network = await P2PWebNetwork.create({
|
|
122
|
-
location,
|
|
142
|
+
location: loc,
|
|
123
143
|
infoLogger: log,
|
|
124
144
|
debugLogger: debug
|
|
125
145
|
});
|
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]));
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
function heartbeat() {
|
|
@@ -22,7 +22,7 @@ export function configureWebsocket(server) {
|
|
|
22
22
|
ws.on('message', async message => {
|
|
23
23
|
const [id, methodName, ...rest] = JSON.parse(message);
|
|
24
24
|
const result = await operator[methodName](...rest);
|
|
25
|
-
ws.send(JSON.stringify([id, result]));
|
|
25
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify([id, result]));
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
ws.isAlive = true;
|
|
@@ -31,6 +31,7 @@ export function configureWebsocket(server) {
|
|
|
31
31
|
ws.on('error', console.error);
|
|
32
32
|
ws.on('close', () => {
|
|
33
33
|
console.log('Disconnected', nodeTag);
|
|
34
|
+
delete sockets[nodeTag];
|
|
34
35
|
operator.deleteSubscriber(nodeTag);
|
|
35
36
|
});
|
|
36
37
|
});
|
|
@@ -38,9 +39,11 @@ export function configureWebsocket(server) {
|
|
|
38
39
|
const interval = setInterval(function ping() { // Keep-alive ping/pong on interval
|
|
39
40
|
wss.clients.forEach(function each(ws) {
|
|
40
41
|
if (!ws.isAlive) return ws.terminate();
|
|
41
|
-
ws.
|
|
42
|
-
|
|
42
|
+
if (ws.readyState == WebSocket.OPEN) {
|
|
43
|
+
ws.isAlive = false;
|
|
44
|
+
ws.ping();
|
|
45
|
+
}
|
|
43
46
|
return null;
|
|
44
47
|
});
|
|
45
|
-
},
|
|
48
|
+
}, 20e3);
|
|
46
49
|
}
|