meita/realtime-notifications

Fast, reliable realtime notifications over SSE backed by JSON/TXT files in storage (per user_id).

Installs: 0

Dependents: 0

Suggesters: 0

Security: 0

Stars: 0

Watchers: 0

Forks: 0

Open Issues: 0

pkg:composer/meita/realtime-notifications

1.0.0 2025-12-20 04:48 UTC

This package is auto-updated.

Last update: 2025-12-20 04:52:53 UTC


README

Author: Mohamed A. Eita (maa1987@hotmail.com)

File-backed, reliable realtime notifications over Server-Sent Events (SSE). Notifications are stored per user_id as JSON/TXT under storage, moved to inflight/ when streamed, and deleted after ACK.

Works standalone in pure PHP or plugged into Laravel (10/11/12). Comes with a Laravel bridge (routes + provider) and a plain PHP adapter.

Features

  • SSE push (event: meita.notification) with auto ACK support
  • Storage as files: JSON (structured) or TXT (plain)
  • Per user folders: meita/notifications/{pending|inflight}/{userId}/...
  • Keyword filtering for JSON (?keywords=order,urgent)
  • Lease handling: pending → inflight until ACK; reclaim stale inflight files
  • Auth via signed token (or Laravel auth/session when using the bridge)

Requirements

  • PHP ^8.1
  • Optional Laravel bridge: Laravel ^10|^11|^12

Install

composer require meita/realtime-notifications

For a local path repo (adjust the path as needed):

{
  "repositories": [
    { "type": "path", "url": "../realtime-notifications", "options": { "symlink": true } }
  ],
  "require": { "meita/realtime-notifications": "*" }
}

Storage layout

storage/app/meita/notifications/
  pending/{userId}/
  inflight/{userId}/

Flow: push → pending → stream moves to inflight → ACK deletes.

Usage with Laravel

  1. Install the package (auto-discovery registers the provider and facade).
  2. Optional publish:
php artisan vendor:publish --tag=meita-notifications-config
php artisan vendor:publish --tag=meita-notifications-assets --force

Push from PHP

use Meita\RealtimeNotifications\Facades\MeitaNotifications;

MeitaNotifications::push(42, 'Order created successfully', [
    'title' => 'Orders',
    'type' => 'success',
    'keywords' => ['order', 'payments'],
    'data' => ['order_id' => 999],
    'format' => 'json', // or 'txt'
    'ttl_seconds' => 3600,
]);

Generate a token (for SSE/ACK)

$token = MeitaNotifications::tokenForUser(42);

Endpoints (provided by the bridge)

  • Stream (SSE): GET /api/meita/notifications/stream/{userId}?meita_token=...
  • ACK: POST /api/meita/notifications/ack/{userId}?meita_token=...

Browser (vanilla JS)

<script>
  const userId = 42;
  const token = 'PUT_TOKEN_HERE';
  const streamUrl = `/api/meita/notifications/stream/${userId}?meita_token=${encodeURIComponent(token)}`;
  const ackUrl = `/api/meita/notifications/ack/${userId}?meita_token=${encodeURIComponent(token)}`;

  const es = new EventSource(streamUrl);
  es.addEventListener('meita.notification', async (event) => {
    const n = JSON.parse(event.data);
    console.log('NOTIF', n);
    await fetch(ackUrl, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ file: n.file, id: n.id }),
    });
  });
</script>

Using the shipped JS client

php artisan vendor:publish --tag=meita-notifications-assets --force
<script type="module">
  import { MeitaNotificationsClient } from '/vendor/meita/meita-notifications.js';
  const userId = 42;
  const token = 'PUT_TOKEN_HERE';
  const client = new MeitaNotificationsClient({
    userId,
    streamUrl: `/api/meita/notifications/stream/${userId}?meita_token=${encodeURIComponent(token)}`,
    ackUrl: `/api/meita/notifications/ack/${userId}?meita_token=${encodeURIComponent(token)}`,
    onNotification: async (n) => console.log('NOTIF', n),
    onError: (e) => console.error('SSE error', e),
  });
  client.connect();
</script>

Usage in pure PHP (or any framework)

Use the core manager with the local filesystem adapter:

use Meita\RealtimeNotifications\Adapters\LocalFilesystemAdapter;
use Meita\RealtimeNotifications\MeitaNotificationsManager;

$config = [
    'disk_root' => __DIR__.'/storage',       // where files are stored
    'base_dir' => 'meita/notifications',
    'secret_key' => 'base64:your-key-or-plain-string',
    'auth' => ['token_ttl_seconds' => 3600],
];

$manager = new MeitaNotificationsManager(
    $config,
    new LocalFilesystemAdapter($config['disk_root'])
);

// push
$ref = $manager->push('user-123', 'Hello standalone', ['keywords' => ['demo']]);

// token (sign/verify yourself in your HTTP layer)
$token = $manager->tokenForUser('user-123');

Build your own HTTP endpoints (any framework) mirroring the Laravel controllers:

  • SSE stream: emit event: meita.notification with JSON payload from reserveNext(...), move file to inflight, flush(), heartbeat : ping.
  • ACK: delete by file or id using $manager->store()->ackMany(...).

Configuration (ENV for Laravel bridge)

MEITA_NOTIFICATIONS_DISK=local
MEITA_NOTIFICATIONS_BASE_DIR=meita/notifications
MEITA_NOTIFICATIONS_SECRET=base64:...or key...
MEITA_NOTIFICATIONS_AUTH_MODE=token_or_auth   # token_or_auth | token | auth | none
MEITA_NOTIFICATIONS_TOKEN_TTL=3600
MEITA_NOTIFICATIONS_TOKEN_PARAM=meita_token
MEITA_NOTIFICATIONS_SSE_RETRY_MS=2000
MEITA_NOTIFICATIONS_POLL_MS=500
MEITA_NOTIFICATIONS_HEARTBEAT_SECONDS=15
MEITA_NOTIFICATIONS_LEASE_TTL_SECONDS=30
MEITA_NOTIFICATIONS_RECLAIM_ON_STREAM_START=true

Tests

composer install
composer test

Tests are under tests/Feature/MeitaRealtimeNotificationsTest.php.

License

MIT