Is LoveDeals: Automatic Coupons & Deals safe?
LoveDeals reports the host of nearly every site you visit to lovedeals.ai and scrapes your Google search result URLs.
A content script running on all sites sends the main domain of each page you open to lovedeals.ai/deal/api/v1/info on navigation (re-reported every 5 minutes), building a server-side browsing profile; an allowlist skips some banking, search, and major sites. On Google search pages it collects all result URLs from the page and posts them to lovedeals.ai/deal/api/v1/g-search. The server's responses control on-page behavior including coupon popups, banner/ad injection, and silent affiliate-redirect tabs that route purchases through lovedeals.ai/deal/api/v1/transit.
AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.
Publishers can request a review.
Findings
LoveDeals reports every visited domain to lovedeals.ai on each page load
Each http/https visit, LoveDeals POSTs the hostname to lovedeals.ai/deal/api/v1/info with a guid and timestamp. ~40 domains are excluded; others, incl. banking sites, are reported every 5 min. 8 requests seen across amazon, ebay, bestbuy.
You navigate to any website not on the extension's built-in exclusion list.
The exclusion list covers approximately 40 domain substrings (e.g. google., facebook., chatgpt.com) but does not exclude the vast majority of http/https origins.
The extension POSTs the site's hostname and your device's persistent identifier to lovedeals.ai without prompting you.
The same domain is re-reported every five minutes if you stay on the page.
| Version | 2.7.0 |
| Auth-Token | TUFaVE9OLkFNQVpPTi5DT00uMTcxNzUwMDAwMC5CQVNFLjE= |
| Content-Type | application/x-www-form-urlencoded |
domain=https%3A%2F%2Fwww.amazon.com&time=1717500000&type=base&sendType=1&pageAd=0
| Field | Value | Why it matters | |
|---|---|---|---|
Visited domain | https://www.amazon.com | The full origin (scheme + hostname + port) of every site you visit, sent on every page load. | |
Device identifier (guid) | a3f82c1d9e4b0756a3f82c1d9e4b0756 | A persistent random token set on first install, stored locally and in a lovedeals.ai cookie, tying visits across sessions to one device. | |
Unix timestamp | 1717500000 | The current time in seconds, included with every request. | |
Extension version | 2.7.0 | The installed extension version, sent in the Version header on every request. |
Navigation handler and domain-reporting logic
static start(listener) {
this.addListener(listener);
this.sendCmd('docStart', {});
}
static sendCmd(cmd, options = {}) {
try {
chrome.runtime.sendMessage({ cmd, options }, () => {});
} catch (e) {}
return true;
}router(message, tab) {
tab.urlInfo = new URL(tab.url);
tab.urlInfo.mainhost = tab.urlInfo.origin;
switch (message.cmd) {
case 'docStart':
this.loadInit(tab);
break;
}
}loadInit(tab) {
if (!tab?.tab?.id || this.returnTabIds[tab.tab.id]) return false;
const now = Math.floor(Date.now() / 1000);
let forceRefresh = false;
if (this.domainInfo[tab.urlInfo.mainhost] &&
now - parseInt(this.domainInfo[tab.urlInfo.mainhost].time) > 300) {
forceRefresh = true;
}
if (this.domainInfo[tab.urlInfo.mainhost] === false ||
this.domainInfo[tab.urlInfo.mainhost] === undefined ||
forceRefresh) {
this.getGuid().then(guid => {
if (guid) {
if (forceRefresh) this.domainInfo[tab.urlInfo.mainhost] = undefined;
this.getBaseConf(tab);
}
});
}
}getBaseConf(tab) {
if (this.domainInfo[tab.urlInfo.mainhost] == null) {
if (this.noPassDomain(tab.urlInfo.mainhost)) {
this.sendHot(tab);
return false;
}
this.request('base', 1, tab); // POSTs domain to /deal/api/v1/info
} else {
this.sendDomainAd(tab);
this.sendHot(tab);
}
}request(type, method = 1, tab = false) {
const now = Math.floor(Date.now() / 1000);
const opts = Object.assign({}, this.requestOption[method]);
let url = this.origin + '/deal/api/v1/';
const payload = {};
payload.domain = tab ? tab.urlInfo.mainhost : '';
payload.time = now;
payload.type = type;
payload.sendType = method;
switch (type) {
case 'base': url += 'info'; break;
}
if (url !== this.origin + '/deal/api/v1/' && payload.domain) {
const authToken = this.toEncrypt({ ...payload });
opts.url = url;
opts.headers['Auth-Token'] = authToken;
opts.headers['Version'] = '2.7.0';
opts.body = new URLSearchParams(payload).toString();
this.goRequest(opts, tab); // fetch to url + '?guid=' + this.guid
}
}- lovedeals.ai
Receives the visited hostname, device guid, and timestamp on every page navigation. Operated by the LoveDeals extension developer.
Server Response Controls LoveDeals Page UI
LoveDeals requested config from lovedeals.ai/deal/api/v1/info and got route=base with domainAd for en.wikipedia.org.
The worker uses that to pick which messages to send the active tab: popups, domain ads, hot-deal data, page ads, prompts.
You visit a site where the extension is allowed to run.
The manifest registers content scripts for all HTTP and HTTPS pages.
The extension asks lovedeals.ai which deal UI or page action to activate.
A route=base response can lead to coupon UI, domain ads, hot-deal data, page ads, transit tabs, or an update prompt.
| Field | Value | Why it matters | |
|---|---|---|---|
Route selector | route: "base" | This tells the extension which branch of page behavior to run for the site you are viewing. | |
Domain ad payload | domainAd for en.wikipedia.org with img, url, and ballClose fields | This can place a domain-specific ad banner into the page and open a transit URL when it is clicked. | |
Coupon popup data | coupons[0].code with couponTotal, brand, worked, and successRate | This fills the coupon UI shown on the page, including a code, brand, total count, and success-rate labels. | |
Transit tab target | https://lovedeals.ai/deal/api/v1/transit?type=1&cid=42&goto=https%3A%2F%2Fwww.example-store.com%2Fdeal | This can open a background transit tab that routes through lovedeals.ai before going to a deal URL. | |
Update prompt flag | update: true | This can show an alert asking you to update the extension while you are browsing. |
| When | You did | Extension did |
|---|---|---|
| immediate | remote The server returns route=base with domainAd data for the current site. | service_worker The service worker sends a domainAd message into the active tab. |
| same response | remote The server returns coupon data in the base response. | service_worker The service worker sends rBase data that fills the coupon popup. |
| same response | remote The server returns pageAd data and brand context. | service_worker The service worker sends adDisplay data to draw a page ad. |
| same response | remote The server includes an update flag in the base response. | content_script The content script shows an extension update alert in the page. |
Service worker route handling and tab messages
request(e, t = 1, r = !1) {
let i = Math.floor(new Date().getTime() / 1e3),
n = this.requestOption[t],
o = this.origin + "/deal/api/v1/",
a = {},
s = !1;
switch (a.domain = r ? r.urlInfo.mainhost : "", a.time = i, a.type = e, a.sendType = t, e) {
case "base":
o += "info", !this.pageAdExp || new Date().getTime() / 1e3 - this.pageAdExp > 43200 ? a.pageAd = 0 : a.pageAd = this.pageAd ? 1 : 0;
break;
case "site":
o += "site";
break;
case "ping":
o += "ping";
break;
case "search":
o += "search", a.text = r.text;
break;
case "hot":
o += "hot", s = !0;
break;
case "g-search":
o += "g-search", a.urls = r.urls, s = !0;
break;
case "make":
o += "make", s = !0;
break;
case "user-info":
o += "user-info", n.headers["Auth-Cookie"] = r.cookie, s = !0
}
if (o != this.origin + "/deal/api/v1/" && (s || a.domain)) {
let e = {
...a
};
delete e.urls, delete e.pageAd;
let t = this.toEncrypt(e);
n.url = o, n.headers[this.encryptField] = t, n.headers.Authorization = void 0, n.headers.Version = "2.7.0", n.headers["Content-Type"] = "application/x-www-form-urlencoded", n.headers.Authorization || delete n.headers.Authorization, Object.keys(a).length > 0 ? n.body = new URLSearchParams(a).toString() : delete n.body, this.goRequest(n, r)
}
}
goRequest(e, t = !1) {
fetch(e.url + "?guid=" + this.guid, e).then(e => (e.ok || console.log("net connect error!"), e.json())).then(e => {
e && e.route && this.responseRoute(e, t)
})
}responseRoute(e = {}, t = !1) {
let r = this.tabs();
switch (e.route) {
case "base":
e.pageAd && (this.pageAd = e.pageAd, this.pageAdExp = new Date().getTime() / 1e3), e.domainAd && (this.domainAd = e.domainAd, this.domainAdList[t.urlInfo.mainhost] = 1, this.sendDomainAd(t).then()), e.coupons && e.coupons.length > 0 ? (this.domainInfo[t.urlInfo.mainhost] = e, this.domainInfo[t.urlInfo.mainhost].time = Math.floor(new Date().getTime() / 1e3), this.setExtInfo(t).then()) : (this.pageAd && !e.platform && e.brand && this.adSearch(t.urlInfo.host, t), this.domainInfo[t.urlInfo.mainhost] = !1, e.hotDeals ? (e.hotVersion > this.hotDeals.hotVersion && (this.hotDeals = {
route: "hot",
hotDeals: e.hotDeals,
hotVersion: e.hotVersion
}, this.storage.set("hotDeals", this.hotDeals).then()), this.getTabById(t.tab.id).then(i => {
i && r.sendMessage(t.tab.id, {
cmd: "rHot",
data: e
}).then()
}).catch(e => {
console.log(e.message)
})) : this.hotDeals.hotVersion && this.getTabById(t.tab.id).then(e => {
e && r.sendMessage(t.tab.id, {
cmd: "rHot",
data: this.hotDeals
}).then()
}).catch(e => {
console.log(e.message)
})), e.update && this.getTabById(t.tab.id).then(e => {
e && r.sendMessage(t.tab.id, {
cmd: "update"
}).then()
}).catch(e => {
console.log(e.message)
});
break;
case "hot":
e.hotVersion && (this.hotDeals = e, this.storage.set("hotDeals", e).then(), t && this.sendHot(t).then());
break;
case "search":
this.runtime().sendMessage(t.id, {
cmd: "rSearch",
data: e.data
}).then();
break;
case "g-search":
e.data.ad ? this.imageToBase64(e.data.ad.img).then(r => {
e.data.ad.img = r, this.tabs().sendMessage(t.tab.id, {
cmd: "gSearch",
data: e.data
}).then()
}) : this.tabs().sendMessage(t.tab.id, {
cmd: "gSearch",
data: e.data
}).then();
break;
case "installed":
this.setInstalled(e.token);
break;
case "user-info":
this.storage.set("user_info", e.data).then(), this.runtime().sendMessage(t.id, {
cmd: "userInfo",
data: e.data
}).then()
}
}async setExtInfo(e = !1) {
let t = this.domainInfo[e.urlInfo.mainhost];
if (!t) return !1;
t = await this.checkOpen(t, e), this.autoOpenTrack(t);
let r = this.browserAction(),
i = this.tabs();
r.setIcon({
path: {
19: v.default,
38: _.default
},
tabId: e.tab.id
}).then(), r.setBadgeBackgroundColor({
color: "#242c3c",
tabId: e.tab.id
}).then(), r.setBadgeText({
text: t.couponTotal + "",
tabId: e.tab.id
}).then(), t.noDisplayFirst = !1, t.noDisplay3h = !1, t.noDisplay1h = !1;
let n = Math.floor(new Date().getTime() / 1e3);
if (t.popupCanOpen) {
let r = parseInt(await this.storage.get("noDisplay3h_" + e.urlInfo.mainhost)),
i = parseInt(await this.storage.get("noDisplay1h_" + e.urlInfo.mainhost)),
o = parseInt(await this.storage.get("noDisplayFirst_" + e.urlInfo.mainhost));
(r || i || o) && (r > 0 && (t.noDisplay3h = n - r < 10800, t.noDisplay3h || this.storage.remove("noDisplay3h_" + e.urlInfo.mainhost).then()), i > 0 && (t.noDisplay1h = n - i < 86400, t.noDisplay1h || this.storage.remove("noDisplay1h_" + e.urlInfo.mainhost).then()), o > 0 && (t.noDisplayFirst = n - o < 86400, t.noDisplayFirst || this.storage.remove("noDisplayFirst_" + e.urlInfo.mainhost).then())), o || this.storage.set("noDisplayFirst_" + e.urlInfo.mainhost, n).then()
}
let o = parseInt(await this.storage.get("ballNoDisplay24h"));
if (o > 0) {
let e = n - o < 86400;
e ? t.ball_open = 0 : this.storage.remove("ballNoDisplay24h").then()
}
this.getTabById(e.tab.id).then(r => {
r && (i.sendMessage(e.tab.id, {
cmd: "rBase",
data: t
}).then(), this.pageAd && !t.platform && t.brand && this.adSearch(e.urlInfo.host, e))
}).catch(e => {
console.log(e.message)
})
}
async sendDomainAd(e) {
if (this.domainAd && 1 == this.domainAdList[e.urlInfo.mainhost]) {
let t = parseInt(await this.storage.get("ld_bottom_ad")),
r = parseInt(await this.storage.get("ld_bottom_ad_ball")),
i = Math.floor(new Date().getTime() / 1e3),
n = {
ad: !1,
ball: !1,
info: this.domainAd
};
(!t || t - i > 86400) && (n.ad = !0), (!r || r - i > 86400) && (n.ball = !0), this.domainAd.ballClose && (n.ball = !1), n.ad ? this.imageToBase64(n.info.img).then(t => {
t && (n.info.img = t, this.getTabById(e.tab.id).then(t => {
t && this.tabs().sendMessage(e.tab.id, {
cmd: "domainAd",
data: n
}).then()
}).catch(e => {
console.log(e.message)
}))
}) : this.getTabById(e.tab.id).then(t => {
t && this.tabs().sendMessage(e.tab.id, {
cmd: "domainAd",
data: n
}).then()
}).catch(e => {
console.log(e.message)
})
}
}
adSearch(e, t) {
if (this.pageAd) {
let r = (0, a.default).getNowDate();
this.storage.get("ad_display_none").then(i => {
if (i) {
let t = JSON.parse(i),
n = new Set(t[r]);
if (n.has(e)) return !1
}
this.tabs().sendMessage(t.tab.id, {
cmd: "adDisplay",
data: this.pageAd
}).then()
})
}
}Content script handlers for server-selected messages
eX = (e, n) => {
let r = [];
switch (e) {
case "rBase": {
if (eo = n, !n.coupons || !n.coupons[0]) break;
i(n.logo), j(n.coupons[0]), l = n.coupons[0], m(n.coupons[0].code), t(n.couponTotal), v(n.successRate), b(et[n.successRateAvg]), I(n.brand), f(n.worked), B(n.name), H(1 === parseInt(n.win_open)), J(1 === parseInt(n.ball_open)), ek(n.noDisplay3h);
let e = !(n.noDisplay3h || n.noDisplay1h || n.noDisplayFirst);
if (L(e), n.active) {
let e = {
...eC
};
n.active.content && (e.content.box = n.active.content.style?.box, e.content.img = n.active.content.style?.img), n.active.icon && (e.icon.box = n.active.icon.style?.box, e.icon.img = n.active.icon.style?.img), n.active.popup && (e.popup.box = n.active.popup.style.box, e.popup.img = n.active.popup.style.img), n.active.logo && (1 == n.active.logo ? (e.logo.in = er + P.default, e.logo.out = er + D.default) : n.active.logo.in && n.active.logo.out && (e.logo.in = n.active.logo.in, e.logo.out = n.active.logo.out)), eE(e)
}
n.codeTest && (n = eJ(n)), eb(n), o = n, n.popupCanOpen || (L(!1), J(!1));
break
}
case "gData":
eo || (eo = (0, d.default).sendCmd("hot", {})), r = eo;
break;
case "gSearch":
(0, p.default).setGCoupons(n.list, n.className), n.ad && (0, p.default).rightAd(n.ad);
break;
case "adDisplay":
(0, X.default).displayAd(n);
break;
case "domainAd":
eK(n);
break;
case "rHot":
eo = n;
break;
case "update":
alert("Lovedeals Extension Version is too old, please update!");
break;
case "codeTest":
e5();
return
}
return r
}static displayAd(e) {
if (this.isSetAd) return !1;
this.isSetAd = !0;
let t = document.getElementById("LdAd_456");
if (!t) {
let t = document.createElement("div");
t.id = "LdAd_456", t.style.width = "100%", t.style.zIndex = "2147483647", t.style.boxShadow = "0 1px 6px rgba(0, 0, 0, .2)", t.style.position = "fixed", t.style.bottom = "0", t.style.left = "0", t.style.cursor = "pointer", t.onclick = n => {
n.stopPropagation(), t.remove(), (0, l.default).sendCmd("domainAdClose", {
ad: !1
}), (0, l.default).sendCmd("openNewTab", {
url: e.url,
cid: 0,
type: 5,
transit: !0
})
};
let n = t.attachShadow({
mode: "closed"
}),
r = document.createElement("link");
r.href = u + i.default, r.type = "text/css", r.rel = "stylesheet";
let a = document.createElement("div");
a.className = "ld-ad-body";
let o = document.createElement("img");
o.style.width = "100%", o.src = e.img, a.appendChild(o), n.appendChild(r), n.appendChild(a), document.body.prepend(t)
}
}- lovedeals.ai
Receives the configuration request and returns route data that selects the extension's page messages.
Install Token Written as a Cookie on Four Vendor Domains
LoveDeals requests an install token from lovedeals.ai and writes it into a loveDeal_installed cookie on lovedeals.ai, lovepik.com, pikbest.com, and pngtree.com.
All four sites can recognize your browser as the same install; no disclosure.
You install or reload the LoveDeals extension with no local token yet stored.
The extension's background script runs its init routine on chrome.runtime.onInstalled.
The extension writes the same install token into a cookie on four separate websites.
It sets a loveDeal_installed cookie on lovedeals.ai, lovepik.com, pikbest.com, and pngtree.com, each carrying the identical token value.
setInstalled() and setInstallCookie() in the background service worker
setInstalled(e = "") {
e
? (this.storage.set("token", e).then(), this.setInstallCookie(e))
: this.storage.get("token").then((e) => {
e ? this.setInstallCookie(e) : this.request("make", 1);
});
}
setInstallCookie(e) {
let t = this.cookie();
for (let r of [
"https://lovepik.com",
"https://pikbest.com",
"https://pngtree.com",
"https://lovedeals.ai",
])
t.get({ url: r, name: "loveDeal_installed" }).then((i) => {
i ||
t.set({
url: r,
name: "loveDeal_installed",
domain: r.replace("https://", "."),
value: e,
}).then();
});
}| Field | Value | Why it matters | |
|---|---|---|---|
Cookie name | loveDeal_installed | The name used to store the install token in your browser's cookie jar for this domain. | |
Cookie value | a4f9c7e2-91b3-4d5a-8f6e-1c2d3e4f5a6b (illustrative) | The same install token returned by the extension's token-creation request, reused as a cross-domain identifier. | |
Cookie domain | .lovepik.com | The site the cookie is attached to; the extension repeats this write for all four vendor domains. |
- lovedeals.ai
Issues the install token via /deal/api/v1/make and receives the same cookie back.
- lovepik.com
Stock-content marketplace; receives the install token as a cookie for cross-site recognition.
- pikbest.com
Stock-content marketplace; receives the install token as a cookie for cross-site recognition.
- pngtree.com
Stock-content marketplace; receives the install token as a cookie for cross-site recognition.