# เว็บคอนโซลและ Web Push

> HTML: https://remote.ivjn.us/docs/th/web-console/

คอนโซลของเบราว์เซอร์ให้บริการที่ `https://remote.ivjn.us/console/` รองรับการลงทะเบียนด้วยการยืนยันอีเมล บัญชี/รหัสผ่านหรือเข้าสู่ระบบโทเค็นของผู้ปฏิบัติงาน อุปกรณ์ที่กำหนดขอบเขตพื้นที่ทำงาน คำสั่งการสตรีม xterm PTY, ภาพหน้าจอ Android/การแตะ/การนำทาง/การป้อนข้อความ การวัด บันทึกการตรวจสอบ การสร้าง/การลบอุปกรณ์ และการแจ้งเตือนการเลือกเข้าร่วม บัญชีแบบอ่านอย่างเดียวไม่สามารถดำเนินการคำสั่งหรือดูหน้าจอ Android อุปกรณ์ออฟไลน์ยังคงมองเห็นได้

อุปกรณ์ที่โฆษณา `ubus` เปิดเผยแท็บ **ubus** พร้อมอ็อบเจ็กต์ วิธีการ และพารามิเตอร์ JSON-อ็อบเจ็กต์เสริม (ว่างเปล่าจะส่ง `null`) การโทรใช้เซสชันที่ปิดสนิทและต้องใช้อุปกรณ์ที่เปิดใช้งานออนไลน์และเข้าถึงการทำงานได้ ยกเลิกหยุดรอ; ไม่สามารถยกเลิกวิธีการที่ดำเนินการบนอุปกรณ์แล้วได้ สวิตช์อุปกรณ์และการสูญเสียสิทธิ์จะล้างการโทรและผลลัพธ์ที่ค้างอยู่

การลงทะเบียนจะสร้างพื้นที่ทำงานแยกต่างหาก อีเมลต้องได้รับการยืนยันโดยใช้รหัสแปดหลักก่อนเข้าสู่ระบบ คอนโซลรองรับการส่งรหัสซ้ำและกลับสู่การตรวจสอบหลังจากโหลดซ้ำ Exchange ต้องมีการกำหนดค่า SMTP (`SMTP_HOST`, `SMTP_PORT`, `SMTP_FROM` และการตั้งค่าการรับรองความถูกต้อง/ความปลอดภัยใน `.env.example`) หากไม่มีบริการอีเมล การลงทะเบียนจะรายงานข้อผิดพลาดแทนที่จะข้ามการยืนยัน

ข้อมูลประจำตัวจะอยู่ในหน่วยความจำหน้า การรีเฟรชจำเป็นต้องเข้าสู่ระบบอีกครั้ง การออกจากระบบจะปิดเซสชัน ความพยายามที่จะลบการสมัครสมาชิกแบบพุชของเบราว์เซอร์นี้ และเพิกถอนเซสชันการเข้าสู่ระบบด้วยรหัสผ่าน โทเค็นตัวดำเนินการหลักจะคงอายุการใช้งานที่มีอยู่ไว้ การคลิกการแจ้งเตือนจะเน้นที่คอนโซลแบบเปิดโดยไม่ต้องโหลดข้อมูลประจำตัวซ้ำ

## สร้างและให้บริการ

```sh
cd website/console
npm ci
npm test
npm run build
```

คัดลอก `dist/` ไปยังไดเรกทอรี `console/` ของเว็บไซต์ เก็บ `website/assets/` ที่มีอยู่ไว้ที่ `/assets/` สำหรับแบบอักษรที่ใช้ร่วมกันและเครื่องหมาย ให้บริการ `index.html`, `sw.js` และรายการที่มีการตรวจสอบความถูกต้องอีกครั้ง/ไม่มีแคช เนื้อหาที่ถูกแฮชอาจถูกแคชไว้อย่างไม่เปลี่ยนแปลง อย่าแคช `/api/*` หรือ `/ws/*`

