@btraut/browser-bridge 0.11.0 → 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 +29 -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 +164 -23
- 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";
|
|
@@ -423,13 +497,10 @@ var HISTORY_DISPATCH_TIMEOUT_MS = 2e3;
|
|
|
423
497
|
var HISTORY_NAVIGATION_SIGNAL_TIMEOUT_MS = 8e3;
|
|
424
498
|
var HISTORY_POST_NAV_DOM_GRACE_TIMEOUT_MS = 2e3;
|
|
425
499
|
var AGENT_TAB_ID_KEY = "agentTabId";
|
|
426
|
-
var AGENT_TAB_GROUP_TITLE = "
|
|
427
|
-
var
|
|
500
|
+
var AGENT_TAB_GROUP_TITLE = "Browser Bridge";
|
|
501
|
+
var AGENT_TAB_BOOTSTRAP_PATH = "agent-tab.html";
|
|
428
502
|
var getAgentTabBootstrapUrl = () => {
|
|
429
|
-
|
|
430
|
-
return `data:text/html;charset=UTF-8,${encodeURIComponent(
|
|
431
|
-
`<!doctype html><html><head><meta charset="utf-8"><title>${AGENT_TAB_GROUP_TITLE}</title><link rel="icon" type="image/png" href="${faviconUrl}"></head><body></body></html>`
|
|
432
|
-
)}`;
|
|
503
|
+
return typeof chrome.runtime?.getURL === "function" ? chrome.runtime.getURL(AGENT_TAB_BOOTSTRAP_PATH) : AGENT_TAB_BOOTSTRAP_PATH;
|
|
433
504
|
};
|
|
434
505
|
var nowIso = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
435
506
|
var makeEventId = /* @__PURE__ */ (() => {
|
|
@@ -601,24 +672,36 @@ var renderDataUrlToFormat = async (dataUrl, format, quality) => {
|
|
|
601
672
|
bitmap.close();
|
|
602
673
|
}
|
|
603
674
|
};
|
|
604
|
-
var
|
|
675
|
+
var readCoreEndpointConfig = async () => {
|
|
605
676
|
return await new Promise((resolve) => {
|
|
606
677
|
chrome.storage.local.get(
|
|
607
678
|
[CORE_PORT_KEY],
|
|
608
679
|
(result) => {
|
|
609
680
|
const raw = result?.[CORE_PORT_KEY];
|
|
610
681
|
if (typeof raw === "number" && Number.isFinite(raw)) {
|
|
611
|
-
resolve(
|
|
682
|
+
resolve({
|
|
683
|
+
host: "127.0.0.1",
|
|
684
|
+
port: raw,
|
|
685
|
+
portSource: "storage"
|
|
686
|
+
});
|
|
612
687
|
return;
|
|
613
688
|
}
|
|
614
689
|
if (typeof raw === "string") {
|
|
615
690
|
const parsed = Number(raw);
|
|
616
691
|
if (Number.isFinite(parsed)) {
|
|
617
|
-
resolve(
|
|
692
|
+
resolve({
|
|
693
|
+
host: "127.0.0.1",
|
|
694
|
+
port: parsed,
|
|
695
|
+
portSource: "storage"
|
|
696
|
+
});
|
|
618
697
|
return;
|
|
619
698
|
}
|
|
620
699
|
}
|
|
621
|
-
resolve(
|
|
700
|
+
resolve({
|
|
701
|
+
host: "127.0.0.1",
|
|
702
|
+
port: DEFAULT_CORE_PORT,
|
|
703
|
+
portSource: "default"
|
|
704
|
+
});
|
|
622
705
|
}
|
|
623
706
|
);
|
|
624
707
|
});
|
|
@@ -1063,9 +1146,12 @@ var waitForDomContentLoaded = async (tabId, timeoutMs) => {
|
|
|
1063
1146
|
}, timeoutMs);
|
|
1064
1147
|
});
|
|
1065
1148
|
};
|
|
1066
|
-
var
|
|
1067
|
-
const
|
|
1068
|
-
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
|
+
};
|
|
1069
1155
|
};
|
|
1070
1156
|
var DriveSocket = class {
|
|
1071
1157
|
constructor() {
|
|
@@ -1077,10 +1163,12 @@ var DriveSocket = class {
|
|
|
1077
1163
|
this.keepAliveIntervalMs = 3e4;
|
|
1078
1164
|
this.debuggerSessions = /* @__PURE__ */ new Map();
|
|
1079
1165
|
this.debuggerIdleTimeoutMs = null;
|
|
1166
|
+
this.connection = new ConnectionStateTracker();
|
|
1080
1167
|
}
|
|
1081
1168
|
start() {
|
|
1169
|
+
this.connection.markConnecting();
|
|
1082
1170
|
void this.connect().catch((error) => {
|
|
1083
|
-
|
|
1171
|
+
this.recordConnectionFailure("initial connect", error);
|
|
1084
1172
|
});
|
|
1085
1173
|
}
|
|
1086
1174
|
stop() {
|
|
@@ -1093,6 +1181,7 @@ var DriveSocket = class {
|
|
|
1093
1181
|
this.socket.close();
|
|
1094
1182
|
this.socket = null;
|
|
1095
1183
|
}
|
|
1184
|
+
this.connection.markDisconnected();
|
|
1096
1185
|
}
|
|
1097
1186
|
sendTabReport() {
|
|
1098
1187
|
void this.emitTabReport().catch((error) => {
|
|
@@ -1104,10 +1193,12 @@ var DriveSocket = class {
|
|
|
1104
1193
|
return;
|
|
1105
1194
|
}
|
|
1106
1195
|
const delay2 = this.reconnectDelayMs;
|
|
1196
|
+
this.connection.markBackoff(delay2);
|
|
1107
1197
|
this.reconnectTimer = self.setTimeout(() => {
|
|
1108
1198
|
this.reconnectTimer = null;
|
|
1199
|
+
this.connection.markConnecting();
|
|
1109
1200
|
void this.connect().catch((error) => {
|
|
1110
|
-
|
|
1201
|
+
this.recordConnectionFailure("reconnect", error);
|
|
1111
1202
|
});
|
|
1112
1203
|
}, delay2);
|
|
1113
1204
|
this.reconnectDelayMs = Math.min(
|
|
@@ -1116,12 +1207,20 @@ var DriveSocket = class {
|
|
|
1116
1207
|
);
|
|
1117
1208
|
}
|
|
1118
1209
|
async connect() {
|
|
1119
|
-
const url = await
|
|
1210
|
+
const { endpoint, url } = await getWsEndpoint();
|
|
1211
|
+
this.connection.setEndpoint(endpoint);
|
|
1120
1212
|
try {
|
|
1121
1213
|
const socket2 = new WebSocket(url);
|
|
1122
1214
|
this.socket = socket2;
|
|
1123
1215
|
socket2.addEventListener("open", () => {
|
|
1124
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
|
+
}
|
|
1125
1224
|
this.startKeepAlive();
|
|
1126
1225
|
void this.sendHello().catch((error) => {
|
|
1127
1226
|
console.error("DriveSocket hello failed:", error);
|
|
@@ -1131,22 +1230,49 @@ var DriveSocket = class {
|
|
|
1131
1230
|
this.handleMessage(event.data);
|
|
1132
1231
|
});
|
|
1133
1232
|
socket2.addEventListener("close", () => {
|
|
1134
|
-
this.socket
|
|
1135
|
-
this.stopKeepAlive();
|
|
1136
|
-
this.scheduleReconnect();
|
|
1233
|
+
this.handleSocketUnavailable(socket2, "socket closed");
|
|
1137
1234
|
});
|
|
1138
1235
|
socket2.addEventListener("error", () => {
|
|
1139
|
-
this.socket
|
|
1140
|
-
this.stopKeepAlive();
|
|
1141
|
-
this.scheduleReconnect();
|
|
1236
|
+
this.handleSocketUnavailable(socket2, "socket error");
|
|
1142
1237
|
});
|
|
1143
1238
|
} catch (error) {
|
|
1144
|
-
|
|
1239
|
+
this.recordConnectionFailure("connect", error);
|
|
1240
|
+
this.connection.markDisconnected();
|
|
1145
1241
|
this.scheduleReconnect();
|
|
1146
1242
|
}
|
|
1147
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
|
+
}
|
|
1148
1273
|
async sendHello() {
|
|
1149
1274
|
const manifest = chrome.runtime.getManifest();
|
|
1275
|
+
const endpoint = await readCoreEndpointConfig();
|
|
1150
1276
|
let tabs = [];
|
|
1151
1277
|
try {
|
|
1152
1278
|
tabs = await queryTabs();
|
|
@@ -1156,6 +1282,9 @@ var DriveSocket = class {
|
|
|
1156
1282
|
}
|
|
1157
1283
|
const params = {
|
|
1158
1284
|
version: manifest.version,
|
|
1285
|
+
core_host: endpoint.host,
|
|
1286
|
+
core_port: endpoint.port,
|
|
1287
|
+
core_port_source: endpoint.portSource,
|
|
1159
1288
|
tabs
|
|
1160
1289
|
};
|
|
1161
1290
|
this.sendEvent("drive.hello", params);
|
|
@@ -3191,5 +3320,17 @@ chrome.debugger.onDetach.addListener(
|
|
|
3191
3320
|
});
|
|
3192
3321
|
}
|
|
3193
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
|
+
);
|
|
3194
3335
|
socket.start();
|
|
3195
3336
|
//# sourceMappingURL=background.js.map
|