opal-phoenix 0.0.7 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: df6879b1e256c3b413a8b7f32a6e589afc27b78f
4
- data.tar.gz: 626bf02f1075747fc059e34ed2655aff671d5e09
2
+ SHA256:
3
+ metadata.gz: 38ac7cf7be4678294fd06dc5f09bcb1b1b25e89c8f1f460f1667565a2db0b46e
4
+ data.tar.gz: 07e31ef83dbda2ff2997ab026f406bd0889eb3f43e7104ee3404b5f1dbde19da
5
5
  SHA512:
6
- metadata.gz: 7118bbd0f3cdbaaa010695fa0e58513869dc3080651b7adec6cd8824c65058757a19c16119e9d4b2a78532e4c8b3280009cbcb6df4afe4de75c5cc011ef5fe02
7
- data.tar.gz: c0e4fbf6f3941554b79aabba3699edb929ded07d9dfa8bb0eed631ce2cf0725db7b6f85a39e4ba033d69ad1e0dab799c4363e1ac8dc1820b5fbad292eac25b8d
6
+ metadata.gz: f6a758d3bd9f8760049a2c30000fc82d7ef80f2366cb7acd24d56ae673b87f099dca853b8fb2804cc35c2b9b4a27181ef7c62cf252089bdcf5046f927a8ff74b
7
+ data.tar.gz: fa341b8f9f8fb71ccfec3f977888e60b499cce11ad6c83092a9cea21cf15cf86ee8b161064105bc7a3fb62158b70cb6914aca9ff7880796cba990976dd1a8cfe
@@ -1,17 +1,18 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'opal-phoenix'
3
- s.version = '0.0.7'
3
+ s.version = '0.0.8'
4
4
  s.authors = ['Michał Kalbarczyk']
5
5
  s.email = 'fazibear@gmail.com'
6
6
  s.homepage = 'http://github.com/fazibear/opal-phoenix'
7
7
  s.summary = 'Phoenix client wrapper for opal'
8
8
  s.description = 'Phoenix client wrapper for opal'
9
+ s.license = 'MIT'
9
10
 
10
11
  s.files = `git ls-files`.split("\n")
11
12
  s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
12
13
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
13
14
  s.require_paths = ['lib']
14
15
 
15
- s.add_dependency 'opal'
16
- s.add_development_dependency 'rake'
16
+ s.add_dependency 'opal', '~> 0'
17
+ s.add_development_dependency 'rake', '~> 0'
17
18
  end
@@ -12,7 +12,7 @@ module Phoenix
12
12
 
13
13
  def callback(block)
14
14
  proc do |e|
15
- block.call(Native(e))
15
+ block.call(Hash.new(e))
16
16
  end
17
17
  end
18
18
  end
