[2026] relax versioncheck to major versions

This commit is contained in:
Devin Zuczek
2026-10-12 21:28:29 -04:00
parent bd15e30bfc
commit 1dd5fd409e
19 changed files with 304 additions and 88 deletions
+6 -5
View File
@@ -2,7 +2,7 @@ import { Hono } from 'hono'
import { describeRoute } from 'hono-openapi'
import { GAME_VERSION, isSupportedGameVersion } from '@repo/domain'
import { validateAndGetVersion } from '@repo/jwt'
import { validateAndGetBuild } from '@repo/jwt'
import apiConfigV2 from '../../static/api-config-v2.json'
import gameConfigsV1All2025 from '../../static/gameconfigs-v1-all-2025.json'
@@ -140,8 +140,9 @@ export const configRoutes = new Hono<App>({ strict: false })
// Which one a caller gets is decided by the token's `rn.ver` claim — the build the
// client posted at login — since the request itself carries no version. Anything NEWER
// than `GAME_VERSION` (20230414) gets the 2025 catalog; that build and anything older
// get the 2023 one. Builds are date-stamped (`20230414`, `20250718.01`), so they order
// as strings, the same comparison `match` makes for cross-build joins.
// get the 2023 one. Builds are date-stamped (`20230414`, `20250718`), so they order as
// strings, the same comparison `match` makes for cross-build joins — compared as BUILDS,
// so a same-day point release of the target (`20230414.01`) stays on the 2023 catalog.
//
// A request with no readable token version gets the 2023 catalog, the same body this
// route has always served: unauthenticated is not evidence of a newer client, and this
@@ -163,8 +164,8 @@ export const configRoutes = new Hono<App>({ strict: false })
responses: { 200: json(JsonObject, 'The game config catalog for the caller’s build') },
}),
async (c) => {
const version = await validateAndGetVersion(c.req.raw, await c.env.JWT_SECRET.get())
const newerThanTarget = version !== null && version > GAME_VERSION
const build = await validateAndGetBuild(c.req.raw, await c.env.JWT_SECRET.get())
const newerThanTarget = build !== null && build > GAME_VERSION
return c.json(newerThanTarget ? gameConfigsV1All2025 : gameConfigsV1All)
}
)
+6 -5
View File
@@ -3,7 +3,7 @@ import { describeRoute } from 'hono-openapi'
import { Accessibility, createNotification, GAME_VERSION, MessageType } from '@repo/domain'
import { logger } from '@repo/hono-helpers'
import { validateAndGetVersion } from '@repo/jwt'
import { validateAndGetBuild } from '@repo/jwt'
// The notification-type ids the hub carries (owned by the `notify` worker). Imported
// as a value — the enum has no runtime dependencies.
@@ -306,15 +306,16 @@ async function pushEventInvitation(
* the split is the one `/api/gameconfigs/v1/all` already makes: anything NEWER than
* `GAME_VERSION` (20230414) gets the pairs; that build, anything older, and a request
* with no readable token version all get the 2023 names. Builds are date-stamped, so they
* order as strings. `TagModifyResult.Tags` is a name list to every build — only the
* inline field moves.
* order as strings — compared as BUILDS, with the client's point release dropped, so a
* same-day rebuild of the target stays on its side. `TagModifyResult.Tags` is a name list
* to every build — only the inline field moves.
*
* Like the other version gates here the claim is unverified — a client that lies about its
* build only empties its own tag chips.
*/
async function eventResult(c: Context<App>, event: PlayerEvent, tags: EventTag[]) {
const version = await validateAndGetVersion(c.req.raw, await c.env.JWT_SECRET.get())
const isModernBuild = version !== null && version > GAME_VERSION
const build = await validateAndGetBuild(c.req.raw, await c.env.JWT_SECRET.get())
const isModernBuild = build !== null && build > GAME_VERSION
return toEventResult(event, tags, isModernBuild)
}
+15
View File
@@ -301,6 +301,18 @@ describe('public endpoints', () => {
}
})
test('GET /api/versioncheck/v4 ignores the point release: every .NN of a supported build is current', async () => {
// The list holds bare dates; the client reports `YYYYMMDD.NN`, and the `.NN` is a
// same-day rebuild of the same build. All of these are the beta build.
for (const version of ['20250718.01', '20250718.2', '20250718', '20250718.99']) {
const res = await exports.default.fetch(`${ORIGIN}/api/versioncheck/v4?v=${version}`)
expect(await res.json(), version).toMatchObject({ VersionStatus: 0 })
}
// ...and a point release of a build that ISN'T supported is still not.
const res = await exports.default.fetch(`${ORIGIN}/api/versioncheck/v4?v=19990101.01`)
expect(await res.json()).toMatchObject({ VersionStatus: 1 })
})
test('GET /api/versioncheck/v4 flags a mismatched build', async () => {
const res = await exports.default.fetch(`${ORIGIN}/api/versioncheck/v4?v=19990101`)
expect(await res.json()).toMatchObject({ VersionStatus: 1 })
@@ -331,6 +343,9 @@ describe('public endpoints', () => {
test('GET /api/gameconfigs/v1/all serves the 2023 catalog to the target build', async () => {
expect(await gameConfigKeys(GAME_VERSION)).not.toContain(KEY_2025_ONLY)
// A same-day point release of the target is the target build: compared as a build,
// not as a string that happens to sort after `20230414`.
expect(await gameConfigKeys(`${GAME_VERSION}.01`)).not.toContain(KEY_2025_ONLY)
})
test('GET /api/gameconfigs/v1/all serves the 2023 catalog to an older build', async () => {