Replace fetch with CapacitorHTTP

This commit is contained in:
oleksandr-shysh
2025-06-20 12:57:24 +03:00
parent 652aefd273
commit 315a77c7d3
14 changed files with 101 additions and 148 deletions
+1 -25
View File
@@ -32,7 +32,6 @@
"binary-loader": "^0.0.1",
"colord": "^2.9.3",
"copy-webpack-plugin": "^13.0.0",
"cors": "^2.8.5",
"d3": "^7.9.0",
"dompurify": "^3.1.7",
"dotenv": "^16.5.0",
@@ -73,7 +72,6 @@
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
"@types/chai": "^4.3.17",
"@types/cors": "^2.8.19",
"@types/d3": "^7.4.3",
"@types/jest": "^30.0.0",
"@types/jquery": "^3.5.31",
@@ -9018,16 +9016,6 @@
"@types/node": "*"
}
},
"node_modules/@types/cors": {
"version": "2.8.19",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz",
"integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/d3": {
"version": "7.4.3",
"resolved": "https://registry.npmjs.org/@types/d3/-/d3-7.4.3.tgz",
@@ -12047,19 +12035,6 @@
"integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
"license": "MIT"
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"license": "MIT",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/cosmiconfig": {
"version": "9.0.0",
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
@@ -18942,6 +18917,7 @@
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
-2
View File
@@ -29,7 +29,6 @@
"@eslint/compat": "^1.2.7",
"@eslint/js": "^9.21.0",
"@types/chai": "^4.3.17",
"@types/cors": "^2.8.19",
"@types/d3": "^7.4.3",
"@types/jest": "^30.0.0",
"@types/jquery": "^3.5.31",
@@ -106,7 +105,6 @@
"binary-loader": "^0.0.1",
"colord": "^2.9.3",
"copy-webpack-plugin": "^13.0.0",
"cors": "^2.8.5",
"d3": "^7.9.0",
"dompurify": "^3.1.7",
"dotenv": "^16.5.0",
+14 -13
View File
@@ -1,3 +1,4 @@
import { CapacitorHttp } from "@capacitor/core";
import { LitElement, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import randomMap from "../../resources/images/RandomMap.webp";
@@ -472,12 +473,12 @@ export class HostLobbyModal extends LitElement {
private async putGameConfig() {
const url = await buildGameUrl(this.lobbyId, "game");
const response = await fetch(url, {
method: "PUT",
const response = await CapacitorHttp.put({
url: url,
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
data: JSON.stringify({
gameMap: this.selectedMap,
difficulty: this.selectedDifficulty,
disableNPCs: this.disableNPCs,
@@ -519,8 +520,8 @@ export class HostLobbyModal extends LitElement {
);
this.close();
const url = await buildGameUrl(this.lobbyId, "start_game");
const response = await fetch(url, {
method: "POST",
const response = await CapacitorHttp.post({
url,
headers: {
"Content-Type": "application/json",
},
@@ -545,13 +546,13 @@ export class HostLobbyModal extends LitElement {
private async pollPlayers() {
const url = await buildGameUrl(this.lobbyId, "game");
fetch(url, {
method: "GET",
CapacitorHttp.get({
url,
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((response) => response.data)
.then((data: GameInfo) => {
console.log(`got game info response: ${JSON.stringify(data)}`);
this.players = data.clients?.map((p) => p.username) ?? [];
@@ -563,19 +564,19 @@ async function createLobby(): Promise<GameInfo> {
try {
const id = generateID();
const url = await buildGameUrl(id, "create_game");
const response = await fetch(url, {
method: "POST",
const response = await CapacitorHttp.post({
url,
headers: {
"Content-Type": "application/json",
},
// body: JSON.stringify(data), // Include this if you need to send data
// data: JSON.stringify(data), // Include this if you need to send data
});
if (!response.ok) {
if (!response.data) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
const data = response.data;
console.log("Success:", data);
return data as GameInfo;
+10 -9
View File
@@ -1,3 +1,4 @@
import { CapacitorHttp } from "@capacitor/core";
import { LitElement, html } from "lit";
import { customElement, query, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
@@ -173,12 +174,12 @@ export class JoinPrivateLobbyModal extends LitElement {
private async checkActiveLobby(lobbyId: string): Promise<boolean> {
const url = await buildGameUrl(lobbyId, "exists");
const response = await fetch(url, {
method: "GET",
const response = await CapacitorHttp.get({
url,
headers: { "Content-Type": "application/json" },
});
const gameInfo = await response.json();
const gameInfo = response.data;
if (gameInfo.exists) {
this.message = translateText("private_lobby.joined_waiting");
@@ -205,12 +206,12 @@ export class JoinPrivateLobbyModal extends LitElement {
private async checkArchivedGame(lobbyId: string): Promise<boolean> {
const url = await buildGameUrl(lobbyId, "archived_game");
const archiveResponse = await fetch(url, {
method: "GET",
const archiveResponse = await CapacitorHttp.get({
url,
headers: { "Content-Type": "application/json" },
});
const archiveData = await archiveResponse.json();
const archiveData = archiveResponse.data;
if (
archiveData.success === false &&
@@ -250,13 +251,13 @@ export class JoinPrivateLobbyModal extends LitElement {
if (!this.lobbyIdInput?.value) return;
const url = await buildGameUrl(this.lobbyIdInput.value, "game");
fetch(url, {
method: "GET",
CapacitorHttp.get({
url,
headers: {
"Content-Type": "application/json",
},
})
.then((response) => response.json())
.then((response) => JSON.parse(response.data))
.then((data: GameInfo) => {
this.players = data.clients?.map((p) => p.username) ?? [];
})
+7 -2
View File
@@ -1,3 +1,4 @@
import { CapacitorHttp } from "@capacitor/core";
import { LitElement, css, html } from "lit";
import { resolveMarkdown } from "lit-markdown";
import { customElement, property, query } from "lit/decorators.js";
@@ -87,8 +88,12 @@ export class NewsModal extends LitElement {
public open() {
if (!this.initialized) {
this.initialized = true;
fetch(changelog)
.then((response) => (response.ok ? response.text() : "Failed to load"))
CapacitorHttp.get({
url: `${process.env.APP_BASE_URL || ""}${changelog}`,
})
.then((response) =>
response.status === 200 ? response.data : "Failed to load",
)
.then((markdown) => (this.markdown = markdown));
}
this.requestUpdate();
+7 -5
View File
@@ -1,3 +1,4 @@
import { CapacitorHttp } from "@capacitor/core";
import { LitElement, html } from "lit";
import { customElement, state } from "lit/decorators.js";
import { translateText } from "../client/Utils";
@@ -56,12 +57,13 @@ export class PublicLobby extends LitElement {
async fetchLobbies(): Promise<GameInfo[]> {
try {
const response = await fetch(
`${process.env.APP_BASE_URL || ""}/api/public_lobbies`,
);
if (!response.ok)
const response = await CapacitorHttp.get({
url: `${process.env.APP_BASE_URL || ""}/api/public_lobbies`,
});
if (response.status !== 200)
throw new Error(`HTTP error! status: ${response.status}`);
const data = await response.json();
const data = JSON.parse(response.data);
return data.lobbies;
} catch (error) {
console.error("Error fetching lobbies:", error);
+13 -15
View File
@@ -1,6 +1,6 @@
import { App } from "@capacitor/app";
import { Browser } from "@capacitor/browser";
import { Capacitor } from "@capacitor/core";
import { Capacitor, CapacitorHttp } from "@capacitor/core";
import { decodeJwt } from "jose";
import { z } from "zod/v4";
import {
@@ -139,17 +139,14 @@ export async function logOut(allSessions: boolean = false) {
localStorage.removeItem("token");
__isLoggedIn = false;
const response = await fetch(
getApiBase() + (allSessions ? "/revoke" : "/logout"),
{
method: "POST",
headers: {
authorization: `Bearer ${token}`,
},
const response = await CapacitorHttp.post({
url: getApiBase() + (allSessions ? "/revoke" : "/logout"),
headers: {
authorization: `Bearer ${token}`,
},
);
});
if (response.ok === false) {
if (response.status !== 200) {
console.error("Logout failed", response);
return false;
}
@@ -254,8 +251,8 @@ export async function postRefresh(): Promise<boolean> {
if (!token) return false;
// Refresh the JWT
const response = await fetch(getApiBase() + "/refresh", {
method: "POST",
const response = await CapacitorHttp.post({
url: getApiBase() + "/refresh",
headers: {
authorization: `Bearer ${token}`,
},
@@ -266,7 +263,7 @@ export async function postRefresh(): Promise<boolean> {
return false;
}
if (response.status !== 200) return false;
const body = await response.json();
const body = response.data;
const result = RefreshResponseSchema.safeParse(body);
if (!result.success) {
const error = z.prettifyError(result.error);
@@ -287,7 +284,8 @@ export async function getUserMe(): Promise<UserMeResponse | false> {
if (!token) return false;
// Get the user object
const response = await fetch(getApiBase() + "/users/@me", {
const response = await CapacitorHttp.get({
url: getApiBase() + "/users/@me",
headers: {
authorization: `Bearer ${token}`,
},
@@ -298,7 +296,7 @@ export async function getUserMe(): Promise<UserMeResponse | false> {
return false;
}
if (response.status !== 200) return false;
const body = await response.json();
const body = response.data;
const result = UserMeResponseSchema.safeParse(body);
if (!result.success) {
const error = z.prettifyError(result.error);
-1
View File
@@ -62,7 +62,6 @@ export interface ServerConfig {
cloudflareApiToken(): string;
cloudflareConfigPath(): string;
cloudflareCredsPath(): string;
origin(): string;
}
export interface NukeMagnitude {
+44 -10
View File
@@ -1,3 +1,4 @@
import { CapacitorHttp } from "@capacitor/core";
import { UserSettings } from "../game/UserSettings";
import { GameConfig } from "../Schemas";
import { Config, GameEnv, ServerConfig } from "./Config";
@@ -29,19 +30,52 @@ export async function getServerConfigFromClient(): Promise<ServerConfig> {
if (cachedSC) {
return cachedSC;
}
const response = await fetch(`${process.env.APP_BASE_URL || ""}/api/env`);
if (!response.ok) {
throw new Error(
`Failed to fetch server config: ${response.status} ${response.statusText}`,
try {
const response = await CapacitorHttp.get({
url: `${process.env.APP_BASE_URL || ""}/api/env`,
headers: {
"Content-Type": "application/json",
},
});
if (!response.data) {
throw new Error(`Failed to fetch server config: ${response.status}`);
}
// Check if response is HTML (error case)
const dataStr =
typeof response.data === "string"
? response.data
: JSON.stringify(response.data);
if (dataStr.includes("<!doctype html>") || dataStr.includes("<html")) {
console.warn(
"Server returned HTML instead of JSON, falling back to environment variable",
);
return getServerConfigFromServer();
}
const config = response.data;
// Validate that we got the expected structure
if (!config || typeof config.game_env !== "string") {
console.warn(
"Invalid config structure received, falling back to environment variable",
);
return getServerConfigFromServer();
}
console.log("Server config loaded:", config);
cachedSC = getServerConfig(config.game_env);
return cachedSC;
} catch (error) {
console.warn(
"Error fetching server config from API, falling back to environment variable:",
error,
);
return getServerConfigFromServer();
}
const config = await response.json();
// Log the retrieved configuration
console.log("Server config loaded:", config);
cachedSC = getServerConfig(config.game_env);
return cachedSC;
}
export function getServerConfigFromServer(): ServerConfig {
const gameEnv = process.env.GAME_ENV ?? "dev";
+5 -14
View File
@@ -1,3 +1,4 @@
import { CapacitorHttp } from "@capacitor/core";
import { JWK } from "jose";
import { z } from "zod/v4";
import {
@@ -85,18 +86,6 @@ export abstract class DefaultServerConfig implements ServerConfig {
return process.env.CF_CREDS_PATH ?? "";
}
origin(): string {
const audience = this.jwtAudience();
const subdomain = this.subdomain();
if (audience === "localhost") {
return "http://localhost:9000";
}
if (subdomain === "") {
return `https://${audience}`;
}
return `https://${subdomain}.${audience}`;
}
private publicKey: JWK;
abstract jwtAudience(): string;
jwtIssuer(): string {
@@ -109,8 +98,10 @@ export abstract class DefaultServerConfig implements ServerConfig {
if (this.publicKey) return this.publicKey;
const jwksUrl = this.jwtIssuer() + "/.well-known/jwks.json";
console.log(`Fetching JWKS from ${jwksUrl}`);
const response = await fetch(jwksUrl);
const result = JwksSchema.safeParse(await response.json());
const response = await CapacitorHttp.get({
url: jwksUrl,
});
const result = JwksSchema.safeParse(response.data);
if (!result.success) {
const error = z.prettifyError(result.error);
console.error("Error parsing JWKS", error);
-2
View File
@@ -7,7 +7,6 @@ import { fileURLToPath } from "url";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
import { GameInfo } from "../core/Schemas";
import { generateID } from "../core/Util";
import { corsMiddleware } from "./cors";
import { gatekeeper, LimiterType } from "./Gatekeeper";
import { logger } from "./Logger";
import { MapPlaylist } from "./MapPlaylist";
@@ -24,7 +23,6 @@ const log = logger.child({ comp: "m" });
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.use(corsMiddleware);
app.use(
express.static(path.join(__dirname, "../../static"), {
maxAge: "1y", // Set max-age to 1 year for all static assets
-2
View File
@@ -18,7 +18,6 @@ import {
import { CreateGameInputSchema, GameInputSchema } from "../core/WorkerSchemas";
import { archive, readGameRecord } from "./Archive";
import { Client } from "./Client";
import { corsMiddleware } from "./cors";
import { GameManager } from "./GameManager";
import { gatekeeper, LimiterType } from "./Gatekeeper";
import { getUserMe, verifyClientToken } from "./jwt";
@@ -72,7 +71,6 @@ export function startWorker() {
next();
});
app.use(corsMiddleware);
app.set("trust proxy", 3);
app.use(express.json());
app.use(express.static(path.join(__dirname, "../../out")));
-45
View File
@@ -1,45 +0,0 @@
import cors from "cors";
import { GameEnv } from "../core/configuration/Config";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
const config = getServerConfigFromServer();
const origin = config.origin();
const allowedOriginsSet = new Set<string>([origin]);
switch (config.env()) {
case GameEnv.Prod:
allowedOriginsSet.add("capacitor://openfront.io");
allowedOriginsSet.add("https://openfront.io");
break;
case GameEnv.Preprod:
allowedOriginsSet.add("capacitor://openfront.dev");
allowedOriginsSet.add("https://openfront.dev");
break;
case GameEnv.Dev: {
allowedOriginsSet.add("capacitor://localhost");
allowedOriginsSet.add("http://localhost");
allowedOriginsSet.add("http://localhost:8787");
break;
}
}
const allowedOrigins = Array.from(allowedOriginsSet);
const corsOptions = {
origin: (
origin: string | undefined,
callback: (err: Error | null, allow?: boolean) => void,
) => {
// allow requests with no origin (like mobile apps or curl requests)
if (!origin) return callback(null, true);
if (allowedOrigins.some((o) => origin.startsWith(o))) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
credentials: true,
};
export const corsMiddleware = cors(corsOptions);
-3
View File
@@ -4,9 +4,6 @@ import { GameMapType } from "../../src/core/game/Game";
import { GameID } from "../../src/core/Schemas";
export class TestServerConfig implements ServerConfig {
origin(): string {
return "unused";
}
cloudflareConfigPath(): string {
throw new Error("Method not implemented.");
}