From 67d0ef421b9605b06255e5a169d92e5be52e234d Mon Sep 17 00:00:00 2001 From: mhoennig Date: Thu, 27 Aug 2026 05:23:19 +0200 Subject: [PATCH] =?UTF-8?q?fix(teilen):=20Basis-Adresse=20per=20Lebendprob?= =?UTF-8?q?e=20pr=C3=BCfen,=20sonst=20fragen=20(D81-Nachtrag)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Auf einer Instanz ohne eigenes Backend (GitHub Pages) endete Teilen mit HTTP 405: Die Vorgabe "eigene Herkunft" (D76-Nachtrag 8) stimmt nur auf der produktiven Installation. serverBaseOrAsk() prüft die Vorgabe jetzt mit GET /api/v1/info (D77), bevor gePOSTet wird, und fragt sonst nach der Server-Adresse; gemerkt wird nur eine Adresse, die die Probe besteht. Von Pages aus trägt man werkbaum.javagil.de ein — CORS erlaubt es. Co-Authored-By: Claude Fable 5 --- docs/CHANGELOG.md | 4 ++++ docs/DECISIONS.md | 35 +++++++++++++++++++++++++++++++++++ frontend/src/app.js | 30 +++++++++++++++++++++++++----- frontend/src/live.js | 8 ++++++++ frontend/tests/live.test.js | 8 +++++++- 5 files changed, 79 insertions(+), 6 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 246c3fb..b120143 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -17,6 +17,10 @@ the git history of `docs/examples/werkbaum.werkbaum`. A day can therefore carry a link without having a note (someone forgot to write one) — but never the reverse. +## 2026-08-27 + +- Fix: sharing from an instance without its own backend (GitHub Pages) failed with HTTP 405 — the share button now probes the default address and asks for the server instead + ## 2026-08-26 - The document picker moved into the app header: `Werkbaum › name` opens the menu — on the phone it is reachable from both panes, and the name takes over the subtitle line diff --git a/docs/DECISIONS.md b/docs/DECISIONS.md index da2f29a..f12b8d1 100644 --- a/docs/DECISIONS.md +++ b/docs/DECISIONS.md @@ -7006,3 +7006,38 @@ stellt Text und Knopfzustände zurück, ohne das Menü zu schließen; Löschen eines nicht-aktiven Dokuments lässt den Editor unangetastet; auf dem Telefon ist der Wähler aus dem Diagramm-Bereich erreichbar und der Wechsel rendert 183 Knoten, langer Name füllt die 335-px-Chipzeile mit Ellipse. + +**Nachtrag — vor dem Teilen wird die Basis-Adresse per Lebendprobe geprüft.** +Gemeldet vom ersten echten Druck auf den neuen Teilen-Knopf: „Server-Dokument +nicht geladen: https://mhoennig.github.io/api/v1/documents (HTTP 405)." Die +Ursache liegt nicht im Knopf, sondern in der Vorgabe aus D76-Nachtrag 8: Die +Basis-Adresse ist die **eigene Herkunft** — richtig auf der produktiven +Installation (dort liegt `/api/` per Proxy dahinter, D77), falsch auf jeder +statischen Instanz. GitHub Pages beantwortet den POST mit 405, und die +Meldung führte auf die falsche Fährte („ist die Adresse eine +Dokument-Adresse?"). Der Fehler war schon vor D81 da; der Knopf in der +Titelzeile hat ihn nur sichtbar gemacht — im Menü hat ihn dort schlicht +niemand gedrückt. + +**Gefixt mit der Lebendprobe, die es schon gibt:** `GET /api/v1/info` (D77, +gebaut genau als „antwortet hier die Anwendung?"). `serverBaseOrAsk()` prüft +die Vorgabe damit, **bevor** gePOSTet wird; besteht sie nicht, wird +**gefragt** — der vorhandene Dialog samt gemerkter Antwort. Gemerkt wird nur +eine Adresse, die die Probe besteht: Ein Tippfehler klemmt sich nicht fest +(und eine gemerkte Adresse, deren Backend verschwindet, heilt sich beim +nächsten Teilen von selbst — Probe scheitert, es wird neu gefragt). Eine +eingegebene Adresse, die nicht antwortet, wird trotzdem versucht — der +POST-Fehlerpfad nennt dann ehrlich, was nicht erreichbar war, statt dass der +Dialog stumm wieder aufgeht. + +**Von Pages aus funktioniert das Teilen damit wirklich:** Das Backend erlaubt +CORS `*` (D76-Nachtrag 7) — wer auf `mhoennig.github.io` arbeitet und im +Dialog `https://werkbaum.javagil.de` einträgt, legt sein Dokument dort ab. +Gemessen aus dem Browser: Die Probe gegen die stabile Instanz besteht +cross-origin. + +**Nachgemessen** am Dev-Server (dieselbe Lage wie Pages: keine eigene +`/api/`): Teilen öffnet jetzt den Adress-Dialog statt der 405-Warnung, +Abbruch tut nichts; die Probe gegen das echte Backend liefert +`{name:"editor-backend"}`. `infoUrl()` liegt headless in live.js (39 +Live-Tests, +1). diff --git a/frontend/src/app.js b/frontend/src/app.js index 2c9f343..24ee573 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -4866,27 +4866,47 @@ function displayName(){ const SERVER_PARAM = 'server'; const LS_SERVER = 'werkbaum-server'; +/* Antwortet unter dieser Basis wirklich ein Werkbaum-Backend? Die Lebendprobe + `/api/v1/info` (D77), bevor blind gePOSTet wird. */ +async function probeServer(basis){ + try{ + const info = await fetchJson(live.infoUrl(basis)); + return !!(info && info.name); + }catch(_){ return false; } +} + /* Wohin? Reihenfolge und Begründung stehen in live.js. Was dort nicht hingehört, ist der letzte Ausweg: fragen und die Antwort merken — ein - Dialog ist keine entscheidbare Regel. */ -function serverBaseOrAsk(){ + Dialog ist keine entscheidbare Regel. + + Die Vorgabe „eigene Herkunft" stimmt nur, wo das Backend wirklich dahinter + liegt (produktive Installation, D77) — auf GitHub Pages oder einer anderen + statischen Instanz gibt es keins, und der POST endete dort mit einem + kryptischen 405 (D81-Nachtrag). Deshalb wird die Vorgabe erst per + Lebendprobe geprüft und sonst GEFRAGT; gemerkt wird nur eine Adresse, die + die Probe besteht — ein Tippfehler klemmt sich so nicht fest. Eine + eingegebene Adresse, die nicht antwortet, wird trotzdem versucht: Der + POST-Fehlerpfad nennt dann ehrlich, was nicht erreichbar war. */ +async function serverBaseOrAsk(){ const offen = liveState ? liveState.urls.doc : null; let gemerkt = null; try{ gemerkt = localStorage.getItem(LS_SERVER); }catch(_){} const basis = live.serverBase(urlParam(SERVER_PARAM) || gemerkt, offen, location.href); - if(basis) return basis; + if(basis && await probeServer(basis)) return basis; const eingabe = window.prompt(t('docToServerAsk'), 'https://'); if(!eingabe) return null; const geprueft = live.serverBase(eingabe, null, location.href); - if(geprueft){ try{ localStorage.setItem(LS_SERVER, geprueft); }catch(_){} } + if(geprueft && await probeServer(geprueft)){ + try{ localStorage.setItem(LS_SERVER, geprueft); }catch(_){} + } return geprueft; } async function putOnServer(){ const d = activeDoc(); if(!d) return; - const basis = serverBaseOrAsk(); + const basis = await serverBaseOrAsk(); if(!basis) return; try{ diff --git a/frontend/src/live.js b/frontend/src/live.js index 286a342..115686b 100644 --- a/frontend/src/live.js +++ b/frontend/src/live.js @@ -77,6 +77,14 @@ export function documentsUrl(base){ return String(base).replace(/\/+$/, '') + '/api/v1/documents'; } +/* Lebendprobe (D77): antwortet hier wirklich ein Werkbaum-Backend? Vor dem + Teilen gefragt, denn die Vorgabe „eigene Herkunft" stimmt nur auf der + produktiven Installation — auf einer statischen Instanz (GitHub Pages) + bekäme der POST sonst ein kryptisches 405 (D81-Nachtrag). */ +export function infoUrl(base){ + return String(base).replace(/\/+$/, '') + '/api/v1/info'; +} + /* ------------------------------------------------------------------ Zeilen */ /* Zeilenenden auf LF (SPEC §12). Der Server normalisiert beim Speichern diff --git a/frontend/tests/live.test.js b/frontend/tests/live.test.js index 187d6a7..d08b13f 100644 --- a/frontend/tests/live.test.js +++ b/frontend/tests/live.test.js @@ -1,7 +1,7 @@ import {describe, it, expect} from 'vitest'; import { liveUrls, normalize, lines, text, computeOps, applyOps, - mapLine, caretToLineCol, lineColToCaret, feedAction, serverBase, documentsUrl, + mapLine, caretToLineCol, lineColToCaret, feedAction, serverBase, documentsUrl, infoUrl, } from '../src/live.js'; describe('Adressen', () => { @@ -195,6 +195,12 @@ describe('Basis-Adresse des Backends', () => { .toBe('https://werkbaum.example/api/v1/documents'); }); + it('die Lebendprobe fragt /api/v1/info an der Basis (D81-Nachtrag)', () => { + expect(infoUrl('https://werkbaum.example')).toBe('https://werkbaum.example/api/v1/info'); + expect(infoUrl('https://werkbaum.example/unterverzeichnis/')) + .toBe('https://werkbaum.example/unterverzeichnis/api/v1/info'); + }); + it('der ?server=-Parameter hat Vorrang', () => { expect(serverBase('http://localhost:8080', 'https://fremd.example/api/v1/documents/x', seite)) .toBe('http://localhost:8080');