diff --git a/gulpfile.js b/gulpfile.js index 2566b52de59..7c4ca306886 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -32,6 +32,9 @@ var prebid = require('./package.json'); var dateString = 'Updated : ' + (new Date()).toISOString().substring(0, 10); var banner = '/* <%= prebid.name %> v<%= prebid.version %>\n' + dateString + ' */\n'; var port = 9999; +const mockServerPort = 4444; +const host = argv.host ? argv.host : 'localhost'; +const { spawn } = require('child_process'); // these modules must be explicitly listed in --modules to be included in the build, won't be part of "all" modules var explicitModules = [ @@ -234,12 +237,26 @@ function test(done) { wdioConf ]; } + + //run mock-server + const mockServer = spawn('node', ['./test/mock-server/index.js', '--port='+mockServerPort]); + mockServer.stdout.on('data', (data) => { + console.log(`stdout: ${data}`); + }); + mockServer.stderr.on('data', (data) => { + console.log(`stderr: ${data}`); + }); + execa(wdioCmd, wdioOpts, { stdio: 'inherit' }) .then(stdout => { + //kill mock server + mockServer.kill('SIGINT'); done(); process.exit(0); }) .catch(err => { + //kill mock server + mockServer.kill('SIGINT'); done(new Error(`Tests failed with error: ${err}`)); process.exit(1); }); @@ -309,6 +326,12 @@ function setupE2e(done) { done(); } +gulp.task('updatepath', function(){ + return gulp.src(['build/dist/*.js']) + .pipe(replace('ib.adnxs.com/ut/v3/prebid', host + ':' + mockServerPort + '/')) + .pipe(gulp.dest('build/dist')); +}); + // support tasks gulp.task(lint); gulp.task(watch); @@ -334,7 +357,7 @@ gulp.task('build-postbid', gulp.series(escapePostbidConfig, buildPostbid)); gulp.task('serve', gulp.series(clean, lint, gulp.parallel('build-bundle-dev', watch, test))); gulp.task('default', gulp.series(clean, makeWebpackPkg)); -gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), test)) +gulp.task('e2e-test', gulp.series(clean, setupE2e, gulp.parallel('build-bundle-prod', watch), 'updatepath', test)); // other tasks gulp.task(bundleToStdout); gulp.task('bundle', gulpBundle.bind(null, false)); // used for just concatenating pre-built files with no build step diff --git a/hpmodules.json b/hpmodules.json index 40e69ca2370..b3938509b0d 100644 --- a/hpmodules.json +++ b/hpmodules.json @@ -2,6 +2,8 @@ "33acrossBidAdapter", "aolBidAdapter", "appnexusBidAdapter", + "consentManagement", + "consentManagementUsp", "emx_digitalBidAdapter", "audienceNetworkBidAdapter", "ixBidAdapter", diff --git a/integrationExamples/gpt/hello_world.html b/integrationExamples/gpt/hello_world.html index 337c762adc5..d68e65011be 100644 --- a/integrationExamples/gpt/hello_world.html +++ b/integrationExamples/gpt/hello_world.html @@ -8,84 +8,84 @@ --> - - - - + + - - + + - - + + - - - -

Prebid.js Test

-
Div-1
-
- + + + +

Prebid.js Test

+
Div-1
+
+ -
- - + +
+ + diff --git a/integrationExamples/gpt/proxistore_example.html b/integrationExamples/gpt/proxistore_example.html new file mode 100644 index 00000000000..acd95baef2a --- /dev/null +++ b/integrationExamples/gpt/proxistore_example.html @@ -0,0 +1,118 @@ + + + + + + + + + + + + + +

Prebid.js Test

+ +
Div-1
+ +
+ +
+ + + \ No newline at end of file diff --git a/modules.json b/modules.json index b1191779170..2398177d3bb 100644 --- a/modules.json +++ b/modules.json @@ -4,6 +4,7 @@ "aolBidAdapter", "appnexusBidAdapter", "consentManagement", + "consentManagementUsp", "consumableBidAdapter", "dfpAdServerVideo", "emx_digitalBidAdapter", diff --git a/modules/.submodules.json b/modules/.submodules.json index 09063deea40..0841f5e9777 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -4,10 +4,15 @@ "id5IdSystem", "criteortusIdSystem", "parrableIdSystem", - "liveIntentIdSystem" + "britepoolIdSystem", + "liveIntentIdSystem", + "criteoIdSystem" ], "adpod": [ "freeWheelAdserverVideo", "dfpAdServerVideo" + ], + "rtdModule": [ + "browsiRtdProvider" ] } diff --git a/modules/1ad4goodBidAdapter.js b/modules/1ad4goodBidAdapter.js new file mode 100644 index 00000000000..26057b5e2b2 --- /dev/null +++ b/modules/1ad4goodBidAdapter.js @@ -0,0 +1,399 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import find from 'core-js/library/fn/array/find'; +import includes from 'core-js/library/fn/array/includes'; + +const BIDDER_CODE = '1ad4good'; +const URL = 'https://hb.1ad4good.org/prebid'; +const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', + 'startdelay', 'skippable', 'playback_method', 'frameworks']; +const USER_PARAMS = ['age', 'externalUid', 'segments', 'gender', 'dnt', 'language']; +const APP_DEVICE_PARAMS = ['geo', 'device_id']; // appid is collected separately +const SOURCE = 'pbjs'; +const MAX_IMPS_PER_REQUEST = 15; + +export const spec = { + code: BIDDER_CODE, + aliases: ['adsforgood', 'ads4good', '1adsforgood'], + supportedMediaTypes: [BANNER, VIDEO], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {object} bid The bid to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid: function(bid) { + return !!(bid.params.placementId); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(bidRequests, bidderRequest) { + const tags = bidRequests.map(bidToTag); + const userObjBid = find(bidRequests, hasUserInfo); + let userObj; + if (userObjBid) { + userObj = {}; + Object.keys(userObjBid.params.user) + .filter(param => includes(USER_PARAMS, param)) + .forEach(param => userObj[param] = userObjBid.params.user[param]); + } + + const appDeviceObjBid = find(bidRequests, hasAppDeviceInfo); + let appDeviceObj; + if (appDeviceObjBid && appDeviceObjBid.params && appDeviceObjBid.params.app) { + appDeviceObj = {}; + Object.keys(appDeviceObjBid.params.app) + .filter(param => includes(APP_DEVICE_PARAMS, param)) + .forEach(param => appDeviceObj[param] = appDeviceObjBid.params.app[param]); + } + + const appIdObjBid = find(bidRequests, hasAppId); + let appIdObj; + if (appIdObjBid && appIdObjBid.params && appDeviceObjBid.params.app && appDeviceObjBid.params.app.id) { + appIdObj = { + appid: appIdObjBid.params.app.id + }; + } + + const payload = { + tags: [...tags], + user: userObj, + sdk: { + source: SOURCE, + version: '$prebid.version$' + } + }; + + if (appDeviceObjBid) { + payload.device = appDeviceObj + } + if (appIdObjBid) { + payload.app = appIdObj; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + // note - objects for impbus use underscore instead of camelCase + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } + + if (bidderRequest && bidderRequest.refererInfo) { + let refererinfo = { + rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), + rd_top: bidderRequest.refererInfo.reachedTop, + rd_ifs: bidderRequest.refererInfo.numIframes, + rd_stk: bidderRequest.refererInfo.stack.map((url) => encodeURIComponent(url)).join(',') + } + payload.referrer_detection = refererinfo; + } + + const request = formatRequest(payload, bidderRequest); + return request; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {*} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + const bids = []; + if (!serverResponse || serverResponse.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse && serverResponse.error) { errorMessage += `: ${serverResponse.error}`; } + utils.logError(errorMessage); + return bids; + } + + if (serverResponse.tags) { + serverResponse.tags.forEach(serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid) { + if (rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + bids.push(bid); + } + } + }); + } + + return bids; + }, + + transformBidParams: function(params, isOpenRtb) { + params = utils.convertTypes({ + 'placementId': 'number', + 'keywords': utils.transformBidderParamKeywords + }, params); + + if (isOpenRtb) { + params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; + if (params.usePaymentRule) { delete params.usePaymentRule; } + + if (isPopulatedArray(params.keywords)) { + params.keywords.forEach(deleteValues); + } + + Object.keys(params).forEach(paramKey => { + let convertedKey = utils.convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + } + + return params; + }, + + /** + * Add element selector to javascript tracker to improve native viewability + * @param {Bid} bid + */ + onBidWon: function(bid) { + } +} + +function isPopulatedArray(arr) { + return !!(utils.isArray(arr) && arr.length > 0); +} + +function deleteValues(keyPairObj) { + if (isPopulatedArray(keyPairObj.value) && keyPairObj.value[0] === '') { + delete keyPairObj.value; + } +} + +function formatRequest(payload, bidderRequest) { + let request = []; + + if (payload.tags.length > MAX_IMPS_PER_REQUEST) { + const clonedPayload = utils.deepClone(payload); + + utils.chunk(payload.tags, MAX_IMPS_PER_REQUEST).forEach(tags => { + clonedPayload.tags = tags; + const payloadString = JSON.stringify(clonedPayload); + request.push({ + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }); + }); + } else { + const payloadString = JSON.stringify(payload); + request = { + method: 'POST', + url: URL, + data: payloadString, + bidderRequest + }; + } + + return request; +} + +/** + * Unpack the Server's Bid into a Prebid-compatible one. + * @param serverBid + * @param rtbBid + * @param bidderRequest + * @return Bid + */ +function newBid(serverBid, rtbBid, bidderRequest) { + const bidRequest = utils.getBidRequest(serverBid.uuid, [bidderRequest]); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + ads4good: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + if (rtbBid.advertiser_id) { + bid.meta = Object.assign({}, bid.meta, { advertiserId: rtbBid.advertiser_id }); + } + + if (rtbBid.rtb.video) { + Object.assign(bid, { + width: rtbBid.rtb.video.player_width, + height: rtbBid.rtb.video.player_height, + vastUrl: rtbBid.rtb.video.asset_url, + vastImpUrl: rtbBid.notify_url, + ttl: 3600 + }); + } else { + Object.assign(bid, { + width: rtbBid.rtb.banner.width, + height: rtbBid.rtb.banner.height, + ad: rtbBid.rtb.banner.content + }); + try { + const url = rtbBid.rtb.trackers[0].impression_urls[0]; + const tracker = utils.createTrackPixelHtml(url); + bid.ad += tracker; + } catch (error) { + utils.logError('Error appending tracking pixel', error); + } + } + + return bid; +} + +function bidToTag(bid) { + const tag = {}; + tag.sizes = transformSizes(bid.sizes); + tag.primary_size = tag.sizes[0]; + tag.ad_types = []; + tag.uuid = bid.bidId; + if (bid.params.placementId) { + tag.id = parseInt(bid.params.placementId, 10); + } + if (bid.params.cpm) { + tag.cpm = bid.params.cpm; + } + tag.allow_smaller_sizes = bid.params.allowSmallerSizes || false; + tag.use_pmt_rule = bid.params.usePaymentRule || false + tag.prebid = true; + tag.disable_psa = true; + if (bid.params.reserve) { + tag.reserve = bid.params.reserve; + } + if (bid.params.position) { + tag.position = {'above': 1, 'below': 2}[bid.params.position] || 0; + } + if (bid.params.trafficSourceCode) { + tag.traffic_source_code = bid.params.trafficSourceCode; + } + if (bid.params.privateSizes) { + tag.private_sizes = transformSizes(bid.params.privateSizes); + } + if (bid.params.supplyType) { + tag.supply_type = bid.params.supplyType; + } + if (bid.params.pubClick) { + tag.pubclick = bid.params.pubClick; + } + if (bid.params.extInvCode) { + tag.ext_inv_code = bid.params.extInvCode; + } + if (bid.params.externalImpId) { + tag.external_imp_id = bid.params.externalImpId; + } + if (!utils.isEmpty(bid.params.keywords)) { + let keywords = utils.transformBidderParamKeywords(bid.params.keywords); + + if (keywords.length > 0) { + keywords.forEach(deleteValues); + } + tag.keywords = keywords; + } + + const videoMediaType = utils.deepAccess(bid, `mediaTypes.${VIDEO}`); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + + if (bid.mediaType === VIDEO || videoMediaType) { + tag.ad_types.push(VIDEO); + } + + // instream gets vastUrl, outstream gets vastXml + if (bid.mediaType === VIDEO || (videoMediaType && context !== 'outstream')) { + tag.require_asset_url = true; + } + + if (bid.params.video) { + tag.video = {}; + // place any valid video params on the tag + Object.keys(bid.params.video) + .filter(param => includes(VIDEO_TARGETING, param)) + .forEach(param => tag.video[param] = bid.params.video[param]); + } + + if (bid.renderer) { + tag.video = Object.assign({}, tag.video, {custom_renderer_present: true}); + } + + if ( + (utils.isEmpty(bid.mediaType) && utils.isEmpty(bid.mediaTypes)) || + (bid.mediaType === BANNER || (bid.mediaTypes && bid.mediaTypes[BANNER])) + ) { + tag.ad_types.push(BANNER); + } + + return tag; +} + +/* Turn bid request sizes into ut-compatible format */ +function transformSizes(requestSizes) { + let sizes = []; + let sizeObj = {}; + + if (utils.isArray(requestSizes) && requestSizes.length === 2 && + !utils.isArray(requestSizes[0])) { + sizeObj.width = parseInt(requestSizes[0], 10); + sizeObj.height = parseInt(requestSizes[1], 10); + sizes.push(sizeObj); + } else if (typeof requestSizes === 'object') { + for (let i = 0; i < requestSizes.length; i++) { + let size = requestSizes[i]; + sizeObj = {}; + sizeObj.width = parseInt(size[0], 10); + sizeObj.height = parseInt(size[1], 10); + sizes.push(sizeObj); + } + } + + return sizes; +} + +function hasUserInfo(bid) { + return !!bid.params.user; +} + +function hasAppDeviceInfo(bid) { + if (bid.params) { + return !!bid.params.app + } +} + +function hasAppId(bid) { + if (bid.params && bid.params.app) { + return !!bid.params.app.id + } + return !!bid.params.app +} + +function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); +} + +function parseMediaType(rtbBid) { + const adType = rtbBid.ad_type; + if (adType === VIDEO) { + return VIDEO; + } else { + return BANNER; + } +} + +registerBidder(spec); diff --git a/modules/1ad4goodBidAdapter.md b/modules/1ad4goodBidAdapter.md new file mode 100644 index 00000000000..1e9dfe5e04c --- /dev/null +++ b/modules/1ad4goodBidAdapter.md @@ -0,0 +1,87 @@ +# Overview + +``` +Module Name: 1ad4good Bid Adapter +Module Type: Bidder Adapter +Maintainer: info@1ad4good.org +``` + +# Description + +Connects to 1ad4good exchange for bids. + +1ad4good bid adapter supports Banner and Video (instream). + +# Test Parameters +``` +var adUnits = [ + // Banner adUnit + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, + bids: [{ + bidder: '1ad4good', + params: { + placementId: 13144370 + } + }] + }, + // Video instream adUnit + { + code: 'video-instream', + sizes: [[640, 480]], + mediaTypes: { + video: { + playerSize: [[640, 480]], + context: 'instream' + }, + }, + bids: [{ + bidder: '1ad4good', + params: { + placementId: 13232361, + video: { + skippable: true, + playback_methods: ['auto_play_sound_off'] + } + } + }] + }, + + // Banner adUnit in a App Webview + // Only use this for situations where prebid.js is in a webview of an App + // See Prebid Mobile for displaying ads via an SDK + { + code: 'banner-div', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + } + bids: [{ + bidder: '1ad4good', + params: { + placementId: 13144370, + app: { + id: "B1O2W3M4AN.com.prebid.webview", + geo: { + lat: 40.0964439, + lng: -75.3009142 + }, + device_id: { + idfa: "4D12078D-3246-4DA4-AD5E-7610481E7AE", // Apple advertising identifier + aaid: "38400000-8cf0-11bd-b23e-10b96e40000d", // Android advertising identifier + md5udid: "5756ae9022b2ea1e47d84fead75220c8", // MD5 hash of the ANDROID_ID + sha1udid: "4DFAA92388699AC6539885AEF1719293879985BF", // SHA1 hash of the ANDROID_ID + windowsadid: "750c6be243f1c4b5c9912b95a5742fc5" // Windows advertising identifier + } + } + } + }] + } +]; +``` diff --git a/modules/7xbidBidAdapter.js b/modules/7xbidBidAdapter.js index ad0eeaaff1a..5464f87ee99 100644 --- a/modules/7xbidBidAdapter.js +++ b/modules/7xbidBidAdapter.js @@ -57,14 +57,12 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { refererInfo = bidderRequest.refererInfo; } - var g = (typeof (geparams) !== 'undefined' && typeof (geparams) == 'object' && geparams) ? geparams : {}; validBidRequests.forEach((bid, i) => { let endpoint = ENDPOINT_BANNER let data = { 'placementid': bid.params.placementId, 'cur': bid.params.hasOwnProperty('currency') ? bid.params.currency : DEFAULT_CURRENCY, 'ua': navigator.userAgent, - 'adtk': _encodeURIComponent(g.lat ? '0' : '1'), 'loc': utils.getTopWindowUrl(), 'topframe': (window.parent === window.self) ? 1 : 0, 'sw': screen && screen.width, diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index 1cdbec829d9..32b9f0d1b0c 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -4,17 +4,53 @@ import adapter from '../src/AnalyticsAdapter'; import adapterManager from '../src/adapterManager'; +import CONSTANTS from '../src/constants.json'; +import * as utils from '../src/utils'; -// This config makes Prebid.js call this function on each event: -// `window['AdagioPrebidAnalytics']('on', eventType, args)` -// If it is missing, then Prebid.js will immediately log an error, -// instead of queueing the events until the function appears. -var adagioAdapter = adapter({ - global: 'AdagioPrebidAnalytics', - handler: 'on', - analyticsType: 'bundle' +const emptyUrl = ''; +const analyticsType = 'endpoint'; +const events = Object.keys(CONSTANTS.EVENTS).map(key => CONSTANTS.EVENTS[key]); +const VERSION = '2.0.0'; + +const adagioEnqueue = function adagioEnqueue(action, data) { + utils.getWindowTop().ADAGIO.queue.push({ action, data, ts: Date.now() }); +} + +function canAccessTopWindow() { + try { + if (utils.getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; + } +} + +let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { + track: function({ eventType, args }) { + if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) { + adagioEnqueue('pb-analytics-event', { eventName: eventType, args }); + } + } }); +adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics; + +adagioAdapter.enableAnalytics = config => { + if (!canAccessTopWindow()) { + return; + } + + const w = utils.getWindowTop(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.versions = w.ADAGIO.versions || {}; + w.ADAGIO.versions.adagioAnalyticsAdapter = VERSION; + + adagioAdapter.originEnableAnalytics(config); +} + adapterManager.registerAnalyticsAdapter({ adapter: adagioAdapter, code: 'adagio' diff --git a/modules/adagioAnalyticsAdapter.md b/modules/adagioAnalyticsAdapter.md index 5734bc85b2a..312a26ea8da 100644 --- a/modules/adagioAnalyticsAdapter.md +++ b/modules/adagioAnalyticsAdapter.md @@ -1,7 +1,7 @@ # Overview Module Name: Adagio Analytics Adapter -Module Type: Adagio Adapter +Module Type: Analytics Adapter Maintainer: dev@adagio.io # Description diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index 8f6e59b0633..76614e52bc5 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,36 +1,232 @@ import find from 'core-js/library/fn/array/find'; import * as utils from '../src/utils'; -import { registerBidder } from '../src/adapters/bidderFactory'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import { loadExternalScript } from '../src/adloader' +import JSEncrypt from 'jsencrypt/bin/jsencrypt'; +import sha256 from 'crypto-js/sha256'; const BIDDER_CODE = 'adagio'; -const VERSION = '1.0.0'; +const VERSION = '2.0.0'; +const FEATURES_VERSION = '1'; const ENDPOINT = 'https://mp.4dex.io/prebid'; const SUPPORTED_MEDIA_TYPES = ['banner']; +const ADAGIO_TAG_URL = '//script.4dex.io/localstore.js'; +const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; -/** - * Based on https://github.com/ua-parser/uap-cpp/blob/master/UaParser.cpp#L331, with the following updates: - * - replaced `mobile` by `mobi` in the table regexp, so Opera Mobile on phones is not detected as a tablet. - */ -function _getDeviceType() { - let ua = navigator.userAgent; +export const ADAGIO_PUBKEY = `-----BEGIN PUBLIC KEY----- +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9el0+OEn6fvEh1RdVHQu4cnT0 +jFSzIbGJJyg3cKqvtE6A0iaz9PkIdJIvSSSNrmJv+lRGKPEyRA/VnzJIieL39Ngl +t0b0lsHN+W4n9kitS/DZ/xnxWK/9vxhv0ZtL1LL/rwR5Mup7rmJbNtDoNBw4TIGj +pV6EP3MTLosuUEpLaQIDAQAB +-----END PUBLIC KEY-----`; + +export function getAdagioScript() { + try { + const w = utils.getWindowTop(); + const ls = w.localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY); + + if (!ls) { + utils.logWarn('Adagio Script not found'); + return; + } + + const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; - // Tablets must be checked before phones. - if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { - return 5; // "tablet" + if (!hashRgx.test(ls)) { + utils.logWarn('No hash found in Adagio script'); + w.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + } else { + const r = ls.match(hashRgx); + const hash = r[2]; + const content = r[3]; + + var jsEncrypt = new JSEncrypt(); + jsEncrypt.setPublicKey(ADAGIO_PUBKEY); + + if (jsEncrypt.verify(content, hash, sha256)) { + utils.logInfo('Start Adagio script'); + Function(ls)(); // eslint-disable-line no-new-func + } else { + utils.logWarn('Invalid Adagio script found'); + w.localStorage.removeItem(ADAGIO_LOCALSTORAGE_KEY); + } + } + } catch (err) { + // } - if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { - return 4; // "phone" +} + +function canAccessTopWindow() { + try { + if (utils.getWindowTop().location.href) { + return true; + } + } catch (error) { + return false; } - // Consider that all other devices are personal computers - return 2; +} + +function initAdagio() { + const w = utils.getWindowTop(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + w.ADAGIO.versions = w.ADAGIO.versions || {}; + w.ADAGIO.versions.adagioBidderAdapter = VERSION; + + getAdagioScript(); + + loadExternalScript(ADAGIO_TAG_URL, BIDDER_CODE) +} + +if (canAccessTopWindow()) { + initAdagio(); +} + +const _features = { + getPrintNumber: function(adUnitCode) { + const adagioAdUnit = _getOrAddAdagioAdUnit(adUnitCode); + return adagioAdUnit.printNumber || 1; + }, + + getPageDimensions: function() { + const viewportDims = _features.getViewPortDimensions().split('x'); + const w = utils.getWindowTop(); + const body = w.document.body; + const html = w.document.documentElement; + const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); + + return viewportDims[0] + 'x' + pageHeight; + }, + + getViewPortDimensions: function() { + let viewPortWidth; + let viewPortHeight; + const w = utils.getWindowTop(); + const d = w.document; + + if (w.innerWidth) { + viewPortWidth = w.innerWidth; + viewPortHeight = w.innerHeight; + } else { + viewPortWidth = d.getElementsByTagName('body')[0].clientWidth; + viewPortHeight = d.getElementsByTagName('body')[0].clientHeight; + } + + return viewPortWidth + 'x' + viewPortHeight; + }, + + isDomLoading: function() { + const w = utils.getWindowTop(); + let performance = w.performance || w.msPerformance || w.webkitPerformance || w.mozPerformance; + let domLoading = -1; + + if (performance && performance.timing && performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) domLoading = val; + } + return domLoading; + }, + + getSlotPosition: function(element) { + const w = utils.getWindowTop(); + const d = w.document; + const el = element; + + let box = el.getBoundingClientRect(); + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = w.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = w.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = w.getComputedStyle(el, null); + const elComputedDisplay = elComputedStyle.display || 'block'; + const mustDisplayElement = elComputedDisplay === 'none'; + + if (mustDisplayElement) { + el.style = el.style || {}; + el.style.display = 'block'; + box = el.getBoundingClientRect(); + el.style.display = elComputedDisplay; + } + + const position = { + x: Math.round(box.left + scrollLeft - clientLeft), + y: Math.round(box.top + scrollTop - clientTop) + }; + + return position.x + 'x' + position.y; + }, + + getTimestamp: function() { + return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; + }, + + getDevice: function() { + if (!canAccessTopWindow()) return false; + const w = utils.getWindowTop(); + const ua = w.navigator.userAgent; + + if ((/(tablet|ipad|playbook|silk)|(android(?!.*mobi))/i).test(ua)) { + return 5; // "tablet" + } + if ((/Mobile|iP(hone|od|ad)|Android|BlackBerry|IEMobile|Kindle|NetFront|Silk-Accelerated|(hpw|web)OS|Fennec|Minimo|Opera M(obi|ini)|Blazer|Dolfin|Dolphin|Skyfire|Zune/).test(ua)) { + return 4; // "phone" + } + return 2; // personal computers + }, + + getBrowser: function() { + const w = utils.getWindowTop(); + const ua = w.navigator.userAgent; + const uaLowerCase = ua.toLowerCase(); + return /Edge\/\d./i.test(ua) ? 'edge' : uaLowerCase.indexOf('chrome') > 0 ? 'chrome' : uaLowerCase.indexOf('firefox') > 0 ? 'firefox' : uaLowerCase.indexOf('safari') > 0 ? 'safari' : uaLowerCase.indexOf('opera') > 0 ? 'opera' : uaLowerCase.indexOf('msie') > 0 || w.MSStream ? 'ie' : 'unknow'; + }, + + getOS: function() { + const w = window.top; + const ua = w.navigator.userAgent; + const uaLowerCase = ua.toLowerCase(); + return uaLowerCase.indexOf('linux') > 0 ? 'linux' : uaLowerCase.indexOf('mac') > 0 ? 'mac' : uaLowerCase.indexOf('win') > 0 ? 'windows' : ''; + } +} + +function _pushInAdagioQueue(ob) { + if (!canAccessTopWindow()) return; + const w = utils.getWindowTop(); + w.ADAGIO.queue.push(ob); }; +function _getOrAddAdagioAdUnit(adUnitCode) { + const w = utils.getWindowTop(); + if (w.ADAGIO.adUnits[adUnitCode]) { + return w.ADAGIO.adUnits[adUnitCode] + } + return w.ADAGIO.adUnits[adUnitCode] = {}; +} + +function _computePrintNumber(adUnitCode) { + let printNumber = 1; + const w = utils.getWindowTop(); + if ( + w.ADAGIO && + w.ADAGIO.adUnits && w.ADAGIO.adUnits[adUnitCode] && + w.ADAGIO.adUnits[adUnitCode].pageviewId === _getPageviewId() && + w.ADAGIO.adUnits[adUnitCode].printNumber + ) { + printNumber = parseInt(w.ADAGIO.adUnits[adUnitCode].printNumber, 10) + 1; + } + return printNumber; +} + function _getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { userAgent: navigator.userAgent, language: navigator[language], - deviceType: _getDeviceType(), + deviceType: _features.getDevice(), dnt: utils.getDNT() ? 1 : 0, geo: {}, js: 1 @@ -38,36 +234,91 @@ function _getDevice() { }; function _getSite() { - const topLocation = utils.getTopWindowLocation(); + const w = utils.getWindowTop(); return { - domain: topLocation.hostname, - page: topLocation.href, - referrer: utils.getTopWindowReferrer() + domain: w.location.hostname, + page: w.location.href, + referrer: w.document.referrer || '' }; }; function _getPageviewId() { - return (!window.top.ADAGIO || !window.top.ADAGIO.pageviewId) ? '_' : window.top.ADAGIO.pageviewId; + if (!canAccessTopWindow()) return false; + const w = utils.getWindowTop(); + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || utils.generateUUID(); + return w.ADAGIO.pageviewId; }; +function _getElementFromTopWindow(element, currentWindow) { + if (utils.getWindowTop() === currentWindow) { + if (!element.getAttribute('id')) { + element.setAttribute('id', `adg-${utils.getUniqueIdentifierStr()}`); + } + return element; + } else { + const frame = currentWindow.frameElement; + return _getElementFromTopWindow(frame, currentWindow.parent); + } +} + +/** + * Returns all features for a specific adUnit element + * + * @param {Object} bidRequest + * @returns {Object} features for an element (see specs) + */ function _getFeatures(bidRequest) { - if (!window.top._ADAGIO || !window.top._ADAGIO.features) { - return {}; + if (!canAccessTopWindow()) return; + const w = utils.getWindowTop(); + const adUnitElementId = bidRequest.params.adUnitElementId; + const adUnitCode = bidRequest.adUnitCode; + + let element = window.document.getElementById(adUnitElementId); + + if (bidRequest.params.postBid === true) { + element = _getElementFromTopWindow(element, window); + w.ADAGIO.pbjsAdUnits.map((adUnit) => { + if (adUnit.code === adUnitCode) { + const outerElementId = element.getAttribute('id'); + adUnit.outerAdUnitElementId = outerElementId; + bidRequest.params.outerAdUnitElementId = outerElementId; + } + }); + } else { + element = w.document.getElementById(adUnitElementId); } - const rawFeatures = window.top._ADAGIO.features.getFeatures( - document.getElementById(bidRequest.adUnitCode), - function(features) { - return { - site_id: bidRequest.params.siteId, - placement: bidRequest.params.placementId, - pagetype: bidRequest.params.pagetypeId, - categories: bidRequest.params.categories - }; - } - ); - return rawFeatures; -} + let features = {}; + if (element) { + features = Object.assign({}, { + print_number: _features.getPrintNumber(bidRequest.adUnitCode).toString(), + page_dimensions: _features.getPageDimensions().toString(), + viewport_dimensions: _features.getViewPortDimensions().toString(), + dom_loading: _features.isDomLoading().toString(), + // layout: features.getLayout().toString(), + adunit_position: _features.getSlotPosition(element).toString(), + user_timestamp: _features.getTimestamp().toString(), + device: _features.getDevice().toString(), + url: w.location.origin + w.location.pathname, + browser: _features.getBrowser(), + os: _features.getOS() + }) + } + + const adUnitFeature = {}; + adUnitFeature[adUnitElementId] = { + features: features, + version: FEATURES_VERSION + }; + + _pushInAdagioQueue({ + action: 'features', + ts: Date.now(), + data: adUnitFeature + }); + + return features; +}; function _getGdprConsent(bidderRequest) { const consent = {}; @@ -91,45 +342,77 @@ export const spec = { supportedMediaType: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function(bid) { - return !!(bid.params.siteId && bid.params.placementId); + const { adUnitCode, auctionId, sizes, bidder, params, mediaTypes } = bid; + const { organizationId, site, placement, adUnitElementId } = bid.params; + let isValid = false; + + if (canAccessTopWindow()) { + const w = utils.getWindowTop(); + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.adUnits = w.ADAGIO.adUnits || {}; + w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits || []; + isValid = !!(organizationId && site && placement && adUnitElementId && document.getElementById(adUnitElementId) !== null); + const tempAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== adUnitCode); + tempAdUnits.push({ + code: adUnitCode, + sizes: (mediaTypes && mediaTypes.banner && Array.isArray(mediaTypes.banner.sizes)) ? mediaTypes.banner.sizes : sizes, + bids: [{ + bidder, + params + }] + }); + w.ADAGIO.pbjsAdUnits = tempAdUnits; + + if (isValid === true) { + let printNumber = _computePrintNumber(adUnitCode); + w.ADAGIO.adUnits[adUnitCode] = { + auctionId: auctionId, + pageviewId: _getPageviewId(), + printNumber + }; + } + } + + return isValid; }, buildRequests: function(validBidRequests, bidderRequest) { + // AdagioBidAdapter works when window.top can be reached only + if (!bidderRequest.refererInfo.reachedTop) return []; + const secure = (location.protocol === 'https:') ? 1 : 0; const device = _getDevice(); const site = _getSite(); const pageviewId = _getPageviewId(); const gdprConsent = _getGdprConsent(bidderRequest); const adUnits = utils._map(validBidRequests, (bidRequest) => { - bidRequest.params.features = _getFeatures(bidRequest); - const categories = bidRequest.params.categories; - if (typeof categories !== 'undefined' && !Array.isArray(categories)) { - bidRequest.params.categories = [categories]; - } + bidRequest.features = _getFeatures(bidRequest); return bidRequest; }); // Regroug ad units by siteId const groupedAdUnits = adUnits.reduce((groupedAdUnits, adUnit) => { - (groupedAdUnits[adUnit.params.siteId] = groupedAdUnits[adUnit.params.siteId] || []).push(adUnit); + (groupedAdUnits[adUnit.params.organizationId] = groupedAdUnits[adUnit.params.organizationId] || []).push(adUnit); return groupedAdUnits; }, {}); // Build one request per siteId - const requests = utils._map(Object.keys(groupedAdUnits), (siteId) => { + const requests = utils._map(Object.keys(groupedAdUnits), (organizationId) => { return { method: 'POST', url: ENDPOINT, data: { id: utils.generateUUID(), + organizationId: organizationId, secure: secure, device: device, site: site, - siteId: siteId, pageviewId: pageviewId, - adUnits: groupedAdUnits[siteId], + adUnits: groupedAdUnits[organizationId], gdpr: gdprConsent, - adapterVersion: VERSION + prebidVersion: '$prebid.version$', + adapterVersion: VERSION, + featuresVersion: FEATURES_VERSION }, options: { contentType: 'application/json' @@ -145,15 +428,27 @@ export const spec = { try { const response = serverResponse.body; if (response) { - response.bids.forEach(bidObj => { - const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); - if (bidReq) { - bidObj.placementId = bidReq.params.placementId; - bidObj.pagetypeId = bidReq.params.pagetypeId; - bidObj.categories = (bidReq.params.features && bidReq.params.features.categories) ? bidReq.params.features.categories : []; - } - bidResponses.push(bidObj); - }); + if (response.data) { + _pushInAdagioQueue({ + action: 'ssp-data', + ts: Date.now(), + data: response.data + }); + } + if (response.bids) { + response.bids.forEach(bidObj => { + const bidReq = (find(bidRequest.data.adUnits, bid => bid.bidId === bidObj.requestId)); + if (bidReq) { + bidObj.site = bidReq.params.site; + bidObj.placement = bidReq.params.placement; + bidObj.pagetype = bidReq.params.pagetype; + bidObj.category = bidReq.params.category; + bidObj.subcategory = bidReq.params.subcategory; + bidObj.environment = bidReq.params.environment; + } + bidResponses.push(bidObj); + }); + } } } catch (err) { utils.logError(err); diff --git a/modules/adagioBidAdapter.md b/modules/adagioBidAdapter.md index ff33b035e5f..5dc8aa3d80c 100644 --- a/modules/adagioBidAdapter.md +++ b/modules/adagioBidAdapter.md @@ -12,21 +12,33 @@ Connects to Adagio demand source to fetch bids. ```javascript var adUnits = [ + { + code: 'dfp_banniere_atf', + sizes: [[300, 250], [300, 600]], + bids: [ { - code: 'ad-unit_code', - sizes: [[300, 250], [300, 600]], - bids: [ - { - bidder: 'adagio', // Required - params: { - siteId: '0', // Required - Site ID from Adagio. - placementId: '4', // Required - Placement ID from Adagio. Refers to the placement of an ad unit in a page. - pagetypeId: '343', // Required - Page type ID from Adagio. - categories: ['IAB12', 'IAB12-2'], // IAB categories of the page. - } - } - ] - } + bidder: 'adagio', // Required + params: { + organizationId: '0', // Required - Organization ID provided by Adagio. + site: 'news-of-the-day', // Required - Site Name provided by Adagio. + adUnitElementId: 'dfp_banniere_atf', // Required - AdUnit element id. Refers to the adunit id in a page. Usually equals to the adunit code above. + + // The following params are limited to 30 characters, + // and can only contain the following characters: + // - alphanumeric (A-Z+a-z+0-9, case-insensitive) + // - dashes `-` + // - underscores `_` + // Also, each param can have at most 50 unique active values (case-insensitive). + environment: 'mobile', // Required. Environment where the page is displayed. + placement: 'ban_atf', // Required. Refers to the placement of an adunit in a page. Must not contain any information about the type of device. Other example: `mpu_btf'. + pagetype: 'article', // Required. The pagetype describes what kind of content will be present in the page. + category: 'sport', // Recommended. Category of the content displayed in the page. + subcategory: 'handball', // Optional. Subcategory of the content displayed in the page. + postBid: false // Optional. Use it in case of Post-bid integration only. + } + } + ] + } ]; pbjs.addAdUnits(adUnits); @@ -35,22 +47,40 @@ Connects to Adagio demand source to fetch bids. adagio: { alwaysUseBid: true, adserverTargeting: [ + { + key: "site", + val: function (bidResponse) { + return bidResponse.site; + } + }, + { + key: "environment", + val: function (bidResponse) { + return bidResponse.environment; + } + }, { key: "placement", val: function (bidResponse) { - return bidResponse.placementId; + return bidResponse.placement; } }, { key: "pagetype", val: function (bidResponse) { - return bidResponse.pagetypeId; + return bidResponse.pagetype; + } + }, + { + key: "category", + val: function (bidResponse) { + return bidResponse.category; } }, { - key: "categories", + key: "subcategory", val: function (bidResponse) { - return bidResponse.categories.join(","); + return bidResponse.subcategory; } } ] diff --git a/modules/adformBidAdapter.js b/modules/adformBidAdapter.js index 0ac083e7e7c..d344908c638 100644 --- a/modules/adformBidAdapter.js +++ b/modules/adformBidAdapter.js @@ -98,7 +98,7 @@ export const spec = { response = responses[i]; type = response.response === 'banner' ? BANNER : VIDEO; bid = bids[i]; - if (VALID_RESPONSES[response.response] && (verifySize(response, bid.sizes) || type === VIDEO)) { + if (VALID_RESPONSES[response.response] && (verifySize(response, utils.getAdUnitSizes(bid)) || type === VIDEO)) { bidObject = { requestId: bid.bidId, cpm: response.win_bid, @@ -117,7 +117,7 @@ export const spec = { mediaType: type }; - if (!bid.renderer && utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { + if (!bid.renderer && type === VIDEO && utils.deepAccess(bid, 'mediaTypes.video.context') === 'outstream') { bidObject.renderer = Renderer.install({id: bid.bidId, url: OUTSTREAM_RENDERER_URL}); bidObject.renderer.setRender(renderer); } diff --git a/modules/adheseBidAdapter.js b/modules/adheseBidAdapter.js index 6ca8c8a6aa6..445c9956410 100644 --- a/modules/adheseBidAdapter.js +++ b/modules/adheseBidAdapter.js @@ -11,7 +11,7 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bid) { - return !!(bid.params.account && bid.params.location && bid.params.format); + return !!(bid.params.account && bid.params.location && (bid.params.format || bid.mediaTypes.banner.sizes)); }, buildRequests: function(validBidRequests, bidderRequest) { @@ -83,7 +83,10 @@ function adResponse(bid, ad) { width: Number(ad.width), height: Number(ad.height), creativeId: adDetails.creativeId, - dealId: adDetails.dealId + dealId: adDetails.dealId, + adhese: { + originData: adDetails.originData + } }); if (bidResponse.mediaType === VIDEO) { @@ -112,7 +115,19 @@ function mergeTargets(targets, target) { } function bidToSlotName(bid) { - return bid.params.location + '-' + bid.params.format; + if (bid.params.format) { + return bid.params.location + '-' + bid.params.format; + } + + var sizes = bid.mediaTypes.banner.sizes; + sizes.sort(); + var format = sizes.map(size => size[0] + 'x' + size[1]).join('_'); + + if (format.length > 0) { + return bid.params.location + '-' + format; + } else { + return bid.params.location; + } } function getAccount(validBidRequests) { @@ -150,22 +165,27 @@ function getPrice(ad) { function getAdDetails(ad) { let creativeId = ''; let dealId = ''; + let originData = {}; if (isAdheseAd(ad)) { creativeId = ad.id; dealId = ad.orderId; + originData = { priority: ad.priority, orderProperty: ad.orderProperty, adFormat: ad.adFormat, adType: ad.adType, libId: ad.libId, adspaceId: ad.adspaceId, viewableImpressionCounter: ad.viewableImpressionCounter }; } else { creativeId = ad.origin + (ad.originInstance ? '-' + ad.originInstance : ''); - if (ad.originData && ad.originData.seatbid && ad.originData.seatbid.length) { - const seatbid = ad.originData.seatbid[0]; - if (seatbid.bid && seatbid.bid.length) { - const bid = seatbid.bid[0]; - creativeId = String(bid.crid || ''); - dealId = String(bid.dealid || ''); + if (ad.originData) { + originData = ad.originData; + if (ad.originData.seatbid && ad.originData.seatbid.length) { + const seatbid = ad.originData.seatbid[0]; + if (seatbid.bid && seatbid.bid.length) { + const bid = seatbid.bid[0]; + creativeId = String(bid.crid || ''); + dealId = String(bid.dealid || ''); + } } } } - return { creativeId: creativeId, dealId: dealId }; + return { creativeId: creativeId, dealId: dealId, originData: originData }; } function base64urlEncode(s) { diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 08842db37e3..3131aa2a38c 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -139,7 +139,7 @@ export const spec = { let request = buildRequestParams(dispatch[host][pubId], auctionId, transactionId, gdprConsent, refererInfo); requests.push({ method: 'POST', - url: `//${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(refererInfo) ? '&debug=1' : ''}`, + url: `https://${host}/tag?account=${pubId}&pb=1${isRtbDebugEnabled(refererInfo) ? '&debug=1' : ''}`, data: JSON.stringify(request) }) }); diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index c406604b20e..29c23452814 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -23,7 +23,7 @@ const VERSION = '1.3'; export const spec = { code: 'adkernel', - aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak'], + aliases: ['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: function(bidRequest) { return 'params' in bidRequest && @@ -36,14 +36,14 @@ export const spec = { }, buildRequests: function(bidRequests, bidderRequest) { let impDispatch = dispatchImps(bidRequests, bidderRequest.refererInfo); - const {gdprConsent, auctionId} = bidderRequest; + const {gdprConsent, auctionId, refererInfo, timeout} = bidderRequest; const requests = []; Object.keys(impDispatch).forEach(host => { Object.keys(impDispatch[host]).forEach(zoneId => { - const request = buildRtbRequest(impDispatch[host][zoneId], auctionId, gdprConsent, bidderRequest.refererInfo); + const request = buildRtbRequest(impDispatch[host][zoneId], auctionId, gdprConsent, refererInfo, timeout); requests.push({ method: 'POST', - url: `${window.location.protocol}//${host}/hb?zone=${zoneId}&v=${VERSION}`, + url: `https://${host}/hb?zone=${zoneId}&v=${VERSION}`, data: JSON.stringify(request) }); }); @@ -107,8 +107,7 @@ function dispatchImps(bidRequests, refererInfo) { return bidRequests.map(bidRequest => buildImp(bidRequest, secure)) .reduce((acc, curr, index) => { let bidRequest = bidRequests[index]; - let zoneId = bidRequest.params.zoneId; - let host = bidRequest.params.host; + let {zoneId, host} = bidRequest.params; acc[host] = acc[host] || {}; acc[host][zoneId] = acc[host][zoneId] || []; acc[host][zoneId].push(curr); @@ -126,14 +125,14 @@ function buildImp(bidRequest, secure) { }; if (utils.deepAccess(bidRequest, `mediaTypes.banner`)) { - let sizes = canonicalizeSizesArray(bidRequest.mediaTypes.banner.sizes); + let sizes = utils.getAdUnitSizes(bidRequest); imp.banner = { format: sizes.map(wh => utils.parseGPTSingleSizeArrayToRtbSize(wh)), topframe: 0 }; } else if (utils.deepAccess(bidRequest, 'mediaTypes.video')) { - let size = canonicalizeSizesArray(bidRequest.mediaTypes.video.playerSize)[0]; - imp.video = utils.parseGPTSingleSizeArrayToRtbSize(size); + let sizes = bidRequest.mediaTypes.video.playerSize || []; + imp.video = utils.parseGPTSingleSizeArrayToRtbSize(sizes[0]) || {}; if (bidRequest.params.video) { Object.keys(bidRequest.params.video) .filter(key => includes(VIDEO_TARGETING, key)) @@ -146,27 +145,16 @@ function buildImp(bidRequest, secure) { return imp; } -/** - * Convert input array of sizes to canonical form Array[Array[Number]] - * @param sizes - * @return Array[Array[Number]] - */ -function canonicalizeSizesArray(sizes) { - if (sizes.length === 2 && !utils.isArray(sizes[0])) { - return [sizes]; - } - return sizes; -} - /** * Builds complete rtb request * @param imps collection of impressions * @param auctionId * @param gdprConsent * @param refInfo + * @param timeout * @return Object complete rtb request */ -function buildRtbRequest(imps, auctionId, gdprConsent, refInfo) { +function buildRtbRequest(imps, auctionId, gdprConsent, refInfo, timeout) { let req = { 'id': auctionId, 'imp': imps, @@ -178,6 +166,7 @@ function buildRtbRequest(imps, auctionId, gdprConsent, refInfo) { 'js': 1, 'language': getLanguage() }, + 'tmax': parseInt(timeout), 'ext': { 'adk_usersync': 1 } diff --git a/modules/adpod.js b/modules/adpod.js index 875809b8df5..8c352b67d04 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -1,6 +1,6 @@ /** * This module houses the functionality to evaluate and process adpod adunits/bids. Specifically there are several hooked functions, - * that either supplement the base function (ie to check something additional or unique to adpod objects) or to replace the base funtion + * that either supplement the base function (ie to check something additional or unique to adpod objects) or to replace the base function * entirely when appropriate. * * Brief outline of each hook: @@ -112,6 +112,19 @@ function createDispatcher(timeoutDuration) { }; } +function getPricePartForAdpodKey(bid) { + let pricePart + let prioritizeDeals = config.getConfig('adpod.prioritizeDeals'); + if (prioritizeDeals && utils.deepAccess(bid, 'video.dealTier')) { + const adpodDealPrefix = config.getConfig(`adpod.dealTier.${bid.bidderCode}.prefix`); + pricePart = (adpodDealPrefix) ? adpodDealPrefix + utils.deepAccess(bid, 'video.dealTier') : utils.deepAccess(bid, 'video.dealTier'); + } else { + const granularity = getPriceGranularity(bid.mediaType); + pricePart = getPriceByGranularity(granularity)(bid); + } + return pricePart +} + /** * This function reads certain fields from the bid to generate a specific key used for caching the bid in Prebid Cache * @param {Object} bid bid object to update @@ -120,16 +133,14 @@ function createDispatcher(timeoutDuration) { function attachPriceIndustryDurationKeyToBid(bid, brandCategoryExclusion) { let initialCacheKey = bidCacheRegistry.getInitialCacheKey(bid); let duration = utils.deepAccess(bid, 'video.durationBucket'); - const granularity = getPriceGranularity(bid.mediaType); - let cpmFixed = getPriceByGranularity(granularity)(bid); - + const pricePart = getPricePartForAdpodKey(bid); let pcd; if (brandCategoryExclusion) { let category = utils.deepAccess(bid, 'meta.adServerCatId'); - pcd = `${cpmFixed}_${category}_${duration}s`; + pcd = `${pricePart}_${category}_${duration}s`; } else { - pcd = `${cpmFixed}_${duration}s`; + pcd = `${pricePart}_${duration}s`; } if (!bid.adserverTargeting) { @@ -457,7 +468,31 @@ export function getTargeting({codes, callback} = {}) { let bids = getBidsForAdpod(bidsReceived, adPodAdUnits); bids = (competiveExclusionEnabled || deferCachingEnabled) ? getExclusiveBids(bids) : bids; - bids.sort(sortByPricePerSecond); + + let prioritizeDeals = config.getConfig('adpod.prioritizeDeals'); + if (prioritizeDeals) { + let [otherBids, highPriorityDealBids] = bids.reduce((partitions, bid) => { + let bidDealTier = utils.deepAccess(bid, 'video.dealTier'); + let minDealTier = config.getConfig(`adpod.dealTier.${bid.bidderCode}.minDealTier`); + if (minDealTier && bidDealTier) { + if (bidDealTier >= minDealTier) { + partitions[1].push(bid) + } else { + partitions[0].push(bid) + } + } else if (bidDealTier) { + partitions[1].push(bid) + } else { + partitions[0].push(bid); + } + return partitions; + }, [[], []]); + highPriorityDealBids.sort(sortByPricePerSecond); + otherBids.sort(sortByPricePerSecond); + bids = highPriorityDealBids.concat(otherBids); + } else { + bids.sort(sortByPricePerSecond); + } let targeting = {}; if (deferCachingEnabled === false) { diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 0aa6df3d03d..58792cf2675 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -5,6 +5,12 @@ import CONSTANTS from '../src/constants.json'; import * as url from '../src/url'; import * as utils from '../src/utils'; +/** + * Analytics adapter from adxcg.com + * maintainer info@adxcg.com + * updated 201911 for prebid 3.0 + */ + const emptyUrl = ''; const analyticsType = 'endpoint'; const adxcgAnalyticsVersion = 'v2.01'; @@ -107,11 +113,8 @@ function mapBidWon (bidResponse) { } function send (data) { - let location = utils.getTopWindowLocation(); - let secure = location.protocol === 'https:'; - let adxcgAnalyticsRequestUrl = url.format({ - protocol: secure ? 'https' : 'http', + protocol: 'https', hostname: adxcgAnalyticsAdapter.context.host, pathname: '/pbrx/v2', search: { @@ -145,14 +148,13 @@ adxcgAnalyticsAdapter.enableAnalytics = function (config) { return; } - let secure = location.protocol === 'https:'; adxcgAnalyticsAdapter.context = { events: { bidRequests: [], bidResponses: [] }, initOptions: config.options, - host: config.options.host || (secure ? 'hbarxs.adxcg.net' : 'hbarx.adxcg.net') + host: config.options.host || ('hbarxs.adxcg.net') }; adxcgAnalyticsAdapter.originEnableAnalytics(config); diff --git a/modules/adxcgAnalyticsAdapter.md b/modules/adxcgAnalyticsAdapter.md index e134ed23e3f..5b47ca856c5 100644 --- a/modules/adxcgAnalyticsAdapter.md +++ b/modules/adxcgAnalyticsAdapter.md @@ -16,7 +16,7 @@ https://www.adxcg.com/ { provider: 'adxcg', options : { - publisherId: ["42"] + publisherId: "42" } } diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index 34b5ea25fb0..5e460217e8a 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -12,6 +12,7 @@ import includes from 'core-js/library/fn/array/includes' * updated to pass aditional auction and impression level parameters. added pass for video targeting parameters * updated to fix native support for image width/height and icon 2019.03.17 * updated support for userid - pubcid,ttid 2019.05.28 + * updated to support prebid 3.0 - remove non https, move to banner.xx.sizes, remove utils.getTopWindowLocation,remove utils.getTopWindowUrl(),remove utils.getTopWindowReferrer() */ const BIDDER_CODE = 'adxcg' @@ -20,7 +21,7 @@ const SOURCE = 'pbjs10' const VIDEO_TARGETING = ['id', 'mimes', 'minduration', 'maxduration', 'startdelay', 'skippable', 'playback_method', 'frameworks'] const USER_PARAMS_AUCTION = ['forcedDspIds', 'forcedCampaignIds', 'forcedCreativeIds', 'gender', 'dnt', 'language'] const USER_PARAMS_BID = ['lineparam1', 'lineparam2', 'lineparam3'] -const BIDADAPTERVERSION = 'r20180703PB10' +const BIDADAPTERVERSION = 'r20191128PB30' export const spec = { code: BIDDER_CODE, @@ -71,8 +72,6 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { utils.logMessage(`buildRequests: ${JSON.stringify(validBidRequests)}`) - let location = utils.getTopWindowLocation() - let secure = location.protocol === 'https:' let dt = new Date() let ratio = window.devicePixelRatio || 1 let iobavailable = window && window.IntersectionObserver && window.IntersectionObserverEntry && window.IntersectionObserverEntry.prototype && 'intersectionRatio' in window.IntersectionObserverEntry.prototype @@ -82,16 +81,11 @@ export const spec = { bt = Math.min(window.PREBID_TIMEOUT, bt) } - let requestUrl = url.parse(location.href) - requestUrl.search = null - requestUrl.hash = null - // add common parameters let beaconParams = { renderformat: 'javascript', ver: BIDADAPTERVERSION, - url: encodeURIComponent(utils.getTopWindowUrl()), - secure: secure ? '1' : '0', + secure: '1', source: SOURCE, uw: window.screen.width, uh: window.screen.height, @@ -106,9 +100,10 @@ export const spec = { rndid: Math.floor(Math.random() * (999999 - 100000 + 1)) + 100000 } - if (utils.getTopWindowReferrer()) { - beaconParams.ref = encodeURIComponent(utils.getTopWindowReferrer()) - } + const referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); + const page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); + beaconParams.ref = encodeURIComponent(referrer); + beaconParams.url = encodeURIComponent(page); if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { beaconParams.gdpr = bidderRequest.gdprConsent.gdprApplies ? '1' : '0' @@ -131,7 +126,14 @@ export const spec = { validBidRequests.forEach((bid, index) => { adZoneIds.push(utils.getBidIdParameter('adzoneid', bid.params)) prebidBidIds.push(bid.bidId) - sizes.push(utils.parseSizesInput(bid.sizes).join('|')) + + if (isBannerRequest(bid)) { + sizes.push(utils.parseSizesInput(bid.mediaTypes.banner.sizes).join('|')) + } + + if (isNativeRequest(bid)) { + sizes.push('0x0') + } let bidfloor = utils.getBidIdParameter('bidfloor', bid.params) || 0 bidfloors.push(bidfloor) @@ -144,6 +146,7 @@ export const spec = { } // copy video context params beaconParams['video.context' + '.' + index] = utils.deepAccess(bid, 'mediaTypes.video.context') + sizes.push(utils.parseSizesInput(bid.mediaTypes.video.playerSize).join('|')) } // copy all custom parameters impression level parameters not supported above @@ -168,9 +171,17 @@ export const spec = { beaconParams.tdid = validBidRequests[0].userId.tdid; } + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.id5id'))) { + beaconParams.id5id = validBidRequests[0].userId.id5id; + } + + if (utils.isStr(utils.deepAccess(validBidRequests, '0.userId.idl_env'))) { + beaconParams.idl_env = validBidRequests[0].userId.idl_env; + } + let adxcgRequestUrl = url.format({ - protocol: secure ? 'https' : 'http', - hostname: secure ? 'hbps.adxcg.net' : 'hbp.adxcg.net', + protocol: 'https', + hostname: 'hbps.adxcg.net', pathname: '/get/adi', search: beaconParams }) @@ -272,7 +283,7 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: '//cdn.adxcg.net/pb-sync.html' + url: 'https://cdn.adxcg.net/pb-sync.html' }] } } @@ -282,4 +293,12 @@ function isVideoRequest (bid) { return bid.mediaType === 'video' || !!utils.deepAccess(bid, 'mediaTypes.video') } +function isBannerRequest (bid) { + return bid.mediaType === 'banner' || !!utils.deepAccess(bid, 'mediaTypes.banner') +} + +function isNativeRequest (bid) { + return bid.mediaType === 'native' || !!utils.deepAccess(bid, 'mediaTypes.native') +} + registerBidder(spec) diff --git a/modules/adxcgBidAdapter.md b/modules/adxcgBidAdapter.md index f3cd8c6d308..39f27adf163 100644 --- a/modules/adxcgBidAdapter.md +++ b/modules/adxcgBidAdapter.md @@ -10,41 +10,73 @@ Module that connects to an Adxcg.com zone to fetch bids. # Test Parameters ``` - `` - var adUnits = [{ - code: 'banner-ad-div', - sizes: [[300, 250]], - bids: [{ - bidder: 'adxcg', - params: { +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [{ + bidder: 'adxcg', + params: { adzoneid: '1' - } - }] - },{ - code: 'native-ad-div', - sizes: [[300, 250], [1, 1]], - nativeParams: { - title: { required: true, len: 75 }, - image: { required: true }, - body: { len: 200 }, - sponsoredBy: { len: 20 } - }, - bids: [{ - bidder: 'adxcg', - params: { - adzoneid: '2379' - } } - }] - },{ - code: 'video', - sizes: [[640, 480]], - bids: [{ - bidder: 'adxcg', + }] + }, { + code: 'native-ad-div', + mediaTypes: { + native: { + image: { + sendId: false, + required: true, + sizes: [80, 80] + }, + title: { + required: true, + len: 75 + }, + body: { + required: true, + len: 200 + }, + sponsoredBy: { + required: false, + len: 20 + } + } + }, + bids: [{ + bidder: 'adxcg', params: { - adzoneid: '20' + adzoneid: '2379' } } - }] - }]; + }] + }, + { + code: 'video-div', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + } + }, + bids: [{ + bidder: 'adxcg', + params: { + adzoneid: '20', + video: { + maxduration: 100, + mimes: ['video/mp4'], + skippable: true, + playback_method: ['auto_play_sound_off'] + } + } + }] + } + ]; ``` diff --git a/modules/aolBidAdapter.js b/modules/aolBidAdapter.js index c065828c10d..434593fbf58 100644 --- a/modules/aolBidAdapter.js +++ b/modules/aolBidAdapter.js @@ -106,7 +106,11 @@ export const spec = { return isMarketplaceBid(bid) || isMobileBid(bid); }, buildRequests(bids, bidderRequest) { - let consentData = bidderRequest ? bidderRequest.gdprConsent : null; + const consentData = {}; + if (bidderRequest) { + consentData.gdpr = bidderRequest.gdprConsent; + consentData.uspConsent = bidderRequest.uspConsent; + } return bids.map(bid => { const endpointCode = resolveEndpointCode(bid); @@ -230,7 +234,7 @@ export const spec = { } return (url.indexOf('//') === 0) ? `${DEFAULT_PROTO}:${url}` : `${DEFAULT_PROTO}://${url}`; }, - formatMarketplaceDynamicParams(params = {}, consentData) { + formatMarketplaceDynamicParams(params = {}, consentData = {}) { let queryParams = {}; if (params.bidFloor) { @@ -247,7 +251,7 @@ export const spec = { return paramsFormatted; }, - formatOneMobileDynamicParams(params = {}, consentData) { + formatOneMobileDynamicParams(params = {}, consentData = {}) { if (this.isSecureProtocol()) { params.secure = NUMERIC_VALUES.TRUE; } @@ -261,32 +265,27 @@ export const spec = { return paramsFormatted; }, - buildOpenRtbRequestData(bid, consentData) { + buildOpenRtbRequestData(bid, consentData = {}) { let openRtbObject = { id: bid.params.id, imp: bid.params.imp }; - if (this.isConsentRequired(consentData)) { - openRtbObject.regs = { - ext: { - gdpr: NUMERIC_VALUES.TRUE - } - }; - - if (consentData.consentString) { - openRtbObject.user = { - ext: { - consent: consentData.consentString - } - }; + if (this.isEUConsentRequired(consentData)) { + utils.deepSetValue(openRtbObject, 'regs.ext.gdpr', NUMERIC_VALUES.TRUE); + if (consentData.gdpr.consentString) { + utils.deepSetValue(openRtbObject, 'user.ext.consent', consentData.gdpr.consentString); } } + if (consentData.uspConsent) { + utils.deepSetValue(openRtbObject, 'regs.ext.us_privacy', consentData.uspConsent); + } + return openRtbObject; }, - isConsentRequired(consentData) { - return !!(consentData && consentData.gdprApplies); + isEUConsentRequired(consentData) { + return !!(consentData && consentData.gdpr && consentData.gdpr.gdprApplies); }, formatKeyValues(keyValues) { let keyValuesHash = {}; @@ -300,14 +299,18 @@ export const spec = { formatConsentData(consentData) { let params = {}; - if (this.isConsentRequired(consentData)) { + if (this.isEUConsentRequired(consentData)) { params.gdpr = NUMERIC_VALUES.TRUE; - if (consentData.consentString) { - params.euconsent = consentData.consentString; + if (consentData.gdpr.consentString) { + params.euconsent = consentData.gdpr.consentString; } } + if (consentData.uspConsent) { + params.us_privacy = consentData.uspConsent; + } + return params; }, parsePixelItems(pixels) { diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 95f5c162657..c387c9e4818 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -158,6 +158,10 @@ export const spec = { }; } + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent + } + if (bidderRequest && bidderRequest.refererInfo) { let refererinfo = { rd_ref: encodeURIComponent(bidderRequest.refererInfo.referer), @@ -498,9 +502,11 @@ function newBid(serverBid, rtbBid, bidderRequest) { case ADPOD: const iabSubCatId = getIabSubCategory(bidRequest.bidder, rtbBid.brand_category_id); bid.meta = Object.assign({}, bid.meta, { iabSubCatId }); + const dealTier = rtbBid.rtb.dealPriority; bid.video = { context: ADPOD, durationSeconds: Math.floor(rtbBid.rtb.video.duration_ms / 1000), + dealTier }; bid.vastUrl = rtbBid.rtb.video.asset_url; break; @@ -517,7 +523,7 @@ function newBid(serverBid, rtbBid, bidderRequest) { } break; case INSTREAM: - bid.vastUrl = rtbBid.rtb.video.asset_url; + bid.vastUrl = rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url); break; } } else if (rtbBid.rtb[NATIVE]) { diff --git a/modules/astraoneBidAdapter.js b/modules/astraoneBidAdapter.js new file mode 100644 index 00000000000..efff699b0dc --- /dev/null +++ b/modules/astraoneBidAdapter.js @@ -0,0 +1,140 @@ +import * as utils from '../src/utils' +import { registerBidder } from '../src/adapters/bidderFactory' +import { BANNER } from '../src/mediaTypes' + +const BIDDER_CODE = 'astraone'; +const SSP_ENDPOINT = 'https://ssp.astraone.io/auction/prebid'; +const TTL = 60; + +function buildBidRequests(validBidRequests) { + return utils._map(validBidRequests, function(validBidRequest) { + const params = validBidRequest.params; + const bidRequest = { + bidId: validBidRequest.bidId, + transactionId: validBidRequest.transactionId, + sizes: validBidRequest.sizes, + placement: params.placement, + placeId: params.placeId, + imageUrl: params.imageUrl + }; + + return bidRequest; + }) +} + +function buildBid(bidData) { + const bid = { + requestId: bidData.bidId, + cpm: bidData.price, + width: bidData.width, + height: bidData.height, + creativeId: bidData.content.seanceId, + currency: bidData.currency, + netRevenue: true, + mediaType: BANNER, + ttl: TTL, + content: bidData.content + }; + + bid.ad = wrapAd(bid, bidData); + + return bid; +} + +function getMediaTypeFromBid(bid) { + return bid.mediaTypes && Object.keys(bid.mediaTypes)[0] +} + +function wrapAd(bid, bidData) { + return ` + + + + + + + + +
+ + + `; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + + /** + * Determines whether or not the given bid request is valid. + * + * @param {BidRequest} bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ + isBidRequestValid(bid) { + return ( + getMediaTypeFromBid(bid) === BANNER && + !!bid.params.placeId && + !!bid.params.imageUrl && + !!bid.params.placement && + (bid.params.placement === 'inImage') + ); + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests(validBidRequests, bidderRequest) { + const payload = { + url: bidderRequest.refererInfo.referer, + cmp: !!bidderRequest.gdprConsent, + bidRequests: buildBidRequests(validBidRequests) + }; + + if (payload.cmp) { + const gdprApplies = bidderRequest.gdprConsent.gdprApplies; + if (gdprApplies !== undefined) payload['ga'] = gdprApplies; + payload['cs'] = bidderRequest.gdprConsent.consentString; + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: SSP_ENDPOINT, + data: payloadString, + options: { + contentType: 'application/json' + } + } + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function(serverResponse) { + const serverBody = serverResponse.body; + if (serverBody && utils.isArray(serverBody)) { + return utils._map(serverBody, function(bid) { + return buildBid(bid); + }); + } else { + return []; + } + } + +} +registerBidder(spec); diff --git a/modules/astraoneBidAdapter.md b/modules/astraoneBidAdapter.md new file mode 100644 index 00000000000..0c7b79489a5 --- /dev/null +++ b/modules/astraoneBidAdapter.md @@ -0,0 +1,198 @@ +# Overview + + +**Module Name**: AstraOne Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: prebid@astraone.io + +# Description + +You can use this adapter to get a bid from AstraOne. +Please reach out to your AstraOne account team before using this plugin to get placeId. +The code below returns a demo ad. + +About us: https://astraone.io + +# Test Parameters +```js +var adUnits = [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [], + } + }, + bids: [{ + bidder: "astraone", + params: { + placement: "inImage", + placeId: "5af45ad34d506ee7acad0c26", + imageUrl: "https://creative.astraone.io/files/default_image-1-600x400.jpg" + } + }] +}]; +``` + +# Example page + +```html + + + + + Prebid.js Banner Example + + + + + + +

Prebid.js InImage Banner Test

+ +
+ + +
+ + + +``` +# Example page with GPT + +```html + + + + + Prebid.js Banner Example + + + + + + +

Prebid.js Banner Ad Unit Test

+ +
+ + + +
+ + +``` diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js index 626aa99f5de..862933d076b 100644 --- a/modules/brightcomBidAdapter.js +++ b/modules/brightcomBidAdapter.js @@ -25,9 +25,10 @@ function buildRequests(bidReqs, bidderRequest) { const brightcomImps = []; const publisherId = utils.getBidIdParameter('publisherId', bidReqs[0].params); utils._each(bidReqs, function (bid) { - bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]); - bid.sizes = bid.sizes.filter(size => utils.isArray(size)); - const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]); + bidSizes = bidSizes.filter(size => utils.isArray(size)); + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); const element = document.getElementById(bid.adUnitCode); const minSize = _getMinSize(processedSizes); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md index badc6ea94a4..9f9aa0e5dd7 100644 --- a/modules/brightcomBidAdapter.md +++ b/modules/brightcomBidAdapter.md @@ -16,17 +16,25 @@ Brightcom's adapter integration to the Prebid library. var adUnits = [ { code: 'test-leaderboard', - sizes: [[728, 90]], + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, bids: [{ bidder: 'brightcom', params: { - publisherId: 2141020, - bidFloor: 0.01 + publisherId: 2141020, + bidFloor: 0.01 } }] }, { code: 'test-banner', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, bids: [{ bidder: 'brightcom', params: { diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js new file mode 100644 index 00000000000..2eb24968c08 --- /dev/null +++ b/modules/britepoolIdSystem.js @@ -0,0 +1,132 @@ +/** + * This module adds BritePoolId to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/britepoolIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils' +import {ajax} from '../src/ajax'; +import {submodule} from '../src/hook'; + +/** @type {Submodule} */ +export const britepoolIdSubmodule = { + /** + * Used to link submodule with config + * @type {string} + */ + name: 'britepoolId', + /** + * Decode the stored id value for passing to bid requests + * @function + * @param {string} value + * @returns {{britepoolid:string}} + */ + decode(value) { + return (value && typeof value['primaryBPID'] === 'string') ? { 'britepoolid': value['primaryBPID'] } : null; + }, + /** + * Performs action to obtain id and return a value in the callback's response argument + * @function + * @param {SubmoduleParams} [configParams] + * @returns {function(callback:function)} + */ + getId(submoduleConfigParams, consentData) { + const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams(submoduleConfigParams, consentData); + let getterResponse = null; + if (typeof getter === 'function') { + getterResponse = getter(params); + // First let's rule out that the response is not a function + if (typeof getterResponse !== 'function') { + // Optimization to return value from getter + return { + id: britepoolIdSubmodule.normalizeValue(getterResponse) + }; + } + } + // Return for async operation + return { + callback: function(callback) { + if (errors.length > 0) { + errors.forEach(error => utils.logError(error)); + callback(); + return; + } + if (getterResponse) { + // Resolve the getter function response + try { + getterResponse(function(response) { + callback(britepoolIdSubmodule.normalizeValue(response)); + }); + } catch (error) { + if (error !== '') utils.logError(error); + callback(); + } + } else { + ajax(url, { + success: response => { + const responseObj = britepoolIdSubmodule.normalizeValue(response); + callback(responseObj ? { primaryBPID: responseObj.primaryBPID } : null); + }, + error: error => { + if (error !== '') utils.logError(error); + callback(); + } + }, JSON.stringify(params), { customHeaders: headers, contentType: 'application/json', method: 'POST', withCredentials: true }); + } + } + } + }, + /** + * Helper method to create params for our API call + * @param {SubmoduleParams} [configParams] + * @returns {object} Object with parsed out params + */ + createParams(submoduleConfigParams, consentData) { + let errors = []; + const headers = {}; + let params = Object.assign({}, submoduleConfigParams); + if (params.getter) { + // Custom getter will not require other params + if (typeof params.getter !== 'function') { + errors.push(`${MODULE_NAME} - britepoolId submodule requires getter to be a function`); + return { errors }; + } + } else { + if (params.api_key) { + // Add x-api-key into the header + headers['x-api-key'] = params.api_key; + } + } + const url = params.url || 'https://api.britepool.com/v1/britepool/id'; + const getter = params.getter; + delete params.api_key; + delete params.url; + delete params.getter; + return { + params, + headers, + url, + getter, + errors + }; + }, + /** + * Helper method to normalize a JSON value + */ + normalizeValue(value) { + let valueObj = null; + if (typeof value === 'object') { + valueObj = value; + } else if (typeof value === 'string') { + try { + valueObj = JSON.parse(value); + } catch (error) { + utils.logError(error); + } + } + return valueObj; + } +}; + +submodule('userId', britepoolIdSubmodule); diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md new file mode 100644 index 00000000000..89287aed7ca --- /dev/null +++ b/modules/britepoolIdSystem.md @@ -0,0 +1,42 @@ +## BritePool User ID Submodule + +BritePool User ID Module. For assistance setting up your module please contact us at [prebid@britepool.com](prebid@britepool.com). + +### Prebid Params + +Individual params may be set for the BritePool User ID Submodule. At least one identifier must be set in the params. +``` +pbjs.setConfig({ + usersync: { + userIds: [{ + name: 'britepoolId', + storage: { + name: 'britepoolid', + type: 'cookie', + expires: 30 + }, + params: { + url: 'https://sandbox-api.britepool.com/v1/britepool/id', // optional + api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by britepool + hash: '31c5543c1734d25c7206f5fd591525d0295bec6fe84ff82f946a34fe970a1e66', // example hash identifier (sha256) + ssid: '221aa074-57fc-453b-81f0-6c74f628cd5c' // example identifier + } + }] + } +}); +``` +## Parameter Descriptions for the `usersync` Configuration Section +The below parameters apply only to the BritePool User ID Module integration. + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String | ID value for the BritePool module - `"britepoolId"` | `"britepoolId"` | +| params | Required | Object | Details for BritePool initialization. | | +| params.api_key | Required | String |BritePool API Key provided by BritePool | "3fdbe297-3690-4f5c-9e11-ee9186a6d77c" | +| params.url | Optional | String |BritePool API url | "https://sandbox-api.britepool.com/v1/britepool/id" | +| params.identifier | Required | String | Where identifier in the params object is the key name. At least one identifier is required. Available Identifiers `aaid` `dtid` `idfa` `ilid` `luid` `mmid` `msid` `mwid` `rida` `ssid` `hash` | `params.ssid` `params.aaid` | +| storage | Required | Object | The publisher must specify the local storage in which to store the results of the call to get the user ID. This can be either cookie or HTML5 storage. | | +| storage.type | Required | String | This is where the results of the user ID will be stored. The recommended method is `localStorage` by specifying `html5`. | `"html5"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"britepoolid"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. | `365` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the BritePool ID. The value is an object containing the values to be sent to the adapters. In this scenario, no URL is called and nothing is added to local storage | `{"primaryBPID": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js new file mode 100644 index 00000000000..795c9c86f1e --- /dev/null +++ b/modules/browsiRtdProvider.js @@ -0,0 +1,253 @@ +/** + * This module adds browsi provider to the eal time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch predictions from browsi server + * The module will place browsi bootstrap script on page + * @module modules/browsiProvider + * @requires module:modules/realTimeData + */ + +/** + * @typedef {Object} ModuleParams + * @property {string} siteKey + * @property {string} pubKey + * @property {string} url + * @property {?string} keyName + * @property {number} auctionDelay + */ + +import {config} from '../src/config.js'; +import * as utils from '../src/utils'; +import {submodule} from '../src/hook'; +import {ajax} from '../src/ajax'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +/** @type {ModuleParams} */ +let _moduleParams = {}; +/** @type {null|Object} */ +let _data = null; +/** @type {null | function} */ +let _dataReadyCallback = null; + +/** + * add browsi script to page + * @param {string} bptUrl + */ +export function addBrowsiTag(bptUrl) { + let script = document.createElement('script'); + script.async = true; + script.setAttribute('data-sitekey', _moduleParams.siteKey); + script.setAttribute('data-pubkey', _moduleParams.pubKey); + script.setAttribute('prebidbpt', 'true'); + script.setAttribute('id', 'browsi-tag'); + script.setAttribute('src', bptUrl); + document.head.appendChild(script); + return script; +} + +/** + * collect required data from page + * send data to browsi server to get predictions + */ +function collectData() { + const win = window.top; + const doc = win.document; + let browsiData = null; + try { + browsiData = utils.getDataFromLocalStorage('__brtd'); + } catch (e) { + utils.logError('unable to parse __brtd'); + } + + let predictorData = { + ...{ + sk: _moduleParams.siteKey, + sw: (win.screen && win.screen.width) || -1, + sh: (win.screen && win.screen.height) || -1, + url: encodeURIComponent(`${doc.location.protocol}//${doc.location.host}${doc.location.pathname}`), + }, + ...(browsiData ? {us: browsiData} : {us: '{}'}), + ...(document.referrer ? {r: document.referrer} : {}), + ...(document.title ? {at: document.title} : {}) + }; + getPredictionsFromServer(`//${_moduleParams.url}/prebid?${toUrlParams(predictorData)}`); +} + +export function setData(data) { + _data = data; + + if (typeof _dataReadyCallback === 'function') { + _dataReadyCallback(_data); + _dataReadyCallback = null; + } +} + +/** + * wait for data from server + * call callback when data is ready + * @param {function} callback + */ +function waitForData(callback) { + if (_data) { + _dataReadyCallback = null; + callback(_data); + } else { + _dataReadyCallback = callback; + } +} + +/** + * filter server data according to adUnits received + * call callback (onDone) when data is ready + * @param {adUnit[]} adUnits + * @param {function} onDone callback function + */ +function sendDataToModule(adUnits, onDone) { + try { + waitForData(_predictionsData => { + const _predictions = _predictionsData.p; + if (!_predictions || !Object.keys(_predictions).length) { + return onDone({}); + } + const slots = getAllSlots(); + if (!slots) { + return onDone({}); + } + let dataToReturn = adUnits.reduce((rp, cau) => { + const adUnitCode = cau && cau.code; + if (!adUnitCode) { return rp } + const predictionData = _predictions[adUnitCode]; + if (!predictionData) { return rp } + + if (predictionData.p) { + if (!isIdMatchingAdUnit(adUnitCode, slots, predictionData.w)) { + return rp; + } + rp[adUnitCode] = getKVObject(predictionData.p, _predictionsData.kn); + } + return rp; + }, {}); + return onDone(dataToReturn); + }); + } catch (e) { + onDone({}); + } +} + +/** + * get all slots on page + * @return {Object[]} slot GoogleTag slots + */ +function getAllSlots() { + return utils.isGptPubadsDefined && window.googletag.pubads().getSlots(); +} +/** + * get prediction and return valid object for key value set + * @param {number} p + * @param {string?} keyName + * @return {Object} key:value + */ +function getKVObject(p, keyName) { + const prValue = p < 0 ? 'NA' : (Math.floor(p * 10) / 10).toFixed(2); + let prObject = {}; + prObject[((_moduleParams['keyName'] || keyName).toString())] = prValue.toString(); + return prObject; +} +/** + * check if placement id matches one of given ad units + * @param {number} id placement id + * @param {Object[]} allSlots google slots on page + * @param {string[]} whitelist ad units + * @return {boolean} + */ +export function isIdMatchingAdUnit(id, allSlots, whitelist) { + if (!whitelist || !whitelist.length) { + return true; + } + const slot = allSlots.filter(s => s.getSlotElementId() === id); + const slotAdUnits = slot.map(s => s.getAdUnitPath()); + return slotAdUnits.some(a => whitelist.indexOf(a) !== -1); +} + +/** + * XMLHttpRequest to get data form browsi server + * @param {string} url server url with query params + */ +function getPredictionsFromServer(url) { + ajax(url, + { + success: function (response, req) { + if (req.status === 200) { + try { + const data = JSON.parse(response); + if (data && data.p && data.kn) { + setData({p: data.p, kn: data.kn}); + } else { + setData({}); + } + addBrowsiTag(data.u); + } catch (err) { + utils.logError('unable to parse data'); + setData({}) + } + } else if (req.status === 204) { + // unrecognized site key + setData({}); + } + }, + error: function () { + setData({}); + utils.logError('unable to get prediction data'); + } + } + ); +} + +/** + * serialize object and return query params string + * @param {Object} data + * @return {string} + */ +function toUrlParams(data) { + return Object.keys(data) + .map(key => key + '=' + encodeURIComponent(data[key])) + .join('&'); +} + +/** @type {RtdSubmodule} */ +export const browsiSubmodule = { + /** + * used to link submodule with realTimeData + * @type {string} + */ + name: 'browsi', + /** + * get data and send back to realTimeData module + * @function + * @param {adUnit[]} adUnits + * @param {function} onDone + */ + getData: sendDataToModule +}; + +export function init(config) { + const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { + try { + _moduleParams = realTimeData.dataProviders && realTimeData.dataProviders.filter( + pr => pr.name && pr.name.toLowerCase() === 'browsi')[0].params; + _moduleParams.auctionDelay = realTimeData.auctionDelay; + } catch (e) { + _moduleParams = {}; + } + if (_moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { + confListener(); + collectData(); + } else { + utils.logError('missing params for Browsi provider'); + } + }); +} + +submodule('realTimeData', browsiSubmodule); +init(config); diff --git a/modules/cedatoBidAdapter.js b/modules/cedatoBidAdapter.js index d81ae858869..a9e7814842c 100644 --- a/modules/cedatoBidAdapter.js +++ b/modules/cedatoBidAdapter.js @@ -35,6 +35,9 @@ export const spec = { const user = { id: getUserID() } const currency = CURRENCY; const tmax = bidderRequest.timeout; + const auctionId = bidderRequest.auctionId; + const auctionStart = bidderRequest.auctionStart; + const bidderRequestId = bidderRequest.bidderRequestId; const imp = bidRequests.map(req => { const banner = getMediaType(req, 'banner'); @@ -42,6 +45,8 @@ export const spec = { const bidfloor = params.bidfloor; const bidId = req.bidId; const adUnitCode = req.adUnitCode; + const bidRequestsCount = req.bidRequestsCount; + const transactionId = req.transactionId; return { bidId, @@ -49,6 +54,8 @@ export const spec = { video, adUnitCode, bidfloor, + bidRequestsCount, + transactionId }; }); @@ -61,13 +68,19 @@ export const spec = { imp, currency, tmax, + auctionId, + auctionStart, + bidderRequestId }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; + if (bidderRequest) { + payload.referer_info = bidderRequest.refererInfo; + if (bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + } } return { diff --git a/modules/coinzillaBidAdapter.js b/modules/coinzillaBidAdapter.js index 6918d47eb10..26ea7bb71e1 100644 --- a/modules/coinzillaBidAdapter.js +++ b/modules/coinzillaBidAdapter.js @@ -31,7 +31,7 @@ export const spec = { return []; } return validBidRequests.map(bidRequest => { - const sizes = utils.parseSizesInput(bidRequest.sizes)[0]; + const sizes = utils.parseSizesInput(bidRequest.params.size || bidRequest.sizes)[0]; const width = sizes.split('x')[0]; const height = sizes.split('x')[1]; const payload = { diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 2ad320ede38..adcd5df9fb6 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -3,8 +3,8 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; import * as utils from '../src/utils'; const BIDDER_CODE = 'colossusssp'; -const URL = '//colossusssp.com/?c=o&m=multi'; -const URL_SYNC = '//colossusssp.com/?c=o&m=cookie'; +const G_URL = 'https://colossusssp.com/?c=o&m=multi'; +const G_URL_SYNC = 'https://colossusssp.com/?c=o&m=cookie'; function isBidResponseValid(bid) { if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { @@ -42,15 +42,16 @@ export const spec = { * @param {BidRequest[]} validBidRequests A non-empty list of valid bid requests that should be sent to the Server. * @return ServerRequest Info describing the request to the server. */ - buildRequests: (validBidRequests) => { + buildRequests: (validBidRequests, bidderRequest) => { let winTop = window; + let location; try { - window.top.location.toString(); + location = new URL(bidderRequest.refererInfo.referer) winTop = window.top; } catch (e) { + location = winTop.location; utils.logMessage(e); }; - let location = utils.getTopWindowLocation(); let placements = []; let request = { 'deviceWidth': winTop.screen.width, @@ -61,19 +62,30 @@ export const spec = { 'page': location.pathname, 'placements': placements }; + + if (bidderRequest) { + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + } + for (let i = 0; i < validBidRequests.length; i++) { let bid = validBidRequests[i]; + let traff = bid.params.traffic || BANNER let placement = { placementId: bid.params.placement_id, bidId: bid.bidId, - sizes: bid.sizes, - traffic: bid.params.traffic || BANNER + sizes: bid.mediaTypes[traff].sizes, + traffic: traff }; + if (bid.schain) { + placement.schain = bid.schain; + } placements.push(placement); } return { method: 'POST', - url: URL, + url: G_URL, data: request }; }, @@ -103,7 +115,7 @@ export const spec = { getUserSyncs: () => { return [{ type: 'image', - url: URL_SYNC + url: G_URL_SYNC }]; } }; diff --git a/modules/colossussspBidAdapter.md b/modules/colossussspBidAdapter.md index 4760002f0db..d95080546c2 100644 --- a/modules/colossussspBidAdapter.md +++ b/modules/colossussspBidAdapter.md @@ -14,7 +14,11 @@ Module that connects to Colossus SSP demand sources ``` var adUnits = [{ code: 'placementid_0', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]] + } + }, bids: [{ bidder: 'colossusssp', params: { diff --git a/modules/consentManagement.js b/modules/consentManagement.js index 1e2a6648145..d5703c1a784 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagement.js @@ -335,6 +335,7 @@ function exitModule(errMsg, hookConfig, extraArgs) { */ export function resetConsentData() { consentData = undefined; + userCMP = undefined; gdprDataHandler.setConsentData(null); } @@ -343,6 +344,13 @@ export function resetConsentData() { * @param {object} config required; consentManagement module config settings; cmp (string), timeout (int), allowAuctionWithoutConsent (boolean) */ export function setConsentConfig(config) { + // if `config.gdpr` or `config.usp` exist, assume new config format. + // else for backward compatability, just use `config` + config = config.gdpr || config.usp ? config.gdpr : config; + if (!config || typeof config !== 'object') { + utils.logWarn('consentManagement config not defined, exiting consent manager'); + return; + } if (utils.isStr(config.cmpApi)) { userCMP = config.cmpApi; } else { diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js new file mode 100644 index 00000000000..a210132947c --- /dev/null +++ b/modules/consentManagementUsp.js @@ -0,0 +1,291 @@ +/** + * This module adds USPAPI (CCPA) consentManagement support to prebid.js. It + * interacts with supported USP Consent APIs to grab the user's consent + * information and make it available for any USP (CCPA) supported adapters to + * read/pass this information to their system. + */ +import * as utils from '../src/utils'; +import { config } from '../src/config'; +import { uspDataHandler } from '../src/adapterManager'; + +const DEFAULT_CONSENT_API = 'iab'; +const DEFAULT_CONSENT_TIMEOUT = 50; +const USPAPI_VERSION = 1; + +export let consentAPI; +export let consentTimeout; + +let consentData; +let addedConsentHook = false; + +// consent APIs +const uspCallMap = { + 'iab': lookupUspConsent +}; + +/** + * This function handles interacting with an USP compliant consent manager to obtain the consent information of the user. + * Given the async nature of the USP's API, we pass in acting success/error callback functions to exit this function + * based on the appropriate result. + * @param {function(string)} uspSuccess acts as a success callback when USPAPI returns a value; pass along consentObject (string) from UPSAPI + * @param {function(string)} uspError acts as an error callback while interacting with USPAPI; pass along an error message (string) + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + */ +function lookupUspConsent(uspSuccess, uspError, hookConfig) { + function handleUspApiResponseCallbacks() { + const uspResponse = {}; + + function afterEach() { + if (uspResponse.usPrivacy) { + uspSuccess(uspResponse, hookConfig); + } else { + uspError('Unable to get USP consent string.', hookConfig); + } + } + + return { + consentDataCallback: (consentResponse, success) => { + if (success && consentResponse.uspString) { + uspResponse.usPrivacy = consentResponse.uspString; + } + afterEach(); + } + }; + } + + let callbackHandler = handleUspApiResponseCallbacks(); + let uspapiCallbacks = {}; + + // to collect the consent information from the user, we perform a call to USPAPI + // to collect the user's consent choices represented as a string (via getUSPData) + + // the following code also determines where the USPAPI is located and uses the proper workflow to communicate with it: + // - use the USPAPI locator code to see if USP's located in the current window or an ancestor window. This works in friendly or cross domain iframes + // - if USPAPI is not found, the iframe function will call the uspError exit callback to abort the rest of the USPAPI workflow + // - try to call the __uspapi() function directly, otherwise use the postMessage() api + // find the CMP frame/window + + try { + // try to call __uspapi directly + window.__uspapi('getUSPData', USPAPI_VERSION, callbackHandler.consentDataCallback); + } catch (e) { + // must not have been accessible, try using postMessage() api + let f = window; + let uspapiFrame; + while (!uspapiFrame) { + try { + if (f.frames['__uspapiLocator']) uspapiFrame = f; + } catch (e) { } + if (f === window.top) break; + f = f.parent; + } + + if (!uspapiFrame) { + return uspError('USP CMP not found.', hookConfig); + } + callUspApiWhileInIframe('getUSPData', uspapiFrame, callbackHandler.consentDataCallback); + } + + function callUspApiWhileInIframe(commandName, uspapiFrame, moduleCallback) { + /* Setup up a __uspapi function to do the postMessage and stash the callback. + This function behaves, from the caller's perspective, identicially to the in-frame __uspapi call (although it is not synchronous) */ + window.__uspapi = function (cmd, ver, callback) { + let callId = Math.random() + ''; + let msg = { + __uspapiCall: { + command: cmd, + version: ver, + callId: callId + } + }; + + uspapiCallbacks[callId] = callback; + uspapiFrame.postMessage(msg, '*'); + } + + /** when we get the return message, call the stashed callback */ + window.addEventListener('message', readPostMessageResponse, false); + + // call uspapi + window.__uspapi(commandName, USPAPI_VERSION, uspapiCallback); + + function readPostMessageResponse(event) { + const res = event && event.data && event.data.__uspapiReturn; + if (res && res.callId) { + if (typeof uspapiCallbacks[res.callId] !== 'undefined') { + uspapiCallbacks[res.callId](res.returnValue, res.success); + delete uspapiCallbacks[res.callId]; + } + } + } + + function uspapiCallback(consentObject, success) { + window.removeEventListener('message', readPostMessageResponse, false); + moduleCallback(consentObject, success); + } + } +} + +/** + * If consentManagementUSP module is enabled (ie included in setConfig), this hook function will attempt to fetch the + * user's encoded consent string from the supported USPAPI. Once obtained, the module will store this + * data as part of a uspConsent object which gets transferred to adapterManager's uspDataHandler object. + * This information is later added into the bidRequest object for any supported adapters to read/pass along to their system. + * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + * @param {function} fn required; The next function in the chain, used by hook.js + */ +export function requestBidsHook(fn, reqBidsConfigObj) { + // preserves all module related variables for the current auction instance (used primiarily for concurrent auctions) + const hookConfig = { + context: this, + args: [reqBidsConfigObj], + nextFn: fn, + adUnits: reqBidsConfigObj.adUnits || $$PREBID_GLOBAL$$.adUnits, + bidsBackHandler: reqBidsConfigObj.bidsBackHandler, + haveExited: false, + timer: null + }; + + // in case we already have consent (eg during bid refresh) + if (consentData) { + return exitModule(null, hookConfig); + } + + if (!uspCallMap[consentAPI]) { + utils.logWarn(`USP framework (${consentAPI}) is not a supported framework. Aborting consentManagement module and resuming auction.`); + return hookConfig.nextFn.apply(hookConfig.context, hookConfig.args); + } + + uspCallMap[consentAPI].call(this, processUspData, uspapiFailed, hookConfig); + + // only let this code run if module is still active (ie if the callbacks used by USPs haven't already finished) + if (!hookConfig.haveExited) { + if (consentTimeout === 0) { + processUspData(undefined, hookConfig); + } else { + hookConfig.timer = setTimeout(uspapiTimeout.bind(null, hookConfig), consentTimeout); + } + } +} + +/** + * This function checks the consent data provided by USPAPI to ensure it's in an expected state. + * If it's bad, we exit the module depending on config settings. + * If it's good, then we store the value and exits the module. + * @param {object} consentObject required; object returned by USPAPI that contains user's consent choices + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + */ +function processUspData(consentObject, hookConfig) { + const valid = !!(consentObject && consentObject.usPrivacy); + if (!valid) { + uspapiFailed(`UPSAPI returned unexpected value during lookup process.`, hookConfig, consentObject); + return; + } + + clearTimeout(hookConfig.timer); + storeUspConsentData(consentObject); + exitModule(null, hookConfig); +} + +/** + * General timeout callback when interacting with USPAPI takes too long. + */ +function uspapiTimeout(hookConfig) { + uspapiFailed('USPAPI workflow exceeded timeout threshold.', hookConfig); +} + +/** + * This function contains the controlled steps to perform when there's a problem with USPAPI. + * @param {string} errMsg required; should be a short descriptive message for why the failure/issue happened. + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging +*/ +function uspapiFailed(errMsg, hookConfig, extraArgs) { + clearTimeout(hookConfig.timer); + + exitModule(errMsg, hookConfig, extraArgs); +} + +/** + * Stores USP data locally in module and then invokes uspDataHandler.setConsentData() to make information available in adaptermanger.js for later in the auction + * @param {object} cmpConsentObject required; an object representing user's consent choices (can be undefined in certain use-cases for this function only) + */ +function storeUspConsentData(consentObject) { + if (consentObject && consentObject.usPrivacy) { + consentData = consentObject.usPrivacy; + uspDataHandler.setConsentData(consentData); + } +} + +/** + * This function handles the exit logic for the module. + * There are a couple paths in the module's logic to call this function and we only allow 1 of the 2 potential exits to happen before suppressing others. + * + * We prevent multiple exits to avoid conflicting messages in the console depending on certain scenarios. + * One scenario could be auction was canceled due to timeout with USPAPI being reached. + * While the timeout is the accepted exit and runs first, the USP's callback still tries to process the user's data (which normally leads to a good exit). + * In this case, the good exit will be suppressed since we already decided to cancel the auction. + * + * Three exit paths are: + * 1. good exit where auction runs (USPAPI data is processed normally). + * 2. bad exit but auction still continues (warning message is logged, USPAPI data is undefined and still passed along). + * @param {string} errMsg optional; only to be used when there was a 'bad' exit. String is a descriptive message for the failure/issue encountered. + * @param {object} hookConfig contains module related variables (see comment in requestBidsHook function) + * @param {object} extraArgs contains additional data that's passed along in the error/warning messages for easier debugging + */ +function exitModule(errMsg, hookConfig, extraArgs) { + if (hookConfig.haveExited === false) { + hookConfig.haveExited = true; + + let context = hookConfig.context; + let args = hookConfig.args; + let nextFn = hookConfig.nextFn; + + if (errMsg) { + utils.logWarn(errMsg + ' Resuming auction without consent data as per consentManagement config.', extraArgs); + } + nextFn.apply(context, args); + } +} + +/** + * Simply resets the module's consentData variable back to undefined, mainly for testing purposes + */ +export function resetConsentData() { + consentData = undefined; + consentAPI = undefined; + uspDataHandler.setConsentData(null); +} + +/** + * A configuration function that initializes some module variables, as well as add a hook into the requestBids function + * @param {object} config required; consentManagementUSP module config settings; usp (string), timeout (int), allowAuctionWithoutConsent (boolean) + */ +export function setConsentConfig(config) { + config = config.usp; + if (!config || typeof config !== 'object') { + utils.logWarn('consentManagement.usp config not defined, exiting usp consent manager'); + return; + } + if (utils.isStr(config.cmpApi)) { + consentAPI = config.cmpApi; + } else { + consentAPI = DEFAULT_CONSENT_API; + utils.logInfo(`consentManagement.usp config did not specify cmpApi. Using system default setting (${DEFAULT_CONSENT_API}).`); + } + + if (utils.isNumber(config.timeout)) { + consentTimeout = config.timeout; + } else { + consentTimeout = DEFAULT_CONSENT_TIMEOUT; + utils.logInfo(`consentManagement.usp config did not specify timeout. Using system default setting (${DEFAULT_CONSENT_TIMEOUT}).`); + } + + utils.logInfo('USPAPI consentManagement module has been activated...'); + + if (!addedConsentHook) { + $$PREBID_GLOBAL$$.requestBids.before(requestBidsHook, 50); + } + addedConsentHook = true; +} +config.getConfig('consentManagement', config => setConsentConfig(config.consentManagement)); diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index d462acaee59..bd24870c5e5 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -47,7 +47,7 @@ export const spec = { const data = Object.assign({ placements: [], time: Date.now(), - url: utils.getTopWindowUrl(), + url: bidderRequest.refererInfo.referer, referrer: document.referrer, source: [{ 'name': 'prebidjs', @@ -63,9 +63,10 @@ export const spec = { } validBidRequests.map(bid => { + const sizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes || []; const placement = Object.assign({ divName: bid.bidId, - adTypes: bid.adTypes || getSize(bid.sizes) + adTypes: bid.adTypes || getSize(sizes) }, bid.params); if (placement.networkId && placement.siteId && placement.unitId && placement.unitName) { @@ -75,6 +76,7 @@ export const spec = { ret.data = JSON.stringify(data); ret.bidRequest = validBidRequests; + ret.bidderRequest = bidderRequest; ret.url = BASE_URI; return ret; @@ -117,7 +119,7 @@ export const spec = { bid.creativeId = decision.adId; bid.ttl = 30; bid.netRevenue = true; - bid.referrer = utils.getTopWindowUrl(); + bid.referrer = bidRequest.bidderRequest.refererInfo.referer; bidResponses.push(bid); } diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index a3479a9d1d1..bb11b1f87cb 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -52,11 +52,14 @@ export const spec = { let siteId = ''; let requestId = ''; let pubcid = null; + let pubcidName = '_pubcid'; const conversantImps = validBidRequests.map(function(bid) { const bidfloor = utils.getBidIdParameter('bidfloor', bid.params); - siteId = utils.getBidIdParameter('site_id', bid.params); + siteId = utils.getBidIdParameter('site_id', bid.params) || siteId; + pubcidName = utils.getBidIdParameter('pubcid_name', bid.params) || pubcidName; + requestId = bid.auctionId; const imp = { @@ -119,24 +122,36 @@ export const spec = { let userExt = {}; - // Add GDPR flag and consent string - if (bidderRequest && bidderRequest.gdprConsent) { - userExt.consent = bidderRequest.gdprConsent.consentString; + if (bidderRequest) { + // Add GDPR flag and consent string + if (bidderRequest.gdprConsent) { + userExt.consent = bidderRequest.gdprConsent.consentString; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { - payload.regs = { - ext: { - gdpr: (bidderRequest.gdprConsent.gdprApplies ? 1 : 0) - } - }; + if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') { + utils.deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + } + } + + if (bidderRequest.uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); } } + if (!pubcid) { + pubcid = readStoredValue(pubcidName); + } + // Add common id if available if (pubcid) { userExt.fpc = pubcid; } + // Add Eids if available + const eids = collectEids(validBidRequests); + if (eids.length > 0) { + userExt.eids = eids; + } + // Only add the user object if it's not empty if (!utils.isEmpty(userExt)) { payload.user = {ext: userExt}; @@ -183,7 +198,12 @@ export const spec = { }; if (request.video) { - bid.vastUrl = responseAd; + if (responseAd.charAt(0) === '<') { + bid.vastXml = responseAd; + } else { + bid.vastUrl = responseAd; + } + bid.mediaType = 'video'; bid.width = request.video.w; bid.height = request.video.h; @@ -287,4 +307,74 @@ function copyOptProperty(src, dst, dstName) { } } +/** + * Collect IDs from validBidRequests and store them as an extended id array + * @param bidRequests valid bid requests + */ +function collectEids(bidRequests) { + const request = bidRequests[0]; // bidRequests have the same userId object + const eids = []; + + addEid(eids, request, 'userId.tdid', 'adserver.org'); + addEid(eids, request, 'userId.idl_env', 'liveramp.com'); + addEid(eids, request, 'userId.criteoId', 'criteo.com'); + addEid(eids, request, 'userId.id5id', 'id5-sync.com'); + addEid(eids, request, 'userId.parrableid', 'parrable.com'); + addEid(eids, request, 'userId.digitrustid.data.id', 'digitru.st'); + addEid(eids, request, 'userId.lipb.lipbid', 'liveintent.com'); + + return eids; +} + +/** + * Extract and push a single extended id into eids array + * @param eids Array of extended IDs + * @param idObj Object containing IDs + * @param keyPath Nested properties expressed as a path + * @param source Source for the ID + */ +function addEid(eids, idObj, keyPath, source) { + const id = utils.deepAccess(idObj, keyPath); + if (id) { + eids.push({ + source: source, + uids: [{ + id: id, + atype: 1 + }] + }); + } +} + +/** + * Look for a stored value from both cookie and local storage and return the first value found. + * @param key Key for the search + * @return {string} Stored value + */ +function readStoredValue(key) { + let storedValue; + try { + // check cookies first + storedValue = utils.getCookie(key); + + if (!storedValue) { + // check expiration time before reading local storage + const storedValueExp = utils.getDataFromLocalStorage(`${key}_exp`); + if (storedValueExp === '' || (storedValueExp && (new Date(storedValueExp)).getTime() - Date.now() > 0)) { + storedValue = utils.getDataFromLocalStorage(key); + storedValue = storedValue ? decodeURIComponent(storedValue) : storedValue; + } + } + + // deserialize JSON if needed + if (utils.isStr(storedValue) && storedValue.charAt(0) === '{') { + storedValue = JSON.parse(storedValue); + } + } catch (e) { + utils.logError(e); + } + + return storedValue; +} + registerBidder(spec); diff --git a/modules/criteoIdSystem.js b/modules/criteoIdSystem.js new file mode 100644 index 00000000000..fe89de5d341 --- /dev/null +++ b/modules/criteoIdSystem.js @@ -0,0 +1,132 @@ +/** + * This module adds Criteo Real Time User Sync to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/criteoIdSystem + * @requires module:modules/userId + */ + +import * as utils from '../src/utils' +import * as ajax from '../src/ajax' +import * as urlLib from '../src/url' +import { getRefererInfo } from '../src/refererDetection' +import { submodule } from '../src/hook'; + +const bididStorageKey = 'cto_bidid'; +const bundleStorageKey = 'cto_bundle'; +const cookieWriteableKey = 'cto_test_cookie'; +const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; + +const pastDateString = new Date(0).toString(); +const expirationString = new Date(utils.timestamp() + cookiesMaxAge).toString(); + +function areCookiesWriteable() { + utils.setCookie(cookieWriteableKey, '1'); + const canWrite = utils.getCookie(cookieWriteableKey) === '1'; + utils.setCookie(cookieWriteableKey, '', pastDateString); + return canWrite; +} + +function extractProtocolHost (url, returnOnlyHost = false) { + const parsedUrl = urlLib.parse(url) + return returnOnlyHost + ? `${parsedUrl.hostname}` + : `${parsedUrl.protocol}://${parsedUrl.hostname}${parsedUrl.port ? ':' + parsedUrl.port : ''}/`; +} + +function getFromAllStorages(key) { + return utils.getCookie(key) || utils.getDataFromLocalStorage(key); +} + +function saveOnAllStorages(key, value) { + if (key && value) { + utils.setCookie(key, value, expirationString); + utils.setDataInLocalStorage(key, value); + } +} + +function deleteFromAllStorages(key) { + utils.setCookie(key, '', pastDateString); + utils.removeDataFromLocalStorage(key); +} + +function getCriteoDataFromAllStorages() { + return { + bundle: getFromAllStorages(bundleStorageKey), + bidId: getFromAllStorages(bididStorageKey), + } +} + +function buildCriteoUsersyncUrl(topUrl, domain, bundle, areCookiesWriteable, isPublishertagPresent) { + const url = 'https://gum.criteo.com/sid/json?origin=prebid' + + `${topUrl ? '&topUrl=' + encodeURIComponent(topUrl) : ''}` + + `${domain ? '&domain=' + encodeURIComponent(domain) : ''}` + + `${bundle ? '&bundle=' + encodeURIComponent(bundle) : ''}` + + `${areCookiesWriteable ? '&cw=1' : ''}` + + `${isPublishertagPresent ? '&pbt=1' : ''}` + + return url; +} + +function callCriteoUserSync(parsedCriteoData) { + const cw = areCookiesWriteable(); + const topUrl = extractProtocolHost(getRefererInfo().referer); + const domain = extractProtocolHost(document.location.href, true); + const isPublishertagPresent = typeof criteo_pubtag !== 'undefined'; // eslint-disable-line camelcase + + const url = buildCriteoUsersyncUrl( + topUrl, + domain, + parsedCriteoData.bundle, + cw, + isPublishertagPresent + ); + + ajax.ajaxBuilder()( + url, + response => { + const jsonResponse = JSON.parse(response); + if (jsonResponse.bidId) { + saveOnAllStorages(bididStorageKey, jsonResponse.bidId); + } else { + deleteFromAllStorages(bididStorageKey); + } + + if (jsonResponse.acwsUrl) { + const urlsToCall = typeof jsonResponse.acwsUrl === 'string' ? [jsonResponse.acwsUrl] : jsonResponse.acwsUrl; + urlsToCall.forEach(url => utils.triggerPixel(url)); + } else if (jsonResponse.bundle) { + saveOnAllStorages(bundleStorageKey, jsonResponse.bundle); + } + } + ); +} + +/** @type {Submodule} */ +export const criteoIdSubmodule = { + /** + * used to link submodule with config + * @type {string} + */ + name: 'criteo', + /** + * decode the stored id value for passing to bid requests + * @function + * @returns {{criteoId: string} | undefined} + */ + decode(bidId) { + return bidId; + }, + /** + * get the Criteo Id from local storages and initiate a new user sync + * @function + * @returns {{id: {criteoId: string} | undefined}}} + */ + getId() { + let localData = getCriteoDataFromAllStorages(); + callCriteoUserSync(localData); + + return { id: localData.bidId ? { criteoId: localData.bidId } : undefined } + } +}; + +submodule('userId', criteoIdSubmodule); diff --git a/modules/currency.js b/modules/currency.js index ae2f9ac1f1b..28e033fb1a2 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -185,9 +185,6 @@ export function addBidResponseHook(fn, adUnitCode, bid) { return (parseFloat(this.cpm) * getCurrencyConversion(this.currency, toCurrency)).toFixed(3); }; - bid.originalCpm = bid.cpm; - bid.originalCurrency = bid.currency; - // execute immediately if the bid is already in the desired currency if (bid.currency === adServerCurrency) { return fn.call(this, adUnitCode, bid); diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js new file mode 100644 index 00000000000..39866086cfa --- /dev/null +++ b/modules/deepintentBidAdapter.js @@ -0,0 +1,170 @@ +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER} from '../src/mediaTypes'; +import * as utils from '../src/utils'; +const BIDDER_CODE = 'deepintent'; +const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; +const USER_SYNC_URL = 'https://beacon.deepintent.com/usersync.html'; +const DI_M_V = '1.0.0'; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER], + aliases: [], + + // tagId is mandatory param + isBidRequestValid: bid => { + let valid = false; + if (bid && bid.params && bid.params.tagId) { + if (typeof bid.params.tagId === 'string' || bid.params.tagId instanceof String) { + valid = true; + } + } + return valid; + }, + interpretResponse: function(bidResponse, request) { + let responses = []; + if (bidResponse && bidResponse.body) { + let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : []; + responses = bids.map(bid => formatResponse(bid)) + } + return responses; + }, + buildRequests: function (validBidRequests, bidderRequest) { + var user = validBidRequests.map(bid => buildUser(bid)); + clean(user); + const openRtbBidRequest = { + id: utils.generateUUID(), + at: 1, + imp: validBidRequests.map(bid => buildImpression(bid)), + site: buildSite(bidderRequest), + device: buildDevice(), + user: user && user.length == 1 ? user[0] : {} + }; + + return { + method: 'POST', + url: BIDDER_ENDPOINT, + data: JSON.stringify(openRtbBidRequest), + options: { + contentType: 'application/json' + } + }; + }, + /** + * Register User Sync. + */ + getUserSyncs: syncOptions => { + if (syncOptions.iframeEnabled) { + return [{ + type: 'iframe', + url: USER_SYNC_URL + }]; + } + } + +}; +function clean(obj) { + for (let propName in obj) { + if (obj[propName] === null || obj[propName] === undefined) { + delete obj[propName]; + } + } +} + +function formatResponse(bid) { + return { + requestId: bid && bid.impid ? bid.impid : undefined, + cpm: bid && bid.price ? bid.price : 0.0, + width: bid && bid.w ? bid.w : 0, + height: bid && bid.h ? bid.h : 0, + ad: bid && bid.adm ? bid.adm : '', + creativeId: bid && bid.crid ? bid.crid : undefined, + netRevenue: false, + currency: bid && bid.cur ? bid.cur : 'USD', + ttl: 300, + dealId: bid && bid.dealId ? bid.dealId : undefined + } +} + +function buildImpression(bid) { + return { + id: bid.bidId, + tagid: bid.params.tagId || '', + secure: window.location.protocol === 'https' ? 1 : 0, + banner: buildBanner(bid), + displaymanager: 'di_prebid', + displaymanagerver: DI_M_V, + ext: buildCustomParams(bid) + }; +} +function buildCustomParams(bid) { + if (bid.params && bid.params.custom) { + return { + deepintent: bid.params.custom + + } + } else { + return {} + } +} +function buildUser(bid) { + if (bid && bid.params && bid.params.user) { + return { + id: bid.params.user.id && typeof bid.params.user.id == 'string' ? bid.params.user.id : undefined, + buyeruid: bid.params.user.buyeruid && typeof bid.params.user.buyeruid == 'string' ? bid.params.user.buyeruid : undefined, + yob: bid.params.user.yob && typeof bid.params.user.yob == 'number' ? bid.params.user.yob : null, + gender: bid.params.user.gender && typeof bid.params.user.gender == 'string' ? bid.params.user.gender : undefined, + keywords: bid.params.user.keywords && typeof bid.params.user.keywords == 'string' ? bid.params.user.keywords : undefined, + customdata: bid.params.user.customdata && typeof bid.params.user.customdata == 'string' ? bid.params.user.customdata : undefined + } + } +} + +function buildBanner(bid) { + if (utils.deepAccess(bid, 'mediaTypes.banner')) { + // Get Sizes from MediaTypes Object, Will always take first size, will be overrided by params for exact w,h + if (utils.deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) { + let sizes = utils.deepAccess(bid, 'mediaTypes.banner.sizes'); + if (utils.isArray(sizes) && sizes.length > 0) { + return { + h: sizes[0][1], + w: sizes[0][0] + } + } + } else { + return { + h: bid.params.height, + w: bid.params.width + } + } + } +} + +function buildSite(bidderRequest) { + let site = {}; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) { + site.page = bidderRequest.refererInfo.referer; + site.domain = getDomain(bidderRequest.refererInfo.referer); + } + return site; +} + +function getDomain(referer) { + if (referer) { + let domainA = document.createElement('a'); + domainA.href = referer; + return domainA.hostname; + } +} + +function buildDevice() { + return { + ua: navigator.userAgent, + js: 1, + dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack === '1') ? 1 : 0, + h: screen.height, + w: screen.width, + language: navigator.language + } +} + +registerBidder(spec); diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md new file mode 100644 index 00000000000..7f5afbd233a --- /dev/null +++ b/modules/deepintentBidAdapter.md @@ -0,0 +1,53 @@ +# Overview + +``` +Module Name: Deepintent Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid@deepintent.com +``` + +# Description + +Deepintent currently supports the BANNER type ads through prebid js + +Module that connects to Deepintent's demand sources. + +# Banner Test Request +``` + var adUnits = [ + { + code: 'di_adUnit1', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size, only first one will be picked up since multiple ad sizes are not supported yet + } + } + bids: [ + { + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter + w: 300, // Width and Height here will override sizes in mediatype + h: 250, + custom: { // Custom parameters in form of key value pairs + user_min_age: 18 + } + } + } + ] + } + ]; +``` + +###Recommended User Sync Configuration + +```javascript +pbjs.setConfig({ + userSync: { + iframeEnabled: true, + enabledBidders: ['deepintent'], + syncDelay: 3000 + }}); + + +``` diff --git a/modules/districtmDMXBidAdapter.js b/modules/districtmDMXBidAdapter.js index f69fca919c0..205017066a8 100644 --- a/modules/districtmDMXBidAdapter.js +++ b/modules/districtmDMXBidAdapter.js @@ -22,7 +22,7 @@ export const spec = { if (oBid.price < nBid.price) { const bid = matchRequest(nBid.impid, bidRequest); const {width, height} = defaultSize(bid); - nBid.cpm = nBid.price; + nBid.cpm = parseFloat(nBid.price).toFixed(2); nBid.bidId = nBid.impid; nBid.requestId = nBid.impid; nBid.width = nBid.w || width; @@ -32,7 +32,6 @@ export const spec = { nBid.creativeId = nBid.crid; nBid.currency = 'USD'; nBid.ttl = 60; - return nBid; } else { oBid.cpm = oBid.price; @@ -69,6 +68,14 @@ export const spec = { site: { publisher: { id: String(bidRequest[0].params.memberid) || null } } + } + try { + let params = config.getConfig('dmx'); + dmxRequest.user = params.user || {}; + let site = params.site || {}; + dmxRequest.site = {...dmxRequest.site, ...site} + } catch (e) { + } if (!dmxRequest.test) { delete dmxRequest.test; @@ -91,24 +98,28 @@ export const spec = { var obj = {}; obj.id = dmx.bidId; obj.tagid = String(dmx.params.dmxid); - obj.secure = window.location.protocol === 'https:' ? 1 : 0; + obj.secure = 1; obj.banner = { topframe: 1, - w: dmx.sizes[0][0] || 0, - h: dmx.sizes[0][1] || 0, - format: dmx.sizes.map(s => { + w: cleanSizes(dmx.sizes, 'w'), + h: cleanSizes(dmx.sizes, 'h'), + format: cleanSizes(dmx.sizes).map(s => { return {w: s[0], h: s[1]}; }).filter(obj => typeof obj.w === 'number' && typeof obj.h === 'number') }; return obj; }); - dmxRequest.imp = tosendtags; - return { - method: 'POST', - url: DMXURI, - data: JSON.stringify(dmxRequest), - bidderRequest + if (tosendtags.length <= 5) { + dmxRequest.imp = tosendtags; + return { + method: 'POST', + url: DMXURI, + data: JSON.stringify(dmxRequest), + bidderRequest + } + } else { + return upto5(tosendtags, dmxRequest, bidderRequest, DMXURI); } }, test() { @@ -124,6 +135,101 @@ export const spec = { } } +export function cleanSizes(sizes, value) { + const supportedSize = [ + { + size: [300, 250], + s: 100 + }, + { + size: [728, 90], + s: 95 + }, + { + size: [320, 50], + s: 90 + }, + { + size: [160, 600], + s: 88 + }, + { + size: [300, 600], + s: 85 + }, + { + size: [300, 50], + s: 80 + }, + { + size: [970, 250], + s: 75 + }, + { + size: [970, 90], + s: 60 + }, + ]; + let newArray = shuffle(sizes, supportedSize); + switch (value) { + case 'w': + return newArray[0][0] || 0; + case 'h': + return newArray[0][1] || 0; + case 'size': + return newArray; + default: + return newArray; + } +} + +export function shuffle(sizes, list) { + let removeSizes = sizes.filter(size => { + return list.map(l => `${l.size[0]}x${l.size[1]}`).indexOf(`${size[0]}x${size[1]}`) === -1 + }) + let reOrder = sizes.reduce((results, current) => { + if (results.length === 0) { + results.push(current); + return results; + } + results.push(current); + results = list.filter(l => results.map(r => `${r[0]}x${r[1]}`).indexOf(`${l.size[0]}x${l.size[1]}`) !== -1); + results = results.sort(function(a, b) { + return b.s - a.s; + }) + return results.map(r => r.size); + }, []) + return removeDuplicate([...reOrder, ...removeSizes]); +} + +export function removeDuplicate(arrayValue) { + return arrayValue.filter((elem, index) => { + return arrayValue.map(e => `${e[0]}x${e[1]}`).indexOf(`${elem[0]}x${elem[1]}`) === index + }) +} + +export function upto5(allimps, dmxRequest, bidderRequest, DMXURI) { + let start = 0; + let step = 5; + let req = []; + while (allimps.length !== 0) { + if (allimps.length >= 5) { + req.push(allimps.splice(start, step)) + } else { + req.push(allimps.splice(start, allimps.length)) + } + } + return req.map(r => { + dmxRequest.imp = r; + return { + method: 'POST', + url: DMXURI, + data: JSON.stringify(dmxRequest), + bidderRequest + } + }) +} + /** * Function matchRequest(id: string, BidRequest: object) * @param id diff --git a/modules/districtmDmxBidAdapter.md b/modules/districtmDmxBidAdapter.md index 2859bcfa19d..5bc1e5d5780 100644 --- a/modules/districtmDmxBidAdapter.md +++ b/modules/districtmDmxBidAdapter.md @@ -115,3 +115,25 @@ Our demand and adapter supports multiple sizes per placement, as such a single d ###### 4. Implementation Checking Once the bidder is live in your Prebid configuration you may confirm it is making requests to our end point by looking for requests to `https://dmx.districtm.io/b/v1`. + + +###### 5. Setting first party data + +```code +pbjs.setConfig({ + dmx: { + user: { + 'gender': 'M', + 'yob': 1992, + // keywords example + 'keywords': 'automotive,dodge,engine,car' + + }, + site: { + cat: ['IAB-12'], + pagecat: ['IAB-14'], + sectioncat: ['IAB-24'] + } + } +}); +``` diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index 8b763202b7c..a109bc612db 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -20,7 +20,7 @@ export const spec = { const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); - const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + const referrer = bidderRequest.refererInfo.referer; const bidId = bidRequest.bidId; const payload = { _f: 'html', diff --git a/modules/emoteevBidAdapter.js b/modules/emoteevBidAdapter.js index db84b6ea36d..fb62e4381e3 100644 --- a/modules/emoteevBidAdapter.js +++ b/modules/emoteevBidAdapter.js @@ -36,8 +36,8 @@ export const BIDDER_CODE = 'emoteev'; */ export const ADAPTER_VERSION = '1.35.0'; -export const DOMAIN = 'prebid.emoteev.io'; -export const DOMAIN_STAGING = 'prebid-staging.emoteev.io'; +export const DOMAIN = 'prebid.emoteev.xyz'; +export const DOMAIN_STAGING = 'prebid-staging.emoteev.xyz'; export const DOMAIN_DEVELOPMENT = 'localhost:3000'; /** diff --git a/modules/emx_digitalBidAdapter.js b/modules/emx_digitalBidAdapter.js index 7167f9018aa..0768c8386fb 100644 --- a/modules/emx_digitalBidAdapter.js +++ b/modules/emx_digitalBidAdapter.js @@ -7,7 +7,7 @@ import includes from 'core-js/library/fn/array/includes'; const BIDDER_CODE = 'emx_digital'; const ENDPOINT = 'hb.emxdgt.com'; const RENDERER_URL = '//js.brealtime.com/outstream/1.30.0/bundle.js'; -const ADAPTER_VERSION = '1.41.1'; +const ADAPTER_VERSION = '1.41.2'; const DEFAULT_CUR = 'USD'; export const emxAdapter = { @@ -230,6 +230,9 @@ export const spec = { }; emxData = emxAdapter.getGdpr(bidRequest, Object.assign({}, emxData)); + if (bidRequest && bidRequest.uspConsent) { + emxData.us_privacy = bidRequest.uspConsent + } return { method: 'POST', url: url, diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index 01a956d1bd8..d9e14554483 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -11,9 +11,12 @@ const NET_REVENUE = true; const TTL = 120; const NULL_SIZE = '1x1'; const FILE = 'file'; +const STORAGE_RENDER_PREFIX = 'pbsr_'; +const STORAGE_VIEW_PREFIX = 'pbvi_'; export const spec = { code: BIDDER_CODE, + isBidRequestValid: function(bid) { return Boolean(bid.params.ci) || Boolean(bid.params.t); }, @@ -26,21 +29,26 @@ export const spec = { let params; const urlConfig = getUrlConfig(bidRequests); const pcrs = getCharset(); - + const spaces = getSpaces(bidRequests); if (urlConfig.t) { url = urlConfig.isv + '/layers/t_pbjs_2.json'; params = {}; } else { url = '//' + (urlConfig.sv || DEFAULT_SV) + '/hb/1/' + urlConfig.ci + '/' + dfpClientId + '/' + (utils.getTopWindowLocation().hostname || FILE) + '/' + sec; const referrerUrl = utils.getTopWindowReferrer(); - const spacesString = getSpacesString(bidRequests); + + if (utils.hasLocalStorage()) { + registerViewabilityAllBids(bidRequests); + } + params = { rnd: rnd, - e: spacesString, + e: spaces.str, ur: utils.getTopWindowUrl() || FILE, r: 'pbjs', pbv: '$prebid.version$', - ncb: '1' + ncb: '1', + vs: spaces.vs }; if (pcrs) { @@ -56,7 +64,7 @@ export const spec = { method: method, url: url, data: params, - adUnitToBidId: getBidIdMap(bidRequests), + adUnitToBidId: spaces.map, }; }, interpretResponse: function(serverResponse, request) { @@ -111,9 +119,6 @@ export const spec = { }, } -function cleanName(name) { - return name.replace(/_|\.|-|\//g, '').replace(/\)\(|\(|\)|:/g, '_').replace(/^_+|_+$/g, ''); -} function getUrlConfig(bidRequests) { if (isTestRequest(bidRequests)) { return getTestConfig(bidRequests.filter(br => br.params.t)); @@ -135,9 +140,12 @@ function getUrlConfig(bidRequests) { return config; } function isTestRequest(bidRequests) { - let isTest = false; - bidRequests.forEach(bid => isTest = bid.params.t); - return isTest; + for (let i = 0; i < bidRequests.length; i++) { + if (bidRequests[i].params.t) { + return true; + } + } + return false; } function getTestConfig(bidRequests) { let isv; @@ -147,14 +155,55 @@ function getTestConfig(bidRequests) { isv: '//' + (isv || DEFAULT_ISV) }; } -function getSpacesString(bids) { - const spacesString = bids.map(bid => - cleanName(bid.adUnitCode) + ':' + (bid.sizes && bid.sizes.length ? utils.parseSizesInput(bid.sizes).join(',') : NULL_SIZE) - ).join('+'); - return spacesString; +function getSize(bid, first) { + return bid.sizes && bid.sizes.length ? utils.parseSizesInput(first ? bid.sizes[0] : bid.sizes).join(',') : NULL_SIZE; +} + +function getSpacesStruct(bids) { + let e = {}; + bids.forEach(bid => { + let size = getSize(bid, true); + e[size] = e[size] ? e[size] : []; + e[size].push(bid); + }); + + return e; +} + +function getSpaces(bidRequests) { + let spacesStruct = getSpacesStruct(bidRequests); + let es = {str: '', vs: '', map: {}}; + es.str = Object.keys(spacesStruct).map(size => spacesStruct[size].map((bid, i) => { + es.vs += getVs(bid); + let name = getSize(bid, true) + '_' + i; + es.map[name] = bid.bidId; + return name + ':' + getSize(bid); + }).join('+')).join('+'); + return es; +} + +function getVs(bid) { + let s; + let vs = ''; + if (utils.hasLocalStorage()) { + s = getViewabilityData(bid); + vs += s.render >= 4 ? s.ratio.toString(16) : 'F'; + } else { + vs += 'F'; + } + return vs; } +function getViewabilityData(bid) { + let r = utils.getDataFromLocalStorage(STORAGE_RENDER_PREFIX + bid.adUnitCode) || 0; + let v = utils.getDataFromLocalStorage(STORAGE_VIEW_PREFIX + bid.adUnitCode) || 0; + let ratio = r > 0 ? (v / r) : 0; + return { + render: r, + ratio: window.parseInt(ratio * 10, 10) + }; +} function getCharset() { try { return window.top.document.charset || window.top.document.characterSet; @@ -163,10 +212,184 @@ function getCharset() { } } -function getBidIdMap(bidRequests) { - let map = {}; - bidRequests.forEach(bid => map[cleanName(bid.adUnitCode)] = bid.bidId); - return map; +function waitForElementsPresent(elements) { + const observer = new MutationObserver(function (mutationList, observer) { + if (mutationList && Array.isArray(mutationList)) { + mutationList.forEach(mr => { + if (mr && mr.addedNodes && Array.isArray(mr.addedNodes)) { + mr.addedNodes.forEach(ad => { + let index = elements.indexOf(ad.id); + if (index >= 0) { + registerViewability(ad); + elements.splice(index, 1); + if (!elements.length) { + observer.disconnect(); + } + } + }); + } + }); + } + }); + const config = {childList: true, subtree: true, characterData: true, attributes: true, attributeOldValue: true}; + observer.observe(document.body, config); +} + +function registerViewability(div) { + visibilityHandler({ + name: div.id, + div: div + }); +} + +function registerViewabilityAllBids(bids) { + let elementsNotPresent = []; + bids.forEach(bid => { + let div = document.getElementById(bid.adUnitCode); + if (div) { + registerViewability(div); + } else { + elementsNotPresent.push(bid.adUnitCode); + } + }); + if (elementsNotPresent.length) { + waitForElementsPresent(elementsNotPresent); + } } +function getViewabilityTracker() { + let TIME_PARTITIONS = 5; + let VIEWABILITY_TIME = 1000; + let VIEWABILITY_MIN_RATIO = 0.5; + let publicApi; + let context; + + function segmentIsOutsideTheVisibleRange(visibleRangeEnd, p1, p2) { + return p1 > visibleRangeEnd || p2 < 0; + } + + function segmentBeginsBeforeTheVisibleRange(p1) { + return p1 < 0; + } + + function segmentEndsAfterTheVisibleRange(visibleRangeEnd, p2) { + return p2 < visibleRangeEnd; + } + + function axialVisibilityRatio(visibleRangeEnd, p1, p2) { + let visibilityRatio = 0; + if (!segmentIsOutsideTheVisibleRange(visibleRangeEnd, p1, p2)) { + if (segmentBeginsBeforeTheVisibleRange(p1)) { + visibilityRatio = p2 / (p2 - p1); + } else { + visibilityRatio = segmentEndsAfterTheVisibleRange(visibleRangeEnd, p2) ? 1 : (visibleRangeEnd - p1) / (p2 - p1); + } + } + return visibilityRatio; + } + + function isNotHiddenByNonFriendlyIframe() { + return (window === window.top) || window.frameElement; + } + + function defineContext(e) { + context = e && window.document.body.contains(e) ? window : (window.top.document.body.contains(e) ? top : undefined); + return context; + } + + function getContext(e) { + return context; + } + + function verticalVisibilityRatio(position) { + return axialVisibilityRatio(getContext().innerHeight, position.top, position.bottom); + } + + function horizontalVisibilityRatio(position) { + return axialVisibilityRatio(getContext().innerWidth, position.left, position.right); + } + + function itIsNotHiddenByBannerAreaPosition(e) { + let position = e.getBoundingClientRect(); + return (verticalVisibilityRatio(position) * horizontalVisibilityRatio(position)) > VIEWABILITY_MIN_RATIO; + } + + function itIsNotHiddenByDisplayStyleCascade(e) { + return e.offsetHeight > 0 && e.offsetWidth > 0; + } + + function itIsNotHiddenByOpacityStyleCascade(e) { + let s = e.style; + let p = e.parentNode; + return !(s && parseFloat(s.opacity) === 0) && (!p || itIsNotHiddenByOpacityStyleCascade(p)); + } + + function itIsNotHiddenByVisibilityStyleCascade(e) { + return getContext().getComputedStyle(e).visibility !== 'hidden'; + } + + function itIsNotHiddenByTabFocus() { + return getContext().top.document.hasFocus(); + } + + function isDefined(e) { + return (e !== null) && (typeof e !== 'undefined'); + } + + function itIsNotHiddenByOrphanBranch() { + return isDefined(getContext()); + } + + function isContextInAnIframe() { + return isDefined(getContext().frameElement); + } + + function processIntervalVisibilityStatus(elapsedVisibleIntervals, element, callback) { + let visibleIntervals = isVisible(element) ? (elapsedVisibleIntervals + 1) : 0; + if (visibleIntervals === TIME_PARTITIONS) { + callback(); + } else { + setTimeout(processIntervalVisibilityStatus.bind(this, visibleIntervals, element, callback), VIEWABILITY_TIME / TIME_PARTITIONS); + } + } + + function isVisible(element) { + defineContext(element); + return isNotHiddenByNonFriendlyIframe() && + itIsNotHiddenByOrphanBranch() && + itIsNotHiddenByTabFocus() && + itIsNotHiddenByDisplayStyleCascade(element) && + itIsNotHiddenByVisibilityStyleCascade(element) && + itIsNotHiddenByOpacityStyleCascade(element) && + itIsNotHiddenByBannerAreaPosition(element) && + (!isContextInAnIframe() || isVisible(getContext().frameElement)); + } + + publicApi = { + isVisible: isVisible, + onView: processIntervalVisibilityStatus.bind(this, 0) + }; + + return publicApi; +}; + +function visibilityHandler(obj) { + if (obj.div) { + registerAuction(STORAGE_RENDER_PREFIX + obj.name); + getViewabilityTracker().onView(obj.div, registerAuction.bind(undefined, STORAGE_VIEW_PREFIX + obj.name)); + } +} + +function registerAuction(storageID) { + let value; + try { + value = utils.getDataFromLocalStorage(storageID); + value = value ? window.parseInt(value, 10) + 1 : 1; + utils.setDataInLocalStorage(storageID, value); + } catch (exc) { + return false; + } + + return true; +} registerBidder(spec); diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index b18188cf33a..89ffde51889 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -72,7 +72,10 @@ export const spec = { 'ua': navigator.userAgent }, 'imp': [], - 'ext': {} + 'ext': {}, + 'user': { + 'ext': {} + } }; const gdprConsent = bidderRequest.gdprConsent; diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index ac9a7d30429..57ab4ff4ed9 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -5,9 +5,12 @@ import { VIDEO, BANNER } from '../src/mediaTypes'; const BIDDER_CODE = 'grid'; const ENDPOINT_URL = '//grid.bidswitch.net/hb'; +const SYNC_URL = '//x.bidswitch.net/sync?ssp=iow_labs'; const TIME_TO_LIVE = 360; const RENDERER_URL = '//acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; +let hasSynced = false; + const LOG_ERROR_MESS = { noAuid: 'Bid from response has no auid parameter - ', noAdm: 'Bid from response has no adm parameter - ', @@ -136,6 +139,25 @@ export const spec = { } if (errorMessage) utils.logError(errorMessage); return bidResponses; + }, + getUserSyncs: function (syncOptions, responses, gdprConsent) { + if (!hasSynced && syncOptions.pixelEnabled) { + let params = ''; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + params += `&gdpr_consent=${gdprConsent.consentString}`; + } + } + + hasSynced = true; + return { + type: 'image', + url: SYNC_URL + params + }; + } } }; @@ -244,4 +266,12 @@ function createRenderer (bid, rendererParams) { return renderer; } +export function resetUserSync() { + hasSynced = false; +} + +export function getSyncUrl() { + return SYNC_URL; +} + registerBidder(spec); diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 60f446b12ee..2325e1bc448 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -41,7 +41,7 @@ function _getBrowserParams(topWindowUrl) { try { topWindow = global.top; topScreen = topWindow.screen; - topUrl = topWindowUrl || utils.getTopWindowUrl(); + topUrl = topWindowUrl || ''; } catch (error) { utils.logError(error); return browserParams @@ -98,6 +98,28 @@ function _getDigiTrustQueryParams(userId) { }; } +/** + * Serializes the supply chain object according to IAB standards + * @see https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md + * @param {Object} schainObj supply chain object + * @returns {string} + */ +function _serializeSupplyChainObj(schainObj) { + let serializedSchain = `${schainObj.ver},${schainObj.complete}`; + + // order of properties: asi,sid,hp,rid,name,domain + schainObj.nodes.map(node => { + serializedSchain += `!${encodeURIComponent(node['asi'] || '')},`; + serializedSchain += `${encodeURIComponent(node['sid'] || '')},`; + serializedSchain += `${encodeURIComponent(node['hp'] || '')},`; + serializedSchain += `${encodeURIComponent(node['rid'] || '')},`; + serializedSchain += `${encodeURIComponent(node['name'] || '')},`; + serializedSchain += `${encodeURIComponent(node['domain'] || '')}`; + }) + + return serializedSchain; +} + /** * Determines whether or not the given bid request is valid. * @@ -141,10 +163,12 @@ function buildRequests (validBidRequests, bidderRequest) { const { bidId, params = {}, + schain, transactionId, userId = {} } = bidRequest; const data = {}; + const sizes = bidRequest.mediaTypes && bidRequest.mediaTypes.banner && bidRequest.mediaTypes.banner.sizes; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer; if (pageViewId) { data.pv = pageViewId @@ -152,6 +176,10 @@ function buildRequests (validBidRequests, bidderRequest) { if (params.bidfloor) { data.fp = params.bidfloor; } + if (params.inScreenPubID) { + data.pubId = params.inScreenPubID; + data.pi = 2; + } if (params.inScreen) { data.t = params.inScreen; data.pi = 2; @@ -170,6 +198,9 @@ function buildRequests (validBidRequests, bidderRequest) { if (data.gdprApplies) { data.gdprConsent = gdprConsent.consentString; } + if (schain && schain.nodes) { + data.schain = _serializeSupplyChainObj(schain); + } bids.push({ id: bidId, @@ -177,7 +208,7 @@ function buildRequests (validBidRequests, bidderRequest) { tId: transactionId, pi: data.pi, selector: params.selector, - sizes: bidRequest.sizes || bidRequest.mediatype[banner].sizes, + sizes: sizes || bidRequest.sizes, url: BID_ENDPOINT, method: 'GET', data: Object.assign(data, _getBrowserParams(topWindowUrl), _getDigiTrustQueryParams(userId), _getTradeDeskIDParam(userId)) diff --git a/modules/identityLinkIdSystem.js b/modules/identityLinkIdSystem.js index a269799e92a..ffbfe466035 100644 --- a/modules/identityLinkIdSystem.js +++ b/modules/identityLinkIdSystem.js @@ -28,16 +28,19 @@ export const identityLinkSubmodule = { /** * performs action to obtain id and return a value in the callback's response argument * @function + * @param {ConsentData} [consentData] * @param {SubmoduleParams} [configParams] * @returns {IdResponse|undefined} */ - getId(configParams) { + getId(configParams, consentData) { if (!configParams || typeof configParams.pid !== 'string') { utils.logError('identityLink submodule requires partner id to be defined'); return; } + const hasGdpr = (consentData && typeof consentData.gdprApplies === 'boolean' && consentData.gdprApplies) ? 1 : 0; + const gdprConsentString = hasGdpr ? consentData.consentString : ''; // use protocol relative urls for http or https - const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}`; + const url = `https://api.rlcdn.com/api/identity/envelope?pid=${configParams.pid}${hasGdpr ? '&ct=1&cv=' + gdprConsentString : ''}`; let resp; // if ats library is initialised, use it to retrieve envelope. If not use standard third party endpoint if (window.ats) { diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index 4ee2226395b..cd7ab1bd50b 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes'; const BIDDER_CODE = 'improvedigital'; export const spec = { - version: '6.0.0', + version: '6.0.1', code: BIDDER_CODE, aliases: ['id'], supportedMediaTypes: [BANNER, NATIVE, VIDEO], @@ -170,6 +170,12 @@ export const spec = { } }; +function isInstreamVideo(bid) { + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + return bid.mediaType === 'video' || (videoMediaType && context !== 'outstream'); +} + function getNormalizedBidRequest(bid) { let adUnitId = utils.getBidIdParameter('adUnitCode', bid) || null; let placementId = utils.getBidIdParameter('placementId', bid.params) || null; @@ -189,9 +195,7 @@ function getNormalizedBidRequest(bid) { const bidFloorCur = utils.getBidIdParameter('bidFloorCur', bid.params); let normalizedBidRequest = {}; - const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); - const context = utils.deepAccess(bid, 'mediaTypes.video.context'); - if (bid.mediaType === 'video' || (videoMediaType && context !== 'outstream')) { + if (isInstreamVideo(bid)) { normalizedBidRequest.adTypes = [ VIDEO ]; } if (placementId) { @@ -209,7 +213,7 @@ function getNormalizedBidRequest(bid) { normalizedBidRequest.keyValues = keyValues; } - if (config.getConfig('improvedigital.usePrebidSizes') === true && bid.sizes && bid.sizes.length > 0) { + if (config.getConfig('improvedigital.usePrebidSizes') === true && !isInstreamVideo(bid) && bid.sizes && bid.sizes.length > 0) { normalizedBidRequest.format = bid.sizes; } else if (singleSizeFilter && singleSizeFilter.w && singleSizeFilter.h) { normalizedBidRequest.size = {}; diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index a8c98d9d437..f46e3e13f54 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -7,7 +7,7 @@ const CONSTANTS = { SYNC_ENDPOINT: '//k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 1, + PREBID_VERSION: 2, METHOD: 'GET', INVIBES_VENDOR_ID: 436 }; @@ -114,7 +114,9 @@ function buildRequest(bidRequests, bidderRequest) { height: topWin.innerHeight, noc: !cookieDomain, - oi: invibes.optIn + oi: invibes.optIn, + + kw: keywords }; if (invibes.dom.id) { @@ -190,7 +192,7 @@ function handleResponse(responseObj, bidRequests) { for (let i = 0; i < bidRequests.length; i++) { let bidRequest = bidRequests[i]; - if (bidModel.PlacementId === bidRequest.params.placementId) { + if (bidModel.PlacementId == bidRequest.params.placementId) { let size = getBiggerSize(bidRequest.sizes); bidResponses.push({ @@ -444,7 +446,7 @@ let initDomainId = function (options) { let persistence = options.persistence || cookiePersistence; let state; - let minHC = 7; + let minHC = 2; let validGradTime = function (state) { if (!state.cr) { return false; } @@ -468,23 +470,31 @@ let initDomainId = function (options) { state.id = invibes.Uid.generate(); } - let graduate; - - let setId = function () { - invibes.dom = { - id: (!state.cr && invibes.optIn > 0) ? state.id : undefined, - tempId: (invibes.optIn > 0) ? state.id : undefined, - graduate: graduate - }; - }; - - graduate = function () { + let graduate = function () { if (!state.cr) { return; } delete state.cr; delete state.hc; persistence.save(state); setId(); - } + }; + + let regenerateId = function () { + state.id = invibes.Uid.generate(); + persistence.save(state); + }; + + let setId = function () { + invibes.dom = { + get id() { + return (!state.cr && invibes.optIn > 0) ? state.id : undefined; + }, + get tempId() { + return (invibes.optIn > 0) ? state.id : undefined; + }, + graduate: graduate, + regen: regenerateId + }; + }; if (state.cr && !options.noVisit) { if (state.hc < minHC) { @@ -498,6 +508,75 @@ let initDomainId = function (options) { setId(); ivLogger.info('Did=' + invibes.dom.id); }; + +let keywords = (function () { + const cap = 300; + let headTag = document.getElementsByTagName('head')[0]; + let metaTag = headTag ? headTag.getElementsByTagName('meta') : []; + + function parse(str, cap) { + let parsedStr = str.replace(/[<>~|\\"`!@#$%^&*()=+?]/g, ''); + + function onlyUnique(value, index, self) { + return value !== '' && self.indexOf(value) === index; + } + + let words = parsedStr.split(/[\s,;.:]+/); + let uniqueWords = words.filter(onlyUnique); + parsedStr = ''; + + for (let i = 0; i < uniqueWords.length; i++) { + parsedStr += uniqueWords[i]; + if (parsedStr.length >= cap) { + return parsedStr; + } + if (i < uniqueWords.length - 1) { + parsedStr += ','; + } + } + + return parsedStr; + } + + function gt(cap, prefix) { + cap = cap || 300; + prefix = prefix || ''; + let title = document.title || headTag + ? headTag.getElementsByTagName('title')[0] + ? headTag.getElementsByTagName('title')[0].innerHTML + : '' + : ''; + + return parse(prefix + ',' + title, cap); + } + + function gmeta(metaName, cap, prefix) { + metaName = metaName || 'keywords'; + cap = cap || 100; + prefix = prefix || ''; + let fallbackKw = prefix; + + for (let i = 0; i < metaTag.length; i++) { + if (metaTag[i].name && metaTag[i].name.toLowerCase() === metaName.toLowerCase()) { + let kw = prefix + ',' + metaTag[i].content || ''; + return parse(kw, cap); + } else if (metaTag[i].name && metaTag[i].name.toLowerCase().indexOf(metaName.toLowerCase()) > -1) { + fallbackKw = prefix + ',' + metaTag[i].content || ''; + } + } + + return parse(fallbackKw, cap); + } + + let kw = gmeta('keywords', cap); + if (!kw || kw.length < cap - 8) { + kw = gmeta('description', cap, kw); + if (!kw || kw.length < cap - 8) { + kw = gt(cap, kw); + } + } + return kw; +}()); // ===================== export function resetInvibes() { diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js new file mode 100644 index 00000000000..e3bcb39ec4c --- /dev/null +++ b/modules/invisiblyAnalyticsAdapter.js @@ -0,0 +1,224 @@ +/** + * invisiblyAdapterAdapter.js - analytics adapter for Invisibly + */ +import { ajaxBuilder } from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import adapterManager from '../src/adapterManager'; + +const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events'; +const analyticsType = 'endpoint'; +const analyticsName = 'Invisibly Analytics Adapter:'; + +const utils = require('../src/utils'); +const CONSTANTS = require('../src/constants.json'); +const ajax = ajaxBuilder(0); + +// Events needed +const { + EVENTS: { + AUCTION_INIT, + AUCTION_END, + BID_ADJUSTMENT, + BID_TIMEOUT, + BID_REQUESTED, + BID_RESPONSE, + NO_BID, + BID_WON, + BIDDER_DONE, + SET_TARGETING, + REQUEST_BIDS, + ADD_AD_UNITS, + AD_RENDER_FAILED + } +} = CONSTANTS; + +const _VERSION = 1; +const _pageViewId = utils.generateUUID(); +let initOptions = null; +let _startAuction = 0; +let _bidRequestTimeout = 0; +let flushInterval; +let invisiblyAnalyticsEnabled = false; + +const w = window; +const d = document; +let e = d.documentElement; +let g = d.getElementsByTagName('body')[0]; +let x = w.innerWidth || e.clientWidth || g.clientWidth; +let y = w.innerHeight || e.clientHeight || g.clientHeight; + +let _pageView = { + eventType: 'pageView', + userAgent: window.navigator.userAgent, + timestamp: Date.now(), + timezoneOffset: new Date().getTimezoneOffset(), + language: window.navigator.language, + vendor: window.navigator.vendor, + screenWidth: x, + screenHeight: y +}; + +let _eventQueue = [_pageView]; + +let invisiblyAdapter = Object.assign( + adapter({ url: DEFAULT_EVENT_URL, analyticsType }), + { + track({ eventType, args }) { + handleEvent(eventType, args); + }, + sendEvent + } +); + +invisiblyAdapter.originEnableAnalytics = invisiblyAdapter.enableAnalytics; +invisiblyAdapter.enableAnalytics = function(config) { + initOptions = config.options || {}; + initOptions.url = initOptions.url || DEFAULT_EVENT_URL; + if (initOptions.url && initOptions.account) { + invisiblyAnalyticsEnabled = true; + invisiblyAdapter.originEnableAnalytics(config); + } else { + invisiblyAnalyticsEnabled = false; + invisiblyAdapter.originDisableAnalytics(); + } + flushInterval = setInterval(flush, 1000); +}; + +invisiblyAdapter.originDisableAnalytics = invisiblyAdapter.disableAnalytics; +invisiblyAdapter.disableAnalytics = function() { + if (!invisiblyAnalyticsEnabled) { + return; + } + flush(); + clearInterval(flushInterval); + invisiblyAdapter.originDisableAnalytics(); +}; + +function flush() { + if (!invisiblyAnalyticsEnabled) { + return; + } + + if (_eventQueue.length > 0) { + while (_eventQueue.length) { + let eventFromQue = _eventQueue.shift(); + let eventtype = 'PREBID_' + eventFromQue.eventType; + delete eventFromQue.eventType; + + let data = { + pageViewId: _pageViewId, + ver: _VERSION, + bundleId: initOptions.bundleId, + ...eventFromQue + }; + + let payload = { + event_type: eventtype, + event_data: { ...data } + }; + ajax( + initOptions.url, + () => utils.logInfo(`${analyticsName} sent events batch`), + JSON.stringify(payload), + { + contentType: 'application/json', + method: 'POST', + withCredentials: true + } + ); + } + } +} + +function handleEvent(eventType, eventArgs) { + eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; + let invisiblyEvent = {}; + + switch (eventType) { + case AUCTION_INIT: { + invisiblyEvent = eventArgs; + _startAuction = invisiblyEvent.timestamp; + _bidRequestTimeout = invisiblyEvent.timeout; + break; + } + case AUCTION_END: { + invisiblyEvent = eventArgs; + invisiblyEvent.start = _startAuction; + invisiblyEvent.end = Date.now(); + break; + } + case BID_ADJUSTMENT: { + invisiblyEvent.bidders = eventArgs; + break; + } + case BID_TIMEOUT: { + invisiblyEvent.bidders = eventArgs; + invisiblyEvent.duration = _bidRequestTimeout; + break; + } + case BID_REQUESTED: { + invisiblyEvent = eventArgs; + break; + } + case BID_RESPONSE: { + invisiblyEvent = eventArgs; + break; + } + case NO_BID: { + invisiblyEvent.noBid = eventArgs; + break; + } + case BID_WON: { + invisiblyEvent = eventArgs; + break; + } + case BIDDER_DONE: { + invisiblyEvent = eventArgs; + break; + } + case SET_TARGETING: { + invisiblyEvent.targetings = eventArgs; + break; + } + case REQUEST_BIDS: { + invisiblyEvent = eventArgs; + break; + } + case ADD_AD_UNITS: { + invisiblyEvent = eventArgs; + break; + } + case AD_RENDER_FAILED: { + invisiblyEvent = eventArgs; + break; + } + default: + return; + } + invisiblyEvent.eventType = eventType; + invisiblyEvent.timestamp = invisiblyEvent.timestamp || Date.now(); + sendEvent(invisiblyEvent); +} + +function sendEvent(event) { + _eventQueue.push(event); + utils.logInfo(`${analyticsName}Event ${event.eventType}:`, event); + + if (event.eventType === AUCTION_END) { + flush(); + clearInterval(flushInterval); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: invisiblyAdapter, + code: 'invisiblyAnalytics' +}); + +invisiblyAdapter.getOptions = function() { + return initOptions; +}; + +invisiblyAdapter.flush = flush; + +export default invisiblyAdapter; diff --git a/modules/invisiblyAnalyticsAdapter.md b/modules/invisiblyAnalyticsAdapter.md new file mode 100644 index 00000000000..1023e6baf96 --- /dev/null +++ b/modules/invisiblyAnalyticsAdapter.md @@ -0,0 +1,24 @@ +# Overview + +``` +Module Name: Invisibly Analytics + +Module Type: Analytics Adapter + +Maintainer: sanjay.rawlani@invisibly.com +``` + +# Description + +Analytics adapter for Invisibly. Please contact: sanjay.rawlani@invisibly.com for any additional information. Official website link to the vendor: https://invisibly.com/ + +# Test Parameters + +``` +{ + provider: 'invisiblyAnalytics', + options : { + account: 'invisibly' //account is a mandatory input to adapter configuration + } +} +``` diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index 85d3be2f0ec..6ca5aaf1b65 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -1,36 +1,72 @@ import * as utils from '../src/utils'; -import { BANNER } from '../src/mediaTypes'; +import { BANNER, VIDEO } from '../src/mediaTypes'; +import find from 'core-js/library/fn/array/find'; import { config } from '../src/config'; import isInteger from 'core-js/library/fn/number/is-integer'; import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'ix'; -const BANNER_SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; -const SUPPORTED_AD_TYPES = [BANNER]; -const ENDPOINT_VERSION = 7.2; +const SECURE_BID_URL = 'https://as-sec.casalemedia.com/cygnus'; +const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +const BANNER_ENDPOINT_VERSION = 7.2; +const VIDEO_ENDPOINT_VERSION = 8.1; const CENT_TO_DOLLAR_FACTOR = 100; -const TIME_TO_LIVE = 35; +const BANNER_TIME_TO_LIVE = 35; +const VIDEO_TIME_TO_LIVE = 3600; // 1hr const NET_REVENUE = true; const PRICE_TO_DOLLAR_FACTOR = { JPY: 1 }; /** - * Transform valid bid request config object to impression object that will be sent to ad server. + * Transform valid bid request config object to banner impression object that will be sent to ad server. * * @param {object} bid A valid bid request config object. * @return {object} A impression object that will be sent to ad server. */ function bidToBannerImp(bid) { - const imp = {}; - - imp.id = bid.bidId; + const imp = bidToImp(bid); imp.banner = {}; imp.banner.w = bid.params.size[0]; imp.banner.h = bid.params.size[1]; imp.banner.topframe = utils.inIframe() ? 0 : 1; + return imp; +} + +/** + * Transform valid bid request config object to video impression object that will be sent to ad server. + * + * @param {object} bid A valid bid request config object. + * @return {object} A impression object that will be sent to ad server. + */ +function bidToVideoImp(bid) { + const imp = bidToImp(bid); + + imp.video = utils.deepClone(bid.params.video) + imp.video.w = bid.params.size[0]; + imp.video.h = bid.params.size[1]; + + const context = utils.deepAccess(bid, 'mediaTypes.video.context'); + if (context) { + if (context === 'instream') { + imp.video.placement = 1; + } else if (context === 'outstream') { + imp.video.placement = 4; + } else { + utils.logWarn(`ix bidder params: video context '${context}' is not supported`); + } + } + + return imp; +} + +function bidToImp(bid) { + const imp = {}; + + imp.id = bid.bidId; + imp.ext = {}; imp.ext.siteID = bid.params.siteId; @@ -56,7 +92,7 @@ function bidToBannerImp(bid) { * @param {string} currency Global currency in bid response. * @return {object} bid The parsed bid. */ -function parseBid(rawBid, currency) { +function parseBid(rawBid, currency, bidRequest) { const bid = {}; if (PRICE_TO_DOLLAR_FACTOR.hasOwnProperty(currency)) { @@ -66,15 +102,27 @@ function parseBid(rawBid, currency) { } bid.requestId = rawBid.impid; - bid.width = rawBid.w; - bid.height = rawBid.h; - bid.ad = rawBid.adm; + bid.dealId = utils.deepAccess(rawBid, 'ext.dealid'); - bid.ttl = TIME_TO_LIVE; bid.netRevenue = NET_REVENUE; bid.currency = currency; bid.creativeId = rawBid.hasOwnProperty('crid') ? rawBid.crid : '-'; + // in the event of a video + if (utils.deepAccess(rawBid, 'ext.vasturl')) { + bid.vastUrl = rawBid.ext.vasturl + bid.width = bidRequest.video.w; + bid.height = bidRequest.video.h; + bid.mediaType = VIDEO; + bid.ttl = VIDEO_TIME_TO_LIVE; + } else { + bid.ad = rawBid.adm; + bid.width = rawBid.w; + bid.height = rawBid.h; + bid.mediaType = BANNER; + bid.ttl = BANNER_TIME_TO_LIVE; + } + bid.meta = {}; bid.meta.networkId = utils.deepAccess(rawBid, 'ext.dspid'); bid.meta.brandId = utils.deepAccess(rawBid, 'ext.advbrandid'); @@ -131,6 +179,138 @@ function isValidBidFloorParams(bidFloor, bidFloorCur) { bidFloorCur.match(curRegex)); } +/** + * Finds the impression with the associated id. + * + * @param {*} id Id of the impression. + * @param {array} impressions List of impressions sent in the request. + * @return {object} The impression with the associated id. + */ +function getBidRequest(id, impressions) { + if (!id) { + return; + } + return find(impressions, imp => imp.id === id); +} + +/** + * Builds a request object to be sent to the ad server based on bid requests. + * + * @param {array} validBidRequests A list of valid bid request config objects. + * @param {object} bidderRequest An object containing other info like gdprConsent. + * @param {array} impressions List of impression objects describing the bids. + * @param {array} version Endpoint version denoting banner or video. + * @return {object} Info describing the request to the server. + * + */ +function buildRequest(validBidRequests, bidderRequest, impressions, version) { + const userEids = []; + + // Always use secure HTTPS protocol. + let baseUrl = SECURE_BID_URL; + + // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded + // and if the data for the partner exist + if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { + let identityInfo = window.headertag.getIdentityInfo(); + if (identityInfo && typeof identityInfo === 'object') { + for (const partnerName in identityInfo) { + if (identityInfo.hasOwnProperty(partnerName)) { + let response = identityInfo[partnerName]; + if (!response.responsePending && response.data && typeof response.data === 'object' && Object.keys(response.data).length) { + userEids.push(response.data); + } + } + } + } + } + const r = {}; + + // Since bidderRequestId are the same for different bid request, just use the first one. + r.id = validBidRequests[0].bidderRequestId; + + r.imp = impressions; + + r.site = {}; + r.ext = {}; + r.ext.source = 'prebid'; + if (userEids.length > 0) { + r.user = {}; + r.user.eids = userEids; + } + + if (document.referrer && document.referrer !== '') { + r.site.ref = document.referrer; + } + + // Apply GDPR information to the request if GDPR is enabled. + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + const gdprConsent = bidderRequest.gdprConsent; + + if (gdprConsent.hasOwnProperty('gdprApplies')) { + r.regs = { + ext: { + gdpr: gdprConsent.gdprApplies ? 1 : 0 + } + }; + } + + if (gdprConsent.hasOwnProperty('consentString')) { + r.user = r.user || {}; + r.user.ext = { + consent: gdprConsent.consentString || '' + }; + } + } + + if (bidderRequest.refererInfo) { + r.site.page = bidderRequest.refererInfo.referer; + } + } + + const payload = {}; + + // Parse additional runtime configs. + const otherIxConfig = config.getConfig('ix'); + if (otherIxConfig) { + // Append firstPartyData to r.site.page if firstPartyData exists. + if (typeof otherIxConfig.firstPartyData === 'object') { + const firstPartyData = otherIxConfig.firstPartyData; + let firstPartyString = '?'; + for (const key in firstPartyData) { + if (firstPartyData.hasOwnProperty(key)) { + firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`; + } + } + firstPartyString = firstPartyString.slice(0, -1); + + r.site.page += firstPartyString; + } + + // Create t in payload if timeout is configured. + if (typeof otherIxConfig.timeout === 'number') { + payload.t = otherIxConfig.timeout; + } + } + + // Use the siteId in the first bid request as the main siteId. + payload.s = validBidRequests[0].params.siteId; + payload.v = version; + payload.r = JSON.stringify(r); + payload.ac = 'j'; + payload.sd = 1; + if (version === VIDEO_ENDPOINT_VERSION) { + payload.nf = 1; + } + + return { + method: 'GET', + url: baseUrl, + data: payload + }; +} + export const spec = { code: BIDDER_CODE, @@ -144,22 +324,25 @@ export const spec = { */ isBidRequestValid: function (bid) { if (!isValidSize(bid.params.size)) { + utils.logError('ix bidder params: bid size has invalid format.'); return false; } if (!includesSize(bid.sizes, bid.params.size)) { + utils.logError('ix bidder params: bid size is not included in ad unit sizes.'); return false; } - if (bid.hasOwnProperty('mediaType') && bid.mediaType !== 'banner') { + if (bid.hasOwnProperty('mediaType') && !(utils.contains(SUPPORTED_AD_TYPES, bid.mediaType))) { return false; } - if (bid.hasOwnProperty('mediaTypes') && !utils.deepAccess(bid, 'mediaTypes.banner.sizes')) { + if (bid.hasOwnProperty('mediaTypes') && !(utils.deepAccess(bid, 'mediaTypes.banner.sizes') || utils.deepAccess(bid, 'mediaTypes.video.playerSize'))) { return false; } if (typeof bid.params.siteId !== 'string' && typeof bid.params.siteId !== 'number') { + utils.logError('ix bidder params: siteId must be string or number value.'); return false; } @@ -167,8 +350,10 @@ export const spec = { const hasBidFloorCur = bid.params.hasOwnProperty('bidFloorCur'); if (hasBidFloor || hasBidFloorCur) { - return hasBidFloor && hasBidFloorCur && - isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur); + if (!(hasBidFloor && hasBidFloorCur && isValidBidFloorParams(bid.params.bidFloor, bid.params.bidFloorCur))) { + utils.logError('ix bidder params: bidFloor / bidFloorCur parameter has invalid format.'); + return false; + } } return true; @@ -178,132 +363,50 @@ export const spec = { * Make a server request from the list of BidRequests. * * @param {array} validBidRequests A list of valid bid request config objects. - * @param {object} options A object contains bids and other info like gdprConsent. + * @param {object} bidderRequest A object contains bids and other info like gdprConsent. * @return {object} Info describing the request to the server. */ - buildRequests: function (validBidRequests, options) { - const bannerImps = []; - const userEids = []; + buildRequests: function (validBidRequests, bidderRequest) { + let reqs = []; + let bannerImps = []; + let videoImps = []; let validBidRequest = null; - let bannerImp = null; - - // Always use secure HTTPS protocol. - let baseUrl = BANNER_SECURE_BID_URL; for (let i = 0; i < validBidRequests.length; i++) { validBidRequest = validBidRequests[i]; - // Transform the bid request based on the banner format. - bannerImp = bidToBannerImp(validBidRequest); - bannerImps.push(bannerImp); - } - - // RTI ids will be included in the bid request if the function getIdentityInfo() is loaded - // and if the data for the partner exist - if (window.headertag && typeof window.headertag.getIdentityInfo === 'function') { - let identityInfo = window.headertag.getIdentityInfo(); - if (identityInfo && typeof identityInfo === 'object') { - for (const partnerName in identityInfo) { - if (identityInfo.hasOwnProperty(partnerName)) { - let response = identityInfo[partnerName]; - if (!response.responsePending && response.data && typeof response.data === 'object' && Object.keys(response.data).length) { - userEids.push(response.data); - } - } + if (validBidRequest.mediaType === VIDEO || utils.deepAccess(validBidRequest, 'mediaTypes.video')) { + if (validBidRequest.mediaType === VIDEO || includesSize(validBidRequest.mediaTypes.video.playerSize, validBidRequest.params.size)) { + videoImps.push(bidToVideoImp(validBidRequest)); + } else { + utils.logError('Bid size is not included in video playerSize') } } - } - const r = {}; - - // Since bidderRequestId are the same for different bid request, just use the first one. - r.id = validBidRequests[0].bidderRequestId; - - r.imp = bannerImps; - r.site = {}; - r.ext = {}; - r.ext.source = 'prebid'; - if (userEids.length > 0) { - r.user = {}; - r.user.eids = userEids; - } - if (document.referrer && document.referrer !== '') { - r.site.ref = document.referrer; - } - - // Apply GDPR information to the request if GDPR is enabled. - if (options) { - if (options.gdprConsent) { - const gdprConsent = options.gdprConsent; - - if (gdprConsent.hasOwnProperty('gdprApplies')) { - r.regs = { - ext: { - gdpr: gdprConsent.gdprApplies ? 1 : 0 - } - }; - } - - if (gdprConsent.hasOwnProperty('consentString')) { - r.user = r.user || {}; - r.user.ext = { - consent: gdprConsent.consentString || '' - }; - } - } - - if (options.refererInfo) { - r.site.page = options.refererInfo.referer; + if (validBidRequest.mediaType === BANNER || utils.deepAccess(validBidRequest, 'mediaTypes.banner') || + (!validBidRequest.mediaType && !validBidRequest.mediaTypes)) { + bannerImps.push(bidToBannerImp(validBidRequest)); } } - const payload = {}; - - // Parse additional runtime configs. - const otherIxConfig = config.getConfig('ix'); - if (otherIxConfig) { - // Append firstPartyData to r.site.page if firstPartyData exists. - if (typeof otherIxConfig.firstPartyData === 'object') { - const firstPartyData = otherIxConfig.firstPartyData; - let firstPartyString = '?'; - for (const key in firstPartyData) { - if (firstPartyData.hasOwnProperty(key)) { - firstPartyString += `${encodeURIComponent(key)}=${encodeURIComponent(firstPartyData[key])}&`; - } - } - firstPartyString = firstPartyString.slice(0, -1); - - r.site.page += firstPartyString; - } - - // Create t in payload if timeout is configured. - if (typeof otherIxConfig.timeout === 'number') { - payload.t = otherIxConfig.timeout; - } + if (bannerImps.length > 0) { + reqs.push(buildRequest(validBidRequests, bidderRequest, bannerImps, BANNER_ENDPOINT_VERSION)); + } + if (videoImps.length > 0) { + reqs.push(buildRequest(validBidRequests, bidderRequest, videoImps, VIDEO_ENDPOINT_VERSION)); } - // Use the siteId in the first bid request as the main siteId. - payload.s = validBidRequests[0].params.siteId; - - payload.v = ENDPOINT_VERSION; - payload.r = JSON.stringify(r); - payload.ac = 'j'; - payload.sd = 1; - - return { - method: 'GET', - url: baseUrl, - data: payload - }; + return reqs; }, /** * Unpack the response from the server into a list of bids. * * @param {object} serverResponse A successful response from the server. + * @param {object} bidderRequest The bid request sent to the server. * @return {array} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse) { + interpretResponse: function (serverResponse, bidderRequest) { const bids = []; let bid = null; @@ -320,8 +423,11 @@ export const spec = { // Transform rawBid in bid response to the format that will be accepted by prebid. const innerBids = seatbid[i].bid; + let requestBid = JSON.parse(bidderRequest.data.r); + for (let j = 0; j < innerBids.length; j++) { - bid = parseBid(innerBids[j], responseBody.cur); + const bidRequest = getBidRequest(innerBids[j].impid, requestBid.imp); + bid = parseBid(innerBids[j], responseBody.cur, bidRequest); bids.push(bid); } } diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index e99c42408f2..d343b007c5b 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -42,16 +42,21 @@ var adUnits = [{ ```javascript var adUnits = [{ // ... - mediaTypes: { banner: { sizes: [ [300, 250], [300, 600] ] + }, + video: { + context: 'instream', + playerSize: [ + [300, 250], + [300, 600] + ] } - } - + }, // ... }]; ``` @@ -61,7 +66,7 @@ var adUnits = [{ | Type | Support | --- | --- | Banner | Fully supported for all IX approved sizes. -| Video | Not supported. +| Video | Fully supported for all IX approved sizes. | Native | Not supported. # Bid Parameters @@ -76,6 +81,17 @@ object are detailed here. | siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` | size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.banner.sizes`. Examples: `[300, 250]`, `[300, 600]`, `[728, 90]` +### Video + +| Key | Scope | Type | Description +| --- | --- | --- | --- +| siteId | Required | String | An IX-specific identifier that is associated with a specific size on this ad unit. This is similar to a placement ID or an ad unit ID that some other modules have. Examples: `'3723'`, `'6482'`, `'3639'` +| size | Required | Number[] | The single size associated with the site ID. It should be one of the sizes listed in the ad unit under `adUnits[].sizes` or `adUnits[].mediaTypes.video.playerSize`. Examples: `[300, 250]`, `[300, 600]` +| video | Required | Hash | The video object will serve as the properties of the video ad. You can create any field under the video object that is mentioned in the `OpenRTB Spec v2.5`. Some fields like `mimes, protocols, minduration, maxduration` are required. +| video.mimes | Required | String[] | Array list of content MIME types supported. Popular MIME types include, but are not limited to, `"video/x-ms- wmv"` for Windows Media and `"video/x-flv"` for Flash Video. +|video.minduration| Required | Integer | Minimum video ad duration in seconds. +|video.maxduration| Required | Integer | Maximum video ad duration in seconds. +|video.protocol / video.protocols| Required | Integer / Integer[] | Either a single protocol provided as an integer, or protocols provided as a list of integers. `2` - VAST 2.0, `3` - VAST 3.0, `5` - VAST 2.0 Wrapper, `6` - VAST 3.0 Wrapper Setup Guide @@ -84,7 +100,9 @@ Setup Guide Follow these steps to configure and add the IX module to your Prebid.js integration. -The examples in this guide assume the following starting configuration: +The examples in this guide assume the following starting configuration (you may remove banner or video, if either does not apply). + +In regards to video, `context` can either be `'instream'` or `'outstream'`. Note that `outstream` requires additional configuration on the adUnit. ```javascript var adUnits = [{ @@ -98,6 +116,19 @@ var adUnits = [{ } }, bids: [] +}, +{ + code: 'video-div-a', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [] }]; ``` @@ -119,7 +150,9 @@ bid objects under `adUnits[].bids`: Set `params.siteId` and `params.size` in each bid object to the values provided by your IX representative. -**Example** +**Examples** + +**Banner:** ```javascript var adUnits = [{ code: 'banner-div-a', @@ -146,18 +179,92 @@ var adUnits = [{ }] }]; ``` - +**Video (Instream):** +```javascript +var adUnits = [{ + code: 'video-div-a', + mediaTypes: { + video: { + context: 'instream', + playerSize: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [{ + bidder: 'ix', + params: { + siteId: '12345', + size: [300, 250], + video: { + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [6] + } + } + }, { + bidder: 'ix', + params: { + siteId: '12345', + size: [300, 600], + video: { + // openrtb v2.5 compatible video obj + } + } + }] +}]; +``` Please note that you can re-use the existing `siteId` within the same flex position. +**Video (Outstream):** +Note that currently, outstream video rendering must be configured by the publisher. In the adUnit, a `renderer` object must be defined, which includes a `url` pointing to the video rendering script, and a `render` function for creating the video player. See http://prebid.org/dev-docs/show-outstream-video-ads.html for more information. +```javascript +var adUnits = [{ + code: 'video-div-a', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [[300, 250]] + } + }, + renderer: { + url: 'https://test.com/my-video-player.js', + render: function (bid) { + ... + } + }, + bids: [{ + bidder: 'ix', + params: { + siteId: '12345', + size: [300, 250], + video: { + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0, + maxduration: 60, + protocols: [6] + } + } + }] +}]; +``` ##### 2. Include `ixBidAdapter` in your build process -When running the build command, include `ixBidAdapter` as a module. +When running the build command, include `ixBidAdapter` as a module, as well as `dfpAdServerVideo` if you require video support. ``` -gulp build --modules=ixBidAdapter,fooBidAdapter,bazBidAdapter +gulp build --modules=ixBidAdapter,dfpAdServerVideo,fooBidAdapter,bazBidAdapter ``` If a JSON file is being used to specify the bidder modules, add `"ixBidAdapter"` @@ -166,6 +273,7 @@ to the top-level array in that file. ```json [ "ixBidAdapter", + "dfpAdServerVideo", "fooBidAdapter", "bazBidAdapter" ] @@ -210,9 +318,7 @@ changes will be reflected in any proceeding bid requests. Setting a Server Side Timeout ============================= -Setting a server-side timeout allows you to control the max length of time the -servers will wait on DSPs to respond before generating the final bid response -and returning it to this module. +Setting a server-side timeout allows you to control the max length of time taken to connect to the server. The default value when unspecified is 50ms. This is distinctly different from the global bidder timeout that can be set in Prebid.js in the browser. diff --git a/modules/justpremiumBidAdapter.js b/modules/justpremiumBidAdapter.js index 68b73c333ca..a7aebae0fd9 100644 --- a/modules/justpremiumBidAdapter.js +++ b/modules/justpremiumBidAdapter.js @@ -2,12 +2,9 @@ import { registerBidder } from '../src/adapters/bidderFactory' import { deepAccess } from '../src/utils'; const BIDDER_CODE = 'justpremium' -const ENDPOINT_URL = '//pre.ads.justpremium.com/v/2.0/t/xhr' -const JP_ADAPTER_VERSION = '1.4' +const ENDPOINT_URL = 'https://pre.ads.justpremium.com/v/2.0/t/xhr' +const JP_ADAPTER_VERSION = '1.6' const pixels = [] -const TRACK_START_TIME = Date.now() -let LAST_PAYLOAD = {} -let AD_UNIT_IDS = [] export const spec = { code: BIDDER_CODE, @@ -20,11 +17,6 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { const c = preparePubCond(validBidRequests) const dim = getWebsiteDim() - AD_UNIT_IDS = validBidRequests.map(b => { - return b.adUnitCode - }).filter((value, index, self) => { - return self.indexOf(value) === index - }) const payload = { zone: validBidRequests.map(b => { return parseInt(b.params.zone) @@ -44,7 +36,7 @@ export const spec = { const zone = b.params.zone const sizes = payload.sizes sizes[zone] = sizes[zone] || [] - sizes[zone].push.apply(sizes[zone], b.sizes) + sizes[zone].push.apply(sizes[zone], b.mediaTypes && b.mediaTypes.banner && b.mediaTypes.banner.sizes) }) if (deepAccess(validBidRequests[0], 'userId.pubcid')) { @@ -69,8 +61,6 @@ export const spec = { const payloadString = JSON.stringify(payload) - LAST_PAYLOAD = payload - return { method: 'POST', url: ENDPOINT_URL + '?i=' + (+new Date()), @@ -85,7 +75,7 @@ export const spec = { bidRequests.bids.forEach(adUnit => { let bid = findBid(adUnit.params, body.bid) if (bid) { - let size = (adUnit.sizes && adUnit.sizes.length && adUnit.sizes[0]) || [] + let size = (adUnit.mediaTypes && adUnit.mediaTypes.banner && adUnit.mediaTypes.banner.sizes && adUnit.mediaTypes.banner.sizes.length && adUnit.mediaTypes.banner.sizes[0]) || [] let bidResponse = { requestId: adUnit.bidId, creativeId: bid.id, @@ -101,12 +91,11 @@ export const spec = { bidResponses.push(bidResponse) } }) - return bidResponses }, getUserSyncs: function getUserSyncs(syncOptions, responses, gdprConsent) { - let url = '//pre.ads.justpremium.com/v/1.0/t/sync' + '?_c=' + 'a' + Math.random().toString(36).substring(7) + Date.now(); + let url = 'https://pre.ads.justpremium.com/v/1.0/t/sync' + '?_c=' + 'a' + Math.random().toString(36).substring(7) + Date.now(); if (gdprConsent && (typeof gdprConsent.gdprApplies === 'boolean')) { url = url + '&consentString=' + encodeURIComponent(gdprConsent.consentString) } @@ -118,53 +107,6 @@ export const spec = { } return pixels }, - - onTimeout: (timeoutData) => { - timeoutData.forEach((data) => { - if (AD_UNIT_IDS.indexOf(data.adUnitCode) != -1) { - track(data, LAST_PAYLOAD, 'btm') - } - }) - }, - -} - -export let pixel = { - fire(url) { - let img = document.createElement('img') - img.src = url - img.id = 'jp-pixel-track' - img.style.cssText = 'display:none !important;' - document.body.appendChild(img) - } -}; - -function track (data, payload, type) { - let pubUrl = '' - - let jp = { - auc: data.adUnitCode, - to: data.timeout - } - - if (window.top == window) { - pubUrl = window.location.href - } else { - try { - pubUrl = window.top.location.href - } catch (e) { - pubUrl = document.referrer - } - } - - let duration = Date.now() - TRACK_START_TIME - - const pixelUrl = `//emea-v3.tracking.justpremium.com/tracking.gif?rid=&sid=&uid=&vr=& -ru=${encodeURIComponent(pubUrl)}&tt=&siw=&sh=${payload.sh}&sw=${payload.sw}&wh=${payload.wh}&ww=${payload.ww}&an=&vn=& -sd=&_c=&et=&aid=&said=&ei=&fc=&sp=&at=bidder&cid=&ist=&mg=&dl=&dlt=&ev=&vt=&zid=${payload.id}&dr=${duration}&di=&pr=& -cw=&ch=&nt=&st=&jp=${encodeURIComponent(JSON.stringify(jp))}&ty=${type}` - - pixel.fire(pixelUrl); } function findBid (params, bids) { diff --git a/modules/justpremiumBidAdapter.md b/modules/justpremiumBidAdapter.md index c3e17abfec5..45dcb7b7f99 100644 --- a/modules/justpremiumBidAdapter.md +++ b/modules/justpremiumBidAdapter.md @@ -12,7 +12,11 @@ To get more information or your unique zone id please contact Justpremium. ``` var adUnits = [ { - sizes: [[1, 1]], + mediaTypes: { + banner: { + sizes: [[1, 1]] + } + }, code: 'div-gpt-ad-1471513102552-0', bids: [ { @@ -25,7 +29,11 @@ To get more information or your unique zone id please contact Justpremium. ] }, { - sizes: [[300, 600]], + mediaTypes: { + banner: { + sizes: [[300, 600]] + } + }, code: 'div-gpt-ad-1471513102552-1', bids: [ { diff --git a/modules/lockerdomeBidAdapter.js b/modules/lockerdomeBidAdapter.js index 3832ed20d57..ce9deedd160 100644 --- a/modules/lockerdomeBidAdapter.js +++ b/modules/lockerdomeBidAdapter.js @@ -9,13 +9,16 @@ export const spec = { return !!bid.params.adUnitId; }, buildRequests: function(bidRequests, bidderRequest) { + let schain; + const adUnitBidRequests = bidRequests.map(function (bid) { + if (bid.schain) schain = schain || bid.schain; return { requestId: bid.bidId, adUnitCode: bid.adUnitCode, adUnitId: utils.getBidIdParameter('adUnitId', bid.params), sizes: bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes - } + }; }); const bidderRequestCanonicalUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.canonicalUrl) || ''; @@ -25,12 +28,21 @@ export const spec = { url: encodeURIComponent(bidderRequestCanonicalUrl), referrer: encodeURIComponent(bidderRequestReferer) }; - - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr = { - applies: bidderRequest.gdprConsent.gdprApplies, - consent: bidderRequest.gdprConsent.consentString - }; + if (schain) { + payload.schain = schain; + } + if (bidderRequest) { + if (bidderRequest.gdprConsent) { + payload.gdpr = { + applies: bidderRequest.gdprConsent.gdprApplies, + consent: bidderRequest.gdprConsent.consentString + }; + } + if (bidderRequest.uspConsent) { + payload.us_privacy = { + consent: bidderRequest.uspConsent + } + } } const payloadString = JSON.stringify(payload); @@ -58,5 +70,5 @@ export const spec = { }; }); }, -} +}; registerBidder(spec); diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index 8236fb0888c..27a20af398c 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -3,9 +3,10 @@ import * as utils from '../src/utils'; import { config } from '../src/config'; import * as url from '../src/url'; import { BANNER, NATIVE } from '../src/mediaTypes'; +import { getRefererInfo } from '../src/refererDetection'; const BIDDER_CODE = 'medianet'; -const BID_URL = '//prebid.media.net/rtb/prebid'; +const BID_URL = 'https://prebid.media.net/rtb/prebid'; const SLOT_VISIBILITY = { NOT_DETERMINED: 0, ABOVE_THE_FOLD: 1, @@ -17,16 +18,32 @@ const EVENTS = { }; const EVENT_PIXEL_URL = 'qsearch-a.akamaihd.net/log'; +let refererInfo = getRefererInfo(); + let mnData = {}; +mnData.urlData = { + domain: url.parse(refererInfo.referer).host, + page: refererInfo.referer, + isTop: refererInfo.reachedTop +} $$PREBID_GLOBAL$$.medianetGlobals = {}; +function getTopWindowReferrer() { + try { + return window.top.document.referrer; + } catch (e) { + return document.referrer; + } +} + function siteDetails(site) { site = site || {}; let siteData = { - domain: site.domain || utils.getTopWindowLocation().host, - page: site.page || utils.getTopWindowUrl(), - ref: site.ref || utils.getTopWindowReferrer() + domain: site.domain || mnData.urlData.domain, + page: site.page || mnData.urlData.page, + ref: site.ref || getTopWindowReferrer(), + isTop: site.isTop || mnData.urlData.isTop }; return Object.assign(siteData, getPageMeta()); @@ -141,8 +158,10 @@ function slotParams(bidRequest) { }, all: bidRequest.params }; - if (bidRequest.sizes.length > 0) { - params.banner = transformSizes(bidRequest.sizes); + let bannerSizes = utils.deepAccess(bidRequest, 'mediaTypes.banner.sizes') || bidRequest.sizes || []; + + if (bannerSizes.length > 0) { + params.banner = transformSizes(bannerSizes); } if (bidRequest.nativeParams) { try { @@ -252,8 +271,9 @@ function getLoggingData(event, data) { params.cid = $$PREBID_GLOBAL$$.medianetGlobals.cid || ''; params.crid = data.map((adunit) => utils.deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); params.adunit_count = data.length || 0; - params.dn = utils.getTopWindowLocation().host || ''; - params.requrl = utils.getTopWindowUrl() || ''; + params.dn = mnData.urlData.domain || ''; + params.requrl = mnData.urlData.page || ''; + params.istop = mnData.urlData.isTop || ''; params.event = event.name || ''; params.value = event.value || ''; params.rd = event.related_data || ''; diff --git a/modules/mobsmartBidAdapter.js b/modules/mobsmartBidAdapter.js new file mode 100644 index 00000000000..ba02aef38ef --- /dev/null +++ b/modules/mobsmartBidAdapter.js @@ -0,0 +1,94 @@ +import { registerBidder } from '../src/adapters/bidderFactory'; +import { config } from '../src/config'; + +const BIDDER_CODE = 'mobsmart'; +const ENDPOINT = 'https://prebid.mobsmart.net/prebid/endpoint'; + +export const spec = { + code: BIDDER_CODE, + isBidRequestValid: function(bid) { + if (bid.bidder !== BIDDER_CODE) { + return false; + } + + return true; + }, + buildRequests: function(validBidRequests, bidderRequest) { + const timeout = config.getConfig('bidderTimeout'); + const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + + return validBidRequests.map(bidRequest => { + const adUnit = { + code: bidRequest.adUnitCode, + bids: { + bidder: bidRequest.bidder, + params: bidRequest.params + }, + mediaTypes: bidRequest.mediaTypes + }; + + if (bidRequest.hasOwnProperty('sizes') && bidRequest.sizes.length > 0) { + adUnit.sizes = bidRequest.sizes; + } + + const request = { + auctionId: bidRequest.auctionId, + requestId: bidRequest.bidId, + bidRequestsCount: bidRequest.bidRequestsCount, + bidderRequestId: bidRequest.bidderRequestId, + transactionId: bidRequest.transactionId, + referrer: referrer, + timeout: timeout, + adUnit: adUnit + }; + + if (bidRequest.userId && bidRequest.userId.pubcid) { + request.userId = {pubcid: bidRequest.userId.pubcid}; + } + + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(request) + } + }); + }, + interpretResponse: function(serverResponse) { + const bidResponses = []; + + if (serverResponse.body) { + const response = serverResponse.body; + const bidResponse = { + requestId: response.requestId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creativeId, + currency: response.currency, + netRevenue: response.netRevenue, + ttl: response.ttl, + ad: response.ad, + }; + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + let syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: 'https://tags.mobsmart.net/tags/iframe' + }); + } else if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: 'https://tags.mobsmart.net/tags/image' + }); + } + + return syncs; + } +} +registerBidder(spec); diff --git a/modules/mobsmartBidAdapter.md b/modules/mobsmartBidAdapter.md new file mode 100644 index 00000000000..1240d6db494 --- /dev/null +++ b/modules/mobsmartBidAdapter.md @@ -0,0 +1,50 @@ +# Overview + +``` +Module Name: Mobsmart Bidder Adapter +Module Type: Bidder Adapter +Maintainer: adx@kpis.jp +``` + +# Description + +Module that connects to Mobsmart demand sources to fetch bids. + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "mobsmart", + params: { + floorPrice: 100, + currency: 'JPY' + } + } + ] + },{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[320, 50]], // a mobile size + } + }, + bids: [ + { + bidder: "mobsmart", + params: { + floorPrice: 90, + currency: 'JPY' + } + } + ] + } + ]; +``` diff --git a/modules/nanointeractiveBidAdapter.js b/modules/nanointeractiveBidAdapter.js index a76cc90ac10..13944cd5a35 100644 --- a/modules/nanointeractiveBidAdapter.js +++ b/modules/nanointeractiveBidAdapter.js @@ -14,6 +14,8 @@ export const SUB_ID = 'subId'; export const REF = 'ref'; export const LOCATION = 'loc'; +var nanoPid = '5a1ec660eb0a191dfa591172'; + export const spec = { code: BIDDER_CODE, @@ -29,7 +31,7 @@ export const spec = { validBidRequests.forEach( bid => payload.push(createSingleBidRequest(bid, bidderRequest)) ); - const url = getEndpointUrl('main') + '/hb'; + const url = getEndpointUrl() + '/hb'; return { method: 'POST', @@ -45,6 +47,23 @@ export const spec = { } }); return bids; + }, + getUserSyncs: function(syncOptions) { + const syncs = []; + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: getEndpointUrl() + '/hb/cookieSync/' + nanoPid + }); + } + + if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: getEndpointUrl() + '/hb/cookieSync/' + nanoPid + }); + } + return syncs; } }; @@ -52,6 +71,9 @@ export const spec = { function createSingleBidRequest(bid, bidderRequest) { const location = utils.deepAccess(bidderRequest, 'refererInfo.referer'); const origin = utils.getOrigin(); + + nanoPid = bid.params[SSP_PLACEMENT_ID] || nanoPid; + const data = { [SSP_PLACEMENT_ID]: bid.params[SSP_PLACEMENT_ID], [NQ]: [createNqParam(bid)], @@ -117,10 +139,9 @@ function isEngineResponseValid(response) { /** * Used mainly for debugging * - * @param type * @returns string */ -function getEndpointUrl(type) { +function getEndpointUrl() { const nanoConfig = config.getConfig('nano'); return (nanoConfig && nanoConfig['endpointUrl']) || END_POINT_URL; } diff --git a/modules/newborntownWebBidAdapter.js b/modules/newborntownWebBidAdapter.js new file mode 100644 index 00000000000..103fac7ba6a --- /dev/null +++ b/modules/newborntownWebBidAdapter.js @@ -0,0 +1,156 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {BANNER, NATIVE} from '../src/mediaTypes'; +const BIDDER_CODE = 'newborntownWeb'; + +const REQUEST_URL = 'https://us-west.solortb.com/adx/api/rtb?from=4' + +function randomn(n) { + return parseInt((Math.random() + 1) * Math.pow(10, n - 1)) + ''; +} +function generateGUID() { + var d = new Date().getTime(); + var guid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d / 16); + return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16); + }) + return guid; +} +function _isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} +function _isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} +function _getDeviceType() { + return _isMobile() ? 1 : _isConnectedTV() ? 3 : 2; +} +var platform = (function getPlatform() { + var ua = navigator.userAgent; + if (ua.indexOf('Android') > -1 || ua.indexOf('Adr') > -1) { + return 'Android' + } + if (ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/)) { + return 'iOS' + } + return 'windows' +})(); +function getLanguage() { + const language = navigator.language ? 'language' : 'userLanguage'; + return navigator[language].split('-')[0]; +} +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE], + isBidRequestValid: function(bid) { + return !!(bid.params.publisher_id && bid.params.slot_id && bid.params.bidfloor); + }, + + buildRequests: function(validBidRequests, bidderRequest) { + let requestArr = [] + if (validBidRequests.length === 0) { + return null; + } + var guid; + if (localStorage.getItem('sax_user_id') == null) { + localStorage.setItem('sax_user_id', generateGUID()) + } + guid = localStorage.getItem('sax_user_id') + utils._each(validBidRequests, function(bidRequest) { + const bidRequestObj = bidRequest.params + var req = { + id: randomn(12) + randomn(12), + tmax: bidderRequest.timeout, + bidId: bidRequest.bidId, + user: { + id: guid + }, + imp: [ + { + id: '1', + bidfloor: bidRequestObj.bidfloor, + bidfloorcur: 'USD', + banner: { + w: 0, + h: 0 + } + } + ], + site: { + domain: window.location.host, + id: bidRequestObj.slot_id, + page: window.location.href, + publisher: { + id: bidRequestObj.publisher_id + }, + }, + device: { + ip: '', + ua: navigator.userAgent, + os: platform, + geo: { + country: '', + type: 0, + ipservice: 1, + region: '', + city: '', + }, + language: getLanguage(), + devicetype: _getDeviceType() + }, + ext: { + solomath: { + slotid: bidRequestObj.slot_id + } + } + }; + var sizes = bidRequest.sizes; + if (sizes) { + if (sizes && utils.isArray(sizes[0])) { + req.imp[0].banner.w = sizes[0][0]; + req.imp[0].banner.h = sizes[0][1]; + } else if (sizes && utils.isNumber(sizes[0])) { + req.imp[0].banner.w = sizes[0]; + req.imp[0].banner.h = sizes[1]; + } + } else { + return false; + } + const options = { + withCredentials: false + } + requestArr.push({ + method: 'POST', + url: REQUEST_URL, + data: req, + bidderRequest, + options: options + }) + }) + return requestArr; + }, + interpretResponse: function(serverResponse, request) { + var bidResponses = []; + if (serverResponse.body.seatbid && serverResponse.body.seatbid.length > 0 && serverResponse.body.seatbid[0].bid && serverResponse.body.seatbid[0].bid.length > 0 && serverResponse.body.seatbid[0].bid[0].adm) { + utils._each(serverResponse.body.seatbid[0].bid, function(bodyAds) { + var adstr = ''; + adstr = bodyAds.adm; + var bidResponse = { + requestId: request.data.bidId || 0, + cpm: bodyAds.price || 0, + width: bodyAds.w ? bodyAds.w : 0, + height: bodyAds.h ? bodyAds.h : 0, + ad: adstr, + netRevenue: true, + currency: serverResponse.body.cur || 'USD', + ttl: 600, + creativeId: bodyAds.cid + }; + bidResponses.push(bidResponse); + }); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/newborntownWebBidAdapter.md b/modules/newborntownWebBidAdapter.md new file mode 100644 index 00000000000..f607369ffb6 --- /dev/null +++ b/modules/newborntownWebBidAdapter.md @@ -0,0 +1,35 @@ +# Overview + +``` +Module Name: NewborntownWeb Bidder Adapter +Module Type: Bidder Adapter +Maintainer: zhuyushuang@newborntown.com +``` + +# Description + +Integration for website + +# Test Parameters +``` + var adUnits = [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: "newborntownWeb", + params: { + 'publisher_id': '1238122', + 'slot_id': '123123', + 'bidfloor': 0.2 + } + } + ] + } + ]; +``` diff --git a/modules/oneVideoBidAdapter.js b/modules/oneVideoBidAdapter.js index 16883aedc86..f125d88c80c 100644 --- a/modules/oneVideoBidAdapter.js +++ b/modules/oneVideoBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'oneVideo'; export const spec = { code: 'oneVideo', - ENDPOINT: '//ads.adaptv.advertising.com/rtb/openrtb?ext_id=', + ENDPOINT: 'https://ads.adaptv.advertising.com/rtb/openrtb?ext_id=', SYNC_ENDPOINT1: 'https://cm.g.doubleclick.net/pixel?google_nid=adaptv_dbm&google_cm&google_sc', SYNC_ENDPOINT2: 'https://pr-bh.ybp.yahoo.com/sync/adaptv_ortb/{combo_uid}', SYNC_ENDPOINT3: 'https://sync-tm.everesttech.net/upi/pid/m7y5t93k?redir=https%3A%2F%2Fsync.adap.tv%2Fsync%3Ftype%3Dgif%26key%3Dtubemogul%26uid%3D%24%7BUSER_ID%7D', @@ -45,8 +45,10 @@ export const spec = { return bids.map(bid => { return { method: 'POST', - url: location.protocol + spec.ENDPOINT + bid.params.pubId, - data: getRequestData(bid, consentData), + /** removing adding local protocal since we + * can get cookie data only if we call with https. */ + url: spec.ENDPOINT + bid.params.pubId, + data: getRequestData(bid, consentData, bidRequest), bidRequest: bid } }) @@ -136,10 +138,10 @@ function isConsentRequired(consentData) { return !!(consentData && consentData.gdprApplies); } -function getRequestData(bid, consentData) { - let loc = utils.getTopWindowLocation(); +function getRequestData(bid, consentData, bidRequest) { + let loc = bidRequest.refererInfo.referer; let page = (bid.params.site && bid.params.site.page) ? (bid.params.site.page) : (loc.href); - let ref = (bid.params.site && bid.params.site.referrer) ? bid.params.site.referrer : utils.getTopWindowReferrer(); + let ref = (bid.params.site && bid.params.site.referrer) ? bid.params.site.referrer : bidRequest.refererInfo.referer; let bidData = { id: utils.generateUUID(), at: 2, @@ -148,13 +150,6 @@ function getRequestData(bid, consentData) { id: '1', secure: isSecure(), bidfloor: bid.params.bidfloor, - video: { - mimes: bid.params.video.mimes, - w: bid.params.video.playerWidth, - h: bid.params.video.playerHeight, - linearity: 1, - protocols: bid.params.video.protocols || [2, 5] - }, ext: { hb: 1, } @@ -169,35 +164,53 @@ function getRequestData(bid, consentData) { tmax: 200 }; - if (bid.params.video.maxbitrate) { - bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate - } - if (bid.params.video.maxduration) { - bidData.imp[0].video.maxduration = bid.params.video.maxduration - } - if (bid.params.video.minduration) { - bidData.imp[0].video.minduration = bid.params.video.minduration - } - if (bid.params.video.api) { - bidData.imp[0].video.api = bid.params.video.api - } - if (bid.params.video.delivery) { - bidData.imp[0].video.delivery = bid.params.video.delivery - } - if (bid.params.video.position) { - bidData.imp[0].video.pos = bid.params.video.position - } - if (bid.params.video.playbackmethod) { - bidData.imp[0].video.playbackmethod = bid.params.video.playbackmethod - } - if (bid.params.video.placement) { - bidData.imp[0].ext.placement = bid.params.video.placement - } - if (bid.params.video.rewarded) { - bidData.imp[0].ext.rewarded = bid.params.video.rewarded + if (bid.params.video.display == undefined || bid.params.video.display != 1) { + bidData.imp[0].video = { + mimes: bid.params.video.mimes, + w: bid.params.video.playerWidth, + h: bid.params.video.playerHeight, + pos: bid.params.video.position, + }; + if (bid.params.video.maxbitrate) { + bidData.imp[0].video.maxbitrate = bid.params.video.maxbitrate + } + if (bid.params.video.maxduration) { + bidData.imp[0].video.maxduration = bid.params.video.maxduration + } + if (bid.params.video.minduration) { + bidData.imp[0].video.minduration = bid.params.video.minduration + } + if (bid.params.video.api) { + bidData.imp[0].video.api = bid.params.video.api + } + if (bid.params.video.delivery) { + bidData.imp[0].video.delivery = bid.params.video.delivery + } + if (bid.params.video.position) { + bidData.imp[0].video.pos = bid.params.video.position + } + if (bid.params.video.playbackmethod) { + bidData.imp[0].video.playbackmethod = bid.params.video.playbackmethod + } + if (bid.params.video.placement) { + bidData.imp[0].video.placement = bid.params.video.placement + } + if (bid.params.video.rewarded) { + bidData.imp[0].ext.rewarded = bid.params.video.rewarded + } + } else if (bid.params.video.display == 1) { + bidData.imp[0].banner = { + mimes: bid.params.video.mimes, + w: bid.params.video.playerWidth, + h: bid.params.video.playerHeight, + pos: bid.params.video.position, + }; + if (bid.params.video.placement) { + bidData.imp[0].banner.placement = bid.params.video.placement + } } - if (bid.params.site && bid.params.site.id) { - bidData.site.id = bid.params.site.id + if (bid.params.video.inventoryid) { + bidData.imp[0].ext.inventoryid = bid.params.video.inventoryid } if (bid.params.video.sid) { bidData.source = { @@ -212,7 +225,9 @@ function getRequestData(bid, consentData) { } } } - + if (bid.params.site && bid.params.site.id) { + bidData.site.id = bid.params.site.id + } if (isConsentRequired(consentData)) { bidData.regs = { ext: { @@ -228,6 +243,13 @@ function getRequestData(bid, consentData) { }; } } + if (bidRequest && bidRequest.uspConsent) { + bidData.regs = { + ext: { + us_privacy: bidRequest.uspConsent + } + }; + } return bidData; } diff --git a/modules/oneVideoBidAdapter.md b/modules/oneVideoBidAdapter.md index c7f6af399e7..145af1a6fb9 100644 --- a/modules/oneVideoBidAdapter.md +++ b/modules/oneVideoBidAdapter.md @@ -2,16 +2,17 @@ **Module Name**: One Video Bidder Adapter **Module Type**: Bidder Adapter -**Maintainer**: ankur.modi@oath.com +**Maintainer**: deepthi.neeladri.sravana@verizonmedia.com # Description Connects to One Video demand source to fetch bids. -# Test Parameters +# Test Parameters for Video ``` var adUnits = [ + { code: 'video1', sizes: [640,480], @@ -33,20 +34,54 @@ Connects to One Video demand source to fetch bids. position: 1, delivery: [2], playbackmethod: [1,5], - placement: 123, sid: , - rewarded: 1 - }, - }, - site: { - id: 1, - page: 'http://abhi12345.com', - referrer: 'http://abhi12345.com' - }, - pubId: 'brxd' - } - } + rewarded: 1, + placement: 1, + inventoryid: 123 + }, + site: { + id: 1, + page: 'http://abhi12345.com', + referrer: 'http://abhi12345.com' + }, + pubId: 'brxd' + } + } ] } - ]; +] +``` +# Test Parameters for banner request +``` + var adUnits = [ + { + code: 'video1', + sizes: [640,480], + mediaTypes: { + video: { + context: "instream" + } + }, + bids: [ + { + bidder: 'oneVideo', + params: { + video: { + playerWidth: 480, + playerHeight: 640, + mimes: ['video/mp4', 'application/javascript'], + position: 1, + display: 1 + }, + site: { + id: 1, + page: 'http://abhi12345.com', + referrer: 'http://abhi12345.com' + }, + pubId: 'OneMDisplay' + } + } + ] + } +] ``` diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index a5c5b432a55..1e69dc01471 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -1,14 +1,13 @@ import {config} from '../src/config'; import {registerBidder} from '../src/adapters/bidderFactory'; import * as utils from '../src/utils'; -import {userSync} from '../src/userSync'; import {BANNER, VIDEO} from '../src/mediaTypes'; import {parse} from '../src/url'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openx'; const BIDDER_CONFIG = 'hb_pb'; -const BIDDER_VERSION = '2.1.9'; +const BIDDER_VERSION = '3.0.0'; const USER_ID_CODE_TO_QUERY_ARG = { idl_env: 'lre', // liveramp @@ -16,12 +15,6 @@ const USER_ID_CODE_TO_QUERY_ARG = { tdid: 'ttduuid' // the trade desk }; -let shouldSendBoPixel = true; - -export function resetBoPixel() { - shouldSendBoPixel = true; -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_AD_TYPES, @@ -132,24 +125,10 @@ function createBannerBidResponses(oxResponseObj, {bids, startTime}) { } bidResponses.push(bidResponse); - - registerBeacon(BANNER, adUnit, startTime); } return bidResponses; } -function buildQueryStringFromParams(params) { - for (let key in params) { - if (params.hasOwnProperty(key)) { - if (!params[key]) { - delete params[key]; - } - } - } - return utils._map(Object.keys(params), key => `${key}=${params[key]}`) - .join('&'); -} - function getViewportDimensions(isIfr) { let width; let height; @@ -210,8 +189,7 @@ function buildCommonQueryParamsFromBids(bids, bidderRequest) { let defaultParams; defaultParams = { - ju: config.getConfig('pageUrl') || utils.getTopWindowUrl(), - jr: utils.getTopWindowReferrer(), + ju: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer, ch: document.charSet || document.characterSet, res: `${screen.width}x${screen.height}x${screen.colorDepth}`, ifr: isInIframe, @@ -285,7 +263,8 @@ function buildOXBannerRequest(bids, bidderRequest) { let hasCustomParam = false; let queryParams = buildCommonQueryParamsFromBids(bids, bidderRequest); let auids = utils._map(bids, bid => bid.params.unit); - queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.sizes).join(',')).join('|'); + + queryParams.aus = utils._map(bids, bid => utils.parseSizesInput(bid.mediaTypes.banner.sizes).join(',')).join('|'); queryParams.divIds = utils._map(bids, bid => encodeURIComponent(bid.adUnitCode)).join(','); if (auids.some(auid => auid)) { @@ -425,53 +404,9 @@ function createVideoBidResponses(response, {bid, startTime}) { response.ts = vastQueryParams.ts; bidResponses.push(bidResponse); - - registerBeacon(VIDEO, response, startTime) } return bidResponses; } -function registerBeacon(mediaType, adUnit, startTime) { - // only register beacon once - if (!shouldSendBoPixel) { - return; - } - shouldSendBoPixel = false; - - let bt = config.getConfig('bidderTimeout'); - let beaconUrl; - if (window.PREBID_TIMEOUT) { - bt = Math.min(window.PREBID_TIMEOUT, bt); - } - - let beaconParams = { - bd: +(new Date()) - startTime, - bp: adUnit.pub_rev, - br: '0', // may be 0, t, or p - bs: utils.getTopWindowLocation().hostname, - bt: bt, - ts: adUnit.ts - }; - - beaconParams.br = beaconParams.bt < beaconParams.bd ? 't' : 'p'; - - if (mediaType === VIDEO) { - let url = parse(adUnit.colo); - beaconParams.ph = adUnit.ph; - beaconUrl = `https://${url.hostname}/w/1.0/bo?${buildQueryStringFromParams(beaconParams)}` - } else { - let recordPixel = utils.deepAccess(adUnit, 'creative.0.tracking.impression'); - let boBase = recordPixel.match(/([^?]+\/)ri\?/); - - if (boBase && boBase.length > 1) { - beaconUrl = `${boBase[1]}bo?${buildQueryStringFromParams(beaconParams)}`; - } - } - - if (beaconUrl) { - userSync.registerSync('image', BIDDER_CODE, beaconUrl); - } -} - registerBidder(spec); diff --git a/modules/openxoutstreamBidAdapter.js b/modules/openxoutstreamBidAdapter.js index 9011a949e7b..b9760c0c3bb 100644 --- a/modules/openxoutstreamBidAdapter.js +++ b/modules/openxoutstreamBidAdapter.js @@ -6,7 +6,7 @@ import { BANNER } from '../src/mediaTypes'; const SUPPORTED_AD_TYPES = [BANNER]; const BIDDER_CODE = 'openxoutstream'; const BIDDER_CONFIG = 'hb_pb_ym'; -const BIDDER_VERSION = '1.0.0'; +const BIDDER_VERSION = '1.0.1'; const CURRENCY = 'USD'; const NET_REVENUE = true; const TIME_TO_LIVE = 300; @@ -70,8 +70,7 @@ function buildCommonQueryParamsFromBids(bid, bidderRequest) { const width = '414'; const aus = '304x184%7C412x184%7C375x184%7C414x184'; defaultParams = { - ju: config.getConfig('pageUrl') || utils.getTopWindowUrl(), - jr: utils.getTopWindowReferrer(), + ju: config.getConfig('pageUrl') || bidderRequest.refererInfo.referer, ch: document.charSet || document.characterSet, res: `${screen.width}x${screen.height}x${screen.colorDepth}`, ifr: isInIframe, @@ -118,7 +117,7 @@ function buildOXBannerRequest(bid, bidderRequest) { queryParams.tfcd = 1; } - let url = `https://${bid.params.delDomain}/v/1.0/avjp` + let url = `https://${bid.params.delDomain}/v/1.0/avjp`; return { method: 'GET', url: url, diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 88534ae1596..a3ed3e3f74d 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -28,9 +28,11 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { referer = bidderRequest.refererInfo.referer || ''; } + const ret = { url: `${spec.orbidderHost}/bid`, method: 'POST', + options: { withCredentials: true }, data: { pageUrl: referer, bidId: bidRequest.bidId, @@ -86,7 +88,7 @@ export const spec = { }, ajaxCall(endpoint, data) { - ajax(endpoint, null, data); + ajax(endpoint, null, data, { withCredentials: true }); } }; diff --git a/modules/outconBidAdapter.js b/modules/outconBidAdapter.js index ba0c1164279..b636c18cf88 100644 --- a/modules/outconBidAdapter.js +++ b/modules/outconBidAdapter.js @@ -18,13 +18,13 @@ export const spec = { switch (validBidRequests[i].params.env) { case 'test': par = par + '&demo=true'; - url = 'http://test.outcondigital.com:8048/ad/' + par; + url = 'https://test.outcondigital.com/ad/' + par; break; case 'api': - url = 'http://api.outcondigital.com:8048/ad/' + par; + url = 'https://api.outcondigital.com/ad/' + par; break; case 'stg': - url = 'http://stg.outcondigital.com:8048/ad/' + par; + url = 'https://stg.outcondigital.com/ad/' + par; break; } return { diff --git a/modules/playgroundxyzBidAdapter.js b/modules/playgroundxyzBidAdapter.js index 26483f1277a..3699266ea46 100644 --- a/modules/playgroundxyzBidAdapter.js +++ b/modules/playgroundxyzBidAdapter.js @@ -166,7 +166,7 @@ function isMobile() { } function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(global.navigator.userAgent); + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); } registerBidder(spec); diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 93b8dc085f8..8d512554489 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -120,7 +120,7 @@ export function resetSyncedStatus() { /** * @param {Array} bidderCodes list of bidders to request user syncs for. */ -function queueSync(bidderCodes, gdprConsent) { +function queueSync(bidderCodes, gdprConsent, uspConsent) { if (_synced) { return; } @@ -147,6 +147,12 @@ function queueSync(bidderCodes, gdprConsent) { payload.gdpr_consent = gdprConsent.consentString; } } + + // US Privace (CCPA) support + if (uspConsent) { + payload.us_privacy = uspConsent; + } + const jsonPayload = JSON.stringify(payload); ajax(_s2sConfig.syncEndpoint, (response) => { @@ -510,10 +516,11 @@ const OPEN_RTB_PROTOCOL = { type: imgTypeId, w: utils.deepAccess(params, 'sizes.0'), h: utils.deepAccess(params, 'sizes.1'), - wmin: utils.deepAccess(params, 'aspect_ratios.0.min_width') + wmin: utils.deepAccess(params, 'aspect_ratios.0.min_width'), + hmin: utils.deepAccess(params, 'aspect_ratios.0.min_height') }); - if (!(asset.w || asset.wmin)) { - throw 'invalid img sizes (must provided sizes or aspect_ratios)'; + if (!((asset.w && asset.h) || (asset.hmin && asset.wmin))) { + throw 'invalid img sizes (must provide sizes or min_height & min_width if using aspect_ratios)'; } if (Array.isArray(params.aspect_ratios)) { // pass aspect_ratios as ext data I guess? @@ -639,7 +646,7 @@ const OPEN_RTB_PROTOCOL = { }); if (!imps.length) { - utils.logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.') + utils.logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); return; } const request = { @@ -697,7 +704,7 @@ const OPEN_RTB_PROTOCOL = { } const bidUserId = utils.deepAccess(bidRequests, '0.bids.0.userId'); - if (bidUserId && typeof bidUserId === 'object' && (bidUserId.tdid || bidUserId.pubcid || bidUserId.parrableid || bidUserId.lipb)) { + if (bidUserId && typeof bidUserId === 'object' && (bidUserId.tdid || bidUserId.pubcid || bidUserId.parrableid || bidUserId.lipb || bidUserId.id5id || bidUserId.criteoId || bidUserId.britepoolid)) { utils.deepSetValue(request, 'user.ext.eids', []); if (bidUserId.tdid) { @@ -714,7 +721,7 @@ const OPEN_RTB_PROTOCOL = { if (bidUserId.pubcid) { request.user.ext.eids.push({ - source: 'pubcommon', + source: 'pubcid.org', uids: [{ id: bidUserId.pubcid, }] @@ -731,33 +738,64 @@ const OPEN_RTB_PROTOCOL = { } if (bidUserId.lipb && bidUserId.lipb.lipbid) { - request.user.ext.eids.push({ + const liveIntent = { source: 'liveintent.com', uids: [{ id: bidUserId.lipb.lipbid }] + }; + + if (Array.isArray(bidUserId.lipb.segments) && bidUserId.lipb.segments.length) { + liveIntent.ext = { + segments: bidUserId.lipb.segments + }; + } + request.user.ext.eids.push(liveIntent); + } + + if (bidUserId.id5id) { + request.user.ext.eids.push({ + source: 'id5-sync.com', + uids: [{ + id: bidUserId.id5id, + }] + }); + } + + if (bidUserId.criteoId) { + request.user.ext.eids.push({ + source: 'criteo.com', + uids: [{ + id: bidUserId.criteoId + }] }); } - } - if (bidRequests && bidRequests[0].gdprConsent) { - // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module - let gdprApplies; - if (typeof bidRequests[0].gdprConsent.gdprApplies === 'boolean') { - gdprApplies = bidRequests[0].gdprConsent.gdprApplies ? 1 : 0; + if (bidUserId.britepoolid) { + request.user.ext.eids.push({ + source: 'britepool.com', + uids: [{ + id: bidUserId.britepoolid + }] + }); } + } - if (request.regs) { - if (request.regs.ext) { - request.regs.ext.gdpr = gdprApplies; - } else { - request.regs.ext = { gdpr: gdprApplies }; + if (bidRequests) { + if (bidRequests[0].gdprConsent) { + // note - gdprApplies & consentString may be undefined in certain use-cases for consentManagement module + let gdprApplies; + if (typeof bidRequests[0].gdprConsent.gdprApplies === 'boolean') { + gdprApplies = bidRequests[0].gdprConsent.gdprApplies ? 1 : 0; } - } else { - request.regs = { ext: { gdpr: gdprApplies } }; + utils.deepSetValue(request, 'regs.ext.gdpr', gdprApplies); + utils.deepSetValue(request, 'user.ext.consent', bidRequests[0].gdprConsent.consentString); } - utils.deepSetValue(request, 'user.ext.consent', bidRequests[0].gdprConsent.consentString); + // US Privacy (CCPA) support + if (bidRequests[0].uspConsent) { + utils.deepSetValue(request, 'regs.ext.us_privacy', bidRequests[0].uspConsent); + } } if (getConfig('coppa') === true) { @@ -897,10 +935,10 @@ const OPEN_RTB_PROTOCOL = { bidObject.creative_id = bid.crid; bidObject.creativeId = bid.crid; if (bid.burl) { bidObject.burl = bid.burl; } + bidObject.currency = (response.cur) ? response.cur : DEFAULT_S2S_CURRENCY; - // TODO: Remove when prebid-server returns ttl, currency and netRevenue + // TODO: Remove when prebid-server returns ttl and netRevenue bidObject.ttl = (bid.ttl) ? bid.ttl : DEFAULT_S2S_TTL; - bidObject.currency = (bid.currency) ? bid.currency : DEFAULT_S2S_CURRENCY; bidObject.netRevenue = (bid.netRevenue) ? bid.netRevenue : DEFAULT_S2S_NETREVENUE; bids.push({ adUnit: bid.impid, bid: bidObject }); @@ -917,7 +955,7 @@ const isOpenRtb = () => { const endpoint = (_s2sConfig && _s2sConfig.endpoint) || ''; return ~endpoint.indexOf(OPEN_RTB_PATH); -} +}; /* * Returns the required protocol adapter to communicate with the configured @@ -958,12 +996,17 @@ export function PrebidServer() { .filter(utils.uniques); if (_s2sConfig && _s2sConfig.syncEndpoint) { - let consent = (Array.isArray(bidRequests) && bidRequests.length > 0) ? bidRequests[0].gdprConsent : undefined; + let gdprConsent, uspConsent; + if (Array.isArray(bidRequests) && bidRequests.length > 0) { + gdprConsent = bidRequests[0].gdprConsent; + uspConsent = bidRequests[0].uspConsent; + } + let syncBidders = _s2sConfig.bidders .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) .filter((bidder, index, array) => (array.indexOf(bidder) === index)); - queueSync(syncBidders, consent); + queueSync(syncBidders, gdprConsent, uspConsent); } const request = protocolAdapter().buildRequest(s2sBidRequest, bidRequests, validAdUnits); diff --git a/modules/proxistoreBidAdapter.js b/modules/proxistoreBidAdapter.js new file mode 100644 index 00000000000..44d4acadee2 --- /dev/null +++ b/modules/proxistoreBidAdapter.js @@ -0,0 +1,125 @@ +const { registerBidder } = require('../src/adapters/bidderFactory'); +const BIDDER_CODE = 'proxistore'; + +function _getFormatSize(sizeArr) { + return { + width: sizeArr[0], + height: sizeArr[1] + } +} + +function _createServerRequest(bidRequest, bidderRequest) { + const payload = { + bidId: bidRequest.bidId, + auctionId: bidRequest.auctionId, + transactionId: bidRequest.transactionId, + sizes: bidRequest.sizes.map(_getFormatSize), + website: bidRequest.params.website, + language: bidRequest.params.language, + gdpr: { + applies: false + } + }; + + const options = { + contentType: 'application/json', + withCredentials: true + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + if ((typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') && bidderRequest.gdprConsent.gdprApplies) { + payload.gdpr.applies = true; + } + if ((typeof bidderRequest.gdprConsent.consentString === 'string') && bidderRequest.gdprConsent.consentString) { + payload.gdpr['consentString'] = bidderRequest.gdprConsent.consentString; + } + } + + return { + method: 'POST', + url: bidRequest.params.url || '//abs.proxistore.com/' + bidRequest.params.language + '/v3/rtb/prebid', + data: JSON.stringify(payload), + options: options + }; +} + +function _createBidResponse(response) { + return { + requestId: response.requestId, + cpm: response.cpm, + width: response.width, + height: response.height, + ad: response.ad, + ttl: response.ttl, + creativeId: response.creativeId, + currency: response.currency, + netRevenue: response.netRevenue, + vastUrl: response.vastUrl, + vastXml: response.vastXml, + dealId: response.dealId + } +} + +/** + * Determines whether or not the given bid request is valid. + * + * @param bid The bid params to validate. + * @return boolean True if this is a valid bid, and false otherwise. + */ +function isBidRequestValid(bid) { + return !!(bid.params.website && bid.params.language); +} + +/** + * Make a server request from the list of BidRequests. + * + * @param bidRequests - an array of bids + * @param bidderRequest + * @return ServerRequest Info describing the request to the server. + */ +function buildRequests(bidRequests, bidderRequest) { + var requests = []; + for (var i = 0; i < bidRequests.length; i++) { + var prebidReq = _createServerRequest(bidRequests[i], bidderRequest); + requests.push(prebidReq); + } + return requests; +} + +/** + * Unpack the response from the server into a list of bids. + * + * @param serverResponse A successful response from the server. + * @param bidRequest Request original server request + * @return An array of bids which were nested inside the server. + */ +function interpretResponse(serverResponse, bidRequest) { + if (serverResponse.body.length > 0) { + return serverResponse.body.map(_createBidResponse); + } else { + return []; + } +} + +/** + * Register the user sync pixels which should be dropped after the auction. + * + * @param syncOptions Which user syncs are allowed? + * @param serverResponses List of server's responses. + * @return The user syncs which should be dropped. + */ +function getUserSyncs(syncOptions, serverResponses) { + return []; +} + +const spec = { + code: BIDDER_CODE, + isBidRequestValid: isBidRequestValid, + buildRequests: buildRequests, + interpretResponse: interpretResponse, + getUserSyncs: getUserSyncs +}; + +registerBidder(spec); + +module.exports = spec; diff --git a/modules/proxistoreBidAdapter.md b/modules/proxistoreBidAdapter.md new file mode 100644 index 00000000000..e958d52c321 --- /dev/null +++ b/modules/proxistoreBidAdapter.md @@ -0,0 +1,73 @@ +# Overview + +``` +Module Name: Proxistore Bid Adapter +Module Type: Bidder Adapter +GDPR compliant: true +``` + +# Description + +Connects any publisher to Proxistore.com's exchange for bids. + +The present document is available [online](https://abs.proxistore.com/scripts/proxistore-prebid-adapter.md); +the companion Javascript file is available [online](https://abs.proxistore.com/scripts/proxistore-prebid-adapter.js) as well. + +# Sample Ad Unit: For Publishers + +Parameters: +* website: __Required__ - Publisher website as registered with Proxistore - __Each publisher must get this value from Proxistore__ +* language: __Required__ - Publisher website language - Must be one of __fr__,__nl__,__es__ + +Example for a website 'example.com' in French + +``` +var adUnits = [ +{ + code: 'half-page', + mediaTypes: { + banner: { + sizes: [[300,600]] + } + }, + bids: [ + { + bidder: 'proxistore', + params: { + website: 'example.com', + language: 'fr' + } + }] +}, +{ + code: 'rectangle', + mediaTypes: { + banner: { + sizes: [[300,250]] + } + }, + bids: [{ + bidder: 'proxistore', + params: { + website: 'example.com', + language: 'fr' + } + }] +}, +{ + code: 'leaderboard', + mediaTypes: { + banner: { + sizes: [[970,250]] + } + }, + bids: [{ + bidder: 'proxistore', + params: { + website: 'example.com', + language: 'fr' + } + }] +} +]; +``` diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 73e6d8cfb52..cf4ccee17d0 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -198,14 +198,10 @@ function _parseAdSlot(bid) { } function _initConf(refererInfo) { - var conf = {}; - conf.pageURL = utils.getTopWindowUrl(); - if (refererInfo && refererInfo.referer) { - conf.refURL = refererInfo.referer; - } else { - conf.refURL = ''; - } - return conf; + return { + pageURL: (refererInfo && refererInfo.referer) ? refererInfo.referer : window.location.href, + refURL: window.document.referrer + }; } function _handleCustomParams(params, conf) { @@ -645,13 +641,14 @@ function _handleEids(payload, validBidRequests) { _handleTTDId(eids, validBidRequests); const bidRequest = validBidRequests[0]; if (bidRequest && bidRequest.userId) { - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcommon', 1); + _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.pubcid`), 'pubcid.org', 1); _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.digitrustid.data.id`), 'digitru.st', 1); _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.id5id`), 'id5-sync.com', 1); - _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteortus.${BIDDER_CODE}.userid`), 'criteortus', 1); + _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.criteoId`), 'criteo.com', 1);// replacing criteoRtus _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.idl_env`), 'liveramp.com', 1); _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.lipb.lipbid`), 'liveintent.com', 1); _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.parrableid`), 'parrable.com', 1); + _addExternalUserId(eids, utils.deepAccess(bidRequest, `userId.britepoolid`), 'britepool.com', 1); } if (eids.length > 0) { payload.user.eids = eids; @@ -898,6 +895,11 @@ export const spec = { payload.site.page = conf.kadpageurl.trim() || payload.site.page.trim(); payload.site.domain = _getDomainFromURL(payload.site.page); + // test bids + if (window.location.href.indexOf('pubmaticTest=true') !== -1) { + payload.test = 1; + } + // adding schain object if (validBidRequests[0].schain) { payload.source = { @@ -920,6 +922,11 @@ export const spec = { }; } + // CCPA + if (bidderRequest && bidderRequest.uspConsent) { + utils.deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + // coppa compliance if (config.getConfig('coppa') === true) { utils.deepSetValue(payload, 'regs.coppa', 1); @@ -1015,7 +1022,7 @@ export const spec = { /** * Register User Sync. */ - getUserSyncs: (syncOptions, responses, gdprConsent) => { + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { let syncurl = USYNCURL + publisherId; // Attaching GDPR Consent Params in UserSync url @@ -1024,6 +1031,11 @@ export const spec = { syncurl += '&gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || ''); } + // CCPA + if (uspConsent) { + syncurl += '&us_privacy=' + encodeURIComponent(uspConsent); + } + // coppa compliance if (config.getConfig('coppa') === true) { syncurl += '&coppa=1'; diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index 1948acba40f..fc7766430a2 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -199,4 +199,7 @@ pbjs.setConfig({ }); ``` -Note: Combine the above the configuration with any other UserSync configuration. Multiple setConfig() calls overwrite each other and only last call for a given attribute will take effect. +Note: Combine the above the configuration with any other UserSync configuration. Multiple setConfig() calls overwrite each other and only last call for a given attribute will take effect. + +Note: PubMatic will return a test-bid if "pubmaticTest=true" is present in page URL + diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index fee247ba31f..b898618673e 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -47,6 +47,7 @@ export const spec = { badv: bidRequests[0].params.badv, user: user(bidRequests[0], bidderRequest), regs: regs(bidderRequest), + source: source(bidRequests[0].schain), }; return { method: 'POST', @@ -114,9 +115,9 @@ function bidResponseAvailable(request, response) { creative_id: idToBidMap[id].crid, creativeId: idToBidMap[id].crid, adId: id, - ttl: DEFAULT_BID_TTL, + ttl: idToBidMap[id].exp || DEFAULT_BID_TTL, netRevenue: DEFAULT_NET_REVENUE, - currency: DEFAULT_CURRENCY + currency: bidResponse.cur || DEFAULT_CURRENCY }; if (idToImpMap[id]['native']) { bid['native'] = nativeResponse(idToImpMap[id], idToBidMap[id]); @@ -135,21 +136,12 @@ function bidResponseAvailable(request, response) { bid.width = idToImpMap[id].banner.w; bid.height = idToImpMap[id].banner.h; } - applyExt(bid, idToBidMap[id]) bids.push(bid); } }); return bids; } -function applyExt(bid, ortbBid) { - if (ortbBid && ortbBid.ext) { - bid.ttl = ortbBid.ext.ttl || bid.ttl; - bid.currency = ortbBid.ext.currency || bid.currency; - bid.netRevenue = ortbBid.ext.netRevenue != null ? ortbBid.ext.netRevenue : bid.netRevenue; - } -} - /** * Produces an OpenRTBImpression from a slot config. */ @@ -405,9 +397,31 @@ function user(bidRequest, bidderRequest) { if (bidRequest.userId) { ext.eids = []; addExternalUserId(ext.eids, bidRequest.userId.pubcid, 'pubcommon'); - addExternalUserId(ext.eids, bidRequest.userId.tdid, 'ttdid'); - addExternalUserId(ext.eids, utils.deepAccess(bidRequest.userId.digitrustid, 'data.id'), 'digitrust'); - addExternalUserId(ext.eids, bidRequest.userId.id5id, 'id5id'); + addExternalUserId(ext.eids, bidRequest.userId.britepoolid, 'britepool.com'); + addExternalUserId(ext.eids, bidRequest.userId.criteoId, 'criteo'); + addExternalUserId(ext.eids, bidRequest.userId.idl_env, 'identityLink'); + addExternalUserId(ext.eids, bidRequest.userId.id5id, 'id5-sync.com'); + addExternalUserId(ext.eids, bidRequest.userId.parrableid, 'parrable.com'); + // liveintent + if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { + addExternalUserId(ext.eids, bidRequest.userId.lipb.lipbid, 'liveintent.com'); + } + // TTD + addExternalUserId(ext.eids, bidRequest.userId.tdid, 'adserver.org', { + rtiPartner: 'TDID' + }); + // digitrust + const digitrustResponse = bidRequest.userId.digitrustid; + if (digitrustResponse && digitrustResponse.data) { + var digitrust = {}; + if (digitrustResponse.data.id) { + digitrust.id = digitrustResponse.data.id; + } + if (digitrustResponse.data.keyv) { + digitrust.keyv = digitrustResponse.data.keyv; + } + ext.digitrust = digitrust; + } } } return { ext }; @@ -416,13 +430,15 @@ function user(bidRequest, bidderRequest) { /** * Produces external userid object in ortb 3.0 model. */ -function addExternalUserId(eids, value, source) { - if (value) { +function addExternalUserId(eids, id, source, uidExt) { + if (id) { + var uid = { id }; + if (uidExt) { + uid.ext = uidExt; + } eids.push({ source, - uids: [{ - id: value - }] + uids: [ uid ] }); } } @@ -431,8 +447,29 @@ function addExternalUserId(eids, value, source) { * Produces the regulations ortb object */ function regs(bidderRequest) { - if (bidderRequest && bidderRequest.gdprConsent) { - return { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + if (bidderRequest.gdprConsent || bidderRequest.uspConsent) { + var ext = {}; + // GDPR applies attribute (actual consent value is in user object) + if (bidderRequest.gdprConsent) { + ext.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + // CCPA + if (bidderRequest.uspConsent) { + ext.us_privacy = bidderRequest.uspConsent; + } + return { ext }; + } + return null; +} + +/** + * Creates source object with supply chain + */ +function source(schain) { + if (schain) { + return { + ext: { schain } + }; } return null; } diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 739cf75c555..93f8e398a9d 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -11,27 +11,8 @@ export const QUANTCAST_TEST_DOMAIN = 's2s-canary.quantserve.com'; export const QUANTCAST_NET_REVENUE = true; export const QUANTCAST_TEST_PUBLISHER = 'test-publisher'; export const QUANTCAST_TTL = 4; -export const QUANTCAST_PROTOCOL = - window.location.protocol === 'http:' - ? 'http' - : 'https'; -export const QUANTCAST_PORT = - QUANTCAST_PROTOCOL === 'http' - ? '8080' - : '8443'; - -function extractBidSizes(bid) { - const bidSizes = []; - - bid.sizes.forEach(size => { - bidSizes.push({ - width: size[0], - height: size[1] - }); - }); - - return bidSizes; -} +export const QUANTCAST_PROTOCOL = 'https'; +export const QUANTCAST_PORT = '8443'; function makeVideoImp(bid) { const video = {}; @@ -67,10 +48,17 @@ function makeVideoImp(bid) { } function makeBannerImp(bid) { + const sizes = bid.sizes || bid.mediaTypes.banner.sizes; + return { banner: { battr: bid.params.battr, - sizes: extractBidSizes(bid), + sizes: sizes.map(size => { + return { + width: size[0], + height: size[1] + }; + }) }, placementCode: bid.placementCode, bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR @@ -113,8 +101,8 @@ export const spec = { */ buildRequests(bidRequests, bidderRequest) { const bids = bidRequests || []; - const gdprConsent = (bidderRequest && bidderRequest.gdprConsent) ? bidderRequest.gdprConsent : {}; - + const gdprConsent = utils.deepAccess(bidderRequest, 'gdprConsent') || {}; + const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); const referrer = utils.deepAccess(bidderRequest, 'refererInfo.referer'); const page = utils.deepAccess(bidderRequest, 'refererInfo.canonicalUrl') || config.getConfig('pageUrl') || utils.deepAccess(window, 'location.href'); const domain = getDomain(page); @@ -151,6 +139,8 @@ export const spec = { bidId: bid.bidId, gdprSignal: gdprConsent.gdprApplies ? 1 : 0, gdprConsent: gdprConsent.consentString, + uspSignal: uspConsent ? 1 : 0, + uspConsent, prebidJsVersion: '$prebid.version$' }; diff --git a/modules/rhythmoneBidAdapter.js b/modules/rhythmoneBidAdapter.js index dc818e89f32..6994173f4be 100644 --- a/modules/rhythmoneBidAdapter.js +++ b/modules/rhythmoneBidAdapter.js @@ -163,7 +163,7 @@ function RhythmOneBidAdapter() { } function frameBid(BRs, bidderRequest) { - return { + let bid = { id: BRs[0].bidderRequestId, imp: frameImp(BRs, bidderRequest), site: frameSite(bidderRequest), @@ -181,6 +181,14 @@ function RhythmOneBidAdapter() { } } }; + if (BRs[0].schain) { + bid.source = { + 'ext': { + 'schain': BRs[0].schain + } + } + } + return bid; } function getFirstParam(key, validBidRequests) { diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js old mode 100644 new mode 100755 index bd47e481a76..2a4fa03c7d6 --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -4,6 +4,7 @@ import {BANNER, VIDEO} from '../src/mediaTypes'; import * as utils from '../src/utils'; const BIDDER_CODE = 'richaudience'; +let REFERER = ''; export const spec = { code: BIDDER_CODE, @@ -39,22 +40,22 @@ export const spec = { bidder: bid.bidder, bidderRequestId: bid.bidderRequestId, tagId: bid.adUnitCode, - sizes: bid.sizes.map(size => ({ - w: size[0], - h: size[1], - })), + sizes: raiGetSizes(bid), referer: (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null), numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), + user: raiSetEids(bid) }; + REFERER = (typeof bidderRequest.refererInfo.referer != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.referer) : null) + + payload.gdpr_consent = ''; + payload.gdpr = null; + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; - payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side - } else { - payload.gdpr_consent = ''; - payload.gdpr = null; + payload.gdpr = bidderRequest.gdprConsent.gdprApplies; } var payloadString = JSON.stringify(payload); @@ -76,34 +77,29 @@ export const spec = { */ interpretResponse: function (serverResponse, bidRequest) { const bidResponses = []; - + // try catch var response = serverResponse.body; + if (response) { + var bidResponse = { + requestId: JSON.parse(bidRequest.data).bidId, + cpm: response.cpm, + width: response.width, + height: response.height, + creativeId: response.creative_id, + mediaType: response.media_type, + netRevenue: response.netRevenue, + currency: response.currency, + ttl: response.ttl, + dealId: response.dealId, + }; - try { - if (response) { - var bidResponse = { - requestId: JSON.parse(bidRequest.data).bidId, - cpm: response.cpm, - width: response.width, - height: response.height, - creativeId: response.creative_id, - mediaType: response.media_type, - netRevenue: response.netRevenue, - currency: response.currency, - ttl: response.ttl, - dealId: response.dealId, - }; - - if (response.media_type === 'video') { - bidResponse.vastXml = response.vastXML; - } else { - bidResponse.ad = response.adm - } - - bidResponses.push(bidResponse); + if (response.media_type === 'video') { + bidResponse.vastXml = response.vastXML; + } else { + bidResponse.ad = response.adm } - } catch (error) { - utils.logError('Error while parsing Rich Audience response', error); + + bidResponses.push(bidResponse); } return bidResponses }, @@ -121,20 +117,60 @@ export const spec = { var rand = Math.floor(Math.random() * 9999999999); var syncUrl = ''; - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand + '&pubconsent=' + gdprConsent.consentString + '&euconsent=' + gdprConsent.consentString; - } else { - syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand; - } + gdprConsent && typeof gdprConsent.consentString === 'string' ? syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand + '&pubconsent=' + gdprConsent.consentString + '&euconsent=' + gdprConsent.consentString : syncUrl = 'https://sync.richaudience.com/dcf3528a0b8aa83634892d50e91c306e/?ord=' + rand; if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', url: syncUrl }); + } else if (syncOptions.pixelEnabled && REFERER != null) { + syncs.push({ + type: 'image', + url: `https://sync.richaudience.com/bf7c142f4339da0278e83698a02b0854/?euconsent=${gdprConsent.consentString}&referrer=${REFERER}` + }); } return syncs }, }; registerBidder(spec); + +function raiGetSizes(bid) { + let raiNewSizes; + if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) { + raiNewSizes = bid.mediaTypes.banner.sizes + } else { + raiNewSizes = bid.sizes + } + if (raiNewSizes != null) { + return raiNewSizes.map(size => ({ + w: size[0], + h: size[1] + })); + } +} + +function raiSetEids(bid) { + let eids = []; + + if (bid && bid.userId) { + raiSetUserId(bid, eids, 'id5-sync.com', utils.deepAccess(bid, `userId.id5id`)); + raiSetUserId(bid, eids, 'pubcommon', utils.deepAccess(bid, `userId.pubcid`)); + raiSetUserId(bid, eids, 'criteo.com', utils.deepAccess(bid, `userId.criteoId`)); + raiSetUserId(bid, eids, 'liveramp.com', utils.deepAccess(bid, `userId.idl_env`)); + raiSetUserId(bid, eids, 'liveintent.com', utils.deepAccess(bid, `userId.lipb.lipbid`)); + raiSetUserId(bid, eids, 'adserver.org', utils.deepAccess(bid, `userId.tdid`)); + } + + return eids; +} + +function raiSetUserId(bid, eids, source, value) { + if (utils.isStr(value)) { + eids.push({ + userId: value, + source: source + }); + } +} diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 245fc5f3ea2..36c08fc8626 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -115,10 +115,11 @@ function mapBanner(slot) { if (slot.mediaType === 'banner' || utils.deepAccess(slot, 'mediaTypes.banner') || (!slot.mediaType && !slot.mediaTypes)) { + var sizes = slot.sizes || slot.mediaTypes.banner.sizes; return { - w: slot.sizes[0][0], - h: slot.sizes[0][1], - format: slot.sizes.map(size => ({ + w: sizes[0][0], + h: sizes[0][1], + format: sizes.map(size => ({ w: size[0], h: size[1] })) diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index 233ed34053a..b8b59aa9edc 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -17,7 +17,11 @@ Please reach out to pmp@rtbhouse.com to receive your own // banner { code: 'test-div', - sizes: [[300, 250]], + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, bids: [ { bidder: "rtbhouse", diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js new file mode 100644 index 00000000000..e7ba364c0e5 --- /dev/null +++ b/modules/rtdModule/index.js @@ -0,0 +1,211 @@ +/** + * This module adds Real time data support to prebid.js + * @module modules/realTimeData + */ + +/** + * @interface RtdSubmodule + */ + +/** + * @function + * @summary return real time data + * @name RtdSubmodule#getData + * @param {AdUnit[]} adUnits + * @param {function} onDone + */ + +/** + * @property + * @summary used to link submodule with config + * @name RtdSubmodule#name + * @type {string} + */ + +/** + * @interface ModuleConfig + */ + +/** + * @property + * @summary sub module name + * @name ModuleConfig#name + * @type {string} + */ + +/** + * @property + * @summary auction delay + * @name ModuleConfig#auctionDelay + * @type {number} + */ + +/** + * @property + * @summary params for provide (sub module) + * @name ModuleConfig#params + * @type {Object} + */ + +import {getGlobal} from '../../src/prebidGlobal'; +import {config} from '../../src/config.js'; +import {targeting} from '../../src/targeting'; +import {getHook, module} from '../../src/hook'; +import * as utils from '../../src/utils'; + +/** @type {string} */ +const MODULE_NAME = 'realTimeData'; +/** @type {RtdSubmodule[]} */ +let subModules = []; +/** @type {ModuleConfig} */ +let _moduleConfig; + +/** + * enable submodule in User ID + * @param {RtdSubmodule} submodule + */ +export function attachRealTimeDataProvider(submodule) { + subModules.push(submodule); +} + +export function init(config) { + const confListener = config.getConfig(MODULE_NAME, ({realTimeData}) => { + if (!realTimeData.dataProviders) { + utils.logError('missing parameters for real time module'); + return; + } + confListener(); // unsubscribe config listener + _moduleConfig = realTimeData; + if (typeof (_moduleConfig.auctionDelay) === 'undefined') { + _moduleConfig.auctionDelay = 0; + } + // delay bidding process only if auctionDelay > 0 + if (!_moduleConfig.auctionDelay > 0) { + getHook('bidsBackCallback').before(setTargetsAfterRequestBids); + } else { + getGlobal().requestBids.before(requestBidsHook); + } + }); +} + +/** + * get data from sub module + * @param {AdUnit[]} adUnits received from auction + * @param {function} callback callback function on data received + */ +function getProviderData(adUnits, callback) { + const callbackExpected = subModules.length; + let dataReceived = []; + let processDone = false; + const dataWaitTimeout = setTimeout(() => { + processDone = true; + callback(dataReceived); + }, _moduleConfig.auctionDelay); + + subModules.forEach(sm => { + sm.getData(adUnits, onDataReceived); + }); + + function onDataReceived(data) { + if (processDone) { + return + } + dataReceived.push(data); + if (dataReceived.length === callbackExpected) { + processDone = true; + clearTimeout(dataWaitTimeout); + callback(dataReceived); + } + } +} + +/** + * run hook after bids request and before callback + * get data from provider and set key values to primary ad server + * @param {function} next - next hook function + * @param {AdUnit[]} adUnits received from auction + */ +export function setTargetsAfterRequestBids(next, adUnits) { + getProviderData(adUnits, (data) => { + if (data && Object.keys(data).length) { + const _mergedData = deepMerge(data); + if (Object.keys(_mergedData).length) { + setDataForPrimaryAdServer(_mergedData); + } + } + next(adUnits); + }); +} + +/** + * deep merge array of objects + * @param {array} arr - objects array + * @return {Object} merged object + */ +export function deepMerge(arr) { + if (!Array.isArray(arr) || !arr.length) { + return {}; + } + return arr.reduce((merged, obj) => { + for (let key in obj) { + if (obj.hasOwnProperty(key)) { + if (!merged.hasOwnProperty(key)) merged[key] = obj[key]; + else { + // duplicate key - merge values + const dp = obj[key]; + for (let dk in dp) { + if (dp.hasOwnProperty(dk)) merged[key][dk] = dp[dk]; + } + } + } + } + return merged; + }, {}); +} + +/** + * run hook before bids request + * get data from provider and set key values to primary ad server & bidders + * @param {function} fn - hook function + * @param {Object} reqBidsConfigObj - request bids object + */ +export function requestBidsHook(fn, reqBidsConfigObj) { + getProviderData(reqBidsConfigObj.adUnits || getGlobal().adUnits, (data) => { + if (data && Object.keys(data).length) { + const _mergedData = deepMerge(data); + if (Object.keys(_mergedData).length) { + setDataForPrimaryAdServer(_mergedData); + addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, _mergedData); + } + } + return fn.call(this, reqBidsConfigObj); + }); +} + +/** + * set data to primary ad server + * @param {Object} data - key values to set + */ +function setDataForPrimaryAdServer(data) { + if (!utils.isGptPubadsDefined()) { + utils.logError('window.googletag is not defined on the page'); + return; + } + targeting.setTargetingForGPT(data, null); +} + +/** + * @param {AdUnit[]} adUnits + * @param {Object} data - key values to set + */ +function addIdDataToAdUnitBids(adUnits, data) { + adUnits.forEach(adUnit => { + adUnit.bids = adUnit.bids.map(bid => { + const rd = data[adUnit.code] || {}; + return Object.assign(bid, {realTimeData: rd}); + }) + }); +} + +init(config); +module('realTimeData', attachRealTimeDataProvider); diff --git a/modules/rtdModule/provider.md b/modules/rtdModule/provider.md new file mode 100644 index 00000000000..fb42e7188d3 --- /dev/null +++ b/modules/rtdModule/provider.md @@ -0,0 +1,27 @@ +New provider must include the following: + +1. sub module object: +``` +export const subModuleName = { + name: String, + getData: Function +}; +``` + +2. Function that returns the real time data according to the following structure: +``` +{ + "adUnitCode":{ + "key":"value", + "key2":"value" + }, + "adUnitCode2":{ + "dataKey":"dataValue", + } +} +``` + +3. Hook to Real Time Data module: +``` +submodule('realTimeData', subModuleName); +``` diff --git a/modules/rtdModule/realTimeData.md b/modules/rtdModule/realTimeData.md new file mode 100644 index 00000000000..b2859098b1f --- /dev/null +++ b/modules/rtdModule/realTimeData.md @@ -0,0 +1,32 @@ +## Real Time Data Configuration Example + +Example showing config using `browsi` sub module +``` + pbjs.setConfig({ + "realTimeData": { + "auctionDelay": 1000, + dataProviders[{ + "name": "browsi", + "params": { + "url": "testUrl.com", + "siteKey": "testKey", + "pubKey": "testPub", + "keyName":"bv" + } + }] + } + }); +``` + +Example showing real time data object received form `browsi` real time data provider +``` +{ + "adUnitCode":{ + "key":"value", + "key2":"value" + }, + "adUnitCode2":{ + "dataKey":"dataValue", + } +} +``` diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index d07b6731cda..f42ed0be5b6 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -44,6 +44,7 @@ var sizeMap = { 39: '750x100', 40: '750x200', 41: '750x300', + 42: '2x4', 43: '320x50', 44: '300x50', 48: '300x300', @@ -88,8 +89,11 @@ var sizeMap = { 199: '640x200', 213: '1030x590', 214: '980x360', + 221: '1x1', 229: '320x180', 232: '580x400', + 234: '6x6', + 251: '2x2', 257: '400x600', 264: '970x1000', 265: '1920x1080', @@ -151,7 +155,7 @@ export const spec = { id: bidRequest.adUnitCode, secure: 1, ext: { - rubicon: bidRequest.params + [bidRequest.bidder]: bidRequest.params }, video: utils.deepAccess(bidRequest, 'mediaTypes.video') || {} }], @@ -171,12 +175,20 @@ export const spec = { } } } + + // Add alias if it is there + if (bidRequest.bidder !== 'rubicon') { + data.ext.prebid.aliases = { + [bidRequest.bidder]: 'rubicon' + } + } + const bidFloor = parseFloat(utils.deepAccess(bidRequest, 'params.floor')); if (!isNaN(bidFloor)) { data.imp[0].bidfloor = bidFloor; } // if value is set, will overwrite with same value - data.imp[0].ext.rubicon.video.size_id = determineRubiconVideoSizeId(bidRequest) + data.imp[0].ext[bidRequest.bidder].video.size_id = determineRubiconVideoSizeId(bidRequest) appendSiteAppDevice(data, bidRequest, bidderRequest); @@ -194,21 +206,16 @@ export const spec = { gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; } - if (data.regs) { - if (data.regs.ext) { - data.regs.ext.gdpr = gdprApplies; - } else { - data.regs.ext = {gdpr: gdprApplies}; - } - } else { - data.regs = {ext: {gdpr: gdprApplies}}; - } - + utils.deepSetValue(data, 'regs.ext.gdpr', gdprApplies); utils.deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } + if (bidderRequest.uspConsent) { + utils.deepSetValue(data, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + if (bidRequest.userId && typeof bidRequest.userId === 'object' && - (bidRequest.userId.tdid || bidRequest.userId.pubcid)) { + (bidRequest.userId.tdid || bidRequest.userId.pubcid || bidRequest.userId.lipb)) { utils.deepSetValue(data, 'user.ext.eids', []); if (bidRequest.userId.tdid) { @@ -231,12 +238,35 @@ export const spec = { }] }); } + + // support liveintent ID + if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { + data.user.ext.eids.push({ + source: 'liveintent.com', + uids: [{ + id: bidRequest.userId.lipb.lipbid + }] + }); + + data.user.ext.tpid = { + source: 'liveintent.com', + uid: bidRequest.userId.lipb.lipbid + }; + + if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { + utils.deepSetValue(data, 'rp.target.LIseg', bidRequest.userId.lipb.segments); + } + } } if (config.getConfig('coppa') === true) { utils.deepSetValue(data, 'regs.coppa', 1); } + if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { + utils.deepSetValue(data, 'source.ext.schain', bidRequest.schain); + } + return { method: 'POST', url: VIDEO_ENDPOINT, @@ -254,7 +284,7 @@ export const spec = { url: FASTLANE_ENDPOINT, data: spec.getOrderedParams(bidParams).reduce((paramString, key) => { const propValue = bidParams[key]; - return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${key}=${encodeURIComponent(propValue)}&` : paramString; + return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; }, '') + `slots=1&rand=${Math.random()}`, bidRequest }; @@ -285,7 +315,7 @@ export const spec = { url: FASTLANE_ENDPOINT, data: spec.getOrderedParams(combinedSlotParams).reduce((paramString, key) => { const propValue = combinedSlotParams[key]; - return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${key}=${encodeURIComponent(propValue)}&` : paramString; + return ((utils.isStr(propValue) && propValue !== '') || utils.isNumber(propValue)) ? `${paramString}${encodeParam(key, propValue)}&` : paramString; }, '') + `slots=${bidsInGroup.length}&rand=${Math.random()}`, bidRequest: bidsInGroup }); @@ -301,7 +331,6 @@ export const spec = { const containsTgI = /^tg_i/ const orderedParams = [ - 'tpid_tdid', 'account_id', 'site_id', 'zone_id', @@ -310,10 +339,15 @@ export const spec = { 'p_pos', 'gdpr', 'gdpr_consent', - 'rf', + 'us_privacy', + 'rp_schain', + 'tpid_tdid', + 'tpid_liveintent.com', + 'tg_v.LIseg', 'dt.id', 'dt.keyv', 'dt.pref', + 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw' @@ -322,6 +356,7 @@ export const spec = { .concat([ 'tk_flint', 'x_source.tid', + 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', @@ -396,6 +431,7 @@ export const spec = { 'rp_secure': '1', 'tk_flint': `${configIntType || DEFAULT_INTEGRATION}_v$prebid.version$`, 'x_source.tid': bidRequest.transactionId, + 'x_source.pchain': params.pchain, 'p_screen_res': _getScreenResolution(), 'kw': Array.isArray(params.keywords) ? params.keywords.join(',') : '', 'tk_user_key': params.userId, @@ -409,8 +445,18 @@ export const spec = { // For SRA we need to explicitly put empty semi colons so AE treats it as empty, instead of copying the latter value data['p_pos'] = (params.position === 'atf' || params.position === 'btf') ? params.position : ''; - if ((bidRequest.userId || {}).tdid) { - data['tpid_tdid'] = bidRequest.userId.tdid; + if (bidRequest.userId) { + if (bidRequest.userId.tdid) { + data['tpid_tdid'] = bidRequest.userId.tdid; + } + + // support liveintent ID + if (bidRequest.userId.lipb && bidRequest.userId.lipb.lipbid) { + data['tpid_liveintent.com'] = bidRequest.userId.lipb.lipbid; + if (Array.isArray(bidRequest.userId.lipb.segments) && bidRequest.userId.lipb.segments.length) { + data['tg_v.LIseg'] = bidRequest.userId.lipb.segments.join(','); + } + } } if (bidderRequest.gdprConsent) { @@ -421,6 +467,10 @@ export const spec = { data['gdpr_consent'] = bidderRequest.gdprConsent.consentString; } + if (bidderRequest.uspConsent) { + data['us_privacy'] = encodeURIComponent(bidderRequest.uspConsent); + } + // visitor properties if (params.visitor !== null && typeof params.visitor === 'object') { Object.keys(params.visitor).forEach((key) => { @@ -447,9 +497,38 @@ export const spec = { data['coppa'] = 1; } + // if SupplyChain is supplied and contains all required fields + if (bidRequest.schain && hasValidSupplyChainParams(bidRequest.schain)) { + data.rp_schain = spec.serializeSupplyChain(bidRequest.schain); + } + return data; }, + /** + * Serializes schain params according to OpenRTB requirements + * @param {Object} supplyChain + * @returns {String} + */ + serializeSupplyChain: function (supplyChain) { + const supplyChainIsValid = hasValidSupplyChainParams(supplyChain); + if (!supplyChainIsValid) return ''; + const { ver, complete, nodes } = supplyChain; + return `${ver},${complete}!${spec.serializeSupplyChainNodes(nodes)}`; + }, + + /** + * Properly sorts schain object params + * @param {Array} nodes + * @returns {String} + */ + serializeSupplyChainNodes: function (nodes) { + const nodePropOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + return nodes.map(node => { + return nodePropOrder.map(prop => encodeURIComponent(node[prop] || '')).join(','); + }).join('!'); + }, + /** * @param {*} responseObj * @param {BidRequest|Object.} bidRequest - if request was SRA the bidRequest argument will be a keyed BidRequest array object, @@ -480,7 +559,7 @@ export const spec = { cpm: bid.price || 0, bidderCode: seatbid.seat, ttl: 300, - netRevenue: config.getConfig('rubicon.netRevenue') || true, + netRevenue: config.getConfig('rubicon.netRevenue') !== false, // If anything other than false, netRev is true width: bid.w || utils.deepAccess(bidRequest, 'mediaTypes.video.w') || utils.deepAccess(bidRequest, 'params.video.playerWidth'), height: bid.h || utils.deepAccess(bidRequest, 'mediaTypes.video.h') || utils.deepAccess(bidRequest, 'params.video.playerHeight'), }; @@ -560,7 +639,7 @@ export const spec = { cpm: ad.cpm || 0, dealId: ad.deal, ttl: 300, // 5 minutes - netRevenue: config.getConfig('rubicon.netRevenue') || false, + netRevenue: config.getConfig('rubicon.netRevenue') !== false, // If anything other than false, netRev is true rubicon: { advertiserId: ad.advertiser, networkId: ad.network }, @@ -601,7 +680,7 @@ export const spec = { return (adB.cpm || 0.0) - (adA.cpm || 0.0); }); }, - getUserSyncs: function (syncOptions, responses, gdprConsent) { + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to SYNC_ENDPOINT let params = ''; @@ -615,6 +694,10 @@ export const spec = { } } + if (uspConsent) { + params += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; + } + hasSynced = true; return { type: 'iframe', @@ -947,6 +1030,35 @@ export function hasValidVideoParams(bid) { return isValid; } +/** + * Make sure the required params are present + * @param {Object} schain + * @param {Bool} + */ +export function hasValidSupplyChainParams(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node[field]); + }, true); + if (!isValid) utils.logError('Rubicon: required schain params missing'); + return isValid; +} + +/** + * Creates a URL key value param, encoding the + * param unless the key is schain + * @param {String} key + * @param {String} param + * @returns {String} + */ +export function encodeParam(key, param) { + if (key === 'rp_schain') return `rp_schain=${param}`; + return `${key}=${encodeURIComponent(param)}`; +} + /** * split array into multiple arrays of defined size * @param {Array} array diff --git a/modules/shBidAdapter.js b/modules/showheroes-bsBidAdapter.js similarity index 100% rename from modules/shBidAdapter.js rename to modules/showheroes-bsBidAdapter.js diff --git a/modules/shBidAdapter.md b/modules/showheroes-bsBidAdapter.md similarity index 100% rename from modules/shBidAdapter.md rename to modules/showheroes-bsBidAdapter.md diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 401c72bffaf..f081c8ac8e1 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,4 +1,8 @@ import * as utils from '../src/utils'; +import { + BANNER, + VIDEO +} from '../src/mediaTypes'; import { config } from '../src/config'; @@ -9,6 +13,7 @@ const BIDDER_CODE = 'smartadserver'; export const spec = { code: BIDDER_CODE, aliases: ['smart'], // short code + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. * @@ -33,6 +38,7 @@ export const spec = { // pull requested transaction ID from bidderRequest.bids[].transactionId return validBidRequests.map(bid => { + // Common bid request attributes for banner, outstream and instream. var payload = { siteid: bid.params.siteId, pageid: bid.params.pageId, @@ -44,10 +50,6 @@ export const spec = { appname: bid.params.appName && bid.params.appName != '' ? bid.params.appName : undefined, ckid: bid.params.ckId || 0, tagId: bid.adUnitCode, - sizes: bid.sizes.map(size => ({ - w: size[0], - h: size[1] - })), pageDomain: utils.getTopWindowUrl(), transactionId: bid.transactionId, timeout: config.getConfig('bidderTimeout'), @@ -55,6 +57,26 @@ export const spec = { prebidVersion: '$prebid.version$' }; + const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); + if (!videoMediaType) { + payload.sizes = bid.sizes.map(size => ({ + w: size[0], + h: size[1] + })); + } else if (videoMediaType && videoMediaType.context === 'instream') { + // Specific attributes for instream. + var playerSize = videoMediaType.playerSize[0]; + payload.isVideo = true; + payload.videoData = { + videoProtocol: bid.params.video.protocol, + playerWidth: playerSize[0], + playerHeight: playerSize[1], + adBreak: bid.params.video.startDelay || 0 + }; + } else { + return {}; + } + if (bidderRequest && bidderRequest.gdprConsent) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; payload.gdpr = bidderRequest.gdprConsent.gdprApplies; // we're handling the undefined case server side @@ -74,13 +96,15 @@ export const spec = { * @param {*} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse, bidRequest) { + interpretResponse: function (serverResponse, bidRequestString) { const bidResponses = []; var response = serverResponse.body; try { if (response) { - const bidResponse = { - requestId: JSON.parse(bidRequest.data).bidId, + const bidRequest = JSON.parse(bidRequestString.data); + + var bidResponse = { + requestId: bidRequest.bidId, cpm: response.cpm, width: response.width, height: response.height, @@ -89,10 +113,18 @@ export const spec = { currency: response.currency, netRevenue: response.isNetCpm, ttl: response.ttl, - referrer: utils.getTopWindowUrl(), - adUrl: response.adUrl, - ad: response.ad + referrer: utils.getTopWindowUrl() }; + + if (bidRequest.isVideo) { + bidResponse.mediaType = VIDEO; + bidResponse.vastUrl = response.adUrl; + bidResponse.vastXml = response.ad; + } else { + bidResponse.adUrl = response.adUrl; + bidResponse.ad = response.ad; + } + bidResponses.push(bidResponse); } } catch (error) { diff --git a/modules/smartadserverBidAdapter.md b/modules/smartadserverBidAdapter.md index 1200c0961a0..c1fc0d819c5 100644 --- a/modules/smartadserverBidAdapter.md +++ b/modules/smartadserverBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Smart Ad Server Bidder Adapter Module Type: Bidder Adapter -Maintainer: gcarnec@smartadserver.com +Maintainer: support@smartadserver.com ``` # Description @@ -18,45 +18,72 @@ Please reach out to your Technical account manager for more information. ## Web ``` var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "smart", - params: { - domain: 'http://ww251.smartadserver.com', - siteId: 207435, - pageId: 896536, - formatId: 62913, - ckId: 1122334455 // optional - } - } - ] - } - ]; + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "smart", + params: { + domain: 'http://ww251.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 62913, + ckId: 1122334455 // optional + } + } + ] + } + ]; ``` ## In-app ``` var adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], // a display size - bids: [ - { - bidder: "smart", - params: { - domain: 'http://ww251.smartadserver.com', - siteId: 207435, - pageId: 896536, - formatId: 65906, - buId: "com.smartadserver.android.dashboard", // in-app only - appName: "Smart AdServer Preview", // in-app only - ckId: 1122334455 // optional - } - } - ] - } - ]; + { + code: 'test-div', + sizes: [[300, 250]], // a display size + bids: [ + { + bidder: "smart", + params: { + domain: 'http://ww251.smartadserver.com', + siteId: 207435, + pageId: 896536, + formatId: 65906, + buId: "com.smartadserver.android.dashboard", // in-app only + appName: "Smart AdServer Preview", // in-app only + ckId: 1122334455 // optional + } + } + ] + } + ]; +``` + +## Instream Video +``` + var videoAdUnit = { + code: 'test-div', + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bids: [{ + bidder: "smart", + params: { + domain: 'http://ww251.smartadserver.com', + siteId: 326147, + pageId: 1153895, + formatId: 55710 + bidfloor: 5, + video: { + protocol: 6, + startDelay: 0 + } + } + }] + }; ``` \ No newline at end of file diff --git a/modules/smmsBidAdapter.js b/modules/smmsBidAdapter.js new file mode 100644 index 00000000000..dafd4aba734 --- /dev/null +++ b/modules/smmsBidAdapter.js @@ -0,0 +1,155 @@ +import * as utils from '../src/utils'; +import { registerBidder } from '../src/adapters/bidderFactory'; + +const BIDDER_CODE = 'smms'; +const ENDPOINT_BANNER = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/banner'; +const ENDPOINT_NATIVE = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/native'; +const COOKIE_SYNC_URL = 'https://bidder.mediams.mb.softbank.jp/api/v1/cookie/gen'; +const SUPPORTED_MEDIA_TYPES = ['banner', 'native']; +const SUPPORTED_CURRENCIES = ['USD', 'JPY']; +const DEFAULT_CURRENCY = 'JPY'; +const NET_REVENUE = true; + +function _encodeURIComponent(a) { + let b = window.encodeURIComponent(a); + b = b.replace(/'/g, '%27'); + return b; +} + +export function _getUrlVars(url) { + let hash; + const myJson = {}; + const hashes = url.slice(url.indexOf('?') + 1).split('&'); + for (let i = 0; i < hashes.length; i++) { + hash = hashes[i].split('='); + myJson[hash[0]] = hash[1]; + } + return myJson; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + isBidRequestValid: function(bid) { + let valid = !!(bid.params.placementId); + if (valid && bid.params.hasOwnProperty('currency')) { + if (SUPPORTED_CURRENCIES.indexOf(bid.params.currency) === -1) { + utils.logError('Invalid currency type, we support only JPY and USD!'); + valid = false; + } + } + + return valid; + }, + /** + * Make a server request from the list of BidRequests. + * + * @param {validBidRequests[]} - an array of bids + * @return ServerRequest Info describing the request to the server. + */ + buildRequests: function(validBidRequests, bidderRequest) { + const serverRequests = []; + let refererInfo; + if (bidderRequest && bidderRequest.refererInfo) { + refererInfo = bidderRequest.refererInfo; + } + + const g = (typeof (geparams) !== 'undefined' && typeof (geparams) == 'object' && geparams) ? geparams : {}; + validBidRequests.forEach((bid, i) => { + let endpoint = ENDPOINT_BANNER; + let data = { + 'placementid': bid.params.placementId, + 'cur': bid.params.hasOwnProperty('currency') ? bid.params.currency : DEFAULT_CURRENCY, + 'ua': navigator.userAgent, + 'adtk': _encodeURIComponent(g.lat ? '0' : '1'), + 'loc': (refererInfo && refererInfo.referer) ? refererInfo.referer : '', + 'topframe': (window.parent == window.self) ? 1 : 0, + 'sw': screen && screen.width, + 'sh': screen && screen.height, + 'cb': Math.floor(Math.random() * 99999999999), + 'tpaf': 1, + 'cks': 1, + 'requestid': bid.bidId, + 'referer': (refererInfo && refererInfo.referer) ? refererInfo.referer : '' + }; + + if (bid.hasOwnProperty('nativeParams')) { + endpoint = ENDPOINT_NATIVE; + data.tkf = 1; // return url tracker + data.ad_track = '1'; + data.apiv = '1.1.0'; + } + + serverRequests.push({ + method: 'GET', + url: endpoint, + data: utils.parseQueryStringParameters(data) + }); + }); + + return serverRequests; + }, + interpretResponse: function(serverResponse, request) { + const data = _getUrlVars(request.data) + const successBid = serverResponse.body || {}; + let bidResponses = []; + if (successBid.hasOwnProperty(data.placementid)) { + let bid = successBid[data.placementid] + let bidResponse = { + requestId: bid.requestid, + cpm: bid.price, + creativeId: bid.creativeId, + currency: bid.cur, + netRevenue: NET_REVENUE, + ttl: 700 + }; + + if (bid.hasOwnProperty('title')) { // it is native ad response + bidResponse.mediaType = 'native' + bidResponse.native = { + title: bid.title, + body: bid.description, + cta: bid.cta, + sponsoredBy: bid.advertiser, + clickUrl: _encodeURIComponent(bid.landingURL), + impressionTrackers: bid.trackings, + } + if (bid.screenshots) { + bidResponse.native.image = { + url: bid.screenshots.url, + height: bid.screenshots.height, + width: bid.screenshots.width, + } + } + if (bid.icon) { + bidResponse.native.icon = { + url: bid.icon.url, + height: bid.icon.height, + width: bid.icon.width, + } + } + } else { + bidResponse.ad = bid.adm + bidResponse.width = bid.width + bidResponse.height = bid.height + } + + bidResponses.push(bidResponse); + } + + return bidResponses; + }, + getUserSyncs: function(syncOptions, serverResponses) { + let syncs = [] + syncs.push({ + type: 'image', + url: COOKIE_SYNC_URL + }) + + return syncs; + }, + onTimeout: function(timeoutData) {}, + onBidWon: function(bid) {}, + onSetTargeting: function(bid) {} +} +registerBidder(spec); diff --git a/modules/smmsBidAdapter.md b/modules/smmsBidAdapter.md new file mode 100644 index 00000000000..f1d6e233f96 --- /dev/null +++ b/modules/smmsBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +Module Name: SMMS Bid Adapter + +Maintainer: SBBGRP-SSP-PMP@g.softbank.co.jp + +# Description + +Module that connects to softbank's demand sources + +# Test Parameters + +```javascript + var adUnits = [ + { + code: 'test', + mediaTypes: { + banner: { + sizes: [[300, 250], [300,600]], + } + }, + bids: [ + { + bidder: 'smms', + params: { + placementId: 1440837, + currency: 'USD' + } + } + ] + }, + { + code: 'test', + mediaTypes: { + native: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + } + } + }, + bids: [ + { + bidder: 'smms', + params: { + placementId: 1440838, + currency: 'USD' + } + }, + ], + } + ]; +``` diff --git a/modules/somoBidAdapter.js b/modules/somoBidAdapter.js index 95e823a5d94..38e93fcd8a9 100644 --- a/modules/somoBidAdapter.js +++ b/modules/somoBidAdapter.js @@ -61,10 +61,11 @@ export const spec = { function bidResponseAvailable(bidRequest, bidResponse) { let bidResponses = []; + if (bidResponse.body) { let bidData = bidResponse.body.seatbid[0].bid[0]; const bid = { - requestId: bidData.impid, + requestId: bidResponse.body.id, cpm: bidData.price, width: bidData.w, height: bidData.h, @@ -74,6 +75,7 @@ function bidResponseAvailable(bidRequest, bidResponse) { adId: bidData.impid, netRevenue: false, currency: 'USD', + adUnitCode: bidRequest.bidRequest.adUnitCode }; if (isVideo(bidRequest.bidRequest)) { bid.vastXml = bidData.adm; @@ -89,7 +91,7 @@ function bidResponseAvailable(bidRequest, bidResponse) { function openRtbRequest(bidRequest, bidderRequest) { var openRtbRequest = { - id: bidRequest.bidderRequestId, + id: bidRequest.bidId, imp: [openRtbImpression(bidRequest)], at: 1, tmax: 400, diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index 8b08b3ad6be..5cd76981e8d 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -1,8 +1,7 @@ import * as utils from '../src/utils' +import {parse} from '../src/url' import { registerBidder } from '../src/adapters/bidderFactory' import { BANNER } from '../src/mediaTypes' -const errorUrl = 'https://pcb.aws.lijit.com/c' -let errorpxls = [] export const spec = { code: 'sovrn', @@ -24,14 +23,30 @@ export const spec = { */ buildRequests: function(bidReqs, bidderRequest) { try { - const loc = utils.getTopWindowLocation(); let sovrnImps = []; let iv; + let schain; + let digitrust; + utils._each(bidReqs, function (bid) { + if (!digitrust) { + const bidRequestDigitrust = utils.deepAccess(bid, 'userId.digitrustid.data'); + if (bidRequestDigitrust && (!bidRequestDigitrust.privacy || !bidRequestDigitrust.privacy.optout)) { + digitrust = { + id: bidRequestDigitrust.id, + keyv: bidRequestDigitrust.keyv + } + } + } + if (bid.schain) { + schain = schain || bid.schain; + } iv = iv || utils.getBidIdParameter('iv', bid.params); - bid.sizes = ((utils.isArray(bid.sizes) && utils.isArray(bid.sizes[0])) ? bid.sizes : [bid.sizes]) - bid.sizes = bid.sizes.filter(size => utils.isArray(size)) - const processedSizes = bid.sizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) + + let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; + bidSizes = ((utils.isArray(bidSizes) && utils.isArray(bidSizes[0])) ? bidSizes : [bidSizes]) + bidSizes = bidSizes.filter(size => utils.isArray(size)) + const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})) sovrnImps.push({ id: bid.bidId, banner: { @@ -43,27 +58,45 @@ export const spec = { bidfloor: utils.getBidIdParameter('bidfloor', bid.params) }); }); + + const page = bidderRequest.refererInfo.referer + + // clever trick to get the domain + const domain = parse(page).hostname + const sovrnBidReq = { id: utils.getUniqueIdentifierStr(), imp: sovrnImps, site: { - domain: loc.host, - page: loc.host + loc.pathname + loc.search + loc.hash + page, + domain } }; - if (bidderRequest && bidderRequest.gdprConsent) { - sovrnBidReq.regs = { - ext: { - gdpr: +bidderRequest.gdprConsent.gdprApplies - }}; - sovrnBidReq.user = { + if (schain) { + sovrnBidReq.source = { ext: { - consent: bidderRequest.gdprConsent.consentString - }}; + schain + } + }; + } + + if (bidderRequest.gdprConsent) { + utils.deepSetValue(sovrnBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); + utils.deepSetValue(sovrnBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString) + } + if (bidderRequest.uspConsent) { + utils.deepSetValue(sovrnBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + + if (digitrust) { + utils.deepSetValue(sovrnBidReq, 'user.ext.digitrust', { + id: digitrust.id, + keyv: digitrust.keyv + }) } - let url = `//ap.lijit.com/rtb/bid?` + + let url = `https://ap.lijit.com/rtb/bid?` + `src=$$REPO_AND_VERSION$$`; if (iv) url += `&iv=${iv}`; @@ -74,7 +107,8 @@ export const spec = { options: {contentType: 'text/plain'} } } catch (e) { - new LogError(e, {bidReqs, bidderRequest}).append() + console.log('error in build:') + console.log(e) } }, @@ -109,74 +143,48 @@ export const spec = { } return sovrnBidResponses } catch (e) { - new LogError(e, {id, seatbid}).append() + console.log('error in interpret:') + console.log(e) } }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent) { + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { try { - let tracks = [] - if (serverResponses && serverResponses.length !== 0 && syncOptions.iframeEnabled) { - let iidArr = serverResponses.filter(rsp => rsp.body && rsp.body.ext && rsp.body.ext.iid) - .map(rsp => { return rsp.body.ext.iid }); - let consentString = ''; - if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { - consentString = gdprConsent.consentString + const tracks = [] + if (serverResponses && serverResponses.length !== 0) { + if (syncOptions.iframeEnabled) { + const iidArr = serverResponses.filter(resp => utils.deepAccess(resp, 'body.ext.iid')) + .map(resp => resp.body.ext.iid); + const params = []; + if (gdprConsent && gdprConsent.gdprApplies && typeof gdprConsent.consentString === 'string') { + params.push(['gdpr_consent', gdprConsent.consentString]); + } + if (uspConsent) { + params.push(['us_privacy', uspConsent]); + } + + if (iidArr[0]) { + params.push(['informer', iidArr[0]]); + tracks.push({ + type: 'iframe', + url: 'https://ap.lijit.com/beacon?' + params.map(p => p.join('=')).join('&') + }); + } } - if (iidArr[0]) { - tracks.push({ - type: 'iframe', - url: '//ap.lijit.com/beacon?informer=' + iidArr[0] + '&gdpr_consent=' + consentString, - }); + + if (syncOptions.pixelEnabled) { + serverResponses.filter(resp => utils.deepAccess(resp, 'body.ext.sync.pixels')) + .reduce((acc, resp) => acc.concat(resp.body.ext.sync.pixels), []) + .map(pixel => pixel.url) + .forEach(url => tracks.push({ type: 'image', url })) } } - if (errorpxls.length && syncOptions.pixelEnabled) { - tracks = tracks.concat(errorpxls) - } + return tracks } catch (e) { - if (syncOptions.pixelEnabled) { - return errorpxls - } return [] } }, } -export class LogError { - constructor(e, data) { - utils.logError(e) - this.error = {} - this.error.t = utils.timestamp() - this.error.m = e.message - this.error.s = e.stack - this.error.d = data - this.error.v = $$REPO_AND_VERSION$$ - this.error.u = utils.getTopWindowLocation().href - this.error.ua = navigator.userAgent - } - buildErrorString(obj) { - return errorUrl + '?b=' + btoa(JSON.stringify(obj)) - } - append() { - let errstr = this.buildErrorString(this.error) - if (errstr.length > 2083) { - delete this.error.d - errstr = this.buildErrorString(this.error) - if (errstr.length > 2083) { - delete this.error.s - errstr = this.buildErrorString(this.error) - if (errstr.length > 2083) { - errstr = this.buildErrorString({m: 'unknown error message', t: this.error.t, u: this.error.u}) - } - } - } - let obj = {type: 'image', url: errstr} - errorpxls.push(obj) - } - static getErrPxls() { - return errorpxls - } -} - registerBidder(spec); diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js index cfa308a8670..ef07190de5d 100644 --- a/modules/spotxBidAdapter.js +++ b/modules/spotxBidAdapter.js @@ -212,12 +212,15 @@ export const spec = { // ID5 fied if (bid && bid.userId && bid.userId.id5id) { - userExt.eids = [{ - source: 'id5-sync.com', - uids: [{ - id: bid.userId.id5id - }] - }]; + userExt.eids = userExt.eids || []; + userExt.eids.push( + { + source: 'id5-sync.com', + uids: [{ + id: bid.userId.id5id + }] + } + ) } // Add common id if available @@ -234,6 +237,21 @@ export const spec = { }; } + if (bid && bid.userId && bid.userId.tdid) { + userExt.eids = userExt.eids || []; + userExt.eids.push( + { + source: 'adserver.org', + uids: [{ + id: bid.userId.tdid, + ext: { + rtiPartner: 'TDID' + } + }] + } + ) + } + // Only add the user object if it's not empty if (!utils.isEmpty(userExt)) { requestPayload.user = { ext: userExt }; diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js index 4d8b81b7be2..225f8cdc43d 100644 --- a/modules/staqAnalyticsAdapter.js +++ b/modules/staqAnalyticsAdapter.js @@ -1,10 +1,10 @@ import adapter from '../src/AnalyticsAdapter'; import CONSTANTS from '../src/constants.json'; import adapterManager from '../src/adapterManager'; -import {getRefererInfo} from '../src/refererDetection'; -import {parse} from '../src/url'; +import { getRefererInfo } from '../src/refererDetection'; +import { parse } from '../src/url'; import * as utils from '../src/utils'; -import {ajax} from '../src/ajax'; +import { ajax } from '../src/ajax'; const ANALYTICS_VERSION = '1.0.0'; const DEFAULT_QUEUE_TIMEOUT = 4000; @@ -43,50 +43,49 @@ function buildRequestTemplate(connId) { } } -let analyticsAdapter = Object.assign(adapter({analyticsType: 'endpoint'}), - { - track({ eventType, args }) { - if (!analyticsAdapter.context) { - return; - } - let handler = null; - switch (eventType) { - case CONSTANTS.EVENTS.AUCTION_INIT: - if (analyticsAdapter.context.queue) { - analyticsAdapter.context.queue.init(); - } - handler = trackAuctionInit; - break; - case CONSTANTS.EVENTS.BID_REQUESTED: - handler = trackBidRequest; - break; - case CONSTANTS.EVENTS.BID_RESPONSE: - handler = trackBidResponse; - break; - case CONSTANTS.EVENTS.BID_WON: - handler = trackBidWon; - break; - case CONSTANTS.EVENTS.BID_TIMEOUT: - handler = trackBidTimeout; - break; - case CONSTANTS.EVENTS.AUCTION_END: - handler = trackAuctionEnd; - break; - } - if (handler) { - let events = handler(args); +let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { + track({ eventType, args }) { + if (!analyticsAdapter.context) { + return; + } + let handler = null; + switch (eventType) { + case CONSTANTS.EVENTS.AUCTION_INIT: if (analyticsAdapter.context.queue) { - analyticsAdapter.context.queue.push(events); - if (eventType === CONSTANTS.EVENTS.BID_WON) { - analyticsAdapter.context.queue.updateWithWins(events); - } + analyticsAdapter.context.queue.init(); } - if (eventType === CONSTANTS.EVENTS.AUCTION_END) { - sendAll(); + handler = trackAuctionInit; + break; + case CONSTANTS.EVENTS.BID_REQUESTED: + handler = trackBidRequest; + break; + case CONSTANTS.EVENTS.BID_RESPONSE: + handler = trackBidResponse; + break; + case CONSTANTS.EVENTS.BID_WON: + handler = trackBidWon; + break; + case CONSTANTS.EVENTS.BID_TIMEOUT: + handler = trackBidTimeout; + break; + case CONSTANTS.EVENTS.AUCTION_END: + handler = trackAuctionEnd; + break; + } + if (handler) { + let events = handler(args); + if (analyticsAdapter.context.queue) { + analyticsAdapter.context.queue.push(events); + if (eventType === CONSTANTS.EVENTS.BID_WON) { + analyticsAdapter.context.queue.updateWithWins(events); } } + if (eventType === CONSTANTS.EVENTS.AUCTION_END) { + sendAll(); + } } - }); + } +}); analyticsAdapter.context = {}; @@ -123,17 +122,17 @@ export default analyticsAdapter; function sendAll() { let events = analyticsAdapter.context.queue.popAll(); if (events.length !== 0) { - let req = events.map(event => { - return Object.assign({}, event, analyticsAdapter.context.requestTemplate) - }); + let req = analyticsAdapter.context.requestTemplate; + req.auctionId = analyticsAdapter.context.auctionId; + req.events = events + analyticsAdapter.ajaxCall(JSON.stringify(req)); } } analyticsAdapter.ajaxCall = function ajaxCall(data) { utils.logInfo('SENDING DATA: ' + data); - ajax(`//${analyticsAdapter.context.url}/prebid/${analyticsAdapter.context.connectionId}`, () => { - }, data, {contentType: 'text/plain'}); + ajax(`//${analyticsAdapter.context.url}/prebid/${analyticsAdapter.context.connectionId}`, () => {}, data, { contentType: 'text/plain' }); }; function trackAuctionInit(args) { @@ -166,9 +165,7 @@ function trackAuctionEnd(args) { } function trackBidTimeout(args) { - return args.map(arg => - createHbEvent(arg.auctionId, arg.bidderCode, STAQ_EVENTS.TIMEOUT) - ); + return args.map(arg => createHbEvent(arg.auctionId, arg.bidderCode, STAQ_EVENTS.TIMEOUT)); } function createHbEvent(auctionId, adapter, event, adUnitCode = undefined, value = 0, time = 0, bidWon = undefined, eventArgs) { @@ -206,7 +203,8 @@ function createHbEvent(auctionId, adapter, event, adUnitCode = undefined, value } const UTM_TAGS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', - 'utm_c1', 'utm_c2', 'utm_c3', 'utm_c4', 'utm_c5']; + 'utm_c1', 'utm_c2', 'utm_c3', 'utm_c4', 'utm_c5' +]; const STAQ_PREBID_KEY = 'staq_analytics'; const DIRECT = '(direct)'; const REFERRAL = '(referral)'; @@ -334,7 +332,8 @@ export function getUmtSource(pageUrl, referrer) { function chooseActualUtm(prev, curr) { if (ord(prev) < ord(curr)) { return [true, curr]; - } if (ord(prev) > ord(curr)) { + } + if (ord(prev) > ord(curr)) { return [false, prev]; } else { if (prev.campaign === REFERRAL && prev.content !== curr.content) { diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index ac655f2013d..b8fb58c0ab3 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -28,7 +28,7 @@ export const spec = { const placementId = params.placement; const rnd = Math.floor(Math.random() * 99999999999); - const referrer = encodeURIComponent(bidderRequest.refererInfo.referer); + const referrer = bidderRequest.refererInfo.referer; const bidId = bidRequest.bidId; let endpoint = VADS_ENDPOINT_URL; diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index 121232a6605..856df879270 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -46,6 +46,10 @@ export const spec = { hb_version: '$prebid.version$' }; + if (validBidRequests[0].schain) { + payload.schain = validBidRequests[0].schain; + } + let gdpr = bidderRequest.gdprConsent; if (bidderRequest && gdpr) { let isCmp = (typeof gdpr.gdprApplies === 'boolean') diff --git a/modules/trionBidAdapter.js b/modules/trionBidAdapter.js index 97f82f57b85..3595b709a51 100644 --- a/modules/trionBidAdapter.js +++ b/modules/trionBidAdapter.js @@ -84,6 +84,9 @@ function buildTrionUrlParams(bid) { var re = utils.getBidIdParameter('re', bid.params); var url = utils.getTopWindowUrl(); var sizes = utils.parseSizesInput(bid.sizes).join(','); + var isAutomated = (navigator && navigator.webdriver) ? 1 : 0; + var isHidden = (document.hidden) ? 1 : 0; + var visibilityState = encodeURIComponent(document.visibilityState); var intT = window.TR_INT_T && window.TR_INT_T != -1 ? window.TR_INT_T : null; if (!intT) { @@ -108,6 +111,9 @@ function buildTrionUrlParams(bid) { if (intT) { trionUrl = utils.tryAppendQueryString(trionUrl, 'int_t', encodeURIComponent(intT)); } + trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_wd', isAutomated); + trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_hd', isHidden); + trionUrl = utils.tryAppendQueryString(trionUrl, 'tr_vs', visibilityState); // remove the trailing "&" if (trionUrl.lastIndexOf('&') === trionUrl.length - 1) { diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index d49e4cde480..7120929059d 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -42,6 +42,10 @@ export const tripleliftAdapterSpec = { } } + if (bidderRequest && bidderRequest.uspConsent) { + tlCall = utils.tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); + } + if (tlCall.lastIndexOf('&') === tlCall.length - 1) { tlCall = tlCall.substring(0, tlCall.length - 1); } @@ -62,22 +66,39 @@ export const tripleliftAdapterSpec = { }); }, - getUserSyncs: function(syncOptions) { - let ibCall = '//ib.3lift.com/sync?'; + getUserSyncs: function(syncOptions, responses, gdprConsent, usPrivacy) { + let syncType = _getSyncType(syncOptions); + if (!syncType) return; + + let syncEndpoint = 'https://eb2.3lift.com/sync?'; + + if (syncType === 'image') { + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'px', 1); + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'src', 'prebid'); + } + if (consentString !== null) { - ibCall = utils.tryAppendQueryString(ibCall, 'gdpr', gdprApplies); - ibCall = utils.tryAppendQueryString(ibCall, 'cmp_cs', consentString); + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'gdpr', gdprApplies); + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'cmp_cs', consentString); } - if (syncOptions.iframeEnabled) { - return [{ - type: 'iframe', - url: ibCall - }]; + if (usPrivacy) { + syncEndpoint = utils.tryAppendQueryString(syncEndpoint, 'us_privacy', usPrivacy); } + + return [{ + type: syncType, + url: syncEndpoint + }]; } } +function _getSyncType(syncOptions) { + if (!syncOptions) return; + if (syncOptions.iframeEnabled) return 'iframe'; + if (syncOptions.pixelEnabled) return 'image'; +} + function _buildPostBody(bidRequests) { let data = {}; let { schain } = bidRequests[0]; diff --git a/modules/trustxBidAdapter.js b/modules/trustxBidAdapter.js index 00c86dec0d3..94d541d1fe2 100644 --- a/modules/trustxBidAdapter.js +++ b/modules/trustxBidAdapter.js @@ -121,6 +121,9 @@ export const spec = { (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } + if (bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } } return { diff --git a/modules/ucfunnelAnalyticsAdapter.js b/modules/ucfunnelAnalyticsAdapter.js new file mode 100644 index 00000000000..3a1f29c5225 --- /dev/null +++ b/modules/ucfunnelAnalyticsAdapter.js @@ -0,0 +1,198 @@ +import {ajax} from '../src/ajax'; +import adapter from '../src/AnalyticsAdapter'; +import CONSTANTS from '../src/constants.json'; +import adapterManager from '../src/adapterManager'; +import {logError, logInfo, deepClone} from '../src/utils'; + +const analyticsType = 'endpoint'; + +export const ANALYTICS_VERSION = '1.0.0'; + +const ANALYTICS_SERVER = 'https://hbwa.aralego.com'; + +const { + EVENTS: { + AUCTION_END, + BID_WON, + BID_TIMEOUT + } +} = CONSTANTS; + +export const BIDDER_STATUS = { + BID: 'bid', + NO_BID: 'noBid', + BID_WON: 'bidWon', + TIMEOUT: 'timeout' +}; + +const analyticsOptions = {}; + +export const parseBidderCode = function (bid) { + let bidderCode = bid.bidderCode || bid.bidder; + return bidderCode.toLowerCase(); +}; + +export const parseAdUnitCode = function (bidResponse) { + return bidResponse.adUnitCode.toLowerCase(); +}; + +export const ucfunnelAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER, analyticsType}), { + + cachedAuctions: {}, + + initConfig(config) { + /** + * Required option: pbuid + * Required option: adid + * @type {boolean} + */ + analyticsOptions.options = deepClone(config.options); + if (typeof config.options.pbuid !== 'string' || config.options.pbuid.length < 1) { + logError('"options.pbuid" is required.'); + return false; + } + if (typeof config.options.adid !== 'string' || config.options.adid.length < 1) { + logError('"options.adid" is required.'); + return false; + } + analyticsOptions.sampled = true; + if (typeof config.options.sampling === 'number') { + analyticsOptions.sampled = Math.random() < parseFloat(config.options.sampling); + } + + analyticsOptions.pbuid = config.options.pbuid + analyticsOptions.adid = config.options.adid + analyticsOptions.server = ANALYTICS_SERVER; + return true; + }, + sendEventMessage(endPoint, data) { + logInfo(`AJAX: ${endPoint}: ` + JSON.stringify(data)); + + ajax(`${analyticsOptions.server}/${endPoint}`, null, JSON.stringify(data), { + contentType: 'application/json', + withCredentials: true + }); + }, + createCommonMessage(auctionId) { + return { + version: ANALYTICS_VERSION, + auctionId: auctionId, + referrer: window.location.href, + sampling: analyticsOptions.options.sampling, + prebid: '$prebid.version$', + adid: analyticsOptions.adid, + pbuid: analyticsOptions.pbuid, + adUnits: {}, + }; + }, + serializeBidResponse(bid, status) { + const result = { + prebidWon: (status === BIDDER_STATUS.BID_WON), + isTimeout: (status === BIDDER_STATUS.TIMEOUT), + status: status, + }; + if (status === BIDDER_STATUS.BID || status === BIDDER_STATUS.BID_WON) { + Object.assign(result, { + time: bid.timeToRespond, + cpm: bid.cpm, + currency: bid.currency, + }); + } + return result; + }, + addBidResponseToMessage(message, bid, status) { + const adUnitCode = parseAdUnitCode(bid); + message.adUnits[adUnitCode] = message.adUnits[adUnitCode] || {}; + const bidder = parseBidderCode(bid); + const bidResponse = this.serializeBidResponse(bid, status); + message.adUnits[adUnitCode][bidder] = bidResponse; + }, + createBidMessage(auctionEndArgs, winningBids, timeoutBids) { + const {auctionId, timestamp, auctionEnd, adUnitCodes, bidsReceived, noBids} = auctionEndArgs; + const message = this.createCommonMessage(auctionId); + + message.auctionElapsed = (auctionEnd - timestamp); + + adUnitCodes.forEach((adUnitCode) => { + message.adUnits[adUnitCode] = {}; + }); + + // In this situation, the bid exists in both noBids and bids arrays. + noBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.NO_BID)); + + // This array may contain some timeout bids (responses come back after auction timeout) + bidsReceived.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID)); + + // We handle timeout after bids since it's possible that a bid has a response, but the response comes back + // after auction end. In this case, the bid exists in both bidsReceived and timeoutBids arrays. + timeoutBids.forEach(bid => this.addBidResponseToMessage(message, bid, BIDDER_STATUS.TIMEOUT)); + + // mark the winning bids with prebidWon = true + winningBids.forEach(bid => { + const adUnitCode = parseAdUnitCode(bid); + const bidder = parseBidderCode(bid); + message.adUnits[adUnitCode][bidder].prebidWon = true; + }); + return message; + }, + createImpressionMessage(bid) { + const message = this.createCommonMessage(bid.auctionId); + this.addBidResponseToMessage(message, bid, BIDDER_STATUS.BID_WON); + return message; + }, + getCachedAuction(auctionId) { + this.cachedAuctions[auctionId] = this.cachedAuctions[auctionId] || { + timeoutBids: [], + }; + return this.cachedAuctions[auctionId]; + }, + handleAuctionEnd(auctionEndArgs) { + const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); + const highestCpmBids = pbjs.getHighestCpmBids(); + this.sendEventMessage('bid', + this.createBidMessage(auctionEndArgs, highestCpmBids, cachedAuction.timeoutBids) + ); + }, + handleBidTimeout(timeoutBids) { + timeoutBids.forEach((bid) => { + const cachedAuction = this.getCachedAuction(bid.auctionId); + cachedAuction.timeoutBids.push(bid); + }); + }, + handleBidWon(bidWonArgs) { + this.sendEventMessage('imp', this.createImpressionMessage(bidWonArgs)); + }, + track({eventType, args}) { + if (analyticsOptions.sampled) { + switch (eventType) { + case BID_WON: + this.handleBidWon(args); + break; + case BID_TIMEOUT: + this.handleBidTimeout(args); + break; + case AUCTION_END: + this.handleAuctionEnd(args); + break; + } + } + }, + getAnalyticsOptions() { + return analyticsOptions; + }, +}); + +// save the base class function +ucfunnelAnalyticsAdapter.originEnableAnalytics = ucfunnelAnalyticsAdapter.enableAnalytics; + +// override enableAnalytics so we can get access to the config passed in from the page +ucfunnelAnalyticsAdapter.enableAnalytics = function (config) { + if (this.initConfig(config)) { + ucfunnelAnalyticsAdapter.originEnableAnalytics(config); // call the base class function + } +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: ucfunnelAnalyticsAdapter, + code: 'ucfunnelAnalytics' +}); diff --git a/modules/ucfunnelAnalyticsAdapter.md b/modules/ucfunnelAnalyticsAdapter.md new file mode 100644 index 00000000000..be15e307332 --- /dev/null +++ b/modules/ucfunnelAnalyticsAdapter.md @@ -0,0 +1,23 @@ +# Overview + +``` +Module Name: ucfunnel Analytics Adapter +Module Type: Analytics Adapter +Maintainer: cliff.liu@ucfunnel.com +``` + +# Description + +Analytics adapter for ucfunnel + +# Test Parameters + +``` +{ + provider: 'ucfunnelAnalytics', + options: { + pbuid: "PBUID_FROM_UCFUNNEL" + adid: "BIDDING_ADID_FROM_UCFUNNEL" + } +} +``` diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 7974f053bbd..993c33534a3 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -64,13 +64,16 @@ export const spec = { let bid = { requestId: bidRequest.bidId, cpm: ad.cpm || 0, - creativeId: ad.ad_id, + creativeId: ad.ad_id || bidRequest.params.adid, dealId: ad.deal || null, currency: 'USD', netRevenue: true, ttl: 1800 }; + if (bidRequest.params && bidRequest.params.bidfloor && ad.cpm && ad.cpm < bidRequest.params.bidfloor) { + bid.cpm = 0; + } if (ad.creative_type) { bid.mediaType = ad.creative_type; } @@ -108,10 +111,11 @@ export const spec = { break; case BANNER: default: + var size = parseSizes(bidRequest); Object.assign(bid, { - width: ad.width, - height: ad.height, - ad: ad.adm + width: ad.width || size[0], + height: ad.height || size[1], + ad: ad.adm || '' }); } @@ -122,12 +126,12 @@ export const spec = { if (syncOptions.iframeEnabled) { return [{ type: 'iframe', - url: '//cdn.aralego.com/ucfad/cookie/sync.html' + url: '//cdn.aralego.net/ucfad/cookie/sync.html' }]; } else if (syncOptions.pixelEnabled) { return [{ type: 'image', - url: '//sync.aralego.com/idSync' + url: 'https://sync.aralego.com/idSync' }]; } } @@ -158,6 +162,28 @@ function parseSizes(bid) { return transformSizes(bid.sizes); } +function getSupplyChain(schain) { + var supplyChain = ''; + if (schain != null && schain.nodes) { + supplyChain = schain.ver + ',' + schain.complete; + for (let i = 0; i < schain.nodes.length; i++) { + supplyChain += '!'; + supplyChain += (schain.nodes[i].asi) ? encodeURIComponent(schain.nodes[i].asi) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].sid) ? encodeURIComponent(schain.nodes[i].sid) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].hp) ? encodeURIComponent(schain.nodes[i].hp) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].rid) ? encodeURIComponent(schain.nodes[i].rid) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].name) ? encodeURIComponent(schain.nodes[i].name) : ''; + supplyChain += ','; + supplyChain += (schain.nodes[i].domain) ? encodeURIComponent(schain.nodes[i].domain) : ''; + } + } + return supplyChain; +} + function getRequestData(bid, bidderRequest) { const size = parseSizes(bid); const loc = utils.getTopWindowLocation(); @@ -169,6 +195,7 @@ function getRequestData(bid, bidderRequest) { const videoContext = utils.deepAccess(bid, 'mediaTypes.video.context'); const videoMediaType = utils.deepAccess(bid, 'mediaTypes.video'); const userIdTdid = (bid.userId && bid.userId.tdid) ? bid.userId.tdid : ''; + const supplyChain = getSupplyChain(bid.schain); // general bid data let bidData = { ver: VER, @@ -182,7 +209,9 @@ function getRequestData(bid, bidderRequest) { adid: utils.getBidIdParameter('adid', bid.params), w: size[0], h: size[1], - tdid: userIdTdid + tdid: userIdTdid, + schain: supplyChain, + fp: utils.getBidIdParameter('bidfloor', bid.params) }; if (bid.mediaType === 'video' || videoMediaType) { diff --git a/modules/underdogmediaBidAdapter.js b/modules/underdogmediaBidAdapter.js index 534b77db3b1..03c815ab005 100644 --- a/modules/underdogmediaBidAdapter.js +++ b/modules/underdogmediaBidAdapter.js @@ -2,17 +2,19 @@ import * as utils from '../src/utils'; import { config } from '../src/config'; import { registerBidder } from '../src/adapters/bidderFactory'; const BIDDER_CODE = 'underdogmedia'; -const UDM_ADAPTER_VERSION = '1.13V'; +const UDM_ADAPTER_VERSION = '3.0V'; const UDM_VENDOR_ID = '159'; +const prebidVersion = '$prebid.version$'; -utils.logMessage(`Initializing UDM Adapter. PBJS Version: ${$$PREBID_GLOBAL$$.version} with adapter version: ${UDM_ADAPTER_VERSION} Updated 20180604`); +utils.logMessage(`Initializing UDM Adapter. PBJS Version: ${prebidVersion} with adapter version: ${UDM_ADAPTER_VERSION} Updated 20191028`); export const spec = { code: BIDDER_CODE, bidParams: [], isBidRequestValid: function (bid) { - return !!((bid.params && bid.params.siteId) && (bid.sizes && bid.sizes.length > 0)); + const bidSizes = bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes ? bid.mediaTypes.banner.sizes : bid.sizes; + return !!((bid.params && bid.params.siteId) && (bidSizes && bidSizes.length > 0)); }, buildRequests: function (validBidRequests, bidderRequest) { @@ -20,7 +22,8 @@ export const spec = { var siteId = 0; validBidRequests.forEach(bidParam => { - sizes = utils.flatten(sizes, utils.parseSizesInput(bidParam.sizes)); + let bidParamSizes = bidParam.mediaTypes && bidParam.mediaTypes.banner && bidParam.mediaTypes.banner.sizes ? bidParam.mediaTypes.banner.sizes : bidParam.sizes; + sizes = utils.flatten(sizes, utils.parseSizesInput(bidParamSizes)); siteId = bidParam.params.siteId; }); @@ -47,7 +50,7 @@ export const spec = { if (!data.gdprApplies || data.consentGiven) { return { method: 'GET', - url: `${window.location.protocol}//udmserve.net/udm/img.fetch`, + url: 'https://udmserve.net/udm/img.fetch', data: data, bidParams: validBidRequests }; @@ -67,7 +70,8 @@ export const spec = { } var sizeNotFound = true; - utils.parseSizesInput(bidParam.sizes).forEach(size => { + const bidParamSizes = bidParam.mediaTypes && bidParam.mediaTypes.banner && bidParam.mediaTypes.banner.sizes ? bidParam.mediaTypes.banner.sizes : bidParam.sizes + utils.parseSizesInput(bidParamSizes).forEach(size => { if (size === mid.width + 'x' + mid.height) { sizeNotFound = false; } diff --git a/modules/underdogmediaBidAdapter.md b/modules/underdogmediaBidAdapter.md index f652e2fcbbf..ac7434da482 100644 --- a/modules/underdogmediaBidAdapter.md +++ b/modules/underdogmediaBidAdapter.md @@ -13,7 +13,11 @@ Module that connects to Underdog Media's servers to fetch bids. var adUnits = [ { code: 'test-div', - sizes: [[300, 250]], // a display size + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, bids: [ { bidder: "underdogmedia", diff --git a/modules/userId/index.js b/modules/userId/index.js index ac96fd2cec8..91eaff13d47 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -386,6 +386,10 @@ function initSubmodules(submodules, consentData) { refreshNeeded = storedDate && (Date.now() - storedDate.getTime() > submodule.config.storage.refreshInSeconds * 1000); } + if (CONSTANTS.SUBMODULES_THAT_ALWAYS_REFRESH_ID[submodule.config.name] === true) { + refreshNeeded = true; + } + if (!storedId || refreshNeeded) { // No previously saved id. Request one from submodule. response = submodule.submodule.getId(submodule.config.params, consentData, storedId); diff --git a/modules/viBidAdapter.js b/modules/viBidAdapter.js index 93ead1c2380..8d8cb06e47e 100644 --- a/modules/viBidAdapter.js +++ b/modules/viBidAdapter.js @@ -302,6 +302,10 @@ export function mergeArrays(hashFn, ...args) { return merged; } +export function documentFocus(doc) { + return typeof doc.hasFocus === 'function' ? +doc.hasFocus() : undefined; +} + const spec = { code: 'vi', supportedMediaTypes: [mediaTypes.VIDEO, mediaTypes.BANNER], @@ -372,7 +376,8 @@ const spec = { ...params }; } - ) + ), + focus: documentFocus(document) }, options: { contentType: 'application/json', diff --git a/modules/viewdeosDXBidAdapter.js b/modules/viewdeosDXBidAdapter.js new file mode 100644 index 00000000000..a591a28b18d --- /dev/null +++ b/modules/viewdeosDXBidAdapter.js @@ -0,0 +1,243 @@ +import * as utils from '../src/utils'; +import {registerBidder} from '../src/adapters/bidderFactory'; +import {VIDEO, BANNER} from '../src/mediaTypes'; +import {Renderer} from '../src/Renderer'; +import findIndex from 'core-js/library/fn/array/find-index'; + +const URL = '//hb.sync.viewdeos.com/auction/'; +const OUTSTREAM_SRC = '//player.sync.viewdeos.com/outstream-unit/2.01/outstream.min.js'; +const BIDDER_CODE = 'viewdeosDX'; +const OUTSTREAM = 'outstream'; +const DISPLAY = 'display'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['viewdeos'], + supportedMediaTypes: [VIDEO, BANNER], + isBidRequestValid: function (bid) { + return !!utils.deepAccess(bid, 'params.aid'); + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + function addSyncs(bid) { + const uris = bid.cookieURLs; + const types = bid.cookieURLSTypes || []; + + if (Array.isArray(uris)) { + uris.forEach((uri, i) => { + const type = types[i] || 'image'; + + if ((!syncOptions.pixelEnabled && type === 'image') || + (!syncOptions.iframeEnabled && type === 'iframe')) { + return; + } + + syncs.push({ + type: type, + url: uri + }) + }) + } + } + + if (syncOptions.pixelEnabled || syncOptions.iframeEnabled) { + utils.isArray(serverResponses) && serverResponses.forEach((response) => { + if (response.body) { + if (utils.isArray(response.body)) { + response.body.forEach(b => { + addSyncs(b); + }) + } else { + addSyncs(response.body) + } + } + }) + } + return syncs; + }, + /** + * Make a server request from the list of BidRequests + * @param bidRequests + * @param bidderRequest + */ + buildRequests: function (bidRequests, bidderRequest) { + return { + data: bidToTag(bidRequests, bidderRequest), + bidderRequest, + method: 'GET', + url: URL + }; + }, + + /** + * Unpack the response from the server into a list of bids + * @param serverResponse + * @param bidderRequest + * @return {Bid[]} An array of bids which were nested inside the server + */ + interpretResponse: function (serverResponse, {bidderRequest}) { + serverResponse = serverResponse.body; + let bids = []; + + if (!utils.isArray(serverResponse)) { + return parseRTBResponse(serverResponse, bidderRequest); + } + + serverResponse.forEach(serverBidResponse => { + bids = utils.flatten(bids, parseRTBResponse(serverBidResponse, bidderRequest)); + }); + + return bids; + } +}; + +function parseRTBResponse(serverResponse, bidderRequest) { + const isInvalidValidResp = !serverResponse || !utils.isArray(serverResponse.bids); + + const bids = []; + + if (isInvalidValidResp) { + const extMessage = serverResponse && serverResponse.ext && serverResponse.ext.message ? `: ${serverResponse.ext.message}` : ''; + const errorMessage = `in response for ${bidderRequest.bidderCode} adapter ${extMessage}`; + + utils.logError(errorMessage); + + return bids; + } + + serverResponse.bids.forEach(serverBid => { + const requestId = findIndex(bidderRequest.bids, (bidRequest) => { + return bidRequest.bidId === serverBid.requestId; + }); + + if (serverBid.cpm !== 0 && requestId !== -1) { + const bid = createBid(serverBid, getMediaType(bidderRequest.bids[requestId])); + + bids.push(bid); + } + }); + + return bids; +} + +function bidToTag(bidRequests, bidderRequest) { + const tag = { + domain: utils.deepAccess(bidderRequest, 'refererInfo.referer') + }; + + if (utils.deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + tag.gdpr = 1; + tag.gdpr_consent = utils.deepAccess(bidderRequest, 'gdprConsent.consentString'); + } + + for (let i = 0, length = bidRequests.length; i < length; i++) { + Object.assign(tag, prepareRTBRequestParams(i, bidRequests[i])); + } + + return tag; +} + +/** + * Parse mediaType + * @param _index {number} + * @param bid {object} + * @returns {object} + */ +function prepareRTBRequestParams(_index, bid) { + const mediaType = utils.deepAccess(bid, 'mediaTypes.video') ? VIDEO : DISPLAY; + const index = !_index ? '' : `${_index + 1}`; + const sizes = bid.sizes ? bid.sizes : (mediaType === VIDEO ? utils.deepAccess(bid, 'mediaTypes.video.playerSize') : utils.deepAccess(bid, 'mediaTypes.banner.sizes')); + return { + ['callbackId' + index]: bid.bidId, + ['aid' + index]: bid.params.aid, + ['ad_type' + index]: mediaType, + ['sizes' + index]: utils.parseSizesInput(sizes).join() + }; +} + +/** + * Prepare all parameters for request + * @param bidderRequest {object} + * @returns {object} + */ +function getMediaType(bidderRequest) { + const videoMediaType = utils.deepAccess(bidderRequest, 'mediaTypes.video'); + const context = utils.deepAccess(bidderRequest, 'mediaTypes.video.context'); + + return !videoMediaType ? DISPLAY : context === OUTSTREAM ? OUTSTREAM : VIDEO; +} + +/** + * Configure new bid by response + * @param bidResponse {object} + * @param mediaType {Object} + * @returns {object} + */ +function createBid(bidResponse, mediaType) { + const bid = { + requestId: bidResponse.requestId, + creativeId: bidResponse.cmpId, + height: bidResponse.height, + currency: bidResponse.cur, + width: bidResponse.width, + cpm: bidResponse.cpm, + netRevenue: true, + mediaType, + ttl: 3600 + }; + + if (mediaType === DISPLAY) { + return Object.assign(bid, { + ad: bidResponse.ad + }); + } + + Object.assign(bid, { + vastUrl: bidResponse.vastUrl + }); + + if (mediaType === OUTSTREAM) { + Object.assign(bid, { + mediaType: 'video', + adResponse: bidResponse, + renderer: newRenderer(bidResponse.requestId) + }); + } + + return bid; +} + +/** + * Create renderer + * @param requestId + * @returns {*} + */ +function newRenderer(requestId) { + const renderer = Renderer.install({ + id: requestId, + url: OUTSTREAM_SRC, + loaded: false + }); + + renderer.setRender(outstreamRender); + + return renderer; +} + +/** + * Initialise outstream + * @param bid + */ +function outstreamRender(bid) { + bid.renderer.push(() => { + window.VOutstreamAPI.initOutstreams([{ + width: bid.width, + height: bid.height, + vastUrl: bid.vastUrl, + elId: bid.adUnitCode + }]); + }); +} + +registerBidder(spec); diff --git a/modules/viewdeosDXBidAdapter.md b/modules/viewdeosDXBidAdapter.md new file mode 100644 index 00000000000..d9ede8cc312 --- /dev/null +++ b/modules/viewdeosDXBidAdapter.md @@ -0,0 +1,60 @@ +# Overview + +**Module Name**: Viewdeos DX Bidder Adapter +**Module Type**: Bidder Adapter + +# Description + +Get access to multiple demand partners across Viewdeos and maximize your yield with Viewdeos header bidding adapter. + +# Test Parameters +``` + var adUnits = [ + + // Video instream adUnit + { + code: 'div-test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'instream' + } + }, + bids: [{ + bidder: 'viewdeosDX', + params: { + aid: 331133 + } + }] + }, + + // Video outstream adUnit + { + code: 'outstream-test-div', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'outstream' + } + }, + bids: [{ + bidder: 'viewdeosDX', + params: { + aid: 331133 + } + }] + }, + + // Banner adUnit + { + code: 'div-test-div', + sizes: [[300, 250]], + bids: [{ + bidder: 'viewdeosDX', + params: { + aid: 350975 + } + }] + } + ]; +``` diff --git a/modules/vubleBidAdapter.js b/modules/vubleBidAdapter.js index 63f817358de..288390f01c9 100644 --- a/modules/vubleBidAdapter.js +++ b/modules/vubleBidAdapter.js @@ -57,7 +57,8 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (utils.isEmpty(bid.sizes) || utils.parseSizesInput(bid.sizes).length == 0) { + let rawSizes = utils.deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes; + if (utils.isEmpty(rawSizes) || utils.parseSizesInput(rawSizes).length == 0) { return false; } @@ -78,21 +79,22 @@ export const spec = { * @param {validBidRequests[]} - an array of bids * @return ServerRequest Info describing the request to the server. */ - buildRequests: function (validBidRequests) { + buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { // We take the first size - let size = utils.parseSizesInput(bidRequest.sizes)[0].split('x'); + let rawSize = utils.deepAccess(bidRequest, 'mediaTypes.video.playerSize') || bidRequest.sizes; + let size = utils.parseSizesInput(rawSize)[0].split('x'); // Get the page's url - let referrer = utils.getTopWindowUrl(); + let referer = (bidderRequest && bidderRequest.refererInfo) ? bidderRequest.refererInfo.referer : ''; if (bidRequest.params.referrer) { - referrer = bidRequest.params.referrer; + referer = bidRequest.params.referrer; } // Get Video Context let context = utils.deepAccess(bidRequest, 'mediaTypes.video.context'); - let url = '//player.mediabong.' + bidRequest.params.env + '/prebid/request'; + let url = 'https://player.mediabong.' + bidRequest.params.env + '/prebid/request'; let data = { width: size[0], height: size[1], @@ -100,7 +102,7 @@ export const spec = { zone_id: bidRequest.params.zoneId, context: context, floor_price: bidRequest.params.floorPrice ? bidRequest.params.floorPrice : 0, - url: referrer, + url: referer, env: bidRequest.params.env, bid_id: bidRequest.bidId, adUnitCode: bidRequest.adUnitCode diff --git a/modules/yieldoneAnalyticsAdapter.js b/modules/yieldoneAnalyticsAdapter.js index 94dd0daa0b2..dec14ff5d0f 100644 --- a/modules/yieldoneAnalyticsAdapter.js +++ b/modules/yieldoneAnalyticsAdapter.js @@ -18,6 +18,49 @@ let currentAuctionId = ''; let url = defaultUrl; let pubId = ''; +function makeAdUnitNameMap() { + if (window.googletag && window.googletag.pubads) { + const p = googletag.pubads(); + if (p && p.getSlots) { + const slots = p.getSlots(); + if (slots && slots.length) { + const map = {}; + slots.forEach((slot) => { + const id = slot.getSlotElementId(); + const name = (slot.getAdUnitPath() || '').split('/').pop(); + map[id] = name; + }); + return map; + } + } + } +} + +function addAdUnitNameForArray(ar, map) { + if (utils.isArray(ar)) { + ar.forEach((it) => { addAdUnitName(it, map) }); + } +} + +function addAdUnitName(params, map) { + if (params.adUnitCode && map[params.adUnitCode]) { + params.adUnitName = map[params.adUnitCode]; + } + if (utils.isArray(params.adUnits)) { + params.adUnits.forEach((adUnit) => { + if (adUnit.code && map[adUnit.code]) { + adUnit.name = map[adUnit.code]; + } + }); + } + if (utils.isArray(params.adUnitCodes)) { + params.adUnitNames = params.adUnitCodes.map((code) => map[code]); + } + ['bids', 'bidderRequests', 'bidsReceived', 'noBids'].forEach((it) => { + addAdUnitNameForArray(params[it], map); + }); +} + const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { getUrl() { return url; }, track({eventType, args = {}}) { @@ -36,7 +79,7 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { const reqBidId = `${bid.bidId}_${bid.auctionId}`; const reqBidderId = `${bid.bidder}_${bid.auctionId}`; if (!eventsStorage[bid.auctionId]) eventsStorage[bid.auctionId] = []; - if (requestedBidders[reqBidderId] && requestedBids[reqBidId]) { + if ((requestedBidders[reqBidderId] || reqBidders[bid.bidder]) && requestedBids[reqBidId]) { if (!reqBidders[bid.bidder]) { reqBidders[bid.bidder] = requestedBidders[reqBidderId]; reqBidders[bid.bidder].pubId = pubId; @@ -73,6 +116,10 @@ const yieldoneAnalytics = Object.assign(adapter({analyticsType}), { if (yieldoneAnalytics.eventsStorage[args.auctionId]) { yieldoneAnalytics.eventsStorage[args.auctionId].forEach((it) => { it.page = {url: referrers[currentAuctionId]}; + const adUnitNameMap = makeAdUnitNameMap(); + if (adUnitNameMap) { + addAdUnitName(it.params, adUnitNameMap); + } }); } yieldoneAnalytics.sendStat(yieldoneAnalytics.eventsStorage[args.auctionId], args.auctionId); diff --git a/modules/yieldoneBidAdapter.js b/modules/yieldoneBidAdapter.js index c706a6e7d45..9920c93696e 100644 --- a/modules/yieldoneBidAdapter.js +++ b/modules/yieldoneBidAdapter.js @@ -8,6 +8,7 @@ const BIDDER_CODE = 'yieldone'; const ENDPOINT_URL = 'https://y.one.impact-ad.jp/h_bid'; const USER_SYNC_URL = 'https://y.one.impact-ad.jp/push_sync'; const VIDEO_PLAYER_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/dac-video-prebid.min.js'; +const VIEWABLE_PERCENTAGE_URL = 'https://img.ak.impact-ad.jp/ic/pone/ivt/firstview/js/prebid-adformat-config.js'; export const spec = { code: BIDDER_CODE, @@ -64,6 +65,7 @@ export const spec = { const cpm = response.cpm * 1000 || 0; if (width !== 0 && height !== 0 && cpm !== 0 && crid !== 0) { const dealId = response.dealId || ''; + const renderId = response.renderid || ''; const currency = response.currency || 'JPY'; const netRevenue = (response.netRevenue === undefined) ? true : response.netRevenue; const referrer = utils.getTopWindowUrl(); @@ -80,7 +82,53 @@ export const spec = { referrer: referrer }; - if (response.adTag) { + if (response.adTag && renderId === 'ViewableRendering') { + bidResponse.mediaType = BANNER; + let viewableScript = ` + + + `; + bidResponse.ad = viewableScript; + } else if (response.adTag) { bidResponse.mediaType = BANNER; bidResponse.ad = response.adTag; } else if (response.adm) { diff --git a/modules/zedoBidAdapter.js b/modules/zedoBidAdapter.js index b09913ecf60..5fbb02e1f3d 100644 --- a/modules/zedoBidAdapter.js +++ b/modules/zedoBidAdapter.js @@ -6,8 +6,7 @@ import { Renderer } from '../src/Renderer'; import * as url from '../src/url'; const BIDDER_CODE = 'zedo'; -const URL = '//z2.zedo.com/asw/fmh.json'; -const SECURE_URL = '//saxp.zedo.com/asw/fmh.json'; +const SECURE_URL = 'https://saxp.zedo.com/asw/fmh.json'; const DIM_TYPE = { '7': 'display', '9': 'display', @@ -23,7 +22,6 @@ const DIM_TYPE = { '103': 'display' // '85': 'pre-mid-post-roll', }; -const EVENT_PIXEL_URL = 'm1.zedo.com/log/p.gif'; const SECURE_EVENT_PIXEL_URL = 'tt1.zedo.com/log/p.gif'; export const spec = { @@ -86,10 +84,13 @@ export const spec = { } data['placements'].push(placement); }); - let reqUrl = utils.getTopWindowLocation().protocol === 'http:' ? URL : SECURE_URL; + // adding schain object + if (bidRequests[0].schain) { + data['supplyChain'] = getSupplyChain(bidRequests[0].schain); + } return { method: 'GET', - url: reqUrl, + url: SECURE_URL, data: 'g=' + JSON.stringify(data) } }, @@ -127,7 +128,7 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent) { if (syncOptions.iframeEnabled) { - let url = utils.getTopWindowLocation().protocol === 'http:' ? 'http://d3.zedo.com/rs/us/fcs.html' : 'https://tt3.zedo.com/rs/us/fcs.html'; + let url = 'https://tt3.zedo.com/rs/us/fcs.html'; if (gdprConsent && typeof gdprConsent.consentString === 'string') { // add 'gdpr' only if 'gdprApplies' is defined if (typeof gdprConsent.gdprApplies === 'boolean') { @@ -157,12 +158,20 @@ export const spec = { } catch (e) { utils.logError(e); } - }, + } + +}; + +function getSupplyChain (supplyChain) { + return { + complete: supplyChain.complete, + nodes: supplyChain.nodes + } }; function getCreative(ad) { return ad && ad.creatives && ad.creatives.length && find(ad.creatives, creative => creative.adId); -} +}; /** * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid @@ -194,7 +203,7 @@ function newBid(serverBid, creativeBid, bidderRequest) { bidderRequest, 'renderer.options' ); - let rendererUrl = utils.getTopWindowLocation().protocol === 'http:' ? 'http://c14.zedo.com/gecko/beta/fmpbgt.min.js' : 'https://ss3.zedo.com/gecko/beta/fmpbgt.min.js'; + let rendererUrl = 'https://ss3.zedo.com/gecko/beta/fmpbgt.min.js'; Object.assign(bid, { adResponse: serverBid, renderer: getRenderer(bid.adUnitCode, serverBid.slotId, rendererUrl, rendererOptions) @@ -279,8 +288,8 @@ function parseMediaType(creativeBid) { function logEvent(eid, data) { let getParams = { - protocol: utils.getTopWindowLocation().protocol === 'http:' ? 'http' : 'https', - hostname: utils.getTopWindowLocation().protocol === 'http:' ? EVENT_PIXEL_URL : SECURE_EVENT_PIXEL_URL, + protocol: 'https', + hostname: SECURE_EVENT_PIXEL_URL, search: getLoggingData(eid, data) }; utils.triggerPixel(url.format(getParams).replace(/&/g, ';')); diff --git a/package-lock.json b/package-lock.json index 0d0372c55a9..2917f038101 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.35.0-pre", + "version": "2.44.0-pre", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -14,19 +14,19 @@ } }, "@babel/core": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.6.2.tgz", - "integrity": "sha512-l8zto/fuoZIbncm+01p8zPSDZu/VuuJhAfA7d/AbzM09WR7iVhavvfNDYCNpo1VvLk6E6xgAoP9P+/EMJHuRkQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.7.4.tgz", + "integrity": "sha512-+bYbx56j4nYBmpsWtnPUsKW3NdnYxbqyfrP2w9wILBuHzdfIKz9prieZK0DFPyIzkjYVUe4QkusGL07r5pXznQ==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.2", - "@babel/helpers": "^7.6.2", - "@babel/parser": "^7.6.2", - "@babel/template": "^7.6.0", - "@babel/traverse": "^7.6.2", - "@babel/types": "^7.6.0", - "convert-source-map": "^1.1.0", + "@babel/generator": "^7.7.4", + "@babel/helpers": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4", + "convert-source-map": "^1.7.0", "debug": "^4.1.0", "json5": "^2.1.0", "lodash": "^4.17.13", @@ -36,136 +36,146 @@ } }, "@babel/generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.6.2.tgz", - "integrity": "sha512-j8iHaIW4gGPnViaIHI7e9t/Hl8qLjERI6DcV9kEpAIDJsAOrcnXqRS7t+QbhL76pwbtqP+QCQLL0z1CyVmtjjQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.4.tgz", + "integrity": "sha512-m5qo2WgdOJeyYngKImbkyQrnUN1mPceaG5BV+G0E3gWsa4l/jCSryWJdM2x8OuGAOyh+3d5pVYfZWCiNFtynxg==", "dev": true, "requires": { - "@babel/types": "^7.6.0", + "@babel/types": "^7.7.4", "jsesc": "^2.5.1", "lodash": "^4.17.13", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz", - "integrity": "sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.7.4.tgz", + "integrity": "sha512-2BQmQgECKzYKFPpiycoF9tlb5HA4lrVyAmLLVK177EcQAqjVLciUb2/R+n1boQ9y5ENV3uz2ZqiNw7QMBBw1Og==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.7.4" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz", - "integrity": "sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.7.4.tgz", + "integrity": "sha512-Biq/d/WtvfftWZ9Uf39hbPBYDUo986m5Bb4zhkeYDGUllF43D+nUe5M6Vuo6/8JDK/0YX/uBdeoQpyaNhNugZQ==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-explode-assignable-expression": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helper-call-delegate": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.4.4.tgz", - "integrity": "sha512-l79boDFJ8S1c5hvQvG+rc+wHw6IuH7YldmRKsYtpbawsxURu/paVy57FZMomGK22/JckepaikOkY0MoAmdyOlQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.7.4.tgz", + "integrity": "sha512-8JH9/B7J7tCYJ2PpWVpw9JhPuEVHztagNVuQAFBVFYluRMlpG7F1CgKEgGeL6KFqcsIa92ZYVj6DSc0XwmN1ZA==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.7.4.tgz", + "integrity": "sha512-Mt+jBKaxL0zfOIWrfQpnfYCN7/rS6GKx6CCCfuoqVVd+17R8zNDlzVYmIi9qyb2wOk002NsmSTDymkIygDUH7A==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.4.4", - "@babel/traverse": "^7.4.4", - "@babel/types": "^7.4.4" + "@babel/helper-regex": "^7.4.4", + "regexpu-core": "^4.6.0" } }, "@babel/helper-define-map": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.5.5.tgz", - "integrity": "sha512-fTfxx7i0B5NJqvUOBBGREnrqbTxRh7zinBANpZXAVDlsZxYdclDp467G1sQ8VZYMnAURY3RpBUAgOYT9GfzHBg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.7.4.tgz", + "integrity": "sha512-v5LorqOa0nVQUvAUTUF3KPastvUt/HzByXNamKQ6RdJRTV7j8rLL+WB5C/MzzWAwOomxDhYFb1wLLxHqox86lg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/types": "^7.5.5", + "@babel/helper-function-name": "^7.7.4", + "@babel/types": "^7.7.4", "lodash": "^4.17.13" } }, "@babel/helper-explode-assignable-expression": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz", - "integrity": "sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.7.4.tgz", + "integrity": "sha512-2/SicuFrNSXsZNBxe5UGdLr+HZg+raWBLE9vC98bdYOKX/U6PY0mdGlYUJdtTDPSU0Lw0PNbKKDpwYHJLn2jLg==", "dev": true, "requires": { - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helper-function-name": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz", - "integrity": "sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz", + "integrity": "sha512-AnkGIdiBhEuiwdoMnKm7jfPfqItZhgRaZfMg1XX3bS25INOnLPjPG1Ppnajh8eqgt5kPJnfqrRHqFqmjKDZLzQ==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.0.0", - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-get-function-arity": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helper-get-function-arity": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz", - "integrity": "sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz", + "integrity": "sha512-QTGKEdCkjgzgfJ3bAyRwF4yyT3pg+vDgan8DSivq1eS0gwi+KGKE5x8kRcbeFTb/673mkO5SN1IZfmCfA5o+EA==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.7.4" } }, "@babel/helper-hoist-variables": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.4.4.tgz", - "integrity": "sha512-VYk2/H/BnYbZDDg39hr3t2kKyifAm1W6zHRfhx8jGjIHpQEBv9dry7oQ2f3+J703TLu69nYdxsovl0XYfcnK4w==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.7.4.tgz", + "integrity": "sha512-wQC4xyvc1Jo/FnLirL6CEgPgPCa8M74tOdjWpRhQYapz5JC7u3NYU1zCVoVAGCE3EaIP9T1A3iW0WLJ+reZlpQ==", "dev": true, "requires": { - "@babel/types": "^7.4.4" + "@babel/types": "^7.7.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.5.5.tgz", - "integrity": "sha512-5qZ3D1uMclSNqYcXqiHoA0meVdv+xUEex9em2fqMnrk/scphGlGgg66zjMrPJESPwrFJ6sbfFQYUSa0Mz7FabA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.7.4.tgz", + "integrity": "sha512-9KcA1X2E3OjXl/ykfMMInBK+uVdfIVakVe7W7Lg3wfXUNyS3Q1HWLFRwZIjhqiCGbslummPDnmb7vIekS0C1vw==", "dev": true, "requires": { - "@babel/types": "^7.5.5" + "@babel/types": "^7.7.4" } }, "@babel/helper-module-imports": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz", - "integrity": "sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.7.4.tgz", + "integrity": "sha512-dGcrX6K9l8258WFjyDLJwuVKxR4XZfU0/vTUgOQYWEnRD8mgr+p4d6fCUMq/ys0h4CCt/S5JhbvtyErjWouAUQ==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.7.4" } }, "@babel/helper-module-transforms": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.5.5.tgz", - "integrity": "sha512-jBeCvETKuJqeiaCdyaheF40aXnnU1+wkSiUs/IQg3tB85up1LyL8x77ClY8qJpuRJUcXQo+ZtdNESmZl4j56Pw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.7.4.tgz", + "integrity": "sha512-ehGBu4mXrhs0FxAqN8tWkzF8GSIGAiEumu4ONZ/hD9M88uHcD+Yu2ttKfOCgwzoesJOJrtQh7trI5YPbRtMmnA==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/template": "^7.4.4", - "@babel/types": "^7.5.5", + "@babel/helper-module-imports": "^7.7.4", + "@babel/helper-simple-access": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4", "lodash": "^4.17.13" } }, "@babel/helper-optimise-call-expression": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz", - "integrity": "sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.7.4.tgz", + "integrity": "sha512-VB7gWZ2fDkSuqW6b1AKXkJWO5NyNI3bFL/kK79/30moK57blr6NbH8xcl2XcKCwOmJosftWunZqfO84IGq3ZZg==", "dev": true, "requires": { - "@babel/types": "^7.0.0" + "@babel/types": "^7.7.4" } }, "@babel/helper-plugin-utils": { @@ -184,70 +194,70 @@ } }, "@babel/helper-remap-async-to-generator": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz", - "integrity": "sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.7.4.tgz", + "integrity": "sha512-Sk4xmtVdM9sA/jCI80f+KS+Md+ZHIpjuqmYPk1M7F/upHou5e4ReYmExAiu6PVe65BhJPZA2CY9x9k4BqE5klw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-wrap-function": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/helper-annotate-as-pure": "^7.7.4", + "@babel/helper-wrap-function": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helper-replace-supers": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.5.5.tgz", - "integrity": "sha512-XvRFWrNnlsow2u7jXDuH4jDDctkxbS7gXssrP4q2nUD606ukXHRvydj346wmNg+zAgpFx4MWf4+usfC93bElJg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.7.4.tgz", + "integrity": "sha512-pP0tfgg9hsZWo5ZboYGuBn/bbYT/hdLPVSS4NMmiRJdwWhP0IznPwN9AE1JwyGsjSPLC364I0Qh5p+EPkGPNpg==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.5.5", - "@babel/helper-optimise-call-expression": "^7.0.0", - "@babel/traverse": "^7.5.5", - "@babel/types": "^7.5.5" + "@babel/helper-member-expression-to-functions": "^7.7.4", + "@babel/helper-optimise-call-expression": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helper-simple-access": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz", - "integrity": "sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.7.4.tgz", + "integrity": "sha512-zK7THeEXfan7UlWsG2A6CI/L9jVnI5+xxKZOdej39Y0YtDYKx9raHk5F2EtK9K8DHRTihYwg20ADt9S36GR78A==", "dev": true, "requires": { - "@babel/template": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/template": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helper-split-export-declaration": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", - "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz", + "integrity": "sha512-guAg1SXFcVr04Guk9eq0S4/rWS++sbmyqosJzVs8+1fH5NI+ZcmkaSkc7dmtAFbHFva6yRJnjW3yAcGxjueDug==", "dev": true, "requires": { - "@babel/types": "^7.4.4" + "@babel/types": "^7.7.4" } }, "@babel/helper-wrap-function": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz", - "integrity": "sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.7.4.tgz", + "integrity": "sha512-VsfzZt6wmsocOaVU0OokwrIytHND55yvyT4BPB9AIIgwr8+x7617hetdJTsuGwygN5RC6mxA9EJztTjuwm2ofg==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.2.0" + "@babel/helper-function-name": "^7.7.4", + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/helpers": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.6.2.tgz", - "integrity": "sha512-3/bAUL8zZxYs1cdX2ilEE0WobqbCmKWr/889lf2SS0PpDcpEIY8pb1CCyz0pEcX3pEb+MCbks1jIokz2xLtGTA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.7.4.tgz", + "integrity": "sha512-ak5NGZGJ6LV85Q1Zc9gn2n+ayXOizryhjSUBTdu5ih1tlVCJeuQENzc4ItyCVhINVXvIT/ZQ4mheGIsfBkpskg==", "dev": true, "requires": { - "@babel/template": "^7.6.0", - "@babel/traverse": "^7.6.2", - "@babel/types": "^7.6.0" + "@babel/template": "^7.7.4", + "@babel/traverse": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/highlight": { @@ -262,151 +272,159 @@ } }, "@babel/parser": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.6.2.tgz", - "integrity": "sha512-mdFqWrSPCmikBoaBYMuBulzTIKuXVPtEISFbRRVNwMWpCms/hmE2kRq0bblUHaNRKrjRlmVbx1sDHmjmRgD2Xg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.4.tgz", + "integrity": "sha512-jIwvLO0zCL+O/LmEJQjWA75MQTWwx3c3u2JOTDK5D3/9egrWRRA0/0hk9XXywYnXZVVpzrBYeIQTmhwUaePI9g==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz", - "integrity": "sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.7.4.tgz", + "integrity": "sha512-1ypyZvGRXriY/QP668+s8sFr2mqinhkRDMPSQLNghCQE+GAkFtp+wkHVvg2+Hdki8gwP+NFzJBJ/N1BfzCCDEw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0", - "@babel/plugin-syntax-async-generators": "^7.2.0" + "@babel/helper-remap-async-to-generator": "^7.7.4", + "@babel/plugin-syntax-async-generators": "^7.7.4" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.5.0.tgz", - "integrity": "sha512-x/iMjggsKTFHYC6g11PL7Qy58IK8H5zqfm9e6hu4z1iH2IRyAp9u9dL80zA6R76yFovETFLKz2VJIC2iIPBuFw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.7.4.tgz", + "integrity": "sha512-StH+nGAdO6qDB1l8sZ5UBV8AC3F2VW2I8Vfld73TMKyptMU9DY5YsJAS8U81+vEtxcH3Y/La0wG0btDrhpnhjQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0" + "@babel/plugin-syntax-dynamic-import": "^7.7.4" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz", - "integrity": "sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.7.4.tgz", + "integrity": "sha512-wQvt3akcBTfLU/wYoqm/ws7YOAQKu8EVJEvHip/mzkNtjaclQoCCIqKXFP5/eyfnfbQCDV3OLRIK3mIVyXuZlw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-json-strings": "^7.2.0" + "@babel/plugin-syntax-json-strings": "^7.7.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.6.2.tgz", - "integrity": "sha512-LDBXlmADCsMZV1Y9OQwMc0MyGZ8Ta/zlD9N67BfQT8uYwkRswiu2hU6nJKrjrt/58aH/vqfQlR/9yId/7A2gWw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.7.4.tgz", + "integrity": "sha512-rnpnZR3/iWKmiQyJ3LKJpSwLDcX/nSXhdLk4Aq/tXOApIvyu7qoabrige0ylsAJffaUC51WiBu209Q0U+86OWQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0" + "@babel/plugin-syntax-object-rest-spread": "^7.7.4" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.7.4.tgz", + "integrity": "sha512-DyM7U2bnsQerCQ+sejcTNZh8KQEUuC3ufzdnVnSiUv/qoGJp2Z3hanKL18KDhsBT5Wj6a7CMT5mdyCNJsEaA9w==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding": "^7.7.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.6.2.tgz", - "integrity": "sha512-NxHETdmpeSCtiatMRYWVJo7266rrvAC3DTeG5exQBIH/fMIUK7ejDNznBbn3HQl/o9peymRRg7Yqkx6PdUXmMw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.7.4.tgz", + "integrity": "sha512-cHgqHgYvffluZk85dJ02vloErm3Y6xtH+2noOBOJ2kXOJH3aVCDnj5eR/lVNlTnYu4hndAPJD3rTFjW3qee0PA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-async-generators": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz", - "integrity": "sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.7.4.tgz", + "integrity": "sha512-Li4+EjSpBgxcsmeEF8IFcfV/+yJGxHXDirDkEoyFjumuwbmfCVHUt0HuowD/iGM7OhIRyXJH9YXxqiH6N815+g==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-dynamic-import": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.2.0.tgz", - "integrity": "sha512-mVxuJ0YroI/h/tbFTPGZR8cv6ai+STMKNBq0f8hFxsxWjl94qqhsb+wXbpNMDPU3cfR1TIsVFzU3nXyZMqyK4w==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.7.4.tgz", + "integrity": "sha512-jHQW0vbRGvwQNgyVxwDh4yuXu4bH1f5/EICJLAhl1SblLs2CDhrsmCk+v5XLdE9wxtAFRyxx+P//Iw+a5L/tTg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-json-strings": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz", - "integrity": "sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.7.4.tgz", + "integrity": "sha512-QpGupahTQW1mHRXddMG5srgpHWqRLwJnJZKXTigB9RPFCCGbDGCgBeM/iC82ICXp414WeYx/tD54w7M2qRqTMg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-object-rest-spread": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz", - "integrity": "sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.7.4.tgz", + "integrity": "sha512-mObR+r+KZq0XhRVS2BrBKBpr5jqrqzlPvS9C9vuOf5ilSwzloAl7RPWLrgKdWS6IreaVrjHxTjtyqFiOisaCwg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz", - "integrity": "sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.7.4.tgz", + "integrity": "sha512-4ZSuzWgFxqHRE31Glu+fEr/MirNZOMYmD/0BhBWyLyOOQz/gTAl7QmWm2hX1QxEIXsr2vkdlwxIzTyiYRC4xcQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.7.4.tgz", + "integrity": "sha512-wdsOw0MvkL1UIgiQ/IFr3ETcfv1xb8RMM0H9wbiDyLaJFyiDg5oZvDLCXosIXmFeIlweML5iOBXAkqddkYNizg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz", - "integrity": "sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.7.4.tgz", + "integrity": "sha512-zUXy3e8jBNPiffmqkHRNDdZM2r8DWhCB7HhcoyZjiK1TxYEluLHAvQuYnTT+ARqRpabWqy/NHkO6e3MsYB5YfA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.5.0.tgz", - "integrity": "sha512-mqvkzwIGkq0bEF1zLRRiTdjfomZJDV33AH3oQzHVGkI2VzEmXLpKKOBvEVaFZBJdN0XTyH38s9j/Kiqr68dggg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.7.4.tgz", + "integrity": "sha512-zpUTZphp5nHokuy8yLlyafxCJ0rSlFoSHypTUWgpdwoDXWQcseaect7cJ8Ppk6nunOM6+5rPMkod4OYKPR5MUg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-module-imports": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-remap-async-to-generator": "^7.1.0" + "@babel/helper-remap-async-to-generator": "^7.7.4" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz", - "integrity": "sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.7.4.tgz", + "integrity": "sha512-kqtQzwtKcpPclHYjLK//3lH8OFsCDuDJBaFhVwf8kqdnF6MN4l618UDlcA7TfRs3FayrHj+svYnSX8MC9zmUyQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.6.2.tgz", - "integrity": "sha512-zZT8ivau9LOQQaOGC7bQLQOT4XPkPXgN2ERfUgk1X8ql+mVkLc4E8eKk+FO3o0154kxzqenWCorfmEXpEZcrSQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.7.4.tgz", + "integrity": "sha512-2VBe9u0G+fDt9B5OV5DQH4KBf5DoiNkwFKOz0TCvBWvdAN2rOykCTkrL+jTLxfCAm76l9Qo5OqL7HBOx2dWggg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -414,238 +432,237 @@ } }, "@babel/plugin-transform-classes": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.5.5.tgz", - "integrity": "sha512-U2htCNK/6e9K7jGyJ++1p5XRU+LJjrwtoiVn9SzRlDT2KubcZ11OOwy3s24TjHxPgxNwonCYP7U2K51uVYCMDg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.7.4.tgz", + "integrity": "sha512-sK1mjWat7K+buWRuImEzjNf68qrKcrddtpQo3swi9j7dUcG6y6R6+Di039QN2bD1dykeswlagupEmpOatFHHUg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", - "@babel/helper-define-map": "^7.5.5", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-optimise-call-expression": "^7.0.0", + "@babel/helper-annotate-as-pure": "^7.7.4", + "@babel/helper-define-map": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-optimise-call-expression": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.5.5", - "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/helper-replace-supers": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz", - "integrity": "sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.7.4.tgz", + "integrity": "sha512-bSNsOsZnlpLLyQew35rl4Fma3yKWqK3ImWMSC/Nc+6nGjC9s5NFWAer1YQ899/6s9HxO2zQC1WoFNfkOqRkqRQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-destructuring": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.6.0.tgz", - "integrity": "sha512-2bGIS5P1v4+sWTCnKNDZDxbGvEqi0ijeqM/YqHtVGrvG2y0ySgnEEhXErvE9dA0bnIzY9bIzdFK0jFA46ASIIQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.7.4.tgz", + "integrity": "sha512-4jFMXI1Cu2aXbcXXl8Lr6YubCn6Oc7k9lLsu8v61TZh+1jny2BWmdtvY9zSUlLdGUvcy9DMAWyZEOqjsbeg/wA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.6.2.tgz", - "integrity": "sha512-KGKT9aqKV+9YMZSkowzYoYEiHqgaDhGmPNZlZxX6UeHC4z30nC1J9IrZuGqbYFB1jaIGdv91ujpze0exiVK8bA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.7.4.tgz", + "integrity": "sha512-mk0cH1zyMa/XHeb6LOTXTbG7uIJ8Rrjlzu91pUx/KS3JpcgaTDwMS8kM+ar8SLOvlL2Lofi4CGBAjCo3a2x+lw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.5.0.tgz", - "integrity": "sha512-igcziksHizyQPlX9gfSjHkE2wmoCH3evvD2qR5w29/Dk0SMKE/eOI7f1HhBdNhR/zxJDqrgpoDTq5YSLH/XMsQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.7.4.tgz", + "integrity": "sha512-g1y4/G6xGWMD85Tlft5XedGaZBCIVN+/P0bs6eabmcPP9egFleMAo65OOjlhcz1njpwagyY3t0nsQC9oTFegJA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz", - "integrity": "sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.7.4.tgz", + "integrity": "sha512-MCqiLfCKm6KEA1dglf6Uqq1ElDIZwFuzz1WH5mTf8k2uQSxEJMbOIEh7IZv7uichr7PMfi5YVSrr1vz+ipp7AQ==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.1.0", + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-for-of": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.4.4.tgz", - "integrity": "sha512-9T/5Dlr14Z9TIEXLXkt8T1DU7F24cbhwhMNUziN3hB1AXoZcdzPcTiKGRn/6iOymDqtTKWnr/BtRKN9JwbKtdQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.7.4.tgz", + "integrity": "sha512-zZ1fD1B8keYtEcKF+M1TROfeHTKnijcVQm0yO/Yu1f7qoDoxEIc/+GX6Go430Bg84eM/xwPFp0+h4EbZg7epAA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-function-name": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.4.4.tgz", - "integrity": "sha512-iU9pv7U+2jC9ANQkKeNF6DrPy4GBa4NWQtl6dHB4Pb3izX2JOEvDTFarlNsBj/63ZEzNNIAMs3Qw4fNCcSOXJA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.7.4.tgz", + "integrity": "sha512-E/x09TvjHNhsULs2IusN+aJNRV5zKwxu1cpirZyRPw+FyyIKEHPXTsadj48bVpc1R5Qq1B5ZkzumuFLytnbT6g==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.1.0", + "@babel/helper-function-name": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz", - "integrity": "sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.7.4.tgz", + "integrity": "sha512-X2MSV7LfJFm4aZfxd0yLVFrEXAgPqYoDG53Br/tCKiKYfX0MjVjQeWPIhPHHsCqzwQANq+FLN786fF5rgLS+gw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.2.0.tgz", - "integrity": "sha512-HiU3zKkSU6scTidmnFJ0bMX8hz5ixC93b4MHMiYebmk2lUVNGOboPsqQvx5LzooihijUoLR/v7Nc1rbBtnc7FA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.7.4.tgz", + "integrity": "sha512-9VMwMO7i69LHTesL0RdGy93JU6a+qOPuvB4F4d0kR0zyVjJRVJRaoaGjhtki6SzQUu8yen/vxPKN6CWnCUw6bA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.5.0.tgz", - "integrity": "sha512-n20UsQMKnWrltocZZm24cRURxQnWIvsABPJlw/fvoy9c6AgHZzoelAIzajDHAQrDpuKFFPPcFGd7ChsYuIUMpg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.7.4.tgz", + "integrity": "sha512-/542/5LNA18YDtg1F+QHvvUSlxdvjZoD/aldQwkq+E3WCkbEjNSN9zdrOXaSlfg3IfGi22ijzecklF/A7kVZFQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-module-transforms": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.6.0.tgz", - "integrity": "sha512-Ma93Ix95PNSEngqomy5LSBMAQvYKVe3dy+JlVJSHEXZR5ASL9lQBedMiCyVtmTLraIDVRE3ZjTZvmXXD2Ozw3g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.7.4.tgz", + "integrity": "sha512-k8iVS7Jhc367IcNF53KCwIXtKAH7czev866ThsTgy8CwlXjnKZna2VHwChglzLleYrcHz1eQEIJlGRQxB53nqA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.4.4", + "@babel/helper-module-transforms": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-simple-access": "^7.1.0", + "@babel/helper-simple-access": "^7.7.4", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.5.0.tgz", - "integrity": "sha512-Q2m56tyoQWmuNGxEtUyeEkm6qJYFqs4c+XyXH5RAuYxObRNz9Zgj/1g2GMnjYp2EUyEy7YTrxliGCXzecl/vJg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.7.4.tgz", + "integrity": "sha512-y2c96hmcsUi6LrMqvmNDPBBiGCiQu0aYqpHatVVu6kD4mFEXKjyNxd/drc18XXAf9dv7UXjrZwBVmTTGaGP8iw==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.4.4", + "@babel/helper-hoist-variables": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", "babel-plugin-dynamic-import-node": "^2.3.0" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz", - "integrity": "sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.7.4.tgz", + "integrity": "sha512-u2B8TIi0qZI4j8q4C51ktfO7E3cQ0qnaXFI1/OXITordD40tt17g/sXqgNNCcMTcBFKrUPcGDx+TBJuZxLx7tw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.1.0", + "@babel/helper-module-transforms": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.6.2.tgz", - "integrity": "sha512-xBdB+XOs+lgbZc2/4F5BVDVcDNS4tcSKQc96KmlqLEAwz6tpYPEvPdmDfvVG0Ssn8lAhronaRs6Z6KSexIpK5g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.7.4.tgz", + "integrity": "sha512-jBUkiqLKvUWpv9GLSuHUFYdmHg0ujC1JEYoZUfeOOfNydZXp1sXObgyPatpcwjWgsdBGsagWW0cdJpX/DO2jMw==", "dev": true, "requires": { - "regexpu-core": "^4.6.0" + "@babel/helper-create-regexp-features-plugin": "^7.7.4" } }, "@babel/plugin-transform-new-target": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.4.4.tgz", - "integrity": "sha512-r1z3T2DNGQwwe2vPGZMBNjioT2scgWzK9BCnDEh+46z8EEwXBq24uRzd65I7pjtugzPSj921aM15RpESgzsSuA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.7.4.tgz", + "integrity": "sha512-CnPRiNtOG1vRodnsyGX37bHQleHE14B9dnnlgSeEs3ek3fHN1A1SScglTCg1sfbe7sRQ2BUcpgpTpWSfMKz3gg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-object-super": { - "version": "7.5.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.5.5.tgz", - "integrity": "sha512-un1zJQAhSosGFBduPgN/YFNvWVpRuHKU7IHBglLoLZsGmruJPOo6pbInneflUdmq7YvSVqhpPs5zdBvLnteltQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.7.4.tgz", + "integrity": "sha512-ho+dAEhC2aRnff2JCA0SAK7V2R62zJd/7dmtoe7MHcso4C2mS+vZjn1Pb1pCVZvJs1mgsvv5+7sT+m3Bysb6eg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-replace-supers": "^7.5.5" + "@babel/helper-replace-supers": "^7.7.4" } }, "@babel/plugin-transform-parameters": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.4.4.tgz", - "integrity": "sha512-oMh5DUO1V63nZcu/ZVLQFqiihBGo4OpxJxR1otF50GMeCLiRx5nUdtokd+u9SuVJrvvuIh9OosRFPP4pIPnwmw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.7.4.tgz", + "integrity": "sha512-VJwhVePWPa0DqE9vcfptaJSzNDKrWU/4FbYCjZERtmqEs05g3UMXnYMZoXja7JAJ7Y7sPZipwm/pGApZt7wHlw==", "dev": true, "requires": { - "@babel/helper-call-delegate": "^7.4.4", - "@babel/helper-get-function-arity": "^7.0.0", + "@babel/helper-call-delegate": "^7.7.4", + "@babel/helper-get-function-arity": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-property-literals": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.2.0.tgz", - "integrity": "sha512-9q7Dbk4RhgcLp8ebduOpCbtjh7C0itoLYHXd9ueASKAG/is5PQtMR5VJGka9NKqGhYEGn5ITahd4h9QeBMylWQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.7.4.tgz", + "integrity": "sha512-MatJhlC4iHsIskWYyawl53KuHrt+kALSADLQQ/HkhTjX954fkxIEh4q5slL4oRAnsm/eDoZ4q0CIZpcqBuxhJQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-regenerator": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.4.5.tgz", - "integrity": "sha512-gBKRh5qAaCWntnd09S8QC7r3auLCqq5DI6O0DlfoyDjslSBVqBibrMdsqO+Uhmx3+BlOmE/Kw1HFxmGbv0N9dA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.7.4.tgz", + "integrity": "sha512-e7MWl5UJvmPEwFJTwkBlPmqixCtr9yAASBqff4ggXTNicZiwbF8Eefzm6NVgfiBp7JdAGItecnctKTgH44q2Jw==", "dev": true, "requires": { "regenerator-transform": "^0.14.0" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.2.0.tgz", - "integrity": "sha512-fz43fqW8E1tAB3DKF19/vxbpib1fuyCwSPE418ge5ZxILnBhWyhtPgz8eh1RCGGJlwvksHkyxMxh0eenFi+kFw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.7.4.tgz", + "integrity": "sha512-OrPiUB5s5XvkCO1lS7D8ZtHcswIC57j62acAnJZKqGGnHP+TIc/ljQSrgdX/QyOTdEK5COAhuc820Hi1q2UgLQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz", - "integrity": "sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.7.4.tgz", + "integrity": "sha512-q+suddWRfIcnyG5YiDP58sT65AJDZSUhXQDZE3r04AuqD6d/XLaQPPXSBzP2zGerkgBivqtQm9XKGLuHqBID6Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-spread": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.6.2.tgz", - "integrity": "sha512-DpSvPFryKdK1x+EDJYCy28nmAaIMdxmhot62jAXF/o99iA33Zj2Lmcp3vDmz+MUh0LNYVPvfj5iC3feb3/+PFg==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.7.4.tgz", + "integrity": "sha512-8OSs0FLe5/80cndziPlg4R0K6HcWSM0zyNhHhLsmw/Nc5MaA49cAsnoJ/t/YZf8qkG7fD+UjTRaApVDB526d7Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz", - "integrity": "sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.7.4.tgz", + "integrity": "sha512-Ls2NASyL6qtVe1H1hXts9yuEeONV2TJZmplLONkMPUG158CtmnrzW5Q5teibM5UVOFjG0D3IC5mzXR6pPpUY7A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0", @@ -653,86 +670,86 @@ } }, "@babel/plugin-transform-template-literals": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.4.4.tgz", - "integrity": "sha512-mQrEC4TWkhLN0z8ygIvEL9ZEToPhG5K7KDW3pzGqOfIGZ28Jb0POUkeWcoz8HnHvhFy6dwAT1j8OzqN8s804+g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.7.4.tgz", + "integrity": "sha512-sA+KxLwF3QwGj5abMHkHgshp9+rRz+oY9uoRil4CyLtgEuE/88dpkeWgNk5qKVsJE9iSfly3nvHapdRiIS2wnQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.0.0", + "@babel/helper-annotate-as-pure": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz", - "integrity": "sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.7.4.tgz", + "integrity": "sha512-KQPUQ/7mqe2m0B8VecdyaW5XcQYaePyl9R7IsKd+irzj6jvbhoGnRE+M0aNkyAzI07VfUQ9266L5xMARitV3wg==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.6.2.tgz", - "integrity": "sha512-orZI6cWlR3nk2YmYdb0gImrgCUwb5cBUwjf6Ks6dvNVvXERkwtJWOQaEOjPiu0Gu1Tq6Yq/hruCZZOOi9F34Dw==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.7.4.tgz", + "integrity": "sha512-N77UUIV+WCvE+5yHw+oks3m18/umd7y392Zv7mYTpFqHtkpcc+QUz+gLJNTWVlWROIWeLqY0f3OjZxV5TcXnRw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/helper-regex": "^7.4.4", - "regexpu-core": "^4.6.0" + "@babel/helper-create-regexp-features-plugin": "^7.7.4", + "@babel/helper-plugin-utils": "^7.0.0" } }, "@babel/preset-env": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.6.2.tgz", - "integrity": "sha512-Ru7+mfzy9M1/YTEtlDS8CD45jd22ngb9tXnn64DvQK3ooyqSw9K4K9DUWmYknTTVk4TqygL9dqCrZgm1HMea/Q==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.7.4.tgz", + "integrity": "sha512-Dg+ciGJjwvC1NIe/DGblMbcGq1HOtKbw8RLl4nIjlfcILKEOkWT/vRqPpumswABEBVudii6dnVwrBtzD7ibm4g==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-module-imports": "^7.7.4", "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-async-generator-functions": "^7.2.0", - "@babel/plugin-proposal-dynamic-import": "^7.5.0", - "@babel/plugin-proposal-json-strings": "^7.2.0", - "@babel/plugin-proposal-object-rest-spread": "^7.6.2", - "@babel/plugin-proposal-optional-catch-binding": "^7.2.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.6.2", - "@babel/plugin-syntax-async-generators": "^7.2.0", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/plugin-syntax-json-strings": "^7.2.0", - "@babel/plugin-syntax-object-rest-spread": "^7.2.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.2.0", - "@babel/plugin-transform-arrow-functions": "^7.2.0", - "@babel/plugin-transform-async-to-generator": "^7.5.0", - "@babel/plugin-transform-block-scoped-functions": "^7.2.0", - "@babel/plugin-transform-block-scoping": "^7.6.2", - "@babel/plugin-transform-classes": "^7.5.5", - "@babel/plugin-transform-computed-properties": "^7.2.0", - "@babel/plugin-transform-destructuring": "^7.6.0", - "@babel/plugin-transform-dotall-regex": "^7.6.2", - "@babel/plugin-transform-duplicate-keys": "^7.5.0", - "@babel/plugin-transform-exponentiation-operator": "^7.2.0", - "@babel/plugin-transform-for-of": "^7.4.4", - "@babel/plugin-transform-function-name": "^7.4.4", - "@babel/plugin-transform-literals": "^7.2.0", - "@babel/plugin-transform-member-expression-literals": "^7.2.0", - "@babel/plugin-transform-modules-amd": "^7.5.0", - "@babel/plugin-transform-modules-commonjs": "^7.6.0", - "@babel/plugin-transform-modules-systemjs": "^7.5.0", - "@babel/plugin-transform-modules-umd": "^7.2.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.6.2", - "@babel/plugin-transform-new-target": "^7.4.4", - "@babel/plugin-transform-object-super": "^7.5.5", - "@babel/plugin-transform-parameters": "^7.4.4", - "@babel/plugin-transform-property-literals": "^7.2.0", - "@babel/plugin-transform-regenerator": "^7.4.5", - "@babel/plugin-transform-reserved-words": "^7.2.0", - "@babel/plugin-transform-shorthand-properties": "^7.2.0", - "@babel/plugin-transform-spread": "^7.6.2", - "@babel/plugin-transform-sticky-regex": "^7.2.0", - "@babel/plugin-transform-template-literals": "^7.4.4", - "@babel/plugin-transform-typeof-symbol": "^7.2.0", - "@babel/plugin-transform-unicode-regex": "^7.6.2", - "@babel/types": "^7.6.0", + "@babel/plugin-proposal-async-generator-functions": "^7.7.4", + "@babel/plugin-proposal-dynamic-import": "^7.7.4", + "@babel/plugin-proposal-json-strings": "^7.7.4", + "@babel/plugin-proposal-object-rest-spread": "^7.7.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.7.4", + "@babel/plugin-proposal-unicode-property-regex": "^7.7.4", + "@babel/plugin-syntax-async-generators": "^7.7.4", + "@babel/plugin-syntax-dynamic-import": "^7.7.4", + "@babel/plugin-syntax-json-strings": "^7.7.4", + "@babel/plugin-syntax-object-rest-spread": "^7.7.4", + "@babel/plugin-syntax-optional-catch-binding": "^7.7.4", + "@babel/plugin-syntax-top-level-await": "^7.7.4", + "@babel/plugin-transform-arrow-functions": "^7.7.4", + "@babel/plugin-transform-async-to-generator": "^7.7.4", + "@babel/plugin-transform-block-scoped-functions": "^7.7.4", + "@babel/plugin-transform-block-scoping": "^7.7.4", + "@babel/plugin-transform-classes": "^7.7.4", + "@babel/plugin-transform-computed-properties": "^7.7.4", + "@babel/plugin-transform-destructuring": "^7.7.4", + "@babel/plugin-transform-dotall-regex": "^7.7.4", + "@babel/plugin-transform-duplicate-keys": "^7.7.4", + "@babel/plugin-transform-exponentiation-operator": "^7.7.4", + "@babel/plugin-transform-for-of": "^7.7.4", + "@babel/plugin-transform-function-name": "^7.7.4", + "@babel/plugin-transform-literals": "^7.7.4", + "@babel/plugin-transform-member-expression-literals": "^7.7.4", + "@babel/plugin-transform-modules-amd": "^7.7.4", + "@babel/plugin-transform-modules-commonjs": "^7.7.4", + "@babel/plugin-transform-modules-systemjs": "^7.7.4", + "@babel/plugin-transform-modules-umd": "^7.7.4", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.7.4", + "@babel/plugin-transform-new-target": "^7.7.4", + "@babel/plugin-transform-object-super": "^7.7.4", + "@babel/plugin-transform-parameters": "^7.7.4", + "@babel/plugin-transform-property-literals": "^7.7.4", + "@babel/plugin-transform-regenerator": "^7.7.4", + "@babel/plugin-transform-reserved-words": "^7.7.4", + "@babel/plugin-transform-shorthand-properties": "^7.7.4", + "@babel/plugin-transform-spread": "^7.7.4", + "@babel/plugin-transform-sticky-regex": "^7.7.4", + "@babel/plugin-transform-template-literals": "^7.7.4", + "@babel/plugin-transform-typeof-symbol": "^7.7.4", + "@babel/plugin-transform-unicode-regex": "^7.7.4", + "@babel/types": "^7.7.4", "browserslist": "^4.6.0", "core-js-compat": "^3.1.1", "invariant": "^2.2.2", @@ -741,37 +758,37 @@ } }, "@babel/template": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.6.0.tgz", - "integrity": "sha512-5AEH2EXD8euCk446b7edmgFdub/qfH1SN6Nii3+fyXP807QRx9Q73A2N5hNwRRslC2H9sNzaFhsPubkS4L8oNQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.4.tgz", + "integrity": "sha512-qUzihgVPguAzXCK7WXw8pqs6cEwi54s3E+HrejlkuWO6ivMKx9hZl3Y2fSXp9i5HgyWmj7RKP+ulaYnKM4yYxw==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.6.0", - "@babel/types": "^7.6.0" + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4" } }, "@babel/traverse": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.6.2.tgz", - "integrity": "sha512-8fRE76xNwNttVEF2TwxJDGBLWthUkHWSldmfuBzVRmEDWOtu4XdINTgN7TDWzuLg4bbeIMLvfMFD9we5YcWkRQ==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.4.tgz", + "integrity": "sha512-P1L58hQyupn8+ezVA2z5KBm4/Zr4lCC8dwKCMYzsa5jFMDMQAzaBNy9W5VjB+KAmBjb40U7a/H6ao+Xo+9saIw==", "dev": true, "requires": { "@babel/code-frame": "^7.5.5", - "@babel/generator": "^7.6.2", - "@babel/helper-function-name": "^7.1.0", - "@babel/helper-split-export-declaration": "^7.4.4", - "@babel/parser": "^7.6.2", - "@babel/types": "^7.6.0", + "@babel/generator": "^7.7.4", + "@babel/helper-function-name": "^7.7.4", + "@babel/helper-split-export-declaration": "^7.7.4", + "@babel/parser": "^7.7.4", + "@babel/types": "^7.7.4", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.13" } }, "@babel/types": { - "version": "7.6.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.6.1.tgz", - "integrity": "sha512-X7gdiuaCmA0uRjCmRtYJNAVCc/q+5xSgsfKJHqMN4iNLILX39677fJE1O40arPMh0TTtS9ItH67yre6c7k6t0g==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.4.tgz", + "integrity": "sha512-cz5Ji23KCi4T+YIE/BolWosrJuSmoZeN1EFnRtBwF+KKLi8GG/Z2c2hOJJeCXPk4mwk4QFvTmwIodJowXgttRA==", "dev": true, "requires": { "esutils": "^2.0.2", @@ -779,6 +796,16 @@ "to-fast-properties": "^2.0.0" } }, + "@cnakazawa/watch": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.3.tgz", + "integrity": "sha512-r5160ogAvGyHsal38Kux7YYtodEKOj89RGb28ht1jh3SJb08VwRwAKKJL0bGb04Zd/3r9FL3BFIc3bBidYffCA==", + "dev": true, + "requires": { + "exec-sh": "^0.3.2", + "minimist": "^1.2.0" + } + }, "@gulp-sourcemaps/identity-map": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@gulp-sourcemaps/identity-map/-/identity-map-1.0.2.tgz", @@ -830,6 +857,241 @@ } } }, + "@jest/console": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-24.9.0.tgz", + "integrity": "sha512-Zuj6b8TnKXi3q4ymac8EQfc3ea/uhLeCGThFqXeC8H9/raaH8ARPUTdId+XyGd03Z4In0/VjD2OYFcBF09fNLQ==", + "dev": true, + "requires": { + "@jest/source-map": "^24.9.0", + "chalk": "^2.0.1", + "slash": "^2.0.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "@jest/core": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-24.9.0.tgz", + "integrity": "sha512-Fogg3s4wlAr1VX7q+rhV9RVnUv5tD7VuWfYy1+whMiWUrvl7U3QJSJyWcDio9Lq2prqYsZaeTv2Rz24pWGkJ2A==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/reporters": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-changed-files": "^24.9.0", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-resolve-dependencies": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "jest-watcher": "^24.9.0", + "micromatch": "^3.1.10", + "p-each-series": "^1.0.0", + "realpath-native": "^1.1.0", + "rimraf": "^2.5.4", + "slash": "^2.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "@jest/environment": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-24.9.0.tgz", + "integrity": "sha512-5A1QluTPhvdIPFYnO3sZC3smkNeXPVELz7ikPbhUj0bQjB07EoE9qtLrem14ZUYWdVayYbsjVwIiL4WBIMV4aQ==", + "dev": true, + "requires": { + "@jest/fake-timers": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/fake-timers": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-24.9.0.tgz", + "integrity": "sha512-eWQcNa2YSwzXWIMC5KufBh3oWRIijrQFROsIqt6v/NS9Io/gknw1jsAC9c+ih/RQX4A3O7SeWAhQeN0goKhT9A==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0" + } + }, + "@jest/reporters": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-24.9.0.tgz", + "integrity": "sha512-mu4X0yjaHrffOsWmVLzitKmmmWSQ3GGuefgNscUSWNiUNcEOSEQk9k3pERKEQVBb0Cnn88+UESIsZEMH3o88Gw==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.2", + "istanbul-lib-coverage": "^2.0.2", + "istanbul-lib-instrument": "^3.0.1", + "istanbul-lib-report": "^2.0.4", + "istanbul-lib-source-maps": "^3.0.1", + "istanbul-reports": "^2.2.6", + "jest-haste-map": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "node-notifier": "^5.4.2", + "slash": "^2.0.0", + "source-map": "^0.6.0", + "string-length": "^2.0.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/source-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-24.9.0.tgz", + "integrity": "sha512-/Xw7xGlsZb4MJzNDgB7PW5crou5JqWiBQaz6xyPd3ArOg2nfn/PunV8+olXbbEZzNl591o5rWKE9BRDaFAuIBg==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.1.15", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/test-result": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-24.9.0.tgz", + "integrity": "sha512-XEFrHbBonBJ8dGp2JmF8kP/nQI/ImPpygKHwQ/SY+es59Z3L5PI4Qb9TQQMAEeYsThG1xF0k6tmG0tIKATNiiA==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/istanbul-lib-coverage": "^2.0.0" + } + }, + "@jest/test-sequencer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-24.9.0.tgz", + "integrity": "sha512-6qqsU4o0kW1dvA95qfNog8v8gkRN9ph6Lz7r96IvZpHdNipP2cBcb07J1Z45mz/VIS01OHJ3pY8T5fUY38tg4A==", + "dev": true, + "requires": { + "@jest/test-result": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-runner": "^24.9.0", + "jest-runtime": "^24.9.0" + } + }, + "@jest/transform": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-24.9.0.tgz", + "integrity": "sha512-TcQUmyNRxV94S0QpMOnZl0++6RMiqpbH/ZMccFB/amku6Uwvyb1cjYX7xkp5nGNkbX4QPH/FcB6q1HBTHynLmQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^24.9.0", + "babel-plugin-istanbul": "^5.1.0", + "chalk": "^2.0.1", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.1.15", + "jest-haste-map": "^24.9.0", + "jest-regex-util": "^24.9.0", + "jest-util": "^24.9.0", + "micromatch": "^3.1.10", + "pirates": "^4.0.1", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "2.4.1" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + } + }, "@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -871,6 +1133,93 @@ "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", "dev": true }, + "@types/babel__core": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.3.tgz", + "integrity": "sha512-8fBo0UR2CcwWxeX7WIIgJ7lXjasFxoYgRnFHUj+hRvKkpiBJbxhdAPTCY6/ZKM0uxANFVzt4yObSLuTiTnazDA==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "@types/babel__generator": { + "version": "7.6.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.0.tgz", + "integrity": "sha512-c1mZUu4up5cp9KROs/QAw0gTeHrw/x7m52LcnvMxxOZ03DmLwPV0MlGmlgzV3cnSdjhJOZsj7E7FHeioai+egw==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0" + } + }, + "@types/babel__template": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.0.2.tgz", + "integrity": "sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==", + "dev": true, + "requires": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "@types/babel__traverse": { + "version": "7.0.8", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.8.tgz", + "integrity": "sha512-yGeB2dHEdvxjP0y4UbRtQaSkXJ9649fYCmIdRoul5kfAoGCwxuCbMhag0k3RPfnuh9kPGm8x89btcfDEXdVWGw==", + "dev": true, + "requires": { + "@babel/types": "^7.3.0" + } + }, + "@types/istanbul-lib-coverage": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==", + "dev": true + }, + "@types/istanbul-lib-report": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz", + "integrity": "sha512-3BUTyMzbZa2DtDI2BkERNC6jJw2Mr2Y0oGI7mRxYNBPxppbtEK1F66u3bKwU2g+wxwWI7PAoRpJnOY1grJqzHg==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*" + } + }, + "@types/istanbul-reports": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz", + "integrity": "sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "@types/stack-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-1.0.1.tgz", + "integrity": "sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==", + "dev": true + }, + "@types/yargs": { + "version": "13.0.3", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.3.tgz", + "integrity": "sha512-K8/LfZq2duW33XW/tFwEAfnZlqIfVsoyRB3kfXdPXYhl0nfM8mmh7GS0jg7WrX2Dgq/0Ha/pR1PaR+BvmWwjiQ==", + "dev": true, + "requires": { + "@types/yargs-parser": "*" + } + }, + "@types/yargs-parser": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-13.1.0.tgz", + "integrity": "sha512-gCubfBUZ6KxzoibJ+SCUc/57Ms1jz5NjHe4+dI2krNmU5zCPAphyLJYyTOg06ueIyfj+SaCUqmzun7ImlxDcKg==", + "dev": true + }, "JSONStream": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", @@ -881,6 +1230,12 @@ "through": ">=2.2.7 <3" } }, + "abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", + "dev": true + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -891,7 +1246,6 @@ "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", - "dev": true, "requires": { "mime-types": "~2.1.24", "negotiator": "0.6.2" @@ -920,6 +1274,24 @@ } } }, + "acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dev": true, + "requires": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + }, + "dependencies": { + "acorn": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", + "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "dev": true + } + } + }, "acorn-jsx": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", @@ -1005,10 +1377,13 @@ "dev": true }, "ansi-colors": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", - "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", - "dev": true + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", + "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", + "dev": true, + "requires": { + "ansi-wrap": "^0.1.0" + } }, "ansi-escapes": { "version": "3.2.0", @@ -1206,6 +1581,12 @@ "integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8=", "dev": true }, + "array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", + "dev": true + }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -1215,8 +1596,7 @@ "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=", - "dev": true + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-from": { "version": "2.1.1", @@ -1377,6 +1757,12 @@ "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", "dev": true }, + "astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -1435,9 +1821,9 @@ "dev": true }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.0.tgz", + "integrity": "sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A==", "dev": true }, "babel-code-frame": { @@ -1729,6 +2115,29 @@ "babel-template": "^6.24.1" } }, + "babel-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-24.9.0.tgz", + "integrity": "sha512-ntuddfyiN+EhMw58PTNL1ph4C9rECiQXjI4nMMBKBaNjXvqLdkXpPRcMSr4iyBrJg/+wz9brFUD6RhOAT6r4Iw==", + "dev": true, + "requires": { + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/babel__core": "^7.1.0", + "babel-plugin-istanbul": "^5.1.0", + "babel-preset-jest": "^24.9.0", + "chalk": "^2.4.2", + "slash": "^2.0.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, "babel-loader": { "version": "8.0.6", "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.0.6.tgz", @@ -1768,6 +2177,27 @@ "object.assign": "^4.1.0" } }, + "babel-plugin-istanbul": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-5.2.0.tgz", + "integrity": "sha512-5LphC0USA8t4i1zCtjbbNb6jJj/9+X6P37Qfirc/70EQ34xKlMW+a1RHGwxGI+SwWpNwZ27HqvzAobeqaXwiZw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "find-up": "^3.0.0", + "istanbul-lib-instrument": "^3.3.0", + "test-exclude": "^5.2.3" + } + }, + "babel-plugin-jest-hoist": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-24.9.0.tgz", + "integrity": "sha512-2EMA2P8Vp7lG0RAzr4HXqtYwacfMErOuv1U3wrvxHX6rD1sV6xS3WXG3r8TRQ2r6w8OhvSdWt+z41hQNwNm3Xw==", + "dev": true, + "requires": { + "@types/babel__traverse": "^7.0.6" + } + }, "babel-plugin-syntax-async-functions": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz", @@ -2398,6 +2828,16 @@ "babel-plugin-transform-flow-strip-types": "^6.22.0" } }, + "babel-preset-jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-24.9.0.tgz", + "integrity": "sha512-izTUuhE4TMfTRPF92fFwD2QfdXaZW08qvWTFCI51V8rW5x00UuPgc3ajRoWofXOuxjfcOM5zzSYsQS3H8KGCAg==", + "dev": true, + "requires": { + "@babel/plugin-syntax-object-rest-spread": "^7.0.0", + "babel-plugin-jest-hoist": "^24.9.0" + } + }, "babel-preset-react": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", @@ -2731,9 +3171,9 @@ "dev": true }, "binaryextensions": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.2.tgz", - "integrity": "sha512-xVNN69YGDghOqCCtA6FI7avYrr02mTJjOgB0/f1VPD3pJC8QEvjTKWc4epDx8AqxxA75NI0QpVM2gPJXUbE4Tg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.2.0.tgz", + "integrity": "sha512-bHhs98rj/7i/RZpCSJ3uk55pLXOItjIrh2sRQZSM6OoktScX+LxJzvlU+FELp9j3TdcddTmmYArLSGptCTwjuw==", "dev": true }, "bl": { @@ -2753,9 +3193,9 @@ "dev": true }, "bluebird": { - "version": "3.5.5", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz", - "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w==", + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, "bn.js": { @@ -2905,6 +3345,12 @@ "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", "dev": true }, + "browser-process-hrtime": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", + "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", + "dev": true + }, "browser-resolve": { "version": "1.11.3", "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", @@ -3000,14 +3446,14 @@ } }, "browserslist": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.7.0.tgz", - "integrity": "sha512-9rGNDtnj+HaahxiVV38Gn8n8Lr8REKsel68v1sPFfIGEK6uSXTY3h9acgiT1dZVtOOUtifo/Dn8daDQ5dUgVsA==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.8.0.tgz", + "integrity": "sha512-HYnxc/oLRWvJ3TsGegR0SRL/UDnknGq2s/a8dYYEO+kOQ9m9apKoS5oiathLKZdh/e9uE+/J3j92qPlGD/vTqA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30000989", - "electron-to-chromium": "^1.3.247", - "node-releases": "^1.1.29" + "caniuse-lite": "^1.0.30001012", + "electron-to-chromium": "^1.3.317", + "node-releases": "^1.1.41" } }, "browserstack": { @@ -3031,6 +3477,15 @@ "temp-fs": "^0.9.9" } }, + "bser": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", + "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", + "dev": true, + "requires": { + "node-int64": "^0.4.0" + } + }, "buffer": { "version": "5.4.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz", @@ -3189,6 +3644,14 @@ "dev": true, "requires": { "callsites": "^0.2.0" + }, + "dependencies": { + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + } } }, "callsite": { @@ -3198,9 +3661,9 @@ "dev": true }, "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true }, "camelcase": { @@ -3228,11 +3691,20 @@ } }, "caniuse-lite": { - "version": "1.0.30000997", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000997.tgz", - "integrity": "sha512-BQLFPIdj2ntgBNWp9Q64LGUIEmvhKkzzHhUHR3CD5A9Lb7ZKF20/+sgadhFap69lk5XmK1fTUleDclaRFvgVUA==", + "version": "1.0.30001015", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001015.tgz", + "integrity": "sha512-/xL2AbW/XWHNu1gnIrO8UitBGoFthcsDgU9VLK1/dpsoxbaD5LscHozKze05R6WLsBvLhqv78dAPozMFQBYLbQ==", "dev": true }, + "capture-exit": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", + "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", + "dev": true, + "requires": { + "rsvp": "^4.8.4" + } + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -3342,6 +3814,12 @@ "upath": "^1.1.1" } }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -3397,29 +3875,29 @@ "dev": true }, "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" }, "dependencies": { "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", "dev": true }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -3553,9 +4031,9 @@ "dev": true }, "commander": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", - "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, "commondir": { @@ -3676,13 +4154,10 @@ "dev": true }, "console-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.1.0.tgz", - "integrity": "sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA=", - "dev": true, - "requires": { - "date-now": "^0.1.4" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", + "dev": true }, "constants-browserify": { "version": "1.0.0", @@ -3697,19 +4172,14 @@ "dev": true }, "content-disposition": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", - "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", - "dev": true, - "requires": { - "safe-buffer": "5.1.2" - } + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" }, "content-type": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" }, "continuable-cache": { "version": "0.3.1", @@ -3718,9 +4188,9 @@ "dev": true }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -3729,14 +4199,12 @@ "cookie": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", - "dev": true + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", - "dev": true + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "copy-descriptor": { "version": "0.1.1", @@ -3755,17 +4223,17 @@ } }, "core-js": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz", - "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==" + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.10.tgz", + "integrity": "sha512-I39t74+4t+zau64EN1fE5v2W31Adtc/REhzWN+gWRRXg6WH5qAsZm62DHpQ1+Yhe4047T55jvzz7MUqF/dBBlA==" }, "core-js-compat": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.2.1.tgz", - "integrity": "sha512-MwPZle5CF9dEaMYdDeWm73ao/IflDH+FjeJCWEADcEgFSE9TLimFKwJsfmkwzI8eC0Aj0mgvMDjeQjrElkz4/A==", + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.4.7.tgz", + "integrity": "sha512-57+mgz/P/xsGdjwQYkwtBZR3LuISaxD1dEwVDtbk8xJMqAmwqaxLOvnNT7kdJ7jYE/NjNptyzXi+IQFMi/2fCw==", "dev": true, "requires": { - "browserslist": "^4.6.6", + "browserslist": "^4.8.0", "semver": "^6.3.0" }, "dependencies": { @@ -3784,17 +4252,16 @@ "dev": true }, "coveralls": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.6.tgz", - "integrity": "sha512-Pgh4v3gCI4T/9VijVrm8Ym5v0OgjvGLKj3zTUwkvsCiwqae/p6VLzpsFNjQS2i6ewV7ef+DjFJ5TSKxYt/mCrA==", + "version": "3.0.9", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.9.tgz", + "integrity": "sha512-nNBg3B1+4iDox5A5zqHKzUTiwl2ey4k2o0NEcVZYvl+GOSJdKBj4AJGKLv6h3SvWch7tABHePAQOSZWM9E2hMg==", "dev": true, "requires": { - "growl": "~> 1.10.0", "js-yaml": "^3.13.1", - "lcov-parse": "^0.0.10", + "lcov-parse": "^1.0.0", "log-driver": "^1.2.7", "minimist": "^1.2.0", - "request": "^2.86.0" + "request": "^2.88.0" } }, "crc": { @@ -3925,6 +4392,21 @@ "integrity": "sha1-Xv1sLupeof1rasV+wEJ7GEUkJOo=", "dev": true }, + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, + "cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "dev": true, + "requires": { + "cssom": "0.3.x" + } + }, "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", @@ -3959,18 +4441,36 @@ "assert-plus": "^1.0.0" } }, + "data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + }, + "dependencies": { + "whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + } + } + }, "date-format": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/date-format/-/date-format-1.2.0.tgz", "integrity": "sha1-YV6CjiM90aubua4JUODOzPpuytg=", "dev": true }, - "date-now": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz", - "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", - "dev": true - }, "dateformat": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz", @@ -4042,6 +4542,11 @@ "type-detect": "^4.0.0" } }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4162,13 +4667,12 @@ "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "dev": true + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" }, "des.js": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", - "integrity": "sha1-wHTS4qpqipoH29YfmhXCzYPsjsw=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", + "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -4178,8 +4682,7 @@ "destroy": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", - "dev": true + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, "detab": { "version": "2.0.2", @@ -4239,6 +4742,12 @@ "integrity": "sha1-fyjS657nsVqX79ic5j3P2qPMur8=", "dev": true }, + "diff-sequences": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", + "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", + "dev": true + }, "diffie-hellman": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", @@ -4350,6 +4859,12 @@ "yargs": "^9.0.1" }, "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", @@ -4380,32 +4895,6 @@ } } }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -4421,12 +4910,6 @@ "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", "dev": true }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -4436,15 +4919,6 @@ "number-is-nan": "^1.0.0" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -4475,32 +4949,6 @@ "path-exists": "^3.0.0" } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -4574,6 +5022,56 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", @@ -4642,6 +5140,15 @@ "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", "dev": true }, + "domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dev": true, + "requires": { + "webidl-conversions": "^4.0.2" + } + }, "dset": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/dset/-/dset-2.0.1.tgz", @@ -4709,8 +5216,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=", - "dev": true + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { "version": "2.5.9", @@ -4719,15 +5225,15 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.266", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.266.tgz", - "integrity": "sha512-UTuTZ4v8T0gLPHI7U75PXLQePWI65MTS3mckRrnLCkNljHvsutbYs+hn2Ua/RFul3Jt/L3Ht2rLP+dU/AlBfrQ==", + "version": "1.3.322", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.322.tgz", + "integrity": "sha512-Tc8JQEfGQ1MzfSzI/bTlSr7btJv/FFO7Yh6tanqVmIWOuNCu6/D1MilIEgLtmWqIrsv+o4IjpLAhgMBr/ncNAA==", "dev": true }, "elliptic": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.1.tgz", - "integrity": "sha512-xvJINNLbTeWQjrl6X+7eQCrIy/YPv5XCpKW6kB5mKvtnGILoLDcySuwomfdzt0BMdLNVnuRNTuzKNHj0bva1Cg==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.2.tgz", + "integrity": "sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -4754,8 +5260,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "dev": true + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "end-of-stream": { "version": "1.4.4", @@ -4794,6 +5299,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, @@ -4836,6 +5352,17 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "ws": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", + "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "dev": true, + "requires": { + "async-limiter": "~1.0.0", + "safe-buffer": "~5.1.0", + "ultron": "~1.1.0" + } } } }, @@ -4880,9 +5407,9 @@ } }, "error": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.0.tgz", - "integrity": "sha512-M6t3j3Vt3uDicrViMP5fLq2AeADNrCVFD8Oj4Qt2MHsX0mPYG7D5XdnEfSdRpaHQzjAJ19wu+I1mw9rQYMTAPg==", + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", "dev": true, "requires": { "string-template": "~0.2.1" @@ -4898,27 +5425,27 @@ } }, "es-abstract": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.14.2.tgz", - "integrity": "sha512-DgoQmbpFNOofkjJtKwr87Ma5EW4Dc8fWhD0R+ndq7Oc456ivUfGOOP6oAZTTKl5/CcNMP+EN+e3/iUzgE0veZg==", + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.3.tgz", + "integrity": "sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw==", "dev": true, "requires": { - "es-to-primitive": "^1.2.0", + "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", "has": "^1.0.3", - "has-symbols": "^1.0.0", + "has-symbols": "^1.0.1", "is-callable": "^1.1.4", "is-regex": "^1.0.4", - "object-inspect": "^1.6.0", + "object-inspect": "^1.7.0", "object-keys": "^1.1.1", - "string.prototype.trimleft": "^2.0.0", - "string.prototype.trimright": "^2.0.0" + "string.prototype.trimleft": "^2.1.0", + "string.prototype.trimright": "^2.1.0" } }, "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, "requires": { "is-callable": "^1.1.4", @@ -4927,14 +5454,14 @@ } }, "es5-ext": { - "version": "0.10.51", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.51.tgz", - "integrity": "sha512-oRpWzM2WcLHVKpnrcyB7OW8j/s67Ba04JCm0WnNv3RiABSvs7mrQlutB8DBv793gKcp0XENR8Il8WxGTlZ73gQ==", + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", "dev": true, "requires": { "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.1", - "next-tick": "^1.0.0" + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" } }, "es5-shim": { @@ -5009,13 +5536,13 @@ } }, "es6-symbol": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.2.tgz", - "integrity": "sha512-/ZypxQsArlv+KHpGvng52/Iz8by3EQPxhmbuz8yFG89N/caTFBSbcXONDw0aMjy827gQg26XAjP4uXFvnfINmQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { "d": "^1.0.1", - "es5-ext": "^0.10.51" + "ext": "^1.1.2" } }, "es6-weak-map": { @@ -5033,8 +5560,7 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=", - "dev": true + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", @@ -5043,39 +5569,30 @@ "dev": true }, "escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz", + "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==", "dev": true, "requires": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", + "esprima": "^3.1.3", + "estraverse": "^4.2.0", "esutils": "^2.0.2", "optionator": "^0.8.1", - "source-map": "~0.2.0" + "source-map": "~0.6.1" }, "dependencies": { "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", "dev": true }, "source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "optional": true, - "requires": { - "amdefine": ">=0.0.4" - } + "optional": true } } }, @@ -5532,8 +6049,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", - "dev": true + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "event-emitter": { "version": "0.3.5", @@ -5590,6 +6106,12 @@ "safe-buffer": "^5.1.1" } }, + "exec-sh": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", + "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", + "dev": true + }, "execa": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", @@ -5616,6 +6138,12 @@ } } }, + "exit": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", + "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", + "dev": true + }, "expand-braces": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", @@ -5733,123 +6261,173 @@ "homedir-polyfill": "^1.0.1" } }, - "express": { - "version": "4.17.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", - "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "expect": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-24.9.0.tgz", + "integrity": "sha512-wvVAx8XIol3Z5m9zvZXiyZOQ+sRJqNTIm6sGjdWlaZIeupQGO3WbYI+15D/AmEwZywL6wtJkbAbJtzkOfBuR0Q==", "dev": true, "requires": { - "accepts": "~1.3.7", + "@jest/types": "^24.9.0", + "ansi-styles": "^3.2.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-regex-util": "^24.9.0" + } + }, + "express": { + "version": "4.15.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.15.4.tgz", + "integrity": "sha1-Ay4iU0ic+PzgJma+yj0R7XotrtE=", + "requires": { + "accepts": "~1.3.3", "array-flatten": "1.1.1", - "body-parser": "1.19.0", - "content-disposition": "0.5.3", - "content-type": "~1.0.4", - "cookie": "0.4.0", + "content-disposition": "0.5.2", + "content-type": "~1.0.2", + "cookie": "0.3.1", "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", + "debug": "2.6.8", + "depd": "~1.1.1", + "encodeurl": "~1.0.1", "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", + "etag": "~1.8.0", + "finalhandler": "~1.0.4", + "fresh": "0.5.0", "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.3", + "parseurl": "~1.3.1", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.5", - "qs": "6.7.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.1.2", - "send": "0.17.1", - "serve-static": "1.14.1", - "setprototypeof": "1.1.1", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "proxy-addr": "~1.1.5", + "qs": "6.5.0", + "range-parser": "~1.2.0", + "send": "0.15.4", + "serve-static": "1.12.4", + "setprototypeof": "1.0.3", + "statuses": "~1.3.1", + "type-is": "~1.6.15", + "utils-merge": "1.0.0", + "vary": "~1.1.1" }, "dependencies": { - "cookie": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", - "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", - "dev": true - }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "version": "2.6.8", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", + "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", "requires": { "ms": "2.0.0" } }, - "http-errors": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", - "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", - "dev": true, + "finalhandler": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "integrity": "sha1-AHrqM9Gk0+QgF/YkhIrVjSEvgU8=", "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.0" + "debug": "2.6.9", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } } }, + "fresh": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.0.tgz", + "integrity": "sha1-9HTKXmqSRtb9jglTz6m5yAWvp44=" + }, + "mime": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.4.tgz", + "integrity": "sha1-EV+eO2s9rylZmDyzjxSaLUDrXVM=" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", - "dev": true + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "qs": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", - "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", - "dev": true + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.0.tgz", + "integrity": "sha512-fjVFjW9yhqMhVGwRExCXLhJKrLlkYSaxNWdyc9rmHlrVZbk35YHH312dFd7191uQeXkI3mKLZTIbSvIeFwFemg==" }, "send": { - "version": "0.17.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", - "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", - "dev": true, + "version": "0.15.4", + "resolved": "https://registry.npmjs.org/send/-/send-0.15.4.tgz", + "integrity": "sha1-mF+qPihLAnPHkzZKNcZze9k5Bbk=", "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", + "debug": "2.6.8", + "depd": "~1.1.1", "destroy": "~1.0.4", - "encodeurl": "~1.0.2", + "encodeurl": "~1.0.1", "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.7.2", - "mime": "1.6.0", - "ms": "2.1.1", + "etag": "~1.8.0", + "fresh": "0.5.0", + "http-errors": "~1.6.2", + "mime": "1.3.4", + "ms": "2.0.0", "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, - "dependencies": { - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } + "range-parser": "~1.2.0", + "statuses": "~1.3.1" + } + }, + "serve-static": { + "version": "1.12.4", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.12.4.tgz", + "integrity": "sha1-m2qpjutyU8Tu3Ewfb9vKYJkBqWE=", + "requires": { + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "parseurl": "~1.3.1", + "send": "0.15.4" } }, "setprototypeof": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", - "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", + "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" + }, + "statuses": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=" + }, + "utils-merge": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.0.tgz", + "integrity": "sha1-ApT7kiu5N1FTVBxPcJYjHyh8ivg=" + } + } + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", + "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==", "dev": true } } @@ -6008,6 +6586,15 @@ "websocket-driver": ">=0.5.1" } }, + "fb-watchman": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", + "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", + "dev": true, + "requires": { + "bser": "^2.0.0" + } + }, "fibers": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fibers/-/fibers-3.1.1.tgz", @@ -6164,23 +6751,6 @@ "integrity": "sha512-lNaHNVymajmk0OJMBn8fVUAU1BtDeKIqKoVhk4xAALB57aALg6b4W0MfJ/cUE0g9YBXy5XhSlPIpYIJ7HaY/3Q==", "dev": true }, - "flat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", - "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", - "dev": true, - "requires": { - "is-buffer": "~2.0.3" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", - "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==", - "dev": true - } - } - }, "flat-cache": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", @@ -6287,8 +6857,7 @@ "forwarded": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", - "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", - "dev": true + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" }, "fragment-cache": { "version": "0.2.1", @@ -6353,6 +6922,12 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", "dev": true + }, + "rimraf": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", + "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", + "dev": true } } }, @@ -6940,9 +7515,12 @@ } }, "fun-hooks": { - "version": "0.9.6", - "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.6.tgz", - "integrity": "sha512-0+CUJWTcx/vtm3qfvb9IfILItgDAq28lEsdEzu8622ttSVfFStDQTaSpU/sn2NyFXo5dN2qwwPLcqB/CvDkacg==" + "version": "0.9.7", + "resolved": "https://registry.npmjs.org/fun-hooks/-/fun-hooks-0.9.7.tgz", + "integrity": "sha512-2G0S9op/u+Ye/IJVPS++bs8oDuC/x7MpTKPkJ85U0PBfocJYd+f8WQRN1gqaCa2egKwjOOtXYVFcu0ksns+lWQ==", + "requires": { + "typescript-tuple": "^2.2.1" + } }, "function-bind": { "version": "1.1.1", @@ -7048,9 +7626,9 @@ } }, "glob": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", - "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -7240,9 +7818,9 @@ } }, "graceful-fs": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz", - "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", "dev": true }, "grapheme-splitter": { @@ -7257,6 +7835,12 @@ "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", "dev": true }, + "growly": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", + "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", + "dev": true + }, "gulp": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", @@ -7269,15 +7853,6 @@ "vinyl-fs": "^3.0.0" }, "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", @@ -7337,12 +7912,6 @@ "yargs": "^7.1.0" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -7352,15 +7921,6 @@ "number-is-nan": "^1.0.0" } }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", @@ -7471,6 +8031,16 @@ "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", "dev": true }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", @@ -8130,9 +8700,9 @@ } }, "handlebars": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.3.1.tgz", - "integrity": "sha512-c0HoNHzDiHpBt4Kqe99N8tdLPKAnGCQ73gYMPWtAYM4PwGnf7xl8PBUHJqh9ijlzt2uQKaSRxbXRt+rZ7M2/kA==", + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", + "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", "dev": true, "requires": { "neo-async": "^2.6.0", @@ -8254,9 +8824,9 @@ "dev": true }, "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", "dev": true }, "has-to-string-tag-x": { @@ -8369,15 +8939,15 @@ "dev": true }, "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, "highlight.js": { - "version": "9.15.10", - "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.15.10.tgz", - "integrity": "sha512-RoV7OkQm0T3os3Dd2VHLNMoaoDVx77Wygln3n9l5YV172XonWG6rgQD3XnF/BuFFZw9A0TJgmMSO8FEWQgvcXw==", + "version": "9.16.2", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-9.16.2.tgz", + "integrity": "sha512-feMUrVLZvjy0oC7FVJQcSQRqbBq9kwqnYE4+Kj9ZjbHh3g+BisiPgF49NyQbVLNdrL/qqZr3Ca9yOKwgn2i/tw==", "dev": true }, "hmac-drbg": { @@ -8417,11 +8987,20 @@ "dev": true }, "hosted-git-info": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.4.tgz", - "integrity": "sha512-pzXIvANXEFrc5oFFXRMkbLPQ2rXRoDERwDLyrcUxGhaZhgP54BBSl9Oheh7Vv0T090cszWBxPjkQQ5Sq1PbBRQ==", + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz", + "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==", "dev": true }, + "html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dev": true, + "requires": { + "whatwg-encoding": "^1.0.1" + } + }, "html-void-elements": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-1.0.4.tgz", @@ -8438,7 +9017,6 @@ "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", - "dev": true, "requires": { "depd": "~1.1.2", "inherits": "2.0.3", @@ -8449,8 +9027,7 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" } } }, @@ -8489,9 +9066,9 @@ "dev": true }, "https-proxy-agent": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.2.tgz", - "integrity": "sha512-c8Ndjc9Bkpfx/vCJueCPy0jlP4ccCCSNDp8xwCZzPjKJUm+B+u9WX2x98Qx4n1PiMNTWo3D7KK5ifNV/yJyRzg==", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "requires": { "agent-base": "^4.3.0", @@ -8536,6 +9113,16 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "import-local": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", + "integrity": "sha512-b6s04m3O+s3CGSbqDIyP4R6aAwAeYlVq9+WUWep6iHa8ETRf9yei1U48C5MmfJmV9AiLYYBKPMq/W+/WRpQmCQ==", + "dev": true, + "requires": { + "pkg-dir": "^3.0.0", + "resolve-cwd": "^2.0.0" + } + }, "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", @@ -8607,6 +9194,16 @@ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", "dev": true }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", @@ -8644,16 +9241,15 @@ } }, "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, "ipaddr.js": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", - "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", - "dev": true + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.4.0.tgz", + "integrity": "sha1-KWrKh4qCGBbluF0KKFqZvP9FgvA=" }, "is-absolute": { "version": "1.0.0", @@ -8734,6 +9330,15 @@ "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", "dev": true }, + "is-ci": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", + "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", + "dev": true, + "requires": { + "ci-info": "^2.0.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -8833,6 +9438,12 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "is-generator-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", + "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", + "dev": true + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -8965,12 +9576,12 @@ "dev": true }, "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", "dev": true, "requires": { - "has-symbols": "^1.0.0" + "has-symbols": "^1.0.1" } }, "is-typedarray": { @@ -9019,9 +9630,9 @@ "dev": true }, "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { @@ -9079,12 +9690,31 @@ "wordwrap": "^1.0.0" }, "dependencies": { + "escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha1-WltTr0aTEQvrsIZ6o0MN07cKEBg=", + "dev": true, + "requires": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.2.0" + } + }, "esprima": { "version": "2.7.3", "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, + "estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", + "dev": true + }, "glob": { "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", @@ -9110,6 +9740,16 @@ "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", "dev": true }, + "source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha1-2rc/vPwrqBm03gO9b26qSBZLP50=", + "dev": true, + "optional": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, "supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", @@ -9118,6 +9758,12 @@ "requires": { "has-flag": "^1.0.0" } + }, + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true } } }, @@ -9148,6 +9794,85 @@ "requires": { "lodash": "^4.17.14" } + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + }, + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", + "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^1.2.1", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", + "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.2.1", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + } + }, + "istanbul-reports": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", + "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "supports-color": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", + "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } } } }, @@ -9161,12 +9886,35 @@ "istanbul-lib-instrument": "^1.7.3", "loader-utils": "^1.1.0", "schema-utils": "^0.3.0" + }, + "dependencies": { + "istanbul-lib-coverage": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", + "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "dev": true + }, + "istanbul-lib-instrument": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", + "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.1", + "semver": "^5.3.0" + } + } } }, "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", "dev": true }, "istanbul-lib-hook": { @@ -9179,89 +9927,78 @@ } }, "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", "dev": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", "dev": true, "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" }, "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", "dev": true, "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" }, "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true } } }, "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz", + "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==", "dev": true, "requires": { - "handlebars": "^4.0.3" + "handlebars": "^4.1.2" } }, "istextorbinary": { @@ -9285,6 +10022,516 @@ "is-object": "^1.0.1" } }, + "jest": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-24.9.0.tgz", + "integrity": "sha512-YvkBL1Zm7d2B1+h5fHEOdyjCG+sGMz4f8D86/0HiqJ6MB4MnDc8FgP5vdWsGnemOQro7lnYo8UakZ3+5A0jxGw==", + "dev": true, + "requires": { + "import-local": "^2.0.0", + "jest-cli": "^24.9.0" + }, + "dependencies": { + "jest-cli": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-24.9.0.tgz", + "integrity": "sha512-+VLRKyitT3BWoMeSUIHRxV/2g8y9gw91Jh5z2UmXZzkZKpbC08CSehVxgHUwTpy+HwGcns/tqafQDJW7imYvGg==", + "dev": true, + "requires": { + "@jest/core": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "import-local": "^2.0.0", + "is-ci": "^2.0.0", + "jest-config": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "prompts": "^2.0.1", + "realpath-native": "^1.1.0", + "yargs": "^13.3.0" + } + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + } + } + }, + "jest-changed-files": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-24.9.0.tgz", + "integrity": "sha512-6aTWpe2mHF0DhL28WjdkO8LyGjs3zItPET4bMSeXU6T3ub4FPMw+mcOcbdGXQOAfmLcxofD23/5Bl9Z4AkFwqg==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "execa": "^1.0.0", + "throat": "^4.0.0" + } + }, + "jest-config": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-24.9.0.tgz", + "integrity": "sha512-RATtQJtVYQrp7fvWg6f5y3pEFj9I+H8sWw4aKxnDZ96mob5i5SD6ZEGWgMLXQ4LE8UurrjbdlLWdUeo+28QpfQ==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^24.9.0", + "@jest/types": "^24.9.0", + "babel-jest": "^24.9.0", + "chalk": "^2.0.1", + "glob": "^7.1.1", + "jest-environment-jsdom": "^24.9.0", + "jest-environment-node": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "micromatch": "^3.1.10", + "pretty-format": "^24.9.0", + "realpath-native": "^1.1.0" + } + }, + "jest-diff": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", + "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "diff-sequences": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-docblock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-24.9.0.tgz", + "integrity": "sha512-F1DjdpDMJMA1cN6He0FNYNZlo3yYmOtRUnktrT9Q37njYzC5WEaDdmbynIgy0L/IvXvvgsG8OsqhLPXTpfmZAA==", + "dev": true, + "requires": { + "detect-newline": "^2.1.0" + } + }, + "jest-each": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-24.9.0.tgz", + "integrity": "sha512-ONi0R4BvW45cw8s2Lrx8YgbeXL1oCQ/wIDwmsM3CqM/nlblNCPmnC3IPQlMbRFZu3wKdQ2U8BqM6lh3LJ5Bsog==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-environment-jsdom": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-24.9.0.tgz", + "integrity": "sha512-Zv9FV9NBRzLuALXjvRijO2351DRQeLYXtpD4xNvfoVFw21IOKNhZAEUKcbiEtjTkm2GsJ3boMVgkaR7rN8qetA==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0", + "jsdom": "^11.5.1" + } + }, + "jest-environment-node": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-24.9.0.tgz", + "integrity": "sha512-6d4V2f4nxzIzwendo27Tr0aFm+IXWa0XEUnaH6nU0FMaozxovt+sfRvh4J47wL1OvF83I3SSTu0XK+i4Bqe7uA==", + "dev": true, + "requires": { + "@jest/environment": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/types": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-util": "^24.9.0" + } + }, + "jest-get-type": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", + "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", + "dev": true + }, + "jest-haste-map": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-24.9.0.tgz", + "integrity": "sha512-kfVFmsuWui2Sj1Rp1AJ4D9HqJwE4uwTlS/vO+eRUaMmd54BFpli2XhMQnPC2k4cHFVbB2Q2C+jtI1AGLgEnCjQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "anymatch": "^2.0.0", + "fb-watchman": "^2.0.0", + "fsevents": "^1.2.7", + "graceful-fs": "^4.1.15", + "invariant": "^2.2.4", + "jest-serializer": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.9.0", + "micromatch": "^3.1.10", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-24.9.0.tgz", + "integrity": "sha512-Cq7vkAgaYKp+PsX+2/JbTarrk0DmNhsEtqBXNwUHkdlbrTBLtMJINADf2mf5FkowNsq8evbPc07/qFO0AdKTzw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "co": "^4.6.0", + "expect": "^24.9.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "pretty-format": "^24.9.0", + "throat": "^4.0.0" + } + }, + "jest-leak-detector": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-24.9.0.tgz", + "integrity": "sha512-tYkFIDsiKTGwb2FG1w8hX9V0aUb2ot8zY/2nFg087dUageonw1zrLMP4W6zsRO59dPkTSKie+D4rhMuP9nRmrA==", + "dev": true, + "requires": { + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-matcher-utils": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-24.9.0.tgz", + "integrity": "sha512-OZz2IXsu6eaiMAwe67c1T+5tUAtQyQx27/EMEkbFAGiw52tB9em+uGbzpcgYVpA8wl0hlxKPZxrly4CXU/GjHA==", + "dev": true, + "requires": { + "chalk": "^2.0.1", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "pretty-format": "^24.9.0" + } + }, + "jest-message-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-24.9.0.tgz", + "integrity": "sha512-oCj8FiZ3U0hTP4aSui87P4L4jC37BtQwUMqk+zk/b11FR19BJDeZsZAvIHutWnmtw7r85UmR3CEWZ0HWU2mAlw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/stack-utils": "^1.0.1", + "chalk": "^2.0.1", + "micromatch": "^3.1.10", + "slash": "^2.0.0", + "stack-utils": "^1.0.1" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, + "jest-mock": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-24.9.0.tgz", + "integrity": "sha512-3BEYN5WbSq9wd+SyLDES7AHnjH9A/ROBwmz7l2y+ol+NtSFO8DYiEBzoO1CeFc9a8DYy10EO4dDFVv/wN3zl1w==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0" + } + }, + "jest-pnp-resolver": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz", + "integrity": "sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==", + "dev": true + }, + "jest-regex-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-24.9.0.tgz", + "integrity": "sha512-05Cmb6CuxaA+Ys6fjr3PhvV3bGQmO+2p2La4hFbU+W5uOc479f7FdLXUWXw4pYMAhhSZIuKHwSXSu6CsSBAXQA==", + "dev": true + }, + "jest-resolve": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-24.9.0.tgz", + "integrity": "sha512-TaLeLVL1l08YFZAt3zaPtjiVvyy4oSA6CRe+0AFPPVX3Q/VI0giIWWoAvoS5L96vj9Dqxj4fB5p2qrHCmTU/MQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "browser-resolve": "^1.11.3", + "chalk": "^2.0.1", + "jest-pnp-resolver": "^1.2.1", + "realpath-native": "^1.1.0" + } + }, + "jest-resolve-dependencies": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-24.9.0.tgz", + "integrity": "sha512-Fm7b6AlWnYhT0BXy4hXpactHIqER7erNgIsIozDXWl5dVm+k8XdGVe1oTg1JyaFnOxarMEbax3wyRJqGP2Pq+g==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-snapshot": "^24.9.0" + } + }, + "jest-runner": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-24.9.0.tgz", + "integrity": "sha512-KksJQyI3/0mhcfspnxxEOBueGrd5E4vV7ADQLT9ESaCzz02WnbdbKWIf5Mkaucoaj7obQckYPVX6JJhgUcoWWg==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "chalk": "^2.4.2", + "exit": "^0.1.2", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-docblock": "^24.3.0", + "jest-haste-map": "^24.9.0", + "jest-jasmine2": "^24.9.0", + "jest-leak-detector": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "jest-runtime": "^24.9.0", + "jest-util": "^24.9.0", + "jest-worker": "^24.6.0", + "source-map-support": "^0.5.6", + "throat": "^4.0.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "jest-runtime": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-24.9.0.tgz", + "integrity": "sha512-8oNqgnmF3v2J6PVRM2Jfuj8oX3syKmaynlDMMKQ4iyzbQzIG6th5ub/lM2bCMTmoTKM3ykcUYI2Pw9xwNtjMnw==", + "dev": true, + "requires": { + "@jest/console": "^24.7.1", + "@jest/environment": "^24.9.0", + "@jest/source-map": "^24.3.0", + "@jest/transform": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "chalk": "^2.0.1", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "jest-config": "^24.9.0", + "jest-haste-map": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-mock": "^24.9.0", + "jest-regex-util": "^24.3.0", + "jest-resolve": "^24.9.0", + "jest-snapshot": "^24.9.0", + "jest-util": "^24.9.0", + "jest-validate": "^24.9.0", + "realpath-native": "^1.1.0", + "slash": "^2.0.0", + "strip-bom": "^3.0.0", + "yargs": "^13.3.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "yargs": { + "version": "13.3.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz", + "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.1" + } + } + } + }, + "jest-serializer": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-24.9.0.tgz", + "integrity": "sha512-DxYipDr8OvfrKH3Kel6NdED3OXxjvxXZ1uIY2I9OFbGg+vUkkg7AGvi65qbhbWNPvDckXmzMPbK3u3HaDO49bQ==", + "dev": true + }, + "jest-snapshot": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-24.9.0.tgz", + "integrity": "sha512-uI/rszGSs73xCM0l+up7O7a40o90cnrk429LOiK3aeTvfC0HHmldbd81/B7Ix81KSFe1lwkbl7GnBGG4UfuDew==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^24.9.0", + "chalk": "^2.0.1", + "expect": "^24.9.0", + "jest-diff": "^24.9.0", + "jest-get-type": "^24.9.0", + "jest-matcher-utils": "^24.9.0", + "jest-message-util": "^24.9.0", + "jest-resolve": "^24.9.0", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "pretty-format": "^24.9.0", + "semver": "^6.2.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "jest-util": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-24.9.0.tgz", + "integrity": "sha512-x+cZU8VRmOJxbA1K5oDBdxQmdq0OIdADarLxk0Mq+3XS4jgvhG/oKGWcIDCtPG0HgjxOYvF+ilPJQsAyXfbNOg==", + "dev": true, + "requires": { + "@jest/console": "^24.9.0", + "@jest/fake-timers": "^24.9.0", + "@jest/source-map": "^24.9.0", + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "callsites": "^3.0.0", + "chalk": "^2.0.1", + "graceful-fs": "^4.1.15", + "is-ci": "^2.0.0", + "mkdirp": "^0.5.1", + "slash": "^2.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "jest-validate": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-24.9.0.tgz", + "integrity": "sha512-HPIt6C5ACwiqSiwi+OfSSHbK8sG7akG8eATl+IPKaeIjtPOeBUd/g3J7DghugzxrGjI93qS/+RPKe1H6PqvhRQ==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "camelcase": "^5.3.1", + "chalk": "^2.0.1", + "jest-get-type": "^24.9.0", + "leven": "^3.1.0", + "pretty-format": "^24.9.0" + } + }, + "jest-watcher": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-24.9.0.tgz", + "integrity": "sha512-+/fLOfKPXXYJDYlks62/4R4GoT+GU1tYZed99JSCOsmzkkF7727RqKrjNAxtfO4YpGv11wybgRvCjR73lK2GZw==", + "dev": true, + "requires": { + "@jest/test-result": "^24.9.0", + "@jest/types": "^24.9.0", + "@types/yargs": "^13.0.0", + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.1", + "jest-util": "^24.9.0", + "string-length": "^2.0.0" + } + }, + "jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "requires": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "js-levenshtein": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", @@ -9313,6 +10560,40 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, + "jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dev": true, + "requires": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, "jsencrypt": { "version": "3.0.0-rc.1", "resolved": "https://registry.npmjs.org/jsencrypt/-/jsencrypt-3.0.0-rc.1.tgz", @@ -9367,9 +10648,9 @@ "dev": true }, "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.1.tgz", + "integrity": "sha512-l+3HXD0GEI3huGq1njuqtzYK8OYJyXMkOLtQ53pjWh89tvWS2h6l+1zMkYWqlb57+SiQodKZyvMEFb2X+KrFhQ==", "dev": true, "requires": { "minimist": "^1.2.0" @@ -9458,15 +10739,6 @@ "integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", "dev": true }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -9537,6 +10809,14 @@ "dev": true, "requires": { "is-wsl": "^2.1.0" + }, + "dependencies": { + "is-wsl": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", + "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", + "dev": true + } } }, "karma-ie-launcher": { @@ -9673,6 +10953,12 @@ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, "last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -9699,18 +10985,18 @@ } }, "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "^2.0.0" + "invert-kv": "^1.0.0" } }, "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha1-6w1GtUER68VhrLTECO+TY73I9+A=", "dev": true }, "lead": { @@ -9722,6 +11008,18 @@ "flush-write-stream": "^1.0.2" } }, + "left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "dev": true + }, + "leven": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -10001,6 +11299,12 @@ "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", "dev": true }, + "lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", + "dev": true + }, "lodash.template": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-2.4.1.tgz", @@ -10186,13 +11490,13 @@ "kind-of": "^6.0.2" } }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "makeerror": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", + "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", "dev": true, "requires": { - "p-defer": "^1.0.0" + "tmpl": "1.0.x" } }, "map-cache": { @@ -10287,18 +11591,18 @@ } }, "mdast-util-compact": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.3.tgz", - "integrity": "sha512-nRiU5GpNy62rZppDKbLwhhtw5DXoFMqw9UNZFmlPsNaQCZ//WLjGKUwWMdJrUH+Se7UvtO2gXtAMe0g/N+eI5w==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mdast-util-compact/-/mdast-util-compact-1.0.4.tgz", + "integrity": "sha512-3YDMQHI5vRiS2uygEFYaqckibpJtKq5Sj2c8JioeOQBU6INpKbdWzfyLqFFnDwEcEnRFIdMsguzs5pC1Jp4Isg==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" } }, "mdast-util-definitions": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-1.2.4.tgz", - "integrity": "sha512-HfUArPog1j4Z78Xlzy9Q4aHLnrF/7fb57cooTHypyGoe2XFNbcx/kWZDoOz+ra8CkUzvg3+VHV434yqEd1DRmA==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-1.2.5.tgz", + "integrity": "sha512-CJXEdoLfiISCDc2JB6QLb79pYfI6+GcIH+W2ox9nMc7od0Pz+bovcHsiq29xAQY6ayqe/9CsK2VzkSJdg1pFYA==", "dev": true, "requires": { "unist-util-visit": "^1.0.0" @@ -10333,9 +11637,9 @@ } }, "mdast-util-to-string": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.6.tgz", - "integrity": "sha512-868pp48gUPmZIhfKrLbaDneuzGiw3OTDjHc5M1kAepR2CWBJ+HpEsm252K4aXdiP5coVZaJPOqGtVU6Po8xnXg==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/mdast-util-to-string/-/mdast-util-to-string-1.0.7.tgz", + "integrity": "sha512-P+gdtssCoHOX+eJUrrC30Sixqao86ZPlVjR5NEAoy0U79Pfxb1Y0Gntei0+GrnQD4T04X9xA8tcugp90cSmNow==", "dev": true }, "mdast-util-toc": { @@ -10382,26 +11686,15 @@ "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", - "dev": true + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - }, - "dependencies": { - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - } + "mimic-fn": "^1.0.0" } }, "memoizee": { @@ -10547,23 +11840,18 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=", - "dev": true + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", - "dev": true + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "micromatch": { "version": "3.1.10", @@ -10603,24 +11891,24 @@ "dev": true }, "mime-db": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", - "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==", "dev": true }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", "dev": true, "requires": { - "mime-db": "1.40.0" + "mime-db": "1.42.0" } }, "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, "mimic-response": { @@ -10748,12 +12036,6 @@ "path-is-absolute": "^1.0.0" } }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", - "dev": true - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -10937,8 +12219,7 @@ "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", - "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, "neo-async": { "version": "2.6.1", @@ -10972,9 +12253,9 @@ }, "dependencies": { "@sinonjs/formatio": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.1.tgz", - "integrity": "sha512-tsHvOB24rvyvV2+zKMmPkZ7dXX6LSLKZ7aOtXY6Edklp0uRcgGpOsQTTGTcWViFyx4uhWc6GV8QdnALbIbIdeQ==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", "dev": true, "requires": { "@sinonjs/commons": "^1", @@ -10989,15 +12270,11 @@ } } }, - "node-environment-flags": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", - "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", - "dev": true, - "requires": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", + "dev": true }, "node-libs-browser": { "version": "2.2.1", @@ -11031,9 +12308,9 @@ }, "dependencies": { "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -11049,13 +12326,40 @@ } } }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, + "node-notifier": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", + "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", + "dev": true, + "requires": { + "growly": "^1.3.0", + "is-wsl": "^1.1.0", + "semver": "^5.5.0", + "shellwords": "^0.1.1", + "which": "^1.3.0" + } + }, "node-releases": { - "version": "1.1.32", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.32.tgz", - "integrity": "sha512-VhVknkitq8dqtWoluagsGPn3dxTvN9fwgR59fV3D7sLBHe0JfDramsMI8n8mY//ccq/Kkrf8ZRHRpsyVZ3qw1A==", + "version": "1.1.42", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.42.tgz", + "integrity": "sha512-OQ/ESmUqGawI2PRX+XIRao44qWYBBfN54ImQYdWVTQqUckuejOg76ysSqDBK8NG3zwySRVnX36JwDQ6x+9GxzA==", "dev": true, "requires": { - "semver": "^5.3.0" + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "nopt": { @@ -11150,6 +12454,12 @@ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, + "nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", + "dev": true + }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -11200,9 +12510,9 @@ } }, "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", "dev": true }, "object-keys": { @@ -11320,7 +12630,6 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "dev": true, "requires": { "ee-first": "1.1.1" } @@ -11341,14 +12650,6 @@ "dev": true, "requires": { "mimic-fn": "^1.0.0" - }, - "dependencies": { - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - } } }, "opener": { @@ -11364,14 +12665,6 @@ "dev": true, "requires": { "is-wsl": "^1.1.0" - }, - "dependencies": { - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - } } }, "optimist": { @@ -11389,27 +12682,21 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true } } }, "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "requires": { "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", + "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", - "wordwrap": "~1.0.0" + "word-wrap": "~1.2.3" } }, "ordered-read-streams": { @@ -11434,14 +12721,42 @@ "dev": true }, "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + } } }, "os-tmpdir": { @@ -11456,11 +12771,14 @@ "integrity": "sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==", "dev": true }, - "p-defer": { + "p-each-series": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true + "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-1.0.0.tgz", + "integrity": "sha1-kw89Et0fUOdDRFeiLNbwSsatf3E=", + "dev": true, + "requires": { + "p-reduce": "^1.0.0" + } }, "p-finally": { "version": "1.0.0", @@ -11492,6 +12810,12 @@ "p-limit": "^2.0.0" } }, + "p-reduce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", + "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=", + "dev": true + }, "p-timeout": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", @@ -11537,138 +12861,15 @@ } }, "parse-domain": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-2.3.2.tgz", - "integrity": "sha512-ywE9/YZwJZ8b2viJybvAyu9xO+4qbuLr0Jx73zoL8bHsDdDrFKWKGJKKEuvyNT0ZN1whQGnUAwdQMnsU0dqlPA==", + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/parse-domain/-/parse-domain-2.3.4.tgz", + "integrity": "sha512-LlFJJVTry4DD3Xa76CsVNP6MIu3JZ8GXd5HEEp38KSDGBCVsnccagAJ5YLy7uEEabvwtauQEQPcvXWgUGkJbMA==", "dev": true, "requires": { - "chai": "^4.2.0", "got": "^8.3.2", + "jest": "^24.9.0", "mkdirp": "^0.5.1", - "mocha": "^6.1.4", "npm-run-all": "^4.1.5" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "mocha": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.0.tgz", - "integrity": "sha512-qwfFgY+7EKAAUAdv7VYMZQknI7YJSGesxHyhn6qD52DV8UcSZs5XwCifcZGMVIE4a5fbmhvbotxC0DLQ0oKohQ==", - "dev": true, - "requires": { - "ansi-colors": "3.2.3", - "browser-stdout": "1.3.1", - "debug": "3.2.6", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "find-up": "3.0.0", - "glob": "7.1.3", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "3.13.1", - "log-symbols": "2.2.0", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "ms": "2.1.1", - "node-environment-flags": "1.0.5", - "object.assign": "4.1.0", - "strip-json-comments": "2.0.1", - "supports-color": "6.0.0", - "which": "1.3.1", - "wide-align": "1.1.3", - "yargs": "13.2.2", - "yargs-parser": "13.0.0", - "yargs-unparser": "1.5.0" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", - "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "yargs": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz", - "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^2.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^3.0.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^13.0.0" - } - } } }, "parse-entities": { @@ -11778,6 +12979,12 @@ "protocols": "^1.4.0" } }, + "parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", + "dev": true + }, "parseqs": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.5.tgz", @@ -11799,8 +13006,7 @@ "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "dev": true + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "pascalcase": { "version": "0.1.1", @@ -11872,9 +13078,9 @@ "dev": true }, "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", "dev": true, "requires": { "isarray": "0.0.1" @@ -11972,6 +13178,15 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -11991,17 +13206,6 @@ "arr-diff": "^4.0.0", "arr-union": "^3.1.0", "extend-shallow": "^3.0.2" - }, - "dependencies": { - "ansi-colors": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", - "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", - "dev": true, - "requires": { - "ansi-wrap": "^0.1.0" - } - } } }, "pluralize": { @@ -12010,6 +13214,12 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, + "pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", + "dev": true + }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -12034,6 +13244,26 @@ "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", "dev": true }, + "pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "requires": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + } + } + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -12064,6 +13294,16 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, + "prompts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.3.0.tgz", + "integrity": "sha512-NfbbPPg/74fT7wk2XYQ7hAIp9zJyZp5Fu19iRbORqqy1BhtrkZ0fPafBU+7bmn8ie69DpT0R6QpJIN2oisYjJg==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.3" + } + }, "property-information": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/property-information/-/property-information-3.2.0.tgz", @@ -12077,13 +13317,12 @@ "dev": true }, "proxy-addr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", - "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", - "dev": true, + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.1.5.tgz", + "integrity": "sha1-ccDuOxAt4/IC87ZPYI0XP8uhqRg=", "requires": { - "forwarded": "~0.1.2", - "ipaddr.js": "1.9.0" + "forwarded": "~0.1.0", + "ipaddr.js": "1.4.0" } }, "prr": { @@ -12108,9 +13347,9 @@ "dev": true }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.5.0.tgz", + "integrity": "sha512-4vqUjKi2huMu1OJiLhi3jN6jeeKvMZdI1tYgi/njW5zV52jNLgSAZSdN16m9bJFe61/cT8ulmw4qFitV9QRsEA==", "dev": true }, "public-encrypt": { @@ -12207,9 +13446,9 @@ "dev": true }, "querystringify": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.3.tgz", - "integrity": "sha1-DJ02+/jHpPces3CFd2NXemMzW+c=", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", + "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", "dev": true }, "randomatic": { @@ -12253,8 +13492,7 @@ "range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "dev": true + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" }, "raw-body": { "version": "1.1.7", @@ -12274,6 +13512,12 @@ } } }, + "react-is": { + "version": "16.12.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.12.0.tgz", + "integrity": "sha512-rPCkf/mWBtKc97aLL9/txD8DZdemK0vkA3JMLShjlJB3Pj3s+lpf1KaBzMfQrAmhMQB0n1cU/SUGgKKBCe837Q==", + "dev": true + }, "read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -12366,6 +13610,15 @@ "readable-stream": "^2.0.2" } }, + "realpath-native": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", + "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", + "dev": true, + "requires": { + "util.promisify": "^1.0.0" + } + }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", @@ -12454,9 +13707,9 @@ } }, "regjsgen": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.0.tgz", - "integrity": "sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA==", + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.1.tgz", + "integrity": "sha512-5qxzGZjDs9w4tzT3TPhCJqWdCc3RLYwy9J2NB0nm5Lz+S273lvWcpjaTGHsT1dc6Hhfq41uSEOw8wBmxrKOuyg==", "dev": true }, "regjsparser": { @@ -12688,24 +13941,35 @@ } }, "request-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.4.tgz", - "integrity": "sha512-8wgMrvE546PzbR5WbYxUQogUnUDfM0S7QIFZMID+J73vdFARkFy+HElj4T+MWYhpXwlLp0EQ8Zoj8xUA0he4Vg==", + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", + "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", "dev": true, "requires": { "bluebird": "^3.5.0", - "request-promise-core": "1.1.2", + "request-promise-core": "1.1.3", "stealthy-require": "^1.1.1", "tough-cookie": "^2.3.3" } }, "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", "dev": true, "requires": { - "lodash": "^4.17.11" + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, "require-directory": { @@ -12745,14 +14009,31 @@ "dev": true }, "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.13.1.tgz", + "integrity": "sha512-CxqObCX8K8YtAhOBRg+lrcdn+LK+WYOS8tSjqSFbjtrI5PnS63QPhZl4+yKfrU9tdsbMu9Anr/amegT87M9Z6w==", "dev": true, "requires": { "path-parse": "^1.0.6" } }, + "resolve-cwd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", + "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", + "dev": true, + "requires": { + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -12816,9 +14097,9 @@ "dev": true }, "rgb2hex": { - "version": "0.1.9", - "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.9.tgz", - "integrity": "sha512-32iuQzhOjyT+cv9aAFRBJ19JgHwzQwbjUhH3Fj2sWW2EEGAW8fpFrDFP5ndoKDxJaLO06x1hE3kyuIFrUQtybQ==", + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/rgb2hex/-/rgb2hex-0.1.10.tgz", + "integrity": "sha512-vKz+kzolWbL3rke/xeTE2+6vHmZnNxGyDnaVW4OckntAIcc7DcZzWkQSfxMDwqHS8vhgySnIFyBUH7lIk6PxvQ==", "dev": true }, "right-align": { @@ -12831,10 +14112,13 @@ } }, "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha1-5Dm+Kq7jJzIZUnMPmaiSnk/FBYI=", - "dev": true + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } }, "ripemd160": { "version": "2.0.2", @@ -12846,6 +14130,12 @@ "inherits": "^2.0.1" } }, + "rsvp": { + "version": "4.8.5", + "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", + "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", + "dev": true + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -12903,6 +14193,29 @@ "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", "dev": true }, + "sane": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", + "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", + "dev": true, + "requires": { + "@cnakazawa/watch": "^1.0.3", + "anymatch": "^2.0.0", + "capture-exit": "^2.0.0", + "exec-sh": "^0.3.2", + "execa": "^1.0.0", + "fb-watchman": "^2.0.0", + "micromatch": "^3.1.4", + "minimist": "^1.1.1", + "walker": "~1.0.5" + } + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "schema-utils": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-0.3.0.tgz", @@ -13124,8 +14437,7 @@ "setprototypeof": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" }, "sha.js": { "version": "2.4.11", @@ -13169,6 +14481,12 @@ "rechoir": "^0.6.2" } }, + "shellwords": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", + "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", + "dev": true + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", @@ -13198,6 +14516,12 @@ } } }, + "sisteransi": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.4.tgz", + "integrity": "sha512-/ekMoM4NJ59ivGSfKapeG+FWtrmWvA1p6FBZwXrqojw90vJu8lBmrTxCMuBCydKtkaUe2zt4PlxeTKpjwMbyig==", + "dev": true + }, "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", @@ -13367,9 +14691,9 @@ } }, "socket.io-adapter": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.1.tgz", - "integrity": "sha1-KoBeihTWNyEk3ZFZrUUC+MsH8Gs=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-1.1.2.tgz", + "integrity": "sha512-WzZRUj1kUjrTIrUKpZLEzFZ1OLj5FwLlAFQs9kuZJzJi5DKdU7FsWc36SNmA8iDOtwBQyT8FkrriRM8vXLYz8g==", "dev": true }, "socket.io-client": { @@ -13597,6 +14921,12 @@ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", "dev": true }, + "stack-utils": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", + "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", + "dev": true + }, "state-toggle": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.2.tgz", @@ -13627,8 +14957,7 @@ "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "dev": true + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" }, "stealthy-require": { "version": "1.1.1", @@ -13757,19 +15086,13 @@ "integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=", "dev": true }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "string-length": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", + "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", + "astral-regex": "^1.0.0", "strip-ansi": "^4.0.0" }, "dependencies": { @@ -13790,6 +15113,40 @@ } } }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, "string.prototype.padend": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz", @@ -13912,6 +15269,12 @@ "es6-symbol": "^3.1.1" } }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "table": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", @@ -13924,6 +15287,33 @@ "lodash": "^4.17.4", "slice-ansi": "1.0.0", "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } } }, "tapable": { @@ -13977,6 +15367,41 @@ "fork-stream": "^0.0.4", "merge-stream": "^1.0.0", "through2": "^2.0.1" + }, + "dependencies": { + "merge-stream": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", + "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", + "dev": true, + "requires": { + "readable-stream": "^2.0.1" + } + } + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, + "dependencies": { + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + } + } } }, "text-table": { @@ -13986,9 +15411,15 @@ "dev": true }, "textextensions": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.5.0.tgz", - "integrity": "sha512-1IkVr355eHcomgK7fgj1Xsokturx6L5S2JRT5WcRdA6v5shk9sxWuO/w/VbpQexwkXJMQIa/j1dBi3oo7+HhcA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", + "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==", + "dev": true + }, + "throat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", + "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", "dev": true }, "through": { @@ -14082,6 +15513,12 @@ "os-tmpdir": "~1.0.2" } }, + "tmpl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", + "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", + "dev": true + }, "to-absolute-glob": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/to-absolute-glob/-/to-absolute-glob-2.0.2.tgz", @@ -14191,6 +15628,15 @@ } } }, + "tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "trim": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/trim/-/trim-0.0.1.tgz", @@ -14279,7 +15725,6 @@ "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "dev": true, "requires": { "media-typer": "0.3.0", "mime-types": "~2.1.24" @@ -14291,13 +15736,34 @@ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", "dev": true }, + "typescript-compare": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/typescript-compare/-/typescript-compare-0.0.2.tgz", + "integrity": "sha512-8ja4j7pMHkfLJQO2/8tut7ub+J3Lw2S3061eJLFQcvs3tsmJKp8KG5NtpLn7KcY2w08edF74BSVN7qJS0U6oHA==", + "requires": { + "typescript-logic": "^0.0.0" + } + }, + "typescript-logic": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/typescript-logic/-/typescript-logic-0.0.0.tgz", + "integrity": "sha512-zXFars5LUkI3zP492ls0VskH3TtdeHCqu0i7/duGt60i5IGPIpAHE/DWo5FqJ6EjQ15YKXrt+AETjv60Dat34Q==" + }, + "typescript-tuple": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/typescript-tuple/-/typescript-tuple-2.2.1.tgz", + "integrity": "sha512-Zcr0lbt8z5ZdEzERHAMAniTiIKerFCMgd7yjq1fPnDJ43et/k9twIFQMUYff9k5oXcsQ0WpvFcgzK2ZKASoW6Q==", + "requires": { + "typescript-compare": "^0.0.2" + } + }, "uglify-js": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz", - "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==", + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.7.1.tgz", + "integrity": "sha512-pnOF7jY82wdIhATVn87uUY/FHU+MDUdPLkmGFvGoclQmeu229eTkbG5gjGGBi3R7UuYYSEeYXY/TTY5j2aym2g==", "dev": true, "requires": { - "commander": "~2.20.0", + "commander": "~2.20.3", "source-map": "~0.6.1" }, "dependencies": { @@ -14493,9 +15959,9 @@ } }, "unist-util-generated": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.4.tgz", - "integrity": "sha512-SA7Sys3h3X4AlVnxHdvN/qYdr4R38HzihoEVY2Q2BZu8NHWDnw5OGcC/tXWjQfd4iG+M6qRFNIRGqJmp2ez4Ww==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/unist-util-generated/-/unist-util-generated-1.1.5.tgz", + "integrity": "sha512-1TC+NxQa4N9pNdayCYA1EGUOCAO0Le3fVp7Jzns6lnua/mYgwHo0tz5WUAfrdpNch1RZLHc61VZ1SDgrtNXLSw==", "dev": true }, "unist-util-is": { @@ -14505,15 +15971,15 @@ "dev": true }, "unist-util-position": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.0.3.tgz", - "integrity": "sha512-28EpCBYFvnMeq9y/4w6pbnFmCUfzlsc41NJui5c51hOFjBA1fejcwc+5W4z2+0ECVbScG3dURS3JTVqwenzqZw==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-3.0.4.tgz", + "integrity": "sha512-tWvIbV8goayTjobxDIr4zVTyG+Q7ragMSMeKC3xnPl9xzIc0+she8mxXLM3JVNDDsfARPbCd3XdzkyLdo7fF3g==", "dev": true }, "unist-util-remove-position": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.3.tgz", - "integrity": "sha512-CtszTlOjP2sBGYc2zcKA/CvNdTdEs3ozbiJ63IPBxh8iZg42SCCb8m04f8z2+V1aSk5a7BxbZKEdoDjadmBkWA==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/unist-util-remove-position/-/unist-util-remove-position-1.1.4.tgz", + "integrity": "sha512-tLqd653ArxJIPnKII6LMZwH+mb5q+n/GtXQZo6S6csPRs5zB0u79Yw8ouR3wTw8wxvdJFhpP6Y7jorWdCgLO0A==", "dev": true, "requires": { "unist-util-visit": "^1.1.0" @@ -14546,8 +16012,7 @@ "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "dev": true + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, "unset-value": { "version": "1.0.0", @@ -14642,14 +16107,6 @@ "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" - }, - "dependencies": { - "querystringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.1.1.tgz", - "integrity": "sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA==", - "dev": true - } } }, "url-parse-lax": { @@ -14714,6 +16171,16 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "util.promisify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", + "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "object.getownpropertydescriptors": "^2.0.3" + } + }, "utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -14754,8 +16221,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "dev": true + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" }, "verror": { "version": "1.10.0", @@ -14781,9 +16247,9 @@ } }, "vfile-location": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.5.tgz", - "integrity": "sha512-Pa1ey0OzYBkLPxPZI3d9E+S4BmvfVwNAAXrrqGbwTVXWaX2p9kM1zZ+n35UtVM06shmWKH4RPRN8KI80qE3wNQ==", + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/vfile-location/-/vfile-location-2.0.6.tgz", + "integrity": "sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA==", "dev": true }, "vfile-message": { @@ -14932,9 +16398,9 @@ } }, "vm-browserify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.0.tgz", - "integrity": "sha512-iq+S7vZJE60yejDYM0ek6zg308+UZsdtPExWP9VZoCFCz1zkJoXFnAX7aZfd/ZwrkidzdUZL0C/ryW+JwAiIGw==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", "dev": true }, "void-elements": { @@ -14943,6 +16409,15 @@ "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", "dev": true }, + "w3c-hr-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", + "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", + "dev": true, + "requires": { + "browser-process-hrtime": "^0.1.2" + } + }, "walk": { "version": "2.3.14", "resolved": "https://registry.npmjs.org/walk/-/walk-2.3.14.tgz", @@ -14952,6 +16427,15 @@ "foreachasync": "^3.0.0" } }, + "walker": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", + "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", + "dev": true, + "requires": { + "makeerror": "1.0.x" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -15066,6 +16550,12 @@ } } }, + "webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true + }, "webpack": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", @@ -15114,6 +16604,12 @@ "integrity": "sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ==", "dev": true }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, "async": { "version": "2.6.3", "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", @@ -15153,32 +16649,6 @@ } } }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", @@ -15206,12 +16676,6 @@ "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", "dev": true }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -15231,16 +16695,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } + "dev": true }, "load-json-file": { "version": "2.0.0", @@ -15264,32 +16719,6 @@ "path-exists": "^3.0.0" } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", @@ -15365,6 +16794,33 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", "dev": true }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "supports-color": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", @@ -15374,6 +16830,29 @@ "has-flag": "^2.0.0" } }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", @@ -15413,9 +16892,9 @@ } }, "webpack-bundle-analyzer": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.5.1.tgz", - "integrity": "sha512-CDdaT3TTu4F9X3tcDq6PNJOiNGgREOM0WdN2vVAoUUn+M6NLB5kJ543HImCWbrDwOpbpGARSwU8r+u0Pl367kA==", + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-3.6.0.tgz", + "integrity": "sha512-orUfvVYEfBMDXgEKAKVvab5iQ2wXneIEorGNsyuOyVYpjYrI7CUOhhXNDd3huMwQ3vNNWWlGP+hzflMFYNzi2g==", "dev": true, "requires": { "acorn": "^6.0.7", @@ -15434,15 +16913,159 @@ }, "dependencies": { "acorn": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz", - "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.0.tgz", + "integrity": "sha512-gac8OEcQ2Li1dxIEWGZzsp2BitJxwkwcOm0zHAJLcPJaVvm58FRnk6RkuLRpU1EujipU2ZFODv2P9DLMfnV8mw==", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + } + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", "dev": true }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, "ejs": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.1.tgz", - "integrity": "sha512-kS/gEPzZs3Y1rRsbGX4UOSjtP/CeJP0CxSNZHYxGfVM/VgLcv0ZqM7C45YyTj2DI2g7+P9Dd24C+IMIg6D0nYQ==", + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "dev": true + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dev": true, + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "ipaddr.js": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.0.tgz", + "integrity": "sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA==", + "dev": true + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", + "dev": true + }, + "proxy-addr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.5.tgz", + "integrity": "sha512-t/7RxHXPH6cJtP0pRG6smSr9QJidhB+3kXu0KgXnbGYMgzEnUxRQ4/LDdfOwZEMyIh3/xHb8PX3t+lfL9z+YVQ==", + "dev": true, + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.0" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "dev": true + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dev": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", "dev": true }, "ws": { @@ -15618,9 +17241,9 @@ } }, "buffer": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", - "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", + "version": "4.9.2", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", + "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", "dev": true, "requires": { "base64-js": "^1.0.2", @@ -16114,6 +17737,32 @@ "integrity": "sha1-7vikudVYzEla06mit1FZfs2a9pA=", "dev": true }, + "whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dev": true, + "requires": { + "iconv-lite": "0.4.24" + } + }, + "whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", + "dev": true + }, + "whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dev": true, + "requires": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", @@ -16129,55 +17778,48 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "window-size": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", "dev": true }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "ansi-regex": "^4.1.0" } } } @@ -16197,15 +17839,24 @@ "mkdirp": "^0.5.1" } }, + "write-file-atomic": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.1.tgz", + "integrity": "sha512-TGHFeZEZMnv+gBFRfjAcxL5bPHrsGKtnb4qsFAws7/vlh+QfwAaySIw4AXP9ZskTTh5GWu3FLuJhsWVdiJPGvg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, "ws": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-3.3.3.tgz", - "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "~1.0.0" } }, "x-is-string": { @@ -16214,6 +17865,12 @@ "integrity": "sha1-R0tQhlrzpJqcRlfwWs0UVFj3fYI=", "dev": true }, + "xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", + "dev": true + }, "xmlhttprequest-ssl": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", @@ -16245,70 +17902,15 @@ "dev": true }, "yargs-parser": { - "version": "13.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz", - "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==", + "version": "13.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", + "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" } }, - "yargs-unparser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz", - "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==", - "dev": true, - "requires": { - "flat": "^4.1.0", - "lodash": "^4.17.11", - "yargs": "^12.0.5" - }, - "dependencies": { - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "yargs": { - "version": "12.0.5", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", - "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.2.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^11.1.1" - } - }, - "yargs-parser": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", - "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, "yeast": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", diff --git a/package.json b/package.json index 740d6024e2a..ba5bdf31470 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "prebid.js", - "version": "2.38.0", + "version": "2.44.1", "description": "Header Bidding Management Library", "main": "src/prebid.js", "scripts": { @@ -79,7 +79,6 @@ "lodash": "^4.17.4", "mocha": "^5.0.0", "opn": "^5.4.0", - "querystringify": "0.0.3", "resolve-from": "^5.0.0", "sinon": "^4.1.3", "through2": "^2.0.3", @@ -98,8 +97,10 @@ "babel-plugin-transform-object-assign": "^6.22.0", "core-js": "^2.4.1", "crypto-js": "^3.1.9-1", + "deep-equal": "^1.0.1", "dlv": "1.1.3", "dset": "2.0.1", + "express": "^4.15.4", "fun-hooks": "^0.9.5", "jsencrypt": "^3.0.0-rc.1", "just-clone": "^1.0.2" diff --git a/src/adUnits.js b/src/adUnits.js index bbdc82b6073..aa88cf6eac5 100644 --- a/src/adUnits.js +++ b/src/adUnits.js @@ -2,15 +2,47 @@ import { deepAccess } from './utils'; let adUnits = {}; +function ensureAdUnit(adunit, bidderCode) { + let adUnit = adUnits[adunit] = adUnits[adunit] || { bidders: {} }; + if (bidderCode) { + return adUnit.bidders[bidderCode] = adUnit.bidders[bidderCode] || {} + } + return adUnit; +} + +function incrementAdUnitCount(adunit, counter, bidderCode) { + let adUnit = ensureAdUnit(adunit, bidderCode); + adUnit[counter] = (adUnit[counter] || 0) + 1; + return adUnit[counter]; +} + /** * Increments and returns current Adunit counter * @param {string} adunit id * @returns {number} current adunit count */ -function incrementCounter(adunit) { - adUnits[adunit] = adUnits[adunit] || {}; - adUnits[adunit].counter = (deepAccess(adUnits, `${adunit}.counter`) + 1) || 1; - return adUnits[adunit].counter; +function incrementRequestsCounter(adunit) { + return incrementAdUnitCount(adunit, 'requestsCounter'); +} + +/** + * Increments and returns current Adunit requests counter for a bidder + * @param {string} adunit id + * @param {string} bidderCode code + * @returns {number} current adunit bidder requests count + */ +function incrementBidderRequestsCounter(adunit, bidderCode) { + return incrementAdUnitCount(adunit, 'requestsCounter', bidderCode); +} + +/** + * Increments and returns current Adunit wins counter for a bidder + * @param {string} adunit id + * @param {string} bidderCode code + * @returns {number} current adunit bidder requests count + */ +function incrementBidderWinsCounter(adunit, bidderCode) { + return incrementAdUnitCount(adunit, 'winsCounter', bidderCode); } /** @@ -18,8 +50,28 @@ function incrementCounter(adunit) { * @param {string} adunit id * @returns {number} current adunit count */ -function getCounter(adunit) { - return deepAccess(adUnits, `${adunit}.counter`) || 0; +function getRequestsCounter(adunit) { + return deepAccess(adUnits, `${adunit}.requestsCounter`) || 0; +} + +/** + * Returns current Adunit requests counter for a specific bidder code + * @param {string} adunit id + * @param {string} bidder code + * @returns {number} current adunit bidder requests count + */ +function getBidderRequestsCounter(adunit, bidder) { + return deepAccess(adUnits, `${adunit}.bidders.${bidder}.requestsCounter`) || 0; +} + +/** + * Returns current Adunit requests counter for a specific bidder code + * @param {string} adunit id + * @param {string} bidder code + * @returns {number} current adunit bidder requests count + */ +function getBidderWinsCounter(adunit, bidder) { + return deepAccess(adUnits, `${adunit}.bidders.${bidder}.winsCounter`) || 0; } /** @@ -27,8 +79,12 @@ function getCounter(adunit) { * @module adunitCounter */ let adunitCounter = { - incrementCounter, - getCounter + incrementRequestsCounter, + incrementBidderRequestsCounter, + incrementBidderWinsCounter, + getRequestsCounter, + getBidderRequestsCounter, + getBidderWinsCounter } export { adunitCounter }; diff --git a/src/adapterManager.js b/src/adapterManager.js index 6b1bc9508c8..f4b63a4d58b 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -1,6 +1,6 @@ /** @module adaptermanger */ -import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest } from './utils'; +import { flatten, getBidderCodes, getDefinedParams, shuffle, timestamp, getBidderRequest, bind } from './utils'; import { getLabels, resolveStatus } from './sizeMapping'; import { processNativeAdUnitParams, nativeAdapters } from './native'; import { newBidder } from './adapters/bidderFactory'; @@ -100,7 +100,9 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, labels, src}) bidderRequestId, auctionId, src, - bidRequestsCount: adunitCounter.getCounter(adUnit.code), + bidRequestsCount: adunitCounter.getRequestsCounter(adUnit.code), + bidderRequestsCount: adunitCounter.getBidderRequestsCounter(adUnit.code, bid.bidder), + bidderWinsCount: adunitCounter.getBidderWinsCounter(adUnit.code, bid.bidder), })); } return bids; @@ -160,6 +162,16 @@ export let gdprDataHandler = { } }; +export let uspDataHandler = { + consentData: null, + setConsentData: function(consentInfo) { + uspDataHandler.consentData = consentInfo; + }, + getConsentData: function() { + return uspDataHandler.consentData; + } +}; + adapterManager.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTimeout, labels) { let bidRequests = []; @@ -263,6 +275,12 @@ adapterManager.makeBidRequests = function(adUnits, auctionStart, auctionId, cbTi bidRequest['gdprConsent'] = gdprDataHandler.getConsentData(); }); } + + if (uspDataHandler.getConsentData()) { + bidRequests.forEach(bidRequest => { + bidRequest['uspConsent'] = uspDataHandler.getConsentData(); + }); + } return bidRequests; }; @@ -323,6 +341,8 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request s2sAjax ); } + } else { + utils.logError('missing ' + _s2sConfig.adapter); } } @@ -337,9 +357,21 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request request: requestCallbacks.request.bind(null, bidRequest.bidderCode), done: requestCallbacks.done } : undefined); - adapter.callBids(bidRequest, addBidResponse.bind(bidRequest), doneCb.bind(bidRequest), ajax, onTimelyResponse); + config.runWithBidder( + bidRequest.bidderCode, + bind.call( + adapter.callBids, + adapter, + bidRequest, + addBidResponse.bind(bidRequest), + doneCb.bind(bidRequest), + ajax, + onTimelyResponse, + config.callbackWithBidder(bidRequest.bidderCode) + ) + ); }); -} +}; function doingS2STesting() { return _s2sConfig && _s2sConfig.enabled && _s2sConfig.testing && s2sTestingModule; @@ -464,7 +496,7 @@ function tryCallBidderMethod(bidder, method, param) { const spec = adapter.getSpec(); if (spec && spec[method] && typeof spec[method] === 'function') { utils.logInfo(`Invoking ${bidder}.${method}`); - spec[method](param); + config.runWithBidder(bidder, bind.call(spec[method], spec, param)); } } catch (e) { utils.logWarn(`Error calling ${method} of ${bidder}`); @@ -488,6 +520,7 @@ adapterManager.callTimedOutBidders = function(adUnits, timedOutBidders, cbTimeou adapterManager.callBidWonBidder = function(bidder, bid, adUnits) { // Adding user configured params to bidWon event data bid.params = utils.getUserConfiguredParams(adUnits, bid.adUnitCode, bid.bidder); + adunitCounter.incrementBidderWinsCounter(bid.adUnitCode, bid.bidder); tryCallBidderMethod(bidder, 'onBidWon', bid); }; diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 4ccbfd89457..602d9e53b03 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -168,7 +168,7 @@ export function newBidder(spec) { return Object.freeze(spec); }, registerSyncs, - callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse) { + callBids: function(bidderRequest, addBidResponse, done, ajax, onTimelyResponse, configEnabledCallback) { if (!Array.isArray(bidderRequest.bids)) { return; } @@ -187,7 +187,7 @@ export function newBidder(spec) { function afterAllResponses() { done(); events.emit(CONSTANTS.EVENTS.BIDDER_DONE, bidderRequest); - registerSyncs(responses, bidderRequest.gdprConsent); + registerSyncs(responses, bidderRequest.gdprConsent, bidderRequest.uspConsent); } const validBidRequests = bidderRequest.bids.filter(filterAndWarn); @@ -216,7 +216,7 @@ export function newBidder(spec) { // Callbacks don't compose as nicely as Promises. We should call done() once _all_ the // Server requests have returned and been processed. Since `ajax` accepts a single callback, // we need to rig up a function which only executes after all the requests have been responded. - const onResponse = delayExecution(afterAllResponses, requests.length) + const onResponse = delayExecution(configEnabledCallback(afterAllResponses), requests.length) requests.forEach(processRequest); function formatGetParameters(data) { @@ -233,7 +233,7 @@ export function newBidder(spec) { ajax( `${request.url}${formatGetParameters(request.data)}`, { - success: onSuccess, + success: configEnabledCallback(onSuccess), error: onFailure }, undefined, @@ -247,7 +247,7 @@ export function newBidder(spec) { ajax( request.url, { - success: onSuccess, + success: configEnabledCallback(onSuccess), error: onFailure }, typeof request.data === 'string' ? request.data : JSON.stringify(request.data), @@ -301,6 +301,9 @@ export function newBidder(spec) { function addBidUsingRequestMap(bid) { const bidRequest = bidRequestMap[bid.requestId]; if (bidRequest) { + // creating a copy of original values as cpm and currency are modified later + bid.originalCpm = bid.cpm; + bid.originalCurrency = bid.currency; const prebidBid = Object.assign(createBid(CONSTANTS.STATUS.GOOD, bidRequest), bid); addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { @@ -327,13 +330,13 @@ export function newBidder(spec) { } }); - function registerSyncs(responses, gdprConsent) { - if (spec.getUserSyncs) { + function registerSyncs(responses, gdprConsent, uspConsent) { + if (spec.getUserSyncs && !adapterManager.aliasRegistry[spec.code]) { let filterConfig = config.getConfig('userSync.filterSettings'); let syncs = spec.getUserSyncs({ iframeEnabled: !!(config.getConfig('userSync.iframeEnabled') || (filterConfig && (filterConfig.iframe || filterConfig.all))), pixelEnabled: !!(config.getConfig('userSync.pixelEnabled') || (filterConfig && (filterConfig.image || filterConfig.all))), - }, responses, gdprConsent); + }, responses, gdprConsent, uspConsent); if (syncs) { if (!Array.isArray(syncs)) { syncs = [syncs]; diff --git a/src/adloader.js b/src/adloader.js index b422c802393..c45dacd8af0 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -4,6 +4,7 @@ import * as utils from './utils'; const _requestCache = {}; const _vendorWhitelist = [ 'criteo', + 'adagio' ] /** diff --git a/src/auction.js b/src/auction.js index fd29aec4b16..fe1b70085e9 100644 --- a/src/auction.js +++ b/src/auction.js @@ -27,8 +27,17 @@ */ /** - * @typedef {Object} BidRequest - * //TODO add all properties + * @typedef {Object} BidderRequest + * + * @property {string} bidderCode - adUnit bidder + * @property {number} auctionId - random UUID + * @property {string} bidderRequestId - random string, unique key set on all bidRequest.bids[] + * @property {Array.} bids + * @property {number} auctionStart - Date.now() at auction start + * @property {number} timeout - callback timeout + * @property {refererInfo} refererInfo - referer info object + * @property {string} [tid] - random UUID (used for s2s) + * @property {string} [src] - s2s or client (used for s2s) */ /** @@ -48,7 +57,7 @@ * @property {function(): void} callBids - sends requests to all adapters for bids */ -import { flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue } from './utils'; +import {flatten, timestamp, adUnitsFilter, deepAccess, getBidRequest, getValue} from './utils'; import { parse as parseURL } from './url'; import { getPriceBucketString } from './cpmBucketManager'; import { getNativeTargeting } from './native'; @@ -59,6 +68,7 @@ import { userSync } from './userSync'; import { hook } from './hook'; import find from 'core-js/library/fn/array/find'; import { OUTSTREAM } from './video'; +import { VIDEO } from './mediaTypes'; const { syncUsers } = userSync; const utils = require('./utils'); @@ -85,7 +95,11 @@ const queuedCalls = []; * * @param {Object} requestConfig * @param {AdUnit} requestConfig.adUnits - * @param {AdUnitCode} requestConfig.adUnitCode + * @param {AdUnitCode} requestConfig.adUnitCodes + * @param {function():void} requestConfig.callback + * @param {number} requestConfig.cbTimeout + * @param {Array.} requestConfig.labels + * @param {string} requestConfig.auctionId * * @returns {Auction} auction instance */ @@ -154,29 +168,31 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a _auctionEnd = Date.now(); events.emit(CONSTANTS.EVENTS.AUCTION_END, getProperties()); - try { - if (_callback != null) { - const adUnitCodes = _adUnitCodes; - const bids = _bidsReceived - .filter(utils.bind.call(adUnitsFilter, this, adUnitCodes)) - .reduce(groupByPlacement, {}); - _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut]); - _callback = null; - } - } catch (e) { - utils.logError('Error executing bidsBackHandler', null, e); - } finally { - // Calling timed out bidders - if (timedOutBidders.length) { - adapterManager.callTimedOutBidders(adUnits, timedOutBidders, _timeout); - } - // Only automatically sync if the publisher has not chosen to "enableOverride" - let userSyncConfig = config.getConfig('userSync') || {}; - if (!userSyncConfig.enableOverride) { - // Delay the auto sync by the config delay - syncUsers(userSyncConfig.syncDelay); + bidsBackCallback(_adUnitCodes, function () { + try { + if (_callback != null) { + const adUnitCodes = _adUnitCodes; + const bids = _bidsReceived + .filter(utils.bind.call(adUnitsFilter, this, adUnitCodes)) + .reduce(groupByPlacement, {}); + _callback.apply($$PREBID_GLOBAL$$, [bids, timedOut]); + _callback = null; + } + } catch (e) { + utils.logError('Error executing bidsBackHandler', null, e); + } finally { + // Calling timed out bidders + if (timedOutBidders.length) { + adapterManager.callTimedOutBidders(adUnits, timedOutBidders, _timeout); + } + // Only automatically sync if the publisher has not chosen to "enableOverride" + let userSyncConfig = config.getConfig('userSync') || {}; + if (!userSyncConfig.enableOverride) { + // Delay the auto sync by the config delay + syncUsers(userSyncConfig.syncDelay); + } } - } + }) } } @@ -328,6 +344,12 @@ export const addBidResponse = hook('async', function(adUnitCode, bid) { this.dispatch.call(this.bidderRequest, adUnitCode, bid); }, 'addBidResponse'); +export const bidsBackCallback = hook('async', function (adUnits, callback) { + if (callback) { + callback(); + } +}, 'bidsBackCallback'); + export function auctionCallbacks(auctionDone, auctionInstance) { let outstandingBidsAdded = 0; let allAdapterCalledDone = false; @@ -482,8 +504,7 @@ function getPreparedBidForAuction({adUnitCode, bid, bidderRequest, auctionId}) { } // Use the config value 'mediaTypeGranularity' if it has been defined for mediaType, else use 'customPriceBucket' - const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${bid.mediaType}`); - + const mediaTypeGranularity = getMediaTypeGranularity(bid.mediaType, bidReq, config.getConfig('mediaTypePriceGranularity')); const priceStringsObj = getPriceBucketString( bidObject.cpm, (typeof mediaTypeGranularity === 'object') ? mediaTypeGranularity : config.getConfig('customPriceBucket'), @@ -510,14 +531,33 @@ function setupBidTargeting(bidObject, bidderRequest) { bidObject.adserverTargeting = Object.assign(bidObject.adserverTargeting || {}, keyValues); } +/** + * @param {MediaType} mediaType + * @param {Bid} [bidReq] + * @param {MediaTypePriceGranularity} [mediaTypePriceGranularity] + * @returns {(Object|string|undefined)} + */ +export function getMediaTypeGranularity(mediaType, bidReq, mediaTypePriceGranularity) { + if (mediaType && mediaTypePriceGranularity) { + if (mediaType === VIDEO) { + const context = deepAccess(bidReq, `mediaTypes.${VIDEO}.context`, 'instream'); + if (mediaTypePriceGranularity[`${VIDEO}-${context}`]) { + return mediaTypePriceGranularity[`${VIDEO}-${context}`]; + } + } + return mediaTypePriceGranularity[mediaType]; + } +} + /** * This function returns the price granularity defined. It can be either publisher defined or default value * @param {string} mediaType + * @param {BidRequest} bidReq * @returns {string} granularity */ -export const getPriceGranularity = (mediaType) => { +export const getPriceGranularity = (mediaType, bidReq) => { // Use the config value 'mediaTypeGranularity' if it has been set for mediaType, else use 'priceGranularity' - const mediaTypeGranularity = config.getConfig(`mediaTypePriceGranularity.${mediaType}`); + const mediaTypeGranularity = getMediaTypeGranularity(mediaType, bidReq, config.getConfig('mediaTypePriceGranularity')); const granularity = (typeof mediaType === 'string' && mediaTypeGranularity) ? ((typeof mediaTypeGranularity === 'string') ? mediaTypeGranularity : 'custom') : config.getConfig('priceGranularity'); return granularity; } @@ -548,9 +588,10 @@ export const getPriceByGranularity = (granularity) => { /** * @param {string} mediaType * @param {string} bidderCode + * @param {BidRequest} bidReq * @returns {*} */ -export function getStandardBidderSettings(mediaType, bidderCode) { +export function getStandardBidderSettings(mediaType, bidderCode, bidReq) { // factory for key value objs function createKeyVal(key, value) { return { @@ -565,7 +606,7 @@ export function getStandardBidderSettings(mediaType, bidderCode) { }; } const TARGETING_KEYS = CONSTANTS.TARGETING_KEYS; - const granularity = getPriceGranularity(mediaType); + const granularity = getPriceGranularity(mediaType, bidReq); let bidderSettings = $$PREBID_GLOBAL$$.bidderSettings; if (!bidderSettings[CONSTANTS.JSON_MAPPING.BD_SETTING_STANDARD]) { @@ -619,7 +660,7 @@ export function getKeyValueTargetingPairs(bidderCode, custBidObj, bidReq) { // 1) set the keys from "standard" setting or from prebid defaults if (bidderSettings) { // initialize default if not set - const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode); + const standardSettings = getStandardBidderSettings(custBidObj.mediaType, bidderCode, bidReq); setKeys(keyValues, standardSettings, custBidObj); // 2) set keys from specific bidder setting override if they exist diff --git a/src/config.js b/src/config.js index ec26c3d51d0..19a582e86d5 100644 --- a/src/config.js +++ b/src/config.js @@ -1,11 +1,24 @@ /* * Module for getting and setting Prebid configuration. */ + +/** + * @typedef {Object} MediaTypePriceGranularity + * + * @property {(string|Object)} [banner] + * @property {(string|Object)} [native] + * @property {(string|Object)} [video] + * @property {(string|Object)} [video-instream] + * @property {(string|Object)} [video-outstream] + */ + import { isValidPriceConfig } from './cpmBucketManager'; import find from 'core-js/library/fn/array/find'; import includes from 'core-js/library/fn/array/includes'; +import Set from 'core-js/library/fn/set'; import { parseQS } from './url'; +const from = require('core-js/library/fn/array/from'); const utils = require('./utils'); const CONSTANTS = require('./constants'); @@ -49,6 +62,8 @@ export function newConfig() { let listeners = []; let defaults; let config; + let bidderConfig; + let currBidder = null; function resetConfig() { defaults = {}; @@ -86,7 +101,7 @@ export function newConfig() { if (validatePriceGranularity(val)) { if (typeof val === 'string') { this._priceGranularity = (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM; - } else if (typeof val === 'object') { + } else if (utils.isPlainObject(val)) { this._customPriceBucket = val; this._priceGranularity = GRANULARITY_OPTIONS.CUSTOM; utils.logMessage('Using custom price granularity'); @@ -102,7 +117,12 @@ export function newConfig() { return this._customPriceBucket; }, + /** + * mediaTypePriceGranularity + * @type {MediaTypePriceGranularity} + */ _mediaTypePriceGranularity: {}, + get mediaTypePriceGranularity() { return this._mediaTypePriceGranularity; }, @@ -111,7 +131,7 @@ export function newConfig() { if (validatePriceGranularity(val[item])) { if (typeof val === 'string') { aggregate[item] = (hasGranularity(val[item])) ? val[item] : this._priceGranularity; - } else if (typeof val === 'object') { + } else if (utils.isPlainObject(val)) { aggregate[item] = val[item]; utils.logMessage(`Using custom price granularity for ${item}`); } @@ -182,6 +202,7 @@ export function newConfig() { } config = newConfig; + bidderConfig = {}; function hasGranularity(val) { return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]); @@ -196,7 +217,7 @@ export function newConfig() { if (!hasGranularity(val)) { utils.logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); } - } else if (typeof val === 'object') { + } else if (utils.isPlainObject(val)) { if (!isValidPriceConfig(val)) { utils.logError('Invalid custom price value passed to `setPriceGranularity()`'); return false; @@ -206,6 +227,33 @@ export function newConfig() { } } + /** + * Returns base config with bidder overrides (if there is currently a bidder) + * @private + */ + function _getConfig() { + if (currBidder && bidderConfig && utils.isPlainObject(bidderConfig[currBidder])) { + let currBidderConfig = bidderConfig[currBidder]; + const configTopicSet = new Set(Object.keys(config).concat(Object.keys(currBidderConfig))); + + return from(configTopicSet).reduce((memo, topic) => { + if (typeof currBidderConfig[topic] === 'undefined') { + memo[topic] = config[topic]; + } else if (typeof config[topic] === 'undefined') { + memo[topic] = currBidderConfig[topic]; + } else { + if (utils.isPlainObject(currBidderConfig[topic])) { + memo[topic] = Object.assign({}, config[topic], currBidderConfig[topic]); + } else { + memo[topic] = currBidderConfig[topic]; + } + } + return memo; + }, {}); + } + return Object.assign({}, config); + } + /* * Returns configuration object if called without parameters, * or single configuration property if given a string matching a configuration @@ -217,18 +265,25 @@ export function newConfig() { function getConfig(...args) { if (args.length <= 1 && typeof args[0] !== 'function') { const option = args[0]; - return option ? utils.deepAccess(config, option) : config; + return option ? utils.deepAccess(_getConfig(), option) : _getConfig(); } return subscribe(...args); } + /** + * Internal API for modules (such as prebid-server) that might need access to all bidder config + */ + function getBidderConfig() { + return bidderConfig; + } + /* * Sets configuration given an object containing key-value pairs and calls * listeners that were added by the `subscribe` function */ function setConfig(options) { - if (typeof options !== 'object') { + if (!utils.isPlainObject(options)) { utils.logError('setConfig options must be an object'); return; } @@ -239,7 +294,7 @@ export function newConfig() { topics.forEach(topic => { let option = options[topic]; - if (typeof defaults[topic] === 'object' && typeof option === 'object') { + if (utils.isPlainObject(defaults[topic]) && utils.isPlainObject(option)) { option = Object.assign({}, defaults[topic], option); } @@ -254,7 +309,7 @@ export function newConfig() { * @param {object} options */ function setDefaults(options) { - if (typeof defaults !== 'object') { + if (!utils.isPlainObject(defaults)) { utils.logError('defaults must be an object'); return; } @@ -300,11 +355,12 @@ export function newConfig() { return; } - listeners.push({ topic, callback }); + const nl = { topic, callback }; + listeners.push(nl); // save and call this function to remove the listener return function unsubscribe() { - listeners.splice(listeners.indexOf(listener), 1); + listeners.splice(listeners.indexOf(nl), 1); }; } @@ -327,13 +383,68 @@ export function newConfig() { .forEach(listener => listener.callback(options)); } + function setBidderConfig(config) { + try { + check(config); + config.bidders.forEach(bidder => { + if (!bidderConfig[bidder]) { + bidderConfig[bidder] = {}; + } + Object.keys(config.config).forEach(topic => { + let option = config.config[topic]; + if (utils.isPlainObject(option)) { + bidderConfig[bidder][topic] = Object.assign({}, bidderConfig[bidder][topic] || {}, option); + } else { + bidderConfig[bidder][topic] = option; + } + }); + }); + } catch (e) { + utils.logError(e); + } + function check(obj) { + if (!utils.isPlainObject(obj)) { + throw 'setBidderConfig bidder options must be an object'; + } + if (!(Array.isArray(obj.bidders) && obj.bidders.length)) { + throw 'setBidderConfig bidder options must contain a bidders list with at least 1 bidder'; + } + if (!utils.isPlainObject(obj.config)) { + throw 'setBidderConfig bidder options must contain a config object'; + } + } + } + + /** + * Internal functions for core to execute some synchronous code while having an active bidder set. + */ + function runWithBidder(bidder, fn) { + currBidder = bidder; + try { + return fn(); + } finally { + currBidder = null; + } + } + function callbackWithBidder(bidder) { + return function(cb) { + return function(...args) { + return runWithBidder(bidder, utils.bind.call(cb, this, ...args)) + } + } + } + resetConfig(); return { getConfig, setConfig, setDefaults, - resetConfig + resetConfig, + runWithBidder, + callbackWithBidder, + setBidderConfig, + getBidderConfig }; } diff --git a/src/constants.json b/src/constants.json index 99e2bd792b9..d993a6531ad 100644 --- a/src/constants.json +++ b/src/constants.json @@ -95,5 +95,8 @@ "BID_STATUS" : { "BID_TARGETING_SET": "targetingSet", "RENDERED": "rendered" + }, + "SUBMODULES_THAT_ALWAYS_REFRESH_ID": { + "parrableId": true } } diff --git a/src/prebid.js b/src/prebid.js index 14c63651ba5..20d93372837 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -456,9 +456,11 @@ $$PREBID_GLOBAL$$.requestBids = hook('async', function ({ bidsBackHandler, timeo // drop the bidder from the ad unit if it's not compatible utils.logWarn(utils.unsupportedBidderMessage(adUnit, bidder)); adUnit.bids = adUnit.bids.filter(bid => bid.bidder !== bidder); + } else { + adunitCounter.incrementBidderRequestsCounter(adUnit.code, bidder); } }); - adunitCounter.incrementCounter(adUnit.code); + adunitCounter.incrementRequestsCounter(adUnit.code); }); if (!adUnits || adUnits.length === 0) { @@ -780,6 +782,7 @@ $$PREBID_GLOBAL$$.getConfig = config.getConfig; * ``` */ $$PREBID_GLOBAL$$.setConfig = config.setConfig; +$$PREBID_GLOBAL$$.setBidderConfig = config.setBidderConfig; $$PREBID_GLOBAL$$.que.push(() => listenMessagesFromCreative()); diff --git a/src/prebidGlobal.js b/src/prebidGlobal.js index ec685236468..5eed4b3670f 100644 --- a/src/prebidGlobal.js +++ b/src/prebidGlobal.js @@ -4,6 +4,10 @@ window.$$PREBID_GLOBAL$$ = (window.$$PREBID_GLOBAL$$ || {}); window.$$PREBID_GLOBAL$$.cmd = window.$$PREBID_GLOBAL$$.cmd || []; window.$$PREBID_GLOBAL$$.que = window.$$PREBID_GLOBAL$$.que || []; +// create a pbjs global pointer +window._pbjsGlobals = window._pbjsGlobals || []; +window._pbjsGlobals.push('$$PREBID_GLOBAL$$'); + export function getGlobal() { return window.$$PREBID_GLOBAL$$; } diff --git a/src/secureCreatives.js b/src/secureCreatives.js index df7abe4ebee..7584c5d2bf7 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -81,8 +81,9 @@ export function _sendAdToCreative(adObject, remoteDomain, source) { function resizeRemoteCreative({ adUnitCode, width, height }) { // resize both container div + iframe - ['div:last-child', 'div:last-child iframe'].forEach(elmType => { - let element = getElementByAdUnit(elmType); + ['div', 'iframe'].forEach(elmType => { + // not select element that gets removed after dfp render + let element = getElementByAdUnit(elmType + ':not([style*="display: none"])'); if (element) { let elementStyle = element.style; elementStyle.width = width + 'px'; diff --git a/src/utils.js b/src/utils.js index 2caaedc4164..5efc969896e 100644 --- a/src/utils.js +++ b/src/utils.js @@ -954,7 +954,7 @@ export function delayExecution(func, numRequiredCalls) { return function () { numCalls++; if (numCalls === numRequiredCalls) { - func.apply(null, arguments); + func.apply(this, arguments); } } } @@ -1264,6 +1264,12 @@ export function getDataFromLocalStorage(key) { } } +export function removeDataFromLocalStorage(key) { + if (hasLocalStorage()) { + window.localStorage.removeItem(key); + } +} + export function hasLocalStorage() { try { return !!window.localStorage; diff --git a/test/mock-server/expectations/request-response-pairs/banner/index.js b/test/mock-server/expectations/request-response-pairs/banner/index.js new file mode 100644 index 00000000000..17538c8b33d --- /dev/null +++ b/test/mock-server/expectations/request-response-pairs/banner/index.js @@ -0,0 +1,96 @@ +var app = require('../../../index'); + +/** + * This file will have the fixtures for request and response. Each one has to export two functions getRequest and getResponse. + * expectation directory will hold all the request reponse pairs of different types. middlewares added to the server will parse + * these files and return the response when expecation is met + * + * The expecation created here is replicating trafficSourceCode example in Prebid. + */ + +/** + * This function will return the request object with all the entities method, path, body, header etc. + * + * @return {object} Request object + */ +exports.getRequest = function() { + return { + 'httpRequest': { + 'method': 'POST', + 'path': '/', + 'body': { + 'tags': [{ + 'sizes': [{ + 'width': 300, + 'height': 250 + }, { + 'width': 300, + 'height': 600 + }], + 'primary_size': { + 'width': 300, + 'height': 250 + }, + 'ad_types': ['banner'], + 'id': 13144370, + 'allow_smaller_sizes': false, + 'use_pmt_rule': false, + 'prebid': true, + 'disable_psa': true + }], + 'user': {} + } + } + } +} + +/** + * This function will return the response object with all the entities method, path, body, header etc. + * + * @return {object} Response object + */ +exports.getResponse = function() { + return { + 'httpResponse': { + 'body': { + 'version': '3.0.0', + 'tags': [{ + 'uuid': '2c8c83a1deaf1a', + 'tag_id': 13144370, + 'auction_id': '8147841645883553832', + 'nobid': false, + 'no_ad_url': 'http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QKzCKAzBAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAkCABEJBywAABkAAACA61HgPyEREgApEQkAMREb8GkwsqKiBjjtSEDtSEgAUABYnPFbYABotc95eACAAQGKAQCSAQNVU0SYAawCoAH6AagBAbABALgBAcABAMgBAtABANgBAOABAPABAIoCO3VmKCdhJywgMjUyOTg4NSwgMTU3MzQ3MjE0Nik7AR0scicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggA4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmAFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08L_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=68cfb6ed042ea47f5d3fc2c32cc068500e542066', + 'timeout_ms': 0, + 'ad_profile_id': 1182765, + 'rtb_video_fallback': false, + 'ads': [{ + 'content_source': 'rtb', + 'ad_type': 'banner', + 'buyer_member_id': 9325, + 'advertiser_id': 2529885, + 'creative_id': 96846035, + 'media_type_id': 1, + 'media_subtype_id': 1, + 'cpm': 0.500000, + 'cpm_publisher_currency': 0.500000, + 'is_bin_price_applied': false, + 'publisher_currency_code': '$', + 'brand_category_id': 0, + 'client_initiated_ad_counting': true, + 'rtb': { + 'banner': { + 'content': "
", + 'width': 300, + 'height': 250 + }, + 'trackers': [{ + 'impression_urls': ['http://sin3-ib.adnxs.com/it?an_audit=0&referrer=http%3A%2F%2Ftest.localhost%3A9999%2FintegrationExamples%2Fgpt%2Fhello_world.html%3Fpbjs_debug%3Dtrue&e=wqT_3QK7CKA7BAAAAwDWAAUBCJKPpe4FEKjwl-js2byJcRiq5MnUovf28WEqNgkAAAECCOA_EQEHNAAA4D8ZAAAAgOtR4D8hERIAKREJADERG6gwsqKiBjjtSEDtSEgCUNOBly5YnPFbYABotc95eJK4BYABAYoBA1VTRJIBAQbwUpgBrAKgAfoBqAEBsAEAuAEBwAEEyAEC0AEA2AEA4AEA8AEAigI7dWYoJ2EnLCAyNTI5ODg1LCAxNTczNDcyMTQ2KTt1ZigncicsIDk2ODQ2MDM1Nh4A8PWSAqUCIXp6ZmhVQWl1c0s0S0VOT0JseTRZQUNDYzhWc3dBRGdBUUFSSTdVaFFzcUtpQmxnQVlJSUNhQUJ3Q0hncWdBRWtpQUVxa0FFQW1BRUFvQUVCcUFFRHNBRUF1UUVwaTRpREFBRGdQOEVCS1l1SWd3QUE0RF9KQVozRkl5WjA1Tm9fMlFFQUFBQUFBQUR3UC1BQkFQVUJBQUFBQUpnQ0FLQUNBTFVDQUFBQUFMMENBQUFBQU9BQ0FPZ0NBUGdDQUlBREFaZ0RBYWdEcnJDdUNyb0RDVk5KVGpNNk5EY3pOZUFEcUJXSUJBQ1FCQUNZQkFIQkIFRQkBCHlRUQkJAQEUTmdFQVBFEY0BkEg0QkFDSUJmOGuaAokBIUl3OTBCOikBJG5QRmJJQVFvQUQROFhEZ1B6b0pVMGxPTXpvME56TTFRS2dWUxFoDFBBX1URDAxBQUFXHQwAWR0MAGEdDABjHQzwQGVBQS7CAi9odHRwOi8vcHJlYmlkLm9yZy9kZXYtZG9jcy9nZXR0aW5nLXN0YXJ0ZWQuaHRtbNgCAOACrZhI6gJTDTrYdGVzdC5sb2NhbGhvc3Q6OTk5OS9pbnRlZ3JhdGlvbkV4YW1wbGVzL2dwdC9oZWxsb193b3JsZAVO8EA_cGJqc19kZWJ1Zz10cnVlgAMAiAMBkAMAmAMXoAMBqgMAwAOsAsgDANgDAOADAOgDAPgDAYAEAJIEDS91dC92Mw248F6YBACiBAsxMC43NS43NC42OagEkECyBBIIBBAEGKwCIPoBKAEoAjAAOAK4BADABADIBADSBA45MzI1I1NJTjM6NDczNdoEAggB4AQB8ATTgZcuiAUBmAUAoAX______wEDFAHABQDJBWmIFPA_0gUJCQkMcAAA2AUB4AUB8AUB-gUECAAQAJAGAJgGALgGAMEGCSM08D_IBgDQBvUv2gYWChAJFBkBUBAAGADgBgHyBgIIAIAHAYgHAKAHAQ..&s=951a029669a69e3f0c527c937c2d852be92802e1'], + 'video_events': {} + }] + } + }] + }] + }, + } + } +} diff --git a/test/mock-server/index.js b/test/mock-server/index.js new file mode 100644 index 00000000000..30ead952fcc --- /dev/null +++ b/test/mock-server/index.js @@ -0,0 +1,36 @@ +const express = require('express'); +const argv = require('yargs').argv; +const app = module.exports = express(); +const port = (argv.port) ? argv.port : 3000; +const bodyParser = require('body-parser'); +const renderCreative = require('./request-middlewares/prebid-request.js'); + +app.use(express.static(__dirname + '/content')); +app.use(bodyParser.text({type: 'text/plain'})); + +app.locals = { + 'port': port, + 'host': 'localhost' +}; + +// get type will be used to test prebid jsonp requests +app.get('/', renderCreative, (request, response) => { + response.send(); +}); + +// prebid make POST type request to ut endpoint so here we will match ut endpoint request. +app.post('/', renderCreative, (request, response) => { + response.send(); +}); + +app.listen(port, (err) => { + if (err) { + return console.log('something bad happened', err); + } + + console.log(`server is listening on ${port}`); +}); + +process.on('SIGTERM', function() { console.log('halt mock-server'); process.exit(0) }); + +process.on('SIGINT', function() { console.log('shutdown mock-server'); process.exit(0) }); diff --git a/test/mock-server/request-middlewares/prebid-request.js b/test/mock-server/request-middlewares/prebid-request.js new file mode 100644 index 00000000000..6e2d03487cf --- /dev/null +++ b/test/mock-server/request-middlewares/prebid-request.js @@ -0,0 +1,75 @@ +/** + * This middleware will be used to find matching request hitting the ut endpoint by prebid. + * As of now it only uses the request payload to compare with httpRequest.body defined in expectations dir. + * Matching headers or cookies can also be the use case. + */ + +const glob = require('glob'); +const path = require('path'); +const deepEqual = require('deep-equal'); + +module.exports = function (req, res, next) { + let reqBody; + try { + if (req.method === 'GET') { + reqBody = JSON.parse(req.query.q); + } else { + reqBody = JSON.parse(req.body); + } + } catch (e) { + // error + } + + // prebid uses uuid to match request response pairs. + // On each request new uuid is generated, so here i am grabbing the uuid from incoming request and adding it to matched response. + let uuidObj = {}; + if (reqBody && reqBody.uuid) { + uuidObj.response = reqBody.uuid; + delete reqBody.uuid; + } + + if (reqBody && reqBody.tags) { + uuidObj.tags = reqBody.tags.map((tag) => { + let uuid = tag.uuid; + delete tag.uuid; + return uuid; + }); + } + + // values within these request props are dynamically generated and aren't + // vital to check in these tests, so they are deleted rather than updating + // the request-response pairs continuously + ['sdk', 'referrer_detection'].forEach(prop => { + if (reqBody && reqBody[prop]) { + delete reqBody[prop]; + } + }); + + // Parse all the expectation to find response for this request + glob.sync('./test/mock-server/expectations/**/*.js').some((file) => { + file = require(path.resolve(file)); + let expectedReqBody = JSON.parse(JSON.stringify(file.getRequest().httpRequest.body)); + // respond to all requests + // TODO send a 404 if resource not found + res.set({ + 'Access-Control-Allow-Credentials': 'true', + 'Access-Control-Allow-Origin': req.headers.origin + }); + + // As of now only body is compared. We can also add other request properties like headers, cookies if required + if (deepEqual(reqBody, expectedReqBody)) { + let response = file.getResponse().httpResponse.body; + if (Object.keys(uuidObj).length > 0) { + response.tags.forEach((tag, index) => { + tag.uuid = uuidObj.tags[index]; + }); + } + res.type('json'); + response = JSON.stringify(response); + res.write(response); + return true; + } + }); + + next(); +}; diff --git a/test/pages/banner.html b/test/pages/banner.html index 05085089f72..e1859abdd85 100644 --- a/test/pages/banner.html +++ b/test/pages/banner.html @@ -30,22 +30,24 @@ placementId: 13144370 } }] - }, { - code: 'div-gpt-ad-1460505748561-1', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - bids: [{ - bidder: "rubicon", - params: { - accountId: 14062, - siteId: 70608, - zoneId: 498816 - } - }] - }]; + } + //, { + // code: 'div-gpt-ad-1460505748561-1', + // mediaTypes: { + // banner: { + // sizes: [[300, 250], [300, 600]], + // } + // }, + // bids: [{ + // bidder: "appnexus", + // params: { + // accountId: 14062, + // siteId: 70608, + // zoneId: 498816 + // } + // }] + // } + ]; ' + }, + 'rtb': { + 'banner': { + 'content': '', + 'width': 300, + 'height': 250 + }, + 'trackers': [ + { + 'impression_urls': [ + 'http://lax1-ib.adnxs.com/impression' + ], + 'video_events': {} + } + ] + } + } + ] + } + ] + }; + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '3db3773286ee59', + 'cpm': 0.5, + 'creativeId': 29681110, + 'dealId': undefined, + 'width': 300, + 'height': 250, + 'ad': '', + 'mediaType': 'banner', + 'currency': 'USD', + 'ttl': 300, + 'netRevenue': true, + 'adUnitCode': 'code', + 'ads4good': { + 'buyerMemberId': 958 + } + } + ]; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + }); + + it('handles nobid responses', function () { + let response = { + 'version': '0.0.1', + 'tags': [{ + 'uuid': '84ab500420319d', + 'tag_id': 5976557, + 'auction_id': '297492697822162468', + 'nobid': true + }] + }; + let bidderRequest; + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result.length).to.equal(0); + }); + + it('handles non-banner media responses', function () { + let response = { + 'tags': [{ + 'uuid': '84ab500420319d', + 'ads': [{ + 'ad_type': 'video', + 'cpm': 0.500000, + 'notify_url': 'imptracker.com', + 'rtb': { + 'video': { + 'content': '' + } + }, + 'javascriptTrackers': '' + }] + }] + }; + let bidderRequest = { + bids: [{ + bidId: '84ab500420319d', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response }, {bidderRequest}); + expect(result[0]).to.have.property('vastUrl'); + expect(result[0]).to.have.property('vastImpUrl'); + expect(result[0]).to.have.property('mediaType', 'video'); + }); + + it('should add deal_priority and deal_code', function() { + let responseWithDeal = deepClone(response); + responseWithDeal.tags[0].ads[0].deal_priority = 'high'; + responseWithDeal.tags[0].ads[0].deal_code = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseWithDeal }, {bidderRequest}); + expect(Object.keys(result[0].ads4good)).to.include.members(['buyerMemberId', 'dealPriority', 'dealCode']); + }); + + it('should add advertiser id', function() { + let responseAdvertiserId = deepClone(response); + responseAdvertiserId.tags[0].ads[0].advertiser_id = '123'; + + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + let result = spec.interpretResponse({ body: responseAdvertiserId }, {bidderRequest}); + expect(Object.keys(result[0].meta)).to.include.members(['advertiserId']); + }) + }); +}); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..75b476f67af --- /dev/null +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -0,0 +1,188 @@ +import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter'; +import { expect } from 'chai'; +import * as utils from 'src/utils'; + +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('adagio analytics adapter', () => { + let xhr; + let requests; + let sandbox + let adagioQueuePushSpy; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + + xhr = sandbox.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + sandbox.stub(events, 'getEvents').returns([]); + + const w = utils.getWindowTop(); + + adapterManager.registerAnalyticsAdapter({ + code: 'adagio', + adapter: adagioAnalyticsAdapter + }); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.queue = w.ADAGIO.queue || []; + + adagioQueuePushSpy = sandbox.spy(w.ADAGIO.queue, 'push'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('track', () => { + beforeEach(() => { + adapterManager.enableAnalytics({ + provider: 'adagio' + }); + }); + + afterEach(() => { + adagioAnalyticsAdapter.disableAnalytics(); + }); + + it('builds and sends auction data', () => { + const w = utils.getWindowTop(); + + let bidRequest = { + bids: [{ + adUnitCode: 'div-1', + params: { + features: { + siteId: '2', + placement: 'pave_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + } + }, { + adUnitCode: 'div-2', + params: { + features: { + siteId: '2', + placement: 'ban_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + }, + }], + }; + let bidResponse = { + bidderCode: 'adagio', + width: 300, + height: 250, + statusMessage: 'Bid available', + cpm: 6.2189757658226075, + currency: '', + netRevenue: false, + adUnitCode: 'div-1', + timeToRespond: 132, + }; + + // Step 1: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 2: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 3: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {}); + + sandbox.assert.callCount(adagioQueuePushSpy, 3); + + const call0 = adagioQueuePushSpy.getCall(0); + expect(call0.args[0].action).to.equal('pb-analytics-event'); + expect(call0.args[0].ts).to.not.be.undefined; + expect(call0.args[0].data).to.not.be.undefined; + expect(call0.args[0].data).to.deep.equal({eventName: constants.EVENTS.BID_REQUESTED, args: bidRequest}); + + const call1 = adagioQueuePushSpy.getCall(1); + expect(call1.args[0].action).to.equal('pb-analytics-event'); + expect(call1.args[0].ts).to.not.be.undefined; + expect(call1.args[0].data).to.not.be.undefined; + expect(call1.args[0].data).to.deep.equal({eventName: constants.EVENTS.BID_RESPONSE, args: bidResponse}); + + const call2 = adagioQueuePushSpy.getCall(2); + expect(call2.args[0].action).to.equal('pb-analytics-event'); + expect(call2.args[0].ts).to.not.be.undefined; + expect(call2.args[0].data).to.not.be.undefined; + expect(call2.args[0].data).to.deep.equal({eventName: constants.EVENTS.AUCTION_END, args: {}}); + }); + }); + + describe('no track', () => { + beforeEach(() => { + sandbox.stub(utils, 'getWindowTop').throws(); + + adapterManager.enableAnalytics({ + provider: 'adagio' + }); + }); + + afterEach(() => { + adagioAnalyticsAdapter.disableAnalytics(); + sandbox.restore(); + }); + + it('builds and sends auction data', () => { + let bidRequest = { + bids: [{ + adUnitCode: 'div-1', + params: { + features: { + siteId: '2', + placement: 'pave_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + } + }, { + adUnitCode: 'div-2', + params: { + features: { + siteId: '2', + placement: 'ban_top', + pagetype: 'article', + category: 'IAB12,IAB12-2', + device: '2', + } + }, + }], + }; + let bidResponse = { + bidderCode: 'adagio', + width: 300, + height: 250, + statusMessage: 'Bid available', + cpm: 6.2189757658226075, + currency: '', + netRevenue: false, + adUnitCode: 'div-1', + timeToRespond: 132, + }; + + // Step 1: Send bid requested event + events.emit(constants.EVENTS.BID_REQUESTED, bidRequest); + + // Step 2: Send bid response event + events.emit(constants.EVENTS.BID_RESPONSE, bidResponse); + + // Step 3: Send auction end event + events.emit(constants.EVENTS.AUCTION_END, {}); + + utils.getWindowTop.restore(); + + sandbox.assert.callCount(adagioQueuePushSpy, 0); + }); + }); +}); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index 7437b45b6c1..6c804418d03 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,10 +1,22 @@ import { expect } from 'chai'; +import { getAdagioScript, spec } from 'modules/adagioBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; -import { spec } from 'modules/adagioBidAdapter'; +import * as utils from 'src/utils'; describe('adagioAdapter', () => { + let utilsMock; const adapter = newBidder(spec); const ENDPOINT = 'https://mp.4dex.io/prebid'; + const VERSION = '2.0.0'; + + beforeEach(function() { + localStorage.removeItem('adagioScript'); + utilsMock = sinon.mock(utils); + }); + + afterEach(function() { + utilsMock.restore(); + }); describe('inherited functions', () => { it('exists and is a function', () => { @@ -13,12 +25,40 @@ describe('adagioAdapter', () => { }); describe('isBidRequestValid', () => { + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + let element = { + x: 0, + y: 0, + width: 200, + height: 300, + getBoundingClientRect: () => { + return { + width: element.width, + height: element.height, + left: element.x, + top: element.y, + right: element.x + element.width, + bottom: element.y + element.height + }; + } + }; + sandbox.stub(document, 'getElementById').withArgs('banner-atf').returns(element); + }); + + afterEach(function () { + sandbox.restore(); + }); + let bid = { 'bidder': 'adagio', 'params': { - siteId: '123', - placementId: 4, - categories: ['IAB12', 'IAB12-2'] + organizationId: '0', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf' }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], @@ -27,33 +67,168 @@ describe('adagioAdapter', () => { 'auctionId': 'lel4fhp239i9km', }; + let bidWithMediaTypes = { + 'bidder': 'adagio', + 'params': { + organizationId: '0', + placement: 'PAVE_ATF', + site: 'SITE-NAME', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf' + }, + 'adUnitCode': 'adunit-code-2', + 'mediaTypes': { + banner: { + sizes: [[300, 250]], + } + }, + sizes: [[300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + } + it('should return true when required params found', () => { expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + }) + + it('should compute a printNumber for the new bid request on same adUnitCode and same pageviewId', () => { + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.adUnits).ok; + expect(window.top.ADAGIO.adUnits['adunit-code']).ok; + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(2); + + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(3); + + window.top.ADAGIO.pageviewId = 123; + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.adUnits['adunit-code'].printNumber).to.equal(1); + }); + + it('should return false when organization params is not passed', () => { + let bidTest = Object.assign({}, bid); + delete bidTest.params.organizationId; + expect(spec.isBidRequestValid(bidTest)).to.equal(false); }); it('should return false when site params is not passed', () => { let bidTest = Object.assign({}, bid); - delete bidTest.params.siteId; + delete bidTest.params.site; expect(spec.isBidRequestValid(bidTest)).to.equal(false); }); it('should return false when placement params is not passed', () => { let bidTest = Object.assign({}, bid); - delete bidTest.params.placementId; + delete bidTest.params.placement; expect(spec.isBidRequestValid(bidTest)).to.equal(false); }); + + it('should return false when adUnit element id params is not passed', () => { + let bidTest = Object.assign({}, bid); + delete bidTest.params.adUnitElementId; + expect(spec.isBidRequestValid(bidTest)).to.equal(false); + }); + + it('should return false if not in the window.top', () => { + sandbox.stub(utils, 'getWindowTop').throws(); + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should expose ADAGIO.pbjsAdUnits in window', () => { + spec.isBidRequestValid(bidWithMediaTypes); + spec.isBidRequestValid(bid); + expect(window.top.ADAGIO.pbjsAdUnits).ok; + expect(window.top.ADAGIO.pbjsAdUnits).to.have.lengthOf(2); + const adUnitWithMediaTypeSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code-2')[0]; + const adUnitWithSizes = window.top.ADAGIO.pbjsAdUnits.filter((aU) => aU.code === 'adunit-code')[0]; + expect(adUnitWithMediaTypeSizes.sizes).to.eql([[300, 250]]); + expect(adUnitWithSizes.sizes).to.eql([[300, 250], [300, 600]]); + }); }); describe('buildRequests', () => { + const sandbox = sinon.createSandbox(); + + const banner300x250 = { + x: 0, + y: 0, + width: 300, + height: 250, + getBoundingClientRect: () => { + return { + width: banner300x250.width, + height: banner300x250.height, + left: banner300x250.x, + top: banner300x250.y, + right: banner300x250.x + banner300x250.width, + bottom: banner300x250.y + banner300x250.height + }; + }, + }; + + const banner300x600 = { + x: 0, + y: 0, + width: 300, + height: 600, + getBoundingClientRect: () => { + return { + width: banner300x600.width, + height: banner300x600.height, + left: banner300x600.x, + top: banner300x600.y, + right: banner300x600.x + banner300x600.width, + bottom: banner300x600.y + banner300x600.height + }; + }, + }; + + const computedStyleBlock = { + display: 'block' + }; + + const computedStyleNone = { + display: 'none' + }; + + const stubs = { + topGetElementById: undefined, + topGetComputedStyle: undefined + } + + top.ADAGIO = top.ADAGIO || {}; + top.ADAGIO.adUnits = top.ADAGIO.adUnits || {}; + top.ADAGIO.pbjsAdUnits = top.ADAGIO.pbjsAdUnits || []; + + beforeEach(function () { + stubs.topGetElementById = sandbox.stub(top.document, 'getElementById'); + stubs.topGetComputedStyle = sandbox.stub(top, 'getComputedStyle'); + + stubs.topGetElementById.withArgs('banner-atf-123').returns(banner300x250); + stubs.topGetElementById.withArgs('banner-atf-456').returns(banner300x600); + stubs.topGetElementById.withArgs('does-not-exist').returns(null); + stubs.topGetComputedStyle.returns(computedStyleBlock); + }); + + afterEach(function () { + sandbox.restore(); + }); + + after(function() { + sandbox.reset(); + }) + let bidRequests = [ - // siteId 123 { 'bidder': 'adagio', 'params': { - siteId: '123', - placementId: 4, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '123', + site: 'ADAGIO-123', + placement: 'PAVE_ATF-123', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-123' }, 'adUnitCode': 'adunit-code1', 'sizes': [[300, 250], [300, 600]], @@ -64,10 +239,11 @@ describe('adagioAdapter', () => { { 'bidder': 'adagio', 'params': { - siteId: '123', - placementId: 3, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '123', + site: 'ADAGIO-123', + placement: 'PAVE_ATF-123', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-123' }, 'adUnitCode': 'adunit-code2', 'sizes': [[300, 250], [300, 600]], @@ -75,14 +251,38 @@ describe('adagioAdapter', () => { 'bidderRequestId': '8vfscuixrovn8i', 'auctionId': 'lel4fhp239i9km', }, - // siteId 456 { 'bidder': 'adagio', 'params': { - siteId: '456', - placementId: 4, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '456', + site: 'ADAGIO-456', + placement: 'PAVE_ATF-456', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-456' + }, + 'adUnitCode': 'adunit-code3', + 'mediaTypes': { + banner: { + sizes: [[300, 250]] + } + }, + 'sizes': [[300, 250], [300, 600]], + 'bidId': 'c180kg4267tyqz', + 'bidderRequestId': '8vfscuixrovn8i', + 'auctionId': 'lel4fhp239i9km', + } + ]; + + const bidRequestsWithPostBid = [ + { + 'bidder': 'adagio', + 'params': { + organizationId: '456', + site: 'ADAGIO-456', + placement: 'PAVE_ATF-456', + pagetype: 'ARTICLE', + adUnitElementId: 'banner-atf-456', + postBid: true }, 'adUnitCode': 'adunit-code3', 'sizes': [[300, 250], [300, 600]], @@ -102,36 +302,120 @@ describe('adagioAdapter', () => { consentString: consentString, gdprApplies: true, allowAuctionWithoutConsent: true + }, + 'refererInfo': { + 'numIframes': 0, + 'reachedTop': true, + 'referer': 'http://test.io/index.html?pbjs_debug=true' } }; it('groups requests by siteId', () => { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); - expect(requests[0].data.siteId).to.equal('123'); + expect(requests[0].data.organizationId).to.equal('123'); expect(requests[0].data.adUnits).to.have.lengthOf(2); - expect(requests[1].data.siteId).to.equal('456'); + expect(requests[1].data.organizationId).to.equal('456'); expect(requests[1].data.adUnits).to.have.lengthOf(1); }); it('sends bid request to ENDPOINT_PB via POST', () => { - const requests = spec.buildRequests(bidRequests); + const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); const request = requests[0]; expect(request.method).to.equal('POST'); expect(request.url).to.equal(ENDPOINT); + expect(request.data.prebidVersion).to.equal('$prebid.version$'); }); - it('features params must be an empty object if featurejs is not loaded', () => { - const requests = spec.buildRequests(bidRequests); - expect(requests).to.have.lengthOf(2); + it('features params must be empty if param adUnitElementId is not found', () => { + const requests = spec.buildRequests([Object.assign({}, bidRequests[0], {params: {adUnitElementId: 'does-not-exist'}})], bidderRequest); const request = requests[0]; - const expected = {}; - expect(request.data.adUnits[0].params.features).to.deep.equal(expected); + const expected = {} + expect(request.data.adUnits[0].features).to.deep.equal(expected); + }); + + it('features params "adunit_position" should be computed even if DOM element is display:none', () => { + stubs.topGetComputedStyle.returns(computedStyleNone); + const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + expect(request.data.adUnits[0].features.adunit_position).to.equal('0x0'); + }); + + it('features params "viewport" should be computed even if window.innerWidth is not supported', () => { + sandbox.stub(top, 'innerWidth').value(undefined); + const requests = spec.buildRequests([Object.assign({}, bidRequests[0])], bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + expect(request.data.adUnits[0].features.viewport_dimensions).to.match(/^[\d]+x[\d]+$/); + }); + + it('AdUnit requested should have the correct sizes array depending on the config', () => { + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[1].data.adUnits[0]).to.have.property('mediaTypes'); + }); + + it('features params must be an object if featurejs is loaded', () => { + let requests = spec.buildRequests(bidRequests, bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + }); + + it('outerAdUnitElementId must be added when PostBid param has been set', () => { + top.ADAGIO = top.ADAGIO || {}; + top.ADAGIO.pbjsAdUnits = []; + + top.ADAGIO.pbjsAdUnits.push({ + code: bidRequestsWithPostBid[0].adUnitCode, + sizes: bidRequestsWithPostBid[0].sizes, + bids: [{ + bidder: bidRequestsWithPostBid[0].bidder, + params: bidRequestsWithPostBid[0].params + }] + }); + let requests = spec.buildRequests(bidRequestsWithPostBid, bidderRequest); + let request = requests[0]; + expect(request.data.adUnits[0].features).to.exist; + expect(request.data.adUnits[0].params.outerAdUnitElementId).to.exist; + }); + + it('generates a pageviewId if missing', () => { + window.top.ADAGIO = window.top.ADAGIO || {}; + delete window.top.ADAGIO.pageviewId; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + + expect(requests[0].data.pageviewId).to.exist.and.to.not.equal('_').and.to.not.equal(''); + expect(requests[0].data.pageviewId).to.equal(requests[1].data.pageviewId); + }); + + it('uses an existing pageviewId if present', () => { + window.top.ADAGIO = window.top.ADAGIO || {}; + window.top.ADAGIO.pageviewId = 'abc'; + + const requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests).to.have.lengthOf(2); + + expect(requests[0].data.pageviewId).to.equal('abc'); + expect(requests[1].data.pageviewId).to.equal('abc'); }); + it('should send the printNumber in features object', () => { + window.top.ADAGIO = window.top.ADAGIO || {}; + window.top.ADAGIO.pageviewId = 'abc'; + window.top.ADAGIO.adUnits['adunit-code1'] = { + pageviewId: 'abc', + printNumber: 2 + }; + const requests = spec.buildRequests([bidRequests[0]], bidderRequest); + const request = requests[0]; + expect(request.data.adUnits[0].features.print_number).to.equal('2'); + }) + it('GDPR consent is applied', () => { const requests = spec.buildRequests(bidRequests, bidderRequest); expect(requests).to.have.lengthOf(2); @@ -172,11 +456,30 @@ describe('adagioAdapter', () => { expect(request.data.gdpr).to.exist; expect(request.data.gdpr).to.be.empty; }); + + it('should expose version in window', () => { + expect(window.top.ADAGIO).ok; + expect(window.top.ADAGIO.versions).ok; + expect(window.top.ADAGIO.versions.adagioBidderAdapter).to.eq(VERSION); + }); + + it('should returns an empty array if the bidder cannot access to window top (based on refererInfo.reachedTop)', () => { + const requests = spec.buildRequests(bidRequests, { + ...bidderRequest, + refererInfo: { reachedTop: false } + }); + expect(requests).to.be.empty; + }); }); describe('interpretResponse', () => { + const sandbox = sinon.createSandbox(); + let serverResponse = { body: { + data: { + pred: 1 + }, bids: [ { ad: '
', @@ -193,27 +496,66 @@ describe('adagioAdapter', () => { } }; + let emptyBodyServerResponse = { + body: null + }; + + let withoutBidsArrayServerResponse = { + body: { + bids: [] + } + }; + + let serverResponseWhichThrowsException = { + body: { + data: { + pred: 1 + }, + bids: { + foo: 'bar' + } + } + }; + let bidRequest = { 'data': { 'adUnits': [ { 'bidder': 'adagio', 'params': { - siteId: '666', - placementId: 4, - pagetypeId: '232', - categories: ['IAB12'] + organizationId: '456', + site: 'ADAGIO-456', + placement: 'PAVE_ATF-456', + adUnitElementId: 'banner-atf-456', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'SITE-MOBILE' }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], 'bidId': 'c180kg4267tyqz', 'bidderRequestId': '8vfscuixrovn8i', 'auctionId': 'lel4fhp239i9km', + 'pageviewId': 'd8c4fl2k39i0wn', } ] } }; + afterEach(function() { + sandbox.restore(); + }); + + it('Should returns empty response if body is empty', () => { + expect(spec.interpretResponse(emptyBodyServerResponse, bidRequest)).to.be.an('array').length(0); + expect(spec.interpretResponse({body: {}}, bidRequest)).to.be.an('array').length(0); + }); + + it('Should returns empty response if bids array is empty', () => { + expect(spec.interpretResponse({withoutBidsArrayServerResponse}, bidRequest)).to.be.an('array').length(0); + }); + it('should get correct bid response', () => { let expectedResponse = [{ ad: '
', @@ -225,13 +567,35 @@ describe('adagioAdapter', () => { requestId: 'c180kg4267tyqz', ttl: 360, width: 300, - categories: [], - pagetypeId: '232', - placementId: 4, + placement: 'PAVE_ATF-456', + site: 'ADAGIO-456', + pagetype: 'ARTICLE', + category: 'NEWS', + subcategory: 'SPORT', + environment: 'SITE-MOBILE' }]; expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.deep.equal(expectedResponse); }); + + it('Should populate ADAGIO queue with ssp-data', () => { + spec.interpretResponse(serverResponse, bidRequest); + expect(window.top.ADAGIO).ok; + expect(window.top.ADAGIO.queue).to.be.an('array'); + }); + + it('Should not populate ADAGIO queue with ssp-data if not in top window', () => { + utils.getWindowTop().ADAGIO.queue = []; + sandbox.stub(utils, 'getWindowTop').throws(); + spec.interpretResponse(serverResponse, bidRequest); + expect(window.top.ADAGIO).ok; + expect(window.top.ADAGIO.queue).to.be.an('array'); + expect(window.top.ADAGIO.queue).empty; + }); + + it('should return an empty response even if an exception is ', () => { + expect(spec.interpretResponse(serverResponseWhichThrowsException, bidRequest)).to.be.an('array').length(0); + }); }); describe('getUserSyncs', () => { @@ -270,4 +634,64 @@ describe('adagioAdapter', () => { expect(emptyResult).to.equal(false); }); }); + + describe('getAdagioScript', () => { + const VALID_HASH = 'Lddcw3AADdQDrPtbRJkKxvA+o1CtScGDIMNRpHB3NnlC/FYmy/9RKXelKrYj/sjuWusl5YcOpo+lbGSkk655i8EKuDiOvK6ae/imxSrmdziIp+S/TA6hTFJXcB8k1Q9OIp4CMCT52jjXgHwX6G0rp+uYoCR25B1jHaHnpH26A6I='; + const INVALID_HASH = 'invalid'; + const VALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){};(_ADAGIO)();\n'; + const INVALID_SCRIPT_CONTENT = 'var _ADAGIO=function(){//corrupted};(_ADAGIO)();\n'; + const ADAGIO_LOCALSTORAGE_KEY = 'adagioScript'; + + it('should verify valid hash with valid script', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').once(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.equals('// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + utilsMock.verify(); + }); + + it('should verify valid hash with invalid script', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + VALID_HASH + '\n' + INVALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + + it('should verify invalid hash with valid script', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, '// hash: ' + INVALID_HASH + '\n' + VALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').once(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + + it('should verify missing hash', function () { + localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); + + utilsMock.expects('logInfo').withExactArgs('Start Adagio script').never(); + utilsMock.expects('logWarn').withExactArgs('No hash found in Adagio script').once(); + utilsMock.expects('logWarn').withExactArgs('Invalid Adagio script found').never(); + + getAdagioScript(); + + expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; + utilsMock.verify(); + }); + }); }); diff --git a/test/spec/modules/adformBidAdapter_spec.js b/test/spec/modules/adformBidAdapter_spec.js index 17eef06c603..80f89614c5e 100644 --- a/test/spec/modules/adformBidAdapter_spec.js +++ b/test/spec/modules/adformBidAdapter_spec.js @@ -258,11 +258,12 @@ describe('Adform adapter', function () { }; }); - it('should set a renderer for an outstream context', function () { - serverResponse.body = [serverResponse.body[3]]; - bidRequest.bids = [bidRequest.bids[6]]; + it('should set a renderer only for an outstream context', function () { + serverResponse.body = [serverResponse.body[3], serverResponse.body[2]]; + bidRequest.bids = [bidRequest.bids[6], bidRequest.bids[6]]; let result = spec.interpretResponse(serverResponse, bidRequest); assert.ok(result[0].renderer); + assert.equal(result[1].renderer, undefined); }); describe('verifySizes', function () { @@ -314,9 +315,9 @@ describe('Adform adapter', function () { beforeEach(function () { config.setConfig({ currency: {} }); - let sizes = [[250, 300], [300, 250], [300, 600]]; + let sizes = [[250, 300], [300, 250], [300, 600], [600, 300]]; let placementCode = ['div-01', 'div-02', 'div-03', 'div-04', 'div-05']; - let mediaTypes = [{video: {context: 'outstream'}}]; + let mediaTypes = [{video: {context: 'outstream'}, banner: {sizes: sizes[3]}}]; let params = [{ mid: 1, url: 'some// there' }, {adxDomain: null, mid: 2, someVar: 'someValue', pt: 'gross'}, { adxDomain: null, mid: 3, pdom: 'home' }, {mid: 5, pt: 'net'}, {mid: 6, pt: 'gross'}]; bids = [ { diff --git a/test/spec/modules/adheseBidAdapter_spec.js b/test/spec/modules/adheseBidAdapter_spec.js index 348fb772319..32658e2bb27 100644 --- a/test/spec/modules/adheseBidAdapter_spec.js +++ b/test/spec/modules/adheseBidAdapter_spec.js @@ -153,6 +153,16 @@ describe('AdheseAdapter', function () { mediaType: 'banner', netRevenue: NET_REVENUE, ttl: TTL, + adhese: { + originData: { + seatbid: [ + { + bid: [ { crid: '60613369', dealid: null } ], + seat: '958' + } + ] + } + } }]; expect(spec.interpretResponse(sspBannerResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -185,6 +195,7 @@ describe('AdheseAdapter', function () { mediaType: 'video', netRevenue: NET_REVENUE, ttl: TTL, + adhese: { originData: {} } }]; expect(spec.interpretResponse(sspVideoResponse, bidRequest)).to.deep.equal(expectedResponse); }); @@ -235,6 +246,17 @@ describe('AdheseAdapter', function () { let expectedResponse = [{ requestId: BID_ID, ad: '', + adhese: { + originData: { + adFormat: 'largeleaderboard', + adType: 'largeleaderboard', + adspaceId: '162363', + libId: '90511', + orderProperty: undefined, + priority: undefined, + viewableImpressionCounter: undefined + } + }, cpm: 5.96, currency: 'USD', creativeId: '742898', @@ -279,6 +301,17 @@ describe('AdheseAdapter', function () { let expectedResponse = [{ requestId: BID_ID, vastXml: '', + adhese: { + originData: { + adFormat: '', + adType: 'preroll', + adspaceId: '164196', + libId: '89860', + orderProperty: undefined, + priority: undefined, + viewableImpressionCounter: undefined + } + }, cpm: 0, currency: 'USD', creativeId: '742470', diff --git a/test/spec/modules/adkernelAdnBidAdapter_spec.js b/test/spec/modules/adkernelAdnBidAdapter_spec.js index 1147520131b..277faf2a351 100644 --- a/test/spec/modules/adkernelAdnBidAdapter_spec.js +++ b/test/spec/modules/adkernelAdnBidAdapter_spec.js @@ -74,7 +74,6 @@ describe('AdkernelAdn adapter', function () { bidderRequestId: 'req1', auctionId: '5c66da22-426a-4bac-b153-77360bef5337', bidId: 'bidid_5', - sizes: [[1920, 1080]], mediaTypes: { video: { playerSize: [1920, 1080], @@ -91,7 +90,6 @@ describe('AdkernelAdn adapter', function () { bidderRequestId: 'req-001', auctionId: 'auc-001', bidId: 'Bid_01', - sizes: [[300, 250], [300, 200]], mediaTypes: { banner: {sizes: [[300, 250], [300, 200]]}, video: {context: 'instream', playerSize: [[640, 480]]} diff --git a/test/spec/modules/adkernelBidAdapter_spec.js b/test/spec/modules/adkernelBidAdapter_spec.js index ddb3f3dddf2..5b3d76ef071 100644 --- a/test/spec/modules/adkernelBidAdapter_spec.js +++ b/test/spec/modules/adkernelBidAdapter_spec.js @@ -81,7 +81,6 @@ describe('Adkernel adapter', function () { bidId: 'Bid_Video', bidderRequestId: '18b2a61ea5d9a7', auctionId: 'de45acf1-9109-4e52-8013-f2b7cf5f6766', - sizes: [[640, 480]], params: { zoneId: 1, host: 'rtb.adkernel.com', @@ -103,7 +102,6 @@ describe('Adkernel adapter', function () { }, adUnitCode: 'ad-unit-1', transactionId: 'f82c64b8-c602-42a4-9791-4a268f6559ed', - sizes: [[300, 250], [300, 200]], bidId: 'Bid_01', bidderRequestId: 'req-001', auctionId: 'auc-001' @@ -163,9 +161,10 @@ describe('Adkernel adapter', function () { }; function buildBidderRequest(url = 'https://example.com/index.html', params = {}) { - return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}}) + return Object.assign({}, params, {refererInfo: {referer: url, reachedTop: true}, timeout: 3000}); } const DEFAULT_BIDDER_REQUEST = buildBidderRequest(); + function buildRequest(bidRequests, bidderRequest = DEFAULT_BIDDER_REQUEST, dnt = true) { let dntmock = sinon.stub(utils, 'getDNT').callsFake(() => dnt); let pbRequests = spec.buildRequests(bidRequests, bidderRequest); @@ -268,6 +267,12 @@ describe('Adkernel adapter', function () { let [_, bidRequests] = buildRequest([bid1_zone1], DEFAULT_BIDDER_REQUEST, false); expect(bidRequests[0].device).to.not.have.property('dnt'); }); + + it('should forward default bidder timeout', function() { + let [_, bidRequests] = buildRequest([bid1_zone1], DEFAULT_BIDDER_REQUEST); + let bidRequest = bidRequests[0]; + expect(bidRequests[0]).to.have.property('tmax', 3000); + }); }); describe('video request building', function () { @@ -379,8 +384,8 @@ describe('Adkernel adapter', function () { describe('adapter configuration', () => { it('should have aliases', () => { - expect(spec.aliases).to.have.lengthOf(5); - expect(spec.aliases).to.be.eql(['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak']); + expect(spec.aliases).to.have.lengthOf(6); + expect(spec.aliases).to.include.members(['headbidding', 'adsolut', 'oftmediahb', 'audiencemedia', 'waardex_ak', 'roqoon']); }); }); }); diff --git a/test/spec/modules/adpod_spec.js b/test/spec/modules/adpod_spec.js index 8b7701c5631..f8c5387b6ce 100644 --- a/test/spec/modules/adpod_spec.js +++ b/test/spec/modules/adpod_spec.js @@ -766,6 +766,83 @@ describe('adpod.js', function () { expect(storeStub.called).to.equal(false); expect(auctionBids.length).to.equal(1); }); + + it('should set deal tier in place of cpm when prioritzeDeals is true', function() { + config.setConfig({ + adpod: { + deferCaching: true, + brandCategoryExclusion: true, + prioritizeDeals: true, + dealTier: { + 'appnexus': { + 'prefix': 'tier', + 'minDealTier': 5 + } + } + } + }); + + let bidResponse1 = { + adId: 'adId01277', + auctionId: 'no_defer_123', + mediaType: 'video', + bidderCode: 'appnexus', + cpm: 5, + pbMg: '5.00', + adserverTargeting: { + hb_pb: '5.00' + }, + meta: { + adServerCatId: 'test' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15, + dealTier: 7 + } + }; + + let bidResponse2 = { + adId: 'adId46547', + auctionId: 'no_defer_123', + mediaType: 'video', + bidderCode: 'appnexus', + cpm: 12, + pbMg: '12.00', + adserverTargeting: { + hb_pb: '12.00' + }, + meta: { + adServerCatId: 'value' + }, + video: { + context: ADPOD, + durationSeconds: 15, + durationBucket: 15 + } + }; + + let bidderRequest = { + adUnitCode: 'adpod_1', + auctionId: 'no_defer_123', + mediaTypes: { + video: { + context: ADPOD, + playerSize: [300, 300], + adPodDurationSec: 300, + durationRangeSec: [15, 30, 45], + requireExactDuration: false + } + }, + }; + + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse1, afterBidAddedSpy, bidderRequest); + callPrebidCacheHook(callbackFn, auctionInstance, bidResponse2, afterBidAddedSpy, bidderRequest); + + expect(auctionBids[0].adserverTargeting.hb_pb_cat_dur).to.equal('tier7_test_15s'); + expect(auctionBids[1].adserverTargeting.hb_pb_cat_dur).to.equal('12.00_value_15s'); + }) }); describe('checkAdUnitSetupHook', function () { diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index 5bac9523b18..3e73479259c 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -1,400 +1,549 @@ -import { expect } from 'chai' -import * as url from 'src/url' -import { spec } from 'modules/adxcgBidAdapter' +import {expect} from 'chai'; +import * as url from 'src/url'; +import {spec} from 'modules/adxcgBidAdapter'; +import {deepClone} from '../../../src/utils'; describe('AdxcgAdapter', function () { - describe('isBidRequestValid', function () { - let bidBanner = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - } - - let bidVideo = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1', - 'api': [2], - 'protocols': [1, 2], - 'mimes': ['video/mp4', 'video/x-flv'], - 'maxduration': 30 - }, - 'mediaTypes': { - 'video': { - 'context': 'instream' + let bidBanner = { + bidder: 'adxcg', + params: { + adzoneid: '1' + }, + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [640, 360], + [1, 1] + ] + } + }, + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; + + let bidVideo = { + bidder: 'adxcg', + params: { + adzoneid: '20', + video: { + api: [2], + protocols: [1, 2], + mimes: ['video/mp4'], + maxduration: 30 + } + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]] + } + }, + adUnitCode: 'adunit-code', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; + + let bidNative = { + bidder: 'adxcg', + params: { + adzoneid: '2379' + }, + mediaTypes: { + native: { + image: { + sendId: false, + required: true, + sizes: [80, 80] + }, + title: { + required: true, + len: 75 + }, + body: { + required: true, + len: 200 + }, + sponsoredBy: { + required: false, + len: 20 } - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - } + } + }, + adUnitCode: 'adunit-code', + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; + + describe('isBidRequestValid', function () { + it('should return true when required params found bidNative', function () { + expect(spec.isBidRequestValid(bidNative)).to.equal(true); + }); + + it('should return true when required params found bidVideo', function () { + expect(spec.isBidRequestValid(bidVideo)).to.equal(true); + }); - it('should return true when required params found', function () { - expect(spec.isBidRequestValid(bidBanner)).to.equal(true) - }) + it('should return true when required params found bidBanner', function () { + expect(spec.isBidRequestValid(bidBanner)).to.equal(true); + }); it('should return true when required params not found', function () { - expect(spec.isBidRequestValid({})).to.be.false - }) + expect(spec.isBidRequestValid({})).to.be.false; + }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bidBanner) - delete bid.params - bid.params = {} - expect(spec.isBidRequestValid(bid)).to.equal(false) - }) + let bid = Object.assign({}, bidBanner); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); it('should return true when required video params not found', function () { - const simpleVideo = JSON.parse(JSON.stringify(bidVideo)) - simpleVideo.params.adzoneid = 123 - expect(spec.isBidRequestValid(simpleVideo)).to.be.false - simpleVideo.params.mimes = [1, 2, 3] - expect(spec.isBidRequestValid(simpleVideo)).to.be.false - simpleVideo.params.mimes = 'bad type' - expect(spec.isBidRequestValid(simpleVideo)).to.be.false - }) - }) + const simpleVideo = JSON.parse(JSON.stringify(bidVideo)); + simpleVideo.params.adzoneid = 123; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + simpleVideo.params.mimes = [1, 2, 3]; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + simpleVideo.params.mimes = 'bad type'; + expect(spec.isBidRequestValid(simpleVideo)).to.be.false; + }); + }); describe('request function http', function () { - let bid = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - } - - it('creates a valid adxcg request url', function () { - let request = spec.buildRequests([bid]) - expect(request).to.exist - expect(request.method).to.equal('GET') - let parsedRequestUrl = url.parse(request.url) - expect(parsedRequestUrl.hostname).to.equal('hbp.adxcg.net') - expect(parsedRequestUrl.pathname).to.equal('/get/adi') - - let query = parsedRequestUrl.search - expect(query.renderformat).to.equal('javascript') - expect(query.ver).to.equal('r20180703PB10') - expect(query.source).to.equal('pbjs10') - expect(query.pbjs).to.equal('$prebid.version$') - expect(query.adzoneid).to.equal('1') - expect(query.format).to.equal('300x250|640x360|1x1') - expect(query.jsonp).to.be.undefined - expect(query.prebidBidIds).to.equal('84ab500420319d') - }) - }) + it('creates a valid adxcg request url bidBanner', function () { + let request = spec.buildRequests([bidBanner]); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + let parsedRequestUrl = url.parse(request.url); + expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20191128PB30'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.adzoneid).to.equal('1'); + expect(query.format).to.equal('300x250|640x360|1x1'); + expect(query.jsonp).to.be.undefined; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + expect(query.bidfloors).to.equal('0'); + + expect(query).to.have.property('secure'); + expect(query).to.have.property('uw'); + expect(query).to.have.property('uh'); + expect(query).to.have.property('dpr'); + expect(query).to.have.property('bt'); + expect(query).to.have.property('cookies'); + expect(query).to.have.property('tz'); + expect(query).to.have.property('dt'); + expect(query).to.have.property('iob'); + expect(query).to.have.property('rndid'); + expect(query).to.have.property('ref'); + expect(query).to.have.property('url'); + }); + + it('creates a valid adxcg request url bidVideo', function () { + let request = spec.buildRequests([bidVideo]); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + let parsedRequestUrl = url.parse(request.url); + expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + // general part + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20191128PB30'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.adzoneid).to.equal('20'); + expect(query.format).to.equal('640x480'); + expect(query.jsonp).to.be.undefined; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + expect(query.bidfloors).to.equal('0'); + + expect(query).to.have.property('secure'); + expect(query).to.have.property('uw'); + expect(query).to.have.property('uh'); + expect(query).to.have.property('dpr'); + expect(query).to.have.property('bt'); + expect(query).to.have.property('cookies'); + expect(query).to.have.property('tz'); + expect(query).to.have.property('dt'); + expect(query).to.have.property('iob'); + expect(query).to.have.property('rndid'); + expect(query).to.have.property('ref'); + expect(query).to.have.property('url'); + + // video specific part + expect(query['video.maxduration.0']).to.equal('30'); + expect(query['video.mimes.0']).to.equal('video/mp4'); + expect(query['video.context.0']).to.equal('instream'); + }); + + it('creates a valid adxcg request url bidNative', function () { + let request = spec.buildRequests([bidNative]); + expect(request).to.exist; + expect(request.method).to.equal('GET'); + let parsedRequestUrl = url.parse(request.url); + expect(parsedRequestUrl.hostname).to.equal('hbps.adxcg.net'); + expect(parsedRequestUrl.pathname).to.equal('/get/adi'); + + let query = parsedRequestUrl.search; + expect(query.renderformat).to.equal('javascript'); + expect(query.ver).to.equal('r20191128PB30'); + expect(query.source).to.equal('pbjs10'); + expect(query.pbjs).to.equal('$prebid.version$'); + expect(query.adzoneid).to.equal('2379'); + expect(query.format).to.equal('0x0'); + expect(query.jsonp).to.be.undefined; + expect(query.prebidBidIds).to.equal('84ab500420319d'); + expect(query.bidfloors).to.equal('0'); + + expect(query).to.have.property('secure'); + expect(query).to.have.property('uw'); + expect(query).to.have.property('uh'); + expect(query).to.have.property('dpr'); + expect(query).to.have.property('bt'); + expect(query).to.have.property('cookies'); + expect(query).to.have.property('tz'); + expect(query).to.have.property('dt'); + expect(query).to.have.property('iob'); + expect(query).to.have.property('rndid'); + expect(query).to.have.property('ref'); + expect(query).to.have.property('url'); + }); + }); describe('gdpr compliance', function () { - let bid = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - } - it('should send GDPR Consent data if gdprApplies', function () { - let request = spec.buildRequests([bid], {gdprConsent: {gdprApplies: true, consentString: 'consentDataString'}}) - let parsedRequestUrl = url.parse(request.url) - let query = parsedRequestUrl.search + let request = spec.buildRequests([bidBanner], { + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString' + } + }); + let parsedRequestUrl = url.parse(request.url); + let query = parsedRequestUrl.search; - expect(query.gdpr).to.equal('1') - expect(query.gdpr_consent).to.equal('consentDataString') - }) + expect(query.gdpr).to.equal('1'); + expect(query.gdpr_consent).to.equal('consentDataString'); + }); it('should not send GDPR Consent data if gdprApplies is false or undefined', function () { - let request = spec.buildRequests([bid], { + let request = spec.buildRequests([bidBanner], { gdprConsent: { gdprApplies: false, consentString: 'consentDataString' } - }) - let parsedRequestUrl = url.parse(request.url) - let query = parsedRequestUrl.search + }); + let parsedRequestUrl = url.parse(request.url); + let query = parsedRequestUrl.search; - expect(query.gdpr).to.be.undefined - expect(query.gdpr_consent).to.be.undefined - }) - }) + expect(query.gdpr).to.be.undefined; + expect(query.gdpr_consent).to.be.undefined; + }); + }); describe('userid pubcid should be passed to querystring', function () { - let bid = [{ - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - }] + let bidderRequests = {}; + let bid = deepClone([bidBanner]); + bid[0].userId = {pubcid: 'pubcidabcd'}; + + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = url.parse(request.url); + let query = parsedRequestUrl.search; + expect(query.pubcid).to.equal('pubcidabcd'); + }); + }); + describe('userid tdid should be passed to querystring', function () { + let bid = deepClone([bidBanner]); let bidderRequests = {}; - bid[0].userId = {'pubcid': 'pubcidabcd'}; + bid[0].userId = {tdid: 'tdidabcd'}; it('should send pubcid if available', function () { - let request = spec.buildRequests(bid, bidderRequests) - let parsedRequestUrl = url.parse(request.url) - let query = parsedRequestUrl.search - expect(query.pubcid).to.equal('pubcidabcd') - }) - }) + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = url.parse(request.url); + let query = parsedRequestUrl.search; + expect(query.tdid).to.equal('tdidabcd'); + }); + }); - describe('userid tdid should be passed to querystring', function () { - let bid = [{ - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - }] + describe('userid id5id should be passed to querystring', function () { + let bid = deepClone([bidBanner]); + let bidderRequests = {}; + + bid[0].userId = {id5id: 'id5idsample'}; + it('should send pubcid if available', function () { + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = url.parse(request.url); + let query = parsedRequestUrl.search; + expect(query.id5id).to.equal('id5idsample'); + }); + }); + + describe('userid idl_env should be passed to querystring', function () { + let bid = deepClone([bidBanner]); let bidderRequests = {}; - bid[0].userId = {'tdid': 'tdidabcd'}; + bid[0].userId = {idl_env: 'idl_envsample'}; it('should send pubcid if available', function () { - let request = spec.buildRequests(bid, bidderRequests) - let parsedRequestUrl = url.parse(request.url) - let query = parsedRequestUrl.search - expect(query.tdid).to.equal('tdidabcd'); - }) - }) + let request = spec.buildRequests(bid, bidderRequests); + let parsedRequestUrl = url.parse(request.url); + let query = parsedRequestUrl.search; + expect(query.idl_env).to.equal('idl_envsample'); + }); + }); describe('response handler', function () { let BIDDER_REQUEST = { - 'bidder': 'adxcg', - 'params': { - 'adzoneid': '1' + bidder: 'adxcg', + params: { + adzoneid: '1' }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [640, 360], [1, 1]], - 'bidId': '84ab500420319d', - 'bidderRequestId': '7101db09af0db2', - 'auctionId': '1d1a030790a475', - } - - let BANNER_RESPONSE = - { - body: [{ - 'bidId': '84ab500420319d', - 'bidderCode': 'adxcg', - 'width': 300, - 'height': 250, - 'creativeId': '42', - 'cpm': 0.45, - 'currency': 'USD', - 'netRevenue': true, - 'ad': '' - }], - header: {'someheader': 'fakedata'} - } - - let BANNER_RESPONSE_WITHDEALID = - { - body: [{ - 'bidId': '84ab500420319d', - 'bidderCode': 'adxcg', - 'width': 300, - 'height': 250, - 'deal_id': '7722', - 'creativeId': '42', - 'cpm': 0.45, - 'currency': 'USD', - 'netRevenue': true, - 'ad': '' - }], - header: {'someheader': 'fakedata'} - } - - let VIDEO_RESPONSE = - { - body: [{ - 'bidId': '84ab500420319d', - 'bidderCode': 'adxcg', - 'width': 640, - 'height': 360, - 'creativeId': '42', - 'cpm': 0.45, - 'currency': 'USD', - 'netRevenue': true, - 'vastUrl': 'vastContentUrl' - }], - header: {'someheader': 'fakedata'} - } - - let NATIVE_RESPONSE = - { - body: [{ - 'bidId': '84ab500420319d', - 'bidderCode': 'adxcg', - 'width': 0, - 'height': 0, - 'creativeId': '42', - 'cpm': 0.45, - 'currency': 'USD', - 'netRevenue': true, - 'nativeResponse': { - 'assets': [{ - 'id': 1, - 'required': 0, - 'title': { - 'text': 'titleContent' - } - }, { - 'id': 2, - 'required': 0, - 'img': { - 'url': 'imageContent', - 'w': 600, - 'h': 600 - } - }, { - 'id': 3, - 'required': 0, - 'data': { - 'label': 'DESC', - 'value': 'descriptionContent' - } - }, { - 'id': 0, - 'required': 0, - 'data': { - 'label': 'SPONSORED', - 'value': 'sponsoredByContent' - } - }, { - 'id': 5, - 'required': 0, - 'icon': { - 'url': 'iconContent', - 'w': 400, - 'h': 400 + adUnitCode: 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [640, 360], + [1, 1] + ] + } + }, + bidId: '84ab500420319d', + bidderRequestId: '7101db09af0db2', + auctionId: '1d1a030790a475' + }; + + let BANNER_RESPONSE = { + body: [ + { + bidId: '84ab500420319d', + bidderCode: 'adxcg', + width: 300, + height: 250, + creativeId: '42', + cpm: 0.45, + currency: 'USD', + netRevenue: true, + ad: '' + } + ], + header: {someheader: 'fakedata'} + }; + + let BANNER_RESPONSE_WITHDEALID = { + body: [ + { + bidId: '84ab500420319d', + bidderCode: 'adxcg', + width: 300, + height: 250, + deal_id: '7722', + creativeId: '42', + cpm: 0.45, + currency: 'USD', + netRevenue: true, + ad: '' + } + ], + header: {someheader: 'fakedata'} + }; + + let VIDEO_RESPONSE = { + body: [ + { + bidId: '84ab500420319d', + bidderCode: 'adxcg', + width: 640, + height: 360, + creativeId: '42', + cpm: 0.45, + currency: 'USD', + netRevenue: true, + vastUrl: 'vastContentUrl' + } + ], + header: {someheader: 'fakedata'} + }; + + let NATIVE_RESPONSE = { + body: [ + { + bidId: '84ab500420319d', + bidderCode: 'adxcg', + width: 0, + height: 0, + creativeId: '42', + cpm: 0.45, + currency: 'USD', + netRevenue: true, + nativeResponse: { + assets: [ + { + id: 1, + required: 0, + title: { + text: 'titleContent' + } + }, + { + id: 2, + required: 0, + img: { + url: 'imageContent', + w: 600, + h: 600 + } + }, + { + id: 3, + required: 0, + data: { + label: 'DESC', + value: 'descriptionContent' + } + }, + { + id: 0, + required: 0, + data: { + label: 'SPONSORED', + value: 'sponsoredByContent' + } + }, + { + id: 5, + required: 0, + icon: { + url: 'iconContent', + w: 400, + h: 400 + } } - }], - 'link': { - 'url': 'linkContent' + ], + link: { + url: 'linkContent' }, - 'imptrackers': ['impressionTracker1', 'impressionTracker2'] + imptrackers: ['impressionTracker1', 'impressionTracker2'] } - }], - header: {'someheader': 'fakedata'} - } + } + ], + header: {someheader: 'fakedata'} + }; it('handles regular responses', function () { - let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST) - - expect(result).to.have.lengthOf(1) - - expect(result[0]).to.exist - expect(result[0].width).to.equal(300) - expect(result[0].height).to.equal(250) - expect(result[0].creativeId).to.equal(42) - expect(result[0].cpm).to.equal(0.45) - expect(result[0].ad).to.equal('') - expect(result[0].currency).to.equal('USD') - expect(result[0].netRevenue).to.equal(true) - expect(result[0].ttl).to.equal(300) - expect(result[0].dealId).to.not.exist - }) + let result = spec.interpretResponse(BANNER_RESPONSE, BIDDER_REQUEST); + + expect(result).to.have.lengthOf(1); + + expect(result[0]).to.exist; + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + expect(result[0].dealId).to.not.exist; + }); it('handles regular responses with dealid', function () { - let result = spec.interpretResponse(BANNER_RESPONSE_WITHDEALID, BIDDER_REQUEST) - - expect(result).to.have.lengthOf(1) - - expect(result[0].width).to.equal(300) - expect(result[0].height).to.equal(250) - expect(result[0].creativeId).to.equal(42) - expect(result[0].cpm).to.equal(0.45) - expect(result[0].ad).to.equal('') - expect(result[0].currency).to.equal('USD') - expect(result[0].netRevenue).to.equal(true) - expect(result[0].ttl).to.equal(300) - }) + let result = spec.interpretResponse( + BANNER_RESPONSE_WITHDEALID, + BIDDER_REQUEST + ); + + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].ad).to.equal(''); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + }); it('handles video responses', function () { - let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST) - expect(result).to.have.lengthOf(1) - - expect(result[0].width).to.equal(640) - expect(result[0].height).to.equal(360) - expect(result[0].mediaType).to.equal('video') - expect(result[0].creativeId).to.equal(42) - expect(result[0].cpm).to.equal(0.45) - expect(result[0].vastUrl).to.equal('vastContentUrl') - expect(result[0].currency).to.equal('USD') - expect(result[0].netRevenue).to.equal(true) - expect(result[0].ttl).to.equal(300) - }) + let result = spec.interpretResponse(VIDEO_RESPONSE, BIDDER_REQUEST); + expect(result).to.have.lengthOf(1); + + expect(result[0].width).to.equal(640); + expect(result[0].height).to.equal(360); + expect(result[0].mediaType).to.equal('video'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].vastUrl).to.equal('vastContentUrl'); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + }); it('handles native responses', function () { - let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST) - - expect(result[0].width).to.equal(0) - expect(result[0].height).to.equal(0) - expect(result[0].mediaType).to.equal('native') - expect(result[0].creativeId).to.equal(42) - expect(result[0].cpm).to.equal(0.45) - expect(result[0].currency).to.equal('USD') - expect(result[0].netRevenue).to.equal(true) - expect(result[0].ttl).to.equal(300) - - expect(result[0].native.clickUrl).to.equal('linkContent') - expect(result[0].native.impressionTrackers).to.deep.equal(['impressionTracker1', 'impressionTracker2']) - expect(result[0].native.title).to.equal('titleContent') - - expect(result[0].native.image.url).to.equal('imageContent') - expect(result[0].native.image.height).to.equal(600) - expect(result[0].native.image.width).to.equal(600) - - expect(result[0].native.icon.url).to.equal('iconContent') - expect(result[0].native.icon.height).to.equal(400) - expect(result[0].native.icon.width).to.equal(400) - - expect(result[0].native.body).to.equal('descriptionContent') - expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent') - }) + let result = spec.interpretResponse(NATIVE_RESPONSE, BIDDER_REQUEST); + + expect(result[0].width).to.equal(0); + expect(result[0].height).to.equal(0); + expect(result[0].mediaType).to.equal('native'); + expect(result[0].creativeId).to.equal(42); + expect(result[0].cpm).to.equal(0.45); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + expect(result[0].ttl).to.equal(300); + + expect(result[0].native.clickUrl).to.equal('linkContent'); + expect(result[0].native.impressionTrackers).to.deep.equal([ + 'impressionTracker1', + 'impressionTracker2' + ]); + expect(result[0].native.title).to.equal('titleContent'); + + expect(result[0].native.image.url).to.equal('imageContent'); + expect(result[0].native.image.height).to.equal(600); + expect(result[0].native.image.width).to.equal(600); + + expect(result[0].native.icon.url).to.equal('iconContent'); + expect(result[0].native.icon.height).to.equal(400); + expect(result[0].native.icon.width).to.equal(400); + + expect(result[0].native.body).to.equal('descriptionContent'); + expect(result[0].native.sponsoredBy).to.equal('sponsoredByContent'); + }); it('handles nobid responses', function () { - let response = [] - let bidderRequest = BIDDER_REQUEST + let response = []; + let bidderRequest = BIDDER_REQUEST; - let result = spec.interpretResponse(response, bidderRequest) - expect(result.length).to.equal(0) - }) - }) + let result = spec.interpretResponse(response, bidderRequest); + expect(result.length).to.equal(0); + }); + }); describe('getUserSyncs', function () { let syncoptionsIframe = { - 'iframeEnabled': 'true' - } + iframeEnabled: 'true' + }; it('should return iframe sync option', function () { - expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe') - expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal('//cdn.adxcg.net/pb-sync.html') - }) - }) -}) + expect(spec.getUserSyncs(syncoptionsIframe)[0].type).to.equal('iframe'); + expect(spec.getUserSyncs(syncoptionsIframe)[0].url).to.equal( + 'https://cdn.adxcg.net/pb-sync.html' + ); + }); + }); +}); diff --git a/test/spec/modules/aolBidAdapter_spec.js b/test/spec/modules/aolBidAdapter_spec.js index 8386c2c2462..0f99901bce4 100644 --- a/test/spec/modules/aolBidAdapter_spec.js +++ b/test/spec/modules/aolBidAdapter_spec.js @@ -495,6 +495,94 @@ describe('AolAdapter', function () { }); }); + describe('buildOpenRtbRequestData', () => { + const bid = { + params: { + id: 'bid-id', + imp: [] + } + }; + let euConsentRequiredStub; + + beforeEach(function () { + euConsentRequiredStub = sinon.stub(spec, 'isEUConsentRequired'); + }); + + afterEach(function () { + euConsentRequiredStub.restore(); + }); + + it('returns the basic bid info when regulation data is omitted', () => { + expect(spec.buildOpenRtbRequestData(bid)).to.deep.equal({ + id: 'bid-id', + imp: [] + }); + }); + + it('returns the basic bid info with gdpr data when gdpr consent data is included', () => { + let consentData = { + gdpr: { + consentString: 'someEUConsent' + } + }; + euConsentRequiredStub.returns(true); + expect(spec.buildOpenRtbRequestData(bid, consentData)).to.deep.equal({ + id: 'bid-id', + imp: [], + regs: { + ext: { + gdpr: 1 + } + }, + user: { + ext: { + consent: 'someEUConsent' + } + } + }); + }); + + it('returns the basic bid info with CCPA data when CCPA consent data is included', () => { + let consentData = { + uspConsent: 'someUSPConsent' + }; + expect(spec.buildOpenRtbRequestData(bid, consentData)).to.deep.equal({ + id: 'bid-id', + imp: [], + regs: { + ext: { + us_privacy: 'someUSPConsent' + } + } + }); + }); + + it('returns the basic bid info with GDPR and CCPA data when GDPR and CCPA consent data is included', () => { + let consentData = { + gdpr: { + consentString: 'someEUConsent' + }, + uspConsent: 'someUSPConsent' + }; + euConsentRequiredStub.returns(true); + expect(spec.buildOpenRtbRequestData(bid, consentData)).to.deep.equal({ + id: 'bid-id', + imp: [], + regs: { + ext: { + gdpr: 1, + us_privacy: 'someUSPConsent' + } + }, + user: { + ext: { + consent: 'someEUConsent' + } + } + }); + }); + }); + describe('getUserSyncs()', function () { let serverResponses; let bidResponse; @@ -545,36 +633,42 @@ describe('AolAdapter', function () { }); }); - describe('isConsentRequired()', function () { + describe('isEUConsentRequired()', function () { it('should return false when consentData object is not present', function () { - expect(spec.isConsentRequired(null)).to.be.false; + expect(spec.isEUConsentRequired(null)).to.be.false; }); it('should return true when gdprApplies equals true and consentString is not present', function () { let consentData = { - consentString: null, - gdprApplies: true + gdpr: { + consentString: null, + gdprApplies: true + } }; - expect(spec.isConsentRequired(consentData)).to.be.true; + expect(spec.isEUConsentRequired(consentData)).to.be.true; }); it('should return false when consentString is present and gdprApplies equals false', function () { let consentData = { - consentString: 'consent-string', - gdprApplies: false + gdpr: { + consentString: 'consent-string', + gdprApplies: false + } }; - expect(spec.isConsentRequired(consentData)).to.be.false; + expect(spec.isEUConsentRequired(consentData)).to.be.false; }); it('should return true when consentString is present and gdprApplies equals true', function () { let consentData = { - consentString: 'consent-string', - gdprApplies: true + gdpr: { + consentString: 'consent-string', + gdprApplies: true + } }; - expect(spec.isConsentRequired(consentData)).to.be.true; + expect(spec.isEUConsentRequired(consentData)).to.be.true; }); }); @@ -596,7 +690,7 @@ describe('AolAdapter', function () { expect(spec.formatMarketplaceDynamicParams()).to.be.equal(''); }); - it('should return formatted params when formatConsentData returns data', function () { + it('should return formatted EU consent params when formatConsentData returns GDPR data', function () { formatConsentDataStub.returns({ euconsent: 'test-consent', gdpr: 1 @@ -604,6 +698,23 @@ describe('AolAdapter', function () { expect(spec.formatMarketplaceDynamicParams()).to.be.equal('euconsent=test-consent;gdpr=1;'); }); + it('should return formatted US privacy params when formatConsentData returns USP data', function () { + formatConsentDataStub.returns({ + us_privacy: 'test-usp-consent' + }); + expect(spec.formatMarketplaceDynamicParams()).to.be.equal('us_privacy=test-usp-consent;'); + }); + + it('should return formatted EU and USP consent params when formatConsentData returns all data', function () { + formatConsentDataStub.returns({ + euconsent: 'test-consent', + gdpr: 1, + us_privacy: 'test-usp-consent' + }); + expect(spec.formatMarketplaceDynamicParams()).to.be.equal( + 'euconsent=test-consent;gdpr=1;us_privacy=test-usp-consent;'); + }); + it('should return formatted params when formatKeyValues returns data', function () { formatKeyValuesStub.returns({ param1: 'val1', @@ -622,16 +733,16 @@ describe('AolAdapter', function () { }); describe('formatOneMobileDynamicParams()', function () { - let consentRequiredStub; + let euConsentRequiredStub; let secureProtocolStub; beforeEach(function () { - consentRequiredStub = sinon.stub(spec, 'isConsentRequired'); + euConsentRequiredStub = sinon.stub(spec, 'isEUConsentRequired'); secureProtocolStub = sinon.stub(spec, 'isSecureProtocol'); }); afterEach(function () { - consentRequiredStub.restore(); + euConsentRequiredStub.restore(); secureProtocolStub.restore(); }); @@ -648,14 +759,35 @@ describe('AolAdapter', function () { expect(spec.formatOneMobileDynamicParams(params)).to.contain('¶m1=val1¶m2=val2¶m3=val3'); }); - it('should return formatted gdpr params when isConsentRequired returns true', function () { + it('should return formatted gdpr params when isEUConsentRequired returns true', function () { let consentData = { - consentString: 'test-consent' + gdpr: { + consentString: 'test-consent' + } }; - consentRequiredStub.returns(true); + euConsentRequiredStub.returns(true); expect(spec.formatOneMobileDynamicParams({}, consentData)).to.be.equal('&gdpr=1&euconsent=test-consent'); }); + it('should return formatted US privacy params when consentData contains USP data', function () { + let consentData = { + uspConsent: 'test-usp-consent' + }; + expect(spec.formatMarketplaceDynamicParams({}, consentData)).to.be.equal('us_privacy=test-usp-consent;'); + }); + + it('should return formatted EU and USP consent params when consentData contains gdpr and usp values', function () { + euConsentRequiredStub.returns(true); + let consentData = { + gdpr: { + consentString: 'test-consent' + }, + uspConsent: 'test-usp-consent' + }; + expect(spec.formatMarketplaceDynamicParams({}, consentData)).to.be.equal( + 'gdpr=1;euconsent=test-consent;us_privacy=test-usp-consent;'); + }); + it('should return formatted secure param when isSecureProtocol returns true', function () { secureProtocolStub.returns(true); expect(spec.formatOneMobileDynamicParams()).to.be.equal('&secure=1'); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index 762833f29b8..c1d307521a5 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -590,6 +590,24 @@ describe('AppNexusAdapter', function () { expect(payload.gdpr_consent.consent_required).to.exist.and.to.be.true; }); + it('should add us privacy string to payload', function() { + let consentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'appnexus', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': consentString + }; + bidderRequest.bids = bidRequests; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.us_privacy).to.exist; + expect(payload.us_privacy).to.exist.and.to.equal(consentString); + }); + it('supports sending hybrid mobile app parameters', function () { let appRequest = Object.assign({}, bidRequests[0], diff --git a/test/spec/modules/astraoneBidAdapter_spec.js b/test/spec/modules/astraoneBidAdapter_spec.js new file mode 100644 index 00000000000..cd80e742b7d --- /dev/null +++ b/test/spec/modules/astraoneBidAdapter_spec.js @@ -0,0 +1,210 @@ +import { expect } from 'chai' +import { spec } from 'modules/astraoneBidAdapter' + +function getSlotConfigs(mediaTypes, params) { + return { + params: params, + sizes: [], + bidId: '2df8c0733f284e', + bidder: 'astraone', + mediaTypes: mediaTypes, + transactionId: '31a58515-3634-4e90-9c96-f86196db1459' + } +} + +describe('AstraOne Adapter', function() { + describe('isBidRequestValid method', function() { + const PLACE_ID = '5af45ad34d506ee7acad0c26'; + const IMAGE_URL = 'https://creative.astraone.io/files/default_image-1-600x400.jpg'; + + describe('returns true', function() { + describe('when banner slot config has all mandatory params', () => { + describe('and placement has the correct value', function() { + const createBannerSlotConfig = placement => { + return getSlotConfigs( + { banner: {} }, + { + placeId: PLACE_ID, + imageUrl: IMAGE_URL, + placement + } + ) + } + const placements = ['inImage']; + placements.forEach(placement => { + it('should be ' + placement, function() { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig(placement) + ) + expect(isBidRequestValid).to.equal(true) + }) + }) + }) + }) + }) + describe('returns false', function() { + describe('when params are not correct', function() { + function createSlotconfig(params) { + return getSlotConfigs({ banner: {} }, params) + } + it('does not have the placeId.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + imageUrl: IMAGE_URL, + placement: 'inImage' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the imageUrl.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID, + placement: 'inImage' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have the placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID, + imageUrl: IMAGE_URL, + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + it('does not have a the correct placement.', function() { + const isBidRequestValid = spec.isBidRequestValid( + createSlotconfig({ + placeId: PLACE_ID, + imageUrl: IMAGE_URL, + placement: 'something' + }) + ) + expect(isBidRequestValid).to.equal(false) + }) + }) + }) + }) + + describe('buildRequests method', function() { + const bidderRequest = { + refererInfo: { referer: 'referer' } + } + const mandatoryParams = { + placeId: '5af45ad34d506ee7acad0c26', + imageUrl: 'https://creative.astraone.io/files/default_image-1-600x400.jpg', + placement: 'inImage' + } + const validBidRequests = [ + getSlotConfigs({ banner: {} }, mandatoryParams) + ] + it('Url params should be correct ', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + expect(request.method).to.equal('POST') + expect(request.url).to.equal('https://ssp.astraone.io/auction/prebid') + }) + + it('Common data request should be correct', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(Array.isArray(data.bidRequests)).to.equal(true) + data.bidRequests.forEach(bid => { + expect(bid.placeId).to.equal('5af45ad34d506ee7acad0c26') + expect(typeof bid.imageUrl).to.equal('string') + }) + }) + + describe('GDPR params', function() { + describe('when there are not consent management platform', function() { + it('cmp should be false', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(false) + }) + }) + describe('when there are consent management platform', function() { + it('cmps should be true and ga should not sended, when gdprApplies is undefined', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: undefined, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(Object.keys(data).indexOf('data')).to.equal(-1) + expect(data.cs).to.equal('consentString') + }) + it('cmps should be true and all gdpr parameters should be sended, when there are gdprApplies', function() { + bidderRequest['gdprConsent'] = { + gdprApplies: true, + consentString: 'consentString' + } + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + expect(data.cmp).to.equal(true) + expect(data.ga).to.equal(true) + expect(data.cs).to.equal('consentString') + }) + }) + }) + + describe('BidRequests params', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest) + const data = JSON.parse(request.data) + const bidRequests = data.bidRequests + it('should request a Banner', function() { + const bannerBid = bidRequests[0] + expect(bannerBid.bidId).to.equal('2df8c0733f284e') + expect(bannerBid.transactionId).to.equal('31a58515-3634-4e90-9c96-f86196db1459') + expect(bannerBid.placeId).to.equal('5af45ad34d506ee7acad0c26') + }) + }) + }) + + describe('interpret response method', function() { + it('should return a void array, when the server response have not got bids.', function() { + const serverResponse = { + body: [] + } + const bids = spec.interpretResponse(serverResponse) + expect(Array.isArray(bids)).to.equal(true) + expect(bids.length).to.equal(0) + }) + describe('when the server response return a bid', function() { + describe('the bid is a banner', function() { + it('should return a banner bid', function() { + const serverResponse = { + body: [ + { + bidId: '2df8c0733f284e', + price: 0.5, + currency: 'USD', + content: { + content: 'html', + actionUrls: {}, + seanceId: '123123' + }, + width: 100, + height: 100, + ttl: 360 + } + ] + } + const bids = spec.interpretResponse(serverResponse) + expect(bids.length).to.equal(1) + expect(bids[0].requestId).to.equal('2df8c0733f284e') + expect(bids[0].creativeId).to.equal('123123') + expect(bids[0].cpm).to.equal(0.5) + expect(bids[0].width).to.equal(100) + expect(bids[0].height).to.equal(100) + expect(bids[0].currency).to.equal('USD') + expect(bids[0].netRevenue).to.equal(true) + expect(typeof bids[0].ad).to.equal('string') + expect(typeof bids[0].content).to.equal('object') + }) + }) + }) + }) +}) diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js index 73186250f5b..14ed4d3024d 100644 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ b/test/spec/modules/brightcomBidAdapter_spec.js @@ -45,10 +45,11 @@ describe('brightcomBidAdapter', function() { 'publisherId': 1234567 }, 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '5fb26ac22bde4', 'bidderRequestId': '4bf93aeb730cb9', 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e' @@ -71,10 +72,11 @@ describe('brightcomBidAdapter', function() { 'publisherId': 1234567 }, 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, 'bidId': '5fb26ac22bde4', 'bidderRequestId': '4bf93aeb730cb9', 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', @@ -84,7 +86,7 @@ describe('brightcomBidAdapter', function() { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when tagid not passed correctly', function () { + it('should return false when publisherId not passed correctly', function () { bid.params.publisherId = undefined; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -114,7 +116,7 @@ describe('brightcomBidAdapter', function() { }); it('accepts a single array as a size', function() { - bidRequests[0].sizes = [300, 250]; + bidRequests[0].mediaTypes.banner.sizes = [300, 250]; const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]); @@ -169,7 +171,7 @@ describe('brightcomBidAdapter', function() { context('when width or height of the element is zero', function() { it('try to use alternative values', function() { Object.assign(element, { width: 0, height: 0 }); - bidRequests[0].sizes = [[800, 2400]]; + bidRequests[0].mediaTypes.banner.sizes = [[800, 2400]]; const request = spec.buildRequests(bidRequests); const payload = JSON.parse(request.data); expect(payload.imp[0].banner.ext.viewability).to.equal(25); diff --git a/test/spec/modules/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js new file mode 100644 index 00000000000..d7250eeb941 --- /dev/null +++ b/test/spec/modules/britepoolIdSystem_spec.js @@ -0,0 +1,67 @@ +import { expect } from 'chai'; +import {britepoolIdSubmodule} from 'modules/britepoolIdSystem'; + +describe('BritePool Submodule', () => { + const api_key = '1111'; + const aaid = '4421ea96-34a9-45df-a4ea-3c41a48a18b1'; + const idfa = '2d1c4fac-5507-4e28-991c-ca544e992dba'; + const bpid = '279c0161-5152-487f-809e-05d7f7e653fd'; + const url_override = 'https://override'; + const getter_override = function(params) { + return JSON.stringify({ 'primaryBPID': bpid }); + }; + const getter_callback_override = function(params) { + return callback => { + callback(JSON.stringify({ 'primaryBPID': bpid })); + }; + }; + + it('sends x-api-key in header and one identifier', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid }); + assert(errors.length === 0, errors); + expect(headers['x-api-key']).to.equal(api_key); + expect(params).to.eql({ aaid }); + }); + + it('sends x-api-key in header and two identifiers', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, idfa }); + assert(errors.length === 0, errors); + expect(headers['x-api-key']).to.equal(api_key); + expect(params).to.eql({ aaid, idfa }); + }); + + it('allows call without api_key', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ aaid, idfa }); + expect(params).to.eql({ aaid, idfa }); + expect(errors.length).to.equal(0); + }); + + it('test url override', () => { + const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override }); + expect(url).to.equal(url_override); + // Making sure it did not become part of params + expect(params.url).to.be.undefined; + }); + + it('test getter override with value', () => { + const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_override }); + expect(getter).to.equal(getter_override); + // Making sure it did not become part of params + expect(params.getter).to.be.undefined; + const response = britepoolIdSubmodule.getId({ api_key, aaid, url: url_override, getter: getter_override }); + assert.deepEqual(response, { id: { 'primaryBPID': bpid } }); + }); + + it('test getter override with callback', done => { + const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_callback_override }); + expect(getter).to.equal(getter_callback_override); + // Making sure it did not become part of params + expect(params.getter).to.be.undefined; + const response = britepoolIdSubmodule.getId({ api_key, aaid, url: url_override, getter: getter_callback_override }); + expect(response.callback).to.not.be.undefined; + response.callback(result => { + assert.deepEqual(result, { 'primaryBPID': bpid }); + done(); + }); + }); +}); diff --git a/test/spec/modules/colossussspBidAdapter_spec.js b/test/spec/modules/colossussspBidAdapter_spec.js index 62b4158676e..9ed2dbe6e6b 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -11,9 +11,41 @@ describe('ColossussspAdapter', function () { }, placementCode: 'placementid_0', auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - sizes: [[300, 250]], - transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62' + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + transactionId: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'example.com', + sid: '0', + hp: 1, + rid: 'bidrequestid', + // name: 'alladsallthetime', + domain: 'example.com' + } + ] + } }; + let bidderRequest = { + bidderCode: 'colossus', + auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', + bidderRequestId: 'ffffffffffffff', + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000, + uspConsent: '1YN-', + refererInfo: { + referer: 'http://www.example.com', + reachedTop: true, + }, + bids: [bid] + } describe('isBidRequestValid', function () { it('Should return true when placement_id can be cast to a number', function () { @@ -26,7 +58,7 @@ describe('ColossussspAdapter', function () { }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid]); + let serverRequest = spec.buildRequests([bid], bidderRequest); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -37,12 +69,16 @@ describe('ColossussspAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('//colossusssp.com/?c=o&m=multi'); + expect(serverRequest.url).to.equal('https://colossusssp.com/?c=o&m=multi'); }); + it('Should contain ccpa', function() { + expect(serverRequest.data.ccpa).to.be.an('string') + }) + it('Returns valid data if array of bids is valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); @@ -52,7 +88,8 @@ describe('ColossussspAdapter', function () { let placements = data['placements']; for (let i = 0; i < placements.length; i++) { let placement = placements[i]; - expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes'); + expect(placement).to.have.all.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); + expect(placement.schain).to.be.an('object') expect(placement.placementId).to.be.a('number'); expect(placement.bidId).to.be.a('string'); expect(placement.traffic).to.be.a('string'); @@ -112,7 +149,7 @@ describe('ColossussspAdapter', function () { expect(userSync[0].type).to.exist; expect(userSync[0].url).to.exist; expect(userSync[0].type).to.be.equal('image'); - expect(userSync[0].url).to.be.equal('//colossusssp.com/?c=o&m=cookie'); + expect(userSync[0].url).to.be.equal('https://colossusssp.com/?c=o&m=cookie'); }); }); }); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js new file mode 100644 index 00000000000..d757ea0b86f --- /dev/null +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -0,0 +1,335 @@ +import { + setConsentConfig, + requestBidsHook, + resetConsentData, + consentAPI, + consentTimeout +} from 'modules/consentManagementUsp'; +import * as utils from 'src/utils'; +import { config } from 'src/config'; +import { uspDataHandler } from 'src/adapterManager'; + +let assert = require('chai').assert; +let expect = require('chai').expect; + +function createIFrameMarker() { + var ifr = document.createElement('iframe'); + ifr.width = 0; + ifr.height = 0; + ifr.name = '__uspapiLocator'; + document.body.appendChild(ifr); + return ifr; +} + +describe('consentManagement', function () { + describe('setConsentConfig tests:', function () { + describe('empty setConsentConfig value', function () { + beforeEach(function () { + sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); + }); + + afterEach(function () { + utils.logInfo.restore(); + utils.logWarn.restore(); + config.resetConfig(); + resetConsentData(); + }); + + it('should not run if no config', function () { + setConsentConfig({}); + expect(consentAPI).to.be.undefined; + expect(consentTimeout).to.be.undefined; + sinon.assert.callCount(utils.logWarn, 1); + }); + + it('should use system default values', function () { + setConsentConfig({usp: {}}); + expect(consentAPI).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(50); + sinon.assert.callCount(utils.logInfo, 3); + }); + + it('should exit the consent manager if config.usp is not an object', function() { + setConsentConfig({}); + expect(consentAPI).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + sinon.assert.notCalled(utils.logInfo); + }); + + it('should exit the consent manager if only config.gdpr is an object', function() { + setConsentConfig({ gdpr: { cmpApi: 'iab' } }); + expect(consentAPI).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + sinon.assert.notCalled(utils.logInfo); + }); + }); + + describe('valid setConsentConfig value', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('results in all user settings overriding system defaults', function () { + let allConfig = { + usp: { + cmpApi: 'daa', + timeout: 7500 + } + }; + + setConsentConfig(allConfig); + expect(consentAPI).to.be.equal('daa'); + expect(consentTimeout).to.be.equal(7500); + }); + }); + }); + + describe('requestBidsHook tests:', function () { + let goodConfig = { + usp: { + cmpApi: 'iab', + timeout: 7500 + } + }; + + let noConfig = {}; + + let didHookReturn; + + afterEach(function () { + uspDataHandler.consentData = null; + resetConsentData(); + }); + + describe('error checks:', function () { + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logWarn'); + sinon.stub(utils, 'logError'); + }); + + afterEach(function () { + utils.logWarn.restore(); + utils.logError.restore(); + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + resetConsentData(); + }); + + it('should throw a warning and return to hooked function when an unknown USPAPI framework ID is used', function () { + let badCMPConfig = { usp: { cmpApi: 'bad' } }; + setConsentConfig(badCMPConfig); + expect(consentAPI).to.be.equal(badCMPConfig.usp.cmpApi); + requestBidsHook(() => { didHookReturn = true; }, {}); + let consent = uspDataHandler.getConsentData(); + sinon.assert.calledOnce(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent).to.be.null; + }); + + it('should throw proper errors when USP config is not found', function () { + setConsentConfig(noConfig); + requestBidsHook(() => { didHookReturn = true; }, {}); + let consent = uspDataHandler.getConsentData(); + // throw 2 warnings; one for no bidsBackHandler and for CMP not being found (this is an error due to gdpr config) + sinon.assert.calledTwice(utils.logWarn); + expect(didHookReturn).to.be.true; + expect(consent).to.be.null; + }); + }); + + describe('already known consentData:', function () { + let uspStub = sinon.stub(); + let ifr = null; + + beforeEach(function () { + didHookReturn = false; + ifr = createIFrameMarker(); + window.__uspapi = function() {}; + }); + + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + uspStub.restore(); + document.body.removeChild(ifr); + delete window.__uspapi; + resetConsentData(); + }); + + it('should bypass CMP and simply use previously stored consentData', function () { + let testConsentData = { + uspString: '1YY' + }; + + uspStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfig); + requestBidsHook(() => {}, {}); + uspStub.restore(); + + // reset the stub to ensure it wasn't called during the second round of calls + uspStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + requestBidsHook(() => { didHookReturn = true; }, {}); + + let consent = uspDataHandler.getConsentData(); + expect(didHookReturn).to.be.true; + expect(consent).to.equal(testConsentData.uspString); + sinon.assert.notCalled(uspStub); + }); + }); + + describe('USPAPI workflow for iframed page', function () { + let ifr = null; + let stringifyResponse = false; + + beforeEach(function () { + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + ifr = createIFrameMarker(); + window.addEventListener('message', uspapiMessageHandler, false); + }); + + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + delete window.__uspapi; + utils.logError.restore(); + utils.logWarn.restore(); + resetConsentData(); + document.body.removeChild(ifr); + window.removeEventListener('message', uspapiMessageHandler); + }); + + function uspapiMessageHandler(event) { + if (event && event.data) { + var data = event.data; + if (data.__uspapiCall) { + var callId = data.__uspapiCall.callId; + var response = { + __uspapiReturn: { + callId, + returnValue: { uspString: '1YY' }, + success: true + } + }; + event.source.postMessage(stringifyResponse ? JSON.stringify(response) : response, '*'); + } + } + } + + // Run tests with JSON response and String response + // from CMP window postMessage listener. + testIFramedPage('with/JSON response', false); + // testIFramedPage('with/String response', true); + + function testIFramedPage(testName, messageFormatString) { + it(`should return the consent string from a postmessage + addEventListener response - ${testName}`, (done) => { + stringifyResponse = messageFormatString; + setConsentConfig(goodConfig); + requestBidsHook(() => { + let consent = uspDataHandler.getConsentData(); + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + expect(consent).to.equal('1YY'); + done(); + }, {}); + }); + } + }); + + describe('test without iframe locater', function() { + let uspapiStub = sinon.stub(); + + beforeEach(function () { + didHookReturn = false; + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + window.__uspapi = function() {}; + }); + + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + uspapiStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + delete window.__uspapi; + resetConsentData(); + }); + + it('Workflow for normal page withoout iframe locater', function() { + let testConsentData = { + uspString: '1NY' + }; + + uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfig); + requestBidsHook(() => { didHookReturn = true; }, {}); + + let consent = uspDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + expect(didHookReturn).to.be.true; + expect(consent).to.equal(testConsentData.uspString); + }); + }); + + describe('USPAPI workflow for normal pages:', function () { + let uspapiStub = sinon.stub(); + let ifr = null; + + beforeEach(function () { + didHookReturn = false; + ifr = createIFrameMarker(); + sinon.stub(utils, 'logError'); + sinon.stub(utils, 'logWarn'); + window.__uspapi = function() {}; + }); + + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + uspapiStub.restore(); + utils.logError.restore(); + utils.logWarn.restore(); + document.body.removeChild(ifr); + delete window.__uspapi; + resetConsentData(); + }); + + it('performs lookup check and stores consentData for a valid existing user', function () { + let testConsentData = { + uspString: '1NY' + }; + + uspapiStub = sinon.stub(window, '__uspapi').callsFake((...args) => { + args[2](testConsentData, true); + }); + + setConsentConfig(goodConfig); + requestBidsHook(() => { didHookReturn = true; }, {}); + + let consent = uspDataHandler.getConsentData(); + + sinon.assert.notCalled(utils.logWarn); + sinon.assert.notCalled(utils.logError); + + expect(didHookReturn).to.be.true; + expect(consent).to.equal(testConsentData.uspString); + }); + }); + }); +}); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index 6be96427750..9731164c655 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -11,11 +11,14 @@ describe('consentManagement', function () { describe('empty setConsentConfig value', function () { beforeEach(function () { sinon.stub(utils, 'logInfo'); + sinon.stub(utils, 'logWarn'); }); afterEach(function () { utils.logInfo.restore(); + utils.logWarn.restore(); config.resetConfig(); + resetConsentData(); }); it('should use system default values', function () { @@ -25,6 +28,18 @@ describe('consentManagement', function () { expect(allowAuction).to.be.true; sinon.assert.callCount(utils.logInfo, 4); }); + + it('should exit consent manager if config is not an object', function() { + setConsentConfig(''); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); + + it('should exit consent manager if gdpr not set with new config structure', function() { + setConsentConfig({ usp: { cmpApi: 'iab', timeout: 50 } }); + expect(userCMP).to.be.undefined; + sinon.assert.calledOnce(utils.logWarn); + }); }); describe('valid setConsentConfig value', function () { @@ -32,6 +47,7 @@ describe('consentManagement', function () { config.resetConfig(); $$PREBID_GLOBAL$$.requestBids.removeAll(); }); + it('results in all user settings overriding system defaults', function () { let allConfig = { cmpApi: 'iab', @@ -44,6 +60,57 @@ describe('consentManagement', function () { expect(consentTimeout).to.be.equal(7500); expect(allowAuction).to.be.false; }); + + it('should use new consent manager config structure for gdpr', function() { + setConsentConfig({ + gdpr: { cmpApi: 'daa', timeout: 8700 } + }); + + expect(userCMP).to.be.equal('daa'); + expect(consentTimeout).to.be.equal(8700); + }); + + it('should ignore config.usp and use config.gdpr, with default cmpApi', function() { + setConsentConfig({ + gdpr: { timeout: 5000 }, + usp: { cmpApi: 'daa', timeout: 50 } + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(5000); + }); + + it('should ignore config.usp and use config.gdpr, with default cmpAip and timeout', function() { + setConsentConfig({ + gdpr: {}, + usp: { cmpApi: 'daa', timeout: 50 } + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + }); + + it('should recognize config.gdpr, with default cmpAip and timeout', function() { + setConsentConfig({ + gdpr: {} + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(10000); + }); + + it('should fallback to old consent manager config object if no config.gdpr', function() { + setConsentConfig({ + cmpApi: 'iab', + timeout: 3333, + allowAuctionWithoutConsent: false, + gdpr: false + }); + + expect(userCMP).to.be.equal('iab'); + expect(consentTimeout).to.be.equal(3333); + expect(allowAuction).to.be.equal(false); + }); }); describe('static consent string setConsentConfig value', () => { diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index fc5e1d1b45a..7785454f8e1 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -1,58 +1,127 @@ -import { expect } from 'chai'; -import { spec } from 'modules/consumableBidAdapter'; -import { createBid } from 'src/bidfactory'; +import {expect} from 'chai'; +import {spec} from 'modules/consumableBidAdapter'; +import {createBid} from 'src/bidfactory'; const ENDPOINT = 'https://e.serverbid.com/api/v2'; const SMARTSYNC_CALLBACK = 'serverbidCallBids'; -const REQUEST = { - 'bidderCode': 'consumable', - 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d', - 'bidderRequestId': '109f2a181342a9', - 'bidRequest': [{ - 'bidder': 'consumable', - 'params': { - 'networkId': 9969, - 'siteId': 730181, - 'unitId': 123456, - 'unitName': 'cnsmbl-unit' - }, - 'placementCode': 'div-gpt-ad-1487778092495-0', - 'sizes': [ - [728, 90], - [970, 90] - ], - 'bidId': '2b0f82502298c9', - 'bidderRequestId': '109f2a181342a9', - 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' +const BIDDER_REQUEST_1 = { + bidderCode: 'consumable', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + bidRequest: [ + { + bidder: 'consumable', + params: { + networkId: '9969', + siteId: '730181', + unitId: '123456', + unitName: 'cnsmbl-unit' + }, + placementCode: 'header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + bidId: '23acc48ad47af5', + auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', + bidderRequestId: '1c56ad30b9b8ca8', + transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' + } + ], + gdprConsent: { + consentString: 'consent-test', + gdprApplies: false }, - { - 'bidder': 'consumable', - 'params': { - 'networkId': 9969, - 'siteId': 730181, - 'unitId': 123456, - 'unitName': 'cnsmbl-unit' + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ] + } +}; + +const BIDDER_REQUEST_2 = { + bidderCode: 'consumable', + auctionId: 'a4713c32-3762-4798-b342-4ab810ca770d', + bidderRequestId: '109f2a181342a9', + bidRequest: [ + { + bidder: 'consumable', + params: { + networkId: 9969, + siteId: 730181, + unitId: 123456, + unitName: 'cnsmbl-unit' + }, + placementCode: 'div-gpt-ad-1487778092495-0', + mediaTypes: { + banner: { + sizes: [ + [728, 90], + [970, 90] + ] + } + }, + bidId: '2b0f82502298c9', + bidderRequestId: '109f2a181342a9', + auctionId: 'a4713c32-3762-4798-b342-4ab810ca770d' }, - 'placementCode': 'div-gpt-ad-1487778092495-0', - 'sizes': [ - [728, 90], - [970, 90] - ], - 'bidId': '123', - 'bidderRequestId': '109f2a181342a9', - 'auctionId': 'a4713c32-3762-4798-b342-4ab810ca770d' - }], - 'gdprConsent': { - 'consentString': 'consent-test', - 'gdprApplies': true + { + bidder: 'consumable', + params: { + networkId: 9969, + siteId: 730181, + unitId: 123456, + unitName: 'cnsmbl-unit' + }, + placementCode: 'div-gpt-ad-1487778092495-0', + mediaTypes: { + banner: { + sizes: [ + [728, 90], + [970, 90] + ] + } + }, + bidId: '123', + bidderRequestId: '109f2a181342a9', + auctionId: 'a4713c32-3762-4798-b342-4ab810ca770d' + } + ], + gdprConsent: { + consentString: 'consent-test', + gdprApplies: true }, - 'start': 1487883186070, - 'auctionStart': 1487883186069, - 'timeout': 3000 + refererInfo: { + referer: 'http://example.com/page.html', + reachedTop: true, + numIframes: 2, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ] + } }; -const RESPONSE = { +const BIDDER_REQUEST_EMPTY = { + bidderCode: 'consumable', + auctionId: 'b06458ef-4fe5-4a0b-a61b-bccbcedb7b11', + bidderRequestId: '8c8006750b10fd', + bidRequest: [], + gdprConsent: { + consentString: 'consent-test', + gdprApplies: false + } +}; + +const AD_SERVER_RESPONSE = { 'headers': null, 'body': { 'user': { 'key': 'ue1-2d33e91b71e74929b4aeecc23f4376f1' }, @@ -108,30 +177,17 @@ const RESPONSE = { } }; +const BUILD_REQUESTS_OUTPUT = { + method: 'POST', + url: 'https://e.serverbid.com/api/v2', + data: '', + bidRequest: BIDDER_REQUEST_2.bidRequest, + bidderRequest: BIDDER_REQUEST_2 +}; + describe('Consumable BidAdapter', function () { - let bidRequests; let adapter = spec; - beforeEach(function () { - bidRequests = [ - { - bidder: 'consumable', - params: { - networkId: '9969', - siteId: '730181', - unitId: '123456', - unitName: 'cnsmbl-unit' - }, - placementCode: 'header-bid-tag-1', - sizes: [[300, 250], [300, 600]], - bidId: '23acc48ad47af5', - auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', - bidderRequestId: '1c56ad30b9b8ca8', - transactionId: '92489f71-1bf2-49a0-adf9-000cea934729' - } - ]; - }); - describe('bid request validation', function () { it('should accept valid bid requests', function () { let bid = { @@ -187,43 +243,49 @@ describe('Consumable BidAdapter', function () { describe('buildRequests validation', function () { it('creates request data', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); expect(request).to.exist.and.to.be.a('object'); }); it('request to consumable should contain a url', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); expect(request.url).to.have.string('serverbid.com'); }); it('requires valid bids to make request', function () { - let request = spec.buildRequests([]); + let request = spec.buildRequests(BIDDER_REQUEST_EMPTY.bidRequest, BIDDER_REQUEST_EMPTY); expect(request.bidRequest).to.be.empty; }); it('sends bid request to ENDPOINT via POST', function () { - let request = spec.buildRequests(bidRequests); + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); expect(request.method).to.have.string('POST'); }); + + it('passes through bidderRequest', function () { + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); + + expect(request.bidderRequest).to.equal(BIDDER_REQUEST_1); + }) }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { - let bidRequest = spec.buildRequests(REQUEST.bidRequest, REQUEST); + let bidRequest = spec.buildRequests(BIDDER_REQUEST_2.bidRequest, BIDDER_REQUEST_2); let bid = createBid(1, bidRequest.bidRequest[0]); expect(bid.bidderCode).to.equal('consumable'); }); it('response should include objects for all bids', function () { - let bids = spec.interpretResponse(RESPONSE, REQUEST); + let bids = spec.interpretResponse(AD_SERVER_RESPONSE, BUILD_REQUESTS_OUTPUT); expect(bids.length).to.equal(2); }); it('registers bids', function () { - let bids = spec.interpretResponse(RESPONSE, REQUEST); + let bids = spec.interpretResponse(AD_SERVER_RESPONSE, BUILD_REQUESTS_OUTPUT); bids.forEach(b => { expect(b).to.have.property('cpm'); expect(b.cpm).to.be.above(0); @@ -243,14 +305,14 @@ describe('Consumable BidAdapter', function () { }); it('handles nobid responses', function () { - let EMPTY_RESP = Object.assign({}, RESPONSE, {'body': {'decisions': null}}) - let bids = spec.interpretResponse(EMPTY_RESP, REQUEST); + let EMPTY_RESP = Object.assign({}, AD_SERVER_RESPONSE, {'body': {'decisions': null}}) + let bids = spec.interpretResponse(EMPTY_RESP, BUILD_REQUESTS_OUTPUT); expect(bids).to.be.empty; }); it('handles no server response', function () { - let bids = spec.interpretResponse(null, REQUEST); + let bids = spec.interpretResponse(null, BUILD_REQUESTS_OUTPUT); expect(bids).to.be.empty; }); @@ -272,7 +334,7 @@ describe('Consumable BidAdapter', function () { it('should return a sync url if pixel syncs are enabled and some are returned from the server', function () { let syncOptions = {'pixelEnabled': true}; - let opts = spec.getUserSyncs(syncOptions, [RESPONSE]); + let opts = spec.getUserSyncs(syncOptions, [AD_SERVER_RESPONSE]); expect(opts.length).to.equal(1); }); diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 6c9f0c8e6e5..67dd721a059 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -156,6 +156,14 @@ describe('Conversant adapter tests', function() { price: 3.99, adomain: ['https://example.com'], id: 'bid003' + }, { + nurl: 'notify004', + adm: '', + crid: '1004', + impid: 'bid004', + price: 4.99, + adomain: ['https://example.com'], + id: 'bid004' }] }] }, @@ -315,7 +323,7 @@ describe('Conversant adapter tests', function() { it('Verify interpretResponse', function() { const request = spec.buildRequests(bidRequests); const response = spec.interpretResponse(bidResponses, request); - expect(response).to.be.an('array').with.lengthOf(3); + expect(response).to.be.an('array').with.lengthOf(4); let bid = response[0]; expect(bid).to.have.property('requestId', 'bid000'); @@ -352,6 +360,9 @@ describe('Conversant adapter tests', function() { expect(bid).to.have.property('mediaType', 'video'); expect(bid).to.have.property('ttl', 300); expect(bid).to.have.property('netRevenue', true); + + bid = response[3]; + expect(bid).to.have.property('vastXml', ''); }); it('Verify handling of bad responses', function() { @@ -374,6 +385,7 @@ describe('Conversant adapter tests', function() { // construct http post payload const payload = spec.buildRequests(requests).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 12345); + expect(payload).to.not.have.nested.property('user.ext.eids'); }); it('Verify User ID publisher commond id support', function() { @@ -387,32 +399,183 @@ describe('Conversant adapter tests', function() { // construct http post payload const payload = spec.buildRequests(requests).data; expect(payload).to.have.deep.nested.property('user.ext.fpc', 67890); + expect(payload).to.not.have.nested.property('user.ext.eids'); }); it('Verify GDPR bid request', function() { // add gdpr info - const bidRequest = { + const bidderRequest = { gdprConsent: { consentString: 'BOJObISOJObISAABAAENAA4AAAAAoAAA', gdprApplies: true } }; - const payload = spec.buildRequests(bidRequests, bidRequest).data; + const payload = spec.buildRequests(bidRequests, bidderRequest).data; expect(payload).to.have.deep.nested.property('user.ext.consent', 'BOJObISOJObISAABAAENAA4AAAAAoAAA'); expect(payload).to.have.deep.nested.property('regs.ext.gdpr', 1); }); it('Verify GDPR bid request without gdprApplies', function() { // add gdpr info - const bidRequest = { + const bidderRequest = { gdprConsent: { consentString: '' } }; - const payload = spec.buildRequests(bidRequests, bidRequest).data; + const payload = spec.buildRequests(bidRequests, bidderRequest).data; expect(payload).to.have.deep.nested.property('user.ext.consent', ''); expect(payload).to.not.have.deep.nested.property('regs.ext.gdpr'); }); + + describe('CCPA', function() { + it('should have us_privacy', function() { + const bidderRequest = { + uspConsent: '1NYN' + }; + + const payload = spec.buildRequests(bidRequests, bidderRequest).data; + expect(payload).to.have.deep.nested.property('regs.ext.us_privacy', '1NYN'); + expect(payload).to.not.have.deep.nested.property('regs.ext.gdpr'); + }); + + it('should have no us_privacy', function() { + const payload = spec.buildRequests(bidRequests, {}).data; + expect(payload).to.not.have.deep.nested.property('regs.ext.us_privacy'); + }); + + it('should have both gdpr and us_privacy', function() { + const bidderRequest = { + gdprConsent: { + consentString: 'BOJObISOJObISAABAAENAA4AAAAAoAAA', + gdprApplies: true + }, + uspConsent: '1NYN' + }; + + const payload = spec.buildRequests(bidRequests, bidderRequest).data; + expect(payload).to.have.deep.nested.property('user.ext.consent', 'BOJObISOJObISAABAAENAA4AAAAAoAAA'); + expect(payload).to.have.deep.nested.property('regs.ext.gdpr', 1); + expect(payload).to.have.deep.nested.property('regs.ext.us_privacy', '1NYN'); + }); + }); + + describe('Extended ID', function() { + it('Verify unifiedid and liveramp', function() { + // clone bidRequests + let requests = utils.deepClone(bidRequests); + + // add pubcid to every entry + requests.forEach((unit) => { + Object.assign(unit, {userId: {pubcid: 112233, tdid: 223344, idl_env: 334455}}); + }); + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.eids', [ + {source: 'adserver.org', uids: [{id: 223344, atype: 1}]}, + {source: 'liveramp.com', uids: [{id: 334455, atype: 1}]} + ]); + }); + }); + + describe('direct reading pubcid', function() { + const ID_NAME = '_pubcid'; + const CUSTOM_ID_NAME = 'myid'; + const EXP = '_exp'; + const TIMEOUT = 2000; + + function cleanUp(key) { + window.document.cookie = key + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'; + localStorage.removeItem(key); + localStorage.removeItem(key + EXP); + } + + function expStr(timeout) { + return (new Date(Date.now() + timeout * 60 * 60 * 24 * 1000)).toUTCString(); + } + + afterEach(() => { + cleanUp(ID_NAME); + cleanUp(CUSTOM_ID_NAME); + }); + + it('reading cookie', function() { + // clone bidRequests + const requests = utils.deepClone(bidRequests); + + // add a pubcid cookie + utils.setCookie(ID_NAME, '12345', expStr(TIMEOUT)); + + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.fpc', '12345'); + }); + + it('reading custom cookie', function() { + // clone bidRequests + const requests = utils.deepClone(bidRequests); + requests[0].params.pubcid_name = CUSTOM_ID_NAME; + + // add a pubcid cookie + utils.setCookie(CUSTOM_ID_NAME, '12345', expStr(TIMEOUT)); + + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.fpc', '12345'); + }); + + it('reading local storage with empty exp time', function() { + // clone bidRequests + const requests = utils.deepClone(bidRequests); + + // add a pubcid in local storage + utils.setDataInLocalStorage(ID_NAME + EXP, ''); + utils.setDataInLocalStorage(ID_NAME, 'abcde'); + + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.fpc', 'abcde'); + }); + + it('reading local storage with valid exp time', function() { + // clone bidRequests + const requests = utils.deepClone(bidRequests); + + // add a pubcid in local storage + utils.setDataInLocalStorage(ID_NAME + EXP, expStr(TIMEOUT)); + utils.setDataInLocalStorage(ID_NAME, 'fghijk'); + + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.fpc', 'fghijk'); + }); + + it('reading expired local storage', function() { + // clone bidRequests + const requests = utils.deepClone(bidRequests); + + // add a pubcid in local storage + utils.setDataInLocalStorage(ID_NAME + EXP, expStr(-TIMEOUT)); + utils.setDataInLocalStorage(ID_NAME, 'lmnopq'); + + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.not.have.deep.nested.property('user.ext.fpc'); + }); + + it('reading local storage with custom name', function() { + // clone bidRequests + const requests = utils.deepClone(bidRequests); + requests[0].params.pubcid_name = CUSTOM_ID_NAME; + + // add a pubcid in local storage + utils.setDataInLocalStorage(CUSTOM_ID_NAME + EXP, expStr(TIMEOUT)); + utils.setDataInLocalStorage(CUSTOM_ID_NAME, 'fghijk'); + + // construct http post payload + const payload = spec.buildRequests(requests).data; + expect(payload).to.have.deep.nested.property('user.ext.fpc', 'fghijk'); + }); + }); }); diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js new file mode 100644 index 00000000000..44decfd56b6 --- /dev/null +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -0,0 +1,135 @@ +import { criteoIdSubmodule } from 'modules/criteoIdSystem'; +import * as utils from 'src/utils'; +import * as ajaxLib from 'src/ajax'; +import * as urlLib from 'src/url'; + +const pastDateString = new Date(0).toString() + +function mockResponse(responseText, fakeResponse = (url, callback) => callback(responseText)) { + return function() { + return fakeResponse; + } +} + +describe('CriteoId module', function () { + const cookiesMaxAge = 13 * 30 * 24 * 60 * 60 * 1000; + + const nowTimestamp = new Date().getTime(); + + let getCookieStub; + let setCookieStub; + let getLocalStorageStub; + let setLocalStorageStub; + let removeFromLocalStorageStub; + let timeStampStub; + let parseUrlStub; + let ajaxBuilderStub; + let triggerPixelStub; + + beforeEach(function (done) { + getCookieStub = sinon.stub(utils, 'getCookie'); + setCookieStub = sinon.stub(utils, 'setCookie'); + getLocalStorageStub = sinon.stub(utils, 'getDataFromLocalStorage'); + setLocalStorageStub = sinon.stub(utils, 'setDataInLocalStorage'); + removeFromLocalStorageStub = sinon.stub(utils, 'removeDataFromLocalStorage'); + timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); + ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(mockResponse('{}')); + parseUrlStub = sinon.stub(urlLib, 'parse').returns({protocol: 'https', hostname: 'testdev.com'}) + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); + done(); + }); + + afterEach(function () { + getCookieStub.restore(); + setCookieStub.restore(); + getLocalStorageStub.restore(); + setLocalStorageStub.restore(); + removeFromLocalStorageStub.restore(); + timeStampStub.restore(); + ajaxBuilderStub.restore(); + triggerPixelStub.restore(); + parseUrlStub.restore(); + }); + + const storageTestCases = [ + { cookie: 'bidId', localStorage: 'bidId2', expected: 'bidId' }, + { cookie: 'bidId', localStorage: undefined, expected: 'bidId' }, + { cookie: undefined, localStorage: 'bidId', expected: 'bidId' }, + { cookie: undefined, localStorage: undefined, expected: undefined }, + ] + + storageTestCases.forEach(testCase => it('getId() should return the bidId when it exists in local storages', function () { + getCookieStub.withArgs('cto_bidid').returns(testCase.cookie); + getLocalStorageStub.withArgs('cto_bidid').returns(testCase.localStorage); + + const id = criteoIdSubmodule.getId(); + expect(id).to.be.deep.equal({id: testCase.expected ? { criteoId: testCase.expected } : undefined}); + })) + + it('decode() should return the bidId when it exists in local storages', function () { + const id = criteoIdSubmodule.decode('testDecode'); + expect(id).to.equal('testDecode') + }); + + it('should call user sync url with the right params', function () { + getCookieStub.withArgs('cto_test_cookie').returns('1'); + getCookieStub.withArgs('cto_bundle').returns('bundle'); + window.criteo_pubtag = {} + + const emptyObj = '{}'; + let ajaxStub = sinon.stub().callsFake((url, callback) => callback(emptyObj)); + ajaxBuilderStub.callsFake(mockResponse(undefined, ajaxStub)) + + criteoIdSubmodule.getId(); + const expectedUrl = `https://gum.criteo.com/sid/json?origin=prebid&topUrl=https%3A%2F%2Ftestdev.com%2F&domain=testdev.com&bundle=bundle&cw=1&pbt=1`; + + expect(ajaxStub.calledWith(expectedUrl)).to.be.true; + + window.criteo_pubtag = undefined; + }); + + const responses = [ + { bundle: 'bundle', bidId: 'bidId', acwsUrl: 'acwsUrl' }, + { bundle: 'bundle', bidId: undefined, acwsUrl: 'acwsUrl' }, + { bundle: 'bundle', bidId: 'bidId', acwsUrl: undefined }, + { bundle: undefined, bidId: 'bidId', acwsUrl: 'acwsUrl' }, + { bundle: 'bundle', bidId: undefined, acwsUrl: undefined }, + { bundle: undefined, bidId: 'bidId', acwsUrl: undefined }, + { bundle: undefined, bidId: undefined, acwsUrl: 'acwsUrl' }, + { bundle: undefined, bidId: undefined, acwsUrl: ['acwsUrl', 'acwsUrl2'] }, + { bundle: undefined, bidId: undefined, acwsUrl: undefined }, + ] + + responses.forEach(response => describe('test user sync response behavior', function () { + const expirationTs = new Date(nowTimestamp + cookiesMaxAge).toString(); + + beforeEach(function (done) { + const fakeResponse = (url, callback) => { + callback(JSON.stringify(response)); + setTimeout(done, 0); + } + ajaxBuilderStub.callsFake(mockResponse(undefined, fakeResponse)); + criteoIdSubmodule.getId(); + }) + + it('should save bidId if it exists', function () { + if (response.acwsUrl) { + expect(triggerPixelStub.called).to.be.true; + expect(setCookieStub.calledWith('cto_bundle')).to.be.false; + expect(setLocalStorageStub.calledWith('cto_bundle')).to.be.false; + } else if (response.bundle) { + expect(setCookieStub.calledWith('cto_bundle', response.bundle, expirationTs)).to.be.true; + expect(setLocalStorageStub.calledWith('cto_bundle', response.bundle)).to.be.true; + expect(triggerPixelStub.called).to.be.false; + } + + if (response.bidId) { + expect(setCookieStub.calledWith('cto_bidid', response.bidId, expirationTs)).to.be.true; + expect(setLocalStorageStub.calledWith('cto_bidid', response.bidId)).to.be.true; + } else { + expect(setCookieStub.calledWith('cto_bidid', '', pastDateString)).to.be.true; + expect(removeFromLocalStorageStub.calledWith('cto_bidid')).to.be.true; + } + }); + })); +}); diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js new file mode 100644 index 00000000000..f4adad7faf2 --- /dev/null +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -0,0 +1,175 @@ +import {expect} from 'chai'; +import {spec} from 'modules/deepintentBidAdapter'; +import * as utils from '../../../src/utils'; + +describe('Deepintent adapter', function () { + let request; + let bannerResponse; + + beforeEach(function () { + request = [ + { + bidder: 'deepintent', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + tagId: '100013', + w: 728, + h: 90, + user: { + id: 'di_testuid', + buyeruid: 'di_testbuyeruid', + yob: 2002, + gender: 'F' + }, + custom: { + 'position': 'right-box' + } + } + } + ]; + bannerResponse = { + 'body': { + 'id': '303e1fae-9677-41e2-9a92-15a23445363f', + 'seatbid': [{ + 'bid': [{ + 'id': '11447bb1-a266-470d-b0d7-8810f5b1b75f', + 'impid': 'a7e92b9b-d9db-4de8-9c3f-f90737335445', + 'price': 0.6, + 'adid': '10001', + 'adm': "\r\n", + 'adomain': ['deepintent.com'], + 'cid': '103389', + 'crid': '13665', + 'w': 300, + 'h': 250, + 'dealId': 'dee_12312stdszzsx' + }], + 'seat': '10000' + }], + 'bidid': '0b08b09f-aaa1-4c14-b1c8-7debb1a7c1cd' + } + } + }); + + describe('validations', function () { + it('validBid : tagId is passed', function () { + let bid = { + bidder: 'deepintent', + params: { + tagId: '1232' + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('invalidBid : tagId is not passed', function () { + let bid = { + bidder: 'deepintent', + params: { + h: 200, + w: 300 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + it('invalidBid : tagId is not a string', function () { + let bid = { + bidder: 'deepintent', + params: { + tagId: 12345 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('request check', function () { + it('unmutaable bid request check', function () { + let oRequest = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(oRequest); + }); + it('bidder connection check', function () { + let bRequest = spec.buildRequests(request); + expect(bRequest.url).to.equal('https://prebid.deepintent.com/prebid'); + expect(bRequest.method).to.equal('POST'); + expect(bRequest.options.contentType).to.equal('application/json'); + }); + it('bid request check : Device', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.device.ua).to.be.a('string'); + expect(data.device.js).to.equal(1); + expect(data.device.dnt).to.be.a('number'); + expect(data.device.h).to.be.a('number'); + expect(data.device.w).to.be.a('number'); + }); + it('bid request check : Impression', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.at).to.equal(1); // auction type + expect(data.imp[0].id).to.equal(request[0].bidId); + expect(data.imp[0].tagid).to.equal('100013'); + }); + it('bid request check : ad size', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].banner).to.be.a('object'); + expect(data.imp[0].banner.w).to.equal(300); + expect(data.imp[0].banner.h).to.equal(250); + }); + it('bid request check : custom params', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].ext).to.be.a('object'); + expect(data.imp[0].ext.deepintent.position).to.equal('right-box'); + }); + it('bid request check: displaymanager check', function() { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].displaymanager).to.equal('di_prebid'); + expect(data.imp[0].displaymanagerver).to.equal('1.0.0'); + }); + it('bid request check: user object check', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + expect(data.user).to.be.a('object'); + expect(data.user.id).to.equal('di_testuid'); + expect(data.user.buyeruid).to.equal('di_testbuyeruid'); + expect(data.user.yob).to.equal(2002); + expect(data.user.gender).to.equal('F'); + }) + }); + describe('user sync check', function () { + it('user sync url check', function () { + let syncOptions = { + iframeEnabled: true + }; + let userSync = spec.getUserSyncs(syncOptions); + expect(userSync).to.be.an('array').with.length.above(0); + expect(userSync[0].type).to.equal('iframe'); + expect(userSync[0].url).to.equal('https://beacon.deepintent.com/usersync.html'); + }); + }); + describe('response check', function () { + it('bid response check: valid bid response', function () { + let bRequest = spec.buildRequests(request); + let data = JSON.parse(bRequest.data); + let bResponse = spec.interpretResponse(bannerResponse, request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + }) +}); diff --git a/test/spec/modules/districtmDmxBidAdapter_spec.js b/test/spec/modules/districtmDmxBidAdapter_spec.js index 64f76eb026d..ac8698f63a9 100644 --- a/test/spec/modules/districtmDmxBidAdapter_spec.js +++ b/test/spec/modules/districtmDmxBidAdapter_spec.js @@ -1,7 +1,41 @@ import {expect} from 'chai'; import * as _ from 'lodash'; -import {spec, matchRequest, checkDeepArray, defaultSize} from '../../../modules/districtmDMXBidAdapter'; +import {spec, matchRequest, checkDeepArray, defaultSize, upto5, cleanSizes, shuffle} from '../../../modules/districtmDMXBidAdapter'; +const supportedSize = [ + { + size: [300, 250], + s: 100 + }, + { + size: [728, 90], + s: 95 + }, + { + size: [300, 600], + s: 90 + }, + { + size: [160, 600], + s: 88 + }, + { + size: [320, 50], + s: 85 + }, + { + size: [300, 50], + s: 80 + }, + { + size: [970, 250], + s: 75 + }, + { + size: [970, 90], + s: 60 + }, +]; const bidRequest = [{ 'bidder': 'districtmDMX', 'params': { @@ -422,6 +456,12 @@ const emptyResponseSeatBid = { body: { seatbid: [] } }; describe('DistrictM Adaptor', function () { const districtm = spec; + describe('verification of upto5', function() { + it('upto5 function should always break 12 imps into 3 request same for 15', function() { + expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) + expect(upto5([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15], bidRequest, bidderRequest, 'https://google').length).to.be.equal(3) + }) + }) describe('All needed functions are available', function () { it(`isBidRequestValid is present and type function`, function () { expect(districtm.isBidRequestValid).to.exist.and.to.be.a('function') diff --git a/test/spec/modules/emx_digitalBidAdapter_spec.js b/test/spec/modules/emx_digitalBidAdapter_spec.js index 80fd12a237c..525e5808e40 100644 --- a/test/spec/modules/emx_digitalBidAdapter_spec.js +++ b/test/spec/modules/emx_digitalBidAdapter_spec.js @@ -355,6 +355,14 @@ describe('emx_digital Adapter', function () { expect(request.regs.ext).to.have.property('gdpr', 0); expect(request).to.not.have.property('user'); }); + it('should add us privacy info to request', function() { + let consentString = '1YNN'; + bidderRequest.uspConsent = consentString; + let request = spec.buildRequests(bidderRequest.bids, bidderRequest); + request = JSON.parse(request.data); + expect(request.us_privacy).to.exist; + expect(request.us_privacy).to.exist.and.to.equal(consentString); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/eplanningBidAdapter_spec.js b/test/spec/modules/eplanningBidAdapter_spec.js index 93290229ce3..3152c43c6ff 100644 --- a/test/spec/modules/eplanningBidAdapter_spec.js +++ b/test/spec/modules/eplanningBidAdapter_spec.js @@ -8,10 +8,14 @@ describe('E-Planning Adapter', function () { const CI = '12345'; const ADUNIT_CODE = 'adunit-co:de'; const ADUNIT_CODE2 = 'adunit-code-dos'; - const CLEAN_ADUNIT_CODE2 = 'adunitcodedos'; - const CLEAN_ADUNIT_CODE = 'adunitco_de'; + const ADUNIT_CODE_VIEW = 'adunit-code-view'; + const ADUNIT_CODE_VIEW2 = 'adunit-code-view2'; + const ADUNIT_CODE_VIEW3 = 'adunit-code-view3'; + const CLEAN_ADUNIT_CODE2 = '300x250_1'; + const CLEAN_ADUNIT_CODE = '300x250_0'; const BID_ID = '123456789'; const BID_ID2 = '987654321'; + const BID_ID3 = '998877665'; const CPM = 1.3; const W = '300'; const H = '250'; @@ -37,6 +41,33 @@ describe('E-Planning Adapter', function () { 'adUnitCode': ADUNIT_CODE2, 'sizes': [[300, 250], [300, 600]], }; + const validBidView = { + 'bidder': 'eplanning', + 'bidId': BID_ID, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE_VIEW, + 'sizes': [[300, 250], [300, 600]], + }; + const validBidView2 = { + 'bidder': 'eplanning', + 'bidId': BID_ID2, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE_VIEW2, + 'sizes': [[300, 250], [300, 600]], + }; + const validBidView3 = { + 'bidder': 'eplanning', + 'bidId': BID_ID3, + 'params': { + 'ci': CI, + }, + 'adUnitCode': ADUNIT_CODE_VIEW3, + 'sizes': [[300, 250], [300, 600]], + }; const testBid = { 'bidder': 'eplanning', 'params': { @@ -212,7 +243,7 @@ describe('E-Planning Adapter', function () { it('should return e parameter with value according to the adunit sizes', function () { const e = spec.buildRequests(bidRequests).data.e; - expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600'); + expect(e).to.equal('300x250_0:300x250,300x600'); }); it('should return correct e parameter with more than one adunit', function () { @@ -229,7 +260,7 @@ describe('E-Planning Adapter', function () { bidRequests.push(anotherBid); const e = spec.buildRequests(bidRequests).data.e; - expect(e).to.equal(CLEAN_ADUNIT_CODE + ':300x250,300x600+' + CLEAN_NEW_CODE + ':100x100'); + expect(e).to.equal('300x250_0:300x250,300x600+100x100_0:100x100'); }); it('should return correct e parameter when the adunit has no size', function () { @@ -242,7 +273,7 @@ describe('E-Planning Adapter', function () { }; const e = spec.buildRequests([noSizeBid]).data.e; - expect(e).to.equal(CLEAN_ADUNIT_CODE + ':1x1'); + expect(e).to.equal('1x1_0:1x1'); }); it('should return ur parameter with current window url', function () { @@ -360,4 +391,334 @@ describe('E-Planning Adapter', function () { expect(responses[1].requestId).to.equal(BID_ID2); }); }); + describe('viewability', function() { + let storageIdRender = 'pbsr_' + validBidView.adUnitCode; + let storageIdView = 'pbvi_' + validBidView.adUnitCode; + let bidRequests = [validBidView]; + let bidRequestMultiple = [validBidView, validBidView2, validBidView3]; + let getLocalStorageSpy; + let setDataInLocalStorageSpy; + let hasLocalStorageStub; + let clock; + let element; + let getBoundingClientRectStub; + let sandbox = sinon.sandbox.create(); + let focusStub; + function createElement(id) { + element = document.createElement('div'); + element.id = id || ADUNIT_CODE_VIEW; + element.style.width = '50px'; + element.style.height = '50px'; + if (frameElement) { + frameElement.style.width = '100px'; + frameElement.style.height = '100px'; + } + element.style.background = 'black'; + document.body.appendChild(element); + } + function createElementVisible(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 0, + y: 0, + width: 50, + height: 50, + top: 0, + right: 50, + bottom: 50, + left: 0, + }); + } + function createElementOutOfView(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 100, + y: 100, + width: 250, + height: 250, + top: 100, + right: 350, + bottom: 350, + left: 100, + }); + } + + function createPartiallyVisibleElement(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 0, + y: 0, + width: 50, + height: 150, + top: 0, + right: 50, + bottom: 150, + left: 0, + }); + } + function createPartiallyInvisibleElement(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 0, + y: 0, + width: 150, + height: 150, + top: 0, + right: 150, + bottom: 150, + left: 0, + }); + } + function createElementOutOfRange(id) { + createElement(id); + sandbox.stub(element, 'getBoundingClientRect').returns({ + x: 200, + y: 200, + width: 350, + height: 350, + top: 200, + right: 350, + bottom: 350, + left: 200, + }); + } + beforeEach(function () { + getLocalStorageSpy = sandbox.spy(utils, 'getDataFromLocalStorage'); + setDataInLocalStorageSpy = sandbox.spy(utils, 'setDataInLocalStorage'); + + hasLocalStorageStub = sandbox.stub(utils, 'hasLocalStorage'); + hasLocalStorageStub.returns(true); + + clock = sandbox.useFakeTimers(); + + focusStub = sandbox.stub(window.top.document, 'hasFocus'); + focusStub.returns(true); + }); + afterEach(function () { + sandbox.restore(); + if (document.getElementById(ADUNIT_CODE_VIEW)) { + document.body.removeChild(element); + } + window.top.localStorage.removeItem(storageIdRender); + window.top.localStorage.removeItem(storageIdView); + }); + + it('should create the url correctly without LocalStorage', function() { + createElementVisible(); + hasLocalStorageStub.returns(false); + const response = spec.buildRequests(bidRequests); + + expect(response.url).to.equal('//ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + expect(response.data.vs).to.equal('F'); + + sinon.assert.notCalled(getLocalStorageSpy); + sinon.assert.notCalled(setDataInLocalStorageSpy); + }); + + it('should create the url correctly with LocalStorage', function() { + createElementVisible(); + const response = spec.buildRequests(bidRequests); + expect(response.url).to.equal('//ads.us.e-planning.net/hb/1/' + CI + '/1/localhost/ROS'); + + expect(response.data.vs).to.equal('F'); + + sinon.assert.called(getLocalStorageSpy); + sinon.assert.called(setDataInLocalStorageSpy); + sinon.assert.calledWith(getLocalStorageSpy, storageIdRender); + sinon.assert.calledWith(setDataInLocalStorageSpy, storageIdRender); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + }); + + context('when element is fully in view', function() { + let respuesta; + beforeEach(function () { + createElementVisible(); + }); + it('when you have a render', function() { + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + + expect(respuesta.data.vs).to.equal('F'); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + it('when you have more than four render', function() { + utils.setDataInLocalStorage(storageIdRender, 4); + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + + expect(respuesta.data.vs).to.equal('0'); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('5'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + it('when you have more than four render and already record visibility', function() { + utils.setDataInLocalStorage(storageIdRender, 4); + utils.setDataInLocalStorage(storageIdView, 4); + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + + expect(respuesta.data.vs).to.equal('a'); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('5'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal('5'); + }); + }); + + context('when element is out of view', function() { + let respuesta; + beforeEach(function () { + createElementOutOfView(); + }); + + it('when you have a render', function() { + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + expect(respuesta.data.vs).to.equal('F'); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + it('when you have more than four render', function() { + utils.setDataInLocalStorage(storageIdRender, 4); + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + expect(respuesta.data.vs).to.equal('0'); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('5'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + + context('when element is partially in view', function() { + let respuesta; + it('should register visibility with more than 50%', function() { + createPartiallyVisibleElement(); + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal('1'); + }); + it('you should not register visibility with less than 50%', function() { + createPartiallyInvisibleElement(); + respuesta = spec.buildRequests(bidRequests); + clock.tick(1005); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('when width or height of the element is zero', function() { + beforeEach(function () { + createElementVisible(); + }); + it('if the width is zero but the height is within the range', function() { + element.style.width = '0px'; + spec.buildRequests(bidRequests) + clock.tick(1005); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + it('if the height is zero but the width is within the range', function() { + element.style.height = '0px'; + spec.buildRequests(bidRequests) + clock.tick(1005); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + it('if both are zero', function() { + element.style.height = '0px'; + element.style.width = '0px'; + spec.buildRequests(bidRequests) + clock.tick(1005); + + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('when tab is inactive', function() { + it('I should not register if it is not in focus', function() { + createElementVisible(); + focusStub.returns(false); + spec.buildRequests(bidRequests); + clock.tick(1005); + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('segmentBeginsBeforeTheVisibleRange', function() { + it('segmentBeginsBeforeTheVisibleRange', function() { + createElementOutOfRange(); + spec.buildRequests(bidRequests); + clock.tick(1005); + expect(utils.getDataFromLocalStorage(storageIdRender)).to.equal('1'); + expect(utils.getDataFromLocalStorage(storageIdView)).to.equal(null); + }); + }); + context('when there are multiple adunit', function() { + let respuesta; + beforeEach(function () { + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + utils.setDataInLocalStorage('pbsr_' + ac, 5); + utils.setDataInLocalStorage('pbvi_' + ac, 5); + }); + }); + afterEach(function () { + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + if (document.getElementById(ac)) { + document.body.removeChild(document.getElementById(ac)); + } + window.top.localStorage.removeItem(ac); + window.top.localStorage.removeItem(ac); + }); + }); + it('all visibles', function() { + createElementVisible(ADUNIT_CODE_VIEW); + createElementVisible(ADUNIT_CODE_VIEW2); + createElementVisible(ADUNIT_CODE_VIEW3); + + respuesta = spec.buildRequests(bidRequestMultiple); + clock.tick(1005); + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + expect(utils.getDataFromLocalStorage('pbsr_' + ac)).to.equal('6'); + expect(utils.getDataFromLocalStorage('pbvi_' + ac)).to.equal('6'); + }); + expect('aaa').to.equal(respuesta.data.vs); + }); + it('none visible', function() { + createElementOutOfView(ADUNIT_CODE_VIEW); + createElementOutOfView(ADUNIT_CODE_VIEW2); + createElementOutOfView(ADUNIT_CODE_VIEW3); + + respuesta = spec.buildRequests(bidRequestMultiple); + clock.tick(1005); + [ADUNIT_CODE_VIEW, ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + expect(utils.getDataFromLocalStorage('pbsr_' + ac)).to.equal('6'); + expect(utils.getDataFromLocalStorage('pbvi_' + ac)).to.equal('5'); + }); + + expect('aaa').to.equal(respuesta.data.vs); + }); + it('some visible and others not visible', function() { + createElementVisible(ADUNIT_CODE_VIEW); + createElementOutOfView(ADUNIT_CODE_VIEW2); + createElementOutOfView(ADUNIT_CODE_VIEW3); + + respuesta = spec.buildRequests(bidRequestMultiple); + clock.tick(1005); + expect(utils.getDataFromLocalStorage('pbsr_' + ADUNIT_CODE_VIEW)).to.equal('6'); + expect(utils.getDataFromLocalStorage('pbvi_' + ADUNIT_CODE_VIEW)).to.equal('6'); + [ADUNIT_CODE_VIEW2, ADUNIT_CODE_VIEW3].forEach(ac => { + expect(utils.getDataFromLocalStorage('pbsr_' + ac)).to.equal('6'); + expect(utils.getDataFromLocalStorage('pbvi_' + ac)).to.equal('5'); + }); + expect('aaa').to.equal(respuesta.data.vs); + }); + }); + }); }); diff --git a/test/spec/modules/freeWheelAdserverVideo_spec.js b/test/spec/modules/freeWheelAdserverVideo_spec.js index f958a2733db..c2afaf8d69f 100644 --- a/test/spec/modules/freeWheelAdserverVideo_spec.js +++ b/test/spec/modules/freeWheelAdserverVideo_spec.js @@ -197,6 +197,96 @@ describe('freeWheel adserver module', function() { expect(targeting['preroll_1'].length).to.equal(3); expect(targeting['midroll_1'].length).to.equal(3); }); + + it('should prioritize bids with deal', function() { + config.setConfig({ + adpod: { + deferCaching: true, + prioritizeDeals: true + } + }); + + let tier6Bid = createBid(10, 'preroll_1', 15, 'tier6_395_15s', '123', '395'); + tier6Bid['video']['dealTier'] = 'tier6' + + let tier7Bid = createBid(11, 'preroll_1', 45, 'tier7_395_15s', '123', '395'); + tier7Bid['video']['dealTier'] = 'tier7' + + let bidsReceived = [ + tier6Bid, + tier7Bid, + createBid(15, 'preroll_1', 90, '15.00_395_90s', '123', '395'), + ] + amStub.returns(bidsReceived); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + requests[0].respond( + 200, + { 'Content-Type': 'text/plain' }, + JSON.stringify({'responses': bidsReceived.slice(1)}) + ); + + expect(targeting['preroll_1'].length).to.equal(3); + expect(targeting['preroll_1']).to.deep.include({'hb_pb_cat_dur': 'tier6_395_15s'}); + expect(targeting['preroll_1']).to.deep.include({'hb_pb_cat_dur': 'tier7_395_15s'}); + expect(targeting['preroll_1']).to.deep.include({'hb_cache_id': '123'}); + }); + + it('should apply minDealTier to bids if configured', function() { + config.setConfig({ + adpod: { + deferCaching: true, + prioritizeDeals: true, + dealTier: { + 'appnexus': { + prefix: 'tier', + minDealTier: 5 + } + } + } + }); + + let tier2Bid = createBid(10, 'preroll_1', 15, 'tier2_395_15s', '123', '395'); + tier2Bid['video']['dealTier'] = 2 + tier2Bid['adserverTargeting']['hb_pb'] = '10.00' + + let tier7Bid = createBid(11, 'preroll_1', 45, 'tier7_395_15s', '123', '395'); + tier7Bid['video']['dealTier'] = 7 + tier7Bid['adserverTargeting']['hb_pb'] = '11.00' + + let bid = createBid(15, 'preroll_1', 15, '15.00_395_90s', '123', '395'); + bid['adserverTargeting']['hb_pb'] = '15.00' + + let bidsReceived = [ + tier2Bid, + tier7Bid, + bid + ] + amStub.returns(bidsReceived); + let targeting; + adpodUtils.getTargeting({ + callback: function(errorMsg, targetingResult) { + targeting = targetingResult; + } + }); + + requests[0].respond( + 200, + { 'Content-Type': 'text/plain' }, + JSON.stringify({'responses': [tier7Bid, bid]}) + ); + + expect(targeting['preroll_1'].length).to.equal(3); + expect(targeting['preroll_1']).to.deep.include({'hb_pb_cat_dur': 'tier7_395_15s'}); + expect(targeting['preroll_1']).to.deep.include({'hb_pb_cat_dur': '15.00_395_90s'}); + expect(targeting['preroll_1']).to.not.include({'hb_pb_cat_dur': 'tier2_395_15s'}); + expect(targeting['preroll_1']).to.deep.include({'hb_cache_id': '123'}); + }) }); function getBidsReceived() { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index 36aaddb8b42..d972ec25c8a 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/gridBidAdapter'; +import { spec, resetUserSync, getSyncUrl } from 'modules/gridBidAdapter'; import { newBidder } from 'src/adapters/bidderFactory'; describe('TheMediaGrid Adapter', function () { @@ -577,4 +577,91 @@ describe('TheMediaGrid Adapter', function () { expect(result).to.deep.equal(expectedResponse); }); }); + + describe('user sync', function () { + const syncUrl = getSyncUrl(); + + beforeEach(function () { + resetUserSync(); + }); + + it('should register the Emily iframe', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + }); + + it('should not register the Emily iframe more than once', function () { + let syncs = spec.getUserSyncs({ + pixelEnabled: true + }); + expect(syncs).to.deep.equal({type: 'image', url: syncUrl}); + + // when called again, should still have only been called once + syncs = spec.getUserSyncs(); + expect(syncs).to.equal(undefined); + }); + + it('should pass gdpr params if consent is true', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: true, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=1&gdpr_consent=foo` + }); + }); + + it('should pass gdpr params if consent is false', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + gdprApplies: false, consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr=0&gdpr_consent=foo` + }); + }); + + it('should pass gdpr param gdpr_consent only when gdprApplies is undefined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 'foo' + })).to.deep.equal({ + type: 'image', url: `${syncUrl}&gdpr_consent=foo` + }); + }); + + it('should pass no params if gdpr consentString is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, {})).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a number', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: 0 + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is null', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: null + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr consentString is a object', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, { + consentString: {} + })).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + + it('should pass no params if gdpr is not defined', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, {}, undefined)).to.deep.equal({ + type: 'image', url: syncUrl + }); + }); + }); }); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 82c8e494533..cbd71cc82f0 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -21,7 +21,11 @@ describe('gumgumAdapter', function () { 'bidfloor': 0.05 }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600], [1, 1]], + 'mediaTypes': { + 'banner': { + sizes: [[300, 250], [300, 600], [1, 1]] + } + }, 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', @@ -70,7 +74,29 @@ describe('gumgumAdapter', function () { }, 'adUnitCode': 'adunit-code', 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e' + 'bidId': '30b31c1838de1e', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + }, + { + 'asi': 'exchange2.com', + 'sid': 'abcd', + 'hp': 1, + 'rid': 'bid-request-2', + 'name': 'intermediary', + 'domain': 'intermediary.com' + } + ] + } } ]; @@ -92,6 +118,17 @@ describe('gumgumAdapter', function () { expect(bidRequest.data).to.include.any.keys('t'); expect(bidRequest.data).to.include.any.keys('fp'); }); + it('should send pubId if inScreenPubID param is specified', function () { + const request = Object.assign({}, bidRequests[0]); + delete request.params; + request.params = { + 'inScreenPubID': 123 + }; + const bidRequest = spec.buildRequests([request])[0]; + expect(bidRequest.data).to.include.any.keys('pubId'); + expect(bidRequest.data.pubId).to.equal(request.params.inScreenPubID); + expect(bidRequest.data).to.not.include.any.keys('t'); + }); it('should correctly set the request paramters depending on params field', function () { const request = Object.assign({}, bidRequests[0]); delete request.params; @@ -136,6 +173,12 @@ describe('gumgumAdapter', function () { const request = spec.buildRequests(bidRequests)[0]; expect(request.data).to.not.include.any.keys('tdid'); }); + it('should send schain parameter in serialized form', function () { + const serializedForm = '1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com' + const request = spec.buildRequests(bidRequests)[0]; + expect(request.data).to.include.any.keys('schain'); + expect(request.data.schain).to.eq(serializedForm); + }); it('should send ns parameter if browser contains navigator.connection property', function () { const bidRequest = spec.buildRequests(bidRequests)[0]; const connection = window.navigator && window.navigator.connection; diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index 2d9d51c0d68..8ac8c8e85df 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -212,6 +212,22 @@ describe('Improve Digital Adapter Tests', function () { expect(params.bid_request.imp[0].ad_types).to.deep.equal(['video']); }); + it('should not set Prebid sizes in bid request for instream video', function () { + const getConfigStub = sinon.stub(config, 'getConfig'); + getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); + const bidRequest = Object.assign({}, simpleBidRequest); + bidRequest.mediaTypes = { + video: { + context: 'instream', + playerSize: [640, 480] + } + }; + const request = spec.buildRequests([bidRequest])[0]; + const params = JSON.parse(decodeURIComponent(request.data.substring(PARAM_PREFIX.length))); + expect(params.bid_request.imp[0].banner.format).to.not.exist; + getConfigStub.restore(); + }); + it('should not set ad type for outstream video', function() { const bidRequest = Object.assign({}, simpleBidRequest); bidRequest.mediaTypes = { diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index 6391f168599..57ca35e2b28 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -135,6 +135,42 @@ describe('invibesBidAdapter:', function () { expect(parsedData.height).to.exist; }); + it('has capped ids if local storage variable is correctly formatted', function () { + localStorage.ivvcap = '{"9731":[1,1768600800000]}'; + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + expect(request.data.capCounts).to.equal('9731=1'); + }); + + it('does not have capped ids if local storage variable is incorrectly formatted', function () { + localStorage.ivvcap = ':[1,1574334216992]}'; + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + expect(request.data.capCounts).to.equal(''); + }); + + it('does not have capped ids if local storage variable is expired', function () { + localStorage.ivvcap = '{"9731":[1,1574330064104]}'; + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + expect(request.data.capCounts).to.equal(''); + }); + + it('sends query string params from localstorage 1', function () { + localStorage.ivbs = JSON.stringify({ bvci: 1 }); + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + expect(request.data.bvci).to.equal(1); + }); + + it('sends query string params from localstorage 2', function () { + localStorage.ivbs = JSON.stringify({ invibbvlog: true }); + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + expect(request.data.invibbvlog).to.equal(true); + }); + + it('does not send query string params from localstorage if unknwon', function () { + localStorage.ivbs = JSON.stringify({ someparam: true }); + const request = spec.buildRequests(bidRequests, { auctionStart: Date.now() }); + expect(request.data.someparam).to.be.undefined; + }); + it('sends all Placement Ids', function () { const request = spec.buildRequests(bidRequests); expect(JSON.parse(request.data.bidParamsJson).placementIds).to.contain(bidRequests[0].params.placementId); @@ -154,7 +190,7 @@ describe('invibesBidAdapter:', function () { }); it('try to graduate but not enough count - doesnt send the domain id', function () { - global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":5}'; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":0}'; let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; @@ -179,6 +215,7 @@ describe('invibesBidAdapter:', function () { stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; + expect(top.window.invibes.dom.tempId).to.exist; }); it('send the domain id after replacing it with new format', function () { @@ -186,6 +223,7 @@ describe('invibesBidAdapter:', function () { stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.exist; + expect(top.window.invibes.dom.tempId).to.exist; }); it('dont send the domain id if consent declined', function () { @@ -193,6 +231,7 @@ describe('invibesBidAdapter:', function () { stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; + expect(top.window.invibes.dom.tempId).to.not.exist; }); it('dont send the domain id if no consent', function () { @@ -200,6 +239,16 @@ describe('invibesBidAdapter:', function () { stubDomainOptions(new StubbedPersistence('{"id":"f8zoh044p9oi.8537626"}')); let request = spec.buildRequests(bidRequests, bidderRequest); expect(request.data.lId).to.not.exist; + expect(top.window.invibes.dom.tempId).to.not.exist; + }); + + it('try to init id but was already loaded on page - does not increment the id again', function () { + let bidderRequest = { gdprConsent: { vendorData: { vendorConsents: { 436: true } } } }; + global.document.cookie = 'ivbsdid={"id":"dvdjkams6nkq","cr":1521818537626,"hc":0}'; + let request = spec.buildRequests(bidRequests, bidderRequest); + request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.lId).to.not.exist; + expect(top.window.invibes.dom.tempId).to.exist; }); }); @@ -273,6 +322,8 @@ describe('invibesBidAdapter:', function () { context('when the response is valid', function () { it('responds with a valid bid', function () { + top.window.invibes.setCookie('a', 'b', 370); + top.window.invibes.setCookie('c', 'd', 0); let result = spec.interpretResponse({ body: response }, { bidRequests }); expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); }); diff --git a/test/spec/modules/invisiblyAnalyticsAdapter_spec.js b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..6ee989907b5 --- /dev/null +++ b/test/spec/modules/invisiblyAnalyticsAdapter_spec.js @@ -0,0 +1,496 @@ +import invisiblyAdapter from 'modules/invisiblyAnalyticsAdapter'; +import { expect } from 'chai'; +let events = require('src/events'); +let constants = require('src/constants.json'); + +describe('Invisibly Analytics Adapter test suite', function() { + let xhr; + let requests = []; + const BID1 = { + width: 980, + height: 240, + cpm: 1.1, + timeToRespond: 200, + bidId: '2ecff0db240757', + requestId: '2ecff0db240757', + adId: '2ecff0db240757', + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', + adUnitCode: '/19968336/header-bid-tag-0', + adserverTargeting: { + hb_bidder: 'testBidder', + hb_adid: '2ecff0db240757', + hb_pb: 1.2, + hb_size: '640x480', + hb_source: 'client' + }, + getStatusCode() { + return CONSTANTS.STATUS.GOOD; + } + }; + + const BID2 = Object.assign({}, BID1, { + width: 300, + height: 250, + cpm: 2.2, + timeToRespond: 300, + bidId: '3ecff0db240757', + requestId: '3ecff0db240757', + adId: '3ecff0db240757' + }); + + const BID3 = { + bidId: '4ecff0db240757', + requestId: '4ecff0db240757', + adId: '4ecff0db240757', + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', + adUnitCode: '/19968336/header-bid-tag1', + adserverTargeting: { + hb_bidder: 'testBidder', + hb_adid: '3bd4ebb1c900e2', + hb_pb: '1.500', + hb_size: '728x90', + hb_source: 'server' + }, + getStatusCode() { + return CONSTANTS.STATUS.NO_BID; + } + }; + + const MOCK = { + config: { + provider: 'invisiblyAnalytics', + options: { + bundleId: '', + account: 'invisibly' + } + }, + AUCTION_INIT: { + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa' + }, + BID_REQUESTED: { + bidder: 'mockBidder', + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa', + bidderRequestId: '1be65d7958826a', + bids: [ + { + bidder: 'mockBidder', + adUnitCode: 'panorama_d_1', + bidId: '2ecff0db240757' + }, + { + bidder: 'mockBidder', + adUnitCode: 'box_d_1', + bidId: '3ecff0db240757' + }, + { + bidder: 'mockBidder', + adUnitCode: 'box_d_2', + bidId: '4ecff0db240757' + } + ], + start: 1519149562216 + }, + BID_RESPONSE: [BID1, BID2], + AUCTION_END: { + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990 + }, + BID_WON: { + bidderCode: 'appnexus', + width: 300, + height: 250, + adId: '1ebb82ec35375e', + mediaType: 'banner', + cpm: 0.5, + requestId: '1582271863760569973', + creative_id: '96846035', + creativeId: '96846035', + ttl: 60, + currency: 'USD', + netRevenue: true, + auctionId: '9c7b70b9-b6ab-4439-9e71-b7b382797c18', + responseTimestamp: 1537521629657, + requestTimestamp: 1537521629331, + bidder: 'appnexus', + adUnitCode: 'div-gpt-ad-1460505748561-0', + timeToRespond: 326, + size: '300x250', + status: 'rendered', + eventType: 'bidWon', + ad: 'some ad', + adUrl: 'ad url' + }, + BIDDER_DONE: { + bidderCode: 'mockBidder', + bids: [BID1, BID2, BID3] + }, + BID_TIMEOUT: [ + { + bidId: '2ecff0db240757', + auctionId: '25c6d7f5-699a-4bfc-87c9-996f915341fa' + } + ], + BID_ADJUSTMENT: { + ad: 'html', + adId: '298bf14ecbafb', + cpm: 1.01, + creativeId: '2249:92806132', + currency: 'USD', + height: 250, + mediaType: 'banner', + requestId: '298bf14ecbafb', + size: '300x250', + source: 'client', + status: 'rendered', + statusMessage: 'Bid available', + timeToRespond: 421, + ttl: 300, + width: 300 + }, + NO_BID: { + testKey: false + }, + SET_TARGETING: { + [BID1.adUnitCode]: BID1.adserverTargeting, + [BID3.adUnitCode]: BID3.adserverTargeting + }, + REQUEST_BIDS: { + call: 'request' + }, + ADD_AD_UNITS: { call: 'addAdUnits' }, + AD_RENDER_FAILED: { call: 'adRenderFailed' }, + INVALID_EVENT: { + mockKey: 'this event should not emit' + } + }; + + describe('Invisibly Analytic tests specs', function() { + beforeEach(function() { + xhr = sinon.useFakeXMLHttpRequest(); + requests = []; + xhr.onCreate = request => requests.push(request); + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(invisiblyAdapter, 'track'); + }); + + afterEach(function() { + invisiblyAdapter.disableAnalytics(); + events.getEvents.restore(); + invisiblyAdapter.track.restore(); + xhr.restore(); + }); + + // specs to test invisibly account input to enableAnaylitcs + describe('monitor enableAnalytics method', function() { + it('should catch all events triggered with invisibly account config', function() { + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: { + account: 'invisibly' + } + }); + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + sinon.assert.callCount(invisiblyAdapter.track, 5); + }); + + it('should not catch events triggered without invisibly account config', function() { + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: {} + }); + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + invisiblyAdapter.flush(); + sinon.assert.callCount(invisiblyAdapter.track, 0); + }); + // spec to test custom api endpoint + it('support custom endpoint', function() { + let custom_url = 'custom url'; + invisiblyAdapter.enableAnalytics({ + provider: 'invisiblyAnalytics', + options: { + url: custom_url, + bundleId: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', + account: 'invisibly' + } + }); + expect(invisiblyAdapter.getOptions().url).to.equal(custom_url); + }); + }); + + // spec for auction init event + it('auction init event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[1].requestBody.substring(0)); + // pageView is default event initially hence expecting 2 requests + expect(requests.length).to.equal(2); + expect(requests[1].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.auctionId).to.equal( + MOCK.AUCTION_INIT.auctionId + ); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_type).to.equal('PREBID_auctionInit'); + expect(invisiblyEvents.event_data.ver).to.equal(1); + }); + + // spec for bid adjustment event + it('bid adjustment event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidAdjustment'); + expect(invisiblyEvents.event_data.bidders.cpm).to.equal( + MOCK.BID_ADJUSTMENT.cpm + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid timeout event + it('bid timeout event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidTimeout'); + expect(invisiblyEvents.event_data.bidders.bidId).to.equal( + MOCK.BID_TIMEOUT.bidId + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid requested event + it('bid requested event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidRequested'); + expect(invisiblyEvents.event_data.auctionId).to.equal( + MOCK.BID_REQUESTED.auctionId + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid response event + it('bid response event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidResponse'); + expect(invisiblyEvents.event_data.cpm).to.equal(MOCK.BID_REQUESTED.cpm); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for no bid event + it('no bid event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_noBid'); + expect(invisiblyEvents.event_data.noBid.testKey).to.equal( + MOCK.NO_BID.testKey + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bid won event + it('bid won event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidWon'); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for bidder done event + it('bidder done event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_bidderDone'); + expect(invisiblyEvents.event_data.bidderCode).to.equal( + MOCK.BIDDER_DONE.bidderCode + ); + expect(invisiblyEvents.event_data.bids.length).to.equal( + MOCK.BIDDER_DONE.bids.length + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for set targeting event + it('set targeting event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_setTargeting'); + expect( + invisiblyEvents.event_data.targetings[BID1.adUnitCode] + ).to.deep.equal(BID1.adserverTargeting); + expect( + invisiblyEvents.event_data.targetings[BID3.adUnitCode] + ).to.deep.equal(BID3.adserverTargeting); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for request bids event + it('request bids event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_requestBids'); + expect(invisiblyEvents.event_data.call).to.equal(MOCK.REQUEST_BIDS.call); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for add ad units event + it('add ad units event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_addAdUnits'); + expect(invisiblyEvents.event_data.call).to.equal(MOCK.ADD_AD_UNITS.call); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for ad render failed event + it('ad render failed event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_adRenderFailed'); + expect(invisiblyEvents.event_data.call).to.equal( + MOCK.AD_RENDER_FAILED.call + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // spec for auction end event + it('auction end event', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + invisiblyAdapter.flush(); + + const invisiblyEvents = JSON.parse(requests[0].requestBody.substring(0)); + expect(requests.length).to.equal(1); + expect(requests[0].url).to.equal('https://api.pymx5.com/v1/sites/events'); + expect(invisiblyEvents.event_data.pageViewId).to.exist; + expect(invisiblyEvents.event_data.ver).to.equal(1); + expect(invisiblyEvents.event_type).to.equal('PREBID_auctionEnd'); + expect(invisiblyEvents.event_data.auctionId).to.equal( + MOCK.AUCTION_END.auctionId + ); + sinon.assert.callCount(invisiblyAdapter.track, 1); + }); + + // should not call sendEvent for events not supported by the adapter + it('it should not call sendEvent for this event emit', function() { + sinon.spy(invisiblyAdapter, 'sendEvent'); + invisiblyAdapter.enableAnalytics(MOCK.config); + events.emit(constants.EVENTS.INVALID_EVENT, MOCK.INVALID_EVENT); + invisiblyAdapter.flush(); + + expect(requests.length).to.equal(0); + sinon.assert.callCount(invisiblyAdapter.track, 0); + sinon.assert.callCount(invisiblyAdapter.sendEvent, 0); + }); + + // spec to emit all events + it('track all event without errors', function() { + invisiblyAdapter.enableAnalytics(MOCK.config); + + events.emit(constants.EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(constants.EVENTS.AUCTION_END, MOCK.AUCTION_END); + events.emit(constants.EVENTS.BID_ADJUSTMENT, MOCK.BID_ADJUSTMENT); + events.emit(constants.EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT); + events.emit(constants.EVENTS.BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(constants.EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(constants.EVENTS.NO_BID, MOCK.NO_BID); + events.emit(constants.EVENTS.BID_WON, MOCK.BID_WON); + events.emit(constants.EVENTS.BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(constants.EVENTS.SET_TARGETING, MOCK.SET_TARGETING); + events.emit(constants.EVENTS.REQUEST_BIDS, MOCK.REQUEST_BIDS); + events.emit(constants.EVENTS.ADD_AD_UNITS, MOCK.ADD_AD_UNITS); + events.emit(constants.EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED); + + sinon.assert.callCount(invisiblyAdapter.track, 13); + }); + }); +}); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 120d02408d7..245cf01610c 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -6,7 +6,8 @@ import { spec } from 'modules/ixBidAdapter'; describe('IndexexchangeAdapter', function () { const IX_SECURE_ENDPOINT = 'https://as-sec.casalemedia.com/cygnus'; - const BIDDER_VERSION = 7.2; + const VIDEO_ENDPOINT_VERSION = 8.1; + const BANNER_ENDPOINT_VERSION = 7.2; const DEFAULT_BANNER_VALID_BID = [ { @@ -28,17 +29,37 @@ describe('IndexexchangeAdapter', function () { auctionId: '1aa2bb3cc4dd' } ]; - const DEFAULT_BANNER_OPTION = { - gdprConsent: { - gdprApplies: true, - consentString: '3huaa11=qu3198ae', - vendorData: {} - }, - refererInfo: { - referer: 'http://www.prebid.org', - canonicalUrl: 'http://www.prebid.org/the/link/to/the/page' + + const DEFAULT_VIDEO_VALID_BID = [ + { + bidder: 'ix', + params: { + siteId: '456', + video: { + skippable: false, + mimes: [ + 'video/mp4', + 'video/webm' + ], + minduration: 0 + }, + size: [400, 100] + }, + sizes: [[400, 100], [200, 400]], + mediaTypes: { + video: { + context: 'instream', + playerSize: [[400, 100], [200, 400]] + } + }, + adUnitCode: 'div-gpt-ad-1460505748562-0', + transactionId: '173f49a8-7549-4218-a23c-e7ba59b47230', + bidId: '1a2b3c4e', + bidderRequestId: '11a22b33c44e', + auctionId: '1aa2bb3cc4de' } - }; + ]; + const DEFAULT_BANNER_BID_RESPONSE = { cur: 'USD', id: '11a22b33c44d', @@ -68,6 +89,48 @@ describe('IndexexchangeAdapter', function () { } ] }; + + const DEFAULT_VIDEO_BID_RESPONSE = { + cur: 'USD', + id: '1aa2bb3cc4de', + seatbid: [ + { + bid: [ + { + crid: '12346', + adomain: ['www.abcd.com'], + adid: '14851456', + impid: '1a2b3c4e', + cid: '3051267', + price: 110, + id: '2', + ext: { + vasturl: 'www.abcd.com/vast', + errorurl: 'www.abcd.com/error', + dspid: 51, + pricelevel: '_110', + advbrandid: 303326, + advbrand: 'OECTB' + } + } + ], + seat: '3971' + } + ] + }; + + const DEFAULT_OPTION = { + gdprConsent: { + gdprApplies: true, + consentString: '3huaa11=qu3198ae', + vendorData: {} + }, + refererInfo: { + referer: 'http://www.prebid.org', + canonicalUrl: 'http://www.prebid.org/the/link/to/the/page' + } + }; + const DEFAULT_IDENTITY_RESPONSE = { IdentityIp: { responsePending: false, @@ -82,6 +145,31 @@ describe('IndexexchangeAdapter', function () { } }; + const DEFAULT_BIDDER_REQUEST_DATA = { + ac: 'j', + r: JSON.stringify({ + id: '345', + imp: [ + { + id: '1a2b3c4e', + video: { + w: 640, + h: 480, + placement: 1 + } + } + ], + site: { + ref: 'http://ref.com/ref.html', + page: 'http://page.com' + }, + }), + s: '21', + sd: 1, + t: 1000, + v: 8.1 + }; + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -90,11 +178,12 @@ describe('IndexexchangeAdapter', function () { }); describe('isBidRequestValid', function () { - it('should return true when required params found for a banner ad', function () { + it('should return true when required params found for a banner or video ad', function () { expect(spec.isBidRequestValid(DEFAULT_BANNER_VALID_BID[0])).to.equal(true); + expect(spec.isBidRequestValid(DEFAULT_VIDEO_VALID_BID[0])).to.equal(true); }); - it('should return true when optional params found for a banner ad', function () { + it('should return true when optional bidFloor params found for an ad', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; bid.params.bidFloorCur = 'USD'; @@ -135,10 +224,10 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaTypes is not banner', function () { + it('should return false when mediaTypes is not banner or video', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.mediaTypes = { - video: { + native: { sizes: [[300, 250]] } }; @@ -155,19 +244,13 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when mediaType is not banner', function () { - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - delete bid.params.mediaTypes; - bid.mediaType = 'banne'; - bid.sizes = [[300, 250]]; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when mediaType is video', function () { - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - delete bid.params.mediaTypes; - bid.mediaType = 'video'; - bid.sizes = [[300, 250]]; + it('should return false when mediaTypes.video does not have sizes', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes = { + video: { + size: [[300, 250]] + } + }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); @@ -194,6 +277,14 @@ describe('IndexexchangeAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('should return true when mediaType is video', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.mediaTypes; + bid.mediaType = 'video'; + bid.sizes = [[400, 100]]; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when there is only bidFloor', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; @@ -231,7 +322,7 @@ describe('IndexexchangeAdapter', function () { window.headertag.getIdentityInfo = function() { return testCopy; }; - request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; }); afterEach(function() { @@ -342,7 +433,7 @@ describe('IndexexchangeAdapter', function () { window.headertag.getIdentityInfo = function() { return undefined; }; - request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); @@ -355,7 +446,7 @@ describe('IndexexchangeAdapter', function () { responsePending: true, data: {} } - request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); @@ -365,7 +456,7 @@ describe('IndexexchangeAdapter', function () { it('payload should not have any user eids if identity data is pending for all partners', function () { testCopy.IdentityIp.responsePending = true; - request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); @@ -376,7 +467,7 @@ describe('IndexexchangeAdapter', function () { it('payload should not have any user eids if identity data is pending or not available for all partners', function () { testCopy.IdentityIp.responsePending = false; testCopy.IdentityIp.data = {}; - request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; query = request.data; const payload = JSON.parse(query.r); @@ -386,8 +477,8 @@ describe('IndexexchangeAdapter', function () { }); }); - describe('buildRequestsBanner', function () { - const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + describe('buildRequests', function () { + const request = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const requestUrl = request.url; const requestMethod = request.method; const query = request.data; @@ -395,7 +486,7 @@ describe('IndexexchangeAdapter', function () { const bidWithoutMediaType = utils.deepClone(DEFAULT_BANNER_VALID_BID); delete bidWithoutMediaType[0].mediaTypes; bidWithoutMediaType[0].sizes = [[300, 250], [300, 600]]; - const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType, DEFAULT_BANNER_OPTION); + const requestWithoutMediaType = spec.buildRequests(bidWithoutMediaType, DEFAULT_OPTION)[0]; const queryWithoutMediaType = requestWithoutMediaType.data; it('request should be made to IX endpoint with GET method', function () { @@ -404,11 +495,12 @@ describe('IndexexchangeAdapter', function () { }); it('query object (version, siteID and request) should be correct', function () { - expect(query.v).to.equal(BIDDER_VERSION); + expect(query.v).to.equal(BANNER_ENDPOINT_VERSION); expect(query.s).to.equal(DEFAULT_BANNER_VALID_BID[0].params.siteId); expect(query.r).to.exist; expect(query.ac).to.equal('j'); expect(query.sd).to.equal(1); + expect(query.nf).not.to.exist; }); it('payload should have correct format and value', function () { @@ -416,7 +508,7 @@ describe('IndexexchangeAdapter', function () { expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.site).to.exist; - expect(payload.site.page).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext).to.exist; expect(payload.ext.source).to.equal('prebid'); @@ -444,7 +536,7 @@ describe('IndexexchangeAdapter', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.bidFloor = 50; bid.params.bidFloorCur = 'USD'; - const requestBidFloor = spec.buildRequests([bid]); + const requestBidFloor = spec.buildRequests([bid])[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.bidfloor).to.equal(bid.params.bidFloor); @@ -456,7 +548,7 @@ describe('IndexexchangeAdapter', function () { expect(payload.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidderRequestId); expect(payload.site).to.exist; - expect(payload.site.page).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(payload.site.page).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(payload.site.ref).to.equal(document.referrer); expect(payload.ext).to.exist; expect(payload.ext.source).to.equal('prebid'); @@ -483,7 +575,7 @@ describe('IndexexchangeAdapter', function () { it('impression should have sid if id is configured as number', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.id = 50; - const requestBidFloor = spec.buildRequests([bid]); + const requestBidFloor = spec.buildRequests([bid])[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); @@ -500,7 +592,7 @@ describe('IndexexchangeAdapter', function () { it('impression should have sid if id is configured as string', function () { const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); bid.params.id = 'abc'; - const requestBidFloor = spec.buildRequests([bid]); + const requestBidFloor = spec.buildRequests([bid])[0]; const impression = JSON.parse(requestBidFloor.data.r).imp[0]; expect(impression.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); expect(impression.banner).to.exist; @@ -525,9 +617,9 @@ describe('IndexexchangeAdapter', function () { } }); - const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + const requestWithFirstPartyData = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithFirstPartyData.data.r).site.page; - const expectedPageUrl = DEFAULT_BANNER_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; + const expectedPageUrl = DEFAULT_OPTION.refererInfo.referer + '?ab=123&cd=123%23ab&e%2Ff=456&h%3Fg=456%23cd'; expect(pageUrl).to.equal(expectedPageUrl); }); @@ -539,10 +631,10 @@ describe('IndexexchangeAdapter', function () { } }); - const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + const requestFirstPartyDataNumber = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestFirstPartyDataNumber.data.r).site.page; - expect(pageUrl).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); }); it('should not set first party or timeout if it is not present', function () { @@ -550,18 +642,18 @@ describe('IndexexchangeAdapter', function () { ix: {} }); - const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - expect(pageUrl).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(requestWithoutConfig.data.t).to.be.undefined; }); it('should not set first party or timeout if it is setConfig is not called', function () { - const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); + const requestWithoutConfig = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION)[0]; const pageUrl = JSON.parse(requestWithoutConfig.data.r).site.page; - expect(pageUrl).to.equal(DEFAULT_BANNER_OPTION.refererInfo.referer); + expect(pageUrl).to.equal(DEFAULT_OPTION.refererInfo.referer); expect(requestWithoutConfig.data.t).to.be.undefined; }); @@ -571,7 +663,7 @@ describe('IndexexchangeAdapter', function () { timeout: 500 } }); - const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestWithTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; expect(requestWithTimeout.data.t).to.equal(500); }); @@ -582,14 +674,99 @@ describe('IndexexchangeAdapter', function () { timeout: '500' } }); - const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID); + const requestStringTimeout = spec.buildRequests(DEFAULT_BANNER_VALID_BID)[0]; expect(requestStringTimeout.data.t).to.be.undefined; }); + + it('request should contain both banner and video requests', function () { + const request = spec.buildRequests([DEFAULT_BANNER_VALID_BID[0], DEFAULT_VIDEO_VALID_BID[0]]); + + const bannerImp = JSON.parse(request[0].data.r).imp[0]; + expect(JSON.parse(request[0].data.v)).to.equal(BANNER_ENDPOINT_VERSION); + expect(bannerImp.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); + expect(bannerImp.id).to.equal(DEFAULT_BANNER_VALID_BID[0].bidId); + expect(bannerImp.banner).to.exist; + expect(bannerImp.banner.w).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[0]); + expect(bannerImp.banner.h).to.equal(DEFAULT_BANNER_VALID_BID[0].params.size[1]); + + const videoImp = JSON.parse(request[1].data.r).imp[0]; + expect(JSON.parse(request[1].data.v)).to.equal(VIDEO_ENDPOINT_VERSION); + expect(videoImp.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(videoImp.video).to.exist; + expect(videoImp.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); + expect(videoImp.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); + }); }); - describe('interpretResponseBanner', function () { - it('should get correct bid response', function () { + describe('buildRequestVideo', function () { + const request = spec.buildRequests(DEFAULT_VIDEO_VALID_BID, DEFAULT_OPTION); + const query = request[0].data; + + it('query object (version, siteID and request) should be correct', function () { + expect(query.v).to.equal(VIDEO_ENDPOINT_VERSION); + expect(query.s).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId); + expect(query.r).to.exist; + expect(query.ac).to.equal('j'); + expect(query.sd).to.equal(1); + expect(query.nf).to.equal(1); + }); + + it('impression should have correct format and value', function () { + const impression = JSON.parse(query.r).imp[0]; + const sidValue = `${DEFAULT_VIDEO_VALID_BID[0].params.size[0].toString()}x${DEFAULT_VIDEO_VALID_BID[0].params.size[1].toString()}`; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video).to.exist; + expect(impression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); + expect(impression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); + expect(impression.video.placement).to.exist; + expect(impression.video.placement).to.equal(1); + expect(impression.video.minduration).to.exist; + expect(impression.video.minduration).to.equal(0); + expect(impression.video.mimes).to.exist; + expect(impression.video.mimes[0]).to.equal('video/mp4'); + expect(impression.video.mimes[1]).to.equal('video/webm'); + + expect(impression.video.skippable).to.equal(false); + expect(impression.ext).to.exist; + expect(impression.ext.siteID).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId.toString()); + expect(impression.ext.sid).to.equal(sidValue); + }); + + it('impression should have correct format when mediaType is specified.', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + delete bid.mediaTypes; + bid.mediaType = 'video'; + const requestBidFloor = spec.buildRequests([bid])[0]; + const impression = JSON.parse(requestBidFloor.data.r).imp[0]; + const sidValue = `${DEFAULT_VIDEO_VALID_BID[0].params.size[0].toString()}x${DEFAULT_VIDEO_VALID_BID[0].params.size[1].toString()}`; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video).to.exist; + expect(impression.video.w).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[0]); + expect(impression.video.h).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.size[1]); + expect(impression.video.placement).to.not.exist; + expect(impression.ext).to.exist; + expect(impression.ext.siteID).to.equal(DEFAULT_VIDEO_VALID_BID[0].params.siteId.toString()); + expect(impression.ext.sid).to.equal(sidValue); + }); + + it('should set correct placement if context is outstream', function () { + const bid = utils.deepClone(DEFAULT_VIDEO_VALID_BID[0]); + bid.mediaTypes.video.context = 'outstream'; + const request = spec.buildRequests([bid])[0]; + const impression = JSON.parse(request.data.r).imp[0]; + + expect(impression.id).to.equal(DEFAULT_VIDEO_VALID_BID[0].bidId); + expect(impression.video).to.exist; + expect(impression.video.placement).to.exist; + expect(impression.video.placement).to.equal(4); + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid response for banner ad', function () { const expectedParse = [ { requestId: '1a2b3c4d', @@ -597,6 +774,7 @@ describe('IndexexchangeAdapter', function () { creativeId: '12345', width: 300, height: 250, + mediaType: 'banner', ad: '', currency: 'USD', ttl: 35, @@ -609,7 +787,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }); + const result = spec.interpretResponse({ body: DEFAULT_BANNER_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA }); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -623,6 +801,7 @@ describe('IndexexchangeAdapter', function () { creativeId: '-', width: 300, height: 250, + mediaType: 'banner', ad: '', currency: 'USD', ttl: 35, @@ -635,8 +814,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }); - expect(result[0]).to.deep.equal(expectedParse[0]); + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); }); it('should set Japanese price correctly', function () { @@ -649,6 +827,7 @@ describe('IndexexchangeAdapter', function () { creativeId: '12345', width: 300, height: 250, + mediaType: 'banner', ad: '', currency: 'JPY', ttl: 35, @@ -661,7 +840,7 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }); + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); expect(result[0]).to.deep.equal(expectedParse[0]); }); @@ -675,6 +854,7 @@ describe('IndexexchangeAdapter', function () { creativeId: '12345', width: 300, height: 250, + mediaType: 'banner', ad: '', currency: 'USD', ttl: 35, @@ -687,13 +867,38 @@ describe('IndexexchangeAdapter', function () { } } ]; - const result = spec.interpretResponse({ body: bidResponse }); + const result = spec.interpretResponse({ body: bidResponse }, { data: DEFAULT_BIDDER_REQUEST_DATA }); + expect(result[0]).to.deep.equal(expectedParse[0]); + }); + + it('should get correct bid response for video ad', function () { + const expectedParse = [ + { + requestId: '1a2b3c4e', + cpm: 1.1, + creativeId: '12346', + mediaType: 'video', + width: 640, + height: 480, + currency: 'USD', + ttl: 3600, + netRevenue: true, + dealId: undefined, + vastUrl: 'www.abcd.com/vast', + meta: { + networkId: 51, + brandId: 303326, + brandName: 'OECTB' + } + } + ]; + const result = spec.interpretResponse({ body: DEFAULT_VIDEO_BID_RESPONSE }, { data: DEFAULT_BIDDER_REQUEST_DATA }); expect(result[0]).to.deep.equal(expectedParse[0]); }); it('bidrequest should have consent info if gdprApplies and consentString exist', function () { - const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_BANNER_OPTION); - const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, DEFAULT_OPTION); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); @@ -707,7 +912,7 @@ describe('IndexexchangeAdapter', function () { } }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); expect(requestWithConsent.regs.ext.gdpr).to.equal(1); expect(requestWithConsent.user).to.be.undefined; @@ -721,7 +926,7 @@ describe('IndexexchangeAdapter', function () { } }; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); expect(requestWithConsent.regs).to.be.undefined; expect(requestWithConsent.user.ext.consent).to.equal('3huaa11=qu3198ae'); @@ -730,7 +935,7 @@ describe('IndexexchangeAdapter', function () { it('bidrequest should not have consent info if options.gdprConsent is undefined', function () { const options = {}; const validBidWithConsent = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithConsent = JSON.parse(validBidWithConsent.data.r); + const requestWithConsent = JSON.parse(validBidWithConsent[0].data.r); expect(requestWithConsent.regs).to.be.undefined; expect(requestWithConsent.user).to.be.undefined; @@ -739,10 +944,10 @@ describe('IndexexchangeAdapter', function () { it('bidrequest should not have page if options is undefined', function () { const options = {}; const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo.data.r); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); expect(requestWithoutreferInfo.site.page).to.be.undefined; - expect(validBidWithoutreferInfo.url).to.equal(IX_SECURE_ENDPOINT); + expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); }); it('bidrequest should not have page if options.refererInfo is an empty object', function () { @@ -750,10 +955,10 @@ describe('IndexexchangeAdapter', function () { refererInfo: {} }; const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo.data.r); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); expect(requestWithoutreferInfo.site.page).to.be.undefined; - expect(validBidWithoutreferInfo.url).to.equal(IX_SECURE_ENDPOINT); + expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); }); it('bidrequest should sent to secure endpoint if page url is secure', function () { @@ -763,10 +968,10 @@ describe('IndexexchangeAdapter', function () { } }; const validBidWithoutreferInfo = spec.buildRequests(DEFAULT_BANNER_VALID_BID, options); - const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo.data.r); + const requestWithoutreferInfo = JSON.parse(validBidWithoutreferInfo[0].data.r); expect(requestWithoutreferInfo.site.page).to.equal(options.refererInfo.referer); - expect(validBidWithoutreferInfo.url).to.equal(IX_SECURE_ENDPOINT); + expect(validBidWithoutreferInfo[0].url).to.equal(IX_SECURE_ENDPOINT); }); }); }); diff --git a/test/spec/modules/justpremiumBidAdapter_spec.js b/test/spec/modules/justpremiumBidAdapter_spec.js index c3cd015b6e3..b16586d0e14 100644 --- a/test/spec/modules/justpremiumBidAdapter_spec.js +++ b/test/spec/modules/justpremiumBidAdapter_spec.js @@ -1,13 +1,11 @@ import { expect } from 'chai' -import { spec, pixel } from 'modules/justpremiumBidAdapter' +import { spec } from 'modules/justpremiumBidAdapter' describe('justpremium adapter', function () { let sandbox; - let pixelStub; beforeEach(function() { sandbox = sinon.sandbox.create(); - pixelStub = sandbox.stub(pixel, 'fire'); }); afterEach(function() { @@ -47,7 +45,7 @@ describe('justpremium adapter', function () { let bidderRequest = { refererInfo: { - referer: 'http://justpremium.com' + referer: 'https://justpremium.com' } } @@ -73,16 +71,16 @@ describe('justpremium adapter', function () { const jpxRequest = JSON.parse(request.data) expect(jpxRequest).to.not.equal(null) expect(jpxRequest.zone).to.not.equal('undefined') - expect(bidderRequest.refererInfo.referer).to.equal('http://justpremium.com') + expect(bidderRequest.refererInfo.referer).to.equal('https://justpremium.com') expect(jpxRequest.sw).to.equal(window.top.screen.width) expect(jpxRequest.sh).to.equal(window.top.screen.height) expect(jpxRequest.ww).to.equal(window.top.innerWidth) expect(jpxRequest.wh).to.equal(window.top.innerHeight) expect(jpxRequest.c).to.not.equal('undefined') expect(jpxRequest.id).to.equal(adUnits[0].params.zone) - expect(jpxRequest.sizes).to.not.equal('undefined') + expect(jpxRequest.mediaTypes && jpxRequest.mediaTypes.banner && jpxRequest.mediaTypes.banner.sizes).to.not.equal('undefined') expect(jpxRequest.version.prebid).to.equal('$prebid.version$') - expect(jpxRequest.version.jp_adapter).to.equal('1.4') + expect(jpxRequest.version.jp_adapter).to.equal('1.6') expect(jpxRequest.pubcid).to.equal('0000000') expect(jpxRequest.uids.tdid).to.equal('1111111') expect(jpxRequest.uids.id5id).to.equal('2222222') @@ -163,34 +161,4 @@ describe('justpremium adapter', function () { expect(options[0].url).to.match(/\/\/pre.ads.justpremium.com\/v\/1.0\/t\/sync/) }) }) - - describe('onTimeout', function () { - it('onTimeout', function(done) { - spec.onTimeout([{ - 'bidId': '25cd3ec3fd6ed7', - 'bidder': 'justpremium', - 'adUnitCode': 'div-gpt-ad-1471513102552-1', - 'auctionId': '6fbd0562-f613-4151-a6df-6cb446fc717b', - 'params': [{ - 'adType': 'iab', - 'zone': 21521 - }], - 'timeout': 1 - }, { - 'bidId': '3b51df1f254e32', - 'bidder': 'justpremium', - 'adUnitCode': 'div-gpt-ad-1471513102552-3', - 'auctionId': '6fbd0562-f613-4151-a6df-6cb446fc717b', - 'params': [{ - 'adType': 'iab', - 'zone': 21521 - }], - 'timeout': 1 - }]); - - expect(pixelStub.calledOnce).to.equal(true); - - done() - }) - }) }) diff --git a/test/spec/modules/lockerdomeBidAdapter_spec.js b/test/spec/modules/lockerdomeBidAdapter_spec.js index a108b25e2ff..895164c633c 100644 --- a/test/spec/modules/lockerdomeBidAdapter_spec.js +++ b/test/spec/modules/lockerdomeBidAdapter_spec.js @@ -17,7 +17,18 @@ describe('LockerDomeAdapter', function () { transactionId: 'b55e97d7-792c-46be-95a5-3df40b115734', bidId: '2652ca954bce9', bidderRequestId: '14a54fade69854', - auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72' + auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } }, { bidder: 'lockerdome', params: { @@ -32,7 +43,18 @@ describe('LockerDomeAdapter', function () { transactionId: '73459f05-c482-4706-b2b7-72e6f6264ce6', bidId: '4510f2834773ce', bidderRequestId: '14a54fade69854', - auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72' + auctionId: 'd4c83108-615d-4c2c-9384-dac9ffd4fd72', + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + } }]; describe('isBidRequestValid', function () { @@ -96,11 +118,48 @@ describe('LockerDomeAdapter', function () { }; const request = spec.buildRequests(bidRequests, bidderRequest); const requestData = JSON.parse(request.data); - expect(requestData.gdpr).to.be.an('object'); expect(requestData.gdpr).to.have.property('applies', true); expect(requestData.gdpr).to.have.property('consent', 'AAABBB'); }); + + it('should add US Privacy data to request if available', function () { + const bidderRequest = { + uspConsent: 'AAABBB', + refererInfo: { + canonicalUrl: 'https://example.com/canonical', + referer: 'https://example.com' + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestData = JSON.parse(request.data); + expect(requestData.us_privacy).to.be.an('object'); + expect(requestData.us_privacy).to.have.property('consent', 'AAABBB'); + }); + + it('should add schain to request if available', function () { + const bidderRequest = { + refererInfo: { + canonicalUrl: 'https://example.com/canonical', + referer: 'https://example.com' + } + }; + const schainExpected = { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'indirectseller.com', + sid: '00001', + hp: 1 + } + ] + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestData = JSON.parse(request.data); + expect(requestData.schain).to.be.an('object'); + expect(requestData.schain).to.deep.equal(schainExpected); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 336a86a7793..4ab0809a3ed 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -9,12 +9,17 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', - 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -26,7 +31,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', @@ -37,6 +43,51 @@ let VALID_BID_REQUEST = [{ 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'bidRequestsCount': 1 }], + + VALID_BID_REQUEST_WITH_CRID = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }, { + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }], + VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ 'bidder': 'medianet', 'params': { @@ -45,12 +96,18 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -62,12 +119,18 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'sizes': [[300, 251]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -80,12 +143,18 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'sizes': [[300, 250]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -127,12 +196,18 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'sizes': [[300, 251]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -170,12 +245,18 @@ let VALID_BID_REQUEST = [{ }], VALID_AUCTIONDATA = { 'timeout': config.getConfig('bidderTimeout'), + 'refererInfo': { + referer: 'http://media.net/prebidtest', + stack: ['http://media.net/prebidtest'], + reachedTop: true + } }, VALID_PAYLOAD_INVALID_BIDFLOOR = { 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true }, 'ext': { 'customer_id': 'customer_id', @@ -215,7 +296,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }, { @@ -245,7 +327,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }], @@ -255,7 +338,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true }, 'ext': { 'customer_id': 'customer_id', @@ -295,7 +379,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }, { @@ -326,7 +411,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }], @@ -336,7 +422,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true }, 'ext': { 'customer_id': 'customer_id', @@ -375,7 +462,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }, { @@ -405,7 +493,94 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + } + }], + 'tmax': config.getConfig('bidderTimeout') + }, + VALID_PAYLOAD_WITH_CRID = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': $$PREBID_GLOBAL$$.version, + 'gdpr_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + } + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + 'tagid': 'crid', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + } + }, { + 'id': '3f97ca71b1e5c2', + 'tagid': 'crid', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }], @@ -429,6 +604,9 @@ let VALID_BID_REQUEST = [{ cid: '8CUV090' } }, + PARAMS_MISSING = { + bidder: 'medianet', + }, PARAMS_WITHOUT_CID = { bidder: 'medianet', params: {} @@ -479,68 +657,127 @@ let VALID_BID_REQUEST = [{ url: 'pixel-url' }], SERVER_RESPONSE_CPM_MISSING = { - 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', - 'bidList': [{ - 'no_bid': false, - 'requestId': '27210feac00e96', - 'ad': 'ad', - 'width': 300, - 'height': 250, - 'creativeId': '375068987', - 'netRevenue': true - }], - 'ext': { - 'csUrl': [{ - 'type': 'image', - 'url': 'http://cs.media.net/cksync.php' - }, { - 'type': 'iframe', - 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' - }] + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } } }, SERVER_RESPONSE_CPM_ZERO = { - 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', - 'bidList': [{ - 'no_bid': false, - 'requestId': '27210feac00e96', - 'ad': 'ad', - 'width': 300, - 'height': 250, - 'creativeId': '375068987', - 'netRevenue': true, - 'cpm': 0.0 - }], - 'ext': { - 'csUrl': [{ - 'type': 'image', - 'url': 'http://cs.media.net/cksync.php' - }, { - 'type': 'iframe', - 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' - }] + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true, + 'cpm': 0.0 + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } } }, SERVER_RESPONSE_NOBID = { - 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', - 'bidList': [{ - 'no_bid': true, - 'requestId': '3a62cf7a853f84', - 'width': 0, - 'height': 0, - 'ttl': 0, - 'netRevenue': false - }], - 'ext': { - 'csUrl': [{ - 'type': 'image', - 'url': 'http://cs.media.net/cksync.php' - }, { - 'type': 'iframe', - 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' - }] + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': true, + 'requestId': '3a62cf7a853f84', + 'width': 0, + 'height': 0, + 'ttl': 0, + 'netRevenue': false + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + SERVER_RESPONSE_NOBODY = { + + }, + SERVER_RESPONSE_EMPTY_BIDLIST = { + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': 'bid', + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + + }, + SERVER_RESPONSE_VALID_BID = { + body: { + 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', + 'bidList': [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'csUrl': [{ + 'type': 'image', + 'url': 'http://cs.media.net/cksync.php' + }, { + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } } }, + SERVER_VALID_BIDS = [{ + 'no_bid': false, + 'requestId': '27210feac00e96', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': '375068987', + 'netRevenue': true, + 'cpm': 0.1 + }], BID_REQUEST_SIZE_AS_1DARRAY = [{ 'bidder': 'medianet', 'params': { @@ -548,12 +785,18 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-0', 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', 'sizes': [300, 250], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, 'bidId': '28f8f8130a583e', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -565,12 +808,18 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } }, 'adUnitCode': 'div-gpt-ad-1460505748561-123', 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', 'sizes': [300, 251], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, 'bidId': '3f97ca71b1e5c2', 'bidderRequestId': '1e9b1f07797c1c', 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -581,13 +830,20 @@ let VALID_BID_REQUEST = [{ 'consentString': 'consentString', 'gdprApplies': true, }, - 'timeout': 3000 + 'timeout': 3000, + refererInfo: { + referer: 'http://media.net/prebidtest', + stack: ['http://media.net/prebidtest'], + reachedTop: true + } }, VALID_PAYLOAD_FOR_GDPR = { 'site': { 'domain': 'media.net', 'page': 'http://media.net/prebidtest', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true + }, 'ext': { 'customer_id': 'customer_id', @@ -627,7 +883,8 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }, { @@ -657,13 +914,13 @@ let VALID_BID_REQUEST = [{ 'site': { 'page': 'http://media.net/prebidtest', 'domain': 'media.net', - 'ref': 'http://media.net/prebidtest' + 'ref': 'http://media.net/prebidtest', + 'isTop': true } } }], 'tmax': 3000, }; - describe('Media.net bid adapter', function () { let sandbox; beforeEach(function () { @@ -694,6 +951,11 @@ describe('Media.net bid adapter', function () { let isValid = spec.isBidRequestValid(PARAMS_WITH_EMPTY_CID); expect(isValid).to.equal(false); }); + + it('should have missing params', function () { + let isValid = spec.isBidRequestValid(PARAMS_MISSING); + expect(isValid).to.equal(false); + }); }); describe('buildRequests', function () { @@ -743,6 +1005,11 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_NATIVE); }); + it('should have valid crid present in bid request', function() { + let bidreq = spec.buildRequests(VALID_BID_REQUEST_WITH_CRID, VALID_AUCTIONDATA); + expect(JSON.parse(bidreq.data)).to.deep.equal(VALID_PAYLOAD_WITH_CRID); + }); + describe('build requests: when page meta-data is available', () => { beforeEach(() => { spec.clearMnData(); @@ -852,6 +1119,11 @@ describe('Media.net bid adapter', function () { let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_BOTH_ENABLED, SERVER_CSYNC_RESPONSE); expect(userSyncs).to.deep.equal(ENABLED_SYNC_IFRAME); }); + + it('should have empty user sync array', function() { + let userSyncs = spec.getUserSyncs(SYNC_OPTIONS_IFRAME_ENABLED, {}); + expect(userSyncs).to.deep.equal([]); + }); }); describe('interpretResponse', function () { @@ -872,5 +1144,35 @@ describe('Media.net bid adapter', function () { let bids = spec.interpretResponse(SERVER_RESPONSE_NOBID, []); expect(bids).to.deep.equal(validBids); }); + + it('should have empty bid response', function() { + let bids = spec.interpretResponse(SERVER_RESPONSE_NOBODY, []); + expect(bids).to.deep.equal([]); + }); + + it('should have valid bids', function () { + let bids = spec.interpretResponse(SERVER_RESPONSE_VALID_BID, []); + expect(bids).to.deep.equal(SERVER_VALID_BIDS); + }); + + it('should have empty bid list', function() { + let validBids = []; + let bids = spec.interpretResponse(SERVER_RESPONSE_EMPTY_BIDLIST, []); + expect(bids).to.deep.equal(validBids); + }); + }); + + describe('onTimeout', function () { + it('should have valid timeout data', function() { + let response = spec.onTimeout({}); + expect(response).to.deep.equal(undefined); + }); + }); + + describe('onBidWon', function () { + it('should have valid bid data', function() { + let response = spec.onBidWon(undefined); + expect(response).to.deep.equal(undefined); + }); }); }); diff --git a/test/spec/modules/mobsmartBidAdapter_spec.js b/test/spec/modules/mobsmartBidAdapter_spec.js new file mode 100644 index 00000000000..7c1128276ab --- /dev/null +++ b/test/spec/modules/mobsmartBidAdapter_spec.js @@ -0,0 +1,214 @@ +import { expect } from 'chai'; +import { spec } from 'modules/mobsmartBidAdapter'; + +describe('mobsmartBidAdapter', function () { + describe('isBidRequestValid', function () { + let bid; + beforeEach(function() { + bid = { + bidder: 'mobsmart', + params: { + floorPrice: 100, + currency: 'JPY' + }, + mediaTypes: { + banner: { + size: [[300, 250]] + } + } + }; + }); + + it('should return true when valid bid request is set', function() { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when bidder is not set to "mobsmart"', function() { + bid.bidder = 'bidder'; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + + it('should return true when params are not set', function() { + delete bid.params.floorPrice; + delete bid.params.currency; + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let bidRequests; + beforeEach(function() { + bidRequests = [ + { + bidder: 'mobsmart', + adUnitCode: 'mobsmart-ad-code', + auctionId: 'auctionid-123', + bidId: 'bidid123', + bidRequestsCount: 1, + bidderRequestId: 'bidderrequestid123', + transactionId: 'transaction-id-123', + sizes: [[300, 250]], + requestId: 'requestid123', + params: { + floorPrice: 100, + currency: 'JPY' + }, + mediaTypes: { + banner: { + size: [[300, 250]] + } + }, + userId: { + pubcid: 'pubc-id-123' + } + }, { + bidder: 'mobsmart', + adUnitCode: 'mobsmart-ad-code2', + auctionId: 'auctionid-456', + bidId: 'bidid456', + bidRequestsCount: 1, + bidderRequestId: 'bidderrequestid456', + transactionId: 'transaction-id-456', + sizes: [[320, 50]], + requestId: 'requestid456', + params: { + floorPrice: 100, + currency: 'JPY' + }, + mediaTypes: { + banner: { + size: [[320, 50]] + } + }, + userId: { + pubcid: 'pubc-id-456' + } + } + ]; + }); + + let bidderRequest = { + refererInfo: { + referer: 'https://example.com' + } + }; + + it('should not contain a sizes when sizes is not set', function() { + delete bidRequests[0].sizes; + delete bidRequests[1].sizes; + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.sizes).to.be.an('undefined'); + expect(requests[1].data.sizes).to.be.an('undefined'); + }); + + it('should not contain a userId when userId is not set', function() { + delete bidRequests[0].userId; + delete bidRequests[1].userId; + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].data.userId).to.be.an('undefined'); + expect(requests[1].data.userId).to.be.an('undefined'); + }); + + it('should have a post method', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].method).to.equal('POST'); + expect(requests[1].method).to.equal('POST'); + }); + + it('should contain a request id equals to the bid id', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(JSON.parse(requests[0].data).requestId).to.equal(bidRequests[0].bidId); + expect(JSON.parse(requests[1].data).requestId).to.equal(bidRequests[1].bidId); + }); + + it('should have an url that match the default endpoint', function() { + let requests = spec.buildRequests(bidRequests, bidderRequest); + expect(requests[0].url).to.equal('https://prebid.mobsmart.net/prebid/endpoint'); + expect(requests[1].url).to.equal('https://prebid.mobsmart.net/prebid/endpoint'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse; + beforeEach(function() { + serverResponse = { + body: { + 'requestId': 'request-id', + 'cpm': 100, + 'width': 300, + 'height': 250, + 'ad': '
ad
', + 'ttl': 300, + 'creativeId': 'creative-id', + 'netRevenue': true, + 'currency': 'JPY' + } + }; + }); + + it('should return a valid response', () => { + var responses = spec.interpretResponse(serverResponse); + expect(responses).to.be.an('array').that.is.not.empty; + + let response = responses[0]; + expect(response).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', + 'netRevenue', 'currency'); + expect(response.requestId).to.equal('request-id'); + expect(response.cpm).to.equal(100); + expect(response.width).to.equal(300); + expect(response.height).to.equal(250); + expect(response.ad).to.equal('
ad
'); + expect(response.ttl).to.equal(300); + expect(response.creativeId).to.equal('creative-id'); + expect(response.netRevenue).to.be.true; + expect(response.currency).to.equal('JPY'); + }); + + it('should return an empty array when serverResponse is empty', () => { + serverResponse = {}; + var responses = spec.interpretResponse(serverResponse); + expect(responses).to.deep.equal([]); + }); + }); + + describe('getUserSyncs', function () { + it('should return nothing when sync is disabled', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': false + } + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs).to.deep.equal([]); + }); + + it('should register iframe sync when iframe is enabled', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': false + } + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://tags.mobsmart.net/tags/iframe'); + }); + + it('should register image sync when image is enabled', function () { + const syncOptions = { + 'iframeEnabled': false, + 'pixelEnabled': true + } + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs[0].type).to.equal('image'); + expect(syncs[0].url).to.equal('https://tags.mobsmart.net/tags/image'); + }); + + it('should register iframe sync when iframe is enabled', function () { + const syncOptions = { + 'iframeEnabled': true, + 'pixelEnabled': true + } + let syncs = spec.getUserSyncs(syncOptions); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://tags.mobsmart.net/tags/iframe'); + }); + }); +}); diff --git a/test/spec/modules/newborntownWebBidAdapter_spec.js b/test/spec/modules/newborntownWebBidAdapter_spec.js new file mode 100644 index 00000000000..d7cf4a7dd95 --- /dev/null +++ b/test/spec/modules/newborntownWebBidAdapter_spec.js @@ -0,0 +1,152 @@ +import { expect } from 'chai'; +import {spec} from 'modules/newborntownWebBidAdapter'; +describe('NewborntownWebAdapter', function() { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': 'newborntownWeb', + 'params': { + 'publisher_id': '1238122', + 'slot_id': '123123', + 'bidfloor': 0.3 + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'sizes': [[300, 250]], + 'bidId': '2e9cf65f23dbd9', + 'bidderRequestId': '1f01d9d22ee657', + 'auctionId': '2bf455a4-a889-41d5-b48f-9b56b89fbec7', + } + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, bid); + delete bid.params; + bid.params = {}; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }) + describe('buildRequests', function () { + let bidderRequest = { + 'bidderCode': 'newborntownWeb', + 'bidderRequestId': '1f5c279a4c5de3', + 'bids': [ + { + 'bidder': 'newborntownWeb', + 'params': { + 'publisher_id': '1238122', + 'slot_id': '123123', + 'bidfloor': 0.3 + }, + 'mediaTypes': { + 'banner': {'sizes': [[300, 250]]} + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '9b954797-d6f4-4730-9cbe-5a1bc8480f52', + 'sizes': [[300, 250]], + 'bidId': '215f48d07eb8b8', + 'bidderRequestId': '1f5c279a4c5de3', + 'auctionId': '5ed4f607-e11c-45b0-aba9-f67768e1f9f4', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'auctionStart': 1573123289380, + 'timeout': 9000, + 'start': 1573123289383 + } + const request = spec.buildRequests(bidderRequest['bids'], bidderRequest); + it('Returns POST method', function () { + expect(request[0].method).to.equal('POST'); + expect(request[0].url.indexOf('//us-west.solortb.com/adx/api/rtb?from=4') !== -1).to.equal(true); + expect(request[0].data).to.exist; + }); + it('request params multi size format object check', function () { + let bidderRequest = { + 'bidderCode': 'newborntownWeb', + 'bidderRequestId': '1f5c279a4c5de3', + 'bids': [ + { + 'bidder': 'newborntownWeb', + 'params': { + 'publisher_id': '1238122', + 'slot_id': '123123', + 'bidfloor': 0.3 + }, + 'mediaTypes': { + 'native': {'sizes': [[300, 250]]} + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': '9b954797-d6f4-4730-9cbe-5a1bc8480f52', + 'sizes': [300, 250], + 'bidId': '215f48d07eb8b8', + 'bidderRequestId': '1f5c279a4c5de3', + 'auctionId': '5ed4f607-e11c-45b0-aba9-f67768e1f9f4', + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'auctionStart': 1573123289380, + 'timeout': 9000, + 'start': 1573123289383 + } + let requstTest = spec.buildRequests(bidderRequest['bids'], bidderRequest) + expect(requstTest[0].data.imp[0].banner.w).to.equal(300); + expect(requstTest[0].data.imp[0].banner.h).to.equal(250); + }); + }) + describe('interpretResponse', function () { + let serverResponse; + let bidRequest = { + data: { + bidId: '2d359291dcf53b' + } + }; + beforeEach(function () { + serverResponse = { + 'body': { + 'id': '174548259807190369860081', + 'seatbid': [ + { + 'bid': [ + { + 'id': '1573540665390298996', + 'impid': '1', + 'price': 0.3001, + 'adid': '1573540665390299172', + 'nurl': 'https://us-west.solortb.com/winnotice?price=${AUCTION_PRICE}&ssp=4&req_unique_id=740016d1-175b-4c19-9744-58a59632dabe&unique_id=06b08e40-2489-439a-8f9e-6413f3dd0bc8&isbidder=1&up=bQyvVo7tgbBVW2dDXzTdBP95Mv35YqqEika0T_btI1h6xjqA8GSXQe51_2CCHQcfuwAEOgdwN8u3VgUHmCuqNPKiBmIPaYUOQBBKjJr05zeKtabKnGT7_JJKcurrXqQ5Sl804xJear_qf2-jOaKB4w', + 'adm': "
", + 'adomain': [ + 'newborntown.com' + ], + 'iurl': 'https://sdkvideo.s3.amazonaws.com/4aa1d9533c4ce71bb1cf750ed38e3a58.png', + 'cid': '345', + 'crid': '41_11113', + 'cat': [ + '' + ], + 'h': 250, + 'w': 300 + } + ], + 'seat': '1' + } + ], + 'bidid': 'bid1573540665390298585' + }, + 'headers': { + + } + } + }); + it('result is correct', function () { + const result = spec.interpretResponse(serverResponse, bidRequest); + if (result && result[0]) { + expect(result[0].requestId).to.equal('2d359291dcf53b'); + expect(result[0].cpm).to.equal(0.3001); + expect(result[0].width).to.equal(300); + expect(result[0].height).to.equal(250); + expect(result[0].creativeId).to.equal('345'); + } + }); + }) +}) diff --git a/test/spec/modules/oneVideoBidAdapter_spec.js b/test/spec/modules/oneVideoBidAdapter_spec.js index 58b90b0a017..3d21601387b 100644 --- a/test/spec/modules/oneVideoBidAdapter_spec.js +++ b/test/spec/modules/oneVideoBidAdapter_spec.js @@ -5,7 +5,21 @@ import {config} from 'src/config'; describe('OneVideoBidAdapter', function () { let bidRequest; - let bidderRequest; + let bidderRequest = { + 'bidderCode': 'oneVideo', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '1e498b84fffc39', + 'bids': bidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' + } + }; let mockConfig; beforeEach(function () { @@ -30,9 +44,10 @@ describe('OneVideoBidAdapter', function () { position: 1, delivery: [2], playbackmethod: [1, 5], - placement: 123, sid: 134, - rewarded: 1 + rewarded: 1, + placement: 1, + inventoryid: 123 }, site: { id: 1, @@ -67,9 +82,10 @@ describe('OneVideoBidAdapter', function () { position: 1, delivery: [2], playbackmethod: [1, 5], - placement: 123, sid: 134, - rewarded: 1 + rewarded: 1, + placement: 1, + inventoryid: 123 } }; expect(spec.isBidRequestValid(bidRequest)).to.equal(false); @@ -85,9 +101,10 @@ describe('OneVideoBidAdapter', function () { position: 1, delivery: [2], playbackmethod: [1, 5], - placement: 123, sid: 134, - rewarded: 1 + rewarded: 1, + placement: 1, + inventoryid: 123 }, pubId: 'brxd' }; @@ -102,34 +119,36 @@ describe('OneVideoBidAdapter', function () { describe('spec.buildRequests', function () { it('should create a POST request for every bid', function () { - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], bidderRequest); expect(requests[0].method).to.equal('POST'); - expect(requests[0].url).to.equal(location.protocol + spec.ENDPOINT + bidRequest.params.pubId); + expect(requests[0].url).to.equal(spec.ENDPOINT + bidRequest.params.pubId); }); it('should attach the bid request object', function () { - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], bidderRequest); expect(requests[0].bidRequest).to.equal(bidRequest); }); it('should attach request data', function () { - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], bidderRequest); const data = requests[0].data; const [ width, height ] = bidRequest.sizes; const placement = bidRequest.params.video.placement; const rewarded = bidRequest.params.video.rewarded; + const inventoryid = bidRequest.params.video.inventoryid; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); - expect(data.imp[0].ext.placement).to.equal(placement); expect(data.imp[0].bidfloor).to.equal(bidRequest.params.bidfloor); expect(data.imp[0].ext.rewarded).to.equal(rewarded); + expect(data.imp[0].video.placement).to.equal(placement); + expect(data.imp[0].ext.inventoryid).to.equal(inventoryid); }); it('must parse bid size from a nested array', function () { const width = 640; const height = 480; bidRequest.sizes = [[ width, height ]]; - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], bidderRequest); const data = requests[0].data; expect(data.imp[0].video.w).to.equal(width); expect(data.imp[0].video.h).to.equal(height); @@ -180,9 +199,23 @@ describe('OneVideoBidAdapter', function () { describe('when GDPR applies', function () { beforeEach(function () { bidderRequest = { - gdprConsent: { - consentString: 'test-gdpr-consent-string', - gdprApplies: true + 'gdprConsent': { + 'consentString': 'test-gdpr-consent-string', + 'gdprApplies': true + }, + 'uspConsent\'s': '1YN-', + 'bidderCode': 'oneVideo', + 'auctionId': 'e158486f-8c7f-472f-94ce-b0cbfbb50ab4', + 'bidderRequestId': '1e498b84fffc39', + 'bids': bidRequest, + 'auctionStart': 1520001292880, + 'timeout': 3000, + 'start': 1520001292884, + 'doneCbCallCount': 0, + 'refererInfo': { + 'numIframes': 1, + 'reachedTop': true, + 'referer': 'test.com' } }; @@ -205,11 +238,156 @@ describe('OneVideoBidAdapter', function () { expect(request[0].data.user.ext.consent).to.equal(bidderRequest.gdprConsent.consentString); }); + it('should send the uspConsent string', function () { + const request = spec.buildRequests([ bidRequest ], bidderRequest); + expect(request[0].data.regs.ext.us_privacy).to.equal(bidderRequest.uspConsent); + }); + it('should send schain object', function () { - const requests = spec.buildRequests([ bidRequest ]); + const requests = spec.buildRequests([ bidRequest ], bidderRequest); const data = requests[0].data; expect(data.source.ext.schain.nodes[0].sid).to.equal(bidRequest.params.video.sid); expect(data.source.ext.schain.nodes[0].rid).to.equal(data.id); }); }); + describe('should send banner object', function () { + it('should send banner object when display is 1', function () { + bidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1, 5], + placement: 1, + inventoryid: 123, + sid: 134, + display: 1 + }, + site: { + id: 1, + page: 'https://www.yahoo.com/', + referrer: 'http://www.yahoo.com' + }, + pubId: 'OneMDisplay' + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const width = bidRequest.params.video.playerWidth; + const height = bidRequest.params.video.playerHeight; + const position = bidRequest.params.video.position; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + expect(data.imp[0].banner.w).to.equal(width); + expect(data.imp[0].banner.h).to.equal(height); + expect(data.imp[0].banner.pos).to.equal(position); + expect(data.imp[0].ext.inventoryid).to.equal(bidRequest.params.video.inventoryid); + expect(data.imp[0].banner.mimes).to.equal(bidRequest.params.video.mimes); + expect(data.imp[0].banner.placement).to.equal(bidRequest.params.video.placement); + expect(data.site.id).to.equal(bidRequest.params.site.id); + }); + it('should send video object when display is other than 1', function () { + bidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1, 5], + placement: 123, + sid: 134, + display: 12 + }, + site: { + id: 1, + page: 'https://www.yahoo.com/', + referrer: 'http://www.yahoo.com' + }, + pubId: 'OneMDisplay' + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const width = bidRequest.params.video.playerWidth; + const height = bidRequest.params.video.playerHeight; + const position = bidRequest.params.video.position; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].video.pos).to.equal(position); + expect(data.imp[0].video.mimes).to.equal(bidRequest.params.video.mimes); + }); + it('should send video object when display is not passed', function () { + bidRequest = { + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 480] + } + }, + bidder: 'oneVideo', + sizes: [640, 480], + bidId: '30b3efwfwe1e', + adUnitCode: 'video1', + params: { + video: { + playerWidth: 640, + playerHeight: 480, + mimes: ['video/mp4', 'application/javascript'], + protocols: [2, 5], + api: [2], + position: 1, + delivery: [2], + playbackmethod: [1, 5], + placement: 123, + sid: 134 + }, + site: { + id: 1, + page: 'https://www.yahoo.com/', + referrer: 'http://www.yahoo.com' + }, + pubId: 'OneMDisplay' + } + }; + const requests = spec.buildRequests([ bidRequest ], bidderRequest); + const data = requests[0].data; + const width = bidRequest.params.video.playerWidth; + const height = bidRequest.params.video.playerHeight; + const position = bidRequest.params.video.position; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + expect(data.imp[0].video.w).to.equal(width); + expect(data.imp[0].video.h).to.equal(height); + expect(data.imp[0].video.pos).to.equal(position); + expect(data.imp[0].video.mimes).to.equal(bidRequest.params.video.mimes); + }); + }); }); diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 0002d25c37d..353c3342627 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -234,12 +234,6 @@ describe('OpenxAdapter', function () { videoBidWithMediaTypes.params = {}; expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); }); - it('should send bid request to openx url via GET, with mediaType specified as video', function () { - const request = spec.buildRequests([videoBidWithMediaTypes]); - expect(request[0].url).to.equal(`https://${videoBidWithMediaTypes.params.delDomain}${URLBASEVIDEO}`); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); }); describe('and request config uses both delDomain and platform', () => { const videoBidWithDelDomainAndPlatform = { @@ -269,12 +263,6 @@ describe('OpenxAdapter', function () { videoBidWithMediaTypes.params = {}; expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); }); - it('should send bid request to openx url via GET, with mediaType specified as video', function () { - const request = spec.buildRequests([videoBidWithDelDomainAndPlatform]); - expect(request[0].url).to.equal(`https://u.openx.net${URLBASEVIDEO}`); - expect(request[0].data.ph).to.equal(videoBidWithDelDomainAndPlatform.params.platform); - expect(request[0].method).to.equal('GET'); - }); }); describe('and request config uses mediaType', () => { const videoBidWithMediaType = { @@ -301,30 +289,11 @@ describe('OpenxAdapter', function () { videoBidWithMediaType.params = {}; expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); }); - it('should send bid request to openx url via GET, with mediaType specified as video', function () { - const request = spec.buildRequests([videoBidWithMediaType]); - expect(request[0].url).to.equal(`https://${videoBidWithMediaType.params.delDomain}${URLBASEVIDEO}`); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); }); }); }); describe('buildRequests for banner ads', function () { - const bidRequestsWithMediaType = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'mediaType': 'banner', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; const bidRequestsWithMediaTypes = [{ 'bidder': 'openx', 'params': { @@ -356,6 +325,7 @@ describe('OpenxAdapter', function () { 'bidderRequestId': 'test-bid-request-2', 'auctionId': 'test-auction-2' }]; + const bidRequestsWithPlatform = [{ 'bidder': 'openx', 'params': { @@ -388,22 +358,17 @@ describe('OpenxAdapter', function () { 'auctionId': 'test-auction-1' }]; - it('should send bid request to openx url via GET, with mediaType specified as banner', function () { - const request = spec.buildRequests(bidRequestsWithMediaType); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaType[0].params.delDomain + URLBASE); - expect(request[0].data.ph).to.be.undefined; - expect(request[0].method).to.equal('GET'); - }); + const mockBidderRequest = {refererInfo: {}}; it('should send bid request to openx url via GET, with mediaTypes specified with banner type', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASE); expect(request[0].data.ph).to.be.undefined; expect(request[0].method).to.equal('GET'); }); it('should send bid request to openx platform url via GET, if platform is present', function () { - const request = spec.buildRequests(bidRequestsWithPlatform); + const request = spec.buildRequests(bidRequestsWithPlatform, mockBidderRequest); expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); expect(request[0].method).to.equal('GET'); @@ -444,14 +409,14 @@ describe('OpenxAdapter', function () { 'auctionId': 'test-auction-1' }]; - const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain); + const request = spec.buildRequests(bidRequestsWithPlatformAndDelDomain, mockBidderRequest); expect(request[0].url).to.equal(`https://u.openx.net${URLBASE}`); expect(request[0].data.ph).to.equal(bidRequestsWithPlatform[0].params.platform); expect(request[0].method).to.equal('GET'); }); it('should send the adunit codes', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.divIds).to.equal(`${encodeURIComponent(bidRequestsWithMediaTypes[0].adUnitCode)},${encodeURIComponent(bidRequestsWithMediaTypes[1].adUnitCode)}`); }); @@ -486,7 +451,7 @@ describe('OpenxAdapter', function () { 'bidderRequestId': 'test-bid-request-2', 'auctionId': 'test-auction-2' }]; - const request = spec.buildRequests(bidRequestsWithUnitIds); + const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); expect(request[0].data.auid).to.equal(`,${bidRequestsWithUnitIds[1].params.unit}`); }); @@ -520,39 +485,10 @@ describe('OpenxAdapter', function () { 'bidderRequestId': 'test-bid-request-2', 'auctionId': 'test-auction-2' }]; - const request = spec.buildRequests(bidRequestsWithoutUnitIds); + const request = spec.buildRequests(bidRequestsWithoutUnitIds, mockBidderRequest); expect(request[0].data).to.not.have.any.keys('auid'); }); - describe('when there is a legacy request with no media type', function () { - const deprecatedBidRequestsFormatWithNoMediaType = [{ - 'bidder': 'openx', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475' - }]; - - let requestData; - - beforeEach(function () { - requestData = spec.buildRequests(deprecatedBidRequestsFormatWithNoMediaType)[0].data; - }); - - it('should have an ad unit id', function () { - expect(requestData.auid).to.equal('12345678'); - }); - - it('should have ad sizes', function () { - expect(requestData.aus).to.equal('300x250,300x600'); - }); - }); - it('should send out custom params on bids that have customParams specified', function () { const bidRequest = Object.assign({}, bidRequestsWithMediaTypes[0], @@ -565,7 +501,7 @@ describe('OpenxAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], mockBidderRequest); const dataParams = request[0].data; expect(dataParams.tps).to.exist; @@ -584,7 +520,7 @@ describe('OpenxAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], mockBidderRequest); const dataParams = request[0].data; expect(dataParams.aumfs).to.exist; @@ -603,7 +539,7 @@ describe('OpenxAdapter', function () { } ); - const request = spec.buildRequests([bidRequest]); + const request = spec.buildRequests([bidRequest], mockBidderRequest); const dataParams = request[0].data; expect(dataParams.bc).to.exist; @@ -611,7 +547,7 @@ describe('OpenxAdapter', function () { }); it('should not send any consent management properties', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data.gdpr).to.equal(undefined); expect(request[0].data.gdpr_consent).to.equal(undefined); expect(request[0].data.x_gdpr_f).to.equal(undefined); @@ -669,7 +605,8 @@ describe('OpenxAdapter', function () { gdprConsent: { consentString: 'test-gdpr-consent-string', gdprApplies: true - } + }, + refererInfo: {} }; mockConfig = { @@ -710,7 +647,8 @@ describe('OpenxAdapter', function () { gdprConsent: { consentString: 'test-gdpr-consent-string', gdprApplies: false - } + }, + refererInfo: {} }; mockConfig = { @@ -751,7 +689,8 @@ describe('OpenxAdapter', function () { gdprConsent: { consentString: 'test-gdpr-consent-string', gdprApplies: true - } + }, + refererInfo: {} }; mockConfig = { @@ -823,7 +762,7 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-2', auctionId: 'test-auction-2' }]; - const request = spec.buildRequests(bidRequestsWithoutCoppa); + const request = spec.buildRequests(bidRequestsWithoutCoppa, mockBidderRequest); expect(request[0].data).to.not.have.any.keys('tfcd'); }); @@ -861,7 +800,7 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-2', auctionId: 'test-auction-2' }]; - const request = spec.buildRequests(bidRequestsWithCoppa); + const request = spec.buildRequests(bidRequestsWithCoppa, mockBidderRequest); expect(request[0].data.tfcd).to.equal(1); }); @@ -898,7 +837,7 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-2', auctionId: 'test-auction-2' }]; - const request = spec.buildRequests(bidRequestsWithoutDnt); + const request = spec.buildRequests(bidRequestsWithoutDnt, mockBidderRequest); expect(request[0].data).to.not.have.any.keys('ns'); }); @@ -936,7 +875,7 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-2', auctionId: 'test-auction-2' }]; - const request = spec.buildRequests(bidRequestsWithDnt); + const request = spec.buildRequests(bidRequestsWithDnt, mockBidderRequest); expect(request[0].data.ns).to.equal(1); }); @@ -998,7 +937,7 @@ describe('OpenxAdapter', function () { }); it('should send a schain parameter with the proper delimiter symbols', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, mockBidderRequest); const dataParams = request[0].data; const numNodes = schainConfig.nodes.length; @@ -1011,7 +950,7 @@ describe('OpenxAdapter', function () { }); it('should send a schain with the right version', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, mockBidderRequest); const dataParams = request[0].data; let serializedSupplyChain = dataParams.schain.split('!'); let version = serializedSupplyChain.shift().split(',')[0]; @@ -1020,7 +959,7 @@ describe('OpenxAdapter', function () { }); it('should send a schain with the right complete value', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, mockBidderRequest); const dataParams = request[0].data; let serializedSupplyChain = dataParams.schain.split('!'); let isComplete = serializedSupplyChain.shift().split(',')[1]; @@ -1029,7 +968,7 @@ describe('OpenxAdapter', function () { }); it('should send all available params in the right order', function () { - const request = spec.buildRequests(bidRequests); + const request = spec.buildRequests(bidRequests, mockBidderRequest); const dataParams = request[0].data; let serializedSupplyChain = dataParams.schain.split('!'); serializedSupplyChain.shift(); @@ -1051,7 +990,7 @@ describe('OpenxAdapter', function () { describe('when there are userid providers', function () { describe('with publisher common id', function () { it('should not send a pubcid query param when there is no crumbs.pubcid and no userId.pubcid defined in the bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaType); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data).to.not.have.any.keys('pubcid'); }); @@ -1075,7 +1014,7 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1' }]; - const request = spec.buildRequests(bidRequestsWithPubcid); + const request = spec.buildRequests(bidRequestsWithPubcid, mockBidderRequest); expect(request[0].data.pubcid).to.equal('c4a4c843-2368-4b5e-b3b1-6ee4702b9ad6'); }); @@ -1099,14 +1038,14 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1' }]; - const request = spec.buildRequests(bidRequestsWithPubcid); + const request = spec.buildRequests(bidRequestsWithPubcid, mockBidderRequest); expect(request[0].data.pubcid).to.equal('c1a4c843-2368-4b5e-b3b1-6ee4702b9ad6'); }); }); describe('with the trade desk unified id', function () { it('should not send a tdid query param when there is no userId.tdid defined in the bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaType); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data).to.not.have.any.keys('ttduuid'); }); @@ -1130,14 +1069,14 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1' }]; - const request = spec.buildRequests(bidRequestsWithTdid); + const request = spec.buildRequests(bidRequestsWithTdid, mockBidderRequest); expect(request[0].data.ttduuid).to.equal('00000000-aaaa-1111-bbbb-222222222222'); }); }); describe('with the liveRamp identity link envelope', function () { it('should not send a tdid query param when there is no userId.lre defined in the bid requests', function () { - const request = spec.buildRequests(bidRequestsWithMediaType); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].data).to.not.have.any.keys('lre'); }); @@ -1161,7 +1100,7 @@ describe('OpenxAdapter', function () { bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1' }]; - const request = spec.buildRequests(bidRequestsWithLiveRampEnvelope); + const request = spec.buildRequests(bidRequestsWithLiveRampEnvelope, mockBidderRequest); expect(request[0].data.lre).to.equal('00000000-aaaa-1111-bbbb-222222222222'); }); }); @@ -1187,36 +1126,15 @@ describe('OpenxAdapter', function () { 'auctionId': '1d1a030790a475', 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' }]; - - const bidRequestsWithMediaType = [{ - 'bidder': 'openx', - 'mediaType': 'video', - 'params': { - 'unit': '12345678', - 'delDomain': 'test-del-domain' - }, - 'adUnitCode': 'adunit-code', - 'sizes': [640, 480], - 'bidId': '30b31c1838de1e', - 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', - 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' - }]; - - it('should send bid request to openx url via GET, with mediaType as video', function () { - const request = spec.buildRequests(bidRequestsWithMediaType); - expect(request[0].url).to.equal('https://' + bidRequestsWithMediaType[0].params.delDomain + URLBASEVIDEO); - expect(request[0].method).to.equal('GET'); - }); + const mockBidderRequest = {refererInfo: {}}; it('should send bid request to openx url via GET, with mediaTypes having video parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); expect(request[0].url).to.equal('https://' + bidRequestsWithMediaTypes[0].params.delDomain + URLBASEVIDEO); expect(request[0].method).to.equal('GET'); }); - it('should have the correct parameters', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); const dataParams = request[0].data; expect(dataParams.auid).to.equal('12345678'); @@ -1225,7 +1143,7 @@ describe('OpenxAdapter', function () { }); it('should send a bc parameter', function () { - const request = spec.buildRequests(bidRequestsWithMediaTypes); + const request = spec.buildRequests(bidRequestsWithMediaTypes, mockBidderRequest); const dataParams = request[0].data; expect(dataParams.bc).to.have.string('hb_pb'); @@ -1233,6 +1151,7 @@ describe('OpenxAdapter', function () { describe('when using the video param', function () { let videoBidRequest; + let mockBidderRequest = {refererInfo: {}}; beforeEach(function () { videoBidRequest = { @@ -1253,13 +1172,14 @@ describe('OpenxAdapter', function () { 'auctionId': '1d1a030790a475', 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' } + mockBidderRequest = {refererInfo: {}}; }); it('should not allow you to set a url', function () { videoBidRequest.params.video = { url: 'test-url' }; - const request = spec.buildRequests([videoBidRequest]); + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); expect(request[0].data.url).to.be.undefined; }); @@ -1269,7 +1189,7 @@ describe('OpenxAdapter', function () { videoBidRequest.params.video = { ju: myUrl }; - const request = spec.buildRequests([videoBidRequest]); + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); expect(request[0].data.ju).to.not.equal(myUrl); }); @@ -1280,7 +1200,7 @@ describe('OpenxAdapter', function () { videoBidRequest.params.video = { openrtb: myOpenRTBObject }; - const request = spec.buildRequests([videoBidRequest]); + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); expect(request[0].data.openrtb).to.equal(JSON.stringify(myOpenRTBObject)); }); @@ -1292,7 +1212,7 @@ describe('OpenxAdapter', function () { videoBidRequest.params.video = { openrtb: myOpenRTBObject }; - const request = spec.buildRequests([videoBidRequest]); + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); const openRtbRequestParams = JSON.parse(request[0].data.openrtb); expect(openRtbRequestParams.w).to.not.equal(width); @@ -1308,7 +1228,7 @@ describe('OpenxAdapter', function () { }; videoBidRequest.mediaTypes.video.playerSize = undefined; - const request = spec.buildRequests([videoBidRequest]); + const request = spec.buildRequests([videoBidRequest], mockBidderRequest); const openRtbRequestParams = JSON.parse(request[0].data.openrtb); expect(openRtbRequestParams.w).to.equal(width); @@ -1339,10 +1259,13 @@ describe('OpenxAdapter', function () { auctionId: '1d1a030790a475', transactionId: '4008d88a-8137-410b-aa35-fbfdabcb478e' }; + let mockBidderRequest = {refererInfo: {}}; + + it('should default to a banner request', function () { + const request = spec.buildRequests([multiformatBid], mockBidderRequest); + const dataParams = request[0].data; - it('should send bid request to openx url via GET, with mediaType specified as banner', function () { - const request = spec.buildRequests([multiformatBid]); - expect(request[0].url).to.equal(`https://${multiformatBid.params.delDomain}${URLBASE}`); + expect(dataParams.divIds).to.have.string(multiformatBid.adUnitCode); }); }); @@ -1450,12 +1373,6 @@ describe('OpenxAdapter', function () { it('should return a brand ID', function () { expect(bid.meta.dspid).to.equal(DEFAULT_TEST_ARJ_AD_UNIT.adv_id); }); - - it('should register a beacon', function () { - resetBoPixel(); - spec.interpretResponse({body: bidResponse}, bidRequest); - sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(new RegExp(`https:\/\/openx-d\.openx\.net.*\/bo\?.*ts=${adUnitOverride.ts}`))); - }); }); describe('when there is a deal', function () { @@ -1745,14 +1662,6 @@ describe('OpenxAdapter', function () { const result = spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaType); expect(result.length).to.equal(0); }); - - it('should register a beacon', function () { - resetBoPixel(); - spec.interpretResponse({body: bidResponse}, bidRequestsWithMediaTypes); - sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/^https:\/\/test-colo\.com/)); - sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/ph=test-ph/)); - sinon.assert.calledWith(userSync.registerSync, 'image', 'openx', sinon.match(/ts=test-ts/)); - }); }); describe('user sync', function () { diff --git a/test/spec/modules/openxoutstreamBidAdapter_spec.js b/test/spec/modules/openxoutstreamBidAdapter_spec.js index 634df1c8c6a..9d2b7082a22 100644 --- a/test/spec/modules/openxoutstreamBidAdapter_spec.js +++ b/test/spec/modules/openxoutstreamBidAdapter_spec.js @@ -13,12 +13,6 @@ describe('OpenXOutstreamAdapter', function () { const CR_ID = '2052941939925262540'; const AD_ID = '1991358644725162800'; - describe('inherited functions', function () { - it('exists and is a function', function () { - expect(adapter.callBids).to.exist.and.to.be.a('function'); - }); - }); - describe('isBidRequestValid', function () { describe('when request is for a banner ad', function () { let bannerBid; @@ -80,8 +74,10 @@ describe('OpenXOutstreamAdapter', function () { 'auctionId': '1d1a030790a475' }]; + const mockBidderRequest = {refererInfo: {}}; + it('should send bid request to openx url via GET, with mediaType specified as banner', function () { - const request = spec.buildRequests(bidRequestsWithMediaType); + const request = spec.buildRequests(bidRequestsWithMediaType, mockBidderRequest); const params = bidRequestsWithMediaType[0].params; expect(request[0].url).to.equal(`https://` + params.delDomain + URLBASE); expect(request[0].method).to.equal('GET'); @@ -97,7 +93,6 @@ describe('OpenXOutstreamAdapter', function () { 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', - sizes: [300, 250], mediaTypes: { banner: { sizes: [[728, 90]] @@ -112,7 +107,6 @@ describe('OpenXOutstreamAdapter', function () { 'delDomain': 'test-del-domain' }, 'adUnitCode': 'adunit-code', - 'sizes': [300, 250], mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] @@ -122,7 +116,7 @@ describe('OpenXOutstreamAdapter', function () { 'bidderRequestId': 'test-bid-request-1', 'auctionId': 'test-auction-1' }]; - const request = spec.buildRequests(bidRequestsWithUnitIds); + const request = spec.buildRequests(bidRequestsWithUnitIds, mockBidderRequest); expect(request[0].data.auid).to.equal(`${bidRequestsWithUnitIds[0].params.unit}`); expect(request[0].data.vht).to.not.equal(`${bidRequestsWithUnitIds[0].params.height}`); expect(request[0].data.vwd).to.not.equal(`${bidRequestsWithUnitIds[0].params.width}`); diff --git a/test/spec/modules/outconBidAdapter_spec.js b/test/spec/modules/outconBidAdapter_spec.js index a263bc9dbf9..d9e763b9df9 100644 --- a/test/spec/modules/outconBidAdapter_spec.js +++ b/test/spec/modules/outconBidAdapter_spec.js @@ -23,7 +23,6 @@ describe('outconBidAdapter', function () { })).to.equal(true); }); }); - describe('buildRequests', function () { it('Build requests with pod param', function () { expect(spec.buildRequests([{ @@ -34,7 +33,6 @@ describe('outconBidAdapter', function () { } }])).to.have.keys('method', 'url', 'data'); }); - it('Build requests with internalID and publisherID params', function () { expect(spec.buildRequests([{ bidder: 'outcon', @@ -46,11 +44,10 @@ describe('outconBidAdapter', function () { }])).to.have.keys('method', 'url', 'data'); }); }); - describe('interpretResponse', function () { const bidRequest = { method: 'GET', - url: 'http://test.outcondigital.com:8048/ad/', + url: 'https://test.outcondigital.com/ad/', data: { pod: '5d603538eba7192ae14e39a4', env: 'test', @@ -64,7 +61,7 @@ describe('outconBidAdapter', function () { exp: 10, creatives: [ { - url: 'http://test.outcondigital.com/uploads/5d42e7a7306ea4689b67c122/frutas.mp4', + url: 'https://test.outcondigital.com/uploads/5d42e7a7306ea4689b67c122/frutas.mp4', size: 3, width: 1920, height: 1080, @@ -74,8 +71,8 @@ describe('outconBidAdapter', function () { ad: '5d6e6aef22063e392bf7f564', type: 'video', campaign: '5d42e44b306ea469593c76a2', - trackingURL: 'http://test.outcondigital.com:8048/ad/track?track=5d6e6aef22063e392bf7f564', - vastURL: 'http://test.outcondigital.com:8048/outcon.xml?impression=5d6e6aef22063e392bf7f564&demo=true' + trackingURL: 'https://test.outcondigital.com/ad/track?track=5d6e6aef22063e392bf7f564', + vastURL: 'https://test.outcondigital.com/outcon.xml?impression=5d6e6aef22063e392bf7f564&demo=true' }, }; it('check all the keys that are needed to interpret the response', function () { diff --git a/test/spec/modules/polluxBidAdapter_spec.js b/test/spec/modules/polluxBidAdapter_spec.js index ad30771e15b..22c7f470f83 100644 --- a/test/spec/modules/polluxBidAdapter_spec.js +++ b/test/spec/modules/polluxBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/polluxBidAdapter'; import {utils} from 'src/utils'; import {newBidder} from 'src/adapters/bidderFactory'; +import { parseQS } from 'src/url'; describe('POLLUX Bid Adapter tests', function () { // ad units setup @@ -178,13 +179,12 @@ describe('POLLUX Bid Adapter tests', function () { it('TEST: verify url and query params', function () { const URL = require('url-parse'); - const querystringify = require('querystringify'); const request = spec.buildRequests(setup_single_bid); const parsedUrl = new URL('https:' + request.url); expect(parsedUrl.origin).to.equal('https://adn.polluxnetwork.com'); expect(parsedUrl.pathname).to.equal('/prebid/v1'); expect(parsedUrl).to.have.property('query'); - const parsedQuery = querystringify.parse(parsedUrl.query); + const parsedQuery = parseQS(parsedUrl.query); expect(parsedQuery).to.have.property('domain').and.to.have.length.above(1); }); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 7945be68282..52c8919b557 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -41,6 +41,15 @@ const REQUEST = { 'required': true, 'sizes': [989, 742], }, + 'icon': { + 'required': true, + 'aspect_ratios': [{ + 'min_height': 10, + 'min_width': 10, + 'ratio_height': 1, + 'ratio_width': 1 + }] + }, 'sponsoredBy': { 'required': true } @@ -344,6 +353,7 @@ const RESPONSE_OPENRTB = { 'seat': 'appnexus' }, ], + 'cur': 'EUR', 'ext': { 'responsetimemillis': { 'appnexus': 8, @@ -418,6 +428,19 @@ const RESPONSE_OPENRTB_NATIVE = { } } }, + { + 'id': 2, + 'img': { + 'url': 'https://vcdn.adnxs.com/p/creative-image/1a/3e/e9/5b/1a3ee95b-06cd-4260-98c7-0258627c9197.png', + 'w': 127, + 'h': 83, + 'ext': { + 'appnexus': { + 'prevent_crop': 0 + } + } + } + }, { 'id': 0, 'title': { @@ -425,7 +448,7 @@ const RESPONSE_OPENRTB_NATIVE = { } }, { - 'id': 2, + 'id': 3, 'data': { 'value': 'Prebid.org' } @@ -558,7 +581,7 @@ describe('S2S Adapter', function () { it('should not add outstrean without renderer', function () { let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; config.setConfig({ s2sConfig: ortb2Config }); adapter.callBids(OUTSTREAM_VIDEO_REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -589,7 +612,7 @@ describe('S2S Adapter', function () { it('adds gdpr consent information to ortb2 request depending on presence of module', function () { let ortb2Config = utils.deepClone(CONFIG); - ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; let consentConfig = { consentManagement: { cmpApi: 'iab' }, s2sConfig: ortb2Config }; config.setConfig(consentConfig); @@ -680,6 +703,109 @@ describe('S2S Adapter', function () { }); }); + describe('us_privacy (ccpa) consent data', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('is added to ortb2 request when in bidRequest', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + config.setConfig({ s2sConfig: ortb2Config }); + + let uspBidRequest = utils.deepClone(BID_REQUESTS); + uspBidRequest[0].uspConsent = '1NYN'; + + adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); + + config.resetConfig(); + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[1].requestBody); + + expect(requestBid.regs).to.not.exist; + }); + + it('is added to cookie_sync request when in bidRequest', function () { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + config.setConfig({ s2sConfig: cookieSyncConfig }); + + let uspBidRequest = utils.deepClone(BID_REQUESTS); + uspBidRequest[0].uspConsent = '1YNN'; + + adapter.callBids(REQUEST, uspBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.us_privacy).is.equal('1YNN'); + expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); + expect(requestBid.account).is.equal('1'); + }); + }); + + describe('gdpr and us_privacy (ccpa) consent data', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('is added to ortb2 request when in bidRequest', function () { + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + config.setConfig({ s2sConfig: ortb2Config }); + + let consentBidRequest = utils.deepClone(BID_REQUESTS); + consentBidRequest[0].uspConsent = '1NYN'; + consentBidRequest[0].gdprConsent = { + consentString: 'abc123', + gdprApplies: true + }; + + adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.regs.ext.us_privacy).is.equal('1NYN'); + expect(requestBid.regs.ext.gdpr).is.equal(1); + expect(requestBid.user.ext.consent).is.equal('abc123'); + + config.resetConfig(); + config.setConfig({ s2sConfig: CONFIG }); + + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + requestBid = JSON.parse(requests[1].requestBody); + + expect(requestBid.regs).to.not.exist; + expect(requestBid.user).to.not.exist; + }); + + it('is added to cookie_sync request when in bidRequest', function () { + let cookieSyncConfig = utils.deepClone(CONFIG); + cookieSyncConfig.syncEndpoint = 'https://prebid.adnxs.com/pbs/v1/cookie_sync'; + config.setConfig({ s2sConfig: cookieSyncConfig }); + + let consentBidRequest = utils.deepClone(BID_REQUESTS); + consentBidRequest[0].uspConsent = '1YNN'; + consentBidRequest[0].gdprConsent = { + consentString: 'abc123def', + gdprApplies: true + }; + + adapter.callBids(REQUEST, consentBidRequest, addBidResponse, done, ajax); + let requestBid = JSON.parse(requests[0].requestBody); + + expect(requestBid.us_privacy).is.equal('1YNN'); + expect(requestBid.gdpr).is.equal(1); + expect(requestBid.gdpr_consent).is.equal('abc123def'); + expect(requestBid.bidders).to.contain('appnexus').and.to.have.lengthOf(1); + expect(requestBid.account).is.equal('1'); + }); + }); + it('sets invalid cacheMarkup value to 0', function () { const s2sConfig = Object.assign({}, CONFIG, { cacheMarkup: 999 @@ -829,6 +955,17 @@ describe('S2S Adapter', function () { 'h': 742 } }, + { + 'required': 1, + 'img': { + 'type': 1, + 'wmin': 10, + 'hmin': 10, + 'ext': { + 'aspectratios': ['1:1'] + } + } + }, { 'required': 1, 'data': { @@ -848,7 +985,7 @@ describe('S2S Adapter', function () { const _config = { s2sConfig: s2sConfig, - } + }; config.setConfig(_config); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); @@ -1046,11 +1183,13 @@ describe('S2S Adapter', function () { let userIdBidRequest = utils.deepClone(BID_REQUESTS); userIdBidRequest[0].bids[0].userId = { + criteoId: '44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU', tdid: 'abc123', pubcid: '1234', parrableid: '01.1563917337.test-eid', lipb: { - lipbid: 'li-xyz' + lipbid: 'li-xyz', + segments: ['segA', 'segB'] } }; @@ -1060,12 +1199,17 @@ describe('S2S Adapter', function () { expect(Array.isArray(requestBid.user.ext.eids)).to.be.true; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'adserver.org')).is.not.empty; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'adserver.org')[0].uids[0].id).is.equal('abc123'); - expect(requestBid.user.ext.eids.filter(eid => eid.source === 'pubcommon')).is.not.empty; - expect(requestBid.user.ext.eids.filter(eid => eid.source === 'pubcommon')[0].uids[0].id).is.equal('1234'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'criteo.com')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'criteo.com')[0].uids[0].id).is.equal('44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'pubcid.org')).is.not.empty; + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'pubcid.org')[0].uids[0].id).is.equal('1234'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'parrable.com')).is.not.empty; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'parrable.com')[0].uids[0].id).is.equal('01.1563917337.test-eid'); expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')).is.not.empty; expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].uids[0].id).is.equal('li-xyz'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments.length).is.equal(2); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[0]).is.equal('segA'); + expect(requestBid.user.ext.eids.filter(eid => eid.source === 'liveintent.com')[0].ext.segments[1]).is.equal('segB'); }); it('when config \'currency.adServerCurrency\' value is an array: ORTB has property \'cur\' value set to a single item array', function () { @@ -1401,9 +1545,34 @@ describe('S2S Adapter', function () { expect(response).to.have.property('dealId', 'test-dealid'); }); + it('should set the bidResponse currency to whats in the PBS response', function() { + server.respondWith(JSON.stringify(RESPONSE_OPENRTB)); + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + config.setConfig({ s2sConfig: ortb2Config }); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + sinon.assert.calledOnce(addBidResponse); + const pbjsResponse = addBidResponse.firstCall.args[1]; + expect(pbjsResponse).to.have.property('currency', 'EUR'); + }); + + it('should set the default bidResponse currency when not specified in OpenRTB', function() { + let modifiedResponse = utils.deepClone(RESPONSE_OPENRTB); + modifiedResponse.cur = ''; + let ortb2Config = utils.deepClone(CONFIG); + ortb2Config.endpoint = 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction'; + config.setConfig({ s2sConfig: ortb2Config }); + server.respondWith(JSON.stringify(modifiedResponse)); + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + server.respond(); + sinon.assert.calledOnce(addBidResponse); + const pbjsResponse = addBidResponse.firstCall.args[1]; + expect(pbjsResponse).to.have.property('currency', 'USD'); + }); + it('should pass through default adserverTargeting if present in bidObject', function () { server.respondWith(JSON.stringify(RESPONSE)); - config.setConfig({ s2sConfig: CONFIG }); adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); server.respond(); @@ -1625,7 +1794,7 @@ describe('S2S Adapter', function () { const _config = { s2sConfig: s2sConfig, - } + }; config.setConfig(_config); config.setConfig({ s2sConfig: CONFIG }); diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js new file mode 100644 index 00000000000..302b7634fbc --- /dev/null +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -0,0 +1,86 @@ +import { expect } from 'chai'; +let spec = require('modules/proxistoreBidAdapter'); + +const BIDDER_CODE = 'proxistore'; +describe('ProxistoreBidAdapter', function () { + const bidderRequest = { + 'bidderCode': BIDDER_CODE, + 'auctionId': '1025ba77-5463-4877-b0eb-14b205cb9304', + 'bidderRequestId': '10edf38ec1a719', + 'gdprConsent': { + 'gdprApplies': true, + 'consentString': 'CONSENT_STRING', + } + }; + let bid = { + sizes: [[300, 600]], + params: { + website: 'example.fr', + language: 'fr' + }, + auctionId: 442133079, + bidId: 464646969, + transactionId: 511916005 + }; + describe('isBidRequestValid', function () { + it('it should be true if required params are presents', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const url = '//abs.proxistore.com/fr/v3/rtb/prebid'; + const request = spec.buildRequests([bid], bidderRequest); + it('should return an empty array if no cookie sent', function () { + expect(request).to.be.an('array'); + expect(request.length).to.equal(1); + }); + it('request method should be POST', function () { + expect(request[0].method).to.equal('POST'); + }); + it('should contain a valid url', function () { + expect(request[0].url).equal(url); + }) + }); + + describe('interpretResponse', function () { + const responses = { + body: + [{ + cpm: 6.25, + creativeId: '48fd47c9-ce35-4fda-804b-17e16c8c36ac', + currency: 'EUR', + dealId: '2019-10_e3ecad8e-d07a-4c90-ad46-cd0f306c8960', + height: 600, + netRevenue: true, + requestId: '923756713', + ttl: 10, + vastUrl: null, + vastXml: null, + width: 300, + }] + }; + const badResponse = { body: [] }; + const interpretedResponse = spec.interpretResponse(responses, bid)[0]; + it('should send an empty array if body is empty', function () { + expect(spec.interpretResponse(badResponse, bid)).to.be.an('array'); + expect(spec.interpretResponse(badResponse, bid).length).equal(0); + }); + it('should interprnet the response correctly if it is valid', function () { + expect(interpretedResponse.cpm).equal(6.25); + expect(interpretedResponse.creativeId).equal('48fd47c9-ce35-4fda-804b-17e16c8c36ac'); + expect(interpretedResponse.currency).equal('EUR'); + expect(interpretedResponse.height).equal(600); + expect(interpretedResponse.width).equal(300); + expect(interpretedResponse.requestId).equal('923756713'); + expect(interpretedResponse.netRevenue).to.be.true; + }) + }); + + describe('interpretResponse', function () { + it('should aways return an empty array', function () { + expect(spec.getUserSyncs()).to.be.an('array'); + expect(spec.getUserSyncs().length).equal(0); + }); + }); +}); diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index feb64dfe4ac..85d135d9f79 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -719,9 +719,23 @@ describe('PubMatic adapter', function () { expect(request.method).to.equal('POST'); }); + it('test flag not sent when pubmaticTest=true is absent in page url', function() { + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.test).to.equal(undefined); + }); + + it('test flag set to 1 when pubmaticTest=true is present in page url', function() { + window.location.href += '#pubmaticTest=true'; + // now all the test cases below will have window.location.href with #pubmaticTest=true + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); + expect(data.test).to.equal(1); + }); + it('Request params check', function () { - let request = spec.buildRequests(bidRequests); - let data = JSON.parse(request.data); + let request = spec.buildRequests(bidRequests); + let data = JSON.parse(request.data); expect(data.at).to.equal(1); // auction type expect(data.cur[0]).to.equal('USD'); // currency expect(data.site.domain).to.be.a('string'); // domain should be set @@ -982,6 +996,43 @@ describe('PubMatic adapter', function () { expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid }); + it('Request params check with USP/CCPA Consent', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.ext.us_privacy).to.equal('1NYN');// USP/CCPAs + expect(data.at).to.equal(1); // auction type + expect(data.cur[0]).to.equal('USD'); // currency + expect(data.site.domain).to.be.a('string'); // domain should be set + expect(data.site.page).to.equal(bidRequests[0].params.kadpageurl); // forced pageURL + expect(data.site.publisher.id).to.equal(bidRequests[0].params.publisherId); // publisher Id + expect(data.user.yob).to.equal(parseInt(bidRequests[0].params.yob)); // YOB + expect(data.user.gender).to.equal(bidRequests[0].params.gender); // Gender + expect(data.device.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.device.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.user.geo.lat).to.equal(parseFloat(bidRequests[0].params.lat)); // Latitude + expect(data.user.geo.lon).to.equal(parseFloat(bidRequests[0].params.lon)); // Lognitude + expect(data.ext.wrapper.wv).to.equal($$REPO_AND_VERSION$$); // Wrapper Version + expect(data.ext.wrapper.transactionId).to.equal(bidRequests[0].transactionId); // Prebid TransactionId + expect(data.ext.wrapper.wiid).to.equal(bidRequests[0].params.wiid); // OpenWrap: Wrapper Impression ID + expect(data.ext.wrapper.profile).to.equal(parseInt(bidRequests[0].params.profId)); // OpenWrap: Wrapper Profile ID + expect(data.ext.wrapper.version).to.equal(parseInt(bidRequests[0].params.verId)); // OpenWrap: Wrapper Profile Version ID + + expect(data.imp[0].id).to.equal(bidRequests[0].bidId); // Prebid bid id is passed as id + expect(data.imp[0].bidfloor).to.equal(parseFloat(bidRequests[0].params.kadfloor)); // kadfloor + expect(data.imp[0].tagid).to.equal('/15671365/DMDemo'); // tagid + expect(data.imp[0].banner.w).to.equal(300); // width + expect(data.imp[0].banner.h).to.equal(250); // height + expect(data.imp[0].ext.pmZoneId).to.equal(bidRequests[0].params.pmzoneid.split(',').slice(0, 50).map(id => id.trim()).join()); // pmzoneid + + // second request without USP/CCPA + let request2 = spec.buildRequests(bidRequests, {}); + let data2 = JSON.parse(request2.data); + expect(data2.regs).to.equal(undefined);// USP/CCPAs + }); + it('Request should have digitrust params', function() { window.DigiTrust = { getUser: function () { @@ -1524,7 +1575,7 @@ describe('PubMatic adapter', function () { let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ - 'source': 'pubcommon', + 'source': 'pubcid.org', 'uids': [{ 'id': 'pub_common_user_id', 'atype': 1 @@ -1625,16 +1676,16 @@ describe('PubMatic adapter', function () { }); }); - describe('CriteoRTUS Id', function() { + describe('Criteo Id', function() { it('send the criteo id if it is present', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.criteortus = {pubmatic: {userid: 'criteo-rtus-user-id'}}; + bidRequests[0].userId.criteoId = 'criteo-user-id'; let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.deep.equal([{ - 'source': 'criteortus', + 'source': 'criteo.com', 'uids': [{ - 'id': 'criteo-rtus-user-id', + 'id': 'criteo-user-id', 'atype': 1 }] }]); @@ -1642,23 +1693,19 @@ describe('PubMatic adapter', function () { it('do not pass if not string', function() { bidRequests[0].userId = {}; - bidRequests[0].userId.criteortus = {appnexus: {userid: 'criteo-rtus-user-id'}}; + bidRequests[0].userId.criteoId = 1; let request = spec.buildRequests(bidRequests, {}); let data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.criteortus = {pubmatic: {userid: 1}}; - request = spec.buildRequests(bidRequests, {}); - data = JSON.parse(request.data); - expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.criteortus = {pubmatic: {userid: []}}; + bidRequests[0].userId.criteoId = []; request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.criteortus = {pubmatic: {userid: null}}; + bidRequests[0].userId.criteoId = null; request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); - bidRequests[0].userId.criteortus = {pubmatic: {userid: {}}}; + bidRequests[0].userId.criteoId = {}; request = spec.buildRequests(bidRequests, {}); data = JSON.parse(request.data); expect(data.user.eids).to.equal(undefined); @@ -1772,6 +1819,42 @@ describe('PubMatic adapter', function () { expect(data.user.eids).to.equal(undefined); }); }); + + describe('Britepool Id', function() { + it('send the Britepool id if it is present', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.britepoolid = 'britepool-user-id'; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.deep.equal([{ + 'source': 'britepool.com', + 'uids': [{ + 'id': 'britepool-user-id', + 'atype': 1 + }] + }]); + }); + + it('do not pass if not string', function() { + bidRequests[0].userId = {}; + bidRequests[0].userId.britepoolid = 1; + let request = spec.buildRequests(bidRequests, {}); + let data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.britepoolid = []; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.britepoolid = null; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + bidRequests[0].userId.britepoolid = {}; + request = spec.buildRequests(bidRequests, {}); + data = JSON.parse(request.data); + expect(data.user.eids).to.equal(undefined); + }); + }); }); it('Request params check for video ad', function () { @@ -2412,5 +2495,77 @@ describe('PubMatic adapter', function () { expect(response[0].mediaType).to.equal('native'); }); }); + + describe('getUserSyncs', function() { + const syncurl = 'https://ads.pubmatic.com/AdServer/js/showad.js#PIX&kdntuid=1&p=5670'; + let sandbox; + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + afterEach(function() { + sandbox.restore(); + }) + + it('execute only if iframeEnabled', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: syncurl + }]); + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.equal(undefined); + }); + + it('CCPA/USP', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&us_privacy=1NYN` + }]); + }); + + it('GDPR', function() { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: false, consentString: 'foo'}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=0&gdpr_consent=foo` + }]); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: undefined}, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=` + }]); + }); + + it('COPPA: true', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&coppa=1` + }]); + }); + + it('COPPA: false', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': false + }; + return config[key]; + }); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${syncurl}` + }]); + }); + + it('GDPR + COPPA:true + CCPA/USP', function() { + sandbox.stub(config, 'getConfig').callsFake(key => { + const config = { + 'coppa': true + }; + return config[key]; + }); + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, {gdprApplies: true, consentString: 'foo'}, '1NYN')).to.deep.equal([{ + type: 'iframe', url: `${syncurl}&gdpr=1&gdpr_consent=foo&us_privacy=1NYN&coppa=1` + }]); + }); + }); }); }); diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 9ed6d3631f5..6ab05a0a001 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -138,6 +138,35 @@ describe('PulsePoint Adapter Tests', function () { } } }]; + + const schainParamsSlotConfig = [{ + placementCode: '/DfpAccount1/slot1', + bidId: 'bid12345', + params: { + cp: 'p10000', + ct: 't10000', + cf: '1x1', + bcat: ['IAB-1', 'IAB-20'], + battr: [1, 2, 3], + bidfloor: 1.5, + badv: ['cocacola.com', 'lays.com'] + }, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + }, + }]; + const bidderRequest = { refererInfo: { referer: 'https://publisher.com/home' @@ -200,7 +229,7 @@ describe('PulsePoint Adapter Tests', function () { expect(bid.ttl).to.equal(20); }); - it('Verify use ttl in ext', function () { + it('Verify ttl/currency applied to bid', function () { const request = spec.buildRequests(slotConfigs, bidderRequest); const ortbRequest = request.data; const ortbResponse = { @@ -208,22 +237,31 @@ describe('PulsePoint Adapter Tests', function () { bid: [{ impid: ortbRequest.imp[0].id, price: 1.25, - adm: 'This is an Ad', - ext: { - ttl: 30, - netRevenue: false, - currency: 'INR' - } + adm: 'This is an Ad#1', + crid: 'Creative#123', + exp: 50 + }, { + impid: ortbRequest.imp[1].id, + price: 1.25, + adm: 'This is an Ad#2', + crid: 'Creative#123' }] - }] + }], + cur: 'GBP' }; const bids = spec.interpretResponse({ body: ortbResponse }, request); - expect(bids).to.have.lengthOf(1); + expect(bids).to.have.lengthOf(2); // verify first bid const bid = bids[0]; - expect(bid.ttl).to.equal(30); - expect(bid.netRevenue).to.equal(false); - expect(bid.currency).to.equal('INR'); + expect(bid.cpm).to.equal(1.25); + expect(bid.ad).to.equal('This is an Ad#1'); + expect(bid.ttl).to.equal(50); + expect(bid.currency).to.equal('GBP'); + const secondBid = bids[1]; + expect(secondBid.cpm).to.equal(1.25); + expect(secondBid.ad).to.equal('This is an Ad#2'); + expect(secondBid.ttl).to.equal(20); + expect(secondBid.currency).to.equal('GBP'); }); it('Verify full passback', function () { @@ -393,6 +431,20 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.regs.ext.gdpr).to.equal(1); }); + it('Verify CCPA', function () { + const bidderRequestUSPrivacy = { + uspConsent: '1YYY' + }; + const request = spec.buildRequests(slotConfigs, Object.assign({}, bidderRequest, bidderRequestUSPrivacy)); + expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + // regs object + expect(ortbRequest.regs).to.not.equal(null); + expect(ortbRequest.regs.ext).to.not.equal(null); + expect(ortbRequest.regs.ext.us_privacy).to.equal('1YYY'); + }); + it('Verify Video request', function () { const request = spec.buildRequests(videoSlotConfig, bidderRequest); expect(request.url).to.equal('https://bid.contextweb.com/header/ortb?src=prebid'); @@ -476,6 +528,25 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.imp[1].ext).to.be.null; }); + it('Verify schain parameters', function () { + const request = spec.buildRequests(schainParamsSlotConfig, bidderRequest); + const ortbRequest = request.data; + expect(ortbRequest).to.not.equal(null); + expect(ortbRequest.source).to.not.equal(null); + expect(ortbRequest.source.ext).to.not.equal(null); + expect(ortbRequest.source.ext.schain).to.not.equal(null); + expect(ortbRequest.source.ext.schain.complete).to.equal(1); + expect(ortbRequest.source.ext.schain.ver).to.equal('1.0'); + expect(ortbRequest.source.ext.schain.nodes).to.not.equal(null); + expect(ortbRequest.source.ext.schain.nodes).to.lengthOf(1); + expect(ortbRequest.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); + expect(ortbRequest.source.ext.schain.nodes[0].sid).to.equal('1234'); + expect(ortbRequest.source.ext.schain.nodes[0].hp).to.equal(1); + expect(ortbRequest.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); + expect(ortbRequest.source.ext.schain.nodes[0].name).to.equal('publisher'); + expect(ortbRequest.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); + }); + it('Verify outstream renderer', function () { const bidderRequestOutstream = Object.assign({}, bidderRequest, {bids: [outstreamSlotConfig[0]]}); const request = spec.buildRequests(outstreamSlotConfig, bidderRequestOutstream); @@ -535,15 +606,53 @@ describe('PulsePoint Adapter Tests', function () { expect(ortbRequest.user).to.not.be.undefined; expect(ortbRequest.user.ext).to.not.be.undefined; expect(ortbRequest.user.ext.eids).to.not.be.undefined; - expect(ortbRequest.user.ext.eids).to.have.lengthOf(3); + expect(ortbRequest.user.ext.eids).to.have.lengthOf(2); expect(ortbRequest.user.ext.eids[0].source).to.equal('pubcommon'); expect(ortbRequest.user.ext.eids[0].uids).to.have.lengthOf(1); expect(ortbRequest.user.ext.eids[0].uids[0].id).to.equal('userid_pubcid'); - expect(ortbRequest.user.ext.eids[1].source).to.equal('ttdid'); + expect(ortbRequest.user.ext.eids[1].source).to.equal('adserver.org'); expect(ortbRequest.user.ext.eids[1].uids).to.have.lengthOf(1); expect(ortbRequest.user.ext.eids[1].uids[0].id).to.equal('userid_ttd'); - expect(ortbRequest.user.ext.eids[2].source).to.equal('digitrust'); - expect(ortbRequest.user.ext.eids[2].uids).to.have.lengthOf(1); - expect(ortbRequest.user.ext.eids[2].uids[0].id).to.equal('userid_digitrust'); + expect(ortbRequest.user.ext.eids[1].uids[0].ext).to.not.be.null; + expect(ortbRequest.user.ext.eids[1].uids[0].ext.rtiPartner).to.equal('TDID'); + expect(ortbRequest.user.ext.digitrust).to.not.be.null; + expect(ortbRequest.user.ext.digitrust.id).to.equal('userid_digitrust'); + expect(ortbRequest.user.ext.digitrust.keyv).to.equal(4); + }); + it('Verify new external user id partners', function () { + const bidRequests = deepClone(slotConfigs); + bidRequests[0].userId = { + britepoolid: 'britepool_id123', + criteoId: 'criteo_id234', + idl_env: 'idl_id123', + id5id: 'id5id_234', + parrableid: 'parrable_id234', + lipb: { + lipbid: 'liveintent_id123' + } + }; + const userVerify = function(obj, source, id) { + expect(obj).to.deep.equal({ + source, + uids: [{ + id + }] + }); + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request).to.be.not.null; + const ortbRequest = request.data; + expect(request.data).to.be.not.null; + // user object + expect(ortbRequest.user).to.not.be.undefined; + expect(ortbRequest.user.ext).to.not.be.undefined; + expect(ortbRequest.user.ext.eids).to.not.be.undefined; + expect(ortbRequest.user.ext.eids).to.have.lengthOf(6); + userVerify(ortbRequest.user.ext.eids[0], 'britepool.com', 'britepool_id123'); + userVerify(ortbRequest.user.ext.eids[1], 'criteo', 'criteo_id234'); + userVerify(ortbRequest.user.ext.eids[2], 'identityLink', 'idl_id123'); + userVerify(ortbRequest.user.ext.eids[3], 'id5-sync.com', 'id5id_234'); + userVerify(ortbRequest.user.ext.eids[4], 'parrable.com', 'parrable_id234'); + userVerify(ortbRequest.user.ext.eids[5], 'liveintent.com', 'liveintent_id123'); }); }); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index b553cf5d37e..7e7d47d3644 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -28,7 +28,11 @@ describe('Quantcast adapter', function () { publisherId: QUANTCAST_TEST_PUBLISHER, // REQUIRED - Publisher ID provided by Quantcast battr: [1, 2] // OPTIONAL - Array of blocked creative attributes as per OpenRTB Spec List 5.3 }, - sizes: [[300, 250]] + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + } }; bidderRequest = { @@ -83,17 +87,10 @@ describe('Quantcast adapter', function () { }); describe('`buildRequests`', function () { - it('selects protocol and port', function () { - switch (window.location.protocol) { - case 'https:': - expect(QUANTCAST_PROTOCOL).to.equal('https'); - expect(QUANTCAST_PORT).to.equal('8443'); - break; - default: - expect(QUANTCAST_PROTOCOL).to.equal('http'); - expect(QUANTCAST_PORT).to.equal('8080'); - break; - } + it('sends secure bid requests', function () { + const requests = qcSpec.buildRequests([bidRequest]); + const url = parse(requests[0]['url']); + expect(url.protocol).to.equal('https'); }); it('sends bid requests to Quantcast Canary Endpoint if `publisherId` is `test-publisher`', function () { @@ -120,30 +117,40 @@ describe('Quantcast adapter', function () { expect(requests[0].method).to.equal('POST'); }); + const expectedBannerBidRequest = { + publisherId: QUANTCAST_TEST_PUBLISHER, + requestId: '2f7b179d443f14', + imp: [ + { + banner: { + battr: [1, 2], + sizes: [{ width: 300, height: 250 }] + }, + placementCode: 'div-gpt-ad-1438287399331-0', + bidFloor: 1e-10 + } + ], + site: { + page: 'http://example.com/hello.html', + referrer: 'http://example.com/hello.html', + domain: 'example.com' + }, + bidId: '2f7b179d443f14', + gdprSignal: 0, + uspSignal: 0, + prebidJsVersion: '$prebid.version$' + }; + it('sends banner bid requests contains all the required parameters', function () { const requests = qcSpec.buildRequests([bidRequest], bidderRequest); - const expectedBannerBidRequest = { - publisherId: QUANTCAST_TEST_PUBLISHER, - requestId: '2f7b179d443f14', - imp: [ - { - banner: { - battr: [1, 2], - sizes: [{ width: 300, height: 250 }] - }, - placementCode: 'div-gpt-ad-1438287399331-0', - bidFloor: 1e-10 - } - ], - site: { - page: 'http://example.com/hello.html', - referrer: 'http://example.com/hello.html', - domain: 'example.com' - }, - bidId: '2f7b179d443f14', - gdprSignal: 0, - prebidJsVersion: '$prebid.version$' - }; + + expect(requests[0].data).to.equal(JSON.stringify(expectedBannerBidRequest)); + }); + + it('supports deprecated banner format', function () { + bidRequest.sizes = bidRequest.mediaTypes.banner.sizes; + delete bidRequest.mediaTypes; + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); expect(requests[0].data).to.equal(JSON.stringify(expectedBannerBidRequest)); }); @@ -197,6 +204,7 @@ describe('Quantcast adapter', function () { }, bidId: '2f7b179d443f14', gdprSignal: 0, + uspSignal: 0, prebidJsVersion: '$prebid.version$' }; @@ -231,6 +239,7 @@ describe('Quantcast adapter', function () { }, bidId: '2f7b179d443f14', gdprSignal: 0, + uspSignal: 0, prebidJsVersion: '$prebid.version$' }; @@ -261,6 +270,7 @@ describe('Quantcast adapter', function () { }, bidId: '2f7b179d443f14', gdprSignal: 0, + uspSignal: 0, prebidJsVersion: '$prebid.version$' }; @@ -297,7 +307,6 @@ describe('Quantcast adapter', function () { playerSize: [[550, 310]] } }; - bidRequest.sizes = [[300, 250], [728, 90], [250, 250], [468, 60], [320, 50]]; const requests = qcSpec.buildRequests([bidRequest], bidderRequest); const expectedBidRequest = { @@ -324,6 +333,7 @@ describe('Quantcast adapter', function () { }, bidId: '2f7b179d443f14', gdprSignal: 0, + uspSignal: 0, prebidJsVersion: '$prebid.version$' }; @@ -332,11 +342,19 @@ describe('Quantcast adapter', function () { }); it('propagates GDPR consent string and signal', function () { - const gdprConsent = { gdprApplies: true, consentString: 'consentString' } - const requests = qcSpec.buildRequests([bidRequest], { gdprConsent }); - const parsed = JSON.parse(requests[0].data) + const bidderRequest = { gdprConsent: { gdprApplies: true, consentString: 'consentString' } } + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); expect(parsed.gdprSignal).to.equal(1); - expect(parsed.gdprConsent).to.equal(gdprConsent.consentString); + expect(parsed.gdprConsent).to.equal('consentString'); + }); + + it('propagates US Privacy/CCPA consent information', function () { + const bidderRequest = { uspConsent: 'consentString' } + const requests = qcSpec.buildRequests([bidRequest], bidderRequest); + const parsed = JSON.parse(requests[0].data); + expect(parsed.uspSignal).to.equal(1); + expect(parsed.uspConsent).to.equal('consentString'); }); describe('`interpretResponse`', function () { diff --git a/test/spec/modules/realTimeModule_spec.js b/test/spec/modules/realTimeModule_spec.js new file mode 100644 index 00000000000..807781d5a9c --- /dev/null +++ b/test/spec/modules/realTimeModule_spec.js @@ -0,0 +1,193 @@ +import { + init, + requestBidsHook, + setTargetsAfterRequestBids, + deepMerge +} from 'modules/rtdModule/index'; +import { + init as browsiInit, + addBrowsiTag, + isIdMatchingAdUnit, + setData +} from 'modules/browsiRtdProvider'; +import {config} from 'src/config'; +import {makeSlot} from '../integration/faker/googletag'; + +let expect = require('chai').expect; + +describe('Real time module', function() { + const conf = { + 'realTimeData': { + 'auctionDelay': 250, + dataProviders: [{ + 'name': 'browsi', + 'params': { + 'url': 'testUrl.com', + 'siteKey': 'testKey', + 'pubKey': 'testPub', + 'keyName': 'bv' + } + }] + + } + }; + + const predictions = + {p: { + 'browsiAd_2': { + 'w': [ + '/57778053/Browsi_Demo_Low', + '/57778053/Browsi_Demo_300x250' + ], + 'p': 0.07 + }, + 'browsiAd_1': { + 'w': [], + 'p': 0.06 + }, + 'browsiAd_3': { + 'w': [], + 'p': 0.53 + }, + 'browsiAd_4': { + 'w': [ + '/57778053/Browsi_Demo' + ], + 'p': 0.85 + } + } + }; + + function getAdUnitMock(code = 'adUnit-code') { + return { + code, + mediaTypes: {banner: {}, native: {}}, + sizes: [[300, 200], [300, 600]], + bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] + }; + } + + function createSlots() { + const slot1 = makeSlot({code: '/57778053/Browsi_Demo_300x250', divId: 'browsiAd_1'}); + return [slot1]; + } + + describe('Real time module with browsi provider', function() { + afterEach(function () { + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('check module using bidsBackCallback', function () { + let adUnits1 = [getAdUnitMock('browsiAd_1')]; + let targeting = []; + init(config); + browsiInit(config); + config.setConfig(conf); + setData(predictions); + + // set slot + const slots = createSlots(); + window.googletag.pubads().setSlots(slots); + + function afterBidHook() { + slots.map(s => { + targeting = []; + s.getTargeting().map(value => { + targeting.push(Object.keys(value).toString()); + }); + }); + } + setTargetsAfterRequestBids(afterBidHook, adUnits1, true); + + setTimeout(() => { + expect(targeting.indexOf('bv')).to.be.greaterThan(-1); + }, 200); + }); + + it('check module using requestBidsHook', function () { + console.log('entrance', new Date().getMinutes() + ':' + new Date().getSeconds()); + let adUnits1 = [getAdUnitMock('browsiAd_1')]; + let targeting = []; + let dataReceived = null; + + // set slot + const slotsB = createSlots(); + window.googletag.pubads().setSlots(slotsB); + + function afterBidHook(data) { + dataReceived = data; + slotsB.map(s => { + targeting = []; + s.getTargeting().map(value => { + targeting.push(Object.keys(value).toString()); + }); + }); + } + requestBidsHook(afterBidHook, {adUnits: adUnits1}); + setTimeout(() => { + expect(targeting.indexOf('bv')).to.be.greaterThan(-1); + dataReceived.adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid.realTimeData).to.have.property('bv'); + }); + }); + }, 200); + }); + + it('check object deep merge', function () { + const obj1 = { + id1: { + key: 'value', + key2: 'value2' + }, + id2: { + k: 'v' + } + }; + const obj2 = { + id1: { + key3: 'value3' + } + }; + const obj3 = { + id3: { + key: 'value' + } + }; + const expected = { + id1: { + key: 'value', + key2: 'value2', + key3: 'value3' + }, + id2: { + k: 'v' + }, + id3: { + key: 'value' + } + }; + + const merged = deepMerge([obj1, obj2, obj3]); + assert.deepEqual(expected, merged); + }); + + it('check browsi sub module', function () { + const script = addBrowsiTag('scriptUrl.com'); + expect(script.getAttribute('data-sitekey')).to.equal('testKey'); + expect(script.getAttribute('data-pubkey')).to.equal('testPub'); + expect(script.async).to.equal(true); + + const slots = createSlots(); + const test1 = isIdMatchingAdUnit('browsiAd_1', slots, ['/57778053/Browsi_Demo_300x250']); // true + const test2 = isIdMatchingAdUnit('browsiAd_1', slots, ['/57778053/Browsi_Demo_300x250', '/57778053/Browsi']); // true + const test3 = isIdMatchingAdUnit('browsiAd_1', slots, ['/57778053/Browsi_Demo_Low']); // false + const test4 = isIdMatchingAdUnit('browsiAd_1', slots, []); // true + + expect(test1).to.equal(true); + expect(test2).to.equal(true); + expect(test3).to.equal(false); + expect(test4).to.equal(true); + }) + }); +}); diff --git a/test/spec/modules/rhythmoneBidAdapter_spec.js b/test/spec/modules/rhythmoneBidAdapter_spec.js index b6ac09a6207..29c8de43c16 100644 --- a/test/spec/modules/rhythmoneBidAdapter_spec.js +++ b/test/spec/modules/rhythmoneBidAdapter_spec.js @@ -702,6 +702,46 @@ describe('rhythmone adapter tests', function () { expect(openrtbRequest.imp[0].secure).to.equal(1); }); + it('should pass schain', function() { + var schain = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [{ + 'asi': 'indirectseller.com', + 'sid': '00001', + 'hp': 1 + }, { + 'asi': 'indirectseller-2.com', + 'sid': '00002', + 'hp': 1 + }] + }; + var bidRequestList = [ + { + 'bidder': 'rhythmone', + 'params': { + 'placementId': 'myplacement', + 'zone': 'myzone', + 'path': 'mypath' + }, + 'mediaType': 'banner', + 'adUnitCode': 'div-gpt-ad-1438287399331-0', + 'sizes': [[300, 250]], + 'transactionId': 'd7b773de-ceaa-484d-89ca-d9f51b8d61ec', + 'bidderRequestId': '418b37f85e772c', + 'auctionId': '18fd8b8b0bd757', + 'bidRequestsCount': 1, + 'bidId': '51ef8751f9aead', + 'schain': schain + } + ]; + + var bidRequest = r1adapter.buildRequests(bidRequestList, this.defaultBidderRequest); + const openrtbRequest = JSON.parse(bidRequest.data); + + expect(openrtbRequest.source.ext.schain).to.deep.equal(schain); + }); + describe('misc interpretResponse', function () { it('No bid response', function() { var noBidResponse = r1adapter.interpretResponse({ diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index 16d67ce7ceb..6c7030676c7 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -25,7 +25,30 @@ describe('Richaudience adapter tests', function () { auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', bidRequestsCount: 1, bidderRequestId: '1858b7382993ca', - transactionId: '29df2112-348b-4961-8863-1b33684d95e6' + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} + }]; + + var DEFAULT_PARAMS_NEW_SIZES = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + transactionId: '29df2112-348b-4961-8863-1b33684d95e6', + user: {} }]; var DEFAULT_PARAMS_APP = [{ @@ -101,6 +124,17 @@ describe('Richaudience adapter tests', function () { } }; + var DEFAULT_PARAMS_GDPR = { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: { + referer: 'http://domain.com', + numIframes: 0 + } + } + it('Verify build request', function () { config.setConfig({ 'currency': { @@ -108,7 +142,40 @@ describe('Richaudience adapter tests', function () { } }); + const request = spec.buildRequests(DEFAULT_PARAMS, DEFAULT_PARAMS_GDPR); + + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('sizes'); + expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); + expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); + expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); + expect(requestContent.sizes[2]).to.have.property('w').and.to.equal(728); + expect(requestContent.sizes[2]).to.have.property('h').and.to.equal(90); + expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); + expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); + }); + + it('Referer undefined', function() { + config.setConfig({ + 'currency': {'adServerCurrency': 'USD'} + }) + const request = spec.buildRequests(DEFAULT_PARAMS, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('referer').and.to.equal(null); + expect(requestContent).to.have.property('referer').and.to.equal(null); + }) + + it('Verify build request to prebid 3.0', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES, { gdprConsent: { consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', gdprApplies: true @@ -131,14 +198,18 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('bidderRequestId').and.to.equal('1858b7382993ca'); expect(requestContent).to.have.property('tagId').and.to.equal('test-div'); expect(requestContent).to.have.property('referer').and.to.equal('http%3A%2F%2Fdomain.com'); - expect(requestContent).to.have.property('sizes'); expect(requestContent.sizes[0]).to.have.property('w').and.to.equal(300); expect(requestContent.sizes[0]).to.have.property('h').and.to.equal(250); expect(requestContent.sizes[1]).to.have.property('w').and.to.equal(300); expect(requestContent.sizes[1]).to.have.property('h').and.to.equal(600); + expect(requestContent.sizes[2]).to.have.property('w').and.to.equal(728); + expect(requestContent.sizes[2]).to.have.property('h').and.to.equal(90); + expect(requestContent.sizes[3]).to.have.property('w').and.to.equal(970); + expect(requestContent.sizes[3]).to.have.property('h').and.to.equal(250); expect(requestContent).to.have.property('transactionId').and.to.equal('29df2112-348b-4961-8863-1b33684d95e6'); expect(requestContent).to.have.property('timeout').and.to.equal(3000); - }); + expect(requestContent).to.have.property('numIframes').and.to.equal(0); + }) describe('gdpr test', function () { it('Verify build request with GDPR', function () { @@ -153,31 +224,15 @@ describe('Richaudience adapter tests', function () { } }); - const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - referer: 'http://domain.com', - numIframes: 0 - } - }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); const requestContent = JSON.parse(request[0].data); expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); }); it('Verify adding ifa when supplyType equal to app', function () { - const request = spec.buildRequests(DEFAULT_PARAMS_APP, { - gdprConsent: { - consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', - gdprApplies: true - }, - refererInfo: { - referer: 'http://domain.com', - numIframes: 0 - } - }); + const request = spec.buildRequests(DEFAULT_PARAMS_APP, DEFAULT_PARAMS_GDPR); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA'); }); it('Verify build request with GDPR without gdprApplies', function () { @@ -206,6 +261,224 @@ describe('Richaudience adapter tests', function () { }); }); + describe('UID test', function () { + pbjs.setConfig({ + consentManagement: { + cmpApi: 'iab', + timeout: 5000, + allowAuctionWithoutConsent: true + } + }); + it('Verify build id5', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = 'id5-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'id5-user-id', + 'source': 'id5-sync.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.id5id = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build pubCommonId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 'pub_common_user_id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'pub_common_user_id', + 'source': 'pubcommon' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.pubcid = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build criteoId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 'criteo-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'criteo-user-id', + 'source': 'criteo.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + + it('Verify build identityLink', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'identity-link-user-id', + 'source': 'liveramp.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + it('Verify build liveIntentId', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 'identity-link-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data) + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'identity-link-user-id', + 'source': 'liveramp.com' + }]); + + var request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + it('Verify build TradeDesk', function () { + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.tdid = 'tdid-user-id'; + + var request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + var requestContent = JSON.parse(request[0].data) + + expect(requestContent.user).to.deep.equal([{ + 'userId': 'tdid-user-id', + 'source': 'adserver.org' + }]); + + request; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId = {}; + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = 1; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.criteoId = []; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = null; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + + DEFAULT_PARAMS_WO_OPTIONAL[0].userId.idl_env = {}; + request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, DEFAULT_PARAMS_GDPR); + requestContent = JSON.parse(request[0].data); + expect(requestContent.user.eids).to.equal(undefined); + }); + }); + it('Verify interprete response', function () { const request = spec.buildRequests(DEFAULT_PARAMS, { gdprConsent: { @@ -343,5 +616,39 @@ describe('Richaudience adapter tests', function () { iframeEnabled: true }, [], {consentString: '', gdprApplies: false}); expect(syncs).to.have.lengthOf(1); + + syncs = spec.getUserSyncs({ + iframeEnabled: false + }, [], {consentString: '', gdprApplies: true}); + expect(syncs).to.have.lengthOf(0); + + pbjs.setConfig({ + consentManagement: { + cmpApi: 'iab', + timeout: 5000, + allowAuctionWithoutConsent: true, + pixelEnabled: true + } + }); + + syncs = spec.getUserSyncs({ + pixelEnabled: true + }, [], { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + + syncs = spec.getUserSyncs({ + pixelEnabled: true + }, [], { + consentString: '', + referer: 'http://domain.com', + gdprApplies: true + }) + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); }); }); diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 707a5f91bec..67845fa1983 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -19,7 +19,11 @@ describe('RTBHouseAdapter', () => { 'region': 'prebid-eu' }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]], + } + }, 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475' @@ -29,6 +33,13 @@ describe('RTBHouseAdapter', () => { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + it('Checking backward compatibility. should return true', function () { + let bid2 = Object.assign({}, bid); + delete bid2.mediaTypes; + bid2.sizes = [[300, 250], [300, 600]]; + expect(spec.isBidRequestValid(bid2)).to.equal(true); + }); + it('should return false when required params are not passed', function () { let bid = Object.assign({}, bid); delete bid.params; @@ -49,7 +60,11 @@ describe('RTBHouseAdapter', () => { 'test': 1 }, 'adUnitCode': 'adunit-code', - 'sizes': [[300, 250], [300, 600]], + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]], + } + }, 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475', diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index a3e53c68294..c6941584652 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -68,6 +68,49 @@ describe('the rubicon adapter', function () { * @param {Array.} [indexOverMap] * @return {{status: string, cpm: number, zone_id: *, size_id: *, impression_id: *, ad_id: *, creative_id: string, type: string, targeting: *[]}} */ + + function getBidderRequest() { + return { + bidderCode: 'rubicon', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + bidderRequestId: '178e34bad3658f', + bids: [ + { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + userId: '12346', + keywords: ['a', 'b', 'c'], + inventory: { + rating: '5-star', // This actually should not be sent to frank!! causes 400 + prodtype: ['tech', 'mobile'] + }, + visitor: { + ucat: 'new', + lastsearch: 'iphone', + likes: ['sports', 'video games'] + }, + position: 'atf', + referrer: 'localhost', + latLong: [40.7607823, '111.8910325'] + }, + adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', + sizes: [[300, 250], [320, 50]], + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b' + } + ], + start: 1472239426002, + auctionStart: 1472239426000, + timeout: 5000 + }; + }; + function createResponseAdByIndex(i, sizeId, indexOverMap) { const overridePropMap = (indexOverMap && indexOverMap[i] && typeof indexOverMap[i] === 'object') ? indexOverMap[i] : {}; const overrideProps = Object.keys(overridePropMap).reduce((aggregate, key) => { @@ -143,8 +186,13 @@ describe('the rubicon adapter', function () { } } + function createUspBidderRequest() { + bidderRequest.uspConsent = '1NYN'; + } + function createVideoBidderRequest() { createGdprBidderRequest(true); + createUspBidderRequest(); let bid = bidderRequest.bids[0]; bid.mediaTypes = { @@ -172,6 +220,12 @@ describe('the rubicon adapter', function () { 'playerWidth': 640, 'size_id': 201, }; + bid.userId = { + lipb: { + lipbid: '0000-1111-2222-3333', + segments: ['segA', 'segB'] + } + } } function createVideoBidderRequestNoVideo() { @@ -230,6 +284,7 @@ describe('the rubicon adapter', function () { accountId: '14062', siteId: '70608', zoneId: '335918', + pchain: 'GAM:11111-reseller1:22222', userId: '12346', keywords: ['a', 'b', 'c'], inventory: { @@ -311,7 +366,7 @@ describe('the rubicon adapter', function () { describe('buildRequests implementation', function () { describe('for requests', function () { describe('to fastlane', function () { - it('should make a well-formed request objects', function () { + it('should make a well-formed request object', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); let data = parseQuery(request.data); @@ -330,6 +385,7 @@ describe('the rubicon adapter', function () { 'rand': '0.1', 'tk_flint': INTEGRATION, 'x_source.tid': 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + 'x_source.pchain': 'GAM:11111-reseller1:22222', 'p_screen_res': /\d+x\d+/, 'tk_user_key': '12346', 'kw': 'a,b,c', @@ -411,11 +467,20 @@ describe('the rubicon adapter', function () { expect(data['p_pos']).to.equal('atf;;btf;;'); }); + it('should not send x_source.pchain to AE if params.pchain is not specified', function() { + var noPchainRequest = utils.deepClone(bidderRequest); + delete noPchainRequest.bids[0].params.pchain; + + let [request] = spec.buildRequests(noPchainRequest.bids, noPchainRequest); + expect(request.data).to.contain('&site_id=70608&'); + expect(request.data).to.not.contain('x_source.pchain'); + }); + it('ad engine query params should be ordered correctly', function () { sandbox.stub(Math, 'random').callsFake(() => 0.1); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'p_screen_res', 'rp_floor', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; + const referenceOrdering = ['account_id', 'site_id', 'zone_id', 'size_id', 'alt_size_ids', 'p_pos', 'rf', 'p_geo.latitude', 'p_geo.longitude', 'kw', 'tg_v.ucat', 'tg_v.lastsearch', 'tg_v.likes', 'tg_i.rating', 'tg_i.prodtype', 'tk_flint', 'x_source.tid', 'x_source.pchain', 'p_screen_res', 'rp_floor', 'rp_secure', 'tk_user_key', 'tg_fl.eid', 'slots', 'rand']; request.data.split('&').forEach((item, i) => { expect(item.split('=')[0]).to.equal(referenceOrdering[i]); @@ -833,6 +898,23 @@ describe('the rubicon adapter', function () { }); }); + describe('USP Consent', function () { + it('should send us_privacy if bidderRequest has a value for uspConsent', function () { + createUspBidderRequest(); + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(data['us_privacy']).to.equal('1NYN'); + }); + + it('should not send us_privacy if bidderRequest has no uspConsent value', function () { + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + let data = parseQuery(request.data); + + expect(data['us_privacy']).to.equal(undefined); + }); + }); + describe('first party data', function () { it('should not have any tg_v or tg_i params if all are undefined', function () { let params = { @@ -1147,6 +1229,36 @@ describe('the rubicon adapter', function () { expect(data['tpid_tdid']).to.equal('abcd-efgh-ijkl-mnop-1234'); }); + + describe('LiveIntent support', function () { + it('should send tpid_liveintent.com when userId defines lipd', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + lipb: { + lipbid: '0000-1111-2222-3333' + } + }; + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = parseQuery(request.data); + + expect(data['tpid_liveintent.com']).to.equal('0000-1111-2222-3333'); + }); + + it('should send tg_v.LIseg when userId defines lipd.segments', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + clonedBid.userId = { + lipb: { + lipbid: '1111-2222-3333-4444', + segments: ['segD', 'segE'] + } + }; + let [request] = spec.buildRequests([clonedBid], bidderRequest); + const unescapedData = unescape(request.data); + + expect(unescapedData.indexOf('&tpid_liveintent.com=1111-2222-3333-4444&') !== -1).to.equal(true); + expect(unescapedData.indexOf('&tg_v.LIseg=segD,segE&') !== -1).to.equal(true); + }); + }); }) }); @@ -1184,7 +1296,18 @@ describe('the rubicon adapter', function () { expect(imp.ext.rubicon.video.skip).to.equal(1); expect(imp.ext.rubicon.video.skipafter).to.equal(15); expect(post.user.ext.consent).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(post.user.ext.eids[0].source).to.equal('liveintent.com'); + expect(post.user.ext.eids[0].uids[0].id).to.equal('0000-1111-2222-3333'); + expect(post.user.ext.tpid).that.is.an('object'); + expect(post.user.ext.tpid.source).to.equal('liveintent.com'); + expect(post.user.ext.tpid.uid).to.equal('0000-1111-2222-3333'); + expect(post.rp).that.is.an('object'); + expect(post.rp.target).that.is.an('object'); + expect(post.rp.target.LIseg).that.is.an('array'); + expect(post.rp.target.LIseg[0]).to.equal('segA'); + expect(post.rp.target.LIseg[1]).to.equal('segB'); expect(post.regs.ext.gdpr).to.equal(1); + expect(post.regs.ext.us_privacy).to.equal('1NYN'); expect(post).to.have.property('ext').that.is.an('object'); expect(post.ext.prebid.targeting.includewinners).to.equal(true); expect(post.ext.prebid).to.have.property('cache').that.is.an('object') @@ -1193,6 +1316,22 @@ describe('the rubicon adapter', function () { expect(post.ext.prebid.cache.vastxml.returnCreative).to.equal(false) }); + it('should add alias name to PBS Request', function() { + createVideoBidderRequest(); + + bidderRequest.bidderCode = 'superRubicon'; + bidderRequest.bids[0].bidder = 'superRubicon'; + let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); + + // should have the aliases object sent to PBS + expect(request.data.ext.prebid).to.haveOwnProperty('aliases'); + expect(request.data.ext.prebid.aliases).to.deep.equal({superRubicon: 'rubicon'}); + + // should have the imp ext bidder params be under the alias name not rubicon superRubicon + expect(request.data.imp[0].ext).to.have.property('superRubicon').that.is.an('object'); + expect(request.data.imp[0].ext).to.not.haveOwnProperty('rubicon'); + }); + it('should send correct bidfloor to PBS', function() { createVideoBidderRequest(); @@ -1621,7 +1760,7 @@ describe('the rubicon adapter', function () { expect(bids[0].height).to.equal(50); expect(bids[0].cpm).to.equal(0.911); expect(bids[0].ttl).to.equal(300); - expect(bids[0].netRevenue).to.equal(false); + expect(bids[0].netRevenue).to.equal(true); expect(bids[0].rubicon.advertiserId).to.equal(7); expect(bids[0].rubicon.networkId).to.equal(8); expect(bids[0].creativeId).to.equal('crid-9'); @@ -1636,7 +1775,7 @@ describe('the rubicon adapter', function () { expect(bids[1].height).to.equal(250); expect(bids[1].cpm).to.equal(0.811); expect(bids[1].ttl).to.equal(300); - expect(bids[1].netRevenue).to.equal(false); + expect(bids[1].netRevenue).to.equal(true); expect(bids[1].rubicon.advertiserId).to.equal(7); expect(bids[1].rubicon.networkId).to.equal(8); expect(bids[1].creativeId).to.equal('crid-9'); @@ -1648,6 +1787,118 @@ describe('the rubicon adapter', function () { expect(bids[1].rubiconTargeting.rpfl_14062).to.equal('15_tier_all_test'); }); + it('should pass netRevenue correctly if set in setConfig', function () { + let response = { + 'status': 'ok', + 'account_id': 14062, + 'site_id': 70608, + 'zone_id': 530022, + 'size_id': 15, + 'alt_size_ids': [ + 43 + ], + 'tracking': '', + 'inventory': {}, + 'ads': [ + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374c', + 'size_id': '15', + 'ad_id': '6', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.811, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_tier_all_test' + ] + } + ] + }, + { + 'status': 'ok', + 'impression_id': '153dc240-8229-4604-b8f5-256933b9374d', + 'size_id': '43', + 'ad_id': '7', + 'advertiser': 7, + 'network': 8, + 'creative_id': 'crid-9', + 'type': 'script', + 'script': 'alert(\'foo\')', + 'campaign_id': 10, + 'cpm': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ] + }; + + // Set to false => false + config.setConfig({ + rubicon: { + netRevenue: false + } + }); + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids).to.be.lengthOf(2); + expect(bids[0].netRevenue).to.equal(false); + expect(bids[1].netRevenue).to.equal(false); + + // Set to true => true + config.setConfig({ + rubicon: { + netRevenue: true + } + }); + bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids).to.be.lengthOf(2); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[1].netRevenue).to.equal(true); + + // Set to undefined => true + config.setConfig({ + rubicon: { + netRevenue: undefined + } + }); + bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids).to.be.lengthOf(2); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[1].netRevenue).to.equal(true); + + // Set to string => true + config.setConfig({ + rubicon: { + netRevenue: 'someString' + } + }); + bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids).to.be.lengthOf(2); + expect(bids[0].netRevenue).to.equal(true); + expect(bids[1].netRevenue).to.equal(true); + + config.resetConfig(); + }); it('should use "network-advertiser" if no creative_id', function () { let response = { 'status': 'ok', @@ -2186,6 +2437,20 @@ describe('the rubicon adapter', function () { type: 'iframe', url: `${emilyUrl}` }); }); + + it('should pass us_privacy if uspConsent is defined', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, undefined, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?us_privacy=1NYN` + }); + }); + + it('should pass us_privacy after gdpr if both are present', function () { + expect(spec.getUserSyncs({ iframeEnabled: true }, {}, { + consentString: 'foo' + }, '1NYN')).to.deep.equal({ + type: 'iframe', url: `${emilyUrl}?gdpr_consent=foo&us_privacy=1NYN` + }); + }); }); describe('get price granularity', function() { @@ -2221,4 +2486,98 @@ describe('the rubicon adapter', function () { }); }); }); + + describe('Supply Chain Support', function() { + const nodePropsOrder = ['asi', 'sid', 'hp', 'rid', 'name', 'domain']; + let bidRequests; + let schainConfig; + + const getSupplyChainConfig = () => { + return { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'rubicon.com', + sid: '1234', + hp: 1, + rid: 'bid-request-1', + name: 'pub one', + domain: 'pub1.com' + }, + { + asi: 'theexchange.com', + sid: '5678', + hp: 1, + rid: 'bid-request-2', + name: 'pub two', + domain: 'pub2.com' + }, + { + asi: 'wesellads.com', + sid: '9876', + hp: 1, + rid: 'bid-request-3', + // name: 'alladsallthetime', + domain: 'alladsallthetime.com' + } + ] + }; + }; + + beforeEach(() => { + bidRequests = getBidderRequest(); + schainConfig = getSupplyChainConfig(); + bidRequests.bids[0].schain = schainConfig; + }); + + it('should properly serialize schain object with correct delimiters', () => { + const results = spec.buildRequests(bidRequests.bids, bidRequests); + const numNodes = schainConfig.nodes.length; + const schain = parseQuery(results[0].data).rp_schain; + + // each node serialization should start with an ! + expect(schain.match(/!/g).length).to.equal(numNodes); + + // 5 commas per node plus 1 for version + expect(schain.match(/,/g).length).to.equal(numNodes * 5 + 1); + }); + + it('should send the proper version for the schain', () => { + const results = spec.buildRequests(bidRequests.bids, bidRequests); + const schain = parseQuery(results[0].data).rp_schain.split('!'); + const version = schain.shift().split(',')[0]; + expect(version).to.equal(bidRequests.bids[0].schain.ver); + }); + + it('should send the correct value for complete in schain', () => { + const results = spec.buildRequests(bidRequests.bids, bidRequests); + const schain = parseQuery(results[0].data).rp_schain.split('!'); + const complete = schain.shift().split(',')[1]; + expect(complete).to.equal(String(bidRequests.bids[0].schain.complete)); + }); + + it('should send available params in the right order', () => { + const results = spec.buildRequests(bidRequests.bids, bidRequests); + const schain = parseQuery(results[0].data).rp_schain.split('!'); + schain.shift(); + + schain.forEach((serializeNode, nodeIndex) => { + const nodeProps = serializeNode.split(','); + nodeProps.forEach((nodeProp, propIndex) => { + const node = schainConfig.nodes[nodeIndex]; + const key = nodePropsOrder[propIndex]; + expect(nodeProp).to.equal(node[key] ? String(node[key]) : ''); + }); + }); + }); + + it('should copy the schain JSON to to bid.source.ext.schain', () => { + createVideoBidderRequest(); + const schain = getSupplyChainConfig(); + bidderRequest.bids[0].schain = schain; + const request = spec.buildRequests(bidderRequest.bids, bidderRequest); + expect(request[0].data.source.ext.schain).to.deep.equal(schain); + }); + }); }); diff --git a/test/spec/modules/shBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js similarity index 99% rename from test/spec/modules/shBidAdapter_spec.js rename to test/spec/modules/showheroes-bsBidAdapter_spec.js index 525642da7d6..124f98600d5 100644 --- a/test/spec/modules/shBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -1,5 +1,5 @@ import {expect} from 'chai' -import {spec} from 'modules/shBidAdapter' +import {spec} from 'modules/showheroes-bsBidAdapter' import {newBidder} from 'src/adapters/bidderFactory' import {VIDEO, BANNER} from 'src/mediaTypes' diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index c5ae9e59054..30afad308c7 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -100,58 +100,6 @@ describe('Smart bid adapter tests', function () { expect(requestContent).to.have.property('ckid').and.to.equal(42); }); - describe('gdpr tests', function () { - afterEach(function () { - config.resetConfig(); - $$PREBID_GLOBAL$$.requestBids.removeAll(); - }); - - it('Verify build request with GDPR', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - }, - consentManagement: { - cmp: 'iab', - consentRequired: true, - timeout: 1000, - allowAuctionWithoutConsent: true - } - }); - const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { - gdprConsent: { - consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', - gdprApplies: true - } - }); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.have.property('gdpr').and.to.equal(true); - expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); - }); - - it('Verify build request with GDPR without gdprApplies', function () { - config.setConfig({ - 'currency': { - 'adServerCurrency': 'EUR' - }, - consentManagement: { - cmp: 'iab', - consentRequired: true, - timeout: 1000, - allowAuctionWithoutConsent: true - } - }); - const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { - gdprConsent: { - consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==' - } - }); - const requestContent = JSON.parse(request[0].data); - expect(requestContent).to.not.have.property('gdpr'); - expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); - }); - }); - it('Verify parse response', function () { const request = spec.buildRequests(DEFAULT_PARAMS); const bids = spec.interpretResponse(BID_RESPONSE, request[0]); @@ -261,4 +209,191 @@ describe('Smart bid adapter tests', function () { }, []); expect(syncs).to.have.lengthOf(0); }); + + describe('gdpr tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + it('Verify build request with GDPR', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==', + gdprApplies: true + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('gdpr').and.to.equal(true); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); + }); + + it('Verify build request with GDPR without gdprApplies', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + }, + consentManagement: { + cmp: 'iab', + consentRequired: true, + timeout: 1000, + allowAuctionWithoutConsent: true + } + }); + const request = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL, { + gdprConsent: { + consentString: 'BOKAVy4OKAVy4ABAB8AAAAAZ+A==' + } + }); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.not.have.property('gdpr'); + expect(requestContent).to.have.property('gdpr_consent').and.to.equal('BOKAVy4OKAVy4ABAB8AAAAAZ+A=='); + }); + }); + + describe('Instream video tests', function () { + afterEach(function () { + config.resetConfig(); + $$PREBID_GLOBAL$$.requestBids.removeAll(); + }); + + const INSTREAM_DEFAULT_PARAMS = [{ + adUnitCode: 'sas_42', + bidId: 'abcd1234', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'instream', + playerSize: [[640, 480]] // It seems prebid.js transforms the player size array into an array of array... + } + }, + params: { + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42, + video: { + protocol: 6 + } + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }]; + + var INSTREAM_BID_RESPONSE = { + body: { + cpm: 12, + width: 640, + height: 480, + creativeId: 'zioeufg', + currency: 'GBP', + isNetCpm: true, + ttl: 300, + adUrl: 'http://awesome.fake-vast.url', + ad: '', + cSyncUrl: 'http://awesome.fake.csync.url' + } + }; + + it('Verify instream video build request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS); + expect(request[0]).to.have.property('url').and.to.equal('https://prg.smartadserver.com/prebid/v1'); + expect(request[0]).to.have.property('method').and.to.equal('POST'); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('siteid').and.to.equal('1234'); + expect(requestContent).to.have.property('pageid').and.to.equal('5678'); + expect(requestContent).to.have.property('formatid').and.to.equal('90'); + expect(requestContent).to.have.property('currencyCode').and.to.equal('EUR'); + expect(requestContent).to.have.property('bidfloor').and.to.equal(0.42); + expect(requestContent).to.have.property('targeting').and.to.equal('test=prebid'); + expect(requestContent).to.have.property('tagId').and.to.equal('sas_42'); + expect(requestContent).to.have.property('pageDomain').and.to.equal(utils.getTopWindowUrl()); + expect(requestContent).to.have.property('transactionId').and.to.not.equal(null).and.to.not.be.undefined; + expect(requestContent).to.have.property('buid').and.to.equal('7569'); + expect(requestContent).to.have.property('appname').and.to.equal('Mozilla'); + expect(requestContent).to.have.property('ckid').and.to.equal(42); + expect(requestContent).to.have.property('isVideo').and.to.equal(true); + expect(requestContent).to.have.property('videoData'); + expect(requestContent.videoData).to.have.property('videoProtocol').and.to.equal(6); + expect(requestContent.videoData).to.have.property('playerWidth').and.to.equal(640); + expect(requestContent.videoData).to.have.property('playerHeight').and.to.equal(480); + }); + + it('Verify instream parse response', function () { + const request = spec.buildRequests(INSTREAM_DEFAULT_PARAMS); + const bids = spec.interpretResponse(INSTREAM_BID_RESPONSE, request[0]); + expect(bids).to.have.lengthOf(1); + const bid = bids[0]; + expect(bid.cpm).to.equal(12); + expect(bid.mediaType).to.equal('video'); + expect(bid.vastUrl).to.equal('http://awesome.fake-vast.url'); + expect(bid.vastXml).to.equal(''); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.creativeId).to.equal('zioeufg'); + expect(bid.currency).to.equal('GBP'); + expect(bid.netRevenue).to.equal(true); + expect(bid.ttl).to.equal(300); + expect(bid.requestId).to.equal(INSTREAM_DEFAULT_PARAMS[0].bidId); + expect(bid.referrer).to.equal(utils.getTopWindowUrl()); + + expect(function () { + spec.interpretResponse(INSTREAM_BID_RESPONSE, { + data: 'invalid Json' + }) + }).to.not.throw(); + }); + + it('Verify not handled media type return empty request', function () { + config.setConfig({ + 'currency': { + 'adServerCurrency': 'EUR' + } + }); + const request = spec.buildRequests([{ + adUnitCode: 'sas_42', + bidder: 'smartadserver', + mediaTypes: { + video: { + context: 'badcontext' + } + }, + params: { + domain: 'http://prg.smartadserver.com', + siteId: '1234', + pageId: '5678', + formatId: '90', + target: 'test=prebid', + bidfloor: 0.420, + buId: '7569', + appName: 'Mozilla', + ckId: 42 + }, + requestId: 'efgh5678', + transactionId: 'zsfgzzg' + }, INSTREAM_DEFAULT_PARAMS[0]]); + expect(request[0]).to.be.empty; + expect(request[1]).to.not.be.empty; + }); + }); }); diff --git a/test/spec/modules/smmsBidAdapter_spec.js b/test/spec/modules/smmsBidAdapter_spec.js new file mode 100644 index 00000000000..3ed952811ea --- /dev/null +++ b/test/spec/modules/smmsBidAdapter_spec.js @@ -0,0 +1,161 @@ +import {expect} from 'chai'; +import {spec, _getUrlVars} from 'modules/smmsBidAdapter'; +import * as utils from 'src/utils'; + +const BASE_URI = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/banner' +const NATIVE_BASE_URI = 'https://bidder.mediams.mb.softbank.jp/api/v1/prebid/native' + +describe('smms adapter', function() { + let bidRequests; + let nativeBidRequests; + + beforeEach(function() { + bidRequests = [ + { + bidder: 'smms', + params: { + placementId: 1440837, + currency: 'USD' + } + } + ] + + nativeBidRequests = [ + { + bidder: 'smms', + params: { + placementId: 1440838, + currency: 'USD' + }, + nativeParams: { + title: { + required: true, + len: 80 + }, + image: { + required: true, + sizes: [150, 50] + }, + sponsoredBy: { + required: true + } + } + } + ] + }) + + describe('isBidRequestValid', function () { + it('valid bid case: required params found', function () { + let validBid = { + bidder: 'smms', + params: { + placementId: 1440837, + currency: 'USD' + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(true); + }); + + it('invalid bid case: placementId is not passed', function() { + let validBid = { + bidder: 'smms', + params: { + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }) + + it('invalid bid case: currency is not support', function() { + let validBid = { + bidder: 'smms', + params: { + placementId: 1440837, + currency: 'AUD' + } + } + let isValid = spec.isBidRequestValid(validBid); + expect(isValid).to.equal(false); + }) + }) + + describe('buildRequests', function () { + it('sends bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(bidRequests)[0]; + expect(request.url).to.equal(BASE_URI); + expect(request.method).to.equal('GET'); + }); + + it('sends native bid request to ENDPOINT via GET', function () { + const request = spec.buildRequests(nativeBidRequests)[0]; + expect(request.url).to.equal(NATIVE_BASE_URI); + expect(request.method).to.equal('GET'); + }); + + it('buildRequests function should not modify original bidRequests object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + spec.buildRequests(bidRequests); + expect(bidRequests).to.deep.equal(originalBidRequests); + }); + + it('buildRequests function should not modify original nativeBidRequests object', function () { + let originalBidRequests = utils.deepClone(nativeBidRequests); + spec.buildRequests(nativeBidRequests); + expect(nativeBidRequests).to.deep.equal(originalBidRequests); + }); + + it('Request params check', function() { + let request = spec.buildRequests(bidRequests)[0]; + const data = _getUrlVars(request.data) + expect(parseInt(data.placementid)).to.exist.and.to.equal(bidRequests[0].params.placementId); + expect(data.cur).to.exist.and.to.equal(bidRequests[0].params.currency); + }) + + it('Native request params check', function() { + let request = spec.buildRequests(nativeBidRequests)[0]; + const data = _getUrlVars(request.data) + expect(parseInt(data.placementid)).to.exist.and.to.equal(nativeBidRequests[0].params.placementId); + expect(data.cur).to.exist.and.to.equal(nativeBidRequests[0].params.currency); + }) + }) + + describe('interpretResponse', function () { + let response = { + 1440837: + { + 'creativeId': '', + 'cur': 'USD', + 'price': 0.0920, + 'width': 300, + 'height': 250, + 'requestid': '2e42361a6172bf', + 'adm': '' + } + } + + it('should get correct bid response', function () { + let expectedResponse = [ + { + 'requestId': '2e42361a6172bf', + 'cpm': 0.0920, + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'currency': 'USD', + 'creativeId': '', + 'ttl': 700, + 'ad': '' + } + ]; + let request = spec.buildRequests(bidRequests)[0]; + let result = spec.interpretResponse({body: response}, request); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(result[0].cpm).to.not.equal(null); + expect(result[0].creativeId).to.not.equal(null); + expect(result[0].ad).to.not.equal(null); + expect(result[0].currency).to.equal('USD'); + expect(result[0].netRevenue).to.equal(true); + }); + }) +}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 7179ec00bc3..0778b6487c2 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -1,9 +1,8 @@ -import { expect } from 'chai'; -import { spec, LogError } from 'modules/sovrnBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory'; -import { SSL_OP_SINGLE_ECDH_USE } from 'constants'; +import {expect} from 'chai'; +import {LogError, spec} from 'modules/sovrnBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; -const ENDPOINT = `//ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; +const ENDPOINT = `https://ap.lijit.com/rtb/bid?src=$$REPO_AND_VERSION$$`; describe('sovrnBidAdapter', function() { const adapter = newBidder(spec); @@ -55,8 +54,12 @@ describe('sovrnBidAdapter', function() { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475' }]; - - const request = spec.buildRequests(bidRequests); + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + const request = spec.buildRequests(bidRequests, bidderRequest); it('sends bid request to our endpoint via POST', function () { expect(request.method).to.equal('POST'); @@ -85,8 +88,13 @@ describe('sovrnBidAdapter', function() { 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475' - }]; - const request = spec.buildRequests(singleSize) + }] + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } + } + const request = spec.buildRequests(singleSize, bidderRequest) const payload = JSON.parse(request.data) expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}]) expect(payload.imp[0].banner.w).to.equal(1) @@ -109,7 +117,12 @@ describe('sovrnBidAdapter', function() { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475' }]; - const request = spec.buildRequests(ivBidRequests); + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + const request = spec.buildRequests(ivBidRequests, bidderRequest); expect(request.url).to.contain('iv=vet') }); @@ -121,20 +134,41 @@ describe('sovrnBidAdapter', function() { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'gdprConsent': { + gdprConsent: { consentString: consentString, gdprApplies: true + }, + refererInfo: { + referer: 'http://example.com/page.html', } }; bidderRequest.bids = bidRequests; - const request = spec.buildRequests(bidRequests, bidderRequest); - const payload = JSON.parse(request.data); + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + + expect(data.regs.ext.gdpr).to.exist.and.to.be.a('number'); + expect(data.regs.ext.gdpr).to.equal(1); + expect(data.user.ext.consent).to.exist.and.to.be.a('string'); + expect(data.user.ext.consent).to.equal(consentString); + }); + + it('should send us_privacy if bidderRequest has a value for uspConsent', function () { + let uspString = '1NYN'; + let bidderRequest = { + 'bidderCode': 'sovrn', + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + uspConsent: uspString, + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + bidderRequest.bids = bidRequests; + + const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); - expect(payload.regs.ext.gdpr).to.exist.and.to.be.a('number'); - expect(payload.regs.ext.gdpr).to.equal(1); - expect(payload.user.ext.consent).to.exist.and.to.be.a('string'); - expect(payload.user.ext.consent).to.equal(consentString); + expect(data.regs.ext['us_privacy']).to.equal(uspString); }); it('converts tagid to string', function () { @@ -153,10 +187,86 @@ describe('sovrnBidAdapter', function() { 'bidderRequestId': '22edbae2733bf6', 'auctionId': '1d1a030790a475' }]; - const request = spec.buildRequests(ivBidRequests); + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + const request = spec.buildRequests(ivBidRequests, bidderRequest); expect(request.data).to.contain('"tagid":"403370"') - }) + }); + + it('should add schain if present', function() { + const schainRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': 403370 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'directseller.com', + 'sid': '00001', + 'rid': 'BidRequest1', + 'hp': 1 + } + ] + } + }].concat(bidRequests); + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + const data = JSON.parse(spec.buildRequests(schainRequests, bidderRequest).data); + + expect(data.source.ext.schain.nodes.length).to.equal(1) + }); + + it('should add digitrust data if present', function() { + const digitrustRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': 403370 + }, + 'adUnitCode': 'adunit-code', + 'sizes': [ + [300, 250], + [300, 600] + ], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475', + 'userId': { + 'digitrustid': { + 'data': { + 'id': 'digitrust-id-123', + 'keyv': 4 + } + } + } + }].concat(bidRequests); + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', + } + }; + const data = JSON.parse(spec.buildRequests(digitrustRequests, bidderRequest).data); + + expect(data.user.ext.digitrust.id).to.equal('digitrust-id-123'); + expect(data.user.ext.digitrust.keyv).to.equal(4); + }); }); describe('interpretResponse', function () { @@ -253,9 +363,9 @@ describe('sovrnBidAdapter', function() { }); }); - describe('getUserSyncs ', () => { - let syncOptions = {iframeEnabled: true, pixelEnabled: true}; - let iframeDisabledSyncOptions = {iframeEnabled: false, pixelEnabled: true}; + describe('getUserSyncs ', function() { + let syncOptions = { iframeEnabled: true, pixelEnabled: false }; + let iframeDisabledSyncOptions = { iframeEnabled: false, pixelEnabled: false }; let serverResponse = [ { 'body': { @@ -287,111 +397,181 @@ describe('sovrnBidAdapter', function() { } ], 'ext': { - 'iid': 13487408 + 'iid': 13487408, + sync: { + pixels: [ + { + url: 'http://idprovider1.com' + }, + { + url: 'http://idprovider2.com' + } + ] + } } }, 'headers': {} } ]; - it('should return if iid present on server response & iframe syncs enabled', () => { - let expectedReturnStatement = [ + + it('should return if iid present on server response & iframe syncs enabled', function() { + const expectedReturnStatement = [ { 'type': 'iframe', - 'url': '//ap.lijit.com/beacon?informer=13487408&gdpr_consent=', + 'url': 'https://ap.lijit.com/beacon?informer=13487408', } - ] - let returnStatement = spec.getUserSyncs(syncOptions, serverResponse); + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse); expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); - }) - - it('should not return if iid missing on server response', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []) - expect(returnStatement).to.be.empty; - }) + }); - it('should not return if iframe syncs disabled', () => { - let returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse) - expect(returnStatement).to.be.empty - }) - }) - describe('LogError', () => { - it('should build and append an error object', () => { - const thrown = { - message: 'message', - stack: 'stack' + it('should include gdpr consent string if present', function() { + const gdprConsent = { + gdprApplies: 1, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - const data = {name: 'Oscar Hathenswiotch'} - const err = new LogError(thrown, data) - err.append() - const errList = LogError.getErrPxls() - expect(errList.length).to.equal(1) - const errdata = JSON.parse(atob(errList[0].url.split('=')[1])) - expect(errdata.d.name).to.equal('Oscar Hathenswiotch') - }) - it('should drop data when there is too much', () => { - const thrown = { - message: 'message', - stack: 'stack' - } - const tooLong = () => { - let str = '' - for (let i = 0; i < 10000; i++) { - str = str + String.fromCharCode(i % 100) + const expectedReturnStatement = [ + { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&informer=13487408`, } - return str - } - const data = {name: 'Oscar Hathenswiotch', tooLong: tooLong()} - const err = new LogError(thrown, data) - err.append() - const errList = LogError.getErrPxls() - expect(errList.length).to.equal(2) - const errdata = JSON.parse(atob(errList[1].url.split('=')[1])) - expect(errdata.d).to.be.an('undefined') - }) - it('should drop data and stack when there is too much', () => { - const thrown = { - message: 'message', - stack: 'stack' - } - const tooLong = () => { - let str = '' - for (let i = 0; i < 10000; i++) { - str = str + String.fromCharCode(i % 100) + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, ''); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + }); + + it('should include us privacy string if present', function() { + const uspString = '1NYN'; + const expectedReturnStatement = [ + { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?us_privacy=${uspString}&informer=13487408`, } - return str + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, null, uspString); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + }); + + it('should include all privacy strings if present', function() { + const gdprConsent = { + gdprApplies: 1, + consentString: 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A==' } - const data = {name: 'Oscar Hathenswiotch'} - thrown.stack = tooLong() - const err = new LogError(thrown, data) - err.append() - const errList = LogError.getErrPxls() - expect(errList.length).to.equal(3) - const errdata = JSON.parse(atob(errList[2].url.split('=')[1])) - expect(errdata.d).to.be.an('undefined') - expect(errdata.s).to.be.an('undefined') - }) - it('should drop send a reduced message when other reduction methods fail', () => { - const thrown = { - message: 'message', - stack: 'stack' + const uspString = '1NYN'; + const expectedReturnStatement = [ + { + 'type': 'iframe', + 'url': `https://ap.lijit.com/beacon?gdpr_consent=${gdprConsent.consentString}&us_privacy=${uspString}&informer=13487408`, + } + ]; + const returnStatement = spec.getUserSyncs(syncOptions, serverResponse, gdprConsent, uspString); + expect(returnStatement[0]).to.deep.equal(expectedReturnStatement[0]); + }); + + it('should not return if iid missing on server response', function() { + const returnStatement = spec.getUserSyncs(syncOptions, []); + expect(returnStatement).to.be.empty; + }); + + it('should not return if iframe syncs disabled', function() { + const returnStatement = spec.getUserSyncs(iframeDisabledSyncOptions, serverResponse); + expect(returnStatement).to.be.empty; + }); + + it('should include pixel syncs', function() { + let pixelEnabledOptions = { iframeEnabled: false, pixelEnabled: true }; + const resp2 = { + 'body': { + 'id': '546956d68c757f-2', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'a_448326_16c2ada014224bee815a90d2248322f5-2', + 'impid': '2a3826aae345f4', + 'price': 1.0099999904632568, + 'nurl': 'http://localhost/rtb/impression?bannerid=220958&campaignid=3890&rtb_tid=15588614-75d2-40ab-b27e-13d2127b3c2e&rpid=1295&seatid=seat1&zoneid=448326&cb=26900712&tid=a_448326_16c2ada014224bee815a90d2248322f5', + 'adm': 'yo a creative', + 'crid': 'cridprebidrtb', + 'w': 160, + 'h': 600 + }, + { + 'id': 'a_430392_beac4c1515da4576acf6cb9c5340b40c-2', + 'impid': '3cf96fd26ed4c5', + 'price': 1.0099999904632568, + 'nurl': 'http://localhost/rtb/impression?bannerid=220957&campaignid=3890&rtb_tid=5bc0e68b-3492-448d-a6f9-26fa3fd0b646&rpid=1295&seatid=seat1&zoneid=430392&cb=62735099&tid=a_430392_beac4c1515da4576acf6cb9c5340b40c', + 'adm': 'yo a creative', + 'crid': 'cridprebidrtb', + 'w': 300, + 'h': 250 + }, + ] + } + ], + 'ext': { + 'iid': 13487408, + sync: { + pixels: [ + { + url: 'http://idprovider3.com' + }, + { + url: 'http://idprovider4.com' + } + ] + } + } + }, + 'headers': {} } - const tooLong = () => { - let str = '' - for (let i = 0; i < 10000; i++) { - str = str + String.fromCharCode(i % 100) + const returnStatement = spec.getUserSyncs(pixelEnabledOptions, [...serverResponse, resp2]); + expect(returnStatement.length).to.equal(4); + expect(returnStatement).to.deep.include.members([ + { type: 'image', url: 'http://idprovider1.com' }, + { type: 'image', url: 'http://idprovider2.com' }, + { type: 'image', url: 'http://idprovider3.com' }, + { type: 'image', url: 'http://idprovider4.com' } + ]); + }); + }); + + describe('prebid 3 upgrade', function() { + const bidRequests = [{ + 'bidder': 'sovrn', + 'params': { + 'tagid': '403370' + }, + 'adUnitCode': 'adunit-code', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] } - return str + }, + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + }]; + const bidderRequest = { + refererInfo: { + referer: 'http://example.com/page.html', } - const data = {name: 'Oscar Hathenswiotch'} - thrown.message = tooLong() - const err = new LogError(thrown, data) - err.append() - const errList = LogError.getErrPxls() - expect(errList.length).to.equal(4) - const errdata = JSON.parse(atob(errList[3].url.split('=')[1])) - expect(errdata.d).to.be.an('undefined') - expect(errdata.s).to.be.an('undefined') - expect(errdata.m).to.equal('unknown error message') + }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + it('gets sizes from mediaTypes.banner', function() { + expect(payload.imp[0].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]) + expect(payload.imp[0].banner.w).to.equal(1) + expect(payload.imp[0].banner.h).to.equal(1) + }) + + it('gets correct site info', function() { + expect(payload.site.page).to.equal('http://example.com/page.html'); + expect(payload.site.domain).to.equal('example.com'); }) }) }) diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js index d1662e162aa..be550e8fc83 100644 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ b/test/spec/modules/spotxBidAdapter_spec.js @@ -148,7 +148,8 @@ describe('the spotx adapter', function () { }; bid.userId = { - id5id: 'id5id_1' + id5id: 'id5id_1', + tdid: 'tdid_1' }; bid.crumbs = { @@ -192,6 +193,15 @@ describe('the spotx adapter', function () { uids: [{ id: 'id5id_1' }] + }, + { + source: 'adserver.org', + uids: [{ + id: 'tdid_1', + ext: { + rtiPartner: 'TDID' + } + }] }], fpc: 'pubcid_1' }) diff --git a/test/spec/modules/staqAnalyticsAdapter_spec.js b/test/spec/modules/staqAnalyticsAdapter_spec.js index 33c85d11431..593871fd9d6 100644 --- a/test/spec/modules/staqAnalyticsAdapter_spec.js +++ b/test/spec/modules/staqAnalyticsAdapter_spec.js @@ -1,5 +1,5 @@ -import analyticsAdapter, {ExpiringQueue, getUmtSource, storage} from 'modules/staqAnalyticsAdapter'; -import {expect} from 'chai'; +import analyticsAdapter, { ExpiringQueue, getUmtSource, storage } from 'modules/staqAnalyticsAdapter'; +import { expect } from 'chai'; import adapterManager from 'src/adapterManager'; import CONSTANTS from 'src/constants.json'; @@ -32,60 +32,60 @@ const CAMPAIGN = { c5: '5' }; -describe('', function () { +describe('', function() { let sandbox; - before(function () { + before(function() { sandbox = sinon.sandbox.create(); }); - after(function () { + after(function() { sandbox.restore(); analyticsAdapter.disableAnalytics(); }); - describe('UTM source parser', function () { + describe('UTM source parser', function() { let stubSetItem; let stubGetItem; - before(function () { + before(function() { stubSetItem = sandbox.stub(storage, 'setItem'); stubGetItem = sandbox.stub(storage, 'getItem'); }); - afterEach(function () { + afterEach(function() { sandbox.reset(); }); - it('should parse first direct visit as (direct)', function () { + it('should parse first direct visit as (direct)', function() { stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); stubSetItem.returns(undefined); let source = getUmtSource('http://example.com'); expect(source).to.be.eql(DIRECT); }); - it('should parse visit from google as organic', function () { + it('should parse visit from google as organic', function() { stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); stubSetItem.returns(undefined); let source = getUmtSource('http://example.com', 'https://www.google.com/search?q=pikachu'); expect(source).to.be.eql(GOOGLE_ORGANIC); }); - it('should parse referral visit', function () { + it('should parse referral visit', function() { stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); stubSetItem.returns(undefined); let source = getUmtSource('http://example.com', 'http://lander.com/lander.html'); expect(source).to.be.eql(REFERRER); }); - it('should parse referral visit from same domain as direct', function () { + it('should parse referral visit from same domain as direct', function() { stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); stubSetItem.returns(undefined); let source = getUmtSource('http://lander.com/news.html', 'http://lander.com/lander.html'); expect(source).to.be.eql(DIRECT); }); - it('should parse campaign visit', function () { + it('should parse campaign visit', function() { stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); stubSetItem.returns(undefined); let source = getUmtSource('http://lander.com/index.html?utm_campaign=new_campaign&utm_source=adkernel&utm_medium=email&utm_c1=1&utm_c2=2&utm_c3=3&utm_c4=4&utm_c5=5'); @@ -93,12 +93,12 @@ describe('', function () { }); }); - describe('ExpiringQueue', function () { + describe('ExpiringQueue', function() { let timer; - before(function () { + before(function() { timer = sandbox.useFakeTimers(0); }); - after(function () { + after(function() { timer.restore(); }); @@ -135,7 +135,9 @@ describe('', function () { params: {}, adUnitCode: 'container-1', transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', - sizes: [[300, 250]], + sizes: [ + [300, 250] + ], bidId: '208750227436c1', bidderRequestId: '1a6fc81528d0f6', auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' @@ -176,26 +178,26 @@ describe('', function () { auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' }]; - describe('Analytics adapter', function () { + describe('Analytics adapter', function() { let ajaxStub; let timer; - before(function () { + before(function() { ajaxStub = sandbox.stub(analyticsAdapter, 'ajaxCall'); timer = sandbox.useFakeTimers(0); }); - beforeEach(function () { + beforeEach(function() { sandbox.stub(events, 'getEvents').callsFake(() => { return [] }); }); - afterEach(function () { + afterEach(function() { events.getEvents.restore(); }); - it('should be configurable', function () { + it('should be configurable', function() { adapterManager.registerAnalyticsAdapter({ code: 'staq', adapter: analyticsAdapter @@ -213,14 +215,14 @@ describe('', function () { expect(analyticsAdapter.context).to.have.property('connectionId', 777); }); - it('should handle auction init event', function () { - events.emit(CONSTANTS.EVENTS.AUCTION_INIT, {config: {}, timeout: 3000}); + it('should handle auction init event', function() { + events.emit(CONSTANTS.EVENTS.AUCTION_INIT, { config: {}, timeout: 3000 }); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(1); - expect(ev[0]).to.be.eql({event: 'auctionInit', auctionId: undefined}); + expect(ev[0]).to.be.eql({ event: 'auctionInit', auctionId: undefined }); }); - it('should handle bid request event', function () { + it('should handle bid request event', function() { events.emit(CONSTANTS.EVENTS.BID_REQUESTED, REQUEST); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(2); @@ -233,7 +235,7 @@ describe('', function () { }); }); - it('should handle bid response event', function () { + it('should handle bid response event', function() { events.emit(CONSTANTS.EVENTS.BID_RESPONSE, RESPONSE); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(3); @@ -265,7 +267,7 @@ describe('', function () { }) }); - it('should handle winning bid', function () { + it('should handle winning bid', function() { events.emit(CONSTANTS.EVENTS.BID_WON, RESPONSE); const ev = analyticsAdapter.context.queue.peekAll(); expect(ev).to.have.length(6); @@ -283,7 +285,7 @@ describe('', function () { }); }); - it('should handle auction end event', function () { + it('should handle auction end event', function() { timer.tick(447); events.emit(CONSTANTS.EVENTS.AUCTION_END, RESPONSE); let ev = analyticsAdapter.context.queue.peekAll(); @@ -291,9 +293,8 @@ describe('', function () { expect(ajaxStub.calledOnce).to.be.equal(true); let firstCallArgs0 = ajaxStub.firstCall.args[0]; ev = JSON.parse(firstCallArgs0); - // console.log('AUCTION END EVENT SHAPE ' + JSON.stringify(ev)); - const ev6 = ev[6]; - expect(ev6.connId).to.be.eql(777); + const ev6 = ev['events'][6]; + expect(ev['connId']).to.be.eql(777); expect(ev6.auctionId).to.be.eql('5018eb39-f900-4370-b71e-3bb5b48d324f'); expect(ev6.event).to.be.eql('auctionEnd'); }); diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index af1c7a9c01b..5d92f9d7a08 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -226,6 +226,34 @@ describe('teadsBidAdapter', () => { checkMediaTypesSizes(mediaTypesPlayerSize, '32x34') }); + it('should add schain info to payload if available', function () { + const bidRequest = Object.assign({}, bidRequests[0], { + schain: { + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + } + }); + + const request = spec.buildRequests([bidRequest], bidderResquestDefault); + const payload = JSON.parse(request.data); + + expect(payload.schain).to.exist; + expect(payload.schain).to.deep.equal({ + ver: '1.0', + complete: 1, + nodes: [{ + asi: 'example.com', + sid: '00001', + hp: 1 + }] + }); + }); + it('should use good mediaTypes video sizes', function() { const mediaTypesVideoSizes = { 'mediaTypes': { diff --git a/test/spec/modules/trionBidAdapter_spec.js b/test/spec/modules/trionBidAdapter_spec.js index 805ae70a339..63c2b71a31a 100644 --- a/test/spec/modules/trionBidAdapter_spec.js +++ b/test/spec/modules/trionBidAdapter_spec.js @@ -119,6 +119,61 @@ describe('Trion adapter tests', function () { expect(bidUrlParams).to.include(utils.getTopWindowUrl()); delete params.re; }); + + describe('webdriver', function () { + let originalWD; + + beforeEach(function () { + originalWD = window.navigator.webdriver; + window.navigator['__defineGetter__']('webdriver', function () { + return 1; + }); + }); + + afterEach(function () { + window.navigator['__defineGetter__']('webdriver', function () { + return originalWD; + }); + }); + + it('should send the correct state when there is non human traffic', function () { + let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + let bidUrlParams = bidRequests[0].data; + expect(bidUrlParams).to.include('tr_wd=1'); + }); + }); + + describe('visibility', function () { + let originalHD; + let originalVS; + + beforeEach(function () { + originalHD = document.hidden; + originalVS = document.visibilityState; + document['__defineGetter__']('hidden', function () { + return 1; + }); + document['__defineGetter__']('visibilityState', function () { + return 'hidden'; + }); + }); + + afterEach(function () { + document['__defineGetter__']('hidden', function () { + return originalHD; + }); + document['__defineGetter__']('visibilityState', function () { + return originalVS; + }); + }); + + it('should send the correct states when the document is not visible', function () { + let bidRequests = spec.buildRequests(TRION_BID_REQUEST); + let bidUrlParams = bidRequests[0].data; + expect(bidUrlParams).to.include('tr_hd=1'); + expect(bidUrlParams).to.include('tr_vs=hidden'); + }); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 12a2f66dde2..5ad7468f2dc 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -5,6 +5,7 @@ import { deepClone } from 'src/utils'; import prebid from '../../../package.json'; const ENDPOINT = 'https://tlx.3lift.com/header/auction?'; +const GDPR_CONSENT_STR = 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY'; describe('triplelift adapter', function () { const adapter = newBidder(tripleliftAdapterSpec); @@ -107,7 +108,7 @@ describe('triplelift adapter', function () { referer: 'http://examplereferer.com' }, gdprConsent: { - consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', + consentString: GDPR_CONSENT_STR, gdprApplies: true }, }; @@ -235,6 +236,12 @@ describe('triplelift adapter', function () { expect(url).to.match(new RegExp('(?:' + prebid.version + ')')) expect(url).to.match(/(?:referrer)/); }); + it('should return us_privacy param when CCPA info is available', function() { + bidderRequest.uspConsent = '1YYY'; + const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); + const url = request.url; + expect(url).to.match(/(\?|&)us_privacy=1YYY/); + }); it('should return schain when present', function() { const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const { data: payload } = request; @@ -281,7 +288,7 @@ describe('triplelift adapter', function () { referer: 'http://examplereferer.com' }, gdprConsent: { - consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', + consentString: GDPR_CONSENT_STR, gdprApplies: true } }; @@ -355,7 +362,7 @@ describe('triplelift adapter', function () { referer: 'http://examplereferer.com' }, gdprConsent: { - consentString: 'BOONm0NOONm0NABABAENAa-AAAARh7______b9_3__7_9uz_Kv_K7Vf7nnG072lPVA9LTOQ6gEaY', + consentString: GDPR_CONSENT_STR, gdprApplies: true } }; @@ -363,4 +370,49 @@ describe('triplelift adapter', function () { expect(result).to.have.length(2); }); }); + + describe('getUserSyncs', function() { + let expectedIframeSyncUrl = 'https://eb2.3lift.com/sync?gdpr=true&cmp_cs=' + GDPR_CONSENT_STR + '&'; + let expectedImageSyncUrl = 'https://eb2.3lift.com/sync?px=1&src=prebid&gdpr=true&cmp_cs=' + GDPR_CONSENT_STR + '&'; + + it('returns undefined when syncing is not enabled', function() { + expect(tripleliftAdapterSpec.getUserSyncs({})).to.equal(undefined); + expect(tripleliftAdapterSpec.getUserSyncs()).to.equal(undefined); + }); + + it('returns iframe user sync pixel when iframe syncing is enabled', function() { + let syncOptions = { + iframeEnabled: true + }; + let result = tripleliftAdapterSpec.getUserSyncs(syncOptions); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal(expectedIframeSyncUrl); + }); + + it('returns image user sync pixel when iframe syncing is disabled', function() { + let syncOptions = { + pixelEnabled: true + }; + let result = tripleliftAdapterSpec.getUserSyncs(syncOptions); + expect(result[0].type).to.equal('image') + expect(result[0].url).to.equal(expectedImageSyncUrl); + }); + + it('returns iframe user sync pixel when both options are enabled', function() { + let syncOptions = { + pixelEnabled: true, + iframeEnabled: true + }; + let result = tripleliftAdapterSpec.getUserSyncs(syncOptions); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal(expectedIframeSyncUrl); + }); + it('sends us_privacy param when info is available', function() { + let syncOptions = { + iframeEnabled: true + }; + let result = tripleliftAdapterSpec.getUserSyncs(syncOptions, null, null, '1YYY'); + expect(result[0].url).to.match(/(\?|&)us_privacy=1YYY/); + }); + }); }); diff --git a/test/spec/modules/trustxBidAdapter_spec.js b/test/spec/modules/trustxBidAdapter_spec.js index 4256012ba0b..aee6311cb85 100644 --- a/test/spec/modules/trustxBidAdapter_spec.js +++ b/test/spec/modules/trustxBidAdapter_spec.js @@ -168,6 +168,14 @@ describe('TrustXAdapter', function () { expect(payload).to.have.property('gdpr_applies', '1'); }); + it('if usPrivacy is present payload must have us_privacy param', function () { + const bidderRequestWithUSP = Object.assign({uspConsent: '1YNN'}, bidderRequest); + const request = spec.buildRequests(bidRequests, bidderRequestWithUSP); + expect(request.data).to.be.an('string'); + const payload = parseRequest(request.data); + expect(payload).to.have.property('us_privacy', '1YNN'); + }); + it('should convert keyword params to proper form and attaches to request', function () { const bidRequestWithKeywords = [].concat(bidRequests); bidRequestWithKeywords[1] = Object.assign({}, diff --git a/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js b/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..791b559ef25 --- /dev/null +++ b/test/spec/modules/ucfunnelAnalyticsAdapter_spec.js @@ -0,0 +1,491 @@ +import { + ucfunnelAnalyticsAdapter, parseBidderCode, parseAdUnitCode, + ANALYTICS_VERSION, BIDDER_STATUS +} from 'modules/ucfunnelAnalyticsAdapter'; + +import {expect} from 'chai'; + +const events = require('src/events'); +const constants = require('src/constants.json'); + +const pbuid = 'pbuid-AA778D8A796AEA7A0843E2BBEB677766'; +const adid = 'test-ad-83444226E44368D1E32E49EEBE6D29'; +const auctionId = 'b0b39610-b941-4659-a87c-de9f62d3e13e'; + +describe('ucfunnel Prebid AnalyticsAdapter Testing', function () { + describe('event tracking and message cache manager', function () { + let xhr; + + beforeEach(function () { + const configOptions = { + pbuid: pbuid, + adid: adid, + sampling: 0, + }; + + ucfunnelAnalyticsAdapter.enableAnalytics({ + provider: 'ucfunnelAnalytics', + options: configOptions + }); + xhr = sinon.useFakeXMLHttpRequest(); + }); + + afterEach(function () { + ucfunnelAnalyticsAdapter.disableAnalytics(); + xhr.restore(); + }); + + describe('#parseBidderCode()', function() { + it('should get lower case bidder code from bidderCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'ucfunnel', + bidderCode: 'UCFUNNEL', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('ucfunnel'); + }); + it('should get lower case bidder code from bidder field value as bidderCode field is missing', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'UCFUNNEL', + bidderCode: '', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseBidderCode(receivedBids[0]); + expect(result).to.equal('ucfunnel'); + }); + }); + + describe('#parseAdUnitCode()', function() { + it('should get lower case adUnit code from adUnitCode field value', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'ADUNIT', + bidder: 'ucfunnel', + bidderCode: 'UCFUNNEL', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ]; + const result = parseAdUnitCode(receivedBids[0]); + expect(result).to.equal('adunit'); + }); + }); + + describe('#getCachedAuction()', function() { + const existing = {timeoutBids: [{}]}; + ucfunnelAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; + + it('should get the existing cached object if it exists', function() { + const result = ucfunnelAnalyticsAdapter.getCachedAuction('test_auction_id'); + + expect(result).to.equal(existing); + }); + + it('should create a new object and store it in the cache on cache miss', function() { + const result = ucfunnelAnalyticsAdapter.getCachedAuction('no_such_id'); + + expect(result).to.deep.include({ + timeoutBids: [], + }); + }); + }); + + describe('when formatting JSON payload sent to backend', function() { + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'ucfunnel', + bidderCode: 'ucfunnel', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'ucfunnelx', + bidderCode: 'ucfunnelx', + requestId: 'b2c3d4e5', + timeToRespond: 100, + cpm: 0.08, + currency: 'USD', + ad: 'fake ad2' + }, + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'ucfunnel', + bidderCode: 'ucfunnel', + requestId: 'c3d4e5f6', + timeToRespond: 120, + cpm: 0.09, + currency: 'USD', + ad: 'fake ad3' + }, + ]; + const highestCpmBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'ucfunnel', + bidderCode: 'ucfunnel', + // No requestId + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + } + ]; + const noBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'ucfunnel', + bidderCode: 'ucfunnel', + bidId: 'a1b2c3d4', + } + ]; + const timeoutBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_2', + bidder: 'ucfunnel', + bidderCode: 'ucfunnel', + bidId: '00123d4c', + } + ]; + function assertHavingRequiredMessageFields(message) { + expect(message).to.include({ + version: ANALYTICS_VERSION, + auctionId: auctionId, + pbuid: pbuid, + adid: adid, + // referrer: window.location.href, + sampling: 0, + prebid: '$prebid.version$', + }); + } + + describe('#createCommonMessage', function() { + it('should correctly serialize some common fields', function() { + const message = ucfunnelAnalyticsAdapter.createCommonMessage(auctionId); + + assertHavingRequiredMessageFields(message); + }); + }); + + describe('#serializeBidResponse', function() { + it('should handle BID properly and serialize bid price related fields', function() { + const result = ucfunnelAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); + + expect(result).to.include({ + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 72, + cpm: 0.1, + currency: 'USD', + }); + }); + + it('should handle NO_BID properly and set status to noBid', function() { + const result = ucfunnelAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); + + expect(result).to.include({ + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.NO_BID, + }); + }); + + it('should handle BID_WON properly and serialize bid price related fields', function() { + const result = ucfunnelAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID_WON); + + expect(result).to.include({ + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 72, + cpm: 0.1, + currency: 'USD', + }); + }); + + it('should handle TIMEOUT properly and set status to timeout and isTimeout to true', function() { + const result = ucfunnelAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); + + expect(result).to.include({ + prebidWon: false, + isTimeout: true, + status: BIDDER_STATUS.TIMEOUT, + }); + }); + }); + + describe('#addBidResponseToMessage()', function() { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + const message = { + adUnits: {} + }; + ucfunnelAnalyticsAdapter.addBidResponseToMessage(message, noBids[0], BIDDER_STATUS.NO_BID); + + expect(message.adUnits).to.deep.include({ + 'adunit_2': { + 'ucfunnel': { + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.NO_BID, + } + } + }); + }); + }); + + describe('#createBidMessage()', function() { + it('should format auction message sent to the backend', function() { + const args = { + auctionId: auctionId, + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + adUnitCodes: ['adunit_1', 'adunit_2'], + bidsReceived: receivedBids, + noBids: noBids + }; + + const result = ucfunnelAnalyticsAdapter.createBidMessage(args, highestCpmBids, timeoutBids); + + assertHavingRequiredMessageFields(result); + expect(result).to.deep.include({ + auctionElapsed: 100, + adUnits: { + 'adunit_1': { + 'ucfunnel': { + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 72, + cpm: 0.1, + currency: 'USD', + }, + 'ucfunnelx': { + prebidWon: false, + isTimeout: false, + status: BIDDER_STATUS.BID, + time: 100, + cpm: 0.08, + currency: 'USD', + } + }, + 'adunit_2': { + // this bid result exists in both bid and noBid arrays and should be treated as bid + 'ucfunnel': { + prebidWon: false, + isTimeout: true, + status: BIDDER_STATUS.TIMEOUT, + } + } + } + }); + }); + }); + + describe('#createImpressionMessage()', function() { + it('should format message sent to the backend with the bid result', function() { + const bid = receivedBids[0]; + const result = ucfunnelAnalyticsAdapter.createImpressionMessage(bid); + + assertHavingRequiredMessageFields(result); + expect(result.adUnits).to.deep.include({ + 'adunit_1': { + 'ucfunnel': { + prebidWon: true, + isTimeout: false, + status: BIDDER_STATUS.BID_WON, + time: 72, + cpm: 0.1, + currency: 'USD', + } + } + }); + }); + }); + + describe('#handleBidTimeout()', function() { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + ucfunnelAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; + const args = [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }]; + + ucfunnelAnalyticsAdapter.handleBidTimeout(args); + const result = ucfunnelAnalyticsAdapter.getCachedAuction('test_timeout_auction_id'); + expect(result).to.deep.include({ + timeoutBids: [{ + auctionId: 'test_timeout_auction_id', + timestamp: 1234567890, + timeout: 3000, + auctionEnd: 1234567990, + bidsReceived: receivedBids, + noBids: noBids + }] + }); + }); + }); + describe('#handleBidWon()', function() { + it('should call createImpressionMessage once as BID_WON event was triggered', function() { + sinon.spy(ucfunnelAnalyticsAdapter, 'createImpressionMessage'); + const receivedBids = [ + { + auctionId: auctionId, + adUnitCode: 'adunit_1', + bidder: 'ucfunnel', + bidderCode: 'ucfunnel', + requestId: 'a1b2c3d4', + timeToRespond: 72, + cpm: 0.1, + currency: 'USD', + ad: 'fake ad1' + }, + ] + + ucfunnelAnalyticsAdapter.handleBidWon(receivedBids[0]); + sinon.assert.callCount(ucfunnelAnalyticsAdapter.createImpressionMessage, 1); + ucfunnelAnalyticsAdapter.createImpressionMessage.restore(); + }); + }); + }); + }); + + describe('ucfunnel Analytics Adapter track handler ', function () { + const configOptions = { + pbuid: pbuid, + adid: adid, + sampling: 1, + }; + + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + ucfunnelAnalyticsAdapter.enableAnalytics({ + provider: 'ucfunnelAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + ucfunnelAnalyticsAdapter.disableAnalytics(); + events.getEvents.restore(); + }); + + it('should call handleBidWon as BID_WON trigger event', function() { + sinon.spy(ucfunnelAnalyticsAdapter, 'handleBidWon'); + events.emit(constants.EVENTS.BID_WON, {}); + sinon.assert.callCount(ucfunnelAnalyticsAdapter.handleBidWon, 1); + ucfunnelAnalyticsAdapter.handleBidWon.restore(); + }); + + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + sinon.spy(ucfunnelAnalyticsAdapter, 'handleBidTimeout'); + events.emit(constants.EVENTS.BID_TIMEOUT, {}); + sinon.assert.callCount(ucfunnelAnalyticsAdapter.handleBidTimeout, 1); + ucfunnelAnalyticsAdapter.handleBidTimeout.restore(); + }); + + it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + sinon.spy(ucfunnelAnalyticsAdapter, 'handleAuctionEnd'); + events.emit(constants.EVENTS.AUCTION_END, {}); + sinon.assert.callCount(ucfunnelAnalyticsAdapter.handleAuctionEnd, 1); + ucfunnelAnalyticsAdapter.handleAuctionEnd.restore(); + }); + }); + + describe('enableAnalytics and config parser', function () { + const configOptions = { + pbuid: pbuid, + adid: adid, + sampling: 0, + }; + + beforeEach(function () { + ucfunnelAnalyticsAdapter.enableAnalytics({ + provider: 'ucfunnelAnalytics', + options: configOptions + }); + }); + + afterEach(function () { + ucfunnelAnalyticsAdapter.disableAnalytics(); + }); + + it('should parse config correctly with optional values', function () { + expect(ucfunnelAnalyticsAdapter.getAnalyticsOptions().options).to.deep.equal(configOptions); + expect(ucfunnelAnalyticsAdapter.getAnalyticsOptions().pbuid).to.equal(configOptions.pbuid); + expect(ucfunnelAnalyticsAdapter.getAnalyticsOptions().adid).to.equal(configOptions.adid); + expect(ucfunnelAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + }); + + it('should not enable Analytics when adid is missing', function () { + const configOptions = { + options: { + pbuid: pbuid + } + }; + const validConfig = ucfunnelAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + + it('should not enable Analytics when pbuid is missing', function () { + const configOptions = { + options: { + adid: adid + } + }; + const validConfig = ucfunnelAnalyticsAdapter.initConfig(configOptions); + expect(validConfig).to.equal(false); + }); + it('should fall back to default value when sampling factor is not number', function () { + const configOptions = { + options: { + pbuid: pbuid, + adid: adid, + sampling: 'string', + } + }; + ucfunnelAnalyticsAdapter.enableAnalytics({ + provider: 'ucfunnelAnalytics', + options: configOptions + }); + + expect(ucfunnelAnalyticsAdapter.getAnalyticsOptions().sampled).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/ucfunnelBidAdapter_spec.js b/test/spec/modules/ucfunnelBidAdapter_spec.js index 32daf5ecb96..bebd780a587 100644 --- a/test/spec/modules/ucfunnelBidAdapter_spec.js +++ b/test/spec/modules/ucfunnelBidAdapter_spec.js @@ -7,11 +7,26 @@ const BIDDER_CODE = 'ucfunnel'; const validBannerBidReq = { bidder: BIDDER_CODE, params: { - adid: 'ad-34BBD2AA24B678BBFD4E7B9EE3B872D' + adid: 'ad-34BBD2AA24B678BBFD4E7B9EE3B872D', + bidfloor: 1.0 }, sizes: [[300, 250]], bidId: '263be71e91dd9d', auctionId: '9ad1fa8d-2297-4660-a018-b39945054746', + 'schain': { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'exchange1.com', + 'sid': '1234', + 'hp': 1, + 'rid': 'bid-request-1', + 'name': 'publisher', + 'domain': 'publisher.com' + } + ] + } }; const invalidBannerBidReq = { @@ -28,11 +43,13 @@ const validBannerBidRes = { creative_type: BANNER, ad_id: 'ad-34BBD2AA24B678BBFD4E7B9EE3B872D', adm: '
', - cpm: 0.01, + cpm: 1.01, height: 250, width: 300 }; +const invalidBannerBidRes = ''; + const validVideoBidReq = { bidder: BIDDER_CODE, params: { @@ -48,7 +65,7 @@ const validVideoBidRes = { ad_id: 'ad-9A22D466494297EAC443D967B2622DA9', vastUrl: 'https://ads.aralego.com/ads/58f9749f-0553-4993-8d9a-013a38b29e55', vastXml: 'ucX I-Primo 00:00:30', - cpm: 0.01, + cpm: 1.01, width: 640, height: 360 }; @@ -84,7 +101,7 @@ const validNativeBidRes = { clickUrl: 'https://www.ucfunnel.com', impressionTrackers: ['https://www.aralego.net/imp?mf=native&adid=ad-9A22D466494297EAC443D967B2622DA9&auc=9ad1fa8d-2297-4660-a018-b39945054746'], }, - cpm: 0.01, + cpm: 1.01, height: 1, width: 1 }; @@ -114,6 +131,7 @@ describe('ucfunnel Adapter', function () { const [ width, height ] = validBannerBidReq.sizes[0]; expect(data.w).to.equal(width); expect(data.h).to.equal(height); + expect(data.schain).to.equal('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com'); }); it('must parse bid size from a nested array', function () { @@ -141,7 +159,43 @@ describe('ucfunnel Adapter', function () { expect(bid.mediaType).to.equal(BANNER); expect(bid.ad).to.exist; expect(bid.requestId).to.equal('263be71e91dd9d'); - expect(bid.cpm).to.equal(0.01); + expect(bid.cpm).to.equal(1.01); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + }); + }); + + describe('handle banner no ad', function () { + const request = spec.buildRequests([ validBannerBidReq ]); + const result = spec.interpretResponse({body: invalidBannerBidRes}, request[0]); + it('should build bid array for banner', function () { + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', function () { + const bid = result[0]; + + expect(bid.ad).to.exist; + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(0); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + }); + }); + + describe('handle banner cpm under bidfloor', function () { + const request = spec.buildRequests([ validBannerBidReq ]); + const result = spec.interpretResponse({body: invalidBannerBidRes}, request[0]); + it('should build bid array for banner', function () { + expect(result.length).to.equal(1); + }); + + it('should have all relevant fields', function () { + const bid = result[0]; + + expect(bid.ad).to.exist; + expect(bid.requestId).to.equal('263be71e91dd9d'); + expect(bid.cpm).to.equal(0); expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); }); @@ -161,7 +215,7 @@ describe('ucfunnel Adapter', function () { expect(bid.vastUrl).to.exist; expect(bid.vastXml).to.exist; expect(bid.requestId).to.equal('263be71e91dd9f'); - expect(bid.cpm).to.equal(0.01); + expect(bid.cpm).to.equal(1.01); expect(bid.width).to.equal(640); expect(bid.height).to.equal(360); }); @@ -180,7 +234,7 @@ describe('ucfunnel Adapter', function () { expect(bid.mediaType).to.equal(NATIVE); expect(bid.native).to.exist; expect(bid.requestId).to.equal('263be71e91dda0'); - expect(bid.cpm).to.equal(0.01); + expect(bid.cpm).to.equal(1.01); expect(bid.width).to.equal(1); expect(bid.height).to.equal(1); }); diff --git a/test/spec/modules/underdogmediaBidAdapter_spec.js b/test/spec/modules/underdogmediaBidAdapter_spec.js index 6f4df57d316..68d6c985450 100644 --- a/test/spec/modules/underdogmediaBidAdapter_spec.js +++ b/test/spec/modules/underdogmediaBidAdapter_spec.js @@ -13,7 +13,11 @@ describe('UnderdogMedia adapter', function () { siteId: 12143 }, adUnitCode: '/19968336/header-bid-tag-1', - sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600], [728, 90], [160, 600], [320, 50]], + } + }, bidId: '23acc48ad47af5', auctionId: '0fb4905b-9456-4152-86be-c6f6d259ba99', bidderRequestId: '1c56ad30b9b8ca8', @@ -43,7 +47,11 @@ describe('UnderdogMedia adapter', function () { params: { siteId: '12143' }, - sizes: [[300, 250], [300, 600]] + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } }; const isValid = spec.isBidRequestValid(validBid); @@ -66,7 +74,11 @@ describe('UnderdogMedia adapter', function () { let invalidBid = { bidder: 'underdogmedia', params: {}, - sizes: [[300, 250], [300, 600]] + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + } }; const isValid = spec.isBidRequestValid(invalidBid); @@ -77,8 +89,12 @@ describe('UnderdogMedia adapter', function () { let bidRequests = [ { bidId: '3c9408cdbf2f68', - sizes: [[300, 250]], bidder: 'underdogmedia', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, params: { siteId: '12143' }, @@ -95,7 +111,11 @@ describe('UnderdogMedia adapter', function () { let bidRequests = [ { bidId: '3c9408cdbf2f68', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, bidder: 'underdogmedia', params: { siteId: '12143' @@ -113,7 +133,11 @@ describe('UnderdogMedia adapter', function () { let bidRequests = [ { bidId: '3c9408cdbf2f68', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, bidder: 'underdogmedia', params: { siteId: '12143' @@ -133,7 +157,11 @@ describe('UnderdogMedia adapter', function () { let bidRequests = [ { bidId: '3c9408cdbf2f68', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, bidder: 'underdogmedia', params: { siteId: '12143' @@ -164,7 +192,11 @@ describe('UnderdogMedia adapter', function () { let bidRequests = [ { bidId: '3c9408cdbf2f68', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, bidder: 'underdogmedia', params: { siteId: '12143' @@ -199,7 +231,11 @@ describe('UnderdogMedia adapter', function () { let bidRequests = [ { bidId: '3c9408cdbf2f68', - sizes: [[300, 250], [728, 90]], + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + } + }, bidder: 'underdogmedia', params: { siteId: '12143' diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index cd72ca9670f..40ffb726051 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -12,6 +12,7 @@ import events from 'src/events'; import CONSTANTS from 'src/constants.json'; import {unifiedIdSubmodule} from 'modules/userId/unifiedIdSystem'; import {pubCommonIdSubmodule} from 'modules/userId/pubCommonIdSystem'; +import {britepoolIdSubmodule} from 'modules/britepoolIdSystem'; import {id5IdSubmodule} from 'modules/id5IdSystem'; import {identityLinkSubmodule} from 'modules/identityLinkIdSystem'; import {liveIntentIdSubmodule} from 'modules/liveIntentIdSystem'; @@ -21,7 +22,7 @@ let expect = require('chai').expect; const EXPIRED_COOKIE_DATE = 'Thu, 01 Jan 1970 00:00:01 GMT'; describe('User ID', function() { - function getConfigMock(configArr1, configArr2, configArr3, configArr4) { + function getConfigMock(configArr1, configArr2, configArr3, configArr4, configArr5) { return { userSync: { syncDelay: 0, @@ -29,7 +30,8 @@ describe('User ID', function() { (configArr1 && configArr1.length >= 3) ? getStorageMock.apply(null, configArr1) : null, (configArr2 && configArr2.length >= 3) ? getStorageMock.apply(null, configArr2) : null, (configArr3 && configArr3.length >= 3) ? getStorageMock.apply(null, configArr3) : null, - (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null + (configArr4 && configArr4.length >= 3) ? getStorageMock.apply(null, configArr4) : null, + (configArr5 && configArr5.length >= 3) ? getStorageMock.apply(null, configArr5) : null ].filter(i => i)} } } @@ -298,7 +300,7 @@ describe('User ID', function() { expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); - it('config with 1 configuration should create 1 submodule', function () { + it('config with 1 configurations should create 1 submodules', function () { setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); init(config); config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); @@ -306,8 +308,8 @@ describe('User ID', function() { expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 1 submodules'); }); - it('config with 5 configurations should result in 5 submodules add', function () { - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule]); + it('config with 6 configurations should result in 6 submodules add', function () { + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule]); init(config); config.setConfig({ usersync: { @@ -326,10 +328,14 @@ describe('User ID', function() { }, { name: 'liveIntentId', storage: { name: '_li_pbid', type: 'cookie' } + }, { + name: 'britepoolId', + value: { 'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd' } + }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 5 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.equal('User ID - usersync config updated for 6 submodules'); }); it('config syncDelay updates module correctly', function () { @@ -816,18 +822,40 @@ describe('User ID', function() { }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId and id5Id have data to pass', function(done) { + it('test hook from britepoolid cookies', function(done) { + // simulate existing browser local storage values + utils.setCookie('britepoolid', JSON.stringify({'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'}), (new Date(Date.now() + 5000).toUTCString())); + + setSubmoduleRegistry([britepoolIdSubmodule]); + init(config); + config.setConfig(getConfigMock(['britepoolId', 'britepoolid', 'cookie'])); + + requestBidsHook(function() { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('279c0161-5152-487f-809e-05d7f7e653fd'); + }); + }); + utils.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); + done(); + }, {adUnits}); + }); + + it('test hook when pubCommonId, unifiedId, id5Id, identityLink, and britepoolId have data to pass', function(done) { utils.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); + utils.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule]); init(config); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], ['id5Id', 'id5id', 'cookie'], - ['identityLink', 'idl_env', 'cookie'])); + ['identityLink', 'idl_env', 'cookie'], + ['britepoolId', 'britepoolid', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -844,21 +872,26 @@ describe('User ID', function() { // check that identityLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.idl_env'); expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('testbritepoolid'); }); }); utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); utils.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + utils.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook when pubCommonId, unifiedId and id5Id have their modules added before and after init', function(done) { + it('test hook when pubCommonId, unifiedId, id5Id and britepoolId have their modules added before and after init', function(done) { utils.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); + utils.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); setSubmoduleRegistry([]); @@ -871,11 +904,13 @@ describe('User ID', function() { attachIdSystem(unifiedIdSubmodule); attachIdSystem(id5IdSubmodule); attachIdSystem(identityLinkSubmodule); + attachIdSystem(britepoolIdSubmodule); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], ['unifiedId', 'unifiedid', 'cookie'], ['id5Id', 'id5id', 'cookie'], - ['identityLink', 'idl_env', 'cookie'])); + ['identityLink', 'idl_env', 'cookie'], + ['britepoolId', 'britepoolid', 'cookie'])); requestBidsHook(function() { adUnits.forEach(unit => { @@ -892,12 +927,16 @@ describe('User ID', function() { // also check that identityLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.idl_env'); expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('testbritepoolid'); }); }); utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); utils.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + utils.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -907,9 +946,10 @@ describe('User ID', function() { utils.setCookie('unifiedid', JSON.stringify({'TDID': 'cookie-value-add-module-variations'}), new Date(Date.now() + 5000).toUTCString()); utils.setCookie('id5id', JSON.stringify({'ID5ID': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', new Date(Date.now() + 5000).toUTCString()); + utils.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); utils.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule]); + setSubmoduleRegistry([pubCommonIdSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule]); init(config); config.setConfig({ @@ -923,6 +963,8 @@ describe('User ID', function() { name: 'id5Id', storage: { name: 'id5id', type: 'cookie' } }, { name: 'identityLink', storage: { name: 'idl_env', type: 'cookie' } + }, { + name: 'britepoolId', storage: { name: 'britepoolid', type: 'cookie' } }, { name: 'mockId', storage: { name: 'MOCKID', type: 'cookie' } }] @@ -958,6 +1000,9 @@ describe('User ID', function() { // also check that identityLink id data was copied to bid expect(bid).to.have.deep.nested.property('userId.idl_env'); expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); + // also check that britepoolId id data was copied to bid + expect(bid).to.have.deep.nested.property('userId.britepoolid'); + expect(bid.userId.britepoolid).to.equal('testbritepoolid'); // check MockId data was copied to bid expect(bid).to.have.deep.nested.property('userId.mid'); expect(bid.userId.mid).to.equal('123456778'); @@ -967,6 +1012,7 @@ describe('User ID', function() { utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); utils.setCookie('id5id', '', EXPIRED_COOKIE_DATE); utils.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); + utils.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); utils.setCookie('MOCKID', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); @@ -985,6 +1031,7 @@ describe('User ID', function() { sinon.stub(utils, 'triggerPixel'); utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); afterEach(function() { @@ -993,6 +1040,7 @@ describe('User ID', function() { utils.triggerPixel.restore(); utils.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); utils.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); + utils.setCookie('_parrable_eid', '', EXPIRED_COOKIE_DATE); }); it('pubcid callback with url', function () { @@ -1042,5 +1090,73 @@ describe('User ID', function() { events.emit(CONSTANTS.EVENTS.AUCTION_END, {}); expect(requests[0].url).to.equal('//match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); }); + + it('callback for submodules that always need to refresh stored id', function(done) { + let adUnits = [getAdUnitMock()]; + let innerAdUnits; + const parrableStoredId = '01.1111111111.test-eid'; + const parrableRefreshedId = '02.2222222222.test-eid'; + utils.setCookie('_parrable_eid', parrableStoredId, (new Date(Date.now() + 5000).toUTCString())); + + const parrableIdSubmoduleMock = { + name: 'parrableId', + decode: function(value) { + return { 'parrableid': value }; + }, + getId: function() { + return { + callback: function(cb) { + cb(parrableRefreshedId); + } + }; + } + }; + + const parrableConfigMock = { + userSync: { + syncDelay: 0, + userIds: [{ + name: 'parrableId', + storage: { + type: 'cookie', + name: '_parrable_eid' + } + }] + } + }; + + setSubmoduleRegistry([parrableIdSubmoduleMock]); + attachIdSystem(parrableIdSubmoduleMock); + init(config); + config.setConfig(parrableConfigMock); + + // make first bid request, should use stored id value + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + innerAdUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.parrableid'); + expect(bid.userId.parrableid).to.equal(parrableStoredId); + }); + }); + + // attach a handler for auction end event to run the second bid request + events.on(CONSTANTS.EVENTS.AUCTION_END, function handler(submodule) { + if (submodule === 'parrableIdSubmoduleMock') { + // make the second bid request, id should have been refreshed + requestBidsHook((config) => { innerAdUnits = config.adUnits }, {adUnits}); + innerAdUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userId.parrableid'); + expect(bid.userId.parrableid).to.equal(parrableRefreshedId); + }); + }); + events.off(CONSTANTS.EVENTS.AUCTION_END, handler); + done(); + } + }); + + // emit an auction end event to run the submodule callback + events.emit(CONSTANTS.EVENTS.AUCTION_END, 'parrableIdSubmoduleMock'); + }); }); }); diff --git a/test/spec/modules/viBidAdapter_spec.js b/test/spec/modules/viBidAdapter_spec.js index b12d534ff02..8f1c07047ec 100644 --- a/test/spec/modules/viBidAdapter_spec.js +++ b/test/spec/modules/viBidAdapter_spec.js @@ -23,7 +23,8 @@ import { area, get, getViewabilityDescription, - mergeArrays + mergeArrays, + documentFocus } from 'modules/viBidAdapter'; describe('ratioToPercentageCeil', () => { @@ -892,3 +893,19 @@ describe('mergeSizes', () => { ).to.deep.equal([[1, 2], [2, 4], [400, 500], [500, 600]]); }); }); + +describe('documentFocus', () => { + it('calls hasFocus function if it present, converting boolean to an int 0/1 value, returns undefined otherwise', () => { + expect( + documentFocus({ + hasFocus: () => true + }) + ).to.equal(1); + expect( + documentFocus({ + hasFocus: () => false + }) + ).to.equal(0); + expect(documentFocus({})).to.be.undefined; + }); +}); diff --git a/test/spec/modules/viewdeosDXBidAdapter_spec.js b/test/spec/modules/viewdeosDXBidAdapter_spec.js new file mode 100644 index 00000000000..80082347687 --- /dev/null +++ b/test/spec/modules/viewdeosDXBidAdapter_spec.js @@ -0,0 +1,308 @@ +import {expect} from 'chai'; +import {spec} from 'modules/viewdeosDXBidAdapter'; +import {newBidder} from 'src/adapters/bidderFactory'; + +const ENDPOINT = '//hb.sync.viewdeos.com/auction/'; + +const DISPLAY_REQUEST = { + 'bidder': 'viewdeos', + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'mediaTypes': {'banner': {'sizes': [[300, 250], [300, 600]]}} +}; + +const VIDEO_REQUEST = { + 'bidder': 'viewdeos', + 'params': { + 'aid': 12345 + }, + 'bidderRequestId': '7101db09af0db2', + 'auctionId': '2e41f65424c87c', + 'adUnitCode': 'adunit-code', + 'bidId': '84ab500420319d', + 'mediaTypes': {'video': {'playerSize': [480, 360], 'context': 'instream'}} +}; + +const SERVER_VIDEO_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'vastUrl': 'http://rtb.sync.viewdeos.com/vast/?adid=44F2AEB9BFC881B3', + 'requestId': '2e41f65424c87c', + 'url': '44F2AEB9BFC881B3', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 480, + 'cur': 'USD', + 'width': 640, + 'cpm': 0.9 + } + ] +}; + +const SERVER_DISPLAY_RESPONSE = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link1', 'link2'] +}; +const SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS = { + 'source': {'aid': 12345, 'pubId': 54321}, + 'bids': [{ + 'ad': '', + 'requestId': '2e41f65424c87c', + 'creative_id': 342516, + 'cmpId': 342516, + 'height': 250, + 'cur': 'USD', + 'width': 300, + 'cpm': 0.9 + }], + 'cookieURLs': ['link3', 'link4'], + 'cookieURLSTypes': ['image', 'iframe'] +}; + +const videoBidderRequest = { + bidderCode: 'bidderCode', + bids: [{mediaTypes: {video: {}}, bidId: '2e41f65424c87c'}] +}; + +const displayBidderRequest = { + bidderCode: 'bidderCode', + bids: [{bidId: '2e41f65424c87c'}] +}; + +const displayBidderRequestWithGdpr = { + bidderCode: 'bidderCode', + bids: [{bidId: '2e41f65424c87c'}], + gdprConsent: { + gdprApplies: true, + consentString: 'test' + } +}; + +const videoEqResponse = [{ + vastUrl: 'http://rtb.sync.viewdeos.com/vast/?adid=44F2AEB9BFC881B3', + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'video', + netRevenue: true, + currency: 'USD', + height: 480, + width: 640, + ttl: 3600, + cpm: 0.9 +}]; + +const displayEqResponse = [{ + requestId: '2e41f65424c87c', + creativeId: 342516, + mediaType: 'display', + netRevenue: true, + currency: 'USD', + ad: '', + height: 250, + width: 300, + ttl: 3600, + cpm: 0.9 +}]; + +describe('viewdeosDXBidAdapter', function () { // todo remove only + const adapter = newBidder(spec); + + describe('user syncs as image', function () { + it('should be returned if pixel enabled', function () { + const syncs = spec.getUserSyncs({pixelEnabled: true}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[0]]); + expect(syncs.map(s => s.type)).to.deep.equal(['image']); + }) + }) + + describe('user syncs as iframe', function () { + it('should be returned if iframe enabled', function () { + const syncs = spec.getUserSyncs({iframeEnabled: true}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs.map(s => s.url)).to.deep.equal([SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs[1]]); + expect(syncs.map(s => s.type)).to.deep.equal(['iframe']); + }) + }) + + describe('user syncs with both types', function () { + it('should be returned if pixel and iframe enabled', function () { + const syncs = spec.getUserSyncs({ + iframeEnabled: true, + pixelEnabled: true + }, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs.map(s => s.url)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLs); + expect(syncs.map(s => s.type)).to.deep.equal(SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS.cookieURLSTypes); + }) + }) + + describe('user syncs', function () { + it('should not be returned if pixel not set', function () { + const syncs = spec.getUserSyncs({}, [{body: SERVER_DISPLAY_RESPONSE_WITH_MIXED_SYNCS}]); + + expect(syncs).to.be.empty; + }) + }) + + describe('inherited functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValid', function () { + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(VIDEO_REQUEST)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let bid = Object.assign({}, VIDEO_REQUEST); + delete bid.params; + expect(spec.isBidRequestValid(bid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + let videoBidRequests = [VIDEO_REQUEST]; + let displayBidRequests = [DISPLAY_REQUEST]; + let videoAndDisplayBidRequests = [DISPLAY_REQUEST, VIDEO_REQUEST]; + + const displayRequest = spec.buildRequests(displayBidRequests, {}); + const videoRequest = spec.buildRequests(videoBidRequests, {}); + const videoAndDisplayRequests = spec.buildRequests(videoAndDisplayBidRequests, {}); + + it('sends bid request to ENDPOINT via GET', function () { + expect(videoRequest.method).to.equal('GET'); + expect(displayRequest.method).to.equal('GET'); + expect(videoAndDisplayRequests.method).to.equal('GET'); + }); + + it('sends bid request to correct ENDPOINT', function () { + expect(videoRequest.url).to.equal(ENDPOINT); + expect(displayRequest.url).to.equal(ENDPOINT); + expect(videoAndDisplayRequests.url).to.equal(ENDPOINT); + }); + + it('sends correct video bid parameters', function () { + const bid = Object.assign({}, videoRequest.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'video', + aid: 12345, + sizes: '480x360' + }; + + expect(bid).to.deep.equal(eq); + }); + + it('sends correct display bid parameters', function () { + const bid = Object.assign({}, displayRequest.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'display', + aid: 12345, + sizes: '300x250,300x600' + }; + + expect(bid).to.deep.equal(eq); + }); + + it('sends correct video and display bid parameters', function () { + const bid = Object.assign({}, videoAndDisplayRequests.data); + delete bid.domain; + + const eq = { + callbackId: '84ab500420319d', + ad_type: 'display', + aid: 12345, + sizes: '300x250,300x600', + callbackId2: '84ab500420319d', + ad_type2: 'video', + aid2: 12345, + sizes2: '480x360' + }; + + expect(bid).to.deep.equal(eq); + }); + }); + + describe('interpretResponse', function () { + let serverResponse; + let bidderRequest; + let eqResponse; + + afterEach(function () { + serverResponse = null; + bidderRequest = null; + eqResponse = null; + }); + + it('should get correct video bid response', function () { + serverResponse = SERVER_VIDEO_RESPONSE; + bidderRequest = videoBidderRequest; + eqResponse = videoEqResponse; + + bidServerResponseCheck(); + }); + + it('should get correct display bid response', function () { + serverResponse = SERVER_DISPLAY_RESPONSE; + bidderRequest = displayBidderRequest; + eqResponse = displayEqResponse; + + bidServerResponseCheck(); + }); + + it('should set gdpr data correctly', function () { + const builtRequestData = spec.buildRequests([DISPLAY_REQUEST], displayBidderRequestWithGdpr); + + expect(builtRequestData.data.gdpr).to.be.equal(1); + expect(builtRequestData.data.gdpr_consent).to.be.equal(displayBidderRequestWithGdpr.gdprConsent.consentString); + }); + + function bidServerResponseCheck() { + const result = spec.interpretResponse({body: serverResponse}, {bidderRequest}); + + expect(result).to.deep.equal(eqResponse); + } + + function nobidServerResponseCheck() { + const noBidServerResponse = {bids: []}; + const noBidResult = spec.interpretResponse({body: noBidServerResponse}, {bidderRequest}); + + expect(noBidResult.length).to.equal(0); + } + + it('handles video nobid responses', function () { + bidderRequest = videoBidderRequest; + + nobidServerResponseCheck(); + }); + + it('handles display nobid responses', function () { + bidderRequest = displayBidderRequest; + + nobidServerResponseCheck(); + }); + }); +}); diff --git a/test/spec/modules/vubleBidAdapter_spec.js b/test/spec/modules/vubleBidAdapter_spec.js index b38ad8f8584..b00dbca9b01 100644 --- a/test/spec/modules/vubleBidAdapter_spec.js +++ b/test/spec/modules/vubleBidAdapter_spec.js @@ -36,9 +36,25 @@ describe('VubleAdapter', function () { } }, }; + let bid2 = { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '12345', + floorPrice: 5.00 // optional + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + }; it('should be true', function () { expect(adapter.isBidRequestValid(bid)).to.be.true; + expect(adapter.isBidRequestValid(bid2)).to.be.true; }); it('should be false because the sizes are missing or in the wrong format', function () { @@ -85,12 +101,6 @@ describe('VubleAdapter', function () { }); describe('Check buildRequests method', function () { - let sandbox; - before(function () { - sandbox = sinon.sandbox.create(); - sandbox.stub(utils, 'getTopWindowUrl').returns('http://www.vuble.tv/'); - }); - // Bids to be formatted let bid1 = { bidder: 'vuble', @@ -115,7 +125,7 @@ describe('VubleAdapter', function () { env: 'com', pubId: '8', zoneId: '2468', - referrer: 'http://www.vuble.fr/' + referrer: 'https://www.vuble.fr/' }, sizes: '640x360', mediaTypes: { @@ -126,11 +136,27 @@ describe('VubleAdapter', function () { bidId: 'efgh', adUnitCode: 'code' }; + let bid3 = { + bidder: 'vuble', + params: { + env: 'net', + pubId: '3', + zoneId: '3579', + }, + mediaTypes: { + video: { + context: 'instream', + playerSize: [640, 360] + } + }, + bidId: 'ijkl', + adUnitCode: '' + }; // Formatted requets let request1 = { method: 'POST', - url: '//player.mediabong.net/prebid/request', + url: 'https://player.mediabong.net/prebid/request', data: { width: '640', height: '360', @@ -138,7 +164,7 @@ describe('VubleAdapter', function () { zone_id: '12345', context: 'instream', floor_price: 5.5, - url: 'http://www.vuble.tv/', + url: '', env: 'net', bid_id: 'abdc', adUnitCode: '' @@ -146,7 +172,7 @@ describe('VubleAdapter', function () { }; let request2 = { method: 'POST', - url: '//player.mediabong.com/prebid/request', + url: 'https://player.mediabong.com/prebid/request', data: { width: '640', height: '360', @@ -154,20 +180,44 @@ describe('VubleAdapter', function () { zone_id: '2468', context: 'outstream', floor_price: 0, - url: 'http://www.vuble.fr/', + url: 'https://www.vuble.fr/', env: 'com', bid_id: 'efgh', adUnitCode: 'code' } }; + let request3 = { + method: 'POST', + url: 'https://player.mediabong.net/prebid/request', + data: { + width: '640', + height: '360', + pub_id: '3', + zone_id: '3579', + context: 'instream', + floor_price: 0, + url: 'https://www.vuble.tv/', + env: 'net', + bid_id: 'ijkl', + adUnitCode: '' + } + }; + let bidderRequest = { + refererInfo: { + referer: 'https://www.vuble.tv/', + reachedTop: true, + numIframes: 1, + stack: [ + 'http://example.com/page.html', + 'http://example.com/iframe1.html', + 'http://example.com/iframe2.html' + ] + } + }; it('must return the right formatted requests', function () { - let rs = adapter.buildRequests([bid1, bid2]); expect(adapter.buildRequests([bid1, bid2])).to.deep.equal([request1, request2]); - }); - - after(function () { - sandbox.restore(); + expect(adapter.buildRequests([bid3], bidderRequest)).to.deep.equal([request3]); }); }); @@ -196,7 +246,7 @@ describe('VubleAdapter', function () { adUnitCode: 'code' }, method: 'POST', - url: '//player.mediabong.net/prebid/request' + url: 'https://player.mediabong.net/prebid/request' }; // Formatted reponse let result = { @@ -262,7 +312,7 @@ describe('VubleAdapter', function () { // Formatted reponse let result = { type: 'iframe', - url: 'http://player.mediabong.net/csifr?1234' + url: 'https://player.mediabong.net/csifr?1234' }; it('should return an empty array', function () { @@ -276,7 +326,7 @@ describe('VubleAdapter', function () { }); it('should be equal to the expected result', function () { - response.body.iframeSync = 'http://player.mediabong.net/csifr?1234'; + response.body.iframeSync = 'https://player.mediabong.net/csifr?1234'; expect(adapter.getUserSyncs(syncOptions, [response])).to.deep.equal([result]); }) }); @@ -296,7 +346,7 @@ describe('VubleAdapter', function () { adUnitCode: 'code' }, method: 'POST', - url: '//player.mediabong.net/prebid/request' + url: 'https://player.mediabong.net/prebid/request' }; // Server's response let response = { diff --git a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js index a297403b2e0..b7b4cd82b07 100644 --- a/test/spec/modules/yieldoneAnalyticsAdapter_spec.js +++ b/test/spec/modules/yieldoneAnalyticsAdapter_spec.js @@ -97,6 +97,15 @@ describe('Yieldone Prebid Analytic', function () { mediaTypes: {banner: {sizes: [[300, 250], [336, 280]]}}, params: {param_1: '111', param_2: '222'}, sizes: [[300, 250], [336, 280]] + }, + { + adUnitCode: '0000', + auctionId: auctionId, + bidId: '14151', + bidder: 'biddertest_3', + mediaTypes: {banner: {sizes: [[300, 250], [336, 280]]}}, + params: {param_1: '333', param_2: '222'}, + sizes: [[300, 250], [336, 280]] } ] } @@ -140,6 +149,11 @@ describe('Yieldone Prebid Analytic', function () { bidId: '12131', auctionId: auctionId, bidder: 'biddertest_3' + }, + { + bidId: '14151', + auctionId: auctionId, + bidder: 'biddertest_3' } ]; @@ -234,7 +248,7 @@ describe('Yieldone Prebid Analytic', function () { events.emit(constants.EVENTS.BID_RESPONSE, responses[1]); events.emit(constants.EVENTS.BID_RESPONSE, responses[2]); - events.emit(constants.EVENTS.BID_TIMEOUT, [responses[3]]); + events.emit(constants.EVENTS.BID_TIMEOUT, [responses[3], responses[4]]); events.emit(constants.EVENTS.AUCTION_END, {auctionId: auctionId}); diff --git a/test/spec/modules/zedoBidAdapter_spec.js b/test/spec/modules/zedoBidAdapter_spec.js index 76301ba9018..f6702e6f459 100644 --- a/test/spec/modules/zedoBidAdapter_spec.js +++ b/test/spec/modules/zedoBidAdapter_spec.js @@ -1,354 +1,354 @@ -import { expect } from 'chai'; -import { spec } from 'modules/zedoBidAdapter'; - -describe('The ZEDO bidding adapter', function () { - describe('isBidRequestValid', function () { - it('should return false when given an invalid bid', function () { - const bid = { - bidder: 'zedo', - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(false); - }); - - it('should return true when given a channelcode bid', function () { - const bid = { - bidder: 'zedo', - params: { - channelCode: 20000000, - dimId: 9 - }, - }; - const isValid = spec.isBidRequestValid(bid); - expect(isValid).to.equal(true); - }); - }); - - describe('buildRequests', function () { - const bidderRequest = { - timeout: 3000, - }; - - it('should properly build a channelCode request for dim Id with type not defined', function () { - const bidRequests = [ - { - bidder: 'zedo', - adUnitCode: 'p12345', - transactionId: '12345667', - sizes: [[300, 200]], - params: { - channelCode: 20000000, - dimId: 10, - pubId: 1 - }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^\/\/saxp.zedo.com\/asw\/fmh.json/); - expect(request.method).to.equal('GET'); - const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":1,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); - }); - - it('should properly build a channelCode request for video with type defined', function () { - const bidRequests = [ - { - bidder: 'zedo', - adUnitCode: 'p12345', - transactionId: '12345667', - sizes: [640, 480], - mediaTypes: { - video: { - context: 'instream', - }, - }, - params: { - channelCode: 20000000, - dimId: 85 - }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^\/\/saxp.zedo.com\/asw\/fmh.json/); - expect(request.method).to.equal('GET'); - const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); - }); - - describe('buildGDPRRequests', function () { - let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { - timeout: 3000, - gdprConsent: { - 'consentString': consentString, - 'gdprApplies': true - } - }; - - it('should properly build request with gdpr consent', function () { - const bidRequests = [ - { - bidder: 'zedo', - adUnitCode: 'p12345', - transactionId: '12345667', - sizes: [[300, 200]], - params: { - channelCode: 20000000, - dimId: 10 - }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.method).to.equal('GET'); - const zedoRequest = request.data; - expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":0,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}],"gdpr":1,"gdpr_consent":"BOJ8RZsOJ8RZsABAB8AAAAAZ+A=="}'); - }); - }); - }); - describe('interpretResponse', function () { - it('should return an empty array when there is bid response', function () { - const response = {}; - const request = { bidRequests: [] }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - - it('should properly parse a bid response with no valid creative', function () { - const response = { - body: { - ad: [ - { - 'slotId': 'ad1d762', - 'network': '2000', - 'creatives': [ - { - 'adId': '12345', - 'height': '600', - 'width': '160', - 'isFoc': true, - 'creativeDetails': { - 'type': 'StdBanner', - 'adContent': { - 'focImage': { - 'url': 'https://c13.zedo.com/OzoDB/0/0/0/blank.gif', - 'target': '_blank', - } - } - }, - 'cpm': '0' - } - ] - } - ] - } - }; - const request = { - bidRequests: [{ - bidder: 'zedo', - adUnitCode: 'p12345', - bidId: 'test-bidId', - params: { - channelCode: 2000000, - dimId: 9 - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - - it('should properly parse a bid response with valid display creative', function () { - const response = { - body: { - ad: [ - { - 'slotId': 'ad1d762', - 'network': '2000', - 'creatives': [ - { - 'adId': '12345', - 'height': '600', - 'width': '160', - 'isFoc': true, - 'creativeDetails': { - 'type': 'StdBanner', - 'adContent': '' - }, - 'cpm': '1200000' - } - ] - } - ] - } - }; - const request = { - bidRequests: [{ - bidder: 'zedo', - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - channelCode: 2000000, - dimId: 9 - }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('ad1d762'); - expect(bids[0].cpm).to.equal(0.72); - expect(bids[0].width).to.equal('160'); - expect(bids[0].height).to.equal('600'); - }); - - it('should properly parse a bid response with valid video creative', function () { - const response = { - body: { - ad: [ - { - 'slotId': 'ad1d762', - 'network': '2000', - 'creatives': [ - { - 'adId': '12345', - 'height': '480', - 'width': '640', - 'isFoc': true, - 'creativeDetails': { - 'type': 'VAST', - 'adContent': '' - }, - 'cpm': '1200000' - } - ] - } - ] - } - }; - const request = { - bidRequests: [{ - bidder: 'zedo', - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - channelCode: 2000000, - dimId: 85 - }, - }] - }; - - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('ad1d762'); - expect(bids[0].cpm).to.equal(0.78); - expect(bids[0].width).to.equal('640'); - expect(bids[0].height).to.equal('480'); - expect(bids[0].adType).to.equal('VAST'); - expect(bids[0].vastXml).to.not.equal(''); - expect(bids[0].ad).to.be.an('undefined'); - expect(bids[0].renderer).not.to.be.an('undefined'); - }); - }); - - describe('user sync', function () { - it('should register the iframe sync url', function () { - let syncs = spec.getUserSyncs({ - iframeEnabled: true - }); - expect(syncs).to.not.be.an('undefined'); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - }); - - it('should pass gdpr params', function () { - let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { - gdprApplies: false, consentString: 'test' - }); - expect(syncs).to.not.be.an('undefined'); - expect(syncs).to.have.lengthOf(1); - expect(syncs[0].type).to.equal('iframe'); - expect(syncs[0].url).to.contains('gdpr=0'); - }); - }); - - describe('bid events', function () { - it('should trigger a win pixel', function () { - const bid = { - 'bidderCode': 'zedo', - 'width': '300', - 'height': '250', - 'statusMessage': 'Bid available', - 'adId': '148018fe5e', - 'cpm': 0.5, - 'ad': 'dummy data', - 'ad_id': '12345', - 'sizeId': '15', - 'adResponse': - { - 'creatives': [ - { - 'adId': '12345', - 'height': '480', - 'width': '640', - 'isFoc': true, - 'creativeDetails': { - 'type': 'VAST', - 'adContent': '' - }, - 'seeder': { - 'network': 1234, - 'servedChan': 1234567, - }, - 'cpm': '1200000', - 'servedChan': 1234, - }] - }, - 'params': [{ - 'channelCode': '123456', - 'dimId': '85' - }], - 'requestTimestamp': 1540401686, - 'responseTimestamp': 1540401687, - 'timeToRespond': 6253, - 'pbLg': '0.50', - 'pbMg': '0.50', - 'pbHg': '0.53', - 'adUnitCode': '/123456/header-bid-tag-0', - 'bidder': 'zedo', - 'size': '300x250', - 'adserverTargeting': { - 'hb_bidder': 'zedo', - 'hb_adid': '148018fe5e', - 'hb_pb': '10.00', - } - }; - spec.onBidWon(bid); - spec.onTimeout(bid); - }); - it('should trigger a timeout pixel', function () { - const bid = { - 'bidderCode': 'zedo', - 'width': '300', - 'height': '250', - 'statusMessage': 'Bid available', - 'adId': '148018fe5e', - 'cpm': 0.5, - 'ad': 'dummy data', - 'ad_id': '12345', - 'sizeId': '15', - 'params': [{ - 'channelCode': '123456', - 'dimId': '85' - }], - 'timeout': 1, - 'requestTimestamp': 1540401686, - 'responseTimestamp': 1540401687, - 'timeToRespond': 6253, - 'adUnitCode': '/123456/header-bid-tag-0', - 'bidder': 'zedo', - 'size': '300x250', - }; - spec.onBidWon(bid); - spec.onTimeout(bid); - }); - }); -}); +import { expect } from 'chai'; +import { spec } from 'modules/zedoBidAdapter'; + +describe('The ZEDO bidding adapter', function () { + describe('isBidRequestValid', function () { + it('should return false when given an invalid bid', function () { + const bid = { + bidder: 'zedo', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when given a channelcode bid', function () { + const bid = { + bidder: 'zedo', + params: { + channelCode: 20000000, + dimId: 9 + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + const bidderRequest = { + timeout: 3000, + }; + + it('should properly build a channelCode request for dim Id with type not defined', function () { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [[300, 200]], + params: { + channelCode: 20000000, + dimId: 10, + pubId: 1 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/saxp.zedo.com\/asw\/fmh.json/); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":1,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}]}'); + }); + + it('should properly build a channelCode request for video with type defined', function () { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [640, 480], + mediaTypes: { + video: { + context: 'instream', + }, + }, + params: { + channelCode: 20000000, + dimId: 85 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.url).to.match(/^https:\/\/saxp.zedo.com\/asw\/fmh.json/); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":0,"width":640,"height":480,"dimension":85,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"Inarticle"}]}]}'); + }); + + describe('buildGDPRRequests', function () { + let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + const bidderRequest = { + timeout: 3000, + gdprConsent: { + 'consentString': consentString, + 'gdprApplies': true + } + }; + + it('should properly build request with gdpr consent', function () { + const bidRequests = [ + { + bidder: 'zedo', + adUnitCode: 'p12345', + transactionId: '12345667', + sizes: [[300, 200]], + params: { + channelCode: 20000000, + dimId: 10 + }, + }, + ]; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.method).to.equal('GET'); + const zedoRequest = request.data; + expect(zedoRequest).to.equal('g={"placements":[{"network":20,"channel":0,"publisher":0,"width":300,"height":200,"dimension":10,"version":"$prebid.version$","keyword":"","transactionId":"12345667","renderers":[{"name":"display"}]}],"gdpr":1,"gdpr_consent":"BOJ8RZsOJ8RZsABAB8AAAAAZ+A=="}'); + }); + }); + }); + describe('interpretResponse', function () { + it('should return an empty array when there is bid response', function () { + const response = {}; + const request = { bidRequests: [] }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with no valid creative', function () { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '600', + 'width': '160', + 'isFoc': true, + 'creativeDetails': { + 'type': 'StdBanner', + 'adContent': { + 'focImage': { + 'url': 'https://c13.zedo.com/OzoDB/0/0/0/blank.gif', + 'target': '_blank', + } + } + }, + 'cpm': '0' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'p12345', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 9 + } + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a bid response with valid display creative', function () { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '600', + 'width': '160', + 'isFoc': true, + 'creativeDetails': { + 'type': 'StdBanner', + 'adContent': '' + }, + 'cpm': '1200000' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 9 + }, + }] + }; + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('ad1d762'); + expect(bids[0].cpm).to.equal(0.72); + expect(bids[0].width).to.equal('160'); + expect(bids[0].height).to.equal('600'); + }); + + it('should properly parse a bid response with valid video creative', function () { + const response = { + body: { + ad: [ + { + 'slotId': 'ad1d762', + 'network': '2000', + 'creatives': [ + { + 'adId': '12345', + 'height': '480', + 'width': '640', + 'isFoc': true, + 'creativeDetails': { + 'type': 'VAST', + 'adContent': '' + }, + 'cpm': '1200000' + } + ] + } + ] + } + }; + const request = { + bidRequests: [{ + bidder: 'zedo', + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + params: { + channelCode: 2000000, + dimId: 85 + }, + }] + }; + + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].requestId).to.equal('ad1d762'); + expect(bids[0].cpm).to.equal(0.78); + expect(bids[0].width).to.equal('640'); + expect(bids[0].height).to.equal('480'); + expect(bids[0].adType).to.equal('VAST'); + expect(bids[0].vastXml).to.not.equal(''); + expect(bids[0].ad).to.be.an('undefined'); + expect(bids[0].renderer).not.to.be.an('undefined'); + }); + }); + + describe('user sync', function () { + it('should register the iframe sync url', function () { + let syncs = spec.getUserSyncs({ + iframeEnabled: true + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + }); + + it('should pass gdpr params', function () { + let syncs = spec.getUserSyncs({ iframeEnabled: true }, {}, { + gdprApplies: false, consentString: 'test' + }); + expect(syncs).to.not.be.an('undefined'); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.contains('gdpr=0'); + }); + }); + + describe('bid events', function () { + it('should trigger a win pixel', function () { + const bid = { + 'bidderCode': 'zedo', + 'width': '300', + 'height': '250', + 'statusMessage': 'Bid available', + 'adId': '148018fe5e', + 'cpm': 0.5, + 'ad': 'dummy data', + 'ad_id': '12345', + 'sizeId': '15', + 'adResponse': + { + 'creatives': [ + { + 'adId': '12345', + 'height': '480', + 'width': '640', + 'isFoc': true, + 'creativeDetails': { + 'type': 'VAST', + 'adContent': '' + }, + 'seeder': { + 'network': 1234, + 'servedChan': 1234567, + }, + 'cpm': '1200000', + 'servedChan': 1234, + }] + }, + 'params': [{ + 'channelCode': '123456', + 'dimId': '85' + }], + 'requestTimestamp': 1540401686, + 'responseTimestamp': 1540401687, + 'timeToRespond': 6253, + 'pbLg': '0.50', + 'pbMg': '0.50', + 'pbHg': '0.53', + 'adUnitCode': '/123456/header-bid-tag-0', + 'bidder': 'zedo', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'zedo', + 'hb_adid': '148018fe5e', + 'hb_pb': '10.00', + } + }; + spec.onBidWon(bid); + spec.onTimeout(bid); + }); + it('should trigger a timeout pixel', function () { + const bid = { + 'bidderCode': 'zedo', + 'width': '300', + 'height': '250', + 'statusMessage': 'Bid available', + 'adId': '148018fe5e', + 'cpm': 0.5, + 'ad': 'dummy data', + 'ad_id': '12345', + 'sizeId': '15', + 'params': [{ + 'channelCode': '123456', + 'dimId': '85' + }], + 'timeout': 1, + 'requestTimestamp': 1540401686, + 'responseTimestamp': 1540401687, + 'timeToRespond': 6253, + 'adUnitCode': '/123456/header-bid-tag-0', + 'bidder': 'zedo', + 'size': '300x250', + }; + spec.onBidWon(bid); + spec.onTimeout(bid); + }); + }); +}); diff --git a/test/spec/unit/adUnits_spec.js b/test/spec/unit/adUnits_spec.js index ff77315ba4a..fb666feb9b8 100644 --- a/test/spec/unit/adUnits_spec.js +++ b/test/spec/unit/adUnits_spec.js @@ -4,20 +4,46 @@ import { adunitCounter } from 'src/adUnits'; describe('Adunit Counter', function () { const ADUNIT_ID_1 = 'test1'; const ADUNIT_ID_2 = 'test2'; + const BIDDER_ID_1 = 'bidder1'; + const BIDDER_ID_2 = 'bidder2'; - it('increments and checks counter of adunit 1', function () { - adunitCounter.incrementCounter(ADUNIT_ID_1); - expect(adunitCounter.getCounter(ADUNIT_ID_1)).to.be.equal(1); + it('increments and checks requests counter of adunit 1', function () { + adunitCounter.incrementRequestsCounter(ADUNIT_ID_1); + expect(adunitCounter.getRequestsCounter(ADUNIT_ID_1)).to.be.equal(1); }); - it('checks counter of adunit 2', function () { - expect(adunitCounter.getCounter(ADUNIT_ID_2)).to.be.equal(0); + it('checks requests counter of adunit 2', function () { + expect(adunitCounter.getRequestsCounter(ADUNIT_ID_2)).to.be.equal(0); }); - it('increments and checks counter of adunit 1', function () { - adunitCounter.incrementCounter(ADUNIT_ID_1); - expect(adunitCounter.getCounter(ADUNIT_ID_1)).to.be.equal(2); + it('increments and checks requests counter of adunit 1', function () { + adunitCounter.incrementRequestsCounter(ADUNIT_ID_1); + expect(adunitCounter.getRequestsCounter(ADUNIT_ID_1)).to.be.equal(2); }); - it('increments and checks counter of adunit 2', function () { - adunitCounter.incrementCounter(ADUNIT_ID_2); - expect(adunitCounter.getCounter(ADUNIT_ID_2)).to.be.equal(1); + it('increments and checks requests counter of adunit 2', function () { + adunitCounter.incrementRequestsCounter(ADUNIT_ID_2); + expect(adunitCounter.getRequestsCounter(ADUNIT_ID_2)).to.be.equal(1); + }); + it('increments and checks requests counter of adunit 1 for bidder 1', function () { + adunitCounter.incrementBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1); + expect(adunitCounter.getBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1)).to.be.equal(1); + }); + it('increments and checks requests counter of adunit 1 for bidder 2', function () { + adunitCounter.incrementBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_2); + expect(adunitCounter.getBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_2)).to.be.equal(1); + }); + it('increments and checks requests counter of adunit 1 for bidder 1', function () { + adunitCounter.incrementBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1); + expect(adunitCounter.getBidderRequestsCounter(ADUNIT_ID_1, BIDDER_ID_1)).to.be.equal(2); + }); + it('increments and checks wins counter of adunit 1 for bidder 1', function () { + adunitCounter.incrementBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_1); + expect(adunitCounter.getBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_1)).to.be.equal(1); + }); + it('increments and checks wins counter of adunit 2 for bidder 1', function () { + adunitCounter.incrementBidderWinsCounter(ADUNIT_ID_2, BIDDER_ID_1); + expect(adunitCounter.getBidderWinsCounter(ADUNIT_ID_2, BIDDER_ID_1)).to.be.equal(1); + }); + it('increments and checks wins counter of adunit 1 for bidder 2', function () { + adunitCounter.incrementBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_2); + expect(adunitCounter.getBidderWinsCounter(ADUNIT_ID_1, BIDDER_ID_2)).to.be.equal(1); }); }); diff --git a/test/spec/unit/core/adapterManager_spec.js b/test/spec/unit/core/adapterManager_spec.js index 1933e4a736d..bd8f880378b 100644 --- a/test/spec/unit/core/adapterManager_spec.js +++ b/test/spec/unit/core/adapterManager_spec.js @@ -3,7 +3,8 @@ import adapterManager, { gdprDataHandler } from 'src/adapterManager'; import { getAdUnits, getServerTestingConfig, - getServerTestingsAds + getServerTestingsAds, + getBidRequests } from 'test/fixtures/fixtures'; import CONSTANTS from 'src/constants.json'; import * as utils from 'src/utils'; @@ -135,6 +136,112 @@ describe('adapterManager tests', function () { sinon.assert.calledOnce(appnexusAdapterMock.callBids); events.off(CONSTANTS.EVENTS.BID_REQUESTED, count); }); + + it('should give bidders access to bidder-specific config', function(done) { + let mockBidders = ['rubicon', 'appnexus', 'pubmatic']; + let bidderRequest = getBidRequests().filter(bidRequest => includes(mockBidders, bidRequest.bidderCode)); + let adUnits = getAdUnits(); + + let bidders = {}; + let results = {}; + let cbCount = 0; + + function mock(bidder) { + bidders[bidder] = adapterManager.bidderRegistry[bidder]; + adapterManager.bidderRegistry[bidder] = { + callBids: function(bidRequest, addBidResponse, done, ajax, timeout, configCallback) { + let myResults = results[bidRequest.bidderCode] = []; + myResults.push(config.getConfig('buildRequests')); + myResults.push(config.getConfig('test1')); + myResults.push(config.getConfig('test2')); + // emulate ajax callback that would register bids + setTimeout(configCallback(() => { + myResults.push(config.getConfig('interpretResponse')); + myResults.push(config.getConfig('afterInterpretResponse')); + if (++cbCount === Object.keys(bidders).length) { + assertions(); + } + }), 1); + done(); + } + } + } + + mockBidders.forEach(bidder => { + mock(bidder); + }); + + config.setConfig({ + buildRequests: { + data: 1 + }, + test1: { speedy: true }, + interpretResponse: 'baseInterpret', + afterInterpretResponse: 'anotherBaseInterpret' + }); + config.setBidderConfig({ + bidders: [ 'appnexus' ], + config: { + buildRequests: { + test: 2 + }, + test1: { fun: { safe: true, cheap: false } }, + interpretResponse: 'appnexusInterpret' + } + }); + config.setBidderConfig({ + bidders: [ 'rubicon' ], + config: { + buildRequests: 'rubiconBuild', + interpretResponse: null + } + }); + config.setBidderConfig({ + bidders: [ 'appnexus', 'rubicon' ], + config: { + test2: { amazing: true } + } + }); + + adapterManager.callBids(adUnits, bidderRequest, () => {}, () => {}); + + function assertions() { + expect(results).to.deep.equal({ + 'appnexus': [ + { + data: 1, + test: 2 + }, + { fun: { safe: true, cheap: false }, speedy: true }, + { amazing: true }, + 'appnexusInterpret', + 'anotherBaseInterpret' + ], + 'pubmatic': [ + { + data: 1 + }, + { speedy: true }, + undefined, + 'baseInterpret', + 'anotherBaseInterpret' + ], + 'rubicon': [ + 'rubiconBuild', + { speedy: true }, + { amazing: true }, + null, + 'anotherBaseInterpret' + ] + }); + + // restore bid adapters + Object.keys(bidders).forEach(bidder => { + adapterManager.bidderRegistry[bidder] = bidders[bidder]; + }); + done(); + } + }); }); describe('callTimedOutBidders', function () { diff --git a/test/spec/unit/core/bidderFactory_spec.js b/test/spec/unit/core/bidderFactory_spec.js index fd54d2911e1..e8ddf52c128 100644 --- a/test/spec/unit/core/bidderFactory_spec.js +++ b/test/spec/unit/core/bidderFactory_spec.js @@ -33,6 +33,8 @@ function onTimelyResponseStub() { } +let wrappedCallback = config.callbackWithBidder(CODE); + describe('bidders created by newBidder', function () { let spec; let bidder; @@ -71,7 +73,7 @@ describe('bidders created by newBidder', function () { spec.getUserSyncs.returns([]); bidder.callBids({}); - bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids({ bids: 'nothing useful' }, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.called).to.equal(false); @@ -85,7 +87,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.returns(true); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -99,7 +101,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.returns(false); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -113,7 +115,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.onSecondCall().returns(false); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.called).to.equal(false); expect(spec.isBidRequestValid.calledTwice).to.equal(true); @@ -127,7 +129,7 @@ describe('bidders created by newBidder', function () { spec.isBidRequestValid.returns(true); spec.buildRequests.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.called).to.equal(false); }); @@ -143,7 +145,7 @@ describe('bidders created by newBidder', function () { data: data }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(url); @@ -168,7 +170,7 @@ describe('bidders created by newBidder', function () { options: options }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(url); @@ -191,7 +193,7 @@ describe('bidders created by newBidder', function () { data: data }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); @@ -215,7 +217,7 @@ describe('bidders created by newBidder', function () { options: opt }); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledOnce).to.equal(true); expect(ajaxStub.firstCall.args[0]).to.equal(`${url}?arg=2&`); @@ -244,7 +246,7 @@ describe('bidders created by newBidder', function () { } ]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(ajaxStub.calledTwice).to.equal(true); }); @@ -257,7 +259,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns([]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.callCount).to.equal(0); }); @@ -297,7 +299,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(spec.interpretResponse.calledOnce).to.equal(true); const response = spec.interpretResponse.firstCall.args[0] @@ -329,7 +331,7 @@ describe('bidders created by newBidder', function () { ]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(spec.interpretResponse.calledTwice).to.equal(true); expect(doneStub.calledOnce).to.equal(true); @@ -360,10 +362,14 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); + let bidObject = addBidResponseStub.firstCall.args[1]; + // checking the fields added by our code + expect(bidObject.originalCpm).to.equal(bid.cpm); + expect(bidObject.originalCurrency).to.equal(bid.currency); expect(doneStub.calledOnce).to.equal(true); expect(logErrorSpy.callCount).to.equal(0); }); @@ -379,7 +385,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(spec.getUserSyncs.calledOnce).to.equal(true); expect(spec.getUserSyncs.firstCall.args[1].length).to.equal(1); @@ -398,7 +404,7 @@ describe('bidders created by newBidder', function () { url: 'usersync.com' }]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(userSyncStub.called).to.equal(true); expect(userSyncStub.firstCall.args[0]).to.equal('iframe'); @@ -427,7 +433,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(logErrorSpy.calledOnce).to.equal(true); }); @@ -457,7 +463,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns(bid); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(logErrorSpy.calledOnce).to.equal(true); }); @@ -489,7 +495,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(spec.interpretResponse.called).to.equal(false); expect(doneStub.calledOnce).to.equal(true); @@ -507,7 +513,7 @@ describe('bidders created by newBidder', function () { spec.interpretResponse.returns([]); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.callCount).to.equal(0); expect(doneStub.calledOnce).to.equal(true); @@ -524,7 +530,7 @@ describe('bidders created by newBidder', function () { }); spec.getUserSyncs.returns([]); - bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(MOCK_BIDS_REQUEST, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(spec.getUserSyncs.calledOnce).to.equal(true); expect(spec.getUserSyncs.firstCall.args[1]).to.deep.equal([]); @@ -670,7 +676,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -707,7 +713,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(false); expect(logErrorSpy.callCount).to.equal(1); @@ -740,7 +746,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); @@ -772,7 +778,7 @@ describe('validate bid response: ', function () { const bidder = newBidder(spec); spec.interpretResponse.returns(bids1); - bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub); + bidder.callBids(bidRequest, addBidResponseStub, doneStub, ajaxStub, onTimelyResponseStub, wrappedCallback); expect(addBidResponseStub.calledOnce).to.equal(true); expect(addBidResponseStub.firstCall.args[0]).to.equal('mock/placement'); diff --git a/wdio.conf.js b/wdio.conf.js index dd94e82cf90..22765e34346 100644 --- a/wdio.conf.js +++ b/wdio.conf.js @@ -35,8 +35,9 @@ function getCapabilities() { } exports.config = { + // TODO: below change is only for testing and is to be removed. specs: [ - './test/spec/e2e/**/*.spec.js' + './test/spec/e2e/banner/*.spec.js' ], services: ['browserstack'], user: process.env.BROWSERSTACK_USERNAME,