diff --git a/ghost/portal/src/utils/api.js b/ghost/portal/src/utils/api.js index df5546201f..c5803a2506 100644 --- a/ghost/portal/src/utils/api.js +++ b/ghost/portal/src/utils/api.js @@ -1,16 +1,4 @@ -import {transformApiSiteData, transformApiTiersData} from './helpers'; - -function getAnalyticsMetadata() { - const analyticsTag = document.querySelector('meta[name=ghost-analytics-id]'); - const analyticsId = analyticsTag?.content; - if (analyticsTag) { - return { - entry_id: analyticsId, - source_url: window.location.href - }; - } - return null; -} +import {transformApiSiteData, transformApiTiersData, getUrlHistory} from './helpers'; function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) { const apiPath = 'members/api'; @@ -188,10 +176,6 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) { body.enable_comment_notifications = enableCommentNotifications; } - const analyticsData = getAnalyticsMetadata(); - if (analyticsData) { - body.metadata = analyticsData; - } return makeRequest({ url, method: 'PUT', @@ -219,10 +203,11 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) { labels, requestSrc: 'portal' }; - const analyticsData = getAnalyticsMetadata(); - if (analyticsData) { - body.metadata = analyticsData; + const urlHistory = getUrlHistory(); + if (urlHistory) { + body.urlHistory = urlHistory; } + return makeRequest({ url, method: 'POST', @@ -334,6 +319,7 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) { newsletters: JSON.stringify(newsletters), requestSrc: 'portal', fp_tid: (window.FPROM || window.$FPROM)?.data?.tid, + urlHistory: getUrlHistory(), ...metadata }; @@ -439,10 +425,6 @@ function setupGhostApi({siteUrl = window.location.origin, apiUrl, apiKey}) { identity: identity, priceId: planId }; - const analyticsData = getAnalyticsMetadata(); - if (body) { - body.metadata = analyticsData; - } if (tierId && cadence) { delete body.priceId; diff --git a/ghost/portal/src/utils/helpers.js b/ghost/portal/src/utils/helpers.js index d18870adff..33633a5b2f 100644 --- a/ghost/portal/src/utils/helpers.js +++ b/ghost/portal/src/utils/helpers.js @@ -791,3 +791,28 @@ export const transformApiTiersData = ({tiers}) => { }; }); }; + +/** + * Returns the member attribution URL history, which is stored in localStorage, if there is any. + * @returns {Object[]|undefined} + */ +export function getUrlHistory() { + const STORAGE_KEY = 'ghost-history'; + + try { + const historyString = localStorage.getItem(STORAGE_KEY); + if (historyString) { + const parsed = JSON.parse(historyString); + + if (Array.isArray(parsed)) { + return parsed; + } + } + } catch (error) { + // Failed to access localStorage or something related to that. + // Log a warning, as this shouldn't happen on a modern browser. + + /* eslint-disable no-console */ + console.warn(`[Portal] Failed to load member URL history:`, error); + } +} diff --git a/ghost/portal/src/utils/helpers.test.js b/ghost/portal/src/utils/helpers.test.js index 56a9cd3dda..8b95bade3a 100644 --- a/ghost/portal/src/utils/helpers.test.js +++ b/ghost/portal/src/utils/helpers.test.js @@ -1,4 +1,4 @@ -import {getCurrencySymbol, getFreeProduct, getMemberName, getMemberSubscription, getPriceFromSubscription, getPriceIdFromPageQuery, getSupportAddress, hasMultipleProducts, isActiveOffer, isInviteOnlySite, isPaidMember, isSameCurrency, transformApiTiersData} from './helpers'; +import {getCurrencySymbol, getFreeProduct, getMemberName, getMemberSubscription, getPriceFromSubscription, getPriceIdFromPageQuery, getSupportAddress, getUrlHistory, hasMultipleProducts, isActiveOffer, isInviteOnlySite, isPaidMember, isSameCurrency, transformApiTiersData} from './helpers'; import * as Fixtures from './fixtures-generator'; import {site as FixturesSite, member as FixtureMember, offer as FixtureOffer, transformTierFixture as TransformFixtureTiers} from '../utils/test-fixtures'; import {isComplimentaryMember} from '../utils/helpers'; @@ -255,4 +255,43 @@ describe('Helpers - ', () => { expect(transformedTiers[1].benefits).toHaveLength(3); }); }); + + describe('getUrlHistory', () => { + beforeEach(() => { + jest.spyOn(console, 'warn').mockImplementation(() => { + // don't log for these tests + }); + }); + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('returns valid history ', () => { + jest.spyOn(Storage.prototype, 'getItem').mockReturnValue(JSON.stringify([ + { + path: '/', + time: 0 + } + ])); + const urlHistory = getUrlHistory(); + expect(localStorage.getItem).toHaveBeenCalled(); + expect(urlHistory).toHaveLength(1); + }); + + test('ignores invalid history ', () => { + jest.spyOn(Storage.prototype, 'getItem').mockReturnValue('invalid'); + const urlHistory = getUrlHistory(); + expect(localStorage.getItem).toHaveBeenCalled(); + expect(urlHistory).toBeUndefined(); + }); + + test('doesn\'t throw if localStorage is disabled', () => { + jest.spyOn(Storage.prototype, 'getItem').mockImplementation(() => { + throw new Error('LocalStorage disabled'); + }); + const urlHistory = getUrlHistory(); + expect(localStorage.getItem).toHaveBeenCalled(); + expect(urlHistory).toBeUndefined(); + }); + }); });