چطور دو نفر روی یک وایت‌برد بدون تداخل با هم نقاشی می‌کنند

قابلیتی که تا وقتی دو نفر استفاده‌اش نکنند ساده به‌نظر می‌رسد

وایت‌برد برای یک نفر فقط یک بوم با ذخیره/بارگذاری است. لحظه‌ای که نفر دوم همان بورد را باز می‌کند، یک مسئله‌ی سیستم‌های توزیع‌شده با لباس اپلیکیشن نقاشی دارید: کدام تغییر برنده می‌شود، سرعت دیدن تغییر توسط طرف دیگر چقدر است، و — بخشی که راحت جا می‌افتد — چطور مطمئن شوید حرکت نشانگر در وایت‌برد پروژه‌ی A به جلسه‌ی پروژه‌ی B روی همان سرور نشت نمی‌کند.

چیزی که WKFGo دوباره استفاده می‌کند به‌جای ساختن از نو

WKFGo از قبل یک هاب وب‌سوکت اجرا می‌کند که به‌روزرسانی‌های زنده‌ی بورد را به هر کلاینت متصل هُل می‌دهد. وایت‌بردها سرور وب‌سوکت جدا نمی‌گیرند؛ یک برش باریک و صریحاً محدود از همان هاب می‌گیرند. هر پیام وایت‌برد با پیشوند wb: نام‌گذاری شده، و تابع ریلی هاب فقط پیام‌های مطابق با آن پیشوند را لمس می‌کند:

// relay re-broadcasts a client-sent message to everyone else in the sender's
// project room. Only whiteboard collaboration events ("wb:*" — scene deltas,
// pointer positions) are accepted; ProjectID/Actor are stamped server-side so
// a client can't spoof another room or user.
func (h *Hub) relay(sender *Client, raw []byte) {
    if sender.projectID == 0 {
        return
    }
    var ev Event
    if err := json.Unmarshal(raw, &ev); err != nil || !strings.HasPrefix(ev.Type, "wb:") {
        return
    }
    ev.ProjectID = sender.projectID
    ev.Actor = sender.userID
    ...
}

دو جزئیات اینجا واقعاً کار امنیت را انجام می‌دهند. اول، هر پیامی که با wb: شروع نشود بی‌صدا رد می‌شود — کلاینت نمی‌تواند از این کانال ریلی برای قاچاق رویدادهای دلخواه به جلسه‌ی کلاینت‌های دیگر استفاده کند. دوم، ProjectID و Actor سمت سرور از اتصال احراز‌هویت‌شده‌ی فرستنده بازنویسی می‌شوند، نه از هرچیزی که کلاینت در بدنه‌ی پیام ادعا می‌کند. کلاینتی که یک projectId جعلی در بار داده‌اش می‌فرستد نادیده گرفته می‌شود — سرور از قبل می‌داند اتصال متعلق به کدام اتاق است، چون این در زمان اتصال تعیین شده، نه به‌ازای هر پیام.

چرا ریلی، نه ذخیره-بعد-پخش

موقعیت‌های نشانگر و دلتاهای صحنه در حال پیشرفت مستقیم به کلاینت‌های دیگر در همان اتاق پروژه ریلی می‌شوند — عمداً از همان مسیر یک به‌روزرسانی تسک عبور نمی‌کنند، که وب‌هوک‌ها و لاگ رویداد بیرونی را هم مطلع می‌کند. کامنت کد صریح است: «حرکت‌های نشانگر مادّه‌ی وب‌هوک نیستند.» یک جلسه‌ی وایت‌برد می‌تواند ده‌ها پیام موقعیت-نشانگر در ثانیه تولید کند؛ رفتار با هر کدام به‌عنوان یک رویداد دامنه‌ی قابل‌ممیزی، خط‌لوله‌ی اعلان را با نویزی پر می‌کند که هیچ‌کس برایش وب‌هوک نمی‌خواهد. ذخیره‌ی محتوای واقعی بورد جداگانه اتفاق می‌افتد، هنگام ذخیره — ریلی وب‌سوکت فقط برای لایه‌ی زنده‌ی زودگذر «همین الان همه دارند چه‌کار می‌کنند» است.

چه اتفاقی برای یک کلاینت کند می‌افتد

اگر اتصال یکی از همکاران کند شود، هاب همه‌ی بقیه را منتظر تخلیه‌ی بافر ارسال همان یک کلاینت نگه نمی‌دارد:

select {
case c.send <- data:
default:
    // Slow/stuck client: drop the message rather than block the
    // broadcaster. The client will resync on its next full fetch.
}

یک پیام موقعیت-نشانگر افتاده نامرئی است — پیام بعدی کسری از ثانیه بعد می‌رسد و هیچ‌کس شکافی در ردّ نشانگر نمی‌بیند. این فقط چون پیام‌های ریلی وایت‌برد موقعیت‌های زودگذرند کار می‌کند، نه وضعیت معتبر؛ افتادن یکی چیزی را خراب نمی‌کند، فقط یعنی نمای گیرنده از یک نشانگر سریع‌الحرکت برای یک فریم کمی کمتر روان است.

چرا Excalidraw پین شده، نه شناور

خود ویرایشگر Excalidraw است و lockfile فعلی نسخه‌ی ۰٫۱۷٫۶ را resolve می‌کند. این تثبیت باعث می‌شود رفتار بوم، فایل‌های جانبی و سازگاری build در استقرارهای تکراری تغییر ناگهانی نکند. به‌روزرسانی کتابخانه باید مثل یک تغییر محصول آزمایش شود: build تولید، بارگذاری صحنه‌های قبلی، ذخیره‌ی فایل‌های تصویری و یک جلسه‌ی دونفره را کنترل کنید؛ صرفاً دنبال‌کردن آخرین تگ برای قابلیتی که داده‌ی کاربر را نگه می‌دارد معیار کافی نیست.

اشتباهات رایج

اعتماد به شناسه‌ی اتاق یا کاربر که کلاینت می‌فرستد. هر طراحی ریلی که «کدام اتاق» را از بار داده‌ی پیام بخواند به‌جای اتصال احراز‌هویت‌شده، فقط یک پیام دستکاری‌شده تا نشت بین‌پروژه‌ای فاصله دارد.

رفتار با هر پیام بلادرنگ به‌عنوان یک رویداد دامنه‌ی ذخیره‌شده. موقعیت نشانگر و خط‌های موقتی متعلق به همان خط‌لوله‌ای نیستند که چیزهایی که آدم‌ها و یکپارچه‌سازی‌ها برایشان اهمیت می‌دهند وب‌هوک راه می‌اندازد — آن خط‌لوله برای تغییرات وضعیتی است که اهمیت دارند، نه مختصات نشانگر ۶۰ بار در ثانیه.

مسدودکردن پخش‌کننده روی یک خواننده‌ی کند. یک کلاینت وب‌سوکت گیرکرده نباید بتواند به‌روزرسانی‌های زنده را برای بقیه‌ی اتاق متوقف کند؛ افتادن-و-همگام‌سازی‌مجدد بده‌بستان درستی برای داده‌ی زودگذر است.

سؤالات متداول

آیا این روی چند نمونه‌ی بک‌اند کار می‌کند؟

ریلی زنده‌ی وایت‌برد محلیِ همان نمونه‌ای می‌ماند که اتصالات وب‌سوکت را نگه می‌دارد — پل‌زدن بین‌نمونه‌ای برای رویدادهای بورد/اعلان اختیاری است، اما دلتاهای صحنه‌ی وایت‌برد می‌توانند به‌راحتی از سقف ۸ کیلوبایتیِ بار داده‌ی NOTIFY پستگرس که برای آن پل استفاده می‌شود بگذرند، پس همکاری وایت‌بردیِ بین‌نمونه‌ای فعلاً به نشست‌های چسبنده نیاز دارد (کلاینت همیشه به همان نمونه‌ی بک‌اند مسیریابی می‌شود).

اگر اتصال وسط ویرایش قطع شود چه؟

پیام‌های ریلی‌شده از دست می‌روند، اما وضعیت ذخیره‌شده‌ی بورد نه — اتصال مجدد از آخرین ذخیره همگام می‌شود، نه از جریان وب‌سوکت.

آیا کسی خارج از پروژه می‌تواند جلسه‌ی وایت‌برد را ببیند؟

نه — اتصال به یک projectID محدود است که در زمان اتصال از نشست احراز‌هویت‌شده تعیین شده، و ریلی فقط درون همان اتاق پخش می‌شود.

چه زمانی این وایت‌برد انتخاب درستی است؟

برای رترو، طوفان فکری، نقشه‌ی داستان و طرح اولیه‌ای که باید کنار پروژه بماند، وایت‌برد پروژه‌ای فاصله‌ی میان ایده و اجرا را کم می‌کند. یک پایلوت مفید این است که دو نفر هم‌زمان یک بورد آزمایشی را باز کنند، هرکدام چند شکل بسازند، یکی اتصالش را برای چند ثانیه قطع کند و پس از اتصال دوباره نتیجه را کنترل کنند. سپس صفحه را تازه‌سازی کنید تا مطمئن شوید صحنه‌ی ذخیره‌شده همان چیزی است که انتظار داشتید.

اما این قابلیت یک ویرایشگر تراکنشی یا CRDT کامل نیست. پیام‌های زنده به همکاران رله می‌شوند، ولی ذخیره‌ی پایدار صحنه به‌صورت «آخرین نویسنده برنده است» انجام می‌شود و اندازه‌ی داده نیز به ۸ مگابایت محدود است. در ویرایش سنگینِ هم‌زمان یا اینترنت ناپایدار، مالک جلسه باید پیش از خروج یک ذخیره‌ی نهایی را کنترل کند. برای سند تصمیم رسمی هم بهتر است خروجی جلسه را به یک صفحه‌ی دانش پروژه تبدیل کنید، نه اینکه تنها نسخه‌ی حقیقت داخل بورد بماند.

برای ارزیابی، یک جلسه‌ی واقعی ۲۰دقیقه‌ای برگزار کنید و سه معیار را بسنجید: آیا همه بدون اشتراک صفحه مشارکت کردند، آیا نتیجه پس از بارگذاری مجدد باقی ماند و آیا تبدیل خروجی به تسک کمتر از پنج دقیقه زمان برد. اگر پاسخ هر سه مثبت بود، فضای کاری خود را بسازید و قالب مناسب جلسه‌ی بعد را در همان پروژه نگه دارید.

جمع‌بندی

همکاری بلادرنگ روی وضعیت بصری مشترک به‌خاطر نقاشی سخت نیست — به‌خاطر این سخت است که هر پیام باید به اتاق درست محدود شود، به فرستنده‌ی درست نسبت داده شود صرف‌نظر از ادعای کلاینت، و آن‌قدر ارزان باشد که وقتی خواننده‌ای عقب می‌ماند بدون عاقبت بتوان آن را رها کرد.