diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..c235c7c
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,53 @@
+# Changelog
+
+All notable changes to this project will be documented in this file.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
+and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+
+## [Unreleased]
+
+### Added
+- Audio notification feature when peer joins chat [@Avinash-yadav103](https://github.com/Avinash-yadav103)
+ - Pleasant beep sound using Web Audio API
+ - Toggle button to mute/unmute notifications
+ - Persistent settings using localStorage
+ - Fully accessible and keyboard navigable
+ - Mobile-friendly implementation
+- Comprehensive documentation for audio notification feature
+- CONTRIBUTORS.md file for proper contributor recognition
+- Feature documentation in docs/AUDIO_NOTIFICATION_FEATURE.md
+
+### Changed
+- Updated README.md with new audio notification feature
+- Enhanced contributors section in README.md
+
+### Technical Details
+- Implemented Web Audio API for client-side sound generation
+- Added localStorage persistence for user preferences
+- SVG icon updates for visual feedback
+- No external dependencies added
+
+---
+
+## How to Update This Changelog
+
+When contributing, please add your changes under the `[Unreleased]` section following this format:
+
+### Added
+- New features
+
+### Changed
+- Changes to existing functionality
+
+### Deprecated
+- Soon-to-be removed features
+
+### Removed
+- Removed features
+
+### Fixed
+- Bug fixes
+
+### Security
+- Security improvements
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
new file mode 100644
index 0000000..5f1d807
--- /dev/null
+++ b/CONTRIBUTORS.md
@@ -0,0 +1,53 @@
+# Contributors
+
+Thank you to all the amazing people who have contributed to chat-e2ee! 🎉
+
+This project exists because of the contributions from developers around the world. Below is a list of contributors who have helped make this project better.
+
+## How to Contribute
+
+We welcome contributions! Please see our [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute to this project.
+
+## Feature Contributors
+
+### Audio Notification Feature
+**Feature**: Audio notification when peer joins chat with mute/unmute option
+**Contributors**:
+- [@Avinash-yadav103](https://github.com/Avinash-yadav103) - Implemented user join audio notifications with localStorage persistence and toggle controls (February 2026)
+
+## All Contributors
+
+This project follows the [all-contributors](https://allcontributors.org) specification.
+
+
+
+
+
+## Legend
+
+- ✨ Features
+- 💻 Code
+- 🐛 Bug fixes
+- 📖 Documentation
+- 🎨 Design
+- 💡 Ideas & Planning
+- 🚧 Maintenance
+- ⚠️ Tests
+- 🔧 Tools
+
+---
+
+*Want to see your name here? Check out our [contribution guidelines](CONTRIBUTING.md) and start contributing today!*
diff --git a/README.md b/README.md
index 9967f12..4244352 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,7 @@ Demo: https://chat-e2ee-2.azurewebsites.net
1. :negative_squared_cross_mark: No login/signup - the end users **don't identify** themselves.
2. :closed_lock_with_key: End-to-end encrypted Audio-Call (Experimental - added on [19th September, 2024](https://github.com/muke1908/chat-e2ee/commit/efae545c4c378dd7cae3c133843c1d58fded8a56)).
:warning: Note that Audio encryption in webrtc call is done diffrently, please refer [Wiki](https://github.com/muke1908/chat-e2ee/wiki/End%E2%80%90to%E2%80%90end-encryption-in-Webrtc-audio-call). It internally uses RTCRtpSender API: `createEncodedStreams` that has [limited Support](https://caniuse.com/mdn-api_rtcrtpsender_createencodedstreams)
+3. :bell: Audio notification when a peer joins the chat - with option to mute/unmute notifications.
4. :no_entry_sign: Data is **not** stored on any remote server, encrypted data is just relayed to other users, the data can't be decrypted by any man in the middle. **No history** i.e. once chat is closed the data is not recoverable, however encrypted data can be found on memory trace. [Read More](https://github.com/muke1908/chat-e2ee/wiki/How-and-when-your-data-can-be-compromised%3F)
## :star: JS SDK
@@ -112,8 +113,15 @@ Example:
## ✨ Contributors
+We thank all contributors who have helped improve chat-e2ee!
+
+### Recent Contributors
+- **[@Avinash-yadav103](https://github.com/Avinash-yadav103)** - Added audio notification feature for user joins with mute/unmute controls
+
+See [CONTRIBUTORS.md](CONTRIBUTORS.md) for detailed contribution history.
+
---
## :closed_lock_with_key: Cryptographic notice
This distribution includes cryptographic software. The country in which you currently reside may have restrictions on the import, possession, use, and/or re-export to another country, of encryption software. BEFORE using any encryption software, please check your country's laws, regulations and policies concerning the import, possession, or use, and re-export of encryption software, to see if this is permitted. See http://www.wassenaar.org/ for more information.
diff --git a/client/app.ts b/client/app.ts
index 92b1176..ae19771 100644
--- a/client/app.ts
+++ b/client/app.ts
@@ -6,6 +6,10 @@ let userId: string = '';
let channelHash: string = '';
let privateKey: string = '';
+// Audio notification settings
+let audioNotificationsEnabled: boolean = true;
+const AUDIO_SETTINGS_KEY = 'chat-e2ee-audio-notifications';
+
// DOM Elements
// DOM Elements
const setupOverlay = document.getElementById('setup-overlay')!;
@@ -34,6 +38,7 @@ const participantInfo = document.getElementById('participant-info')!;
const headerHashDisplay = document.getElementById('channel-hash-display')!;
const headerHashText = document.getElementById('header-hash')!;
const copyHeaderHashBtn = document.getElementById('copy-header-hash') as HTMLButtonElement;
+const audioToggleBtn = document.getElementById('audio-toggle-btn') as HTMLButtonElement;
// Call Elements
const callOverlay = document.getElementById('call-overlay')!;
@@ -41,6 +46,71 @@ const callStatusText = document.getElementById('call-status')!;
const endCallBtn = document.getElementById('end-call-btn') as HTMLButtonElement;
const callDuration = document.getElementById('call-duration')!;
+// Audio Notification Functions
+function loadAudioSettings() {
+ const saved = localStorage.getItem(AUDIO_SETTINGS_KEY);
+ audioNotificationsEnabled = saved === null ? true : saved === 'true';
+ updateAudioToggleButton();
+}
+
+function saveAudioSettings() {
+ localStorage.setItem(AUDIO_SETTINGS_KEY, audioNotificationsEnabled.toString());
+}
+
+function updateAudioToggleButton() {
+ if (audioToggleBtn) {
+ const icon = audioToggleBtn.querySelector('svg');
+ if (audioNotificationsEnabled) {
+ audioToggleBtn.title = 'Mute join notifications';
+ if (icon) {
+ icon.innerHTML = ``;
+ }
+ } else {
+ audioToggleBtn.title = 'Unmute join notifications';
+ if (icon) {
+ icon.innerHTML = ``;
+ }
+ }
+ }
+}
+
+function playJoinBeep() {
+ if (!audioNotificationsEnabled) return;
+
+ try {
+ // Create AudioContext
+ const audioContext = new (window.AudioContext || (window as any).webkitAudioContext)();
+
+ // Create oscillator for beep sound
+ const oscillator = audioContext.createOscillator();
+ const gainNode = audioContext.createGain();
+
+ // Configure beep: pleasant notification sound
+ oscillator.type = 'sine';
+ oscillator.frequency.setValueAtTime(800, audioContext.currentTime); // 800Hz
+
+ // Envelope for smooth sound
+ gainNode.gain.setValueAtTime(0, audioContext.currentTime);
+ gainNode.gain.linearRampToValueAtTime(0.15, audioContext.currentTime + 0.02);
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.2);
+
+ // Connect nodes
+ oscillator.connect(gainNode);
+ gainNode.connect(audioContext.destination);
+
+ // Play beep
+ oscillator.start(audioContext.currentTime);
+ oscillator.stop(audioContext.currentTime + 0.2);
+
+ // Cleanup
+ oscillator.onended = () => {
+ audioContext.close();
+ };
+ } catch (err) {
+ console.error('Audio notification error:', err);
+ }
+}
+
// Initialize Chat
async function initChat() {
try {
@@ -52,6 +122,9 @@ async function initChat() {
privateKey = keys.privateKey;
setupStatus.textContent = '';
+ // Load audio settings
+ loadAudioSettings();
+
// Check for URL hash on load
handleUrlHash();
} catch (err) {
@@ -183,6 +256,7 @@ function setupChatListeners() {
chat.on('on-alice-join', () => {
chatHeader.classList.add('active');
participantInfo.textContent = 'Peer joined. Communication is encrypted.';
+ playJoinBeep(); // Play notification sound
});
chat.on('on-alice-disconnect', () => {
@@ -299,5 +373,26 @@ function hideCallOverlay() {
callOverlay.classList.add('hidden');
}
+// Audio Toggle Button Handler
+if (audioToggleBtn) {
+ audioToggleBtn.addEventListener('click', () => {
+ audioNotificationsEnabled = !audioNotificationsEnabled;
+ saveAudioSettings();
+ updateAudioToggleButton();
+
+ // Show feedback
+ const originalText = participantInfo.textContent;
+ participantInfo.textContent = audioNotificationsEnabled
+ ? 'Join notifications enabled'
+ : 'Join notifications muted';
+ setTimeout(() => {
+ if (participantInfo.textContent === 'Join notifications enabled' ||
+ participantInfo.textContent === 'Join notifications muted') {
+ participantInfo.textContent = originalText || 'Waiting for someone to join...';
+ }
+ }, 2000);
+ });
+}
+
// Start
initChat();
diff --git a/client/index.html b/client/index.html
index 191ff71..059d050 100644
--- a/client/index.html
+++ b/client/index.html
@@ -80,6 +80,14 @@ Secure Channel
Waiting for someone to join...