Video Summary and Transcription
La charla discute el desarrollo de la extensión de navegador Jam, que es una herramienta de informe de errores. Explora los desafíos de la mensajería entre diferentes entornos de ejecución dentro de un navegador y la necesidad de dividir los mensajes para superar las limitaciones de tamaño. La charla también explica cómo el equipo de desarrollo reconstruyó el sistema utilizando un enfoque de pila de red TCP/IP, lo que les permitió resolver dificultades de mensajería similares a los problemas de redes. Los beneficios de este enfoque incluyen un despliegue más fluido, una depuración más sencilla y un enfoque en el desarrollo de funciones sin preocuparse por las limitaciones de mensajería.
1. Introducción a la extensión del navegador Jam
Hola, soy Cyrus, un ingeniero en Jam. Hoy hablaré sobre la creación de una pila de red para nuestra extensión de navegador llamada Jam. Jam es una extensión para informar errores que te ayuda a recibir informes detallados de errores con funciones como capturas de pantalla, solicitudes de red y registros de consola. Ahorra tiempo y brinda una experiencia sin complicaciones. La extensión consta de componentes como el script de fondo, el pop-up, el script de contenido y el script de host, todos operando dentro de una ventana del navegador.
Hola, soy Cyrus. Soy un ingeniero en Jam, y esta charla trata sobre la creación de una pila de red para nuestra extensión de navegador.
Ahora, si no sabes qué es una pila de red, o si sabes qué es una pila de red y te parece extraño que estemos construyendo una en la capa de aplicación dentro de una extensión de navegador, no te preocupes, responderemos todas esas preguntas en esta charla.
Primero, ¿qué estamos construyendo? ¿Qué es Jam? Jam es una extensión de navegador para informar errores. Y lo que eso significa es que te ayudamos a recibir informes de errores perfectos cada vez. Normalmente, cuando recibes un informe de errores de un gerente de producto o un QA, no es una experiencia divertida porque no obtienes muchos detalles sobre el error. Puede que recibas una o dos frases como `esta página está rota` y tienes que descifrar qué significa eso. Con Jam, cada vez que tu gerente de producto presenta un error, tienen que seleccionar una captura de pantalla o grabar la pestaña o una repetición instantánea. Así que puedes ver visualmente cuál es el error. Y luego automáticamente incluimos otra información como los registros de la consola, las solicitudes de red, los metadatos de la página, los pasos de reproducción. Entonces, cada vez que un usuario hace clic en un botón, se incluyen detalles como la información del dispositivo, la marca de tiempo, y otros detalles que te gustaría tener una idea de lo que realmente sucedió. Cuando recibes este error, puedes abrirlo y es como tener las DevTools de tu navegador abiertas, como si tuvieras el error justo ahí en tu computadora portátil. Y puedes ver las cabeceras, el cuerpo de la solicitud, el cuerpo de la respuesta, los registros de la consola, lo que sea. Básicamente, te ahorramos mucho tiempo en idas y venidas. No tienes que hacer una llamada. Y es una experiencia sin complicaciones.
Para hacer esto, tenemos una extensión de navegador. Y esta extensión de navegador tiene algunos componentes dentro de ella. El componente principal es un script de fondo. Y un script de fondo es como un servidor para una extensión de navegador. Como se ejecuta en segundo plano, pero es local. Y tiene una vida útil prolongada. Hay algunos otros componentes efímeros, como el pop-up, el script de contenido, el script de host. Todos estos viven dentro de una ventana del navegador. Y una ventana del navegador es como, abres una ventana de Chrome. Una ventana de Chrome puede tener varias pestañas. Y también puede tener un pop-up. Entonces, el pop-up es como, cuando haces clic en un icono de extensión, aparece este pop-up ventana. Puedes interactuar con ella y luego se cierra.
2. Execution Environments and Messaging
Dentro de una pestaña, hay dos entornos de ejecución: el script de contenido y el script de host. Se comunican de manera diferente y tienen restricciones específicas. Se utilizan API de mensajería como window.postMessage y la API de mensajería de Chrome. Algunas API tienen limitaciones de tamaño. El script de host solo puede comunicarse con el script de contenido, lo que requiere el reenvío de mensajes. Varias instancias de componentes pueden plantear desafíos al dirigir mensajes al script de contenido correcto.
Y luego una pestaña es como cuando vas a Hacker News o Google, o algo así. Y dentro de esta pestaña, hay dos componentes, o más bien dos entornos de ejecución. Uno de ellos es el entorno de ejecución del script de contenido. Y uno de ellos es el entorno de ejecución del script de host. El entorno de ejecución del script de contenido es como un entorno personalizado donde está aislado de la propia página. Entonces, si la página en la que te encuentras modifica una propiedad de la ventana, el script de contenido no se ve afectado por ello. Y esto básicamente es para que las extensiones inyecten code dentro de este entorno de ejecución del script de contenido y no permitan que sus scripts sean modificados por la página de host.
Y luego el entorno de ejecución del script de host es donde residen los scripts de la página de host. Entonces, todos estos componentes de esta extensión de navegador se comunican, pero se comunican de manera ligeramente diferente. Todos estos componentes se comunican en ambas direcciones. Desde el pop-up hasta el script de fondo y viceversa, puedes enviar mensajes desde el script de contenido y el script de fondo y viceversa. Pero el script de host es la excepción. Y el script de host solo puede comunicarse con un script de contenido. Entonces, si quieres enviar un mensaje desde el script de fondo al script de host y obtener una respuesta, debes reenviarlo a través del script de contenido. Básicamente, debes configurar un controlador que redirija ese mensaje al script de host y luego redirija el mensaje del script de host de vuelta al script de contenido.
Y esto es un poco difícil de manejar cuando solo estás tratando de crear una función dentro de tu extensión de navegador. Como, simplemente quieres construir algo, y ahora tienes que pensar en todas estas diferentes restricciones que debes tener en cuenta al enviar mensajes de ida y vuelta para alimentar esa función. Las restricciones son básicamente las que se enumeran aquí. Tenemos diferentes API de mensajería que debemos usar para todos estos componentes. Entonces, entre el script de contenido y el script de host, podríamos estar usando window.postMessage. Pero luego, entre el script de contenido y el fondo, podríamos estar usando una API de mensajería de Chrome. Y esa es una API diferente a la que se utiliza entre el pop-up y el script de fondo. Entonces, aquí estamos utilizando algunas API diferentes y debemos tenerlo en cuenta. Además, algunas de esas API tienen restricciones de tamaño. El tamaño de un mensaje está limitado. Y luego, al igual que antes, algunos de estos componentes no pueden comunicarse directamente. Entonces, cuando estás lidiando con el script de host, debes redirigir mensajes de ida y vuelta hacia él a menos que solo estés enviando mensajes desde el script de contenido. Y muchos de estos componentes pueden... Puedes tener varias instancias de ellos, por lo que puedes tener varias pestañas, puedes tener múltiples pop-ups. Y eso puede presentar algunos problemas cuando intentas dirigir mensajes a las pestañas correctas del script de contenido.
3. Troubleshooting and Preview Challenges
Los mensajes pueden perderse si se cierra la ventana emergente o la pestaña. También pueden perderse si el hilo de una pestaña está sobrecargado o si los mensajes son demasiado grandes. El soporte del modo incógnito es crucial para Jam como una extensión de informe de errores. El flujo de usuario para informar un error implica seleccionar un tipo de informe, previsualizar el informe y presentarlo en un rastreador de problemas. La previsualización de repeticiones instantáneas grandes planteó desafíos debido a las restricciones de tamaño de los mensajes, lo que requirió un truco para compartir datos entre el script de fondo y el script de contenido.
También puedes perder mensajes, o más bien, los mensajes pueden perderse. Entonces, si se cierra la ventana emergente, no recibirá ese mensaje. O si tienes una pestaña abierta, pero se cierra mientras está recibiendo o mientras está respondiendo, ese mensaje se pierde efectivamente. O si el hilo de una pestaña está procesando tanto porque JavaScript es de un solo hilo, que no puede extraer mensajes en cola de la cola de mensajes y no puede responder a ellos antes de que expiren. Por lo tanto, esos mensajes se pierden efectivamente.
Y luego este último punto aquí, compartición de memoria. Hay un truco que podemos usar para enviar mensajes de ida y vuelta si un mensaje es demasiado grande para caber dentro de un solo... Los data dentro de un mensaje son demasiado grandes para caber dentro de un solo mensaje. Lo veremos más adelante, en un segundo.
Pero todo esto es para decir que cuando comenzamos a construir el modo incógnito, tuvimos algunos problemas. Entonces, el soporte del modo incógnito para Jam es bastante importante porque somos una extensión de informe de errores, y... A los QA les gusta usar el modo incógnito cuando están ejecutando ciertos flujos. Es bastante importante. Así que queríamos que esto funcionara, pero tuvimos algunos problemas. Y explicaré cómo. Básicamente, este es el flujo general del usuario de lo que hace un informante de errores al informar un error con Jam. Entonces, primero, pueden abrir la ventana emergente y seleccionar un tipo de informe de errores. Esto podría ser algo anterior, como una captura de pantalla, una pestaña, una grabación o una repetición instantánea, o una grabación de escritorio. Y una vez que seleccionan ese tipo, digamos que seleccionan una repetición instantánea, entonces mostraremos una previsualización de esa repetición instantánea en la pestaña en la que se encuentran. Así que la seleccionan y aparece instantáneamente. Y pueden decidir recortar esta repetición instantánea. Pueden ver algunos de los errores que aparecieron durante su tiempo, como algunos errores de solicitud de red o algunos errores de registro de console. Pueden enviar esto a Linear, o cualquier rastreador de problemas que deseen, e incluir un título y seleccionar el equipo correcto, y luego crear el problema. Eso suena bastante simple. Pero esta parte de la previsualización nos estaba causando problemas porque los datos de previsualización para una repetición instantánea se almacenan en el fondo. Y cada vez que queremos mostrarlo en la página, tenemos que enviar esos datos desde el fondo al script de contenido. Pero las repeticiones instantáneas pueden ser bastante grandes. Y típicamente no cabrían en un solo mensaje, simplemente debido a las restricciones de tamaño de las API de mensajería que usaríamos para enviar desde el fondo al script de contenido. Así que usaríamos este truco anterior para compartir los datos desde el fondo hasta el contenido script. Y este truco es básicamente, creas una URL de objeto en un extremo en el fondo, y envías esa URL, como una cadena, como una URL.
4. Message Chunking and TCP IP Network Stack
Obtener datos en el lado del script de contenido permite que los procesos compartan memoria, resolviendo las restricciones de tamaño en las API de mensajería. La implementación del modo incógnito requiere la fragmentación de mensajes para superar las diferencias de contexto de URL de objeto. Tomando prestado del stack de red TCP/IP, reconstruimos el sistema para abordar las dificultades de mensajería, similares a los problemas de redes como diferentes protocolos de capa de enlace, restricciones de tamaño de mensaje y múltiples dispositivos independientes.
Y luego lo obtienes en el lado del script de contenido. Y lo obtienes como si fuera una solicitud HTTP regular, excepto que todo está dentro de tu navegador, y básicamente permite que los procesos compartan un objeto en memoria, esencialmente. Lo cual es realmente útil para evitar problemas con las restricciones de tamaño en las API de mensajería. Pero cuando estábamos implementando el modo incógnito, nos dimos cuenta de que esto era un problema, porque las pestañas de incógnito tienen diferentes contextos de URL de objeto. Entonces, dentro de tu script de fondo y tus pestañas no incógnitas, tienes un contexto, y dentro de tus pestañas de incógnito, tienes otro contexto. Entonces, si creas una URL de objeto desde tu fondo y la envías a tu pestaña de incógnito, tu pestaña de incógnito intentaría obtenerla y simplemente no obtendría nada, básicamente un error 404.
Así que tuvimos que descubrir cómo implementar la fragmentación de mensajes. Y básicamente tomar este mensaje grande, que tenía nuestros datos de repetición instantánea dentro, y simplemente fragmentarlo y enviarlo en una serie de mensajes, y luego reconstruirlo en el otro lado. El problema era que, dado que teníamos este sistema que no fue construido teniendo en cuenta la fragmentación de mensajes en mente, tuvimos que retroceder y reconstruirlo teniendo eso en cuenta. Afortunadamente, teníamos una conveniencia realmente buena aquí, que es que las dificultades de mensajería que estamos resolviendo ya han sido resueltas. Son problemas generales de redes, en realidad.
Es posible que reconozcas algunas similitudes aquí, donde tener diferentes API de mensajería subyacentes también ocurre dentro del stack de TCP/IP. Tendrás diferentes protocolos de capa de enlace, como ethernet versus Wi-Fi. Las restricciones de tamaño de mensaje también ocurren cuando tienes un límite de tamaño de paquete Wi-Fi y ethernet. Los componentes no pueden comunicarse directamente. Es como enrutar desde tu computadora a Google.com, definitivamente requerirá algunos saltos para reenviar esos paquetes. Múltiples pestañas y ventanas emergentes pueden existir de forma independiente. Es como tener múltiples dispositivos en Internet. Debes abordarlos de forma independiente. No puedes transmitir en broadcast. La pérdida de mensajes es similar a la pérdida de paquetes. No se permite compartir memoria, es como tener máquinas separadas donde no puedes hacer trampa. Debes serializar tus datos. No puedes transferirlos instantáneamente. Esto es realmente útil porque la abstracción que tomamos prestada fue básicamente el stack de red TCP/IP.
5. Network Stack and TCP IP Layers
Una pila de red es una capa de protocolos que resuelve problemas específicos. Cada capa se basa en las garantías ofrecidas por las capas anteriores, lo que permite a los desarrolladores centrarse en resolver problemas de los usuarios finales sin preocuparse por los detalles de nivel inferior. En la pila TCP/IP, la capa de enlace maneja la mensajería entre dispositivos conectados en red localmente, la capa de internet maneja el enrutamiento y el reenvío de paquetes utilizando una tabla de enrutamiento, y la capa de aplicación proporciona herramientas amigables para los desarrolladores, como los métodos HTTP y el cifrado.
Lo que es una pila de red es básicamente una capa de estos diferentes protocolos. Entonces, cada capa de protocolo resuelve un problema específico. Por ejemplo, si quieres resolver el direccionamiento, puedes mirar la capa de red, donde pensamos en las direcciones IP. Si quieres pensar en la confiabilidad de los paquetes, como asegurarte de que tu transmisión esté intacta, como enviar un flujo TCP, eso ocurriría en la capa de transporte. Entonces, cada capa básicamente se basará en las garantías ofrecidas por las capas anteriores.
Así que cuando llegas a la capa de aplicación y estás haciendo solicitudes HTTP, tu solicitud HTTP no tiene que preocuparse por la capa física. Entonces, si hay un usuario que usa Ethernet o un usuario que usa Wi-Fi, simplemente no te importa eso, lo cual es realmente bueno, porque estás resolviendo problemas de los usuarios finales. Estás construyendo características y no tienes que pensar en estas cosas. Nosotros también queríamos hacer eso, para no tener que pensar en las dificultades de mensajería mientras construíamos características dentro de nuestra extensión.
Entonces, dentro de la pila TCP/IP, esto es lo que parece: en la parte inferior, tienes la capa de enlace, que tiene Ethernet y Wi-Fi, es como la mensajería entre dispositivos conectados en red localmente. Encima de eso, en la capa de internet, tienes paquetes IP, básicamente, donde la capa de internet maneja el enrutamiento y el reenvío de paquetes, y utiliza una tabla de enrutamiento. Una tabla de enrutamiento es básicamente una tabla de búsqueda rápida para determinar dónde enrutará los paquetes IP en función de su dirección. Esto es útil porque es posible que no estés conectado directamente a cada máquina a la que intentas enrutar, por lo que una tabla de enrutamiento te ayuda a determinar cuál es el siguiente salto mejor para enviarlo. Y esa tabla de enrutamiento puede estar alimentada por diferentes cosas, pero a un nivel muy alto, está alimentada por el Protocolo de Puerta de Enlace de Borde, o BGP, que es un protocolo de enrutamiento dinámico para determinar la ruta más económica entre sistemas autónomos, que básicamente son proveedores de servicios de Internet. Y todo eso se alimenta en el direccionamiento de esa capa de internet.
6. Transport and Application Layers
La capa de transporte proporciona funciones de confiabilidad como prevenir paquetes recibidos duplicados, reintentar automáticamente paquetes perdidos y manejar la transmisión. La capa de aplicación ofrece herramientas amigables para los desarrolladores como métodos HTTP, encabezados, cookies, cifrado y direccionamiento legible para el usuario. La capa de enlace maneja la selección de la API de mensajería correcta proporcionada por el navegador, mientras que la capa de paquetes maneja el enrutamiento a través de una tabla de enrutamiento simplificada. La capa de datagramas implementa la fragmentación para el modo incógnito, y la capa de aplicación utiliza un patrón de estilo de intermediario para el envío y recepción de mensajes. Estas capas abordan varios problemas encontrados durante el proyecto, lo que resulta en beneficios significativos.
Por encima de esa capa se encuentra una capa de transporte, por lo que UDP o TCP son ejemplos de implementaciones de protocolos de esta capa, y estos proporcionan cosas como confiabilidad, como prevenir paquetes recibidos duplicados, reintentar automáticamente paquetes perdidos o manejar la transmisión, como lo hace TCP.
Y luego, por encima de la capa de transporte, se encuentra la capa de aplicación, que todos conocemos y amamos, y es muy fácil de trabajar como desarrollador. Nos brinda un montón de herramientas agradables para desarrolladores de usuario final, como proporcionar métodos HTTP, encabezados, cookies, cifrado o direccionamiento legible para el usuario, que es cuando DNS traduce una URL del sitio web a la dirección IP a la que intentas apuntar. Y decidimos tomar todo esto para nuestra pila, que se ve así.
No tiene un nombre muy creativo. Básicamente son los mismos nombres de capas. Pero en la parte inferior, tenemos la capa de enlace, que, al igual que la capa de enlace en la pila TCP/IP, nuestra capa de enlace maneja la selección de la API de mensajería correcta proporcionada por el navegador. Entonces, un ejemplo es Chrome Runtime.sendMessage. Otro ejemplo es window.postMessage. Simplemente elegimos cuál es la API de mensajería correcta en función de la fuente y el destino.
La capa por encima de eso es la capa de paquetes, que, al igual que la capa de internet y los paquetes IP, manejamos el enrutamiento en esta capa a través de nuestra tabla de enrutamiento. Nuestra tabla de enrutamiento es mucho más simple porque la estructura de nuestra extensión de navegador está bastante definida. No tienes que tener un algoritmo como BGP para determinarlo. Entonces nuestra tabla de enrutamiento está codificada. Por encima de la capa de paquetes se encuentra la capa de datagramas, que es donde implementamos la fragmentación que necesitábamos para el modo incógnito en esta capa. Todavía utilizamos la obtención de URL de objetos, pero básicamente determinamos qué protocolo utilizar en esta capa, como si necesitamos fragmentación o si podemos usar URL de objetos, y esto depende de si el remitente y el receptor están en el mismo contexto de incógnito.
Y luego, por encima de la capa de datagramas, está la capa de aplicación, donde implementamos un patrón de estilo de intermediario, que se puede utilizar para enviar y recibir mensajes, y es muy conveniente. Puedes pensar en ello como HTTP. No tienes que pensar en ninguna de estas otras capas cuando estás trabajando en la capa de aplicación. Esto básicamente resolvió todos nuestros problemas. Aquí están nuestros problemas y aquí están nuestras soluciones. Para diferentes API de mensajería subyacentes, abstraemos esto con la capa de enlace, que maneja eso. Con restricciones de tamaño para los mensajes, lo manejamos fragmentando en la capa de datagramas. La incapacidad de los componentes para comunicarse directamente, lo manejamos reenviando paquetes en la capa de paquetes, para que podamos saltar sobre componentes y llegar al script del host. Tener múltiples pestañas o ventanas emergentes, esto se manejó con direccionamiento independiente en la capa de paquetes también. Los mensajes que se pierden, los manejamos mediante reintentos de paquetes en la capa de datagramas, y no se permite compartir memoria. Esto es el cambio dinámico entre la fragmentación y otro protocolo en la capa de datagramas según si el remitente o el receptor está en modo incógnito. Y eso es prácticamente todo. Mirando hacia atrás en este proyecto, esto fue hace aproximadamente ocho meses. Tuvimos algunos grandes beneficios de esto.
7. Smooth Rollout and Developer Focus
Mantuvimos la interfaz de alto nivel de nuestro sistema de mensajería anterior mientras reestructurábamos las capas subyacentes, lo que resultó en un despliegue sin problemas. La depuración se volvió más sencilla con la separación de responsabilidades en cada capa y la capacidad de escribir pruebas. Nuestro objetivo principal era permitir que los desarrolladores se centren en construir características sin preocuparse por las restricciones de enlace de mensajes o el reenvío de mensajes. Esta abstracción se logró a través de nuestra pila de red.
Nuestro sistema anterior que utilizábamos para la mensajería tenía una interfaz agradable, y simplemente mantuvimos esa interfaz de alto nivel mientras reestructurábamos todo lo que había debajo. Eso nos dio un despliegue bastante fluido. No tuvimos que cambiar ninguna de nuestras aplicaciones. Simplemente creamos esta pila de red y luego reemplazamos nuestro antiguo sistema de mensajería por esto, pero tenían la misma interfaz final, por lo que el despliegue fue realmente sencillo.
También tuvimos una depuración más sencilla aquí, porque cada capa se enfoca en su propia tarea, por lo que tenemos una separación de responsabilidades que nos permite identificar dónde se producen los problemas, y podemos escribir pruebas en cada capa. Básicamente, si algo sale mal, lo descubriremos cuando nuestra integración continua ejecute nuestra suite de pruebas.
Realmente, nuestro objetivo principal con esto, además de construir el soporte de incógnito, era permitir que nuestros desarrolladores se centren en construir características. Antes de esto, teníamos que pensar realmente si necesitábamos implementar un controlador de reenvío en la capa de script de contenido si queríamos enviar un mensaje al script del host. Ahora todo esto es básicamente programático. Cuando estás construyendo una nueva característica en la extensión Jam, nuestros desarrolladores no tienen que pensar en las restricciones de enlace de mensajes. No tienen que pensar en reenviar estos mensajes a través del script de contenido. Todos estos problemas básicamente se abstraen mediante nuestra pila de red.
Eso es todo. Espero que hayas disfrutado esta charla.
Comments