คอนโซลใช้ `/api/*` และ `/ws/browser` ที่มีต้นกำเนิดเดียวกัน ย้อนกลับพร็อกซีเหล่านี้เป็น Exchange รักษาโฮสต์และอนุญาตให้อัปเกรด WebSocket อย่าตัดส่วนนำหน้า `/api` หรือ `/ws` พร็อกซีเซิร์ฟเวอร์การพัฒนา Vite API/WS ไปยัง `127.0.0.1:18082` Rust Exchange อนุญาตให้เข้าถึงเบราว์เซอร์ที่มีต้นกำเนิดเดียวกันตามค่าเริ่มต้น กำหนดค่าต้นทางที่แน่นอนด้วย `REMOTE_CORS_ORIGINS` เฉพาะ `*` ที่ชัดเจนเท่านั้นที่อนุญาตต้นกำเนิดทั้งหมด ต้นกำเนิดของผู้ปฏิบัติงานใช้ `CORS_ORIGINS` และค่าเริ่มต้นของตัวเอง ยังคงต้องมีการตรวจสอบสิทธิ์ผู้ถือและตั๋ว WebSocket แบบใช้ครั้งเดียว ไม่ได้เปิดใช้งานคุกกี้ข้ามต้นทาง

การตรวจสอบสิทธิ์เบราว์เซอร์ใช้ `POST /api/ws-ticket` พร้อมการให้สิทธิ์ Bearer ตามด้วย `GET /ws/browser` และเฟรม JSON แรก `{"ticket":"..."}` ตั๋วจะหมดอายุใน 30 วินาที เป็นแบบใช้ครั้งเดียว และตรวจสอบสถานะเปิดใช้งานของผู้ให้บริการและความถูกต้องของข้อมูลประจำตัวอีกครั้งเมื่อถูกใช้ ข้อมูลประจำตัวที่มีอายุการใช้งานยาวนานจะไม่ถูกใส่ใน URL ของ WebSocket ไคลเอ็นต์ดั้งเดิมยังคงใช้ `/ws/operator` ต่อไป

การทำงานของอุปกรณ์ทั้งหมดใช้เซสชัน WebSocket ที่ปิดผนึกแบบเนทีฟ HTTP ให้การรับรองความถูกต้อง ข้อมูลเมตาของอุปกรณ์ ไดเร็กทอรีคีย์และสิทธิ์ และการดูแลเซสชันรีเลย์ โปรดดูที่ [HTTP API สำหรับการดำเนินการ Agent](/docs/th/http-api.md)

## เปิดใช้งานการพุชเว็บ

ตั้งค่าตัวแปรทั้งสองในสภาพแวดล้อม Exchange (ขาดการพุชปิดการใช้งานทั้งสอง):

```text
WEB_PUSH_VAPID_PRIVATE_KEY=<base64url-unpadded-32-byte-P256-private-key>
WEB_PUSH_VAPID_SUBJECT=https://remote.ivjn.us
```

สร้างคีย์ส่วนตัวด้วย `python scripts/generate-vapid-key.py` เก็บไว้นอก Git ในไฟล์สภาพแวดล้อมที่จำกัด รักษาความเสถียรในการรีสตาร์ท และสำรองข้อมูลอย่างปลอดภัย รหัสสาธารณะจะได้รับมาโดยอัตโนมัติ การเปลี่ยนคีย์ทำให้เบราว์เซอร์ต้องสมัครใหม่อีกครั้ง

`WEB_PUSH_ALLOWED_ORIGINS` มีค่าเริ่มต้นเป็นต้นกำเนิดที่เชื่อถือได้ `https://fcm.googleapis.com,https://updates.push.services.mozilla.com,https://web.push.apple.com` เพิ่มผู้จำหน่ายเบราว์เซอร์รายอื่นหลังจากยืนยันต้นทางการพุช HTTPS แล้วเท่านั้น ปลายทางที่ไคลเอ็นต์ระบุไม่สามารถขยายรายการที่อนุญาตนี้ได้ การเปลี่ยนเส้นทางจะไม่ถูกติดตาม

เส้นทางที่ได้รับการรับรองความถูกต้อง:

