smoldot 1.0.0 → 1.0.2

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.
@@ -49,6 +49,8 @@ function start(options) {
49
49
  return Promise.resolve((0, pako_1.inflate)((0, base64_js_1.classicDecode)(input)));
50
50
  },
51
51
  registerShouldPeriodicallyYield: (callback) => {
52
+ if (typeof document === 'undefined') // We might be in a web worker.
53
+ return [false, () => { }];
52
54
  const wrappedCallback = () => callback(document.visibilityState === 'visible');
53
55
  document.addEventListener('visibilitychange', wrappedCallback);
54
56
  return [document.visibilityState === 'visible', () => { document.removeEventListener('visibilitychange', wrappedCallback); }];
@@ -78,9 +80,8 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
78
80
  // Attempt to parse the multiaddress.
79
81
  // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)
80
82
  const wsParsed = config.address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
81
- const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc\/certhash\/(.*?)$/);
83
+ const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/);
82
84
  if (wsParsed != null) {
83
- let connection;
84
85
  const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss';
85
86
  if ((proto == 'ws' && forbidWs) ||
86
87
  (proto == 'ws' && wsParsed[2] != 'localhost' && wsParsed[2] != '127.0.0.1' && forbidNonLocalWs) ||
@@ -90,10 +91,36 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
90
91
  const url = (wsParsed[1] == 'ip6') ?
91
92
  (proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) :
92
93
  (proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
93
- connection = new WebSocket(url);
94
+ const connection = new WebSocket(url);
94
95
  connection.binaryType = 'arraybuffer';
96
+ const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
97
+ const checkBufferedAmount = () => {
98
+ if (connection.readyState != 1)
99
+ return;
100
+ // Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
101
+ // of all the data that has been sent, but that might not be the case. For this
102
+ // reason, we use `bufferedAmount` as a hint rather than a correct value.
103
+ const bufferedAmount = connection.bufferedAmount;
104
+ let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
105
+ if (wasSent < 0)
106
+ wasSent = 0;
107
+ bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
108
+ if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
109
+ setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
110
+ bufferedAmountCheck.nextTimeout *= 2;
111
+ if (bufferedAmountCheck.nextTimeout > 500)
112
+ bufferedAmountCheck.nextTimeout = 500;
113
+ }
114
+ // Note: it is important to call `onWritableBytes` at the very end, as it might
115
+ // trigger a call to `send`.
116
+ if (wasSent != 0)
117
+ config.onWritableBytes(wasSent);
118
+ };
95
119
  connection.onopen = () => {
96
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
120
+ config.onOpen({
121
+ type: 'single-stream', handshake: 'multistream-select-noise-yamux',
122
+ initialWritableBytes: 1024 * 1024, writeClosable: false,
123
+ });
97
124
  };
98
125
  connection.onclose = (event) => {
99
126
  const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
@@ -112,7 +139,13 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
112
139
  },
113
140
  send: (data) => {
114
141
  connection.send(data);
142
+ if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
143
+ bufferedAmountCheck.nextTimeout = 10;
144
+ setTimeout(checkBufferedAmount, 10);
145
+ }
146
+ bufferedAmountCheck.quenedUnreportedBytes += data.length;
115
147
  },
148
+ closeSend: () => { throw new Error('Wrong connection type'); },
116
149
  openOutSubstream: () => { throw new Error('Wrong connection type'); }
117
150
  };
118
151
  }
@@ -162,16 +195,18 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
162
195
  pc.onnegotiationneeded = null;
163
196
  pc.ondatachannel = null;
164
197
  for (const channel of Array.from(dataChannels.values())) {
165
- channel.onopen = null;
166
- channel.onerror = null;
167
- channel.onclose = null;
168
- channel.onmessage = null;
198
+ channel.channel.onopen = null;
199
+ channel.channel.onerror = null;
200
+ channel.channel.onclose = null;
201
+ channel.channel.onbufferedamountlow = null;
202
+ channel.channel.onmessage = null;
169
203
  }
170
204
  dataChannels.clear();
171
205
  if (handshakeDataChannel) {
172
206
  handshakeDataChannel.onopen = null;
173
207
  handshakeDataChannel.onerror = null;
174
208
  handshakeDataChannel.onclose = null;
209
+ handshakeDataChannel.onbufferedamountlow = null;
175
210
  handshakeDataChannel.onmessage = null;
176
211
  }
177
212
  handshakeDataChannel = undefined;
@@ -200,7 +235,7 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
200
235
  }
