Files
blitzkit/packages/website-ue/src/core/api/server.ts
T

124 lines
3.5 KiB
TypeScript
Raw Normal View History

2025-11-28 13:51:17 -06:00
import type { MetadataAccessor } from "@blitzkit/closed";
2025-11-28 14:13:03 -06:00
import { sluggify } from "@blitzkit/core";
import type { RemoteStorageComponent } from "@protos/blitz_static_remote_storage_component";
import { parse } from "yaml";
2025-12-26 14:14:29 -06:00
import type { Tank } from "../../protos/tank";
2025-11-28 13:51:17 -06:00
import type { TanksEntry } from "../../protos/tanks";
import { AbstractAPI } from "./abstract";
2025-11-30 20:54:36 -06:00
if (typeof window !== "undefined") {
throw new Error("ServerAPI is being evaluated in the browser");
}
2025-11-28 14:13:03 -06:00
interface LocalizationConfig {
namespaces: string[];
}
2025-11-28 13:51:17 -06:00
export class ServerAPI extends AbstractAPI {
constructor(private metadata: MetadataAccessor) {
super();
2025-11-28 14:13:03 -06:00
}
2025-11-28 13:51:17 -06:00
2025-11-28 14:13:03 -06:00
async strings(locale: string) {
2025-12-05 21:35:58 -06:00
const group = this.metadata.group("ClientConfigsEntity");
if (group.length !== 1) {
throw new RangeError(
`Don't know how to handle ${group.length} ClientConfigsEntities`
);
}
const clientConfig = this.metadata.item(group[0].id);
2025-11-28 14:13:03 -06:00
const localizationResources = clientConfig.LocalizationResources();
if (!localizationResources.remote_storage) {
throw new Error("Localization resources not found");
}
let remoteStorage: RemoteStorageComponent | undefined = undefined;
for (const candidateId of localizationResources.remote_storage
.remote_urls) {
const item = this.metadata.item(candidateId);
const candidate = item.RemoteStorage();
if (
remoteStorage === undefined ||
candidate.download_speed_weight > remoteStorage.download_speed_weight
) {
remoteStorage = candidate;
}
}
if (remoteStorage === undefined) {
throw new Error("No suitable production remote storage found");
}
const strings: Record<string, string> = {};
2025-12-13 13:16:42 -06:00
const configPath = `${remoteStorage.url}/${remoteStorage.relative_path}/config.yaml`;
2025-11-28 14:13:03 -06:00
2025-12-13 13:16:42 -06:00
try {
const config = await fetch(configPath)
.then((response) => response.text())
.then((text) => parse(text) as LocalizationConfig);
2025-11-28 14:13:03 -06:00
2025-12-13 13:16:42 -06:00
for (const namespace of config.namespaces) {
Object.assign(
strings,
await fetch(
`${remoteStorage.url}/${remoteStorage.relative_path}/${namespace}/${locale}.yaml`
)
.then((response) => response.text())
.then((text) => {
const strings: Record<string, string> = {};
const parsed = parse(text) as Record<string, string>;
2025-11-28 14:13:03 -06:00
2025-12-13 13:16:42 -06:00
for (const key in parsed) {
strings[key] = parsed[key].replaceAll('\\"', '"');
}
return strings;
})
);
}
} catch (error) {
console.warn(
`Failed to fetch config from ${configPath}. Returning empty strings. This will throw an error in production. Error:`,
error
2025-11-28 14:13:03 -06:00
);
}
return strings;
2025-11-28 13:51:17 -06:00
}
async tanks() {
const group = this.metadata.group("TankEntity");
const tanks: TanksEntry[] = [];
2025-11-28 14:13:03 -06:00
const strings = await this.strings("en");
2025-11-28 13:51:17 -06:00
for (const item of group) {
const tankCatalog = item.TankCatalog();
2025-11-28 14:21:26 -06:00
if (!tankCatalog.name) continue;
const name = strings[tankCatalog.name.value];
if (!name) continue;
2025-11-28 13:51:17 -06:00
2025-11-28 14:13:03 -06:00
const id = item.name;
const slug = sluggify(name);
2025-11-28 13:51:17 -06:00
tanks.push({ id, slug });
}
return { tanks };
}
2025-11-28 14:21:26 -06:00
async tank(id: string) {
const item = this.metadata.item(`TankEntity.${id}`);
2025-12-26 14:14:29 -06:00
const tank = item.TankCatalog();
const compensation = item.Compensation();
2025-11-28 14:21:26 -06:00
2025-12-26 14:14:29 -06:00
return { tank, compensation } satisfies Tank;
2025-11-28 14:21:26 -06:00
}
2025-11-28 13:51:17 -06:00
}