Skip to content
Misha-ChanPublic

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

78 Commits

Folders and files

Repository files navigation

نظام المراسلات (Messaging System)

نظام مراسلات مبني من الصفر (مو تعديل على مشروع hostaka القديم): محادثات خاصة + مجموعات، مع تشفير طرف-لطرف اختياري للمحادثات الخاصة، تفاعلات، تعديل/حذف، إيصالات قراءة، مؤشر كتابة، خصوصية المراسلة، والحظر.

الفرق عن المشروع القديم

المشروع القديم (hostaka) كان كل شيء بملفين ضخمين: server.js (4146 سطر) وdatabase.js (1247 سطر)، فيهم كل شيء (منشورات، فيديو، AI، إدارة، مراسلات...) مكدّس ببعضه. هذا خلى أي تعديل بسيط بنظام المراسلات محفوف بالمخاطر لأنه يمر بمنطق مرتبط بميزات تانية.

هذا المشروع مبني بمعمارية مفصولة:

src/
  db/         اتصال قاعدة البيانات + تعريف الجداول (schema.js)
  services/   منطق العمل الفعلي (استعلامات SQL) — لا يعرف شيء عن HTTP
  routes/     طبقة HTTP الرقيقة — تستدعي services وترجع JSON، ما فيها منطق أعمال
  middleware/ المصادقة (JWT) + معالج أخطاء مركزي واحد
public/       الواجهة (بدون build step — HTML/CSS/JS عادي)

كل جزء مسؤول عن شيء واحد فقط. لو صار خلل بميزة معيّنة (مثلاً التفاعلات)، المكان يلي فيه المنطق محدود بملف واحد صغير (messages.service.js أو groups.service.js)، مو مبعثر بملف 4000 سطر.

التشغيل محلياً

npm install
cp .env.example .env
# عدّل .env: حط رابط قاعدة بيانات Turso (أو استخدم TURSO_DATABASE_URL=file:./local.db للتجربة المحلية)
npm start

يفتح على http://localhost:3000 — صفحة الدخول أول شيء تظهر (/login.html).

النشر على Vercel

