#systems#workmonitor#webrtc#sse#telemetry#threejs
ตัวแทนบนเครื่องพนักงาน สัญญาณ WebRTC และจอผู้ดู
บันทึกนี้อธิบายกลไกภายในของ 3D จอพนักงาน ภาพรวม (SYS-14 WorkMonitor) ว่าหน้าจอพนักงานไปถึงผู้ดูอย่างไร ทั้งสองฝั่งคุยกันผ่านเซิร์ฟเวอร์ Next เท่านั้น (SSE + POST) เซิร์ฟเวอร์ทำหน้าที่ "ไปรษณีย์" ส่งต่อสัญญาณ WebRTC (offer / answer / ICE) ภายในหน่วยความจำ ส่วนภาพจริงวิ่งตรงจากเครื่องพนักงานไปยังเครื่องผู้ดู
1) ตัวแทนบนเครื่องพนักงาน (WorkstationTelemetryAgent)
- ติดตั้ง: วางไว้ในทุกหน้า workspace ผ่าน
PersistentWorkspaceHostและไม่ทำงานเมื่อเป็น iframe กระจก (workmonitor_mirror=1หรือหน้าต่างอยู่ใน frame อื่น) หรือเมื่ออยู่หน้า/workmonitor,/fraudwarningผลลัพธ์ของคอมโพเนนต์เป็นnullไม่มี UI - รหัสโต๊ะ
deskId=live-<รหัสผู้ใช้ตัวเล็กเฉพาะตัวอักษรและตัวเลข>-<4 ตัวสุ่ม>เก็บในsessionStorageคีย์bc_workmonitor_tab_desk_id_v2ดังนั้น 1 แท็บเบราว์เซอร์ = 1 โต๊ะ ผู้ใช้เปิดหลายแท็บจะเห็นหลายโต๊ะ - ลงทะเบียน: เปิด SSE
/api/workmonitor/stream?role=agentพร้อมพารามิเตอร์deskId,empCode,empName,machineLabel,desktopResolution,routePath,moduleId,menuCode,screenTitleฝั่งเซิร์ฟเวอร์สร้างโต๊ะทันทีและแจ้งผู้ดูด้วยdesk_update - ชื่อหน้าจอ: แปลง
routePathเป็นรหัสเมนู + ชื่อไทยด้วยตาราง 12 เส้นทาง (/home,/pos,/dashboard,/crm,/market,/procure,/wms,/tms,/etax,/ai,/monitormap,/lineoa) และอ่านแท็บที่กำลัง active ใน workspace ของหน้า/home(ดู พื้นที่ทำงานแบบแท็บ MDI และสิทธิ์จอ (TabWorkspace)) - ตัวชี้วัด: ตำแหน่งเมาส์เป็นสัดส่วน 0-1,
kpmRate= จำนวนการกดแป้นภายใน 60 วินาทีล่าสุด, ช่องที่กำลังพิมพ์ (ชื่อช่อง + ค่า), สถานะTYPINGถ้ามีการกดแป้นไม่เช่นนั้นONLINE, และธง "สลับไปดูเว็บอื่น" เมื่อแท็บถูกซ่อนหรือหน้าต่างเสียโฟกัส (ชื่อหน้าจอถูกขึ้นต้นด้วยข้อความเตือน) - ภาพรวม DOM (
captureCurrentScreenDomSnapshot): หัวเรื่องไม่เกิน 80 ตัวอักษร, ช่องกรอกไม่เกิน 10 ช่อง (ชื่อช่อง ≤32, ค่า ≤48 ตัวอักษร ไม่รวมhiddenและpassword), ตารางแรกที่เจอ (หัวคอลัมน์ ≤5 คอลัมน์, แถวที่มองเห็น ≤6 แถว, ข้อความในเซลล์ ≤24 ตัวอักษร), รายการแท็บที่เปิด, ตำแหน่ง scroll และliveHtml liveHtml: ก๊อปปี้ HTML ของส่วนหัว + โมดูลที่เปิดอยู่ + dialog ที่ลอยบนbodyโดยตัดแท็บที่ซ่อนและแท็กscriptออก และคัดลอกค่าที่ผู้ใช้กรอก (input / textarea / select) ลงในแอตทริบิวต์ของสำเนา ยกเว้นinputชนิดpassword- รหัสผ่าน: ข้ามชนิด
passwordทุกจุด (ภาพรวมช่องกรอก, สำเนา HTML, ค่าตั้งต้นตอนโฟกัส) และฟังก์ชันอ่านค่าช่องคืน[REDACTED]ให้ชนิดนี้ - จังหวะส่ง: ตัวจับเวลา 2 วินาทีเรียก
telemetry_pingเมื่อมีคนดู;MutationObserverตัวหน่วง 160 มิลลิวินาทีส่งทันทีเมื่อ DOM เปลี่ยน; และส่งซ้ำเมื่อสลับแท็บ, เลื่อนหน้าจอ, คลิก, พิมพ์ ข้อมูลหน้าจอdomSnapshotถูกแนบเฉพาะตอนมีคนดู (หรือถูกบังคับ) - ปิดแท็บ:
pagehide/beforeunloadส่งdisconnectด้วยkeepalive - บันทึกพฤติกรรมไป FraudWarning: ตัวแทนตัวเดียวกันส่ง
POST /api/fraudwarning(record_activity) ทุกครั้งที่เปิดจอ แก้ช่อง บันทึก ยกเลิก ลบ หรือทิ้งจอที่แก้ค้าง ไม่ขึ้นกับว่ามีผู้ดูWorkMonitorหรือไม่ (ค่าเก่า/ค่าใหม่ของช่องที่แก้เก็บยาวไม่เกิน 64 ตัวอักษร และข้ามช่องที่ชื่อเป็นการค้นหา/กรอง) รายละเอียดที่ 3D Warning ตรวจจับความผิดปกติและทุจริต ภาพรวม (SYS-15 FraudWarning)
2) สัญญาณและท่อ WebRTC
- STUN: ตั้งไว้ 2 ตัวในค่าคงที่
RTC_ICE_SERVERSทั้งฝั่งตัวแทนและผู้ดู ไม่มีการตั้ง TURN จึงไม่มีตัวส่งต่อเมื่อเครือข่ายสองฝั่งต่อกันตรงไม่ได้ - ลำดับ: ผู้ดูส่ง
viewer-request→ ตัวแทนสร้างRTCPeerConnectionต่อviewerIdทำ offer → ผู้ดูตอบ answer → ทั้งคู่แลก ICE ผู้ดูหลายคนได้พร้อมกัน (ตัวแทนถือหนึ่ง connection ต่อผู้ดู) - เซิร์ฟเวอร์ส่งต่อสัญญาณ:
POST /api/workmonitoractionwebrtc_signal→relayWebRtcSignalผู้ดูส่งหาตัวแทนตามdeskId, ตัวแทนส่งหาผู้ดูตามviewerIdส่งถึงจะตอบdelivered: trueเซิร์ฟเวอร์นับจำนวนสัญญาณไว้ในtotalSignalsRelayed - สิ่งที่ส่งในท่อ: (ก) วิดีโอจาก canvas ขนาด 1280×720 ที่
captureStream(6)(6 เฟรมต่อวินาที) ซึ่งrenderLiveEmployeeScreenCanvasวาดขึ้นใหม่จากข้อมูลสถานะ (หน้าต่างจำลอง แถบที่อยู่ ตาราง/ช่องกรอก เมาส์ แถบงาน) และ (ข) DataChannel ชื่อbc-screen(ordered: false,maxRetransmits: 0) ส่งscreen_meta= ข้อมูลโต๊ะ + ภาพรวม DOM ทุก 350 มิลลิวินาที โดยตัดliveHtmlถ้ายาวเกิน 180000 ตัวอักษร - ไม่ใช่การแชร์หน้าจอของเบราว์เซอร์: ไฟล์ใน
frontend/workmonitor/ไม่เรียกgetDisplayMediaภาพที่ผู้ดูเห็นคือภาพที่วาดซ้ำจากสถานะกับสำเนา HTML จึงไม่เห็นหน้าต่างอื่นนอกแอป ธงentireScreenSharedฝั่งผู้ดูมีให้อ่านแต่ตัวแทนไม่ได้ตั้งค่านี้ - ปิดท่อ: เมื่อสถานะ "ไม่มีผู้ดู" หรือได้รับ
viewer-leaveตัวแทนปิด peer; ถ้า peer สุดท้ายปิด จะหยุดตัวจับเวลา 350 มิลลิวินาทีและปิดแทร็กวิดีโอ เซิร์ฟเวอร์ส่งviewer-leaveให้ตัวแทนทุกตัวเมื่อผู้ดูปิดสวิตช์หรือหลุดจาก SSE
3) จอผู้ดู (EmployeeWorkMonitor3DScreen)
viewerIdสร้างสุ่มใหม่ทุกครั้งที่เปิดจอ (viewer-<6 ตัว>) และสวิตช์ดูสดmonitorPowerOnเริ่มเป็น "เปิด" เมื่อเข้าหน้า/workmonitorจึงเริ่มดูทันที ปุ่ม "เปิดดูหน้าจอสดอยู่ (กดเพื่อปิด)" ส่งset_monitor_power- SSE ฝั่งผู้ดู รับ
init_snapshot,watch_state,desk_update,desk_removed,layout_update,snapshot_refresh,webrtc_signalเมื่อพบโต๊ะใหม่ผู้ดูส่งviewer-requestให้ทันที - ฉาก 3 มิติ: Three.js วางโต๊ะตามตำแหน่ง
posX/posZ/rotationYภาพวิดีโอที่รับมาแปะเป็นพื้นผิวบนจอคอมพิวเตอร์ของโต๊ะนั้น (THREE.VideoTexture) ป้ายบนโต๊ะแสดงชื่อและหน้าจอที่เปิด กล้องหมุน/ซูมด้วยเมาส์ ลากโต๊ะเพื่อย้าย โดยเซิร์ฟเวอร์จำกัดตำแหน่ง X อยู่ใน ±24 และ Z ใน ±18 (move_desk) พื้นโซนในฉากมี 3 โซน (ออนไลน์สด, ฝ่ายบริหารและบัญชี, ฝ่ายขายหน้าร้านและออนไลน์) - จัดผัง: ปุ่ม "จัดผังแยกโซน" (
DEPARTMENT_ZONES) วางตามช่องมาตรฐาน 10 ช่องแรกแล้วเป็นตาราง 4 คอลัมน์, ปุ่ม "จัดผังวงกลม" (COMMAND_RING) วางเป็นวงรี และปุ่ม "มุมมองทั้งห้อง 3D" รีเซ็ตกล้อง ชนิดผังCLASSROOM_GRIDมีในชนิดข้อมูลแต่ไม่มีปุ่มและไม่มีสูตรของตัวเอง (ได้ผลเหมือนผังมาตรฐาน) - กรอบดูหน้าจอ (Inspector): เลือกโต๊ะแล้วแสดงชื่อพนักงาน ชื่อหน้าจอ และสำเนาหน้าจอใน
iframeแบบดูอย่างเดียว (ปิดการคลิกpointer-events-none) ถ้ามีliveHtmlที่มีตัวระบุโมดูลdata-persistent-moduleจะเขียนลงiframeพร้อมคัดลอกสไตล์ของหน้าหลัก แล้วจัดตำแหน่ง scroll ให้ตรงกับเครื่องพนักงาน ถ้าไม่มีจะเปิดเส้นทางหน้าเดียวกันในiframeด้วย?workmonitor_mirror=1(ตัวแทนในหน้านั้นหยุดทำงาน) มีเคอร์เซอร์เมาส์จำลองวาดทับตามสัดส่วน ซูม 100%-400% ลากเลื่อน เต็มจอ และปุ่มดูความสูงเต็ม/เลื่อนไปบนสุด/ล่างสุด - โหมดสลับจอ: "ผู้ควบคุมกดดูเอง (ล็อกจอ)" (
MANUALค่าเริ่มต้น) คงโต๊ะที่เลือก ส่วน "เปลี่ยนจออัตโนมัติตามการใช้งาน" (AUTO) จะเลือกโต๊ะที่เพิ่งสลับแท็บ/เลื่อนจอ/กำลังพิมพ์ให้เอง แต่ไม่ถี่กว่า 4 วินาทีและไม่ทับการเลือกด้วยมือภายใน 4 วินาที - แถบล่าง (
workmonitor-bottom-dock) แสดงรายการจอที่ออนไลน์ เลือกเพื่อดู และปุ่มขยับโต๊ะที่เลือก ตัวอย่างdata-testid:toggle-webrtc-monitor-btn,switch-mode-manual-btn,switch-mode-auto-btn,preset-layout-zones-btn,preset-layout-ring-btn,inspector-live-screen-iframe,webrtc-zoom-viewport,desk-card-<deskId>
ข้อควรระวัง
- รหัสผ่านถูกกันไว้ แต่ข้อความอื่นในจอ (ค่าในช่อง ตัวเลขยอดเงิน เซลล์ตาราง) ถูกคัดลอกไปทั้งในภาพรวมและใน
liveHtmlผู้ดูจึงเห็นข้อมูลธุรกิจจริงของแท็บนั้น ควรกำหนดผู้มีสิทธิ์เปิดจอนี้ ดู เช็กลิสต์ความปลอดภัย API: ตัวตน สิทธิ์ และช่องโหว่เว็บ (Secure API Checklist) - ความถี่ 6 เฟรมต่อวินาที และ 350 มิลลิวินาทีเป็นค่าที่เขียนไว้ในโค้ด ไม่ได้ปรับตามคุณภาพเครือข่าย
- สถานะอยู่ในหน่วยความจำของ process เดียว ผู้ดูและพนักงานต้องต่อเข้า process เดียวกัน ดู 3D จอพนักงาน ภาพรวม (SYS-14 WorkMonitor)
ที่มาในโค้ด
- ตัวแทน
frontend/workmonitor/WorkstationTelemetryAgent.tsx::19-54STUN, ตารางเส้นทาง, รหัสโต๊ะ;:84-123คัดลอกค่าฟอร์ม (ข้าม password);:147-389ภาพรวม DOM และliveHtml;:489-509เงื่อนไข iframe กระจก;:548-622ตัวชี้วัด;:624-681วาดเฟรม,captureStream(6), DataChannel;:740-822ทำ offer;:830-882telemetry_ping;:922-996ส่งrecord_activity;:1119-1203SSE และ[REDACTED];:1560-1675observer, ตัวจับเวลา 2 วินาที,return null - เซิร์ฟเวอร์:
frontend/workmonitor/inMemoryHub.ts:90-195สถานะดู ส่งต่อสัญญาณ สวิตช์ดู;frontend/src/app/api/workmonitor/route.ts:96-166สัญญาณ ย้ายโต๊ะ จัดผัง ตัดการเชื่อมต่อ;frontend/src/app/api/workmonitor/stream/route.ts:18-176SSE สองบทบาท - ผู้ดู
frontend/workmonitor/EmployeeWorkMonitor3DScreen.tsx::62-96รหัสผู้ดูและโหมด;:262-310เขียนสำเนา HTML ลง iframe;:448-598รับ offer ตอบ answer;:601-778SSE;:780-830สวิตช์และจัดผัง;:1264-1265ขีดจำกัดซูม;:1294-1412แถบควบคุม;:1698-1717iframe สองแบบ - ผัง
frontend/workmonitor/workmonitorData.ts:151-200ช่องวางโต๊ะและผังวงกลม - ดูเพิ่ม: 3D จอพนักงาน ภาพรวม (SYS-14 WorkMonitor) · 3D Warning ตรวจจับความผิดปกติและทุจริต ภาพรวม (SYS-15 FraudWarning) · API ตรวจสุขภาพ แผนที่ติดตาม และเส้นทางเฉพาะชั้น Next (Health, Monitormap, Next-only routes)