hypha-debugger 0.1.5 → 0.1.7
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 +163 -11
- 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 +162 -12
- 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";
|
|
@@ -13943,7 +14069,20 @@
|
|
|
13943
14069
|
connectConfig.workspace = this.config.workspace;
|
|
13944
14070
|
if (this.config.token)
|
|
13945
14071
|
connectConfig.token = this.config.token;
|
|
13946
|
-
|
|
14072
|
+
try {
|
|
14073
|
+
this.server = await connect(connectConfig);
|
|
14074
|
+
}
|
|
14075
|
+
catch (connErr) {
|
|
14076
|
+
// If connecting to a saved workspace fails (e.g. expired/garbage-collected),
|
|
14077
|
+
// retry without the workspace to get a fresh one.
|
|
14078
|
+
if (this.config.workspace) {
|
|
14079
|
+
console.warn(`[hypha-debugger] Failed to rejoin workspace "${this.config.workspace}", getting a fresh one:`, connErr.message ?? connErr);
|
|
14080
|
+
this.server = await connect({ server_url: this.config.server_url });
|
|
14081
|
+
}
|
|
14082
|
+
else {
|
|
14083
|
+
throw connErr;
|
|
14084
|
+
}
|
|
14085
|
+
}
|
|
13947
14086
|
// Register debug service
|
|
13948
14087
|
this.serviceInfo = await this.server.registerService(this.buildServiceDefinition());
|
|
13949
14088
|
// Update overlay and build session
|
|
@@ -13958,6 +14097,9 @@
|
|
|
13958
14097
|
this.saveConfigToStorage();
|
|
13959
14098
|
this.boundBeforeUnload = () => this.saveConfigToStorage();
|
|
13960
14099
|
window.addEventListener("beforeunload", this.boundBeforeUnload);
|
|
14100
|
+
// Intercept same-origin link clicks, form submits, and popstate
|
|
14101
|
+
// so the debugger survives user-initiated navigation
|
|
14102
|
+
this.cleanupInterceptor = installNavigationInterceptor();
|
|
13961
14103
|
return session;
|
|
13962
14104
|
}
|
|
13963
14105
|
catch (err) {
|
|
@@ -13973,11 +14115,15 @@
|
|
|
13973
14115
|
}
|
|
13974
14116
|
}
|
|
13975
14117
|
async destroy() {
|
|
13976
|
-
// Remove
|
|
14118
|
+
// Remove event listeners
|
|
13977
14119
|
if (this.boundBeforeUnload) {
|
|
13978
14120
|
window.removeEventListener("beforeunload", this.boundBeforeUnload);
|
|
13979
14121
|
this.boundBeforeUnload = null;
|
|
13980
14122
|
}
|
|
14123
|
+
if (this.cleanupInterceptor) {
|
|
14124
|
+
this.cleanupInterceptor();
|
|
14125
|
+
this.cleanupInterceptor = null;
|
|
14126
|
+
}
|
|
13981
14127
|
try {
|
|
13982
14128
|
if (this.serviceInfo && this.server) {
|
|
13983
14129
|
await this.server.unregisterService(this.serviceInfo.id);
|
|
@@ -14011,10 +14157,14 @@
|
|
|
14011
14157
|
*/
|
|
14012
14158
|
saveConfigToStorage() {
|
|
14013
14159
|
try {
|
|
14160
|
+
// Save workspace so we can rejoin the same one (keeps URL stable),
|
|
14161
|
+
// but never save the token — anonymous workspace tokens expire and
|
|
14162
|
+
// cause "Permission denied" on reconnect. For anonymous workspaces
|
|
14163
|
+
// no token is needed to rejoin; for authenticated workspaces the
|
|
14164
|
+
// user must provide a fresh token via data attributes or config.
|
|
14014
14165
|
const data = {
|
|
14015
14166
|
server_url: this.config.server_url,
|
|
14016
14167
|
workspace: this.server?.config?.workspace ?? this.config.workspace,
|
|
14017
|
-
token: this.config.token,
|
|
14018
14168
|
service_id: this.config.service_id,
|
|
14019
14169
|
service_name: this.config.service_name,
|
|
14020
14170
|
show_ui: this.config.show_ui,
|
|
@@ -14386,6 +14536,7 @@
|
|
|
14386
14536
|
exports.goForward = goForward;
|
|
14387
14537
|
exports.inputText = inputText;
|
|
14388
14538
|
exports.installConsoleCapture = installConsoleCapture;
|
|
14539
|
+
exports.installNavigationInterceptor = installNavigationInterceptor;
|
|
14389
14540
|
exports.navigate = navigate;
|
|
14390
14541
|
exports.queryDom = queryDom;
|
|
14391
14542
|
exports.reload = reload;
|
|
@@ -14393,6 +14544,7 @@
|
|
|
14393
14544
|
exports.scroll = scroll;
|
|
14394
14545
|
exports.scrollTo = scrollTo;
|
|
14395
14546
|
exports.selectOption = selectOption;
|
|
14547
|
+
exports.softReplace = softReplace;
|
|
14396
14548
|
exports.startDebugger = startDebugger;
|
|
14397
14549
|
exports.takeScreenshot = takeScreenshot;
|
|
14398
14550
|
exports.wrapFn = wrapFn;
|