waffensachkunde

Waffensachkunde – Lernsoftware für die Sachkundeprüfung nach § 7 WaffG. Barrierefrei, offline, EUPL-1.2.

/ app src shared unterstuetzung.ts

6,1 KB Rohdatei
app/src/shared/unterstuetzung.ts — 134 Zeilen
1 /**
2 * Das Unterstützungsangebot – eine Bitte, kein Handel.
3 *
4 * ## Eine Adresse, und leer heißt lautlos
5 *
6 * {@link UNTERSTUETZUNG_URL} ist die **einzige** Stelle, an der die Adresse
7 * steht. Ist sie leer, lässt die Anwendung das Angebot vollständig weg: keine
8 * Überschrift, kein Knopf, keine leere Zeile. Ein Knopf, der ins Nichts führt,
9 * wäre schlimmer als kein Knopf – er sagt zu, dass es dort weitergeht. Wer den
10 * Dienst wechselt oder das Angebot zurücknehmen will, ändert diese eine
11 * Zeile.
12 *
13 * Gewählt ist Ko-fi. Die Form bleibt geprüft: `https:`, keine Zugangsdaten
14 * in der Adresse, sonst gilt sie als nicht gesetzt. Ein Wechsel des Dienstes
15 * ist auch später eine Zeile – die Adresse steht nur hier, und
16 * `main/sicherheit.ts` liest sie von dieser Stelle.
17 *
18 * ## Warum es nichts freischaltet
19 *
20 * Die Anwendung soll in den Microsoft Store. Dessen Richtlinien lassen bei
21 * kostenlosen Anwendungen einen Verweis auf eine Spendenseite zu, verlangen
22 * aber, dass dahinter **keine digitalen Güter und keine Funktionen verkauft**
23 * werden. Deshalb schaltet dieser Knopf nichts frei: keinen Pro-Modus, keine
24 * Zusatzfunktion, keine Werbefreiheit – es gibt hier keine Werbung, die man
25 * abstellen könnte. Wer nichts gibt, hat dieselbe Anwendung wie alle anderen.
26 * Das ist nicht bloß eine Store-Auflage, sondern der Grund, warum das Angebot
27 * überhaupt vertretbar ist: Eine Prüfungsvorbereitung, die einen Teil ihres
28 * Stoffs hinter eine Zahlung legt, benachteiligt genau die Prüflinge, die
29 * ohnehin am wenigsten übrig haben.
30 *
31 * ## Warum es sich nicht von selbst meldet
32 *
33 * `docs/entscheidung-motivation.md` hält fest, warum es hier keinen Streak
34 * und keinen Tagesziel-Ring gibt: Diese Anwendung erzeugt keinen Druck, um
35 * Verhalten zu erzeugen. Für eine Spendenbitte gilt dasselbe, nur schärfer –
36 * sie hat ein Eigeninteresse. Deshalb gibt es **kein** Einblenden nach der
37 * zehnten Frage, keinen Zähler „schon 300 Fragen gelernt“, keine Erinnerung,
38 * kein zweites Fragen. Das Angebot steht an einer Stelle, an der ohnehin
39 * nachsieht, wer wissen will, woher diese Anwendung kommt und was sie kostet:
40 * in „Über diese Software“, unterhalb der Lizenzangaben – und ausdrücklich
41 * **nicht** vor dem Rückmeldeweg, der dort zuoberst steht. Wer die Ansicht
42 * öffnet, weil er auf eine Barriere gestoßen ist, soll nicht zuerst um Geld
43 * gebeten werden.
44 *
45 * ## Warum die Anwendung dabei keine Verbindung öffnet
46 *
47 * „Diese Anwendung läuft vollständig offline“ ist eine Zusage, die in „Über
48 * diese Software“ ausdrücklich steht. Der Knopf bricht sie nicht: Er lädt
49 * nichts, prüft nichts nach und meldet nichts – er übergibt die Adresse an
50 * den Hauptprozess, und der reicht sie mit `shell.openExternal` an den
51 * Standardbrowser weiter. Das Fenster verlässt dabei niemand unangekündigt:
52 * {@link UNTERSTUETZUNG_KNOPF} sagt es in der Beschriftung (WCAG 3.2).
53 *
54 * Was über die Brücke geht, wird trotzdem geprüft – `main/sicherheit.ts`
55 * vergleicht die gewünschte Adresse mit genau dieser Konstanten und öffnet
56 * sonst nichts. Eine Adresse mitschicken zu dürfen heißt sonst, auch eine
57 * andere mitschicken zu dürfen.
58 */
59
60 /**
61 * Adresse der Unterstützungsseite. **Leer = kein Angebot.**
62 *
63 * Muss mit `https://` beginnen; alles andere behandelt
64 * {@link unterstuetzungsziel} wie „nicht gesetzt“. Wird zugleich von
65 * `main/sicherheit.ts` als einzige zulässige Zieladresse gelesen – eine
66 * Änderung hier verschiebt also auch, was die Brücke überhaupt öffnen darf.
67 */
68 export const UNTERSTUETZUNG_URL = 'https://ko-fi.com/olafwillerding';
69
70 /** Überschrift des Blocks in „Über diese Software“. */
71 export const UNTERSTUETZUNG_TITEL = 'Diese Arbeit unterstützen';
72
73 /**
74 * Beschriftung des Knopfes.
75 *
76 * Sie sagt beides: was geschieht und **dass das Fenster verlassen wird**. Ein
77 * Verweis, der unangekündigt den Browser startet, ist genau die Überraschung,
78 * die WCAG 3.2 vermeiden will. Die sichtbare Beschriftung ist zugleich der
79 * zugängliche Name – es gibt kein `aria-label`, das daneben laufen könnte
80 * (WCAG 2.5.3).
81 */
82 export const UNTERSTUETZUNG_KNOPF = 'Unterstützungsseite im Browser öffnen';
83
84 /**
85 * Die geprüfte Adresse, oder `null`, wenn keine gesetzt ist.
86 *
87 * Eine reine Funktion mit ausdrücklichem Eingabewert, damit beide Seiten
88 * dasselbe rechnen und beide Fälle prüfbar sind – auch der, den es heute noch
89 * nicht gibt.
90 *
91 * Abgelehnt wird alles, was kein `https:` ist. `http:` fiele in der
92 * Auslieferung an unbekannte Empfänger auf; `mailto:`, `javascript:` und
93 * Ähnliches haben an dieser Stelle nichts verloren. Zugangsdaten in der
94 * Adresse (`https://name:wort@…`) gelten ebenfalls als Fehleintrag: Sie
95 * gehören in keinen Verweis, den jemand im Browser sieht.
96 *
97 * Ein Vertipper führt damit zu demselben Ergebnis wie ein leeres Feld – das
98 * Angebot entfällt. Eine Fehlermeldung an dieser Stelle wäre für den
99 * Lernenden ein Mangel der Anwendung, obwohl es einer des Eintrags ist.
100 */
101 export function unterstuetzungsziel(url: string): string | null {
102 const adresse = url.trim();
103 if (adresse.length === 0) {
104 return null;
105 }
106
107 let geprueft: URL;
108 try {
109 geprueft = new URL(adresse);
110 } catch {
111 return null;
112 }
113
114 if (geprueft.protocol !== 'https:') {
115 return null;
116 }
117
118 /* Hier stand zusätzlich eine Prüfung auf einen leeren Rechnernamen. Sie ist
119 entfernt, weil sie nicht auslösen kann: Für die besonderen Schemata –
120 `https:` gehört dazu – verlangt der URL-Standard einen Rechnernamen.
121 Nachgemessen mit Node: `https://` wirft `ERR_INVALID_URL` (dann greift der
122 `catch` oben), und `https:///pfad` liefert `pfad` als Rechnernamen. Eine
123 Bedingung, die nie zutrifft, behauptet einen Schutz, den es nicht gibt –
124 dieselbe Überlegung wie bei den stummen Wachen in `sicherheit.test.ts`. */
125
126 if (geprueft.username.length > 0 || geprueft.password.length > 0) {
127 return null;
128 }
129
130 /* Zurück kommt die eingetragene Schreibweise und nicht `geprueft.href`:
131 Angezeigt und geöffnet wird so dieselbe Zeichenkette, und der Vergleich
132 im Hauptprozess trifft genau das, was auf dem Bildschirm stand. */
133 return adresse;
134 }