laurent22/joplin · error · Error
Could not get geolocation from any of the services
Error message
Could not get geolocation from any of the services
What it means
Thrown by GeolocationNode.currentPosition after it iterates every registered GeoIP service (geoipServices map in packages/lib/geolocation-node.ts:27) and each one rejects. Today only the 'ipwhois' service is registered (a GET to https://ipwho.is/), so in practice this fires whenever that single call fails: the HTTP request errors, the response is not ok, or the JSON lacks the required latitude/longitude keys. The per-service failure is swallowed with a logger.warn, so this aggregate error is the only signal the caller receives.
Source
Thrown at packages/lib/geolocation-node.ts:59
},
};
export default class {
public static async currentPosition(options: CurrentPositionOptions = null) {
if (!options) options = {};
for (const [serviceName, handler] of Object.entries(geoipServices)) {
try {
const response = await handler();
return response;
} catch (error) {
logger.warn(`Could not get geolocation from service "${serviceName}"`);
logger.warn(error);
}
}
throw new Error('Could not get geolocation from any of the services');
}
}
View on GitHub (pinned to 2654b33620)
Solutions
- Confirm outbound connectivity to https://ipwho.is/ from the host (curl -sS https://ipwho.is/ | jq) and check the logged upstream warning printed just before this throw — it carries the real cause.
- If the host is permanently offline or behind a proxy, register an additional GeoipService in geoipServices or supply a local fallback rather than relying on the single ipwho.is entry.
- Verify shim.fetch is initialized (packages/lib/shim-init-node.ts) before currentPosition is called; an unset fetch will reject on every service.
- Make callers tolerate geolocation failure — catch the error and degrade to a default coordinate instead of treating it as fatal.
Example fix
// before
const pos = await GeolocationNode.currentPosition();
// after
let pos;
try {
pos = await GeolocationNode.currentPosition();
} catch (error) {
logger.warn('Geolocation unavailable, falling back to default', error);
pos = { timestamp: Date.now(), coords: { latitude: 0, longitude: 0, altitude: 0 } };
} Defensive patterns
Strategy: fallback
Validate before calling
// Cheap pre-flight: only attempt geolocation if the host can reach the upstream.
// Reuse the same transport the service uses.
async function canReachIpwhois(): Promise<boolean> {
try {
const r = await shim.fetch('https://ipwho.is/', { method: 'HEAD' });
return r.ok;
} catch {
return false;
}
}
if (!(await canReachIpwhois())) {
return defaultPosition();
}
return await GeolocationNode.currentPosition(); Try / catch
// Geolocation is best-effort; never let it abort the surrounding flow.
let position: CurrentPositionResponse;
try {
position = await GeolocationNode.currentPosition();
} catch (error) {
logger.warn('Geolocation lookup failed, using default coordinates', error);
position = {
timestamp: Date.now(),
coords: { latitude: 0, longitude: 0, altitude: 0 },
};
} Prevention
- Treat geolocation as optional: always provide a sensible default coordinate so callers never depend on the external call succeeding.
- Wire shim.fetch (packages/lib/shim-init-node.ts) before any geolocation use; an uninitialized fetch rejects for every service.
- Monitor the per-service logger.warn lines — they reveal upstream drift (status changes, missing latitude/longitude) before this aggregate error surfaces.
- If you control the deployment environment, ensure ipwho.is is reachable or register an additional GeoipService entry with a redundant provider.
When it happens
Trigger: Calling GeolocationNode.currentPosition() when: (1) the host has no outbound network, (2) https://ipwho.is/ returns a non-2xx status (response.ok is false -> 'Could not get geolocation: ...'), (3) the endpoint returns a body without 'latitude'/'longitude' (e.g. an error object like { success:false, error:{...} }), or (4) shim.fetch itself throws (proxy/DNS/TLS). Because ipwhois is the only entry, any single failure escalates straight to this throw.
Common situations: Offline or air-gapped CI runners; corporate proxies that block ipwho.is; ipwho.is being down or rate-limiting; the upstream API contract changing shape; a Node shim where shim.fetch was never wired (shim-init-node). Often surfaces as a startup/location-defaulting failure on desktop/CLI builds.
Related errors
- Cannot find "%s".
- Could not check for updates. The server rate limit has been
- Could not check for updates. Please try again later (Error $
- Could not download from ${modelUrl}: Error ${response.status
- fetchBlob: ${method} ${url}: ${error.toString()}
AI-assisted analysis of laurent22/joplin@2654b33620 (2026-08-12).
Data as JSON: /api/errors/7e14268808cd13b0.
Report an issue: GitHub.