diff --git a/fluxer_api/src/api/Config.ts b/fluxer_api/src/api/Config.ts index f4e0ff64b..aa7851252 100644 --- a/fluxer_api/src/api/Config.ts +++ b/fluxer_api/src/api/Config.ts @@ -272,8 +272,6 @@ export function buildAPIConfigFromMaster(master: MasterConfig): APIConfig { donationProxyKey, }, hosts: { - invite: extractHostname(master.endpoints.invite), - gift: extractHostname(master.endpoints.gift), marketing: extractHostname(master.endpoints.marketing), unfurlIgnored: master.services.api.unfurl_ignored_hosts, }, diff --git a/fluxer_api/src/api/config/APIConfig.ts b/fluxer_api/src/api/config/APIConfig.ts index 3394c335d..ad7b248cc 100644 --- a/fluxer_api/src/api/config/APIConfig.ts +++ b/fluxer_api/src/api/config/APIConfig.ts @@ -143,8 +143,6 @@ export interface APIConfig { donationProxyKey: string; }; hosts: { - invite: string; - gift: string; marketing: string; unfurlIgnored: Array; }; diff --git a/fluxer_api/src/api/utils/InviteUtils.ts b/fluxer_api/src/api/utils/InviteUtils.ts index af6239bdb..be0969fcb 100644 --- a/fluxer_api/src/api/utils/InviteUtils.ts +++ b/fluxer_api/src/api/utils/InviteUtils.ts @@ -5,13 +5,18 @@ import * as RegexUtils from '@app/api/utils/RegexUtils'; let _invitePattern: RegExp | null = null; +function getInviteEndpointBase(): string { + const url = new URL(Config.endpoints.invite); + return `${url.hostname}${url.pathname.replace(/\/+$/, '')}`; +} + function getInvitePattern(): RegExp { if (!_invitePattern) { _invitePattern = new RegExp( [ '(?:https?:\\/\\/)?', '(?:', - `${RegexUtils.escapeRegex(Config.hosts.invite)}(?:\\/#)?\\/(?!invite\\/)([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`, + `${RegexUtils.escapeRegex(getInviteEndpointBase())}(?:\\/#)?\\/(?!invite\\/)([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`, '|', `${RegexUtils.escapeRegex(new URL(Config.endpoints.webApp).hostname)}(?:\\/#)?\\/invite\\/([a-zA-Z0-9\\-]{2,32})(?![a-zA-Z0-9\\-])`, ')', diff --git a/fluxer_api/src/api/utils/UnfurlerUtils.ts b/fluxer_api/src/api/utils/UnfurlerUtils.ts index 357e1f245..2cfd7f598 100644 --- a/fluxer_api/src/api/utils/UnfurlerUtils.ts +++ b/fluxer_api/src/api/utils/UnfurlerUtils.ts @@ -8,24 +8,17 @@ import * as InviteUtils from '@app/api/utils/InviteUtils'; import {URL_REGEX} from '@fluxer/constants/src/Core'; import * as idna from 'idna-uts46-hx'; -const MARKETING_PATH_PREFIXES = ['/channels/', '/theme/']; +const CLIENT_ROUTE_PATH_PREFIXES = ['/channels/', '/theme/']; + +interface ExcludedLinkBase { + hostname: string; + pathPrefix: string; +} function normalizeHostname(hostname: string | undefined) { return hostname?.trim().toLowerCase() || ''; } -let _marketingHostname: string | null = null; - -function getMarketingHostname() { - if (!_marketingHostname) { - _marketingHostname = normalizeHostname(Config.hosts.marketing); - } - return _marketingHostname; -} - -const isMarketingPath = (hostname: string, pathname: string) => - hostname === getMarketingHostname() && MARKETING_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix)); - function getWebAppHostname() { try { return new URL(Config.endpoints.webApp).hostname; @@ -34,23 +27,32 @@ function getWebAppHostname() { } } -let _excludedHostnames: Set | null = null; - -function getExcludedHostnames(): Set { - if (!_excludedHostnames) { - _excludedHostnames = new Set(); - const addHostname = (hostname: string | undefined) => { - const normalized = normalizeHostname(hostname); - if (normalized) { - _excludedHostnames!.add(normalized); - } - }; - addHostname(Config.hosts.invite); - addHostname(Config.hosts.gift); - Config.hosts.unfurlIgnored.forEach(addHostname); - addHostname(getWebAppHostname()); +function endpointLinkBase(endpoint: string): ExcludedLinkBase | null { + try { + const url = new URL(endpoint); + return {hostname: normalizeHostname(url.hostname), pathPrefix: `${url.pathname.replace(/\/+$/, '')}/`}; + } catch { + return null; } - return _excludedHostnames; +} + +let _excludedLinkBases: Array | null = null; + +function getExcludedLinkBases(): Array { + if (!_excludedLinkBases) { + const bases: Array = [ + ...Config.hosts.unfurlIgnored.map((hostname) => ({hostname: normalizeHostname(hostname), pathPrefix: '/'})), + endpointLinkBase(Config.endpoints.invite), + endpointLinkBase(Config.endpoints.gift), + ]; + for (const hostname of [getWebAppHostname(), Config.hosts.marketing]) { + for (const pathPrefix of CLIENT_ROUTE_PATH_PREFIXES) { + bases.push({hostname: normalizeHostname(hostname), pathPrefix}); + } + } + _excludedLinkBases = bases.filter((base): base is ExcludedLinkBase => base !== null && base.hostname !== ''); + } + return _excludedLinkBases; } function idnaEncodeURL(url: string) { @@ -80,8 +82,9 @@ function isFluxerAppExcludedURL(url: string) { try { const parsedUrl = new URL(url); const hostname = normalizeHostname(parsedUrl.hostname); - const isMarketingPathMatch = isMarketingPath(hostname, parsedUrl.pathname); - return isMarketingPathMatch || getExcludedHostnames().has(hostname); + return getExcludedLinkBases().some( + (base) => base.hostname === hostname && parsedUrl.pathname.startsWith(base.pathPrefix), + ); } catch { return false; }