Codelab: Compila una extensión de Chrome en JavaScript con Gemini

1. Introducción

¿Te unirás a una llamada de Meet pero no quieres ser la primera persona en hacerlo? Si esto te describe, tenemos la solución para ti.

En este codelab, crearás una extensión de Chrome que te alertará cuando el primer participante se una a la llamada.

Aprenderás los diferentes elementos de una extensión de Chrome y, luego, profundizarás en cada parte de la extensión. Aprenderás sobre funciones de extensión, como secuencias de comandos de contenido, service workers y transmisión de mensajes.

Debes cumplir con la versión 3 del manifiesto para poder recibir una notificación cada vez que un participante se una a una llamada de Meet.

2. Antes de comenzar

Requisitos previos

Si bien este codelab es adecuado para principiantes, tener un conocimiento básico de JavaScript puede mejorar enormemente tu experiencia.

Configuración y requisitos

  • El navegador Chrome
  • Una configuración de IDE/Editor en tu sistema local
  • Instala gcloud cli si quieres habilitar la API de Gemini con gcloud.

Habilita la API de Gemini

Note that if you're writing the code in the Cloud Shell editor,
then you will have to download the folder somewhere on your local filesystem to test the extension locally.

3. Empecemos a celebrar

Instalación básica de extensiones

Creemos un directorio que utilizaremos como raíz de nuestro proyecto.

mkdir gemini-chrome-ext
cd gemini-chrome-ext

Antes de empezar a hacerle preguntas específicas a Gemini, hagámosle algunas preguntas sobre la estructura genérica de una extensión de Chrome.

Instrucción:

What are the important parts to build a chrome extension?

Obtenemos una respuesta que especifica detalles menores sobre el archivo manifest, background script y los detalles de la interfaz de usuario. Analicemos con más detalle estos archivos específicos.

Instrucción:

Create a manifest.json file to build a chrome extension.
Make the name of the extension "Meet Joinees Notifier"
and the author "<YOUR_EMAIL>"

Puedes usar el nombre que quieras y tu correo electrónico en el campo de autor.

Gemini muestra el contenido del archivo de manifiesto que necesitamos, pero obtenemos algunos campos adicionales que no necesitaremos, como el campo action. Además, necesitamos una descripción. Sin embargo, podemos solucionarlo.

Instrucción:

Remove the "action" field and make the description as
"Adds the ability to receive a notification when a participant joins a Google meet".

Agreguemos este contenido al archivo manifest.json, en la raíz de tu proyecto.

En esta etapa, el archivo de manifiesto debería verse de la siguiente manera.

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>"
}

Quita cualquier otro campo adicional generado en tu archivo de manifiesto por ahora, ya que en este codelab se asumen estos campos en tu archivo de manifiesto.

¿Cómo probamos que la extensión funcione? Preguntemos a nuestro amigo Gemini, ¿de acuerdo?

Instrucción:

Guide me on the steps needed to test a chrome extension on my local filesystem.

Me da algunos pasos para probarlo. Naveguemos a chrome://extensions para navegar a "Extensions Page" y asegurémonos de habilitar el botón "Developer Mode", que debería mostrar el botón "Load unpacked", que podemos usar para navegar a la carpeta que contiene los archivos de extensión de forma local. Una vez que lo hagamos, deberíamos poder ver la extensión en "Extensions Page".

3d802a497ce0cfc2.png

92db1999a1800ecd.png

¡Genial! Podemos ver nuestra extensión, pero comencemos a agregar algunas funcionalidades.

4. Agrega una secuencia de comandos de contenido

Queremos ejecutar alguna parte del código JavaScript solo en https://meet.google.com. Podemos hacerlo mediante las secuencias de comandos de contenido. Pregúntale a Gemini cómo lograrlo en nuestra extensión.

Instrucción:

How to add a content script in our chrome extension?

O de manera más específica:

Instrucción:

How to add a content script to run on meet.google.com subdomain in our chrome extension?

O en otra versión:

Instrucción:

Help me add a content script named content.js to run on meet.google.com subdomain
in our chrome extension. The content
script should simply log "Hello Gemini" when we navigate to "meet.google.com".

Gemini nos proporciona los cambios exactos que debemos hacer en nuestro archivo manifest.json y el código JavaScript que necesitamos en nuestro archivo content.js.

Con la incorporación de content_scripts, nuestro archivo de manifiesto pasa a ser:

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "abc@example.com",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ]
}

Esto le indica a Chrome que inserte la secuencia de comandos de contenido content.js cada vez que naveguemos a una página del subdominio " https://meet.google.com". Agreguemos este archivo y probémoslo, ¿de acuerdo?

Agreguemos este código al archivo content.js.

console.log("Hello Gemini");

¡Claro que sí! Cuando visitamos meet.google.com, vemos el mensaje "Hello Gemini" en la consola de JavaScript(Mac: Cmd + Opt + J / Win/Linux: Ctrl + Shift + J).

manifest.json

