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
This commit is contained in:
mayeaux 2021-11-23 16:21:33 +01:00 committed by GitHub
parent 3c4ccdd2fe
commit f2715fa97b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 92 additions and 26 deletions

View file

@ -47,6 +47,7 @@ export const MAIN_WRAPPER_CLASS = 'main-wrapper';
export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1; export const IS_MAC = navigator.userAgent.indexOf('Mac OS X') !== -1;
const imaLibraryPath = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'; const imaLibraryPath = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js';
const securePrivacyScriptUrl = 'https://app.secureprivacy.ai/script/6194129b66262906dd4a5f43.js';
type Props = { type Props = {
language: string, 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. // ready for sync syncs, however after signin when hasVerifiedEmail, that syncs too.
useEffect(() => { useEffect(() => {
// signInSyncPref is cleared after sharedState loop. // signInSyncPref is cleared after sharedState loop.

View file

@ -3,7 +3,7 @@ import type { Node } from 'react';
import * as PAGES from 'constants/pages'; import * as PAGES from 'constants/pages';
import * as ICONS from 'constants/icons'; import * as ICONS from 'constants/icons';
import * as KEYCODES from 'constants/keycodes'; import * as KEYCODES from 'constants/keycodes';
import React from 'react'; import React, { useEffect } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
import Icon from 'component/common/icon'; import Icon from 'component/common/icon';
@ -323,6 +323,15 @@ function SideNavigation(props: Props) {
return () => window.removeEventListener('keydown', handleKeydown); return () => window.removeEventListener('keydown', handleKeydown);
}, [sidebarOpen, setSidebarOpen, isAbsolute]); }, [sidebarOpen, setSidebarOpen, isAbsolute]);
useEffect(() => {
if (!window.sp) {
const gdprDiv = document.getElementById('gdprPrivacyFooter');
if (gdprDiv) {
gdprDiv.style.display = 'none';
}
}
}, [sidebarOpen]);
const unAuthNudge = const unAuthNudge =
DOMAIN === 'lbry.tv' ? null : ( DOMAIN === 'lbry.tv' ? null : (
<div className="navigation__auth-nudge"> <div className="navigation__auth-nudge">
@ -354,6 +363,9 @@ function SideNavigation(props: Props) {
<li className="navigation-link"> <li className="navigation-link">
<Button label={__('Privacy Policy')} href="https://odysee.com/$/privacypolicy" /> <Button label={__('Privacy Policy')} href="https://odysee.com/$/privacypolicy" />
</li> </li>
<li className="navigation-link" id="gdprPrivacyFooter">
<Button label={__('Cookies')} onClick={() => window.sp && window.sp.showPrivacyBanner()} />
</li>
</ul> </ul>
); );

View file

@ -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 = const IS_IOS =
(/iPad|iPhone|iPod/.test(navigator.platform) || (/iPad|iPhone|iPod/.test(navigator.platform) ||
// for iOS 13+ , platform is MacIntel, so use this to test
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) && (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)) &&
!window.MSStream; !window.MSStream;

View file

@ -8,10 +8,20 @@ import Button from 'component/button';
import classnames from 'classnames'; import classnames from 'classnames';
// $FlowFixMe // $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 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 = { type Props = {
location: { pathname: string }, location: { pathname: string },
type: string, type: string,
@ -27,18 +37,23 @@ function Ads(props: Props) {
small, small,
} = props; } = props;
let scriptUrlToUse;
let tagNameToUse;
if (IS_IOS) {
tagNameToUse = IOS_ADS_TAG;
scriptUrlToUse = IOS_ADS_URL;
} else {
tagNameToUse = ADS_TAG;
scriptUrlToUse = ADS_URL;
}
useEffect(() => { useEffect(() => {
if (SHOW_ADS && type === 'video') { if (SHOW_ADS && type === 'video') {
let script; let script;
try { try {
const d = document; let fjs = document.getElementsByTagName('script')[0];
const s = 'script'; script = document.createElement('script');
const n = 'vidcrunch'; script.src = scriptUrlToUse;
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';
// $FlowFixMe // $FlowFixMe
fjs.parentNode.insertBefore(script, fjs); fjs.parentNode.insertBefore(script, fjs);
} catch (e) {} } catch (e) {}
@ -83,7 +98,7 @@ function Ads(props: Props) {
const videoAd = ( const videoAd = (
<div className="ads__claim-item"> <div className="ads__claim-item">
<div id="vidcrunchJS537102317" className="ads__injected-video" style={{display: 'none'}} /> <div id={tagNameToUse} className="ads__injected-video" style={{display: 'none'}} />
<div <div
className={classnames('ads__claim-text', { className={classnames('ads__claim-text', {
'ads__claim-text--small': small, 'ads__claim-text--small': small,
@ -95,24 +110,12 @@ function Ads(props: Props) {
</div> </div>
); );
const sidebarAd = (
<div className="ads-wrapper">
<p>Ads</p>
<p>{adsSignInDriver}</p>
<div id="vidcrunchJS537102317" />
</div>
);
if (!SHOW_ADS) { if (!SHOW_ADS) {
return false; return false;
} }
if (type === 'video') { if (type === 'video') {
return videoAd; return videoAd;
} }
if (type === 'sidebar') {
return sidebarAd;
}
} }
export default withRouter(Ads); export default withRouter(Ads);

View file

@ -1,8 +1,14 @@
import React from 'react'; import React, { useEffect } from 'react';
import Button from 'component/button'; import Button from 'component/button';
import { SIMPLE_SITE } from 'config'; import { SIMPLE_SITE } from 'config';
export default function Footer() { export default function Footer() {
useEffect(() => {
if (!window.sp) {
document.getElementById('gdprPrivacyFooter').style.display = 'none';
}
}, []);
if (!SIMPLE_SITE) { if (!SIMPLE_SITE) {
return null; return null;
} }
@ -27,6 +33,9 @@ export default function Footer() {
<li className="footer__link"> <li className="footer__link">
<Button label={__('Privacy Policy')} href="https://odysee.com/$/privacypolicy" /> <Button label={__('Privacy Policy')} href="https://odysee.com/$/privacypolicy" />
</li> </li>
<li className="footer__link" id="gdprPrivacyFooter">
<Button label={__('Cookies')} onClick={() => window.sp && window.sp.showPrivacyBanner()} />
</li>
</ul> </ul>
</footer> </footer>
); );