From 290664bf0ba14c948382d9f8813fd0bee30431a1 Mon Sep 17 00:00:00 2001 From: Sulaiman AlRomaih Date: Sun, 5 Apr 2026 22:35:28 +0300 Subject: [PATCH 1/7] Web: Add connection failure notifications - Shows a warning banner when peer-to-peer connections are lost for over 5 seconds. - Disables the send button and file attachment options when no peers are connected. - Adds English and Arabic translations for troubleshooting network issues. - Includes UI buttons to refresh the page or dismiss the warning banner. - Tracks peer connection states in real-time to provide immediate feedback. --- .../src/app/core/i18n/localizations/ar.json | 6 ++ .../src/app/core/i18n/localizations/en.json | 6 ++ .../communication/webrtc-signaling.service.ts | 6 ++ .../services/communication/webrtc.service.ts | 8 ++ .../src/app/features/chat/chat.component.html | 94 ++++++++++++++-- .../src/app/features/chat/chat.component.ts | 101 +++++++++++++++++- client/web/src/app/utils/constants.ts | 1 + 7 files changed, 208 insertions(+), 14 deletions(-) diff --git a/client/web/src/app/core/i18n/localizations/ar.json b/client/web/src/app/core/i18n/localizations/ar.json index 32b72d7..38f5416 100644 --- a/client/web/src/app/core/i18n/localizations/ar.json +++ b/client/web/src/app/core/i18n/localizations/ar.json @@ -108,6 +108,12 @@ "TERMS_GOVERNING_LAW": "تُفسَّر هذه الشروط وتُطبَّق وفقاً لقوانين الولاية القضائية التي توجد فيها جهة تشغيل الخدمة، دون اعتبار لتنازع القوانين.", "LEGAL_NOTICE": "هذه الوثيقة لأغراض المعلومات فقط ولا تُعد استشارة قانونية. يُرجى استشارة محامٍ مختص لملابساتك الخاصة.", + "_CONNECTION_WARNING_SECTION": "==== CONNECTION WARNING ====", + "CONNECTION_WARNING_TITLE": "تعذّر الاتصال ببعض الأعضاء", + "CONNECTION_WARNING_DESC": "قد تمنع شبكتك الاتصال المباشر بين الأجهزة. جرّب تحديث الصفحة، أو التبديل إلى شبكة أخرى، أو تعطيل VPN أو جدار الحماية.", + "CONNECTION_WARNING_REFRESH": "تحديث", + "CONNECTION_WARNING_DISMISS": "تجاهل", + "_STATUS_ERRORS_SECTION": "==== STATUS & ERRORS ====", "CONNECTION_LOST": "تم فقد الاتصال", "ERROR": "حدث خطأ", diff --git a/client/web/src/app/core/i18n/localizations/en.json b/client/web/src/app/core/i18n/localizations/en.json index f4b5a8a..66f5561 100644 --- a/client/web/src/app/core/i18n/localizations/en.json +++ b/client/web/src/app/core/i18n/localizations/en.json @@ -108,6 +108,12 @@ "TERMS_GOVERNING_LAW": "These Terms shall be governed by and construed in accordance with the laws of the jurisdiction where the Service operator is established, without regard to its conflict of law provisions.", "LEGAL_NOTICE": "This document is provided for informational purposes only and does not constitute legal advice. Consult a qualified attorney for advice specific to your situation.", + "_CONNECTION_WARNING_SECTION": "==== CONNECTION WARNING ====", + "CONNECTION_WARNING_TITLE": "Unable to connect to some members", + "CONNECTION_WARNING_DESC": "Your network may be blocking peer-to-peer connections. Try refreshing the page, switching to a different network, or disabling your VPN/firewall.", + "CONNECTION_WARNING_REFRESH": "Refresh", + "CONNECTION_WARNING_DISMISS": "Dismiss", + "_STATUS_ERRORS_SECTION": "==== STATUS & ERRORS ====", "CONNECTION_LOST": "Connection lost", "ERROR": "Something went wrong", diff --git a/client/web/src/app/core/services/communication/webrtc-signaling.service.ts b/client/web/src/app/core/services/communication/webrtc-signaling.service.ts index 6ee8858..24078f2 100644 --- a/client/web/src/app/core/services/communication/webrtc-signaling.service.ts +++ b/client/web/src/app/core/services/communication/webrtc-signaling.service.ts @@ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; import { NGXLogger } from 'ngx-logger'; import { WebRTCCommunicationService } from './webrtc-communication.service'; import { HotToastService } from '@ngxpert/hot-toast'; +import { Subject } from 'rxjs'; @Injectable({ providedIn: 'root', @@ -30,6 +31,9 @@ export class WebRTCSignalingService { private communicationService = inject(WebRTCCommunicationService); // =============== Properties =============== + public peerDisconnected$ = new Subject(); + public peerConnected$ = new Subject(); + private peerConnections = new Map(); private reconnectAttempts = new Map(); private connectionLocks = new Set(); @@ -435,6 +439,7 @@ export class WebRTCSignalingService { iceGatheringTimeout = null; } this.logger.info('createPeerConnection', `Successfully connected to ${targetUser}`); + this.peerConnected$.next(targetUser); } else if (state === 'failed' || state === 'disconnected') { // Clear timeout on failure if (iceGatheringTimeout) { @@ -475,6 +480,7 @@ export class WebRTCSignalingService { * @param targetUser The user to handle disconnection for */ private handleDisconnection(targetUser: string) { + this.peerDisconnected$.next(targetUser); const attempts = this.reconnectAttempts.get(targetUser) ?? 0; // Log diagnostic info on first failure diff --git a/client/web/src/app/core/services/communication/webrtc.service.ts b/client/web/src/app/core/services/communication/webrtc.service.ts index 3748497..0116b08 100644 --- a/client/web/src/app/core/services/communication/webrtc.service.ts +++ b/client/web/src/app/core/services/communication/webrtc.service.ts @@ -15,6 +15,14 @@ export class WebRTCService implements IWebRTCService { private logger = inject(NGXLogger); // =============== Public Properties =============== + public get peerDisconnected$(): Subject { + return this.signalingService.peerDisconnected$; + } + + public get peerConnected$(): Subject { + return this.signalingService.peerConnected$; + } + /** * Gets the data channel open subject */ diff --git a/client/web/src/app/features/chat/chat.component.html b/client/web/src/app/features/chat/chat.component.html index 7da701a..1229ecb 100644 --- a/client/web/src/app/features/chat/chat.component.html +++ b/client/web/src/app/features/chat/chat.component.html @@ -228,7 +228,8 @@

{{ 'MEMBERS' | translate }}

+ + + + + + + } + -
+
@if (messages.length === 0) { @@ -1929,8 +1998,11 @@
[src]="'/icons/link.svg'" alt="Attach Files" title="Attach Files" - (click)="fileInput.click()" - class="h-5 w-5 cursor-pointer" + (click)="hasNoConnectedPeers ? null : fileInput.click()" + class="h-5 w-5" + [ngClass]=" + hasNoConnectedPeers ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' + " loading="lazy" width="20" height="20" @@ -1939,8 +2011,11 @@
[src]="'/icons/emoji.svg'" alt="Emoji" title="Open emoji picker" - class="h-5 w-5 cursor-pointer" - (mouseenter)="openEmojiPicker()" + class="h-5 w-5" + [ngClass]=" + hasNoConnectedPeers ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer' + " + (mouseenter)="hasNoConnectedPeers ? null : openEmojiPicker()" (mouseleave)="handleEmojiIconMouseLeave()" loading="lazy" width="20" @@ -1948,7 +2023,8 @@
/>
+