@yz-social/kdht 0.1.2 → 0.1.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.
@@ -6,48 +6,99 @@ export class SimulatedContact extends Contact {
6
6
  get key() { return this.node.key; }
7
7
  get isServerNode() { return this.node.isServerNode; }
8
8
 
9
- get isRunning() { // Ask our canonical home contact.
9
+ get isRunning() { // Is the far node running.
10
10
  return this.node.isRunning;
11
11
  }
12
12
  connection = null;
13
13
  async connect() { return this; }
14
- disconnectTransport() { }
15
- async transmitRPC(method, ...rest) { // Transmit the call to the receiving node's contact.
14
+ // Dispatch directly on the node, returning the response. This is different than the send to and from with messageTag used by
15
+ // SimulatedConnectionContact and WebContact.
16
+ async transmitRPC(messageTag, method, sender, ...rest) {
17
+ // Use delay from the destination node if set, representing a laggy VM/connection
18
+ const delayMs = this.node.delayMs;
16
19
  return await this.constructor.ensureTime(async () => {
17
20
  if (!this.isRunning) return null; // Receiver closed.
18
- return await this.node.contact.receiveRPC(method, this.node.ensureContact(this.host.contact), ...rest);
19
- });
21
+ return await this.node.receiveRPC(method, this.node.ensureContact(this.host.contact), ...rest);
22
+ }, delayMs);
20
23
  }
21
24
  }
22
25
 
