Merge pull request #187 from lbryio/canonical_url

use canonical_url for everything
This commit is contained in:
Sean Yesmunt 2019-08-22 11:04:49 -04:00 committed by GitHub
commit 6da0697e3f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 544 additions and 350 deletions

View file

@ -6,6 +6,7 @@
./flow-typed
[options]
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
module.system.node.resolve_dirname=./src
module.name_mapper='^redux\(.*\)$' -> '<PROJECT_ROOT>/src/redux\1'
module.name_mapper='^util\(.*\)$' -> '<PROJECT_ROOT>/src/util\1'

353
dist/bundle.es.js vendored
View file

@ -897,45 +897,46 @@ const getSearchQueryString = (query, options = {}, includeUserOptions = false) =
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
const channelNameMinLength = 1;
const claimIdMaxLength = 40;
// see https://spec.lbry.com/#urls
const regexInvalidURI = /[ =&#:$@%?;/\\"<>%{}|^~[\]`\u{0000}-\u{0008}\u{000b}-\u{000c}\u{000e}-\u{001F}\u{D800}-\u{DFFF}\u{FFFE}-\u{FFFF}]/u;
const regexAddress = /^(b|r)(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/;
const regexPartProtocol = '^((?:lbry://)?)';
const regexPartStreamOrChannelName = '([^:$#/]*)';
const regexPartModifierSeparator = '([:$#]?)([^/]*)';
/**
* Parses a LBRY name into its component parts. Throws errors with user-friendly
* messages for invalid names.
*
* N.B. that "name" indicates the value in the name position of the URI. For
* claims for channel content, this will actually be the channel name, and
* the content name is in the path (e.g. lbry://@channel/content)
*
* In most situations, you'll want to use the contentName and channelName keys
* and ignore the name key.
*
* Returns a dictionary with keys:
* - name (string): The value in the "name" position in the URI. Note that this
* could be either content name or channel name; see above.
* - path (string, if persent)
* - claimSequence (int, if present)
* - bidPosition (int, if present)
* - claimId (string, if present)
* - path (string)
* - isChannel (boolean)
* - contentName (string): For anon claims, the name; for channel claims, the path
* - channelName (string, if present): Channel name without @
* - streamName (string, if present)
* - streamClaimId (string, if present)
* - channelName (string, if present)
* - channelClaimId (string, if present)
* - primaryClaimSequence (int, if present)
* - secondaryClaimSequence (int, if present)
* - primaryBidPosition (int, if present)
* - secondaryBidPosition (int, if present)
*/
function parseURI(URI, requireProto = false) {
// Break into components. Empty sub-matches are converted to null
const componentsRegex = new RegExp('^((?:lbry://)?)' + // protocol
'([^:$#/]*)' + // claim name (stops at the first separator or end)
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
'(/?)(.*)' // path separator, path
);
const [proto, claimName, modSep, modVal, pathSep, path] = componentsRegex.exec(URI).slice(1).map(match => match || null);
let contentName;
function parseURI(URL, requireProto = false) {
// Break into components. Empty sub-matches are converted to null
const componentsRegex = new RegExp(regexPartProtocol + // protocol
regexPartStreamOrChannelName + // stream or channel name (stops at the first separator or end)
regexPartModifierSeparator + // modifier separator, modifier (stops at the first path separator or end)
'(/?)' + // path separator, there should only be one (optional) slash to separate the stream and channel parts
regexPartStreamOrChannelName + regexPartModifierSeparator);
const regexMatch = componentsRegex.exec(URL) || [];
const [proto, ...rest] = regexMatch.slice(1).map(match => match || null);
const path = rest.join('');
const [streamNameOrChannelName, primaryModSeparator, primaryModValue, pathSep, possibleStreamName, secondaryModSeparator, secondaryModValue] = rest;
// Validate protocol
if (requireProto && !proto) {
@ -943,14 +944,15 @@ function parseURI(URI, requireProto = false) {
}
// Validate and process name
if (!claimName) {
if (!streamNameOrChannelName) {
throw new Error(__('URI does not include name.'));
}
const isChannel = claimName.startsWith('@');
const channelName = isChannel ? claimName.slice(1) : claimName;
const includesChannel = streamNameOrChannelName.startsWith('@');
const isChannel = streamNameOrChannelName.startsWith('@') && !possibleStreamName;
const channelName = includesChannel && streamNameOrChannelName.slice(1);
if (isChannel) {
if (includesChannel) {
if (!channelName) {
throw new Error(__('No channel name after @.'));
}
@ -958,30 +960,42 @@ function parseURI(URI, requireProto = false) {
if (channelName.length < channelNameMinLength) {
throw new Error(__(`Channel names must be at least %s characters.`, channelNameMinLength));
}
contentName = path;
}
const nameBadChars = (channelName || claimName).match(regexInvalidURI);
if (nameBadChars) {
throw new Error(__(`Invalid character %s in name: %s.`, nameBadChars.length === 1 ? '' : 's', nameBadChars.join(', ')));
// Validate and process modifier
const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(primaryModSeparator, primaryModValue);
const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(secondaryModSeparator, secondaryModValue);
const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
const channelClaimId = includesChannel && primaryClaimId;
return _extends({
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) } : {}, {
// The values below should not be used for new uses of parseURI
// They will not work properly with canonical_urls
claimName: streamNameOrChannelName,
claimId: primaryClaimId
}, streamName ? { contentName: streamName } : {});
}
// Validate and process modifier (claim ID, bid position or claim sequence)
function parseURIModifier(modSeperator, modValue) {
let claimId;
let claimSequence;
let bidPosition;
if (modSep) {
if (!modVal) {
throw new Error(__(`No modifier provided after separator %s.`, modSep));
if (modSeperator) {
if (!modValue) {
throw new Error(__(`No modifier provided after separator %s.`, modSeperator));
}
if (modSep === '#') {
claimId = modVal;
} else if (modSep === ':') {
claimSequence = modVal;
} else if (modSep === '$') {
bidPosition = modVal;
if (modSeperator === '#') {
claimId = modValue;
} else if (modSeperator === ':') {
claimSequence = modValue;
} else if (modSeperator === '$') {
bidPosition = modValue;
}
}
@ -997,27 +1011,7 @@ function parseURI(URI, requireProto = false) {
throw new Error(__('Bid position must be a number.'));
}
// Validate and process path
if (path) {
if (!isChannel) {
throw new Error(__('Only channel URIs may have a path.'));
}
const pathBadChars = path.match(regexInvalidURI);
if (pathBadChars) {
throw new Error(__(`Invalid character in path: %s`, pathBadChars.join(', ')));
}
contentName = path;
} else if (pathSep) {
throw new Error(__('No path provided after /'));
}
return _extends({
claimName,
path,
isChannel
}, contentName ? { contentName } : {}, channelName ? { channelName } : {}, claimSequence ? { claimSequence: parseInt(claimSequence, 10) } : {}, bidPosition ? { bidPosition: parseInt(bidPosition, 10) } : {}, claimId ? { claimId } : {}, path ? { path } : {});
return [claimId, claimSequence, bidPosition];
}
/**
@ -1025,67 +1019,107 @@ function parseURI(URI, requireProto = false) {
*
* The channelName key will accept names with or without the @ prefix.
*/
function buildURI(URIObj, includeProto = true, protoDefault = 'lbry://') {
const { claimId, claimSequence, bidPosition, contentName, channelName } = URIObj;
function buildURI(UrlObj, includeProto = true, protoDefault = 'lbry://') {
const {
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryClaimSequence,
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition
} = UrlObj,
deprecatedParts = _objectWithoutProperties(UrlObj, ['streamName', 'streamClaimId', 'channelName', 'channelClaimId', 'primaryClaimSequence', 'primaryBidPosition', 'secondaryClaimSequence', 'secondaryBidPosition']);
const { claimId, claimName, contentName } = deprecatedParts;
let { claimName, path } = URIObj;
if (channelName) {
const channelNameFormatted = channelName.startsWith('@') ? channelName : `@${channelName}`;
if (!claimName) {
claimName = channelNameFormatted;
} else if (claimName !== channelNameFormatted) {
throw new Error(__('Received a channel content URI, but claim name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.'));
}
if (!claimName && !channelName && !streamName) {
throw new Error(__("'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url."));
}
if (contentName) {
if (!claimName) {
claimName = contentName;
} else if (!path) {
path = contentName;
}
if (path && path !== contentName) {
throw new Error(__('Path and contentName do not match. Only one is required; most likely you wanted contentName.'));
}
const formattedChannelName = channelName && (channelName.startsWith('@') ? channelName : `@${channelName}`);
const primaryClaimName = claimName || contentName || formattedChannelName || streamName;
const primaryClaimId = claimId || (formattedChannelName ? channelClaimId : streamClaimId);
const secondaryClaimName = !claimName && contentName || (formattedChannelName ? streamName : null);
const secondaryClaimId = secondaryClaimName && streamClaimId;
return (includeProto ? protoDefault : '') +
// primaryClaimName will always exist here because we throw above if there is no "name" value passed in
// $FlowFixMe
primaryClaimName + (primaryClaimId ? `#${primaryClaimId}` : '') + (primaryClaimSequence ? `:${primaryClaimSequence}` : '') + (primaryBidPosition ? `${primaryBidPosition}` : '') + (secondaryClaimName ? `/${secondaryClaimName}` : '') + (secondaryClaimId ? `#${secondaryClaimId}` : '') + (secondaryClaimSequence ? `:${secondaryClaimSequence}` : '') + (secondaryBidPosition ? `${secondaryBidPosition}` : '');
}
return (includeProto ? protoDefault : '') + claimName + (claimId ? `#${claimId}` : '') + (claimSequence ? `:${claimSequence}` : '') + (bidPosition ? `${bidPosition}` : '') + (path ? `/${path}` : '');
/* Takes a parseable LBRY URL and converts it to standard, canonical format */
function normalizeURI(URL) {
const {
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryClaimSequence,
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition
} = parseURI(URL);
return buildURI({
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryClaimSequence,
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition
});
}
/* Takes a parseable LBRY URI and converts it to standard, canonical format */
function normalizeURI(URI) {
const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI);
return buildURI({ claimName, path, claimSequence, bidPosition, claimId });
}
function isURIValid(URI) {
let parts;
function isURIValid(URL) {
try {
parts = parseURI(normalizeURI(URI));
parseURI(normalizeURI(URL));
} catch (error) {
return false;
}
return parts && parts.claimName;
return true;
}
function isNameValid(claimName) {
return !regexInvalidURI.test(claimName);
}
function isURIClaimable(URI) {
function isURIClaimable(URL) {
let parts;
try {
parts = parseURI(normalizeURI(URI));
parts = parseURI(normalizeURI(URL));
} catch (error) {
return false;
}
return parts && parts.claimName && !parts.claimId && !parts.bidPosition && !parts.claimSequence && !parts.isChannel && !parts.path;
return parts && parts.streamName && !parts.streamClaimId && !parts.isChannel;
}
function convertToShareLink(URI) {
const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI);
return buildURI({ claimName, path, claimSequence, bidPosition, claimId }, true, 'https://open.lbry.com/');
function convertToShareLink(URL) {
const {
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryBidPosition,
primaryClaimSequence,
secondaryBidPosition,
secondaryClaimSequence
} = parseURI(URL);
return buildURI({
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryBidPosition,
primaryClaimSequence,
secondaryBidPosition,
secondaryClaimSequence
}, true, 'https://open.lbry.com/');
}
var _extends$1 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
@ -1130,13 +1164,13 @@ const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selec
let searchSuggestions = [];
try {
const uri = normalizeURI(query);
const { claimName, isChannel } = parseURI(uri);
const { channelName, streamName, isChannel } = parseURI(uri);
searchSuggestions.push({
value: claimName,
value: streamName,
type: SEARCH_TYPES.SEARCH
}, {
value: uri,
shorthand: isChannel ? claimName.slice(1) : claimName,
shorthand: isChannel ? channelName : streamName,
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE
});
} catch (e) {
@ -1157,11 +1191,11 @@ const selectSearchSuggestions = reselect.createSelector(selectSearchValue, selec
// determine if it's a channel
try {
const uri = normalizeURI(suggestion);
const { claimName, isChannel } = parseURI(uri);
const { channelName, streamName, isChannel } = parseURI(uri);
return {
value: uri,
shorthand: isChannel ? claimName.slice(1) : claimName,
shorthand: isChannel ? channelName : streamName,
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE
};
} catch (e) {
@ -1377,7 +1411,7 @@ const selectTransactionListFilter = reselect.createSelector(selectState$1, state
var _extends$2 = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _objectWithoutProperties$1(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
const matureTagMap = MATURE_TAGS.reduce((acc, tag) => _extends$2({}, acc, { [tag]: true }), {});
@ -1404,7 +1438,7 @@ const isClaimNsfw = claim => {
function createNormalizedClaimSearchKey(options) {
// Ignore page because we don't care what the last page searched was, we want everything
// Ignore release_time because that will change depending on when you call claim_search ex: release_time: ">12344567"
const rest = _objectWithoutProperties(options, ['page', 'release_time']);
const rest = _objectWithoutProperties$1(options, ['page', 'release_time']);
const query = JSON.stringify(rest);
return query;
}
@ -1445,8 +1479,10 @@ const selectPendingClaims = reselect.createSelector(selectState$2, state => Obje
const makeSelectClaimIsPending = uri => reselect.createSelector(selectPendingById, pendingById => {
let claimId;
try {
({ claimId } = parseURI(uri));
const { isChannel, channelClaimId, streamClaimId } = parseURI(uri);
claimId = isChannel ? channelClaimId : streamClaimId;
} catch (e) {}
if (claimId) {
@ -1455,7 +1491,8 @@ const makeSelectClaimIsPending = uri => reselect.createSelector(selectPendingByI
});
const makeSelectPendingByUri = uri => reselect.createSelector(selectPendingById, pendingById => {
const { claimId } = parseURI(uri);
const { isChannel, channelClaimId, streamClaimId } = parseURI(uri);
const claimId = isChannel ? channelClaimId : streamClaimId;
return pendingById[claimId];
});
@ -1464,13 +1501,16 @@ const makeSelectClaimForUri = uri => reselect.createSelector(selectClaimsByUri,
// It won't be in claimsByUri because resolving it will return nothing
let valid;
let claimId;
let channelClaimId;
let streamClaimId;
let isChannel;
try {
({ claimId } = parseURI(uri));
({ isChannel, channelClaimId, streamClaimId } = parseURI(uri));
valid = true;
} catch (e) {}
if (valid) {
const claimId = isChannel ? channelClaimId : streamClaimId;
const pendingClaim = pendingById[claimId];
if (pendingClaim) {
@ -1720,15 +1760,18 @@ const selectClaimSearchByQueryLastPageReached = reselect.createSelector(selectSt
const makeSelectShortUrlForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => claim && claim.short_url);
const makeSelectCanonicalUrlForUri = uri => reselect.createSelector(makeSelectClaimForUri(uri), claim => claim && claim.canonical_url);
const makeSelectSupportsForUri = uri => reselect.createSelector(selectSupportsByOutpoint, makeSelectClaimForUri(uri), (byOutpoint, claim) => {
if (!claim || !claim.is_mine) {
return null;
}
const { claim_id: claimId } = claim;
let total = parseFloat("0.0");
let total = 0;
Object.values(byOutpoint).forEach(support => {
// $FlowFixMe
const { claim_id, amount } = support;
total = claim_id === claimId && amount ? total + parseFloat(amount) : total;
});
@ -2436,10 +2479,10 @@ function doClaimSearch(options = {
const success = data => {
const resolveInfo = {};
const uris = [];
const urls = [];
data.items.forEach(stream => {
resolveInfo[stream.permanent_url] = { stream };
uris.push(stream.permanent_url);
resolveInfo[stream.canonical_url] = { stream };
urls.push(stream.canonical_url);
});
dispatch({
@ -2447,7 +2490,7 @@ function doClaimSearch(options = {
data: {
query,
resolveInfo,
uris,
urls,
append: options.page && options.page !== 1,
pageSize: options.page_size
}
@ -2861,12 +2904,12 @@ function doSetFileListSort(page, value) {
};
}
function _objectWithoutProperties$1(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _objectWithoutProperties$2(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
const selectState$5 = state => state.publish || {};
const selectPublishFormValues = reselect.createSelector(selectState$5, state => {
const formValues = _objectWithoutProperties$1(state, ['pendingPublish']);
const formValues = _objectWithoutProperties$2(state, ['pendingPublish']);
return formValues;
});
const makeSelectPublishFormValue = item => reselect.createSelector(selectState$5, state => state[item]);
@ -2879,8 +2922,16 @@ const selectIsStillEditing = reselect.createSelector(selectPublishFormValues, pu
return false;
}
const { isChannel: currentIsChannel, claimName: currentClaimName, contentName: currentContentName } = parseURI(uri);
const { isChannel: editIsChannel, claimName: editClaimName, contentName: editContentName } = parseURI(editingURI);
const {
isChannel: currentIsChannel,
claimName: currentClaimName,
contentName: currentContentName
} = parseURI(uri);
const {
isChannel: editIsChannel,
claimName: editClaimName,
contentName: editContentName
} = parseURI(editingURI);
// Depending on the previous/current use of a channel, we need to compare different things
// ex: going from a channel to anonymous, the new uri won't return contentName, so we need to use claimName
@ -2907,7 +2958,7 @@ const selectIsResolvingPublishUris = reselect.createSelector(selectState$5, sele
let isResolvingShortUri;
if (isChannel) {
const shortUri = buildURI({ contentName: name });
const shortUri = buildURI({ streamName: name });
isResolvingShortUri = resolvingUris.includes(shortUri);
}
@ -3379,13 +3430,21 @@ from, isBackgroundSearch = false) => (dispatch, getState) => {
const actions = [];
data.forEach(result => {
if (result.name) {
const uri = buildURI({
claimName: result.name,
claimId: result.claimId
});
actions.push(doResolveUri(uri));
uris.push(uri);
if (result) {
const { name, claimId } = result;
const urlObj = {};
if (name.startsWith('@')) {
urlObj.channelName = name;
urlObj.channelClaimId = claimId;
} else {
urlObj.streamName = name;
urlObj.streamClaimId = claimId;
}
const url = buildURI(urlObj);
actions.push(doResolveUri(url));
uris.push(url);
}
});
@ -3564,26 +3623,25 @@ function handleClaimAction(state, action) {
const byId = Object.assign({}, state.byId);
const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
Object.entries(resolveInfo).forEach(([uri, resolveResponse]) => {
Object.entries(resolveInfo).forEach(([url, resolveResponse]) => {
// $FlowFixMe
if (resolveResponse.claimsInChannel) {
// $FlowFixMe
channelClaimCounts[uri] = resolveResponse.claimsInChannel;
const { claimsInChannel, stream, channel } = resolveResponse;
if (claimsInChannel) {
channelClaimCounts[url] = claimsInChannel;
}
});
// $FlowFixMe
Object.entries(resolveInfo).forEach(([uri, { channel, stream }]) => {
if (stream) {
byId[stream.claim_id] = stream;
byUri[uri] = stream.claim_id;
byUri[url] = stream.claim_id;
}
if (channel) {
byId[channel.claim_id] = channel;
byUri[stream ? channel.permanent_url : uri] = channel.claim_id;
byUri[stream ? channel.canonical_url : url] = channel.claim_id;
}
if (!stream && !channel) {
byUri[uri] = null;
byUri[url] = null;
}
});
@ -3795,17 +3853,17 @@ reducers[CLAIM_SEARCH_COMPLETED] = (state, action) => {
const fetchingClaimSearchByQuery = Object.assign({}, state.fetchingClaimSearchByQuery);
const claimSearchByQuery = Object.assign({}, state.claimSearchByQuery);
const claimSearchByQueryLastPageReached = Object.assign({}, state.claimSearchByQueryLastPageReached);
const { append, query, uris, pageSize } = action.data;
const { append, query, urls, pageSize } = action.data;
if (append) {
// todo: check for duplicate uris when concatenating?
claimSearchByQuery[query] = claimSearchByQuery[query] && claimSearchByQuery[query].length ? claimSearchByQuery[query].concat(uris) : uris;
// todo: check for duplicate urls when concatenating?
claimSearchByQuery[query] = claimSearchByQuery[query] && claimSearchByQuery[query].length ? claimSearchByQuery[query].concat(urls) : urls;
} else {
claimSearchByQuery[query] = uris;
claimSearchByQuery[query] = urls;
}
// the returned number of uris is less than the page size, so we're on the last page
claimSearchByQueryLastPageReached[query] = uris.length < pageSize;
// the returned number of urls is less than the page size, so we're on the last page
claimSearchByQueryLastPageReached[query] = urls.length < pageSize;
delete fetchingClaimSearchByQuery[query];
@ -4235,7 +4293,7 @@ const notificationsReducer = handleActions({
var _extends$a = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
function _objectWithoutProperties$2(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
function _objectWithoutProperties$3(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
const defaultState$6 = {
editingURI: undefined,
@ -4285,7 +4343,7 @@ const publishReducer = handleActions({
publishSuccess: true
}),
[DO_PREPARE_EDIT]: (state, action) => {
const publishData = _objectWithoutProperties$2(action.data, []);
const publishData = _objectWithoutProperties$3(action.data, []);
const { channel, name, uri } = publishData;
// The short uri is what is presented to the user
@ -4895,6 +4953,7 @@ exports.isNameValid = isNameValid;
exports.isURIClaimable = isURIClaimable;
exports.isURIValid = isURIValid;
exports.makeSelectAmountForUri = makeSelectAmountForUri;
exports.makeSelectCanonicalUrlForUri = makeSelectCanonicalUrlForUri;
exports.makeSelectChannelForClaimUri = makeSelectChannelForClaimUri;
exports.makeSelectClaimForUri = makeSelectClaimForUri;
exports.makeSelectClaimIsMine = makeSelectClaimIsMine;

View file

@ -23,6 +23,7 @@ declare type GenericClaim = {
decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044
timestamp?: number, // date of last transaction
height: number, // block height the tx was confirmed
is_mine: boolean,
name: string,
normalized_name: string, // `name` normalized via unicode NFD spec,
nout: number, // index number for an output of a tx

View file

@ -76,7 +76,7 @@ declare type BalanceResponse = {
declare type ResolveResponse = {
// Keys are the url(s) passed to resolve
[string]: Claim | { error?: {} },
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, claimsInChannel?: number },
};
declare type GetResponse = FileListItem & { error?: string };

2
dist/flow-typed/i18n.js vendored Normal file
View file

@ -0,0 +1,2 @@
// @flow
declare function __(a: string, b?: string | number): string;

1
flow-typed/Claim.js vendored
View file

@ -23,6 +23,7 @@ declare type GenericClaim = {
decoded_claim: boolean, // Not available currently https://github.com/lbryio/lbry/issues/2044
timestamp?: number, // date of last transaction
height: number, // block height the tx was confirmed
is_mine: boolean,
name: string,
normalized_name: string, // `name` normalized via unicode NFD spec,
nout: number, // index number for an output of a tx

2
flow-typed/Lbry.js vendored
View file

@ -76,7 +76,7 @@ declare type BalanceResponse = {
declare type ResolveResponse = {
// Keys are the url(s) passed to resolve
[string]: Claim | { error?: {} },
[string]: { error?: {}, stream?: StreamClaim, channel?: ChannelClaim, claimsInChannel?: number },
};
declare type GetResponse = FileListItem & { error?: string };

2
flow-typed/i18n.js vendored Normal file
View file

@ -0,0 +1,2 @@
// @flow
declare function __(a: string, b?: string | number): string;

20
flow-typed/lbryURI.js vendored Normal file
View file

@ -0,0 +1,20 @@
// @flow
declare type LbryUrlObj = {
// Path and channel will always exist when calling parseURI
// But they may not exist when code calls buildURI
isChannel?: boolean,
path?: string,
streamName?: string,
streamClaimId?: string,
channelName?: string,
channelClaimId?: string,
primaryClaimSequence?: number,
secondaryClaimSequence?: number,
primaryBidPosition?: number,
secondaryBidPosition?: number,
// Below are considered deprecated and should not be used due to unreliableness with claim.canonical_url
claimName?: string,
claimId?: string,
contentName?: string,
};

View file

@ -21,7 +21,7 @@
"main": "dist/bundle.es.js",
"module": "dist/bundle.es.js",
"scripts": {
"build": "rollup --config",
"build": "NODE_ENV=production rollup --config",
"dev": "rollup --config --watch",
"precommit": "flow check && lint-staged",
"lint": "eslint 'src/**/*.js' --fix",
@ -59,7 +59,8 @@
"rollup-plugin-copy": "^1.1.0",
"rollup-plugin-eslint": "^5.1.0",
"rollup-plugin-flow": "^1.1.1",
"rollup-plugin-includepaths": "^0.2.3"
"rollup-plugin-includepaths": "^0.2.3",
"rollup-plugin-replace": "^2.2.0"
},
"engines": {
"yarn": "^1.3"

View file

@ -2,6 +2,7 @@ import babel from 'rollup-plugin-babel';
import flow from 'rollup-plugin-flow';
import includePaths from 'rollup-plugin-includepaths';
import copy from 'rollup-plugin-copy';
import replace from 'rollup-plugin-replace';
let includePathOptions = {
include: {},
@ -10,6 +11,8 @@ let includePathOptions = {
extensions: ['.js'],
};
const production = process.env.NODE_ENV === 'production';
export default {
input: 'src/index.js',
output: {
@ -24,5 +27,10 @@ export default {
presets: ['stage-2'],
}),
copy({ targets: ['flow-typed'] }),
replace({
'process.env.NODE_ENV': production
? JSON.stringify('production')
: JSON.stringify('development'),
}),
],
};

View file

@ -172,6 +172,7 @@ export {
makeSelectPendingByUri,
makeSelectClaimsInChannelForCurrentPageState,
makeSelectShortUrlForUri,
makeSelectCanonicalUrlForUri,
makeSelectSupportsForUri,
selectPendingById,
selectClaimsById,

View file

@ -1,46 +1,55 @@
// @flow
const isProduction = process.env.NODE_ENV === 'production';
const channelNameMinLength = 1;
const claimIdMaxLength = 40;
// see https://spec.lbry.com/#urls
export const regexInvalidURI = /[ =&#:$@%?;/\\"<>%{}|^~[\]`\u{0000}-\u{0008}\u{000b}-\u{000c}\u{000e}-\u{001F}\u{D800}-\u{DFFF}\u{FFFE}-\u{FFFF}]/u;
export const regexAddress = /^(b|r)(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/;
const regexPartProtocol = '^((?:lbry://)?)';
const regexPartStreamOrChannelName = '([^:$#/]*)';
const regexPartModifierSeparator = '([:$#]?)([^/]*)';
/**
* Parses a LBRY name into its component parts. Throws errors with user-friendly
* messages for invalid names.
*
* N.B. that "name" indicates the value in the name position of the URI. For
* claims for channel content, this will actually be the channel name, and
* the content name is in the path (e.g. lbry://@channel/content)
*
* In most situations, you'll want to use the contentName and channelName keys
* and ignore the name key.
*
* Returns a dictionary with keys:
* - name (string): The value in the "name" position in the URI. Note that this
* could be either content name or channel name; see above.
* - path (string, if persent)
* - claimSequence (int, if present)
* - bidPosition (int, if present)
* - claimId (string, if present)
* - path (string)
* - isChannel (boolean)
* - contentName (string): For anon claims, the name; for channel claims, the path
* - channelName (string, if present): Channel name without @
* - streamName (string, if present)
* - streamClaimId (string, if present)
* - channelName (string, if present)
* - channelClaimId (string, if present)
* - primaryClaimSequence (int, if present)
* - secondaryClaimSequence (int, if present)
* - primaryBidPosition (int, if present)
* - secondaryBidPosition (int, if present)
*/
export function parseURI(URI, requireProto = false) {
export function parseURI(URL: string, requireProto: boolean = false): LbryUrlObj {
// Break into components. Empty sub-matches are converted to null
const componentsRegex = new RegExp(
'^((?:lbry://)?)' + // protocol
'([^:$#/]*)' + // claim name (stops at the first separator or end)
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
'(/?)(.*)' // path separator, path
regexPartProtocol + // protocol
regexPartStreamOrChannelName + // stream or channel name (stops at the first separator or end)
regexPartModifierSeparator + // modifier separator, modifier (stops at the first path separator or end)
'(/?)' + // path separator, there should only be one (optional) slash to separate the stream and channel parts
regexPartStreamOrChannelName +
regexPartModifierSeparator
);
const [proto, claimName, modSep, modVal, pathSep, path] = componentsRegex
.exec(URI)
.slice(1)
.map(match => match || null);
let contentName;
const regexMatch = componentsRegex.exec(URL) || [];
const [proto, ...rest] = regexMatch.slice(1).map(match => match || null);
const path = rest.join('');
const [
streamNameOrChannelName,
primaryModSeparator,
primaryModValue,
pathSep,
possibleStreamName,
secondaryModSeparator,
secondaryModValue,
] = rest;
// Validate protocol
if (requireProto && !proto) {
@ -48,14 +57,15 @@ export function parseURI(URI, requireProto = false) {
}
// Validate and process name
if (!claimName) {
if (!streamNameOrChannelName) {
throw new Error(__('URI does not include name.'));
}
const isChannel = claimName.startsWith('@');
const channelName = isChannel ? claimName.slice(1) : claimName;
const includesChannel = streamNameOrChannelName.startsWith('@');
const isChannel = streamNameOrChannelName.startsWith('@') && !possibleStreamName;
const channelName = includesChannel && streamNameOrChannelName.slice(1);
if (isChannel) {
if (includesChannel) {
if (!channelName) {
throw new Error(__('No channel name after @.'));
}
@ -63,36 +73,58 @@ export function parseURI(URI, requireProto = false) {
if (channelName.length < channelNameMinLength) {
throw new Error(__(`Channel names must be at least %s characters.`, channelNameMinLength));
}
contentName = path;
}
const nameBadChars = (channelName || claimName).match(regexInvalidURI);
if (nameBadChars) {
throw new Error(
__(
`Invalid character %s in name: %s.`,
nameBadChars.length === 1 ? '' : 's',
nameBadChars.join(', ')
)
// Validate and process modifier
const [primaryClaimId, primaryClaimSequence, primaryBidPosition] = parseURIModifier(
primaryModSeparator,
primaryModValue
);
const [secondaryClaimId, secondaryClaimSequence, secondaryBidPosition] = parseURIModifier(
secondaryModSeparator,
secondaryModValue
);
const streamName = includesChannel ? possibleStreamName : streamNameOrChannelName;
const streamClaimId = includesChannel ? secondaryClaimId : primaryClaimId;
const channelClaimId = includesChannel && primaryClaimId;
return {
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) } : {}),
// The values below should not be used for new uses of parseURI
// They will not work properly with canonical_urls
claimName: streamNameOrChannelName,
claimId: primaryClaimId,
...(streamName ? { contentName: streamName } : {}),
};
}
// Validate and process modifier (claim ID, bid position or claim sequence)
function parseURIModifier(modSeperator: ?string, modValue: ?string) {
let claimId;
let claimSequence;
let bidPosition;
if (modSep) {
if (!modVal) {
throw new Error(__(`No modifier provided after separator %s.`, modSep));
if (modSeperator) {
if (!modValue) {
throw new Error(__(`No modifier provided after separator %s.`, modSeperator));
}
if (modSep === '#') {
claimId = modVal;
} else if (modSep === ':') {
claimSequence = modVal;
} else if (modSep === '$') {
bidPosition = modVal;
if (modSeperator === '#') {
claimId = modValue;
} else if (modSeperator === ':') {
claimSequence = modValue;
} else if (modSeperator === '$') {
bidPosition = modValue;
}
}
@ -108,33 +140,7 @@ export function parseURI(URI, requireProto = false) {
throw new Error(__('Bid position must be a number.'));
}
// Validate and process path
if (path) {
if (!isChannel) {
throw new Error(__('Only channel URIs may have a path.'));
}
const pathBadChars = path.match(regexInvalidURI);
if (pathBadChars) {
throw new Error(__(`Invalid character in path: %s`, pathBadChars.join(', ')));
}
contentName = path;
} else if (pathSep) {
throw new Error(__('No path provided after /'));
}
return {
claimName,
path,
isChannel,
...(contentName ? { contentName } : {}),
...(channelName ? { channelName } : {}),
...(claimSequence ? { claimSequence: parseInt(claimSequence, 10) } : {}),
...(bidPosition ? { bidPosition: parseInt(bidPosition, 10) } : {}),
...(claimId ? { claimId } : {}),
...(path ? { path } : {}),
};
return [claimId, claimSequence, bidPosition];
}
/**
@ -142,91 +148,145 @@ export function parseURI(URI, requireProto = false) {
*
* The channelName key will accept names with or without the @ prefix.
*/
export function buildURI(URIObj, includeProto = true, protoDefault = 'lbry://') {
const { claimId, claimSequence, bidPosition, contentName, channelName } = URIObj;
export function buildURI(
UrlObj: LbryUrlObj,
includeProto: boolean = true,
protoDefault: string = 'lbry://'
): string {
const {
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryClaimSequence,
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition,
...deprecatedParts
} = UrlObj;
const { claimId, claimName, contentName } = deprecatedParts;
let { claimName, path } = URIObj;
if (channelName) {
const channelNameFormatted = channelName.startsWith('@') ? channelName : `@${channelName}`;
if (!claimName) {
claimName = channelNameFormatted;
} else if (claimName !== channelNameFormatted) {
throw new Error(
if (!isProduction) {
if (claimId) {
console.error(
__("'claimId' should no longer be used. Use 'streamClaimId' or 'channelClaimId' instead")
);
}
if (claimName) {
console.error(
__(
'Received a channel content URI, but claim name and channelName do not match. "name" represents the value in the name position of the URI (lbry://name...), which for channel content will be the channel name. In most cases, to construct a channel URI you should just pass channelName and contentName.'
"'claimName' should no longer be used. Use 'streamClaimName' or 'channelClaimName' instead"
)
);
}
}
if (contentName) {
if (!claimName) {
claimName = contentName;
} else if (!path) {
path = contentName;
console.error(__("'contentName' should no longer be used. Use 'streamName' instead"));
}
if (path && path !== contentName) {
}
if (!claimName && !channelName && !streamName) {
throw new Error(
__(
'Path and contentName do not match. Only one is required; most likely you wanted contentName.'
"'claimName', 'channelName', and 'streamName' are all empty. One must be present to build a url."
)
);
}
}
const formattedChannelName =
channelName && (channelName.startsWith('@') ? channelName : `@${channelName}`);
const primaryClaimName = claimName || contentName || formattedChannelName || streamName;
const primaryClaimId = claimId || (formattedChannelName ? channelClaimId : streamClaimId);
const secondaryClaimName =
(!claimName && contentName) || (formattedChannelName ? streamName : null);
const secondaryClaimId = secondaryClaimName && streamClaimId;
return (
(includeProto ? protoDefault : '') +
claimName +
(claimId ? `#${claimId}` : '') +
(claimSequence ? `:${claimSequence}` : '') +
(bidPosition ? `${bidPosition}` : '') +
(path ? `/${path}` : '')
// primaryClaimName will always exist here because we throw above if there is no "name" value passed in
// $FlowFixMe
primaryClaimName +
(primaryClaimId ? `#${primaryClaimId}` : '') +
(primaryClaimSequence ? `:${primaryClaimSequence}` : '') +
(primaryBidPosition ? `${primaryBidPosition}` : '') +
(secondaryClaimName ? `/${secondaryClaimName}` : '') +
(secondaryClaimId ? `#${secondaryClaimId}` : '') +
(secondaryClaimSequence ? `:${secondaryClaimSequence}` : '') +
(secondaryBidPosition ? `${secondaryBidPosition}` : '')
);
}
/* Takes a parseable LBRY URI and converts it to standard, canonical format */
export function normalizeURI(URI) {
const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI);
return buildURI({ claimName, path, claimSequence, bidPosition, claimId });
/* Takes a parseable LBRY URL and converts it to standard, canonical format */
export function normalizeURI(URL: string) {
const {
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryClaimSequence,
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition,
} = parseURI(URL);
return buildURI({
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryClaimSequence,
primaryBidPosition,
secondaryClaimSequence,
secondaryBidPosition,
});
}
export function isURIValid(URI) {
let parts;
export function isURIValid(URL: string): boolean {
try {
parts = parseURI(normalizeURI(URI));
parseURI(normalizeURI(URL));
} catch (error) {
return false;
}
return parts && parts.claimName;
return true;
}
export function isNameValid(claimName) {
export function isNameValid(claimName: string) {
return !regexInvalidURI.test(claimName);
}
export function isURIClaimable(URI) {
export function isURIClaimable(URL: string) {
let parts;
try {
parts = parseURI(normalizeURI(URI));
parts = parseURI(normalizeURI(URL));
} catch (error) {
return false;
}
return (
parts &&
parts.claimName &&
!parts.claimId &&
!parts.bidPosition &&
!parts.claimSequence &&
!parts.isChannel &&
!parts.path
);
return parts && parts.streamName && !parts.streamClaimId && !parts.isChannel;
}
export function convertToShareLink(URI) {
const { claimName, path, bidPosition, claimSequence, claimId } = parseURI(URI);
export function convertToShareLink(URL: string) {
const {
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryBidPosition,
primaryClaimSequence,
secondaryBidPosition,
secondaryClaimSequence,
} = parseURI(URL);
return buildURI(
{ claimName, path, claimSequence, bidPosition, claimId },
{
streamName,
streamClaimId,
channelName,
channelClaimId,
primaryBidPosition,
primaryClaimSequence,
secondaryBidPosition,
secondaryClaimSequence,
},
true,
'https://open.lbry.com/'
);

View file

@ -328,10 +328,10 @@ export function doClaimSearch(
const success = (data: ClaimSearchResponse) => {
const resolveInfo = {};
const uris = [];
const urls = [];
data.items.forEach((stream: Claim) => {
resolveInfo[stream.permanent_url] = { stream };
uris.push(stream.permanent_url);
resolveInfo[stream.canonical_url] = { stream };
urls.push(stream.canonical_url);
});
dispatch({
@ -339,7 +339,7 @@ export function doClaimSearch(
data: {
query,
resolveInfo,
uris,
urls,
append: options.page && options.page !== 1,
pageSize: options.page_size,
},

View file

@ -112,18 +112,26 @@ export const doSearch = (
fetch(`${CONNECTION_STRING}search?${queryWithOptions}`)
.then(handleFetchResponse)
.then((data: Array<{ name: String, claimId: string }>) => {
.then((data: Array<{ name: string, claimId: string }>) => {
const uris = [];
const actions = [];
data.forEach(result => {
if (result.name) {
const uri = buildURI({
claimName: result.name,
claimId: result.claimId,
});
actions.push(doResolveUri(uri));
uris.push(uri);
if (result) {
const { name, claimId } = result;
const urlObj: LbryUrlObj = {};
if (name.startsWith('@')) {
urlObj.channelName = name;
urlObj.channelClaimId = claimId;
} else {
urlObj.streamName = name;
urlObj.streamClaimId = claimId;
}
const url = buildURI(urlObj);
actions.push(doResolveUri(url));
uris.push(url);
}
});

View file

@ -66,26 +66,25 @@ function handleClaimAction(state: State, action: any): State {
const byId = Object.assign({}, state.byId);
const channelClaimCounts = Object.assign({}, state.channelClaimCounts);
Object.entries(resolveInfo).forEach(([uri: string, resolveResponse: Claim]) => {
Object.entries(resolveInfo).forEach(([url: string, resolveResponse: ResolveResponse]) => {
// $FlowFixMe
if (resolveResponse.claimsInChannel) {
// $FlowFixMe
channelClaimCounts[uri] = resolveResponse.claimsInChannel;
const { claimsInChannel, stream, channel } = resolveResponse;
if (claimsInChannel) {
channelClaimCounts[url] = claimsInChannel;
}
});
// $FlowFixMe
Object.entries(resolveInfo).forEach(([uri, { channel, stream }]) => {
if (stream) {
byId[stream.claim_id] = stream;
byUri[uri] = stream.claim_id;
byUri[url] = stream.claim_id;
}
if (channel) {
byId[channel.claim_id] = channel;
byUri[stream ? channel.permanent_url : uri] = channel.claim_id;
byUri[stream ? channel.canonical_url : url] = channel.claim_id;
}
if (!stream && !channel) {
byUri[uri] = null;
byUri[url] = null;
}
});
@ -305,20 +304,20 @@ reducers[ACTIONS.CLAIM_SEARCH_COMPLETED] = (state: State, action: any): State =>
{},
state.claimSearchByQueryLastPageReached
);
const { append, query, uris, pageSize } = action.data;
const { append, query, urls, pageSize } = action.data;
if (append) {
// todo: check for duplicate uris when concatenating?
// todo: check for duplicate urls when concatenating?
claimSearchByQuery[query] =
claimSearchByQuery[query] && claimSearchByQuery[query].length
? claimSearchByQuery[query].concat(uris)
: uris;
? claimSearchByQuery[query].concat(urls)
: urls;
} else {
claimSearchByQuery[query] = uris;
claimSearchByQuery[query] = urls;
}
// the returned number of uris is less than the page size, so we're on the last page
claimSearchByQueryLastPageReached[query] = uris.length < pageSize;
// the returned number of urls is less than the page size, so we're on the last page
claimSearchByQueryLastPageReached[query] = urls.length < pageSize;
delete fetchingClaimSearchByQuery[query];

View file

@ -62,8 +62,10 @@ export const makeSelectClaimIsPending = (uri: string) =>
selectPendingById,
pendingById => {
let claimId;
try {
({ claimId } = parseURI(uri));
const { isChannel, channelClaimId, streamClaimId } = parseURI(uri);
claimId = isChannel ? channelClaimId : streamClaimId;
} catch (e) {}
if (claimId) {
@ -76,7 +78,8 @@ export const makeSelectPendingByUri = (uri: string) =>
createSelector(
selectPendingById,
pendingById => {
const { claimId } = parseURI(uri);
const { isChannel, channelClaimId, streamClaimId } = parseURI(uri);
const claimId = isChannel ? channelClaimId : streamClaimId;
return pendingById[claimId];
}
);
@ -90,13 +93,16 @@ export const makeSelectClaimForUri = (uri: string) =>
// It won't be in claimsByUri because resolving it will return nothing
let valid;
let claimId;
let channelClaimId;
let streamClaimId;
let isChannel;
try {
({ claimId } = parseURI(uri));
({ isChannel, channelClaimId, streamClaimId } = parseURI(uri));
valid = true;
} catch (e) {}
if (valid) {
const claimId = isChannel ? channelClaimId : streamClaimId;
const pendingClaim = pendingById[claimId];
if (pendingClaim) {
@ -521,23 +527,18 @@ export const selectClaimSearchByQueryLastPageReached = createSelector(
state => state.claimSearchByQueryLastPageReached || {}
);
export const makeSelectClaimSearchUrisByOptions = (options: {}) =>
createSelector(
selectClaimSearchByQuery,
byQuery => {
// We don't care what options are passed to this selector. Just forward them.
// $FlowFixMe
const query = createNormalizedClaimSearchKey(options);
return byQuery[query];
}
);
export const makeSelectShortUrlForUri = (uri: string) =>
createSelector(
makeSelectClaimForUri(uri),
claim => claim && claim.short_url
);
export const makeSelectCanonicalUrlForUri = (uri: string) =>
createSelector(
makeSelectClaimForUri(uri),
claim => claim && claim.canonical_url
);
export const makeSelectSupportsForUri = (uri: string) =>
createSelector(
selectSupportsByOutpoint,
@ -548,11 +549,12 @@ export const makeSelectSupportsForUri = (uri: string) =>
}
const { claim_id: claimId } = claim;
let total = parseFloat("0.0");
let total = 0;
Object.values(byOutpoint).forEach(support => {
const { claim_id, amount } = support
total = (claim_id === claimId && amount) ? total + parseFloat(amount) : total;
// $FlowFixMe
const { claim_id, amount } = support;
total = claim_id === claimId && amount ? total + parseFloat(amount) : total;
});
return total;

View file

@ -33,8 +33,16 @@ export const selectIsStillEditing = createSelector(
return false;
}
const { isChannel: currentIsChannel, claimName: currentClaimName, contentName: currentContentName } = parseURI(uri);
const { isChannel: editIsChannel, claimName: editClaimName, contentName: editContentName } = parseURI(editingURI);
const {
isChannel: currentIsChannel,
claimName: currentClaimName,
contentName: currentContentName,
} = parseURI(uri);
const {
isChannel: editIsChannel,
claimName: editClaimName,
contentName: editContentName,
} = parseURI(editingURI);
// Depending on the previous/current use of a channel, we need to compare different things
// ex: going from a channel to anonymous, the new uri won't return contentName, so we need to use claimName
@ -60,7 +68,9 @@ export const selectMyClaimForUri = createSelector(
return isStillEditing
? claimsById[editClaimId]
: myClaims.find(claim =>
!contentName ? claim.name === claimName : claim.name === contentName || claim.name === claimName
!contentName
? claim.name === claimName
: claim.name === contentName || claim.name === claimName
);
}
);
@ -75,7 +85,7 @@ export const selectIsResolvingPublishUris = createSelector(
let isResolvingShortUri;
if (isChannel) {
const shortUri = buildURI({ contentName: name });
const shortUri = buildURI({ streamName: name });
isResolvingShortUri = resolvingUris.includes(shortUri);
}

View file

@ -77,15 +77,15 @@ export const selectSearchSuggestions: Array<SearchSuggestion> = createSelector(
let searchSuggestions = [];
try {
const uri = normalizeURI(query);
const { claimName, isChannel } = parseURI(uri);
const { channelName, streamName, isChannel } = parseURI(uri);
searchSuggestions.push(
{
value: claimName,
value: streamName,
type: SEARCH_TYPES.SEARCH,
},
{
value: uri,
shorthand: isChannel ? claimName.slice(1) : claimName,
shorthand: isChannel ? channelName : streamName,
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
}
);
@ -110,11 +110,11 @@ export const selectSearchSuggestions: Array<SearchSuggestion> = createSelector(
// determine if it's a channel
try {
const uri = normalizeURI(suggestion);
const { claimName, isChannel } = parseURI(uri);
const { channelName, streamName, isChannel } = parseURI(uri);
return {
value: uri,
shorthand: isChannel ? claimName.slice(1) : claimName,
shorthand: isChannel ? channelName : streamName,
type: isChannel ? SEARCH_TYPES.CHANNEL : SEARCH_TYPES.FILE,
};
} catch (e) {

View file

@ -1,13 +0,0 @@
// @flow
import { parseURI } from 'lbryURI';
export const formatLbryUriForWeb = (uri: string) => {
const { claimName, claimId } = parseURI(uri);
let webUrl = `/${claimName}`;
if (claimId) {
webUrl += `/${claimId}`;
}
return webUrl;
};

View file

@ -1911,6 +1911,11 @@ estree-walker@^0.6.0:
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.0.tgz#5d865327c44a618dde5699f763891ae31f257dae"
integrity sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
integrity sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==
esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
@ -3440,6 +3445,13 @@ lru-cache@^4.0.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
magic-string@^0.25.2:
version "0.25.3"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.3.tgz#34b8d2a2c7fec9d9bdf9929a3fd81d271ef35be9"
integrity sha512-6QK0OpF/phMz0Q2AxILkX2mFhi7m+WMwTRg0LQKq/WBB0cDP4rYH3Wp4/d3OTXlrPLVJT/RFqj8tFeAR4nk8AA==
dependencies:
sourcemap-codec "^1.4.4"
make-dir@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
@ -4379,6 +4391,14 @@ rollup-plugin-includepaths@^0.2.3:
resolved "https://registry.yarnpkg.com/rollup-plugin-includepaths/-/rollup-plugin-includepaths-0.2.3.tgz#244d21b9669a0debe476d825e4a02ed08c06b258"
integrity sha512-4QbSIZPDT+FL4SViEVCRi4cGCA64zQJu7u5qmCkO3ecHy+l9EQBsue15KfCpddfb6Br0q47V/v2+E2YUiqts9g==
rollup-plugin-replace@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-replace/-/rollup-plugin-replace-2.2.0.tgz#f41ae5372e11e7a217cde349c8b5d5fd115e70e3"
integrity sha512-/5bxtUPkDHyBJAKketb4NfaeZjL5yLZdeUihSfbF2PQMz+rSTEb8ARKoOl3UBT4m7/X+QOXJo3sLTcq+yMMYTA==
dependencies:
magic-string "^0.25.2"
rollup-pluginutils "^2.6.0"
rollup-pluginutils@^1.5.0, rollup-pluginutils@^1.5.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408"
@ -4395,6 +4415,13 @@ rollup-pluginutils@^2.3.0:
estree-walker "^0.6.0"
micromatch "^3.1.10"
rollup-pluginutils@^2.6.0:
version "2.8.1"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.1.tgz#8fa6dd0697344938ef26c2c09d2488ce9e33ce97"
integrity sha512-J5oAoysWar6GuZo0s+3bZ6sVZAC0pfqKz68De7ZgDi5z63jOVZn1uJL/+z1jeKHNbGII8kAyHF5q8LnxSX5lQg==
dependencies:
estree-walker "^0.6.1"
rollup@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-1.8.0.tgz#e3ce8b708ad4325166717f74f244f691595d35e2"
@ -4613,6 +4640,11 @@ source-map@^0.6.0, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sourcemap-codec@^1.4.4:
version "1.4.6"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9"
integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg==
spdx-correct@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82"