Conflicto con la solución RGPD de Quantcast "choice" y ember.js

Hola,

Este es un error bastante específico, pero su impacto podría ir mucho más allá de este caso concreto, por lo que tengo una pregunta al respecto.

(También, me disculpo por mi inglés; soy francés, así que no soy un hablante nativo en absoluto…)

Pero primero, permíteme explicar el contexto.
Llevo un tiempo usando Discourse para un foro en francés sobre Raspberry Pi (forum.raspberry-pi.fr). Este foro utiliza gestión de publicidad (Themoneytizer). Como probablemente sepas, Europa nos obliga a implementar el RGPD para proteger la privacidad de los usuarios. El actor principal (al menos en Francia) para el consentimiento del RGPD es Quantcast y su solución “Choice”.

Así que he estado usando la solución de Quantcast durante bastante tiempo sin problemas, hasta que recientemente noté que el botón “Aceptar todo” ya no funciona correctamente. Al hacer clic, no ocurre nada, y al revisar la consola de desarrollo obtengo este error: “Uncaught TypeError: can’t define property ‘status’: Function is not extensible”.

Lo que sucede (al menos según mi mejor comprensión):
Créanme cuando digo que me llevó muchísimo tiempo encontrar la fuente del problema. Aparentemente, Ember.js (con el cual no estoy muy familiarizado) extiende algunos objetos nativos de JavaScript, como Array, String y Function. Y por alguna razón, parece también impedir de alguna manera la extensión de esos objetos (aún no he comprendido completamente esta parte).

Por su parte, la solución de Quantcast intenta, probablemente en la función FunctionAcceptAll (lo que explica que el error solo ocurra al hacer clic en los botones “Aceptar todo” y “Rechazar todo”), extender un objeto, presumiblemente un array, cuyo comportamiento normal ha sido modificado por Ember.js.

Después de mucha investigación para entender este error, también descubrí que es posible modificar el comportamiento de Ember.js para que no extienda los prototipos de JavaScript, como se explica en esta página: https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/.

He realizado algunas pruebas y el error desaparece si agrego la línea window.EmberENV.EXTEND_PROTOTYPES = {String: true, Array: false}; en el archivo _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, justo después de la línea window.EmberENV.FORCE_JQUERY = true;.

Para aquellos que quieran probarlo, pueden visitar la página /tst/index.html en el foro (es posible que necesiten una IP europea para que el script se inicie; no tengo idea de por qué).
Ahora creo que les he proporcionado toda la información que puedo ofrecer.

Ahora, aquí está mi pregunta.
Aunque este es un error bastante específico, el RGPD está cada vez más presente en Europa y no será más fácil de cumplir.
Quantcast ocupa una posición casi monopolística, al menos para aquellos actores que no pueden permitirse pagar cientos de dólares para implementar el RGPD. Este error impide cualquier uso de Quantcast y, por lo tanto, de la publicidad en Discourse en Europa, lo cual me parece un gran problema.
Además, aunque solo encontré el error usando Quantcast, este tipo de problemas podría ocurrir con muchos scripts de terceros que debemos integrar para anuncios u otros fines, sobre los cuales no tenemos ningún control y que dependen del comportamiento “normal” de JavaScript para los objetos Array, String y Function.

No conozco lo suficiente el código de Discourse, así que les pregunto: ¿las propiedades que Ember.js agrega a los objetos Array, String y Function (ver el enlace anterior) son utilizadas por Discourse o no? Si no lo son, ¿quizás deberíamos considerar deshabilitar las extensiones de prototipos de Ember.js para evitar efectos secundarios como este?

Espero que alguien pueda darme información al respecto.

Gracias

Esto no es compatible en este momento; dependemos de las extensiones. Quizás en una versión futura no será necesario. @eviltrout puede proporcionar más contexto.

No estoy seguro de qué hacer en este caso, pero creo que deberíamos tener alguna solución alternativa. Me sorprende que esto rompa el RGPD; ¿quizás deberíamos abrir un ticket con Quantcast para discutirlo y enlazarlo aquí?

¿Cómo estás agregando los scripts necesarios para Quantcast? ¿Los agregas mediante una etiqueta script con un src (externo) como este:

<script src="foo"></script>

