Complete Feature Reference

Everything that powers
IR Qonnect

A deep-dive into every feature β€” how it's built, why it matters, and what makes it work under the hood.

40+
Features
AES-256
Encryption
<100ms
Message Delivery
P2P
Calls
πŸ”
Authentication & Security
🌐
Google Sign-In Authentication
OAuth 2.0

IR Qonnect uses Google Sign-In as its primary authentication method, implementing the OAuth 2.0 authorization framework. When a user clicks "Sign in with Google," the app redirects them to Google's secure login page, which handles credential validation entirely outside of IR Qonnect's servers.

After successful login, Google issues an access token and an ID token. The ID token β€” a signed JWT (JSON Web Token) β€” is sent to Firebase Authentication, which verifies the signature, extracts the user's UID, email, display name, and avatar URL, then creates or updates a user record in Firebase's identity system.

IR Qonnect never sees or stores the user's Google password. The OAuth token is short-lived (typically 1 hour) and automatically refreshed by the Firebase SDK in the background without requiring the user to log in again. This eliminates the risk of password leakage and delegates credential management entirely to Google's battle-tested infrastructure.

OAuth 2.0JWTFirebase Auth
πŸ”‘
TOTP / 2FA Support
RFC 6238

Two-factor authentication adds a critical second layer of security beyond the Google Sign-In flow. IR Qonnect implements Time-based One-Time Password (TOTP) authentication following RFC 6238 β€” the same standard used by Google Authenticator, Authy, and Microsoft Authenticator.

When a user enables 2FA in their profile settings, IR Qonnect generates a random 160-bit secret key and encodes it as a Base32 string. This is displayed as both a manual code and a QR code that the user scans into their authenticator app. Each 6-digit TOTP code is derived by computing HMAC-SHA1 of the current Unix timestamp (floored to 30-second intervals) with this secret.

On each login, IR Qonnect verifies the entered code by computing the same HMAC-SHA1 client-side using the stored secret, allowing a Β±1 window (90 seconds) for clock drift. If matched, a session flag is set. This entirely prevents unauthorized access even if the user's Google account is compromised.

TOTPHMAC-SHA1Base3230s window
πŸ”’
End-to-End Encryption
ECDH + AES-256-GCM

Every message in IR Qonnect is encrypted on the sender's device before it ever touches a network connection. The encryption system is built on a hybrid cryptographic architecture: ECDH (Elliptic Curve Diffie-Hellman) for key agreement, HKDF for key derivation, and AES-256-GCM for symmetric encryption.

On first login, the Web Crypto API generates a NIST P-256 elliptic curve key pair. The private key is stored exclusively in the device's IndexedDB β€” it never leaves the device. The public key is uploaded to Firestore. To encrypt a message, the sender fetches the recipient's public key, performs ECDH to produce a 32-byte shared secret, passes it through HKDF with a random salt, producing a 256-bit AES-GCM key. Each message uses a unique 12-byte IV and the output includes a 16-byte authentication tag ensuring tamper detection.

Even if IR Qonnect's servers were fully compromised, the attacker would have only ciphertext blobs β€” mathematically useless without the user's private key. A ratchet mechanism rotates keys every 50 messages for perfect forward secrecy.

P-256AES-256-GCMHKDF12-byte IV
πŸ“‹
Secure Firestore Rules
Server-side validation

Firestore Security Rules act as a server-side firewall for the database. Every read and write request is evaluated against a set of declarative rules before it reaches the database β€” regardless of what code runs on the client side. This is IR Qonnect's last line of defense against malicious actors who might try to bypass the app's frontend.

The rules enforce strict ownership semantics: a user can only read their own profile document, only write to messages where their UID matches the senderId field, only access chat documents where their UID appears in the participants array. Group documents can only be modified by the group admin. No document is readable by unauthenticated users β€” all requests require a valid Firebase Auth token.

Rules also include field-level validation: message documents must include all required fields (encryptedText, iv, timestamp, senderId), timestamps must be the Firestore server timestamp (preventing timestamp forgery), and message size is capped at 64 KB to prevent abuse. These rules run in Google's infrastructure and cannot be bypassed by any client-side code modification.

Field validationUID matchingServer-enforced
πŸ”„
Session Management
Concurrent sessions

IR Qonnect tracks all active sessions for a user across multiple devices. When a user logs in, a session document is created in Firestore containing the device fingerprint (derived from User-Agent, platform, and a random persistent salt), the login timestamp, last active timestamp, and IP address (hashed for privacy).

