kiro-mobile-bridge 1.0.7 → 1.0.10

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.
@@ -0,0 +1,210 @@
1
+ /**
2
+ * Chrome DevTools Protocol (CDP) connection service
3
+ */
4
+ import http from 'http';
5
+ import { WebSocket } from 'ws';
6
+ import { CDP_CALL_TIMEOUT, HTTP_TIMEOUT } from '../utils/constants.js';
7
+
8
+ /**
9
+ * Fetch JSON from a CDP endpoint
10
+ * @param {number} port - The port to fetch from
11
+ * @param {string} path - The path to fetch (default: /json/list)
12
+ * @returns {Promise<any>} - Parsed JSON response
13
+ */
14
+ export function fetchCDPTargets(port, path = '/json/list') {
15
+ return new Promise((resolve, reject) => {
16
+ const url = `http://127.0.0.1:${port}${path}`;
17
+
18
+ const req = http.get(url, { timeout: HTTP_TIMEOUT }, (res) => {
19
+ let data = '';
20
+ res.on('data', chunk => data += chunk);
21
+ res.on('end', () => {
22
+ try {
23
+ resolve(JSON.parse(data));
24
+ } catch (e) {
25
+ reject(new Error(`Failed to parse JSON from ${url}: ${e.message}`));
26
+ }
27
+ });
28
+ });
29
+
30
+ req.on('error', (err) => reject(new Error(`Failed to fetch ${url}: ${err.message}`)));
31
+ req.on('timeout', () => {
32
+ req.destroy();
33
+ reject(new Error(`Timeout fetching ${url}`));
34
+ });
35
+ });
36
+ }
37
+
38
+ /**
39
+ * @typedef {Object} PendingCall
40
+ * @property {Function} resolve - Promise resolve function
41
+ * @property {Function} reject - Promise reject function
42
+ * @property {NodeJS.Timeout} timeoutId - Timeout handle for cleanup
43
+ */
44
+
45
+ /**
46
+ * @typedef {Object} CDPConnection
47
+ * @property {WebSocket} ws - WebSocket connection
48
+ * @property {Array} contexts - Execution contexts
49
+ * @property {number|null} rootContextId - Root context ID
50
+ * @property {Function} call - Make a CDP call
51
+ * @property {Function} close - Close the connection
52
+ */
53
+
54
+ /**
55
+ * Create a CDP connection to a target
56
+ * @param {string} wsUrl - WebSocket debugger URL
57
+ * @returns {Promise<CDPConnection>} - CDP connection object
58
+ */
59
+ export function connectToCDP(wsUrl) {
60
+ return new Promise((resolve, reject) => {
61
+ const ws = new WebSocket(wsUrl);
62
+ let idCounter = 1;
63
+ /** @type {Map<number, PendingCall>} */
64
+ const pendingCalls = new Map();
65
+ const contexts = [];
66
+ let rootContextId = null;
67
+ let isConnected = false;
68
+
69
+ /**
70
+ * Clear all pending calls and their timeouts
71
+ * @param {Error} error - Error to reject pending calls with
72
+ */
73
+ function clearAllPendingCalls(error) {
74
+ for (const [id, pending] of pendingCalls) {
75
+ // Clear the timeout to prevent memory leak
76
+ if (pending.timeoutId) {
77
+ clearTimeout(pending.timeoutId);
78
+ }
79
+ pending.reject(error);
80
+ }
81
+ pendingCalls.clear();
82
+ }
83
+
84
+ ws.on('message', (rawMsg) => {
85
+ try {
86
+ const msg = JSON.parse(rawMsg.toString());
87
+
88
+ if (msg.method === 'Runtime.executionContextCreated') {
89
+ const ctx = msg.params.context;
90
+ contexts.push(ctx);
91
+ if (rootContextId === null || ctx.auxData?.isDefault) {
92
+ rootContextId = ctx.id;
93
+ }
94
+ }
95
+
96
+ if (msg.method === 'Runtime.executionContextDestroyed') {
97
+ const ctxId = msg.params.executionContextId;
98
+ const idx = contexts.findIndex(c => c.id === ctxId);
99
+ if (idx !== -1) contexts.splice(idx, 1);
100
+ if (rootContextId === ctxId) {
101
+ rootContextId = contexts.length > 0 ? contexts[0].id : null;
102
+ }
103
+ }
104
+
105
+ if (msg.method === 'Runtime.executionContextsCleared') {
106
+ contexts.length = 0;
107
+ rootContextId = null;
108
+ }
109
+
110
+ if (msg.id !== undefined && pendingCalls.has(msg.id)) {
111
+ const pending = pendingCalls.get(msg.id);
112
+
113
+ // Clear the timeout since we got a response
114
+ if (pending.timeoutId) {
115
+ clearTimeout(pending.timeoutId);
116
+ }
117
+
118
+ pendingCalls.delete(msg.id);
119
+
120
+ if (msg.error) {
121
+ pending.reject(new Error(`CDP Error: ${msg.error.message} (code: ${msg.error.code})`));
122
+ } else {
123
+ pending.resolve(msg.result);
124
+ }
125
+ }
126
+ } catch (e) {
127
+ console.error('[CDP] Failed to parse message:', e.message);
128
+ }
129
+ });
130
+
131
+ ws.on('open', async () => {
132
+ isConnected = true;
133
+ console.log(`[CDP] Connected to ${wsUrl}`);
134
+
135
+ const cdp = {
136
+ ws,
137
+ contexts,
138
+ get rootContextId() { return rootContextId; },
139
+
140
+ /**
141
+ * Make a CDP call
142
+ * @param {string} method - CDP method name
143
+ * @param {object} params - Method parameters
144
+ * @returns {Promise<any>} - Call result
145
+ */
146
+ call(method, params = {}) {
147
+ return new Promise((res, rej) => {
148
+ if (!isConnected) {
149
+ rej(new Error('CDP connection is closed'));
150
+ return;
151
+ }
152
+
153
+ const id = idCounter++;
154
+
155
+ // Set up timeout with cleanup
156
+ const timeoutId = setTimeout(() => {
157
+ if (pendingCalls.has(id)) {
158
+ pendingCalls.delete(id);
159
+ rej(new Error(`CDP call timeout: ${method}`));
160
+ }
161
+ }, CDP_CALL_TIMEOUT);
162
+
163
+ // Store pending call with timeout reference for cleanup
164
+ pendingCalls.set(id, { resolve: res, reject: rej, timeoutId });
165
+
166
+ try {
167
+ ws.send(JSON.stringify({ id, method, params }));
168
+ } catch (sendError) {
169
+ // Clean up on send failure
170
+ clearTimeout(timeoutId);
171
+ pendingCalls.delete(id);
172
+ rej(new Error(`Failed to send CDP call: ${sendError.message}`));
173
+ }
174
+ });
175
+ },
176
+
177
+ /**
178
+ * Close the CDP connection
179
+ */
180
+ close() {
181
+ isConnected = false;
182
+ clearAllPendingCalls(new Error('CDP connection closed'));
183
+ ws.terminate();
184
+ }
185
+ };
186
+
187
+ try {
188
+ await cdp.call('Runtime.enable', {});
189
+ await new Promise(r => setTimeout(r, 300));
190
+ console.log(`[CDP] Runtime enabled, found ${contexts.length} context(s)`);
191
+ resolve(cdp);
192
+ } catch (err) {
193
+ cdp.close();
194
+ reject(err);
195
+ }
196
+ });
197
+
198
+ ws.on('error', (err) => {
199
+ console.error(`[CDP] WebSocket error: ${err.message}`);
200
+ isConnected = false;
201
+ reject(err);
202
+ });
203
+
204
+ ws.on('close', () => {
205
+ console.log('[CDP] Connection closed');
206
+ isConnected = false;
207
+ clearAllPendingCalls(new Error('CDP connection closed'));
208
+ });
209
+ });
210
+ }