All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 2m43s
Clean-arch split: - Application: IRealtimeNotifier (PushToUserAsync, abstraction) - Api: NotificationHub (/hubs/notifications, [Authorize]) + SignalRNotifier impl với IHubContext<NotificationHub>, uses Clients.User(userId) (default provider resolves NameIdentifier="sub") - Infrastructure: NotificationPushInterceptor — SaveChangesInterceptor capture Notification entities state=Added trong SavingChanges, push qua IRealtimeNotifier trong SavedChanges sau khi commit thành công. Zero caller changes — handlers chỉ cần db.Add(Notification). Attached vào ApplicationDbContext cùng với AuditingInterceptor. Auth: - JWT config thêm OnMessageReceived event: read ?access_token= từ query string khi path = /hubs/* (WebSockets không set headers). - SignalRNotifier singleton (stateless, chỉ delegate IHubContext). FE (both apps): - @microsoft/signalr 8.0.7 vào package.json. - lib/realtime.ts: singleton connection với lazy start + automatic reconnect [0,2s,5s,10s,15s] + accessTokenFactory lấy từ localStorage. - NotificationBell: useEffect subscribe 'notification-created' khi isAuthenticated. On push: invalidate query + toast.message. Fallback polling giảm từ 30s → 60s (realtime cover gap). - AuthContext.logout: dynamic import stopConnection() — avoid leaking auth'd socket across users. Result: ERP-grade feel. Contract transition → Drafter nhận toast ngay trong vòng 100-300ms (same-origin WebSocket), không cần F5 hay polling. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
45 lines
1.1 KiB
JSON
45 lines
1.1 KiB
JSON
{
|
|
"name": "fe-user",
|
|
"private": true,
|
|
"version": "0.0.0",
|
|
"type": "module",
|
|
"engines": {
|
|
"node": ">=20"
|
|
},
|
|
"scripts": {
|
|
"dev": "vite",
|
|
"build": "tsc -b && vite build",
|
|
"lint": "eslint .",
|
|
"preview": "vite preview"
|
|
},
|
|
"dependencies": {
|
|
"@microsoft/signalr": "^8.0.7",
|
|
"@tailwindcss/vite": "^4.2.3",
|
|
"@tanstack/react-query": "^5.99.2",
|
|
"axios": "^1.15.1",
|
|
"class-variance-authority": "^0.7.1",
|
|
"clsx": "^2.1.1",
|
|
"lucide-react": "^1.8.0",
|
|
"react": "^19.2.5",
|
|
"react-dom": "^19.2.5",
|
|
"react-router-dom": "^7.14.1",
|
|
"sonner": "^2.0.7",
|
|
"tailwind-merge": "^3.5.0",
|
|
"tailwindcss": "^4.2.3"
|
|
},
|
|
"devDependencies": {
|
|
"@eslint/js": "^9.39.4",
|
|
"@types/node": "^24.12.2",
|
|
"@types/react": "^19.2.14",
|
|
"@types/react-dom": "^19.2.3",
|
|
"@vitejs/plugin-react": "^6.0.1",
|
|
"eslint": "^9.39.4",
|
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
"eslint-plugin-react-refresh": "^0.5.2",
|
|
"globals": "^17.5.0",
|
|
"typescript": "~6.0.2",
|
|
"typescript-eslint": "^8.58.2",
|
|
"vite": "^8.0.9"
|
|
}
|
|
}
|