تتيح لك واجهات برمجة التطبيقات الخاصة بالمراسلة التواصل بين النصوص البرمجية المختلفة التي يتم تشغيلها في سياقات مرتبطة بالإضافة. ويشمل ذلك التواصل بين عامل الخدمة وصفحات 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) {
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');
عند استدعاء أداة معالجة الأحداث، يتم تمرير الدالة sendResponse كالمَعلمة الثالثة. هذه دالة يمكن استدعاؤها لتقديم ردّ. بشكل
تلقائي، يجب استدعاء دالة sendResponse بشكل متزامن.
إذا اتصلت بـ sendResponse بدون أي مَعلمات، سيتم إرسال null كاستجابة.
لإرسال رد بشكل غير متزامن، لديك خياران: عرض القيمة true أو عرض وعد.
رجوع true
للرد بشكل غير متزامن باستخدام sendResponse()، عليك عرض قيمة حرفية true
(وليس مجرد قيمة صحيحة) من معالج الأحداث. سيؤدي ذلك إلى إبقاء قناة الرسائل مفتوحة على الطرف الآخر إلى أن يتم استدعاء sendResponse، ما يتيح لك الاتصال بها لاحقًا.
إرجاع وعد
اعتبارًا من الإصدار 144 من Chrome، يمكنك عرض وعد من أداة معالجة الرسائل للرد بشكل غير متزامن. إذا تم تنفيذ الوعد، سيتم إرسال القيمة التي تم تنفيذها كاستجابة.
في حال رفض الوعد، سيتم رفض طلب sendMessage() الذي أرسله المرسِل
مع رسالة الخطأ. يمكنك الاطّلاع على قسم معالجة الأخطاء للحصول على مزيد من التفاصيل والأمثلة.
مثال يعرض إرجاع وعد يمكن تنفيذه أو رفضه:
// 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);
يمكنك أيضًا تعريف أداة معالجة على أنّها async لعرض وعد:
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};
});
إرجاع وعد: async المشاكل الشائعة في الدوال
يُرجى العِلم أنّ الدالة async التي تعمل كدالة معالجة ستعرض دائمًا وعدًا، حتى بدون عبارة return. إذا لم يعرض معالج async قيمة، سيتم تلقائيًا تحويل وعده إلى undefined، وسيتم إرسال null كاستجابة إلى المرسِل. يمكن أن يؤدي ذلك إلى حدوث سلوك غير متوقّع عند توفّر عدة معالجات:
// 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');
});
});
معالجة الأخطاء
اعتبارًا من الإصدار 144 من Chrome، إذا عرض المستمع onMessage خطأً (إما بشكل متزامن أو غير متزامن من خلال عرض وعد مرفوض)، سيتم رفض الوعد الذي تعرضه الدالة sendMessage() في المرسِل مع رسالة الخطأ.
يمكن أن يحدث ذلك أيضًا إذا حاول مستمع عرض ردّ لا يمكن تسلسله بتنسيق JSON بدون رصد TypeError الناتج.
إذا عرض المستمع وعدًا مرفوضًا، يجب أن يكون الرفض باستخدام مثيل Error لكي يتلقّى المرسِل رسالة الخطأ هذه. إذا تم رفض الوعد باستخدام أي قيمة أخرى (مثل null أو undefined)، سيتم رفض sendMessage() باستخدام رسالة خطأ عامة بدلاً من ذلك.
إذا تم تسجيل عدة معالجات للأحداث من أجل onMessage، سيؤثر المعالج الأول الذي يستجيب أو يرفض أو يعرض خطأ على المرسل، وسيتم تشغيل جميع المعالجات الأخرى، ولكن سيتم تجاهل نتائجها.
أمثلة
إذا عرض المستمع وعدًا مرفوضًا، سيتم رفض sendMessage():
// 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'));
});
إذا ردّ المستمع بقيمة لا يمكن تسلسلها، سيتم رفض sendMessage():
// 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
});
إذا أظهر معالج الأحداث خطأً بشكل متزامن قبل أن يستجيب أي معالج أحداث آخر، سيتم رفض الوعد sendMessage() الخاص بمعالج الأحداث:
// 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!');
});
ومع ذلك، إذا استجاب أحد المستمعين قبل أن يعرض مستمع آخر خطأ، ستنجح عملية 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!');
});
الاتصالات الطويلة الأمد
لإنشاء قناة تمرير رسائل طويلة الأمد وقابلة لإعادة الاستخدام، استخدِم الأمر التالي:
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.stringify(). سيتم تحويل القيم الأخرى إلى قيم قابلة للتسلسل (سيتم على وجه الخصوص تحويل undefined إلى null).
الحدود القصوى لحجم الرسائل
يبلغ الحد الأقصى لحجم الرسالة 64 ميغابايت.
مدة صلاحية المنفذ
تم تصميم المنافذ كآلية تواصل ثنائية الاتجاه بين الأجزاء المختلفة
لإحدى الإضافات. عندما يستدعي جزء من الإضافة 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; });