@@ -1,185 +1,217 @@
1
- (function(exports){
1
+ (function (global, factory) {
2
+ typeof exports === 'object' ? factory(exports) :
3
+ typeof define === 'function' && define.amd ? define(['exports'], factory) :
4
+ factory(global.Phoenix = global.Phoenix || {});
5
+ }(this, (function (exports) {
2
6
  "use strict";
3
7
 
4
- var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
5
-
6
8
  Object.defineProperty(exports, "__esModule", {
7
9
  value: true
8
10
  });
9
11
 
10
- function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
12
+ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
11
13
 
12
- function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; }
14
+ var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
15
+
16
+ var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
17
+
18
+ function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
13
19
 
14
20
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
15
21
 
16
- // Phoenix Channels JavaScript client
17
- //
18
- // ## Socket Connection
19
- //
20
- // A single connection is established to the server and
21
- // channels are multiplexed over the connection.
22
- // Connect to the server using the `Socket` class:
23
- //
24
- // let socket = new Socket("/ws", {params: {userToken: "123"}})
25
- // socket.connect()
26
- //
27
- // The `Socket` constructor takes the mount point of the socket,
28
- // the authentication params, as well as options that can be found in
29
- // the Socket docs, such as configuring the `LongPoll` transport, and
30
- // heartbeat.
31
- //
32
- // ## Channels
33
- //
34
- // Channels are isolated, concurrent processes on the server that
35
- // subscribe to topics and broker events between the client and server.
36
- // To join a channel, you must provide the topic, and channel params for
37
- // authorization. Here's an example chat room example where `"new_msg"`
38
- // events are listened for, messages are pushed to the server, and
39
- // the channel is joined with ok/error/timeout matches:
40
- //
41
- // let channel = socket.channel("rooms:123", {token: roomToken})
42
- // channel.on("new_msg", msg => console.log("Got message", msg) )
43
- // $input.onEnter( e => {
44
- // channel.push("new_msg", {body: e.target.val}, 10000)
45
- // .receive("ok", (msg) => console.log("created message", msg) )
46
- // .receive("error", (reasons) => console.log("create failed", reasons) )
47
- // .receive("timeout", () => console.log("Networking issue...") )
48
- // })
49
- // channel.join()
50
- // .receive("ok", ({messages}) => console.log("catching up", messages) )
51
- // .receive("error", ({reason}) => console.log("failed join", reason) )
52
- // .receive("timeout", () => console.log("Networking issue. Still waiting...") )
53
- //
54
- //
55
- // ## Joining
56
- //
57
- // Creating a channel with `socket.channel(topic, params)`, binds the params to
58
- // `channel.params`, which are sent up on `channel.join()`.
59
- // Subsequent rejoins will send up the modified params for
60
- // updating authorization params, or passing up last_message_id information.
61
- // Successful joins receive an "ok" status, while unsuccessful joins
62
- // receive "error".
63
- //
64
- //
65
- // ## Pushing Messages
66
- //
67
- // From the previous example, we can see that pushing messages to the server
68
- // can be done with `channel.push(eventName, payload)` and we can optionally
69
- // receive responses from the push. Additionally, we can use
70
- // `receive("timeout", callback)` to abort waiting for our other `receive` hooks
71
- // and take action after some period of waiting. The default timeout is 5000ms.
72
- //
73
- //
74
- // ## Socket Hooks
75
- //
76
- // Lifecycle events of the multiplexed connection can be hooked into via
77
- // `socket.onError()` and `socket.onClose()` events, ie:
78
- //
79
- // socket.onError( () => console.log("there was an error with the connection!") )
80
- // socket.onClose( () => console.log("the connection dropped") )
81
- //
82
- //
83
- // ## Channel Hooks
84
- //
85
- // For each joined channel, you can bind to `onError` and `onClose` events
86
- // to monitor the channel lifecycle, ie:
87
- //
88
- // channel.onError( () => console.log("there was an error!") )
89
- // channel.onClose( () => console.log("the channel has gone away gracefully") )
90
- //
91
- // ### onError hooks
92
- //
93
- // `onError` hooks are invoked if the socket connection drops, or the channel
94
- // crashes on the server. In either case, a channel rejoin is attemtped
95
- // automatically in an exponential backoff manner.
96
- //
97
- // ### onClose hooks
98
- //
99
- // `onClose` hooks are invoked only in two cases. 1) the channel explicitly
100
- // closed on the server, or 2). The client explicitly closed, by calling
101
- // `channel.leave()`
102
- //
103
- //
104
- // ## Presence
105
- //
106
- // The `Presence` object provides features for syncing presence information
107
- // from the server with the client and handling presences joining and leaving.
108
- //
109
- // ### Syncing initial state from the server
110
- //
111
- // `Presence.syncState` is used to sync the list of presences on the server
112
- // with the client's state. An optional `onJoin` and `onLeave` callback can
113
- // be provided to react to changes in the client's local presences across
114
- // disconnects and reconnects with the server.
115
- //
116
- // `Presence.syncDiff` is used to sync a diff of presence join and leave
117
- // events from the server, as they happen. Like `syncState`, `syncDiff`
118
- // accepts optional `onJoin` and `onLeave` callbacks to react to a user
119
- // joining or leaving from a device.
120
- //
121
- // ### Listing Presences
122
- //
123
- // `Presence.list` is used to return a list of presence information
124
- // based on the local state of metadata. By default, all presence
125
- // metadata is returned, but a `listBy` function can be supplied to
126
- // allow the client to select which metadata to use for a given presence.
127
- // For example, you may have a user online from different devices with a
128
- // a metadata status of "online", but they have set themselves to "away"
129
- // on another device. In this case, they app may choose to use the "away"
130
- // status for what appears on the UI. The example below defines a `listBy`
131
- // function which prioritizes the first metadata which was registered for
132
- // each user. This could be the first tab they opened, or the first device
133
- // they came online from:
134
- //
135
- // let state = {}
136
- // Presence.syncState(state, stateFromServer)
137
- // let listBy = (id, {metas: [first, ...rest]}) => {
138
- // first.count = rest.length + 1 // count of this user's presences
139
- // first.id = id
140
- // return first
141
- // }
142
- // let onlineUsers = Presence.list(state, listBy)
143
- //
144
- //
145
- // ### Example Usage
146
- //
147
- // // detect if user has joined for the 1st time or from another tab/device
148
- // let onJoin = (id, current, newPres) => {
149
- // if(!current){
150
- // console.log("user has entered for the first time", newPres)
151
- // } else {
152
- // console.log("user additional presence", newPres)
153
- // }
154
- // }
155
- // // detect if user has left from all tabs/devices, or is still present
156
- // let onLeave = (id, current, leftPres) => {
157
- // if(current.metas.length === 0){
158
- // console.log("user has left from all devices", leftPres)
159
- // } else {
160
- // console.log("user left from a device", leftPres)
161
- // }
162
- // }
163
- // let presences = {} // client's initial empty presence state
164
- // // receive initial presence data from server, sent after join
165
- // myChannel.on("presences", state => {
166
- // Presence.syncState(presences, state, onJoin, onLeave)
167
- // displayUsers(Presence.list(presences))
168
- // })
169
- // // receive "presence_diff" from server, containing join/leave events
170
- // myChannel.on("presence_diff", diff => {
171
- // Presence.syncDiff(presences, diff, onJoin, onLeave)
172
- // this.setState({users: Presence.list(room.presences, listBy)})
173
- // })
174
- //
175
- var VSN = "1.0.0";
22
+ /**
23
+ * Phoenix Channels JavaScript client
24
+ *
25
+ * ## Socket Connection
26
+ *
27
+ * A single connection is established to the server and
28
+ * channels are multiplexed over the connection.
29
+ * Connect to the server using the `Socket` class:
30
+ *
31
+ * ```javascript
32
+ * let socket = new Socket("/socket", {params: {userToken: "123"}})
33
+ * socket.connect()
34
+ * ```
35
+ *
36
+ * The `Socket` constructor takes the mount point of the socket,
37
+ * the authentication params, as well as options that can be found in
38
+ * the Socket docs, such as configuring the `LongPoll` transport, and
39
+ * heartbeat.
40
+ *
41
+ * ## Channels
42
+ *
43
+ * Channels are isolated, concurrent processes on the server that
44
+ * subscribe to topics and broker events between the client and server.
45
+ * To join a channel, you must provide the topic, and channel params for
46
+ * authorization. Here's an example chat room example where `"new_msg"`
47
+ * events are listened for, messages are pushed to the server, and
48
+ * the channel is joined with ok/error/timeout matches:
49
+ *
50
+ * ```javascript
51
+ * let channel = socket.channel("room:123", {token: roomToken})
52
+ * channel.on("new_msg", msg => console.log("Got message", msg) )
53
+ * $input.onEnter( e => {
54
+ * channel.push("new_msg", {body: e.target.val}, 10000)
55
+ * .receive("ok", (msg) => console.log("created message", msg) )
56
+ * .receive("error", (reasons) => console.log("create failed", reasons) )
57
+ * .receive("timeout", () => console.log("Networking issue...") )
58
+ * })
59
+ *
60
+ * channel.join()
61
+ * .receive("ok", ({messages}) => console.log("catching up", messages) )
62
+ * .receive("error", ({reason}) => console.log("failed join", reason) )
63
+ * .receive("timeout", () => console.log("Networking issue. Still waiting..."))
64
+ *```
65
+ *
66
+ * ## Joining
67
+ *
68
+ * Creating a channel with `socket.channel(topic, params)`, binds the params to
69
+ * `channel.params`, which are sent up on `channel.join()`.
70
+ * Subsequent rejoins will send up the modified params for
71
+ * updating authorization params, or passing up last_message_id information.
72
+ * Successful joins receive an "ok" status, while unsuccessful joins
73
+ * receive "error".
74
+ *
75
+ * ## Duplicate Join Subscriptions
76
+ *
77
+ * While the client may join any number of topics on any number of channels,
78
+ * the client may only hold a single subscription for each unique topic at any
79
+ * given time. When attempting to create a duplicate subscription,
80
+ * the server will close the existing channel, log a warning, and
81
+ * spawn a new channel for the topic. The client will have their
82
+ * `channel.onClose` callbacks fired for the existing channel, and the new
83
+ * channel join will have its receive hooks processed as normal.
84
+ *
85
+ * ## Pushing Messages
86
+ *
87
+ * From the previous example, we can see that pushing messages to the server
88
+ * can be done with `channel.push(eventName, payload)` and we can optionally
89
+ * receive responses from the push. Additionally, we can use
90
+ * `receive("timeout", callback)` to abort waiting for our other `receive` hooks
91
+ * and take action after some period of waiting. The default timeout is 10000ms.
92
+ *
93
+ *
94
+ * ## Socket Hooks
95
+ *
96
+ * Lifecycle events of the multiplexed connection can be hooked into via
97
+ * `socket.onError()` and `socket.onClose()` events, ie:
98
+ *
99
+ * ```javascript
100
+ * socket.onError( () => console.log("there was an error with the connection!") )
101
+ * socket.onClose( () => console.log("the connection dropped") )
102
+ * ```
103
+ *
104
+ *
105
+ * ## Channel Hooks
106
+ *
107
+ * For each joined channel, you can bind to `onError` and `onClose` events
108
+ * to monitor the channel lifecycle, ie:
109
+ *
110
+ * ```javascript
111
+ * channel.onError( () => console.log("there was an error!") )
112
+ * channel.onClose( () => console.log("the channel has gone away gracefully") )
113
+ * ```
114
+ *
115
+ * ### onError hooks
116
+ *
117
+ * `onError` hooks are invoked if the socket connection drops, or the channel
118
+ * crashes on the server. In either case, a channel rejoin is attempted
119
+ * automatically in an exponential backoff manner.
120
+ *
121
+ * ### onClose hooks
122
+ *
123
+ * `onClose` hooks are invoked only in two cases. 1) the channel explicitly
124
+ * closed on the server, or 2). The client explicitly closed, by calling
125
+ * `channel.leave()`
126
+ *
127
+ *
128
+ * ## Presence
129
+ *
130
+ * The `Presence` object provides features for syncing presence information
131
+ * from the server with the client and handling presences joining and leaving.
132
+ *
133
+ * ### Syncing initial state from the server
134
+ *
135
+ * `Presence.syncState` is used to sync the list of presences on the server
136
+ * with the client's state. An optional `onJoin` and `onLeave` callback can
137
+ * be provided to react to changes in the client's local presences across
138
+ * disconnects and reconnects with the server.
139
+ *
140
+ * `Presence.syncDiff` is used to sync a diff of presence join and leave
141
+ * events from the server, as they happen. Like `syncState`, `syncDiff`
142
+ * accepts optional `onJoin` and `onLeave` callbacks to react to a user
143
+ * joining or leaving from a device.
144
+ *
145
+ * ### Listing Presences
146
+ *
147
+ * `Presence.list` is used to return a list of presence information
148
+ * based on the local state of metadata. By default, all presence
149
+ * metadata is returned, but a `listBy` function can be supplied to
150
+ * allow the client to select which metadata to use for a given presence.
151
+ * For example, you may have a user online from different devices with
152
+ * a metadata status of "online", but they have set themselves to "away"
153
+ * on another device. In this case, the app may choose to use the "away"
154
+ * status for what appears on the UI. The example below defines a `listBy`
155
+ * function which prioritizes the first metadata which was registered for
156
+ * each user. This could be the first tab they opened, or the first device
157
+ * they came online from:
158
+ *
159
+ * ```javascript
160
+ * let state = {}
161
+ * state = Presence.syncState(state, stateFromServer)
162
+ * let listBy = (id, {metas: [first, ...rest]}) => {
163
+ * first.count = rest.length + 1 // count of this user's presences
164
+ * first.id = id
165
+ * return first
166
+ * }
167
+ * let onlineUsers = Presence.list(state, listBy)
168
+ * ```
169
+ *
170
+ *
171
+ * ### Example Usage
172
+ * ```javascript
173
+ * // detect if user has joined for the 1st time or from another tab/device
174
+ * let onJoin = (id, current, newPres) => {
175
+ * if(!current){
176
+ * console.log("user has entered for the first time", newPres)
177
+ * } else {
178
+ * console.log("user additional presence", newPres)
179
+ * }
180
+ * }
181
+ * // detect if user has left from all tabs/devices, or is still present
182
+ * let onLeave = (id, current, leftPres) => {
183
+ * if(current.metas.length === 0){
184
+ * console.log("user has left from all devices", leftPres)
185
+ * } else {
186
+ * console.log("user left from a device", leftPres)
187
+ * }
188
+ * }
189
+ * let presences = {} // client's initial empty presence state
190
+ * // receive initial presence data from server, sent after join
191
+ * myChannel.on("presence_state", state => {
192
+ * presences = Presence.syncState(presences, state, onJoin, onLeave)
193
+ * displayUsers(Presence.list(presences))
194
+ * })
195
+ * // receive "presence_diff" from server, containing join/leave events
196
+ * myChannel.on("presence_diff", diff => {
197
+ * presences = Presence.syncDiff(presences, diff, onJoin, onLeave)
198
+ * this.setState({users: Presence.list(room.presences, listBy)})
199
+ * })
200
+ * ```
201
+ * @module phoenix
202
+ */
203
+
204
+ var global = typeof self !== "undefined" ? self : window;
205
+ var VSN = "2.0.0";
176
206
  var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
177
207
  var DEFAULT_TIMEOUT = 10000;
208
+ var WS_CLOSE_NORMAL = 1000;
178
209
  var CHANNEL_STATES = {
179
210
  closed: "closed",
180
211
  errored: "errored",
181
212
  joined: "joined",
182
- joining: "joining"
213
+ joining: "joining",
214
+ leaving: "leaving"
183
215
  };
184
216
  var CHANNEL_EVENTS = {
185
217
  close: "phx_close",
@@ -188,21 +220,21 @@ var CHANNEL_EVENTS = {
188
220
  reply: "phx_reply",
189
221
  leave: "phx_leave"
190
222
  };
223
+ var CHANNEL_LIFECYCLE_EVENTS = [CHANNEL_EVENTS.close, CHANNEL_EVENTS.error, CHANNEL_EVENTS.join, CHANNEL_EVENTS.reply, CHANNEL_EVENTS.leave];
191
224
  var TRANSPORTS = {
192
225
  longpoll: "longpoll",
193
226
  websocket: "websocket"
194
227
  };
195
228
 
196
- var Push = (function () {
197
-
198
- // Initializes the Push
199
- //
200
- // channel - The Channel
201
- // event - The event, for example `"phx_join"`
202
- // payload - The payload, for example `{user_id: 123}`
203
- // timeout - The push timeout in milliseconds
204
- //
229
+ /**
230
+ * Initializes the Push
231
+ * @param {Channel} channel - The Channel
232
+ * @param {string} event - The event, for example `"phx_join"`
233
+ * @param {Object} payload - The payload, for example `{user_id: 123}`
234
+ * @param {number} timeout - The push timeout in milliseconds
235
+ */
205
236
 
237
+ var Push = function () {
206
238
  function Push(channel, event, payload, timeout) {
207
239
  _classCallCheck(this, Push);
208
240
 
@@ -216,17 +248,24 @@ var Push = (function () {
216
248
  this.sent = false;
217
249
  }
218
250
 
251
+ /**
252
+ *
253
+ * @param {number} timeout
254
+ */
255
+
256
+
219
257
  _createClass(Push, [{
220
258
  key: "resend",
221
259
  value: function resend(timeout) {
222
260
  this.timeout = timeout;
223
- this.cancelRefEvent();
224
- this.ref = null;
225
- this.refEvent = null;
226
- this.receivedResp = null;
227
- this.sent = false;
261
+ this.reset();
228
262
  this.send();
229
263
  }
264
+
265
+ /**
266
+ *
267
+ */
268
+
230
269
  }, {
231
270
  key: "send",
232
271
  value: function send() {
@@ -239,9 +278,17 @@ var Push = (function () {
239
278
  topic: this.channel.topic,
240
279
  event: this.event,
241
280
  payload: this.payload,
242
- ref: this.ref
281
+ ref: this.ref,
282
+ join_ref: this.channel.joinRef()
243
283
  });
244
284
  }
285
+
286
+ /**
287
+ *
288
+ * @param {*} status
289
+ * @param {*} callback
290
+ */
291
+
245
292
  }, {
246
293
  key: "receive",
247
294
  value: function receive(status, callback) {
@@ -253,14 +300,43 @@ var Push = (function () {
253
300
  return this;
254
301
  }
255
302
 
256
- // private
303
+ /**
304
+ * Updates the Push payload for subsequent resends
305
+ *
306
+ * @param {Object} payload
307
+ * @returns {Push}
308
+ */
309
+
310
+ }, {
311
+ key: "updatePayload",
312
+ value: function updatePayload(payload) {
313
+ this.payload = payload;
314
+ }
315
+
316
+ /**
317
+ * @private
318
+ */
319
+
320
+ }, {
321
+ key: "reset",
322
+ value: function reset() {
323
+ this.cancelRefEvent();
324
+ this.ref = null;
325
+ this.refEvent = null;
326
+ this.receivedResp = null;
327
+ this.sent = false;
328
+ }
329
+
330
+ /**
331
+ * @private
332
+ */
257
333
 
258
334
  }, {
259
335
  key: "matchReceive",
260
336
  value: function matchReceive(_ref) {
261
- var status = _ref.status;
262
- var response = _ref.response;
263
- var ref = _ref.ref;
337
+ var status = _ref.status,
338
+ response = _ref.response,
339
+ ref = _ref.ref;
264
340
 
265
341
  this.recHooks.filter(function (h) {
266
342
  return h.status === status;
@@ -268,6 +344,11 @@ var Push = (function () {
268
344
  return h.callback(response);
269
345
  });
270
346
  }
347
+
348
+ /**
349
+ * @private
350
+ */
351
+
271
352
  }, {
272
353
  key: "cancelRefEvent",
273
354
  value: function cancelRefEvent() {
@@ -276,19 +357,29 @@ var Push = (function () {
276
357
  }
277
358
  this.channel.off(this.refEvent);
278
359
  }
360
+
361
+ /**
362
+ * @private
363
+ */
364
+
279
365
  }, {
280
366
  key: "cancelTimeout",
281
367
  value: function cancelTimeout() {
282
368
  clearTimeout(this.timeoutTimer);
283
369
  this.timeoutTimer = null;
284
370
  }
371
+
372
+ /**
373
+ * @private
374
+ */
375
+
285
376
  }, {
286
377
  key: "startTimeout",
287
378
  value: function startTimeout() {
288
379
  var _this = this;
289
380
 
290
381
  if (this.timeoutTimer) {
291
- return;
382
+ this.cancelTimeout();
292
383
  }
293
384
  this.ref = this.channel.socket.makeRef();
294
385
  this.refEvent = this.channel.replyEventName(this.ref);
@@ -304,11 +395,21 @@ var Push = (function () {
304
395
  _this.trigger("timeout", {});
305
396
  }, this.timeout);
306
397
  }
398
+
399
+ /**
400
+ * @private
401
+ */
402
+
307
403
  }, {
308
404
  key: "hasReceived",
309
405
  value: function hasReceived(status) {
310
406
  return this.receivedResp && this.receivedResp.status === status;
311
407
  }
408
+
409
+ /**
410
+ * @private
411
+ */
412
+
312
413
  }, {
313
414
  key: "trigger",
314
415
  value: function trigger(status, response) {
@@ -317,9 +418,17 @@ var Push = (function () {
317
418
  }]);
318
419
 
319
420
  return Push;
320
- })();
421
+ }();
321
422
 
322
- var Channel = exports.Channel = (function () {
423
+ /**
424
+ *
425
+ * @param {string} topic
426
+ * @param {Object} params
427
+ * @param {Socket} socket
428
+ */
429
+
430
+
431
+ var Channel = exports.Channel = function () {
323
432
  function Channel(topic, params, socket) {
324
433
  var _this2 = this;
325
434
 
@@ -330,6 +439,7 @@ var Channel = exports.Channel = (function () {
330
439
  this.params = params || {};
331
440
  this.socket = socket;
332
441
  this.bindings = [];
442
+ this.bindingRef = 0;
333
443
  this.timeout = this.socket.timeout;
334
444
  this.joinedOnce = false;
335
445
  this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
@@ -346,22 +456,28 @@ var Channel = exports.Channel = (function () {
346
456
  _this2.pushBuffer = [];
347
457
  });
348
458
  this.onClose(function () {
349
- _this2.socket.log("channel", "close " + _this2.topic);
459
+ _this2.rejoinTimer.reset();
460
+ _this2.socket.log("channel", "close " + _this2.topic + " " + _this2.joinRef());
350
461
  _this2.state = CHANNEL_STATES.closed;
351
462
  _this2.socket.remove(_this2);
352
463
  });
353
464
  this.onError(function (reason) {
465
+ if (_this2.isLeaving() || _this2.isClosed()) {
466
+ return;
467
+ }
354
468
  _this2.socket.log("channel", "error " + _this2.topic, reason);
355
469
  _this2.state = CHANNEL_STATES.errored;
356
470
  _this2.rejoinTimer.scheduleTimeout();
357
471
  });
358
472
  this.joinPush.receive("timeout", function () {
359
- if (_this2.state !== CHANNEL_STATES.joining) {
473
+ if (!_this2.isJoining()) {
360
474
  return;
361
475
  }
362
-
363
- _this2.socket.log("channel", "timeout " + _this2.topic, _this2.joinPush.timeout);
476
+ _this2.socket.log("channel", "timeout " + _this2.topic + " (" + _this2.joinRef() + ")", _this2.joinPush.timeout);
477
+ var leavePush = new Push(_this2, CHANNEL_EVENTS.leave, {}, _this2.timeout);
478
+ leavePush.send();
364
479
  _this2.state = CHANNEL_STATES.errored;
480
+ _this2.joinPush.reset();
365
481
  _this2.rejoinTimer.scheduleTimeout();
366
482
  });
367
483
  this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
@@ -369,6 +485,11 @@ var Channel = exports.Channel = (function () {
369
485
  });
370
486
  }
371
487
 
488
+ /**
489
+ * @private
490
+ */
491
+
492
+
372
493
  _createClass(Channel, [{
373
494
  key: "rejoinUntilConnected",
374
495
  value: function rejoinUntilConnected() {
@@ -377,52 +498,127 @@ var Channel = exports.Channel = (function () {
377
498
  this.rejoin();
378
499
  }
379
500
  }
501
+
502
+ /**
503
+ * Join the channel
504
+ * @param {integer} timeout
505
+ * @returns {Push}
506
+ */
507
+
380
508
  }, {
381
509
  key: "join",
382
510
  value: function join() {
383
- var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
511
+ var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
384
512
 
385
513
  if (this.joinedOnce) {
386
514
  throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
387
515
  } else {
388
516
  this.joinedOnce = true;
517
+ this.rejoin(timeout);
518
+ return this.joinPush;
389
519
  }
390
- this.rejoin(timeout);
391
- return this.joinPush;
392
520
  }
521
+
522
+ /**
523
+ * Updates the params passed as the second argument to `new Channel("topic", params, socket)`
524
+ * Any subsequent reconnects on the channel will send the updated params to the `join` callback on the sever
525
+ *
526
+ * @param {Object} params
527
+ */
528
+
529
+ }, {
530
+ key: "updateJoinParams",
531
+ value: function updateJoinParams() {
532
+ var params = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
533
+
534
+ this.params = params;
535
+ this.joinPush.updatePayload(params);
536
+ }
537
+
538
+ /**
539
+ * Hook into channel close
540
+ * @param {Function} callback
541
+ */
542
+
393
543
  }, {
394
544
  key: "onClose",
395
545
  value: function onClose(callback) {
396
546
  this.on(CHANNEL_EVENTS.close, callback);
397
547
  }
548
+
549
+ /**
550
+ * Hook into channel errors
551
+ * @param {Function} callback
552
+ */
553
+
398
554
  }, {
399
555
  key: "onError",
400
556
  value: function onError(callback) {
401
- this.on(CHANNEL_EVENTS.error, function (reason) {
557
+ return this.on(CHANNEL_EVENTS.error, function (reason) {
402
558
  return callback(reason);
403
559
  });
404
560
  }
561
+
562
+ /**
563
+ * Subscribes on channel events
564
+ *
565
+ * Subscription returns a ref counter, which can be used later to
566
+ * unsubscribe the exact event listener
567
+ *
568
+ * @example
569
+ * const ref1 = channel.on("event", do_stuff)
570
+ * const ref2 = channel.on("event", do_other_stuff)
571
+ * channel.off("event", ref1)
572
+ * // Since unsubscription, do_stuff won't fire,
573
+ * // while do_other_stuff will keep firing on the "event"
574
+ *
575
+ * @param {string} event
576
+ * @param {Function} callback
577
+ * @returns {integer} ref
578
+ */
579
+
405
580
  }, {
406
581
  key: "on",
407
582
  value: function on(event, callback) {
408
- this.bindings.push({ event: event, callback: callback });
583
+ var ref = this.bindingRef++;
584
+ this.bindings.push({ event: event, ref: ref, callback: callback });
585
+ return ref;
409
586
  }
587
+
588
+ /**
589
+ * @param {string} event
590
+ * @param {integer} ref
591
+ */
592
+
410
593
  }, {
411
594
  key: "off",
412
- value: function off(event) {
595
+ value: function off(event, ref) {
413
596
  this.bindings = this.bindings.filter(function (bind) {
414
- return bind.event !== event;
597
+ return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref));
415
598
  });
416
599
  }
600
+
601
+ /**
602
+ * @private
603
+ */
604
+
417
605
  }, {
418
606
  key: "canPush",
419
607
  value: function canPush() {
420
- return this.socket.isConnected() && this.state === CHANNEL_STATES.joined;
608
+ return this.socket.isConnected() && this.isJoined();
421
609
  }
610
+
611
+ /**
612
+ * @param {string} event
613
+ * @param {Object} payload
614
+ * @param {number} [timeout]
615
+ * @returns {Push}
616
+ */
617
+
422
618
  }, {
423
619
  key: "push",
424
620
  value: function push(event, payload) {
425
- var timeout = arguments.length <= 2 || arguments[2] === undefined ? this.timeout : arguments[2];
621
+ var timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.timeout;
426
622
 
427
623
  if (!this.joinedOnce) {
428
624
  throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
@@ -438,26 +634,31 @@ var Channel = exports.Channel = (function () {
438
634
  return pushEvent;
439
635
  }
440
636
 
441
- // Leaves the channel
442
- //
443
- // Unsubscribes from server events, and
444
- // instructs channel to terminate on server
445
- //
446
- // Triggers onClose() hooks
447
- //
448
- // To receive leave acknowledgements, use the a `receive`
449
- // hook to bind to the server ack, ie:
450
- //
451
- // channel.leave().receive("ok", () => alert("left!") )
452
- //
637
+ /** Leaves the channel
638
+ *
639
+ * Unsubscribes from server events, and
640
+ * instructs channel to terminate on server
641
+ *
642
+ * Triggers onClose() hooks
643
+ *
644
+ * To receive leave acknowledgements, use the a `receive`
645
+ * hook to bind to the server ack, ie:
646
+ *
647
+ * @example
648
+ * channel.leave().receive("ok", () => alert("left!") )
649
+ *
650
+ * @param {integer} timeout
651
+ * @returns {Push}
652
+ */
453
653
 
454
654
  }, {
455
655
  key: "leave",
456
656
  value: function leave() {
457
657
  var _this3 = this;
458
658
 
459
- var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
659
+ var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
460
660
 
661
+ this.state = CHANNEL_STATES.leaving;
461
662
  var onClose = function onClose() {
462
663
  _this3.socket.log("channel", "leave " + _this3.topic);
463
664
  _this3.trigger(CHANNEL_EVENTS.close, "leave");
@@ -476,88 +677,245 @@ var Channel = exports.Channel = (function () {
476
677
  return leavePush;
477
678
  }
478
679
 
479
- // Overridable message hook
480
- //
481
- // Receives all events for specialized message handling
680
+ /**
681
+ * Overridable message hook
682
+ *
683
+ * Receives all events for specialized message handling
684
+ * before dispatching to the channel callbacks.
685
+ *
686
+ * Must return the payload, modified or unmodified
687
+ * @param {string} event
688
+ * @param {Object} payload
689
+ * @param {integer} ref
690
+ * @returns {Object}
691
+ */
482
692
 
483
693
  }, {
484
694
  key: "onMessage",
485
- value: function onMessage(event, payload, ref) {}
695
+ value: function onMessage(event, payload, ref) {
696
+ return payload;
697
+ }
486
698
 
487
- // private
699
+ /**
700
+ * @private
701
+ */
488
702
 
489
703
  }, {
490
704
  key: "isMember",
491
- value: function isMember(topic) {
492
- return this.topic === topic;
705
+ value: function isMember(topic, event, payload, joinRef) {
706
+ if (this.topic !== topic) {
707
+ return false;
708
+ }
709
+ var isLifecycleEvent = CHANNEL_LIFECYCLE_EVENTS.indexOf(event) >= 0;
710
+
711
+ if (joinRef && isLifecycleEvent && joinRef !== this.joinRef()) {
712
+ this.socket.log("channel", "dropping outdated message", { topic: topic, event: event, payload: payload, joinRef: joinRef });
713
+ return false;
714
+ } else {
715
+ return true;
716
+ }
717
+ }
718
+
719
+ /**
720
+ * @private
721
+ */
722
+
723
+ }, {
724
+ key: "joinRef",
725
+ value: function joinRef() {
726
+ return this.joinPush.ref;
493
727
  }
728
+
729
+ /**
730
+ * @private
731
+ */
732
+
494
733
  }, {
495
734
  key: "sendJoin",
496
735
  value: function sendJoin(timeout) {
497
736
  this.state = CHANNEL_STATES.joining;
498
737
  this.joinPush.resend(timeout);
499
738
  }
739
+
740
+ /**
741
+ * @private
742
+ */
743
+
500
744
  }, {
501
745
  key: "rejoin",
502
746
  value: function rejoin() {
503
- var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
747
+ var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
748
+ if (this.isLeaving()) {
749
+ return;
750
+ }
504
751
  this.sendJoin(timeout);
505
752
  }
753
+
754
+ /**
755
+ * @private
756
+ */
757
+
506
758
  }, {
507
759
  key: "trigger",
508
- value: function trigger(triggerEvent, payload, ref) {
509
- this.onMessage(triggerEvent, payload, ref);
760
+ value: function trigger(event, payload, ref, joinRef) {
761
+ var _this4 = this;
762
+
763
+ var handledPayload = this.onMessage(event, payload, ref, joinRef);
764
+ if (payload && !handledPayload) {
765
+ throw "channel onMessage callbacks must return the payload, modified or unmodified";
766
+ }
767
+
510
768
  this.bindings.filter(function (bind) {
511
- return bind.event === triggerEvent;
769
+ return bind.event === event;
512
770
  }).map(function (bind) {
513
- return bind.callback(payload, ref);
771
+ return bind.callback(handledPayload, ref, joinRef || _this4.joinRef());
514
772
  });
515
773
  }
774
+
775
+ /**
776
+ * @private
777
+ */
778
+
516
779
  }, {
517
780
  key: "replyEventName",
518
781
  value: function replyEventName(ref) {
519
782
  return "chan_reply_" + ref;
520
783
  }
784
+
785
+ /**
786
+ * @private
787
+ */
788
+
789
+ }, {
790
+ key: "isClosed",
791
+ value: function isClosed() {
792
+ return this.state === CHANNEL_STATES.closed;
793
+ }
794
+
795
+ /**
796
+ * @private
797
+ */
798
+
799
+ }, {
800
+ key: "isErrored",
801
+ value: function isErrored() {
802
+ return this.state === CHANNEL_STATES.errored;
803
+ }
804
+
805
+ /**
806
+ * @private
807
+ */
808
+
809
+ }, {
810
+ key: "isJoined",
811
+ value: function isJoined() {
812
+ return this.state === CHANNEL_STATES.joined;
813
+ }
814
+
815
+ /**
816
+ * @private
817
+ */
818
+
819
+ }, {
820
+ key: "isJoining",
821
+ value: function isJoining() {
822
+ return this.state === CHANNEL_STATES.joining;
823
+ }
824
+
825
+ /**
826
+ * @private
827
+ */
828
+
829
+ }, {
830
+ key: "isLeaving",
831
+ value: function isLeaving() {
832
+ return this.state === CHANNEL_STATES.leaving;
833
+ }
521
834
  }]);
522
835
 
523
836
  return Channel;
524
- })();
525
-
526
- var Socket = exports.Socket = (function () {
527
-
528
- // Initializes the Socket
529
- //
530
- // endPoint - The string WebSocket endpoint, ie, "ws://example.com/ws",
531
- // "wss://example.com"
532
- // "/ws" (inherited host & protocol)
533
- // opts - Optional configuration
534
- // transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
535
- // Defaults to WebSocket with automatic LongPoll fallback.
536
- // timeout - The default timeout in milliseconds to trigger push timeouts.
537
- // Defaults `DEFAULT_TIMEOUT`
538
- // heartbeatIntervalMs - The millisec interval to send a heartbeat message
539
- // reconnectAfterMs - The optional function that returns the millsec
540
- // reconnect interval. Defaults to stepped backoff of:
541
- //
542
- // function(tries){
543
- // return [1000, 5000, 10000][tries - 1] || 10000
544
- // }
545
- //
546
- // logger - The optional function for specialized logging, ie:
547
- // `logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
548
- //
549
- // longpollerTimeout - The maximum timeout of a long poll AJAX request.
550
- // Defaults to 20s (double the server long poll timer).
551
- //
552
- // params - The optional params to pass when connecting
553
- //
554
- // For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
555
- //
837
+ }();
838
+
839
+ var Serializer = {
840
+ encode: function encode(msg, callback) {
841
+ var payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
842
+ return callback(JSON.stringify(payload));
843
+ },
844
+ decode: function decode(rawPayload, callback) {
845
+ var _JSON$parse = JSON.parse(rawPayload),
846
+ _JSON$parse2 = _slicedToArray(_JSON$parse, 5),
847
+ join_ref = _JSON$parse2[0],
848
+ ref = _JSON$parse2[1],
849
+ topic = _JSON$parse2[2],
850
+ event = _JSON$parse2[3],
851
+ payload = _JSON$parse2[4];
852
+
853
+ return callback({ join_ref: join_ref, ref: ref, topic: topic, event: event, payload: payload });
854
+ }
855
+ };
556
856
 
857
+ /** Initializes the Socket
858
+ *
859
+ *
860
+ * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
861
+ *
862
+ * @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`,
863
+ * `"wss://example.com"`
864
+ * `"/socket"` (inherited host & protocol)
865
+ * @param {Object} [opts] - Optional configuration
866
+ * @param {string} [opts.transport] - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
867
+ *
868
+ * Defaults to WebSocket with automatic LongPoll fallback.
869
+ * @param {Function} [opts.encode] - The function to encode outgoing messages.
870
+ *
871
+ * Defaults to JSON:
872
+ *
873
+ * ```javascript
874
+ * (payload, callback) => callback(JSON.stringify(payload))
875
+ * ```
876
+ *
877
+ * @param {Function} [opts.decode] - The function to decode incoming messages.
878
+ *
879
+ * Defaults to JSON:
880
+ *
881
+ * ```javascript
882
+ * (payload, callback) => callback(JSON.parse(payload))
883
+ * ```
884
+ *
885
+ * @param {number} [opts.timeout] - The default timeout in milliseconds to trigger push timeouts.
886
+ *
887
+ * Defaults `DEFAULT_TIMEOUT`
888
+ * @param {number} [opts.heartbeatIntervalMs] - The millisec interval to send a heartbeat message
889
+ * @param {number} [opts.reconnectAfterMs] - The optional function that returns the millsec reconnect interval.
890
+ *
891
+ * Defaults to stepped backoff of:
892
+ *
893
+ * ```javascript
894
+ * function(tries){
895
+ * return [1000, 5000, 10000][tries - 1] || 10000
896
+ * }
897
+ * ```
898
+ * @param {Function} [opts.logger] - The optional function for specialized logging, ie:
899
+ * ```javascript
900
+ * function(kind, msg, data) {
901
+ * console.log(`${kind}: ${msg}`, data)
902
+ * }
903
+ * ```
904
+ *
905
+ * @param {number} [opts.longpollerTimeout] - The maximum timeout of a long poll AJAX request.
906
+ *
907
+ * Defaults to 20s (double the server long poll timer).
908
+ *
909
+ * @param {Object} [opts.params] - The optional params to pass when connecting
910
+ *
911
+ *
912
+ */
913
+
914
+ var Socket = exports.Socket = function () {
557
915
  function Socket(endPoint) {
558
- var _this4 = this;
916
+ var _this5 = this;
559
917
 
560
- var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
918
+ var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
561
919
 
562
920
  _classCallCheck(this, Socket);
563
921
 
@@ -566,7 +924,16 @@ var Socket = exports.Socket = (function () {
566
924
  this.sendBuffer = [];
567
925
  this.ref = 0;
568
926
  this.timeout = opts.timeout || DEFAULT_TIMEOUT;
569
- this.transport = opts.transport || window.WebSocket || LongPoll;
927
+ this.transport = opts.transport || global.WebSocket || LongPoll;
928
+ this.defaultEncoder = Serializer.encode;
929
+ this.defaultDecoder = Serializer.decode;
930
+ if (this.transport !== LongPoll) {
931
+ this.encode = opts.encode || this.defaultEncoder;
932
+ this.decode = opts.decode || this.defaultDecoder;
933
+ } else {
934
+ this.encode = this.defaultEncoder;
935
+ this.decode = this.defaultDecoder;
936
+ }
570
937
  this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
571
938
  this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
572
939
  return [1000, 2000, 5000, 10000][tries - 1] || 10000;
@@ -575,18 +942,34 @@ var Socket = exports.Socket = (function () {
575
942
  this.longpollerTimeout = opts.longpollerTimeout || 20000;
576
943
  this.params = opts.params || {};
577
944
  this.endPoint = endPoint + "/" + TRANSPORTS.websocket;
945
+ this.heartbeatTimer = null;
946
+ this.pendingHeartbeatRef = null;
578
947
  this.reconnectTimer = new Timer(function () {
579
- _this4.disconnect(function () {
580
- return _this4.connect();
948
+ _this5.disconnect(function () {
949
+ return _this5.connect();
581
950
  });
582
951
  }, this.reconnectAfterMs);
583
952
  }
584
953
 
954
+ /**
955
+ * Returns the socket protocol
956
+ *
957
+ * @returns {string}
958
+ */
959
+
960
+
585
961
  _createClass(Socket, [{
586
962
  key: "protocol",
587
963
  value: function protocol() {
588
964
  return location.protocol.match(/^https/) ? "wss" : "ws";
589
965
  }
966
+
967
+ /**
968
+ * The fully qualifed socket url
969
+ *
970
+ * @returns {string}
971
+ */
972
+
590
973
  }, {
591
974
  key: "endPointURL",
592
975
  value: function endPointURL() {
@@ -600,6 +983,13 @@ var Socket = exports.Socket = (function () {
600
983
 
601
984
  return this.protocol() + "://" + location.host + uri;
602
985
  }
986
+
987
+ /**
988
+ * @param {Function} callback
989
+ * @param {integer} code
990
+ * @param {string} reason
991
+ */
992
+
603
993
  }, {
604
994
  key: "disconnect",
605
995
  value: function disconnect(callback, code, reason) {
@@ -615,12 +1005,15 @@ var Socket = exports.Socket = (function () {
615
1005
  callback && callback();
616
1006
  }
617
1007
 
618
- // params - The params to send when connecting, for example `{user_id: userToken}`
1008
+ /**
1009
+ *
1010
+ * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
1011
+ */
619
1012
 
620
1013
  }, {
621
1014
  key: "connect",
622
1015
  value: function connect(params) {
623
- var _this5 = this;
1016
+ var _this6 = this;
624
1017
 
625
1018
  if (params) {
626
1019
  console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
@@ -633,20 +1026,25 @@ var Socket = exports.Socket = (function () {
633
1026
  this.conn = new this.transport(this.endPointURL());
634
1027
  this.conn.timeout = this.longpollerTimeout;
635
1028
  this.conn.onopen = function () {
636
- return _this5.onConnOpen();
1029
+ return _this6.onConnOpen();
637
1030
  };
638
1031
  this.conn.onerror = function (error) {
639
- return _this5.onConnError(error);
1032
+ return _this6.onConnError(error);
640
1033
  };
641
1034
  this.conn.onmessage = function (event) {
642
- return _this5.onConnMessage(event);
1035
+ return _this6.onConnMessage(event);
643
1036
  };
644
1037
  this.conn.onclose = function (event) {
645
- return _this5.onConnClose(event);
1038
+ return _this6.onConnClose(event);
646
1039
  };
647
1040
  }
648
1041
 
649
- // Logs the message. Override `this.logger` for specialized logging. noops by default
1042
+ /**
1043
+ * Logs the message. Override `this.logger` for specialized logging. noops by default
1044
+ * @param {string} kind
1045
+ * @param {string} msg
1046
+ * @param {Object} data
1047
+ */
650
1048
 
651
1049
  }, {
652
1050
  key: "log",
@@ -654,51 +1052,83 @@ var Socket = exports.Socket = (function () {
654
1052
  this.logger(kind, msg, data);
655
1053
  }
656
1054
 
657
- // Registers callbacks for connection state change events
658
- //
659
- // Examples
660
- //
661
- // socket.onError(function(error){ alert("An error occurred") })
662
- //
1055
+ /**
1056
+ * Registers callbacks for connection open events
1057
+ *
1058
+ * @example socket.onOpen(function(){ console.info("the socket was opened") })
1059
+ *
1060
+ * @param {Function} callback
1061
+ */
663
1062
 
664
1063
  }, {
665
1064
  key: "onOpen",
666
1065
  value: function onOpen(callback) {
667
1066
  this.stateChangeCallbacks.open.push(callback);
668
1067
  }
1068
+
1069
+ /**
1070
+ * Registers callbacks for connection close events
1071
+ * @param {Function} callback
1072
+ */
1073
+
669
1074
  }, {
670
1075
  key: "onClose",
671
1076
  value: function onClose(callback) {
672
1077
  this.stateChangeCallbacks.close.push(callback);
673
1078
  }
1079
+
1080
+ /**
1081
+ * Registers callbacks for connection error events
1082
+ *
1083
+ * @example socket.onError(function(error){ alert("An error occurred") })
1084
+ *
1085
+ * @param {Function} callback
1086
+ */
1087
+
674
1088
  }, {
675
1089
  key: "onError",
676
1090
  value: function onError(callback) {
677
1091
  this.stateChangeCallbacks.error.push(callback);
678
1092
  }
1093
+
1094
+ /**
1095
+ * Registers callbacks for connection message events
1096
+ * @param {Function} callback
1097
+ */
1098
+
679
1099
  }, {
680
1100
  key: "onMessage",
681
1101
  value: function onMessage(callback) {
682
1102
  this.stateChangeCallbacks.message.push(callback);
683
1103
  }
1104
+
1105
+ /**
1106
+ * @private
1107
+ */
1108
+
684
1109
  }, {
685
1110
  key: "onConnOpen",
686
1111
  value: function onConnOpen() {
687
- var _this6 = this;
1112
+ var _this7 = this;
688
1113
 
689
- this.log("transport", "connected to " + this.endPointURL(), this.transport.prototype);
1114
+ this.log("transport", "connected to " + this.endPointURL());
690
1115
  this.flushSendBuffer();
691
1116
  this.reconnectTimer.reset();
692
1117
  if (!this.conn.skipHeartbeat) {
693
1118
  clearInterval(this.heartbeatTimer);
694
1119
  this.heartbeatTimer = setInterval(function () {
695
- return _this6.sendHeartbeat();
1120
+ return _this7.sendHeartbeat();
696
1121
  }, this.heartbeatIntervalMs);
697
1122
  }
698
1123
  this.stateChangeCallbacks.open.forEach(function (callback) {
699
1124
  return callback();
700
1125
  });
701
1126
  }
1127
+
1128
+ /**
1129
+ * @private
1130
+ */
1131
+
702
1132
  }, {
703
1133
  key: "onConnClose",
704
1134
  value: function onConnClose(event) {
@@ -710,6 +1140,11 @@ var Socket = exports.Socket = (function () {
710
1140
  return callback(event);
711
1141
  });
712
1142
  }
1143
+
1144
+ /**
1145
+ * @private
1146
+ */
1147
+
713
1148
  }, {
714
1149
  key: "onConnError",
715
1150
  value: function onConnError(error) {
@@ -719,6 +1154,11 @@ var Socket = exports.Socket = (function () {
719
1154
  return callback(error);
720
1155
  });
721
1156
  }
1157
+
1158
+ /**
1159
+ * @private
1160
+ */
1161
+
722
1162
  }, {
723
1163
  key: "triggerChanError",
724
1164
  value: function triggerChanError() {
@@ -726,6 +1166,11 @@ var Socket = exports.Socket = (function () {
726
1166
  return channel.trigger(CHANNEL_EVENTS.error);
727
1167
  });
728
1168
  }
1169
+
1170
+ /**
1171
+ * @returns {string}
1172
+ */
1173
+
729
1174
  }, {
730
1175
  key: "connectionState",
731
1176
  value: function connectionState() {
@@ -740,41 +1185,68 @@ var Socket = exports.Socket = (function () {
740
1185
  return "closed";
741
1186
  }
742
1187
  }
1188
+
1189
+ /**
1190
+ * @returns {boolean}
1191
+ */
1192
+
743
1193
  }, {
744
1194
  key: "isConnected",
745
1195
  value: function isConnected() {
746
1196
  return this.connectionState() === "open";
747
1197
  }
1198
+
1199
+ /**
1200
+ * @param {Channel}
1201
+ */
1202
+
748
1203
  }, {
749
1204
  key: "remove",
750
1205
  value: function remove(channel) {
751
1206
  this.channels = this.channels.filter(function (c) {
752
- return !c.isMember(channel.topic);
1207
+ return c.joinRef() !== channel.joinRef();
753
1208
  });
754
1209
  }
1210
+
1211
+ /**
1212
+ * Initiates a new channel for the given topic
1213
+ *
1214
+ * @param {string} topic
1215
+ * @param {Object} chanParams - Parameters for the channel
1216
+ * @returns {Channel}
1217
+ */
1218
+
755
1219
  }, {
756
1220
  key: "channel",
757
1221
  value: function channel(topic) {
758
- var chanParams = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
1222
+ var chanParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
759
1223
 
760
1224
  var chan = new Channel(topic, chanParams, this);
761
1225
  this.channels.push(chan);
762
1226
  return chan;
763
1227
  }
1228
+
1229
+ /**
1230
+ * @param {Object} data
1231
+ */
1232
+
764
1233
  }, {
765
1234
  key: "push",
766
1235
  value: function push(data) {
767
- var _this7 = this;
1236
+ var _this8 = this;
768
1237
 
769
- var topic = data.topic;
770
- var event = data.event;
771
- var payload = data.payload;
772
- var ref = data.ref;
1238
+ var topic = data.topic,
1239
+ event = data.event,
1240
+ payload = data.payload,
1241
+ ref = data.ref,
1242
+ join_ref = data.join_ref;
773
1243
 
774
1244
  var callback = function callback() {
775
- return _this7.conn.send(JSON.stringify(data));
1245
+ _this8.encode(data, function (result) {
1246
+ _this8.conn.send(result);
1247
+ });
776
1248
  };
777
- this.log("push", topic + " " + event + " (" + ref + ")", payload);
1249
+ this.log("push", topic + " " + event + " (" + join_ref + ", " + ref + ")", payload);
778
1250
  if (this.isConnected()) {
779
1251
  callback();
780
1252
  } else {
@@ -782,7 +1254,10 @@ var Socket = exports.Socket = (function () {
782
1254
  }
783
1255
  }
784
1256
 
785
- // Return the next message ref, accounting for overflows
1257
+ /**
1258
+ * Return the next message ref, accounting for overflows
1259
+ * @returns {string}
1260
+ */
786
1261
 
787
1262
  }, {
788
1263
  key: "makeRef",
@@ -802,7 +1277,14 @@ var Socket = exports.Socket = (function () {
802
1277
  if (!this.isConnected()) {
803
1278
  return;
804
1279
  }
805
- this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() });
1280
+ if (this.pendingHeartbeatRef) {
1281
+ this.pendingHeartbeatRef = null;
1282
+ this.log("transport", "heartbeat timeout. Attempting to re-establish connection");
1283
+ this.conn.close(WS_CLOSE_NORMAL, "hearbeat timeout");
1284
+ return;
1285
+ }
1286
+ this.pendingHeartbeatRef = this.makeRef();
1287
+ this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef });
806
1288
  }
807
1289
  }, {
808
1290
  key: "flushSendBuffer",
@@ -817,28 +1299,36 @@ var Socket = exports.Socket = (function () {
817
1299
  }, {
818
1300
  key: "onConnMessage",
819
1301
  value: function onConnMessage(rawMessage) {
820
- var msg = JSON.parse(rawMessage.data);
821
- var topic = msg.topic;
822
- var event = msg.event;
823
- var payload = msg.payload;
824
- var ref = msg.ref;
825
-
826
- this.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
827
- this.channels.filter(function (channel) {
828
- return channel.isMember(topic);
829
- }).forEach(function (channel) {
830
- return channel.trigger(event, payload, ref);
831
- });
832
- this.stateChangeCallbacks.message.forEach(function (callback) {
833
- return callback(msg);
1302
+ var _this9 = this;
1303
+
1304
+ this.decode(rawMessage.data, function (msg) {
1305
+ var topic = msg.topic,
1306
+ event = msg.event,
1307
+ payload = msg.payload,
1308
+ ref = msg.ref,
1309
+ join_ref = msg.join_ref;
1310
+
1311
+ if (ref && ref === _this9.pendingHeartbeatRef) {
1312
+ _this9.pendingHeartbeatRef = null;
1313
+ }
1314
+
1315
+ _this9.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
1316
+ _this9.channels.filter(function (channel) {
1317
+ return channel.isMember(topic, event, payload, join_ref);
1318
+ }).forEach(function (channel) {
1319
+ return channel.trigger(event, payload, ref, join_ref);
1320
+ });
1321
+ _this9.stateChangeCallbacks.message.forEach(function (callback) {
1322
+ return callback(msg);
1323
+ });
834
1324
  });
835
1325
  }
836
1326
  }]);
837
1327
 
838
1328
  return Socket;
839
- })();
1329
+ }();
840
1330
 
841
- var LongPoll = exports.LongPoll = (function () {
1331
+ var LongPoll = exports.LongPoll = function () {
842
1332
  function LongPoll(endPoint) {
843
1333
  _classCallCheck(this, LongPoll);
844
1334
 
@@ -880,7 +1370,7 @@ var LongPoll = exports.LongPoll = (function () {
880
1370
  }, {
881
1371
  key: "poll",
882
1372
  value: function poll() {
883
- var _this8 = this;
1373
+ var _this10 = this;
884
1374
 
885
1375
  if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
886
1376
  return;
@@ -888,11 +1378,11 @@ var LongPoll = exports.LongPoll = (function () {
888
1378
 
889
1379
  Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
890
1380
  if (resp) {
891
- var status = resp.status;
892
- var token = resp.token;
893
- var messages = resp.messages;
1381
+ var status = resp.status,
1382
+ token = resp.token,
1383
+ messages = resp.messages;
894
1384
 
895
- _this8.token = token;
1385
+ _this10.token = token;
896
1386
  } else {
897
1387
  var status = 0;
898
1388
  }
@@ -900,22 +1390,22 @@ var LongPoll = exports.LongPoll = (function () {
900
1390
  switch (status) {
901
1391
  case 200:
902
1392
  messages.forEach(function (msg) {
903
- return _this8.onmessage({ data: JSON.stringify(msg) });
1393
+ return _this10.onmessage({ data: msg });
904
1394
  });
905
- _this8.poll();
1395
+ _this10.poll();
906
1396
  break;
907
1397
  case 204:
908
- _this8.poll();
1398
+ _this10.poll();
909
1399
  break;
910
1400
  case 410:
911
- _this8.readyState = SOCKET_STATES.open;
912
- _this8.onopen();
913
- _this8.poll();
1401
+ _this10.readyState = SOCKET_STATES.open;
1402
+ _this10.onopen();
1403
+ _this10.poll();
914
1404
  break;
915
1405
  case 0:
916
1406
  case 500:
917
- _this8.onerror();
918
- _this8.closeAndRetry();
1407
+ _this10.onerror();
1408
+ _this10.closeAndRetry();
919
1409
  break;
920
1410
  default:
921
1411
  throw "unhandled poll status " + status;
@@ -925,12 +1415,12 @@ var LongPoll = exports.LongPoll = (function () {
925
1415
  }, {
926
1416
  key: "send",
927
1417
  value: function send(body) {
928
- var _this9 = this;
1418
+ var _this11 = this;
929
1419
 
930
1420
  Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
931
1421
  if (!resp || resp.status !== 200) {
932
- _this9.onerror(status);
933
- _this9.closeAndRetry();
1422
+ _this11.onerror(resp && resp.status);
1423
+ _this11.closeAndRetry();
934
1424
  }
935
1425
  });
936
1426
  }
@@ -943,9 +1433,9 @@ var LongPoll = exports.LongPoll = (function () {
943
1433
  }]);
944
1434
 
945
1435
  return LongPoll;
946
- })();
1436
+ }();
947
1437
 
948
- var Ajax = exports.Ajax = (function () {
1438
+ var Ajax = exports.Ajax = function () {
949
1439
  function Ajax() {
950
1440
  _classCallCheck(this, Ajax);
951
1441
  }
@@ -953,24 +1443,24 @@ var Ajax = exports.Ajax = (function () {
953
1443
  _createClass(Ajax, null, [{
954
1444
  key: "request",
955
1445
  value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
956
- if (window.XDomainRequest) {
1446
+ if (global.XDomainRequest) {
957
1447
  var req = new XDomainRequest(); // IE8, IE9
958
1448
  this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
959
1449
  } else {
960
- var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
1450
+ var _req = global.XMLHttpRequest ? new global.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
961
1451
  new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
962
- this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
1452
+ this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback);
963
1453
  }
964
1454
  }
965
1455
  }, {
966
1456
  key: "xdomainRequest",
967
1457
  value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
968
- var _this10 = this;
1458
+ var _this12 = this;
969
1459
 
970
1460
  req.timeout = timeout;
971
1461
  req.open(method, endPoint);
972
1462
  req.onload = function () {
973
- var response = _this10.parseJSON(req.responseText);
1463
+ var response = _this12.parseJSON(req.responseText);
974
1464
  callback && callback(response);
975
1465
  };
976
1466
  if (ontimeout) {
@@ -985,17 +1475,17 @@ var Ajax = exports.Ajax = (function () {
985
1475
  }, {
986
1476
  key: "xhrRequest",
987
1477
  value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
988
- var _this11 = this;
1478
+ var _this13 = this;
989
1479
 
990
- req.timeout = timeout;
991
1480
  req.open(method, endPoint, true);
1481
+ req.timeout = timeout;
992
1482
  req.setRequestHeader("Content-Type", accept);
993
1483
  req.onerror = function () {
994
1484
  callback && callback(null);
995
1485
  };
996
1486
  req.onreadystatechange = function () {
997
- if (req.readyState === _this11.states.complete && callback) {
998
- var response = _this11.parseJSON(req.responseText);
1487
+ if (req.readyState === _this13.states.complete && callback) {
1488
+ var response = _this13.parseJSON(req.responseText);
999
1489
  callback(response);
1000
1490
  }
1001
1491
  };
@@ -1008,7 +1498,16 @@ var Ajax = exports.Ajax = (function () {
1008
1498
  }, {
1009
1499
  key: "parseJSON",
1010
1500
  value: function parseJSON(resp) {
1011
- return resp && resp !== "" ? JSON.parse(resp) : null;
1501
+ if (!resp || resp === "") {
1502
+ return null;
1503
+ }
1504
+
1505
+ try {
1506
+ return JSON.parse(resp);
1507
+ } catch (e) {
1508
+ console && console.log("failed to parse JSON response", resp);
1509
+ return null;
1510
+ }
1012
1511
  }
1013
1512
  }, {
1014
1513
  key: "serialize",
@@ -1041,76 +1540,101 @@ var Ajax = exports.Ajax = (function () {
1041
1540
  }]);
1042
1541
 
1043
1542
  return Ajax;
1044
- })();
1543
+ }();
1045
1544
 
1046
1545
  Ajax.states = { complete: 4 };
1047
1546
 
1048
1547
  var Presence = exports.Presence = {
1049
- syncState: function syncState(state, newState, onJoin, onLeave) {
1050
- var _this12 = this;
1548
+ syncState: function syncState(currentState, newState, onJoin, onLeave) {
1549
+ var _this14 = this;
1051
1550
 
1551
+ var state = this.clone(currentState);
1552
+ state.presences = state.presences || {};
1553
+ state.synced = true;
1554
+ var pendingDiffs = state.pendingDiffs || [];
1052
1555
  var joins = {};
1053
1556
  var leaves = {};
1054
1557
 
1055
- this.map(state, function (key, presence) {
1558
+ this.map(state.presences, function (key, presence) {
1056
1559
  if (!newState[key]) {
1057
- leaves[key] = _this12.clone(presence);
1560
+ leaves[key] = presence;
1058
1561
  }
1059
1562
  });
1060
1563
  this.map(newState, function (key, newPresence) {
1061
- var currentPresence = state[key];
1564
+ var currentPresence = state.presences[key];
1062
1565
  if (currentPresence) {
1063
- (function () {
1064
- var newRefs = newPresence.metas.map(function (m) {
1065
- return m.phx_ref;
1066
- });
1067
- var curRefs = currentPresence.metas.map(function (m) {
1068
- return m.phx_ref;
1069
- });
1070
- var joinedMetas = newPresence.metas.filter(function (m) {
1071
- return curRefs.indexOf(m.phx_ref) < 0;
1072
- });
1073
- var leftMetas = currentPresence.metas.filter(function (m) {
1074
- return newRefs.indexOf(m.phx_ref) < 0;
1075
- });
1076
- if (joinedMetas.length > 0) {
1077
- joins[key] = newPresence;
1078
- joins[key].metas = joinedMetas;
1079
- }
1080
- if (leftMetas.length > 0) {
1081
- leaves[key] = _this12.clone(currentPresence);
1082
- leaves[key].metas = leftMetas;
1083
- }
1084
- })();
1566
+ var newRefs = newPresence.metas.map(function (m) {
1567
+ return m.phx_ref;
1568
+ });
1569
+ var curRefs = currentPresence.metas.map(function (m) {
1570
+ return m.phx_ref;
1571
+ });
1572
+ var joinedMetas = newPresence.metas.filter(function (m) {
1573
+ return curRefs.indexOf(m.phx_ref) < 0;
1574
+ });
1575
+ var leftMetas = currentPresence.metas.filter(function (m) {
1576
+ return newRefs.indexOf(m.phx_ref) < 0;
1577
+ });
1578
+ if (joinedMetas.length > 0) {
1579
+ joins[key] = newPresence;
1580
+ joins[key].metas = joinedMetas;
1581
+ }
1582
+ if (leftMetas.length > 0) {
1583
+ leaves[key] = _this14.clone(currentPresence);
1584
+ leaves[key].metas = leftMetas;
1585
+ }
1085
1586
  } else {
1086
1587
  joins[key] = newPresence;
1087
1588
  }
1088
1589
  });
1089
- this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
1590
+
1591
+ state = this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
1592
+
1593
+ pendingDiffs.forEach(function (diff) {
1594
+ state = _this14.syncDiff(state, { joins: diff.joins, leaves: diff.leaves }, diff.onJoin, diff.onLeave);
1595
+ });
1596
+ state.pendingDiffs = [];
1597
+
1598
+ return state;
1090
1599
  },
1091
- syncDiff: function syncDiff(state, _ref2, onJoin, onLeave) {
1092
- var joins = _ref2.joins;
1093
- var leaves = _ref2.leaves;
1600
+ syncDiff: function syncDiff(currentState, _ref2, onJoin, onLeave) {
1601
+ var joins = _ref2.joins,
1602
+ leaves = _ref2.leaves;
1603
+
1604
+ var state = this.clone(currentState);
1605
+ state.presences = state.presences || {};
1606
+ state.pendingDiffs = state.pendingDiffs || [];
1607
+
1608
+ if (!state.synced) {
1609
+ state.pendingDiffs.push({ joins: joins, leaves: leaves, onJoin: onJoin, onLeave: onLeave });
1610
+ return state;
1611
+ }
1094
1612
 
1095
1613
  if (!onJoin) {
1096
- onJoin = function () {};
1614
+ onJoin = function onJoin() {};
1097
1615
  }
1098
1616
  if (!onLeave) {
1099
- onLeave = function () {};
1617
+ onLeave = function onLeave() {};
1100
1618
  }
1101
1619
 
1102
1620
  this.map(joins, function (key, newPresence) {
1103
- var currentPresence = state[key];
1104
- state[key] = newPresence;
1621
+ var currentPresence = state.presences[key];
1622
+ state.presences[key] = newPresence;
1105
1623
  if (currentPresence) {
1106
- var _state$key$metas;
1624
+ var _state$presences$key$;
1107
1625
 
1108
- (_state$key$metas = state[key].metas).unshift.apply(_state$key$metas, _toConsumableArray(currentPresence.metas));
1626
+ var joinedRefs = state.presences[key].metas.map(function (m) {
1627
+ return m.phx_ref;
1628
+ });
1629
+ var curMetas = currentPresence.metas.filter(function (m) {
1630
+ return joinedRefs.indexOf(m.phx_ref) < 0;
1631
+ });
1632
+ (_state$presences$key$ = state.presences[key].metas).unshift.apply(_state$presences$key$, _toConsumableArray(curMetas));
1109
1633
  }
1110
1634
  onJoin(key, currentPresence, newPresence);
1111
1635
  });
1112
1636
  this.map(leaves, function (key, leftPresence) {
1113
- var currentPresence = state[key];
1637
+ var currentPresence = state.presences[key];
1114
1638
  if (!currentPresence) {
1115
1639
  return;
1116
1640
  }
@@ -1122,13 +1646,15 @@ var Presence = exports.Presence = {
1122
1646
  });
1123
1647
  onLeave(key, currentPresence, leftPresence);
1124
1648
  if (currentPresence.metas.length === 0) {
1125
- delete state[key];
1649
+ delete state.presences[key];
1126
1650
  }
1127
1651
  });
1652
+ return state;
1128
1653
  },
1129
- list: function list(presences, chooser) {
1654
+ list: function list(state, chooser) {
1655
+ var presences = state.presences || {};
1130
1656
  if (!chooser) {
1131
- chooser = function (key, pres) {
1657
+ chooser = function chooser(key, pres) {
1132
1658
  return pres;
1133
1659
  };
1134
1660
  }
@@ -1138,6 +1664,7 @@ var Presence = exports.Presence = {
1138
1664
  });
1139
1665
  },
1140
1666
 
1667
+
1141
1668
  // private
1142
1669
 
1143
1670
  map: function map(obj, func) {
@@ -1150,21 +1677,25 @@ var Presence = exports.Presence = {
1150
1677
  }
1151
1678
  };
1152
1679
 
1153
- // Creates a timer that accepts a `timerCalc` function to perform
1154
- // calculated timeout retries, such as exponential backoff.
1155
- //
1156
- // ## Examples
1157
- //
1158
- // let reconnectTimer = new Timer(() => this.connect(), function(tries){
1159
- // return [1000, 5000, 10000][tries - 1] || 10000
1160
- // })
1161
- // reconnectTimer.scheduleTimeout() // fires after 1000
1162
- // reconnectTimer.scheduleTimeout() // fires after 5000
1163
- // reconnectTimer.reset()
1164
- // reconnectTimer.scheduleTimeout() // fires after 1000
1165
- //
1166
-
1167
- var Timer = (function () {
1680
+ /**
1681
+ *
1682
+ * Creates a timer that accepts a `timerCalc` function to perform
1683
+ * calculated timeout retries, such as exponential backoff.
1684
+ *
1685
+ * @example
1686
+ * let reconnectTimer = new Timer(() => this.connect(), function(tries){
1687
+ * return [1000, 5000, 10000][tries - 1] || 10000
1688
+ * })
1689
+ * reconnectTimer.scheduleTimeout() // fires after 1000
1690
+ * reconnectTimer.scheduleTimeout() // fires after 5000
1691
+ * reconnectTimer.reset()
1692
+ * reconnectTimer.scheduleTimeout() // fires after 1000
1693
+ *
1694
+ * @param {Function} callback
1695
+ * @param {Function} timerCalc
1696
+ */
1697
+
1698
+ var Timer = function () {
1168
1699
  function Timer(callback, timerCalc) {
1169
1700
  _classCallCheck(this, Timer);
1170
1701
 
@@ -1181,24 +1712,25 @@ var Timer = (function () {
1181
1712
  clearTimeout(this.timer);
1182
1713
  }
1183
1714
 
1184
- // Cancels any previous scheduleTimeout and schedules callback
1715
+ /**
1716
+ * Cancels any previous scheduleTimeout and schedules callback
1717
+ */
1185
1718
 
1186
1719
  }, {
1187
1720
  key: "scheduleTimeout",
1188
1721
  value: function scheduleTimeout() {
1189
- var _this13 = this;
1722
+ var _this15 = this;
1190
1723
 
1191
1724
  clearTimeout(this.timer);
1192
1725
 
1193
1726
  this.timer = setTimeout(function () {
1194
- _this13.tries = _this13.tries + 1;
1195
- _this13.callback();
1727
+ _this15.tries = _this15.tries + 1;
1728
+ _this15.callback();
1196
1729
  }, this.timerCalc(this.tries + 1));
1197
1730
  }
1198
1731
  }]);
1199
1732
 
1200
1733
  return Timer;
1201
- })();
1202
-
1734
+ }();
1203
1735
 
1204
- })(typeof(exports) === "undefined" ? window.Phoenix = window.Phoenix || {} : exports);
1736
+ })));