【问题标题】:GDPR, Cookies consent banner Flutter webGDPR,Cookies 同意横幅 Flutter web
【发布时间】:2020-12-22 03:51:27
【问题描述】:

我正在使用 Flutter 构建我的网站,但网络编程对我来说非常陌生,我不太确定我是否完全了解 Cookie 的工作原理。 我仍然需要了解要在哪里写入哪些 cookie,以及从哪里获取这些 cookie。 建立横幅来管理应该很容易,如果我没记错的话,它应该是首页中弹出的第一件事。 例如,中等横幅只是一个可关闭的横幅,摆动消息 To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy. 带有隐私政策的链接,但它没有任何选择加入,因此它看起来不符合 GDPR..

这里https://medium.com/@mayur_solanki/flutter-web-formerly-hummingbird-ba52f5135fb0 展示了 cookie 在 Flutter Web 中是如何写入和读取的

html.window.localStorage['key']
html.window.sessionStorage['key']
html.window.document.cookie

html.window.localStorage['local_value'] = localStorage;
html.window.sessionStorage['session_value'] = sessionStorage;
html.window.document.cookie= "username=${cookies}; expires=Thu, 18 Dec 2020 12:00:00 UTC";

据我了解,cookie 属于这些类型。

第一方: 要跟踪用户行为(页面访问量、用户数量等),并且当我使用谷歌分析时,我确实需要征得这些用户的同意。 这里Google Analytics, Flutter, cookies and the General Data Protection Regulation (GDPR) 展示了如何激活/停用它,所以如果我没记错的话,我不应该自己存储任何东西。

第三方: 例如,这些将来自我主页上的 YouTube 链接视频,因此我也需要征求同意。 还没有检查过,但我想它应该类似于谷歌分析

会话 cookie: 这些应该用于记住购物篮中的项目。 我不应该需要这些..

持久性 cookie: 这些应该是为了保持用户的登录状态。 其中一个网页是Retailer access,它是零售商的应用程序(市场的供应方)。 我正在使用 Google 签名来登录用户,所以我应该需要这些,因为即使在用户登录后导航到 Retailer access 时,它也会始终呈现给用户的登录表单。

安全 cookie: 这些仅用于 https 并用于结帐/付款页面。 在我的网络中,零售商的应用程序仅创建产品、管理研讨会预订并处理与客户的沟通。 移动应用程序(市场的需求方)是使用 Stripe 进行所有支付的地方,所以我不应该在网络上存储任何东西......对吧?

对不起,这个问题太长了,我希望它足够清楚。 感谢您的帮助。

【问题讨论】:

  • SatSom 写了一个 Answer 说“为什么不为此使用一些第三方?例如:想到 Termly。他们甚至为您的域配置它,而无需您编写单个代码。 "
  • @SatSom 这将如何工作?我的意思是..您只需向他们注册,给他们您的域名,然后横幅就会出现?或者你必须在你的代码中集成一些sn-p?非常感谢
  • 用户在此处发布了关于您的问题的答案。然而,它被删除了。这就是为什么我将它作为评论发布的原因,因为我认为它可能有一些价值。您将无法 ping 通该用户。
  • @Scratte 感谢您的评论。我仍然不确定如何在 Flutter Web 应用程序中使用 auggested 服务。你有什么建议吗?
  • 对不起,我不知道。我不熟悉这项技术。

标签: cookies flutter-web


【解决方案1】:

我基本上遇到了问题,因为我也在使用第三方脚本(firebase、stripe、...),并且在运行任何这些脚本之前我需要用户的同意。