| วิธีการและเส้นทาง | วัตถุประสงค์ |
| --- | --- |
| รับ `/api/webpush/config` | เปิดใช้งานสถานะและคีย์แอปพลิเคชันเซิร์ฟเวอร์สาธารณะ |
| รับ/โพสต์ `/api/webpush/subscriptions` | แสดงรายการข้อมูลเมตาที่เป็นเจ้าของหรือสมัครสมาชิก/เพิ่ม |
| ลบ `/api/webpush/subscriptions/{id}` | ลบการสมัครสมาชิกที่เป็นเจ้าของ |
| โพสต์ `/api/webpush/test` | จัดคิวการทดสอบที่มองเห็นได้สำหรับการสมัครรับข้อมูลของผู้โทร |
| โพสต์ `/api/webpush/messages` | ส่งให้ตัวเองหรือเป็นผู้ดูแลระบบไปยังบัญชีอื่นในพื้นที่ทำงานเดียวกัน |
| โพสต์ `/api/webpush/devices/{id}/messages` | แจ้งผู้ดูแลระบบที่สมัครสมาชิกซึ่งจัดการอุปกรณ์ที่ได้รับการตรวจสอบสิทธิ์ |

การสมัครสมาชิกใช้ `PushSubscription.toJSON()`, `application_server_key` และ `triggers` ของเบราว์เซอร์ `triggers` เก็บอ็อบเจ็กต์ `online` ได้มากที่สุดหนึ่งรายการและอ็อบเจ็กต์ `offline` หนึ่งรายการ แต่ละอ็อบเจ็กต์มี `kind`, `tick_count` (ค่าเริ่มต้น 3, ช่วง 1–100) และ `device_ids` เสริม `offline` แจ้งเตือนหลังจาก `tick_count` พลาดติดต่อกัน `online` แจ้งเตือนหลังจาก `tick_count` สำเร็จติดต่อกัน `device_ids` คือหนึ่งรหัสอุปกรณ์ (สตริง) รายการของรหัสอุปกรณ์ หรือละไว้/`null` สำหรับอุปกรณ์ทุกตัวในพื้นที่ทำงาน อาร์เรย์ `triggers` ว่างสมัครใช้งานเบราว์เซอร์โดยไม่มีการแจ้งเตือนสถานะอัตโนมัติ ไม่ยอมรับ `missed_count` และ `success_count` ข้อความใช้ตัวเลือก `operator_id` และ `notification` กับ `title`, `body`, ตัวเลือกที่มีต้นกำเนิดเดียวกันซึ่งเป็นทางเลือก `url` และ `tag` `/webpush/` เป็นไคลเอ็นต์อ้างอิงขั้นต่ำ คอนโซลการผลิตใช้ผู้ปฏิบัติงานของตัวเองซึ่งกำหนดขอบเขตเป็น `/console/`

Migration `0005_web_push.sql` เพิ่มการสมัครสมาชิกและกล่องขาออกที่คงทน `0008_online_offline_triggers.sql` ย้ายการแจ้งเตือนของอุปกรณ์ไปยัง `triggers` และวาง `missed_count` / `success_count` `202` หมายถึงอยู่ในคิว ไม่ได้ส่งหรือแสดง งานจะหมดอายุหลังจาก 15 นาที ข้อผิดพลาดของเครือข่าย 408, 429 และ 5xx ลองใหม่สูงสุดห้าครั้ง 404/410 ลบการแก้ไขการสมัครสมาชิกที่ตรงกัน สัญญาเช่า 90 วินาทีช่วยให้งานที่ค้างอยู่สามารถฟื้นตัวได้หลังจากที่พนักงานล้มเหลว การส่งมอบอย่างน้อยหนึ่งครั้งและการแสดงผลของเบราว์เซอร์ขึ้นอยู่กับการส่งมอบของผู้ขาย การอนุญาต และนโยบายระบบปฏิบัติการ เหตุการณ์ของอุปกรณ์ก่อนการเข้าคิวแบบคงทนคือความพยายามที่ดีที่สุดภายใต้การโอเวอร์โหลด เหตุการณ์เข้าคิวแล้ว รอดจากการรีสตาร์ท ขีดจำกัด: 20 การสมัครสมาชิก/บัญชี, 32 ข้อความในคิว/การสมัครสมาชิก และ 30 การเปลี่ยนแปลง/นาที/บัญชี

iOS/iPadOS อาจต้องเพิ่มคอนโซลไปที่หน้าจอหลักก่อนที่จะเปิดใช้งานการแจ้งเตือน ความสามารถในการเข้าถึงบริการ Browser Push แยกจากการเชื่อมต่อ Exchange

