mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-11-12 23:31:02 +00:00
refactor(server): move the identified Places request into a file of its own
The instance token and the request that carries the installation key leave trek-places.client.ts, which had grown past the 1000-line limit.
This commit is contained in:
@@ -12,9 +12,9 @@
|
||||
* refused key falls back to the shared pool. The failure modes stay the network
|
||||
* and the service being down, both of which fall back to what TREK did before.
|
||||
*/
|
||||
import { readEnv, getAppUrl } from '../../app-config';
|
||||
import { serviceIdentitySlot } from '../service-identity/service-identity.interface';
|
||||
import { readEnv } from '../../app-config';
|
||||
import { toApiLang } from './maps.helpers';
|
||||
import { identifiedFetch } from './trek-places.fetch';
|
||||
import {
|
||||
normalizePlaceWebsite,
|
||||
skiRouteBuildSchema,
|
||||
@@ -106,28 +106,6 @@ export interface TrekPlacesSearchResponse {
|
||||
results: TrekPlace[];
|
||||
}
|
||||
|
||||
/**
|
||||
* One opaque string per instance, so the service can rate-limit per caller
|
||||
* instead of per IP. Derived from the instance URL rather than generated and
|
||||
* stored: it must be stable across restarts, and it must not be anything that
|
||||
* identifies a person. It is never resolved back to anything on the far side.
|
||||
*/
|
||||
function instanceToken(): string {
|
||||
// getAppUrl(), not the raw APP_URL. The variable is commented out in the
|
||||
// shipped compose file, so reading it raw gave every default install the same
|
||||
// string — and a token every caller shares is not a caller. One install
|
||||
// running a large import would then rate-limit all the others, who would fall
|
||||
// back to Nominatim with nothing to point at. The resolver falls through to
|
||||
// ALLOWED_ORIGINS and then to the port, so an unconfigured instance still
|
||||
// differs from its neighbours.
|
||||
const url = getAppUrl();
|
||||
let hash = 0;
|
||||
for (let i = 0; i < url.length; i++) {
|
||||
hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0;
|
||||
}
|
||||
return `trek-${(hash >>> 0).toString(36)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop asking a service that is not answering.
|
||||
*
|
||||
@@ -255,26 +233,6 @@ function noteFailure(err: unknown, breaker: Breaker): void {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A request carrying who is asking: the instance token, and the installation
|
||||
* key where it may go. A refused key costs no answer: the request goes once
|
||||
* more without it, through the shared pool, while the identity counts the refusal.
|
||||
*/
|
||||
async function identifiedFetch(url: URL, headers: Record<string, string>, signal: AbortSignal): Promise<Response> {
|
||||
const identity = serviceIdentitySlot.get();
|
||||
const own = identity.headersFor(url);
|
||||
const base = { ...headers, 'X-TREK-Instance': instanceToken() };
|
||||
const res = await fetch(url, { signal, headers: { ...base, ...own } });
|
||||
identity.observe(url, res);
|
||||
if (res.status !== 401 || !own.Authorization) return res;
|
||||
await res.body?.cancel();
|
||||
return fetch(url, { signal, headers: { ...base, ...withoutKey(own) } });
|
||||
}
|
||||
|
||||
function withoutKey(headers: Record<string, string>): Record<string, string> {
|
||||
return Object.fromEntries(Object.entries(headers).filter(([name]) => name !== 'Authorization'));
|
||||
}
|
||||
|
||||
function statusError(status: number): Error & { status: number } {
|
||||
const err = new Error(`TREK Places API ${status}`) as Error & { status: number };
|
||||
err.status = status;
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { getAppUrl } from '../../app-config';
|
||||
import { serviceIdentitySlot } from '../service-identity/service-identity.interface';
|
||||
|
||||
/**
|
||||
* A request carrying who is asking: the instance token, and the installation
|
||||
* key where it may go. A refused key costs no answer: the request goes once
|
||||
* more without it, through the shared pool, while the identity counts the refusal.
|
||||
*/
|
||||
export async function identifiedFetch(
|
||||
url: URL,
|
||||
headers: Record<string, string>,
|
||||
signal: AbortSignal,
|
||||
): Promise<Response> {
|
||||
const identity = serviceIdentitySlot.get();
|
||||
const own = identity.headersFor(url);
|
||||
const base = { ...headers, 'X-TREK-Instance': instanceToken() };
|
||||
const res = await fetch(url, { signal, headers: { ...base, ...own } });
|
||||
identity.observe(url, res);
|
||||
if (res.status !== 401 || !own.Authorization) return res;
|
||||
await res.body?.cancel();
|
||||
return fetch(url, { signal, headers: { ...base, ...withoutKey(own) } });
|
||||
}
|
||||
|
||||
function withoutKey(headers: Record<string, string>): Record<string, string> {
|
||||
return Object.fromEntries(Object.entries(headers).filter(([name]) => name !== 'Authorization'));
|
||||
}
|
||||
|
||||
/**
|
||||
* One opaque string per instance, so the service can rate-limit per caller
|
||||
* instead of per IP. Derived from the instance URL rather than generated and
|
||||
* stored: it must be stable across restarts, and it must not be anything that
|
||||
* identifies a person. It is never resolved back to anything on the far side.
|
||||
*/
|
||||
function instanceToken(): string {
|
||||
// getAppUrl(), not the raw APP_URL. The variable is commented out in the
|
||||
// shipped compose file, so reading it raw gave every default install the same
|
||||
// string — and a token every caller shares is not a caller. One install
|
||||
// running a large import would then rate-limit all the others, who would fall
|
||||
// back to Nominatim with nothing to point at. The resolver falls through to
|
||||
// ALLOWED_ORIGINS and then to the port, so an unconfigured instance still
|
||||
// differs from its neighbours.
|
||||
const url = getAppUrl();
|
||||
let hash = 0;
|
||||
for (let i = 0; i < url.length; i++) {
|
||||
hash = ((hash << 5) - hash + url.charCodeAt(i)) | 0;
|
||||
}
|
||||
return `trek-${(hash >>> 0).toString(36)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user