Initial commit
This commit is contained in:
commit
85eb12a22d
1397 changed files with 173048 additions and 0 deletions
469
node_modules/@apple-media-services/media-api/src/models/urls.ts
generated
vendored
Normal file
469
node_modules/@apple-media-services/media-api/src/models/urls.ts
generated
vendored
Normal file
|
|
@ -0,0 +1,469 @@
|
|||
/**
|
||||
* Created by keithpk on 12/2/16.
|
||||
*/
|
||||
|
||||
import { isNothing, Nothing, Opt } from "@jet/environment/types/optional";
|
||||
import { isDefinedNonNullNonEmpty, isNullOrEmpty } from "./server-data";
|
||||
|
||||
export type Query = {
|
||||
[key: string]: string | undefined;
|
||||
};
|
||||
|
||||
export type URLComponent = "protocol" | "username" | "password" | "host" | "port" | "pathname" | "query" | "hash";
|
||||
|
||||
const protocolRegex = /^([a-z][a-z0-9.+-]*:)(\/\/)?([\S\s]*)/i;
|
||||
const queryParamRegex = /([^=?&]+)=?([^&]*)/g;
|
||||
const componentOrder: URLComponent[] = ["hash", "query", "pathname", "host"];
|
||||
|
||||
type URLSplitStyle = "prefix" | "suffix";
|
||||
|
||||
type URLSplitResult = {
|
||||
result?: string;
|
||||
remainder: string;
|
||||
};
|
||||
|
||||
function splitUrlComponent(input: string, marker: string, style: URLSplitStyle): URLSplitResult {
|
||||
const index = input.indexOf(marker);
|
||||
let result;
|
||||
let remainder = input;
|
||||
if (index !== -1) {
|
||||
const prefix = input.slice(0, index);
|
||||
const suffix = input.slice(index + marker.length, input.length);
|
||||
|
||||
if (style === "prefix") {
|
||||
result = prefix;
|
||||
remainder = suffix;
|
||||
} else {
|
||||
result = suffix;
|
||||
remainder = prefix;
|
||||
}
|
||||
}
|
||||
|
||||
// log("Token: " + marker + " String: " + input, " Result: " + result + " Remainder: " + remainder)
|
||||
|
||||
return {
|
||||
result: result,
|
||||
remainder: remainder,
|
||||
};
|
||||
}
|
||||
|
||||
export class URL {
|
||||
protocol?: Opt<string>;
|
||||
username: string;
|
||||
password: string;
|
||||
host?: Opt<string>;
|
||||
port: string;
|
||||
pathname?: Opt<string>;
|
||||
query?: Query = {};
|
||||
hash?: string;
|
||||
|
||||
constructor(url?: string) {
|
||||
if (isNullOrEmpty(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Split the protocol from the rest of the urls
|
||||
let remainder = url;
|
||||
const match = protocolRegex.exec(url);
|
||||
if (match != null) {
|
||||
// Pull out the protocol
|
||||
let protocol = match[1];
|
||||
if (protocol) {
|
||||
protocol = protocol.split(":")[0];
|
||||
}
|
||||
|
||||
this.protocol = protocol;
|
||||
|
||||
// Save the remainder
|
||||
remainder = match[3];
|
||||
}
|
||||
|
||||
// Then match each component in a specific order
|
||||
let parse: URLSplitResult = { remainder: remainder, result: undefined };
|
||||
for (const component of componentOrder) {
|
||||
if (!parse.remainder) {
|
||||
break;
|
||||
}
|
||||
|
||||
switch (component) {
|
||||
case "hash": {
|
||||
parse = splitUrlComponent(parse.remainder, "#", "suffix");
|
||||
this.hash = parse.result;
|
||||
break;
|
||||
}
|
||||
case "query": {
|
||||
parse = splitUrlComponent(parse.remainder, "?", "suffix");
|
||||
if (isDefinedNonNullNonEmpty(parse.result)) {
|
||||
this.query = URL.queryFromString(parse.result);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "pathname": {
|
||||
parse = splitUrlComponent(parse.remainder, "/", "suffix");
|
||||
|
||||
if (isDefinedNonNullNonEmpty(parse.result)) {
|
||||
// Replace the initial /, since paths require it
|
||||
this.pathname = "/" + parse.result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "host": {
|
||||
if (parse.remainder) {
|
||||
const authorityParse = splitUrlComponent(parse.remainder, "@", "prefix");
|
||||
const userInfo = authorityParse.result;
|
||||
const hostPort = authorityParse.remainder;
|
||||
if (isDefinedNonNullNonEmpty(userInfo)) {
|
||||
const userInfoSplit = userInfo.split(":");
|
||||
this.username = decodeURIComponent(userInfoSplit[0]);
|
||||
this.password = decodeURIComponent(userInfoSplit[1]);
|
||||
}
|
||||
|
||||
if (hostPort) {
|
||||
const hostPortSplit = hostPort.split(":");
|
||||
this.host = hostPortSplit[0];
|
||||
this.port = hostPortSplit[1];
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new Error("Unhandled case!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set(component: URLComponent, value: string | Query): URL {
|
||||
if (isNullOrEmpty(value)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
if (component === "query") {
|
||||
if (typeof value === "string") {
|
||||
value = URL.queryFromString(value);
|
||||
}
|
||||
}
|
||||
|
||||
switch (component) {
|
||||
// Exhaustive match to make sure TS property minifiers and other
|
||||
// transformer plugins do not break this code.
|
||||
case "protocol":
|
||||
this.protocol = value as string;
|
||||
break;
|
||||
case "username":
|
||||
this.username = value as string;
|
||||
break;
|
||||
case "password":
|
||||
this.password = value as string;
|
||||
break;
|
||||
case "port":
|
||||
this.port = value as string;
|
||||
break;
|
||||
case "pathname":
|
||||
this.pathname = value as string;
|
||||
break;
|
||||
case "query":
|
||||
this.query = value as Query;
|
||||
break;
|
||||
case "hash":
|
||||
this.hash = value as string;
|
||||
break;
|
||||
default:
|
||||
// The fallback for component which is not a property of URL object.
|
||||
this[component] = value as string;
|
||||
break;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
private get(component: URLComponent): string | Query | Nothing {
|
||||
switch (component) {
|
||||
// Exhaustive match to make sure TS property minifiers and other
|
||||
// transformer plugins do not break this code.
|
||||
case "protocol":
|
||||
return this.protocol;
|
||||
case "username":
|
||||
return this.username;
|
||||
case "password":
|
||||
return this.password;
|
||||
case "port":
|
||||
return this.port;
|
||||
case "pathname":
|
||||
return this.pathname;
|
||||
case "query":
|
||||
return this.query;
|
||||
case "hash":
|
||||
return this.hash;
|
||||
default:
|
||||
// The fallback for component which is not a property of URL object.
|
||||
return this[component];
|
||||
}
|
||||
}
|
||||
|
||||
append(component: URLComponent, value: string | Query): URL {
|
||||
const existingValue = this.get(component);
|
||||
let newValue;
|
||||
|
||||
if (component === "query") {
|
||||
if (typeof value === "string") {
|
||||
value = URL.queryFromString(value);
|
||||
}
|
||||
|
||||
if (typeof existingValue === "string") {
|
||||
newValue = { existingValue, ...value };
|
||||
} else {
|
||||
newValue = { ...existingValue, ...value };
|
||||
}
|
||||
} else {
|
||||
let existingValueString = existingValue as string;
|
||||
|
||||
if (!existingValueString) {
|
||||
existingValueString = "";
|
||||
}
|
||||
|
||||
let newValueString = existingValueString;
|
||||
|
||||
if (component === "pathname") {
|
||||
const pathLength = existingValueString.length;
|
||||
if (!pathLength || existingValueString[pathLength - 1] !== "/") {
|
||||
newValueString += "/";
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-base-to-string
|
||||
newValueString += value;
|
||||
newValue = newValueString;
|
||||
}
|
||||
|
||||
return this.set(component, newValue);
|
||||
}
|
||||
|
||||
param(key: string, value?: string): URL {
|
||||
if (!key) {
|
||||
return this;
|
||||
}
|
||||
if (this.query == null) {
|
||||
this.query = {};
|
||||
}
|
||||
this.query[key] = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
removeParam(key: string): URL {
|
||||
if (!key || this.query == null) {
|
||||
return this;
|
||||
}
|
||||
if (this.query[key] !== undefined) {
|
||||
delete this.query[key];
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Push a new string value onto the path for this url
|
||||
* @returns URL this object with the updated path.
|
||||
*/
|
||||
path(value: string): URL {
|
||||
return this.append("pathname", value);
|
||||
}
|
||||
|
||||
pathExtension(): Opt<string> {
|
||||
// Extract path extension if one exists
|
||||
if (isNothing(this.pathname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastFilenameComponents = this.pathname
|
||||
.split("/")
|
||||
.filter((item) => item.length > 0) // Remove any double or trailing slashes
|
||||
.pop()
|
||||
?.split(".");
|
||||
if (lastFilenameComponents === undefined) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
lastFilenameComponents.filter((part) => {
|
||||
return part !== "";
|
||||
}).length < 2 // Remove any empty parts (e.g. .ssh_config -> ["ssh_config"])
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return lastFilenameComponents.pop();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path components of the URL
|
||||
* @returns An array of non-empty path components from `urls`.
|
||||
*/
|
||||
pathComponents(): string[] {
|
||||
if (isNullOrEmpty(this.pathname)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.pathname.split("/").filter((component) => component.length > 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the last path component from this url, updating the url to not include this path component
|
||||
* @returns String the last path component from this url.
|
||||
*/
|
||||
popPathComponent(): string | null {
|
||||
if (isNullOrEmpty(this.pathname)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const lastPathComponent = this.pathname.slice(this.pathname.lastIndexOf("/") + 1);
|
||||
|
||||
if (lastPathComponent.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
this.pathname = this.pathname.slice(0, this.pathname.lastIndexOf("/"));
|
||||
|
||||
return lastPathComponent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as toString
|
||||
*
|
||||
* @returns {string} A string representation of the URL
|
||||
*/
|
||||
build(): string {
|
||||
return this.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the URL to a string
|
||||
*
|
||||
* @returns {string} A string representation of the URL
|
||||
*/
|
||||
toString(): string {
|
||||
let url = "";
|
||||
|
||||
if (isDefinedNonNullNonEmpty(this.protocol)) {
|
||||
url += this.protocol + "://";
|
||||
}
|
||||
|
||||
if (this.username) {
|
||||
url += encodeURIComponent(this.username);
|
||||
|
||||
if (this.password) {
|
||||
url += ":" + encodeURIComponent(this.password);
|
||||
}
|
||||
|
||||
url += "@";
|
||||
}
|
||||
|
||||
if (isDefinedNonNullNonEmpty(this.host)) {
|
||||
url += this.host;
|
||||
|
||||
if (this.port) {
|
||||
url += ":" + this.port;
|
||||
}
|
||||
}
|
||||
|
||||
if (isDefinedNonNullNonEmpty(this.pathname)) {
|
||||
url += this.pathname;
|
||||
/// Trim off trailing path separators when we have a valid path
|
||||
/// We don't do this unless pathname has elements otherwise we will trim the `://`
|
||||
if (url.endsWith("/") && this.pathname.length > 0) {
|
||||
url = url.slice(0, -1);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.query != null && Object.keys(this.query).length > 0) {
|
||||
url += "?" + URL.toQueryString(this.query);
|
||||
}
|
||||
|
||||
if (isDefinedNonNullNonEmpty(this.hash)) {
|
||||
url += "#" + this.hash;
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
// ----------------
|
||||
// Static API
|
||||
// ----------------
|
||||
|
||||
/**
|
||||
* Converts a string into a query dictionary
|
||||
* @param query The string to parse
|
||||
* @returns The query dictionary containing the key-value pairs in the query string
|
||||
*/
|
||||
static queryFromString(query: string): Query {
|
||||
const result = {};
|
||||
|
||||
let parseResult = queryParamRegex.exec(query);
|
||||
while (parseResult != null) {
|
||||
const key = decodeURIComponent(parseResult[1]);
|
||||
const value = decodeURIComponent(parseResult[2]);
|
||||
result[key] = value;
|
||||
parseResult = queryParamRegex.exec(query);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a query dictionary into a query string
|
||||
*
|
||||
* @param query The query dictionary
|
||||
* @returns {string} The string representation of the query dictionary
|
||||
*/
|
||||
static toQueryString(query: Query) {
|
||||
let queryString = "";
|
||||
|
||||
let first = true;
|
||||
for (const key of Object.keys(query)) {
|
||||
if (!first) {
|
||||
queryString += "&";
|
||||
}
|
||||
first = false;
|
||||
|
||||
queryString += encodeURIComponent(key);
|
||||
|
||||
const value = query[key];
|
||||
if (isDefinedNonNullNonEmpty(value) && value.length) {
|
||||
queryString += "=" + encodeURIComponent(value);
|
||||
}
|
||||
}
|
||||
|
||||
return queryString;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to instantiate a URL from a string
|
||||
* @param url The URL string to parse
|
||||
* @returns {URL} The new URL object representing the URL
|
||||
*/
|
||||
static from(url: string): URL {
|
||||
return new URL(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method to instantiate a URL from numerous (optional) components
|
||||
* @param protocol The protocol type
|
||||
* @param host The host name
|
||||
* @param path The path
|
||||
* @param query The query
|
||||
* @param hash The hash
|
||||
* @returns {URL} The new URL object representing the URL
|
||||
*/
|
||||
static fromComponents(
|
||||
protocol?: Opt<string>,
|
||||
host?: Opt<string>,
|
||||
path?: Opt<string>,
|
||||
query?: Query,
|
||||
hash?: string,
|
||||
): URL {
|
||||
const url = new URL();
|
||||
url.protocol = protocol;
|
||||
url.host = host;
|
||||
url.pathname = path;
|
||||
url.query = query;
|
||||
url.hash = hash;
|
||||
return url;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue