Skip to content

Repository files navigation

Chatrix — Anonymous Encrypted Group Chat (PWA)

Zero-login, real-time group chat with end-to-end-like encryption. No account creation needed — just pick a name and share a 4-digit room code. Works as a Progressive Web App (installable, offline shell, push notifications).

Features

  • No authentication — choose any display name, that's it
  • 4-digit room codes — join by typing a code, create new rooms instantly
  • AES-256-GCM encryption — messages encrypted client-side with PBKDF2-derived key from room code. Server sees only ciphertext
  • Real-time messaging — Firestore onSnapshot listeners for instant updates
  • File sharing — share images, PDFs, documents, and other files via encrypted data URLs (end-to-end encrypted like all messages)
  • Image sharing — compress and share images inline with automatic resize
  • Voice calls — WebRTC peer-to-peer voice calls with Firestore-based signaling
  • Push notifications — service worker watches Firestore directly; no Cloud Functions (Blaze) required
  • Reply / @mentions — reply to specific messages, tag users with @name, highlighted in green
  • Emoji picker — built-in emoji selector with 96 emojis and search
  • Message reactions — react to messages with emojis
  • Message editing & deletion — edit or delete your own messages
  • Typing indicators — see who's typing in real-time
  • Avatars — auto-generated initials with hash-based background colors
  • Room history — infinite scroll pagination, all previous messages loaded on enter
  • Room name editing — rename rooms inline with real-time sync
  • Member list — view all members and their online status with search/filter
  • Room owner controls — the room creator gets an owner badge and can remove members from the room (UI-enforced, see Security Rules section)
  • Room types — create Permanent rooms or Auto-delete rooms that are removed 1 hour after the last message
  • Burn-on-read — 🔥 toggle to send messages that self-delete 30 seconds after sending
  • Polls — create polls with up to 6 options; everyone can vote/toggle their vote, live percentages
  • Read receipts — see how many members have read your messages
  • Room fingerprint — unique XXXX-XXXX-XXXX fingerprint per room for verifying you're in the right room
  • Encryption key rotation — the owner can rotate the room's encryption key; old messages stay readable, new messages use the new key
  • Slow mode & word filters — owner sets a cooldown between messages and blocks unwanted words
  • Room freeze — owner can freeze the room so members can't send messages
  • Invite links — one-time links (24h expiry) generated by the owner, or share the room code
  • System feed — "joined the room" / "was removed" events appear inline in the chat
  • Activity rings & observer tag — member avatars show recency-based rings; "observer" tag for members who never spoke; activity timeline ("Active 2m ago")
  • Member devices — see and revoke the devices connected to your account
  • In-call wake lock + persistent call notification — screen stays awake during calls; a notification with "Return to call" appears when a call is active in the background
  • Quick reply from notifications — "Reply" action on a notification jumps straight to the input
  • Message sounds & haptics — per-room notification tone (Pop/Ding/Soft/Silent) with vibration
  • Date separators & smart timestamps — "Today / Yesterday / Monday" pills between message groups, locale-aware times, relative room previews ("5m ago", "Yesterday"), full date on hover
  • Share & shortcuts — share modal with join link + QR code, system share sheet, and a PWA shortcut to create a new room
  • Pure black UI — dark, distraction-free ChatGPT-style interface
  • Installable PWA — add to home screen, standalone mode
  • Vercel Analytics — privacy-friendly analytics

Tech Stack

Layer Technology
Frontend React 19, TypeScript, Vite
Styling Tailwind CSS v4
Database Firebase Firestore (free tier)
Push FCM + Firestore listeners in Service Worker
Encryption Web Crypto API (PBKDF2 + AES-256-GCM)
Local DB Dexie.js (IndexedDB wrapper)
State Zustand
PWA vite-plugin-pwa (injectManifest)
Routing React Router v7
QR Codes qrcode.react (SVG)
Voice Calls WebRTC (peer-to-peer)
Analytics Vercel Analytics
Linting oxlint

Prerequisites

  • Node.js 18+
  • A Firebase project with Firestore enabled (Spark plan is sufficient)

