Messaging API ช่วยให้คุณสื่อสารระหว่างสคริปต์ต่างๆ ที่ทำงานใน บริบทที่เชื่อมโยงกับส่วนขยายได้ ซึ่งรวมถึงการสื่อสารระหว่าง Service Worker, chrome-extension://pages และ Content Script ตัวอย่างเช่น ส่วนขยายโปรแกรมอ่าน RSS อาจใช้ Content Script เพื่อตรวจหาฟีด RSS ในหน้าเว็บ จากนั้นแจ้ง Service Worker ให้อัปเดตไอคอนการดำเนินการสำหรับหน้าเว็บนั้น
API การส่งผ่านข้อความมี 2 แบบ ได้แก่ แบบสำหรับคำขอแบบครั้งเดียว และแบบที่ซับซ้อนกว่าสำหรับการเชื่อมต่อที่ใช้งานได้นานซึ่งอนุญาตให้ส่งข้อความได้หลายรายการ
ดูข้อมูลเกี่ยวกับการส่งข้อความระหว่างส่วนขยายได้ที่ส่วนข้อความข้ามส่วนขยาย
คำขอแบบครั้งเดียว
หากต้องการส่งข้อความเดียวไปยังส่วนอื่นของส่วนขยาย และรับการตอบกลับหรือไม่ก็ได้ ให้เรียกใช้ runtime.sendMessage() หรือ tabs.sendMessage() วิธีการเหล่านี้ช่วยให้คุณส่งข้อความที่แปลงเป็น JSON ได้แบบครั้งเดียวจาก Content Script ไปยัง ส่วนขยาย หรือจากส่วนขยายไปยัง Content Script ทั้ง 2 API จะแสดงผล Promise ซึ่งจะเปลี่ยนเป็นการตอบกลับที่ผู้รับให้ไว้
การส่งคำขอจาก Content Script จะมีลักษณะดังนี้
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) { 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' }); เมื่อมีการเรียก Listener เหตุการณ์ ระบบจะส่งsendResponseฟังก์ชันเป็นพารามิเตอร์ที่สาม นี่คือฟังก์ชันที่เรียกใช้เพื่อแสดงคำตอบได้ โดย ค่าเริ่มต้น ระบบจะเรียกใช้การเรียกกลับ sendResponse แบบพร้อมกัน หากต้องการ ทำงานแบบไม่พร้อมกันเพื่อรับค่าที่ส่งไปยัง sendResponse คุณต้อง ส่งคืนค่าตามตัวอักษร true (ไม่ใช่แค่ค่าที่เป็นจริง) จากเครื่องมือฟังเหตุการณ์ การทำเช่นนี้จะทำให้ช่องข้อความเปิดอยู่กับปลายทางอีกด้านจนกว่าจะมีการเรียกใช้ sendResponse
หากคุณเรียกใช้ sendResponse โดยไม่มีพารามิเตอร์ ระบบจะส่ง null เป็นการตอบกลับ
หากหลายหน้าเว็บกำลังรอรับฟังเหตุการณ์ onMessage จะมีเพียงหน้าเว็บแรกที่เรียกใช้ sendResponse() สำหรับ เหตุการณ์หนึ่งๆ เท่านั้นที่จะส่งการตอบกลับได้สำเร็จ ระบบจะเพิกเฉยต่อการตอบกลับอื่นๆ ทั้งหมดสำหรับกิจกรรมนั้น
การเชื่อมต่อที่ใช้งานได้นาน
หากต้องการสร้างช่องการส่งข้อความแบบมีอายุยาวนานที่ใช้ซ้ำได้ ให้เรียกใช้
runtime.connect()เพื่อส่งข้อความจากสคริปต์เนื้อหา ไปยังหน้าส่วนขยายtabs.connect()เพื่อส่งข้อความจากหน้าส่วนขยายไปยัง Content Script
คุณตั้งชื่อช่องได้โดยส่งพารามิเตอร์ options ที่มีคีย์ name เพื่อ แยกความแตกต่างระหว่างการเชื่อมต่อประเภทต่างๆ ดังนี้
const port = chrome.runtime.connect({name: "example"}); กรณีการใช้งานที่เป็นไปได้ของการเชื่อมต่อที่มีอายุการใช้งานยาวนานคือส่วนขยายการกรอกแบบฟอร์มอัตโนมัติ Content Script อาจเปิดช่องทางไปยังหน้าส่วนขยายสำหรับการเข้าสู่ระบบที่เฉพาะเจาะจง และ ส่งข้อความไปยังส่วนขยายสำหรับองค์ประกอบอินพุตแต่ละรายการในหน้าเว็บเพื่อขอข้อมูล แบบฟอร์มที่จะกรอก การเชื่อมต่อที่แชร์ช่วยให้ส่วนขยายแชร์สถานะระหว่างคอมโพเนนต์ของส่วนขยายได้
เมื่อสร้างการเชื่อมต่อ ระบบจะกำหนดออบเจ็กต์ runtime.Port ให้แต่ละปลายทางเพื่อ ส่งและรับข้อความผ่านการเชื่อมต่อนั้น
ใช้โค้ดต่อไปนี้เพื่อเปิดแชแนลจาก Content Script รวมถึงส่งและรับฟังข้อความ
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"}); หากต้องการส่งคำขอจากส่วนขยายไปยัง Content Script ให้แทนที่การเรียกใช้ runtime.connect() ในตัวอย่างก่อนหน้าด้วย tabs.connect()
หากต้องการจัดการการเชื่อมต่อขาเข้าสำหรับ Content Script หรือหน้าส่วนขยาย ให้ตั้งค่า Listener เหตุการณ์ 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 ซึ่งหมายความว่าข้อความ (และการตอบกลับที่ผู้รับระบุ) สามารถมีค่า JSON ที่ถูกต้อง (null บูลีน ตัวเลข สตริง อาร์เรย์ หรือออบเจ็กต์) ระบบจะบังคับให้ค่าอื่นๆ เป็นค่าที่ทำให้เป็นอนุกรมได้
โปรดทราบว่าการดำเนินการนี้แตกต่างจากเบราว์เซอร์อื่นๆ ที่ใช้ API เดียวกันกับอัลกอริทึมการโคลนที่มีโครงสร้าง
อายุการใช้งานของพอร์ต
พอร์ตได้รับการออกแบบให้เป็นกลไกการสื่อสารแบบ 2 ทางระหว่างส่วนต่างๆ ของส่วนขยาย เมื่อส่วนหนึ่งของส่วนขยายเรียกใช้ tabs.connect(), runtime.connect() หรือ runtime.connectNative() ระบบจะสร้าง พอร์ตที่ส่งข้อความได้ทันทีโดยใช้ postMessage()
หากมีหลายเฟรมในแท็บ การเรียก tabs.connect() จะเรียกใช้เหตุการณ์ runtime.onConnect 1 ครั้งสำหรับแต่ละเฟรมในแท็บ ในทํานองเดียวกัน หากมีการเรียกใช้ runtime.connect() เหตุการณ์ onConnect จะทํางานได้ 1 ครั้งต่อเฟรมในกระบวนการของส่วนขยาย
คุณอาจต้องการทราบเมื่อการเชื่อมต่อปิดลง เช่น หากคุณรักษาสถานะแยกต่างหากสำหรับแต่ละพอร์ตที่เปิดอยู่ โดยให้ฟังเหตุการณ์ runtime.Port.onDisconnect เหตุการณ์นี้จะทริกเกอร์เมื่อไม่มีพอร์ตที่ถูกต้องที่ปลายอีกด้านของแชแนล ซึ่งอาจเกิดจากสาเหตุต่อไปนี้
- ไม่มีผู้ฟังสำหรับ
runtime.onConnectที่ปลายสาย - ระบบจะยกเลิกการโหลดแท็บที่มีพอร์ต (เช่น หากมีการไปยังส่วนต่างๆ ในแท็บ)
- เฟรมที่เรียกใช้
connect()ได้เลิกโหลดแล้ว - เฟรมทั้งหมดที่ได้รับพอร์ต (ผ่าน
runtime.onConnect) ได้ยกเลิกการโหลดแล้ว runtime.Port.disconnect()จะถูกเรียกใช้โดยปลายทางอีกฝั่ง หากการโทรผ่านconnect()ทำให้มีการโอนหลายครั้งที่ฝั่งผู้รับ และมีการโทรหาdisconnect()ในพอร์ตใดพอร์ตหนึ่งเหล่านี้ เหตุการณ์onDisconnectจะทริกเกอร์เฉพาะที่พอร์ตส่งเท่านั้น ไม่ใช่ที่พอร์ตอื่นๆ
การรับส่งข้อความข้ามส่วนขยาย
นอกเหนือจากการส่งข้อความระหว่างคอมโพเนนต์ต่างๆ ในส่วนขยายแล้ว คุณยังใช้ Messaging 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. }); }); หากต้องการส่งข้อความไปยังส่วนขยายอื่น ให้ส่งรหัสของส่วนขยายที่ต้องการสื่อสารด้วยดังนี้
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 ว่าต้องการอนุญาตให้เว็บไซต์ใดส่งข้อความโดยใช้คีย์ไฟล์ Manifest "externally_connectable" เช่น
manifest.json
"externally_connectable": { "matches": ["https://*.example.com/*"] } ซึ่งจะเปิดเผย API การรับส่งข้อความไปยังหน้าเว็บใดก็ตามที่ตรงกับรูปแบบ URL ที่คุณระบุ รูปแบบ URL ต้องมีโดเมนระดับที่ 2 อย่างน้อย 1 รายการ นั่นคือระบบไม่รองรับรูปแบบชื่อโฮสต์ เช่น "*" "*.com" "*.co.uk" และ "*.appspot.com" คุณใช้ <all_urls> เพื่อเข้าถึงโดเมนทั้งหมดได้
ใช้ API runtime.sendMessage() หรือ runtime.connect() เพื่อส่ง ข้อความไปยังส่วนขยายที่เฉพาะเจาะจง เช่น
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); } ); } จากส่วนขยาย ให้รับฟังข้อความจากหน้าเว็บโดยใช้ API runtime.onMessageExternal หรือ runtime.onConnectExternal ตามที่ระบุไว้ในการรับส่งข้อความระหว่างส่วนขยาย เช่น
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); }); คุณไม่สามารถส่งข้อความจากส่วนขยาย ไปยังหน้าเว็บได้
การรับส่งข้อความแบบเดิม
ส่วนขยายแลกเปลี่ยนข้อความกับแอปพลิเคชันเนทีฟที่ลงทะเบียนเป็นโฮสต์การรับส่งข้อความเนทีฟได้ ดูข้อมูลเพิ่มเติมเกี่ยวกับฟีเจอร์นี้ได้ที่การรับส่งข้อความเนทีฟ
ข้อควรพิจารณาด้านความปลอดภัย
ข้อควรพิจารณาด้านความปลอดภัยบางประการที่เกี่ยวข้องกับการรับส่งข้อความมีดังนี้
สคริปต์เนื้อหาน่าเชื่อถือน้อยกว่า
สคริปต์เนื้อหาน่าเชื่อถือน้อยกว่า Service Worker ของส่วนขยาย ตัวอย่างเช่น หน้าเว็บที่เป็นอันตรายอาจทำให้กระบวนการแสดงผลที่เรียกใช้สคริปต์เนื้อหาถูกบุกรุก พึงระลึกว่าข้อความจาก Content Script อาจถูกสร้างขึ้นโดยผู้โจมตี และอย่าลืมตรวจสอบและล้างข้อมูลอินพุตทั้งหมด พึงระลึกว่าข้อมูลใดก็ตามที่ส่งไปยัง Content Script อาจรั่วไหลไปยังหน้าเว็บ จำกัดขอบเขตของการดำเนินการที่มีสิทธิ์ซึ่งทริกเกอร์ได้โดยข้อความที่ได้รับจาก Content Scripts
Cross-site Scripting
อย่าลืมปกป้องสคริปต์จากการเขียนสคริปต์ข้ามเว็บไซต์ เมื่อได้รับข้อมูลจากแหล่งที่ไม่น่าเชื่อถือ เช่น ข้อมูลที่ผู้ใช้ป้อน เว็บไซต์อื่นๆ ผ่าน Content Script หรือ 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; });