feat: implement NotificationsHub for real-time vault sync notifications

- Added NotificationsHub durable object to handle WebSocket connections for vault sync notifications.
- Integrated SignalR protocol for message framing and communication.
- Updated storage service methods to return revision date and user ID for vault sync notifications.
- Enhanced existing handlers (attachments, ciphers, folders, sends, and import) to notify users of vault sync events.
- Created new notifications handler for WebSocket negotiation and binding user IDs.
- Updated frontend to establish WebSocket connection for receiving vault sync notifications.
- Improved CORS headers to support new notification endpoints.
- Bumped wrangler version in package.json to 4.71.0.
This commit is contained in:
shuaiplus
2026-03-09 00:25:34 +08:00
parent 54cf1ff718
commit 899f1004a3
18 changed files with 779 additions and 76 deletions
+20 -2
View File
@@ -104,6 +104,10 @@ import {
handleAdminExportBackup,
handleAdminImportBackup,
} from './handlers/backup';
import {
handleNotificationsHub,
handleNotificationsNegotiate,
} from './handlers/notifications';
function isSameOriginWriteRequest(request: Request): boolean {
const targetOrigin = new URL(request.url).origin;
@@ -474,6 +478,14 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
return errorResponse('Server configuration error: JWT_SECRET is not set or too weak', 500);
}
if (path === '/notifications/hub/negotiate' && method === 'POST') {
return handleNotificationsNegotiate(request, env);
}
if (path === '/notifications/hub' && method === 'GET') {
return handleNotificationsHub(request, env);
}
// All other API endpoints require authentication
const auth = new AuthService(env);
const authHeader = request.headers.get('Authorization');
@@ -483,6 +495,13 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
return errorResponse('Unauthorized', 401);
}
const actingDeviceId = String(payload.did || '').trim();
if (actingDeviceId) {
const nextHeaders = new Headers(request.headers);
nextHeaders.set('X-NodeWarden-Acting-Device-Id', actingDeviceId);
request = new Request(request, { headers: nextHeaders });
}
const userId = payload.sub;
const storage = new StorageService(env.DB);
const currentUser = await storage.getUserById(userId);
@@ -566,9 +585,8 @@ export async function handleRequest(request: Request, env: Env): Promise<Respons
return handleSync(request, env, userId);
}
// Notifications hub (stub): now requires authentication.
if (path.startsWith('/notifications/')) {
return new Response(null, { status: 200 });
return errorResponse('Not found', 404);
}
// Cipher endpoints