Presentando FilterBubbler: Una WebExtension construida con React/Redux

10 diciembre, 2017 20:26 por

Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks.

Hace unos pocos meses mi compañero en software libre durante mucho tiempo, Don Marti, me llamó sobre una idea para una WebExtension. WebExtensions es el nuevo genial estándar para extensiones del navegador en que los equipos Mozilla como también Opera, Edge y unos cuantos otros navegadores principales están colaborando (nota del editor: el artículo original indica incorrectamente que Chrome y Mozilla están colaborando en WebExtensions). El API de WebExtensions te permite escribir complementos usando la misma metodología JavaScript y HTML que usas para implementar cualquier otro sitio web.

La idea de Don era básicamente escribir una herramienta de análisis de texto con la nueva API de WebExtensions. Esta herramienta te permitiría seguir varias actividades de navegador y recursos (historia, marcadores, etc.) y después te permite usar módulos de análisis de texto para descubrir patrones en tu propio historial de navegación. La idea era revolucionar sobre tipos de sofisticados análisis que los anunciantes hacen con las actividades cotidianas de navegación que damos por hecho. Las grandes compañías están usando técnicas avanzadas para modelar el comportamiento del usuario y controlar el contenido que recibimos, para manipular los resultados como el tiempo que un usuario gasta en el sistema o los anuncios que ven. Si proporcionamos herramientas para los usuarios para hacer esto con sus propios datos de navegación, ellos tendrían una mejor oportunidad de comprender sus propios comportamientos y una mayor conciencia de cuando los sistemas externos están intentando manipularles.

El otro objetivo principal sería proporcionar un ejemplo bien documentado de usar el nuevo API WebExtensions. Cuanto más leo sobre WebExtensions más me doy cuenta de que representa un cambio de estrategia por mover la inteligencia de la navegación web “fuera del límite”. Todas las clases de análisis y automatización pueden ser hechas con WebExtensions en una manera que potencialmente permita a las herramientas ser usadas en cualquiera de los navegadores web modernos. La única cosa que vi que faltaba era una manera de colaborar fácilmente sobre estas “recetas” para analizar contenido web. Yo sugerí que creáramos un plugin WordPress que proporcionaría una interfaz RESTful para compartir clasificaciones y el plan básico para “FilterBubbler” nació.

Nuestro prototipo inicial fue una prueba de concepto que usaba un pop-up extremadamente básico de HTML y un clasificador Bayesiano. Esta versión probó que podríamos obtener una clasificación útil de contenido web basado en cuerpos cargados a mano, pero estaba claro que necesitamos herramientas adicionales para tener el toque de un “consumidor”. Antes de que pudiéramos comenzar a añadir características importantes como servidores remotos de recetas, visualizaciones de clasificaciones y configuración, teníamos que tomar algunas decisiones sobre nuestra infraestructura. En este artículo, cubriré nuestros esfuerzos para  proveer un entorno UI moderno y el reto que planteó cuando se trabaja con WebExtensions.

React/Redux

El framework React y su implementación asociada Flux tomaron el mundo UI HTML cuando Facebook publicó la herramienta como software libre en 2013. React fue originalmente desplegado en 2011 como parte de la infraestructura de visualización de noticias en el mismo Facebook. Desde entonces la librería ha encontrado uso en Instagram, Netflix, AirBnB y muchos otros servicios populares. La herramienta gira en torno de una estrategia llamada Flux, la cual estrechamente define la manera en que el estado es actualizado en la aplicación.

Flux es una estrategia, no una implementación en sí, y hay muchas librerías que proporcionan su funcionalidad. Una de las librerías más populares hoy es Redux. El valor central de Redux es una vista universal simplificada del estado de la aplicación. Porque hay un único estado para la aplicación, el comportamiento que resulta desde una series de acciones de eventos es completamente determinística y predecible. Esto hace tu aplicación más fácil de comprender, probar y depurar. Un debate completo sobre los conceptos detrás de React y Redux está más allá del alcance de este artículo así que si estás comenzando, te recomendaría que leas el material introductorio de Redux o revisar el excelente curso introductorio de Dan Ambramov en Egghead.

Integración con WebExtensions

Entrando en detalle en el framework WebExtensions, una de los primeros obstáculos es que el contexto del UI del pop-up y la página de configuración están separados del contexto en el background (fondo). El estado del contexto del UI es regenerado cada vez que abres y cierras el UI. La comunicación entre el contexto UI y el contexto del script en el background se consigue mediante una arquitectura de paso de mensajes.

Contextos en WebExtensions

El estado de la extensión FilterBubbler será almacenado en el contexto de background, pero nosotros necesitamos enlazar ese estado con los elementos en el contexto de pop-up y la página de configuración. El proyecto Redux-WebExt de Alexander Ivantsov ofrece una solución para este problema. Su herramienta proporciona una  capa de abstracción entre el UI y las páginas en el background con un proxy. El proxy te da una apariencia de acceso directo para el almacenamiento Redux en el UI, pero en realidad dirige acciones al contexto background, y entonces envía las modificaciones de estado resultantes generadas por los reductores de vuelta al contexto UI.

Mapeo de acciones

Me llevo algo de esfuerzo tener las cosas funcionando a través del puente Redux-WebExt. Los componentes React que se ejecutan en los contextos UI piensan que están hablando con un almacenamiento Redux; de hecho, es una fachada que está intercambiando mensajes con tu contexto background. Los objetos acción que piensas que están dirigidos hacia tus reductores están en realidad serializados en mensajes, enviados al contexto de fondo y después desempaquetados y entregados al almacenamiento. Una vez los reductores terminan sus modificaciones de estado, el estado resultante es empaquetado y enviado de vuelta al proxy así que puede actualizar el estado del equivalente UI.

Redux-WebExt pone una tabla de mapping en el medio de sus procesos que permite modificar como los eventos de acción desde el front-end son entregados al almacenamiento. En algunos casos (i.e., operaciones asíncronas) realmente necesitas este mapeo para separar acciones que no pueden serializadas en objetos mensaje (como funciones callback).

En algunos casos esto puede ser un mapeo directo que solo copia datos desde un evento de acción UI, como este desde el mapeo FilterBubbler en store.js:

actions[formActionTypes.TOUCH] = (data) => { 
    return { type: formActionTypes.TOUCH, ...data }; 
}

O, puedes necesitar mapear la acción UI para algo totalmente diferente, como este mapeo que llama una función asíncrona solo disponible en el almacenamiento de fondo:

actions[UI_REQUEST_ACTIVE_URL] = requestActiveUrl;

En definitiva, ten en cuenta el mapeo! Me llevo unas horas comprender su propósito. Comprender esto es crítico si quieres usar React/Reduct en tu extensión como nosotros.

Contextos WebExtensions y Redux

Este arreglo hace posible usar herramientas React/Redux con mínimos cambios y configuración. Existen sofisticadas librerías para manejar formularios y otras tareas importantes de UI que se pueden conectar con el entorno WebExtensions sin tener ningún conocimiento de la conectividad basada en mensajes subyacentes. Una herramienta ejemplo que hemos ya integrado es Redux Form, que proporciona un sistema completo de manejo de formularios de entrada con validación y de otros servicios que tú esperarías en un desarrollo moderno.

Habiendo establecido que podemos usar una gran herramienta UI sin comenzar desde cero, nuestra siguiente preocupación es hacer que las cosas se vean bien. El material de diseño de Google es un estándar en interfaces popular, y la plataforma React tiene el popular Material UI, el cual implementa el estándar Google como un conjunto de componentes React/Redux. Esto nos da la habilidad de producir UI pop-ups y pantallas de configuración con gran apariencia sin tener que desarrollar una nueva herramienta.

Ponte ‘thunky’

Algunas de las operaciones que necesitamos realizar son basadas en callbacks, lo cual las hace asíncronas. En el modelo React/Redux esto presenta algunos problemas. Las funciones generadoras de acciones y reductores son diseñados para hacer su trabajo inmediatamente cuando son llamados. Las soluciones como dar acceso al almacenamiento dentro de un generador de acciones y llamar dispatch en un callback es considerado un antipatrón. Una solución popular es el puente Redux-Thunk. Agregar Redux-Thunk a tu aplicación es sencillo, solo tienes que pasarlo cuando creas el almacenamiento.

import thunk from 'redux-thunk'
const store = createStore(
    reducers,
    applyMiddleware(thunk))

Tener Redux-Thunk instalado te proporciona un nuevo estilo de generadores de acciones en los cuales devuelves una función para el almacenamiento, el cual será pasado más tarde mediante una función dispatch. Esta inversión de control permite a Redux permanecer en control para secuenciar tus operaciones asíncronas con otras acciones en la cola. Como ejemplo, aquí tenemos una función que pide una URL de la pestaña actual y después sirve la petición para establecer el resultado en el UI:

export function requestActiveUrl() {
    return dispatch => {
        return browser.tabs.query({active: true}, tabs => {
            return dispatch(activeUrl(tabs[0].url));
        })
    }
}

La función activeURL() se ve más típica:

export function activeUrl(url) {
    return {
        type: ACTIVE_URL,
        url
    }
}

Debido que WebExtensions se extiende sobre diferentes contextos y se comunica con mensajería asíncrona, una herramienta como Redux-Thunk es indispensable.

Depurando WebExtensions

Depurar WebExtensions presenta algunos retos que funcionan un poco diferente dependiendo del navegador que estés usando. Independientemente del navegador que uses, la primera diferencia más importante es que el contexto de fondo de la extensión no tiene página visible y debe ser específicamente seleccionado para depurar. Comencemos con ese proceso para Firefox y Chrome.

Firefox

Depuración en Firefox

En Firefox, puedes acceder a tu extensión escribiendo about:debugging en el campo URL del navegador. Esta página te permite añadir y desempaquetar extensiones con el botón “Cargar complemento temporal” (o puedes usar la útil herramienta web-ext que permite comenzar el proceso desde la línea de comandos). Presionando el botón “Depurar” traerá un depurador de código para tu extensión. Con FilterBubbler, estamos usando la flexible herramienta webpack para aprovecharnos de las últimas características de JavaScript. Webpack usa el transpilador Babel para convertir nuevas características del lenguaje JavaScript en código que es compatible con los navegadores actuales. Esto significa que las fuentes ejecutadas por el navegador están significativamente modificadas respecto al original. Asegúrate de seleccionar la opción  “Mostrar fuentes originales” del menú de preferencias en el depurador o tu código parecerá muy diferente!

Mostrar fuentes originales

Una vez que lo has seleccionado deberías ver algo más parecido a lo que esperas:

Depuración en Firefox

Desde aquí puedes establecer puntos de ruptura y hacer todo lo demás.

Chrome

En Chrome es todo básicamente la misma idea, solo con unas pocas diferencias en el UI. Primero vas al menú principal, navegar abajo en “Más herramientas” y después seleccionar “Extensiones”:

Extensiones en Chrome

Esto te lleva a la página de listado de extensiones.

Listado de extensiones en Chrome

La sección “Vistas de inspección” te permitirá iniciar un depurador para tus scripts de fondo.

Depuración de fondo en Chrome

Donde el depurador Firefox muestra todo tu actividad de segundo y primer plano en un solo lugar, el entorno Chrome hace las cosas un poco diferente. La vista UI es activada mediante click con botón derecho en el ícono de tu extensión y seleccionando la opción “Inspeccionar popup”.

Inspeccionar popup en Chrome

Desde aquí las cosas son bastante como esperarías. Si has escrito aplicaciones JavaScript y usado la funcionalidad integrada en el navegador entonces esto te resultará bastante familiar.

