Báo Cáo Giải Trình Kiến Trúc: Tính Năng Web Push Notification Trên Màn Hình Khóa (Lock Screen)

📋 Báo Cáo Giải Trình Kiến Trúc: Tính Năng Web Push Notification Trên Màn Hình Khóa (Lock Screen)


I. Mục Đích & Ý Nghĩa Nghiệp Vụ

Tính năng Web Push Notification Lock Screen trong VinhPhatERP cho phép:

  • Thông báo tức thì khi đóng ứng dụng: Người dùng (Khách hàng, Nhân viên, Tài xế, Quản lý) nhận được thông báo nổi trên Màn hình khóa (Lock Screen) kèm chuông/rung ngay cả khi đã tắt trình duyệt hoặc khóa điện thoại.
  • Đồng bộ 2 chiều thời gian thực:
    • Admin / Nhân viên ➔ Khách hàng: Khách hàng nhận thông báo khi có tin nhắn hỗ trợ, cập nhật đơn hàng, giao hàng.
    • Khách hàng ➔ Admin / Nhân viên: Admin và nhân viên trực nhận thông báo trên điện thoại/máy tính khi khách nhắn tin hoặc tạo đơn mới.
  • Huy hiệu số đếm (App Badging): Hiển thị số chấm đỏ trên icon ứng dụng ngoài màn hình chính (HomeScreen) tương tự app Native.
  • Điều hướng 1-chạm (Deep Link): Chạm vào thông báo trên màn hình khóa sẽ tự động mở đúng hộp thoại chat mà không bị lỗi trang.

II. Sơ Đồ Toàn Bộ Pipeline Vận Hành 5 Tầng (End-to-End Architecture)

 [ 1. ĐĂNG KÝ THIẾT BỊ ]
  Trình duyệt / iOS Safari PWA xin quyền Notification -> Lưu PushSubscription vào bảng `push_subscriptions`
                                    │
                                    ▼
 [ 2. SỰ KIỆN DATABASE ]
  Có tin nhắn mới chèn vào `chat_messages` -> PostgreSQL Trigger kích hoạt `pg_net.http_post()`
                                    │
                                    ▼
 [ 3. ĐIỀU PHỐI CLOUD EDGE FUNCTION ]
  Edge Function `send-web-push` lấy token người nhận -> Ký số VAPID -> Đẩy sang Apple APNs / Google FCM
                                    │
                                    ▼
 [ 4. DỊCH VỤ HỆ ĐIỀU HÀNH & SERVICE WORKER ]
  Hệ điều hành đánh thức `sw.js` ngầm -> Gọi `showNotification` với tag timestamp duy nhất
                                    │
                                    ▼
 [ 5. MÀN HÌNH KHÓA & DEEP LINK ]
  Màn hình iPhone/Android sáng lên + Kêu chuông/rung -> User chạm vào -> Tự động mở đúng Hộp chat

III. Chi Tiết Kỹ Thuật Từng Tầng

1. Tầng 1: Đăng ký & Đồng bộ Thiết bị (Client Subscription)

Tệp nguồn: usePushSubscription.ts, AppShell.tsx, CustomerPortalLayout.tsx.

  • Sử dụng cặp khóa mã hóa chuẩn công nghiệp VAPID (VAPID_PUBLIC_KEY & VAPID_PRIVATE_KEY).
  • Khi người dùng đăng nhập và cấp quyền, PushManager tạo ra một bộ định danh bảo mật duy nhất (endpoint, p256dh, auth).
  • Dữ liệu này được lưu vào bảng push_subscriptions (kèm thông tin thiết bị: iOS Safari PWA, Chrome, Edge, Windows, v.v.).
  • Tự động đồng bộ (Auto Re-sync): Khi user mở lại app trên bất kỳ máy nào đã cấp quyền, hệ thống tự động kiểm tra và làm mới token trong DB.

2. Tầng 2: Kích hoạt Tự động từ Database (PostgreSQL Event Trigger)

Tệp nguồn: 20260828000003_chat_push_trigger.sql.

  • Tạo Trigger trg_chat_message_inserted chạy AFTER INSERT trên bảng chat_messages.
  • Tự động tăng unread_count cho những người cùng phòng chat (trừ người gửi).
  • Sử dụng extension pg_net của PostgreSQL để gọi bất đồng bộ (net.http_post) sang Cloud Edge Function với payload dạng jsonb chứa message_id.
  • Đảm bảo an toàn (Fail-safe): Lệnh gọi mạng nằm trong khối EXCEPTION WHEN OTHERS để đảm bảo ngay cả khi mạng chập chờn thì tin nhắn gốc vẫn luôn được lưu thành công vào cơ sở dữ liệu.

3. Tầng 3: Điều phối Push bảo mật trên Cloud (Edge Function send-web-push)

