Bitta ilova, ikki dunyo. Shaxsiy chatlar odatdagidek Telegram serverlariga, korxona chatlari esa o'z serverimizga boradi. Admin xodimni o'chirsa, korporativ qism shu zahoti yo'qoladi, shaxsiy Telegram ishlashda davom etadi.
logout → Corp moduli o'zini tozalaydi)Manba: README.mddocs/plan.md §4docs/client-contract.md
| Nima | Qayerdan | Qayerga | Protokol | Kod |
|---|---|---|---|---|
| Shaxsiy chatlar | tdesktop MTProto qatlami | Telegram DC | MTProto (o'zgarmagan) | apiwrap.cpp, mtproto/* |
| Korxona chatlari | Corp::Api (QNetworkAccessManager) | https://tele.dtwin.uz/api/* | HTTPS REST + JSON, Bearer | corp/corp_api.cpp |
| Real-time | Core::WebSocketClient (QSslSocket) | wss://tele.dtwin.uz/hub?access_token=… | SignalR JSON protokoli, faqat WebSockets | corp_api.cpp: connectHub, Realtime/CorpHub.cs |
| Admin | Brauzer, /teletwin/admin/ | /api/admin/* (role admin) | HTTPS REST | wwwroot/admin/index.html, AdminEndpoints.cs |
Default server: DefaultServer() → https://tele.dtwin.uz, TELETWIN_SERVER env bilan almashtiriladi corp_manager.cpp: DefaultServer
Ssenariyni tanlang: sequence diagramma qadamma-qadam o'ynaydi. Har bir qadamdagi method, path va JSON koddan olingan. Qadamni bosib, uning payload'ini ko'ring.
Hammasi Endpoints/*.cs va Program.cs dan. Token bo'lmasa yoki sessiya o'chirilgan bo'lsa: 401; admin bo'lmagan foydalanuvchi /api/admin/* ga: 403. Tashqi URL'da hammasi /teletwin prefiksi ostida (Caddy uni kesadi).
| Method | Path | Auth | Request | Response | Status kodlar |
|---|---|---|---|---|---|
| Program.cs | |||||
| GET | /health | yo'q | — | { status: "ok" } | 200 |
| GET | /admin | yo'q | — | redirect admin/ → wwwroot/admin/index.html | 302 |
| AuthEndpoints.cs | |||||
| POST | /api/auth/login | yo'q · rate limit login 10/min/IP | { login, password, deviceId } | { accessToken, cacheKey, employee { id, login, fullName, isAdmin } } | 200 · 401 parol/nofaol · 400 deviceId yo'q · 429 |
| GET | /api/me | Bearer | — | EmployeeDto | 200 · 401 · 404 |
| POST | /api/auth/logout | Bearer | — | faqat shu sessiya (sid claim) o'chadi | 204 · 401 |
| ChatEndpoints.cs | |||||
| GET | /api/chats | Bearer | — | [{ id, title, kind: 1|2, canPost }] faqat a'zo bo'lgan chatlar | 200 · 401 |
| GET | /api/chats/{chatId}/messages?afterId=&limit= | Bearer + a'zolik | limit 1..200 (default 50); afterId bo'lmasa: oxirgi sahifa | [{ id, chatId, senderId, senderName, text, sentAt }] o'sish tartibida | 200 · 404 a'zo emas |
| POST | /api/chats/{chatId}/messages | Bearer + CanPost | { text } | MessageDto + hub message → chat:{id} | 201 · 404 · 403 kanalga huquq yo'q · 400 bo'sh / >4096 |
| AdminEndpoints.cs · RequireRole("admin") | |||||
| GET | /api/admin/employees | admin | — | [{ id, login, fullName, isActive, isAdmin, activeSessions, chatIds }] | 200 · 403 |
| POST | /api/admin/employees | admin | { login, fullName, password, chatIds, isAdmin } | AdminEmployeeDto | 201 · 400 parol < 8 · 409 login band |
| POST | /api/admin/employees/{id}/password | admin | { password } | — | 204 · 400 · 404 |
| POST | /api/admin/employees/{id}/revoke | admin | — | { sessions, connections } + hub logout + Abort | 200 · 400 o'zini · 404 |
| POST | /api/admin/employees/{id}/restore | admin | — | IsActive = true | 204 · 404 |
| GET | /api/admin/chats | admin | — | [{ id, title, kind, memberIds }] | 200 |
| POST | /api/admin/chats | admin | { title, kind } | AdminChatDto, yaratuvchi a'zo + chats_changed | 201 · 400 |
| PUT | /api/admin/chats/{id}/members | admin | { employeeIds } | AdminChatDto; qo'shilgan/chiqarilganlarga chats_changed, guruhlar jonli yangilanadi | 200 · 404 |
| Realtime/CorpHub.cs · MapHub(Transports = WebSockets) | |||||
| WS | /hub?access_token= | token query'da (faqat /hub uchun) | invoke Ready() | qaytaradi employee id · eventlar: message MessageDto, logout { reason: "access_revoked" }, chats_changed | 101 · 401 |
backend/src/TeleTwin.Api/Endpoints/AuthEndpoints.csChatEndpoints.csAdminEndpoints.csContracts.csAuth/SessionAuthHandler.cs: ReadToken
tdesktop UI faqat MTProto obyektlarini taniydi. Shuning uchun Corp moduli serverdan kelgan JSON'ni lokal sun'iy MTP_channel / MTP_user / MTP_message / MTP_dialog ga aylantirib, tdesktop'ning o'z data qatlamiga beradi. Oddiy UI ularni oddiy chatdek chizadi.
{ "id": 57, "chatId": 2,
"senderId": 3,
"senderName": "Aziz Karimov",
"text": "Salom!",
"sentAt": "2026-…" }hub message yoki REST
ParseMessage(obj) .id = 57 .chatId = 2 .date = sentAt .toSecsSinceEpoch()
corp_api.cpp
MTP_message(flags, MTP_int(57), from = UserFor(3), peer = ChannelFor(2), date, MTP_string(text) …)
kanal: f_post | f_post_author
processUser(MTP_user) processChat(MTP_channel) history->addOlderSlice() applyDialogs(MTP_dialog) addNewMessage(…, Unread)
tdesktop'ning o'z API'si
corp/corp_manager.cpp: prepareMessage, PrepareChannel, PrepareUser, PrepareDialog, applyInitial, applyLivecorp/corp_api.cpp: ParseMessage
Haqiqiy Telegram id'lari 234 dan kichik. Corp peer'lar 48-bitli id maydonining eng oxirgi 232 ta qiymatini oladi, shuning uchun hech qachon to'qnashmaydi va IsCorpPeer() bitta taqqoslash bilan aniqlanadi.
O'q log2 shkalada. corp/corp_ids.h: kIdSpaceEnd, kCorpBase, IsCorpBare, ChannelFor, UserFor, ChatIdOf
| Server | tdesktop obyekti |
|---|---|
kind = 1 Group | MTP_channel + f_megagroup |
kind = 2 Channel | f_broadcast | f_signatures; xabar post_author = senderName |
kanal + canPost | chatAdminRights(post | edit | delete) |
guruh + !canPost | default_banned_rights(send_messages | send_media | send_plain) |
| chat ro'yxatdan chiqdi | f_left + History::clear(DeleteChat) |
| xodim (sender) | MTP_user(UserFor(id)), ism SplitName() |
| access_hash | doimiy 0x434F525054574E31 = ASCII "CORPTWN1" |
| Korxona folder | ChatFilters::setLocal, id 0x40000000, sarlavha "Korxona", emoji 💼 |
Telegram folder'larni o'zi sinxronlaydi va noma'lumini o'chiradi. Shuning uchun data_chat_filters.cpp ga lokal id'lar to'plami qo'shilgan: received() sinxrondan oldin takeLocal() bilan chiqarib, keyin restoreLocal() bilan qaytaradi; applyOrder() server tartibiga lokal id'larni qo'shadi; saveOrder() esa MTPmessages_UpdateDialogFiltersOrder ga faqat !isLocal(id) larni yuboradi; remove() lokalni o'chirmaydi.
Asosiy tamoyil: revoke darhol ishlasin va korporativ ma'lumot hech qachon Telegram'ga oqmasin.
Har so'rovda SessionAuthHandler: token → SHA-256 → Sessions + Employee.IsActive. JWT yo'q, demak "muddati tugashini kutish" oynasi ham yo'q: sessiya o'chdi = keyingi so'rov 401. Auth/SessionAuthHandler.csAuth/Secrets.cs
100000.<salt16 b64>.<hash32 b64>
100 000 iteratsiya, 16 bayt salt, 32 bayt hash, CryptographicOperations.FixedTimeEquals. Admin yaratgan parol kamida 8 belgi.
CryptProtectData(json, L"TeleTwin", …) → tdata/teletwin_<userId>.bin
Har Telegram akkaunti uchun alohida fayl: server, accessToken, cacheKey, employee. Faqat shu Windows foydalanuvchisi ochadi. Revoke/logout'da removeStored().
Bootstrap__AdminLogin Bootstrap__AdminPassword Bootstrap__AdminFullName
Bazada admin bo'lmasa yaratiladi. deploy.sh birinchi deployda .env ga openssl rand parol yozadi (umask 077).
tdesktop o'z-o'zidan har bir peer uchun Telegram'ga so'rov yuboradi (full info, o'qildi belgisi, draft, typing, link preview…). Corp id bilan bunday so'rov ketsa, Telegram xato qaytaradi va id'lar oshkor bo'ladi. Kodda 17 ta Corp::IsCorpPeer() to'sig'i, bitta yo'naltirish va bitta folder qoidasi bor (brifdagi "~15" dan biroz ko'p).
O'lchov natijalari brifdan (prod sinovi), kod emas. Mexanizm kodda: AdminEndpoints.cs: revokeRevoke:AbortGraceMs = 1000
Long polling'da har poll qayta autentifikatsiya qilinadi. Revoke'dan keyin sessiya o'chgani uchun keyingi poll 401 oladi va logout event yetkazilmay qoladi. WebSocket ulanishi ochiqligicha qoladi, event yetadi, keyin server uni o'zi uzadi. Program.cs: MapHub(o => o.Transports = HttpTransportType.WebSockets)
429; IP Caddy'ning X-Forwarded-For dan (UseForwardedHeaders).400).dtwin-prod_default tarmog'ida Caddy orqali.401 ni logout deb biladi: REST, scheduleReconnect dagi GET /api/me, hub logout.DESKTOP_APP_DISABLE_AUTOUPDATE=ON, crash report o'chiq.TeleTwin dtwin bilan umumiy Eskiz VPS'da turadi. HTTPS'ni dtwin'ning Caddy'si tugatadi, TeleTwin'ning o'z ikki konteyneri bor.
deploy/docker-compose.ymlbackend/Dockerfilecommit 0e71790 "deploy: point the API at teletwin-postgres, not the ambiguous 'postgres' alias" Caddy bloki dtwin repo'sidagi Caddyfile'da, bu repo'da yo'q: u brifdan olingan.
Host=postgresIkkala compose loyihasi bitta dtwin-prod_default tarmog'iga ulangan. U yerda dtwin'ning servisi ham postgres nomi bilan DNS alias oladi, shuning uchun Host=postgres dtwin bazasiga yo'naldi. Yechim: container_name bo'yicha aniq nom.
ConnectionStrings__TeleTwin:
"Host=teletwin-postgres;Database=teletwin;
Username=teletwin;Password=${POSTGRES_PASSWORD}"handle_path /teletwin/* {
reverse_proxy teletwin-api:8080 {
flush_interval -1
}
}handle_path prefiksni kesadi: API ichida yo'llar /api/…, /hub. flush_interval -1 WebSocket/stream uchun bufersiz uzatish.
ssh dtwincurrent symlink yangilanadiopenssl rand--env-file /opt/teletwin/.envDatabase__Provider=postgresEnsureCreateddocker exec dtwin-prod-web-1 wget teletwin-api:8080/health, 30 × 2 sdeploy/deploy.shProgram.cs: UsePostgres → Migrate
tdesktop'ni Windows'da yig'ish o'zi alohida loyiha: hamma kutubxona (Qt ham) manbadan statik yig'iladi. Bizning skriptlar uni bitta buyruqqa o'raydi.
build-win.cmd prepare → Telegram/build/prepare/win.batRo'yxat client/prepare.log: [1/36] … [36/36] dan. Fork'dagi o'zgarishlar: 293f74361c prepare: skip breakpad dump_syms on Windowsffefc5ba9d prepare: build qtwebsockets, disable pacman download timeout
build-win.cmd prepare → win.bat silent
ThirdParty/, Libraries/
configure.bat x64 -D TDESKTOP_API_ID=… -D TDESKTOP_API_HASH=… -D DESKTOP_APP_DISABLE_AUTOUPDATE=ON -D DESKTOP_APP_DISABLE_CRASH_REPORTS=ON
api_id: git-ignored api-credentials.local.cmd
cd tdesktop\out cmake --build . --config Release --target Telegram --parallel
corp/*.cpp CMakeLists.txt:630-636 da
package-win.ps1 -Version 0.1.0 → TeleTwin-0.1.0-win64-portable.zip → TeleTwin-Setup-0.1.0.exe
ZIP: TelegramForcePortable; Inno Setup: per-user, admin shart emas
core/version.h: AppName = AppFile = "TeleTwin", yangi AppId = {DFCABADF-14C0-4CF6-939F-01EB39867F20} (installer'da ham shu) → o'z %APPDATA%\TeleTwin papkasi. Commit 7b7c0796af "Brand data dir and app id as TeleTwin…". Fork'ga rasmiy update kelmasligi kerak: autoupdate configure'da o'chirilgan.
CONFIG env o'zgaruvchisi pacman-key'ni buzdimsys2 stage'ida pacman-key --init tizimdagi CONFIG qiymatini o'qib yiqildi. Yechim: o'zgaruvchisiz muhitda ishga tushirish. manba: sessiya tarixi (brif), kodda iz yo'q
Yuklab olish timeout bilan uzildi → pacman -Syu --noconfirm --disable-download-timeout. prepare.py:543ffefc5ba9d
dump_syms yig'ilmadiBuildTools'da ATL komponenti yo'q edi; crash report baribir o'chiq, shuning uchun Windows'da dump_syms o'tkazib yuborildi. 293f74361c
C:\msys64 PATH'da: MinGW header'lari MSVC'ga sizdibuild-win.cmd vcvars'dan oldin C:\msys64\ucrt64\bin, C:\msys64\usr\bin va scoop mingw'ni PATH'dan olib tashlaydi. build-win.cmd:11-13d6d4d6c
NoDefaultCurrentDirectoryInExePathBu sozlama yoqilganda cmd joriy papkadagi configure.bat ni topmaydi → skript uni to'liq yo'l bilan chaqiradi. build-win.cmd:3167f49d8
.pch qoldirdiQt 6.11.2 static (Debug + RelWithDebInfo) precompiled header'lari diskni yedi; tozalab, build davom ettirildi. manba: brif, kodda iz yo'q
Client asosi: UI, MTProto, multi-account. Fork teletwin-desktop, branch teletwin, base f985723 (dev, v7.3.3 dan keyin).
.NET 10 minimal API: auth, chatlar, admin. TeleTwin.Api.
Hub /hub, faqat WebSockets: message, logout, chats_changed. Client SignalR JSON protokolini qo'lda gapiradi.
Npgsql (prod, migratsiyalar) va SQLite (dev, testlar). TeleTwinDb.
teletwin-postgres, volume pgdata, faqat ichki tarmoqda.
Loyiha teletwin: api + postgres, tashqi tarmoq dtwin-prod_default.
dtwin konteynerida TLS va handle_path /teletwin/* reverse proxy.
tdesktop ichidagi QSslSocket asosidagi WS client; QtWebSockets moduliga bog'lanmaydi.
Ikkala private repo: XakimovNosirbek/teletwin va XakimovNosirbek/teletwin-desktop (submodule).
installer/teletwin.iss: per-user o'rnatuvchi, {localappdata}\Programs\TeleTwin.
12 integratsion test: haqiqiy HTTP va WebSocket orqali CorpFlowTests.cs.
Admin panel tekshiruvi (CDP), shu sahifa va deck animatsiyasi.
| Yo'l | Qatorlar | Mas'uliyat |
|---|---|---|
| backend/src/TeleTwin.Api | ||
Program.cs | 85 | DbContext (Npgsql/SQLite), Session auth scheme, SignalR, ForwardedHeaders, login rate limit, Migrate/EnsureCreated, Bootstrap + DevSeed, static admin, /hub faqat WebSockets |
Endpoints/AuthEndpoints.cs | 55 | login, /api/me, logout |
Endpoints/ChatEndpoints.cs | 69 | chatlar ro'yxati, tarix (afterId/limit), yuborish + hub push |
Endpoints/AdminEndpoints.cs | 137 | xodimlar CRUD, parol, revoke / restore, chatlar va a'zolar |
Endpoints/Contracts.cs | 21 | DTO record'lar (wire format) |
Realtime/CorpHub.cs | 47 | hub: OnConnectedAsync guruhlar, Ready(), event nomlari |
Realtime/ConnectionRegistry.cs | 36 | xodim → jonli ulanishlar (Abort uchun), MembershipNotifier |
Auth/SessionAuthHandler.cs | 62 | Bearer / access_token → SHA-256 → Sessions; claims sid, role admin |
Auth/Secrets.cs | 34 | PBKDF2, token generatsiya, token hash |
Data/TeleTwinDb.cs · Bootstrap.cs · DevSeed.cs | 21 · 42 · 34 | EF model + indekslar; birinchi admin; dev seed |
Domain/Entities.cs | 56 | Employee, Session, Chat, ChatMember, Message |
wwwroot/admin/index.html | 10 KB | admin panel (vanilla JS) |
backend/tests/…/CorpFlowTests.cs | 198 | 12 ta [Fact]: login, revoke, rate limit, live a'zolik… |
| deploy | ||
deploy/docker-compose.yml · deploy.sh · backend/Dockerfile | 43 · 48 · 16 | konteynerlar/tarmoqlar; release + health; multi-stage image |
| client/tdesktop/Telegram/SourceFiles (fork) | ||
corp/corp_ids.h | 39 | id diapazoni, IsCorpPeer, kCorpFilterId |
corp/corp_api.h/.cpp | 120 / 318 | REST client, SignalR-over-WebSocket (handshake, Ready, ping 15 s, 0x1E freymlar), reconnect backoff 1→30 s |
corp/corp_manager.h/.cpp | 76 / 584 | JSON → MTP obyektlar, Data::Session'ga berish, lokal folder, dedupe, wipe, DPAPI saqlash |
corp/corp_box.h/.cpp | 17 / 112 | "Korxonaga kirish" oynasi (Server, Login, Parol) va holat oynasi |
apiwrap.cpp | 7 joy | sendMessage yo'naltirish; full peer, peer settings, draft, forward, sendFiles, sendFile to'siqlari |
data/data_chat_filters.cpp | — | setLocal / removeLocal / takeLocal / restoreLocal, applyOrder, saveOrder, contains |
data/data_histories.cpp | 3 joy | dialog entry, read request, sendRequest to'siqlari |
api/api_sending.cpp · api_send_progress.cpp · api_chat_participants.cpp | 2 · 1 · 1 | media yuborish, typing, self participant |
data/data_peer.cpp · components/sponsored_messages.cpp · history_view_webpage_processor.cpp | 1 · 2 · 3 | full update, reklama, link preview |
main/main_session.cpp:276 · main_session.h | — | sessiya boshida Corp::Manager yaratiladi; session->corp() |
window/window_main_menu.cpp:711 | — | menyuda "Korxona" → ShowCorpBox |
core/version.h · CMakeLists.txt:630-636 | — | brending; corp fayllarini build'ga qo'shish |
| client (teletwin repo) | ||
build-win.cmd · package-win.ps1 · installer/teletwin.iss · README-colleagues.txt | — | prepare/configure/build; ZIP + installer; hamkasblar uchun qo'llanma |
| Qism | Holat | Dalil |
|---|---|---|
| Backend API + hub | tayyor | 12 integratsion test (haqiqiy HTTP + WebSocket, TestServer); brif bo'yicha 5/5 ketma-ket barqaror. Bu sahifa uchun qayta yurgizilmadi. CorpFlowTests.cs |
| Production | ishlayapti | tele.dtwin.uz, admin panel headless Chrome'da tekshirilgan; revoke o'lchovi 91 ms / ~1.1 s / 401 (brifdan). |
| Client C++ corp qatlami | E2E tekshirilmagan | Build hali davom etmoqda. Fork commit'ining o'zi: 696255a3a4 Corp layer … (WIP, not compiled yet). Joriy client/build.log (2026-10-29 15:46) da corp_box.cpp da 7 ta kompilyatsiya xatosi: C2672 Ui::GenericBox::addRow (40-qator) va C2660 Corp::Manager::login: function does not take 4 arguments (63-qator). |
cacheKey bilan shifrlangan lokal kesh | hali yo'q | Server beradi, client DPAPI faylida saqlaydi, lekin ishlatmaydi: corp xabarlar faqat xotirada turadi, wipe() ularni tozalaydi. |
| O'qilmagan/o'qildi holati | lokal | PrepareDialog da unread_count = 0, read so'rovi data_histories.cpp da lokal yopiladi. |
| README holat jadvali | eskirgan | "7 integration tests", "client not started" deydi; kod 12 test va client qatlamini ko'rsatadi. |
| Litsenziya | ochiq savol | GPL-3.0 (+OpenSSL istisno); tashqariga tarqatish manba ochishni talab qiladi; repo'lar private, yurist tekshiruvi kutilmoqda. |
tdesktop yozuvchisi (OGG Opus) qayta ishlatiladi; upload endpoint kerak.
MinIO (S3) + webFileLoader orqali yuklab olish. Hozir sendFiles corp peer uchun bloklangan.
Self-hosted LiveKit SFU, avval webview, keyin native.
Ikonka va nomni to'liq almashtirish (Telegram nomi/logosi ishlatilmaydi), o'z update serveri.