npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

pair-relayer

v1.0.0

Published

Node.js server that listens for incoming connections and forwards data between them

Downloads

1

Readme

pair-relayer

Simple (and insecure) way of relaying data between two machines connected to this server.

This server will admit multiple connections but will get pair of sockets and pipe the data from each other.

The code for this server has been completely generated by ChatGPT3.5 and it's prompts are registered in this readme file.

Motivations

I just wanted to play Wacky Wheels. DoxBOX, the emulator used, emulates also the old serial connection into TCP/IP connection, without any type of protocol, just raw data going through raw (insecure) internet pipes.

In order to play, I needed a relay server that connects two machines and start forwarding each other data. At the same time, we are living in the boom of AI generative tools and, despite of knowing what to do, I asked ChatGPT 3.5 how to do it.

So, this is the result of an AI helping me to get the software that I wanted, and the steps that I did to make it happen.

The prompts

Step 1: The main code

I asked to generate the server, I wanted to be sure the server can support more than 2 connections (we are a bunch of friends, and want to do a championship :)), here is the prompt:

Write a nodejs program that listens in port 3000 for incoming connections.

It will group connections in pairs, forwarding each other data and keep a list of open pairs of sockets.

And the answer:

const net = require('net');

const socketPairs = [];

const server = net.createServer((socket) => {
  if (socketPairs.length % 2 === 0) {
    // Create a new socket pair
    const newPair = [socket];
    socketPairs.push(newPair);
  } else {
    // Forward data between the last pair and the new socket
    const lastPair = socketPairs[socketPairs.length - 1];
    lastPair.push(socket);
    forwardData(lastPair);
  }

  // Handle socket events
  socket.on('data', (data) => {
    const pair = getPair(socket);
    const otherSocket = getOtherSocket(pair, socket);
    otherSocket.write(data);
  });

  socket.on('end', () => {
    const pair = getPair(socket);
    if (pair) {
      // Remove the socket pair
      socketPairs.splice(socketPairs.indexOf(pair), 1);
    }
  });

  socket.on('error', (err) => {
    console.error(`Socket error: ${err}`);
  });
});

function getPair(socket) {
  return socketPairs.find((pair) => pair.includes(socket));
}

function getOtherSocket(pair, socket) {
  return pair.find((s) => s !== socket);
}

function forwardData(pair) {
  const [socket1, socket2] = pair;
  socket1.pipe(socket2);
  socket2.pipe(socket1);
}

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});

Almost spot on! On the first try! Or at least that's what it looks like.

Step 2: Refine

I observed that on the end event we are just removing from memory the pair os sockets and I asked to be sure we disconnect both clients before removing from memory.

Here is prompt:

The program seems correct, to ensure that is more accurate, it does remove the socket pair when one of the ends finish the connection, can you ensure that you close the connection on the other socket before removing from memory?

And here is the answer:

const net = require('net');

const socketPairs = [];

const server = net.createServer((socket) => {
  if (socketPairs.length % 2 === 0) {
    // Create a new socket pair
    const newPair = [socket];
    socketPairs.push(newPair);
  } else {
    // Forward data between the last pair and the new socket
    const lastPair = socketPairs[socketPairs.length - 1];
    lastPair.push(socket);
    forwardData(lastPair);
  }

  // Handle socket events
  socket.on('data', (data) => {
    const pair = getPair(socket);
    const otherSocket = getOtherSocket(pair, socket);
    otherSocket.write(data);
  });

  socket.on('end', () => {
    const pair = getPair(socket);
    if (pair) {
      // Close the other socket before removing the pair
      const otherSocket = getOtherSocket(pair, socket);
      otherSocket.end();
      // Remove the socket pair
      socketPairs.splice(socketPairs.indexOf(pair), 1);
    }
  });

  socket.on('error', (err) => {
    console.error(`Socket error: ${err}`);
  });
});

function getPair(socket) {
  return socketPairs.find((pair) => pair.includes(socket));
}

function getOtherSocket(pair, socket) {
  return pair.find((s) => s !== socket);
}

function forwardData(pair) {
  const [socket1, socket2] = pair;
  socket1.pipe(socket2);
  socket2.pipe(socket1);
}

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});

Step 3: Checking that we are doing the right things

Now I decided to question certain decisions to understand that our server does wht I want:

Can we enter in a loop because of the 'end' event in both sockets?

With the following correct answer:

No, we won't enter into a loop because of the end event being emitted on both sockets. The reason is that when one of the sockets emits the end event, the corresponding pair is removed from the socketPairs array and the other socket is closed using the end method. This means that when the other socket emits the end event, it will not find the corresponding pair in the socketPairs array and nothing will happen.

To clarify, in this implementation, the end event is only used to close the socket and remove the corresponding pair from the socketPairs array. It is not used for any other purpose that would cause a loop or other unexpected behavior.

During my code review and testing of the solution found 3 bugs:

  • Deciding when do we have a new pair fails since it checks by length %2, so when we get to the 1 pair, we always hit the same else code branch.
  • We pipe the sockets and added the data event, ending with duplicate information in both ends.
  • During the end event, the otherSocket variable might be null while disconnecting, producing a fatal error while calling a method in a null object.

Here are the 3 prompts that I did to fix them:

The code, once you create the first pair of sockets, it nevers create more pairs as the socketsPair length'%' operation will be always 1.

As we are forwarding sockets, we don't need to listen to the 'data' event on sockets.

When you are handling a socket closing on the 'end' event, check that otherSocket is null before trying to execute the 'end' method

And the final version of the code, that is the initial commit in this repo:

const net = require('net');

const socketPairs = [];

const server = net.createServer((socket) => {
  if (socketPairs.length === 0 || socketPairs[socketPairs.length - 1].length === 2) {
    // Create a new socket pair
    const newPair = [socket];
    socketPairs.push(newPair);
  } else {
    // Forward data between the last pair and the new socket
    const lastPair = socketPairs[socketPairs.length - 1];
    lastPair.push(socket);
    forwardData(lastPair);
  }

  socket.on('end', () => {
    const pair = getPair(socket);
    if (pair) {
      // Close the other socket before removing the pair
      const otherSocket = getOtherSocket(pair, socket);
      if (otherSocket) {
        otherSocket.end();
      }
      // Remove the socket pair
      socketPairs.splice(socketPairs.indexOf(pair), 1);
    }
  });

  socket.on('error', (err) => {
    console.error(`Socket error: ${err}`);
  });
});

function getPair(socket) {
  return socketPairs.find((pair) => pair.includes(socket));
}

function getOtherSocket(pair, socket) {
  return pair.find((s) => s !== socket);
}

function forwardData(pair) {
  const [socket1, socket2] = pair;
  socket1.pipe(socket2);
  socket2.pipe(socket1);
}

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});

Conclusions

  • It was fun to use.
  • It definitely saved me 80% of the time, in a task that I knew how to do.
  • It failed in their first intent.
  • I had to do a code review (and testing) to provide instructions to fix the problems.
  • Those intructions provided a valid solution.

From now on, I'll continue changing this code through prompts, that will be added to the different commit messages.