{

"name": "Meet Joinees Notifier",

"version": "1.0",

"manifest_version": 3,

"description": "Adds the ability to receive a notification when a participant joins a Google Meet",

"author": "luke@cloudadvocacyorg.joonix.net",

"permissions": [

    "tabs",

    "notifications"

],

"content_scripts": [

    {

        "matches": [

            "https://meet.google.com/*"

        ],

        "js": [

            "content.js"

        ]

    }

]

}

content.js

Ђconsole.log("¡Hola, Gemini!");

6216bab627c31e6c.png

d61631cd9962ffe5.png

¡Genial! Ahora estamos en una posición en la que podemos agregar algunas funcionalidades específicas de JavaScript a nuestra app. Dediquemos un momento a pensar en lo que estamos tratando de lograr.

Mejora el guion de contenido

Queremos recibir una notificación cuando alguien se una a una reunión cuando estemos en la página de la reunión(donde tenemos la opción de unirnos a la reunión). Para lograrlo, observemos cómo cambia visualmente la pantalla cuando la reunión está vacía en comparación con cuando alguien se unió a ella.

Así se ve cuando no hay nadie en la reunión.

fe5a0c95b20e7f72.png

Por otro lado, estas son las imágenes cuando hay algunos participantes en la reunión.

7a5ef60521d961cc.png

Podemos observar 2 diferencias notables desde el principio:

  1. El texto de estado cambia de "No hay nadie más aquí". a "[Usuario] está en esta llamada".
  2. Podemos ver las imágenes de los usuarios que se unieron a la llamada.

Ambos cambios nos servirán si queremos saber si alguien se unió a la reunión, pero el último tiene algunas posibilidades de obtener información sobre los usuarios que ya se unieron, así que intentemos usar esa información.

Abre el "Inspector de elementos". en Chrome con la tecla de acceso directo ( Mac: Cmd + Opt + C / Win: Ctrl + Shift + C) y, a continuación, haz clic en la imagen del usuario que se unió.

Podemos notar que hay una imagen con pocas cadenas de clase y el atributo title de la imagen tiene el nombre del usuario que se unió a la reunión. Además, esta etiqueta de imagen está unida a un div con la clase U04fid. Después de agregar algunos participantes a nuestra reunión de prueba, podemos ver que este elemento div superior aloja varias imágenes(correspondientes a diferentes usuarios).

Entonces, tenemos en mente una estrategia parcial:

  1. Detecta si el elemento div con la clase U04fid contiene algún elemento.
  2. Si es así, significa que ya tenemos participantes de la reunión.
  3. Notifica al usuario.

Volvamos a Gemini para preguntarle cómo podemos lograr esto paso a paso.

Instrucción:

How to detect for element changes in the HTML page?

Nos brinda una solución que involucra MutationObserver, pero no es una solución específica para nuestro requisito. Intentemos modificar la instrucción.

Instrucción:

How can I detect if an element "div.U04fid" has any child images in the HTML page?

Esta vez, obtenemos una solución que no incluye MutationObserver, sino una que verifica las uniones con el elemento div superior.

Esto es lo que recibimos:

const divU04fid = document.querySelector('div.U04fid');
const childImages = divU04fid.querySelectorAll('img');
if (childImages.length > 0) {
  // div.U04fid has at least one child image.
}

Intentemos modificar aún más nuestro enfoque:

Instrucción:

Create a method which checks if the element div.U04fid has any child elements,
if it does it should log it on the console. The method should be called checkJoinees
and it should be called as a callback for MutationObserver which is observing the document.body.

Se muestra con el siguiente fragmento de código:

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
          // div.U04fid has at least one child element.
          console.log('div.U04fid has at least one child element.');
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

Copia este código en el archivo content.js y vuelve a cargar la extensión(luego, vuelve a cargar la página de Meet).

Ahora, cuando alguien se une a nuestra reunión, podemos ver la instrucción de registro en la consola.

5. Envía una notificación al usuario

Ahora que podemos detectar cuándo un participante se unió a la reunión, intenta agregar la notificación en nuestra extensión de Chrome. Podemos navegar por la documentación de la extensión de Chrome o incluso modificar las instrucciones para saber qué estamos buscando. Sin embargo, básicamente debemos usar la API de chrome.notifications.create y la llamada a este método debe provenir de un service worker en segundo plano.

Instrucción:

Using the documentation for chrome notifications tell me how to use the chrome.notifications.create method.

Vemos algunos pasos detallados, pero los aspectos más destacados son los siguientes:

  • Agrega el permiso notifications en el archivo de manifiesto.
  • Realizar una llamada a chrome.notifications.create
  • La llamada debe realizarse en una secuencia de comandos en segundo plano.

Para agregar una secuencia de comandos en segundo plano a una extensión de Chrome en manifest version 3, necesitamos la declaración background.service_worker en nuestro archivo manifest.json.

Por lo tanto, creamos un archivo llamado background.js y agregamos lo siguiente a nuestro archivo manifest.json.

"background": {
        "service_worker": "background.js"
},
"permissions": [
        "notifications"
]

Con las adiciones anteriores, nuestro archivo de manifiesto pasa a ser de la siguiente manera:

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ],
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
            "notifications"
    ]
}

Instrucción:

Create a method sendNotification that calls the chrome.notifications.create
method with the message, "A user joined the call" for a chrome extension with manifest v3,
the code is in the background service worker

