Files
recflare/apps/api/src/routes/config.ts
T
2026-08-01 12:50:45 -04:00

153 lines
4.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Hono } from 'hono'
import { describeRoute } from 'hono-openapi'
import { GAME_VERSION } from '@repo/domain'
import apiConfigV2 from '../../static/api-config-v2.json'
import gameConfigsV1All from '../../static/gameconfigs-v1-all.json'
import {
AmplitudeConfig,
ApiConfigV2,
AzureSpeechConfig,
BacktraceConfig,
json,
JsonObject,
VersionCheck,
} from '../openapi'
import type { App } from '../context'
/**
* Client builds the version check answers as current. `GAME_VERSION` is the build the
* rest of the stack targets; `20230616` and `20231207` are later clients that talk the
* same protocol, so we let them through rather than telling them to update.
*
* DEBUGGING ONLY: the extra builds are here so we can point other clients at this
* server while working on it — they are not a supported-version list. Nothing else in
* the stack targets them, so a client waved through here can still hit protocol
* differences the version check would otherwise have caught. Trim this back to
* `GAME_VERSION` alone before anyone but us is playing.
*/
const ACCEPTED_GAME_VERSIONS = new Set([GAME_VERSION, '20230616', '20231207'])
// ---- Config / version ------------------------------------------------------
export const configRoutes = new Hono<App>({ strict: false })
.get(
'/api/config/v1/amplitude',
describeRoute({
tags: ['Config'],
summary: 'Analytics keys',
description:
'The Amplitude / StatSig / RudderStack keys the client initialises its analytics ' +
'with. This server collects nothing, so the keys are placeholders and RudderStack ' +
'is off — but the client needs the object to finish loading.',
responses: { 200: json(AmplitudeConfig, 'Placeholder analytics keys') },
}),
(c) =>
c.json({
AmplitudeKey: 'a',
StatSigKey: 'a',
RudderStackKey: 'a',
UseRudderStack: false,
})
)
.get(
'/api/config/v1/azurespeech',
describeRoute({
tags: ['Config'],
summary: 'Speech-to-text config',
description:
'Azure Speech credentials for the clients voice transcription. `Enabled` is false ' +
'here, so the key and region are never used.',
responses: { 200: json(AzureSpeechConfig, 'Speech config, disabled') },
}),
(c) =>
c.json({
Key: 'dce8de5b297747d9b5bddcc7f19e8c5b',
Region: 'eastus',
Enabled: false,
})
)
.get(
'/api/config/v1/backtrace',
describeRoute({
tags: ['Config'],
summary: 'Crash reporter config',
description:
'Budget, sampling and log-capture settings for the clients Backtrace crash ' +
'reporter. Nothing on this server receives the reports.',
responses: { 200: json(BacktraceConfig, 'Crash reporter settings') },
}),
(c) =>
c.json({
ReportBudget: 125,
FilterType: 0,
SampleRate: 1,
LogLineCount: 50,
CaptureNativeCrashes: 1,
AMRThresholdMS: 0,
MessageCount: 1000,
MessageRegex: '^.*$',
VersionRegex: '.*',
})
)
// ShareBaseUrl is derived from the deploy-time base domain; the rest of the
// config is static.
.get(
'/api/config/v2',
describeRoute({
tags: ['Config'],
summary: 'The main client config blob',
description:
'The large feature-switch / endpoint config the client reads at startup. Served ' +
'from a static asset, except `ShareBaseUrl`, which is templated from the ' +
'deploy-time base domain so share links point at this deployment.',
responses: { 200: json(ApiConfigV2, 'The client config') },
}),
(c) => c.json({ ...apiConfigV2, ShareBaseUrl: `https://www.${c.env.DOMAIN}/{0}` })
)
.get(
'/api/versioncheck/v4',
describeRoute({
tags: ['Config'],
summary: 'Client version check',
description:
'Whether the client build is current. Compares the clients `?v=` build against ' +
'the builds we accept — our target `GAME_VERSION` plus `20230616`: ' +
'`VersionStatus` is 0 for either, 1 for any other build.',
responses: { 200: json(VersionCheck, 'Version status') },
}),
(c) =>
c.json({
VersionStatus: ACCEPTED_GAME_VERSIONS.has(c.req.query('v') ?? '') ? 0 : 1,
UpdateNotificationStage: 0,
IsVersionIslanded: false,
IsCrossPlayDisabled: false,
})
)
.get(
'/api/gameconfigs/v1/all',
describeRoute({
tags: ['Config'],
summary: 'Per-game configuration',
description: 'An opaque static catalog of per-game settings, served verbatim.',
responses: { 200: json(JsonObject, 'The game config catalog') },
}),
(c) => c.json(gameConfigsV1All)
)
// Voice chat config. The client fetches it to set up voice.
// No reference shape, so return an empty object until the client needs fields.
.get(
'/voice/config',
describeRoute({
tags: ['Config'],
summary: 'Voice chat config',
description:
'Fetched by the client while setting up voice. We have no reference shape for it, ' +
'so it stays an empty object until the client is observed needing a field.',
responses: { 200: json(JsonObject, 'An empty object') },
}),
(c) => c.json({})
)