@virtualstate/navigation
Version:
Native JavaScript [navigation](https://developer.mozilla.org/en-US/docs/Web/API/Navigation_API) implementation
1,662 lines (1,651 loc) • 129 kB
JavaScript
// src/path-to-regex-modified.ts
var Part = class {
constructor(type, name, prefix, value, suffix, modifier) {
this.type = 3 /* kFixed */;
this.name = "";
this.prefix = "";
this.value = "";
this.suffix = "";
this.modifier = 3 /* kNone */;
this.type = type;
this.name = name;
this.prefix = prefix;
this.value = value;
this.suffix = suffix;
this.modifier = modifier;
}
hasCustomName() {
return this.name !== "" && typeof this.name !== "number";
}
};
var regexIdentifierStart = /[$_\p{ID_Start}]/u;
var regexIdentifierPart = /[$_\u200C\u200D\p{ID_Continue}]/u;
var kFullWildcardRegex = ".*";
function isASCII(str, extended) {
return (extended ? /^[\x00-\xFF]*$/ : /^[\x00-\x7F]*$/).test(str);
}
function lexer(str, lenient = false) {
const tokens = [];
let i = 0;
while (i < str.length) {
const char = str[i];
const ErrorOrInvalid = function(msg) {
if (!lenient)
throw new TypeError(msg);
tokens.push({ type: "INVALID_CHAR", index: i, value: str[i++] });
};
if (char === "*") {
tokens.push({ type: "ASTERISK", index: i, value: str[i++] });
continue;
}
if (char === "+" || char === "?") {
tokens.push({ type: "OTHER_MODIFIER", index: i, value: str[i++] });
continue;
}
if (char === "\\") {
tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
continue;
}
if (char === "{") {
tokens.push({ type: "OPEN", index: i, value: str[i++] });
continue;
}
if (char === "}") {
tokens.push({ type: "CLOSE", index: i, value: str[i++] });
continue;
}
if (char === ":") {
let name = "";
let j = i + 1;
while (j < str.length) {
const code = str.substr(j, 1);
if (j === i + 1 && regexIdentifierStart.test(code) || j !== i + 1 && regexIdentifierPart.test(code)) {
name += str[j++];
continue;
}
break;
}
if (!name) {
ErrorOrInvalid(`Missing parameter name at ${i}`);
continue;
}
tokens.push({ type: "NAME", index: i, value: name });
i = j;
continue;
}
if (char === "(") {
let count = 1;
let pattern = "";
let j = i + 1;
let error = false;
if (str[j] === "?") {
ErrorOrInvalid(`Pattern cannot start with "?" at ${j}`);
continue;
}
while (j < str.length) {
if (!isASCII(str[j], false)) {
ErrorOrInvalid(`Invalid character '${str[j]}' at ${j}.`);
error = true;
break;
}
if (str[j] === "\\") {
pattern += str[j++] + str[j++];
continue;
}
if (str[j] === ")") {
count--;
if (count === 0) {
j++;
break;
}
} else if (str[j] === "(") {
count++;
if (str[j + 1] !== "?") {
ErrorOrInvalid(`Capturing groups are not allowed at ${j}`);
error = true;
break;
}
}
pattern += str[j++];
}
if (error) {
continue;
}
if (count) {
ErrorOrInvalid(`Unbalanced pattern at ${i}`);
continue;
}
if (!pattern) {
ErrorOrInvalid(`Missing pattern at ${i}`);
continue;
}
tokens.push({ type: "REGEX", index: i, value: pattern });
i = j;
continue;
}
tokens.push({ type: "CHAR", index: i, value: str[i++] });
}
tokens.push({ type: "END", index: i, value: "" });
return tokens;
}
function parse(str, options = {}) {
const tokens = lexer(str);
options.delimiter ?? (options.delimiter = "/#?");
options.prefixes ?? (options.prefixes = "./");
const segmentWildcardRegex = `[^${escapeString(options.delimiter)}]+?`;
const result = [];
let key = 0;
let i = 0;
let nameSet = /* @__PURE__ */ new Set();
const tryConsume = (type) => {
if (i < tokens.length && tokens[i].type === type)
return tokens[i++].value;
};
const tryConsumeModifier = () => {
return tryConsume("OTHER_MODIFIER") ?? tryConsume("ASTERISK");
};
const mustConsume = (type) => {
const value = tryConsume(type);
if (value !== void 0)
return value;
const { type: nextType, index } = tokens[i];
throw new TypeError(`Unexpected ${nextType} at ${index}, expected ${type}`);
};
const consumeText = () => {
let result2 = "";
let value;
while (value = tryConsume("CHAR") ?? tryConsume("ESCAPED_CHAR")) {
result2 += value;
}
return result2;
};
const DefaultEncodePart = (value) => {
return value;
};
const encodePart = options.encodePart || DefaultEncodePart;
let pendingFixedValue = "";
const appendToPendingFixedValue = (value) => {
pendingFixedValue += value;
};
const maybeAddPartFromPendingFixedValue = () => {
if (!pendingFixedValue.length) {
return;
}
result.push(new Part(3 /* kFixed */, "", "", encodePart(pendingFixedValue), "", 3 /* kNone */));
pendingFixedValue = "";
};
const addPart = (prefix, nameToken, regexOrWildcardToken, suffix, modifierToken) => {
let modifier = 3 /* kNone */;
switch (modifierToken) {
case "?":
modifier = 1 /* kOptional */;
break;
case "*":
modifier = 0 /* kZeroOrMore */;
break;
case "+":
modifier = 2 /* kOneOrMore */;
break;
}
if (!nameToken && !regexOrWildcardToken && modifier === 3 /* kNone */) {
appendToPendingFixedValue(prefix);
return;
}
maybeAddPartFromPendingFixedValue();
if (!nameToken && !regexOrWildcardToken) {
if (!prefix) {
return;
}
result.push(new Part(3 /* kFixed */, "", "", encodePart(prefix), "", modifier));
return;
}
let regexValue;
if (!regexOrWildcardToken) {
regexValue = segmentWildcardRegex;
} else if (regexOrWildcardToken === "*") {
regexValue = kFullWildcardRegex;
} else {
regexValue = regexOrWildcardToken;
}
let type = 2 /* kRegex */;
if (regexValue === segmentWildcardRegex) {
type = 1 /* kSegmentWildcard */;
regexValue = "";
} else if (regexValue === kFullWildcardRegex) {
type = 0 /* kFullWildcard */;
regexValue = "";
}
let name;
if (nameToken) {
name = nameToken;
} else if (regexOrWildcardToken) {
name = key++;
}
if (nameSet.has(name)) {
throw new TypeError(`Duplicate name '${name}'.`);
}
nameSet.add(name);
result.push(new Part(type, name, encodePart(prefix), regexValue, encodePart(suffix), modifier));
};
while (i < tokens.length) {
const charToken = tryConsume("CHAR");
const nameToken = tryConsume("NAME");
let regexOrWildcardToken = tryConsume("REGEX");
if (!nameToken && !regexOrWildcardToken) {
regexOrWildcardToken = tryConsume("ASTERISK");
}
if (nameToken || regexOrWildcardToken) {
let prefix = charToken ?? "";
if (options.prefixes.indexOf(prefix) === -1) {
appendToPendingFixedValue(prefix);
prefix = "";
}
maybeAddPartFromPendingFixedValue();
let modifierToken = tryConsumeModifier();
addPart(prefix, nameToken, regexOrWildcardToken, "", modifierToken);
continue;
}
const value = charToken ?? tryConsume("ESCAPED_CHAR");
if (value) {
appendToPendingFixedValue(value);
continue;
}
const openToken = tryConsume("OPEN");
if (openToken) {
const prefix = consumeText();
const nameToken2 = tryConsume("NAME");
let regexOrWildcardToken2 = tryConsume("REGEX");
if (!nameToken2 && !regexOrWildcardToken2) {
regexOrWildcardToken2 = tryConsume("ASTERISK");
}
const suffix = consumeText();
mustConsume("CLOSE");
const modifierToken = tryConsumeModifier();
addPart(prefix, nameToken2, regexOrWildcardToken2, suffix, modifierToken);
continue;
}
maybeAddPartFromPendingFixedValue();
mustConsume("END");
}
return result;
}
function escapeString(str) {
return str.replace(/([.+*?^${}()[\]|/\\])/g, "\\$1");
}
function flags(options) {
return options && options.ignoreCase ? "ui" : "u";
}
function stringToRegexp(path, names, options) {
return partsToRegexp(parse(path, options), names, options);
}
function modifierToString(modifier) {
switch (modifier) {
case 0 /* kZeroOrMore */:
return "*";
case 1 /* kOptional */:
return "?";
case 2 /* kOneOrMore */:
return "+";
case 3 /* kNone */:
return "";
}
}
function partsToRegexp(parts, names, options = {}) {
options.delimiter ?? (options.delimiter = "/#?");
options.prefixes ?? (options.prefixes = "./");
options.sensitive ?? (options.sensitive = false);
options.strict ?? (options.strict = false);
options.end ?? (options.end = true);
options.start ?? (options.start = true);
options.endsWith = "";
let result = options.start ? "^" : "";
for (const part of parts) {
if (part.type === 3 /* kFixed */) {
if (part.modifier === 3 /* kNone */) {
result += escapeString(part.value);
} else {
result += `(?:${escapeString(part.value)})${modifierToString(part.modifier)}`;
}
continue;
}
if (names)
names.push(part.name);
const segmentWildcardRegex = `[^${escapeString(options.delimiter)}]+?`;
let regexValue = part.value;
if (part.type === 1 /* kSegmentWildcard */)
regexValue = segmentWildcardRegex;
else if (part.type === 0 /* kFullWildcard */)
regexValue = kFullWildcardRegex;
if (!part.prefix.length && !part.suffix.length) {
if (part.modifier === 3 /* kNone */ || part.modifier === 1 /* kOptional */) {
result += `(${regexValue})${modifierToString(part.modifier)}`;
} else {
result += `((?:${regexValue})${modifierToString(part.modifier)})`;
}
continue;
}
if (part.modifier === 3 /* kNone */ || part.modifier === 1 /* kOptional */) {
result += `(?:${escapeString(part.prefix)}(${regexValue})${escapeString(part.suffix)})`;
result += modifierToString(part.modifier);
continue;
}
result += `(?:${escapeString(part.prefix)}`;
result += `((?:${regexValue})(?:`;
result += escapeString(part.suffix);
result += escapeString(part.prefix);
result += `(?:${regexValue}))*)${escapeString(part.suffix)})`;
if (part.modifier === 0 /* kZeroOrMore */) {
result += "?";
}
}
const endsWith = `[${escapeString(options.endsWith)}]|$`;
const delimiter = `[${escapeString(options.delimiter)}]`;
if (options.end) {
if (!options.strict) {
result += `${delimiter}?`;
}
if (!options.endsWith.length) {
result += "$";
} else {
result += `(?=${endsWith})`;
}
return new RegExp(result, flags(options));
}
if (!options.strict) {
result += `(?:${delimiter}(?=${endsWith}))?`;
}
let isEndDelimited = false;
if (parts.length) {
const lastPart = parts[parts.length - 1];
if (lastPart.type === 3 /* kFixed */ && lastPart.modifier === 3 /* kNone */) {
isEndDelimited = options.delimiter.indexOf(lastPart) > -1;
}
}
if (!isEndDelimited) {
result += `(?=${delimiter}|${endsWith})`;
}
return new RegExp(result, flags(options));
}
// src/url-utils.ts
var DEFAULT_OPTIONS = {
delimiter: "",
prefixes: "",
sensitive: true,
strict: true
};
var HOSTNAME_OPTIONS = {
delimiter: ".",
prefixes: "",
sensitive: true,
strict: true
};
var PATHNAME_OPTIONS = {
delimiter: "/",
prefixes: "/",
sensitive: true,
strict: true
};
function isAbsolutePathname(pathname, isPattern) {
if (!pathname.length) {
return false;
}
if (pathname[0] === "/") {
return true;
}
if (!isPattern) {
return false;
}
if (pathname.length < 2) {
return false;
}
if ((pathname[0] == "\\" || pathname[0] == "{") && pathname[1] == "/") {
return true;
}
return false;
}
function maybeStripPrefix(value, prefix) {
if (value.startsWith(prefix)) {
return value.substring(prefix.length, value.length);
}
return value;
}
function maybeStripSuffix(value, suffix) {
if (value.endsWith(suffix)) {
return value.substr(0, value.length - suffix.length);
}
return value;
}
function treatAsIPv6Hostname(value) {
if (!value || value.length < 2) {
return false;
}
if (value[0] === "[") {
return true;
}
if ((value[0] === "\\" || value[0] === "{") && value[1] === "[") {
return true;
}
return false;
}
var SPECIAL_SCHEMES = [
"ftp",
"file",
"http",
"https",
"ws",
"wss"
];
function isSpecialScheme(protocol_regexp) {
if (!protocol_regexp) {
return true;
}
for (const scheme of SPECIAL_SCHEMES) {
if (protocol_regexp.test(scheme)) {
return true;
}
}
return false;
}
function canonicalizeHash(hash, isPattern) {
hash = maybeStripPrefix(hash, "#");
if (isPattern || hash === "") {
return hash;
}
const url = new URL("https://example.com");
url.hash = hash;
return url.hash ? url.hash.substring(1, url.hash.length) : "";
}
function canonicalizeSearch(search, isPattern) {
search = maybeStripPrefix(search, "?");
if (isPattern || search === "") {
return search;
}
const url = new URL("https://example.com");
url.search = search;
return url.search ? url.search.substring(1, url.search.length) : "";
}
function canonicalizeHostname(hostname, isPattern) {
if (isPattern || hostname === "") {
return hostname;
}
if (treatAsIPv6Hostname(hostname)) {
return ipv6HostnameEncodeCallback(hostname);
} else {
return hostnameEncodeCallback(hostname);
}
}
function canonicalizePassword(password, isPattern) {
if (isPattern || password === "") {
return password;
}
const url = new URL("https://example.com");
url.password = password;
return url.password;
}
function canonicalizeUsername(username, isPattern) {
if (isPattern || username === "") {
return username;
}
const url = new URL("https://example.com");
url.username = username;
return url.username;
}
function canonicalizePathname(pathname, protocol, isPattern) {
if (isPattern || pathname === "") {
return pathname;
}
if (protocol && !SPECIAL_SCHEMES.includes(protocol)) {
const url = new URL(`${protocol}:${pathname}`);
return url.pathname;
}
const leadingSlash = pathname[0] == "/";
pathname = new URL(
!leadingSlash ? "/-" + pathname : pathname,
"https://example.com"
).pathname;
if (!leadingSlash) {
pathname = pathname.substring(2, pathname.length);
}
return pathname;
}
function canonicalizePort(port, protocol, isPattern) {
if (defaultPortForProtocol(protocol) === port) {
port = "";
}
if (isPattern || port === "") {
return port;
}
return portEncodeCallback(port);
}
function canonicalizeProtocol(protocol, isPattern) {
protocol = maybeStripSuffix(protocol, ":");
if (isPattern || protocol === "") {
return protocol;
}
return protocolEncodeCallback(protocol);
}
function defaultPortForProtocol(protocol) {
switch (protocol) {
case "ws":
case "http":
return "80";
case "wws":
case "https":
return "443";
case "ftp":
return "21";
default:
return "";
}
}
function protocolEncodeCallback(input) {
if (input === "") {
return input;
}
if (/^[-+.A-Za-z0-9]*$/.test(input))
return input.toLowerCase();
throw new TypeError(`Invalid protocol '${input}'.`);
}
function usernameEncodeCallback(input) {
if (input === "") {
return input;
}
const url = new URL("https://example.com");
url.username = input;
return url.username;
}
function passwordEncodeCallback(input) {
if (input === "") {
return input;
}
const url = new URL("https://example.com");
url.password = input;
return url.password;
}
function hostnameEncodeCallback(input) {
if (input === "") {
return input;
}
if (/[\t\n\r #%/:<>?@[\]^\\|]/g.test(input)) {
throw new TypeError(`Invalid hostname '${input}'`);
}
const url = new URL("https://example.com");
url.hostname = input;
return url.hostname;
}
function ipv6HostnameEncodeCallback(input) {
if (input === "") {
return input;
}
if (/[^0-9a-fA-F[\]:]/g.test(input)) {
throw new TypeError(`Invalid IPv6 hostname '${input}'`);
}
return input.toLowerCase();
}
function portEncodeCallback(input) {
if (input === "") {
return input;
}
if (/^[0-9]*$/.test(input) && parseInt(input) <= 65535) {
return input;
}
throw new TypeError(`Invalid port '${input}'.`);
}
function standardURLPathnameEncodeCallback(input) {
if (input === "") {
return input;
}
const url = new URL("https://example.com");
url.pathname = input[0] !== "/" ? "/-" + input : input;
if (input[0] !== "/") {
return url.pathname.substring(2, url.pathname.length);
}
return url.pathname;
}
function pathURLPathnameEncodeCallback(input) {
if (input === "") {
return input;
}
const url = new URL(`data:${input}`);
return url.pathname;
}
function searchEncodeCallback(input) {
if (input === "") {
return input;
}
const url = new URL("https://example.com");
url.search = input;
return url.search.substring(1, url.search.length);
}
function hashEncodeCallback(input) {
if (input === "") {
return input;
}
const url = new URL("https://example.com");
url.hash = input;
return url.hash.substring(1, url.hash.length);
}
// src/url-pattern-parser.ts
var Parser = class {
constructor(input) {
// The list of `LexToken`s produced by the path-to-regexp `lexer()` function
// when passed `input` with lenient mode enabled.
this.tokenList = [];
// As we parse the input string we populate a `URLPatternInit` dictionary
// with each component pattern. This is then the final result of the parse.
this.internalResult = {};
// The index of the current `LexToken` being considered.
this.tokenIndex = 0;
// The value to add to `tokenIndex` on each turn through the parse loop.
// While typically this is `1`, it is also set to `0` at times for things
// like state transitions, etc. It is automatically reset back to `1` at
// the top of the parse loop.
this.tokenIncrement = 1;
// The index of the first `LexToken` to include in the component string.
this.componentStart = 0;
// The current parse state. This should only be changed via `changeState()`
// or `rewindAndSetState()`.
this.state = 0 /* INIT */;
// The current nest depth of `{ }` pattern groupings.
this.groupDepth = 0;
// The current nesting depth of `[ ]` in hostname patterns.
this.hostnameIPv6BracketDepth = 0;
// True if we should apply parse rules as if this is a "standard" URL. If
// false then this is treated as a "not a base URL".
this.shouldTreatAsStandardURL = false;
this.input = input;
}
// Return the parse result. The result is only available after the
// `parse()` method completes.
get result() {
return this.internalResult;
}
// Attempt to parse the input string used to construct the Parser object.
// This method may only be called once. Any errors will be thrown as an
// exception. Retrieve the parse result by accessing the `Parser.result`
// property getter.
parse() {
this.tokenList = lexer(
this.input,
/*lenient=*/
true
);
for (; this.tokenIndex < this.tokenList.length; this.tokenIndex += this.tokenIncrement) {
this.tokenIncrement = 1;
if (this.tokenList[this.tokenIndex].type === "END") {
if (this.state === 0 /* INIT */) {
this.rewind();
if (this.isHashPrefix()) {
this.changeState(
9 /* HASH */,
/*skip=*/
1
);
} else if (this.isSearchPrefix()) {
this.changeState(
8 /* SEARCH */,
/*skip=*/
1
);
this.internalResult.hash = "";
} else {
this.changeState(
7 /* PATHNAME */,
/*skip=*/
0
);
this.internalResult.search = "";
this.internalResult.hash = "";
}
continue;
} else if (this.state === 2 /* AUTHORITY */) {
this.rewindAndSetState(5 /* HOSTNAME */);
continue;
}
this.changeState(
10 /* DONE */,
/*skip=*/
0
);
break;
}
if (this.groupDepth > 0) {
if (this.isGroupClose()) {
this.groupDepth -= 1;
} else {
continue;
}
}
if (this.isGroupOpen()) {
this.groupDepth += 1;
continue;
}
switch (this.state) {
case 0 /* INIT */:
if (this.isProtocolSuffix()) {
this.internalResult.username = "";
this.internalResult.password = "";
this.internalResult.hostname = "";
this.internalResult.port = "";
this.internalResult.pathname = "";
this.internalResult.search = "";
this.internalResult.hash = "";
this.rewindAndSetState(1 /* PROTOCOL */);
}
break;
case 1 /* PROTOCOL */:
if (this.isProtocolSuffix()) {
this.computeShouldTreatAsStandardURL();
let nextState = 7 /* PATHNAME */;
let skip = 1;
if (this.shouldTreatAsStandardURL) {
this.internalResult.pathname = "/";
}
if (this.nextIsAuthoritySlashes()) {
nextState = 2 /* AUTHORITY */;
skip = 3;
} else if (this.shouldTreatAsStandardURL) {
nextState = 2 /* AUTHORITY */;
}
this.changeState(nextState, skip);
}
break;
case 2 /* AUTHORITY */:
if (this.isIdentityTerminator()) {
this.rewindAndSetState(3 /* USERNAME */);
} else if (this.isPathnameStart() || this.isSearchPrefix() || this.isHashPrefix()) {
this.rewindAndSetState(5 /* HOSTNAME */);
}
break;
case 3 /* USERNAME */:
if (this.isPasswordPrefix()) {
this.changeState(
4 /* PASSWORD */,
/*skip=*/
1
);
} else if (this.isIdentityTerminator()) {
this.changeState(
5 /* HOSTNAME */,
/*skip=*/
1
);
}
break;
case 4 /* PASSWORD */:
if (this.isIdentityTerminator()) {
this.changeState(
5 /* HOSTNAME */,
/*skip=*/
1
);
}
break;
case 5 /* HOSTNAME */:
if (this.isIPv6Open()) {
this.hostnameIPv6BracketDepth += 1;
} else if (this.isIPv6Close()) {
this.hostnameIPv6BracketDepth -= 1;
}
if (this.isPortPrefix() && !this.hostnameIPv6BracketDepth) {
this.changeState(
6 /* PORT */,
/*skip=*/
1
);
} else if (this.isPathnameStart()) {
this.changeState(
7 /* PATHNAME */,
/*skip=*/
0
);
} else if (this.isSearchPrefix()) {
this.changeState(
8 /* SEARCH */,
/*skip=*/
1
);
} else if (this.isHashPrefix()) {
this.changeState(
9 /* HASH */,
/*skip=*/
1
);
}
break;
case 6 /* PORT */:
if (this.isPathnameStart()) {
this.changeState(
7 /* PATHNAME */,
/*skip=*/
0
);
} else if (this.isSearchPrefix()) {
this.changeState(
8 /* SEARCH */,
/*skip=*/
1
);
} else if (this.isHashPrefix()) {
this.changeState(
9 /* HASH */,
/*skip=*/
1
);
}
break;
case 7 /* PATHNAME */:
if (this.isSearchPrefix()) {
this.changeState(
8 /* SEARCH */,
/*skip=*/
1
);
} else if (this.isHashPrefix()) {
this.changeState(
9 /* HASH */,
/*skip=*/
1
);
}
break;
case 8 /* SEARCH */:
if (this.isHashPrefix()) {
this.changeState(
9 /* HASH */,
/*skip=*/
1
);
}
break;
}
}
}
changeState(newState, skip) {
switch (this.state) {
case 0 /* INIT */:
break;
case 1 /* PROTOCOL */:
this.internalResult.protocol = this.makeComponentString();
break;
case 2 /* AUTHORITY */:
break;
case 3 /* USERNAME */:
this.internalResult.username = this.makeComponentString();
break;
case 4 /* PASSWORD */:
this.internalResult.password = this.makeComponentString();
break;
case 5 /* HOSTNAME */:
this.internalResult.hostname = this.makeComponentString();
break;
case 6 /* PORT */:
this.internalResult.port = this.makeComponentString();
break;
case 7 /* PATHNAME */:
this.internalResult.pathname = this.makeComponentString();
break;
case 8 /* SEARCH */:
this.internalResult.search = this.makeComponentString();
break;
case 9 /* HASH */:
this.internalResult.hash = this.makeComponentString();
break;
}
this.changeStateWithoutSettingComponent(newState, skip);
}
changeStateWithoutSettingComponent(newState, skip) {
this.state = newState;
this.componentStart = this.tokenIndex + skip;
this.tokenIndex += skip;
this.tokenIncrement = 0;
}
rewind() {
this.tokenIndex = this.componentStart;
this.tokenIncrement = 0;
}
rewindAndSetState(newState) {
this.rewind();
this.state = newState;
}
safeToken(index) {
if (index < 0) {
index = this.tokenList.length - index;
}
if (index < this.tokenList.length) {
return this.tokenList[index];
}
return this.tokenList[this.tokenList.length - 1];
}
isNonSpecialPatternChar(index, value) {
const token = this.safeToken(index);
return token.value === value && (token.type === "CHAR" || token.type === "ESCAPED_CHAR" || token.type === "INVALID_CHAR");
}
isProtocolSuffix() {
return this.isNonSpecialPatternChar(this.tokenIndex, ":");
}
nextIsAuthoritySlashes() {
return this.isNonSpecialPatternChar(this.tokenIndex + 1, "/") && this.isNonSpecialPatternChar(this.tokenIndex + 2, "/");
}
isIdentityTerminator() {
return this.isNonSpecialPatternChar(this.tokenIndex, "@");
}
isPasswordPrefix() {
return this.isNonSpecialPatternChar(this.tokenIndex, ":");
}
isPortPrefix() {
return this.isNonSpecialPatternChar(this.tokenIndex, ":");
}
isPathnameStart() {
return this.isNonSpecialPatternChar(this.tokenIndex, "/");
}
isSearchPrefix() {
if (this.isNonSpecialPatternChar(this.tokenIndex, "?")) {
return true;
}
if (this.tokenList[this.tokenIndex].value !== "?") {
return false;
}
const previousToken = this.safeToken(this.tokenIndex - 1);
return previousToken.type !== "NAME" && previousToken.type !== "REGEX" && previousToken.type !== "CLOSE" && previousToken.type !== "ASTERISK";
}
isHashPrefix() {
return this.isNonSpecialPatternChar(this.tokenIndex, "#");
}
isGroupOpen() {
return this.tokenList[this.tokenIndex].type == "OPEN";
}
isGroupClose() {
return this.tokenList[this.tokenIndex].type == "CLOSE";
}
isIPv6Open() {
return this.isNonSpecialPatternChar(this.tokenIndex, "[");
}
isIPv6Close() {
return this.isNonSpecialPatternChar(this.tokenIndex, "]");
}
makeComponentString() {
const token = this.tokenList[this.tokenIndex];
const componentCharStart = this.safeToken(this.componentStart).index;
return this.input.substring(componentCharStart, token.index);
}
computeShouldTreatAsStandardURL() {
const options = {};
Object.assign(options, DEFAULT_OPTIONS);
options.encodePart = protocolEncodeCallback;
const regexp = stringToRegexp(
this.makeComponentString(),
/*keys=*/
void 0,
options
);
this.shouldTreatAsStandardURL = isSpecialScheme(regexp);
}
};
// src/url-pattern.ts
var COMPONENTS = [
"protocol",
"username",
"password",
"hostname",
"port",
"pathname",
"search",
"hash"
];
var DEFAULT_PATTERN = "*";
function extractValues(url, baseURL) {
if (typeof url !== "string") {
throw new TypeError(`parameter 1 is not of type 'string'.`);
}
const o = new URL(url, baseURL);
return {
protocol: o.protocol.substring(0, o.protocol.length - 1),
username: o.username,
password: o.password,
hostname: o.hostname,
port: o.port,
pathname: o.pathname,
search: o.search !== "" ? o.search.substring(1, o.search.length) : void 0,
hash: o.hash !== "" ? o.hash.substring(1, o.hash.length) : void 0
};
}
function processBaseURLString(input, isPattern) {
if (!isPattern) {
return input;
}
return escapePatternString(input);
}
function applyInit(o, init, isPattern) {
let baseURL;
if (typeof init.baseURL === "string") {
try {
baseURL = new URL(init.baseURL);
o.protocol = processBaseURLString(baseURL.protocol.substring(0, baseURL.protocol.length - 1), isPattern);
o.username = processBaseURLString(baseURL.username, isPattern);
o.password = processBaseURLString(baseURL.password, isPattern);
o.hostname = processBaseURLString(baseURL.hostname, isPattern);
o.port = processBaseURLString(baseURL.port, isPattern);
o.pathname = processBaseURLString(baseURL.pathname, isPattern);
o.search = processBaseURLString(baseURL.search.substring(1, baseURL.search.length), isPattern);
o.hash = processBaseURLString(baseURL.hash.substring(1, baseURL.hash.length), isPattern);
} catch {
throw new TypeError(`invalid baseURL '${init.baseURL}'.`);
}
}
if (typeof init.protocol === "string") {
o.protocol = canonicalizeProtocol(init.protocol, isPattern);
}
if (typeof init.username === "string") {
o.username = canonicalizeUsername(init.username, isPattern);
}
if (typeof init.password === "string") {
o.password = canonicalizePassword(init.password, isPattern);
}
if (typeof init.hostname === "string") {
o.hostname = canonicalizeHostname(init.hostname, isPattern);
}
if (typeof init.port === "string") {
o.port = canonicalizePort(init.port, o.protocol, isPattern);
}
if (typeof init.pathname === "string") {
o.pathname = init.pathname;
if (baseURL && !isAbsolutePathname(o.pathname, isPattern)) {
const slashIndex = baseURL.pathname.lastIndexOf("/");
if (slashIndex >= 0) {
o.pathname = processBaseURLString(baseURL.pathname.substring(0, slashIndex + 1), isPattern) + o.pathname;
}
}
o.pathname = canonicalizePathname(o.pathname, o.protocol, isPattern);
}
if (typeof init.search === "string") {
o.search = canonicalizeSearch(init.search, isPattern);
}
if (typeof init.hash === "string") {
o.hash = canonicalizeHash(init.hash, isPattern);
}
return o;
}
function escapePatternString(value) {
return value.replace(/([+*?:{}()\\])/g, "\\$1");
}
function escapeRegexpString(value) {
return value.replace(/([.+*?^${}()[\]|/\\])/g, "\\$1");
}
function partsToPattern(parts, options) {
options.delimiter ?? (options.delimiter = "/#?");
options.prefixes ?? (options.prefixes = "./");
options.sensitive ?? (options.sensitive = false);
options.strict ?? (options.strict = false);
options.end ?? (options.end = true);
options.start ?? (options.start = true);
options.endsWith = "";
const kFullWildcardRegex2 = ".*";
const segmentWildcardRegex = `[^${escapeRegexpString(options.delimiter)}]+?`;
const regexIdentifierPart2 = /[$_\u200C\u200D\p{ID_Continue}]/u;
let result = "";
for (let i = 0; i < parts.length; ++i) {
const part = parts[i];
if (part.type === 3 /* kFixed */) {
if (part.modifier === 3 /* kNone */) {
result += escapePatternString(part.value);
continue;
}
result += `{${escapePatternString(part.value)}}${modifierToString(part.modifier)}`;
continue;
}
const customName = part.hasCustomName();
let needsGrouping = !!part.suffix.length || !!part.prefix.length && (part.prefix.length !== 1 || !options.prefixes.includes(part.prefix));
const lastPart = i > 0 ? parts[i - 1] : null;
const nextPart = i < parts.length - 1 ? parts[i + 1] : null;
if (!needsGrouping && customName && part.type === 1 /* kSegmentWildcard */ && part.modifier === 3 /* kNone */ && nextPart && !nextPart.prefix.length && !nextPart.suffix.length) {
if (nextPart.type === 3 /* kFixed */) {
const code = nextPart.value.length > 0 ? nextPart.value[0] : "";
needsGrouping = regexIdentifierPart2.test(code);
} else {
needsGrouping = !nextPart.hasCustomName();
}
}
if (!needsGrouping && !part.prefix.length && lastPart && lastPart.type === 3 /* kFixed */) {
const code = lastPart.value[lastPart.value.length - 1];
needsGrouping = options.prefixes.includes(code);
}
if (needsGrouping) {
result += "{";
}
result += escapePatternString(part.prefix);
if (customName) {
result += `:${part.name}`;
}
if (part.type === 2 /* kRegex */) {
result += `(${part.value})`;
} else if (part.type === 1 /* kSegmentWildcard */) {
if (!customName) {
result += `(${segmentWildcardRegex})`;
}
} else if (part.type === 0 /* kFullWildcard */) {
if (!customName && (!lastPart || lastPart.type === 3 /* kFixed */ || lastPart.modifier !== 3 /* kNone */ || needsGrouping || part.prefix !== "")) {
result += "*";
} else {
result += `(${kFullWildcardRegex2})`;
}
}
if (part.type === 1 /* kSegmentWildcard */ && customName && !!part.suffix.length) {
if (regexIdentifierPart2.test(part.suffix[0])) {
result += "\\";
}
}
result += escapePatternString(part.suffix);
if (needsGrouping) {
result += "}";
}
if (part.modifier !== 3 /* kNone */) {
result += modifierToString(part.modifier);
}
}
return result;
}
var URLPattern$1 = class {
constructor(init = {}, baseURLOrOptions, options) {
this.regexp = {};
this.names = {};
this.component_pattern = {};
this.parts = {};
try {
let baseURL = void 0;
if (typeof baseURLOrOptions === "string") {
baseURL = baseURLOrOptions;
} else {
options = baseURLOrOptions;
}
if (typeof init === "string") {
const parser = new Parser(init);
parser.parse();
init = parser.result;
if (baseURL === void 0 && typeof init.protocol !== "string") {
throw new TypeError(`A base URL must be provided for a relative constructor string.`);
}
init.baseURL = baseURL;
} else {
if (!init || typeof init !== "object") {
throw new TypeError(`parameter 1 is not of type 'string' and cannot convert to dictionary.`);
}
if (baseURL) {
throw new TypeError(`parameter 1 is not of type 'string'.`);
}
}
if (typeof options === "undefined") {
options = { ignoreCase: false };
}
const ignoreCaseOptions = { ignoreCase: options.ignoreCase === true };
const defaults = {
pathname: DEFAULT_PATTERN,
protocol: DEFAULT_PATTERN,
username: DEFAULT_PATTERN,
password: DEFAULT_PATTERN,
hostname: DEFAULT_PATTERN,
port: DEFAULT_PATTERN,
search: DEFAULT_PATTERN,
hash: DEFAULT_PATTERN
};
this.pattern = applyInit(defaults, init, true);
if (defaultPortForProtocol(this.pattern.protocol) === this.pattern.port) {
this.pattern.port = "";
}
let component;
for (component of COMPONENTS) {
if (!(component in this.pattern))
continue;
const options2 = {};
const pattern = this.pattern[component];
this.names[component] = [];
switch (component) {
case "protocol":
Object.assign(options2, DEFAULT_OPTIONS);
options2.encodePart = protocolEncodeCallback;
break;
case "username":
Object.assign(options2, DEFAULT_OPTIONS);
options2.encodePart = usernameEncodeCallback;
break;
case "password":
Object.assign(options2, DEFAULT_OPTIONS);
options2.encodePart = passwordEncodeCallback;
break;
case "hostname":
Object.assign(options2, HOSTNAME_OPTIONS);
if (treatAsIPv6Hostname(pattern)) {
options2.encodePart = ipv6HostnameEncodeCallback;
} else {
options2.encodePart = hostnameEncodeCallback;
}
break;
case "port":
Object.assign(options2, DEFAULT_OPTIONS);
options2.encodePart = portEncodeCallback;
break;
case "pathname":
if (isSpecialScheme(this.regexp.protocol)) {
Object.assign(options2, PATHNAME_OPTIONS, ignoreCaseOptions);
options2.encodePart = standardURLPathnameEncodeCallback;
} else {
Object.assign(options2, DEFAULT_OPTIONS, ignoreCaseOptions);
options2.encodePart = pathURLPathnameEncodeCallback;
}
break;
case "search":
Object.assign(options2, DEFAULT_OPTIONS, ignoreCaseOptions);
options2.encodePart = searchEncodeCallback;
break;
case "hash":
Object.assign(options2, DEFAULT_OPTIONS, ignoreCaseOptions);
options2.encodePart = hashEncodeCallback;
break;
}
try {
this.parts[component] = parse(pattern, options2);
this.regexp[component] = partsToRegexp(
this.parts[component],
/* out */
this.names[component],
options2
);
this.component_pattern[component] = partsToPattern(this.parts[component], options2);
} catch (err) {
throw new TypeError(`invalid ${component} pattern '${this.pattern[component]}'.`);
}
}
} catch (err) {
throw new TypeError(`Failed to construct 'URLPattern': ${err.message}`);
}
}
test(input = {}, baseURL) {
let values = {
pathname: "",
protocol: "",
username: "",
password: "",
hostname: "",
port: "",
search: "",
hash: ""
};
if (typeof input !== "string" && baseURL) {
throw new TypeError(`parameter 1 is not of type 'string'.`);
}
if (typeof input === "undefined") {
return false;
}
try {
if (typeof input === "object") {
values = applyInit(values, input, false);
} else {
values = applyInit(values, extractValues(input, baseURL), false);
}
} catch (err) {
return false;
}
let component;
for (component of COMPONENTS) {
if (!this.regexp[component].exec(values[component])) {
return false;
}
}
return true;
}
exec(input = {}, baseURL) {
let values = {
pathname: "",
protocol: "",
username: "",
password: "",
hostname: "",
port: "",
search: "",
hash: ""
};
if (typeof input !== "string" && baseURL) {
throw new TypeError(`parameter 1 is not of type 'string'.`);
}
if (typeof input === "undefined") {
return;
}
try {
if (typeof input === "object") {
values = applyInit(values, input, false);
} else {
values = applyInit(values, extractValues(input, baseURL), false);
}
} catch (err) {
return null;
}
let result = {};
if (baseURL) {
result.inputs = [input, baseURL];
} else {
result.inputs = [input];
}
let component;
for (component of COMPONENTS) {
let match = this.regexp[component].exec(values[component]);
if (!match) {
return null;
}
let groups = {};
for (let [i, name] of this.names[component].entries()) {
if (typeof name === "string" || typeof name === "number") {
let value = match[i + 1];
groups[name] = value;
}
}
result[component] = {
input: values[component] ?? "",
groups
};
}
return result;
}
static compareComponent(component, left, right) {
const comparePart = (left2, right2) => {
for (let attr of ["type", "modifier", "prefix", "value", "suffix"]) {
if (left2[attr] < right2[attr])
return -1;
else if (left2[attr] === right2[attr])
continue;
else
return 1;
}
return 0;
};
const emptyFixedPart = new Part(3 /* kFixed */, "", "", "", "", 3 /* kNone */);
const wildcardOnlyPart = new Part(0 /* kFullWildcard */, "", "", "", "", 3 /* kNone */);
const comparePartList = (left2, right2) => {
let i = 0;
for (; i < Math.min(left2.length, right2.length); ++i) {
let result = comparePart(left2[i], right2[i]);
if (result)
return result;
}
if (left2.length === right2.length) {
return 0;
}
return comparePart(left2[i] ?? emptyFixedPart, right2[i] ?? emptyFixedPart);
};
if (!left.component_pattern[component] && !right.component_pattern[component]) {
return 0;
}
if (left.component_pattern[component] && !right.component_pattern[component]) {
return comparePartList(left.parts[component], [wildcardOnlyPart]);
}
if (!left.component_pattern[component] && right.component_pattern[component]) {
return comparePartList([wildcardOnlyPart], right.parts[component]);
}
return comparePartList(left.parts[component], right.parts[component]);
}
get protocol() {
return this.component_pattern.protocol;
}
get username() {
return this.component_pattern.username;
}
get password() {
return this.component_pattern.password;
}
get hostname() {
return this.component_pattern.hostname;
}
get port() {
return this.component_pattern.port;
}
get pathname() {
return this.component_pattern.pathname;
}
get search() {
return this.component_pattern.search;
}
get hash() {
return this.component_pattern.hash;
}
};
if (!globalThis.URLPattern) {
globalThis.URLPattern = URLPattern$1;
}
if (typeof URLPattern === "undefined") {
throw new Error("urlpattern-polyfill did not import correctly");
}
const globalURLPattern = URLPattern;
/* c8 ignore start */
function isLike(value, ...and) {
if (!and.length)
return !!value;
return !!value && and.every((value) => !!value);
}
function ok$2(value, message, ...conditions) {
if (conditions.length ? !conditions.every((value) => value) : !value) {
throw new Error(message ?? "Expected value");
}
}
function isObjectLike(value) {
return !!(typeof value === "object" && value) || typeof value === "function";
}
class DoubleMap {
objects;
values;
get(parts, fn) {
const found = this.pick(parts);
if (isLike(found)) {
return found;
}
return this.place(parts, fn(parts));
}
getStored(key) {
if (isObjectLike(key)) {
return this.objects?.get(key);
}
else {
return this.values?.get(key);
}
}
pick(parts) {
const [key, ...rest] = parts;
const value = this.getStored(key);
if (value instanceof DoubleMap) {
return value.pick(rest);
}
return value;
}
place(parts, value) {
const target = parts.slice(0, -1).reduce((map, key) => {
const existing = map.getStored(key);
if (existing instanceof DoubleMap) {
return existing;
}
const next = new DoubleMap();
map.set(key, next);
return next;
}, this);
ok$2(target instanceof DoubleMap);
target.set(parts.at(-1), value);
return value;
}
set(key, value) {
if (isObjectLike(key)) {
if (!this.objects) {
this.objects = new WeakMap();
}
this.objects.set(key, value);
}
else {
if (!this.values) {
this.values = new Map();
}
this.values.set(key, value);
}
}
}
function createCompositeValue(fn) {
const keys = new Map();
return function compositeValue(...parts) {
const { length } = parts;
let map = keys.get(length);
if (!map) {
map = new DoubleMap();
keys.set(length, map);
}
return map.get(parts, fn);
};
}
function createCompositeKey() {
return createCompositeValue(() => Object.freeze({ __proto__: null }));
}
let internalCompositeKey = undefined;
function compositeKey(...parts) {
if (!internalCompositeKey) {
internalCompositeKey = createCompositeKey();
}
return internalCompositeKey(...parts);
}
function isURLPatternStringWildcard(pattern) {
return pattern === "*";
}
const patternSymbols = Object.values({
// From https://wicg.github.io/urlpattern/#parsing-patterns
open: "{",
close: "}",
regexpOpen: "(",
regexpClose: ")",
nameStart: ":",
asterisk: "*"
});
const patternParts = [
"protocol",
"hostname",
"username",
"password",
"port",
"pathname",
"search",
"hash"
];
function isURLPatternStringPlain(pattern) {
for (const symbol of patternSymbols) {
if (pattern.includes(symbol)) {
return false;
}
}
return true;
}
function isURLPatternPlainPathname(pattern) {
if (!isURLPatternStringPlain(pattern.pathname)) {
return false;
}
for (const part of patternParts) {
if (part === "pathname")
continue;
if (!isURLPatternStringWildcard(pattern[part])) {
return false;
}
}
return true;
}
// Note, this weak map will contain all urls
// matched in the current process.
// This may not be wanted by everyone
let execCache = undefined;
function enableURLPatternCache() {
execCache = execCache ?? new WeakMap();
}
function exec(pattern, url) {
if (!execCache) {
return pattern.exec(url);
}
const key = compositeKey(pattern, ...patternParts
.filter(part => !isURLPatternStringWildcard(pattern[part]))
.map(part => url[part]));
const existing = execCache.get(key);
if (existing)
return existing;
if (existing === false)
return undefined;
const result = pattern.exec(url);
execCache.set(key, result ?? false);
return result;
}
function isPromise(value) {
return (like(value) &&
typeof value.then === "function");
}
function ok$1(value, message = "Expected value") {
if (!value) {
throw new Error(message);
}
}
function isPromiseRejectedResult(value) {
return value.status === "rejected";
}
function like(value) {
return !!value;
}
async function transitionEvent(router, event) {
const promises = [];
const { signal, } = event;
const url = getURL(event);
const { pathname } = url;
transitionPart("route", (route, match) => route.fn(event, match), handleResolve, handleReject);
if (promises.length) {
await Promise.all(promises);
}
function transitionPart(type, fn, resolve = handleResolve, reject = handleReject) {
let isRoute = false;
resolveRouter(router);
return isRoute;
function matchRoute(route, parentMatch) {
const { router, pattern, string } = route;
let match = parentMatch;
if (string) {
if (string !== pathname) {
return;
}
}
else if (pattern) {
match = exec(pattern, url);
if (!match)
return;
}
if (isRouter(router)) {
return resolveRouter(router, match);
}
isRoute = true;
try {
const maybe = fn(route, match);
if (isPromise(maybe)) {
promises.push(maybe
.then(resolve)
.catch(reject));
}
else {
resolve(maybe);
}
}
catch (error) {
reject(error);
}
}
function resolveRouter(router, match) {
const routes = getRouterRoutes(router);
resolveRoutes(routes[type]);
resolveRoutes(routes.router);
function resolveRoutes(routes) {
for (const route of routes) {
if (signal?.aborted)
break;
matchRoute(route, match);
}
}
}
}
function noop() { }
function handleResolve(value) {
transitionPart("resolve", (route, match) => route.fn(value, event, match), noop, handleReject);
}
function handleReject(error) {