This document details the technical and architectural decisions made to enable robust, scalable, and secure multi-device push notifications in ClarityBox. The goal: seamless experience for users across all their devices, with real-time sync, strong data integrity, and future-proof extensibility.


1. Firestore Data Model & Device-Centric Notification Storage

1.1. User Document Schema

All notification-related data is centralized in the /users/{userId} document. Each user document contains:

  • pushSubscriptions: an object mapping deviceId to the push subscription payload for that device.
  • reminderSettings: the user’s daily reminder configuration (time, enabled, timezone).

Firestore Schema Example:

{
  "pushSubscriptions": {
    "device-abc123": {
      "endpoint": "https://fcm.googleapis.com/fcm/send/xxxx...",
      "keys": { "p256dh": "...", "auth": "..." }
    },
    "device-def456": { /* ... */ }
  },
  "reminderSettings": {
    "time": "18:00",
    "enabled": true,
    "timezone": "Europe/Paris"
  }
}
  • DeviceId: Each device generates a unique, persistent deviceId (stored in localStorage) to allow independent management of subscriptions per device.
  • Why this model?
    • Enables true multi-device support: users can subscribe/unsubscribe on any device without affecting others.
    • Avoids race conditions and data loss from array-based or flat-list models.
    • Scales to any number of devices per user.

1.2. Firestore Write Patterns & Merge

  • All updates to pushSubscriptions use Firestore’s { merge: true } to avoid overwriting other device entries.
  • Example (pseudo):
await setDoc(doc(db, 'users', userId), {
  pushSubscriptions: {
    [deviceId]: subscriptionData
  }
}, { merge: true })
  • This ensures atomic, device-scoped updates and prevents accidental data loss.

2. Real-Time Sync, DeviceId Management & Client Architecture

2.1. DeviceId Generation & Storage

  • On first load, each device generates a UUIDv4 and stores it in localStorage as ClarityBox-device-id.
  • This ID is used as the key in pushSubscriptions and is never exposed to other users.

2.2. Real-Time Firestore Sync

  • The client uses a centralized helper (subscribeToUserReminderSettings) that wraps Firestore’s onSnapshot for real-time updates.
  • All Firestore access is abstracted via ergonomic helpers (users-extended.ts, firestore.ts), never direct in components.
  • Example:
unsubscribeRef.current = subscribeToUserReminderSettings(user.uid, (reminderSettings) => {
  // ...update local state
})

2.3. Reminder Time Slot Optimization

  • The UI offers a grouped select (morning/afternoon/evening) with 15-minute slots, generated programmatically.
  • This design:
    • Reduces Firestore write frequency (users rarely change reminder time, and only in 15min increments).
    • Simplifies backend scheduling logic (all reminders align to 15min boundaries).
  • Example (slot generation):
Array.from({ length: 7 * 4 }, (_, i) => {
  const h = 5 + Math.floor(i / 4);
  const m = (i % 4) * 15;
  return `${String(h).padStart(2, '0')}:${String(m).padStart(2, '0')}`;
})

3. Backend, Security & API Design

3.1. Secure Firestore Access

  • All server-side logic (Next.js API routes) uses Firestore Admin SDK helpers only.
  • No sensitive data (push keys, endpoints) is ever exposed to the client or logs.
  • Example (sensitive data masked):
await setDoc(doc(db, 'users', userId), {
  pushSubscriptions: {
    [deviceId]: {
      endpoint: 'https://.../send/xxxx',
      keys: { p256dh: '***', auth: '***' }
    }
  }
}, { merge: true })

3.2. Robustness & Error Handling

  • All Firestore operations are wrapped in try/catch with detailed logging (server-side only).
  • The backend validates all input and enforces device/user isolation.

4. Modern UX, Internationalization & Extensibility

4.1. Modern, Mobile-First UX

  • The NotificationScheduler component provides a grouped, accessible time selector and clear status feedback.
  • DeviceId is shown for debugging, and offline/permission states are handled gracefully.

4.2. Full Internationalization

  • All UI strings are managed via a useDictionary hook and translation files (en.json, fr.json).
  • The system supports dynamic language switching and is ready for further locales.

4.3. Extensibility & Future-Proofing

  • The architecture supports:
    • Advanced scheduling (multiple reminders, per-device settings)
    • Analytics (per-device delivery, engagement tracking)
    • Easy migration to other notification providers

This architecture enables ClarityBox to deliver reliable, secure, and scalable multi-device notifications. Centralized data, ergonomic helpers, and a modern UX ensure maintainability and a seamless user experience, ready for future growth.