@microbitsclub/paywall-solid
Version:
Solid components for Microbits paywalls
280 lines (279 loc) • 7.61 kB
JavaScript
const isCharCodeRange = (c, a, b) => {
const u = c.charCodeAt(0);
return u >= a && u <= b;
};
const isCharRange = (c, a, b) => {
return isCharCodeRange(c, a.charCodeAt(0), b.charCodeAt(0));
};
const isNumeric = (c) => {
const u = c.charCodeAt(0);
return u >= 48 && u <= 57;
};
const isUpperAlpha = (c) => {
const u = c.charCodeAt(0);
return u >= 65 && u <= 90;
};
const isLowerAlpha = (c) => {
const u = c.charCodeAt(0);
return u >= 97 && u <= 122;
};
const isAlpha = (c) => {
const u = c.charCodeAt(0);
return (u >= 65 && u <= 90) || (u >= 97 && u <= 122);
};
const parseProto = (s) => {
const l = s.length;
for (let i = 0; i < l; ++i) {
const c = s[i];
if (c === ':') {
if (s[i + 1] === '/' && s[i + 2] === '/') {
return s.slice(0, i);
}
else {
break;
}
}
if (!isAlpha(c)) {
break;
}
}
return undefined;
};
const parseHostname = (s) => {
const l = s.length + 1;
let isIp6 = s[0] === '[';
for (let i = 0; i < l; ++i) {
const c = s[i];
if (isIp6) {
if (i === 0)
continue;
if (c === ':')
continue;
if (isNumeric(c))
continue;
if (isCharRange(c, 'a', 'f'))
continue;
if (isCharRange(c, 'A', 'F'))
continue;
if (c === ']') {
if (i === 1)
return undefined;
return [s.slice(i + 1), s.slice(1, i)];
}
break;
}
else {
if (c === ':' || c === '/' || c === '?' || c === undefined) {
if (i === 0)
return undefined;
return [s.slice(i), s.slice(0, i)];
}
}
}
return undefined;
};
const parsePort = (s) => {
if (s[0] !== ':')
return undefined;
const l = s.length + 1;
for (let i = 1; i < l; ++i) {
const c = s[i];
if (c === undefined || !isNumeric(c)) {
if (i === 1)
break;
return s.slice(1, i);
}
}
return undefined;
};
export const createUrlPath = (path, trailing = false) => {
if (typeof path === 'string') {
const pathResult = parsePath(path);
if (pathResult === undefined || pathResult[0].length > 0) {
throw Error('Invalid path');
}
return pathResult[1];
}
else {
return { path, trailing };
}
};
const parsePath = (s) => {
if (s[0] !== '/')
return undefined;
const l = s.length + 1;
const path = [];
let part = '';
let trailing = true;
for (let i = 1; i < l; ++i) {
const c = s[i];
if (c === undefined || c === '?') {
if (part.length > 0)
path.push(part);
return [s.slice(i), { path, trailing }];
}
if (c === '/') {
trailing = true;
if (part.length === 0)
break;
path.push(part);
part = '';
continue;
}
part += c;
trailing = false;
}
return undefined;
};
const parseQuery = (s) => {
if (s[0] !== '?')
return undefined;
const l = s.length + 1;
const query = {};
let pivot = 0;
let count = 0;
for (let i = 1; i < l; ++i) {
const c = s[i];
if (c === undefined || c === '&') {
const [key, val] = s.slice(pivot + 1, i).split('=');
if (val !== undefined) {
const prevVal = query[key];
if (prevVal === undefined) {
query[key] = decodeURIComponent(val ?? '');
count += 1;
}
else if (typeof prevVal === 'string') {
query[key] = [prevVal, decodeURIComponent(val ?? '')];
}
else {
prevVal.push(decodeURIComponent(val ?? ''));
}
}
pivot = i;
}
}
return count === 0 ? undefined : query;
};
export const parseUrl = (s) => {
const proto = parseProto(s);
if (proto !== undefined) {
s = s.slice(proto.length + 3);
}
let hostname;
const hostnameResult = parseHostname(s);
if (hostnameResult !== undefined) {
s = hostnameResult[0];
hostname = hostnameResult[1];
}
let port;
const portResult = parsePort(s);
if (portResult !== undefined) {
s = s.slice(portResult.length + 1);
port = Number.parseInt(portResult);
}
let path = [];
let trailing;
const pathResult = parsePath(s);
if (pathResult !== undefined) {
s = pathResult[0];
path = pathResult[1].path;
trailing = pathResult[1].trailing;
}
const query = parseQuery(s);
const url = {
proto,
hostname,
port,
path,
trailing: trailing ?? false,
query,
};
if (url.proto === undefined)
delete url.proto;
if (url.hostname === undefined)
delete url.hostname;
if (url.port === undefined)
delete url.port;
if (url.query === undefined)
delete url.query;
return url;
};
export const stringifyUrlPath = ({ path, trailing }) => {
let s = '';
if (path)
s += '/' + path.join('/');
if (trailing === true)
s += '/';
return s;
};
export const stringifyUrl = (url) => {
let s = '';
if (url.proto)
s += url.proto + '://';
if (url.hostname)
s += url.hostname;
if (url.port)
s += `:${url.port}`;
s += stringifyUrlPath(url);
if (url.query !== undefined) {
const parts = [];
for (const k in url.query) {
const v = url.query[k];
if (v !== undefined) {
if (typeof v === 'string') {
parts.push(`${k}=${encodeURIComponent(v)}`);
}
else {
for (const x of v) {
parts.push(`${k}=${encodeURIComponent(x)}`);
}
}
}
}
s += `?${parts.join('&')}`;
}
return s;
};
export const URL_PROTOCOL_PORT_MAP = {
http: 80,
https: 443,
ws: 80,
wss: 443,
};
export const normalizeUrl = (url) => {
// Protocol and explicit port
if (url.proto !== undefined && url.port !== undefined) {
const defaultPort = URL_PROTOCOL_PORT_MAP[url.proto];
if (defaultPort === url.port) {
const { port: _, ...oldUrl } = url;
const newUrl = {
...oldUrl,
proto: url.proto,
path: url.path ?? [],
trailing: url.trailing ?? false,
};
return newUrl;
}
}
// No protocol and explicit port
if (url.proto === undefined && url.port !== undefined) {
for (const proto in URL_PROTOCOL_PORT_MAP) {
if (URL_PROTOCOL_PORT_MAP[proto] === url.port) {
const { port: _, ...oldUrl } = url;
const newUrl = {
...oldUrl,
proto,
path: url.path ?? [],
trailing: url.trailing ?? false,
};
return newUrl;
}
}
}
const newUrl = {
...url,
path: url.path ?? [],
trailing: url.trailing ?? false,
};
return newUrl;
};