pusher-fake 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (35) hide show
  1. data/features/channel_presence.feature +21 -0
  2. data/features/channel_subscribe.feature +33 -0
  3. data/features/channel_trigger.feature +74 -0
  4. data/features/client_connect.feature +8 -0
  5. data/features/step_definitions/channel_steps.rb +82 -0
  6. data/features/step_definitions/client_steps.rb +28 -0
  7. data/features/step_definitions/event_steps.rb +41 -0
  8. data/features/step_definitions/navigation_steps.rb +3 -0
  9. data/features/step_definitions/presence_steps.rb +6 -0
  10. data/features/support/application.rb +29 -0
  11. data/features/support/application/public/javascripts/vendor/pusher-1.11.js +1155 -0
  12. data/features/support/application/views/index.erb +43 -0
  13. data/features/support/environment.rb +12 -0
  14. data/features/support/pusher-fake.rb +7 -0
  15. data/features/support/wait.rb +9 -0
  16. data/lib/pusher-fake.rb +35 -0
  17. data/lib/pusher-fake/channel.rb +51 -0
  18. data/lib/pusher-fake/channel/presence.rb +50 -0
  19. data/lib/pusher-fake/channel/private.rb +44 -0
  20. data/lib/pusher-fake/channel/public.rb +63 -0
  21. data/lib/pusher-fake/configuration.rb +31 -0
  22. data/lib/pusher-fake/connection.rb +54 -0
  23. data/lib/pusher-fake/server.rb +48 -0
  24. data/lib/pusher-fake/server/application.rb +60 -0
  25. data/spec/lib/pusher-fake/channel/presence_spec.rb +133 -0
  26. data/spec/lib/pusher-fake/channel/private_spec.rb +125 -0
  27. data/spec/lib/pusher-fake/channel/public_spec.rb +90 -0
  28. data/spec/lib/pusher-fake/channel_spec.rb +117 -0
  29. data/spec/lib/pusher-fake/configuration_spec.rb +10 -0
  30. data/spec/lib/pusher-fake/connection_spec.rb +193 -0
  31. data/spec/lib/pusher-fake/server/application_spec.rb +133 -0
  32. data/spec/lib/pusher-fake/server_spec.rb +150 -0
  33. data/spec/spec_helper.rb +12 -0
  34. data/spec/support/have_configuration_option_matcher.rb +19 -0
  35. metadata +265 -0