201
236
  else {
202
237
  console.assert(direction !== 'outbound' || !handshakeDataChannel, "handshakeDataChannel still defined");
203
- config.onStreamOpened(dataChannelId, direction);
238
+ config.onStreamOpened(dataChannelId, direction, 65536);
204
239
  }
205
240
  };
206
241
  dataChannel.onerror = dataChannel.onclose = (_error) => {
@@ -222,6 +257,7 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
222
257
  handshakeDataChannel.onopen = null;
223
258
  handshakeDataChannel.onerror = null;
224
259
  handshakeDataChannel.onclose = null;
260
+ handshakeDataChannel.onbufferedamountlow = null;
225
261
  handshakeDataChannel.onmessage = null;
226
262
  handshakeDataChannel = undefined;
227
263
  }
@@ -238,12 +274,18 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
238
274
  config.onStreamReset(dataChannelId);
239
275
  }
240
276
  };
277
+ dataChannel.onbufferedamountlow = () => {
278
+ const channel = dataChannels.get(dataChannelId);
279
+ const val = channel.bufferedBytes;
280
+ channel.bufferedBytes = 0;
281
+ config.onWritableBytes(val, dataChannelId);
282
+ };
241
283
  dataChannel.onmessage = (m) => {
242
284
  // The `data` field is an `ArrayBuffer`.
243
285
  config.onMessage(new Uint8Array(m.data), dataChannelId);
244
286
  };
245
287
  if (direction !== 'first-outbound')
246
- dataChannels.set(dataChannelId, dataChannel);
288
+ dataChannels.set(dataChannelId, { channel: dataChannel, bufferedBytes: 0 });
247
289
  else
248
290
  handshakeDataChannel = dataChannel;
249
291
  };
@@ -385,7 +427,8 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
385
427
  // (UDP or TCP)
386
428
  "a=sctp-port:5000" + "\n" +
387
429
  // The maximum SCTP user message size (in bytes) (RFC8841)
388
- "a=max-message-size:16384" + "\n" + // TODO: should this be part of the spec?
430
+ // Setting this field is part of the libp2p spec.
431
+ "a=max-message-size:16384" + "\n" +
389
432
  // A transport address for a candidate that can be used for connectivity
390
433
  // checks (RFC8839).
391
434
  "a=candidate:1 1 UDP 1 " + targetIp + " " + targetPort + " typ host" + "\n";
@@ -414,17 +457,21 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
414
457
  }
415
458
  else {
416
459
  const channel = dataChannels.get(streamId);
417
- channel.onopen = null;
418
- channel.onerror = null;
419
- channel.onclose = null;
420
- channel.onmessage = null;
421
- channel.close();
460
+ channel.channel.onopen = null;
461
+ channel.channel.onerror = null;
462
+ channel.channel.onclose = null;
463
+ channel.channel.onbufferedamountlow = null;
464
+ channel.channel.onmessage = null;
465
+ channel.channel.close();
422
466
  dataChannels.delete(streamId);
423
467
  }
424
468
  },
425
469
  send: (data, streamId) => {
426
- dataChannels.get(streamId).send(data);
470
+ const channel = dataChannels.get(streamId);
471
+ channel.channel.send(data);
472
+ channel.bufferedBytes += data.length;
427
473
  },
474
+ closeSend: () => { throw new Error('Wrong connection type'); },
428
475
  openOutSubstream: () => {
429
476
  // `openOutSubstream` can only be called after we have called `config.onOpen`, therefore
430
477
  // `pc` is guaranteed to be non-null.
@@ -436,8 +483,8 @@ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
436
483
  // We need to check again if `handshakeDataChannel` is still defined, as the
437
484
  // connection might have been closed.
438
485
  if (handshakeDataChannel) {
439
- config.onStreamOpened(handshakeDataChannel.id, 'outbound');
440
- dataChannels.set(handshakeDataChannel.id, handshakeDataChannel);
486
+ config.onStreamOpened(handshakeDataChannel.id, 'outbound', 1024 * 1024);
487
+ dataChannels.set(handshakeDataChannel.id, { channel: handshakeDataChannel, bufferedBytes: 0 });
441
488
  handshakeDataChannel = undefined;
442
489
  }
443
490
  }))();
