Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 61 additions & 46 deletions extension/panel-bridge.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,65 +7,80 @@ const status = document.getElementById('status');
const tabId = chrome.devtools.inspectedWindow.tabId;
const LOCAL_HOSTS = ['localhost', '127.0.0.1'];

// Try to find the devframe connection on the inspected page
// Where devframe may be mounted.
const PATHS = ['/__ng-devtools/', '/__devframe/', '/'];
const CONNECTION_FILES = ['__devframe/__connection.json', '__connection.json'];
const PROBE_TIMEOUT_MS = 1500;

let detection = 0;

// Look for a devframe connection, but only on a loopback page: nothing else
// can be connected to, so nothing else is worth probing.
function detectConnection() {
// Try common devframe mount paths
const paths = ['/__ng-devtools/', '/__devframe/', '/'];
const run = ++detection;
chrome.devtools.inspectedWindow.eval('location.origin', (origin, error) => {
if (run !== detection) return;
if (error || typeof origin !== 'string') {
loadPanel(null);
return;
}

chrome.devtools.inspectedWindow.eval(
`(function() {
const paths = ${JSON.stringify(paths)};
for (const base of paths) {
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', base + '__devframe/__connection.json', false);
xhr.send();
if (xhr.status === 200) {
return { base: base, connection: JSON.parse(xhr.responseText) };
}
} catch(e) {}
try {
const xhr = new XMLHttpRequest();
xhr.open('GET', base + '__connection.json', false);
xhr.send();
if (xhr.status === 200) {
return { base: base, connection: JSON.parse(xhr.responseText) };
}
} catch(e) {}
}
return null;
})()`,
(result, err) => {
if (result && paths.includes(result.base)) {
loadPanel(result.base);
} else {
// No live devframe found — load in standalone/static mode
loadPanel(null);
let hostname;
try {
hostname = new URL(origin).hostname;
} catch {
loadPanel(null);
return;
}

if (!LOCAL_HOSTS.includes(hostname)) {
loadPanel(null);
return;
}

findConnection(origin).then((base) => {
if (run === detection) loadPanel(base, origin);
});
});
}

// The first mount path that answers with a connection file, or null.
async function findConnection(origin) {
for (const base of PATHS) {
for (const file of CONNECTION_FILES) {
try {
const response = await fetch(new URL(base + file, origin), {
Comment thread
erkamyaman marked this conversation as resolved.
credentials: 'omit',
cache: 'no-store',
redirect: 'error',
signal: AbortSignal.timeout(PROBE_TIMEOUT_MS),
});
if (!response.ok) continue;
await response.json();
return base;
} catch {
// Not mounted here; try the next one.
}
},
);
}
}
return null;
}

function loadPanel(baseURL) {
function loadPanel(baseURL, origin) {
status.classList.add('hidden');
frame.style.display = 'block';

// The SPA is bundled inside the extension at ui/index.html
const panelUrl = chrome.runtime.getURL('ui/index.html');

if (baseURL) {
// Get the inspected page's origin to build the full baseURL
chrome.devtools.inspectedWindow.eval('location.origin', (origin) => {
const url = new URL(baseURL, origin);
if (!LOCAL_HOSTS.includes(url.hostname)) {
frame.src = panelUrl;
return;
}
frame.src = `${panelUrl}?baseURL=${encodeURIComponent(url.href)}`;
});
} else {
if (!baseURL || !origin) {
frame.src = panelUrl;
return;
}
const url = new URL(baseURL, origin);
frame.src = LOCAL_HOSTS.includes(url.hostname)
? `${panelUrl}?baseURL=${encodeURIComponent(url.href)}`
: panelUrl;
}

// Start detection after a short delay to let the page settle
Expand Down
Loading