Skip to content

Commit

Permalink
Fix #794 differently (#800)
Browse files Browse the repository at this point in the history
* Revert "Don't panic when platformBindings.connect throws an exception (#795)"

This reverts commit 34d6a72.

* Fix the problem differently

* Must not throw

* PR link

* Be more passive aggressive
  • Loading branch information
tomaka authored Jun 23, 2023
1 parent 12644b3 commit 3b30af6
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 71 deletions.
2 changes: 1 addition & 1 deletion wasm-node/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

### Fixed

- Fix not absorbing the JavaScript exception triggered by the browser when connecting to a `ws://` node when smoldot is embedded in a web page served over `https://`. ([#795](https://github.com/smol-dot/smoldot/pull/795))
- Fix not absorbing the JavaScript exception triggered by the browser when connecting to a `ws://` node when smoldot is embedded in a web page served over `https://`. ([#795](https://github.com/smol-dot/smoldot/pull/795), [#800](https://github.com/smol-dot/smoldot/pull/800))

## 1.0.10 - 2023-06-19

Expand Down
83 changes: 34 additions & 49 deletions wasm-node/javascript/src/internals/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ export interface PlatformBindings {
* Tries to open a new connection using the given configuration.
*
* @see Connection
* @throws {@link Error}
*/
connect(config: ConnectionConfig): Connection;

Expand Down Expand Up @@ -331,54 +330,40 @@ export function start(options: ClientOptions, wasmModule: SmoldotBytecode | Prom
}
case "new-connection": {
const connectionId = event.connectionId;

let connection;
try {
connection = platformBindings.connect({
address: event.address,
onConnectionReset(message) {
if (state.instance.status !== "ready")
throw new Error();
state.connections.delete(connectionId);
state.instance.instance.connectionReset(connectionId, message);
},
onMessage(message, streamId) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamMessage(connectionId, message, streamId);
},
onStreamOpened(streamId, direction, initialWritableBytes) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamOpened(connectionId, streamId, direction, initialWritableBytes);
},
onOpen(info) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.connectionOpened(connectionId, info);
},
onWritableBytes(numExtra, streamId) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamWritableBytes(connectionId, numExtra, streamId);
},
onStreamReset(streamId) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamReset(connectionId, streamId);
},
});

} catch(error) {
const errorMsg = error instanceof Error ? error.toString() : "Uncaught exception while connecting";
setTimeout(() => {
if (state.instance.status === 'ready')
state.instance.instance.connectionReset(connectionId, errorMsg);
}, 0);
break;
}

state.connections.set(connectionId, connection);
state.connections.set(connectionId, platformBindings.connect({
address: event.address,
onConnectionReset(message) {
if (state.instance.status !== "ready")
throw new Error();
state.connections.delete(connectionId);
state.instance.instance.connectionReset(connectionId, message);
},
onMessage(message, streamId) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamMessage(connectionId, message, streamId);
},
onStreamOpened(streamId, direction, initialWritableBytes) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamOpened(connectionId, streamId, direction, initialWritableBytes);
},
onOpen(info) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.connectionOpened(connectionId, info);
},
onWritableBytes(numExtra, streamId) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamWritableBytes(connectionId, numExtra, streamId);
},
onStreamReset(streamId) {
if (state.instance.status !== "ready")
throw new Error();
state.instance.instance.streamReset(connectionId, streamId);
},
}));
break;
}
case "connection-reset": {
Expand Down
69 changes: 48 additions & 21 deletions wasm-node/javascript/src/no-auto-bytecode-browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,23 @@ export function startWithBytecode(options: ClientOptionsWithBytecode): Client {
*/
function connect(config: ConnectionConfig): Connection {
if (config.address.ty === "websocket") {
const connection = new WebSocket(config.address.url);
connection.binaryType = 'arraybuffer';
// Even though the WHATWG specification (<https://websockets.spec.whatwg.org/#dom-websocket-websocket>)
// doesn't mention it, `new WebSocket` can throw an exception if the URL is forbidden
// for security reasons. We absord this exception as soon as it is thrown.
// `connection` can be either a `WebSocket` object (the normal case), or a string
// indicating an error message that must be propagated with `onConnectionReset` as soon
// as possible, or `null` if the API user considers the connection as reset.
let connection: WebSocket | string | null;
try {
connection = new WebSocket(config.address.url);
} catch(error) {
connection = error instanceof Error ? error.toString() : "Exception thrown by new WebSocket";
}

const bufferedAmountCheck = { quenedUnreportedBytes: 0, nextTimeout: 10 };
const checkBufferedAmount = () => {
if (!(connection instanceof WebSocket))
return;
if (connection.readyState != 1)
return;
// Note that we might expect `bufferedAmount` to always be <= the sum of the lengths
Expand All @@ -107,31 +119,46 @@ function connect(config: ConnectionConfig): Connection {
config.onWritableBytes(wasSent);
};

connection.onopen = () => {
config.onOpen({
type: 'single-stream', handshake: 'multistream-select-noise-yamux',
initialWritableBytes: 1024 * 1024, writeClosable: false,
});
};
connection.onclose = (event) => {
const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
config.onConnectionReset(message);
};
connection.onmessage = (msg) => {
config.onMessage(new Uint8Array(msg.data as ArrayBuffer));
};
if (connection instanceof WebSocket) {
connection.binaryType = 'arraybuffer';

connection.onopen = () => {
config.onOpen({
type: 'single-stream', handshake: 'multistream-select-noise-yamux',
initialWritableBytes: 1024 * 1024, writeClosable: false,
});
};
connection.onclose = (event) => {
const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
config.onConnectionReset(message);
};
connection.onmessage = (msg) => {
config.onMessage(new Uint8Array(msg.data as ArrayBuffer));
};
} else {
setTimeout(() => {
if (connection && !(connection instanceof WebSocket)) {
config.onConnectionReset(connection);
connection = null;
}
}, 1)
}

return {
reset: (): void => {
connection.onopen = null;
connection.onclose = null;
connection.onmessage = null;
connection.onerror = null;
connection.close();
if (connection instanceof WebSocket) {
connection.onopen = null;
connection.onclose = null;
connection.onmessage = null;
connection.onerror = null;
connection.close();
}

connection = null;
},

send: (data: Uint8Array): void => {
connection.send(data);
(connection as WebSocket).send(data);
if (bufferedAmountCheck.quenedUnreportedBytes == 0) {
bufferedAmountCheck.nextTimeout = 10;
setTimeout(checkBufferedAmount, 10);
Expand Down

0 comments on commit 3b30af6

Please sign in to comment.