Writing a Modern MUSH Server with Typescript Part 2: The Input Parser
Tags: typescript, tutorial, mush, node
Published: 2020-03-12 15:39:27

In [Part 1](https://dev.to/lcanady/writing-a-modern-mush-like-server-with-typescript-socket-io-and-express-part-1-4f8o) of the tutorial series, we went over some basic setup for the project. We also addressed some of the overall goals for the **UrsaMU** project. 

## The Parser
First, we're going to setup the parser that will handle input from a socket, then we'll define the socket server and accompanying support code!

First, we need to define a couple of interfaces to act as contracts from what we expect the shape of our data to look like.
```JavaScript
import { Socket } from "socket.io";
import { Marked } from "@ts-stack/markdown";
import text from "../api/text";

export type MiddlewareNext = (
  err: Error | null,
  req: MuRequest
) => Promise<any>;

export type MiddlewareLayer = (
  data: MuRequest,
  next: MiddlewareNext
) => Promise<MuResponse>;

export interface MuRequest {
  socket: Socket;
  payload: {
    command: string;
    message?: string;
    [key: string]: any;
  };
}
export interface MuResponse {
  id: string;
  payload: {
    command: string;
    message?: string;
    [key: string]: any;
  };
}
```
Now we define the `Parser` class itself.  Like `MU`, this is meant to be a `singleton`, needed only once through the life of the process.
```JavaScript
export class Parser {
  private stack: MiddlewareLayer[];
  private static instance: Parser;

  private constructor() {
    this.stack = [];
  }

  static getInstance(){
    if(!this.instance) this.instance = new Parser();
    return this.instance;
  }
```
This is the method that we're going to call to handle input from the sockets.
```JavaScript
  async process(req: MuRequest): Promise<MuResponse> {
    const command = req.payload.command;
    const socket = req.socket;
    const message = req.payload.message;
    const data = req.payload.data;

    switch (command) {
      case "message":
        return this._handle(req);
      case "connect":
        return {
          id: socket.id,
          payload: {
            command: "message",
            message: text.get("connect")
              ? Marked.parse(text.get("connect"))
              : "File Not Found!"
          }
        };
      default:
        return {
          id: socket.id,
          payload: {
            command: "message",
            message
          }
        };
    }
  }
```
Here is where I've started to define the middleware system that will handle piping an input string through a series of transforms before returning the results to the client.  It will use a `next()` function, like one would expect to see in `ExpressJS`. :)
```JavaScript
  /**
   * Add a new middleware to the stack.
   * @param layer The layer to add to the middleware stack.
   */
  use(layer: MiddlewareLayer) {
    this.stack.push(layer);
  }

  /**
   * Run a string through a series of middleware.
   * @param req The request object to be pushed through the pipeline.
   */
  private async _handle(req: MuRequest): Promise<MuResponse> {
    let idx = 0;
```
Here's where it gets a little fun! We're going to make a recursive function expression. Every piece of software that goes through the middleware system will have to make sure to individually call `next()`, or the server will hang!
```JavaScript
    /**
     * Recursive function to walk through each piece of
     * middleware in the stack.
     * @param err Any possible errors hit by the middleware.
     * @param data The string the middleware pipeline is going
     * to be working with
     */
    const next = async (
      err: Error | null,
      req: MuRequest
    ): Promise<MuResponse> => {
      // Return early if there's an error, or if we've processed through
      // the entire stack.
      if (err != null) return Promise.reject(err);
      if (idx === this.stack.length) {
        return {
          id: req.socket.id,
          payload: req.payload
        };
      }

      // Grab a new layer from the stack
      const layer = this.stack[idx++];
      // Run the layer
      return await layer(req, next);
    };

    // Return the modified data.
    return await next(null, req).catch((err: Error) => next(err, req));
  }
}

export default Parser.getInstance();

```
## Updating MU
Now we're going to update `mu.ts` to handle the server traffic from the sockets and routing them to our parser system.  We're going to add the following to the `startup()` method.
```JavaScript
async start(callback?: () => void) {
    // Handle new client connections.
    this.io?.on("connection", async (socket: Socket) => {
      const { id, payload }: MuResponse = await parser.process({
        socket: socket,
        payload: {
          command: "connect"
        }
      });

      // Send the results back to the client.
      this.io?.to(id).send(payload);

      // When a new message comes from the client, process
      // it and return the results.
      socket.on("message", async (message: string) => {
        const { id, payload }: MuResponse = await parser.process({
          socket,
          payload: { command: "message", message }
        });

        // Send the results back to the client after converting
        // any markdown.
        if (payload.message) payload.message = Marked.parse(payload.message);
        this.io?.to(id).send(payload);
      });
    });
```
Well, that's it for today! We've made our middleware system, and handled our socket.io connections and client activity! Perfect! In the next installment, we'll create a piece of middleware to handle commands, define a command or two!

Thanks for stopping in for a read! please feel free to leave a comment, ask a question or just discuss!