The Sessions panel in Profile Settings lists all active sessions: "Chrome on Windows β€” logged in 2 days ago, last active 3 hours ago." Users can click "Log out this device" to remotely invalidate any session. This triggers a Firebase custom claim update that the remote client detects on its next request, forcing a logout and clearing all cached data.

For additional security, if a login is detected from an unrecognized device fingerprint, IR Qonnect sends an email alert via Firebase Auth's notification system. The session is still allowed (to avoid lockout), but the user is informed so they can revoke it if unauthorized. Sessions older than 90 days of inactivity are automatically purged by a scheduled Cloud Function.

Device fingerprintRemote revoke90-day TTL
πŸ—„οΈ
Cache Management System
3-tier architecture

IR Qonnect's caching system operates across three distinct layers to balance speed, persistence, and privacy. The memory cache lives in a JavaScript Map object β€” the fastest possible access (under 1ms) β€” holding the active chat's last 50 messages and current user profile, with an LRU eviction policy at 20 MB.

The session cache uses sessionStorage for data that should persist across page reloads but clear when the tab closes: unsent draft messages, active call session IDs, and UI preferences. If a user refreshes mid-draft, their message is fully restored from sessionStorage.

The persistent tier uses IndexedDB with structured object stores for messages, users, groups, and media. On startup, the app renders from IndexedDB in under 50ms, then performs a delta sync with Firestore using the last-known timestamp β€” fetching only new changes rather than entire collections. This reduces bandwidth by ~85%. On logout, all three caches are completely wiped to protect data on shared devices.

Memory LRUIndexedDBsessionStorageDelta sync
πŸ’¬
Real-Time Chat Features
⚑
Real-Time Messaging
Firestore Listeners

IR Qonnect's real-time engine uses Firestore's onSnapshot listeners β€” persistent WebSocket connections to Firebase's backend. When a chat is opened, the client attaches a listener to the messages collection filtered by chatId, ordered by timestamp descending, limited to 50. The initial snapshot renders from local cache immediately; Firestore streams fresh data in parallel.

When a new message arrives via the listener, the encrypted payload is decrypted in a Web Worker (off the main thread) to prevent any UI lag. While decryption runs, a shimmer skeleton placeholder is shown. Once ready, the message is inserted into the DOM with a smooth slide-in animation. Message delivery globally averages under 100 milliseconds β€” validated via Firebase Performance Monitoring.

The listener also handles edits, deletions, and reaction updates. When any field of a message document changes in Firestore, the listener receives a diff of only the changed fields (not the full document), minimizing bandwidth. Offline writes are queued automatically and replayed when connectivity resumes, maintaining exact timestamp ordering.

WebSocketWeb Worker<100ms
πŸ‘₯
Group Chats
Per-message key wrapping

Group chats in IR Qonnect use a hybrid encryption model designed for multi-party communication without a central key server. For each outbound group message, a fresh random AES-256 key is generated. This key encrypts the actual message content using GCM mode. Then, this one-time key is individually "wrapped" β€” encrypted using each group member's public ECDH key using an ECIES-like scheme.

The Firestore message document stores the ciphertext once, plus a map of userId β†’ wrappedKey for every member. When a member receives the message, they locate their wrapped key, unwrap it using their private ECDH key, and decrypt the message. This means the actual message is stored only once regardless of group size, but only members can decrypt their copy of the key.

When a member is removed from a group, future messages use new keys that don't include a wrapped copy for them β€” enforcing forward exclusion. When a member is added, they can only decrypt messages after their join timestamp, not historical messages. Key rotation is also triggered by membership changes, maintaining clean cryptographic boundaries.

ECIES wrappingPer-message keysMember isolation
↩️
Message Reply Feature
Threaded context

Replying to a specific message creates a threaded context that makes conversations easier to follow in fast-moving chats. When a user long-presses or swipes a message, a reply action is triggered. The app stores the replyToId (the Firestore document ID of the referenced message) and a replyPreview (a truncated, decrypted excerpt, max 80 characters) in the new outgoing message document.

When rendering, EduChat looks up the referenced message from IndexedDB first (avoiding an extra Firestore read). If found, a quoted preview block is rendered above the reply body β€” showing the original sender's name, a colored left-border indicator, and the truncated content. If the reply references a media message, the preview shows a thumbnail image instead of text.

Tapping the reply preview scrolls the chat to the original message and briefly highlights it with a fade animation. For group chats, the original sender's name is shown even if they've since left the group. The reply chain depth is intentionally limited to one level (no nested threads) to keep the UI clean and readable on mobile screens.

replyToIdIndexedDB lookupScroll animation
πŸ˜„
Emoji Reactions
Aggregate counts

Emoji reactions allow users to respond to messages expressively without cluttering the conversation with short reply messages. Each message document in Firestore contains a reactions field β€” a map where each key is a Unicode emoji and the value is an array of user IDs who selected it: { "πŸ‘": ["uid1", "uid3"], "❀️": ["uid2"] }.

When a user reacts, a Firestore arrayUnion update atomically adds their UID to the emoji's array. If they tap the same emoji again, an arrayRemove removes them β€” toggling off. The UI renders reaction bubbles below each message showing the emoji and count. If the current user has reacted with a particular emoji, that bubble is highlighted with an accent border.

Reactions update in real-time via the existing message snapshot listener β€” no separate subscription needed. The emoji picker for reactions is a compact floating panel (not the full emoji picker) showing the 6 most common reactions plus a "+" to open the full selector. On mobile, a haptic feedback pulse accompanies each reaction tap via the Vibration API.

arrayUnionReal-timeHaptic feedback
⌨️
Typing Indicators
3-second TTL

The typing indicator gives users real-time awareness that someone is composing a message. When a user types in the input field, EduChat writes a document to the typing_status/{chatId} subcollection with fields: userId, isTyping: true, and a server timestamp. This write is debounced β€” it fires once when typing starts, not on every keystroke, reducing Firestore writes significantly.

A Cloud Function with a scheduled trigger runs every 3 seconds and permanently deletes any typing documents older than 3 seconds. This ensures that if a user closes the app while the indicator is active, it automatically disappears on the recipient's screen within 3 seconds β€” no stale "User is typing..." phantom indicators.

The recipient listens to the typing_status subcollection using an onSnapshot listener. When a document appears for a userId that isn't their own, the animated typing bubble (three pulsing dots) appears in the chat footer. When the document disappears (TTL deleted or the user sent the message), the bubble vanishes. In group chats, multiple simultaneous typers are shown as "Alice and Bob are typing..."

Debounced writesCloud Function TTL3s cleanup
πŸ–ΌοΈ
Media Sharing
Compressed upload

Media sharing in IR Qonnect handles images, videos, voice notes, and arbitrary files with an optimized pipeline that balances quality and bandwidth. When a user selects an image, the app first resizes it client-side using an offscreen Canvas element β€” full images are scaled to a maximum of 1920Γ—1080 and re-encoded as WebP at 85% quality before upload, typically reducing file size by 60–70% without visible quality loss.

Uploads go to Firebase Storage using a chunked resumable upload protocol, meaning large files survive network interruptions and resume from where they stopped. A real-time progress bar is shown using the uploadBytesResumable API's state_changed event. For voice notes, the MediaRecorder API captures audio in Opus format, which produces excellent quality at low bitrates (32 kbps).

When a media message arrives, the recipient first sees a low-resolution blur placeholder (a 16Γ—16 pixel thumbnail stored inline in the Firestore document as a base64 string). The full image loads progressively in the background. Clicking opens a full-screen lightbox with pinch-to-zoom and swipe navigation between adjacent media messages.

WebP encodeResumable uploadBlur placeholder
⏱️
Disappearing Messages
Auto-delete timers

Disappearing messages give users ephemeral communication β€” content that automatically self-destructs after a configurable period. The feature can be toggled per-chat with durations of 1 hour, 24 hours, 7 days, or 30 days. When enabled, every new message in that chat receives an expiresAt Firestore Timestamp field equal to the current server timestamp plus the chosen duration.

On the server side, a Cloud Function runs on a scheduled interval (every 10 minutes) and queries for messages where expiresAt is less than the current server timestamp. These documents are permanently deleted from Firestore in batches of 500 using batched deletes, ensuring efficient cleanup even for high-traffic chats. The encrypted media files in Firebase Storage associated with expired messages are also deleted.

On the client side, a countdown timer is shown for each message in the chat β€” a small clock icon with remaining time. The client also runs a local interval that hides expired messages from the UI even before the server cleanup runs, giving users the impression of instant deletion. Enabling/changing the disappear timer is itself logged as a chat system message ("Alice set messages to disappear after 24h") for transparency.

expiresAt fieldBatch deleteStorage cleanup
πŸ“
File Sharing & Google Drive
100 MB limit

EduChat supports sharing any file type up to 100 MB via direct upload to Firebase Storage. Files are encrypted client-side before upload (using the same AES-256-GCM pipeline as messages), so storage contents are never readable by IR Qonnect's infrastructure. A virus scan step runs via a Cloud Function trigger after every upload β€” the file is passed through Google Cloud's Web Risk API and deleted if flagged.

