diff --git a/modules/engageyaBidAdapter.js b/modules/engageyaBidAdapter.js
new file mode 100644
index 00000000000..27d1bb15af8
--- /dev/null
+++ b/modules/engageyaBidAdapter.js
@@ -0,0 +1,157 @@
+import {
+ BANNER,
+ NATIVE
+} from '../src/mediaTypes.js';
+import { createTrackPixelHtml } from '../src/utils.js';
+
+const {
+ registerBidder
+} = require('../src/adapters/bidderFactory.js');
+const BIDDER_CODE = 'engageya';
+const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json';
+const ENDPOINT_METHOD = 'GET';
+
+function getPageUrl() {
+ var pUrl = window.location.href;
+ if (isInIframe()) {
+ pUrl = document.referrer ? document.referrer : pUrl;
+ }
+ pUrl = encodeURIComponent(pUrl);
+ return pUrl;
+}
+
+function isInIframe() {
+ try {
+ var isInIframe = (window.self !== window.top);
+ } catch (e) {
+ isInIframe = true;
+ }
+ return isInIframe;
+}
+
+function getImageSrc(rec) {
+ return rec.thumbnail_path.indexOf('http') === -1 ? 'https:' + rec.thumbnail_path : rec.thumbnail_path;
+}
+
+function getImpressionTrackers(rec) {
+ if (!rec.trackers) {
+ return [];
+ }
+ const impressionTrackers = rec.trackers.impressionPixels || [];
+ const viewTrackers = rec.trackers.viewPixels || [];
+ return [...impressionTrackers, ...viewTrackers];
+}
+
+function parseNativeResponse(rec, response) {
+ return {
+ title: rec.title,
+ body: '',
+ image: {
+ url: getImageSrc(rec),
+ width: response.imageWidth,
+ height: response.imageHeight
+ },
+ privacyLink: '',
+ clickUrl: rec.clickUrl,
+ displayUrl: rec.url,
+ cta: '',
+ sponsoredBy: rec.displayName,
+ impressionTrackers: getImpressionTrackers(rec),
+ };
+}
+
+function parseBannerResponse(rec, response) {
+ if (rec.tag) {
+ return rec.tag;
+ }
+ let style;
+ try {
+ let additionalData = JSON.parse(response.widget.additionalData);
+ const css = additionalData.css || '';
+ style = css ? `` : '';
+ } catch (e) {
+ style = '';
+ }
+ const title = rec.title && rec.title.trim() ? `
${rec.title}
` : '';
+ const displayName = rec.displayName && title ? `${rec.displayName}
` : '';
+ const trackers = getImpressionTrackers(rec)
+ .map(createTrackPixelHtml)
+ .join('');
+ return `${style}`;
+}
+
+export const spec = {
+ code: BIDDER_CODE,
+ supportedMediaTypes: [BANNER, NATIVE],
+ isBidRequestValid: function (bid) {
+ return bid && bid.params && bid.params.hasOwnProperty('widgetId') && bid.params.hasOwnProperty('websiteId') && !isNaN(bid.params.widgetId) && !isNaN(bid.params.websiteId);
+ },
+
+ buildRequests: function (validBidRequests, bidderRequest) {
+ var bidRequests = [];
+ if (validBidRequests && validBidRequests.length > 0) {
+ validBidRequests.forEach(function (bidRequest) {
+ if (bidRequest.params) {
+ var mediaType = bidRequest.hasOwnProperty('nativeParams') ? 1 : 2;
+ var imageWidth = -1;
+ var imageHeight = -1;
+ if (bidRequest.sizes && bidRequest.sizes.length > 0) {
+ imageWidth = bidRequest.sizes[0][0];
+ imageHeight = bidRequest.sizes[0][1];
+ } else if (bidRequest.nativeParams && bidRequest.nativeParams.image && bidRequest.nativeParams.image.sizes) {
+ imageWidth = bidRequest.nativeParams.image.sizes[0];
+ imageHeight = bidRequest.nativeParams.image.sizes[1];
+ }
+
+ var widgetId = bidRequest.params.widgetId;
+ var websiteId = bidRequest.params.websiteId;
+ var pageUrl = (bidRequest.params.pageUrl && bidRequest.params.pageUrl != '[PAGE_URL]') ? bidRequest.params.pageUrl : '';
+ if (!pageUrl) {
+ pageUrl = (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.referer) ? bidderRequest.refererInfo.referer : getPageUrl();
+ }
+ var bidId = bidRequest.bidId;
+ var finalUrl = ENDPOINT_URL + '?pubid=0&webid=' + websiteId + '&wid=' + widgetId + '&url=' + pageUrl + '&ireqid=' + bidId + '&pbtpid=' + mediaType + '&imw=' + imageWidth + '&imh=' + imageHeight;
+ if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprApplies && bidderRequest.consentString) {
+ finalUrl += '&is_gdpr=1&gdpr_consent=' + bidderRequest.consentString;
+ }
+ bidRequests.push({
+ url: finalUrl,
+ method: ENDPOINT_METHOD,
+ data: ''
+ });
+ }
+ });
+ }
+
+ return bidRequests;
+ },
+
+ interpretResponse: function (serverResponse, bidRequest) {
+ if (!serverResponse.body || !serverResponse.body.recs || !serverResponse.body.recs.length) {
+ return [];
+ }
+ var response = serverResponse.body;
+ var isNative = response.pbtypeId == 1;
+ return response.recs.map(rec => {
+ let bid = {
+ requestId: response.ireqId,
+ cpm: rec.ecpm,
+ width: response.imageWidth,
+ height: response.imageHeight,
+ creativeId: rec.postId,
+ currency: 'USD',
+ netRevenue: false,
+ ttl: 360,
+ meta: { advertiserDomains: rec.domain ? [rec.domain] : [] },
+ }
+ if (isNative) {
+ bid.native = parseNativeResponse(rec, response);
+ } else {
+ bid.ad = parseBannerResponse(rec, response);
+ }
+ return bid;
+ });
+ }
+};
+
+registerBidder(spec);
diff --git a/test/spec/modules/engageyaBidAdapter_spec.js b/test/spec/modules/engageyaBidAdapter_spec.js
new file mode 100644
index 00000000000..ae22948994b
--- /dev/null
+++ b/test/spec/modules/engageyaBidAdapter_spec.js
@@ -0,0 +1,286 @@
+import { expect } from 'chai';
+import { spec } from 'modules/engageyaBidAdapter.js';
+import * as utils from 'src/utils.js';
+
+const ENDPOINT_URL = 'https://recs.engageya.com/rec-api/getrecs.json';
+
+export const _getUrlVars = function (url) {
+ var hash;
+ var myJson = {};
+ var hashes = url.slice(url.indexOf('?') + 1).split('&');
+ for (var i = 0; i < hashes.length; i++) {
+ hash = hashes[i].split('=');
+ myJson[hash[0]] = hash[1];
+ }
+ return myJson;
+}
+
+describe('engageya adapter', function () {
+ let bidRequests;
+ let nativeBidRequests;
+
+ beforeEach(function () {
+ bidRequests = [
+ {
+ bidder: 'engageya',
+ params: {
+ widgetId: 85610,
+ websiteId: 91140,
+ pageUrl: '[PAGE_URL]'
+ }
+ }
+ ]
+
+ nativeBidRequests = [
+ {
+ bidder: 'engageya',
+ params: {
+ widgetId: 85610,
+ websiteId: 91140,
+ pageUrl: '[PAGE_URL]'
+ },
+ nativeParams: {
+ title: {
+ required: true,
+ len: 80
+ },
+ image: {
+ required: true,
+ sizes: [150, 50]
+ },
+ sponsoredBy: {
+ required: true
+ }
+ }
+ }
+ ]
+ })
+ describe('isBidRequestValid', function () {
+ it('valid bid case', function () {
+ let validBid = {
+ bidder: 'engageya',
+ params: {
+ widgetId: 85610,
+ websiteId: 91140,
+ pageUrl: '[PAGE_URL]'
+ }
+ }
+ let isValid = spec.isBidRequestValid(validBid);
+ expect(isValid).to.equal(true);
+ });
+
+ it('invalid bid case: widgetId and websiteId is not passed', function () {
+ let validBid = {
+ bidder: 'engageya',
+ params: {}
+ }
+ let isValid = spec.isBidRequestValid(validBid);
+ expect(isValid).to.equal(false);
+ })
+
+ it('invalid bid case: widget id must be number', function () {
+ let invalidBid = {
+ bidder: 'engageya',
+ params: {
+ widgetId: '157746a',
+ websiteId: 91140,
+ pageUrl: '[PAGE_URL]'
+ }
+ }
+ let isValid = spec.isBidRequestValid(invalidBid);
+ 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.include(ENDPOINT_URL);
+ expect(request.method).to.equal('GET');
+ });
+
+ it('buildRequests function should not modify original bidRequests object', function () {
+ let originalBidRequests = utils.deepClone(bidRequests);
+ let request = spec.buildRequests(bidRequests);
+ expect(bidRequests).to.deep.equal(originalBidRequests);
+ });
+
+ it('buildRequests function should not modify original nativeBidRequests object', function () {
+ let originalBidRequests = utils.deepClone(nativeBidRequests);
+ let request = spec.buildRequests(nativeBidRequests);
+ expect(nativeBidRequests).to.deep.equal(originalBidRequests);
+ });
+
+ it('Request params check', function () {
+ let request = spec.buildRequests(bidRequests)[0];
+ const data = _getUrlVars(request.url)
+ expect(parseInt(data.wid)).to.exist.and.to.equal(bidRequests[0].params.widgetId);
+ expect(parseInt(data.webid)).to.exist.and.to.equal(bidRequests[0].params.websiteId);
+ })
+ })
+
+ describe('interpretResponse', function () {
+ it('should return empty array if no response', function () {
+ const result = spec.interpretResponse({}, [])
+ expect(result).to.be.an('array').that.is.empty
+ });
+
+ it('should return empty array if no valid bids', function () {
+ let response = {
+ recs: [],
+ imageWidth: 300,
+ imageHeight: 250,
+ ireqId: '1d236f7890b',
+ pbtypeId: 2
+ };
+ let request = spec.buildRequests(bidRequests)[0];
+ const result = spec.interpretResponse({ body: response }, request)
+ expect(result).to.be.an('array').that.is.empty
+ });
+
+ it('should interpret native response', function () {
+ let serverResponse = {
+ recs: [
+ {
+ ecpm: 0.0920,
+ postId: '',
+ thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png',
+ domain: 'domain.test',
+ title: 'Test title',
+ clickUrl: '//click.test',
+ url: '//url.test',
+ displayName: 'Test displayName',
+ trackers: {
+ impressionPixels: ['//impression.test'],
+ viewPixels: ['//view.test'],
+ }
+ }
+ ],
+ imageWidth: 300,
+ imageHeight: 250,
+ ireqId: '1d236f7890b',
+ pbtypeId: 1
+ };
+ let expectedResult = [
+ {
+ requestId: '1d236f7890b',
+ cpm: 0.0920,
+ width: 300,
+ height: 250,
+ netRevenue: false,
+ currency: 'USD',
+ creativeId: '',
+ ttl: 360,
+ meta: {
+ advertiserDomains: ['domain.test']
+ },
+ native: {
+ title: 'Test title',
+ body: '',
+ image: {
+ url: 'https://engageya.live/wp-content/uploads/2019/05/images.png',
+ width: 300,
+ height: 250
+ },
+ privacyLink: '',
+ clickUrl: '//click.test',
+ displayUrl: '//url.test',
+ cta: '',
+ sponsoredBy: 'Test displayName',
+ impressionTrackers: ['//impression.test', '//view.test'],
+ },
+ }
+ ];
+ let request = spec.buildRequests(bidRequests)[0];
+ let result = spec.interpretResponse({ body: serverResponse }, request);
+ expect(result).to.deep.equal(expectedResult);
+ });
+
+ it('should interpret display response', function () {
+ let serverResponse = {
+ recs: [
+ {
+ ecpm: 0.0920,
+ postId: '',
+ thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png',
+ domain: 'domain.test',
+ title: 'Test title',
+ clickUrl: '//click.test',
+ url: '//url.test',
+ displayName: 'Test displayName',
+ trackers: {
+ impressionPixels: ['//impression.test'],
+ viewPixels: ['//view.test'],
+ }
+ }
+ ],
+ imageWidth: 300,
+ imageHeight: 250,
+ ireqId: '1d236f7890b',
+ pbtypeId: 2,
+ widget: {
+ additionalData: '{"css":".eng_tag_ttl{display:block!important}"}'
+ }
+ };
+ let expectedResult = [
+ {
+ requestId: '1d236f7890b',
+ cpm: 0.0920,
+ width: 300,
+ height: 250,
+ netRevenue: false,
+ currency: 'USD',
+ creativeId: '',
+ ttl: 360,
+ meta: {
+ advertiserDomains: ['domain.test']
+ },
+ ad: ``,
+ }
+ ];
+ let request = spec.buildRequests(bidRequests)[0];
+ let result = spec.interpretResponse({ body: serverResponse }, request);
+ expect(result).to.deep.equal(expectedResult);
+ });
+
+ it('should interpret display response without title', function () {
+ let serverResponse = {
+ recs: [
+ {
+ ecpm: 0.0920,
+ postId: '',
+ thumbnail_path: 'https://engageya.live/wp-content/uploads/2019/05/images.png',
+ domain: 'domain.test',
+ title: ' ',
+ clickUrl: '//click.test',
+ url: '//url.test',
+ displayName: 'Test displayName',
+ }
+ ],
+ imageWidth: 300,
+ imageHeight: 250,
+ ireqId: '1d236f7890b',
+ pbtypeId: 2,
+ };
+ let expectedResult = [
+ {
+ requestId: '1d236f7890b',
+ cpm: 0.0920,
+ width: 300,
+ height: 250,
+ netRevenue: false,
+ currency: 'USD',
+ creativeId: '',
+ ttl: 360,
+ meta: {
+ advertiserDomains: ['domain.test']
+ },
+ ad: ``,
+ }
+ ];
+ let request = spec.buildRequests(bidRequests)[0];
+ let result = spec.interpretResponse({ body: serverResponse }, request);
+ expect(result).to.deep.equal(expectedResult);
+ });
+ })
+})