• בלוג
  • יומי
  • ניסוי Hono - סינכרון ערך בין דפדפנים

ניסוי Hono - סינכרון ערך בין דפדפנים

07/09/2024

התמיכה של דינו ב Web Standards הביאה לזה שמאוד קל לכתוב קוד הונו שמשתמש ב Streams כדי לקבל הודעות מגולשים ולדווח אותן הלאה לגולשים האחרים באתר, הכל ב SSE. בואו נראה איך לחבר את החוטים בפרויקט דינו ו Hono.

1. מה אנחנו בונים

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

2. קוד צד שרת

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

const clients: Array<SSEStreamingApi> = [];

הממשק SSEStreamingApi מגיע מ Hono ומאפשר לשלוח אירועי צד שרת ללקוחות שממתינים להם. נתיב הרישום נראה כך:

app.get('/count', (c) => {
  return streamSSE(c, async (stream) => {
    stream.writeln("data: Start\n\n");
    clients.push(stream);
    const id = clients.length - 1;

    stream.onAbort(() => {
      clients.splice(id, 1);
      console.log(`removed client ${id}`);
    });

    while(true) {
      await stream.sleep(1000);
    }
  })
})

לקוחות שמתחברים לאתר ייכנסו לנתיב זה כדי להירשם לקבלת הודעות. הפונקציה streamSSE מגיעה מ hono. היא מקבלת את אוביקט החיבור של hono עצמו ופונקציית Callback. כל פעם שלקוח מתחבר מופעלת פונקציית ה Callback עם הפרמטר stream שמייצג את זרם התקשורת לאותו גולש. בקוד הדוגמה אני מדביק את האוביקט הזה למערך וגם רושם עליו פונקציית ביטול כך שכשהזרם ייסגר אוטומטית הוא יוסר מהמערך.

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

app.post('/count', async (c) => {
  console.log(`POST /count`);
  const { value } = await c.req.json();
  console.log(`Got ${value}`);

  clients.forEach(c => {
    c.writeSSE({ data: value });
  })

  return c.json({ message: 'Value received' });
})

קוד זה נקרא כל פעם שנשלחת בקשת POST לנתיב העידכון. הקוד רץ על כל הלקוחות במערך וכותב לכל אחד מהם את הערך שנשלח.

העליתי את הקוד המלא של main.ts עם כל ה import-ים לגיטהאב מוזמנים למצוא אותו בדף הפרויקט:

https://github.com/ynonp/hono-sync-counter-demo

3. קוד צד לקוח

בצד הלקוח אני רוצה להתחבר לנתיב הרישום בעלייה, ואחרי זה לשלוח הודעות POST לנתיב העדכון כל פעם שלוחצים על הכפתור. זה קובץ ה JavaScript שאני שולח ללקוח:

// client/sync.js

const evtSource = new EventSource("/count");
const ti = document.querySelector('input');

evtSource.onmessage = (event) => {
  ti.value = event.data;
};

document.querySelector('button').addEventListener('click', async () => {
  await fetch('/count', {
    method: "POST",
    body: JSON.stringify({ value: ti.value }),
    contentType: 'application/json',
  });
});

העבודה עם SSE בדפדפן מאוד נוחה ואני מעדיף אותו על פני Web Sockets עבור עידכונים אסינכרוניים מצד שרת.