From 1fece8178e9063ff6367999a8c1525ae561f0a92 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis Date: Tue, 19 Sep 2023 10:50:18 +0100 Subject: [PATCH 1/8] remove add free cookie expiry --- .../conf/switches/CommercialSwitches.scala | 10 -- .../modules/commercial/user-features.spec.js | 108 -------------- .../common/modules/commercial/lib/cookie.ts | 1 + .../modules/commercial/user-features.spec.ts | 132 +----------------- .../modules/commercial/user-features.ts | 14 +- 5 files changed, 7 insertions(+), 258 deletions(-) diff --git a/common/app/conf/switches/CommercialSwitches.scala b/common/app/conf/switches/CommercialSwitches.scala index bcab923228b6..8ced2ff4b042 100644 --- a/common/app/conf/switches/CommercialSwitches.scala +++ b/common/app/conf/switches/CommercialSwitches.scala @@ -26,16 +26,6 @@ trait CommercialSwitches { exposeClientSide = true, ) - val AdFreeStrictExpiryEnforcement = Switch( - Commercial, - "ad-free-strict-expiry-enforcement", - "When ON, the ad-free cookie is valid for max. 48 hours. OFF doesn't enforce expiry check.", - owners = Seq(Owner.withGithub("JustinPinner")), - safeState = Off, - sellByDate = never, - exposeClientSide = true, - ) - val ImrWorldwideSwitch = Switch( Commercial, "imr-worldwide", diff --git a/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js b/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js index e8173fbaf9d1..e03cc9686f3f 100644 --- a/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js +++ b/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js @@ -41,7 +41,6 @@ const fetchJsonSpy: any = fetchJson; const isUserLoggedIn: any = isUserLoggedIn_; const PERSISTENCE_KEYS = { - USER_FEATURES_EXPIRY_COOKIE: 'gu_user_features_expiry', PAYING_MEMBER_COOKIE: 'gu_paying_member', RECURRING_CONTRIBUTOR_COOKIE: 'gu_recurring_contributor', AD_FREE_USER_COOKIE: 'GU_AF1', @@ -56,52 +55,16 @@ const PERSISTENCE_KEYS = { 'gu.contributions.recurring.contrib-timestamp.Annual', }; -const setAllFeaturesData = opts => { - const currentTime = new Date().getTime(); - const msInOneDay = 24 * 60 * 60 * 1000; - const expiryDate = opts.isExpired - ? new Date(currentTime - msInOneDay) - : new Date(currentTime + msInOneDay); - const adFreeExpiryDate = opts.isExpired - ? new Date(currentTime - msInOneDay * 2) - : new Date(currentTime + msInOneDay * 2); - addCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE, 'true'); - addCookie( - PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, - adFreeExpiryDate.getTime().toString() - ); - addCookie( - PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, - expiryDate.getTime().toString() - ); - addCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE, 'test'); -}; - -const setExpiredAdFreeData = () => { - const currentTime = new Date().getTime(); - const msInOneDay = 24 * 60 * 60 * 1000; - const expiryDate = new Date(currentTime - msInOneDay * 2); - addCookie( - PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, - expiryDate.getTime().toString() - ); -}; - const deleteAllFeaturesData = () => { removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); removeCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE); removeCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE); - removeCookie(PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE); removeCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE); removeCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE); removeCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE); }; beforeAll(() => { - config.set('switches.adFreeStrictExpiryEnforcement', true); config.set('page.userAttributesApiUrl', ''); }); @@ -119,56 +82,17 @@ describe('Refreshing the features data', () => { expect(fetchJsonSpy).toHaveBeenCalledTimes(1); }); - it('Performs an update if the user has expired data', () => { - setAllFeaturesData({ isExpired: true }); - refresh(); - expect(fetchJsonSpy).toHaveBeenCalledTimes(1); - }); - - it('Does not delete the data just because it has expired', () => { - setAllFeaturesData({ isExpired: true }); - refresh(); - expect(getCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE)).toBe( - 'true' - ); - expect( - getCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE) - ).toBe('true'); - expect( - getCookie(PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE) - ).toEqual(expect.stringMatching(/\d{13}/)); - expect(getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE)).toEqual( - expect.stringMatching(/\d{13}/) - ); - }); - it('Does not perform update if user has fresh feature data', () => { - setAllFeaturesData({ isExpired: false }); refresh(); expect(fetchJsonSpy).not.toHaveBeenCalled(); }); it('Performs an update if membership-frontend wipes just the paying-member cookie', () => { - // Set everything except paying-member cookie - setAllFeaturesData({ isExpired: true }); removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); refresh(); expect(fetchJsonSpy).toHaveBeenCalledTimes(1); }); - - it('Performs an update if the ad-free state is stale and strict expiry enforcement is enabled', () => { - // This is a slightly synthetic setup - the ad-free cookie is rewritten with every - // refresh that happens as a result of expired features data, but we want to check - // that a refresh could be triggered based on ad-free state alone if the strict - // expiry enforcement switch is ON. - // Set everything except the ad-free cookie - setAllFeaturesData({ isExpired: false }); - setExpiredAdFreeData(); - - refresh(); - expect(fetchJsonSpy).toHaveBeenCalledTimes(1); - }); }); describe('If user signed out', () => { @@ -185,7 +109,6 @@ describe('Refreshing the features data', () => { }); it('Deletes leftover feature data', () => { - setAllFeaturesData({ isExpired: false }); refresh(); expect(getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE)).toBeNull(); expect(getCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE)).toBeNull(); @@ -414,35 +337,8 @@ describe('Storing new feature data', () => { expect( getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE) ).toBeTruthy(); - expect( - Number.isNaN( - parseInt( - getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE), - 10 - ) - ) - ).toBe(false); }); }); - - it('Puts an expiry date in an accompanying cookie', () => - refresh().then(() => { - const expiryDate = getCookie( - PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE - ); - expect(expiryDate).toBeTruthy(); - expect(Number.isNaN(parseInt(expiryDate, 10))).toBe(false); - })); - - it('The expiry date is in the future', () => - refresh().then(() => { - const expiryDateString = getCookie( - PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE - ); - const expiryDateEpoch = parseInt(expiryDateString, 10); - const currentTimeEpoch = new Date().getTime(); - expect(currentTimeEpoch < expiryDateEpoch).toBe(true); - })); }); const setSupportFrontendOneOffContributionCookie = (value: any): void => @@ -482,10 +378,6 @@ describe('getting the last one-off contribution date of a user', () => { expect(getLastOneOffContributionDate()).toBe(null); }); - it('returns the correct date if cookie from attributes is set', () => { - setAttributesOneOffContributionCookie(contributionDate.toString()); - expect(getLastOneOffContributionDate()).toBe(contributionDateTimeEpoch); - }); }); const setMonthlyContributionCookie = (value: any): void => diff --git a/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts b/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts index bceb71eff8e6..9f26e071215f 100644 --- a/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts +++ b/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts @@ -12,6 +12,7 @@ const timeInDaysFromNow = (daysFromNow: number): string => { return tmpDate.getTime().toString(); }; +console.log('Test in FRONTEND- '); const cookieIsExpiredOrMissing = (cookieName: string): boolean => { const expiryDateFromCookie = getCookie({ name: cookieName }); if (!expiryDateFromCookie) return true; diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts index 705721e40850..7c7020459653 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts @@ -63,52 +63,16 @@ const PERSISTENCE_KEYS = { 'gu.contributions.recurring.contrib-timestamp.Annual', }; -const setAllFeaturesData = (opts: { isExpired: boolean }) => { - const currentTime = new Date().getTime(); - const msInOneDay = 24 * 60 * 60 * 1000; - const expiryDate = opts.isExpired - ? new Date(currentTime - msInOneDay) - : new Date(currentTime + msInOneDay); - const adFreeExpiryDate = opts.isExpired - ? new Date(currentTime - msInOneDay * 2) - : new Date(currentTime + msInOneDay * 2); - addCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE, 'true'); - addCookie( - PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, - adFreeExpiryDate.getTime().toString(), - ); - addCookie( - PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, - expiryDate.getTime().toString(), - ); - addCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE, 'test'); -}; - -const setExpiredAdFreeData = () => { - const currentTime = new Date().getTime(); - const msInOneDay = 24 * 60 * 60 * 1000; - const expiryDate = new Date(currentTime - msInOneDay * 2); - addCookie( - PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, - expiryDate.getTime().toString(), - ); -}; - const deleteAllFeaturesData = () => { removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); removeCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE); removeCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE); - removeCookie(PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE); removeCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE); removeCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE); removeCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE); }; beforeAll(() => { - window.guardian.config.switches.adFreeStrictExpiryEnforcement = true; window.guardian.config.page.userAttributesApiUrl = ''; }); @@ -128,61 +92,6 @@ describe('Refreshing the features data', () => { await refresh(); expect(fetchJsonSpy).toHaveBeenCalledTimes(1); }); - - it('Performs an update if the user has expired data', async () => { - setAllFeaturesData({ isExpired: true }); - await refresh(); - expect(fetchJsonSpy).toHaveBeenCalledTimes(1); - }); - - it('Does not delete the data just because it has expired', async () => { - setAllFeaturesData({ isExpired: true }); - await refresh(); - expect( - getCookie({ name: PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE }), - ).toBe('true'); - expect( - getCookie({ - name: PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, - }), - ).toBe('true'); - expect( - getCookie({ - name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, - }), - ).toEqual(expect.stringMatching(/\d{13}/)); - expect( - getCookie({ name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE }), - ).toEqual(expect.stringMatching(/\d{13}/)); - }); - - it('Does not perform update if user has fresh feature data', async () => { - setAllFeaturesData({ isExpired: false }); - await refresh(); - expect(fetchJsonSpy).not.toHaveBeenCalled(); - }); - - it('Performs an update if membership-frontend wipes just the paying-member cookie', async () => { - // Set everything except paying-member cookie - setAllFeaturesData({ isExpired: true }); - removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); - - await refresh(); - expect(fetchJsonSpy).toHaveBeenCalledTimes(1); - }); - - it('Performs an update if the ad-free state is stale and strict expiry enforcement is enabled', async () => { - // This is a slightly synthetic setup - the ad-free cookie is rewritten with every - // refresh that happens as a result of expired features data, but we want to check - // that a refresh could be triggered based on ad-free state alone if the strict - // expiry enforcement switch is ON. - // Set everything except the ad-free cookie - setAllFeaturesData({ isExpired: false }); - setExpiredAdFreeData(); - - await refresh(); - expect(fetchJsonSpy).toHaveBeenCalledTimes(1); - }); }); describe('If user signed out', () => { @@ -200,7 +109,6 @@ describe('Refreshing the features data', () => { }); it('Deletes leftover feature data', async () => { - setAllFeaturesData({ isExpired: false }); await refresh(); expect( getCookie({ name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE }), @@ -216,11 +124,6 @@ describe('Refreshing the features data', () => { expect( getCookie({ name: PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE }), ).toBeNull(); - expect( - getCookie({ - name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, - }), - ).toBeNull(); }); }); }); @@ -444,6 +347,9 @@ describe('Storing new feature data', () => { adFree: true, }), ); + addCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE, 'true'); return refresh().then(() => { expect( getCookie({ name: PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE }), @@ -459,40 +365,8 @@ describe('Storing new feature data', () => { expect( getCookie({ name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE }), ).toBeTruthy(); - expect( - Number.isNaN( - parseInt( - // @ts-expect-error -- we’re testing it - getCookie({ - name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, - }), - 10, - ), - ), - ).toBe(false); }); }); - - it('Puts an expiry date in an accompanying cookie', () => - refresh().then(() => { - const expiryDate = getCookie({ - name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, - }); - expect(expiryDate).toBeTruthy(); - // @ts-expect-error -- we’re testing it - expect(Number.isNaN(parseInt(expiryDate, 10))).toBe(false); - })); - - it('The expiry date is in the future', () => - refresh().then(() => { - const expiryDateString = getCookie({ - name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, - }); - // @ts-expect-error -- we’re testing it - const expiryDateEpoch = parseInt(expiryDateString, 10); - const currentTimeEpoch = new Date().getTime(); - expect(currentTimeEpoch < expiryDateEpoch).toBe(true); - })); }); const setSupportFrontendOneOffContributionCookie = (value: string) => diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.ts index 49e0b18cc567..e30fdb9219ca 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.ts @@ -42,6 +42,7 @@ const AD_FREE_USER_COOKIE = 'GU_AF1'; // TODO: isn’t this duplicated from commercial features? // https://github.com/guardian/frontend/blob/2a222cfb77748aa1140e19adca10bfc688fe6cad/static/src/javascripts/projects/common/modules/commercial/commercial-features.ts + const forcedAdFreeMode = !!/[#&]noadsaf(&.*)?$/.exec(window.location.hash); const getAdFreeCookie = (): string | null => @@ -53,14 +54,6 @@ const adFreeDataIsPresent = (): boolean => { return !Number.isNaN(parseInt(cookieVal, 10)); }; -const adFreeDataIsOld = (): boolean => { - const { switches } = window.guardian.config; - return ( - Boolean(switches.adFreeStrictExpiryEnforcement) && - cookieIsExpiredOrMissing(AD_FREE_USER_COOKIE) - ); -}; - const setAdFreeCookie = (daysToLive = 1): void => { const expires = new Date(); expires.setMonth(expires.getMonth() + 6); @@ -142,7 +135,6 @@ const persistResponse = (JsonResponse: UserFeaturesResponse) => { value: JsonResponse.alertAvailableFor, }); } - if (JsonResponse.contentAccess.digitalPack) { setAdFreeCookie(2); } else if (adFreeDataIsPresent() && !forcedAdFreeMode) { @@ -195,7 +187,7 @@ const featuresDataIsOld = () => const userNeedsNewFeatureData = (): boolean => featuresDataIsOld() || - (adFreeDataIsPresent() && adFreeDataIsOld()) || + adFreeDataIsPresent() || (isDigitalSubscriber() && !adFreeDataIsPresent()); const userHasDataAfterSignout = async (): Promise => @@ -369,7 +361,7 @@ const fakeOneOffContributor = (): void => { }; const isAdFreeUser = (): boolean => - isDigitalSubscriber() || (adFreeDataIsPresent() && !adFreeDataIsOld()); + isDigitalSubscriber() || adFreeDataIsPresent(); // Extend the expiry of the contributions cookie by 1 year beyond the date of the contribution const extendContribsCookieExpiry = (): void => { From 4bd4119d3f7d88554f88ba8f8905f477c92dbb75 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis Date: Tue, 19 Sep 2023 17:03:09 +0100 Subject: [PATCH 2/8] revert test file --- .../modules/commercial/user-features.spec.js | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js b/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js index e03cc9686f3f..e8173fbaf9d1 100644 --- a/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js +++ b/static/src/javascripts.flow.archive/projects/common/modules/commercial/user-features.spec.js @@ -41,6 +41,7 @@ const fetchJsonSpy: any = fetchJson; const isUserLoggedIn: any = isUserLoggedIn_; const PERSISTENCE_KEYS = { + USER_FEATURES_EXPIRY_COOKIE: 'gu_user_features_expiry', PAYING_MEMBER_COOKIE: 'gu_paying_member', RECURRING_CONTRIBUTOR_COOKIE: 'gu_recurring_contributor', AD_FREE_USER_COOKIE: 'GU_AF1', @@ -55,16 +56,52 @@ const PERSISTENCE_KEYS = { 'gu.contributions.recurring.contrib-timestamp.Annual', }; +const setAllFeaturesData = opts => { + const currentTime = new Date().getTime(); + const msInOneDay = 24 * 60 * 60 * 1000; + const expiryDate = opts.isExpired + ? new Date(currentTime - msInOneDay) + : new Date(currentTime + msInOneDay); + const adFreeExpiryDate = opts.isExpired + ? new Date(currentTime - msInOneDay * 2) + : new Date(currentTime + msInOneDay * 2); + addCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE, 'true'); + addCookie( + PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, + adFreeExpiryDate.getTime().toString() + ); + addCookie( + PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, + expiryDate.getTime().toString() + ); + addCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE, 'test'); +}; + +const setExpiredAdFreeData = () => { + const currentTime = new Date().getTime(); + const msInOneDay = 24 * 60 * 60 * 1000; + const expiryDate = new Date(currentTime - msInOneDay * 2); + addCookie( + PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, + expiryDate.getTime().toString() + ); +}; + const deleteAllFeaturesData = () => { removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); removeCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE); removeCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE); + removeCookie(PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE); removeCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE); removeCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE); removeCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE); }; beforeAll(() => { + config.set('switches.adFreeStrictExpiryEnforcement', true); config.set('page.userAttributesApiUrl', ''); }); @@ -82,17 +119,56 @@ describe('Refreshing the features data', () => { expect(fetchJsonSpy).toHaveBeenCalledTimes(1); }); + it('Performs an update if the user has expired data', () => { + setAllFeaturesData({ isExpired: true }); + refresh(); + expect(fetchJsonSpy).toHaveBeenCalledTimes(1); + }); + + it('Does not delete the data just because it has expired', () => { + setAllFeaturesData({ isExpired: true }); + refresh(); + expect(getCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE)).toBe( + 'true' + ); + expect( + getCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE) + ).toBe('true'); + expect( + getCookie(PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE) + ).toEqual(expect.stringMatching(/\d{13}/)); + expect(getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE)).toEqual( + expect.stringMatching(/\d{13}/) + ); + }); + it('Does not perform update if user has fresh feature data', () => { + setAllFeaturesData({ isExpired: false }); refresh(); expect(fetchJsonSpy).not.toHaveBeenCalled(); }); it('Performs an update if membership-frontend wipes just the paying-member cookie', () => { + // Set everything except paying-member cookie + setAllFeaturesData({ isExpired: true }); removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); refresh(); expect(fetchJsonSpy).toHaveBeenCalledTimes(1); }); + + it('Performs an update if the ad-free state is stale and strict expiry enforcement is enabled', () => { + // This is a slightly synthetic setup - the ad-free cookie is rewritten with every + // refresh that happens as a result of expired features data, but we want to check + // that a refresh could be triggered based on ad-free state alone if the strict + // expiry enforcement switch is ON. + // Set everything except the ad-free cookie + setAllFeaturesData({ isExpired: false }); + setExpiredAdFreeData(); + + refresh(); + expect(fetchJsonSpy).toHaveBeenCalledTimes(1); + }); }); describe('If user signed out', () => { @@ -109,6 +185,7 @@ describe('Refreshing the features data', () => { }); it('Deletes leftover feature data', () => { + setAllFeaturesData({ isExpired: false }); refresh(); expect(getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE)).toBeNull(); expect(getCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE)).toBeNull(); @@ -337,8 +414,35 @@ describe('Storing new feature data', () => { expect( getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE) ).toBeTruthy(); + expect( + Number.isNaN( + parseInt( + getCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE), + 10 + ) + ) + ).toBe(false); }); }); + + it('Puts an expiry date in an accompanying cookie', () => + refresh().then(() => { + const expiryDate = getCookie( + PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE + ); + expect(expiryDate).toBeTruthy(); + expect(Number.isNaN(parseInt(expiryDate, 10))).toBe(false); + })); + + it('The expiry date is in the future', () => + refresh().then(() => { + const expiryDateString = getCookie( + PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE + ); + const expiryDateEpoch = parseInt(expiryDateString, 10); + const currentTimeEpoch = new Date().getTime(); + expect(currentTimeEpoch < expiryDateEpoch).toBe(true); + })); }); const setSupportFrontendOneOffContributionCookie = (value: any): void => @@ -378,6 +482,10 @@ describe('getting the last one-off contribution date of a user', () => { expect(getLastOneOffContributionDate()).toBe(null); }); + it('returns the correct date if cookie from attributes is set', () => { + setAttributesOneOffContributionCookie(contributionDate.toString()); + expect(getLastOneOffContributionDate()).toBe(contributionDateTimeEpoch); + }); }); const setMonthlyContributionCookie = (value: any): void => From c28e0c9a30d38a0868886fab5593944846729c03 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis <49187886+dskamiotis@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:03:47 +0100 Subject: [PATCH 3/8] Update static/src/javascripts/projects/common/modules/commercial/user-features.ts Co-authored-by: Emma Imber <108270776+emma-imber@users.noreply.github.com> --- .../projects/common/modules/commercial/user-features.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.ts index e30fdb9219ca..e84d0d51dc63 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.ts @@ -187,7 +187,7 @@ const featuresDataIsOld = () => const userNeedsNewFeatureData = (): boolean => featuresDataIsOld() || - adFreeDataIsPresent() || + (adFreeDataIsPresent() && cookieIsExpiredOrMissing(AD_FREE_USER_COOKIE)) || (isDigitalSubscriber() && !adFreeDataIsPresent()); const userHasDataAfterSignout = async (): Promise => From 160cb607a0481f2be48440ce5dcb0e566ba4ae26 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis <49187886+dskamiotis@users.noreply.github.com> Date: Tue, 19 Sep 2023 17:04:38 +0100 Subject: [PATCH 4/8] Update static/src/javascripts/projects/common/modules/commercial/user-features.ts Co-authored-by: Emma Imber <108270776+emma-imber@users.noreply.github.com> --- .../projects/common/modules/commercial/user-features.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.ts index e84d0d51dc63..cbc4b43914d5 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.ts @@ -361,7 +361,7 @@ const fakeOneOffContributor = (): void => { }; const isAdFreeUser = (): boolean => - isDigitalSubscriber() || adFreeDataIsPresent(); + isDigitalSubscriber() || (adFreeDataIsPresent() && !cookieIsExpiredOrMissing(AD_FREE_USER_COOKIE)); // Extend the expiry of the contributions cookie by 1 year beyond the date of the contribution const extendContribsCookieExpiry = (): void => { From 531c0b09c1080b45f4fa9c8eb2ee982583a3b0d1 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis Date: Tue, 19 Sep 2023 17:13:20 +0100 Subject: [PATCH 5/8] remove logs and make fix eslint --- .../common/modules/commercial/lib/cookie.ts | 1 - .../modules/commercial/user-features.spec.ts | 108 +++++++++++++++++- .../modules/commercial/user-features.ts | 7 +- 3 files changed, 107 insertions(+), 9 deletions(-) diff --git a/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts b/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts index 9f26e071215f..bceb71eff8e6 100644 --- a/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts +++ b/static/src/javascripts/projects/common/modules/commercial/lib/cookie.ts @@ -12,7 +12,6 @@ const timeInDaysFromNow = (daysFromNow: number): string => { return tmpDate.getTime().toString(); }; -console.log('Test in FRONTEND- '); const cookieIsExpiredOrMissing = (cookieName: string): boolean => { const expiryDateFromCookie = getCookie({ name: cookieName }); if (!expiryDateFromCookie) return true; diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts index 7c7020459653..33c1282fc81c 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.spec.ts @@ -63,10 +63,35 @@ const PERSISTENCE_KEYS = { 'gu.contributions.recurring.contrib-timestamp.Annual', }; +const setAllFeaturesData = (opts: { isExpired: boolean }) => { + const currentTime = new Date().getTime(); + const msInOneDay = 24 * 60 * 60 * 1000; + const expiryDate = opts.isExpired + ? new Date(currentTime - msInOneDay) + : new Date(currentTime + msInOneDay); + const adFreeExpiryDate = opts.isExpired + ? new Date(currentTime - msInOneDay * 2) + : new Date(currentTime + msInOneDay * 2); + addCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE, 'true'); + addCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE, 'true'); + addCookie( + PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, + adFreeExpiryDate.getTime().toString(), + ); + addCookie( + PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, + expiryDate.getTime().toString(), + ); + addCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE, 'test'); +}; + const deleteAllFeaturesData = () => { removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); removeCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE); removeCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE); + removeCookie(PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE); removeCookie(PERSISTENCE_KEYS.AD_FREE_USER_COOKIE); removeCookie(PERSISTENCE_KEYS.ACTION_REQUIRED_FOR_COOKIE); removeCookie(PERSISTENCE_KEYS.HIDE_SUPPORT_MESSAGING_COOKIE); @@ -92,6 +117,48 @@ describe('Refreshing the features data', () => { await refresh(); expect(fetchJsonSpy).toHaveBeenCalledTimes(1); }); + + it('Performs an update if the user has expired data', async () => { + setAllFeaturesData({ isExpired: true }); + await refresh(); + expect(fetchJsonSpy).toHaveBeenCalledTimes(1); + }); + + it('Does not delete the data just because it has expired', async () => { + setAllFeaturesData({ isExpired: true }); + await refresh(); + expect( + getCookie({ name: PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE }), + ).toBe('true'); + expect( + getCookie({ + name: PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, + }), + ).toBe('true'); + expect( + getCookie({ + name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, + }), + ).toEqual(expect.stringMatching(/\d{13}/)); + expect( + getCookie({ name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE }), + ).toEqual(expect.stringMatching(/\d{13}/)); + }); + + it('Does not perform update if user has fresh feature data', async () => { + setAllFeaturesData({ isExpired: false }); + await refresh(); + expect(fetchJsonSpy).not.toHaveBeenCalled(); + }); + + it('Performs an update if membership-frontend wipes just the paying-member cookie', async () => { + // Set everything except paying-member cookie + setAllFeaturesData({ isExpired: true }); + removeCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE); + + await refresh(); + expect(fetchJsonSpy).toHaveBeenCalledTimes(1); + }); }); describe('If user signed out', () => { @@ -109,6 +176,7 @@ describe('Refreshing the features data', () => { }); it('Deletes leftover feature data', async () => { + setAllFeaturesData({ isExpired: false }); await refresh(); expect( getCookie({ name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE }), @@ -124,6 +192,11 @@ describe('Refreshing the features data', () => { expect( getCookie({ name: PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE }), ).toBeNull(); + expect( + getCookie({ + name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, + }), + ).toBeNull(); }); }); }); @@ -347,9 +420,6 @@ describe('Storing new feature data', () => { adFree: true, }), ); - addCookie(PERSISTENCE_KEYS.RECURRING_CONTRIBUTOR_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE, 'true'); - addCookie(PERSISTENCE_KEYS.DIGITAL_SUBSCRIBER_COOKIE, 'true'); return refresh().then(() => { expect( getCookie({ name: PERSISTENCE_KEYS.PAYING_MEMBER_COOKIE }), @@ -365,8 +435,40 @@ describe('Storing new feature data', () => { expect( getCookie({ name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE }), ).toBeTruthy(); + expect( + Number.isNaN( + parseInt( + // @ts-expect-error -- we’re testing it + getCookie({ + name: PERSISTENCE_KEYS.AD_FREE_USER_COOKIE, + }), + 10, + ), + ), + ).toBe(false); }); }); + + it('Puts an expiry date in an accompanying cookie', () => + refresh().then(() => { + const expiryDate = getCookie({ + name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, + }); + expect(expiryDate).toBeTruthy(); + // @ts-expect-error -- we’re testing it + expect(Number.isNaN(parseInt(expiryDate, 10))).toBe(false); + })); + + it('The expiry date is in the future', () => + refresh().then(() => { + const expiryDateString = getCookie({ + name: PERSISTENCE_KEYS.USER_FEATURES_EXPIRY_COOKIE, + }); + // @ts-expect-error -- we’re testing it + const expiryDateEpoch = parseInt(expiryDateString, 10); + const currentTimeEpoch = new Date().getTime(); + expect(currentTimeEpoch < expiryDateEpoch).toBe(true); + })); }); const setSupportFrontendOneOffContributionCookie = (value: string) => diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.ts index cbc4b43914d5..2b367208bbd0 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.ts @@ -186,9 +186,7 @@ const featuresDataIsOld = () => cookieIsExpiredOrMissing(USER_FEATURES_EXPIRY_COOKIE); const userNeedsNewFeatureData = (): boolean => - featuresDataIsOld() || - (adFreeDataIsPresent() && cookieIsExpiredOrMissing(AD_FREE_USER_COOKIE)) || - (isDigitalSubscriber() && !adFreeDataIsPresent()); + featuresDataIsOld() || (isDigitalSubscriber() && !adFreeDataIsPresent()); const userHasDataAfterSignout = async (): Promise => !(await isUserLoggedIn()) && userHasData(); @@ -360,8 +358,7 @@ const fakeOneOffContributor = (): void => { }); }; -const isAdFreeUser = (): boolean => - isDigitalSubscriber() || (adFreeDataIsPresent() && !cookieIsExpiredOrMissing(AD_FREE_USER_COOKIE)); +const isAdFreeUser = (): boolean => isDigitalSubscriber(); // Extend the expiry of the contributions cookie by 1 year beyond the date of the contribution const extendContribsCookieExpiry = (): void => { From 29cba018683b67aea79b1ab4775f2967d07f0ec3 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis <49187886+dskamiotis@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:53:52 +0100 Subject: [PATCH 6/8] Update static/src/javascripts/projects/common/modules/commercial/user-features.ts Co-authored-by: Emma Imber <108270776+emma-imber@users.noreply.github.com> --- .../projects/common/modules/commercial/user-features.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/src/javascripts/projects/common/modules/commercial/user-features.ts b/static/src/javascripts/projects/common/modules/commercial/user-features.ts index 2b367208bbd0..b1b5f29d6e6a 100644 --- a/static/src/javascripts/projects/common/modules/commercial/user-features.ts +++ b/static/src/javascripts/projects/common/modules/commercial/user-features.ts @@ -358,7 +358,8 @@ const fakeOneOffContributor = (): void => { }); }; -const isAdFreeUser = (): boolean => isDigitalSubscriber(); +const isAdFreeUser = (): boolean => + isDigitalSubscriber() || adFreeDataIsPresent(); // Extend the expiry of the contributions cookie by 1 year beyond the date of the contribution const extendContribsCookieExpiry = (): void => { From 756722b46dfcccb87507c8a2de0b46214547b5cf Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis Date: Wed, 20 Sep 2023 13:10:38 +0100 Subject: [PATCH 7/8] bump to beta version --- package.json | 2 +- yarn.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index fedf19808b59..01ff648a4292 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@guardian/atom-renderer": "^2.0.0", "@guardian/automat-modules": "^0.3.8", "@guardian/braze-components": "^14.1.1", - "@guardian/commercial": "11.9.0", + "@guardian/commercial": "^0.0.0-beta-20230920120447", "@guardian/consent-management-platform": "^13.6.1", "@guardian/core-web-vitals": "^5.0.0", "@guardian/identity-auth": "1.1.0", diff --git a/yarn.lock b/yarn.lock index 7f32a4b58cc3..99da3c3e2bf8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1556,17 +1556,17 @@ resolved "https://registry.yarnpkg.com/@guardian/braze-components/-/braze-components-14.1.1.tgz#12672e3992ad3eab3bade848861ca01631a0edf5" integrity sha512-wHuJwdOC2hsTBie+zWJYFyasP4fOJXOPcKXpN2Cp+GlljPah3YtS2Fbgn8pcxxt8d5JKO9ra7sHKvX8FNxkAyg== -"@guardian/commercial@11.9.0": - version "11.9.0" - resolved "https://registry.yarnpkg.com/@guardian/commercial/-/commercial-11.9.0.tgz#8f8ee116cd6b8fd228d15f53be0480639c13f258" - integrity sha512-GFdg3hsVhDcUQnmXZgcVsKtq4DbfSKeZw4mTUqaL8NQ3fO3QRvhP3tS5cQGqSu6p7K+dzaThVveMHbXekbLcFA== +"@guardian/commercial@^0.0.0-beta-20230920120447": + version "0.0.0-beta-20230920120447" + resolved "https://registry.yarnpkg.com/@guardian/commercial/-/commercial-0.0.0-beta-20230920120447.tgz#74ad793bf130f6220327acca71797c71723d6ae9" + integrity sha512-Lzbs72FsNx1/S+gFsWpqxTI9VU77iQYFKiFeycghSTLDAX4Jax4NHpOrrzFV08LrJJ5W5lQKwYPjyA7hSr6oUw== dependencies: "@changesets/cli" "^2.26.2" "@octokit/core" "^4.0.5" fastdom "^1.0.11" lodash-es "^4.17.21" ophan-tracker-js "^1.4.0" - prebid.js guardian/prebid.js#b8263f2 + prebid.js guardian/prebid.js#356f371 process "^0.11.10" raven-js "^3.27.2" tslib "^2.5.3" @@ -10494,9 +10494,9 @@ preact@^10.5.13: resolved "https://registry.yarnpkg.com/preact/-/preact-10.13.2.tgz#2c40c73d57248b57234c4ae6cd9ab9d8186ebc0a" integrity sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw== -prebid.js@guardian/prebid.js#b8263f2: - version "7.54.4" - resolved "https://codeload.github.com/guardian/prebid.js/tar.gz/b8263f2e041833b0ec0d05d14de6c3f1bdd466ff" +"prebid.js@guardian/prebid.js#356f371": + version "7.54.5" + resolved "https://codeload.github.com/guardian/prebid.js/tar.gz/356f3714fda0459b6740e90ff265055fe6d4f813" dependencies: "@babel/core" "^7.16.7" "@babel/plugin-transform-runtime" "^7.18.9" From cc8af65c6e11ddd4648f0accf77625889c49ca46 Mon Sep 17 00:00:00 2001 From: Demetrios Skamiotis Date: Wed, 20 Sep 2023 14:52:48 +0100 Subject: [PATCH 8/8] Revert "bump to beta version" This reverts commit 756722b46dfcccb87507c8a2de0b46214547b5cf. --- package.json | 2 +- yarn.lock | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index 01ff648a4292..fedf19808b59 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@guardian/atom-renderer": "^2.0.0", "@guardian/automat-modules": "^0.3.8", "@guardian/braze-components": "^14.1.1", - "@guardian/commercial": "^0.0.0-beta-20230920120447", + "@guardian/commercial": "11.9.0", "@guardian/consent-management-platform": "^13.6.1", "@guardian/core-web-vitals": "^5.0.0", "@guardian/identity-auth": "1.1.0", diff --git a/yarn.lock b/yarn.lock index 99da3c3e2bf8..7f32a4b58cc3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1556,17 +1556,17 @@ resolved "https://registry.yarnpkg.com/@guardian/braze-components/-/braze-components-14.1.1.tgz#12672e3992ad3eab3bade848861ca01631a0edf5" integrity sha512-wHuJwdOC2hsTBie+zWJYFyasP4fOJXOPcKXpN2Cp+GlljPah3YtS2Fbgn8pcxxt8d5JKO9ra7sHKvX8FNxkAyg== -"@guardian/commercial@^0.0.0-beta-20230920120447": - version "0.0.0-beta-20230920120447" - resolved "https://registry.yarnpkg.com/@guardian/commercial/-/commercial-0.0.0-beta-20230920120447.tgz#74ad793bf130f6220327acca71797c71723d6ae9" - integrity sha512-Lzbs72FsNx1/S+gFsWpqxTI9VU77iQYFKiFeycghSTLDAX4Jax4NHpOrrzFV08LrJJ5W5lQKwYPjyA7hSr6oUw== +"@guardian/commercial@11.9.0": + version "11.9.0" + resolved "https://registry.yarnpkg.com/@guardian/commercial/-/commercial-11.9.0.tgz#8f8ee116cd6b8fd228d15f53be0480639c13f258" + integrity sha512-GFdg3hsVhDcUQnmXZgcVsKtq4DbfSKeZw4mTUqaL8NQ3fO3QRvhP3tS5cQGqSu6p7K+dzaThVveMHbXekbLcFA== dependencies: "@changesets/cli" "^2.26.2" "@octokit/core" "^4.0.5" fastdom "^1.0.11" lodash-es "^4.17.21" ophan-tracker-js "^1.4.0" - prebid.js guardian/prebid.js#356f371 + prebid.js guardian/prebid.js#b8263f2 process "^0.11.10" raven-js "^3.27.2" tslib "^2.5.3" @@ -10494,9 +10494,9 @@ preact@^10.5.13: resolved "https://registry.yarnpkg.com/preact/-/preact-10.13.2.tgz#2c40c73d57248b57234c4ae6cd9ab9d8186ebc0a" integrity sha512-q44QFLhOhty2Bd0Y46fnYW0gD/cbVM9dUVtNTDKPcdXSMA7jfY+Jpd6rk3GB0lcQss0z5s/6CmVP0Z/hV+g6pw== -"prebid.js@guardian/prebid.js#356f371": - version "7.54.5" - resolved "https://codeload.github.com/guardian/prebid.js/tar.gz/356f3714fda0459b6740e90ff265055fe6d4f813" +prebid.js@guardian/prebid.js#b8263f2: + version "7.54.4" + resolved "https://codeload.github.com/guardian/prebid.js/tar.gz/b8263f2e041833b0ec0d05d14de6c3f1bdd466ff" dependencies: "@babel/core" "^7.16.7" "@babel/plugin-transform-runtime" "^7.18.9"