angular2-actioncable-v15
Version:
Simple and flexible integration for ActionCable and Angular applications
145 lines (121 loc) • 4.09 kB
text/typescript
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());
}
}