¿O los agregas en línea como este?

<script>
  alert("¡Hola Mundo!");
</script>

Hola,
gracias por vuestras respuestas.

@sam Abrí un ticket con Quantcast el mismo día en que creé este tema y me respondieron hoy. Al parecer, lo están revisando en este momento; ojalá encuentren una solución. Creo que para ellos es una corrección bastante sencilla, ya que el botón “Aceptar selección” funciona correctamente. Esperemos que consideren que Discourse y Ember.js son soluciones lo suficientemente utilizadas como para merecer una corrección dedicada.

@Johani En realidad, no agrego los scripts de Quantcast yo mismo; TheMoneyTizer proporciona un script que se encarga de ello por mí (además de buscar eventos tcf2, etc.). Si quieres, puedes ver este script aquí.

Este script parece agregar un nuevo elemento antes del primer script de la página. No estoy seguro, pero tengo la firme impresión de haber visto exactamente este script en algunos ejemplos de la documentación de Quantcast, así que presumo que es un script bastante utilizado para integrar Quantcast Choice.

Gracias de nuevo por vuestro tiempo.

No veo que podamos eliminar los prototipos de arrays de forma segura en un futuro cercano. Han sido bastante inofensivos a lo largo de los años y añaden mucha comodidad.

¿Quizás podrías contactar con Quantcast para conseguir una versión de los scripts que no dependa de los prototipos de arrays?

¡Hola a todos,
tengo algunas nuevas noticias para ustedes y he encontrado una solución a este problema.

Así que, hoy he estado investigando de nuevo este error y creo que he encontrado más información.

Al examinar el archivo cmp2ui-fr.js de Quantcast, pude localizar dónde ocurre el error; está en esta función (solo tenemos la versión minificada):

function(t){for(var n in t){t[n].status=e;}}

Como pueden ver, esta función utiliza un bucle for..in, y la variable t es un array. Ya habíamos explicado esto anteriormente: Ember.js extiende el Array nativo de JavaScript. Resulta que una de las modificaciones es la adición de una entrada _super.

La entrada _super apunta a una función ROOT(), que parece referirse a _utils.ROOT en Ember.js (quizás esto les suene a algunos de ustedes, pero a mí no ^^). Esta función ROOT() no es extensible.

Aparentemente, esta propiedad _super se considera enumerable, lo que significa que al usar un bucle for..in en un array, la entrada _super se trata como una entrada “normal” (a diferencia, por ejemplo, de values, bind, valueOf, básicamente todas las funciones de un objeto Array).

Creo que esto es un error de Ember.js y no un comportamiento deseable.

Así que he logrado hacer que el error sea muy reproducible. Para ello, simplemente cree un array de objetos, así:

var objs = [{'key':'val'},{'key':'val'}];

Luego, cree una función estricta que itere sobre un array y establezca una nueva propiedad para cada entrada:

var tst_func = function (objs){'use strict';for(var i in objs){objs[i].newproperty = true }};

Finalmente, llame a esta función con el array de objetos:

tst_func(objs);

Debería obtener un error: Uncaught TypeError: can't define property "newproperty": Function is not extensible.

Al analizar esto, me hace pensar que el error probablemente ya está en circulación y no es específicamente de Quantcast. Básicamente, cualquiera que intente usar for..in se expone a comportamientos inconsistentes o errores críticos.

Para mí, esto no depende realmente de Discourse ni de Quantcast, sino claramente de Ember.js. Sin embargo, eso no cambia el hecho de que debemos encontrar una solución mientras no se corrija en Ember.js ^^.

La buena noticia es que creo que he encontrado una manera de solucionarlo.

Una forma de hacerlo es agregar una línea if (!objs.hasOwnProperty(i)) {continue}; en cada bucle for..in (probablemente también en forEach, for...of, etc.). Esto no modifica el extraño comportamiento de _super en Array, pero evita localmente acceder a él. Obviamente, esto significa que no funciona para cualquier script externo que no tengamos bajo control, como mi caso particular de uso de Quantcast.

Otra forma, que creo que es el camino a seguir, es modificar el prototipo de Array en JavaScript (así que básicamente sobrescribimos la sobrescritura de Ember.js ^^) para que _super no sea enumerable. Para ello, debemos ejecutar esta línea de JS DESPUÉS de que Ember.js sea llamado y evaluado:

