openvibers/openvibe.tools Commits
Route OpenChargeMap searches through shared egress (#7)
Changes
@@@ -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 {@@@ -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;nnew file mode 100644@@@ -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; });
@@ -26,8 +26,10 @@ const https = require('https');
@@ -26,8 +26,10 @@ const https = require('https');
const path = require('path');
const path = require('path');
// The public-address rule is openvibe-shared/egress (the one Live and Events use too). apps/_shared has
// 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;
const { isPublicAddress, embeddedV4, normalizeHost, isInternalName } = shared;
class TargetRefused extends Error {
class TargetRefused extends Error {
@@ -8,10 +8,11 @@
@@ -8,10 +8,11 @@
* Free & open — works without key but key gives higher rate limits
* 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 { haversine } = require('./utils');
const BASE_URL = 'https://api.openchargemap.io/v3/poi';
const BASE_URL = 'https://api.openchargemap.io/v3/poi';
+const egress = createEgress();
/**
/**
* Search for EV charging stations near a location
* Search for EV charging stations near a location
@@ -32,15 +33,19 @@ async function search(lat, lon, radiusMiles, apiKey) {
@@ -32,15 +33,19 @@ async function search(lat, lon, radiusMiles, apiKey) {
if (apiKey) params.key = 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)
.filter(s => s.AddressInfo?.Latitude && s.AddressInfo?.Longitude)
.map(s => {
.map(s => {
const addr = s.AddressInfo;
const addr = s.AddressInfo;
new file mode 100644
new file mode 100644
@@ -0,0 +1,63 @@
@@ -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; });