Move the jwt logic to an interface, rename the api base url env var

This commit is contained in:
oleksandr-shysh
2025-06-16 11:49:23 +03:00
parent 13e14a92c2
commit 179eb6f72f
7 changed files with 105 additions and 69 deletions
+1 -1
View File
@@ -592,6 +592,6 @@ export async function buildGameUrl(
const config = await getServerConfigFromClient();
const apiPath = `/api/${path}/${gameID}`;
const baseUrl = process.env.API_BASE_URL || "";
const baseUrl = process.env.APP_BASE_URL || "";
return `${baseUrl}/${config.workerPath(gameID)}${apiPath}`;
}
+1 -1
View File
@@ -173,7 +173,7 @@ export class JoinPrivateLobbyModal extends LitElement {
private async checkActiveLobby(lobbyId: string): Promise<boolean> {
const config = await getServerConfigFromClient();
const url = `${process.env.API_BASE_URL || ""}/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;
const url = `${process.env.APP_BASE_URL || ""}/${config.workerPath(lobbyId)}/api/game/${lobbyId}/exists`;
const response = await fetch(url, {
method: "GET",
+1 -1
View File
@@ -57,7 +57,7 @@ export class PublicLobby extends LitElement {
async fetchLobbies(): Promise<GameInfo[]> {
try {
const response = await fetch(
`${process.env.API_BASE_URL}/api/public_lobbies`,
`${process.env.APP_BASE_URL}/api/public_lobbies`,
);
if (!response.ok)
throw new Error(`HTTP error! status: ${response.status}`);
+98 -56
View File
@@ -11,8 +11,74 @@ import {
UserMeResponseSchema,
} from "../core/ApiSchemas";
const isNative = Capacitor.getPlatform() !== "web";
const NATIVE_REDIRECT_URI = `${process.env.API_BASE_URL}/discord-redirect.html`;
interface Platform {
getRedirectUri(): string;
setLocation(url: string): Promise<void> | void;
getApiBaseForLocalhost(): string;
initializeAuthListener(): void;
}
class BrowserPlatform implements Platform {
getRedirectUri(): string {
return window.location.href.split("#")[0];
}
setLocation(url: string): void {
window.location.href = url;
}
getApiBaseForLocalhost(): string {
return localStorage.getItem("apiHost") ?? "http://localhost:8787";
}
initializeAuthListener(): void {
// No-op for web
}
}
class CapacitorPlatform implements Platform {
getRedirectUri(): string {
return `${process.env.APP_BASE_URL}/discord-redirect.html`;
}
async setLocation(url: string): Promise<void> {
await Browser.open({ url });
}
getApiBaseForLocalhost(): string {
return process.env.APP_BASE_URL
? process.env.APP_BASE_URL!.replace("9000", "8787")
: "http://localhost:8787";
}
initializeAuthListener(): void {
App.addListener("appUrlOpen", async (data) => {
try {
const url = new URL(data.url);
if (handleToken(url, false)) {
__isLoggedIn = undefined; // Force re-evaluation
await Browser.close();
window.location.assign(window.location.origin || "/");
return;
}
const error = url.search;
if (error) {
console.error(`Error from auth provider: ${error}`);
}
await Browser.close();
} catch (e) {
console.error("Error handling appUrlOpen", e);
await Browser.close();
}
});
}
}
const platform: Platform =
Capacitor.getPlatform() !== "web"
? new CapacitorPlatform()
: new BrowserPlatform();
function getAudience() {
const hostname =
@@ -25,24 +91,34 @@ function getAudience() {
function getApiBase() {
const domainname = getAudience();
return domainname === "localhost"
? (localStorage.getItem("apiHost") ??
(isNative && process.env.API_BASE_URL))
? process.env.API_BASE_URL!.replace("9000", "8787")
: "http://localhost:8787"
? platform.getApiBaseForLocalhost()
: `https://api.${domainname}`;
}
function getToken(): string | null {
const { hash } = window.location;
if (hash.startsWith("#")) {
const params = new URLSearchParams(hash.slice(1));
const token = params.get("token");
if (token) {
localStorage.setItem("token", token);
params.delete("token");
params.toString();
function handleToken(url: URL, isFromHash: boolean): boolean {
let token: string | null = null;
if (isFromHash) {
if (url.hash.startsWith("#")) {
const params = new URLSearchParams(url.hash.slice(1));
token = params.get("token");
}
} else {
token = url.searchParams.get("token");
}
if (token) {
localStorage.setItem("token", token);
return true;
}
return false;
}
function getToken(): string | null {
const url = new URL(window.location.href);
if (handleToken(url, true)) {
// Clean the URL
const params = new URLSearchParams(url.hash.slice(1));
params.delete("token");
history.replaceState(
null,
"",
@@ -55,21 +131,11 @@ function getToken(): string | null {
}
export async function discordLogin() {
let redirectUri: string;
if (isNative) {
redirectUri = NATIVE_REDIRECT_URI;
} else {
redirectUri = window.location.href.split("#")[0];
}
const url = `${getApiBase()}/login/discord?redirect_uri=${encodeURIComponent(redirectUri)}`;
if (isNative) {
await Browser.open({ url });
} else {
window.location.href = url;
}
const redirectUri = platform.getRedirectUri();
const url = `${getApiBase()}/login/discord?redirect_uri=${encodeURIComponent(
redirectUri,
)}`;
await platform.setLocation(url);
}
export async function logOut(allSessions: boolean = false) {
@@ -126,7 +192,7 @@ function _isLoggedIn(): IsLoggedInResponse {
const payload = decodeJwt(token);
const { iss, aud, exp, iat } = payload;
if (iss !== getApiBase() && !isNative) {
if (iss !== getApiBase()) {
// JWT was not issued by the correct server
console.error(
'unexpected "iss" claim value',
@@ -184,31 +250,7 @@ function _isLoggedIn(): IsLoggedInResponse {
}
export function initializeAuthListener() {
if (Capacitor.getPlatform() === "web") return;
App.addListener("appUrlOpen", async (data) => {
try {
const url = new URL(data.url);
const token = url.searchParams.get("token");
if (token) {
localStorage.setItem("token", token);
__isLoggedIn = undefined; // Force re-evaluation
await Browser.close();
window.location.assign(window.location.origin || "/");
return;
}
const error = url.search;
if (error) {
console.error(`Error from auth provider: ${error}`);
}
await Browser.close();
} catch (e) {
console.error("Error handling appUrlOpen", e);
await Browser.close();
}
});
platform.initializeAuthListener();
}
export async function postRefresh(): Promise<boolean> {
+1 -1
View File
@@ -29,7 +29,7 @@ export async function getServerConfigFromClient(): Promise<ServerConfig> {
if (cachedSC) {
return cachedSC;
}
const response = await fetch(`${process.env.API_BASE_URL}/api/env`);
const response = await fetch(`${process.env.APP_BASE_URL}/api/env`);
if (!response.ok) {
throw new Error(
-5
View File
@@ -1,5 +1,4 @@
import cors from "cors";
import { getLocalIP } from "../../webpack.config";
import { GameEnv } from "../core/configuration/Config";
import { getServerConfigFromServer } from "../core/configuration/ConfigLoader";
@@ -21,10 +20,6 @@ switch (config.env()) {
"http://localhost",
"http://localhost:8787",
);
const localIp = getLocalIP();
if (localIp) {
allowedOrigins.push(`http://${localIp}:9000`);
}
break;
}
}
+3 -4
View File
@@ -30,8 +30,7 @@ const gitCommit =
export default async (env, argv) => {
const isProduction = argv.mode === "production";
// Note: when running capacitor apps locally, run `npm run dev` before building the app to avoid env vars being overridden
const apiBaseUrl = process.env.CAPACITOR_BUILD
const appBaseUrl = process.env.CAPACITOR_BUILD
? isProduction && process.env.CAPACITOR_PRODUCTION_HOSTNAME
? `https://${process.env.CAPACITOR_PRODUCTION_HOSTNAME}`
: `http://${getLocalIP()}:9000`
@@ -159,11 +158,11 @@ export default async (env, argv) => {
}),
new webpack.DefinePlugin({
"process.env.WEBSOCKET_URL": JSON.stringify(
apiBaseUrl.split("://")[1], // remove protocol
appBaseUrl.split("://")[1], // remove protocol
),
"process.env.GAME_ENV": JSON.stringify(isProduction ? "prod" : "dev"),
"process.env.GIT_COMMIT": JSON.stringify(gitCommit),
"process.env.API_BASE_URL": JSON.stringify(apiBaseUrl),
"process.env.APP_BASE_URL": JSON.stringify(appBaseUrl),
"process.env.CAPACITOR_PRODUCTION_HOSTNAME": JSON.stringify(
process.env.CAPACITOR_PRODUCTION_HOSTNAME,
),