Skip to content

Commit 968f0bc

Browse files
committed
Merge branch 'larsnystrom/master'
2 parents 6948a74 + 2cbbd0c commit 968f0bc

File tree

6 files changed

+144
-45
lines changed

6 files changed

+144
-45
lines changed

src/channel/channel.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@ export abstract class Channel {
2929
/**
3030
* Stop listening to an event on the channel instance.
3131
*/
32-
abstract stopListening(event: string): Channel;
32+
abstract stopListening(event: string, callback?: Function): Channel;
3333

3434
/**
3535
* Stop listening for a whisper event on the channel instance.
3636
*/
37-
stopListeningForWhisper(event: string): Channel {
38-
return this.stopListening('.client-' + event);
37+
stopListeningForWhisper(event: string, callback?: Function): Channel {
38+
return this.stopListening('.client-' + event, callback);
3939
}
4040

4141
/**

src/channel/null-channel.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export class NullChannel extends Channel {
2828
/**
2929
* Stop listening for an event on the channel instance.
3030
*/
31-
stopListening(event: string): NullChannel {
31+
stopListening(event: string, callback?: Function): NullChannel {
3232
return this;
3333
}
3434

src/channel/pusher-channel.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ export class PusherChannel extends Channel {
7070
/**
7171
* Stop listening for an event on the channel instance.
7272
*/
73-
stopListening(event: string): PusherChannel {
74-
this.subscription.unbind(this.eventFormatter.format(event));
73+
stopListening(event: string, callback?: Function): PusherChannel {
74+
if (callback) {
75+
this.subscription.unbind(this.eventFormatter.format(event), callback);
76+
} else {
77+
this.subscription.unbind(this.eventFormatter.format(event));
78+
}
7579

7680
return this;
7781
}

src/channel/socketio-channel.ts

Lines changed: 42 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,15 @@ export class SocketIoChannel extends Channel {
2626
eventFormatter: EventFormatter;
2727

2828
/**
29-
* The event callbacks applied to the channel.
29+
* The event callbacks applied to the socket.
3030
*/
3131
events: any = {};
3232

33+
/**
34+
* User supplied callbacks for events on this channel.
35+
*/
36+
private listeners: any = {};
37+
3338
/**
3439
* Create a new class instance.
3540
*/
@@ -42,7 +47,6 @@ export class SocketIoChannel extends Channel {
4247
this.eventFormatter = new EventFormatter(this.options.namespace);
4348

4449
this.subscribe();
45-
this.configureReconnector();
4650
}
4751

4852
/**
@@ -79,10 +83,8 @@ export class SocketIoChannel extends Channel {
7983
/**
8084
* Stop listening for an event on the channel instance.
8185
*/
82-
stopListening(event: string): SocketIoChannel {
83-
const name = this.eventFormatter.format(event);
84-
this.socket.removeListener(name);
85-
delete this.events[name];
86+
stopListening(event: string, callback?: Function): SocketIoChannel {
87+
this.unbindEvent(this.eventFormatter.format(event), callback);
8688

8789
return this;
8890
}
@@ -108,47 +110,51 @@ export class SocketIoChannel extends Channel {
108110
/**
109111
* Bind the channel's socket to an event and store the callback.
110112
*/
111-
on(event: string, callback: Function): void {
112-
let listener = (channel, data) => {
113-
if (this.name == channel) {
114-
callback(data);
115-
}
116-
};
113+
on(event: string, callback: Function): SocketIoChannel {
114+
this.listeners[event] = this.listeners[event] || [];
117115

118-
this.socket.on(event, listener);
119-
this.bind(event, listener);
120-
}
116+
if (! this.events[event]) {
117+
this.events[event] = (channel, data) => {
118+
if (this.name === channel && this.listeners[event]) {
119+
this.listeners[event].forEach((cb) => cb(data));
120+
}
121+
};
121122

122-
/**
123-
* Attach a 'reconnect' listener and bind the event.
124-
*/
125-
configureReconnector(): void {
126-
const listener = () => {
127-
this.subscribe();
128-
};
123+
this.socket.on(event, this.events[event]);
124+
}
129125

130-
this.socket.on('reconnect', listener);
131-
this.bind('reconnect', listener);
132-
}
126+
this.listeners[event].push(callback);
133127

134-
/**
135-
* Bind the channel's socket to an event and store the callback.
136-
*/
137-
bind(event: string, callback: Function): void {
138-
this.events[event] = this.events[event] || [];
139-
this.events[event].push(callback);
128+
return this;
140129
}
141130

142131
/**
143132
* Unbind the channel's socket from all stored event callbacks.
144133
*/
145134
unbind(): void {
146135
Object.keys(this.events).forEach((event) => {
147-
this.events[event].forEach((callback) => {
148-
this.socket.removeListener(event, callback);
149-
});
150-
151-
delete this.events[event];
136+
this.unbindEvent(event);
152137
});
153138
}
139+
140+
/**
141+
* Unbind the listeners for the given event.
142+
*/
143+
protected unbindEvent(event: string, callback?: Function): void {
144+
this.listeners[event] = this.listeners[event] || [];
145+
146+
if (callback) {
147+
this.listeners[event] = this.listeners[event].filter((cb) => cb !== callback);
148+
}
149+
150+
if (!callback || this.listeners[event].length === 0) {
151+
if (this.events[event]) {
152+
this.socket.removeListener(event, this.events[event]);
153+
154+
delete this.events[event];
155+
}
156+
157+
delete this.listeners[event];
158+
}
159+
}
154160
}

src/connector/socketio-connector.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class SocketIoConnector extends Connector {
1313
/**
1414
* All of the subscribed channel names.
1515
*/
16-
channels: any = {};
16+
channels: { [name: string]: SocketIoChannel } = {};
1717

1818
/**
1919
* Create a fresh Socket.io connection.
@@ -23,6 +23,12 @@ export class SocketIoConnector extends Connector {
2323

2424
this.socket = io(this.options.host, this.options);
2525

26+
this.socket.on('reconnect', () => {
27+
Object.values(this.channels).forEach((channel) => {
28+
channel.subscribe();
29+
});
30+
});
31+
2632
return this.socket;
2733
}
2834

@@ -67,7 +73,7 @@ export class SocketIoConnector extends Connector {
6773
this.channels['private-' + name] = new SocketIoPrivateChannel(this.socket, 'private-' + name, this.options);
6874
}
6975

70-
return this.channels['private-' + name];
76+
return this.channels['private-' + name] as SocketIoPrivateChannel;
7177
}
7278

7379
/**
@@ -82,7 +88,7 @@ export class SocketIoConnector extends Connector {
8288
);
8389
}
8490

85-
return this.channels['presence-' + name];
91+
return this.channels['presence-' + name] as SocketIoPresenceChannel;
8692
}
8793

8894
/**
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import { SocketIoChannel } from '../../src/channel';
2+
3+
describe('SocketIoChannel', () => {
4+
let channel;
5+
let socket;
6+
7+
beforeEach(() => {
8+
const channelName = 'some.channel';
9+
let listeners = [];
10+
socket = {
11+
emit: (event, data) => listeners.filter(([e]) => e === event).forEach(([, fn]) => fn(channelName, data)),
12+
on: (event, fn) => listeners.push([event, fn]),
13+
removeListener: (event, fn) => {
14+
listeners = listeners.filter(([e, f]) => (!fn ? e !== event : e !== event || f !== fn));
15+
},
16+
};
17+
18+
channel = new SocketIoChannel(socket, channelName, {
19+
namespace: false,
20+
});
21+
});
22+
23+
test('triggers all listeners for an event', () => {
24+
const l1 = jest.fn();
25+
const l2 = jest.fn();
26+
const l3 = jest.fn();
27+
channel.listen('MyEvent', l1);
28+
channel.listen('MyEvent', l2);
29+
channel.listen('MyOtherEvent', l3);
30+
31+
socket.emit('MyEvent', {});
32+
33+
expect(l1).toBeCalled();
34+
expect(l2).toBeCalled();
35+
expect(l3).not.toBeCalled();
36+
37+
socket.emit('MyOtherEvent', {});
38+
39+
expect(l3).toBeCalled();
40+
});
41+
42+
test('can remove a listener for an event', () => {
43+
const l1 = jest.fn();
44+
const l2 = jest.fn();
45+
const l3 = jest.fn();
46+
channel.listen('MyEvent', l1);
47+
channel.listen('MyEvent', l2);
48+
channel.listen('MyOtherEvent', l3);
49+
50+
channel.stopListening('MyEvent', l1);
51+
52+
socket.emit('MyEvent', {});
53+
54+
expect(l1).not.toBeCalled();
55+
expect(l2).toBeCalled();
56+
expect(l3).not.toBeCalled();
57+
58+
socket.emit('MyOtherEvent', {});
59+
60+
expect(l3).toBeCalled();
61+
});
62+
63+
test('can remove all listeners for an event', () => {
64+
const l1 = jest.fn();
65+
const l2 = jest.fn();
66+
const l3 = jest.fn();
67+
channel.listen('MyEvent', l1);
68+
channel.listen('MyEvent', l2);
69+
channel.listen('MyOtherEvent', l3);
70+
71+
channel.stopListening('MyEvent');
72+
73+
socket.emit('MyEvent', {});
74+
75+
expect(l1).not.toBeCalled();
76+
expect(l2).not.toBeCalled();
77+
expect(l3).not.toBeCalled();
78+
79+
socket.emit('MyOtherEvent', {});
80+
81+
expect(l3).toBeCalled();
82+
});
83+
});

0 commit comments

Comments
 (0)