fuzzyworld

north-star / commit

reworked the data format to make it more compact

ec059e0379e1488b2c84419b3046c4eb9f709eeb · Pavel Safronov · 2026-03-15 09:43:23 -0700 · parents 1909c7ef6d74

reworked the data format to make it more compact
 readme.md     | 42 ++++++++++++++++++++++++++----------------
 src/app.js    | 31 +++++++++++++++++++------------
 src/style.css |  7 ++++---
 3 files changed, 49 insertions(+), 31 deletions(-)

diff --git a/readme.md b/readme.md
index 3b2292e..e5b3b5c 100644
--- a/readme.md
+++ b/readme.md
@@ -6,18 +6,39 @@ North Star is a web application that allows users to share their location in rea
 
 ## How to use it?
 
-1. Open the application in your browser.
+1. Open `https://pavelsafronov.gitlab.io/north-star/`
 2. You're in a room now.
-3. Share the room link with your friends.
+3. Share the room QR code or the room link with your friends so they can join the same room.
 4. Everyone who joins the room will be able to see each other's location on the map.
 5. Everyone can set a custom name and color for their marker.
-6. The app has a control panel where you can disable participants and click on a participant's name to open their location in Google Maps.
+6. You can disable participants or click on a participant's name to open their location in Google Maps.
+7. The North participant is a special marker that always points to the north, helping you orient yourself on the map.
+8. If North isn't pointing to true north, orient your phone so "up" on the screen is facing north, and then click the "Calibrate North" button. This will set the current orientation as north.
+9. A mis-aligned north is only a visual issue and does not affect the actual location sharing functionality.
 
 ## How does it work?
 
 The app uses Firebase Realtime Database to store and sync the location data of all participants in real-time. When a user joins a room, they are added to the database with their location and other information. The app listens for changes in the database and updates the map accordingly.
 
-## Firebase database rules
+## Is it secure?
+
+The app does not require any authentication, so anyone with the room information can join and see the location of all participants.
+
+The room QR code contains a URL with a unique room id and a unique encryption key. This means that even if someone were to gain access to the database (like myself, or a bad actor), they would not be able to read anyone's location data without the room link.
+
+It's important to share the room QR code/link only with trusted individuals to ensure your privacy.
+
+If your link gets leaked, you can simply create a new room and share the new QR code/link with your friends.
+
+## Links
+
+Repo: https://gitlab.com/PavelSafronov/north-star
+
+App: https://pavelsafronov.gitlab.io/north-star/
+
+## Dev stuff
+
+### Firebase database rules
 
 This repo includes Realtime Database security rules in [database.rules.json](database.rules.json).
 
@@ -30,15 +51,7 @@ Before sharing the app publicly, add these rules in Firebase Console:
 
 Without these rules, your database may allow unsafe writes.
 
-## Is it secure?
-
-The app does not require any authentication, so anyone with the room link can join and see the location of all participants.
-
-The room link contains a unique room id (so it's hard to guess), but also an encryption key that is used to encrypt the location data before storing it in the database. This means that even if someone were to gain access to the database (like myself, or a bad actor), they would not be able to read the location data without the encryption key.
-
-It's important to share the room link only with trusted individuals to ensure your privacy.
-
-## Is it open source?
+### Is it open source?
 
 You're looking at it.
 
@@ -46,6 +59,3 @@ You're looking at it.
 
 This project is licensed under the MIT License.
 
-## Links
-
-Repo: https://gitlab.com/PavelSafronov/north-star
diff --git a/src/app.js b/src/app.js
index 1eb135d..04b83d9 100644
--- a/src/app.js
+++ b/src/app.js
@@ -152,15 +152,23 @@ console.debug('User ID:', uid);
 
 const BASE_ROOM_URL = `${window.location.origin}${window.location.pathname}`;
 const ROOM_ID_LENGTH = 8;
-const ROOM_ID_PATTERN = new RegExp(`^[a-z0-9]{${ROOM_ID_LENGTH}}$`);
+const ROOM_KEY_PATTERN = /^[A-Za-z0-9_-]+$/;
 
-// Parse both room ID and optional encryption key from the URL hash.
+const buildRoomHash = (nextRoomId, nextRoomKeyBase64 = null) => {
+    const keyPart = nextRoomKeyBase64 ? `.${nextRoomKeyBase64}` : '';
+    return `${nextRoomId}${keyPart}`;
+};
+
+// Parse room ID and optional encryption key from compact hash format: #<roomId>[.<roomKeyBase64>]
 const parseRoomFromHash = (hashValue) => {
     const normalizedHash = (hashValue || '').startsWith('#') ? hashValue.slice(1) : (hashValue || '');
-    const hashParams = new URLSearchParams(normalizedHash);
-    const roomValue = (hashParams.get('room') || '').trim();
-    if (!ROOM_ID_PATTERN.test(roomValue)) return null;
-    const keyValue = (hashParams.get('key') || '').trim() || null;
+
+    const compactMatch = normalizedHash.match(/^([a-z0-9]{8})(?:\.([A-Za-z0-9_-]+))?$/);
+    if (!compactMatch) return null;
+
+    const roomValue = compactMatch[1];
+    const keyValue = compactMatch[2] || null;
+    if (keyValue && !ROOM_KEY_PATTERN.test(keyValue)) return null;
     return { roomId: roomValue, roomKeyBase64: keyValue };
 };
 
@@ -184,7 +192,7 @@ if (parsedRoom) {
     roomId = crypto.randomUUID().replace(/-/g, '').slice(0, ROOM_ID_LENGTH);
     roomKey = await generateRoomKey();
     roomKeyBase64 = await exportRoomKey(roomKey);
-    location.hash = `room=${roomId}&key=${roomKeyBase64}`;
+    location.hash = buildRoomHash(roomId, roomKeyBase64);
 }
 console.debug('Room ID:', roomId, '| Encrypted:', roomKey !== null);
 
@@ -192,8 +200,7 @@ if (roomCurrent) {
     roomCurrent.textContent = `You are currently in room ${roomId}`;
 }
 
-const keyFragment = roomKeyBase64 ? `&key=${encodeURIComponent(roomKeyBase64)}` : '';
-const inviteRoomUrl = `${BASE_ROOM_URL}#room=${encodeURIComponent(roomId)}${keyFragment}`;
+const inviteRoomUrl = `${BASE_ROOM_URL}#${buildRoomHash(roomId, roomKeyBase64)}`;
 if (createRoomLink) {
     createRoomLink.href = BASE_ROOM_URL;
 }
@@ -201,9 +208,9 @@ if (createRoomLink) {
 if (inviteRoomQr) {
     try {
         const qrDataUrl = await QRCode.toDataURL(inviteRoomUrl, {
-            width: 280,
-            margin: 1,
-            errorCorrectionLevel: 'M',
+            width: 400,
+            margin: 4,
+            errorCorrectionLevel: 'H',
             color: {
                 dark: '#0f172a',
                 light: '#ffffff'
diff --git a/src/style.css b/src/style.css
index 69c7ab3..27ffb4f 100644
--- a/src/style.css
+++ b/src/style.css
@@ -105,11 +105,12 @@ a {
 }
 
 .room-qr {
-    width: 140px;
-    height: 140px;
+    width: 200px;
+    height: 200px;
     display: block;
     background: #ffffff;
-    border-radius: 8px;
+    border-radius: 0;
+    image-rendering: pixelated;
 }
 
 .room-links span {