The Google Drive integration goes further: users can share files directly from their Drive without downloading them first. When a user selects "Share from Drive," the Google Picker API opens a Drive file browser. The selected file's Drive file ID and shareable link are stored in the message document. The recipient sees a rich preview card showing the file name, type icon, size, and a "Open in Drive" button.

Drive file references are handled with the narrowest possible OAuth scope (drive.file) β€” EduChat can only access files the user explicitly selects, not their entire Drive. This prevents any inadvertent data access. File metadata is cached in IndexedDB to avoid repeated Drive API calls for the same file reference.

E2EE uploadVirus scanGoogle Picker
πŸ“ž
Calling Features
🎀
Voice & Video Calls
WebRTC P2P

Voice and video calls use WebRTC β€” the browser-native real-time communication protocol that establishes direct peer-to-peer connections without routing media through any server. When a call starts, the caller creates an RTCPeerConnection, generates an SDP (Session Description Protocol) offer describing their media capabilities (codecs, resolutions), and sends it via Firestore's signaling channel.

For voice-only calls, getUserMedia({ audio: true }) captures the microphone stream encoded with the Opus codec at 32–64 kbps β€” a codec optimized specifically for voice with excellent quality even at low bitrates and in poor network conditions. For video, H.264 or VP8 (whichever both peers support) is negotiated via SDP, starting at 720p and scaling down adaptively via RTCRtpSender.setParameters if bandwidth drops.

ICE (Interactive Connectivity Establishment) handles the complex problem of connecting two devices that may be behind NATs or firewalls. STUN servers help peers discover their public IP/port. If direct connection fails (symmetric NAT), a TURN relay server is used as a fallback. The entire media path β€” audio and video β€” is encrypted by DTLS-SRTP, WebRTC's built-in encryption layer. No media ever transits IR Qonnect's servers.

Opus codecDTLS-SRTPICE/STUN/TURN
πŸ‘₯
Group Calls
SFU mesh, 5 participants

Group calls support up to 5 participants using a mesh topology β€” each participant maintains a direct WebRTC peer connection to every other participant. For 5 people this means 10 total connections (n*(n-1)/2). While this approach demands more uplink bandwidth than a centralized server relay, it guarantees that no server ever touches the media, preserving full privacy.

Bandwidth management is critical in mesh calls. Each sender uses simulcast β€” transmitting the same stream at multiple resolutions (720p, 480p, 360p) simultaneously. Each receiver then selects which resolution to consume based on its current downlink bandwidth, detected via getStats() RTT and packet loss measurements. If a participant's uplink is congested, RTCRtpSender.setParameters dynamically lowers the maximum bitrate.

An RTCDataChannel alongside the media channels handles in-call signaling: mute/unmute events, screen share start/stop, participant join/leave, and speaker detection (who is loudest determines the "spotlight" view). When a participant drops, their stream is immediately removed and the grid layout reflows. Group call documents in Firestore expire via TTL 24 hours after the call ends.

Mesh topologySimulcastDataChannel
πŸ“‘
Call Signaling System
Firestore-based

WebRTC requires a "signaling" channel to exchange session descriptions and ICE candidates before direct media connections can be established. IR Qonnect uses Firestore as its signaling channel β€” an elegant choice because Firestore's real-time listeners deliver documents within 100ms globally, making it ideal for the time-sensitive SDP offer/answer exchange.

The call flow: Caller writes an offer SDP to call_offers/{callId} with status "pending." ICE candidates are written individually to a candidates subcollection as they're gathered. The callee's app listens to their user document for new call_offers entries. On accept, the callee writes their answer SDP and begins gathering their own ICE candidates. Both sides exchange candidates until a viable connection path is found.

Firestore rules ensure only the two participants can read/write each signaling document. After the call ends, the signaling documents are immediately deleted by a Cloud Function, and any remaining documents have a 24-hour TTL as a safety net. If the callee doesn't answer within 60 seconds, the caller's client writes status "missed" and cleans up, triggering a missed call notification on the callee's device.

SDP exchangeICE candidates24h TTL

Friend requests form the trust foundation of IR Qonnect's contact graph. Users can search for others by display name or scan their QR code. Sending a request writes a document to the friend_requests collection with status "pending," senderId, recipientId, and a timestamp. A Cloud Function triggers immediately, sending a push notification to the recipient's devices.

The recipient sees the request in their Notifications panel with Accept and Decline buttons. Accepting atomically updates the request status to "accepted" and creates reciprocal friend entries in both users' friends subcollections β€” a single Firestore batch write ensuring both operations succeed or both fail. Declining sets status to "declined" and removes the request document after 30 days.