23
26
  export class SimulatedConnectionContact extends SimulatedContact {
24
27
  connection = null; // The cached connection (to another node's connected contact back to us) over which messages can be directly sent, if any.
25
- disconnectTransport() {
28
+ disconnect() {
29
+ // Report if we are the last node to hold a value.
30
+ if (Node.refreshTimeIntervalMS && Node.contacts?.length) { // i.e., not shutting down and in simulation where we track all Contacts.
31
+ for (const key of this.host.storage.keys()) {
32
+ const found = Node.contacts.find(contact => contact?.host.storage.has(key) && (contact?.key !== this.key));
33
+ if (!found) console.log('\n\n*** removing last storer for ', key, this.host.storage.get(key), 'among', Node.contacts.filter(e => e).length, 'contacts ***\n');
34
+ }
35
+ }
36
+ return super.disconnect();
37
+ }
38
+ disconnectTransport(andNotify = true) {
26
39
  const farContactForUs = this.connection;
27
40
  if (!farContactForUs) return;
28
41
  Node.assert(farContactForUs.key === this.host.key, 'Far contact backpointer', farContactForUs.node.name, 'does not point to us', this.host.name);
29
42
  Node.assert(farContactForUs.host.key === this.key, 'Far contact host', farContactForUs.host.name, 'is not hosted at contact', this.name);
30
- farContactForUs.connection = null;
31
- this.connection = null;
43
+ super.disconnectTransport(andNotify);
44
+ this.connection = farContactForUs.connection = null;
32
45
  }
33
46
  async connect(forMethod = 'findNodes') { // Connect from host to node, promising a possibly cloned contact that has been noted.
34
47
  // Simulates the setup of a bilateral transport between this host and node, including bookkeeping.
35
48
  // TODO: Simulate webrtc signaling.
36
49
  const contact = this;
37
- let { host, node, isServerNode } = contact;
50
+ let { host, node, isServerNode, connection } = contact;
51
+ if (connection) return connection;
38
52
 
39
53
  // Anyone can connect to a server node using the server's connect endpoint.
40
54
  // Anyone in the DHT can connect to another DHT node through a sponsor.
41
- if (!isServerNode) {
55
+ if (isServerNode) {
56
+ // No point in slowing the tests down to actually wait for this. It doesn't change the outcome.
57
+ //await Node.delay(250); // Connect through portal.
58
+ } else {
59
+ //this.host.xlog('connecting', this.sname);
42
60
  let mutualSponsor = null;
43
- for (const sponsor of this._sponsors.values()) {
44
- if (!sponsor.connection || !sponsor.node.existingContact(this.node.name)?.connection) continue;
45
- mutualSponsor = sponsor;
61
+ const isConnected = (contact) => { // Is contact already connected to us?
62
+ return contact.connection && contact.node.existingContact(this.node.name)?.connection;
63
+ };
64
+ await Node.delay(100);
65
+ const sponsors = Array.from(this._sponsors.values());
66
+ const target = this.node, targetKey = target.key;
67
+ const findSponsor = () => {
68
+ for (const sponsor of sponsors) {
69
+ //if (isConnected(sponsor)) return sponsor;
70
+ if (sponsor.sendRPC('signals', this.key, [])) {
71
+ return sponsor;
72
+ }
73
+ }
74
+ return null;
75
+ };
76
+ function findPath(contact, excluded) {
77
+ if (contact.key === targetKey) return true;
78
+ if (!isConnected(contact)) return false;
79
+ const closest = contact.node.findClosestHelpers(targetKey)
80
+ .map(helper => helper.contact)
81
+ .filter(contact => !excluded.includes(contact.key));
82
+ for (const sub of closest) {
83
+ if (findPath(sub, [sub.node.key, ...excluded])) return true;
84
+ }
85
+ return false;
86
+ }
87
+ if (! findSponsor()) {
88
+ await Node.delay(100);
89
+ if ( findSponsor()) console.log('*** found sponsor after delay ***');
90
+ else if (findPath(this.host.contact, [this.host.key])) console.log('*** found path ***');
91
+ else {
92
+ // console.log('No connection path from', this.host.contact.report, 'to', this.report, 'sponsors:', sponsors.map(c => c.report)
93
+ // //, 'contacts:', this.node.findClosestHelpers(targetKey).map(helper => helper.contact.report)
94
+ // );
95
+ return null;
96
+ }
46
97
  }
47
- if (!mutualSponsor) return null;
48
98
  }
49
-
50
- const farContactForUs = node.ensureContact(host.contact, contact.sponsor);
99
+
100
+ // our sponsors are not transferred to the other side.
101
+ const farContactForUs = node.ensureContact(host.contact);
51
102
 
52
103
  contact.connection = farContactForUs;
53
104
  host.noteContactForTransport(contact);
@@ -57,10 +108,26 @@ export class SimulatedConnectionContact extends SimulatedContact {
57
108
 
58
109
  return contact;
59
110
  }
60
- async transmitRPC(method, ...rest) { // A message from this.host to this.node. Forward to this.node through overlay connection for bucket.
111
+ signals(...rest) {
112
+ return [this.name]; // Just a simulation
113
+ }
114
+ async send(message) {
115
+ await Node.delay(10);
116
+ this.connection?.receiveRPC(...message);
117
+ }
118
+ async synchronousSend(message) {
119
+ const other = this.connection;
120
+ await Node.delay(1);
121
+ other?.receiveRPC(...message);
122
+ }
123
+ async transmitRPC(messageTag, method, sender, ...rest) { // "transmit" the call (with sending contact added).
61
124
  if (!this.isRunning) return null; // Receiver closed.
62
- const farContactForUs = this.connection || (await this.connect(method))?.connection;
63
- if (!farContactForUs) return null; // receiver no longer reachable.
64
- return await this.constructor.ensureTime(() => farContactForUs.receiveRPC(method, farContactForUs, ...rest));
125
+ const farContactForUs = this.connection;
126
+ if (!farContactForUs) return await Node.delay(this.constructor.maxPingMs, null);
127
+ // Use delay from the destination node if set, representing a laggy VM/connection
128
+ const delayMs = this.node.delayMs;
129
+ const responsePromise = Promise.race([this.getResponsePromise(messageTag), this.rpcTimeout(method)]);
130
+ this.constructor.ensureTime(() => farContactForUs.receiveRPC(messageTag, method, farContactForUs, ...rest), delayMs);
131
+ return await responsePromise;
65
132
  }
66
133
  }
@@ -5,7 +5,6 @@ import { Helper } from '../dht/helper.js';
5
5
  import { Contact } from './contact.js';
6
6
  import { WebRTC } from '@yz-social/webrtc';
7
7
 
8
-
9
8
  export class WebContact extends Contact { // Our wrapper for the means of contacting a remote node.
10
9
  // Can this set all be done more simply?
11
10
  get name() { return this.node.name; } // Key of remote node as a string (e.g., as a guid).
@@ -14,12 +13,15 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
14
13
  get isRunning() { return this.node.isRunning; } // Have we marked at is no longer running.
15
14
 
16
15
  checkResponse(response) { // Return a fetch response, or throw error if response is not a 200 series.
16
+ if (!response) return;
17
17
  if (!response.ok) throw new Error(`fetch ${response.url} failed ${response.status}: ${response.statusText}.`);
18
18
  }
19
+ // connection:close is far more robust against pooling issues common to some implementations (e.g., NodeJS).
20
+ // https://github.com/nodejs/undici/issues/3492
19
21
  async fetchBootstrap(baseURL, label = 'random') { // Promise to ask portal (over http(s)) to convert a portal
20
22
  // worker index or the string 'random' to an available sname to which we can connect().
21
23
  const url = `${baseURL}/name/${label}`;
22
- const response = await fetch(url);
24
+ const response = await fetch(url, {headers: { 'Connection': 'close' } }).catch(e => this.host.xlog(e));
23
25
  this.checkResponse(response);
24
26
  return await response.json();
25
27
  }
@@ -33,21 +35,49 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
33
35
  async fetchSignals(url, signalsToSend) {
34
36
  const response = await fetch(url, {
35
37
  method: 'POST',
36
- headers: { 'Content-Type': 'application/json' },
38
+ headers: { 'Content-Type': 'application/json', 'Connection': 'close' },
37
39
  body: JSON.stringify(signalsToSend)
38
- });
40
+ }).catch(e => this.host.xlog(e));
39
41
  this.checkResponse(response);
40
- return this.checkSignals(await response.json());
42
+ return this.checkSignals(await response?.json());
41
43
  }
42
44
  async messsageSignals(signals) {
43
- return this.checkSignals(await this.host.message({targetKey: this.key, targetSname: this.sname,
44
- payload: ['signal', this.host.contact.sname, ...signals]}));
45
+ // Try sponsors first (two hops if connected).
46
+ const payload = [this.host.contact.sname, ...signals];
47
+ //this.host.xlog('contact messageSignals', payload);
48
+ const sponsors = Array.from(this._sponsors.values());
49
+ for (const sponsor of sponsors) {
50
+ const response = await sponsor.sendRPC('signals', this.key, payload);
51
+ if (response) return response;
52
+ this._sponsors.delete(sponsor.key);
53
+ }
54
+ if (!this.host.sRunning) return [];
55
+ this.host.xlog('Unable to signal through sponsor. Using recursive message to', this.sname, this.key);
56
+ const recursive = await this.host.contact.sendRPC('signals', this.key, payload, []);
57
+ //this.host.xlog('got recursive response', recursive);
58
+ if (!this.host.isRunning) return [];
59
+ if (!recursive) this.host.xlog('Unable to deliver signals to', this.sname);
60
+ return this.checkSignals(recursive);
61
+ // return this.checkSignals(await this.host.message({targetKey: this.key, targetSname: this.sname,
62
+ // payload: ['signal', this.host.contact.sname, ...signals]}));
63
+ }
64
+ async signals(senderSname, ...signals) { // Accept directed WebRTC signals from a sender sname, creating if necessary the
65
+ // new contact on host to receive them, and promising a response.
66
+ //this.host.xlog('contact signals', senderSname, signals);
67
+ let contact = await this.ensureRemoteContact(senderSname);
68
+
69
+ if (contact.webrtc) return await contact.webrtc.respond(signals);
70
+
71
+ this.host.noteContactForTransport(contact);
72
+ contact.ensureWebRTC();
73
+ return await contact.webrtc.respond(signals);
45
74
  }
46
75
  get webrtcLabel() {
47
76
  return `@${this.host.contact.sname} ==> ${this.sname}`;
48
77
  }
49
78
 
50
- ensureWebRTC(initiate = false, timeoutMS = this.host.timeoutMS || 5e3) { // If not already configured, sets up contact to have properties:
79
+ ensureWebRTC(initiate = false, timeoutMS = this.host.timeoutMS || 5e3) { // Ensure we are connected, if possible.
80
+ // If not already configured, sets up contact to have properties:
51
81
  // - connection - a promise for an open webrtc data channel:
52
82
  // this.send(string) puts data on the channel
53
83
  // incomming messages are dispatched to receiveWebRTC(string)
@@ -68,7 +98,7 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
68
98
  const onclose = () => { // Does NOT mean that the far side has gone away. It could just be over maxTransports.
69
99
  this.host.log('connection closed');
70
100
  resolve(null); // closed promise
71
- this.webrtc = this.connection = this.overlay = null;
101
+ this.webrtc = this.connection = this.unsafeData = null;
72
102
  };
73
103
  if (initiate) {
74
104
  if (bootstrapHost/* || isServerNode*/) {
@@ -90,39 +120,32 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
90
120
  dataChannel.addEventListener('message', event => this.receiveWebRTC(event.data));
91
121
  if (this.host.debug) await webrtc.reportConnection(true); // TODO: make this asymchronous?
92
122
  if (webrtc.statsElapsed > 500) this.host.xlog(`** slow connection to ${this.sname} took ${webrtc.statsElapsed.toLocaleString()} ms. **`);
123
+ this.unsafeData = dataChannel;
93
124
  return dataChannel;
94
125
  });
95
- const overlayChannelName = 'overlay';
96
- const overlayPromise = webrtc.getDataChannelPromise(overlayChannelName);
97
- webrtc.createChannel(overlayChannelName, {negotiated: true});
98
- overlayPromise.then(async overlay => {
99
- overlay.addEventListener('message', event => host.messageHandler(event.data));
100
- return overlay;
101
- });
102
126
  if (!timeoutMS) {
103
127
  this.connection = channelPromise;
104
- this.overlay = overlayPromise;
105
128
  return;
106
129
  }
107
130
  const timerPromise = new Promise(expired => {
108
131
  timeout = setTimeout(async () => {
109
132
  const now = Date.now();
110
- this.host.xlog('**** connection timeout', this.sname, now - start,
111
- 'status:', webrtc.pc.connectionState, 'signaling:', webrtc.pc.signalingState,
112
- 'last signal:', now - webrtc.lastOutboundSignal,
113
- 'last send:', now - webrtc.lastOutboundSend,
114
- 'last response:', now - webrtc.lastResponse,
115
- '****');
133
+ this.host.xlog('Unable to connect to', this.sname);
134
+ // this.host.xlog('**** connection timeout', this.sname, now - start,
135
+ // 'status:', webrtc.pc.connectionState, 'signaling:', webrtc.pc.signalingState,
136
+ // 'last signal:', now - webrtc.lastOutboundSignal,
137
+ // 'last send:', now - webrtc.lastOutboundSend,
138
+ // 'last response:', now - webrtc.lastResponse,
139
+ // '****');
116
140
  onclose();
117
141
  await this.host.removeContact(this); // fixme?
118
142
  expired(null);
119
143
  }, timeoutMS);
120
144
  });
121
145
  this.connection = Promise.race([channelPromise, timerPromise]);
122
- this.overlay = Promise.race([overlayPromise, timerPromise]);
123
146
  }
124
147
  async connect() { // Connect from host to node, promising a possibly cloned contact that has been noted.
125
- // Creates a WebRTC instance and uses it's connectVia to to signal.
148
+ // Creates a connected WebRTC instance.
126
149
  const contact = this.host.noteContactForTransport(this);
127
150
  ///if (contact.connection) contact.host.xlog('connect existing', contact.sname, contact.counter);
128
151
 
@@ -131,25 +154,24 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
131
154
  // Anyone in the DHT can connect to another DHT node through a sponsor.
132
155
  if (contact.connection) return contact.connection;
133
156
  contact.ensureWebRTC(true);
134
- await Promise.all([this.connection, this.overlay]);
157
+ await this.connection;
135
158
  return this.connection;
136
159
  }
137
- async signals(senderSname, ...signals) { // Accept directed WebRTC signals from a sender sname, creating if necessary the
138
- // new contact on host to receive them, and promising a response.
139
- let contact = await this.ensureRemoteContact(senderSname);
140
-
141
- if (contact.webrtc) return await contact.webrtc.respond(signals);
142
-
143
- this.host.noteContactForTransport(contact);
144
- contact.ensureWebRTC();
145
- return await contact.webrtc.respond(signals);
146
- }
147
160
 
148
161
  async send(message) { // Promise to send through previously opened connection promise.
149
162
  let channel = await this.connection;
150
163
  if (channel?.readyState === 'open') channel.send(JSON.stringify(message));
151
164
  else this.host.xlog('Unable to open channel');
152
165
  }
166
+ synchronousSend(message) { // this.send awaits channel open promise. This is if we know it has been opened.
167
+ if (this.unsafeData?.readyState !== 'open') return; // But it may have since been closed.
168
+ this.host.log('sending', message, 'to', this.sname);
169
+ try {
170
+ this.unsafeData.send(JSON.stringify(message));
171
+ } catch (e) { // Some webrtc can change readyState in background.
172
+ this.host.log(e);
173
+ }
174
+ }
153
175
  getName(sname) { // Answer name from sname.
154
176
  if (sname.startsWith(this.constructor.serverSignifier)) return sname.slice(1);
155
177
  return sname;
@@ -172,79 +194,59 @@ export class WebContact extends Contact { // Our wrapper for the means of contac
172
194
  else if (typeof(sponsor) === 'string') contact.bootstrapHost = sponsor;
173
195
  return contact;
174
196
  }
175
- receiveRPC(method, sender, key, ...rest) { // Receive an RPC from sender, dispatch, and return that value, which will be awaited and sent back to sender.
176
- if (method === 'forwardSignals') { // Can't be handled by Node, because 'forwardSignals' is specific to WebContact.
177
- const [sendingSname, ...signals] = rest;
178
- if (key === this.host.key) { // for us!
179
- return this.signals(...rest);
180
- } else { // Forward to the target.
181
- const target = this.host.findContactByKey(key);
182
- if (!target) {
183
- console.log(this.host.contact.sname, 'does not have contact to', sendingSname);
184
- return null;
185
- }
186
- return target.sendRPC('forwardSignals', key, ...rest);
187
- }
188
- }
189
- return super.receiveRPC(method, sender, key, ...rest);
190
- }
191
- inFlight = new Map();
192
- async transmitRPC(method, key, ...rest) { // Must return a promise.
193
- const MAX_PING_MS = 250; // No including connect time. These are single-hop WebRTC data channels.
197
+ serializeRequest(messageTag, method, sender, targetKey, ...rest) { // Stringify sender and targetKey.
198
+ Node.assert(sender instanceof Contact, 'no sender', sender);
199
+ return [messageTag, method, sender.sname, targetKey.toString(), ...rest];
200
+ }
201
+ async deserializeRequest(method, sender, targetKey, ...rest) { // Inverse of serializeRequest. Response object will be spread for Node receiveRPC.
202
+ // TODO: Currently, parameters do NOT include messageTag! (Because of how receiveRPC is called without it.)
203
+ return [method, await this.ensureRemoteContact(sender), BigInt(targetKey), ...rest];
204
+ }
205
+ isSignalResponse(response) {
206
+ const first = response[0];
207
+ if (!first) return false;
208
+ if (('description' in first) || ('candidate' in first)) return true;
209
+ return false;
210
+ }
211
+ serializeResponse(response) {
212
+ if (!this.host.constructor.isContactsResult(response)) return response;
213
+ if (this.isSignalResponse(response)) return response;
214
+ return response.map(helper => [helper.contact.sname, helper.distance.toString()]);
215
+ }
216
+ async deserializeResponse(result) {
217
+ let response;
218
+ if (!Node.isContactsResult(result)) return result;
219
+ if (!result.length) return result;
220
+ if (this.isSignalResponse(result)) return result;
221
+ return await Promise.all(result.map(async ([sname, distance]) =>
222
+ new Helper(await this.ensureRemoteContact(sname, this), BigInt(distance))));
223
+ }
224
+ async transmitRPC(messageTag, method, ...rest) { // Must return a promise.
194
225
  // this.host.log('transmit to', this.sname, this.connection ? 'with connection' : 'WITHOUT connection');
195
- if (!await this.connect()) return null;
196
- const sender = this.host.contact.sname;
197
- // uuid so that the two sides don't send a request with the same id to each other.
198
- // Alternatively, we could concatenate a counter to our host.name.
199
- const messageTag = uuidv4();
200
- const responsePromise = new Promise(resolve => this.inFlight.set(messageTag, resolve));
201
- const message = [messageTag, method, sender, key.toString(), ...rest];
202
- this.send(message);
203
- const timeout = Node.delay(MAX_PING_MS, null); // Faster than waiting for webrtc to observe a close
204
- return await Promise.race([responsePromise, timeout, this.closed]);
226
+ const responsePromise = this.getResponsePromise(messageTag);
227
+ await this.send([messageTag, method, ...rest]);
228
+ return await Promise.race([responsePromise, this.rpcTimeout(method), this.closed]);
205
229
  }
230
+
206
231
  async receiveWebRTC(dataString) { // Handle receipt of a WebRTC data channel message that was sent to this contact.
207
232
  // The message could the start of an RPC sent from the peer, or it could be a response to an RPC that we made.
208
233
  // As we do the latter, we generate and note (in transmitRPC) a message tag included in the message.
209
- // If we find that in our inFlight tags, then the message is a response.
234
+ // If we find that in our messageResolvers tags, then the message is a response.
210
235
  if (dataString === '"bye"') { // Special messsage that the other side is disconnecting, so we can clean up early.
211
236
  this.webrtc.close();
212
- this.host.xlog('removing disconnected contact');
237
+ this.host.xlog('removing disconnected contact', this.sname);
213
238
  await this.host.removeContact(this); // TODO: Make sure we're not invoking this in maxTransports cases.
214
239
  return;
215
240
  }
216
241
  const [messageTag, ...data] = JSON.parse(dataString);
217
- const responder = this.inFlight.get(messageTag);
218
- if (responder) { // A response to something we sent and are waiting for.
219
- let [result] = data;
220
- this.inFlight.delete(messageTag);
221
- if (Array.isArray(result)) {
222
- if (!result.length) responder(result);
223
- const first = result[0];
224
- const isSignal = Array.isArray(first) && ['offer', 'answer', 'icecandidate'].includes(first[0]);
225
- if (isSignal) {
226
- responder(result); // This could be the sponsor or the original sender. Either way, it will know what to do.
227
- } else {
228
- responder(await Promise.all(result.map(async ([sname, distance]) =>
229
- new Helper(await this.ensureRemoteContact(sname, this), BigInt(distance)))));
230
- }
231
- } else {
232
- responder(result);
233
- }
234
- } else { // An incoming request.
235
- const [method, senderLabel, key, ...rest] = data;
236
- const sender = await this.ensureRemoteContact(senderLabel);
237
- let response = await this.receiveRPC(method, sender, BigInt(key), ...rest);
238
- if ((method !== 'forwardSignals') && this.host.constructor.isContactsResult(response)) {
239
- response = response.map(helper => [helper.contact.sname, helper.distance.toString()]);
240
- }
241
- this.send([messageTag, response]);
242
- }
242
+ await this.receiveRPC(messageTag, ...data);
243
243
  }
244
244
  async disconnectTransport() {
245
- await this.send('bye'); await Node.delay(50); // FIXME: Hack: We'll need to be able to pass tests without this, too.
246
-
247
- this.webrtc?.close();
248
- this.connection = this.webrtc = this.initiating = null;
245
+ if (!this.connection) return;
246
+ super.disconnectTransport();
247
+ Node.delay(100).then(() => { // Allow time for super to send close/bye message.
248
+ this.webrtc?.close();
249
+ this.connection = this.webrtc = this.initiating = null;
250
+ });
249
251
  }
250
252
  }
@@ -1,56 +0,0 @@
1
- #!/usr/bin/env npx jasmine
2
- const { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } = globalThis; // For linters.
3
- import process from 'node:process';
4
- import { exec } from 'node:child_process';
5
- import { v4 as uuidv4 } from 'uuid';
6
- import { WebContact, Node } from '../index.js';
7
-
8
- // I cannot get yargs to work properly within jasmine. Get args by hand.
9
- // Note: jasmine will treat --options as arguments to itself. To pass them to the script, you need to separate with '--'.
10
- const nWritesIndex = process.argv.indexOf('--nWrites');
11
- const baseURLIndex = process.argv.indexOf('--baseURL');
12
- const waitBeforeReadIndex = process.argv.indexOf('--waitBeforeRead');
13
- const verboseIndex = process.argv.indexOf('--verbose');
14
- const shutdownIndex = process.argv.indexOf('--shutdown');
15
-
16
- const nWrites = nWritesIndex >= 0 ? JSON.parse(process.argv[nWritesIndex + 1]) : 10;
17
- const baseURL = baseURLIndex >= 0 ? process.argv[baseURLIndex + 1] : 'http://localhost:3000/kdht';
18
- const waitBeforeRead = waitBeforeReadIndex >= 0 ? JSON.parse(process.argv[waitBeforeReadIndex + 1]) : 10;
19
- const verbose = verboseIndex >= 0 ? JSON.parse( process.argv[verboseIndex + 1] || 'true' ) : false;
20
- const shutdown = shutdownIndex >= 0 ? JSON.parse( process.argv[shutdownIndex + 1] || 'true' ) : true;
21
-
22
- describe("DHT write/read", function () {
23
- let contact;
24
- beforeAll(async function () {
25
- contact = await WebContact.create({name: uuidv4(), debug: verbose});
26
- const bootstrapName = await contact.fetchBootstrap(baseURL);
27
- const bootstrapContact = await contact.ensureRemoteContact(bootstrapName, baseURL);
28
- console.log(new Date(), contact.sname, 'joining', bootstrapContact.sname);
29
- await contact.join(bootstrapContact);
30
- console.log(new Date(), contact.sname, 'joined');
31
- for (let index = 0; index < nWrites; index++) {
32
- const wrote = await contact.storeValue(index, index);
33
- console.log('Wrote', index);
34
- }
35
- if (waitBeforeRead) {
36
- console.log(new Date(), `Written. Waiting ${waitBeforeRead.toLocaleString()} ms before reading.`);
37
- await Node.delay(waitBeforeRead);
38
- }
39
- console.log(new Date(), 'Reading');
40
- }, 5e3 * nWrites + 2 * Node.refreshTimeIntervalMS);
41
- afterAll(async function () {
42
- if (shutdown) {
43
- contact.disconnect();
44
- exec('pkill kdht-portal-server');
45
- } else {
46
- contact.disconnect();
47
- }
48
- });
49
- for (let index = 0; index < nWrites; index++) {
50
- it(`reads ${index}.`, async function () {
51
- const read = await contact.node.locateValue(index);
52
- console.log('read', read);
53
- expect(read).toBe(index);
54
- }, 10e3); // Can take longer to re-establish multiple connections.
55
- }
56
- });