From c4c823bd60d7e6f54727265713732c142e9cb167 Mon Sep 17 00:00:00 2001 From: "Alyssa.Yu" Date: Wed, 11 Sep 2024 17:04:21 -0700 Subject: [PATCH 1/2] feat: decode URI for page view properties --- .../src/page-view-tracking.ts | 20 ++- .../test/page-view-tracking.test.ts | 161 ++++++++++++++++++ 2 files changed, 177 insertions(+), 4 deletions(-) diff --git a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts index 9e19768d5..668bfec94 100644 --- a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts +++ b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts @@ -22,20 +22,32 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (options: Op let localConfig: BrowserConfig; const { trackOn, trackHistoryChanges, eventType = defaultPageViewEvent } = options; + const getDecodeURI = (locationStr: string): string => { + let decodedLocationStr = locationStr; + try { + decodedLocationStr = decodeURI(locationStr); + } catch (e) { + /* istanbul ignore next */ + loggerProvider?.log('Malformed URI sequence: ', e); + } + + return decodedLocationStr; + }; + const createPageViewEvent = async (): Promise => { + const locationHref = getDecodeURI(location.href); return { event_type: eventType, event_properties: { ...(await getCampaignParams()), '[Amplitude] Page Domain': /* istanbul ignore next */ (typeof location !== 'undefined' && location.hostname) || '', - '[Amplitude] Page Location': - /* istanbul ignore next */ (typeof location !== 'undefined' && location.href) || '', + '[Amplitude] Page Location': /* istanbul ignore next */ (typeof location !== 'undefined' && locationHref) || '', '[Amplitude] Page Path': - /* istanbul ignore next */ (typeof location !== 'undefined' && location.pathname) || '', + /* istanbul ignore next */ (typeof location !== 'undefined' && getDecodeURI(location.pathname)) || '', '[Amplitude] Page Title': /* istanbul ignore next */ (typeof document !== 'undefined' && document.title) || '', '[Amplitude] Page URL': - /* istanbul ignore next */ (typeof location !== 'undefined' && location.href.split('?')[0]) || '', + /* istanbul ignore next */ (typeof location !== 'undefined' && locationHref.split('?')[0]) || '', }, }; }; diff --git a/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts b/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts index 22f164704..7c1e0b8d1 100644 --- a/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts +++ b/packages/plugin-page-view-tracking-browser/test/page-view-tracking.test.ts @@ -197,6 +197,167 @@ describe('pageViewTrackingPlugin', () => { expect(track).toHaveBeenCalledTimes(1); }); + + test('should track dynamic page view with decoded URI location info', async () => { + mockConfig.pageCounter = 0; + + const amplitude = createInstance(); + const track = jest.spyOn(amplitude, 'track').mockReturnValue({ + promise: Promise.resolve({ + code: 200, + message: '', + event: { + event_type: '[Amplitude] Page Viewed', + }, + }), + }); + + const oldURL = new URL('https://www.example.com'); + mockWindowLocationFromURL(oldURL); + const plugin = pageViewTrackingPlugin(); + await plugin.setup?.(mockConfig, amplitude); + + // https://www.example.com/home-шеллы?x=test + const newURL = new URL('https://www.example.com/home-%D1%88%D0%B5%D0%BB%D0%BB%D1%8B?x=test'); + mockWindowLocationFromURL(newURL); + window.history.pushState(undefined, newURL.href); + + // Page view tracking on push state executes async + // Block event loop for 1s before asserting + await new Promise((resolve) => setTimeout(resolve, 1000)); + + expect(track).toHaveBeenNthCalledWith(1, { + event_properties: { + '[Amplitude] Page Domain': oldURL.hostname, + '[Amplitude] Page Location': oldURL.toString(), + '[Amplitude] Page Path': oldURL.pathname, + '[Amplitude] Page Title': '', + '[Amplitude] Page URL': oldURL.toString(), + }, + event_type: '[Amplitude] Page Viewed', + }); + + expect(track).toHaveBeenNthCalledWith(2, { + event_properties: { + '[Amplitude] Page Domain': newURL.hostname, + '[Amplitude] Page Location': 'https://www.example.com/home-шеллы?x=test', + '[Amplitude] Page Path': '/home-шеллы', + '[Amplitude] Page Title': '', + '[Amplitude] Page URL': 'https://www.example.com/home-шеллы', + }, + event_type: '[Amplitude] Page Viewed', + }); + + expect(track).toHaveBeenCalledTimes(2); + }); + + test('should track dynamic page view with malformed location info', async () => { + mockConfig.pageCounter = 0; + + const amplitude = createInstance(); + const track = jest.spyOn(amplitude, 'track').mockReturnValue({ + promise: Promise.resolve({ + code: 200, + message: '', + event: { + event_type: '[Amplitude] Page Viewed', + }, + }), + }); + + const oldURL = new URL('https://www.example.com'); + mockWindowLocationFromURL(oldURL); + const plugin = pageViewTrackingPlugin(); + await plugin.setup?.(mockConfig, amplitude); + + const malformedPath = '/home-%D1%88%D0%B5%D0BB%D0%BB%D1%8B'; // Invalid encoding string + const malformedURL = `https://www.example.com${malformedPath}`; + const malformedLocation = `https://www.example.com${malformedPath}?x=test`; + const newURL = new URL(malformedLocation); + mockWindowLocationFromURL(newURL); + window.history.pushState(undefined, newURL.href); + + // Page view tracking on push state executes async + // Block event loop for 1s before asserting + await new Promise((resolve) => setTimeout(resolve, 1000)); + + expect(track).toHaveBeenNthCalledWith(1, { + event_properties: { + '[Amplitude] Page Domain': oldURL.hostname, + '[Amplitude] Page Location': oldURL.toString(), + '[Amplitude] Page Path': oldURL.pathname, + '[Amplitude] Page Title': '', + '[Amplitude] Page URL': oldURL.toString(), + }, + event_type: '[Amplitude] Page Viewed', + }); + + expect(track).toHaveBeenNthCalledWith(2, { + event_properties: { + '[Amplitude] Page Domain': newURL.hostname, + '[Amplitude] Page Location': malformedLocation, + '[Amplitude] Page Path': malformedPath, + '[Amplitude] Page Title': '', + '[Amplitude] Page URL': malformedURL, + }, + event_type: '[Amplitude] Page Viewed', + }); + + expect(track).toHaveBeenCalledTimes(2); + }); + + test('should track dynamic page view with regular location info', async () => { + mockConfig.pageCounter = 0; + + const amplitude = createInstance(); + const track = jest.spyOn(amplitude, 'track').mockReturnValue({ + promise: Promise.resolve({ + code: 200, + message: '', + event: { + event_type: '[Amplitude] Page Viewed', + }, + }), + }); + + const oldURL = new URL('https://www.example.com'); + mockWindowLocationFromURL(oldURL); + const plugin = pageViewTrackingPlugin(); + await plugin.setup?.(mockConfig, amplitude); + + const newBaseURL = `https://www.example.com/home-shell`; + const newURL = new URL(`${newBaseURL}?x=test`); + mockWindowLocationFromURL(newURL); + window.history.pushState(undefined, newURL.href); + + // Page view tracking on push state executes async + // Block event loop for 1s before asserting + await new Promise((resolve) => setTimeout(resolve, 1000)); + + expect(track).toHaveBeenNthCalledWith(1, { + event_properties: { + '[Amplitude] Page Domain': oldURL.hostname, + '[Amplitude] Page Location': oldURL.toString(), + '[Amplitude] Page Path': oldURL.pathname, + '[Amplitude] Page Title': '', + '[Amplitude] Page URL': oldURL.toString(), + }, + event_type: '[Amplitude] Page Viewed', + }); + + expect(track).toHaveBeenNthCalledWith(2, { + event_properties: { + '[Amplitude] Page Domain': newURL.hostname, + '[Amplitude] Page Location': newURL.toString(), + '[Amplitude] Page Path': newURL.pathname, + '[Amplitude] Page Title': '', + '[Amplitude] Page URL': newBaseURL, + }, + event_type: '[Amplitude] Page Viewed', + }); + + expect(track).toHaveBeenCalledTimes(2); + }); }); describe('execute', () => { From cf26b71872eb6b8580145eec27069431f17fb346 Mon Sep 17 00:00:00 2001 From: "Alyssa.Yu" Date: Thu, 12 Sep 2024 10:56:42 -0700 Subject: [PATCH 2/2] fix: fix location undefined --- .../src/page-view-tracking.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts index 668bfec94..96bf9a771 100644 --- a/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts +++ b/packages/plugin-page-view-tracking-browser/src/page-view-tracking.ts @@ -28,26 +28,26 @@ export const pageViewTrackingPlugin: CreatePageViewTrackingPlugin = (options: Op decodedLocationStr = decodeURI(locationStr); } catch (e) { /* istanbul ignore next */ - loggerProvider?.log('Malformed URI sequence: ', e); + loggerProvider?.error('Malformed URI sequence: ', e); } return decodedLocationStr; }; const createPageViewEvent = async (): Promise => { - const locationHref = getDecodeURI(location.href); + /* istanbul ignore next */ + const locationHREF = getDecodeURI((typeof location !== 'undefined' && location.href) || ''); return { event_type: eventType, event_properties: { ...(await getCampaignParams()), '[Amplitude] Page Domain': /* istanbul ignore next */ (typeof location !== 'undefined' && location.hostname) || '', - '[Amplitude] Page Location': /* istanbul ignore next */ (typeof location !== 'undefined' && locationHref) || '', + '[Amplitude] Page Location': locationHREF, '[Amplitude] Page Path': /* istanbul ignore next */ (typeof location !== 'undefined' && getDecodeURI(location.pathname)) || '', '[Amplitude] Page Title': /* istanbul ignore next */ (typeof document !== 'undefined' && document.title) || '', - '[Amplitude] Page URL': - /* istanbul ignore next */ (typeof location !== 'undefined' && locationHref.split('?')[0]) || '', + '[Amplitude] Page URL': locationHREF.split('?')[0], }, }; };