fix(app-proxy): make the SPA shell identical for every visitor (#3112)

This commit is contained in:
Hampus authored and GitHub committed 2026-10-02 15:13:36 +02:00
1 parent 1eed347ffb
commit e297a6a653
15 files changed
+578 -220

No files matched your search

+77 -20
View File
@@ -1,40 +1,97 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
import type {GeoEntry, GeolocationResponse} from '@fluxer/instance_bootstrap/src/Types';
import {Logger} from '@app/features/platform/utils/AppLogger';
import ageGeos from '@fluxer/constants/src/AgeGeos.json';
import type {GeoEntry} from '@fluxer/instance_bootstrap/src/Types';
import {GeolocationResponse} from '@fluxer/schema/src/domains/geolocation/GeolocationSchemas';
import {makeAutoObservable, runInAction} from 'mobx';
const GEOIP_PATH = '/_geoip';
const GEOIP_ATTEMPTS = 3;
const GEOIP_ATTEMPT_TIMEOUT_MS = 2000;
const GEOIP_RETRY_DELAY_MS = 200;
const logger = new Logger('GeoIP');
interface ConnectionGeoCoordinates {
latitude: string | null;
longitude: string | null;
}
function getInlinedGeoip(): GeolocationResponse {
const bootstrap = typeof window !== 'undefined' ? window.__FLUXER_BOOTSTRAP__ : undefined;
if (!bootstrap) {
throw new Error('window.__FLUXER_BOOTSTRAP__ is missing — app must be served by fluxer_app_proxy');
}
return bootstrap.geoip;
const UNRESOLVED_GEOIP: GeolocationResponse = {
countryCode: null,
regionCode: null,
latitude: null,
longitude: null,
ageRestrictedGeos: ageGeos.ageRestrictedGeos,
ageBlockedGeos: ageGeos.ageBlockedGeos,
};
function wait(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function fetchGeoipOnce(): Promise<GeolocationResponse> {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), GEOIP_ATTEMPT_TIMEOUT_MS);
try {
const response = await fetch(GEOIP_PATH, {
cache: 'no-store',
credentials: 'omit',
headers: {Accept: 'application/json'},
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`GeoIP lookup failed with status ${response.status}`);
}
return GeolocationResponse.parse(await response.json());
} finally {
clearTimeout(timer);
}
}
async function fetchGeoip(): Promise<GeolocationResponse> {
for (let attempt = 1; ; attempt++) {
try {
return await fetchGeoipOnce();
} catch (error) {
if (attempt >= GEOIP_ATTEMPTS) {
logger.warn('GeoIP lookup failed, continuing without a location:', error);
return UNRESOLVED_GEOIP;
}
await wait(GEOIP_RETRY_DELAY_MS * attempt);
}
}
}
let loading: Promise<void> | null = null;
class GeoIP {
countryCode: string | null;
regionCode: string | null;
latitude: string | null;
longitude: string | null;
ageRestrictedGeos: ReadonlyArray<GeoEntry>;
ageBlockedGeos: ReadonlyArray<GeoEntry>;
countryCode: string | null = UNRESOLVED_GEOIP.countryCode;
regionCode: string | null = UNRESOLVED_GEOIP.regionCode;
latitude: string | null = UNRESOLVED_GEOIP.latitude;
longitude: string | null = UNRESOLVED_GEOIP.longitude;
ageRestrictedGeos: ReadonlyArray<GeoEntry> = UNRESOLVED_GEOIP.ageRestrictedGeos;
ageBlockedGeos: ReadonlyArray<GeoEntry> = UNRESOLVED_GEOIP.ageBlockedGeos;
constructor() {
const data = getInlinedGeoip();
this.countryCode = data.countryCode;
this.regionCode = data.regionCode;
this.latitude = data.latitude;
this.longitude = data.longitude;
this.ageRestrictedGeos = data.ageRestrictedGeos;
this.ageBlockedGeos = data.ageBlockedGeos;
makeAutoObservable(this, {}, {autoBind: true});
}
load(): Promise<void> {
loading ??= fetchGeoip().then((data) => {
runInAction(() => {
this.countryCode = data.countryCode;
this.regionCode = data.regionCode;
this.latitude = data.latitude;
this.longitude = data.longitude;
this.ageRestrictedGeos = data.ageRestrictedGeos;
this.ageBlockedGeos = data.ageBlockedGeos;
});
});
return loading;
}
applyConnectionFallbackCoordinates(data: ConnectionGeoCoordinates): void {
if (data.latitude === null || data.longitude === null) {
return;