@@ -109,7 +109,6 @@ function trustedBase64Decode(base64) {
109
109
  * @throws {@link ConnectionError} If the multiaddress couldn't be parsed or contains an invalid protocol.
110
110
  */
111
111
  function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
112
- let connection;
113
112
  // Attempt to parse the multiaddress.
114
113
  // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)
115
114
  const wsParsed = config.address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
@@ -124,24 +123,72 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
124
123
  const url = (wsParsed[1] == 'ip6') ?
125
124
  (proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) :
126
125
  (proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
127
- connection = {
128
- ty: 'websocket',
129
- socket: new WebSocket(url)
126
+ const socket = new WebSocket(url);
127
+ socket.binaryType = 'arraybuffer';
128
+ const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
129
+ const checkBufferedAmount = () => {
130
+ if (socket.readyState != 1)
131
+ return;
132
+ // Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
133
+ // of all the data that has been sent, but that might not be the case. For this
134
+ // reason, we use `bufferedAmount` as a hint rather than a correct value.
135
+ const bufferedAmount = socket.bufferedAmount;
136
+ let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
137
+ if (wasSent < 0)
138
+ wasSent = 0;
139
+ bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
140
+ if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
141
+ setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
142
+ bufferedAmountCheck.nextTimeout *= 2;
143
+ if (bufferedAmountCheck.nextTimeout > 500)
144
+ bufferedAmountCheck.nextTimeout = 500;
145
+ }
146
+ // Note: it is important to call `onWritableBytes` at the very end, as it might
147
+ // trigger a call to `send`.
148
+ if (wasSent != 0)
149
+ config.onWritableBytes(wasSent);
130
150
  };
131
- connection.socket.binaryType = 'arraybuffer';
132
- connection.socket.onopen = () => {
133
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
151
+ socket.onopen = () => {
152
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
134
153
  };
135
- connection.socket.onclose = (event) => {
154
+ socket.onclose = (event) => {
136
155
  const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
137
156
  config.onConnectionReset(message);
138
157
  };
139
- connection.socket.onmessage = (msg) => {
158
+ socket.onmessage = (msg) => {
140
159
  config.onMessage(new Uint8Array(msg.data));
141
160
  };
161
+ return {
162
+ reset: () => {
163
+ // We can't set these fields to null because the TypeScript definitions don't
164
+ // allow it, but we can set them to dummy values.
165
+ socket.onopen = () => { };
166
+ socket.onclose = () => { };
167
+ socket.onmessage = () => { };
168
+ socket.onerror = () => { };
169
+ socket.close();
170
+ },
171
+ send: (data) => {
172
+ // The WebSocket library that we use seems to spontaneously transition connections
173
+ // to the "closed" state but not call the `onclosed` callback immediately. Calling
174
+ // `send` on that object throws an exception. In order to avoid panicking smoldot,
175
+ // we thus absorb any exception thrown here.
176
+ // See also <https://github.com/paritytech/smoldot/issues/2937>.
177
+ try {
178
+ socket.send(data);
179
+ if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
180
+ bufferedAmountCheck.nextTimeout = 10;
181
+ setTimeout(checkBufferedAmount, 10);
182
+ }
183
+ bufferedAmountCheck.quenedUnreportedBytes += data.length;
184
+ }
185
+ catch (_error) { }
186
+ },
187
+ closeSend: () => { throw new Error('Wrong connection type'); },
188
+ openOutSubstream: () => { throw new Error('Wrong connection type'); }
189
+ };
142
190
  }
143
191
  else if (tcpParsed != null) {
144
- // `net` module will be missing when we're not in NodeJS.
145
192
  if (forbidTcp) {
146
193
  throw new instance_js_1.ConnectionError('TCP connections not available');
147
194
  }
@@ -156,13 +203,12 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
156
203
  return null;
157
204
  })
158
205
  };
159
- connection = { ty: 'tcp', socket };
160
206
  socket.inner = socket.inner.then((established) => {
161
207
  // TODO: at the time of writing of this comment, `setNoDelay` is still unstable
162
208
  //established.setNoDelay();
163
209
  if (socket.destroyed)
164
210
  return established;
165
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
211
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: true });
166
212
  // Spawns an asynchronous task that continuously reads from the socket.
167
213
  // Every time data is read, the task re-executes itself in order to continue reading.
168
214
  // The task ends automatically if an EOF or error is detected, which should also happen
@@ -194,53 +240,21 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
194
240
  read(new Uint8Array(1024));
195
241
  return established;
196
242
  });
