hyperinstant 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,27 +1,21 @@
1
- # HyperInstant v1.0.2
1
+ # HyperInstant v1.1.1 (Legacy-fast)
2
2
 
3
- v1.0.2 focuses on **real-world speed**:
4
- - **Prerender only on high-confidence intent** (mousedown/touchstart), not on hover
5
- - **Lower overhead scheduler** (capped queue + dedupe; no heavy sorting)
6
- - **Safer auto-deny** (less over-blocking)
7
- - **Optional auto-tuning** (OFF by default)
3
+ Same ultra-light behavior as 0.1.0 / 1.1.0, with **practical fixes**:
4
+ - **Same-site support**: allows `www.` / non-`www` and subdomains by default
5
+ - **More reliable events**: uses `pointerover` + capture listeners
8
6
 
9
7
  ## CDN
10
8
  ```html
11
9
  <script>
12
10
  window.HyperInstant = {
13
- mode: "balanced",
14
- adapter: "auto",
11
+ strategy: "auto",
12
+ hoverDelay: 65,
13
+ maxPrefetch: 6,
14
+ maxPrerender: 2,
15
+ sameSite: true, // allow www/non-www + subdomains (default true)
16
+ deny: ["/login","/logout","/customer/info","/cart","/shoppingcart","/checkout","addproducttocart"],
15
17
  debug: false
16
18
  };
17
19
  </script>
18
- <script src="https://unpkg.com/hyperinstant@1.0.2/dist/hyperinstant.min.js" defer></script>
19
- ```
20
-
21
- ## “Very fast” preset (nopCommerce)
22
- ```js
23
- mode: "aggressive",
24
- adapter: "nopcommerce",
25
- hoverDelay: 35,
26
- enablePrerender: false
20
+ <script src="https://unpkg.com/hyperinstant@1.1.1/dist/hyperinstant.min.js" defer></script>
27
21
  ```