我围绕 Yett (https://github.com/elbywan/yett) 构建了我的解决方案,它会阻止属于先前定义的黑名单的脚本。你甚至可以自己实现这个功能,作者写了一个有趣的medium article

在我的情况下,我只有“基本”脚本,所以我构建了一个解决方案,只有在用户同意所有必要的脚本时才会加载颤振应用程序。但是,如果需要对用户的 cookie 设置进行更细粒度的控制,那么调整此解决方案应该不会太难,我添加了第二个“分析”条目作为可能的起点。

我将用户的设置存储在 localStorage 中,并在应用启动时直接检索它们以创建黑名单并决定是否应显示 cookie 横幅。

这是我的index.html

它引用了以下脚本:get_consent.jsset_consent.jsinit_firebase.jsload_app.js(下面有更多信息)。

<!DOCTYPE html>
<html>
<head>
  <!--
    If you are serving your web app in a path other than the root, change the
    href value below to reflect the base path you are serving from.

    The path provided below has to start and end with a slash "/" in order for
    it to work correctly.

    For more details:
    * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
  -->
  <base href="/">

  <meta charset="UTF-8">
  <meta content="IE=Edge" http-equiv="X-UA-Compatible">
  <meta name="description" content="A new Flutter project.">

  <!-- iOS meta tags & icons -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black">
  <meta name="apple-mobile-web-app-title" content="flutter_utils">
  <link rel="apple-touch-icon" href="icons/Icon-192.png">
  <!-- Assigns blacklist of urls based on localStorage (must be placed before yett script) -->
  <script src="get_consent.js"></script>
  <!-- Yett is used to block all third-party scripts that are part of the blacklist (must be placed before all other (third-party) scripts) -->
  <script src="https://unpkg.com/yett"></script>
  <script src="https://js.stripe.com/v3/"></script>

  <title>flutter_utils</title>
  <link rel="manifest" href="manifest.json">
  <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
  <!-- The standard consent popup (hidden by default) -->
  <div id="consent-popup" class="hidden consent-div">
    <h2>We use cookies and other technologies</h2>
    <p>This website uses cookies and similar functions to process end device information and personal data. The processing serves the integration of content, external services and elements of third parties, statistical analysis/measurement, personalized advertising and the integration of social media. Depending on the function, data may be passed on to third parties within the EU in the process. Your consent is always voluntary, not required for the use of our website and can be rejected or revoked at any time via the icon at the bottom right.
    </p>
    <div>
      <button id="accept-btn" class="btn inline">Accept</button>
      <button id="reject-btn" class="btn inline">Reject</button>
      <button id="info-btn" class="btn inline">More info</button>    
    </div>
  </div>
  <!-- Detailed consent popup allows the user to control scripts by their category -->
  <div id="consent-popup-details" class="hidden consent-div">
    <h2>Choose what to accept</h2>
    <div>
      <div class="row-div">
        <h3>Essential</h3>
        <label class="switch">
          <!-- Essentials must always be checked -->
          <input id="essential-cb" type="checkbox" checked disabled=true>
          <span class="slider round"></span>
        </label>    
      </div>
      <p>
      Here you can find all technically necessary scripts, cookies and other elements that are necessary for the operation of the website.
      </p>
    </div>
        <div>
      <div class="row-div">
        <h3>Analytics</h3>
        <label class="switch">
          <input id ="analytics-cb" type="checkbox">
          <span class="slider round"></span>
        </label>    
      </div>
      <p>
      For the site, visitors, web page views and diveerse other data are stored anonymously.
      </p>
    </div>
    <div>
      <button id="save-btn" class="btn inline">Save</button>
      <button id="cancel-btn" class="btn inline">Cancel</button>   
    </div>
  </div>
  <!-- Updates localStorage with user's cookie settings -->
  <script src="set_consent.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-app.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-auth.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-firestore.js"></script>
  <script src="https://www.gstatic.com/firebasejs/8.6.1/firebase-storage.js"></script>
  <!-- Initializes firebase (if user gave consent) -->
  <script src="init_firebase.js"></script>
  <!-- Loads flutter app (if user gave consent) -->
  <script src="load_app.js"></script>     
</body>
</html>

get_consent.js 是第一个脚本,从 localStorage 中检索用户的设置,还定义了 Yett 黑名单:

const essentialCookies = ["js.stripe.com", "www.gstatic.com"];
const analyticsCookies = ["www.google-analytics.com"];
const allCookies = [...essentialCookies, ...analyticsCookies];
const consentPropertyName = "cookie_consent";

const retrieveConsentSettings = () => {
  const consentJsonString = localStorage.getItem(consentPropertyName);
  return JSON.parse(consentJsonString);
};

const checkConsentIsMissing = () => {
  const consentObj = retrieveConsentSettings();
  if (!consentObj || consentObj.length == 0) {
    return true;
  }
  return false;
};

const consentIsMissing = checkConsentIsMissing();

var blacklist;
if (consentIsMissing) {
  blacklist = allCookies;
} else {
  const acceptedCookies = retrieveConsentSettings();
  // Remove all script urls from blacklist that the user accepts (if all are accepted the blacklist will be empty)
  blacklist = allCookies.filter( ( el ) => !acceptedCookies.includes( el ) );
}

// Yett blacklist expects list of RegExp objects
var blacklistRegEx = [];
for (let index = 0; index < blacklist.length; index++) {
  const regExp = new RegExp(blacklist[index]);
  blacklistRegEx.push(regExp);
}

YETT_BLACKLIST = blacklistRegEx;

set_consent.js 负责使用用户的设置更新 localStorage,并隐藏/显示相应的 div 以获取 cookie 同意。通常,可以简单地调用 window.yett.unblock() 来解除对脚本的阻止,但由于它们的顺序很重要,我决定在 localStorage 更新后简单地重新加载窗口:

const saveToStorage = (acceptedCookies) => {
  const jsonString = JSON.stringify(acceptedCookies);
  localStorage.setItem(consentPropertyName, jsonString);
};

window.onload = () => {
  const consentPopup = document.getElementById("consent-popup");
  const consentPopupDetails = document.getElementById("consent-popup-details");
  const acceptBtn = document.getElementById("accept-btn");
  const moreInfoBtn = document.getElementById("info-btn");
  const saveBtn = document.getElementById("save-btn");
  const cancelBtn = document.getElementById("cancel-btn");
  const rejectBtn = document.getElementById("reject-btn");

  const acceptFn = (event) => {
    const cookiesTmp = [...essentialCookies, ...analyticsCookies];
    saveToStorage(cookiesTmp);
    // Reload window after localStorage was updated.
    // The blacklist will then only contain items the user has not yet consented to.
    window.location.reload();
  };

  const cancelFn = (event) => {
    consentPopup.classList.remove("hidden");
    consentPopupDetails.classList.add("hidden");
  };

  const rejectFn = (event) => {
    console.log("Rejected!");
    // Possible To-Do: Show placeholder content if even essential scripts are rejected.
  };

  const showDetailsFn = () => {
    consentPopup.classList.add("hidden");
    consentPopupDetails.classList.remove("hidden");
  };

  const saveFn = (event) => {
    const analyticsChecked = document.getElementById("analytics-cb").checked;
    var cookiesTmp = [...essentialCookies];
    if (analyticsChecked) {
      cookiesTmp.push(...analyticsCookies);
    }
    saveToStorage(cookiesTmp);
    // Reload window after localStorage was updated.
    // The blacklist will then only contain items the user has not yet consented to.
    window.location.reload();
  };

  acceptBtn.addEventListener("click", acceptFn);
  moreInfoBtn.addEventListener("click", showDetailsFn);
  saveBtn.addEventListener("click", saveFn);
  cancelBtn.addEventListener("click", cancelFn);
  rejectBtn.addEventListener("click", rejectFn);

  if (consentIsMissing) {
    consentPopup.classList.remove("hidden");
  }
};

init_firebase.js 是初始化服务的常用脚本,但我只有在获得同意的情况下才会初始化:

var firebaseConfig = {
  // your standard config
};

// Initialize Firebase only if user consented
if (!consentIsMissing) {
  firebase.initializeApp(firebaseConfig);
}

同样的逻辑适用于脚本load_app.js。只有在用户同意的情况下才会加载 Flutter 应用。

因此,可能会向index.html 添加一些后备内容,如果用户拒绝必要的脚本,则会显示这些内容。根据您的用例,它也可能是加载应用程序的一个选项,然后通过从 localStorage 访问用户的设置来区分应用程序。

var serviceWorkerVersion = null;
var scriptLoaded = false;
function loadMainDartJs() {
  if (scriptLoaded) {
    return;
  }
  scriptLoaded = true;
  var scriptTag = document.createElement("script");
  scriptTag.src = "main.dart.js";
  scriptTag.type = "application/javascript";
  document.body.append(scriptTag);
}

// Load app only if user consented
if (!consentIsMissing) {
  if ("serviceWorker" in navigator) {
    // Service workers are supported. Use them.
    window.addEventListener("load", function () {
      // Wait for registration to finish before dropping the <script> tag.
      // Otherwise, the browser will load the script multiple times,
      // potentially different versions.
      var serviceWorkerUrl =
        "flutter_service_worker.js?v=" + serviceWorkerVersion;
      navigator.serviceWorker.register(serviceWorkerUrl).then((reg) => {
        function waitForActivation(serviceWorker) {
          serviceWorker.addEventListener("statechange", () => {
            if (serviceWorker.state == "activated") {
              console.log("Installed new service worker.");
              loadMainDartJs();
            }
          });
        }
        if (!reg.active && (reg.installing || reg.waiting)) {
          // No active web worker and we have installed or are installing
          // one for the first time. Simply wait for it to activate.
          waitForActivation(reg.installing ?? reg.waiting);
        } else if (!reg.active.scriptURL.endsWith(serviceWorkerVersion)) {
          // When the app updates the serviceWorkerVersion changes, so we
          // need to ask the service worker to update.
          console.log("New service worker available.");
          reg.update();
          waitForActivation(reg.installing);
        } else {
          // Existing service worker is still good.
          console.log("Loading app from service worker.");
          loadMainDartJs();
        }
      });

      // If service worker doesn't succeed in a reasonable amount of time,
      // fallback to plaint <script> tag.
      setTimeout(() => {
        if (!scriptLoaded) {
          console.warn(
            "Failed to load app from service worker. Falling back to plain <script> tag."
          );
          loadMainDartJs();
        }
      }, 4000);
    });
  } else {
    // Service workers not supported. Just drop the <script> tag.
    loadMainDartJs();
  }
}

这是我的style.css

html,
body {
  height: 100%;
  width: 100%;
  background-color: #2d2d2d;
  font-family: Arial, Helvetica, sans-serif;
}

.hidden {
  display: none;
  visibility: hidden;
}

.consent-div {
  position: fixed;
  bottom: 40px;
  left: 10%;
  right: 10%;
  width: 80%;
  padding: 14px 14px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: space-between;
  background-color: #eee;
  border-radius: 5px;
  box-shadow: 0 0 5px 5px rgba(0, 0, 0, 0.2);
}

.row-div {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

#accept-btn,
#save-btn {
  background-color: #103900;
}

#reject-btn,
#cancel-btn {
  background-color: #ff0000;
}

