메시지 API를 사용하면 확장 프로그램과 연결된 컨텍스트에서 실행되는 여러 스크립트 간에 통신할 수 있습니다. 여기에는 서비스 워커, chrome-extension://pages, 콘텐츠 스크립트 간의 통신이 포함됩니다. 예를 들어 RSS 리더 확장 프로그램은 콘텐츠 스크립트를 사용하여 페이지에 RSS 피드가 있는지 감지한 다음 서비스 워커에 알림을 보내 해당 페이지의 작업 아이콘을 업데이트할 수 있습니다.
메시지 전달 API에는 두 가지가 있습니다. 일회성 요청을 위한 API와 여러 메시지를 전송할 수 있는 장기 연결을 위한 더 복잡한 API입니다.
확장 프로그램 간에 메시지를 전송하는 방법에 관한 자세한 내용은 교차 확장 프로그램 메시지 섹션을 참고하세요.
일회성 요청
확장 프로그램의 다른 부분에 단일 메시지를 전송하고 선택적으로 응답을 받으려면 응답을 받으려면 runtime.sendMessage() 또는 tabs.sendMessage()를 호출합니다.
이러한 메서드를 사용하면 콘텐츠 스크립트에서 확장 프로그램으로 또는 확장 프로그램에서 콘텐츠 스크립트로 일회성 JSON 직렬화 가능 메시지를 전송할 수 있습니다. 두 API 모두 수신자가 제공한 응답으로 확인되는 프로미스를 반환합니다.
콘텐츠 스크립트에서 요청을 전송하는 방법은 다음과 같습니다.
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) {
if (message !== 'get-status') return;
fetch('https://example.com')
.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('get-status');
이벤트 리스너가 호출되면 sendResponse 함수가 세 번째 매개변수로 전달됩니다. 이 함수는 응답을 제공하기 위해 호출할 수 있는 함수입니다. 기본적으로 sendResponse 콜백은 동기적으로 호출되어야 합니다.
매개변수 없이 sendResponse를 호출하면 null이 응답으로 전송됩니다.
비동기적으로 응답을 전송하려면 true를 반환하거나 프로미스를 반환하는 두 가지 옵션이 있습니다.
true 반환
sendResponse()를 사용하여 비동기적으로 응답하려면 이벤트 리스너에서 리터럴 true(truthy 값이 아님)를 반환합니다. 이렇게 하면 sendResponse가 호출될 때까지 메시지 채널이 다른 끝에 열린 상태로 유지되므로 나중에 호출할 수 있습니다.
프로미스 반환
Chrome 146부터 메시지 리스너에서 프로미스를 반환하여 비동기적으로 응답할 수 있습니다. 이 업데이트는 점진적으로 출시되므로 일부 사용자의 브라우저에서는 아직 제공되지 않을 수 있습니다. 확장 프로그램에서 이 기능을 사용 설정할 수 있는지 여부를 처리할 수 있는지 확인해야 합니다. return true;를 사용하면 이 기능이 사용 설정되어 있는지 여부와 관계없이 비동기 응답이 계속 작동합니다.
프로미스가 확인되면 확인된 값이 응답으로 전송됩니다.
프로미스가 거부되면 발신자의 sendMessage() 호출이 오류 메시지와 함께 거부됩니다. 자세한 내용과 예는
오류 처리 섹션을 참고하세요.
확인되거나 거부될 수 있는 프로미스를 반환하는 방법을 보여주는 예는 다음과 같습니다.
// Event listener
function handleMessages(message, sender, sendResponse) {
// Return a promise that wraps fetch
// If the response is OK, resolve with the status. If it's not OK then reject
// with the network error that prevents the fetch from completing.
return new Promise((resolve, reject) => {
fetch('https://example.com')
.then(response => {
if (!response.ok) {
reject(response);
} else {
resolve(response.status);
}
})
.catch(error => {
reject(error);
});
});
}
chrome.runtime.onMessage.addListener(handleMessages);
리스너를 async로 선언하여 프로미스를 반환할 수도 있습니다.
chrome.runtime.onMessage.addListener(async function(message, sender) {
const response = await fetch('https://example.com');
if (!response.ok) {
// rejects the promise returned by `async function`.
throw new Error(`Fetch failed: ${response.status}`);
}
// resolves the promise returned by `async function`.
return {statusCode: response.status};
});
프로미스 반환: async 함수 주의사항
리스너로서 async 함수는 return 문이 없어도 항상 프로미스를 반환합니다. async 리스너가 값을 반환하지 않으면 프로미스가 암시적으로 undefined로 확인되고 null이 발신자에게 응답으로 전송됩니다. 이로 인해 리스너가 여러 개 있는 경우 예기치 않은 동작이 발생할 수 있습니다.
// content_script.js
function handleResponse(message) {
// The first listener promise resolves to `undefined` before the second
// listener can respond. When a listener responds with `undefined`, Chrome
// sends null as the response.
console.assert(message === null);
}
function notifyBackgroundPage() {
const sending = chrome.runtime.sendMessage('test');
sending.then(handleResponse);
}
notifyBackgroundPage();
// background.js
chrome.runtime.onMessage.addListener(async (message) => {
// This just causes the function to pause for a millisecond, but the promise
// is *not* returned from the listener so it doesn't act as a response.
await new Promise(resolve => {
setTimeout(resolve, 1, 'OK');
});
// `async` functions always return promises. So once we
// reach here there is an implicit `return undefined;`. Chrome translates
// `undefined` responses to `null`.
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
return new Promise((resolve) => {
setTimeout(resolve, 1000, 'response');
});
});
오류 처리
Chrome 146부터 onMessage 리스너가 오류를 발생시키면 (동기적으로 또는 거부되는 프로미스를 반환하여 비동기적으로) 발신자의 sendMessage()에서 반환된 프로미스가 오류 메시지와 함께 거부됩니다. 이 업데이트는 점진적으로 출시되므로 일부 사용자의 브라우저에서는 아직 이 변경사항이 적용되지 않을 수 있습니다. 확장 프로그램에서 이 변경사항을 사용 설정할 수 있는지 확인해야 합니다.
리스너가 직렬화할 수 없는 응답을 반환하려고 하면 (결과 TypeError를 포착하지 않고) 리스너가 오류를 발생시키는 것으로 간주됩니다.
리스너가 거부되는 프로미스를 반환하는 경우 발신자가 오류 메시지를 수신하려면 Error 인스턴스와 함께 거부해야 합니다. 프로미스가 다른 값 (null 또는 undefined 등)과 함께 거부되면 sendMessage()가 대신 일반 오류 메시지와 함께 거부됩니다.
onMessage에 여러 리스너가 등록된 경우 응답, 거부 또는 오류를 발생시키는 첫 번째 리스너만 발신자에게 영향을 미칩니다. 다른 모든 리스너는 실행되지만 결과는 무시됩니다.
예
리스너가 거부되는 프로미스를 반환하면 sendMessage()가 거부됩니다.
// sender.js
try {
await chrome.runtime.sendMessage('test');
} catch (e) {
console.log(e.message); // "some error"
}
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
return Promise.reject(new Error('some error'));
});
리스너가 직렬화할 수 없는 값으로 응답하면 sendMessage()가 거부됩니다.
// sender.js
try {
await chrome.runtime.sendMessage('test');
} catch (e) {
console.log(e.message); // "Error: Could not serialize message."
}
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse(() => {}); // Functions are not serializable
return true; // Keep channel open for async sendResponse
});
다른 리스너가 응답하기 전에 리스너가 동기적으로 오류를 발생시키면 리스너의 sendMessage() 프로미스가 거부됩니다.
// sender.js
try {
await chrome.runtime.sendMessage('test');
} catch (e) {
console.log(e.message); // "error!"
}
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
throw new Error('error!');
});
그러나 한 리스너가 다른 리스너가 오류를 발생시키기 전에 응답하면 sendMessage()가 성공합니다.
// sender.js
const response = await chrome.runtime.sendMessage('test');
console.log(response); // "OK"
// listener.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
sendResponse('OK');
});
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
throw new Error('error!');
});
장기 연결
재사용 가능한 장기 메시지 전달 채널을 만들려면 다음을 호출합니다.
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 직렬화를 사용합니다. 특히 이는 구조화된 클론 알고리즘으로 동일한 API를 구현하는 다른 브라우저와 다릅니다.
즉, 메시지 (및 수신자가 제공한 응답)에는 유효한
유효한
JSON.stringify()
값이 포함될 수 있습니다. 다른 값은 직렬화 가능한 값으로 강제 변환됩니다 (특히 undefined는 null로 직렬화됨).
메시지 크기 한도
메시지의 최대 크기는 64MiB입니다.
포트 수명
포트는 확장 프로그램의 여러 부분 간의 양방향 통신 메커니즘으로 설계되었습니다. 확장 프로그램의 일부가
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()가 다른 끝 에서 호출됩니다. If aconnect()호출로 인해 수신자 끝에 여러 포트가 생성되고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/*"]
}
이렇게 하면 지정한 일치 패턴과 일치하는 모든 페이지에 메시지 API가 노출됩니다.
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; });