diff --git a/README.md b/README.md index f95ebe1a..696954c3 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,8 @@ To set up your own instance of `MiroTalk P2P` on a dedicated cloud server, pleas - ianramzy (html [template](https://cruip.com/demos/neon/)) - vasanthv (webrtc-logic) - fabric.js (whiteboard) +- [Robohash.org](https://robohash.org) (random avatars) +- [Image by ddraw on Freepik](https://www.freepik.com/free-vector/collection-female-male-avatars_1105371.htm) (avatar illustrations) diff --git a/app/src/server.js b/app/src/server.js index b4e71abc..bfd6a41a 100755 --- a/app/src/server.js +++ b/app/src/server.js @@ -1616,17 +1616,19 @@ io.sockets.on('connect', async (socket) => { let peer_id_to_update = null; for (let peer_id in peers[room_id]) { - if (peers[room_id][peer_id]['peer_name'] == peer_name_old && peer_id == socket.id) { + if (peer_id == socket.id) { peers[room_id][peer_id]['peer_name'] = peer_name_new; + peers[room_id][peer_id]['peer_avatar'] = peer_avatar; // presenter if (presenters && presenters[room_id] && presenters[room_id][peer_id]) { presenters[room_id][peer_id]['peer_name'] = peer_name_new; } peer_id_to_update = peer_id; - log.debug('[' + socket.id + '] Peer name changed', { + log.debug('[' + socket.id + '] Peer profile changed', { peer_name_old: peer_name_old, peer_name_new: peer_name_new, }); + break; } } diff --git a/public/css/client.css b/public/css/client.css index 82ba618b..f692e9cd 100755 --- a/public/css/client.css +++ b/public/css/client.css @@ -3161,6 +3161,8 @@ button { #activeRoomsBtn, #myPeerNameSetBtn, +#myProfileAvatarUploadBtn, +#myProfileAvatarResetBtn, #captionEveryoneBtn, #muteEveryoneBtn, #hideEveryoneBtn, @@ -3177,6 +3179,8 @@ button { } #myPeerNameSetBtn:hover, +#myProfileAvatarUploadBtn:hover, +#myProfileAvatarResetBtn:hover, #roomSendEmailBtn:hover, #lockRoomBtn:hover, #unlockRoomBtn:hover { @@ -3220,6 +3224,11 @@ button { transition: all 0.3s ease-in-out; } +#myProfileAvatarUploadBtn, +#myProfileAvatarResetBtn { + margin-top: 6px; +} + /*-------------------------------------------------------------- # Settings Table --------------------------------------------------------------*/ diff --git a/public/images/avatars/avatar_01.png b/public/images/avatars/avatar_01.png new file mode 100644 index 00000000..acdcc3c8 Binary files /dev/null and b/public/images/avatars/avatar_01.png differ diff --git a/public/images/avatars/avatar_02.png b/public/images/avatars/avatar_02.png new file mode 100644 index 00000000..15d2864d Binary files /dev/null and b/public/images/avatars/avatar_02.png differ diff --git a/public/images/avatars/avatar_03.png b/public/images/avatars/avatar_03.png new file mode 100644 index 00000000..9a82eaf1 Binary files /dev/null and b/public/images/avatars/avatar_03.png differ diff --git a/public/images/avatars/avatar_04.png b/public/images/avatars/avatar_04.png new file mode 100644 index 00000000..3a1299d5 Binary files /dev/null and b/public/images/avatars/avatar_04.png differ diff --git a/public/images/avatars/avatar_05.png b/public/images/avatars/avatar_05.png new file mode 100644 index 00000000..138e5052 Binary files /dev/null and b/public/images/avatars/avatar_05.png differ diff --git a/public/images/avatars/avatar_06.png b/public/images/avatars/avatar_06.png new file mode 100644 index 00000000..13254f80 Binary files /dev/null and b/public/images/avatars/avatar_06.png differ diff --git a/public/images/avatars/avatar_07.png b/public/images/avatars/avatar_07.png new file mode 100644 index 00000000..689874b0 Binary files /dev/null and b/public/images/avatars/avatar_07.png differ diff --git a/public/images/avatars/avatar_08.png b/public/images/avatars/avatar_08.png new file mode 100644 index 00000000..01804174 Binary files /dev/null and b/public/images/avatars/avatar_08.png differ diff --git a/public/images/avatars/avatar_09.png b/public/images/avatars/avatar_09.png new file mode 100644 index 00000000..c8e1f825 Binary files /dev/null and b/public/images/avatars/avatar_09.png differ diff --git a/public/images/avatars/avatar_10.png b/public/images/avatars/avatar_10.png new file mode 100644 index 00000000..620270a2 Binary files /dev/null and b/public/images/avatars/avatar_10.png differ diff --git a/public/images/avatars/avatar_11.png b/public/images/avatars/avatar_11.png new file mode 100644 index 00000000..c15b50c4 Binary files /dev/null and b/public/images/avatars/avatar_11.png differ diff --git a/public/images/avatars/avatar_12.png b/public/images/avatars/avatar_12.png new file mode 100644 index 00000000..b94b0786 Binary files /dev/null and b/public/images/avatars/avatar_12.png differ diff --git a/public/images/avatars/avatar_13.png b/public/images/avatars/avatar_13.png new file mode 100644 index 00000000..d0a532b6 Binary files /dev/null and b/public/images/avatars/avatar_13.png differ diff --git a/public/images/avatars/avatar_14.png b/public/images/avatars/avatar_14.png new file mode 100644 index 00000000..582897e5 Binary files /dev/null and b/public/images/avatars/avatar_14.png differ diff --git a/public/images/avatars/avatar_15.png b/public/images/avatars/avatar_15.png new file mode 100644 index 00000000..f1de3936 Binary files /dev/null and b/public/images/avatars/avatar_15.png differ diff --git a/public/images/avatars/avatar_16.png b/public/images/avatars/avatar_16.png new file mode 100644 index 00000000..fe473c3d Binary files /dev/null and b/public/images/avatars/avatar_16.png differ diff --git a/public/images/avatars/avatar_17.png b/public/images/avatars/avatar_17.png new file mode 100644 index 00000000..40a73a69 Binary files /dev/null and b/public/images/avatars/avatar_17.png differ diff --git a/public/images/avatars/avatar_18.png b/public/images/avatars/avatar_18.png new file mode 100644 index 00000000..3c1d8229 Binary files /dev/null and b/public/images/avatars/avatar_18.png differ diff --git a/public/images/avatars/avatar_19.png b/public/images/avatars/avatar_19.png new file mode 100644 index 00000000..882cbd46 Binary files /dev/null and b/public/images/avatars/avatar_19.png differ diff --git a/public/images/avatars/avatar_20.png b/public/images/avatars/avatar_20.png new file mode 100644 index 00000000..24246574 Binary files /dev/null and b/public/images/avatars/avatar_20.png differ diff --git a/public/images/avatars/avatar_21.png b/public/images/avatars/avatar_21.png new file mode 100644 index 00000000..eda8b473 Binary files /dev/null and b/public/images/avatars/avatar_21.png differ diff --git a/public/images/avatars/avatar_22.png b/public/images/avatars/avatar_22.png new file mode 100644 index 00000000..5c263774 Binary files /dev/null and b/public/images/avatars/avatar_22.png differ diff --git a/public/images/avatars/avatar_23.png b/public/images/avatars/avatar_23.png new file mode 100644 index 00000000..10175dbc Binary files /dev/null and b/public/images/avatars/avatar_23.png differ diff --git a/public/images/avatars/avatar_24.png b/public/images/avatars/avatar_24.png new file mode 100644 index 00000000..21094df7 Binary files /dev/null and b/public/images/avatars/avatar_24.png differ diff --git a/public/images/avatars/avatar_25.png b/public/images/avatars/avatar_25.png new file mode 100644 index 00000000..7d1efc58 Binary files /dev/null and b/public/images/avatars/avatar_25.png differ diff --git a/public/js/client.js b/public/js/client.js index 5770aee6..3e897263 100644 --- a/public/js/client.js +++ b/public/js/client.js @@ -360,6 +360,8 @@ const tabLanguagesBtn = getId('tabLanguagesBtn'); const mySettingsCloseBtn = getId('mySettingsCloseBtn'); const myPeerNameSet = getId('myPeerNameSet'); const myPeerNameSetBtn = getId('myPeerNameSetBtn'); +const myProfileAvatarUploadBtn = getId('myProfileAvatarUploadBtn'); +const myProfileAvatarResetBtn = getId('myProfileAvatarResetBtn'); const switchSounds = getId('switchSounds'); const switchShare = getId('switchShare'); const switchKeepButtonsVisible = getId('switchKeepButtonsVisible'); @@ -563,6 +565,7 @@ let thisMaxRoomParticipants = 8; let swBg = 'rgba(0, 0, 0, 0.7)'; // swAlert background color let isDocumentOnFullScreen = false; let isToggleExtraBtnClicked = false; +let hasTemporaryAvatar = false; // peer let myPeerId; // This socket.id @@ -847,6 +850,8 @@ function setButtonsToolTip() { // Settings setTippy(mySettingsCloseBtn, 'Close', 'bottom'); setTippy(myPeerNameSetBtn, 'Change name', 'top'); + setTippy(myProfileAvatarUploadBtn, 'Set temporary avatar URL', 'top'); + setTippy(myProfileAvatarResetBtn, 'Reset temporary avatar', 'top'); setTippy(myRoomId, 'Room name (click to copy/share)', 'right'); setTippy(mySessionTime, 'Session time', 'right'); setTippy( @@ -1237,10 +1242,11 @@ function generateRandomName() { function getPeerAvatar() { const avatar = getQueryParam('avatar'); const avatarDisabled = avatar === '0' || avatar === 'false'; + const isBase64Avatar = typeof avatar === 'string' && avatar.startsWith('data:image/'); console.log('Direct join', { avatar: avatar }); - if (avatarDisabled || !isImageURL(avatar)) { + if (avatarDisabled || isBase64Avatar || !isValidAvatarURL(avatar)) { return false; } return avatar; @@ -2633,6 +2639,10 @@ async function handleAddPeer(config) { return; } + // Re-broadcast current profile to ensure late joiners receive latest avatar/name. + // This uses the existing peerName signaling path. + emitMyPeerProfile(); + console.log('iceServers', iceServers[0]); // https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection @@ -2695,6 +2705,18 @@ async function handleAddPeer(config) { screenReaderAccessibility.announceMessage(`${peer_name} joined the room`); } +/** + * Broadcast my current profile (name + avatar) to room peers + */ +function emitMyPeerProfile() { + sendToServer('peerName', { + room_id: roomId, + peer_name_old: myPeerName, + peer_name_new: myPeerName, + peer_avatar: myPeerAvatar, + }); +} + /** * Handle peers connection state * https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/connectionstatechange_event @@ -5259,10 +5281,11 @@ function genAvatarSvg(peerName, avatarImgSize) { */ function setPeerAvatarImgName(videoAvatarImageId, peerName, peerAvatar) { const videoAvatarImageElement = getId(videoAvatarImageId); + if (!videoAvatarImageElement) return; videoAvatarImageElement.style.pointerEvents = 'none'; // If a valid avatar image URL is provided - if (peerAvatar && isImageURL(peerAvatar)) { + if (peerAvatar && isValidAvatarURL(peerAvatar)) { videoAvatarImageElement.setAttribute('src', peerAvatar); } // If not, use SVG based on the email validity @@ -5285,7 +5308,7 @@ function setPeerAvatarImgName(videoAvatarImageId, peerName, peerAvatar) { */ function setPeerChatAvatarImgName(avatar, peerName, peerAvatar) { const avatarImg = - peerAvatar && isImageURL(peerAvatar) + peerAvatar && isValidAvatarURL(peerAvatar) ? peerAvatar : isValidEmail(peerName) ? genGravatar(peerName) @@ -7121,6 +7144,13 @@ function setMySettingsBtn() { myPeerNameSetBtn.addEventListener('click', (e) => { updateMyPeerName(); }); + myProfileAvatarUploadBtn.addEventListener('click', async () => { + await updateMyPeerAvatarByUrl(); + }); + myProfileAvatarResetBtn.addEventListener('click', () => { + resetMyPeerAvatarInMemory(); + }); + updateMyAvatarResetButtonVisibility(); // Sounds switchSounds.addEventListener('change', (e) => { notifyBySound = e.currentTarget.checked; @@ -10569,7 +10599,7 @@ function handleSpeechTranscript(config) { const time_stamp = getFormatDate(new Date()); const avatar_image = - peer_avatar && isImageURL(peer_avatar) + peer_avatar && isValidAvatarURL(peer_avatar) ? peer_avatar : isValidEmail(peer_name) ? genGravatar(peer_name) @@ -10577,9 +10607,14 @@ function handleSpeechTranscript(config) { if (!isCaptionBoxVisible && transcriptShowOnMsg) showCaptionDraggable(); + // avatar_image is a user-controlled URL; do NOT interpolate it into + // insertAdjacentHTML — filterXSS encodes " to " which the HTML + // parser decodes back to " in attribute context (double-decode XSS). + // Use a temporary id and setAttribute instead. + const captionAvatarTmpId = `capt-av-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`; const msgHTML = `