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.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +289 -28
  3. package/dist/chunk-JVBMT2O5.js +7173 -0
  4. package/dist/chunk-JVBMT2O5.js.map +1 -0
  5. package/dist/cli/index.js +3690 -275
  6. package/dist/cli/index.js.map +1 -1
  7. package/dist/index.js +1080 -2857
  8. package/dist/index.js.map +1 -1
  9. package/dist/opendevbrowser.js +1080 -2857
  10. package/dist/opendevbrowser.js.map +1 -1
  11. package/extension/dist/annotate-content.css +237 -0
  12. package/extension/dist/annotate-content.js +934 -0
  13. package/extension/dist/background.js +1291 -8
  14. package/extension/dist/logging.js +50 -0
  15. package/extension/dist/ops/dom-bridge.js +355 -0
  16. package/extension/dist/ops/ops-runtime.js +1249 -0
  17. package/extension/dist/ops/ops-session-store.js +189 -0
  18. package/extension/dist/ops/redaction.js +52 -0
  19. package/extension/dist/ops/snapshot-builder.js +4 -0
  20. package/extension/dist/ops/snapshot-shared.js +220 -0
  21. package/extension/dist/popup.js +398 -21
  22. package/extension/dist/relay-settings.js +3 -1
  23. package/extension/dist/services/CDPRouter.js +501 -103
  24. package/extension/dist/services/ConnectionManager.js +464 -57
  25. package/extension/dist/services/NativePortManager.js +182 -0
  26. package/extension/dist/services/RelayClient.js +227 -26
  27. package/extension/dist/services/TabManager.js +81 -0
  28. package/extension/dist/services/TargetSessionMap.js +146 -0
  29. package/extension/dist/services/cdp-router-commands.js +203 -0
  30. package/extension/dist/services/url-restrictions.js +41 -0
  31. package/extension/dist/types.js +3 -1
  32. package/extension/icons/icon128.png +0 -0
  33. package/extension/icons/icon16.png +0 -0
  34. package/extension/icons/icon32.png +0 -0
  35. package/extension/icons/icon48.png +0 -0
  36. package/extension/manifest.json +17 -3
  37. package/extension/popup.html +469 -65
  38. package/package.json +2 -2
  39. package/skills/AGENTS.md +34 -61
  40. package/skills/data-extraction/SKILL.md +95 -103
  41. package/skills/form-testing/SKILL.md +75 -82
  42. package/skills/login-automation/SKILL.md +76 -66
  43. package/skills/opendevbrowser-best-practices/SKILL.md +90 -49
  44. package/skills/opendevbrowser-continuity-ledger/SKILL.md +57 -23
  45. package/dist/chunk-R5VUZEUU.js +0 -128
  46. package/dist/chunk-R5VUZEUU.js.map +0 -1
  47. package/extension/dist/popup.jsx +0 -150
