Конфликт решения Quantcast RGPD "choice" и ember.js

Здравствуйте,

Это довольно специфичная ошибка, но её последствия могут выйти далеко за рамки данного конкретного случая, поэтому у меня к вам есть вопрос.

(Также прошу прощения за мой английский: я француз, поэтому не являюсь носителем языка…)

Но сначала позвольте объяснить контекст.
Я уже давно использую Discourse для французского форума о Raspberry Pi (forum.raspberry-pi.fr). На этом форуме используется управление рекламой (themoneytizer). Как вы, вероятно, знаете, в Европе мы обязаны внедрять RGPD для защиты конфиденциальности пользователей. Основным игроком (по крайней мере во Франции) в области получения согласия в соответствии с RGPD является Quantcast и их решение «Choice».

Итак, я довольно долго использовал решение Quantcast без каких-либо проблем, пока недавно не заметил, что кнопка «Принять всё» перестала работать корректно. При клике ничего не происходит, а в консоли разработчика я получаю следующую ошибку: “Uncaught TypeError: can’t define property “status”: Function is not extensible”.

Что происходит (насколько я могу судить):
Поверьте мне, когда я говорю, что мне потребовалось очень-очень-очень долгое время, чтобы найти источник проблемы. Похоже, что ember.js (с которым я мало знаком) расширяет некоторые нативные объекты JavaScript, такие как Array, String и Function. И по какой-то причине, кажется, также каким-то образом предотвращает расширение этих объектов (я пока не до конца понял эту часть).

С своей стороны решение Quantcast пытается, вероятно, в функции FunctionAcceptAll (объясняя, что ошибка возникает только при нажатии на кнопки «Принять всё» и «Отклонить всё»), расширить объект, предположительно массив, нормальное поведение которого было изменено ember.js.

После множества исследований для понимания этой ошибки я также обнаружил, что можно изменить поведение Ember.js так, чтобы оно не расширяло прототипы JavaScript, как объясняется на этой странице: https://guides.emberjs.com/release/configuring-ember/disabling-prototype-extensions/.

Я провёл несколько тестов, и ошибка исчезает, если я добавлю строку window.EmberENV.EXTEND_PROTOTYPES = {String: true, Array: false}; в файл _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js после строки window.EmberENV.FORCE_JQUERY = true;.

Для тех из вас, кто захочет попробовать, вы можете посмотреть страницу /tst/index.html на форуме (возможно, вам понадобится европейский IP-адрес, чтобы скрипт запустился, я не знаю точно).
Теперь, думаю, у вас есть вся информация, которую я могу предоставить.

Итак, теперь мой вопрос.
Хотя это довольно специфичная ошибка, RGPD становится всё более распространённым в Европе, и ситуация не станет проще.
Quantcast занимает довольно монопольное положение, по крайней мере для тех, кто не может позволить себе платить сотни долларов за внедрение RGPD. Эта ошибка делает невозможным использование Quantcast, а следовательно, и размещение рекламы на Discourse в Европе, что, на мой взгляд, является серьёзной проблемой.
Кроме того, даже если я обнаружил эту ошибку только с Quantcast, такой тип ошибки может возникнуть для множества сторонних скриптов, которые мы вынуждены внедрять для рекламы или других целей, над которыми у нас нет никакого контроля, и которые полагаются на «нормальное» поведение JavaScript для объектов Array, String и Function.

Я недостаточно хорошо знаю код Discourse, поэтому спрашиваю вас: используются ли свойства, добавленные ember.js к объектам Array, String и Function (см. ссылку выше), в Discourse или нет? Если нет, возможно, стоит рассмотреть возможность отключения расширения прототипов в ember.js, чтобы предотвратить побочные эффекты, подобные этому?

Надеюсь, кто-то сможет дать информацию по этому вопросу.
Спасибо.

В данный момент это не поддерживается, так как мы полагаемся на расширения. Возможно, в будущей версии это изменится. @eviltrout может предоставить больше информации.

