@quanta-intellect/vessel-browser 0.1.69 → 0.1.73
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 +14 -2
- package/out/main/index.js +2192 -416
- package/out/preload/index.js +52 -0
- package/out/renderer/assets/{index-BYk0kH1i.js → index-C2iAShPa.js} +1518 -879
- package/out/renderer/assets/{index-B_C8ayic.css → index-MB-_tN9U.css} +266 -7
- package/out/renderer/index.html +2 -2
- package/package.json +1 -1
package/out/main/index.js
CHANGED
|
@@ -77,7 +77,7 @@ const defaults = {
|
|
|
77
77
|
};
|
|
78
78
|
const SAVE_DEBOUNCE_MS$6 = 150;
|
|
79
79
|
const CHAT_PROVIDER_SECRET_FILENAME = "vessel-chat-provider-secret";
|
|
80
|
-
const logger$
|
|
80
|
+
const logger$k = createLogger("Settings");
|
|
81
81
|
const SETTABLE_KEYS = new Set(Object.keys(defaults));
|
|
82
82
|
let settings = null;
|
|
83
83
|
let settingsIssues = [];
|
|
@@ -223,7 +223,7 @@ function persistNow() {
|
|
|
223
223
|
getSettingsPath(),
|
|
224
224
|
JSON.stringify(buildPersistedSettings(settings), null, 2)
|
|
225
225
|
)
|
|
226
|
-
).catch((err) => logger$
|
|
226
|
+
).catch((err) => logger$k.error("Failed to save settings:", err));
|
|
227
227
|
}
|
|
228
228
|
function saveSettings() {
|
|
229
229
|
saveDirty = true;
|
|
@@ -327,7 +327,7 @@ function assertPermittedNavigationURL(url) {
|
|
|
327
327
|
}
|
|
328
328
|
const MAX_CUSTOM_HISTORY = 50;
|
|
329
329
|
const READER_MODE_DATA_URL_PREFIX = "data:text/html;charset=utf-8,";
|
|
330
|
-
const logger$
|
|
330
|
+
const logger$j = createLogger("Tab");
|
|
331
331
|
class Tab {
|
|
332
332
|
id;
|
|
333
333
|
view;
|
|
@@ -339,6 +339,7 @@ class Tab {
|
|
|
339
339
|
onHighlightSelection;
|
|
340
340
|
onHighlightRemove;
|
|
341
341
|
onHighlightRecolor;
|
|
342
|
+
onSavePage;
|
|
342
343
|
_highlightModeActive = false;
|
|
343
344
|
_readerOriginalUrl = null;
|
|
344
345
|
// Fully custom URL history — we never rely on Chromium's native back/forward
|
|
@@ -368,7 +369,7 @@ class Tab {
|
|
|
368
369
|
guardedLoadURL(url, options) {
|
|
369
370
|
const blockReason = this.getNavigationBlockReason(url);
|
|
370
371
|
if (blockReason) {
|
|
371
|
-
logger$
|
|
372
|
+
logger$j.warn(blockReason);
|
|
372
373
|
return blockReason;
|
|
373
374
|
}
|
|
374
375
|
void this.view.webContents.loadURL(url, options);
|
|
@@ -383,14 +384,18 @@ class Tab {
|
|
|
383
384
|
this.onHighlightSelection = options?.onHighlightSelection;
|
|
384
385
|
this.onHighlightRemove = options?.onHighlightRemove;
|
|
385
386
|
this.onHighlightRecolor = options?.onHighlightRecolor;
|
|
386
|
-
this.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
387
|
+
this.onSavePage = options?.onSavePage;
|
|
388
|
+
const webPreferences = {
|
|
389
|
+
preload: path.join(__dirname, "../preload/content-script.js"),
|
|
390
|
+
sandbox: true,
|
|
391
|
+
contextIsolation: true,
|
|
392
|
+
nodeIntegration: false,
|
|
393
|
+
spellcheck: false
|
|
394
|
+
};
|
|
395
|
+
if (options?.sessionPartition) {
|
|
396
|
+
webPreferences.session = electron.session.fromPartition(options.sessionPartition);
|
|
397
|
+
}
|
|
398
|
+
this.view = new electron.WebContentsView({ webPreferences });
|
|
394
399
|
const initialUrl = url || "about:blank";
|
|
395
400
|
this._state = {
|
|
396
401
|
id,
|
|
@@ -402,13 +407,31 @@ class Tab {
|
|
|
402
407
|
canGoForward: false,
|
|
403
408
|
isReaderMode: false,
|
|
404
409
|
adBlockingEnabled: options?.adBlockingEnabled ?? true,
|
|
410
|
+
isPinned: false,
|
|
411
|
+
isAudible: false,
|
|
412
|
+
isMuted: false,
|
|
405
413
|
role: options?.role
|
|
406
414
|
};
|
|
407
|
-
this.view.webContents.on("before-input-event", (
|
|
415
|
+
this.view.webContents.on("before-input-event", (event, input) => {
|
|
408
416
|
if (!input.control && !input.meta) return;
|
|
409
417
|
if (input.type !== "keyDown") return;
|
|
410
418
|
const key = input.key.toLowerCase();
|
|
411
419
|
const wc = this.view.webContents;
|
|
420
|
+
if (key === "+" || key === "=") {
|
|
421
|
+
this.zoomIn();
|
|
422
|
+
event.preventDefault();
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
if (key === "-") {
|
|
426
|
+
this.zoomOut();
|
|
427
|
+
event.preventDefault();
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
if (key === "0") {
|
|
431
|
+
this.zoomReset();
|
|
432
|
+
event.preventDefault();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
412
435
|
if (key === "c") wc.copy();
|
|
413
436
|
else if (key === "v") wc.paste();
|
|
414
437
|
else if (key === "x") wc.cut();
|
|
@@ -429,7 +452,7 @@ class Tab {
|
|
|
429
452
|
wc.setWindowOpenHandler(({ url, disposition }) => {
|
|
430
453
|
const error = this.getNavigationBlockReason(url);
|
|
431
454
|
if (error) {
|
|
432
|
-
logger$
|
|
455
|
+
logger$j.warn(error);
|
|
433
456
|
return { action: "deny" };
|
|
434
457
|
}
|
|
435
458
|
this.onOpenUrl?.({
|
|
@@ -443,7 +466,7 @@ class Tab {
|
|
|
443
466
|
const error = this.getNavigationBlockReason(url);
|
|
444
467
|
if (!error) return;
|
|
445
468
|
event.preventDefault();
|
|
446
|
-
logger$
|
|
469
|
+
logger$j.warn(`${context}: ${error}`);
|
|
447
470
|
};
|
|
448
471
|
wc.on("will-navigate", (event, url) => {
|
|
449
472
|
blockNavigation(event, url, "Blocked top-level navigation");
|
|
@@ -507,12 +530,25 @@ class Tab {
|
|
|
507
530
|
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.12); border-radius: 999px; }
|
|
508
531
|
::-webkit-scrollbar-thumb:hover { background: rgba(255,255,255,0.22); }
|
|
509
532
|
::-webkit-scrollbar-corner { background: transparent; }
|
|
510
|
-
`).catch((err) => logger$
|
|
533
|
+
`).catch((err) => logger$j.warn("Failed to inject scrollbar CSS:", err));
|
|
511
534
|
});
|
|
512
535
|
wc.on("page-favicon-updated", (_, favicons) => {
|
|
513
536
|
this._state.favicon = favicons[0] || "";
|
|
514
537
|
this.onChange();
|
|
515
538
|
});
|
|
539
|
+
wc.on("media-started-playing", () => {
|
|
540
|
+
this._state.isAudible = true;
|
|
541
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
542
|
+
this.onChange();
|
|
543
|
+
});
|
|
544
|
+
wc.on("media-paused", () => {
|
|
545
|
+
setTimeout(() => {
|
|
546
|
+
if (wc.isDestroyed()) return;
|
|
547
|
+
this._state.isAudible = wc.isCurrentlyAudible();
|
|
548
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
549
|
+
this.onChange();
|
|
550
|
+
}, 250);
|
|
551
|
+
});
|
|
516
552
|
wc.on("context-menu", (_event, params) => {
|
|
517
553
|
const x = params.x;
|
|
518
554
|
const y = params.y;
|
|
@@ -530,7 +566,7 @@ class Tab {
|
|
|
530
566
|
).then((highlightedText) => {
|
|
531
567
|
this.buildContextMenu(wc, params, highlightedText.trim());
|
|
532
568
|
}).catch((err) => {
|
|
533
|
-
logger$
|
|
569
|
+
logger$j.warn("Failed to inspect highlighted text for context menu:", err);
|
|
534
570
|
this.buildContextMenu(wc, params, "");
|
|
535
571
|
});
|
|
536
572
|
});
|
|
@@ -604,6 +640,19 @@ class Tab {
|
|
|
604
640
|
})
|
|
605
641
|
);
|
|
606
642
|
}
|
|
643
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
644
|
+
menu.append(
|
|
645
|
+
new electron.MenuItem({
|
|
646
|
+
label: "Save Page As...",
|
|
647
|
+
click: () => this.onSavePage?.()
|
|
648
|
+
})
|
|
649
|
+
);
|
|
650
|
+
menu.append(
|
|
651
|
+
new electron.MenuItem({
|
|
652
|
+
label: "View Page Source",
|
|
653
|
+
click: () => void this.viewSource()
|
|
654
|
+
})
|
|
655
|
+
);
|
|
607
656
|
menu.popup({ window: this.parentWindow });
|
|
608
657
|
}
|
|
609
658
|
get state() {
|
|
@@ -676,12 +725,74 @@ class Tab {
|
|
|
676
725
|
reload() {
|
|
677
726
|
this.view.webContents.reload();
|
|
678
727
|
}
|
|
728
|
+
zoomIn() {
|
|
729
|
+
const wc = this.view.webContents;
|
|
730
|
+
const level = wc.getZoomLevel();
|
|
731
|
+
wc.setZoomLevel(level + 0.5);
|
|
732
|
+
}
|
|
733
|
+
zoomOut() {
|
|
734
|
+
const wc = this.view.webContents;
|
|
735
|
+
const level = wc.getZoomLevel();
|
|
736
|
+
wc.setZoomLevel(level - 0.5);
|
|
737
|
+
}
|
|
738
|
+
zoomReset() {
|
|
739
|
+
this.view.webContents.setZoomLevel(0);
|
|
740
|
+
}
|
|
741
|
+
async viewSource() {
|
|
742
|
+
const url = this.view.webContents.getURL();
|
|
743
|
+
let html;
|
|
744
|
+
try {
|
|
745
|
+
html = await this.view.webContents.executeJavaScript(
|
|
746
|
+
"document.documentElement.outerHTML"
|
|
747
|
+
);
|
|
748
|
+
} catch (err) {
|
|
749
|
+
logger$j.warn("Failed to retrieve page source:", err);
|
|
750
|
+
return;
|
|
751
|
+
}
|
|
752
|
+
const escaped = html.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
753
|
+
const win = new electron.BrowserWindow({
|
|
754
|
+
width: 960,
|
|
755
|
+
height: 700,
|
|
756
|
+
title: `view-source:${url}`,
|
|
757
|
+
backgroundColor: "#1a1a1e",
|
|
758
|
+
webPreferences: {
|
|
759
|
+
sandbox: true,
|
|
760
|
+
contextIsolation: true,
|
|
761
|
+
nodeIntegration: false,
|
|
762
|
+
spellcheck: false
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
const style = "background:#1a1a1e;color:#e0e0e0;font-family:monospace;font-size:12px;line-height:1.5;padding:16px;margin:0;white-space:pre-wrap;word-break:break-all;";
|
|
766
|
+
const dataUrl = `data:text/html;charset=utf-8,<!DOCTYPE html><html><head><title>view-source:${url}</title></head><body style="${style}"><pre>${escaped}</pre></body></html>`;
|
|
767
|
+
win.loadURL(dataUrl);
|
|
768
|
+
}
|
|
679
769
|
setAdBlockingEnabled(enabled) {
|
|
680
770
|
if (this._state.adBlockingEnabled === enabled) return false;
|
|
681
771
|
this._state.adBlockingEnabled = enabled;
|
|
682
772
|
this.onChange();
|
|
683
773
|
return true;
|
|
684
774
|
}
|
|
775
|
+
setPinned(pinned) {
|
|
776
|
+
if (this._state.isPinned === pinned) return;
|
|
777
|
+
this._state.isPinned = pinned;
|
|
778
|
+
this.onChange();
|
|
779
|
+
}
|
|
780
|
+
setGroup(groupId) {
|
|
781
|
+
if (this._state.groupId === groupId) return;
|
|
782
|
+
this._state.groupId = groupId;
|
|
783
|
+
this.onChange();
|
|
784
|
+
}
|
|
785
|
+
setMuted(muted) {
|
|
786
|
+
const wc = this.view.webContents;
|
|
787
|
+
wc.setAudioMuted(muted);
|
|
788
|
+
this._state.isMuted = wc.isAudioMuted();
|
|
789
|
+
this._state.isAudible = wc.isCurrentlyAudible();
|
|
790
|
+
this.onChange();
|
|
791
|
+
}
|
|
792
|
+
toggleMuted() {
|
|
793
|
+
this.setMuted(!this._state.isMuted);
|
|
794
|
+
return this._state.isMuted;
|
|
795
|
+
}
|
|
685
796
|
get highlightModeActive() {
|
|
686
797
|
return this._highlightModeActive;
|
|
687
798
|
}
|
|
@@ -761,7 +872,7 @@ class Tab {
|
|
|
761
872
|
document.addEventListener('mouseup', window.__vesselHighlightHandler);
|
|
762
873
|
}
|
|
763
874
|
})()
|
|
764
|
-
`).catch((err) => logger$
|
|
875
|
+
`).catch((err) => logger$j.warn("Failed to inject highlight listener:", err));
|
|
765
876
|
} else {
|
|
766
877
|
void wc.executeJavaScript(`
|
|
767
878
|
(function() {
|
|
@@ -772,7 +883,7 @@ class Tab {
|
|
|
772
883
|
delete window.__vesselHighlightHandler;
|
|
773
884
|
}
|
|
774
885
|
})()
|
|
775
|
-
`).catch((err) => logger$
|
|
886
|
+
`).catch((err) => logger$j.warn("Failed to remove highlight listener:", err));
|
|
776
887
|
}
|
|
777
888
|
}
|
|
778
889
|
get webContentsId() {
|
|
@@ -783,7 +894,33 @@ class Tab {
|
|
|
783
894
|
this.view.webContents.close();
|
|
784
895
|
}
|
|
785
896
|
}
|
|
786
|
-
const
|
|
897
|
+
const TAB_GROUP_COLORS = [
|
|
898
|
+
"blue",
|
|
899
|
+
"green",
|
|
900
|
+
"yellow",
|
|
901
|
+
"orange",
|
|
902
|
+
"red",
|
|
903
|
+
"purple",
|
|
904
|
+
"gray"
|
|
905
|
+
];
|
|
906
|
+
const TAB_GROUP_COLOR_LABELS = {
|
|
907
|
+
blue: "Blue",
|
|
908
|
+
green: "Green",
|
|
909
|
+
yellow: "Yellow",
|
|
910
|
+
orange: "Orange",
|
|
911
|
+
red: "Red",
|
|
912
|
+
purple: "Purple",
|
|
913
|
+
gray: "Gray"
|
|
914
|
+
};
|
|
915
|
+
const SEARCH_ENGINE_PRESETS = {
|
|
916
|
+
duckduckgo: { label: "DuckDuckGo", url: "https://duckduckgo.com/?q=" },
|
|
917
|
+
google: { label: "Google", url: "https://www.google.com/search?q=" },
|
|
918
|
+
bing: { label: "Bing", url: "https://www.bing.com/search?q=" },
|
|
919
|
+
brave: { label: "Brave Search", url: "https://search.brave.com/search?q=" },
|
|
920
|
+
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
921
|
+
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
922
|
+
};
|
|
923
|
+
const logger$i = createLogger("JsonPersistence");
|
|
787
924
|
function canUseSafeStorage() {
|
|
788
925
|
try {
|
|
789
926
|
return electron.safeStorage.isEncryptionAvailable();
|
|
@@ -848,7 +985,7 @@ function createDebouncedJsonPersistence({
|
|
|
848
985
|
data,
|
|
849
986
|
typeof data === "string" ? { encoding: "utf-8", mode: 384 } : { mode: 384 }
|
|
850
987
|
)
|
|
851
|
-
).catch((err) => logger$
|
|
988
|
+
).catch((err) => logger$i.error(`Failed to save ${logLabel}:`, err));
|
|
852
989
|
};
|
|
853
990
|
const schedule = () => {
|
|
854
991
|
saveDirty2 = true;
|
|
@@ -876,6 +1013,20 @@ const SAVE_DEBOUNCE_MS$5 = 250;
|
|
|
876
1013
|
function getHighlightsPath() {
|
|
877
1014
|
return path.join(electron.app.getPath("userData"), "vessel-highlights.json");
|
|
878
1015
|
}
|
|
1016
|
+
function createPersistence$1() {
|
|
1017
|
+
return createDebouncedJsonPersistence({
|
|
1018
|
+
debounceMs: SAVE_DEBOUNCE_MS$5,
|
|
1019
|
+
filePath: getHighlightsPath(),
|
|
1020
|
+
getValue: () => state$4,
|
|
1021
|
+
logLabel: "highlights",
|
|
1022
|
+
resetOnSchedule: true
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
let persistence$5 = null;
|
|
1026
|
+
function getPersistence$1() {
|
|
1027
|
+
persistence$5 ??= createPersistence$1();
|
|
1028
|
+
return persistence$5;
|
|
1029
|
+
}
|
|
879
1030
|
function load$4() {
|
|
880
1031
|
if (state$4) return state$4;
|
|
881
1032
|
state$4 = loadJsonFile({
|
|
@@ -890,15 +1041,8 @@ function load$4() {
|
|
|
890
1041
|
});
|
|
891
1042
|
return state$4;
|
|
892
1043
|
}
|
|
893
|
-
const persistence$5 = createDebouncedJsonPersistence({
|
|
894
|
-
debounceMs: SAVE_DEBOUNCE_MS$5,
|
|
895
|
-
filePath: getHighlightsPath(),
|
|
896
|
-
getValue: () => state$4,
|
|
897
|
-
logLabel: "highlights",
|
|
898
|
-
resetOnSchedule: true
|
|
899
|
-
});
|
|
900
1044
|
function save$2() {
|
|
901
|
-
|
|
1045
|
+
getPersistence$1().schedule();
|
|
902
1046
|
}
|
|
903
1047
|
function emit$2() {
|
|
904
1048
|
if (!state$4) return;
|
|
@@ -980,7 +1124,7 @@ function clearHighlightsForUrl(url) {
|
|
|
980
1124
|
return removed;
|
|
981
1125
|
}
|
|
982
1126
|
function flushPersist$4() {
|
|
983
|
-
return
|
|
1127
|
+
return getPersistence$1().flush();
|
|
984
1128
|
}
|
|
985
1129
|
const SKIP_TAGS_JS = "var SKIP_TAGS = {SCRIPT:1,STYLE:1,NOSCRIPT:1,TEMPLATE:1,IFRAME:1,SVG:1};";
|
|
986
1130
|
const CONTENT_ROOTS_JS = `
|
|
@@ -2336,24 +2480,37 @@ function destroySession(tabId) {
|
|
|
2336
2480
|
sessions.delete(tabId);
|
|
2337
2481
|
}
|
|
2338
2482
|
}
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2483
|
+
const logger$h = createLogger("TabManager");
|
|
2484
|
+
function sanitizePdfFilename(title) {
|
|
2485
|
+
const clean = title.replace(/[<>:"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2486
|
+
const base = (clean || "Vessel Page").replace(/\.pdf$/i, "");
|
|
2487
|
+
return `${base}.pdf`;
|
|
2488
|
+
}
|
|
2489
|
+
function sanitizePageFilename(title, ext) {
|
|
2490
|
+
const clean = title.replace(/[<>:\"/\\|?*\x00-\x1f]/g, " ").replace(/\s+/g, " ").trim();
|
|
2491
|
+
const escapedExt = ext.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2492
|
+
const regex = new RegExp(`\\.${escapedExt}$`, "i");
|
|
2493
|
+
const base = (clean || "Vessel Page").replace(regex, "");
|
|
2494
|
+
return `${base}.${ext}`;
|
|
2344
2495
|
}
|
|
2345
|
-
const logger$g = createLogger("TabManager");
|
|
2346
2496
|
class TabManager {
|
|
2347
2497
|
tabs = /* @__PURE__ */ new Map();
|
|
2348
2498
|
order = [];
|
|
2499
|
+
tabGroups = /* @__PURE__ */ new Map();
|
|
2349
2500
|
activeTabId = null;
|
|
2350
2501
|
window;
|
|
2351
2502
|
onStateChange;
|
|
2352
2503
|
highlightCaptureCallback = null;
|
|
2353
2504
|
pageLoadCallback = null;
|
|
2354
|
-
|
|
2505
|
+
closedTabs = [];
|
|
2506
|
+
MAX_CLOSED_TABS = 20;
|
|
2507
|
+
isPrivate;
|
|
2508
|
+
sessionPartition;
|
|
2509
|
+
constructor(window2, onStateChange, options) {
|
|
2355
2510
|
this.window = window2;
|
|
2356
2511
|
this.onStateChange = onStateChange;
|
|
2512
|
+
this.isPrivate = options?.isPrivate ?? false;
|
|
2513
|
+
this.sessionPartition = options?.sessionPartition ?? (this.isPrivate ? "private-mode" : void 0);
|
|
2357
2514
|
}
|
|
2358
2515
|
onPageLoad(cb) {
|
|
2359
2516
|
this.pageLoadCallback = cb;
|
|
@@ -2364,17 +2521,23 @@ class TabManager {
|
|
|
2364
2521
|
const tab = new Tab(id, url, () => this.broadcastState(), {
|
|
2365
2522
|
adBlockingEnabled: options?.adBlockingEnabled,
|
|
2366
2523
|
parentWindow: this.window,
|
|
2524
|
+
sessionPartition: this.sessionPartition,
|
|
2367
2525
|
onOpenUrl: ({ url: requestedUrl, background: background2, adBlockingEnabled }) => {
|
|
2368
2526
|
this.createTab(requestedUrl, { background: background2, adBlockingEnabled });
|
|
2369
2527
|
},
|
|
2370
2528
|
onPageLoad: (pageUrl, wc) => {
|
|
2371
2529
|
this.reapplyHighlights(pageUrl, wc);
|
|
2372
|
-
|
|
2530
|
+
if (!this.isPrivate) {
|
|
2531
|
+
addEntry$1(pageUrl, wc.getTitle());
|
|
2532
|
+
}
|
|
2373
2533
|
this.pageLoadCallback?.(pageUrl, wc);
|
|
2374
2534
|
},
|
|
2375
2535
|
onHighlightSelection: (wc) => this.captureHighlightFromPage(wc),
|
|
2376
2536
|
onHighlightRemove: (url2, text) => this.removeHighlightByText(url2, text),
|
|
2377
|
-
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color)
|
|
2537
|
+
onHighlightRecolor: (url2, text, color) => this.recolorHighlightByText(url2, text, color),
|
|
2538
|
+
onSavePage: () => {
|
|
2539
|
+
void this.savePage(id);
|
|
2540
|
+
}
|
|
2378
2541
|
});
|
|
2379
2542
|
this.tabs.set(id, tab);
|
|
2380
2543
|
this.order.push(id);
|
|
@@ -2401,6 +2564,16 @@ class TabManager {
|
|
|
2401
2564
|
closeTab(id) {
|
|
2402
2565
|
const tab = this.tabs.get(id);
|
|
2403
2566
|
if (!tab) return;
|
|
2567
|
+
if (tab.state.isPinned) return;
|
|
2568
|
+
const groupId = tab.state.groupId;
|
|
2569
|
+
this.closedTabs.push({
|
|
2570
|
+
url: tab.state.url,
|
|
2571
|
+
title: tab.state.title,
|
|
2572
|
+
adBlockingEnabled: tab.state.adBlockingEnabled
|
|
2573
|
+
});
|
|
2574
|
+
if (this.closedTabs.length > this.MAX_CLOSED_TABS) {
|
|
2575
|
+
this.closedTabs.shift();
|
|
2576
|
+
}
|
|
2404
2577
|
const wcId = tab.webContentsId;
|
|
2405
2578
|
if (wcId !== void 0) {
|
|
2406
2579
|
this.lastReapply.delete(wcId);
|
|
@@ -2410,6 +2583,7 @@ class TabManager {
|
|
|
2410
2583
|
tab.destroy();
|
|
2411
2584
|
this.tabs.delete(id);
|
|
2412
2585
|
this.order = this.order.filter((tid) => tid !== id);
|
|
2586
|
+
this.removeGroupIfEmpty(groupId);
|
|
2413
2587
|
if (this.activeTabId === id) {
|
|
2414
2588
|
if (this.order.length > 0) {
|
|
2415
2589
|
this.switchTab(this.order[this.order.length - 1]);
|
|
@@ -2434,6 +2608,137 @@ class TabManager {
|
|
|
2434
2608
|
reloadTab(id) {
|
|
2435
2609
|
this.tabs.get(id)?.reload();
|
|
2436
2610
|
}
|
|
2611
|
+
zoomIn(id) {
|
|
2612
|
+
this.tabs.get(id)?.zoomIn();
|
|
2613
|
+
}
|
|
2614
|
+
zoomOut(id) {
|
|
2615
|
+
this.tabs.get(id)?.zoomOut();
|
|
2616
|
+
}
|
|
2617
|
+
zoomReset(id) {
|
|
2618
|
+
this.tabs.get(id)?.zoomReset();
|
|
2619
|
+
}
|
|
2620
|
+
reopenClosedTab() {
|
|
2621
|
+
const last = this.closedTabs.pop();
|
|
2622
|
+
if (!last) return null;
|
|
2623
|
+
return this.createTab(last.url, { adBlockingEnabled: last.adBlockingEnabled });
|
|
2624
|
+
}
|
|
2625
|
+
duplicateTab(id) {
|
|
2626
|
+
const tab = this.tabs.get(id);
|
|
2627
|
+
if (!tab) return null;
|
|
2628
|
+
return this.createTab(tab.state.url, { adBlockingEnabled: tab.state.adBlockingEnabled });
|
|
2629
|
+
}
|
|
2630
|
+
pinTab(id) {
|
|
2631
|
+
const tab = this.tabs.get(id);
|
|
2632
|
+
if (!tab) return;
|
|
2633
|
+
tab.setPinned(true);
|
|
2634
|
+
this.order = this.order.filter((tid) => tid !== id);
|
|
2635
|
+
const firstNonPinned = this.order.findIndex((tid) => !this.tabs.get(tid)?.state.isPinned);
|
|
2636
|
+
if (firstNonPinned === -1) {
|
|
2637
|
+
this.order.push(id);
|
|
2638
|
+
} else {
|
|
2639
|
+
this.order.splice(firstNonPinned, 0, id);
|
|
2640
|
+
}
|
|
2641
|
+
this.broadcastState();
|
|
2642
|
+
}
|
|
2643
|
+
unpinTab(id) {
|
|
2644
|
+
const tab = this.tabs.get(id);
|
|
2645
|
+
if (!tab) return;
|
|
2646
|
+
tab.setPinned(false);
|
|
2647
|
+
this.order = this.order.filter((tid) => tid !== id);
|
|
2648
|
+
const firstNonPinned = this.order.findIndex((tid) => !this.tabs.get(tid)?.state.isPinned);
|
|
2649
|
+
if (firstNonPinned === -1) {
|
|
2650
|
+
this.order.push(id);
|
|
2651
|
+
} else {
|
|
2652
|
+
this.order.splice(firstNonPinned, 0, id);
|
|
2653
|
+
}
|
|
2654
|
+
this.broadcastState();
|
|
2655
|
+
}
|
|
2656
|
+
createGroupFromTab(id, options) {
|
|
2657
|
+
const tab = this.tabs.get(id);
|
|
2658
|
+
if (!tab) return null;
|
|
2659
|
+
const previousGroupId = tab.state.groupId;
|
|
2660
|
+
const groupId = crypto$1.randomUUID();
|
|
2661
|
+
const color = options?.color && TAB_GROUP_COLORS.includes(options.color) ? options.color : TAB_GROUP_COLORS[this.tabGroups.size % TAB_GROUP_COLORS.length];
|
|
2662
|
+
this.tabGroups.set(groupId, {
|
|
2663
|
+
id: groupId,
|
|
2664
|
+
name: options?.name?.trim() || `Group ${this.tabGroups.size + 1}`,
|
|
2665
|
+
color,
|
|
2666
|
+
collapsed: false
|
|
2667
|
+
});
|
|
2668
|
+
this.assignTabToGroup(id, groupId);
|
|
2669
|
+
this.removeGroupIfEmpty(previousGroupId);
|
|
2670
|
+
return groupId;
|
|
2671
|
+
}
|
|
2672
|
+
assignTabToGroup(id, groupId) {
|
|
2673
|
+
const tab = this.tabs.get(id);
|
|
2674
|
+
if (!tab || !this.tabGroups.has(groupId)) return;
|
|
2675
|
+
const previousGroupId = tab.state.groupId;
|
|
2676
|
+
tab.setGroup(groupId);
|
|
2677
|
+
this.removeGroupIfEmpty(previousGroupId);
|
|
2678
|
+
this.broadcastState();
|
|
2679
|
+
}
|
|
2680
|
+
removeTabFromGroup(id) {
|
|
2681
|
+
const tab = this.tabs.get(id);
|
|
2682
|
+
if (!tab) return;
|
|
2683
|
+
const groupId = tab.state.groupId;
|
|
2684
|
+
tab.setGroup(void 0);
|
|
2685
|
+
this.removeGroupIfEmpty(groupId);
|
|
2686
|
+
this.broadcastState();
|
|
2687
|
+
}
|
|
2688
|
+
toggleGroupCollapsed(groupId) {
|
|
2689
|
+
const group = this.tabGroups.get(groupId);
|
|
2690
|
+
if (!group) return null;
|
|
2691
|
+
group.collapsed = !group.collapsed;
|
|
2692
|
+
this.broadcastState();
|
|
2693
|
+
return group.collapsed;
|
|
2694
|
+
}
|
|
2695
|
+
setGroupColor(groupId, color) {
|
|
2696
|
+
const group = this.tabGroups.get(groupId);
|
|
2697
|
+
if (!group || !TAB_GROUP_COLORS.includes(color)) return;
|
|
2698
|
+
group.color = color;
|
|
2699
|
+
this.broadcastState();
|
|
2700
|
+
}
|
|
2701
|
+
toggleMuted(id) {
|
|
2702
|
+
return this.tabs.get(id)?.toggleMuted() ?? null;
|
|
2703
|
+
}
|
|
2704
|
+
printTab(id) {
|
|
2705
|
+
const tab = this.tabs.get(id);
|
|
2706
|
+
if (!tab) return;
|
|
2707
|
+
tab.view.webContents.print();
|
|
2708
|
+
}
|
|
2709
|
+
async saveTabAsPdf(id) {
|
|
2710
|
+
const tab = this.tabs.get(id);
|
|
2711
|
+
if (!tab) return null;
|
|
2712
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
2713
|
+
title: "Save Page as PDF",
|
|
2714
|
+
defaultPath: sanitizePdfFilename(tab.state.title || "Vessel Page"),
|
|
2715
|
+
filters: [{ name: "PDF", extensions: ["pdf"] }]
|
|
2716
|
+
});
|
|
2717
|
+
if (canceled || !filePath) return null;
|
|
2718
|
+
const data = await tab.view.webContents.printToPDF({
|
|
2719
|
+
printBackground: true
|
|
2720
|
+
});
|
|
2721
|
+
await fs.promises.writeFile(filePath, data);
|
|
2722
|
+
return filePath;
|
|
2723
|
+
}
|
|
2724
|
+
async savePage(id, format = "MHTML") {
|
|
2725
|
+
const tab = this.tabs.get(id);
|
|
2726
|
+
if (!tab) return null;
|
|
2727
|
+
const ext = format === "MHTML" ? "mhtml" : "html";
|
|
2728
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
2729
|
+
title: "Save Page As",
|
|
2730
|
+
defaultPath: sanitizePageFilename(
|
|
2731
|
+
tab.state.title || "Vessel Page",
|
|
2732
|
+
ext
|
|
2733
|
+
),
|
|
2734
|
+
filters: [
|
|
2735
|
+
{ name: format === "MHTML" ? "MHTML" : "HTML", extensions: [ext] }
|
|
2736
|
+
]
|
|
2737
|
+
});
|
|
2738
|
+
if (canceled || !filePath) return null;
|
|
2739
|
+
await tab.view.webContents.savePage(filePath, format);
|
|
2740
|
+
return filePath;
|
|
2741
|
+
}
|
|
2437
2742
|
getActiveTab() {
|
|
2438
2743
|
return this.activeTabId ? this.tabs.get(this.activeTabId) : void 0;
|
|
2439
2744
|
}
|
|
@@ -2444,7 +2749,10 @@ class TabManager {
|
|
|
2444
2749
|
return this.activeTabId;
|
|
2445
2750
|
}
|
|
2446
2751
|
getAllStates() {
|
|
2447
|
-
return this.order.map((id) => this.tabs.get(id).state);
|
|
2752
|
+
return this.order.map((id) => this.withGroupState(this.tabs.get(id).state));
|
|
2753
|
+
}
|
|
2754
|
+
getGroups() {
|
|
2755
|
+
return Array.from(this.tabGroups.values());
|
|
2448
2756
|
}
|
|
2449
2757
|
findTabByWebContentsId(webContentsId) {
|
|
2450
2758
|
for (const id of this.order) {
|
|
@@ -2477,7 +2785,10 @@ class TabManager {
|
|
|
2477
2785
|
id: state2.id,
|
|
2478
2786
|
url: state2.url || "about:blank",
|
|
2479
2787
|
title: state2.title,
|
|
2480
|
-
adBlockingEnabled: state2.adBlockingEnabled
|
|
2788
|
+
adBlockingEnabled: state2.adBlockingEnabled,
|
|
2789
|
+
isPinned: state2.isPinned,
|
|
2790
|
+
groupName: state2.groupName,
|
|
2791
|
+
groupColor: state2.groupColor
|
|
2481
2792
|
})),
|
|
2482
2793
|
activeIndex: activeIndex >= 0 ? activeIndex : 0,
|
|
2483
2794
|
activeTabId: activeId || void 0,
|
|
@@ -2492,12 +2803,33 @@ class TabManager {
|
|
|
2492
2803
|
Math.min(snapshot.activeIndex, tabs.length - 1)
|
|
2493
2804
|
);
|
|
2494
2805
|
this.destroyAllTabs();
|
|
2806
|
+
const restoredGroups = /* @__PURE__ */ new Map();
|
|
2495
2807
|
const ids = tabs.map(
|
|
2496
2808
|
(tab, index) => this.createTab(tab.url || "about:blank", {
|
|
2497
2809
|
background: index !== activeIndex,
|
|
2498
2810
|
adBlockingEnabled: tab.adBlockingEnabled ?? true
|
|
2499
2811
|
})
|
|
2500
2812
|
);
|
|
2813
|
+
tabs.forEach((tab, index) => {
|
|
2814
|
+
if (tab.isPinned && ids[index]) {
|
|
2815
|
+
this.pinTab(ids[index]);
|
|
2816
|
+
}
|
|
2817
|
+
if (tab.groupName && ids[index]) {
|
|
2818
|
+
const key = `${tab.groupName}|${tab.groupColor ?? "blue"}`;
|
|
2819
|
+
let groupId = restoredGroups.get(key);
|
|
2820
|
+
if (!groupId) {
|
|
2821
|
+
groupId = crypto$1.randomUUID();
|
|
2822
|
+
restoredGroups.set(key, groupId);
|
|
2823
|
+
this.tabGroups.set(groupId, {
|
|
2824
|
+
id: groupId,
|
|
2825
|
+
name: tab.groupName,
|
|
2826
|
+
color: tab.groupColor ?? "blue",
|
|
2827
|
+
collapsed: false
|
|
2828
|
+
});
|
|
2829
|
+
}
|
|
2830
|
+
this.assignTabToGroup(ids[index], groupId);
|
|
2831
|
+
}
|
|
2832
|
+
});
|
|
2501
2833
|
const activeId = ids[activeIndex];
|
|
2502
2834
|
if (activeId) {
|
|
2503
2835
|
this.switchTab(activeId);
|
|
@@ -2507,15 +2839,16 @@ class TabManager {
|
|
|
2507
2839
|
return ids;
|
|
2508
2840
|
}
|
|
2509
2841
|
destroyAllTabs() {
|
|
2510
|
-
|
|
2511
|
-
for (const id of this.order) {
|
|
2842
|
+
for (const id of [...this.order]) {
|
|
2512
2843
|
const tab = this.tabs.get(id);
|
|
2513
2844
|
if (!tab) continue;
|
|
2845
|
+
destroySession(id);
|
|
2514
2846
|
this.window.contentView.removeChildView(tab.view);
|
|
2515
2847
|
tab.destroy();
|
|
2516
2848
|
}
|
|
2517
2849
|
this.tabs.clear();
|
|
2518
2850
|
this.order = [];
|
|
2851
|
+
this.tabGroups.clear();
|
|
2519
2852
|
this.activeTabId = null;
|
|
2520
2853
|
this.broadcastState();
|
|
2521
2854
|
}
|
|
@@ -2536,7 +2869,7 @@ class TabManager {
|
|
|
2536
2869
|
}));
|
|
2537
2870
|
if (entries.length > 0) {
|
|
2538
2871
|
void highlightBatchOnPage(wc, entries).catch(
|
|
2539
|
-
(err) => logger$
|
|
2872
|
+
(err) => logger$h.warn("Failed to batch highlight:", err)
|
|
2540
2873
|
);
|
|
2541
2874
|
}
|
|
2542
2875
|
}
|
|
@@ -2558,12 +2891,12 @@ class TabManager {
|
|
|
2558
2891
|
const result = await captureSelectionHighlight(wc);
|
|
2559
2892
|
if (result.success && result.text) {
|
|
2560
2893
|
await highlightOnPage(wc, null, result.text, void 0, void 0, "yellow").catch(
|
|
2561
|
-
(err) => logger$
|
|
2894
|
+
(err) => logger$h.warn("Failed to capture highlight:", err)
|
|
2562
2895
|
);
|
|
2563
2896
|
}
|
|
2564
2897
|
this.highlightCaptureCallback?.(result);
|
|
2565
2898
|
} catch (err) {
|
|
2566
|
-
logger$
|
|
2899
|
+
logger$h.warn("Failed to capture highlight from page:", err);
|
|
2567
2900
|
this.highlightCaptureCallback?.({
|
|
2568
2901
|
success: false,
|
|
2569
2902
|
message: "Could not capture selection"
|
|
@@ -2588,7 +2921,7 @@ class TabManager {
|
|
|
2588
2921
|
void this.removeHighlightMarksForText(wc, text);
|
|
2589
2922
|
}
|
|
2590
2923
|
} catch (err) {
|
|
2591
|
-
logger$
|
|
2924
|
+
logger$h.warn("Failed to remove highlight from matching tab:", err);
|
|
2592
2925
|
}
|
|
2593
2926
|
}
|
|
2594
2927
|
this.highlightCaptureCallback?.({
|
|
@@ -2619,12 +2952,12 @@ class TabManager {
|
|
|
2619
2952
|
void 0,
|
|
2620
2953
|
color
|
|
2621
2954
|
).catch(
|
|
2622
|
-
(err) => logger$
|
|
2955
|
+
(err) => logger$h.warn("Failed to update highlight color:", err)
|
|
2623
2956
|
);
|
|
2624
2957
|
});
|
|
2625
2958
|
}
|
|
2626
2959
|
} catch (err) {
|
|
2627
|
-
logger$
|
|
2960
|
+
logger$h.warn("Failed to iterate highlights for color change:", err);
|
|
2628
2961
|
}
|
|
2629
2962
|
}
|
|
2630
2963
|
this.highlightCaptureCallback?.({
|
|
@@ -2632,6 +2965,24 @@ class TabManager {
|
|
|
2632
2965
|
message: `Color changed to ${color}`
|
|
2633
2966
|
});
|
|
2634
2967
|
}
|
|
2968
|
+
withGroupState(state2) {
|
|
2969
|
+
if (!state2.groupId) return state2;
|
|
2970
|
+
const group = this.tabGroups.get(state2.groupId);
|
|
2971
|
+
if (!group) return { ...state2, groupId: void 0 };
|
|
2972
|
+
return {
|
|
2973
|
+
...state2,
|
|
2974
|
+
groupName: group.name,
|
|
2975
|
+
groupColor: group.color,
|
|
2976
|
+
groupCollapsed: group.collapsed
|
|
2977
|
+
};
|
|
2978
|
+
}
|
|
2979
|
+
removeGroupIfEmpty(groupId) {
|
|
2980
|
+
if (!groupId) return;
|
|
2981
|
+
for (const tab of this.tabs.values()) {
|
|
2982
|
+
if (tab.state.groupId === groupId) return;
|
|
2983
|
+
}
|
|
2984
|
+
this.tabGroups.delete(groupId);
|
|
2985
|
+
}
|
|
2635
2986
|
async removeHighlightMarksForText(wc, text) {
|
|
2636
2987
|
await wc.executeJavaScript(
|
|
2637
2988
|
`(function() {
|
|
@@ -2646,7 +2997,7 @@ class TabManager {
|
|
|
2646
2997
|
});
|
|
2647
2998
|
})()`
|
|
2648
2999
|
).catch(
|
|
2649
|
-
(err) => logger$
|
|
3000
|
+
(err) => logger$h.warn("Failed to remove highlight marks:", err)
|
|
2650
3001
|
);
|
|
2651
3002
|
}
|
|
2652
3003
|
broadcastState() {
|
|
@@ -2713,6 +3064,8 @@ const Channels = {
|
|
|
2713
3064
|
BOOKMARK_SAVE: "bookmarks:save",
|
|
2714
3065
|
BOOKMARK_UPDATE: "bookmarks:update-item",
|
|
2715
3066
|
BOOKMARK_REMOVE: "bookmarks:remove",
|
|
3067
|
+
BOOKMARKS_EXPORT_HTML: "bookmarks:export-html",
|
|
3068
|
+
BOOKMARKS_EXPORT_JSON: "bookmarks:export-json",
|
|
2716
3069
|
BOOKMARK_ADD_CONTEXT_TO_CHAT: "bookmarks:add-context-to-chat",
|
|
2717
3070
|
FOLDER_CREATE: "bookmarks:folder-create",
|
|
2718
3071
|
FOLDER_REMOVE: "bookmarks:folder-remove",
|
|
@@ -2733,6 +3086,34 @@ const Channels = {
|
|
|
2733
3086
|
DEVTOOLS_PANEL_RESIZE: "devtools-panel:resize",
|
|
2734
3087
|
// Ad blocking
|
|
2735
3088
|
TAB_TOGGLE_AD_BLOCK: "tab:toggle-ad-block",
|
|
3089
|
+
// Zoom
|
|
3090
|
+
TAB_ZOOM_IN: "tab:zoom-in",
|
|
3091
|
+
TAB_ZOOM_OUT: "tab:zoom-out",
|
|
3092
|
+
TAB_ZOOM_RESET: "tab:zoom-reset",
|
|
3093
|
+
// Closed tabs / duplication
|
|
3094
|
+
TAB_REOPEN_CLOSED: "tab:reopen-closed",
|
|
3095
|
+
TAB_DUPLICATE: "tab:duplicate",
|
|
3096
|
+
TAB_CONTEXT_MENU: "tab:context-menu",
|
|
3097
|
+
// Pin tabs
|
|
3098
|
+
TAB_PIN: "tab:pin",
|
|
3099
|
+
TAB_UNPIN: "tab:unpin",
|
|
3100
|
+
// Tab groups
|
|
3101
|
+
TAB_GROUP_CREATE: "tab-group:create",
|
|
3102
|
+
TAB_GROUP_ADD_TAB: "tab-group:add-tab",
|
|
3103
|
+
TAB_GROUP_REMOVE_TAB: "tab-group:remove-tab",
|
|
3104
|
+
TAB_GROUP_TOGGLE_COLLAPSED: "tab-group:toggle-collapsed",
|
|
3105
|
+
TAB_GROUP_SET_COLOR: "tab-group:set-color",
|
|
3106
|
+
TAB_GROUP_CONTEXT_MENU: "tab-group:context-menu",
|
|
3107
|
+
// Audio / mute
|
|
3108
|
+
TAB_TOGGLE_MUTE: "tab:toggle-mute",
|
|
3109
|
+
// Print
|
|
3110
|
+
TAB_PRINT: "tab:print",
|
|
3111
|
+
TAB_PRINT_TO_PDF: "tab:print-to-pdf",
|
|
3112
|
+
// Windows
|
|
3113
|
+
OPEN_NEW_WINDOW: "window:open-new",
|
|
3114
|
+
// Private browsing
|
|
3115
|
+
OPEN_PRIVATE_WINDOW: "private:open-window",
|
|
3116
|
+
IS_PRIVATE_MODE: "private:is-private",
|
|
2736
3117
|
// Find in page
|
|
2737
3118
|
FIND_IN_PAGE_START: "find:start",
|
|
2738
3119
|
FIND_IN_PAGE_NEXT: "find:next",
|
|
@@ -3750,7 +4131,7 @@ function errorResult(error, value) {
|
|
|
3750
4131
|
function getErrorMessage(error, fallback = "Unknown error") {
|
|
3751
4132
|
return error instanceof Error && error.message ? error.message : fallback;
|
|
3752
4133
|
}
|
|
3753
|
-
const logger$
|
|
4134
|
+
const logger$g = createLogger("Premium");
|
|
3754
4135
|
const VERIFICATION_API = process.env.VESSEL_PREMIUM_API || "https://vesselpremium.quantaintellect.com";
|
|
3755
4136
|
const FREE_TOOL_ITERATION_LIMIT = 50;
|
|
3756
4137
|
const REVALIDATION_INTERVAL_MS = 24 * 60 * 60 * 1e3;
|
|
@@ -3863,7 +4244,7 @@ async function verifySubscription(identifier) {
|
|
|
3863
4244
|
});
|
|
3864
4245
|
if (!res.ok) {
|
|
3865
4246
|
const detail = await readApiErrorDetail(res);
|
|
3866
|
-
logger$
|
|
4247
|
+
logger$g.warn(
|
|
3867
4248
|
"Verification API returned a non-OK status:",
|
|
3868
4249
|
res.status,
|
|
3869
4250
|
detail
|
|
@@ -3882,7 +4263,7 @@ async function verifySubscription(identifier) {
|
|
|
3882
4263
|
setSetting("premium", updated);
|
|
3883
4264
|
return updated;
|
|
3884
4265
|
} catch (err) {
|
|
3885
|
-
logger$
|
|
4266
|
+
logger$g.warn("Verification failed:", err);
|
|
3886
4267
|
return current;
|
|
3887
4268
|
}
|
|
3888
4269
|
}
|
|
@@ -4455,7 +4836,7 @@ const EXTRACT_TIMEOUT_MAX_MS = 2e4;
|
|
|
4455
4836
|
const MUTATION_CAPTURE_INTERVAL_MS = 5e3;
|
|
4456
4837
|
const MUTATION_SETTLE_AFTER_MS = 1500;
|
|
4457
4838
|
const AGENT_STREAM_IDLE_TIMEOUT_MS = 3e4;
|
|
4458
|
-
const logger$
|
|
4839
|
+
const logger$f = createLogger("Extractor");
|
|
4459
4840
|
const EMPTY_PAGE_CONTENT = {
|
|
4460
4841
|
title: "",
|
|
4461
4842
|
content: "",
|
|
@@ -5205,7 +5586,7 @@ async function executeScript(webContents, script) {
|
|
|
5205
5586
|
})
|
|
5206
5587
|
]);
|
|
5207
5588
|
} catch (err) {
|
|
5208
|
-
logger$
|
|
5589
|
+
logger$f.warn("Failed to execute page script:", err);
|
|
5209
5590
|
return null;
|
|
5210
5591
|
} finally {
|
|
5211
5592
|
if (timer) {
|
|
@@ -5312,7 +5693,7 @@ async function estimateExtractionTimeout(webContents) {
|
|
|
5312
5693
|
return EXTRACT_TIMEOUT_BASE_MS + extra;
|
|
5313
5694
|
}
|
|
5314
5695
|
} catch (err) {
|
|
5315
|
-
logger$
|
|
5696
|
+
logger$f.warn("Failed to estimate extraction timeout, using base timeout:", err);
|
|
5316
5697
|
}
|
|
5317
5698
|
return EXTRACT_TIMEOUT_BASE_MS;
|
|
5318
5699
|
}
|
|
@@ -5623,7 +6004,7 @@ function enableClipboardShortcuts(view) {
|
|
|
5623
6004
|
}
|
|
5624
6005
|
});
|
|
5625
6006
|
}
|
|
5626
|
-
const CHROME_HEIGHT = 110;
|
|
6007
|
+
const CHROME_HEIGHT$2 = 110;
|
|
5627
6008
|
const DEFAULT_DEVTOOLS_PANEL_HEIGHT = 250;
|
|
5628
6009
|
const MIN_DEVTOOLS_PANEL = 120;
|
|
5629
6010
|
const MAX_DEVTOOLS_PANEL = 600;
|
|
@@ -5765,7 +6146,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
5765
6146
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
5766
6147
|
sandbox: true,
|
|
5767
6148
|
contextIsolation: true,
|
|
5768
|
-
nodeIntegration: false
|
|
6149
|
+
nodeIntegration: false,
|
|
6150
|
+
spellcheck: false
|
|
5769
6151
|
}
|
|
5770
6152
|
});
|
|
5771
6153
|
chromeView.setBackgroundColor("#00000000");
|
|
@@ -5775,7 +6157,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
5775
6157
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
5776
6158
|
sandbox: true,
|
|
5777
6159
|
contextIsolation: true,
|
|
5778
|
-
nodeIntegration: false
|
|
6160
|
+
nodeIntegration: false,
|
|
6161
|
+
spellcheck: false
|
|
5779
6162
|
}
|
|
5780
6163
|
});
|
|
5781
6164
|
sidebarView.setBackgroundColor("#00000000");
|
|
@@ -5789,7 +6172,8 @@ function createMainWindow(onTabStateChange) {
|
|
|
5789
6172
|
preload: path.join(__dirname, "../preload/index.js"),
|
|
5790
6173
|
sandbox: true,
|
|
5791
6174
|
contextIsolation: true,
|
|
5792
|
-
nodeIntegration: false
|
|
6175
|
+
nodeIntegration: false,
|
|
6176
|
+
spellcheck: false
|
|
5793
6177
|
}
|
|
5794
6178
|
});
|
|
5795
6179
|
devtoolsPanelView.setBackgroundColor("#00000000");
|
|
@@ -5838,7 +6222,7 @@ function layoutViews(state2) {
|
|
|
5838
6222
|
uiState
|
|
5839
6223
|
} = state2;
|
|
5840
6224
|
const [width, height] = mainWindow.getContentSize();
|
|
5841
|
-
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
6225
|
+
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT$2;
|
|
5842
6226
|
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
5843
6227
|
const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
|
|
5844
6228
|
const chromeNeedsFullHeight = uiState.settingsOpen;
|
|
@@ -5888,7 +6272,7 @@ function layoutViews(state2) {
|
|
|
5888
6272
|
function resizeSidebarViews(state2) {
|
|
5889
6273
|
const { mainWindow, sidebarView, devtoolsPanelView, tabManager, uiState } = state2;
|
|
5890
6274
|
const [width, height] = mainWindow.getContentSize();
|
|
5891
|
-
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT;
|
|
6275
|
+
const chromeHeight = uiState.focusMode ? 0 : CHROME_HEIGHT$2;
|
|
5892
6276
|
const sidebarWidth = uiState.sidebarOpen ? uiState.sidebarWidth : 0;
|
|
5893
6277
|
const devtoolsHeight = uiState.devtoolsPanelOpen ? uiState.devtoolsPanelHeight : 0;
|
|
5894
6278
|
const resizeHandleOverlap = 6;
|
|
@@ -6618,7 +7002,7 @@ const MAX_MCP_NAV_CONTENT_LENGTH = 3e4;
|
|
|
6618
7002
|
const MAX_AGENT_DEBUG_CONTENT_LENGTH = 2e4;
|
|
6619
7003
|
const LLAMA_CPP_MIN_CTX_TOKENS = 16384;
|
|
6620
7004
|
const LLAMA_CPP_RECOMMENDED_CTX_TOKENS = 32768;
|
|
6621
|
-
const logger$
|
|
7005
|
+
const logger$e = createLogger("OpenAIProvider");
|
|
6622
7006
|
function shouldDebugAgentLoop() {
|
|
6623
7007
|
const value = process.env.VESSEL_DEBUG_AGENT_LOOP;
|
|
6624
7008
|
return value === "1" || value === "true";
|
|
@@ -7119,9 +7503,9 @@ function resolveToolCallName(rawName, args, availableToolNames) {
|
|
|
7119
7503
|
function logAgentLoopDebug(payload) {
|
|
7120
7504
|
if (!shouldDebugAgentLoop()) return;
|
|
7121
7505
|
try {
|
|
7122
|
-
logger$
|
|
7506
|
+
logger$e.info(`[agent-debug] ${JSON.stringify(payload)}`);
|
|
7123
7507
|
} catch (err) {
|
|
7124
|
-
logger$
|
|
7508
|
+
logger$e.warn("Failed to serialize debug payload:", err);
|
|
7125
7509
|
}
|
|
7126
7510
|
}
|
|
7127
7511
|
function recoverTextEncodedToolCalls(text, availableToolNames) {
|
|
@@ -7755,7 +8139,7 @@ function createProvider(config) {
|
|
|
7755
8139
|
return new OpenAICompatProvider(normalized);
|
|
7756
8140
|
}
|
|
7757
8141
|
const require$1 = node_module.createRequire(require("url").pathToFileURL(__filename).href);
|
|
7758
|
-
const logger$
|
|
8142
|
+
const logger$d = createLogger("DevTrace");
|
|
7759
8143
|
let cachedFactory;
|
|
7760
8144
|
function createNoopTraceSession() {
|
|
7761
8145
|
return {
|
|
@@ -7788,7 +8172,7 @@ function loadLocalFactory() {
|
|
|
7788
8172
|
return cachedFactory;
|
|
7789
8173
|
}
|
|
7790
8174
|
} catch (err) {
|
|
7791
|
-
logger$
|
|
8175
|
+
logger$d.warn("Failed to load local trace logger:", err);
|
|
7792
8176
|
}
|
|
7793
8177
|
}
|
|
7794
8178
|
return cachedFactory;
|
|
@@ -9780,7 +10164,7 @@ function buildCompactScopedContext(page, mode, pageType = detectPageType(page))
|
|
|
9780
10164
|
const primaryResults = primaryResultElements.map(formatElement);
|
|
9781
10165
|
if (primaryResults.length > 0) {
|
|
9782
10166
|
lines.push("");
|
|
9783
|
-
lines.push("### Results
|
|
10167
|
+
lines.push("### Primary Results");
|
|
9784
10168
|
lines.push(...primaryResults.map((item) => `- ${item}`));
|
|
9785
10169
|
lines.push("");
|
|
9786
10170
|
lines.push("IMPORTANT: Use click(index=N) on a result above. Do NOT click filter or sort links.");
|
|
@@ -9924,6 +10308,61 @@ const TOOL_DEFINITIONS = [
|
|
|
9924
10308
|
},
|
|
9925
10309
|
tier: 2
|
|
9926
10310
|
},
|
|
10311
|
+
{
|
|
10312
|
+
name: "list_groups",
|
|
10313
|
+
title: "List Tab Groups",
|
|
10314
|
+
description: "List all tab groups with their IDs, names, colors, collapsed state, and member tab count.",
|
|
10315
|
+
tier: 2
|
|
10316
|
+
},
|
|
10317
|
+
{
|
|
10318
|
+
name: "create_group",
|
|
10319
|
+
title: "Create Tab Group",
|
|
10320
|
+
description: "Create a new tab group from the active tab or a specified tab. Optionally provide a name and color.",
|
|
10321
|
+
inputSchema: {
|
|
10322
|
+
tabId: zod.z.string().optional().describe("Tab ID to group (defaults to active tab)"),
|
|
10323
|
+
name: zod.z.string().optional().describe("Optional group name"),
|
|
10324
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).optional().describe("Optional group color")
|
|
10325
|
+
},
|
|
10326
|
+
tier: 2
|
|
10327
|
+
},
|
|
10328
|
+
{
|
|
10329
|
+
name: "assign_to_group",
|
|
10330
|
+
title: "Assign Tab to Group",
|
|
10331
|
+
description: "Move a tab into an existing group by ID. Defaults to the active tab.",
|
|
10332
|
+
inputSchema: {
|
|
10333
|
+
groupId: zod.z.string().describe("Group ID to assign the tab to"),
|
|
10334
|
+
tabId: zod.z.string().optional().describe("Tab ID to move (defaults to active tab)")
|
|
10335
|
+
},
|
|
10336
|
+
tier: 2
|
|
10337
|
+
},
|
|
10338
|
+
{
|
|
10339
|
+
name: "remove_from_group",
|
|
10340
|
+
title: "Remove Tab from Group",
|
|
10341
|
+
description: "Ungroup a tab. Defaults to the active tab.",
|
|
10342
|
+
inputSchema: {
|
|
10343
|
+
tabId: zod.z.string().optional().describe("Tab ID to ungroup (defaults to active tab)")
|
|
10344
|
+
},
|
|
10345
|
+
tier: 2
|
|
10346
|
+
},
|
|
10347
|
+
{
|
|
10348
|
+
name: "toggle_group",
|
|
10349
|
+
title: "Toggle Group Collapsed",
|
|
10350
|
+
description: "Collapse or expand a tab group.",
|
|
10351
|
+
inputSchema: {
|
|
10352
|
+
groupId: zod.z.string().describe("Group ID to toggle")
|
|
10353
|
+
},
|
|
10354
|
+
tier: 2
|
|
10355
|
+
},
|
|
10356
|
+
{
|
|
10357
|
+
name: "set_group_color",
|
|
10358
|
+
title: "Set Group Color",
|
|
10359
|
+
description: "Change the color of a tab group.",
|
|
10360
|
+
inputSchema: {
|
|
10361
|
+
groupId: zod.z.string().describe("Group ID"),
|
|
10362
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).describe("New color")
|
|
10363
|
+
},
|
|
10364
|
+
tier: 2
|
|
10365
|
+
},
|
|
9927
10366
|
// --- Navigation ---
|
|
9928
10367
|
{
|
|
9929
10368
|
name: "navigate",
|
|
@@ -10706,6 +11145,12 @@ function shouldIncludeTool(toolName, pageType, intents, profile) {
|
|
|
10706
11145
|
case "switch_tab":
|
|
10707
11146
|
case "create_tab":
|
|
10708
11147
|
case "set_ad_blocking":
|
|
11148
|
+
case "list_groups":
|
|
11149
|
+
case "create_group":
|
|
11150
|
+
case "assign_to_group":
|
|
11151
|
+
case "remove_from_group":
|
|
11152
|
+
case "toggle_group":
|
|
11153
|
+
case "set_group_color":
|
|
10709
11154
|
return intents.has("tabs") || intents.has("debug");
|
|
10710
11155
|
case "save_session":
|
|
10711
11156
|
case "load_session":
|
|
@@ -10757,14 +11202,6 @@ function pruneToolsForContext(tools, pageType, query = "", options = {}) {
|
|
|
10757
11202
|
return description !== tool.description ? { ...tool, description } : tool;
|
|
10758
11203
|
});
|
|
10759
11204
|
}
|
|
10760
|
-
const SEARCH_ENGINE_PRESETS = {
|
|
10761
|
-
duckduckgo: { label: "DuckDuckGo", url: "https://duckduckgo.com/?q=" },
|
|
10762
|
-
google: { label: "Google", url: "https://www.google.com/search?q=" },
|
|
10763
|
-
bing: { label: "Bing", url: "https://www.bing.com/search?q=" },
|
|
10764
|
-
brave: { label: "Brave Search", url: "https://search.brave.com/search?q=" },
|
|
10765
|
-
ecosia: { label: "Ecosia", url: "https://www.ecosia.org/search?q=" },
|
|
10766
|
-
kagi: { label: "Kagi", url: "https://kagi.com/search?q=" }
|
|
10767
|
-
};
|
|
10768
11205
|
function trimText(value) {
|
|
10769
11206
|
return typeof value === "string" ? value.trim() : "";
|
|
10770
11207
|
}
|
|
@@ -10956,6 +11393,7 @@ function normalizeBookmarkMetadataUpdate(input) {
|
|
|
10956
11393
|
}
|
|
10957
11394
|
const UNSORTED_ID = "unsorted";
|
|
10958
11395
|
const ARCHIVE_FOLDER_NAME = "Archive";
|
|
11396
|
+
const NETSCAPE_BOOKMARKS_DOCTYPE = "<!DOCTYPE NETSCAPE-Bookmark-file-1>";
|
|
10959
11397
|
const SAVE_DEBOUNCE_MS$1 = 250;
|
|
10960
11398
|
let state$1 = null;
|
|
10961
11399
|
const listeners = /* @__PURE__ */ new Set();
|
|
@@ -10968,6 +11406,19 @@ function cloneState(current) {
|
|
|
10968
11406
|
function getBookmarksPath() {
|
|
10969
11407
|
return path.join(electron.app.getPath("userData"), "vessel-bookmarks.json");
|
|
10970
11408
|
}
|
|
11409
|
+
function createPersistence() {
|
|
11410
|
+
return createDebouncedJsonPersistence({
|
|
11411
|
+
debounceMs: SAVE_DEBOUNCE_MS$1,
|
|
11412
|
+
filePath: getBookmarksPath(),
|
|
11413
|
+
getValue: () => state$1,
|
|
11414
|
+
logLabel: "bookmarks"
|
|
11415
|
+
});
|
|
11416
|
+
}
|
|
11417
|
+
let persistence$1 = null;
|
|
11418
|
+
function getPersistence() {
|
|
11419
|
+
persistence$1 ??= createPersistence();
|
|
11420
|
+
return persistence$1;
|
|
11421
|
+
}
|
|
10971
11422
|
function load$1() {
|
|
10972
11423
|
if (state$1) return state$1;
|
|
10973
11424
|
state$1 = loadJsonFile({
|
|
@@ -10983,14 +11434,8 @@ function load$1() {
|
|
|
10983
11434
|
});
|
|
10984
11435
|
return state$1;
|
|
10985
11436
|
}
|
|
10986
|
-
const persistence$1 = createDebouncedJsonPersistence({
|
|
10987
|
-
debounceMs: SAVE_DEBOUNCE_MS$1,
|
|
10988
|
-
filePath: getBookmarksPath(),
|
|
10989
|
-
getValue: () => state$1,
|
|
10990
|
-
logLabel: "bookmarks"
|
|
10991
|
-
});
|
|
10992
11437
|
function save() {
|
|
10993
|
-
|
|
11438
|
+
getPersistence().schedule();
|
|
10994
11439
|
}
|
|
10995
11440
|
function assignDefinedBookmarkFields(bookmark, fields) {
|
|
10996
11441
|
if (!fields) return;
|
|
@@ -11006,9 +11451,81 @@ function emit() {
|
|
|
11006
11451
|
listener(snapshot);
|
|
11007
11452
|
}
|
|
11008
11453
|
}
|
|
11454
|
+
function escapeBookmarkHtml(value) {
|
|
11455
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
11456
|
+
}
|
|
11457
|
+
function toNetscapeTimestamp(value) {
|
|
11458
|
+
if (!value) return Math.floor(Date.now() / 1e3);
|
|
11459
|
+
const time = Date.parse(value);
|
|
11460
|
+
return Number.isNaN(time) ? Math.floor(Date.now() / 1e3) : Math.floor(time / 1e3);
|
|
11461
|
+
}
|
|
11462
|
+
function getBookmarkDescription(bookmark) {
|
|
11463
|
+
const lines = [
|
|
11464
|
+
bookmark.note ? `Note: ${bookmark.note}` : "",
|
|
11465
|
+
bookmark.intent ? `Intent: ${bookmark.intent}` : "",
|
|
11466
|
+
bookmark.expectedContent ? `Expected content: ${bookmark.expectedContent}` : "",
|
|
11467
|
+
bookmark.keyFields?.length ? `Key fields: ${bookmark.keyFields.join(", ")}` : "",
|
|
11468
|
+
bookmark.agentHints && Object.keys(bookmark.agentHints).length > 0 ? `Agent hints: ${Object.entries(bookmark.agentHints).map(([key, value]) => `${key}: ${value}`).join("; ")}` : ""
|
|
11469
|
+
].filter(Boolean);
|
|
11470
|
+
return lines.join("\n");
|
|
11471
|
+
}
|
|
11472
|
+
function appendBookmarkHtml(lines, bookmark, options, indent) {
|
|
11473
|
+
const addDate = toNetscapeTimestamp(bookmark.savedAt);
|
|
11474
|
+
lines.push(
|
|
11475
|
+
`${indent}<DT><A HREF="${escapeBookmarkHtml(bookmark.url)}" ADD_DATE="${addDate}">${escapeBookmarkHtml(bookmark.title || bookmark.url)}</A>`
|
|
11476
|
+
);
|
|
11477
|
+
if (!options.includeNotes) return;
|
|
11478
|
+
const description = getBookmarkDescription(bookmark);
|
|
11479
|
+
if (description) {
|
|
11480
|
+
lines.push(`${indent}<DD>${escapeBookmarkHtml(description)}`);
|
|
11481
|
+
}
|
|
11482
|
+
}
|
|
11009
11483
|
function getState() {
|
|
11010
11484
|
return cloneState(load$1());
|
|
11011
11485
|
}
|
|
11486
|
+
function exportBookmarksHtml(options = {}) {
|
|
11487
|
+
const current = getState();
|
|
11488
|
+
const resolvedOptions = {
|
|
11489
|
+
includeNotes: options.includeNotes ?? false
|
|
11490
|
+
};
|
|
11491
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
11492
|
+
const folders = [
|
|
11493
|
+
{ id: UNSORTED_ID, name: "Vessel Bookmarks", createdAt: "", summary: "" },
|
|
11494
|
+
...current.folders
|
|
11495
|
+
];
|
|
11496
|
+
const lines = [
|
|
11497
|
+
NETSCAPE_BOOKMARKS_DOCTYPE,
|
|
11498
|
+
'<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=UTF-8">',
|
|
11499
|
+
"<TITLE>Bookmarks</TITLE>",
|
|
11500
|
+
"<H1>Bookmarks</H1>",
|
|
11501
|
+
"<DL><p>"
|
|
11502
|
+
];
|
|
11503
|
+
for (const folder of folders) {
|
|
11504
|
+
const items = current.bookmarks.filter(
|
|
11505
|
+
(bookmark) => bookmark.folderId === folder.id
|
|
11506
|
+
);
|
|
11507
|
+
if (items.length === 0) continue;
|
|
11508
|
+
const addDate = toNetscapeTimestamp(folder.createdAt) || now;
|
|
11509
|
+
lines.push(
|
|
11510
|
+
` <DT><H3 ADD_DATE="${addDate}" LAST_MODIFIED="${now}">${escapeBookmarkHtml(folder.name)}</H3>`
|
|
11511
|
+
);
|
|
11512
|
+
if (resolvedOptions.includeNotes && folder.summary) {
|
|
11513
|
+
lines.push(` <DD>${escapeBookmarkHtml(folder.summary)}`);
|
|
11514
|
+
}
|
|
11515
|
+
lines.push(" <DL><p>");
|
|
11516
|
+
for (const bookmark of items) {
|
|
11517
|
+
appendBookmarkHtml(lines, bookmark, resolvedOptions, " ");
|
|
11518
|
+
}
|
|
11519
|
+
lines.push(" </DL><p>");
|
|
11520
|
+
}
|
|
11521
|
+
lines.push("</DL><p>");
|
|
11522
|
+
return `${lines.join("\n")}
|
|
11523
|
+
`;
|
|
11524
|
+
}
|
|
11525
|
+
function exportBookmarksJson() {
|
|
11526
|
+
return `${JSON.stringify(getState(), null, 2)}
|
|
11527
|
+
`;
|
|
11528
|
+
}
|
|
11012
11529
|
function subscribe(listener) {
|
|
11013
11530
|
listeners.add(listener);
|
|
11014
11531
|
return () => {
|
|
@@ -11279,7 +11796,7 @@ function renameFolder(id, newName, summary) {
|
|
|
11279
11796
|
return { ...folder };
|
|
11280
11797
|
}
|
|
11281
11798
|
function flushPersist$1() {
|
|
11282
|
-
return
|
|
11799
|
+
return getPersistence().flush();
|
|
11283
11800
|
}
|
|
11284
11801
|
function normalizeText(text) {
|
|
11285
11802
|
return text?.trim() ?? "";
|
|
@@ -11603,7 +12120,7 @@ function formatDeadLinkMessage(label, result) {
|
|
|
11603
12120
|
const status = result.statusCode ? `HTTP ${result.statusCode}` : "dead link";
|
|
11604
12121
|
return `Skipped stale link "${label}" because ${destination} returned ${status}. Try a different link or URL instead.`;
|
|
11605
12122
|
}
|
|
11606
|
-
const logger$
|
|
12123
|
+
const logger$c = createLogger("Screenshot");
|
|
11607
12124
|
const SCREENSHOT_RETRY_COUNT = 3;
|
|
11608
12125
|
const SCREENSHOT_RETRY_BASE_DELAY_MS = 120;
|
|
11609
12126
|
async function captureScreenshot(wc) {
|
|
@@ -11625,7 +12142,7 @@ async function captureScreenshot(wc) {
|
|
|
11625
12142
|
}
|
|
11626
12143
|
}
|
|
11627
12144
|
} catch (err) {
|
|
11628
|
-
logger$
|
|
12145
|
+
logger$c.debug(
|
|
11629
12146
|
`capturePage attempt ${attempt + 1} failed; retrying if attempts remain.`,
|
|
11630
12147
|
getErrorMessage(err)
|
|
11631
12148
|
);
|
|
@@ -12499,7 +13016,7 @@ function buildHuggingFaceSearchShortcut(currentUrl, rawQuery) {
|
|
|
12499
13016
|
appliedFilters
|
|
12500
13017
|
};
|
|
12501
13018
|
}
|
|
12502
|
-
const logger$
|
|
13019
|
+
const logger$b = createLogger("PageActions");
|
|
12503
13020
|
function getBookmarkMetadataFromArgs(args) {
|
|
12504
13021
|
return normalizeBookmarkMetadata({
|
|
12505
13022
|
intent: args.intent ?? args.intent,
|
|
@@ -12685,7 +13202,7 @@ async function executePageScript(wc, script, options) {
|
|
|
12685
13202
|
return result;
|
|
12686
13203
|
} catch (err) {
|
|
12687
13204
|
const label = options?.label ? ` (${options.label})` : "";
|
|
12688
|
-
logger$
|
|
13205
|
+
logger$b.warn(`Failed to execute page script${label}:`, err);
|
|
12689
13206
|
return null;
|
|
12690
13207
|
} finally {
|
|
12691
13208
|
if (timer) {
|
|
@@ -12786,7 +13303,7 @@ Search results snapshot:
|
|
|
12786
13303
|
${truncated}`;
|
|
12787
13304
|
}
|
|
12788
13305
|
} catch (err) {
|
|
12789
|
-
logger$
|
|
13306
|
+
logger$b.warn("Failed to build post-search summary, falling back to nav summary:", err);
|
|
12790
13307
|
}
|
|
12791
13308
|
const fallback = await getPostNavSummary(wc);
|
|
12792
13309
|
return fallback ? `${fallback}
|
|
@@ -12809,7 +13326,7 @@ Page snapshot after navigation:
|
|
|
12809
13326
|
${truncated}`;
|
|
12810
13327
|
}
|
|
12811
13328
|
} catch (err) {
|
|
12812
|
-
logger$
|
|
13329
|
+
logger$b.warn("Failed to build post-click navigation summary:", err);
|
|
12813
13330
|
}
|
|
12814
13331
|
return "";
|
|
12815
13332
|
}
|
|
@@ -13303,7 +13820,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
13303
13820
|
}
|
|
13304
13821
|
}
|
|
13305
13822
|
} catch (err) {
|
|
13306
|
-
logger$
|
|
13823
|
+
logger$b.warn("Failed to restore locale via history navigation, trying URL reload fallback:", err);
|
|
13307
13824
|
}
|
|
13308
13825
|
if (snapshot.url && snapshot.url !== wc.getURL()) {
|
|
13309
13826
|
try {
|
|
@@ -13312,7 +13829,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
13312
13829
|
await waitForLoad(wc, 3e3);
|
|
13313
13830
|
return;
|
|
13314
13831
|
} catch (err) {
|
|
13315
|
-
logger$
|
|
13832
|
+
logger$b.warn("Failed to restore locale via safe URL load, trying page reload fallback:", err);
|
|
13316
13833
|
}
|
|
13317
13834
|
}
|
|
13318
13835
|
if (snapshot.url) {
|
|
@@ -13320,7 +13837,7 @@ async function restoreLocaleSnapshot(wc, snapshot) {
|
|
|
13320
13837
|
await wc.reload();
|
|
13321
13838
|
await waitForLoad(wc, 3e3);
|
|
13322
13839
|
} catch (err) {
|
|
13323
|
-
logger$
|
|
13840
|
+
logger$b.warn("Failed to restore locale via page reload:", err);
|
|
13324
13841
|
}
|
|
13325
13842
|
}
|
|
13326
13843
|
}
|
|
@@ -13672,7 +14189,7 @@ ${postActivationOverlayHint}`;
|
|
|
13672
14189
|
return `${clickText} -> ${hrefFallbackUrl} (recovered via href fallback)`;
|
|
13673
14190
|
}
|
|
13674
14191
|
} catch (err) {
|
|
13675
|
-
logger$
|
|
14192
|
+
logger$b.warn("Failed href fallback after click, returning generic click result:", err);
|
|
13676
14193
|
}
|
|
13677
14194
|
}
|
|
13678
14195
|
}
|
|
@@ -13717,7 +14234,7 @@ async function tryAutoDismissCartDialog(wc) {
|
|
|
13717
14234
|
return result;
|
|
13718
14235
|
}
|
|
13719
14236
|
} catch (err) {
|
|
13720
|
-
logger$
|
|
14237
|
+
logger$b.warn("Failed to auto-dismiss cart dialog, falling back to dialog actions:", err);
|
|
13721
14238
|
}
|
|
13722
14239
|
return null;
|
|
13723
14240
|
}
|
|
@@ -15987,7 +16504,7 @@ async function executeAction(name, args, ctx) {
|
|
|
15987
16504
|
)
|
|
15988
16505
|
]);
|
|
15989
16506
|
} catch (err) {
|
|
15990
|
-
logger$
|
|
16507
|
+
logger$b.warn("Failed to extract content for read_page, falling back to lighter recovery:", err);
|
|
15991
16508
|
content = null;
|
|
15992
16509
|
}
|
|
15993
16510
|
if (!content || content.content.length === 0) {
|
|
@@ -16004,12 +16521,12 @@ async function executeAction(name, args, ctx) {
|
|
|
16004
16521
|
new Promise((resolve) => setTimeout(() => resolve(null), 3e3))
|
|
16005
16522
|
]);
|
|
16006
16523
|
} catch (err) {
|
|
16007
|
-
logger$
|
|
16524
|
+
logger$b.warn("Failed to re-extract content after iframe consent dismissal:", err);
|
|
16008
16525
|
content = null;
|
|
16009
16526
|
}
|
|
16010
16527
|
}
|
|
16011
16528
|
} catch (err) {
|
|
16012
|
-
logger$
|
|
16529
|
+
logger$b.warn("Failed iframe consent dismissal during read_page recovery:", err);
|
|
16013
16530
|
}
|
|
16014
16531
|
}
|
|
16015
16532
|
if (content && content.content.length > 0) {
|
|
@@ -16422,7 +16939,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
16422
16939
|
try {
|
|
16423
16940
|
page = await extractContent(wc);
|
|
16424
16941
|
} catch (err) {
|
|
16425
|
-
logger$
|
|
16942
|
+
logger$b.warn("Failed to extract content for suggest:", err);
|
|
16426
16943
|
return "Could not read page. Try navigate to a working URL.";
|
|
16427
16944
|
}
|
|
16428
16945
|
const suggestions = [];
|
|
@@ -17753,7 +18270,7 @@ const ALGORITHM = "aes-256-gcm";
|
|
|
17753
18270
|
const IV_LENGTH = 12;
|
|
17754
18271
|
const AUTH_TAG_LENGTH = 16;
|
|
17755
18272
|
let cachedEntries = null;
|
|
17756
|
-
const logger$
|
|
18273
|
+
const logger$a = createLogger("Vault");
|
|
17757
18274
|
function getVaultDir() {
|
|
17758
18275
|
return electron.app.getPath("userData");
|
|
17759
18276
|
}
|
|
@@ -17820,7 +18337,7 @@ function loadVault() {
|
|
|
17820
18337
|
cachedEntries = JSON.parse(json);
|
|
17821
18338
|
return cachedEntries;
|
|
17822
18339
|
} catch (err) {
|
|
17823
|
-
logger$
|
|
18340
|
+
logger$a.error("Failed to load vault:", err);
|
|
17824
18341
|
throw new Error(
|
|
17825
18342
|
"Could not unlock the Agent Credential Vault. Check that OS secret storage is available and that the stored vault key can be decrypted."
|
|
17826
18343
|
);
|
|
@@ -17962,7 +18479,7 @@ async function requestConsent(request) {
|
|
|
17962
18479
|
}
|
|
17963
18480
|
const AUDIT_FILENAME = "vessel-vault-audit.jsonl";
|
|
17964
18481
|
const MAX_ENTRIES = 1e3;
|
|
17965
|
-
const logger$
|
|
18482
|
+
const logger$9 = createLogger("VaultAudit");
|
|
17966
18483
|
function getAuditPath() {
|
|
17967
18484
|
return path$1.join(electron.app.getPath("userData"), AUDIT_FILENAME);
|
|
17968
18485
|
}
|
|
@@ -17972,7 +18489,7 @@ function appendAuditEntry(entry) {
|
|
|
17972
18489
|
fs$1.mkdirSync(path$1.dirname(auditPath), { recursive: true });
|
|
17973
18490
|
fs$1.appendFileSync(auditPath, JSON.stringify(entry) + "\n");
|
|
17974
18491
|
} catch (err) {
|
|
17975
|
-
logger$
|
|
18492
|
+
logger$9.error("Failed to write audit log:", err);
|
|
17976
18493
|
}
|
|
17977
18494
|
}
|
|
17978
18495
|
function readAuditLog(limit = 100) {
|
|
@@ -17982,13 +18499,13 @@ function readAuditLog(limit = 100) {
|
|
|
17982
18499
|
const lines = fs$1.readFileSync(auditPath, "utf-8").split("\n").filter((l) => l.trim());
|
|
17983
18500
|
return lines.slice(-Math.min(limit, MAX_ENTRIES)).map((line) => JSON.parse(line)).reverse();
|
|
17984
18501
|
} catch (err) {
|
|
17985
|
-
logger$
|
|
18502
|
+
logger$9.error("Failed to read audit log:", err);
|
|
17986
18503
|
return [];
|
|
17987
18504
|
}
|
|
17988
18505
|
}
|
|
17989
18506
|
let httpServer = null;
|
|
17990
18507
|
let mcpAuthToken = null;
|
|
17991
|
-
const logger$
|
|
18508
|
+
const logger$8 = createLogger("MCP");
|
|
17992
18509
|
const MCP_AUTH_FILENAME = "mcp-auth.json";
|
|
17993
18510
|
function getMcpAuthFilePath() {
|
|
17994
18511
|
const configDir = process.env.VESSEL_CONFIG_DIR || path$1.join(
|
|
@@ -18024,7 +18541,7 @@ function writeMcpAuthFile(endpoint, token) {
|
|
|
18024
18541
|
{ mode: 384 }
|
|
18025
18542
|
);
|
|
18026
18543
|
} catch (err) {
|
|
18027
|
-
logger$
|
|
18544
|
+
logger$8.warn("Failed to write auth file:", err);
|
|
18028
18545
|
}
|
|
18029
18546
|
}
|
|
18030
18547
|
function clearMcpAuthFile() {
|
|
@@ -18049,7 +18566,7 @@ function clearMcpAuthFile() {
|
|
|
18049
18566
|
{ mode: 384 }
|
|
18050
18567
|
);
|
|
18051
18568
|
} catch (err) {
|
|
18052
|
-
logger$
|
|
18569
|
+
logger$8.warn("Failed to clear auth file:", err);
|
|
18053
18570
|
}
|
|
18054
18571
|
}
|
|
18055
18572
|
function asTextResponse(text) {
|
|
@@ -18139,7 +18656,7 @@ async function getPostActionState(tabManager, name) {
|
|
|
18139
18656
|
}
|
|
18140
18657
|
}
|
|
18141
18658
|
} catch (err) {
|
|
18142
|
-
logger$
|
|
18659
|
+
logger$8.warn("Failed to compute post-action state warning:", err);
|
|
18143
18660
|
}
|
|
18144
18661
|
return `${warning}
|
|
18145
18662
|
[state: url=${wc.getURL()}, canGoBack=${tab.canGoBack()}, canGoForward=${tab.canGoForward()}, loading=${wc.isLoading()}]`;
|
|
@@ -18241,7 +18758,7 @@ async function waitForConditionMcp(wc, text, selector, timeoutMs) {
|
|
|
18241
18758
|
}
|
|
18242
18759
|
})()
|
|
18243
18760
|
`).catch((err) => {
|
|
18244
|
-
logger$
|
|
18761
|
+
logger$8.warn("Failed to gather wait_for timeout diagnostic:", err);
|
|
18245
18762
|
return null;
|
|
18246
18763
|
});
|
|
18247
18764
|
if (typeof diagnostic === "string" && diagnostic.trim()) {
|
|
@@ -18328,7 +18845,7 @@ function registerTools(server, tabManager, runtime2) {
|
|
|
18328
18845
|
const page = await extractContent(wc);
|
|
18329
18846
|
pageType = detectPageType(page);
|
|
18330
18847
|
} catch (err) {
|
|
18331
|
-
logger$
|
|
18848
|
+
logger$8.warn("Failed to detect page type for tool scoring, falling back to GENERAL:", err);
|
|
18332
18849
|
}
|
|
18333
18850
|
}
|
|
18334
18851
|
const scored = TOOL_DEFINITIONS.map((def) => {
|
|
@@ -19234,54 +19751,168 @@ ${buildScopedContext(pageContent, mode)}`;
|
|
|
19234
19751
|
})
|
|
19235
19752
|
);
|
|
19236
19753
|
server.registerTool(
|
|
19237
|
-
"
|
|
19754
|
+
"list_groups",
|
|
19238
19755
|
{
|
|
19239
|
-
title: "
|
|
19240
|
-
description: "
|
|
19756
|
+
title: "List Tab Groups",
|
|
19757
|
+
description: "List all tab groups with their IDs, names, colors, collapsed state, and member tab count."
|
|
19758
|
+
},
|
|
19759
|
+
async () => {
|
|
19760
|
+
const groups = tabManager.getGroups();
|
|
19761
|
+
const tabs = tabManager.getAllStates();
|
|
19762
|
+
if (groups.length === 0) {
|
|
19763
|
+
return asTextResponse("No tab groups");
|
|
19764
|
+
}
|
|
19765
|
+
const lines = groups.map((g) => {
|
|
19766
|
+
const count = tabs.filter((t) => t.groupId === g.id).length;
|
|
19767
|
+
return `[${g.id}] ${g.name} — color:${g.color} collapsed:${g.collapsed} tabs:${count}`;
|
|
19768
|
+
});
|
|
19769
|
+
return asTextResponse(lines.join("\n"));
|
|
19770
|
+
}
|
|
19771
|
+
);
|
|
19772
|
+
server.registerTool(
|
|
19773
|
+
"create_group",
|
|
19774
|
+
{
|
|
19775
|
+
title: "Create Tab Group",
|
|
19776
|
+
description: "Create a new tab group from the active tab or a specified tab. Optionally provide a name and color.",
|
|
19241
19777
|
inputSchema: {
|
|
19242
|
-
|
|
19243
|
-
|
|
19778
|
+
tabId: zod.z.string().optional().describe("Tab ID to group (defaults to active tab)"),
|
|
19779
|
+
name: zod.z.string().optional().describe("Optional group name"),
|
|
19780
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).optional().describe("Optional group color")
|
|
19244
19781
|
}
|
|
19245
19782
|
},
|
|
19246
|
-
async ({ name,
|
|
19247
|
-
|
|
19248
|
-
|
|
19249
|
-
|
|
19250
|
-
|
|
19251
|
-
|
|
19252
|
-
|
|
19253
|
-
|
|
19783
|
+
async ({ tabId, name, color }) => withAction(runtime2, tabManager, "create_group", { tabId, name, color }, async () => {
|
|
19784
|
+
const targetId = tabId || tabManager.getActiveTabId();
|
|
19785
|
+
if (!targetId) {
|
|
19786
|
+
return "Error: No active tab";
|
|
19787
|
+
}
|
|
19788
|
+
const groupId = tabManager.createGroupFromTab(targetId, {
|
|
19789
|
+
name: name || void 0,
|
|
19790
|
+
color: color || void 0
|
|
19791
|
+
});
|
|
19792
|
+
if (!groupId) {
|
|
19793
|
+
return "Error: Could not create group";
|
|
19254
19794
|
}
|
|
19255
|
-
|
|
19795
|
+
return `Created group ${groupId}`;
|
|
19796
|
+
})
|
|
19256
19797
|
);
|
|
19257
19798
|
server.registerTool(
|
|
19258
|
-
"
|
|
19799
|
+
"assign_to_group",
|
|
19259
19800
|
{
|
|
19260
|
-
title: "
|
|
19261
|
-
description: "
|
|
19801
|
+
title: "Assign Tab to Group",
|
|
19802
|
+
description: "Move a tab into an existing group by ID. Defaults to the active tab.",
|
|
19262
19803
|
inputSchema: {
|
|
19263
|
-
|
|
19264
|
-
|
|
19804
|
+
groupId: zod.z.string().describe("Group ID to assign the tab to"),
|
|
19805
|
+
tabId: zod.z.string().optional().describe("Tab ID to move (defaults to active tab)")
|
|
19265
19806
|
}
|
|
19266
19807
|
},
|
|
19267
|
-
async ({
|
|
19268
|
-
|
|
19269
|
-
|
|
19270
|
-
|
|
19271
|
-
{ checkpointId, name },
|
|
19272
|
-
async () => {
|
|
19273
|
-
const state2 = runtime2.getState();
|
|
19274
|
-
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
19275
|
-
if (!checkpoint) {
|
|
19276
|
-
return "Error: No matching checkpoint found";
|
|
19277
|
-
}
|
|
19278
|
-
runtime2.restoreCheckpoint(checkpoint.id);
|
|
19279
|
-
return `Restored checkpoint ${checkpoint.name}`;
|
|
19808
|
+
async ({ groupId, tabId }) => withAction(runtime2, tabManager, "assign_to_group", { groupId, tabId }, async () => {
|
|
19809
|
+
const targetId = tabId || tabManager.getActiveTabId();
|
|
19810
|
+
if (!targetId) {
|
|
19811
|
+
return "Error: No active tab";
|
|
19280
19812
|
}
|
|
19281
|
-
|
|
19813
|
+
tabManager.assignTabToGroup(targetId, groupId);
|
|
19814
|
+
return `Assigned tab ${targetId} to group ${groupId}`;
|
|
19815
|
+
})
|
|
19282
19816
|
);
|
|
19283
19817
|
server.registerTool(
|
|
19284
|
-
"
|
|
19818
|
+
"remove_from_group",
|
|
19819
|
+
{
|
|
19820
|
+
title: "Remove Tab from Group",
|
|
19821
|
+
description: "Ungroup a tab. Defaults to the active tab.",
|
|
19822
|
+
inputSchema: {
|
|
19823
|
+
tabId: zod.z.string().optional().describe("Tab ID to ungroup (defaults to active tab)")
|
|
19824
|
+
}
|
|
19825
|
+
},
|
|
19826
|
+
async ({ tabId }) => withAction(runtime2, tabManager, "remove_from_group", { tabId }, async () => {
|
|
19827
|
+
const targetId = tabId || tabManager.getActiveTabId();
|
|
19828
|
+
if (!targetId) {
|
|
19829
|
+
return "Error: No active tab";
|
|
19830
|
+
}
|
|
19831
|
+
tabManager.removeTabFromGroup(targetId);
|
|
19832
|
+
return `Removed tab ${targetId} from group`;
|
|
19833
|
+
})
|
|
19834
|
+
);
|
|
19835
|
+
server.registerTool(
|
|
19836
|
+
"toggle_group",
|
|
19837
|
+
{
|
|
19838
|
+
title: "Toggle Group Collapsed",
|
|
19839
|
+
description: "Collapse or expand a tab group.",
|
|
19840
|
+
inputSchema: {
|
|
19841
|
+
groupId: zod.z.string().describe("Group ID to toggle")
|
|
19842
|
+
}
|
|
19843
|
+
},
|
|
19844
|
+
async ({ groupId }) => withAction(runtime2, tabManager, "toggle_group", { groupId }, async () => {
|
|
19845
|
+
const collapsed = tabManager.toggleGroupCollapsed(groupId);
|
|
19846
|
+
if (collapsed === null) {
|
|
19847
|
+
return "Error: Group not found";
|
|
19848
|
+
}
|
|
19849
|
+
return collapsed ? `Collapsed group ${groupId}` : `Expanded group ${groupId}`;
|
|
19850
|
+
})
|
|
19851
|
+
);
|
|
19852
|
+
server.registerTool(
|
|
19853
|
+
"set_group_color",
|
|
19854
|
+
{
|
|
19855
|
+
title: "Set Group Color",
|
|
19856
|
+
description: "Change the color of a tab group.",
|
|
19857
|
+
inputSchema: {
|
|
19858
|
+
groupId: zod.z.string().describe("Group ID"),
|
|
19859
|
+
color: zod.z.enum(["blue", "green", "yellow", "orange", "red", "purple", "gray"]).describe("New color")
|
|
19860
|
+
}
|
|
19861
|
+
},
|
|
19862
|
+
async ({ groupId, color }) => withAction(runtime2, tabManager, "set_group_color", { groupId, color }, async () => {
|
|
19863
|
+
tabManager.setGroupColor(groupId, color);
|
|
19864
|
+
return `Set group ${groupId} color to ${color}`;
|
|
19865
|
+
})
|
|
19866
|
+
);
|
|
19867
|
+
server.registerTool(
|
|
19868
|
+
"checkpoint_create",
|
|
19869
|
+
{
|
|
19870
|
+
title: "Create Checkpoint",
|
|
19871
|
+
description: "Capture the current session as a named checkpoint.",
|
|
19872
|
+
inputSchema: {
|
|
19873
|
+
name: zod.z.string().optional().describe("Optional checkpoint name"),
|
|
19874
|
+
note: zod.z.string().optional().describe("Optional note")
|
|
19875
|
+
}
|
|
19876
|
+
},
|
|
19877
|
+
async ({ name, note }) => withAction(
|
|
19878
|
+
runtime2,
|
|
19879
|
+
tabManager,
|
|
19880
|
+
"create_checkpoint",
|
|
19881
|
+
{ name, note },
|
|
19882
|
+
async () => {
|
|
19883
|
+
const checkpoint = runtime2.createCheckpoint(name, note);
|
|
19884
|
+
return `Created checkpoint ${checkpoint.name} (${checkpoint.id})`;
|
|
19885
|
+
}
|
|
19886
|
+
)
|
|
19887
|
+
);
|
|
19888
|
+
server.registerTool(
|
|
19889
|
+
"checkpoint_restore",
|
|
19890
|
+
{
|
|
19891
|
+
title: "Restore Checkpoint",
|
|
19892
|
+
description: "Restore a saved checkpoint by ID or exact name.",
|
|
19893
|
+
inputSchema: {
|
|
19894
|
+
checkpointId: zod.z.string().optional().describe("Checkpoint ID"),
|
|
19895
|
+
name: zod.z.string().optional().describe("Exact checkpoint name")
|
|
19896
|
+
}
|
|
19897
|
+
},
|
|
19898
|
+
async ({ checkpointId, name }) => withAction(
|
|
19899
|
+
runtime2,
|
|
19900
|
+
tabManager,
|
|
19901
|
+
"restore_checkpoint",
|
|
19902
|
+
{ checkpointId, name },
|
|
19903
|
+
async () => {
|
|
19904
|
+
const state2 = runtime2.getState();
|
|
19905
|
+
const checkpoint = state2.checkpoints.find((item) => item.id === checkpointId) || state2.checkpoints.find((item) => item.name === name);
|
|
19906
|
+
if (!checkpoint) {
|
|
19907
|
+
return "Error: No matching checkpoint found";
|
|
19908
|
+
}
|
|
19909
|
+
runtime2.restoreCheckpoint(checkpoint.id);
|
|
19910
|
+
return `Restored checkpoint ${checkpoint.name}`;
|
|
19911
|
+
}
|
|
19912
|
+
)
|
|
19913
|
+
);
|
|
19914
|
+
server.registerTool(
|
|
19915
|
+
"save_session",
|
|
19285
19916
|
{
|
|
19286
19917
|
title: "Save Session",
|
|
19287
19918
|
description: "Persist the current cookies, localStorage, and tab layout under a reusable session name.",
|
|
@@ -19612,7 +20243,7 @@ ${JSON.stringify(otherHighlights, null, 2)}`
|
|
|
19612
20243
|
void 0,
|
|
19613
20244
|
h.color
|
|
19614
20245
|
).catch(
|
|
19615
|
-
(err) => logger$
|
|
20246
|
+
(err) => logger$8.warn("Failed to restore highlight after removal:", err)
|
|
19616
20247
|
);
|
|
19617
20248
|
}
|
|
19618
20249
|
}
|
|
@@ -20460,7 +21091,7 @@ ${flow.steps.map((s, i) => ` ${i === 0 ? "→" : " "} ${s.label}`).join("\n")}`
|
|
|
20460
21091
|
try {
|
|
20461
21092
|
page = await extractContent(wc);
|
|
20462
21093
|
} catch (err) {
|
|
20463
|
-
logger$
|
|
21094
|
+
logger$8.warn("Failed to extract page while generating suggestions:", err);
|
|
20464
21095
|
return asTextResponse(
|
|
20465
21096
|
"Could not read page. Try navigate to a working URL."
|
|
20466
21097
|
);
|
|
@@ -21069,7 +21700,7 @@ ${JSON.stringify(tableJson, null, 2)}`;
|
|
|
21069
21700
|
try {
|
|
21070
21701
|
targetDomain = new URL(tab.state.url).hostname;
|
|
21071
21702
|
} catch (err) {
|
|
21072
|
-
logger$
|
|
21703
|
+
logger$8.warn("Failed to parse active tab URL for vault_status:", err);
|
|
21073
21704
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
21074
21705
|
}
|
|
21075
21706
|
}
|
|
@@ -21135,7 +21766,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21135
21766
|
try {
|
|
21136
21767
|
hostname = new URL(tab.state.url).hostname;
|
|
21137
21768
|
} catch (err) {
|
|
21138
|
-
logger$
|
|
21769
|
+
logger$8.warn("Failed to parse active tab URL for vault_login:", err);
|
|
21139
21770
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
21140
21771
|
}
|
|
21141
21772
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -21229,7 +21860,7 @@ Use vault_login to fill the login form. Credentials are filled directly — you
|
|
|
21229
21860
|
try {
|
|
21230
21861
|
hostname = new URL(tab.state.url).hostname;
|
|
21231
21862
|
} catch (err) {
|
|
21232
|
-
logger$
|
|
21863
|
+
logger$8.warn("Failed to parse active tab URL for vault_totp:", err);
|
|
21233
21864
|
return asErrorTextResponse("Could not parse active tab URL");
|
|
21234
21865
|
}
|
|
21235
21866
|
const matches = findEntriesForDomain(`https://${hostname}`);
|
|
@@ -21401,7 +22032,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
21401
22032
|
await mcpServer.connect(transport);
|
|
21402
22033
|
await transport.handleRequest(req, res);
|
|
21403
22034
|
} catch (error) {
|
|
21404
|
-
logger$
|
|
22035
|
+
logger$8.error("Error handling request:", error);
|
|
21405
22036
|
if (!res.headersSent) {
|
|
21406
22037
|
res.writeHead(500, { "Content-Type": "application/json" });
|
|
21407
22038
|
res.end(
|
|
@@ -21420,7 +22051,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
21420
22051
|
};
|
|
21421
22052
|
server.once("error", (error) => {
|
|
21422
22053
|
const message = error.code === "EADDRINUSE" ? `Port ${port} is already in use. MCP server not started.` : error.message;
|
|
21423
|
-
logger$
|
|
22054
|
+
logger$8.error("Server error:", error);
|
|
21424
22055
|
clearMcpAuthFile();
|
|
21425
22056
|
setMcpHealth({
|
|
21426
22057
|
configuredPort: port,
|
|
@@ -21452,7 +22083,7 @@ function startMcpServer(tabManager, runtime2, port) {
|
|
|
21452
22083
|
message: `MCP server listening on ${endpoint}.`
|
|
21453
22084
|
});
|
|
21454
22085
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
21455
|
-
logger$
|
|
22086
|
+
logger$8.info(`Server listening on ${endpoint} (auth enabled)`);
|
|
21456
22087
|
}
|
|
21457
22088
|
if (mcpAuthToken) {
|
|
21458
22089
|
writeMcpAuthFile(endpoint, mcpAuthToken);
|
|
@@ -21491,7 +22122,7 @@ function stopMcpServer() {
|
|
|
21491
22122
|
message: "MCP server is stopped."
|
|
21492
22123
|
});
|
|
21493
22124
|
if (process.env.VESSEL_DEBUG_MCP === "1" || process.env.VESSEL_DEBUG_MCP === "true") {
|
|
21494
|
-
logger$
|
|
22125
|
+
logger$8.info("Server stopped");
|
|
21495
22126
|
}
|
|
21496
22127
|
resolve();
|
|
21497
22128
|
});
|
|
@@ -21512,7 +22143,7 @@ const KIT_ID_UNSAFE_CHAR_PATTERN = /[\/\\\0]/;
|
|
|
21512
22143
|
function isSafeAutomationKitId(id) {
|
|
21513
22144
|
return id.length > 0 && !KIT_ID_UNSAFE_CHAR_PATTERN.test(id);
|
|
21514
22145
|
}
|
|
21515
|
-
const logger$
|
|
22146
|
+
const logger$7 = createLogger("KitRegistry");
|
|
21516
22147
|
function getUserKitsDir() {
|
|
21517
22148
|
return path$1.join(electron.app.getPath("userData"), "kits");
|
|
21518
22149
|
}
|
|
@@ -21550,10 +22181,10 @@ function getInstalledKits() {
|
|
|
21550
22181
|
if (isValidKit(parsed)) {
|
|
21551
22182
|
kits.push(parsed);
|
|
21552
22183
|
} else {
|
|
21553
|
-
logger$
|
|
22184
|
+
logger$7.warn(`Skipping invalid kit file: ${file}`);
|
|
21554
22185
|
}
|
|
21555
22186
|
} catch (err) {
|
|
21556
|
-
logger$
|
|
22187
|
+
logger$7.warn(`Failed to read kit file: ${file}`, err);
|
|
21557
22188
|
}
|
|
21558
22189
|
}
|
|
21559
22190
|
return kits;
|
|
@@ -21625,7 +22256,7 @@ function uninstallKit(id, scheduledKitIds) {
|
|
|
21625
22256
|
return errorResult("Failed to remove the kit file.");
|
|
21626
22257
|
}
|
|
21627
22258
|
}
|
|
21628
|
-
const logger$
|
|
22259
|
+
const logger$6 = createLogger("Scheduler");
|
|
21629
22260
|
let jobs = [];
|
|
21630
22261
|
let removeIdleListener = null;
|
|
21631
22262
|
let broadcastFn = null;
|
|
@@ -21650,7 +22281,7 @@ function saveJobs() {
|
|
|
21650
22281
|
try {
|
|
21651
22282
|
fs$1.writeFileSync(getJobsPath(), JSON.stringify(jobs, null, 2), "utf-8");
|
|
21652
22283
|
} catch (err) {
|
|
21653
|
-
logger$
|
|
22284
|
+
logger$6.warn("Failed to save jobs:", err);
|
|
21654
22285
|
}
|
|
21655
22286
|
}
|
|
21656
22287
|
function normalizeJob(job, now = /* @__PURE__ */ new Date()) {
|
|
@@ -21772,7 +22403,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
21772
22403
|
};
|
|
21773
22404
|
startActivity();
|
|
21774
22405
|
if (!settings2.chatProvider) {
|
|
21775
|
-
logger$
|
|
22406
|
+
logger$6.warn(`Job "${job.kitName}" skipped — no chat provider configured`);
|
|
21776
22407
|
appendActivity(
|
|
21777
22408
|
"Chat provider not configured. Open Settings (Ctrl+,) to choose a provider."
|
|
21778
22409
|
);
|
|
@@ -21780,7 +22411,7 @@ async function fireJob(job, windowState, runtime2) {
|
|
|
21780
22411
|
return;
|
|
21781
22412
|
}
|
|
21782
22413
|
if (process.env.VESSEL_DEBUG_SCHEDULER === "1" || process.env.VESSEL_DEBUG_SCHEDULER === "true") {
|
|
21783
|
-
logger$
|
|
22414
|
+
logger$6.info(`Firing scheduled job: ${job.kitName} (${job.id})`);
|
|
21784
22415
|
}
|
|
21785
22416
|
try {
|
|
21786
22417
|
const provider = createProvider(settings2.chatProvider);
|
|
@@ -21833,7 +22464,7 @@ function tick(windowState, runtime2) {
|
|
|
21833
22464
|
saveJobs();
|
|
21834
22465
|
broadcastFn?.(Channels.SCHEDULE_JOBS_UPDATE, jobs);
|
|
21835
22466
|
void fireJob(job, windowState, runtime2).catch((err) => {
|
|
21836
|
-
logger$
|
|
22467
|
+
logger$6.warn("Unexpected error firing job:", err);
|
|
21837
22468
|
}).finally(fireNext);
|
|
21838
22469
|
};
|
|
21839
22470
|
fireNext();
|
|
@@ -22403,96 +23034,1078 @@ function registerWindowControlHandlers(mainWindow) {
|
|
|
22403
23034
|
mainWindow.close();
|
|
22404
23035
|
});
|
|
22405
23036
|
}
|
|
22406
|
-
|
|
22407
|
-
|
|
22408
|
-
|
|
22409
|
-
|
|
22410
|
-
|
|
22411
|
-
|
|
22412
|
-
|
|
22413
|
-
|
|
22414
|
-
|
|
22415
|
-
|
|
22416
|
-
|
|
22417
|
-
|
|
22418
|
-
|
|
22419
|
-
|
|
22420
|
-
|
|
22421
|
-
|
|
22422
|
-
|
|
22423
|
-
|
|
22424
|
-
|
|
22425
|
-
|
|
22426
|
-
|
|
22427
|
-
|
|
22428
|
-
|
|
22429
|
-
|
|
22430
|
-
|
|
22431
|
-
|
|
22432
|
-
|
|
22433
|
-
|
|
22434
|
-
|
|
22435
|
-
|
|
22436
|
-
|
|
22437
|
-
|
|
22438
|
-
|
|
22439
|
-
|
|
22440
|
-
|
|
23037
|
+
const BLOCKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
23038
|
+
"script",
|
|
23039
|
+
"image",
|
|
23040
|
+
"xhr",
|
|
23041
|
+
"fetch",
|
|
23042
|
+
"subFrame",
|
|
23043
|
+
"media",
|
|
23044
|
+
"ping",
|
|
23045
|
+
"webSocket"
|
|
23046
|
+
]);
|
|
23047
|
+
const BLOCKED_HOST_SUFFIXES = [
|
|
23048
|
+
"doubleclick.net",
|
|
23049
|
+
"googlesyndication.com",
|
|
23050
|
+
"googleadservices.com",
|
|
23051
|
+
"adservice.google.com",
|
|
23052
|
+
"adnxs.com",
|
|
23053
|
+
"adsrvr.org",
|
|
23054
|
+
"taboola.com",
|
|
23055
|
+
"outbrain.com",
|
|
23056
|
+
"criteo.com",
|
|
23057
|
+
"criteo.net",
|
|
23058
|
+
"pubmatic.com",
|
|
23059
|
+
"rubiconproject.com",
|
|
23060
|
+
"openx.net",
|
|
23061
|
+
"casalemedia.com",
|
|
23062
|
+
"advertising.com",
|
|
23063
|
+
"amazon-adsystem.com",
|
|
23064
|
+
"adsymptotic.com",
|
|
23065
|
+
"moatads.com",
|
|
23066
|
+
"quantserve.com",
|
|
23067
|
+
"scorecardresearch.com"
|
|
23068
|
+
];
|
|
23069
|
+
const THIRD_PARTY_PATH_PATTERNS = [
|
|
23070
|
+
/\/ads?[/?._-]/i,
|
|
23071
|
+
/\/adservice/i,
|
|
23072
|
+
/\/advert/i,
|
|
23073
|
+
/\/prebid/i,
|
|
23074
|
+
/\/banner/i,
|
|
23075
|
+
/\/sponsor/i,
|
|
23076
|
+
/\/promotions?\//i,
|
|
23077
|
+
/\/trk\//i,
|
|
23078
|
+
/\/track(ing)?\//i,
|
|
23079
|
+
/\/beacon/i,
|
|
23080
|
+
/\/pixel/i
|
|
23081
|
+
];
|
|
23082
|
+
let installed = false;
|
|
23083
|
+
const defaultSessionTabManagers = /* @__PURE__ */ new Set();
|
|
23084
|
+
function normalizeHostname(value) {
|
|
23085
|
+
return value.trim().toLowerCase().replace(/\.$/, "");
|
|
23086
|
+
}
|
|
23087
|
+
function hostnameMatches(hostname, suffix) {
|
|
23088
|
+
return hostname === suffix || hostname.endsWith(`.${suffix}`);
|
|
23089
|
+
}
|
|
23090
|
+
function parseHostname(url) {
|
|
23091
|
+
try {
|
|
23092
|
+
const parsed = new URL(url);
|
|
23093
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
23094
|
+
return null;
|
|
22441
23095
|
}
|
|
22442
|
-
|
|
22443
|
-
|
|
22444
|
-
|
|
22445
|
-
|
|
22446
|
-
|
|
23096
|
+
return normalizeHostname(parsed.hostname);
|
|
23097
|
+
} catch {
|
|
23098
|
+
return null;
|
|
23099
|
+
}
|
|
23100
|
+
}
|
|
23101
|
+
function isThirdParty(url, firstPartyHost) {
|
|
23102
|
+
if (!firstPartyHost) return true;
|
|
23103
|
+
const target = normalizeHostname(url.hostname);
|
|
23104
|
+
return !(target === firstPartyHost || target.endsWith(`.${firstPartyHost}`));
|
|
23105
|
+
}
|
|
23106
|
+
function shouldBlockRequest(details) {
|
|
23107
|
+
if (!BLOCKED_RESOURCE_TYPES.has(details.resourceType)) return false;
|
|
23108
|
+
let parsed;
|
|
23109
|
+
try {
|
|
23110
|
+
parsed = new URL(details.url);
|
|
23111
|
+
} catch {
|
|
23112
|
+
return false;
|
|
23113
|
+
}
|
|
23114
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
23115
|
+
return false;
|
|
23116
|
+
}
|
|
23117
|
+
const hostname = normalizeHostname(parsed.hostname);
|
|
23118
|
+
if (BLOCKED_HOST_SUFFIXES.some((suffix) => hostnameMatches(hostname, suffix))) {
|
|
23119
|
+
return true;
|
|
23120
|
+
}
|
|
23121
|
+
const firstPartyHost = parseHostname(details.referrer) || parseHostname(details.initiator || "");
|
|
23122
|
+
if (!isThirdParty(parsed, firstPartyHost)) return false;
|
|
23123
|
+
const candidate = `${hostname}${parsed.pathname}${parsed.search}`;
|
|
23124
|
+
return THIRD_PARTY_PATH_PATTERNS.some((pattern) => pattern.test(candidate));
|
|
23125
|
+
}
|
|
23126
|
+
function installAdBlocking(tabManager) {
|
|
23127
|
+
defaultSessionTabManagers.add(tabManager);
|
|
23128
|
+
if (installed) return;
|
|
23129
|
+
installed = true;
|
|
23130
|
+
electron.session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
|
23131
|
+
const webContentsId = typeof details.webContentsId === "number" ? details.webContentsId : null;
|
|
23132
|
+
if (webContentsId == null) {
|
|
23133
|
+
callback({});
|
|
23134
|
+
return;
|
|
22447
23135
|
}
|
|
22448
|
-
|
|
22449
|
-
|
|
22450
|
-
|
|
22451
|
-
|
|
22452
|
-
|
|
22453
|
-
|
|
22454
|
-
flushRuntimeUpdate();
|
|
22455
|
-
}, 32);
|
|
22456
|
-
};
|
|
22457
|
-
electron.app.on("before-quit", () => {
|
|
22458
|
-
if (runtimeUpdateTimer) {
|
|
22459
|
-
clearTimeout(runtimeUpdateTimer);
|
|
22460
|
-
runtimeUpdateTimer = null;
|
|
23136
|
+
const manager = [...defaultSessionTabManagers].find(
|
|
23137
|
+
(candidate) => candidate.findTabByWebContentsId(webContentsId)
|
|
23138
|
+
);
|
|
23139
|
+
if (!manager?.isAdBlockingEnabledForWebContents(webContentsId)) {
|
|
23140
|
+
callback({});
|
|
23141
|
+
return;
|
|
22461
23142
|
}
|
|
22462
|
-
|
|
23143
|
+
callback({ cancel: shouldBlockRequest(details) });
|
|
22463
23144
|
});
|
|
22464
|
-
|
|
22465
|
-
|
|
22466
|
-
|
|
22467
|
-
|
|
22468
|
-
|
|
22469
|
-
|
|
22470
|
-
const
|
|
22471
|
-
|
|
22472
|
-
|
|
22473
|
-
|
|
22474
|
-
|
|
22475
|
-
|
|
22476
|
-
|
|
22477
|
-
|
|
22478
|
-
}
|
|
22479
|
-
|
|
22480
|
-
|
|
22481
|
-
|
|
22482
|
-
|
|
22483
|
-
|
|
22484
|
-
|
|
22485
|
-
|
|
22486
|
-
|
|
22487
|
-
|
|
22488
|
-
|
|
22489
|
-
|
|
22490
|
-
|
|
22491
|
-
|
|
22492
|
-
|
|
22493
|
-
|
|
22494
|
-
|
|
22495
|
-
|
|
23145
|
+
}
|
|
23146
|
+
function unregisterAdBlockingTabManager(tabManager) {
|
|
23147
|
+
defaultSessionTabManagers.delete(tabManager);
|
|
23148
|
+
}
|
|
23149
|
+
function installAdBlockingForSession(ses, tabManager) {
|
|
23150
|
+
ses.webRequest.onBeforeRequest((details, callback) => {
|
|
23151
|
+
const webContentsId = typeof details.webContentsId === "number" ? details.webContentsId : null;
|
|
23152
|
+
if (webContentsId == null) {
|
|
23153
|
+
callback({});
|
|
23154
|
+
return;
|
|
23155
|
+
}
|
|
23156
|
+
if (!tabManager.isAdBlockingEnabledForWebContents(webContentsId)) {
|
|
23157
|
+
callback({});
|
|
23158
|
+
return;
|
|
23159
|
+
}
|
|
23160
|
+
callback({ cancel: shouldBlockRequest(details) });
|
|
23161
|
+
});
|
|
23162
|
+
}
|
|
23163
|
+
const defaultDownloadViews = /* @__PURE__ */ new Set();
|
|
23164
|
+
let defaultDownloadHandlerInstalled = false;
|
|
23165
|
+
function resolveDownloadPath(downloadDir, filename) {
|
|
23166
|
+
fs$1.mkdirSync(downloadDir, { recursive: true });
|
|
23167
|
+
const parsed = path.parse(filename);
|
|
23168
|
+
let attempt = 0;
|
|
23169
|
+
while (true) {
|
|
23170
|
+
const candidateName = attempt === 0 ? filename : `${parsed.name} (${attempt})${parsed.ext}`;
|
|
23171
|
+
const candidatePath = path.join(downloadDir, candidateName);
|
|
23172
|
+
if (!fs$1.existsSync(candidatePath)) {
|
|
23173
|
+
return candidatePath;
|
|
23174
|
+
}
|
|
23175
|
+
attempt += 1;
|
|
23176
|
+
}
|
|
23177
|
+
}
|
|
23178
|
+
function installDownloadHandler(chromeView) {
|
|
23179
|
+
defaultDownloadViews.add(chromeView);
|
|
23180
|
+
if (defaultDownloadHandlerInstalled) return;
|
|
23181
|
+
defaultDownloadHandlerInstalled = true;
|
|
23182
|
+
installDownloadHandlerForSession(electron.session.defaultSession, defaultDownloadViews);
|
|
23183
|
+
}
|
|
23184
|
+
function unregisterDownloadHandler(chromeView) {
|
|
23185
|
+
defaultDownloadViews.delete(chromeView);
|
|
23186
|
+
}
|
|
23187
|
+
function installDownloadHandlerForSession(targetSession, chromeView) {
|
|
23188
|
+
const send = (channel, info) => {
|
|
23189
|
+
const views = chromeView instanceof electron.WebContentsView ? [chromeView] : [...chromeView];
|
|
23190
|
+
for (const view of views) {
|
|
23191
|
+
if (!view.webContents.isDestroyed()) {
|
|
23192
|
+
view.webContents.send(channel, info);
|
|
23193
|
+
}
|
|
23194
|
+
}
|
|
23195
|
+
};
|
|
23196
|
+
targetSession.on("will-download", (_event, item) => {
|
|
23197
|
+
const settings2 = loadSettings();
|
|
23198
|
+
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
23199
|
+
const filename = item.getFilename();
|
|
23200
|
+
const savePath = resolveDownloadPath(downloadDir, filename);
|
|
23201
|
+
item.setSavePath(savePath);
|
|
23202
|
+
const info = {
|
|
23203
|
+
filename,
|
|
23204
|
+
savePath,
|
|
23205
|
+
totalBytes: item.getTotalBytes(),
|
|
23206
|
+
receivedBytes: 0,
|
|
23207
|
+
state: "progressing"
|
|
23208
|
+
};
|
|
23209
|
+
send(Channels.DOWNLOAD_STARTED, info);
|
|
23210
|
+
item.on("updated", (_event2, state2) => {
|
|
23211
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
23212
|
+
info.totalBytes = item.getTotalBytes();
|
|
23213
|
+
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
23214
|
+
send(Channels.DOWNLOAD_PROGRESS, info);
|
|
23215
|
+
});
|
|
23216
|
+
item.once("done", (_event2, state2) => {
|
|
23217
|
+
info.receivedBytes = item.getReceivedBytes();
|
|
23218
|
+
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
23219
|
+
send(Channels.DOWNLOAD_DONE, info);
|
|
23220
|
+
});
|
|
23221
|
+
});
|
|
23222
|
+
}
|
|
23223
|
+
const logger$5 = createLogger("PrivateWindow");
|
|
23224
|
+
const CHROME_HEIGHT$1 = 110;
|
|
23225
|
+
const privateWindows = /* @__PURE__ */ new Set();
|
|
23226
|
+
function resolveRendererFile$2() {
|
|
23227
|
+
const candidates = [
|
|
23228
|
+
path.join(__dirname, "../../out/renderer/index.html"),
|
|
23229
|
+
path.join(__dirname, "../../../out/renderer/index.html")
|
|
23230
|
+
];
|
|
23231
|
+
for (const c of candidates) {
|
|
23232
|
+
try {
|
|
23233
|
+
fs.accessSync(c);
|
|
23234
|
+
return c;
|
|
23235
|
+
} catch {
|
|
23236
|
+
}
|
|
23237
|
+
}
|
|
23238
|
+
return path.join(__dirname, "../../out/renderer/index.html");
|
|
23239
|
+
}
|
|
23240
|
+
function layoutPrivateViews(state2) {
|
|
23241
|
+
const { window: win, chromeView, tabManager } = state2;
|
|
23242
|
+
const [width, height] = win.getContentSize();
|
|
23243
|
+
chromeView.setBounds({ x: 0, y: 0, width, height: CHROME_HEIGHT$1 });
|
|
23244
|
+
win.contentView.removeChildView(chromeView);
|
|
23245
|
+
win.contentView.addChildView(chromeView);
|
|
23246
|
+
const activeTab = tabManager.getActiveTab();
|
|
23247
|
+
if (activeTab) {
|
|
23248
|
+
activeTab.view.setBounds({
|
|
23249
|
+
x: 0,
|
|
23250
|
+
y: CHROME_HEIGHT$1,
|
|
23251
|
+
width,
|
|
23252
|
+
height: height - CHROME_HEIGHT$1
|
|
23253
|
+
});
|
|
23254
|
+
}
|
|
23255
|
+
}
|
|
23256
|
+
function loadPrivateRenderer(chromeView) {
|
|
23257
|
+
const devUrl = process.env.ELECTRON_RENDERER_URL;
|
|
23258
|
+
if (devUrl) {
|
|
23259
|
+
const url = new URL(devUrl);
|
|
23260
|
+
url.searchParams.set("view", "chrome");
|
|
23261
|
+
url.searchParams.set("private", "1");
|
|
23262
|
+
chromeView.webContents.loadURL(url.toString());
|
|
23263
|
+
} else {
|
|
23264
|
+
chromeView.webContents.loadFile(resolveRendererFile$2(), {
|
|
23265
|
+
query: { view: "chrome", private: "1" }
|
|
23266
|
+
});
|
|
23267
|
+
}
|
|
23268
|
+
}
|
|
23269
|
+
function registerPrivateIpcHandlers(state2) {
|
|
23270
|
+
const { chromeView, tabManager } = state2;
|
|
23271
|
+
const ipc = chromeView.webContents.ipc;
|
|
23272
|
+
let findResultListener = null;
|
|
23273
|
+
let findWiredWcId = null;
|
|
23274
|
+
const wireFindEvents = (wc) => {
|
|
23275
|
+
if (findWiredWcId === wc.id && findResultListener) return;
|
|
23276
|
+
if (findWiredWcId && findResultListener) {
|
|
23277
|
+
const previous = tabManager.findTabByWebContentsId(findWiredWcId);
|
|
23278
|
+
previous?.view.webContents.removeListener(
|
|
23279
|
+
"found-in-page",
|
|
23280
|
+
findResultListener
|
|
23281
|
+
);
|
|
23282
|
+
}
|
|
23283
|
+
findWiredWcId = wc.id;
|
|
23284
|
+
if (wc.isDestroyed()) return;
|
|
23285
|
+
const listener = (_event, result) => {
|
|
23286
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
23287
|
+
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
23288
|
+
}
|
|
23289
|
+
};
|
|
23290
|
+
findResultListener = listener;
|
|
23291
|
+
wc.on("found-in-page", listener);
|
|
23292
|
+
const capturedWcId = wc.id;
|
|
23293
|
+
wc.once("destroyed", () => {
|
|
23294
|
+
if (findWiredWcId === capturedWcId) {
|
|
23295
|
+
findWiredWcId = null;
|
|
23296
|
+
findResultListener = null;
|
|
23297
|
+
}
|
|
23298
|
+
});
|
|
23299
|
+
};
|
|
23300
|
+
ipc.handle(Channels.TAB_CREATE, (_e, url) => {
|
|
23301
|
+
return tabManager.createTab(url);
|
|
23302
|
+
});
|
|
23303
|
+
ipc.handle(Channels.TAB_CLOSE, (_e, id) => {
|
|
23304
|
+
tabManager.closeTab(id);
|
|
23305
|
+
layoutPrivateViews(state2);
|
|
23306
|
+
});
|
|
23307
|
+
ipc.handle(Channels.TAB_SWITCH, (_e, id) => {
|
|
23308
|
+
tabManager.switchTab(id);
|
|
23309
|
+
layoutPrivateViews(state2);
|
|
23310
|
+
});
|
|
23311
|
+
ipc.handle(Channels.TAB_NAVIGATE, (_e, id, url) => {
|
|
23312
|
+
return tabManager.navigateTab(id, url);
|
|
23313
|
+
});
|
|
23314
|
+
ipc.handle(Channels.TAB_BACK, (_e, id) => {
|
|
23315
|
+
tabManager.goBack(id);
|
|
23316
|
+
});
|
|
23317
|
+
ipc.handle(Channels.TAB_FORWARD, (_e, id) => {
|
|
23318
|
+
tabManager.goForward(id);
|
|
23319
|
+
});
|
|
23320
|
+
ipc.handle(Channels.TAB_RELOAD, (_e, id) => {
|
|
23321
|
+
tabManager.reloadTab(id);
|
|
23322
|
+
});
|
|
23323
|
+
ipc.handle(Channels.TAB_TOGGLE_AD_BLOCK, (_e, id) => {
|
|
23324
|
+
const tab = tabManager.getTab(id);
|
|
23325
|
+
if (!tab) return null;
|
|
23326
|
+
const newState = !tab.state.adBlockingEnabled;
|
|
23327
|
+
tab.setAdBlockingEnabled(newState);
|
|
23328
|
+
return newState;
|
|
23329
|
+
});
|
|
23330
|
+
ipc.handle(Channels.TAB_ZOOM_IN, (_e, id) => {
|
|
23331
|
+
tabManager.zoomIn(id);
|
|
23332
|
+
});
|
|
23333
|
+
ipc.handle(Channels.TAB_ZOOM_OUT, (_e, id) => {
|
|
23334
|
+
tabManager.zoomOut(id);
|
|
23335
|
+
});
|
|
23336
|
+
ipc.handle(Channels.TAB_ZOOM_RESET, (_e, id) => {
|
|
23337
|
+
tabManager.zoomReset(id);
|
|
23338
|
+
});
|
|
23339
|
+
ipc.handle(Channels.TAB_STATE_GET, () => ({
|
|
23340
|
+
tabs: tabManager.getAllStates(),
|
|
23341
|
+
activeId: tabManager.getActiveTabId() || ""
|
|
23342
|
+
}));
|
|
23343
|
+
ipc.handle(Channels.TAB_REOPEN_CLOSED, () => {
|
|
23344
|
+
const id = tabManager.reopenClosedTab();
|
|
23345
|
+
if (id) layoutPrivateViews(state2);
|
|
23346
|
+
return id;
|
|
23347
|
+
});
|
|
23348
|
+
ipc.handle(Channels.TAB_DUPLICATE, (_e, id) => {
|
|
23349
|
+
const newId = tabManager.duplicateTab(id);
|
|
23350
|
+
if (newId) layoutPrivateViews(state2);
|
|
23351
|
+
return newId;
|
|
23352
|
+
});
|
|
23353
|
+
ipc.handle(Channels.TAB_PIN, (_e, id) => {
|
|
23354
|
+
tabManager.pinTab(id);
|
|
23355
|
+
});
|
|
23356
|
+
ipc.handle(Channels.TAB_UNPIN, (_e, id) => {
|
|
23357
|
+
tabManager.unpinTab(id);
|
|
23358
|
+
});
|
|
23359
|
+
ipc.handle(Channels.TAB_GROUP_CREATE, (_e, id) => {
|
|
23360
|
+
return tabManager.createGroupFromTab(id);
|
|
23361
|
+
});
|
|
23362
|
+
ipc.handle(Channels.TAB_GROUP_ADD_TAB, (_e, id, groupId) => {
|
|
23363
|
+
tabManager.assignTabToGroup(id, groupId);
|
|
23364
|
+
});
|
|
23365
|
+
ipc.handle(Channels.TAB_GROUP_REMOVE_TAB, (_e, id) => {
|
|
23366
|
+
tabManager.removeTabFromGroup(id);
|
|
23367
|
+
});
|
|
23368
|
+
ipc.handle(Channels.TAB_GROUP_TOGGLE_COLLAPSED, (_e, groupId) => {
|
|
23369
|
+
return tabManager.toggleGroupCollapsed(groupId);
|
|
23370
|
+
});
|
|
23371
|
+
ipc.handle(
|
|
23372
|
+
Channels.TAB_GROUP_SET_COLOR,
|
|
23373
|
+
(_e, groupId, color) => {
|
|
23374
|
+
tabManager.setGroupColor(groupId, color);
|
|
23375
|
+
}
|
|
23376
|
+
);
|
|
23377
|
+
ipc.handle(Channels.TAB_TOGGLE_MUTE, (_e, id) => {
|
|
23378
|
+
return tabManager.toggleMuted(id);
|
|
23379
|
+
});
|
|
23380
|
+
ipc.handle(Channels.TAB_PRINT, (_e, id) => {
|
|
23381
|
+
tabManager.printTab(id);
|
|
23382
|
+
});
|
|
23383
|
+
ipc.handle(Channels.TAB_PRINT_TO_PDF, (_e, id) => {
|
|
23384
|
+
return tabManager.saveTabAsPdf(id);
|
|
23385
|
+
});
|
|
23386
|
+
ipc.on(Channels.TAB_CONTEXT_MENU, (_e, id) => {
|
|
23387
|
+
const { Menu, MenuItem } = require("electron");
|
|
23388
|
+
const tab = tabManager.getTab(id);
|
|
23389
|
+
const isPinned = tab?.state.isPinned ?? false;
|
|
23390
|
+
const groupId = tab?.state.groupId;
|
|
23391
|
+
const isMuted = tab?.state.isMuted ?? false;
|
|
23392
|
+
const groups = tabManager.getAllStates().filter((state22) => state22.groupId && state22.groupId !== groupId).reduce(
|
|
23393
|
+
(map, state22) => map.set(state22.groupId, {
|
|
23394
|
+
id: state22.groupId,
|
|
23395
|
+
name: state22.groupName || "Group"
|
|
23396
|
+
}),
|
|
23397
|
+
/* @__PURE__ */ new Map()
|
|
23398
|
+
);
|
|
23399
|
+
const menu = new Menu();
|
|
23400
|
+
menu.append(
|
|
23401
|
+
new MenuItem({
|
|
23402
|
+
label: isPinned ? "Unpin Tab" : "Pin Tab",
|
|
23403
|
+
click: () => {
|
|
23404
|
+
if (isPinned) {
|
|
23405
|
+
tabManager.unpinTab(id);
|
|
23406
|
+
} else {
|
|
23407
|
+
tabManager.pinTab(id);
|
|
23408
|
+
}
|
|
23409
|
+
}
|
|
23410
|
+
})
|
|
23411
|
+
);
|
|
23412
|
+
menu.append(
|
|
23413
|
+
new MenuItem({
|
|
23414
|
+
label: "Duplicate Tab",
|
|
23415
|
+
click: () => {
|
|
23416
|
+
const newId = tabManager.duplicateTab(id);
|
|
23417
|
+
if (newId) layoutPrivateViews(state2);
|
|
23418
|
+
}
|
|
23419
|
+
})
|
|
23420
|
+
);
|
|
23421
|
+
menu.append(
|
|
23422
|
+
new MenuItem({
|
|
23423
|
+
label: "Add to New Group",
|
|
23424
|
+
click: () => {
|
|
23425
|
+
tabManager.createGroupFromTab(id);
|
|
23426
|
+
}
|
|
23427
|
+
})
|
|
23428
|
+
);
|
|
23429
|
+
if (groups.size > 0) {
|
|
23430
|
+
menu.append(
|
|
23431
|
+
new MenuItem({
|
|
23432
|
+
label: "Add to Group",
|
|
23433
|
+
submenu: [...groups.values()].map(
|
|
23434
|
+
(group) => new MenuItem({
|
|
23435
|
+
label: group.name,
|
|
23436
|
+
click: () => tabManager.assignTabToGroup(id, group.id)
|
|
23437
|
+
})
|
|
23438
|
+
)
|
|
23439
|
+
})
|
|
23440
|
+
);
|
|
23441
|
+
}
|
|
23442
|
+
if (groupId) {
|
|
23443
|
+
menu.append(
|
|
23444
|
+
new MenuItem({
|
|
23445
|
+
label: "Remove from Group",
|
|
23446
|
+
click: () => {
|
|
23447
|
+
tabManager.removeTabFromGroup(id);
|
|
23448
|
+
}
|
|
23449
|
+
})
|
|
23450
|
+
);
|
|
23451
|
+
}
|
|
23452
|
+
menu.append(
|
|
23453
|
+
new MenuItem({
|
|
23454
|
+
label: isMuted ? "Unmute Tab" : "Mute Tab",
|
|
23455
|
+
click: () => {
|
|
23456
|
+
tabManager.toggleMuted(id);
|
|
23457
|
+
}
|
|
23458
|
+
})
|
|
23459
|
+
);
|
|
23460
|
+
menu.append(new MenuItem({ type: "separator" }));
|
|
23461
|
+
menu.append(
|
|
23462
|
+
new MenuItem({
|
|
23463
|
+
label: "Print Page",
|
|
23464
|
+
click: () => {
|
|
23465
|
+
tabManager.printTab(id);
|
|
23466
|
+
}
|
|
23467
|
+
})
|
|
23468
|
+
);
|
|
23469
|
+
menu.append(
|
|
23470
|
+
new MenuItem({
|
|
23471
|
+
label: "Save Page as PDF",
|
|
23472
|
+
click: () => {
|
|
23473
|
+
void tabManager.saveTabAsPdf(id).catch((error) => {
|
|
23474
|
+
logger$5.warn("Failed to save private page as PDF:", error);
|
|
23475
|
+
});
|
|
23476
|
+
}
|
|
23477
|
+
})
|
|
23478
|
+
);
|
|
23479
|
+
if (!isPinned) {
|
|
23480
|
+
menu.append(new MenuItem({ type: "separator" }));
|
|
23481
|
+
menu.append(
|
|
23482
|
+
new MenuItem({
|
|
23483
|
+
label: "Close Tab",
|
|
23484
|
+
click: () => {
|
|
23485
|
+
tabManager.closeTab(id);
|
|
23486
|
+
layoutPrivateViews(state2);
|
|
23487
|
+
}
|
|
23488
|
+
})
|
|
23489
|
+
);
|
|
23490
|
+
}
|
|
23491
|
+
menu.popup({ window: state2.window });
|
|
23492
|
+
});
|
|
23493
|
+
ipc.on(Channels.TAB_GROUP_CONTEXT_MENU, (_e, groupId) => {
|
|
23494
|
+
const { Menu, MenuItem } = require("electron");
|
|
23495
|
+
const firstTab = tabManager.getAllStates().find((tab) => tab.groupId === groupId);
|
|
23496
|
+
if (!firstTab) return;
|
|
23497
|
+
const menu = new Menu();
|
|
23498
|
+
menu.append(
|
|
23499
|
+
new MenuItem({
|
|
23500
|
+
label: firstTab.groupCollapsed ? "Expand Group" : "Collapse Group",
|
|
23501
|
+
click: () => tabManager.toggleGroupCollapsed(groupId)
|
|
23502
|
+
})
|
|
23503
|
+
);
|
|
23504
|
+
menu.append(
|
|
23505
|
+
new MenuItem({
|
|
23506
|
+
label: "Group Color",
|
|
23507
|
+
submenu: TAB_GROUP_COLORS.map(
|
|
23508
|
+
(color) => new MenuItem({
|
|
23509
|
+
label: TAB_GROUP_COLOR_LABELS[color],
|
|
23510
|
+
type: "radio",
|
|
23511
|
+
checked: firstTab.groupColor === color,
|
|
23512
|
+
click: () => tabManager.setGroupColor(groupId, color)
|
|
23513
|
+
})
|
|
23514
|
+
)
|
|
23515
|
+
})
|
|
23516
|
+
);
|
|
23517
|
+
menu.popup({ window: state2.window });
|
|
23518
|
+
});
|
|
23519
|
+
ipc.handle(Channels.IS_PRIVATE_MODE, () => true);
|
|
23520
|
+
ipc.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
23521
|
+
createPrivateWindow();
|
|
23522
|
+
});
|
|
23523
|
+
ipc.handle(Channels.OPEN_NEW_WINDOW, () => {
|
|
23524
|
+
const { createSecondaryWindow: createSecondaryWindow2 } = require("../secondary/window");
|
|
23525
|
+
createSecondaryWindow2();
|
|
23526
|
+
});
|
|
23527
|
+
ipc.handle(Channels.WINDOW_MINIMIZE, () => {
|
|
23528
|
+
state2.window.minimize();
|
|
23529
|
+
});
|
|
23530
|
+
ipc.handle(Channels.WINDOW_MAXIMIZE, () => {
|
|
23531
|
+
if (state2.window.isMaximized()) {
|
|
23532
|
+
state2.window.unmaximize();
|
|
23533
|
+
} else {
|
|
23534
|
+
state2.window.maximize();
|
|
23535
|
+
}
|
|
23536
|
+
});
|
|
23537
|
+
ipc.handle(Channels.WINDOW_CLOSE, () => {
|
|
23538
|
+
state2.window.close();
|
|
23539
|
+
});
|
|
23540
|
+
ipc.handle(Channels.SETTINGS_VISIBILITY, () => {
|
|
23541
|
+
return false;
|
|
23542
|
+
});
|
|
23543
|
+
ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
|
|
23544
|
+
ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
|
|
23545
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => ({ open: false }));
|
|
23546
|
+
ipc.handle(
|
|
23547
|
+
Channels.FIND_IN_PAGE_START,
|
|
23548
|
+
(_e, text, options) => {
|
|
23549
|
+
const tab = tabManager.getActiveTab();
|
|
23550
|
+
if (!tab) return null;
|
|
23551
|
+
const wc = tab.view.webContents;
|
|
23552
|
+
if (wc.isDestroyed()) return null;
|
|
23553
|
+
wireFindEvents(wc);
|
|
23554
|
+
return wc.findInPage(text, {
|
|
23555
|
+
forward: options?.forward ?? true,
|
|
23556
|
+
findNext: options?.findNext ?? false
|
|
23557
|
+
});
|
|
23558
|
+
}
|
|
23559
|
+
);
|
|
23560
|
+
ipc.handle(Channels.FIND_IN_PAGE_NEXT, (_e, forward) => {
|
|
23561
|
+
const tab = tabManager.getActiveTab();
|
|
23562
|
+
if (!tab) return null;
|
|
23563
|
+
const wc = tab.view.webContents;
|
|
23564
|
+
if (wc.isDestroyed()) return null;
|
|
23565
|
+
wireFindEvents(wc);
|
|
23566
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
23567
|
+
});
|
|
23568
|
+
ipc.handle(
|
|
23569
|
+
Channels.FIND_IN_PAGE_STOP,
|
|
23570
|
+
(_e, action) => {
|
|
23571
|
+
const tab = tabManager.getActiveTab();
|
|
23572
|
+
if (!tab) return;
|
|
23573
|
+
const wc = tab.view.webContents;
|
|
23574
|
+
if (wc.isDestroyed()) return;
|
|
23575
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
23576
|
+
}
|
|
23577
|
+
);
|
|
23578
|
+
}
|
|
23579
|
+
function createPrivateWindow() {
|
|
23580
|
+
const privateSessionPartition = `private-${crypto$1.randomUUID()}`;
|
|
23581
|
+
const privateSession = electron.session.fromPartition(privateSessionPartition);
|
|
23582
|
+
privateSession.setUserAgent(electron.session.defaultSession.getUserAgent());
|
|
23583
|
+
const win = new electron.BaseWindow({
|
|
23584
|
+
width: 1280,
|
|
23585
|
+
height: 800,
|
|
23586
|
+
minWidth: 800,
|
|
23587
|
+
minHeight: 600,
|
|
23588
|
+
frame: false,
|
|
23589
|
+
show: false,
|
|
23590
|
+
backgroundColor: "#1e1a2e",
|
|
23591
|
+
title: "Vessel - Private Browsing"
|
|
23592
|
+
});
|
|
23593
|
+
const chromeView = new electron.WebContentsView({
|
|
23594
|
+
webPreferences: {
|
|
23595
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
23596
|
+
sandbox: true,
|
|
23597
|
+
contextIsolation: true,
|
|
23598
|
+
nodeIntegration: false
|
|
23599
|
+
}
|
|
23600
|
+
});
|
|
23601
|
+
chromeView.setBackgroundColor("#00000000");
|
|
23602
|
+
win.contentView.addChildView(chromeView);
|
|
23603
|
+
const tabManager = new TabManager(
|
|
23604
|
+
win,
|
|
23605
|
+
(tabs, activeId) => {
|
|
23606
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
23607
|
+
chromeView.webContents.send(Channels.TAB_STATE_UPDATE, tabs, activeId);
|
|
23608
|
+
}
|
|
23609
|
+
layoutPrivateViews(state2);
|
|
23610
|
+
},
|
|
23611
|
+
{ isPrivate: true, sessionPartition: privateSessionPartition }
|
|
23612
|
+
);
|
|
23613
|
+
const state2 = {
|
|
23614
|
+
window: win,
|
|
23615
|
+
chromeView,
|
|
23616
|
+
tabManager,
|
|
23617
|
+
session: privateSession,
|
|
23618
|
+
sessionPartition: privateSessionPartition
|
|
23619
|
+
};
|
|
23620
|
+
installAdBlockingForSession(privateSession, tabManager);
|
|
23621
|
+
installDownloadHandlerForSession(privateSession, chromeView);
|
|
23622
|
+
registerPrivateIpcHandlers(state2);
|
|
23623
|
+
win.on("resize", () => layoutPrivateViews(state2));
|
|
23624
|
+
win.on("show", () => layoutPrivateViews(state2));
|
|
23625
|
+
win.on("closed", () => {
|
|
23626
|
+
privateWindows.delete(state2);
|
|
23627
|
+
tabManager.destroyAllTabs();
|
|
23628
|
+
void Promise.all([
|
|
23629
|
+
privateSession.clearStorageData(),
|
|
23630
|
+
privateSession.clearCache()
|
|
23631
|
+
]).catch((error) => {
|
|
23632
|
+
logger$5.warn("Failed to clear private browsing session:", error);
|
|
23633
|
+
});
|
|
23634
|
+
});
|
|
23635
|
+
privateWindows.add(state2);
|
|
23636
|
+
chromeView.webContents.once("dom-ready", () => {
|
|
23637
|
+
tabManager.createTab("about:blank");
|
|
23638
|
+
layoutPrivateViews(state2);
|
|
23639
|
+
});
|
|
23640
|
+
loadPrivateRenderer(chromeView);
|
|
23641
|
+
win.show();
|
|
23642
|
+
logger$5.info("Private browsing window opened");
|
|
23643
|
+
return state2;
|
|
23644
|
+
}
|
|
23645
|
+
const CHROME_HEIGHT = 110;
|
|
23646
|
+
const secondaryWindows = /* @__PURE__ */ new Set();
|
|
23647
|
+
function resolveRendererFile$1() {
|
|
23648
|
+
const candidates = [
|
|
23649
|
+
path.join(__dirname, "../../out/renderer/index.html"),
|
|
23650
|
+
path.join(__dirname, "../../../out/renderer/index.html")
|
|
23651
|
+
];
|
|
23652
|
+
for (const candidate of candidates) {
|
|
23653
|
+
try {
|
|
23654
|
+
fs.accessSync(candidate);
|
|
23655
|
+
return candidate;
|
|
23656
|
+
} catch {
|
|
23657
|
+
}
|
|
23658
|
+
}
|
|
23659
|
+
return path.join(__dirname, "../../out/renderer/index.html");
|
|
23660
|
+
}
|
|
23661
|
+
function layoutSecondaryViews(state2) {
|
|
23662
|
+
const { window: win, chromeView, tabManager } = state2;
|
|
23663
|
+
const [width, height] = win.getContentSize();
|
|
23664
|
+
chromeView.setBounds({ x: 0, y: 0, width, height: CHROME_HEIGHT });
|
|
23665
|
+
win.contentView.removeChildView(chromeView);
|
|
23666
|
+
win.contentView.addChildView(chromeView);
|
|
23667
|
+
const activeTab = tabManager.getActiveTab();
|
|
23668
|
+
if (activeTab) {
|
|
23669
|
+
activeTab.view.setBounds({
|
|
23670
|
+
x: 0,
|
|
23671
|
+
y: CHROME_HEIGHT,
|
|
23672
|
+
width,
|
|
23673
|
+
height: height - CHROME_HEIGHT
|
|
23674
|
+
});
|
|
23675
|
+
}
|
|
23676
|
+
}
|
|
23677
|
+
function loadSecondaryRenderer(chromeView) {
|
|
23678
|
+
const devUrl = process.env.ELECTRON_RENDERER_URL;
|
|
23679
|
+
if (devUrl) {
|
|
23680
|
+
const url = new URL(devUrl);
|
|
23681
|
+
url.searchParams.set("view", "chrome");
|
|
23682
|
+
url.searchParams.set("secondary", "1");
|
|
23683
|
+
chromeView.webContents.loadURL(url.toString());
|
|
23684
|
+
} else {
|
|
23685
|
+
chromeView.webContents.loadFile(resolveRendererFile$1(), {
|
|
23686
|
+
query: { view: "chrome", secondary: "1" }
|
|
23687
|
+
});
|
|
23688
|
+
}
|
|
23689
|
+
}
|
|
23690
|
+
function showTabContextMenu(state2, id) {
|
|
23691
|
+
const { tabManager } = state2;
|
|
23692
|
+
const tab = tabManager.getTab(id);
|
|
23693
|
+
const isPinned = tab?.state.isPinned ?? false;
|
|
23694
|
+
const groupId = tab?.state.groupId;
|
|
23695
|
+
const isMuted = tab?.state.isMuted ?? false;
|
|
23696
|
+
const groups = tabManager.getAllStates().filter((state22) => state22.groupId && state22.groupId !== groupId).reduce(
|
|
23697
|
+
(map, state22) => map.set(state22.groupId, {
|
|
23698
|
+
id: state22.groupId,
|
|
23699
|
+
name: state22.groupName || "Group"
|
|
23700
|
+
}),
|
|
23701
|
+
/* @__PURE__ */ new Map()
|
|
23702
|
+
);
|
|
23703
|
+
const menu = new electron.Menu();
|
|
23704
|
+
menu.append(
|
|
23705
|
+
new electron.MenuItem({
|
|
23706
|
+
label: isPinned ? "Unpin Tab" : "Pin Tab",
|
|
23707
|
+
click: () => isPinned ? tabManager.unpinTab(id) : tabManager.pinTab(id)
|
|
23708
|
+
})
|
|
23709
|
+
);
|
|
23710
|
+
menu.append(
|
|
23711
|
+
new electron.MenuItem({
|
|
23712
|
+
label: "Duplicate Tab",
|
|
23713
|
+
click: () => {
|
|
23714
|
+
const newId = tabManager.duplicateTab(id);
|
|
23715
|
+
if (newId) layoutSecondaryViews(state2);
|
|
23716
|
+
}
|
|
23717
|
+
})
|
|
23718
|
+
);
|
|
23719
|
+
menu.append(
|
|
23720
|
+
new electron.MenuItem({
|
|
23721
|
+
label: "Add to New Group",
|
|
23722
|
+
click: () => {
|
|
23723
|
+
tabManager.createGroupFromTab(id);
|
|
23724
|
+
}
|
|
23725
|
+
})
|
|
23726
|
+
);
|
|
23727
|
+
if (groups.size > 0) {
|
|
23728
|
+
menu.append(
|
|
23729
|
+
new electron.MenuItem({
|
|
23730
|
+
label: "Add to Group",
|
|
23731
|
+
submenu: [...groups.values()].map(
|
|
23732
|
+
(group) => new electron.MenuItem({
|
|
23733
|
+
label: group.name,
|
|
23734
|
+
click: () => tabManager.assignTabToGroup(id, group.id)
|
|
23735
|
+
})
|
|
23736
|
+
)
|
|
23737
|
+
})
|
|
23738
|
+
);
|
|
23739
|
+
}
|
|
23740
|
+
if (groupId) {
|
|
23741
|
+
menu.append(
|
|
23742
|
+
new electron.MenuItem({
|
|
23743
|
+
label: "Remove from Group",
|
|
23744
|
+
click: () => tabManager.removeTabFromGroup(id)
|
|
23745
|
+
})
|
|
23746
|
+
);
|
|
23747
|
+
}
|
|
23748
|
+
menu.append(
|
|
23749
|
+
new electron.MenuItem({
|
|
23750
|
+
label: isMuted ? "Unmute Tab" : "Mute Tab",
|
|
23751
|
+
click: () => tabManager.toggleMuted(id)
|
|
23752
|
+
})
|
|
23753
|
+
);
|
|
23754
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
23755
|
+
menu.append(
|
|
23756
|
+
new electron.MenuItem({ label: "Print Page", click: () => tabManager.printTab(id) })
|
|
23757
|
+
);
|
|
23758
|
+
menu.append(
|
|
23759
|
+
new electron.MenuItem({
|
|
23760
|
+
label: "Save Page as PDF",
|
|
23761
|
+
click: () => void tabManager.saveTabAsPdf(id)
|
|
23762
|
+
})
|
|
23763
|
+
);
|
|
23764
|
+
if (!isPinned) {
|
|
23765
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
23766
|
+
menu.append(
|
|
23767
|
+
new electron.MenuItem({
|
|
23768
|
+
label: "Close Tab",
|
|
23769
|
+
click: () => {
|
|
23770
|
+
tabManager.closeTab(id);
|
|
23771
|
+
layoutSecondaryViews(state2);
|
|
23772
|
+
}
|
|
23773
|
+
})
|
|
23774
|
+
);
|
|
23775
|
+
}
|
|
23776
|
+
menu.popup({ window: state2.window });
|
|
23777
|
+
}
|
|
23778
|
+
function showGroupContextMenu(state2, groupId) {
|
|
23779
|
+
const { tabManager } = state2;
|
|
23780
|
+
const firstTab = tabManager.getAllStates().find((tab) => tab.groupId === groupId);
|
|
23781
|
+
if (!firstTab) return;
|
|
23782
|
+
const menu = new electron.Menu();
|
|
23783
|
+
menu.append(
|
|
23784
|
+
new electron.MenuItem({
|
|
23785
|
+
label: firstTab.groupCollapsed ? "Expand Group" : "Collapse Group",
|
|
23786
|
+
click: () => tabManager.toggleGroupCollapsed(groupId)
|
|
23787
|
+
})
|
|
23788
|
+
);
|
|
23789
|
+
menu.append(
|
|
23790
|
+
new electron.MenuItem({
|
|
23791
|
+
label: "Group Color",
|
|
23792
|
+
submenu: TAB_GROUP_COLORS.map(
|
|
23793
|
+
(color) => new electron.MenuItem({
|
|
23794
|
+
label: TAB_GROUP_COLOR_LABELS[color],
|
|
23795
|
+
type: "radio",
|
|
23796
|
+
checked: firstTab.groupColor === color,
|
|
23797
|
+
click: () => tabManager.setGroupColor(groupId, color)
|
|
23798
|
+
})
|
|
23799
|
+
)
|
|
23800
|
+
})
|
|
23801
|
+
);
|
|
23802
|
+
menu.popup({ window: state2.window });
|
|
23803
|
+
}
|
|
23804
|
+
function registerSecondaryIpcHandlers(state2) {
|
|
23805
|
+
const { chromeView, tabManager } = state2;
|
|
23806
|
+
const ipc = chromeView.webContents.ipc;
|
|
23807
|
+
let findResultListener = null;
|
|
23808
|
+
let findWiredWcId = null;
|
|
23809
|
+
const wireFindEvents = (wc) => {
|
|
23810
|
+
if (findWiredWcId === wc.id && findResultListener) return;
|
|
23811
|
+
if (findWiredWcId && findResultListener) {
|
|
23812
|
+
const previous = tabManager.findTabByWebContentsId(findWiredWcId);
|
|
23813
|
+
previous?.view.webContents.removeListener(
|
|
23814
|
+
"found-in-page",
|
|
23815
|
+
findResultListener
|
|
23816
|
+
);
|
|
23817
|
+
}
|
|
23818
|
+
findWiredWcId = wc.id;
|
|
23819
|
+
if (wc.isDestroyed()) return;
|
|
23820
|
+
const listener = (_event, result) => {
|
|
23821
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
23822
|
+
chromeView.webContents.send(Channels.FIND_IN_PAGE_RESULT, result);
|
|
23823
|
+
}
|
|
23824
|
+
};
|
|
23825
|
+
findResultListener = listener;
|
|
23826
|
+
wc.on("found-in-page", listener);
|
|
23827
|
+
const capturedWcId = wc.id;
|
|
23828
|
+
wc.once("destroyed", () => {
|
|
23829
|
+
if (findWiredWcId === capturedWcId) {
|
|
23830
|
+
findWiredWcId = null;
|
|
23831
|
+
findResultListener = null;
|
|
23832
|
+
}
|
|
23833
|
+
});
|
|
23834
|
+
};
|
|
23835
|
+
ipc.handle(Channels.TAB_CREATE, (_e, url) => {
|
|
23836
|
+
return tabManager.createTab(url || loadSettings().defaultUrl);
|
|
23837
|
+
});
|
|
23838
|
+
ipc.handle(Channels.TAB_CLOSE, (_e, id) => {
|
|
23839
|
+
tabManager.closeTab(id);
|
|
23840
|
+
layoutSecondaryViews(state2);
|
|
23841
|
+
});
|
|
23842
|
+
ipc.handle(Channels.TAB_SWITCH, (_e, id) => {
|
|
23843
|
+
tabManager.switchTab(id);
|
|
23844
|
+
layoutSecondaryViews(state2);
|
|
23845
|
+
});
|
|
23846
|
+
ipc.handle(Channels.TAB_NAVIGATE, (_e, id, url) => {
|
|
23847
|
+
return tabManager.navigateTab(id, url);
|
|
23848
|
+
});
|
|
23849
|
+
ipc.handle(Channels.TAB_BACK, (_e, id) => tabManager.goBack(id));
|
|
23850
|
+
ipc.handle(Channels.TAB_FORWARD, (_e, id) => tabManager.goForward(id));
|
|
23851
|
+
ipc.handle(Channels.TAB_RELOAD, (_e, id) => tabManager.reloadTab(id));
|
|
23852
|
+
ipc.handle(Channels.TAB_TOGGLE_AD_BLOCK, (_e, id) => {
|
|
23853
|
+
const tab = tabManager.getTab(id);
|
|
23854
|
+
if (!tab) return null;
|
|
23855
|
+
const enabled = !tab.state.adBlockingEnabled;
|
|
23856
|
+
tab.setAdBlockingEnabled(enabled);
|
|
23857
|
+
return enabled;
|
|
23858
|
+
});
|
|
23859
|
+
ipc.handle(Channels.TAB_ZOOM_IN, (_e, id) => tabManager.zoomIn(id));
|
|
23860
|
+
ipc.handle(Channels.TAB_ZOOM_OUT, (_e, id) => tabManager.zoomOut(id));
|
|
23861
|
+
ipc.handle(Channels.TAB_ZOOM_RESET, (_e, id) => tabManager.zoomReset(id));
|
|
23862
|
+
ipc.handle(Channels.TAB_REOPEN_CLOSED, () => {
|
|
23863
|
+
const id = tabManager.reopenClosedTab();
|
|
23864
|
+
if (id) layoutSecondaryViews(state2);
|
|
23865
|
+
return id;
|
|
23866
|
+
});
|
|
23867
|
+
ipc.handle(Channels.TAB_DUPLICATE, (_e, id) => {
|
|
23868
|
+
const newId = tabManager.duplicateTab(id);
|
|
23869
|
+
if (newId) layoutSecondaryViews(state2);
|
|
23870
|
+
return newId;
|
|
23871
|
+
});
|
|
23872
|
+
ipc.handle(Channels.TAB_PIN, (_e, id) => tabManager.pinTab(id));
|
|
23873
|
+
ipc.handle(Channels.TAB_UNPIN, (_e, id) => tabManager.unpinTab(id));
|
|
23874
|
+
ipc.handle(
|
|
23875
|
+
Channels.TAB_GROUP_CREATE,
|
|
23876
|
+
(_e, id) => tabManager.createGroupFromTab(id)
|
|
23877
|
+
);
|
|
23878
|
+
ipc.handle(
|
|
23879
|
+
Channels.TAB_GROUP_ADD_TAB,
|
|
23880
|
+
(_e, id, groupId) => tabManager.assignTabToGroup(id, groupId)
|
|
23881
|
+
);
|
|
23882
|
+
ipc.handle(
|
|
23883
|
+
Channels.TAB_GROUP_REMOVE_TAB,
|
|
23884
|
+
(_e, id) => tabManager.removeTabFromGroup(id)
|
|
23885
|
+
);
|
|
23886
|
+
ipc.handle(
|
|
23887
|
+
Channels.TAB_GROUP_TOGGLE_COLLAPSED,
|
|
23888
|
+
(_e, groupId) => tabManager.toggleGroupCollapsed(groupId)
|
|
23889
|
+
);
|
|
23890
|
+
ipc.handle(
|
|
23891
|
+
Channels.TAB_GROUP_SET_COLOR,
|
|
23892
|
+
(_e, groupId, color) => tabManager.setGroupColor(groupId, color)
|
|
23893
|
+
);
|
|
23894
|
+
ipc.handle(
|
|
23895
|
+
Channels.TAB_TOGGLE_MUTE,
|
|
23896
|
+
(_e, id) => tabManager.toggleMuted(id)
|
|
23897
|
+
);
|
|
23898
|
+
ipc.handle(Channels.TAB_PRINT, (_e, id) => tabManager.printTab(id));
|
|
23899
|
+
ipc.handle(
|
|
23900
|
+
Channels.TAB_PRINT_TO_PDF,
|
|
23901
|
+
(_e, id) => tabManager.saveTabAsPdf(id)
|
|
23902
|
+
);
|
|
23903
|
+
ipc.handle(Channels.TAB_STATE_GET, () => ({
|
|
23904
|
+
tabs: tabManager.getAllStates(),
|
|
23905
|
+
activeId: tabManager.getActiveTabId() || ""
|
|
23906
|
+
}));
|
|
23907
|
+
ipc.on(
|
|
23908
|
+
Channels.TAB_CONTEXT_MENU,
|
|
23909
|
+
(_e, id) => showTabContextMenu(state2, id)
|
|
23910
|
+
);
|
|
23911
|
+
ipc.on(
|
|
23912
|
+
Channels.TAB_GROUP_CONTEXT_MENU,
|
|
23913
|
+
(_e, groupId) => showGroupContextMenu(state2, groupId)
|
|
23914
|
+
);
|
|
23915
|
+
ipc.handle(Channels.OPEN_NEW_WINDOW, () => createSecondaryWindow());
|
|
23916
|
+
ipc.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
23917
|
+
const { createPrivateWindow: createPrivateWindow2 } = require("../private/window");
|
|
23918
|
+
createPrivateWindow2();
|
|
23919
|
+
});
|
|
23920
|
+
ipc.handle(Channels.IS_PRIVATE_MODE, () => false);
|
|
23921
|
+
ipc.handle(Channels.WINDOW_MINIMIZE, () => state2.window.minimize());
|
|
23922
|
+
ipc.handle(Channels.WINDOW_MAXIMIZE, () => {
|
|
23923
|
+
if (state2.window.isMaximized()) state2.window.unmaximize();
|
|
23924
|
+
else state2.window.maximize();
|
|
23925
|
+
});
|
|
23926
|
+
ipc.handle(Channels.WINDOW_CLOSE, () => state2.window.close());
|
|
23927
|
+
ipc.handle(Channels.SETTINGS_VISIBILITY, () => false);
|
|
23928
|
+
ipc.handle(Channels.FOCUS_MODE_TOGGLE, () => false);
|
|
23929
|
+
ipc.handle(Channels.SIDEBAR_TOGGLE, () => ({ open: false, width: 0 }));
|
|
23930
|
+
ipc.handle(Channels.DEVTOOLS_PANEL_TOGGLE, () => ({ open: false }));
|
|
23931
|
+
ipc.handle(
|
|
23932
|
+
Channels.FIND_IN_PAGE_START,
|
|
23933
|
+
(_e, text, options) => {
|
|
23934
|
+
const tab = tabManager.getActiveTab();
|
|
23935
|
+
if (!tab) return null;
|
|
23936
|
+
const wc = tab.view.webContents;
|
|
23937
|
+
if (wc.isDestroyed()) return null;
|
|
23938
|
+
wireFindEvents(wc);
|
|
23939
|
+
return wc.findInPage(text, {
|
|
23940
|
+
forward: options?.forward ?? true,
|
|
23941
|
+
findNext: options?.findNext ?? false
|
|
23942
|
+
});
|
|
23943
|
+
}
|
|
23944
|
+
);
|
|
23945
|
+
ipc.handle(Channels.FIND_IN_PAGE_NEXT, (_e, forward) => {
|
|
23946
|
+
const tab = tabManager.getActiveTab();
|
|
23947
|
+
if (!tab) return null;
|
|
23948
|
+
const wc = tab.view.webContents;
|
|
23949
|
+
if (wc.isDestroyed()) return null;
|
|
23950
|
+
wireFindEvents(wc);
|
|
23951
|
+
return wc.findInPage("", { forward: forward ?? true, findNext: true });
|
|
23952
|
+
});
|
|
23953
|
+
ipc.handle(
|
|
23954
|
+
Channels.FIND_IN_PAGE_STOP,
|
|
23955
|
+
(_e, action) => {
|
|
23956
|
+
const tab = tabManager.getActiveTab();
|
|
23957
|
+
if (!tab) return;
|
|
23958
|
+
const wc = tab.view.webContents;
|
|
23959
|
+
if (wc.isDestroyed()) return;
|
|
23960
|
+
wc.stopFindInPage(action ?? "clearSelection");
|
|
23961
|
+
}
|
|
23962
|
+
);
|
|
23963
|
+
}
|
|
23964
|
+
function createSecondaryWindow() {
|
|
23965
|
+
const win = new electron.BaseWindow({
|
|
23966
|
+
width: 1280,
|
|
23967
|
+
height: 800,
|
|
23968
|
+
minWidth: 800,
|
|
23969
|
+
minHeight: 600,
|
|
23970
|
+
frame: false,
|
|
23971
|
+
show: false,
|
|
23972
|
+
backgroundColor: "#1a1a1e",
|
|
23973
|
+
title: "Vessel"
|
|
23974
|
+
});
|
|
23975
|
+
const chromeView = new electron.WebContentsView({
|
|
23976
|
+
webPreferences: {
|
|
23977
|
+
preload: path.join(__dirname, "../preload/index.js"),
|
|
23978
|
+
sandbox: true,
|
|
23979
|
+
contextIsolation: true,
|
|
23980
|
+
nodeIntegration: false
|
|
23981
|
+
}
|
|
23982
|
+
});
|
|
23983
|
+
chromeView.setBackgroundColor("#00000000");
|
|
23984
|
+
win.contentView.addChildView(chromeView);
|
|
23985
|
+
const tabManager = new TabManager(win, (tabs, activeId) => {
|
|
23986
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
23987
|
+
chromeView.webContents.send(Channels.TAB_STATE_UPDATE, tabs, activeId);
|
|
23988
|
+
}
|
|
23989
|
+
layoutSecondaryViews(state2);
|
|
23990
|
+
});
|
|
23991
|
+
const state2 = { window: win, chromeView, tabManager };
|
|
23992
|
+
installAdBlocking(tabManager);
|
|
23993
|
+
installDownloadHandler(chromeView);
|
|
23994
|
+
registerSecondaryIpcHandlers(state2);
|
|
23995
|
+
win.on("resize", () => layoutSecondaryViews(state2));
|
|
23996
|
+
win.on("show", () => layoutSecondaryViews(state2));
|
|
23997
|
+
win.on("closed", () => {
|
|
23998
|
+
secondaryWindows.delete(state2);
|
|
23999
|
+
unregisterAdBlockingTabManager(tabManager);
|
|
24000
|
+
unregisterDownloadHandler(chromeView);
|
|
24001
|
+
tabManager.destroyAllTabs();
|
|
24002
|
+
});
|
|
24003
|
+
secondaryWindows.add(state2);
|
|
24004
|
+
chromeView.webContents.once("dom-ready", () => {
|
|
24005
|
+
tabManager.createTab(loadSettings().defaultUrl);
|
|
24006
|
+
layoutSecondaryViews(state2);
|
|
24007
|
+
});
|
|
24008
|
+
loadSecondaryRenderer(chromeView);
|
|
24009
|
+
win.show();
|
|
24010
|
+
return state2;
|
|
24011
|
+
}
|
|
24012
|
+
let activeChatProvider = null;
|
|
24013
|
+
const logger$4 = createLogger("IPC");
|
|
24014
|
+
const VALID_APPROVAL_MODES = ["auto", "confirm-dangerous", "manual"];
|
|
24015
|
+
function registerIpcHandlers(windowState, runtime2) {
|
|
24016
|
+
const { tabManager, chromeView, sidebarView, devtoolsPanelView, mainWindow } = windowState;
|
|
24017
|
+
electron.ipcMain.handle(Channels.OPEN_PRIVATE_WINDOW, () => {
|
|
24018
|
+
createPrivateWindow();
|
|
24019
|
+
});
|
|
24020
|
+
electron.ipcMain.handle(Channels.OPEN_NEW_WINDOW, () => {
|
|
24021
|
+
createSecondaryWindow();
|
|
24022
|
+
});
|
|
24023
|
+
electron.ipcMain.handle(Channels.IS_PRIVATE_MODE, () => false);
|
|
24024
|
+
let sidebarResizeRecoveryTimer = null;
|
|
24025
|
+
let sidebarResizeActive = false;
|
|
24026
|
+
let runtimeUpdateTimer = null;
|
|
24027
|
+
let pendingRuntimeState = null;
|
|
24028
|
+
const premiumApiOrigin = process.env.VESSEL_PREMIUM_API ? new URL(process.env.VESSEL_PREMIUM_API).origin : "https://vesselpremium.quantaintellect.com";
|
|
24029
|
+
const clearSidebarResizeRecoveryTimer = () => {
|
|
24030
|
+
if (!sidebarResizeRecoveryTimer) return;
|
|
24031
|
+
clearTimeout(sidebarResizeRecoveryTimer);
|
|
24032
|
+
sidebarResizeRecoveryTimer = null;
|
|
24033
|
+
};
|
|
24034
|
+
const restoreSidebarLayoutAfterResize = () => {
|
|
24035
|
+
clearSidebarResizeRecoveryTimer();
|
|
24036
|
+
if (!sidebarResizeActive) return;
|
|
24037
|
+
sidebarResizeActive = false;
|
|
24038
|
+
layoutViews(windowState);
|
|
24039
|
+
};
|
|
24040
|
+
const scheduleSidebarResizeRecovery = () => {
|
|
24041
|
+
clearSidebarResizeRecoveryTimer();
|
|
24042
|
+
sidebarResizeRecoveryTimer = setTimeout(() => {
|
|
24043
|
+
restoreSidebarLayoutAfterResize();
|
|
24044
|
+
}, 1200);
|
|
24045
|
+
};
|
|
24046
|
+
const flushRuntimeUpdate = () => {
|
|
24047
|
+
runtimeUpdateTimer = null;
|
|
24048
|
+
if (!pendingRuntimeState) return;
|
|
24049
|
+
if (!chromeView.webContents.isDestroyed()) {
|
|
24050
|
+
chromeView.webContents.send(
|
|
24051
|
+
Channels.AGENT_RUNTIME_UPDATE,
|
|
24052
|
+
pendingRuntimeState
|
|
24053
|
+
);
|
|
24054
|
+
}
|
|
24055
|
+
if (!sidebarView.webContents.isDestroyed()) {
|
|
24056
|
+
sidebarView.webContents.send(
|
|
24057
|
+
Channels.AGENT_RUNTIME_UPDATE,
|
|
24058
|
+
pendingRuntimeState
|
|
24059
|
+
);
|
|
24060
|
+
}
|
|
24061
|
+
pendingRuntimeState = null;
|
|
24062
|
+
};
|
|
24063
|
+
const scheduleRuntimeUpdate = (state2) => {
|
|
24064
|
+
pendingRuntimeState = state2;
|
|
24065
|
+
if (runtimeUpdateTimer) return;
|
|
24066
|
+
runtimeUpdateTimer = setTimeout(() => {
|
|
24067
|
+
flushRuntimeUpdate();
|
|
24068
|
+
}, 32);
|
|
24069
|
+
};
|
|
24070
|
+
electron.app.on("before-quit", () => {
|
|
24071
|
+
if (runtimeUpdateTimer) {
|
|
24072
|
+
clearTimeout(runtimeUpdateTimer);
|
|
24073
|
+
runtimeUpdateTimer = null;
|
|
24074
|
+
}
|
|
24075
|
+
flushRuntimeUpdate();
|
|
24076
|
+
});
|
|
24077
|
+
const sendToRendererViews = (channel, ...args) => {
|
|
24078
|
+
chromeView.webContents.send(channel, ...args);
|
|
24079
|
+
sidebarView.webContents.send(channel, ...args);
|
|
24080
|
+
devtoolsPanelView.webContents.send(channel, ...args);
|
|
24081
|
+
};
|
|
24082
|
+
const watchPremiumCheckoutTab = (tabId) => {
|
|
24083
|
+
const tab = tabManager.getTab(tabId);
|
|
24084
|
+
const wc = tab?.view.webContents;
|
|
24085
|
+
if (!wc) return;
|
|
24086
|
+
let completed = false;
|
|
24087
|
+
const cleanup = () => {
|
|
24088
|
+
wc.removeListener("did-navigate", onNavigate);
|
|
24089
|
+
wc.removeListener("did-navigate-in-page", onNavigateInPage);
|
|
24090
|
+
wc.removeListener("destroyed", cleanup);
|
|
24091
|
+
};
|
|
24092
|
+
const handleUrl = async (rawUrl) => {
|
|
24093
|
+
if (completed) return;
|
|
24094
|
+
let parsed;
|
|
24095
|
+
try {
|
|
24096
|
+
parsed = new URL(rawUrl);
|
|
24097
|
+
} catch (err) {
|
|
24098
|
+
logger$4.warn("Failed to parse premium checkout URL while watching checkout tab:", err);
|
|
24099
|
+
return;
|
|
24100
|
+
}
|
|
24101
|
+
if (parsed.origin !== premiumApiOrigin) return;
|
|
24102
|
+
if (parsed.pathname === "/canceled") {
|
|
24103
|
+
completed = true;
|
|
24104
|
+
trackPremiumFunnel("checkout_canceled");
|
|
24105
|
+
cleanup();
|
|
24106
|
+
return;
|
|
24107
|
+
}
|
|
24108
|
+
if (parsed.pathname !== "/success") return;
|
|
22496
24109
|
completed = true;
|
|
22497
24110
|
trackPremiumFunnel("checkout_success_seen");
|
|
22498
24111
|
const sessionId = parsed.searchParams.get("session_id")?.trim();
|
|
@@ -22577,23 +24190,226 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22577
24190
|
assertString(url, "url");
|
|
22578
24191
|
return tabManager.navigateTab(id, url, postBody);
|
|
22579
24192
|
}
|
|
22580
|
-
);
|
|
22581
|
-
electron.ipcMain.handle(Channels.TAB_BACK, (_, id) => {
|
|
22582
|
-
tabManager.goBack(id);
|
|
22583
|
-
});
|
|
22584
|
-
electron.ipcMain.handle(Channels.TAB_FORWARD, (_, id) => {
|
|
22585
|
-
tabManager.goForward(id);
|
|
22586
|
-
});
|
|
22587
|
-
electron.ipcMain.handle(Channels.TAB_RELOAD, (_, id) => {
|
|
22588
|
-
tabManager.reloadTab(id);
|
|
24193
|
+
);
|
|
24194
|
+
electron.ipcMain.handle(Channels.TAB_BACK, (_, id) => {
|
|
24195
|
+
tabManager.goBack(id);
|
|
24196
|
+
});
|
|
24197
|
+
electron.ipcMain.handle(Channels.TAB_FORWARD, (_, id) => {
|
|
24198
|
+
tabManager.goForward(id);
|
|
24199
|
+
});
|
|
24200
|
+
electron.ipcMain.handle(Channels.TAB_RELOAD, (_, id) => {
|
|
24201
|
+
tabManager.reloadTab(id);
|
|
24202
|
+
});
|
|
24203
|
+
electron.ipcMain.handle(Channels.TAB_TOGGLE_AD_BLOCK, (_, id) => {
|
|
24204
|
+
assertString(id, "id");
|
|
24205
|
+
const tab = tabManager.getTab(id);
|
|
24206
|
+
if (!tab) return null;
|
|
24207
|
+
const newState = !tab.state.adBlockingEnabled;
|
|
24208
|
+
tab.setAdBlockingEnabled(newState);
|
|
24209
|
+
return newState;
|
|
24210
|
+
});
|
|
24211
|
+
electron.ipcMain.handle(Channels.TAB_ZOOM_IN, (_, id) => {
|
|
24212
|
+
assertString(id, "id");
|
|
24213
|
+
tabManager.zoomIn(id);
|
|
24214
|
+
});
|
|
24215
|
+
electron.ipcMain.handle(Channels.TAB_ZOOM_OUT, (_, id) => {
|
|
24216
|
+
assertString(id, "id");
|
|
24217
|
+
tabManager.zoomOut(id);
|
|
24218
|
+
});
|
|
24219
|
+
electron.ipcMain.handle(Channels.TAB_ZOOM_RESET, (_, id) => {
|
|
24220
|
+
assertString(id, "id");
|
|
24221
|
+
tabManager.zoomReset(id);
|
|
24222
|
+
});
|
|
24223
|
+
electron.ipcMain.handle(Channels.TAB_REOPEN_CLOSED, () => {
|
|
24224
|
+
const id = tabManager.reopenClosedTab();
|
|
24225
|
+
if (id) layoutViews(windowState);
|
|
24226
|
+
return id;
|
|
24227
|
+
});
|
|
24228
|
+
electron.ipcMain.handle(Channels.TAB_DUPLICATE, (_, id) => {
|
|
24229
|
+
assertString(id, "id");
|
|
24230
|
+
const newId = tabManager.duplicateTab(id);
|
|
24231
|
+
if (newId) layoutViews(windowState);
|
|
24232
|
+
return newId;
|
|
24233
|
+
});
|
|
24234
|
+
electron.ipcMain.handle(Channels.TAB_PIN, (_, id) => {
|
|
24235
|
+
assertString(id, "id");
|
|
24236
|
+
tabManager.pinTab(id);
|
|
24237
|
+
});
|
|
24238
|
+
electron.ipcMain.handle(Channels.TAB_UNPIN, (_, id) => {
|
|
24239
|
+
assertString(id, "id");
|
|
24240
|
+
tabManager.unpinTab(id);
|
|
24241
|
+
});
|
|
24242
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_CREATE, (_, id) => {
|
|
24243
|
+
assertString(id, "id");
|
|
24244
|
+
return tabManager.createGroupFromTab(id);
|
|
24245
|
+
});
|
|
24246
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_ADD_TAB, (_, id, groupId) => {
|
|
24247
|
+
assertString(id, "id");
|
|
24248
|
+
assertString(groupId, "groupId");
|
|
24249
|
+
tabManager.assignTabToGroup(id, groupId);
|
|
24250
|
+
});
|
|
24251
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_REMOVE_TAB, (_, id) => {
|
|
24252
|
+
assertString(id, "id");
|
|
24253
|
+
tabManager.removeTabFromGroup(id);
|
|
24254
|
+
});
|
|
24255
|
+
electron.ipcMain.handle(Channels.TAB_GROUP_TOGGLE_COLLAPSED, (_, groupId) => {
|
|
24256
|
+
assertString(groupId, "groupId");
|
|
24257
|
+
return tabManager.toggleGroupCollapsed(groupId);
|
|
24258
|
+
});
|
|
24259
|
+
electron.ipcMain.handle(
|
|
24260
|
+
Channels.TAB_GROUP_SET_COLOR,
|
|
24261
|
+
(_, groupId, color) => {
|
|
24262
|
+
assertString(groupId, "groupId");
|
|
24263
|
+
assertString(color, "color");
|
|
24264
|
+
tabManager.setGroupColor(groupId, color);
|
|
24265
|
+
}
|
|
24266
|
+
);
|
|
24267
|
+
electron.ipcMain.handle(Channels.TAB_TOGGLE_MUTE, (_, id) => {
|
|
24268
|
+
assertString(id, "id");
|
|
24269
|
+
return tabManager.toggleMuted(id);
|
|
24270
|
+
});
|
|
24271
|
+
electron.ipcMain.handle(Channels.TAB_PRINT, (_, id) => {
|
|
24272
|
+
assertString(id, "id");
|
|
24273
|
+
tabManager.printTab(id);
|
|
24274
|
+
});
|
|
24275
|
+
electron.ipcMain.handle(Channels.TAB_PRINT_TO_PDF, (_, id) => {
|
|
24276
|
+
assertString(id, "id");
|
|
24277
|
+
return tabManager.saveTabAsPdf(id);
|
|
24278
|
+
});
|
|
24279
|
+
electron.ipcMain.on(Channels.TAB_CONTEXT_MENU, (_event, id) => {
|
|
24280
|
+
assertString(id, "id");
|
|
24281
|
+
const tab = tabManager.getTab(id);
|
|
24282
|
+
const isPinned = tab?.state.isPinned ?? false;
|
|
24283
|
+
const groupId = tab?.state.groupId;
|
|
24284
|
+
const isMuted = tab?.state.isMuted ?? false;
|
|
24285
|
+
const groups = tabManager.getAllStates().filter((state2) => state2.groupId && state2.groupId !== groupId).reduce(
|
|
24286
|
+
(map, state2) => map.set(state2.groupId, {
|
|
24287
|
+
id: state2.groupId,
|
|
24288
|
+
name: state2.groupName || "Group"
|
|
24289
|
+
}),
|
|
24290
|
+
/* @__PURE__ */ new Map()
|
|
24291
|
+
);
|
|
24292
|
+
const menu = new electron.Menu();
|
|
24293
|
+
menu.append(
|
|
24294
|
+
new electron.MenuItem({
|
|
24295
|
+
label: isPinned ? "Unpin Tab" : "Pin Tab",
|
|
24296
|
+
click: () => {
|
|
24297
|
+
if (isPinned) {
|
|
24298
|
+
tabManager.unpinTab(id);
|
|
24299
|
+
} else {
|
|
24300
|
+
tabManager.pinTab(id);
|
|
24301
|
+
}
|
|
24302
|
+
}
|
|
24303
|
+
})
|
|
24304
|
+
);
|
|
24305
|
+
menu.append(
|
|
24306
|
+
new electron.MenuItem({
|
|
24307
|
+
label: "Duplicate Tab",
|
|
24308
|
+
click: () => {
|
|
24309
|
+
const newId = tabManager.duplicateTab(id);
|
|
24310
|
+
if (newId) layoutViews(windowState);
|
|
24311
|
+
}
|
|
24312
|
+
})
|
|
24313
|
+
);
|
|
24314
|
+
menu.append(
|
|
24315
|
+
new electron.MenuItem({
|
|
24316
|
+
label: "Add to New Group",
|
|
24317
|
+
click: () => {
|
|
24318
|
+
tabManager.createGroupFromTab(id);
|
|
24319
|
+
}
|
|
24320
|
+
})
|
|
24321
|
+
);
|
|
24322
|
+
if (groups.size > 0) {
|
|
24323
|
+
menu.append(
|
|
24324
|
+
new electron.MenuItem({
|
|
24325
|
+
label: "Add to Group",
|
|
24326
|
+
submenu: [...groups.values()].map(
|
|
24327
|
+
(group) => new electron.MenuItem({
|
|
24328
|
+
label: group.name,
|
|
24329
|
+
click: () => tabManager.assignTabToGroup(id, group.id)
|
|
24330
|
+
})
|
|
24331
|
+
)
|
|
24332
|
+
})
|
|
24333
|
+
);
|
|
24334
|
+
}
|
|
24335
|
+
if (groupId) {
|
|
24336
|
+
menu.append(
|
|
24337
|
+
new electron.MenuItem({
|
|
24338
|
+
label: "Remove from Group",
|
|
24339
|
+
click: () => {
|
|
24340
|
+
tabManager.removeTabFromGroup(id);
|
|
24341
|
+
}
|
|
24342
|
+
})
|
|
24343
|
+
);
|
|
24344
|
+
}
|
|
24345
|
+
menu.append(
|
|
24346
|
+
new electron.MenuItem({
|
|
24347
|
+
label: isMuted ? "Unmute Tab" : "Mute Tab",
|
|
24348
|
+
click: () => {
|
|
24349
|
+
tabManager.toggleMuted(id);
|
|
24350
|
+
}
|
|
24351
|
+
})
|
|
24352
|
+
);
|
|
24353
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
24354
|
+
menu.append(
|
|
24355
|
+
new electron.MenuItem({
|
|
24356
|
+
label: "Print Page",
|
|
24357
|
+
click: () => {
|
|
24358
|
+
tabManager.printTab(id);
|
|
24359
|
+
}
|
|
24360
|
+
})
|
|
24361
|
+
);
|
|
24362
|
+
menu.append(
|
|
24363
|
+
new electron.MenuItem({
|
|
24364
|
+
label: "Save Page as PDF",
|
|
24365
|
+
click: () => {
|
|
24366
|
+
void tabManager.saveTabAsPdf(id).catch((error) => {
|
|
24367
|
+
logger$4.warn("Failed to save page as PDF:", error);
|
|
24368
|
+
});
|
|
24369
|
+
}
|
|
24370
|
+
})
|
|
24371
|
+
);
|
|
24372
|
+
if (!isPinned) {
|
|
24373
|
+
menu.append(new electron.MenuItem({ type: "separator" }));
|
|
24374
|
+
menu.append(
|
|
24375
|
+
new electron.MenuItem({
|
|
24376
|
+
label: "Close Tab",
|
|
24377
|
+
click: () => {
|
|
24378
|
+
tabManager.closeTab(id);
|
|
24379
|
+
layoutViews(windowState);
|
|
24380
|
+
}
|
|
24381
|
+
})
|
|
24382
|
+
);
|
|
24383
|
+
}
|
|
24384
|
+
menu.popup({ window: mainWindow });
|
|
22589
24385
|
});
|
|
22590
|
-
electron.ipcMain.
|
|
22591
|
-
assertString(
|
|
22592
|
-
const
|
|
22593
|
-
if (!
|
|
22594
|
-
const
|
|
22595
|
-
|
|
22596
|
-
|
|
24386
|
+
electron.ipcMain.on(Channels.TAB_GROUP_CONTEXT_MENU, (_event, groupId) => {
|
|
24387
|
+
assertString(groupId, "groupId");
|
|
24388
|
+
const firstTab = tabManager.getAllStates().find((tab) => tab.groupId === groupId);
|
|
24389
|
+
if (!firstTab) return;
|
|
24390
|
+
const menu = new electron.Menu();
|
|
24391
|
+
menu.append(
|
|
24392
|
+
new electron.MenuItem({
|
|
24393
|
+
label: firstTab.groupCollapsed ? "Expand Group" : "Collapse Group",
|
|
24394
|
+
click: () => {
|
|
24395
|
+
tabManager.toggleGroupCollapsed(groupId);
|
|
24396
|
+
}
|
|
24397
|
+
})
|
|
24398
|
+
);
|
|
24399
|
+
menu.append(
|
|
24400
|
+
new electron.MenuItem({
|
|
24401
|
+
label: "Group Color",
|
|
24402
|
+
submenu: TAB_GROUP_COLORS.map(
|
|
24403
|
+
(color) => new electron.MenuItem({
|
|
24404
|
+
label: TAB_GROUP_COLOR_LABELS[color],
|
|
24405
|
+
type: "radio",
|
|
24406
|
+
checked: firstTab.groupColor === color,
|
|
24407
|
+
click: () => tabManager.setGroupColor(groupId, color)
|
|
24408
|
+
})
|
|
24409
|
+
)
|
|
24410
|
+
})
|
|
24411
|
+
);
|
|
24412
|
+
menu.popup({ window: mainWindow });
|
|
22597
24413
|
});
|
|
22598
24414
|
electron.ipcMain.handle(Channels.TAB_STATE_GET, () => ({
|
|
22599
24415
|
tabs: tabManager.getAllStates(),
|
|
@@ -22859,6 +24675,41 @@ function registerIpcHandlers(windowState, runtime2) {
|
|
|
22859
24675
|
trackBookmarkAction("remove");
|
|
22860
24676
|
return removeBookmark(id);
|
|
22861
24677
|
});
|
|
24678
|
+
electron.ipcMain.handle(
|
|
24679
|
+
Channels.BOOKMARKS_EXPORT_HTML,
|
|
24680
|
+
async (_, options) => {
|
|
24681
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
24682
|
+
title: "Export Bookmarks",
|
|
24683
|
+
defaultPath: "vessel-bookmarks.html",
|
|
24684
|
+
filters: [{ name: "HTML Bookmarks", extensions: ["html"] }]
|
|
24685
|
+
});
|
|
24686
|
+
if (canceled || !filePath) return null;
|
|
24687
|
+
const content = exportBookmarksHtml({
|
|
24688
|
+
includeNotes: options?.includeNotes ?? false
|
|
24689
|
+
});
|
|
24690
|
+
await fs.promises.writeFile(filePath, content, "utf-8");
|
|
24691
|
+
trackBookmarkAction("export");
|
|
24692
|
+
return {
|
|
24693
|
+
filePath,
|
|
24694
|
+
count: getState().bookmarks.length
|
|
24695
|
+
};
|
|
24696
|
+
}
|
|
24697
|
+
);
|
|
24698
|
+
electron.ipcMain.handle(Channels.BOOKMARKS_EXPORT_JSON, async () => {
|
|
24699
|
+
const { canceled, filePath } = await electron.dialog.showSaveDialog({
|
|
24700
|
+
title: "Export Vessel Bookmark Archive",
|
|
24701
|
+
defaultPath: "vessel-bookmarks.json",
|
|
24702
|
+
filters: [{ name: "Vessel Bookmark Archive", extensions: ["json"] }]
|
|
24703
|
+
});
|
|
24704
|
+
if (canceled || !filePath) return null;
|
|
24705
|
+
const content = exportBookmarksJson();
|
|
24706
|
+
await fs.promises.writeFile(filePath, content, "utf-8");
|
|
24707
|
+
trackBookmarkAction("export");
|
|
24708
|
+
return {
|
|
24709
|
+
filePath,
|
|
24710
|
+
count: getState().bookmarks.length
|
|
24711
|
+
};
|
|
24712
|
+
});
|
|
22862
24713
|
electron.ipcMain.handle(Channels.FOLDER_REMOVE, (_, id, deleteContents) => {
|
|
22863
24714
|
trackBookmarkAction("folder_remove");
|
|
22864
24715
|
return removeFolder(id, deleteContents ?? false);
|
|
@@ -24102,157 +25953,6 @@ ${progress}
|
|
|
24102
25953
|
});
|
|
24103
25954
|
}
|
|
24104
25955
|
}
|
|
24105
|
-
const BLOCKED_RESOURCE_TYPES = /* @__PURE__ */ new Set([
|
|
24106
|
-
"script",
|
|
24107
|
-
"image",
|
|
24108
|
-
"xhr",
|
|
24109
|
-
"fetch",
|
|
24110
|
-
"subFrame",
|
|
24111
|
-
"media",
|
|
24112
|
-
"ping",
|
|
24113
|
-
"webSocket"
|
|
24114
|
-
]);
|
|
24115
|
-
const BLOCKED_HOST_SUFFIXES = [
|
|
24116
|
-
"doubleclick.net",
|
|
24117
|
-
"googlesyndication.com",
|
|
24118
|
-
"googleadservices.com",
|
|
24119
|
-
"adservice.google.com",
|
|
24120
|
-
"adnxs.com",
|
|
24121
|
-
"adsrvr.org",
|
|
24122
|
-
"taboola.com",
|
|
24123
|
-
"outbrain.com",
|
|
24124
|
-
"criteo.com",
|
|
24125
|
-
"criteo.net",
|
|
24126
|
-
"pubmatic.com",
|
|
24127
|
-
"rubiconproject.com",
|
|
24128
|
-
"openx.net",
|
|
24129
|
-
"casalemedia.com",
|
|
24130
|
-
"advertising.com",
|
|
24131
|
-
"amazon-adsystem.com",
|
|
24132
|
-
"adsymptotic.com",
|
|
24133
|
-
"moatads.com",
|
|
24134
|
-
"quantserve.com",
|
|
24135
|
-
"scorecardresearch.com"
|
|
24136
|
-
];
|
|
24137
|
-
const THIRD_PARTY_PATH_PATTERNS = [
|
|
24138
|
-
/\/ads?[/?._-]/i,
|
|
24139
|
-
/\/adservice/i,
|
|
24140
|
-
/\/advert/i,
|
|
24141
|
-
/\/prebid/i,
|
|
24142
|
-
/\/banner/i,
|
|
24143
|
-
/\/sponsor/i,
|
|
24144
|
-
/\/promotions?\//i,
|
|
24145
|
-
/\/trk\//i,
|
|
24146
|
-
/\/track(ing)?\//i,
|
|
24147
|
-
/\/beacon/i,
|
|
24148
|
-
/\/pixel/i
|
|
24149
|
-
];
|
|
24150
|
-
let installed = false;
|
|
24151
|
-
function normalizeHostname(value) {
|
|
24152
|
-
return value.trim().toLowerCase().replace(/\.$/, "");
|
|
24153
|
-
}
|
|
24154
|
-
function hostnameMatches(hostname, suffix) {
|
|
24155
|
-
return hostname === suffix || hostname.endsWith(`.${suffix}`);
|
|
24156
|
-
}
|
|
24157
|
-
function parseHostname(url) {
|
|
24158
|
-
try {
|
|
24159
|
-
const parsed = new URL(url);
|
|
24160
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
24161
|
-
return null;
|
|
24162
|
-
}
|
|
24163
|
-
return normalizeHostname(parsed.hostname);
|
|
24164
|
-
} catch {
|
|
24165
|
-
return null;
|
|
24166
|
-
}
|
|
24167
|
-
}
|
|
24168
|
-
function isThirdParty(url, firstPartyHost) {
|
|
24169
|
-
if (!firstPartyHost) return true;
|
|
24170
|
-
const target = normalizeHostname(url.hostname);
|
|
24171
|
-
return !(target === firstPartyHost || target.endsWith(`.${firstPartyHost}`));
|
|
24172
|
-
}
|
|
24173
|
-
function shouldBlockRequest(details) {
|
|
24174
|
-
if (!BLOCKED_RESOURCE_TYPES.has(details.resourceType)) return false;
|
|
24175
|
-
let parsed;
|
|
24176
|
-
try {
|
|
24177
|
-
parsed = new URL(details.url);
|
|
24178
|
-
} catch {
|
|
24179
|
-
return false;
|
|
24180
|
-
}
|
|
24181
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
24182
|
-
return false;
|
|
24183
|
-
}
|
|
24184
|
-
const hostname = normalizeHostname(parsed.hostname);
|
|
24185
|
-
if (BLOCKED_HOST_SUFFIXES.some((suffix) => hostnameMatches(hostname, suffix))) {
|
|
24186
|
-
return true;
|
|
24187
|
-
}
|
|
24188
|
-
const firstPartyHost = parseHostname(details.referrer) || parseHostname(details.initiator || "");
|
|
24189
|
-
if (!isThirdParty(parsed, firstPartyHost)) return false;
|
|
24190
|
-
const candidate = `${hostname}${parsed.pathname}${parsed.search}`;
|
|
24191
|
-
return THIRD_PARTY_PATH_PATTERNS.some((pattern) => pattern.test(candidate));
|
|
24192
|
-
}
|
|
24193
|
-
function installAdBlocking(tabManager) {
|
|
24194
|
-
if (installed) return;
|
|
24195
|
-
installed = true;
|
|
24196
|
-
electron.session.defaultSession.webRequest.onBeforeRequest((details, callback) => {
|
|
24197
|
-
const webContentsId = typeof details.webContentsId === "number" ? details.webContentsId : null;
|
|
24198
|
-
if (webContentsId == null) {
|
|
24199
|
-
callback({});
|
|
24200
|
-
return;
|
|
24201
|
-
}
|
|
24202
|
-
if (!tabManager.isAdBlockingEnabledForWebContents(webContentsId)) {
|
|
24203
|
-
callback({});
|
|
24204
|
-
return;
|
|
24205
|
-
}
|
|
24206
|
-
callback({ cancel: shouldBlockRequest(details) });
|
|
24207
|
-
});
|
|
24208
|
-
}
|
|
24209
|
-
function resolveDownloadPath(downloadDir, filename) {
|
|
24210
|
-
fs$1.mkdirSync(downloadDir, { recursive: true });
|
|
24211
|
-
const parsed = path.parse(filename);
|
|
24212
|
-
let attempt = 0;
|
|
24213
|
-
while (true) {
|
|
24214
|
-
const candidateName = attempt === 0 ? filename : `${parsed.name} (${attempt})${parsed.ext}`;
|
|
24215
|
-
const candidatePath = path.join(downloadDir, candidateName);
|
|
24216
|
-
if (!fs$1.existsSync(candidatePath)) {
|
|
24217
|
-
return candidatePath;
|
|
24218
|
-
}
|
|
24219
|
-
attempt += 1;
|
|
24220
|
-
}
|
|
24221
|
-
}
|
|
24222
|
-
function installDownloadHandler(chromeView) {
|
|
24223
|
-
electron.session.defaultSession.on("will-download", (_event, item) => {
|
|
24224
|
-
const settings2 = loadSettings();
|
|
24225
|
-
const downloadDir = settings2.downloadPath.trim() || electron.app.getPath("downloads");
|
|
24226
|
-
const filename = item.getFilename();
|
|
24227
|
-
const savePath = resolveDownloadPath(downloadDir, filename);
|
|
24228
|
-
item.setSavePath(savePath);
|
|
24229
|
-
const info = {
|
|
24230
|
-
filename,
|
|
24231
|
-
savePath,
|
|
24232
|
-
totalBytes: item.getTotalBytes(),
|
|
24233
|
-
receivedBytes: 0,
|
|
24234
|
-
state: "progressing"
|
|
24235
|
-
};
|
|
24236
|
-
if (!chromeView.webContents.isDestroyed()) {
|
|
24237
|
-
chromeView.webContents.send(Channels.DOWNLOAD_STARTED, info);
|
|
24238
|
-
}
|
|
24239
|
-
item.on("updated", (_event2, state2) => {
|
|
24240
|
-
info.receivedBytes = item.getReceivedBytes();
|
|
24241
|
-
info.totalBytes = item.getTotalBytes();
|
|
24242
|
-
info.state = state2 === "progressing" ? "progressing" : "interrupted";
|
|
24243
|
-
if (!chromeView.webContents.isDestroyed()) {
|
|
24244
|
-
chromeView.webContents.send(Channels.DOWNLOAD_PROGRESS, info);
|
|
24245
|
-
}
|
|
24246
|
-
});
|
|
24247
|
-
item.once("done", (_event2, state2) => {
|
|
24248
|
-
info.receivedBytes = item.getReceivedBytes();
|
|
24249
|
-
info.state = state2 === "completed" ? "completed" : "cancelled";
|
|
24250
|
-
if (!chromeView.webContents.isDestroyed()) {
|
|
24251
|
-
chromeView.webContents.send(Channels.DOWNLOAD_DONE, info);
|
|
24252
|
-
}
|
|
24253
|
-
});
|
|
24254
|
-
});
|
|
24255
|
-
}
|
|
24256
25956
|
const logger$2 = createLogger("Shortcuts");
|
|
24257
25957
|
function registerHighlightShortcut(mainWindow, tabManager) {
|
|
24258
25958
|
const register = () => {
|
|
@@ -24273,8 +25973,28 @@ function registerHighlightShortcut(mainWindow, tabManager) {
|
|
|
24273
25973
|
mainWindow.removeListener("focus", register);
|
|
24274
25974
|
};
|
|
24275
25975
|
}
|
|
24276
|
-
function setupAppMenu() {
|
|
25976
|
+
function setupAppMenu(handlers) {
|
|
24277
25977
|
const appMenu = electron.Menu.buildFromTemplate([
|
|
25978
|
+
{
|
|
25979
|
+
label: "File",
|
|
25980
|
+
submenu: [
|
|
25981
|
+
{
|
|
25982
|
+
label: "New Window",
|
|
25983
|
+
accelerator: "CommandOrControl+N",
|
|
25984
|
+
click: handlers.newWindow
|
|
25985
|
+
},
|
|
25986
|
+
{
|
|
25987
|
+
label: "Save Page As...",
|
|
25988
|
+
accelerator: "CommandOrControl+S",
|
|
25989
|
+
click: handlers.savePageAs
|
|
25990
|
+
},
|
|
25991
|
+
{
|
|
25992
|
+
label: "Reopen Closed Tab",
|
|
25993
|
+
accelerator: "CommandOrControl+Shift+T",
|
|
25994
|
+
click: handlers.reopenClosedTab
|
|
25995
|
+
}
|
|
25996
|
+
]
|
|
25997
|
+
},
|
|
24278
25998
|
{
|
|
24279
25999
|
label: "Edit",
|
|
24280
26000
|
submenu: [
|
|
@@ -24286,6 +26006,32 @@ function setupAppMenu() {
|
|
|
24286
26006
|
{ role: "paste" },
|
|
24287
26007
|
{ role: "selectAll" }
|
|
24288
26008
|
]
|
|
26009
|
+
},
|
|
26010
|
+
{
|
|
26011
|
+
label: "View",
|
|
26012
|
+
submenu: [
|
|
26013
|
+
{
|
|
26014
|
+
label: "Zoom In",
|
|
26015
|
+
accelerator: "CommandOrControl+Plus",
|
|
26016
|
+
click: handlers.zoomIn
|
|
26017
|
+
},
|
|
26018
|
+
{
|
|
26019
|
+
label: "Zoom Out",
|
|
26020
|
+
accelerator: "CommandOrControl+-",
|
|
26021
|
+
click: handlers.zoomOut
|
|
26022
|
+
},
|
|
26023
|
+
{
|
|
26024
|
+
label: "Actual Size",
|
|
26025
|
+
accelerator: "CommandOrControl+0",
|
|
26026
|
+
click: handlers.zoomReset
|
|
26027
|
+
},
|
|
26028
|
+
{ type: "separator" },
|
|
26029
|
+
{
|
|
26030
|
+
label: "View Page Source",
|
|
26031
|
+
accelerator: "CommandOrControl+U",
|
|
26032
|
+
click: handlers.viewPageSource
|
|
26033
|
+
}
|
|
26034
|
+
]
|
|
24289
26035
|
}
|
|
24290
26036
|
]);
|
|
24291
26037
|
electron.Menu.setApplicationMenu(appMenu);
|
|
@@ -24644,7 +26390,37 @@ async function bootstrap() {
|
|
|
24644
26390
|
});
|
|
24645
26391
|
registerIpcHandlers(windowState, runtime);
|
|
24646
26392
|
registerHighlightShortcut(windowState.mainWindow, tabManager);
|
|
24647
|
-
setupAppMenu(
|
|
26393
|
+
setupAppMenu({
|
|
26394
|
+
newWindow: () => {
|
|
26395
|
+
createSecondaryWindow();
|
|
26396
|
+
},
|
|
26397
|
+
reopenClosedTab: () => {
|
|
26398
|
+
const id = tabManager.reopenClosedTab();
|
|
26399
|
+
if (id) layoutViews(windowState);
|
|
26400
|
+
},
|
|
26401
|
+
zoomIn: () => {
|
|
26402
|
+
const id = tabManager.getActiveTabId();
|
|
26403
|
+
if (id) tabManager.zoomIn(id);
|
|
26404
|
+
},
|
|
26405
|
+
zoomOut: () => {
|
|
26406
|
+
const id = tabManager.getActiveTabId();
|
|
26407
|
+
if (id) tabManager.zoomOut(id);
|
|
26408
|
+
},
|
|
26409
|
+
zoomReset: () => {
|
|
26410
|
+
const id = tabManager.getActiveTabId();
|
|
26411
|
+
if (id) tabManager.zoomReset(id);
|
|
26412
|
+
},
|
|
26413
|
+
viewPageSource: () => {
|
|
26414
|
+
const activeTab = tabManager.getActiveTab();
|
|
26415
|
+
if (activeTab) activeTab.viewSource();
|
|
26416
|
+
},
|
|
26417
|
+
savePageAs: () => {
|
|
26418
|
+
const activeTabId = tabManager.getActiveTabId();
|
|
26419
|
+
if (activeTabId) {
|
|
26420
|
+
void tabManager.savePage(activeTabId);
|
|
26421
|
+
}
|
|
26422
|
+
}
|
|
26423
|
+
});
|
|
24648
26424
|
subscribe((state2) => {
|
|
24649
26425
|
chromeView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|
|
24650
26426
|
sidebarView.webContents.send(Channels.BOOKMARKS_UPDATE, state2);
|