mirror of
https://github.com/djdevin/recflare.git
synced 2026-09-09 07:01:27 -07:00
153 lines
4.9 KiB
TypeScript
153 lines
4.9 KiB
TypeScript
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 client’s 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 client’s 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 client’s `?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({})
|
||
)
|