메시지 API를 사용하면 확장 프로그램과 연결된 컨텍스트에서 실행되는 여러 스크립트 간에 통신할 수 있습니다. 여기에는 서비스 워커, chrome-extension://페이지, 콘텐츠 스크립트 간의 통신이 포함됩니다. 예를 들어 RSS 리더 확장 프로그램은 콘텐츠 스크립트를 사용하여 페이지에 RSS 피드가 있는지 감지한 다음 서비스 워커에 해당 페이지의 작업 아이콘을 업데이트하도록 알릴 수 있습니다.
메시지 전달 API에는 두 가지가 있습니다. 하나는 일회성 요청용이고 다른 하나는 여러 메시지를 전송할 수 있는 장기 연결용으로 더 복잡합니다.
확장 프로그램 간 메시지 전송에 관한 자세한 내용은 크로스 확장 프로그램 메시지 섹션을 참고하세요.
일회성 요청
확장 프로그램의 다른 부분에 단일 메시지를 보내고 선택적으로 응답을 받으려면 runtime.sendMessage()
또는 tabs.sendMessage()
를 호출합니다.
이러한 메서드를 사용하면 콘텐츠 스크립트에서 확장 프로그램으로 또는 확장 프로그램에서 콘텐츠 스크립트로 일회성 JSON 직렬화 가능 메시지를 보낼 수 있습니다. 두 API 모두 수신자가 제공한 응답으로 확인되는 Promise를 반환합니다.
콘텐츠 스크립트에서 요청을 보내는 방법은 다음과 같습니다.
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
응답
메시지를 수신 대기하려면 chrome.runtime.onMessage
이벤트를 사용하세요.
// Event listener
function handleMessages(message, sender, sendResponse) {
fetch(message.url)
.then((response) => sendResponse({statusCode: response.status}))
// Since `fetch` is asynchronous, must return an explicit `true`
return true;
}
chrome.runtime.onMessage.addListener(handleMessages);
// From the sender's context...
const {statusCode} = await chrome.runtime.sendMessage({
url: 'https://example.com'
});
이벤트 리스너가 호출되면 sendResponse
함수가 세 번째 매개변수로 전달됩니다. 응답을 제공하기 위해 호출할 수 있는 함수입니다. 기본적으로 sendResponse
콜백은 동기적으로 호출되어야 합니다. sendResponse
에 전달된 값을 가져오기 위해 비동기 작업을 실행하려면 이벤트 리스너에서 리터럴 true
(참 값이 아님)를 반환해야 합니다. 이렇게 하면 sendResponse
가 호출될 때까지 메시지 채널이 다른 쪽으로 열린 상태로 유지됩니다.
매개변수 없이 sendResponse
를 호출하면 null
이 응답으로 전송됩니다.
여러 페이지에서 onMessage
이벤트를 수신 대기하는 경우 특정 이벤트에 대해 sendResponse()
를 처음 호출하는 페이지에서만 응답을 전송할 수 있습니다. 해당 이벤트에 대한 다른 모든 응답은 무시됩니다.
장기 연결
재사용 가능한 장기 메시지 전달 채널을 만들려면 다음을 호출하세요.
- 콘텐츠 스크립트에서 확장 프로그램 페이지로 메시지를 전달하는
runtime.connect()
tabs.connect()
를 사용하여 확장 프로그램 페이지에서 콘텐츠 스크립트로 메시지를 전달합니다.
name
키가 있는 옵션 매개변수를 전달하여 다양한 유형의 연결을 구분할 수 있도록 채널 이름을 지정할 수 있습니다.
const port = chrome.runtime.connect({name: "example"});
장기 연결의 한 가지 잠재적 사용 사례는 자동 양식 채우기 확장 프로그램입니다. 콘텐츠 스크립트는 특정 로그인에 대해 확장 프로그램 페이지로 채널을 열고 페이지의 각 입력 요소에 대해 확장 프로그램에 메시지를 전송하여 채울 양식 데이터를 요청할 수 있습니다. 공유 연결을 사용하면 확장 프로그램이 확장 프로그램 구성요소 간에 상태를 공유할 수 있습니다.
연결을 설정할 때 각 끝에는 해당 연결을 통해 메시지를 보내고 받기 위한 runtime.Port
객체가 할당됩니다.
다음 코드를 사용하여 콘텐츠 스크립트에서 채널을 열고 메시지를 전송하고 수신합니다.
content-script.js:
const port = chrome.runtime.connect({name: "knockknock"});
port.onMessage.addListener(function(msg) {
if (msg.question === "Who's there?") {
port.postMessage({answer: "Madame"});
} else if (msg.question === "Madame who?") {
port.postMessage({answer: "Madame... Bovary"});
}
});
port.postMessage({joke: "Knock knock"});
확장 프로그램에서 콘텐츠 스크립트로 요청을 보내려면 이전 예의 runtime.connect()
호출을 tabs.connect()
로 바꿉니다.
콘텐츠 스크립트 또는 확장 프로그램 페이지의 수신 연결을 처리하려면 runtime.onConnect
이벤트 리스너를 설정합니다. 확장의 다른 부분에서 connect()
를 호출하면 이 이벤트와 runtime.Port
객체가 활성화됩니다. 수신 연결에 응답하는 코드는 다음과 같습니다.
service-worker.js:
chrome.runtime.onConnect.addListener(function(port) {
if (port.name !== "knockknock") {
return;
}
port.onMessage.addListener(function(msg) {
if (msg.joke === "Knock knock") {
port.postMessage({question: "Who's there?"});
} else if (msg.answer === "Madame") {
port.postMessage({question: "Madame who?"});
} else if (msg.answer === "Madame... Bovary") {
port.postMessage({question: "I don't get it."});
}
});
});
직렬화
Chrome에서 메시지 전달 API는 JSON 직렬화를 사용합니다. 즉, 메시지(및 수신자가 제공한 응답)에는 유효한 JSON 값 (null, 불리언, 숫자, 문자열, 배열 또는 객체)이 포함될 수 있습니다. 다른 값은 직렬화 가능한 값으로 강제 변환됩니다.
특히 이는 구조화된 클론 알고리즘으로 동일한 API를 구현하는 다른 브라우저와는 다릅니다.
포트 수명
포트는 확장 프로그램의 여러 부분 간 양방향 통신 메커니즘으로 설계됩니다. 확장 프로그램의 일부가 tabs.connect()
, runtime.connect()
또는 runtime.connectNative()
를 호출하면 postMessage()
를 사용하여 즉시 메시지를 보낼 수 있는 포트가 생성됩니다.
탭에 프레임이 여러 개 있는 경우 tabs.connect()
를 호출하면 탭의 각 프레임에 대해 runtime.onConnect
이벤트가 한 번씩 호출됩니다. 마찬가지로 runtime.connect()
가 호출되면 onConnect
이벤트가 확장 프로그램 프로세스의 모든 프레임에 대해 한 번씩 발생할 수 있습니다.
예를 들어 열린 각 포트에 대해 별도의 상태를 유지하는 경우 연결이 닫히는 시점을 알아야 할 수 있습니다. 이렇게 하려면 runtime.Port.onDisconnect
이벤트를 수신 대기합니다. 이 이벤트는 채널의 다른 쪽에 유효한 포트가 없을 때 발생하며, 이는 다음 중 한 가지 원인일 수 있습니다.
- 다른 쪽 끝에
runtime.onConnect
리스너가 없습니다. - 포트를 포함하는 탭이 언로드됩니다 (예: 탭이 탐색된 경우).
connect()
가 호출된 프레임이 언로드되었습니다.- 포트를 수신한 모든 프레임 (
runtime.onConnect
을 통해)이 언로드되었습니다. runtime.Port.disconnect()
은 다른 쪽 끝에서 호출됩니다.connect()
호출로 인해 수신자 측에 여러 포트가 생성되고 이러한 포트 중 하나에서disconnect()
이 호출되면onDisconnect
이벤트는 다른 포트가 아닌 전송 포트에서만 발생합니다.
교차 확장 프로그램 메시지
확장 프로그램의 여러 구성요소 간에 메시지를 전송하는 것 외에도 메시지 API를 사용하여 다른 확장 프로그램과 통신할 수 있습니다. 이렇게 하면 다른 확장 프로그램이 사용할 수 있는 공개 API를 노출할 수 있습니다.
다른 확장 프로그램에서 수신되는 요청과 연결을 수신 대기하려면 runtime.onMessageExternal
또는 runtime.onConnectExternal
메서드를 사용하세요. 각각의 예는 다음과 같습니다.
service-worker.js
// For a single request:
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.id !== allowlistedExtension) {
return; // don't allow this extension access
}
if (request.getTargetData) {
sendResponse({ targetData: targetData });
} else if (request.activateLasers) {
const success = activateLasers();
sendResponse({ activateLasers: success });
}
}
);
// For long-lived connections:
chrome.runtime.onConnectExternal.addListener(function(port) {
port.onMessage.addListener(function(msg) {
// See other examples for sample onMessage handlers.
});
});
다른 확장 프로그램에 메시지를 보내려면 통신하려는 확장 프로그램의 ID를 다음과 같이 전달하세요.
service-worker.js
// The ID of the extension we want to talk to.
const laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";
// For a minimal request:
chrome.runtime.sendMessage(laserExtensionId, {getTargetData: true},
function(response) {
if (targetInRange(response.targetData))
chrome.runtime.sendMessage(laserExtensionId, {activateLasers: true});
}
);
// For a long-lived connection:
const port = chrome.runtime.connect(laserExtensionId);
port.postMessage(...);
웹페이지에서 메시지 보내기
확장은 웹페이지의 메시지를 수신하고 이에 응답할 수도 있습니다. 웹페이지에서 확장 프로그램으로 메시지를 보내려면 manifest.json
에서 "externally_connectable"
매니페스트 키를 사용하여 메시지를 허용할 웹사이트를 지정합니다. 예를 들면 다음과 같습니다.
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
이렇게 하면 지정한 URL 패턴과 일치하는 모든 페이지에 메시지 API가 노출됩니다. URL 패턴에는 2단계 도메인이 하나 이상 포함되어야 합니다. 즉, '*', '*.com', '*.co.uk', '*.appspot.com'과 같은 호스트 이름 패턴은 지원되지 않습니다. <all_urls>
을 사용하여 모든 도메인에 액세스할 수 있습니다.
runtime.sendMessage()
또는 runtime.connect()
API를 사용하여 특정 확장 프로그램에 메시지를 보냅니다. 예를 들면 다음과 같습니다.
webpage.js
// The ID of the extension we want to talk to.
const editorExtensionId = 'abcdefghijklmnoabcdefhijklmnoabc';
// Check if extension is installed
if (chrome && chrome.runtime) {
// Make a request:
chrome.runtime.sendMessage(
editorExtensionId,
{
openUrlInEditor: url
},
(response) => {
if (!response.success) handleError(url);
}
);
}
확장 프로그램에서 교차 확장 프로그램 메시지와 같이 runtime.onMessageExternal
또는 runtime.onConnectExternal
API를 사용하여 웹페이지의 메시지를 수신 대기합니다. 예를 들면 다음과 같습니다.
service-worker.js
chrome.runtime.onMessageExternal.addListener(
function(request, sender, sendResponse) {
if (sender.url === blocklistedWebsite)
return; // don't allow this web page access
if (request.openUrlInEditor)
openUrl(request.openUrlInEditor);
});
확장 프로그램에서 웹페이지로 메시지를 보낼 수는 없습니다.
기본 메시지
확장 프로그램은 기본 메시지 호스트로 등록된 기본 애플리케이션과 메시지를 교환할 수 있습니다. 이 기능에 대해 자세히 알아보려면 네이티브 메시지를 참고하세요.
보안 고려사항
다음은 메시지와 관련된 몇 가지 보안 고려사항입니다.
콘텐츠 스크립트의 신뢰도가 낮음
콘텐츠 스크립트는 확장 프로그램 서비스 워커보다 신뢰성이 낮습니다. 예를 들어 악성 웹페이지가 콘텐츠 스크립트를 실행하는 렌더링 프로세스를 손상시킬 수 있습니다. 콘텐츠 스크립트의 메시지가 공격자에 의해 작성되었을 수 있다고 가정하고 모든 입력을 검증하고 정리해야 합니다. 콘텐츠 스크립트로 전송된 데이터가 웹페이지로 유출될 수 있다고 가정합니다. 콘텐츠 스크립트에서 수신한 메시지로 트리거할 수 있는 권한이 있는 작업의 범위를 제한합니다.
교차 사이트 스크립팅
크로스 사이트 스크립팅으로부터 스크립트를 보호해야 합니다. 사용자 입력, 콘텐츠 스크립트를 통한 다른 웹사이트, API와 같은 신뢰할 수 없는 소스에서 데이터를 수신할 때는 이를 HTML로 해석하거나 예상치 못한 코드가 실행될 수 있는 방식으로 사용하지 않도록 주의하세요.
가능하면 스크립트를 실행하지 않는 API를 사용하세요.
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // JSON.parse doesn't evaluate the attacker's scripts. const resp = JSON.parse(response.farewell); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // innerText does not let the attacker inject HTML elements. document.getElementById("resp").innerText = response.farewell; });
확장 프로그램을 취약하게 만드는 다음 방법을 사용하지 마세요.
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be evaluating a malicious script! const resp = eval(`(${response.farewell})`); });
service-worker.js
chrome.tabs.sendMessage(tab.id, {greeting: "hello"}, function(response) { // WARNING! Might be injecting a malicious script! document.getElementById("resp").innerHTML = response.farewell; });