197
- }
198
- else {
199
- throw new instance_js_1.ConnectionError('Unrecognized multiaddr format');
200
- }
201
- return {
202
- reset: () => {
203
- if (connection.ty == 'websocket') {
204
- // WebSocket
205
- // We can't set these fields to null because the TypeScript definitions don't
206
- // allow it, but we can set them to dummy values.
207
- connection.socket.onopen = () => { };
208
- connection.socket.onclose = () => { };
209
- connection.socket.onmessage = () => { };
210
- connection.socket.onerror = () => { };
211
- connection.socket.close();
212
- }
213
- else {
214
- // TCP
215
- connection.socket.destroyed = true;
216
- connection.socket.inner.then((connec) => connec.close());
217
- }
218
- },
219
- send: (data) => {
220
- if (connection.ty == 'websocket') {
221
- // WebSocket
222
- // The WebSocket library that we use seems to spontaneously transition connections
223
- // to the "closed" state but not call the `onclosed` callback immediately. Calling
224
- // `send` on that object throws an exception. In order to avoid panicking smoldot,
225
- // we thus absorb any exception thrown here.
226
- // See also <https://github.com/paritytech/smoldot/issues/2937>.
227
- try {
228
- connection.socket.send(data);
229
- }
230
- catch (_error) { }
231
- }
232
- else {
233
- // TCP
234
- // TODO: at the moment, sending data doesn't have any back-pressure mechanism; as such, we just buffer data indefinitely
243
+ return {
244
+ reset: () => {
245
+ socket.destroyed = true;
246
+ socket.inner.then((connec) => connec.close());
247
+ },
248
+ send: (data) => {
235
249
  let dataCopy = Uint8Array.from(data); // Deep copy of the data
236
- const socket = connection.socket;
237
- connection.socket.inner = connection.socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
250
+ socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
238
251
  while (dataCopy.length > 0) {
239
252
  if (socket.destroyed || c === null)
240
253
  return c;
241
254
  let outcome;
242
255
  try {
243
256
  outcome = yield c.write(dataCopy);
257
+ config.onWritableBytes(dataCopy.length);
244
258
  }
245
259
  catch (error) {
246
260
  // The type of `error` is unclear, but we assume that it implements `Error`
@@ -260,8 +274,17 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
260
274
  }
261
275
  return c;
262
276
  }));
263
- }
264
- },
265
- openOutSubstream: () => { throw new Error('Wrong connection type'); }
266
- };
277
+ },
278
+ closeSend: () => {
279
+ socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
280
+ yield (c === null || c === void 0 ? void 0 : c.closeWrite());
281
+ return c;
282
+ }));
283
+ },
284
+ openOutSubstream: () => { throw new Error('Wrong connection type'); }
285
+ };
286
+ }
287
+ else {
288
+ throw new instance_js_1.ConnectionError('Unrecognized multiaddr format');
289
+ }
267
290
  }
@@ -51,7 +51,7 @@ function start(options) {
51
51
  return node_perf_hooks_1.performance.now();
52
52
  },
53
53
  getRandomValues: (buffer) => {
54
- if (buffer.length >= 65536)
54
+ if (buffer.length >= 1024 * 1024)
55
55
  throw new Error('getRandomValues buffer too large');
56
56
  (0, node_crypto_1.randomFillSync)(buffer);
57
57
  },
@@ -68,7 +68,6 @@ exports.start = start;
68
68
  * @throws {@link ConnectionError} If the multiaddress couldn't be parsed or contains an invalid protocol.
69
69
  */
70
70
  function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
71
- let connection;
72
71
  // Attempt to parse the multiaddress.
73
72
  // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)
74
73
  const wsParsed = config.address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
@@ -85,8 +84,33 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
85
84
  (proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
86
85
  const socket = new ws_1.WebSocket(url);
87
86
  socket.binaryType = 'arraybuffer';
87
+ const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
88
+ const checkBufferedAmount = () => {
89
+ if (socket.readyState != 1)
90
+ return;
91
+ // Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
92
+ // of all the data that has been sent, but that seems to not be the case. It is
93
+ // unclear whether this is intended or a bug, but is is likely that `bufferedAmount`
94
+ // also includes WebSocket headers. For this reason, we use `bufferedAmount` as a hint
95
+ // rather than a correct value.
96
+ const bufferedAmount = socket.bufferedAmount;
97
+ let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
98
+ if (wasSent < 0)
99
+ wasSent = 0;
100
+ bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
101
+ if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
102
+ setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
103
+ bufferedAmountCheck.nextTimeout *= 2;
104
+ if (bufferedAmountCheck.nextTimeout > 500)
105
+ bufferedAmountCheck.nextTimeout = 500;
106
+ }
107
+ // Note: it is important to call `onWritableBytes` at the very end, as it might
108
+ // trigger a call to `send`.
109
+ if (wasSent != 0)
110
+ config.onWritableBytes(wasSent);
111
+ };
88
112
  socket.onopen = () => {
89
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
113
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
90
114
  };
