[IMP] website_livechat_external: init module#387
[IMP] website_livechat_external: init module#387augusto-weiss wants to merge 1 commit intoingadhoc:18.0from
Conversation
ebe67a6 to
348813a
Compare
There was a problem hiding this comment.
Pull request overview
Inicializa el módulo website_livechat_external para incrustar un LiveChat provisto por otra instancia de Odoo (p. ej. Odoo 19) dentro de Odoo 18, aislándolo en un iframe y agregando un acceso rápido en el backend.
Changes:
- Agrega vistas de configuración (
res.config.settings) para habilitar el livechat externo y definir URL del proveedor + ID de canal. - Inyecta un iframe en el layout del website con lógica JS para alternar
pointer-eventsy permitir interacción solo sobre el widget. - Incorpora un servicio + ítem de systray en backend para mostrar/abrir el chat.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| website_livechat_external/manifest.py | Declara el nuevo módulo, sus datos y assets backend. |
| website_livechat_external/init.py | Inicializa imports de controllers y models. |
| website_livechat_external/controllers/init.py | Exporta el controlador del módulo. |
| website_livechat_external/controllers/main.py | Expone endpoints para servir el frame y configuración del backend. |
| website_livechat_external/models/init.py | Exporta la extensión de res.config.settings. |
| website_livechat_external/models/res_config_settings.py | Define parámetros de sistema para habilitación/URL/canal. |
| website_livechat_external/views/res_config_settings_views.xml | Agrega sección de configuración “LiveChat Externo” en Website. |
| website_livechat_external/views/website_templates.xml | Hereda website.layout para inyectar iframe + JS en el website. |
| website_livechat_external/static/src/js/external_livechat_service.js | Servicio backend que crea el iframe y expone openChat(). |
| website_livechat_external/static/src/js/external_livechat_systray.js | Componente systray para disparar openChat(). |
| website_livechat_external/static/src/xml/external_livechat_systray.xml | Template OWL del botón de systray. |
| id="external_livechat_frame" | ||
| name="External LiveChat Frame" | ||
| inherit_id="website.layout" | ||
| active="False" |
There was a problem hiding this comment.
El template hereda website.layout pero está cargado con active="False". Así la vista queda inactiva en BD y no se va a inyectar el iframe aunque el parámetro website_livechat_external.enabled esté en True. Si la activación se controla por ir.config_parameter, conviene dejar la vista activa y que la condición sea solo el t-if (o implementar lógica explícita que active/desactive la vista).
| active="False" |
| loader_src = f"{provider_url}/im_livechat/loader/{channel_id}" | ||
| embed_src = f"{provider_url}/im_livechat/assets_embed.js" | ||
|
|
||
| html = f"""<!DOCTYPE html> | ||
| <html> | ||
| <head> | ||
| <meta charset="utf-8"/> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1"/> | ||
| <style> | ||
| html, body {{ | ||
| margin: 0; | ||
| padding: 0; | ||
| background: transparent; | ||
| overflow: hidden; | ||
| }} | ||
| </style> | ||
| </head> | ||
| <body> | ||
| <!-- | ||
| Esta página corre en su propio contexto JS (sin los globals de Odoo 18), | ||
| por lo que los scripts de Odoo 19 no colisionan con los de Odoo 18. | ||
| --> | ||
| <script defer="defer" type="text/javascript" src="{loader_src}"></script> | ||
| <script defer="defer" type="text/javascript" src="{embed_src}"></script> | ||
| <script type="text/javascript"> |
There was a problem hiding this comment.
/frame sirve un HTML de mismo origen que ejecuta JS remoto ({provider_url}/im_livechat/...). Aunque el script venga de otro dominio, se ejecuta con el origen de esta instancia y por lo tanto tiene acceso total a cookies/sesión y endpoints (especialmente grave en /backend_frame con sesión del usuario). Si el proveedor no es 100% confiable, esto es una escalada de confianza importante. Alternativas: aislar en otro origen/subdominio (y comunicarte por postMessage), o al menos restringir/whitelistear dominios permitidos (y documentar explícitamente el riesgo) y evitar habilitarlo en backend si no es necesario.
| // Escucha mensajes del parent para abrir/cerrar el livechat. | ||
| // El botón vive dentro de un shadow DOM (.o-livechat-root) y no es | ||
| // accesible con querySelector desde el parent frame. | ||
| window.addEventListener("message", function (ev) {{ | ||
| if (!ev.data || ev.data.type !== "toggle_livechat") return; | ||
| var root = document.querySelector(".o-livechat-root"); | ||
| if (!root || !root.shadowRoot) return; | ||
| var btn = root.shadowRoot.querySelector(".o-livechat-LivechatButton"); | ||
| if (btn) btn.click(); | ||
| }}); |
There was a problem hiding this comment.
En el listener window.addEventListener("message", ...) del HTML embebido no se valida ev.origin ni ev.source. Aunque X-Frame-Options: SAMEORIGIN reduce el riesgo de embedding cross-origin, sigue siendo recomendable validar que el mensaje proviene del window.parent y del origen esperado antes de ejecutar btn.click() (defensa en profundidad).
| document.addEventListener('mousemove', function (e) { | ||
| if (!iframeDoc) { return; } | ||
| frame.style.pointerEvents = isOverLivechat(e.clientX, e.clientY) | ||
| ? 'auto' | ||
| : 'none'; | ||
| }); |
There was a problem hiding this comment.
Se está ejecutando elementFromPoint() en cada mousemove del documento. En páginas con mucho movimiento de mouse esto puede generar overhead notable (y además se duplica lógica similar en backend). Conviene throttle/debounce (p. ej. con requestAnimationFrame o un timer) para limitar la frecuencia de cómputo y cambios de pointer-events.
| document.addEventListener("mousemove", (e) => { | ||
| if (!iframeDoc) { | ||
| return; | ||
| } | ||
| frame.style.pointerEvents = isOverLivechat(e.clientX, e.clientY) | ||
| ? "auto" | ||
| : "none"; | ||
| }); |
There was a problem hiding this comment.
Idem al website: document.addEventListener("mousemove", ...) + elementFromPoint() en cada evento puede impactar performance del backend (web client) y correr todo el tiempo aunque el chat no se use. Sugiero throttlear el handler (p. ej. requestAnimationFrame) y, si es posible, activarlo solo cuando el iframe ya cargó y/o cuando el chat esté disponible/abierto.
| setup() { | ||
| this.livechat = useState(useService("external_livechat_backend")); | ||
| } |
There was a problem hiding this comment.
En el resto del repo los componentes suelen consumir servicios con useService(...) directamente (p. ej. mail_ux/static/src/core/common/composer.js:13-14) sin envolverlos en useState. Aquí useState(useService(...)) puede ser innecesario y dificulta entender si la reactividad depende del servicio o del wrapper. Considerá usar this.livechat = useService("external_livechat_backend") y, si hace falta reactividad, exponer un estado reactivo desde el propio servicio.
| @http.route( | ||
| "/website_livechat_external/frame", | ||
| type="http", | ||
| auth="public", | ||
| website=True, | ||
| sitemap=False, | ||
| ) | ||
| def livechat_frame(self): | ||
| """ | ||
| Devuelve una página HTML mínima y aislada que carga los scripts del | ||
| livechat del proveedor externo (Odoo 19). Al servirse desde el mismo | ||
| origen de Odoo 18, el iframe que la contiene puede manipular su DOM | ||
| para habilitar pointer-events sólo sobre el widget del chat. | ||
| """ | ||
| ICP = request.env["ir.config_parameter"].sudo() | ||
|
|
||
| enabled = ICP.get_param("website_livechat_external.enabled", "False") | ||
| if enabled not in ("True", "1", "true"): | ||
| return request.not_found() | ||
|
|
||
| provider_url = ICP.get_param("website_livechat_external.provider_url", "").rstrip("/") | ||
| channel_id_raw = ICP.get_param("website_livechat_external.channel_id", "0") | ||
|
|
||
| # Validaciones de seguridad básicas | ||
| if not provider_url or not _VALID_ORIGIN_RE.match(provider_url): | ||
| return request.make_response("URL del proveedor inválida.", status=400) | ||
|
|
||
| try: | ||
| channel_id = int(channel_id_raw) | ||
| if channel_id <= 0: | ||
| raise ValueError | ||
| except (ValueError, TypeError): | ||
| return request.make_response("ID de canal inválido.", status=400) |
There was a problem hiding this comment.
Este módulo introduce rutas nuevas con validaciones (enabled/provider_url/channel_id) y distintos status codes (404/400/200). En el repo ya hay tests Odoo (base_bg/tests/test_bg_job.py), pero acá no se agregan. Sería bueno sumar al menos tests de controlador para: (1) /frame devuelve 404 si está deshabilitado, (2) 400 si la URL o el channel_id son inválidos, (3) 200 y contiene los <script src=...> esperados cuando la config es válida.

No description provided.