如何在遵守不允许内联脚本的 CSP 的同时为 Google Universal Analytics 动态设置跟踪代码?

How can I dynamically set the tracking code for Google Universal Analytics while adhering to a CSP that disallows inline scripts?

我有一个部署到多个不同 URL 的网站解决方案,每个 URL 都针对不同的客户进行标记。部署过程在 web.config、数据库等中设置各种特定于客户端的配置数据。相同的网站代码用于所有客户端,但不同的配置数据意味着该站点的外观和行为对于每个客户端都不同。

我想为每个客户设置的其中一件事是 Google Universal Analytics 跟踪代码。这是出现在添加到每个页面的脚本块中的代码(下例中的“UA-12345678-1”):

<script>
  (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
  (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
  m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
  })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

  ga('create', 'UA-12345678-1', 'auto');
  ga('send', 'pageview');

</script> 

我在 ViewBag 中有跟踪代码,所以如果我准备将此脚本块直接放在我的 HTML 页面(或我的主布局)中,那么插入跟踪代码将非常容易:

ga('create', '@ViewBag.GoogleTrackingCode', 'auto');

但是,我正在使用 Content Security Policy (CSP) 功能来锁定网站,作为其中的一部分,我已经禁用了内联脚本:所有脚本都必须从 .js 文件加载。

所以,我创建了一个 JavaScript 文件,其中包含上面脚本块的修改版本,我从我的 HTML 页面引用了它:

(function (i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', ga_token, 'auto');
ga('send', 'pageview');

我的计划是在这段脚本运行之前设置 ga_token 变量的值。我以为我会把变量值藏在 HTML 的某处(在 data-* 属性中):

<div id="ga-token-data" data-ga-token="@ViewBag.GoogleAnalyticsToken">

... 并使用一些 JavaScript(当然是在 .js 文件中)获取该值:

$(document).ready(function () {
    ga_token = $("#ga-token-data").data("ga-token");
})

但是,由于执行顺序,这被证明是棘手的。根据 Google 的文档,脚本块应该放在 HTML 页面中结束 <\head> 标记之前,虽然有一些花哨的东西,但它不会尝试在页面加载之前实际执行任何操作,使用跟踪代码的 ga('create', ... 调用实际上会立即执行。因此,ga_token 的值在使用前并未设置。

理论上我可以将设置 ga_token 值的代码移动到 Google 脚本块的正上方,但这样做我会在 [=50= 之前执行该代码] 已加载 - 在这种情况下,我可能无法获取所需的值。 (而且我当然不能使用 jQuery,因为我直到稍后才加载 jQuery 库)。

还有其他方法吗?

在完全加载之前阅读DOM是可以的,对节点进行操作是有风险的。 所以你的解决方案可能是这样的:

您生成的 HTML 页面

<form id="serverData">
    <input type="hidden" name="gaToken" value="@ViewBag.GoogleAnalyticsToken" />
</form>

您修改后的分析加载程序

<script>
var ga_token = document.getElementById("serverData").gaToken.value;
(function (i, s, o, g, r, a, m) {
    i['GoogleAnalyticsObject'] = r; i[r] = i[r] || function () {
          (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
          m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
      })(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', ga_token, 'auto');
ga('send', 'pageview');
</script>

另一种没有 jQuery 且没有 <form> ...

的实现
<html id="dashboard" data-ga_id="{{ settings.GOOGLE_ANALYTICS_ACCOUNT }}">

然后...

var ga_id = document.getElementById("push-dashboard").dataset.ga_id;

if (ga_id) {
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','//www.google-analytics.com/analytics.js,'ga');

    ga('create', ga_id, 'auto');
    ga('send', 'pageview');
}