ویجت‌های درون پیام، بلوک‌های تعاملی HTML، CSS و JavaScript هستند که مستقیماً در چت‌ها قرار می‌گیرند. این ویجت‌ها اکنون به نسخه‌های بتا تلگرام برای اندروید و iOS رسیده‌اند. در تاریخ ۲۵ اکتبر، این قابلیت در دسترس قرار گرفت در اندروید نسخه 13.2.0 (7254) و iOS نسخه 13.2 (35726)، و در اندروید، یک ویجت دست‌ساز قبلاً جاوااسکریپت را اجرا کرده است. بیلد اندروید 13.2.0 (7260) مورخ ۲۷ اکتبر نشان می‌دهد که این قابلیت چگونه کار می‌کند: بدون موتور مرورگر، با مفسر اختصاصی خود و سهمیه پردازنده، و با یک داور جداگانه که تشخیص می‌دهد آیا یک ضربه (tap) واقعی بوده است یا خیر. رباتی که ویجت درخواستی به آن ارسال کرده است، شناسه کاربری (ID) فردی که ضربه زده را می‌داند. در تلگرام دسکتاپ و Bot API عمومی هنوز خبری از ویجت‌ها نیست.

مکانیسم درخواست‌ها به ربات در بتای macOS در مقاله‌ای از ۲۷ اکتبر بررسی شد؛ در اینجا، به آنچه بیلد اندروید اضافه می‌کند، می‌پردازیم.

ویجت‌ها در کجا موجود هستند

کلاینتبیلدچه چیزی قابل مشاهده است
macOS, بتا13.1.0سندباکس، درخواست‌ها به ربات، صفحات آزمایشی — از اواسط اکتبر
Android, بتا13.2.0 (7254, 7260)طرح، سندباکس، متون رابط کاربری؛ ویجت‌ها کار می‌کنند
iOS, بتا13.2 (35726)ویجت‌های درون پیام و ابزارهای بررسی آن‌ها
Telegram Desktop7.3.3خیر: نه در کد و نه در طرح لایه 232
Bot API10.3خیر

بتای اندروید از طریق کانال رسمی و لینک مستقیم از وب‌سایت تلگرام توزیع می‌شود. پروتکل کلاینت در آن لایه 233 است که یک لایه جدیدتر از نسخه عمومی است.

نحوه قرارگیری ویجت در پیام

ویجت یک نوع رسانه جدید است: messageMediaWidget. علاوه بر کد اصلی (payload) و ابعاد، شامل دو مجموعه داده است: chat_data — داده‌های مربوط به این پیام، و public_data به همراه هش — داده‌های عمومی ویجت که ربات می‌تواند پس از ارسال آن‌ها را تغییر دهد (messages.editMessageWidgetData, updateMessageWidgetData). پرچم‌های can_query و background_query نشان می‌دهند که آیا می‌توان از ویجت به ربات با ضربه یا بدون آن نوشت. is_copy یک کپی فوروارد شده را مشخص می‌کند. ویجت همچنین می‌تواند به عنوان یک نتیجه اینلاین از ربات دریافت شود — botInlineMessageWidget.

مجوزها توسط فرستنده تنظیم می‌شوند. در پیام‌های خطای بخش بومی کلاینت، پرچم‌های سرور allow_queries و fwd_allow_queries ذکر شده‌اند: بدون اولی، ویجت اصلاً به ربات نمی‌نویسد، و بدون دومی، کپی فوروارد شده آن ساکت می‌ماند. درخواست پس‌زمینه به background_query نیز نیاز دارد و تنها زمانی ارسال می‌شود که ویجت روی صفحه نمایش باشد.

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

یک ضربه (tap) به messages.getBotWidgetQueryAnswer تبدیل می‌شود که شامل فیلدهای چت، پیام و data — تا ۶۴ بایت — است. ربات به‌روزرسانی updateBotWidgetQuery را دریافت کرده و با متد messages.setBotWidgetQueryAnswer با یک رشته result پاسخ می‌دهد.

در به‌روزرسانی برای ربات، فیلد user_id اجباری است و بدون پرچم می‌آید: ربات همیشه می‌داند چه کسی ضربه زده است. در کنار آن، chat_type، chat_instance — همان شناسه چت ناشناس که برای دکمه‌های اینلاین معمولی استفاده می‌شود — شماره پیام و widget_id ارسال می‌شوند. برای کپی‌های فوروارد شده و پیام‌های اینلاین، به‌روزرسانی‌های خاص خود وجود دارد — updateBotWidgetCopyQuery و updateInlineBotWidgetQuery.

این شبیه به ضربه زدن روی یک دکمه اینلاین با کال‌بک است، با این تفاوت که دکمه توسط اسکریپت ترسیم می‌شود و داده‌ها نیز توسط همان اسکریپت جمع‌آوری می‌شوند.

نحوه اجرای کد ویجت

در اندروید، نه WebView و نه Chromium در زیر ویجت وجود ندارد. کد توسط کتابخانه بومی twidget در داخل کتابخانه اصلی کلاینت اجرا می‌شود — همین نام برای انواع Twidget* در بتای macOS و مخزن خصوصی متصل به سورس کد آن استفاده می‌شود. اسکریپت‌های سندباکس دو بار در کلاینت قرار دارند: به عنوان سورس کد و به عنوان بایت‌کد .qbc که توسط مفسر داخلی جاوااسکریپت اجرا می‌شود. تصویر توسط رندرکننده اختصاصی مستقیماً در پنجره اندروید ترسیم می‌شود — طرح‌بندی مرورگر وجود ندارد، بلکه تجزیه آن انجام می‌شود: یک بسته validator جداگانه HTML و CSS را بررسی می‌کند.

محدودیت‌ها در کد نوشته شده‌اند، نه در قوانین برای توسعه‌دهندگان:

  • پردازنده — ویجت دارای سهمیه زمانی (cpuQuotaMs) است؛ تجاوز از آن منجر به خطای «CPU limit exceeded» می‌شود.
  • تایمرها — تعداد تایمرهای همزمان محدود است، موتور تایمرهای اضافی را رد می‌کند.
  • شبکه — fetch, XMLHttpRequest, WebSocket و navigator.sendBeacon مسدود شده‌اند، navigator.onLine همیشه نادرست است، userAgent — TelegramWidget/1.0.
  • ذخیره‌سازی — هر ویجت با یک فضای خالی شروع می‌کند.
  • خروجی‌های خارجی — openLink فقط آدرس https تا ۲۵۶ کاراکتر را می‌پذیرد و می‌پرسد: «This widget wants to open:»، copyText — تا ۲۵۶ کاراکتر.
  • پل به کلاینت — پس از اجرا، توابع سیستمی __host, __hostOps و توابع مشابه از شیء سراسری حذف می‌شوند و کد ویجت به آن‌ها دسترسی ندارد.

فایل‌های سندباکس توسط کلاینت با چک‌سام‌های موجود در manifest.txt مقایسه می‌شوند؛ عدم تطابق منجر به خطای جداگانه «Bundle checksum mismatch» می‌شود.

نحوه تشخیص ضربه واقعی از شبیه‌سازی توسط کلاینت

درخواست به ربات بدون مجوز پس‌زمینه تنها از طریق کنترل‌کننده ضربه (tap handler) ارسال می‌شود — «یک فراخوانی برای هر ضربه روی یک ویجت قابل مشاهده»، همانطور که در پیام خطا ذکر شده است. این اسکریپت نیست که تصمیم می‌گیرد آیا ضربه واقعی بوده است، بلکه داور لمسی بومی این کار را انجام می‌دهد: توابع arbiterDown, arbiterMove, arbiterUp و arbiterHoldDeadline مختصات انگشت و زمان نگه داشتن را دریافت می‌کنند. طبق طراحی، یک کلیک مصنوعی از جاوااسکریپت، تایمر یا کشیدن و رها کردن، درخواستی ارسال نخواهد کرد.

در نسخه بتا، ابزارهایی برای بررسی این مرز نیز وجود دارد: صفحه تطابق (WidgetConformanceActivity) و اجرای مجموعه‌ای از حملات (attackScan, WidgetAttacks). مجموعه صفحات آزمایشی حمله برای macOS — ۲۸ صفحه که سعی می‌کنند بدون ضربه پیامی ارسال کنند یا کاربر را به لینکی هدایت کنند — در کد باز کلاینت موجود است.

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

متون رابط کاربری از قبل در دیکشنری انگلیسی کلاینت موجود هستند: «Loading widget…», «Widget failed to load», «Widget stopped», «Preview of the widget» و هشدار «This widget can send data to its bot's server when you tap it.»

در تنظیمات بتا، سوئیچ جداگانه‌ای برای غیرفعال کردن ویجت‌ها وجود ندارد — تسترها به این نکته اشاره می‌کنند. ارسال ویجت‌ها هنوز برای همه ممکن نیست و در Bot API متدی برای آن‌ها وجود ندارد.