mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 19:04:02 +00:00
skip Nominatim lookups when the server has none configured
Add GET /api/geocode/status ({configured}), like /api/overpass/status. The outline client and the last-resort place search ask it once per page and send no lookup a server without a Nominatim endpoint would refuse, so a default install no longer logs a 503 on its first named landmark or grounds ask. An older server or a failed probe leaves the capability unknown and lookups proceed as before.
This commit is contained in:
1 parent
24228b145e
commit
f32ab5446b
10 files changed
+157
-6
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { readResponseJsonCapped } from './httpBody.js';
|
||||
import { createNominatimStatus } from './nominatimStatus.js';
|
||||
|
||||
/**
|
||||
* Browser client for the server's guarded outline route. The server owns the
|
||||
@@ -61,6 +62,11 @@ export function createNominatimOutlineClient({
|
||||
fetchImpl = (...args) => globalThis.fetch(...args),
|
||||
endpoint = DEFAULT_ENDPOINT,
|
||||
maxRemembered = 64,
|
||||
// The app's own route can say up front that Nominatim is not configured;
|
||||
// another endpoint is simply asked.
|
||||
configured = endpoint === DEFAULT_ENDPOINT
|
||||
? createNominatimStatus({ fetchImpl })
|
||||
: null,
|
||||
} = {}) {
|
||||
let unavailable = null;
|
||||
const remembered = new Map();
|
||||
@@ -90,6 +96,15 @@ export function createNominatimOutlineClient({
|
||||
if (remembered.has(key)) return remembered.get(key);
|
||||
// A cancelled ask spends nothing.
|
||||
signal?.throwIfAborted();
|
||||
if (configured && (await configured()) === false) {
|
||||
signal?.throwIfAborted();
|
||||
unavailable = {
|
||||
unavailable: true,
|
||||
retryable: false,
|
||||
code: 'NOMINATIM_DISABLED',
|
||||
};
|
||||
return unavailable;
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const abort = () => controller.abort();
|
||||
|
||||
@@ -11,20 +11,30 @@ const ring = [
|
||||
[2.3, 48.8],
|
||||
];
|
||||
|
||||
function client(respond) {
|
||||
const reply = (status, body, headers = {}) =>
|
||||
new Response(JSON.stringify(body), { status, headers });
|
||||
|
||||
// `calls` counts outline requests only; the capability probe is answered by
|
||||
// `status` and defaults to an older server without it (capability unknown).
|
||||
function client(
|
||||
respond,
|
||||
{ status = () => reply(404, { error: 'Not Found' }) } = {},
|
||||
) {
|
||||
const calls = [];
|
||||
const probes = [];
|
||||
const lookup = createNominatimOutlineClient({
|
||||
fetchImpl: async (url) => {
|
||||
if (String(url) === '/api/geocode/status') {
|
||||
probes.push(String(url));
|
||||
return status(probes.length);
|
||||
}
|
||||
calls.push(String(url));
|
||||
return respond(calls.length);
|
||||
},
|
||||
}).lookup;
|
||||
return { calls, lookup };
|
||||
return { calls, probes, lookup };
|
||||
}
|
||||
|
||||
const reply = (status, body, headers = {}) =>
|
||||
new Response(JSON.stringify(body), { status, headers });
|
||||
|
||||
test('an accepted outline comes back with its polygons and is remembered', async () => {
|
||||
const hole = ring.map(([lon, lat]) => [
|
||||
2.35 + (lon - 2.35) / 2,
|
||||
@@ -139,3 +149,33 @@ test('a cancelled ask sends nothing', async () => {
|
||||
);
|
||||
assert.equal(calls.length, 0);
|
||||
});
|
||||
|
||||
test('a server that reports Nominatim unconfigured is never asked for outlines', async () => {
|
||||
const { calls, probes, lookup } = client(
|
||||
() => {
|
||||
throw new Error('the outline route must not be asked');
|
||||
},
|
||||
{ status: () => reply(200, { configured: false }) },
|
||||
);
|
||||
const first = await lookup({ query: 'Austin High School', kind: 'landmark' });
|
||||
assert.deepEqual(first, {
|
||||
unavailable: true,
|
||||
retryable: false,
|
||||
code: 'NOMINATIM_DISABLED',
|
||||
});
|
||||
const second = await lookup({ query: 'Paris', kind: 'city' });
|
||||
assert.equal(second.unavailable, true);
|
||||
assert.equal(calls.length, 0, 'no outline request at all');
|
||||
assert.equal(probes.length, 1, 'one capability probe per session');
|
||||
});
|
||||
|
||||
test('a configured server is asked as usual after one probe', async () => {
|
||||
const { calls, probes, lookup } = client(
|
||||
() => reply(200, { status: 'ZERO_RESULTS', outline: null }),
|
||||
{ status: () => reply(200, { configured: true }) },
|
||||
);
|
||||
assert.equal(await lookup({ query: 'Nowhere', kind: 'landmark' }), null);
|
||||
assert.equal(await lookup({ query: 'Elsewhere', kind: 'landmark' }), null);
|
||||
assert.equal(calls.length, 2);
|
||||
assert.equal(probes.length, 1);
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/**
|
||||
* Whether the app's server has a Nominatim endpoint configured, asked once per
|
||||
* page from `/api/geocode/status` (like `/api/overpass/status`). When it says
|
||||
* no, callers skip lookups the server could only refuse, so a default install
|
||||
* never asks and never logs a failed request. An older server without the
|
||||
* probe, a failed or timed-out probe and a malformed answer leave the
|
||||
* capability unknown (`null`): lookups then go to the server as before, and
|
||||
* the probe is retried after a short backoff.
|
||||
*
|
||||
* @returns {() => Promise<boolean|null>}
|
||||
*/
|
||||
export function createNominatimStatus({
|
||||
fetchImpl = (...args) => globalThis.fetch(...args),
|
||||
url = '/api/geocode/status',
|
||||
timeoutMs = 3000,
|
||||
retryMs = 30_000,
|
||||
now = Date.now,
|
||||
} = {}) {
|
||||
let decision = null;
|
||||
let retryAt = 0;
|
||||
return function configured() {
|
||||
if (decision) return decision;
|
||||
if (now() < retryAt) return Promise.resolve(null);
|
||||
decision = (async () => {
|
||||
try {
|
||||
const response = await fetchImpl(url, {
|
||||
signal: AbortSignal.timeout(timeoutMs),
|
||||
});
|
||||
const body = response.ok ? await response.json() : null;
|
||||
if (typeof body?.configured === 'boolean') return body.configured;
|
||||
} catch {
|
||||
// Unknown capability: ask the server as before.
|
||||
}
|
||||
decision = null;
|
||||
retryAt = now() + retryMs;
|
||||
return null;
|
||||
})();
|
||||
return decision;
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user