Deduplicate pages by ID within each device (#42282)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/42282

Changelog: [Internal] `inspector-proxy` now assumes each app will report pages with locally unique IDs.

In order to simplify some upcoming logic changes in `inspector-proxy`, in this diff we begin to enforce the assumption that each app ( = platform-specific implementation of `InspectorPackagerConnection`) assigns a locally unique ID to each inspector page. The inspector proxy will silently drop page descriptors that have conflicting IDs, and log a message to `debug()`.

NOTE: As an implementation detail, integrators may use `DEBUG=Metro:InspectorProxy` to see debug messages from `inspector-proxy`.

Reviewed By: huntie

Differential Revision: D50969752

fbshipit-source-id: a4e6faa91d97594fc5343ce4bee66233523cd175
This commit is contained in:
Moti Zilberman
2024-01-17 22:53:42 -08:00
committed by Facebook GitHub Bot
parent 67532e07ce
commit 8ac08c8d18
2 changed files with 65 additions and 10 deletions
@@ -220,6 +220,45 @@ xdescribe('inspector proxy HTTP API', () => {
}
});
test('removes pages with duplicate IDs', async () => {
const device1 = await createDeviceMock(
`${serverRef.serverBaseWsUrl}/inspector/device?device=device1&name=foo&app=bar`,
autoCleanup.signal,
);
try {
device1.getPages.mockImplementation(() => [
{
app: 'bar-app',
id: 'page1',
title: 'bar-title',
vm: 'bar-vm',
},
{
app: 'bar-app-other',
id: 'page1',
title: 'bar-title-other',
vm: 'bar-vm-other',
},
]);
jest.advanceTimersByTime(PAGES_POLLING_DELAY);
const json = await fetchJson<JsonPagesListResponse>(
`${serverRef.serverBaseUrl}${endpoint}`,
);
expect(json).toEqual([
expect.objectContaining({
id: 'device1-page1',
title: 'bar-title-other',
vm: 'bar-vm-other',
}),
]);
} finally {
device1.close();
}
});
describe('HTTP vs HTTPS', () => {
const secureServerRef = withServerForEachTest({
logger: undefined,
@@ -68,8 +68,8 @@ export default class Device {
// Stores socket connection between Inspector Proxy and device.
#deviceSocket: WS;
// Stores last list of device's pages.
#pages: $ReadOnlyArray<Page>;
// Stores the most recent listing of device's pages, keyed by the `id` field.
#pages: $ReadOnlyMap<string, Page>;
// Stores information about currently connected debugger (if any).
#debuggerConnection: ?DebuggerInfo = null;
@@ -106,7 +106,7 @@ export default class Device {
this.#id = id;
this.#name = name;
this.#app = app;
this.#pages = [];
this.#pages = new Map();
this.#deviceSocket = socket;
this.#projectRoot = projectRoot;
this.#deviceEventReporter = eventReporter
@@ -166,9 +166,9 @@ export default class Device {
vm: "don't use",
app: this.#app,
};
return this.#pages.concat(reactNativeReloadablePage);
return [...this.#pages.values(), reactNativeReloadablePage];
} else {
return this.#pages;
return [...this.#pages.values()];
}
}
@@ -302,17 +302,33 @@ export default class Device {
// locations).
#handleMessageFromDevice(message: MessageFromDevice) {
if (message.event === 'getPages') {
this.#pages = message.payload;
this.#pages = new Map(message.payload.map(page => [page.id, page]));
if (message.payload.length !== this.#pages.size) {
const duplicateIds = new Set<string>();
const idsSeen = new Set<string>();
for (const page of message.payload) {
if (!idsSeen.has(page.id)) {
idsSeen.add(page.id);
} else {
duplicateIds.add(page.id);
}
}
debug(
`Received duplicate page IDs from device: ${[...duplicateIds].join(
', ',
)}`,
);
}
// Check if device have new React Native page.
// There is usually no more than 2-3 pages per device so this operation
// is not expensive.
// TODO(hypuk): It is better for VM to send update event when new page is
// created instead of manually checking this on every getPages result.
for (let i = 0; i < this.#pages.length; ++i) {
if (this.#pages[i].title.indexOf('React') >= 0) {
if (this.#pages[i].id !== this.#lastConnectedReactNativePage?.id) {
this.#newReactNativePage(this.#pages[i]);
for (const page of this.#pages.values()) {
if (page.title.indexOf('React') >= 0) {
if (page.id !== this.#lastConnectedReactNativePage?.id) {
this.#newReactNativePage(page);
break;
}
}