URL Inspector — JavaScript source
Break any URL into its components - protocol, host, port, path, query params, hash, and credentials. Detects default ports and security at a glance, with a decode toggle for query values. Runs entirely in your browser.
This is the JavaScript implementation — the same logic the interactive tool runs, in a shareable, citable form.
/**
* url-inspector - JavaScript polyglot showcase port.
*
* Wraps the platform WHATWG URL parser in a pure, never-throwing function and
* exposes a flat, serialisable report of every URL component - including the
* signals the URL API hides (credentials, default-vs-explicit ports,
* root-only/fragment-only URLs).
*
* Display source - part of CosmoDev's polyglot tool pages.
* Ported from src/lib/url-inspector.ts (the canonical TypeScript lib).
*
* JavaScript is the closest sibling to the TypeScript original: it leans on
* the same platform `URL` / `URLSearchParams` globals, so the WHATWG
* normalisation (lowercased host, default-port elision, opaque-origin "null",
* path canonicalisation) is identical for free. The port drops the type
* annotations and uses modern ES module syntax.
*/
/**
* Well-known default ports per scheme. The WHATWG URL API normalises these
* away (https:443 → ""), so we keep the table to detect when an *explicitly
* written* port is in fact the scheme default.
*/
const DEFAULT_PORTS = {
'http:': '80',
'https:': '443',
'ftp:': '21',
'ws:': '80',
'wss:': '443',
};
/**
* Percent-decode a query value, treating '+' as a space; never throws.
*
* `decodeURIComponent` is the WHATWG-aligned decoder, but unlike
* `URLSearchParams` it does NOT convert '+', so we do that first by hand.
* Malformed percent-encoding (e.g. a stray '%') would make it throw - we
* swallow that and return the original string verbatim.
*/
export function decodeParam(v) {
try {
return decodeURIComponent(v.replace(/\+/g, ' '));
} catch {
return v; // malformed percent-encoding - return the original
}
}
/**
* Read an explicitly-written port straight from the raw input.
*
* The WHATWG URL API normalises default ports (https:443 → '') away, so to
* report "port 443 is the default" we have to re-parse the authority segment
* ourselves. Handles userinfo (`user:pass@`) and IPv6 literals (`[::1]:8080`).
*
* Returns `undefined` when no port is present or what follows the colon is
* non-numeric.
*/
function rawPort(trimmed) {
// Scheme must start with a letter, then alnum / '+' / '-' / '.', then "://".
// [\s\S]* matches the "rest" including newlines (the TS used the /s flag).
const schemeMatch = trimmed.match(/^([a-zA-Z][a-zA-Z0-9+.-]*):\/\/([\s\S]*)$/);
if (!schemeMatch) return undefined;
const rest = schemeMatch[2];
// The authority runs until the first path/query/fragment delimiter.
const authorityEnd = rest.search(/[/?#]/);
const authority = authorityEnd === -1 ? rest : rest.slice(0, authorityEnd);
// Drop userinfo: everything up to the LAST '@' belongs to credentials.
const atIdx = authority.lastIndexOf('@');
const hostport = atIdx === -1 ? authority : authority.slice(atIdx + 1);
let portCandidate;
if (hostport.startsWith('[')) {
// IPv6 literal - the port (if any) lives after the closing bracket.
const closeBracket = hostport.indexOf(']');
if (closeBracket === -1) return undefined; // unterminated bracket
const tail = hostport.slice(closeBracket + 1);
if (tail.startsWith(':')) portCandidate = tail.slice(1);
} else {
const colon = hostport.indexOf(':');
if (colon !== -1) portCandidate = hostport.slice(colon + 1);
}
if (portCandidate === undefined) return undefined;
return /^\d+$/.test(portCandidate) ? portCandidate : undefined;
}
/** Parse and decompose a URL into a structured report; never throws. */
export function inspectUrl(raw) {
const warnings = [];
const trimmed = (raw ?? '').trim();
if (!trimmed) {
return { valid: false, warnings: ['URL is empty'] };
}
let url;
try {
url = new URL(trimmed);
} catch {
return {
valid: false,
warnings: ['Invalid URL - could not be parsed (include the scheme, e.g. https://)'],
};
}
// Preserve insertion order AND duplicates (URLSearchParams.forEach does both).
const searchParams = [];
url.searchParams.forEach((value, key) => searchParams.push({ key, value }));
if (url.username) warnings.push('URL contains a username credential');
if (url.password) warnings.push('URL contains a password credential');
// Recover the explicitly-written port and check it against the scheme default.
const explicitPort = rawPort(trimmed);
let isDefaultPort;
if (explicitPort !== undefined) {
const expected = DEFAULT_PORTS[url.protocol];
isDefaultPort = expected ? explicitPort === expected : false;
if (isDefaultPort) {
warnings.push(`Port ${explicitPort} is the default for ${url.protocol}`);
}
}
if (url.pathname === '/' && !url.search && searchParams.length === 0) {
warnings.push('URL points to the site root (no path or query)');
}
const isSecure = url.protocol === 'https:' || url.protocol === 'wss:';
return {
valid: true,
protocol: url.protocol,
username: url.username || undefined,
password: url.password || undefined,
host: url.host,
hostname: url.hostname,
port: explicitPort,
pathname: url.pathname,
search: url.search || undefined,
hash: url.hash || undefined,
searchParams,
// WHATWG yields the literal string "null" for opaque origins (file:, data:…).
origin: url.origin !== 'null' ? url.origin : undefined,
isSecure,
defaultPort: isDefaultPort,
warnings,
};
}
export { DEFAULT_PORTS };
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →