@@ -198,7 +198,7 @@ export let FileCardStream = React.createClass({
{title}
{ !this.props.hidePrice ? : null}
-
diff --git a/ui/js/lbry.js b/ui/js/lbry.js
index 244b82142..adc8ba4b4 100644
--- a/ui/js/lbry.js
+++ b/ui/js/lbry.js
@@ -1,7 +1,7 @@
import lbryio from './lbryio.js';
import lighthouse from './lighthouse.js';
import jsonrpc from './jsonrpc.js';
-import uri from './uri.js';
+import lbryuri from './lbryuri.js';
import {getLocal, getSession, setSession, setLocal} from './utils.js';
const {remote} = require('electron');
@@ -12,19 +12,19 @@ const menu = remote.require('./menu/main-menu');
* needed to make a dummy claim or file info object.
*/
function savePendingPublish({name, channel_name}) {
- let lbryUri;
+ let uri;
if (channel_name) {
- lbryUri = uri.buildLbryUri({name: channel_name, path: name}, false);
+ uri = lbryuri.build({name: channel_name, path: name}, false);
} else {
- lbryUri = uri.buildLbryUri({name: name}, false);
+ uri = lbryuri.build({name: name}, false);
}
const pendingPublishes = getLocal('pendingPublishes') || [];
const newPendingPublish = {
name, channel_name,
- claim_id: 'pending_claim_' + lbryUri,
- txid: 'pending_' + lbryUri,
+ claim_id: 'pending_claim_' + uri,
+ txid: 'pending_' + uri,
nout: 0,
- outpoint: 'pending_' + lbryUri + ':0',
+ outpoint: 'pending_' + uri + ':0',
time: Date.now(),
};
setLocal('pendingPublishes', [...pendingPublishes, newPendingPublish]);
@@ -215,35 +215,35 @@ lbry.getPeersForBlobHash = function(blobHash, callback) {
* from Lighthouse is included.
*/
lbry.costPromiseCache = {}
-lbry.getCostInfo = function(lbryUri) {
- if (lbry.costPromiseCache[lbryUri] === undefined) {
- lbry.costPromiseCache[lbryUri] = new Promise((resolve, reject) => {
+lbry.getCostInfo = function(uri) {
+ if (lbry.costPromiseCache[uri] === undefined) {
+ lbry.costPromiseCache[uri] = new Promise((resolve, reject) => {
const COST_INFO_CACHE_KEY = 'cost_info_cache';
let costInfoCache = getSession(COST_INFO_CACHE_KEY, {})
function cacheAndResolve(cost, includesData) {
- costInfoCache[lbryUri] = {cost, includesData};
+ costInfoCache[uri] = {cost, includesData};
setSession(COST_INFO_CACHE_KEY, costInfoCache);
resolve({cost, includesData});
}
- if (!lbryUri) {
+ if (!uri) {
return reject(new Error(`URI required.`));
}
- if (costInfoCache[lbryUri] && costInfoCache[lbryUri].cost) {
- return resolve(costInfoCache[lbryUri])
+ if (costInfoCache[uri] && costInfoCache[uri].cost) {
+ return resolve(costInfoCache[uri])
}
- function getCost(lbryUri, size) {
- lbry.stream_cost_estimate({uri: lbryUri, ... size !== null ? {size} : {}}).then((cost) => {
+ function getCost(uri, size) {
+ lbry.stream_cost_estimate({uri, ... size !== null ? {size} : {}}).then((cost) => {
cacheAndResolve(cost, size !== null);
}, reject);
}
- function getCostGenerous(lbryUri) {
+ function getCostGenerous(uri) {
// If generous is on, the calculation is simple enough that we might as well do it here in the front end
- lbry.resolve({uri: lbryUri}).then((resolutionInfo) => {
+ lbry.resolve({uri: uri}).then((resolutionInfo) => {
const fee = resolutionInfo.claim.value.stream.metadata.fee;
if (fee === undefined) {
cacheAndResolve(0, true);
@@ -257,12 +257,12 @@ lbry.getCostInfo = function(lbryUri) {
});
}
- const uriObj = uri.parseLbryUri(lbryUri);
+ const uriObj = lbryuri.parse(uri);
const name = uriObj.path || uriObj.name;
lbry.settings_get({allow_cached: true}).then(({is_generous_host}) => {
if (is_generous_host) {
- return getCostGenerous(lbryUri);
+ return getCostGenerous(uri);
}
lighthouse.get_size_for_name(name).then((size) => {
@@ -278,7 +278,7 @@ lbry.getCostInfo = function(lbryUri) {
});
});
}
- return lbry.costPromiseCache[lbryUri];
+ return lbry.costPromiseCache[uri];
}
lbry.getMyClaims = function(callback) {
diff --git a/ui/js/uri.js b/ui/js/lbryuri.js
similarity index 54%
rename from ui/js/uri.js
rename to ui/js/lbryuri.js
index 67615f7b5..55a964e66 100644
--- a/ui/js/uri.js
+++ b/ui/js/lbryuri.js
@@ -1,22 +1,31 @@
const CHANNEL_NAME_MIN_LEN = 4;
const CLAIM_ID_MAX_LEN = 40;
-const uri = {};
+const lbryuri = {};
/**
* 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)
- * - properName (string; strips off @ for channels)
- * - isChannel (boolean)
+ * - 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, if persent)
+ * - isChannel (boolean)
+ * - contentName (string): For anon claims, the name; for channel claims, the path
+ * - channelName (string, if present): Channel name without @
*/
-uri.parseLbryUri = function(lbryUri, requireProto=false) {
+lbryuri.parse = function(uri, requireProto=false) {
// Break into components. Empty sub-matches are converted to null
const componentsRegex = new RegExp(
'^((?:lbry:\/\/)?)' + // protocol
@@ -24,7 +33,9 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
'([:$#]?)([^/]*)' + // modifier separator, modifier (stops at the first path separator or end)
'(/?)(.*)' // path separator, path
);
- const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(lbryUri).slice(1).map(match => match || null);
+ const [proto, name, modSep, modVal, pathSep, path] = componentsRegex.exec(uri).slice(1).map(match => match || null);
+
+ let contentName;
// Validate protocol
if (requireProto && !proto) {
@@ -36,20 +47,22 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
throw new Error('URI does not include name.');
}
- const isChannel = name[0] == '@';
- const properName = isChannel ? name.substr(1) : name;
+ const isChannel = name.startsWith('@');
+ const channelName = isChannel ? name.slice(1) : name;
if (isChannel) {
- if (!properName) {
+ if (!channelName) {
throw new Error('No channel name after @.');
}
- if (properName.length < CHANNEL_NAME_MIN_LEN) {
+ if (channelName.length < CHANNEL_NAME_MIN_LEN) {
throw new Error(`Channel names must be at least ${CHANNEL_NAME_MIN_LEN} characters.`);
}
+
+ contentName = path;
}
- const nameBadChars = properName.match(/[^A-Za-z0-9-]/g);
+ const nameBadChars = (channelName || name).match(/[^A-Za-z0-9-]/g);
if (nameBadChars) {
throw new Error(`Invalid character${nameBadChars.length == 1 ? '' : 's'} in name: ${nameBadChars.join(', ')}.`);
}
@@ -82,7 +95,7 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
throw new Error('Bid position must be a number.');
}
- // Validate path
+ // Validate and process path
if (path) {
if (!isChannel) {
throw new Error('Only channel URIs may have a path.');
@@ -92,12 +105,16 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
if (pathBadChars) {
throw new Error(`Invalid character${count == 1 ? '' : 's'} in path: ${nameBadChars.join(', ')}`);
}
+
+ contentName = path;
} else if (pathSep) {
throw new Error('No path provided after /');
}
return {
- name, properName, isChannel,
+ name, path, isChannel,
+ ... contentName ? {contentName} : {},
+ ... channelName ? {channelName} : {},
... claimSequence ? {claimSequence: parseInt(claimSequence)} : {},
... bidPosition ? {bidPosition: parseInt(bidPosition)} : {},
... claimId ? {claimId} : {},
@@ -105,20 +122,45 @@ uri.parseLbryUri = function(lbryUri, requireProto=false) {
};
}
-uri.buildLbryUri = function(uriObj, includeProto=true) {
- const {name, claimId, claimSequence, bidPosition, path} = uriObj;
+/**
+ * Takes an object in the same format returned by lbryuri.parse() and builds a URI.
+ *
+ * The channelName key will accept names with or without the @ prefix.
+ */
+lbryuri.build = function(uriObj, includeProto=true, allowExtraProps=false) {
+ let {name, claimId, claimSequence, bidPosition, path, contentName, channelName} = uriObj;
+
+ if (channelName) {
+ const channelNameFormatted = channelName.startsWith('@') ? channelName : '@' + channelName;
+ if (!name) {
+ name = channelNameFormatted;
+ } else if (name !== channelNameFormatted) {
+ throw new Error('Received a channel content URI, but 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 (contentName) {
+ if (!path) {
+ path = contentName;
+ } else if (path !== contentName) {
+ throw new Error('path and contentName do not match. Only one is required; most likely you wanted contentName.');
+ }
+ }
return (includeProto ? 'lbry://' : '') + name +
(claimId ? `#${claimId}` : '') +
(claimSequence ? `:${claimSequence}` : '') +
(bidPosition ? `\$${bidPosition}` : '') +
(path ? `/${path}` : '');
+
}
/* Takes a parseable LBRY URI and converts it to standard, canonical format (currently this just
- * consists of making sure it has a lbry:// prefix) */
-uri.normalizeLbryUri = function(lbryUri) {
- return uri.buildLbryUri(uri.parseLbryUri(lbryUri));
+ * consists of adding the lbry:// prefix if needed) */
+lbryuri.normalize= function(uri) {
+ const {name, path, bidPosition, claimSequence, claimId} = lbryuri.parse(uri);
+ return lbryuri.build({name, path, claimSequence, bidPosition, claimId});
}
-export default uri;
+window.lbryuri = lbryuri;
+export default lbryuri;
diff --git a/ui/js/page/discover.js b/ui/js/page/discover.js
index fd0f25811..dc2811cfd 100644
--- a/ui/js/page/discover.js
+++ b/ui/js/page/discover.js
@@ -1,7 +1,7 @@
import React from 'react';
import lbry from '../lbry.js';
import lbryio from '../lbryio.js';
-import uri from '../uri.js';
+import lbryuri from '../lbryuri.js';
import lighthouse from '../lighthouse.js';
import {FileTile, FileTileStream} from '../component/file-tile.js';
import {Link} from '../component/link.js';
@@ -47,22 +47,14 @@ var SearchResults = React.createClass({
var rows = [],
seenNames = {}; //fix this when the search API returns claim IDs
for (let {name, claim, claim_id, channel_name, channel_id, txid, nout} of this.props.results) {
- let lbryUri;
- if (channel_name) {
- lbryUri = uri.buildLbryUri({
- name: channel_name,
- path: name,
- claimId: channel_id,
- });
- } else {
- lbryUri = uri.buildLbryUri({
- name: name,
- claimId: claim_id,
- })
- }
+ const uri = lbryuri.build({
+ channelName: channel_name,
+ contentName: name,
+ claimId: channel_id || claim_id,
+ });
rows.push(
-