WebSocket على TypeScript: بناء تطبيقات الوقت الفعلي المتقدمة
\n\nأصبحت تطبيقات الوقت الفعلي جزءًا لا يتجزأ من الويب الحديث. من الدردشات والإشعارات إلى الألعاب عبر الإنترنت ومنصات التداول - توفر تقنية WebSocket اتصالاً ثنائي الاتجاه دائمًا بين العميل والخادم. على المستوى المتقدم، يتطلب تطوير هذه الأنظمة ليس فقط فهم البروتوكول، ولكن أيضًا القدرة على بناء بنى تحتية متسامحة مع الأخطاء وقابلة للتوسع. في هذه المقالة، سنشرح كيفية إنشاء تطبيقات WebSocket بجودة إنتاجية باستخدام TypeScript، مع أفضل الممارسات والأنماط.
\n\n1. بنية الخادم وتجميع WebSocket
\n\nخادم WebSocket واحد لن يتحمل حمولة مشروع حقيقي. المهمة الرئيسية هي تنظيم التجميع (Clustering) وتبادل الرسائل بين النسخ. سننظر في بنية باستخدام مكتبة ws وناقل أحداث Redis.
سنقوم بتثبيت التبعيات:
\nnpm install ws redis ioredis @types/ws @types/ioredis\n\nسننشئ خادمًا مجمعًا أساسيًا:
\n\nimport { WebSocketServer, WebSocket } from 'ws';\nimport Redis from 'ioredis';\n\nconst redisPub = new Redis();\nconst redisSub = new Redis();\n\nconst wss = new WebSocketServer({ port: 8080 });\n\n// قناة للتواصل بين الخوادم\nconst CHANNEL = 'ws:messages';\n\nredisSub.subscribe(CHANNEL, (err, count) => {\n if (err) console.error('خطأ في الاشتراك:', err);\n console.log(`تم الاشتراك في ${count} قناة`);\n});\n\n// استقبال الرسائل من النسخ الأخرى\nredisSub.on('message', (channel, message) => {\n const parsed = JSON.parse(message);\n wss.clients.forEach((client) => {\n if (client.readyState === WebSocket.OPEN) {\n client.send(parsed.data);\n }\n });\n});\n\nwss.on('connection', (ws) => {\n console.log('اتصال جديد');\n\n ws.on('message', (data) => {\n const message = data.toString();\n // النشر في Redis لجميع النسخ\n redisPub.publish(CHANNEL, JSON.stringify({ data: message }));\n });\n});\n\nالمزايا:
\n- \n
- التوسع الأفقي - أضف أي عدد تريده من الخوادم \n
- تحمل الأخطاء - عند تعطل نسخة واحدة، تستمر النسخ الأخرى في العمل \n
- ناقل رسائل موحد عبر Redis Pub/Sub \n
2. المصادقة وإدارة الجلسات
\n\nفي تطبيقات الإنتاج، لا يمكن الوثوق بالاتصالات غير الموثقة. سنقوم بتنفيذ التحقق من الرموز عبر JWT عند إنشاء الاتصال.
\n\nسنقوم بتثبيت jsonwebtoken:
npm install jsonwebtoken @types/jsonwebtoken\n\nسننشئ middleware لمصادقة WebSocket:
\n\nimport { IncomingMessage } from 'http';\nimport jwt from 'jsonwebtoken';\n\nconst JWT_SECRET = process.env.JWT_SECRET || 'your-secret-key';\n\ninterface AuthenticatedSocket extends WebSocket {\n userId?: string;\n}\n\nfunction authenticate(ws: AuthenticatedSocket, req: IncomingMessage): boolean {\n const url = new URL(req.url || '', 'http://localhost');\n const token = url.searchParams.get('token');\n\n if (!token) {\n ws.close(4001, 'الرمز غير مقدم');\n return false;\n }\n\n try {\n const decoded = jwt.verify(token, JWT_SECRET) as { userId: string };\n ws.userId = decoded.userId;\n console.log(`المستخدم ${decoded.userId} تمت المصادقة عليه`);\n return true;\n } catch (err) {\n ws.close(4002, 'رمز غير صالح');\n return false;\n }\n}\n\n// الاستخدام\nwss.on('connection', (ws: AuthenticatedSocket, req) => {\n if (!authenticate(ws, req)) return;\n\n ws.on('message', (data) => {\n // الآن نعرف userId\n console.log(`رسالة من ${ws.userId}: ${data}`);\n });\n});\n\nالتوصيات:
\n- \n
- قم بتمرير الرمز عبر معامل query أو الرؤوس عند إنشاء الاتصال \n
- استخدم رموز وصول قصيرة العمر ورموز تحديث \n
- قم بتخزين الجلسات في Redi