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