diff --git a/modules/realvuAnalyticsAdapter.js b/modules/realvuAnalyticsAdapter.js
new file mode 100644
index 00000000000..7dbb309ea18
--- /dev/null
+++ b/modules/realvuAnalyticsAdapter.js
@@ -0,0 +1,945 @@
+// RealVu Analytics Adapter
+import adapter from 'src/AnalyticsAdapter';
+import adaptermanager from 'src/adaptermanager';
+import CONSTANTS from 'src/constants.json';
+
+const utils = require('src/utils.js');
+
+let realvuAnalyticsAdapter = adapter({
+ global: 'realvuAnalytics',
+ handler: 'on',
+ analyticsType: 'bundle'
+});
+window.top1 = window;
+try {
+ let wnd = window;
+ while ((window.top1 != top) && (typeof (wnd.document) != 'undefined')) {
+ window.top1 = wnd;
+ wnd = wnd.parent;
+ }
+} catch (e) {
+ /* continue regardless of error */
+}
+window.top1.realvu_aa_fifo = window.top1.realvu_aa_fifo || [];
+window.top1.realvu_aa = window.top1.realvu_aa || {
+ ads: [],
+ x1: 0,
+ y1: 0,
+ x2: 0,
+ y2: 0,
+ t0: new Date(),
+ nn: 0,
+ frm: false, // check first if we are inside other domain iframe
+ msg: [],
+ foc: !window.top1.document.hidden, // 1-in, 0-out of focus
+ c: '', // owner id
+ sr: '', //
+ beacons: [], // array of beacons to collect while 'conf' is not responded
+ init: function () {
+ let z = this;
+ let u = navigator.userAgent;
+ z.device = u.match(/iPad|Tablet/gi) ? 'tablet' : u.match(/iPhone|iPod|Android|Opera Mini|IEMobile/gi) ? 'mobile' : 'desktop';
+ if (typeof (z.len) == 'undefined') z.len = 0; // check, meybe too much, just make it len:0,
+ z.ie = navigator.appVersion.match(/MSIE/);
+ z.saf = (u.match(/Safari/) && !u.match(/Chrome/));
+ z.ff = u.match(/Firefox/i);
+ z.cr = (u.match(/Chrome/));
+ z.ope = window.opera;
+ z.fr = 0;
+ if (window.top1 != top) {
+ z.fr = 2;
+ if (typeof window.top1.$sf != 'undefined') {
+ z.fr = 1;
+ }
+ }
+ z.add_evt(window.top1, 'focus', function () {
+ window.top1.realvu_aa.foc = 1; /* window.top1.realvu_aa.log('focus',-1); */
+ });
+ // z.add_evt(window.top1, "scroll", function(){window.top1.realvu_aa.foc=1;window.top1.realvu_aa.log('scroll focus',-1);});
+ z.add_evt(window.top1, 'blur', function () {
+ window.top1.realvu_aa.foc = 0; /* window.top1.realvu_aa.log('blur',-1); */
+ });
+ // + http://www.w3.org/TR/page-visibility/
+ z.add_evt(window.top1.document, 'blur', function () {
+ window.top1.realvu_aa.foc = 0; /* window.top1.realvu_aa.log('blur',-1); */
+ });
+ z.add_evt(window.top1, 'visibilitychange', function () {
+ window.top1.realvu_aa.foc = !window.top1.document.hidden;
+ /* window.top1.realvu_aa.log('vis-ch '+window.top1.realvu_aa.foc,-1); */
+ });
+ // -
+ z.doLog = (window.top1.location.search.match(/boost_log/) || document.referrer.match(/boost_log/)) ? 1 : 0;
+ if (z.doLog) {
+ window.setTimeout(z.scr(window.top1.location.protocol + '//ac.realvu.net/realvu_aa_viz.js'), 500);
+ }
+ },
+
+ add_evt: function (elem, evtType, func) {
+ if (elem.addEventListener) {
+ elem.addEventListener(evtType, func, true);
+ } else if (elem.attachEvent) {
+ elem.attachEvent('on' + evtType, func);
+ } else {
+ elem['on' + evtType] = func;
+ }
+ },
+
+ update: function () {
+ let z = this;
+ let de = window.top1.document.documentElement;
+ z.x1 = window.top1.pageXOffset ? window.top1.pageXOffset : de.scrollLeft;
+ z.y1 = window.top1.pageYOffset ? window.top1.pageYOffset : de.scrollTop;
+ let w1 = window.top1.innerWidth ? window.top1.innerWidth : de.clientWidth;
+ let h1 = window.top1.innerHeight ? window.top1.innerHeight : de.clientHeight;
+ z.x2 = z.x1 + w1;
+ z.y2 = z.y1 + h1;
+ },
+ brd: function (s, p) { // return a board Width, s-element, p={Top,Right,Bottom, Left}
+ let u;
+ if (window.getComputedStyle) u = window.getComputedStyle(s, null);
+ else u = s.style;
+ let a = u['border' + p + 'Width'];
+ return parseInt(a.length > 2 ? a.slice(0, -2) : 0);
+ },
+
+ padd: function (s, p) { // return a board Width, s-element, p={Top,Right,Bottom, Left}
+ let u;
+ if (window.getComputedStyle) u = window.getComputedStyle(s, null);
+ else u = s.style;
+ let a = u['padding' + p];
+ return parseInt(a.length > 2 ? a.slice(0, -2) : 0);
+ },
+
+ viz_area: function (x1, x2, y1, y2) { // coords of Ad
+ if (this.fr == 1) {
+ try {
+ let iv = Math.round(100 * window.top1.$sf.ext.geom().self.iv);
+ return iv;
+ } catch (e) {
+ /* continue regardless of error */
+ }
+ }
+ let xv1 = Math.max(x1, this.x1);
+ let yv1 = Math.max(y1, this.y1);
+ let xv2 = Math.min(x2, this.x2);
+ let yv2 = Math.min(y2, this.y2);
+ let A = Math.round(100 * ((xv2 - xv1) * (yv2 - yv1)) / ((x2 - x1) * (y2 - y1)));
+ return (A > 0) ? A : 0;
+ },
+
+ viz_dist: function (x1, x2, y1, y2) { // coords of Ad
+ let d = Math.max(0, this.x1 - x2, x1 - this.x2) + Math.max(0, this.y1 - y2, y1 - this.y2);
+ return d;
+ },
+
+ track: function (a, f, params) {
+ let z = this;
+ let s1 = z.tru(a, f) + params;
+ if (f == 'conf') {
+ z.scr(s1, a);
+ z.log(' ' + f + '', a.num);
+ } else {
+ let bk = {
+ s1: s1,
+ a: a,
+ f: f
+ };
+ z.beacons.push(bk);
+ }
+ },
+
+ send_track: function () {
+ let z = this;
+ if (z.sr >= 'a') { // conf, send beacons
+ let bk = z.beacons.shift();
+ while (typeof bk != 'undefined') {
+ bk.s1 = bk.s1.replace(/_sr=0*_/, '_sr=' + z.sr + '_');
+ z.log(' ' + bk.a.riff + ' ' + bk.a.unit_id + /* " "+pin.mode+ */ ' ' + bk.a.w + 'x' + bk.a.h + '@' + bk.a.x + ',' + bk.a.y +
+ ' ' + bk.f + '', bk.a.num);
+ if (bk.a.rnd < Math.pow(10, 1 - (z.sr.charCodeAt(0) & 7))) {
+ z.scr(bk.s1, bk.a);
+ }
+ bk = z.beacons.shift();
+ }
+ }
+ },
+
+ scr: function (s1, a) {
+ let st = document.createElement('script');
+ st.async = true;
+ st.type = 'text/javascript';
+ st.src = s1;
+ if (a && a.dv0 != null) {
+ a.dv0.appendChild(st);
+ } else {
+ let x = document.getElementsByTagName('script')[0];
+ x.parentNode.insertBefore(st, x);
+ }
+ },
+
+ tru: function (a, f) {
+ let pin = a.pins[0];
+ let s2 = '//ac.realvu.net/flip/3/c=' + pin.partner_id +
+ '_f=' + f + '_r=' + a.riff +
+ '_s=' + a.w + 'x' + a.h;
+ if (a.p) s2 += '_p=' + a.p;
+ s2 += '_ps=' + this.enc(a.unit_id) + // 08-Jun-15 - _p= is replaced with _ps= - p-number, ps-string
+ '_dv=' + this.device +
+ // + '_a=' + this.enc(a.a)
+ '_d=' + pin.mode +
+ '_sr=' + this.sr +
+ '_h=' + this.enc(a.ru) + '?';
+ return s2.replace(/%/g, '!');
+ },
+
+ enc: function (s1) {
+ // return escape(s1).replace(/[0-9a-f]{5,}/gi,'RANDOM').replace(/\*/g, "%2A").replace(/_/g, "%5F").replace(/\+/g,
+ return escape(s1).replace(/\*/g, '%2A').replace(/_/g, '%5F').replace(/\+/g,
+ '%2B').replace(/\./g, '%2E').replace(/\x2F/g, '%2F');
+ },
+
+ findPosG: function (adi) {
+ let t = this;
+ let ad = adi;
+ let xp = 0;
+ let yp = 0;
+ let dc = adi.ownerDocument;
+ let wnd = dc.defaultView || dc.parentWindow;
+
+ try {
+ while (ad != null && typeof (ad) != 'undefined') {
+ if (ad.getBoundingClientRect) { // Internet Explorer, Firefox 3+, Google Chrome, Opera 9.5+, Safari 4+
+ let r = ad.getBoundingClientRect();
+ xp += r.left; // +sL;
+ yp += r.top; // +sT;
+ if (wnd == window.top1) {
+ xp += t.x1;
+ yp += t.y1;
+ }
+ } else {
+ if (ad.tagName == 'IFRAME') {
+ xp += t.brd(ad, 'Left');
+ yp += t.brd(ad, 'Top');
+ }
+ xp += ad.offsetLeft;
+ yp += ad.offsetTop;
+
+ let op = ad.offsetParent;
+ let pn = ad.parentNode;
+ let opf = ad;
+ while (opf != null) {
+ let cs = window.getComputedStyle(opf, null);
+ if (cs.position == 'fixed') {
+ if (cs.top) yp += parseInt(cs.top) + this.y1;
+ }
+ if (opf == op) break;
+ opf = opf.parentNode;
+ }
+ while (op != null && typeof (op) != 'undefined') {
+ xp += op.offsetLeft;
+ yp += op.offsetTop;
+ let ptn = op.tagName;
+ if (t.cr || t.saf || (t.ff && ptn == 'TD')) {
+ xp += t.brd(op, 'Left');
+ yp += t.brd(op, 'Top');
+ }
+ if (ad.tagName != 'IFRAME' && op != document.body && op != document.documentElement) {
+ xp -= op.scrollLeft;
+ yp -= op.scrollTop;
+ }
+ if (!t.ie) {
+ while (op != pn && pn != null) {
+ xp -= pn.scrollLeft;
+ yp -= pn.scrollTop;
+ if (t.ff_o) {
+ xp += t.brd(pn, 'Left');
+ yp += t.brd(pn, 'Top');
+ }
+ pn = pn.parentNode;
+ }
+ }
+ pn = pn.parentNode;
+ op = op.offsetParent;
+ }
+ }
+ if (this.fr) break; // inside different domain iframe or sf
+ ad = wnd.frameElement; // in case Ad is allocated inside iframe here we go up
+ wnd = wnd.parent;
+ }
+ } catch (e) {
+ /* continue regardless of error */
+ }
+ let q = {
+ 'x': Math.round(xp),
+ 'y': Math.round(yp)
+ };
+ return q;
+ },
+
+ poll: function () {
+ let fifo = window.top1.realvu_aa_fifo;
+ while (fifo.length > 0) {
+ (fifo.shift())();
+ }
+ let z = this;
+ z.update();
+ let now = new Date();
+ if (typeof (z.ptm) == 'undefined') {
+ z.ptm = now;
+ }
+ let dvz = now - z.ptm;
+ z.ptm = now;
+ for (let i = 0; i < z.len; i++) {
+ let a = z.ads[i];
+ let restored = false;
+ if (a.div == null) { // ad unit is not found yet
+ let adobj = document.getElementById(a.pins[0].unit_id);
+ if (adobj == null) {
+ restored = z.readPos(a);
+ if (!restored) continue; // do nothing if not found
+ } else {
+ z.bind_obj(a, adobj);
+ z.log('{m}"' + a.unit_id + '" is bound', a.num);
+ }
+ }
+ if (!restored) {
+ a.target = z.questA(a.div);
+ let target = (a.target !== null) ? a.target : a.div;
+ a.box.w = Math.max(target.offsetWidth, a.w);
+ a.box.h = Math.max(target.offsetHeight, a.h);
+ let q = z.findPosG(target);
+ let pad = {};
+ pad.t = z.padd(target, 'Top');
+ pad.l = z.padd(target, 'Left');
+ pad.r = z.padd(target, 'Right');
+ pad.b = z.padd(target, 'Bottom');
+ let ax = q.x + pad.l;
+ let ay = q.y + pad.t;
+ a.box.x = ax;
+ a.box.y = ay;
+ if (a.box.w > a.w && a.box.w > 1) {
+ ax += (a.box.w - a.w - pad.l - pad.r) / 2;
+ }
+ if (a.box.h > a.h && a.box.h > 1) {
+ ay += (a.box.h - a.h - pad.t - pad.b) / 2;
+ }
+ if ((ax > 0 && ay > 0) && (a.x != ax || a.y != ay)) {
+ a.x = ax;
+ a.y = ay;
+ z.writePos(a);
+ }
+ }
+ let vtr = ((a.box.w * a.box.h) < 242500) ? 49 : 29; // treashfold more then 49% and more then 29% for "oversized"
+ if (a.pins[0].edge) {
+ vtr = a.pins[0].edge - 1; // override default edge 50% (>49)
+ }
+ a.vz = z.viz_area(a.box.x, a.box.x + a.box.w, a.box.y, a.box.y + a.box.h);
+ a.r = (z.fr > 1 ? 'frame' : (((a.vz > vtr) && z.foc) ? 'yes' : 'no')); // f-frame, y-yes in view,n-not in view
+ if (a.y < 0) {
+ a.r = 'out'; // if the unit intentionaly moved out, count it as out.
+ }
+ if (a.vz > vtr && z.foc) {
+ a.vt += dvz; // real dt counter in milliseconds, because of poll() can be called irregularly
+ a.vtu += dvz;
+ }
+ // now process every pin
+ let plen = a.pins.length;
+ for (let j = 0; j < plen; j++) {
+ let pin = a.pins[j];
+ if (pin.state <= 1) {
+ let dist = z.viz_dist(a.x, a.x + a.w, a.y, a.y + a.h);
+ let near = (pin.dist != null && dist <= pin.dist);
+ // apply "near" rule for ad call only
+ a.r = (z.fr > 1) ? 'frame' : (((a.vz > vtr) && z.foc) ? 'yes' : 'no');
+ if (near && a.r == 'no') {
+ a.r = 'yes';
+ }
+ if (a.riff === '') {
+ a.riff = a.r;
+ let vr_score = z.score(a, 'v:r');
+ if (vr_score != null) {
+ if (a.r == 'no' && vr_score > 75) {
+ a.riff = 'yes';
+ }
+ }
+ let vv0_score = z.score(a, 'v:v0');
+ if (vv0_score != null) {
+ if (a.r == 'yes' && vv0_score < (30 + 25 * Math.random())) {
+ a.riff = 'no';
+ }
+ }
+ }
+ if ((pin.mode == 'kvp' || pin.mode == 'tx2') || (((a.vz > vtr) || near) && ((pin.mode == 'in-view' || pin.mode == 'video')))) {
+ z.show(a, pin); // in-view or flip show immediately if initial realvu=yes, or delay is over
+ }
+ }
+ if (pin.state == 2) {
+ a.target = z.questA(a.div);
+ if (a.target != null) {
+ pin.state = 3;
+ dvz = 0;
+ a.vt = 0;
+ // @if NODE_ENV='debug'
+ let now = new Date();
+ let msg = (now.getTime() - time0) / 1000 + ' RENDERED ' + a.unit_id;
+ utils.logMessage(msg);
+ // @endif
+ let rpt = z.bids_rpt(a, true);
+ z.track(a, 'rend', rpt);
+ z.incrMem(a, 'r', 'v:r');
+ }
+ }
+ if (pin.state > 2) {
+ let tmin = (pin.mode == 'video') ? 2E3 : 1E3; // mrc min view time
+ if (a.vz > vtr) {
+ pin.vt += dvz; // real dt counter in milliseconds, because of poll() can be called irregularly
+ if (pin.state == 3) {
+ pin.state = 4;
+ z.incrMem(a, 'r', 'v:v0');
+ }
+ if (pin.state == 4 && pin.vt >= tmin) {
+ pin.state = 5;
+ let rpt = z.bids_rpt(a, true);
+ z.track(a, 'view', rpt);
+ z.incrMem(a, 'v', 'v:r');
+ z.incrMem(a, 'v', 'v:v0');
+ }
+ if (pin.state == 5 && pin.vt >= 5 * tmin) {
+ pin.state = 6;
+ let rpt = z.bids_rpt(a, true);
+ z.track(a, 'view2', rpt);
+ }
+ } else if (pin.vt < tmin) {
+ pin.vt = 0; // reset to track continuous 1 sec
+ }
+ }
+ if (pin.state >= 2 && pin.mode === 'tx2' &&
+ ((a.vtu > pin.rotate) || (pin.delay > 0 && a.vtu > pin.delay && a.riff === 'no' && a.ncall < 2)) && pin.tx2n > 0) {
+ // flip or rotate
+ pin.tx2n--;
+ pin.state = 1;
+ a.vtu = 0;
+ a.target = null;
+ }
+ }
+ }
+ this.send_track();
+ },
+
+ questA: function (a) { // look for the visible object of ad_sizes size
+ // returns the object or null
+ if (a == null) return a;
+ if (a.nodeType == Node.TEXT_NODE) {
+ let dc = a.ownerDocument;
+ let wnd = dc.defaultView || dc.parentWindow;
+ let par = a.parentNode;
+ if (wnd == wnd.top) {
+ return par;
+ } else {
+ return par.offsetParent;
+ }
+ }
+ let not_friendly = false;
+ let ain = null;
+ let tn = a.tagName;
+ if (tn == 'HEAD' || tn == 'SCRIPT') return null;
+ if (tn == 'IFRAME') {
+ ain = this.doc(a);
+ if (ain == null) {
+ not_friendly = true;
+ } else {
+ a = ain;
+ tn = a.tagName;
+ }
+ }
+ if (not_friendly || tn == 'OBJECT' || tn == 'IMG' || tn == 'EMBED' || tn == 'SVG' || tn == 'CANVAS' ||
+ (tn == 'DIV' && a.style.backgroundImage)) {
+ let w1 = a.offsetWidth;
+ let h1 = a.offsetHeight;
+ if (w1 > 33 && h1 > 33 && a.style.display != 'none') return a;
+ }
+ if (a.hasChildNodes()) {
+ let b = a.firstChild;
+ while (b != null) {
+ let c = this.questA(b);
+ if (c != null) return c;
+ b = b.nextSibling;
+ }
+ }
+ return null;
+ },
+
+ doc: function(f) { // return document of f-iframe, keep here "n" as a parameter because of call from setTimeout()
+ let d = null;
+ try {
+ if (f.contentDocument) d = f.contentDocument; // DOM
+ else if (f.contentWindow) d = f.contentWindow.document; // IE
+ } catch (e) {
+ /* continue regardless of error */
+ }
+ return d;
+ },
+
+ bind_obj: function (a, adobj) {
+ a.div = adobj;
+ a.target = null; // initially null, found ad when served
+ a.unit_id = adobj.id; // placement id or name
+ a.w = adobj.offsetWidth || 1; // width, min 1
+ a.h = adobj.offsetHeight || 1; // height, min 1
+ },
+ add: function (wnd1, p) { // p - realvu unit id
+ let a = {
+ num: this.len,
+ x: 0,
+ y: 0,
+ box: {
+ x: 0,
+ y: 0,
+ h: 1,
+ w: 1
+ }, // measured ad box
+ p: p,
+ state: 0, // 0-init, (1-loaded,2-rendered,3-viewed)
+ delay: 0, // delay in msec to show ad after gets in view
+ vt: 0, // total view time
+ vtu: 0, // view time to update and mem
+ a: '', // ad_placement id
+ wnd: wnd1,
+ div: null,
+ pins: [],
+ frm: null, // it will be frame when "show"
+ riff: '', // r to report
+ rnd: Math.random(),
+ ncall: 0, // a callback number
+ rq_bids: [], // rq bids of registered partners
+ bids: [] // array of bids
+ };
+ a.ru = window.top1.location.hostname;
+ window.top1.realvu_aa.ads[this.len++] = a;
+ return a;
+ },
+
+ fmt: function (a, pin) {
+ return {
+ 'realvu': a.r,
+ 'riff': a.riff,
+ 'area': a.vz,
+ 'ncall': a.ncall,
+ 'n': a.num,
+ 'id': a.unit_id,
+ 'pin': pin
+ };
+ },
+
+ show: function (a, pin) {
+ pin.state = 2; // 2-published
+ pin.vt = 0; // reset view time counter
+ if (pin.size) {
+ let asz = this.setSize(pin.size);
+ if (asz != null) {
+ a.w = asz.w;
+ a.h = asz.h;
+ }
+ }
+ if (typeof pin.callback != 'undefined') {
+ pin.callback(this.fmt(a, pin));
+ }
+ a.ncall++;
+ this.track(a, 'show', '');
+ },
+
+ check: function (p1) {
+ let pin = {
+ dist: 150,
+ state: 0,
+ tx2n: 7
+ }; // if dist is set trigger ad when distance < pin.dist
+ for (let attr in p1) {
+ if (p1.hasOwnProperty(attr)) {
+ if ((attr == 'ad_sizes') && (typeof (p1[attr]) == 'string')) {
+ pin[attr] = p1[attr].split(',');
+ } else if (attr == 'edge') {
+ try {
+ let ed = parseInt(p1[attr]);
+ if (ed > 0 && ed < 251) pin[attr] = ed;
+ } catch (e) {
+ /* continue regardless of error */
+ }
+ } else {
+ pin[attr] = p1[attr];
+ }
+ }
+ }
+ let a = null;
+ let z = this;
+ try {
+ // not to track the same object more than one time
+ for (let i = 0; i < z.len; i++) {
+ // if (z.ads[i].div == adobj) { a = z.ads[i]; break; }
+ if (z.ads[i].unit_id == pin.unit_id) {
+ a = z.ads[i];
+ break;
+ }
+ }
+ pin.wnd = pin.wnd || window;
+ if (a == null) {
+ a = z.add(pin.wnd, pin.p);
+ a.unit_id = pin.unit_id;
+ let adobj = (pin.unit) ? pin.unit : document.getElementById(a.unit_id);
+ if (adobj != null) {
+ z.bind_obj(a, adobj);
+ } else {
+ z.log('{w}"' + pin.unit_id + '" not found', a.num);
+ }
+ if (pin.size) {
+ let asz = z.setSize(pin.size);
+ if (asz != null) {
+ a.w = asz.w;
+ a.h = asz.h;
+ }
+ }
+ pin.delay = pin.delay || 0; // delay in msec
+ if (typeof pin.mode == 'undefined') {
+ if ((typeof pin.callback != 'undefined') || (typeof pin.content != 'undefined')) {
+ pin.mode = (pin.delay > 0) ? 'tx2' : 'in-view';
+ } else {
+ pin.mode = 'kvp';
+ }
+ // delays are for views only
+ }
+ pin.vt = 0; // view time
+ pin.state = 0;
+ a.pins.push(pin);
+ }
+ if (this.sr === '') {
+ z.track(a, 'conf', '');
+ this.sr = '0';
+ }
+ this.poll();
+ return a;
+ } catch (e) {
+ z.log(e.message, -1);
+ return {
+ r: 'err'
+ };
+ }
+ },
+
+ setSize: function (sa) {
+ let sb = sa;
+ try {
+ if (typeof (sa) == 'string') sb = sa.split('x'); // pin.size is a string WWWxHHH or array
+ else if (Array.isArray(sa)) {
+ let mm = 4;
+ while (--mm > 0 && Array.isArray(sa[0]) && Array.isArray(sa[0][0])) {
+ sa = sa[0];
+ }
+ for (let m = 0; m < sa.length; m++) {
+ if (Array.isArray(sa[m])) {
+ sb = sa[m]; // if size is [][]
+ let s = sb[0] + 'x' + sb[1];
+ if (s == '300x250' || s == '728x90' || s == '320x50' || s == '970x90') {
+ break; // use most popular sizes
+ }
+ } else if (sa.length > 1) {
+ sb = sa;
+ }
+ }
+ }
+ let w1 = parseInt(sb[0]);
+ let h1 = parseInt(sb[1]);
+ return {
+ w: w1,
+ h: h1
+ };
+ } catch (e) {
+ /* continue regardless of error */
+ }
+ return null;
+ },
+ // API functions
+ addUnitById: function (partner_id, unit_id, callback, delay) {
+ let p1 = partner_id;
+ if (typeof (p1) == 'string') {
+ p1 = {
+ partner_id: partner_id,
+ unit_id: unit_id,
+ callback: callback,
+ delay: delay
+ };
+ }
+ let a = window.top1.realvu_aa.check(p1);
+ return a.r;
+ },
+
+ checkBidIn: function(partnerId, args, b) { // process a bid from hb
+ // b==true - add/update, b==false - update only
+ if (args.cpm == 0) return; // collect only bids submitted
+ const boost = window.top1.realvu_aa;
+ let push_bid = false;
+ let adi = null;
+ if (!b) { // update only if already checked in by xyzBidAdapter
+ for (let i = 0; i < boost.ads.length; i++) {
+ adi = boost.ads[i];
+ if (adi.unit_id == args.adUnitCode) {
+ push_bid = true;
+ break;
+ }
+ }
+ } else {
+ push_bid = true;
+ adi = window.top1.realvu_aa.check({
+ unit_id: args.adUnitCode,
+ size: args.size,
+ partner_id: partnerId
+ });
+ }
+ if (push_bid) {
+ let pb = {
+ bidder: args.bidder,
+ cpm: args.cpm,
+ size: args.size,
+ adId: args.adId,
+ requestId: args.requestId,
+ crid: '',
+ ttr: args.timeToRespond,
+ winner: 0
+ };
+ if (args.creative_id) {
+ pb.crid = args.creative_id;
+ }
+ adi.bids.push(pb);
+ }
+ },
+
+ checkBidWon: function(partnerId, args, b) {
+ // b==true - add/update, b==false - update only
+ const z = this;
+ const unit_id = args.adUnitCode;
+ for (let i = 0; i < z.ads.length; i++) {
+ let adi = z.ads[i];
+ if (adi.unit_id == unit_id) {
+ for (let j = 0; j < adi.bids.length; j++) {
+ let bj = adi.bids[j];
+ if (bj.adId == args.adId) {
+ bj.winner = 1;
+ break;
+ }
+ }
+ let rpt = z.bids_rpt(adi, false);
+ z.track(adi, 'win', rpt);
+ break;
+ }
+ }
+ },
+
+ bids_rpt: function(a, wo) { // a-unit, wo=true - WinnerOnly
+ let rpt = '';
+ for (let i = 0; i < a.bids.length; i++) {
+ let g = a.bids[i];
+ if (wo && !g.winner) continue;
+ rpt += '&bdr=' + g.bidder + '&cpm=' + g.cpm + '&vi=' + a.riff +
+ '&gw=' + g.winner + '&crt=' + g.crid + '&ttr=' + g.ttr;
+ // append bid partner_id if any
+ let pid = '';
+ for (let j = 0; j < a.rq_bids.length; j++) {
+ let rqb = a.rq_bids[j];
+ if (rqb.adId == g.adId) {
+ pid = rqb.partner_id;
+ break;
+ }
+ }
+ rpt += '&bc=' + pid;
+ }
+ return rpt;
+ },
+
+ getStatusById: function (unit_id) { // return status object
+ for (let i = 0; i < this.ads.length; i++) {
+ let adi = this.ads[i];
+ if (adi.unit_id == unit_id) return this.fmt(adi);
+ }
+ return null;
+ },
+
+ log: function (m1, i) {
+ if (this.doLog) {
+ this.msg.push({
+ dt: new Date() - this.t0,
+ s: 'U' + (i + 1) + m1
+ });
+ }
+ },
+
+ keyPos: function (a) {
+ if (a.pins[0].unit_id) {
+ let level = 'L' + (window.top1.location.pathname.match(/\//g) || []).length;
+ return 'realvu.' + level + '.' + a.pins[0].unit_id.replace(/[0-9]{5,}/gi, 'RANDOM');
+ }
+ },
+
+ writePos: function (a) {
+ try {
+ let v = a.x + ',' + a.y + ',' + a.w + ',' + a.h;
+ localStorage.setItem(this.keyPos(a), v);
+ } catch (ex) {
+ /* continue regardless of error */
+ }
+ },
+
+ readPos: function (a) {
+ try {
+ let s = localStorage.getItem(this.keyPos(a));
+ if (s) {
+ let v = s.split(',');
+ a.x = parseInt(v[0], 10);
+ a.y = parseInt(v[1], 10);
+ a.w = parseInt(v[2], 10);
+ a.h = parseInt(v[3], 10);
+ a.box = {x: a.x, y: a.y, w: a.w, h: a.h};
+ return true;
+ }
+ } catch (ex) {
+ /* do nothing */
+ }
+ return false;
+ },
+
+ incrMem: function(a, evt, name) {
+ try {
+ let k1 = this.keyPos(a) + '.' + name;
+ let vmem = localStorage.getItem(k1);
+ if (vmem == null) vmem = '1:3';
+ let vr = vmem.split(':');
+ let nv = parseInt(vr[0], 10);
+ let nr = parseInt(vr[1], 10);
+ if (evt == 'r') {
+ nr <<= 1;
+ nr |= 1;
+ nv <<= 1;
+ }
+ if (evt == 'v') {
+ nv |= 1;
+ }
+ localStorage.setItem(k1, nv + ':' + nr);
+ } catch (ex) {
+ /* do nothing */
+ }
+ },
+
+ score: function (a, name) {
+ try {
+ let vstr = localStorage.getItem(this.keyPos(a) + '.' + name);
+ if (vstr != null) {
+ let vr = vstr.split(':');
+ let nv = parseInt(vr[0], 10);
+ let nr = parseInt(vr[1], 10);
+ let sv = 0;
+ let sr = 0;
+ for (nr &= 0x3FF; nr > 0; nr >>>= 1, nv >>>= 1) { // count 10 deliveries
+ if (nr & 0x1) sr++;
+ if (nv & 0x1) sv++;
+ }
+ return Math.round(sv * 100 / sr);
+ }
+ } catch (ex) {
+ /* do nothing */
+ }
+ return null;
+ }
+};
+
+if (typeof (window.top1.boost_poll) == 'undefined') {
+ window.top1.realvu_aa.init();
+ window.top1.boost_poll = setInterval(function () {
+ window.top1 && window.top1.realvu_aa && window.top1.realvu_aa.poll();
+ }, 20);
+}
+
+let _options = {};
+
+realvuAnalyticsAdapter.originEnableAnalytics = realvuAnalyticsAdapter.enableAnalytics;
+
+realvuAnalyticsAdapter.enableAnalytics = function (config) {
+ _options = config.options;
+ if (typeof (_options.partnerId) == 'undefined' || _options.partnerId == '') {
+ utils.logError('Missed realvu.com partnerId parameter', 101, 'Missed partnerId parameter');
+ }
+ realvuAnalyticsAdapter.originEnableAnalytics(config);
+ return _options.partnerId;
+};
+
+const time0 = (new Date()).getTime();
+
+realvuAnalyticsAdapter.track = function ({eventType, args}) {
+ // @if NODE_ENV='debug'
+ let msg = '';
+ let now = new Date();
+ msg += (now.getTime() - time0) / 1000 + ' eventType=' + eventType;
+ if (typeof (args) != 'undefined') {
+ msg += ', args.bidder=' + args.bidder + ' args.adUnitCode=' + args.adUnitCode +
+ ' args.adId=' + args.adId +
+ ' args.cpm=' + args.cpm +
+ ' creativei_id=' + args.creative_id;
+ }
+ // msg += '\nargs=' + JSON.stringify(args) + '
';
+ utils.logMessage(msg);
+ // @endif
+
+ const boost = window.top1.realvu_aa;
+ let b = false; // false - update only, true - add if not checked in yet
+ let partnerId = null;
+ if (_options && _options.partnerId && args) {
+ partnerId = _options.partnerId;
+ let code = args.adUnitCode;
+ b = _options.regAllUnits;
+ if (!b && _options.unitIds) {
+ for (let j = 0; j < _options.unitIds.length; j++) {
+ if (code === _options.unitIds[j]) {
+ b = true;
+ break;
+ }
+ }
+ }
+ }
+ if (eventType === CONSTANTS.EVENTS.BID_RESPONSE) {
+ boost.checkBidIn(partnerId, args, b);
+ } else if (eventType === CONSTANTS.EVENTS.BID_WON) {
+ boost.checkBidWon(partnerId, args, b);
+ }
+};
+
+// xyzBidAdapter calls checkin() to obtain "yes/no" viewability
+realvuAnalyticsAdapter.checkIn = function (bid, partnerId) {
+ // find (or add if not registered yet) the unit in boost
+ if (typeof (partnerId) == 'undefined' || partnerId == '') {
+ utils.logError('Missed realvu.com partnerId parameter', 102, 'Missed partnerId parameter');
+ }
+ let a = window.top1.realvu_aa.check({
+ unit_id: bid.adUnitCode,
+ size: bid.sizes,
+ partner_id: partnerId
+ });
+ a.rq_bids.push({
+ bidder: bid.bidder,
+ adId: bid.bidId,
+ partner_id: partnerId
+ });
+ return a.riff;
+};
+
+realvuAnalyticsAdapter.isInView = function (adUnitCode) {
+ let r = 'NA';
+ let s = window.top1.realvu_aa.getStatusById(adUnitCode);
+ if (s) {
+ r = s.realvu;
+ }
+ return r;
+};
+
+adaptermanager.registerAnalyticsAdapter({
+ adapter: realvuAnalyticsAdapter,
+ code: 'realvuAnalytics'
+});
+
+module.exports = realvuAnalyticsAdapter;
diff --git a/modules/realvuAnalyticsAdapter.md b/modules/realvuAnalyticsAdapter.md
new file mode 100644
index 00000000000..c534f78bc94
--- /dev/null
+++ b/modules/realvuAnalyticsAdapter.md
@@ -0,0 +1,9 @@
+# Overview
+
+Module Name: RealVu Analytics Adapter
+Module Type: Analytics Adapter
+Maintainer: it@realvu.com
+
+# Description
+
+Analytics adapter for realvu.com. Contact support@realvu.com for information.
diff --git a/package-lock.json b/package-lock.json
index f3713a52f53..16b7eede42b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "prebid.js",
- "version": "1.4.0-pre",
+ "version": "1.7.0-pre",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
diff --git a/test/spec/modules/realvuAnalyticsAdapter_spec.js b/test/spec/modules/realvuAnalyticsAdapter_spec.js
new file mode 100644
index 00000000000..f198780077f
--- /dev/null
+++ b/test/spec/modules/realvuAnalyticsAdapter_spec.js
@@ -0,0 +1,163 @@
+import { expect } from 'chai';
+import realvuAnalyticsAdapter from 'modules/realvuAnalyticsAdapter';
+import CONSTANTS from 'src/constants.json';
+
+function addDiv(id) {
+ let dv = document.createElement('div');
+ dv.id = id;
+ dv.style.width = '728px';
+ dv.style.height = '90px';
+ dv.style.display = 'block';
+ document.body.appendChild(dv);
+ let f = document.createElement('iframe');
+ f.width = 728;
+ f.height = 90;
+ dv.appendChild(f);
+ let d = null;
+ if (f.contentDocument) d = f.contentDocument; // DOM
+ else if (f.contentWindow) d = f.contentWindow.document; // IE
+ d.open()
+ d.write('');
+ d.close();
+ return dv;
+}
+
+describe('RealVu Analytics Adapter.', () => {
+ before(() => {
+ addDiv('ad1');
+ addDiv('ad2');
+ });
+ after(() => {
+ let a1 = document.getElementById('ad1');
+ document.body.removeChild(a1);
+ let a2 = document.getElementById('ad2');
+ document.body.removeChild(a2);
+ });
+
+ it('enableAnalytics', () => {
+ const config = {
+ options: {
+ partnerId: '1Y',
+ regAllUnits: true
+ // unitIds: ['ad1', 'ad2']
+ }
+ };
+ let p = realvuAnalyticsAdapter.enableAnalytics(config);
+ expect(p).to.equal('1Y');
+ });
+
+ it('checkIn', () => {
+ const bid = {
+ adUnitCode: 'ad1',
+ sizes: [
+ [728, 90],
+ [970, 250],
+ [970, 90]
+ ]
+ };
+ let result = realvuAnalyticsAdapter.checkIn(bid, '1Y');
+ const b = window.top1.realvu_aa;
+ let a = b.ads[0];
+ // console.log('a: ' + a.x + ', ' + a.y + ', ' + a.w + ', ' + a.h);
+ // console.log('b: ' + b.x1 + ', ' + b.y1 + ', ' + b.x2 + ', ' + b.y2);
+ expect(result).to.equal('yes');
+
+ result = realvuAnalyticsAdapter.checkIn(bid); // test invalid partnerId 'undefined'
+ result = realvuAnalyticsAdapter.checkIn(bid, ''); // test invalid partnerId ''
+ });
+
+ it('isInView returns "yes"', () => {
+ let inview = realvuAnalyticsAdapter.isInView('ad1');
+ expect(inview).to.equal('yes');
+ });
+
+ it('isInView return "NA"', () => {
+ const adUnitCode = '1234';
+ let result = realvuAnalyticsAdapter.isInView(adUnitCode);
+ expect(result).to.equal('NA');
+ });
+
+ it('bid response event', () => {
+ const config = {
+ options: {
+ partnerId: '1Y',
+ regAllUnits: true
+ // unitIds: ['ad1', 'ad2']
+ }
+ };
+ realvuAnalyticsAdapter.enableAnalytics(config);
+ const args = {
+ 'biddercode': 'realvu',
+ 'adUnitCode': 'ad1',
+ 'width': 300,
+ 'height': 250,
+ 'statusMessage': 'Bid available',
+ 'adId': '7ba299eba818c1',
+ 'mediaType': 'banner',
+ 'creative_id': 85792851,
+ 'cpm': 0.4308
+ };
+ realvuAnalyticsAdapter.track({
+ eventType: CONSTANTS.EVENTS.BID_RESPONSE,
+ args: args
+ });
+ const boost = window.top1.realvu_aa;
+ expect(boost.ads[boost.len - 1].bids.length).to.equal(1);
+
+ realvuAnalyticsAdapter.track({
+ eventType: CONSTANTS.EVENTS.BID_WON,
+ args: args
+ });
+ expect(boost.ads[boost.len - 1].bids[0].winner).to.equal(1);
+ });
+});
+
+describe('RealVu Boost.', () => {
+ before(() => {
+ addDiv('ad1');
+ addDiv('ad2');
+ });
+ after(() => {
+ let a1 = document.getElementById('ad1');
+ document.body.removeChild(a1);
+ let a2 = document.getElementById('ad2');
+ document.body.removeChild(a2);
+ });
+
+ const boost = window.top1.realvu_aa;
+
+ it('brd', () => {
+ let a1 = document.getElementById('ad1');
+ let p = boost.brd(a1, 'Left');
+ expect(typeof p).to.not.equal('undefined');
+ });
+
+ it('addUnitById', () => {
+ let a1 = document.getElementById('ad1');
+ let p = boost.addUnitById('1Y', 'ad1');
+ expect(typeof p).to.not.equal('undefined');
+ });
+
+ it('questA', () => {
+ const dv = document.getElementById('ad1');
+ let q = boost.questA(dv);
+ expect(q).to.not.equal(null);
+ });
+
+ it('render', () => {
+ let dv = document.getElementById('ad1');
+ // dv.style.width = '728px';
+ // dv.style.height = '90px';
+ // dv.style.display = 'block';
+ dv.getBoundingClientRect = false;
+ // document.body.appendChild(dv);
+ let q = boost.findPosG(dv);
+ expect(q).to.not.equal(null);
+ });
+
+ it('readPos', () => {
+ const a = boost.ads[boost.len - 1];
+ let r = boost.readPos(a);
+ expect(r).to.equal(true);
+ });
+});