UNPKG

sveltekit-sync

Version:
140 lines (139 loc) 4.13 kB
/** * SyncChannel - Channel-based API for real-time features * * Provides a composable API for presence tracking, custom events, * and channel-scoped communication. */ import { PresenceStore } from './presence.svelte.js'; /** * Channel for scoped real-time communication */ export class SyncChannel { name; presence = null; realtimeClient; options; subscribed = false; eventHandlers = new Map(); constructor(realtimeClient, name, user, options = {}) { this.realtimeClient = realtimeClient; this.name = name; this.options = { presence: options.presence ?? false, broadcast: options.broadcast ?? false, }; // Initialize presence if enabled if (this.options.presence && user) { this.presence = new PresenceStore(realtimeClient, name, user); } // Setup event listeners for channel-specific events this.setupEventListeners(); } unsubscribeEphemeral; setupEventListeners() { // Listen for ephemeral events on this channel this.unsubscribeEphemeral = this.realtimeClient.on('ephemeral', (data) => { if (data.channel === this.name) { const handlers = this.eventHandlers.get(data.event); if (handlers) { handlers.forEach(handler => handler(data.data)); } } }); } /** * Subscribe to the channel */ async subscribe() { if (this.subscribed) { console.warn("Already subscribed to channel"); return; } await this.realtimeClient.joinChannel(this.name); this.subscribed = true; } async unsubscribe() { if (!this.subscribed) { return; } await this.realtimeClient.leaveChannel(this.name); this.subscribed = false; // Clean up presence if enabled if (this.presence) { this.presence.destroy(); } // Remove ephemeral listener if (this.unsubscribeEphemeral) { this.unsubscribeEphemeral(); this.unsubscribeEphemeral = undefined; } // Clear event handlers this.eventHandlers.clear(); } /** * Track presence state (if presence is enabled) */ track(state) { if (!this.presence) { console.warn('Presence is not enabled for this channel'); return; } this.presence.updatePresence({ custom: state }); } /** * Stop tracking presence */ untrack() { if (!this.presence) { return; } this.presence.setStatus('offline'); } /** * Listen for custom events on this channel */ on(event, handler) { if (!this.options.broadcast) { console.warn('Broadcasting is not enabled for this channel'); return () => { }; } if (!this.eventHandlers.has(event)) { this.eventHandlers.set(event, new Set()); } this.eventHandlers.get(event).add(handler); // Return unsubscribe function return () => { const handlers = this.eventHandlers.get(event); if (handlers) { handlers.delete(handler); if (handlers.size === 0) { this.eventHandlers.delete(event); } } }; } /** * Broadcast a custom event to all subscribers of this channel */ async broadcast(event, data) { if (!this.options.broadcast) { console.warn('Broadcasting is not enabled for this channel'); return; } if (!this.subscribed) { console.warn(`Cannot broadcast on unsubscribed channel ${this.name}`); return; } await this.realtimeClient.send('ephemeral', { channel: this.name, event, data }); } /** * Check if the channel is subscribed */ isSubscribed() { return this.subscribed; } }