@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.
- package/CHANGELOG.md +23 -0
- package/README.md +41 -0
- package/dist/api.js +878 -128
- package/dist/api.js.map +4 -4
- package/dist/index.js +861 -378
- package/dist/index.js.map +4 -4
- package/extension/assets/ui.css +108 -1
- package/extension/dist/background.js +161 -17
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/popup-ui.js +110 -3
- package/extension/dist/popup-ui.js.map +2 -2
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
- package/skills/browser-bridge/skill.json +1 -1
package/extension/assets/ui.css
CHANGED
|
@@ -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:
|
|
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
|
|
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(
|
|
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(
|
|
692
|
+
resolve({
|
|
693
|
+
host: "127.0.0.1",
|
|
694
|
+
port: parsed,
|
|
695
|
+
portSource: "storage"
|
|
696
|
+
});
|
|
615
697
|
return;
|
|
616
698
|
}
|
|
617
699
|
}
|
|
618
|
-
resolve(
|
|
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
|
|
1064
|
-
const
|
|
1065
|
-
return
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
1132
|
-
this.stopKeepAlive();
|
|
1133
|
-
this.scheduleReconnect();
|
|
1233
|
+
this.handleSocketUnavailable(socket2, "socket closed");
|
|
1134
1234
|
});
|
|
1135
1235
|
socket2.addEventListener("error", () => {
|
|
1136
|
-
this.socket
|
|
1137
|
-
this.stopKeepAlive();
|
|
1138
|
-
this.scheduleReconnect();
|
|
1236
|
+
this.handleSocketUnavailable(socket2, "socket error");
|
|
1139
1237
|
});
|
|
1140
1238
|
} catch (error) {
|
|
1141
|
-
|
|
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
|