@yz-social/civildefense.io 4.4.4 → 4.5.4
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/package.json +2 -2
- package/public/javascripts/alert.js +556 -0
- package/public/javascripts/conversation.js +100 -0
- package/public/javascripts/hashtags.js +10 -9
- package/public/javascripts/main.js +4 -5
- package/public/javascripts/map.js +8 -572
- package/public/javascripts/p2pWebNetwork.js +9 -7
- package/public/javascripts/service-manager.js +1 -1
- package/public/service-worker.js +5 -2
- package/server/app.js +8 -7
- package/server/getLocation.js +1 -1
- package/server/location.json +2 -2
- package/spec/axonSpec.js +5 -11
- package/spec/conversationSpec.js +116 -0
|
@@ -2,7 +2,7 @@ const { Request, Response, URL, localStorage, BroadcastChannel } = globalThis;
|
|
|
2
2
|
import { appVersion } from './versions.js';
|
|
3
3
|
import { resetInactivityTimer, clickTip } from './main.js';
|
|
4
4
|
import { openDisplay } from './display.js';
|
|
5
|
-
import { go } from './
|
|
5
|
+
import { go } from './alert.js';
|
|
6
6
|
import { Int } from './translations.js';
|
|
7
7
|
|
|
8
8
|
/*
|
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 = '4.
|
|
3
|
+
const serviceVersion = '4.5.4';
|
|
4
4
|
|
|
5
5
|
async function cacheFirst({request, event}) {
|
|
6
6
|
// Handle request from any cache, else fetch and store it in serviceCache.
|
|
@@ -82,7 +82,9 @@ async function cacheSource(version, event) { // Cache source in the given versio
|
|
|
82
82
|
"javascripts/versions.js",
|
|
83
83
|
"javascripts/main.js",
|
|
84
84
|
"javascripts/display.js",
|
|
85
|
-
"javascripts/
|
|
85
|
+
"javascripts/conversation.js",
|
|
86
|
+
"javascripts/alert.js",
|
|
87
|
+
"javascripts/map.js",
|
|
86
88
|
"javascripts/hashtags.js",
|
|
87
89
|
"javascripts/s2.js",
|
|
88
90
|
"javascripts/agent.js",
|
|
@@ -163,6 +165,7 @@ async function cacheSource(version, event) { // Cache source in the given versio
|
|
|
163
165
|
|
|
164
166
|
// TODO: the libraries
|
|
165
167
|
].map(name => new Request(name, {cache: 'no-store'}))); // Might not be necessary, but if any browsers insist on their own caching...
|
|
168
|
+
|
|
166
169
|
await Promise.all([
|
|
167
170
|
// These are referenced within material web, but missing. Turns out we don't need them,
|
|
168
171
|
// but let's cache empty responses to keep the console cleaner.
|
package/server/app.js
CHANGED
|
@@ -18,7 +18,7 @@ const argv = yargs(hideBin(process.argv))
|
|
|
18
18
|
.option('nPortals', {
|
|
19
19
|
alias: 'p',
|
|
20
20
|
type: 'number',
|
|
21
|
-
default: logicalCores,
|
|
21
|
+
default: Math.min(logicalCores, 5),
|
|
22
22
|
description: "The number of steady nodes that handle initial connections."
|
|
23
23
|
})
|
|
24
24
|
.option('baseURL', {
|
|
@@ -82,7 +82,6 @@ if (cluster.isPrimary) { // Parent process with portal webserver through which c
|
|
|
82
82
|
// expressWs(app);
|
|
83
83
|
// const Yz = await import('./routes/index.js');
|
|
84
84
|
|
|
85
|
-
//console.log(`${cpus()[0].model}, ${logicalCores} logical cores. Starting ${argv.nPortals}.`);
|
|
86
85
|
app.use(express.json());
|
|
87
86
|
|
|
88
87
|
app.use('/images', express.static(resolve('../public/images'), {
|
|
@@ -96,17 +95,19 @@ if (cluster.isPrimary) { // Parent process with portal webserver through which c
|
|
|
96
95
|
|
|
97
96
|
app.listen(port);
|
|
98
97
|
console.log('Listening on', port, 'and starting', argv.nPortals, 'nodes.');
|
|
99
|
-
for (let i = 0; i < argv.nPortals; i++)
|
|
98
|
+
for (let i = 0; i < argv.nPortals; i++) {
|
|
99
|
+
cluster.fork();
|
|
100
|
+
await new Promise(resolve => setTimeout(resolve, 1e3));
|
|
101
|
+
}
|
|
100
102
|
} else {
|
|
101
103
|
process.title = 'axona-starting';
|
|
102
104
|
const { P2PWebNetwork, location } = await import('../index.js');
|
|
103
|
-
await P2PWebNetwork.delay(cluster.worker?.id * 1e3); // One second between startups.
|
|
104
105
|
const network = await P2PWebNetwork.create({region: location});
|
|
105
|
-
process.title = 'axona-' + network.
|
|
106
|
-
let update =
|
|
106
|
+
process.title = 'axona-' + network.nodeIdentity.id;
|
|
107
|
+
//let update = setInterval(() => network.info(network.peer.health().axonRoles.length, 'axons'), 10e3);
|
|
107
108
|
process.on('SIGINT', async () => { // Leave the network politely.
|
|
108
109
|
console.log(process.title, 'Shutdown for Ctrl+C');
|
|
109
|
-
clearInterval(update)
|
|
110
|
+
//clearInterval(update)
|
|
110
111
|
await network.disconnect();
|
|
111
112
|
process.exit(0);
|
|
112
113
|
});
|
package/server/getLocation.js
CHANGED
|
@@ -14,6 +14,6 @@ export const data = await import(filename, {with: { type: 'json' }})
|
|
|
14
14
|
await fs.writeFile(resolve(filename), string, 'utf8');
|
|
15
15
|
return {default: JSON.parse(string)};
|
|
16
16
|
});
|
|
17
|
-
export const [lat, lng] = data.default.loc.split(',').map(parseFloat);
|
|
17
|
+
export const [lat, lng] = (globalThis.process?.env.LAT_LNG || data.default.loc).split(',').map(parseFloat);
|
|
18
18
|
export const location = {lat, lng};
|
|
19
19
|
|
package/server/location.json
CHANGED
package/spec/axonSpec.js
CHANGED
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
/*
|
|
2
2
|
FIXME: Things that either don't pass, or require undocumented workarounds.
|
|
3
3
|
TODO: Things that ought to be dealt with at some point, but can be deferred until later.
|
|
4
|
-
CURRENTLY:
|
|
5
|
-
- This passes (with the FIXMEs in place) in main/3.8.0
|
|
6
|
-
- This usually fails to receive some of the expected subscription callbacks in testnet/4.3.2, and thus hangs.
|
|
7
4
|
|
|
8
5
|
To RUN, e.g., in NodeJS:
|
|
9
6
|
- You may need to adjust the path to webTransport. See the first TODO entry.
|
|
10
|
-
- To switch between them, don't forget to change the wss url a few lines down from
|
|
7
|
+
- To switch between them, don't forget to change the wss url a few lines down from here.
|
|
11
8
|
- Have jasmine or the like installed and initialized, and then e.g., npx jasmine spec/axonSpec.js.
|
|
12
9
|
|
|
13
10
|
It is worth running this several times. It sometimes works once, and then fails or has enormous connect times on another run.
|
|
14
11
|
|
|
15
12
|
The logging tells the story.
|
|
16
|
-
Alice, Bob, and
|
|
13
|
+
Alice, Bob, Carol, David, and Emma are Node instances. (Defined below, followed by the Jasmine test suite.)
|
|
17
14
|
Alice and Bob will subscribe and publish to an open/since:'all' topic.
|
|
18
15
|
Carol will join and subscribe between the previous subscriptions and their publications.
|
|
19
16
|
After publications, Bob will politely disconnect, and then restart and subscribe again to get same results.
|
|
20
17
|
Carol will restart without an explicit disconnect, and subscribe again after publications.
|
|
21
18
|
David will join and subscribe after publications.
|
|
19
|
+
Alice kills her publication and Emma joins.
|
|
22
20
|
*/
|
|
23
21
|
const { describe, it, expect, beforeAll, afterAll, BigInt } = globalThis;
|
|
24
22
|
import { createAuthorIdentity, regionCenter, geoCellId, geoCellCenter, WIRE_VERSION, KERNEL_VERSION, deriveTopicId, metricTopic } from '@axona/protocol';
|
|
25
23
|
import { connect } from '@axona/protocol/connect.js';
|
|
26
24
|
globalThis.RTCPeerConnection ||= await import('node-datachannel/polyfill').then(ndc => ndc.RTCPeerConnection);
|
|
27
25
|
|
|
28
|
-
class Node { // Stuff we have to do every time.
|
|
26
|
+
class Node { // Stuff we have to do every time.
|
|
29
27
|
static version = KERNEL_VERSION;
|
|
30
28
|
log(...rest) {
|
|
31
29
|
console.log(new Date(), this.label, this.nodeIdentity.id.slice(0, 10), ...rest);
|
|
@@ -132,10 +130,6 @@ describe("CivilDefense", function () {
|
|
|
132
130
|
return this.ready = new Promise(resolve => {
|
|
133
131
|
const handlerTime = Date.now();
|
|
134
132
|
this.handler = ({message, receiver, ts:pubTime}) => { // Ensure that the receiver's events[currentOperation] is a list, and push message on to it.
|
|
135
|
-
// FIXME: ts is undefined for a kill, which is weird:
|
|
136
|
-
// 1. I would think that Axona needs the time in order to dedupe and order properly?
|
|
137
|
-
// 2. The app may need the time, especially since we are not reliably getting events in ts order. (See "wrong order" comment, below.)
|
|
138
|
-
pubTime ||= 0;
|
|
139
133
|
const start = Math.max(pubTime, handlerTime);
|
|
140
134
|
const elapsed = Date.now() - start;
|
|
141
135
|
const data = receiver.events[currentOperation] ||= [];
|
|
@@ -175,8 +169,8 @@ describe("CivilDefense", function () {
|
|
|
175
169
|
currentOperation = 'initial';
|
|
176
170
|
// 'alice pub' starts and completes before 'bob pub' starts.
|
|
177
171
|
aliceKillTag = await alice.publish({message: 'alice pub'});
|
|
178
|
-
await TestNode.delay(500); // FIXME: without this delay, subscription handlers are called in the wrong order.
|
|
179
172
|
console.log('alice published');
|
|
173
|
+
await TestNode.delay(500); // FIXME: without this delay, subscription handlers are called in the wrong order.
|
|
180
174
|
await bob.publish({message: ' bob pub'});
|
|
181
175
|
console.log('bob published');
|
|
182
176
|
//alice.subscribeOpenMetrics({eventName, region:regionCode, handler: envelope => console.log('*** fixme got metrics', envelope)});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
const { describe, it, expect, beforeAll, afterAll, BigInt } = globalThis;
|
|
2
|
+
import { Conversation } from '../public/javascripts/conversation.js';
|
|
3
|
+
|
|
4
|
+
describe("Conversation", function () {
|
|
5
|
+
let agent;
|
|
6
|
+
let conversation;
|
|
7
|
+
const tag = '123';
|
|
8
|
+
const payload = 'cake';
|
|
9
|
+
beforeAll(function () {
|
|
10
|
+
agent = {handle: 'alice'};
|
|
11
|
+
conversation = Conversation.ensure({tag, payload, agent});
|
|
12
|
+
});
|
|
13
|
+
describe("creation", function () {
|
|
14
|
+
it("initializes properties.", function () {
|
|
15
|
+
expect(conversation.agent).toBe(agent);
|
|
16
|
+
expect(conversation.payload).toBe(payload);
|
|
17
|
+
});
|
|
18
|
+
it("remembers conversations with the same tag.", function () {
|
|
19
|
+
expect(Conversation.ensure({tag, payload, agent})).toBe(conversation);
|
|
20
|
+
});
|
|
21
|
+
it("Properties can be ommitted for existing tag.", function () {
|
|
22
|
+
expect(Conversation.ensure({tag, payload})).toBe(conversation);
|
|
23
|
+
});
|
|
24
|
+
it("rejects changes by default.", function () {
|
|
25
|
+
expect(() => Conversation.ensure({tag, payload: 'other', agent})).toThrow();
|
|
26
|
+
});
|
|
27
|
+
describe("caching", function () {
|
|
28
|
+
let keep = true;
|
|
29
|
+
let tag = "caching";
|
|
30
|
+
class CacheConversation extends Conversation {
|
|
31
|
+
update() { return keep && this; }
|
|
32
|
+
initialize() { return keep && this; }
|
|
33
|
+
}
|
|
34
|
+
it("ends with explicit removal.", function () {
|
|
35
|
+
let initial = Conversation.ensure({tag, agent, payload});
|
|
36
|
+
expect(initial).toBeTruthy();
|
|
37
|
+
expect(Conversation.getItem(tag)).toBe(initial);
|
|
38
|
+
expect(Conversation.removeItem(tag)).toBe(initial);
|
|
39
|
+
expect(Conversation.getItem(tag)).toBeFalsy();
|
|
40
|
+
});
|
|
41
|
+
describe("deleting data", function () {
|
|
42
|
+
it("keeps new if not deleting.", function () {
|
|
43
|
+
tag = 'keep';
|
|
44
|
+
let initial = Conversation.ensure({tag, agent, payload});
|
|
45
|
+
expect(initial).toBeTruthy();
|
|
46
|
+
expect(Conversation.ensure({tag, agent, payload})).toBe(initial);
|
|
47
|
+
expect(Conversation.getItem(tag)).toBe(initial);
|
|
48
|
+
});
|
|
49
|
+
it("is skipped if delete data.", function () {
|
|
50
|
+
tag = 'skip';
|
|
51
|
+
let initial = Conversation.ensure({tag, agent});
|
|
52
|
+
expect(initial).toBeFalsy();
|
|
53
|
+
expect(Conversation.getItem(tag)).toBeFalsy();
|
|
54
|
+
});
|
|
55
|
+
it("is removed if delete data.", function () {
|
|
56
|
+
tag = 'skip';
|
|
57
|
+
let initial = Conversation.ensure({tag, agent, payload});
|
|
58
|
+
expect(initial).toBeTruthy();
|
|
59
|
+
expect(Conversation.getItem(tag)).toBeTruthy();
|
|
60
|
+
Conversation.ensure({tag, agent});
|
|
61
|
+
expect(Conversation.getItem(tag)).toBeFalsy();
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
describe("null/item caching convention", function () {
|
|
65
|
+
// Do we really need/want this convetion?
|
|
66
|
+
it("keeps if initialize answers conversation.", function () {
|
|
67
|
+
keep = true;
|
|
68
|
+
tag = 'keepA';
|
|
69
|
+
let initial = CacheConversation.ensure({tag, agent, payload});
|
|
70
|
+
expect(initial).toBeTruthy();
|
|
71
|
+
expect(CacheConversation.ensure({tag, agent, payload})).toBe(initial);
|
|
72
|
+
expect(CacheConversation.getItem(tag)).toBe(initial);
|
|
73
|
+
});
|
|
74
|
+
it("is skipped if initialize answers falsy.", function () {
|
|
75
|
+
keep = false;
|
|
76
|
+
tag = 'skipA';
|
|
77
|
+
let initial = CacheConversation.ensure({tag, agent, payload});
|
|
78
|
+
expect(initial).toBeFalsy();
|
|
79
|
+
expect(CacheConversation.getItem(tag)).toBeFalsy();
|
|
80
|
+
});
|
|
81
|
+
it("keeps existing if update answers conversation.", function () {
|
|
82
|
+
keep = true;
|
|
83
|
+
tag = 'keepB';
|
|
84
|
+
let initial = CacheConversation.ensure({tag, agent, payload});
|
|
85
|
+
expect(CacheConversation.ensure({tag, agent, payload})).toBe(initial);
|
|
86
|
+
expect(CacheConversation.ensure({tag, agent, payload})).toBe(initial);
|
|
87
|
+
expect(CacheConversation.getItem(tag)).toBe(initial);
|
|
88
|
+
});
|
|
89
|
+
it("destroys existing if update answers falsy.", function () {
|
|
90
|
+
keep = true;
|
|
91
|
+
tag = 'skipB';
|
|
92
|
+
let initial = CacheConversation.ensure({tag, agent, payload});
|
|
93
|
+
expect(CacheConversation.ensure({tag, agent, payload})).toBe(initial);
|
|
94
|
+
keep = false;
|
|
95
|
+
expect(CacheConversation.ensure({tag, agent, payload})).toBeFalsy();
|
|
96
|
+
expect(CacheConversation.getItem(tag)).toBeFalsy();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe("replies", function () {
|
|
103
|
+
beforeAll(function () {
|
|
104
|
+
conversation.ensure({payload: "second", issuedTime: 3, tag: 'z'});
|
|
105
|
+
conversation.ensure({payload: "deleted", issuedTime: 2, tag: 'y'});
|
|
106
|
+
conversation.ensure({payload: "first", issuedTime: 1, tag: 'x'});
|
|
107
|
+
conversation.ensure({payload: null, issuedTime: 4, tag: 'y'});
|
|
108
|
+
});
|
|
109
|
+
it("adds replies in timestamp order.", function () {
|
|
110
|
+
expect(conversation.items.map(reply => reply.payload)).toEqual(["first", "second"]);
|
|
111
|
+
});
|
|
112
|
+
it("removes deleted replies.", function () { // fixme eachReply
|
|
113
|
+
expect(conversation.items.find(reply => !reply.payload)).toBeFalsy();
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|