Extensión web de navegador cruzado para scripts personalizados Parte 3

En este artículo, continúo la serie de publicaciones en las que quiero hablar sobre mi experiencia escribiendo una extensión web para navegadores. Ya tenía experiencia en la creación de una extensión web, que fue instalada por unos 100,000 usuarios de Chrome, que funcionó de manera autónoma, pero en esta serie de artículos decidí profundizar en el proceso de desarrollo de la extensión web al integrarla estrechamente con el lado del servidor.

imagenimagenimagenimagenimagen

Parte 1 , Parte 2 , Parte 4

Errores en la implementación de la interacción de las extensiones web y el lado del servidor


Como ya se describió anteriormente, el lado del servidor usa Meteor.js. Para implementar la API RESTful, se usa el paquete github.com/kahmali/meteor-restivus . Ya tiene parte implementada para cubrir los mecanismos de usuario relacionados con la autorización.

Por ejemplo, es suficiente especificar authRequired: true , como en el ejemplo a continuación, para que el punto API funcione solo para usuarios autorizados.

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

Por lo tanto, se agregaron tres puntos API para el registro, para recibir datos de perfil y actualizarlos, para restablecer la contraseña.

En la extensión web en sí, cuando se llaman métodos que requieren autorización, se usa el siguiente código:

 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 ejemplo de una solicitud de autorización es claramente visible aquí. Los encabezados incluyen X-Auth-Token y X-User-Id, que se obtuvieron como resultado del proceso de registro o autorización. Estos datos se almacenan en el almacenamiento local de la extensión web y siempre están disponibles en el script content.js.

La descarga de archivos en la extensión web se realiza leyendo el archivo en el lado del navegador y enviando a través de 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 importante tener en cuenta la línea event.target.result.replace (/ ^ data: [^;] *; base64, /, "") . El archivo en el lado del navegador está codificado en base64, pero por compatibilidad en el lado del servidor cuando se usa esta codificación en la línea Buffer.from (nueva cadena (this.bodyParams.content), "base64") debemos cortar el prefijo de codificación y leer solo el "cuerpo" del archivo . También es necesario tener en cuenta la envoltura en encodeURIComponent, ya que a menudo se encuentra el mismo + en base64 y nombres de archivo.

Al editar guiones, debe considerar la codificación de caracteres en el cuerpo del guión al transferir contenido. En algunos casos, la codificación base64 no produjo los resultados correctos al decodificar en el lado del servidor usando encodeURIComponent. Por lo tanto, la codificación forzada en utf8 se usa preliminarmente con utf8.encode (str); donde mths.be/utf8js v3.0.0 de @mathias

Las descargas de archivos se implementan utilizando la bien establecida biblioteca FileSaver. Los datos recibidos a través de XHR simplemente se transfieren a la entrada del constructor de archivos y luego se inicializa la descarga del archivo:

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

Biblioteca interna para scripts personalizados


Para que el script, la extensión web y la parte del servidor interactúen, es necesario tener un enlace intermedio que le permita recibir rápidamente los datos del archivo descargado, guardar los datos después de ejecutar el script, etc.

Para este propósito, se escribió una biblioteca interna, que se inicializa antes de que cualquier script comience a funcionar agregándose al código de la página. Aquí debe agregar información sobre la política de protección de origen para cargar recursos, es decir, la política de seguridad de contenido.

Muchos sitios usan encabezados CSP para proteger contra la ejecución de código arbitrario de JavaScript en sus páginas de servicios web, protegiéndose así de XSS en el lado del navegador web.

Como el usuario instala la extensión web por su cuenta, puede cambiar los encabezados y el contenido del recurso descargado. Debido a un error en Mozilla Firefox, este es un problema para algunos sitios. Es decir, en la extensión web para Firefox no será posible modificar los encabezados o agregar una metaetiqueta para cancelar la política CSP para los sitios en los que se utilizan. Este error no se ha cerrado durante varios años, aunque los estándares estipulan claramente las disposiciones para las extensiones web, que establece que la política con respecto a los recursos descargados desde el lado del servidor de aplicaciones no puede ser dominante en relación con las extensiones web instaladas por el propio usuario.

Se puede implementar una restricción de política CSP utilizando el marco de trabajo de kango de la siguiente manera:

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

En este caso, no debe olvidarse de agregar líneas en el manifiesto de extensión web que permitan trabajar con el objeto webRequest en modo de bloqueo:

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

Después de resolver el problema con las restricciones impuestas por el CSP, el usuario puede usar los scripts escritos por él en cualquier página de Internet.

Las funciones de llamada desde la biblioteca interna están disponibles a través del objeto Gc global.
Funciones implementadas actualmente:

  • GC.saveRow (nombre, contenido, [reescribir = 0, asíncrono = falso]); , donde nombre es el nombre de las líneas para escribir en la colección, el contenido es la línea de datos para escribir, reescribir es la bandera para sobrescribir toda la colección, utilizada en la llamada a Gc.saveRow (nombre, 'borrar', 1); que elimina todas las entradas en la colección de cadenas, async es un indicador para trabajar en modo asíncrono.
  • GC.getRows (nombre, número, [cuenta = 1, asíncrono = falso]); donde nombre es el nombre de las líneas de la colección, número es el número de serie de la línea para recibir datos, cuenta es el número de datos recibidos comenzando por el número, asíncrono es la bandera para trabajar en modo asíncrono.
  • GC.console (cadena); , donde string es una cadena para salida a la consola GC en la página donde se ejecuta el script. Por ejemplo, para demostrar el progreso de una tarea.
  • GC.clearConsole (); , la función borra la consola del GC en la página donde se ejecuta el script.
  • GC.stopScript (); , función para detener la ejecución del script.
  • GC.loadFile (nombre, [parseAs = texto]); , donde nombre es el nombre del archivo con la extensión cuyos contenidos desea obtener, parseAs es el formato de preprocesador de datos, actualmente se admite json y texto.

En el próximo artículo hablaré sobre " tareas programadas ".

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


All Articles