You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

19 KiB

راهنمای گرفتن توکن و ورود کلاینت به کلاس‌های plugNmeet

این راهنما خلاصه می‌کند که برای سناریوی استاد/دانشجو چگونه از سرویس plugNmeet توکن بگیریم و کلاینت فرانت‌اند (client/) با آن وارد کلاس شود.

پیش‌نیازها

  • آدرس سرویس: window.PLUG_N_MEET_SERVER_URL = "https://meet.newhorizonco.uk" (در config.js).
  • api_key و secret از فایل پیکربندی بک‌اند (services/plugnmeet-server/config.yaml).
  • بدنهٔ درخواست‌ها باید با پروتکل JSON متناظر با پیام‌های پروتوباف (plugnmeet-protocol) ارسال شود؛ سرور طبق HandleAuthHeaderCheck هدرهای امنیتی را بررسی می‌کند.

گام ۱: ایجاد یا فعال بودن اتاق

API Endpoint برای Django Backend:

POST /api/courses/<course-slug>/online/room/create/

بدنه درخواست از فرانت به Django:

{
  "subject": "کلاس جبر فصل ۱"         // اختیاری - عنوان روم
}

⚠️ نکات مهم:

  • فرانت نباید metadata ارسال کند!
  • بک‌اند Django (در apps/course/views/live_session.py) به‌طور خودکار تنظیمات امنیتی را اعمال می‌کند
  • این تضمین می‌کند که تنظیمات امنیتی به‌صورت متمرکز و یکسان اعمال شود

🎯 تنظیمات ضروری برای نمایش فیچرها:

  • برای نمایش Whiteboard: باید whiteboardFeatures.allowedWhiteboard: true باشد
  • برای نمایش SharedNotePad: باید sharedNotePadFeatures.allowedSharedNotePad: true باشد و Etherpad service فعال باشد
  • برای نمایش BreakoutRoom: باید breakoutRoomFeatures.isAllow: true باشد (فقط در منوی admin)

بدنه درخواست از Django به PlugNMeet (خودکار):

بک‌اند Django این بدنه را خودش به PlugNMeet ارسال می‌کند:

⚠️ توجه به نامگذاری:

  • در Python می‌توانید از snake_case استفاده کنید
  • اما حتماً قبل از ارسال به PlugNMeet API باید به camelCase تبدیل شود
  • مثال: default_lock_settingsdefaultLockSettings
  • مثال: room_featuresroomFeatures
{
  "room_id": "algebra-1402",
  "metadata": {
    "room_title": "کلاس جبر فصل ۱",
    "defaultLockSettings": {
      "lockMicrophone": true,       // 🔒 قفل - فقط میزبان می‌تواند باز کند
      "lockWebcam": true,           // 🔒 قفل - فقط میزبان می‌تواند باز کند
      "lockScreenSharing": true,    // 🔒 قفل - فقط میزبان می‌تواند باز کند
      "lockWhiteboard": false,      //  همه می‌توانند ویرایش کنند
      "lockSharedNotepad": false,   //  همه می‌توانند ویرایش کنند
      "lockChat": false,
      "lockChatSendMessage": false,
      "lockChatFileShare": false,
      "lockPrivateChat": false
    },
    "roomFeatures": {
      "allowWebcams": true,
      "muteOnStart": true,          // 🔇 همه با میک خاموش وارد می‌شوند
      "allowScreenSharing": true,
      "allowRecording": true,
      "allowRtmp": false,
      "allowViewOtherWebcams": true,
      "allowViewOtherParticipantsList": true,
      "adminOnlyWebcams": false,
      "allowPolls": true,
      "roomDuration": 0,
      "chatFeatures": {
        "allowChat": true,
        "allowFileUpload": true
      },
      "sharedNotePadFeatures": {
        "allowedSharedNotePad": true
      },
      "whiteboardFeatures": {
        "allowedWhiteboard": true
      },
      "breakoutRoomFeatures": {
        "isAllow": true,
        "allowedNumberRooms": 6
      },
      "waitingRoomFeatures": {
        "isActive": false
      },
      "recordingFeatures": {
        "isAllow": true,
        "isAllowCloud": true,
        "enableAutoCloudRecording": false
      }
    }
  }
}

چرا بک‌اند این کار را می‌کند؟

  • امنیت متمرکز: تنظیمات امنیتی در یک جا کنترل می‌شود
  • جلوگیری از دستکاری: فرانت نمی‌تواند تنظیمات را تغییر دهد
  • یکپارچگی: همه کلاس‌ها با تنظیمات یکسان ساخته می‌شوند
  • 🔒 طبق تابع AssignLockSettingsToUser در pkg/models/user_lock.go این مقادیر برای کاربران غیر-admin اعمال می‌شود

