# SSE Integration Guide

**Praktischer Guide** für die Integration von Server-Sent Events (SSE) in LiveComponents.

## Quick Start

Eine LiveComponent SSE-fähig zu machen erfordert **2 einfache Schritte**:

### 1. Action-Attribute hinzufügen

```php
use App\Framework\LiveComponents\Attributes\Action;

#[Action]
public function markAsRead(string $notificationId): ComponentData
{
    // ... your logic ...
}
```

### 2. getSseChannel() Methode implementieren

```php
public function getSseChannel(): string
{
    return "user:{$this->userId}";
}
```

**Das war's!** 🎉 Die Component verbindet sich automatisch zum SSE-Stream und empfängt Echtzeit-Updates.

## Step-by-Step Tutorial

### Schritt 1: Bestehende Component vorbereiten

Nehmen wir eine einfache NotificationComponent:

```php
#[LiveComponent('notifications')]
final readonly class NotificationComponent implements LiveComponentContract
{
    private ComponentId $id;
    private NotificationState $state;

    public function __construct(
        ComponentId $id,
        ?ComponentData $initialData = null,
        array $notifications = []
    ) {
        $this->id = $id;

        if ($initialData !== null) {
            $this->state = NotificationState::fromComponentData($initialData);
            return;
        }

        $this->state = new NotificationState($notifications);
    }

    public function getId(): ComponentId
    {
        return $this->id;
    }

    public function getData(): ComponentData
    {
        return $this->state->toComponentData();
    }

    public function getRenderData(): ComponentRenderData
    {
        return new ComponentRenderData(
            templatePath: 'notifications',
            data: [
                'notifications' => $this->state->notifications,
                'unread_count' => $this->state->getUnreadCount()
            ]
        );
    }

    // Action methods...
    public function markAsRead(string $notificationId): ComponentData
    {
        $newState = $this->state->withNotificationRead($notificationId);
        return $newState->toComponentData();
    }

    public function deleteNotification(string $notificationId): ComponentData
    {
        $newState = $this->state->withNotificationDeleted($notificationId);
        return $newState->toComponentData();
    }
}
```

### Schritt 2: Action-Attribute hinzufügen

Füge `#[Action]` zu allen Action-Methoden hinzu:

```php
use App\Framework\LiveComponents\Attributes\Action;

#[Action]
public function markAsRead(string $notificationId): ComponentData
{
    $newState = $this->state->withNotificationRead($notificationId);
    return $newState->toComponentData();
}

#[Action]
public function deleteNotification(string $notificationId): ComponentData
{
    $newState = $this->state->withNotificationDeleted($notificationId);
    return $newState->toComponentData();
}
```

**Warum?** Das `#[Action]` Attribute markiert Methoden als sichere, von außen aufrufbare Actions.

### Schritt 3: getSseChannel() implementieren

Wähle die passende Channel-Strategie für deinen Use Case:

#### Option A: User-Specific Channel

```php
/**
 * Get SSE channel for real-time notification updates
 *
 * Uses user-specific channel for private notifications.
 * Falls back to 'global' for guest users.
 */
public function getSseChannel(): string
{
    // Extract user ID from component instance ID
    // ComponentId format: "notifications:user-123"
    $instanceId = $this->id->instanceId;

    if ($instanceId === 'global' || $instanceId === 'guest') {
        return 'global';
    }

    // User-specific channel for private notifications
    return "user:{$instanceId}";
}
```

#### Option B: Context-Specific Channel

```php
/**
 * Get SSE channel for context-based notifications
 *
 * Examples:
 * - "notifications:post-123" → "notifications:post-123"
 * - "notifications:project-456" → "notifications:project-456"
 */
public function getSseChannel(): string
{
    $context = $this->id->instanceId;
    return "notifications:{$context}";
}
```

#### Option C: Global Channel

```php
/**
 * Get SSE channel for system-wide notifications
 */
public function getSseChannel(): string
{
    return 'global';
}
```

### Schritt 4: Testen

Die Component ist jetzt SSE-fähig! Teste es:

