Browserübergreifende Web-Erweiterung für benutzerdefinierte Skripte Teil 3

In diesem Artikel setze ich die Reihe von Veröffentlichungen fort, in denen ich über meine Erfahrungen beim Schreiben einer Web-Erweiterung für Browser sprechen möchte. Ich hatte bereits Erfahrung mit der Erstellung einer Web-Erweiterung, die von etwa 100.000 Chrome-Nutzern installiert wurde und autonom arbeitete. In dieser Artikelserie habe ich mich jedoch entschlossen, den Entwicklungsprozess der Web-Erweiterung zu untersuchen, indem ich sie eng in die Serverseite integriert habe.

BildBildBildBildBild

Teil 1 , Teil 2 , Teil 4

Fallstricke bei der Implementierung der Interaktion von Web-Erweiterungen und der Serverseite


Wie bereits zuvor beschrieben, verwendet die Serverseite Meteor.js. Zur Implementierung der RESTful-API wird das Paket github.com/kahmali/meteor-restivus verwendet. Es ist bereits ein Teil implementiert, der Benutzermechanismen im Zusammenhang mit der Autorisierung abdeckt.

Beispielsweise reicht es aus, authRequired: true wie im folgenden Beispiel anzugeben, damit der API-Punkt nur für autorisierte Benutzer funktioniert.

