Welcome to the Jose Madrid Salsa developer docs — explore features, APIs, and deployment guides.
Jose Madrid SalsaJMS Docs

Battle Arena 3D Game Accounts

How players sign in to the 3D Battle Arena game with their account, and the profile and leaderboard API behind it.

Battle Arena 3D game: accounts, profiles and leaderboards

The 3D Battle Arena brawler (built from the separate battle-arena-3d repository) requires every player to sign in with their José Madrid Salsa account before they can fight. This site owns those accounts, the players' profiles and the leaderboards. The sales-driven team standings it sits beside are described in Battle Arena Integration.

Where the game is served

The fundraising app serves the game at /battle-arena (https://fundraising.josemadridsalsa.com/battle-arena), and each month's arena page (/arena/[period]) opens it with its Play now card. It is one static file, apps/fundraising/public/battle-arena/index.html, with its link-preview image beside it:

  • next.config.mjs rewrites /battle-arena to that file and gives /battle-arena/* its own Content-Security-Policy, which lets the page reach www.josemadridsalsa.com (this API), the game's own deployment (fundraising teams for its donate button), the CDN its sounds are on and the PeerJS server that links online rooms.
  • app/battle-arena/share/route.ts is the page shared match links point to: its Open Graph tags name the winner, and it sends the visitor on to the game (into the room, for an invite).

To update the game, build it in the battle-arena-3d repository (its root index.html is the build), then from apps/fundraising run node scripts/sync-battle-arena.mjs <path to that checkout>, which copies the page and image and points the page's preview tags at this site. The game's own deployment (the battle-arena-3d Vercel project) is published at https://battle.josemadridsalsa.com, which the storefront's Battle Live nav tab and homepage Battle Arena section link to, and still answers at https://battle-arena-3d-mauve.vercel.app.

Battle Live

The masthead's Battle Live tab (next to Shop, and in the mobile menu) opens the game. Its dot blinks green while anyone is playing: it polls GET /api/arena/live every 30 seconds, which counts players whose game checked in (POST /api/arena/friends/presence, about every 30 seconds while the game is open and signed in) within the last 90 seconds. The homepage section shows the same count.

Signing in

  1. The game sends the browser to /battle-arena/connect?return_to=<game page>&state=<random>.
  2. A signed-out visitor goes through /auth/signin (or /auth/signup) and comes back to the same page.
  3. The visitor presses Play as …, which posts to /api/arena/auth/authorize.
  4. That route creates the account's ArenaPlayer on first use, issues a game token and redirects to return_to#arena_token=<token>&state=<state>. The token is in the fragment, so it never reaches a server log; the game checks state against the one it sent.

Tokens only go to listed game origins: the fundraising site (NEXT_PUBLIC_FUNDRAISING_SITE_URL), the game's own deployment (battle.josemadridsalsa.com and its Vercel URL), plus any in ARENA_GAME_ORIGINS. Plain http://localhost is accepted outside production for developing the game. The game keeps the token for 90 days (ArenaPlayerSession, SHA-256 only) and sends it as Authorization: Bearer <token>.

Groups

A player's fundraising group (ArenaPlayer.teamId, a FundraiserTeam) comes from their account when it can: the team of a FundraiserCharacter they claimed, else the team of the fundraiser their FundraiserAccount runs. Those players cannot change it. Anyone else picks an ACTIVE team in the game and can switch once every 7 days. Each match stores the group it was played for, so switching never moves old results.

Recording matches

The game is peer to peer, so no server watches the fight. Instead the game opens an ArenaMatch when the first round starts and closes it with the player's own result when the match ends. A result is accepted once, only for an open match, within 3 hours, and only if the match could have produced it (at least 6 seconds a round, no more knockouts than opponents allow, a damage ceiling). A player can open at most 40 matches an hour. Closing a match adds it to the player's running totals in the same transaction.

API

All routes live in apps/storefront/app/api/arena and the logic in apps/storefront/lib/arena-game.

RouteAuthPurpose
POST /api/arena/auth/authorizeSite session (form)Issue a game token and return to the game
POST /api/arena/auth/sign-outGame tokenRevoke the token
GET /api/arena/meGame tokenProfile: name, group, totals, rank, recent matches
PATCH /api/arena/meGame token{ handle?, teamId? } rename or choose a group
GET /api/arena/teamsPublicGroups a player can choose
POST /api/arena/matchesGame token{ mode, fighter?, opponents, room? } open a match
POST /api/arena/matches/[id]Game token{ won, roundsWon, rounds, knockouts, damage } close it
GET /api/arena/leaderboardPublicboard=players|teams, period=week|month|all, mode=all|versus|cpu|online|tournament, teamId, limit
GET /api/arena/players/[handle]PublicA fighter's public profile
GET /api/arena/livePublic{ playing }: players with the game open right now

Signed-in routes answer cross-origin only to the listed game origins; the public ones to anyone. Errors are { error, code? }, where code: "signed_out" tells the game to sign in again. Weeks start Monday 00:00 UTC and months on the 1st (UTC), like the fundraiser battle months. Boards rank by wins, then knockouts.

How is this guide?

Edit on GitHub

Last updated on

On this page