Kabinenfieber-Codierung: Ein Node.js-Back-End-Tutorial
Veröffentlicht: 2022-03-11Die COVID-19-Sperre hat viele von uns zu Hause festsitzen lassen, vielleicht in der Hoffnung, dass bloßes Lagerkoller die schlimmste Art von Fieber ist, die wir erleben werden. Viele von uns konsumieren mehr Videoinhalte als je zuvor. Während Bewegung gerade jetzt besonders wichtig ist, gibt es manchmal Nostalgie für den Luxus einer guten, altmodischen Fernbedienung, wenn der Laptop nicht in Reichweite ist.
Hier kommt dieses Projekt ins Spiel: Die Möglichkeit, jedes Smartphone – selbst ein altes, das ansonsten mangels Updates unbrauchbar ist – in eine handliche Fernbedienung für das nächste Netflix/YouTube/Amazon Prime Video/etc. zu verwandeln. Binge-Watch. Es ist auch ein Node.js-Back-End-Tutorial: eine Gelegenheit, die Grundlagen von Back-End-JavaScript mit dem Express-Framework und der Vorlagen-Engine Pug (ehemals Jade) zu erlernen.
Wenn das abschreckend klingt, wird das vollständige Node.js-Projekt am Ende präsentiert; Leser müssen nur so viel lernen, wie sie am Lernen interessiert sind, und es wird eine ganze Reihe sanfterer Erklärungen einiger Grundlagen auf dem Weg geben, die erfahrenere Leser überspringen können.
Warum nicht einfach...?
Leser fragen sich vielleicht: „Warum sollte man sich mit der Programmierung eines Node.js-Backends beschäftigen?“ (Abgesehen von der Lernmöglichkeit natürlich.) „Gibt es dafür nicht schon eine App?“
Sicher – viele davon. Aber es gibt zwei Hauptgründe, warum dies möglicherweise nicht wünschenswert ist:
- Für diejenigen, die versuchen, ein älteres Telefon wiederzuverwenden, ist dies möglicherweise einfach keine Option mehr , wie es bei dem Windows Phone 8.1-Gerät der Fall ist, das ich verwenden wollte. (Der App Store wurde Ende 2019 offiziell geschlossen.)
- Vertrauen (oder dessen Fehlen). Wie so viele Apps, die auf jeder mobilen Plattform zu finden sind, kommen sie oft mit der Anforderung, dass Benutzer weit mehr Berechtigungen erteilen müssen, als die App für das benötigt, was sie vorgibt zu tun. Aber selbst wenn dieser Aspekt angemessen eingeschränkt ist, bedeutet die Natur einer Fernsteuerungs-App, dass Benutzer immer noch darauf vertrauen müssen, dass App-Entwickler ihre Privilegien auf der Desktop-Seite der Lösung nicht missbrauchen, indem sie Spyware oder andere Malware einschließen.
Diese Probleme gibt es schon lange und waren sogar die Motivation für ein ähnliches Projekt aus dem Jahr 2014, das auf GitHub gefunden wurde. nvm
macht es einfach, ältere Versionen von Node.js zu installieren, und selbst wenn einige Abhängigkeiten aktualisiert werden mussten, hatte Node.js einen hervorragenden Ruf für Abwärtskompatibilität.
Leider hat bitrot gewonnen. Ein hartnäckiger Ansatz und die Back-End-Kompatibilität von Node.js waren kein Gegner für endlose Verwerfungen und unmögliche Abhängigkeitsschleifen zwischen alten Versionen von Grunt, Bower und Dutzenden anderer Komponenten. Stunden später war mehr als klar, dass es viel einfacher sein würde, ganz von vorn anzufangen – ungeachtet des Ratschlags dieses Autors, das Rad nicht neu zu erfinden.
Neue Gizmos aus alten: Neuverwendung von Telefonen als Fernbedienungen mit einem Node.js-Backend
Beachten Sie zunächst, dass dieses Node.js-Projekt derzeit spezifisch für Linux ist – insbesondere unter Linux Mint 19 und Linux Mint 19.3 entwickelt und getestet –, aber die Unterstützung für andere Plattformen könnte sicherlich hinzugefügt werden. Es kann bereits auf einem Mac funktionieren.
Angenommen, eine moderne Version von Node.js ist installiert und eine Eingabeaufforderung ist in einem neuen Verzeichnis geöffnet, das als Projektstamm dient, können wir mit Express loslegen:
npx express-generator --view=pug
Hinweis: Hier ist npx
ein praktisches Tool, das mit npm
geliefert wird, dem Node.js-Paketmanager, der mit Node.js ausgeliefert wird. Wir verwenden es, um den Anwendungsskelettgenerator von Express auszuführen. Während ich dies schreibe, erstellt der Generator ein Express/Node.js-Projekt, das standardmäßig immer noch eine Vorlagen-Engine namens Jade einzieht, obwohl sich das Jade-Projekt ab Version 2.0 in „Pug“ umbenannt hat. Um also auf dem neuesten Stand zu sein und Pug sofort zu verwenden – und um Verfallswarnungen zu vermeiden – setzen wir auf --view=pug
, eine Befehlszeilenoption für das express-generator
Skript, das von npx
ausgeführt wird.
Sobald dies erledigt ist, müssen wir einige Pakete aus der neu gefüllten Abhängigkeitsliste unseres Node.js-Projekts in package.json
. Der traditionelle Weg, dies zu tun, besteht darin, npm i
( i
für „install“) auszuführen. Einige bevorzugen jedoch immer noch die Geschwindigkeit von Yarn. Wenn Sie das also installiert haben, führen Sie einfach yarn
ohne Parameter aus.
In diesem Fall sollte es sicher sein, die (hoffentlich bald behobene) Verfallswarnung von einer der Unterabhängigkeiten von Pug zu ignorieren, solange der Zugriff im lokalen Netzwerk nur nach Bedarf erfolgt.
Ein schneller yarn start
oder npm start
, gefolgt von der Navigation zu localhost:3000
in einem Browser, zeigt, dass unser einfaches Express-basiertes Node.js-Backend funktioniert. Wir können es mit Ctrl+C
.
Node.js-Back-End-Tutorial, Schritt 2: So senden Sie Tastenanschläge auf dem Hostcomputer
Nachdem der Remote -Teil bereits zur Hälfte fertig ist, wenden wir uns dem Steuerungsteil zu. Wir brauchen etwas, das die Maschine, auf der wir unser Node.js-Backend ausführen, programmgesteuert steuern kann und so tut, als würde sie Tasten auf der Tastatur drücken.
Dazu installieren wir xdotool
den offiziellen Anweisungen. Ein schneller Test ihres Beispielbefehls in einem Terminal:
xdotool search "Mozilla Firefox" windowactivate --sync key --clearmodifiers ctrl+l
… sollte genau das tun, was es sagt, vorausgesetzt, Mozilla Firefox ist zu diesem Zeitpunkt geöffnet. Das ist gut! Es ist einfach, unser Node.js-Projekt dazu zu bringen, Befehlszeilentools wie xdotool
, wie wir bald sehen werden.
Node.js-Backend-Tutorial, Schritt 3: Feature-Design
Das mag nicht für alle gelten, aber ich persönlich finde, dass viele moderne physische Fernbedienungen etwa fünfmal so viele Tasten haben, wie ich jemals benutzen werde. Für dieses Projekt betrachten wir also ein Vollbild-Layout mit einem Drei-mal-Drei-Raster aus schönen, großen, einfach zu bedienenden Schaltflächen. Es liegt an den persönlichen Vorlieben, was diese neun Tasten sein können.
Es stellt sich heraus, dass die Tastaturkürzel selbst für die einfachsten Funktionen bei Netflix, YouTube und Amazon Prime Video nicht identisch sind. Diese Dienste funktionieren auch nicht mit generischen Medienschlüsseln, wie dies bei einer nativen Musikplayer-App wahrscheinlich der Fall ist. Außerdem sind bestimmte Funktionen möglicherweise nicht bei allen Diensten verfügbar.
Wir müssen also für jeden Dienst ein anderes Fernbedienungslayout definieren und eine Möglichkeit bieten, zwischen ihnen zu wechseln.
Definieren von Fernbedienungslayouts und deren Zuordnung zu Tastaturkürzeln
Lassen Sie uns einen schnellen Prototyp erstellen, der mit einer Handvoll Voreinstellungen arbeitet. Wir werden sie in common/preset_commands.js
– „common“, weil wir diese Daten aus mehr als einer Datei einschließen:
module.exports = { // We could use ️ but some older phones (eg, Android 5.1.1) won't show it, hence ️ instead 'Netflix': { commands: { '-': 'Escape', '+': 'f', '': 'Up', '⇤': 'XF86Back', '️': 'Return', '': 'Down', '': 'Left', '': 'Right', '': 'm', }, }, 'YouTube': { commands: { '⇤': 'shift+p', '⇥': 'shift+n', '': 'Up', 'CC': 'c', '️': 'k', '': 'Down', '': 'j', '': 'l', '': 'm', }, }, 'Amazon Prime Video': { window_name_override: 'Prime Video', commands: { '⇤': 'Escape', '+': 'f', '': 'Up', 'CC': 'c', '️': 'space', '': 'Down', '': 'Left', '': 'Right', '': 'm', }, }, 'Generic / Music Player': { window_name_override: '', commands: { '⇤': 'XF86AudioPrev', '⇥': 'XF86AudioNext', '': 'XF86AudioRaiseVolume', '': 'r', '️': 'XF86AudioPlay', '': 'XF86AudioLowerVolume', '': 'Left', '': 'Right', '': 'XF86AudioMute', }, }, };
Die Keycode-Werte können mit xev
gefunden werden. (Für mich waren die „audio mute“ und „audio play“ mit dieser Methode nicht erkennbar, also habe ich auch eine Liste von Medientasten konsultiert.)
Leser bemerken möglicherweise den Unterschied zwischen space
und Return
– unabhängig vom Grund dafür muss dieses Detail beachtet werden, damit xdotool
korrekt funktioniert. Damit zusammenhängend haben wir ein paar Definitionen explizit geschrieben – z. B. shift+p
, obwohl P
auch funktionieren würde – nur um unsere Absichten klar zu halten.
Node.js-Backend-Tutorial, Schritt 4: Der „Schlüssel“-Endpunkt unserer API (Pardon the Pun)
Wir benötigen einen Endpunkt für den POST
, der wiederum Tastenanschläge mit xdotool
simuliert. Da wir verschiedene Gruppen von Schlüsseln haben, die wir senden können (eine für jeden Dienst), rufen wir den Endpunkt für eine bestimmte group
. Wir werden den generierten users
umbenennen, indem wir routes/users.js
in routes/group.js
und die entsprechenden Änderungen in app.js
:
// ... var indexRouter = require('./routes/index'); var groupRouter = require('./routes/group'); // ... app.use('/', indexRouter); app.use('/group', groupRouter); // ...
Die Schlüsselfunktionalität ist die Verwendung von xdotool
über einen System-Shell-Aufruf in routes/group.js
. Wir werden YouTube
vorerst nur zu Testzwecken als Menü der Wahl fest codieren.
const express = require('express'); const router = express.Router(); const debug = require('debug')('app'); const cp = require('child_process'); const preset_commands = require('../common/preset_commands'); /* POST keystroke to simulate */ router.post('/', function(req, res, next) { const keystroke_name = req.body.keystroke_name; const keystroke_code = preset_commands['YouTube'].commands[keystroke_name]; const final_command = `xdotool \ search "YouTube" \ windowactivate --sync \ key --clearmodifiers ${keystroke_code}`; debug(`Executing ${final_command}`); cp.exec(final_command, (err, stdout, stderr) => { debug(`Executed ${keystroke_name}`); return res.redirect(req.originalUrl); }); }); module.exports = router;
Hier holen wir uns den angeforderten Schlüssel „Name“ aus dem Körper der POST
-Anforderung ( req.body
) unter dem Parameter namens keystroke_name
. Das wird so etwas wie ️
. Wir verwenden dies dann, um den entsprechenden Code aus dem Befehlsobjekt von commands
preset_commands['YouTube']
.
Der letzte Befehl befindet sich in mehr als einer Zeile, sodass das \
s am Ende jeder Zeile alle Teile zu einem einzigen Befehl verbindet:
-
search "YouTube"
ruft das erste Fenster mit "YouTube" im Titel ab. -
windowactivate --sync
aktiviert das abgerufene Fenster und wartet, bis es bereit ist, einen Tastendruck zu empfangen. -
key --clearmodifiers ${keystroke_code}
sendet den Tastendruck und stellt sicher, dass Modifikatortasten wie die Feststelltaste vorübergehend gelöscht werden, die das, was wir senden, beeinträchtigen könnten.
An diesem Punkt geht der Code davon aus, dass wir ihn mit gültigen Eingaben füttern – etwas, worauf wir später noch genauer eingehen werden.
Der Einfachheit halber geht der Code auch davon aus, dass nur ein Anwendungsfenster mit „YouTube“ im Titel geöffnet ist – wenn es mehr als eine Übereinstimmung gibt, gibt es keine Garantie dafür, dass wir Tastenanschläge an das beabsichtigte Fenster senden. Wenn das ein Problem ist, kann es hilfreich sein, dass Fenstertitel einfach geändert werden können, indem Sie die Browser-Tabs in allen Fenstern außer dem, das ferngesteuert werden soll, wechseln.
Damit können wir unseren Server erneut starten, diesmal jedoch mit aktiviertem Debugging, damit wir die Ausgabe unserer debug
-Aufrufe sehen können. Führen Sie dazu einfach DEBUG=old-fashioned-remote:* yarn start
oder DEBUG=old-fashioned-remote:* npm start
. Sobald es ausgeführt wird, spielen Sie ein Video auf YouTube ab, öffnen Sie ein anderes Terminalfenster und versuchen Sie einen cURL-Aufruf:
curl --data "keystroke_name=️" http://localhost:3000/group
Dadurch wird eine POST
-Anforderung mit dem angeforderten Tastendrucknamen im Text an unseren lokalen Computer an Port 3000
, dem Port, an dem unser Back-End lauscht. Das Ausführen dieses Befehls sollte Hinweise zu Executing
und Executed
im npm
-Fenster ausgeben und, was noch wichtiger ist, den Browser aufrufen und sein Video anhalten. Wenn Sie diesen Befehl erneut ausführen, sollte die gleiche Ausgabe erfolgen und die Pause aufgehoben werden.
Node.js-Backend-Tutorial, Schritt 5: Mehrere Remote-Control-Layouts
Unser Backend ist noch nicht ganz fertig. Wir benötigen es auch, um Folgendes tun zu können:
- Erzeugt eine Liste von Fernbedienungslayouts aus
preset_commands
. - Erstellen Sie eine Liste mit „Namen“ von Tastenanschlägen, sobald wir uns für ein bestimmtes Fernbedienungslayout entschieden haben. (Wir hätten uns auch dafür entscheiden können,
common/preset_commands.js
direkt am Frontend zu verwenden, da es bereits JavaScript ist, und dort gefiltert. Das ist einer der potenziellen Vorteile eines Node.js-Backends, wir verwenden es hier einfach nicht .)
In beiden Funktionen überschneidet sich unser Node.js-Back-End-Tutorial mit dem Pug-basierten Front-End, das wir erstellen werden.
Verwenden von Pug-Vorlagen zum Präsentieren einer Liste von Fernbedienungen
Der Back-End-Teil der Gleichung bedeutet, dass die Datei " routes/index.js
" so geändert wird, dass sie wie folgt aussieht:
const express = require('express'); const router = express.Router(); const preset_commands = require('../common/preset_commands'); /* GET home page. */ router.get('/', function(req, res, next) { const group_names = Object.keys(preset_commands); res.render('index', { title: 'Which Remote?', group_names, portrait_css: `.group_bar { height: calc(100%/${Math.min(4, group_names.length)}); line-height: calc(100vh/${Math.min(4, group_names.length)}); }`, landscape_css: `.group_bar { height: calc(100%/${Math.min(2, group_names.length)}); line-height: calc(100vh/${Math.min(2, group_names.length)}); }`, }); }); module.exports = router;
Hier holen wir uns die Namen unserer Fernbedienungslayouts ( group_names
), indem Object.keys
in unserer Datei preset_commands
. Anschließend senden wir sie und einige andere Daten, die wir benötigen, an die Pug-Vorlagen-Engine, die automatisch über res.render()
aufgerufen wird.
Achten Sie darauf, die Bedeutung von keys
hier nicht mit den von uns Object.keys
gibt uns ein Array (eine geordnete Liste) mit allen Schlüsseln der Schlüssel-Wert-Paare , aus denen ein Objekt in JavaScript besteht:

const my_object = { 'a key' : 'its corresponding value' , 'another key' : 'its separate corresponding value' , };
Wenn wir uns common/preset_commands.js
, sehen wir das obige Muster, und unsere Schlüssel (im Objektsinn) sind die Namen unserer Gruppen: 'Netflix'
, 'YouTube'
usw. Ihre entsprechenden Werte sind es nicht einfache Strings wie my_object
oben – sie sind selbst ganze Objekte mit ihren eigenen Schlüsseln, dh commands
und möglicherweise window_name_override
.
Das benutzerdefinierte CSS, das hier übergeben wird, ist zugegebenermaßen ein bisschen wie ein Hack. Der Grund, warum wir es überhaupt brauchen, anstatt eine moderne, Flexbox-basierte Lösung zu verwenden, ist die bessere Kompatibilität mit der wunderbaren Welt der mobilen Browser in ihren noch wunderbareren älteren Inkarnationen. In diesem Fall ist vor allem zu beachten, dass wir im Querformat die Schaltflächen groß halten, indem wir nicht mehr als zwei Optionen pro Bildschirminhalt anzeigen. im Hochformat vier.
Aber wo wird das eigentlich in HTML umgewandelt, das an den Browser gesendet wird? Hier kommt views/index.pug
ins Spiel, das wie folgt aussehen soll:
extends layout block header_injection style(media='(orientation: portrait)') #{portrait_css} style(media='(orientation: landscape)') #{landscape_css} block content each group_name in group_names span(class="group_bar") a(href='/group/?group_name=' + group_name) #{group_name}
Die allererste Zeile ist wichtig: Erweitert das extends layout
bedeutet, dass Pug diese Datei im Kontext von views/layout.pug
, was eine Art übergeordnete Vorlage ist, die wir hier und auch in einer anderen Ansicht wiederverwenden werden. Wir müssen nach der link
-Zeile ein paar Zeilen hinzufügen, damit die endgültige Datei so aussieht:
doctype html html head title= title link(rel='stylesheet', href='/stylesheets/style.css') block header_injection meta(name='viewport', content='user-scalable=no') body block content
Wir werden hier nicht auf die Grundlagen von HTML eingehen, aber für Leser, die damit nicht vertraut sind, spiegelt dieser Pug-Code Standard-HTML-Code wider, der fast überall zu finden ist. Der Templating -Aspekt beginnt mit title= title
, wodurch der HTML-Titel auf einen beliebigen Wert gesetzt wird, der dem title
des Objekts entspricht, das wir Pug über res.render
.
Zwei Zeilen später sehen wir einen anderen Aspekt der Vorlagenerstellung mit einem block
, den wir header_injection
. Blöcke wie diese sind Platzhalter, die durch Vorlagen ersetzt werden können, die die aktuelle erweitern. (Unabhängig davon ist die meta
-Zeile einfach eine schnelle Problemumgehung für mobile Browser. Wenn Benutzer also mehrmals hintereinander auf die Lautstärkeregler tippen, zoomt das Telefon nicht hinein oder heraus.)
Zurück zu unseren block
: Aus diesem Grund definiert views/index.pug
seine eigenen block
mit denselben Namen wie in views/layout.pug
. In diesem Fall von header_injection
können wir CSS verwenden, das für die Ausrichtung des Telefons im Hoch- oder Querformat spezifisch ist.
content
ist, wo wir den sichtbaren Hauptteil der Webseite platzieren, der in diesem Fall:
- Durchläuft das Array
group_names
, das wir übergeben, - erstellt für jedes Element ein
<span>
-Element, auf das die CSS-Klassegroup_bar
angewendet wird, und - erstellt einen Link innerhalb jedes
<span>
basierend aufgroup_name
.
Die CSS-Klasse group_bar
können wir in der über views/layout.pug
eingezogenen Datei definieren, nämlich public/stylesheets/style.css
:
html, body, form { padding: 0; margin: 0; height: 100%; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } .group_bar, .group_bar a, .remote_button { box-sizing: border-box; border: 1px solid white; color: greenyellow; background-color: black; } .group_bar { width: 100%; font-size: 6vh; text-align: center; display: inline-block; } .group_bar a { text-decoration: none; display: block; }
Wenn npm start
noch läuft, sollten zu diesem Zeitpunkt in einem Desktop-Browser zu http://localhost:3000/
zwei sehr große Schaltflächen für Netflix und YouTube angezeigt werden, wobei der Rest durch Scrollen nach unten verfügbar ist.
Aber wenn wir sie an dieser Stelle anklicken, funktionieren sie nicht, weil wir noch nicht die Route definiert haben, auf die sie verlinken (der GET
-Ting von /group
.)
Zeigt das gewählte Fernbedienungslayout
Dazu fügen wir dies direkt vor der letzten module.exports
-Zeile zu routes/group.js
hinzu:
router.get('/', function(req, res, next) { const group_name = req.query.group_name || ''; const group = preset_commands[group_name]; return res.render('group', { keystroke_names: Object.keys(group.commands), group_name, title: `${group_name.match(/([AZ])/g).join('')}-Remote` }); });
Dadurch wird der Gruppenname an den Endpunkt gesendet (z. B. durch Einfügen von ?group_name=Netflix
am Ende von /group/
) und dieser verwendet, um den Wert der commands
von der entsprechenden Gruppe abzurufen. Dieser Wert ( group.commands
) ist ein Objekt, und die Tasten dieses Objekts sind die Namen ( keystroke_names
), die wir auf unserem Fernbedienungslayout anzeigen.
Hinweis: Unerfahrene Entwickler müssen sich nicht mit den Einzelheiten der Funktionsweise befassen, aber der Wert für title
verwendet ein wenig reguläre Ausdrücke, um unsere Gruppen-/Layoutnamen in Akronyme umzuwandeln – unsere YouTube-Fernbedienung hat beispielsweise den Browsertitel YT-Remote
. Auf diese Weise, wenn wir auf unserem Host-Rechner debuggen, bevor wir Dinge auf einem Telefon ausprobieren, wird xdotool
nicht das Fernsteuerungs-Browserfenster selbst greifen, anstatt dasjenige, das wir zu steuern versuchen. In der Zwischenzeit wird der Titel auf unseren Telefonen nett und kurz sein, falls wir die Fernbedienung mit einem Lesezeichen versehen möchten.
Wie bei unserer vorherigen Begegnung mit res.render
sendet diese ihre Daten, um sie mit der Vorlage views/group.pug
zu vermischen. Wir erstellen diese Datei und füllen sie damit:
extends layout block header_injection script(type='text/javascript', src='/javascript/group-client.js') block content form(action="/group?group_name=" + group_name, method="post") each keystroke_name in keystroke_names input(type="submit", name="keystroke_name", value=keystroke_name, class="remote_button")
Wie bei views/index.pug
überschreiben wir die beiden Blogs von views/layout.pug
. Diesmal ist es nicht CSS, das wir in den Header einfügen, sondern etwas clientseitiges JavaScript, auf das wir gleich noch eingehen werden. (Und ja, in einem Moment der Hartnäckigkeit habe ich die falsch pluralisierten javascripts
umbenannt …)
Der content
hier ist ein HTML-Formular, das aus einer Reihe verschiedener Submit-Schaltflächen besteht, eine für jeden keystroke_name
. Jede Schaltfläche sendet das Formular (durch eine POST
-Anforderung) unter Verwendung des angezeigten Tastendrucknamens als Wert, den sie mit dem Formular sendet.
Wir brauchen auch etwas mehr CSS in unserer Haupt-Stylesheet-Datei:
.remote_button { float: left; width: calc(100%/3); height: calc(100%/3); font-size: 12vh; }
Früher, als wir den Endpunkt eingerichtet haben, haben wir die Bearbeitung der Anfrage abgeschlossen mit:
return res.redirect(req.originalUrl);
Dies bedeutet effektiv, dass das Node.js-Backend antwortet, wenn der Browser das Formular absendet, indem es den Browser anweist, zu der Seite zurückzukehren, von der das Formular abgeschickt wurde – dh zum Hauptlayout der Fernbedienung. Ohne Seitenwechsel wäre es eleganter; Wir möchten jedoch maximale Kompatibilität mit der seltsamen und wunderbaren Welt der heruntergekommenen mobilen Browser. Auf diese Weise sollte unser Node.js-Backend-Projekt auch ohne überhaupt funktionierendes Front-End-JavaScript funktionieren.
Eine Prise Front-End-JavaScript
Der Nachteil bei der Verwendung eines Formulars zum Senden von Tastenanschlägen ist, dass der Browser warten und dann einen zusätzlichen Roundtrip ausführen muss: Die Seite und ihre Abhängigkeiten müssen dann von unserem Node.js-Backend angefordert und geliefert werden. Anschließend müssen sie erneut vom Browser gerendert werden.
Die Leser fragen sich vielleicht, wie viel von einer Wirkung dies haben könnte. Schließlich ist die Seite winzig, ihre Abhängigkeiten sind äußerst gering, und unser letztes Node.js-Projekt wird über eine lokale WLAN-Verbindung ausgeführt. Sollte ein Setup mit niedriger Latenz sein, oder?
Wie sich herausstellt – zumindest beim Testen auf älteren Smartphones mit Windows Phone 8.1 und Android 4.4.2 – ist der Effekt im üblichen Fall des schnellen Antippens, um die Wiedergabelautstärke um einige Stufen zu erhöhen oder zu verringern, leider deutlich spürbar. Hier kann JavaScript helfen, ohne unseren anmutigen Fallback von manuellen POST
s über HTML-Formulare zu beeinträchtigen.
An diesem Punkt muss unser endgültiges Client-JavaScript (das in public/javascript/group-client.js
werden soll) mit alten, nicht mehr unterstützten mobilen Browsern kompatibel sein. Aber wir brauchen nicht viel davon:
(function () { function form_submit(event) { var request = new XMLHttpRequest(); request.open('POST', window.location.pathname + window.location.search, true); request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); request.send('keystroke_name=' + encodeURIComponent(event.target.value)); event.preventDefault(); } window.addEventListener("DOMContentLoaded", function() { var inputs = document.querySelectorAll("input"); for (var i = 0; i < inputs.length; i++) { inputs[i].addEventListener("click", form_submit); } }); })();
Hier sendet die form_submit
Funktion die Daten einfach über einen asynchronen Aufruf, und die letzte Zeile verhindert das normale Sendeverhalten von Browsern, wodurch eine neue Seite basierend auf der Serverantwort geladen wird. Die zweite Hälfte dieses Snippets wartet einfach, bis die Seite geladen wird, und verbindet dann jeden Submit-Button mit der Verwendung von form_submit
. Das Ganze ist in ein IIFE gehüllt.
Letzter Schliff
Es gibt eine Reihe von Änderungen an den obigen Snippets in der endgültigen Version unseres Node.js-Backend-Tutorial-Codes, hauptsächlich zum Zwecke einer besseren Fehlerbehandlung:
- Das Node.js-Backend prüft nun die Namen der Gruppen und die an es gesendeten Tastenanschläge auf deren Existenz. Dieser Code befindet sich in einer Funktion, die sowohl für die
GET
als auch für diePOST
-Funktion vonroutes/group.js
. - Wir verwenden die Pug
error
, wenn dies nicht der Fall ist. - Das Front-End-JavaScript und -CSS lassen Schaltflächen jetzt vorübergehend grau umranden, während sie auf eine Antwort vom Server warten, grün, sobald das Signal den ganzen Weg durch
xdotool
und zurück ohne Probleme gegangen ist, und rot, wenn etwas nicht wie erwartet funktioniert hat . - Das Back-End von Node.js druckt einen Stack-Trace, wenn es stirbt, was angesichts des oben Gesagten weniger wahrscheinlich ist.
Leser sind herzlich eingeladen, das vollständige Node.js-Projekt auf GitHub durchzusehen (und/oder zu klonen).
Node.js-Back-End-Tutorial, Schritt 5: Ein realer Test
Es ist an der Zeit, es auf einem echten Telefon auszuprobieren, das mit demselben WLAN-Netzwerk verbunden ist wie der Host, auf dem npm start
und ein Film- oder Musikplayer ausgeführt werden. Es muss nur der Webbrowser eines Smartphones auf die lokale IP-Adresse des Hosts (mit angehängtem :3000
) verweisen, die wahrscheinlich am einfachsten durch Ausführen von hostname -I | awk '{print $1}'
gefunden wird hostname -I | awk '{print $1}'
in einem Terminal auf dem Host.
Ein Problem, das Benutzern von Windows Phone 8.1 möglicherweise auffallen, ist, dass beim Versuch, zu etwas wie 192.168.2.5:3000
zu navigieren, ein Fehler-Popup ausgegeben wird:
Zum Glück müssen Sie sich nicht entmutigen lassen: Wenn Sie einfach http://
oder ein nachgestelltes /
hinzufügen, wird die Adresse ohne weitere Beschwerden abgerufen.
Wenn Sie dort eine Option auswählen, sollten Sie zu einer funktionierenden Fernbedienung gelangen.
Für zusätzlichen Komfort können Benutzer die DHCP-Einstellungen ihres Routers anpassen, um dem Host immer dieselbe IP-Adresse zuzuweisen, und den Layout-Auswahlbildschirm und/oder beliebige bevorzugte Layouts mit einem Lesezeichen versehen.
Pull-Requests willkommen
Es ist wahrscheinlich, dass nicht jeder dieses Projekt genau so mögen wird, wie es ist. Hier sind einige Ideen für Verbesserungen, für diejenigen, die tiefer in den Code eintauchen möchten:
- Es sollte einfach sein, die Layouts zu optimieren oder neue für andere Dienste wie Disney Plus hinzuzufügen.
- Vielleicht würden einige ein „Light Mode“-Layout und die Option zum Umschalten bevorzugen.
- Das Zurückziehen von Netflix, da es nicht umkehrbar ist, könnte wirklich ein „Sind Sie sicher?“ Verwenden. Bestätigung irgendeiner Art.
- Das Projekt würde sicherlich von der Windows-Unterstützung profitieren.
- In der Dokumentation
xdotool
wird OSX erwähnt – funktioniert dieses (oder könnte dieses) Projekt auf einem modernen Mac? - Für fortgeschrittenes Faulenzen eine Möglichkeit, Filme zu suchen und zu durchsuchen, anstatt einen einzelnen Netflix/Amazon Prime Video-Film auswählen oder am Computer eine YouTube-Wiedergabeliste erstellen zu müssen.
- Eine automatisierte Testsuite für den Fall, dass eine der vorgeschlagenen Änderungen die ursprüngliche Funktionalität beeinträchtigt.
Ich hoffe, Ihnen hat dieses Node.js-Back-End-Tutorial gefallen und Sie haben ein verbessertes Medienerlebnis als Ergebnis. Viel Spaß beim Streamen – und Programmieren!