From 17a7958145ee86e52d23d35f30026cd1e2a00d9d Mon Sep 17 00:00:00 2001 From: Jose Climaco Date: Wed, 4 Mar 2026 09:29:15 +0000 Subject: [PATCH 01/29] InsurAds Rtd Provider --- modules/insuradsRtdProvider.md | 59 ++++ modules/insuradsRtdProvider.ts | 96 +++++ test/spec/modules/insuradsRtdProvider_spec.js | 334 ++++++++++++++++++ 3 files changed, 489 insertions(+) create mode 100644 modules/insuradsRtdProvider.md create mode 100644 modules/insuradsRtdProvider.ts create mode 100644 test/spec/modules/insuradsRtdProvider_spec.js diff --git a/modules/insuradsRtdProvider.md b/modules/insuradsRtdProvider.md new file mode 100644 index 00000000000..5488754da02 --- /dev/null +++ b/modules/insuradsRtdProvider.md @@ -0,0 +1,59 @@ +## InsurAds Real-time Data Submodule + +The [InsurAds](https://insurads.com) real-time data module in Prebid enables publishers to leverage +contextual targeting and audience segmentation capabilities. This module provides real-time +key-value targeting data that seamlessly integrates into your existing Prebid deployment, +helping you maximize your advertising strategies. + +## Building Prebid with InsurAds Support + +Compile the InsurAds RTD module into your Prebid build: + +`gulp serve --modules=rtdModule,insuradsBidAdapter,insurAdsRtdProvider` + +Please visit https://insurads.com/ for more information. + +```javascript +pbjs.setConfig({ + ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [{ + name: 'insuradsRtd', + waitForIt: true, + params: { + publicId: 'YOUR_PUBLIC_ID' + } + }] + } + ... +}); +``` + +### Parameter Descriptions for the InsurAds Configuration Section + +| Name | Type | Description | Notes | +|:------------------|:--------|:--------------------------------------------------------------------------|:-------------------------| +| name | String | Real time data module name | Always 'insuradsRtd' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | +| params | Object | | | +| params.publicId | String | This is the Public ID value obtained from InsurAds | Required | + +## Testing + +To view an example of InsurAds RTD provider: + +`gulp serve --modules=rtdModule,insuradsBidAdapter,insurAdsRtdProvider` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/insurads.html` + +## How It Works + +The InsurAds RTD provider: + +1. Fetches contextual targeting data from the InsurAds API using your `publicId` +2. Stores the returned key-values internally +3. Applies the key-values as targeting data to all ad units in the auction +4. Enhances ad targeting without requiring additional code changes diff --git a/modules/insuradsRtdProvider.ts b/modules/insuradsRtdProvider.ts new file mode 100644 index 00000000000..1975bfe4103 --- /dev/null +++ b/modules/insuradsRtdProvider.ts @@ -0,0 +1,96 @@ +import { submodule } from '../src/hook.js'; +import { logInfo } from '../src/utils.js' +import { fetch } from '../src/ajax.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + +const SERVER_IP = 'https://services.insurads.com'; +const MODULE_NAME = 'insuradsRtd'; +const ENDPOINT = `${SERVER_IP}/core/init/prebid`; +const LOG_PREFIX = 'insurads Rtd: '; +const GVLID = 596; + +// Internal state to store keyValues +let keyValues = {}; + +/** @type {RtdSubmodule} */ +export const subModuleObj = { + name: MODULE_NAME, + gvlid: GVLID, + init: init, + getTargetingData: getTargetingData +}; + +export const insurAdsRtdProvider = { + name: MODULE_NAME, + gvlid: GVLID, + init: init, + getTargetingData: getTargetingData +}; + +function init(config, userConsent) { + const publicId = config?.params?.publicId; + + if (!publicId) { + logInfo(LOG_PREFIX + 'publicId is required'); + return false; + } + + // Start fetch immediately without blocking init + makeApiCall(publicId); + + logInfo(LOG_PREFIX + 'submodule init', config, userConsent); + return true; +} + +function makeApiCall(publicId) { + const currentUrl = encodeURIComponent(location.href); + + fetch(`${ENDPOINT}/${publicId}?url=${currentUrl}`, { + keepalive: true, + credentials: 'include', + method: 'GET', + headers: { + 'Content-Type': 'application/json' + } + }).then((response) => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }).then((data) => { + // Store the keyValues internally for later use in getTargetingData + if (data && data.keyValues) { + keyValues = data.keyValues; + logInfo(LOG_PREFIX + 'Received keyValues from endpoint', data.keyValues); + } + }).catch((_e) => { + // ignore errors for now + }); +} + +function getTargetingData(adUnitCodes, config, userConsent, auctionDetails) { + logInfo(LOG_PREFIX + 'submodule getTargetingData', adUnitCodes, config, userConsent); + const targetingData = {}; + + // Use the keyValues from the API response stored internally + if (!keyValues || Object.keys(keyValues).length === 0) { + logInfo(LOG_PREFIX + 'No keyValues available to set targeting data'); + return targetingData; + } + + adUnitCodes.forEach(code => { + targetingData[code] = keyValues; + }); + + return targetingData; +} + +function beforeInit() { + // take actions to get data as soon as possible + submodule('realTimeData', subModuleObj); +} + +beforeInit(); diff --git a/test/spec/modules/insuradsRtdProvider_spec.js b/test/spec/modules/insuradsRtdProvider_spec.js new file mode 100644 index 00000000000..bffd8d0c3b8 --- /dev/null +++ b/test/spec/modules/insuradsRtdProvider_spec.js @@ -0,0 +1,334 @@ +import { insurAdsRtdProvider } from 'modules/insurAdsRtdProvider.js'; +import * as ajax from 'src/ajax.js'; +import { config } from 'src/config.js'; + +const responseHeader = { 'Content-Type': 'application/json' }; + +const sampleConfig = { + name: 'insuradsRtd', + waitForIt: true, + params: { + publicId: 'ALIYJZJD' + } +}; + +const sampleKeyValues = { + 'iat-imp': ['1'], + 'iat-imp-app': ['1'], + 'iat-app': ['2954'] +}; + +describe('insurAdsRtdProvider', function () { + let fetchStub; + + beforeEach(function () { + config.resetConfig(); + fetchStub = sinon.stub(ajax, 'fetch'); + }); + + afterEach(function () { + fetchStub.restore(); + }); + + describe('insurAdsRtdProvider submodule', function () { + it('should have correct module name', function () { + expect(insurAdsRtdProvider.name).to.equal('insuradsRtd'); + }); + + it('should have correct GVLID', function () { + expect(insurAdsRtdProvider.gvlid).to.equal(596); + }); + }); + + describe('init', function () { + it('should return false when publicId is missing', function () { + const config = { + params: {} + }; + expect(insurAdsRtdProvider.init(config)).to.be.false; + }); + + it('should return false when params is missing', function () { + const config = {}; + expect(insurAdsRtdProvider.init(config)).to.be.false; + }); + + it('should return true when publicId is provided', function () { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + expect(insurAdsRtdProvider.init(sampleConfig)).to.be.true; + }); + + it('should make API call with correct URL', function () { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + insurAdsRtdProvider.init(sampleConfig); + + expect(fetchStub.called).to.be.true; + const fetchUrl = fetchStub.getCall(0).args[0]; + expect(fetchUrl).to.include(`https://services.insurads.com/core/init/prebid/${sampleConfig.params.publicId}`); + expect(fetchUrl).to.include('url='); + }); + + it('should make API call with correct options', function () { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + insurAdsRtdProvider.init(sampleConfig); + + const fetchOptions = fetchStub.getCall(0).args[1]; + expect(fetchOptions.keepalive).to.be.true; + expect(fetchOptions.credentials).to.equal('include'); + expect(fetchOptions.method).to.equal('GET'); + expect(fetchOptions.headers['Content-Type']).to.equal('application/json'); + }); + + it('should handle successful API response with keyValues', function (done) { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: sampleKeyValues }) + }); + + insurAdsRtdProvider.init(sampleConfig); + + // Give the promise time to resolve + setTimeout(() => { + const targetingData = insurAdsRtdProvider.getTargetingData(['ad-unit-1']); + expect(targetingData['ad-unit-1']).to.deep.equal(sampleKeyValues); + done(); + }, 50); + }); + + it('should handle API error gracefully', function () { + fetchStub.rejects(new Error('Network error')); + + expect(insurAdsRtdProvider.init(sampleConfig)).to.be.true; + }); + + it('should handle non-ok response gracefully', function () { + fetchStub.resolves({ + ok: false, + status: 404 + }); + + expect(insurAdsRtdProvider.init(sampleConfig)).to.be.true; + }); + + it('should handle 204 No Content response for invalid publicId', function (done) { + // First clear state with empty keyValues + fetchStub.reset(); + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + const clearConfig = { + params: { + publicId: 'clear-state-204' + } + }; + + insurAdsRtdProvider.init(clearConfig); + + setTimeout(() => { + // Now test with 204 response + fetchStub.reset(); + fetchStub.resolves({ + ok: false, + status: 204 + }); + + const invalidConfig = { + params: { + publicId: 'INVALID_ID' + } + }; + + const initResult = insurAdsRtdProvider.init(invalidConfig); + expect(initResult).to.be.true; + + // Wait and verify no keyValues are set + setTimeout(() => { + const targetingData = insurAdsRtdProvider.getTargetingData(['ad-unit-1']); + expect(targetingData).to.deep.equal({}); + done(); + }, 50); + }, 50); + }); + }); + + describe('getTargetingData', function () { + it('should return empty object when no keyValues are available', function (done) { + const config = { + params: { + publicId: 'reset' + } + }; + + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + insurAdsRtdProvider.init(config); + + // Wait for promise to resolve and reset state + setTimeout(() => { + const adUnitCodes = ['ad-unit-1', 'ad-unit-2']; + const result = insurAdsRtdProvider.getTargetingData(adUnitCodes, config); + + // When keyValues is empty, getTargetingData returns empty object (not populated with ad unit keys) + expect(result).to.be.an('object'); + expect(Object.keys(result)).to.have.lengthOf(0); + done(); + }, 50); + }); + + it('should return keyValues for all ad units', function (done) { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: sampleKeyValues }) + }); + + insurAdsRtdProvider.init(sampleConfig); + + setTimeout(() => { + const adUnitCodes = ['ad-unit-1', 'ad-unit-2', 'ad-unit-3']; + const result = insurAdsRtdProvider.getTargetingData(adUnitCodes, sampleConfig); + + expect(result).to.be.an('object'); + expect(result['ad-unit-1']).to.deep.equal(sampleKeyValues); + expect(result['ad-unit-2']).to.deep.equal(sampleKeyValues); + expect(result['ad-unit-3']).to.deep.equal(sampleKeyValues); + done(); + }, 50); + }); + + it('should handle empty ad unit codes array', function () { + const adUnitCodes = []; + const config = {}; + + const result = insurAdsRtdProvider.getTargetingData(adUnitCodes, config); + + expect(result).to.be.an('object'); + expect(Object.keys(result)).to.have.lengthOf(0); + }); + + it('should apply same keyValues to different ad units', function (done) { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: sampleKeyValues }) + }); + + insurAdsRtdProvider.init(sampleConfig); + + setTimeout(() => { + const adUnitCodes = ['div-1', 'div-2']; + const result = insurAdsRtdProvider.getTargetingData(adUnitCodes, sampleConfig); + + expect(result['div-1']).to.deep.equal(sampleKeyValues); + expect(result['div-2']).to.deep.equal(sampleKeyValues); + expect(result['div-1']).to.equal(result['div-2']); + done(); + }, 50); + }); + }); + + describe('integration scenarios', function () { + it('should handle complete flow: init -> API call -> getTargetingData', function (done) { + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: sampleKeyValues }) + }); + + // Step 1: Initialize + const initResult = insurAdsRtdProvider.init(sampleConfig); + expect(initResult).to.be.true; + + // Step 2: Wait for API call to complete + setTimeout(() => { + // Step 3: Get targeting data + const adUnitCodes = ['banner-ad', 'video-ad']; + const targetingData = insurAdsRtdProvider.getTargetingData(adUnitCodes, sampleConfig); + + expect(targetingData['banner-ad']).to.deep.equal(sampleKeyValues); + expect(targetingData['video-ad']).to.deep.equal(sampleKeyValues); + done(); + }, 50); + }); + + it('should handle API response without keyValues property', function (done) { + // First, clear state by setting empty keyValues + fetchStub.reset(); + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + const clearConfig = { + params: { + publicId: 'clear-state' + } + }; + + insurAdsRtdProvider.init(clearConfig); + + // Wait for state to clear + setTimeout(() => { + // Now test with no keyValues property + fetchStub.reset(); + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({}) + }); + + const config = { + params: { + publicId: 'test-no-keyvalues' + } + }; + + insurAdsRtdProvider.init(config); + + setTimeout(() => { + const targetingData = insurAdsRtdProvider.getTargetingData(['ad-unit-1']); + // When no keyValues in response, should return empty object + expect(targetingData).to.deep.equal({}); + done(); + }, 50); + }, 50); + }); + + it('should handle empty keyValues from API', function (done) { + const config = { + params: { + publicId: 'test-empty-keyvalues' + } + }; + + // Reset stub behavior for this test + fetchStub.reset(); + fetchStub.resolves({ + ok: true, + json: () => Promise.resolve({ keyValues: {} }) + }); + + insurAdsRtdProvider.init(config); + + setTimeout(() => { + const targetingData = insurAdsRtdProvider.getTargetingData(['ad-unit-1']); + // When keyValues is empty object, should return empty object + expect(targetingData).to.deep.equal({}); + done(); + }, 50); + }); + }); +}); From 8727523548faee3ae388f9ba11f0ab58ef597168 Mon Sep 17 00:00:00 2001 From: Jose Climaco Date: Wed, 4 Mar 2026 09:30:37 +0000 Subject: [PATCH 02/29] Update Example with Rtd config --- integrationExamples/gpt/insurads.html | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/integrationExamples/gpt/insurads.html b/integrationExamples/gpt/insurads.html index 92f6b7df8b2..18af67f4bcb 100644 --- a/integrationExamples/gpt/insurads.html +++ b/integrationExamples/gpt/insurads.html @@ -5,7 +5,7 @@