Guarda esta imagen en la raíz de la carpeta y cámbiale el nombre a success.png.

b2c22f064a3f2d9c.png

Luego, agrega el siguiente fragmento de código a tu background.js.

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

sendNotification("notif-id", "test message");

Ahora, vuelve a cargar la extensión desde la página de extensiones y deberías ver una notificación emergente de inmediato.

6. Agrega mensajes y pasa tu extensión de Chrome.

Ahora, el último paso importante que necesitamos es conectar la detección de un participante de la secuencia de comandos de contenido y el método sendNotification de la secuencia de comandos en segundo plano. En el contexto de las extensiones de Chrome, la manera de hacerlo es mediante una técnica llamada message passing.

Esto permite la comunicación entre diferentes partes de una extensión de Chrome, en nuestro caso, desde la secuencia de comandos de contenido hasta el service worker en segundo plano. Pregúntale a nuestro amigo Gemini cómo lograrlo.

Instrucción:

How to send a message from the content script to the background script in a chrome extension

Gemini responde con llamadas relevantes a chrome.runtime.sendMessage y chrome.runtime.onMessage.addListener.

En esencia, usaremos sendMessage para enviar un mensaje desde la secuencia de comandos de contenido de que alguien se unió a la llamada de Meet y onMessage.addListener como objeto de escucha de eventos para reaccionar al mensaje enviado por la secuencia de comandos de contenido. En este caso, activaremos la llamada al método sendNotification desde este objeto de escucha de eventos.

Pasaremos el mensaje de notificación y una propiedad action al service worker en segundo plano. La propiedad action describe a qué responde la secuencia de comandos en segundo plano.

Por lo tanto, este es nuestro código content.js:

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
          // div.U04fid has at least one child element.
          sendMessage();
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

function sendMessage() {
    chrome.runtime.sendMessage({
        txt: "A user has joined the call!",
        action: "people_joined"
    });
}

Y este es nuestro código background.js:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "people_joined") {
      sendNotification("notif-id", message.txt);
    }
  });
  

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

Intentemos personalizar el mensaje de notificación y obtener un ID de notificación único. Para el mensaje de notificación, podemos incluir el nombre del usuario. Si recordamos un paso anterior, podríamos ver el nombre del usuario en el atributo title de la imagen. Por lo tanto, podemos recuperar el nombre del participante con document.querySelector('div.U04fid > img').getAttribute('title')..

Con respecto al ID de la notificación, podemos recuperar el ID de la pestaña de la secuencia de comandos del contenido y usarlo como ID de la notificación. Esto se puede lograr dentro de nuestro objeto de escucha de eventos chrome.runtime.onMessage.addListener si usas sender.tab.id..

Por último, nuestros archivos deberían verse así:

manifest.json

{
    "name": "Meet Joinees Notifier",
    "version": "1.0",
    "manifest_version": 3,
    "description": "Adds the ability to receive a notification when a participant joins a Google Meet",
    "author": "<YOUR_EMAIL>",
    "content_scripts": [
        {
          "matches": ["https://meet.google.com/*"],
          "js": ["content.js"]
        }
    ],
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
            "notifications"
    ]
}

content.js

function checkJoinees(mutations) {
    for (const mutation of mutations) {
      if (mutation.type === 'childList') {
        // A child node was added or removed.
        if (document.querySelector('div.U04fid') && document.querySelector('div.U04fid').childElementCount > 0) {
            const name = document.querySelector('div.U04fid > img').getAttribute('title');
            sendMessage(name);
        }
      }
    }
    return false;
}

const observer = new MutationObserver(checkJoinees);
observer.observe(document.body, {
  childList: true,
  delay: 1000
});

function sendMessage(name) {
    const joinee = (name === null ? 'Someone' : name),
        txt = `${joinee} has joined the call!`;

    chrome.runtime.sendMessage({
        txt,
        action: "people_joined",
    });
}

background.js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === "people_joined") {
      sendNotification("" + sender.tab.id, message.txt); // We are casting this to string as notificationId is expected to be a string while sender.tab.id is an integer.
    }
  });
  

function sendNotification(notificationId, message) {
    chrome.notifications.create(notificationId, {
      type: "basic",
      title: "A user joined the call",
      message: message,
      iconUrl: "./success.png"
    });
}

7. Felicitaciones

En poco tiempo pudimos compilar una extensión de Chrome con la ayuda de Gemini. Tanto si eres un desarrollador experimentado de extensiones de Chrome como si eres nuevo en el mundo de las extensiones, Gemini puede ayudarte con las tareas que quieras realizar.

Te recomiendo que preguntes sobre las diferentes cosas que puedes hacer con una extensión de Chrome. Hay muchas APIs que vale la pena explorar, como chrome.storage, alarms, etc. Cuando te sientas atascado, usa Gemini o la documentación para saber qué estás haciendo mal o para encontrar diferentes formas de resolver el problema.

A menudo, lleva modificar la instrucción para obtener la ayuda que necesitamos, pero podemos hacerlo desde una pestaña que conserva todo nuestro recorrido contextual.