Messaging API memungkinkan Anda berkomunikasi antara berbagai skrip yang berjalan dalam konteks yang terkait dengan ekstensi Anda. Hal ini mencakup komunikasi antara pekerja layanan, chrome-extension://pages, dan skrip konten. Misalnya, ekstensi pembaca RSS dapat menggunakan skrip konten untuk mendeteksi keberadaan feed RSS di halaman, lalu memberi tahu pekerja layanan untuk memperbarui ikon tindakan untuk halaman tersebut.
Ada dua API penerusan pesan: satu untuk permintaan satu kali, dan yang lebih kompleks untuk koneksi yang berjalan lama yang memungkinkan beberapa pesan dikirim.
Untuk mengetahui informasi tentang pengiriman pesan antar-ekstensi, lihat bagian pesan antar-ekstensi.
Permintaan satu kali
Untuk mengirim satu pesan ke bagian lain dari ekstensi Anda, dan secara opsional mendapatkan respons, panggil runtime.sendMessage() atau tabs.sendMessage().
Metode ini memungkinkan Anda mengirim pesan sekali pakai yang dapat diserialisasi JSON dari skrip konten ke ekstensi, atau dari ekstensi ke skrip konten. Kedua API menampilkan Promise
yang di-resolve ke respons yang diberikan oleh penerima.
Mengirim permintaan dari skrip konten akan terlihat seperti ini:
content-script.js:
(async () => {
const response = await chrome.runtime.sendMessage({greeting: "hello"});
// do something with response here, not outside the function
console.log(response);
})();
Respons
Untuk mendengarkan pesan, gunakan peristiwa 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');
Saat pemroses peristiwa dipanggil, fungsi sendResponse diteruskan sebagai parameter ketiga. Ini adalah fungsi yang dapat dipanggil untuk memberikan respons. Secara
default, callback sendResponse harus dipanggil secara sinkron.
Jika Anda memanggil sendResponse tanpa parameter apa pun, null akan dikirim sebagai respons.
Untuk mengirim respons secara asinkron, Anda memiliki dua opsi: menampilkan true atau menampilkan promise.
Kembali true
Untuk merespons secara asinkron menggunakan sendResponse(), tampilkan true literal
(bukan hanya nilai benar) dari pemroses peristiwa. Dengan melakukannya, channel pesan akan tetap terbuka untuk ujung lainnya hingga sendResponse dipanggil, sehingga Anda dapat memanggilnya nanti.
Mengembalikan promise
Mulai Chrome 144, Anda dapat menampilkan promise dari pemroses pesan untuk merespons secara asinkron. Jika promise di-resolve, nilai yang di-resolve akan dikirim sebagai respons.
Jika promise ditolak, panggilan sendMessage() pengirim
akan ditolak dengan pesan error. Lihat bagian
penanganan error untuk mengetahui detail dan contoh selengkapnya.
Contoh yang menunjukkan cara menampilkan promise yang dapat di-resolve atau ditolak:
// 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);
Anda juga dapat mendeklarasikan pemroses sebagai async untuk menampilkan promise:
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};
});
Mengembalikan promise: Perangkap fungsi async
Perlu diingat bahwa fungsi async sebagai pemroses akan selalu menampilkan
promise, meskipun tanpa pernyataan return. Jika pemroses async tidak menampilkan nilai, promise-nya akan di-resolve secara implisit ke undefined, dan null akan dikirim sebagai respons kepada pengirim. Hal ini dapat menyebabkan perilaku yang tidak terduga saat ada beberapa pemroses:
// 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');
});
});
Penanganan error
Mulai Chrome 144, jika pemroses onMessage menampilkan error (baik secara sinkron, atau secara asinkron dengan menampilkan promise yang ditolak), promise yang ditampilkan oleh sendMessage() di pengirim akan ditolak dengan pesan error.
Hal ini juga dapat terjadi jika pemroses mencoba menampilkan respons yang tidak dapat
diserialisasi JSON tanpa menangkap TypeError yang dihasilkan.
Jika pemroses menampilkan promise yang ditolak, pemroses harus menolak dengan instance Error agar pengirim menerima pesan error tersebut. Jika promise ditolak dengan nilai lain (seperti null atau undefined), sendMessage() akan ditolak dengan pesan error umum.
Jika beberapa pemroses terdaftar untuk onMessage, hanya pemroses pertama yang merespons, menolak, atau memunculkan error yang akan memengaruhi pengirim; semua pemroses lainnya akan berjalan, tetapi hasilnya akan diabaikan.
Contoh
Jika pemroses menampilkan promise yang ditolak, sendMessage() akan ditolak:
// 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'));
});
Jika pemroses merespons dengan nilai yang tidak dapat diserialisasi, sendMessage()
akan ditolak:
// 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
});
Jika pemroses menampilkan error secara serentak sebelum pemroses lain merespons,
promise sendMessage() pemroses akan ditolak:
// 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!');
});
Namun, jika satu pendengar merespons sebelum pendengar lain menampilkan error, sendMessage() akan berhasil:
// 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!');
});
Koneksi yang bertahan lama
Untuk membuat saluran penerusan pesan yang dapat digunakan kembali dan berumur panjang, panggil:
runtime.connect()untuk meneruskan pesan dari skrip konten ke halaman ekstensitabs.connect()untuk meneruskan pesan dari halaman ekstensi ke skrip konten.
Anda dapat memberi nama channel dengan meneruskan parameter opsi dengan kunci name untuk
membedakan berbagai jenis koneksi:
const port = chrome.runtime.connect({name: "example"});
Salah satu kasus penggunaan potensial untuk koneksi yang berjalan lama adalah ekstensi pengisian formulir otomatis. Skrip konten dapat membuka saluran ke halaman ekstensi untuk login tertentu, dan mengirim pesan ke ekstensi untuk setiap elemen input di halaman untuk meminta data formulir yang akan diisi. Koneksi bersama memungkinkan ekstensi berbagi status antara komponen ekstensi.
Saat membuat koneksi, setiap ujung diberi objek runtime.Port untuk
mengirim dan menerima pesan melalui koneksi tersebut.
Gunakan kode berikut untuk membuka saluran dari skrip konten, serta mengirim dan memproses pesan:
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"});
Untuk mengirim permintaan dari ekstensi ke skrip konten, ganti panggilan ke runtime.connect()
dalam contoh sebelumnya dengan tabs.connect().
Untuk menangani koneksi masuk untuk skrip konten atau halaman ekstensi, siapkan pemroses peristiwa runtime.onConnect. Saat bagian lain dari ekstensi Anda memanggil connect(), peristiwa ini dan objek runtime.Port akan diaktifkan. Kode untuk merespons koneksi masuk akan terlihat seperti ini:
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."});
}
});
});
Serialisasi
Di Chrome, API penerusan pesan menggunakan serialisasi JSON. Khususnya, hal ini berbeda dengan browser lain yang menerapkan API yang sama dengan algoritma clone terstruktur.
Artinya, pesan (dan respons yang diberikan oleh penerima) dapat berisi nilai
JSON.stringify()
yang valid. Nilai lainnya akan dipaksa menjadi nilai yang dapat diserialisasi (terutama
undefined akan diserialisasi sebagai null);
Batas ukuran pesan
Ukuran maksimum pesan adalah 64 MiB.
Masa aktif port
Port dirancang sebagai mekanisme komunikasi dua arah antara berbagai bagian
ekstensi. Saat menjadi bagian dari panggilan ekstensi
tabs.connect(), runtime.connect(), atau
runtime.connectNative(), Port akan dibuat dan dapat langsung mengirim pesan menggunakan
postMessage().
Jika ada beberapa frame dalam tab, memanggil tabs.connect() akan memanggil peristiwa runtime.onConnect satu kali untuk setiap frame dalam tab. Demikian pula, jika
runtime.connect() dipanggil, peristiwa onConnect dapat diaktifkan satu kali untuk setiap
frame dalam proses ekstensi.
Anda mungkin ingin mengetahui kapan koneksi ditutup, misalnya jika Anda mempertahankan status terpisah untuk setiap port terbuka. Untuk melakukannya, dengarkan peristiwa runtime.Port.onDisconnect. Peristiwa
ini dipicu saat tidak ada port yang valid di ujung lain saluran, yang dapat disebabkan oleh salah satu hal berikut:
- Tidak ada pendengar untuk
runtime.onConnectdi ujung lainnya. - Tab yang berisi port dibongkar (misalnya, jika tab dinavigasi).
- Frame tempat
connect()dipanggil telah dibongkar. - Semua frame yang menerima port (melalui
runtime.onConnect) telah dibongkar. runtime.Port.disconnect()dipanggil oleh ujung lainnya. Jika panggilanconnect()menghasilkan beberapa port di sisi penerima, dandisconnect()dipanggil di salah satu port ini, maka peristiwaonDisconnecthanya diaktifkan di port pengirim, bukan di port lain.
Fitur pesan lintas ekstensi
Selain mengirim pesan antar-komponen yang berbeda dalam ekstensi, Anda dapat menggunakan API pesan untuk berkomunikasi dengan ekstensi lain. Dengan demikian, Anda dapat mengekspos API publik untuk digunakan oleh ekstensi lain.
Untuk memproses permintaan dan koneksi masuk dari ekstensi lain, gunakan metode
runtime.onMessageExternal
atau runtime.onConnectExternal. Berikut contoh
masing-masing:
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.
});
});
Untuk mengirim pesan ke ekstensi lain, teruskan ID ekstensi yang ingin Anda ajak berkomunikasi sebagai berikut:
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(...);
Mengirim pesan dari halaman web
Ekstensi juga dapat menerima dan merespons pesan dari halaman web. Untuk mengirim
pesan dari halaman web ke ekstensi, tentukan di manifest.json Anda situs
mana yang ingin Anda izinkan untuk mengirim pesan menggunakan
kunci manifes "externally_connectable". Contoh:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
Hal ini mengekspos API pesan ke halaman mana pun yang cocok dengan pola URL yang Anda tentukan. Pola URL harus berisi minimal domain tingkat kedua; yaitu, pola nama host seperti "*", "*.com", "*.co.uk", dan "*.appspot.com" tidak didukung. Anda dapat menggunakan
<all_urls> untuk mengakses semua domain.
Gunakan API runtime.sendMessage() atau runtime.connect() untuk mengirim
pesan ke ekstensi tertentu. Contoh:
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);
}
);
}
Dari ekstensi Anda, dengarkan pesan dari halaman web menggunakan
API runtime.onMessageExternal atau runtime.onConnectExternal
seperti dalam pengiriman pesan lintas ekstensi. Berikut contohnya:
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);
});
Pesan tidak dapat dikirim dari ekstensi ke halaman web.
Pesan native
Ekstensi dapat bertukar pesan dengan aplikasi native yang terdaftar sebagai host pesan native. Untuk mempelajari fitur ini lebih lanjut, lihat Pesan native.
Pertimbangan keamanan
Berikut adalah beberapa pertimbangan keamanan terkait fitur pesan.
Skrip konten kurang tepercaya
Skrip konten kurang tepercaya daripada service worker ekstensi. Misalnya, halaman web berbahaya mungkin dapat membahayakan proses rendering yang menjalankan skrip konten. Asumsikan bahwa pesan dari skrip konten mungkin telah dibuat oleh penyerang dan pastikan untuk memvalidasi dan membersihkan semua input. Asumsikan bahwa semua data yang dikirim ke skrip konten dapat bocor ke halaman web. Membatasi cakupan tindakan istimewa yang dapat dipicu oleh pesan yang diterima dari skrip konten.
Pembuatan skrip lintas situs
Pastikan untuk melindungi skrip Anda dari cross-site scripting. Saat menerima data dari sumber yang tidak tepercaya seperti input pengguna, situs lain melalui skrip konten, atau API, berhati-hatilah agar tidak menafsirkannya sebagai HTML atau menggunakannya dengan cara yang dapat memungkinkan kode yang tidak terduga berjalan.
Gunakan API yang tidak menjalankan skrip jika memungkinkan:
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; });
Hindari penggunaan metode berikut yang membuat ekstensi Anda rentan:
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; });