boardgameio / boardgame.io

State Management and Multiplayer Networking for Turn-Based Games
https://boardgame.io
MIT License
10.03k stars 709 forks source link

Koa example in Typescript - cant modify stream #1143

Open webrunner42 opened 1 year ago

webrunner42 commented 1 year ago

I'm a trading card game, and looking for a way to set up the decks at the beginning of the match. The docs have an example :

const server = Server({ /* options */ });

// Add middleware to the create game route.
server.router.use('/games/:name/create', async (ctx, next) => {
  // Decide number of players etc. based on some other API.
  const { numPlayers, setupData } = await fetchDataFromSomeCustomAPI();
  // Set request body to be used by the create game route.
  ctx.request.body.numPlayers = numPlayers;
  ctx.request.body.setupData = setupData;
  next();
});

server.run(8000);

but I'm not sure if something has changed since here but using koa, the body isn't parsed yet when we get to this point, so ctx.request.body doesnt exist. If we try to parse the body here (using koaBody() or something) then later, since the body is already parsed, the boardgame.io game creation ui breaks because the stream is already closed and no longer readable.

I've tried something like this:

ctx.request.body = {}
ctx.request.body.setupData = {decks:decks}
next();

but it gets clobbered by koabody.

And obviously I cant make the changes -after- the game creation api does it's thing because the game constructor is already called.

Is there a work around? making the changes and writing 'back' a new stream to the context? A setting that makes bgio use the previously parsed body and skip using koaBody?

webrunner42 commented 1 year ago

For people in the future3 who have this problem:

 let chunk;
    while (null !== (chunk = ctx.req.read())) {
      // get rid of the existing things
    }
    const toUnshift = JSON.stringify(bodyJson);
    ctx.req.unshift(toUnshift);
    ctx.req.headers["content-length"] = toUnshift.length.toString();

this will clobber the existing body and replace it with your own

kevinddchen commented 1 year ago

Your specific use-case may be handled by the setup() callback: https://boardgame.io/documentation/#/api/Game

However, I also want to add middleware to modify the server behavior, e.g. invalidate player names upon joining a match if another player has that same name. #1107 is another issue on the same topic.

kevinddchen commented 1 year ago

@webrunner42 Thank you very much for your snippet! 🎉 I was able to arrive at a good workaround. My use-case was pretty simple, since I just needed to intercept the body and then pass it without any changes. For reference, this is what I used:

async (ctx, next) => { 

    const chunks = [];
    let chunk;
    while (null !== (chunk = ctx.req.read())) {
      chunks.push(chunk);
    }

    const rawBody = Buffer.concat(chunks).toString('utf8');
    const body = JSON.parse(rawBody);

    // do stuff

    ctx.req.unshift(rawBody);
    ctx.req.headers["content-length"] = rawBody.length.toString();
    await next();
  });

It seems that a more permanent fix may be added to the koa library: https://github.com/koajs/bodyparser/issues/127.