boardgameio / boardgame.io

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

Issues while deploying TicTacToe tutorial on Heroku #815

Closed KillianKemps closed 4 years ago

KillianKemps commented 4 years ago

Hello,

I read previous issues #571 #319 and also the Heroku deployment guide, but I still have issues to make the TicTacToe work on Heroku.

I'm able to run npm run serve and to play TicTacToe locally on http://localhost:8000

However, on Heroku when I click on a cell I get this error in the console: Uncaught TypeError: can't access property "currentPlayer", r.ctx is undefined.

Also, several minutes later I get these errors in the console: Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at https://boardgameio-tictactoe.herokuapp.com:8000/socket.io/?EIO=3&transport=polling&t=NJ-DnWk. (Reason: CORS request did not succeed).

Here is the Procfile for Heroku:

web: node -r esm src/server.js

Here is the entire server.js file:

import { Server } from 'boardgame.io/server';
import path from 'path';
import serve from 'koa-static';
import { TicTacToe } from './Game';

const server = Server({ games: [TicTacToe] });
const PORT = process.env.PORT || 8000;

// Build path relative to the server.js file
const frontEndAppBuildPath = path.resolve(__dirname, '../dist');
server.app.use(serve(frontEndAppBuildPath))

server.run(PORT, () => {
  server.app.use(
    async (ctx, next) => await serve(frontEndAppBuildPath)(
      Object.assign(ctx, { path: 'index.html' }),
      next
    )
  )
});

And here the beginning of App.js:

import { Client } from 'boardgame.io/client';
import { SocketIO } from 'boardgame.io/multiplayer';
import { TicTacToe } from './Game';

class TicTacToeClient {
  constructor(rootElement, { playerID } = {}) {
    this.client = Client({
      game: TicTacToe,
      multiplayer: SocketIO({ server: `https://${process.env.HOST || 'localhost'}:${process.env.PORT || 8000}` }),
      playerID
    });
    this.client.start();
    this.rootElement = rootElement;
    this.createBoard();
    this.attachListeners();
    this.client.subscribe(state => this.update(state));
  }

Can you tell what may be missing?

delucis commented 4 years ago

@KillianKemps I’ve posted to our Gitter chat, to see if there are any Heroku users who may be able to help.

Perhaps @arome @nagyadam2092 who wrote the deployment guide might have some ideas?

nagyadam2092 commented 4 years ago

hey @delucis ! Did the API change? I haven't had a look at boardgame.io in a while, and I don't remember this.client.start(); calls - maybe that's the issue?

delucis commented 4 years ago

Thanks @nagyadam2092! This is using the plain JavaScript client rather than the React one, so that’s where the start() method comes from. The client code looks ok (and works locally), so I wonder if it’s something to do with the server config. That CORS error seems to happen quite often when a connection to the game server fails (not really related to CORS which is confusing.)

@KillianKemps How do you build the client on Heroku? Using something like parcel build index.html?

KillianKemps commented 4 years ago

@delucis Yes, here is the scripts section of package.json:

  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "parcel index.html --open",
    "serve": "node -r esm src/server.js",
    "build": "parcel build index.html"
  },

You can also directly test my deployment on https://boardgameio-tictactoe.herokuapp.com/

I also precise that I have set the environment variables on Heroku, in case it could be the source of the CORS error:

HOST=boardgameio-tictactoe.herokuapp.com
PORT=8000
delucis commented 4 years ago

If I try to connect to the server directly the request times out (e.g. via a socket URL or to access the lobby API), so I’d guess that’s the source of the CORS error (here are the error details on MDN).

According to the deployment docs, Heroku can use the start script in the package.json to run your application. Is it possible the start script that runs parcel is being preferred to the Procfile? Are there some logs you can check to confirm which script is running?

KillianKemps commented 4 years ago

I finally made it work!

I first had the same doubt if Heroku used the right command, but it should use the Procfile in priority when it is present. I can confirm the right command is being used because I can see this in the logs when I access the app:

2020-10-07T09:53:16.724381+00:00 heroku[web.1]: Starting process with command `node -r esm src/server.js`
2020-10-07T09:53:21.023310+00:00 heroku[web.1]: State changed from starting to up

Concerning the CORS error, it looks like it may be a closed port issue. I have manually registered the PORT environment variable to 8000, but Heroku has to manage it itself.

I have now unregistered the HOST and PORT variables from Heroku. I also removed the .env.production file I had for Parcel in which I first had put this:

HOST=boardgameio-tictactoe.herokuapp.com
PORT=$PORT

After have removed the environment variables, I changed this in App.js:

-      multiplayer: SocketIO({ server: `https://${process.env.HOST || 'localhost'}:${process.env.PORT || 8000}` }),
+      multiplayer: SocketIO({ server: `https://${window.location.hostname}` }),

Now, the socket.io requests work by directly accessing https://boardgameio-tictactoe.herokuapp.com/ without having to specify the port number.

Coming back to the Heroku deployment guide, as it was specified for the Lobby to use https://${window.location.hostname}, I think it would be nice to add that we need to configure the same thing in App.js for the client constructor.

I copy here again my files in order to help others:

server.js:

import { Server } from 'boardgame.io/server';
import path from 'path';
import serve from 'koa-static';
import { TicTacToe } from './Game';

const server = Server({ games: [TicTacToe] });
const PORT = process.env.PORT || 8000;

// Build path relative to the server.js file
const frontEndAppBuildPath = path.resolve(__dirname, '../dist');
server.app.use(serve(frontEndAppBuildPath))

server.run(PORT, () => {
  server.app.use(
    async (ctx, next) => await serve(frontEndAppBuildPath)(
      Object.assign(ctx, { path: 'index.html' }),
      next
    )
  )
});

Beginning of App.js:

import { SocketIO } from 'boardgame.io/multiplayer';
import { TicTacToe } from './Game';

class TicTacToeClient {
  constructor(rootElement, { playerID } = {}) {
    this.client = Client({
      game: TicTacToe,
      multiplayer: SocketIO({ server: `https://${window.location.hostname}` }),
      playerID
    });
    this.client.start();
    this.rootElement = rootElement;
    this.createBoard();
    this.attachListeners();
    this.client.subscribe(state => this.update(state));
  }

Thanks for helping!

KillianKemps commented 4 years ago

Note: in fact my solution is not suitable for local developing anymore. As the port is not specified, Socket.io can't connect to the local server like localhost:8000.

So, here is the right App.js to make the connection work locally and on the server without using environment variables:

import { Client } from 'boardgame.io/client';
import { SocketIO } from 'boardgame.io/multiplayer';
import { TicTacToe } from './Game';

class TicTacToeClient {
  constructor(rootElement, { playerID } = {}) {
    this.client = Client({
      game: TicTacToe,
      multiplayer: SocketIO({ server: `${window.location.protocol}//${window.location.hostname}:${window.location.port}` }),
      playerID
    });
    this.client.start();
    this.rootElement = rootElement;
    this.createBoard();
    this.attachListeners();
    this.client.subscribe(state => this.update(state));
  }