```php
// Test-Script
use App\Framework\LiveComponents\ValueObjects\ComponentId;

$component = new NotificationComponent(
    id: ComponentId::fromString('notifications:user-123')
);

// Check if getSseChannel() exists and returns correct channel
$channel = $component->getSseChannel();
assert($channel === 'user:user-123');

// Check HTML rendering includes data-sse-channel attribute
$html = $componentRegistry->renderWithWrapper($component);
assert(str_contains($html, 'data-sse-channel="user:user-123"'));
```

### Schritt 5: Verifizieren im Browser

1. **Öffne Browser DevTools** (F12)
2. **Network Tab** → Filter: "EventSource" oder "eventsource"
3. **Lade die Seite** mit deiner Component
4. **Prüfe**: Du solltest eine SSE-Verbindung zu `/sse/events/{channel}` sehen
5. **Trigger Action**: Klicke auf einen Button der eine Action aufruft
6. **Prüfe Console**: Du solltest SSE-Events sehen

```
[LiveComponent] Setting up SSE for notifications:user-123 on channel user:user-123
[SSE] Connected to user:user-123
[LiveComponent] SSE update received for notifications:user-123
```

## Channel-Strategien

### 1. Global Channel

**Verwendung**: System-weite Updates für alle Benutzer

```php
public function getSseChannel(): string
{
    return 'global';
}
```

**Wann verwenden?**
- ✅ Public Activity Feeds
- ✅ System Announcements
- ✅ Global Statistics
- ❌ User-spezifische Daten

**Beispiel**: Public Activity Feed

```php
#[LiveComponent('activity-feed')]
final readonly class ActivityFeedComponent implements LiveComponentContract
{
    public function getSseChannel(): string
    {
        // Alle Benutzer sehen dasselbe Feed
        return 'global';
    }
}
```

### 2. User-Specific Channels

**Verwendung**: Benutzer-spezifische Updates

```php
public function getSseChannel(): string
{
    $instanceId = $this->id->instanceId; // z.B. "user-123"

    if ($instanceId === 'guest' || $instanceId === 'global') {
        return 'global';
    }

    return "user:{$instanceId}";
}
```

**Wann verwenden?**
- ✅ Private Benachrichtigungen
- ✅ Personal Dashboards
- ✅ User-spezifische Activity Feeds
- ❌ Public Daten

**Beispiel**: Private Notification Center

```php
#[LiveComponent('notification-center')]
final readonly class NotificationCenterComponent implements LiveComponentContract
{
    public function getSseChannel(): string
    {
        $instanceId = $this->id->instanceId;

        if ($instanceId === 'global' || $instanceId === 'guest') {
            return 'global'; // Fallback für Gäste
        }

        // User-spezifischer Channel: user:user-123
        return "user:{$instanceId}";
    }
}
```

### 3. Context-Based Channels

**Verwendung**: Kontext-spezifische Updates (Posts, Articles, Rooms)

```php
public function getSseChannel(): string
{
    $context = $this->id->instanceId; // z.B. "post-123", "article-456"
    return "comments:{$context}";
}
```

**Wann verwenden?**
- ✅ Post/Article Comments
- ✅ Document Collaboration
- ✅ Context-specific Updates
- ❌ User-übergreifende Updates

**Beispiel**: Comment Thread

```php
#[LiveComponent('comment-thread')]
final readonly class CommentThreadComponent implements LiveComponentContract
{
    public function getSseChannel(): string
    {
        // Extract context from instance ID
        // "comment-thread:post-123" → "comments:post-123"
        $context = $this->id->instanceId;
        return "comments:{$context}";
    }
}
```

### 4. Presence Channels

**Verwendung**: Real-time Präsenz-Tracking

```php
public function getSseChannel(): string
{
    $room = $this->id->instanceId; // z.B. "lobby", "room-5"
    return "presence:{$room}";
}
```

**Wann verwenden?**
- ✅ Live Presence Indicators
- ✅ Active User Lists
- ✅ Collaborative Editing Sessions
- ❌ Asynchrone Updates

**Beispiel**: Live Presence

