How the Meetily Analytics Module Tracks Usage Patterns While Maintaining Privacy
The Meetily analytics subsystem gathers essential usage metrics like session duration and feature adoption through a privacy-first architecture that stores data locally, uses random anonymous identifiers, and never transmits meeting content or personally identifiable information.
The analytics module in Zackriya-Solutions/meetily demonstrates how desktop applications can understand user behavior without compromising privacy. By combining local-only storage, anonymized identifiers, and strict opt-in controls, the system captures high-level usage patterns while ensuring sensitive meeting data never leaves the user's device. This approach aligns with modern privacy regulations and user expectations for transparent data handling.
Architecture Overview
The analytics pipeline operates across four distinct layers, each implementing specific privacy-preserving constraints to prevent data leakage.
Frontend TypeScript Client
The TypeScript layer exposes a static Analytics class that manages initialization and event recording. Located in frontend/src/lib/analytics.ts, this module never transmits raw transcripts or meeting titles. Instead, it generates a random anonymous user ID stored locally in analytics.json and restricts all event payloads to non-PII fields such as event names, timestamps, and device counters.
Local Persistence Layer
Data storage relies on the Tauri plugin-store API, which sandboxed JSON files within the application's private storage area. Because analytics.json resides in the app’s local directory—accessible only to Meetily—no other applications can read usage statistics. This local-first approach ensures that raw audio data, transcript content, and meeting metadata remain on the device.
Tauri Rust Backend
The Rust implementation in frontend/src-tauri/src/analytics/analytics.rs handles the backend side of invoke calls including init_analytics, track_event, and disable_analytics. This layer can be compiled as a no-network stub for fully offline operation, or configured to forward only sanitized events to a self-hosted analytics endpoint that respects organizational privacy policies.
Opt-In and Opt-Out Controls
Privacy autonomy is enforced through explicit initialization requirements. The Analytics.init() method must be called to activate tracking, while Analytics.disable() immediately halts all telemetry and clears the in-memory user identifier. These controls persist the opt-out flag locally, preventing further invoke calls until the user explicitly re-enables tracking.
Anonymous User Identification
Rather than using hardware identifiers or email addresses, the system generates a cryptographically random ID once per installation. The getPersistentUserId() method in frontend/src/lib/analytics.ts implements this as follows:
static async getPersistentUserId(): Promise<string> {
const { Store } = await import('@tauri-apps/plugin-store');
const store = await Store.load('analytics.json');
let userId = await store.get<string>('user_id');
if (!userId) {
userId = `user_${Date.now()}_${Math.random().toString(36).substr(2,9)}`;
await store.set('user_id', userId);
await store.set('is_first_launch', true);
await store.save();
}
return userId;
}
This identifier contains no personally identifiable information and remains stable across application sessions while being completely dissociated from the user's identity or meeting content.
Minimal Event Payloads
All tracking methods funnel through the generic track() helper, which forwards only event names and flat property maps to the Rust backend. The implementation explicitly excludes user-generated text:
static async track(eventName: string, properties?: AnalyticsProperties): Promise<void> {
if (!this.initialized) { console.warn('Analytics not initialized'); return; }
await invoke('track_event', { eventName, properties });
}
Sanitized event examples include:
- Session start: Contains session ID, platform type, and days since last meeting—never usernames or meeting titles.
- Meeting completed: Reports duration in seconds, word count, words-per-minute calculation, and temporal patterns (day/hour), but omits the actual transcript segments.
- Feature usage: Tracks first-use flags and device capabilities without logging the content of user interactions or exported files.
Device Context Collection
The getDeviceInfo() method collects only high-level system metadata necessary for debugging platform-specific issues:
static async getDeviceInfo(): Promise<DeviceInfo> {
const platform = await this.getPlatform(); // "macOS", "Windows", "Linux"
const osVersion = await this.getOSVersion(); // User-agent derived
// Architecture detection: x86_64, aarch64, etc.
return { platform, os_version: osVersion, architecture };
}
This provides developers with essential telemetry regarding operating system distribution and hardware architectures without exposing network details, user locations, or account information.
Implementation Examples
Basic Initialization and Event Tracking
To begin collecting anonymous usage statistics, initialize the module at application startup and record specific milestones:
import Analytics from '@/analytics';
// Initialize analytics (respects user opt-in status)
await Analytics.init();
// Record meeting completion metrics
await Analytics.trackMeetingCompleted('meeting_123', {
duration_seconds: 540,
transcript_segments: 12,
transcript_word_count: 3500,
words_per_minute: 388.89,
meetings_today: await Analytics.getMeetingsCountToday(),
});
User-Controlled Opt-Out
When users disable telemetry through the application settings, the following immediately ceases all tracking:
// Toggle "Send Anonymous Usage Data" to OFF
await Analytics.disable();
Feature Adoption Tracking
Monitor feature utilization without capturing sensitive content:
await Analytics.trackFeatureUsedEnhanced('transcript_export', {
export_format: 'pdf',
});
This automatically appends is_first_use, current platform, and OS version to the payload, enabling product teams to understand adoption curves across different operating systems.
Summary
- Local-only storage: All analytics data resides in
analytics.jsonusing Tauri’s plugin-store, preventing unauthorized external access. - Anonymous identifiers: The
getPersistentUserId()function generates random strings containing no PII, stored once per installation. - Opt-in architecture: The
Analyticsclass requires explicitinit()calls and supports immediatedisable()invocations that persist user preferences. - Sanitized payloads: Event tracking deliberately excludes meeting transcripts, audio data, titles, and user-generated content, limiting telemetry to numerical metrics and system context.
- Cross-platform metadata: Device information is limited to platform type, OS version, and CPU architecture to diagnose compatibility issues without identifying individuals.
Frequently Asked Questions
Does Meetily analytics collect meeting transcripts or audio recordings?
No. According to the source code in frontend/src/lib/analytics.ts, the module explicitly excludes all meeting content from telemetry. While it may count words or track that a transcript was exported, the actual text, audio data, and meeting titles never enter the analytics pipeline.
How is the anonymous user ID generated and stored?
The ID is generated using Date.now() and Math.random() concatenated into a random string prefixed with "user_", then persisted in the local analytics.json file via the Tauri store plugin. This occurs in the getPersistentUserId() method and creates a stable but completely anonymous identifier that cannot be traced back to the user's identity.
Can users completely disable analytics tracking?
Yes. Users can call Analytics.disable() at any time, which invokes the disable_analytics Tauri command, sets the initialized flag to false, and clears the current user ID from memory. This state persists across application restarts, ensuring no further events are recorded until explicitly re-enabled.
What device information does the analytics module collect?
The system collects only high-level technical metadata through getDeviceInfo(), including the operating system platform (macOS, Windows, Linux), OS version string derived from the user agent, and CPU architecture (x86_64 or aarch64). This data helps developers optimize performance for specific platforms without revealing network locations, IP addresses, or hardware serial numbers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →