use canonical_url for everything #187

Merged
neb-b merged 2 commits from canonical_url into master 2019-08-22 17:04:50 +02:00
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;
// Validate and process modifier (claim ID, bid position or claim sequence)
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 } : {});
}
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 : '') + claimName + (claimId ? `#${claimId}` : '') + (claimSequence ? `:${claimSequence}` : '') + (bidPosition ? `${bidPosition}` : '') + (path ? `/${path}` : '');
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}` : '');
}
/* 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 });
/* 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
});
}
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;
});
@ -2433,10 +2476,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({
@ -2444,7 +2487,7 @@ function doClaimSearch(options = {
data: {
query,
resolveInfo,
uris,
urls,
append: options.page && options.page !== 1,
pageSize: options.page_size
}
@ -2858,12 +2901,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]);
@ -2876,8 +2919,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
@ -2904,7 +2955,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);
}
@ -3376,13 +3427,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);
}
});
@ -3561,26 +3620,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;
}
});
@ -3792,17 +3850,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];
@ -4232,7 +4290,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,
@ -4282,7 +4340,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
@ -4892,6 +4950,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

@ -65,7 +65,7 @@ declare type VersionResponse = {
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

@ -65,7 +65,7 @@ declare type VersionResponse = {
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('@');
kauffj commented 2019-08-20 16:45:04 +02:00 (Migrated from github.com)
Review

This could possibly be removed or moved. The function is called parseURI, not parseAndValidateURI. It's one thing to error if invalid characters make it impossible to parse, but I think erroring on a validly structured URI is probably incorrect here.

This could possibly be removed or moved. The function is called parseURI, not parseAndValidateURI. It's one thing to error if invalid characters make it impossible to parse, but I think erroring on a validly structured URI is probably incorrect here.
neb-b commented 2019-08-20 18:52:19 +02:00 (Migrated from github.com)
Review

I like removing validation from this. We already have isURIValid to validate

I like removing validation from this. We already have `isURIValid` to validate
const isChannel = streamNameOrChannelName.startsWith('@') && !possibleStreamName;
const channelName = includesChannel && streamNameOrChannelName.slice(1);
if (isChannel) {
if (includesChannel) {
if (!channelName) {
neb-b commented 2019-08-19 06:04:26 +02:00 (Migrated from github.com)
Review

Not sure the best way to do this.

Should path include the claimId? Should there be path and pathClaimId?

Not sure the best way to do this. Should `path` include the claimId? Should there be `path` and `pathClaimId`?
kauffj commented 2019-08-20 16:49:01 +02:00 (Migrated from github.com)
Review

According to https://lbry.tech/spec#urls, the path should include the channel name, claim name, and any modifiers. The path is everything between the scheme and any query parameters.

Channel name, claim name, and any modifiers would be sub-properties of the path (and could potentially be returned/parsed out by this function as well).

@lyoshenka, please correct me if I'm wrong about this.

@seanyesmunt we should try to have this function use the same names and standards that the spec specifies. If you think we should do something different than what the spec says, we can also consider amending the spec.

According to https://lbry.tech/spec#urls, the `path` should include the channel name, claim name, and any modifiers. The path is everything between the scheme and any query parameters. Channel name, claim name, and any modifiers would be sub-properties of the path (and could potentially be returned/parsed out by this function as well). @lyoshenka, please correct me if I'm wrong about this. @seanyesmunt we should try to have this function use the same names and standards that the spec specifies. If you think we should do something different than what the spec says, we can also consider amending the spec.
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;
// Validate and process modifier (claim ID, bid position or claim sequence)
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 } : {}),
};
}
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) {
console.error(__("'contentName' should no longer be used. Use 'streamName' instead"));
}
}
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.'
)
);
}
if (!claimName && !channelName && !streamName) {
throw new Error(
__(
"'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(
kauffj commented 2019-08-20 16:49:52 +02:00 (Migrated from github.com)
Review

why keep two sets of URIs?

why keep two sets of URIs?
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);
kauffj commented 2019-08-20 16:50:34 +02:00 (Migrated from github.com)
Review

URI or URL? are we consistent with when we use one vs. the other?

URI or URL? are we consistent with when we use one vs. the other?
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"