GDPR、Cookies 同意横幅 Flutter web
GDPR, Cookies consent banner Flutter web
我正在使用 Flutter 构建我的网站,但网络编程对我来说是一个新事物,我不太确定我是否确切了解 Cookies 的工作原理。
我仍然需要了解要在何处写入哪些 cookie,以及我从何处获取这些 cookie。
构建横幅来管理应该很容易,如果我没记错的话,它应该是主页中弹出的第一件事。
例如 Medium banner 只是一个可关闭的 banner swing the message
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy.
link 隐私政策,但它没有任何选择加入,因此它看起来不符合 GDPR..
这里https://medium.com/@mayur_solanki/flutter-web-formerly-hummingbird-ba52f5135fb0展示flutter web中cookie的写入和读取方式
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 分析时,我确实需要征得同意。
这里 Google Analytics, Flutter, cookies and the General Data Protection Regulation (GDPR) 展示了如何 activate/deactivate 它,所以如果我没记错,我不应该自己存储任何东西。
第三方:
例如,这些来自我主页上的 YouTube linked 视频,因此我也需要征求他们的同意。
还没有检查过,但我想它应该类似于 Google Analytics
会话 cookie:
这些应该用于记住购物篮中的物品。
我不应该需要这些..
持久性 cookie:
这些应该是为了保持用户的登录状态。
其中一个网页是 Retailer access
,它是零售商的应用程序(市场的供应方)。
我正在使用 Google 签名来登录用户,所以我应该需要这些,因为即使在用户登录后导航到 Retailer access
时,它始终会向用户显示登录表单。
安全 cookie:
这些仅适用于 https,用于 check-out/payment 页面。
在我的网站中,零售商的应用程序仅创建产品、管理车间预订以及处理与客户的沟通。
移动应用程序(市场的需求方)是使用 Stripe 进行所有付款的地方,所以我不应该在网络上存储任何东西..对吧?
很抱歉问了这么长的问题,我希望你已经足够清楚了。
谢谢你的帮助。
我基本上遇到了问题,因为我也在使用第三方脚本(firebase、stripe 等),我需要用户的同意才能使用这些脚本运行。
我围绕 Yett (https://github.com/elbywan/yett), which blocks scripts that are part of a previously defined blacklist. You could even implement this functionality by yourself, the author has written an interesting medium article.
构建我的解决方案
在我的例子中,我只有“必要的”脚本,所以我构建了一个解决方案,只有当用户同意所有必要的脚本时,flutter 应用程序才会加载。但是,如果需要对用户的 cookie 设置进行更细粒度的控制,并且我为“分析”添加了第二个条目作为可能的起点,那么调整此解决方案应该不会太困难。
我将用户的设置存储在 localStorage 中,并在应用启动时直接检索它们以创建黑名单并决定是否应显示 cookie 横幅。
这是我的 index.html
.
它引用了以下脚本:get_consent.js
、set_consent.js
、init_firebase.js
和 load_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,并 hides/shows 用于 cookie 同意的相应 div。通常,可以简单地调用 window.yett.unblock()
来解锁脚本,但由于它们的顺序很重要,我决定在 localStorage 更新后简单地重新加载 window:
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%;
}
我正在使用 Flutter 构建我的网站,但网络编程对我来说是一个新事物,我不太确定我是否确切了解 Cookies 的工作原理。
我仍然需要了解要在何处写入哪些 cookie,以及我从何处获取这些 cookie。
构建横幅来管理应该很容易,如果我没记错的话,它应该是主页中弹出的第一件事。
例如 Medium banner 只是一个可关闭的 banner swing the message
To make Medium work, we log user data. By using Medium, you agree to our Privacy Policy.
link 隐私政策,但它没有任何选择加入,因此它看起来不符合 GDPR..
这里https://medium.com/@mayur_solanki/flutter-web-formerly-hummingbird-ba52f5135fb0展示flutter web中cookie的写入和读取方式
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 分析时,我确实需要征得同意。 这里 Google Analytics, Flutter, cookies and the General Data Protection Regulation (GDPR) 展示了如何 activate/deactivate 它,所以如果我没记错,我不应该自己存储任何东西。
第三方: 例如,这些来自我主页上的 YouTube linked 视频,因此我也需要征求他们的同意。 还没有检查过,但我想它应该类似于 Google Analytics
会话 cookie: 这些应该用于记住购物篮中的物品。 我不应该需要这些..
持久性 cookie:
这些应该是为了保持用户的登录状态。
其中一个网页是 Retailer access
,它是零售商的应用程序(市场的供应方)。
我正在使用 Google 签名来登录用户,所以我应该需要这些,因为即使在用户登录后导航到 Retailer access
时,它始终会向用户显示登录表单。
安全 cookie: 这些仅适用于 https,用于 check-out/payment 页面。 在我的网站中,零售商的应用程序仅创建产品、管理车间预订以及处理与客户的沟通。 移动应用程序(市场的需求方)是使用 Stripe 进行所有付款的地方,所以我不应该在网络上存储任何东西..对吧?
很抱歉问了这么长的问题,我希望你已经足够清楚了。 谢谢你的帮助。
我基本上遇到了问题,因为我也在使用第三方脚本(firebase、stripe 等),我需要用户的同意才能使用这些脚本运行。
我围绕 Yett (https://github.com/elbywan/yett), which blocks scripts that are part of a previously defined blacklist. You could even implement this functionality by yourself, the author has written an interesting medium article.
构建我的解决方案在我的例子中,我只有“必要的”脚本,所以我构建了一个解决方案,只有当用户同意所有必要的脚本时,flutter 应用程序才会加载。但是,如果需要对用户的 cookie 设置进行更细粒度的控制,并且我为“分析”添加了第二个条目作为可能的起点,那么调整此解决方案应该不会太困难。
我将用户的设置存储在 localStorage 中,并在应用启动时直接检索它们以创建黑名单并决定是否应显示 cookie 横幅。
这是我的 index.html
.
它引用了以下脚本:get_consent.js
、set_consent.js
、init_firebase.js
和 load_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,并 hides/shows 用于 cookie 同意的相应 div。通常,可以简单地调用 window.yett.unblock()
来解锁脚本,但由于它们的顺序很重要,我决定在 localStorage 更新后简单地重新加载 window:
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%;
}