@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.
@@ -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 './map.js';
5
+ import { go } from './alert.js';
6
6
  import { Int } from './translations.js';
7
7
 
8
8
  /*
@@ -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.4.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/map.js",
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++) cluster.fork();
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.identity.id;
106
- let update = null//setInterval(() => network.info(network.health().axonRoles.map(role => role.topic)), 5e3);
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
  });
@@ -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
 
@@ -2,6 +2,6 @@
2
2
  "uswest/80": "37.4852,-122.2364",
3
3
  "useast/89": "40,-75",
4
4
  "uscentle/88": "35,-83",
5
- "easteu/47": "50,17",
6
- "loc": "37.4852,-122.2364"
5
+ "easteu/47": "50,17",
6
+ "loc": "37,-122"
7
7
  }
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 hehre.
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 Carol are Node instances. (Defined below, followed by the Jasmine test suite.)
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. TODO: build something like this into Axona.
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
+ });