.btn {
  height: 25px;
  width: 140px;
  background-color: #777;
  border: none;
  color: white;
  border-radius: 5px;
  cursor: pointer;
}

.inline {
  display: inline-block;
  margin-right: 5px;
}

h2 {
  margin-block-start: 0.5em;
  margin-block-end: 0em;
}

h3 {
  margin-block-start: 0.5em;
  margin-block-end: 0em;
}

/* The switch - the box around the slider */
.switch {
  position: relative;
  display: inline-block;
  width: 50px;
  height: 25px;
}

/* Hide default HTML checkbox */
.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

/* The slider */
.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #ccc;
}

.slider:before {
  position: absolute;
  content: "";
  height: 18px;
  width: 18px;
  left: 4px;
  bottom: 4px;
  background-color: white;
}

input:checked + .slider {
  background-color: #2196f3;
}

input:focus + .slider {
  box-shadow: 0 0 1px #2196f3;
}

input:checked + .slider:before {
  -webkit-transform: translateX(24px);
  -ms-transform: translateX(24px);
  transform: translateX(24px);
}

/* Rounded sliders */
.slider.round {
  border-radius: 34px;
}

.slider.round:before {
  border-radius: 50%;
}

【讨论】:

  • 哇,多么详尽的答案!非常感谢。很抱歉很晚才回复。我会尝试实现它,做出我需要的改变..我会问你一些问题肯定..me 和 html 仍然在两个不同的星球上,哈哈。再一次非常感谢你。干杯
猜你喜欢
  • 2020-05-08
  • 2018-11-08
  • 2021-12-12
  • 1970-01-01
  • 2022-01-10
  • 1970-01-01
  • 2021-07-26
  • 2020-10-28
  • 2021-03-17
相关资源
最近更新 更多