Inhaltsverzeichnis
Extensions für WShell: Vom leeren Ordner bis zur verschlüsselten WebAPI-Abfrage
WShell führt Extensions aus — dieselben, die auch in VSCode laufen. Dieser Artikel zeigt Schritt für Schritt, wie Sie Ihre erste Extension erstellen, in die WShell-Oberfläche integrieren, ein Master-Detail-Formular entwerfen und über verschlüsselte WebAPI-Aufrufe mit einem Backend kommunizieren.
Bevor Sie starten
Stellen Sie sicher, dass Sie eine laufende WShell haben (Installationsanleitung). Für die Entwicklung brauchen Sie außerdem:
- Node.js 20 LTS oder neuer — als Extension-Host für JavaScript-Extensions
- pas2js 3.3.1 — wenn Sie Pascal statt JavaScript schreiben möchten
- VSCode mit zwei Extensions aus dem WvdS-Ökosystem:
- FPC Solution Manager (
wvds.fpc-solution-manager) — Projekt anlegen, bauen, ausführen - pas2js Studio (
wvds.fpc-pas2js-studio) — visueller Formular-Designer, Code-Generator, Live-Vorschau
Ohne diese beiden Extensions legen Sie Manifest, Compiler-Flags und Formularcode von Hand an — fehlerträchtig und bei jedem neuen Projekt dieselbe Arbeit. Der Solution Manager übernimmt die Projektstruktur: Er kennt die richtigen pas2js-Flags, erzeugt ein konsistentes Build-Skript und bindet das Projekt direkt in die VSCode-Toolchain ein. pas2js Studio ergänzt den visuellen Teil: Formulare entstehen per Drag & Drop statt durch manuelles JSON-Editieren, und der Code-Generator hält Felder, FindComponent-Aufrufe und Event-Bindungen automatisch synchron. Das Ergebnis ist ein reproduzierbarer Workflow, der Einarbeitungszeit verkürzt, Flüchtigkeitsfehler eliminiert und den Weg vom Prototyp zur fertigen Extension auf Minuten statt Stunden reduziert.
Schritt 1: Ein Extension-Projekt mit dem Solution Manager anlegen
Der FPC Solution Manager erzeugt die komplette Projektstruktur für Sie — Manifest, Einstiegspunkt, Build-Skript. Sie müssen nichts von Hand anlegen.
Den Projekt-Wizard starten
Öffnen Sie die Befehlspalette (Ctrl+Shift+P) und wählen Sie:
FPCSE: Start Project Wizard
Der Wizard führt Sie durch fünf Schritte:
- Willkommen — Neues FPC-Projekt, neues pas2js-Projekt oder bestehendes öffnen?
- Toolchain prüfen — Sind FPC, Lazarus und pas2js korrekt konfiguriert?
- Projekttyp wählen — Web App, Node.js CLI, VSCode/WvdS Extension, PWA, …
- Konfigurieren — Name, Speicherort, Build-Optionen
- Zusammenfassung — Übersicht der Dateien, die erzeugt werden
Für eine WShell Extension wählen Sie den Typ „VSCode Extension“ unter der Kategorie pas2js Web.
Was der Wizard erzeugt
Nach Abschluss haben Sie ein vollständiges Projekt:
phonebook/
package.json <-- Manifest (vom Wizard ausgefüllt)
src-pas/
extension.pas <-- Einstiegspunkt mit activate/deactivate
dist/
extension.js <-- Wird beim ersten Build erzeugt
build.ps1 <-- Build-Skript
Der Solution Manager registriert das Projekt automatisch in der Projektbaumansicht. Von dort können Sie bauen (Ctrl+Shift+B), ausführen (F5) und Einstellungen ändern (F4).
Schritt 2: Ein Master-Detail-Formular entwerfen (Telefonbuch)
Jetzt wird es praktisch. Wir entwerfen ein einfaches Telefonbuch mit zwei Bereichen:
- Master (links): Kontaktliste
- Detail (rechts): Name, Telefonnummer, E-Mail des ausgewählten Kontakts
2a. Neues Formular anlegen
Öffnen Sie die Befehlspalette und wählen Sie:
pas2js: New Web Form
Geben Sie als Namen PhonebookForm ein. Das erzeugt zwei Dateien:
PhonebookForm.wfm— die Formularbeschreibung (JSON)PhonebookForm.pas— die Pascal-Unit mit der Formularklasse
2b. Das Layout im Designer aufbauen
Öffnen Sie PhonebookForm.wfm — der visuelle Designer startet automatisch. Er zeigt drei Bereiche:
- Links: Komponentenpalette (Standard, Editors, Data, …)
- Mitte: Formular-Canvas (Drag & Drop)
- Rechts: Eigenschafts-Inspektor
Bauen Sie das Layout Schritt für Schritt auf:
Schritt 1 — Hauptcontainer: Das Formular hat bereits ein leeres flexColumn-Layout. Ändern Sie es auf flexRow (im Inspektor unter Layout > Typ). Das teilt das Formular in eine linke und rechte Hälfte.
Schritt 2 — Master-Panel: Ziehen Sie ein TWPanel aus der Palette auf das Formular. Benennen Sie es MasterPanel. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Layout-Typ | flexColumn |
| Breite | 300 (feste Breite in Pixel) |
| Höhe | 100% |
Schritt 3 — Suchfeld: Ziehen Sie ein TWEdit in das MasterPanel. Benennen Sie es SearchEdit. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Placeholder | Kontakt suchen… |
| Breite | 100% |
| Margin | sm |
Schritt 4 — Kontaktliste: Ziehen Sie eine TWListBox unter das Suchfeld. Benennen Sie sie ContactList. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Breite | 100% |
| FlexGrow | 1 (füllt den restlichen Platz) |
| Margin | sm |
Schritt 5 — Detail-Panel: Ziehen Sie ein zweites TWPanel rechts neben das MasterPanel. Benennen Sie es DetailPanel. Setzen Sie:
| Eigenschaft | Wert |
|---|---|
| Layout-Typ | flexColumn |
| FlexGrow | 1 (füllt den gesamten Restplatz) |
| Margin | md |
Schritt 6 — Detail-Felder: Ziehen Sie in das DetailPanel drei TWEdit-Felder untereinander:
| Name | Placeholder |
|---|---|
NameEdit | Vorname Nachname |
PhoneEdit | +43 1 234 5678 |
EmailEdit | name@example.com |
Schritt 7 — Speichern-Button: Ziehen Sie einen TWButton unter die drei Felder. Benennen Sie ihn SaveButton, Text: „Speichern“.
2c. Die WFM-Datei im Überblick
Der Designer speichert alles als JSON. Sie können die WFM-Datei jederzeit auch direkt als JSON bearbeiten — der Designer und der Text-Editor bleiben synchron.
Schritt 3: Events binden
Das Formular sieht jetzt gut aus, aber es tut noch nichts. Jetzt verbinden wir Benutzeraktionen mit Pascal-Code.
3a. Ein Event im Designer verdrahten
Klicken Sie im Designer auf ContactList. Im rechten Inspektor wechseln Sie zum Tab Events. Dort sehen Sie:
| Event | Handler |
|---|---|
OnClick | (leer) |
OnDblClick | (leer) |
Doppelklicken Sie auf die leere Zeile neben OnClick. Der Designer:
- Erzeugt einen Handler-Namen (
ContactListClick) - Fügt die Deklaration in die
.pas-Datei ein - Fügt den Rumpf in die Implementation ein
- Springt direkt zur neuen Prozedur
Wiederholen Sie das für:
| Komponente | Event | Erzeugter Handler |
|---|---|---|
SearchEdit | OnChange | SearchEditChange |
SaveButton | OnClick | SaveButtonClick |
3b. Was der Code-Generator in der .pas-Datei macht
Der Designer pflegt automatisch zwei markierte Bereiche in Ihrer Unit: Felder (zwischen { WFM-AUTO-BEGIN } und { WFM-AUTO-END }) sowie FindComponent-Aufrufe und Event-Bindungen in AfterCreate. Alles außerhalb dieser Marker bleibt bei jeder Regenerierung erhalten — Sie können beliebig eigenen Code ergänzen, ohne dass der Designer ihn überschreibt.
3c. Die Event-Handler implementieren
Jetzt füllen Sie die leeren Handler mit Logik. ContactListClick zeigt die Detail-Felder des ausgewählten Kontakts, SearchEditChange filtert die Liste live, und SaveButtonClick schreibt Änderungen zurück ins Array.
Schritt 4: Daten an das Formular binden
Das Telefonbuch braucht Kontaktdaten. Diese kommen aus dem Shell DataBridge (Schritt 8) oder — zum Testen — aus einem lokalen Array.
4a. Ein Datenmodell definieren
Ergänzen Sie in der Unit ein Record und ein Array:
type TContact = record Name: string; Phone: string; Email: string; end; var FContacts: array of TContact;
4b. Kontakte beim Formular-Start laden
Fügen Sie eine Lademethode hinzu und rufen Sie sie in AfterCreate auf (nach den WFM-Markern):
procedure TPhonebookForm.LoadContacts; begin SetLength(FContacts, 3); FContacts[0].Name := 'Anna Huber'; FContacts[0].Phone := '+43 1 234 5678'; FContacts[0].Email := 'anna@example.com'; FContacts[1].Name := 'Max Bauer'; FContacts[1].Phone := '+43 1 876 5432'; FContacts[1].Email := 'max@example.com'; FContacts[2].Name := 'Lisa Gruber'; FContacts[2].Phone := '+43 1 555 0000'; FContacts[2].Email := 'lisa@example.com'; FContactList.Items.Clear; for var I := 0 to High(FContacts) do FContactList.Items.Add(FContacts[I].Name); end;
4c. Daten stattdessen aus der Datenbank laden
Wenn die Extension in WShell läuft und ein Daten-Provider konfiguriert ist, ersetzen Sie LoadContacts durch eine DataBridge-Abfrage. Der DataBridge kümmert sich um die Datenbankverbindung — Ihre Extension sendet nur die SQL-Abfrage und empfängt ein JSON-Ergebnis.
Schritt 5: Bauen, testen, verpacken
5a. Im Solution Manager bauen
Drücken Sie Ctrl+Shift+B oder klicken Sie im Projektbaum auf Build. Der Solution Manager:
- Ruft pas2js mit den richtigen Flags auf (
-Tnodejs -Jc -Jirtl.js) - Sammelt alle Unit-Pfade aus Ihrer Projektkonfiguration
- Zeigt Fehler, Warnungen und Hinweise im Diagnose-Panel
Ziel: 0 Fehler, 0 Warnungen, 0 Hinweise.
5b. In der WShell testen
Klicken Sie im Projektbaum mit der rechten Maustaste auf Ihr Projekt und wählen Sie Extension Build → Install Extension. Der Solution Manager baut die Extension, erzeugt das VSIX-Paket und installiert es direkt in die lokale WShell-Instanz. Nach einem Neustart der WShell erscheint Ihr Telefonbuch als neuer Tab.
5c. Als VSIX verpacken
Rechtsklick auf das Projekt → Extension Build → Package VSIX. Der Solution Manager:
- Erstellt einen Release-Build (optimiert, ohne Debug-Symbole)
- Erzeugt das VSIX-Paket unter
binaries/vsix/{name}-{version}.vsix - Zeigt das Ergebnis im Output-Panel
Die fertige .vsix-Datei können Sie an andere Entwickler weitergeben oder über den Extension-Manager installieren. Zum Veröffentlichen im Marketplace verwenden Sie Extension Build → Publish Extension.
Falls Sie den Solution Manager nicht verwenden oder den Build in eine CI/CD-Pipeline einbinden möchten, können Sie eine eigene build.ps1 schreiben. Das folgende Unter-Howto zeigt Ihnen den Aufbau Schritt für Schritt.
Schritt 6: Menüs in die WShell einbinden
Ab hier geht es um die Integration Ihrer fertigen Extension in die WShell-Oberfläche. Die WShell hat eine Menüleiste (Datei, Bearbeiten, Ansicht, …). Ihre Extension kann Einträge zu bestehenden Menüs hinzufügen oder komplett neue Menüs anlegen.
Einträge zu einem bestehenden Menü hinzufügen
In der package.json tragen Sie unter contributes.menus ein, wo Ihr Befehl erscheinen soll. Der Schlüssel ist der Menü-Ort, der Wert ist eine Liste von Einträgen.
{
"contributes": {
"commands": [
{
"command": "myext.export",
"title": "Daten exportieren",
"category": "Meine Extension"
}
],
"menus": {
"menuBar/file": [
{
"command": "myext.export",
"group": "export",
"order": 1
}
]
}
}
}
Dieser Eintrag fügt „Daten exportieren“ ins Datei-Menü ein.
So funktioniert das Zusammenführen:
- Die WShell sammelt alle
menus-Beiträge aus allen Extensions menuBar/filebedeutet: suche das bestehende Datei-Menü- Einträge werden nach Gruppe (alphabetisch) und Reihenfolge (numerisch) sortiert
- Trennlinien zwischen verschiedenen Gruppen entstehen automatisch
Ein neues Top-Level-Menü anlegen
Um ein eigenes Menü (z.B. „Werkzeuge“) hinzuzufügen, verwenden Sie Untermenüs:
{
"contributes": {
"submenus": [
{ "id": "myext.tools", "label": "Werkzeuge" }
],
"menus": {
"menuBar": [
{ "submenu": "myext.tools", "order": 50 }
],
"myext.tools": [
{ "command": "myext.export", "group": "io", "order": 1 },
{ "command": "myext.import", "group": "io", "order": 2 }
]
}
}
}
Die WShell verarbeitet das in zwei Durchläufen:
- Durchlauf 1:
menuBar→ legt das neue Top-Level-Menü „Werkzeuge“ an - Durchlauf 2:
myext.tools→ füllt es mit Ihren Befehlen
Wo können Menü-Einträge erscheinen?
| Ort | Wo er erscheint |
|---|---|
menuBar | Neues Top-Level-Menü |
menuBar/file | Im Datei-Menü |
commandPalette | In der Befehlspalette (Ctrl+Shift+P) |
view/title | Als Aktionsbutton in der Kopfzeile einer Ansicht |
view/item/context | Im Rechtsklick-Menü auf Baumeinträge |
Schritt 7: QuickBar-Buttons in die Titelleiste einbinden
Die QuickBar ist die Reihe von Symbolbuttons in der Titelleiste der WShell — Schnellzugriffe, die immer sichtbar sind.
Wie QuickBar-Buttons funktionieren
Im Gegensatz zu Menüs werden QuickBar-Buttons nicht über die package.json definiert, sondern zur Laufzeit bereitgestellt. Der Ablauf:
- Ein Tab wird aktiv → die WShell fragt: „Welche Schnellbefehle hast du?“
- Der Tab liefert eine Liste von Buttons zurück
- Die WShell baut die QuickBar mit diesen Buttons neu auf
- Wird ein anderer Tab aktiv, werden die Buttons entfernt und durch neue ersetzt
Buttons in Pascal definieren
function TMyTab.GetQuickCommands: TWvdSQuickCommands; begin SetLength(Result, 2); Result[0].CommandID := 'myext.save'; Result[0].ImageIndex := 0; Result[0].Hint := 'Speichern'; Result[0].Enabled := True; Result[0].Visible := True; Result[0].OnExecute := @HandleSave; Result[1].CommandID := 'myext.refresh'; Result[1].ImageIndex := 1; Result[1].Hint := 'Aktualisieren'; Result[1].Enabled := True; Result[1].Visible := True; Result[1].OnExecute := @HandleRefresh; end;
Zwei Bereiche in der QuickBar
Die QuickBar zeigt zwei Gruppen, getrennt durch eine sichtbare Linie:
| Seite | Quelle | Beispiel |
|---|---|---|
| Links | Editor-Aktionen | Speichern, Rückgängig |
| Rechts | Globale Schnellbefehle | Aktualisieren, Einstellungen |
Wenn mehr Buttons vorhanden sind als Platz in der Titelleiste, erscheint automatisch ein „…“ Überlauf-Menü.
Schritt 8: WShell-Datendienste für WebAPI-Abfragen nutzen
Die WShell stellt einen DataBridge bereit. Damit kann Ihre Extension Datenbanken abfragen oder HTTP-Endpunkte ansprechen — ohne eigenen Low-Level-Code.
So funktioniert der Datenfluss
Ihre Extension sendet eine Nachricht an die WShell. Die WShell leitet alles, was mit data. beginnt, an den DataBridge weiter:
Ihre Extension --> postMessage({command: 'data.query', ...})
--> SystemHandler
--> DataBridge
--> Datenbank-Provider
--> JSON-Ergebnis zurück an Ihre Extension
Verfügbare Datenbefehle
| Befehl | Zweck | Gibt zurück |
|---|---|---|
data.query | SELECT-Abfrage | Spalten und Zeilen |
data.execute | INSERT/UPDATE/DELETE | Anzahl betroffener Zeilen |
data.applyDelta | Massenänderungen | Anzahl betroffener Zeilen |
data.getSchema | Tabellenstruktur | Spaltenname, Typ, Nullable |
data.getTables | Tabellen auflisten | Liste der Tabellennamen |
data.ping | Verbindung testen | ok: true/false |
Beispiel: Daten aus der Extension abfragen
// In Ihrer Extension: // 1. Abfrage an die WShell senden window.wvdxHost.postMessage({ type: 'request', command: 'data.query', id: 1, params: { provider: 'main', query: 'SELECT id, name, email FROM customers WHERE active = :active', parameters: { active: true } } }); // 2. Ergebnis empfangen window.addEventListener('message', function(event) { var msg = event.data; if (msg.id === 1 && msg.result) { console.log('Spalten:', msg.result.columns); console.log('Zeilen:', msg.result.rows); } });
Den Daten-Provider konfigurieren
In der settings.json der WShell definieren Sie Ihren Provider:
{
"data.providers": {
"main": {
"type": "sqldb",
"connection": "Host=localhost;Database=mydb;User=app;Password=..."
}
}
}
Wichtig zur Sicherheit:
- Alle Abfragen nutzen parametrisierte Statements — kein SQL-Injection möglich
- Verbindungszeichenfolgen werden nie an die Extension weitergegeben
- SQL-Fehlermeldungen werden bereinigt, bevor sie zurückgesendet werden
Schritt 9: Payloads mit Post-Quantum-Kryptographie verschlüsseln
Die WShell enthält Post-Quantum-Kryptographie (PQC) auf Basis von OpenSSL 3.6. Damit kann Ihre Extension Daten verschlüsseln, bevor sie an einen WebAPI-Endpunkt gesendet werden — zum Beispiel den WvdS Gateway Service.
Warum Post-Quantum?
Herkömmliche Verschlüsselung (RSA, ECDH) wird durch Quantencomputer gebrochen werden. Die neuen NIST-Standards sind gegen Quantenangriffe resistent (mehr dazu):
| Algorithmus | Standard | Zweck |
|---|---|---|
| ML-KEM (Kyber) | FIPS 203 | Schlüsselaustausch |
| ML-DSA (Dilithium) | FIPS 204 | Digitale Signaturen |
| AES-256-GCM | — | Symmetrische Verschlüsselung (schnell, für Nutzdaten) |
| HKDF-SHA256 | — | Schlüsselableitung |
So verschlüsselt die WShell Ihren Payload
Wenn Ihre Extension crypto.encrypt aufruft, läuft intern ein vierstufiger Prozess:
- ML-KEM Encapsulate mit dem öffentlichen Schlüssel des Empfängers → gemeinsames Geheimnis + KEM-Chiffretext
- HKDF-SHA256 aus dem Geheimnis → AES-256-Schlüssel (32 Bytes)
- AES-GCM-Encrypt mit dem AES-Schlüssel → Nonce + Chiffretext + Authentifizierungs-Tag
- Zusammenpacken →
{ kemCiphertext, aesPayload }
Der Empfänger (z.B. Gateway) kehrt den Prozess mit seinem privaten ML-KEM-Schlüssel um.
Verfügbare Krypto-Befehle
| Befehl | Zweck |
|---|---|
crypto.isAvailable | Prüfen, ob OpenSSL 3.6 verfügbar ist |
crypto.encrypt | Payload PQ-verschlüsseln (ML-KEM + AES-GCM) |
crypto.decrypt | PQ-verschlüsselten Payload entschlüsseln |
crypto.sign | ML-DSA digitale Signatur erstellen |
crypto.verify | ML-DSA Signatur prüfen |
crypto.generateKeyPair | ML-KEM oder ML-DSA Schlüsselpaar erzeugen |
crypto.keyStore.generate | Schlüsselpaar erzeugen und speichern |
crypto.keyStore.list | Gespeicherte Schlüssel auflisten |
crypto.keyStore.rotate | Schlüssel rotieren (neuer Schlüssel, alter bleibt) |
Beispiel: Einen Payload für den Gateway verschlüsseln
Der Ablauf in vier Schritten: PQC-Verfügbarkeit prüfen, Schlüsselpaar erzeugen, Payload verschlüsseln, verschlüsseltes Ergebnis per HTTPS an den Gateway senden.
Der Gateway Service als Endpunkt
Der WvdS Gateway Service ist ein Backend, das PQ-verschlüsselte Payloads nativ versteht. Er sitzt zwischen Ihrer Extension und dem eigentlichen API:
Extension --> PQ-Encrypt(Payload)
--> HTTPS POST an Gateway
--> Gateway-Middleware entschlüsselt
--> Weiterleitung an Backend-API
--> Antwort wird zurück verschlüsselt
--> Extension entschlüsselt Ergebnis
Der Gateway unterstützt drei Verschlüsselungsmodi:
| Modus | Wann verwendet | Schlüsselquelle |
|---|---|---|
| Session-basiert | X-Session-Id Header vorhanden | ML-KEM-abgeleiteter Sitzungsschlüssel |
| Statisch | Kein Session-Header | HKDF aus MasterSecret |
| Durchleitung | Verschlüsselung deaktiviert | Keine Verschlüsselung |
Sicherheitsgarantien
- Private Schlüssel verlassen nie die WShell — Ihre Extension erhält nur öffentliche Schlüssel
- Schlüsselmaterial wird aus dem Speicher gelöscht nach Verwendung
- OpenSSL-Fehlermeldungen werden bereinigt — keine internen Details gelangen zur Extension
- Maximale Payload-Größe: 10 MB (Schutz gegen Denial-of-Service)
Zusammenfassung
| Schritt | Thema | Werkzeug / Mechanismus |
|---|---|---|
| 1 | Projekt anlegen | FPC Solution Manager → FPCSE: Start Project Wizard |
| 2 | Formular-Layout entwerfen | pas2js Studio → WFM-Designer (Drag & Drop) |
| 3 | Events binden | Designer → Doppelklick auf Event → Handler wird erzeugt |
| 4 | Daten binden | TContact-Record lokal oder per DataBridge aus DB |
| 5 | Bauen und testen | Solution Manager → Rechtsklick → Extension Build (Build / Install / Package VSIX) |
| 6 | Menü-Integration | contributes.menus mit Ort-Schlüsseln (menuBar/file etc.) |
| 7 | QuickBar-Buttons | IWvdSShellContribution.GetQuickCommands() zur Laufzeit |
| 8 | Datenabfragen | data.query per postMessage an den DataBridge |
| 9 | PQ-Verschlüsselung | crypto.encrypt (ML-KEM + AES-256-GCM) über CryptoBridge + Gateway |