أحداث يرسلها الخادم (Server-Sent Events) في TypeScript: بث البيانات بدون صداع
\n\nتخيل: خادمك يولد بيانات — سجلات، إشعارات، تحديثات أسعار العملات — ويجب إرسالها فوراً إلى الواجهة الأمامية. WebSocket؟ قوي لكنه مبالغ فيه. Polling؟ بطيء ويستهلك النطاق الترددي. هناك طريق ثالث — أنيق، بسيط ومفيد بشكل لا يصدق: أحداث يرسلها الخادم (SSE).
\n\nفي هذه المقالة، سنشرح SSE باستخدام TypeScript من الألف إلى الياء. سنكتب العميل والخادم، ونتعلم كيفية معالجة الأخطاء وإعادة الاتصال، والأهم من ذلك، سنفهم متى يكون SSE أفضل من الطرق الأخرى. هيا بنا!
\n\nما هي أحداث يرسلها الخادم (Server-Sent Events) ولماذا نحتاجها؟
\n\nأحداث يرسلها الخادم هي معيار HTML5 يسمح للخادم بإرسال البيانات إلى العميل عبر اتصال HTTP واحد في الوقت الفعلي. على عكس WebSocket، فإن SSE أحادي الاتجاه: الخادم فقط هو من يرسل الرسائل. ولكن في هذا تكمن قوته — فهو بسيط للغاية ولا يتطلب مكتبات إضافية.
\n\nمتى تختار SSE؟
\n\n- \n
- البث أحادي الاتجاه: الإشعارات، السجلات، أسعار العملات، مؤشرات الأسهم. \n
- تكامل بسيط: يعمل عبر
EventSourceالقياسي في المتصفح — لا حاجة لحزم npm. \n - إعادة اتصال تلقائية: المتصفح يعيد الاتصال تلقائياً عند انقطاعه. \n
- التوافق مع HTTP/2: تعدد الإرسال لا يتعارض، على عكس WebSocket. \n
لكن تذكر: SSE لا يمكنه إرسال بيانات من العميل إلى الخادم (استخدم طلبات POST العادية لذلك) ولا يدعم في المتصفحات القديمة (مرحباً IE).
\n\nجزء الخادم: كتابة SSE باستخدام TypeScript + Express
\n\nلنبدأ من الواجهة الخلفية. سننشئ خادماً بسيطاً باستخدام Node.js مع Express و TypeScript. قم بتثبيت التبعيات:
\n\nnpm init -y\nnpm install express cors\nnpm install -D typescript @types/express @types/node ts-node\nnpx tsc --init\n\nالآن لنكتب الخادم نفسه. النقطة الأساسية هي تعيين الرؤوس الصحيحة وتنسيق الرسائل.
\n\n// server.ts\nimport express, { Request, Response } from 'express';\nimport cors from 'cors';\n\nconst app = express();\napp.use(cors());\n\n// نقطة النهاية لـ SSE\napp.get('/events', (req: Request, res: Response) => {\n // تعيين رؤوس SSE\n res.writeHead(200, {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n });\n\n // إرسال رسالة أولية\n res.write('data: {"message": "تم إنشاء الاتصال"}\\n\\n');\n\n // إرسال حدث كل 3 ثوانٍ\n const intervalId = setInterval(() => {\n const data = JSON.stringify({\n time: new Date().toISOString(),\n value: Math.random() * 100,\n });\n res.write(`data: ${data}\\n\\n`);\n }, 3000);\n\n // معالجة فصل العميل\n req.on('close', () => {\n clearInterval(intervalId);\n res.end();\n console.log('تم فصل العميل');\n });\n});\n\nconst PORT = 3000;\napp.listen(PORT, () => console.log(`خادم SSE يعمل على المنفذ ${PORT}`));\n\nلاحظ التنسيق: كل رسالة تبدأ بـ data: وتنتهي بسطرين جديدين \\n\\n. يمكنك أيضاً تحديد id و event و retry — لكن هذا هو الأساس.
إرسال أحداث مسماة
\n\nأحياناً تحتاج إلى تمييز أنواع الرسائل. أضف حقل event:
res.write('event: update\\n');\nres.write(`data: ${JSON.stringify({ id: 1, title: 'منشور جديد' })}\\n\\n`);\n\n// ثم — حدث آخر\nres.write('event: notification\\n');\nres.write('data: {"level": "info", "text": "الخادم يعمل"}\\n\\n');\n\nعلى العميل، سنتمكن من الاشتراك في أحداث محددة.
\n\nجزء العميل: استقبال الأحداث باستخدام TypeScript
\n\nالآن لنكتب العميل. في المتصفح، يتم استخدام EventSource المدمج. ولكن إذا