parent
68a4697c7d
commit
68ceb7f440
8 changed files with 78 additions and 32 deletions
2
flow-typed/lbryURI.js
vendored
2
flow-typed/lbryURI.js
vendored
|
@ -13,6 +13,8 @@ declare type LbryUrlObj = {
|
||||||
primaryBidPosition?: number,
|
primaryBidPosition?: number,
|
||||||
secondaryBidPosition?: number,
|
secondaryBidPosition?: number,
|
||||||
startTime?: number,
|
startTime?: number,
|
||||||
|
queryString?: string,
|
||||||
|
pathHash?: string,
|
||||||
|
|
||||||
// Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url
|
// Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url
|
||||||
claimName?: string,
|
claimName?: string,
|
||||||
|
|
|
@ -5,7 +5,7 @@ import { lazyImport } from 'util/lazyImport';
|
||||||
import { tusUnlockAndNotify, tusHandleTabUpdates } from 'util/tus';
|
import { tusUnlockAndNotify, tusHandleTabUpdates } from 'util/tus';
|
||||||
import analytics from 'analytics';
|
import analytics from 'analytics';
|
||||||
import { setSearchUserId } from 'redux/actions/search';
|
import { setSearchUserId } from 'redux/actions/search';
|
||||||
import { normalizeURI } from 'util/lbryURI';
|
import { parseURI, buildURI } from 'util/lbryURI';
|
||||||
import { generateGoogleCacheUrl } from 'util/url';
|
import { generateGoogleCacheUrl } from 'util/url';
|
||||||
import Router from 'component/router/index';
|
import Router from 'component/router/index';
|
||||||
import ModalRouter from 'modal/modalRouter';
|
import ModalRouter from 'modal/modalRouter';
|
||||||
|
@ -187,7 +187,10 @@ function App(props: Props) {
|
||||||
|
|
||||||
let uri;
|
let uri;
|
||||||
try {
|
try {
|
||||||
uri = normalizeURI(path);
|
// here queryString and startTime are "removed" from the buildURI process
|
||||||
|
// to build only the uri itself
|
||||||
|
const { queryString, startTime, ...parsedUri } = parseURI(path);
|
||||||
|
uri = buildURI({ ...parsedUri });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const match = path.match(/[#/:]/);
|
const match = path.match(/[#/:]/);
|
||||||
|
|
||||||
|
|
|
@ -64,7 +64,7 @@ function Page(props: Props) {
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
location: { pathname },
|
location: { pathname, hash },
|
||||||
} = useHistory();
|
} = useHistory();
|
||||||
|
|
||||||
const theaterMode = renderMode === 'video' || renderMode === 'audio' ? videoTheaterMode : false;
|
const theaterMode = renderMode === 'video' || renderMode === 'audio' ? videoTheaterMode : false;
|
||||||
|
@ -73,11 +73,10 @@ function Page(props: Props) {
|
||||||
const isLandscapeRotated = useIsMobileLandscape();
|
const isLandscapeRotated = useIsMobileLandscape();
|
||||||
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', false);
|
const [sidebarOpen, setSidebarOpen] = usePersistedState('sidebar', false);
|
||||||
|
|
||||||
const url = pathname.slice(1).replace(/:/g, '#');
|
const urlPath = `lbry://${(pathname + hash).slice(1).replace(/:/g, '#')}`;
|
||||||
let isOnFilePage = false;
|
let isOnFilePage = false;
|
||||||
try {
|
try {
|
||||||
const url = pathname.slice(1).replace(/:/g, '#');
|
const { isChannel } = parseURI(urlPath);
|
||||||
const { isChannel } = parseURI(url);
|
|
||||||
|
|
||||||
if (!isChannel) isOnFilePage = true;
|
if (!isChannel) isOnFilePage = true;
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
@ -93,7 +92,7 @@ function Page(props: Props) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Wallpaper uri={url} />
|
<Wallpaper uri={urlPath} />
|
||||||
{!noHeader && (
|
{!noHeader && (
|
||||||
<Header
|
<Header
|
||||||
authHeader={authPage}
|
authHeader={authPage}
|
||||||
|
|
|
@ -31,18 +31,26 @@ import { doClaimEligiblePurchaseRewards } from 'redux/actions/rewards';
|
||||||
import { selectDaemonSettings, selectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
import { selectDaemonSettings, selectClientSetting, selectHomepageData } from 'redux/selectors/settings';
|
||||||
import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings';
|
import { toggleVideoTheaterMode, toggleAutoplayNext, doSetClientSetting } from 'redux/actions/settings';
|
||||||
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
import { selectUserVerifiedEmail, selectUser } from 'redux/selectors/user';
|
||||||
|
import { parseURI } from 'util/lbryURI';
|
||||||
import { doToast } from 'redux/actions/notifications';
|
import { doToast } from 'redux/actions/notifications';
|
||||||
|
|
||||||
const select = (state, props) => {
|
const select = (state, props) => {
|
||||||
const { search } = props.location;
|
const { search, pathname, hash } = props.location;
|
||||||
const urlParams = new URLSearchParams(search);
|
const urlParams = new URLSearchParams(search);
|
||||||
const autoplay = urlParams.get('autoplay');
|
const autoplay = urlParams.get('autoplay');
|
||||||
const uri = props.uri;
|
const uri = props.uri;
|
||||||
|
|
||||||
|
const urlPath = `lbry://${(pathname + hash).slice(1)}`;
|
||||||
|
let startTime;
|
||||||
|
try {
|
||||||
|
({ startTime } = parseURI(urlPath));
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
const claim = selectClaimForUri(state, uri);
|
const claim = selectClaimForUri(state, uri);
|
||||||
|
|
||||||
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796)
|
// TODO: eventually this should be received from DB and not local state (https://github.com/lbryio/lbry-desktop/issues/6796)
|
||||||
const position = urlParams.get('t') !== null ? urlParams.get('t') : selectContentPositionForUri(state, uri);
|
const position =
|
||||||
|
startTime || (urlParams.get('t') !== null ? urlParams.get('t') : selectContentPositionForUri(state, uri));
|
||||||
const userId = selectUser(state) && selectUser(state).id;
|
const userId = selectUser(state) && selectUser(state).id;
|
||||||
const internalFeature = selectUser(state) && selectUser(state).internal_feature;
|
const internalFeature = selectUser(state) && selectUser(state).internal_feature;
|
||||||
const playingUri = selectPlayingUri(state);
|
const playingUri = selectPlayingUri(state);
|
||||||
|
|
|
@ -137,29 +137,37 @@ export default function ShowPage(props: Props) {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (canonicalUrl) {
|
if (canonicalUrl) {
|
||||||
const urlPath = pathname + hash;
|
const statePos =
|
||||||
const fullParams =
|
hash.indexOf('#state') > -1
|
||||||
urlPath.indexOf('?') > 0 ? urlPath.substring(urlPath.indexOf('?')) : search.length > 0 ? search : '';
|
? hash.indexOf('#state')
|
||||||
const canonicalUrlPath = '/' + canonicalUrl.replace(/^lbry:\/\//, '').replace(/#/g, ':') + fullParams;
|
: 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)
|
// replaceState will fail if on a different domain (like webcache.googleusercontent.com)
|
||||||
const hostname = isDev ? 'localhost' : DOMAIN;
|
const hostname = isDev ? 'localhost' : DOMAIN;
|
||||||
|
|
||||||
if (canonicalUrlPath !== pathname && hostname === window.location.hostname && fullParams !== search) {
|
|
||||||
const urlParams = new URLSearchParams(search);
|
|
||||||
let replaceUrl = canonicalUrlPath;
|
let replaceUrl = canonicalUrlPath;
|
||||||
|
if (canonicalUrlPath !== urlPath && hostname === window.location.hostname) {
|
||||||
|
const urlParams = new URLSearchParams(search || queryString);
|
||||||
if (urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID)) {
|
if (urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID)) {
|
||||||
const listId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || '';
|
const listId = urlParams.get(COLLECTIONS_CONSTS.COLLECTION_ID) || '';
|
||||||
urlParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId);
|
urlParams.set(COLLECTIONS_CONSTS.COLLECTION_ID, listId);
|
||||||
replaceUrl += `?${urlParams.toString()}`;
|
|
||||||
}
|
|
||||||
history.replaceState(history.state, '', replaceUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const windowHref = window.location.href;
|
if (urlParams.toString()) replaceUrl += `?${urlParams.toString()}`;
|
||||||
const noUrlParams = search.length === 0;
|
if (pathHash || (!pathHash && !queryString && pageHash)) replaceUrl += String(pathHash || pageHash);
|
||||||
if (windowHref.includes('?') && noUrlParams) {
|
|
||||||
history.replaceState(history.state, '', windowHref.substring(0, windowHref.length - 1));
|
history.replaceState(history.state, '', replaceUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [canonicalUrl, pathname, hash, search]);
|
}, [canonicalUrl, pathname, hash, search]);
|
||||||
|
|
|
@ -103,17 +103,18 @@ export function parseURI(url: string, requireProto: boolean = false): LbryUrlObj
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and process modifier
|
// Validate and process modifier
|
||||||
const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(
|
const [primaryClaimId, primaryClaimSequence, primaryBidPosition, primaryPathHash] = parseURIModifier(
|
||||||
primaryModSeparator,
|
primaryModSeparator,
|
||||||
primaryModValue
|
primaryModValue
|
||||||
);
|
);
|
||||||
const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(
|
const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition, secondaryPathHash] = parseURIModifier(
|
||||||
secondaryModSeparator,
|
secondaryModSeparator,
|
||||||
secondaryModValue
|
secondaryModValue
|
||||||
);
|
);
|
||||||
const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
|
const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
|
||||||
const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
|
const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
|
||||||
const channelClaimId = includesChannel && primaryClaimId;
|
const channelClaimId = includesChannel && primaryClaimId;
|
||||||
|
const pathHash = primaryPathHash || secondaryPathHash;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isChannel,
|
isChannel,
|
||||||
|
@ -127,6 +128,7 @@ export function parseURI(url: string, requireProto: boolean = false): LbryUrlObj
|
||||||
...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
|
...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
|
||||||
...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
|
...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
|
||||||
...(startTime ? { startTime: parseInt(startTime, 10) } : {}),
|
...(startTime ? { startTime: parseInt(startTime, 10) } : {}),
|
||||||
|
...(pathHash ? { pathHash } : {}),
|
||||||
|
|
||||||
// The values below should not be used for new uses of parseURI
|
// The values below should not be used for new uses of parseURI
|
||||||
// They will not work properly with canonical_urls
|
// They will not work properly with canonical_urls
|
||||||
|
@ -141,6 +143,7 @@ function parseURIModifier(modSeperator: ?string, modValue: ?string) {
|
||||||
let claimId;
|
let claimId;
|
||||||
let claimSequence;
|
let claimSequence;
|
||||||
let bidPosition;
|
let bidPosition;
|
||||||
|
let pathHash;
|
||||||
|
|
||||||
if (modSeperator) {
|
if (modSeperator) {
|
||||||
if (!modValue) {
|
if (!modValue) {
|
||||||
|
@ -157,8 +160,18 @@ function parseURIModifier(modSeperator: ?string, modValue: ?string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) {
|
if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) {
|
||||||
|
const hashIndex = claimId.indexOf('#');
|
||||||
|
|
||||||
|
if (hashIndex >= 0) {
|
||||||
|
pathHash = claimId.substring(hashIndex);
|
||||||
|
claimId = claimId.substring(0, hashIndex);
|
||||||
|
// As a pre-caution to catch future odd urls coming in,
|
||||||
|
// validate the new claimId length and characters again after stripping off the pathHash
|
||||||
|
[claimId] = parseURIModifier(modSeperator, claimId);
|
||||||
|
} else {
|
||||||
throw new Error(__(`Invalid claim ID %claimId%.`, { claimId }));
|
throw new Error(__(`Invalid claim ID %claimId%.`, { claimId }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
||||||
throw new Error(__('Claim sequence must be a number.'));
|
throw new Error(__('Claim sequence must be a number.'));
|
||||||
|
@ -168,7 +181,7 @@ function parseURIModifier(modSeperator: ?string, modValue: ?string) {
|
||||||
throw new Error(__('Bid position must be a number.'));
|
throw new Error(__('Bid position must be a number.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [claimId, claimSequence, bidPosition];
|
return [claimId, claimSequence, bidPosition, pathHash];
|
||||||
}
|
}
|
||||||
|
|
||||||
const errorHistory = [];
|
const errorHistory = [];
|
||||||
|
|
|
@ -205,7 +205,7 @@ async function buildClaimOgMetadata(uri, claim, overrideOptions = {}, referrerQu
|
||||||
const title = overrideOptions.title || claimTitle;
|
const title = overrideOptions.title || claimTitle;
|
||||||
const description = overrideOptions.description || claimDescription;
|
const description = overrideOptions.description || claimDescription;
|
||||||
const cleanDescription = removeMd(description);
|
const cleanDescription = removeMd(description);
|
||||||
const claimPath = `${URL}/${claim.canonical_url.replace('lbry://', '').replace('#', ':')}`;
|
const claimPath = `${URL}/${claim.canonical_url.replace('lbry://', '').replace(/#/g, ':')}`;
|
||||||
|
|
||||||
let head = '';
|
let head = '';
|
||||||
|
|
||||||
|
|
|
@ -108,17 +108,18 @@ function parseURI(url, requireProto = false) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and process modifier
|
// Validate and process modifier
|
||||||
const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(
|
const [primaryClaimId, primaryClaimSequence, primaryBidPosition, primaryPathHash] = parseURIModifier(
|
||||||
primaryModSeparator,
|
primaryModSeparator,
|
||||||
primaryModValue
|
primaryModValue
|
||||||
);
|
);
|
||||||
const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(
|
const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition, secondaryPathHash] = parseURIModifier(
|
||||||
secondaryModSeparator,
|
secondaryModSeparator,
|
||||||
secondaryModValue
|
secondaryModValue
|
||||||
);
|
);
|
||||||
const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
|
const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
|
||||||
const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
|
const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
|
||||||
const channelClaimId = includesChannel && primaryClaimId;
|
const channelClaimId = includesChannel && primaryClaimId;
|
||||||
|
const pathHash = primaryPathHash || secondaryPathHash;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isChannel,
|
isChannel,
|
||||||
|
@ -132,6 +133,7 @@ function parseURI(url, requireProto = false) {
|
||||||
...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
|
...(primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}),
|
||||||
...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
|
...(secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}),
|
||||||
...(startTime ? { startTime: parseInt(startTime, 10) } : {}),
|
...(startTime ? { startTime: parseInt(startTime, 10) } : {}),
|
||||||
|
...(pathHash ? { pathHash } : {}),
|
||||||
|
|
||||||
// The values below should not be used for new uses of parseURI
|
// The values below should not be used for new uses of parseURI
|
||||||
// They will not work properly with canonical_urls
|
// They will not work properly with canonical_urls
|
||||||
|
@ -146,6 +148,7 @@ function parseURIModifier(modSeperator, modValue) {
|
||||||
let claimId;
|
let claimId;
|
||||||
let claimSequence;
|
let claimSequence;
|
||||||
let bidPosition;
|
let bidPosition;
|
||||||
|
let pathHash;
|
||||||
|
|
||||||
if (modSeperator) {
|
if (modSeperator) {
|
||||||
if (!modValue) {
|
if (!modValue) {
|
||||||
|
@ -162,7 +165,17 @@ function parseURIModifier(modSeperator, modValue) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) {
|
if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) {
|
||||||
throw new Error(__(`Invalid claim ID ${claimId}.`, { claimId }));
|
const hashIndex = claimId.indexOf('#');
|
||||||
|
|
||||||
|
if (hashIndex >= 0) {
|
||||||
|
pathHash = claimId.substring(hashIndex);
|
||||||
|
claimId = claimId.substring(0, hashIndex);
|
||||||
|
// As a pre-caution to catch future odd urls coming in,
|
||||||
|
// validate the new claimId length and characters again after stripping off the pathHash
|
||||||
|
[claimId] = parseURIModifier(modSeperator, claimId);
|
||||||
|
} else {
|
||||||
|
throw new Error(__(`Invalid claim ID %claimId%.`, { claimId }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
||||||
|
@ -173,7 +186,7 @@ function parseURIModifier(modSeperator, modValue) {
|
||||||
throw new Error(__('Bid position must be a number.'));
|
throw new Error(__('Bid position must be a number.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
return [claimId, claimSequence, bidPosition];
|
return [claimId, claimSequence, bidPosition, pathHash];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue