smoldot 2.0.8 → 2.0.10
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/internals/bytecode/wasm0.js +1 -1
- package/dist/cjs/internals/bytecode/wasm1.js +1 -1
- package/dist/cjs/internals/bytecode/wasm2.js +1 -1
- package/dist/cjs/internals/client.d.ts +1 -1
- package/dist/cjs/internals/client.js +2 -2
- package/dist/cjs/internals/local-instance.d.ts +1 -1
- package/dist/cjs/internals/local-instance.js +4 -2
- package/dist/cjs/internals/remote-instance.js +6 -6
- package/dist/cjs/no-auto-bytecode-browser.js +66 -48
- package/dist/mjs/internals/bytecode/wasm0.js +1 -1
- package/dist/mjs/internals/bytecode/wasm1.js +1 -1
- package/dist/mjs/internals/bytecode/wasm2.js +1 -1
- package/dist/mjs/internals/client.d.ts +1 -1
- package/dist/mjs/internals/client.js +2 -2
- package/dist/mjs/internals/local-instance.d.ts +1 -1
- package/dist/mjs/internals/local-instance.js +4 -2
- package/dist/mjs/internals/remote-instance.js +6 -6
- package/dist/mjs/no-auto-bytecode-browser.js +66 -48
- package/package.json +1 -1
|
@@ -140,7 +140,7 @@ export interface ConnectionConfig {
|
|
|
140
140
|
*
|
|
141
141
|
* This function must only be called for connections of type "multi-stream".
|
|
142
142
|
*/
|
|
143
|
-
onStreamReset: (streamId: number) => void;
|
|
143
|
+
onStreamReset: (streamId: number, message: string) => void;
|
|
144
144
|
/**
|
|
145
145
|
* Callback called when some data sent using {@link Connection.send} has effectively been
|
|
146
146
|
* written on the stream, meaning that some buffer space is now free.
|
|
@@ -153,10 +153,10 @@ function start(options, wasmModule, platformBindings) {
|
|
|
153
153
|
throw new Error();
|
|
154
154
|
state.instance.instance.streamWritableBytes(connectionId, numExtra, streamId);
|
|
155
155
|
},
|
|
156
|
-
onStreamReset(streamId) {
|
|
156
|
+
onStreamReset(streamId, message) {
|
|
157
157
|
if (state.instance.status !== "ready")
|
|
158
158
|
throw new Error();
|
|
159
|
-
state.instance.instance.streamReset(connectionId, streamId);
|
|
159
|
+
state.instance.instance.streamReset(connectionId, streamId, message);
|
|
160
160
|
},
|
|
161
161
|
}));
|
|
162
162
|
break;
|
|
@@ -111,7 +111,7 @@ export interface Instance {
|
|
|
111
111
|
streamWritableBytes: (connectionId: number, numExtra: number, streamId?: number) => void;
|
|
112
112
|
streamMessage: (connectionId: number, message: Uint8Array, streamId?: number) => void;
|
|
113
113
|
streamOpened: (connectionId: number, streamId: number, direction: 'inbound' | 'outbound') => void;
|
|
114
|
-
streamReset: (connectionId: number, streamId: number) => void;
|
|
114
|
+
streamReset: (connectionId: number, streamId: number, message: string) => void;
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
117
|
* Starts a new instance using the given configuration.
|
|
@@ -438,10 +438,12 @@ function startLocalInstance(config, wasmModule, eventCallback) {
|
|
|
438
438
|
return;
|
|
439
439
|
state.instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0);
|
|
440
440
|
},
|
|
441
|
-
streamReset: (connectionId, streamId) => {
|
|
441
|
+
streamReset: (connectionId, streamId, message) => {
|
|
442
442
|
if (!state.instance)
|
|
443
443
|
return;
|
|
444
|
-
state.
|
|
444
|
+
state.bufferIndices[0] = new TextEncoder().encode(message);
|
|
445
|
+
state.instance.exports.stream_reset(connectionId, streamId, 0);
|
|
446
|
+
delete state.bufferIndices[0];
|
|
445
447
|
},
|
|
446
448
|
};
|
|
447
449
|
});
|
|
@@ -196,9 +196,9 @@ function connectToInstanceServer(config) {
|
|
|
196
196
|
const msg = { ty: "stream-writable-bytes", connectionId, numExtra, streamId };
|
|
197
197
|
portToServer.postMessage(msg);
|
|
198
198
|
},
|
|
199
|
-
streamReset(connectionId, streamId) {
|
|
199
|
+
streamReset(connectionId, streamId, message) {
|
|
200
200
|
state.connections.get(connectionId).delete(streamId);
|
|
201
|
-
const msg = { ty: "stream-reset", connectionId, streamId };
|
|
201
|
+
const msg = { ty: "stream-reset", connectionId, streamId, message };
|
|
202
202
|
portToServer.postMessage(msg);
|
|
203
203
|
},
|
|
204
204
|
};
|
|
@@ -333,7 +333,7 @@ function startInstanceServer(config, initPortToClient) {
|
|
|
333
333
|
if (!state.connections.has(message.connectionId))
|
|
334
334
|
return;
|
|
335
335
|
// The stream might have been reset locally in the past.
|
|
336
|
-
if (message.streamId && !state.connections.get(message.connectionId).has(message.streamId))
|
|
336
|
+
if (message.streamId !== undefined && !state.connections.get(message.connectionId).has(message.streamId))
|
|
337
337
|
return;
|
|
338
338
|
state.instance.streamMessage(message.connectionId, message.message, message.streamId);
|
|
339
339
|
break;
|
|
@@ -351,7 +351,7 @@ function startInstanceServer(config, initPortToClient) {
|
|
|
351
351
|
if (!state.connections.has(message.connectionId))
|
|
352
352
|
return;
|
|
353
353
|
// The stream might have been reset locally in the past.
|
|
354
|
-
if (message.streamId && !state.connections.get(message.connectionId).has(message.streamId))
|
|
354
|
+
if (message.streamId !== undefined && !state.connections.get(message.connectionId).has(message.streamId))
|
|
355
355
|
return;
|
|
356
356
|
state.instance.streamWritableBytes(message.connectionId, message.numExtra, message.streamId);
|
|
357
357
|
break;
|
|
@@ -361,10 +361,10 @@ function startInstanceServer(config, initPortToClient) {
|
|
|
361
361
|
if (!state.connections.has(message.connectionId))
|
|
362
362
|
return;
|
|
363
363
|
// The stream might have been reset locally in the past.
|
|
364
|
-
if (
|
|
364
|
+
if (!state.connections.get(message.connectionId).has(message.streamId))
|
|
365
365
|
return;
|
|
366
366
|
state.connections.get(message.connectionId).delete(message.streamId);
|
|
367
|
-
state.instance.streamReset(message.connectionId, message.streamId);
|
|
367
|
+
state.instance.streamReset(message.connectionId, message.streamId, message.message);
|
|
368
368
|
break;
|
|
369
369
|
}
|
|
370
370
|
}
|
|
@@ -155,56 +155,52 @@ function connect(config) {
|
|
|
155
155
|
}
|
|
156
156
|
else if (config.address.ty === "webrtc") {
|
|
157
157
|
const { targetPort, ipVersion, targetIp, remoteTlsCertificateSha256 } = config.address;
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
let pc = undefined;
|
|
165
|
-
// Contains the data channels that are open and have been reported to smoldot.
|
|
166
|
-
const dataChannels = new Map();
|
|
167
|
-
// SHA256 hash of the DTLS certificate of the local node. Unknown as long as it hasn't been
|
|
168
|
-
// generated.
|
|
169
|
-
// TODO: could be merged with `pc` in one variable, and maybe even the other fields as well
|
|
170
|
-
let localTlsCertificateSha256;
|
|
158
|
+
const state = {
|
|
159
|
+
pc: undefined,
|
|
160
|
+
dataChannels: new Map(),
|
|
161
|
+
nextStreamId: 0,
|
|
162
|
+
isFirstOutSubstream: true,
|
|
163
|
+
};
|
|
171
164
|
// Kills all the JavaScript objects (the connection and all its substreams), ensuring that no
|
|
172
165
|
// callback will be called again. Doesn't report anything to smoldot, as this should be done
|
|
173
166
|
// by the caller.
|
|
174
167
|
const killAllJs = () => {
|
|
175
168
|
// The `RTCPeerConnection` is created pretty quickly. It is however still possible for
|
|
176
169
|
// smoldot to cancel the opening, in which case `pc` will still be undefined.
|
|
177
|
-
if (!pc) {
|
|
178
|
-
console.assert(dataChannels.size === 0, "substreams exist while pc is undef");
|
|
179
|
-
pc = null;
|
|
170
|
+
if (!state.pc) {
|
|
171
|
+
console.assert(state.dataChannels.size === 0, "substreams exist while pc is undef");
|
|
172
|
+
state.pc = null;
|
|
180
173
|
return;
|
|
181
174
|
}
|
|
182
|
-
pc.onconnectionstatechange = null;
|
|
183
|
-
pc.onnegotiationneeded = null;
|
|
184
|
-
pc.ondatachannel = null;
|
|
185
|
-
for (const channel of Array.from(dataChannels.values())) {
|
|
175
|
+
state.pc.onconnectionstatechange = null;
|
|
176
|
+
state.pc.onnegotiationneeded = null;
|
|
177
|
+
state.pc.ondatachannel = null;
|
|
178
|
+
for (const channel of Array.from(state.dataChannels.values())) {
|
|
186
179
|
channel.channel.onopen = null;
|
|
187
180
|
channel.channel.onerror = null;
|
|
188
181
|
channel.channel.onclose = null;
|
|
189
182
|
channel.channel.onbufferedamountlow = null;
|
|
190
183
|
channel.channel.onmessage = null;
|
|
191
184
|
}
|
|
192
|
-
dataChannels.clear();
|
|
193
|
-
pc.close(); // Not necessarily necessary, but it doesn't hurt to do so.
|
|
185
|
+
state.dataChannels.clear();
|
|
186
|
+
state.pc.close(); // Not necessarily necessary, but it doesn't hurt to do so.
|
|
194
187
|
};
|
|
195
188
|
// Function that configures a newly-opened channel and adds it to the map. Used for both
|
|
196
189
|
// inbound and outbound substreams.
|
|
197
190
|
const addChannel = (dataChannel, direction) => {
|
|
198
|
-
const
|
|
191
|
+
const streamId = state.nextStreamId;
|
|
192
|
+
state.nextStreamId += 1;
|
|
199
193
|
dataChannel.binaryType = 'arraybuffer';
|
|
200
194
|
let isOpen = { value: false };
|
|
201
195
|
dataChannel.onopen = () => {
|
|
202
196
|
console.assert(!isOpen.value, "substream opened twice");
|
|
203
197
|
isOpen.value = true;
|
|
204
|
-
config.onStreamOpened(
|
|
205
|
-
config.onWritableBytes(65536,
|
|
198
|
+
config.onStreamOpened(streamId, direction);
|
|
199
|
+
config.onWritableBytes(65536, streamId);
|
|
206
200
|
};
|
|
207
|
-
dataChannel.onerror = dataChannel.onclose = (
|
|
201
|
+
dataChannel.onerror = dataChannel.onclose = (event) => {
|
|
202
|
+
// Note that Firefox doesn't support <https://developer.mozilla.org/en-US/docs/Web/API/RTCErrorEvent>.
|
|
203
|
+
const message = (event instanceof RTCErrorEvent) ? event.error.toString() : "RTCDataChannel closed";
|
|
208
204
|
if (!isOpen.value) {
|
|
209
205
|
// Substream wasn't opened yet and thus has failed to open. The API has no
|
|
210
206
|
// mechanism to report substream openings failures. We could try opening it
|
|
@@ -212,24 +208,30 @@ function connect(config) {
|
|
|
212
208
|
// entire connection.
|
|
213
209
|
killAllJs();
|
|
214
210
|
// Note that the event doesn't give any additional reason for the failure.
|
|
215
|
-
config.onConnectionReset("data channel failed to open");
|
|
211
|
+
config.onConnectionReset("data channel failed to open: " + message);
|
|
216
212
|
}
|
|
217
213
|
else {
|
|
218
214
|
// Substream was open and is now closed. Normal situation.
|
|
219
|
-
|
|
215
|
+
dataChannel.onopen = null;
|
|
216
|
+
dataChannel.onerror = null;
|
|
217
|
+
dataChannel.onclose = null;
|
|
218
|
+
dataChannel.onbufferedamountlow = null;
|
|
219
|
+
dataChannel.onmessage = null;
|
|
220
|
+
state.dataChannels.delete(streamId);
|
|
221
|
+
config.onStreamReset(streamId, message);
|
|
220
222
|
}
|
|
221
223
|
};
|
|
222
224
|
dataChannel.onbufferedamountlow = () => {
|
|
223
|
-
const channel = dataChannels.get(
|
|
225
|
+
const channel = state.dataChannels.get(streamId);
|
|
224
226
|
const val = channel.bufferedBytes;
|
|
225
227
|
channel.bufferedBytes = 0;
|
|
226
|
-
config.onWritableBytes(val,
|
|
228
|
+
config.onWritableBytes(val, streamId);
|
|
227
229
|
};
|
|
228
230
|
dataChannel.onmessage = (m) => {
|
|
229
231
|
// The `data` field is an `ArrayBuffer`.
|
|
230
|
-
config.onMessage(new Uint8Array(m.data),
|
|
232
|
+
config.onMessage(new Uint8Array(m.data), streamId);
|
|
231
233
|
};
|
|
232
|
-
dataChannels.set(
|
|
234
|
+
state.dataChannels.set(streamId, { channel: dataChannel, bufferedBytes: 0 });
|
|
233
235
|
};
|
|
234
236
|
// It is possible for the browser to use multiple different certificates.
|
|
235
237
|
// In order for our local certificate to be deterministic, we need to generate it manually and
|
|
@@ -237,10 +239,23 @@ function connect(config) {
|
|
|
237
239
|
// According to <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-generatecertificate>,
|
|
238
240
|
// browsers are guaranteed to support `{ name: "ECDSA", namedCurve: "P-256" }`.
|
|
239
241
|
RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" }).then((localCertificate) => __awaiter(this, void 0, void 0, function* () {
|
|
240
|
-
if (pc === null)
|
|
242
|
+
if (state.pc === null)
|
|
243
|
+
return;
|
|
244
|
+
// Due to <https://bugzilla.mozilla.org/show_bug.cgi?id=1659672>, connections from
|
|
245
|
+
// Firefox to a localhost WebRTC server always fails. Since this bug has been opened
|
|
246
|
+
// for three years at the time of writing, it is unlikely to be fixed in the short
|
|
247
|
+
// term. In order to provider better user feedback, we straight up refuse connecting
|
|
248
|
+
// and stop the connection.
|
|
249
|
+
// Note that this is just a hint. Failing to detect this will lead to the WebRTC
|
|
250
|
+
// handshake timing out.
|
|
251
|
+
// TODO: eventually remove this if the Firefox bug is fixed
|
|
252
|
+
if ((targetIp == 'localhost' || targetIp == '127.0.0.1' || targetIp == '::1') && navigator.userAgent.indexOf('Firefox') !== -1) {
|
|
253
|
+
killAllJs();
|
|
254
|
+
config.onConnectionReset("Firefox can't connect to a localhost WebRTC server");
|
|
241
255
|
return;
|
|
256
|
+
}
|
|
242
257
|
// Create a new WebRTC connection.
|
|
243
|
-
pc = new RTCPeerConnection({ certificates: [localCertificate] });
|
|
258
|
+
state.pc = new RTCPeerConnection({ certificates: [localCertificate] });
|
|
244
259
|
// We need to build the multihash corresponding to the local certificate.
|
|
245
260
|
// While there exists a `RTCPeerConnection.getFingerprints` function, Firefox notably
|
|
246
261
|
// doesn't support it.
|
|
@@ -263,7 +278,7 @@ function connect(config) {
|
|
|
263
278
|
}
|
|
264
279
|
}
|
|
265
280
|
else {
|
|
266
|
-
const localSdpOffer = yield pc.createOffer();
|
|
281
|
+
const localSdpOffer = yield state.pc.createOffer();
|
|
267
282
|
// Note that this regex is not strict. The browser isn't a malicious actor, and the
|
|
268
283
|
// objective of this regex is not to detect invalid input.
|
|
269
284
|
const localSdpOfferFingerprintMatch = localSdpOffer.sdp.match(/a(\s*)=(\s*)fingerprint:(\s*)(sha|SHA)-256(\s*)(([a-fA-F0-9]{2}(:)*){32})/);
|
|
@@ -277,23 +292,23 @@ function connect(config) {
|
|
|
277
292
|
config.onConnectionReset('Failed to obtain the browser certificate fingerprint');
|
|
278
293
|
return;
|
|
279
294
|
}
|
|
280
|
-
localTlsCertificateSha256 = new Uint8Array(32);
|
|
295
|
+
let localTlsCertificateSha256 = new Uint8Array(32);
|
|
281
296
|
localTlsCertificateSha256.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 0);
|
|
282
297
|
// `onconnectionstatechange` is used to detect when the connection has closed or has failed
|
|
283
298
|
// to open.
|
|
284
299
|
// Note that smoldot will think that the connection is open even when it is still opening.
|
|
285
300
|
// Therefore we don't care about events concerning the fact that the connection is now fully
|
|
286
301
|
// open.
|
|
287
|
-
pc.onconnectionstatechange = (_event) => {
|
|
288
|
-
if (pc.connectionState == "closed" || pc.connectionState == "disconnected" || pc.connectionState == "failed") {
|
|
302
|
+
state.pc.onconnectionstatechange = (_event) => {
|
|
303
|
+
if (state.pc.connectionState == "closed" || state.pc.connectionState == "disconnected" || state.pc.connectionState == "failed") {
|
|
289
304
|
killAllJs();
|
|
290
|
-
config.onConnectionReset("WebRTC state transitioned to " + pc.connectionState);
|
|
305
|
+
config.onConnectionReset("WebRTC state transitioned to " + state.pc.connectionState);
|
|
291
306
|
}
|
|
292
307
|
};
|
|
293
|
-
pc.onnegotiationneeded = (_event) => __awaiter(this, void 0, void 0, function* () {
|
|
308
|
+
state.pc.onnegotiationneeded = (_event) => __awaiter(this, void 0, void 0, function* () {
|
|
294
309
|
var _a;
|
|
295
310
|
// Create a new offer and set it as local description.
|
|
296
|
-
let sdpOffer = (yield pc.createOffer()).sdp;
|
|
311
|
+
let sdpOffer = (yield state.pc.createOffer()).sdp;
|
|
297
312
|
// We check that the locally-generated SDP offer has a data channel with the UDP
|
|
298
313
|
// protocol. If that isn't the case, the connection will likely fail.
|
|
299
314
|
if (sdpOffer.match(/^m=application(\s+)(\d+)(\s+)UDP\/DTLS\/SCTP(\s+)webrtc-datachannel$/m) === null) {
|
|
@@ -312,7 +327,7 @@ function connect(config) {
|
|
|
312
327
|
const ufragPwd = "libp2p+webrtc+v1/" + browserGeneratedPwd;
|
|
313
328
|
sdpOffer = sdpOffer.replace(/^a=ice-ufrag.*$/m, 'a=ice-ufrag:' + ufragPwd);
|
|
314
329
|
sdpOffer = sdpOffer.replace(/^a=ice-pwd.*$/m, 'a=ice-pwd:' + ufragPwd);
|
|
315
|
-
yield pc.setLocalDescription({ type: 'offer', sdp: sdpOffer });
|
|
330
|
+
yield state.pc.setLocalDescription({ type: 'offer', sdp: sdpOffer });
|
|
316
331
|
// Transform certificate hash into fingerprint (upper-hex; each byte separated by ":").
|
|
317
332
|
const fingerprint = Array.from(remoteTlsCertificateSha256).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':');
|
|
318
333
|
// Note that the trailing line feed is important, as otherwise Chrome
|
|
@@ -373,9 +388,9 @@ function connect(config) {
|
|
|
373
388
|
// A transport address for a candidate that can be used for connectivity
|
|
374
389
|
// checks (RFC8839).
|
|
375
390
|
"a=candidate:1 1 UDP 1 " + targetIp + " " + String(targetPort) + " typ host" + "\n";
|
|
376
|
-
yield pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
|
|
391
|
+
yield state.pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
|
|
377
392
|
});
|
|
378
|
-
pc.ondatachannel = ({ channel }) => {
|
|
393
|
+
state.pc.ondatachannel = ({ channel }) => {
|
|
379
394
|
// TODO: is the substream maybe already open? according to the Internet it seems that no but it's unclear
|
|
380
395
|
addChannel(channel, 'inbound');
|
|
381
396
|
};
|
|
@@ -391,18 +406,18 @@ function connect(config) {
|
|
|
391
406
|
killAllJs();
|
|
392
407
|
}
|
|
393
408
|
else {
|
|
394
|
-
const channel = dataChannels.get(streamId);
|
|
409
|
+
const channel = state.dataChannels.get(streamId);
|
|
395
410
|
channel.channel.onopen = null;
|
|
396
411
|
channel.channel.onerror = null;
|
|
397
412
|
channel.channel.onclose = null;
|
|
398
413
|
channel.channel.onbufferedamountlow = null;
|
|
399
414
|
channel.channel.onmessage = null;
|
|
400
415
|
channel.channel.close();
|
|
401
|
-
dataChannels.delete(streamId);
|
|
416
|
+
state.dataChannels.delete(streamId);
|
|
402
417
|
}
|
|
403
418
|
},
|
|
404
419
|
send: (data, streamId) => {
|
|
405
|
-
const channel = dataChannels.get(streamId);
|
|
420
|
+
const channel = state.dataChannels.get(streamId);
|
|
406
421
|
channel.channel.send(data);
|
|
407
422
|
channel.bufferedBytes += data.length;
|
|
408
423
|
},
|
|
@@ -412,7 +427,10 @@ function connect(config) {
|
|
|
412
427
|
// therefore `pc` is guaranteed to be non-null.
|
|
413
428
|
// Note that the label passed to `createDataChannel` is required to be empty as
|
|
414
429
|
// per the libp2p WebRTC specification.
|
|
415
|
-
|
|
430
|
+
// TODO: adjusting the options based on the first substream is a bit hacky
|
|
431
|
+
const opts = state.isFirstOutSubstream ? { negotiated: true, id: 0 } : {};
|
|
432
|
+
state.isFirstOutSubstream = false;
|
|
433
|
+
addChannel(state.pc.createDataChannel("", opts), 'outbound');
|
|
416
434
|
}
|
|
417
435
|
};
|
|
418
436
|
}
|