Presets, credentials & advanced
Loader (Wrapper)
Engine
Dynamic Schema
Docs
Costs
Wrappers
Ad rotation
Loading cost dashboard…
⚠️ The cost dashboard couldn't load.
Loading wrappers dashboard…
⚠️ The wrappers dashboard couldn't load.
🔀 Ad rotation — cfg/<id>.js Edits the rotation config for the id in the ☁️ Cloudflare bar above, with the same Worker URL and admin token. Load reads the current config; Save writes cfg/<id>.js to R2 and purges its edge copy — it does NOT re-publish the wrapper. Every destination must be an absolute http(s) URL with no #. Saving points AD TARGET at the cfg URL, so Publish then enables rotation; for a single ad, put a normal URL in AD TARGET.
total: 0%
General targetingDevice and VPN apply to every ad. Countries are ISO-2 codes, comma-separated: allow shows the ad ONLY in those countries (empty = all countries), block hides it there, and block always wins over allow. Each URL card can override all of it under “Who sees it”.
Applies to all ads
Seeds every URL card; countries are ISO-2 and block wins over allow.
Rotation URLs
Weights are shares of the total; each card’s bottom rail is its share.
📄 cfg/<id>.js — file structure & device/country targeting
Authored here, stored on R2 as cfg/<id>.js, served from cfg.analyytics.com. Save writes it and purges the edge copy — the wrapper embedded on your sites is NOT re-published; it re-reads this file on every load. So you change ads / weights / device / countries remotely, zero re-embed.
window.__ADCFG = {
"mode": "weighted", // "weighted" (share % by w) | "roundrobin" (ordered per visitor)
"device": "all", // GENERAL device — every ad: all | mobile | desktop
"countries": { // GENERAL country filter — every ad (ISO-2 codes)
"allow": [], // show ONLY in these (empty = all countries)
"block": [] // hide in these (block ALWAYS wins over allow)
},
"urls": [
{
"u": "https://ad1.example/land", // destination (absolute http(s), no '#')
"w": 3, // weight / share % (weighted mode)
"subid": "abc", // optional — appended as ?subid=
"device": "all", // PER-AD device
"countries": { "allow": [], "block": [] } // PER-AD country filter
},
{
"u": "https://ad3.example/land",
"w": 1,
"device": "mobile", // ad 3: mobile only …
"countries": { "allow": [], "block": ["US"] } // … and everywhere EXCEPT US
}
]
};
How an ad is chosen for a visitor: an ad is eligible only if it passes BOTH the GENERAL filter and its own PER-AD filter (device + country). Then weighted/roundrobin picks among the eligible ads. If none is eligible → no pop fires (this is also the global gate).
- allow = show ONLY in those countries (empty = all). block = hide there. Block wins (allow
US+ blockUS→ hidden in US). - device:
mobile/desktop/all— from the visitor User-Agent. - Country comes from the Worker
/geo(request.cf.country), cached ~12h in the visitor's browser (~1 request per visitor). Geo unavailable → ad is shown (fail-open). - Codes are ISO-2 uppercase:
US, GB, DE, FR…(comma-separated in the fields above).
API reference — the methods, globals and settings you can use in code, with examples. The Loader/Engine tabs above set these same options through the form; this tab is the detailed manual so you don't have to remember what each thing does.
API reference — Engine & Wrapper methods, globals & settings
⚡ Engine — window.BetterJsPop API (16)
BetterJsPop.version propertyversion
Read-only string property holding the engine's semantic version. Defined literally as the first key/value pair when the public API object is built ("version", "3.0.28"). Use it for diagnostics, logging, feature-gating, or to confirm which build of the popunder engine is loaded on the page. It is a plain data property (not a getter), so reading it has no side effects.
Returns:
string — The version string, e.g. "3.0.28".Read the engine version
console.log(window.BetterJsPop.version); // "3.0.28"
Plain string; safe to read at any time.
Source: publicApi = arrayToObject(["version", "3.0.28", ...]). Not chainable.
.config(options) => instance methodconfig
Applies global engine settings by merging the supplied options object into the internal config store, then returns the library instance so calls can be chained. For each key/value pair it normalizes a few friendly aliases before writing: "delay" is converted to the internal "interval" key and its value is divided by 1000 and rounded (so you pass milliseconds, the engine stores seconds); "perpage" is mapped to "perPage"; "coverScrollbar" is mapped to "allowScrollbar"; and "popFallbackOptions" is mapped to "fallback". All other keys are written under their canonical name. The internal setter only accepts keys that already exist in the default config (unknown keys are silently ignored), coerces values for numeric settings (NaN becomes 0 via parseFloat), and for the bindTo/ignoreTo keys runs the value through element collection. Use it once at startup to configure prefixing, throttling, per-page caps, referrer/opener policy, blank-anchor handling, etc. Side effects: mutates the shared config store used by the click handler and storage key prefixing.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
options | object | An object whose keys are configuration settings. Recognized canonical keys (with defaults): debug (boolean, default false) - when true, every Logger.log entry is also echoed to console.log. prefix (string, default "BetterJsPop") - namespace prepended to all localStorage and cookie keys. interval (number seconds, default 0) - minimum time between two popups; you may instead pass delay in milliseconds which is auto-converted. perPage (number, default -1; alias "perpage") - max popups per page view, -1 means unlimited. bindTo (array|selector, default null) - restrict triggering to these elements/selectors. ignoreTo (array|selector, default null) - never trigger inside these elements/selectors. noOpener (boolean, default true) - set window opener policy for opened windows. noReferrer (boolean, default false) - strip the referrer when opening. mobileSensitive (number px, default 15) - touch move tolerance before a tap is treated as a tap. allowScrollbar (boolean, default true; alias "coverScrollbar") - whether clicks on the scrollbar gutter are allowed to trigger. allowPopUnderTrick (boolean, default true) - enables the pop-under focus/blur trick. blankAnchor (string, default "TAB_UNDER") - how to treat clicks on target="_blank" anchors; one of "IGNORE", "TAB_UNDER", "PREVENT". fallback (object, default {tab:true, under:true}; alias "popFallbackOptions") - default open mode used when a stack entry has no explicit fallback. |
Returns:
object — The BetterJsPop instance (chainable).Configure then add a popunder
BetterJsPop
.config({
prefix: 'myAds',
delay: 30000, // ms; stored internally as 30 (seconds)
perPage: 2,
noReferrer: true,
blankAnchor: 'PREVENT',
debug: true
})
.add('https://example.com/lander');delay is in milliseconds and auto-converted to the internal seconds interval.
Disable scrollbar-gutter triggering
BetterJsPop.config({ allowScrollbar: false, allowPopUnderTrick: true });Unknown/unsupported keys are ignored silently.
Aliases handled in source: delay->interval(/1000 rounded), perpage->perPage, coverScrollbar->allowScrollbar, popFallbackOptions->fallback. Chainable (returns this).
.add(url, options) => instance methodadd
Registers a popunder/popup destination (a 'pop' entry) onto the internal pop stack and returns the instance for chaining. It seeds a new entry with defaults name = "pop_<currentStackLength>", under = true, tab = true, and url set to the first argument. It then copies recognized keys from the options object onto the entry. Two friendly aliases are normalized: "newTab" maps to the internal "tab" key and "cookieExpires" maps to the internal "expires" key. Only keys present in the engine's valid option list (derived from every internal pop-option name) are accepted; anything else is ignored. Each click that passes all gating rules consumes the next entry in the stack (round-robin via an internal counter). Side effects: pushes onto popStack (which getStack returns and emptyStack clears); the stack drives what handleClickEvent opens.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
url | string | function | yes | Destination URL, or a function returning a URL (resolved lazily at open time). Stored under the entry's "url" key. | |
options | object | Per-pop overrides. Recognized keys: url (string|function) - override destination. under (boolean) - open as pop-under (default true). tab (boolean; alias "newTab") - open in a new tab vs new window (default true). name (string) - unique entry name / cookie key (defaults to pop_<index>). expires (number seconds; alias "cookieExpires") - cooldown/cookie lifetime before this entry can fire again. width (number) - window width. height (number) - window height. top (number) - window top position. left (number) - window left position. noOpener (boolean) - per-pop opener policy override. noReferrer (boolean) - per-pop referrer policy override. device (string) - restrict to "mobile" or "desktop"; non-matching devices skip this entry. shouldFire (function(entry, lastEvent, clickedElement) -> boolean) - predicate gate; return falsey to skip this entry on a given click. beforeOpen (function(entry)) - callback invoked just before the window is opened. afterOpen (function(entry, openedWindow)) - callback invoked right after opening. tabUnderUrl (string) - URL navigated to in the current tab for the tab-under variant. |
Returns:
object — The BetterJsPop instance (chainable).Add a desktop-only popunder with callbacks
BetterJsPop.add('https://example.com/offer', {
newTab: false, // alias for tab
under: true,
cookieExpires: 3600, // alias for expires (seconds)
device: 'desktop',
shouldFire: function(entry, ev, el) { return el.tagName !== 'INPUT'; },
beforeOpen: function(entry) { console.log('opening', entry.url); },
afterOpen: function(entry, win) { console.log('opened', win); }
});newTab/cookieExpires are aliases for tab/expires; unknown keys are dropped.
Rotate between two destinations
BetterJsPop
.add('https://a.example/lander1')
.add(function() { return 'https://b.example/lander2?ts=' + Date.now(); });url may be a function resolved at open time; entries are consumed round-robin.
Source: popEntry seeded with url/name/under(true)/tab(true); aliases newTab->tab, cookieExpires->expires; only validOptionKeys accepted. Chainable.
.bindTo(...targets) => instance methodbindTo
Restricts popup triggering to only the supplied elements or CSS selectors. Accepts either a single array of targets as the first argument or a variadic list of targets, then writes them to the internal bindTo config (collecting them into the live bindToList of resolved elements). When a bindTo list is active, a click only triggers a pop if it occurs inside one of the bound elements; clicks elsewhere are ignored. Returns the instance for chaining. Side effects: appends to the bindTo config/list (it concatenates rather than replacing on repeated calls).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
targets | string | Element | Array<string|Element> | yes | One or more CSS selector strings and/or DOM elements, passed either as multiple arguments or as a single array. Passing false/null clears the collected list. |
Returns:
object — The BetterJsPop instance (chainable).Only trigger inside specific zones
BetterJsPop.bindTo('#content', '.ad-zone');Variadic selectors; clicks outside #content/.ad-zone won't pop.
Mix elements and selectors via array
BetterJsPop.bindTo([document.getElementById('player'), '.thumbnail']);Equivalent to passing them as separate arguments.
Source: config['H'](configKeyMap['l'], isArray(args[0]) ? args[0] : args); returns this. Pairs with getBindTo().
.ignoreTo(...targets) => instance methodignoreTo
Defines elements or selectors inside which popups must never trigger. Accepts a single array or a variadic list of targets and writes them to the internal ignoreTo config (collected into the live ignoreToList). When no bindTo list is set, a click is allowed to pop unless it falls inside an ignored element. If bindTo is also configured, bindTo takes precedence (the allow-check returns based on bindTo first). Returns the instance for chaining. Side effects: appends to the ignoreTo config/list, concatenating on repeated calls.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
targets | string | Element | Array<string|Element> | yes | One or more CSS selectors and/or DOM elements (as multiple args or a single array). Passing false/null clears the collected list. |
Returns:
object — The BetterJsPop instance (chainable).Never pop on real navigation or the login form
BetterJsPop.ignoreTo('a.real-link', '#login-form');Clicks inside these are exempted from triggering.
Exempt elements by class via array
BetterJsPop.ignoreTo(['.no-pop']);
bindTo, if set, is evaluated before ignoreTo.
Source: config['H'](configKeyMap['v'], ...); returns this. Pairs with getIgnoreTo().
.getBindTo() => Array methodgetBindTo
Returns the live internal array of resolved bindTo elements (the bindToList that the engine actually checks at click time). Use it to inspect which elements are currently bound after one or more bindTo() calls, or to debug why pops are/aren't firing. The array is the engine's internal reference (not a copy), so mutating it affects engine behavior; treat it as read-only.
Returns:
Array — The internal list of bound elements.Inspect bound elements
BetterJsPop.bindTo('.ad-zone');
console.log(BetterJsPop.getBindTo().length);Returns the live internal array; do not mutate.
Source: function getBindTo(){ return bindToList; }. Not chainable.
.getIgnoreTo() => Array methodgetIgnoreTo
Returns the live internal array of resolved ignoreTo elements (the ignoreToList consulted at click time). Useful for inspecting/debugging which elements are currently exempted from triggering popups. Returns the engine's internal reference, so treat the returned array as read-only.
Returns:
Array — The internal list of ignored elements.Inspect ignored elements
BetterJsPop.ignoreTo('#login-form');
console.log(BetterJsPop.getIgnoreTo());Returns the live internal array; do not mutate.
Source: function getIgnoreTo(){ return ignoreToList; }. Not chainable.
.getConfig() => object methodgetConfig
Returns the full internal configuration store object (config.X) containing all current settings keyed by their canonical internal names (debug, prefix, interval, perPage, bindTo, ignoreTo, fallback, noOpener, noReferrer, mobileSensitive, allowScrollbar, allowPopUnderTrick, blankAnchor). Use it to read the effective configuration after calling config()/bindTo()/ignoreTo(). It returns the live object (not a clone), so treat it as read-only; modify settings through config() rather than mutating this object.
Returns:
object — The live config store (config.X) with canonical setting keys.Read effective config
BetterJsPop.config({ perPage: 3 });
var cfg = BetterJsPop.getConfig();
console.log(cfg.perPage, cfg.prefix, cfg.interval);Keys are canonical internal names; values reflect normalization (e.g. interval in seconds).
Source: function getConfig(){ return config['X']; }. Not chainable.
.getStack() => Array methodgetStack
Returns the live internal pop stack: the array of pop entries registered via add(). Each entry is an object using canonical keys (url, name, under, tab, expires, device, shouldFire, beforeOpen, afterOpen, etc.). Use it to inspect what destinations are queued, how many entries exist, or to debug round-robin rotation. Returns the engine's internal reference; treat as read-only and use add()/emptyStack() to modify it.
Returns:
Array — The internal popStack array of pop-entry objects.Inspect the pop stack
BetterJsPop.add('https://example.com/a').add('https://example.com/b');
console.log(BetterJsPop.getStack().length); // 2
console.log(BetterJsPop.getStack()[0].name); // "pop_0"Entry names default to pop_<index>.
Source: function getStack(){ return popStack; }. Not chainable.
.emptyStack() => instance methodemptyStack
Clears all registered pop entries by replacing the internal pop stack with a fresh empty array, then returns the instance for chaining. Use it to remove all previously added destinations before registering a new set (e.g. when reconfiguring on a single-page-app route change). Note: unlike reset(), it does NOT clear cookies, the per-page counter, or the lastOpenedAt timestamp; it only empties the queue of destinations.
Returns:
object — The BetterJsPop instance (chainable).Replace all destinations
BetterJsPop.emptyStack().add('https://example.com/new-offer');Empties the stack but leaves cooldown cookies/counters intact.
Source: function emptyStack(){ popStack = []; return this; }. Chainable.
.reset() => instance methodreset
Resets the engine's runtime/frequency state and returns the instance for chaining. It iterates the current pop stack and deletes each entry's cooldown cookie (by the entry's name), resets the in-memory per-page visit counter to 0, and removes the persisted 'pageCounter' and 'lastOpenedAt' storage keys. Use it to clear frequency-capping / cooldown state so popups can fire again as if on a fresh page/session. It does NOT remove the registered pop entries themselves (use emptyStack() for that); it only clears the throttling/cap state tied to them.
Returns:
object — The BetterJsPop instance (chainable).Clear frequency caps and cooldowns
BetterJsPop.reset(); // clears per-page count, lastOpenedAt, and per-entry cooldown cookies
Keeps registered destinations; pair with emptyStack() to also drop entries.
Reset then reconfigure
BetterJsPop.reset().config({ perPage: 1 });Chainable.
Source: deletes cookie per popStack entry name, pageVisitCount=0, storage.K('pageCounter'/'lastOpenedAt'); returns this. Chainable.
BetterJsPop.Browser propertyBrowser
A namespace object exposing detected browser/platform information, computed once at init from the user agent. Boolean flags: isLinux, isWin, isMac, isIOS, isAndroid, isMobile, isWebkit, isMozilla, isEdge, isChrome, isIE, isFirefox, isOpera, isSafari. String/number values: version (parsed integer major version), longVersion (full dotted version string). Method: versionCompare(other) - compares the detected version against a supplied value (bound to the internal browser-info object). Use these for conditional logic such as enabling certain pop strategies only on desktop or specific browsers. Read-only data; do not mutate.
Returns:
object — Object of boolean flags, version/longVersion, and versionCompare().Branch on platform
if (BetterJsPop.Browser.isMobile && BetterJsPop.Browser.isIOS) {
BetterJsPop.add('https://example.com/ios-offer', { device: 'mobile' });
}isMobile forces tab-style opening internally regardless of the tab option.
Read browser identity
console.log(BetterJsPop.Browser.isChrome, BetterJsPop.Browser.version, BetterJsPop.Browser.longVersion);
version is the integer major; longVersion is the full string.
Source: 'Browser', ['isLinux',...,'versionCompare', bindFn(browserInfo['bn'], browserInfo)]. Sub-property object.
BetterJsPop.Logger propertyLogger
A namespace object exposing the engine's logging helpers, each bound to the internal logger. Methods: log(...) - appends a log entry to the internal log buffer and, when config debug is true, also forwards it to the browser console.log. print() - outputs/prints the accumulated log buffer. Use Logger.log to add custom diagnostic entries that participate in the engine's debug output, and Logger.print to dump them.
Returns:
object — Object with log() and print() functions.Custom debug logging
BetterJsPop.config({ debug: true });
BetterJsPop.Logger.log('initialised popunder');
BetterJsPop.Logger.print();log() only mirrors to console when debug is enabled.
Source: 'Logger', ['log', bindFn(logger['hn'], logger), 'print', bindFn(logger['gn'], logger)]. Sub-property object.
BetterJsPop.Cookie propertyCookie
A namespace object exposing the engine's prefixed cookie helpers (bound to the internal cookie manager). All keys are automatically namespaced with the configured prefix (default "BetterJsPop"). Methods: set(name, value, expires, attributes) - writes a cookie; when expires is a number it is treated as seconds-from-now, otherwise it may be a UTC string, and attributes are merged over the engine's cookie defaults. get(name) - reads a cookie value. remove(name) - deletes a cookie. Use it to share frequency-capping or state cookies with the same namespacing the engine uses.
Returns:
object — Object with set(), get(), remove() functions.Prefixed cookie access
BetterJsPop.Cookie.set('seen', 1, 3600); // expires in 3600 seconds
var v = BetterJsPop.Cookie.get('seen');
BetterJsPop.Cookie.remove('seen');Numeric expires is seconds-from-now; keys are auto-prefixed.
Source: 'Cookie', ['set', bindFn(cookieManager['H'], ...), 'get', ..., 'remove', ...]. Sub-property object.
BetterJsPop.Storage propertyStorage
A namespace object exposing the engine's prefixed persistent storage helpers (bound to the internal storage adapter, which uses localStorage when available and silently falls back to an in-memory store otherwise). All keys are namespaced with the configured prefix. Methods: set(key, value) - JSON-stringifies and stores a value, returning true/false on success/failure. get(key) - reads and JSON-parses a value (returns null on parse error). remove(key) - deletes a key. clear() - clears the underlying store. Use it for persisting custom state alongside the engine's own counters with consistent namespacing.
Returns:
object — Object with set(), get(), remove(), clear() functions.Prefixed persistent storage
BetterJsPop.Storage.set('lastCampaign', { id: 42, ts: Date.now() });
var c = BetterJsPop.Storage.get('lastCampaign');
BetterJsPop.Storage.remove('lastCampaign');Values are JSON-serialized; falls back to memory if localStorage is unavailable.
Clear storage
BetterJsPop.Storage.clear(); // wipe the underlying store
clear() empties the whole backing store.
Source: 'Storage', ['set','get','remove','clear'] each bindFn(storage[...], storage). Sub-property object.
BetterJsPop.object (Array, set externally) / internally read as publicApi['object'] and publicApi['href'] propertyobject
License / integrity-payload handshake slot on the public API object. The engine itself does NOT set this; it is assigned by the separate license IIFE appended at the bottom of the same file (lines 1728-1740): popInstance.object = [licensedDomains, <hash-vector>, expiryDate, <hash-vector>, document.currentScript]. The engine's internal integrity routine processPopQueue() (invoked via runIntegrityCheck on a staggered timer) reads it back as publicApi['object'] (and publicApi['href']) and shifts the array apart: element[0]=allowed-domain list, [1]=verifyStringHashes vector for the script-source domain, [2]=expiry date for checkLicenseExpiry-style date comparison, [3]=verifyIntegrity numeric vector for keyword/category checks, [4]=the currentScript node. If any hash/domain/date check mismatches, checkAndFlagChange sets state.Rn=true and replaces windowOpen with noop, disabling all popups. In effect, .object is the contract by which an external license file licenses (or revokes) the engine. publicApi['href'] is the parallel slot read for the optional href allowlist vector (left empty by the bundled license file).
Returns:
Array — When set: [domainList, domainHashVector, expiryDateArray, keywordHashVector, currentScriptNode]. Undefined if the license file did not load before the engine (the license IIFE then logs 'Script must be loaded before license file').How the bundled license file populates it
var licensedDomains = ["tasty-permission.com", "@network", ".local", "127.0.0.1", "localhost"],
expiryDate = [2026, 6, 7];
window.BetterJsPop.object = [
licensedDomains,
[123, 195, 102, /* ...hash vector... */ 2862],
expiryDate,
[125, 121, 179, /* ...hash vector... */ 12163],
document.currentScript
];Verbatim shape from the license IIFE (lines 1731-1736). The engine reads it back inside processPopQueue (lines 1630-1657).
This is a security/anti-tamper handshake, not a user-facing API: legitimate integrators do not write to it themselves. The '@network' token in the domain list is honored specially by domainMatchesList (line 1485). If .object is absent the integrity check simply leaves state.Rn as its default and popups still run; the kill-switch only triggers on an actual mismatch.
📦 Wrapper — methods, globals & configuration (24)
(function(global){ ... config_2.init(global); })(POP_CONFIG) — equivalently the snippet form (function(eqltv){ ...; s.settings = eqltv ... })({}) globalLoader IIFE / settings injection (POP_CONFIG)
This file is the de-obfuscated loader/wrapper that boots the popunder engine. It is a top-level self-invoking IIFE (line 12) that defines three top-level constants — URLS, POP_CONFIG, DC_CONFIG — and then invokes two inner self-invoking modules. The FIRST inner module is the pop engine boot: (function(global){ 'use strict'; var config_2 = {...}; config_2.init(global); })(POP_CONFIG) (lines 73-1300). The argument you pass in (called `global` internally, the `eqltv` of the prompt's snippet) IS the runtime settings object — it is forwarded to config_2.init(initConfig), which calls mergeSettings(initConfig). In mergeSettings (lines 195-205) your object is deep-merged ON TOP of config_2.defaults (lines 78-182) and also on top of any external settings discovered from a <script src*=scriptSrc> element's `.settings` property or from window.__htapop. The merged result is stored at config_2.settings. So configuring the popunder = supplying this settings object (in this file it is the literal POP_CONFIG, lines 21-68). After merge, init() runs addPostfixToCounters(), and if settings.autobind is truthy (default true) runs init3rdp() which actually loads the engine and arms the popunder; then addEmbeds() injects any add.pixels / add.scripts. Note: the engine only arms if window[settings.popns] is not already set (init3rdp guard, line 297), giving idempotency.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
global / eqltv (the settings object) | object | {} (here the POP_CONFIG literal) | Runtime overrides deep-merged over config_2.defaults. May contain any of the documented top-level keys: script, scriptSrc, barrier, popns, zh, revert, autobind, priorBjs, otherBjs, window, freq, misc, counters, counterPostfixes, names, callbacks, add, elements, otherFormats, delayPop, pageGroup, onlyClickable, hash, bdVar. Missing keys fall back to defaults. |
Returns:
void — No return value. Effect: populates config_2.settings, installs window globals, loads the engine script, and arms the popunder handler.Boot with an inline settings object
(function(global){ /* engine wrapper as in file */ })({
script: "tasty-permission.final.js",
popns: "ecc874",
window: { url: "https://barren-theory.com/landing" },
freq: { qty: 3, period: 10800, scheme: "time" },
misc: { newTab: true, under: false, coverIframe: true }
});This mirrors how POP_CONFIG is passed in the file. The object is merged over defaults, then init3rdp arms the popunder.
External settings via script tag (no inline object)
<script src="/cjD/9.6nbc2/..."></script>
<script>window.__htapop = { window:{ url:"https://barren-theory.com/x" }, freq:{ qty:1 } };</script>mergeSettings reads <script src*=scriptSrc>.settings first, else window.__htapop. The window.url found externally always wins (line 199).
window[POP_CONFIG.popns] // e.g. window["ecc874"] globalwindow[settings.popns] — engine handshake slot
The single most important runtime global. In init3rdp (line 301) the wrapper writes a freshly generated 6-char random string (namespaceKey) into window[settings.popns || 'popns']. It also serves as a re-entry guard at the top of init3rdp (line 297): if window[settings.popns] is already truthy the engine will NOT arm again. After loading the engine script, a 5ms polling interval (lines 301-348) waits for the LOADED ENGINE to OVERWRITE window[namespaceKey] with the engine's own popunder controller object (the object exposing .config(...).add(url, opts) and Browser feature flags). Once that object appears, the interval clears and the wrapper calls browserInfo.config(stackConfig).add(self.getPopUrl(), popOptions) on it to register the popunder. So: developers SET settings.popns to choose the slot name; the engine and wrapper use window[that name] as the shared handshake/controller. In this file popns = 'ecc874' (line 60), so the live controller lands at window.ecc874.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
popns (settings key) | string | "popns" (fallback if settings.popns is falsy) | Name of the window property used both as the boot guard and as the slot the engine populates with its controller object. Set in POP_CONFIG as "ecc874". |
Returns:
string then object — Initially a random 6-char string written by the wrapper; later replaced by the engine's controller object exposing .config() and .add().Observe the handshake completing
var slot = 'ecc874';
var t = setInterval(function(){
if (window[slot] && typeof window[slot] === 'object' && window[slot].config) {
clearInterval(t);
console.log('engine ready', window[slot].Browser);
}
}, 50);While booting, window.ecc874 is a string; once the engine loads it becomes the controller object with .config/.add and a Browser flag object.
Prevent the engine from arming
window.ecc874 = true; // set BEFORE the loader runs // init3rdp() early-returns because window[settings.popns] is truthy
The guard at line 297 makes the loader idempotent / suppressible via the popns slot.
window.ppuDisableTrigger // boolean globalwindow.ppuDisableTrigger — prior-popunder kill switch
Installed by the prior() method (decoded from the reversed literal 'reggirTelbasiDupp', line 288). It is the master gate for the 'prior' behavior — the logic that hijacks OTHER popunder libraries on the page (discovered via getOtherBjs) so they defer to this engine. enablePrior() sets window.ppuDisableTrigger = true (line 285) and patches other libs' shouldFire hooks; disablePrior() sets it falsy (line 282). The custom shouldFire installed on other libraries (customShouldFire, lines 259-262) returns false when the body 'prior' flag is set, effectively letting THIS engine win. prior() only runs when settings.priorBjs is true and the current URL is not a stop URL (line 301). It re-runs every 5s and on each pop (open callback). Read this global to know whether prior-suppression is currently active.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
priorBjs (settings key gating this) | boolean | true | When true (and URL not stopping), prior() runs, installs window.ppuDisableTrigger and patches competing popunder libs. |
Returns:
boolean — true while prior suppression is enabled (scheme not yet reached); falsy/undefined once the frequency scheme is reached (disablePrior).Check whether prior suppression is active
if (window.ppuDisableTrigger) console.log('competing popunders are being suppressed');Set true by enablePrior, cleared by disablePrior when isSchemeReached() returns true.
window._storage([key[, value]]) globalwindow._storage — localStorage helper factory
A singleton localStorage wrapper factory installed on window during utils._storage IIFE (assigned at line 1071: window._storage = storageFactory). Calling it with zero args returns the storage instance (exposing .subscribe(key, cb), .get, .set); with one arg returns the parsed stored value for that key (.get); with two args stores the value (typed serialization, returns the value). It auto type-tags values using a `:t:` prefix (0=Null, n=Number, b=Boolean, d=Date, f=Function via Function()) else JSON. It also wires a cross-tab 'storage' event dispatcher so .subscribe callbacks fire on changes from other tabs (used by findMultipleTabs for the callRequest/callResponse multi-tab handshake). All the wrapper's frequency counters are persisted through this. Developers can use it to read or subscribe to the engine's counters.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
key | string | — | Storage key. One-arg call = get; with value = set. | |
value | any | — | Value to store; type-tagged on serialization. |
Returns:
instance | any | value — 0 args: the storage singleton (with .subscribe/.get/.set). 1 arg: parsed value. 2 args: the value passed.Read a counter the engine persisted
var domainCounter = window._storage('kadPD28bf6d3f52c38a9e7ea8c34a508c32df');
console.log(domainCounter);Counter storage keys are the configured counter name PLUS the postfix (settings.zh or counterPostfixes), appended by addPostfixToCounters (line 678).
Subscribe to cross-tab messages
window._storage().subscribe('callResponse', function(val, ev){ console.log('another tab popped', val); });Same subscription mechanism findMultipleTabs uses for session-scoped multi-tab dedupe.
document.body.psnosp // read via utils.getParamFromBody('psonsp') globaldocument.body psnosp marker (cneckLoadBjs handshake)
A body-level handshake flag the wrapper writes via utils.setParamToBody('psonsp', value) which reverses the name to set document.body.psnosp (setParamToBody, line 942-944). cneckLoadBjs (lines 932-941) fires an XHR GET against settings.script; on HTTP 2xx it sets the flag to 'f' (engine reachable), on any error/timeout it sets it to settings.popns (e.g. 'ecc874'). This lets the loaded engine detect whether its own script URL was fetchable / whether it is in a blocked state. Read document.body.psnosp to observe this. Counterpart getParamFromBody reverses a name to read a body property.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
settings.script (URL probed) | string | yes | URLS.tastyEngine = "tasty-permission.final.js" | The engine script URL that cneckLoadBjs XHR-probes to decide the psnosp value. |
Returns:
string — 'f' when the engine URL returns 2xx; otherwise the popns value (e.g. 'ecc874').Inspect the engine-reachability marker
console.log(document.body.psnosp); // 'f' = reachable, 'ecc874' = error/timeout
Set asynchronously by the cneckLoadBjs XHR before the engine script is appended.
document.body.bоdy-flag // set by changePriorClass(enabled) globaldocument.body prior flag (changePriorClass marker)
changePriorClass(enabled) (lines 236-238) toggles a reversed-name boolean property on document.body used by the prior() machinery. customShouldFire (line 260) reads it: when set, competing popunder libraries' shouldFire returns false so this engine wins. enablePrior sets it true; disablePrior sets it falsy. Not normally set by developers; documented here because it is an observable side effect on the body that drives prior-suppression.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
enabled | boolean | yes | — | Truthy enables suppression of other popunder libs; falsy disables it. |
Returns:
void — Sets/clears a boolean flag property on document.body.Behavioral note
// internal: enablePrior() -> changePriorClass(true); disablePrior() -> changePriorClass(false)
Drives whether patched competing libraries fire; not a documented public setter.
settings.script: string; settings.scriptSrc: string settingsettings.script / settings.scriptSrc / URLS.tastyEngine
Engine/script group. `script` is the URL of the popunder ENGINE that gets loaded (utils.addScript at line 301; default null). In this file it is URLS.tastyEngine = 'tasty-permission.final.js' (a LOCAL file, replacing the original www.tasty-permission.com/ecc874/9ae4658c6426.js per the header comment). `scriptSrc` is a URL FRAGMENT used by mergeSettings to LOCATE the loader's own <script> tag via document.querySelector('script[src*="'+scriptSrc+'"]') and read external `.settings` off it (line 198). cneckLoadBjs XHR-probes `script` to set the body psnosp marker.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
script | string (URL) | null (set to URLS.tastyEngine here) | Engine script URL appended to the page; also XHR-probed by cneckLoadBjs. | |
scriptSrc | string (URL substring) | (none in defaults; set in POP_CONFIG) | Substring used to find this loader's own <script> element and read its external .settings. |
Returns:
n/a — Configuration only.Point at the engine and locate the loader tag
{ script: 'tasty-permission.final.js', scriptSrc: '/cjD/9.6nbc2/5alyS/...' }If a <script> whose src contains scriptSrc exposes a .settings object, those settings are merged before your inline object.
settings.barrier: string (URL) | null settingsettings.barrier
Anti-adblock barrier group. URL of a fallback 'barrier' script loaded when adblock/anti-pop tampering is detected. checkBarrier() (lines 350-504) runs only if settings.barrier is set; it executes a battery of detection probes (detectBarrier / barrierChecks 1,2,3,5,6,7,8,9): bait ad-like DIVs with ids/classes like googlead, ad-wrapper, gpt video ids; checks for blocked window globals (snpop, sdApoP, ExoLoader, Fingerprint2, app_vars), patched window.open (pbWindowOpen), GA hitCallback, eval traps, and a MutationObserver watching injected <style> hiding the bait. On a positive detection it calls loadBarrierScript(token) which appends settings.barrier with a ?b=<token> (or &b=) query param (line 356). Runs once immediately and re-checks on DOMContentLoaded and every 2s until fired.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
barrier | string (URL) | null | Barrier script URL; when set, enables adblock-detection and conditional barrier load. In POP_CONFIG it is a long obfuscated barren-theory.com URL. |
Returns:
void — On detection, appends the barrier script with a numeric detection-id token appended as b=.Enable the barrier
{ barrier: 'https://barren-theory.com/Y.m_xQvRYS2Tt-...' }Leave null/absent to disable all barrier detection (checkBarrier early-returns).
settings.window: { url, fullscreen, w, h, type, chromePopunder } settingsettings.window
Window group — defines the popunder target and window characteristics. The crucial key is `url`: the destination opened in the pop, consumed by getPopUrl() (lines 206-221) which appends tracking query params (schemeSeq, dailySeq, windowType=popscript_<type>, optional iabc=1 on mobile-Java UAs, ce/cel for thumbnail vs text-link clicks) plus any add.urlParamsObj entries. `type` becomes part of windowType param and gates tab/under behavior (only non-'popup' types use misc.newTab/under, line 317). w/h/fullscreen/chromePopunder are window sizing/mode hints. Note: an external <script>.settings window.url overrides yours (line 199).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
window.url | string (URL) | yes | null | Popunder destination URL. Required for a pop to open. In POP_CONFIG it is a long obfuscated barren-theory.com URL. |
window.type | string | "popunder" | Window type; when not 'popup', misc.newTab/under apply. Included in the windowType query param as popscript_<type>. | |
window.fullscreen | boolean | false | Fullscreen hint passed to the engine window options. | |
window.w | number | 1001 | Window width hint. | |
window.h | number | 800 | Window height hint. | |
window.chromePopunder | boolean | false | Chrome-specific popunder mode hint. |
Returns:
n/a — Configuration only; window.url is read by getPopUrl().Set the pop destination
{ window: { url: 'https://barren-theory.com/offer', type: 'popunder', w: 1001, h: 800 } }getPopUrl appends &sseq=, &dseq=, &rsrc=popscript_popunder and click-type params automatically.
settings.freq: { qty, period, distances, distance, scheme, context, session, sessionKeepAliveTime, sessionExpiration, hashed, pagelim, max } settingsettings.freq
Frequency / capping group — the core throttling logic, evaluated by isSchemeReached() (line 567) which combines isMaxReached, isPagelimReached, isQtyReached, isBlockedByDistance. `context` selects which counter (domain/page/iframe-page) governs counting and hashing. `qty` caps pops per context window. `period` is the counter expiry in seconds. `max` is an absolute domain-total cap. `pagelim` caps per page/iframe-page. `scheme` ('time' or 'clicks') chooses the spacing rule used by isBlockedByDistance with `distance` (single gap) or `distances` (comma list consumed per pop index). `session`/`sessionExpiration`/`sessionKeepAliveTime` drive session reset logic (runSessionOption, checkSession) including multi-tab detection. `hashed` includes the URL hash in the page-hash key (getHash).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
freq.qty | number | 2 | Max pops within the active context window before isQtyReached blocks. POP_CONFIG sets 3. | |
freq.period | number (seconds) | 86400 | Counter expiry / scheme-seq period. POP_CONFIG sets 10800 (3h). | |
freq.scheme | "time" | "clicks" | "time" | Spacing scheme for isBlockedByDistance. | |
freq.distance | number | 0 | Minimum gap (seconds for time / clicks for clicks) between pops after the first. | |
freq.distances | string (csv) | null | null | Per-index gap list (e.g. "15,15,15"); index = current page counter, falls back to last value. POP_CONFIG sets "15,15,15". | |
freq.context | "domain" | "page" | "iframe-page" | "domain" | Counting/hashing context. POP_CONFIG sets "page". | |
freq.max | number | 0 | Absolute domain-total cap (0 = off). | |
freq.pagelim | number | 0 | Per-page (or iframe-page) cap (0 = off). | |
freq.session | boolean | false | Enable session tracking (multi-tab detection, session reset of counters). POP_CONFIG sets false. | |
freq.sessionExpiration | number (seconds) | 3600 | Session window-timer expiry. | |
freq.sessionKeepAliveTime | number (seconds) | 0 | Keep-alive threshold used by checkSession. | |
freq.hashed | boolean | true | Include location.hash in the page-hash key (getHash). Not present in POP_CONFIG so default applies. |
Returns:
n/a — Configuration only; consumed throughout the frequency engine.3 pops per 3h per page, time-spaced 15s
{ freq: { context:'page', qty:3, period:10800, scheme:'time', distances:'15,15,15', session:false } }Matches the POP_CONFIG.freq block. isSchemeReached() returns true once any of qty/max/pagelim/distance limits are hit.
settings.misc: { newTab, under, bindTo, ignoreTo, stopUrls, perpage, coverIframe, coverScrollbar, fallbackToPopup, forcePopup } settingsettings.misc
Miscellaneous behavior group passed largely into the engine's stackConfig/popOptions (init3rdp, lines 303-347). newTab/under set the engine tab/under flags (only for non-popup window types). stopUrls disables popping on matching URLs (isUrlStopping, line 526). bindTo/include restrict which elements are 'active' (isActiveElement, line 552); ignoreTo and perpage are forwarded to the engine config. coverIframe enables transparent click-catching overlays over iframes (runIframeWrappers/syncIframeWrappers). coverScrollbar -> engine allowScrollbar. forcePopup / fallbackToPopup switch the pop to a sized popup window (esp. Chrome>=68 desktop) computing top/left/width/height (lines 334).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
misc.newTab | boolean | false | Open in a new tab (non-popup types). POP_CONFIG sets true. | |
misc.under | boolean | true | Open as under (background). POP_CONFIG sets false. | |
misc.stopUrls | string | string[] | [] | URLs (substring match) on which popping is suppressed. | |
misc.bindTo | string[] (selectors) | [] | If non-empty, only clicks within these selectors are eligible (isActiveElement). | |
misc.ignoreTo | array | [] | Forwarded to engine stackConfig.ignoreTo. | |
misc.perpage | number | 1000 | Engine perpage cap (stackConfig.perpage). | |
misc.coverIframe | boolean | true | Overlay transparent clickable wrappers over iframes. POP_CONFIG sets true. | |
misc.coverScrollbar | boolean | true | Engine allowScrollbar flag. | |
misc.fallbackToPopup | boolean | false | Fall back to sized popup on Chrome>=68 desktop. | |
misc.forcePopup | boolean | false | Always use a sized popup window. |
Returns:
n/a — Configuration only.New-tab pop with iframe cover
{ misc: { newTab:true, under:false, coverIframe:true } }Mirrors POP_CONFIG.misc. coverIframe triggers runIframeWrappers after the engine handshake.
settings.counters: { domain, page, iframePage, max, schemeClicks, schemeSeq, dailySeq, firstLoadTimer, lastTimer, lastPageTimer, lastPageScroll } settingsettings.counters / counterPostfixes / settings.zh
Storage-key group. Each value is the localStorage key base for that counter. addPostfixToCounters() (line 677) appends counterPostfixes[key] OR settings.zh to every counter name, namespacing per-site so different deployments don't collide. These keys are what you read via window._storage. settings.zh is the shared postfix (a hash, '28bf6d3f...' in POP_CONFIG). counterPostfixes lets you override the postfix per counter (all null by default => use zh).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
counters.domain | string | "__cntd" | Domain-context pop counter key (POP_CONFIG: "kadPD"). | |
counters.page | string | "__cntp" | Page-context pop counter (POP_CONFIG: "kadPP"). | |
counters.iframePage | string | "__cntip" | Iframe-page counter (POP_CONFIG: "kadPIP"). | |
counters.schemeSeq / dailySeq | string | "__cnss" / "__cns24s" | Scheme sequence and daily sequence counters (POP_CONFIG: "kadSS"/"kadDS"), appended to pop URL. | |
counters.schemeClicks | string | "__cnsc" | Click counter for the clicks scheme (POP_CONFIG: "kadSC"). | |
counters.firstLoadTimer / lastTimer / lastPageTimer / lastPageScroll | string | "__tlfr"/"__tlo"/"__tlop"/"__cnlps" | Timer and scroll-restore keys (POP_CONFIG: kadFLT/kadLT/kadLPT/kadLPS). | |
counters.max | string | "__cntm" | Max-counter key (POP_CONFIG: "kadPM"). | |
zh | string | (none in defaults) | Global postfix appended to every counter key. POP_CONFIG: "28bf6d3f52c38a9e7ea8c34a508c32df". | |
counterPostfixes.<key> | string | null | null (=> use zh) | Optional per-counter postfix override. |
Returns:
n/a — Configuration only; defines persistence keys.Namespace counters per deployment
{ counters:{ domain:'kadPD', page:'kadPP' }, zh:'28bf6d3f52c38a9e7ea8c34a508c32df' }Effective key for the page counter becomes 'kadPP28bf6d3f52c38a9e7ea8c34a508c32df'.
settings.names: { iabc, schemeSeq, dailySeq, clickedElement, windowType, clickedElementLink } settingsettings.names
Query-parameter NAME group — the names of the tracking params appended to the pop URL by getPopUrl() (lines 212-218). schemeSeq/dailySeq carry the incremented sequence values; windowType carries popscript_<window.type>; iabc=1 is added on mobile UAs flagged by utils.ige; clickedElement (value tmb/lnk) and clickedElementLink carry info about a thumbnail vs text link click target.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
names.schemeSeq | string | "sseq" | Param name for scheme sequence (POP_CONFIG: "sseq"). | |
names.dailySeq | string | "dseq" | Param name for daily sequence (POP_CONFIG: "dseq"). | |
names.iabc | string | "__iabc" | Param name flagged =1 on certain mobile UAs (POP_CONFIG: "iabc"). | |
names.clickedElement | string | "1" | Param name carrying click-target type tmb/lnk (POP_CONFIG: "ce"). | |
names.windowType | string | "rsrc" | Param name for popscript_<type>. Not in POP_CONFIG => default "rsrc". | |
names.clickedElementLink | string | "cel" | Param name carrying the encoded href of the clicked link. |
Returns:
n/a — Configuration only; controls pop URL query keys.Customize tracking param names
{ names:{ schemeSeq:'sseq', dailySeq:'dseq', iabc:'iabc', clickedElement:'ce' } }Resulting pop URL gains e.g. &sseq=1&dseq=1&rsrc=popscript_popunder.
settings.priorBjs: boolean; settings.otherBjs: string[] settingsettings.priorBjs / settings.otherBjs
Prior / competing-library group. When priorBjs is true (and the URL is not a stop URL) init3rdp calls prior() (line 301), which discovers other popunder libraries on the page via getOtherBjs() — it scans window own-property names for string-valued props whose target object has a reversed 'getStack' method (lines 240-249) — and patches their shouldFire so this engine takes precedence (installs window.ppuDisableTrigger and the body prior flag). otherBjs is the accumulated list of those discovered library namespace names (you may seed it; getOtherBjs pushes more, skipping settings.popns).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
priorBjs | boolean | true | Enable prior-suppression of competing popunder libs. POP_CONFIG sets false. | |
otherBjs | string[] | [] | Names of competing library window slots; getOtherBjs auto-populates this. |
Returns:
n/a — Configuration only; gates prior() behavior.Disable prior suppression
{ priorBjs: false }As in POP_CONFIG; prior() will not run and window.ppuDisableTrigger is never installed.
settings.add: { pixels:[], scripts:[], callbacks:{open:[],scheme:[]}, title, keywords, urlParamsObj:{} } settingsettings.add (pixels / scripts / callbacks / urlParamsObj / title / keywords)
Embeds & extras group. addEmbeds() (line 222) injects each add.pixels URL as a hidden 16x16 IMG and each add.scripts URL as a SCRIPT into the body. add.urlParamsObj is a map of extra query params appended to every pop URL by getPopUrl (lines 210-211); it can also be auto-populated from window[settings.bdVar].getValues() (addBDToUrl, line 846). add.callbacks.open is a list of callbacks; combined with settings.callbacks.open they run via runCallbacks() after each pop (afterOpen -> runCallbacks, line 841).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
add.pixels | string[] (URLs) | [] | Tracking pixel URLs injected as hidden IMG elements. | |
add.scripts | string[] (URLs) | [] | Extra script URLs injected into the body. | |
add.urlParamsObj | object | {} | Extra key/value query params appended to the pop URL (also auto-filled from bdVar). | |
add.callbacks.open | function[] | [] | Callbacks invoked after each pop opens. | |
add.title / add.keywords | string | null | null | Optional metadata fields (present in defaults; not consumed by core flow shown). |
Returns:
n/a — Configuration only; pixels/scripts injected, params appended, callbacks queued.Add a tracking pixel and a post-pop callback
{ add:{ pixels:['//track.example/p.gif'], urlParamsObj:{ src:'home' }, callbacks:{ open:[function(){console.log('popped')}] } } }Pixel injected at init via addEmbeds; src=home appended to every pop URL; callback runs in afterOpen.
settings.callbacks: { open: function[], scheme: function[] } settingsettings.callbacks
Top-level callback registry. callbacks.open functions are executed by runCallbacks() after each successful pop (afterOpen, line 604/841). prior() also pushes itself onto callbacks.open so prior-suppression re-runs on every pop (line 289). callbacks.scheme exists in defaults but is not invoked by the core paths in this file.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
callbacks.open | function[] | [] | Run after every pop open. | |
callbacks.scheme | function[] | [] | Reserved; present in defaults, not invoked in shown flow. |
Returns:
n/a — Configuration only; open callbacks fire post-pop.React to each pop
{ callbacks:{ open:[ function(){ /* fire analytics */ } ] } }Invoked from runCallbacks() inside afterOpen().
settings.elements: { media, include, exclude, isActiveElement }; settings.onlyClickable: boolean settingsettings.elements / settings.onlyClickable / settings.bindTo eligibility
Click-eligibility group. isElementPopping (line 534) / isActiveElement (line 552) / isClickableElement (line 558) decide whether a clicked element qualifies to trigger a pop. elements.include is a selector (or array) that force-qualifies matching elements; misc.bindTo restricts to within given selectors. When onlyClickable is true, only elements that look interactive (cursor:pointer, anchor/button/input/etc., or having on* handlers) qualify; otherwise any click qualifies.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
elements.include | string | string[] | null | null | Selector(s) that force a clicked element to be treated as active. | |
elements.media / exclude / isActiveElement | various | true / false / null | Additional element-matching defaults present in config_2.defaults. | |
onlyClickable | boolean | false | When true, only interactive-looking elements (or ancestors) trigger pops. |
Returns:
n/a — Configuration only; governs allowPop eligibility.Only pop on clickable elements within a container
{ onlyClickable:true, elements:{ include:'#content' }, misc:{ bindTo:['#content'] } }isElementPopping walks up the DOM checking isClickableElement when onlyClickable is set.
settings.delayPop: { url, isActive, delay } settingsettings.delayPop
Secondary delayed pop group. When delayPop.isActive and delayPop.url are set (and not a Mac without screenTop/screenY), init3rdp registers a SECOND tiny (5x5px, offscreen at 9999,9999) pop via browserInfo.add(delayPop.url, {...}) (lines 334-346), under on non-Opera/non-Mac, with a 24h expiry. On Chrome the engine interval is set to delayPop.delay.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
delayPop.url | string | null | null | Secondary delayed pop destination. | |
delayPop.isActive | boolean | false | Enable the secondary delayed pop. | |
delayPop.delay | number | 30 | Engine interval (Chrome) for the delayed pop. |
Returns:
n/a — Configuration only; registers a second pop window.Enable a delayed secondary pop
{ delayPop:{ isActive:true, url:'https://barren-theory.com/second', delay:30 } }Skipped on Mac without screenTop/screenY; opens a 5x5 offscreen window.
settings.bdVar: string // window property name settingsettings.bdVar / addBDToUrl
Name of a window global providing dynamic URL params. addBDToUrl() (lines 846-851) reads window[settings.bdVar]; if it exposes getValuesAsParams(), the wrapper sets add.urlParamsObj = bdObj.getValues() and subscribes to bdObj.lateValuesUpdateCbs to refresh them later. This lets an external data provider inject params into every pop URL.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
bdVar | string | "__htaBDI" | Window property name of the dynamic-params provider object. |
Returns:
n/a — Configuration only; bridges an external data object into add.urlParamsObj.Provide dynamic pop URL params
window.__htaBDI = { getValuesAsParams(){return ''}, getValues(){return {geo:'us'}}, lateValuesUpdateCbs:[] };addBDToUrl copies getValues() into add.urlParamsObj, appended by getPopUrl.
pageGroup: string; hash: string|null; revert: boolean; autobind: boolean settingsettings.pageGroup / settings.hash / settings.revert / settings.autobind
Misc top-level keys. pageGroup is the fixed hash used for domain-context counting (getHash returns it when context is 'domain', line 768). hash, if set, overrides per-page hashing with its first 8 chars (getHash, line 767). autobind (default true) gates whether init() actually arms the engine via init3rdp (line 193); set false to merge settings without arming. revert is present in POP_CONFIG (false) as a flag carried in settings.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
pageGroup | string | "abcdefgh" | Hash key used for domain-context counters. | |
hash | string | null | null | Force a specific page hash (first 8 chars used). | |
autobind | boolean | true | If false, init() merges settings but does NOT load engine/arm pop. | |
revert | boolean | (not in defaults; POP_CONFIG sets false) | Flag carried in settings; set false in POP_CONFIG. |
Returns:
n/a — Configuration only.Merge config without arming
{ autobind:false }init() calls mergeSettings + addPostfixToCounters + addEmbeds but skips init3rdp, so no pop is armed.
(function(moduleConfig){ ...trackerModule.run(moduleConfig) })(DC_CONFIG) // DC_CONFIG = { url } globalDC_CONFIG tracker module (ht_dc)
The SECOND inner module (lines 1302-1370), independent of the popunder engine. trackerModule.run(config) merges config into options then runs collecting(): it iterates getters and calls the `referrer` getter, which sends ref (origin+pathname+search) and prevRef (document.referrer) to options.url via navigator.sendBeacon (falling back to an XHR POST). Errors are beaconed to URLS.errorBeacon with tag=dc. You configure it by editing DC_CONFIG.url (the only key). scriptInfo.version is '1.0.3'.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
url | string (URL) | yes | DC_CONFIG.url (obfuscated barren-theory.com URL) | Endpoint that receives the beacon payload ref=...&prevRef=... . |
Returns:
void — Fires a referrer beacon on load; no return value.Configure the referrer tracker
(function(c){ /* trackerModule */ })({ url: 'https://barren-theory.com/YR2Sx...' });Sends ref/prevRef via sendBeacon to the configured url immediately on load.
var URLS = { tastyEngine, errorBeacon, infoBeacon } globalURLS (top-level constant)
Top-level editable constants block (lines 16-20). tastyEngine is the engine script URL injected as settings.script (here the LOCAL 'tasty-permission.final.js' instead of the original remote). errorBeacon and infoBeacon are the telemetry endpoints used by config_2.sendError (line 918, tag=pop) / config_2.sendInfo (line 929, tags=pop) and by the tracker module's sendError (tag=dc). Edit these to repoint engine and telemetry.
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
tastyEngine | string (URL) | yes | "tasty-permission.final.js" | Engine script URL (assigned to POP_CONFIG.script). |
errorBeacon | string (URL) | yes | "//barren-theory.com/jserr" | Error telemetry endpoint (?msg=&ua=&tag=pop|dc). |
infoBeacon | string (URL) | yes | "//barren-theory.com/jsinfo" | Info telemetry endpoint (?msg=&ua=&tags=pop). |
Returns:
n/a — Configuration constants.Repoint the engine to a remote URL
var URLS = { tastyEngine:'https://cdn.example/engine.js', errorBeacon:'//t.example/jserr', infoBeacon:'//t.example/jsinfo' };POP_CONFIG.script = URLS.tastyEngine, so changing tastyEngine changes what gets loaded.
settings.otherFormats: { [formatKey]: { id, isAllowClicks, isAllowCloseButton } } settingsettings.otherFormats
Other-format click gating group, consumed by checkOtherFormats(el) (lines 505-522) during allowPop. For each configured format, if the clicked element is inside the element with that format's id and isAllowClicks is false, the click is disallowed — except the special 'inpage' format which still allows clicks on a close button (selector matching [class$="__close"]). Default is {} (no restrictions).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
otherFormats.<key>.id | string | yes | — | Element id whose subtree is governed by this format. |
otherFormats.<key>.isAllowClicks | boolean | (checked via 'in') | If false, clicks inside this format do not trigger pops. | |
otherFormats.inpage.isAllowCloseButton | boolean | — | For the 'inpage' format, allow clicks on a [class$="__close"] element even when clicks are otherwise blocked. |
Returns:
n/a — Configuration only; influences allowPop via checkOtherFormats.Block pops inside an in-page unit except its close button
{ otherFormats:{ inpage:{ id:'inpage-unit', isAllowClicks:false, isAllowCloseButton:true } } }checkOtherFormats returns false for clicks inside #inpage-unit unless on its __close element.
window.__htapop = { /* settings overrides */ } globalwindow.__htapop
Global external-settings fallback slot read by the wrapper's config.mergeSettings (line 199). On init the wrapper first tries to read settings from the loader <script> element itself (document.querySelector('script[src*="' + scriptSrc + '"]').settings); if that script element is not found or has no .settings property, it falls back to window.__htapop (or {} if that is also absent). Whatever object is found is deep-merged into config.defaults BEFORE the in-code POP_CONFIG/init argument is merged on top, so __htapop acts as a lower-precedence host-page override layer. It is the documented host integration point for supplying configuration without editing the script tag's settings attribute. A special case: if the external settings object has window.url set, that url is force-copied onto settings.window.url after the merge (line 199).
| Param | Type | Req | Default | Description |
|---|---|---|---|---|
<any settings key> | Object | Same shape as POP_CONFIG / config.defaults (e.g. barrier, freq, misc, window, counters, names, add, etc.). Only the keys you set are merged. |
Returns:
Object — Read-only from the wrapper's perspective; the wrapper consumes it, it does not write to it.Host page overriding frequency before the loader runs
// Must be defined before hill.btheory.final.js executes
window.__htapop = {
freq: { qty: 1, period: 7200 },
window: { url: "https://example.com/landing" }
};Lower precedence than the inline POP_CONFIG passed to config.init; higher precedence than config.defaults. window.url here would also be re-applied to settings.window.url by the special-case at line 199.
Precedence order is: config.defaults < external settings (script[src].settings OR window.__htapop) < POP_CONFIG/init argument. The script-element .settings source takes priority over window.__htapop (the latter is only used when the script element or its .settings is missing).
🔀 Live flow Change a value in the Loader or Engine tab and this flow redraws live: the real path of the pop under your config — branches, capping, opening type. Select a node for its explanation.
…
📋 Detail by phase — all config with its current value
When does the Wrapper load the Engine?
Immediately at startup, NOT on click. As soon as the Wrapper starts: 1) it reserves
window[popns] · 2) loads the Engine (engine.official.js) · 3) checks the barrier · 4) turns off pops from other networks. Then the Engine waits; the click only does the last step: opening the pop.
The barren-theory.com URLs
Wrapper src//barren-theory.com/cjD/…
The Wrapper script (the network's package). The loader downloads this.
barrierbarren-theory.com/Y.m_…
Anti-adblock bait: the Wrapper tries to load it; if adblock blocks it, plan B kicks in.
ad urlbarren-theory.com/bE3_…
The ad that opens in the pop (
window.url).jserr / jsinfo//barren-theory.com/jserr · /jsinfo
Telemetry (errors/info). Does not open a pop.
① Loader — just the link (empty config), paste it into the page
…
🔐 Loader (base64 · CSS-var) — optimized & protected
…
…
② Wrapper — ALL the code with your config
Every field is prefilled with a working default. Change only what you need — the code on the left updates live. Prefilled defaults · live preview