Extension Web multi-navigateur pour les scripts personnalisés Partie 3

Dans cet article, je continue la série de publications dans lesquelles je veux parler de mon expérience d'écriture d'une extension web pour les navigateurs. J'avais déjà de l'expérience dans la création d'une extension Web, qui a été installée par environ 100000 utilisateurs de Chrome, qui fonctionnait de manière autonome, mais dans cette série d'articles, j'ai décidé de me plonger dans le processus de développement de l'extension Web en l'intégrant étroitement avec le côté serveur.

imageimageimageimageimage

Partie 1 , partie 2 , partie 4

Pièges dans la mise en œuvre de l'interaction des extensions Web et du côté serveur


Comme déjà décrit précédemment, le côté serveur utilise Meteor.js. Pour implémenter l'API RESTful, le package github.com/kahmali/meteor-restivus est utilisé. Il a déjà mis en œuvre une partie de celui-ci pour couvrir les mécanismes des utilisateurs liés à l'autorisation.

Par exemple, il suffit de spécifier authRequired: true , comme dans l'exemple ci-dessous, pour que le point API ne fonctionne que pour les utilisateurs autorisés.

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

Ainsi, trois points API ont été ajoutés pour l'enregistrement, pour recevoir les données de profil et les mettre à jour, pour réinitialiser le mot de passe.

Dans l'extension Web elle-même, lors de l'appel de méthodes nécessitant une autorisation, le code suivant est utilisé:

 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 }) 

Un exemple de demande d'autorisation est clairement visible ici. Les en-têtes incluent X-Auth-Token et X-User-Id, qui ont été obtenus à la suite du processus d'enregistrement ou d'autorisation. Ces données sont stockées dans le stockage local de l'extension Web et sont toujours disponibles dans le script content.js.

Le téléchargement de fichiers dans l'extension Web se fait en lisant le fichier côté navigateur et en l'envoyant via 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; }); 

Il est important de noter la ligne event.target.result.replace (/ ^ data: [^;] *; base64, /, "") . Le fichier côté navigateur est encodé en base64, mais pour la compatibilité côté serveur lors de l'utilisation de cet encodage dans la ligne Buffer.from (nouvelle chaîne (this.bodyParams.content), "base64") nous devons couper le préfixe d'encodage et lire uniquement le "corps" du fichier . Il est également nécessaire de noter l'habillage dans encodeURIComponent, car le même + se trouve souvent dans les noms de base64 et de fichier.

Lors de la modification de scripts, vous devez prendre en compte l'encodage des caractères dans le corps du script lors du transfert de contenu. Dans certains cas, l'encodage base64 n'a pas produit les résultats corrects lors du décodage côté serveur à l'aide d'encodeURIComponent. Par conséquent, le codage forcé dans utf8 est utilisé au préalable avec utf8.encode (str); où mths.be/utf8js v3.0.0 de @mathias

Les téléchargements de fichiers sont mis en œuvre à l'aide de la bibliothèque FileSaver bien établie. Les données reçues via XHR sont simplement transférées vers l'entrée du constructeur File, puis le téléchargement du fichier est initialisé:

 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); 

Bibliothèque interne pour les scripts personnalisés


Pour que le script, l'extension Web et la partie serveur interagissent, il est nécessaire d'avoir un lien intermédiaire qui vous permettra de recevoir rapidement les données du fichier téléchargé, de sauvegarder les données après l'exécution du script, etc.

À cet effet, une bibliothèque interne a été écrite, qui est initialisée avant qu'un script ne commence à fonctionner en s'ajoutant au code de la page. Ici, vous devez ajouter des informations sur la politique de protection des sources pour charger les ressources, à savoir la politique de sécurité du contenu.

De nombreux sites utilisent des en-têtes CSP pour se protéger contre l'exécution de code javascript arbitraire sur leurs pages de services Web, se protégeant ainsi contre XSS du côté du navigateur Web.

Étant donné que l'utilisateur installe lui-même l'extension Web, il peut modifier les en-têtes et le contenu de la ressource téléchargée. En raison d'un bogue dans Mozilla Firefox, c'est un problème pour certains sites. Autrement dit, dans l'extension Web pour Firefox, il ne sera pas possible de modifier les en-têtes ou d'ajouter une balise META pour annuler la politique CSP pour les sites sur lesquels ils sont utilisés. Ce bogue n'a pas été fermé depuis plusieurs années, bien que les normes stipulent clairement des dispositions pour les extensions Web, qui stipulent que la politique concernant les ressources téléchargées depuis le côté du serveur d'applications ne peut pas être dominante par rapport aux extensions Web installées par l'utilisateur lui-même.

Une restriction de stratégie CSP peut être implémentée à l'aide du framework kango de la manière suivante:

 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"]); 

Dans ce cas, vous ne devez pas oublier d'ajouter des lignes dans le manifeste de l'extension web qui permettent de travailler avec l'objet webRequest en mode blocage:

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

Après avoir résolu le problème avec les restrictions imposées par le CSP, l'utilisateur peut utiliser les scripts qu'il a écrits sur n'importe quelle page d'Internet.

Les fonctions d'appel à partir de la bibliothèque interne sont disponibles via l'objet Gc global.
Fonctions actuellement implémentées:

  • GC.saveRow (nom, contenu, [réécriture = 0, async = false]); , où nom est le nom des lignes à écrire dans la collection, le contenu est la ligne de données elle-même pour l'écriture, réécrire est l'indicateur pour écraser la collection entière, utilisé dans l'appel Gc.saveRow (nom, 'clear', 1); qui supprime toutes les entrées de la collection de chaînes, async est un indicateur pour travailler en mode asynchrone.
  • GC.getRows (nom, numéro, [count = 1, async = false]); où nom est le nom des lignes de la collection, numéro est le numéro de série de la ligne pour recevoir des données, count est le nombre de données reçues en commençant par nombre, async est l'indicateur pour travailler en mode asynchrone.
  • GC.console (chaîne); , où chaîne est une chaîne de sortie vers la console GC sur la page où le script est exécuté. Par exemple, pour montrer la progression d'une tâche.
  • GC.clearConsole (); , la fonction efface la console GC sur la page où le script est exécuté.
  • GC.stopScript (); , fonction pour arrêter l'exécution du script.
  • GC.loadFile (nom, [parseAs = texte]); , où nom est le nom du fichier avec l'extension dont vous souhaitez obtenir le contenu, parseAs est le format du préprocesseur de données, json et text sont actuellement pris en charge.

Dans le prochain article, je parlerai des « tâches planifiées ».

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


All Articles