improve share modal

This commit is contained in:
Sean Yesmunt 2020-03-27 14:57:03 -04:00
parent f7846e976a
commit 419b3890cd
12 changed files with 316 additions and 50 deletions

View file

@ -4,7 +4,7 @@ const Router = require('@koa/router');
const router = new Router();
router.get(`/$/download/:claimName/:claimId`, async ctx => {
function getStreamUrl(ctx) {
const { claimName, claimId } = ctx.params;
// hack to get around how we managing the continent cookie
@ -12,10 +12,20 @@ router.get(`/$/download/:claimName/:claimId`, async ctx => {
// changes need to be made to that to better work with the server
const streamingContinentCookie = ctx.cookies.get(CONTINENT_COOKIE) || 'NA';
const streamUrl = generateStreamUrl(claimName, claimId, undefined, streamingContinentCookie);
return streamUrl;
}
router.get(`/$/download/:claimName/:claimId`, async ctx => {
const streamUrl = getStreamUrl(ctx);
const downloadUrl = `${streamUrl}?download=1`;
ctx.redirect(downloadUrl);
});
router.get(`/$/stream/:claimName/:claimId`, async ctx => {
const streamUrl = getStreamUrl(ctx);
ctx.redirect(streamUrl);
});
router.get('*', async ctx => {
const html = await getHtml(ctx);
ctx.body = html;

View file

@ -10,7 +10,7 @@ type IconProps = {
// Returns a react component
// Icons with tooltips need to use this function so the ref can be properly forwarded
const buildIcon = (iconStrokes: React$Node) =>
const buildIcon = (iconStrokes: React$Node, customSvgValues = {}) =>
forwardRef((props: IconProps, ref) => {
const { size = 24, color = 'currentColor', ...otherProps } = props;
return (
@ -26,6 +26,7 @@ const buildIcon = (iconStrokes: React$Node) =>
strokeLinecap="round"
strokeLinejoin="round"
{...otherProps}
{...customSvgValues}
>
{iconStrokes}
</svg>
@ -234,10 +235,6 @@ export const icons = {
<path d="M16 8v5a3 3 0 0 0 6 0v-1a10 10 0 1 0-3.92 7.94" />
</g>
),
[ICONS.TWITTER]: buildIcon(
<path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z" />
),
[ICONS.FACEBOOK]: buildIcon(<path d="M18 2h-3a5 5 0 0 0-5 5v3H7v4h3v8h4v-8h3l1-4h-4V7a1 1 0 0 1 1-1h3z" />),
[ICONS.WEB]: buildIcon(
<g>
<circle cx="12" cy="12" r="10" />
@ -432,4 +429,183 @@ export const icons = {
<path d="m 6.363961,14.188893 h 5.701048" />
</g>
),
//
// Share modal social icons
//
[ICONS.TWITTER]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 30C0 13.4315 13.4315 0 30 0C46.5685 0 60 13.4315 60 30C60 46.5685 46.5685 60 30 60C13.4315 60 0 46.5685 0 30Z"
fill="#55ACEE"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M29.1015 24.3844L29.1645 25.4224L28.1152 25.2953C24.2961 24.8081 20.9596 23.1556 18.1267 20.3804L16.7417 19.0034L16.385 20.0203C15.6295 22.2871 16.1122 24.681 17.686 26.291C18.5254 27.1808 18.3365 27.3079 16.8886 26.7783C16.385 26.6088 15.9443 26.4817 15.9023 26.5453C15.7554 26.6935 16.2591 28.6214 16.6578 29.384C17.2034 30.4433 18.3155 31.4814 19.5326 32.0957L20.5609 32.583L19.3438 32.6042C18.1686 32.6042 18.1267 32.6254 18.2526 33.0702C18.6723 34.4473 20.33 35.909 22.1767 36.5446L23.4777 36.9895L22.3445 37.6674C20.6658 38.6419 18.6933 39.1927 16.7207 39.2351C15.7764 39.2563 15 39.341 15 39.4046C15 39.6164 17.5601 40.8028 19.05 41.2689C23.5197 42.6459 28.8287 42.0527 32.8157 39.7012C35.6486 38.0275 38.4815 34.7015 39.8035 31.4814C40.517 29.7654 41.2305 26.63 41.2305 25.1259C41.2305 24.1513 41.2934 24.0242 42.4686 22.8591C43.161 22.1811 43.8116 21.4397 43.9375 21.2278C44.1473 20.8253 44.1263 20.8253 43.0561 21.1854C41.2724 21.821 41.0206 21.7362 41.902 20.7829C42.5525 20.105 43.3289 18.8763 43.3289 18.5161C43.3289 18.4526 43.0141 18.5585 42.6574 18.7492C42.2797 18.961 41.4403 19.2788 40.8108 19.4694L39.6776 19.8296L38.6494 19.1305C38.0828 18.7492 37.2854 18.3255 36.8657 18.1983C35.7955 17.9017 34.1587 17.9441 33.1935 18.2831C30.5704 19.2364 28.9126 21.6939 29.1015 24.3844Z"
fill="white"
/>
</g>,
{
viewBox: '0 0 60 60',
}
),
[ICONS.FACEBOOK]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 30C0 13.4315 13.4315 0 30 0C46.5685 0 60 13.4315 60 30C60 46.5685 46.5685 60 30 60C13.4315 60 0 46.5685 0 30Z"
fill="#3B5998"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M33.1269 47.6393V31.3178H37.6324L38.2295 25.6933H33.1269L33.1346 22.8781C33.1346 21.4112 33.274 20.6251 35.381 20.6251H38.1976V15H33.6915C28.2789 15 26.3738 17.7285 26.3738 22.317V25.6939H23V31.3184H26.3738V47.6393H33.1269Z"
fill="white"
/>
</g>,
{
viewBox: '0 0 60 60',
}
),
[ICONS.REDDIT]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M30 60C46.5685 60 60 46.5685 60 30C60 13.4315 46.5685 0 30 0C13.4315 0 0 13.4315 0 30C0 46.5685 13.4315 60 30 60Z"
fill="#FF5700"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M52 29.6094C52 26.8656 49.7581 24.6331 47.0017 24.6331C45.7411 24.6331 44.5908 25.1045 43.7108 25.8741C40.5791 23.8712 36.4389 22.5679 31.854 22.3398L34.2649 14.753L40.843 16.2952C40.9167 18.5053 42.7408 20.2824 44.9793 20.2824C47.2636 20.2824 49.1224 18.4323 49.1224 16.1575C49.1224 13.8827 47.2632 12.0326 44.9793 12.0326C43.3199 12.0326 41.8896 13.0109 41.228 14.4159L33.8364 12.6845C33.3468 12.5702 32.8494 12.8509 32.6994 13.3286L29.8452 22.3101C24.9638 22.4055 20.5352 23.718 17.2164 25.8084C16.3462 25.0768 15.2228 24.6331 13.9983 24.6331C11.2419 24.6336 9 26.8656 9 29.6094C9 31.3563 9.91082 32.8922 11.2819 33.7795C11.2121 34.2251 11.1744 34.6766 11.1744 35.1334C11.1744 42.2094 19.8037 47.9664 30.412 47.9664C41.0194 47.9664 49.6497 42.2094 49.6497 35.1334C49.6497 34.7097 49.6174 34.2908 49.5573 33.8763C51.0159 33.0084 52 31.4235 52 29.6094ZM44.9792 13.9503C46.2022 13.9503 47.1971 14.9413 47.1971 16.159C47.1971 17.3766 46.2022 18.3671 44.9792 18.3671C43.7556 18.3671 42.7607 17.3766 42.7607 16.159C42.7607 14.9413 43.7556 13.9503 44.9792 13.9503ZM10.9253 29.6094C10.9253 27.9228 12.3037 26.5499 13.9978 26.5499C14.57 26.5499 15.1046 26.71 15.5644 26.9829C13.8498 28.3699 12.5666 30.002 11.843 31.786C11.2766 31.2309 10.9253 30.4608 10.9253 29.6094ZM47.7244 35.1344C47.7244 41.1527 39.957 46.0502 30.412 46.0502C20.8655 46.0502 13.0996 41.1532 13.0996 35.1344C13.0996 34.9223 13.1113 34.7131 13.1299 34.5044C13.1881 33.8647 13.3366 33.2391 13.5628 32.6329C14.1497 31.0615 15.2755 29.6196 16.8132 28.3902C17.3053 27.9967 17.8384 27.625 18.4091 27.2781C21.5242 25.3852 25.7548 24.2177 30.412 24.2177C35.1366 24.2177 39.4244 25.4183 42.5497 27.3599C43.1219 27.7145 43.6536 28.0949 44.1432 28.4973C45.6198 29.7081 46.6992 31.1199 47.2685 32.6548C47.4928 33.2629 47.6413 33.889 47.697 34.5302C47.7141 34.7311 47.7244 34.9315 47.7244 35.1344ZM49.03 31.9003C48.3269 30.0979 47.0467 28.4492 45.333 27.0447C45.8138 26.7328 46.3865 26.5499 47.0022 26.5499C48.6968 26.5499 50.0752 27.9223 50.0752 29.6094C50.0742 30.5216 49.6687 31.3399 49.03 31.9003Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M27.0214 32.827C27.0214 31.2109 25.705 29.855 24.0813 29.855C22.458 29.855 21.0967 31.2109 21.0967 32.827C21.0967 34.4426 22.4585 35.7547 24.0813 35.7547C25.705 35.7527 27.0214 34.4426 27.0214 32.827Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M36.9632 29.8541C35.34 29.8541 33.9742 31.2094 33.9742 32.8255C33.9742 34.4421 35.34 35.7532 36.9632 35.7532C38.5869 35.7532 39.9043 34.4431 39.9043 32.8255C39.9033 31.2084 38.5869 29.8541 36.9632 29.8541Z"
fill="white"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M36.1325 39.9224C35.0434 41.0053 33.2095 41.5312 30.5225 41.5312C30.5142 41.5312 30.5068 41.5336 30.499 41.5336C30.4907 41.5336 30.4839 41.5312 30.4761 41.5312C27.7886 41.5312 25.9542 41.0053 24.8665 39.9224C24.4908 39.5478 23.8809 39.5478 23.5052 39.9224C23.1289 40.2974 23.1289 40.9041 23.5052 41.2772C24.9716 42.7377 27.252 43.4484 30.4761 43.4484C30.4844 43.4484 30.4912 43.4455 30.499 43.4455C30.5068 43.4455 30.5142 43.4484 30.5225 43.4484C33.746 43.4484 36.027 42.7377 37.4948 41.2782C37.8716 40.9031 37.8716 40.297 37.4958 39.9233C37.1191 39.5487 36.5093 39.5487 36.1325 39.9224Z"
fill="white"
/>
</g>,
{
viewBox: '0 0 60 60',
}
),
[ICONS.TELEGRAM]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M30 60C46.5685 60 60 46.5685 60 30C60 13.4315 46.5685 0 30 0C13.4315 0 0 13.4315 0 30C0 46.5685 13.4315 60 30 60Z"
fill="url(#paint0_linear)"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M24.5 43.75C23.5281 43.75 23.6933 43.383 23.3581 42.4576L20.5 33.0515L42.5 20"
fill="#C8DAEA"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M24.5 43.75C25.25 43.75 25.5814 43.407 26 43L30 39.1105L25.0105 36.1017"
fill="#A9C9DD"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M25.01 36.1025L37.1 45.0347C38.4796 45.796 39.4753 45.4018 39.819 43.7539L44.7402 20.5631C45.2441 18.5431 43.9702 17.6269 42.6504 18.2261L13.7529 29.3688C11.7804 30.16 11.7919 31.2605 13.3933 31.7508L20.8091 34.0654L37.9773 23.2341C38.7878 22.7427 39.5317 23.0069 38.9211 23.5487"
fill="url(#paint1_linear)"
/>
<defs>
<linearGradient id="paint0_linear" x1="22.503" y1="2.502" x2="7.503" y2="37.5" gradientUnits="userSpaceOnUse">
<stop stopColor="#37AEE2" />
<stop offset="1" stopColor="#1E96C8" />
</linearGradient>
<linearGradient
id="paint1_linear"
x1="26.2445"
y1="31.8428"
x2="29.4499"
y2="42.2115"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#EFF7FC" />
<stop offset="1" stopColor="white" />
</linearGradient>
</defs>
</g>,
{
viewBox: '0 0 60 60',
}
),
[ICONS.LINKEDIN]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 30C0 13.4315 13.4315 0 30 0C46.5685 0 60 13.4315 60 30C60 46.5685 46.5685 60 30 60C13.4315 60 0 46.5685 0 30Z"
fill="#0077B5"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M21.6484 18.5283C21.6042 16.5255 20.1721 15 17.8462 15C15.5205 15 14 16.5255 14 18.5283C14 20.4897 15.4756 22.0592 17.7581 22.0592H17.8015C20.1721 22.0592 21.6484 20.4897 21.6484 18.5283ZM21.2007 24.8473H14.4021V45.2744H21.2007V24.8473ZM37.8914 24.3677C42.3652 24.3677 45.7192 27.2878 45.7192 33.5621L45.719 45.2745H38.9207V34.3459C38.9207 31.601 37.9368 29.7278 35.4756 29.7278C33.5974 29.7278 32.4785 30.9906 31.9873 32.2102C31.8074 32.6473 31.7634 33.2563 31.7634 33.8668V45.275H24.9639C24.9639 45.275 25.0535 26.7646 24.9639 24.8479H31.7634V27.7412C32.6658 26.3503 34.2817 24.3677 37.8914 24.3677Z"
fill="white"
/>
</g>,
{
viewBox: '0 0 60 60',
}
),
[ICONS.EMBED]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 30C0 13.4315 13.4315 0 30 0C46.5685 0 60 13.4315 60 30C60 46.5685 46.5685 60 30 60C13.4315 60 0 46.5685 0 30Z"
fill="#eee"
/>
<g transform="scale(1.2)">
<polyline points="15 18 9 12 15 6" stroke="black" transform="translate(6,12)" strokeWidth="2" />
<polyline points="9 18 15 12 9 6" stroke="black" transform="translate(20,12)" strokeWidth="2" />
</g>
</g>,
{
viewBox: '0 0 60 60',
}
),
[ICONS.MORE]: buildIcon(
<g>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M0 30C0 13.4315 13.4315 0 30 0C46.5685 0 60 13.4315 60 30C60 46.5685 46.5685 60 30 60C13.4315 60 0 46.5685 0 30Z"
fill="#eee"
/>
<circle cx="20" cy="30" r="2" stroke="black" fill="black" />
<circle cx="30" cy="30" r="2" stroke="black" fill="black" />
<circle cx="40" cy="30" r="2" stroke="black" fill="black" />
</g>,
{
viewBox: '0 0 60 60',
}
),
};

View file

@ -39,25 +39,26 @@ export default function EmbedTextArea(props: Props) {
}
return (
<fieldset-section>
<div className="section">
<FormField
type="textarea"
className="form-field--copyable"
label={label}
value={embedText || ''}
ref={input}
helper={
onFocus={onFocus}
/>
<div className="section__actions">
<Button
icon={ICONS.COPY}
button="link"
button="secondary"
label={__('Copy')}
onClick={() => {
copyToClipboard();
}}
/>
}
onFocus={onFocus}
/>
</fieldset-section>
</div>
</div>
);
}

View file

@ -48,7 +48,7 @@ function FileActions(props: Props) {
if (claim.meta.reposted > 0) {
repostLabel = (
<Fragment>
{repostLabel}{' '}
{repostLabel}
<Button
button="alt"
label={__('(%count%)', { count: claim.meta.reposted })}

View file

@ -6,19 +6,18 @@ import Button from 'component/button';
type Props = {
uri: string,
isChannel: boolean,
doOpenModal: (id: string, {}) => void,
};
export default function ShareButton(props: Props) {
const { uri, doOpenModal, isChannel = false } = props;
const { uri, doOpenModal } = props;
return (
<Button
button="alt"
icon={ICONS.SHARE}
label={__('Share')}
onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, webShareable: true, isChannel })}
onClick={() => doOpenModal(MODALS.SOCIAL_SHARE, { uri, webShareable: true })}
/>
);
}

View file

@ -4,32 +4,44 @@ import React from 'react';
import Button from 'component/button';
import CopyableText from 'component/copyableText';
import EmbedTextArea from 'component/embedTextArea';
import { generateDirectUrl } from 'util/lbrytv';
const IOS = !!navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform);
type Props = {
claim: Claim,
webShareable: boolean,
isChannel: boolean,
referralCode: string,
user: any,
};
class SocialShare extends React.PureComponent<Props> {
static defaultProps = {
isChannel: false,
};
type State = {
showEmbed: boolean,
showExtra: boolean,
};
class SocialShare extends React.PureComponent<Props, State> {
constructor(props: Props) {
super(props);
this.state = {
showEmbed: false,
showExtra: false,
};
this.input = undefined;
}
input: ?HTMLInputElement;
render() {
const { claim, isChannel, referralCode, user } = this.props;
const { canonical_url: canonicalUrl, permanent_url: permanentUrl } = claim;
const { webShareable } = this.props;
const { claim, referralCode, user, webShareable } = this.props;
const { showEmbed, showExtra } = this.state;
if (!claim) {
return null;
}
const { canonical_url: canonicalUrl, permanent_url: permanentUrl, name, claim_id: claimId } = claim;
const isChannel = claim.value_type === 'channel';
const rewardsApproved = user && user.is_reward_approved;
const OPEN_URL = 'https://open.lbry.com/';
const lbryUrl = canonicalUrl ? canonicalUrl.split('lbry://')[1] : permanentUrl.split('lbry://')[1];
@ -37,28 +49,74 @@ class SocialShare extends React.PureComponent<Props> {
const encodedLbryURL: string = `${OPEN_URL}${encodeURIComponent(lbryWebUrl)}`;
const referralParam: string = referralCode && rewardsApproved ? `?r=${referralCode}` : '';
const lbryURL: string = `${OPEN_URL}${lbryWebUrl}${referralParam}`;
const shareOnFb = __('Share on Facebook');
const shareOnTwitter = __('Share On Twitter');
const directUrl = generateDirectUrl(name, claimId);
return (
<React.Fragment>
<CopyableText label={__('LBRY Link')} copyable={lbryURL} noSnackbar />
<div className="">
<Button
icon={ICONS.FACEBOOK}
button="link"
description={shareOnFb}
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`}
/>{' '}
<CopyableText label={__('LBRY Link')} copyable={lbryURL} />
<div className="section__actions">
<Button
className="share"
iconSize={24}
icon={ICONS.TWITTER}
button="link"
description={shareOnTwitter}
href={`https://twitter.com/intent/tweet?text=${encodedLbryURL}`}
/>
<Button
className="share"
iconSize={24}
icon={ICONS.REDDIT}
title={__('Share on Facebook')}
href={`https://reddit.com/submit?url=${encodedLbryURL}`}
/>
{IOS && (
// Only ios client supports share urls
<Button
className="share"
iconSize={24}
icon={ICONS.TELEGRAM}
title={__('Share on Telegram')}
href={`tg://msg_url?url=${encodedLbryURL}&amp;text=text`}
/>
)}
<Button
className="share"
iconSize={24}
icon={ICONS.LINKEDIN}
title={__('Share on LinkedIn')}
href={`https://www.linkedin.com/sharing/share-offsite/?url=${encodedLbryURL}`}
/>
<Button
className="share"
iconSize={24}
icon={ICONS.FACEBOOK}
title={__('Share on Facebook')}
href={`https://facebook.com/sharer/sharer.php?u=${encodedLbryURL}`}
/>
{webShareable && !isChannel && (
<React.Fragment>
<Button
className="share"
iconSize={24}
icon={ICONS.EMBED}
title={__('Embed this content')}
onClick={() => this.setState({ showEmbed: !showEmbed })}
/>
<Button
className="share"
iconSize={24}
icon={ICONS.MORE}
title={__('More actions')}
onClick={() => this.setState({ showExtra: !showExtra })}
/>
</React.Fragment>
)}
</div>
{webShareable && !isChannel && <EmbedTextArea label={__('Embedded')} claim={claim} noSnackbar />}
{showEmbed && <EmbedTextArea label={__('Embedded')} claim={claim} />}
{showExtra && (
<div className="section">
<CopyableText label={__('Direct Link')} copyable={directUrl} />
</div>
)}
</React.Fragment>
);
}

View file

@ -39,6 +39,11 @@ export const TIP = 'Gift';
export const PLAY = 'Play';
export const FACEBOOK = 'Facebook';
export const TWITTER = 'Twitter';
export const TELEGRAM = 'Telegram';
export const REDDIT = 'Reddit';
export const LINKEDIN = 'LinkedIn';
export const EMBED = 'Embed';
export const MORE = 'More';
export const ACCOUNT = 'User';
export const SETTINGS = 'Settings';
export const INVITE = 'Users';

View file

@ -2,20 +2,20 @@
import React from 'react';
import { Modal } from 'modal/modal';
import SocialShare from 'component/socialShare';
import Card from 'component/common/card';
type Props = {
closeModal: () => void,
uri: string,
webShareable: boolean,
isChannel: boolean,
};
class ModalSocialShare extends React.PureComponent<Props> {
render() {
const { closeModal, uri, webShareable, isChannel } = this.props;
const { closeModal, uri, webShareable } = this.props;
return (
<Modal isOpen onAborted={closeModal} onConfirmed={closeModal} title={__('Share')}>
<SocialShare uri={uri} webShareable={webShareable} isChannel={isChannel} />
<Modal isOpen onAborted={closeModal} type="card">
<Card title={__('Share')} actions={<SocialShare uri={uri} webShareable={webShareable} />} />
</Modal>
);
}

View file

@ -198,7 +198,7 @@ function ChannelPage(props: Props) {
)}
<header className="channel-cover">
<div className="channel__quick-actions">
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} isChannel />}
{!channelIsBlocked && !channelIsBlackListed && <ShareButton uri={uri} />}
{!channelIsMine && (
<Button
button="alt"

View file

@ -35,6 +35,7 @@
@import 'component/search';
@import 'component/claim-search';
@import 'component/section';
@import 'component/share';
@import 'component/snack-bar';
@import 'component/spinner';
@import 'component/splash';

View file

@ -0,0 +1,12 @@
.share {
border-radius: 50%;
.icon {
height: 2.5rem;
width: 2.5rem;
stroke: none;
}
&:hover {
box-shadow: var(--card-box-shadow);
}
}

View file

@ -38,5 +38,9 @@ function generateDownloadUrl(claimName, claimId) {
return `/$/download/${claimName}/${claimId}`;
}
function generateDirectUrl(claimName, claimId) {
return `${URL}/$/stream/${claimName}/${claimId}`;
}
// module.exports needed since the web server imports this function
module.exports = { generateStreamUrl, generateEmbedUrl, generateDownloadUrl, CONTINENT_COOKIE };
module.exports = { generateStreamUrl, generateEmbedUrl, generateDownloadUrl, generateDirectUrl, CONTINENT_COOKIE };