Conflito com a solução RGPD da Quantcast "choice" e ember.js

Olá,

este é um bug bastante específico, mas seu impacto pode ir muito além deste caso em particular, então tenho uma pergunta sobre isso.

(Também peço desculpas pelo meu inglês; sou francês, então não sou falante nativo de forma alguma…)

Mas, antes, deixe-me explicar o contexto.

Uso o Discourse há algum tempo para um fórum em francês sobre o Raspberry Pi (forum.raspberry-pi.fr). Este fórum utiliza gerenciamento de publicidade (themoneytizer). Como você provavelmente sabe, a Europa nos obriga a implementar o RGPD para proteção da privacidade dos usuários. O principal ator (pelo menos na França) para consentimento do RGPD é a Quantcast e sua solução “Choice”.

Então, tenho usado a solução da Quantcast há bastante tempo, sem problemas, até recentemente, quando percebi que o botão “Aceitar tudo” não está funcionando corretamente mais. Ao clicar, nada acontece e, ao verificar no console de desenvolvedor, obtenho este erro: “Uncaught TypeError: can’t define property “status”: Function is not extensible”.

O que acontece (pelo menos, até onde entendo):
Acredite em mim quando digo que me levou muito, muito tempo para encontrar a origem do problema. Aparentemente, o Ember.js (com o qual não tenho muita familiaridade) estende alguns objetos nativos do JavaScript, como Array, String e Function. E, por algum motivo, também parece impedir de alguma forma a extensão desses objetos (ainda não entendi completamente essa parte).

A solução da Quantcast, por sua vez, tenta — provavelmente na função FunctionAcceptAll (o que explica o bug ocorrer apenas ao clicar no botão “Aceitar tudo” e no botão “Rejeitar tudo”) — estender um objeto, presumo que um array, cujo comportamento normal foi modificado pelo Ember.js.

Após muita pesquisa para entender esse bug, também descobri que é possível modificar o comportamento do Ember.js para não estender os protótipos do JavaScript, conforme explicado nesta página: https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/.

Fiz alguns testes e o bug desaparece se eu adicionar a linha window.EmberENV.EXTEND_PROTOTYPES = {String: true, Array: false}; no arquivo _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, logo após a linha window.EmberENV.FORCE_JQUERY = true;.

Para aqueles de vocês que quiserem testar, podem verificar a página /tst/index.html no fórum (talvez seja necessário um IP europeu para que o script inicie; não tenho certeza).
Agora, acho que tenho todas as informações que posso fornecer.

Agora, aqui está minha pergunta:
Embora este seja um bug bastante específico, o RGPD está cada vez mais presente na Europa atualmente, e a situação não vai ficar mais fácil.

A Quantcast ocupa uma posição bastante monopolística, pelo menos para atores que não podem pagar centenas de dólares para implementar o RGPD. Este bug impede qualquer uso da Quantcast e, consequentemente, de publicidade no Discourse na Europa, o que, para mim, parece ser um grande problema.

Além disso, mesmo que eu tenha encontrado o bug apenas usando a Quantcast, esse tipo de problema pode realmente acontecer com muitos scripts de terceiros que precisamos incorporar para anúncios ou outros fins, sobre os quais não temos nenhum controle e que dependem do comportamento “normal” do JavaScript para os objetos Array, String e Function.

Não conheço o código do Discourse o suficiente, então estou perguntando a vocês: as propriedades adicionadas pelo Ember.js aos objetos Array, String e Function (veja o link acima) são usadas pelo Discourse ou não? Se não, talvez devêssemos considerar desativar as extensões de protótipo do Ember.js, a fim de evitar efeitos colaterais como este?

Espero que alguém possa me dar informações sobre isso.

Obrigado.

Isso não é suportado no momento; contamos com as extensões. Talvez em uma versão futura isso não seja mais necessário. @eviltrout pode fornecer mais contexto.

Não tenho certeza do que fazer aqui, mas acho que devemos ter alguma solução alternativa. Estou surpreso que isso quebre a LGPD; talvez seja possível abrir um ticket com a Quantcast para discutir e vinculá-lo aqui?

Como você está adicionando os scripts necessários para o Quantcast? Você os está adicionando por meio de uma tag script com um src (externo), assim:

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

Ou você os está adicionando inline, assim?

<script>
  alert("Olá, Mundo!");
</script>

Olá,
obrigado pelas respostas.

@sam, abri um chamado com a Quantcast no mesmo dia em que criei este tópico e eles responderam hoje. Aparentemente, estão analisando o problema agora; espero que encontrem uma solução. Acredito que seja um ajuste bastante simples para eles, já que o botão “aceitar seleção” está funcionando corretamente. Vamos torcer para que considerem o Discourse e o Ember.js como soluções usadas o suficiente para merecerem um ajuste dedicado.

@Johani, na verdade, não adiciono os scripts da Quantcast pessoalmente; a TheMoneyTizer fornece um script que faz essa adição por mim (além de monitorar eventos tcf2, etc.). Se quiser, pode conferir esse script aqui.

Parece que esse script adiciona um novo elemento antes do primeiro script da página. Não tenho certeza absoluta, mas tenho fortes indícios de que já vi exatamente esse script em alguns exemplos de documentação da Quantcast; portanto, presumo que seja um script bastante utilizado para integrar a Quantcast Choice.

Obrigado novamente pelo seu tempo.

Não vejo como remover com segurança os protótipos de array em breve. Eles têm sido bastante inofensivos ao longo dos anos e agregam muita conveniência.

Talvez você possa entrar em contato com a Quantcast para obter uma versão dos scripts que não dependam dos protótipos de array?

Ei pessoal,
Tenho algumas novas informações para vocês e encontrei uma solução para esse problema.

Então, hoje estive investigando novamente esse bug e acho que descobri mais detalhes.

Ao examinar o arquivo cmp2ui-fr.js do Quantcast, consegui identificar onde o bug ocorre, que está nesta função (temos apenas a versão minificada):

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

Como vocês podem ver, essa função usa um for..in, e a variável t é um array. Já explicamos isso anteriormente: o Ember.js estende o Array nativo do JavaScript. Parece que uma das modificações é a adição de uma entrada _super.

A entrada _super aponta para uma função ROOT(), que parece referenciar _utils.ROOT no Ember.js (talvez isso soe familiar para alguns de vocês, mas não para mim ^^). Essa função ROOT() não é extensível.

Aparentemente, essa propriedade _super é considerada enumerable, o que significa que, ao usar um loop for..in em um array, a entrada _super é tratada como uma entrada “normal” (diferente, por exemplo, de values, bind, valueOf, basicamente todas as funções de um objeto Array).

Acho que isso é um bug do Ember.js e não um comportamento desejável.

Consegui tornar o bug altamente reprodutível. Para isso, basta criar um array de objetos, assim:

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

Em seguida, crie uma função usando strict mode, iterando sobre um array e definindo uma nova propriedade para cada entrada:

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

Por fim, basta chamar essa função no array de objetos:

tst_func(objs);

Você deve receber um erro: Uncaught TypeError: can't define property "newproperty": Function is not extensible.

Analisando isso, acredito que o bug provavelmente já existe em produção e não é específico do Quantcast. Basicamente, qualquer pessoa que tente usar for..in se expõe a comportamentos inconsistentes ou bugs críticos.

Na minha opinião, isso não é responsabilidade direta do Discourse ou do Quantcast, mas claramente do Ember.js. No entanto, isso não muda o fato de que precisamos encontrar uma solução enquanto o bug não for corrigido no Ember.js ^^.

A boa notícia é que acho que encontrei uma maneira de corrigir isso.

Uma maneira é adicionar uma linha if (!objs.hasOwnProperty(i)) {continue}; em todos os loops for..in (provavelmente também em forEach, for...of, etc.). Isso não modifica o comportamento estranho do _super do Array, mas impede localmente o acesso a ele. Obviamente, isso significa que não funciona para qualquer script externo que não temos controle, como no meu caso específico do uso do Quantcast.

Outra maneira, que acredito ser o caminho a seguir, é modificar o protótipo do Array do JavaScript (então, basicamente, sobrescrevemos a própria sobrescrita do Ember.js ^^) para tornar _super não enumerável. Para isso, precisamos executar essa linha de JavaScript DEPOIS que o Ember.js for chamado e avaliado:

//Tornar _super não enumerável para evitar bugs entre Ember.js e for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

A segunda opção mantém a possibilidade de uso direto de _super, conforme deveria ser, e impede que ele apareça nos loops. No entanto, não posso garantir que esse comportamento estranho não seja utilizado por nenhuma função interna do Ember.js ou plugin externo.

Examinando o arquivo _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, vi algumas linhas específicas do 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/",
  };

Talvez pudéssemos adicionar nossa linha Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); aqui?

No momento, corrigi o bug adicionando as linhas:
//Tornar _super não enumerável para evitar bugs entre Ember.js e for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});

Antes de chamar o script choice.js do Quantcast.

Acho que alguém pode corrigir o bug localmente criando um arquivo ‘fix_ember.js’ com essas duas linhas, servindo-o estáticamente pelo proxy reverso (por exemplo, Nginx), e adicionando uma linha <script src="/fix_emmber.js"></script> no rodapé do tema usando personalização. Escrever o script diretamente em vez de criar um link não funciona por causa da extração de scripts do Discourse (veja Custom javascript in <head> disappear).

Espero que este tópico ajude outros. Vou abrir um ticket no Ember.js amanhã para verificar se isso é um bug ou um comportamento muito estranho, mas intencional.

Vou mantê-los informados para ver se precisamos incluir uma correção no Discourse.

PS: Muitos agradecimentos a Angus Croll de Extending JavaScript Natives – JavaScript, JavaScript…, seu post me ajudou muito!

Muito obrigado @OsaAjani por estar em cima disso. Temos exatamente o mesmo problema com a escolha do Quantcast em nosso fórum Discourse e estamos presos na versão 12 (que foi a última versão que não gerava o erro).

A versão mais recente é a 23 e gostaríamos muito de usar a mais atual.

Aguardamos saber como resolver isso o mais breve possível.

Olá @Terrapop, fico feliz se este tópico puder ajudá-lo. Por enquanto, não recebi absolutamente nenhum feedback sobre o problema que abri no repositório do ember.js ([Bug] Property "_super" is enumerable on Array, creating conflicts with external libs (ex : Quantcast Choice RGPD) · Issue #19289 · emberjs/ember.js · GitHub). Também não houve nenhum retorno da Quantcast.

A boa notícia, no entanto, é que tenho usado a correção descrita na minha mensagem anterior, criando um arquivo fix_ember.js com o código:

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

E adicionando uma linha:

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

No rodapé do tema, usando a personalização do painel de administração.

Essa solução resolve o problema e, até agora, não observei nenhum efeito colateral. Portanto, acho que você pode usar essa correção com confiança e migrar para a versão 23 da Quantcast.

Adicionamos a linha antes de carregar o choice.js. Isso também parece funcionar bem.

Quanto à outra solução: não temos um proxy reverso na frente do nosso Discourse, então não temos certeza de como criar o arquivo fix_ember.js da maneira correta. Isso é mesmo possível dentro do Docker? Alguma sugestão?

Bom, se você puder carregá-lo antes do choice.js, será ainda melhor!

Sim, funciona. Não podemos agradecer o suficiente; ficamos de cabeça quebrada por dias com isso, pois não conseguimos encontrar o problema. Trocamos 20 e-mails com a Quantcast, e eles também não conseguiram localizar o problema.

Bem, se você tiver um bom canal de comunicação com a Quantcast, talvez possa encaminhá-los para esta postagem, para que eles possam corrigir o problema. Eu tentei, mas como não sou realmente um cliente deles, o suporte não me deu muitas respostas.

Vou fazer. Bom ponto.