Once friends, both users' public keys are fetched and cached, enabling E2EE for all subsequent messages. A mutual friends count is computed by intersecting both users' friend lists and displayed on profile cards. Users can also block contacts β€” blocking adds a blocklist entry, prevents future requests, and causes all listener queries to exclude the blocked user's documents.

Batch writePush triggerBlock list

The presence system gives users real-time visibility of who's available. When the app loads, the client writes "online" and a server timestamp to user_status/{uid}. Crucially, it also registers an onDisconnect hook β€” a Firestore feature that automatically writes a predefined update when the client's connection drops, even abruptly. This ensures the status correctly transitions to "offline" even if the tab crashes.

"Away" status is detected using the Idle Detection API (available in Chromium) or a fallback using mousemove, keydown, and touchstart events. After 5 minutes of inactivity, the status is updated to "away" with a yellow indicator. When activity resumes, status reverts to "online" immediately. The lastSeen timestamp is updated every 60 seconds while online, enabling "last seen X minutes ago" display for offline users.

Presence updates are propagated to all of a user's contacts via a Firestore query: each client listens to user_status documents for UIDs in their friends list (batched in groups of 30 using the "in" operator, Firestore's array-in limit). When any friend's status changes, the contact list and chat headers update in real-time without requiring any page navigation or manual refresh.

onDisconnect hookIdle Detection APIlastSeen

Users have full control over their identity on EduChat. The profile page allows editing display name, bio (max 160 characters), custom status message (like "In a meeting" or "Available"), and avatar image. Avatar updates go through the standard media pipeline: client-side resize to 256Γ—256, WebP encoding, upload to Firebase Storage under a path keyed to the user's UID, then the download URL is saved to the Firestore user document.

Profile changes propagate to contacts in real-time via Firestore listeners. When a friend updates their name or avatar, the listener event invalidates the cached profile and fetches the fresh data β€” updating their name and avatar across all chat headers, contact lists, and group member panels simultaneously without any page refresh needed.

Profiles also include a key fingerprint display: a 12-character hex string derived from the user's public ECDH key via SHA-256. Friends can compare fingerprints out-of-band (in person, via a phone call, or by scanning each other's QR codes) to verify they're not subject to a man-in-the-middle attack. A "Safety number changed" banner appears in chats when a contact's public key changes due to device reinstallation.

WebP avatarReal-time propagationKey fingerprint
🎨
UI / UX Features
πŸŒ™
Dark Mode & Themes
6 palettes Β· System sync

IR Qonnect's theming system is built on CSS custom properties (CSS variables), allowing the entire app's visual identity to switch instantly without re-rendering any components. On first launch, the app reads the system preference via window.matchMedia('(prefers-color-scheme: dark)') and applies the matching theme automatically. The user's explicit preference is saved to localStorage and Firestore, syncing across all their devices.

Beyond dark/light mode, EduChat offers 6 color palette themes: Indigo (default blue), Ocean (teal and cyan), Forest (green), Sunset (amber and orange), Rose (pink), and Midnight (deep purple). Each theme defines a complete set of ~30 CSS variables covering backgrounds, surfaces, borders, accents, shadows, and text colors. Switching themes updates all variables simultaneously, with a 200ms CSS transition on background-color and color properties creating a smooth cross-fade.

Theme changes don't require any JavaScript re-rendering β€” no React state updates, no component remounts. Only the CSS variable values change on the :root element, and all derived colors cascade automatically. Chat bubble colors, status indicators, highlight colors, and button accents all adapt to the active theme, maintaining consistent brand identity across all 6 palettes.

CSS variablesprefers-color-schemeCross-device sync
πŸ“±
Progressive Web App (PWA)
Installable Β· Offline

EduChat is a full-featured Progressive Web App β€” installable directly from the browser onto Android, iOS, Windows, and macOS without going through an app store. Installation requires no APK download, no review delay, and no app store account. A manifest.json declares the app's identity, icons (9 sizes from 72Γ—72 to 512Γ—512), theme colors, display mode (standalone β€” no browser chrome), and start URL.

The service worker (sw.js) sits between the browser and the network, implementing a stale-while-revalidate caching strategy for static assets β€” CSS, JavaScript, and fonts are served from cache instantly, then updated in the background. For API calls, a network-first strategy with a 5-second timeout ensures fresh data when online and cached fallback when offline. A custom offline page shows the last known messages with a clear "reconnecting" indicator.

Push notifications work via Firebase Cloud Messaging integration with the service worker. Notification payloads contain only a notification ID (not the message content) for privacy β€” the service worker fetches the notification details from Firestore using the current auth token. After installation, EduChat behaves identically to a native app: its own task manager entry, access to the device camera/microphone, and Lighthouse PWA scores of 98+ consistently.