گام ۲: گرفتن توکن ورود

API Endpoint برای Django Backend:

POST /api/courses/online/room/token/

درخواست از فرانت به Django:

Headers:
  Authorization: Token <USER_TOKEN>
  Content-Type: application/json

Body:
{
  "course_slug": "algebra-10"
}

⚠️ نکات مهم:

  • فرانت فقط course_slug ارسال می‌کند!
  • بک‌اند Django از Authorization header کاربر را شناسایی می‌کند
  • بک‌اند خودش live session فعال دوره را پیدا می‌کند:
    # 1. پیدا کردن دوره
    course = Course.objects.get(slug=course_slug)
      
    # 2. پیدا کردن live session فعال
    session = CourseLiveSession.objects.get(
        course=course,
        ended_at__isnull=True  # session هایی که هنوز به پایان نرسیده‌اند
    )
      
    # 3. گرفتن room_id
    room_id = session.room_id
    
  • بک‌اند خودش همه اطلاعات کاربر را می‌سازد:
    • user_id از request.user
    • name از user.get_full_name() یا user.email
    • is_admin از user.can_manage_course(course)
    • profilePic از user.avatar
    • lock_settings برای غیر-admin

بدنه درخواست از Django به PlugNMeet (خودکار):

بک‌اند Django این payload را خودش می‌سازد و به PlugNMeet می‌فرستد:

برای استاد:

{
  "room_id": "algebra-1402",
  "user_info": {
    "user_id": "10",                    // 🔐 از request.user
    "name": "استاد نمونه",              // 🔐 از user.get_full_name()
    "is_admin": true,                   // 🔐 از user.can_manage_course()
    "user_metadata": {
      "is_hidden": false,
      "profilePic": "https://..."       // 🔐 از user.avatar
    }
  }
}

برای دانشجو:

{
  "room_id": "algebra-1402",
  "user_info": {
    "user_id": "27",                    // 🔐 از request.user
    "name": "دانشجو نمونه",             // 🔐 از user.get_full_name()
    "is_admin": false,                  // 🔐 از user.can_manage_course()
    "user_metadata": {
      "profilePic": "https://...",      // 🔐 از user.avatar
      "lock_settings": {                // 🔒 خودکار برای غیر-admin
        "lock_microphone": true,
        "lock_screen_sharing": true,
        "lock_webcam": true,
        "lock_whiteboard": false,       //  می‌تواند روی whiteboard بنویسد
        "lock_shared_notepad": false,   //  می‌تواند در notepad بنویسد
        "lock_chat": false,
        "lock_chat_send_message": false,
        "lock_chat_file_share": false,
        "lock_private_chat": false
      }
    }
  }
}

نحوه کار بک‌اند Django:

# 1. شناسایی کاربر از token
user = request.user  # از Authorization header

# 2. پیدا کردن دوره و session فعال
course = Course.objects.get(slug=course_slug)
session = CourseLiveSession.objects.get(course=course, ended_at__isnull=True)
room_id = session.room_id

# 3. تشخیص نقش
is_admin = user.can_manage_course(course)  # استاد یا مالک دوره

# 4. ساخت user_info
user_info = {
    'user_id': str(user.id),
    'name': user.get_full_name() or user.email,
    'is_admin': is_admin,
}

# 4. اضافه کردن profilePic
profile_pic = request.build_absolute_uri(user.avatar.url)
user_metadata['profilePic'] = profile_pic

# 5. اضافه کردن lock_settings برای غیر-admin
if not is_admin:
    user_metadata['lock_settings'] = {
        'lock_microphone': True,
        'lock_screen_sharing': True,
        'lock_webcam': True,
        'lock_whiteboard': False,      # دانشجو می‌تواند روی whiteboard بنویسد
        'lock_shared_notepad': False,  # دانشجو می‌تواند در notepad بنویسد
        'lock_chat': False,
        'lock_chat_send_message': False,
        'lock_chat_file_share': False,
        'lock_private_chat': False,
    }

ارسال به PlugNMeet:

بک‌اند Django با هدرهای امنیتی به PlugNMeet ارسال می‌کند:

  • API-KEY: از settings
  • HASH-SIGNATURE: HMAC_SHA256(body, secret)
  • این توکن JWT اختصاصی plugNmeet است که در GeneratePNMJoinToken ساخته می‌شود
  • is_admin: true باعث می‌شود در GetPNMJoinToken کاربر به عنوان presenter با تمام دسترسی‌ها ثبت شود
  • lock_settings باعث می‌شود در فرانت‌اند PlugNMeet دکمه‌های میکروفون/وبکم غیرفعال شوند

پاسخ Django به فرانت:

{
  "room_id": "algebra-1402",
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "plugnmeet": {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "expires": 300,
    ...
  }
}

فرانت با این token می‌تواند کاربر را به PlugNMeet وارد کند:

https://meet.newhorizonco.uk/?access_token=<TOKEN>

گام ۳: ورود کلاینت با توکن

۱. توکن را در URL یا کوکی قرار دهید؛ کلاینت مقدار را از access_token در کوئری‌استرینگ یا از کوکی pnm_access_token می‌خواند (getAccessToken در client/src/helpers/utils.ts). ۲. آدرس ورود: https://meet.newhorizonco.uk/?access_token=<TOKEN>. ۳. اپلیکیشن React موجود در client/src/components/app/index.tsx پس از بارگذاری:

  • درخواست POST /api/verifyToken را با هدر Authorization: <TOKEN> می‌فرستد (HandleVerifyToken).
  • اگر توکن معتبر باشد، لیست آدرس‌های NATS و موضوعات لازم را می‌گیرد و اتصال را آغاز می‌کند (startNatsConn). ۴. پس از اتصال، وضعیت کاربر و اتاق در Redux ذخیره می‌شود (sessionSlice). اگر کاربر ادمین باشد، تمام امکانات بدون محدودیت فعال است؛ در غیر این صورت مقدارهای lock_settings تعیین می‌کنند چه دکمه‌هایی فعال باشند.

کنترل حالت صحبت/شنیدن برای استاد و دانشجو

استاد (Moderator/Host):

  • در توکن is_admin: true ارسال می‌شود
  • بک‌اند Django در apps/course/views/live_session.py این را تشخیص می‌دهد:
    is_admin = user.can_manage_course(course)  # استاد یا مالک دوره
    
  • سرور PlugNMeet در GetPNMJoinToken رول presenter را فعال می‌کند
  • هیچ قفلی روی میکروفون، وبکم یا اشتراک صفحه اعمال نمی‌شود
  • 🎤 استاد می‌تواند بلافاصله صحبت کند و به دانشجو اجازه صحبت دهد

دانشجو (Participant):

  • 🔒 در توکن is_admin: false ارسال می‌شود
  • 🔒 بک‌اند Django خودکار lock_settings را اضافه می‌کند:
    if not is_admin:
        user_metadata['lock_settings'] = {
            'lock_microphone': True,
            'lock_screen_sharing': True,
            'lock_webcam': True,
            'lock_whiteboard': False,      # می‌تواند روی whiteboard بنویسد
            'lock_shared_notepad': False,  # می‌تواند در notepad بنویسد
            'lock_chat': False,
            'lock_chat_send_message': False,
            'lock_chat_file_share': False,
            'lock_private_chat': False,
        }
    
  • 🔇 دکمه‌های میکروفون، وبکم و اشتراک صفحه غیرفعال هستند
  • 👂 فقط می‌تواند گوش دهد تا میزبان اجازه دهد
  • اما می‌تواند در Whiteboard و SharedNotePad بنویسد و چت کند
  • این منطق در joinModal.tsx با متغیر isMicLock پیاده‌سازی شده است

نحوه دادن اجازه به دانشجو:

  • میزبان باید از داخل کلاس از طریق UI کنترل کند
  • یا از API /api/updateLockSettings یا switchPresenter استفاده کند

نکات تکمیلی

توکن‌ها و انقضا:

  • توکن‌ها زمان انقضای مفهومی دارند (client.token_validity در YAML)
  • در صورت نزدیک شدن به انقضا، کلاینت خودکار با REQ_RENEW_PNM_TOKEN درخواست تمدید می‌دهد

Authorization:

  • برای درخواست‌های بعدی به /api/... همان هدر Authorization را ست کنید
  • کلاینت این کار را در helpers/api/plugNmeetAPI.ts انجام می‌دهد

مدیریت دسترسی‌ها:

  • اگر می‌خواهید دانشجو را به صحبت‌کننده ارتقا دهید: /api/updateLockSettings یا switchPresenter
  • این کار فقط توسط میزبان امکان‌پذیر است

🔐 جمع‌بندی امنیت

چیزهایی که فرانت نباید انجام دهد:

موقع ساخت روم:

  • ارسال metadata
  • ارسال default_lock_settings
  • ارسال room_features

موقع گرفتن توکن:

  • ارسال room_id (بک‌اند خودش از session فعال می‌گیرد)
  • ارسال user_info
  • ارسال is_admin
  • ارسال lock_settings
  • ارسال user_id یا name

چیزهایی که فرانت فقط ارسال می‌کند:

موقع ساخت روم:

{
  "room_id": "algebra-1402",  // اختیاری
  "subject": "کلاس جبر"       // اختیاری
}

موقع گرفتن توکن:

{
  "course_slug": "algebra-10"   // فقط این!
}
  • Authorization: Token <USER_TOKEN> در header

