Support for creator-requested geoblocking (#1063)
Attempt to fix logic + case Fix logic Co-authored-by: Thomas Zarebczan <thomas.zarebczan@gmail.com>
This commit is contained in:
parent
d52fd8e26e
commit
ca7b98ecf5
7 changed files with 94 additions and 3 deletions
|
@ -120,7 +120,7 @@ ENABLE_WILD_WEST=true
|
||||||
BRANDED_SITE=odysee
|
BRANDED_SITE=odysee
|
||||||
LOADING_BAR_COLOR=#e50054
|
LOADING_BAR_COLOR=#e50054
|
||||||
|
|
||||||
# FIREBASE
|
# --- Firebase ---
|
||||||
FIREBASE_API_KEY=AIzaSyAgc-4QORyglpYZ3qH9E5pDauEDOJXgM3A
|
FIREBASE_API_KEY=AIzaSyAgc-4QORyglpYZ3qH9E5pDauEDOJXgM3A
|
||||||
FIREBASE_AUTH_DOMAIN=lbry-mobile.firebaseapp.com
|
FIREBASE_AUTH_DOMAIN=lbry-mobile.firebaseapp.com
|
||||||
FIREBASE_PROJECT_ID=lbry-mobile
|
FIREBASE_PROJECT_ID=lbry-mobile
|
||||||
|
@ -130,6 +130,13 @@ FIREBASE_APP_ID=1:638894153788:web:35b295b15297201bd2e339
|
||||||
FIREBASE_MEASUREMENT_ID=G-2MPJGFEEXC
|
FIREBASE_MEASUREMENT_ID=G-2MPJGFEEXC
|
||||||
FIREBASE_VAPID_KEY=BFayEBpwMTU9GQQpXgitIJkfx-SD8-ltrFb3wLTZWgA27MfBhG4948pe0eERl432NzPrMKsbkXnA7ap_vLPgLYk
|
FIREBASE_VAPID_KEY=BFayEBpwMTU9GQQpXgitIJkfx-SD8-ltrFb3wLTZWgA27MfBhG4948pe0eERl432NzPrMKsbkXnA7ap_vLPgLYk
|
||||||
|
|
||||||
# Development
|
# --- Geoblock ---
|
||||||
|
# Note: our current version of dotenv doesn't support multiline definition.
|
||||||
|
#
|
||||||
|
# FORMAT: "<channel_id>; type=[livestream|video|*]; country=[xx|*]; continent=[xx|*]; | ..."
|
||||||
|
#
|
||||||
|
GEOBLOCKED_CHANNELS="148bfcb49da2c2e781ae27387e45043a4bcbd51e; types=livestream; countries=FR,MS; continents=AF,EU; | 636098f86be74be8b609c38e643e48786d58f413; types=livestream,video; countries=EU,SA; continents=EU;"
|
||||||
|
|
||||||
|
# --- Development ---
|
||||||
REPORT_NEW_STRINGS=false
|
REPORT_NEW_STRINGS=false
|
||||||
USE_LOCAL_HOMEPAGE_DATA=false
|
USE_LOCAL_HOMEPAGE_DATA=false
|
||||||
|
|
24
config.js
24
config.js
|
@ -90,6 +90,30 @@ const config = {
|
||||||
AD_KEYWORD_BLOCKLIST_CHECK_DESCRIPTION: process.env.AD_KEYWORD_BLOCKLIST_CHECK_DESCRIPTION,
|
AD_KEYWORD_BLOCKLIST_CHECK_DESCRIPTION: process.env.AD_KEYWORD_BLOCKLIST_CHECK_DESCRIPTION,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
config.GEOBLOCKED_CHANNELS = {};
|
||||||
|
if (process.env.GEOBLOCKED_CHANNELS) {
|
||||||
|
const entries = process.env.GEOBLOCKED_CHANNELS.split('|');
|
||||||
|
|
||||||
|
entries.forEach((entry) => {
|
||||||
|
const fields = entry.split(';');
|
||||||
|
|
||||||
|
if (fields.length > 0) {
|
||||||
|
const channelId = fields[0].trim();
|
||||||
|
config.GEOBLOCKED_CHANNELS[channelId] = {};
|
||||||
|
|
||||||
|
for (let i = 1; i < fields.length; ++i) {
|
||||||
|
const kv = fields[i].split('=');
|
||||||
|
|
||||||
|
if (kv.length === 2) {
|
||||||
|
const key = kv[0].trim();
|
||||||
|
const values = kv[1].trim();
|
||||||
|
config.GEOBLOCKED_CHANNELS[channelId][key] = values.split(',');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
config.SDK_API_PATH = `${config.LBRY_WEB_API}/api/v1`;
|
config.SDK_API_PATH = `${config.LBRY_WEB_API}/api/v1`;
|
||||||
config.PROXY_URL = `${config.SDK_API_PATH}/proxy`;
|
config.PROXY_URL = `${config.SDK_API_PATH}/proxy`;
|
||||||
|
|
||||||
|
|
13
flow-typed/user.js
vendored
13
flow-typed/user.js
vendored
|
@ -34,3 +34,16 @@ declare type User = {
|
||||||
global_mod: boolean,
|
global_mod: boolean,
|
||||||
odyseeMembershipsPerClaimIds: ?{},
|
odyseeMembershipsPerClaimIds: ?{},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare type LocaleInfo = {
|
||||||
|
continent: string,
|
||||||
|
country: string,
|
||||||
|
gdpr_required: boolean,
|
||||||
|
is_eu_member: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
declare type GeoBlock = {
|
||||||
|
types: Array<string>,
|
||||||
|
countries: Array<string>,
|
||||||
|
continents: Array<string>,
|
||||||
|
};
|
||||||
|
|
|
@ -2218,6 +2218,7 @@
|
||||||
"The minimum duration must not exceed Feb 8th, 2022.": "The minimum duration must not exceed Feb 8th, 2022.",
|
"The minimum duration must not exceed Feb 8th, 2022.": "The minimum duration must not exceed Feb 8th, 2022.",
|
||||||
"No limit": "No limit",
|
"No limit": "No limit",
|
||||||
"Search results are being filtered by language. Click here to change the setting.": "Search results are being filtered by language. Click here to change the setting.",
|
"Search results are being filtered by language. Click here to change the setting.": "Search results are being filtered by language. Click here to change the setting.",
|
||||||
|
"This creator has requested that their livestream be blocked in your region.": "This creator has requested that their livestream be blocked in your region.",
|
||||||
"There are language translations available for your location! Do you want to switch from English?": "There are language translations available for your location! Do you want to switch from English?",
|
"There are language translations available for your location! Do you want to switch from English?": "There are language translations available for your location! Do you want to switch from English?",
|
||||||
"A homepage and language translations are available for your location! Do you want to switch?": "A homepage and language translations are available for your location! Do you want to switch?",
|
"A homepage and language translations are available for your location! Do you want to switch?": "A homepage and language translations are available for your location! Do you want to switch?",
|
||||||
"A homepage is available for your location! Do you want to switch?": "A homepage is available for your location! Do you want to switch?",
|
"A homepage is available for your location! Do you want to switch?": "A homepage is available for your location! Do you want to switch?",
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
import * as PAGES from 'constants/pages';
|
import * as PAGES from 'constants/pages';
|
||||||
|
import * as SETTINGS from 'constants/settings';
|
||||||
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
import React, { useEffect, useRef, useState, useLayoutEffect } from 'react';
|
||||||
import { lazyImport } from 'util/lazyImport';
|
import { lazyImport } from 'util/lazyImport';
|
||||||
import { tusUnlockAndNotify, tusHandleTabUpdates } from 'util/tus';
|
import { tusUnlockAndNotify, tusHandleTabUpdates } from 'util/tus';
|
||||||
|
@ -364,6 +365,17 @@ function App(props: Props) {
|
||||||
}
|
}
|
||||||
}, [previousRewardApproved, isRewardApproved]);
|
}, [previousRewardApproved, isRewardApproved]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchLocaleApi().then((response) => {
|
||||||
|
const locale: LocaleInfo = response?.data;
|
||||||
|
if (locale) {
|
||||||
|
// Put in 'window' for now. Can be moved to localStorage or wherever,
|
||||||
|
// but the key should remain the same so clients are not affected.
|
||||||
|
window[SETTINGS.LOCALE] = locale;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Load IMA3 SDK for aniview
|
// Load IMA3 SDK for aniview
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// if (!isAuthenticated && SHOW_ADS) {
|
// if (!isAuthenticated && SHOW_ADS) {
|
||||||
|
|
|
@ -40,6 +40,7 @@ export const SUPPORT_OPTION = 'support_option';
|
||||||
export const TILE_LAYOUT = 'tile_layout';
|
export const TILE_LAYOUT = 'tile_layout';
|
||||||
export const VIDEO_THEATER_MODE = 'video_theater_mode';
|
export const VIDEO_THEATER_MODE = 'video_theater_mode';
|
||||||
export const VIDEO_PLAYBACK_RATE = 'video_playback_rate';
|
export const VIDEO_PLAYBACK_RATE = 'video_playback_rate';
|
||||||
|
export const LOCALE = 'odysee_user_locale';
|
||||||
|
|
||||||
export const SETTINGS_GRP = {
|
export const SETTINGS_GRP = {
|
||||||
APPEARANCE: 'appearance',
|
APPEARANCE: 'appearance',
|
||||||
|
|
|
@ -6,11 +6,32 @@ import analytics from 'analytics';
|
||||||
import LivestreamLayout from 'component/livestreamLayout';
|
import LivestreamLayout from 'component/livestreamLayout';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import Page from 'component/page';
|
import Page from 'component/page';
|
||||||
|
import Yrbl from 'component/yrbl';
|
||||||
|
import { GEOBLOCKED_CHANNELS } from 'config';
|
||||||
|
import * as SETTINGS from 'constants/settings';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useIsMobile } from 'effects/use-screensize';
|
import { useIsMobile } from 'effects/use-screensize';
|
||||||
|
|
||||||
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
|
const LivestreamChatLayout = lazyImport(() => import('component/livestreamChatLayout' /* webpackChunkName: "chat" */));
|
||||||
|
|
||||||
|
function isLivestreamGeoAllowed(channelId: ?string, isLive: boolean) {
|
||||||
|
const locale: LocaleInfo = window[SETTINGS.LOCALE];
|
||||||
|
const geoBlock: GeoBlock = GEOBLOCKED_CHANNELS[channelId];
|
||||||
|
|
||||||
|
if (locale && geoBlock) {
|
||||||
|
const typeBlocked = geoBlock.types && geoBlock.types.includes('livestream');
|
||||||
|
const countryBlocked = geoBlock.countries && geoBlock.countries.includes(locale.country);
|
||||||
|
const europeanUnionOnly = geoBlock.continents && geoBlock.continents.includes('EU-UNION') && locale.is_eu_member;
|
||||||
|
const continentBlocked =
|
||||||
|
europeanUnionOnly || (geoBlock.continents && geoBlock.continents.includes(locale.continent));
|
||||||
|
|
||||||
|
return typeBlocked && !countryBlocked && !continentBlocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If 'locale/get' fails, we don't know whether to block or not. Flaw?
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
activeLivestreamForChannel: any,
|
activeLivestreamForChannel: any,
|
||||||
activeLivestreamInitialized: boolean,
|
activeLivestreamInitialized: boolean,
|
||||||
|
@ -54,6 +75,7 @@ export default function LivestreamPage(props: Props) {
|
||||||
const claimId = claim && claim.claim_id;
|
const claimId = claim && claim.claim_id;
|
||||||
const isCurrentClaimLive = isChannelBroadcasting && activeLivestreamForChannel.claimId === claimId;
|
const isCurrentClaimLive = isChannelBroadcasting && activeLivestreamForChannel.claimId === claimId;
|
||||||
const livestreamChannelId = channelClaimId || '';
|
const livestreamChannelId = channelClaimId || '';
|
||||||
|
const isGeoBlocked = !isLivestreamGeoAllowed(channelClaimId, isCurrentClaimLive);
|
||||||
|
|
||||||
// $FlowFixMe
|
// $FlowFixMe
|
||||||
const release = moment.unix(claim.value.release_time);
|
const release = moment.unix(claim.value.release_time);
|
||||||
|
@ -160,6 +182,7 @@ export default function LivestreamPage(props: Props) {
|
||||||
livestream
|
livestream
|
||||||
chatDisabled={hideComments}
|
chatDisabled={hideComments}
|
||||||
rightSide={
|
rightSide={
|
||||||
|
!isGeoBlocked &&
|
||||||
!hideComments &&
|
!hideComments &&
|
||||||
isInitialized && (
|
isInitialized && (
|
||||||
<React.Suspense fallback={null}>
|
<React.Suspense fallback={null}>
|
||||||
|
@ -168,7 +191,17 @@ export default function LivestreamPage(props: Props) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{isInitialized && (
|
{isGeoBlocked && (
|
||||||
|
<div className="main--empty">
|
||||||
|
<Yrbl
|
||||||
|
title={__('This creator has requested that their livestream be blocked in your region.')}
|
||||||
|
type="sad"
|
||||||
|
alwaysShow
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isInitialized && !isGeoBlocked && (
|
||||||
<LivestreamLayout
|
<LivestreamLayout
|
||||||
uri={uri}
|
uri={uri}
|
||||||
hideComments={hideComments}
|
hideComments={hideComments}
|
||||||
|
|
Loading…
Reference in a new issue