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