Interfejsy API do obsługi wiadomości umożliwiają komunikację między różnymi skryptami działającymi w kontekstach powiązanych z rozszerzeniem. Obejmuje to komunikację między skryptem service worker, stronami chrome-extension://i skryptami dotyczącymi zawartości. Na przykład rozszerzenie czytnika RSS może używać skryptów treści do wykrywania obecności kanału RSS na stronie, a następnie powiadamiać skrypt usługi o konieczności zaktualizowania ikony działania na tej stronie.
Dostępne są 2 interfejsy API do przekazywania wiadomości: jeden do jednorazowych żądań, a drugi, bardziej złożony, do długotrwałych połączeń, które umożliwiają wysyłanie wielu wiadomości.
Informacje o wysyłaniu wiadomości między rozszerzeniami znajdziesz w sekcji Wiadomości między rozszerzeniami.
Żądania jednorazowe
Aby wysłać pojedynczą wiadomość do innej części rozszerzenia i opcjonalnie uzyskać odpowiedź, wywołaj funkcję runtime.sendMessage()
lub tabs.sendMessage()
.
Te metody umożliwiają wysłanie jednorazowej wiadomości w formacie JSON z skryptu treści do rozszerzenia lub z rozszerzenia do skryptu treści. Oba interfejsy API zwracają obiekt Promise, który jest rozwiązywany w odpowiedzi podanej przez odbiorcę.
Wysyłanie żądania ze skryptu treści wygląda tak:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Odpowiedzi
Aby nasłuchiwać wiadomości, użyj zdarzenia 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'
});
Gdy wywoływany jest detektor zdarzeń, jako trzeci parametr przekazywana jest funkcja sendResponse
. Jest to funkcja, którą można wywołać, aby podać odpowiedź. Domyślnie wywołanie zwrotne sendResponse
musi być wywoływane synchronicznie. Jeśli chcesz wykonać asynchroniczną pracę, aby uzyskać wartość przekazaną do sendResponse
, musisz zwrócić z funkcji nasłuchującej zdarzeń literał true
(nie tylko wartość prawdziwą). Dzięki temu kanał wiadomości pozostanie otwarty dla drugiej strony, dopóki nie zadzwonisz do sendResponse
.
Jeśli wywołasz funkcję sendResponse
bez żadnych parametrów, w odpowiedzi zostanie wysłana wartość null
.
Jeśli wiele stron nasłuchuje zdarzeń onMessage
, tylko pierwsza, która wywoła sendResponse()
w przypadku danego zdarzenia, zdoła wysłać odpowiedź. Wszystkie inne odpowiedzi na to wydarzenie zostaną zignorowane.
Długotrwałe połączenia
Aby utworzyć kanał przekazywania wiadomości wielokrotnego użytku o długim czasie życia, wywołaj:
runtime.connect()
– przekazywanie wiadomości ze skryptu treści na stronę rozszerzenia;tabs.connect()
– przekazywanie wiadomości ze strony rozszerzenia do skryptu treści.
Możesz nazwać kanał, przekazując parametr options z kluczem name
, aby odróżnić różne typy połączeń:
const port = chrome.runtime.connect({name: "example"});
Jednym z możliwych zastosowań długotrwałego połączenia jest rozszerzenie do automatycznego wypełniania formularzy. Skrypt treści może otworzyć kanał na stronę rozszerzenia dla konkretnego logowania i wysłać do rozszerzenia wiadomość dotyczącą każdego elementu wejściowego na stronie, aby poprosić o dane formularza do wypełnienia. Udostępnione połączenie umożliwia rozszerzeniu udostępnianie stanu między komponentami rozszerzenia.
Podczas nawiązywania połączenia każda strona otrzymuje obiekt runtime.Port
do wysyłania i odbierania wiadomości za pomocą tego połączenia.
Aby otworzyć kanał ze skryptu treści oraz wysyłać i odbierać wiadomości, użyj tego kodu:
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"});
Aby wysłać żądanie z rozszerzenia do skryptu treści, zastąp wywołanie runtime.connect()
w poprzednim przykładzie wywołaniem tabs.connect()
.
Aby obsługiwać połączenia przychodzące ze skryptu treści lub strony rozszerzenia, skonfiguruj detektor zdarzeń runtime.onConnect
. Gdy inna część rozszerzenia wywoła connect()
, aktywuje to zdarzenie i obiekt runtime.Port
. Kod do odpowiadania na połączenia przychodzące wygląda tak:
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."});
}
});
});
Publikacja w odcinkach
W Chrome interfejsy API do przekazywania wiadomości używają serializacji JSON. Oznacza to, że wiadomość (i odpowiedzi odbiorców) może zawierać dowolną prawidłową wartość JSON (null, wartość logiczną, liczbę, ciąg znaków, tablicę lub obiekt). Inne wartości zostaną przekształcone w wartości możliwe do serializacji.
W przeciwieństwie do innych przeglądarek, które implementują te same interfejsy API za pomocą algorytmu klonowania strukturalnego.
Czas życia portu
Porty są przeznaczone do dwukierunkowej komunikacji między różnymi częściami rozszerzenia. Gdy część rozszerzenia wywołuje tabs.connect()
, runtime.connect()
lub runtime.connectNative()
, tworzy Port, który może natychmiast wysyłać wiadomości za pomocą postMessage()
.
Jeśli karta zawiera wiele ramek, wywołanie tabs.connect()
powoduje wywołanie zdarzenia runtime.onConnect
w przypadku każdej ramki na karcie. Podobnie jeśli wywoływana jest funkcja runtime.connect()
, zdarzenie onConnect
może być uruchamiane raz na każdą klatkę w procesie rozszerzenia.
Może Cię interesować, kiedy połączenie zostanie zamknięte, np. jeśli utrzymujesz oddzielne stany dla każdego otwartego portu. Aby to zrobić, nasłuchuj zdarzenia runtime.Port.onDisconnect
. To zdarzenie jest wywoływane, gdy po drugiej stronie kanału nie ma prawidłowych portów. Może to być spowodowane dowolną z tych przyczyn:
- Po drugiej stronie nie ma odbiorców dla
runtime.onConnect
. - Karta zawierająca port jest zwalniana (np. jeśli nastąpi przejście do innej karty).
- Ramka, w której wywołano funkcję
connect()
, została zwolniona. - Wszystkie ramki, które otrzymały port (za pomocą
runtime.onConnect
), zostały zwolnione. runtime.Port.disconnect()
jest wywoływana przez drugą stronę. Jeśli wywołanieconnect()
spowoduje wiele portów po stronie odbiorcy, adisconnect()
zostanie wywołane na dowolnym z tych portów, zdarzenieonDisconnect
zostanie wywołane tylko na porcie wysyłającym, a nie na innych portach.
Przesyłanie wiadomości między rozszerzeniami
Oprócz wysyłania wiadomości między różnymi komponentami rozszerzenia możesz używać interfejsu Messaging API do komunikowania się z innymi rozszerzeniami. Dzięki temu możesz udostępnić publiczny interfejs API, z którego mogą korzystać inne rozszerzenia.
Aby nasłuchiwać przychodzących żądań i połączeń z innych rozszerzeń, użyj metod runtime.onMessageExternal
lub runtime.onConnectExternal
. Oto przykład każdego z nich:
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.
});
});
Aby wysłać wiadomość do innego rozszerzenia, przekaż identyfikator rozszerzenia, z którym chcesz się skontaktować, w ten sposób:
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(...);
Wysyłanie wiadomości ze stron internetowych
Rozszerzenia mogą też odbierać wiadomości ze stron internetowych i na nie odpowiadać. Aby wysyłać wiadomości ze strony internetowej do rozszerzenia, w pliku manifest.json
określ, z których witryn chcesz zezwolić na wysyłanie wiadomości, używając klucza manifestu "externally_connectable"
. Na przykład:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Udostępnia to interfejs API do obsługi wiadomości na każdej stronie pasującej do określonych przez Ciebie wzorców adresów URL. Wzorzec adresu URL musi zawierać co najmniej domenę drugiego poziomu, co oznacza, że wzorce nazw hostów, takie jak „*”, „*.com”, „*.co.uk” i „*.appspot.com”, nie są obsługiwane. Możesz użyć
<all_urls>
, aby uzyskać dostęp do wszystkich domen.
Użyj interfejsów API runtime.sendMessage()
lub runtime.connect()
, aby wysłać wiadomość do konkretnego rozszerzenia. Na przykład:
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);
}
);
}
W rozszerzeniu nasłuchuj wiadomości ze stron internetowych za pomocą interfejsów runtime.onMessageExternal
lub runtime.onConnectExternal
, tak jak w przypadku komunikacji między rozszerzeniami. Oto przykład:
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);
});
Nie można wysłać wiadomości z rozszerzenia do strony internetowej.
Natywne aplikacje do obsługi wiadomości
Rozszerzenia mogą wymieniać wiadomości z aplikacjami natywnymi zarejestrowanymi jako natywny host wiadomości. Więcej informacji o tej funkcji znajdziesz w artykule Wiadomości natywne.
Bezpieczeństwo
Oto kilka kwestii związanych z bezpieczeństwem przesyłania wiadomości.
Skrypty dotyczące zawartości są mniej wiarygodne
Skrypty treści są mniej wiarygodne niż proces roboczy usługi rozszerzenia. Na przykład złośliwa strona internetowa może naruszyć proces renderowania, w którym działają skrypty treści. Załóż, że wiadomości ze skryptu treści mogły zostać utworzone przez osobę przeprowadzającą atak, i sprawdź oraz oczyść wszystkie dane wejściowe. Załóż, że wszystkie dane wysyłane do skryptu treści mogą wyciec na stronę internetową. Ograniczanie zakresu działań uprzywilejowanych, które mogą być wywoływane przez wiadomości otrzymywane ze skryptów treści.
Cross-site scripting
Zadbaj o to, aby Twoje skrypty były chronione przed skryptami działającymi na różnych stronach. Podczas odbierania danych z niezaufanego źródła, np. z danych wejściowych użytkownika, innych witryn za pomocą skryptu treści lub interfejsu API, unikaj interpretowania ich jako kodu HTML lub używania ich w sposób, który mógłby umożliwić uruchomienie nieoczekiwanego kodu.
W miarę możliwości używaj interfejsów API, które nie uruchamiają skryptów:
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; });
Unikaj tych metod, które sprawiają, że rozszerzenie jest podatne na ataki:
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; });