main
HIDDEN SPECTRE — server edition 👻
An augmented-reality ghost-hunting web app — a fan-made tribute to the AR play concept of the LEGO® Hidden Side™ app. This version adds a Node/Express backend with a secure admin panel for managing ghost GIFs and QR-based set recognition.
Fan-made. Not affiliated with, sponsored by, or endorsed by the LEGO Group. LEGO® and Hidden Side™ are trademarks of the LEGO Group. This project does not use LEGO logos or artwork.
What's here
- Game (
public/) — WebXR world-tracked AR with a camera+gyro fallback. Ghosts roam and you trap them by holding the reticle on them. Ghosts render as animated GIF billboards when a set is scanned, or as procedural wisps in free-hunt mode. - QR set recognition — scan a set's QR code; the game asks the API which ghosts that set unlocks and spawns them (weighted by rarity).
- Admin panel (
/admin) — JWT-protected login to upload/enable/disable/delete ghost GIFs and create/manage sets and their QR codes. - Backend (
server/) — Express 5, SQLite (better-sqlite3), bcrypt password hashing, JWT auth, image-only uploads via multer.
Architecture
Browser ──► nginx (HTTPS) ──► Node/Express (localhost:3000)
├─ / game (static)
├─ /admin admin UI (static)
├─ /api/login bcrypt + JWT
├─ /api/ghosts CRUD + GIF upload (auth)
├─ /api/sets CRUD + ghost links (auth)
├─ /api/scan/:code public — game reads roster
└─ /uploads/* served GIFs
└─ SQLite: server/data/spectre.db
Security notes
- Admin writes require a valid JWT (8h expiry). The public game only hits the read-only
/api/scan/:code. - Passwords are bcrypt-hashed (cost 12). The first admin is created by the seed script from
.env. JWT_SECRETmust be set or the server refuses to start.- Uploads are restricted to GIF/PNG/WebP, max 8 MB, stored with random filenames.
.env, the database, and uploaded files are git-ignored.
Deploy on Ubuntu behind nginx
1. Install Node + build tools
better-sqlite3 is a native module and needs a compiler toolchain to build:
sudo apt update
sudo apt install -y nodejs npm build-essential python3
# (or install Node 20+ from NodeSource for a newer runtime)
2. Get the code and install
git clone <this-repo-url> /opt/hidden-spectre
cd /opt/hidden-spectre
npm install # compiles better-sqlite3 — needs build-essential
3. Configure secrets
cp .env.example .env
# generate a strong secret:
node -e "console.log('JWT_SECRET=' + require('crypto').randomBytes(48).toString('hex'))" >> .env
# then edit .env to set ADMIN_USER / ADMIN_PASS (used once for seeding)
nano .env
4. Create the first admin
npm run seed # reads ADMIN_USER / ADMIN_PASS from .env
5. Run it as a service
Create /etc/systemd/system/hidden-spectre.service:
[Unit]
Description=Hidden Spectre server
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/hidden-spectre
ExecStart=/usr/bin/node server/index.js
Restart=on-failure
Environment=NODE_ENV=production
User=www-data
[Install]
WantedBy=multi-user.target
sudo chown -R www-data:www-data /opt/hidden-spectre
sudo systemctl daemon-reload
sudo systemctl enable --now hidden-spectre
6. Reverse-proxy with nginx
In your existing HTTPS server block:
server {
listen 443 ssl;
server_name spectre.hideawaygaming.com.au;
ssl_certificate /etc/letsencrypt/live/spectre.hideawaygaming.com.au/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/spectre.hideawaygaming.com.au/privkey.pem;
client_max_body_size 10M; # allow GIF uploads
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
sudo nginx -t && sudo systemctl reload nginx
HTTPS is required — the camera, gyro, and WebXR all refuse to run on an insecure origin.
Using it
- Go to
https://your-host/admin, log in, upload a few ghost GIFs. - Create a set with a code like
SET-GRAVEYARDand tick which ghosts it unlocks. - Make a QR code whose payload is exactly that code (any QR generator works) and print/display it.
- On a phone, open
https://your-host/, tap Scan a Set, scan the QR — those ghosts spawn. Or tap Free hunt for procedural ghosts with no set.
Asset tips for ghost GIFs
- Transparent background (GIF or, better, animated WebP/PNG for alpha) reads best floating in space.
- Roughly square or portrait framing; the billboard auto-fits the image aspect.
- Keep file size modest (a few hundred KB) so they load fast on mobile data.
.obj3D minifigure ghosts are a planned future mode; this version is GIF/image billboards.
Files
server/index.js Express app + all routes
server/db.js SQLite schema
server/auth.js JWT issue/verify
server/upload.js multer image upload config
server/seed.js first-admin creation
public/ the game + /admin panel
public/js/qr.js QR scanning (BarcodeDetector + manual fallback)
Description
Node/Express backend + admin panel for Hidden Spectre AR ghost hunt. GIF ghost management, JWT auth, QR-based set recognition. Fan-made tribute, not affiliated with the LEGO Group.
Languages
JavaScript
61.8%
CSS
26.6%
HTML
11.6%