diff --git a/.sw b/.sw index 8d57935..e02373a 100644 --- a/.sw +++ b/.sw @@ -12,24 +12,52 @@ Never store secrets in this file: no Telegram tokens, proxy passwords, API keys, ## Product -Self-hosted live chat similar to Jivo. Website widget sends visitor messages to Telegram operators. Operators reply in Telegram, and replies return back into the website widget through WebSocket. +Self-hosted live chat similar to Jivo/Tidio. Website widget sends visitor messages to Telegram operators. Operators reply in Telegram, and replies return back into the website widget through WebSocket. + +MVP is currently functional: + +```text +website -> widget.js -> backend -> Telegram operator -> backend -> WebSocket -> website widget +``` ## Deployment ```text -widget/admin: https://widget.stackworks.ru/ -admin panel: https://widget.stackworks.ru/admin/ -backend API: https://api.stackworks.ru/health -backend local: http://127.0.0.1:3000 -PM2 process: wschat-backend -source: /opt/ws-chat/source -data: /opt/ws-chat/data -SQLite: /opt/ws-chat/data/chat.sqlite -webroot: /var/www/widget_stack_usr/data/www/widget.stackworks.ru +widget/test page: https://widget.stackworks.ru/ +admin panel: https://widget.stackworks.ru/admin/ +backend API: https://api.stackworks.ru/health +backend local: http://127.0.0.1:3000 +PM2 process: wschat-backend +source: /opt/ws-chat/source +data: /opt/ws-chat/data +SQLite: /opt/ws-chat/data/chat.sqlite +webroot: /var/www/widget_stack_usr/data/www/widget.stackworks.ru ``` MVP architecture is VPS-only. Raspberry Pi was removed from MVP because of Node.js/OS/DPKG instability. +## Working domains/sites + +Known working sites: + +```text +widget.stackworks.ru -> site_default +stackworks.ru -> site_stackworks +``` + +Known Telegram operator: + +```text +telegram_user_id: 833333156 +``` + +This operator is linked to both: + +```text +site_default +site_stackworks +``` + ## Database and settings Correct DB path: @@ -40,10 +68,13 @@ Correct DB path: Production fallback DB path was fixed to the same value even if `.env` is not loaded. -`/health` now includes `dbPath`; it must show: +`/health` must show: ```text -/opt/ws-chat/data/chat.sqlite +dbPath=/opt/ws-chat/data/chat.sqlite +telegram.running=true +telegram.hasBot=true +wsClients= ``` Settings come from SQLite table `settings`, not from frontend. @@ -60,24 +91,41 @@ telegram.proxy.username telegram.proxy.password ``` +Diagnostics: + +```bash +sqlite3 /opt/ws-chat/data/chat.sqlite "select id,name,domain,widget_key,is_active from sites;" +sqlite3 /opt/ws-chat/data/chat.sqlite "select id,name,telegram_user_id,is_active from operators;" +sqlite3 /opt/ws-chat/data/chat.sqlite "select * from site_operators;" +sqlite3 /opt/ws-chat/data/chat.sqlite "select key, value from settings where key like 'telegram.proxy.%';" +sqlite3 /opt/ws-chat/data/chat.sqlite "select key, length(value) as len from settings where key='telegram.bot_token';" +``` + +If a DB exists inside `/opt/ws-chat/source/backend/data/`, that was a wrong fallback DB from older code. + ## Current endpoints ```text -GET /health -GET /api/config/public -GET /api/admin/stats -GET /api/admin/messages?limit=50 -GET /api/admin/telegram/settings -POST /api/admin/telegram/settings -POST /api/admin/telegram/test-proxy -POST /api/admin/telegram/restart -POST /api/widget/message -GET /ws +GET /health +GET /api/config/public +GET /api/admin/stats +GET /api/admin/messages?limit=50 +GET /api/admin/sites +POST /api/admin/sites +GET /api/admin/operators +POST /api/admin/site-operators +DELETE /api/admin/site-operators +GET /api/admin/telegram/settings +POST /api/admin/telegram/settings +POST /api/admin/telegram/test-proxy +POST /api/admin/telegram/restart +POST /api/widget/message +GET /ws ``` ## WebSocket architecture -Widget now opens: +Widget opens: ```text /ws?siteId=&visitorId= @@ -109,6 +157,37 @@ Payload format: } ``` +Nginx for `api.stackworks.ru` must proxy `/ws` with upgrade headers. Includes file used: + +```text +/etc/nginx/fastpanel2-sites/api_stackwor_usr/api.stackworks.ru.includes +``` + +Required block: + +```nginx +location /ws { + proxy_pass http://127.0.0.1:3000/ws; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_read_timeout 86400; +} +``` + +Check: + +```bash +wscat -c "wss://api.stackworks.ru/ws?siteId=site_default&visitorId=test" +``` + +Expected: + +```json +{"type":"connected","siteId":"site_default","visitorId":"test"} +``` + ## Telegram bridge Runtime file: @@ -129,30 +208,25 @@ Implemented: - bridge startup from SQLite settings; - SOCKS5 proxy support; +- `socks5h://` proxy URL so DNS resolves through proxy; - `/start` registers operator; -- `/status` replies with bridge state; +- `/status` replies with bridge state and active dialog; - visitor messages go to active Telegram operators; +- Telegram reply to notification still works; +- inline button `Ответить в этот чат` selects active conversation; +- after selecting active conversation, normal Telegram messages are sent to that website visitor without reply; +- inline button `Сбросить активный диалог` clears selected dialog; - Telegram replies are saved as operator messages; - Telegram replies are delivered back to widget through WebSocket. -Important proxy fix: +Important: only one polling instance may run per Telegram bot token. -```text -Telegram bridge must use socks5h:// so DNS resolution goes through proxy. -``` - -## Current debugging status - -Current observed Telegram error: +Old observed Telegram error: ```text 409 Conflict: terminated by other getUpdates request; make sure that only one bot instance is running ``` -Meaning: Telegram token/proxy are usable, but two polling instances are running for the same bot token. - -Most likely cause: a manual `node src/server.js` process was started during debugging and is still alive while PM2 also runs `wschat-backend`. - Fix on VPS: ```bash @@ -166,22 +240,96 @@ sleep 5 curl -s http://127.0.0.1:3000/health | jq ``` -If 409 persists: +## Widget -```bash -ps aux | grep -E "node|server.js|wschat" | grep -v grep -``` - -Only one polling instance may run per Telegram bot token. - -## Important recent commits +Runtime file: ```text -4b46aabc8210158f97c3c6e9274b8f703f7a8aa2 - Telegram proxy changed to socks5h -6f14353cd403f351c89e4274d189808951dcf940 - production DB fallback fixed -be004fd14ab16e3b1262980fe044356d2efbe806 - health endpoint includes dbPath -ec2eeeea66047dcd982a4c0258d1338314e25c29 - WebSocket delivery from Telegram replies implemented -49f502f84c8e2d2979481d71010908457fb0ce84 - widget connected to WebSocket backend +widget/src/widget.js +``` + +Production build now emits stable: + +```text +https://widget.stackworks.ru/widget.js +``` + +Embed code example for `stackworks.ru`: + +```html + +``` + +Current widget implementation uses Shadow DOM for full style isolation. This was required because CSS on `stackworks.ru` broke the chat layout even with `!important` rules. + +Current widget features: + +- floating chat button; +- close button in header; +- mobile close works; +- header fixed height; +- form fixed at bottom; +- messages area fills remaining height; +- WebSocket auto-connect; +- automatic reconnect message; +- operator replies open chat automatically; +- Shadow DOM prevents host-site CSS from breaking input/button/form/layout. + +## Admin panel + +Admin URL: + +```text +https://widget.stackworks.ru/admin/ +``` + +Admin current features: + +- dashboard stats; +- recent messages; +- Telegram/SOCKS5 settings; +- test proxy; +- restart Telegram bridge; +- list sites; +- add site; +- show widget key; +- show/copy embed code; +- list Telegram operators; +- bind/unbind operators to sites. + +Admin UI was compacted, but visual polish is postponed. + +Important deployment caveat: admin lives inside the same webroot as widget. Running this command deletes `/admin/` and causes 404: + +```bash +rsync -av --delete /opt/ws-chat/source/widget/dist/ /var/www/widget_stack_usr/data/www/widget.stackworks.ru/ +``` + +Do not use `--delete` when deploying widget into the shared webroot unless `/admin` is excluded. + +Safe widget deploy: + +```bash +cp -a /opt/ws-chat/source/widget/dist/. /var/www/widget_stack_usr/data/www/widget.stackworks.ru/ +``` + +or: + +```bash +rsync -av /opt/ws-chat/source/widget/dist/ /var/www/widget_stack_usr/data/www/widget.stackworks.ru/ +``` + +Safe admin deploy: + +```bash +mkdir -p /var/www/widget_stack_usr/data/www/widget.stackworks.ru/admin +rsync -av --delete /opt/ws-chat/source/admin-panel/dist/ /var/www/widget_stack_usr/data/www/widget.stackworks.ru/admin/ +chown -R widget_stack_usr:widget_stack_usr /var/www/widget_stack_usr/data/www/widget.stackworks.ru ``` ## Deployment commands @@ -196,36 +344,74 @@ npm install pm2 restart wschat-backend --update-env ``` -Frontend build/publish: +Frontend build: ```bash cd /opt/ws-chat/source npm install npm run build -rsync -av --delete widget/dist/ /var/www/widget_stack_usr/data/www/widget.stackworks.ru/ -rsync -av --delete admin-panel/dist/ /var/www/widget_stack_usr/data/www/widget.stackworks.ru/admin/ +``` + +Safe frontend publish: + +```bash +cp -a /opt/ws-chat/source/widget/dist/. /var/www/widget_stack_usr/data/www/widget.stackworks.ru/ +mkdir -p /var/www/widget_stack_usr/data/www/widget.stackworks.ru/admin +rsync -av --delete /opt/ws-chat/source/admin-panel/dist/ /var/www/widget_stack_usr/data/www/widget.stackworks.ru/admin/ +chown -R widget_stack_usr:widget_stack_usr /var/www/widget_stack_usr/data/www/widget.stackworks.ru ``` Health/logs: ```bash curl -s http://127.0.0.1:3000/health | jq +curl -s https://api.stackworks.ru/health | jq pm2 logs wschat-backend --lines 100 ``` +Check public assets: + +```bash +curl -I https://widget.stackworks.ru/widget.js +curl -I https://widget.stackworks.ru/admin/ +``` + ## FastPanel safety rules Never edit global FastPanel/nginx configs from project scripts. Never bind project services directly to ports 80/443. Never run `rsync --delete` against `/var/www` or parent directories. Copy static files only into exact domain webroots. +`widget.stackworks.ru` currently uses Apache backend under FastPanel, but DocumentRoot is correct: + +```text +/var/www/widget_stack_usr/data/www/widget.stackworks.ru +``` + +## Important recent commits + +```text +4b46aabc8210158f97c3c6e9274b8f703f7a8aa2 - Telegram proxy changed to socks5h +6f14353cd403f351c89e4274d189808951dcf940 - production DB fallback fixed +be004fd14ab16e3b1262980fe044356d2efbe806 - health endpoint includes dbPath +ec2eeeea66047dcd982a4c0258d1338314e25c29 - WebSocket delivery from Telegram replies implemented +49f502f84c8e2d2979481d71010908457fb0ce84 - widget connected to WebSocket backend +258d5285382e9038167881fd6eccead02f3d6457 - stable Vite widget.js build +5ba7bd846b19bc2ea76c7610e16d2b00aa7b838a - admin API for sites/operators +5fb492ef7b6aa94374929d2a90caf71a4881c59d - Telegram inline answer button and active dialog mode +ccda416945d3608c5945e7adc3ce5502abf9a707 - compact admin CSS pass +bfdc9f091c1990a04b0c62593dfb94c4ec600f72 - widget moved to Shadow DOM +``` + ## Current next steps -1. Kill duplicate Telegram polling process. -2. Start exactly one PM2 process `wschat-backend`. -3. Confirm `/health`: correct `dbPath`, `telegram.running=true`, and `wsClients` counter. -4. Send `/start` to the Telegram bot. -5. Send widget message and confirm it reaches Telegram. -6. Reply in Telegram and confirm it instantly appears in widget. -7. Add admin bridge status/restart button. -8. Add admin operator list. -9. Add admin auth. -10. Fix production widget build to emit stable `widget.js`. +Highest priority: + +1. Add admin auth/login. Current admin is public and must not remain public. +2. Add web admin conversations page with selected conversation and full history. +3. Add conversation status: open/closed. +4. Add operator assignment / `take in work` flow. +5. Add offline mode: visitor name/phone/email + Telegram ticket. +6. Add sound notifications and unread counters. +7. Improve widget customization from admin: title, subtitle, color, position. +8. Later: split admin to separate subdomain, for example `admin.widget.stackworks.ru`, to avoid shared-webroot deploy collisions. +9. Later: replace SQLite with PostgreSQL for production scale. +10. Later: Docker compose / installer.