mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-20 01:03:23 +03:00
c04d8532ca
no issue When a user fails to pay the first time, and returns to the site, the next payment will be attributed to Stripe instead of the original referrer. This change always ignores the Stripe checkout as referrer in the URL history.
215 lines
6.5 KiB
JavaScript
215 lines
6.5 KiB
JavaScript
/**
|
|
* @typedef {Object} ReferrerData
|
|
* @prop {string|null} [referrerSource]
|
|
* @prop {string|null} [referrerMedium]
|
|
* @prop {string|null} [referrerUrl]
|
|
*/
|
|
|
|
const knownReferrers = require('@tryghost/referrers');
|
|
|
|
/**
|
|
* Translates referrer info into Source and Medium
|
|
*/
|
|
class ReferrerTranslator {
|
|
/**
|
|
*
|
|
* @param {Object} deps
|
|
* @param {string} deps.siteUrl
|
|
* @param {string} deps.adminUrl
|
|
*/
|
|
constructor({adminUrl, siteUrl}) {
|
|
this.adminUrl = this.getUrlFromStr(adminUrl);
|
|
this.siteUrl = this.getUrlFromStr(siteUrl);
|
|
}
|
|
|
|
/**
|
|
* Calculate referrer details from history
|
|
* @param {import('./UrlHistory').UrlHistoryArray} history
|
|
* @returns {ReferrerData|null}
|
|
*/
|
|
getReferrerDetails(history) {
|
|
// Empty history will return null as it means script is not loaded
|
|
if (history.length === 0) {
|
|
return {
|
|
referrerSource: null,
|
|
referrerMedium: null,
|
|
referrerUrl: null
|
|
};
|
|
}
|
|
|
|
for (const item of history) {
|
|
const referrerUrl = this.getUrlFromStr(item.referrerUrl);
|
|
|
|
if (referrerUrl?.hostname === 'checkout.stripe.com') {
|
|
// Ignore stripe, because second try payments should not be attributed to Stripe
|
|
continue;
|
|
}
|
|
|
|
const referrerSource = item.referrerSource;
|
|
const referrerMedium = item.referrerMedium;
|
|
|
|
// If referrer is Ghost Explore
|
|
if (this.isGhostExploreRef({referrerUrl, referrerSource})) {
|
|
return {
|
|
referrerSource: 'Ghost Explore',
|
|
referrerMedium: 'Ghost Network',
|
|
referrerUrl: referrerUrl?.hostname ?? null
|
|
};
|
|
}
|
|
|
|
// If referrer is Ghost.org
|
|
if (this.isGhostOrgUrl(referrerUrl)) {
|
|
return {
|
|
referrerSource: 'Ghost.org',
|
|
referrerMedium: 'Ghost Network',
|
|
referrerUrl: referrerUrl?.hostname
|
|
};
|
|
}
|
|
|
|
// If referrer is Ghost Newsletter
|
|
if (this.isGhostNewsletter({referrerSource})) {
|
|
return {
|
|
referrerSource: referrerSource.replace(/-/g, ' '),
|
|
referrerMedium: 'Email',
|
|
referrerUrl: referrerUrl?.hostname ?? null
|
|
};
|
|
}
|
|
|
|
// If referrer is from query params
|
|
if (referrerSource) {
|
|
const urlData = referrerUrl ? this.getDataFromUrl(referrerUrl) : null;
|
|
return {
|
|
referrerSource: referrerSource,
|
|
referrerMedium: referrerMedium || urlData?.medium || null,
|
|
referrerUrl: referrerUrl?.hostname ?? null
|
|
};
|
|
}
|
|
|
|
// If referrer is known external URL
|
|
if (referrerUrl && !this.isSiteDomain(referrerUrl)) {
|
|
const urlData = this.getDataFromUrl(referrerUrl);
|
|
|
|
// Use known source/medium if available
|
|
if (urlData) {
|
|
return {
|
|
referrerSource: urlData?.source ?? null,
|
|
referrerMedium: urlData?.medium ?? null,
|
|
referrerUrl: referrerUrl?.hostname ?? null
|
|
};
|
|
}
|
|
// Use the hostname as a source
|
|
return {
|
|
referrerSource: referrerUrl?.hostname ?? null,
|
|
referrerMedium: null,
|
|
referrerUrl: referrerUrl?.hostname ?? null
|
|
};
|
|
}
|
|
}
|
|
|
|
return {
|
|
referrerSource: 'Direct',
|
|
referrerMedium: null,
|
|
referrerUrl: null
|
|
};
|
|
}
|
|
|
|
// Fetches referrer data from known external URLs
|
|
getDataFromUrl(url) {
|
|
// Allow matching both "google.ac/products" and "google.ac" as a source
|
|
const urlHostPath = url?.host + url?.pathname;
|
|
const urlDataKey = Object.keys(knownReferrers).sort((a, b) => {
|
|
// The longer key has higher the priority so google.ac/products is selected before google.ac
|
|
return b.length - a.length;
|
|
}).find((source) => {
|
|
return urlHostPath?.startsWith(source);
|
|
});
|
|
|
|
return urlDataKey ? knownReferrers[urlDataKey] : null;
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Return URL object for provided URL string
|
|
* @param {string} url
|
|
* @returns {URL|null}
|
|
*/
|
|
getUrlFromStr(url) {
|
|
try {
|
|
return new URL(url);
|
|
} catch (e) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Return whether the provided URL is a link to the site
|
|
* @param {URL} url
|
|
* @returns {boolean}
|
|
*/
|
|
isSiteDomain(url) {
|
|
try {
|
|
if (this.siteUrl && this.siteUrl?.hostname === url?.hostname) {
|
|
if (url?.pathname?.startsWith(this.siteUrl?.pathname)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Return whether provided ref is a Ghost newsletter
|
|
* @param {Object} deps
|
|
* @param {string|null} deps.referrerSource
|
|
* @returns {boolean}
|
|
*/
|
|
isGhostNewsletter({referrerSource}) {
|
|
// if refferer source ends with -newsletter
|
|
return referrerSource?.endsWith('-newsletter');
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Return whether provided ref is a Ghost.org URL
|
|
* @param {URL|null} referrerUrl
|
|
* @returns {boolean}
|
|
*/
|
|
isGhostOrgUrl(referrerUrl) {
|
|
return referrerUrl?.hostname === 'ghost.org';
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* Return whether provided ref is Ghost Explore
|
|
* @param {Object} deps
|
|
* @param {URL|null} deps.referrerUrl
|
|
* @param {string|null} deps.referrerSource
|
|
* @returns {boolean}
|
|
*/
|
|
isGhostExploreRef({referrerUrl, referrerSource}) {
|
|
if (referrerSource === 'ghost-explore') {
|
|
return true;
|
|
}
|
|
|
|
if (referrerUrl?.hostname
|
|
&& this.adminUrl?.hostname === referrerUrl?.hostname
|
|
&& referrerUrl?.pathname?.startsWith(this.adminUrl?.pathname)
|
|
) {
|
|
return true;
|
|
}
|
|
|
|
if (referrerUrl?.hostname === 'ghost.org' && referrerUrl?.pathname?.startsWith('/explore')) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
module.exports = ReferrerTranslator;
|