BroadcastChannel API – אפיק הודעות לאינטרנט

BroadcastChannel API מאפשר לסקריפטים מאותו מקור לשלוח הודעות להקשרים אחרים של גלישה. אפשר לחשוב עליו כעל אוטובוס הודעות פשוט שמאפשר סמנטיקה של פרסום/הרשמה (pub/sub) בין חלונות/כרטיסיות, רכיבי iframe, מכונות worker באינטרנט וקובצי שירות.

יסודות של ממשקי API

Broadcast Channel API הוא ממשק API פשוט שמקל על התקשורת בין הקשרי הגלישה. כלומר, תקשורת בין חלונות/כרטיסיות, רכיבי iframe, משימות אינטרנט ושירותי עבודה. הודעות שמתפרסמות בערוץ מסוים נשלחות לכל המאזינים של הערוץ הזה.

למבנה BroadcastChannel יש פרמטר אחד: שם הערוץ. השם משמש לזיהוי הערוץ והוא מופיע בהקשרים שונים של גלישה.

// Connect to the channel named "my_bus".
const channel = new BroadcastChannel('my_bus');

// Send a message on "my_bus".
channel.postMessage('This is a test message.');

// Listen for messages on "my_bus".
channel.onmessage = function(e) {
    console.log('Received', e.data);
};

// Close the channel when you're done.
channel.close();

שליחת הודעות

הודעות יכולות להיות מחרוזות או כל דבר שנתמך על ידי האלגוריתם של העתקה מובנית (מחרוזות, אובייקטים, מערכים, Blobs, ArrayBuffer, מפה).

דוגמה – שליחת Blob או קובץ

channel.postMessage(new Blob(['foo', 'bar'], {type: 'plain/text'}));

הערוץ לא ישדר לעצמו. לכן, אם יש לכם מאזין onmessage באותו דף שבו יש מאזין postMessage() לאותו ערוץ, האירוע message לא יופעל.

הבדלים בין שיטות אחרות

בשלב הזה, יכול להיות שתתהו איך השיטה הזו קשורה לשיטות אחרות להעברת הודעות, כמו WebSockets, ‏SharedWorkers, ‏MessageChannel API ו-window.postMessage(). ממשק ה-API של ערוץ השידור לא מחליף את ממשקי ה-API האלה. לכל אחת מהן יש מטרה. Broadcast Channel API מיועד לתקשורת פשוטה בין סקריפטים באותו מקור, מאחד לשנים רבים.

תרחישים לדוגמה לשימוש בערוצי שידור:

  • זיהוי פעולות של משתמשים בכרטיסיות אחרות
  • לדעת מתי משתמש מתחבר לחשבון בחלון או בכרטיסייה אחרים.
  • איך מבקשים מעובד לבצע עבודה ברקע
  • לדעת מתי שירות מסיים לבצע פעולה כלשהי.
  • כשהמשתמש מעלה תמונה בחלון אחד, אפשר להעביר אותה לדפים פתוחים אחרים.

דוגמה – דף שמזהה מתי המשתמש יוצא מהחשבון, גם מכרטיסייה פתוחה אחרת באותו אתר:

<button id="logout">Logout</button>

<script>
function doLogout() {
    // update the UI login state for this page.
}

const authChannel = new BroadcastChannel('auth');

const button = document.querySelector('#logout');
button.addEventListener('click', e => {
    // A channel won't broadcast to itself so we invoke doLogout()
    // manually on this page.
    doLogout();
    authChannel.postMessage({cmd: 'logout', user: 'Eric Bidelman'});
});

authChannel.onmessage = function(e) {
    if (e.data.cmd === 'logout') {
    doLogout();
    }
};
</script>

דוגמה נוספת: נניח שרציתם להורות לקובץ שירות (service worker) להסיר תוכן שנשמר במטמון אחרי שהמשתמש משנה את 'הגדרת האחסון במצב אופליין' באפליקציה. תוכלו למחוק את המטמון שלהם באמצעות window.caches, אבל יכול להיות שכבר יש בקובץ השירות כלי שיכול לעשות זאת. אנחנו יכולים להשתמש ב-Broadcast Channel API כדי לעשות שימוש חוזר בקוד הזה. בלי Broadcast Channel API, תצטרכו לבצע לולאה על התוצאות של self.clients.matchAll() ולקרוא ל-postMessage() בכל לקוח כדי לבצע את התקשורת מ-service worker לכל הלקוחות שלו (הקוד בפועל שמבצע את הפעולה הזו). אם משתמשים בערוץ שידור, הערך הזה הוא O(1) במקום O(N).

דוגמה – נותנים הוראה ל-service worker להסיר מטמון, תוך שימוש חוזר בשיטות השירות הפנימיות שלו.

בקובץ index.html

const channel = new BroadcastChannel('app-channel');
channel.onmessage = function(e) {
    if (e.data.action === 'clearcache') {
    console.log('Cache removed:', e.data.removed);
    }
};

const messageChannel = new MessageChannel();

// Send the service worker a message to clear the cache.
// We can't use a BroadcastChannel for this because the
// service worker may need to be woken up. MessageChannels do that.
navigator.serviceWorker.controller.postMessage({
    action: 'clearcache',
    cacheName: 'v1-cache'
}, [messageChannel.port2]);

בקובץ sw.js

function nukeCache(cacheName) {
    return caches.delete(cacheName).then(removed => {
    // ...do more stuff (internal) to this service worker...
    return removed;
    });
}

self.onmessage = function(e) {
    const action = e.data.action;
    const cacheName = e.data.cacheName;

    if (action === 'clearcache') {
    nukeCache(cacheName).then(removed => {
        // Send the main page a response via the BroadcastChannel API.
        // We could also use e.ports[0].postMessage(), but the benefit
        // of responding with the BroadcastChannel API is that other
        // subscribers may be listening.
        const channel = new BroadcastChannel('app-channel');
        channel.postMessage({action, removed});
    });
    }
};

ההבדל ביחס ל-postMessage()

בניגוד ל-postMessage(), כבר אין צורך לשמור על הפניה ל-iframe או ל-worker כדי לתקשר איתו:

// Don't have to save references to window objects.
const popup = window.open('https://another-origin.com', ...);
popup.postMessage('Sup popup!', 'https://another-origin.com');

window.postMessage() מאפשר גם לתקשר בין מקורות שונים. Broadcast Channel API הוא ממשק API עם מקור זהה. מאחר שהודעות מגיעות תמיד מאותו מקור, אין צורך לאמת אותן כמו שעשינו בעבר עם window.postMessage():

// Don't have to validate the origin of a message.
const iframe = document.querySelector('iframe');
iframe.contentWindow.onmessage = function(e) {
    if (e.origin !== 'https://expected-origin.com') {
    return;
    }
    e.source.postMessage('Ack!', e.origin);
};

פשוט 'נרשמים' לערוץ מסוים ומנהלים תקשורת דו-כיוונית מאובטחת!

ההבדל ביחס ל-SharedWorkers

משתמשים ב-BroadcastChannel במקרים פשוטים שבהם צריך לשלוח הודעה לכמה חלונות/כרטיסיות או לעובדים.

בתרחישי שימוש מתקדמים יותר, כמו ניהול מנעולים, מצב משותף, סנכרון משאבים בין שרת למספר לקוחות או שיתוף חיבור WebSocket עם מארח מרוחק, עובדים משותפים הם הפתרון המתאים ביותר.

ההבדל ביחס ל-MessageChannel API

ההבדל העיקרי בין Channel Messaging API לבין BroadcastChannel הוא שהאחרון משמש לשליחת הודעות למספר מאזינים (אחד-לרבים). MessageChannel מיועד לתקשורת חד-ל-חד בין סקריפטים. בנוסף, היא מורכבת יותר, כי צריך להגדיר ערוצים עם יציאה בכל קצה.

זיהוי תכונות ותמיכה בדפדפנים

נכון לעכשיו, הדפדפנים Chrome 54,‏ Firefox 38 ו-Opera 41 תומכים ב-Broadcast Channel API.

if ('BroadcastChannel' in self) {
    // BroadcastChannel API supported!
}

לגבי polyfills, יש כמה פריטים כאלה:

לא ניסיתי את האפשרויות האלה, ולכן יכול להיות שהתוצאות שלך יהיו שונות.

משאבים