Я не уверен, что делать в этой ситуации, но думаю, нам стоит найти какое-то обходное решение. Меня удивляет, что это нарушает RGPD. Возможно, стоит открыть тикет в Quantcast для обсуждения и добавить ссылку сюда?

Как вы добавляете скрипты, необходимые для Quantcast? Добавляете ли вы их через тег script с атрибутом src (внешний), например:

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

Или добавляете их непосредственно в код, как в этом примере?

<script>
  alert("Hello World!");
</script>

Привет,
спасибо за ваши ответы.

@sam Я открыл тикет в Quantcast в день создания этой темы, и они ответили мне сегодня. Похоже, они сейчас изучают проблему, и, надеюсь, найдут решение. Мне кажется, что для них это довольно простое исправление, так как кнопка «Accept selection» работает корректно. Будем надеяться, что они посчитают Discourse и Ember.js достаточно распространёнными решениями, чтобы выделить отдельное исправление.

@Johani На самом деле я сам не добавляю скрипты Quantcast; сервис TheMoneyTizer предоставляет скрипт, который делает это за меня (а также отслеживает события TCF2 и т. д.). Если хотите, можете посмотреть этот скрипт здесь.

Этот скрипт, похоже, добавляет новый элемент перед первым скриптом на странице. Я не уверен на сто процентов, но мне очень сильно кажется, что я видел практически точно такой же скрипт в примерах документации Quantcast, поэтому предполагаю, что это довольно распространённый скрипт для интеграции Quantcast Choice.

Ещё раз спасибо за ваше время.

Я не думаю, что мы сможем безопасно отказаться от прототипов массивов в ближайшее время. За эти годы они оказались вполне безобидными и приносят большую пользу.

Может быть, стоит связаться с Quantcast, чтобы получить версию скриптов, которая не зависит от прототипов массивов?

Всем привет,
У меня есть для вас новая информация, и я нашел решение этой проблемы.

Сегодня я снова копался в этом баге и, думаю, нашел больше деталей.

Просматривая файл cmp2ui-fr.js от Quantcast, я смог определить место, где возникает ошибка — это функция (к сожалению, у нас есть только минифицированная версия):

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

Как видите, эта функция использует for..in, а переменная t является массивом. Мы уже упоминали ранее, что ember.js расширяет нативный массив JavaScript. Оказывается, одно из изменений — это добавление свойства _super.

Свойство _super указывает на функцию ROOT(), которая, похоже, ссылается на _utils.ROOT в ember.js (возможно, это кому-то из вас знакомо, но не мне ^^). Эта функция ROOT() не является расширяемой.

Похоже, что свойство _super считается enumerable (перебираемым), что означает: при использовании цикла for..in по массиву запись _super воспринимается как «обычная» запись (в отличие, например, от values, bind, valueOf — в общем, всех функций объекта массива).

Я считаю, что это баг в ember.js, а не желаемое поведение.

Мне удалось сделать баг легко воспроизводимым. Для этого просто создайте массив объектов, например:

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

Затем создайте функцию в строгом режиме, которая перебирает массив и добавляет новое свойство для каждого элемента:

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

Наконец, вызовите эту функцию для массива объектов:

tst_func(objs);

Вы получите ошибку: Uncaught TypeError: can't define property "newproperty": Function is not extensible.

Это заставляет меня думать, что баг уже существует в дикой природе и не специфичен для Quantcast. В принципе, любой, кто использует for..in, подвергает себя риску некорректного поведения или критических ошибок.

На мой взгляд, проблема не в Discourse или Quantcast, а явно в ember.js. Тем не менее, пока это не исправлено в ember.js, нам нужно найти решение ^^.

Хорошая новость в том, что я, кажется, нашел способ исправить это.

Один из способов — добавить строку if (!objs.hasOwnProperty(i)) {continue}; в каждый цикл for..in (вероятно, также в forEach, for...of и т.д.). Это не изменит странное поведение _super в массиве, но локально предотвратит доступ к нему. Очевидно, что это не сработает для внешних скриптов, которыми мы не управляем, как в моём конкретном случае с Quantcast.

