تتيح لك واجهات برمجة التطبيقات الخاصة بالمراسلة التواصل بين النصوص البرمجية المختلفة التي يتم تشغيلها في سياقات مرتبطة بإضافتك. ويشمل ذلك التواصل بين عامل الخدمة وصفحات chrome-extension://والنصوص البرمجية للمحتوى. على سبيل المثال، قد يستخدم أحد الإضافات الخاصة بقارئ RSS نصوصًا برمجية خاصة بالمحتوى لرصد توفّر خلاصة RSS على إحدى الصفحات، ثم إرسال إشعار إلى عامل الخدمة لتعديل رمز الإجراء الخاص بهذه الصفحة.
تتوفّر واجهتا برمجة تطبيقات لنقل الرسائل: إحداهما للطلبات لمرة واحدة، والأخرى أكثر تعقيدًا للاتصالات الطويلة الأمد التي تتيح إرسال رسائل متعددة.
للحصول على معلومات حول إرسال الرسائل بين الإضافات، اطّلِع على القسم الرسائل بين الإضافات.
الطلبات لمرة واحدة
لإرسال رسالة واحدة إلى جزء آخر من الإضافة، والحصول على رد اختياريًا، استخدِم runtime.sendMessage()
أو tabs.sendMessage()
.
تتيح لك هذه الطرق إرسال رسالة يمكن تحويلها إلى تنسيق JSON لمرة واحدة من نص برمجي خاص بالمحتوى إلى الإضافة، أو من الإضافة إلى نص برمجي خاص بالمحتوى. تعرض كلتا واجهتَي برمجة التطبيقات Promise
الذي يتم تحويله إلى الردّ المقدَّم من المستلِم.
يبدو إرسال طلب من نص برمجي للمحتوى على النحو التالي:
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'
});
عند استدعاء أداة معالجة الأحداث، يتم تمرير دالة sendResponse
كالمَعلمة الثالثة. هذه دالة يمكن استدعاؤها لتقديم ردّ. بشكل
تلقائي، يجب استدعاء دالة sendResponse
بشكل متزامن. إذا أردت تنفيذ عمل غير متزامن للحصول على القيمة التي تم تمريرها إلى sendResponse
، يجب عرض القيمة الحرفية true
(وليس مجرد قيمة صحيحة) من معالج الأحداث. سيؤدي ذلك إلى إبقاء قناة الرسائل مفتوحة إلى الطرف الآخر إلى أن يتم الاتصال بـ sendResponse
.
إذا اتصلت بـ sendResponse
بدون أي مَعلمات، سيتم إرسال null
كاستجابة.
إذا كانت صفحات متعددة تستمع إلى أحداث onMessage
، لن تنجح في إرسال الرد إلا الصفحة الأولى التي تستدعي sendResponse()
لحدث معيّن. وسيتم تجاهل جميع الردود الأخرى على هذا الحدث.
الاتصالات الطويلة الأمد
لإنشاء قناة تمرير رسائل طويلة الأمد وقابلة لإعادة الاستخدام، استخدِم الأمر التالي:
runtime.connect()
لنقل الرسائل من نص برمجي للمحتوى إلى صفحة إضافةtabs.connect()
لنقل الرسائل من صفحة إضافة إلى برنامج نصي للمحتوى.
يمكنك تسمية قناتك من خلال تمرير مَعلمة خيارات باستخدام مفتاح name
للتمييز بين أنواع الاتصالات المختلفة:
const port = chrome.runtime.connect({name: "example"});
إحدى حالات الاستخدام المحتملة للاتصال الطويل الأمد هي إضافة الملء التلقائي للنماذج. قد يفتح النص البرمجي للمحتوى قناة إلى صفحة الإضافة لتسجيل دخول معيّن، ويرسل رسالة إلى الإضافة لكل عنصر إدخال في الصفحة لطلب بيانات النموذج المطلوب ملؤها. يتيح الاتصال المشترك للإضافة مشاركة الحالة بين مكوّنات الإضافة.
عند إنشاء اتصال، يتم تعيين عنصر runtime.Port
لكل طرف لإرسال الرسائل وتلقّيها من خلال هذا الاتصال.
استخدِم الرمز التالي لفتح قناة من نص برمجي خاص بالمحتوى وإرسال الرسائل والاستماع إليها:
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"});
لإرسال طلب من الإضافة إلى نص برمجي خاص بالمحتوى، استبدِل استدعاء runtime.connect()
في المثال السابق بـ tabs.connect()
.
للتعامل مع الاتصالات الواردة من نص برمجي للمحتوى أو صفحة إضافة، عليك إعداد أداة معالجة الحدث 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، تستخدم واجهات برمجة التطبيقات لنقل الرسائل تسلسل JSON. وهذا يعني أنّ الرسالة (والردود التي يقدّمها المستلمون) يمكن أن تحتوي على أي قيمة JSON صالحة (قيمة فارغة أو قيمة منطقية أو رقم أو سلسلة أو مصفوفة أو كائن). سيتم تحويل القيم الأخرى إلى قيم قابلة للتسلسل.
يُرجى العِلم أنّ هذا يختلف عن المتصفحات الأخرى التي تنفّذ واجهات برمجة التطبيقات نفسها باستخدام خوارزمية النسخ المتطابق المنظَّم.
مدة صلاحية المنفذ
تم تصميم المنافذ كآلية تواصل ثنائية الاتجاه بين الأجزاء المختلفة
لإحدى الإضافات. عندما يستدعي جزء من الإضافة tabs.connect()
أو runtime.connect()
أو runtime.connectNative()
، يتم إنشاء منفذ يمكنه إرسال الرسائل على الفور باستخدام postMessage()
.
إذا كانت هناك إطارات متعددة في علامة تبويب، سيؤدي استدعاء tabs.connect()
إلى تشغيل الحدث runtime.onConnect
مرة واحدة لكل إطار في علامة التبويب. وبالمثل، إذا تم استدعاء runtime.connect()
، يمكن أن يتم تشغيل الحدث onConnect
مرة واحدة لكل إطار في عملية الإضافة.
قد تحتاج إلى معرفة وقت إغلاق اتصال، مثلاً إذا كنت تحتفظ بحالات منفصلة لكل منفذ مفتوح. لإجراء ذلك، استمع إلى الحدث runtime.Port.onDisconnect
. يتم تشغيل هذا الحدث عندما لا تكون هناك منافذ صالحة في الطرف الآخر من القناة، ويمكن أن يكون ذلك لأي من الأسباب التالية:
- ما مِن أدوات لمعالجة الحدث
runtime.onConnect
في الطرف الآخر. - يتم إلغاء تحميل علامة التبويب التي تحتوي على المنفذ (على سبيل المثال، إذا تم الانتقال إلى علامة التبويب).
- تم إلغاء تحميل الإطار الذي تم استدعاء
connect()
فيه. - تم إلغاء تحميل جميع الإطارات التي تلقّت المنفذ (عبر
runtime.onConnect
). - يتم استدعاء
runtime.Port.disconnect()
من خلال الطرف الآخر. إذا أدت مكالمةconnect()
إلى عمليات نقل متعددة في جهاز الاستقبال، وتم استدعاءdisconnect()
على أي من هذه المنافذ، سيتم تشغيل الحدثonDisconnect
فقط في منفذ الإرسال، وليس في المنافذ الأخرى.
المراسلة بين الإضافات
بالإضافة إلى إرسال الرسائل بين المكوّنات المختلفة في الإضافة، يمكنك استخدام واجهة برمجة التطبيقات الخاصة بالمراسلة للتواصل مع إضافات أخرى. يتيح لك ذلك عرض واجهة برمجة تطبيقات عامة يمكن للإضافات الأخرى استخدامها.
للاستماع إلى الطلبات والاتصالات الواردة من إضافات أخرى، استخدِم الطريقتَين
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
المواقع الإلكترونية التي تريد السماح لها بإرسال الرسائل باستخدام مفتاح البيان "externally_connectable"
. على سبيل المثال:
manifest.json
"externally_connectable": {
"matches": ["https://*.example.com/*"]
}
يؤدي ذلك إلى إتاحة واجهة برمجة التطبيقات الخاصة بالمراسلة لأي صفحة تتطابق مع أنماط عناوين URL التي تحدّدها. يجب أن يحتوي نمط عنوان URL على نطاق من المستوى الثاني على الأقل، أي أنّ أنماط أسماء المضيفين مثل "*" و"*.com" و"*.co.uk" و "*.appspot.com" غير متاحة. يمكنك استخدام
<all_urls>
للوصول إلى جميع النطاقات.
استخدِم واجهات برمجة التطبيقات 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);
}
);
}
من إضافتك، استمِع إلى الرسائل الواردة من صفحات الويب باستخدام واجهتَي برمجة التطبيقات
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);
});
لا يمكن إرسال رسالة من إضافة إلى صفحة ويب.
المراسلة الأصلية
يمكن للإضافات تبادل الرسائل مع التطبيقات الأصلية المسجّلة كمضيف مراسلة أصلي. لمزيد من المعلومات عن هذه الميزة، اطّلِع على مقالة الرسائل الأصلية.
الاعتبارات الأمنية
في ما يلي بعض اعتبارات الأمان المتعلقة بالمراسلة.
النصوص البرمجية للمحتوى أقل موثوقية
نصوص المحتوى البرمجية أقل موثوقية من عامل خدمة الإضافة. على سبيل المثال، قد تتمكّن صفحة ويب ضارة من اختراق عملية العرض التي تشغّل النصوص البرمجية للمحتوى. افترض أنّ الرسائل الواردة من نص برمجي للمحتوى ربما تم إنشاؤها من قِبل مهاجم، واحرص على التحقّق من صحة جميع المدخلات وتنظيفها. افترِض أنّ أي بيانات يتم إرسالها إلى نص برمجي للمحتوى قد يتم تسريبها إلى صفحة الويب. تحديد نطاق الإجراءات ذات الامتيازات التي يمكن أن يتم تشغيلها من خلال الرسائل الواردة من نصوص برمجية خاصة بالمحتوى
هجوم البرمجة عبر المواقع الإلكترونية
احرِص على حماية النصوص البرمجية من البرمجة النصية على مواقع إلكترونية متعددة. عند تلقّي بيانات من مصدر غير موثوق به، مثل إدخال المستخدم أو مواقع إلكترونية أخرى من خلال نص برمجي للمحتوى أو واجهة برمجة تطبيقات، احرص على تجنُّب تفسير هذه البيانات على أنّها HTML أو استخدامها بطريقة قد تسمح بتشغيل رمز غير متوقّع.
استخدِم واجهات برمجة التطبيقات التي لا تنفّذ نصوصًا برمجية كلما أمكن:
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; });