manifest.jsonService WorkerOffline fallback
πŸ”­
Media Lightbox Preview
Swipe Β· Pinch-zoom

Tapping any image or video in a chat opens a full-screen lightbox overlay with gesture controls. The lightbox uses CSS transform: scale() and translate() for zoom and pan, driven by touch events (touchstart, touchmove, touchend). Pinch-to-zoom detects two-finger touch events, computes the distance ratio between gesture start and current position, and maps it to a scale value between 1Γ— and 5Γ—. Double-tap toggles between 1Γ— and 2Γ— instantly.

Swiping left/right navigates between adjacent media messages in the same chat β€” the lightbox preloads the next and previous images in the background so navigation feels instant. A transition using translateX with CSS ease-out creates a fluid swipe feel. Videos auto-play with sound when opened, with a native controls overlay for seek, volume, and fullscreen. Voice notes show a custom waveform visualizer using the Web Audio API's AnalyserNode to display real-time amplitude bars.

The lightbox also includes a download button that triggers a programmatic anchor click with the download attribute, saving the full-resolution decrypted file to the device. On Android, this goes to the Downloads folder; on iOS, it saves to the Photos library (images) or Files app (other types). Closing the lightbox with a downward swipe uses momentum detection β€” a fast flick dismisses it, a slow drag snaps back.

Pinch-zoomWeb Audio APIGesture momentum
πŸ””
Notifications
πŸ“²
Push Notifications
FCM + Service Worker

Push notifications in IR Qonnect use Firebase Cloud Messaging (FCM) integrated with the service worker, enabling message alerts even when the app isn't open. When a user grants notification permission, the browser registers with FCM and returns a unique FCM token. This token is stored in the user's Firestore document (in a tokens map keyed by device fingerprint), so the backend knows which FCM endpoint to target for each device.

When a message is sent, a Cloud Function (triggered by the Firestore write) reads all FCM tokens for the recipient's registered devices and sends a notification payload. Crucially, the payload contains only a notification ID and the sender's name β€” never the message content itself. This privacy-first design means Google's FCM infrastructure never sees private message text. The service worker receives the push event, fetches the actual notification details from Firestore using the stored auth token, then calls showNotification() with the rich content.

Clicking the notification focuses an existing EduChat window (if open) or opens a new one, then navigates directly to the relevant chat using postMessage communication between the service worker and the main thread. Notifications are batched intelligently: if a user receives 5 messages rapidly, a single summary notification ("5 new messages from Alice") is shown rather than 5 separate alerts.

FCM tokenPrivacy payloadBatching
πŸ”Š
Message Alerts & Sounds
Customizable per sender

EduChat provides granular control over notification sounds and vibration patterns. The default notification sound is a short, pleasant chime loaded as a Web Audio API buffer (AudioBuffer), decoded and cached on first use. Playing the sound is a simple AudioBufferSourceNode.start() call β€” no audio element DOM manipulation needed, making it instantaneous even on mobile.

Users can customize notification behavior per contact: a specific chat can be set to a different tone (5 built-in tones plus silent), or notifications from that contact can be completely muted for a specified duration (1 hour, 8 hours, always). These preferences are stored in localStorage (per device) and synced to Firestore for cross-device consistency. Group chats support @mention-only mode: the phone vibrates and sounds only when the user's name is specifically mentioned.

On mobile, vibration patterns use the Vibration API (navigator.vibrate([200, 100, 200])) for tactile alerts even when the phone is on silent. Priority senders (starred contacts) can be configured to always bypass Do Not Disturb using the Notification.requireInteraction flag, keeping the notification visible until explicitly dismissed. Badge counts on the app icon are updated using navigator.setAppBadge() on supported platforms.

Web Audio APIVibration APIsetAppBadge
⚑
Performance & Storage
πŸ’Ύ
Auto Backup System
Weekly Β· Google Drive

IR Qonnect's backup system provides automatic weekly exports of all chat data to the user's personal Google Drive, using OAuth with the narrowest possible scope (drive.file) β€” EduChat can only touch files it created, never browsing the user's other Drive contents. The backup data is structured as JSON containing chat metadata, participant lists, and message objects in their encrypted form β€” never decrypted plaintext, ensuring even Google cannot read the backup content.

Backup files are gzip-compressed (60–70% size reduction) and named with the user ID and timestamp. A Cloud Function handles the actual process: it reads all user data via paginated Firestore queries (500 docs per page), serializes to JSON, compresses, and uploads to Drive using a resumable upload protocol. The function runs with a 9-minute Cloud Run timeout to handle large datasets. Only the 10 most recent backups are retained; older ones are moved to Drive Trash (30-day soft delete) then permanently removed.

Restoring on a new device fetches the latest backup from Drive, verifies its SHA-256 integrity hash against metadata stored in Firestore, then streams the data back into Firestore using batched writes (500 per batch, 5-second delays to avoid quota exhaustion). During restore, existing newer data is never overwritten β€” a timestamp comparison ensures only missing historical data is imported. Decryption of message content happens entirely on the client device using the user's private key.

gzip compressionSHA-256 verifyPaginated export
πŸ”„
Offline-Friendly Structure
Background Sync Β· Queue

EduChat is designed to remain fully functional even without internet connectivity. When a user goes offline, Firestore's built-in offline persistence mode transparently serves all read queries from the local IndexedDB cache. The UI shows data immediately while a subtle "Offline mode" banner indicates the connection status. Outgoing messages sent while offline are optimistically rendered in the chat with a "pending" clock icon.

Pending messages are queued in IndexedDB using the Background Sync API β€” a service worker feature that registers a sync tag ("message-queue") and attempts to flush the queue whenever connectivity returns. This works even if the user closes the app: the service worker wakes up in the background when connectivity is detected and replays all queued messages in chronological order, preserving original timestamps. Successfully sent messages have their temporary client-side IDs replaced with server-generated IDs.

Media downloads are also handled gracefully offline: if a user attempts to open an image that isn't cached, a clear "Available when online" placeholder is shown. The Cache API stores recently accessed media for 7 days. Static assets (app shell, fonts, icons) are pre-cached during service worker installation, enabling the app to load and be navigable even with zero connectivity. The connection state is monitored via Firestore's onSnapshotsInSync callback, updating the status badge in real-time.

Background SyncFirestore offlineCache API 7d
πŸ€–
AI Features
✨
AI Assistant Integration
Claude API Β· Memory

EduChat includes an optional AI assistant that appears as a special contact in the chat list β€” indistinguishable in UI from a regular chat, but powered by Anthropic's Claude language model via a secure API integration (modules/ai.js). The assistant is designed for educational support: answering questions, explaining concepts, helping with writing, generating study materials, or providing productivity assistance.

The AI module maintains conversation memory by sending the full conversation history to the Claude API on each message. The history is stored locally in IndexedDB for the AI chat, enabling persistent multi-turn conversations that remember context from previous sessions. When the conversation grows long, a rolling window of the last 20 message pairs is sent to stay within API context limits while preserving the most relevant recent context.

Privacy is strictly maintained: only messages explicitly sent to the AI contact are transmitted to the Claude API. Personal conversations between users are never shared with any AI service. The AI contact can be hidden from the chat list or permanently deleted from Settings β€” there is no mandatory usage. Responses from the AI are streamed token-by-token using the Anthropic streaming API, displaying progressively as text appears rather than waiting for the full response, for a more natural conversational feel.

Claude APIStreamingRolling contextIndexedDB memory
πŸ—„οΈ
Backend & Infrastructure
πŸ”₯
Firebase Firestore Database
NoSQL Β· Real-time

Firestore is IR Qonnect's primary database β€” a fully managed, serverless NoSQL document store hosted on Google Cloud infrastructure. Every message, user profile, chat metadata, friend relationship, group membership, and call signaling record is stored as a Firestore document within a hierarchical collection structure. The database scales automatically from one user to millions with zero configuration changes, and Google's global edge network delivers documents in under 100ms worldwide.

The data model is carefully designed around Firestore's querying limitations: messages are stored in a top-level messages collection (not nested under chat documents) to enable efficient pagination queries. Indexes are carefully tuned β€” Firestore requires composite indexes for multi-field queries, and IR Qonnect's firestore.indexes.json defines all necessary compound indexes for the app's query patterns (chatId + timestamp, userId + status, etc.).

Firestore's atomic batch writes ensure data consistency: when a message is sent, a single batch atomically writes the message document AND updates the chat's lastMessage and unreadCount fields. This prevents states where a message appears in the chat but the chat list doesn't update (or vice versa). The database also uses Firestore's TTL feature (time-to-live policies on document fields) for automatic expiry of typing indicators, call signaling documents, and expired messages.

Compound indexesBatch writesTTL policies
πŸ–₯️
Node.js Server & Admin Routes
server.js Β· Admin API

