Creando un complemento de notificaciones para Firefox OS

8 marzo, 2016 18:17 por

Esta es una traducción del artículo original publicado en el blog de Mozilla Hacks. Traducción por Ruth Hernández.

¿Qué es un complemento para Firefox OS y por qué lo necesitamos?

El ecosistema de complementos de Firefox ha sido un diferenciador clave en la arena de los navegadores de escritorio. No obstante, el espacio móvil carece de un marco sólido de complementos. Existen algunas soluciones para Android, tales como Xposed, pero estas soluciones suelen requerir hacerle root a un teléfono y el contenido, por lo general, no lo verifica un equipo de confianza; esto lo convierte en un desafío de uso para la persona promedio. Además, requiere un conocimiento profundo de la plataforma Android, por lo que es difícil que los desarrolladores web se involucren.

En la versión 2.5 de Firefox OS, Mozilla introduce un nuevo modelo de complementos. Estos complementos nuevos utilizan la API WebExtensions, sobre las cuales están también construidas las extensiones para Chrome. Ya que Firefox OS está construida usando tecnologías estándar HTML5, los diseñadores web pueden iniciar fácilmente.

Una de las características más útiles de los complementos de Firefox OS es el API "content_scripts". Se usa para inyectar JavaScript y CSS personalizado en las apps seleccionadas. El script inyectado se ejecutará cuando la aplicación inicie (o cuando el complemento se active primero), ¡de manera que podamos cambiar inmediatamente el comportamiento de las apps o del propio sistema!

En este artículo, te mostraremos cómo desarrollar las notificaciones de “mensaje sin leer” al estilo iOS usando el nuevo sistema de complementos para Firefox OS.

Arquitectura de alto nivel del complemento

Nuestro complemento desplegará el conteo de mensajes sin leer en los íconos de las apps en la pantalla de inicio (ver la captura a continuación). El complemento tiene que ser capaz de recuperar las notificaciones sin leer desde el sistema, guardarlas en el almacenamiento persistente (de manera que podamos recuperar el número correcto incluso después de reiniciar el sistema), y desplegarlas con precisión junto con los íconos de app en la pantalla de inicio.

Notificaciones sobre íconos

Nota: podrás encontrar todos el código de Unread Icon en GitHub. Los fragmentos de código que se muestran a continuación se simplificaron para facilitar la lectura, así que copiarlos y pegarlos desde el código de ejemplo en este artículo no funcionará.

Afortunadamente, no necesitamos acceder a cada aplicación y escribir lógica personalizada para obtener el conteo de mensajes no leídos. En cambio, podemos observar las notificaciones integradas de Firefox OS. La mayoría de las aplicaciones básica como Phone, SMS, E-Mail y Calendar usan este sistema de notificación cuando entran nuevos mensajes sin leer. Podemos analizar el contenido de estas notificaciones para detectar cuando una aplicación recibe un mensaje nuevo no leído.

Ahora, suena tentador inyectar JavaScript directamente en la aplicación de la pantalla de inicio para que el complemento pueda escuchar las notificaciones del sistema y agregue números rojos pequeños en los íconos de las apps al mismo tiempo. Ten en cuenta que el código JavaScript inyectado tendrá los mismos permisos que la aplicación en donde se inyecta, y que la aplicación de la pantalla de inicio no posee los permisos para acceder a las notificaciones de otras apps. Así que en su lugar, necesitamos inyectar JavaScript en la aplicación del sistema (System) ya que se puede acceder a todas las notificaciones de las otras apps.

Hasta ahora todo bien, sin embargo la aplicación del sistema no tiene acceso al DOM de la pantalla principal, por lo que también es necesario inyectar JavaScript en la pantalla de inicio con el fin de agregar los números en los íconos de las apps.

Para que la aplicación del sistema pueda comunicar las conteos de mensajes no leídos a la aplicación de pantalla de inicio, usamos el API de configuración (Settings). Ya que ésta es un almacenaje de clave/valor, podemos dejar que la aplicación del sistema escriba los datos en formato JSON en ella, y luego en la pantalla principal podemos leer estos datos y actualizar la interfaz.

Estructura de la carpeta de un complemento

Un complemento de Firefox OS luce como un híbrido de un complemento de WebExtensions y una open web app. Por lo general, la carpeta lucirá como esta:

.
├── icons
│   ├── icon-128.png
├── main.js
└── manifest.json

En primer lugar, echemos un vistazo a  manifest.json, que es el manifiesto para WebExtensions. En manifest.json especificamos explícitamente los archivos JavaScript y CSS a inyectar, y especificamos en cuáles aplicaciones o páginas en las que se va a inyectar. Hacemos esto mediante el campo content_script:

