An anonymous, open-source community safety platform for real-time ICE, Army, and Police sighting alerts.
Defroster is a Progressive Web App (PWA) that empowers communities to stay informed and connected through anonymous, location-based safety alerts. Users can report sightings with a single tap, and nearby community members receive instant notificationsβall while maintaining complete privacy and anonymity.
Defroster was created in response to the October 2, 2024 federal raid on a South Shore Drive apartment building in Chicago, where families were forcibly removed from their homes during a multi-agency operation involving Border Patrol, the FBI, and the ATF.
This is open-source software. We encourage anyone to use, modify, and adapt this codebase to create similar platforms tailored to their own community's needs. Whether you're organizing mutual aid networks, neighborhood watch programs, or other community safety initiatives, this platform provides the foundation you need.
- π No personal data stored - ever
- π Location randomization to nearest city block (~250 feet / ~76 meters)
- π Anonymous device IDs using UUID v4
- ποΈ Auto-deletion: Reports removed from server after 1 day, from device after 1 week
- π« No user accounts or authentication required
- π± Push notifications for sightings within 5 miles
- πΊοΈ Interactive map with age-based opacity (older reports fade)
- π List view of nearby sightings with timestamps
- β‘ Instant reporting - one tap to alert your community
- π Progressive Web App (PWA) - installable on any device
- π Multilingual - English & Spanish with automatic browser detection
- π΄ Offline-capable with IndexedDB caching
- π Real-time synchronization via Firebase Cloud Messaging
- π¨ Modern UI with Tailwind CSS and responsive design
- ποΈ Modular architecture with abstraction layers
- π Easy provider switching (Firebase β MongoDB, PostgreSQL, etc.)
- π§ͺ Comprehensive test coverage (93 tests passing)
- π TypeScript for type safety
- π― Clean code with ESLint and best practices
- Node.js 20+ and npm
- A Firebase account (free tier works)
- Basic knowledge of Next.js and React
git clone https://github.com/erichchampion/defroster.git
cd defroster
npm install- Create a new Firebase project at Firebase Console
- Enable Firestore Database:
- Go to Project Settings β Build β Firestore Database β Create Database
- Start in production mode
- Enable Cloud Messaging:
- Go to Project Settings β Cloud Messaging
- Enable Cloud Messaging API
- Generate Web Push Certificate:
- In Cloud Messaging settings, scroll to "Web Push certificates"
- Click "Generate key pair"
- Save the VAPID key
- Get Web App Configuration:
- Go to Project Settings β General
- Under "Your apps", click "Web" (</> icon)
- Register your app and copy the config
Create .env.local from the example:
cp .env.local.example .env.localEdit .env.local with your Firebase credentials:
# Firebase Web Configuration
NEXT_PUBLIC_FIREBASE_API_KEY=AIza...
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=your-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=your-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=your-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NEXT_PUBLIC_FIREBASE_APP_ID=1:123456789:web:abc123
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID=G-ABC123
NEXT_PUBLIC_FIREBASE_VAPID_KEY=BN4bX... (from step 4 above)
# Base URL for origin validation (REQUIRED)
NEXT_PUBLIC_BASE_URL=http://localhost:3000
# Generate secure CRON secret (run: openssl rand -hex 32)
CRON_SECRET=your_64_character_hex_key
# Upstash Redis for distributed rate limiting (optional for development, required for production)
# UPSTASH_REDIS_REST_URL=https://...upstash.io
# UPSTASH_REDIS_REST_TOKEN=...
# For production (optional for development)
FIREBASE_SERVICE_ACCOUNT_KEY={"type":"service_account",...}For distributed rate limiting across serverless instances:
- Go to Upstash Console
- Create a new Redis database (free tier available)
- Click on your database to view details
- Copy the REST API credentials:
UPSTASH_REDIS_REST_URLUPSTASH_REDIS_REST_TOKEN
- Add these to your
.env.localfile
Why Upstash Redis is required:
- In-memory rate limiting doesn't work in serverless (Vercel, AWS Lambda)
- Each serverless instance would have separate counters
- Attackers could bypass rate limits by hitting different instances
- Upstash Redis provides shared state across all instances
Development note: The app will run without Upstash Redis (rate limiting disabled), but you'll see warnings. This is acceptable for local development.
Pricing: Upstash offers a generous free tier with 10,000 commands per day.
For local development, the app will work without the Admin SDK for notifications. For full functionality:
- Go to Firebase Console β Project Settings β Service Accounts
- Click "Generate new private key"
- Download the JSON file
- Copy the entire JSON content and add to
.env.local:
FIREBASE_SERVICE_ACCOUNT_KEY='{"type":"service_account","project_id":"your-project",...}'Note: For development, you can skip this and notifications will be simulated client-side.
Deploy the security rules:
# Install Firebase CLI if you haven't
npm install -g firebase-tools
# Login to Firebase
firebase login
# Initialize Firebase (select existing project)
firebase init firestore
# Deploy security rules
firebase deploy --only firestore:rulesThe firestore.rules file is already configured with secure rules:
- β Messages: Public read, server-write only
- β Devices: No client access (Admin SDK only)
Firestore will prompt you to create indexes when you first query. Alternatively, create them manually:
- Go to Firestore Console β Indexes
- Create composite index for
messages:- Collection ID:
messages - Fields:
geohash(Ascending),expiresAt(Ascending)
- Collection ID:
- Create composite index for
devices:- Collection ID:
devices - Fields:
geohash(Ascending),updatedAt(Ascending)
- Collection ID:
npm run devOpen http://localhost:3000 and grant location permissions.
-
Push to GitHub:
git add . git commit -m "Initial Defroster setup" git push origin main
-
Deploy to Vercel:
- Go to vercel.com and sign in with GitHub
- Click "New Project" and import your repository
- Add all environment variables from
.env.local - Click "Deploy"
-
Configure Custom Domain (Optional):
- In Vercel project settings β Domains
- Add your custom domain
- Update
ALLOWED_ORIGINin environment variables
-
Set Up Automated Cleanup (Optional but recommended): (Now configured through the vercel.json file)
- In Vercel project β Settings β Cron Jobs
- Create a new cron job:
- Path:
/api/cleanup-messages - Schedule:
0 * * * *(hourly) - Add custom header:
x-cron-secret: your_cron_secret
- Path:
Make sure to add NEXT_PUBLIC_BASE_URL to your Vercel environment variables with the actual deployment URL (e.g., https://your-app.vercel.app).
-
Build the app:
npm run build
-
Deploy Cloud Functions (for cleanup):
cd functions npm install cd .. firebase deploy --only functions
-
Deploy hosting:
firebase deploy --only hosting
-
Build:
npm run build
-
Run production server:
npm start
-
Use PM2 for process management:
npm install -g pm2 pm2 start npm --name "defroster" -- start pm2 save pm2 startup -
Configure Nginx reverse proxy:
server { listen 80; server_name yourdomain.com; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } }
-
Get SSL certificate:
sudo certbot --nginx -d yourdomain.com
Run the full test suite:
npm testRun tests in watch mode:
npm run test:watchGenerate coverage report:
npm run test:coverageAll 128 tests cover:
- Component rendering and interactions
- API routes and error handling
- Service integrations (Firebase, IndexedDB)
- Hooks and state management
- Geolocation and messaging
Generate TypeDoc API documentation:
npm run docsThis generates comprehensive API documentation from TypeScript/JSDoc comments into docs/api/.
The documentation covers all abstractions, services, contexts, hooks, and utilities.
View the generated documentation by opening docs/api/index.html in your browser.
Defroster supports multiple languages with SEO-friendly, server-side rendered locale routes:
/β Redirects to/en-us(default) or/es-us(Spanish browsers)/en-usβ English version (server-rendered, crawlable)/es-usβ Spanish version (server-rendered, crawlable)
- πΊπΈ English (default) -
/en-us - πͺπΈ Spanish (espaΓ±ol) -
/es-us
Middleware (middleware.ts) detects browser language from the Accept-Language header and redirects to the appropriate locale. Each locale has:
- β Server-side rendered metadata (title, description, Open Graph, Twitter cards)
- β
Proper
<html lang="en">or<html lang="es">attributes - β
Localized content from
lib/i18n/en.jsonorlib/i18n/es.json - β SEO-friendly URLs for better search engine indexing
Example:
# English browser β redirects to /en-us
curl -H "Accept-Language: en-US" https://defroster.us/
# Spanish browser β redirects to /es-us
curl -H "Accept-Language: es-MX" https://defroster.us/- Update i18n utilities (
lib/i18n/i18n.ts):
export type Locale = 'en-us' | 'es-us' | 'fr-us'; // Add new locale
export const locales: Locale[] = ['en-us', 'es-us', 'fr-us'];- Create translation file (
lib/i18n/fr.json):
{
"app": {
"name": "Defroster",
"title": "Defroster - Signaler les Observations"
}
// ... copy structure from en.json and translate
}- Update middleware (
middleware.ts):
function getLocale(request: NextRequest): string {
const acceptLanguage = request.headers.get('accept-language');
// ... existing code ...
for (const lang of languages) {
if (lang.code.startsWith('es')) return 'es-us';
if (lang.code.startsWith('fr')) return 'fr-us'; // Add detection
}
return 'en-us';
}- Build and test:
npm run build
npm start
curl -H "Accept-Language: fr-FR" http://localhost:3000/- Open the app in Safari
- Tap the Share button (square with arrow)
- Scroll and tap "Add to Home Screen"
- Tap "Add"
- Open the app in Chrome
- Tap the menu (three dots)
- Tap "Install app" or "Add to Home Screen"
- Look for the install icon (β) in the address bar
- Click to install
- App opens in its own window
- Frontend: Next.js 15, React 19, TypeScript, Tailwind CSS
- Backend: Next.js API Routes (serverless)
- Database: Firebase Firestore with geohash indexing
- Messaging: Firebase Cloud Messaging (FCM)
- Maps: Leaflet with OpenStreetMap
- Caching: IndexedDB (Dexie.js abstraction)
- Geospatial: geofire-common for efficient radius queries
- No sender location tracking: Only sighting location is stored
- Geohash precision: 7 characters (~76m) balances utility and privacy
- Device IDs: UUID v4 instead of FCM tokens for anonymity
- No authentication: Reduces data collection and barriers to use
The codebase uses interfaces to allow easy provider switching:
IMessagingService (lib/abstractions/messaging-service.ts)
- Current: Firebase Cloud Messaging
- Swap with: OneSignal, Pusher, custom WebSocket, etc.
IDataService (lib/abstractions/data-service.ts)
- Current: Firebase Firestore
- Swap with: MongoDB, PostgreSQL+PostGIS, Supabase, etc.
IStorageService (lib/abstractions/storage-service.ts)
- Current: IndexedDB
- Swap with: LocalStorage, AsyncStorage, etc.
- Messages cached locally in IndexedDB
- Works offline with cached data
- Optimistic UI updates
- Background sync when connection returns
defroster/
βββ app/
β βββ [locale]/ # Localized routes (SSR)
β β βββ layout.tsx # Locale-specific layout with i18n metadata
β β βββ page.tsx # Main app page (client component)
β βββ api/ # API routes (serverless functions)
β β βββ cleanup-messages/ # Cron job for message cleanup
β β βββ get-messages/ # Fetch nearby messages
β β βββ register-device/ # Register for notifications
β β βββ send-message/ # Create new sighting report
β βββ components/ # React components
β β βββ LocationPermission.tsx
β β βββ MessageForm.tsx
β β βββ MessageList.tsx
β β βββ SightingMap.tsx
β βββ hooks/ # Custom React hooks
β β βββ useGeolocation.ts
β β βββ useMessaging.ts
β βββ ClientProviders.tsx # Client-side context providers
β βββ layout.tsx # Root layout (minimal)
β βββ globals.css # Global styles
βββ middleware.ts # Locale detection & redirection
βββ lib/
β βββ abstractions/ # Service interfaces
β β βββ data-service.ts
β β βββ messaging-service.ts
β β βββ storage-service.ts
β βββ constants/ # App constants
β β βββ app.ts
β β βββ colors.ts
β βββ contexts/ # React contexts
β β βββ I18nContext.tsx
β β βββ ServicesContext.tsx
β βββ firebase/ # Firebase configuration
β β βββ admin.ts
β β βββ config.ts
β βββ i18n/ # Internationalization
β β βββ en.json
β β βββ es.json
β β βββ i18n.ts
β βββ services/ # Service implementations
β β βββ fcm-messaging-service.ts
β β βββ firestore-data-service.ts
β β βββ indexeddb-storage-service.ts
β βββ types/ # TypeScript types
β β βββ message.ts
β βββ utils/ # Utility functions
β βββ geohash.ts
β βββ register-sw.ts
β βββ time-formatter.ts
β βββ time-formatter-i18n.ts
βββ public/
β βββ appicon/ # PWA icons (all sizes)
β βββ firebase-messaging-sw.js # Service worker
β βββ manifest.json # PWA manifest
βββ functions/ # Firebase Cloud Functions
β βββ src/
β βββ cleanup.ts # Scheduled cleanup function
βββ __tests__/ # Jest tests
βββ firestore.rules # Firestore security rules
βββ firebase.json # Firebase configuration
βββ package.json
β Firestore Security Rules: Server-write only for messages and devices β Origin Validation (BFF Pattern): API routes validate request origin using NEXT_PUBLIC_BASE_URL β CORS Protection: Strict origin enforcement β Input Validation: All user inputs validated with type checking and range limits β Distributed Rate Limiting: Upstash Redis-based rate limiting (5/min sends, 3/min registrations, 20/min reads) β Timing Attack Protection: Constant-time comparison for CRON secrets β Security Headers: CSP, X-Frame-Options, X-Content-Type-Options, HSTS β No XSS Vulnerabilities: React sanitizes all inputs β HTTPS Only: Enforced in production β Environment Validation: Startup checks for required configuration
This app uses the Backend-for-Frontend (BFF) pattern for security:
- Client makes requests without exposed API keys
- Next.js API Routes validate the request origin matches
NEXT_PUBLIC_BASE_URL - Server-side secrets (CRON_SECRET, Firebase Admin SDK) never exposed to clients
This prevents the critical vulnerability where API keys are embedded in client JavaScript.
β Now implemented using Upstash Redis for serverless-friendly rate limiting:
- Works across multiple serverless instances on Vercel
- Persists between deployments
- Shared state across all instances
- Automatic cleanup via Redis TTL
- Simple configuration with REST API
Setup required:
- Create account at Upstash Console
- Create a Redis database
- Copy REST API credentials to environment variables
- See Setup section above
How it works:
// Rate limiting uses Upstash Redis REST API
import { Redis } from '@upstash/redis';
const count = await redis.incr(`ratelimit:${clientId}`);
if (count === 1) {
await redis.expire(key, windowSeconds);
}Fallback behavior: If Upstash Redis is not configured, rate limiting is disabled (with warnings). This allows development without Redis, but production deployments should always configure Upstash Redis.
- Never commit
.env.localto version control - Use secrets management (Vercel Environment Variables, AWS Secrets Manager, etc.)
- Rotate CRON_SECRET regularly (recommended: quarterly)
- Ensure
NEXT_PUBLIC_BASE_URLmatches your production domain exactly - Required for production: Configure Upstash Redis for distributed rate limiting
Set up monitoring for:
- Unusual spike in API requests (potential abuse)
- 403 errors (origin validation failures)
- 429 errors (rate limit hits)
- Firestore quota usage
- Failed notification deliveries
Recommended tools:
- Error tracking: Sentry, LogRocket
- Uptime monitoring: Better Uptime, UptimeRobot
- Firestore alerts: Firebase Console β Usage and billing
- Security rules are already configured (see
firestore.rules) - Review rules regularly, especially if modifying data structure
- Enable audit logging in Firebase Console
- Set up billing alerts to prevent unexpected costs
- Consider Firestore backup strategy for disaster recovery
Thanks to abstraction layers, you can easily swap providers without changing business logic.
- Create MongoDB implementation:
// lib/services/mongodb-data-service.ts
import { IDataService } from '@/lib/abstractions/data-service';
export class MongoDBDataService implements IDataService {
async saveMessage(message: Message): Promise<void> {
// MongoDB implementation
}
async getNearbyMessages(location: GeoLocation, radius: number): Promise<Message[]> {
// Use MongoDB geospatial queries
}
// ... implement all interface methods
}- Update context:
// lib/contexts/ServicesContext.tsx
import { MongoDBDataService } from '@/lib/services/mongodb-data-service';
const dataService = new MongoDBDataService();That's it! No changes needed in components or hooks.
This platform is designed to be easily customized for different use cases:
-
Change Sighting Types:
- Edit
lib/types/message.tsto change from ICE/Army/Police to your needs - Update
lib/constants/colors.tsfor custom colors and emojis - Modify
lib/i18n/en.jsonandlib/i18n/es.jsonfor new labels
- Edit
-
Adjust Radius:
- Edit
lib/constants/app.tsto change default radius - Update privacy notices in translation files
- Edit
-
Custom Branding:
- Replace icons in
public/appicon/ - Update
public/manifest.jsonwith new name and description - Modify
app/layout.tsxmetadata
- Replace icons in
-
Add Features:
- Photos/videos of sightings
- Severity levels
- Custom tags or categories
- Direct messaging
- Community forums
- π₯ Healthcare Access: Report medication availability, clinic wait times
- π¨ Emergency Response: Community-organized disaster response
- π Neighborhood Watch: Report suspicious activity
- π§ Infrastructure Issues: Potholes, broken streetlights, flooding
- ποΈ Civic Engagement: Protest locations, voter registration drives
- π² Mutual Aid: Food distribution, supply sharing
- β‘ Lighthouse Score: 95+ (Performance, Accessibility, Best Practices, SEO)
- π¦ Bundle Size: ~188 kB First Load JS
- π Time to Interactive: < 2s on 4G
- π± Mobile Optimized: Responsive design, touch-friendly
Up to 10,000 users:
- β Firebase free tier sufficient
- β No changes needed
10,000 - 100,000 users:
- π‘ Upgrade to Firebase Blaze plan
- π‘ Add CDN (Vercel, Cloudflare)
- π‘ Implement caching strategy
100,000+ users:
- π§ Consider database sharding by region
- π§ Move to dedicated infrastructure
- π§ Implement advanced caching (Redis)
- π§ Use load balancer
- iOS Safari: Settings β Safari β Location β While Using the App
- Android Chrome: Settings β Site Settings β Location β Allow
- β
Check VAPID key is correct in
.env.local - β Verify Firebase Cloud Messaging is enabled
- β Check browser supports notifications (not all do)
- β Ensure HTTPS in production (required for service workers)
- β Check console for Leaflet errors
- β Verify location permissions granted
- β Check network tab for tile loading errors
# Clear Next.js cache
rm -rf .next
# Clear node modules and reinstall
rm -rf node_modules package-lock.json
npm install
# Rebuild
npm run buildMIT License - see LICENSE file for details.
You are free to:
- β Use this code for any purpose (commercial or non-commercial)
- β Modify and adapt it to your needs
- β Distribute copies
- β Sublicense
Under the condition that:
- π You include the original license and copyright notice
- Built with Next.js
- Maps powered by Leaflet and OpenStreetMap
- Icons from Heroicons
- Geospatial queries via geofire-common
- π Check this README first
- π Search existing GitHub Issues
- π¬ Open a new issue for bugs or feature requests
We welcome contributions! Whether it's:
- π Bug fixes
- β¨ New features
- π Documentation improvements
- π Translations
- π¨ UI/UX enhancements
To contribute:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
This is your platform now. Whether you're organizing community safety networks, mutual aid programs, or something entirely new - we're excited to see what you build.
Need help adapting Defroster for your community? Open an issue and we'll do our best to help.
Built something amazing with this code? Share it! We'd love to see how communities are using this platform.
Stay safe. Stay connected. Stay anonymous.
Defroster - Built by the community, for the community.