Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

smoldot

Package Overview
Dependencies
Maintainers
1
Versions
104
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

smoldot - npm Package Compare versions

Comparing version 1.0.0 to 1.0.1

85

dist/cjs/index-browser.js

@@ -52,2 +52,4 @@ "use strict";

registerShouldPeriodicallyYield: (callback) => {
if (typeof document === 'undefined') // We might be in a web worker.
return [false, () => { }];
const wrappedCallback = () => callback(document.visibilityState === 'visible');

@@ -82,5 +84,4 @@ document.addEventListener('visibilitychange', wrappedCallback);

const wsParsed = config.address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc\/certhash\/(.*?)$/);
const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/);
if (wsParsed != null) {
let connection;
const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss';

@@ -95,6 +96,32 @@ if ((proto == 'ws' && forbidWs) ||

(proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
connection = new WebSocket(url);
const connection = new WebSocket(url);
connection.binaryType = 'arraybuffer';
const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (connection.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
// of all the data that has been sent, but that might not be the case. For this
// reason, we use `bufferedAmount` as a hint rather than a correct value.
const bufferedAmount = connection.bufferedAmount;
let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
if (wasSent < 0)
wasSent = 0;
bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
bufferedAmountCheck.nextTimeout *= 2;
if (bufferedAmountCheck.nextTimeout > 500)
bufferedAmountCheck.nextTimeout = 500;
}
// Note: it is important to call `onWritableBytes` at the very end, as it might
// trigger a call to `send`.
if (wasSent != 0)
config.onWritableBytes(wasSent);
};
connection.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({
type: 'single-stream', handshake: 'multistream-select-noise-yamux',
initialWritableBytes: 1024 * 1024, writeClosable: false,
});
};

@@ -118,3 +145,9 @@ connection.onclose = (event) => {

connection.send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
}
bufferedAmountCheck.quenedUnreportedBytes += data.length;
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => { throw new Error('Wrong connection type'); }

@@ -169,6 +202,7 @@ };

for (const channel of Array.from(dataChannels.values())) {
channel.onopen = null;
channel.onerror = null;
channel.onclose = null;
channel.onmessage = null;
channel.channel.onopen = null;
channel.channel.onerror = null;
channel.channel.onclose = null;
channel.channel.onbufferedamountlow = null;
channel.channel.onmessage = null;
}

@@ -180,2 +214,3 @@ dataChannels.clear();

handshakeDataChannel.onclose = null;
handshakeDataChannel.onbufferedamountlow = null;
handshakeDataChannel.onmessage = null;

@@ -209,3 +244,3 @@ }

console.assert(direction !== 'outbound' || !handshakeDataChannel, "handshakeDataChannel still defined");
config.onStreamOpened(dataChannelId, direction);
config.onStreamOpened(dataChannelId, direction, 65536);
}

@@ -232,2 +267,3 @@ };

handshakeDataChannel.onclose = null;
handshakeDataChannel.onbufferedamountlow = null;
handshakeDataChannel.onmessage = null;

@@ -249,2 +285,8 @@ handshakeDataChannel = undefined;

};
dataChannel.onbufferedamountlow = () => {
const channel = dataChannels.get(dataChannelId);
const val = channel.bufferedBytes;
channel.bufferedBytes = 0;
config.onWritableBytes(val, dataChannelId);
};
dataChannel.onmessage = (m) => {

@@ -255,3 +297,3 @@ // The `data` field is an `ArrayBuffer`.

if (direction !== 'first-outbound')
dataChannels.set(dataChannelId, dataChannel);
dataChannels.set(dataChannelId, { channel: dataChannel, bufferedBytes: 0 });
else

@@ -398,3 +440,4 @@ handshakeDataChannel = dataChannel;

// The maximum SCTP user message size (in bytes) (RFC8841)
"a=max-message-size:16384" + "\n" + // TODO: should this be part of the spec?
// Setting this field is part of the libp2p spec.
"a=max-message-size:16384" + "\n" +
// A transport address for a candidate that can be used for connectivity

@@ -428,7 +471,8 @@ // checks (RFC8839).

const channel = dataChannels.get(streamId);
channel.onopen = null;
channel.onerror = null;
channel.onclose = null;
channel.onmessage = null;
channel.close();
channel.channel.onopen = null;
channel.channel.onerror = null;
channel.channel.onclose = null;
channel.channel.onbufferedamountlow = null;
channel.channel.onmessage = null;
channel.channel.close();
dataChannels.delete(streamId);

@@ -438,4 +482,7 @@ }

