npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@jengo/broadcasting

v1.0.0

Published

Universal real-time event broadcasting client for Jengo and CodeIgniter 4 supporting Server-Sent Events (SSE) and WebSockets.

Readme

@jengo/broadcasting

Universal real-time event broadcasting client for Jengo and CodeIgniter 4 applications.

Pairs directly with the jengo/broadcasting PHP backend package. Supports Server-Sent Events (SSE) and WebSockets (Soketi, Pusher, Reverb, Jengo WS) with automatic reconnection, channel authorization handshakes, and first-party React and Vue integrations.


Installation

# npm
npm install @jengo/broadcasting

# pnpm
pnpm add @jengo/broadcasting

# yarn
yarn add @jengo/broadcasting

Quick Start: Server-Sent Events (SSE)

Server-Sent Events allow real-time streaming over standard HTTP connections with zero extra services or WebSocket servers:

import Broadcaster from '@jengo/broadcasting';

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    endpoint: '/broadcasting/sse',
    authEndpoint: '/broadcasting/auth',
});

// Subscribe to a public channel
broadcaster.channel('orders')
    .listen('OrderCreated', (event) => {
        console.log('New Order:', event);
    });

Quick Start: WebSockets (Soketi / Pusher / Jengo WS)

import Broadcaster from '@jengo/broadcasting';

const broadcaster = new Broadcaster({
    broadcaster: 'ws',
    key: 'jengo-app-key',
    wsHost: window.location.hostname,
    wsPort: 6001,
    forceTLS: false,
    authEndpoint: '/broadcasting/auth',
});

// Subscribe to a public channel
broadcaster.channel('orders')
    .listen('OrderCreated', (event) => {
        console.log('New Order:', event);
    });

Channels & Subscriptions

1. Public Channels

Public channels do not require authentication:

const channel = broadcaster.channel('news');

channel.listen('HeadlineUpdated', (data) => {
    console.log(data.headline);
});

// Stop listening to a specific event
channel.stopListening('HeadlineUpdated');

// Leave channel
broadcaster.leave('news');

2. Private Channels

Private channels automatically invoke /broadcasting/auth with CodeIgniter 4 CSRF verification:

const privateChannel = broadcaster.private(`chat.${roomId}`);

privateChannel.listen('NewMessage', (message) => {
    console.log('Private message received:', message);
});

// Client-to-client whisper (typing indicator)
privateChannel.whisper('typing', { userId: 42 });

privateChannel.listenForWhisper('typing', (data) => {
    console.log('User is typing:', data.userId);
});

3. Presence Channels

Presence channels allow tracking who is currently active in a channel:

const presence = broadcaster.join(`room.${roomId}`);

presence
    .here((members) => {
        console.log('Currently in room:', members);
    })
    .joining((newMember) => {
        console.log('Joined:', newMember);
    })
    .leaving((leftMember) => {
        console.log('Left:', leftMember);
    })
    .listen('RoomAnnouncement', (event) => {
        console.log('Announcement:', event);
    });

React Integration

Import directly from @jengo/broadcasting/react:

import React, { useState } from 'react';
import { BroadcastingProvider, useChannel, usePresence } from '@jengo/broadcasting/react';
import Broadcaster from '@jengo/broadcasting';

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    endpoint: '/broadcasting/sse',
});

export function App() {
    return (
        <BroadcastingProvider client={broadcaster}>
            <ChatRoom roomId="general" />
        </BroadcastingProvider>
    );
}

function ChatRoom({ roomId }: { roomId: string }) {
    const [messages, setMessages] = useState<any[]>([]);

    // Automatically binds and unbinds on mount/unmount
    useChannel(`chat.${roomId}`, 'NewMessage', (msg) => {
        setMessages((prev) => [...prev, msg]);
    });

    // Reactive presence member list
    const { members } = usePresence(`room.${roomId}`);

    return (
        <div>
            <h3>Online Users ({members.length})</h3>
            <ul>
                {members.map((m) => (
                    <li key={m.id}>{m.info?.name ?? m.id}</li>
                ))}
            </ul>
        </div>
    );
}

Vue 3 Integration

Import directly from @jengo/broadcasting/vue:

// main.ts
import { createApp } from 'vue';
import App from './App.vue';
import Broadcaster from '@jengo/broadcasting';
import { BroadcastingPlugin } from '@jengo/broadcasting/vue';

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    endpoint: '/broadcasting/sse',
});

const app = createApp(App);
app.use(BroadcastingPlugin, broadcaster);
app.mount('#app');
<!-- ChatRoom.vue -->
<script setup lang="ts">
import { ref } from 'vue';
import { useChannel, usePresence } from '@jengo/broadcasting/vue';

const messages = ref<any[]>([]);

useChannel('orders', 'OrderCreated', (order) => {
    messages.value.push(order);
});

const { members } = usePresence('room.lobby');
</script>

<template>
    <div>
        <h3>Members: {{ members.length }}</h3>
        <ul>
            <li v-for="member in members" :key="member.id">{{ member.info?.name }}</li>
        </ul>
    </div>
</template>

Svelte Integration

Import directly from @jengo/broadcasting/svelte:

Method A: Reactive Svelte Stores ($store)

Use createChannelStore to bind broadcasted events directly to Svelte readable stores with automatic subscription lifecycle management:

<script lang="ts">
import { Broadcaster } from '@jengo/broadcasting';
import { createChannelStore } from '@jengo/broadcasting/svelte';

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    endpoint: '/broadcasting/sse',
});

const latestOrder = createChannelStore('orders', 'OrderCreated', null, broadcaster);
</script>

{#if $latestOrder}
    <p>New order arrived: {$latestOrder.id} (${$latestOrder.total})</p>
{/if}

Method B: Context & Lifecycle Hooks

Set the client once in your root layout or parent component:

<!-- +layout.svelte -->
<script lang="ts">
import { Broadcaster } from '@jengo/broadcasting';
import { setBroadcaster } from '@jengo/broadcasting/svelte';

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    endpoint: '/broadcasting/sse',
});

setBroadcaster(broadcaster);
</script>

<slot />

Use lifecycle-aware hooks in child components:

<!-- ChatRoom.svelte -->
<script lang="ts">
import { useChannel, usePresence } from '@jengo/broadcasting/svelte';

let messages = [];

// Automatically detaches listener on component unmount
useChannel('chat.lobby', 'NewMessage', (msg) => {
    messages = [...messages, msg];
});

// Returns a Svelte store for reactive member list and presence channel handle
const { members } = usePresence('room.lobby');
</script>

<div>
    <h3>Online Users ({$members.length})</h3>
    <ul>
        {#each $members as member (member.id)}
            <li>{member.info?.name ?? member.id}</li>
        {/each}
    </ul>
</div>

CodeIgniter 4 CSRF & Auth Configuration

@jengo/broadcasting automatically resolves CodeIgniter 4's CSRF token from:

  1. <meta name="csrf-token" content="..."> or <meta name="X-CSRF-TOKEN" content="...">
  2. Browser cookies (csrf_cookie_name or csrf_test_name)
  3. Global variables window.jengoCsrf or window.csrfToken

You can also pass custom authorization headers or tokens explicitly:

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    bearerToken: () => localStorage.getItem('auth_token'),
    auth: {
        headers: {
            'X-Custom-Header': 'value',
        },
    },
});

Event Deduplication

To prevent duplicate event rendering caused by network retries or stream reconnections, @jengo/broadcasting includes a built-in event deduplicator. Duplicate event payloads with identical event IDs received within the sliding deduplication window (default: 10 seconds) are automatically ignored.

const broadcaster = new Broadcaster({
    broadcaster: 'sse',
    deduplicationWindowMs: 15000, // 15 seconds window
});

License

Released under the MIT License.