Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
53 changes: 53 additions & 0 deletions CONTRIBUTORS.md
Original file line number Diff line number Diff line change
@@ -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.

<!-- ALL-CONTRIBUTORS-LIST:START -->
<table>
<tr>
<td align="center">
<a href="https://github.com/Avinash-yadav103">
<img src="https://github.com/Avinash-yadav103.png?size=100" width="100px;" alt=""/>
<br />
<sub><b>Avinash Yadav</b></sub>
</a>
<br />
<a href="#features" title="Features">✨</a>
<a href="#code" title="Code">πŸ’»</a>
</td>
</tr>
</table>
<!-- ALL-CONTRIBUTORS-LIST:END -->

## 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!*
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -112,8 +113,15 @@ Example:

## ✨ Contributors

We thank all contributors who have helped improve chat-e2ee!

<img src="https://contributors-img.web.app/image?repo=muke1908/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.
Expand Down
95 changes: 95 additions & 0 deletions client/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')!;
Expand Down Expand Up @@ -34,13 +38,79 @@ 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')!;
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 = `<path d="M11 5L6 9H2v6h4l5 4V5z"></path><path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path><path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>`;
}
} else {
audioToggleBtn.title = 'Unmute join notifications';
if (icon) {
icon.innerHTML = `<path d="M11 5L6 9H2v6h4l5 4V5z"></path><line x1="23" y1="9" x2="17" y2="15"></line><line x1="17" y1="9" x2="23" y2="15"></line>`;
}
}
}
}

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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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', () => {
Expand Down Expand Up @@ -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();
8 changes: 8 additions & 0 deletions client/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ <h2 id="channel-title">Secure Channel</h2>
<p id="participant-info">Waiting for someone to join...</p>
</div>
<div class="header-actions">
<button id="audio-toggle-btn" title="Mute join notifications" class="icon-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path d="M11 5L6 9H2v6h4l5 4V5z"></path>
<path d="M15.54 8.46a5 5 0 0 1 0 7.07"></path>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14"></path>
</svg>
</button>
<button id="start-call-btn" title="Start Audio Call" class="icon-btn">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
Expand Down
4 changes: 2 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ FROM node:lts-alpine

WORKDIR /chat-e2ee
COPY package*.json /chat-e2ee/
RUN mkdir -p /chat-e2ee/client/build
RUN mkdir -p /chat-e2ee/client/dist
COPY --from=BUILD /chat-e2ee/dist /chat-e2ee/dist
COPY --from=BUILD /chat-e2ee/client/build /chat-e2ee/client/build
COPY --from=BUILD /chat-e2ee/client/dist /chat-e2ee/client/dist

# dont install dev deps
ENV NODE_ENV "production"
Expand Down
Loading
Loading