send: (data, streamId) => {
dataChannels.get(streamId).send(data);
const channel = dataChannels.get(streamId);
channel.channel.send(data);
channel.bufferedBytes += data.length;
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => {

@@ -452,4 +499,4 @@ // `openOutSubstream` can only be called after we have called `config.onOpen`, therefore

if (handshakeDataChannel) {
config.onStreamOpened(handshakeDataChannel.id, 'outbound');
dataChannels.set(handshakeDataChannel.id, handshakeDataChannel);
config.onStreamOpened(handshakeDataChannel.id, 'outbound', 1024 * 1024);
dataChannels.set(handshakeDataChannel.id, { channel: handshakeDataChannel, bufferedBytes: 0 });
handshakeDataChannel = undefined;

@@ -456,0 +503,0 @@ }

@@ -112,3 +112,2 @@ "use strict";

function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
let connection;
// Attempt to parse the multiaddress.

@@ -128,20 +127,68 @@ // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)

(proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
connection = {
ty: 'websocket',
socket: new WebSocket(url)
const socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (socket.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
// of all the data that has been sent, but that might not be the case. For this
// reason, we use `bufferedAmount` as a hint rather than a correct value.
const bufferedAmount = socket.bufferedAmount;
let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
if (wasSent < 0)
wasSent = 0;
bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
bufferedAmountCheck.nextTimeout *= 2;
if (bufferedAmountCheck.nextTimeout > 500)
bufferedAmountCheck.nextTimeout = 500;
}
// Note: it is important to call `onWritableBytes` at the very end, as it might
// trigger a call to `send`.
if (wasSent != 0)
config.onWritableBytes(wasSent);
};
connection.socket.binaryType = 'arraybuffer';
connection.socket.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
socket.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
};
connection.socket.onclose = (event) => {
socket.onclose = (event) => {
const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
config.onConnectionReset(message);
};
connection.socket.onmessage = (msg) => {
socket.onmessage = (msg) => {
config.onMessage(new Uint8Array(msg.data));
};
return {
reset: () => {
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
socket.onopen = () => { };
socket.onclose = () => { };
socket.onmessage = () => { };
socket.onerror = () => { };
socket.close();
},
send: (data) => {
// The WebSocket library that we use seems to spontaneously transition connections
// to the "closed" state but not call the `onclosed` callback immediately. Calling
// `send` on that object throws an exception. In order to avoid panicking smoldot,
// we thus absorb any exception thrown here.
// See also <https://github.com/paritytech/smoldot/issues/2937>.
try {
socket.send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
}
bufferedAmountCheck.quenedUnreportedBytes += data.length;
}
catch (_error) { }
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}
else if (tcpParsed != null) {
// `net` module will be missing when we're not in NodeJS.
if (forbidTcp) {

@@ -161,3 +208,2 @@ throw new instance_js_1.ConnectionError('TCP connections not available');

};
connection = { ty: 'tcp', socket };
socket.inner = socket.inner.then((established) => {

@@ -168,3 +214,3 @@ // TODO: at the time of writing of this comment, `setNoDelay` is still unstable

return established;
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: true });
// Spawns an asynchronous task that continuously reads from the socket.

@@ -201,43 +247,10 @@ // Every time data is read, the task re-executes itself in order to continue reading.

});
}
else {
throw new instance_js_1.ConnectionError('Unrecognized multiaddr format');
}
return {
reset: () => {
if (connection.ty == 'websocket') {
// WebSocket
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
connection.socket.onopen = () => { };
connection.socket.onclose = () => { };
connection.socket.onmessage = () => { };
connection.socket.onerror = () => { };
connection.socket.close();
}
else {
// TCP
connection.socket.destroyed = true;
connection.socket.inner.then((connec) => connec.close());
}
},
send: (data) => {
if (connection.ty == 'websocket') {
// WebSocket
// The WebSocket library that we use seems to spontaneously transition connections
// to the "closed" state but not call the `onclosed` callback immediately. Calling
// `send` on that object throws an exception. In order to avoid panicking smoldot,
// we thus absorb any exception thrown here.
// See also <https://github.com/paritytech/smoldot/issues/2937>.
try {
connection.socket.send(data);
}
catch (_error) { }
}
else {
// TCP
// TODO: at the moment, sending data doesn't have any back-pressure mechanism; as such, we just buffer data indefinitely
return {
reset: () => {
socket.destroyed = true;
socket.inner.then((connec) => connec.close());
},
send: (data) => {
let dataCopy = Uint8Array.from(data); // Deep copy of the data
const socket = connection.socket;
connection.socket.inner = connection.socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
while (dataCopy.length > 0) {

@@ -249,2 +262,3 @@ if (socket.destroyed || c === null)

outcome = yield c.write(dataCopy);
config.onWritableBytes(dataCopy.length);
}

@@ -269,6 +283,15 @@ catch (error) {

}));
}
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
},
closeSend: () => {
socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
yield (c === null || c === void 0 ? void 0 : c.closeWrite());
return c;
}));
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}
else {
throw new instance_js_1.ConnectionError('Unrecognized multiaddr format');
}
}

@@ -54,3 +54,3 @@ "use strict";

getRandomValues: (buffer) => {
if (buffer.length >= 65536)
if (buffer.length >= 1024 * 1024)
throw new Error('getRandomValues buffer too large');

@@ -72,3 +72,2 @@ (0, node_crypto_1.randomFillSync)(buffer);

function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
let connection;
// Attempt to parse the multiaddress.

@@ -90,4 +89,29 @@ // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)

socket.binaryType = 'arraybuffer';
const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (socket.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
// of all the data that has been sent, but that seems to not be the case. It is
// unclear whether this is intended or a bug, but is is likely that `bufferedAmount`
// also includes WebSocket headers. For this reason, we use `bufferedAmount` as a hint
// rather than a correct value.
const bufferedAmount = socket.bufferedAmount;
let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
if (wasSent < 0)
wasSent = 0;
bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
bufferedAmountCheck.nextTimeout *= 2;
if (bufferedAmountCheck.nextTimeout > 500)
bufferedAmountCheck.nextTimeout = 500;
}
// Note: it is important to call `onWritableBytes` at the very end, as it might
// trigger a call to `send`.
if (wasSent != 0)
config.onWritableBytes(wasSent);
};
socket.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
};

@@ -112,3 +136,23 @@ socket.onclose = (event) => {

};
connection = { ty: 'websocket', socket };
return {
reset: () => {
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
socket.onopen = () => { };
socket.onclose = () => { };
socket.onmessage = () => { };
socket.onerror = () => { };
socket.close();
},
send: (data) => {
socket.send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
}
bufferedAmountCheck.quenedUnreportedBytes += data.length;
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}

@@ -124,10 +168,14 @@ else if (tcpParsed != null) {

});
connection = { ty: 'tcp', socket };
connection.socket.setNoDelay();
connection.socket.on('connect', () => {
// Number of bytes queued using `socket.write` and where `write` has returned false.
const drainingBytes = { num: 0 };
socket.setNoDelay();
socket.on('connect', () => {
if (socket.destroyed)
return;
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({
type: 'single-stream', handshake: 'multistream-select-noise-yamux',
initialWritableBytes: socket.writableHighWaterMark, writeClosable: true
});
});
connection.socket.on('close', (hasError) => {
socket.on('close', (hasError) => {
if (socket.destroyed)

@@ -140,4 +188,4 @@ return;

});
connection.socket.on('error', () => { });
connection.socket.on('data', (message) => {
socket.on('error', () => { });
socket.on('data', (message) => {
if (socket.destroyed)

@@ -147,2 +195,34 @@ return;

});
socket.on('drain', () => {
// The bytes queued using `socket.write` and where `write` has returned false have now
// been sent. Notify the API that it can write more data.
if (socket.destroyed)
return;
const val = drainingBytes.num;
drainingBytes.num = 0;
config.onWritableBytes(val);
});
return {
reset: () => {
socket.destroy();
},
send: (data) => {
const dataLen = data.length;
const allWritten = socket.write(data);
if (allWritten) {
setImmediate(() => {
if (!socket.writable)
return;
config.onWritableBytes(dataLen);
});
}
else {
drainingBytes.num += dataLen;
}
},
closeSend: () => {
socket.end();
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}

@@ -152,31 +232,2 @@ else {

}
return {
reset: () => {
if (connection.ty == 'websocket') {
// WebSocket
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
connection.socket.onopen = () => { };
connection.socket.onclose = () => { };
connection.socket.onmessage = () => { };
connection.socket.onerror = () => { };
connection.socket.close();
}
else {
// TCP
connection.socket.destroy();
}
},
send: (data) => {
if (connection.ty == 'websocket') {
// WebSocket
connection.socket.send(data);
}
else {
// TCP
connection.socket.write(data);
}
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}

@@ -66,9 +66,30 @@ import type { SmoldotWasmInstance } from './bindings.js';

*
* The connection must currently be in the `Open` state.
* The connection and stream must currently be in the `Open` state.
*
* The number of bytes most never exceed the number of "writable bytes" of the stream.
* `onWritableBytes` can be used in order to notify that more writable bytes are available.
*
* The `streamId` must be provided if and only if the connection is of type "multi-stream".
* It indicates which substream to send the data on.
*
* Must not be called after `closeSend` has been called.
*/
send(data: Uint8Array, streamId?: number): void;
/**
* Closes the writing side of the given stream of the given connection.
*
* Never called for connection types where this isn't possible to implement (i.e. WebSocket
* and WebRTC at the moment).
*
* The connection and stream must currently be in the `Open` state.
*
* Implicitly sets the "writable bytes" of the stream to zero.
*
* The `streamId` must be provided if and only if the connection is of type "multi-stream".
* It indicates which substream to send the data on.
*
* Must only be called once per stream.
*/
closeSend(streamId?: number): void;
/**
* Start opening an additional outbound substream on the given connection.

@@ -108,2 +129,4 @@ *

handshake: 'multistream-select-noise-yamux';
initialWritableBytes: number;
writeClosable: boolean;
} | {

@@ -126,3 +149,3 @@ type: 'multi-stream';

*/
onStreamOpened: (streamId: number, direction: 'inbound' | 'outbound') => void;
onStreamOpened: (streamId: number, direction: 'inbound' | 'outbound', initialWritableBytes: number) => void;
/**

@@ -137,2 +160,13 @@ * Callback called when a stream transitions to the `Reset` state.

/**
* Callback called when more data can be written on the stream.
*
* Can only happen while the connection is in the `Open` state.
*
* This callback must not be called after `closeSend` has been called.
*
* The `streamId` parameter must be provided if and only if the connection is of type
* "multi-stream".
*/
onWritableBytes: (numExtra: number, streamId?: number) => void;
/**
* Callback called when a message sent by the remote has been received.

@@ -139,0 +173,0 @@ *

@@ -100,3 +100,3 @@ "use strict";

// with `1`) and wants you to use `setImmediate` instead.
if (ms == 0 && typeof setImmediate === "function") {
if (ms < 1 && typeof setImmediate === "function") {
setImmediate(() => {

@@ -144,3 +144,3 @@ if (killedTracked.killed)

case 'single-stream': {
instance.exports.connection_open_single_stream(connectionId, 0);
instance.exports.connection_open_single_stream(connectionId, 0, info.initialWritableBytes, info.writeClosable ? 1 : 0);
break;

@@ -173,2 +173,10 @@ }

},
onWritableBytes: (numExtra, streamId) => {
if (killedTracked.killed)
return;
try {
instance.exports.stream_writable_bytes(connectionId, streamId || 0, numExtra);
}
catch (_error) { }
},
onMessage: (message, streamId) => {

@@ -184,7 +192,7 @@ if (killedTracked.killed)

},
onStreamOpened: (streamId, direction) => {
onStreamOpened: (streamId, direction, initialWritableBytes) => {
if (killedTracked.killed)
return;
try {
instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0);
instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0, initialWritableBytes);
}

@@ -251,2 +259,8 @@ catch (_error) { }

},
stream_send_close: (connectionId, streamId) => {
if (killedTracked.killed)
return;
const connection = connections[connectionId];
connection.closeSend(streamId); // TODO: docs says the streamId is provided only for multi-stream connections, but here it's always provided
},
current_task_entered: (ptr, len) => {

@@ -253,0 +267,0 @@ if (killedTracked.killed)

@@ -22,6 +22,7 @@ /**

timer_finished: (timerId: number) => void;
connection_open_single_stream: (connectionId: number, handshakeTy: number) => void;
connection_open_single_stream: (connectionId: number, handshakeTy: number, initialWritableBytes: number, writeClosable: number) => void;
connection_open_multi_stream: (connectionId: number, handshakeTyPtr: number, handshakeTyLen: number) => void;
stream_writable_bytes: (connectionId: number, streamId: number, numBytes: number) => void;
stream_message: (connectionId: number, streamId: number, ptr: number, len: number) => void;
connection_stream_opened: (connectionId: number, streamId: number, outbound: number) => void;
connection_stream_opened: (connectionId: number, streamId: number, outbound: number, initialWritableBytes: number) => void;
connection_reset: (connectionId: number, ptr: number, len: number) => void;

@@ -28,0 +29,0 @@ stream_reset: (connectionId: number, streamId: number) => void;

@@ -43,2 +43,4 @@ // Smoldot

registerShouldPeriodicallyYield: (callback) => {
if (typeof document === 'undefined') // We might be in a web worker.
return [false, () => { }];
const wrappedCallback = () => callback(document.visibilityState === 'visible');

@@ -72,5 +74,4 @@ document.addEventListener('visibilitychange', wrappedCallback);

const wsParsed = config.address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc\/certhash\/(.*?)$/);
const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc-direct\/certhash\/(.*?)$/);
if (wsParsed != null) {
let connection;
const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss';

@@ -85,6 +86,32 @@ if ((proto == 'ws' && forbidWs) ||

(proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
connection = new WebSocket(url);
const connection = new WebSocket(url);
connection.binaryType = 'arraybuffer';
const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (connection.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
// of all the data that has been sent, but that might not be the case. For this
// reason, we use `bufferedAmount` as a hint rather than a correct value.
const bufferedAmount = connection.bufferedAmount;
let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
if (wasSent < 0)
wasSent = 0;
bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
bufferedAmountCheck.nextTimeout *= 2;
if (bufferedAmountCheck.nextTimeout > 500)
bufferedAmountCheck.nextTimeout = 500;
}
// Note: it is important to call `onWritableBytes` at the very end, as it might
// trigger a call to `send`.
if (wasSent != 0)
config.onWritableBytes(wasSent);
};
connection.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({
type: 'single-stream', handshake: 'multistream-select-noise-yamux',
initialWritableBytes: 1024 * 1024, writeClosable: false,
});
};

@@ -108,3 +135,9 @@ connection.onclose = (event) => {

connection.send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
}
bufferedAmountCheck.quenedUnreportedBytes += data.length;
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => { throw new Error('Wrong connection type'); }

@@ -159,6 +192,7 @@ };

for (const channel of Array.from(dataChannels.values())) {
channel.onopen = null;
channel.onerror = null;
channel.onclose = null;
channel.onmessage = null;
channel.channel.onopen = null;
channel.channel.onerror = null;
channel.channel.onclose = null;
channel.channel.onbufferedamountlow = null;
channel.channel.onmessage = null;
}

@@ -170,2 +204,3 @@ dataChannels.clear();

handshakeDataChannel.onclose = null;
handshakeDataChannel.onbufferedamountlow = null;
handshakeDataChannel.onmessage = null;

@@ -199,3 +234,3 @@ }

console.assert(direction !== 'outbound' || !handshakeDataChannel, "handshakeDataChannel still defined");
config.onStreamOpened(dataChannelId, direction);
config.onStreamOpened(dataChannelId, direction, 65536);
}

@@ -222,2 +257,3 @@ };

handshakeDataChannel.onclose = null;
handshakeDataChannel.onbufferedamountlow = null;
handshakeDataChannel.onmessage = null;

@@ -239,2 +275,8 @@ handshakeDataChannel = undefined;

};
dataChannel.onbufferedamountlow = () => {
const channel = dataChannels.get(dataChannelId);
const val = channel.bufferedBytes;
channel.bufferedBytes = 0;
config.onWritableBytes(val, dataChannelId);
};
dataChannel.onmessage = (m) => {

@@ -245,3 +287,3 @@ // The `data` field is an `ArrayBuffer`.

if (direction !== 'first-outbound')
dataChannels.set(dataChannelId, dataChannel);
dataChannels.set(dataChannelId, { channel: dataChannel, bufferedBytes: 0 });
else

@@ -388,3 +430,4 @@ handshakeDataChannel = dataChannel;

// The maximum SCTP user message size (in bytes) (RFC8841)
"a=max-message-size:16384" + "\n" + // TODO: should this be part of the spec?
// Setting this field is part of the libp2p spec.
"a=max-message-size:16384" + "\n" +
// A transport address for a candidate that can be used for connectivity

@@ -418,7 +461,8 @@ // checks (RFC8839).

const channel = dataChannels.get(streamId);
channel.onopen = null;
channel.onerror = null;
channel.onclose = null;
channel.onmessage = null;
channel.close();
channel.channel.onopen = null;
channel.channel.onerror = null;
channel.channel.onclose = null;
channel.channel.onbufferedamountlow = null;
channel.channel.onmessage = null;
channel.channel.close();
dataChannels.delete(streamId);

@@ -428,4 +472,7 @@ }

send: (data, streamId) => {
dataChannels.get(streamId).send(data);
const channel = dataChannels.get(streamId);
channel.channel.send(data);
channel.bufferedBytes += data.length;
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => {

@@ -442,4 +489,4 @@ // `openOutSubstream` can only be called after we have called `config.onOpen`, therefore

if (handshakeDataChannel) {
config.onStreamOpened(handshakeDataChannel.id, 'outbound');
dataChannels.set(handshakeDataChannel.id, handshakeDataChannel);
config.onStreamOpened(handshakeDataChannel.id, 'outbound', 1024 * 1024);
dataChannels.set(handshakeDataChannel.id, { channel: handshakeDataChannel, bufferedBytes: 0 });
handshakeDataChannel = undefined;

@@ -446,0 +493,0 @@ }

@@ -102,3 +102,2 @@ // Smoldot

function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
let connection;
// Attempt to parse the multiaddress.

@@ -118,20 +117,68 @@ // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)

(proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
connection = {
ty: 'websocket',
socket: new WebSocket(url)
const socket = new WebSocket(url);
socket.binaryType = 'arraybuffer';
const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (socket.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
// of all the data that has been sent, but that might not be the case. For this
// reason, we use `bufferedAmount` as a hint rather than a correct value.
const bufferedAmount = socket.bufferedAmount;
let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
if (wasSent < 0)
wasSent = 0;
bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
bufferedAmountCheck.nextTimeout *= 2;
if (bufferedAmountCheck.nextTimeout > 500)
bufferedAmountCheck.nextTimeout = 500;
}
// Note: it is important to call `onWritableBytes` at the very end, as it might
// trigger a call to `send`.
if (wasSent != 0)
config.onWritableBytes(wasSent);
};
connection.socket.binaryType = 'arraybuffer';
connection.socket.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
socket.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
};
connection.socket.onclose = (event) => {
socket.onclose = (event) => {
const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
config.onConnectionReset(message);
};
connection.socket.onmessage = (msg) => {
socket.onmessage = (msg) => {
config.onMessage(new Uint8Array(msg.data));
};
return {
reset: () => {
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
socket.onopen = () => { };
socket.onclose = () => { };
socket.onmessage = () => { };
socket.onerror = () => { };
socket.close();
},
send: (data) => {
// The WebSocket library that we use seems to spontaneously transition connections
// to the "closed" state but not call the `onclosed` callback immediately. Calling
// `send` on that object throws an exception. In order to avoid panicking smoldot,
// we thus absorb any exception thrown here.
// See also <https://github.com/paritytech/smoldot/issues/2937>.
try {
socket.send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
}
bufferedAmountCheck.quenedUnreportedBytes += data.length;
}
catch (_error) { }
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}
else if (tcpParsed != null) {
// `net` module will be missing when we're not in NodeJS.
if (forbidTcp) {

@@ -151,3 +198,2 @@ throw new ConnectionError('TCP connections not available');

};
connection = { ty: 'tcp', socket };
socket.inner = socket.inner.then((established) => {

@@ -158,3 +204,3 @@ // TODO: at the time of writing of this comment, `setNoDelay` is still unstable

return established;
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: true });
// Spawns an asynchronous task that continuously reads from the socket.

@@ -191,43 +237,10 @@ // Every time data is read, the task re-executes itself in order to continue reading.

});
}
else {
throw new ConnectionError('Unrecognized multiaddr format');
}
return {
reset: () => {
if (connection.ty == 'websocket') {
// WebSocket
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
connection.socket.onopen = () => { };
connection.socket.onclose = () => { };
connection.socket.onmessage = () => { };
connection.socket.onerror = () => { };
connection.socket.close();
}
else {
// TCP
connection.socket.destroyed = true;
connection.socket.inner.then((connec) => connec.close());
}
},
send: (data) => {
if (connection.ty == 'websocket') {
// WebSocket
// The WebSocket library that we use seems to spontaneously transition connections
// to the "closed" state but not call the `onclosed` callback immediately. Calling
// `send` on that object throws an exception. In order to avoid panicking smoldot,
// we thus absorb any exception thrown here.
// See also <https://github.com/paritytech/smoldot/issues/2937>.
try {
connection.socket.send(data);
}
catch (_error) { }
}
else {
// TCP
// TODO: at the moment, sending data doesn't have any back-pressure mechanism; as such, we just buffer data indefinitely
return {
reset: () => {
socket.destroyed = true;
socket.inner.then((connec) => connec.close());
},
send: (data) => {
let dataCopy = Uint8Array.from(data); // Deep copy of the data
const socket = connection.socket;
connection.socket.inner = connection.socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
while (dataCopy.length > 0) {

@@ -239,2 +252,3 @@ if (socket.destroyed || c === null)

outcome = yield c.write(dataCopy);
config.onWritableBytes(dataCopy.length);
}

@@ -259,6 +273,15 @@ catch (error) {

}));
}
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
},
closeSend: () => {
socket.inner = socket.inner.then((c) => __awaiter(this, void 0, void 0, function* () {
yield (c === null || c === void 0 ? void 0 : c.closeWrite());
return c;
}));
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}
else {
throw new ConnectionError('Unrecognized multiaddr format');
}
}

@@ -45,3 +45,3 @@ // Smoldot

getRandomValues: (buffer) => {
if (buffer.length >= 65536)
if (buffer.length >= 1024 * 1024)
throw new Error('getRandomValues buffer too large');

@@ -62,3 +62,2 @@ randomFillSync(buffer);

function connect(config, forbidTcp, forbidWs, forbidNonLocalWs, forbidWss) {
let connection;
// Attempt to parse the multiaddress.

@@ -80,4 +79,29 @@ // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)

socket.binaryType = 'arraybuffer';
const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (socket.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
// of all the data that has been sent, but that seems to not be the case. It is
// unclear whether this is intended or a bug, but is is likely that `bufferedAmount`
// also includes WebSocket headers. For this reason, we use `bufferedAmount` as a hint
// rather than a correct value.
const bufferedAmount = socket.bufferedAmount;
let wasSent = bufferedAmountCheck.quenedUnreportedBytes - bufferedAmount;
if (wasSent < 0)
wasSent = 0;
bufferedAmountCheck.quenedUnreportedBytes -= wasSent;
if (bufferedAmountCheck.quenedUnreportedBytes != 0) {
setTimeout(checkBufferedAmount, bufferedAmountCheck.nextTimeout);
bufferedAmountCheck.nextTimeout *= 2;
if (bufferedAmountCheck.nextTimeout > 500)
bufferedAmountCheck.nextTimeout = 500;
}
// Note: it is important to call `onWritableBytes` at the very end, as it might
// trigger a call to `send`.
if (wasSent != 0)
config.onWritableBytes(wasSent);
};
socket.onopen = () => {
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux', initialWritableBytes: 1024 * 1024, writeClosable: false });
};

@@ -102,3 +126,23 @@ socket.onclose = (event) => {

};
connection = { ty: 'websocket', socket };
return {
reset: () => {
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
socket.onopen = () => { };
socket.onclose = () => { };
socket.onmessage = () => { };
socket.onerror = () => { };
socket.close();
},
send: (data) => {
socket.send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
}
bufferedAmountCheck.quenedUnreportedBytes += data.length;
},
closeSend: () => { throw new Error('Wrong connection type'); },
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}

@@ -114,10 +158,14 @@ else if (tcpParsed != null) {

});
connection = { ty: 'tcp', socket };
connection.socket.setNoDelay();
connection.socket.on('connect', () => {
// Number of bytes queued using `socket.write` and where `write` has returned false.
const drainingBytes = { num: 0 };
socket.setNoDelay();
socket.on('connect', () => {
if (socket.destroyed)
return;
config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
config.onOpen({
type: 'single-stream', handshake: 'multistream-select-noise-yamux',
initialWritableBytes: socket.writableHighWaterMark, writeClosable: true
});
});
connection.socket.on('close', (hasError) => {
socket.on('close', (hasError) => {
if (socket.destroyed)

@@ -130,4 +178,4 @@ return;

});
connection.socket.on('error', () => { });
connection.socket.on('data', (message) => {
socket.on('error', () => { });
socket.on('data', (message) => {
if (socket.destroyed)

@@ -137,2 +185,34 @@ return;

});
socket.on('drain', () => {
// The bytes queued using `socket.write` and where `write` has returned false have now
// been sent. Notify the API that it can write more data.
if (socket.destroyed)
return;
const val = drainingBytes.num;
drainingBytes.num = 0;
config.onWritableBytes(val);
});
return {
reset: () => {
socket.destroy();
},
send: (data) => {
const dataLen = data.length;
const allWritten = socket.write(data);
if (allWritten) {
setImmediate(() => {
if (!socket.writable)
return;
config.onWritableBytes(dataLen);
});
}
else {
drainingBytes.num += dataLen;
}
},
closeSend: () => {
socket.end();
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}

@@ -142,31 +222,2 @@ else {

}
return {
reset: () => {
if (connection.ty == 'websocket') {
// WebSocket
// We can't set these fields to null because the TypeScript definitions don't
// allow it, but we can set them to dummy values.
connection.socket.onopen = () => { };
connection.socket.onclose = () => { };
connection.socket.onmessage = () => { };
connection.socket.onerror = () => { };
connection.socket.close();
}
else {
// TCP
connection.socket.destroy();
}
},
send: (data) => {
if (connection.ty == 'websocket') {
// WebSocket
connection.socket.send(data);
}
else {
// TCP
connection.socket.write(data);
}
},
openOutSubstream: () => { throw new Error('Wrong connection type'); }
};
}

@@ -66,9 +66,30 @@ import type { SmoldotWasmInstance } from './bindings.js';

*
* The connection must currently be in the `Open` state.
* The connection and stream must currently be in the `Open` state.
*
* The number of bytes most never exceed the number of "writable bytes" of the stream.
* `onWritableBytes` can be used in order to notify that more writable bytes are available.
*
* The `streamId` must be provided if and only if the connection is of type "multi-stream".
* It indicates which substream to send the data on.
*
* Must not be called after `closeSend` has been called.
*/
send(data: Uint8Array, streamId?: number): void;
/**
* Closes the writing side of the given stream of the given connection.
*
* Never called for connection types where this isn't possible to implement (i.e. WebSocket
* and WebRTC at the moment).
*
* The connection and stream must currently be in the `Open` state.
*
* Implicitly sets the "writable bytes" of the stream to zero.
*
* The `streamId` must be provided if and only if the connection is of type "multi-stream".
* It indicates which substream to send the data on.
*
* Must only be called once per stream.
*/
closeSend(streamId?: number): void;
/**
* Start opening an additional outbound substream on the given connection.

@@ -108,2 +129,4 @@ *

handshake: 'multistream-select-noise-yamux';
initialWritableBytes: number;
writeClosable: boolean;
} | {

@@ -126,3 +149,3 @@ type: 'multi-stream';

*/
onStreamOpened: (streamId: number, direction: 'inbound' | 'outbound') => void;
onStreamOpened: (streamId: number, direction: 'inbound' | 'outbound', initialWritableBytes: number) => void;
/**

@@ -137,2 +160,13 @@ * Callback called when a stream transitions to the `Reset` state.

/**
* Callback called when more data can be written on the stream.
*
* Can only happen while the connection is in the `Open` state.
*
* This callback must not be called after `closeSend` has been called.
*
* The `streamId` parameter must be provided if and only if the connection is of type
* "multi-stream".
*/
onWritableBytes: (numExtra: number, streamId?: number) => void;
/**
* Callback called when a message sent by the remote has been received.

@@ -139,0 +173,0 @@ *

@@ -96,3 +96,3 @@ // Smoldot

// with `1`) and wants you to use `setImmediate` instead.
if (ms == 0 && typeof setImmediate === "function") {
if (ms < 1 && typeof setImmediate === "function") {
setImmediate(() => {

@@ -140,3 +140,3 @@ if (killedTracked.killed)

case 'single-stream': {
instance.exports.connection_open_single_stream(connectionId, 0);
instance.exports.connection_open_single_stream(connectionId, 0, info.initialWritableBytes, info.writeClosable ? 1 : 0);
break;

@@ -169,2 +169,10 @@ }

},
onWritableBytes: (numExtra, streamId) => {
if (killedTracked.killed)
return;
try {
instance.exports.stream_writable_bytes(connectionId, streamId || 0, numExtra);
}
catch (_error) { }
},
onMessage: (message, streamId) => {

@@ -180,7 +188,7 @@ if (killedTracked.killed)

},
onStreamOpened: (streamId, direction) => {
onStreamOpened: (streamId, direction, initialWritableBytes) => {
if (killedTracked.killed)
return;
try {
instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0);
instance.exports.connection_stream_opened(connectionId, streamId, direction === 'outbound' ? 1 : 0, initialWritableBytes);
}

@@ -247,2 +255,8 @@ catch (_error) { }

},
stream_send_close: (connectionId, streamId) => {
if (killedTracked.killed)
return;
const connection = connections[connectionId];
connection.closeSend(streamId); // TODO: docs says the streamId is provided only for multi-stream connections, but here it's always provided
},
current_task_entered: (ptr, len) => {

@@ -249,0 +263,0 @@ if (killedTracked.killed)

@@ -22,6 +22,7 @@ /**

timer_finished: (timerId: number) => void;
connection_open_single_stream: (connectionId: number, handshakeTy: number) => void;
connection_open_single_stream: (connectionId: number, handshakeTy: number, initialWritableBytes: number, writeClosable: number) => void;
connection_open_multi_stream: (connectionId: number, handshakeTyPtr: number, handshakeTyLen: number) => void;
stream_writable_bytes: (connectionId: number, streamId: number, numBytes: number) => void;
stream_message: (connectionId: number, streamId: number, ptr: number, len: number) => void;
connection_stream_opened: (connectionId: number, streamId: number, outbound: number) => void;
connection_stream_opened: (connectionId: number, streamId: number, outbound: number, initialWritableBytes: number) => void;
connection_reset: (connectionId: number, ptr: number, len: number) => void;

@@ -28,0 +29,0 @@ stream_reset: (connectionId: number, streamId: number) => void;

{
"name": "smoldot",
"version": "1.0.0",
"version": "1.0.1",
"description": "Light client that connects to Polkadot and Substrate-based blockchains",

@@ -51,4 +51,4 @@ "author": "Parity Technologies <admin@parity.io>",

"typedoc": "^0.23.15",
"typescript": "^4.5.4"
"typescript": "^5.0.2"
}
}

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc