@btraut/browser-bridge 0.11.1 → 0.12.0

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.
@@ -36,7 +36,7 @@ body.bb-page {
36
36
  body.bb-page.bb-page--popup {
37
37
  height: auto;
38
38
  padding: 10px 10px 12px;
39
- min-width: 260px;
39
+ min-width: 300px;
40
40
  background: var(--bb-surface);
41
41
  }
42
42
 
@@ -68,6 +68,113 @@ body.bb-page.bb-page--popup {
68
68
  padding: 2px;
69
69
  }
70
70
 
71
+ .bb-health {
72
+ border: 1px solid var(--bb-border-2);
73
+ border-radius: var(--bb-radius-sm);
74
+ background: var(--bb-bg);
75
+ box-shadow: 0 3px 8px rgba(0, 0, 0, 0.06);
76
+ padding: 10px 12px;
77
+ margin: 0 0 10px;
78
+ }
79
+
80
+ .bb-health-head {
81
+ display: flex;
82
+ align-items: center;
83
+ justify-content: space-between;
84
+ margin-bottom: 8px;
85
+ }
86
+
87
+ .bb-health-label {
88
+ font-size: 12px;
89
+ font-weight: 700;
90
+ letter-spacing: 0.04em;
91
+ color: var(--bb-ink-2);
92
+ text-transform: uppercase;
93
+ }
94
+
95
+ .bb-health-state {
96
+ border: 1px solid var(--bb-border);
97
+ border-radius: 999px;
98
+ padding: 2px 8px;
99
+ font-size: 11px;
100
+ font-weight: 700;
101
+ text-transform: capitalize;
102
+ color: var(--bb-ink);
103
+ background: rgba(0, 0, 0, 0.04);
104
+ }
105
+
106
+ .bb-health-state[data-state='connected'] {
107
+ color: #1f6f3f;
108
+ border-color: rgba(31, 111, 63, 0.35);
109
+ background: rgba(31, 111, 63, 0.12);
110
+ }
111
+
112
+ .bb-health-state[data-state='connecting'],
113
+ .bb-health-state[data-state='backoff'] {
114
+ color: #8a5200;
115
+ border-color: rgba(138, 82, 0, 0.35);
116
+ background: rgba(138, 82, 0, 0.12);
117
+ }
118
+
119
+ .bb-health-state[data-state='disconnected'] {
120
+ color: #a22b2b;
121
+ border-color: rgba(162, 43, 43, 0.35);
122
+ background: rgba(162, 43, 43, 0.12);
123
+ }
124
+
125
+ .bb-health-grid {
126
+ display: grid;
127
+ grid-template-columns: auto 1fr;
128
+ gap: 6px 8px;
129
+ margin: 0;
130
+ }
131
+
132
+ .bb-health-grid dt {
133
+ margin: 0;
134
+ font-size: 11px;
135
+ color: var(--bb-ink-2);
136
+ }
137
+
138
+ .bb-health-grid dd {
139
+ margin: 0;
140
+ font-size: 12px;
141
+ color: var(--bb-ink);
142
+ overflow-wrap: anywhere;
143
+ }
144
+
145
+ .bb-health-error {
146
+ min-height: 14px;
147
+ margin: 8px 0 0;
148
+ font-size: 11px;
149
+ color: #a22b2b;
150
+ }
151
+
152
+ .bb-health-actions {
153
+ margin-top: 6px;
154
+ display: flex;
155
+ align-items: center;
156
+ gap: 8px;
157
+ }
158
+
159
+ .bb-health-copy {
160
+ border: 1px solid var(--bb-border);
161
+ border-radius: 8px;
162
+ background: var(--bb-bg);
163
+ color: var(--bb-ink);
164
+ padding: 5px 8px;
165
+ font-size: 12px;
166
+ cursor: pointer;
167
+ }
168
+
169
+ .bb-health-copy:hover {
170
+ background: var(--bb-hover);
171
+ }
172
+
173
+ .bb-health-copy-status {
174
+ font-size: 11px;
175
+ color: var(--bb-ink-2);
176
+ }
177
+
71
178
  .bb-popup-head {
72
179
  display: flex;
73
180
  align-items: center;
@@ -410,6 +410,80 @@ var PermissionPromptController = class {
410
410
  }
411
411
  };
412
412
 
413
+ // packages/extension/src/connection-state.ts
414
+ var toIso = (ms) => new Date(ms).toISOString();
415
+ var ConnectionStateTracker = class {
416
+ constructor(now = Date.now, failureLogThrottleMs = 15e3) {
417
+ this.now = now;
418
+ this.failureLogThrottleMs = failureLogThrottleMs;
419
+ this.state = "disconnected";
420
+ this.consecutiveFailures = 0;
421
+ this.lastFailureLogAt = 0;
422
+ this.suppressedFailureLogs = 0;
423
+ }
424
+ setEndpoint(endpoint) {
425
+ this.endpoint = endpoint;
426
+ }
427
+ markConnecting() {
428
+ this.state = "connecting";
429
+ this.reconnectDelayMs = void 0;
430
+ this.retryAt = void 0;
431
+ }
432
+ markConnected() {
433
+ this.state = "connected";
434
+ this.lastConnectedAt = toIso(this.now());
435
+ this.reconnectDelayMs = void 0;
436
+ this.retryAt = void 0;
437
+ this.consecutiveFailures = 0;
438
+ }
439
+ markDisconnected() {
440
+ this.state = "disconnected";
441
+ this.reconnectDelayMs = void 0;
442
+ this.retryAt = void 0;
443
+ this.lastDisconnectedAt = toIso(this.now());
444
+ }
445
+ markBackoff(delayMs2) {
446
+ this.state = "backoff";
447
+ this.reconnectDelayMs = delayMs2;
448
+ this.retryAt = toIso(this.now() + Math.max(0, delayMs2));
449
+ }
450
+ recordFailure(message) {
451
+ this.lastErrorAt = toIso(this.now());
452
+ this.lastErrorMessage = message;
453
+ this.consecutiveFailures += 1;
454
+ }
455
+ consumeFailureLogBudget() {
456
+ const now = this.now();
457
+ if (this.lastFailureLogAt === 0 || now - this.lastFailureLogAt >= this.failureLogThrottleMs) {
458
+ const suppressedCount = this.suppressedFailureLogs;
459
+ this.suppressedFailureLogs = 0;
460
+ this.lastFailureLogAt = now;
461
+ return { shouldLog: true, suppressedCount };
462
+ }
463
+ this.suppressedFailureLogs += 1;
464
+ return { shouldLog: false, suppressedCount: this.suppressedFailureLogs };
465
+ }
466
+ flushSuppressedFailureLogs() {
467
+ const count = this.suppressedFailureLogs;
468
+ this.suppressedFailureLogs = 0;
469
+ return count;
470
+ }
471
+ getStatus() {
472
+ return {
473
+ state: this.state,
474
+ endpoint: this.endpoint,
475
+ ws_url: this.endpoint ? `ws://${this.endpoint.host}:${this.endpoint.port}/drive` : void 0,
476
+ reconnect_delay_ms: this.reconnectDelayMs,
477
+ retry_at: this.retryAt,
478
+ last_connected_at: this.lastConnectedAt,
479
+ last_disconnected_at: this.lastDisconnectedAt,
480
+ last_error_at: this.lastErrorAt,
481
+ last_error_message: this.lastErrorMessage,
482
+ consecutive_failures: this.consecutiveFailures
483
+ };
484
+ }
485
+ };
486
+
413
487
  // packages/extension/src/background.ts
414
488
  var DEFAULT_CORE_PORT = 3210;
415
489
  var CORE_PORT_KEY = "corePort";
@@ -598,24 +672,36 @@ var renderDataUrlToFormat = async (dataUrl, format, quality) => {
598
672
  bitmap.close();
599
673
  }
600
674
  };
601
- var readCorePort = async () => {
675
+ var readCoreEndpointConfig = async () => {
602
676
  return await new Promise((resolve) => {
603
677
  chrome.storage.local.get(
604
678
  [CORE_PORT_KEY],
605
679
  (result) => {
606
680
  const raw = result?.[CORE_PORT_KEY];
607
681
  if (typeof raw === "number" && Number.isFinite(raw)) {
608
- resolve(raw);
682
+ resolve({
683
+ host: "127.0.0.1",
684
+ port: raw,
685
+ portSource: "storage"
686
+ });
609
687
  return;
610
688
  }
611
689
  if (typeof raw === "string") {
612
690
  const parsed = Number(raw);
613
691
  if (Number.isFinite(parsed)) {
614
- resolve(parsed);
692
+ resolve({
693
+ host: "127.0.0.1",
694
+ port: parsed,
695
+ portSource: "storage"
696
+ });
615
697
  return;
616
698
  }
617
699
  }
618
- resolve(DEFAULT_CORE_PORT);
700
+ resolve({
701
+ host: "127.0.0.1",
702
+ port: DEFAULT_CORE_PORT,
703
+ portSource: "default"
704
+ });
619
705
  }
620
706
  );
621
707
  });
@@ -1060,9 +1146,12 @@ var waitForDomContentLoaded = async (tabId, timeoutMs) => {
1060
1146
  }, timeoutMs);
1061
1147
  });