@@ -0,0 +1,21 @@
1
+ @javascript
2
+ Feature: Client on a presence channel
3
+
4
+ Background:
5
+ Given I am connected
6
+
7
+ Scenario: Client subscribes to a presence channel
8
+ When I subscribe to the "presence-game-1" channel with presence events
9
+ Then I should see 1 client
10
+ When Bob is connected
11
+ Then I should see 1 client
12
+ When Bob is subscribed to the "presence-game-1" channel
13
+ Then I should see 2 clients
14
+
15
+ Scenario: Client unsubscribes from a presence channel, with other clients
16
+ Given Bob is connected
17
+ And Bob is subscribed to the "presence-game-1" channel
18
+ When I subscribe to the "presence-game-1" channel with presence events
19
+ Then I should see 2 clients
20
+ When Bob unsubscribes from the "presence-game-1" channel
21
+ Then I should see 1 client
@@ -0,0 +1,33 @@
1
+ @javascript
2
+ Feature: Client subscribing to a channel
3
+
4
+ Background:
5
+ Given I am connected
6
+
7
+ Scenario: Client subscribes to a channel
8
+ When I subscribe to the "chat-message" channel
9
+ Then I should be subscribed to the "chat-message" channel
10
+
11
+ Scenario: Client subscribes to multiple channels
12
+ When I subscribe to the "chat-enter" channel
13
+ And I subscribe to the "chat-exit" channel
14
+ Then I should be subscribed to the "chat-enter" channel
15
+ And I should be subscribed to the "chat-exit" channel
16
+
17
+ Scenario: Client subscribes to a private channel
18
+ When I subscribe to the "private-message-bob" channel
19
+ Then I should be subscribed to the "private-message-bob" channel
20
+
21
+ Scenario: Client unsuccessfully subscribes to a private channel
22
+ Given I change my socket ID
23
+ When I subscribe to the "private-message-bob" channel
24
+ Then I should not be subscribed to the "private-message-bob" channel
25
+
26
+ Scenario: Client subscribes to a presence channel
27
+ When I subscribe to the "presence-game-1" channel
28
+ Then I should be subscribed to the "presence-game-1" channel
29
+
30
+ Scenario: Client unsuccessfully subscribes to a presence channel
31
+ Given I change my socket ID
32
+ When I subscribe to the "presence-game-1" channel
33
+ Then I should not be subscribed to the "presence-game-1" channel
@@ -0,0 +1,74 @@
1
+ @javascript
2
+ Feature: Triggering events on a channel
3
+
4
+ Background:
5
+ Given I am connected
6
+ And Bob is connected
7
+
8
+ Scenario: Server triggers an event on a subscribed public channel
9
+ Given I am subscribed to the "chat" channel
10
+ And Bob is subscribed to the "chat" channel
11
+ When a "message" event is triggered on the "chat" channel
12
+ Then I should receive a "message" event on the "chat" channel
13
+ And Bob should receive a "message" event on the "chat" channel
14
+
15
+ Scenario: Server triggers an event on a previously subscribed public channel
16
+ Given I am subscribed to the "chat" channel
17
+ And Bob is subscribed to the "chat" channel
18
+ And I unsubscribe from the "chat" channel
19
+ When a "message" event is triggered on the "chat" channel
20
+ Then I should not receive a "message" event on the "chat" channel
21
+ And Bob should receive a "message" event on the "chat" channel
22
+
23
+ Scenario: Server triggers an event on an unsubscribed public channel
24
+ When a "message" event is triggered on the "chat" channel
25
+ Then I should not receive a "message" event on the "chat" channel
26
+ And Bob should not receive a "message" event on the "chat" channel
27
+
28
+ Scenario: Server triggers an event on a subscribed private channel
29
+ Given I am subscribed to the "private-chat" channel
30
+ And Bob is subscribed to the "private-chat" channel
31
+ When a "message" event is triggered on the "private-chat" channel
32
+ Then I should receive a "message" event on the "private-chat" channel
33
+ And Bob should receive a "message" event on the "private-chat" channel
34
+
35
+ Scenario: Server triggers an event on a previously subscribed private channel
36
+ Given I am subscribed to the "private-chat" channel
37
+ And Bob is subscribed to the "private-chat" channel
38
+ And I unsubscribe from the "private-chat" channel
39
+ When a "message" event is triggered on the "private-chat" channel
40
+ Then I should not receive a "message" event on the "private-chat" channel
41
+ And Bob should receive a "message" event on the "private-chat" channel
42
+
43
+ Scenario: Server triggers an event on an unsubscribed private channel
44
+ When a "message" event is triggered on the "private-chat" channel
45
+ Then I should not receive a "message" event on the "private-chat" channel
46
+ And Bob should not receive a "message" event on the "private-chat" channel
47
+
48
+ Scenario: Client triggers a client event on a subscribed private channel
49
+ Given I am subscribed to the "private-chat" channel
50
+ And Bob is subscribed to the "private-chat" channel
51
+ When I trigger the "client-message" event on the "private-chat" channel
52
+ Then I should receive a "client-message" event on the "private-chat" channel
53
+ And Bob should receive a "client-message" event on the "private-chat" channel
54
+
55
+ Scenario: Client triggers a client event on a previously subscribed private channel
56
+ Given I am subscribed to the "private-chat" channel
57
+ And Bob is subscribed to the "private-chat" channel
58
+ And I unsubscribe from the "private-chat" channel
59
+ When I manually trigger the "client-message" event on the "private-chat" channel
60
+ Then I should not receive a "client-message" event on the "private-chat" channel
61
+ And Bob should not receive a "client-message" event on the "private-chat" channel
62
+
63
+ Scenario: Client triggers a client event on an unsubscribed private channel
64
+ Given Bob is subscribed to the "private-chat" channel
65
+ When I manually trigger the "client-message" event on the "private-chat" channel
66
+ Then I should not receive a "client-message" event on the "private-chat" channel
67
+ And Bob should not receive a "client-message" event on the "private-chat" channel
68
+
69
+ Scenario: Client triggers a client event on a subscribed public channel
70
+ Given I am subscribed to the "chat" channel
71
+ And Bob is subscribed to the "chat" channel
72
+ When I trigger the "client-message" event on the "chat" channel
73
+ Then I should not receive a "client-message" event on the "chat" channel
74
+ And Bob should not receive a "client-message" event on the "chat" channel
@@ -0,0 +1,8 @@
1
+ @javascript
2
+ Feature: Client connecting to the server
3
+
4
+ Background:
5
+ Given I am on the homepage
6
+
7
+ Scenario: Client connects to the server
8
+ Then I should be connected
@@ -0,0 +1,82 @@
1
+ Given %{I am subscribed to the "$channel" channel} do |channel|
2
+ step %{I subscribe to the "#{channel}" channel}
3
+ step %{I should be subscribed to the "#{channel}" channel}
4
+ end
5
+
6
+ Given %{$name is subscribed to the "$channel" channel} do |name, channel|
7
+ using_session(name) do
8
+ step %{I am subscribed to the "#{channel}" channel}
9
+ end
10
+ end
11
+
12
+ When %{I subscribe to the "$channel" channel} do |channel|
13
+ page.execute_script("Pusher.instance.subscribe(#{channel.to_json})")
14
+ end
15
+
16
+ When %{I subscribe to the "$channel" channel with presence events} do |channel|
17
+ page.execute_script(%{
18
+ var list = document.querySelector("#presence ul"),
19
+ channel = Pusher.instance.subscribe(#{channel.to_json});
20
+
21
+ channel.bind("pusher:subscription_succeeded", function(clients) {
22
+ var
23
+ count = document.querySelector("#presence header h1 span");
24
+ count.innerHTML = clients.count;
25
+
26
+ clients.each(function(client) {
27
+ var
28
+ element = list.appendChild(document.createElement("li"));
29
+ element.setAttribute("id", "client-" + client.id);
30
+ });
31
+ });
32
+ channel.bind("pusher:member_added", function(client) {
33
+ var
34
+ count = document.querySelector("#presence header h1 span");
35
+ count.innerHTML = parseInt(count.innerHTML, 10) + 1;
36
+
37
+ var
38
+ element = list.appendChild(document.createElement("li"));
39
+ element.setAttribute("id", "client-" + client.id);
40
+ });
41
+ channel.bind("pusher:member_removed", function(client) {
42
+ var item = list.querySelector("li#client-" + client.id),
43
+ count = document.querySelector("#presence header h1 span");
44
+
45
+ count.innerHTML = parseInt(count.innerHTML, 10) - 1;
46
+
47
+ list.removeChild(item);
48
+ });
49
+ })
50
+ end
51
+
52
+ When %{I unsubscribe from the "$channel" channel} do |channel|
53
+ page.execute_script("Pusher.instance.unsubscribe(#{channel.to_json})")
54
+ end
55
+
56
+ When %{$name unsubscribes from the "$channel" channel} do |name, channel|
57
+ using_session(name) do
58
+ step %{I unsubscribe from the "#{channel}" channel}
59
+ end
60
+ end
61
+
62
+ Then %{I should be subscribed to the "$channel" channel} do |channel|
63
+ wait_until do
64
+ subscribed = page.evaluate_script(%{
65
+ var
66
+ channel = Pusher.instance.channel(#{channel.to_json});
67
+ channel && channel.subscribed;
68
+ })
69
+ subscribed == true
70
+ end
71
+ end
72
+
73
+ Then %{I should not be subscribed to the "$channel" channel} do |channel|
74
+ wait do
75
+ subscribed = page.evaluate_script(%{
76
+ var
77
+ channel = Pusher.instance.channel(#{channel.to_json});
78
+ channel && channel.subscribed;
79
+ })
80
+ subscribed.should be_false
81
+ end
82
+ end
@@ -0,0 +1,28 @@
1
+ Given "I am connected" do
2
+ step %{I am on the homepage}
3
+ step %{I should be connected}
4
+ end
5
+
6
+ Given %{$name is connected} do |name|
7
+ using_session(name) do
8
+ step %{I am connected}
9
+ end
10
+ end
11
+
12
+ Given "I change my socket ID" do
13
+ page.execute_script("Pusher.instance.connection.socket_id = -1;")
14
+ end
15
+
16
+ Then "I should be connected" do
17
+ wait_until do
18
+ state = page.evaluate_script("Pusher.instance.connection.state")
19
+ state == "connected"
20
+ end
21
+ end
22
+
23
+ Then "I should not be connected" do
24
+ wait_until do
25
+ state = page.evaluate_script("Pusher.instance.connection.state")
26
+ state == "unavailable"
27
+ end
28
+ end
@@ -0,0 +1,41 @@
1
+ When %{a "$event" event is triggered on the "$channel" channel} do |event, channel|
2
+ Pusher[channel].trigger(event, {})
3
+ end
4
+
5
+ When %{I trigger the "$event" event on the "$channel" channel} do |event, channel|
6
+ page.execute_script(%{
7
+ var
8
+ channel = Pusher.instance.channel(#{channel.to_json});
9
+ channel.trigger(#{event.to_json}, {});
10
+ })
11
+ end
12
+
13
+ When %{I manually trigger the "$event" event on the "$channel" channel} do |event, channel|
14
+ page.execute_script(%{Pusher.instance.send_event(#{event.to_json}, {}, #{channel.to_json})})
15
+ end
16
+
17
+ Then /^I should receive a "([^"]+)" event on the "([^"]+)" channel$/ do |event, channel|
18
+ wait do
19
+ events = page.evaluate_script("Pusher.instance.events[#{[channel, event].join(":").to_json}]")
20
+ events.length.should == 1
21
+ end
22
+ end
23
+
24
+ Then /^I should not receive a "([^"]+)" event on the "([^"]+)" channel$/ do |event, channel|
25
+ wait do
26
+ events = page.evaluate_script("Pusher.instance.events[#{[channel, event].join(":").to_json}]")
27
+ events.should be_nil
28
+ end
29
+ end
30
+
31
+ Then /^([^I]+) should receive a "([^"]+)" event on the "([^"]+)" channel$/ do |name, event, channel|
32
+ using_session(name) do
33
+ step %{I should receive a "#{event}" event on the "#{channel}" channel}
34
+ end
35
+ end
36
+
37
+ Then /^([^I]+) should not receive a "([^"]+)" event on the "([^"]+)" channel$/ do |name, event, channel|
38
+ using_session(name) do
39
+ step %{I should not receive a "#{event}" event on the "#{channel}" channel}
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ Given "I am on the homepage" do
2
+ visit "/"
3
+ end
@@ -0,0 +1,6 @@
1
+ Then /^I should see (\d+) clients?$/ do |count|
2
+ within("#presence") do
3
+ should have_css("header h1 span", text: count)
4
+ should have_css("ul li", count: count.to_i)
5
+ end
6
+ end
@@ -0,0 +1,29 @@
1
+ require "pusher"
2
+ require "sinatra"
3
+
4
+ Pusher.url = "http://PUSHER_API_KEY:PUSHER_API_SECRET@localhost:8081/apps/PUSHER_APP_ID"
5
+
6
+ class Sinatra::Application
7
+ set :root, Proc.new { File.join(File.dirname(__FILE__), "application") }
8
+ set :views, Proc.new { File.join(root, "views") }
9
+ set :public_folder, Proc.new { File.join(root, "public") }
10
+
11
+ disable :logging
12
+
13
+ get "/" do
14
+ erb :index
15
+ end
16
+
17
+ post "/pusher/auth" do
18
+ channel = Pusher[params[:channel_name]]
19
+
20
+ if params[:channel_name] =~ /^private-/
21
+ channel.authenticate(params[:socket_id]).to_json
22
+ elsif params[:channel_name] =~ /^presence-/
23
+ channel.authenticate(params[:socket_id], {
24
+ user_id: params[:socket_id],
25
+ user_info: {}
26
+ }).to_json
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,1155 @@
1
+ /*!
2
+ * Pusher JavaScript Library v1.11.0
3
+ * http://pusherapp.com/
4
+ *
5
+ * Copyright 2011, Pusher
6
+ * Released under the MIT licence.
7
+ */
8
+
9
+ if (Function.prototype.scopedTo === undefined) {
10
+ Function.prototype.scopedTo = function(context, args) {
11
+ var f = this;
12
+ return function() {
13
+ return f.apply(context, Array.prototype.slice.call(args || [])
14
+ .concat(Array.prototype.slice.call(arguments)));
15
+ };
16
+ };
17
+ }
18
+
19
+ var Pusher = function(app_key, options) {
20
+ this.options = options || {};
21
+ this.key = app_key;
22
+ this.channels = new Pusher.Channels();
23
+ this.global_emitter = new Pusher.EventsDispatcher()
24
+
25
+ var self = this;
26
+
27
+ this.checkAppKey();
28
+
29
+ this.connection = new Pusher.Connection(this.key, this.options);
30
+
31
+ // Setup / teardown connection
32
+ this.connection
33
+ .bind('connected', function() {
34
+ self.subscribeAll();
35
+ })
36
+ .bind('message', function(params) {
37
+ var internal = (params.event.indexOf('pusher_internal:') === 0);
38
+ if (params.channel) {
39
+ var channel;
40
+ if (channel = self.channel(params.channel)) {
41
+ channel.emit(params.event, params.data);
42
+ }
43
+ }
44
+ // Emit globaly [deprecated]
45
+ if (!internal) self.global_emitter.emit(params.event, params.data);
46
+ })
47
+ .bind('disconnected', function() {
48
+ self.channels.disconnect();
49
+ })
50
+ .bind('error', function(err) {
51
+ Pusher.warn('Error', err);
52
+ });
53
+
54
+ Pusher.instances.push(this);
55
+
56
+ if (Pusher.isReady) self.connect();
57
+ };
58
+ Pusher.instances = [];
59
+ Pusher.prototype = {
60
+ channel: function(name) {
61
+ return this.channels.find(name);
62
+ },
63
+
64
+ connect: function() {
65
+ this.connection.connect();
66
+ },
67
+
68
+ disconnect: function() {
69
+ this.connection.disconnect();
70
+ },
71
+
72
+ bind: function(event_name, callback) {
73
+ this.global_emitter.bind(event_name, callback);
74
+ return this;
75
+ },
76
+
77
+ bind_all: function(callback) {
78
+ this.global_emitter.bind_all(callback);
79
+ return this;
80
+ },
81
+
82
+ subscribeAll: function() {
83
+ var channel;
84
+ for (channel in this.channels.channels) {
85
+ if (this.channels.channels.hasOwnProperty(channel)) {
86
+ this.subscribe(channel);
87
+ }
88
+ }
89
+ },
90
+
91
+ subscribe: function(channel_name) {
92
+ var self = this;
93
+ var channel = this.channels.add(channel_name, this);
94
+ if (this.connection.state === 'connected') {
95
+ channel.authorize(this, function(err, data) {
96
+ if (err) {
97
+ channel.emit('pusher:subscription_error', data);
98
+ } else {
99
+ self.send_event('pusher:subscribe', {
100
+ channel: channel_name,
101
+ auth: data.auth,
102
+ channel_data: data.channel_data
103
+ });
104
+ }
105
+ });
106
+ }
107
+ return channel;
108
+ },
109
+
110
+ unsubscribe: function(channel_name) {
111
+ this.channels.remove(channel_name);
112
+ if (this.connection.state === 'connected') {
113
+ this.send_event('pusher:unsubscribe', {
114
+ channel: channel_name
115
+ });
116
+ }
117
+ },
118
+
119
+ send_event: function(event_name, data, channel) {
120
+ return this.connection.send_event(event_name, data, channel);
121
+ },
122
+
123
+ checkAppKey: function() {
124
+ if(this.key === null || this.key === undefined) {
125
+ Pusher.warn('Warning', 'You must pass your app key when you instantiate Pusher.');
126
+ }
127
+ }
128
+ };
129
+
130
+ Pusher.Util = {
131
+ extend: function extend(target, extensions) {
132
+ for (var property in extensions) {
133
+ if (extensions[property] && extensions[property].constructor &&
134
+ extensions[property].constructor === Object) {
135
+ target[property] = extend(target[property] || {}, extensions[property]);
136
+ } else {
137
+ target[property] = extensions[property];
138
+ }
139
+ }
140
+ return target;
141
+ },
142
+
143
+ stringify: function stringify() {
144
+ var m = ["Pusher"]
145
+ for (var i = 0; i < arguments.length; i++){
146
+ if (typeof arguments[i] === "string") {
147
+ m.push(arguments[i])
148
+ } else {
149
+ if (window['JSON'] == undefined) {
150
+ m.push(arguments[i].toString());
151
+ } else {
152
+ m.push(JSON.stringify(arguments[i]))
153
+ }
154
+ }
155
+ };
156
+ return m.join(" : ")
157
+ },
158
+
159
+ arrayIndexOf: function(array, item) { // MSIE doesn't have array.indexOf
160
+ var nativeIndexOf = Array.prototype.indexOf;
161
+ if (array == null) return -1;
162
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
163
+ for (i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
164
+ return -1;
165
+ }
166
+ };
167
+
168
+ // To receive log output provide a Pusher.log function, for example
169
+ // Pusher.log = function(m){console.log(m)}
170
+ Pusher.debug = function() {
171
+ if (!Pusher.log) return
172
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments))
173
+ }
174
+ Pusher.warn = function() {
175
+ if (window.console && window.console.warn) {
176
+ window.console.warn(Pusher.Util.stringify.apply(this, arguments));
177
+ } else {
178
+ if (!Pusher.log) return
179
+ Pusher.log(Pusher.Util.stringify.apply(this, arguments));
180
+ }
181
+ };
182
+
183
+ // Pusher defaults
184
+ Pusher.VERSION = '1.11.0';
185
+
186
+ Pusher.host = 'ws.pusherapp.com';
187
+ Pusher.ws_port = 80;
188
+ Pusher.wss_port = 443;
189
+ Pusher.channel_auth_endpoint = '/pusher/auth';
190
+ Pusher.cdn_http = 'http://js.pusher.com/'
191
+ Pusher.cdn_https = 'https://d3dy5gmtp8yhk7.cloudfront.net/'
192
+ Pusher.dependency_suffix = '';
193
+ Pusher.channel_auth_transport = 'ajax';
194
+ Pusher.activity_timeout = 120000;
195
+ Pusher.pong_timeout = 30000;
196
+
197
+ Pusher.isReady = false;
198
+ Pusher.ready = function() {
199
+ Pusher.isReady = true;
200
+ for (var i = 0, l = Pusher.instances.length; i < l; i++) {
201
+ Pusher.instances[i].connect();
202
+ }
203
+ };
204
+
205
+ ;(function() {
206
+ /* Abstract event binding
207
+ Example:
208
+
209
+ var MyEventEmitter = function(){};
210
+ MyEventEmitter.prototype = new Pusher.EventsDispatcher;
211
+
212
+ var emitter = new MyEventEmitter();
213
+
214
+ // Bind to single event
215
+ emitter.bind('foo_event', function(data){ alert(data)} );
216
+
217
+ // Bind to all
218
+ emitter.bind_all(function(event_name, data){ alert(data) });
219
+
220
+ --------------------------------------------------------*/
221
+ function EventsDispatcher(failThrough) {
222
+ this.callbacks = {};
223
+ this.global_callbacks = [];
224
+ // Run this function when dispatching an event when no callbacks defined
225
+ this.failThrough = failThrough;
226
+ }
227
+
228
+ EventsDispatcher.prototype.bind = function(event_name, callback) {
229
+ this.callbacks[event_name] = this.callbacks[event_name] || [];
230
+ this.callbacks[event_name].push(callback);
231
+ return this;// chainable
232
+ };
233
+
234
+ EventsDispatcher.prototype.unbind = function(eventName, callback) {
235
+ if(this.callbacks[eventName]) {
236
+ var index = Pusher.Util.arrayIndexOf(this.callbacks[eventName], callback);
237
+ this.callbacks[eventName].splice(index, 1);
238
+ }
239
+ return this;
240
+ };
241
+
242
+ EventsDispatcher.prototype.emit = function(event_name, data) {
243
+ // Global callbacks
244
+ for (var i = 0; i < this.global_callbacks.length; i++) {
245
+ this.global_callbacks[i](event_name, data);
246
+ }
247
+
248
+ // Event callbacks
249
+ var callbacks = this.callbacks[event_name];
250
+ if (callbacks) {
251
+ for (var i = 0; i < callbacks.length; i++) {
252
+ callbacks[i](data);
253
+ }
254
+ } else if (this.failThrough) {
255
+ this.failThrough(event_name, data)
256
+ }
257
+
258
+ return this;
259
+ };
260
+
261
+ EventsDispatcher.prototype.bind_all = function(callback) {
262
+ this.global_callbacks.push(callback);
263
+ return this;
264
+ };
265
+
266
+ this.Pusher.EventsDispatcher = EventsDispatcher;
267
+ }).call(this);
268
+
269
+ ;(function() {
270
+ var Pusher = this.Pusher;
271
+
272
+ /*-----------------------------------------------
273
+ Helpers:
274
+ -----------------------------------------------*/
275
+
276
+ function capitalize(str) {
277
+ return str.substr(0, 1).toUpperCase() + str.substr(1);
278
+ }
279
+
280
+
281
+ function safeCall(method, obj, data) {
282
+ if (obj[method] !== undefined) {
283
+ obj[method](data);
284
+ }
285
+ }
286
+
287
+ /*-----------------------------------------------
288
+ The State Machine
289
+ -----------------------------------------------*/
290
+ function Machine(initialState, transitions, stateActions) {
291
+ Pusher.EventsDispatcher.call(this);
292
+
293
+ this.state = undefined;
294
+ this.errors = [];
295
+
296
+ // functions for each state
297
+ this.stateActions = stateActions;
298
+
299
+ // set up the transitions
300
+ this.transitions = transitions;
301
+
302
+ this.transition(initialState);
303
+ };
304
+
305
+ Machine.prototype.transition = function(nextState, data) {
306
+ var prevState = this.state;
307
+ var stateCallbacks = this.stateActions;
308
+
309
+ if (prevState && (Pusher.Util.arrayIndexOf(this.transitions[prevState], nextState) == -1)) {
310
+ throw new Error('Invalid transition [' + prevState + ' to ' + nextState + ']');
311
+ }
312
+
313
+ // exit
314
+ safeCall(prevState + 'Exit', stateCallbacks, data);
315
+
316
+ // tween
317
+ safeCall(prevState + 'To' + capitalize(nextState), stateCallbacks, data);
318
+
319
+ // pre
320
+ safeCall(nextState + 'Pre', stateCallbacks, data);
321
+
322
+ // change state:
323
+ this.state = nextState;
324
+
325
+ // handy to bind to
326
+ this.emit('state_change', {
327
+ oldState: prevState,
328
+ newState: nextState
329
+ });
330
+
331
+ // Post:
332
+ safeCall(nextState + 'Post', stateCallbacks, data);
333
+ };
334
+
335
+ Machine.prototype.is = function(state) {
336
+ return this.state === state;
337
+ };
338
+
339
+ Machine.prototype.isNot = function(state) {
340
+ return this.state !== state;
341
+ };
342
+
343
+ Pusher.Util.extend(Machine.prototype, Pusher.EventsDispatcher.prototype);
344
+
345
+ this.Pusher.Machine = Machine;
346
+ }).call(this);
347
+
348
+ ;(function() {
349
+ /*
350
+ A little bauble to interface with window.navigator.onLine,
351
+ window.ononline and window.onoffline. Easier to mock.
352
+ */
353
+ var NetInfo = function() {
354
+ var self = this;
355
+ Pusher.EventsDispatcher.call(this);
356
+ // This is okay, as IE doesn't support this stuff anyway.
357
+ if (window.addEventListener !== undefined) {
358
+ window.addEventListener("online", function() {
359
+ self.emit('online', null);
360
+ }, false);
361
+ window.addEventListener("offline", function() {
362
+ self.emit('offline', null);
363
+ }, false);
364
+ }
365
+ };
366
+
367
+ // Offline means definitely offline (no connection to router).
368
+ // Inverse does NOT mean definitely online (only currently supported in Safari
369
+ // and even there only means the device has a connection to the router).
370
+ NetInfo.prototype.isOnLine = function() {
371
+ if (window.navigator.onLine === undefined) {
372
+ return true;
373
+ } else {
374
+ return window.navigator.onLine;
375
+ }
376
+ };
377
+
378
+ Pusher.Util.extend(NetInfo.prototype, Pusher.EventsDispatcher.prototype);
379
+
380
+ this.Pusher.NetInfo = NetInfo;
381
+ }).call(this);
382
+
383
+ ;(function() {
384
+ var Pusher = this.Pusher;
385
+
386
+ var machineTransitions = {
387
+ 'initialized': ['waiting', 'failed'],
388
+ 'waiting': ['connecting', 'permanentlyClosed'],
389
+ 'connecting': ['open', 'permanentlyClosing', 'impermanentlyClosing', 'waiting'],
390
+ 'open': ['connected', 'permanentlyClosing', 'impermanentlyClosing', 'waiting'],
391
+ 'connected': ['permanentlyClosing', 'impermanentlyClosing', 'waiting'],
392
+ 'impermanentlyClosing': ['waiting', 'permanentlyClosing'],
393
+ 'permanentlyClosing': ['permanentlyClosed'],
394
+ 'permanentlyClosed': ['waiting'],
395
+ 'failed': ['permanentlyClosing']
396
+ };
397
+
398
+
399
+ // Amount to add to time between connection attemtpts per failed attempt.
400
+ var UNSUCCESSFUL_CONNECTION_ATTEMPT_ADDITIONAL_WAIT = 2000;
401
+ var UNSUCCESSFUL_OPEN_ATTEMPT_ADDITIONAL_TIMEOUT = 2000;
402
+ var UNSUCCESSFUL_CONNECTED_ATTEMPT_ADDITIONAL_TIMEOUT = 2000;
403
+
404
+ var MAX_CONNECTION_ATTEMPT_WAIT = 5 * UNSUCCESSFUL_CONNECTION_ATTEMPT_ADDITIONAL_WAIT;
405
+ var MAX_OPEN_ATTEMPT_TIMEOUT = 5 * UNSUCCESSFUL_OPEN_ATTEMPT_ADDITIONAL_TIMEOUT;
406
+ var MAX_CONNECTED_ATTEMPT_TIMEOUT = 5 * UNSUCCESSFUL_CONNECTED_ATTEMPT_ADDITIONAL_TIMEOUT;
407
+
408
+ function resetConnectionParameters(connection) {
409
+ connection.connectionWait = 0;
410
+
411
+ if (Pusher.TransportType === 'flash') {
412
+ // Flash needs a bit more time
413
+ connection.openTimeout = 5000;
414
+ } else {
415
+ connection.openTimeout = 2000;
416
+ }
417
+ connection.connectedTimeout = 2000;
418
+ connection.connectionSecure = connection.compulsorySecure;
419
+ connection.connectionAttempts = 0;
420
+ }
421
+
422
+ function Connection(key, options) {
423
+ var self = this;
424
+
425
+ Pusher.EventsDispatcher.call(this);
426
+
427
+ this.options = Pusher.Util.extend({encrypted: false}, options);
428
+
429
+ this.netInfo = new Pusher.NetInfo();
430
+
431
+ this.netInfo.bind('online', function(){
432
+ if (self._machine.is('waiting')) {
433
+ self._machine.transition('connecting');
434
+ triggerStateChange('connecting');
435
+ }
436
+ });
437
+
438
+ this.netInfo.bind('offline', function() {
439
+ if (self._machine.is('connected')) {
440
+ // These are for Chrome 15, which ends up
441
+ // having two sockets hanging around.
442
+ self.socket.onclose = undefined;
443
+ self.socket.onmessage = undefined;
444
+ self.socket.onerror = undefined;
445
+ self.socket.onopen = undefined;
446
+
447
+ self.socket.close();
448
+ self.socket = undefined;
449
+ self._machine.transition('waiting');
450
+ }
451
+ });
452
+
453
+ // define the state machine that runs the connection
454
+ this._machine = new Pusher.Machine('initialized', machineTransitions, {
455
+
456
+ // TODO: Use the constructor for this.
457
+ initializedPre: function() {
458
+ self.compulsorySecure = self.options.encrypted;
459
+
460
+ self.key = key;
461
+ self.socket = null;
462
+ self.socket_id = null;
463
+
464
+ self.state = 'initialized';
465
+ },
466
+
467
+ waitingPre: function() {
468
+ if (self.connectionWait > 0) {
469
+ self.emit('connecting_in', self.connectionWait);
470
+ }
471
+
472
+ if (self.netInfo.isOnLine() === false || self.connectionAttempts > 4){
473
+ triggerStateChange('unavailable');
474
+ } else {
475
+ triggerStateChange('connecting');
476
+ }
477
+
478
+ if (self.netInfo.isOnLine() === true) {
479
+ self._waitingTimer = setTimeout(function() {
480
+ self._machine.transition('connecting');
481
+ }, self.connectionWait);
482
+ }
483
+ },
484
+
485
+ waitingExit: function() {
486
+ clearTimeout(self._waitingTimer);
487
+ },
488
+
489
+ connectingPre: function() {
490
+ // Case that a user manages to get to the connecting
491
+ // state even when offline.
492
+ if (self.netInfo.isOnLine() === false) {
493
+ self._machine.transition('waiting');
494
+ triggerStateChange('unavailable');
495
+
496
+ return;
497
+ }
498
+
499
+ // removed: if not closed, something is wrong that we should fix
500
+ // if(self.socket !== undefined) self.socket.close();
501
+ var url = formatURL(self.key, self.connectionSecure);
502
+ Pusher.debug('Connecting', url);
503
+ self.socket = new Pusher.Transport(url);
504
+ // now that the socket connection attempt has been started,
505
+ // set up the callbacks fired by the socket for different outcomes
506
+ self.socket.onopen = ws_onopen;
507
+ self.socket.onclose = transitionToWaiting;
508
+ self.socket.onerror = ws_onError;
509
+
510
+ // allow time to get ws_onOpen, otherwise close socket and try again
511
+ self._connectingTimer = setTimeout(TransitionToImpermanentClosing, self.openTimeout);
512
+ },
513
+
514
+ connectingExit: function() {
515
+ clearTimeout(self._connectingTimer);
516
+ },
517
+
518
+ connectingToWaiting: function() {
519
+ updateConnectionParameters();
520
+
521
+ // FUTURE: update only ssl
522
+ },
523
+
524
+ connectingToImpermanentlyClosing: function() {
525
+ updateConnectionParameters();
526
+
527
+ // FUTURE: update only timeout
528
+ },
529
+
530
+ openPre: function() {
531
+ self.socket.onmessage = ws_onMessageOpen;
532
+ self.socket.onerror = ws_onError;
533
+ self.socket.onclose = transitionToWaiting;
534
+
535
+ // allow time to get connected-to-Pusher message, otherwise close socket, try again
536
+ self._openTimer = setTimeout(TransitionToImpermanentClosing, self.connectedTimeout);
537
+ },
538
+
539
+ openExit: function() {
540
+ clearTimeout(self._openTimer);
541
+ },
542
+
543
+ openToWaiting: function() {
544
+ updateConnectionParameters();
545
+ },
546
+
547
+ openToImpermanentlyClosing: function() {
548
+ updateConnectionParameters();
549
+ },
550
+
551
+ connectedPre: function(socket_id) {
552
+ self.socket_id = socket_id;
553
+
554
+ self.socket.onmessage = ws_onMessageConnected;
555
+ self.socket.onerror = ws_onError;
556
+ self.socket.onclose = transitionToWaiting;
557
+
558
+ resetConnectionParameters(self);
559
+
560
+ resetActivityCheck();
561
+ },
562
+
563
+ connectedPost: function() {
564
+ triggerStateChange('connected');
565
+ },
566
+
567
+ connectedExit: function() {
568
+ stopActivityCheck();
569
+ triggerStateChange('disconnected');
570
+ },
571
+
572
+ impermanentlyClosingPost: function() {
573
+ if (self.socket) {
574
+ self.socket.onclose = transitionToWaiting;
575
+ self.socket.close();
576
+ }
577
+ },
578
+
579
+ permanentlyClosingPost: function() {
580
+ if (self.socket) {
581
+ self.socket.onclose = function() {
582
+ resetConnectionParameters(self);
583
+ self._machine.transition('permanentlyClosed');
584
+ };
585
+
586
+ self.socket.close();
587
+ } else {
588
+ resetConnectionParameters(self);
589
+ self._machine.transition('permanentlyClosed');
590
+ }
591
+ },
592
+
593
+ failedPre: function() {
594
+ triggerStateChange('failed');
595
+ Pusher.debug('WebSockets are not available in this browser.');
596
+ }
597
+ });
598
+
599
+ /*-----------------------------------------------
600
+ -----------------------------------------------*/
601
+
602
+ function updateConnectionParameters() {
603
+ if (self.connectionWait < MAX_CONNECTION_ATTEMPT_WAIT) {
604
+ self.connectionWait += UNSUCCESSFUL_CONNECTION_ATTEMPT_ADDITIONAL_WAIT;
605
+ }
606
+
607
+ if (self.openTimeout < MAX_OPEN_ATTEMPT_TIMEOUT) {
608
+ self.openTimeout += UNSUCCESSFUL_OPEN_ATTEMPT_ADDITIONAL_TIMEOUT;
609
+ }
610
+
611
+ if (self.connectedTimeout < MAX_CONNECTED_ATTEMPT_TIMEOUT) {
612
+ self.connectedTimeout += UNSUCCESSFUL_CONNECTED_ATTEMPT_ADDITIONAL_TIMEOUT;
613
+ }
614
+
615
+ if (self.compulsorySecure !== true) {
616
+ self.connectionSecure = !self.connectionSecure;
617
+ }
618
+
619
+ self.connectionAttempts++;
620
+ }
621
+
622
+ function formatURL(key, isSecure) {
623
+ var port = Pusher.ws_port;
624
+ var protocol = 'ws://';
625
+
626
+ // Always connect with SSL if the current page has
627
+ // been loaded via HTTPS.
628
+ //
629
+ // FUTURE: Always connect using SSL.
630
+ //
631
+ if (isSecure || document.location.protocol === 'https:') {
632
+ port = Pusher.wss_port;
633
+ protocol = 'wss://';
634
+ }
635
+
636
+ return protocol + Pusher.host + ':' + port + '/app/' + key + '?client=js&version=' + Pusher.VERSION;
637
+ }
638
+
639
+ // callback for close and retry. Used on timeouts.
640
+ function TransitionToImpermanentClosing() {
641
+ self._machine.transition('impermanentlyClosing');
642
+ }
643
+
644
+ function resetActivityCheck() {
645
+ if (self._activityTimer) { clearTimeout(self._activityTimer); }
646
+ // Send ping after inactivity
647
+ self._activityTimer = setTimeout(function() {
648
+ self.send_event('pusher:ping', {})
649
+ // Wait for pong response
650
+ self._activityTimer = setTimeout(function() {
651
+ self.socket.close();
652
+ }, (self.options.pong_timeout || Pusher.pong_timeout))
653
+ }, (self.options.activity_timeout || Pusher.activity_timeout))
654
+ }
655
+
656
+ function stopActivityCheck() {
657
+ if (self._activityTimer) { clearTimeout(self._activityTimer); }
658
+ }
659
+
660
+ /*-----------------------------------------------
661
+ WebSocket Callbacks
662
+ -----------------------------------------------*/
663
+
664
+ // no-op, as we only care when we get pusher:connection_established
665
+ function ws_onopen() {
666
+ self._machine.transition('open');
667
+ };
668
+
669
+ function ws_onMessageOpen(event) {
670
+ var params = parseWebSocketEvent(event);
671
+ if (params !== undefined) {
672
+ if (params.event === 'pusher:connection_established') {
673
+ self._machine.transition('connected', params.data.socket_id);
674
+ } else if (params.event === 'pusher:error') {
675
+ // first inform the end-developer of this error
676
+ self.emit('error', {type: 'PusherError', data: params.data});
677
+
678
+ switch (params.data.code) {
679
+ case 4000:
680
+ Pusher.warn(params.data.message);
681
+
682
+ self.compulsorySecure = true;
683
+ self.connectionSecure = true;
684
+ self.options.encrypted = true;
685
+ break;
686
+ case 4001:
687
+ // App not found by key - close connection
688
+ self._machine.transition('permanentlyClosing');
689
+ break;
690
+ }
691
+ }
692
+ }
693
+ }
694
+
695
+ function ws_onMessageConnected(event) {
696
+ resetActivityCheck();
697
+
698
+ var params = parseWebSocketEvent(event);
699
+ if (params !== undefined) {
700
+ Pusher.debug('Event recd', params);
701
+
702
+ switch (params.event) {
703
+ case 'pusher:error':
704
+ self.emit('error', {type: 'PusherError', data: params.data});
705
+ break;
706
+ case 'pusher:ping':
707
+ self.send_event('pusher:pong', {})
708
+ break;
709
+ }
710
+
711
+ self.emit('message', params);
712
+ }
713
+ }
714
+
715
+
716
+ /**
717
+ * Parses an event from the WebSocket to get
718
+ * the JSON payload that we require
719
+ *
720
+ * @param {MessageEvent} event The event from the WebSocket.onmessage handler.
721
+ **/
722
+ function parseWebSocketEvent(event) {
723
+ try {
724
+ var params = JSON.parse(event.data);
725
+
726
+ if (typeof params.data === 'string') {
727
+ try {
728
+ params.data = JSON.parse(params.data);
729
+ } catch (e) {
730
+ if (!(e instanceof SyntaxError)) {
731
+ throw e;
732
+ }
733
+ }
734
+ }
735
+
736
+ return params;
737
+ } catch (e) {
738
+ self.emit('error', {type: 'MessageParseError', error: e, data: event.data});
739
+ }
740
+ }
741
+
742
+ function transitionToWaiting() {
743
+ self._machine.transition('waiting');
744
+ }
745
+
746
+ function ws_onError() {
747
+ self.emit('error', {
748
+ type: 'WebSocketError'
749
+ });
750
+
751
+ // note: required? is the socket auto closed in the case of error?
752
+ self.socket.close();
753
+ self._machine.transition('impermanentlyClosing');
754
+ }
755
+
756
+ function triggerStateChange(newState, data) {
757
+ // avoid emitting and changing the state
758
+ // multiple times when it's the same.
759
+ if (self.state === newState) return;
760
+
761
+ var prevState = self.state;
762
+
763
+ self.state = newState;
764
+
765
+ Pusher.debug('State changed', prevState + ' -> ' + newState);
766
+
767
+ self.emit('state_change', {previous: prevState, current: newState});
768
+ self.emit(newState, data);
769
+ }
770
+ };
771
+
772
+ Connection.prototype.connect = function() {
773
+ // no WebSockets
774
+ if (Pusher.Transport === null || Pusher.Transport === undefined) {
775
+ this._machine.transition('failed');
776
+ }
777
+ // initial open of connection
778
+ else if(this._machine.is('initialized')) {
779
+ resetConnectionParameters(this);
780
+ this._machine.transition('waiting');
781
+ }
782
+ // user skipping connection wait
783
+ else if (this._machine.is('waiting') && this.netInfo.isOnLine() === true) {
784
+ this._machine.transition('connecting');
785
+ }
786
+ // user re-opening connection after closing it
787
+ else if(this._machine.is("permanentlyClosed")) {
788
+ this._machine.transition('waiting');
789
+ }
790
+ };
791
+
792
+ Connection.prototype.send = function(data) {
793
+ if (this._machine.is('connected')) {
794
+ this.socket.send(data);
795
+ return true;
796
+ } else {
797
+ return false;
798
+ }
799
+ };
800
+
801
+ Connection.prototype.send_event = function(event_name, data, channel) {
802
+ var payload = {
803
+ event: event_name,
804
+ data: data
805
+ };
806
+ if (channel) payload['channel'] = channel;
807
+
808
+ Pusher.debug('Event sent', payload);
809
+ return this.send(JSON.stringify(payload));
810
+ }
811
+
812
+ Connection.prototype.disconnect = function() {
813
+ if (this._machine.is('permanentlyClosed')) return;
814
+
815
+ if (this._machine.is('waiting')) {
816
+ this._machine.transition('permanentlyClosed');
817
+ } else {
818
+ this._machine.transition('permanentlyClosing');
819
+ }
820
+ };
821
+
822
+ Pusher.Util.extend(Connection.prototype, Pusher.EventsDispatcher.prototype);
823
+ this.Pusher.Connection = Connection;
824
+ }).call(this);
825
+
826
+ Pusher.Channels = function() {
827
+ this.channels = {};
828
+ };
829
+
830
+ Pusher.Channels.prototype = {
831
+ add: function(channel_name, pusher) {
832
+ var existing_channel = this.find(channel_name);
833
+ if (!existing_channel) {
834
+ var channel = Pusher.Channel.factory(channel_name, pusher);
835
+ this.channels[channel_name] = channel;
836
+ return channel;
837
+ } else {
838
+ return existing_channel;
839
+ }
840
+ },
841
+
842
+ find: function(channel_name) {
843
+ return this.channels[channel_name];
844
+ },
845
+
846
+ remove: function(channel_name) {
847
+ delete this.channels[channel_name];
848
+ },
849
+
850
+ disconnect: function () {
851
+ for(var channel_name in this.channels){
852
+ this.channels[channel_name].disconnect()
853
+ }
854
+ }
855
+ };
856
+
857
+ Pusher.Channel = function(channel_name, pusher) {
858
+ var self = this;
859
+ Pusher.EventsDispatcher.call(this, function(event_name, event_data) {
860
+ Pusher.debug('No callbacks on ' + channel_name + ' for ' + event_name);
861
+ });
862
+
863
+ this.pusher = pusher;
864
+ this.name = channel_name;
865
+ this.subscribed = false;
866
+
867
+ this.bind('pusher_internal:subscription_succeeded', function(data) {
868
+ self.onSubscriptionSucceeded(data);
869
+ });
870
+ };
871
+
872
+ Pusher.Channel.prototype = {
873
+ // inheritable constructor
874
+ init: function() {},
875
+ disconnect: function() {},
876
+
877
+ onSubscriptionSucceeded: function(data) {
878
+ this.subscribed = true;
879
+ this.emit('pusher:subscription_succeeded');
880
+ },
881
+
882
+ authorize: function(pusher, callback){
883
+ callback(false, {}); // normal channels don't require auth
884
+ },
885
+
886
+ trigger: function(event, data) {
887
+ return this.pusher.send_event(event, data, this.name);
888
+ }
889
+ };
890
+
891
+ Pusher.Util.extend(Pusher.Channel.prototype, Pusher.EventsDispatcher.prototype);
892
+
893
+
894
+
895
+ Pusher.auth_callbacks = {};
896
+
897
+ Pusher.authorizers = {
898
+ ajax: function(pusher, callback){
899
+ var self = this, xhr;
900
+
901
+ if (Pusher.XHR) {
902
+ xhr = new Pusher.XHR();
903
+ } else {
904
+ xhr = (window.XMLHttpRequest ? new window.XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP"));
905
+ }
906
+
907
+ xhr.open("POST", Pusher.channel_auth_endpoint, true);
908
+ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded")
909
+ xhr.onreadystatechange = function() {
910
+ if (xhr.readyState == 4) {
911
+ if (xhr.status == 200) {
912
+ var data, parsed = false;
913
+
914
+ try {
915
+ data = JSON.parse(xhr.responseText);
916
+ parsed = true;
917
+ } catch (e) {
918
+ callback(true, 'JSON returned from webapp was invalid, yet status code was 200. Data was: ' + xhr.responseText);
919
+ }
920
+
921
+ if (parsed) { // prevents double execution.
922
+ callback(false, data);
923
+ }
924
+ } else {
925
+ Pusher.warn("Couldn't get auth info from your webapp", status);
926
+ callback(true, xhr.status);
927
+ }
928
+ }
929
+ };
930
+ xhr.send('socket_id=' + encodeURIComponent(pusher.connection.socket_id) + '&channel_name=' + encodeURIComponent(self.name));
931
+ },
932
+ jsonp: function(pusher, callback){
933
+ var qstring = 'socket_id=' + encodeURIComponent(pusher.connection.socket_id) + '&channel_name=' + encodeURIComponent(this.name);
934
+ var script = document.createElement("script");
935
+ // Hacked wrapper.
936
+ Pusher.auth_callbacks[this.name] = function(data) {
937
+ callback(false, data);
938
+ };
939
+ var callback_name = "Pusher.auth_callbacks['" + this.name + "']";
940
+ script.src = Pusher.channel_auth_endpoint+'?callback='+encodeURIComponent(callback_name)+'&'+qstring;
941
+ var head = document.getElementsByTagName("head")[0] || document.documentElement;
942
+ head.insertBefore( script, head.firstChild );
943
+ }
944
+ };
945
+
946
+ Pusher.Channel.PrivateChannel = {
947
+ authorize: function(pusher, callback){
948
+ Pusher.authorizers[Pusher.channel_auth_transport].scopedTo(this)(pusher, callback);
949
+ }
950
+ };
951
+
952
+ Pusher.Channel.PresenceChannel = {
953
+ init: function(){
954
+ this.bind('pusher_internal:member_added', function(data){
955
+ var member = this.members.add(data.user_id, data.user_info);
956
+ this.emit('pusher:member_added', member);
957
+ }.scopedTo(this))
958
+
959
+ this.bind('pusher_internal:member_removed', function(data){
960
+ var member = this.members.remove(data.user_id);
961
+ if (member) {
962
+ this.emit('pusher:member_removed', member);
963
+ }
964
+ }.scopedTo(this))
965
+ },
966
+
967
+ disconnect: function(){
968
+ this.members.clear();
969
+ },
970
+
971
+ onSubscriptionSucceeded: function(data) {
972
+ this.members._members_map = data.presence.hash;
973
+ this.members.count = data.presence.count;
974
+ this.subscribed = true;
975
+
976
+ this.emit('pusher:subscription_succeeded', this.members);
977
+ },
978
+
979
+ members: {
980
+ _members_map: {},
981
+ count: 0,
982
+
983
+ each: function(callback) {
984
+ for(var i in this._members_map) {
985
+ callback({
986
+ id: i,
987
+ info: this._members_map[i]
988
+ });
989
+ }
990
+ },
991
+
992
+ add: function(id, info) {
993
+ this._members_map[id] = info;
994
+ this.count++;
995
+ return this.get(id);
996
+ },
997
+
998
+ remove: function(user_id) {
999
+ var member = this.get(user_id);
1000
+ if (member) {
1001
+ delete this._members_map[user_id];
1002
+ this.count--;
1003
+ }
1004
+ return member;
1005
+ },
1006
+
1007
+ get: function(user_id) {
1008
+ if (this._members_map.hasOwnProperty(user_id)) { // have heard of this user user_id
1009
+ return {
1010
+ id: user_id,
1011
+ info: this._members_map[user_id]
1012
+ }
1013
+ } else { // have never heard of this user
1014
+ return null;
1015
+ }
1016
+ },
1017
+
1018
+ clear: function() {
1019
+ this._members_map = {};
1020
+ this.count = 0;
1021
+ }
1022
+ }
1023
+ };
1024
+
1025
+ Pusher.Channel.factory = function(channel_name, pusher){
1026
+ var channel = new Pusher.Channel(channel_name, pusher);
1027
+ if (channel_name.indexOf('private-') === 0) {
1028
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1029
+ } else if (channel_name.indexOf('presence-') === 0) {
1030
+ Pusher.Util.extend(channel, Pusher.Channel.PrivateChannel);
1031
+ Pusher.Util.extend(channel, Pusher.Channel.PresenceChannel);
1032
+ };
1033
+ channel.init();
1034
+ return channel;
1035
+ };
1036
+
1037
+ var _require = (function () {
1038
+
1039
+ var handleScriptLoaded;
1040
+ if (document.addEventListener) {
1041
+ handleScriptLoaded = function (elem, callback) {
1042
+ elem.addEventListener('load', callback, false)
1043
+ }
1044
+ } else {
1045
+ handleScriptLoaded = function(elem, callback) {
1046
+ elem.attachEvent('onreadystatechange', function () {
1047
+ if(elem.readyState == 'loaded' || elem.readyState == 'complete') callback()
1048
+ })
1049
+ }
1050
+ }
1051
+
1052
+ return function (deps, callback) {
1053
+ var dep_count = 0,
1054
+ dep_length = deps.length;
1055
+
1056
+ function checkReady (callback) {
1057
+ dep_count++;
1058
+ if ( dep_length == dep_count ) {
1059
+ // Opera needs the timeout for page initialization weirdness
1060
+ setTimeout(callback, 0);
1061
+ }
1062
+ }
1063
+
1064
+ function addScript (src, callback) {
1065
+ callback = callback || function(){}
1066
+ var head = document.getElementsByTagName('head')[0];
1067
+ var script = document.createElement('script');
1068
+ script.setAttribute('src', src);
1069
+ script.setAttribute("type","text/javascript");
1070
+ script.setAttribute('async', true);
1071
+
1072
+ handleScriptLoaded(script, function () {
1073
+ checkReady(callback);
1074
+ });
1075
+
1076
+ head.appendChild(script);
1077
+ }
1078
+
1079
+ for(var i = 0; i < dep_length; i++) {
1080
+ addScript(deps[i], callback);
1081
+ }
1082
+ }
1083
+ })();
1084
+
1085
+ ;(function() {
1086
+ var cdn = (document.location.protocol == 'http:') ? Pusher.cdn_http : Pusher.cdn_https;
1087
+ var root = cdn + Pusher.VERSION;
1088
+ var deps = [];
1089
+
1090
+ if (window['JSON'] === undefined) {
1091
+ deps.push(root + '/json2' + Pusher.dependency_suffix + '.js');
1092
+ }
1093
+ if (window['WebSocket'] === undefined && window['MozWebSocket'] === undefined) {
1094
+ // We manually initialize web-socket-js to iron out cross browser issues
1095
+ window.WEB_SOCKET_DISABLE_AUTO_INITIALIZATION = true;
1096
+ deps.push(root + '/flashfallback' + Pusher.dependency_suffix + '.js');
1097
+ }
1098
+
1099
+ var initialize = function() {
1100
+ if (window['WebSocket'] === undefined && window['MozWebSocket'] === undefined) {
1101
+ return function() {
1102
+ // This runs after flashfallback.js has loaded
1103
+ if (window['WebSocket'] !== undefined && window['MozWebSocket'] === undefined) {
1104
+ // window['WebSocket'] is a flash emulation of WebSocket
1105
+ Pusher.Transport = window['WebSocket'];
1106
+ Pusher.TransportType = 'flash';
1107
+
1108
+ window.WEB_SOCKET_SWF_LOCATION = root + "/WebSocketMain.swf";
1109
+ WebSocket.__addTask(function() {
1110
+ Pusher.ready();
1111
+ })
1112
+ WebSocket.__initialize();
1113
+ } else {
1114
+ // Flash must not be installed
1115
+ Pusher.Transport = null;
1116
+ Pusher.TransportType = 'none';
1117
+ Pusher.ready();
1118
+ }
1119
+ }
1120
+ } else {
1121
+ return function() {
1122
+ // This is because Mozilla have decided to
1123
+ // prefix the WebSocket constructor with "Moz".
1124
+ if (window['MozWebSocket'] !== undefined) {
1125
+ Pusher.Transport = window['MozWebSocket'];
1126
+ } else {
1127
+ Pusher.Transport = window['WebSocket'];
1128
+ }
1129
+ // We have some form of a native websocket,
1130
+ // even if the constructor is prefixed:
1131
+ Pusher.TransportType = 'native';
1132
+
1133
+ // Initialise Pusher.
1134
+ Pusher.ready();
1135
+ }
1136
+ }
1137
+ }();
1138
+
1139
+ var ondocumentbody = function(callback) {
1140
+ var load_body = function() {
1141
+ document.body ? callback() : setTimeout(load_body, 0);
1142
+ }
1143
+ load_body();
1144
+ };
1145
+
1146
+ var initializeOnDocumentBody = function() {
1147
+ ondocumentbody(initialize);
1148
+ }
1149
+
1150
+ if (deps.length > 0) {
1151
+ _require(deps, initializeOnDocumentBody);
1152
+ } else {
1153
+ initializeOnDocumentBody();
1154
+ }
1155
+ })();