const express = require('express'); const auth = require('express-basic-auth'); const { createHash } = require('crypto'); const app = express(); const port = 8355; const geoJsonTemplate = { type: "FeatureCollection", features: [], "marker-symbol-images": { "star": "data:image/svg+xml;utf8,", "star-stroked": "data:image/svg+xml;utf8," } }; const hazeTranslator = { category: [ "Urbex", "Nature", "Spontaneous" ], difficulty: [ "Not Sure", "Baby", "Walk in the Park", "Challenge", "Madman", "Deathwish" ], rating: [ "Not Sure", "Check it Out", "Worth the Trip", "Must See" ], level: [ "Lost Soul", "Roamer", "Adventurer", "Pathfinder", "Trailblazer", "Maven" ] }; app.use(auth({ authorizer: (username, password) => { return true; } })); 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; let hazeSocketCounter = 0; let requestStartTime = new Date().getTime(); if (req.query.auth) { let paramAuth = Buffer.from(req.query.auth, 'base64').toString('utf-8'); hazeUser = 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'); let locations = []; 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": hazeSocketCounter++ })); 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": ' + hazeSocketCounter++ + '}'); } 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) { // locations = data['body']['locations'].map(location => { // return { // id: location['id'], // title: location['name'], // completed: false // }; // }); data['body']['locations'].forEach(location => { locations[hazeSocketCounter++] = { id: location['id'], title: location['name'], latitude: location['coordinates']['latitude'], longitude: location['coordinates']['longitude'], 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({ event: "getLocation_request", body: locations.map(location => location.id), socketMessageId: index })); } }); } else { console.dir(data); res.status(500).send('Failed to retrieve locations'); haze.close(); res.end(); } break; case "getLocation_response": if (data['body']['response'] === 1) { locations[data['socketMessageId']] = { ...locations[data['socketMessageId']], completed: true, 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 comments: data['body']['location']['locationCommentsAndCommunityVotes'].map(comment => ({ id: comment['id'], text: comment['comment'], rating: (comment['upvotes']*1)+(comment['downvotes']* -1), 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(); } 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}`); });