de08c04158
Still needs logic to notify the form when there's invalid input Add address validation to Send page Trim address input on Send page Adds trim prop to FormField Improve LBRY address regexp On Send page, don't prevent form submit, and only show error on blur This isn't a full fix (it only handles blur, it's still the form's job to do validation on submit). A proper solution to this (that can generalize to other forms) will probably involve looking at all of the inputs and asking each one whether it has an error, which will require some tricky state management. On Send page, use error message from daemon Update CHANGELOG.md Further improve invalid address regexp - Remove incorrect check for second character - Add "O" to chars excluded from b58 section - Allow for 33-character addresses Don't internationalize daemon error message on Send page remove console, add i18n
251 lines
6.9 KiB
JavaScript
251 lines
6.9 KiB
JavaScript
const CHANNEL_NAME_MIN_LEN = 4;
|
|
const CLAIM_ID_MAX_LEN = 40;
|
|
|
|
const lbryuri = {};
|
|
|
|
lbryuri.REGEXP_INVALID_URI = /[^A-Za-z0-9-]/g;
|
|
lbryuri.REGEXP_ADDRESS = /^b(?=[^0OIl]{32,33})[0-9A-Za-z]{32,33}$/;
|
|
|
|
/**
|
|
* 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)
|
|
* - isChannel (boolean)
|
|
* - contentName (string): For anon claims, the name; for channel claims, the path
|
|
* - channelName (string, if present): Channel name without @
|
|
*/
|
|
lbryuri.parse = function(uri, requireProto = false) {
|
|
// Break into components. Empty sub-matches are converted to null
|
|
const componentsRegex = new RegExp(
|
|
"^((?:lbry://)?)" + // protocol
|
|
"([^:$#/]*)" + // name (stops at the first separator or end)
|
|
"([:$#]?)([^/]*)" + // modifier separator, modifier (stops at the first path separator or end)
|
|
"(/?)(.*)" // path separator, path
|
|
);
|
|
const [proto, name, modSep, modVal, pathSep, path] = componentsRegex
|
|
.exec(uri)
|
|
.slice(1)
|
|
.map(match => match || null);
|
|
|
|
let contentName;
|
|
|
|
// Validate protocol
|
|
if (requireProto && !proto) {
|
|
throw new Error(__("LBRY URIs must include a protocol prefix (lbry://)."));
|
|
}
|
|
|
|
// Validate and process name
|
|
if (!name) {
|
|
throw new Error(__("URI does not include name."));
|
|
}
|
|
|
|
const isChannel = name.startsWith("@");
|
|
const channelName = isChannel ? name.slice(1) : name;
|
|
|
|
if (isChannel) {
|
|
if (!channelName) {
|
|
throw new Error(__("No channel name after @."));
|
|
}
|
|
|
|
if (channelName.length < CHANNEL_NAME_MIN_LEN) {
|
|
throw new Error(
|
|
__(
|
|
`Channel names must be at least %s characters.`,
|
|
CHANNEL_NAME_MIN_LEN
|
|
)
|
|
);
|
|
}
|
|
|
|
contentName = path;
|
|
}
|
|
|
|
const nameBadChars = (channelName || name).match(lbryuri.REGEXP_INVALID_URI);
|
|
if (nameBadChars) {
|
|
throw new Error(
|
|
__(
|
|
`Invalid character %s in name: %s.`,
|
|
nameBadChars.length == 1 ? "" : "s",
|
|
nameBadChars.join(", ")
|
|
)
|
|
);
|
|
}
|
|
|
|
// Validate and process modifier (claim ID, bid position or claim sequence)
|
|
let claimId, claimSequence, bidPosition;
|
|
if (modSep) {
|
|
if (!modVal) {
|
|
throw new Error(__(`No modifier provided after separator %s.`, modSep));
|
|
}
|
|
|
|
if (modSep == "#") {
|
|
claimId = modVal;
|
|
} else if (modSep == ":") {
|
|
claimSequence = modVal;
|
|
} else if (modSep == "$") {
|
|
bidPosition = modVal;
|
|
}
|
|
}
|
|
|
|
if (
|
|
claimId &&
|
|
(claimId.length > CLAIM_ID_MAX_LEN || !claimId.match(/^[0-9a-f]+$/)) &&
|
|
!claimId.match(/^pending/) //ought to be dropped when savePendingPublish drops hack
|
|
) {
|
|
throw new Error(__(`Invalid claim ID %s.`, claimId));
|
|
}
|
|
|
|
if (claimSequence && !claimSequence.match(/^-?[1-9][0-9]*$/)) {
|
|
throw new Error(__("Claim sequence must be a number."));
|
|
}
|
|
|
|
if (bidPosition && !bidPosition.match(/^-?[1-9][0-9]*$/)) {
|
|
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(lbryuri.REGEXP_INVALID_URI);
|
|
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 {
|
|
name,
|
|
path,
|
|
isChannel,
|
|
...(contentName ? { contentName } : {}),
|
|
...(channelName ? { channelName } : {}),
|
|
...(claimSequence ? { claimSequence: parseInt(claimSequence) } : {}),
|
|
...(bidPosition ? { bidPosition: parseInt(bidPosition) } : {}),
|
|
...(claimId ? { claimId } : {}),
|
|
...(path ? { path } : {}),
|
|
};
|
|
};
|
|
|
|
/**
|
|
* 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 (!name) {
|
|
name = 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."
|
|
)
|
|
);
|
|
}
|
|
}
|
|
|
|
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 adding the lbry:// prefix if needed) */
|
|
lbryuri.normalize = function(uri) {
|
|
if (uri.match(/pending_claim/)) return uri;
|
|
|
|
const { name, path, bidPosition, claimSequence, claimId } = lbryuri.parse(
|
|
uri
|
|
);
|
|
return lbryuri.build({ name, path, claimSequence, bidPosition, claimId });
|
|
};
|
|
|
|
lbryuri.isValid = function(uri) {
|
|
let parts;
|
|
try {
|
|
parts = lbryuri.parse(lbryuri.normalize(uri));
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
return parts && parts.name;
|
|
};
|
|
|
|
lbryuri.isValidName = function(name, checkCase = true) {
|
|
const regexp = new RegExp("^[a-z0-9-]+$", checkCase ? "" : "i");
|
|
return regexp.test(name);
|
|
};
|
|
|
|
lbryuri.isClaimable = function(uri) {
|
|
let parts;
|
|
try {
|
|
parts = lbryuri.parse(lbryuri.normalize(uri));
|
|
} catch (error) {
|
|
return false;
|
|
}
|
|
return (
|
|
parts &&
|
|
parts.name &&
|
|
!parts.claimId &&
|
|
!parts.bidPosition &&
|
|
!parts.claimSequence &&
|
|
!parts.isChannel &&
|
|
!parts.path
|
|
);
|
|
};
|
|
|
|
window.lbryuri = lbryuri;
|
|
export default lbryuri;
|