@flrande/bak-extension 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/.bak-e2e-build-stamp +1 -1
- package/dist/background.global.js +267 -55
- package/dist/manifest.json +1 -1
- package/dist/popup.global.js +92 -2
- package/dist/popup.html +72 -1
- package/package.json +2 -2
- package/public/popup.html +72 -1
- package/src/background.ts +355 -134
- package/src/popup.ts +135 -11
- package/src/session-binding.ts +132 -74
|
@@ -1 +1 @@
|
|
|
1
|
-
2026-03-
|
|
1
|
+
2026-03-13T06:30:44.568Z
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
// package.json
|
|
63
63
|
var package_default = {
|
|
64
64
|
name: "@flrande/bak-extension",
|
|
65
|
-
version: "0.6.
|
|
65
|
+
version: "0.6.5",
|
|
66
66
|
type: "module",
|
|
67
67
|
scripts: {
|
|
68
68
|
build: "tsup src/background.ts src/content.ts src/popup.ts --format iife --out-dir dist --clean && node scripts/copy-assets.mjs",
|
|
@@ -548,7 +548,7 @@
|
|
|
548
548
|
const initialUrl = options.initialUrl ?? DEFAULT_SESSION_BINDING_URL;
|
|
549
549
|
const persisted = await this.storage.load(bindingId);
|
|
550
550
|
const created = !persisted;
|
|
551
|
-
let state = this.normalizeState(persisted, bindingId);
|
|
551
|
+
let state = this.normalizeState(persisted, bindingId, options.label);
|
|
552
552
|
const originalWindowId = state.windowId;
|
|
553
553
|
let window2 = state.windowId !== null ? await this.waitForWindow(state.windowId) : null;
|
|
554
554
|
let tabs = [];
|
|
@@ -681,7 +681,8 @@
|
|
|
681
681
|
const ensured = await this.ensureBinding({
|
|
682
682
|
bindingId,
|
|
683
683
|
focus: false,
|
|
684
|
-
initialUrl: hadBinding ? options.url ?? DEFAULT_SESSION_BINDING_URL : DEFAULT_SESSION_BINDING_URL
|
|
684
|
+
initialUrl: hadBinding ? options.url ?? DEFAULT_SESSION_BINDING_URL : DEFAULT_SESSION_BINDING_URL,
|
|
685
|
+
label: options.label
|
|
685
686
|
});
|
|
686
687
|
let state = { ...ensured.binding, tabIds: [...ensured.binding.tabIds], tabs: [...ensured.binding.tabs] };
|
|
687
688
|
if (state.windowId !== null && state.tabs.length === 0) {
|
|
@@ -715,7 +716,8 @@
|
|
|
715
716
|
const repaired = await this.ensureBinding({
|
|
716
717
|
bindingId,
|
|
717
718
|
focus: false,
|
|
718
|
-
initialUrl: desiredUrl
|
|
719
|
+
initialUrl: desiredUrl,
|
|
720
|
+
label: options.label
|
|
719
721
|
});
|
|
720
722
|
state = { ...repaired.binding };
|
|
721
723
|
reusablePrimaryTab = await this.resolveReusablePrimaryTab(state, true);
|
|
@@ -827,6 +829,43 @@
|
|
|
827
829
|
const refreshed = await this.ensureBinding({ bindingId, focus: false });
|
|
828
830
|
return { ok: true, binding: refreshed.binding };
|
|
829
831
|
}
|
|
832
|
+
async closeTab(bindingId, tabId) {
|
|
833
|
+
const ensured = await this.ensureBinding({ bindingId, focus: false });
|
|
834
|
+
const resolvedTabId = typeof tabId === "number" ? tabId : ensured.binding.activeTabId ?? ensured.binding.primaryTabId ?? ensured.binding.tabs[0]?.id;
|
|
835
|
+
if (typeof resolvedTabId !== "number" || !ensured.binding.tabIds.includes(resolvedTabId)) {
|
|
836
|
+
throw new Error(`Tab ${tabId ?? "active"} does not belong to binding ${bindingId}`);
|
|
837
|
+
}
|
|
838
|
+
await this.browser.closeTab(resolvedTabId);
|
|
839
|
+
const remainingTabIds = ensured.binding.tabIds.filter((candidate) => candidate !== resolvedTabId);
|
|
840
|
+
if (remainingTabIds.length === 0) {
|
|
841
|
+
await this.storage.delete(ensured.binding.id);
|
|
842
|
+
return {
|
|
843
|
+
binding: null,
|
|
844
|
+
closedTabId: resolvedTabId
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
const tabs = await this.readLooseTrackedTabs(remainingTabIds);
|
|
848
|
+
const nextPrimaryTabId = ensured.binding.primaryTabId === resolvedTabId ? tabs[0]?.id ?? null : ensured.binding.primaryTabId;
|
|
849
|
+
const nextActiveTabId = ensured.binding.activeTabId === resolvedTabId ? tabs.find((candidate) => candidate.active)?.id ?? nextPrimaryTabId ?? tabs[0]?.id ?? null : ensured.binding.activeTabId;
|
|
850
|
+
const nextState = {
|
|
851
|
+
id: ensured.binding.id,
|
|
852
|
+
label: ensured.binding.label,
|
|
853
|
+
color: ensured.binding.color,
|
|
854
|
+
windowId: tabs[0]?.windowId ?? ensured.binding.windowId,
|
|
855
|
+
groupId: tabs[0]?.groupId ?? ensured.binding.groupId,
|
|
856
|
+
tabIds: tabs.map((candidate) => candidate.id),
|
|
857
|
+
activeTabId: nextActiveTabId,
|
|
858
|
+
primaryTabId: nextPrimaryTabId
|
|
859
|
+
};
|
|
860
|
+
await this.storage.save(nextState);
|
|
861
|
+
return {
|
|
862
|
+
binding: {
|
|
863
|
+
...nextState,
|
|
864
|
+
tabs
|
|
865
|
+
},
|
|
866
|
+
closedTabId: resolvedTabId
|
|
867
|
+
};
|
|
868
|
+
}
|
|
830
869
|
async reset(options = {}) {
|
|
831
870
|
const bindingId = this.normalizeBindingId(options.bindingId);
|
|
832
871
|
await this.close(bindingId);
|
|
@@ -842,10 +881,11 @@
|
|
|
842
881
|
return { ok: true };
|
|
843
882
|
}
|
|
844
883
|
await this.storage.delete(bindingId);
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
await this.browser.
|
|
884
|
+
const trackedTabs = await this.readLooseTrackedTabs(this.collectCandidateTabIds(state));
|
|
885
|
+
for (const trackedTab of trackedTabs) {
|
|
886
|
+
try {
|
|
887
|
+
await this.browser.closeTab(trackedTab.id);
|
|
888
|
+
} catch {
|
|
849
889
|
}
|
|
850
890
|
}
|
|
851
891
|
return { ok: true };
|
|
@@ -896,10 +936,10 @@
|
|
|
896
936
|
}
|
|
897
937
|
return candidate;
|
|
898
938
|
}
|
|
899
|
-
normalizeState(state, bindingId) {
|
|
939
|
+
normalizeState(state, bindingId, label) {
|
|
900
940
|
return {
|
|
901
941
|
id: bindingId,
|
|
902
|
-
label: state?.label ?? DEFAULT_SESSION_BINDING_LABEL,
|
|
942
|
+
label: label?.trim() ? label.trim() : state?.label ?? DEFAULT_SESSION_BINDING_LABEL,
|
|
903
943
|
color: state?.color ?? DEFAULT_SESSION_BINDING_COLOR,
|
|
904
944
|
windowId: state?.windowId ?? null,
|
|
905
945
|
groupId: state?.groupId ?? null,
|
|
@@ -1066,6 +1106,10 @@
|
|
|
1066
1106
|
state.tabIds = recreatedTabs.map((tab) => tab.id);
|
|
1067
1107
|
state.primaryTabId = nextPrimaryTabId;
|
|
1068
1108
|
state.activeTabId = nextActiveTabId;
|
|
1109
|
+
await this.storage.save({
|
|
1110
|
+
...state,
|
|
1111
|
+
tabIds: [...state.tabIds]
|
|
1112
|
+
});
|
|
1069
1113
|
for (const bindingTab of ownership.bindingTabs) {
|
|
1070
1114
|
await this.browser.closeTab(bindingTab.id);
|
|
1071
1115
|
}
|
|
@@ -1276,10 +1320,14 @@
|
|
|
1276
1320
|
var ws = null;
|
|
1277
1321
|
var reconnectTimer = null;
|
|
1278
1322
|
var nextReconnectInMs = null;
|
|
1323
|
+
var nextReconnectAt = null;
|
|
1279
1324
|
var reconnectAttempt = 0;
|
|
1280
1325
|
var lastError = null;
|
|
1281
1326
|
var manualDisconnect = false;
|
|
1282
1327
|
var sessionBindingStateMutationQueue = Promise.resolve();
|
|
1328
|
+
var preserveHumanFocusDepth = 0;
|
|
1329
|
+
var lastBindingUpdateAt = null;
|
|
1330
|
+
var lastBindingUpdateReason = null;
|
|
1283
1331
|
async function getConfig() {
|
|
1284
1332
|
const stored = await chrome.storage.local.get([STORAGE_KEY_TOKEN, STORAGE_KEY_PORT, STORAGE_KEY_DEBUG_RICH_TEXT]);
|
|
1285
1333
|
return {
|
|
@@ -1316,12 +1364,25 @@
|
|
|
1316
1364
|
reconnectTimer = null;
|
|
1317
1365
|
}
|
|
1318
1366
|
nextReconnectInMs = null;
|
|
1367
|
+
nextReconnectAt = null;
|
|
1319
1368
|
}
|
|
1320
1369
|
function sendResponse(payload) {
|
|
1321
1370
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1322
1371
|
ws.send(JSON.stringify(payload));
|
|
1323
1372
|
}
|
|
1324
1373
|
}
|
|
1374
|
+
function sendEvent(event, data) {
|
|
1375
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
1376
|
+
ws.send(
|
|
1377
|
+
JSON.stringify({
|
|
1378
|
+
type: "event",
|
|
1379
|
+
event,
|
|
1380
|
+
data,
|
|
1381
|
+
ts: Date.now()
|
|
1382
|
+
})
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1325
1386
|
function toError(code, message, data) {
|
|
1326
1387
|
return { code, message, data };
|
|
1327
1388
|
}
|
|
@@ -1399,6 +1460,65 @@
|
|
|
1399
1460
|
async function listSessionBindingStates() {
|
|
1400
1461
|
return Object.values(await loadSessionBindingStateMap());
|
|
1401
1462
|
}
|
|
1463
|
+
function summarizeSessionBindings(states) {
|
|
1464
|
+
const items = states.map((state) => {
|
|
1465
|
+
const detached = state.windowId === null || state.tabIds.length === 0;
|
|
1466
|
+
return {
|
|
1467
|
+
id: state.id,
|
|
1468
|
+
label: state.label,
|
|
1469
|
+
tabCount: state.tabIds.length,
|
|
1470
|
+
activeTabId: state.activeTabId,
|
|
1471
|
+
windowId: state.windowId,
|
|
1472
|
+
groupId: state.groupId,
|
|
1473
|
+
detached
|
|
1474
|
+
};
|
|
1475
|
+
});
|
|
1476
|
+
return {
|
|
1477
|
+
count: items.length,
|
|
1478
|
+
attachedCount: items.filter((item) => !item.detached).length,
|
|
1479
|
+
detachedCount: items.filter((item) => item.detached).length,
|
|
1480
|
+
tabCount: items.reduce((sum, item) => sum + item.tabCount, 0),
|
|
1481
|
+
items
|
|
1482
|
+
};
|
|
1483
|
+
}
|
|
1484
|
+
async function buildPopupState() {
|
|
1485
|
+
const config = await getConfig();
|
|
1486
|
+
const sessionBindings = summarizeSessionBindings(await listSessionBindingStates());
|
|
1487
|
+
const reconnectRemainingMs = nextReconnectAt === null ? null : Math.max(0, nextReconnectAt - Date.now());
|
|
1488
|
+
let connectionState;
|
|
1489
|
+
if (!config.token) {
|
|
1490
|
+
connectionState = "missing-token";
|
|
1491
|
+
} else if (ws?.readyState === WebSocket.OPEN) {
|
|
1492
|
+
connectionState = "connected";
|
|
1493
|
+
} else if (ws?.readyState === WebSocket.CONNECTING) {
|
|
1494
|
+
connectionState = "connecting";
|
|
1495
|
+
} else if (manualDisconnect) {
|
|
1496
|
+
connectionState = "manual";
|
|
1497
|
+
} else if (nextReconnectInMs !== null) {
|
|
1498
|
+
connectionState = "reconnecting";
|
|
1499
|
+
} else {
|
|
1500
|
+
connectionState = "disconnected";
|
|
1501
|
+
}
|
|
1502
|
+
return {
|
|
1503
|
+
ok: true,
|
|
1504
|
+
connected: ws?.readyState === WebSocket.OPEN,
|
|
1505
|
+
connectionState,
|
|
1506
|
+
hasToken: Boolean(config.token),
|
|
1507
|
+
port: config.port,
|
|
1508
|
+
wsUrl: `ws://127.0.0.1:${config.port}/extension`,
|
|
1509
|
+
debugRichText: config.debugRichText,
|
|
1510
|
+
lastError: lastError?.message ?? null,
|
|
1511
|
+
lastErrorAt: lastError?.at ?? null,
|
|
1512
|
+
lastErrorContext: lastError?.context ?? null,
|
|
1513
|
+
reconnectAttempt,
|
|
1514
|
+
nextReconnectInMs: reconnectRemainingMs,
|
|
1515
|
+
manualDisconnect,
|
|
1516
|
+
extensionVersion: EXTENSION_VERSION,
|
|
1517
|
+
lastBindingUpdateAt,
|
|
1518
|
+
lastBindingUpdateReason,
|
|
1519
|
+
sessionBindings
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1402
1522
|
async function saveSessionBindingState(state) {
|
|
1403
1523
|
await mutateSessionBindingStateMap((stateMap) => {
|
|
1404
1524
|
stateMap[state.id] = state;
|
|
@@ -1409,6 +1529,28 @@
|
|
|
1409
1529
|
delete stateMap[bindingId];
|
|
1410
1530
|
});
|
|
1411
1531
|
}
|
|
1532
|
+
function toSessionBindingEventBrowser(state) {
|
|
1533
|
+
if (!state) {
|
|
1534
|
+
return null;
|
|
1535
|
+
}
|
|
1536
|
+
return {
|
|
1537
|
+
windowId: state.windowId,
|
|
1538
|
+
groupId: state.groupId,
|
|
1539
|
+
tabIds: [...state.tabIds],
|
|
1540
|
+
activeTabId: state.activeTabId,
|
|
1541
|
+
primaryTabId: state.primaryTabId
|
|
1542
|
+
};
|
|
1543
|
+
}
|
|
1544
|
+
function emitSessionBindingUpdated(bindingId, reason, state, extras = {}) {
|
|
1545
|
+
lastBindingUpdateAt = Date.now();
|
|
1546
|
+
lastBindingUpdateReason = reason;
|
|
1547
|
+
sendEvent("sessionBinding.updated", {
|
|
1548
|
+
bindingId,
|
|
1549
|
+
reason,
|
|
1550
|
+
browser: toSessionBindingEventBrowser(state),
|
|
1551
|
+
...extras
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1412
1554
|
var sessionBindingBrowser = {
|
|
1413
1555
|
async getTab(tabId) {
|
|
1414
1556
|
try {
|
|
@@ -1756,10 +1898,15 @@
|
|
|
1756
1898
|
return action();
|
|
1757
1899
|
}
|
|
1758
1900
|
const focusContext = await captureFocusContext();
|
|
1901
|
+
preserveHumanFocusDepth += 1;
|
|
1759
1902
|
try {
|
|
1760
1903
|
return await action();
|
|
1761
1904
|
} finally {
|
|
1762
|
-
|
|
1905
|
+
try {
|
|
1906
|
+
await restoreFocusContext(focusContext);
|
|
1907
|
+
} finally {
|
|
1908
|
+
preserveHumanFocusDepth = Math.max(0, preserveHumanFocusDepth - 1);
|
|
1909
|
+
}
|
|
1763
1910
|
}
|
|
1764
1911
|
}
|
|
1765
1912
|
function requireRpcEnvelope(method, value) {
|
|
@@ -2622,11 +2769,13 @@
|
|
|
2622
2769
|
const result = await bindingManager.ensureBinding({
|
|
2623
2770
|
bindingId: String(params.bindingId ?? ""),
|
|
2624
2771
|
focus: params.focus === true,
|
|
2625
|
-
initialUrl: typeof params.url === "string" ? params.url : void 0
|
|
2772
|
+
initialUrl: typeof params.url === "string" ? params.url : void 0,
|
|
2773
|
+
label: typeof params.label === "string" ? params.label : void 0
|
|
2626
2774
|
});
|
|
2627
2775
|
for (const tab of result.binding.tabs) {
|
|
2628
2776
|
void ensureNetworkDebugger(tab.id).catch(() => void 0);
|
|
2629
2777
|
}
|
|
2778
|
+
emitSessionBindingUpdated(result.binding.id, "ensure", result.binding);
|
|
2630
2779
|
return {
|
|
2631
2780
|
browser: result.binding,
|
|
2632
2781
|
created: result.created,
|
|
@@ -2647,11 +2796,13 @@
|
|
|
2647
2796
|
bindingId: String(params.bindingId ?? ""),
|
|
2648
2797
|
url: expectedUrl,
|
|
2649
2798
|
active: params.active === true,
|
|
2650
|
-
focus: params.focus === true
|
|
2799
|
+
focus: params.focus === true,
|
|
2800
|
+
label: typeof params.label === "string" ? params.label : void 0
|
|
2651
2801
|
});
|
|
2652
2802
|
});
|
|
2653
2803
|
const finalized = await finalizeOpenedSessionBindingTab(opened, expectedUrl);
|
|
2654
2804
|
void ensureNetworkDebugger(finalized.tab.id).catch(() => void 0);
|
|
2805
|
+
emitSessionBindingUpdated(finalized.binding.id, "open-tab", finalized.binding);
|
|
2655
2806
|
return {
|
|
2656
2807
|
browser: finalized.binding,
|
|
2657
2808
|
tab: finalized.tab
|
|
@@ -2674,6 +2825,7 @@
|
|
|
2674
2825
|
case "sessionBinding.setActiveTab": {
|
|
2675
2826
|
const result = await bindingManager.setActiveTab(Number(params.tabId), String(params.bindingId ?? ""));
|
|
2676
2827
|
void ensureNetworkDebugger(result.tab.id).catch(() => void 0);
|
|
2828
|
+
emitSessionBindingUpdated(result.binding.id, "set-active-tab", result.binding);
|
|
2677
2829
|
return {
|
|
2678
2830
|
browser: result.binding,
|
|
2679
2831
|
tab: result.tab
|
|
@@ -2691,8 +2843,10 @@
|
|
|
2691
2843
|
const result = await bindingManager.reset({
|
|
2692
2844
|
bindingId: String(params.bindingId ?? ""),
|
|
2693
2845
|
focus: params.focus === true,
|
|
2694
|
-
initialUrl: typeof params.url === "string" ? params.url : void 0
|
|
2846
|
+
initialUrl: typeof params.url === "string" ? params.url : void 0,
|
|
2847
|
+
label: typeof params.label === "string" ? params.label : void 0
|
|
2695
2848
|
});
|
|
2849
|
+
emitSessionBindingUpdated(result.binding.id, "reset", result.binding);
|
|
2696
2850
|
return {
|
|
2697
2851
|
browser: result.binding,
|
|
2698
2852
|
created: result.created,
|
|
@@ -2701,8 +2855,22 @@
|
|
|
2701
2855
|
};
|
|
2702
2856
|
});
|
|
2703
2857
|
}
|
|
2858
|
+
case "sessionBinding.closeTab": {
|
|
2859
|
+
const bindingId = String(params.bindingId ?? "");
|
|
2860
|
+
const result = await bindingManager.closeTab(bindingId, typeof params.tabId === "number" ? params.tabId : void 0);
|
|
2861
|
+
emitSessionBindingUpdated(bindingId, "close-tab", result.binding, {
|
|
2862
|
+
closedTabId: result.closedTabId
|
|
2863
|
+
});
|
|
2864
|
+
return {
|
|
2865
|
+
browser: result.binding,
|
|
2866
|
+
closedTabId: result.closedTabId
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2704
2869
|
case "sessionBinding.close": {
|
|
2705
|
-
|
|
2870
|
+
const bindingId = String(params.bindingId ?? "");
|
|
2871
|
+
const result = await bindingManager.close(bindingId);
|
|
2872
|
+
emitSessionBindingUpdated(bindingId, "close", null);
|
|
2873
|
+
return result;
|
|
2706
2874
|
}
|
|
2707
2875
|
case "page.goto": {
|
|
2708
2876
|
return await preserveHumanFocus(typeof target.tabId !== "number", async () => {
|
|
@@ -3195,9 +3363,11 @@
|
|
|
3195
3363
|
const delayMs = computeReconnectDelayMs(reconnectAttempt);
|
|
3196
3364
|
reconnectAttempt += 1;
|
|
3197
3365
|
nextReconnectInMs = delayMs;
|
|
3366
|
+
nextReconnectAt = Date.now() + delayMs;
|
|
3198
3367
|
reconnectTimer = setTimeout(() => {
|
|
3199
3368
|
reconnectTimer = null;
|
|
3200
3369
|
nextReconnectInMs = null;
|
|
3370
|
+
nextReconnectAt = null;
|
|
3201
3371
|
void connectWebSocket();
|
|
3202
3372
|
}, delayMs);
|
|
3203
3373
|
if (!lastError) {
|
|
@@ -3218,19 +3388,23 @@
|
|
|
3218
3388
|
return;
|
|
3219
3389
|
}
|
|
3220
3390
|
const url = `ws://127.0.0.1:${config.port}/extension?token=${encodeURIComponent(config.token)}`;
|
|
3221
|
-
|
|
3222
|
-
ws
|
|
3391
|
+
const socket = new WebSocket(url);
|
|
3392
|
+
ws = socket;
|
|
3393
|
+
socket.addEventListener("open", () => {
|
|
3394
|
+
if (ws !== socket) {
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3223
3397
|
manualDisconnect = false;
|
|
3224
3398
|
reconnectAttempt = 0;
|
|
3225
3399
|
lastError = null;
|
|
3226
|
-
|
|
3400
|
+
socket.send(JSON.stringify({
|
|
3227
3401
|
type: "hello",
|
|
3228
3402
|
role: "extension",
|
|
3229
3403
|
version: EXTENSION_VERSION,
|
|
3230
3404
|
ts: Date.now()
|
|
3231
3405
|
}));
|
|
3232
3406
|
});
|
|
3233
|
-
|
|
3407
|
+
socket.addEventListener("message", (event) => {
|
|
3234
3408
|
try {
|
|
3235
3409
|
const request = JSON.parse(String(event.data));
|
|
3236
3410
|
if (!request.id || !request.method) {
|
|
@@ -3251,59 +3425,97 @@
|
|
|
3251
3425
|
});
|
|
3252
3426
|
}
|
|
3253
3427
|
});
|
|
3254
|
-
|
|
3428
|
+
socket.addEventListener("close", () => {
|
|
3429
|
+
if (ws !== socket) {
|
|
3430
|
+
return;
|
|
3431
|
+
}
|
|
3255
3432
|
ws = null;
|
|
3256
3433
|
scheduleReconnect("socket-closed");
|
|
3257
3434
|
});
|
|
3258
|
-
|
|
3435
|
+
socket.addEventListener("error", () => {
|
|
3436
|
+
if (ws !== socket) {
|
|
3437
|
+
return;
|
|
3438
|
+
}
|
|
3259
3439
|
setRuntimeError("Cannot connect to bak cli", "socket");
|
|
3260
|
-
|
|
3440
|
+
socket.close();
|
|
3261
3441
|
});
|
|
3262
3442
|
}
|
|
3263
3443
|
chrome.tabs.onRemoved.addListener((tabId) => {
|
|
3264
3444
|
dropNetworkCapture(tabId);
|
|
3265
3445
|
void mutateSessionBindingStateMap((stateMap) => {
|
|
3446
|
+
const updates = [];
|
|
3266
3447
|
for (const [bindingId, state] of Object.entries(stateMap)) {
|
|
3267
3448
|
if (!state.tabIds.includes(tabId)) {
|
|
3268
3449
|
continue;
|
|
3269
3450
|
}
|
|
3270
3451
|
const nextTabIds = state.tabIds.filter((id) => id !== tabId);
|
|
3271
|
-
|
|
3452
|
+
if (nextTabIds.length === 0) {
|
|
3453
|
+
delete stateMap[bindingId];
|
|
3454
|
+
updates.push({ bindingId, state: null });
|
|
3455
|
+
continue;
|
|
3456
|
+
}
|
|
3457
|
+
const fallbackTabId = nextTabIds[0] ?? null;
|
|
3458
|
+
const nextState = {
|
|
3272
3459
|
...state,
|
|
3273
3460
|
tabIds: nextTabIds,
|
|
3274
|
-
activeTabId: state.activeTabId === tabId ?
|
|
3275
|
-
primaryTabId: state.primaryTabId === tabId ?
|
|
3461
|
+
activeTabId: state.activeTabId === tabId ? fallbackTabId : state.activeTabId,
|
|
3462
|
+
primaryTabId: state.primaryTabId === tabId ? fallbackTabId : state.primaryTabId
|
|
3276
3463
|
};
|
|
3464
|
+
stateMap[bindingId] = nextState;
|
|
3465
|
+
updates.push({ bindingId, state: nextState });
|
|
3466
|
+
}
|
|
3467
|
+
return updates;
|
|
3468
|
+
}).then((updates) => {
|
|
3469
|
+
for (const update of updates) {
|
|
3470
|
+
emitSessionBindingUpdated(update.bindingId, "tab-removed", update.state, {
|
|
3471
|
+
closedTabId: tabId
|
|
3472
|
+
});
|
|
3277
3473
|
}
|
|
3278
3474
|
});
|
|
3279
3475
|
});
|
|
3280
3476
|
chrome.tabs.onActivated.addListener((activeInfo) => {
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3477
|
+
if (preserveHumanFocusDepth > 0) {
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
void chrome.windows.get(activeInfo.windowId).then((window2) => window2.focused === true).catch(() => false).then((windowFocused) => {
|
|
3481
|
+
if (!windowFocused) {
|
|
3482
|
+
return [];
|
|
3483
|
+
}
|
|
3484
|
+
return mutateSessionBindingStateMap((stateMap) => {
|
|
3485
|
+
const updates = [];
|
|
3486
|
+
for (const [bindingId, state] of Object.entries(stateMap)) {
|
|
3487
|
+
if (state.windowId !== activeInfo.windowId || !state.tabIds.includes(activeInfo.tabId)) {
|
|
3488
|
+
continue;
|
|
3489
|
+
}
|
|
3490
|
+
const nextState = {
|
|
3491
|
+
...state,
|
|
3492
|
+
activeTabId: activeInfo.tabId
|
|
3493
|
+
};
|
|
3494
|
+
stateMap[bindingId] = nextState;
|
|
3495
|
+
updates.push({ bindingId, state: nextState });
|
|
3285
3496
|
}
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3497
|
+
return updates;
|
|
3498
|
+
});
|
|
3499
|
+
}).then((updates) => {
|
|
3500
|
+
for (const update of updates) {
|
|
3501
|
+
emitSessionBindingUpdated(update.bindingId, "tab-activated", update.state);
|
|
3290
3502
|
}
|
|
3291
3503
|
});
|
|
3292
3504
|
});
|
|
3293
3505
|
chrome.windows.onRemoved.addListener((windowId) => {
|
|
3294
3506
|
void mutateSessionBindingStateMap((stateMap) => {
|
|
3507
|
+
const updates = [];
|
|
3295
3508
|
for (const [bindingId, state] of Object.entries(stateMap)) {
|
|
3296
3509
|
if (state.windowId !== windowId) {
|
|
3297
3510
|
continue;
|
|
3298
3511
|
}
|
|
3299
|
-
stateMap[bindingId]
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
};
|
|
3512
|
+
delete stateMap[bindingId];
|
|
3513
|
+
updates.push({ bindingId, state: null });
|
|
3514
|
+
}
|
|
3515
|
+
return updates;
|
|
3516
|
+
}).then((updates) => {
|
|
3517
|
+
for (const update of updates) {
|
|
3518
|
+
emitSessionBindingUpdated(update.bindingId, "window-removed", update.state);
|
|
3307
3519
|
}
|
|
3308
3520
|
});
|
|
3309
3521
|
});
|
|
@@ -3317,8 +3529,9 @@
|
|
|
3317
3529
|
chrome.runtime.onMessage.addListener((message, _sender, sendResponse2) => {
|
|
3318
3530
|
if (message?.type === "bak.updateConfig") {
|
|
3319
3531
|
manualDisconnect = false;
|
|
3532
|
+
const token = typeof message.token === "string" ? message.token.trim() : "";
|
|
3320
3533
|
void setConfig({
|
|
3321
|
-
token:
|
|
3534
|
+
...token ? { token } : {},
|
|
3322
3535
|
port: Number(message.port ?? DEFAULT_PORT),
|
|
3323
3536
|
debugRichText: message.debugRichText === true
|
|
3324
3537
|
}).then(() => {
|
|
@@ -3328,19 +3541,8 @@
|
|
|
3328
3541
|
return true;
|
|
3329
3542
|
}
|
|
3330
3543
|
if (message?.type === "bak.getState") {
|
|
3331
|
-
void
|
|
3332
|
-
sendResponse2(
|
|
3333
|
-
ok: true,
|
|
3334
|
-
connected: ws?.readyState === WebSocket.OPEN,
|
|
3335
|
-
hasToken: Boolean(config.token),
|
|
3336
|
-
port: config.port,
|
|
3337
|
-
debugRichText: config.debugRichText,
|
|
3338
|
-
lastError: lastError?.message ?? null,
|
|
3339
|
-
lastErrorAt: lastError?.at ?? null,
|
|
3340
|
-
lastErrorContext: lastError?.context ?? null,
|
|
3341
|
-
reconnectAttempt,
|
|
3342
|
-
nextReconnectInMs
|
|
3343
|
-
});
|
|
3544
|
+
void buildPopupState().then((state) => {
|
|
3545
|
+
sendResponse2(state);
|
|
3344
3546
|
});
|
|
3345
3547
|
return true;
|
|
3346
3548
|
}
|
|
@@ -3348,11 +3550,21 @@
|
|
|
3348
3550
|
manualDisconnect = true;
|
|
3349
3551
|
clearReconnectTimer();
|
|
3350
3552
|
reconnectAttempt = 0;
|
|
3553
|
+
lastError = null;
|
|
3351
3554
|
ws?.close();
|
|
3352
3555
|
ws = null;
|
|
3353
3556
|
sendResponse2({ ok: true });
|
|
3354
3557
|
return false;
|
|
3355
3558
|
}
|
|
3559
|
+
if (message?.type === "bak.reconnectNow") {
|
|
3560
|
+
manualDisconnect = false;
|
|
3561
|
+
clearReconnectTimer();
|
|
3562
|
+
reconnectAttempt = 0;
|
|
3563
|
+
ws?.close();
|
|
3564
|
+
ws = null;
|
|
3565
|
+
void connectWebSocket().then(() => sendResponse2({ ok: true }));
|
|
3566
|
+
return true;
|
|
3567
|
+
}
|
|
3356
3568
|
return false;
|
|
3357
3569
|
});
|
|
3358
3570
|
})();
|