ارسال پیام

APIهای پیام‌رسانی به شما این امکان را می‌دهند که بین اسکریپت‌های مختلف در حال اجرا در زمینه‌های مرتبط با برنامه افزودنی خود ارتباط برقرار کنید. این شامل ارتباط بین کارمند خدمات شما، chrome-extension://pages و اسکریپت‌های محتوا می‌شود. به عنوان مثال، یک برنامه افزودنی RSS reader ممکن است از اسکریپت های محتوا برای تشخیص وجود فید RSS در یک صفحه استفاده کند، سپس به کارگر سرویس اطلاع دهد تا نماد عمل آن صفحه را به روز کند.

دو API ارسال پیام وجود دارد: یکی برای درخواست‌های یک‌باره و دیگری پیچیده‌تر برای اتصالات طولانی‌مدت که امکان ارسال چندین پیام را فراهم می‌کند.

برای اطلاعات در مورد ارسال پیام بین برنامه‌های افزودنی، به بخش پیام‌های پسوندی مراجعه کنید.

درخواست های یکبار مصرف

برای ارسال یک پیام واحد به قسمت دیگری از برنامه افزودنی خود و دریافت پاسخ به صورت اختیاری، با runtime.sendMessage() یا tabs.sendMessage() تماس بگیرید. این روش‌ها به شما امکان می‌دهند یک پیام یک‌بار سریال‌سازی با JSON از یک اسکریپت محتوا به برنامه افزودنی یا از برنامه افزودنی به یک اسکریپت محتوا ارسال کنید. هر دو API یک 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 callback باید به صورت همزمان فراخوانی شود. اگر می‌خواهید کار ناهمزمانی انجام دهید تا مقدار ارسال شده به 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، APIهای ارسال پیام از سریال‌سازی JSON استفاده می‌کنند. این بدان معناست که یک پیام (و پاسخ های ارائه شده توسط گیرندگان) می تواند حاوی هر مقدار JSON معتبر (تهی، بولی، عدد، رشته، آرایه یا شی) باشد. مقادیر دیگر به مقادیر سریال‌سازی تبدیل می‌شوند.

قابل ذکر است، این با سایر مرورگرهایی که APIهای یکسانی را با الگوریتم کلون ساخت یافته پیاده سازی می کنند متفاوت است.

طول عمر بندر

پورت ها به عنوان یک مکانیسم ارتباطی دو طرفه بین بخش های مختلف یک داخلی طراحی شده اند. هنگامی که بخشی از یک برنامه افزودنی 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 فقط در پورت ارسال کننده فعال می شود، نه در پورت های دیگر.
پیام رسانی متقاطع

علاوه بر ارسال پیام بین اجزای مختلف در برنامه افزودنی خود، می‌توانید از 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 خود مشخص کنید که از کدام وب‌سایت‌ها می‌خواهید با استفاده از کلید مانیفست "externally_connectable" به پیام‌ها اجازه دهید. به عنوان مثال:

manifest.json

"externally_connectable": {
  "matches": ["https://*.example.com/*"]
}

این API پیام‌رسانی را در معرض هر صفحه‌ای قرار می‌دهد که با الگوهای URL که شما مشخص کرده‌اید مطابقت داشته باشد. الگوی URL باید حداقل شامل یک دامنه سطح دوم باشد. یعنی الگوهای نام میزبان مانند «*»، «*.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);
    }
  );
}

از برنامه افزودنی خود، به پیام‌های صفحات وب با استفاده از 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);
  });

امکان ارسال پیام از یک برنامه افزودنی به یک صفحه وب وجود ندارد.

پیام‌رسانی بومی

برنامه های افزودنی می توانند پیام ها را با برنامه های کاربردی بومی که به عنوان میزبان پیام رسانی بومی ثبت شده اند مبادله کنند . برای کسب اطلاعات بیشتر در مورد این ویژگی، به پیام‌رسانی بومی مراجعه کنید.

ملاحظات امنیتی

در اینجا چند ملاحظات امنیتی مربوط به پیام رسانی وجود دارد.

اسکریپت های محتوا کمتر قابل اعتماد هستند

اسکریپت‌های محتوا نسبت به کارمند خدمات افزودنی کمتر قابل اعتماد هستند . به عنوان مثال، یک صفحه وب مخرب ممکن است بتواند فرآیند رندرینگ را که اسکریپت های محتوا را اجرا می کند، به خطر بیندازد. فرض کنید که پیام‌های یک اسکریپت محتوا ممکن است توسط یک مهاجم ساخته شده باشد و مطمئن شوید که تمام ورودی‌ها را اعتبارسنجی و پاکسازی می‌کنید . فرض کنید هرگونه داده ارسال شده به اسکریپت محتوا ممکن است به صفحه وب نشت کند. دامنه اقدامات ممتازی را که می تواند توسط پیام های دریافتی از اسکریپت های محتوا ایجاد شود، محدود کنید.

اسکریپت بین سایتی

مطمئن شوید که از اسکریپت های خود در برابر اسکریپت های متقابل سایت محافظت می کنید. هنگام دریافت داده‌ها از یک منبع نامعتبر مانند ورودی کاربر، وب‌سایت‌های دیگر از طریق یک اسکریپت محتوا یا یک 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;
});