@@ -1,20 +1,25 @@
1
- /*! HyperInstant v1.0.2 | MIT | Putia Web */
1
+ /*! HyperInstant v1.1.1 (legacy-fast) | MIT | Putia Web */
2
2
  (function () {
3
3
  if (window.__HYPERINSTANT_LOADED__) return;
4
4
  window.__HYPERINSTANT_LOADED__ = true;
5
5
 
6
- var userCfg = window.HyperInstant || {};
7
- var VERSION = "1.0.2";
6
+ var cfg = window.HyperInstant || {};
7
+ var VERSION = "1.1.1";
8
+
9
+ var strategy = (cfg.strategy || "auto").toLowerCase();
10
+ var hoverDelay = (typeof cfg.hoverDelay === "number") ? cfg.hoverDelay : 65;
11
+ var maxPrefetch = (typeof cfg.maxPrefetch === "number") ? cfg.maxPrefetch : 6;
12
+ var maxPrerender = (typeof cfg.maxPrerender === "number") ? cfg.maxPrerender : 2;
13
+ var deny = Array.isArray(cfg.deny) ? cfg.deny : [];
14
+ var debug = !!cfg.debug;
15
+ var sameSite = (cfg.sameSite === undefined) ? true : !!cfg.sameSite;
16
+
17
+ var prefetched = new Set();
18
+ var prerendered = new Set();
19
+ var inflight = new Set();
20
+ var usedPrefetch = 0;
21
+ var usedPrerender = 0;
8
22
 
9
- function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }
10
- function isSameOrigin(url) {
11
- try { return new URL(url, location.href).origin === location.origin; }
12
- catch (e) { return false; }
13
- }
14
- function normalizeUrl(url) {
15
- try { var u = new URL(url, location.href); u.hash = ""; return u.toString(); }
16
- catch (e) { return ""; }
17
- }
18
23
  function supportsPrerender() {
19
24
  try {
20
25
  return "relList" in HTMLLinkElement.prototype &&
@@ -23,367 +28,133 @@
23
28
  HTMLLinkElement.prototype.relList.supports("prerender");
24
29
  } catch (e) { return false; }
25
30
  }
26
- function getConn() {
27
- var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
28
- return {
29
- saveData: !!(c && c.saveData),
30
- effectiveType: (c && c.effectiveType) ? String(c.effectiveType) : "unknown",
31
- downlink: (c && typeof c.downlink === "number") ? c.downlink : null,
32
- rtt: (c && typeof c.rtt === "number") ? c.rtt : null
33
- };
34
- }
35
- function inNav(a) {
36
- try { return !!a.closest("nav, header, [role='navigation'], .header, .site-header"); }
37
- catch (e) { return false; }
38
- }
39
-
40
- function applyModeDefaults(cfg) {
41
- var mode = String(cfg.mode || "safe").toLowerCase();
42
- var out = Object.assign({}, cfg);
43
-
44
- // v1.0.2: prerender is intentionally conservative to avoid slowing pages
45
- if (mode === "aggressive") {
46
- out.maxPrefetch = out.maxPrefetch ?? 10;
47
- out.maxPrerender = out.maxPrerender ?? 1;
48
- out.hoverDelay = out.hoverDelay ?? 35;
49
- } else if (mode === "balanced") {
50
- out.maxPrefetch = out.maxPrefetch ?? 6;
51
- out.maxPrerender = out.maxPrerender ?? 0;
52
- out.hoverDelay = out.hoverDelay ?? 65;
53
- } else {
54
- out.maxPrefetch = out.maxPrefetch ?? 4;
55
- out.maxPrerender = out.maxPrerender ?? 0;
56
- out.hoverDelay = out.hoverDelay ?? 75;
57
- }
58
-
59
- out.adapter = out.adapter ?? "auto";
60
- out.observe = out.observe ?? "intent";
61
- out.events = out.events ?? ["mouseover", "touchstart", "mousedown", "focusin"];
62
- out.deny = out.deny ?? [];
63
- out.debug = !!out.debug;
64
- out.debugOverlay = !!out.debugOverlay;
65
-
66
- // new in 1.0.2
67
- out.enablePrerender = (out.enablePrerender === undefined) ? true : !!out.enablePrerender;
68
- out.autoTune = !!out.autoTune; // OFF by default
69
- out.maxQueue = clamp(out.maxQueue ?? 60, 10, 200);
70
31
 
71
- return out;
32
+ function normalizeUrl(url) {
33
+ try {
34
+ var u = new URL(url, location.href);
35
+ u.hash = "";
36
+ return u.toString();
37
+ } catch (e) { return ""; }
72
38
  }
73
39
 
74
- function hasMeta(name) { return !!document.querySelector('meta[name="' + name + '"]'); }
75
-
76
- function shopifyAdapter(cfg) {
77
- var deny = new Set([].concat(cfg.deny || []));
78
- ["/cart", "/checkout", "/account", "/account/login", "/account/register", "/account/logout", "/search",
79
- "cart/add", "cart/change", "cart/update", "variant=", "sections="].forEach(function (x) { deny.add(x); });
80
- return Object.assign({}, cfg, { deny: Array.from(deny) });
40
+ function baseHost(hostname) {
41
+ hostname = String(hostname || "").toLowerCase();
42
+ return hostname.replace(/^www\./, "");
81
43
  }
82
44
 
83
- function wooAdapter(cfg) {
84
- var deny = new Set([].concat(cfg.deny || []));
85
- ["/cart", "/checkout", "/my-account", "add-to-cart", "wc-ajax", "remove_item", "update_cart", "apply_coupon"].forEach(function (x) { deny.add(x); });
86
- return Object.assign({}, cfg, { deny: Array.from(deny) });
87
- }
45
+ function isAllowedOrigin(url) {
46
+ try {
47
+ var u = new URL(url, location.href);
48
+ if (u.origin === location.origin) return true;
88
49
 
89
- function nopAdapter(cfg) {
90
- var deny = new Set([].concat(cfg.deny || []));
91
- ["/shoppingcart", "/checkout", "/login", "/register", "/logout", "/customer", "/wishlist", "/compareproducts",
92
- "/search", "?q=", "addproducttocart", "updateshoppingcart", "updatecart", "deleteitem", "returnurl=", "token=", "ajax", "flyout"].forEach(function (x) { deny.add(x); });
93
- return Object.assign({}, cfg, { deny: Array.from(deny) });
94
- }
50
+ if (!sameSite) return false;
95
51
 
96
- function detectAdapter(name) {
97
- name = String(name || "auto").toLowerCase();
98
- if (name === "shopify") return shopifyAdapter;
99
- if (name === "woocommerce") return wooAdapter;
100
- if (name === "nopcommerce") return nopAdapter;
101
- if (name === "generic") return function (c) { return c; };
52
+ // allow www/non-www and subdomains for same base host
53
+ var here = baseHost(location.hostname);
54
+ var there = baseHost(u.hostname);
55
+ if (there === here) return true;
56
+ if (there.endsWith("." + here)) return true;
57
+ if (here.endsWith("." + there)) return true; // just in case
102
58
 
103
- try { if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter; } catch (e) { }
104
- try { if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body && String(document.body.className || "").includes("woocommerce"))) return wooAdapter; } catch (e) { }
105
- try { if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter; } catch (e) { }
106
- return function (c) { return c; };
59
+ return false;
60
+ } catch (e) {
61
+ return false;
62
+ }
107
63
  }
108
64
 
109
- // Safer auto-deny (avoid broad keywords like "order" that can hit benign pages)
110
- var AUTO_PATH_PREFIX = ["/checkout", "/cart", "/shoppingcart", "/account", "/my-account", "/logout", "/login", "/register"];
111
- var AUTO_PARAMS = ["add", "remove", "update", "change", "delete", "token", "variant", "sections", "returnurl", "redirect"];
112
- var AUTO_KEYWORDS = ["add-to-cart", "addtocart", "addproducttocart", "wc-ajax", "admin-ajax", "sections=", "variant="];
113
-
114
- function shouldDeny(url, cfg) {
65
+ function isDenied(url) {
115
66
  if (!url) return true;
116
- if (!isSameOrigin(url)) return true;
67
+ if (!isAllowedOrigin(url)) return true;
117
68
 
118
69
  var u = String(url).toLowerCase();
119
-
120
- var denyList = cfg.deny || [];
121
- for (var i = 0; i < denyList.length; i++) {
122
- var r = denyList[i];
123
- if (r && u.includes(String(r).toLowerCase())) return true;
70
+ for (var i = 0; i < deny.length; i++) {
71
+ var d = deny[i];
72
+ if (d && u.indexOf(String(d).toLowerCase()) !== -1) return true;
124
73
  }
125
-
126
- try {
127
- var p = new URL(url, location.href).pathname.toLowerCase();
128
- for (var j = 0; j < AUTO_PATH_PREFIX.length; j++) {
129
- var ap = AUTO_PATH_PREFIX[j];
130
- if (p === ap || p.startsWith(ap + "/")) return true;
131
- }
132
- } catch (e) { }
133
-
134
- for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true;
135
-
136
- try {
137
- var uu = new URL(url, location.href);
138
- var sp = uu.searchParams;
139
- for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;
140
- } catch (e) { }
141
-
142
74
  return false;
143
75
  }
144
76
 
145
- function Stats() {
146
- this.startedAt = Date.now();
147
- this.seen = 0;
148
- this.denied = 0;
149
- this.enqueued = 0;
150
- this.prefetched = 0;
151
- this.prerendered = 0;
152
- this.clicked = 0;
153
- this.hit = 0;
154
- this._loaded = new Set();
155
- }
156
- Stats.prototype.isLoaded = function (url) { return this._loaded.has(url); };
157
- Stats.prototype.markLoaded = function (url, type) {
158
- if (this._loaded.has(url)) return;
159
- this._loaded.add(url);
160
- if (type === "prerender") this.prerendered++; else this.prefetched++;
161
- };
162
- Stats.prototype.markClick = function (url) { this.clicked++; if (this._loaded.has(url)) this.hit++; };
163
- Object.defineProperty(Stats.prototype, "hitRate", { get: function () { var d = this.prefetched + this.prerendered; return d ? (this.hit / d) : 0; } });
164
- Stats.prototype.snapshot = function () {
165
- return {
166
- version: VERSION,
167
- uptimeSec: Math.round((Date.now() - this.startedAt) / 1000),
168
- seen: this.seen,
169
- denied: this.denied,
170
- enqueued: this.enqueued,
171
- prefetched: this.prefetched,
172
- prerendered: this.prerendered,
173
- clicked: this.clicked,
174
- hit: this.hit,
175
- hitRate: Number(this.hitRate.toFixed(3))
176
- };
177
- };
178
-
179
- function createScheduler(cfg, stats) {
180
- var q = [];
181
- var qSet = new Set();
182
- var inFlight = new Set();
183
- var usedPrefetch = 0;
184
- var usedPrerender = 0;
185
- var prerenderSupported = supportsPrerender();
186
-
187
- function budgets() {
188
- var mp = cfg.maxPrefetch;
189
- var mr = cfg.maxPrerender;
190
-
191
- if (!cfg.enablePrerender || !prerenderSupported) mr = 0;
192
-
193
- var ci = getConn();
194
- if (ci.saveData) { mr = 0; mp = Math.min(mp, 2); }
195
- if (String(ci.effectiveType).includes("2g")) { mr = 0; mp = Math.min(mp, 2); }
196
- if (String(ci.effectiveType).includes("3g")) { mr = 0; mp = Math.min(mp, 4); }
197
- if (typeof ci.rtt === "number" && ci.rtt > 350) { mr = 0; mp = Math.min(mp, 3); }
198
- if (typeof ci.downlink === "number" && ci.downlink < 1.2) { mr = 0; mp = Math.min(mp, 3); }
199
-
200
- mp = clamp(mp, 0, 12);
201
- mr = clamp(mr, 0, 3);
202
-
203
- return { mp: mp, mr: mr };
204
- }
205
-
206
- function enqueue(rawUrl, score, signal) {
207
- var url = normalizeUrl(rawUrl);
208
- if (!url) return;
209
- if (stats.isLoaded(url) || inFlight.has(url) || qSet.has(url)) return;
210
-
211
- if (q.length >= cfg.maxQueue) {
212
- var minIdx = 0;
213
- for (var i = 1; i < q.length; i++) if (q[i].score < q[minIdx].score) minIdx = i;
214
- var dropped = q.splice(minIdx, 1)[0];
215
- if (dropped) qSet.delete(dropped.url);
216
- }
217
-
218
- stats.enqueued++;
219
- q.push({ url: url, score: score, signal: signal, t: Date.now() });
220
- qSet.add(url);
221
- pump();
222
- }
223
-
224
- function pump() {
225
- var b = budgets();
226
-
227
- function takeBest() {
228
- if (!q.length) return null;
229
- var bestIdx = 0;
230
- for (var i = 1; i < q.length; i++) if (q[i].score > q[bestIdx].score) bestIdx = i;
231
- var item = q.splice(bestIdx, 1)[0];
232
- if (item) qSet.delete(item.url);
233
- return item;
234
- }
235
-
236
- while (q.length) {
237
- var item = takeBest();
238
- if (!item) break;
239
-
240
- var url = item.url;
241
- var score = item.score;
242
- var signal = item.signal;
243
-
244
- if (stats.isLoaded(url) || inFlight.has(url)) continue;
77
+ function enqueue(rawUrl) {
78
+ var url = normalizeUrl(rawUrl);
79
+ if (!url) return;
80
+ if (isDenied(url)) return;
81
+ if (prefetched.has(url) || prerendered.has(url) || inflight.has(url)) return;
245
82
 
246
- var canPrefetch = usedPrefetch < b.mp;
247
- var canPrerender = usedPrerender < b.mr;
83
+ var canPrerender = supportsPrerender() && (usedPrerender < maxPrerender);
84
+ var canPrefetch = (usedPrefetch < maxPrefetch);
248
85
 
249
- // v1.0.2: prerender only on HIGH confidence signals (not hover)
250
- var highIntent = (signal === "mousedown" || signal === "touchstart");
251
- var doPrerender = canPrerender && highIntent && score >= 110;
86
+ var rel = "prefetch";
87
+ if (strategy === "prefetch") rel = "prefetch";
88
+ else if (strategy === "prerender") rel = canPrerender ? "prerender" : "prefetch";
89
+ else rel = canPrerender ? "prerender" : "prefetch";
252
90
 
253
- if (!doPrerender && !canPrefetch) break;
91
+ if (rel === "prefetch" && !canPrefetch) return;
92
+ if (rel === "prerender" && !canPrerender) rel = canPrefetch ? "prefetch" : "";
93
+ if (!rel) return;
254
94
 
255
- var type = doPrerender ? "prerender" : "prefetch";
256
- if (type === "prerender") usedPrerender++; else usedPrefetch++;
95
+ inflight.add(url);
257
96
 
258
- inFlight.add(url);
259
- var link = document.createElement("link");
260
- link.rel = type;
261
- link.href = url;
262
- link.as = "document";
263
- link.fetchPriority = doPrerender ? "high" : (score >= 90 ? "auto" : "low");
97
+ var link = document.createElement("link");
98
+ link.rel = rel;
99
+ link.href = url;
100
+ link.as = "document";
264
101
 
265
- (function (u, t) {
266
- link.onload = function () { inFlight.delete(u); stats.markLoaded(u, t); };
267
- link.onerror = function () { inFlight.delete(u); };
268
- })(url, type);
102
+ if (rel === "prerender") usedPrerender++;
103
+ else usedPrefetch++;
269
104
 
270
- document.head.appendChild(link);
271
- }
272
- }
273
-
274
- window.addEventListener("pageshow", function () { usedPrefetch = 0; usedPrerender = 0; });
105
+ (function(u, r){
106
+ link.onload = function () {
107
+ inflight.delete(u);
108
+ if (r === "prerender") prerendered.add(u);
109
+ else prefetched.add(u);
110
+ if (debug) console.log("[HyperInstant]", r, u);
111
+ };
112
+ link.onerror = function () { inflight.delete(u); };
113
+ })(url, rel);
275
114
 
276
- return { enqueue: enqueue };
277
- }
278
-
279
- function maybeOverlay(cfg, stats) {
280
- if (!cfg.debug || !cfg.debugOverlay) return;
281
- var el = document.createElement("div");
282
- el.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px";
283
- function render() {
284
- var s = stats.snapshot();
285
- el.innerHTML =
286
- "<div style='font-weight:700;margin-bottom:6px'>HyperInstant " + s.version + "</div>" +
287
- "<div>Loaded: <b>" + (s.prefetched + s.prerendered) + "</b> (P:" + s.prefetched + " R:" + s.prerendered + ")</div>" +
288
- "<div>Hit-rate: <b>" + Math.round(s.hitRate * 100) + "%</b></div>" +
289
- "<div>HoverDelay: <b>" + cfg.hoverDelay + "ms</b></div>" +
290
- "<div style='opacity:.8;margin-top:6px'>Clicks: " + s.clicked + " • Seen: " + s.seen + "</div>";
291
- }
292
- render();
293
- document.addEventListener("DOMContentLoaded", function () { document.body.appendChild(el); });
294
- setInterval(render, 1200);
115
+ document.head.appendChild(link);
295
116
  }
296
117
 
297
- function eligibleLink(target) {
118
+ var t = null;
119
+ function getAnchor(target) {
298
120
  var a = target && target.closest ? target.closest("a[href]") : null;
299
121
  if (!a || !a.href) return null;
300
-
301
- var rel = String(a.getAttribute("rel") || "").toLowerCase();
302
- if (rel.includes("nofollow")) return null;
303
- if (a.getAttribute("data-no-instant") !== null) return null;
304
- if (a.getAttribute("data-hyperinstant") === "off") return null;
305
122
  if (a.target && a.target !== "_self") return null;
306
-
123
+ if (a.getAttribute("data-no-instant") !== null) return null;
307
124
  return a;
308
125
  }
309
126
 
310
- function score(signal, a) {
311
- var s = 60;
312
- if (signal === "mousedown") s = 120;
313
- else if (signal === "touchstart") s = 118;
314
- else if (signal === "focusin") s = 98;
315
- else if (signal === "mouseover") s = 82;
127
+ function onHover(e) {
128
+ var a = getAnchor(e.target);
129
+ if (!a) return;
316
130
 
317
- if (inNav(a)) s += 8;
318
- if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12;
319
- return Math.min(130, s);
131
+ clearTimeout(t);
132
+ t = setTimeout(function () { enqueue(a.href); }, hoverDelay);
320
133
  }
321
134
 
322
- function createTuner(cfg, stats) {
323
- var intentCount = 0;
324
- function maybeTune() {
325
- var total = stats.prefetched + stats.prerendered;
326
- if (total < 16) return;
327
- var hr = stats.hitRate;
328
-
329
- if (hr >= 0.40) cfg.hoverDelay = clamp(cfg.hoverDelay - 8, 20, 120);
330
- else if (hr <= 0.12) cfg.hoverDelay = clamp(cfg.hoverDelay + 10, 25, 180);
331
- }
332
- return { onEnqueue: function () { intentCount++; if (cfg.autoTune && intentCount % 25 === 0) maybeTune(); }, onClick: function () { } };
135
+ function onImmediate(e) {
136
+ var a = getAnchor(e.target);
137
+ if (!a) return;
138
+ enqueue(a.href);
333
139
  }
334
140
 
335
- function start(cfg, scheduler, stats, tuner) {
336
- var hoverTimer = null;
337
-
338
- function onIntent(e) {
339
- var a = eligibleLink(e.target);
340
- if (!a) return;
341
-
342
- var url = a.href;
343
- stats.seen++;
344
-
345
- if (shouldDeny(url, cfg)) { stats.denied++; return; }
141
+ // More reliable than mouseover in many modern themes
142
+ document.addEventListener("pointerover", onHover, { passive: true, capture: true });
143
+ document.addEventListener("touchstart", onImmediate, { passive: true, capture: true });
144
+ document.addEventListener("mousedown", onImmediate, { passive: true, capture: true });
145
+ document.addEventListener("focusin", onImmediate, { passive: true, capture: true });
346
146
 
347
- var sig = e.type;
348
- var sc = score(sig, a);
349
-
350
- clearTimeout(hoverTimer);
351
- var delay = (sig === "mouseover") ? cfg.hoverDelay : 0;
352
-
353
- hoverTimer = setTimeout(function () {
354
- scheduler.enqueue(url, sc, sig);
355
- tuner.onEnqueue();
356
- }, delay);
357
- }
358
-
359
- function onClick(e) {
360
- var a = eligibleLink(e.target);
361
- if (!a) return;
362
- stats.markClick(normalizeUrl(a.href));
363
- tuner.onClick();
364
- }
365
-
366
- (cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt) {
367
- document.addEventListener(evt, onIntent, { passive: true, capture: true });
368
- });
369
- document.addEventListener("click", onClick, { passive: true, capture: true });
370
- }
371
-
372
- var cfg = applyModeDefaults(userCfg);
373
- cfg = detectAdapter(cfg.adapter)(cfg);
374
-
375
- var stats = new Stats();
376
- var tuner = createTuner(cfg, stats);
377
- var scheduler = createScheduler(cfg, stats);
147
+ window.addEventListener("pageshow", function () {
148
+ usedPrefetch = 0;
149
+ usedPrerender = 0;
150
+ });
378
151
 
379
152
  window.HyperInstant = window.HyperInstant || {};
380
153
  window.HyperInstant.version = VERSION;
381
154
 
382
- if (cfg.debug) {
383
- window.HyperInstantStats = stats;
384
- console.log("[HyperInstant] v" + VERSION + " loaded", { mode: cfg.mode, adapter: cfg.adapter });
155
+ if (debug) {
156
+ console.log("[HyperInstant] v"+VERSION+" legacy-fast loaded", {
157
+ strategy: strategy, hoverDelay: hoverDelay, maxPrefetch: maxPrefetch, maxPrerender: maxPrerender, sameSite: sameSite
158
+ });
385
159
  }
386
-
387
- start(cfg, scheduler, stats, tuner);
388
- maybeOverlay(cfg, stats);
389
160
  })();
@@ -1 +1 @@
1
- /*! HyperInstant v1.0.2 | MIT | Putia Web */ (function (){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var userCfg = window.HyperInstant ||{}; var VERSION = "1.0.2"; function clamp(n, a, b){return Math.max(a, Math.min(b, n));} function isSameOrigin(url){try{return new URL(url, location.href).origin === location.origin;} catch (e){return false;}} function normalizeUrl(url){try{var u = new URL(url, location.href); u.hash = ""; return u.toString();} catch (e){return "";}} function supportsPrerender(){try{return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender");} catch (e){return false;}} function getConn(){var c = navigator.connection || navigator.mozConnection || navigator.webkitConnection; return{saveData: !!(c && c.saveData), effectiveType: (c && c.effectiveType) ? String(c.effectiveType) : "unknown", downlink: (c && typeof c.downlink === "number") ? c.downlink : null, rtt: (c && typeof c.rtt === "number") ? c.rtt : null};} function inNav(a){try{return !!a.closest("nav, header, [role='navigation'], .header, .site-header");} catch (e){return false;}} function applyModeDefaults(cfg){var mode = String(cfg.mode || "safe").toLowerCase(); var out = Object.assign({}, cfg); // v1.0.2: prerender is intentionally conservative to avoid slowing pages if (mode === "aggressive"){out.maxPrefetch = out.maxPrefetch ?? 10; out.maxPrerender = out.maxPrerender ?? 1; out.hoverDelay = out.hoverDelay ?? 35;} else if (mode === "balanced"){out.maxPrefetch = out.maxPrefetch ?? 6; out.maxPrerender = out.maxPrerender ?? 0; out.hoverDelay = out.hoverDelay ?? 65;} else{out.maxPrefetch = out.maxPrefetch ?? 4; out.maxPrerender = out.maxPrerender ?? 0; out.hoverDelay = out.hoverDelay ?? 75;} out.adapter = out.adapter ?? "auto"; out.observe = out.observe ?? "intent"; out.events = out.events ?? ["mouseover", "touchstart", "mousedown", "focusin"]; out.deny = out.deny ?? []; out.debug = !!out.debug; out.debugOverlay = !!out.debugOverlay; // new in 1.0.2 out.enablePrerender = (out.enablePrerender === undefined) ? true : !!out.enablePrerender; out.autoTune = !!out.autoTune; // OFF by default out.maxQueue = clamp(out.maxQueue ?? 60, 10, 200); return out;} function hasMeta(name){return !!document.querySelector('meta[name="' + name + '"]');} function shopifyAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/cart", "/checkout", "/account", "/account/login", "/account/register", "/account/logout", "/search", "cart/add", "cart/change", "cart/update", "variant=", "sections="].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function wooAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/cart", "/checkout", "/my-account", "add-to-cart", "wc-ajax", "remove_item", "update_cart", "apply_coupon"].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function nopAdapter(cfg){var deny = new Set([].concat(cfg.deny || [])); ["/shoppingcart", "/checkout", "/login", "/register", "/logout", "/customer", "/wishlist", "/compareproducts", "/search", "?q=", "addproducttocart", "updateshoppingcart", "updatecart", "deleteitem", "returnurl=", "token=", "ajax", "flyout"].forEach(function (x){deny.add(x);}); return Object.assign({}, cfg,{deny: Array.from(deny)});} function detectAdapter(name){name = String(name || "auto").toLowerCase(); if (name === "shopify") return shopifyAdapter; if (name === "woocommerce") return wooAdapter; if (name === "nopcommerce") return nopAdapter; if (name === "generic") return function (c){return c;}; try{if (window.Shopify || hasMeta("shopify-digital-wallet") || hasMeta("shopify-checkout-api-token")) return shopifyAdapter;} catch (e){} try{if (window.wc_add_to_cart_params || window.wc_cart_fragments_params || (document.body && String(document.body.className || "").includes("woocommerce"))) return wooAdapter;} catch (e){} try{if (window.nopCommerce || document.querySelector("a[href*='shoppingcart']") || document.querySelector("form[action*='addproducttocart']")) return nopAdapter;} catch (e){} return function (c){return c;};} // Safer auto-deny (avoid broad keywords like "order" that can hit benign pages) var AUTO_PATH_PREFIX = ["/checkout", "/cart", "/shoppingcart", "/account", "/my-account", "/logout", "/login", "/register"]; var AUTO_PARAMS = ["add", "remove", "update", "change", "delete", "token", "variant", "sections", "returnurl", "redirect"]; var AUTO_KEYWORDS = ["add-to-cart", "addtocart", "addproducttocart", "wc-ajax", "admin-ajax", "sections=", "variant="]; function shouldDeny(url, cfg){if (!url) return true; if (!isSameOrigin(url)) return true; var u = String(url).toLowerCase(); var denyList = cfg.deny || []; for (var i = 0; i < denyList.length; i++){var r = denyList[i]; if (r && u.includes(String(r).toLowerCase())) return true;} try{var p = new URL(url, location.href).pathname.toLowerCase(); for (var j = 0; j < AUTO_PATH_PREFIX.length; j++){var ap = AUTO_PATH_PREFIX[j]; if (p === ap || p.startsWith(ap + "/")) return true;}} catch (e){} for (var k = 0; k < AUTO_KEYWORDS.length; k++) if (u.includes(AUTO_KEYWORDS[k])) return true; try{var uu = new URL(url, location.href); var sp = uu.searchParams; for (var q = 0; q < AUTO_PARAMS.length; q++) if (sp.has(AUTO_PARAMS[q])) return true;} catch (e){} return false;} function Stats(){this.startedAt = Date.now(); this.seen = 0; this.denied = 0; this.enqueued = 0; this.prefetched = 0; this.prerendered = 0; this.clicked = 0; this.hit = 0; this._loaded = new Set();} Stats.prototype.isLoaded = function (url){return this._loaded.has(url);}; Stats.prototype.markLoaded = function (url, type){if (this._loaded.has(url)) return; this._loaded.add(url); if (type === "prerender") this.prerendered++; else this.prefetched++;}; Stats.prototype.markClick = function (url){this.clicked++; if (this._loaded.has(url)) this.hit++;}; Object.defineProperty(Stats.prototype, "hitRate",{get: function (){var d = this.prefetched + this.prerendered; return d ? (this.hit / d) : 0;}}); Stats.prototype.snapshot = function (){return{version: VERSION, uptimeSec: Math.round((Date.now() - this.startedAt) / 1000), seen: this.seen, denied: this.denied, enqueued: this.enqueued, prefetched: this.prefetched, prerendered: this.prerendered, clicked: this.clicked, hit: this.hit, hitRate: Number(this.hitRate.toFixed(3))};}; function createScheduler(cfg, stats){var q = []; var qSet = new Set(); var inFlight = new Set(); var usedPrefetch = 0; var usedPrerender = 0; var prerenderSupported = supportsPrerender(); function budgets(){var mp = cfg.maxPrefetch; var mr = cfg.maxPrerender; if (!cfg.enablePrerender || !prerenderSupported) mr = 0; var ci = getConn(); if (ci.saveData){mr = 0; mp = Math.min(mp, 2);} if (String(ci.effectiveType).includes("2g")){mr = 0; mp = Math.min(mp, 2);} if (String(ci.effectiveType).includes("3g")){mr = 0; mp = Math.min(mp, 4);} if (typeof ci.rtt === "number" && ci.rtt > 350){mr = 0; mp = Math.min(mp, 3);} if (typeof ci.downlink === "number" && ci.downlink < 1.2){mr = 0; mp = Math.min(mp, 3);} mp = clamp(mp, 0, 12); mr = clamp(mr, 0, 3); return{mp: mp, mr: mr};} function enqueue(rawUrl, score, signal){var url = normalizeUrl(rawUrl); if (!url) return; if (stats.isLoaded(url) || inFlight.has(url) || qSet.has(url)) return; if (q.length >= cfg.maxQueue){var minIdx = 0; for (var i = 1; i < q.length; i++) if (q[i].score < q[minIdx].score) minIdx = i; var dropped = q.splice(minIdx, 1)[0]; if (dropped) qSet.delete(dropped.url);} stats.enqueued++; q.push({url: url, score: score, signal: signal, t: Date.now()}); qSet.add(url); pump();} function pump(){var b = budgets(); function takeBest(){if (!q.length) return null; var bestIdx = 0; for (var i = 1; i < q.length; i++) if (q[i].score > q[bestIdx].score) bestIdx = i; var item = q.splice(bestIdx, 1)[0]; if (item) qSet.delete(item.url); return item;} while (q.length){var item = takeBest(); if (!item) break; var url = item.url; var score = item.score; var signal = item.signal; if (stats.isLoaded(url) || inFlight.has(url)) continue; var canPrefetch = usedPrefetch < b.mp; var canPrerender = usedPrerender < b.mr; // v1.0.2: prerender only on HIGH confidence signals (not hover) var highIntent = (signal === "mousedown" || signal === "touchstart"); var doPrerender = canPrerender && highIntent && score >= 110; if (!doPrerender && !canPrefetch) break; var type = doPrerender ? "prerender" : "prefetch"; if (type === "prerender") usedPrerender++; else usedPrefetch++; inFlight.add(url); var link = document.createElement("link"); link.rel = type; link.href = url; link.as = "document"; link.fetchPriority = doPrerender ? "high" : (score >= 90 ? "auto" : "low"); (function (u, t){link.onload = function (){inFlight.delete(u); stats.markLoaded(u, t);}; link.onerror = function (){inFlight.delete(u);};})(url, type); document.head.appendChild(link);}} window.addEventListener("pageshow", function (){usedPrefetch = 0; usedPrerender = 0;}); return{enqueue: enqueue};} function maybeOverlay(cfg, stats){if (!cfg.debug || !cfg.debugOverlay) return; var el = document.createElement("div"); el.style.cssText = "position:fixed;bottom:12px;right:12px;z-index:2147483647;background:rgba(0,0,0,.75);color:#fff;padding:10px 12px;border-radius:10px;font:12px/1.35 system-ui,-apple-system,Segoe UI,Roboto,Arial;box-shadow:0 10px 30px rgba(0,0,0,.35);max-width:280px"; function render(){var s = stats.snapshot(); el.innerHTML = "<div style='font-weight:700;margin-bottom:6px'>HyperInstant " + s.version + "</div>" + "<div>Loaded: <b>" + (s.prefetched + s.prerendered) + "</b> (P:" + s.prefetched + " R:" + s.prerendered + ")</div>" + "<div>Hit-rate: <b>" + Math.round(s.hitRate * 100) + "%</b></div>" + "<div>HoverDelay: <b>" + cfg.hoverDelay + "ms</b></div>" + "<div style='opacity:.8;margin-top:6px'>Clicks: " + s.clicked + " • Seen: " + s.seen + "</div>";} render(); document.addEventListener("DOMContentLoaded", function (){document.body.appendChild(el);}); setInterval(render, 1200);} function eligibleLink(target){var a = target && target.closest ? target.closest("a[href]") : null; if (!a || !a.href) return null; var rel = String(a.getAttribute("rel") || "").toLowerCase(); if (rel.includes("nofollow")) return null; if (a.getAttribute("data-no-instant") !== null) return null; if (a.getAttribute("data-hyperinstant") === "off") return null; if (a.target && a.target !== "_self") return null; return a;} function score(signal, a){var s = 60; if (signal === "mousedown") s = 120; else if (signal === "touchstart") s = 118; else if (signal === "focusin") s = 98; else if (signal === "mouseover") s = 82; if (inNav(a)) s += 8; if (String(a.getAttribute("data-hyperinstant-priority") || "").toLowerCase() === "high") s += 12; return Math.min(130, s);} function createTuner(cfg, stats){var intentCount = 0; function maybeTune(){var total = stats.prefetched + stats.prerendered; if (total < 16) return; var hr = stats.hitRate; if (hr >= 0.40) cfg.hoverDelay = clamp(cfg.hoverDelay - 8, 20, 120); else if (hr <= 0.12) cfg.hoverDelay = clamp(cfg.hoverDelay + 10, 25, 180);} return{onEnqueue: function (){intentCount++; if (cfg.autoTune && intentCount % 25 === 0) maybeTune();}, onClick: function (){}};} function start(cfg, scheduler, stats, tuner){var hoverTimer = null; function onIntent(e){var a = eligibleLink(e.target); if (!a) return; var url = a.href; stats.seen++; if (shouldDeny(url, cfg)){stats.denied++; return;} var sig = e.type; var sc = score(sig, a); clearTimeout(hoverTimer); var delay = (sig === "mouseover") ? cfg.hoverDelay : 0; hoverTimer = setTimeout(function (){scheduler.enqueue(url, sc, sig); tuner.onEnqueue();}, delay);} function onClick(e){var a = eligibleLink(e.target); if (!a) return; stats.markClick(normalizeUrl(a.href)); tuner.onClick();} (cfg.events || ["mouseover", "touchstart", "mousedown", "focusin"]).forEach(function (evt){document.addEventListener(evt, onIntent,{passive: true, capture: true});}); document.addEventListener("click", onClick,{passive: true, capture: true});} var cfg = applyModeDefaults(userCfg); cfg = detectAdapter(cfg.adapter)(cfg); var stats = new Stats(); var tuner = createTuner(cfg, stats); var scheduler = createScheduler(cfg, stats); window.HyperInstant = window.HyperInstant ||{}; window.HyperInstant.version = VERSION; if (cfg.debug){window.HyperInstantStats = stats; console.log("[HyperInstant] v" + VERSION + " loaded",{mode: cfg.mode, adapter: cfg.adapter});} start(cfg, scheduler, stats, tuner); maybeOverlay(cfg, stats);})();
1
+ /*! HyperInstant v1.1.1 (legacy-fast) | MIT | Putia Web */ (function (){if (window.__HYPERINSTANT_LOADED__) return; window.__HYPERINSTANT_LOADED__ = true; var cfg = window.HyperInstant ||{}; var VERSION = "1.1.1"; var strategy = (cfg.strategy || "auto").toLowerCase(); var hoverDelay = (typeof cfg.hoverDelay === "number") ? cfg.hoverDelay : 65; var maxPrefetch = (typeof cfg.maxPrefetch === "number") ? cfg.maxPrefetch : 6; var maxPrerender = (typeof cfg.maxPrerender === "number") ? cfg.maxPrerender : 2; var deny = Array.isArray(cfg.deny) ? cfg.deny : []; var debug = !!cfg.debug; var sameSite = (cfg.sameSite === undefined) ? true : !!cfg.sameSite; var prefetched = new Set(); var prerendered = new Set(); var inflight = new Set(); var usedPrefetch = 0; var usedPrerender = 0; function supportsPrerender(){try{return "relList" in HTMLLinkElement.prototype && HTMLLinkElement.prototype.relList && HTMLLinkElement.prototype.relList.supports && HTMLLinkElement.prototype.relList.supports("prerender");} catch (e){return false;}} function normalizeUrl(url){try{var u = new URL(url, location.href); u.hash = ""; return u.toString();} catch (e){return "";}} function baseHost(hostname){hostname = String(hostname || "").toLowerCase(); return hostname.replace(/^www\./, "");} function isAllowedOrigin(url){try{var u = new URL(url, location.href); if (u.origin === location.origin) return true; if (!sameSite) return false; // allow www/non-www and subdomains for same base host var here = baseHost(location.hostname); var there = baseHost(u.hostname); if (there === here) return true; if (there.endsWith("." + here)) return true; if (here.endsWith("." + there)) return true; // just in case return false;} catch (e){return false;}} function isDenied(url){if (!url) return true; if (!isAllowedOrigin(url)) return true; var u = String(url).toLowerCase(); for (var i = 0; i < deny.length; i++){var d = deny[i]; if (d && u.indexOf(String(d).toLowerCase()) !== -1) return true;} return false;} function enqueue(rawUrl){var url = normalizeUrl(rawUrl); if (!url) return; if (isDenied(url)) return; if (prefetched.has(url) || prerendered.has(url) || inflight.has(url)) return; var canPrerender = supportsPrerender() && (usedPrerender < maxPrerender); var canPrefetch = (usedPrefetch < maxPrefetch); var rel = "prefetch"; if (strategy === "prefetch") rel = "prefetch"; else if (strategy === "prerender") rel = canPrerender ? "prerender" : "prefetch"; else rel = canPrerender ? "prerender" : "prefetch"; if (rel === "prefetch" && !canPrefetch) return; if (rel === "prerender" && !canPrerender) rel = canPrefetch ? "prefetch" : ""; if (!rel) return; inflight.add(url); var link = document.createElement("link"); link.rel = rel; link.href = url; link.as = "document"; if (rel === "prerender") usedPrerender++; else usedPrefetch++; (function(u, r){link.onload = function (){inflight.delete(u); if (r === "prerender") prerendered.add(u); else prefetched.add(u); if (debug) console.log("[HyperInstant]", r, u);}; link.onerror = function (){inflight.delete(u);};})(url, rel); document.head.appendChild(link);} var t = null; function getAnchor(target){var a = target && target.closest ? target.closest("a[href]") : null; if (!a || !a.href) return null; if (a.target && a.target !== "_self") return null; if (a.getAttribute("data-no-instant") !== null) return null; return a;} function onHover(e){var a = getAnchor(e.target); if (!a) return; clearTimeout(t); t = setTimeout(function (){enqueue(a.href);}, hoverDelay);} function onImmediate(e){var a = getAnchor(e.target); if (!a) return; enqueue(a.href);} // More reliable than mouseover in many modern themes document.addEventListener("pointerover", onHover,{passive: true, capture: true}); document.addEventListener("touchstart", onImmediate,{passive: true, capture: true}); document.addEventListener("mousedown", onImmediate,{passive: true, capture: true}); document.addEventListener("focusin", onImmediate,{passive: true, capture: true}); window.addEventListener("pageshow", function (){usedPrefetch = 0; usedPrerender = 0;}); window.HyperInstant = window.HyperInstant ||{}; window.HyperInstant.version = VERSION; if (debug){console.log("[HyperInstant] v"+VERSION+" legacy-fast loaded",{strategy: strategy, hoverDelay: hoverDelay, maxPrefetch: maxPrefetch, maxPrerender: maxPrerender, sameSite: sameSite});}})();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hyperinstant",
3
- "version": "1.0.2",
4
- "description": "Adaptive, safe and intelligent instant navigation engine for any website.",
3
+ "version": "1.1.1",
4
+ "description": "HyperInstant legacy-fast build (0.1.0 behavior) with same-site support fixes.",
5
5
  "main": "dist/hyperinstant.min.js",
6
6
  "module": "dist/hyperinstant.js",
7
7
  "files": [
@@ -13,11 +13,7 @@
13
13
  "prerender",
14
14
  "instant-page",
15
15
  "web-performance",
16
- "shopify",
17
- "woocommerce",
18
- "nopcommerce",
19
- "adaptive",
20
- "navigation"
16
+ "legacy-fast"
21
17
  ],
22
18
  "author": "Putia Web",
23
19
  "license": "MIT"