Add files via upload
This commit is contained in:
1 parent
2ce0531216
commit
fbd97dc9d5
8 files changed
+4617
-4943
No files matched your search
@@ -1,404 +1,70 @@
|
|||||||
# VBB Netz Status
|
# VBB Netz Status - Getrennte Dateien
|
||||||
|
|
||||||
> **Live-Nahverkehr für Berlin & Brandenburg**
|
## 📁 Dateistruktur
|
||||||
> Eine Progressive Web App (PWA) für Echtzeit-Abfahrten, Routenplanung und Live-Fahrzeugverfolgung im VBB-Gebiet
|
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
Die App wurde jetzt in separate Dateien aufgeteilt:
|
||||||
[](https://github.com/Serverlele30/VBB-Status-Web-App)
|
|
||||||
|
|
||||||
**Live-Demo:** [VBB-Status-Web-App](https://github.com/Serverlele30/VBB-Status-Web-App)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 Features
|
|
||||||
|
|
||||||
### 🚇 **Echtzeit-Abfahrten**
|
|
||||||
- **Live-Daten** für alle Stationen im VBB-Netz (Berlin + Brandenburg)
|
|
||||||
- **GPS-Standort** - Automatische Stationserkennung
|
|
||||||
- **Intelligente Suche** mit Autocomplete
|
|
||||||
- **Detaillierte Infos** - Verspätungen, Ausfälle, Gleis/Steig
|
|
||||||
- **Auto-Refresh** - Alle 30 Sekunden (optional)
|
|
||||||
- **Offline-Modus** - Gespeicherte Daten anzeigen
|
|
||||||
|
|
||||||
### 🗺️ **Intelligenter Routenplaner**
|
|
||||||
- **Start/Ziel-Suche** mit GPS-Unterstützung
|
|
||||||
- **5 Min. Umstiegszeit** garantiert
|
|
||||||
- **Detaillierte Fußwege** mit Gehzeit
|
|
||||||
- **Alternative Routen** - Mehrere Optionen
|
|
||||||
- **Robuste Stationserkennung** - Funktioniert auch bei unvollständigen Eingaben
|
|
||||||
|
|
||||||
### 🌍 **Live-Map**
|
|
||||||
- **Echtzeit-Fahrzeugverfolgung** auf Dark-Mode Karte
|
|
||||||
- **Authentische BVG-Farben** für alle Linien (U-Bahn, S-Bahn, Tram, Bus)
|
|
||||||
- **Dynamische Bounds** - Nur sichtbare Fahrzeuge laden (bis zu 69% weniger Daten)
|
|
||||||
- **Filter nach Fahrzeugtyp** - U-Bahn, S-Bahn, Tram, Bus, Fähre
|
|
||||||
- **API-optimiert** - Intelligentes Caching
|
|
||||||
|
|
||||||
### 📱 **Progressive Web App (PWA)**
|
|
||||||
- **Installierbar** - Als App auf Home-Screen
|
|
||||||
- **Offline-Fähig** - Service Worker mit Caching
|
|
||||||
- **Touch-optimiert** - Pull-to-Refresh, Wischgesten
|
|
||||||
- **Responsive** - Funktioniert auf Mobile & Desktop
|
|
||||||
- **Dark-Mode** - Schont die Augen
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🚀 Quick Start
|
|
||||||
|
|
||||||
### **Option 1: Direkt nutzen**
|
|
||||||
Öffne die App einfach im Browser - keine Installation nötig!
|
|
||||||
|
|
||||||
### **Option 2: Als PWA installieren**
|
|
||||||
|
|
||||||
**Mobile (iOS/Android):**
|
|
||||||
1. Öffne die App im Browser
|
|
||||||
2. Tippe auf "Teilen" → "Zum Home-Bildschirm"
|
|
||||||
3. Fertig! App ist jetzt auf deinem Home-Screen
|
|
||||||
|
|
||||||
**Desktop (Chrome/Edge):**
|
|
||||||
1. Öffne die App im Browser
|
|
||||||
2. Klicke auf das Install-Icon in der Adressleiste
|
|
||||||
3. Fertig! App ist jetzt in deinen Apps
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🛠️ Installation (für Entwickler)
|
|
||||||
|
|
||||||
### **Voraussetzungen**
|
|
||||||
- Node.js (v16+)
|
|
||||||
- npm oder yarn
|
|
||||||
- HTTPS-Server (für PWA-Features)
|
|
||||||
|
|
||||||
### **Setup**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Repository klonen
|
|
||||||
git clone https://github.com/Serverlele30/VBB-Status-Web-App.git
|
|
||||||
cd VBB-Status-Web-App
|
|
||||||
|
|
||||||
# Dependencies installieren
|
|
||||||
npm install
|
|
||||||
|
|
||||||
# HTTPS-Server starten
|
|
||||||
npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
Die App ist jetzt verfügbar unter: `https://localhost:3000`
|
|
||||||
|
|
||||||
### **HTTPS-Server**
|
|
||||||
|
|
||||||
Die App benötigt HTTPS für PWA-Features (Service Worker, GPS, etc.).
|
|
||||||
|
|
||||||
**Server-Skript (`server.js`):**
|
|
||||||
```javascript
|
|
||||||
const https = require('https');
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const { execSync } = require('child_process');
|
|
||||||
const mimeTypes = require('mime-types');
|
|
||||||
|
|
||||||
// Auto-generiert SSL-Zertifikate falls nicht vorhanden
|
|
||||||
// Nutzt OpenSSL für self-signed certificates
|
|
||||||
```
|
|
||||||
|
|
||||||
**SSL-Zertifikate werden automatisch generiert!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📂 Projekt-Struktur
|
|
||||||
|
|
||||||
```
|
```
|
||||||
VBB-Status-Web-App/
|
vbb-netz-status/
|
||||||
├── index.html # Haupt-HTML (Single-Page App)
|
├── index.html # HTML-Struktur
|
||||||
├── service-worker.js # PWA Service Worker
|
├── styles.css # Alle CSS-Styles
|
||||||
├── manifest.json # PWA Manifest
|
├── script.js # Alle JavaScript-Funktionen
|
||||||
├── server.js # HTTPS Development Server
|
├── server.js # HTTPS-Entwicklungsserver
|
||||||
├── package.json # Node.js Dependencies
|
├── service-worker.js # PWA Service Worker
|
||||||
├── icons/ # App-Icons (verschiedene Größen)
|
├── manifest.json # PWA Manifest
|
||||||
│ ├── icon-192.png
|
├── package.json # Node.js Dependencies
|
||||||
│ ├── icon-512.png
|
└── LICENSE # MIT Lizenz
|
||||||
│ └── icon-maskable.png
|
|
||||||
├── fonts/ # Custom Fonts
|
|
||||||
│ └── DotMatrix.ttf # BVG-Schriftart
|
|
||||||
└── data/ # Statische Daten
|
|
||||||
└── station_lines.json # Station-zu-Linie Mappings
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## 🚀 Installation & Start
|
||||||
|
|
||||||
## 🎨 Design
|
1. **Abhängigkeiten installieren:**
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
### **Farbschema**
|
2. **Server starten:**
|
||||||
- **Primär:** `#FFED00` (BVG-Gelb)
|
```bash
|
||||||
- **Background:** `#000000` (Schwarz)
|
npm start
|
||||||
- **Sekundär:** `#1a1a1a` (Dunkelgrau)
|
```
|
||||||
- **Text:** `#ffffff` / `#cccccc`
|
|
||||||
- **Borders:** `#333333`
|
|
||||||
|
|
||||||
### **Typografie**
|
3. **Browser öffnen:**
|
||||||
- **Haupt-Font:** DotMatrix (BVG-Stil)
|
```
|
||||||
- **Fallback:** Courier New, monospace
|
https://localhost:3000
|
||||||
|
```
|
||||||
|
|
||||||
### **Icons & Emojis**
|
## 📝 Änderungen
|
||||||
- 🚇 U-Bahn
|
|
||||||
- 🚊 S-Bahn
|
|
||||||
- 🚋 Tram
|
|
||||||
- 🚌 Bus
|
|
||||||
- ⛴️ Fähre
|
|
||||||
|
|
||||||
### **BVG-Linienfarben**
|
**Vorher:**
|
||||||
Authentische Farben für alle 40+ Linien:
|
- Alles in einer `index.html` (4698 Zeilen)
|
||||||
- **U-Bahnen:** U1-U9 mit Original-Farben
|
|
||||||
- **S-Bahnen:** S-Bahn Grün + Ringbahn Orange
|
**Nachher:**
|
||||||
- **Trams:** Metro-Tram Rot + Tram Gelb
|
- `index.html` - Nur HTML-Struktur (~380 Zeilen)
|
||||||
- **Busse:** Gelb + Metro-Bus Rot
|
- `styles.css` - Alle CSS-Regeln (~2300 Zeilen)
|
||||||
|
- `script.js` - Alle JavaScript-Funktionen (~1990 Zeilen)
|
||||||
|
|
||||||
|
## ✅ Vorteile der Trennung
|
||||||
|
|
||||||
|
- 📦 **Bessere Organisation** - Jede Datei hat einen klaren Zweck
|
||||||
|
- 🔧 **Einfachere Wartung** - CSS und JS können unabhängig bearbeitet werden
|
||||||
|
- ⚡ **Caching** - Browser können CSS/JS separat cachen
|
||||||
|
- 🎨 **Entwicklung** - Einfacher zu debuggen und zu bearbeiten
|
||||||
|
- 👥 **Teamarbeit** - Mehrere Entwickler können parallel arbeiten
|
||||||
|
|
||||||
|
## 🛠️ Technische Details
|
||||||
|
|
||||||
|
- **CSS**: Enthält alle Styles inkl. Dark Mode, Responsive Design, Animationen
|
||||||
|
- **JavaScript**: Enthält alle Funktionen für Abfahrten, Routen, Live-Map
|
||||||
|
- **HTML**: Referenziert CSS (`<link>`) und JS (`<script src>`)
|
||||||
|
|
||||||
|
## 📱 PWA Unterstützung
|
||||||
|
|
||||||
|
Die App funktioniert weiterhin als Progressive Web App:
|
||||||
|
- Service Worker für Offline-Funktionalität
|
||||||
|
- Manifest für Installation auf dem Homescreen
|
||||||
|
- HTTPS-Server für sichere Verbindung
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 🔧 Technische Details
|
**Entwickelt von:** Aaron K. & Claude (Anthropic)
|
||||||
|
**Lizenz:** MIT
|
||||||
### **APIs**
|
|
||||||
- **VBB Transport REST API v6**
|
|
||||||
- Endpoint: `https://v6.vbb.transport.rest`
|
|
||||||
- Rate Limit: 100 Requests/Minute
|
|
||||||
- [Dokumentation](https://v6.vbb.transport.rest/api.html)
|
|
||||||
|
|
||||||
### **Libraries**
|
|
||||||
- **Leaflet.js** - Interaktive Karten
|
|
||||||
- **CartoDB Dark Matter** - Karten-Tiles (Dark-Mode)
|
|
||||||
- Vanilla JavaScript (kein Framework!)
|
|
||||||
|
|
||||||
### **Browser-Support**
|
|
||||||
- Chrome/Edge 90+
|
|
||||||
- Firefox 88+
|
|
||||||
- Safari 14+
|
|
||||||
- Mobile Browser (iOS Safari, Chrome Mobile)
|
|
||||||
|
|
||||||
### **Performance**
|
|
||||||
- **First Contentful Paint:** < 1s
|
|
||||||
- **Time to Interactive:** < 2s
|
|
||||||
- **Lighthouse Score:** 95+
|
|
||||||
- **Bundle Size:** ~150KB (unkomprimiert)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🗺️ Datenquellen
|
|
||||||
|
|
||||||
### **Station-Line Mappings**
|
|
||||||
Präzise Station-zu-Linie Zuordnungen für 1000+ Stationen:
|
|
||||||
- Extrahiert via custom Node.js Script
|
|
||||||
- Analysiert echte Trip-Routen statt generische Location-Searches
|
|
||||||
- Deckt alle U-Bahn, S-Bahn, Tram und Metro-Linien ab
|
|
||||||
|
|
||||||
**Extraktion:**
|
|
||||||
```bash
|
|
||||||
node extract_station_lines.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Generiert `data/station_lines.json` mit Format:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"900000017101": {
|
|
||||||
"name": "U Spichernstr.",
|
|
||||||
"lines": ["U3", "U9"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📱 PWA-Features
|
|
||||||
|
|
||||||
### **Manifest.json**
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "VBB Netz Status",
|
|
||||||
"short_name": "VBB Status",
|
|
||||||
"start_url": "/",
|
|
||||||
"display": "standalone",
|
|
||||||
"theme_color": "#FFED00",
|
|
||||||
"background_color": "#000000",
|
|
||||||
"icons": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Service Worker**
|
|
||||||
- **Cache-First-Strategie** für statische Assets
|
|
||||||
- **Network-First** für API-Calls
|
|
||||||
- **Offline-Fallback** für alle Seiten
|
|
||||||
- **Auto-Update** bei neuer Version
|
|
||||||
|
|
||||||
**Cache-Versioning:**
|
|
||||||
```javascript
|
|
||||||
const CACHE_NAME = 'vbb-status-v27';
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Offline-Features**
|
|
||||||
- ✅ Gespeicherte Abfahrten anzeigen
|
|
||||||
- ✅ Letzte Suchanfragen speichern
|
|
||||||
- ✅ Offline-Indikator
|
|
||||||
- ✅ Cached Karten-Tiles
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🎯 Features im Detail
|
|
||||||
|
|
||||||
### **GPS-Standort**
|
|
||||||
```javascript
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
(position) => {
|
|
||||||
const { latitude, longitude } = position.coords;
|
|
||||||
findNearbyStops(latitude, longitude);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Auto-Refresh**
|
|
||||||
- **Standard:** 30 Sekunden
|
|
||||||
- **Togglebar:** An/Aus im Header
|
|
||||||
- **Smart:** Nur sichtbare Values aktualisieren (kein Re-Render)
|
|
||||||
|
|
||||||
### **Pull-to-Refresh**
|
|
||||||
```javascript
|
|
||||||
// Touch-basiert, nur auf Mobile
|
|
||||||
// Haptic Feedback via navigator.vibrate()
|
|
||||||
```
|
|
||||||
|
|
||||||
### **Autocomplete-Suche**
|
|
||||||
- **Debounced:** 300ms Verzögerung
|
|
||||||
- **Min. 2 Zeichen** für Suche
|
|
||||||
- **Keyboard-Navigation:** ↑↓ + Enter
|
|
||||||
- **Touch-optimiert:** Große Tap-Targets
|
|
||||||
|
|
||||||
### **Modal-Details**
|
|
||||||
- **Trip-Details** per Tap auf Abfahrt
|
|
||||||
- **Route-Details** mit Zwischenstopps
|
|
||||||
- **Fußweg-Anleitung** mit Distanz/Zeit
|
|
||||||
- **Swipe-to-Close** auf Mobile
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🔒 Datenschutz
|
|
||||||
|
|
||||||
- ✅ **Keine Cookies**
|
|
||||||
- ✅ **Kein Tracking**
|
|
||||||
- ✅ **Keine Werbung**
|
|
||||||
- ✅ **Kein Analytics**
|
|
||||||
- ✅ **GPS nur auf Anfrage** (Browser-Permission)
|
|
||||||
- ✅ **Lokale Speicherung** (localStorage, keine Server)
|
|
||||||
|
|
||||||
**Alle Daten bleiben auf deinem Gerät!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🐛 Bekannte Probleme
|
|
||||||
|
|
||||||
### **API-Limits**
|
|
||||||
- VBB API: 100 Requests/Minute
|
|
||||||
- Bei Überschreitung: 429 Error → Retry nach 60s
|
|
||||||
|
|
||||||
### **GPS-Genauigkeit**
|
|
||||||
- Abhängig von Gerät & Standort
|
|
||||||
- Indoor oft ungenau
|
|
||||||
- Fallback: Manuelle Suche
|
|
||||||
|
|
||||||
### **Browser-Kompatibilität**
|
|
||||||
- Service Worker: Nur HTTPS
|
|
||||||
- GPS: Nur Secure Context (HTTPS oder localhost)
|
|
||||||
- Ältere Browser: Eingeschränkte Features
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🤝 Contributing
|
|
||||||
|
|
||||||
Contributions sind willkommen! 🎉
|
|
||||||
|
|
||||||
### **Workflow**
|
|
||||||
1. Fork das Repository
|
|
||||||
2. Erstelle einen Feature-Branch (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. Commit deine Änderungen (`git commit -m 'Add some AmazingFeature'`)
|
|
||||||
4. Push zum Branch (`git push origin feature/AmazingFeature`)
|
|
||||||
5. Öffne einen Pull Request
|
|
||||||
|
|
||||||
### **Code-Style**
|
|
||||||
- **Vanilla JavaScript** (kein TypeScript)
|
|
||||||
- **Kommentare** für komplexe Logik
|
|
||||||
- **Konsistente Formatierung** (2 Spaces, Semikolons)
|
|
||||||
- **BVG-Farbschema** beibehalten
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📝 Changelog
|
|
||||||
|
|
||||||
Siehe [CHANGELOG.md](CHANGELOG.md) für Details zu allen Updates.
|
|
||||||
|
|
||||||
**Latest:** v27.0 - Credits & Header Update
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📄 Lizenz
|
|
||||||
|
|
||||||
Dieses Projekt ist unter der **MIT License** lizenziert - siehe [LICENSE](LICENSE) für Details.
|
|
||||||
|
|
||||||
**Das bedeutet:**
|
|
||||||
- ✅ Kostenlos nutzbar
|
|
||||||
- ✅ Für private & kommerzielle Projekte
|
|
||||||
- ✅ Modifizierbar
|
|
||||||
- ✅ Weitergabe erlaubt
|
|
||||||
|
|
||||||
**Aber:**
|
|
||||||
- ⚠️ Ohne Garantie
|
|
||||||
- ⚠️ Auf eigene Verantwortung
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 🙏 Credits
|
|
||||||
|
|
||||||
### **Entwicklung**
|
|
||||||
- **Aaron K.** - Initial Development & Design
|
|
||||||
- **Claude (Anthropic)** - AI-Assisted Development
|
|
||||||
|
|
||||||
### **Daten & APIs**
|
|
||||||
- **VBB Transport REST API v6** - [v6.vbb.transport.rest](https://v6.vbb.transport.rest)
|
|
||||||
- **OpenStreetMap Contributors** - Kartendaten
|
|
||||||
- **CartoDB** - Dark Matter Map Tiles
|
|
||||||
|
|
||||||
### **Schriftart**
|
|
||||||
- **DotMatrix** by [@NikBLN](https://github.com/NikBLN)
|
|
||||||
|
|
||||||
### **Inspiration**
|
|
||||||
- **BVG** - Design & Farbschema
|
|
||||||
- **VBB** - Verkehrsverbund Berlin-Brandenburg
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 📬 Kontakt
|
|
||||||
|
|
||||||
**Repository:** [github.com/Serverlele30/VBB-Status-Web-App](https://github.com/Serverlele30/VBB-Status-Web-App)
|
|
||||||
|
|
||||||
**Issues:** [GitHub Issues](https://github.com/Serverlele30/VBB-Status-Web-App/issues)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## ⚠️ Disclaimer
|
|
||||||
|
|
||||||
Diese App ist ein **inoffizielles Projekt** und wird **nicht** von VBB oder BVG betrieben oder unterstützt.
|
|
||||||
|
|
||||||
Alle Daten stammen von der öffentlichen VBB Transport REST API.
|
|
||||||
|
|
||||||
**Keine Garantie für:**
|
|
||||||
- Richtigkeit der Daten
|
|
||||||
- Verfügbarkeit der API
|
|
||||||
- Fehlerfreien Betrieb
|
|
||||||
|
|
||||||
**Nutze die App auf eigene Verantwortung!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
<div align="center">
|
|
||||||
|
|
||||||
Made with ❤️ for Berlin & Brandenburg
|
|
||||||
|
|
||||||
**[⬆ Back to Top](#vbb-netz-status)**
|
|
||||||
|
|
||||||
</div>
|
|
||||||
+3
-4301
File diff suppressed because it is too large.
Load diff
+26
-26
@@ -1,26 +1,26 @@
|
|||||||
{
|
{
|
||||||
"name": "VBB Netz Status",
|
"name": "VBB Netz Status",
|
||||||
"short_name": "VBB Status",
|
"short_name": "VBB Status",
|
||||||
"description": "Echtzeit-Abfahrtsmonitor für den Verkehrsverbund Berlin-Brandenburg - by Aaron K.",
|
"description": "Echtzeit-Abfahrtsmonitor für den Verkehrsverbund Berlin-Brandenburg - by Aaron K.",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"background_color": "#000000",
|
"background_color": "#000000",
|
||||||
"theme_color": "#000000",
|
"theme_color": "#000000",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "images/favicon.png",
|
"src": "images/favicon.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "images/favicon.png",
|
"src": "images/favicon.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any maskable"
|
"purpose": "any maskable"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"categories": ["travel", "navigation"],
|
"categories": ["travel", "navigation"],
|
||||||
"lang": "de-DE"
|
"lang": "de-DE"
|
||||||
}
|
}
|
||||||
+41
-41
@@ -1,41 +1,41 @@
|
|||||||
{
|
{
|
||||||
"name": "vbb-netz-status",
|
"name": "vbb-netz-status",
|
||||||
"version": "29.0.0",
|
"version": "29.0.0",
|
||||||
"description": "Live-Nahverkehr für Berlin & Brandenburg - Progressive Web App (PWA) für Echtzeit-Abfahrten, Routenplanung und Live-Fahrzeugverfolgung im VBB-Gebiet",
|
"description": "Live-Nahverkehr für Berlin & Brandenburg - Progressive Web App (PWA) für Echtzeit-Abfahrten, Routenplanung und Live-Fahrzeugverfolgung im VBB-Gebiet",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node server.js",
|
"start": "node server.js",
|
||||||
"dev": "node server.js"
|
"dev": "node server.js"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"vbb",
|
"vbb",
|
||||||
"bvg",
|
"bvg",
|
||||||
"berlin",
|
"berlin",
|
||||||
"brandenburg",
|
"brandenburg",
|
||||||
"transit",
|
"transit",
|
||||||
"public-transport",
|
"public-transport",
|
||||||
"pwa",
|
"pwa",
|
||||||
"real-time",
|
"real-time",
|
||||||
"departures",
|
"departures",
|
||||||
"route-planner",
|
"route-planner",
|
||||||
"live-map"
|
"live-map"
|
||||||
],
|
],
|
||||||
"author": "Aaron K. & Claude (Anthropic)",
|
"author": "Aaron K. & Claude (Anthropic)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Serverlele30/VBB-Status-Web-App.git"
|
"url": "https://github.com/Serverlele30/VBB-Status-Web-App.git"
|
||||||
},
|
},
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/Serverlele30/VBB-Status-Web-App/issues"
|
"url": "https://github.com/Serverlele30/VBB-Status-Web-App/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/Serverlele30/VBB-Status-Web-App#readme",
|
"homepage": "https://github.com/Serverlele30/VBB-Status-Web-App#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mime-types": "^2.1.35"
|
"mime-types": "^2.1.35"
|
||||||
},
|
},
|
||||||
"devDependencies": {},
|
"devDependencies": {},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.0.0",
|
"node": ">=16.0.0",
|
||||||
"npm": ">=8.0.0"
|
"npm": ">=8.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,116 +1,116 @@
|
|||||||
const https = require('https');
|
const https = require('https');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const mimeTypes = require('mime-types');
|
const mimeTypes = require('mime-types');
|
||||||
|
|
||||||
const PORT = 3000;
|
const PORT = 3000;
|
||||||
const CERT_DIR = path.join(__dirname, 'certs');
|
const CERT_DIR = path.join(__dirname, 'certs');
|
||||||
const CERT_FILE = path.join(CERT_DIR, 'cert.pem');
|
const CERT_FILE = path.join(CERT_DIR, 'cert.pem');
|
||||||
const KEY_FILE = path.join(CERT_DIR, 'key.pem');
|
const KEY_FILE = path.join(CERT_DIR, 'key.pem');
|
||||||
|
|
||||||
// Create certs directory if it doesn't exist
|
// Create certs directory if it doesn't exist
|
||||||
if (!fs.existsSync(CERT_DIR)) {
|
if (!fs.existsSync(CERT_DIR)) {
|
||||||
fs.mkdirSync(CERT_DIR);
|
fs.mkdirSync(CERT_DIR);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate self-signed certificate if it doesn't exist
|
// Generate self-signed certificate if it doesn't exist
|
||||||
if (!fs.existsSync(CERT_FILE) || !fs.existsSync(KEY_FILE)) {
|
if (!fs.existsSync(CERT_FILE) || !fs.existsSync(KEY_FILE)) {
|
||||||
console.log('🔐 Generating self-signed SSL certificate...');
|
console.log('🔐 Generating self-signed SSL certificate...');
|
||||||
try {
|
try {
|
||||||
execSync(`openssl req -x509 -newkey rsa:4096 -keyout "${KEY_FILE}" -out "${CERT_FILE}" -days 365 -nodes -subj "/CN=localhost"`, {
|
execSync(`openssl req -x509 -newkey rsa:4096 -keyout "${KEY_FILE}" -out "${CERT_FILE}" -days 365 -nodes -subj "/CN=localhost"`, {
|
||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
});
|
});
|
||||||
console.log('✅ Certificate generated successfully!');
|
console.log('✅ Certificate generated successfully!');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error generating certificate:', error.message);
|
console.error('❌ Error generating certificate:', error.message);
|
||||||
console.log('📝 Please install OpenSSL or create certificates manually.');
|
console.log('📝 Please install OpenSSL or create certificates manually.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPS server options
|
// HTTPS server options
|
||||||
const options = {
|
const options = {
|
||||||
key: fs.readFileSync(KEY_FILE),
|
key: fs.readFileSync(KEY_FILE),
|
||||||
cert: fs.readFileSync(CERT_FILE)
|
cert: fs.readFileSync(CERT_FILE)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create HTTPS server
|
// Create HTTPS server
|
||||||
const server = https.createServer(options, (req, res) => {
|
const server = https.createServer(options, (req, res) => {
|
||||||
// Remove query parameters and get clean path
|
// Remove query parameters and get clean path
|
||||||
let filePath = req.url.split('?')[0];
|
let filePath = req.url.split('?')[0];
|
||||||
|
|
||||||
// Default to index.html for root
|
// Default to index.html for root
|
||||||
if (filePath === '/') {
|
if (filePath === '/') {
|
||||||
filePath = '/index.html';
|
filePath = '/index.html';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct full file path
|
// Construct full file path
|
||||||
const fullPath = path.join(__dirname, filePath);
|
const fullPath = path.join(__dirname, filePath);
|
||||||
|
|
||||||
// Security check: prevent directory traversal
|
// Security check: prevent directory traversal
|
||||||
const normalizedPath = path.normalize(fullPath);
|
const normalizedPath = path.normalize(fullPath);
|
||||||
if (!normalizedPath.startsWith(__dirname)) {
|
if (!normalizedPath.startsWith(__dirname)) {
|
||||||
res.writeHead(403);
|
res.writeHead(403);
|
||||||
res.end('Forbidden');
|
res.end('Forbidden');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if file exists
|
// Check if file exists
|
||||||
fs.access(fullPath, fs.constants.F_OK, (err) => {
|
fs.access(fullPath, fs.constants.F_OK, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.writeHead(404);
|
res.writeHead(404);
|
||||||
res.end('File not found');
|
res.end('File not found');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get MIME type
|
// Get MIME type
|
||||||
const mimeType = mimeTypes.lookup(fullPath) || 'application/octet-stream';
|
const mimeType = mimeTypes.lookup(fullPath) || 'application/octet-stream';
|
||||||
|
|
||||||
// Read and serve file
|
// Read and serve file
|
||||||
fs.readFile(fullPath, (err, data) => {
|
fs.readFile(fullPath, (err, data) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
res.writeHead(500);
|
res.writeHead(500);
|
||||||
res.end('Error reading file');
|
res.end('Error reading file');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set appropriate headers
|
// Set appropriate headers
|
||||||
res.writeHead(200, {
|
res.writeHead(200, {
|
||||||
'Content-Type': mimeType,
|
'Content-Type': mimeType,
|
||||||
'Cache-Control': 'no-cache'
|
'Cache-Control': 'no-cache'
|
||||||
});
|
});
|
||||||
res.end(data);
|
res.end(data);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start server
|
// Start server
|
||||||
server.listen(PORT, () => {
|
server.listen(PORT, () => {
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('🚀 VBB Netz Status - HTTPS Development Server');
|
console.log('🚀 VBB Netz Status - HTTPS Development Server');
|
||||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||||
console.log(`✅ Server running at: https://localhost:${PORT}`);
|
console.log(`✅ Server running at: https://localhost:${PORT}`);
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('📱 To test on mobile:');
|
console.log('📱 To test on mobile:');
|
||||||
console.log(' 1. Find your local IP: ipconfig (Windows) or ifconfig (Mac/Linux)');
|
console.log(' 1. Find your local IP: ipconfig (Windows) or ifconfig (Mac/Linux)');
|
||||||
console.log(' 2. Open https://YOUR_IP:3000 on your mobile device');
|
console.log(' 2. Open https://YOUR_IP:3000 on your mobile device');
|
||||||
console.log(' 3. Accept the self-signed certificate warning');
|
console.log(' 3. Accept the self-signed certificate warning');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('⚠️ Note: Your browser will show a security warning.');
|
console.log('⚠️ Note: Your browser will show a security warning.');
|
||||||
console.log(' This is normal for self-signed certificates in development.');
|
console.log(' This is normal for self-signed certificates in development.');
|
||||||
console.log(' Click "Advanced" → "Proceed to localhost" to continue.');
|
console.log(' Click "Advanced" → "Proceed to localhost" to continue.');
|
||||||
console.log('');
|
console.log('');
|
||||||
console.log('🛑 Press Ctrl+C to stop the server');
|
console.log('🛑 Press Ctrl+C to stop the server');
|
||||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||||
console.log('');
|
console.log('');
|
||||||
});
|
});
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
process.on('SIGINT', () => {
|
process.on('SIGINT', () => {
|
||||||
console.log('\n\n👋 Shutting down server...');
|
console.log('\n\n👋 Shutting down server...');
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
console.log('✅ Server stopped');
|
console.log('✅ Server stopped');
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
+69
-69
@@ -1,69 +1,69 @@
|
|||||||
const CACHE_NAME = 'vbb-status-v29';
|
const CACHE_NAME = 'vbb-status-v29';
|
||||||
const urlsToCache = [
|
const urlsToCache = [
|
||||||
'/',
|
'/',
|
||||||
'/index.html',
|
'/index.html',
|
||||||
'/manifest.json',
|
'/manifest.json',
|
||||||
'/DotMatrix.ttf',
|
'/DotMatrix.ttf',
|
||||||
'/images/favicon.png'
|
'/images/favicon.png'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Installation
|
// Installation
|
||||||
self.addEventListener('install', event => {
|
self.addEventListener('install', event => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(CACHE_NAME)
|
caches.open(CACHE_NAME)
|
||||||
.then(cache => {
|
.then(cache => {
|
||||||
console.log('Cache geöffnet');
|
console.log('Cache geöffnet');
|
||||||
return cache.addAll(urlsToCache);
|
return cache.addAll(urlsToCache);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
self.skipWaiting();
|
self.skipWaiting();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Aktivierung
|
// Aktivierung
|
||||||
self.addEventListener('activate', event => {
|
self.addEventListener('activate', event => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.keys().then(cacheNames => {
|
caches.keys().then(cacheNames => {
|
||||||
return Promise.all(
|
return Promise.all(
|
||||||
cacheNames.map(cacheName => {
|
cacheNames.map(cacheName => {
|
||||||
if (cacheName !== CACHE_NAME) {
|
if (cacheName !== CACHE_NAME) {
|
||||||
console.log('Alter Cache wird gelöscht:', cacheName);
|
console.log('Alter Cache wird gelöscht:', cacheName);
|
||||||
return caches.delete(cacheName);
|
return caches.delete(cacheName);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
self.clients.claim();
|
self.clients.claim();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fetch
|
// Fetch
|
||||||
self.addEventListener('fetch', event => {
|
self.addEventListener('fetch', event => {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request)
|
caches.match(event.request)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
// Cache hit - return response
|
// Cache hit - return response
|
||||||
if (response) {
|
if (response) {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
return fetch(event.request).then(
|
return fetch(event.request).then(
|
||||||
response => {
|
response => {
|
||||||
// Check if valid response
|
// Check if valid response
|
||||||
if (!response || response.status !== 200 || response.type !== 'basic') {
|
if (!response || response.status !== 200 || response.type !== 'basic') {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clone the response
|
// Clone the response
|
||||||
const responseToCache = response.clone();
|
const responseToCache = response.clone();
|
||||||
|
|
||||||
caches.open(CACHE_NAME)
|
caches.open(CACHE_NAME)
|
||||||
.then(cache => {
|
.then(cache => {
|
||||||
cache.put(event.request, responseToCache);
|
cache.put(event.request, responseToCache);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
+2314
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user