@jobshimo/browser-link 0.43.0 → 0.44.1

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.
Files changed (59) hide show
  1. package/README.md +26 -2
  2. package/dist/bridge/ws-bridge.d.ts +6 -2
  3. package/dist/bridge/ws-bridge.js +34 -27
  4. package/dist/bridge/ws-bridge.js.map +1 -1
  5. package/dist/cdp/inpage/observer.d.ts +2 -2
  6. package/dist/cdp/inpage/observer.js +11 -4
  7. package/dist/cdp/inpage/observer.js.map +1 -1
  8. package/dist/cli.d.ts +6 -0
  9. package/dist/cli.js +32 -7
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/config.d.ts +15 -0
  12. package/dist/commands/config.js +55 -5
  13. package/dist/commands/config.js.map +1 -1
  14. package/dist/commands/doctor.d.ts +20 -1
  15. package/dist/commands/doctor.js +41 -19
  16. package/dist/commands/doctor.js.map +1 -1
  17. package/dist/config.d.ts +20 -0
  18. package/dist/config.js +20 -0
  19. package/dist/config.js.map +1 -1
  20. package/dist/downloads.d.ts +10 -3
  21. package/dist/downloads.js +23 -4
  22. package/dist/downloads.js.map +1 -1
  23. package/dist/extension/activity.html +30 -4
  24. package/dist/extension/admin.d.ts +1 -0
  25. package/dist/extension/admin.html +290 -0
  26. package/dist/extension/admin.js +210 -0
  27. package/dist/extension/admin.js.map +1 -0
  28. package/dist/extension/background.js +41 -0
  29. package/dist/extension/background.js.map +1 -1
  30. package/dist/extension/inpage/observer.d.ts +2 -2
  31. package/dist/extension/inpage/observer.js +11 -4
  32. package/dist/extension/inpage/observer.js.map +1 -1
  33. package/dist/extension/manifest.json +1 -1
  34. package/dist/extension/popup.html +28 -119
  35. package/dist/extension/popup.js +12 -205
  36. package/dist/extension/popup.js.map +1 -1
  37. package/dist/lifecycle/election.d.ts +16 -5
  38. package/dist/lifecycle/election.js +21 -4
  39. package/dist/lifecycle/election.js.map +1 -1
  40. package/dist/lifecycle/processes.d.ts +18 -0
  41. package/dist/lifecycle/processes.js +40 -7
  42. package/dist/lifecycle/processes.js.map +1 -1
  43. package/dist/lifecycle/repair.d.ts +15 -3
  44. package/dist/lifecycle/repair.js +41 -8
  45. package/dist/lifecycle/repair.js.map +1 -1
  46. package/dist/lifecycle/version-endpoint.d.ts +8 -3
  47. package/dist/lifecycle/version-endpoint.js +19 -4
  48. package/dist/lifecycle/version-endpoint.js.map +1 -1
  49. package/dist/manual/refresh.js +6 -0
  50. package/dist/manual/refresh.js.map +1 -1
  51. package/dist/permissions.js +6 -0
  52. package/dist/permissions.js.map +1 -1
  53. package/dist/server.js +8 -4
  54. package/dist/server.js.map +1 -1
  55. package/dist/tools/browser-definitions.js +6 -1
  56. package/dist/tools/browser-definitions.js.map +1 -1
  57. package/dist/tools/browser-dispatch.js +28 -4
  58. package/dist/tools/browser-dispatch.js.map +1 -1
  59. package/package.json +1 -1
@@ -392,16 +392,37 @@
392
392
 
393
393
  /* ---------- table ---------- */
394
394
 
395
+ /* A horizontal bar only when the window is genuinely narrower than the
396
+ * table's minimum: the fixed layout below sizes columns from these
397
+ * widths, not from the longest cell, so ordinary rows always fit. */
395
398
  .scroll {
396
399
  flex: 1 1 auto;
397
- overflow: auto;
400
+ min-width: 0;
401
+ overflow-x: auto;
402
+ overflow-y: auto;
398
403
  }
399
404
 
400
405
  table {
406
+ table-layout: fixed;
401
407
  width: 100%;
408
+ min-width: 760px;
402
409
  border-collapse: collapse;
403
410
  }
404
411
 
