UNPKG

angular2-actioncable-v15

Version:

Simple and flexible integration for ActionCable and Angular applications

145 lines (121 loc) 4.09 kB
import * as ActionCableNs from 'actioncable'; import { Observable, Subject } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; const ActionCable = ActionCableNs; export class Cable { baseCable: any; private disconnectedSource: Subject<any> = new Subject(); constructor(public url: string, public params?: any) { this.baseCable = ActionCable.createConsumer(this.buildUrl(url, params)); // If a function is passed as params, re-evaluate it before attempting to reconnect if (params instanceof Function) { this.disconnected().subscribe(() => { this.baseCable.url = ActionCable.createWebSocketURL(this.buildUrl(url, params)); }); } } /** * Create a new subscription to a channel, optionally with topic parameters. */ channel(name: string, params = {}): Channel { const channel = new Channel(this, name, params); channel.disconnected().subscribe(data => this.disconnectedSource.next(data)); return channel; } /** * Emits when the WebSocket connection is closed. */ disconnected(): Observable<any> { return this.disconnectedSource.asObservable().pipe(debounceTime(100)); } /** * Close the connection. */ disconnect(): void { this.baseCable.disconnect(); } protected buildUrl(url: string, params?: any): string { if (params instanceof Function) { params = params(); } if (!params) { return url; } const paramString = Object.keys(params) .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`) .join('&'); return [url, paramString].join('?'); } } export class Channel { baseChannel: any; messages: Observable<any>; private initializedSource: Subject<any> = new Subject(); private connectedSource: Subject<any> = new Subject(); private disconnectedSource: Subject<any> = new Subject(); private rejectedSource: Subject<any> = new Subject(); private eventTypes = ['initialized', 'connected', 'disconnected', 'rejected']; constructor(public cable: Cable, public name: string, public params = {}) { const channelParams = Object.assign({}, params, { channel: name }); this.messages = new Observable((observer) => { const mixin = { received: (data: any) => observer.next(data), }; this.eventTypes.forEach((type) => { mixin[type] = (data: any) => this[`${type}Source`].next(data); }); this.baseChannel = this.cable.baseCable.subscriptions.create(channelParams, mixin); return () => this.unsubscribe(); }); } /** * Emits messages that have been broadcast to the channel. * For easy clean-up, when this Observable is completed the ActionCable channel will also be closed. */ received(): Observable<any> { return this.messages; } /** * Emits when the subscription is initialized. */ initialized(): Observable<any> { return this.initializedSource.asObservable(); } /** * Emits when the subscription is ready for use on the server. */ connected(): Observable<any> { return this.connectedSource.asObservable(); } /** * Emits when the WebSocket connection is closed. */ disconnected(): Observable<any> { return this.disconnectedSource.asObservable(); } /** * Emits when the subscription is rejected by the server. */ rejected(): Observable<any> { return this.rejectedSource.asObservable(); } /** * Broadcast message to other clients subscribed to this channel. */ send(data: any): void { this.baseChannel.send(data); } /** * Perform a channel action with the optional data passed as an attribute. */ perform(action: string, data?: any): void { this.baseChannel.perform(action, data); } /** * Unsubscribe from the channel. */ unsubscribe(): void { this.baseChannel.unsubscribe(); this.eventTypes.forEach(type => this[`${type}Source`].complete()); } }