Api.addRoute('clientScript/:id_script', {authRequired: true}, {get: { action: function() { //method for GET on htts://example.com/api/v1/clientScript/:id_script } }); 

Daher wurden drei API-Punkte zur Registrierung, zum Empfangen und Aktualisieren von Profildaten und zum Zurücksetzen des Kennworts hinzugefügt.

In der Web-Erweiterung selbst wird beim Aufrufen autorisierungsbedürftiger Methoden der folgende Code verwendet:

 var details = { url: API_URL + '/api/v1/clientDataRowDownload/' + dataRowId + '/download', method: 'GET', contentType: 'json', headers: {'X-Auth-Token': kango.storage.getItem("authToken"), 'X-User-Id': kango.storage.getItem("userId")} }; kango.xhr.send(details, function(data) { //code for response handler }) 

Ein Beispiel für eine Autorisierungsanfrage ist hier deutlich sichtbar. Die Header enthalten X-Auth-Token und X-User-Id, die als Ergebnis des Registrierungs- oder Autorisierungsprozesses erhalten wurden. Diese Daten werden im lokalen Speicher der Web-Erweiterung gespeichert und sind immer im Skript content.js verfügbar.

Das Herunterladen von Dateien in der Web-Erweiterung erfolgt durch Lesen der Datei auf der Browserseite und Senden über XHR:

 $("form#uploadFile").on("submit", function(event, template) { event.preventDefault(); var reader = new FileReader(); reader.onload = function(evt) { var details = { url: API_URL + '/api/v1/clientFileAdd/' + kango.storage.getItem("userId"), method: 'POST', contentType: 'json', params: {"content": encodeURIComponent(evt.target.result.replace(/^data:[^;]*;base64,/, "")), "name": encodeURIComponent(event.currentTarget.fileInput.files[0].name), "size": event.currentTarget.fileInput.files[0].size, "type": event.currentTarget.fileInput.files[0].type, "lastModified": event.currentTarget.fileInput.files[0].lastModified }, headers: {'X-Auth-Token': kango.storage.getItem("authToken"), 'X-User-Id': kango.storage.getItem("userId")} }; kango.xhr.send(details, function(data) { if (data.status == 200 && data.response != null) { if(data.response.status == "success") { //ok } else { //error } } else { if(data.status == 401) { //notAuth } else { //error } } }); }; if (event.currentTarget.fileInput.files.length != 0) { reader.readAsDataURL(event.currentTarget.fileInput.files[0]); } return false; }); 

Es ist wichtig, die Zeile event.target.result.replace (/ ^ data: [^;] *; base64, /, "") zu beachten. Die Datei auf der Browserseite ist in base64 codiert, aber aus Kompatibilitätsgründen auf der Serverseite müssen wir bei Verwendung dieser Codierung in der Zeile Buffer.from (neuer String (this.bodyParams.content), "base64") das Codierungspräfix abschneiden und nur den "Body" der Datei lesen . Es ist auch notwendig, den Umbruch in encodeURIComponent zu beachten, da das gleiche + häufig in base64- und Dateinamen gefunden wird.

Beim Bearbeiten von Skripten müssen Sie beim Übertragen von Inhalten die Zeichenkodierung im Hauptteil des Skripts berücksichtigen. In einigen Fällen führte die Base64-Codierung beim Decodieren auf der Serverseite mit encodeURIComponent nicht zu den richtigen Ergebnissen. Daher wird die erzwungene Codierung in utf8 vorläufig mit utf8.encode (str) verwendet. wo mths.be/utf8js v3.0.0 von @mathias

Dateidownloads werden mithilfe der bewährten FileSaver-Bibliothek implementiert. Die über XHR empfangenen Daten werden einfach an die Eingabe des Dateikonstruktors übertragen, und anschließend wird der Dateidownload initialisiert:

 var file = new File([data.response.data.join("\n")], "data_rows" + date.getFullYear() + "_" + (date.getMonth() + 1) + "_" + date.getDate() + ".csv", {type: "application/vnd.ms-excel"}); saveAs(file); 

Interne Bibliothek für benutzerdefinierte Skripte


Damit das Skript, die Web-Erweiterung und der Serverteil interagieren können, ist ein Zwischenlink erforderlich, über den Sie schnell Daten aus der heruntergeladenen Datei empfangen, Daten speichern können, nachdem das Skript ausgeführt wurde usw.

Zu diesem Zweck wurde eine interne Bibliothek geschrieben, die initialisiert wird, bevor ein Skript funktioniert, indem es sich selbst zum Seitencode hinzufügt. Hier müssen Sie Informationen zur Quellschutzrichtlinie hinzufügen, um Ressourcen zu laden, nämlich die Inhaltssicherheitsrichtlinie.

Viele Websites verwenden CSP-Header, um sich vor der Ausführung von beliebigem Javascript-Code auf ihren Webserviceseiten zu schützen und sich so vor XSS auf der Webbrowserseite zu schützen.

Da der Benutzer die Web-Erweiterung selbst installiert, kann er die Header und Inhalte der heruntergeladenen Ressource ändern. Aufgrund eines Fehlers in Mozilla Firefox ist dies für einige Websites ein Problem. Das heißt, in der Web-Erweiterung für Firefox ist es nicht möglich, die Header zu ändern oder ein Meta-Tag hinzuzufügen, um die CSP-Richtlinie für Websites abzubrechen, auf denen sie verwendet werden. Dieser Fehler wurde seit mehreren Jahren nicht mehr geschlossen, obwohl die Standards eindeutig Bestimmungen für Web-Erweiterungen vorsehen, wonach die Richtlinie für heruntergeladene Ressourcen von der Seite des Anwendungsservers in Bezug auf vom Benutzer selbst installierte Web-Erweiterungen nicht dominant sein kann.

Eine Einschränkung der CSP-Richtlinien kann mithilfe des Kango-Frameworks folgendermaßen implementiert werden:

 var browserObject; if(kango.browser.getName() == 'chrome') { browserObject = chrome; } else { browserObject = browser; } var filter = { urls: ["*://*/*"], types: ["main_frame", "sub_frame"] }; var onHeadersReceived = function(details) { var newHeaders = []; for (var i = 0; i < details.responseHeaders.length; i++) { if ('content-security-policy' !== details.responseHeaders[i].name.toLowerCase() && 'x-xss-protection' !== details.responseHeaders[i].name.toLowerCase() ) { newHeaders.push(details.responseHeaders[i]); } } return { responseHeaders: newHeaders }; }; browserObject.webRequest.onHeadersReceived.addListener(onHeadersReceived, filter, ["blocking", "responseHeaders"]); 

In diesem Fall dürfen Sie nicht vergessen, dem Web-Erweiterungsmanifest Zeilen hinzuzufügen, die das Arbeiten mit dem webRequest-Objekt im Blockierungsmodus ermöglichen:

 "permissions": { ... "webRequest": true, "webRequestBlocking": true, ... } 

Nachdem das Problem mit den vom CSP auferlegten Einschränkungen gelöst wurde, kann der Benutzer die von ihm auf jeder Seite im Internet geschriebenen Skripte verwenden.

Das Aufrufen von Funktionen aus der internen Bibliothek ist über das globale Gc-Objekt verfügbar.
Derzeit implementierte Funktionen:

  • GC.saveRow (Name, Inhalt, [rewrite = 0, async = false]); , wobei name der Name der Zeilen ist, die in die Sammlung geschrieben werden sollen, content die Datenzeile selbst zum Schreiben ist, rewrite das Flag zum Überschreiben der gesamten Sammlung ist, das beim Aufruf von Gc.saveRow verwendet wird (name, 'clear', 1); async löscht alle Einträge in der Zeichenfolgensammlung und ist ein Flag für die Arbeit im asynchronen Modus.
  • GC.getRows (Name, Nummer, [count = 1, async = false]); Dabei ist name der Name der Zeilen in der Sammlung, number die Seriennummer der Zeile, in der Daten empfangen werden sollen, count die Anzahl der empfangenen Daten, beginnend mit number, async das Flag für das Arbeiten im asynchronen Modus.
  • GC.console (Zeichenfolge); Dabei ist string eine Zeichenfolge für die Ausgabe an die GC-Konsole auf der Seite, auf der das Skript ausgeführt wird. Zum Beispiel, um den Fortschritt einer Aufgabe zu demonstrieren.
  • GC.clearConsole (); Die Funktion löscht die GC-Konsole auf der Seite, auf der das Skript ausgeführt wird.
  • GC.stopScript (); , Funktion zum Stoppen der Skriptausführung.
  • GC.loadFile (Name, [parseAs = Text]); Wenn name der Name der Datei mit der Erweiterung ist, deren Inhalt Sie erhalten möchten, ist parseAs das Datenpräprozessorformat. JSON und Text werden derzeit unterstützt.

Im nächsten Artikel werde ich über " geplante Aufgaben " sprechen.

Source: https://habr.com/ru/post/de416585/


All Articles