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}`);
});