youtube_cookies-2.0.x-dev/js/youtube-cookies.js
js/youtube-cookies.js
/**
* @file
* Do not let videos be played until user accept cookies.
*/
/**
* Manage YouTube cookies on the page played videos.
*
* @param {string} action
* Block YouTube videos until accept cookies / use youtube-nocookie domain.
* @param {string} thumbnailLabel
* Message for the button that open the consent popup.
* @param {string} popupMessage
* Message that explains the cookies tracking.
* @param {string} manageButton
* Allow managing cookies.
* @param {string} okButton
* Accept YouTube's tracking.
* @param {string} exitButton
* Remove the button (only appears in popup).
* @param {string} thumbnailMarkup
* HTML markup for the YouTube cookies thumbnail.
*
* @constructor
*/
function YoutubeCookies(
action,
thumbnailLabel,
popupMessage,
manageButton,
okButton,
exitButton,
thumbnailMarkup
) {
this.checkYoutubeNoCookie = action !== "no_cookies_domain";
this.action = action;
this.youtubeCookiesAccepted = false;
this.manageCookiesCallback = null;
this.acceptedCookiesCallback = null;
this.thumbnailLabel = thumbnailLabel;
this.popupMessage = popupMessage;
this.thumbnailMarkup = thumbnailMarkup;
this.showThirdPartyButton = true;
this.manageButton = manageButton;
this.okButton = okButton;
this.exitButton = exitButton;
this.currentVideo = null;
this.interceptVideo = true;
this.setup();
}
const youtubeCookiesDisabled = new Event("youtubeCookiesDisabled");
const youtubeCookiesEnabled = new Event("youtubeCookiesEnabled");
/**
* Decide whether the manage cookies button should be shown.
*
* @param {boolean} value
* If false, the 'Manage cookies' button won't appear.
*/
YoutubeCookies.prototype.setShowThirdPartyButton = value => {
this.showThirdPartyButton = value;
};
/**
* Callback that allow third parties show the manage cookies form.
*
* @param {function} callback
* Callback that show the form.
*/
YoutubeCookies.prototype.setManageCookiesCallback = function (callback) {
this.manageCookiesCallback = callback;
};
/**
* Check that cookies are accepted.
*
* It allows to use a custom callback to determine that.
*
* @return {boolean|*}
* TRUE when the cookies are accepted.
*/
YoutubeCookies.prototype.areCookiesAccepted = () => {
if (this.acceptedCookiesCallback !== null) {
return this.acceptedCookiesCallback();
}
return this.youtubeCookiesAccepted;
};
/**
* Set up the YouTube videos.
*
* Show / hide videos whether the
* cookies have been accepted.
*
* @param {boolean} youtubeCookiesAccepted
* True or false according the provider value.
*/
YoutubeCookies.prototype.setAcceptedCookies = youtubeCookiesAccepted => {
if (youtubeCookiesAccepted) {
document
.querySelectorAll("iframe[data-yc-iframe-processed]")
.forEach(element => {
Drupal.youtubeCookies.destroyYoutuebeCookiesIframe(element.parentNode);
});
Drupal.youtubeCookies.removePopup();
if (Drupal.youtubeCookies.currentVideo !== null) {
// Propagate the click event to the oembed-lazy original button.
if (Drupal.youtubeCookies.isOembedLazyload(Drupal.youtubeCookies.currentVideo)) {
Drupal.youtubeCookies.currentVideo.querySelector(".oembed-lazyload__button").click();
}
Drupal.youtubeCookies.interceptVideo = true;
Drupal.youtubeCookies.currentVideo = null;
}
document.dispatchEvent(youtubeCookiesDisabled);
} else {
Drupal.youtubeCookies.processYoutubeIframes(document);
document.dispatchEvent(youtubeCookiesEnabled);
}
Drupal.youtubeCookies.youtubeCookiesAccepted = youtubeCookiesAccepted;
};
/**
* Set the callback that will be called when the cookies are not accepted.
*
* @param {function} callback
* Funcgtion to execute.
*/
YoutubeCookies.prototype.onNonAcceptedCookies = callback => {
this.onNonAcceptedCookiesCallback = callback;
};
/**
* Set up the cookies' management.
*/
YoutubeCookies.prototype.setup = function() {
document.addEventListener(
"DOMContentLoaded",
this.onContentLoaded.bind(this)
);
};
/**
* Adds the YouTube cookies popup.
*
* @param {object} element
* Element that will have the popup.
* @param {boolean} showExitButton
* Weather to show the exit button or not.
*
* @return {HTMLDivElement}
* Popup HTML
*/
YoutubeCookies.prototype.addPopup = function(element, showExitButton) {
const popupContainer = document.createElement("div");
popupContainer.classList.add("youtube-cookies-popup");
const popupContainerBox = document.createElement("div");
popupContainerBox.classList.add("youtube-cookies-popup-box");
const popupContainerBoxInfo = document.createElement("div");
popupContainerBoxInfo.classList.add("youtube-cookies-popup-box-info");
popupContainerBoxInfo.insertAdjacentHTML("beforeend", this.popupMessage);
const popupContainerBoxButtons = document.createElement("div");
popupContainerBoxButtons.classList.add("youtube-cookies-popup-box-buttons");
if (this.showThirdPartyButton) {
popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.manageButton);
}
popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.okButton);
if (showExitButton) {
popupContainerBoxButtons.insertAdjacentHTML("beforeend", this.exitButton);
}
popupContainerBoxInfo.appendChild(popupContainerBoxButtons);
popupContainerBox.appendChild(popupContainerBoxInfo);
popupContainer.appendChild(popupContainerBox);
element.append(popupContainer);
popupContainer
.querySelector(".youtube-cookies-button--accept")
.addEventListener("click", this.onAcceptClick.bind(this));
if (showExitButton) {
popupContainer
.querySelector(".youtube-cookies-button--reject")
.addEventListener("click", this.onRejectClick.bind(this));
}
const manageCookiesButton = popupContainer.querySelector(
".youtube-cookies-button--manage"
);
if (manageCookiesButton != null) {
manageCookiesButton.addEventListener(
"click",
this.onManageCookiesClick.bind(this)
);
}
return popupContainer;
};
/**
* Actions to do when the fake/yc thumbnail is clicked.
*
* @param {object} event
* Event.
*/
YoutubeCookies.prototype.onThumbnailClick = function(event) {
if (!this.interceptVideo) {
// @TODO: Check if this condition is needed.
return;
}
event.preventDefault();
this.addPopup(document.body, true);
this.currentVideo = event.currentTarget.parentNode;
this.interceptVideo = false;
};
/**
* Remove the fake thumbnail added by yc.
*
* @param {object} iframeWrapper
* The iframe container.
*/
YoutubeCookies.prototype.removeFakeThumbnail = function(iframeWrapper) {
iframeWrapper.querySelector("[data-yc-fake-button]").remove();
if (this.isOembedLazyload(iframeWrapper)) {
const button = iframeWrapper.querySelector(
"[data-yc-original-oembed-button]"
);
button.style.display = "";
button.removeAttribute("data-yc-original-oembed-button");
}
};
/**
* Dispatch event to allow other modules to add
* their specific behaviours when the thumbnail
* button is clicked.
*
* @param {object} button
* Element that received the click.
*/
YoutubeCookies.prototype.eventDispatch = function(button) {
const iframeWrapper = button.currentTarget.parentNode;
const event = new Event("youtube-cookies-thumbnail-click");
iframeWrapper.querySelector("iframe").dispatchEvent(event);
};
/**
* Remove the popup.
*/
YoutubeCookies.prototype.removePopup = function() {
const popup = document.querySelector(".youtube-cookies-popup");
if (popup !== null) {
popup.remove();
}
};
/**
* When manage cookies is clicked show the manage cookies button.
*/
YoutubeCookies.prototype.onManageCookiesClick = function() {
if (this.manageCookiesCallback !== null) {
this.manageCookiesCallback();
}
this.interceptVideo = true;
};
/**
* When cookies are accepted videos can be played.
*
* It only works in the current page , after refreshing
* consent will be needed again. Consent will only stop
* after managing third party cookies.
*
* @param {object} event
* Event.
*/
YoutubeCookies.prototype.onAcceptClick = function(event) {
this.removePopup();
if (this.currentVideo !== null) {
this.destroyYoutuebeCookiesIframe(this.currentVideo);
// Propagate the click event to the oembed-lazy original button.
if (this.isOembedLazyload(this.currentVideo)) {
this.currentVideo.querySelector(".oembed-lazyload__button").click();
}
this.interceptVideo = true;
this.currentVideo = null;
}
};
/**
* When cookies are rejected the popup is removed.
*
* @param {object} event
* Event.
*/
YoutubeCookies.prototype.onRejectClick = function(event) {
this.removePopup();
this.currentVideo = null;
this.interceptVideo = true;
};
/**
* Add a wrapper div to the YouTube iframe to easily add the thumbnail.
*
* @param {object} iframe
* Iframe.
*/
YoutubeCookies.prototype.setIframeWrapper = iframe => {
const wrapper = document.createElement("div");
wrapper.classList.add("youtube-cookies__iframe-container");
iframe.parentNode.insertBefore(wrapper, iframe);
wrapper.appendChild(iframe);
};
/**
* Remove a wrapper div to the YouTube iframe to easily add the thumbnail.
*
* @param {object} iframe
* Iframe.
*/
YoutubeCookies.prototype.removeIframeWrapper = iframe => {
const wrapper = iframe.parentNode;
wrapper.after(iframe);
wrapper.remove();
};
/**
* Replace a YouTube iframe with a YouTube thumbnail.
*
* If tracking is accepted the video will be loaded.
*
* @param {object} iframe
* Iframe.
* @param {string} iframeSrc
* A YouTube URL.
*/
YoutubeCookies.prototype.setYoutubeThumbnail = function(iframe, iframeSrc) {
const videoId = this.getYoutubeIdFromUrl(iframeSrc);
const button = document.createElement("button");
button.classList.add("youtube-cookies__thumbnail");
button.addEventListener("click", this.onThumbnailClick.bind(this));
button.setAttribute("data-yc-fake-button", "1");
button.setAttribute("aria-label", this.thumbnailLabel);
button.innerHTML = this.thumbnailMarkup.replace(/videoId/g, videoId);
iframe.parentNode.prepend(button);
};
/**
* Check that an url is from YouTube.
*
* @param {string} source
* YouTube url.
*
* @return {boolean}
* True if URL is from YouTube.
*/
YoutubeCookies.prototype.isYoutubeSource = function(source) {
if (source.length === 0) {
return false;
}
const regExps = [/youtu\.be/, /youtube\.com/];
if (this.checkYoutubeNoCookie) {
regExps.push(/youtube-nocookie\.com/);
}
for (const i in regExps) {
if (source.match(regExps[i])) {
return true;
}
}
return false;
};
/**
* Check that an iframe is oembed-lazyload.
*
* @param {object} element
* YouTube url.
*
* @return {boolean}
* True if URL is oembed-lazyload.
*/
YoutubeCookies.prototype.isOembedLazyload = function(element) {
return !!(
element.classList.contains("oembed-lazyload") ||
element.classList.contains("oembed-lazyload__iframe")
);
};
/**
* Stop an iframe.
*
* @param {object} iframe
* Iframe object.
*/
YoutubeCookies.prototype.stopIframe = function(iframe) {
const iframeSrc = iframe.src;
if (iframeSrc.length > 0 && this.isYoutubeSource(iframeSrc)) {
iframe.setAttribute("data-src", iframeSrc);
iframe.src = '';
}
};
/**
* Given a youtube video URL get its ID.
*
* @param {string} url
* Youtube video URL.
*
* @return {null|*}
* Video id, if exists.
*/
YoutubeCookies.prototype.getYoutubeIdFromUrl = function(url) {
const youtubeIdRegexp = "[^\\s&?/#]{11}";
let videoIdMatches = [];
if (url.match(`(youtube\.com\/embed\/)(${youtubeIdRegexp})`)) {
videoIdMatches = url.match(`(youtube\.com\/embed\/)(${youtubeIdRegexp})`);
}
if (url.match(`(youtu\.be\/)(${youtubeIdRegexp})`)) {
videoIdMatches = url.match(
`(youtube.com/embed)|(youtu.be/)(${youtubeIdRegexp})`
);
} else if (url.match(`(youtube.com/watch%3Fv%3D)(${youtubeIdRegexp})`)) {
videoIdMatches = url.match(
`(youtube.com/watch%3Fv%3D)(${youtubeIdRegexp})`
);
}
if (videoIdMatches !== null && videoIdMatches.length > 0) {
return videoIdMatches[videoIdMatches.length - 1];
}
return null;
};
/**
* Replace an element YouTube URL by youtube-nocookie alternative.
*
* @param {object} element
* Element.
* @param {string} url
* Video URL.
*/
YoutubeCookies.prototype.setYoutubeNoCookiesSrc = function(element, url) {
const attribute =
typeof element.src === "string" && element.src.length > 0
? "src"
: "data-src";
const videoId = this.getYoutubeIdFromUrl(url);
if (videoId != null) {
element.setAttribute("data-original-src", url);
element.setAttribute("data-original-src-attribute", attribute);
// @TODO: Improve, the domain replace should change only the domain not the
// entire URL. This changes the formatter default behaviour and must not.
element.setAttribute(
attribute,
`https://www.youtube-nocookie.com/embed/${videoId}?enablejsapi=1`
);
}
};
/**
* Set the YouTube original URL to the iframe.
*
* Only works with videos that haven't been played yet.
*
* @param {object} element
* Iframe.
*/
YoutubeCookies.prototype.setYoutubeOriginalUrl = function(element) {
const attribute = element.getAttribute("data-original-src-attribute");
element.setAttribute(attribute, element.getAttribute("data-original-src"));
element.removeAttribute("data-original-src-attribute");
element.removeAttribute("data-original-src");
};
/**
* Set up the iframes so cookies can be managed.
*
* @param {object} event
* Event objetc.
*/
YoutubeCookies.prototype.onContentLoaded = function(event) {
if (!this.youtubeCookiesAccepted) {
this.processYoutubeIframes(event.target);
}
};
/**
* Process YouTube iframes.
*
* @param {object} container
* Element that contains the iframe.
*/
YoutubeCookies.prototype.processYoutubeIframes = function(container) {
if (this.youtubeCookiesAccepted) {
return;
}
let newButton;
const iframes = container.getElementsByTagName("iframe");
for (const iframeIndex in iframes) {
const iframe = iframes[iframeIndex];
if (typeof iframe !== "object") {
return;
}
if (iframe.hasAttribute("data-yc-iframe-processed")) {
continue;
}
this.stopIframe(iframe);
const iframeSrc = iframe.hasAttribute("data-src")
? iframe.getAttribute("data-src")
: iframe.src;
if (
typeof iframeSrc === "string" &&
!this.isYoutubeSource(iframeSrc)
) {
continue;
}
const iframeWrapper = iframe.parentNode;
if (this.isOembedLazyload(iframeWrapper)) {
iframeWrapper.setAttribute(
"data-original-strategy",
iframeWrapper.getAttribute("data-strategy")
);
iframeWrapper.removeAttribute("data-strategy");
}
switch (this.action) {
case "no_cookies_domain":
this.setYoutubeNoCookiesSrc(iframe, iframeSrc);
break;
case "popup":
if (this.isOembedLazyload(iframeWrapper)) {
const originalButton = iframeWrapper.querySelector("button");
if (typeof originalButton === "object") {
iframe.parentNode.insertAdjacentHTML(
"afterbegin",
originalButton.outerHTML
);
originalButton.setAttribute("data-yc-original-oembed-button", 1);
originalButton.style.display = "none";
// @TODO: Review, for the scope of this module launch an event on
// thumbnail click is trivial. At this point the modules do not
// perform a relevant action.
originalButton.addEventListener(
"click",
this.eventDispatch.bind(this)
);
newButton = iframe.parentNode.querySelector(
"button:not([data-yc-original-oembed-button])"
);
newButton.addEventListener("click", this.onThumbnailClick.bind(this));
newButton.setAttribute("data-yc-fake-button", "1");
}
} else {
// Set up the wrapper.
if (iframe.classList.contains("youtube-cookies__iframe--wysiwyg")) {
this.setIframeWrapper(iframe);
}
if (iframe.classList.contains("youtube-cookies__iframe--oembed")) {
iframe.parentNode.classList.add("youtube-cookies__iframe-container");
}
// Add the thumbnails.
this.setYoutubeThumbnail(iframe, iframeSrc);
}
break;
}
iframe.setAttribute("data-yc-iframe-index", iframeIndex);
iframe.setAttribute("data-yc-iframe-processed", "1");
iframe.dispatchEvent(youtubeCookiesEnabled);
}
};
/**
* Set the YouTube iframe/container to his initial state.
*
* @param {object} iframeWrapper
* Iframe container object.
*/
YoutubeCookies.prototype.destroyYoutuebeCookiesIframe = function(
iframeWrapper
) {
const iframe = iframeWrapper.querySelector("iframe");
switch (this.action) {
case "no_cookies_domain":
this.setYoutubeOriginalUrl(iframe);
break;
case "popup":
this.removeFakeThumbnail(iframeWrapper);
if (this.isOembedLazyload(iframeWrapper)) {
iframeWrapper.setAttribute(
"data-strategy",
iframeWrapper.getAttribute("data-original-strategy")
);
iframeWrapper.removeAttribute("data-original-strategy");
} else {
if (iframe.classList.contains("youtube-cookies__iframe--wysiwyg")) {
this.removeIframeWrapper(iframe);
}
if (iframe.classList.contains("youtube-cookies__iframe--oembed")) {
iframe.parentNode.classList.remove("youtube-cookies__iframe-container");
}
iframe.setAttribute("src", iframe.getAttribute("data-src"));
iframe.removeAttribute("data-src");
}
break;
}
iframe.removeAttribute("data-yc-iframe-processed");
iframe.removeAttribute("data-yc-iframe-index");
iframe.dispatchEvent(youtubeCookiesDisabled);
};
/**
* Set the callback that checks cookies have been accepted.
*
* @param {function} callback
* Callback that determine if cookies have been accepted.
*/
YoutubeCookies.prototype.setAcceptedCookiesCallback = function(callback) {
this.acceptedCookiesCallback = callback;
};
/**
* Initialize YouTube cookies.
*/
(function(Drupal, drupalSettings) {
if (typeof drupalSettings.youtubeCookies === "object") {
Drupal.youtubeCookies = new YoutubeCookies(
drupalSettings.youtubeCookies.action,
drupalSettings.youtubeCookies.thumbnailLabel,
drupalSettings.youtubeCookies.popupMessage,
drupalSettings.youtubeCookies.manageButton,
drupalSettings.youtubeCookies.okButton,
drupalSettings.youtubeCookies.exitButton,
drupalSettings.youtubeCookies.thumbnailMarkup
);
}
Drupal.behaviors.youtubeCookies = {
attach() {
// Force attachment of oembed lazyload behaviour before removing the oembed attributes.
if (
typeof Drupal.behaviors.oembedLazyload === "object" &&
typeof Drupal.behaviors.oembedLazyload.attach === "function"
) {
Drupal.behaviors.oembedLazyload.attach(document);
}
Drupal.youtubeCookies.processYoutubeIframes(document);
document.dispatchEvent(youtubeCookiesEnabled);
}
};
})(Drupal, drupalSettings);
