mirror of
https://github.com/openfrontio/OpenFrontIO.git
synced 2026-06-21 12:51:30 +00:00
initialcommit
This commit is contained in:
+7
-74
@@ -41,6 +41,7 @@ import {
|
||||
SendKickPlayerIntentEvent,
|
||||
SendUpdateGameConfigIntentEvent,
|
||||
} from "./Transport";
|
||||
import { turnstileManager } from "./TurnstileManager";
|
||||
import { UserSettingModal } from "./UserSettingModal";
|
||||
import "./UsernameInput";
|
||||
import { genAnonUsername, UsernameInput } from "./UsernameInput";
|
||||
@@ -162,7 +163,6 @@ declare global {
|
||||
interface Window {
|
||||
GIT_COMMIT: string;
|
||||
INSTANCE_ID: string;
|
||||
turnstile: any;
|
||||
adsEnabled: boolean;
|
||||
PageOS: {
|
||||
session: {
|
||||
@@ -239,18 +239,12 @@ class Client {
|
||||
|
||||
private gutterAds: GutterAds;
|
||||
|
||||
private turnstileTokenPromise: Promise<{
|
||||
token: string;
|
||||
createdAt: number;
|
||||
}> | null = null;
|
||||
|
||||
constructor() {}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
async initialise(): Promise<void> {
|
||||
crazyGamesSDK.maybeInit();
|
||||
// Prefetch turnstile token so it is available when
|
||||
// the user joins a lobby.
|
||||
this.turnstileTokenPromise = getTurnstileToken();
|
||||
// Initialise turnstile manager to prefetch and maintain tokens
|
||||
turnstileManager.initialise();
|
||||
|
||||
// Wait for components to render before setting version
|
||||
await customElements.whenDefined("mobile-nav-bar");
|
||||
@@ -954,29 +948,8 @@ class Client {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Always request a new token on crazygames.
|
||||
if (this.turnstileTokenPromise === null || crazyGamesSDK.isOnCrazyGames()) {
|
||||
console.log("No prefetched turnstile token, getting new token");
|
||||
return (await getTurnstileToken())?.token ?? null;
|
||||
}
|
||||
|
||||
const token = await this.turnstileTokenPromise;
|
||||
// Clear promise so a new token is fetched next time
|
||||
this.turnstileTokenPromise = null;
|
||||
if (!token) {
|
||||
console.log("No turnstile token");
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenTTL = 3 * 60 * 1000;
|
||||
if (Date.now() < token.createdAt + tokenTTL) {
|
||||
console.log("Prefetched turnstile token is valid");
|
||||
|
||||
return token.token;
|
||||
} else {
|
||||
console.log("Turnstile token expired, getting new token");
|
||||
return (await getTurnstileToken())?.token ?? null;
|
||||
}
|
||||
// Use the TurnstileManager to get a token
|
||||
return turnstileManager.getToken();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -992,7 +965,7 @@ const hideCrazyGamesElements = () => {
|
||||
// Initialize the client when the DOM is loaded
|
||||
const bootstrap = () => {
|
||||
initLayout();
|
||||
new Client().initialize();
|
||||
new Client().initialise();
|
||||
initNavigation();
|
||||
|
||||
// Hide elements immediately
|
||||
@@ -1008,43 +981,3 @@ if (document.readyState === "loading") {
|
||||
} else {
|
||||
bootstrap();
|
||||
}
|
||||
|
||||
async function getTurnstileToken(): Promise<{
|
||||
token: string;
|
||||
createdAt: number;
|
||||
}> {
|
||||
// Wait for Turnstile script to load (handles slow connections)
|
||||
let attempts = 0;
|
||||
while (typeof window.turnstile === "undefined" && attempts < 100) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (typeof window.turnstile === "undefined") {
|
||||
throw new Error("Failed to load Turnstile script");
|
||||
}
|
||||
|
||||
const config = await getServerConfigFromClient();
|
||||
const widgetId = window.turnstile.render("#turnstile-container", {
|
||||
sitekey: config.turnstileSiteKey(),
|
||||
size: "normal",
|
||||
appearance: "interaction-only",
|
||||
theme: "light",
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
window.turnstile.execute(widgetId, {
|
||||
callback: (token: string) => {
|
||||
window.turnstile.remove(widgetId);
|
||||
console.log(`Turnstile token received: ${token}`);
|
||||
resolve({ token, createdAt: Date.now() });
|
||||
},
|
||||
"error-callback": (errorCode: string) => {
|
||||
window.turnstile.remove(widgetId);
|
||||
console.error(`Turnstile error: ${errorCode}`);
|
||||
alert(`Turnstile error: ${errorCode}. Please refresh and try again.`);
|
||||
reject(new Error(`Turnstile failed: ${errorCode}`));
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
import { getServerConfigFromClient } from "../core/configuration/ConfigLoader";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
turnstile: {
|
||||
render: (
|
||||
container: string,
|
||||
options: {
|
||||
sitekey: string;
|
||||
size: string;
|
||||
appearance: string;
|
||||
theme: string;
|
||||
},
|
||||
) => string;
|
||||
execute: (
|
||||
widgetId: string,
|
||||
callbacks: {
|
||||
callback: (token: string) => void;
|
||||
"error-callback": (errorCode: string) => void;
|
||||
},
|
||||
) => void;
|
||||
remove: (widgetId: string) => void;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface TokenData {
|
||||
token: string;
|
||||
createdAt: number;
|
||||
}
|
||||
|
||||
type ManagerState = "idle" | "fetching" | "ready";
|
||||
|
||||
const TOKEN_TTL_MS = 3 * 60 * 1000;
|
||||
const POLL_INTERVAL_MS = 30 * 1000;
|
||||
const TOKEN_REFRESH_BUFFER_MS = 30 * 1000; // Refresh 30 seconds before expiry
|
||||
|
||||
class TurnstileManager {
|
||||
private state: ManagerState = "idle";
|
||||
private currentToken: TokenData | null = null;
|
||||
private pendingPromise: Promise<TokenData | null> | null = null;
|
||||
private pollIntervalId: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
initialise(): void {
|
||||
console.log("Turnstile initialising");
|
||||
this.ensureTokenAvailable();
|
||||
this.startPolling();
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
this.stopPolling();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a token for use. This will:
|
||||
* - Return the cached token if valid
|
||||
* - Wait for a pending request if one is in progress
|
||||
* - Start a new request if needed
|
||||
*/
|
||||
async getToken(): Promise<string | null> {
|
||||
const tokenData = await this.acquireToken();
|
||||
if (!tokenData) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Check if token is still valid
|
||||
if (!this.isTokenValid(tokenData)) {
|
||||
console.log("TurnstileManager acquired token is expired, fetching new");
|
||||
// Token expired during wait, need to fetch fresh
|
||||
this.currentToken = null;
|
||||
this.state = "idle";
|
||||
const freshToken = await this.acquireToken();
|
||||
if (freshToken) {
|
||||
this.scheduleRefresh();
|
||||
return freshToken.token;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Mark token as consumed and trigger background refresh
|
||||
this.scheduleRefresh();
|
||||
|
||||
return tokenData.token;
|
||||
}
|
||||
|
||||
hasValidToken(): boolean {
|
||||
return this.currentToken !== null && this.isTokenValid(this.currentToken);
|
||||
}
|
||||
|
||||
isFetching(): boolean {
|
||||
return this.state === "fetching";
|
||||
}
|
||||
|
||||
private async acquireToken(): Promise<TokenData | null> {
|
||||
// If we have a valid cached token, return it
|
||||
if (this.currentToken && this.isTokenValid(this.currentToken)) {
|
||||
console.log("TurnstileManager using cached valid token");
|
||||
return this.currentToken;
|
||||
}
|
||||
|
||||
// If a fetch is already in progress, wait for it
|
||||
if (this.state === "fetching" && this.pendingPromise) {
|
||||
console.log("TurnstileManager waiting for pending token request");
|
||||
return this.pendingPromise;
|
||||
}
|
||||
|
||||
// Need to fetch a new token
|
||||
return this.fetchNewToken();
|
||||
}
|
||||
|
||||
private async fetchNewToken(): Promise<TokenData | null> {
|
||||
console.log("TurnstileManager starting new token fetch");
|
||||
this.state = "fetching";
|
||||
|
||||
this.pendingPromise = this.doFetchToken();
|
||||
|
||||
try {
|
||||
const tokenData = await this.pendingPromise;
|
||||
this.currentToken = tokenData;
|
||||
this.state = tokenData ? "ready" : "idle";
|
||||
console.log(
|
||||
`TurnstileManager token fetch complete, state: ${this.state}`,
|
||||
);
|
||||
return tokenData;
|
||||
} catch (error) {
|
||||
console.error("TurnstileManager token fetch failed:", error);
|
||||
this.state = "idle";
|
||||
this.currentToken = null;
|
||||
return null;
|
||||
} finally {
|
||||
this.pendingPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
private async doFetchToken(): Promise<TokenData | null> {
|
||||
try {
|
||||
// Wait for Turnstile script to load
|
||||
let attempts = 0;
|
||||
while (typeof window.turnstile === "undefined" && attempts < 100) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
attempts++;
|
||||
}
|
||||
|
||||
if (typeof window.turnstile === "undefined") {
|
||||
console.error("TurnstileManager Turnstile script failed to load");
|
||||
return null;
|
||||
}
|
||||
|
||||
const config = await getServerConfigFromClient();
|
||||
const widgetId = window.turnstile.render("#turnstile-container", {
|
||||
sitekey: config.turnstileSiteKey(),
|
||||
size: "normal",
|
||||
appearance: "interaction-only",
|
||||
theme: "light",
|
||||
});
|
||||
|
||||
return new Promise((resolve) => {
|
||||
window.turnstile.execute(widgetId, {
|
||||
callback: (token: string) => {
|
||||
window.turnstile.remove(widgetId);
|
||||
console.log("TurnstileManager token received");
|
||||
resolve({ token, createdAt: Date.now() });
|
||||
},
|
||||
"error-callback": (errorCode: string) => {
|
||||
window.turnstile.remove(widgetId);
|
||||
console.error(`TurnstileManager Turnstile error: ${errorCode}`);
|
||||
resolve(null);
|
||||
},
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("TurnstileManager Error in doFetchToken:", error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private isTokenValid(tokenData: TokenData): boolean {
|
||||
return Date.now() < tokenData.createdAt + TOKEN_TTL_MS;
|
||||
}
|
||||
|
||||
private isTokenNearExpiry(tokenData: TokenData): boolean {
|
||||
return (
|
||||
Date.now() > tokenData.createdAt + TOKEN_TTL_MS - TOKEN_REFRESH_BUFFER_MS
|
||||
);
|
||||
}
|
||||
|
||||
private scheduleRefresh(): void {
|
||||
// Clear current token since it was just used
|
||||
this.currentToken = null;
|
||||
this.state = "idle";
|
||||
|
||||
// Start fetching a new one in the background (don't await)
|
||||
console.log("TurnstileManager scheduling background token refresh");
|
||||
this.ensureTokenAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure we have a token available (or are fetching one)
|
||||
*/
|
||||
private ensureTokenAvailable(): void {
|
||||
if (this.state === "fetching") {
|
||||
// Already fetching, nothing to do
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.currentToken && this.isTokenValid(this.currentToken)) {
|
||||
// Check if token is near expiry and refetch
|
||||
if (this.isTokenNearExpiry(this.currentToken)) {
|
||||
console.log("TurnstileManager token near expiry, refetching");
|
||||
this.fetchNewToken();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// No valid token, start fetching
|
||||
this.fetchNewToken();
|
||||
}
|
||||
|
||||
private startPolling(): void {
|
||||
if (this.pollIntervalId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.pollIntervalId = setInterval(() => {
|
||||
this.ensureTokenAvailable();
|
||||
}, POLL_INTERVAL_MS);
|
||||
|
||||
console.log("TurnstileManager polling started");
|
||||
}
|
||||
|
||||
private stopPolling(): void {
|
||||
if (this.pollIntervalId) {
|
||||
clearInterval(this.pollIntervalId);
|
||||
this.pollIntervalId = null;
|
||||
console.log("TurnstileManager polling stopped");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const turnstileManager = new TurnstileManager();
|
||||
Reference in New Issue
Block a user