@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.
@@ -1 +1 @@
1
- 2026-03-12T12:13:27.330Z
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.3",
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
- if (state.windowId !== null) {
846
- const existingWindow = await this.browser.getWindow(state.windowId);
847
- if (existingWindow) {
848
- await this.browser.closeWindow(state.windowId);
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
- await restoreFocusContext(focusContext);
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
- return await bindingManager.close(String(params.bindingId ?? ""));
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
- ws = new WebSocket(url);
3222
- ws.addEventListener("open", () => {
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
- ws?.send(JSON.stringify({
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
- ws.addEventListener("message", (event) => {
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
- ws.addEventListener("close", () => {
3428
+ socket.addEventListener("close", () => {
3429
+ if (ws !== socket) {
3430
+ return;
3431
+ }
3255
3432
  ws = null;
3256
3433
  scheduleReconnect("socket-closed");
3257
3434
  });
3258
- ws.addEventListener("error", () => {
3435
+ socket.addEventListener("error", () => {
3436
+ if (ws !== socket) {
3437
+ return;
3438
+ }
3259
3439
  setRuntimeError("Cannot connect to bak cli", "socket");
3260
- ws?.close();
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
- stateMap[bindingId] = {
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 ? null : state.activeTabId,
3275
- primaryTabId: state.primaryTabId === tabId ? null : state.primaryTabId
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
- void mutateSessionBindingStateMap((stateMap) => {
3282
- for (const [bindingId, state] of Object.entries(stateMap)) {
3283
- if (state.windowId !== activeInfo.windowId || !state.tabIds.includes(activeInfo.tabId)) {
3284
- continue;
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
- stateMap[bindingId] = {
3287
- ...state,
3288
- activeTabId: activeInfo.tabId
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
- ...state,
3301
- windowId: null,
3302
- groupId: null,
3303
- tabIds: [],
3304
- activeTabId: null,
3305
- primaryTabId: null
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: message.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 getConfig().then((config) => {
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
  })();
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "Browser Agent Kit",
4
- "version": "0.6.3",
4
+ "version": "0.6.5",
5
5
  "action": {
6
6
  "default_popup": "popup.html"
7
7
  },