Skip to content

Commit

Permalink
src: add .opened promise (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
flakey5 authored Nov 6, 2023
1 parent b7f050e commit a88c734
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 8 deletions.
45 changes: 40 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import net from 'node:net';
import tls from 'node:tls';
import { Duplex } from 'node:stream';
import type { ReadableStream, WritableStream } from 'node:stream/web';
import type { SocketAddress, SocketOptions } from './types';
import type { SocketAddress, SocketInfo, SocketOptions } from './types';
import { isSocketAddress } from './is-socket-address';

export function connect(
Expand All @@ -23,11 +23,15 @@ export function connect(
export class Socket {
readable: ReadableStream<unknown>;
writable: WritableStream<unknown>;
opened: Promise<SocketInfo>;
closed: Promise<void>;

private socket: net.Socket | tls.TLSSocket;
private allowHalfOpen: boolean;
private secureTransport: SocketOptions['secureTransport'];
private openedIsResolved: boolean;
private openedResolve!: (info: SocketInfo) => void;
private openedReject!: (reason?: unknown) => void;
private closedResolve!: () => void;
private closedReject!: (reason?: unknown) => void;
private startTlsCalled = false;
Expand All @@ -39,6 +43,19 @@ export class Socket {
this.secureTransport = options?.secureTransport ?? 'off';
this.allowHalfOpen = options?.allowHalfOpen ?? true;

this.openedIsResolved = false;
this.opened = new Promise((resolve, reject) => {
this.openedResolve = (info): void => {
this.openedIsResolved = true;
resolve(info);
};
this.openedReject = (...args): void => {
this.openedIsResolved = true;
// eslint-disable-next-line prefer-promise-reject-errors -- ESLint gets this wrong as we are completely forwarding the arguments to reject.
reject(...args);
};
});

this.closed = new Promise((resolve, reject) => {
this.closedResolve = (...args): void => {
resolve(...args);
Expand All @@ -64,18 +81,36 @@ export class Socket {
this.socket = new tls.TLSSocket(addressOrSocket);
}

if (this.socket instanceof tls.TLSSocket) {
this.socket.on('secureConnect', () => {
this.openedResolve({
remoteAddress: this.socket.remoteAddress,
localAddress: this.socket.localAddress,
});
});
} else {
this.socket.on('connect', () => {
this.openedResolve({
remoteAddress: this.socket.remoteAddress,
localAddress: this.socket.localAddress,
});
});
}

this.socket.on('close', (hadError) => {
if (!hadError) {
this.closedResolve();
}
});

this.socket.on('error', (err) => {
if (err instanceof Error) {
this.closedReject(new SocketError(err.message));
} else {
this.closedReject(new SocketError(err as string));
const socketError = new SocketError(
err instanceof Error ? err.message : (err as string),
);
if (!this.openedIsResolved) {
this.openedReject(socketError);
}
this.closedReject(socketError);
});

// types are wrong. fixed based on docs https://nodejs.org/dist/latest/docs/api/stream.html#streamduplextowebstreamduplex
Expand Down
5 changes: 5 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ export interface SocketAddress {
/** The port number to connect to. Example: `5432`. */
port: number;
}

export interface SocketInfo {
remoteAddress?: string;
localAddress?: string;
}
10 changes: 7 additions & 3 deletions test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
void tap.test(
'Socket connected to tcp server with secureTransport: off',
async (t) => {
t.plan(6);
t.plan(7);
let connectCount = 0;
const message = 'abcde\r\n';

Expand All @@ -38,6 +38,8 @@ void tap.test(
'should pipe message',
);

await t.resolveMatch(socket.opened, { localAddress: '::1' });

const close = socket.close();
t.equal(
socket.closed,
Expand Down Expand Up @@ -137,13 +139,15 @@ for (const data of [
}

void tap.test('SocketError is thrown on connect failure', async (t) => {
t.plan(1);
t.plan(2);

const expectedError = new SocketError('connect ECONNREFUSED 127.0.0.1:1234');
try {
const socket = connect('127.0.0.1:1234');
socket.opened.catch((err) => t.same(err, expectedError));
await socket.closed;
} catch (err) {
t.same(err, new SocketError('connect ECONNREFUSED 127.0.0.1:1234'));
t.same(err, expectedError);
} finally {
t.end();
}
Expand Down

0 comments on commit a88c734

Please sign in to comment.