UNPKG

@boringnode/transmit

Version:

A framework agnostic Server-Sent-Event library

176 lines (175 loc) 5.78 kB
/* * @boringnode/transmit * * @license MIT * @copyright Boring Node */ import { clearInterval } from 'node:timers'; import Emittery from 'emittery'; import { Bus } from '@boringnode/bus'; import string from '@poppinss/utils/string'; import { StreamManager } from './stream_manager.js'; import { TransportMessageType } from './transport_message_type.js'; export class Transmit { /** * The configuration for the transmit instance */ #config; /** * The stream manager instance */ #manager; /** * The transport channel to synchronize messages and subscriptions * across multiple instance. */ #transportChannel; /** * The transport provider to synchronize messages and subscriptions * across multiple instance. */ #bus; /** * The emittery instance to emit events. */ #emittery; /** * The interval to send ping messages to all the subscribers. */ #interval; constructor(config, transport) { this.#config = config; this.#manager = new StreamManager(); this.#emittery = new Emittery(); this.#bus = transport ? new Bus(transport, { retryQueue: { enabled: true } }) : null; this.#transportChannel = this.#config.transport?.channel ?? 'transmit::broadcast'; // Subscribe to the transport channel and handle incoming messages void this.#bus?.subscribe(this.#transportChannel, (message) => { const { type, channel, payload } = message; if (type === TransportMessageType.Broadcast) { void this.#broadcastLocally(channel, payload); } else if (type === TransportMessageType.Subscribe) { void this.#subscribeLocally({ uid: payload.uid, channel }); } else if (type === TransportMessageType.Unsubscribe) { void this.#unsubscribeLocally({ uid: payload.uid, channel }); } }); // Start the ping interval if configured if (this.#config.pingInterval) { const intervalValue = typeof this.#config.pingInterval === 'number' ? this.#config.pingInterval : string.milliseconds.parse(this.#config.pingInterval); this.#interval = setInterval(() => this.#ping(), intervalValue); } } getManager() { return this.#manager; } createStream(params) { return this.#manager.createStream({ ...params, onConnect: () => { void this.#emittery.emit('connect', { uid: params.uid, context: params.context, }); }, onDisconnect: () => { void this.#emittery.emit('disconnect', { uid: params.uid, context: params.context, }); }, }); } authorize(channel, callback) { this.#manager.authorize(channel, callback); } #subscribeLocally(params) { return this.#manager.subscribe({ ...params, skipAuthorization: true, }); } subscribe(params) { return this.#manager.subscribe({ ...params, onSubscribe: ({ uid, channel, context }) => { void this.#emittery.emit('subscribe', { uid, channel, context, }); void this.#bus?.publish(this.#transportChannel, { type: TransportMessageType.Subscribe, channel, payload: { uid }, }); }, }); } #unsubscribeLocally(params) { return this.#manager.unsubscribe({ ...params, }); } unsubscribe(params) { return this.#manager.unsubscribe({ ...params, onUnsubscribe: ({ uid, channel, context }) => { void this.#emittery.emit('unsubscribe', { uid, channel, context, }); void this.#bus?.publish(this.#transportChannel, { type: TransportMessageType.Unsubscribe, channel, payload: { uid }, }); }, }); } #broadcastLocally(channel, payload, senderUid) { const subscribers = this.#manager.findByChannel(channel); for (const subscriber of subscribers) { if (Array.isArray(senderUid) ? senderUid.includes(subscriber.getUid()) : senderUid === subscriber.getUid()) { continue; } subscriber.writeMessage({ data: { channel, payload } }); } } broadcastExcept(channel, payload, senderUid) { return this.#broadcastLocally(channel, payload, senderUid); } broadcast(channel, payload) { if (!payload) { payload = {}; } void this.#bus?.publish(this.#transportChannel, { type: TransportMessageType.Broadcast, channel, payload, }); this.#broadcastLocally(channel, payload); void this.#emittery.emit('broadcast', { channel, payload }); } on(event, callback) { return this.#emittery.on(event, callback); } async shutdown() { if (this.#interval) { clearInterval(this.#interval); } await this.#bus?.disconnect(); } #ping() { for (const [stream] of this.#manager.getAllSubscribers()) { stream.writeMessage({ data: { channel: '$$transmit/ping', payload: {} } }); } } }