From f2715fa97b0725ede950c135cd85e864d433edc5 Mon Sep 17 00:00:00 2001 From: mayeaux Date: Tue, 23 Nov 2021 16:21:33 +0100 Subject: [PATCH] Adds GDPR support (#311) * add gdpr support * only run on production * testing implementation * just needs last touches then ready * ready for merge * add cookies to sidebar * hide button when secureprivacy not available * switch over to loading script as a react hook * conditionally add secureprivacy script * save gdpr status on session * better design --- ui/component/app/view.jsx | 41 ++++++++++++++++ ui/component/sideNavigation/view.jsx | 14 +++++- .../viewers/videoViewer/internal/videojs.jsx | 1 + web/component/ads/view.jsx | 49 ++++++++++--------- web/component/footer.jsx | 13 ++++- 5 files changed, 92 insertions(+), 26 deletions(-) diff --git a/ui/component/app/view.jsx b/ui/component/app/view.jsx index 6b1b4a90c..f6cd8562e 100644 --- a/ui/component/app/view.jsx +++ b/ui/component/app/view.jsx @@ -47,6 +47,7 @@ export const MAIN_WRAPPER_CLASS = 'main-wrapper'; export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1; const imaLibraryPath = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'; +const securePrivacyScriptUrl = 'https://app.secureprivacy.ai/script/6194129b66262906dd4a5f43.js'; type Props = { language: string, @@ -327,6 +328,46 @@ function App(props: Props) { }; }, []); + // add secure privacy script + useEffect(() => { + const script = document.createElement('script'); + script.src = securePrivacyScriptUrl; + script.async = true; + // might use this for future checking to prevent doubleloading + script.id = 'securePrivacy'; + + const getLocaleEndpoint = 'https://api.odysee.com/locale/get'; + const gdprRequired = localStorage.getItem('gdprRequired'); + // gdpr is known to be required, add script + if (gdprRequired === 'true') { + // $FlowFixMe + document.head.appendChild(script); + } + + // haven't done a gdpr check, do it now + if (gdprRequired === null) { + (async function() { + const response = await fetch(getLocaleEndpoint); + const json = await response.json(); + const gdprRequiredBasedOnLocation = json.data.gdpr_required; + // note we need gdpr and load script + if (gdprRequiredBasedOnLocation) { + localStorage.setItem('gdprRequired', 'true'); + // $FlowFixMe + document.head.appendChild(script); + // note we don't need gdpr, save to session + } else if (gdprRequiredBasedOnLocation === false) { + localStorage.setItem('gdprRequired', 'false'); + } + })(); + } + + return () => { + // $FlowFixMe + document.head.removeChild(script); + }; + }, []); + // ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too. useEffect(() => { // signInSyncPref is cleared after sharedState loop. diff --git a/ui/component/sideNavigation/view.jsx b/ui/component/sideNavigation/view.jsx index c17c3ca15..7abe7cf1d 100644 --- a/ui/component/sideNavigation/view.jsx +++ b/ui/component/sideNavigation/view.jsx @@ -3,7 +3,7 @@ import type { Node } from 'react'; import * as PAGES from 'constants/pages'; import * as ICONS from 'constants/icons'; import * as KEYCODES from 'constants/keycodes'; -import React from 'react'; +import React, { useEffect } from 'react'; import Button from 'component/button'; import classnames from 'classnames'; import Icon from 'component/common/icon'; @@ -323,6 +323,15 @@ function SideNavigation(props: Props) { return () => window.removeEventListener('keydown', handleKeydown); }, [sidebarOpen, setSidebarOpen, isAbsolute]); + useEffect(() => { + if (!window.sp) { + const gdprDiv = document.getElementById('gdprPrivacyFooter'); + if (gdprDiv) { + gdprDiv.style.display = 'none'; + } + } + }, [sidebarOpen]); + const unAuthNudge = DOMAIN === 'lbry.tv' ? null : (
@@ -354,6 +363,9 @@ function SideNavigation(props: Props) {
  • +
  • +
  • ); diff --git a/ui/component/viewers/videoViewer/internal/videojs.jsx b/ui/component/viewers/videoViewer/internal/videojs.jsx index 69beb04e6..c1d3cf730 100644 --- a/ui/component/viewers/videoViewer/internal/videojs.jsx +++ b/ui/component/viewers/videoViewer/internal/videojs.jsx @@ -73,6 +73,7 @@ const videoPlaybackRates = [0.25, 0.5, 0.75, 1, 1.1, 1.25, 1.5, 1.75, 2]; const IS_IOS = (/iPad|iPhone|iPod/.test(navigator.platform) || + // for iOS 13+ , platform is MacIntel, so use this to test (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) && !window.MSStream; diff --git a/web/component/ads/view.jsx b/web/component/ads/view.jsx index fcdefae9f..024b88a54 100644 --- a/web/component/ads/view.jsx +++ b/web/component/ads/view.jsx @@ -8,10 +8,20 @@ import Button from 'component/button'; import classnames from 'classnames'; // $FlowFixMe -const ADS_URL = - 'https://cdn.vidcrunch.com/integrations/618bb4d28aac298191eec411/Lbry_Odysee.com_Responsive_Floating_DFP_Rev70_1011.js'; const IS_MOBILE = typeof window.orientation !== 'undefined'; +const ADS_URL = 'https://cdn.vidcrunch.com/integrations/618bb4d28aac298191eec411/Lbry_Odysee.com_Responsive_Floating_DFP_Rev70_1011.js'; +const ADS_TAG = 'vidcrunchJS537102317'; + +const IOS_ADS_URL = 'https://cdn.vidcrunch.com/integrations/618bb4d28aac298191eec411/Lbry_Odysee.com_Mobile_Floating_DFP_Rev70_1611.js'; +const IOS_ADS_TAG = 'vidcrunchJS199212779'; + +const IS_IOS = + (/iPad|iPhone|iPod/.test(navigator.platform) || + // for iOS 13+ , platform is MacIntel, so use this to test + (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) && + !window.MSStream; + type Props = { location: { pathname: string }, type: string, @@ -27,18 +37,23 @@ function Ads(props: Props) { small, } = props; + let scriptUrlToUse; + let tagNameToUse; + if (IS_IOS) { + tagNameToUse = IOS_ADS_TAG; + scriptUrlToUse = IOS_ADS_URL; + } else { + tagNameToUse = ADS_TAG; + scriptUrlToUse = ADS_URL; + } + useEffect(() => { if (SHOW_ADS && type === 'video') { let script; try { - const d = document; - const s = 'script'; - const n = 'vidcrunch'; - let fjs = d.getElementsByTagName(s)[0]; - script = d.createElement(s); - script.className = n; - script.src = - 'https://cdn.vidcrunch.com/integrations/618bb4d28aac298191eec411/Lbry_Odysee.com_Responsive_Floating_DFP_Rev70_1011.js'; + let fjs = document.getElementsByTagName('script')[0]; + script = document.createElement('script'); + script.src = scriptUrlToUse; // $FlowFixMe fjs.parentNode.insertBefore(script, fjs); } catch (e) {} @@ -83,7 +98,7 @@ function Ads(props: Props) { const videoAd = (
    -
    +
    ); - const sidebarAd = ( -
    -

    Ads

    -

    {adsSignInDriver}

    -
    -
    - ); - if (!SHOW_ADS) { return false; } if (type === 'video') { return videoAd; } - - if (type === 'sidebar') { - return sidebarAd; - } } export default withRouter(Ads); diff --git a/web/component/footer.jsx b/web/component/footer.jsx index a588628b2..7a023cf2d 100644 --- a/web/component/footer.jsx +++ b/web/component/footer.jsx @@ -1,8 +1,14 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import Button from 'component/button'; import { SIMPLE_SITE } from 'config'; export default function Footer() { + useEffect(() => { + if (!window.sp) { + document.getElementById('gdprPrivacyFooter').style.display = 'none'; + } + }, []); + if (!SIMPLE_SITE) { return null; } @@ -25,7 +31,10 @@ export default function Footer() {