Analytics & Push Notifications
CivicPulse integrates Google Analytics 4 (GA4) for usage analytics and Web Push Notifications so users can opt in to receive real-time civic alerts.
Google Analytics 4β
How It Worksβ
CivicPulse uses GA4 with the gtag.js library. The Measurement ID is injected at build time via a Vite environment variable so no secret is exposed (GA Measurement IDs are inherently public).
Events tracked automatically:
- Page views on every route change
- Report Issue button clicks
- Issue category selections
- Admin portal login events (no PII)
Setupβ
1. Create a GA4 property at analytics.google.com and copy your Measurement ID (format: G-XXXXXXXXXX).
2. Set the environment variable:
# frontend/.env.production
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
Leave this unset in .env.development to avoid polluting analytics with local traffic.
3. The frontend initialises GA automatically in src/analytics.js:
// Initialised once on app load when VITE_GA_MEASUREMENT_ID is set
export function initGA() {
const id = import.meta.env.VITE_GA_MEASUREMENT_ID;
if (!id) return;
// Loads gtag.js and configures GA4
window.dataLayer = window.dataLayer || [];
window.gtag = function () { dataLayer.push(arguments); };
gtag('js', new Date());
gtag('config', id, { anonymize_ip: true });
}
export function trackEvent(name, params = {}) {
if (typeof window.gtag !== 'function') return;
window.gtag('event', name, params);
}
Tracking Custom Eventsβ
import { trackEvent } from './analytics';
// Track a "Report Issue" button click
trackEvent('report_issue_click', { category: 'Roads', city: 'Mumbai' });
// Track admin login
trackEvent('admin_login', { role: 'admin' });
Privacy Considerationsβ
anonymize_ip: trueis set by default β IP addresses are anonymised before storage- No PII (names, emails, UUIDs) is sent to GA
- Complies with Indian IT Act data-handling guidelines
- Add a cookie banner if expanding to EU users (GDPR)
Web Push Notificationsβ
Citizens can opt in to receive browser push notifications for:
- New issues filed in their city
- Status updates on issues they've upvoted
- Civic alerts and announcements from admins
Architectureβ
Browser Backend
| |
|-- subscribe (VAPID) -------> POST /api/notifications/subscribe
| | (saves PushSubscription to DynamoDB)
| |
|<-- push event -------------- POST /api/notifications/send
| (via Web Push Protocol) | (triggered by admin or issue event)
| |
sw.js handles the event |
shows notification |
Environment Variablesβ
# backend/.env
VAPID_PUBLIC_KEY=<generate below>
VAPID_PRIVATE_KEY=<generate below>
VAPID_MAILTO=mailto:admin@civicpulse.in
Generate VAPID keys:
cd backend
node -e "const wp = require('web-push'); const keys = wp.generateVAPIDKeys(); console.log(keys);"
The public key must also be exposed to the frontend:
# frontend/.env.production
VITE_VAPID_PUBLIC_KEY=<same VAPID public key>
Service Worker (public/sw.js)β
The service worker handles incoming push events and displays notifications:
self.addEventListener('push', event => {
const data = event.data?.json() ?? {};
event.waitUntil(
self.registration.showNotification(data.title || 'CivicPulse', {
body: data.body || 'New civic update',
icon: '/icons/icon-192.png',
badge: '/icons/badge-72.png',
data: { url: data.url || '/' },
})
);
});
self.addEventListener('notificationclick', event => {
event.notification.close();
event.waitUntil(clients.openWindow(event.notification.data.url));
});
Subscribing Usersβ
// src/hooks/usePushNotifications.js
const { subscribe, unsubscribe, isSubscribed } = usePushNotifications();
// Ask user for permission and subscribe
await subscribe(); // registers SW, calls /api/notifications/subscribe
Backend APIβ
| Method | Path | Auth | Description |
|---|---|---|---|
POST | /api/notifications/subscribe | User JWT | Save a push subscription |
DELETE | /api/notifications/unsubscribe | User JWT | Remove subscription |
POST | /api/notifications/send | Admin JWT | Broadcast a push notification |
POST | /api/notifications/send-to-city | Admin JWT | Send to subscribers in a city |
Sending Notifications from Adminβ
From the admin portal (Notifications view):
- Enter a title and body message
- Optionally target a city
- Click Send notification
Alternatively, via API:
curl -X POST https://api.civicpulse.in/api/notifications/send \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"title": "New water shortage report",
"body": "12 reports filed in Andheri West today",
"url": "/issues?city=Mumbai&category=Water"
}'
DynamoDB Table β civicpulse-push-subscriptionsβ
| Attribute | Type | Description |
|---|---|---|
endpoint | String (PK) | Push endpoint URL (unique per browser/device) |
userId | String | Associated user ID (if logged in) |
city | String (opt.) | User's city preference for targeting |
keys | Map | { p256dh, auth } from PushSubscription |
createdAt | String | ISO 8601 timestamp |
Create via AWS CLI:
aws dynamodb create-table \
--table-name civicpulse-push-subscriptions \
--attribute-definitions AttributeName=endpoint,AttributeType=S \
--key-schema AttributeName=endpoint,KeyType=HASH \
--billing-mode PAY_PER_REQUEST \
--region ap-south-1
Browser Supportβ
| Browser | Support |
|---|---|
| Chrome (desktop + Android) | β Full |
| Firefox | β Full |
| Edge | β Full |
| Safari (macOS 13+, iOS 16.4+) | β Full (with limitations) |
| Samsung Internet | β Full |
iOS note: Web Push on iOS requires Safari 16.4+ and the user must add the site to their Home Screen for notifications to work without opening the browser.