diff --git a/dist/bundle.es.js b/dist/bundle.es.js index d9c16c3..5ebb021 100644 --- a/dist/bundle.es.js +++ b/dist/bundle.es.js @@ -1168,7 +1168,9 @@ const separateQuerystring = new RegExp(queryStringBreaker); * messages for invalid names. * * Returns a dictionary with keys: + * - path (string) + * - isValid (boolean) * - isChannel (boolean) * - streamName (string, if present) * - streamClaimId (string, if present) @@ -1200,18 +1202,21 @@ function parseURI(URL, requireProto = false) { const [proto, ...rest] = regexMatch.slice(1).map(match => match || null); const path = rest.join(''); const [streamNameOrChannelName, primaryModSeparator, primaryModValue, pathSep, possibleStreamName, secondaryModSeparator, secondaryModValue] = rest; - + let isValid = true; // Validate protocol if (requireProto && !proto) { - throw new Error(__('LBRY URLs must include a protocol prefix (lbry://).')); + isValid = false; + console.log('LBRY URLs must include a protocol prefix (lbry://).'); } // Validate and process name if (!streamNameOrChannelName) { - throw new Error(__('URL does not include name.')); + isValid = false; + console.log('URL does not include name.'); } rest.forEach(urlPiece => { + isValid = false; if (urlPiece && urlPiece.includes(' ')) { console.error('URL can not include a space'); } @@ -1223,24 +1228,30 @@ function parseURI(URL, requireProto = false) { if (includesChannel) { if (!channelName) { - throw new Error(__('No channel name after @.')); + isValid = false; + console.log('No channel name after @.'); } if (channelName.length < channelNameMinLength) { - throw new Error(__(`Channel names must be at least %channelNameMinLength% characters.`, { + isValid = false; + console.log(`Channel names must be at least %channelNameMinLength% characters.`, { channelNameMinLength - })); + }); } } // Validate and process modifier - const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(primaryModSeparator, primaryModValue); - const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(secondaryModSeparator, secondaryModValue); + const [primaryClaimId, primaryClaimSequence, primaryBidPosition, primaryValid] = parseURIModifier(primaryModSeparator, primaryModValue); + const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition, secondaryValid] = parseURIModifier(secondaryModSeparator, secondaryModValue); + + if (primaryModSeparator && !primaryValid || secondaryModSeparator && !secondaryValid) isValid = false; + const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName; const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId; const channelClaimId = includesChannel && primaryClaimId; return _extends({ + isValid, isChannel, path }, streamName ? { streamName } : {}, streamClaimId ? { streamClaimId } : {}, channelName ? { channelName } : {}, channelClaimId ? { channelClaimId } : {}, primaryClaimSequence ? { primaryClaimSequence: parseInt(primaryClaimSequence, 10) } : {}, secondaryClaimSequence ? { secondaryClaimSequence: parseInt(secondaryClaimSequence, 10) } : {}, primaryBidPosition ? { primaryBidPosition: parseInt(primaryBidPosition, 10) } : {}, secondaryBidPosition ? { secondaryBidPosition: parseInt(secondaryBidPosition, 10) } : {}, { @@ -1256,10 +1267,12 @@ function parseURIModifier(modSeperator, modValue) { let claimId; let claimSequence; let bidPosition; + let isValid = true; if (modSeperator) { if (!modValue) { - throw new Error(__(`No modifier provided after separator %modSeperator%.`, { modSeperator })); + isValid = false; + console.log(`No modifier provided after separator %modSeperator%.`, { modSeperator }); } if (modSeperator === '#') { @@ -1272,18 +1285,21 @@ function parseURIModifier(modSeperator, modValue) { } if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) { - throw new Error(__(`Invalid claim ID %claimId%.`, { claimId })); + isValid = false; + console.log(`Invalid claim ID %claimId%.`, { claimId }); } if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) { - throw new Error(__('Claim sequence must be a number.')); + isValid = false; + console.log('Claim sequence must be a number.'); } if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) { - throw new Error(__('Bid position must be a number.')); + isValid = false; + console.log('Bid position must be a number.'); } - return [claimId, claimSequence, bidPosition]; + return [claimId, claimSequence, bidPosition, isValid]; } /** @@ -1324,6 +1340,7 @@ function buildURI(UrlObj, includeProto = true, protoDefault = 'lbry://') { /* Takes a parseable LBRY URL and converts it to standard, canonical format */ function normalizeURI(URL) { const { + isValid, streamName, streamClaimId, channelName, @@ -1334,7 +1351,7 @@ function normalizeURI(URL) { secondaryBidPosition } = parseURI(URL); - return buildURI({ + return isValid && buildURI({ streamName, streamClaimId, channelName, @@ -1347,13 +1364,9 @@ function normalizeURI(URL) { } function isURIValid(URL) { - try { - parseURI(normalizeURI(URL)); - } catch (error) { - return false; - } - - return true; + let isValid; + ({ isValid } = parseURI(normalizeURI(URL))); + return isValid; } function isNameValid(claimName) { @@ -1361,14 +1374,9 @@ function isNameValid(claimName) { } function isURIClaimable(URL) { - let parts; - try { - parts = parseURI(normalizeURI(URL)); - } catch (error) { - return false; - } + const { isValid, parts } = parseURI(normalizeURI(URL)); - return parts && parts.streamName && !parts.streamClaimId && !parts.isChannel; + return isValid && parts && parts.streamName && !parts.streamClaimId && !parts.isChannel; } function convertToShareLink(URL) { @@ -1445,8 +1453,8 @@ const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selec } let searchSuggestions = []; - try { - const uri = normalizeURI(query); + const uri = normalizeURI(query); + if (uri) { const { channelName, streamName, isChannel } = parseURI(uri); searchSuggestions.push({ value: query, @@ -1456,7 +1464,7 @@ const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selec shorthand: isChannel ? channelName : streamName, type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE }); - } catch (e) { + } else { searchSuggestions.push({ value: query, type: SEARCH_TYPES.SEARCH @@ -1472,16 +1480,16 @@ const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selec if (apiSuggestions.length) { searchSuggestions = searchSuggestions.concat(apiSuggestions.filter(suggestion => suggestion !== query).map(suggestion => { // determine if it's a channel - try { - const uri = normalizeURI(suggestion); - const { channelName, streamName, isChannel } = parseURI(uri); + const uri = normalizeURI(suggestion); + if (uri) { + const { isValid, channelName, streamName, isChannel } = parseURI(uri); return { value: uri, shorthand: isChannel ? channelName : streamName, type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE }; - } catch (e) { + } else { // search result includes some character that isn't valid in claim names return { value: suggestion, @@ -2031,10 +2039,10 @@ const selectPendingClaims = reselect.createSelector(selectState$2, state => Obje const makeSelectClaimIsPending = uri => reselect.createSelector(selectPendingById, pendingById => { let claimId; - try { - const { isChannel, channelClaimId, streamClaimId } = parseURI(uri); + const { isValid, isChannel, channelClaimId, streamClaimId } = parseURI(uri); + if (isValid) { claimId = isChannel ? channelClaimId : streamClaimId; - } catch (e) {} + } if (claimId) { return Boolean(pendingById[claimId]); @@ -2051,16 +2059,13 @@ const makeSelectClaimForUri = (uri, returnRepost = true) => reselect.createSelec // Check if a claim is pending first // It won't be in claimsByUri because resolving it will return nothing - let valid; let channelClaimId; let streamClaimId; let isChannel; - try { - ({ isChannel, channelClaimId, streamClaimId } = parseURI(uri)); - valid = true; - } catch (e) {} + let isValid; + ({ isValid, isChannel, channelClaimId, streamClaimId } = parseURI(uri)); - if (valid && byUri) { + if (isValid && byUri) { const claimId = isChannel ? channelClaimId : streamClaimId; const pendingClaim = pendingById[claimId]; @@ -2096,18 +2101,12 @@ const selectMyActiveClaims = reselect.createSelector(selectMyClaimsRaw, selectAb const makeSelectClaimIsMine = rawUri => { let uri; - try { - uri = normalizeURI(rawUri); - } catch (e) {} + uri = normalizeURI(rawUri); - return reselect.createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => { - try { - parseURI(uri); - } catch (e) { - return false; - } + return uri && reselect.createSelector(selectClaimsByUri, selectMyActiveClaims, (claims, myClaims) => { + const { isValid } = parseURI(uri); - return claims && claims[uri] && claims[uri].claim_id && myClaims.has(claims[uri].claim_id); + return isValid && claims && claims[uri] && claims[uri].claim_id && myClaims.has(claims[uri].claim_id); }); }; diff --git a/dist/flow-typed/lbryURI.js b/dist/flow-typed/lbryURI.js index 4365da3..7ce393b 100644 --- a/dist/flow-typed/lbryURI.js +++ b/dist/flow-typed/lbryURI.js @@ -1,7 +1,8 @@ // @flow declare type LbryUrlObj = { - // Path and channel will always exist when calling parseURI + // Path, channel, and isValid will always exist when calling parseURI // But they may not exist when code calls buildURI + isValid?: boolean, isChannel?: boolean, path?: string, streamName?: string, diff --git a/flow-typed/lbryURI.js b/flow-typed/lbryURI.js index 4365da3..7ce393b 100644 --- a/flow-typed/lbryURI.js +++ b/flow-typed/lbryURI.js @@ -1,7 +1,8 @@ // @flow declare type LbryUrlObj = { - // Path and channel will always exist when calling parseURI + // Path, channel, and isValid will always exist when calling parseURI // But they may not exist when code calls buildURI + isValid?: boolean, isChannel?: boolean, path?: string, streamName?: string, diff --git a/src/lbryURI.js b/src/lbryURI.js index b63c1f2..0adee65 100644 --- a/src/lbryURI.js +++ b/src/lbryURI.js @@ -17,7 +17,9 @@ const separateQuerystring = new RegExp(queryStringBreaker); * messages for invalid names. * * Returns a dictionary with keys: + * - path (string) + * - isValid (boolean) * - isChannel (boolean) * - streamName (string, if present) * - streamClaimId (string, if present) @@ -60,18 +62,21 @@ export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj secondaryModSeparator, secondaryModValue, ] = rest; - + let isValid = true; // Validate protocol if (requireProto && !proto) { - throw new Error(__('LBRY URLs must include a protocol prefix (lbry://).')); + isValid = false; + console.log('LBRY URLs must include a protocol prefix (lbry://).'); } // Validate and process name if (!streamNameOrChannelName) { - throw new Error(__('URL does not include name.')); + isValid = false; + console.log('URL does not include name.'); } rest.forEach(urlPiece => { + isValid = false; if (urlPiece && urlPiece.includes(' ')) { console.error('URL can not include a space'); } @@ -83,32 +88,39 @@ export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj if (includesChannel) { if (!channelName) { - throw new Error(__('No channel name after @.')); + isValid = false; + console.log('No channel name after @.'); } if (channelName.length < channelNameMinLength) { - throw new Error( - __(`Channel names must be at least %channelNameMinLength% characters.`, { - channelNameMinLength, - }) - ); + isValid = false; + console.log(`Channel names must be at least %channelNameMinLength% characters.`, { + channelNameMinLength, + }); } } // Validate and process modifier - const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier( + const [primaryClaimId, primaryClaimSequence, primaryBidPosition, primaryValid] = parseURIModifier( primaryModSeparator, primaryModValue ); - const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier( - secondaryModSeparator, - secondaryModValue - ); + const [ + secondaryClaimId, + secondaryClaimSequence, + secondaryBidPosition, + secondaryValid, + ] = parseURIModifier(secondaryModSeparator, secondaryModValue); + + if ((primaryModSeparator && !primaryValid) || (secondaryModSeparator && !secondaryValid)) + isValid = false; + const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName; const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId; const channelClaimId = includesChannel && primaryClaimId; return { + isValid, isChannel, path, ...(streamName ? { streamName } : {}), @@ -135,10 +147,12 @@ function parseURIModifier(modSeperator: ?string, modValue: ?string) { let claimId; let claimSequence; let bidPosition; + let isValid = true; if (modSeperator) { if (!modValue) { - throw new Error(__(`No modifier provided after separator %modSeperator%.`, { modSeperator })); + isValid = false; + console.log(`No modifier provided after separator %modSeperator%.`, { modSeperator }); } if (modSeperator === '#') { @@ -151,18 +165,21 @@ function parseURIModifier(modSeperator: ?string, modValue: ?string) { } if (claimId && (claimId.length > claimIdMaxLength || !claimId.match(/^[0-9a-f]+$/))) { - throw new Error(__(`Invalid claim ID %claimId%.`, { claimId })); + isValid = false; + console.log(`Invalid claim ID %claimId%.`, { claimId }); } if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) { - throw new Error(__('Claim sequence must be a number.')); + isValid = false; + console.log('Claim sequence must be a number.'); } if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) { - throw new Error(__('Bid position must be a number.')); + isValid = false; + console.log('Bid position must be a number.'); } - return [claimId, claimSequence, bidPosition]; + return [claimId, claimSequence, bidPosition, isValid]; } /** @@ -240,6 +257,7 @@ export function buildURI( /* Takes a parseable LBRY URL and converts it to standard, canonical format */ export function normalizeURI(URL: string) { const { + isValid, streamName, streamClaimId, channelName, @@ -250,41 +268,35 @@ export function normalizeURI(URL: string) { secondaryBidPosition, } = parseURI(URL); - return buildURI({ - streamName, - streamClaimId, - channelName, - channelClaimId, - primaryClaimSequence, - primaryBidPosition, - secondaryClaimSequence, - secondaryBidPosition, - }); + return ( + isValid && + buildURI({ + streamName, + streamClaimId, + channelName, + channelClaimId, + primaryClaimSequence, + primaryBidPosition, + secondaryClaimSequence, + secondaryBidPosition, + }) + ); } -export function isURIValid(URL: string): boolean { - try { - parseURI(normalizeURI(URL)); - } catch (error) { - return false; - } - - return true; +export function isURIValid(URL: string) { + let isValid; + ({ isValid } = parseURI(normalizeURI(URL))); + return isValid; } export function isNameValid(claimName: string) { return !regexInvalidURI.test(claimName); } -export function isURIClaimable(URL: string) { - let parts; - try { - parts = parseURI(normalizeURI(URL)); - } catch (error) { - return false; - } +export function isURIClaimable(URL: string): boolean { + const { isValid, parts } = parseURI(normalizeURI(URL)); - return parts && parts.streamName && !parts.streamClaimId && !parts.isChannel; + return isValid && parts && parts.streamName && !parts.streamClaimId && !parts.isChannel; } export function convertToShareLink(URL: string) { diff --git a/src/redux/reducers/claims.js b/src/redux/reducers/claims.js index 7ca9745..b6288f4 100644 --- a/src/redux/reducers/claims.js +++ b/src/redux/reducers/claims.js @@ -9,7 +9,7 @@ // - Sean import * as ACTIONS from 'constants/action_types'; -import { buildURI, parseURI } from 'lbryURI'; +import { buildURI } from 'lbryURI'; import { concatClaims } from 'util/claim'; type State = { diff --git a/src/redux/selectors/claims.js b/src/redux/selectors/claims.js index 4db130a..665aff1 100644 --- a/src/redux/selectors/claims.js +++ b/src/redux/selectors/claims.js @@ -86,10 +86,10 @@ export const makeSelectClaimIsPending = (uri: string) => pendingById => { let claimId; - try { - const { isChannel, channelClaimId, streamClaimId } = parseURI(uri); + const { isValid, isChannel, channelClaimId, streamClaimId } = parseURI(uri); + if (isValid) { claimId = isChannel ? channelClaimId : streamClaimId; - } catch (e) {} + } if (claimId) { return Boolean(pendingById[claimId]); @@ -115,16 +115,13 @@ export const makeSelectClaimForUri = (uri: string, returnRepost: boolean = true) // Check if a claim is pending first // It won't be in claimsByUri because resolving it will return nothing - let valid; let channelClaimId; let streamClaimId; let isChannel; - try { - ({ isChannel, channelClaimId, streamClaimId } = parseURI(uri)); - valid = true; - } catch (e) {} + let isValid; + ({ isValid, isChannel, channelClaimId, streamClaimId } = parseURI(uri)); - if (valid && byUri) { + if (isValid && byUri) { const claimId = isChannel ? channelClaimId : streamClaimId; const pendingClaim = pendingById[claimId]; @@ -178,22 +175,25 @@ export const selectMyActiveClaims = createSelector( export const makeSelectClaimIsMine = (rawUri: string) => { let uri; - try { - uri = normalizeURI(rawUri); - } catch (e) {} + uri = normalizeURI(rawUri); - return createSelector( - selectClaimsByUri, - selectMyActiveClaims, - (claims, myClaims) => { - try { - parseURI(uri); - } catch (e) { - return false; + return ( + uri && + createSelector( + selectClaimsByUri, + selectMyActiveClaims, + (claims, myClaims) => { + const { isValid } = parseURI(uri); + + return ( + isValid && + claims && + claims[uri] && + claims[uri].claim_id && + myClaims.has(claims[uri].claim_id) + ); } - - return claims && claims[uri] && claims[uri].claim_id && myClaims.has(claims[uri].claim_id); - } + ) ); }; diff --git a/src/redux/selectors/search.js b/src/redux/selectors/search.js index e3efd11..38837aa 100644 --- a/src/redux/selectors/search.js +++ b/src/redux/selectors/search.js @@ -106,8 +106,8 @@ export const selectSearchSuggestions: Array = createSelector( } let searchSuggestions = []; - try { - const uri = normalizeURI(query); + const uri = normalizeURI(query); + if (uri) { const { channelName, streamName, isChannel } = parseURI(uri); searchSuggestions.push( { @@ -120,7 +120,7 @@ export const selectSearchSuggestions: Array = createSelector( type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE, } ); - } catch (e) { + } else { searchSuggestions.push({ value: query, type: SEARCH_TYPES.SEARCH, @@ -139,16 +139,16 @@ export const selectSearchSuggestions: Array = createSelector( .filter(suggestion => suggestion !== query) .map(suggestion => { // determine if it's a channel - try { - const uri = normalizeURI(suggestion); - const { channelName, streamName, isChannel } = parseURI(uri); + const uri = normalizeURI(suggestion); + if (uri) { + const { isValid, channelName, streamName, isChannel } = parseURI(uri); return { value: uri, shorthand: isChannel ? channelName : streamName, type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE, }; - } catch (e) { + } else { // search result includes some character that isn't valid in claim names return { value: suggestion, @@ -172,21 +172,15 @@ type CustomOptions = { from?: number, related_to?: string, nsfw?: boolean, -} +}; -export const makeSelectQueryWithOptions = ( - customQuery: ?string, - options: CustomOptions, -) => +export const makeSelectQueryWithOptions = (customQuery: ?string, options: CustomOptions) => createSelector( selectSearchValue, selectSearchOptions, (query, defaultOptions) => { const searchOptions = { ...defaultOptions, ...options }; - const queryString = getSearchQueryString( - customQuery || query, - searchOptions, - ); + const queryString = getSearchQueryString(customQuery || query, searchOptions); return queryString; }