mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-07 19:22:14 +09:00
fix(app-proxy): make the SPA shell identical for every visitor (#3112)
This commit is contained in:
@@ -23,9 +23,9 @@
|
||||
<meta name="display" content="standalone">
|
||||
<link rel="license" href="/assets/fonts-NOTICE.txt">
|
||||
<!--{{FLUXER_BOOTSTRAP}}-->
|
||||
<script nonce="{{CSP_NONCE_PLACEHOLDER}}">(function(){try{var loc=window.location;if(loc.pathname==='/'){var target='/channels/@me';if(loc.search)target+=loc.search;if(loc.hash)target+=loc.hash;loc.replace(target);}}catch(e){}})();</script>
|
||||
<script nonce="{{CSP_NONCE_PLACEHOLDER}}">(function(){try{var t=localStorage.getItem('theme');if(t){document.documentElement.classList.add('theme-'+t)}}catch{}})()</script>
|
||||
<script nonce="{{CSP_NONCE_PLACEHOLDER}}">(function(){try{if(typeof WebSocket!=='function')return;if(window.location.pathname.indexOf('/migrate/')===0)return;var t=localStorage.getItem('token');if(!t||t==='undefined'||t==='null')return;var b=window.__FLUXER_BOOTSTRAP__;var g=b&&b.instance&&b.instance.endpoints&&b.instance.endpoints.gateway;if(!g)return;var u=new URL(g);u.searchParams.set('v','1');u.searchParams.set('encoding','json');u.searchParams.set('compress','zstd-stream');u.searchParams.set('stream','1');var url=u.toString();var ws=new WebSocket(url);ws.binaryType='arraybuffer';var s={open:false,url:url,messages:[],startedAt:Date.now()};ws.onopen=function(){s.open=true};ws.onmessage=function(e){s.messages.push(e)};ws.onclose=ws.onerror=function(){window.__FLUXER_FAST_CONNECT__=null};window.__FLUXER_FAST_CONNECT__={ws:ws,state:s};setTimeout(function(){var h=window.__FLUXER_FAST_CONNECT__;if(h&&h.ws===ws){window.__FLUXER_FAST_CONNECT__=null;try{ws.close(1000,'Fast connect unclaimed')}catch(e){}}},30000)}catch(e){}})()</script>
|
||||
<script>(function(){try{var loc=window.location;if(loc.pathname==='/'){var target='/channels/@me';if(loc.search)target+=loc.search;if(loc.hash)target+=loc.hash;loc.replace(target);}}catch(e){}})();</script>
|
||||
<script>(function(){try{var t=localStorage.getItem('theme');if(t){document.documentElement.classList.add('theme-'+t)}}catch{}})()</script>
|
||||
<script>(function(){try{if(typeof WebSocket!=='function')return;if(window.location.pathname.indexOf('/migrate/')===0)return;var t=localStorage.getItem('token');if(!t||t==='undefined'||t==='null')return;var b=window.__FLUXER_BOOTSTRAP__;var g=b&&b.instance&&b.instance.endpoints&&b.instance.endpoints.gateway;if(!g)return;var u=new URL(g);u.searchParams.set('v','1');u.searchParams.set('encoding','json');u.searchParams.set('compress','zstd-stream');u.searchParams.set('stream','1');var url=u.toString();var ws=new WebSocket(url);ws.binaryType='arraybuffer';var s={open:false,url:url,messages:[],startedAt:Date.now()};ws.onopen=function(){s.open=true};ws.onmessage=function(e){s.messages.push(e)};ws.onclose=ws.onerror=function(){window.__FLUXER_FAST_CONNECT__=null};window.__FLUXER_FAST_CONNECT__={ws:ws,state:s};setTimeout(function(){var h=window.__FLUXER_FAST_CONNECT__;if(h&&h.ws===ws){window.__FLUXER_FAST_CONNECT__=null;try{ws.close(1000,'Fast connect unclaimed')}catch(e){}}},30000)}catch(e){}})()</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@@ -160,7 +160,7 @@ async function bootstrapApp(): Promise<void> {
|
||||
loadLazyModule(() => import('@app/features/auth/state/AccountManager')),
|
||||
loadLazyModule(() => import('@app/features/channel/state/ChannelDisplayName')),
|
||||
loadLazyModule(() => import('@app/features/channel/state/ChannelFrecency')),
|
||||
loadLazyModule(() => import('@app/features/app/state/GeoIP')),
|
||||
loadLazyModule(() => import('@app/features/app/state/GeoIP')).then(({default: GeoIP}) => GeoIP.load()),
|
||||
loadLazyModule(() => import('@app/features/input/state/InputKeybind')),
|
||||
loadLazyModule(() => import('@app/features/auth/state/NewDeviceMonitoring')),
|
||||
loadLazyModule(() => import('@app/features/ui/state/Notification')),
|
||||
|
||||
Vendored
+1
-5
@@ -2,10 +2,7 @@
|
||||
|
||||
import MediaEngineFacade from '@app/features/voice/engine/MediaEngineFacade';
|
||||
import type {ElectronAPI} from '@app/features/platform/types/Electron';
|
||||
import type {
|
||||
GeolocationResponse,
|
||||
InstanceDiscoveryResponse,
|
||||
} from '@fluxer/instance_bootstrap/src/Types';
|
||||
import type {InstanceDiscoveryResponse} from '@fluxer/instance_bootstrap/src/Types';
|
||||
import {Buffer} from 'buffer';
|
||||
|
||||
type MediaEngineInstance = typeof MediaEngineFacade;
|
||||
@@ -31,7 +28,6 @@ interface FluxerBootstrapGlobal {
|
||||
bootstrapApiPublicEndpoint?: string;
|
||||
};
|
||||
instance: InstanceDiscoveryResponse;
|
||||
geoip: GeolocationResponse;
|
||||
}
|
||||
|
||||
declare global {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -19,14 +19,14 @@ const UNRENDERED_INDEX_TEMPLATE = [
|
||||
'<!doctype html><html lang="en"><head>',
|
||||
'<link rel="preconnect" href="{{STATIC_CDN_ENDPOINT}}">',
|
||||
'<link rel="apple-touch-icon" href="{{STATIC_CDN_ENDPOINT}}/web/apple-touch-icon.png">',
|
||||
'<script nonce="{{CSP_NONCE_PLACEHOLDER}}"></script>',
|
||||
'<script></script>',
|
||||
'</head><body><div id="root"></div></body></html>',
|
||||
].join('');
|
||||
|
||||
const RENDERED_INDEX_DOCUMENT = [
|
||||
'<!doctype html><html lang="en"><head>',
|
||||
'<link rel="preconnect" href="https://cdn.fluxer.test">',
|
||||
'<script nonce="abc123">window.__FLUXER_BOOTSTRAP__={"instance":{}};</script>',
|
||||
'<script>window.__FLUXER_BOOTSTRAP__={"instance":{}};</script>',
|
||||
'</head><body><div id="root"></div></body></html>',
|
||||
].join('');
|
||||
|
||||
|
||||
@@ -344,14 +344,6 @@ const BOOTSTRAP_ENDPOINT = 'https://primary.test/api';
|
||||
registration: {collect_date_of_birth: true},
|
||||
},
|
||||
},
|
||||
geoip: {
|
||||
countryCode: null,
|
||||
regionCode: null,
|
||||
latitude: null,
|
||||
longitude: null,
|
||||
ageRestrictedGeos: [],
|
||||
ageBlockedGeos: [],
|
||||
},
|
||||
};
|
||||
|
||||
const {VoiceEngineV2AppScreenShareExecutionAdapter, shouldRestoreScreenShareAfterReconnect} = await import(
|
||||
|
||||
Reference in New Issue
Block a user