PageSourceSearch

https://www.thenation.com/wp-content/themes/thenation-2023/js/freestar-loader.js?ver=0.3

js thenation.com collected 2026-09-24 06:54:08 UTC 10,915 bytes, 325 lines download raw bytes

1/**
2 * Freestar Ad Loader — gated by SESSuserinfo cookie, with channel
3 * detection replicating the original PHP is_front_page()/is_single()/
4 * is_tax()/etc. conditions using WordPress's default body classes.
5 *
6 * IMPORTANT: window.freestar (and enabled_slots) is created
7 * UNCONDITIONALLY and immediately, regardless of subscriber status.
8 * This is required because ad-slot registration scripts scattered
9 * throughout the page (e.g. freestar.config.enabled_slots.push(...))
10 * run independently of this file and will throw
11 * "freestar is not defined" if the object doesn't exist yet — even
12 * for users where ads end up disabled.
13 */
14
15(function () {
16	'use strict';
17
18	/* ----------------------------------------------------
19	 * 0. ALWAYS create the freestar shell first, before any
20	 *    other logic. This guarantees that any ad-slot script
21	 *    on the page — no matter where it sits in the HTML,
22	 *    and regardless of subscriber status — can safely call
23	 *    freestar.config.enabled_slots.push(...) without error.
24	 * ---------------------------------------------------- */
25	window.freestar = window.freestar || {};
26	freestar.hitTime = freestar.hitTime || Date.now();
27	freestar.queue = freestar.queue || [];
28	freestar.config = freestar.config || {};
29	freestar.config.enabled_slots = freestar.config.enabled_slots || [];
30	freestar.config.disabledProducts = freestar.config.disabledProducts || {};
31	freestar.config.channel = freestar.config.channel || 'tn_general_company';
32	freestar.debug = window.location.search.indexOf('fsdebug') !== -1;
33
34	/* ----------------------------------------------------
35	 * 1. Read a cookie by name
36	 * ---------------------------------------------------- */
37	function getCookie(name) {
38		var match = document.cookie.match(
39			new RegExp('(?:^|; )' + name.replace(/([.$?*|{}()[\]\\/+^])/g, '\\$1') + '=([^;]*)')
40		);
41		return match ? decodeURIComponent(match[1]) : null;
42	}
43
44	/* ----------------------------------------------------
45	 * 2. Check SESSuserinfo cookie for premium_subscriber role
46	 * ---------------------------------------------------- */
47	function isPremiumSubscriber() {
48		var raw = getCookie('SESSuserinfo');
49		if (!raw) {
50			return false;
51		}
52
53		var userInfo;
54		try {
55			userInfo = JSON.parse(atob(raw));
56		} catch (e) {
57			return false;
58		}
59
60		return (
61			userInfo &&
62			Array.isArray(userInfo.roles) &&
63			userInfo.roles.indexOf('premium_subscriber') !== -1
64		);
65	}
66
67	/* ----------------------------------------------------
68	 * 2b. Check SESSexpireduser cookie
69	 *     If this equals '1', the subscriber's access has
70	 *     expired, so ads should NOT be disabled even though
71	 *     the premium_subscriber role is still present.
72	 * ---------------------------------------------------- */
73	function isExpiredUser() {
74		return getCookie('SESSexpireduser') === '1';
75	}
76
77	/* ----------------------------------------------------
78	 * 2c. Final decision: disable ads only for an ACTIVE
79	 *     premium subscriber (role present AND not expired)
80	 * ---------------------------------------------------- */
81	function shouldDisableAds() {
82		return isPremiumSubscriber() && !isExpiredUser();
83	}
84
85	/* ----------------------------------------------------
86	 * 3. Determine freestar.config.channel
87	 *    Mirrors the original PHP if/elseif chain, using the
88	 *    body classes WordPress adds for the same conditions.
89	 * ---------------------------------------------------- */
90	function determineChannel() {
91		var body = document.body.classList;
92		var path = window.location.pathname;
93
94		if (body.contains('home')) {
95			return 'tn_home';
96		}
97
98		if (body.contains('single-article') || body.contains('single-podcast')) {
99			return 'tn_article';
100		}
101
102		if (body.contains('post-type-archive-magazineissue')) {
103			return 'tn_issue_landing';
104		}
105
106		if (body.contains('tax-admin-taxonomy')) {
107			return 'tn_admin_taxonomy';
108		}
109
110		if (body.contains('tax-subject')) {
111			return 'tn_subject_landing';
112		}
113
114		if (body.contains('tax-keyword') || body.contains('tax-special')) {
115			return 'tn_keyword_landing';
116		}
117
118		if (path.indexOf('take-action/') !== -1) {
119			return 'tn_take_action';
120		}
121
122		// page-template-templates-primary_landing_page-php (dots/slashes -> dashes)
123		var landingTemplateClass = Array.prototype.find.call(body, function (cls) {
124			return cls.indexOf('page-template-templates-primary_landing_page') === 0;
125		});
126		if (landingTemplateClass) {
127			var parts = path.split('/').filter(Boolean);
128			var last = parts.length ? parts[parts.length - 1] : '';
129			return 'tn_' + last;
130		}
131
132		if (body.contains('search-results') || body.contains('search-no-results')) {
133			return 'tn_search';
134		}
135
136		return 'tn_general_company';
137	}
138
139	function setChannel() {
140		freestar.config.channel = determineChannel();
141	}
142
143	if (document.body) {
144		setChannel();
145	} else {
146		document.addEventListener('DOMContentLoaded', setChannel);
147	}
148
149	/* ----------------------------------------------------
150	 * 4. If an active (non-expired) subscriber: the freestar
151	 *    shell already exists (step 0), so no ad-slot script
152	 *    on the page will error out — it'll just push into an
153	 *    array that never gets used. Stop here WITHOUT loading
154	 *    preconnects, stylesheet, or pubfig.min.js.
155	 * ---------------------------------------------------- */
156	if (shouldDisableAds()) {
157		return;
158	}
159
160	/* ----------------------------------------------------
161	 * 4b. Reserve fixed-size space for every ad slot to
162	 *     prevent Cumulative Layout Shift (CLS).
163	 *     Runs only for non-subscribers, since active
164	 *     subscribers already returned above at Step 4.
165	 * ---------------------------------------------------- */
166	function parseAdSizes(dataAttr) {
167		return dataAttr
168			.split(/\s+/)
169			.map(function (s) { return s.replace(/_/g, ''); })
170			.filter(function (s) { return s.indexOf('x') !== -1; })
171			.map(function (s) {
172				var parts = s.split('x');
173				return { w: parseInt(parts[0], 10), h: parseInt(parts[1], 10) };
174			});
175	}
176
177	function lockAdSlotSize(slot, minWidth, minHeight) {
178		slot.style.minWidth = minWidth + 'px';
179		slot.style.minHeight = minHeight + 'px';
180
181		var observer = new MutationObserver(function (mutations) {
182			mutations.forEach(function (m) {
183				if (m.attributeName === 'style') {
184					var currentMinH = parseInt(slot.style.minHeight, 10);
185					if (!currentMinH || currentMinH < minHeight) {
186						slot.style.minHeight = minHeight + 'px';
187						slot.style.minWidth = minWidth + 'px';
188					}
189				}
190			});
191		});
192
193		observer.observe(slot, { attributes: true, attributeFilter: ['style'] });
194	}
195
196	function reserveAdSlotSpace() {
197		document.querySelectorAll('[data-freestar-ad]').forEach(function (slot) {
198			var sizes = parseAdSizes(slot.getAttribute('data-freestar-ad'));
199			if (sizes.length === 0) return;
200
201			var maxW = Math.max.apply(null, sizes.map(function (s) { return s.w; }));
202			var maxH = Math.max.apply(null, sizes.map(function (s) { return s.h; }));
203
204			lockAdSlotSize(slot, maxW, maxH);
205		});
206	}
207
208	if (document.readyState === 'loading') {
209		document.addEventListener('DOMContentLoaded', reserveAdSlotSpace);
210	} else {
211		reserveAdSlotSpace();
212	}
213
214	/* ----------------------------------------------------
215	 * 5. Preconnects + stylesheet
216	 * ---------------------------------------------------- */
217	var head = document.head;
218
219	[
220		'https://a.pub.network/',
221		'https://b.pub.network/',
222		'https://c.pub.network/',
223		'https://d.pub.network/',
224		'https://c.amazon-adsystem.com',
225		'https://s.amazon-adsystem.com',
226		'https://btloader.com/',
227		'https://api.btloader.com/',
228		'https://confiant-integrations.global.ssl.fastly.net'
229	].forEach(function (href) {
230		var link = document.createElement('link');
231		link.rel = 'preconnect';
232		link.href = href;
233		link.crossOrigin = 'anonymous';
234		head.appendChild(link);
235	});
236
237	var stylesheet = document.createElement('link');
238	stylesheet.rel = 'stylesheet';
239	stylesheet.href = 'https://a.pub.network/thenation-com/cls.css';
240	head.appendChild(stylesheet);
241
242	freestar.queue.push(function () {
243		googletag.pubads().set('page_url', 'https://www.thenation.com');
244	});
245
246	/* ----------------------------------------------------
247	 * 6. Disable video if .tn-lightbox exists
248	 * ---------------------------------------------------- */
249	(function () {
250		function disableVideo() {
251			freestar.config.disabledProducts.video = true;
252		}
253		if (document.querySelector('.tn-lightbox')) {
254			disableVideo();
255			return;
256		}
257		var observer = new MutationObserver(function () {
258			if (document.querySelector('.tn-lightbox')) {
259				disableVideo();
260				observer.disconnect();
261			}
262		});
263		observer.observe(document.documentElement, { childList: true, subtree: true });
264	})();
265
266	/* ----------------------------------------------------
267	 * 7. Only load the recovery script + pubfig.min.js if
268	 *    this page actually has ad slots registered.
269	 *
270	 *    Ad-slot scripts elsewhere on the page (e.g.
271	 *    freestar.config.enabled_slots.push(...)) are plain
272	 *    synchronous inline scripts, so by the time
273	 *    DOMContentLoaded fires, every one of them has
274	 *    already run. Checking enabled_slots.length at that
275	 *    point tells us definitively whether this page has
276	 *    any ads to show — if not, skip both scripts entirely.
277	 * ---------------------------------------------------- */
278	var tnads = new URLSearchParams(window.location.search).get('tnads');
279
280	function loadAdsIfPresent() {
281		if (tnads !== null && tnads === 'false') {
282			return;
283		}
284
285		if (freestar.config.enabled_slots.length === 0) {
286			return;
286 // no ad slots on this page — nothing to recover or serve
287		}
288
289		function loadPubfig() {
290			var pubfigScript = document.createElement('script');
291			pubfigScript.async = true;
292			pubfigScript.src =
293				'https://a.pub.network/thenation-com' +
294				(freestar.debug ? '/qa/pubfig.min.js' : '/pubfig.min.js');
295			document.head.appendChild(pubfigScript);
296
297			freestar.initCallback = function () {
298				if (freestar.config.enabled_slots.length === 0) {
299					freestar.initCallbackCalled = false;
300				} else {
301					freestar.newAdSlots(freestar.config.enabled_slots, freestar.config.channel);
302				}
303			};
304		}
305
306		var recoveryScript = document.createElement('script');
307		recoveryScript.src =
308    (window.freestarConfig && window.freestarConfig.recoveryScriptUrl) ||
309    'https://www.thenation.com/wp-content/themes/thenation-2023/js/freestar-adblock-recovery.js';
310		recoveryScript.addEventListener('load', loadPubfig);
311		recoveryScript.addEventListener('error', loadPubfig); // don't block ads if recovery script fails to load
312		if (recoveryScript.src) {
313			document.head.appendChild(recoveryScript);
314		} else {
315			loadPubfig(); // no recovery URL configured — load ads anyway
316		}
317	}
318
319	if (document.readyState === 'loading') {
320		document.addEventListener('DOMContentLoaded', loadAdsIfPresent);
321	} else {
322		// DOM already parsed (e.g. script loaded late) — slots already registered, check now
323		loadAdsIfPresent();
324	}
325})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.