diff --git a/frontend/src/dialogues/chat/MultiChatDialog.vue b/frontend/src/dialogues/chat/MultiChatDialog.vue index 6521d1b..d05a35a 100644 --- a/frontend/src/dialogues/chat/MultiChatDialog.vue +++ b/frontend/src/dialogues/chat/MultiChatDialog.vue @@ -321,6 +321,8 @@ export default { pendingRoomCreateName: '', pendingRoomCreateAttempts: 0, pendingRoomCreateTimer: null, + roomPasswords: {}, + passwordPromptActive: false, // Palette state paletteWidth: 420, paletteHeight: 220, @@ -365,7 +367,7 @@ export default { selectedRoom(newVal, oldVal) { if (newVal && this.transportConnected) { const room = this.getSelectedRoomName(); - if (room) this.sendWithToken({ type: 'join', room, password: '' }); + if (room) this.sendWithToken({ type: 'join', room, password: this.getRoomPassword(room) }); this.messages = []; this.usersInRoom = []; this.selectedTargetUser = null; @@ -405,6 +407,43 @@ export default { roomNamesEqual(a, b) { return (a || '').trim().toLowerCase() === (b || '').trim().toLowerCase(); }, + getRoomPassword(roomName) { + if (!roomName) return ''; + return this.roomPasswords[roomName] || ''; + }, + async handleRoomPasswordError(errorCode) { + const room = this.getSelectedRoomName(); + if (!room || this.passwordPromptActive) return; + this.passwordPromptActive = true; + try { + const isInvalid = errorCode === 'room_password_invalid'; + const promptText = isInvalid + ? this.$t('chat.multichat.password.invalidPrompt', { room }) + : this.$t('chat.multichat.password.requiredPrompt', { room }); + const entered = window.prompt(promptText, ''); + if (entered === null) { + this.messages.push({ + id: Date.now(), + user: 'System', + text: this.$t('chat.multichat.password.cancelled', { room }) + }); + return; + } + const password = entered.trim(); + if (!password) { + this.messages.push({ + id: Date.now(), + user: 'System', + text: this.$t('chat.multichat.password.empty') + }); + return; + } + this.roomPasswords[room] = password; + this.sendWithToken({ type: 'join', room, password }); + } finally { + this.passwordPromptActive = false; + } + }, tryConfirmRoomCreateSuccess() { if (!this.pendingRoomCreateName) return false; const created = this.ownRooms.find((r) => this.roomNamesEqual(r.title, this.pendingRoomCreateName)); @@ -805,7 +844,8 @@ export default { // Drop references to losers so GC can collect this.pendingWs = []; // Prepare handshake like before - const init = { type: 'init', name: this.user?.username || '', room: this.getSelectedRoomName() || '', password: '' }; + const initRoom = this.getSelectedRoomName() || ''; + const init = { type: 'init', name: this.user?.username || '', room: initRoom, password: this.getRoomPassword(initRoom) }; if (this.debug) console.log('[Chat WS >>]', init); this.wsSend(init); if (this.connectAttemptTimeout) clearTimeout(this.connectAttemptTimeout); @@ -902,7 +942,8 @@ export default { this.transportConnected = true; const dt = Date.now() - (this.wsStartAt || Date.now()); console.log('[Chat WS] open in', dt, 'ms', '| protocol:', ws.protocol || '(none)', '| url:', url); - const init = { type: 'init', name: this.user?.username || '', room: this.getSelectedRoomName() || '', password: '' }; + const initRoom = this.getSelectedRoomName() || ''; + const init = { type: 'init', name: this.user?.username || '', room: initRoom, password: this.getRoomPassword(initRoom) }; if (this.debug) console.log('[Chat WS >>]', init); this.wsSend(init); if (this.connectOpenTimer) { clearTimeout(this.connectOpenTimer); this.connectOpenTimer = null; } @@ -1413,6 +1454,10 @@ export default { if (!obj) return; if (obj.type === 'error') { const text = obj.message || 'chat_error'; + if (text === 'room_password_required' || text === 'room_password_invalid') { + this.handleRoomPasswordError(text); + return; + } this.setStatus('error'); this.messages.push({ id: Date.now(), user: 'System', text: `Fehler: ${text}` }); return; diff --git a/frontend/src/i18n/locales/de/chat.json b/frontend/src/i18n/locales/de/chat.json index c473b41..4bc53b9 100644 --- a/frontend/src/i18n/locales/de/chat.json +++ b/frontend/src/i18n/locales/de/chat.json @@ -114,6 +114,12 @@ "servicesStatus": "Service-Status" }, "types": {} + }, + "password": { + "requiredPrompt": "Der Raum \"{room}\" ist passwortgeschützt. Bitte Passwort eingeben:", + "invalidPrompt": "Falsches Passwort für \"{room}\". Bitte erneut eingeben:", + "cancelled": "Beitritt zu \"{room}\" abgebrochen.", + "empty": "Passwort darf nicht leer sein." } }, "randomchat": { diff --git a/frontend/src/i18n/locales/en/chat.json b/frontend/src/i18n/locales/en/chat.json index af063b4..c4a5522 100644 --- a/frontend/src/i18n/locales/en/chat.json +++ b/frontend/src/i18n/locales/en/chat.json @@ -114,6 +114,12 @@ "servicesStatus": "Service status" }, "types": {} + }, + "password": { + "requiredPrompt": "Room \"{room}\" is password-protected. Please enter password:", + "invalidPrompt": "Wrong password for \"{room}\". Please try again:", + "cancelled": "Join to \"{room}\" was cancelled.", + "empty": "Password must not be empty." } }, "randomchat": { diff --git a/frontend/src/i18n/locales/es/chat.json b/frontend/src/i18n/locales/es/chat.json index dc0e951..b91ada0 100644 --- a/frontend/src/i18n/locales/es/chat.json +++ b/frontend/src/i18n/locales/es/chat.json @@ -113,6 +113,12 @@ "servicesStatus": "Estado de servicios" }, "types": {} + }, + "password": { + "requiredPrompt": "La sala \"{room}\" está protegida por contraseña. Introduce la contraseña:", + "invalidPrompt": "Contraseña incorrecta para \"{room}\". Inténtalo de nuevo:", + "cancelled": "Se canceló el acceso a \"{room}\".", + "empty": "La contraseña no puede estar vacía." } }, "randomchat": {