diff --git a/client/app.go b/client/app.go index a999d5c..eb0443d 100644 --- a/client/app.go +++ b/client/app.go @@ -113,6 +113,7 @@ type App struct { monitoringTornDown bool chatUnread int replyStaffID uint + fetchAppsMu sync.Mutex } type LocalStats struct { @@ -299,6 +300,7 @@ func (a *App) startLocalServer() { go a.refreshStudentData() // Chuyển hướng WebView về trang dashboard của app bằng cách reload app assets + guard.SuppressFor(5 * time.Second) runtime.WindowReloadApp(a.ctx) w.Write([]byte("ok")) } else { @@ -330,7 +332,10 @@ func (a *App) startLocalServer() { w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS") return } - go runtime.WindowReloadApp(a.ctx) + go func() { + guard.SuppressFor(5 * time.Second) + runtime.WindowReloadApp(a.ctx) + }() w.Write([]byte("ok")) }) @@ -495,6 +500,7 @@ func (a *App) NavigateToLogin() { a.mu.Lock() a.expectingLogin = true a.mu.Unlock() + guard.SuppressFor(5 * time.Second) runtime.WindowExecJS(a.ctx, "window.location.href = 'https://portal.rikkei.edu.vn/dangnhap'") } @@ -519,6 +525,7 @@ func (a *App) Logout() { a.disconnectWS() a.stopScreenshotStream() + guard.SuppressFor(5 * time.Second) runtime.WindowReloadApp(a.ctx) } @@ -912,6 +919,9 @@ func (a *App) rejectUnauthorizedWifi(ssid, bssid string) { } func (a *App) fetchAllowedApps(classId int64) { + a.fetchAppsMu.Lock() + defer a.fetchAppsMu.Unlock() + a.mu.Lock() studentID := int64(0) if a.student != nil { @@ -935,6 +945,12 @@ func (a *App) fetchAllowedApps(classId int64) { } if err := json.NewDecoder(resp.Body).Decode(&res); err == nil { if res.Exit { + a.mu.Lock() + stillExam := a.dashboard.MonitorMode == "exam" + a.mu.Unlock() + if stillExam { + return + } reason := res.Reason if reason == "" { reason = "Không đáp ứng điều kiện học tập (chưa cấu hình ứng dụng được phép hoặc ngoài giờ học)." @@ -984,6 +1000,7 @@ func (a *App) fetchStudentStatus(studentID int64) { } a.mu.Lock() + prevMode := a.dashboard.MonitorMode a.dashboard = res if res.MonitorLabel != "" { a.statusMsg = res.MonitorLabel @@ -998,6 +1015,10 @@ func (a *App) fetchStudentStatus(studentID int64) { a.statusMsg = res.MonitorLabel } a.mu.Unlock() + + if prevMode != res.MonitorMode { + guard.SuppressFor(4 * time.Second) + } } func (a *App) connectWS() { @@ -1292,6 +1313,7 @@ func (a *App) examStudentContext() (int64, *StudentExamSnapshot, error) { // ReturnToDashboard quay lại giao diện chính của app (giữ cookie WebView2). func (a *App) ReturnToDashboard() { + guard.SuppressFor(5 * time.Second) runtime.WindowReloadApp(a.ctx) } @@ -1315,10 +1337,12 @@ func (a *App) openExamWebView(targetURL, title string) error { url.QueryEscape(targetURL), url.QueryEscape(title), ) + guard.SuppressFor(5 * time.Second) runtime.WindowExecJS(a.ctx, fmt.Sprintf("window.location.href = %q", wrapper)) return nil } // Trang thi bên ngoài: mở trực tiếp trong WebView (first-party cookies → giữ phiên đăng nhập). + guard.SuppressFor(5 * time.Second) runtime.WindowExecJS(a.ctx, fmt.Sprintf("window.location.href = %q", targetURL)) return nil } @@ -1481,6 +1505,7 @@ func uniqueFilePath(path string) string { } func openFolderInExplorer(filePath string) { + guard.SuppressFor(5 * time.Second) dir := filepath.Dir(filePath) _ = exec.Command("explorer", dir).Start() } diff --git a/client/internal/blocker/blocker.go b/client/internal/blocker/blocker.go index f58aa67..6089bf7 100644 --- a/client/internal/blocker/blocker.go +++ b/client/internal/blocker/blocker.go @@ -249,6 +249,7 @@ var systemAllowed = map[string]bool{ "powershell.exe": true, "conhost.exe": true, "client.exe": true, // App Wails của ta + "simple_care_v1.0.exe": true, "wails.exe": true, "msedgewebview2.exe": true, // WebView2 runtime của Wails "code.exe": true, // VSCode diff --git a/client/internal/guard/guard_other.go b/client/internal/guard/guard_other.go index 80c5eb2..fecc602 100644 --- a/client/internal/guard/guard_other.go +++ b/client/internal/guard/guard_other.go @@ -2,5 +2,8 @@ package guard +import "time" + func Start(onViolation func(reason string)) {} func Stop() {} +func SuppressFor(_ time.Duration) {} diff --git a/client/internal/guard/guard_windows.go b/client/internal/guard/guard_windows.go index 7593abe..d205b08 100644 --- a/client/internal/guard/guard_windows.go +++ b/client/internal/guard/guard_windows.go @@ -72,15 +72,36 @@ type msg struct { } var ( - guardOnce sync.Once - guardViolation func(string) - guardStop chan struct{} - guardBaselineUser string - guardBaselineSession uint32 - desktopHook uintptr - guardClassAtom uint16 + guardOnce sync.Once + guardViolation func(string) + guardStop chan struct{} + guardBaselineUser string + guardBaselineSession uint32 + desktopHook uintptr + guardClassAtom uint16 + suppressMu sync.Mutex + suppressViolationsUntil time.Time ) +// SuppressFor tạm không thoát app khi WebView/Explorer chuyển màn hình nội bộ. +func SuppressFor(d time.Duration) { + if d <= 0 { + return + } + suppressMu.Lock() + next := time.Now().Add(d) + if next.After(suppressViolationsUntil) { + suppressViolationsUntil = next + } + suppressMu.Unlock() +} + +func violationsSuppressed() bool { + suppressMu.Lock() + defer suppressMu.Unlock() + return time.Now().Before(suppressViolationsUntil) +} + // Start giám sát môi trường Windows — vi phạm thì gọi onViolation (đổi user, đa màn hình, đổi desktop ảo). func Start(onViolation func(reason string)) { guardOnce.Do(func() { @@ -122,7 +143,7 @@ func pollLoop() { } func checkEnvironment() { - if guardViolation == nil { + if guardViolation == nil || violationsSuppressed() { return } if n := monitorCount(); n > 1 { @@ -141,6 +162,10 @@ func checkEnvironment() { } func triggerViolation(reason string) { + if violationsSuppressed() { + log.Printf("[GUARD] suppressed: %s", reason) + return + } if guardViolation != nil { guardViolation(reason) } diff --git a/server/uploads/exams/5/papers/5/main.pdf b/server/uploads/exams/5/papers/5/main.pdf new file mode 100644 index 0000000..7a9dded Binary files /dev/null and b/server/uploads/exams/5/papers/5/main.pdf differ diff --git a/server/uploads/exams/5/papers/5/resources/Gemini_Generated_Image_7259rr7259rr7259.png b/server/uploads/exams/5/papers/5/resources/Gemini_Generated_Image_7259rr7259rr7259.png new file mode 100644 index 0000000..3554e85 Binary files /dev/null and b/server/uploads/exams/5/papers/5/resources/Gemini_Generated_Image_7259rr7259rr7259.png differ diff --git a/server/uploads/exams/5/papers/5/resources/horse_icon.png b/server/uploads/exams/5/papers/5/resources/horse_icon.png new file mode 100644 index 0000000..d8ee5a8 Binary files /dev/null and b/server/uploads/exams/5/papers/5/resources/horse_icon.png differ diff --git a/server/uploads/exams/5/papers/5/resources/main (1).pdf b/server/uploads/exams/5/papers/5/resources/main (1).pdf new file mode 100644 index 0000000..cffc71b Binary files /dev/null and b/server/uploads/exams/5/papers/5/resources/main (1).pdf differ