412
+ /* When · Agent · Tool · Target · Result — Target takes the rest. */
413
+ thead th:nth-child(1) {
414
+ width: 112px;
415
+ }
416
+ thead th:nth-child(2) {
417
+ width: 150px;
418
+ }
419
+ thead th:nth-child(3) {
420
+ width: 200px;
421
+ }
422
+ thead th:nth-child(5) {
423
+ width: 190px;
424
+ }
425
+
405
426
  thead th {
406
427
  position: sticky;
407
428
  top: 0;
@@ -432,9 +453,13 @@
432
453
  background: var(--error-soft);
433
454
  }
434
455
 
456
+ /* The exact time is in the cell's title, so a long locale format may
457
+ * end in an ellipsis rather than widen the column. */
435
458
  .when {
436
459
  color: var(--text-muted);
437
460
  white-space: nowrap;
461
+ overflow: hidden;
462
+ text-overflow: ellipsis;
438
463
  font-variant-numeric: tabular-nums;
439
464
  }
440
465
 
@@ -446,12 +471,13 @@
446
471
  font-weight: 600;
447
472
  background: var(--brand-ring);
448
473
  color: var(--text);
449
- white-space: nowrap;
474
+ max-width: 100%;
475
+ overflow-wrap: anywhere;
450
476
  }
451
477
 
452
478
  .tool {
453
479
  font-weight: 600;
454
- white-space: nowrap;
480
+ overflow-wrap: anywhere;
455
481
  }
456
482
 
457
483
  .target,
@@ -477,8 +503,8 @@
477
503
  }
478
504
 
479
505
  .outcome {
480
- white-space: nowrap;
481
506
  font-variant-numeric: tabular-nums;
507
+ overflow-wrap: anywhere;
482
508
  }
483
509
 