المشروع Express عادي (app.listen())، وهذا ما يشتغل مباشرة على Vercel لأنه بيئة serverless (كل طلب ممكن يوصل لعملية جديدة تماماً، ما فيه سيرفر مستمر شغّال بالخلفية). لهاي السبب مضاف:

  • src/app.js — يبني تطبيق Express بدون app.listen()، قابل لإعادة الاستخدام.
  • server.js — نقطة التشغيل المحلية فقط (npm start)، تستدعي app.listen().
  • api/index.js — نقطة الدخول لـ Vercel، تصدّر دالة (req, res) => ... بدل سيرفر مستمر، وتتأكد قاعدة البيانات جاهزة (ensureSchemaReady) قبل أي طلب — مهم لأن كل "cold start" بالسيرفرلس يشغّل الملف من جديد.
  • vercel.json — يوجّه كل /api/* لملف api/index.js؛ ملفات public/ تُخدَّم مباشرة من Vercel (static hosting) بدون المرور بالدالة إطلاقاً.

خطوات النشر:

  1. اربط المستودع بمشروع Vercel (زي ما سويت).

  2. الأهم: من إعدادات المشروع على Vercel → Settings → Environment Variables، ضيف:

    • TURSO_DATABASE_URL
    • TURSO_AUTH_TOKEN
    • JWT_SECRET (سلسلة عشوائية طويلة)

    ملف .env المحلي ما ينرفع لـ Vercel أبداً (وما لازم يكون بالمستودع أصلاً) — لازم تضبط المتغيرات يدوياً من لوحة Vercel. هذا على الأغلب سبب الكراش (FUNCTION_INVOCATION_FAILED) يلي شفته بالمشروع القديم — لو الدالة ما تلاقي TURSO_DATABASE_URL/TURSO_AUTH_TOKEN صحيحة، القديم كان يطيح بشكل كامل. النسخة الجديدة بترجع رسالة خطأ واضحة (500 مع تفاصيل) بدل ما تطيح بصمت — جرّبتها فعلياً بمفاتيح خاطئة ورجعت رسالة مفهومة.

  3. بعد الإعداد، أعد النشر (Redeploy) — أو أي push جديد يكفي.

قاعدة البيانات

نفس نوع قاعدة البيانات القديمة (libSQL / Turso). يدعم الاتصال بنفس قاعدة بيانات مشروع hostaka القديم مباشرة — عند أول تشغيل، الكود يتحقق تلقائياً من الأعمدة الناقصة (يلي يحتاجها هذا المشروع بس ما كانت موجودة بالجداول القديمة) ويضيفها بأمان (ALTER TABLE ... ADD COLUMN) بدون ما يلمس أي بيانات موجودة — الحسابات وكلمات المرور (bcrypt، نفس الطريقة بالضبط) والرسائل والمجموعات القديمة تبقى كما هي. جرّبت هذا فعلياً بمحاكاة قاعدة بيانات بنفس الـ schema القديم بالضبط + حساب حقيقي، وأكدت إن تسجيل الدخول والمراسلة يشتغلوا فوراً بعد أول تشغيل، وإن التشغيل المتكرر (زي ما بيصير بكل "cold start" على Vercel) آمن ولا يكرر الإضافة ولا يطيح.

الجداول التالية متطابقة تماماً مع القديم (صفر تغيير): message_reactions, groups, group_messages, group_message_reactions, follows, blocks.

الجداول التالية فيها أعمدة إضافية جديدة بيضيفها المشروع تلقائياً لو ناقصة:

جدول أعمدة جديدة تُضاف تلقائياً
users public_key, message_privacy, read_receipts
messages reply_to, encrypted, iv
group_members last_read_at

جداول جديدة بالكامل (ما كانت موجودة بالقديم، تُنشأ فارغة): dm_nicknames, typing_status, key_backups.

جدول الغرض
users الحسابات (نفس حسابات hostaka القديمة لو وصّلت نفس القاعدة)
messages رسائل خاصة (1↔1)
message_reactions تفاعلات الرسائل الخاصة
dm_nicknames كنية خاصة تراها أنت فقط لشخص معيّن
typing_status مؤشر "يكتب الآن" (خاص + مجموعات)
groups / group_members المجموعات وأعضاؤها وأدوارهم
group_messages / group_message_reactions رسائل المجموعات وتفاعلاتها
key_backups نسخة احتياطية من مفتاح التشفير محمية برمز PIN
blocks / follows الحظر وخصوصية المراسلة

التشفير من طرف لطرف (E2EE)

يشمل المحادثات الخاصة فقط (المجموعات تحتاج بنية تشفير مختلفة — تشفير جماعي/توزيع مفاتيح — وهذا خارج نطاق هذا المشروع). التصميم:

  • كل حساب له مفتاح ECDH (P-256) واحد، يتولّد بالمتصفح ويُخزّن بـ IndexedDB.
  • المفتاح العام يُرفع للسيرفر (/api/keys/public)؛ المفتاح الخاص لا يغادر جهازك أبداً بصيغة غير مشفّرة.
  • لكل محادثة، الطرفان يشتقّان نفس مفتاح AES-256-GCM عبر ECDH — السيرفر يرى نص مشفّر فقط.
  • نسخة احتياطية بـ PIN: عشان ما تفقد إمكانية قراءة رسائلك القديمة لو غيّرت جهاز/متصفح، فيه خيار تخزين نسخة من المفتاح الخاص مشفّرة برمز PIN من 6 أرقام (PBKDF2 بـ 600 ألف تكرار). السيرفر ما يشوف رمز الـ PIN أبداً، بس يتحقق من "verifier" ويقفل الحساب مؤقتاً بعد 5 محاولات خاطئة.
  • حدود صادقة: هذا تصميم تشفير ثابت لكل زوج محادثة (مو forward secrecy متل Signal)، ورمز PIN من 6 أرقام مهما كان الـ KDF بطيء يبقى عرضة نظرياً لهجوم برute-force حاسوبي ضخم لو انسرقت قاعدة البيانات كاملة. هذا قيد معروف ومقبول لأنظمة PIN القصيرة، مو خلل بالتصميم.

نقاط تصميم تجنّبت مشاكل شائعة

  • معالج أخطاء مركزي واحد (middleware/errorHandler.js) بدل try/catch متكرر بكل راوت ينسى أحياناً — أي استثناء غير متوقع يوصل هناك بدل ما يعلّق الطلب أو يطيح السيرفر.
  • مسارات API مصمّمة لتفادي تعارض الترتيب: مثلاً /api/keys/backup/status و /api/keys/public/:username بادئات منفصلة تماماً، بدل خلط مسار ثابت مع مسار بباراميتر بنفس المستوى (نمط بيسبب أخطاء "تبين بسيطة بس ما تنحل" لما ترتيب تعريف الراوتات يهم).
  • كل استعلام SQL معامل (parameterized) — بدون استثناء، بدون تركيب SQL بالنص.

الميزات المُنفَّذة

  • محادثات خاصة: إرسال/تعديل/حذف، صور، ردود على رسالة، تفاعلات، إيصالات قراءة، عدّاد غير مقروء
  • كنية خاصة لكل محادثة، حذف محادثة كاملة
  • خصوصية المراسلة (الجميع / متابعيني فقط / لا أحد) + حظر
  • مجموعات: إنشاء/تعديل/حذف، أعضاء وأدوار (مالك/مشرف/عضو)، كنية داخل المجموعة
  • رسائل مجموعات: إرسال/تعديل/حذف/تفاعلات، وسائط المجموعة
  • مؤشر "يكتب الآن" لكل من الخاص والمجموعات
  • تشفير طرف-لطرف اختياري للمحادثات الخاصة + نسخة احتياطية بـ PIN

ماذا لو بدك تدمجه بمشروع فيه users كامل مسبقاً؟

احذف تعريف جدول users من src/db/schema.js (خله يشير لجدولك الموجود بنفس أسماء الأعمدة المستخدمة: id, username, display_name, avatar, message_privacy, read_receipts)، واحذف src/routes/auth.routes.js واستخدم نظام المصادقة عندك، بشرط يحط req.user.id بنفس الشكل يلي يتوقعه middleware/auth.js.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages