Multiplayer status fetching, what am I doing wrong?

I was messing around with the network tab in my browser on the multiplayer game room page, trying to make a working request. My goal was to take a game room code (eg. 535FE6) and return its status (200 if the room exists, or 404 if it doesn’t).

After some fiddling around, I found out that MakeCode sends a request to this URL

mp.makecode.com/api/game/join/{room code}

with some headers to check if the room exists. If the room doesn’t exist, it returns an empty response with a statuscode of 404, and if it exists it returns a join key with a code of 200. I thought I would be able to easily use this for my needs, and I was partially right.

When I copied the request as a fetch method and pasted it into the chrome console, it worked flawlessly. Same thing when I attempted to access it externally. I copied all the headers into Insomnia and sent a GET request which returned one of the expected responses.

However, after attempting to use the fetch method in a JS script for a website locally, it kept returning the same ‘405 Method not allowed’ status code, along with this chrome console error

Access to fetch at ‘https://mp.makecode.com/api/game/join/535FE6’ from origin ‘http://127.0.0.1:5500’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.

This is the code I’m using

async function checkCodeValidity(code) {
    let response = await fetch(`https://mp.makecode.com/api/game/join/${code}`, {
        "headers": {
        "accept": "*/*",
        "accept-language": "en,hr;q=0.9,bs;q=0.8",
        "authorization": "mkcd {Joey just editted this to cut this out because he hates seeing tokens, should be fine with session cookie but never paste anything like this openly pls}",
        "sec-ch-ua": "\"Not/A)Brand\";v=\"99\", \"Google Chrome\";v=\"115\", \"Chromium\";v=\"115\"",
        "sec-ch-ua-mobile": "?0",
        "sec-ch-ua-platform": "\"Windows\"",
        "sec-fetch-dest": "empty",
        "sec-fetch-mode": "cors",
        "sec-fetch-site": "same-site",
    },
    "referrer": "https://arcade.makecode.com/",
    "referrerPolicy": "strict-origin-when-cross-origin",
    "body": null,
    "method": "GET",
    "mode": "cors",
    "credentials": "include"
    })
    .then((response) => alert(response.status));
}

I tried changing the headers, adding new ones, changing the method to no-cors and it made no difference whatsoever.

Did I miss something in my code or request? If so, could someone help me out with this, and if that’s not the case, maybe I’m approaching this all wrong. Is there a better way to achieve what I’m trying to do @jwunderl? Thanks in advance

4 Likes

Yummy CORS prevent a random site from sending a request to a different domain. For example, this prevents someone from hacking a site to DDoS another site in the browser - since website A can’t request website B - there is no DDoS.

I would try making a server that serves an API that does the request itself. (basically a proxy)

I would also assume that mp.makecode.com/api/ is intended to be private and therefore could change at any time, which could break your code at any time.

3 Likes

@Sarge I went ahead and edited your post as you included an auth token, should still be safe in this case as auth gets split into session cookies but please never post anything that looks like a token publicly / share it, can be incredibly dangerous.

CORS issues are always annoying to deal with, I’m not certain off the top of my head / would have to play with it to identify but this may come form http only cookies?

@eanders as an aside should be fine to expose an unauthenticated mp.makecode.com/api/game/exists/${code} endpoint with a reasonable throttle right? Feels better to me than exploding join tokens / etc

2 Likes

This call won’t work for you because localhost is not in the multiplayer service’s list of allowed origins. Because this is an authenticated request and it’s coming from a different origin, the CORS pre-flight check is triggered.

If you did get past CORS, there are some additional auth checks that would fail. But if you get past CORS please let us know :). That would be a vulnerability we’d need to patch!

3 Likes

Whoops sorry :sweat_smile:
Didn’t even realize it’s a secret token because it was the same in an incognito session so I assumed the auth secret was always the same (yes, I’ve seen that before)

2 Likes

Soo… how do I send a legit request from my local machine?

2 Likes

If all you want to do is check whether the game exists, Joey’s suggestion is probably the right way to go. We would have to add a new unauthenticated endpoint for querying existence of a game.

2 Likes

Thankfully the value of the authorization header alone isn’t useful to hackers in this case! We pair it with an http-only secure cookie bound to the domain. They both have to be present.

So, could you guys do that? I mean if it’s not a hassle of course, but since you have to check that to even attempt to connect to a room, I have a feeling it wouldn’t be that hard, and I’d really appreciate if you would do that, since I’d be using that in collaboration with @UnsignedArduino for a pretty significant project.

3 Likes

fyi we’re looking at this, I put up a PR with an implementation that should work just fine but didn’t test it at all yet / will play around with it tomorrow to confirm & double check there isn’t anything else to worry about with shipping it (I don’t know if anyone on this planet can confidently say they won’t run into a CORS issue without careful reading & testing any new change).

5 Likes

Thanks a lot! Will you drop an update here when that’s merged?

1 Like

Can you tell us a little bit about your project? What is the use case for this API?

The idea is to host a live 24/7 server of a game, through a link that automatically updates the game room URL when the server restarts. I just wanted to implement a feature where the site would display if the server is online or offline at the moment by checking if the game room linked exists.

2 Likes

(sorry for late response but fyi I’m merging the change for this now, will probably deploy sometime later this week; took a bit extra as the /api/game/exists/{code} was very easy, but it ended up taking an extra “rethink & reimplement how CORS is handled by our backend” step, which then led into a “rethink & refactor how all the api routes in our multiplayer backend are registered” step which I ended doing in background while waiting on builds / testing other stuff)

3 Likes

Okay it’s in, https://mp.makecode.com/api/game/exists/{join-code}200 for ‘game is there’ and 404 for ‘game is not there’. Do not send authentication or anything like that (it will be stripped by browser or anything but it should just be a normal fetch). Here’s my quick test site for example usage: https://replit.com/@jwunderl/QuarrelsomeRespectfulCleaninstall#script.js

5 Likes

Hi @jwunderl @eanders sorry to bother you both, but is there an API that turns a join code (ex. “E8B7E8”) to get the game code? (ex. “_gwq0FxhLCAUD”) I’ve been ramming my head against the network tab and JavaScript debugger but I don’t see anything… (Yes this is for the same thing that @Sarge and I are working on.)

2 Likes

@eanders and I had actually mentioned we’d almost certainly want to add this :slight_smile: one question is if there is anything else we might want to expose on here as well (e.g. maybe return current number of people in the game, etc as well in json blob?)

eric lemme know if you wanna add this now / I can take a look if you don’t have time~

2 Likes

It would be amazing if you also had API for stuff like the current number of players and if the game started!

Also, couple more questions:

  1. Is it possible to spectate a game? (Even when the game is not full)
  2. What is the rate-limiting used for all these (maybe more soon?) public APIs?

y e s p l e a s e.

2 Likes
  1. Architecture supports it, but we didn’t feel great about the UI of it / making sure users 5+ understood they were just viewing so we dropped from the initial release. Feel free to file issue requesting & we can think on it when time allows
  2. @eanders ? Do we wanna answer specifics on throttles, sometimes that’s preferred as semi-secret (and other times explicitly exposed in api results), not sure we have a preference?
1 Like