# 🔥 Firebase Cloud Messaging (FCM) Push Notifications - Complete Implementation Guide

## 📋 Table of Contents

1. [Overview](#overview)
2. [File Structure](#file-structure)
3. [Setup Instructions](#setup-instructions)
4. [Frontend Integration](#frontend-integration)
5. [Testing](#testing)
6. [API Documentation](#api-documentation)
7. [Webhook Usage](#webhook-usage)
8. [Troubleshooting](#troubleshooting)

---

## 🎯 Overview

This implementation provides a complete FCM push notification system with:

- ✅ Subscribe/Unsubscribe to program notifications
- ✅ Topic-based notifications (program-{id})
- ✅ Webhook endpoints for new episodes/audio
- ✅ Background notification support
- ✅ Clean, production-ready code

---

## 📁 File Structure

```
disrupt/
├── app/
│   ├── Models/
│   │   └── FcmToken.php                 # FCM token model
│   ├── Services/
│   │   └── FirebaseService.php          # FCM API service
│   └── Http/Controllers/
│       ├── NotificationController.php   # Subscribe/Unsubscribe API
│       └── WebhookController.php        # Webhook handlers
├── database/
│   └── migrations/
│       └── 2025_12_03_000001_create_fcm_tokens_table.php
├── public/
│   ├── js/
│   │   └── fcm-notifications.js         # Frontend FCM handler
│   └── firebase-messaging-sw.js         # Service worker
├── routes/
│   └── api.php                          # API routes
└── .env.fcm.example                     # Environment variables example
```

---

## 🚀 Setup Instructions

### Step 1: Firebase Console Setup

1. **Go to Firebase Console**: https://console.firebase.google.com/
2. **Create/Select Project**
3. **Enable Cloud Messaging**:
   - Project Settings > Cloud Messaging
   - Copy your **Server Key**
4. **Get Web App Config**:
   - Project Settings > General
   - Scroll to "Your apps" > Web app
   - Copy configuration values

### Step 2: Environment Configuration

1. **Copy environment variables**:

   ```bash
   # Add to your .env file
   FCM_SERVER_KEY=YOUR_FCM_SERVER_KEY_HERE
   FIREBASE_API_KEY=YOUR_FIREBASE_API_KEY
   FIREBASE_AUTH_DOMAIN=YOUR_PROJECT_ID.firebaseapp.com
   FIREBASE_PROJECT_ID=YOUR_PROJECT_ID
   FIREBASE_STORAGE_BUCKET=YOUR_PROJECT_ID.appspot.com
   FIREBASE_MESSAGING_SENDER_ID=YOUR_MESSAGING_SENDER_ID
   FIREBASE_APP_ID=YOUR_FIREBASE_APP_ID
   FIREBASE_VAPID_PUBLIC_KEY=BPlDvtfqfzg7F9YTLwO-WGtJsrucariD1GCEenN6oayU58X0hCdRqaNfqgTZeNchkoED_uceuaOprdMX74pdQDc
   FIREBASE_VAPID_PRIVATE_KEY=HeLyHGL8l7D3Re4DXwNMlIwJ6Zd33VkZrzv7DZaBBps
   ```

2. **Update Service Worker**:
   Open `public/firebase-messaging-sw.js` and replace with your Firebase config:
   ```javascript
   const firebaseConfig = {
     apiKey: "YOUR_FIREBASE_API_KEY",
     authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
     projectId: "YOUR_PROJECT_ID",
     storageBucket: "YOUR_PROJECT_ID.appspot.com",
     messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
     appId: "YOUR_APP_ID",
   };
   ```

### Step 3: Database Migration

Run the migration to create the `fcm_tokens` table:

```bash
php artisan migrate
```

### Step 4: Clear Cache

```bash
php artisan config:clear
php artisan cache:clear
php artisan route:clear
```

---

## 🎨 Frontend Integration

### Option 1: Add to Your Layout Template

Add these scripts to your main layout blade file (e.g., `resources/views/layouts/app.blade.php`):

```html
<!DOCTYPE html>
<html>
  <head>
    <meta name="csrf-token" content="{{ csrf_token() }}" />
    <!-- Other head content -->
  </head>
  <body>
    <!-- Your content -->

    <!-- Firebase SDK -->
    <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-app-compat.js"></script>
    <script src="https://www.gstatic.com/firebasejs/9.22.0/firebase-messaging-compat.js"></script>

    <!-- Firebase Configuration -->
    <script>
      window.FIREBASE_API_KEY = "{{ env('FIREBASE_API_KEY') }}";
      window.FIREBASE_AUTH_DOMAIN = "{{ env('FIREBASE_AUTH_DOMAIN') }}";
      window.FIREBASE_PROJECT_ID = "{{ env('FIREBASE_PROJECT_ID') }}";
      window.FIREBASE_STORAGE_BUCKET = "{{ env('FIREBASE_STORAGE_BUCKET') }}";
      window.FIREBASE_MESSAGING_SENDER_ID =
        "{{ env('FIREBASE_MESSAGING_SENDER_ID') }}";
      window.FIREBASE_APP_ID = "{{ env('FIREBASE_APP_ID') }}";
    </script>

    <!-- FCM Notification Manager -->
    <script src="{{ asset('js/fcm-notifications.js') }}"></script>
  </body>
</html>
```

### Option 2: Notify Me Button Implementation

Add this to your program page (e.g., where users see program details):

```html
<!-- Notify Me / Unnotify Button -->
<button
  id="notifyBtn"
  data-program-id="{{ $program->id }}"
  class="btn btn-primary notify-button"
>
  <span class="notify-icon">🔔</span>
  <span class="notify-text">Notify Me</span>
</button>

<script>
  document.addEventListener("DOMContentLoaded", function () {
    const notifyBtn = document.getElementById("notifyBtn");
    const programId = notifyBtn.getAttribute("data-program-id");

    // Check current subscription status on page load
    fcmManager.checkSubscriptionStatus(programId).then((status) => {
      if (status.subscribed) {
        notifyBtn.querySelector(".notify-text").textContent = "Unnotify";
        notifyBtn.classList.add("subscribed");
      }
    });

    // Handle button click
    notifyBtn.addEventListener("click", async function () {
      const textSpan = this.querySelector(".notify-text");
      const isSubscribed = this.classList.contains("subscribed");

      if (isSubscribed) {
        // Unsubscribe
        const result = await fcmManager.unsubscribeFromProgram(programId);
        if (result.success) {
          textSpan.textContent = "Notify Me";
          this.classList.remove("subscribed");
        }
      } else {
        // Subscribe
        const result = await fcmManager.subscribeToProgram(programId);
        if (result.success) {
          textSpan.textContent = "Unnotify";
          this.classList.add("subscribed");
        }
      }
    });
  });
</script>

<style>
  .notify-button {
    padding: 10px 20px;
    border-radius: 5px;
    border: none;
    cursor: pointer;
    transition: all 0.3s ease;
  }

  .notify-button.subscribed {
    background-color: #28a745;
    color: white;
  }

  .notify-button:not(.subscribed) {
    background-color: #007bff;
    color: white;
  }
</style>
```

---

## 🧪 Testing

### Test 1: Subscribe to Program Notifications

1. **Open your program page** in a browser
2. **Click "Notify Me"** button
3. **Allow notifications** when prompted
4. **Check console** for success message
5. **Verify in database**: Check `fcm_tokens` table

```sql
SELECT * FROM fcm_tokens WHERE user_id = YOUR_USER_ID;
```

### Test 2: Send Test Notification

Use the test endpoint to send a notification:

```bash
curl -X POST http://your-domain.com/api/webhooks/test-notification \
  -H "Content-Type: application/json" \
  -d '{
    "program_id": 123
  }'
```

### Test 3: Webhook - New Episode

Simulate a new episode notification:

```bash
curl -X POST http://your-domain.com/api/webhooks/new-episode \
  -H "Content-Type: application/json" \
  -d '{
    "program_id": 123,
    "episode_title": "Episode 5: The Big Reveal",
    "episode_description": "Join us for the most exciting episode yet!",
    "episode_url": "https://your-domain.com/episode/5"
  }'
```

### Test 4: Webhook - New Audio

Simulate a new audio notification:

```bash
curl -X POST http://your-domain.com/api/webhooks/new-audio \
  -H "Content-Type: application/json" \
  -d '{
    "program_id": 123,
    "audio_title": "Amazing Song",
    "audio_description": "Listen to this new track!",
    "audio_url": "https://your-domain.com/audio/456",
    "artist_name": "John Doe"
  }'
```

### Test 5: Unsubscribe

1. Click **"Unnotify"** button on program page
2. Check console for success message
3. Verify in database that subscription is removed

---

## 📡 API Documentation

### 1. Save FCM Token

**Endpoint**: `POST /api/notifications/save-token`

**Headers**:

```
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
```

**Body**:

```json
{
  "token": "FCM_TOKEN_STRING",
  "device_type": "web"
}
```

**Response**:

```json
{
  "success": true,
  "message": "Token saved successfully"
}
```

---

### 2. Subscribe to Program

**Endpoint**: `POST /api/notifications/subscribe`

**Headers**:

```
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
```

**Body**:

```json
{
  "token": "FCM_TOKEN_STRING",
  "program_id": 123,
  "device_type": "web"
}
```

**Response**:

```json
{
  "success": true,
  "message": "Successfully subscribed to program notifications",
  "subscribed": true,
  "topic": "program-123"
}
```

---

### 3. Unsubscribe from Program

**Endpoint**: `POST /api/notifications/unsubscribe`

**Headers**:

```
Content-Type: application/json
Authorization: Bearer YOUR_API_TOKEN
```

**Body**:

```json
{
  "token": "FCM_TOKEN_STRING",
  "program_id": 123
}
```

**Response**:

```json
{
  "success": true,
  "message": "Successfully unsubscribed from program notifications",
  "subscribed": false,
  "topic": "program-123"
}
```

---

### 4. Get Subscription Status

**Endpoint**: `GET /api/notifications/subscription-status?program_id=123`

**Headers**:

```
Authorization: Bearer YOUR_API_TOKEN
```

**Response**:

```json
{
  "success": true,
  "subscribed": true,
  "subscription": {
    "id": 1,
    "user_id": 456,
    "token": "FCM_TOKEN",
    "topic": "program-123",
    "subscribed_at": "2025-12-03 10:30:00"
  }
}
```

---

### 5. Get My Subscriptions

**Endpoint**: `GET /api/notifications/my-subscriptions`

**Headers**:

```
Authorization: Bearer YOUR_API_TOKEN
```

**Response**:

```json
{
  "success": true,
  "subscriptions": [
    {
      "program_id": "123",
      "topic": "program-123",
      "subscribed_at": "2025-12-03 10:30:00"
    },
    {
      "program_id": "456",
      "topic": "program-456",
      "subscribed_at": "2025-12-03 11:45:00"
    }
  ]
}
```

---

## 🔗 Webhook Usage

### Integrate with Your CMS/Backend

When a new episode or audio is added to a program, send a POST request to the webhook:

**Example: PHP Integration**

```php
<?php
// When a new episode is created
$programId = 123;
$episodeTitle = "Episode 5: The Big Reveal";
$episodeDescription = "Join us for the most exciting episode yet!";
$episodeUrl = "https://your-domain.com/episode/5";

// Send webhook notification
$webhookUrl = "https://your-domain.com/api/webhooks/new-episode";
$data = [
    'program_id' => $programId,
    'episode_title' => $episodeTitle,
    'episode_description' => $episodeDescription,
    'episode_url' => $episodeUrl
];

$ch = curl_init($webhookUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'Content-Type: application/json'
]);

$response = curl_exec($ch);
curl_close($ch);

echo "Notification sent: " . $response;
```

**Example: Node.js Integration**

```javascript
const axios = require("axios");

async function sendEpisodeNotification(
  programId,
  episodeTitle,
  episodeDescription,
  episodeUrl
) {
  try {
    const response = await axios.post(
      "https://your-domain.com/api/webhooks/new-episode",
      {
        program_id: programId,
        episode_title: episodeTitle,
        episode_description: episodeDescription,
        episode_url: episodeUrl,
      }
    );

    console.log("Notification sent:", response.data);
  } catch (error) {
    console.error("Failed to send notification:", error.message);
  }
}

// Usage
sendEpisodeNotification(
  123,
  "Episode 5",
  "The Big Reveal",
  "https://your-domain.com/episode/5"
);
```

---

## 🔧 Troubleshooting

### Issue 1: Notifications not appearing

**Solution**:

- Check browser console for errors
- Verify notification permissions are granted
- Ensure service worker is registered: Check `chrome://serviceworker-internals`
- Verify Firebase config in `.env` and `firebase-messaging-sw.js`

### Issue 2: Token not saving

**Solution**:

- Check if user is authenticated
- Verify API routes are working
- Check Laravel logs: `storage/logs/laravel.log`
- Test API endpoint with Postman

### Issue 3: Webhook not triggering notifications

**Solution**:

- Verify FCM_SERVER_KEY is correct in `.env`
- Check if users are subscribed to the topic
- Review logs in `storage/logs/laravel.log`
- Test with the test notification endpoint first

### Issue 4: Service Worker errors

**Solution**:

- Ensure `firebase-messaging-sw.js` is in `public/` folder
- Clear browser cache and re-register service worker
- Check service worker console for errors
- Update Firebase SDK version if needed

### Issue 5: CORS errors

**Solution**:

- Add to `config/cors.php`:
  ```php
  'paths' => ['api/*', 'webhooks/*'],
  'allowed_methods' => ['*'],
  'allowed_origins' => ['*'],
  ```

---

## 📝 Additional Notes

### Security Recommendations

1. **Webhook Authentication**: Add authentication to webhook endpoints
2. **Rate Limiting**: Implement rate limiting on subscription endpoints
3. **Token Validation**: Validate FCM tokens before saving
4. **HTTPS**: Always use HTTPS in production

### Performance Tips

1. **Queue Notifications**: For large subscriber lists, queue notifications
2. **Batch Operations**: Use batch topic subscription for multiple tokens
3. **Cache Status**: Cache subscription status to reduce DB queries
4. **Index Database**: Ensure proper indexing on `fcm_tokens` table

### Future Enhancements

- [ ] Add notification history/logs
- [ ] Implement notification scheduling
- [ ] Add rich media notifications
- [ ] Create admin dashboard for managing notifications
- [ ] Add analytics and tracking

---

## ✅ Checklist

Before going live, ensure:

- [ ] Firebase project is configured
- [ ] All environment variables are set
- [ ] Database migration is run
- [ ] Service worker is accessible at `/firebase-messaging-sw.js`
- [ ] Frontend integration is complete
- [ ] Test notifications are working
- [ ] Webhooks are tested
- [ ] HTTPS is enabled
- [ ] Logs are monitored

---

## 🎉 You're Ready!

Your FCM push notification system is now fully implemented and ready to use!

For support or questions, check the Laravel and Firebase documentation:

- Laravel: https://laravel.com/docs
- Firebase: https://firebase.google.com/docs/cloud-messaging

Happy coding! 🚀