Другой способ, который я считаю правильным путём, — модифицировать прототип массива JavaScript (то есть фактически переопределить собственное переопределение ember.js ^^), чтобы сделать _super неперечисляемым. Для этого нужно выполнить следующую строку JS ПОСЛЕ того, как ember.js будет вызван и выполнен:

//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});

Второй способ сохраняет возможность прямого использования _super, как и задумано, но предотвращает его появление в циклах. Однако я не могу гарантировать, что это странное поведение не используется какой-либо внутренней функцией ember.js или внешним плагином.

Просматривая файл _ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js, я заметил несколько строк, специфичных для Discourse, например:

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/",
  };

Может быть, мы могли бы добавить нашу строку Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); сюда?

На данный момент я исправил баг, добавив строки:
//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});

перед вызовом скрипта choice.js от Quantcast.

Я думаю, что кто-то может исправить баг локально, создав файл fix_ember.js с этими двумя строками, разместив его статически через обратный прокси (например, Nginx) и добавив строку <script src="/fix_emmber.js"></script> в подвал темы через кастомизацию. Написание скрипта напрямую вместо создания ссылки не работает из-за извлечения скриптов в Discourse (см. Custom javascript in <head> disappear).

Надеюсь, эта тема поможет другим. Завтра я открою тикет в ember.js, чтобы выяснить, является ли это багом или очень странное, но намеренное поведение.

Я буду держать вас в курсе, нужно ли нам включить исправление в Discourse.

PS: Большое спасибо Энгу Кроллу с Extending JavaScript Natives – JavaScript, JavaScript…, его пост очень помог мне!

Большое спасибо @OsaAjani за то, что вы обратили внимание на эту проблему. У нас возникает точно такая же ошибка с Quantcast Choice на нашем форуме Discourse, и мы вынуждены использовать версию 12 (это последняя версия, которая не вызывает ошибку).

Последняя версия — 23, и мы очень хотели бы использовать именно её.

С нетерпением ждём ваших рекомендаций по решению этой проблемы как можно скорее.

Привет, @Terrapop, я рад, если эта тема может вам помочь. На данный момент я абсолютно не получил никакой обратной связи по проблеме, которую я открыл в репозитории ember.js ([Bug] Property "_super" is enumerable on Array, creating conflicts with external libs (ex : Quantcast Choice RGPD) · Issue #19289 · emberjs/ember.js · GitHub). И никакой обратной связи также от Quantcast.

Хорошая новость, однако, в том, что я использую исправление, описанное в моём предыдущем сообщении: создаю файл fix_ember.js со следующим кодом:

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

И добавляю строку:

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

В подвал темы, используя настройки из панели администратора.

Это решение устраняет проблему, и пока я не заметил никаких побочных эффектов. Поэтому, я думаю, вы можете уверенно использовать это исправление и перейти на версию 23 Quantcast.

Мы добавили строку перед загрузкой choice.js. Это тоже, похоже, работает нормально.

Что касается другого решения: у нас нет обратного прокси-сервера перед нашим Discourse, поэтому мы не уверены, как правильно создать файл fix_ember.js. Возможно ли это вообще в рамках Docker? Есть какие-то предложения?

Что ж, если вы сможете загрузить его до choice.js, это будет ещё лучше!

Да, работает. Мы не можем вас достаточно поблагодарить: мы ломали голову над этим несколько дней, так как не могли найти проблему. Мы переписывались с Quantcast более 20 раз по электронной почте, но они тоже не смогли выявить проблему.

Что ж, если у вас есть хороший канал связи с Quantcast, возможно, вы могли бы направить их к этому посту, чтобы они могли это исправить. У меня тоже есть такой канал, но так как я не являюсь их клиентом, поддержка не дала мне особых результатов.

Сделаю. Хорошая мысль.