smoldot 2.0.9 → 2.0.11
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/README.md +1 -1
- 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 +2 -2
- package/dist/cjs/internals/client.js +2 -2
- package/dist/cjs/internals/local-instance.d.ts +2 -2
- package/dist/cjs/internals/local-instance.js +11 -3
- package/dist/cjs/internals/remote-instance.js +6 -6
- package/dist/cjs/no-auto-bytecode-browser.js +87 -52
- package/dist/cjs/no-auto-bytecode-deno.js +31 -25
- package/dist/cjs/no-auto-bytecode-nodejs.js +17 -13
- 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 +2 -2
- package/dist/mjs/internals/client.js +2 -2
- package/dist/mjs/internals/local-instance.d.ts +2 -2
- package/dist/mjs/internals/local-instance.js +11 -3
- package/dist/mjs/internals/remote-instance.js +6 -6
- package/dist/mjs/no-auto-bytecode-browser.js +87 -52
- package/dist/mjs/no-auto-bytecode-deno.js +31 -25
- package/dist/mjs/no-auto-bytecode-nodejs.js +17 -13
- package/package.json +1 -1
|
@@ -68,7 +68,7 @@ export interface Connection {
|
|
|
68
68
|
*
|
|
69
69
|
* Must not be called after `closeSend` has been called.
|
|
70
70
|
*/
|
|
71
|
-
send(data: Uint8Array
|
|
71
|
+
send(data: Array<Uint8Array>, streamId?: number): void;
|
|
72
72
|
/**
|
|
73
73
|
* Closes the writing side of the given stream of the given connection.
|
|
74
74
|
*
|
|
@@ -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;
|
|
@@ -69,7 +69,7 @@ export type Event = {
|
|
|
69
69
|
ty: "stream-send";
|
|
70
70
|
connectionId: number;
|
|
71
71
|
streamId?: number;
|
|
72
|
-
data: Uint8Array
|
|
72
|
+
data: Array<Uint8Array>;
|
|
73
73
|
} | {
|
|
74
74
|
ty: "stream-send-close";
|
|
75
75
|
connectionId: number;
|
|
@@ -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.
|
|
@@ -250,9 +250,15 @@ function startLocalInstance(config, wasmModule, eventCallback) {
|
|
|
250
250
|
// that this function is called only when the connection is in an open state.
|
|
251
251
|
stream_send: (connectionId, streamId, ptr, len) => {
|
|
252
252
|
const instance = state.instance;
|
|
253
|
+
const mem = new Uint8Array(instance.exports.memory.buffer);
|
|
253
254
|
ptr >>>= 0;
|
|
254
255
|
len >>>= 0;
|
|
255
|
-
const data = new
|
|
256
|
+
const data = new Array();
|
|
257
|
+
for (let i = 0; i < len; ++i) {
|
|
258
|
+
const bufPtr = buffer.readUInt32LE(mem, ptr + 8 * i);
|
|
259
|
+
const bufLen = buffer.readUInt32LE(mem, ptr + 8 * i + 4);
|
|
260
|
+
data.push(mem.slice(bufPtr, bufPtr + bufLen));
|
|
261
|
+
}
|
|
256
262
|
// TODO: docs says the streamId is provided only for multi-stream connections, but here it's always provided
|
|
257
263
|
eventCallback({ ty: "stream-send", connectionId, streamId, data });
|
|
258
264
|
},
|
|
@@ -438,10 +444,12 @@ function startLocalInstance(config, wasmModule, eventCallback) {
|
|
|
438
444
|
return;
|
|
439
445
|
state.instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0);
|
|
440
446
|
},
|
|
441
|
-
streamReset: (connectionId, streamId) => {
|
|
447
|
+
streamReset: (connectionId, streamId, message) => {
|
|
442
448
|
if (!state.instance)
|
|
443
449
|
return;
|
|
444
|
-
state.
|
|
450
|
+
state.bufferIndices[0] = new TextEncoder().encode(message);
|
|
451
|
+
state.instance.exports.stream_reset(connectionId, streamId, 0);
|
|
452
|
+
delete state.bufferIndices[0];
|
|
445
453
|
},
|
|
446
454
|
};
|
|
447
455
|
});
|
|
@@ -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
|
}
|
|
@@ -29,6 +29,19 @@ Object.defineProperty(exports, "QueueFullError", { enumerable: true, get: functi
|
|
|
29
29
|
*/
|
|
30
30
|
function startWithBytecode(options) {
|
|
31
31
|
options.forbidTcp = true;
|
|
32
|
+
// When in a secure context, browsers refuse to open non-secure WebSocket connections to
|
|
33
|
+
// non-localhost. There is an exception if the page is localhost, in which case all connections
|
|
34
|
+
// are allowed.
|
|
35
|
+
// Detecting this ahead of time is better for the overall health of the client, as it will
|
|
36
|
+
// avoid storing in memory addresses that it knows it can't connect to.
|
|
37
|
+
// The condition below is a hint, and false-positives or false-negatives are not fundamentally
|
|
38
|
+
// an issue.
|
|
39
|
+
if ((typeof isSecureContext === 'boolean' && isSecureContext) && typeof location !== undefined) {
|
|
40
|
+
const loc = location.toString();
|
|
41
|
+
if (loc.indexOf('localhost') !== -1 && loc.indexOf('127.0.0.1') !== -1 && loc.indexOf('::1') !== -1) {
|
|
42
|
+
options.forbidNonLocalWs = true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
32
45
|
return (0, client_js_1.start)(options, options.bytecode, {
|
|
33
46
|
performanceNow: () => {
|
|
34
47
|
return performance.now();
|
|
@@ -142,12 +155,14 @@ function connect(config) {
|
|
|
142
155
|
connection = null;
|
|
143
156
|
},
|
|
144
157
|
send: (data) => {
|
|
145
|
-
connection.send(data);
|
|
146
158
|
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
|
|
147
159
|
bufferedAmountCheck.nextTimeout = 10;
|
|
148
160
|
setTimeout(checkBufferedAmount, 10);
|
|
149
161
|
}
|
|
150
|
-
|
|
162
|
+
for (const buffer of data) {
|
|
163
|
+
bufferedAmountCheck.quenedUnreportedBytes += buffer.length;
|
|
164
|
+
}
|
|
165
|
+
connection.send(new Blob(data));
|
|
151
166
|
},
|
|
152
167
|
closeSend: () => { throw new Error('Wrong connection type'); },
|
|
153
168
|
openOutSubstream: () => { throw new Error('Wrong connection type'); }
|
|
@@ -155,56 +170,52 @@ function connect(config) {
|
|
|
155
170
|
}
|
|
156
171
|
else if (config.address.ty === "webrtc") {
|
|
157
172
|
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;
|
|
173
|
+
const state = {
|
|
174
|
+
pc: undefined,
|
|
175
|
+
dataChannels: new Map(),
|
|
176
|
+
nextStreamId: 0,
|
|
177
|
+
isFirstOutSubstream: true,
|
|
178
|
+
};
|
|
171
179
|
// Kills all the JavaScript objects (the connection and all its substreams), ensuring that no
|
|
172
180
|
// callback will be called again. Doesn't report anything to smoldot, as this should be done
|
|
173
181
|
// by the caller.
|
|
174
182
|
const killAllJs = () => {
|
|
175
183
|
// The `RTCPeerConnection` is created pretty quickly. It is however still possible for
|
|
176
184
|
// 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;
|
|
185
|
+
if (!state.pc) {
|
|
186
|
+
console.assert(state.dataChannels.size === 0, "substreams exist while pc is undef");
|
|
187
|
+
state.pc = null;
|
|
180
188
|
return;
|
|
181
189
|
}
|
|
182
|
-
pc.onconnectionstatechange = null;
|
|
183
|
-
pc.onnegotiationneeded = null;
|
|
184
|
-
pc.ondatachannel = null;
|
|
185
|
-
for (const channel of Array.from(dataChannels.values())) {
|
|
190
|
+
state.pc.onconnectionstatechange = null;
|
|
191
|
+
state.pc.onnegotiationneeded = null;
|
|
192
|
+
state.pc.ondatachannel = null;
|
|
193
|
+
for (const channel of Array.from(state.dataChannels.values())) {
|
|
186
194
|
channel.channel.onopen = null;
|
|
187
195
|
channel.channel.onerror = null;
|
|
188
196
|
channel.channel.onclose = null;
|
|
189
197
|
channel.channel.onbufferedamountlow = null;
|
|
190
198
|
channel.channel.onmessage = null;
|
|
191
199
|
}
|
|
192
|
-
dataChannels.clear();
|
|
193
|
-
pc.close(); // Not necessarily necessary, but it doesn't hurt to do so.
|
|
200
|
+
state.dataChannels.clear();
|
|
201
|
+
state.pc.close(); // Not necessarily necessary, but it doesn't hurt to do so.
|
|
194
202
|
};
|
|
195
203
|
// Function that configures a newly-opened channel and adds it to the map. Used for both
|
|
196
204
|
// inbound and outbound substreams.
|
|
197
205
|
const addChannel = (dataChannel, direction) => {
|
|
198
|
-
const
|
|
206
|
+
const streamId = state.nextStreamId;
|
|
207
|
+
state.nextStreamId += 1;
|
|
199
208
|
dataChannel.binaryType = 'arraybuffer';
|
|
200
209
|
let isOpen = { value: false };
|
|
201
210
|
dataChannel.onopen = () => {
|
|
202
211
|
console.assert(!isOpen.value, "substream opened twice");
|
|
203
212
|
isOpen.value = true;
|
|
204
|
-
config.onStreamOpened(
|
|
205
|
-
config.onWritableBytes(65536,
|
|
213
|
+
config.onStreamOpened(streamId, direction);
|
|
214
|
+
config.onWritableBytes(65536, streamId);
|
|
206
215
|
};
|
|
207
|
-
dataChannel.onerror = dataChannel.onclose = (
|
|
216
|
+
dataChannel.onerror = dataChannel.onclose = (event) => {
|
|
217
|
+
// Note that Firefox doesn't support <https://developer.mozilla.org/en-US/docs/Web/API/RTCErrorEvent>.
|
|
218
|
+
const message = (event instanceof RTCErrorEvent) ? event.error.toString() : "RTCDataChannel closed";
|
|
208
219
|
if (!isOpen.value) {
|
|
209
220
|
// Substream wasn't opened yet and thus has failed to open. The API has no
|
|
210
221
|
// mechanism to report substream openings failures. We could try opening it
|
|
@@ -212,24 +223,30 @@ function connect(config) {
|
|
|
212
223
|
// entire connection.
|
|
213
224
|
killAllJs();
|
|
214
225
|
// Note that the event doesn't give any additional reason for the failure.
|
|
215
|
-
config.onConnectionReset("data channel failed to open");
|
|
226
|
+
config.onConnectionReset("data channel failed to open: " + message);
|
|
216
227
|
}
|
|
217
228
|
else {
|
|
218
229
|
// Substream was open and is now closed. Normal situation.
|
|
219
|
-
|
|
230
|
+
dataChannel.onopen = null;
|
|
231
|
+
dataChannel.onerror = null;
|
|
232
|
+
dataChannel.onclose = null;
|
|
233
|
+
dataChannel.onbufferedamountlow = null;
|
|
234
|
+
dataChannel.onmessage = null;
|
|
235
|
+
state.dataChannels.delete(streamId);
|
|
236
|
+
config.onStreamReset(streamId, message);
|
|
220
237
|
}
|
|
221
238
|
};
|
|
222
239
|
dataChannel.onbufferedamountlow = () => {
|
|
223
|
-
const channel = dataChannels.get(
|
|
240
|
+
const channel = state.dataChannels.get(streamId);
|
|
224
241
|
const val = channel.bufferedBytes;
|
|
225
242
|
channel.bufferedBytes = 0;
|
|
226
|
-
config.onWritableBytes(val,
|
|
243
|
+
config.onWritableBytes(val, streamId);
|
|
227
244
|
};
|
|
228
245
|
dataChannel.onmessage = (m) => {
|
|
229
246
|
// The `data` field is an `ArrayBuffer`.
|
|
230
|
-
config.onMessage(new Uint8Array(m.data),
|
|
247
|
+
config.onMessage(new Uint8Array(m.data), streamId);
|
|
231
248
|
};
|
|
232
|
-
dataChannels.set(
|
|
249
|
+
state.dataChannels.set(streamId, { channel: dataChannel, bufferedBytes: 0 });
|
|
233
250
|
};
|
|
234
251
|
// It is possible for the browser to use multiple different certificates.
|
|
235
252
|
// In order for our local certificate to be deterministic, we need to generate it manually and
|
|
@@ -237,10 +254,23 @@ function connect(config) {
|
|
|
237
254
|
// According to <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-generatecertificate>,
|
|
238
255
|
// browsers are guaranteed to support `{ name: "ECDSA", namedCurve: "P-256" }`.
|
|
239
256
|
RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" }).then((localCertificate) => __awaiter(this, void 0, void 0, function* () {
|
|
240
|
-
if (pc === null)
|
|
257
|
+
if (state.pc === null)
|
|
241
258
|
return;
|
|
259
|
+
// Due to <https://bugzilla.mozilla.org/show_bug.cgi?id=1659672>, connections from
|
|
260
|
+
// Firefox to a localhost WebRTC server always fails. Since this bug has been opened
|
|
261
|
+
// for three years at the time of writing, it is unlikely to be fixed in the short
|
|
262
|
+
// term. In order to provider better user feedback, we straight up refuse connecting
|
|
263
|
+
// and stop the connection.
|
|
264
|
+
// Note that this is just a hint. Failing to detect this will lead to the WebRTC
|
|
265
|
+
// handshake timing out.
|
|
266
|
+
// TODO: eventually remove this if the Firefox bug is fixed
|
|
267
|
+
if ((targetIp == 'localhost' || targetIp == '127.0.0.1' || targetIp == '::1') && navigator.userAgent.indexOf('Firefox') !== -1) {
|
|
268
|
+
killAllJs();
|
|
269
|
+
config.onConnectionReset("Firefox can't connect to a localhost WebRTC server");
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
242
272
|
// Create a new WebRTC connection.
|
|
243
|
-
pc = new RTCPeerConnection({ certificates: [localCertificate] });
|
|
273
|
+
state.pc = new RTCPeerConnection({ certificates: [localCertificate] });
|
|
244
274
|
// We need to build the multihash corresponding to the local certificate.
|
|
245
275
|
// While there exists a `RTCPeerConnection.getFingerprints` function, Firefox notably
|
|
246
276
|
// doesn't support it.
|
|
@@ -263,7 +293,7 @@ function connect(config) {
|
|
|
263
293
|
}
|
|
264
294
|
}
|
|
265
295
|
else {
|
|
266
|
-
const localSdpOffer = yield pc.createOffer();
|
|
296
|
+
const localSdpOffer = yield state.pc.createOffer();
|
|
267
297
|
// Note that this regex is not strict. The browser isn't a malicious actor, and the
|
|
268
298
|
// objective of this regex is not to detect invalid input.
|
|
269
299
|
const localSdpOfferFingerprintMatch = localSdpOffer.sdp.match(/a(\s*)=(\s*)fingerprint:(\s*)(sha|SHA)-256(\s*)(([a-fA-F0-9]{2}(:)*){32})/);
|
|
@@ -277,23 +307,23 @@ function connect(config) {
|
|
|
277
307
|
config.onConnectionReset('Failed to obtain the browser certificate fingerprint');
|
|
278
308
|
return;
|
|
279
309
|
}
|
|
280
|
-
localTlsCertificateSha256 = new Uint8Array(32);
|
|
310
|
+
let localTlsCertificateSha256 = new Uint8Array(32);
|
|
281
311
|
localTlsCertificateSha256.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 0);
|
|
282
312
|
// `onconnectionstatechange` is used to detect when the connection has closed or has failed
|
|
283
313
|
// to open.
|
|
284
314
|
// Note that smoldot will think that the connection is open even when it is still opening.
|
|
285
315
|
// Therefore we don't care about events concerning the fact that the connection is now fully
|
|
286
316
|
// open.
|
|
287
|
-
pc.onconnectionstatechange = (_event) => {
|
|
288
|
-
if (pc.connectionState == "closed" || pc.connectionState == "disconnected" || pc.connectionState == "failed") {
|
|
317
|
+
state.pc.onconnectionstatechange = (_event) => {
|
|
318
|
+
if (state.pc.connectionState == "closed" || state.pc.connectionState == "disconnected" || state.pc.connectionState == "failed") {
|
|
289
319
|
killAllJs();
|
|
290
|
-
config.onConnectionReset("WebRTC state transitioned to " + pc.connectionState);
|
|
320
|
+
config.onConnectionReset("WebRTC state transitioned to " + state.pc.connectionState);
|
|
291
321
|
}
|
|
292
322
|
};
|
|
293
|
-
pc.onnegotiationneeded = (_event) => __awaiter(this, void 0, void 0, function* () {
|
|
323
|
+
state.pc.onnegotiationneeded = (_event) => __awaiter(this, void 0, void 0, function* () {
|
|
294
324
|
var _a;
|
|
295
325
|
// Create a new offer and set it as local description.
|
|
296
|
-
let sdpOffer = (yield pc.createOffer()).sdp;
|
|
326
|
+
let sdpOffer = (yield state.pc.createOffer()).sdp;
|
|
297
327
|
// We check that the locally-generated SDP offer has a data channel with the UDP
|
|
298
328
|
// protocol. If that isn't the case, the connection will likely fail.
|
|
299
329
|
if (sdpOffer.match(/^m=application(\s+)(\d+)(\s+)UDP\/DTLS\/SCTP(\s+)webrtc-datachannel$/m) === null) {
|
|
@@ -312,7 +342,7 @@ function connect(config) {
|
|
|
312
342
|
const ufragPwd = "libp2p+webrtc+v1/" + browserGeneratedPwd;
|
|
313
343
|
sdpOffer = sdpOffer.replace(/^a=ice-ufrag.*$/m, 'a=ice-ufrag:' + ufragPwd);
|
|
314
344
|
sdpOffer = sdpOffer.replace(/^a=ice-pwd.*$/m, 'a=ice-pwd:' + ufragPwd);
|
|
315
|
-
yield pc.setLocalDescription({ type: 'offer', sdp: sdpOffer });
|
|
345
|
+
yield state.pc.setLocalDescription({ type: 'offer', sdp: sdpOffer });
|
|
316
346
|
// Transform certificate hash into fingerprint (upper-hex; each byte separated by ":").
|
|
317
347
|
const fingerprint = Array.from(remoteTlsCertificateSha256).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':');
|
|
318
348
|
// Note that the trailing line feed is important, as otherwise Chrome
|
|
@@ -373,9 +403,9 @@ function connect(config) {
|
|
|
373
403
|
// A transport address for a candidate that can be used for connectivity
|
|
374
404
|
// checks (RFC8839).
|
|
375
405
|
"a=candidate:1 1 UDP 1 " + targetIp + " " + String(targetPort) + " typ host" + "\n";
|
|
376
|
-
yield pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
|
|
406
|
+
yield state.pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
|
|
377
407
|
});
|
|
378
|
-
pc.ondatachannel = ({ channel }) => {
|
|
408
|
+
state.pc.ondatachannel = ({ channel }) => {
|
|
379
409
|
// TODO: is the substream maybe already open? according to the Internet it seems that no but it's unclear
|
|
380
410
|
addChannel(channel, 'inbound');
|
|
381
411
|
};
|
|
@@ -391,20 +421,22 @@ function connect(config) {
|
|
|
391
421
|
killAllJs();
|
|
392
422
|
}
|
|
393
423
|
else {
|
|
394
|
-
const channel = dataChannels.get(streamId);
|
|
424
|
+
const channel = state.dataChannels.get(streamId);
|
|
395
425
|
channel.channel.onopen = null;
|
|
396
426
|
channel.channel.onerror = null;
|
|
397
427
|
channel.channel.onclose = null;
|
|
398
428
|
channel.channel.onbufferedamountlow = null;
|
|
399
429
|
channel.channel.onmessage = null;
|
|
400
430
|
channel.channel.close();
|
|
401
|
-
dataChannels.delete(streamId);
|
|
431
|
+
state.dataChannels.delete(streamId);
|
|
402
432
|
}
|
|
403
433
|
},
|
|
404
434
|
send: (data, streamId) => {
|
|
405
|
-
const channel = dataChannels.get(streamId);
|
|
406
|
-
|
|
407
|
-
|
|
435
|
+
const channel = state.dataChannels.get(streamId);
|
|
436
|
+
for (const buffer of data) {
|
|
437
|
+
channel.bufferedBytes += buffer.length;
|
|
438
|
+
}
|
|
439
|
+
channel.channel.send(new Blob(data));
|
|
408
440
|
},
|
|
409
441
|
closeSend: () => { throw new Error('Wrong connection type'); },
|
|
410
442
|
openOutSubstream: () => {
|
|
@@ -412,7 +444,10 @@ function connect(config) {
|
|
|
412
444
|
// therefore `pc` is guaranteed to be non-null.
|
|
413
445
|
// Note that the label passed to `createDataChannel` is required to be empty as
|
|
414
446
|
// per the libp2p WebRTC specification.
|
|
415
|
-
|
|
447
|
+
// TODO: adjusting the options based on the first substream is a bit hacky
|
|
448
|
+
const opts = state.isFirstOutSubstream ? { negotiated: true, id: 0 } : {};
|
|
449
|
+
state.isFirstOutSubstream = false;
|
|
450
|
+
addChannel(state.pc.createDataChannel("", opts), 'outbound');
|
|
416
451
|
}
|
|
417
452
|
};
|
|
418
453
|
}
|
|
@@ -105,12 +105,14 @@ function connect(config) {
|
|
|
105
105
|
// we thus absorb any exception thrown here.
|
|
106
106
|
// See also <https://github.com/paritytech/smoldot/issues/2937>.
|
|
107
107
|
try {
|
|
108
|
-
socket.send(data);
|
|
109
108
|
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
|
|
110
109
|
bufferedAmountCheck.nextTimeout = 10;
|
|
111
110
|
setTimeout(checkBufferedAmount, 10);
|
|
112
111
|
}
|
|
113
|
-
|
|
112
|
+
for (const buffer of data) {
|
|
113
|
+
bufferedAmountCheck.quenedUnreportedBytes += buffer.length;
|
|
114
|
+
}
|
|
115
|
+
socket.send(new Blob(data));
|
|
114
116
|
}
|
|
115
117
|
catch (_error) { }
|
|
116
118
|
},
|
|
@@ -172,31 +174,35 @@ function connect(config) {
|
|
|
172
174
|
socket.inner.then((connec) => connec.close());
|
|
173
175
|
},
|
|
174
176
|
send: (data) => {
|
|
175
|
-
let dataCopy = Uint8Array.from(
|
|
177
|
+
let dataCopy = data.map((buf) => Uint8Array.from(buf)); // Deep copy of the data
|
|
176
178
|
socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
179
|
+
for (let buffer of dataCopy) {
|
|
180
|
+
while (buffer.length > 0) {
|
|
181
|
+
if (socket.destroyed || c === null)
|
|
182
|
+
return c;
|
|
183
|
+
let outcome;
|
|
184
|
+
try {
|
|
185
|
+
outcome = yield c.write(buffer);
|
|
186
|
+
config.onWritableBytes(buffer.length);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
// The type of `error` is unclear, but we assume that it
|
|
190
|
+
// implements `Error`
|
|
191
|
+
outcome = error.toString();
|
|
192
|
+
}
|
|
193
|
+
if (typeof outcome !== 'number') {
|
|
194
|
+
// The socket is reported closed, but `socket.destroyed` is still
|
|
195
|
+
// `false` (see check above). As such, we must inform the
|
|
196
|
+
// inner layers.
|
|
197
|
+
socket.destroyed = true;
|
|
198
|
+
config.onConnectionReset(outcome);
|
|
199
|
+
return c;
|
|
200
|
+
}
|
|
201
|
+
// Note that, contrary to `read`, it is possible for `outcome` to be 0.
|
|
202
|
+
// This happen if the write had to be interrupted, and the only thing
|
|
203
|
+
// we have to do is try writing again.
|
|
204
|
+
buffer = buffer.slice(outcome);
|
|
195
205
|
}
|
|
196
|
-
// Note that, contrary to `read`, it is possible for `outcome` to be 0.
|
|
197
|
-
// This happen if the write had to be interrupted, and the only thing
|
|
198
|
-
// we have to do is try writing again.
|
|
199
|
-
dataCopy = dataCopy.slice(outcome);
|
|
200
206
|
}
|
|
201
207
|
return c;
|
|
202
208
|
}));
|
|
@@ -106,12 +106,14 @@ function connect(config) {
|
|
|
106
106
|
socket.close();
|
|
107
107
|
},
|
|
108
108
|
send: (data) => {
|
|
109
|
-
socket.send(data);
|
|
110
109
|
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
|
|
111
110
|
bufferedAmountCheck.nextTimeout = 10;
|
|
112
111
|
setTimeout(checkBufferedAmount, 10);
|
|
113
112
|
}
|
|
114
|
-
|
|
113
|
+
for (const buffer of data) {
|
|
114
|
+
socket.send(buffer);
|
|
115
|
+
bufferedAmountCheck.quenedUnreportedBytes += buffer.length;
|
|
116
|
+
}
|
|
115
117
|
},
|
|
116
118
|
closeSend: () => { throw new Error('Wrong connection type'); },
|
|
117
119
|
openOutSubstream: () => { throw new Error('Wrong connection type'); }
|
|
@@ -158,17 +160,19 @@ function connect(config) {
|
|
|
158
160
|
socket.destroy();
|
|
159
161
|
},
|
|
160
162
|
send: (data) => {
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
163
|
+
for (const buffer of data) {
|
|
164
|
+
const bufferLen = buffer.length;
|
|
165
|
+
const allWritten = socket.write(buffer);
|
|
166
|
+
if (allWritten) {
|
|
167
|
+
setImmediate(() => {
|
|
168
|
+
if (!socket.writable)
|
|
169
|
+
return;
|
|
170
|
+
config.onWritableBytes(bufferLen);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
drainingBytes.num += bufferLen;
|
|
175
|
+
}
|
|
172
176
|
}
|
|
173
177
|
},
|
|
174
178
|
closeSend: () => {
|