Express.JS server for hosting custom PopSauce-like games.
This server requires you to have an AWS account and that you have an access key to your account.
To avoid flood, the API only authorizes a certain number of requests on resource-consuming endpoints.
There are two limiters:
postLimiter
: The requests limit to all endpoints protected by this limiter (shared) is set to 60 requests by hour (default)randomLimiter
: The requests limit to all endpoints protected by this limiter (shared) is set to 3600 requests by hour (default)
Get a HTML testing page for feeding data to the database
Add a new quote sauce to the database
This endpoint is protected with the postLimiter
requests limiter.
The request body must contain the following fields:
quote
(string): The quote to storeanswer
(string): The awaited answer to the sauce
Add a new image sauce to the database
This endpoint is protected with the postLimiter
requests limiter.
The request body must contain the following fields:
image
(file): The image to store (MIME type must be either "image/jpeg" or "image/png")answer
(string): The awaited answer to the sauce
This endpoint is only able to parse requests sent with Content-Type: multipart/form-data
.
By default, this endpoint will only accept files up to 15 MB. This threshold is configurable with the
maximumImageSizeAllowed
variable of server.config.js
.
Get a random sauce (image or quote) from the database
This endpoint is protected with the randomLimiter
requests limiter.
The response will be a JSON object, its structure depends on whether you have retrieved a quote or an image.
If you are retrieving a quote, the structure will be as follow:
{
"type": "quote",
"quote": "Content of the random quote sauce",
"answer": "Answer to that random quote sauce"
}
If you are retrieving an image, the structure will be as follow:
{
"type": "image",
"imageUrl": "https://secure-link-to-the-image/",
"answer": "Answer to that random image sauce"
}
Get a random quote sauce from the database
This endpoint is protected with the randomLimiter
requests limiter.
This endpoint produces a JSON object with the structure as follow:
{
"type": "quote",
"quote": "Content of the random quote sauce",
"answer": "Answer to that random quote sauce"
}
Get a random image sauce from the database
This endpoint is protected with the randomLimiter
requests limiter.
This endpoint produces a JSON object with the structure as follow:
{
"type": "image",
"imageUrl": "https://secure-link-to-the-image/",
"answer": "Answer to that random image sauce"
}
The server uses socket.io for handling sockets and real-time data.
To use it client-side, you can retrieve the JS code exposed at /socket.io/socket.io.js
.
The available custom actions that the server will understand are the following:
Set your username for the session.
The server includes a set of restricted usernames, by default they are the following:
me
system
root
hitler
You must provide a username in the username
field of the parameter (object).
IMPORTANT: For many events, it is required that you set a username.
The server will respond with the following events:
username_set
: Means that your username has been set. This event carries the set username as a string parameter.username_not_available
: Means that your username has not been set because it is not available. This event carries the unavailable username as a string parameter.forbidden_username
: Means that the username you have chosen is forbidden and cannot be used. This event carries the forbidden username as a string parameter.
Create a game room. Will produce an error if the room already exists.
You must have set a valid username before sending this event. Otherwise it will be denied.
This action takes an object as a parameter, it must have the following properties set:
roomName
(string): Name of the room to create
When performed, this action produces a response in the form of a socket event that you should listen to.
create_room_error
Indicates that the room could not be created, this response comes with an object parameter that will give more details about the failure through itserror
fieldcreate_room_success
Indicates that the room has been created and that you have joined it, this response comes with an object parameter that will give you the name of the room with theroomName
field and whether a game has started in that room with thestarted
field.
Join an existing game room. Will produce an error if the room does not exist.
You must have set a valid username before sending this event. Otherwise it will be denied.
This action takes an object as a parameter, it must have the following properties set:
roomName
(string): Name of the room to join
When performed, this action produces a response in the form of a socket event that you should listen to.
join_room_error
Indicates that the room could not be joined, this response comes with an object parameter that will give more details about the failure through itserror
fieldjoin_room_success
Indicates that the room has been created and that you have joined it, this response comes with an object parameter that will give you the name of the room with theroomName
field and whether a game has started in that room with thestarted
field.
Leave an existing game room. Will produce an error if the room does not exist or if you have not joined it.
This action takes an object as a parameter, it must have the following properties set:
roomName
(string): Name of the room to leave
When performed, this action produces a response in the form of a socket event that you should listen to.
leave_room_error
Indicates that the room could not be left, this response comes with an object parameter that will give more details about the failure through itserror
fieldleave_room_success
Indicates that the room has been left, this response comes with an object parameter that will give you the name of the room
Start the game in the game room. For that, you must have joined a game room.
You must have set a valid username before sending this event. Otherwise it will be denied.
This action takes an object as a parameter, it must have the following properties set:
roomName
(string): Name of the room to start the game in
When performed, this action produces a response in the form of a socket event that you should listen to.
start_game_error
Indicates that the room could not be created, this response comes with an object parameter that will give more details about the failure through itserror
fieldstart_game_success
Indicates that the room has been created and that you have joined it, this response comes with an object parameter that will give you the name of the room
Submit an answer for the current sauce.
The server will only listen for this event in the timespan of a round.
This action takes a string as parameter, it must contain the submitted answer.
The server will respond with events:
wrong_answer
: If the answer is incorrectgood_answer
: If the answer is correctanswer_is_close
: If the answer is close to the correct answer
Send a chat message in the game room you are playing.
This action takes only a string as parameter, it must contain the content of the message.
Report the sauce currently being displayed as being wrong or inacceptable.
This action does not take any parameter.
The server will respond with the report_received
event when it has received and processed the report.
This event does not carry any additional data.
In addition to the socketing responses described for the actions above, the server will send socket events depending on other players activity or game status.
They are described here.
The server sends this event whenever a room is added or deleted.
It contains the list of room names as an array in the roomNames
field of its parameter.
The server sends this event whenever there is no sauce available.
It happens when a game is started but the database does not contain a single record.
The server sends this event to all players in a room when a game is started in the room the players are in.
This event does not carry any data.
The server sends this event when the game in your room ended before a player won.
This event does not carry any data.
The server sends this event at each round start. Its parameter contains the sauce information as a json object:
- In case of a quote
{
"type": "quote",
"quote": "Lorem ipsum dolor sit amet", // Content of the quote
"id": "zretsrvsfv342Adfgt" // ID of the quote
}
- In case of an image
{
"type": "image",
"imageUrl": "https://link-to-the-image.jpg", // Link to the image
"id": "zretsrvsfv342Adfgt" // ID of the image
}
The server sends this event at each time a player in your room updates its score (usually because he found the answer).
Its parameter contains the following:
{
scoreboard: [
{
"player": "johndoe", // Username of the player to update
"found": true, // Boolean indicating if player found answer (should be true most of the time)
"score": 42 // Score of the user
}
]
}
This event name's is self explanatory: the server sends it at each round end.
The server sends this event at each round end, it contains the right answer to the sauce.
WARNING: this event will soon be merged with the round_end
event.
This event is sent by the server to all the players in a room when the remaining time to guess the sauce has changed (every second, then).
It only contains a number representing the remaining time in seconds.
The server sends this event when a player won the game.
Its parameter contaains the following:
{
"username": "johndoe", // Username of winner
"score": 101 // Score of winner
}
The server sends this event to all users in a room when a user of this room sent a message in the chat.
Its data is formatted as follow:
{
"username": "johndoe", // Username of the user who sent the message
"message": "Hello, world!" // Content of the message sent by the user
}
Whenever an error occurs, you should aways be able to retrieve a JSON object describing the nature of the error or your problem.
A quick way to understand is to look at the message
field of this JSON object, it should contain
a human-readable description of the exact problem.
- Create an AWS account
This is pretty straightforward
- Get an IAM access key to your AWS account
This can be done in the IAM section of your AWS Console.
Please keep the access key ID and secret because the server needs them to connect to a S3 Bucket.
- Create a S3 Bucket and setup the following rules
Once again, this is pretty straightforward, so do not forget to untick the "Block Public Access" box.
Because you only want the server to be able to directly write files into your bucket, please set the following strategy for your bucket:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicRead",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::YOUR-BUCKET-NAME-HERE/*"
}
]
}
This strategy will allow any IP to HTTP GET files in your bucket, so be careful what you put into it.
- Configure the environment variables
This server heavily relies on environment variables, so you should place the following
variables into a .env
file at the root of the server.
EXPRESS_LISTENING_PORT
: Port on which you want the server to listen for requests (default is4242
)AWS_REGION_CODE
: Region code in which to use AWS (examples: eu-west-2, us-east-1, ...)AWS_S3_BUCKET_NAME
: AWS S3 Bucket name where to store the data retrieved by the serverAWS_ACCESS_KEY_ID
: ID of the access key that the server should use to access your S3 BucketAWS_SECRET_ACCESS_KEY
: Secret of the access key to required to access the S3 BucketMONGO_INITDB_ROOT_USERNAME
: Username to use for accessing your MongoDB database (does not have to be root)MONGO_INITDB_ROOT_PASSWORD
: Password of the user to use for accessing the MongoDB database- (Optional)
CONTAINERIZED
:(true|false)
Whether you are running your application in a container (LXC/Docker)
- Ensure the database connection string is correct
This information can be found in the file src/server.config.js
under the mongoConnectionString
property.
By default, the connection string allows to work in a local environment with the a local instance of MongoDB (better for debugging) rather than setting up containers already.
Please ensure the database connection string matches the one you want to use : If you use a sub database in your MongoDB instance, you have to specify it here as the application does not handle such cases yet