mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-19 05:55:34 +09:00
fix(app): keep same-host endpoints on the serving origin (#3525)
This commit is contained in:
1 parent
0eb4e1733e
commit
8a7c791e4f
3 files changed
+59
-1
No files matched your search
@@ -480,6 +480,28 @@ describe('InstanceSnapshotStore.resolve on an official web page', () => {
|
||||
expect(resolution.snapshot.webAppEndpoint).toBe('https://alias.example.test');
|
||||
});
|
||||
|
||||
test('an alias host also takes over the gateway and media the canonical host serves', async () => {
|
||||
stubBrowserDocument('https://gateway-alias.example.test', {servesOwnApi: true});
|
||||
const canonical = discoveryDocument('canonical.example.test');
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async () =>
|
||||
jsonResponse({
|
||||
...canonical,
|
||||
endpoints: {
|
||||
...canonical.endpoints,
|
||||
gateway: 'wss://canonical.example.test/gateway',
|
||||
media: 'https://canonical.example.test/media',
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
const resolution = await InstanceSnapshotStore.resolve({input: 'https://gateway-alias.example.test', signal: null});
|
||||
expect(resolution.snapshot.gatewayEndpoint).toBe('wss://gateway-alias.example.test/gateway');
|
||||
expect(resolution.snapshot.mediaEndpoint).toBe('https://gateway-alias.example.test/media');
|
||||
expect(resolution.snapshot.staticCdnEndpoint).toBe('https://cdn.canonical.example.test');
|
||||
});
|
||||
|
||||
test('a host that is not marked as serving its own API still refuses a foreign API declaration', async () => {
|
||||
stubBrowserDocument('https://fluxer.com');
|
||||
vi.stubGlobal('fetch', serveLikeProduction('https://web.fluxer.app'));
|
||||
|
||||
@@ -1656,7 +1656,7 @@ Default `60000`. How often discovery is refetched, in milliseconds, and so the l
|
||||
|
||||
#### `FLUXER_APP_PROXY_SAME_ORIGIN_HOSTS`
|
||||
|
||||
Default empty. Hostnames, comma separated, without a scheme, port or path. When a request's `Host` is in the list, the page bootstrap points the web app and its API at that host, as `https://<host>` and `https://<host>/api`. Every other request gets the discovery values unchanged. Set it when the web app is served on more than one hostname and each hostname routes `/api` to the API. Invalid entries are logged and dropped.
|
||||
Default empty. Hostnames, comma separated, without a scheme, port or path. When a request's `Host` is in the list, the page bootstrap points the web app and its API at that host, as `https://<host>` and `https://<host>/api`. The Gateway, media, upload relay and static CDN endpoints move to that host too when discovery puts them on the same host as the API, so `wss://chat.example.com/gateway` becomes `wss://<host>/gateway`. Endpoints on other hosts stay as they are. Every other request gets the discovery values unchanged. Set it when the web app is served on more than one hostname and each hostname routes `/api`, `/gateway` and `/media` like the main one. Invalid entries are logged and dropped.
|
||||
|
||||
#### `FLUXER_APP_PROXY_MANIFEST_SCOPE_EXTENSIONS`
|
||||
|
||||
|
||||
@@ -329,17 +329,53 @@ function withEndpointsOnServingOrigin(url: string, document: InstanceDiscoveryDo
|
||||
return document;
|
||||
}
|
||||
const api = `${servedBy}${INSTANCE_DISCOVERY_CONVENTIONAL_ENDPOINT_PATH}`;
|
||||
const declaredApiHost = hostOfEndpoint(document.endpoints.api);
|
||||
const moveToServingOrigin = <T extends string | null>(endpoint: T): T =>
|
||||
(endpoint == null ? endpoint : (onServingOrigin(endpoint, declaredApiHost, servedBy) ?? endpoint)) as T;
|
||||
return {
|
||||
...document,
|
||||
endpoints: {
|
||||
...document.endpoints,
|
||||
api,
|
||||
api_client: document.endpoints.api_client == null ? null : api,
|
||||
gateway: moveToServingOrigin(document.endpoints.gateway),
|
||||
media: moveToServingOrigin(document.endpoints.media),
|
||||
upload_relay: moveToServingOrigin(document.endpoints.upload_relay),
|
||||
static_cdn: moveToServingOrigin(document.endpoints.static_cdn),
|
||||
webapp: document.endpoints.webapp == null ? null : servedBy,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function hostOfEndpoint(value: string): string | null {
|
||||
try {
|
||||
return new URL(value).host;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function onServingOrigin(endpoint: string, declaredApiHost: string | null, servedBy: string): string | null {
|
||||
if (declaredApiHost == null) {
|
||||
return null;
|
||||
}
|
||||
let url: URL;
|
||||
let serving: URL;
|
||||
try {
|
||||
url = new URL(endpoint);
|
||||
serving = new URL(servedBy);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
if (url.host !== declaredApiHost) {
|
||||
return null;
|
||||
}
|
||||
const secure = serving.protocol === 'https:';
|
||||
const socket = url.protocol === 'ws:' || url.protocol === 'wss:';
|
||||
const path = url.pathname === '/' && !endpoint.endsWith('/') ? '' : url.pathname;
|
||||
return `${socket ? (secure ? 'wss:' : 'ws:') : serving.protocol}//${serving.host}${path}${url.search}`;
|
||||
}
|
||||
|
||||
export function requireDiscoveryDeclaresItsOwnOrigin(url: string, document: InstanceDiscoveryDocument): void {
|
||||
const servedBy = originOfEndpoint(url);
|
||||
if (servedBy == null) {
|
||||
|
||||
Reference in new issue
Block a user