{
  "content_scripts": [{
    "matches": [  // en cuáles apps queremos inyectar
      "app://system.gaiamobile.org/index.html",
      "app://verticalhome.gaiamobile.org/index.html*"
    ],
    "js": ["main.js"] // el JavaScript que queremos inyectar
    "css": [],
  }],
  "name": "Unread Icons",
  "description": "Add unread count on icons on the homescreen. Works on: Phone, Messages, Calendar, E-mail, Gallery, BzLite",
  "author": "Shing Lyu",
  "version": "1.0",
  "icons": {
    "128": "/icons/icon-128.png"
  }
}

Para simplificar la estructura del complemento, no inyectamos archivos JavaScript separados en la app del sistema y la pantalla de inicio. En vez de esto, se inyecta un único archivo JavaScript (main.js) y revisamos window.location para determinar en cuál app estamos actualmente. Más sobre esto más adelante.

Cómo instalar un complemento a través de WebIDE y activarlo

Antes de profundizar en el código JavaScript, nos desviamos un momento para instalar y activar el complemento.

Aunque probablemente desearás instalar el producto final a través del Firefox Marketplace, durante el desarrollo es más fácil instalar el complemento a través de WebIDE. Utiliza la última version de Firefox Nightly, de otra manera puede que WebIDE no reconozca el complemento.

Empieza por abrir Firefox Nightly y seleccionar Herramientas > Desarrollador Web > WebIDE. Luego, usa el botón “Abrir aplicación empaquetada” para abrir tu complemento (selecciona la carpeta que contenga el código de complemento, no es necesario comprimir). Conecta tu teléfono Firefox OS o el simulador y presiona el botón de instalar.

Instalación con WebIDE

Una vez que el complemento se haya instalado de manera exitosa, necesitamos habilitarlo en el teléfono. Abre el app de configuración y selecciona la sección “complementos”. Deberías ver el complemento al final de la lista.

Lista de complementos

Haz clic sobre el título, y presiona el interruptor para activarlo.

Activar complemento

Es importante recordar que cuando el complemento está activado y la app que se está inyectando está en ejecución (por ejemplo, la del sistema), el código del complemento se ejecutará inmediatamente. De lo contrario, se inyectará el código del complemento solo una vez que se abra la aplicación. Ten en cuenta también que cuando se desactiva un complemento, su código que está en ejecución no se desactivará automáticamente. Esto significa que si se activa y desactiva el complemento múltiples veces, se inyectarán copias múltiples del código. Es una muy buena idea prevenir esto en el código de complemento. Para obtener más información, consulta cómo prevenir inyecciones múltiples en MDN.

Recolectar mensajes sin leer en la aplicación del sistema

En primer lugar, con el fin de recoger las notificaciones sin leer en el app del sistema, el código JavaScript tiene que saber acerca de los mensajes no leídos en la aplicación del sistema:

if (window.location.toString() === 'app://system.gaiamobile.org/index.html') {
  // escuchar notificaciones del sistema de mensajes no leídos
}

Una vez que estemos seguros de que estamos en el app del sistema, añadimos un detector de eventos para el  mozChromeNotificationEvent, el evento del sistema que se activa cuando aparece una notificación. Éste es un ejemplo del evento de notificación:

mozChromeNotificationEvent {
  target: Window,
  detail: Object, // lo importante está aquí
  timeStamp: 1445421387703168, 
  … // detalles irrelevantes que vamos a omitir
}

Al profundizar en el objeto detail se revela:

{
  "id": "app://system.gaiamobile.org/manifest.webapp#tag:screenshot:1445421387658", // queremos ésto
  "appIcon": "app://system.gaiamobile.org/style/icons/system_126.png",
  "appName": "System",
  "data": {
    "systemMessageTarget": "screenshot"
  },
  "manifestURL": "app://system.gaiamobile.org/manifest.webapp",
  "text": "screenshots/2015-10-21-17-56-27.png",
  "title": "Screenshot saved to Gallery",
  … // detalles irrelevantes que vamos a omitir
}

Podemos ver que en event.detail, el campo id es particularmente interesante. Contiene el URL del manifiesto del app, el tipo de evento (screenshot) y algunos UUIDs. Podemos construir fácilmente un filtro que mapea los identificadores en el ID a las apps.

Dado que cada app puede generar muchos tipos de notificaciones, necesitamos filtrar sólo aquellos que nos hablan de un mensaje no leído, y asignarlos a los íconos de las apps. Se requiere del mapeo porque algunas veces el app que genera la notificación no es la misma que tiene un mensaje no leído. Por ejemplo, el sistema se encarga de generar una notificación de “captura de pantalla tomada”, pero el número de notificación sin leer se debe mostrar en la aplicación de galería de imágenes.

