opendevbrowser 0.0.12 → 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 +216 -28
- package/dist/chunk-JVBMT2O5.js +7173 -0
- package/dist/chunk-JVBMT2O5.js.map +1 -0
- package/dist/cli/index.js +2486 -589
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1057 -194
- package/dist/index.js.map +1 -1
- package/dist/opendevbrowser.js +1057 -194
- 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 +1194 -32
- 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 +370 -25
- package/extension/dist/relay-settings.js +1 -0
- 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/manifest.json +17 -3
- package/extension/popup.html +144 -0
- package/package.json +2 -2
- package/skills/AGENTS.md +34 -62
- 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-WTFSMBVH.js +0 -2815
- package/dist/chunk-WTFSMBVH.js.map +0 -1
- package/extension/dist/popup.jsx +0 -150
package/extension/dist/popup.js
CHANGED
|
@@ -1,20 +1,63 @@
|
|
|
1
|
-
import { DEFAULT_AUTO_CONNECT, 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");
|
|
4
5
|
const statusPill = document.getElementById("statusPill");
|
|
5
6
|
const statusNote = document.getElementById("statusNote");
|
|
6
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");
|
|
7
16
|
const relayPortInput = document.getElementById("relayPort");
|
|
8
17
|
const pairingTokenInput = document.getElementById("pairingToken");
|
|
9
18
|
const pairingEnabledInput = document.getElementById("pairingEnabled");
|
|
10
19
|
const autoPairInput = document.getElementById("autoPair");
|
|
11
20
|
const autoConnectInput = document.getElementById("autoConnect");
|
|
12
|
-
|
|
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) {
|
|
13
49
|
throw new Error("Popup DOM missing required elements");
|
|
14
50
|
}
|
|
15
51
|
const defaultNote = "Local relay only. Tokens stay on-device.";
|
|
16
|
-
const
|
|
17
|
-
|
|
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;
|
|
18
61
|
};
|
|
19
62
|
const setStatus = (status) => {
|
|
20
63
|
const isConnected = status === "connected";
|
|
@@ -29,17 +72,147 @@ const setStatus = (status) => {
|
|
|
29
72
|
statusPill.classList.remove("connected");
|
|
30
73
|
}
|
|
31
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;
|
|
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);
|
|
162
|
+
};
|
|
32
163
|
const sendMessage = (message) => {
|
|
33
|
-
return new Promise((resolve) => {
|
|
164
|
+
return new Promise((resolve, reject) => {
|
|
34
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
|
+
}
|
|
35
175
|
resolve(response);
|
|
36
176
|
});
|
|
37
177
|
});
|
|
38
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
|
+
};
|
|
39
194
|
const refreshStatus = async () => {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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;
|
|
43
216
|
};
|
|
44
217
|
const fetchTokenFromPlugin = async (port) => {
|
|
45
218
|
try {
|
|
@@ -51,9 +224,17 @@ const fetchTokenFromPlugin = async (port) => {
|
|
|
51
224
|
return null;
|
|
52
225
|
}
|
|
53
226
|
const data = await response.json();
|
|
54
|
-
|
|
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
|
+
};
|
|
55
235
|
}
|
|
56
|
-
catch {
|
|
236
|
+
catch (error) {
|
|
237
|
+
logError("popup.token_fetch", error, { code: "relay_pair_fetch_failed", extra: { port } });
|
|
57
238
|
return null;
|
|
58
239
|
}
|
|
59
240
|
};
|
|
@@ -84,9 +265,12 @@ const fetchRelayConfig = async (port) => {
|
|
|
84
265
|
return null;
|
|
85
266
|
}
|
|
86
267
|
const pairingRequired = typeof data.pairingRequired === "boolean" ? data.pairingRequired : true;
|
|
87
|
-
|
|
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 };
|
|
88
271
|
}
|
|
89
|
-
catch {
|
|
272
|
+
catch (error) {
|
|
273
|
+
logError("popup.relay_config_fetch", error, { code: "relay_config_fetch_failed", extra: { port } });
|
|
90
274
|
return null;
|
|
91
275
|
}
|
|
92
276
|
};
|
|
@@ -99,12 +283,13 @@ const applyRelayPort = (port) => {
|
|
|
99
283
|
};
|
|
100
284
|
const loadSettings = async () => {
|
|
101
285
|
const data = await new Promise((resolve) => {
|
|
102
|
-
chrome.storage.local.get(["pairingToken", "pairingEnabled", "relayPort", "autoPair", "autoConnect"], (items) => {
|
|
286
|
+
chrome.storage.local.get(["pairingToken", "pairingEnabled", "relayPort", "autoPair", "autoConnect", "nativeEnabled"], (items) => {
|
|
103
287
|
resolve(items);
|
|
104
288
|
});
|
|
105
289
|
});
|
|
106
290
|
const autoPair = typeof data.autoPair === "boolean" ? data.autoPair : DEFAULT_AUTO_PAIR;
|
|
107
291
|
const autoConnect = typeof data.autoConnect === "boolean" ? data.autoConnect : DEFAULT_AUTO_CONNECT;
|
|
292
|
+
const nativeEnabled = typeof data.nativeEnabled === "boolean" ? data.nativeEnabled : DEFAULT_NATIVE_ENABLED;
|
|
108
293
|
const pairingEnabled = typeof data.pairingEnabled === "boolean"
|
|
109
294
|
? data.pairingEnabled
|
|
110
295
|
: DEFAULT_PAIRING_ENABLED;
|
|
@@ -113,6 +298,7 @@ const loadSettings = async () => {
|
|
|
113
298
|
const portValue = parsePort(data.relayPort) ?? DEFAULT_RELAY_PORT;
|
|
114
299
|
autoConnectInput.checked = autoConnect;
|
|
115
300
|
autoPairInput.checked = autoPair;
|
|
301
|
+
nativeEnabledInput.checked = nativeEnabled;
|
|
116
302
|
pairingEnabledInput.checked = pairingEnabled;
|
|
117
303
|
pairingTokenInput.disabled = !pairingEnabled || autoPair;
|
|
118
304
|
pairingTokenInput.value = tokenValue;
|
|
@@ -125,6 +311,9 @@ const loadSettings = async () => {
|
|
|
125
311
|
if (typeof data.autoPair !== "boolean") {
|
|
126
312
|
updates.autoPair = autoPair;
|
|
127
313
|
}
|
|
314
|
+
if (typeof data.nativeEnabled !== "boolean") {
|
|
315
|
+
updates.nativeEnabled = nativeEnabled;
|
|
316
|
+
}
|
|
128
317
|
if (typeof data.pairingEnabled !== "boolean") {
|
|
129
318
|
updates.pairingEnabled = pairingEnabled;
|
|
130
319
|
}
|
|
@@ -132,6 +321,62 @@ const loadSettings = async () => {
|
|
|
132
321
|
chrome.storage.local.set(updates);
|
|
133
322
|
}
|
|
134
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
|
+
};
|
|
135
380
|
const toggle = async () => {
|
|
136
381
|
const isConnected = statusEl.textContent === "Connected";
|
|
137
382
|
setNote();
|
|
@@ -141,32 +386,108 @@ const toggle = async () => {
|
|
|
141
386
|
if (config?.relayPort) {
|
|
142
387
|
applyRelayPort(config.relayPort);
|
|
143
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
|
+
}
|
|
144
396
|
const pairingRequired = config?.pairingRequired ?? true;
|
|
145
397
|
if (pairingRequired) {
|
|
146
398
|
const fetchedToken = await fetchTokenFromPlugin(relayPort);
|
|
147
399
|
if (fetchedToken) {
|
|
148
|
-
pairingTokenInput.value = fetchedToken;
|
|
149
|
-
|
|
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
|
+
});
|
|
150
407
|
}
|
|
151
408
|
else {
|
|
152
409
|
setStatus("disconnected");
|
|
153
|
-
setNote("Auto-pair failed. Start the
|
|
410
|
+
setNote("Auto-pair failed. Start the daemon and retry.");
|
|
154
411
|
setTimeout(() => refreshStatus(), 2000);
|
|
155
412
|
return;
|
|
156
413
|
}
|
|
157
414
|
}
|
|
158
415
|
}
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
}
|
|
164
430
|
};
|
|
165
431
|
toggleButton.addEventListener("click", () => {
|
|
166
|
-
toggle().catch(() => {
|
|
432
|
+
toggle().catch((error) => {
|
|
433
|
+
logError("popup.toggle", error, { code: "toggle_failed" });
|
|
167
434
|
setStatus("disconnected");
|
|
168
435
|
});
|
|
169
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
|
+
});
|
|
170
491
|
autoPairInput.addEventListener("change", () => {
|
|
171
492
|
const enabled = autoPairInput.checked;
|
|
172
493
|
pairingTokenInput.disabled = !pairingEnabledInput.checked || enabled;
|
|
@@ -182,12 +503,19 @@ autoConnectInput.addEventListener("change", () => {
|
|
|
182
503
|
const enabled = autoConnectInput.checked;
|
|
183
504
|
chrome.storage.local.set({ autoConnect: enabled });
|
|
184
505
|
if (enabled && statusEl.textContent !== "Connected") {
|
|
185
|
-
toggle().catch(() => {
|
|
506
|
+
toggle().catch((error) => {
|
|
507
|
+
logError("popup.auto_connect", error, { code: "auto_connect_failed" });
|
|
186
508
|
setStatus("disconnected");
|
|
187
509
|
setNote();
|
|
188
510
|
});
|
|
189
511
|
}
|
|
190
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
|
+
});
|
|
191
519
|
pairingTokenInput.addEventListener("input", () => {
|
|
192
520
|
const value = pairingTokenInput.value.trim();
|
|
193
521
|
chrome.storage.local.set({ pairingToken: value });
|
|
@@ -214,16 +542,33 @@ relayPortInput.addEventListener("input", () => {
|
|
|
214
542
|
chrome.storage.local.set({ relayPort: parsed });
|
|
215
543
|
}
|
|
216
544
|
});
|
|
217
|
-
refreshStatus().catch(() => {
|
|
545
|
+
refreshStatus().catch((error) => {
|
|
546
|
+
logError("popup.refresh_status", error, { code: "status_refresh_failed" });
|
|
218
547
|
setStatus("disconnected");
|
|
219
548
|
setNote();
|
|
220
549
|
});
|
|
221
|
-
loadSettings().catch(() => {
|
|
550
|
+
loadSettings().catch((error) => {
|
|
551
|
+
logError("popup.load_settings", error, { code: "settings_load_failed" });
|
|
222
552
|
autoConnectInput.checked = DEFAULT_AUTO_CONNECT;
|
|
223
553
|
autoPairInput.checked = DEFAULT_AUTO_PAIR;
|
|
554
|
+
nativeEnabledInput.checked = DEFAULT_NATIVE_ENABLED;
|
|
224
555
|
pairingEnabledInput.checked = DEFAULT_PAIRING_ENABLED;
|
|
225
556
|
pairingTokenInput.disabled = !DEFAULT_PAIRING_ENABLED;
|
|
226
557
|
pairingTokenInput.value = DEFAULT_PAIRING_TOKEN || "";
|
|
227
558
|
relayPortInput.value = String(DEFAULT_RELAY_PORT);
|
|
228
559
|
setNote();
|
|
229
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
|
+
});
|
|
574
|
+
});
|