// Hacer que _super no sea enumerable para evitar errores entre Ember.js y for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

La segunda opción permite el uso directo de _super, como debería ser, y evita que aparezca en los bucles. Aunque no puedo garantizar que este comportamiento extraño no sea utilizado por alguna función interna de Ember.js o algún plugin externo.

Al examinar el archivo _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, vi algunas líneas específicas de Discourse, como:

var ALIASES = {
    "ember-addons/ember-computed-decorators":
      "discourse-common/utils/decorators",
    "discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
    "preload-store": "discourse/lib/preload-store",
    "fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures",
  };
  var ALIAS_PREPEND = {
    fixtures: "discourse/tests/",
    helpers: "discourse/tests/",
  };

¿Quizás podríamos agregar nuestra línea Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); aquí?

Por ahora, he corregido el error agregando las líneas:
// Hacer que _super no sea enumerable para evitar errores entre Ember.js y for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});

Antes de llamar al script choice.js de Quantcast.

Creo que alguien podría corregir el error localmente creando un archivo ‘fix_ember.js’ con estas dos líneas, sirviéndolo estáticamente desde el proxy inverso (por ejemplo, Nginx), y agregando una línea <script src="/fix_emmber.js"></script> en el pie del tema mediante personalización. Escribir el script directamente en lugar de crear un enlace no funciona debido a la extracción de scripts de Discourse (ver Custom javascript in <head> disappear).

Espero que este tema ayude a otros. Abriré un ticket en Ember.js mañana para ver si esto es un error o un comportamiento muy extraño pero deliberado.

Les mantendré informados para ver si debemos incluir una corrección en Discourse.

PS: ¡Muchas gracias a Angus Croll de Extending JavaScript Natives – JavaScript, JavaScript…, ¡su post me ayudó mucho!"}

Muchas gracias, @OsaAjani, por estar al tanto de esto. Tenemos exactamente el mismo problema con Quantcast Choice en nuestro foro de Discourse y nos hemos quedado atascados en su versión 12 (que fue la última versión que no generaba el error).

La versión más reciente es la 23 y nos gustaría mucho poder utilizar la última.

Quedamos a la espera de saber cómo resolver esto lo antes posible.

Hola @Terrapop, me alegra que este tema pueda ayudarte. Por ahora, no he recibido absolutamente ningún comentario sobre el problema que abrí en el repositorio de ember.js ([Bug] Property "_super" is enumerable on Array, creating conflicts with external libs (ex : Quantcast Choice RGPD) · Issue #19289 · emberjs/ember.js · GitHub). Tampoco he recibido comentarios de Quantcast.

Sin embargo, la buena noticia es que he estado utilizando la solución descrita en mi mensaje anterior, creando un archivo fix_ember.js con el siguiente código:

Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

Y agregando una línea:

<script defer src="/fix_emmber.js"></script>

En el pie de página del tema, utilizando la personalización desde el panel de administración.

Esta solución resuelve el problema y, por ahora, no he observado ningún efecto secundario. Por lo tanto, creo que puedes utilizar esta solución con confianza y migrar a la versión 23 de Quantcast.

Añadimos la línea antes de cargar choice.js. Esto también parece funcionar bien.

Para la otra solución: No tenemos un proxy inverso delante de nuestro Discourse, así que no estamos seguros de cómo crear el archivo fix_ember.js de la manera adecuada. ¿Es eso incluso posible dentro de Docker? ¿Alguna sugerencia?

¡Bueno, si puedes cargarlo antes que choice.js, ¡aún mejor!

Sí, funciona. No podemos agradecértelo lo suficiente; nos arrancamos los pelos durante días con esto, ya que no pudimos encontrar el problema. Tuvimos 20 correos electrónicos de ida y vuelta con Quantcast, y ellos tampoco pudieron localizar el problema.

Bueno, si tienes un buen canal de comunicación con Quantcast, tal vez podrías dirigirlos a esta publicación para que puedan solucionarlo. Yo lo he intentado, pero como no soy realmente un cliente suyo, el soporte no me ha dado muchas respuestas.

Lo haré. Buen punto.