From 772415207e5a2de07f5a0fdf871b15348d7750dc Mon Sep 17 00:00:00 2001 From: Elizabeth Cray Date: Fri, 17 Jul 2026 21:17:18 -0400 Subject: [PATCH] Full Data Update --- README.md | 6 +- adapt.js | 50 +++++ basicauth_serve.js | 301 +++++++++++++++++++++------ package-lock.json | 500 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- serve.js | 99 --------- 6 files changed, 797 insertions(+), 163 deletions(-) create mode 100644 adapt.js delete mode 100644 serve.js diff --git a/README.md b/README.md index bb7bdab..cf63290 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ This was a quick and dirty project, and will probably get some updates slowly in ```bash npm i -node serve.js +node basicauth_serve.js ``` To use the endpoint, replace 'EMAIL' and 'PASSWORD' with your Haze Explr account details: @@ -23,6 +23,4 @@ Then, make a request to the endpoint with the resulting string as the value of t curl "http://localhost:3000/locations?auth=BASE64_ENCODED_STRING" ``` - -`basicauth_serve.js` does the same thing bus expects account details to be passed via basic HTTP auth, but does not work with QGIS. -This is from a limitation of express-basic-auth. +`adapt.js` converts url-passed auth credentials into the basicauth server, I will refactor these in the future to use a `.env` file and better coding standards. diff --git a/adapt.js b/adapt.js new file mode 100644 index 0000000..3bc74d8 --- /dev/null +++ b/adapt.js @@ -0,0 +1,50 @@ +// Adapt URL-passed auth into basic HTTP auth for QGIS compatibility. + +const express = require('express'); +const request = require('request'); + +const app = express(); +const port = 8356; + +const reqOpts = { + method: 'GET', + headers: { + Authorization: '' + }, + url: 'http://localhost:8355', + timeout: 1200000 // 20 minutes +} + +app.all('*', (req, res) => { + // Verify Inputs + if (!req.query.auth) { + res.status(401).send('Unauthorized'); + return; + } + let hazePwHash = null; + let hazeUser = null; + if (req.query.auth) { + request({ + ...reqOpts, + url: reqOpts.url + req.baseUrl, + headers: { + Authorization: `Basic ${req.query.auth}` + } + }, (error, response, body) => { + if (error) { + res.status(500).send('Internal Server Error'); + return; + } else if (response.statusCode !== 200) { + res.status(response.statusCode).send(body); + return; + } else { + res.setHeader('Content-Type', 'application/json'); + res.send(body); + } + }) + } +}); + +app.listen(port, () => { + console.log(`Server is running on http://localhost:${port}`); +}); \ No newline at end of file diff --git a/basicauth_serve.js b/basicauth_serve.js index 54730e1..896b984 100644 --- a/basicauth_serve.js +++ b/basicauth_serve.js @@ -1,4 +1,5 @@ const express = require('express'); +const WebSocket = require('ws'); const auth = require('express-basic-auth'); const { createHash } = require('crypto'); @@ -40,6 +41,58 @@ const hazeTranslator = { "Pathfinder", "Trailblazer", "Maven" + ], + tag: [ + "Vicious Dogs", + "Biohazard", + "Radioactive", + "Fence", + "Rats", + "Still Water", + "Barbed Wire", + "Fire", + "Flashlight Recommended", + "Climbing Rope Recommended", + "Boat", + "Electrical Hazard", + "Helmet Recommended", + "Boots Recommended", + "Falling Debris", + "Dust Mask Recommended", + "Asbestos", + "Extreme Temperature", + "Squatters", + "Risk of Violence or Crime", + "Emotional Distress", + "Landmines", + "Respirator Recommended", + "Active Security", + "No Trespassing Sign", + "Private Property Sign", + "Flood Risk", + "Locked", + "Dead Air", + "Unstable Structure", + "Black Mold", + "Ticks", + "Mosquitoes", + "Razor Wire", + "Fiberglass", + "Bears", + "Wolves", + "Wildlife", + "Broken Glass", + "Scorpions", + "Snakes", + "Venomous Spiders", + "Karens Present", + "Gloves Recommended", + "Boarded Up", + "Thorns", + "Dangerous Hights", + "Hazmat Suit Recommended", + "Heavy Overgrowth", + "Bats" ] }; app.use(auth({ @@ -48,13 +101,15 @@ app.use(auth({ } })); -app.all('/locations.geojson', (req, res) => { +// Assemble full Haze location data, can take a LONG time +app.all('/locations_full.geojson', (req, res) => { // Verify Inputs if ((!req.auth.user || !req.auth.password) && (!req.query.auth)) { res.status(401).send('Unauthorized'); return; } - // Query Haze API Websocket + + // Setup local variables for request let hazePwHash = null; let hazeUser = null; let hazeSocketCounter = 0; @@ -71,8 +126,64 @@ app.all('/locations.geojson', (req, res) => { hazePwHash = createHash('sha256').update(req.auth.password).digest('hex'); } } - const haze = new WebSocket('wss://do.ecven.com:8120/explr'); + const haze = new WebSocket('wss://do.ecven.com:8120/explr-mb'); let locations = []; + let failCounter = 0; + let queryComplete = false; + + // Socket Message Handlers, defined here to use the local variables above + const sendComplete = () => { + if (!queryComplete) { + queryComplete = true; + haze.close(); + let testTime = (new Date().getTime() - requestStartTime); + const countExported = locations.filter(location => location && location.completed).length; + console.log(`${(new Date().getTime() - requestStartTime)/1000}s | FAILS: ${failCounter} | TIMEOUT: ${testTime} | TOTAL LOCATIONS: ${locations.length} | EXPORTED: ${countExported} | TIMEDOUT: ${locations.length - countExported - failCounter}`); + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ + ...geoJsonTemplate, + features: locations.map(location => ({ + type: "Feature", + geometry: { + type: "Point", + coordinates: [location['longitude'], location['latitude']] + }, + properties: { + id: location['id'], + title: location['name'], + "marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star", + author: location['author'] || '', + explored: location['isExplored'] || false, + confirmed: location['isConfirmed'] || false, + category: location['category'] || '', + level: location['level'] || '', + rating: location['rating'] || '', + difficulty: location['difficulty'] || '', + comments: location['comments'] || [], + querySuccess: location['completed'] + } + })) + })); + } + } + const queryLocation = (index) => { + haze.send(locations[index]['requestQuery']); + setTimeout(() => { + if (locations[index]['completed'] === 0) { + console.log(`Timeout on ${locations[index]['id']}}`); + locations[index]['completed'] = -1; // mark as failed + failCounter++; + let nextIndex = locations.findIndex(location => location && location.completed === 0); + if (locations.every(location => location && location.completed !== 0) || nextIndex === -1) { + sendComplete(); + } else { + queryLocation(nextIndex); + } + } + }, 10000); + } + + // These are the socket event handlers, in order of expected use haze.addEventListener('message', (event) => { const data = JSON.parse(event.data); let haze_authToken = '', haze_username = ''; @@ -103,15 +214,9 @@ app.all('/locations.geojson', (req, res) => { break; case "getMyLocationsRequest_response": if (data['body']['response'] === 1) { - // locations = data['body']['locations'].map(location => { - // return { - // id: location['id'], - // title: location['name'], - // completed: false - // }; - // }); data['body']['locations'].forEach(location => { - locations[hazeSocketCounter++] = { + hazeSocketCounter++; + locations[hazeSocketCounter] = { id: location['id'], title: location['name'], latitude: location['coordinates']['latitude'], @@ -119,21 +224,17 @@ app.all('/locations.geojson', (req, res) => { isExplored: location['explored'] > 0, isConfirmed: location['isUnconfirmedLocation'] !== 1, category: hazeTranslator.category[location['locationCategory']], - completed: false - } - }); - - // need to make the socketIDs dynamic and handle completed status before returning final payload - // FIX: Currently throwing response 2 "User does not have access to location." - locations.forEach((location, index) => { - if (location) { - haze.send(JSON.stringify({ + rawBasicData: JSON.stringify(location), + requestQuery: JSON.stringify({ event: "getLocation_request", - body: locations.map(location => location.id), - socketMessageId: index - })); + body: [ location['id'] ], + socketMessageId: hazeSocketCounter + }), + completed: 0 } }); + queryLocation(locations.findIndex(location => location && location.completed === 0)); + } else { console.dir(data); res.status(500).send('Failed to retrieve locations'); @@ -143,14 +244,29 @@ app.all('/locations.geojson', (req, res) => { break; case "getLocation_response": if (data['body']['response'] === 1) { + let locationTags = {}; + data['body']['location']['locationRating']['ratingItemsList'].map(tag => { + locationTags[hazeTranslator.tag[tag['id']] ? hazeTranslator.tag[tag['id']] : tag['id']] = tag['count']; + }); locations[data['socketMessageId']] = { ...locations[data['socketMessageId']], - completed: true, + completed: 1, author: `${data['body']['location']['submittorUserDatas']['username']} (${data['body']['location']['submittorUserDatas']['email']})`, rating: hazeTranslator.rating[data['body']['location']['locationRating']['overallRating']], difficulty: hazeTranslator.difficulty[data['body']['location']['locationRating']['difficultyRating']], level: hazeTranslator.level[data['body']['location']['locationRating']['levelRating']], - // TODO: use body.location.locationRating.ratingItemsList for location tags + tags: locationTags, + images: [ + ... data['body']['location']['gallery'].map(image => ({ + id: image['id'], + mimetype: `image/${image['mediaType']}`, + author: `${image['uploaderData']['username']} (${image['uploaderData']['email']})`, + url: image['url'], + thumbnail: image['thumbnailUrl'], + likes: image['likes'], + date: image['uploadDate'], + })) + ], comments: data['body']['location']['locationCommentsAndCommunityVotes'].map(comment => ({ id: comment['id'], text: comment['comment'], @@ -158,43 +274,111 @@ app.all('/locations.geojson', (req, res) => { author: comment['submittor']['username'], })) }; - let testTime = (new Date().getTime() - requestStartTime) > 30000; // 30s timeout - if (locations.every(location => location && location.completed) || testTime) { - res.setHeader('Content-Type', 'application/json'); - haze.close(); - res.end(JSON.stringify({ - ...geoJsonTemplate, - features: locations.map(location => ({ - type: "Feature", - geometry: { - type: "Point", - coordinates: [location['longitude'], location['latitude']] - }, - properties: { - id: location['id'], - title: location['name'], - "marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star", - author: location['author'] || '', - explored: location['isExplored'] || false, - confirmed: location['isConfirmed'] || false, - category: location['category'] || '', - level: location['level'] || '', - rating: location['rating'] || '', - difficulty: location['difficulty'] || '', - comments: location['comments'] || [], - querySuccess: location['completed'] - } - })) - })); - } } else { - console.dir(data); - // res.status(500).send('Failed to retrieve locations'); - // haze.close(); - // res.end(); + console.log(`Failure on ${locations[data['socketMessageId']]['id']}: ${data['body']['responseDetails']}`); + locations[data['socketMessageId']]['completed'] = -1; // mark as failed + failCounter++; + } + // Completion Check and increment + let nextIndex = locations.findIndex(location => location && location.completed === 0); + if (locations.every(location => location && location.completed !== 0) || nextIndex === -1) { + sendComplete(); + } else { + queryLocation(nextIndex); } break; + case "ping": + console.log('Ping received from Haze API'); + break; + default: + console.log(JSON.stringify(data, null, 2)); + res.status(500).send('Unexpected response from Haze API'); + haze.close(); + res.end(); + } + }); +}); +// Original API, for only listing locations with minimal data +app.all('/locations.geojson', (req, res) => { + // Verify Inputs + if ((!req.auth.user || !req.auth.password) && (!req.query.auth)) { + res.status(401).send('Unauthorized'); + return; + } + // Query Haze API Websocket + let hazePwHash = null; + let hazeUser = null; + if (req.query.auth) { + let paramAuth = Buffer.from(req.query.auth, 'base64').toString('utf-8'); + hawUser = paramAuth.split(":")[0]; + hazePwHash = paramAuth.split(":")[1]; + } else { + hazeUser = req.auth.user; + if (/\b[A-Fa-f0-9]{64}\b/.test(req.auth.password)) { + hazePwHash = req.auth.password; + } else { + hazePwHash = createHash('sha256').update(req.auth.password).digest('hex'); + } + } + const haze = new WebSocket('wss://do.ecven.com:8120/explr'); + haze.addEventListener('message', (event) => { + const data = JSON.parse(event.data); + let haze_authToken = '', haze_username = ''; + switch (data['event']) { + case "connected": + haze.send(JSON.stringify({ + "event": "authenticateAccount_request", + "body": [ + { + "password": hazePwHash, + "email": hazeUser + } + ], + "socketMessageId": 0 + })); + break; + case "authenticateAccount_response": + if (data['body']['response'] === 1) { + haze_authToken = data['body']['authToken']; + haze_username = data['body']['username']; + haze.send('{"event": "getMyLocationsRequest_request","body": [{"higlightImages": true}],"socketMessageId": 0}'); + } else { + console.dir(req.query); + res.status(401).json({ ...data, password: req.auth.password }); + haze.close(); + res.end(); + } + break; + case "getMyLocationsRequest_response": + if (data['body']['response'] === 1) { + const hazeLocations = data['body']['locations'].map(location => { + return { + type: "Feature", + geometry: { + type: "Point", + coordinates: [location['coordinates']['longitude'], location['coordinates']['latitude']] + }, + properties: { + id: location['id'], + title: location['name'], + "marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star", + } + } + }); + res.setHeader('Content-Type', 'application/json'); + haze.close(); + res.end(JSON.stringify({ + ...geonJsonTemplate, + features: hazeLocations + })); + } else { + console.dir(data); + res.status(500).send('Failed to retrieve locations'); + haze.close(); + res.end(); + } + break; default: console.dir(data); res.status(500).send('Unexpected response from Haze API'); @@ -204,6 +388,7 @@ app.all('/locations.geojson', (req, res) => { }); }); + app.listen(port, () => { console.log(`Server is running on http://localhost:${port}`); }); diff --git a/package-lock.json b/package-lock.json index e31c99b..ab50c7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,9 @@ "dependencies": { "crypto": "^1.0.1", "express": "^5.2.1", - "express-basic-auth": "^1.2.1" + "express-basic-auth": "^1.2.1", + "request": "^2.88.2", + "ws": "^8.21.1" } }, "node_modules/accepts": { @@ -27,6 +29,61 @@ "node": ">= 0.6" } }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT" + }, "node_modules/basic-auth": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", @@ -39,6 +96,15 @@ "node": ">= 0.8" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/body-parser": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", @@ -101,6 +167,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -141,6 +225,12 @@ "node": ">=6.6.0" } }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT" + }, "node_modules/crypto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", @@ -148,6 +238,18 @@ "deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in.", "license": "ISC" }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -165,6 +267,15 @@ } } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -188,6 +299,16 @@ "node": ">= 0.4" } }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -300,6 +421,33 @@ "basic-auth": "^2.0.1" } }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT" + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT" + }, "node_modules/finalhandler": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", @@ -321,6 +469,50 @@ "url": "https://opencollective.com/express" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -385,6 +577,15 @@ "node": ">= 0.4" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -397,6 +598,29 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -441,6 +665,21 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/iconv-lite": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", @@ -478,6 +717,57 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT" + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC" + }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -548,6 +838,15 @@ "node": ">= 0.6" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "engines": { + "node": "*" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -600,6 +899,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -613,6 +918,27 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", + "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "funding": { + "url": "https://github.com/sponsors/lupomontero" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", @@ -652,6 +978,68 @@ "node": ">= 0.10" } }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.5.tgz", + "integrity": "sha512-mzR4sElr1bfCaPJe7m8ilJ6ZXdDaGoObcYR0ZHSsktM/Lt21MVHj5De30GQH2eiZ1qGRTO7LCAzQsUeXTNexWQ==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", @@ -803,6 +1191,31 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -821,6 +1234,37 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense" + }, "node_modules/type-is": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", @@ -844,6 +1288,25 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "license": "MIT", + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -853,11 +1316,46 @@ "node": ">= 0.8" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.1.tgz", + "integrity": "sha512-+0NTnW77fFN/DjQi6k/Sq/Yvk4Sgajw7urW8V+asjXnRgDs9gyGkdb7EzgfhA4goXsRIZKE28fzIXBHEzhuiWw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index dd7ce43..b7d8909 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "dependencies": { "crypto": "^1.0.1", "express": "^5.2.1", - "express-basic-auth": "^1.2.1" + "express-basic-auth": "^1.2.1", + "request": "^2.88.2", + "ws": "^8.21.1" } } diff --git a/serve.js b/serve.js deleted file mode 100644 index 279b8f8..0000000 --- a/serve.js +++ /dev/null @@ -1,99 +0,0 @@ -const express = require('express'); -const { createHash } = require('crypto'); - -const app = express(); -const port = 8356; -const geonJsonTemplate = { - type: "FeatureCollection", - features: [], - "marker-symbol-images": { - "star": "data:image/svg+xml;utf8,", - "star-stroked": "data:image/svg+xml;utf8," - } -}; - -app.all('/locations.geojson', (req, res) => { - // Verify Inputs - if (!req.query.auth) { - res.status(401).send('Unauthorized'); - return; - } - // Query Haze API Websocket - let hazePwHash = null; - let hazeUser = null; - if (req.query.auth) { - let paramAuth = Buffer.from(req.query.auth, 'base64').toString('utf-8').replaceAll('\n', ''); - hazeUser = paramAuth.split(":")[0]; - hazePwHash = paramAuth.split(":")[1]; - } - const haze = new WebSocket('wss://do.ecven.com:8120/explr'); - haze.addEventListener('message', (event) => { - const data = JSON.parse(event.data); - let haze_authToken = '', haze_username = ''; - switch (data['event']) { - case "connected": - let pld = { - "event": "authenticateAccount_request", - "body": [ - { - "password": hazePwHash, - "email": hazeUser - } - ], - "socketMessageId": 0 - }; - haze.send(JSON.stringify(pld)); - break; - case "authenticateAccount_response": - if (data['body']['response'] === 1) { - haze_authToken = data['body']['authToken']; - haze_username = data['body']['username']; - haze.send('{"event": "getMyLocationsRequest_request","body": [{"higlightImages": true}],"socketMessageId": 0}'); - } else { - console.dir(req.query); - res.status(401).json({ ...data, password: req.query.auth }); - haze.close(); - res.end(); - } - break; - case "getMyLocationsRequest_response": - if (data['body']['response'] === 1) { - const hazeLocations = data['body']['locations'].map(location => { - return { - type: "Feature", - geometry: { - type: "Point", - coordinates: [location['coordinates']['longitude'], location['coordinates']['latitude']] - }, - properties: { - id: location['id'], - title: location['name'], - "marker-symbol": location['isUnconfirmedLocation'] === 1 ? "star-stroked" : "star", - } - } - }); - res.setHeader('Content-Type', 'application/json'); - haze.close(); - res.end(JSON.stringify({ - ...geonJsonTemplate, - features: hazeLocations - })); - } else { - console.dir(data); - res.status(500).send('Failed to retrieve locations'); - haze.close(); - res.end(); - } - break; - default: - console.dir(data); - res.status(500).send('Unexpected response from Haze API'); - haze.close(); - res.end(); - } - }); -}); - -app.listen(port, () => { - console.log(`Server is running on http://localhost:${port}`); -});