Después de que recibimos una notificación, comprobamos si representa un nuevo mensaje sin leer buscando su tipo de evento en la tabla de búsqueda descrita anteriormente. Si la notificación representa un mensaje no leído para una app en particular, incrementamos el conteo de no leídos para esa app. Esto se puede hacer utilizando el siguiente ejemplo de código:

window.addEventListener('mozChromeNotificationEvent', function(evt){
  var iconUrl = notificationIdToIconUrl(evt.detail.id); // el filtrado y mapeo se hace aquí
  if (typeof iconUrl != "undefined") {
    increaseUnreadByOne(iconUrl); // lo veremos luego
  }
})

Dentro de la función increaseUnreadByOne() mantenemos una cuenta de los mensajes no leídos para cada app utilizando el siguiente objeto JavaScript:

{
  "unreads": {
    "app://calendar.gaiamobile.org/manifest.webapp": 0,
    "app://communications.gaiamobile.org/manifest.webapp-dialer": 0,
    "app://gallery.gaiamobile.org/manifest.webapp": 0,
    "app://sms.gaiamobile.org/manifest.webapp": 0,
    ...
  }
}

Para acceder a las cuentas sin leer desde la pantalla de inicio, necesitamos escribir dichos valores en la configuración desde el app del sistema. Dado que muchas apps pueden querer acceder a la configuración de forma simultánea, es necesario adquirir un bloqueo (lock) antes de que podamos leer/escribir los datos. Así es como se implementa la función increaseUnreadByOne():

function increaseUnreadByOne(appUrl){

  var unreads = {}
  var lock = navigator.mozSettings.createLock();
  var setting_get = lock.get('unreads');

  setting_get.onsuccess = function () {
    if (typeof setting_get.result.unreads === "undefined"){ 
      unreads[appUrl] = 1
    }
    else {
      unreads = setting_get.result.unreads;
      if (appUrl in unreads){ 
        unreads[appUrl] += 1;
      }
      else {
        unreads[appUrl] = 1;
      }
    }

    // escribir el conteo en la base de datos de configuración.
    var lock = navigator.mozSettings.createLock();
    var setting_set = lock.set({ 'unreads': unreads });
  }

  setting_get.onerror = function () {
    console.log("[UNREAD] An error occurred, the settings remain unchanged");
  }
}

Cómo dibujar las notificaciones en los íconos

Ahora que tenemos las cuentas listas con mensajes no leídos, necesitamos desplegarlas correctamente en la pantalla de inicio. Como se mencionó anteriormente, se inyectará la misma copia del código JavaScript del complemento (main.js) en las apps de sistema y pantalla de inicio. Por consiguiente, ahora queda revisar que estamos en el app de la pantalla de inicio, tal como lo hicimos para el app del sistema.

El siguiente código JavaScript lee las cuentas de no leídos desde la base de datos de configuraciones, y trata de dibujar dichas cuentas en los íconos de la aplicación. El ícono de las cuentas no leídas es realmente un nodo inyectado bajo el nodo DOM del ícono de la aplicación, con un fondo rojo y un border-radius de 100% (lo que lo convierte en un círculo).  El nodo DOM del ícono de la aplicación se puede localizar fácilmente porque tiene un atributo data-identifier que es (casi siempre) el manifestURL del app. (Una excepción es la aplicación Dialer, que tiene un sufijo extraño -dialer, pero en aras de la simplicidad la tratamos como un caso especial.)

function drawUnreadIcon(appUrl, number){
  var app_selector = '.icon[data-identifier="' + appUrl + '"]'; //ubicar el ícono del app
  var unreadIconElement = document.getElementById(app_selector+"-unread"); 
  if (number === 0){
    if (unreadIconElement){
      unreadIconElement.parentNode.removeChild(unreadIconElement);
    }
    return
  }
  else{
    if (number > 99) { number = "N" }
    if (unreadIconElement){
      unreadIconElement.textContent = number;
    }
    else {
      unreadIconElement = document.createElement('div');
      unreadIconElement.id = app_selector + "-unread";
      unreadIconElement.style.backgroundColor = "red";
      unreadIconElement.style.borderRadius = "100%";
      //Más líneas unreadIconElement.style.foo = "bar" fueron omitidas

      unreadIconElement.appendChild(document.createTextNode(number));
      document.querySelector(app_selector).appendChild(unreadIconElement);
    }
  }
}

