@teardown/cli 1.2.27 → 1.2.29
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/dist/modules/dev/dev-menu/keyboard-handler.d.ts +1 -0
- package/dist/modules/dev/dev-menu/keyboard-handler.js +9 -1
- package/dist/modules/dev/dev-server/cdp/index.d.ts +2 -0
- package/dist/modules/dev/dev-server/cdp/index.js +18 -0
- package/dist/modules/dev/dev-server/cdp/types.d.ts +107 -0
- package/dist/modules/dev/dev-server/cdp/types.js +2 -0
- package/dist/modules/dev/dev-server/dev-server.js +45 -23
- package/dist/modules/dev/dev-server/inspector/device.d.ts +46 -0
- package/dist/modules/dev/dev-server/inspector/device.event-reporter.d.ts +37 -0
- package/dist/modules/dev/dev-server/inspector/device.event-reporter.js +165 -0
- package/dist/modules/dev/dev-server/inspector/device.js +577 -0
- package/dist/modules/dev/dev-server/inspector/inspector.d.ts +27 -0
- package/dist/modules/dev/dev-server/inspector/inspector.js +203 -0
- package/dist/modules/dev/dev-server/inspector/types.d.ts +156 -0
- package/dist/modules/dev/dev-server/inspector/types.js +2 -0
- package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.d.ts +14 -0
- package/dist/modules/dev/dev-server/inspector/wss/servers/debugger-connection.server.js +61 -0
- package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.d.ts +19 -0
- package/dist/modules/dev/dev-server/inspector/wss/servers/device-connection.server.js +64 -0
- package/dist/modules/dev/dev-server/plugins/favicon.plugin.js +1 -2
- package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.d.ts +3 -0
- package/dist/modules/dev/dev-server/sybmolicate/sybmolicate.plugin.js +7 -1
- package/dist/modules/dev/terminal/terminal.reporter.d.ts +3 -1
- package/dist/modules/dev/terminal/terminal.reporter.js +3 -0
- package/package.json +4 -4
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.Device = void 0;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_url_1 = require("node:url");
|
|
10
|
+
const ws_1 = __importDefault(require("ws"));
|
|
11
|
+
const device_event_reporter_1 = require("./device.event-reporter");
|
|
12
|
+
const debug = require("debug")("Metro:InspectorProxy");
|
|
13
|
+
const PAGES_POLLING_INTERVAL = 1000;
|
|
14
|
+
// Constants for host rewriting
|
|
15
|
+
const REWRITE_HOSTS_TO_LOCALHOST = [
|
|
16
|
+
"127.0.0.1",
|
|
17
|
+
"10.0.2.2",
|
|
18
|
+
"10.0.3.2",
|
|
19
|
+
];
|
|
20
|
+
const FILE_PREFIX = "file://";
|
|
21
|
+
const REACT_NATIVE_RELOADABLE_PAGE_ID = "-1";
|
|
22
|
+
class Device {
|
|
23
|
+
id;
|
|
24
|
+
name;
|
|
25
|
+
app;
|
|
26
|
+
messageFromDeviceQueue = Promise.resolve();
|
|
27
|
+
deviceSocket;
|
|
28
|
+
pages = new Map();
|
|
29
|
+
debuggerConnection = null;
|
|
30
|
+
lastConnectedLegacyReactNativePage = null;
|
|
31
|
+
isLegacyPageReloading = false;
|
|
32
|
+
lastGetPagesMessage = "";
|
|
33
|
+
scriptIdToSourcePathMapping = new Map();
|
|
34
|
+
projectRoot;
|
|
35
|
+
deviceEventReporter;
|
|
36
|
+
pagesPollingIntervalId;
|
|
37
|
+
createCustomMessageHandler;
|
|
38
|
+
connectedPageIds = new Set();
|
|
39
|
+
constructor(id, name, app, socket, projectRoot, eventReporter) {
|
|
40
|
+
this.id = id;
|
|
41
|
+
this.name = name;
|
|
42
|
+
this.app = app;
|
|
43
|
+
this.deviceSocket = socket;
|
|
44
|
+
this.projectRoot = projectRoot;
|
|
45
|
+
this.deviceEventReporter =
|
|
46
|
+
eventReporter != null
|
|
47
|
+
? new device_event_reporter_1.DeviceEventReporter(eventReporter, {
|
|
48
|
+
deviceId: id,
|
|
49
|
+
deviceName: name,
|
|
50
|
+
appId: app,
|
|
51
|
+
})
|
|
52
|
+
: null;
|
|
53
|
+
this.createCustomMessageHandler = null;
|
|
54
|
+
// Setup message handling
|
|
55
|
+
this.deviceSocket.on("message", (message) => {
|
|
56
|
+
this.messageFromDeviceQueue = this.messageFromDeviceQueue
|
|
57
|
+
.then(async () => {
|
|
58
|
+
const parsedMessage = JSON.parse(message);
|
|
59
|
+
if (parsedMessage.event === "getPages") {
|
|
60
|
+
if (message !== this.lastGetPagesMessage) {
|
|
61
|
+
debug(`(Debugger) (Proxy) <- (Device), getPages ping has changed: ${message}`);
|
|
62
|
+
this.lastGetPagesMessage = message;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
debug(`(Debugger) (Proxy) <- (Device): ${message}`);
|
|
67
|
+
}
|
|
68
|
+
await this.handleMessageFromDevice(parsedMessage);
|
|
69
|
+
})
|
|
70
|
+
.catch((error) => {
|
|
71
|
+
debug("%O\nHandling device message: %s", error, message);
|
|
72
|
+
try {
|
|
73
|
+
this.deviceEventReporter?.logProxyMessageHandlingError("device", error, message);
|
|
74
|
+
}
|
|
75
|
+
catch (loggingError) {
|
|
76
|
+
debug("Error logging message handling error to reporter: %O", loggingError);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
// Setup polling
|
|
81
|
+
this.pagesPollingIntervalId = setInterval(() => this.sendMessageToDevice({ event: "getPages" }), PAGES_POLLING_INTERVAL);
|
|
82
|
+
// Handle socket close
|
|
83
|
+
this.deviceSocket.on("close", () => {
|
|
84
|
+
if (socket === this.deviceSocket) {
|
|
85
|
+
this.deviceEventReporter?.logDisconnection("device");
|
|
86
|
+
this.terminateDebuggerConnection();
|
|
87
|
+
clearInterval(this.pagesPollingIntervalId);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
terminateDebuggerConnection() {
|
|
92
|
+
const debuggerConnection = this.debuggerConnection;
|
|
93
|
+
if (debuggerConnection) {
|
|
94
|
+
this.sendDisconnectEventToDevice(this.mapToDevicePageId(debuggerConnection.pageId));
|
|
95
|
+
debuggerConnection.socket.close();
|
|
96
|
+
this.debuggerConnection = null;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
dangerouslyRecreateDevice(id, name, app, socket, projectRoot, eventReporter) {
|
|
100
|
+
invariant(id === this.id, "dangerouslyRecreateDevice() can only be used for the same device ID");
|
|
101
|
+
const oldDebugger = this.debuggerConnection;
|
|
102
|
+
if (this.app !== app || this.name !== name) {
|
|
103
|
+
this.deviceSocket.close();
|
|
104
|
+
this.terminateDebuggerConnection();
|
|
105
|
+
}
|
|
106
|
+
this.debuggerConnection = null;
|
|
107
|
+
if (oldDebugger) {
|
|
108
|
+
oldDebugger.socket.removeAllListeners();
|
|
109
|
+
this.deviceSocket.close();
|
|
110
|
+
this.handleDebuggerConnection(oldDebugger.socket, oldDebugger.pageId, {
|
|
111
|
+
userAgent: oldDebugger.userAgent,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
this.id = id;
|
|
115
|
+
this.name = name;
|
|
116
|
+
this.app = app;
|
|
117
|
+
this.deviceSocket = socket;
|
|
118
|
+
this.projectRoot = projectRoot;
|
|
119
|
+
this.deviceEventReporter = eventReporter
|
|
120
|
+
? new device_event_reporter_1.DeviceEventReporter(eventReporter, {
|
|
121
|
+
deviceId: id,
|
|
122
|
+
deviceName: name,
|
|
123
|
+
appId: app,
|
|
124
|
+
})
|
|
125
|
+
: null;
|
|
126
|
+
}
|
|
127
|
+
getName() {
|
|
128
|
+
return this.name;
|
|
129
|
+
}
|
|
130
|
+
getApp() {
|
|
131
|
+
return this.app;
|
|
132
|
+
}
|
|
133
|
+
getPagesList() {
|
|
134
|
+
if (this.lastConnectedLegacyReactNativePage) {
|
|
135
|
+
return [...this.pages.values(), this.createSyntheticPage()];
|
|
136
|
+
}
|
|
137
|
+
return [...this.pages.values()];
|
|
138
|
+
}
|
|
139
|
+
handleDebuggerConnection(socket, pageId, metadata) {
|
|
140
|
+
const page = pageId === REACT_NATIVE_RELOADABLE_PAGE_ID
|
|
141
|
+
? this.createSyntheticPage()
|
|
142
|
+
: this.pages.get(pageId);
|
|
143
|
+
if (!page) {
|
|
144
|
+
debug(`Got new debugger connection for page ${pageId} of ${this.name}, but no such page exists`);
|
|
145
|
+
socket.close();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
this.deviceEventReporter?.logDisconnection("debugger");
|
|
149
|
+
this.terminateDebuggerConnection();
|
|
150
|
+
this.deviceEventReporter?.logConnection("debugger", {
|
|
151
|
+
pageId,
|
|
152
|
+
frontendUserAgent: metadata.userAgent,
|
|
153
|
+
});
|
|
154
|
+
const debuggerInfo = {
|
|
155
|
+
socket,
|
|
156
|
+
prependedFilePrefix: false,
|
|
157
|
+
pageId,
|
|
158
|
+
userAgent: metadata.userAgent,
|
|
159
|
+
customHandler: null,
|
|
160
|
+
};
|
|
161
|
+
this.debuggerConnection = debuggerInfo;
|
|
162
|
+
if (this.debuggerConnection && this.createCustomMessageHandler) {
|
|
163
|
+
this.debuggerConnection.customHandler = this.createCustomMessageHandler({
|
|
164
|
+
page,
|
|
165
|
+
debugger: {
|
|
166
|
+
userAgent: debuggerInfo.userAgent,
|
|
167
|
+
sendMessage: (message) => {
|
|
168
|
+
try {
|
|
169
|
+
const payload = JSON.stringify(message);
|
|
170
|
+
debug(`(Debugger) <- (Proxy) (Device): ${payload}`);
|
|
171
|
+
socket.send(payload);
|
|
172
|
+
}
|
|
173
|
+
catch { }
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
device: {
|
|
177
|
+
appId: this.app,
|
|
178
|
+
id: this.id,
|
|
179
|
+
name: this.name,
|
|
180
|
+
sendMessage: (message) => {
|
|
181
|
+
try {
|
|
182
|
+
const payload = JSON.stringify({
|
|
183
|
+
event: "wrappedEvent",
|
|
184
|
+
payload: {
|
|
185
|
+
pageId: this.mapToDevicePageId(pageId),
|
|
186
|
+
wrappedEvent: JSON.stringify(message),
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
debug(`(Debugger) -> (Proxy) (Device): ${payload}`);
|
|
190
|
+
this.deviceSocket.send(payload);
|
|
191
|
+
}
|
|
192
|
+
catch { }
|
|
193
|
+
},
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
this.sendConnectEventToDevice(this.mapToDevicePageId(pageId));
|
|
198
|
+
socket.on("message", (message) => {
|
|
199
|
+
debug(`(Debugger) -> (Proxy) (Device): ${message}`);
|
|
200
|
+
const debuggerRequest = JSON.parse(message);
|
|
201
|
+
this.deviceEventReporter?.logRequest(debuggerRequest, "debugger", {
|
|
202
|
+
pageId: this.debuggerConnection?.pageId ?? null,
|
|
203
|
+
frontendUserAgent: metadata.userAgent,
|
|
204
|
+
prefersFuseboxFrontend: this.isPageFuseboxFrontend(this.debuggerConnection?.pageId ?? null),
|
|
205
|
+
});
|
|
206
|
+
if (this.debuggerConnection?.customHandler?.handleDebuggerMessage(debuggerRequest) === true) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
if (!this.pageHasCapability(page, "nativeSourceCodeFetching")) {
|
|
210
|
+
const processedReq = this.interceptClientMessageForSourceFetching(debuggerRequest, debuggerInfo, socket);
|
|
211
|
+
if (processedReq) {
|
|
212
|
+
this.sendMessageToDevice({
|
|
213
|
+
event: "wrappedEvent",
|
|
214
|
+
payload: {
|
|
215
|
+
pageId: this.mapToDevicePageId(pageId),
|
|
216
|
+
wrappedEvent: JSON.stringify(processedReq),
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
socket.on("close", () => {
|
|
223
|
+
debug(`Debugger for page ${pageId} and ${this.name} disconnected.`);
|
|
224
|
+
this.deviceEventReporter?.logDisconnection("debugger");
|
|
225
|
+
if (this.debuggerConnection?.socket === socket) {
|
|
226
|
+
this.terminateDebuggerConnection();
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
const sendFunc = socket.send.bind(socket);
|
|
230
|
+
socket.send = (message) => {
|
|
231
|
+
debug(`(Debugger) <- (Proxy) (Device): ${message}`);
|
|
232
|
+
return sendFunc(message);
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
sendConnectEventToDevice(devicePageId) {
|
|
236
|
+
if (this.connectedPageIds.has(devicePageId)) {
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
this.connectedPageIds.add(devicePageId);
|
|
240
|
+
this.sendMessageToDevice({
|
|
241
|
+
event: "connect",
|
|
242
|
+
payload: { pageId: devicePageId },
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
sendDisconnectEventToDevice(devicePageId) {
|
|
246
|
+
if (!this.connectedPageIds.has(devicePageId)) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
this.connectedPageIds.delete(devicePageId);
|
|
250
|
+
this.sendMessageToDevice({
|
|
251
|
+
event: "disconnect",
|
|
252
|
+
payload: { pageId: devicePageId },
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
pageHasCapability(page, flag) {
|
|
256
|
+
return page.capabilities[flag] === true;
|
|
257
|
+
}
|
|
258
|
+
createSyntheticPage() {
|
|
259
|
+
return {
|
|
260
|
+
id: REACT_NATIVE_RELOADABLE_PAGE_ID,
|
|
261
|
+
title: "React Native Experimental (Improved Chrome Reloads)",
|
|
262
|
+
vm: "don't use",
|
|
263
|
+
app: this.app,
|
|
264
|
+
capabilities: {},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
async handleMessageFromDevice(message) {
|
|
268
|
+
if (message.event === "getPages") {
|
|
269
|
+
this.pages = new Map(message.payload.map((page) => {
|
|
270
|
+
const { capabilities = {}, ...rest } = page;
|
|
271
|
+
return [
|
|
272
|
+
rest.id,
|
|
273
|
+
{
|
|
274
|
+
...rest,
|
|
275
|
+
capabilities,
|
|
276
|
+
},
|
|
277
|
+
];
|
|
278
|
+
}));
|
|
279
|
+
for (const page of this.pages.values()) {
|
|
280
|
+
if (this.pageHasCapability(page, "nativePageReloads")) {
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
if (page.title.includes("React")) {
|
|
284
|
+
if (page.id !== this.lastConnectedLegacyReactNativePage?.id) {
|
|
285
|
+
this.newLegacyReactNativePage(page);
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (message.event === "disconnect") {
|
|
292
|
+
const pageId = message.payload.pageId;
|
|
293
|
+
const page = this.pages.get(pageId);
|
|
294
|
+
if (page && this.pageHasCapability(page, "nativePageReloads")) {
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
const debuggerSocket = this.debuggerConnection?.socket;
|
|
298
|
+
if (debuggerSocket && debuggerSocket.readyState === ws_1.default.OPEN) {
|
|
299
|
+
if (this.debuggerConnection &&
|
|
300
|
+
this.debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID) {
|
|
301
|
+
debug(`Legacy page ${pageId} is reloading.`);
|
|
302
|
+
debuggerSocket.send(JSON.stringify({ method: "reload" }));
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (message.event === "wrappedEvent") {
|
|
307
|
+
if (!this.debuggerConnection) {
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const debuggerSocket = this.debuggerConnection.socket;
|
|
311
|
+
if (!debuggerSocket || debuggerSocket.readyState !== ws_1.default.OPEN) {
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
const parsedPayload = JSON.parse(message.payload.wrappedEvent);
|
|
315
|
+
const pageId = this.debuggerConnection.pageId;
|
|
316
|
+
if ("id" in parsedPayload) {
|
|
317
|
+
this.deviceEventReporter?.logResponse(parsedPayload, "device", {
|
|
318
|
+
pageId,
|
|
319
|
+
frontendUserAgent: this.debuggerConnection.userAgent,
|
|
320
|
+
prefersFuseboxFrontend: this.isPageFuseboxFrontend(pageId),
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
if (this.debuggerConnection.customHandler?.handleDeviceMessage(parsedPayload) === true) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
await this.processMessageFromDeviceLegacy(parsedPayload, this.debuggerConnection, pageId);
|
|
327
|
+
debuggerSocket.send(JSON.stringify(parsedPayload));
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
sendMessageToDevice(message) {
|
|
331
|
+
try {
|
|
332
|
+
if (message.event !== "getPages") {
|
|
333
|
+
debug(`(Debugger) (Proxy) -> (Device): ${JSON.stringify(message)}`);
|
|
334
|
+
}
|
|
335
|
+
this.deviceSocket.send(JSON.stringify(message));
|
|
336
|
+
}
|
|
337
|
+
catch (error) { }
|
|
338
|
+
}
|
|
339
|
+
newLegacyReactNativePage(page) {
|
|
340
|
+
debug(`React Native page updated to ${page.id}`);
|
|
341
|
+
if (!this.debuggerConnection ||
|
|
342
|
+
this.debuggerConnection.pageId !== REACT_NATIVE_RELOADABLE_PAGE_ID) {
|
|
343
|
+
this.lastConnectedLegacyReactNativePage = page;
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
const oldPageId = this.lastConnectedLegacyReactNativePage?.id;
|
|
347
|
+
this.lastConnectedLegacyReactNativePage = page;
|
|
348
|
+
this.isLegacyPageReloading = true;
|
|
349
|
+
if (oldPageId != null) {
|
|
350
|
+
this.sendDisconnectEventToDevice(oldPageId);
|
|
351
|
+
}
|
|
352
|
+
this.sendConnectEventToDevice(page.id);
|
|
353
|
+
const toSend = [
|
|
354
|
+
{ method: "Runtime.enable", id: 1e9 },
|
|
355
|
+
{ method: "Debugger.enable", id: 1e9 },
|
|
356
|
+
];
|
|
357
|
+
for (const message of toSend) {
|
|
358
|
+
const pageId = this.debuggerConnection?.pageId ?? null;
|
|
359
|
+
this.deviceEventReporter?.logRequest(message, "proxy", {
|
|
360
|
+
pageId,
|
|
361
|
+
frontendUserAgent: this.debuggerConnection?.userAgent ?? null,
|
|
362
|
+
prefersFuseboxFrontend: this.isPageFuseboxFrontend(pageId),
|
|
363
|
+
});
|
|
364
|
+
this.sendMessageToDevice({
|
|
365
|
+
event: "wrappedEvent",
|
|
366
|
+
payload: {
|
|
367
|
+
pageId: this.mapToDevicePageId(page.id),
|
|
368
|
+
wrappedEvent: JSON.stringify(message),
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async processMessageFromDeviceLegacy(payload, debuggerInfo, pageId) {
|
|
374
|
+
const page = pageId != null ? this.pages.get(pageId) : null;
|
|
375
|
+
if ((!page || !this.pageHasCapability(page, "nativeSourceCodeFetching")) &&
|
|
376
|
+
payload.method === "Debugger.scriptParsed" &&
|
|
377
|
+
// @ts-ignore
|
|
378
|
+
payload.params != null) {
|
|
379
|
+
// @ts-ignore
|
|
380
|
+
const params = payload.params;
|
|
381
|
+
if ("sourceMapURL" in params) {
|
|
382
|
+
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
|
|
383
|
+
if (params.sourceMapURL.includes(hostToRewrite)) {
|
|
384
|
+
// @ts-ignore
|
|
385
|
+
payload.params.sourceMapURL = params.sourceMapURL.replace(hostToRewrite, "localhost");
|
|
386
|
+
debuggerInfo.originalSourceURLAddress = hostToRewrite;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
const sourceMapURL = this.tryParseHTTPURL(params.sourceMapURL);
|
|
390
|
+
if (sourceMapURL) {
|
|
391
|
+
try {
|
|
392
|
+
const sourceMap = await this.fetchText(sourceMapURL);
|
|
393
|
+
// @ts-ignore
|
|
394
|
+
payload.params.sourceMapURL = `data:application/json;charset=utf-8;base64,${Buffer.from(sourceMap).toString("base64")}`;
|
|
395
|
+
}
|
|
396
|
+
catch (exception) {
|
|
397
|
+
const exceptionMessage = exception instanceof Error
|
|
398
|
+
? exception.message
|
|
399
|
+
: String(exception);
|
|
400
|
+
this.sendErrorToDebugger(`Failed to fetch source map ${params.sourceMapURL}: ${exceptionMessage}`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
if ("url" in params) {
|
|
405
|
+
for (const hostToRewrite of REWRITE_HOSTS_TO_LOCALHOST) {
|
|
406
|
+
if (params?.url?.includes(hostToRewrite)) {
|
|
407
|
+
payload.params.url = params.url.replace(hostToRewrite, "localhost");
|
|
408
|
+
debuggerInfo.originalSourceURLAddress = hostToRewrite;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
if (payload.params.url.match(/^[0-9a-z]+$/)) {
|
|
412
|
+
payload.params.url = FILE_PREFIX + payload.params.url;
|
|
413
|
+
debuggerInfo.prependedFilePrefix = true;
|
|
414
|
+
}
|
|
415
|
+
if (params.scriptId != null) {
|
|
416
|
+
this.scriptIdToSourcePathMapping.set(params.scriptId, params.url);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
if (payload.method === "Runtime.executionContextCreated" &&
|
|
421
|
+
this.isLegacyPageReloading) {
|
|
422
|
+
debuggerInfo.socket.send(JSON.stringify({ method: "Runtime.executionContextsCleared" }));
|
|
423
|
+
const resumeMessage = { method: "Debugger.resume", id: 0 };
|
|
424
|
+
this.deviceEventReporter?.logRequest(resumeMessage, "proxy", {
|
|
425
|
+
pageId: this.debuggerConnection?.pageId ?? null,
|
|
426
|
+
frontendUserAgent: this.debuggerConnection?.userAgent ?? null,
|
|
427
|
+
prefersFuseboxFrontend: this.isPageFuseboxFrontend(this.debuggerConnection?.pageId ?? null),
|
|
428
|
+
});
|
|
429
|
+
this.sendMessageToDevice({
|
|
430
|
+
event: "wrappedEvent",
|
|
431
|
+
payload: {
|
|
432
|
+
pageId: this.mapToDevicePageId(debuggerInfo.pageId),
|
|
433
|
+
wrappedEvent: JSON.stringify(resumeMessage),
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
this.isLegacyPageReloading = false;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
interceptClientMessageForSourceFetching(req, debuggerInfo, socket) {
|
|
440
|
+
switch (req.method) {
|
|
441
|
+
case "Debugger.setBreakpointByUrl":
|
|
442
|
+
return this.processDebuggerSetBreakpointByUrl(req, debuggerInfo);
|
|
443
|
+
case "Debugger.getScriptSource":
|
|
444
|
+
this.processDebuggerGetScriptSource(req, socket);
|
|
445
|
+
return null;
|
|
446
|
+
default:
|
|
447
|
+
return req;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
processDebuggerSetBreakpointByUrl(req, debuggerInfo) {
|
|
451
|
+
if (debuggerInfo.originalSourceURLAddress != null) {
|
|
452
|
+
const processedReq = { ...req, params: { ...req.params } };
|
|
453
|
+
if (processedReq.params.url != null) {
|
|
454
|
+
processedReq.params.url = processedReq.params.url.replace("localhost", debuggerInfo.originalSourceURLAddress);
|
|
455
|
+
if (processedReq.params.url?.startsWith(FILE_PREFIX) &&
|
|
456
|
+
debuggerInfo.prependedFilePrefix) {
|
|
457
|
+
processedReq.params.url = processedReq.params.url.slice(FILE_PREFIX.length);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
if (processedReq.params.urlRegex != null) {
|
|
461
|
+
processedReq.params.urlRegex = processedReq.params.urlRegex.replace(/localhost/g, debuggerInfo.originalSourceURLAddress);
|
|
462
|
+
}
|
|
463
|
+
return processedReq;
|
|
464
|
+
}
|
|
465
|
+
return req;
|
|
466
|
+
}
|
|
467
|
+
processDebuggerGetScriptSource(req, socket) {
|
|
468
|
+
const sendSuccessResponse = (scriptSource) => {
|
|
469
|
+
const result = { scriptSource };
|
|
470
|
+
const response = {
|
|
471
|
+
id: req.id,
|
|
472
|
+
result,
|
|
473
|
+
};
|
|
474
|
+
socket.send(JSON.stringify(response));
|
|
475
|
+
const pageId = this.debuggerConnection?.pageId ?? null;
|
|
476
|
+
this.deviceEventReporter?.logResponse(response, "proxy", {
|
|
477
|
+
pageId,
|
|
478
|
+
frontendUserAgent: this.debuggerConnection?.userAgent ?? null,
|
|
479
|
+
prefersFuseboxFrontend: this.isPageFuseboxFrontend(pageId),
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
const sendErrorResponse = (error) => {
|
|
483
|
+
const result = { error: { message: error } };
|
|
484
|
+
const response = { id: req.id, result };
|
|
485
|
+
socket.send(JSON.stringify(response));
|
|
486
|
+
this.sendErrorToDebugger(error);
|
|
487
|
+
const pageId = this.debuggerConnection?.pageId ?? null;
|
|
488
|
+
this.deviceEventReporter?.logResponse(response, "proxy", {
|
|
489
|
+
pageId,
|
|
490
|
+
frontendUserAgent: this.debuggerConnection?.userAgent ?? null,
|
|
491
|
+
prefersFuseboxFrontend: this.isPageFuseboxFrontend(pageId),
|
|
492
|
+
});
|
|
493
|
+
};
|
|
494
|
+
const pathToSource = this.scriptIdToSourcePathMapping.get(req.params.scriptId);
|
|
495
|
+
if (pathToSource != null) {
|
|
496
|
+
const httpURL = this.tryParseHTTPURL(pathToSource);
|
|
497
|
+
if (httpURL) {
|
|
498
|
+
this.fetchText(httpURL).then((text) => sendSuccessResponse(text), (err) => sendErrorResponse(`Failed to fetch source url ${pathToSource}: ${err.message}`));
|
|
499
|
+
}
|
|
500
|
+
else {
|
|
501
|
+
let file;
|
|
502
|
+
try {
|
|
503
|
+
file = node_fs_1.default.readFileSync(node_path_1.default.resolve(this.projectRoot, pathToSource), "utf8");
|
|
504
|
+
sendSuccessResponse(file);
|
|
505
|
+
}
|
|
506
|
+
catch (err) {
|
|
507
|
+
const exceptionMessage = err instanceof Error ? err.message : String(err);
|
|
508
|
+
sendErrorResponse(`Failed to fetch source file ${pathToSource}: ${exceptionMessage}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
mapToDevicePageId(pageId) {
|
|
514
|
+
if (pageId === REACT_NATIVE_RELOADABLE_PAGE_ID &&
|
|
515
|
+
this.lastConnectedLegacyReactNativePage != null) {
|
|
516
|
+
return this.lastConnectedLegacyReactNativePage.id;
|
|
517
|
+
}
|
|
518
|
+
return pageId;
|
|
519
|
+
}
|
|
520
|
+
tryParseHTTPURL(url) {
|
|
521
|
+
let parsedURL = null;
|
|
522
|
+
try {
|
|
523
|
+
parsedURL = new node_url_1.URL(url);
|
|
524
|
+
}
|
|
525
|
+
catch { }
|
|
526
|
+
const protocol = parsedURL?.protocol;
|
|
527
|
+
if (protocol !== "http:" && protocol !== "https:") {
|
|
528
|
+
parsedURL = null;
|
|
529
|
+
}
|
|
530
|
+
return parsedURL;
|
|
531
|
+
}
|
|
532
|
+
async fetchText(url) {
|
|
533
|
+
const response = await fetch(url.toString());
|
|
534
|
+
if (!response.ok) {
|
|
535
|
+
throw new Error(`HTTP ${response.status} ${response.statusText}`);
|
|
536
|
+
}
|
|
537
|
+
const text = await response.text();
|
|
538
|
+
if (text.length > 350000000) {
|
|
539
|
+
throw new Error("file too large to fetch via HTTP");
|
|
540
|
+
}
|
|
541
|
+
return text;
|
|
542
|
+
}
|
|
543
|
+
sendErrorToDebugger(message) {
|
|
544
|
+
const debuggerSocket = this.debuggerConnection?.socket;
|
|
545
|
+
if (debuggerSocket && debuggerSocket.readyState === ws_1.default.OPEN) {
|
|
546
|
+
debuggerSocket.send(JSON.stringify({
|
|
547
|
+
method: "Runtime.consoleAPICalled",
|
|
548
|
+
params: {
|
|
549
|
+
args: [
|
|
550
|
+
{
|
|
551
|
+
type: "string",
|
|
552
|
+
value: message,
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
executionContextId: 0,
|
|
556
|
+
type: "error",
|
|
557
|
+
},
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
isPageFuseboxFrontend(pageId) {
|
|
562
|
+
const page = pageId == null ? null : this.pages.get(pageId);
|
|
563
|
+
if (page == null) {
|
|
564
|
+
return null;
|
|
565
|
+
}
|
|
566
|
+
return this.pageHasCapability(page, "prefersFuseboxFrontend");
|
|
567
|
+
}
|
|
568
|
+
dangerouslyGetSocket() {
|
|
569
|
+
return this.deviceSocket;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
exports.Device = Device;
|
|
573
|
+
const invariant = (condition, message) => {
|
|
574
|
+
if (!condition) {
|
|
575
|
+
throw new Error(message);
|
|
576
|
+
}
|
|
577
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
|
+
import type { EventReporter, PageDescription } from "./types";
|
|
4
|
+
import type { CDPAdapter } from "../cdp/cdp.adapter";
|
|
5
|
+
export interface InspectorOptions {
|
|
6
|
+
projectRoot: string;
|
|
7
|
+
serverBaseUrl: string;
|
|
8
|
+
eventReporter?: EventReporter;
|
|
9
|
+
cdpAdapter?: CDPAdapter;
|
|
10
|
+
}
|
|
11
|
+
export declare class Inspector {
|
|
12
|
+
private devices;
|
|
13
|
+
private deviceCounter;
|
|
14
|
+
private readonly projectRoot;
|
|
15
|
+
private readonly serverBaseUrl;
|
|
16
|
+
private readonly eventReporter?;
|
|
17
|
+
private readonly cdpAdapter?;
|
|
18
|
+
constructor(options: InspectorOptions);
|
|
19
|
+
getPageDescriptions(): PageDescription[];
|
|
20
|
+
handleHttpRequest(req: IncomingMessage, res: ServerResponse, next: (error?: Error) => void): void;
|
|
21
|
+
createWebSocketServers(): Record<string, WebSocketServer>;
|
|
22
|
+
private createPageDescription;
|
|
23
|
+
private createDeviceWebSocketServer;
|
|
24
|
+
private createDebuggerWebSocketServer;
|
|
25
|
+
private setupHeartbeat;
|
|
26
|
+
private sendJsonResponse;
|
|
27
|
+
}
|