2021-12-10 02:37:01 +00:00
|
|
|
/**
|
|
|
|
|
* @license
|
|
|
|
|
* Copyright Google LLC All Rights Reserved.
|
|
|
|
|
*
|
|
|
|
|
* Use of this source code is governed by an MIT-style license that can be
|
2024-09-20 15:23:15 +00:00
|
|
|
* found in the LICENSE file at https://angular.dev/license
|
2021-12-10 02:37:01 +00:00
|
|
|
*/
|
|
|
|
|
|
2025-05-08 13:20:37 +00:00
|
|
|
/// <reference types="chrome"/>
|
|
|
|
|
|
2021-12-09 05:44:17 +00:00
|
|
|
import {ChromeMessageBus} from './chrome-message-bus';
|
2026-02-10 19:30:50 +00:00
|
|
|
import {getBackendUri, getContentScriptUri, getDetectAngularScriptUri} from './comm-utils';
|
2021-12-09 05:44:17 +00:00
|
|
|
import {SamePageMessageBus} from './same-page-message-bus';
|
2020-01-27 18:40:18 +00:00
|
|
|
|
2024-03-05 03:54:58 +00:00
|
|
|
let backgroundDisconnected = false;
|
|
|
|
|
let backendInitialized = false;
|
2020-01-27 18:40:18 +00:00
|
|
|
|
2024-03-05 03:54:58 +00:00
|
|
|
const port = chrome.runtime.connect({
|
|
|
|
|
name: `${document.title || location.href}`,
|
|
|
|
|
});
|
2024-01-26 00:31:57 +00:00
|
|
|
|
2025-12-18 10:17:47 +00:00
|
|
|
// Since Manifest V3, the service worker (background)
|
|
|
|
|
// gets terminated after 30s of inactivity. This can
|
|
|
|
|
// break the initialization phase of DevTools or the
|
|
|
|
|
// BE-FE communication channel, if already initialized.
|
|
|
|
|
// To prevent that, we emit a heartbeat in a >30s interval.
|
|
|
|
|
const HEARTBEAT_INTERVAL = 20000; // Keep below 30s
|
|
|
|
|
const heartbeatInterval = setInterval(() => {
|
|
|
|
|
port.postMessage('__NG_DEVTOOLS_BEAT');
|
|
|
|
|
}, HEARTBEAT_INTERVAL);
|
|
|
|
|
|
2024-03-05 03:54:58 +00:00
|
|
|
const handleDisconnect = (): void => {
|
|
|
|
|
localMessageBus.emit('shutdown');
|
|
|
|
|
localMessageBus.destroy();
|
|
|
|
|
chromeMessageBus.destroy();
|
2025-12-18 10:17:47 +00:00
|
|
|
clearInterval(heartbeatInterval);
|
2024-03-05 03:54:58 +00:00
|
|
|
backgroundDisconnected = true;
|
|
|
|
|
};
|
2024-01-26 00:31:57 +00:00
|
|
|
|
2025-12-11 16:26:38 +00:00
|
|
|
function attemptBackendHandshake() {
|
|
|
|
|
if (!backendInitialized) {
|
|
|
|
|
// tslint:disable-next-line:no-console
|
|
|
|
|
console.log('Attempting handshake with backend', new Date());
|
|
|
|
|
|
|
|
|
|
const retry = () => {
|
|
|
|
|
if (backendInitialized || backgroundDisconnected) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
handshakeWithBackend();
|
|
|
|
|
setTimeout(retry, 500);
|
|
|
|
|
};
|
|
|
|
|
retry();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-05 03:54:58 +00:00
|
|
|
port.onDisconnect.addListener(handleDisconnect);
|
|
|
|
|
|
|
|
|
|
const detectAngularMessageBus = new SamePageMessageBus(
|
2026-03-05 23:32:20 +00:00
|
|
|
'[ContentScript=>DetectAngular]',
|
2026-02-10 19:30:50 +00:00
|
|
|
getContentScriptUri(),
|
|
|
|
|
getDetectAngularScriptUri(),
|
2024-03-05 03:54:58 +00:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
detectAngularMessageBus.on('detectAngular', (detectionResult) => {
|
|
|
|
|
if (detectionResult.isAngularDevTools !== true) {
|
|
|
|
|
return;
|
2024-01-26 00:31:57 +00:00
|
|
|
}
|
2024-03-05 03:54:58 +00:00
|
|
|
|
|
|
|
|
if (detectionResult.isAngular !== true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Defensive check against non html page. Realistically this should never happen.
|
|
|
|
|
if (document.contentType !== 'text/html') {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-14 19:56:17 +00:00
|
|
|
// Inform the background page so it can toggle the popup and icon.
|
|
|
|
|
void chrome.runtime.sendMessage(detectionResult);
|
|
|
|
|
|
2024-03-05 03:54:58 +00:00
|
|
|
const script = document.createElement('script');
|
|
|
|
|
script.src = chrome.runtime.getURL('app/backend_bundle.js');
|
|
|
|
|
document.documentElement.appendChild(script);
|
|
|
|
|
document.documentElement.removeChild(script);
|
2025-12-11 16:26:38 +00:00
|
|
|
|
2026-02-10 19:30:50 +00:00
|
|
|
detectAngularMessageBus.emit('backendInstalled', [detectionResult]);
|
2025-12-11 16:26:38 +00:00
|
|
|
|
|
|
|
|
attemptBackendHandshake();
|
2024-03-05 03:54:58 +00:00
|
|
|
});
|
|
|
|
|
|
2026-03-05 23:32:20 +00:00
|
|
|
const localMessageBus = new SamePageMessageBus(
|
|
|
|
|
'[ConstentScript=>BackEnd]',
|
|
|
|
|
getContentScriptUri(),
|
|
|
|
|
getBackendUri(),
|
|
|
|
|
);
|
2024-03-05 03:54:58 +00:00
|
|
|
const chromeMessageBus = new ChromeMessageBus(port);
|
|
|
|
|
|
|
|
|
|
const handshakeWithBackend = (): void => {
|
|
|
|
|
localMessageBus.emit('handshake');
|
2020-01-27 18:40:18 +00:00
|
|
|
};
|
2021-12-16 07:00:43 +00:00
|
|
|
|
2025-12-11 16:26:38 +00:00
|
|
|
// Relaying messages from FE to BE
|
2024-03-05 03:54:58 +00:00
|
|
|
chromeMessageBus.onAny((topic, args) => {
|
|
|
|
|
localMessageBus.emit(topic, args);
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-11 16:26:38 +00:00
|
|
|
// Relaying messages from BE to FE
|
2024-03-05 03:54:58 +00:00
|
|
|
localMessageBus.onAny((topic, args) => {
|
|
|
|
|
chromeMessageBus.emit(topic, args);
|
|
|
|
|
});
|
|
|
|
|
|
2025-12-11 16:26:38 +00:00
|
|
|
localMessageBus.on('backendReady', () => {
|
|
|
|
|
backendInitialized = true;
|
|
|
|
|
});
|
2024-05-06 23:06:31 +00:00
|
|
|
|
|
|
|
|
const proxyEventFromWindowToDevToolsExtension = (event: MessageEvent) => {
|
2025-07-18 23:25:57 +00:00
|
|
|
if (event.source === window && event.data && event.data.__NG_DEVTOOLS_EVENT__) {
|
2024-05-06 23:06:31 +00:00
|
|
|
try {
|
|
|
|
|
chrome.runtime.sendMessage(event.data);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
const {message} = e as Error;
|
|
|
|
|
if (message.includes('Extension context invalidated.')) {
|
|
|
|
|
console.error(
|
|
|
|
|
'Angular DevTools: Disconnecting content script due to invalid extension context. Please reload the page.',
|
|
|
|
|
);
|
|
|
|
|
window.removeEventListener('message', proxyEventFromWindowToDevToolsExtension);
|
|
|
|
|
}
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
window.addEventListener('message', proxyEventFromWindowToDevToolsExtension);
|