diff --git a/modules/fwsspBidAdapter.js b/modules/fwsspBidAdapter.js
new file mode 100644
index 00000000000..9d62a6f9b6a
--- /dev/null
+++ b/modules/fwsspBidAdapter.js
@@ -0,0 +1,708 @@
+import { logInfo, logError, logWarn, isArray, isFn, deepAccess, formatQS } from '../src/utils.js';
+import { BANNER, VIDEO } from '../src/mediaTypes.js';
+import { registerBidder } from '../src/adapters/bidderFactory.js';
+import { config } from '../src/config.js';
+import { getGlobal } from '../src/prebidGlobal.js';
+
+const BIDDER_CODE = 'fwssp';
+const GVL_ID = 285;
+const USER_SYNC_URL = 'https://ads.stickyadstv.com/auto-user-sync';
+
+export const spec = {
+ code: BIDDER_CODE,
+ gvlid: GVL_ID,
+ supportedMediaTypes: [BANNER, VIDEO],
+ aliases: [ 'freewheel-mrm'], // aliases for fwssp
+
+ /**
+ * Determines whether or not the given bid request is valid.
+ *
+ * @param {Object} bid The bid params to validate.
+ * @return boolean True if this is a valid bid, and false otherwise.
+ */
+ isBidRequestValid(bid) {
+ return !!(bid.params.serverUrl && bid.params.networkId && bid.params.profile && bid.params.siteSectionId && bid.params.videoAssetId);
+ },
+
+ /**
+ * Make a server request from the list of BidRequests.
+ *
+ * @param {Object[]} bidRequests - an array of bidRequests
+ * @param {Object[]} bidderRequest - an array of bidderRequests
+ * @return ServerRequest Info describing the request to the server.
+ */
+ buildRequests(bidRequests, bidderRequest) {
+ /**
+ * Builds a bid request object for FreeWheel Server-Side Prebid adapter
+ * @param {Object} currentBidRequest - The bid request object containing bid parameters
+ * @param {Object} bidderRequest - The bidder request object containing consent and other global parameters
+ * @returns {Object} Request object containing method, url, data and original bid request
+ * - method: HTTP method (GET)
+ * - url: Server URL for the bid request
+ * - data: Query parameters string
+ * - bidRequest: Original bid request object
+ * @private
+ */
+ const buildRequest = (currentBidRequest, bidderRequest) => {
+ const globalParams = constructGlobalParams(currentBidRequest);
+ const keyValues = constructKeyValues(currentBidRequest, bidderRequest);
+
+ const slotParams = constructSlotParams(currentBidRequest);
+ const dataString = constructDataString(globalParams, keyValues, slotParams);
+ return {
+ method: 'GET',
+ url: currentBidRequest.params.serverUrl,
+ data: dataString,
+ bidRequest: currentBidRequest
+ };
+ }
+
+ const constructGlobalParams = currentBidRequest => {
+ const sdkVersion = getSDKVersion(currentBidRequest);
+ const prebidVersion = getGlobal().version;
+ return {
+ nw: currentBidRequest.params.networkId,
+ resp: 'vast4',
+ prof: currentBidRequest.params.profile,
+ csid: currentBidRequest.params.siteSectionId,
+ caid: currentBidRequest.params.videoAssetId,
+ pvrn: getRandomNumber(),
+ vprn: getRandomNumber(),
+ flag: setFlagParameter(currentBidRequest.params.flags),
+ mode: currentBidRequest.params.mode ? currentBidRequest.params.mode : 'on-demand',
+ vclr: `js-${sdkVersion}-prebid-${prebidVersion}`
+ };
+ }
+
+ const getRandomNumber = () => {
+ return (new Date().getTime() * Math.random()).toFixed(0);
+ }
+
+ const setFlagParameter = optionalFlags => {
+ logInfo('setFlagParameter, optionalFlags: ', optionalFlags);
+ const requiredFlags = '+fwssp+emcr+nucr+aeti+rema+exvt+fwpbjs';
+ return optionalFlags ? optionalFlags + requiredFlags : requiredFlags;
+ }
+
+ const constructKeyValues = (currentBidRequest, bidderRequest) => {
+ const keyValues = currentBidRequest.params.adRequestKeyValues || {};
+
+ // Add bidfloor to keyValues
+ const bidfloor = getBidFloor(currentBidRequest, config);
+ keyValues._fw_bidfloor = (bidfloor > 0) ? bidfloor : 0;
+ keyValues._fw_bidfloorcur = (bidfloor > 0) ? getFloorCurrency(config) : '';
+
+ // Add GDPR flag and consent string
+ if (bidderRequest && bidderRequest.gdprConsent) {
+ keyValues._fw_gdpr_consent = bidderRequest.gdprConsent.consentString;
+ if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') {
+ keyValues._fw_gdpr = bidderRequest.gdprConsent.gdprApplies;
+ }
+ }
+
+ if (currentBidRequest.params.gdpr_consented_providers) {
+ keyValues._fw_gdpr_consented_providers = currentBidRequest.params.gdpr_consented_providers;
+ }
+
+ // Add CCPA consent string
+ if (bidderRequest && bidderRequest.uspConsent) {
+ keyValues._fw_us_privacy = bidderRequest.uspConsent;
+ }
+
+ // Add GPP consent
+ if (bidderRequest && bidderRequest.gppConsent) {
+ keyValues.gpp = bidderRequest.gppConsent.gppString;
+ keyValues.gpp_sid = bidderRequest.gppConsent.applicableSections;
+ } else if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.gpp) {
+ keyValues.gpp = bidderRequest.ortb2.regs.gpp;
+ keyValues.gpp_sid = bidderRequest.ortb2.regs.gpp_sid;
+ }
+
+ // Add content object
+ if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site && bidderRequest.ortb2.site.content && typeof bidderRequest.ortb2.site.content === 'object') {
+ try {
+ keyValues._fw_prebid_content = JSON.stringify(bidderRequest.ortb2.site.content);
+ } catch (error) {
+ logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the content object: ' + error);
+ }
+ }
+
+ // Add schain object
+ const schain = currentBidRequest.schain;
+ if (schain) {
+ try {
+ keyValues.schain = JSON.stringify(schain);
+ } catch (error) {
+ logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the schain: ' + error);
+ }
+ }
+
+ // Add 3rd party user ID
+ if (currentBidRequest.userIdAsEids && currentBidRequest.userIdAsEids.length > 0) {
+ try {
+ keyValues._fw_prebid_3p_UID = JSON.stringify(currentBidRequest.userIdAsEids);
+ } catch (error) {
+ logWarn('PREBID - ' + BIDDER_CODE + ': Unable to stringify the userIdAsEids: ' + error);
+ }
+ }
+
+ const location = bidderRequest?.refererInfo?.page;
+ if (isValidUrl(location)) {
+ keyValues.loc = location;
+ }
+
+ let playerSize = [];
+ if (currentBidRequest.mediaTypes.video && currentBidRequest.mediaTypes.video.playerSize) {
+ // If mediaTypes is video, get size from mediaTypes.video.playerSize per http://prebid.org/blog/pbjs-3
+ if (isArray(currentBidRequest.mediaTypes.video.playerSize[0])) {
+ playerSize = currentBidRequest.mediaTypes.video.playerSize[0];
+ } else {
+ playerSize = currentBidRequest.mediaTypes.video.playerSize;
+ }
+ } else if (currentBidRequest.mediaTypes.banner.sizes) {
+ // If mediaTypes is banner, get size from mediaTypes.banner.sizes per http://prebid.org/blog/pbjs-3
+ playerSize = getBiggerSizeWithLimit(currentBidRequest.mediaTypes.banner.sizes, currentBidRequest.mediaTypes.banner.minSizeLimit, currentBidRequest.mediaTypes.banner.maxSizeLimit);
+ } else {
+ // Backward compatible code, in case size still pass by sizes in bid request
+ playerSize = getBiggerSize(currentBidRequest.sizes);
+ }
+
+ // Add player size to keyValues
+ if (playerSize[0] > 0 || playerSize[1] > 0) {
+ keyValues._fw_player_width = keyValues._fw_player_width ? keyValues._fw_player_width : playerSize[0];
+ keyValues._fw_player_height = keyValues._fw_player_height ? keyValues._fw_player_height : playerSize[1];
+ }
+
+ // Add video context and placement in keyValues
+ if (currentBidRequest.mediaTypes.video) {
+ let videoContext = currentBidRequest.mediaTypes.video.context ? currentBidRequest.mediaTypes.video.context : '';
+ let videoPlacement = currentBidRequest.mediaTypes.video.placement ? currentBidRequest.mediaTypes.video.placement : null;
+ const videoPlcmt = currentBidRequest.mediaTypes.video.plcmt ? currentBidRequest.mediaTypes.video.plcmt : null;
+
+ if (currentBidRequest.params.format == 'inbanner') {
+ videoContext = 'In-Banner';
+ videoPlacement = 2;
+ }
+
+ keyValues._fw_video_context = videoContext;
+ keyValues._fw_placement_type = videoPlacement;
+ keyValues._fw_plcmt_type = videoPlcmt;
+ }
+ return keyValues;
+ }
+
+ const constructSlotParams = currentBidRequest => {
+ /**
+ * Parameters for ad slot configuration
+ * @property {number} tpos - Position type (default: 0)
+ * @property {string} ptgt - 'a': temporal slot
+ * 's': site section non-temporal slot
+ * 'p': video player non-temporal slot
+ * @property {string} slid - Slot ID
+ * @property {string} slau - Slot Ad Unit
+ * @property {number} mind - Minimum duration for the ad slot
+ * @property {number} maxd - Maximum duration for the ad slot
+ *
+ * Usually we do not suggest to set slid and slau from config,
+ * unless the ad targeting slot is not preroll
+ */
+ const slotParams = {
+ tpos: currentBidRequest.params.tpos ? currentBidRequest.params.tpos : 0,
+ ptgt: 'a', // Currently only support temporal slot
+ slid: currentBidRequest.params.slid ? currentBidRequest.params.slid : 'Preroll_1',
+ slau: currentBidRequest.params.slau ? currentBidRequest.params.slau : 'preroll',
+ }
+ if (currentBidRequest.params.minD) {
+ slotParams.mind = currentBidRequest.params.minD;
+ }
+ if (currentBidRequest.params.maxD) {
+ slotParams.maxd = currentBidRequest.params.maxD
+ }
+ return slotParams
+ }
+
+ const constructDataString = (globalParams, keyValues, slotParams) => {
+ // Helper function to append parameters to the data string and to not include the last '&' param before ';
+ const appendParams = (params) => {
+ const keys = Object.keys(params);
+ return keys.map((key, index) => {
+ const encodedKey = encodeURIComponent(key);
+ const encodedValue = encodeURIComponent(params[key]);
+ return `${encodedKey}=${encodedValue}${index < keys.length - 1 ? '&' : ''}`;
+ }).join('');
+ };
+
+ const globalParamsString = appendParams(globalParams) + ';';
+ const keyValuesString = appendParams(keyValues) + ';';
+ const slotParamsString = appendParams(slotParams) + ';';
+
+ return globalParamsString + keyValuesString + slotParamsString;
+ }
+
+ return bidRequests.map(function(currentBidRequest) {
+ return buildRequest(currentBidRequest, bidderRequest);
+ });
+ },
+
+ /**
+ * Unpack the response from the server into a list of bids.
+ *
+ * @param {*} serverResponse A successful response from the server.
+ * @param {object} request the built request object containing the initial bidRequest.
+ * @return {object[]} An array of bids which were nested inside the server.
+ */
+ interpretResponse: function(serverResponse, request) {
+ const bidrequest = request.bidRequest;
+ let playerSize = [];
+ if (bidrequest.mediaTypes.video && bidrequest.mediaTypes.video.playerSize) {
+ // If mediaTypes is video, get size from mediaTypes.video.playerSize per http://prebid.org/blog/pbjs-3
+ if (isArray(bidrequest.mediaTypes.video.playerSize[0])) {
+ playerSize = bidrequest.mediaTypes.video.playerSize[0];
+ } else {
+ playerSize = bidrequest.mediaTypes.video.playerSize;
+ }
+ } else if (bidrequest.mediaTypes.banner.sizes) {
+ // If mediaTypes is banner, get size from mediaTypes.banner.sizes per http://prebid.org/blog/pbjs-3
+ playerSize = getBiggerSizeWithLimit(bidrequest.mediaTypes.banner.sizes, bidrequest.mediaTypes.banner.minSizeLimit, bidrequest.mediaTypes.banner.maxSizeLimit);
+ } else {
+ // Backward compatible code, in case size still pass by sizes in bid request
+ playerSize = getBiggerSize(bidrequest.sizes);
+ }
+
+ if (typeof serverResponse == 'object' && typeof serverResponse.body == 'string') {
+ serverResponse = serverResponse.body;
+ }
+
+ let xmlDoc;
+ try {
+ const parser = new DOMParser();
+ xmlDoc = parser.parseFromString(serverResponse, 'application/xml');
+ } catch (err) {
+ logWarn('Prebid.js - ' + BIDDER_CODE + ' : ' + err);
+ return;
+ }
+
+ const bidResponses = [];
+
+ const princingData = getPricing(xmlDoc);
+ if (princingData.price) {
+ const bidResponse = {
+ requestId: bidrequest.bidId,
+ cpm: princingData.price,
+ width: playerSize[0],
+ height: playerSize[1],
+ creativeId: getCreativeId(xmlDoc),
+ currency: princingData.currency,
+ netRevenue: true,
+ ttl: 360,
+ meta: { advertiserDomains: getAdvertiserDomain(xmlDoc) },
+ dealId: getDealId(xmlDoc),
+ campaignId: getCampaignId(xmlDoc),
+ bannerId: getBannerId(xmlDoc)
+ };
+
+ if (bidrequest.mediaTypes.video) {
+ bidResponse.mediaType = 'video';
+ }
+
+ const topWin = getTopMostWindow();
+ if (!topWin.fwssp_cache) {
+ topWin.fwssp_cache = {};
+ }
+ topWin.fwssp_cache[bidrequest.adUnitCode] = {
+ response: serverResponse,
+ listeners: bidrequest.params.listeners
+ };
+
+ bidResponse.vastXml = serverResponse;
+ bidResponse.ad = formatAdHTML(bidrequest, playerSize, serverResponse);
+ bidResponses.push(bidResponse);
+ }
+
+ return bidResponses;
+ },
+
+ getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent, gppConsent) {
+ const params = {};
+
+ if (gdprConsent) {
+ if (typeof gdprConsent.gdprApplies === 'boolean') {
+ params.gdpr = Number(gdprConsent.gdprApplies);
+ params.gdpr_consent = gdprConsent.consentString;
+ } else {
+ params.gdpr_consent = gdprConsent.consentString;
+ }
+ }
+
+ if (uspConsent) {
+ params.us_privacy = uspConsent;
+ }
+
+ if (gppConsent) {
+ if (typeof gppConsent.gppString === 'string') {
+ params.gpp = gppConsent.gppString;
+ }
+ if (gppConsent.applicableSections) {
+ params.gpp_sid = gppConsent.applicableSections;
+ }
+ }
+
+ let queryString = '';
+ if (params) {
+ queryString = '?' + `${formatQS(params)}`;
+ }
+
+ const syncs = [];
+ if (syncOptions && syncOptions.pixelEnabled) {
+ syncs.push({
+ type: 'image',
+ url: USER_SYNC_URL + queryString
+ });
+ } else if (syncOptions.iframeEnabled) {
+ syncs.push({
+ type: 'iframe',
+ url: USER_SYNC_URL + queryString
+ });
+ }
+
+ return syncs;
+ }
+}
+
+/**
+ * Generates structured HTML for FreeWheel MRM ad integration with Prebid.js
+ * @param {Object} bidrequest - Prebid bid request
+ * @param {number[]} size - Prebid ad dimensions [width, height]
+ * @returns {string} Formatted HTML string for ad rendering
+ */
+export function formatAdHTML(bidrequest, size) {
+ const sdkUrl = getSdkUrl(bidrequest);
+ const displayBaseId = 'fwssp_display_base';
+
+ const startMuted = typeof bidrequest.params.isMuted == 'boolean' ? bidrequest.params.isMuted : true
+ const showMuteButton = typeof bidrequest.params.showMuteButton == 'boolean' ? bidrequest.params.showMuteButton : false
+
+ let playerParams = null;
+ try {
+ playerParams = JSON.stringify(bidrequest.params.playerParams);
+ } catch (error) {
+ logWarn('Error parsing playerParams:', error);
+ }
+
+ return `
+
+
`;
+}
+
+function getSdkUrl(bidrequest) {
+ const isStg = bidrequest.params.env && bidrequest.params.env.toLowerCase() == 'stg';
+ const host = isStg ? 'adm.stg.fwmrm.net' : 'mssl.fwmrm.net';
+ const sdkVersion = getSDKVersion(bidrequest);
+ return `https://${host}/libs/adm/${sdkVersion}/AdManager-prebid.js`
+}
+
+/**
+ * Determines the SDK version to use based on the bid request parameters.
+ * Returns the higher version between the provided version and default version.
+ * @param {Object} bidRequest - The bid request object containing parameters
+ * @returns {string} The SDK version to use, defaults to '7.10.0' if version parsing fails
+ */
+export function getSDKVersion(bidRequest) {
+ const DEFAULT = '7.10.0';
+
+ try {
+ const paramVersion = getSdkVersionFromBidRequest(bidRequest);
+ if (!paramVersion) {
+ return DEFAULT;
+ }
+ // Compare versions and return the higher one
+ return compareVersions(paramVersion, DEFAULT) > 0 ? paramVersion : DEFAULT;
+ } catch (error) {
+ logError('Version parsing failed, using default version:', error);
+ return DEFAULT;
+ }
+};
+
+/**
+ * Retrieves the sdkVersion from bidRequest.params and removes the leading v if present.
+ * @param {Object} bidRequest - The bid request object containing parameters
+ * @returns {string} The sdkVersion from bidRequest.params
+ */
+function getSdkVersionFromBidRequest(bidRequest) {
+ if (bidRequest.params.sdkVersion && bidRequest.params.sdkVersion.startsWith('v')) {
+ return bidRequest.params.sdkVersion.substring(1);
+ }
+ return bidRequest.params.sdkVersion;
+}
+
+/**
+ * Compares two version strings in semantic versioning format.
+ * Handles versions with trailing build metadata.
+ * @param {string} versionA - First version string to compare
+ * @param {string} versionB - Second version string to compare
+ * @returns {number} Returns 1 if versionA is greater, -1 if versionB is greater, 0 if equal
+ */
+function compareVersions(versionA, versionB) {
+ if (!versionA || !versionB) {
+ return 0;
+ }
+
+ const normalize = (v) => v.split('.').map(Number);
+
+ const partsA = normalize(versionA);
+ const partsB = normalize(versionB);
+
+ // compare parts
+ const maxLength = Math.max(partsA.length, partsB.length);
+ for (let i = 0; i < maxLength; i++) {
+ const a = partsA[i] || 0;
+ const b = partsB[i] || 0;
+ if (a > b) return 1;
+ if (a < b) return -1;
+ }
+
+ return 0;
+};
+
+function getBidFloor(bid, config) {
+ if (!isFn(bid.getFloor)) {
+ return deepAccess(bid, 'params.bidfloor', 0);
+ }
+
+ try {
+ const bidFloor = bid.getFloor({
+ currency: getFloorCurrency(config),
+ mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video',
+ size: '*',
+ });
+ return bidFloor.floor;
+ } catch (e) {
+ return -1;
+ }
+}
+
+function getFloorCurrency(config) {
+ return config.getConfig('floors.data.currency') != null ? config.getConfig('floors.data.currency') : 'USD';
+}
+
+function isValidUrl(str) {
+ let url = null;
+ try {
+ url = new URL(str);
+ } catch (_) {}
+ return url != null;
+}
+
+function getBiggerSize(array) {
+ let result = [0, 0];
+ for (let i = 0; i < array.length; i++) {
+ if (array[i][0] * array[i][1] > result[0] * result[1]) {
+ result = array[i];
+ }
+ }
+ return result;
+}
+
+function getBiggerSizeWithLimit(array, minSizeLimit, maxSizeLimit) {
+ const minSize = minSizeLimit || [0, 0];
+ const maxSize = maxSizeLimit || [Number.MAX_VALUE, Number.MAX_VALUE];
+ const candidates = [];
+
+ for (let i = 0; i < array.length; i++) {
+ if (array[i][0] * array[i][1] >= minSize[0] * minSize[1] && array[i][0] * array[i][1] <= maxSize[0] * maxSize[1]) {
+ candidates.push(array[i]);
+ }
+ }
+
+ return getBiggerSize(candidates);
+}
+
+/*
+* read the pricing extension with this format: 1.0000
+* @return {object} pricing data in format: {currency: 'EUR', price:'1.000'}
+*/
+function getPricing(xmlNode) {
+ let pricingExtNode;
+ let princingData = {};
+
+ const extensions = xmlNode.querySelectorAll('Extension');
+ extensions.forEach(node => {
+ if (node.getAttribute('type') === 'StickyPricing') {
+ pricingExtNode = node;
+ }
+ });
+
+ if (pricingExtNode) {
+ const priceNode = pricingExtNode.querySelector('Price');
+ princingData = {
+ currency: priceNode.getAttribute('currency'),
+ price: priceNode.textContent
+ };
+ } else {
+ logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.');
+ }
+
+ return princingData;
+}
+
+/*
+* Read the StickyBrand extension with following format:
+*
+*
+*
+*
+*
+*
+* @return {object} pricing data in format: {currency: 'EUR', price:'1.000'}
+*/
+function getAdvertiserDomain(xmlNode) {
+ const domain = [];
+ let brandExtNode;
+ const extensions = xmlNode.querySelectorAll('Extension');
+ extensions.forEach(node => {
+ if (node.getAttribute('type') === 'StickyBrand') {
+ brandExtNode = node;
+ }
+ });
+
+ // Currently we only return one Domain
+ if (brandExtNode) {
+ const domainNode = brandExtNode.querySelector('Domain');
+ domain.push(domainNode.textContent);
+ } else {
+ logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing StickyBrand extension.');
+ }
+
+ return domain;
+}
+
+function getCreativeId(xmlNode) {
+ let creaId = '';
+ const adNodes = xmlNode.querySelectorAll('Creative');
+ adNodes.forEach(el => {
+ creaId += '[' + el.getAttribute('id') + ']';
+ });
+
+ return creaId;
+}
+
+function getValueFromKeyInImpressionNode(xmlNode, key) {
+ let value = '';
+ const impNodes = xmlNode.querySelectorAll('Impression');
+ let isRootViewKeyPresent = false;
+ let isAdsDisplayStartedPresent = false;
+
+ impNodes.forEach(el => {
+ if (isRootViewKeyPresent && isAdsDisplayStartedPresent) {
+ return value;
+ }
+ isRootViewKeyPresent = false;
+ isAdsDisplayStartedPresent = false;
+ const text = el.textContent;
+ const queries = text.substring(el.textContent.indexOf('?') + 1).split('&');
+ let tempValue = '';
+ queries.forEach(item => {
+ const split = item.split('=');
+ if (split[0] == key) {
+ tempValue = split[1];
+ }
+ if (split[0] == 'reqType' && split[1] == 'AdsDisplayStarted') {
+ isAdsDisplayStartedPresent = true;
+ }
+ if (split[0] == 'rootViewKey') {
+ isRootViewKeyPresent = true;
+ }
+ });
+ if (isAdsDisplayStartedPresent) {
+ value = tempValue;
+ }
+ });
+
+ return value;
+}
+
+function getDealId(xmlNode) {
+ return getValueFromKeyInImpressionNode(xmlNode, 'dealId');
+}
+
+function getBannerId(xmlNode) {
+ return getValueFromKeyInImpressionNode(xmlNode, 'adId');
+}
+
+function getCampaignId(xmlNode) {
+ return getValueFromKeyInImpressionNode(xmlNode, 'campaignId');
+}
+
+/**
+ * returns the top most accessible window
+ */
+function getTopMostWindow() {
+ let res = window;
+
+ try {
+ while (top !== res) {
+ if (res.parent.location.href.length) {
+ res = res.parent;
+ }
+ }
+ } catch (e) {}
+
+ return res;
+}
+
+registerBidder(spec);
diff --git a/modules/fwsspBidAdapter.md b/modules/fwsspBidAdapter.md
new file mode 100644
index 00000000000..b9d76bb73de
--- /dev/null
+++ b/modules/fwsspBidAdapter.md
@@ -0,0 +1,38 @@
+# Overview
+
+Module Name: Freewheel MRM Bidder Adapter
+Module Type: Bidder Adapter
+Maintainer: vis@freewheel.com
+
+# Description
+
+Module that connects to Freewheel MRM's demand sources
+
+# Test Parameters
+```
+ var adUnits = [
+ {
+ bids: [
+ {
+ bidder: 'fwssp', // or use alias 'freewheel-mrm'
+ params: {
+ serverUrl: 'https://example.com/ad/g/1',
+ networkId: '42015',
+ profile: '42015:js_allinone_profile',
+ siteSectionId: 'js_allinone_demo_site_section',
+ videoAssetId: '0',
+ flags: '+play-uapl' // optional: users may include capability if needed
+ mode: 'live',
+ minD: 30,
+ maxD: 60,
+ adRequestKeyValues: { // optional: users may include adRequestKeyValues if needed
+ _fw_player_width: '1920',
+ _fw_player_height: '1080'
+ },
+ format: 'inbanner'
+ }
+ }
+ ]
+ }
+ ];
+```
diff --git a/test/spec/modules/fwsspBidAdapter_spec.js b/test/spec/modules/fwsspBidAdapter_spec.js
new file mode 100644
index 00000000000..dad76044e3c
--- /dev/null
+++ b/test/spec/modules/fwsspBidAdapter_spec.js
@@ -0,0 +1,913 @@
+const { expect } = require('chai');
+const { spec, getSDKVersion, formatAdHTML } = require('modules/fwsspBidAdapter');
+
+describe('fwsspBidAdapter', () => {
+ describe('isBidRequestValid', () => {
+ it('should return true when all required params are present', () => {
+ const bid = {
+ params: {
+ serverUrl: 'https://example.com/ad/g/1',
+ networkId: '42015',
+ profile: '42015:js_allinone_profile',
+ siteSectionId: 'js_allinone_demo_site_section',
+ videoAssetId: '0'
+ }
+ };
+ expect(spec.isBidRequestValid(bid)).to.be.true;
+ });
+
+ it('should return false when serverUrl is missing', () => {
+ const bid = {
+ params: {
+ networkId: '42015',
+ profile: '42015:js_allinone_profile',
+ siteSectionId: 'js_allinone_demo_site_section',
+ videoAssetId: '0'
+ }
+ };
+ expect(spec.isBidRequestValid(bid)).to.be.false;
+ });
+
+ it('should return false when networkId is missing', () => {
+ const bid = {
+ params: {
+ serverUrl: 'https://example.com/ad/g/1',
+ profile: '42015:js_allinone_profile',
+ siteSectionId: 'js_allinone_demo_site_section',
+ videoAssetId: '0'
+ }
+ };
+ expect(spec.isBidRequestValid(bid)).to.be.false;
+ });
+
+ it('should return false when profile is missing', () => {
+ const bid = {
+ params: {
+ serverUrl: 'https://example.com/ad/g/1',
+ networkId: '42015',
+ siteSectionId: 'js_allinone_demo_site_section',
+ videoAssetId: '0'
+ }
+ };
+ expect(spec.isBidRequestValid(bid)).to.be.false;
+ });
+
+ it('should return false when siteSectionId is missing', () => {
+ const bid = {
+ params: {
+ serverUrl: 'https://example.com/ad/g/1',
+ networkId: '42015',
+ profile: '42015:js_allinone_profile',
+ videoAssetId: '0'
+ }
+ };
+ expect(spec.isBidRequestValid(bid)).to.be.false;
+ });
+
+ it('should return false when videoAssetId is missing', () => {
+ const bid = {
+ params: {
+ serverUrl: 'https://example.com/ad/g/1',
+ networkId: '42015',
+ profile: '42015:js_allinone_profile',
+ siteSectionId: 'js_allinone_demo_site_section'
+ }
+ };
+ expect(spec.isBidRequestValid(bid)).to.be.false;
+ });
+ });
+
+ describe('buildRequestsForBanner', () => {
+ const getBidRequests = () => {
+ return [{
+ 'bidder': 'fwssp',
+ 'adUnitCode': 'adunit-code',
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [
+ [300, 250], [300, 600]
+ ]
+ }
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ 'schain': {
+ 'ver': '1.0',
+ 'complete': 1,
+ 'nodes': [{
+ 'asi': 'example.com',
+ 'sid': '0',
+ 'hp': 1,
+ 'rid': 'bidrequestid',
+ 'domain': 'example.com'
+ }]
+ },
+ 'params': {
+ 'bidfloor': 2.00,
+ 'serverUrl': 'https://example.com/ad/g/1',
+ 'networkId': '42015',
+ 'profile': '42015:js_allinone_profile',
+ 'siteSectionId': 'js_allinone_demo_site_section',
+ 'flags': '+play',
+ 'videoAssetId': '0',
+ 'timePosition': 120,
+ 'adRequestKeyValues': {
+ '_fw_player_width': '1920',
+ '_fw_player_height': '1080'
+ }
+ }
+ }]
+ };
+
+ const bidderRequest = {
+ gdprConsent: {
+ consentString: 'consentString',
+ gdprApplies: true
+ },
+ uspConsent: 'uspConsentString',
+ gppConsent: {
+ gppString: 'gppString',
+ applicableSections: [8]
+ },
+ refererInfo: {
+ page: 'www.test.com'
+ }
+ };
+
+ it('should build a valid server request', () => {
+ const requests = spec.buildRequests(getBidRequests(), bidderRequest);
+ expect(requests).to.be.an('array').that.is.not.empty;
+ const request = requests[0];
+ expect(request.method).to.equal('GET');
+ expect(request.url).to.equal('https://example.com/ad/g/1');
+
+ const actualDataString = request.data;
+ expect(actualDataString).to.include('nw=42015');
+ expect(actualDataString).to.include('resp=vast4');
+ expect(actualDataString).to.include('prof=42015%3Ajs_allinone_profile');
+ expect(actualDataString).to.include('csid=js_allinone_demo_site_section');
+ expect(actualDataString).to.include('caid=0');
+ expect(actualDataString).to.include('pvrn=');
+ expect(actualDataString).to.include('vprn=');
+ expect(actualDataString).to.include('flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs');
+ expect(actualDataString).to.include('mode=on-demand');
+ expect(actualDataString).to.include(`vclr=js-7.10.0-prebid-${pbjs.version};`);
+ expect(actualDataString).to.include('_fw_player_width=1920');
+ expect(actualDataString).to.include('_fw_player_height=1080');
+ expect(actualDataString).to.include('_fw_gdpr_consent=consentString');
+ expect(actualDataString).to.include('_fw_gdpr=true');
+ expect(actualDataString).to.include('_fw_us_privacy=uspConsentString');
+ expect(actualDataString).to.include('gpp=gppString');
+ expect(actualDataString).to.include('gpp_sid=8');
+ expect(actualDataString).to.include('tpos=0');
+ expect(actualDataString).to.include('ptgt=a');
+ expect(actualDataString).to.include('slid=Preroll_1');
+ expect(actualDataString).to.include('slau=preroll');
+ expect(actualDataString).to.not.include('mind');
+ expect(actualDataString).to.not.include('maxd;');
+ // schain check
+ const expectedEncodedSchainString = encodeURIComponent('{"ver":"1.0","complete":1,"nodes":[{"asi":"example.com","sid":"0","hp":1,"rid":"bidrequestid","domain":"example.com"}]}');
+ expect(actualDataString).to.include(expectedEncodedSchainString);
+ });
+
+ it('should construct the full adrequest URL correctly', () => {
+ const requests = spec.buildRequests(getBidRequests(), bidderRequest);
+ expect(requests).to.be.an('array').that.is.not.empty;
+ const request = requests[0];
+ const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=on-demand&vclr=js-7.10.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D;tpos=0&ptgt=a&slid=Preroll_1&slau=preroll;`;
+ const actualUrl = `${request.url}?${request.data}`;
+ // Remove pvrn and vprn from both URLs before comparing
+ const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, '');
+ expect(cleanUrl(actualUrl)).to.equal(cleanUrl(expectedUrl));
+ });
+
+ it('should return the correct width and height when _fw_player_width and _fw_player_height are not present in adRequestKeyValues', () => {
+ const bidRequests = [{
+ 'bidder': 'fwssp',
+ 'adUnitCode': 'adunit-code',
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [
+ [300, 600]
+ ]
+ }
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ 'params': {
+ 'bidfloor': 2.00,
+ 'serverUrl': 'https://example.com/ad/g/1',
+ 'networkId': '42015',
+ 'profile': '42015:js_allinone_profile',
+ 'siteSectionId': 'js_allinone_demo_site_section',
+ }
+ }];
+ const request = spec.buildRequests(bidRequests);
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_player_width=300');
+ expect(payload).to.include('_fw_player_height=600');
+ });
+
+ it('should get bidfloor value from params if no getFloor method', () => {
+ const request = spec.buildRequests(getBidRequests());
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_bidfloor=2');
+ expect(payload).to.include('_fw_bidfloorcur=USD');
+ });
+
+ it('should get bidfloor value from getFloor method if available', () => {
+ const bidRequests = getBidRequests();
+ bidRequests[0].getFloor = () => ({ currency: 'USD', floor: 1.16 });
+ const request = spec.buildRequests(bidRequests);
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_bidfloor=1.16');
+ expect(payload).to.include('_fw_bidfloorcur=USD');
+ });
+
+ it('should return empty bidFloorCurrency when bidfloor <= 0', () => {
+ const bidRequests = getBidRequests();
+ bidRequests[0].getFloor = () => ({ currency: 'USD', floor: -1 });
+ const request = spec.buildRequests(bidRequests);
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_bidfloor=0');
+ expect(payload).to.include('_fw_bidfloorcur=');
+ });
+
+ it('should return image type userSyncs with gdprConsent', () => {
+ const syncOptions = {
+ 'pixelEnabled': true
+ }
+ const userSyncs = spec.getUserSyncs(syncOptions, null, bidderRequest.gdprConsent, null, null);
+ expect(userSyncs).to.deep.equal([{
+ type: 'image',
+ url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=consentString'
+ }]);
+ });
+
+ it('should return iframe type userSyncs with gdprConsent, uspConsent, gppConsent', () => {
+ const syncOptions = {
+ 'iframeEnabled': true
+ }
+ const userSyncs = spec.getUserSyncs(syncOptions, null, bidderRequest.gdprConsent, bidderRequest.uspConsent, bidderRequest.gppConsent);
+ expect(userSyncs).to.deep.equal([{
+ type: 'iframe',
+ url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=consentString&us_privacy=uspConsentString&gpp=gppString&gpp_sid[]=8'
+ }]);
+ });
+ });
+
+ describe('buildRequestsForVideo', () => {
+ const getBidRequests = () => {
+ return [{
+ 'bidder': 'fwssp',
+ 'adUnitCode': 'adunit-code',
+ 'mediaTypes': {
+ 'video': {
+ 'playerSize': [300, 600],
+ }
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ 'schain': {
+ 'ver': '1.0',
+ 'complete': 1,
+ 'nodes': [{
+ 'asi': 'example.com',
+ 'sid': '0',
+ 'hp': 1,
+ 'rid': 'bidrequestid',
+ 'domain': 'example.com'
+ }]
+ },
+ 'params': {
+ 'bidfloor': 2.00,
+ 'serverUrl': 'https://example.com/ad/g/1',
+ 'networkId': '42015',
+ 'profile': '42015:js_allinone_profile',
+ 'siteSectionId': 'js_allinone_demo_site_section',
+ 'flags': '+play',
+ 'videoAssetId': '0',
+ 'mode': 'live',
+ 'timePosition': 120,
+ 'tpos': 300,
+ 'slid': 'Midroll',
+ 'slau': 'midroll',
+ 'minD': 30,
+ 'maxD': 60,
+ 'adRequestKeyValues': {
+ '_fw_player_width': '1920',
+ '_fw_player_height': '1080'
+ },
+ 'gdpr_consented_providers': 'test_providers'
+ }
+ }]
+ };
+
+ const bidderRequest = {
+ gdprConsent: {
+ consentString: 'consentString',
+ gdprApplies: true
+ },
+ uspConsent: 'uspConsentString',
+ gppConsent: {
+ gppString: 'gppString',
+ applicableSections: [8]
+ },
+ ortb2: {
+ regs: {
+ gpp: 'test_ortb2_gpp',
+ gpp_sid: 'test_ortb2_gpp_sid'
+ },
+ site: {
+ content: {
+ id: 'test_content_id',
+ title: 'test_content_title'
+ }
+ }
+ },
+ refererInfo: {
+ page: 'http://www.test.com'
+ }
+ };
+
+ it('should return context and placement with default values', () => {
+ const request = spec.buildRequests(getBidRequests());
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_video_context=&'); ;
+ expect(payload).to.include('_fw_placement_type=null&');
+ expect(payload).to.include('_fw_plcmt_type=null;');
+ });
+
+ it('should assign placement and context when format is inbanner', () => {
+ const bidRequest = getBidRequests()[0];
+ bidRequest.params.format = 'inbanner';
+ bidRequest.mediaTypes.video.plcmt = 'test-plcmt-type';
+ const request = spec.buildRequests([bidRequest]);
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_video_context=In-Banner&'); ;
+ expect(payload).to.include('_fw_placement_type=2&');
+ expect(payload).to.include('_fw_plcmt_type=test-plcmt-type;');
+ });
+
+ it('should build a valid server request', () => {
+ const requests = spec.buildRequests(getBidRequests(), bidderRequest);
+ expect(requests).to.be.an('array').that.is.not.empty;
+ const request = requests[0];
+ expect(request.method).to.equal('GET');
+ expect(request.url).to.equal('https://example.com/ad/g/1');
+
+ const actualDataString = request.data;
+
+ expect(actualDataString).to.include('nw=42015');
+ expect(actualDataString).to.include('resp=vast4');
+ expect(actualDataString).to.include('prof=42015%3Ajs_allinone_profile');
+ expect(actualDataString).to.include('csid=js_allinone_demo_site_section');
+ expect(actualDataString).to.include('caid=0');
+ expect(actualDataString).to.include('pvrn=');
+ expect(actualDataString).to.include('vprn=');
+ expect(actualDataString).to.include('flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs');
+ expect(actualDataString).to.include('mode=live');
+ expect(actualDataString).to.include(`vclr=js-7.10.0-prebid-${pbjs.version};`);
+ expect(actualDataString).to.include('_fw_player_width=1920');
+ expect(actualDataString).to.include('_fw_player_height=1080');
+ expect(actualDataString).to.include('_fw_gdpr_consent=consentString');
+ expect(actualDataString).to.include('_fw_gdpr=true');
+ expect(actualDataString).to.include('_fw_us_privacy=uspConsentString');
+ expect(actualDataString).to.include('gpp=gppString');
+ expect(actualDataString).to.include('gpp_sid=8');
+
+ expect(actualDataString).to.include('loc=http%3A%2F%2Fwww.test.com');
+ expect(actualDataString).to.include('tpos=300');
+ expect(actualDataString).to.include('ptgt=a');
+ expect(actualDataString).to.include('slid=Midroll');
+ expect(actualDataString).to.include('slau=midroll');
+ expect(actualDataString).to.include('mind=30');
+ expect(actualDataString).to.include('maxd=60;');
+ // schain check
+ const expectedEncodedSchainString = encodeURIComponent('{"ver":"1.0","complete":1,"nodes":[{"asi":"example.com","sid":"0","hp":1,"rid":"bidrequestid","domain":"example.com"}]}');
+ expect(actualDataString).to.include(expectedEncodedSchainString);
+ });
+
+ it('should construct the full adrequest URL correctly', () => {
+ const requests = spec.buildRequests(getBidRequests(), bidderRequest);
+ expect(requests).to.be.an('array').that.is.not.empty;
+ const request = requests[0];
+
+ const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.10.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consent=consentString&_fw_gdpr=true&_fw_gdpr_consented_providers=test_providers&_fw_us_privacy=uspConsentString&gpp=gppString&gpp_sid=8&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D&loc=http%3A%2F%2Fwww.test.com&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`;
+ const actualUrl = `${request.url}?${request.data}`;
+ // Remove pvrn and vprn from both URLs before comparing
+ const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, '');
+ expect(cleanUrl(actualUrl)).to.equal(cleanUrl(expectedUrl));
+ });
+
+ it('should use otrb2 gpp if gpp not in bidder request', () => {
+ const bidderRequest2 = {
+ ortb2: {
+ regs: {
+ gpp: 'test_ortb2_gpp',
+ gpp_sid: 'test_ortb2_gpp_sid'
+ },
+ site: {
+ content: {
+ id: 'test_content_id',
+ title: 'test_content_title'
+ }
+ }
+ }
+ };
+
+ const requests = spec.buildRequests(getBidRequests(), bidderRequest2);
+ expect(requests).to.be.an('array').that.is.not.empty;
+ const request = requests[0];
+ const expectedUrl = `https://example.com/ad/g/1?nw=42015&resp=vast4&prof=42015%3Ajs_allinone_profile&csid=js_allinone_demo_site_section&caid=0&flag=%2Bplay%2Bfwssp%2Bemcr%2Bnucr%2Baeti%2Brema%2Bexvt%2Bfwpbjs&mode=live&vclr=js-7.10.0-prebid-${pbjs.version};_fw_player_width=1920&_fw_player_height=1080&_fw_bidfloor=2&_fw_bidfloorcur=USD&_fw_gdpr_consented_providers=test_providers&gpp=test_ortb2_gpp&gpp_sid=test_ortb2_gpp_sid&_fw_prebid_content=%7B%22id%22%3A%22test_content_id%22%2C%22title%22%3A%22test_content_title%22%7D&schain=%7B%22ver%22%3A%221.0%22%2C%22complete%22%3A1%2C%22nodes%22%3A%5B%7B%22asi%22%3A%22example.com%22%2C%22sid%22%3A%220%22%2C%22hp%22%3A1%2C%22rid%22%3A%22bidrequestid%22%2C%22domain%22%3A%22example.com%22%7D%5D%7D&_fw_video_context=&_fw_placement_type=null&_fw_plcmt_type=null;tpos=300&ptgt=a&slid=Midroll&slau=midroll&mind=30&maxd=60;`;
+ const actualUrl = `${request.url}?${request.data}`;
+ // Remove pvrn and vprn from both URLs before comparing
+ const cleanUrl = (url) => url.replace(/&pvrn=[^&]*/g, '').replace(/&vprn=[^&]*/g, '');
+ expect(cleanUrl(actualUrl)).to.equal(cleanUrl(expectedUrl));
+ });
+
+ it('should get bidfloor value from params if no getFloor method', () => {
+ const request = spec.buildRequests(getBidRequests());
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_bidfloor=2');
+ expect(payload).to.include('_fw_bidfloorcur=USD');
+ });
+
+ it('should return image type userSyncs with gdprConsent', () => {
+ const syncOptions = {
+ 'pixelEnabled': true
+ }
+ const userSyncs = spec.getUserSyncs(syncOptions, null, bidderRequest.gdprConsent, null, null);
+ expect(userSyncs).to.deep.equal([{
+ type: 'image',
+ url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=consentString'
+ }]);
+ });
+
+ it('should return iframe type userSyncs with gdprConsent, uspConsent, gppConsent', () => {
+ const syncOptions = {
+ 'iframeEnabled': true
+ }
+ const userSyncs = spec.getUserSyncs(syncOptions, null, bidderRequest.gdprConsent, bidderRequest.uspConsent, bidderRequest.gppConsent);
+ expect(userSyncs).to.deep.equal([{
+ type: 'iframe',
+ url: 'https://ads.stickyadstv.com/auto-user-sync?gdpr=1&gdpr_consent=consentString&us_privacy=uspConsentString&gpp=gppString&gpp_sid[]=8'
+ }]);
+ });
+ });
+
+ describe('buildRequestsForVideoWithContextAndPlacement', () => {
+ it('should return input context and placement', () => {
+ const bidRequests = [{
+ 'bidder': 'fwssp',
+ 'adUnitCode': 'adunit-code',
+ 'mediaTypes': {
+ 'video': {
+ 'context': 'outstream',
+ 'placement': 2,
+ 'plcmt': 3,
+ 'playerSize': [300, 600],
+ }
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ 'params': {
+ 'bidfloor': 2.00,
+ 'serverUrl': 'https://example.com/ad/g/1',
+ 'networkId': '42015',
+ 'profile': '42015:js_allinone_profile',
+ 'siteSectionId': 'js_allinone_demo_site_section',
+ 'flags': '+play',
+ 'videoAssetId': '0',
+ 'mode': 'live',
+ 'vclr': 'js-7.10.0-prebid-',
+ 'timePosition': 120,
+ 'minD': 30,
+ 'maxD': 60,
+ }
+ }];
+ const request = spec.buildRequests(bidRequests);
+ const payload = request[0].data;
+ expect(payload).to.include('_fw_video_context=outstream'); ;
+ expect(payload).to.include('_fw_placement_type=2');
+ expect(payload).to.include('_fw_plcmt_type=3');
+ });
+ });
+
+ describe('getSDKVersion', () => {
+ it('should return the default sdk version when sdkVersion is missing', () => {
+ const bid = {
+ params: {
+ sdkVersion: ''
+ }
+ };
+ expect(getSDKVersion(bid)).to.equal('7.10.0');
+ });
+
+ it('should return the correct sdk version when sdkVersion is higher than the default', () => {
+ const bid = {
+ params: {
+ sdkVersion: '7.11.0'
+ }
+ };
+ expect(getSDKVersion(bid)).to.equal('7.11.0');
+ });
+
+ it('should return the default sdk version when sdkVersion is lower than the default', () => {
+ const bid = {
+ params: {
+ sdkVersion: '7.9.0'
+ }
+ };
+ expect(getSDKVersion(bid)).to.equal('7.10.0');
+ });
+
+ it('should return the default sdk version when sdkVersion is an invalid string', () => {
+ const bid = {
+ params: {
+ sdkVersion: 'abcdef'
+ }
+ };
+ expect(getSDKVersion(bid)).to.equal('7.10.0');
+ });
+
+ it('should return the correct sdk version when sdkVersion starts with v', () => {
+ const bid = {
+ params: {
+ sdkVersion: 'v7.11.0'
+ }
+ };
+ expect(getSDKVersion(bid)).to.equal('7.11.0');
+ });
+ });
+
+ describe('formatAdHTML', () => {
+ it('should return the ad markup in formatAdHTML, with default value of false for showMuteButton and true for isMuted', () => {
+ const expectedAdHtml =
+`
+
+
`;
+
+ const bidRequest = {
+ params: {},
+ adUnitCode: 'test'
+ }
+ const actualAdHtml = formatAdHTML(bidRequest, [640, 480], '');
+ expect(actualAdHtml).to.deep.equal(expectedAdHtml)
+ });
+
+ it('should take bid request showMuteButton, isMuted, and playerParams', () => {
+ const expectedAdHtml =
+`
+
+
`;
+
+ const bidRequest = {
+ params: {
+ showMuteButton: true,
+ isMuted: false,
+ playerParams: { 'test-param': 'test-value' }
+ },
+ adUnitCode: 'test'
+ }
+ const actualAdHtml = formatAdHTML(bidRequest, [640, 480], '');
+ expect(actualAdHtml).to.deep.equal(expectedAdHtml)
+ });
+
+ it('should generate html with the AdManager stg url when env param has value fo stg in bid request', () => {
+ const expectedAdHtml =
+`
+
+
`;
+
+ const bidRequest = {
+ params: {
+ env: 'stg'
+ },
+ adUnitCode: 'test'
+ }
+ const actualAdHtml = formatAdHTML(bidRequest, [640, 480], '');
+ expect(actualAdHtml).to.deep.equal(expectedAdHtml)
+ });
+
+ it('should use the correct version when sdkVersion is in bid params', () => {
+ const expectedAdHtml =
+`
+
+
`;
+
+ const bidRequest = {
+ params: {
+ env: 'stg',
+ sdkVersion: '7.11.0'
+ },
+ adUnitCode: 'test'
+ }
+ const actualAdHtml = formatAdHTML(bidRequest, [640, 480], '');
+ expect(actualAdHtml).to.deep.equal(expectedAdHtml)
+ });
+ });
+
+ describe('interpretResponseForBanner', () => {
+ const getBidRequests = () => {
+ return [{
+ 'bidder': 'fwssp',
+ 'params': {
+ 'serverUrl': 'https://fwmrm.com/ad/g/1',
+ 'sdkVersion': ''
+ },
+ 'adUnitCode': 'adunit-code',
+ 'mediaTypes': {
+ 'banner': {
+ 'sizes': [
+ [300, 600]
+ ]
+ }
+ },
+ 'sizes': [[300, 250], [300, 600]],
+ 'bidId': '30b31c1838de1e',
+ 'bidderRequestId': '22edbae2733bf6',
+ 'auctionId': '1d1a030790a475',
+ }]
+ };
+
+ const response = '' +
+ '' +
+ ' ' +
+ ' Adswizz' +
+ ' ' +
+ ' https://ads.stickyadstv.com/auto-user-sync?dealId=NRJ-PRO-12008' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 00:00:09' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' ' +
+ ' 0.2000' +
+ ' ' +
+ ' ' +
+ ' ' +
+ '';
+
+ it('should get correct bid response', () => {
+ const request = spec.buildRequests(getBidRequests());
+ const result = spec.interpretResponse(response, request[0]);
+
+ expect(result[0].meta.advertiserDomains).to.deep.equal([]);
+ expect(result[0].dealId).to.equal('NRJ-PRO-00008');
+ expect(result[0].campaignId).to.equal('SMF-WOW-55555');
+ expect(result[0].bannerId).to.equal('12345');
+ expect(result[0].requestId).to.equal('30b31c1838de1e');
+ expect(result[0].cpm).to.equal('0.2000');
+ expect(result[0].width).to.equal(300);
+ expect(result[0].height).to.equal(600);
+ expect(result[0].creativeId).to.equal('[28517153]');
+ expect(result[0].currency).to.equal('EUR');
+ expect(result[0].netRevenue).to.equal(true);
+ expect(result[0].ttl).to.equal(360);
+ });
+
+ it('handles nobid responses', () => {
+ const request = spec.buildRequests(getBidRequests());
+ const response = '';
+ const result = spec.interpretResponse(response, request[0]);
+ expect(result.length).to.equal(0);
+ });
+ });
+});