Setup

1. Clone and install

git clone <repo-url> chatrix
cd chatrix
npm install

2. Firebase Console Setup

  1. Go to console.firebase.google.com
  2. Create a new project (or use existing)
  3. Firestore Database → Create → Start in test mode → choose a region
  4. Project Settings → General → Add app → Web → copy the config values
  5. Project Settings → Cloud Messaging → Web Push certificatesGenerate → copy the VAPID key

3. Environment Variables

Copy .env (already created) or create from scratch:

VITE_FIREBASE_API_KEY=your_api_key
VITE_FIREBASE_AUTH_DOMAIN=your_project.firebaseapp.com
VITE_FIREBASE_PROJECT_ID=your_project_id
VITE_FIREBASE_STORAGE_BUCKET=your_project.appspot.com
VITE_FIREBASE_MESSAGING_SENDER_ID=your_sender_id
VITE_FIREBASE_APP_ID=your_app_id
VITE_FIREBASE_VAPID_KEY=your_vapid_public_key

4. Firestore Security Rules

Firestore → Rules → paste:

rules_version = '2';
service cloud.firestore {
  match /databases/{database}/documents {
    // Room content — anyone with the room code can join/read/write.
    // Message bodies are encrypted client-side, so exposure is ciphertext only.
    match /rooms/{code} {
      allow read, write: if true;
      match /{document=**} {
        allow read, write: if true;
      }
    }
    // User profiles + FCM tokens
    match /users/{uid} {
      allow read, write: if true;
      match /{document=**} {
        allow read, write: if true;
      }
    }
  }
}

Note: The app uses client-side encryption so message content is never exposed to the server. These permissive rules are acceptable for the encryption model. For production, consider restricting by path.

Newer features use extra paths

Room settings, invites and per-user devices add these collections (covered by the permissive rules above):

  • rooms/{code}slowModeSec, blockedWords, frozen, keyVersion, autoDelete, lastActivityAt
  • rooms/{code}/invites/{token} — single-use invite links (24h expiry)
  • rooms/{code}/messages/{id}readers (read receipts), burn (self-delete after 30s), poll (polls), sys (join/remove system feed), kv (key rotation version)
  • rooms/{code}/members/{uid}lastSpokeAt (observer detection)
  • users/{uid}/devices/{deviceId} — device list + revoke (used with the rule below)

To only allow members of a room to access these subcollections, tighten with get() checks, e.g.:

match /rooms/{code}/invites/{token} {
  allow read: if exists(/databases/$(database)/documents/rooms/$(code)/members/$(request.auth.uid));
  allow write: if request.auth != null
    && request.auth.uid == get(/databases/$(database)/documents/rooms/$(code)).data.createdBy;
}

Admin / member removal (kicked flag)

Room owners can remove members from the member list (see the owner badge in the member popup). Because Chatrix has no Firebase Authentication (identities are client-generated UUIDs stored in IndexedDB), Firestore rules cannot verify who is the room owner — the admin feature is enforced at the UI level only. Any user who knows another member's UID could technically write kicked: true to their member doc.

To enforce ownership server-side, add Anonymous Auth (firebase/auth, signInAnonymously() at app start) and replace the members write rule with:

match /rooms/{code}/members/{uid} {
  allow update, delete: if request.auth != null && request.auth.uid == get(/databases/$(database)/documents/rooms/$(code)).data.createdBy;
  allow create: if request.auth != null && request.auth.uid == uid;
}

5. Ad-blocker note

Some browser extensions block Firestore long-polling connections. If you see ERR_BLOCKED_BY_CLIENT in the console, add localhost and firestore.googleapis.com to your ad blocker's allowlist, or use initializeFirestore with experimentalAutoDetectLongPolling: true (already configured).

6. Run

npm run dev        # Development server (http://localhost:5173)
npm run build      # Production build → dist/
npm run preview    # Preview production build

Project Structure

src/
├── main.tsx                 # Entry point
├── App.tsx                  # Router + name gate + device manager + SW sync
├── index.css                # Tailwind import + base styles
├── sw.ts                    # Service Worker (notifications + Firestore watcher)
├── vite-env.d.ts            # Env type declarations
├── types/
│   └── index.ts             # All TypeScript interfaces
├── lib/
│   ├── firebase.ts          # Firebase config + initialization
│   ├── db.ts                # Dexie.js IndexedDB setup
│   ├── crypto.ts            # PBKDF2 key derivation + AES-256-GCM + fingerprints
│   ├── roomUtils.ts         # Room deletion helper (auto-delete + manual)
│   └── sw.ts                # SW communication helper
├── store/
│   └── useStore.ts          # Zustand global state
├── components/
│   ├── Avatar.tsx           # Initials avatar with hash color
│   ├── EmojiPicker.tsx      # Emoji selector popup with keyword search
│   ├── InviteToCall.tsx     # Modal to invite room members to voice call
│   ├── NameModal.tsx        # First-time name registration modal
│   ├── OtpInput.tsx         # 4-box OTP-style room code input
│   └── VoiceCallUI.tsx      # Discord-style voice call UI
├── pages/
│   ├── Dashboard.tsx        # Room code entry, join/create, room list, invite joins
│   └── ChatScreen.tsx       # Chat UI: messages, polls, settings, share, members
└── hooks/
    ├── useInstallPrompt.ts  # beforeinstallprompt event handler
    └── useVoiceCall.ts      # WebRTC peer-to-peer voice call logic

Data Model (Firestore)

users/{uid}

{
  "name": "Alice",
  "createdAt": "<timestamp>",
  "lastSeen": "<timestamp>"
}

users/{uid}/devices/{deviceId}

{
  "name": "Chrome · Windows",
  "lastSeen": "<timestamp>",
  "online": true,
  "revoked": false
}

rooms/{code}

{
  "name": "Room 1234",
  "createdAt": "<timestamp>",
  "createdBy": "<uid>",
  "autoDelete": false,
  "lastActivityAt": "<timestamp>",
  "slowModeSec": 0,
  "blockedWords": [],
  "frozen": false,
  "keyVersion": 0
}

rooms/{code}/messages/{messageId}

{
  "senderUid": "<uid>",
  "senderName": "Alice",
  "ciphertext": "<base64>",
  "iv": "<base64>",
  "timestamp": "<serverTimestamp>",
  "seq": "<number>",
  "kv": 0,
  "replyToUid": "<uid>",
  "mentionedUids": ["<uid>", ...],
  "edited": true,
  "deleted": true,
  "burn": false,
  "readers": ["<uid>", ...],
  "reactions": {
    "🔥": ["<uid1>", "<uid2>"]
  }
}

Polls and system events are stored unencrypted (non-sensitive, must be readable for live voting):

{
  "senderUid": "<uid>",
  "senderName": "Alice",
  "timestamp": "<serverTimestamp>",
  "seq": "<number>",
  "poll": {
    "question": "Where should we meet?",
    "multiple": false,
    "options": [{ "text": "Cafe", "voters": ["<uid>", ...] }]
  }
}
{
  "sys": { "type": "join", "uid": "<uid>", "name": "Alice" },
  "seq": "<number>",
  "timestamp": "<serverTimestamp>"
}

rooms/{code}/members/{uid}

{
  "joinedAt": "<timestamp>",
  "name": "Alice",
  "online": true,
  "lastSeen": "<timestamp>",
  "lastSpokeAt": "<timestamp>",
  "kicked": false,
  "kickedAt": "<timestamp>"
}

rooms/{code}/invites/{token}

{
  "createdBy": "<uid>",
  "createdAt": "<timestamp>",
  "expiresAt": "<date>",
  "uses": 0,
  "maxUses": 1
}

rooms/{code}/typing/{uid}

{
  "name": "Alice",
  "timestamp": "<timestamp>"
}

users/{uid}/tokens/{tokenId}

{
  "token": "<fcm_token>",
  "platform": "web",
  "createdAt": "<timestamp>",
  "lastUsed": "<timestamp>"
}

