openvibers/openvibe.tools Commits

Route OpenChargeMap searches through shared egress (#7)

OP OpenVibers committed 3 Oct 2026 · 00f313b8f9 · 3 files, +80 −10

local checks ✓ 2/2 · CI ✓ 3/3 · review SHIP by Codex GPT-6 Sol

https://github.com/OpenVibers/OpenVibe.Tools/pull/7

Changes

ddiff --git a/apps/_shared/egress.js b/apps/_shared/egress.js
iindex e80d97f..dd9b870 100644
---- a/apps/_shared/egress.js
++++ b/apps/_shared/egress.js
@@@ -26,8 +26,10 @@ const https = require('https');
const path = require('path');
// The public-address rule is openvibe-shared/egress (the one Live and Events use too). apps/_shared has
--// no node_modules of its own: resolve it from the gateway, the only app that loads this file.
--const shared = require(require.resolve('openvibe-shared/egress', { paths: [path.join(__dirname, '..', 'gateway')] }));
++// no node_modules of its own: resolve it from either satellite that uses this adapter.
++const shared = require(require.resolve('openvibe-shared/egress', {
++ paths: [path.join(__dirname, '..', 'gateway'), path.join(__dirname, '..', 'maps')],
++}));
const { isPublicAddress, embeddedV4, normalizeHost, isInternalName } = shared;
class TargetRefused extends Error {
ddiff --git a/apps/maps/server/sources/openchargemap.js b/apps/maps/server/sources/openchargemap.js
iindex 40d1690..ba5ff80 100644
---- a/apps/maps/server/sources/openchargemap.js
++++ b/apps/maps/server/sources/openchargemap.js
@@@ -8,10 +8,11 @@
* Free & open — works without key but key gives higher rate limits
*/
--const axios = require('axios');
++const { createEgress } = require('../../../_shared/egress');
const { haversine } = require('./utils');
const BASE_URL = 'https://api.openchargemap.io/v3/poi';
++const egress = createEgress();
/**
* Search for EV charging stations near a location
@@@ -32,15 +33,19 @@ async function search(lat, lon, radiusMiles, apiKey) {
if (apiKey) params.key = apiKey;
-- const resp = await axios.get(BASE_URL, {
-- params,
-- timeout: 12000,
-- headers: { 'User-Agent': 'OpenVibeApp/2.0' },
++ const url = new URL(BASE_URL);
++ for (const [name, value] of Object.entries(params)) url.searchParams.set(name, value);
++ const resp = await egress.follow(url.href, {
++ timeoutMs: 12000,
++ maxBytes: 2 * 1024 * 1024,
++ headers: { 'User-Agent': 'OpenVibeApp/2.0', Accept: 'application/json' },
});
++ if (resp.status < 200 || resp.status >= 300) throw new Error(`HTTP ${resp.status}`);
++ if (resp.truncated) throw new Error('Response too large');
++ const data = JSON.parse(resp.body.toString('utf8'));
++ if (!Array.isArray(data)) return [];
-- if (!Array.isArray(resp.data)) return [];
--
-- return resp.data
++ return data
.filter(s => s.AddressInfo?.Latitude && s.AddressInfo?.Longitude)
.map(s => {
const addr = s.AddressInfo;
ddiff --git a/apps/maps/test/openchargemap-egress.test.js b/apps/maps/test/openchargemap-egress.test.js
nnew file mode 100644
iindex 0000000..445c6ce
---- /dev/null
++++ b/apps/maps/test/openchargemap-egress.test.js
@@@ -0,0 +1,63 @@
++'use strict';
++const assert = require('assert');
++const Module = require('module');
++
++const requests = [];
++let response = { status: 200, truncated: false, body: Buffer.from('[]') };
++const egress = {
++ follow: async (url, options) => {
++ requests.push({ url, options });
++ return response;
++ },
++};
++
++// Keep the upstream offline. utils loads axios for other sources, but this source must use egress.
++const originalLoad = Module._load;
++Module._load = function (request, parent, isMain) {
++ if (request === '../../../_shared/egress') return { createEgress: () => egress };
++ if (request === 'axios') return {};
++ return originalLoad.call(this, request, parent, isMain);
++};
++let openchargemap;
++try {
++ openchargemap = require('../server/sources/openchargemap');
++} finally {
++ Module._load = originalLoad;
++}
++
++(async () => {
++ const station = {
++ ID: 42,
++ AddressInfo: { Title: 'Test charger', Latitude: 47.61, Longitude: -122.33 },
++ UsageCost: 'Free',
++ };
++ response = { status: 200, truncated: false, body: Buffer.from(JSON.stringify([station])) };
++ const key = 'sentinel-ocm-key-not-a-secret';
++ const configured = await openchargemap.search(47.61, -122.33, 25, key);
++ assert.equal(requests.length, 1);
++ const url = new URL(requests[0].url);
++ assert.equal(url.origin, 'https://api.openchargemap.io');
++ assert.equal(url.pathname, '/v3/poi');
++ assert.equal(url.searchParams.get('key'), key);
++ assert.equal(url.searchParams.get('latitude'), '47.61');
++ assert.equal(url.searchParams.get('longitude'), '-122.33');
++ assert.equal(url.searchParams.get('distance'), '25');
++ assert.equal(url.searchParams.get('maxresults'), '50');
++ assert.equal(requests[0].options.timeoutMs, 12000);
++ assert.ok(requests[0].options.maxBytes > 0);
++ assert.equal(requests[0].options.headers['User-Agent'], 'OpenVibeApp/2.0');
++ assert.equal(configured[0].id, 'ocm-42');
++ assert.ok(!JSON.stringify(configured).includes(key));
++
++ requests.length = 0;
++ const unconfigured = await openchargemap.search(47.61, -122.33, 25, '');
++ assert.equal(requests.length, 1);
++ assert.equal(new URL(requests[0].url).searchParams.has('key'), false);
++ assert.equal(unconfigured[0].id, 'ocm-42');
++
++ response = { status: 503, truncated: false, body: Buffer.from('[]') };
++ assert.deepEqual(await openchargemap.search(47.61, -122.33, 25, key), []);
++ response = { status: 200, truncated: true, body: Buffer.from('[]') };
++ assert.deepEqual(await openchargemap.search(47.61, -122.33, 25, key), []);
++ console.log('OpenChargeMap egress: configured, unconfigured, and failed responses passed');
++})().catch(err => { console.error(err); process.exitCode = 1; });