opendevbrowser 0.0.11 → 0.0.15
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/LICENSE +21 -0
- package/README.md +289 -28
- package/dist/chunk-JVBMT2O5.js +7173 -0
- package/dist/chunk-JVBMT2O5.js.map +1 -0
- package/dist/cli/index.js +3690 -275
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1080 -2857
- package/dist/index.js.map +1 -1
- package/dist/opendevbrowser.js +1080 -2857
- package/dist/opendevbrowser.js.map +1 -1
- package/extension/dist/annotate-content.css +237 -0
- package/extension/dist/annotate-content.js +934 -0
- package/extension/dist/background.js +1291 -8
- package/extension/dist/logging.js +50 -0
- package/extension/dist/ops/dom-bridge.js +355 -0
- package/extension/dist/ops/ops-runtime.js +1249 -0
- package/extension/dist/ops/ops-session-store.js +189 -0
- package/extension/dist/ops/redaction.js +52 -0
- package/extension/dist/ops/snapshot-builder.js +4 -0
- package/extension/dist/ops/snapshot-shared.js +220 -0
- package/extension/dist/popup.js +398 -21
- package/extension/dist/relay-settings.js +3 -1
- package/extension/dist/services/CDPRouter.js +501 -103
- package/extension/dist/services/ConnectionManager.js +464 -57
- package/extension/dist/services/NativePortManager.js +182 -0
- package/extension/dist/services/RelayClient.js +227 -26
- package/extension/dist/services/TabManager.js +81 -0
- package/extension/dist/services/TargetSessionMap.js +146 -0
- package/extension/dist/services/cdp-router-commands.js +203 -0
- package/extension/dist/services/url-restrictions.js +41 -0
- package/extension/dist/types.js +3 -1
- package/extension/icons/icon128.png +0 -0
- package/extension/icons/icon16.png +0 -0
- package/extension/icons/icon32.png +0 -0
- package/extension/icons/icon48.png +0 -0
- package/extension/manifest.json +17 -3
- package/extension/popup.html +469 -65
- package/package.json +2 -2
- package/skills/AGENTS.md +34 -61
- package/skills/data-extraction/SKILL.md +95 -103
- package/skills/form-testing/SKILL.md +75 -82
- package/skills/login-automation/SKILL.md +76 -66
- package/skills/opendevbrowser-best-practices/SKILL.md +90 -49
- package/skills/opendevbrowser-continuity-ledger/SKILL.md +57 -23
- package/dist/chunk-R5VUZEUU.js +0 -128
- package/dist/chunk-R5VUZEUU.js.map +0 -1
- package/extension/dist/popup.jsx +0 -150
package/extension/dist/popup.js
CHANGED
|
@@ -1,35 +1,218 @@
|
|
|
1
|
-
import { DEFAULT_AUTO_PAIR, DEFAULT_DISCOVERY_PORT, DEFAULT_PAIRING_ENABLED, DEFAULT_PAIRING_TOKEN, DEFAULT_RELAY_PORT } from "./relay-settings.js";
|
|
1
|
+
import { DEFAULT_AUTO_CONNECT, DEFAULT_AUTO_PAIR, DEFAULT_DISCOVERY_PORT, DEFAULT_NATIVE_ENABLED, DEFAULT_PAIRING_ENABLED, DEFAULT_PAIRING_TOKEN, DEFAULT_RELAY_PORT } from "./relay-settings.js";
|
|
2
|
+
import { logError } from "./logging.js";
|
|
2
3
|
const statusEl = document.getElementById("status");
|
|
3
4
|
const statusIndicator = document.getElementById("statusIndicator");
|
|
5
|
+
const statusPill = document.getElementById("statusPill");
|
|
6
|
+
const statusNote = document.getElementById("statusNote");
|
|
4
7
|
const toggleButton = document.getElementById("toggle");
|
|
8
|
+
const healthRelay = document.getElementById("healthRelay");
|
|
9
|
+
const healthHandshake = document.getElementById("healthHandshake");
|
|
10
|
+
const healthAnnotation = document.getElementById("healthAnnotation");
|
|
11
|
+
const healthInjected = document.getElementById("healthInjected");
|
|
12
|
+
const healthCdp = document.getElementById("healthCdp");
|
|
13
|
+
const healthPairing = document.getElementById("healthPairing");
|
|
14
|
+
const healthNative = document.getElementById("healthNative");
|
|
15
|
+
const healthNote = document.getElementById("healthNote");
|
|
5
16
|
const relayPortInput = document.getElementById("relayPort");
|
|
6
17
|
const pairingTokenInput = document.getElementById("pairingToken");
|
|
7
18
|
const pairingEnabledInput = document.getElementById("pairingEnabled");
|
|
8
19
|
const autoPairInput = document.getElementById("autoPair");
|
|
9
|
-
|
|
20
|
+
const autoConnectInput = document.getElementById("autoConnect");
|
|
21
|
+
const nativeEnabledInput = document.getElementById("nativeEnabled");
|
|
22
|
+
const annotationContextInput = document.getElementById("annotationContext");
|
|
23
|
+
const annotationStartButton = document.getElementById("annotationStart");
|
|
24
|
+
const annotationCopyButton = document.getElementById("annotationCopy");
|
|
25
|
+
const annotationNote = document.getElementById("annotationNote");
|
|
26
|
+
if (!statusEl
|
|
27
|
+
|| !statusIndicator
|
|
28
|
+
|| !statusPill
|
|
29
|
+
|| !statusNote
|
|
30
|
+
|| !toggleButton
|
|
31
|
+
|| !healthRelay
|
|
32
|
+
|| !healthHandshake
|
|
33
|
+
|| !healthAnnotation
|
|
34
|
+
|| !healthInjected
|
|
35
|
+
|| !healthCdp
|
|
36
|
+
|| !healthPairing
|
|
37
|
+
|| !healthNative
|
|
38
|
+
|| !healthNote
|
|
39
|
+
|| !relayPortInput
|
|
40
|
+
|| !pairingTokenInput
|
|
41
|
+
|| !pairingEnabledInput
|
|
42
|
+
|| !autoPairInput
|
|
43
|
+
|| !autoConnectInput
|
|
44
|
+
|| !nativeEnabledInput
|
|
45
|
+
|| !annotationContextInput
|
|
46
|
+
|| !annotationStartButton
|
|
47
|
+
|| !annotationCopyButton
|
|
48
|
+
|| !annotationNote) {
|
|
10
49
|
throw new Error("Popup DOM missing required elements");
|
|
11
50
|
}
|
|
51
|
+
const defaultNote = "Local relay only. Tokens stay on-device.";
|
|
52
|
+
const defaultAnnotationNote = "No annotations captured yet.";
|
|
53
|
+
const LAST_ANNOTATION_META_KEY = "annotationLastMeta";
|
|
54
|
+
const setNote = (message) => {
|
|
55
|
+
const next = message && message.trim() ? message : defaultNote;
|
|
56
|
+
statusNote.textContent = next;
|
|
57
|
+
};
|
|
58
|
+
const setAnnotationNote = (message) => {
|
|
59
|
+
const next = message && message.trim() ? message : defaultAnnotationNote;
|
|
60
|
+
annotationNote.textContent = next;
|
|
61
|
+
};
|
|
12
62
|
const setStatus = (status) => {
|
|
13
63
|
const isConnected = status === "connected";
|
|
14
64
|
statusEl.textContent = isConnected ? "Connected" : "Disconnected";
|
|
15
65
|
toggleButton.textContent = isConnected ? "Disconnect" : "Connect";
|
|
16
66
|
if (isConnected) {
|
|
17
67
|
statusIndicator.classList.add("connected");
|
|
68
|
+
statusPill.classList.add("connected");
|
|
18
69
|
}
|
|
19
70
|
else {
|
|
20
71
|
statusIndicator.classList.remove("connected");
|
|
72
|
+
statusPill.classList.remove("connected");
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const setHealthValue = (element, value, tone) => {
|
|
76
|
+
element.textContent = value;
|
|
77
|
+
element.dataset.tone = tone;
|
|
78
|
+
};
|
|
79
|
+
const formatReason = (reason) => {
|
|
80
|
+
return reason.replace(/_/g, " ");
|
|
81
|
+
};
|
|
82
|
+
const setHealth = (health) => {
|
|
83
|
+
if (!health) {
|
|
84
|
+
setHealthValue(healthRelay, "Unknown", "off");
|
|
85
|
+
setHealthValue(healthHandshake, "Unknown", "off");
|
|
86
|
+
setHealthValue(healthAnnotation, "Unknown", "off");
|
|
87
|
+
setHealthValue(healthInjected, "Unknown", "off");
|
|
88
|
+
setHealthValue(healthCdp, "Unknown", "off");
|
|
89
|
+
setHealthValue(healthPairing, "Unknown", "off");
|
|
90
|
+
healthNote.textContent = "Health check pending.";
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setHealthValue(healthRelay, health.ok ? "Online" : "Offline", health.ok ? "ok" : "warn");
|
|
94
|
+
setHealthValue(healthHandshake, health.extensionHandshakeComplete ? "Complete" : (health.extensionConnected ? "Pending" : "Offline"), health.extensionHandshakeComplete ? "ok" : (health.extensionConnected ? "warn" : "off"));
|
|
95
|
+
setHealthValue(healthAnnotation, health.annotationConnected ? "Connected" : "Idle", health.annotationConnected ? "ok" : "off");
|
|
96
|
+
setHealthValue(healthInjected, "Unknown", "off");
|
|
97
|
+
setHealthValue(healthCdp, health.cdpConnected ? "Active" : "Idle", health.cdpConnected ? "ok" : "off");
|
|
98
|
+
setHealthValue(healthPairing, health.pairingRequired ? "Required" : "Not required", health.pairingRequired ? "warn" : "ok");
|
|
99
|
+
if (health.lastHandshakeError) {
|
|
100
|
+
const detail = health.lastHandshakeError.message ? ` ${health.lastHandshakeError.message}` : "";
|
|
101
|
+
healthNote.textContent = `Last handshake error: ${health.lastHandshakeError.code}.${detail}`;
|
|
102
|
+
}
|
|
103
|
+
else if (!health.ok) {
|
|
104
|
+
healthNote.textContent = `Relay health: ${formatReason(health.reason)}.`;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
healthNote.textContent = "Relay health OK.";
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
const setInjectionStatus = (injected, detail) => {
|
|
111
|
+
if (injected === null) {
|
|
112
|
+
setHealthValue(healthInjected, "Unknown", "off");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (injected) {
|
|
116
|
+
setHealthValue(healthInjected, "Injected", "ok");
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
setHealthValue(healthInjected, "Not injected", "warn");
|
|
120
|
+
if (detail && healthNote.textContent === "Relay health OK.") {
|
|
121
|
+
healthNote.textContent = `Annotation UI: ${detail}`;
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const formatNativeHealth = (health) => {
|
|
125
|
+
if (!health) {
|
|
126
|
+
return { label: "Unknown", tone: "off" };
|
|
127
|
+
}
|
|
128
|
+
if (health.status === "connected") {
|
|
129
|
+
return { label: "Connected", tone: "ok" };
|
|
130
|
+
}
|
|
131
|
+
switch (health.error) {
|
|
132
|
+
case "host_not_installed":
|
|
133
|
+
return { label: "Not installed", tone: "warn", note: "Native host not installed." };
|
|
134
|
+
case "host_forbidden":
|
|
135
|
+
return { label: "Forbidden", tone: "warn", note: "Native host forbidden." };
|
|
136
|
+
case "host_timeout":
|
|
137
|
+
return { label: "Timeout", tone: "warn", note: "Native host timed out." };
|
|
138
|
+
case "host_message_too_large":
|
|
139
|
+
return { label: "Too large", tone: "warn", note: "Native host rejected message size." };
|
|
140
|
+
case "host_disconnect":
|
|
141
|
+
return { label: "Disconnected", tone: "off", note: "Native host disconnected." };
|
|
142
|
+
default:
|
|
143
|
+
return { label: "Unavailable", tone: "off", note: "Native host unavailable." };
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
const setNativeHealth = (health, enabled) => {
|
|
147
|
+
if (!enabled) {
|
|
148
|
+
setHealthValue(healthNative, "Disabled", "off");
|
|
149
|
+
return;
|
|
21
150
|
}
|
|
151
|
+
const formatted = formatNativeHealth(health);
|
|
152
|
+
setHealthValue(healthNative, formatted.label, formatted.tone);
|
|
153
|
+
if (formatted.note && healthNote.textContent === "Relay health OK.") {
|
|
154
|
+
healthNote.textContent = `${healthNote.textContent} ${formatted.note}`;
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const applyStatus = (response) => {
|
|
158
|
+
setStatus(response.status);
|
|
159
|
+
setNote(response.note);
|
|
160
|
+
setHealth(response.relayHealth ?? null);
|
|
161
|
+
setNativeHealth(response.nativeHealth ?? null, response.nativeEnabled === true);
|
|
22
162
|
};
|
|
23
163
|
const sendMessage = (message) => {
|
|
24
|
-
return new Promise((resolve) => {
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
25
165
|
chrome.runtime.sendMessage(message, (response) => {
|
|
166
|
+
const lastError = chrome.runtime.lastError;
|
|
167
|
+
if (lastError) {
|
|
168
|
+
reject(new Error(lastError.message));
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (!response) {
|
|
172
|
+
reject(new Error("No response from background"));
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
26
175
|
resolve(response);
|
|
27
176
|
});
|
|
28
177
|
});
|
|
29
178
|
};
|
|
179
|
+
const setStorage = (items) => {
|
|
180
|
+
return new Promise((resolve) => {
|
|
181
|
+
chrome.storage.local.set(items, () => resolve());
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
const refreshInjectionStatus = async () => {
|
|
185
|
+
try {
|
|
186
|
+
const response = await sendMessage({ type: "annotation:probe" });
|
|
187
|
+
setInjectionStatus(response.injected, response.detail);
|
|
188
|
+
}
|
|
189
|
+
catch (error) {
|
|
190
|
+
logError("popup.annotation_probe", error, { code: "annotation_probe_failed" });
|
|
191
|
+
setInjectionStatus(null);
|
|
192
|
+
}
|
|
193
|
+
};
|
|
30
194
|
const refreshStatus = async () => {
|
|
31
|
-
|
|
32
|
-
|
|
195
|
+
try {
|
|
196
|
+
const response = await sendMessage({ type: "status" });
|
|
197
|
+
applyStatus(response);
|
|
198
|
+
void refreshInjectionStatus();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
logError("popup.status_refresh", error, { code: "status_refresh_failed" });
|
|
202
|
+
setStatus("disconnected");
|
|
203
|
+
const message = error instanceof Error ? error.message : "Background unavailable";
|
|
204
|
+
setNote(message);
|
|
205
|
+
setHealth(null);
|
|
206
|
+
setNativeHealth(null, nativeEnabledInput.checked);
|
|
207
|
+
setInjectionStatus(null);
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
const setCopyEnabled = (enabled) => {
|
|
211
|
+
annotationCopyButton.disabled = !enabled;
|
|
212
|
+
};
|
|
213
|
+
const buildPopupAnnotationOptions = () => {
|
|
214
|
+
const context = annotationContextInput.value.trim();
|
|
215
|
+
return context ? { context } : undefined;
|
|
33
216
|
};
|
|
34
217
|
const fetchTokenFromPlugin = async (port) => {
|
|
35
218
|
try {
|
|
@@ -41,9 +224,17 @@ const fetchTokenFromPlugin = async (port) => {
|
|
|
41
224
|
return null;
|
|
42
225
|
}
|
|
43
226
|
const data = await response.json();
|
|
44
|
-
|
|
227
|
+
if (typeof data.token !== "string") {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
return {
|
|
231
|
+
token: data.token,
|
|
232
|
+
instanceId: typeof data.instanceId === "string" ? data.instanceId : null,
|
|
233
|
+
epoch: typeof data.epoch === "number" ? data.epoch : null
|
|
234
|
+
};
|
|
45
235
|
}
|
|
46
|
-
catch {
|
|
236
|
+
catch (error) {
|
|
237
|
+
logError("popup.token_fetch", error, { code: "relay_pair_fetch_failed", extra: { port } });
|
|
47
238
|
return null;
|
|
48
239
|
}
|
|
49
240
|
};
|
|
@@ -74,9 +265,12 @@ const fetchRelayConfig = async (port) => {
|
|
|
74
265
|
return null;
|
|
75
266
|
}
|
|
76
267
|
const pairingRequired = typeof data.pairingRequired === "boolean" ? data.pairingRequired : true;
|
|
77
|
-
|
|
268
|
+
const instanceId = typeof data.instanceId === "string" ? data.instanceId : null;
|
|
269
|
+
const epoch = typeof data.epoch === "number" ? data.epoch : null;
|
|
270
|
+
return { relayPort, pairingRequired, instanceId, epoch };
|
|
78
271
|
}
|
|
79
|
-
catch {
|
|
272
|
+
catch (error) {
|
|
273
|
+
logError("popup.relay_config_fetch", error, { code: "relay_config_fetch_failed", extra: { port } });
|
|
80
274
|
return null;
|
|
81
275
|
}
|
|
82
276
|
};
|
|
@@ -89,26 +283,37 @@ const applyRelayPort = (port) => {
|
|
|
89
283
|
};
|
|
90
284
|
const loadSettings = async () => {
|
|
91
285
|
const data = await new Promise((resolve) => {
|
|
92
|
-
chrome.storage.local.get(["pairingToken", "pairingEnabled", "relayPort", "autoPair"], (items) => {
|
|
286
|
+
chrome.storage.local.get(["pairingToken", "pairingEnabled", "relayPort", "autoPair", "autoConnect", "nativeEnabled"], (items) => {
|
|
93
287
|
resolve(items);
|
|
94
288
|
});
|
|
95
289
|
});
|
|
96
290
|
const autoPair = typeof data.autoPair === "boolean" ? data.autoPair : DEFAULT_AUTO_PAIR;
|
|
291
|
+
const autoConnect = typeof data.autoConnect === "boolean" ? data.autoConnect : DEFAULT_AUTO_CONNECT;
|
|
292
|
+
const nativeEnabled = typeof data.nativeEnabled === "boolean" ? data.nativeEnabled : DEFAULT_NATIVE_ENABLED;
|
|
97
293
|
const pairingEnabled = typeof data.pairingEnabled === "boolean"
|
|
98
294
|
? data.pairingEnabled
|
|
99
295
|
: DEFAULT_PAIRING_ENABLED;
|
|
100
296
|
const rawToken = typeof data.pairingToken === "string" ? data.pairingToken.trim() : "";
|
|
101
297
|
const tokenValue = pairingEnabled ? (rawToken || DEFAULT_PAIRING_TOKEN || "") : rawToken;
|
|
102
298
|
const portValue = parsePort(data.relayPort) ?? DEFAULT_RELAY_PORT;
|
|
299
|
+
autoConnectInput.checked = autoConnect;
|
|
103
300
|
autoPairInput.checked = autoPair;
|
|
301
|
+
nativeEnabledInput.checked = nativeEnabled;
|
|
104
302
|
pairingEnabledInput.checked = pairingEnabled;
|
|
105
303
|
pairingTokenInput.disabled = !pairingEnabled || autoPair;
|
|
106
304
|
pairingTokenInput.value = tokenValue;
|
|
107
305
|
relayPortInput.value = Number.isInteger(portValue) ? String(portValue) : String(DEFAULT_RELAY_PORT);
|
|
306
|
+
setNote();
|
|
108
307
|
const updates = {};
|
|
308
|
+
if (typeof data.autoConnect !== "boolean") {
|
|
309
|
+
updates.autoConnect = autoConnect;
|
|
310
|
+
}
|
|
109
311
|
if (typeof data.autoPair !== "boolean") {
|
|
110
312
|
updates.autoPair = autoPair;
|
|
111
313
|
}
|
|
314
|
+
if (typeof data.nativeEnabled !== "boolean") {
|
|
315
|
+
updates.nativeEnabled = nativeEnabled;
|
|
316
|
+
}
|
|
112
317
|
if (typeof data.pairingEnabled !== "boolean") {
|
|
113
318
|
updates.pairingEnabled = pairingEnabled;
|
|
114
319
|
}
|
|
@@ -116,38 +321,173 @@ const loadSettings = async () => {
|
|
|
116
321
|
chrome.storage.local.set(updates);
|
|
117
322
|
}
|
|
118
323
|
};
|
|
324
|
+
const formatAnnotationSummary = (meta) => {
|
|
325
|
+
if (meta.status !== "ok") {
|
|
326
|
+
if (meta.error?.message) {
|
|
327
|
+
return `Last annotation ${meta.status}: ${meta.error.message}`;
|
|
328
|
+
}
|
|
329
|
+
return `Last annotation ${meta.status}.`;
|
|
330
|
+
}
|
|
331
|
+
const count = meta.annotationCount ?? 0;
|
|
332
|
+
const target = meta.url ? ` on ${meta.url}` : "";
|
|
333
|
+
return `Last annotation: ${count} item${count === 1 ? "" : "s"}${target}.`;
|
|
334
|
+
};
|
|
335
|
+
const refreshLastAnnotationMeta = async () => {
|
|
336
|
+
try {
|
|
337
|
+
const response = await sendMessage({ type: "annotation:lastMeta" });
|
|
338
|
+
const meta = response.meta;
|
|
339
|
+
if (meta && meta.status === "ok") {
|
|
340
|
+
setCopyEnabled(true);
|
|
341
|
+
setAnnotationNote(formatAnnotationSummary(meta));
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
setCopyEnabled(false);
|
|
345
|
+
if (meta) {
|
|
346
|
+
setAnnotationNote(formatAnnotationSummary(meta));
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
setAnnotationNote();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
catch (error) {
|
|
353
|
+
logError("popup.annotation_meta", error, { code: "annotation_meta_failed" });
|
|
354
|
+
setCopyEnabled(false);
|
|
355
|
+
setAnnotationNote("Annotation status unavailable.");
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
const copyTextToClipboard = async (text) => {
|
|
359
|
+
try {
|
|
360
|
+
await navigator.clipboard.writeText(text);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
catch (error) {
|
|
364
|
+
logError("popup.clipboard_async", error, { code: "clipboard_write_failed" });
|
|
365
|
+
}
|
|
366
|
+
const textarea = document.createElement("textarea");
|
|
367
|
+
textarea.value = text;
|
|
368
|
+
textarea.setAttribute("readonly", "true");
|
|
369
|
+
textarea.style.position = "fixed";
|
|
370
|
+
textarea.style.top = "-1000px";
|
|
371
|
+
textarea.style.opacity = "0";
|
|
372
|
+
document.body.appendChild(textarea);
|
|
373
|
+
textarea.select();
|
|
374
|
+
const ok = document.execCommand("copy");
|
|
375
|
+
document.body.removeChild(textarea);
|
|
376
|
+
if (!ok) {
|
|
377
|
+
throw new Error("Clipboard copy failed");
|
|
378
|
+
}
|
|
379
|
+
};
|
|
119
380
|
const toggle = async () => {
|
|
120
381
|
const isConnected = statusEl.textContent === "Connected";
|
|
382
|
+
setNote();
|
|
121
383
|
if (!isConnected && autoPairInput.checked && pairingEnabledInput.checked) {
|
|
122
384
|
const config = await fetchRelayConfig(DEFAULT_DISCOVERY_PORT);
|
|
123
385
|
const relayPort = config?.relayPort ?? parsePort(relayPortInput.value) ?? DEFAULT_RELAY_PORT;
|
|
124
386
|
if (config?.relayPort) {
|
|
125
387
|
applyRelayPort(config.relayPort);
|
|
126
388
|
}
|
|
389
|
+
if (config?.relayPort || config?.instanceId || config?.epoch) {
|
|
390
|
+
await setStorage({
|
|
391
|
+
relayPort: config?.relayPort ?? relayPort,
|
|
392
|
+
relayInstanceId: config?.instanceId ?? null,
|
|
393
|
+
relayEpoch: config?.epoch ?? null
|
|
394
|
+
});
|
|
395
|
+
}
|
|
127
396
|
const pairingRequired = config?.pairingRequired ?? true;
|
|
128
397
|
if (pairingRequired) {
|
|
129
398
|
const fetchedToken = await fetchTokenFromPlugin(relayPort);
|
|
130
399
|
if (fetchedToken) {
|
|
131
|
-
pairingTokenInput.value = fetchedToken;
|
|
132
|
-
|
|
400
|
+
pairingTokenInput.value = fetchedToken.token;
|
|
401
|
+
await setStorage({
|
|
402
|
+
pairingToken: fetchedToken.token,
|
|
403
|
+
tokenEpoch: fetchedToken.epoch ?? config?.epoch ?? null,
|
|
404
|
+
relayInstanceId: config?.instanceId ?? fetchedToken.instanceId ?? null,
|
|
405
|
+
relayEpoch: config?.epoch ?? fetchedToken.epoch ?? null
|
|
406
|
+
});
|
|
133
407
|
}
|
|
134
408
|
else {
|
|
135
|
-
|
|
409
|
+
setStatus("disconnected");
|
|
410
|
+
setNote("Auto-pair failed. Start the daemon and retry.");
|
|
136
411
|
setTimeout(() => refreshStatus(), 2000);
|
|
137
412
|
return;
|
|
138
413
|
}
|
|
139
414
|
}
|
|
140
415
|
}
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
416
|
+
try {
|
|
417
|
+
const response = await sendMessage({
|
|
418
|
+
type: isConnected ? "disconnect" : "connect"
|
|
419
|
+
});
|
|
420
|
+
applyStatus(response);
|
|
421
|
+
void refreshInjectionStatus();
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
logError("popup.toggle", error, { code: "toggle_failed" });
|
|
425
|
+
setStatus("disconnected");
|
|
426
|
+
const message = error instanceof Error ? error.message : "Background unavailable";
|
|
427
|
+
setNote(message);
|
|
428
|
+
setHealth(null);
|
|
429
|
+
}
|
|
145
430
|
};
|
|
146
431
|
toggleButton.addEventListener("click", () => {
|
|
147
|
-
toggle().catch(() => {
|
|
432
|
+
toggle().catch((error) => {
|
|
433
|
+
logError("popup.toggle", error, { code: "toggle_failed" });
|
|
148
434
|
setStatus("disconnected");
|
|
149
435
|
});
|
|
150
436
|
});
|
|
437
|
+
annotationStartButton.addEventListener("click", () => {
|
|
438
|
+
(async () => {
|
|
439
|
+
setAnnotationNote("Starting annotation...");
|
|
440
|
+
const options = buildPopupAnnotationOptions();
|
|
441
|
+
const response = await sendMessage({
|
|
442
|
+
type: "annotation:start",
|
|
443
|
+
options
|
|
444
|
+
});
|
|
445
|
+
if (!response.ok) {
|
|
446
|
+
const message = response.error?.message ?? "Annotation start failed.";
|
|
447
|
+
setAnnotationNote(message);
|
|
448
|
+
setCopyEnabled(false);
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
setAnnotationNote("Annotation started. Switch to the tab to select elements.");
|
|
452
|
+
setCopyEnabled(false);
|
|
453
|
+
void refreshInjectionStatus();
|
|
454
|
+
})().catch((error) => {
|
|
455
|
+
const message = error instanceof Error ? error.message : "Annotation start failed.";
|
|
456
|
+
setAnnotationNote(message);
|
|
457
|
+
setCopyEnabled(false);
|
|
458
|
+
});
|
|
459
|
+
});
|
|
460
|
+
annotationCopyButton.addEventListener("click", () => {
|
|
461
|
+
(async () => {
|
|
462
|
+
setAnnotationNote("Preparing annotation payload...");
|
|
463
|
+
let response = await sendMessage({
|
|
464
|
+
type: "annotation:getPayload",
|
|
465
|
+
includeScreenshots: true
|
|
466
|
+
});
|
|
467
|
+
if (!response.payload) {
|
|
468
|
+
response = await sendMessage({
|
|
469
|
+
type: "annotation:getPayload",
|
|
470
|
+
includeScreenshots: false
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
if (!response.payload) {
|
|
474
|
+
setAnnotationNote("No completed annotation payload available.");
|
|
475
|
+
setCopyEnabled(false);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
await copyTextToClipboard(JSON.stringify(response.payload, null, 2));
|
|
479
|
+
if (response.warning) {
|
|
480
|
+
setAnnotationNote(`Copied payload (${response.warning.replace(/\.$/, "")}).`);
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
setAnnotationNote("Copied annotation payload to clipboard.");
|
|
484
|
+
}
|
|
485
|
+
await refreshLastAnnotationMeta();
|
|
486
|
+
})().catch((error) => {
|
|
487
|
+
const message = error instanceof Error ? error.message : "Copy failed.";
|
|
488
|
+
setAnnotationNote(message);
|
|
489
|
+
});
|
|
490
|
+
});
|
|
151
491
|
autoPairInput.addEventListener("change", () => {
|
|
152
492
|
const enabled = autoPairInput.checked;
|
|
153
493
|
pairingTokenInput.disabled = !pairingEnabledInput.checked || enabled;
|
|
@@ -156,9 +496,26 @@ autoPairInput.addEventListener("change", () => {
|
|
|
156
496
|
pairingTokenInput.placeholder = "Will be fetched automatically";
|
|
157
497
|
}
|
|
158
498
|
else {
|
|
159
|
-
pairingTokenInput.placeholder = "Enter token or enable
|
|
499
|
+
pairingTokenInput.placeholder = "Enter token or enable auto-pair";
|
|
500
|
+
}
|
|
501
|
+
});
|
|
502
|
+
autoConnectInput.addEventListener("change", () => {
|
|
503
|
+
const enabled = autoConnectInput.checked;
|
|
504
|
+
chrome.storage.local.set({ autoConnect: enabled });
|
|
505
|
+
if (enabled && statusEl.textContent !== "Connected") {
|
|
506
|
+
toggle().catch((error) => {
|
|
507
|
+
logError("popup.auto_connect", error, { code: "auto_connect_failed" });
|
|
508
|
+
setStatus("disconnected");
|
|
509
|
+
setNote();
|
|
510
|
+
});
|
|
160
511
|
}
|
|
161
512
|
});
|
|
513
|
+
nativeEnabledInput.addEventListener("change", () => {
|
|
514
|
+
const enabled = nativeEnabledInput.checked;
|
|
515
|
+
chrome.storage.local.set({ nativeEnabled: enabled });
|
|
516
|
+
setNativeHealth(null, enabled);
|
|
517
|
+
void refreshStatus();
|
|
518
|
+
});
|
|
162
519
|
pairingTokenInput.addEventListener("input", () => {
|
|
163
520
|
const value = pairingTokenInput.value.trim();
|
|
164
521
|
chrome.storage.local.set({ pairingToken: value });
|
|
@@ -185,13 +542,33 @@ relayPortInput.addEventListener("input", () => {
|
|
|
185
542
|
chrome.storage.local.set({ relayPort: parsed });
|
|
186
543
|
}
|
|
187
544
|
});
|
|
188
|
-
refreshStatus().catch(() => {
|
|
545
|
+
refreshStatus().catch((error) => {
|
|
546
|
+
logError("popup.refresh_status", error, { code: "status_refresh_failed" });
|
|
189
547
|
setStatus("disconnected");
|
|
548
|
+
setNote();
|
|
190
549
|
});
|
|
191
|
-
loadSettings().catch(() => {
|
|
550
|
+
loadSettings().catch((error) => {
|
|
551
|
+
logError("popup.load_settings", error, { code: "settings_load_failed" });
|
|
552
|
+
autoConnectInput.checked = DEFAULT_AUTO_CONNECT;
|
|
192
553
|
autoPairInput.checked = DEFAULT_AUTO_PAIR;
|
|
554
|
+
nativeEnabledInput.checked = DEFAULT_NATIVE_ENABLED;
|
|
193
555
|
pairingEnabledInput.checked = DEFAULT_PAIRING_ENABLED;
|
|
194
556
|
pairingTokenInput.disabled = !DEFAULT_PAIRING_ENABLED;
|
|
195
557
|
pairingTokenInput.value = DEFAULT_PAIRING_TOKEN || "";
|
|
196
558
|
relayPortInput.value = String(DEFAULT_RELAY_PORT);
|
|
559
|
+
setNote();
|
|
560
|
+
});
|
|
561
|
+
refreshLastAnnotationMeta().catch((error) => {
|
|
562
|
+
logError("popup.annotation_meta", error, { code: "annotation_meta_failed" });
|
|
563
|
+
setCopyEnabled(false);
|
|
564
|
+
setAnnotationNote();
|
|
565
|
+
});
|
|
566
|
+
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
567
|
+
if (areaName !== "local")
|
|
568
|
+
return;
|
|
569
|
+
if (!changes[LAST_ANNOTATION_META_KEY])
|
|
570
|
+
return;
|
|
571
|
+
refreshLastAnnotationMeta().catch((error) => {
|
|
572
|
+
logError("popup.annotation_meta", error, { code: "annotation_meta_failed" });
|
|
573
|
+
});
|
|
197
574
|
});
|
|
@@ -2,4 +2,6 @@ export const DEFAULT_RELAY_PORT = 8787;
|
|
|
2
2
|
export const DEFAULT_DISCOVERY_PORT = 8787;
|
|
3
3
|
export const DEFAULT_PAIRING_TOKEN = null;
|
|
4
4
|
export const DEFAULT_PAIRING_ENABLED = true;
|
|
5
|
-
export const DEFAULT_AUTO_PAIR =
|
|
5
|
+
export const DEFAULT_AUTO_PAIR = true;
|
|
6
|
+
export const DEFAULT_AUTO_CONNECT = true;
|
|
7
|
+
export const DEFAULT_NATIVE_ENABLED = false;
|