@arkstack/realtime
@arkstack/realtime
The client for consuming Arkstack realtime notifications. A framework-agnostic core plus React and Vue bindings, backed by Pusher or Firebase Cloud Messaging.
Pairs with the realtime notification driver in @arkstack/notifications, which broadcasts a per-user channel (user.<id>) that this client subscribes to.
Install
pnpm add @arkstack/realtime
Then install the client SDK for your transport (both are optional peer dependencies — install only the one you use):
pnpm add pusher-js # Pusher transport
pnpm add firebase # Firebase transport
Usage
Create a client and subscribe to a user's channel. subscribe/forUser resolve to an unsubscribe function.
import { createRealtime } from '@arkstack/realtime'
const realtime = createRealtime({
transport: 'pusher',
pusher: {
key: import.meta.env.VITE_PUSHER_KEY,
cluster: 'mt1',
authEndpoint: '/broadcasting/auth',
},
})
const unsubscribe = await realtime.forUser(user.id, (notification) => {
console.log(notification.title, notification.description)
})
// later
unsubscribe()
await realtime.disconnect()
Client events
Pusher private and presence channels can send ephemeral client events (often called whispers). Subscribe to the channel before sending an event:
const stopTyping = await realtime.listenForWhisper('private-room.7', 'typing', ({ userId }) =>
console.log(`${userId} is typing`),
)
await realtime.whisper('private-room.7', 'typing', { userId: user.id })
stopTyping()
The client- prefix is added automatically. Pusher sends client events over its
private/presence channel. Firebase uses Realtime Database to publish the same
ephemeral events across connected clients. Use listen(channel, event, handler)
and trigger(channel, event, payload) when you need the lower-level APIs with
exact event names.
Each notification matches the payload broadcast by the server:
interface RealtimeNotification {
id: string
type: string | null
title: string
description: string
actionText?: string | null
actionLink?: string | null
meta?: Record<string, unknown> | null
read_at: string | null
created_at: string
}
React
import { useNotifications } from '@arkstack/realtime/react'
function Bell({ realtime, userId }) {
const { notifications, latest, clear } = useNotifications(realtime, realtime.channelFor(userId), { limit: 20 })
return (
<span onClick={clear}>
{notifications.length} · {latest?.title}
</span>
)
}
The hook accumulates notifications (newest first), caps them at limit, and unsubscribes automatically on unmount or when client/channel change.
Vue
<script setup>
import { useNotifications } from "@arkstack/realtime/vue";
const props = defineProps(["realtime", "userId"]);
const { notifications, latest, clear } = useNotifications(
props.realtime,
props.realtime.channelFor(props.userId),
{ limit: 20 },
);
</script>
<template>
<span @click="clear">{{ notifications.length }} · {{ latest?.title }}</span>
</template>
The composable unsubscribes automatically when the component's scope is disposed.
Firebase
Firebase Cloud Messaging delivers to the device (via onMessage), so notifications are matched by event name rather than channel:
const realtime = createRealtime({
transport: 'firebase',
firebase: {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
appId: import.meta.env.VITE_FIREBASE_APP_ID,
messagingSenderId: import.meta.env.VITE_FIREBASE_SENDER_ID,
databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL,
},
})
Firebase client events are written below arkstack/client-events in Realtime
Database and removed immediately after publishing. Configure Firebase Security
Rules for the channels your authenticated clients may read and write. Override
the root with firebase.clientEventsPath when needed.
Custom transport
Provide transportFactory to bridge any backend (a raw WebSocket, SSE, a test double, …):
import { createRealtime, type RealtimeTransport } from '@arkstack/realtime'
const transport: RealtimeTransport = {
subscribe(channel, event, handler) {
const socket = new WebSocket(`wss://example.test/${channel}`)
socket.addEventListener('message', (e) => {
const { event: name, payload } = JSON.parse(e.data)
if (name === event) handler(payload)
})
return { channel, unsubscribe: () => socket.close() }
},
disconnect() {},
}
const realtime = createRealtime({ transportFactory: () => transport })
API
createRealtime(config)— create aRealtimeClient. Config:transport('pusher'|'firebase'),event(defaultnotification),channelPrefix(defaultuser.),pusher/firebasecredentials, or a customtransportFactory.client.subscribe(channel, handler)/client.forUser(userId, handler)— subscribe; returns an unsubscribe function.client.listen(channel, event, handler)— listen for an arbitrary event.client.listenForWhisper(channel, event, handler)/client.whisper(channel, event, payload)— receive and send Pusher client events.client.trigger(channel, event, payload)— emit an exact event name through a transport that supports it.client.channelFor(userId)— the per-user channel name.client.disconnect()— tear down the transport connection.@arkstack/realtime/react—useNotifications(client, channel, { limit? })→{ notifications, latest, clear }.@arkstack/realtime/vue—useNotifications(client, channel, { limit? })→{ notifications, latest, clear, stop }.
See the notifications guide for the server side.
License
MIT
Related Packages
Plug-and-play API + WebSocket client. Framework-agnostic core with optional Vue,...
Self-hosted realtime state store with offline sync, built on lazy-watch: keyed c...
Vite plugin to generate a Firebase Cloud Messaging service worker with env-aware...
Pinia store for task management integration with Taskin
Version History
| Version | Released | License |
|---|---|---|
| 0.20.1 | ||
| 0.20.0 | ||
| 0.19.6 | ||
| 0.19.5 | ||
| 0.19.4 | ||
| 0.19.3 | ||
| 0.19.2 | ||
| 0.19.1 | ||
| 0.19.0 | ||
| 0.18.12 | ||
| 0.18.11 | ||
| 0.18.10 | ||
| 0.18.9 | ||
| 0.18.8 | ||
| 0.18.7 | ||
| 0.18.6 | ||
| 0.18.5 | ||
| 0.18.4 | ||
| 0.18.3 | ||
| 0.18.2 | ||
| 0.18.1 | ||
| 0.18.0 | ||
| 0.17.26 | ||
| 0.17.25 | ||
| 0.17.24 | ||
| 0.17.23 | ||
| 0.17.22 | ||
| 0.17.21 | ||
| 0.17.20 | ||
| 0.17.19 |
Showing the latest 30 of 56. See every release on npm