{"record":{"id":"7e14268808cd13b0","repo":"laurent22/joplin","slug":"could-not-get-geolocation-from-any-of-the-services","errorCode":null,"errorMessage":"Could not get geolocation from any of the services","messagePattern":"Could not get geolocation from any of the services","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/lib/geolocation-node.ts","lineNumber":59,"sourceCode":"\t},\n\n};\n\nexport default class {\n\tpublic static async currentPosition(options: CurrentPositionOptions = null) {\n\t\tif (!options) options = {};\n\n\t\tfor (const [serviceName, handler] of Object.entries(geoipServices)) {\n\t\t\ttry {\n\t\t\t\tconst response = await handler();\n\t\t\t\treturn response;\n\t\t\t} catch (error) {\n\t\t\t\tlogger.warn(`Could not get geolocation from service \"${serviceName}\"`);\n\t\t\t\tlogger.warn(error);\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error('Could not get geolocation from any of the services');\n\t}\n}\n","sourceCodeStart":41,"sourceCodeEnd":62,"githubUrl":"https://github.com/laurent22/joplin/blob/2654b33620775080d1d59c552259d41e33dad3d2/packages/lib/geolocation-node.ts#L41-L62","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nconst pos = await GeolocationNode.currentPosition();\n\n// after\nlet pos;\ntry {\n  pos = await GeolocationNode.currentPosition();\n} catch (error) {\n  logger.warn('Geolocation unavailable, falling back to default', error);\n  pos = { timestamp: Date.now(), coords: { latitude: 0, longitude: 0, altitude: 0 } };\n}","handlingStrategy":"fallback","validationCode":"// Cheap pre-flight: only attempt geolocation if the host can reach the upstream.\n// Reuse the same transport the service uses.\nasync function canReachIpwhois(): Promise<boolean> {\n  try {\n    const r = await shim.fetch('https://ipwho.is/', { method: 'HEAD' });\n    return r.ok;\n  } catch {\n    return false;\n  }\n}\n\nif (!(await canReachIpwhois())) {\n  return defaultPosition();\n}\nreturn await GeolocationNode.currentPosition();","typeGuard":null,"tryCatchPattern":"// Geolocation is best-effort; never let it abort the surrounding flow.\nlet position: CurrentPositionResponse;\ntry {\n  position = await GeolocationNode.currentPosition();\n} catch (error) {\n  logger.warn('Geolocation lookup failed, using default coordinates', error);\n  position = {\n    timestamp: Date.now(),\n    coords: { latitude: 0, longitude: 0, altitude: 0 },\n  };\n}","preventionTips":["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."],"tags":["network","geolocation","geoip","external-service"],"backgroundTag":null,"analyzedSha":"2654b33620775080d1d59c552259d41e33dad3d2","analyzedAt":"2026-08-12T14:26:46.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}