Alongside Firebase's serverless infrastructure, EduChat runs a lightweight Node.js server (server.js) for admin-level operations that require elevated Firebase Admin SDK privileges β€” operations that cannot be safely performed from the client due to permission requirements. This server handles tasks like force-deleting user accounts (purging all associated Firestore data in cascading batches), bulk-revoking sessions, generating signed URLs for private media access, and running database migrations.

Admin routes are protected by Firebase custom claims: only users with the isAdmin: true claim in their ID token can hit /admin/* endpoints. The server verifies the incoming Firebase ID token on every request using admin.auth().verifyIdToken(), extracting the claims and rejecting requests from non-admin users with a 403. Admin actions are logged to a Firestore audit_log collection with the admin's UID, timestamp, action type, and affected user IDs.

The server also exposes health check endpoints (/health, /metrics) consumed by monitoring dashboards showing database connection status, pending queue depths, backup success rates, and error rates. Rate limiting (express-rate-limit) protects all endpoints from abuse β€” 100 requests per 15-minute window per IP for public routes, stricter limits for admin routes. The server runs in a containerized environment with automatic restart on crash.

Admin SDKCustom claimsAudit logRate limiting
✨
Extra Advanced Features
πŸ“·
QR Code Functionality
Generate Β· Scan

QR codes provide a fast, contactless way to add friends in person β€” far faster than searching by name and less error-prone than sharing IDs manually. Each user has a unique QR code generated from their UID and public key fingerprint, encoded using the qr.js library into a canvas element with customizable colors matching the active theme. The QR code is displayed at 250Γ—250 pixels in the user's profile with a share button to save it as an image.

Scanning uses the device camera via getUserMedia({ video: { facingMode: "environment" } }) to access the rear camera. Video frames are captured at 10 fps using a canvas drawImage() loop, and each frame is decoded using a WebAssembly-compiled QR decoder (faster than pure JavaScript decoders by 5–10Γ—). A bounding box overlay highlights detected QR codes before decoding to provide visual feedback.

QR codes also embed a version number so that future changes to the format can be handled with backward compatibility. Decoded QR data is validated β€” checked against expected format, UID length, and a checksum β€” before triggering a friend request. Invalid or expired QR codes show a clear error message. The QR scan view also functions as a login shortcut: scanning IR Qonnect's desktop web login QR code authenticates the mobile session, similar to WhatsApp Web.

WASM decoderCanvas captureQR auth
πŸ“’
Toast Notification System
Auto-dismiss Β· Actions

The toast notification system provides non-blocking feedback for in-app events: message sent, file uploaded, error occurred, friend request received, backup completed, and dozens of other state transitions. Toasts appear as floating cards in the bottom-right corner (bottom-center on mobile), sliding in via a CSS keyframe animation (translateY + opacity) and automatically disappearing after 4 seconds for informational messages or 8 seconds for errors.

Each toast has a type (success, error, warning, info) mapped to a distinct color and icon: green checkmark, red X, amber warning triangle, blue info circle. An optional action button can be embedded β€” for example, an "Undo" button on message deletion that triggers a Firestore restore before the deletion batch is confirmed. This undo window is exactly 5 seconds, matching the toast's visibility duration.

Toasts are queued β€” if multiple events fire in rapid succession, they stack vertically (max 3 visible simultaneously). The queue is managed as a JavaScript array; new toasts push to the end, displayed toasts shift off the front. Each toast has a thin progress bar at the bottom that counts down the visible duration, giving users clear visual indication of how long they have to act on action toasts. The system is implemented as a singleton module importable across all app components without prop drilling.

Queue systemUndo actionProgress timer
πŸ“
Back Navigation Handling
History API Β· SPA routing

As a Single Page Application (SPA), EduChat intercepts the browser's native back button to provide intuitive navigation that matches what users expect from a native app. The History API (history.pushState and history.replaceState) is used to add navigable states to the browser's history stack without actual page loads. When a user opens a chat, a state is pushed; opening a media lightbox pushes another state on top.

A popstate event listener on the window intercepts the back button press and delegates it to the app's router, which determines the appropriate navigation action: closing a lightbox, returning from a chat to the contact list, or dismissing a modal. This creates a coherent back-button experience that matches Android's hardware back button behavior β€” critical for a mobile-first app where users muscle-memorize the back gesture.

The router also handles deep linking: navigating directly to a URL like /chat/{chatId} (via a push notification tap) loads the app, waits for authentication to complete, then directly opens the specified chat. State is passed between views using the history.state object (serialized as JSON), avoiding global state management complexity. URL-based routing also enables browser bookmarking of frequently used chats and sharing links to specific conversations.

pushStatepopstate listenerDeep linking
πŸ€–
EduChat Assistant
🟒 Always here for you