// @flow import { DOMAIN, ENABLE_NO_SOURCE_CLAIMS } from 'config'; import { LINKED_COMMENT_QUERY_PARAM, THREAD_COMMENT_QUERY_PARAM } from 'constants/comment'; import React, { useEffect } from 'react'; import { lazyImport } from 'util/lazyImport'; import { Redirect } from 'react-router-dom'; import Spinner from 'component/spinner'; import ChannelPage from 'page/channel'; import Page from 'component/page'; import Button from 'component/button'; import Card from 'component/common/card'; import Yrbl from 'component/yrbl'; import { formatLbryUrlForWeb } from 'util/url'; import { parseURI } from 'util/lbryURI'; import * as COLLECTIONS_CONSTS from 'constants/collections'; import * as MODALS from 'constants/modal_types'; const AbandonedChannelPreview = lazyImport(() => import('component/abandonedChannelPreview' /* webpackChunkName: "abandonedChannelPreview" */) ); const FilePage = lazyImport(() => import('page/file' /* webpackChunkName: "filePage" */)); const LivestreamPage = lazyImport(() => import('page/livestream' /* webpackChunkName: "livestream" */)); const isDev = process.env.NODE_ENV !== 'production'; type Props = { isResolvingUri: boolean, isSubscribed: boolean, uri: string, claim: StreamClaim, location: UrlLocation, blackListedOutpointMap: { [string]: number }, claimIsMine: boolean, claimIsPending: boolean, isLivestream: boolean, collectionId: string, collection: Collection, collectionUrls: Array, isResolvingCollection: boolean, isAuthenticated: boolean, geoRestriction: ?GeoRestriction, homepageFetched: boolean, latestContentPath?: boolean, liveContentPath?: boolean, latestClaimUrl: ?string, fetchLatestClaimForChannel: (uri: string) => void, fetchChannelLiveStatus: (channelId: string) => void, doResolveUri: (uri: string, returnCached?: boolean, resolveReposts?: boolean, options?: any) => void, doBeginPublish: (name: ?string) => void, doFetchItemsInCollection: ({ collectionId: string }) => void, doOpenModal: (string, {}) => void, }; export default function ShowPage(props: Props) { const { isResolvingUri, uri, claim, blackListedOutpointMap, location, claimIsMine, isSubscribed, claimIsPending, isLivestream, collectionId, collection, collectionUrls, isResolvingCollection, isAuthenticated, geoRestriction, homepageFetched, latestContentPath, liveContentPath, latestClaimUrl, fetchLatestClaimForChannel, fetchChannelLiveStatus, doResolveUri, doBeginPublish, doFetchItemsInCollection, doOpenModal, } = props; const { search, pathname, hash } = location; const urlParams = new URLSearchParams(search); const linkedCommentId = urlParams.get(LINKED_COMMENT_QUERY_PARAM); const threadCommentId = urlParams.get(THREAD_COMMENT_QUERY_PARAM); const signingChannel = claim && claim.signing_channel; const canonicalUrl = claim && claim.canonical_url; const claimExists = claim !== null && claim !== undefined; const haventFetchedYet = claim === undefined; const isMine = claim && claim.is_my_output; const claimId = claim && claim.claim_id; const isNewestPath = latestContentPath || liveContentPath; const { contentName, isChannel } = parseURI(uri); // deprecated contentName - use streamName and channelName const isCollection = claim && claim.value_type === 'collection'; const resolvedCollection = collection && collection.id; // not null const showLiveStream = isLivestream && ENABLE_NO_SOURCE_CLAIMS; const isClaimBlackListed = claim && blackListedOutpointMap && Boolean( (signingChannel && blackListedOutpointMap[`${signingChannel.txid}:${signingChannel.nout}`]) || blackListedOutpointMap[`${claim.txid}:${claim.nout}`] ); const shouldResolveUri = (doResolveUri && !isResolvingUri && uri && haventFetchedYet) || (claimExists && !claimIsPending && (!canonicalUrl || (isMine === undefined && isAuthenticated))); useEffect(() => { if (!canonicalUrl && isNewestPath) { doResolveUri(uri); } // only for mount on a latest content page // eslint-disable-next-line react-hooks/exhaustive-deps }, []); useEffect(() => { if (!latestClaimUrl && liveContentPath && claimId) { fetchChannelLiveStatus(claimId); } }, [claimId, fetchChannelLiveStatus, latestClaimUrl, liveContentPath]); useEffect(() => { if (!latestClaimUrl && latestContentPath && canonicalUrl) { fetchLatestClaimForChannel(canonicalUrl); } }, [canonicalUrl, fetchLatestClaimForChannel, latestClaimUrl, latestContentPath]); // changed this from 'isCollection' to resolve strangers' collections. useEffect(() => { if (collectionId && !resolvedCollection) { doFetchItemsInCollection({ collectionId }); } }, [isCollection, resolvedCollection, collectionId, doFetchItemsInCollection]); useEffect(() => { if (canonicalUrl) { const statePos = hash.indexOf('#state') > -1 ? hash.indexOf('#state') : hash.indexOf('&state') > -1 ? hash.indexOf('&state') : undefined; const pageHash = statePos === undefined ? hash : hash.substring(0, statePos); const urlPath = pathname + pageHash; const path = urlPath.slice(1).replace(/:/g, '#'); // parseURI can parse queries and hashes when they are mixed with the uri let queryString, pathHash; try { ({ queryString, pathHash } = parseURI(path)); } catch (e) {} const canonicalUrlPath = '/' + canonicalUrl.replace(/^lbry:\/\//, '').replace(/#/g, ':'); // replaceState will fail if on a different domain (like webcache.googleusercontent.com) const hostname = isDev ? 'localhost' : DOMAIN; let replaceUrl = canonicalUrlPath; if (canonicalUrlPath !== urlPath && hostname === window.location.hostname) { const urlParams = new URLSearchParams(search || queryString); if (urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID)) { const listId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || ''; urlParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId); } if (urlParams.toString()) replaceUrl += `?${urlParams.toString()}`; if (pathHash || (!pathHash && !queryString && pageHash)) replaceUrl += String(pathHash || pageHash); history.replaceState(history.state, '', replaceUrl); } } }, [canonicalUrl, pathname, hash, search]); useEffect(() => { if (shouldResolveUri) { doResolveUri( uri, false, true, isMine === undefined && isAuthenticated ? { include_is_my_output: true, include_purchase_receipt: true } : {} ); } }, [shouldResolveUri, doResolveUri, uri, isMine, isAuthenticated]); // Wait for latest claim fetch if (isNewestPath && latestClaimUrl === undefined) { return (
); } if (isNewestPath && latestClaimUrl) { const params = urlParams.toString() !== '' ? `?${urlParams.toString()}` : ''; return ; } // Don't navigate directly to repost urls // Always redirect to the actual content // Also redirect to channel page (uri) when on a non-existing latest path (live or content) if (claim && (claim.repost_url === uri || (isNewestPath && latestClaimUrl === null))) { const newUrl = formatLbryUrlForWeb(canonicalUrl); return ; } let urlForCollectionZero; if (claim && isCollection && collectionUrls && collectionUrls.length) { urlForCollectionZero = collectionUrls && collectionUrls[0]; const claimId = claim.claim_id; urlParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, claimId); const newUrl = formatLbryUrlForWeb(`${urlForCollectionZero}?${urlParams.toString()}`); return ; } if (!claim || !claim.name) { const maybeIsCategoryPage = pathname.startsWith('/$/'); const waitingForCategory = maybeIsCategoryPage && !homepageFetched; return ( {(haventFetchedYet || shouldResolveUri || // covers the initial mount case where we haven't run doResolveUri, so 'isResolvingUri' is not true yet. isResolvingUri || isResolvingCollection || // added for collection (isCollection && !urlForCollectionZero)) && ( // added for collection - make sure we accept urls = []
)} {!isResolvingUri && !isSubscribed && !shouldResolveUri && !waitingForCategory && (
) } /> )} {!isResolvingUri && isSubscribed && claim === null && ( )}
); } if (geoRestriction && !claimIsMine) { return (
); } if (claim.name.length && claim.name[0] === '@') { return ; } if (isClaimBlackListed && !claimIsMine) { return (