@tabnews/helpers
Version:
TabNews helpers
163 lines (135 loc) • 5.35 kB
JavaScript
import { isProduction, isServerlessRuntime } from './environment.js';
export const baseUrl = getBaseUrl();
export const webserverHostname = tryParseUrl(baseUrl).hostname;
export const webserverDomain = webserverHostname.replace(/^www\./, '');
const DEFAULT_TRUSTED_DOMAINS = ['tabnews.com.br', 'curso.dev', 'filipedeschamps.com.br', 'github.com'];
const NEXT_PUBLIC_TRUSTED_DOMAINS =
process.env.NEXT_PUBLIC_TRUSTED_DOMAINS?.split(',')
.map((host) => host.trim())
.filter(Boolean) || [];
const externalTrustedDomains = NEXT_PUBLIC_TRUSTED_DOMAINS.length
? NEXT_PUBLIC_TRUSTED_DOMAINS
: DEFAULT_TRUSTED_DOMAINS;
export const trustedDomains = [webserverDomain, ...externalTrustedDomains.filter((host) => host !== webserverDomain)];
/**
* Returns the effective base URL for the application:
* - In production, it uses the `NEXT_PUBLIC_WEBSERVER_HOST`.
* - In preview, it uses the `NEXT_PUBLIC_VERCEL_URL`.
* - In development, it uses the `NEXT_PUBLIC_WEBSERVER_HOST` and `NEXT_PUBLIC_WEBSERVER_PORT`.
* - In browser environments, it uses the current location's origin if available.
* - If none of the above, it defaults to 'https://www.tabnews.com.br'.
*
* @returns {string} The resolved base URL.
*/
export function getBaseUrl() {
const { NEXT_PUBLIC_VERCEL_URL, NEXT_PUBLIC_WEBSERVER_HOST, NEXT_PUBLIC_WEBSERVER_PORT } = process.env;
const protocol = isServerlessRuntime ? 'https' : 'http';
// Vercel Production
if (isProduction && NEXT_PUBLIC_WEBSERVER_HOST) {
return `${protocol}://${NEXT_PUBLIC_WEBSERVER_HOST}`;
}
// Vercel Preview
if (NEXT_PUBLIC_VERCEL_URL) {
return `${protocol}://${NEXT_PUBLIC_VERCEL_URL}`;
}
// Development
if (NEXT_PUBLIC_WEBSERVER_HOST && NEXT_PUBLIC_WEBSERVER_PORT) {
return `${protocol}://${NEXT_PUBLIC_WEBSERVER_HOST}:${NEXT_PUBLIC_WEBSERVER_PORT}`;
}
// Browser
if (typeof location !== 'undefined' && location.origin) {
return location.origin;
}
// Fallback
return 'https://www.tabnews.com.br';
}
/**
* Extracts the domain from a given link.
*
* @param {string | URL} link - The link to extract the domain from. Can be anything coercible to a URL.
* @returns {string} The extracted domain, excluding 'www.' if present.
*/
export function getDomain(link) {
const domain = tryParseUrl(link).hostname;
if (domain.startsWith('www.')) {
return domain.substring(4);
}
return domain;
}
/**
* Checks whether a given link points to an external domain.
*
* @param {string | URL} link - The link to check. Can be anything coercible to a URL.
* @returns {boolean} True if the link is external, false otherwise.
*/
export function isExternalLink(link) {
return tryParseUrl(link).hostname !== webserverHostname;
}
/**
* Checks if a given URL belongs to a trusted domain or subdomain.
*
* @param {string | URL} url - A string or URL-like input.
* @returns {boolean} True if the domain is trusted, false otherwise.
*/
export function isTrustedDomain(url) {
const { hostname } = tryParseUrl(url);
return trustedDomains.some((trustedDomain) => hostname === trustedDomain || hostname?.endsWith(`.${trustedDomain}`));
}
/**
* Replaces or removes search parameters in the current URL using `history.replaceState`.
*
* - If `params` is `null`, all search parameters are removed.
* - If `params` is an object:
* - Keys with `string` or `number` values are added or updated in the URL.
* - Keys with `null` or `undefined` values are removed from the URL.
* - Other value types (e.g., objects, arrays) are ignored and also removed.
*
* In non-production environments, passing a non-object and non-null value throws an error.
*
* @param {Record<string, string | number | null | undefined> | null} params
* An object mapping query parameter names to values:
* - `string` or `number` to set/update the parameter;
* - `null` or `undefined` to remove it;
* Or `null` to remove all parameters.
*
* @throws {Error} If `params` is not an object or `null` (outside production only).
*/
export function replaceParams(params) {
if (typeof params !== 'object' && process.env.NODE_ENV !== 'production') {
throw new Error(`[replaceParams] Expected "params" to be an object or null, but received: "${typeof params}"`);
}
try {
const url = new URL(location.href);
if (params === null) {
url.search = '';
history.replaceState(history.state, '', url.toString());
return;
}
Object.keys(params).forEach((key) => {
if (typeof params[key] !== 'string' && typeof params[key] !== 'number') {
url.searchParams.delete(key);
return;
}
url.searchParams.set(key, params[key].toString());
});
history.replaceState(history.state, '', url.toString());
} catch {
/* empty */
}
}
/**
* Attempts to parse a value into a valid URL.
*
* @param {string | URL} url - Any value that can potentially be coerced into a URL string.
* @param {string} [label='tryParseUrl'] - Label used in development error messages.
* @returns {URL | Object} A valid URL object if parsing succeeds, otherwise an empty object.
*/
export function tryParseUrl(url, label = 'tryParseUrl') {
if (!url) return {};
try {
return new URL(url, baseUrl);
} catch {
console.warn(`[${label}] Invalid URL passed: "${url}"`);
}
return {};
}