```php
#[LiveComponent('live-presence')]
final readonly class LivePresenceComponent implements LiveComponentContract
{
    public function getSseChannel(): string
    {
        // Extract room from instance ID
        // "live-presence:lobby" → "presence:lobby"
        $room = $this->id->instanceId;
        return "presence:{$room}";
    }
}
```

## Beispiel-Implementierungen

### Beispiel 1: Notification Center (User-Specific)

```php
use App\Framework\LiveComponents\Attributes\Action;
use App\Framework\LiveComponents\Attributes\LiveComponent;
use App\Framework\LiveComponents\Contracts\LiveComponentContract;
use App\Framework\LiveComponents\ValueObjects\ComponentData;
use App\Framework\LiveComponents\ValueObjects\ComponentId;
use App\Framework\LiveComponents\ValueObjects\ComponentRenderData;

#[LiveComponent('notification-center')]
final readonly class NotificationCenterComponent implements LiveComponentContract
{
    private ComponentId $id;
    private NotificationCenterState $state;

    public function __construct(
        ComponentId $id,
        ?ComponentData $initialData = null,
        array $notifications = []
    ) {
        $this->id = $id;

        if ($initialData !== null) {
            $this->state = NotificationCenterState::fromComponentData($initialData);
            return;
        }

        $this->state = new NotificationCenterState($notifications);
    }

    public function getId(): ComponentId
    {
        return $this->id;
    }

    public function getData(): ComponentData
    {
        return $this->state->toComponentData();
    }

    public function getRenderData(): ComponentRenderData
    {
        return new ComponentRenderData(
            templatePath: 'notification-center',
            data: [
                'notifications' => $this->state->notifications,
                'unread_count' => $this->state->getUnreadCount()
            ]
        );
    }

    #[Action]
    public function markAsRead(string $notificationId): ComponentData
    {
        $newState = $this->state->withNotificationRead($notificationId);
        return $newState->toComponentData();
    }

    #[Action]
    public function deleteNotification(string $notificationId): ComponentData
    {
        $newState = $this->state->withNotificationDeleted($notificationId);
        return $newState->toComponentData();
    }

    #[Action]
    public function markAllAsRead(): ComponentData
    {
        $newState = $this->state->withAllMarkedAsRead();
        return $newState->toComponentData();
    }

    /**
     * Get SSE channel for real-time notifications
     *
     * Uses user-specific channel for private notifications.
     * Falls back to 'global' for guest users.
     */
    public function getSseChannel(): string
    {
        $instanceId = $this->id->instanceId;

        if ($instanceId === 'global' || $instanceId === 'guest') {
            return 'global';
        }

        return "user:{$instanceId}";
    }
}
```

### Beispiel 2: Comment Thread (Context-Specific)

```php
#[LiveComponent('comment-thread')]
final readonly class CommentThreadComponent implements LiveComponentContract
{
    private ComponentId $id;
    private CommentThreadState $state;

    public function __construct(
        ComponentId $id,
        ?ComponentData $initialData = null,
        array $comments = []
    ) {
        $this->id = $id;

        if ($initialData !== null) {
            $this->state = CommentThreadState::fromComponentData($initialData);
            return;
        }

        $this->state = new CommentThreadState($comments);
    }

    public function getId(): ComponentId
    {
        return $this->id;
    }

    public function getData(): ComponentData
    {
        return $this->state->toComponentData();
    }

    public function getRenderData(): ComponentRenderData
    {
        return new ComponentRenderData(
            templatePath: 'comment-thread',
            data: [
                'comments' => $this->state->buildCommentTree(),
                'total_comments' => count($this->state->comments)
            ]
        );
    }

    #[Action]
    public function addComment(
        string $content,
        string $authorId,
        string $authorName,
        ?string $parentId = null
    ): ComponentData {
        $newState = $this->state->withCommentAdded($content, $authorId, $authorName, $parentId);
        return $newState->toComponentData();
    }

    #[Action]
    public function deleteComment(string $commentId): ComponentData
    {
        $newState = $this->state->withCommentDeleted($commentId);
        return $newState->toComponentData();
    }

    /**
     * Get SSE channel for real-time comment updates
     *
     * Uses context-based channel for post/article-specific comments.
     *
     * Examples:
     * - "comment-thread:post-123" → "comments:post-123"
     * - "comment-thread:article-456" → "comments:article-456"
     */
    public function getSseChannel(): string
    {
        $context = $this->id->instanceId;
        return "comments:{$context}";
    }
}
```

### Beispiel 3: Activity Feed (Context or User)

```php
#[LiveComponent('activity-feed')]
final readonly class ActivityFeedComponent implements LiveComponentContract
{
    private ComponentId $id;
    private ActivityFeedState $state;

    // ... constructor, getId(), getData(), getRenderData() ...

    #[Action]
    public function addActivity(
        string $activityType,
        string $userId,
        string $userName,
        string $content
    ): ComponentData {
        // ... implementation ...
    }

    #[Action]
    public function markAsRead(string $activityId): ComponentData
    {
        // ... implementation ...
    }

    #[Action]
    public function setFilter(string $filter): ComponentData
    {
        // ... implementation ...
    }

    /**
     * Get SSE channel for real-time activity updates
     *
     * Uses context-based channel for flexible activity tracking.
     *
     * Examples:
     * - "activity-feed:user-123" → "activity:user-123" (user-specific)
     * - "activity-feed:global" → "activity:global" (public feed)
     * - "activity-feed:team-5" → "activity:team-5" (team-specific)
     */
    public function getSseChannel(): string
    {
        $context = $this->id->instanceId;
        return "activity:{$context}";
    }
}
```

### Beispiel 4: Live Presence (Presence Channel)

```php
#[LiveComponent('live-presence')]
final readonly class LivePresenceComponent implements LiveComponentContract
{
    private ComponentId $id;
    private LivePresenceState $state;

    // ... constructor, getId(), getData(), getRenderData() ...

    #[Action]
    public function userJoined(string $userId, string $userName): ComponentData
    {
        $newState = $this->state->withUserJoined($userId, $userName);
        return $newState->toComponentData();
    }

    #[Action]
    public function userLeft(string $userId): ComponentData
    {
        $newState = $this->state->withUserLeft($userId);
        return $newState->toComponentData();
    }

    /**
     * Get SSE channel for real-time presence updates
     *
     * Uses presence-scoped channel for room-based tracking.
     *
     * Examples:
     * - "live-presence:lobby" → "presence:lobby"
     * - "live-presence:room-5" → "presence:room-5"
     * - "live-presence:global" → "presence:global"
     */
    public function getSseChannel(): string
    {
        $room = $this->id->instanceId;
        return "presence:{$room}";
    }
}
```

## Testing

### Unit Tests

Test die getSseChannel() Methode:

```php
use App\Framework\LiveComponents\ValueObjects\ComponentId;

it('returns correct SSE channel for user', function () {
    $component = new NotificationCenterComponent(
        id: ComponentId::fromString('notification-center:user-123')
    );

    expect($component->getSseChannel())->toBe('user:user-123');
});

it('returns global channel for guest users', function () {
    $component = new NotificationCenterComponent(
        id: ComponentId::fromString('notification-center:guest')
    );

    expect($component->getSseChannel())->toBe('global');
});
```

### Integration Tests

Test die vollständige SSE-Integration:

```php
it('component renders with data-sse-channel attribute', function () {
    $component = new NotificationCenterComponent(
        id: ComponentId::fromString('notification-center:user-123')
    );

    $html = $this->componentRegistry->renderWithWrapper($component);

    expect($html)->toContain('data-sse-channel="user:user-123"');
});

it('component receives SSE updates', function () {
    // 1. Create component
    $component = new NotificationCenterComponent(
        id: ComponentId::fromString('notification-center:user-123')
    );

    // 2. Trigger action (dispatches ComponentUpdatedEvent)
    $newState = $component->markAsRead('notification-1');

    // 3. Verify SSE event was broadcasted
    $this->assertSseBroadcasted(
        channel: 'user:user-123',
        componentId: 'notification-center:user-123',
        eventType: 'component-update'
    );
});
```

### Manual Browser Testing

1. **Öffne Browser DevTools**
2. **Network Tab** → Filter: "eventsource"
3. **Lade Seite** mit SSE-Component
4. **Prüfe Connection**: `/sse/events/{channel}` sollte sichtbar sein
5. **Trigger Action**: Klicke Button
6. **Prüfe Console**: SSE-Events sollten geloggt werden

## Troubleshooting

### Problem: Component verbindet sich nicht zu SSE

**Symptome**:
- Keine SSE-Connection in Network Tab
- Keine Console-Logs über SSE

**Diagnose**:
```php
// Check 1: Hat Component getSseChannel()?
if (method_exists($component, 'getSseChannel')) {
    echo "✅ getSseChannel() exists\n";
    echo "Channel: " . $component->getSseChannel() . "\n";
} else {
    echo "❌ getSseChannel() missing\n";
}

// Check 2: Wird data-sse-channel im HTML gerendert?
$html = $componentRegistry->renderWithWrapper($component);
if (str_contains($html, 'data-sse-channel')) {
    echo "✅ data-sse-channel attribute present\n";
} else {
    echo "❌ data-sse-channel attribute missing\n";
}
```

**Lösung**:
1. Implementiere `getSseChannel()` Methode
2. Stelle sicher dass Component über `ComponentRegistry::renderWithWrapper()` gerendert wird

### Problem: Updates kommen nicht an

**Symptome**:
- SSE-Connection existiert
- Aber Component updated nicht bei Actions

**Diagnose**:
```javascript
// Browser Console
const sseClient = window.liveComponentManager.sseClients.get('user:user-123');
console.log('SSE Connected:', sseClient?.isConnected);
console.log('Component Channels:', Array.from(window.liveComponentManager.componentChannels));
```

**Lösung**:
1. Prüfe ob Actions `#[Action]` Attribute haben
2. Prüfe ob `ComponentUpdatedEvent` dispatched wird
3. Prüfe Channel-Matching: Component channel === SSE channel

### Problem: Reconnection-Loops

**Symptome**:
- SSE disconnected/reconnected ständig
- Console voll mit Reconnect-Meldungen

**Diagnose**:
```bash
# Server-Logs prüfen
docker exec php tail -f storage/logs/app.log | grep SSE
```

**Lösung**:
- Server-Side Exceptions fixen (meist in `/sse/events/{channel}` Route)
- Heartbeat-Timeout erhöhen falls Server langsam antwortet

### Problem: Multiple Connections für Same Channel

**Symptome**:
- Network Tab zeigt mehrere `/sse/events/{channel}` Connections
- Memory Leaks

**Diagnose**:
```javascript
// Browser Console
console.log('SSE Clients:', window.liveComponentManager.sseClients.size);
// Sollte 1 sein für 1 Channel
```

**Lösung**:
- LiveComponentManager macht automatisch Connection Pooling
- Prüfe ob Components richtig cleanup'd werden (keine Memory Leaks)

## Best Practices

### ✅ DO: Spezifische Channels

```php
// ✅ Good: Spezifisch
public function getSseChannel(): string
{
    return "comments:post-{$this->postId}";
}

// ❌ Bad: Zu generisch
public function getSseChannel(): string
{
    return "all-updates";  // Zu breit, zu viele Events
}
```

### ✅ DO: Dokumentiere Channel-Strategie

```php
/**
 * Get SSE channel for real-time updates
 *
 * Uses user-specific channel for private notifications.
 * Falls back to 'global' for guest users.
 *
 * Channel format: "user:{userId}" or "global"
 *
 * Examples:
 * - "notification-center:user-123" → "user:user-123"
 * - "notification-center:guest" → "global"
 */
public function getSseChannel(): string
{
    // ... implementation
}
```

### ✅ DO: Verwende $this->id->instanceId

```php
// ✅ Good: Korrekte Property
$instanceId = $this->id->instanceId;

// ❌ Bad: Falsches Property (existiert nicht)
$instanceId = $this->id->identifier;
```

### ✅ DO: Fallback für Global/Guest

```php
public function getSseChannel(): string
{
    $instanceId = $this->id->instanceId;

    // Fallback für Gäste
    if ($instanceId === 'global' || $instanceId === 'guest') {
        return 'global';
    }

    return "user:{$instanceId}";
}
```

### ✅ DO: Test SSE Integration

```php
it('has getSseChannel method', function () {
    $component = new MyComponent(ComponentId::fromString('my:test'));
    expect(method_exists($component, 'getSseChannel'))->toBeTrue();
});

it('returns correct channel', function () {
    $component = new MyComponent(ComponentId::fromString('my:user-123'));
    expect($component->getSseChannel())->toBe('user:user-123');
});
```

### ❌ DON'T: Hardcode User IDs

```php
// ❌ Bad: Hardcoded User ID
public function getSseChannel(): string
{
    return "user:123";  // Falsch!
}

// ✅ Good: Dynamisch aus Component ID extrahieren
public function getSseChannel(): string
{
    return "user:{$this->id->instanceId}";
}
```

### ❌ DON'T: Vergiss #[Action] Attribute

```php
// ❌ Bad: Kein Action-Attribute
public function markAsRead(string $id): ComponentData
{
    // Wird nicht als Action erkannt
}

// ✅ Good: Mit Action-Attribute
#[Action]
public function markAsRead(string $id): ComponentData
{
    // Wird als Action erkannt und dispatched ComponentUpdatedEvent
}
```

## Advanced Topics

### Custom SSE Events Broadcasting

Manchmal möchtest du SSE-Updates von außerhalb einer Component-Action triggern:

```php
use App\Framework\Sse\ValueObjects\SseChannel;
use App\Framework\Sse\ValueObjects\SseEvent;
use App\Framework\Sse\SseConnectionPool;

final readonly class NotificationService
{
    public function __construct(
        private SseConnectionPool $connectionPool
    ) {}

    public function sendNotification(string $userId, string $message): void
    {
        // 1. Save notification to database
        $this->notificationRepository->create($userId, $message);

        // 2. Broadcast SSE update
        $channel = new SseChannel("user:{$userId}");
        $event = SseEvent::componentUpdate(
            componentId: "notification-center:user-{$userId}",
            state: ['notifications' => $this->getNotifications($userId)],
            html: $this->renderNotifications($userId)
        );

        $this->connectionPool->broadcast($channel, $event);
    }
}
```

### Partial Fragment Updates

Für Performance kannst du nur Teile der Component updaten:

```php
$event = SseEvent::componentFragments(
    componentId: "notification-center:user-123",
    fragments: [
        '#unread-count' => '<span class="badge">5</span>',
        '#notification-list' => '<div>...</div>'
    ]
);

$this->connectionPool->broadcast($channel, $event);
```

### Server-Side Channel Filtering

Validiere Channel-Zugriff auf Server-Side:

```php
#[Route('/sse/events/{channel}', method: Method::GET)]
public function events(string $channel, HttpRequest $request): StreamedResponse
{
    // Validiere User-Specific Channels
    if (str_starts_with($channel, 'user:')) {
        $requestedUserId = substr($channel, 5);
        $actualUserId = $request->user->id;

        if ($requestedUserId !== $actualUserId) {
            throw new UnauthorizedException('Cannot access other user channels');
        }
    }

    // ... continue with SSE connection
}
```

## Zusammenfassung

SSE-Integration in LiveComponents ist **einfach und automatisch**:

1. ✅ **Add `#[Action]` Attribute** zu allen Action-Methoden
2. ✅ **Implement `getSseChannel()`** mit passender Channel-Strategie
3. ✅ **Test** die Integration mit Unit- und Browser-Tests
4. ✅ **Component verbindet sich automatisch** und empfängt Updates

**Das war's!** Keine JavaScript-Code erforderlich, keine manuelle Event-Handling. Das Framework kümmert sich um:
- Auto-Connection beim Component-Mount
- Auto-Reconnection bei Verbindungsproblemen
- Connection Pooling für Performance
- DOM-Updates via LiveComponentManager

**Next Steps**:
- Siehe [SSE System Documentation](./sse-system.md) für technische Details
- Siehe [LiveComponents System](./livecomponents-system.md) für Component-Entwicklung