Tệp nguồn: send-web-push/index.ts.

  • Nhận message_id từ Database Trigger.
  • Query bảng chat_room_participants để lấy tất cả người nhận trong cuộc trò chuyện (ngoại trừ người vừa gửi).
  • Lấy tất cả token thiết bị còn hoạt động trong bảng push_subscriptions.
  • Truy vấn tên người gửi từ bảng profiles.
  • Ký số thông điệp bằng chuẩn WebPush VAPID (urgency: 'high', TTL: 86400).
  • Phân phát thông báo song song đến máy chủ Apple APNs (dành cho iOS/macOS) và Google FCM (dành cho Android/Chrome/Windows).
  • Tự động dọn dẹp thiết bị rác (Auto 410 Cleanup): Nếu Apple/Google trả về mã 410 Gone hoặc 404 Not Found (người dùng đã xóa PWA hoặc gỡ trình duyệt), hệ thống tự động cập nhật revoked_at để không gửi lại vào token hỏng.

4. Tầng 4: Service Worker & Đánh thức Màn hình khóa (Lock Screen Wake-up)

Tệp nguồn: sw.js.

  • Khi thiết bị ở trạng thái màn hình khóa / ứng dụng đóng, Service Worker nhận sự kiện push.
  • Kiểm tra trạng thái Foreground: Nếu người dùng đang mở đúng phòng chat đó trên màn hình, Service Worker sẽ bỏ qua thông báo đẩy để không gây phiền nhiễu.
  • Khắc phục lỗi Silent Collapse trên iOS:
    • Quy định của Apple: Nếu các thông báo có cùng một tag tĩnh (ví dụ: tag: 'chat-room-1'), iOS sẽ âm thầm gộp tin nhắn vào trung tâm thông báo mà không bật sáng màn hình hay phát chuông.
    • Giải pháp: Đổi tag sang định dạng kèm dấu thời gian: chat-${roomId}-${Date.now()}. Nhờ đó, mỗi tin nhắn gửi đến đều buộc iOS APNs phải sáng màn hình khóa và phát chuông báo.
  • Cập nhật số đếm huy hiệu: Gọi navigator.setAppBadge(unreadCount) để hiện số đỏ trên icon ứng dụng.

Lưu ý kiến trúc quan trọng: Lỗi Silent Collapse trên iOS là nguyên nhân gốc rễ khiến Push Notification không hoạt động trên màn hình khóa. Giải pháp sử dụng tag động kèm timestamp là điểm quyết định để iOS APNs buộc phải đánh thức thiết bị cho mỗi tin nhắn mới.

5. Tầng 5: Điều hướng Deep Link Thông minh (Interactive Click Routing)

Tệp nguồn: sw.js, useNotificationDeepLink.ts, ProtectedRoute.tsx, PortalLayout.tsx, TopBar.tsx.

Trường hợp Điều kiện Hành vi khi chạm vào thông báo
Trường hợp 1 Ứng dụng đang mở ngầm Service Worker gửi tín hiệu NAVIGATE_TO_CHAT vào cửa sổ đang chạy ➔ Tự động mở Chat Drawer tương ứng.
Trường hợp 2 Ứng dụng chưa mở Service Worker mở URL gốc /?chatOpen=1&roomId=...:
  • Admin / Nhân viên: Đăng nhập vào màn hình ERP và tự động mở Drawer Chat Inbox.
  • Khách hàng: ProtectedRoute tự động chuyển tiếp sang Cổng khách hàng /portal/customer?chatOpen=1&roomId=... và tự động mở Chat Drawer hỗ trợ.

IV. Bảng Tổng Hợp Các Điểm Đã Được Khắc Phục Triệt Để

Vấn đề trước đây Nguyên nhân gốc rễ Giải pháp đã hoàn thành
Gửi tin nhắn thật không phát Push Lỗi net.http_post trong Database Trigger do ép kiểu body::text sai chữ ký hàm pg_net (yêu cầu jsonb). Cập nhật Trigger function dùng đúng body jsonb và trỏ vào URL Cloud.
Edge Function bị lỗi PGRST200 Join foreign key sender:sender_id(full_name) không khớp cache schema PostgREST. Tách truy vấn chat_messagesprofiles độc lập trên Edge Function.
iPhone không sáng màn hình khóa khi nhận tin iOS APNs âm thầm gộp thông báo khi tag bị trùng lặp. Đổi tag sang định dạng động có timestamp chat-${roomId}-${Date.now()}.
Bấm thông báo bị lỗi 404 URL cũ trỏ vào route không tồn tại /customer-portal/chat. Chuẩn hóa URL sang /?chatOpen=1 kèm cơ chế Router Guard forward tự động.
Khách nhắn không thông báo cho Admin Layout Admin chưa gọi usePushSubscription. Tích hợp đồng bộ Push và nút kích hoạt nhanh trong AppShellNotificationBell.

V. Kết Luận

Hệ thống Push Notification trên Lock Screen của VinhPhatERP hiện đã đạt chuẩn Enterprise / Production-Ready:

  • Vận hành tự động 100% từ tầng Database PostgreSQL.
  • Đầy đủ tính năng 2 chiều (Admin ↔ Customer).
  • Tương thích hoàn hảo với iOS Safari PWA (Apple Push Service) và Android/Desktop (Google FCM).
  • Đảm bảo 0 lỗi typecheck, 0 lỗi RPC sync, và 100% tuân thủ Architecture Guard.

Đăng nhận xét

Mới hơn Cũ hơn