91
115
  socket.onclose = (event) => {
92
116
  const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
@@ -106,7 +130,27 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
106
130
  socket.onmessage = (msg) => {
107
131
  config.onMessage(new Uint8Array(msg.data));
108
132
  };
109
- connection = { ty: 'websocket', socket };
133
+ return {
134
+ reset: () => {
135
+ // We can't set these fields to null because the TypeScript definitions don't
136
+ // allow it, but we can set them to dummy values.
137
+ socket.onopen = () => { };
138
+ socket.onclose = () => { };
139
+ socket.onmessage = () => { };
140
+ socket.onerror = () => { };
141
+ socket.close();
142
+ },
143
+ send: (data) => {
144
+ socket.send(data);
145
+ if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
146
+ bufferedAmountCheck.nextTimeout = 10;
147
+ setTimeout(checkBufferedAmount, 10);
148
+ }
149
+ bufferedAmountCheck.quenedUnreportedBytes += data.length;
150
+ },
151
+ closeSend: () => { throw new Error('Wrong connection type'); },
152
+ openOutSubstream: () => { throw new Error('Wrong connection type'); }
153
+ };
110
154
  }
111
155
  else if (tcpParsed != null) {
112
156
  // `net` module will be missing when we're not in NodeJS.
@@ -117,14 +161,18 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
117
161
  host: tcpParsed[2],
118
162
  port: parseInt(tcpParsed[3], 10),
119
163
  });
120
- connection = { ty: 'tcp', socket };
121
- connection.socket.setNoDelay();
122
- connection.socket.on('connect', () => {
164
+ // Number of bytes queued using `socket.write` and where `write` has returned false.
165
+ const drainingBytes = { num: 0 };
166
+ socket.setNoDelay();
167
+ socket.on('connect', () => {
123
168
  if (socket.destroyed)
124
169
  return;
125
- config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
170
+ config.onOpen({
171
+ type: 'single-stream', handshake: 'multistream-select-noise-yamux',
172
+ initialWritableBytes: socket.writableHighWaterMark, writeClosable: true
173
+ });
126
174
  });
127
- connection.socket.on('close', (hasError) => {
175
+ socket.on('close', (hasError) => {
128
176
  if (socket.destroyed)
129
177
  return;
130
178
  // NodeJS doesn't provide a reason why the closing happened, but only
@@ -132,43 +180,46 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
132
180
  const message = hasError ? "Error" : "Closed gracefully";
133
181
  config.onConnectionReset(message);
134
182
  });
135
- connection.socket.on('error', () => { });
136
- connection.socket.on('data', (message) => {
183
+ socket.on('error', () => { });
184
+ socket.on('data', (message) => {
137
185
  if (socket.destroyed)
138
186
  return;
139
187
  config.onMessage(new Uint8Array(message.buffer));
140
188
  });
189
+ socket.on('drain', () => {
190
+ // The bytes queued using `socket.write` and where `write` has returned false have now
191
+ // been sent. Notify the API that it can write more data.
192
+ if (socket.destroyed)
193
+ return;
194
+ const val = drainingBytes.num;
195
+ drainingBytes.num = 0;
196
+ config.onWritableBytes(val);
197
+ });
198
+ return {
199
+ reset: () => {
200
+ socket.destroy();
201
+ },
202
+ send: (data) => {
203
+ const dataLen = data.length;
204
+ const allWritten = socket.write(data);
205
+ if (allWritten) {
206
+ setImmediate(() => {
207
+ if (!socket.writable)
208
+ return;
209
+ config.onWritableBytes(dataLen);
210
+ });
211
+ }
212
+ else {
213
+ drainingBytes.num += dataLen;
214
+ }
215
+ },
216
+ closeSend: () => {
217
+ socket.end();
218
+ },
219
+ openOutSubstream: () => { throw new Error('Wrong connection type'); }
220
+ };
141
221
  }
142
222
  else {
143
223
  throw new instance_js_1.ConnectionError('Unrecognized multiaddr format');
144
224
  }
145
- return {
146
- reset: () => {
147
- if (connection.ty == 'websocket') {
148
- // WebSocket
149
- // We can't set these fields to null because the TypeScript definitions don't
150
- // allow it, but we can set them to dummy values.
151
- connection.socket.onopen = () => { };
152
- connection.socket.onclose = () => { };
153
- connection.socket.onmessage = () => { };
154
- connection.socket.onerror = () => { };
155
- connection.socket.close();
156
- }
157
- else {
158
- // TCP
159
- connection.socket.destroy();
160
- }
161
- },
162
- send: (data) => {
163
- if (connection.ty == 'websocket') {
164
- // WebSocket
165
- connection.socket.send(data);
166
- }
167
- else {
168
- // TCP
169
- connection.socket.write(data);
170
- }
171
- },
172
- openOutSubstream: () => { throw new Error('Wrong connection type'); }
173
- };
174
225
  }