Materiales de clasificación

Con toda nuestra nueva infraestructura y un depurador en funcionamiento, regresamos a añadir atributos a FilterBubbler. Uno de nuestros objetivos para el prototipo es proveer el API en la cual las recetas se ejecutarán. Los principales ingredientes para las recetas FilterBubbler son:

Uno o más orígenes: un origen proporciona una corriente de clasificación de eventos en las URLs dadas. El prototipo provee un simple origen el cual emitirá una petición de clasificación cualquier momento que el navegador se dirige a una página en concreto. Otros posibles orígenes podrían incluir una fuente que analiza una red social, una corriente de mensajes de correo electrónico o un segmento del historial del navegador.

Un clasificador: el clasificador toma contenido de un origen y retorna al menos una etiqueta de clasificación con un valor de peso. El clasificador podría devolver un array de parejas de etiquetas y pesos. Si el array está vacío entonces el sistema asume que el clasificador no fue capaz de encontrar una coincidencia.

Uno o más cuerpos: los cuerpos de FilterBubbler proporcionan una lista de URLs con etiqueta y pesos. Las etiquetas y los valores son usados para entrenar la clasificación.

Uno o más sumideros (sink): es el destino para la clasificación de eventos. El prototipo incluye un simple sumidero que conecta con el clasificador dado para un elemento UI, el cual muestra las clasificaciones en el pop-up de la extensión. Otros posibles sumideros podrían generar correos salientes para ciertas coincidencias de etiqueta de clasificación o un sumidero que escribe URL en una carpeta de marcador nombrada con la etiqueta de clasificación

Puede que un diagrama ayude. La siguiente configuración podría decirte si la página que estás actualmente mirando es “increíble o “estúpida” !

Arquitectura de FilterBubbler

Transmitir el conocimiento

Las configuraciones para estos arreglos son llamados “recetas” y pueden ser cargados en tu configuración local. Una receta es definida con un simple formato JSON como el siguiente:

{
    “recipe-version”: “0.1”,
    “name”: “My Recipe”,
        “classifier”: “naive-bayes”,
        “corpora”: [
        “http://mywpblog.com/filterbubbler/v1/corpora/fake-news”,
        “http://mywpblog.com/filterbubbler/v1/corpora/ufos”,
        “http://mywpblog.com/filterbubbler/v1/corpora/food-blog”
    ],
    “source”: [ “default” ],
    “sink”: [ “default” ]
}

El simple extracto de código de muestra arriba puede ayudar al usuario a diferenciar entre noticias falsas, avistamientos OVNIS y blogs de comida (un problema más grande de lo que puedes esperar en algunas ciudades). Actualmente las clasificaciones, fuentes y sumideros deben ser unas de las implementaciones proporcionadas y no pueden ser cargadas dinámicamente en el prototipo inicial. En otros artículos, ampliaremos esta funcionalidad y describiremos los retos que estas actividades presentan en el entorno WebExtensions.

Referencias

FilterBubbler en GitHub: https://github.com/filterbubbler/filterbubbler-web-ext

Material UI basado en React: http://www.material-ui.com/

Redux WebExt: https://github.com/ivantsov/redux-webext

Redux Form: http://redux-form.com/6.7.0/

Mozilla polyfill: https://github.com/mozilla/webextension-polyfill

The following two tabs change content below.

Compartir artículo:

Join the discussion at foro.mozilla-hispano.org

  • ¡Participa!

    Firefox Friends »
    Agrega botones de Firefox en tu sitio web y comparte tu amor por Mozilla Firefox.
    Armada alucinante »
    Ayuda a otros usuarios en Twitter.
    Colabora con la comunidad »
    En Mozilla lo importante son las personas. Descubre cómo puedes colaborar.

    Boletín Firefox

    Suscríbete al boletín de novedades de Firefox.

  • Descargas

    Descarga los programas de Mozilla.

    Lo más visto

    cc-by-sa