feat: Notruf — Telefon in SMS, Geo-Diagnose + Geräteerkennung; Sprach-UI-Fix
Notruf (Punkte 1+2):
- SMS enthält jetzt die Telefonnummer(n) der Person (Mobil/Festnetz).
- Geo-Diagnose (P1): captureGeo liefert bei Fehlschlag einen Grund
(Permission/insecure/Timeout/Code) statt still null; geht als geo_error an
/api/emergency, landet in E-Mail ("Standort nicht verfügbar (<Grund>)") und
im Server-Log (logger.info) — Fehlversuche sind jetzt erklärbar.
- Geräteerkennung (P2): device {mobile, platform} via User-Agent Client Hints
(Fallback UA-String); E-Mail zeigt "Auslösegerät: Mobilgerät/Festgerät (…)".
- schemas: DeviceInfo + EmergencyRequest.geo_error/device; chat reicht durch.
- Tests: Telefon in SMS, geo_error + Geräte-Zeile in E-Mail (240 grün).
Sprach-Bug (Punkt 3): allowed_languages wurde im Menü nur via <option hidden>
gefiltert, was manche Browser bei <select> ignorieren -> Sprachen blieben
wählbar. Jetzt zusätzlich opt.disabled (robust, nicht wählbar); Backend klemmt
ohnehin serverseitig.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
2a013ae9fe
commit
5d05e081d4
5 changed files with 104 additions and 34 deletions
|
|
@ -337,7 +337,9 @@ function applyAllowedLanguages(prefs) {
|
|||
const allowed = String((prefs || {}).allowed_languages || "")
|
||||
.split(",").map((s) => s.trim()).filter(Boolean);
|
||||
Array.from(langSel.options).forEach((opt) => {
|
||||
opt.hidden = allowed.length > 0 && !allowed.includes(opt.value);
|
||||
const blocked = allowed.length > 0 && !allowed.includes(opt.value);
|
||||
opt.hidden = blocked; // versteckt (moderne Browser)
|
||||
opt.disabled = blocked; // robust: auch wo <option hidden> ignoriert wird, nicht wählbar
|
||||
});
|
||||
let val = (prefs || {}).language || "de";
|
||||
if (allowed.length && !allowed.includes(val)) val = allowed[0];
|
||||
|
|
@ -2091,39 +2093,54 @@ $("#load-settings").addEventListener("click", loadSettings);
|
|||
// später; vorerst zeigt der Server einen Hinweis in der Nutzersprache.
|
||||
const emergencyBtn = $("#emergency-btn");
|
||||
|
||||
// Standort (One-Shot) für den Notruf erfassen — nur mit Freigabe (location_consent)
|
||||
// und mit Timeout, damit ein hängendes GPS den Alarm nicht blockiert. null = kein Fix.
|
||||
// Standort (One-Shot) für den Notruf erfassen — nur mit Freigabe (location_consent),
|
||||
// mit Timeout (hängendes GPS blockiert den Alarm nie). Liefert {ok:true,…} oder
|
||||
// {ok:false, error:"…"} mit erklärbarem Grund (für Diagnose in Meldung/Log).
|
||||
function captureGeo(timeoutMs = 8000) {
|
||||
return new Promise((resolve) => {
|
||||
if (!myPrefs.location_consent || !navigator.geolocation) return resolve(null);
|
||||
if (!myPrefs.location_consent) return resolve({ ok: false, error: "Standort-Freigabe aus" });
|
||||
if (!window.isSecureContext) return resolve({ ok: false, error: "kein HTTPS (insecure context)" });
|
||||
if (!navigator.geolocation) return resolve({ ok: false, error: "Browser ohne Geolocation" });
|
||||
let done = false;
|
||||
const finish = (v) => { if (!done) { done = true; resolve(v); } };
|
||||
const timer = setTimeout(() => finish(null), timeoutMs);
|
||||
const timer = setTimeout(() => finish({ ok: false, error: "Timeout" }), timeoutMs);
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
(pos) => {
|
||||
clearTimeout(timer);
|
||||
finish({
|
||||
ok: true,
|
||||
lat: pos.coords.latitude,
|
||||
lon: pos.coords.longitude,
|
||||
accuracy: pos.coords.accuracy,
|
||||
ts: new Date(pos.timestamp).toISOString(),
|
||||
});
|
||||
},
|
||||
() => { clearTimeout(timer); finish(null); },
|
||||
(err) => { clearTimeout(timer); finish({ ok: false, error: "code " + err.code + ": " + err.message }); },
|
||||
{ enableHighAccuracy: true, timeout: timeoutMs, maximumAge: 60000 }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Geräteart/Plattform aus Browser-Daten (User-Agent Client Hints, Fallback UA-String).
|
||||
function deviceInfo() {
|
||||
try {
|
||||
const uad = navigator.userAgentData;
|
||||
if (uad) return { mobile: !!uad.mobile, platform: uad.platform || "" };
|
||||
} catch (e) { /* ignore */ }
|
||||
const ua = navigator.userAgent || "";
|
||||
return { mobile: /Mobi|Android|iPhone|iPad|iPod/i.test(ua), platform: ua.slice(0, 120) };
|
||||
}
|
||||
|
||||
if (emergencyBtn) {
|
||||
emergencyBtn.addEventListener("click", async () => {
|
||||
if (!confirm("Wirklich Hilfe rufen?")) return;
|
||||
const lang = (langSel && langSel.value) || "de";
|
||||
emergencyBtn.disabled = true;
|
||||
try {
|
||||
const geo = await captureGeo(); // null, wenn keine Freigabe/kein Fix
|
||||
const body = { language: lang };
|
||||
if (geo) body.geo = geo;
|
||||
const g = await captureGeo();
|
||||
const body = { language: lang, device: deviceInfo() };
|
||||
if (g.ok) body.geo = { lat: g.lat, lon: g.lon, accuracy: g.accuracy, ts: g.ts };
|
||||
else body.geo_error = g.error;
|
||||
const res = await fetch("/api/emergency", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue