From c3eb0a845d3ecca4a61a46c7661e16f168be2a34 Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Fri, 5 Jan 2024 11:17:31 +0800 Subject: [PATCH 1/8] Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test resolve conflict --- modules/discoveryBidAdapter.js | 74 ++++++ test/spec/modules/discoveryBidAdapter_spec.js | 229 +++++++++++++++++- 2 files changed, 302 insertions(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 315df48fa5d..f3727925664 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -58,6 +58,65 @@ const NATIVERET = { }; /** + * get page title + * @returns {string} + */ + +export function getPageTitle(win = window) { + try { + const ogTitle = win.top.document.querySelector('meta[property="og:title"]') + return win.top.document.title || (ogTitle && ogTitle.content) || ''; + } catch (e) { + const ogTitle = document.querySelector('meta[property="og:title"]') + return document.title || (ogTitle && ogTitle.content) || ''; + } +} + +/** + * get page description + * @returns {string} + */ +export function getPageDescription(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="description"]') || + win.top.document.querySelector('meta[property="og:description"]') + } catch (e) { + element = document.querySelector('meta[name="description"]') || + document.querySelector('meta[property="og:description"]') + } + + return (element && element.content) || ''; +} + +/** + * get page keywords + * @returns {string} + */ +export function getPageKeywords(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="keywords"]'); + } catch (e) { + element = document.querySelector('meta[name="keywords"]'); + } + + return (element && element.content) || ''; +} + +/** + * get connection downlink + * @returns {number} + */ +export function getConnectionDownLink(win = window) { + const nav = win.navigator || {}; + return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : undefined; +} + +/** + * 获取用户id * 获取并生成用户的id * @return {string} */ @@ -362,6 +421,10 @@ function getParam(validBidRequests, bidderRequest) { const page = utils.deepAccess(bidderRequest, 'refererInfo.page'); const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const firstPartyData = bidderRequest.ortb2; + const topWindow = window.top; + const title = getPageTitle(); + const desc = getPageDescription(); + const keywords = getPageKeywords(); if (items && items.length) { let c = { @@ -384,6 +447,17 @@ function getParam(validBidRequests, bidderRequest) { firstPartyData, ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, pmguid: getPmgUID(), + page: { + title: title ? title.slice(0, 100) : undefined, + desc: desc ? desc.slice(0, 300) : undefined, + keywords: keywords ? keywords.slice(0, 100) : undefined, + hLen: topWindow.history?.length || undefined, + }, + device: { + nbw: getConnectionDownLink(), + hc: topWindow.navigator?.hardwareConcurrency || undefined, + dm: topWindow.navigator?.deviceMemory || undefined, + } }, user: { buyeruid: storage.getCookie(COOKIE_KEY_MGUID) || undefined, diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index acfc519bef9..05216ff126c 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, getPmgUID, storage } from 'modules/discoveryBidAdapter.js'; +import { spec, getPmgUID, storage, getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from 'modules/discoveryBidAdapter.js'; import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { @@ -218,3 +218,230 @@ describe('discovery:BidAdapterTests', function () { }); }); }); + +describe('discovery Bid Adapter Tests', function () { + describe('buildRequests', () => { + describe('getPageTitle function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document title if available', function() { + const fakeTopDocument = { + title: 'Top Document Title', + querySelector: () => ({ content: 'Top Document Title test' }) + }; + const fakeTopWindow = { + document: fakeTopDocument + }; + const result = getPageTitle({ top: fakeTopWindow }); + expect(result).to.equal('Top Document Title'); + }); + + it('should return the content of top og:title meta tag if title is empty', function() { + const ogTitleContent = 'Top OG Title Content'; + const fakeTopWindow = { + document: { + title: '', + querySelector: sandbox.stub().withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }) + } + }; + + const result = getPageTitle({ top: fakeTopWindow }); + expect(result).to.equal(ogTitleContent); + }); + + it('should return the document title if no og:title meta tag is present', function() { + document.title = 'Test Page Title'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns(null); + + const result = getPageTitle({ top: undefined }); + expect(result).to.equal('Test Page Title'); + }); + + it('should return the content of og:title meta tag if present', function() { + document.title = ''; + const ogTitleContent = 'Top OG Title Content'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }); + const result = getPageTitle({ top: undefined }); + expect(result).to.equal(ogTitleContent); + }); + + it('should return an empty string if no title or og:title meta tag is found', function() { + document.title = ''; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns(null); + const result = getPageTitle({ top: undefined }); + expect(result).to.equal(''); + }); + + it('should handle exceptions when accessing top.document and fallback to current document', function() { + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const ogTitleContent = 'Current OG Title Content'; + document.title = 'Current Document Title'; + sandbox.stub(document, 'querySelector').withArgs('meta[property="og:title"]').returns({ content: ogTitleContent }); + const result = getPageTitle(fakeWindow); + expect(result).to.equal('Current Document Title'); + }); + }); + + describe('getPageDescription function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document description if available', function() { + const descriptionContent = 'Top Document Description'; + const fakeTopDocument = { + querySelector: sandbox.stub().withArgs('meta[name="description"]').returns({ content: descriptionContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + const result = getPageDescription({ top: fakeTopWindow }); + expect(result).to.equal(descriptionContent); + }); + + it('should return the top document og:description if description is not present', function() { + const ogDescriptionContent = 'Top OG Description'; + const fakeTopDocument = { + querySelector: sandbox.stub().withArgs('meta[property="og:description"]').returns({ content: ogDescriptionContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + const result = getPageDescription({ top: fakeTopWindow }); + expect(result).to.equal(ogDescriptionContent); + }); + + it('should return the current document description if top document is not accessible', function() { + const descriptionContent = 'Current Document Description'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="description"]').returns({ content: descriptionContent }) + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getPageDescription(fakeWindow); + expect(result).to.equal(descriptionContent); + }); + + it('should return the current document og:description if description is not present and top document is not accessible', function() { + const ogDescriptionContent = 'Current OG Description'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[property="og:description"]').returns({ content: ogDescriptionContent }); + + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getPageDescription(fakeWindow); + expect(result).to.equal(ogDescriptionContent); + }); + }); + + describe('getPageKeywords function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the top document keywords if available', function() { + const keywordsContent = 'keyword1, keyword2, keyword3'; + const fakeTopDocument = { + querySelector: sandbox.stub() + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }) + }; + const fakeTopWindow = { document: fakeTopDocument }; + + const result = getPageKeywords({ top: fakeTopWindow }); + expect(result).to.equal(keywordsContent); + }); + + it('should return the current document keywords if top document is not accessible', function() { + const keywordsContent = 'keyword1, keyword2, keyword3'; + sandbox.stub(document, 'querySelector') + .withArgs('meta[name="keywords"]').returns({ content: keywordsContent }); + + // 模拟顶层窗口访问异常 + const fakeWindow = { + get top() { + throw new Error('Access denied'); + } + }; + + const result = getPageKeywords(fakeWindow); + expect(result).to.equal(keywordsContent); + }); + + it('should return an empty string if no keywords meta tag is found', function() { + sandbox.stub(document, 'querySelector').withArgs('meta[name="keywords"]').returns(null); + + const result = getPageKeywords(); + expect(result).to.equal(''); + }); + }); + describe('getConnectionDownLink function', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return the downlink value as a string if available', function() { + const downlinkValue = 2.5; + const fakeNavigator = { + connection: { + downlink: downlinkValue + } + }; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.equal(downlinkValue.toString()); + }); + + it('should return undefined if downlink is not available', function() { + const fakeNavigator = { + connection: {} + }; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.be.undefined; + }); + + it('should return undefined if connection is not available', function() { + const fakeNavigator = {}; + + const result = getConnectionDownLink({ navigator: fakeNavigator }); + expect(result).to.be.undefined; + }); + + it('should handle cases where navigator is not defined', function() { + const result = getConnectionDownLink({}); + expect(result).to.be.undefined; + }); + }); + }); +}); From cb251c07517749b327d65d79fd17039c7d6e1c90 Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Fri, 5 Jan 2024 14:25:14 +0800 Subject: [PATCH 2/8] Discovery Bid Adapter : add title, desc, keywords, hLen, nbw, hc, dm add unit test --- modules/discoveryBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index f3727925664..551c78c1e24 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -116,7 +116,7 @@ export function getConnectionDownLink(win = window) { } /** - * 获取用户id + * get pmg uid * 获取并生成用户的id * @return {string} */ From 137dbbbf5ad91b1e3a51b1c4d89edfcc21a4a26b Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Wed, 10 Jan 2024 20:47:20 +0800 Subject: [PATCH 3/8] Discovery Bid Adapter : synchronize mguid from third party cookie to first party cookie --- modules/discoveryBidAdapter.js | 31 +++++++-- test/spec/modules/discoveryBidAdapter_spec.js | 68 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 551c78c1e24..0496b39a919 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -13,10 +13,11 @@ const MEDIATYPE = [BANNER, NATIVE]; /* ----- _ss_pp_id:start ------ */ const COOKIE_KEY_SSPPID = '_ss_pp_id'; -const COOKIE_KEY_MGUID = '__mguid_'; +export const COOKIE_KEY_MGUID = '__mguid_'; const COOKIE_KEY_PMGUID = '__pmguid_'; const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year const COOKY_SYNC_IFRAME_URL = 'https://asset.popin.cc/js/cookieSync.html'; +export const THIRD_PARTY_COOKIE_ORIGIN = 'https://asset.popin.cc'; const NATIVERET = { id: 'id', @@ -126,10 +127,8 @@ export const getPmgUID = () => { let pmgUid = storage.getCookie(COOKIE_KEY_PMGUID); if (!pmgUid) { pmgUid = utils.generateUUID(); - const date = new Date(); - date.setTime(date.getTime() + COOKIE_RETENTION_TIME); try { - storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, date.toUTCString()); + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); } catch (e) {} } return pmgUid; @@ -295,6 +294,16 @@ function getReferrer(bidRequest = {}, bidderRequest = {}) { return pageUrl; } +/** + * get current time to UTC string + * @returns utc string + */ +export function getCurrentTimeToUTCString() { + const date = new Date(); + date.setTime(date.getTime() + COOKIE_RETENTION_TIME); + return date.toUTCString(); +} + /** * format imp ad test ext params * @@ -640,6 +649,20 @@ export const spec = { } if (syncOptions.iframeEnabled) { + window.addEventListener('message', function handler(event) { + if (!event.data || event.origin != THIRD_PARTY_COOKIE_ORIGIN) { + return; + } + + this.removeEventListener('message', handler); + + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + } + }, true); return [ { type: 'iframe', diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 05216ff126c..05b73dfe2f0 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -1,5 +1,16 @@ import { expect } from 'chai'; -import { spec, getPmgUID, storage, getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from 'modules/discoveryBidAdapter.js'; +import { + spec, + getPmgUID, + storage, + getPageTitle, + getPageDescription, + getPageKeywords, + getConnectionDownLink, + THIRD_PARTY_COOKIE_ORIGIN, + COOKIE_KEY_MGUID, + getCurrentTimeToUTCString +} from 'modules/discoveryBidAdapter.js'; import * as utils from 'src/utils.js'; describe('discovery:BidAdapterTests', function () { @@ -443,5 +454,60 @@ describe('discovery Bid Adapter Tests', function () { expect(result).to.be.undefined; }); }); + + describe('getUserSyncs with message event listener', function() { + function messageHandler(event) { + if (!event.data || event.origin !== THIRD_PARTY_COOKIE_ORIGIN) { + return; + } + + window.removeEventListener('message', messageHandler, true); + event.stopImmediatePropagation(); + + const response = event.data; + if (!response.optout && response.mguid) { + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + } + } + + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(window, 'removeEventListener'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should set a cookie when a valid message is received', () => { + const fakeEvent = { + data: { optout: '', mguid: '12345' }, + origin: THIRD_PARTY_COOKIE_ORIGIN, + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.calledOnce).to.be.true; + expect(window.removeEventListener.calledWith('message', messageHandler, true)).to.be.true; + expect(storage.setCookie.calledWith(COOKIE_KEY_MGUID, '12345', sinon.match.string)).to.be.true; + }); + it('should not do anything when an invalid message is received', () => { + const fakeEvent = { + data: null, + origin: 'http://invalid-origin.com', + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true; + expect(window.removeEventListener.notCalled).to.be.true; + expect(storage.setCookie.notCalled).to.be.true; + }); + }); }); }); From 01ff272ac8f977a9dcc65c4767251c049aee5b2c Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Wed, 21 Feb 2024 20:02:49 +0800 Subject: [PATCH 4/8] test --- modules/discoveryBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 0496b39a919..465e7375a43 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -59,7 +59,7 @@ const NATIVERET = { }; /** - * get page title + * get page title111 * @returns {string} */ From 96c9bebefe92a0450d4fe45e6887d31da5be7664 Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Thu, 29 Feb 2024 11:01:38 +0800 Subject: [PATCH 5/8] Discovery Bid Adapter : Extend the expiration time of pmguid --- modules/discoveryBidAdapter.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 71033dcd86e..270d5aac803 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -133,10 +133,10 @@ export const getPmgUID = () => { let pmgUid = storage.getCookie(COOKIE_KEY_PMGUID); if (!pmgUid) { pmgUid = utils.generateUUID(); - try { - storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); - } catch (e) {} } + try { + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); + } catch (e) {} return pmgUid; }; From 635cd7cad5c8f0cffb879fdccbbd6fb8a9866ec3 Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Thu, 29 Feb 2024 20:00:56 +0800 Subject: [PATCH 6/8] Discovery Bid Adapter : Extend the expiration time of pmguid --- modules/discoveryBidAdapter.js | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 270d5aac803..34f81f91ebb 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -134,6 +134,7 @@ export const getPmgUID = () => { if (!pmgUid) { pmgUid = utils.generateUUID(); } + // 无限续期 try { storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); } catch (e) {} From bc598d532ce8e251a989f74f142017d1236134e0 Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Mon, 4 Mar 2024 11:12:53 +0800 Subject: [PATCH 7/8] Discovery Bid Adapter : Extend the expiration time of pmguid --- modules/discoveryBidAdapter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 34f81f91ebb..aa497b99d00 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -134,7 +134,7 @@ export const getPmgUID = () => { if (!pmgUid) { pmgUid = utils.generateUUID(); } - // 无限续期 + // Extend the expiration time of pmguid try { storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); } catch (e) {} From 2ed03db7ad97026f207ea44535f462702c10e9f1 Mon Sep 17 00:00:00 2001 From: lvhuixin Date: Mon, 4 Mar 2024 11:44:25 +0800 Subject: [PATCH 8/8] Discovery Bid Adapter : Extend the expiration time of pmguid --- test/spec/modules/discoveryBidAdapter_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 961ccb33c4f..4fb4c29b99b 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -219,7 +219,7 @@ describe('discovery:BidAdapterTests', function () { storage.getCookie.callsFake(() => 'existing-uuid'); const uid = getPmgUID(); expect(uid).to.equal('existing-uuid'); - expect(storage.setCookie.called).to.be.false; + expect(storage.setCookie.called).to.be.true; }); it('should not set new UUID when cookies are not enabled', () => {