484
510
  .outcome.ok {
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,290 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>browser-link — Admin</title>
7
+ <style>
8
+ /* Same slate-blue system as popup.html, with the Activity window's dark
9
+ * variant: this window, like Activity, stays open on screen. Only the
10
+ * tokens change between themes — every rule below is written once. */
11
+ :root {
12
+ --brand-from: #5b8def;
13
+ --brand-to: #2a5fd6;
14
+ --brand-ring: rgba(29, 78, 216, 0.32);
15
+
16
+ --bg: #ffffff;
17
+ --bg-soft: #f8fafc;
18
+ --bg-raised: #ffffff;
19
+ --border: #e2e8f0;
20
+ --border-soft: #eef2f7;
21
+
22
+ --text: #0f172a;
23
+ --text-muted: #64748b;
24
+ --text-faint: #94a3b8;
25
+
26
+ --error-fg: #b91c1c;
27
+ --error-soft: #fef2f2;
28
+ }
29
+
30
+ @media (prefers-color-scheme: dark) {
31
+ :root {
32
+ --bg: #0b1120;
33
+ --bg-soft: #111a2e;
34
+ --bg-raised: #131d33;
35
+ --border: #1e293b;
36
+ --border-soft: #172033;
37
+
38
+ --text: #e2e8f0;
39
+ --text-muted: #94a3b8;
40
+ --text-faint: #64748b;
41
+
42
+ --error-fg: #f87171;
43
+ --error-soft: rgba(239, 68, 68, 0.12);
44
+ }
45
+ }
46
+
47
+ * {
48
+ box-sizing: border-box;
49
+ }
50
+
51
+ html,
52
+ body {
53
+ margin: 0;
54
+ padding: 0;
55
+ font-family:
56
+ ui-sans-serif,
57
+ -apple-system,
58
+ BlinkMacSystemFont,
59
+ 'Inter',
60
+ 'Segoe UI',
61
+ system-ui,
62
+ sans-serif;
63
+ font-size: 13px;
64
+ color: var(--text);
65
+ background: var(--bg);
66
+ -webkit-font-smoothing: antialiased;
67
+ }
68
+
69
+ header {
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 12px;
73
+ padding: 14px 18px;
74
+ border-bottom: 1px solid var(--border);
75
+ background: var(--bg-raised);
76
+ }
77
+
78
+ .mark {
79
+ width: 26px;
80
+ height: 26px;
81
+ border-radius: 8px;
82
+ background: linear-gradient(135deg, var(--brand-from), var(--brand-to));
83
+ box-shadow: 0 0 0 3px var(--brand-ring);
84
+ flex: 0 0 auto;
85
+ }
86
+
87
+ .titles {
88
+ display: flex;
89
+ flex-direction: column;
90
+ gap: 1px;
91
+ }
92
+
93
+ h1 {
94
+ margin: 0;
95
+ font-size: 14px;
96
+ font-weight: 650;
97
+ letter-spacing: -0.01em;
98
+ }
99
+
100
+ .subtitle {
101
+ font-size: 11.5px;
102
+ color: var(--text-muted);
103
+ }
104
+
105
+ main {
106
+ padding: 6px 18px 18px;
107
+ }
108
+
109
+ button {
110
+ width: 100%;
111
+ padding: 10px 12px;
112
+ border: none;
113
+ border-radius: 8px;
114
+ cursor: pointer;
115
+ font: inherit;
116
+ font-weight: 600;
117
+ }
118
+
119
+ button.primary {
120
+ background: linear-gradient(160deg, var(--brand-from) 0%, var(--brand-to) 100%);
121
+ color: #ffffff;
122
+ box-shadow: 0 6px 14px var(--brand-ring);
123
+ }
124
+
125
+ button.danger {
126
+ background: var(--bg);
127
+ color: var(--error-fg);
128
+ box-shadow: inset 0 0 0 1px var(--error-fg);
129
+ }
130
+
131
+ button.danger:hover:not(:disabled) {
132
+ background: var(--error-soft);
133
+ }
134
+
135
+ button:disabled {
136
+ opacity: 0.55;
137
+ cursor: progress;
138
+ }
139
+
140
+ button:focus-visible,
141
+ select:focus-visible,
142
+ input:focus-visible {
143
+ outline: 2px solid var(--brand-from);
144
+ outline-offset: 1px;
145
+ }
146
+
147
+ .dialog-actions {
148
+ display: grid;
149
+ grid-template-columns: 1fr 1fr;
150
+ gap: 8px;
151
+ }
152
+
153
+ .flows-title {
154
+ font-size: 10.5px;
155
+ font-weight: 600;
156
+ letter-spacing: 0.06em;
157
+ text-transform: uppercase;
158
+ color: var(--text-faint);
159
+ margin-bottom: 6px;
160
+ }
161
+
162
+ /* Admin rows — rendered generically from the server's AdminState
163
+ * (admin-view.ts); every label inside comes from the server. */
164
+ .admin-row {
165
+ display: flex;
166
+ align-items: center;
167
+ justify-content: space-between;
168
+ gap: 12px;
169
+ padding: 7px 0;
170
+ border-top: 1px solid var(--border-soft);
171
+ font-size: 12.5px;
172
+ color: var(--text-muted);
173
+ }
174
+
175
+ .admin-row-label {
176
+ min-width: 0;
177
+ }
178
+
179
+ .admin-row-value {
180
+ color: var(--text);
181
+ text-align: right;
182
+ overflow-wrap: anywhere;
183
+ }
184
+
185
+ a.admin-row-value {
186
+ color: var(--brand-to);
187
+ }
188
+
189
+ .admin-row select,
190
+ .admin-row button {
191
+ width: auto;
192
+ font-family: inherit;
193
+ font-size: 12px;
194
+ font-weight: 500;
195
+ color: var(--text);
196
+ background: var(--bg-soft);
197
+ border: 1px solid var(--border);
198
+ border-radius: 6px;
199
+ padding: 5px 8px;
200
+ cursor: pointer;
201
+ }
202
+
203
+ .admin-row button:hover:not(:disabled),
204
+ .admin-row select:hover:not(:disabled) {
205
+ border-color: var(--brand-from);
206
+ }
207
+
208
+ .admin-row input[type='checkbox'] {
209
+ width: 15px;
210
+ height: 15px;
211
+ cursor: pointer;
212
+ accent-color: var(--brand-to);
213
+ }
214
+
215
+ .admin-note {
216
+ padding-bottom: 4px;
217
+ font-size: 11.5px;
218
+ color: var(--text-faint);
219
+ overflow-wrap: anywhere;
220
+ }
221
+
222
+ .admin-section-title {
223
+ margin-top: 16px;
224
+ }
225
+
226
+ .admin-status {
227
+ margin-top: 12px;
228
+ color: var(--text);
229
+ }
230
+
231
+ .admin-status[data-kind='error'] {
232
+ color: var(--error-fg);
233
+ }
234
+
235
+ .admin-confirm {
236
+ margin-top: 12px;
237
+ padding: 12px 14px;
238
+ border: 1px solid var(--border);
239
+ border-radius: 10px;
240
+ background: var(--bg-soft);
241
+ }
242
+
243
+ .admin-confirm-preview {
244
+ margin-bottom: 10px;
245
+ color: var(--text);
246
+ overflow-wrap: anywhere;
247
+ }
248
+
249
+ .admin-output {
250
+ margin: 8px 0 0;
251
+ padding: 8px 10px;
252
+ max-height: 240px;
253
+ overflow: auto;
254
+ font-size: 11.5px;
255
+ background: var(--bg-soft);
256
+ border: 1px solid var(--border);
257
+ border-radius: 6px;
258
+ white-space: pre-wrap;
259
+ }
260
+ </style>
261
+ </head>
262
+ <body>
263
+ <header>
264
+ <div class="mark" aria-hidden="true"></div>
265
+ <div class="titles">
266
+ <h1>Admin</h1>
267
+ <span class="subtitle">browser-link server settings · same as the console</span>
268
+ </div>
269
+ </header>
270
+
271
+ <main>
272
+ <!-- Rendered by admin.ts from the server's AdminState (admin.describe
273
+ over any connected tab's socket): the same sections, options and
274
+ labels as the console. Disk-writing actions show the server's
275
+ preview with Confirm/Cancel before applying. -->
276
+ <div id="admin-confirm" class="admin-confirm" hidden>
277
+ <div id="admin-confirm-preview" class="admin-confirm-preview"></div>
278
+ <div class="dialog-actions">
279
+ <button id="admin-confirm-yes" class="primary" type="button">Confirm</button>
280
+ <button id="admin-confirm-no" class="danger" type="button">Cancel</button>
281
+ </div>
282
+ </div>
283
+ <div id="admin-status" class="admin-status" hidden></div>
284
+ <pre id="admin-output" class="admin-output" hidden></pre>
285
+ <div id="admin-body"></div>
286
+ </main>
287
+
288
+ <script type="module" src="admin.js"></script>
289
+ </body>
290
+ </html>
@@ -0,0 +1,210 @@
1
+ import { FALLBACK_ADMIN_UI, adminUi, buildAdminRows, describeAdminError, initialConfirmState, reduceConfirm, } from './admin-view.js';
2
+ // chrome.runtime.sendMessage returns `Promise<any>`; re-bind through
3
+ // `unknown` so the final `as T` is an explicit, auditable narrowing.
4
+ async function send(payload) {
5
+ const raw = await chrome.runtime.sendMessage(payload);
6
+ return raw;
7
+ }
8
+ function $(id) {
9
+ const el = document.getElementById(id);
10
+ if (!el)
11
+ throw new Error(`Element not found: ${id}`);
12
+ return el;
13
+ }
14
+ let adminConfirm = initialConfirmState();
15
+ let adminStrings = FALLBACK_ADMIN_UI;
16
+ let adminBusy = false;
17
+ function renderAdminEmpty(message) {
18
+ const body = $('admin-body');
19
+ body.replaceChildren();
20
+ const empty = document.createElement('div');
21
+ empty.className = 'admin-note';
22
+ empty.textContent = message;
23
+ body.appendChild(empty);
24
+ }
25
+ function adminControlFor(row) {
26
+ switch (row.control) {
27
+ case 'toggle': {
28
+ const input = document.createElement('input');
29
+ input.type = 'checkbox';
30
+ input.checked = row.checked === true;
31
+ input.disabled = row.disabled === true || adminBusy;
32
+ input.setAttribute('aria-label', row.label);
33
+ input.addEventListener('change', () => {
34
+ void runAdminAction({ id: row.itemId, value: input.checked });
35
+ });
36
+ return input;
37
+ }
38
+ case 'select': {
39
+ const select = document.createElement('select');
40
+ select.setAttribute('aria-label', row.label);
41
+ for (const option of row.options ?? []) {
42
+ const el = document.createElement('option');
43
+ el.value = option.value;
44
+ el.textContent = option.label;
45
+ select.appendChild(el);
46
+ }
47
+ select.value = row.selected ?? '';
48
+ select.disabled = row.disabled === true || adminBusy;
49
+ select.addEventListener('change', () => {
50
+ void runAdminAction({ id: row.itemId, value: select.value });
51
+ });
52
+ return select;
53
+ }
54
+ case 'button': {
55
+ const button = document.createElement('button');
56
+ button.type = 'button';
57
+ button.textContent = row.label;
58
+ button.disabled = row.disabled === true || adminBusy;
59
+ button.addEventListener('click', () => {
60
+ void runAdminAction({ id: row.itemId });
61
+ });
62
+ return button;
63
+ }
64
+ case 'link': {
65
+ // admin-view.ts only sets `control: 'link'` for an http(s) URL.
66
+ const link = document.createElement('a');
67
+ link.href = row.href ?? '';
68
+ link.target = '_blank';
69
+ link.rel = 'noopener noreferrer';
70
+ link.className = 'admin-row-value';
71
+ link.textContent = row.valueText;
72
+ return link;
73
+ }
74
+ default: {
75
+ const value = document.createElement('span');
76
+ value.className = 'admin-row-value';
77
+ value.textContent = row.valueText;
78
+ return value;
79
+ }
80
+ }
81
+ }
82
+ function renderAdminRows(rows) {
83
+ const body = $('admin-body');
84
+ body.replaceChildren();
85
+ let section = null;
86
+ for (const row of rows) {
87
+ if (row.sectionId !== section) {
88
+ section = row.sectionId;
89
+ const title = document.createElement('div');
90
+ title.className = 'flows-title admin-section-title';
91
+ title.textContent = row.sectionTitle;
92
+ body.appendChild(title);
93
+ }
94
+ const line = document.createElement('div');
95
+ line.className = 'admin-row';
96
+ // A button's label is its own text; every other kind gets a label span.
97
+ if (row.control !== 'button') {
98
+ const label = document.createElement('span');
99
+ label.className = 'admin-row-label';
100
+ label.textContent = row.label;
101
+ line.appendChild(label);
102
+ }
103
+ line.appendChild(adminControlFor(row));
104
+ body.appendChild(line);
105
+ // Description, console hint and restart flag stay visible under the row
106
+ // rather than in a tooltip: hover discovery hides what a setting does.
107
+ const notes = [
108
+ row.description,
109
+ row.hint,
110
+ row.requiresRestart === true ? adminStrings.restartRequired : undefined,
111
+ ].filter((note) => typeof note === 'string' && note !== '');
112
+ for (const note of notes) {
113
+ const el = document.createElement('div');
114
+ el.className = 'admin-note';
115
+ el.textContent = note;
116
+ body.appendChild(el);
117
+ }
118
+ }
119
+ }
120
+ function renderAdminConfirm() {
121
+ const confirmBox = $('admin-confirm');
122
+ const status = $('admin-status');
123
+ const output = $('admin-output');
124
+ confirmBox.hidden = adminConfirm.phase !== 'confirming';
125
+ status.hidden = true;
126
+ output.hidden = true;
127
+ if (adminConfirm.phase === 'confirming') {
128
+ $('admin-confirm-preview').textContent = adminConfirm.preview;
129
+ $('admin-confirm-yes').textContent = adminStrings.confirm;
130
+ $('admin-confirm-no').textContent = adminStrings.cancel;
131
+ }
132
+ else if (adminConfirm.phase === 'applied' || adminConfirm.phase === 'error') {
133
+ status.hidden = false;
134
+ status.dataset.kind = adminConfirm.phase;
135
+ status.textContent = adminConfirm.message;
136
+ if (adminConfirm.phase === 'applied' && adminConfirm.output) {
137
+ output.hidden = false;
138
+ output.textContent = adminConfirm.output.join('\n');
139
+ }
140
+ }
141
+ }
142
+ /** Describe the server and render it. Called when the window loads and after
143
+ * every action that is not waiting for a confirmation. */
144
+ async function refreshAdmin() {
145
+ let reply;
146
+ try {
147
+ reply = await send({ action: 'adminDescribe' });
148
+ }
149
+ catch {
150
+ // Service worker asleep or mid-restart: same empty state as no socket.
151
+ renderAdminEmpty(adminStrings.notConnected);
152
+ return;
153
+ }
154
+ if (!reply) {
155
+ renderAdminEmpty(adminStrings.notConnected);
156
+ return;
157
+ }
158
+ if (!reply.ok) {
159
+ renderAdminEmpty(describeAdminError(reply.error, adminStrings));
160
+ return;
161
+ }
162
+ adminStrings = adminUi(reply.state);
163
+ document.documentElement.lang = adminStrings.language;
164
+ renderAdminRows(buildAdminRows(reply.state));
165
+ renderAdminConfirm();
166
+ }
167
+ async function runAdminAction(request) {
168
+ if (adminBusy)
169
+ return;
170
+ adminBusy = true;
171
+ const status = $('admin-status');
172
+ status.hidden = false;
173
+ status.dataset.kind = 'busy';
174
+ status.textContent = '…';
175
+ $('admin-confirm').hidden = true;
176
+ $('admin-output').hidden = true;
177
+ let result;
178
+ try {
179
+ const reply = await send({
180
+ action: 'adminAction',
181
+ ...request,
182
+ });
183
+ result = reply?.result ?? { ok: false, error: 'not-connected' };
184
+ }
185
+ catch (err) {
186
+ result = { ok: false, error: err instanceof Error ? err.message : String(err) };
187
+ }
188
+ adminBusy = false;
189
+ adminConfirm = reduceConfirm(adminConfirm, { type: 'result', request, result, ui: adminStrings });
190
+ renderAdminConfirm();
191
+ // A confirming toggle or select already shows the new value; leave it until
192
+ // the human decides. Every other outcome re-describes, so an applied change
193
+ // shows the server's new truth and a failed one snaps the control back.
194
+ if (adminConfirm.phase !== 'confirming')
195
+ await refreshAdmin();
196
+ }
197
+ document.getElementById('admin-confirm-yes')?.addEventListener('click', () => {
198
+ if (adminConfirm.phase !== 'confirming')
199
+ return;
200
+ void runAdminAction(adminConfirm.pendingRequest);
201
+ });
202
+ document.getElementById('admin-confirm-no')?.addEventListener('click', () => {
203
+ adminConfirm = reduceConfirm(adminConfirm, { type: 'cancel' });
204
+ renderAdminConfirm();
205
+ void refreshAdmin();
206
+ });
207
+ refreshAdmin().catch(() => {
208
+ renderAdminEmpty(adminStrings.notConnected);
209
+ });
210
+ //# sourceMappingURL=admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.js","sourceRoot":"","sources":["../src/admin.ts"],"names":[],"mappings":"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"}
@@ -154,6 +154,8 @@ function isRuntimeMessage(msg) {
154
154
  return true;
155
155
  if (m.action === 'openActivityWindow')
156
156
  return true;
157
+ if (m.action === 'openAdminWindow')
158
+ return true;
157
159
  if (m.action === 'activityQuery')
158
160
  return true;
159
161
  if (m.action === 'activityPurge')
@@ -357,6 +359,41 @@ chrome.windows.onRemoved.addListener((closedId) => {
357
359
  if (closedId === activityWindowId)
358
360
  activityWindowId = null;
359
361
  });
362
+ /** Chrome window geometry for the Admin panel: a form of labelled rows, so
363
+ * narrower than the Activity table but tall enough for every section. */
364
+ const ADMIN_WINDOW = { width: 560, height: 720 };
365
+ /**
366
+ * Open the Admin window, or focus it if it is already open. Same contract as
367
+ * `openActivityWindow`: a dedicated window rather than a section of the popup,
368
+ * which closes the moment it loses focus and is too narrow for the form.
369
+ */
370
+ let adminWindowId = null;
371
+ async function openAdminWindow() {
372
+ if (adminWindowId !== null) {
373
+ try {
374
+ const existing = await chrome.windows.get(adminWindowId);
375
+ if (existing.id !== undefined) {
376
+ await chrome.windows.update(existing.id, { focused: true });
377
+ return { ok: true, windowId: existing.id };
378
+ }
379
+ }
380
+ catch {
381
+ // Closed since we last saw it — fall through and open a fresh one.
382
+ adminWindowId = null;
383
+ }
384
+ }
385
+ const created = await chrome.windows.create({
386
+ url: chrome.runtime.getURL('admin.html'),
387
+ type: 'popup',
388
+ ...ADMIN_WINDOW,
389
+ });
390
+ adminWindowId = created?.id ?? null;
391
+ return { ok: true, windowId: created?.id ?? -1 };
392
+ }
393
+ chrome.windows.onRemoved.addListener((closedId) => {
394
+ if (closedId === adminWindowId)
395
+ adminWindowId = null;
396
+ });
360
397
  /**
361
398
  * Every `browser.flow` currently running in this service worker, across
362
399
  * every connected tab. This is the whole basis of the kill switch: the
@@ -3468,6 +3505,10 @@ chrome.runtime.onMessage.addListener((msg, _sender, sendResponse) => {
3468
3505
  void openActivityWindow().then(sendResponse);
3469
3506
  return true;
3470
3507
  }
3508
+ if (msg.action === 'openAdminWindow') {
3509
+ void openAdminWindow().then(sendResponse);
3510
+ return true;
3511
+ }
3471
3512
  if (msg.action === 'activityQuery') {
3472
3513
  void queryActivity(msg.query ?? {}).then(sendResponse);
3473
3514
  return true;