smoldot 0.7.13 → 1.0.1
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/dist/cjs/client.d.ts +61 -28
- package/dist/cjs/index-browser.js +66 -19
- package/dist/cjs/index-deno.js +85 -58
- package/dist/cjs/index-nodejs.js +91 -40
- package/dist/cjs/instance/autogen/wasm0.js +1 -1
- package/dist/cjs/instance/autogen/wasm1.js +1 -1
- package/dist/cjs/instance/autogen/wasm2.js +1 -1
- package/dist/cjs/instance/bindings-smoldot-light.d.ts +36 -2
- package/dist/cjs/instance/bindings-smoldot-light.js +18 -4
- package/dist/cjs/instance/bindings.d.ts +3 -2
- package/dist/mjs/client.d.ts +61 -28
- package/dist/mjs/index-browser.js +66 -19
- package/dist/mjs/index-deno.js +85 -58
- package/dist/mjs/index-nodejs.js +91 -40
- package/dist/mjs/instance/autogen/wasm0.js +1 -1
- package/dist/mjs/instance/autogen/wasm1.js +1 -1
- package/dist/mjs/instance/autogen/wasm2.js +1 -1
- package/dist/mjs/instance/bindings-smoldot-light.d.ts +36 -2
- package/dist/mjs/instance/bindings-smoldot-light.js +18 -4
- package/dist/mjs/instance/bindings.d.ts +3 -2
- package/package.json +2 -2
package/dist/cjs/client.d.ts
CHANGED
|
@@ -26,17 +26,23 @@ export interface Client {
|
|
|
26
26
|
/**
|
|
27
27
|
* Connects to a chain.
|
|
28
28
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
29
|
+
* After you've called this function, the client will verify whether the chain specification is
|
|
30
|
+
* valid. Once this is done, the `Promise` returned by this function will yield a
|
|
31
|
+
* {@link Chain} that can be used to interact with that chain. Only after the `Promise` has
|
|
32
|
+
* yielded will the client actually start establishing networking connections to the chain.
|
|
33
|
+
*
|
|
34
|
+
* The `Promise` throws an exception if the chain specification isn't valid, or if the chain
|
|
35
|
+
* specification concerns a parachain but no corresponding relay chain can be found.
|
|
31
36
|
*
|
|
32
37
|
* Smoldot will automatically de-duplicate chains if multiple identical chains are added, in
|
|
33
38
|
* order to save resources. In other words, it is not a problem to call `addChain` multiple
|
|
34
|
-
* times with the same chain specifications and obtain multiple
|
|
39
|
+
* times with the same chain specifications and obtain multiple {@link Chain} objects.
|
|
35
40
|
* When the same client is used for multiple different purposes, you are in fact strongly
|
|
36
41
|
* encouraged to trust smoldot and not attempt to de-duplicate chains yourself, as determining
|
|
37
42
|
* whether two chains are identical is complicated and might have security implications.
|
|
38
43
|
*
|
|
39
|
-
* Smoldot tries to distribute CPU resources equally between all active
|
|
44
|
+
* Smoldot tries to distribute CPU resources equally between all active {@link Chain} objects
|
|
45
|
+
* of the same client.
|
|
40
46
|
*
|
|
41
47
|
* @param options Configuration of the chain to add.
|
|
42
48
|
*
|
|
@@ -48,6 +54,9 @@ export interface Client {
|
|
|
48
54
|
/**
|
|
49
55
|
* Terminates the client.
|
|
50
56
|
*
|
|
57
|
+
* This implicitly calls {@link Chain.remove} on all the chains associated with this client,
|
|
58
|
+
* then shuts down the client itself.
|
|
59
|
+
*
|
|
51
60
|
* Afterwards, trying to use the client or any of its chains again will lead to an exception
|
|
52
61
|
* being thrown.
|
|
53
62
|
*
|
|
@@ -70,10 +79,15 @@ export interface Chain {
|
|
|
70
79
|
* Be aware that some requests will cause notifications to be sent back using the same callback
|
|
71
80
|
* as the responses.
|
|
72
81
|
*
|
|
73
|
-
* A {@link MalformedJsonRpcError} is thrown if the request isn't a valid JSON-RPC request
|
|
74
|
-
* if the request is unreasonably large (64 MiB at the
|
|
75
|
-
*
|
|
76
|
-
*
|
|
82
|
+
* A {@link MalformedJsonRpcError} is thrown if the request isn't a valid JSON-RPC request
|
|
83
|
+
* (for example if it is not valid JSON) or if the request is unreasonably large (64 MiB at the
|
|
84
|
+
* time of writing of this comment).
|
|
85
|
+
* If, however, the request is a valid JSON-RPC request but that concerns an unknown method, or
|
|
86
|
+
* if for example some parameters are missing, an error response is properly generated and
|
|
87
|
+
* yielded through the JSON-RPC callback.
|
|
88
|
+
* In other words, a {@link MalformedJsonRpcError} is thrown in situations where something
|
|
89
|
+
* is *so wrong* with the request that it is not possible for smoldot to send back an error
|
|
90
|
+
* through the JSON-RPC callback.
|
|
77
91
|
*
|
|
78
92
|
* Two JSON-RPC APIs are supported by smoldot:
|
|
79
93
|
*
|
|
@@ -92,14 +106,17 @@ export interface Chain {
|
|
|
92
106
|
/**
|
|
93
107
|
* Waits for a JSON-RPC response or notification to be generated.
|
|
94
108
|
*
|
|
95
|
-
* If this function is called multiple times "simultaneously" (generating multiple different
|
|
96
|
-
* `Promise`s), each `Promise` will return a different JSON-RPC response or notification.
|
|
97
|
-
*
|
|
98
109
|
* Each chain contains a buffer of the responses waiting to be sent out. Calling this function
|
|
99
110
|
* pulls one element from the buffer. If this function is called at a slower rate than responses
|
|
100
|
-
* are generated, then buffer will eventually become full, at which point calling
|
|
111
|
+
* are generated, then the buffer will eventually become full, at which point calling
|
|
101
112
|
* {@link Chain.sendJsonRpc} will throw an exception.
|
|
102
113
|
*
|
|
114
|
+
* If this function is called multiple times "simultaneously" (generating multiple different
|
|
115
|
+
* `Promise`s), each `Promise` will return a different JSON-RPC response or notification. In
|
|
116
|
+
* that situation, there is no guarantee in the ordering in which the responses or notifications
|
|
117
|
+
* are yielded. Calling this function multiple times "simultaneously" is in general a niche
|
|
118
|
+
* corner case that you are encouraged to avoid.
|
|
119
|
+
*
|
|
103
120
|
* @throws {@link AlreadyDestroyedError} If the chain has been removed or the client has been terminated.
|
|
104
121
|
* @throws {@link JsonRpcDisabledError} If the JSON-RPC system was disabled in the options of the chain.
|
|
105
122
|
* @throws {@link CrashError} If the background client has crashed.
|
|
@@ -108,14 +125,15 @@ export interface Chain {
|
|
|
108
125
|
/**
|
|
109
126
|
* Disconnects from the blockchain.
|
|
110
127
|
*
|
|
111
|
-
* The JSON-RPC callback will no longer be called.
|
|
128
|
+
* The JSON-RPC callback will no longer be called. This is the case immediately after this
|
|
129
|
+
* function is called. Any on-going JSON-RPC request is instantaneously aborted.
|
|
112
130
|
*
|
|
113
131
|
* Trying to use the chain again will lead to an exception being thrown.
|
|
114
132
|
*
|
|
115
133
|
* If this chain is a relay chain, then all parachains that use it will continue to work. Smoldot
|
|
116
134
|
* automatically keeps alive all relay chains that have an active parachains. There is no need
|
|
117
|
-
* to track parachains and
|
|
118
|
-
* handled automatically.
|
|
135
|
+
* to track parachains and relay chains, or to destroy them in the correct order, as this is
|
|
136
|
+
* handled automatically internally.
|
|
119
137
|
*
|
|
120
138
|
* @throws {@link AlreadyDestroyedError} If the chain has already been removed or the client has been terminated.
|
|
121
139
|
* @throws {@link CrashError} If the background client has crashed.
|
|
@@ -140,10 +158,10 @@ export interface ClientOptions {
|
|
|
140
158
|
*/
|
|
141
159
|
logCallback?: LogCallback;
|
|
142
160
|
/**
|
|
143
|
-
* The client will never call the callback with a value of `level` superior to this value.
|
|
161
|
+
* The client will never call the log callback with a value of `level` superior to this value.
|
|
144
162
|
* Defaults to 3.
|
|
145
163
|
*
|
|
146
|
-
* While this filtering could be done
|
|
164
|
+
* While this filtering could be done manually in the `logCallback`, passing a maximum log level
|
|
147
165
|
* leads to better performances as the client doesn't even need to generate a `message` when it
|
|
148
166
|
* knows that this message isn't interesting.
|
|
149
167
|
*/
|
|
@@ -226,32 +244,47 @@ export interface AddChainOptions {
|
|
|
226
244
|
* `<client> build-spec --raw > spec.json`. Only "raw" chain specifications are supported by
|
|
227
245
|
* smoldot at the moment.
|
|
228
246
|
*
|
|
229
|
-
* If the chain specification contains a `
|
|
230
|
-
* the value in `
|
|
247
|
+
* If the chain specification contains a `relayChain` field, then smoldot will try to match
|
|
248
|
+
* the value in `relayChain` with the value in `id` of the chains in
|
|
249
|
+
* {@link AddChainOptions.potentialRelayChains}.
|
|
231
250
|
*/
|
|
232
251
|
chainSpec: string;
|
|
233
252
|
/**
|
|
234
|
-
* Content of the database of this chain.
|
|
235
|
-
*
|
|
253
|
+
* Content of the database of this chain.
|
|
254
|
+
*
|
|
255
|
+
* The content of the database can be obtained by using the
|
|
256
|
+
* `chainHead_unstable_finalizedDatabase` JSON-RPC function. This undocumented JSON-RPC function
|
|
257
|
+
* accepts one parameter of type `number` indicating an upper limit to the size of the database.
|
|
258
|
+
* The content of the database is always a UTF-8 string whose content is at the discretion of
|
|
259
|
+
* the smoldot implementation.
|
|
236
260
|
*
|
|
237
261
|
* Smoldot reserves the right to change its database format, making previous databases
|
|
238
|
-
* incompatible. For this reason, no error is generated if the content of the database is
|
|
239
|
-
* and/or can't be decoded.
|
|
262
|
+
* incompatible. For this reason, no error is generated if the content of the database is
|
|
263
|
+
* invalid and/or can't be decoded.
|
|
264
|
+
*
|
|
265
|
+
* Providing a database can considerably improve the time it takes for smoldot to be fully
|
|
266
|
+
* synchronized with a chain by reducing the amount of data that it has to download.
|
|
267
|
+
* Furthermore, the database also contains a list of nodes that smoldot can use in order to
|
|
268
|
+
* reduce the load that is being put on the bootnodes.
|
|
240
269
|
*
|
|
241
270
|
* Important: please note that using a malicious database content can lead to a security
|
|
242
271
|
* vulnerability. This database content is considered by smoldot as trusted input. It is the
|
|
243
272
|
* responsibility of the API user to make sure that the value passed in this field comes from
|
|
244
273
|
* the same source of trust as the chain specification that was used when retrieving this
|
|
245
|
-
* database content.
|
|
274
|
+
* database content. In other words, if you load this database content for example from the disk
|
|
275
|
+
* or from the browser's local storage, be absolutely certain that no malicious program has
|
|
276
|
+
* modified the content of that file or local storage.
|
|
246
277
|
*/
|
|
247
278
|
databaseContent?: string;
|
|
248
279
|
/**
|
|
249
280
|
* If `chainSpec` concerns a parachain, contains the list of chains whose `id` smoldot will try
|
|
250
|
-
* to match with the parachain's `
|
|
281
|
+
* to match with the parachain's `relayChain`.
|
|
251
282
|
* Defaults to `[]`.
|
|
252
283
|
*
|
|
253
|
-
* Must contain exactly the objects that were returned by previous calls to
|
|
254
|
-
* library uses a `WeakMap` in its implementation in order to identify chains.
|
|
284
|
+
* Must contain exactly the {@link Chain} objects that were returned by previous calls to
|
|
285
|
+
* `addChain`. The library uses a `WeakMap` in its implementation in order to identify chains.
|
|
286
|
+
*
|
|
287
|
+
* # Explanation and usage
|
|
255
288
|
*
|
|
256
289
|
* The primary way smoldot determines which relay chain is associated to a parachain is by
|
|
257
290
|
* inspecting the chain specification of that parachain (i.e. the `chainSpec` field).
|
|
@@ -260,7 +293,7 @@ export interface AddChainOptions {
|
|
|
260
293
|
* applications: multiple applications could add mutiple different chains with the same `id`,
|
|
261
294
|
* creating an ambiguity, or an application could register malicious chains with small variations
|
|
262
295
|
* of a popular chain's `id` and try to benefit from a typo in a legitimate application's
|
|
263
|
-
* `
|
|
296
|
+
* `relayChain`.
|
|
264
297
|
*
|
|
265
298
|
* These problems can be solved by using this parameter to segregate multiple different uses of
|
|
266
299
|
* the same client. To use it, pass the list of all chains that the same application has
|
|
@@ -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({
|
|
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.
|
|
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
|
-
|
|
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.
|
|
421
|
-
channel.
|
|
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)
|
|
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
|
}))();
|
package/dist/cjs/index-deno.js
CHANGED
|
@@ -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
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
132
|
-
|
|
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
|
-
|
|
154
|
+
socket.onclose = (event) => {
|
|
136
155
|
const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
|
|
137
156
|
config.onConnectionReset(message);
|
|
138
157
|
};
|
|
139
|
-
|
|
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
|
}
|
|
@@ -150,21 +197,24 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
|
|
|
150
197
|
inner: Deno.connect({
|
|
151
198
|
hostname: tcpParsed[2],
|
|
152
199
|
port: parseInt(tcpParsed[3], 10),
|
|
200
|
+
}).catch((error) => {
|
|
201
|
+
socket.destroyed = true;
|
|
202
|
+
config.onConnectionReset(error.toString());
|
|
203
|
+
return null;
|
|
153
204
|
})
|
|
154
205
|
};
|
|
155
|
-
connection = { ty: 'tcp', socket };
|
|
156
206
|
socket.inner = socket.inner.then((established) => {
|
|
157
207
|
// TODO: at the time of writing of this comment, `setNoDelay` is still unstable
|
|
158
208
|
//established.setNoDelay();
|
|
159
209
|
if (socket.destroyed)
|
|
160
210
|
return established;
|
|
161
|
-
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 });
|
|
162
212
|
// Spawns an asynchronous task that continuously reads from the socket.
|
|
163
213
|
// Every time data is read, the task re-executes itself in order to continue reading.
|
|
164
214
|
// The task ends automatically if an EOF or error is detected, which should also happen
|
|
165
215
|
// if the user calls `close()`.
|
|
166
216
|
const read = (readBuffer) => __awaiter(this, void 0, void 0, function* () {
|
|
167
|
-
if (socket.destroyed)
|
|
217
|
+
if (socket.destroyed || established === null)
|
|
168
218
|
return;
|
|
169
219
|
let outcome = null;
|
|
170
220
|
try {
|
|
@@ -190,53 +240,21 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
|
|
|
190
240
|
read(new Uint8Array(1024));
|
|
191
241
|
return established;
|
|
192
242
|
});
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (connection.ty == 'websocket') {
|
|
200
|
-
// WebSocket
|
|
201
|
-
// We can't set these fields to null because the TypeScript definitions don't
|
|
202
|
-
// allow it, but we can set them to dummy values.
|
|
203
|
-
connection.socket.onopen = () => { };
|
|
204
|
-
connection.socket.onclose = () => { };
|
|
205
|
-
connection.socket.onmessage = () => { };
|
|
206
|
-
connection.socket.onerror = () => { };
|
|
207
|
-
connection.socket.close();
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
// TCP
|
|
211
|
-
connection.socket.destroyed = true;
|
|
212
|
-
connection.socket.inner.then((connec) => connec.close());
|
|
213
|
-
}
|
|
214
|
-
},
|
|
215
|
-
send: (data) => {
|
|
216
|
-
if (connection.ty == 'websocket') {
|
|
217
|
-
// WebSocket
|
|
218
|
-
// The WebSocket library that we use seems to spontaneously transition connections
|
|
219
|
-
// to the "closed" state but not call the `onclosed` callback immediately. Calling
|
|
220
|
-
// `send` on that object throws an exception. In order to avoid panicking smoldot,
|
|
221
|
-
// we thus absorb any exception thrown here.
|
|
222
|
-
// See also <https://github.com/paritytech/smoldot/issues/2937>.
|
|
223
|
-
try {
|
|
224
|
-
connection.socket.send(data);
|
|
225
|
-
}
|
|
226
|
-
catch (_error) { }
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
// TCP
|
|
230
|
-
// 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) => {
|
|
231
249
|
let dataCopy = Uint8Array.from(data); // Deep copy of the data
|
|
232
|
-
|
|
233
|
-
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* () {
|
|
234
251
|
while (dataCopy.length > 0) {
|
|
235
|
-
if (socket.destroyed)
|
|
252
|
+
if (socket.destroyed || c === null)
|
|
236
253
|
return c;
|
|
237
254
|
let outcome;
|
|
238
255
|
try {
|
|
239
256
|
outcome = yield c.write(dataCopy);
|
|
257
|
+
config.onWritableBytes(dataCopy.length);
|
|
240
258
|
}
|
|
241
259
|
catch (error) {
|
|
242
260
|
// The type of `error` is unclear, but we assume that it implements `Error`
|
|
@@ -256,8 +274,17 @@ function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
|
|
|
256
274
|
}
|
|
257
275
|
return c;
|
|
258
276
|
}));
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
+
}
|
|
263
290
|
}
|