PluginProbe
MainWP Dashboard: Self-hosted WordPress Management for Agencies / 6.2.1
MainWP Dashboard: Self-hosted WordPress Management for Agencies v6.2.1
6.2.2 6.2.1 6.2 6.1.8 6.1.7 6.1.6 6.1.5 6.1.4 6.1.3 6.1.2 6.1.1 6.1 6.0.12 6.0.11 4.6.0.1 5.0 5.0.1 5.0.2 5.0.3 5.0.3.1 5.0.3.2 5.1 5.1.1 5.2 5.2.1 All 155 releases
mainwp / assets / js / mainwp-cache-warm.js

mainwp-cache-warm.js in MainWP Dashboard: Self-hosted WordPress Management for Agencies 6.2.1, at assets/js/mainwp-cache-warm.js

170 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 // Cache-warming prefetch for MainWP (no DOM swap)
2 // Targets only safe links.
3 // Opt-in: .mainwp-js-prefetch or [mainwp-data-warm]; Opt-out: [mainwp-data-no-warm]
4 (function ($) {
5 // -------------------- Config --------------------
6 const HOVER_DELAY_MS = 200; // delay before hover prefetch
7 const REWARM_TTL_MS = 5 * 60 * 1000;// re-warm same URL after 5 min
8 const MAX_INFLIGHT = 3; // throttle concurrent warms
9 const MAX_BYTES = 1.5 * 1024 * 1024; // 1.5 MB content size limit
10
11 // -------------------- State ---------------------
12 const SEEN = new Map(); // url -> timestamp last warmed
13 let inflight = 0;
14
15 // -------------------- Helpers -------------------
16 function canPrefetch() {
17 const c = navigator.connection;
18 if (c) {
19 if (c.saveData) return false;
20 if (['slow-2g', '2g'].includes(c.effectiveType)) return false;
21 }
22 return location.protocol.startsWith('http');
23 }
24
25 function isWarmableLink(a) {
26 if (!a) return false;
27 if (a.hasAttribute('download') || a.target === '_blank') return false;
28 if (a.hasAttribute('mainwp-data-no-warm')) return false;
29
30 const href = a.getAttribute('href');
31 if (!href) return false;
32
33 // Only warm explicitly opt-in links OR MainWP nav links
34 const explicitlyOpted = a.classList.contains('mainwp-js-prefetch') || a.hasAttribute('mainwp-data-warm');
35 const url = new URL(href, location.href);
36
37 if (url.origin !== location.origin) return false; // same-origin only
38 if (url.hash && url.pathname === location.pathname) return false; // same-page anchor
39 if (!explicitlyOpted) return false; // limit scope to MainWP
40
41 // De-dupe within TTL
42 const seenAt = SEEN.get(url.toString());
43 if (seenAt && (Date.now() - seenAt) < REWARM_TTL_MS) return false;
44
45 return true;
46 }
47
48 function markSeen(urlStr) {
49 SEEN.set(urlStr, Date.now());
50 // Clean out old entries occasionally
51 if (SEEN.size > 200) {
52 const now = Date.now();
53 for (const [u, ts] of SEEN) if (now - ts > REWARM_TTL_MS) SEEN.delete(u);
54 }
55 }
56
57 function prefetchViaLink(urlStr) {
58 const l = document.createElement('link');
59 l.rel = 'prefetch';
60 l.href = urlStr;
61 l.as = 'document';
62 document.head.appendChild(l);
63 }
64
65 async function headOKAndSmall(urlStr) {
66 // Try HEAD first to check Content-Length; if missing, allow.
67 try {
68 const res = await fetch(urlStr, {
69 method: 'HEAD',
70 credentials: 'same-origin',
71 mode: 'same-origin',
72 cache: 'default'
73 });
74 if (!res.ok) return false;
75 const len = res.headers.get('content-length');
76 if (!len) return true; // no info -> allow
77 return Number.parseInt(len, 10) <= MAX_BYTES;
78 } catch {
79 // HEAD may be blocked or not supported; allow rather than fail closed.
80 return true;
81 }
82 }
83
84 async function warm(urlStr) {
85 if (inflight >= MAX_INFLIGHT) return;
86 inflight++;
87 try {
88 // 1) Let the browser decide (best-effort)
89 prefetchViaLink(urlStr);
90
91 // 2) Skip very large pages
92 const ok = await headOKAndSmall(urlStr);
93 if (!ok) { markSeen(urlStr); return; }
94
95 // 3) Fetch to warm the HTTP cache & connection with auth cookies
96 await fetch(urlStr, {
97 method: 'GET',
98 credentials: 'same-origin',
99 mode: 'same-origin',
100 cache: 'default'
101 }).catch(() => { /* ignore */ });
102 console.log('Warm cached url: ' + urlStr);
103 markSeen(urlStr);
104 } finally {
105 inflight--;
106 }
107 }
108
109 function scheduleHoverWarm(a) {
110 const href = a.getAttribute('href');
111 if (!href) return;
112 const urlStr = new URL(href, location.href).toString();
113 const t = setTimeout(() => warm(urlStr), HOVER_DELAY_MS);
114 const cancel = () => clearTimeout(t);
115 a.addEventListener('pointerleave', cancel, { once: true });
116 a.addEventListener('focusout', cancel, { once: true });
117 }
118
119 function setupViewportWarm(root) {
120 if (!('IntersectionObserver' in globalThis)) return;
121 const io = new IntersectionObserver((entries) => {
122 entries.forEach((e) => {
123 if (!e.isIntersecting) return;
124 const a = e.target;
125 if (!isWarmableLink(a)) return;
126 const urlStr = new URL(a.getAttribute('href'), location.href).toString();
127 io.unobserve(a);
128 warm(urlStr);
129 });
130 }, { root: null, rootMargin: '200px', threshold: 0 });
131
132 root.querySelectorAll('a.mainwp-js-prefetch, a[mainwp-data-warm]')
133 .forEach((a) => { if (isWarmableLink(a)) io.observe(a); });
134 }
135
136 // -------------------- Boot ----------------------
137 $(function () {
138 if (!canPrefetch()) return;
139
140 // Intent-based warm (hover/focus)
141 document.addEventListener('pointerenter', (e) => {
142 const target = e.target;
143 if (!(target instanceof Element)) return; // skip if not an element
144 const a = target.closest('a');
145 if (!a || !isWarmableLink(a)) return;
146 scheduleHoverWarm(a);
147 }, true);
148 document.addEventListener('focusin', (e) => {
149 const a = e.target.closest('a');
150 if (!isWarmableLink(a)) return;
151 scheduleHoverWarm(a);
152 }, true);
153
154 // Touch warm
155 document.addEventListener('touchstart', (e) => {
156 const a = e.target.closest('a');
157 if (!isWarmableLink(a)) return;
158 const urlStr = new URL(a.getAttribute('href'), location.href).toString();
159 warm(urlStr);
160 }, { passive: true });
161
162 // Viewport warm for persistent nav (e.g., sidebar)
163 setupViewportWarm(document);
164 });
165 })(jQuery);
166
167
168
169
170