Meeting Detection Engine
A macOS meeting detection engine for Electron and Node.js applications. Built with Rust and exposed to JavaScript via napi-rs.
Features
- ✅ Two-tier decision tree algorithm for accurate detection
- ✅ Event-based API (
onMeetingStart,onMeetingEnd) - ✅ Simple JavaScript API
Supported Platforms & Services
| Service | Native App | Browser | Detection Method |
|---|---|---|---|
| Zoom | ✅ | ✅ | Tier 1: Network connections (UDP port 8801) Tier 2: URL patterns ( zoom.us/j/, zoom.us/s/) |
| Google Meet | ❌ | ✅ | Tier 2: URL patterns with meeting code validation (meet.google.com/xxx-yyyy-zzz) |
| Microsoft Teams | ✅ | ✅ | Tier 1: Network connections (STUN/TURN ports) Tier 2: URL patterns ( teams.live.com/v2/, teams.microsoft.com/_#/meet/) |
| Webex | ✅ | ✅ | Tier 1: Network connections (video ports) Tier 2: URL patterns ( *.webex.com/webapp/, *.webex.com/meet/) |
Supported Operating Systems:
- macOS ✅ (Intel and Apple Silicon)
Installation
npm install meeting-detection
The package will automatically build native binaries for your platform during installation.
Usage
Basic Example
const { isMeetingActive, onMeetingStart, onMeetingEnd, init, getLastDetectionDetails, } = require("meeting-detection"); // Initialize the engine (starts background polling every 2 seconds) init(); // Check current status const active = isMeetingActive(); console.log("Meeting active:", active); // Get detailed detection information const details = getLastDetectionDetails(); if (details) { console.log("App:", details.appName); console.log("Reason:", details.reason); if (details.meetingUrl) { console.log("URL:", details.meetingUrl); } } // Listen for meeting start onMeetingStart((_, details) => { console.log("🎥 Meeting started!"); console.log("App:", details.appName); // Do something when meeting starts }); // Listen for meeting end onMeetingEnd((_, details) => { console.log("✅ Meeting ended!"); // Do something when meeting ends });
Electron Example
const { app, BrowserWindow } = require("electron"); const { isMeetingActive, onMeetingStart, onMeetingEnd, init, } = require("meeting-detection"); app.whenReady().then(() => { init(); // Update UI when meeting status changes onMeetingStart((_, details) => { console.log("User is in a meeting:", details.appName); // Update your app UI }); onMeetingEnd((_, details) => { console.log("User is no longer in a meeting"); // Update your app UI }); // Poll current status setInterval(() => { const active = isMeetingActive(); updateStatusIndicator(active); }, 5000); });
API Reference
init()
Initialize the meeting detection engine. This starts the background polling (every 2 seconds).
Note: Call this once when your app starts.
isMeetingActive(): boolean
Returns true if a meeting is currently detected, false otherwise.
Example:
const active = isMeetingActive(); if (active) { console.log("User is in a meeting"); }
onMeetingStart(callback: (error: null, details: JsDetectionDetails) => void): void
Register a callback that will be called when a meeting is detected to start. The callback follows Node.js error-first convention: first parameter is always null (no error), second parameter contains the detection details.
Example:
onMeetingStart((error, details) => { // error is always null, details contains the detection information console.log("Meeting started!"); console.log("App:", details.appName); console.log("Reason:", details.reason); // Your logic here }); // Or ignore the error parameter: onMeetingStart((_, details) => { console.log("Meeting started!"); console.log("App:", details.appName); });
onMeetingEnd(callback: (error: null, details: JsDetectionDetails) => void): void
Register a callback that will be called when a meeting is detected to end. The callback follows Node.js error-first convention: first parameter is always null (no error), second parameter contains the detection details.
Example:
onMeetingEnd((error, details) => { // error is always null, details contains the detection information console.log("Meeting ended!"); // Your logic here }); // Or ignore the error parameter: onMeetingEnd((_, details) => { console.log("Meeting ended!"); });
getLastDetectionDetails(): JsDetectionDetails | null
Get detailed information about the last detection cycle. Useful for debugging and understanding why the engine thinks a meeting is active or not.
Returns:
-
nullif no detection has been performed yet -
JsDetectionDetailsobject (see Type Reference below)
Example:
const details = getLastDetectionDetails(); if (details) { console.log("Active:", details.active); console.log("App:", details.appName); console.log("Reason:", details.reason); }
Type Reference
JsDetectionDetails
The detection details object returned by callbacks and getLastDetectionDetails(). This type is exported and can be imported in TypeScript projects.
interface JsDetectionDetails { active: boolean; // Whether a meeting is currently active score: number; // Legacy scoring (for backward compatibility) appName?: string; // Name of the meeting app (e.g., "Zoom", "Safari", "Chrome") reason: string; // Detection reason (e.g., "NativeAppWithNetwork(Zoom)", "BrowserWithMeetingUrl(Safari)") meetingUrl?: string; // Meeting URL if detected in browser signals: SignalsBreakdown; // Breakdown of detection signals } interface SignalsBreakdown { meetingApp: SignalDetails; meetingWindow: SignalDetails; microphone: SignalDetails; camera: SignalDetails; } interface SignalDetails { active: boolean; weight: number; }
Properties:
-
active:boolean- Whether a meeting is currently active -
appName:string | undefined- Name of the meeting app (e.g., "Zoom", "Safari", "Chrome", "Microsoft Edge") -
reason:string- Detection reason:-
"NativeAppWithNetwork(Zoom)"- Native app detected with active network connections -
"BrowserWithMeetingUrl(Safari)"- Browser tab with meeting URL detected -
"None"- No meeting detected
-
-
meetingUrl:string | undefined- Full meeting URL if detected in browser (e.g.,"https://meet.google.com/abc-def-ghi") -
score:number- Legacy scoring system (kept for backward compatibility, not used in decision logic) -
signals:SignalsBreakdown- Breakdown of individual detection signals (for debugging)
Example:
import { JsDetectionDetails } from "meeting-detection"; onMeetingStart((_, details: JsDetectionDetails) => { if (details.active) { console.log(`Meeting active in ${details.appName}`); if (details.meetingUrl) { console.log(`URL: ${details.meetingUrl}`); } } });
How It Works
The engine uses a two-tier decision tree to accurately detect active meetings:
Tier 1: Native Meeting Apps (Zoom, Teams desktop, Webex desktop)
- Detects if a native meeting app process is running
- Checks for active network connections to meeting domains
- Decision: If network activity detected → MEETING ACTIVE
Tier 2: Browser-Based Meetings (Google Meet, Teams web, Webex web)
- Detects if a browser has a tab with a meeting URL
- Validates meeting URLs (e.g., Google Meet codes must match
xxx-yyyy-zzzformat) - Decision: If valid meeting URL found → MEETING ACTIVE
Detection Signals
- Process Detection: Checks if known meeting applications are running (Zoom, Teams, Webex)
-
Network Connection Detection: Monitors active connections to meeting service domains and ports
- Zoom: UDP port 8801, connections to
zoom.us - Teams: STUN/TURN ports, connections to
teams.microsoft.com - Webex: Video ports, connections to
webex.com
- Zoom: UDP port 8801, connections to
- Browser Tab Detection: Uses AppleScript to retrieve URLs from Chrome, Safari, and Edge tabs
-
URL Pattern Matching: Validates meeting URLs against known patterns
- Google Meet:
meet.google.com/xxx-yyyy-zzz(with code validation) - Teams:
teams.live.com/v2/,teams.microsoft.com/_#/meet/ - Webex:
*.webex.com/webapp/,*.webex.com/meet/
- Google Meet:
Permissions
macOS
The app may need the following permissions:
-
Accessibility: For browser tab URL detection (System Preferences → Security & Privacy → Accessibility)
- Required for AppleScript to access browser tabs
- Grant permission to Terminal/Node.js when prompted
Note: The engine does not require microphone or camera permissions. It detects meetings through network activity and browser tabs.
Limitations
- macOS only: Currently supports macOS (Intel and Apple Silicon) only
- Browser detection: Requires Accessibility permission for AppleScript to access browser tabs
-
Network detection: Uses
lsofcommand which may require appropriate system permissions - Polling interval: Detection runs every 2 seconds (not real-time)
Roadmap
- [ ] Windows support
- [ ] Linux support
- [ ] More meeting apps support (Jitsi, BlueJeans, etc.)
- [ ] Configurable polling interval
- [ ] Performance optimizations
- [ ] Real-time detection (event-driven instead of polling)
License
MIT
Contributing
Contributions welcome! This is v1, so there's plenty of room for improvement.
Support
For issues and questions, please open an issue on GitHub.