UNPKG

@tryghost/referrer-parser

Version:

Simple library for parsing referrer URLs

1 lines 16 kB
{"version":3,"file":"index.cjs","sources":["../lib/ReferrerParser.ts","../index.ts"],"sourcesContent":["/**\n * Interface for parsed referrer data\n */\nexport interface ReferrerData {\n /** The identified source of the referral traffic */\n referrerSource: string | null;\n /** The identified medium of the referral traffic */\n referrerMedium: string | null;\n /** The hostname of the referral URL */\n referrerUrl: string | null;\n}\n\n/**\n * Configuration options for the parser\n */\nexport interface ParserOptions {\n /** URL of the site for identifying internal traffic */\n siteUrl?: string;\n /** URL of the admin panel for identifying admin traffic */\n adminUrl?: string;\n}\n\n/**\n * Interface for referrer source data\n */\ninterface ReferrerSourceData {\n source: string;\n medium: string;\n}\n\n// Import known referrers data\nimport knownReferrers from './referrers.json';\n\n/**\n * Parses referrer URLs to determine source and medium\n */\nexport class ReferrerParser {\n private adminUrl: URL | null;\n private siteUrl: URL | null;\n\n /**\n * Creates a new referrer parser instance\n * \n * @param options - Configuration options\n */\n constructor(options: ParserOptions = {}) {\n this.adminUrl = this.getUrlFromStr(options.adminUrl || '');\n this.siteUrl = this.getUrlFromStr(options.siteUrl || '');\n }\n\n /**\n * Parse a referrer URL to get source, medium and hostname\n * \n * @param referrerUrlStr - URL of the referrer\n * @param referrerSource - Source of the referrer\n * @param referrerMedium - Medium of the referrer\n * @returns Parsed referrer data with source, medium and URL. Internal referrers return null values.\n */\n parse(referrerUrlStr: string, referrerSource?: string, referrerMedium?: string): ReferrerData {\n const referrerUrl = this.getUrlFromStr(referrerUrlStr);\n\n // Ghost-specific cases\n if (this.isGhostExploreRef({referrerUrl, referrerSource})) {\n return {\n referrerSource: 'Ghost Explore',\n referrerMedium: 'Ghost Network',\n referrerUrl: referrerUrl?.hostname ?? null\n };\n }\n\n // If referrer is Ghost.org\n if (this.isGhostOrgUrl(referrerUrl)) {\n return {\n referrerSource: 'Ghost.org',\n referrerMedium: 'Ghost Network',\n referrerUrl: referrerUrl?.hostname ?? null\n };\n }\n\n // Check for Ghost Newsletter\n if (referrerSource && this.isGhostNewsletter({referrerSource})) {\n return {\n referrerSource: referrerSource.replace(/-/g, ' '),\n referrerMedium: 'Email',\n referrerUrl: referrerUrl?.hostname ?? null\n };\n }\n\n // If referrer source is available from parameters\n if (referrerSource) {\n const urlData = this.getDataFromUrl(referrerUrl);\n const knownSource = Object.values(knownReferrers as Record<string, ReferrerSourceData>).find(referrer => \n referrer.source.toLowerCase() === referrerSource.toLowerCase());\n \n return {\n referrerSource: knownSource?.source || referrerSource,\n referrerMedium: knownSource?.medium || referrerMedium || urlData?.medium || null,\n referrerUrl: referrerUrl?.hostname ?? null\n };\n }\n\n // If referrer is known external URL\n if (!this.isSiteDomain(referrerUrl)) {\n const urlData = this.getDataFromUrl(referrerUrl);\n\n // Use known source/medium if available\n if (urlData) {\n return {\n referrerSource: urlData?.source ?? null,\n referrerMedium: urlData?.medium ?? null,\n referrerUrl: referrerUrl?.hostname ?? null\n };\n }\n \n // Use the hostname as a source\n return {\n referrerSource: referrerUrl?.hostname ?? null,\n referrerMedium: null,\n referrerUrl: referrerUrl?.hostname ?? null\n };\n }\n\n return {\n referrerSource: null,\n referrerMedium: null,\n referrerUrl: null\n }\n }\n\n /**\n * Fetches referrer data from known external URLs\n * \n * @param url - The URL to match against known referrers\n * @returns Matched referrer data or null if not found\n */\n getDataFromUrl(url: URL | null): ReferrerSourceData | null {\n // Handle null url case\n if (!url) {\n return null;\n }\n\n // Allow matching both \"google.ac/products\" and \"google.ac\" as a source\n const urlHostPath = url.hostname + url.pathname;\n const urlDataKey = Object.keys(knownReferrers as Record<string, ReferrerSourceData>).sort((a, b) => {\n // The longer key has higher priority so google.ac/products is selected before google.ac\n return b.length - a.length;\n }).find((source) => {\n return urlHostPath.startsWith(source);\n });\n\n return urlDataKey ? (knownReferrers as Record<string, ReferrerSourceData>)[urlDataKey] : null;\n }\n\n /**\n * Return URL object for provided URL string\n * \n * @param url - URL string to parse\n * @returns Parsed URL object or null if invalid\n */\n getUrlFromStr(url: string): URL | null {\n if (!url) {\n return null;\n }\n \n try {\n return new URL(url);\n } catch (e) {\n return null;\n }\n }\n\n /**\n * Determine whether the provided URL is a link to the site\n * \n * @param url - URL to check\n * @returns True if the URL belongs to the configured site\n */\n isSiteDomain(url: URL | null): boolean {\n try {\n // If we don't have siteUrl configured, we can't detect internal traffic\n if (!this.siteUrl) {\n return false;\n }\n\n if (!url) {\n return false;\n }\n\n // Handle subdomain variations (www.example.com vs example.com)\n const siteHostname = this.siteUrl.hostname;\n const urlHostname = url.hostname;\n \n // Check for exact match first\n if (siteHostname === urlHostname) {\n if (url.pathname.startsWith(this.siteUrl.pathname)) {\n return true;\n }\n return false;\n }\n \n // Check for www subdomain variations\n const siteWithoutWww = siteHostname.replace(/^www\\./, '');\n const urlWithoutWww = urlHostname.replace(/^www\\./, '');\n \n if (siteWithoutWww === urlWithoutWww) {\n if (url.pathname.startsWith(this.siteUrl.pathname)) {\n return true;\n }\n return false;\n }\n \n return false;\n } catch (e) {\n return false;\n }\n }\n\n /**\n * Determine whether referrer is a Ghost newsletter\n * \n * @param deps - Input parameters\n * @returns True if the referrer is a Ghost newsletter\n */\n isGhostNewsletter({referrerSource}: {referrerSource: string | null}): boolean {\n if (!referrerSource) {\n return false;\n }\n // if referrer source ends with -newsletter\n return referrerSource.endsWith('-newsletter');\n }\n\n /**\n * Determine whether referrer is a Ghost.org URL\n * \n * @param referrerUrl - The referrer URL to check\n * @returns True if the referrer is from Ghost.org\n */\n isGhostOrgUrl(referrerUrl: URL | null): boolean {\n if (!referrerUrl) {\n return false;\n }\n return referrerUrl.hostname === 'ghost.org';\n }\n\n /**\n * Determine whether referrer is Ghost Explore\n * \n * @param deps - Input parameters\n * @returns True if the referrer is from Ghost Explore\n */\n isGhostExploreRef({referrerUrl, referrerSource}: {referrerUrl: URL | null, referrerSource?: string | null}): boolean {\n if (referrerSource === 'ghost-explore') {\n return true;\n }\n\n if (!referrerUrl) {\n return false;\n }\n\n if (referrerUrl?.hostname\n && this.adminUrl?.hostname === referrerUrl?.hostname\n && referrerUrl?.pathname?.startsWith(this.adminUrl?.pathname)\n ) {\n return true;\n }\n\n if (referrerUrl.hostname === 'ghost.org' && referrerUrl.pathname.startsWith('/explore')) {\n return true;\n }\n\n return false;\n }\n} ","import { ReferrerParser } from './lib/ReferrerParser';\nimport type { ReferrerData, ParserOptions } from './lib/ReferrerParser';\n\n/**\n * Parse a referrer URL to get source, medium and hostname\n * \n * @param referrerUrl - URL of the referrer to parse\n * @param options - Configuration options\n * @param referrerSource - Optional source to override URL parameters\n * @param referrerMedium - Optional medium to override URL parameters\n * @returns Parsed referrer data with source, medium and URL\n * \n * @example\n * // Basic usage\n * const result = parse('https://www.google.com/search?q=ghost+cms');\n * // result: { referrerSource: 'Google', referrerMedium: 'search', referrerUrl: 'www.google.com' }\n * \n * @example\n * // With site configuration\n * const result = parse('https://example.com/blog?utm_source=newsletter', {\n * siteUrl: 'https://example.com'\n * });\n * \n * @example\n * // With explicit source and medium\n * const result = parse('https://example.com', {}, 'newsletter', 'email');\n */\nfunction parse(\n referrerUrl: string, \n options: ParserOptions = {}, \n referrerSource?: string, \n referrerMedium?: string\n): ReferrerData {\n const parser = new ReferrerParser(options);\n return parser.parse(referrerUrl, referrerSource, referrerMedium);\n}\n\nexport {\n parse,\n ReferrerParser\n};\n\nexport type {\n ReferrerData,\n ParserOptions\n}; "],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAoCO,MAAM,eA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"}