opal-phoenix 0.0.7 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ })));