API nhắn tin cho phép bạn giao tiếp giữa các tập lệnh khác nhau đang chạy trong các bối cảnh liên kết với tiện ích của bạn. Điều này bao gồm cả hoạt động giao tiếp giữa trình chạy dịch vụ, chrome-extension://pages và tập lệnh nội dung. Ví dụ: tiện ích trình đọc RSS có thể sử dụng tập lệnh nội dung để phát hiện sự hiện diện của một nguồn cấp dữ liệu RSS trên một trang, sau đó thông báo cho worker dịch vụ để cập nhật biểu tượng thao tác cho trang đó.
Có hai API truyền thông báo: một cho yêu cầu một lần và một API phức tạp hơn cho các kết nối duy trì lâu dài cho phép gửi nhiều thông báo.
Để biết thông tin về cách gửi thông báo giữa các tiện ích, hãy xem phần thông báo giữa các tiện ích.
Yêu cầu một lần
Để gửi một thông báo duy nhất đến một phần khác của tiện ích và nhận phản hồi (không bắt buộc), hãy gọi runtime.sendMessage()
hoặc tabs.sendMessage()
.
Các phương thức này cho phép bạn gửi một thông báo có thể chuyển đổi tuần tự thành JSON một lần từ một tập lệnh nội dung đến tiện ích, hoặc từ tiện ích đến một tập lệnh nội dung. Cả hai API đều trả về một Promise phân giải thành phản hồi do người nhận cung cấp.
Việc gửi yêu cầu từ một tập lệnh nội dung sẽ diễn ra như sau:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Phản hồi
Để theo dõi một thông báo, hãy sử dụng sự kiện 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'
});
Khi trình nghe sự kiện được gọi, một hàm sendResponse
sẽ được truyền dưới dạng tham số thứ ba. Đây là một hàm có thể được gọi để đưa ra phản hồi. Theo mặc định, bạn phải gọi lệnh gọi lại sendResponse
một cách đồng bộ. Nếu muốn thực hiện công việc không đồng bộ để nhận giá trị được truyền đến sendResponse
, bạn phải trả về một giá trị cố định true
(không chỉ là một giá trị đúng) từ trình nghe sự kiện. Thao tác này sẽ giữ cho kênh tin nhắn mở ở đầu kia cho đến khi sendResponse
được gọi.
Nếu bạn gọi sendResponse
mà không có tham số nào, thì null
sẽ được gửi dưới dạng phản hồi.
Nếu nhiều trang đang theo dõi các sự kiện onMessage
, thì chỉ trang đầu tiên gọi sendResponse()
cho một sự kiện cụ thể mới gửi được phản hồi. Tất cả các phản hồi khác cho sự kiện đó sẽ bị bỏ qua.
Kết nối lâu dài
Để tạo một kênh truyền thông điệp có thể sử dụng lại trong thời gian dài, hãy gọi:
runtime.connect()
để truyền thông báo từ một tập lệnh nội dung đến một trang tiện íchtabs.connect()
để truyền thông báo từ trang tiện ích đến một tập lệnh nội dung.
Bạn có thể đặt tên cho kênh bằng cách truyền một tham số lựa chọn có khoá name
để phân biệt giữa các loại kết nối:
const port = chrome.runtime.connect({name: "example"});
Một trường hợp sử dụng tiềm năng cho kết nối duy trì lâu dài là tiện ích tự động điền biểu mẫu. Tập lệnh nội dung có thể mở một kênh đến trang tiện ích cho một lần đăng nhập cụ thể và gửi một thông báo đến tiện ích cho từng phần tử đầu vào trên trang để yêu cầu dữ liệu biểu mẫu cần điền. Kết nối dùng chung cho phép tiện ích chia sẻ trạng thái giữa các thành phần của tiện ích.
Khi thiết lập một kết nối, mỗi đầu sẽ được chỉ định một đối tượng runtime.Port
để gửi và nhận thông báo thông qua kết nối đó.
Sử dụng mã sau đây để mở một kênh từ tập lệnh nội dung, đồng thời gửi và theo dõi tin nhắn:
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"});
Để gửi yêu cầu từ tiện ích đến một tập lệnh nội dung, hãy thay thế lệnh gọi đến runtime.connect()
trong ví dụ trước bằng tabs.connect()
.
Để xử lý các kết nối đến cho tập lệnh nội dung hoặc trang tiện ích, hãy thiết lập trình nghe sự kiện runtime.onConnect
. Khi một phần khác của tiện ích gọi connect()
, phần đó sẽ kích hoạt sự kiện này và đối tượng runtime.Port
. Mã để phản hồi các kết nối đến sẽ có dạng như sau:
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."});
}
});
});
Chuyển đổi tuần tự
Trong Chrome, các API truyền thông báo sử dụng quy trình chuyển đổi tuần tự JSON. Điều này có nghĩa là một thông báo (và phản hồi do người nhận cung cấp) có thể chứa bất kỳ giá trị JSON hợp lệ nào (null, boolean, số, chuỗi, mảng hoặc đối tượng). Các giá trị khác sẽ được chuyển đổi thành các giá trị có thể chuyển đổi tuần tự.
Đáng chú ý là điều này khác với các trình duyệt khác triển khai cùng một API bằng thuật toán sao chép có cấu trúc.
Thời gian tồn tại của cổng
Cổng được thiết kế như một cơ chế giao tiếp hai chiều giữa các phần khác nhau của một tiện ích. Khi một phần của tiện ích gọi tabs.connect()
, runtime.connect()
hoặc runtime.connectNative()
, tiện ích đó sẽ tạo một Port có thể gửi tin nhắn ngay lập tức bằng postMessage()
.
Nếu có nhiều khung trong một thẻ, việc gọi tabs.connect()
sẽ gọi sự kiện runtime.onConnect
một lần cho mỗi khung trong thẻ. Tương tự, nếu runtime.connect()
được gọi, thì sự kiện onConnect
có thể kích hoạt một lần cho mỗi khung hình trong quy trình tiện ích.
Bạn có thể muốn tìm hiểu thời điểm đóng một kết nối, chẳng hạn như nếu bạn đang duy trì các trạng thái riêng biệt cho từng cổng mở. Để thực hiện việc này, hãy theo dõi sự kiện runtime.Port.onDisconnect
. Sự kiện này sẽ kích hoạt khi không có cổng hợp lệ ở đầu kia của kênh. Điều này có thể do bất kỳ nguyên nhân nào sau đây:
- Không có trình nghe nào cho
runtime.onConnect
ở đầu kia. - Thẻ chứa cổng này sẽ được huỷ tải (ví dụ: nếu thẻ được điều hướng).
- Khung nơi
connect()
được gọi đã được huỷ tải. - Tất cả các khung nhận được cổng (thông qua
runtime.onConnect
) đều đã được huỷ tải. runtime.Port.disconnect()
được gọi bởi đầu kia. Nếu lệnh gọiconnect()
dẫn đến nhiều cổng ở phía đầu thu vàdisconnect()
được gọi trên bất kỳ cổng nào trong số này, thì sự kiệnonDisconnect
chỉ kích hoạt ở cổng gửi, chứ không phải ở các cổng khác.
Nhắn tin trên nhiều tiện ích
Ngoài việc gửi thông báo giữa các thành phần khác nhau trong tiện ích, bạn có thể dùng API nhắn tin để giao tiếp với các tiện ích khác. Thao tác này cho phép bạn hiển thị một API công khai để các tiện ích khác sử dụng.
Để theo dõi các yêu cầu và kết nối đến từ các tiện ích khác, hãy sử dụng phương thức runtime.onMessageExternal
hoặc runtime.onConnectExternal
. Sau đây là ví dụ về từng loại:
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.
});
});
Để gửi một thông báo đến một tiện ích khác, hãy truyền mã nhận dạng của tiện ích mà bạn muốn giao tiếp như sau:
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(...);
Gửi tin nhắn từ trang web
Tiện ích cũng có thể nhận và phản hồi tin nhắn từ các trang web. Để gửi thông báo từ một trang web đến một tiện ích, hãy chỉ định trong manifest.json
những trang web mà bạn muốn cho phép gửi thông báo bằng khoá tệp kê khai "externally_connectable"
. Ví dụ:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Thao tác này sẽ hiển thị API nhắn tin cho mọi trang khớp với các mẫu URL mà bạn chỉ định. Mẫu URL phải chứa ít nhất một miền cấp hai; tức là các mẫu tên máy chủ như "*", "*.com", "*.co.uk" và "*.appspot.com" không được hỗ trợ. Bạn có thể sử dụng <all_urls>
để truy cập vào tất cả các miền.
Dùng API runtime.sendMessage()
hoặc runtime.connect()
để gửi thông báo đến một tiện ích cụ thể. Ví dụ:
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);
}
);
}
Từ tiện ích của bạn, hãy theo dõi thông báo từ các trang web bằng cách sử dụng API runtime.onMessageExternal
hoặc runtime.onConnectExternal
như trong nhắn tin giữa các tiện ích. Ví dụ:
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);
});
Bạn không thể gửi thông báo từ một tiện ích đến một trang web.
Nhắn tin bằng ứng dụng gốc
Các tiện ích có thể trao đổi thông báo với các ứng dụng gốc được đăng ký làm máy chủ lưu trữ thông báo gốc. Để tìm hiểu thêm về tính năng này, hãy xem phần Nhắn tin qua ứng dụng gốc.
Lưu ý về bảo mật
Sau đây là một số yếu tố bảo mật cần cân nhắc liên quan đến việc nhắn tin.
Tập lệnh nội dung ít đáng tin cậy hơn
Tập lệnh nội dung ít đáng tin cậy hơn so với trình chạy dịch vụ của tiện ích. Ví dụ: một trang web độc hại có thể xâm nhập vào quy trình hiển thị chạy các tập lệnh nội dung. Giả sử rằng các thông báo từ một tập lệnh nội dung có thể do kẻ tấn công tạo ra và đảm bảo xác thực và dọn dẹp tất cả dữ liệu đầu vào. Giả sử mọi dữ liệu được gửi đến tập lệnh nội dung đều có thể bị rò rỉ sang trang web. Giới hạn phạm vi của các thao tác đặc quyền có thể được kích hoạt bằng các thông báo nhận được từ tập lệnh nội dung.
Tập lệnh trên nhiều trang web
Hãy nhớ bảo vệ các tập lệnh của bạn khỏi tấn công tập lệnh trên nhiều trang web. Khi nhận dữ liệu từ một nguồn không đáng tin cậy, chẳng hạn như dữ liệu đầu vào của người dùng, các trang web khác thông qua một tập lệnh nội dung hoặc một API, hãy cẩn thận để tránh diễn giải dữ liệu này dưới dạng HTML hoặc sử dụng dữ liệu này theo cách có thể cho phép mã không mong muốn chạy.
Sử dụng các API không chạy tập lệnh bất cứ khi nào có thể:
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; });
Tránh sử dụng các phương thức sau đây khiến tiện ích của bạn dễ bị tấn công:
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; });