Nota que usamos la sintaxis element.style.foo="bar" para asignar el CSS en vez de inyectar un archivo CSS separado. Esto inició como una solución para una versión anterior del sistema de complementos que no funcionó bien cuando se inyectaban los archivos CSS. (Puedes revisar el estado de este tema en el bug 1179536.) Ahora, conforme esté disponible el soporte CSS, no existe una razón de peso para cambiar nuestro enfoque. Este complemento es bastante simple, por lo que no nos tomaremos la molestia en dividir el código en múltiples archivos. Sin embargo si deseas construir un complemento más complejo, podrías considerar dividir el CSS en un archivo diferente para mayor claridad.

Cómo realizar la actualización en tiempo real

Queremos que nuestra cuenta no leída se actualice en tiempo real pero no queremos sondear la base de datos de la configuración porque no ofrece información oportuna y puede agotar la batería. En cambio, podemos suscribir el evento de actualización de configuración de manera que cada vez que la información en la base de datos de la configuración cambie, se desencadenará una actualización inmediatamente.

window.navigator.mozSettings.addObserver('unreads', function(evt){
  var settings = evt.settingValue;
  for (appUrl in setting.result.unreads){
    drawUnreadIcon(appUrl, setting.result.unreads[appUrl]);
  }
})

Una nota al margen acerca de la comunicación entre apps

Dado que este complemento se construyó durante un reciente hackathon, escogimos una forma rápida e improvisada de construir el canal de comunicación entre apps, usando el almacenaje de las configuraciones del sistema. Parece no existir aún un patrón bien aceptado para las comunicaciones entre apps para los complementos. Pero tal vez quieras revisar las siguientes opciones (que aún son experimentales y no está garantizado que funcionen):

Depuración y pruebas

Aunque podemos instalar el complemento a través de WebIDE, la consola JavaScript y el depurador aún no están listos para depurar complementos. La única forma de depurar los complementos actualmente es usar console.log() para imprimir los mensajes de depuración en la salida adb logcat. Es posible que desees anteponer algún prefijo a la salida del registro para que puedas filtrarla fácilmente. Además, estarás revisando cómo funcionan las aplicaciones centrales con frecuencia (si deseas cambiar el comportamiento del sistema), y WebIDE será útil en estas situaciones. Puedes conectar tu teléfono real a WebIDE, luego usar “Información del entorno de ejecución” > “Solicitar privilegios más elevados” para acceder a las apps predeterminadas y del sistema. (Revisa MDN para más información.)

Solicitar más privilegios con WebIDE

Una vez que el teléfono se reinicie, podrá ver las opciones de “Proceso Principal” y otras app centrales en la lista de aplicaciones en ejecución:

Apps centrales ejecutándose

Puedes usar el inspector DOM para revisar los elementos DOM que desees ajustar:

Inspector de DOM en WebIDE
Y puedes usar la consola JavaScript para probar algunos fragmentos de código antes de escribirlos en tu app:

Consola JS en WebIDE

Cómo publicar

Una vez de que hayas construido un complemento del que te sientas orgulloso, sin lugar a duda desearás compartirlo con todos. Firefox Marketplace te ofrece la opción de publicarlo para el mundo entero.

Necesitarás comprimir sus archivos, incluyendo manifest.json en un archivo zip. Luego, súbelo a la página de complementos de Marketplace.

Ya que los complementos son muy poderosos, revisores expertos de Mozilla los revisarán para garantizar que la seguridad y privacidad del usuario no se vea comprometida. Podrá encontrar los criterios de revisión aquí. Asegúrate de leerlos antes de enviar tu complemento.

Conclusión

Te hemos guiado a través del proceso de construcción de un complemento simple para Firefox OS. Te hemos mostrado cómo interceptar notificaciones del sistema, leer y escribir configuraciones del sistema y dibujar sus elementos UI personalizados en la pantalla de inicio. También te mostramos cómo instalar y depurar complementos usando WebIDE. Personalmente, considero que los complementos de Firefox OS serán un cambio de juego para la plataforma Firefox OS. ¡Los complementos facultan al usuario a tomar control total de sus experiencias móviles! Este artículo también demuestra que tan fáci es construir un complemento usando la tecnología estandarizada de la web.

¡Esperamos ver muchas personas ingeniosas construyendo complementos!

The following two tabs change content below.

jorgev

AMO Product Manager at Mozilla
Jorge trabaja para el equipo de complementos de Mozilla, y se dedica a Mozilla Hispano y Mozilla Costa Rica en su tiempo libre. Actualmente está encargado del blog de Mozilla Hispano Labs.

Compartir artículo:

  • ¡Participa!

    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