Encryption Details

  • Key derivation: PBKDF2(SHA-256, 100,000 iterations). Versioned salts:
    • v0 (legacy): chatwave-salt-2026
    • v1+: chatrix-kv-<version> — new versions are created on key rotation
  • Cipher: AES-256-GCM with random 12-byte IV per message
  • Message payload: JSON.stringify({ text, type?, file?, replyTo? }) — encrypted as a single blob
  • Key versioning: every message stores its key version in kv. Messages decrypt with the key for their own version, so rotating the room key never breaks history
  • Room fingerprint: SHA-256 of chatrix-room:<code> (first 6 bytes → XXXX-XXXX-XXXX), displayed in the member popup to verify you're in the right room
  • Key lifetime: Derived on room entry, held in memory only, never persisted
  • File encryption: Files are read as data URLs, encrypted with the same AES-256-GCM key, and stored inline in Firestore. File metadata (name, size, type) is included in the encrypted payload.
  • Security model: Room code is the shared secret. Anyone with the code can decrypt all messages in that room. 4-digit code space (9000 possibilities) is acknowledged as a brute-forceable weakness.

Voice Calls (WebRTC)

  • Peer-to-peer voice calls using WebRTC with Google STUN servers
  • Firestore-based signaling (offers, answers, ICE candidates)
  • Call invitations with accept/decline flow
  • Real-time participant list with mute/unmute
  • Screen wake lock keeps the display on during a call
  • Persistent call notification ("Return to call") appears when a call is active in the background and clears on hangup
  • Works without any media server on the free Spark plan

Push Notifications (Spark Plan)

Chatrix does not require Firebase Cloud Functions (Blaze plan). Instead:

  1. The service worker embeds Firebase Firestore SDK
  2. The app sends joined room codes to the SW via postMessage
  3. The SW sets up onSnapshot listeners on each room's messages (last message)
  4. When a new message arrives:
    • Not from the current user
    • User is not viewing that room
    • → Shows a notification with the sender name and room code
  5. Special handling for replies (replyToUid) and @mentions (mentionedUids)
  6. Notifications include Reply / Open actions — Reply jumps straight into the chat input
  7. Per-room vibration patterns derived from the room code hash
  8. FCM push events serve as a fallback mechanism when Cloud Functions are deployed

This approach works on the free Spark plan without any backend server.

Usage Flow

  1. First visit → Name modal → enter display name → saved locally + Firestore
  2. Dashboard → type a 4-digit code → Join existing room or Create new one (Permanent or Auto-delete)
  3. Joining from a link → open /?code=XXXX or /?code=XXXX&invite=token (one-time invite) → auto-joins the room
  4. Chat → send encrypted messages, reply with ↩️, @mention with @name, share files/images, create polls, toggle 🔥 burn-on-read
  5. Share → header share button opens join link + QR code, or share the room code directly
  6. Owner controls → settings gear: slow mode, word filters, freeze room, rotate encryption key, invite links
  7. Voice calls → tap the microphone icon in the header to start or join a call
  8. Returning → room list on dashboard shows recent chats with relative timestamps and previews
  9. Notifications → SW delivers background notifications with quick-reply actions

Limitations

  • 4-digit room codes = 9000 possible rooms. Brute-forceable — acknowledged trade-off
  • Encryption key derived from room code. Anyone with the code can read all messages
  • Admin controls (remove member, freeze, rotate) are UI-enforced only — Firestore rules can't verify the owner without Firebase Auth (see Security Rules section)
  • File sharing limited to ~700KB per file (1MB document limit after base64 encoding)
  • Offline message queuing not implemented (future scope)
  • iOS push notification support limited by platform PWA restrictions

Collaborators

  • Shreyas Pawar — Frontend development, UI/UX design, WebRTC integration
  • Atharva Mahajan — Backend Development, Voice call implementation, feature development

License

MIT

About

Chatrixz is a zero-login, anonymous encrypted group chat PWA. Users pick a display name, join/create rooms via a 4-digit code, and send AES-256-GCM encrypted messages in real-time. Features: name registration, OTP-style room entry, encrypted messaging, push notifications, PWA install, pure..

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages