Berichten-API's stellen je in staat om te communiceren tussen verschillende scripts die draaien in contexten die aan je extensie zijn gekoppeld. Dit omvat communicatie tussen je service worker, chrome-extension://pages en content scripts. Een RSS-reader-extensie kan bijvoorbeeld content scripts gebruiken om de aanwezigheid van een RSS-feed op een pagina te detecteren en vervolgens de service worker te laten weten dat het actie-icoon voor die pagina moet worden bijgewerkt.
Er zijn twee API's voor het uitwisselen van berichten: één voor eenmalige verzoeken en een complexere voor langdurige verbindingen waarmee meerdere berichten kunnen worden verzonden.
Voor informatie over het verzenden van berichten tussen extensies, zie het gedeelte over berichten tussen extensies .
Eenmalige verzoeken
Om een enkel bericht naar een ander onderdeel van uw extensie te sturen en optioneel een reactie te ontvangen, kunt u runtime.sendMessage() of tabs.sendMessage() aanroepen. Met deze methoden kunt u een eenmalig JSON-serialiseerbaar bericht verzenden vanuit een contentscript naar de extensie, of vanuit de extensie naar een contentscript. Beide API's retourneren een Promise die wordt opgelost met de reactie van de ontvanger.
Het versturen van een verzoek vanuit een content-script ziet er als volgt uit:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Reacties
Om naar een bericht te luisteren, gebruikt u de gebeurtenis 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');
Wanneer de gebeurtenislistener wordt aangeroepen, wordt een sendResponse functie als derde parameter doorgegeven. Deze functie kan worden aangeroepen om een reactie te geven. Standaard moet de sendResponse callback synchroon worden aangeroepen.
Als je sendResponse aanroept zonder parameters, wordt null als antwoord verzonden.
Om asynchroon te reageren, heb je twee opties: true retourneren of een promise retourneren.
Retourneer true
Om asynchroon te reageren met sendResponse() , retourneer je een letterlijke true (niet alleen een waarheidsgetrouwe waarde) vanuit de gebeurtenislistener. Hierdoor blijft het berichtenkanaal naar de andere kant open totdat sendResponse wordt aangeroepen, waardoor je het later opnieuw kunt aanroepen.
Een belofte teruggeven
Vanaf Chrome versie 144 kun je vanuit een berichtlistener een promise retourneren om asynchroon te reageren. Als de promise wordt opgelost, wordt de opgeloste waarde als antwoord verzonden.
Als de belofte wordt afgewezen, wordt de sendMessage() -aanroep van de afzender afgewezen met het foutbericht. Zie het gedeelte over foutafhandeling voor meer details en voorbeelden.
Een voorbeeld dat laat zien hoe een promise kan worden geretourneerd die kan worden opgelost of afgewezen:
// 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);
Je kunt een listener ook als async declareren om een promise terug te geven:
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};
});
Een promise retourneren: valkuilen async functies
Houd er rekening mee dat een async functie als listener altijd een promise retourneert, zelfs zonder een return statement. Als een async listener geen waarde retourneert, wordt de promise impliciet opgelost naar undefined en wordt null als antwoord naar de afzender gestuurd. Dit kan onverwacht gedrag veroorzaken wanneer er meerdere listeners zijn.
// 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');
});
});
Foutafhandeling
Vanaf Chrome 144 geldt dat als een onMessage listener een fout genereert (synchroon of asynchroon door een promise terug te geven die wordt afgewezen), de promise die door sendMessage() in de sender wordt geretourneerd, wordt afgewezen met het foutbericht. Dit kan ook gebeuren als een listener probeert een antwoord terug te geven dat niet JSON-geserialiseerd kan worden zonder de resulterende TypeError op te vangen.
Als een listener een promise retourneert die wordt afgewezen, moet deze worden afgewezen met een Error instantie zodat de afzender het foutbericht ontvangt. Als de promise wordt afgewezen met een andere waarde (zoals null of undefined ), wordt sendMessage() in plaats daarvan afgewezen met een algemeen foutbericht.
Als er meerdere listeners zijn geregistreerd voor onMessage , heeft alleen de eerste listener die reageert, afwijst of een foutmelding geeft, invloed op de afzender; alle andere listeners worden wel uitgevoerd, maar hun resultaten worden genegeerd.
Voorbeelden
Als een luisteraar een promise retourneert die wordt afgewezen, wordt sendMessage() afgewezen:
// 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'));
});
Als een luisteraar reageert met een waarde die niet geserialiseerd kan worden, wordt sendMessage() geweigerd:
// 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
});
Als een luisteraar synchroon een fout genereert voordat een andere luisteraar reageert, wordt de sendMessage() -promise van die luisteraar afgewezen:
// 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!');
});
Als echter één luisteraar reageert voordat een andere een foutmelding geeft, slaagt 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!');
});
Langdurige verbindingen
Om een herbruikbaar, langdurig berichtenkanaal te creëren, roept u het volgende aan:
-
runtime.connect()om berichten van een content-script naar een extensiepagina door te geven. -
tabs.connect()om berichten van een extensiepagina door te geven aan een contentscript.
Je kunt je kanaal een naam geven door een optieparameter met een name mee te geven om onderscheid te maken tussen verschillende soorten verbindingen:
const port = chrome.runtime.connect({name: "example"});
Een mogelijk gebruiksscenario voor een langdurige verbinding is een extensie voor het automatisch invullen van formulieren. Het content-script kan een kanaal openen naar de extensiepagina voor een specifieke login en voor elk invoerelement op de pagina een bericht naar de extensie sturen om de formuliergegevens op te vragen. De gedeelde verbinding stelt de extensie in staat om statusinformatie te delen tussen de verschillende extensiecomponenten.
Bij het tot stand brengen van een verbinding krijgt elk uiteinde een runtime.Port -object toegewezen voor het verzenden en ontvangen van berichten via die verbinding.
Gebruik de volgende code om vanuit een contentscript een kanaal te openen en berichten te verzenden en te ontvangen:
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"});
Om vanuit de extensie een verzoek naar een contentscript te sturen, vervangt u de aanroep naar runtime.connect() in het vorige voorbeeld door tabs.connect() .
Om inkomende verbindingen voor een contentscript of een extensiepagina af te handelen, stelt u een runtime.onConnect -gebeurtenislistener in. Wanneer een ander onderdeel van uw extensie connect() aanroept, wordt deze gebeurtenis en het runtime.Port -object geactiveerd. De code voor het reageren op inkomende verbindingen ziet er als volgt uit:
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."});
}
});
});
Serialisatie
In Chrome gebruiken de API's voor berichtuitwisseling JSON-serialisatie . Dit is opvallend anders dan in andere browsers, die dezelfde API's implementeren met het gestructureerde kloonalgoritme.
Dit betekent dat een bericht (en de reacties van de ontvangers) elke geldige JSON.stringify() waarde kan bevatten. Andere waarden worden geconverteerd naar serialiseerbare waarden (met name undefined wordt geserialiseerd als null ').
Beperkingen voor de berichtgrootte
De maximale grootte van een bericht is 64 MiB.
Levensduur van de haven
Poorten zijn ontworpen als een tweewegcommunicatiemechanisme tussen verschillende onderdelen van een extensie. Wanneer een onderdeel van een extensie tabs.connect() , runtime.connect() of runtime.connectNative() aanroept, creëert het een poort die direct berichten kan verzenden met behulp van postMessage() .
Als een tabblad meerdere frames bevat, zorgt het aanroepen tabs.connect() ervoor dat de runtime.onConnect -gebeurtenis één keer wordt geactiveerd voor elk frame in het tabblad. Op dezelfde manier kan de onConnect gebeurtenis één keer worden geactiveerd voor elk frame in het extensieproces als runtime.connect() wordt aangeroepen.
U wilt wellicht weten wanneer een verbinding wordt verbroken, bijvoorbeeld als u aparte statussen bijhoudt voor elke open poort. Luister hiervoor naar de runtime.Port.onDisconnect -gebeurtenis. Deze gebeurtenis wordt geactiveerd wanneer er geen geldige poorten meer zijn aan de andere kant van het kanaal, wat een van de volgende oorzaken kan hebben:
- Er zijn geen listeners voor
runtime.onConnectaan de andere kant. - Het tabblad met de poort wordt gesloten (bijvoorbeeld als er naar een ander tabblad wordt genavigeerd).
- Het frame waarin
connect()werd aangeroepen, is niet meer geladen. - Alle frames die de poort hebben ontvangen (via
runtime.onConnect) zijn ontladen. -
runtime.Port.disconnect()wordt aangeroepen door de andere kant . Als eenconnect()-aanroep resulteert in meerdere poorten aan de ontvangende kant, endisconnect()wordt aangeroepen op een van deze poorten, dan wordt deonDisconnectgebeurtenis alleen geactiveerd op de verzendende poort, niet op de andere poorten.
Berichtenverkeer tussen verschillende extensies
Naast het versturen van berichten tussen verschillende componenten in je extensie, kun je de berichten-API ook gebruiken om met andere extensies te communiceren. Hierdoor kun je een openbare API beschikbaar stellen die andere extensies kunnen gebruiken.
Om inkomende verzoeken en verbindingen van andere extensies te ontvangen, gebruikt u de methoden runtime.onMessageExternal of runtime.onConnectExternal . Hier volgt een voorbeeld van beide:
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.
});
});
Om een bericht naar een ander toestel te sturen, geeft u de ID van het toestel waarmee u wilt communiceren als volgt door:
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(...);
Berichten verzenden vanaf webpagina's
Extensies kunnen ook berichten van webpagina's ontvangen en beantwoorden. Om berichten van een webpagina naar een extensie te verzenden, moet u in uw manifest.json specificeren van welke websites u berichten wilt toestaan met behulp van de manifest-sleutel "externally_connectable" . Bijvoorbeeld:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Hiermee wordt de berichten-API toegankelijk voor elke pagina die overeenkomt met de URL-patronen die u opgeeft. Het URL-patroon moet ten minste een domein op het tweede niveau bevatten; dat wil zeggen dat hostnaampatronen zoals "*", "*.com", "*.co.uk" en "*.appspot.com" niet worden ondersteund. U kunt <all_urls> gebruiken om toegang te krijgen tot alle domeinen.
Gebruik de API's runtime.sendMessage() of runtime.connect() om een bericht naar een specifieke extensie te verzenden. Bijvoorbeeld:
webpagina.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);
}
);
}
Luister vanuit je extensie naar berichten van webpagina's met behulp van de API's runtime.onMessageExternal of runtime.onConnectExternal , net zoals bij berichtenuitwisseling tussen extensies . Hier is een voorbeeld:
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);
});
Het is niet mogelijk om vanuit een extensie een bericht naar een webpagina te sturen.
Native berichten
Extensies kunnen berichten uitwisselen met native applicaties die geregistreerd staan als native messaging host . Zie Native messaging voor meer informatie over deze functie.
Veiligheidsoverwegingen
Hieronder volgen enkele beveiligingsaspecten met betrekking tot berichtenverkeer.
Scripts voor content zijn minder betrouwbaar.
Contentscripts zijn minder betrouwbaar dan de service worker van de extensie. Een kwaadwillende webpagina kan bijvoorbeeld het renderingproces dat de contentscripts uitvoert, compromitteren. Ga ervan uit dat berichten van een contentscript mogelijk door een aanvaller zijn opgesteld en zorg ervoor dat alle invoer wordt gevalideerd en gezuiverd . Ga ervan uit dat alle gegevens die naar het contentscript worden verzonden, naar de webpagina kunnen lekken. Beperk de reikwijdte van geprivilegieerde acties die kunnen worden geactiveerd door berichten die van contentscripts worden ontvangen.
Cross-site scripting
Zorg ervoor dat je je scripts beschermt tegen cross-site scripting (CSS) . Wanneer je gegevens ontvangt van een onbetrouwbare bron, zoals gebruikersinvoer, andere websites via een contentscript of een API, moet je ervoor zorgen dat je deze niet interpreteert als HTML of gebruikt op een manier die het mogelijk maakt dat er onverwachte code wordt uitgevoerd.
Gebruik waar mogelijk API's die geen scripts uitvoeren:
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; });
Vermijd het gebruik van de volgende methoden, aangezien deze uw extensie kwetsbaar maken:
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; });