@@ -0,0 +1,146 @@
1
+ export class TargetSessionMap {
2
+ tabTargets = new Map();
3
+ sessionsById = new Map();
4
+ sessionByTarget = new Map();
5
+ rootWaiters = new Map();
6
+ registerRootTab(tabId, targetInfo, sessionId) {
7
+ const record = { tabId, targetInfo, rootSessionId: sessionId };
8
+ this.tabTargets.set(tabId, record);
9
+ const session = {
10
+ kind: "root",
11
+ sessionId,
12
+ tabId,
13
+ targetId: targetInfo.targetId,
14
+ debuggerSession: { tabId },
15
+ targetInfo
16
+ };
17
+ this.sessionsById.set(sessionId, session);
18
+ this.sessionByTarget.set(targetInfo.targetId, sessionId);
19
+ this.resolveRootWaiters(tabId, session);
20
+ return session;
21
+ }
22
+ registerChildSession(tabId, targetInfo, sessionId) {
23
+ const session = {
24
+ kind: "child",
25
+ sessionId,
26
+ tabId,
27
+ targetId: targetInfo.targetId,
28
+ debuggerSession: { tabId, sessionId },
29
+ targetInfo
30
+ };
31
+ this.sessionsById.set(sessionId, session);
32
+ this.sessionByTarget.set(targetInfo.targetId, sessionId);
33
+ return session;
34
+ }
35
+ getBySessionId(sessionId) {
36
+ return this.sessionsById.get(sessionId) ?? null;
37
+ }
38
+ hasSession(sessionId) {
39
+ return this.sessionsById.has(sessionId);
40
+ }
41
+ getByTargetId(targetId) {
42
+ const sessionId = this.sessionByTarget.get(targetId);
43
+ if (!sessionId) {
44
+ return null;
45
+ }
46
+ return this.sessionsById.get(sessionId) ?? null;
47
+ }
48
+ getByTabId(tabId) {
49
+ return this.tabTargets.get(tabId) ?? null;
50
+ }
51
+ async waitForRootSession(tabId, timeoutMs = 2000) {
52
+ const existing = this.getByTabId(tabId);
53
+ if (existing) {
54
+ const session = this.sessionsById.get(existing.rootSessionId);
55
+ if (session) {
56
+ return session;
57
+ }
58
+ }
59
+ return await new Promise((resolve, reject) => {
60
+ const timeoutId = setTimeout(() => {
61
+ this.rejectRootWaiter(tabId, timeoutId);
62
+ reject(new Error("Target attach timeout"));
63
+ }, timeoutMs);
64
+ const entry = { resolve, reject, timeoutId };
65
+ const waiters = this.rootWaiters.get(tabId) ?? [];
66
+ waiters.push(entry);
67
+ this.rootWaiters.set(tabId, waiters);
68
+ });
69
+ }
70
+ listTargetInfos() {
71
+ const rootTargets = Array.from(this.tabTargets.values()).map((record) => record.targetInfo);
72
+ const childTargets = Array.from(this.sessionsById.values())
73
+ .filter((session) => session.kind === "child" && session.targetInfo)
74
+ .map((session) => session.targetInfo);
75
+ return [...rootTargets, ...childTargets];
76
+ }
77
+ listTabIds() {
78
+ return Array.from(this.tabTargets.keys());
79
+ }
80
+ listSessionIds() {
81
+ return Array.from(this.sessionsById.keys());
82
+ }
83
+ removeByTabId(tabId) {
84
+ const record = this.tabTargets.get(tabId) ?? null;
85
+ if (!record) {
86
+ return null;
87
+ }
88
+ for (const [sessionId, session] of this.sessionsById.entries()) {
89
+ if (session.tabId === tabId) {
90
+ this.sessionsById.delete(sessionId);
91
+ }
92
+ }
93
+ for (const [targetId, sessionId] of this.sessionByTarget.entries()) {
94
+ const session = this.sessionsById.get(sessionId);
95
+ if (!session || session.tabId === tabId) {
96
+ this.sessionByTarget.delete(targetId);
97
+ }
98
+ }
99
+ this.tabTargets.delete(tabId);
100
+ return record;
101
+ }
102
+ removeBySessionId(sessionId) {
103
+ const session = this.sessionsById.get(sessionId) ?? null;
104
+ if (!session) {
105
+ return null;
106
+ }
107
+ if (session.kind === "root") {
108
+ this.removeByTabId(session.tabId);
109
+ return session;
110
+ }
111
+ this.sessionsById.delete(sessionId);
112
+ this.sessionByTarget.delete(session.targetId);
113
+ return session;
114
+ }
115
+ removeByTargetId(targetId) {
116
+ const sessionId = this.sessionByTarget.get(targetId);
117
+ if (!sessionId) {
118
+ return null;
119
+ }
120
+ return this.removeBySessionId(sessionId);
121
+ }
122
+ resolveRootWaiters(tabId, session) {
123
+ const waiters = this.rootWaiters.get(tabId);
124
+ if (!waiters || waiters.length === 0) {
125
+ return;
126
+ }
127
+ this.rootWaiters.delete(tabId);
128
+ for (const waiter of waiters) {
129
+ clearTimeout(waiter.timeoutId);
130
+ waiter.resolve(session);
131
+ }
132
+ }
133
+ rejectRootWaiter(tabId, timeoutId) {
134
+ const waiters = this.rootWaiters.get(tabId);
135
+ if (!waiters || waiters.length === 0) {
136
+ return;
137
+ }
138
+ const remaining = waiters.filter((waiter) => waiter.timeoutId !== timeoutId);
139
+ if (remaining.length === 0) {
140
+ this.rootWaiters.delete(tabId);
141
+ }
142
+ else {
143
+ this.rootWaiters.set(tabId, remaining);
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,203 @@
1
+ export async function handleSetDiscoverTargets(ctx, commandId, params) {
2
+ const discover = params.discover === true;
3
+ const shouldEmit = discover && !ctx.discoverTargets;
4
+ ctx.setDiscoverTargets(discover);
5
+ if (shouldEmit) {
6
+ for (const targetInfo of ctx.sessions.listTargetInfos()) {
7
+ ctx.emitTargetCreated(targetInfo);
8
+ }
9
+ }
10
+ ctx.respond(commandId, {});
11
+ }
12
+ export async function handleSetAutoAttach(ctx, commandId, params, sessionId) {
13
+ if (params.flatten === false) {
14
+ ctx.respondError(commandId, ctx.flatSessionError, sessionId);
15
+ return;
16
+ }
17
+ if (sessionId) {
18
+ ctx.respond(commandId, {}, sessionId);
19
+ return;
20
+ }
21
+ const autoAttach = params.autoAttach === true;
22
+ const waitForDebuggerOnStart = params.waitForDebuggerOnStart === true;
23
+ ctx.setAutoAttachOptions({ autoAttach, waitForDebuggerOnStart, flatten: true, filter: params.filter });
24
+ if (autoAttach && !sessionId) {
25
+ ctx.resetRootAttached();
26
+ }
27
+ try {
28
+ for (const debuggee of ctx.debuggees.values()) {
29
+ await ctx.applyAutoAttach(debuggee);
30
+ }
31
+ }
32
+ catch (error) {
33
+ ctx.respondError(commandId, getErrorMessage(error));
34
+ return;
35
+ }
36
+ if (!autoAttach) {
37
+ ctx.emitRootDetached();
38
+ }
39
+ else {
40
+ for (const targetInfo of ctx.sessions.listTargetInfos()) {
41
+ ctx.emitRootAttached(targetInfo);
42
+ }
43
+ }
44
+ ctx.respond(commandId, {});
45
+ }
46
+ export async function handleCreateTarget(ctx, commandId, params) {
47
+ const url = typeof params.url === "string" ? params.url : undefined;
48
+ const background = params.background === true;
49
+ let createdTabId = null;
50
+ try {
51
+ const tab = await ctx.tabManager.createTab(url, !background);
52
+ if (typeof tab.id !== "number") {
53
+ throw new Error("Target.createTarget did not yield a tab id");
54
+ }
55
+ createdTabId = tab.id;
56
+ await ctx.tabManager.waitForTabComplete(tab.id);
57
+ await ctx.attach(tab.id);
58
+ await ctx.sessions.waitForRootSession(tab.id);
59
+ await ctx.sendCommand({ tabId: tab.id }, "Target.getTargets", {});
60
+ const targetInfo = await ctx.registerRootTab(tab.id);
61
+ if (ctx.discoverTargets) {
62
+ ctx.emitTargetCreated(targetInfo);
63
+ }
64
+ if (ctx.autoAttachOptions.autoAttach) {
65
+ ctx.emitRootAttached(targetInfo);
66
+ }
67
+ if (!background) {
68
+ ctx.updatePrimaryTab(tab.id);
69
+ }
70
+ ctx.respond(commandId, { targetId: targetInfo.targetId });
71
+ }
72
+ catch (error) {
73
+ if (createdTabId !== null) {
74
+ const debuggee = ctx.debuggees.get(createdTabId) ?? null;
75
+ ctx.detachTabState(createdTabId);
76
+ if (debuggee) {
77
+ await ctx.safeDetach(debuggee);
78
+ }
79
+ try {
80
+ await ctx.tabManager.closeTab(createdTabId);
81
+ }
82
+ catch {
83
+ // Best-effort cleanup for partially created targets.
84
+ }
85
+ }
86
+ ctx.respondError(commandId, getErrorMessage(error));
87
+ }
88
+ }
89
+ export async function handleCloseTarget(ctx, commandId, params) {
90
+ const targetId = typeof params.targetId === "string" ? params.targetId : null;
91
+ if (!targetId) {
92
+ ctx.respondError(commandId, "Missing targetId");
93
+ return;
94
+ }
95
+ const session = ctx.sessions.getByTargetId(targetId);
96
+ if (!session || session.kind !== "root") {
97
+ ctx.respondError(commandId, "Target not found");
98
+ return;
99
+ }
100
+ try {
101
+ const debuggee = ctx.debuggees.get(session.tabId) ?? null;
102
+ ctx.detachTabState(session.tabId);
103
+ if (debuggee) {
104
+ await ctx.safeDetach(debuggee);
105
+ }
106
+ await ctx.tabManager.closeTab(session.tabId);
107
+ ctx.respond(commandId, { success: true });
108
+ }
109
+ catch (error) {
110
+ ctx.respondError(commandId, getErrorMessage(error));
111
+ }
112
+ }
113
+ export async function handleActivateTarget(ctx, commandId, params) {
114
+ const targetId = typeof params.targetId === "string" ? params.targetId : null;
115
+ if (!targetId) {
116
+ ctx.respondError(commandId, "Missing targetId");
117
+ return;
118
+ }
119
+ const session = ctx.sessions.getByTargetId(targetId);
120
+ if (!session || session.kind !== "root") {
121
+ ctx.respondError(commandId, "Target not found");
122
+ return;
123
+ }
124
+ try {
125
+ await ctx.tabManager.activateTab(session.tabId);
126
+ ctx.updatePrimaryTab(session.tabId);
127
+ ctx.respond(commandId, {});
128
+ }
129
+ catch (error) {
130
+ ctx.respondError(commandId, getErrorMessage(error));
131
+ }
132
+ }
133
+ export async function handleAttachToTarget(ctx, commandId, params, sessionId) {
134
+ const targetId = typeof params.targetId === "string" ? params.targetId : null;
135
+ if (!targetId) {
136
+ ctx.respondError(commandId, "Missing targetId", sessionId);
137
+ return;
138
+ }
139
+ if (params.flatten === false) {
140
+ ctx.respondError(commandId, ctx.flatSessionError, sessionId);
141
+ return;
142
+ }
143
+ const targetSession = ctx.sessions.getByTargetId(targetId);
144
+ if (targetSession && targetSession.kind === "root") {
145
+ ctx.respond(commandId, { sessionId: targetSession.sessionId }, sessionId);
146
+ return;
147
+ }
148
+ const session = sessionId ? ctx.sessions.getBySessionId(sessionId) : null;
149
+ if (sessionId && !session) {
150
+ ctx.respondError(commandId, `Unknown sessionId: ${sessionId}`, sessionId);
151
+ return;
152
+ }
153
+ const debuggee = session?.debuggerSession ?? ctx.getPrimaryDebuggee();
154
+ if (!debuggee) {
155
+ ctx.respondError(commandId, "No tab attached", sessionId);
156
+ return;
157
+ }
158
+ try {
159
+ const result = await ctx.sendCommand(debuggee, "Target.attachToTarget", { targetId, flatten: true });
160
+ const record = isRecord(result) ? result : {};
161
+ const childSessionId = typeof record.sessionId === "string" ? record.sessionId : null;
162
+ if (childSessionId) {
163
+ const targetInfo = {
164
+ targetId,
165
+ type: "page",
166
+ browserContextId: "default"
167
+ };
168
+ ctx.sessions.registerChildSession(debuggee.tabId, targetInfo, childSessionId);
169
+ }
170
+ ctx.respond(commandId, result);
171
+ }
172
+ catch (error) {
173
+ ctx.respondError(commandId, getErrorMessage(error), sessionId);
174
+ }
175
+ }
176
+ export async function handleRoutedCommand(ctx, commandId, method, params, sessionId) {
177
+ const session = sessionId ? ctx.sessions.getBySessionId(sessionId) : null;
178
+ if (sessionId && !session) {
179
+ ctx.respondError(commandId, `Unknown sessionId: ${sessionId}`, sessionId);
180
+ return;
181
+ }
182
+ const debuggee = session?.debuggerSession ?? ctx.getPrimaryDebuggee();
183
+ if (!debuggee) {
184
+ ctx.respondError(commandId, "No tab attached", sessionId);
185
+ return;
186
+ }
187
+ try {
188
+ const result = await ctx.sendCommand(debuggee, method, params);
189
+ ctx.respond(commandId, result, sessionId);
190
+ }
191
+ catch (error) {
192
+ ctx.respondError(commandId, getErrorMessage(error), sessionId);
193
+ }
194
+ }
195
+ const isRecord = (value) => {
196
+ return typeof value === "object" && value !== null;
197
+ };
198
+ const getErrorMessage = (error) => {
199
+ if (error instanceof Error) {
200
+ return error.message;
201
+ }
202
+ return "Unknown error";
203
+ };
@@ -0,0 +1,41 @@
1
+ const RESTRICTED_PROTOCOLS = new Set([
2
+ "chrome:",
3
+ "chrome-extension:",
4
+ "chrome-search:",
5
+ "chrome-untrusted:",
6
+ "devtools:",
7
+ "chrome-devtools:",
8
+ "edge:",
9
+ "brave:"
10
+ ]);
11
+ const isWebStoreUrl = (url) => {
12
+ if (url.hostname === "chromewebstore.google.com") {
13
+ return true;
14
+ }
15
+ if (url.hostname === "chrome.google.com" && url.pathname.startsWith("/webstore")) {
16
+ return true;
17
+ }
18
+ return false;
19
+ };
20
+ export const getRestrictionMessage = (url) => {
21
+ if (RESTRICTED_PROTOCOLS.has(url.protocol)) {
22
+ return "Active tab uses a restricted URL scheme. Focus a normal http(s) tab and retry.";
23
+ }
24
+ if (isWebStoreUrl(url)) {
25
+ return "Chrome Web Store tabs cannot be debugged. Open a normal tab and retry.";
26
+ }
27
+ return null;
28
+ };
29
+ export const isRestrictedUrl = (rawUrl) => {
30
+ try {
31
+ const url = new URL(rawUrl);
32
+ const message = getRestrictionMessage(url);
33
+ if (message) {
34
+ return { restricted: true, message };
35
+ }
36
+ return { restricted: false };
37
+ }
38
+ catch {
39
+ return { restricted: true, message: "Unable to parse tab URL." };
40
+ }
41
+ };
@@ -1 +1,3 @@
1
- export {};
1
+ export const OPS_PROTOCOL_VERSION = "1";
2
+ export const MAX_OPS_PAYLOAD_BYTES = 12 * 1024 * 1024;
3
+ export const MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024;
Binary file
Binary file
Binary file
Binary file
@@ -1,16 +1,21 @@
1
1
  {
2
2
  "manifest_version": 3,
3
3
  "name": "OpenDevBrowser Relay",
4
- "version": "0.0.11",
4
+ "version": "0.0.15",
5
5
  "description": "Optional bridge to reuse existing Chrome tabs with OpenDevBrowser.",
6
6
  "permissions": [
7
7
  "debugger",
8
+ "alarms",
8
9
  "tabs",
9
- "storage"
10
+ "storage",
11
+ "scripting",
12
+ "activeTab",
13
+ "nativeMessaging"
10
14
  ],
11
15
  "host_permissions": [
12
16
  "http://127.0.0.1/*",
13
- "http://localhost/*"
17
+ "http://localhost/*",
18
+ "<all_urls>"
14
19
  ],
15
20
  "icons": {
16
21
  "16": "icons/icon16.png",
@@ -30,5 +35,14 @@
30
35
  "background": {
31
36
  "service_worker": "dist/background.js",
32
37
  "type": "module"
38
+ },
39
+ "commands": {
40
+ "toggle-annotation": {
41
+ "suggested_key": {
42
+ "default": "Ctrl+Shift+P",
43
+ "mac": "Command+Shift+P"
44
+ },
45
+ "description": "Toggle annotation UI"
46
+ }
33
47
  }
34
48
  }