## การตรวจสอบ

รัน `cargo test --workspace`, `cargo clippy --workspace --all-targets -- -D warnings` และการทดสอบ/บิลด์ npm ของคอนโซล สำหรับการทดสอบการรวม PostgreSQL/HTTPS ให้ติดตั้ง `psycopg[binary]` ตั้งค่า `WEB_PUSH_TEST_ADMIN_URL` เป็นการทดสอบการเชื่อมต่อ PostgreSQL ด้วยสิทธิ์ CREATE DATABASE จากนั้นรัน `python scripts/test-webpush.py` สร้างและลบฐานข้อมูลแบบใช้แล้วทิ้ง อย่าชี้ไปที่บทบาทของฐานข้อมูลด้วยสิทธิ์ที่ไม่ได้ตั้งใจ

## ส่งจากลูกค้าทุกๆท่าน

เว็บ Console มีรูปแบบข้อความในการแจ้งเตือนของ Browser การตั้งค่า Console บนมือถือมีการควบคุมการส่ง/ทดสอบ Web Push และลิงก์ Open Web Console การตั้งค่าท้องถิ่นของเดสก์ท็อป Agent, การตั้งค่า Android Agent และหน้าการกำหนดค่าส่วนขยายเบราว์เซอร์สามารถส่งโดยใช้ข้อมูลประจำตัวของอุปกรณ์ได้ เอเจนต์ Headless/OpenWrt ใช้คำสั่งด้านล่าง รับการแจ้งเตือนโดยเปิดใช้งานในเว็บ Console/PWA สิ่งนี้ไม่ได้เพิ่มตัวรับ FCM/APN ดั้งเดิม

```sh
remote-cli webpush status
remote-cli webpush subscriptions
remote-cli webpush test
remote-cli webpush send --title "Backup ready" --body "Completed"
remote-cli webpush send --operator-id ACCOUNT_UUID --title "Notice" --body "Hello"
remote-agent --config /path/to/config.json webpush --title "Device ready" --body "Completed"
```

```json
{"notification":{"title":"Backup ready","body":"Completed","url":"/console/","device_id":"edge-1"}}
```

POST `/api/webpush/messages` ใช้การรับรองความถูกต้องของผู้ถือโอเปอเรเตอร์ ละเว้น `operator_id` เพื่อส่งถึงตัวคุณเอง ผู้รับรายอื่นต้องได้รับอนุญาตจากผู้ดูแลระบบและต้องอยู่ในพื้นที่ทำงานเดียวกัน เนื้อความ JSON ด้านบนยังใช้ได้กับ `remote-cli webpush send --json-file message.json` อีกด้วย ทริกเกอร์การแสดงตนที่ว่างเปล่าจะปิดการแจ้งเตือนออนไลน์/ออฟไลน์อัตโนมัติ ในขณะที่ยังคงการส่งข้อความทั่วไป

POST `/api/webpush/devices/{id}/messages` ใช้โทเค็น Bearer ของอุปกรณ์นั้นและยอมรับออบเจ็กต์การแจ้งเตือนโดยตรง (`title`, `body`, ตัวเลือก `tag`) เฉพาะการสมัครสมาชิกที่ใช้งานอยู่ของผู้ดูแลระบบที่เปิดใช้งานซึ่งจัดการอุปกรณ์นั้นเท่านั้นที่จะได้รับ เซิร์ฟเวอร์ตั้งค่า `device_id`, `event=device.message`, `occurred_at` และ `/console/`; ผู้โทรไม่สามารถเลือกบัญชีหรือปลอมแปลงเป็นอุปกรณ์อื่นได้ อุปกรณ์ส่งแชร์คำขอ 30 รายการ/นาที/งบประมาณพื้นที่ทำงาน และการสมัครรับข้อมูลสูงสุด 256 รายการต่อการส่ง การสมัครสมาชิกบัญชีและการจำกัดคิวยังคงมีอยู่ ไม่มีผู้รับที่แอ็คทีฟส่งคืน `202 {"queued":0}`; `queued` นับการสมัครสมาชิก ไม่ใช่การส่งมอบ
