forked from laravel/echo
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from ably-forks/ably-connector
Ably connector
- Loading branch information
Showing
24 changed files
with
1,425 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
import { AblyRealtime, AblyRealtimeChannel } from '../../typings/ably'; | ||
import { EventFormatter } from '../util'; | ||
import { Channel } from './channel'; | ||
|
||
/** | ||
* This class represents an Ably channel. | ||
*/ | ||
export class AblyChannel extends Channel { | ||
/** | ||
* The Ably client instance. | ||
*/ | ||
ably: AblyRealtime; | ||
|
||
/** | ||
* The name of the channel. | ||
*/ | ||
name: string; | ||
|
||
/** | ||
* Channel options. | ||
*/ | ||
options: any; | ||
|
||
/** | ||
* The event formatter. | ||
*/ | ||
eventFormatter: EventFormatter; | ||
|
||
/** | ||
* The subscription of the channel. | ||
*/ | ||
channel: AblyRealtimeChannel; | ||
|
||
/** | ||
* An array containing all registered subscribed listeners. | ||
*/ | ||
subscribedListeners: Function[]; | ||
|
||
/** | ||
* An array containing all registered error listeners. | ||
*/ | ||
errorListeners: Function[]; | ||
|
||
/** | ||
* Channel event subscribe callbacks, maps callback to modified implementation. | ||
*/ | ||
callbacks: Map<Function, Function>; | ||
|
||
/** | ||
* Create a new class instance. | ||
*/ | ||
constructor(ably: any, name: string, options: any, autoSubscribe = true) { | ||
super(); | ||
|
||
this.name = name; | ||
this.ably = ably; | ||
this.options = options; | ||
this.eventFormatter = new EventFormatter(this.options.namespace); | ||
this.subscribedListeners = []; | ||
this.errorListeners = []; | ||
this.channel = ably.channels.get(name); | ||
this.callbacks = new Map(); | ||
|
||
if (autoSubscribe) { | ||
this.subscribe(); | ||
} | ||
} | ||
|
||
/** | ||
* Subscribe to an Ably channel. | ||
*/ | ||
subscribe(): any { | ||
this.channel.on(stateChange => { | ||
const { previous, current, reason } = stateChange; | ||
if (previous !== 'attached' && current == 'attached') { | ||
this.subscribedListeners.forEach(listener => listener()); | ||
} else if (reason) { | ||
this._alertErrorListeners(stateChange); | ||
} | ||
}); | ||
this.channel.attach(this._alertErrorListeners); | ||
} | ||
|
||
/** | ||
* Unsubscribe from an Ably channel, unregister all callbacks and finally detach the channel | ||
*/ | ||
unsubscribe(): void { | ||
this.channel.unsubscribe(); | ||
this.callbacks.clear(); | ||
this.unregisterError(); | ||
this.unregisterSubscribed(); | ||
this.channel.off(); | ||
this.channel.detach(); | ||
} | ||
|
||
/** | ||
* Listen for an event on the channel instance. | ||
*/ | ||
listen(event: string, callback: Function): AblyChannel { | ||
this.callbacks.set(callback, ({ data, ...metaData }) => callback(data, metaData)); | ||
this.channel.subscribe(this.eventFormatter.format(event), this.callbacks.get(callback) as any); | ||
return this; | ||
} | ||
|
||
/** | ||
* Listen for all events on the channel instance. | ||
*/ | ||
listenToAll(callback: Function): AblyChannel { | ||
this.callbacks.set(callback, ({ name, data, ...metaData }) => { | ||
let namespace = this.options.namespace.replace(/\./g, '\\'); | ||
|
||
let formattedEvent = name.startsWith(namespace) ? name.substring(namespace.length + 1) : '.' + name; | ||
|
||
callback(formattedEvent, data, metaData); | ||
}); | ||
this.channel.subscribe(this.callbacks.get(callback) as any); | ||
return this; | ||
} | ||
|
||
/** | ||
* Stop listening for an event on the channel instance. | ||
*/ | ||
stopListening(event: string, callback?: Function): AblyChannel { | ||
if (callback) { | ||
this.channel.unsubscribe(this.eventFormatter.format(event), this.callbacks.get(callback) as any); | ||
this.callbacks.delete(callback); | ||
} else { | ||
this.channel.unsubscribe(this.eventFormatter.format(event)); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Stop listening for all events on the channel instance. | ||
*/ | ||
stopListeningToAll(callback?: Function): AblyChannel { | ||
if (callback) { | ||
this.channel.unsubscribe(this.callbacks.get(callback) as any); | ||
this.callbacks.delete(callback); | ||
} else { | ||
this.channel.unsubscribe(); | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Register a callback to be called anytime a subscription succeeds. | ||
*/ | ||
subscribed(callback: Function): AblyChannel { | ||
this.subscribedListeners.push(callback); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Register a callback to be called anytime a subscription error occurs. | ||
*/ | ||
error(callback: Function): AblyChannel { | ||
this.errorListeners.push(callback); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Unregisters given error callback from the listeners. | ||
* @param callback | ||
* @returns AblyChannel | ||
*/ | ||
unregisterSubscribed(callback?: Function): AblyChannel { | ||
if (callback) { | ||
this.subscribedListeners = this.subscribedListeners.filter(s => s != callback); | ||
} else { | ||
this.subscribedListeners = []; | ||
} | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Unregisters given error callback from the listeners. | ||
* @param callback | ||
* @returns AblyChannel | ||
*/ | ||
unregisterError(callback?: Function): AblyChannel { | ||
if (callback) { | ||
this.errorListeners = this.errorListeners.filter(e => e != callback); | ||
} else { | ||
this.errorListeners = []; | ||
} | ||
|
||
return this; | ||
} | ||
|
||
_alertErrorListeners = (err: any) => { | ||
if (err) { | ||
this.errorListeners.forEach(listener => listener(err)); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { AblyChannel } from './ably-channel'; | ||
import { AblyAuth } from './ably/auth'; | ||
import { PresenceChannel } from './presence-channel'; | ||
|
||
/** | ||
* This class represents an Ably presence channel. | ||
*/ | ||
export class AblyPresenceChannel extends AblyChannel implements PresenceChannel { | ||
|
||
presenceData: any; | ||
|
||
constructor(ably: any, name: string, options: any, auth: AblyAuth) { | ||
super(ably, name, options, false); | ||
this.channel.on("failed", auth.onChannelFailed(this)); | ||
this.channel.on("attached", () => this.enter(this.presenceData, this._alertErrorListeners)); | ||
this.subscribe(); | ||
} | ||
|
||
unsubscribe(): void { | ||
this.leave(this.presenceData, this._alertErrorListeners); | ||
this.channel.presence.unsubscribe(); | ||
super.unsubscribe(); | ||
} | ||
|
||
/** | ||
* Register a callback to be called anytime the member list changes. | ||
*/ | ||
here(callback: Function): AblyPresenceChannel { | ||
this.channel.presence.subscribe(['enter', 'update', 'leave'], () => | ||
this.channel.presence.get((err, members) => callback(members, err)// returns local sync copy of updated members | ||
)); | ||
return this; | ||
} | ||
|
||
/** | ||
* Listen for someone joining the channel. | ||
*/ | ||
joining(callback: Function): AblyPresenceChannel { | ||
this.channel.presence.subscribe(['enter', 'update'], ({ data, ...metaData }) => { | ||
callback(data, metaData); | ||
}); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Listen for someone leaving the channel. | ||
*/ | ||
leaving(callback: Function): AblyPresenceChannel { | ||
this.channel.presence.subscribe('leave', ({ data, ...metaData }) => { | ||
callback(data, metaData); | ||
}); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Enter presence | ||
* @param data - Data to be published while entering the channel | ||
* @param callback - success/error callback (err) => {} | ||
* @returns AblyPresenceChannel | ||
*/ | ||
enter(data: any, callback: Function): AblyPresenceChannel { | ||
this.channel.presence.enter(data, callback as any); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Leave presence | ||
* @param data - Data to be published while leaving the channel | ||
* @param callback - success/error callback (err) => {} | ||
* @returns AblyPresenceChannel | ||
*/ | ||
leave(data: any, callback?: Function): AblyPresenceChannel { | ||
this.channel.presence.leave(data, callback as any); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Update presence | ||
* @param data - Update presence with data | ||
* @param callback - success/error callback (err) => {} | ||
* @returns AblyPresenceChannel | ||
*/ | ||
update(data: any, callback: Function): AblyPresenceChannel { | ||
this.channel.presence.update(data, callback as any); | ||
|
||
return this; | ||
} | ||
|
||
/** | ||
* Trigger client event on the channel. | ||
*/ | ||
whisper(eventName: string, data: any): AblyPresenceChannel { | ||
this.channel.publish(`client-${eventName}`, data); | ||
|
||
return this; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { AblyChannel } from './ably-channel'; | ||
import { AblyAuth } from './ably/auth'; | ||
|
||
export class AblyPrivateChannel extends AblyChannel { | ||
|
||
constructor(ably: any, name: string, options: any, auth: AblyAuth) { | ||
super(ably, name, options, false); | ||
this.channel.on("failed", auth.onChannelFailed(this)); | ||
this.subscribe(); | ||
} | ||
/** | ||
* Trigger client event on the channel. | ||
*/ | ||
whisper(eventName: string, data: any): AblyPrivateChannel { | ||
this.channel.publish(`client-${eventName}`, data); | ||
|
||
return this; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { isNullOrUndefined } from './utils'; | ||
|
||
let channelAttachAuthorized = false; | ||
|
||
/** | ||
* Modifies existing channel attach with custom authz implementation | ||
*/ | ||
export const beforeChannelAttach = (ablyClient, authorize: Function) => { | ||
const dummyRealtimeChannel = ablyClient.channels.get("dummy"); | ||
if (channelAttachAuthorized) { //Only once all ably instance | ||
return; | ||
} | ||
const internalAttach = dummyRealtimeChannel.__proto__._attach; // get parent class method inferred from object, store it in temp. variable | ||
if (isNullOrUndefined(internalAttach)) { | ||
console.warn("channel internal attach function not found, please check for right library version") | ||
return; | ||
} | ||
function customInternalAttach(forceReattach, attachReason, errCallback) {// Define new function that needs to be added | ||
if (this.state === 'attached' || this.authorizing) { | ||
return; | ||
} | ||
this.authorizing = true; | ||
const bindedInternalAttach = internalAttach.bind(this); // bind object instance at runtime | ||
// custom logic before attach | ||
authorize(this, (error) => { | ||
this.authorizing = false; | ||
if (error) { | ||
errCallback(error); | ||
return; | ||
} else { | ||
bindedInternalAttach(forceReattach, attachReason, errCallback);// call internal function here | ||
} | ||
}) | ||
} | ||
dummyRealtimeChannel.__proto__._attach = customInternalAttach; // add updated extension method to parent class, auto binded | ||
channelAttachAuthorized = true; | ||
} |
Oops, something went wrong.