چیزهایی که بک‌اند Django خودش انجام می‌دهد:

برای همه درخواست‌ها:

  • شناسایی کاربر از Authorization header
  • بررسی دسترسی با user.can_manage_course() یا Participant.objects.filter()

موقع ساخت روم:

  • تعیین defaultLockSettings (همه true به جز whiteboard/notepad)
  • تعیین roomFeatures کامل شامل:
    • sharedNotePadFeatures.allowedSharedNotePad: true
    • whiteboardFeatures.allowedWhiteboard: true
    • breakoutRoomFeatures.isAllow: true
    • chatFeatures, recordingFeatures, و سایر فیچرها
  • تبدیل نام‌های snake_case به camelCase قبل از ارسال به PlugNMeet
  • ساخت metadata کامل برای PlugNMeet

موقع گرفتن توکن:

  • پیدا کردن live session فعال از course_slug
  • گرفتن room_id از session
  • ساخت user_id از request.user.id
  • ساخت name از user.get_full_name() یا user.email
  • تشخیص is_admin از user.can_manage_course(course)
  • گرفتن profilePic از user.avatar
  • اضافه کردن lock_settings کامل برای غیر-admin شامل:
    • lock_microphone, lock_webcam, lock_screen_sharing (همه True)
    • lock_whiteboard, lock_shared_notepad (همه False - می‌توانند بنویسند)
    • lock_chat, lock_chat_send_message, lock_private_chat (همه False)
  • تبدیل نام‌های snake_case به camelCase قبل از ارسال
  • ساخت user_info کامل برای PlugNMeet

نتیجه:

  • 🔒 امنیت کامل: فرانت نمی‌تواند هیچ تنظیمات امنیتی را دستکاری کند
  • متمرکز: همه logic در بک‌اند Django است
  • 🎯 ساده: فرانت فقط course_slug و Authorization header ارسال می‌کند
  • 🔐 قابل کنترل: بک‌اند تعیین می‌کند کدام session فعال است

🐛 عیب‌یابی

مشکل: Whiteboard/SharedNotePad نمایش داده نمی‌شود

علائم:

  • آیکون Whiteboard در footer نمایش داده نمی‌شود
  • گزینه Enable/Disable SharedNotePad در منوی admin نیست
  • گزینه Manage Breakout Room در منوی admin نیست

راه حل‌ها:

  1. بررسی roomFeatures در room creation:

    "roomFeatures": {
      "sharedNotePadFeatures": {
        "allowedSharedNotePad": true  //  باید true باشد
      },
      "whiteboardFeatures": {
        "allowedWhiteboard": true      //  باید true باشد
      },
      "breakoutRoomFeatures": {
        "isAllow": true                //  باید true باشد
      }
    }
    
  2. بررسی نامگذاری فیلدها:

    • shared_note_pad_features (snake_case) - اشتباه
    • sharedNotePadFeatures (camelCase) - صحیح
  3. بررسی config.yaml در plugnmeet-server:

    shared_notepad:
      enabled: true  # ✅ باید true باشد
      etherpad_hosts:
        - id: "etherpad_node_01"
          host: "http://plugnmeet-etherpad:9001"
          client_id: "plugNmeet"
          client_secret: "..."
    
  4. بررسی Etherpad service:

    docker ps | grep etherpad
    # باید یک container با نام plugnmeet-etherpad اجرا باشد
    
  5. بررسی defaultLockSettings:

    • اگر lockWhiteboard: true باشد، فقط admin می‌تواند ویرایش کند
    • اگر lockSharedNotepad: true باشد، فقط admin می‌تواند ویرایش کند
  6. بررسی user lock_settings در توکن:

    "lock_settings": {
      "lock_whiteboard": false,     // false = می‌تواند ویرایش کند
      "lock_shared_notepad": false  // false = می‌تواند ویرایش کند
    }
    

مشکل: دانشجو نمی‌تواند در Whiteboard بنویسد

علت:

  • lock_whiteboard: true در توکن کاربر

راه حل:

  • در هنگام ساخت توکن برای دانشجو، lock_whiteboard را false کنید
  • یا از منوی admin، lock را برای آن کاربر خاص باز کنید

مشکل: SharedNotePad آیکون دارد اما باز نمی‌شود

علت:

  • Etherpad service اجرا نیست یا در دسترس نیست

راه حل:

# بررسی وضعیت Etherpad
docker-compose -f docker-compose.plugnmeet.yml ps etherpad

# اگر اجرا نیست، راه‌اندازی کنید
docker-compose -f docker-compose.plugnmeet.yml up -d plugnmeet-etherpad

# بررسی logs
docker-compose -f docker-compose.plugnmeet.yml logs -f plugnmeet-etherpad