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

אריק בידלמן

BroadcastChannel API מאפשר לסקריפטים מאותו מקור לשלוח הודעות להקשרי גלישה אחרים. ניתן להתייחס אליו כאל אפיק הודעות פשוט שמאפשר סמנטיקה של Pub/Sub בין חלונות/כרטיסיות, מסגרות iframe, קובצי Web work ו-Service Workers.

מידע בסיסי על API

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

בנאי 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, מפה).

דוגמה - שליחת USD או קובץ

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

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

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

בשלב זה ייתכן שתהיתם כיצד זה קשור לטכניקות אחרות להעברת הודעות, כגון WebSockets, SharedWorkers, MessageChannel API ו-window.postMessage(). ה- Broadcast Channel API לא מחליף את ממשקי ה-API האלה. לכל אחד יש מטרה מסוימת. ממשק ה-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, אבל יכול להיות שה-Service Worker כבר מכיל כלי עזר לעשות זאת. אנחנו יכולים להשתמש בממשק ה-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() אפשר גם לתקשר בין מקורות. The Broadcast Channel API הוא same-origin. מאחר שמובטח שההודעות יגיעו מאותו מקור, אין צורך לאמת אותן כמו שהיינו יודעים לשלוח עם 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, יש כמה כאלה:

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

מקורות מידע