1062
1148
  };
1063
- var getWsUrl = async () => {
1064
- const port = await readCorePort();
1065
- return `ws://127.0.0.1:${port}${CORE_WS_PATH}`;
1149
+ var getWsEndpoint = async () => {
1150
+ const endpoint = await readCoreEndpointConfig();
1151
+ return {
1152
+ endpoint,
1153
+ url: `ws://${endpoint.host}:${endpoint.port}${CORE_WS_PATH}`
1154
+ };
1066
1155
  };
1067
1156
  var DriveSocket = class {
1068
1157
  constructor() {
@@ -1074,10 +1163,12 @@ var DriveSocket = class {
1074
1163
  this.keepAliveIntervalMs = 3e4;
1075
1164
  this.debuggerSessions = /* @__PURE__ */ new Map();
1076
1165
  this.debuggerIdleTimeoutMs = null;
1166
+ this.connection = new ConnectionStateTracker();
1077
1167
  }
1078
1168
  start() {
1169
+ this.connection.markConnecting();
1079
1170
  void this.connect().catch((error) => {
1080
- console.error("DriveSocket connect failed:", error);
1171
+ this.recordConnectionFailure("initial connect", error);
1081
1172
  });
1082
1173
  }
1083
1174
  stop() {
@@ -1090,6 +1181,7 @@ var DriveSocket = class {
1090
1181
  this.socket.close();
1091
1182
  this.socket = null;
1092
1183
  }
1184
+ this.connection.markDisconnected();
1093
1185
  }
1094
1186
  sendTabReport() {
1095
1187
  void this.emitTabReport().catch((error) => {
@@ -1101,10 +1193,12 @@ var DriveSocket = class {
1101
1193
  return;
1102
1194
  }
1103
1195
  const delay2 = this.reconnectDelayMs;
1196
+ this.connection.markBackoff(delay2);
1104
1197
  this.reconnectTimer = self.setTimeout(() => {
1105
1198
  this.reconnectTimer = null;
1199
+ this.connection.markConnecting();
1106
1200
  void this.connect().catch((error) => {
1107
- console.error("DriveSocket reconnect failed:", error);
1201
+ this.recordConnectionFailure("reconnect", error);
1108
1202
  });
1109
1203
  }, delay2);
1110
1204
  this.reconnectDelayMs = Math.min(
@@ -1113,12 +1207,20 @@ var DriveSocket = class {
1113
1207
  );
1114
1208
  }
1115
1209
  async connect() {
1116
- const url = await getWsUrl();
1210
+ const { endpoint, url } = await getWsEndpoint();
1211
+ this.connection.setEndpoint(endpoint);
1117
1212
  try {
1118
1213
  const socket2 = new WebSocket(url);
1119
1214
  this.socket = socket2;
1120
1215
  socket2.addEventListener("open", () => {
1121
1216
  this.reconnectDelayMs = 1e3;
1217
+ this.connection.markConnected();
1218
+ const suppressed = this.connection.flushSuppressedFailureLogs();
1219
+ if (suppressed > 0) {
1220
+ console.info(
1221
+ `DriveSocket reconnected after suppressing ${suppressed} repeated connection failures.`
1222
+ );
1223
+ }
1122
1224
  this.startKeepAlive();
1123
1225
  void this.sendHello().catch((error) => {
1124
1226
  console.error("DriveSocket hello failed:", error);
@@ -1128,22 +1230,49 @@ var DriveSocket = class {
1128
1230
  this.handleMessage(event.data);
1129
1231
  });
1130
1232
  socket2.addEventListener("close", () => {
1131
- this.socket = null;
1132
- this.stopKeepAlive();
1133
- this.scheduleReconnect();
1233
+ this.handleSocketUnavailable(socket2, "socket closed");
1134
1234
  });
1135
1235
  socket2.addEventListener("error", () => {
1136
- this.socket = null;
1137
- this.stopKeepAlive();
1138
- this.scheduleReconnect();
1236
+ this.handleSocketUnavailable(socket2, "socket error");
1139
1237
  });
1140
1238
  } catch (error) {
1141
- console.debug("DriveSocket connect failed, scheduling reconnect.", error);
1239
+ this.recordConnectionFailure("connect", error);
1240
+ this.connection.markDisconnected();
1142
1241
  this.scheduleReconnect();
1143
1242
  }
1144
1243
  }
1244
+ getConnectionStatus() {
1245
+ return this.connection.getStatus();
1246
+ }
1247
+ handleSocketUnavailable(socket2, reason) {
1248
+ if (this.socket !== socket2) {
1249
+ return;
1250
+ }
1251
+ this.socket = null;
1252
+ this.stopKeepAlive();
1253
+ this.connection.markDisconnected();
1254
+ this.recordConnectionFailure(reason, new Error(reason));
1255
+ this.scheduleReconnect();
1256
+ }
1257
+ recordConnectionFailure(context, error) {
1258
+ const detail = error instanceof Error ? error.message : typeof error === "string" ? error : "Unknown error";
1259
+ this.connection.recordFailure(`${context}: ${detail}`);
1260
+ const budget = this.connection.consumeFailureLogBudget();
1261
+ if (!budget.shouldLog) {
1262
+ return;
1263
+ }
1264
+ if (budget.suppressedCount > 0) {
1265
+ console.warn(
1266
+ `DriveSocket ${context} failed (${budget.suppressedCount} repeated failures suppressed).`,
1267
+ error
1268
+ );
1269
+ return;
1270
+ }
1271
+ console.warn(`DriveSocket ${context} failed.`, error);
1272
+ }
1145
1273
  async sendHello() {
1146
1274
  const manifest = chrome.runtime.getManifest();
1275
+ const endpoint = await readCoreEndpointConfig();
1147
1276
  let tabs = [];
1148
1277
  try {
1149
1278
  tabs = await queryTabs();
@@ -1153,6 +1282,9 @@ var DriveSocket = class {
1153
1282
  }
1154
1283
  const params = {
1155
1284
  version: manifest.version,
1285
+ core_host: endpoint.host,
1286
+ core_port: endpoint.port,
1287
+ core_port_source: endpoint.portSource,
1156
1288
  tabs
1157
1289
  };
1158
1290
  this.sendEvent("drive.hello", params);
@@ -3188,5 +3320,17 @@ chrome.debugger.onDetach.addListener(
3188
3320
  });
3189
3321
  }
3190
3322
  );
3323
+ chrome.runtime.onMessage.addListener(
3324
+ (message, _sender, sendResponse) => {
3325
+ if (!message || typeof message !== "object" || message.action !== "drive.connection_status") {
3326
+ return void 0;
3327
+ }
3328
+ sendResponse({
3329
+ ok: true,
3330
+ result: socket.getConnectionStatus()
3331
+ });
3332
+ return true;
3333
+ }
3334
+ );
3191
3335
  socket.start();
3192
3336
  //# sourceMappingURL=background.js.map