hypha-debugger 0.1.5 → 0.1.6
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/dist/debugger.d.ts +1 -0
- package/dist/hypha-debugger.js +144 -9
- package/dist/hypha-debugger.js.map +1 -1
- package/dist/hypha-debugger.min.js +2 -2
- package/dist/hypha-debugger.min.js.map +1 -1
- package/dist/hypha-debugger.mjs +143 -10
- package/dist/hypha-debugger.mjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/services/navigate.d.ts +14 -0
- package/package.json +1 -1
package/dist/debugger.d.ts
CHANGED
package/dist/hypha-debugger.js
CHANGED
|
@@ -10614,8 +10614,6 @@
|
|
|
10614
10614
|
}
|
|
10615
10615
|
/**
|
|
10616
10616
|
* Inject the debugger loader script into HTML before </body> (or append).
|
|
10617
|
-
* Also injects a tiny inline script that reads sessionStorage and passes
|
|
10618
|
-
* config to the debugger via a global, so autoStart() picks it up.
|
|
10619
10617
|
*/
|
|
10620
10618
|
function injectLoader(html, scriptUrl) {
|
|
10621
10619
|
const loader = `<script src="${scriptUrl}"><\/script>`;
|
|
@@ -10629,8 +10627,8 @@
|
|
|
10629
10627
|
}
|
|
10630
10628
|
/**
|
|
10631
10629
|
* Perform a soft page replacement: fetch HTML, inject debugger script,
|
|
10632
|
-
* replace the document
|
|
10633
|
-
*
|
|
10630
|
+
* replace the document via document.write(). If the fetch or write fails,
|
|
10631
|
+
* falls back to hard navigation.
|
|
10634
10632
|
*/
|
|
10635
10633
|
function softReplace(url, pushState) {
|
|
10636
10634
|
const scriptUrl = getScriptUrl();
|
|
@@ -10668,6 +10666,133 @@
|
|
|
10668
10666
|
}
|
|
10669
10667
|
});
|
|
10670
10668
|
}
|
|
10669
|
+
/** Check if a URL is same-origin as the current page. */
|
|
10670
|
+
function isSameOrigin(url) {
|
|
10671
|
+
try {
|
|
10672
|
+
const target = new URL(url, location.href);
|
|
10673
|
+
return target.origin === location.origin;
|
|
10674
|
+
}
|
|
10675
|
+
catch {
|
|
10676
|
+
return false;
|
|
10677
|
+
}
|
|
10678
|
+
}
|
|
10679
|
+
// ── Global navigation interception ────────────────────────────────────
|
|
10680
|
+
let _interceptInstalled = false;
|
|
10681
|
+
/**
|
|
10682
|
+
* Install global listeners that intercept same-origin link clicks and
|
|
10683
|
+
* form submissions, routing them through soft navigation so the debugger
|
|
10684
|
+
* stays connected.
|
|
10685
|
+
*
|
|
10686
|
+
* Called once from HyphaDebugger.start().
|
|
10687
|
+
*/
|
|
10688
|
+
function installNavigationInterceptor() {
|
|
10689
|
+
if (_interceptInstalled)
|
|
10690
|
+
return () => { };
|
|
10691
|
+
_interceptInstalled = true;
|
|
10692
|
+
/**
|
|
10693
|
+
* Click handler: intercept <a> clicks that would navigate to a
|
|
10694
|
+
* same-origin HTML page.
|
|
10695
|
+
*/
|
|
10696
|
+
const onClick = (e) => {
|
|
10697
|
+
// Skip if modifier keys (new tab, etc.) or not left-click
|
|
10698
|
+
if (e.defaultPrevented || e.button !== 0)
|
|
10699
|
+
return;
|
|
10700
|
+
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey)
|
|
10701
|
+
return;
|
|
10702
|
+
// Walk up from target to find the nearest <a>
|
|
10703
|
+
let anchor = null;
|
|
10704
|
+
let el = e.target;
|
|
10705
|
+
while (el) {
|
|
10706
|
+
if (el.tagName === "A") {
|
|
10707
|
+
anchor = el;
|
|
10708
|
+
break;
|
|
10709
|
+
}
|
|
10710
|
+
el = el.parentElement;
|
|
10711
|
+
}
|
|
10712
|
+
if (!anchor)
|
|
10713
|
+
return;
|
|
10714
|
+
const href = anchor.href;
|
|
10715
|
+
if (!href)
|
|
10716
|
+
return;
|
|
10717
|
+
// Skip non-http(s), download links, target=_blank, javascript:, #hash-only
|
|
10718
|
+
if (anchor.target && anchor.target !== "_self")
|
|
10719
|
+
return;
|
|
10720
|
+
if (anchor.hasAttribute("download"))
|
|
10721
|
+
return;
|
|
10722
|
+
if (href.startsWith("javascript:") || href.startsWith("mailto:") || href.startsWith("tel:"))
|
|
10723
|
+
return;
|
|
10724
|
+
// Skip hash-only links (same page anchor)
|
|
10725
|
+
try {
|
|
10726
|
+
const target = new URL(href, location.href);
|
|
10727
|
+
if (target.origin === location.origin &&
|
|
10728
|
+
target.pathname === location.pathname &&
|
|
10729
|
+
target.search === location.search &&
|
|
10730
|
+
target.hash !== location.hash) {
|
|
10731
|
+
return; // Just a hash change, let browser handle it
|
|
10732
|
+
}
|
|
10733
|
+
}
|
|
10734
|
+
catch {
|
|
10735
|
+
return;
|
|
10736
|
+
}
|
|
10737
|
+
// Skip cross-origin
|
|
10738
|
+
if (!isSameOrigin(href))
|
|
10739
|
+
return;
|
|
10740
|
+
// Intercept: prevent default navigation and do soft replace
|
|
10741
|
+
e.preventDefault();
|
|
10742
|
+
const targetUrl = new URL(href, location.href).href;
|
|
10743
|
+
softReplace(targetUrl, targetUrl);
|
|
10744
|
+
};
|
|
10745
|
+
/**
|
|
10746
|
+
* Submit handler: intercept form submissions to same-origin action URLs.
|
|
10747
|
+
* Only handles GET forms (POST forms need the request body which is harder
|
|
10748
|
+
* to replicate via fetch).
|
|
10749
|
+
*/
|
|
10750
|
+
const onSubmit = (e) => {
|
|
10751
|
+
if (e.defaultPrevented)
|
|
10752
|
+
return;
|
|
10753
|
+
const form = e.target;
|
|
10754
|
+
const method = (form.method || "GET").toUpperCase();
|
|
10755
|
+
// Only intercept GET forms — POST forms are too complex to replicate
|
|
10756
|
+
if (method !== "GET")
|
|
10757
|
+
return;
|
|
10758
|
+
const action = form.action || location.href;
|
|
10759
|
+
if (!isSameOrigin(action))
|
|
10760
|
+
return;
|
|
10761
|
+
// Build the URL with form data as query params
|
|
10762
|
+
const formData = new FormData(form);
|
|
10763
|
+
const url = new URL(action, location.href);
|
|
10764
|
+
for (const [key, value] of formData.entries()) {
|
|
10765
|
+
if (typeof value === "string") {
|
|
10766
|
+
url.searchParams.set(key, value);
|
|
10767
|
+
}
|
|
10768
|
+
}
|
|
10769
|
+
// Skip if target is _blank or similar
|
|
10770
|
+
if (form.target && form.target !== "_self")
|
|
10771
|
+
return;
|
|
10772
|
+
e.preventDefault();
|
|
10773
|
+
softReplace(url.href, url.href);
|
|
10774
|
+
};
|
|
10775
|
+
/**
|
|
10776
|
+
* Popstate handler: intercept browser back/forward (bfcache miss).
|
|
10777
|
+
* When the browser navigates via back/forward and there's no bfcache,
|
|
10778
|
+
* we can catch it via popstate and do a soft load of the target URL.
|
|
10779
|
+
*/
|
|
10780
|
+
const onPopState = () => {
|
|
10781
|
+
// The URL has already changed when popstate fires.
|
|
10782
|
+
// Do a soft load of the current URL (which is the target of back/forward).
|
|
10783
|
+
softReplace(location.href);
|
|
10784
|
+
};
|
|
10785
|
+
document.addEventListener("click", onClick, true); // capture phase
|
|
10786
|
+
document.addEventListener("submit", onSubmit, true);
|
|
10787
|
+
window.addEventListener("popstate", onPopState);
|
|
10788
|
+
// Return cleanup function
|
|
10789
|
+
return () => {
|
|
10790
|
+
document.removeEventListener("click", onClick, true);
|
|
10791
|
+
document.removeEventListener("submit", onSubmit, true);
|
|
10792
|
+
window.removeEventListener("popstate", onPopState);
|
|
10793
|
+
_interceptInstalled = false;
|
|
10794
|
+
};
|
|
10795
|
+
}
|
|
10671
10796
|
// ── navigate ──────────────────────────────────────────────────────────
|
|
10672
10797
|
function navigate(url) {
|
|
10673
10798
|
try {
|
|
@@ -10718,7 +10843,7 @@
|
|
|
10718
10843
|
window.history.back();
|
|
10719
10844
|
return {
|
|
10720
10845
|
success: true,
|
|
10721
|
-
message: "Navigated back (
|
|
10846
|
+
message: "Navigated back (debugger will auto-reconnect via popstate)",
|
|
10722
10847
|
};
|
|
10723
10848
|
}
|
|
10724
10849
|
catch (err) {
|
|
@@ -10730,7 +10855,7 @@
|
|
|
10730
10855
|
}
|
|
10731
10856
|
goBack.__schema__ = {
|
|
10732
10857
|
name: "goBack",
|
|
10733
|
-
description: "Navigate back in browser history. The debugger
|
|
10858
|
+
description: "Navigate back in browser history. The debugger auto-reconnects for same-origin pages.",
|
|
10734
10859
|
parameters: {
|
|
10735
10860
|
type: "object",
|
|
10736
10861
|
properties: {},
|
|
@@ -10741,7 +10866,7 @@
|
|
|
10741
10866
|
window.history.forward();
|
|
10742
10867
|
return {
|
|
10743
10868
|
success: true,
|
|
10744
|
-
message: "Navigated forward (
|
|
10869
|
+
message: "Navigated forward (debugger will auto-reconnect via popstate)",
|
|
10745
10870
|
};
|
|
10746
10871
|
}
|
|
10747
10872
|
catch (err) {
|
|
@@ -10753,7 +10878,7 @@
|
|
|
10753
10878
|
}
|
|
10754
10879
|
goForward.__schema__ = {
|
|
10755
10880
|
name: "goForward",
|
|
10756
|
-
description: "Navigate forward in browser history. The debugger
|
|
10881
|
+
description: "Navigate forward in browser history. The debugger auto-reconnects for same-origin pages.",
|
|
10757
10882
|
parameters: {
|
|
10758
10883
|
type: "object",
|
|
10759
10884
|
properties: {},
|
|
@@ -13895,6 +14020,7 @@
|
|
|
13895
14020
|
this.server = null;
|
|
13896
14021
|
this.serviceInfo = null;
|
|
13897
14022
|
this.boundBeforeUnload = null;
|
|
14023
|
+
this.cleanupInterceptor = null;
|
|
13898
14024
|
const requireToken = config.require_token ?? false;
|
|
13899
14025
|
// Always append random suffix unless user provided a custom id.
|
|
13900
14026
|
let serviceId = config.service_id ?? "web-debugger";
|
|
@@ -13958,6 +14084,9 @@
|
|
|
13958
14084
|
this.saveConfigToStorage();
|
|
13959
14085
|
this.boundBeforeUnload = () => this.saveConfigToStorage();
|
|
13960
14086
|
window.addEventListener("beforeunload", this.boundBeforeUnload);
|
|
14087
|
+
// Intercept same-origin link clicks, form submits, and popstate
|
|
14088
|
+
// so the debugger survives user-initiated navigation
|
|
14089
|
+
this.cleanupInterceptor = installNavigationInterceptor();
|
|
13961
14090
|
return session;
|
|
13962
14091
|
}
|
|
13963
14092
|
catch (err) {
|
|
@@ -13973,11 +14102,15 @@
|
|
|
13973
14102
|
}
|
|
13974
14103
|
}
|
|
13975
14104
|
async destroy() {
|
|
13976
|
-
// Remove
|
|
14105
|
+
// Remove event listeners
|
|
13977
14106
|
if (this.boundBeforeUnload) {
|
|
13978
14107
|
window.removeEventListener("beforeunload", this.boundBeforeUnload);
|
|
13979
14108
|
this.boundBeforeUnload = null;
|
|
13980
14109
|
}
|
|
14110
|
+
if (this.cleanupInterceptor) {
|
|
14111
|
+
this.cleanupInterceptor();
|
|
14112
|
+
this.cleanupInterceptor = null;
|
|
14113
|
+
}
|
|
13981
14114
|
try {
|
|
13982
14115
|
if (this.serviceInfo && this.server) {
|
|
13983
14116
|
await this.server.unregisterService(this.serviceInfo.id);
|
|
@@ -14386,6 +14519,7 @@
|
|
|
14386
14519
|
exports.goForward = goForward;
|
|
14387
14520
|
exports.inputText = inputText;
|
|
14388
14521
|
exports.installConsoleCapture = installConsoleCapture;
|
|
14522
|
+
exports.installNavigationInterceptor = installNavigationInterceptor;
|
|
14389
14523
|
exports.navigate = navigate;
|
|
14390
14524
|
exports.queryDom = queryDom;
|
|
14391
14525
|
exports.reload = reload;
|
|
@@ -14393,6 +14527,7 @@
|
|
|
14393
14527
|
exports.scroll = scroll;
|
|
14394
14528
|
exports.scrollTo = scrollTo;
|
|
14395
14529
|
exports.selectOption = selectOption;
|
|
14530
|
+
exports.softReplace = softReplace;
|
|
14396
14531
|
exports.startDebugger = startDebugger;
|
|
14397
14532
|
exports.takeScreenshot = takeScreenshot;
|
|
14398
14533
|
exports.wrapFn = wrapFn;
|