Controllo accessi. Scanna un QR all'ingresso della tessera dematerializzata; controlla col database la sua validità e in tal caso se la tessera è già entrata.
  • JavaScript 75.2%
  • CSS 19%
  • HTML 5.8%
Find a file
2026-10-24 02:32:09 +02:00
api Primo abbozzo 2026-10-24 02:32:09 +02:00
app Primo abbozzo 2026-10-24 02:32:09 +02:00
docs Primo abbozzo 2026-10-24 02:32:09 +02:00
.gitignore Primo abbozzo 2026-10-24 02:32:09 +02:00
README.md Primo abbozzo 2026-10-24 02:32:09 +02:00

Accessi

PWA di controllo accessi con tessera a QR per La Corte dei Miracoli (Spazio Livi APS). Sottoprogetto del sito: vive qui dentro e ne eredita l'estetica, ma è un'applicazione a sé.

Il backend non esiste ancora. Le risposte arrivano da un finto server in app/src/api/mock.js. Serve a validare i flussi e l'interfaccia prima di decidere dove girerà il server vero.


Farla girare

Serve solo un server statico. Da docker-compose.yaml non passa niente: è un'app di file, non ha bisogno di PHP.

python3 -m http.server 5180 --directory accessi/app

Poi apri http://localhost:5180.

⚠️ Deve essere localhost, non l'indirizzo IP della macchina. La fotocamera (getUserMedia) e il service worker funzionano solo in un "contesto sicuro": HTTPS, oppure localhost. Su http://192.168.1.x:5180 il browser non chiede nemmeno il permesso, si limita a non far funzionare niente. È la causa numero uno di "non va".

Per provare da un telefono vero sulla rete di casa serve un tunnel HTTPS (cloudflared tunnel --url http://localhost:5180, o simili).

Tessere di prova: dalle Impostazioni, in fondo → Tessere di prova da inquadrare, oppure direttamente http://localhost:5180/dev/qr-demo.html. Sono QR veri: si inquadrano da un altro schermo, o si stampano.


Che cosa c'è dentro

accessi/
├── docs/note-di-progetto.md      decisioni, cose aperte, idee parcheggiate
├── api/                          il backend, quando ci sarà
│   └── db/migrazioni/001_init.sql  lo schema, già concordato
└── app/                          la PWA
    ├── index.html · manifest.webmanifest · service-worker.js
    ├── icone/
    ├── vendor/                   librerie incluse nel pacchetto, non da CDN
    ├── dev/qr-demo.html          QR di prova (solo sviluppo)
    └── src/
        ├── main.js               rotte
        ├── registrazione.js      ⭐ il cuore: cosa succede a codice letto
        ├── stato.js              preferenze del dispositivo
        ├── api/                  client.js (unico aggancio) + mock.js (finto)
        ├── scanner/              fotocamera · decoder · ciclo di lettura
        ├── offline/              IndexedDB · coda · sincronizzazione
        ├── schermate/            una per rotta
        ├── componenti/esito.js   l'esito a tutto schermo
        └── stili/                tokens.css (copiati dal tema) + app.css

Niente build, niente npm install, niente framework. Sono moduli ES nativi serviti così come sono. Quattro schermate non giustificano una catena di compilazione, e ogni dipendenza in più è una cosa che fra due anni va aggiornata.


I tre flussi

Chi scansiona Chi viene scansionato Come è classificato
Verifica ingresso operatore la tessera del socio origine=operatore
Accesso evento (ven/sab) operatore la tessera del socio tipo=evento
Automisurazione A il socio, dal proprio telefono il cartello al muro origine=self
Automisurazione B chi sta al bar la tessera del socio origine=bar

Il "tipo" lo decide il server, guardando il calendario: venerdì e sabato l'accesso conta come evento, gli altri giorni come automisurazione. Il telefono non lo sa e non deve saperlo — un orologio sbagliato non deve poter falsare le statistiche.

Le due modalità di automisurazione non sono un dettaglio tecnico: chi ha lo smartphone in mano si registra da solo, chi non ce l'ha (o non ha voglia) chiede al banco. Vedi docs/note-di-progetto.md.


Il QR

Nel QR della tessera non c'è il numero di tessera: c'è un token opaco di 128 bit casuali.

CDM1:CCJX5WT5QMETG9HR386E1CN7XS
└┬─┘ └────────────┬────────────┘
 │                └ 128 bit casuali, base32 senza I/L/O/U
 └ prefisso + versione: fa scartare all'istante un QR estraneo, offline

Il numero (SL-2026-0142) resta stampato in chiaro sul cartoncino, per gli umani. Tessera persa → si revoca il token e se ne emette uno nuovo: stesso socio, stesso numero, storico intatto.

Il QR di luogo invece è un URL (…/#/qui/LUOGO-XV958HEJYEEQ), perché va inquadrato con la fotocamera di sistema e il telefono deve saper aprire qualcosa.


Lettura del QR: due strade

  1. BarcodeDetector, nativa del browser — veloce, zero download. C'è su Chrome/Edge Android e desktop.
  2. zxing-wasm, la riserva per Safari e iOS, dove la prima non esiste.

Il .wasm è incluso nel pacchetto (app/vendor/), non preso da una CDN: altrimenti al primo avvio senza rete il lettore non partirebbe. Misurato: ~60 ms per fotogramma, più che sufficiente a 10 letture al secondo.

Dalle Impostazioni si può forzare la strada 2 anche su Chrome, per provare il ramo che serve a iOS senza procurarsi un iPhone.


Offline

Senza rete l'app non si blocca:

  1. la scansione entra in coda (IndexedDB) prima di provare a partire — se l'app viene chiusa a metà invio, non si perde;
  2. all'operatore compare In sospeso (arancione), con quello che risulta dai dati locali: mai un verde che non possiamo garantire;
  3. quando la rete torna, la coda si svuota da sola, riusando la stessa chiave di idempotenza: anche se il primo invio era arrivato, l'accesso non viene contato due volte.

L'istantanea per il giudizio offline contiene solo impronte SHA-256 dei token e lo stato: niente nomi, niente token in chiaro. Se il telefono dell'operatore viene perso, da lì non si ricava né una tessera funzionante né l'elenco dei soci.


Quando ci sarà il backend

  1. Lo schema è già scritto: api/db/migrazioni/001_init.sql.
  2. Si implementa un client REST in app/src/api/rest.js con le stesse firme di mock.js (config, scan, miaTessera, istantanea, stats).
  3. Si cambia una riga in app/src/api/client.js.

Il resto dell'app non se ne accorge, e gli interruttori di prova nelle Impostazioni spariscono da soli.

Endpoint previsti:

POST /api/scan riceve token + origine + orario + chiave di idempotenza, decide il tipo, registra, risponde con l'esito
GET /api/config giorni evento, cooldown, luoghi
GET /api/stats accessi per intervallo di date, filtrabili per tipo
GET /api/me/tessera la tessera di chi è collegato
GET /api/istantanea impronte per il giudizio offline
CRUD tessere creazione, stato attiva/scaduta/bloccata, revoca token

In produzione serve HTTPS, non è negoziabile: senza, niente fotocamera e niente installazione.


Coerenza col sito

app/src/stili/tokens.css è una copia di wp/wp-content/themes/spazio-livi/assets/css/tokens.css. Se cambi i colori nel tema, ricopia il file: è l'unico punto di contatto fra il sito e la PWA.

Gli esiti usano i colori ambito invece di un verde/rosso generico: valida → --formazione, non valida → --sociale, in sospeso → --altre-attivita, già dentro → --eventi.