UNPKG

@bluesky-social/syntax

Version:

Validation for atproto identifiers and formats: DID, handle, NSID, AT URI, etc

107 lines (105 loc) 3.99 kB
"use strict"; /* Grammar: alpha = "a" / "b" / "c" / "d" / "e" / "f" / "g" / "h" / "i" / "j" / "k" / "l" / "m" / "n" / "o" / "p" / "q" / "r" / "s" / "t" / "u" / "v" / "w" / "x" / "y" / "z" / "A" / "B" / "C" / "D" / "E" / "F" / "G" / "H" / "I" / "J" / "K" / "L" / "M" / "N" / "O" / "P" / "Q" / "R" / "S" / "T" / "U" / "V" / "W" / "X" / "Y" / "Z" number = "1" / "2" / "3" / "4" / "5" / "6" / "7" / "8" / "9" / "0" delim = "." segment = alpha *( alpha / number / "-" ) authority = segment *( delim segment ) name = alpha *( alpha ) nsid = authority delim name */ Object.defineProperty(exports, "__esModule", { value: true }); exports.InvalidNsidError = exports.ensureValidNsidRegex = exports.ensureValidNsid = exports.NSID = void 0; class NSID { static parse(nsid) { return new NSID(nsid); } static create(authority, name) { const segments = [...authority.split('.').reverse(), name].join('.'); return new NSID(segments); } static isValid(nsid) { try { NSID.parse(nsid); return true; } catch (e) { return false; } } constructor(nsid) { Object.defineProperty(this, "segments", { enumerable: true, configurable: true, writable: true, value: [] }); (0, exports.ensureValidNsid)(nsid); this.segments = nsid.split('.'); } get authority() { return this.segments .slice(0, this.segments.length - 1) .reverse() .join('.'); } get name() { return this.segments.at(this.segments.length - 1); } toString() { return this.segments.join('.'); } } exports.NSID = NSID; // Human readable constraints on NSID: // - a valid domain in reversed notation // - followed by an additional period-separated name, which is camel-case letters const ensureValidNsid = (nsid) => { const toCheck = nsid; // check that all chars are boring ASCII if (!/^[a-zA-Z0-9.-]*$/.test(toCheck)) { throw new InvalidNsidError('Disallowed characters in NSID (ASCII letters, digits, dashes, periods only)'); } if (toCheck.length > 253 + 1 + 63) { throw new InvalidNsidError('NSID is too long (317 chars max)'); } const labels = toCheck.split('.'); if (labels.length < 3) { throw new InvalidNsidError('NSID needs at least three parts'); } for (let i = 0; i < labels.length; i++) { const l = labels[i]; if (l.length < 1) { throw new InvalidNsidError('NSID parts can not be empty'); } if (l.length > 63) { throw new InvalidNsidError('NSID part too long (max 63 chars)'); } if (l.endsWith('-') || l.startsWith('-')) { throw new InvalidNsidError('NSID parts can not start or end with hyphen'); } if (/^[0-9]/.test(l) && i === 0) { throw new InvalidNsidError('NSID first part may not start with a digit'); } if (!/^[a-zA-Z][a-zA-Z0-9]*$/.test(l) && i + 1 === labels.length) { throw new InvalidNsidError('NSID name part must be only letters and digits (and no leading digit)'); } } }; exports.ensureValidNsid = ensureValidNsid; const ensureValidNsidRegex = (nsid) => { // simple regex to enforce most constraints via just regex and length. // hand wrote this regex based on above constraints if (!/^[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(\.[a-zA-Z]([a-zA-Z0-9]{0,62})?)$/.test(nsid)) { throw new InvalidNsidError("NSID didn't validate via regex"); } if (nsid.length > 253 + 1 + 63) { throw new InvalidNsidError('NSID is too long (317 chars max)'); } }; exports.ensureValidNsidRegex = ensureValidNsidRegex; class InvalidNsidError extends Error { } exports.InvalidNsidError = InvalidNsidError; //# sourceMappingURL=nsid.js.map