Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow passing in a custom createConnection function #1944

Closed
1 task done
connor4312 opened this issue Sep 10, 2021 · 4 comments
Closed
1 task done

Allow passing in a custom createConnection function #1944

connor4312 opened this issue Sep 10, 2021 · 4 comments

Comments

@connor4312
Copy link

connor4312 commented Sep 10, 2021

  • I've searched for any related issues and avoided creating a duplicate issue.

Description

We have a kind of exotic use case where we want to instantiate a websocket off an existing stream that's not from an HTTP or unix socket source. Currently the createConnection method is hardcoded to either create a TLS or standard net connection:

opts.createConnection = isSecure ? tlsConnect : netConnect;

I suggest allowing a custom createConnection function to be passed in the websocket options and, if present, it should take precedence over the existing default logic.

I'm happy to put together a PR for this if it's something you'd accept 🙂

ws version

8.2.2

Node.js Version

v16.6.1

System

System:
OS: Windows 10 10.0.19043
CPU: (24) x64 AMD Ryzen 9 5900X 12-Core Processor
Memory: 23.22 GB / 63.89 GB

Expected result

N/A

Actual result

N/A

Attachments

No response

@lpinca
Copy link
Member

lpinca commented Sep 10, 2021

You can use the agent option and provide a custom Agent to do that.

import { Agent } from 'https';
import { WebSocket } from 'ws';
import { connect } from 'tls';

const agent = new Agent();

agent.createConnection = function (options) {
  return connect(options);
};

const ws = new WebSocket('wss://ws.ifelse.io', { agent });

ws.on('open', function () {
  console.log('open');
});

@connor4312
Copy link
Author

Ah, cool, I didn't realize that that was also an option on the agent. I'll try that out and close this issue if it works.

@connor4312
Copy link
Author

It turns out that it's not enough to just return the stream, it also needs to be wrapped to look at least a bit like a network socket. Here's the conversion function, with TypeScript typings, in case anyone else stumbles upon the need for this:

import { Socket } from 'net';
import { Duplex } from 'stream';

export const makeNetSocketFromDuplexStream = (stream: Duplex) => {
	const cast = stream as Socket;
	const patched: { [K in keyof Omit<Socket, keyof Duplex>]: Socket[K] } = {
		bufferSize: 0,
		bytesRead: 0,
		bytesWritten: 0,
		connecting: false,
		localAddress: '127.0.0.1',
		localPort: 1,
		remoteAddress: '127.0.0.1',
		remoteFamily: 'tcp',
		remotePort: 1,
		address: () => ({ address: '127.0.0.1', family: 'tcp', port: 1 }),
		unref: () => cast,
		ref: () => cast,
		connect: (_port: unknown, _host?: unknown, connectionListener?: () => void) => {
			if (connectionListener) {
				setImmediate(connectionListener);
			}
			return cast;
		},
		setKeepAlive: () => cast,
		setNoDelay: () => cast,
		setTimeout: (_timeout: number, callback?: () => void) => {
			callback?.();
			return cast;
		},
	};

	return Object.assign(stream, patched) as Socket;
};

@lpinca
Copy link
Member

lpinca commented Sep 11, 2021

If the opening handshake can be skipped, then a generic duplex stream can be used by calling the "private" WebSocket#setSocket() method.

function noop() {}

const duplex = new Duplex({
  read() {},
  write() {}
});

duplex.setTimeout = noop;
duplex.setNoDelay = noop;

const ws = new WebSocket(null);

ws.setSocket(duplex, Buffer.alloc(0));

// ...

Anyway this is not recommended as it makes use of a private API.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants