Skip to content

Commit

Permalink
feat: show user 5 closest queues to join
Browse files Browse the repository at this point in the history
to avoid issues with geolocation inaccuracy not letting them join closest queue
  • Loading branch information
barakplasma committed Aug 28, 2021
1 parent 4e00c68 commit 4bc1f81
Show file tree
Hide file tree
Showing 19 changed files with 194 additions and 71 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
node_modules/
*.pem
.env
test/screenshots/
test/screenshots/
*.rdb
2 changes: 1 addition & 1 deletion client/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ function currentUserDone() {

document.addEventListener("DOMContentLoaded", () => {
joinQueue('admin');
adminSocket.emit('join-queue', getQueue());
adminSocket.emit('join-queue', getQueueFromAddressOrCache());
refreshAdminPage();
});

Expand Down
Binary file added client/destination.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 39 additions & 9 deletions client/home.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,29 @@ function gotoPage(pageName, currentOpenLocationCode, password = null) {
location.href = goto.toString();
}

function redirectToQueuePage(q) {
gotoPage('queue', q);
}
document.addEventListener("DOMContentLoaded", async () => {
let q = await generateQueueFromLocation();
homeSocket.emit('get-closest-queues', q, (queues) => {
let tableRow = (queue, size = '?', distance = '?m') => `
<tr>
<td><a href="/queue.html?location=${queue}">Vaccine</a></td>
<td>${size}</td>
<td>${distance}</td>
</tr>
`;
let html = `
<table>
<thead>
<th>Name</th>
<th>In queue</th>
<th><img src="destination.png" height="30px" alt="Distance"></th>
</thead>
${queues.map(q => tableRow(q)).join('\n')}
</table>
`
updateHTML('#queues', html);
});
});

function redirectToAdminPage(q, password) {
gotoPage('admin', q, password);
Expand All @@ -32,12 +52,12 @@ function getLocation() {
function success(position) {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
const currentOpenLocationCode = OpenLocationCode.encode(latitude, longitude);
let queue = btoa(currentOpenLocationCode);
resolve(queue);
resolve([latitude,longitude].join(':'));
}

function error() {
// alert('Unable to retrieve your location');
updateHTML('#warning', '<strong>Unable to retrieve your location</strong>');
reject('Unable to retrieve your location');
}

Expand All @@ -46,12 +66,22 @@ function getLocation() {
})
}

function redirectToQueue() {
getLocation().then(redirectToQueuePage);
/**
* @param {string} latLonConcat
*/
function generateQueueFromLatLonConcat(latLonConcat) {
let [latitude, longitude] = latLonConcat.split(':').map(parseFloat);
const currentOpenLocationCode = OpenLocationCode.encode(latitude, longitude);
let queue = btoa(currentOpenLocationCode);
return queue;
}

function generateQueueFromLocation() {
return getLocation().then(generateQueueFromLatLonConcat);
}

function createQueue() {
getLocation().then((queue) => {
getLocation().then(generateQueueFromLatLonConcat).then((queue) => {
let password = btoa(window.crypto.getRandomValues(new Uint8Array(6)).toString());
homeSocket.emit('create-queue', queue, password, () => {
redirectToAdminPage(queue, password);
Expand Down
9 changes: 6 additions & 3 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,14 @@ <h1>HisoonNumber</h1>
<main>
<section>
<aside>
<button id="becomeAdmin" onclick="createQueue();">Create a <mark>new</mark> queue at my current
location</button>
<button id="becomeUser" onclick="redirectToQueue();">Join an existing queue at my current location</button>
<h3>Nearby Queues</h3>
<div id="warning"></div>
<div id="queues"></div>
</aside>
</section>
<section>
<button id="becomeAdmin" onclick="createQueue();">Create a <mark>new</mark> queue at my current location</button>
</section>
</main>
<footer><a href="https://barakplasma.github.io/in-person-queue/">About</a></footer>
<style>
Expand Down
7 changes: 2 additions & 5 deletions client/queue.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,11 @@ <h4>Message from your admin:</h4>
<aside>
<h3>Queue Position</h3>
<mark id="position-in-queue">Not yet in queue</mark>
</aside>
<details>
<summary>Details</summary>
<p id="queueLengthContainer" onclick="refreshQueueLength()">Queue Length: <span id="queueLengthCount">N/A</span>
</p>
<p id="userIdContainer">UserId: <span id="userId">N/A</span></p>
<p id="userIdContainer">Your User Id: <span id="userId">N/A</span></p>
<p id="locationContainer">Location: <a id="location">N/A</a></p>
</details>
</aside>
</section>
<section>
<p>
Expand Down
2 changes: 1 addition & 1 deletion client/queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ function displayQueueLength(msg) {
roomSocket.on('refresh-queue', displayQueueLength);

function joinQueue(type) {
return new Promise(resolve => roomSocket.emit('join-queue', getQueue(), type, resolve));
return new Promise(resolve => roomSocket.emit('join-queue', getQueueFromAddressOrCache(), type, resolve));
}
11 changes: 9 additions & 2 deletions client/sharedClientUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ let config = {
}[env]
};

function getQueue() {
function getQueueFromAddressOrCache() {
if (!queue) {
const location = urlSearchParams.get('location');
queue = location;
Expand All @@ -19,13 +19,20 @@ function getQueue() {
}

function displayLocation() {
const fixed = atob(getQueue()).replace(' ', '+');
const fixed = atob(getQueueFromAddressOrCache()).replace(' ', '+');
// protect against XSS or invalid locations
if(OpenLocationCode.isValid(fixed)) {
updateHTML('#location', `<a target="_blank" href="https://plus.codes/${fixed}">${fixed}</a>`);
}
}

function generateUserId() {
const distinguishableCharacters = 'CDEHKMPRTUWXY012458'.split('');
const lenDistinguishableCharacters = distinguishableCharacters.length;
const userId = crypto.getRandomValues(new Uint8ClampedArray(6)).reduce((acc, n) => acc + distinguishableCharacters[n % lenDistinguishableCharacters], "");
return userId;
}

function updateHTML(selector, value) {
document.querySelector(selector).innerHTML = value;
}
Binary file added client/sort-ascending.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 10 additions & 8 deletions client/user.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/// <reference types="globals.d.ts">

/**
* @type {string?}
*/
let userId;

const userSocket = io(urlSearchParams.has('location') ?
Expand Down Expand Up @@ -38,7 +41,7 @@ document.addEventListener("DOMContentLoaded", () => {
document.addEventListener("DOMContentLoaded", async () => {
if (hasUserId()) {
await new Promise((resolve) => {
userSocket.emit('join-queue', getQueue(), getUserId(), resolve);
userSocket.emit('join-queue', getQueueFromAddressOrCache(), getUserId(), resolve);
})
refreshQueue();
} else {
Expand All @@ -50,11 +53,7 @@ function getUserId() {
if (!userId) {
const urlUserId = urlSearchParams.get('userId');
if (!urlUserId) {
const distinguishableCharacters = 'CDEHKMPRTUWXY012458'.split('');
const lenDistinguishableCharacters = distinguishableCharacters.length;
userId = crypto.getRandomValues(new Uint8ClampedArray(6)).reduce((acc, n) => acc + distinguishableCharacters[n % lenDistinguishableCharacters], "");
urlSearchParams.set('userId', userId);
location.search = urlSearchParams.toString();
userId = generateUserId();
} else {
userId = urlUserId;
}
Expand All @@ -63,8 +62,11 @@ function getUserId() {
}

function addSelfToQueue() {
userSocket.emit('add-user', getQueue(), getUserId(), displayJoinedState);
let userId = getUserId();
userSocket.emit('add-user', getQueueFromAddressOrCache(), getUserId(), displayJoinedState);
roomSocket.emit('add-to-queue');
urlSearchParams.set('userId', userId);
location.search = urlSearchParams.toString();
}

roomSocket.on('refresh-queue', getMyPosition);
Expand Down Expand Up @@ -109,4 +111,4 @@ function displayMyPosition(msg) {
document.title = `Queue: ${displayPosition} - ${getUserId()}`;
}

userSocket.on('update-queue-position', displayMyPosition);
userSocket.on('update-queue-position', displayMyPosition);
13 changes: 13 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"start": "node ./server.js",
"front-end-dev": "nodemon --inspect ./server.js",
"back-end-dev": "nodemon --inspect --ignore client/ ./server.js",
"test": "jest --testPathPattern queue.test.js",
"test:e2e": "jest --testPathPattern e2e.test.js",
Expand All @@ -27,14 +28,19 @@
"dotenv": "^9.0.2",
"express": "^4.17.1",
"ioredis": "^4.27.8",
"socket.io": "^3.1.2"
"socket.io": "^3.1.2",
"open-location-code": "git+ssh://[email protected]:google/open-location-code.git"
},
"devDependencies": {
"@types/ioredis": "^4.27.1",
"@types/jest": "^27.0.1",
"jest": "^27.0.6",
"nodemon": "^2.0.12",
"playwright": "^1.14.1"
},
"jest": {
"globalTeardown": "./test/e2e/teardown.js"
},
"browserslist": [
"defaults",
"not IE 11",
Expand Down
12 changes: 12 additions & 0 deletions queue/queue.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
const redisLib = require('ioredis');
const REDIS_HOST = process.env.REDIS_HOST || "localhost:6379";
const OpenLocationCode = require('open-location-code/js/src/openlocationcode');

const DEFAULT_EXPIRATION = 86400;

const redis = new redisLib(process.env.NODE_ENV == "production" ? process.env.FLY_REDIS_CACHE_URL : `redis://${REDIS_HOST}`);

Expand Down Expand Up @@ -28,6 +31,8 @@ async function removeUserFromQueue(queue, userId) {

async function createQueue(queue, password) {
await updateQueueMetadata({ queue, password });
let { latitudeCenter = 0, longitudeCenter = 0 } = OpenLocationCode.decode(queue.split(':')[1]);
await redis.geoadd('queues', longitudeCenter, latitudeCenter, queue);
return await redis.zadd(queue, [1, 'Start Queue'])
.then(_ => {
console.log({ EventName: 'created queue', queue });
Expand All @@ -44,6 +49,12 @@ async function getPosition(queue, userId) {
return position;
}

async function getClosestQueues(plusCode) {
let { latitudeCenter = 0, longitudeCenter = 0 } = OpenLocationCode.decode(Buffer.from(plusCode, 'base64').toString());
const closestQueues = await redis.geosearch("queues", "FROMLONLAT", longitudeCenter, latitudeCenter, "BYRADIUS", 1000, 'km', "COUNT", 5, "ASC");
return closestQueues.map(q => Buffer.from(q.split(':')[1], 'utf-8').toString('base64'));
}

async function getQueueLength(queue) {
const queueLength = await redis.zcard(queue);
return queueLength;
Expand Down Expand Up @@ -90,5 +101,6 @@ module.exports = {
checkAuthForQueue,
updateQueueMetadata,
getQueueMetadata,
getClosestQueues,
_redis: redis,
}
20 changes: 15 additions & 5 deletions socket/connection.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { addUserToQueue, removeUserFromQueue, createQueue, getPosition, getQueueLength, getHeadOfQueue, shiftQueue, checkAuthForQueue, updateQueueMetadata, getQueueMetadata } = require('../queue/queue');
const { addUserToQueue, removeUserFromQueue, createQueue, getPosition, getQueueLength, getHeadOfQueue, shiftQueue, checkAuthForQueue, updateQueueMetadata, getQueueMetadata, getClosestQueues } = require('../queue/queue');
const { Server } = require('socket.io');

function decodeQueue(queue) {
Expand All @@ -18,6 +18,11 @@ module.exports.connection = function (server) {
await createQueue(decodeQueue(queue), password);
ack();
});

socket.on('get-closest-queues', async (queue, ack) => {
const closestQueues = await getClosestQueues(queue);
ack(closestQueues)
});
})

const roomNamespace = io.of('/room');
Expand All @@ -35,10 +40,15 @@ module.exports.connection = function (server) {
}

roomSocket.on('join-queue', async (queue, type, ack) => {
queueCache = decodeQueue(queue);
roomSocket.join(queueCache);
ack();
log('person joined', { type });
try {
queueCache = decodeQueue(queue);
roomSocket.join(queueCache);
ack();
log('person joined', { type });
} catch (error) {
console.info(queue, type);
console.error(error);
}
})

async function refreshQueue() {
Expand Down
2 changes: 1 addition & 1 deletion test/e2e/admin.e2e.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ describe('Admin page', () => {

await context.setGeolocation({ latitude: 59.95 + Math.random(), longitude: 30.31667 });
// Click 'Create a new queue at my current location']
await page.click('//button[normalize-space(.)=\'Create a new queue at my current location\']');
await page.click('#becomeAdmin');
})

afterAll(async () => {
Expand Down
Loading

0 comments on commit 4bc1f81

Please sign in to comment.