opal-phoenix 0.0.5 → 0.0.6

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
2
  SHA1:
3
- metadata.gz: 393f70b999fd9c88e27ebd29134ad2b81f446192
4
- data.tar.gz: 66de30311b57a5ae82b58207a22e7f2eb41ce42e
3
+ metadata.gz: 19241d119ea0a5430f2c0717dbd4bbbaa7785ade
4
+ data.tar.gz: a1c297f9387a88be2d98aaa32aeb1ba0816de58a
5
5
  SHA512:
6
- metadata.gz: adadeb87f024eb73b8f98170ec13b67b68d0ece0a3b5848fc65757118824ff6522deaa5dee3c9c1846862cf43617634453c2a1ba04a38f64fbeb01cc0b50d098
7
- data.tar.gz: 90c0616b02c5bc884fff12904334658079e377b33d0a682c5939a4e40fe944352802e11e109248f581ba79af7bd44214b548666fc852fb644a7ee6694210e279
6
+ metadata.gz: 5df826062a78cf352865dc4cbca2c0164e067da6be2de8368cf0f3a8bd3d07e0b3bf397bda7056362e6c81bae498eb94e052407d7602e4933c5f8e2abb06019f
7
+ data.tar.gz: 86f8342f231f2993b90e651bb8e73b73c6c05a8c4dfa2f9737d84671da994ad034fedc3b50248971fa81d9e7f788c0c971cd679632cd27b8ee3f1fe4c4b23fc7
@@ -1,2 +1,2 @@
1
1
  exclude_paths:
2
- - opal/phoenix_js.js
2
+ - opal/vendor/*
data/Rakefile CHANGED
@@ -4,10 +4,10 @@ Bundler::GemHelper.install_tasks
4
4
 
5
5
  require 'open-uri'
6
6
 
7
- desc 'Update Phoenix JS lib'
8
- task :update_js do
7
+ desc 'Update JS dependencies'
8
+ task :js_deps do
9
9
  js_lib_url = 'https://raw.githubusercontent.com/phoenixframework/phoenix/master/priv/static/phoenix.js'
10
- js_lib_dest = File.join(File.dirname(__FILE__), './lib/phoenix_js.js')
10
+ js_lib_dest = File.join(File.dirname(__FILE__), './opal/vendor/phoenix.js')
11
11
  open(js_lib_url) do |f|
12
12
  File.write(js_lib_dest, f.readlines.join)
13
13
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'opal-phoenix'
3
- s.version = '0.0.5'
3
+ s.version = '0.0.6'
4
4
  s.authors = ['Michał Kalbarczyk']
5
5
  s.email = 'fazibear@gmail.com'
6
6
  s.homepage = 'http://github.com/fazibear/opal-phoenix'
@@ -1,4 +1,4 @@
1
- require 'phoenix_js'
1
+ require 'vendor/phoenix'
2
2
  require 'phoenix/channel'
3
3
  require 'phoenix/socket'
4
4
  require 'phoenix/push'
@@ -0,0 +1,1204 @@
1
+ (function(exports){
2
+ "use strict";
3
+
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
+ Object.defineProperty(exports, "__esModule", {
7
+ value: true
8
+ });
9
+
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); } }
11
+
12
+ function _typeof(obj) { return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj; }
13
+
14
+ function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
15
+
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";
176
+ var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
177
+ var DEFAULT_TIMEOUT = 10000;
178
+ var CHANNEL_STATES = {
179
+ closed: "closed",
180
+ errored: "errored",
181
+ joined: "joined",
182
+ joining: "joining"
183
+ };
184
+ var CHANNEL_EVENTS = {
185
+ close: "phx_close",
186
+ error: "phx_error",
187
+ join: "phx_join",
188
+ reply: "phx_reply",
189
+ leave: "phx_leave"
190
+ };
191
+ var TRANSPORTS = {
192
+ longpoll: "longpoll",
193
+ websocket: "websocket"
194
+ };
195
+
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
+ //
205
+
206
+ function Push(channel, event, payload, timeout) {
207
+ _classCallCheck(this, Push);
208
+
209
+ this.channel = channel;
210
+ this.event = event;
211
+ this.payload = payload || {};
212
+ this.receivedResp = null;
213
+ this.timeout = timeout;
214
+ this.timeoutTimer = null;
215
+ this.recHooks = [];
216
+ this.sent = false;
217
+ }
218
+
219
+ _createClass(Push, [{
220
+ key: "resend",
221
+ value: function resend(timeout) {
222
+ this.timeout = timeout;
223
+ this.cancelRefEvent();
224
+ this.ref = null;
225
+ this.refEvent = null;
226
+ this.receivedResp = null;
227
+ this.sent = false;
228
+ this.send();
229
+ }
230
+ }, {
231
+ key: "send",
232
+ value: function send() {
233
+ if (this.hasReceived("timeout")) {
234
+ return;
235
+ }
236
+ this.startTimeout();
237
+ this.sent = true;
238
+ this.channel.socket.push({
239
+ topic: this.channel.topic,
240
+ event: this.event,
241
+ payload: this.payload,
242
+ ref: this.ref
243
+ });
244
+ }
245
+ }, {
246
+ key: "receive",
247
+ value: function receive(status, callback) {
248
+ if (this.hasReceived(status)) {
249
+ callback(this.receivedResp.response);
250
+ }
251
+
252
+ this.recHooks.push({ status: status, callback: callback });
253
+ return this;
254
+ }
255
+
256
+ // private
257
+
258
+ }, {
259
+ key: "matchReceive",
260
+ value: function matchReceive(_ref) {
261
+ var status = _ref.status;
262
+ var response = _ref.response;
263
+ var ref = _ref.ref;
264
+
265
+ this.recHooks.filter(function (h) {
266
+ return h.status === status;
267
+ }).forEach(function (h) {
268
+ return h.callback(response);
269
+ });
270
+ }
271
+ }, {
272
+ key: "cancelRefEvent",
273
+ value: function cancelRefEvent() {
274
+ if (!this.refEvent) {
275
+ return;
276
+ }
277
+ this.channel.off(this.refEvent);
278
+ }
279
+ }, {
280
+ key: "cancelTimeout",
281
+ value: function cancelTimeout() {
282
+ clearTimeout(this.timeoutTimer);
283
+ this.timeoutTimer = null;
284
+ }
285
+ }, {
286
+ key: "startTimeout",
287
+ value: function startTimeout() {
288
+ var _this = this;
289
+
290
+ if (this.timeoutTimer) {
291
+ return;
292
+ }
293
+ this.ref = this.channel.socket.makeRef();
294
+ this.refEvent = this.channel.replyEventName(this.ref);
295
+
296
+ this.channel.on(this.refEvent, function (payload) {
297
+ _this.cancelRefEvent();
298
+ _this.cancelTimeout();
299
+ _this.receivedResp = payload;
300
+ _this.matchReceive(payload);
301
+ });
302
+
303
+ this.timeoutTimer = setTimeout(function () {
304
+ _this.trigger("timeout", {});
305
+ }, this.timeout);
306
+ }
307
+ }, {
308
+ key: "hasReceived",
309
+ value: function hasReceived(status) {
310
+ return this.receivedResp && this.receivedResp.status === status;
311
+ }
312
+ }, {
313
+ key: "trigger",
314
+ value: function trigger(status, response) {
315
+ this.channel.trigger(this.refEvent, { status: status, response: response });
316
+ }
317
+ }]);
318
+
319
+ return Push;
320
+ })();
321
+
322
+ var Channel = exports.Channel = (function () {
323
+ function Channel(topic, params, socket) {
324
+ var _this2 = this;
325
+
326
+ _classCallCheck(this, Channel);
327
+
328
+ this.state = CHANNEL_STATES.closed;
329
+ this.topic = topic;
330
+ this.params = params || {};
331
+ this.socket = socket;
332
+ this.bindings = [];
333
+ this.timeout = this.socket.timeout;
334
+ this.joinedOnce = false;
335
+ this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
336
+ this.pushBuffer = [];
337
+ this.rejoinTimer = new Timer(function () {
338
+ return _this2.rejoinUntilConnected();
339
+ }, this.socket.reconnectAfterMs);
340
+ this.joinPush.receive("ok", function () {
341
+ _this2.state = CHANNEL_STATES.joined;
342
+ _this2.rejoinTimer.reset();
343
+ _this2.pushBuffer.forEach(function (pushEvent) {
344
+ return pushEvent.send();
345
+ });
346
+ _this2.pushBuffer = [];
347
+ });
348
+ this.onClose(function () {
349
+ _this2.socket.log("channel", "close " + _this2.topic);
350
+ _this2.state = CHANNEL_STATES.closed;
351
+ _this2.socket.remove(_this2);
352
+ });
353
+ this.onError(function (reason) {
354
+ _this2.socket.log("channel", "error " + _this2.topic, reason);
355
+ _this2.state = CHANNEL_STATES.errored;
356
+ _this2.rejoinTimer.scheduleTimeout();
357
+ });
358
+ this.joinPush.receive("timeout", function () {
359
+ if (_this2.state !== CHANNEL_STATES.joining) {
360
+ return;
361
+ }
362
+
363
+ _this2.socket.log("channel", "timeout " + _this2.topic, _this2.joinPush.timeout);
364
+ _this2.state = CHANNEL_STATES.errored;
365
+ _this2.rejoinTimer.scheduleTimeout();
366
+ });
367
+ this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
368
+ _this2.trigger(_this2.replyEventName(ref), payload);
369
+ });
370
+ }
371
+
372
+ _createClass(Channel, [{
373
+ key: "rejoinUntilConnected",
374
+ value: function rejoinUntilConnected() {
375
+ this.rejoinTimer.scheduleTimeout();
376
+ if (this.socket.isConnected()) {
377
+ this.rejoin();
378
+ }
379
+ }
380
+ }, {
381
+ key: "join",
382
+ value: function join() {
383
+ var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
384
+
385
+ if (this.joinedOnce) {
386
+ throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
387
+ } else {
388
+ this.joinedOnce = true;
389
+ }
390
+ this.rejoin(timeout);
391
+ return this.joinPush;
392
+ }
393
+ }, {
394
+ key: "onClose",
395
+ value: function onClose(callback) {
396
+ this.on(CHANNEL_EVENTS.close, callback);
397
+ }
398
+ }, {
399
+ key: "onError",
400
+ value: function onError(callback) {
401
+ this.on(CHANNEL_EVENTS.error, function (reason) {
402
+ return callback(reason);
403
+ });
404
+ }
405
+ }, {
406
+ key: "on",
407
+ value: function on(event, callback) {
408
+ this.bindings.push({ event: event, callback: callback });
409
+ }
410
+ }, {
411
+ key: "off",
412
+ value: function off(event) {
413
+ this.bindings = this.bindings.filter(function (bind) {
414
+ return bind.event !== event;
415
+ });
416
+ }
417
+ }, {
418
+ key: "canPush",
419
+ value: function canPush() {
420
+ return this.socket.isConnected() && this.state === CHANNEL_STATES.joined;
421
+ }
422
+ }, {
423
+ key: "push",
424
+ value: function push(event, payload) {
425
+ var timeout = arguments.length <= 2 || arguments[2] === undefined ? this.timeout : arguments[2];
426
+
427
+ if (!this.joinedOnce) {
428
+ throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
429
+ }
430
+ var pushEvent = new Push(this, event, payload, timeout);
431
+ if (this.canPush()) {
432
+ pushEvent.send();
433
+ } else {
434
+ pushEvent.startTimeout();
435
+ this.pushBuffer.push(pushEvent);
436
+ }
437
+
438
+ return pushEvent;
439
+ }
440
+
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
+ //
453
+
454
+ }, {
455
+ key: "leave",
456
+ value: function leave() {
457
+ var _this3 = this;
458
+
459
+ var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
460
+
461
+ var onClose = function onClose() {
462
+ _this3.socket.log("channel", "leave " + _this3.topic);
463
+ _this3.trigger(CHANNEL_EVENTS.close, "leave");
464
+ };
465
+ var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
466
+ leavePush.receive("ok", function () {
467
+ return onClose();
468
+ }).receive("timeout", function () {
469
+ return onClose();
470
+ });
471
+ leavePush.send();
472
+ if (!this.canPush()) {
473
+ leavePush.trigger("ok", {});
474
+ }
475
+
476
+ return leavePush;
477
+ }
478
+
479
+ // Overridable message hook
480
+ //
481
+ // Receives all events for specialized message handling
482
+
483
+ }, {
484
+ key: "onMessage",
485
+ value: function onMessage(event, payload, ref) {}
486
+
487
+ // private
488
+
489
+ }, {
490
+ key: "isMember",
491
+ value: function isMember(topic) {
492
+ return this.topic === topic;
493
+ }
494
+ }, {
495
+ key: "sendJoin",
496
+ value: function sendJoin(timeout) {
497
+ this.state = CHANNEL_STATES.joining;
498
+ this.joinPush.resend(timeout);
499
+ }
500
+ }, {
501
+ key: "rejoin",
502
+ value: function rejoin() {
503
+ var timeout = arguments.length <= 0 || arguments[0] === undefined ? this.timeout : arguments[0];
504
+ this.sendJoin(timeout);
505
+ }
506
+ }, {
507
+ key: "trigger",
508
+ value: function trigger(triggerEvent, payload, ref) {
509
+ this.onMessage(triggerEvent, payload, ref);
510
+ this.bindings.filter(function (bind) {
511
+ return bind.event === triggerEvent;
512
+ }).map(function (bind) {
513
+ return bind.callback(payload, ref);
514
+ });
515
+ }
516
+ }, {
517
+ key: "replyEventName",
518
+ value: function replyEventName(ref) {
519
+ return "chan_reply_" + ref;
520
+ }
521
+ }]);
522
+
523
+ 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
+ //
556
+
557
+ function Socket(endPoint) {
558
+ var _this4 = this;
559
+
560
+ var opts = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
561
+
562
+ _classCallCheck(this, Socket);
563
+
564
+ this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
565
+ this.channels = [];
566
+ this.sendBuffer = [];
567
+ this.ref = 0;
568
+ this.timeout = opts.timeout || DEFAULT_TIMEOUT;
569
+ this.transport = opts.transport || window.WebSocket || LongPoll;
570
+ this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
571
+ this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
572
+ return [1000, 2000, 5000, 10000][tries - 1] || 10000;
573
+ };
574
+ this.logger = opts.logger || function () {}; // noop
575
+ this.longpollerTimeout = opts.longpollerTimeout || 20000;
576
+ this.params = opts.params || {};
577
+ this.endPoint = endPoint + "/" + TRANSPORTS.websocket;
578
+ this.reconnectTimer = new Timer(function () {
579
+ _this4.disconnect(function () {
580
+ return _this4.connect();
581
+ });
582
+ }, this.reconnectAfterMs);
583
+ }
584
+
585
+ _createClass(Socket, [{
586
+ key: "protocol",
587
+ value: function protocol() {
588
+ return location.protocol.match(/^https/) ? "wss" : "ws";
589
+ }
590
+ }, {
591
+ key: "endPointURL",
592
+ value: function endPointURL() {
593
+ var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });
594
+ if (uri.charAt(0) !== "/") {
595
+ return uri;
596
+ }
597
+ if (uri.charAt(1) === "/") {
598
+ return this.protocol() + ":" + uri;
599
+ }
600
+
601
+ return this.protocol() + "://" + location.host + uri;
602
+ }
603
+ }, {
604
+ key: "disconnect",
605
+ value: function disconnect(callback, code, reason) {
606
+ if (this.conn) {
607
+ this.conn.onclose = function () {}; // noop
608
+ if (code) {
609
+ this.conn.close(code, reason || "");
610
+ } else {
611
+ this.conn.close();
612
+ }
613
+ this.conn = null;
614
+ }
615
+ callback && callback();
616
+ }
617
+
618
+ // params - The params to send when connecting, for example `{user_id: userToken}`
619
+
620
+ }, {
621
+ key: "connect",
622
+ value: function connect(params) {
623
+ var _this5 = this;
624
+
625
+ if (params) {
626
+ console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
627
+ this.params = params;
628
+ }
629
+ if (this.conn) {
630
+ return;
631
+ }
632
+
633
+ this.conn = new this.transport(this.endPointURL());
634
+ this.conn.timeout = this.longpollerTimeout;
635
+ this.conn.onopen = function () {
636
+ return _this5.onConnOpen();
637
+ };
638
+ this.conn.onerror = function (error) {
639
+ return _this5.onConnError(error);
640
+ };
641
+ this.conn.onmessage = function (event) {
642
+ return _this5.onConnMessage(event);
643
+ };
644
+ this.conn.onclose = function (event) {
645
+ return _this5.onConnClose(event);
646
+ };
647
+ }
648
+
649
+ // Logs the message. Override `this.logger` for specialized logging. noops by default
650
+
651
+ }, {
652
+ key: "log",
653
+ value: function log(kind, msg, data) {
654
+ this.logger(kind, msg, data);
655
+ }
656
+
657
+ // Registers callbacks for connection state change events
658
+ //
659
+ // Examples
660
+ //
661
+ // socket.onError(function(error){ alert("An error occurred") })
662
+ //
663
+
664
+ }, {
665
+ key: "onOpen",
666
+ value: function onOpen(callback) {
667
+ this.stateChangeCallbacks.open.push(callback);
668
+ }
669
+ }, {
670
+ key: "onClose",
671
+ value: function onClose(callback) {
672
+ this.stateChangeCallbacks.close.push(callback);
673
+ }
674
+ }, {
675
+ key: "onError",
676
+ value: function onError(callback) {
677
+ this.stateChangeCallbacks.error.push(callback);
678
+ }
679
+ }, {
680
+ key: "onMessage",
681
+ value: function onMessage(callback) {
682
+ this.stateChangeCallbacks.message.push(callback);
683
+ }
684
+ }, {
685
+ key: "onConnOpen",
686
+ value: function onConnOpen() {
687
+ var _this6 = this;
688
+
689
+ this.log("transport", "connected to " + this.endPointURL(), this.transport.prototype);
690
+ this.flushSendBuffer();
691
+ this.reconnectTimer.reset();
692
+ if (!this.conn.skipHeartbeat) {
693
+ clearInterval(this.heartbeatTimer);
694
+ this.heartbeatTimer = setInterval(function () {
695
+ return _this6.sendHeartbeat();
696
+ }, this.heartbeatIntervalMs);
697
+ }
698
+ this.stateChangeCallbacks.open.forEach(function (callback) {
699
+ return callback();
700
+ });
701
+ }
702
+ }, {
703
+ key: "onConnClose",
704
+ value: function onConnClose(event) {
705
+ this.log("transport", "close", event);
706
+ this.triggerChanError();
707
+ clearInterval(this.heartbeatTimer);
708
+ this.reconnectTimer.scheduleTimeout();
709
+ this.stateChangeCallbacks.close.forEach(function (callback) {
710
+ return callback(event);
711
+ });
712
+ }
713
+ }, {
714
+ key: "onConnError",
715
+ value: function onConnError(error) {
716
+ this.log("transport", error);
717
+ this.triggerChanError();
718
+ this.stateChangeCallbacks.error.forEach(function (callback) {
719
+ return callback(error);
720
+ });
721
+ }
722
+ }, {
723
+ key: "triggerChanError",
724
+ value: function triggerChanError() {
725
+ this.channels.forEach(function (channel) {
726
+ return channel.trigger(CHANNEL_EVENTS.error);
727
+ });
728
+ }
729
+ }, {
730
+ key: "connectionState",
731
+ value: function connectionState() {
732
+ switch (this.conn && this.conn.readyState) {
733
+ case SOCKET_STATES.connecting:
734
+ return "connecting";
735
+ case SOCKET_STATES.open:
736
+ return "open";
737
+ case SOCKET_STATES.closing:
738
+ return "closing";
739
+ default:
740
+ return "closed";
741
+ }
742
+ }
743
+ }, {
744
+ key: "isConnected",
745
+ value: function isConnected() {
746
+ return this.connectionState() === "open";
747
+ }
748
+ }, {
749
+ key: "remove",
750
+ value: function remove(channel) {
751
+ this.channels = this.channels.filter(function (c) {
752
+ return !c.isMember(channel.topic);
753
+ });
754
+ }
755
+ }, {
756
+ key: "channel",
757
+ value: function channel(topic) {
758
+ var chanParams = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
759
+
760
+ var chan = new Channel(topic, chanParams, this);
761
+ this.channels.push(chan);
762
+ return chan;
763
+ }
764
+ }, {
765
+ key: "push",
766
+ value: function push(data) {
767
+ var _this7 = this;
768
+
769
+ var topic = data.topic;
770
+ var event = data.event;
771
+ var payload = data.payload;
772
+ var ref = data.ref;
773
+
774
+ var callback = function callback() {
775
+ return _this7.conn.send(JSON.stringify(data));
776
+ };
777
+ this.log("push", topic + " " + event + " (" + ref + ")", payload);
778
+ if (this.isConnected()) {
779
+ callback();
780
+ } else {
781
+ this.sendBuffer.push(callback);
782
+ }
783
+ }
784
+
785
+ // Return the next message ref, accounting for overflows
786
+
787
+ }, {
788
+ key: "makeRef",
789
+ value: function makeRef() {
790
+ var newRef = this.ref + 1;
791
+ if (newRef === this.ref) {
792
+ this.ref = 0;
793
+ } else {
794
+ this.ref = newRef;
795
+ }
796
+
797
+ return this.ref.toString();
798
+ }
799
+ }, {
800
+ key: "sendHeartbeat",
801
+ value: function sendHeartbeat() {
802
+ if (!this.isConnected()) {
803
+ return;
804
+ }
805
+ this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.makeRef() });
806
+ }
807
+ }, {
808
+ key: "flushSendBuffer",
809
+ value: function flushSendBuffer() {
810
+ if (this.isConnected() && this.sendBuffer.length > 0) {
811
+ this.sendBuffer.forEach(function (callback) {
812
+ return callback();
813
+ });
814
+ this.sendBuffer = [];
815
+ }
816
+ }
817
+ }, {
818
+ key: "onConnMessage",
819
+ 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);
834
+ });
835
+ }
836
+ }]);
837
+
838
+ return Socket;
839
+ })();
840
+
841
+ var LongPoll = exports.LongPoll = (function () {
842
+ function LongPoll(endPoint) {
843
+ _classCallCheck(this, LongPoll);
844
+
845
+ this.endPoint = null;
846
+ this.token = null;
847
+ this.skipHeartbeat = true;
848
+ this.onopen = function () {}; // noop
849
+ this.onerror = function () {}; // noop
850
+ this.onmessage = function () {}; // noop
851
+ this.onclose = function () {}; // noop
852
+ this.pollEndpoint = this.normalizeEndpoint(endPoint);
853
+ this.readyState = SOCKET_STATES.connecting;
854
+
855
+ this.poll();
856
+ }
857
+
858
+ _createClass(LongPoll, [{
859
+ key: "normalizeEndpoint",
860
+ value: function normalizeEndpoint(endPoint) {
861
+ return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
862
+ }
863
+ }, {
864
+ key: "endpointURL",
865
+ value: function endpointURL() {
866
+ return Ajax.appendParams(this.pollEndpoint, { token: this.token });
867
+ }
868
+ }, {
869
+ key: "closeAndRetry",
870
+ value: function closeAndRetry() {
871
+ this.close();
872
+ this.readyState = SOCKET_STATES.connecting;
873
+ }
874
+ }, {
875
+ key: "ontimeout",
876
+ value: function ontimeout() {
877
+ this.onerror("timeout");
878
+ this.closeAndRetry();
879
+ }
880
+ }, {
881
+ key: "poll",
882
+ value: function poll() {
883
+ var _this8 = this;
884
+
885
+ if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
886
+ return;
887
+ }
888
+
889
+ Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
890
+ if (resp) {
891
+ var status = resp.status;
892
+ var token = resp.token;
893
+ var messages = resp.messages;
894
+
895
+ _this8.token = token;
896
+ } else {
897
+ var status = 0;
898
+ }
899
+
900
+ switch (status) {
901
+ case 200:
902
+ messages.forEach(function (msg) {
903
+ return _this8.onmessage({ data: JSON.stringify(msg) });
904
+ });
905
+ _this8.poll();
906
+ break;
907
+ case 204:
908
+ _this8.poll();
909
+ break;
910
+ case 410:
911
+ _this8.readyState = SOCKET_STATES.open;
912
+ _this8.onopen();
913
+ _this8.poll();
914
+ break;
915
+ case 0:
916
+ case 500:
917
+ _this8.onerror();
918
+ _this8.closeAndRetry();
919
+ break;
920
+ default:
921
+ throw "unhandled poll status " + status;
922
+ }
923
+ });
924
+ }
925
+ }, {
926
+ key: "send",
927
+ value: function send(body) {
928
+ var _this9 = this;
929
+
930
+ Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
931
+ if (!resp || resp.status !== 200) {
932
+ _this9.onerror(status);
933
+ _this9.closeAndRetry();
934
+ }
935
+ });
936
+ }
937
+ }, {
938
+ key: "close",
939
+ value: function close(code, reason) {
940
+ this.readyState = SOCKET_STATES.closed;
941
+ this.onclose();
942
+ }
943
+ }]);
944
+
945
+ return LongPoll;
946
+ })();
947
+
948
+ var Ajax = exports.Ajax = (function () {
949
+ function Ajax() {
950
+ _classCallCheck(this, Ajax);
951
+ }
952
+
953
+ _createClass(Ajax, null, [{
954
+ key: "request",
955
+ value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
956
+ if (window.XDomainRequest) {
957
+ var req = new XDomainRequest(); // IE8, IE9
958
+ this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
959
+ } else {
960
+ var req = window.XMLHttpRequest ? new XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
961
+ new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
962
+ this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback);
963
+ }
964
+ }
965
+ }, {
966
+ key: "xdomainRequest",
967
+ value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
968
+ var _this10 = this;
969
+
970
+ req.timeout = timeout;
971
+ req.open(method, endPoint);
972
+ req.onload = function () {
973
+ var response = _this10.parseJSON(req.responseText);
974
+ callback && callback(response);
975
+ };
976
+ if (ontimeout) {
977
+ req.ontimeout = ontimeout;
978
+ }
979
+
980
+ // Work around bug in IE9 that requires an attached onprogress handler
981
+ req.onprogress = function () {};
982
+
983
+ req.send(body);
984
+ }
985
+ }, {
986
+ key: "xhrRequest",
987
+ value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
988
+ var _this11 = this;
989
+
990
+ req.timeout = timeout;
991
+ req.open(method, endPoint, true);
992
+ req.setRequestHeader("Content-Type", accept);
993
+ req.onerror = function () {
994
+ callback && callback(null);
995
+ };
996
+ req.onreadystatechange = function () {
997
+ if (req.readyState === _this11.states.complete && callback) {
998
+ var response = _this11.parseJSON(req.responseText);
999
+ callback(response);
1000
+ }
1001
+ };
1002
+ if (ontimeout) {
1003
+ req.ontimeout = ontimeout;
1004
+ }
1005
+
1006
+ req.send(body);
1007
+ }
1008
+ }, {
1009
+ key: "parseJSON",
1010
+ value: function parseJSON(resp) {
1011
+ return resp && resp !== "" ? JSON.parse(resp) : null;
1012
+ }
1013
+ }, {
1014
+ key: "serialize",
1015
+ value: function serialize(obj, parentKey) {
1016
+ var queryStr = [];
1017
+ for (var key in obj) {
1018
+ if (!obj.hasOwnProperty(key)) {
1019
+ continue;
1020
+ }
1021
+ var paramKey = parentKey ? parentKey + "[" + key + "]" : key;
1022
+ var paramVal = obj[key];
1023
+ if ((typeof paramVal === "undefined" ? "undefined" : _typeof(paramVal)) === "object") {
1024
+ queryStr.push(this.serialize(paramVal, paramKey));
1025
+ } else {
1026
+ queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
1027
+ }
1028
+ }
1029
+ return queryStr.join("&");
1030
+ }
1031
+ }, {
1032
+ key: "appendParams",
1033
+ value: function appendParams(url, params) {
1034
+ if (Object.keys(params).length === 0) {
1035
+ return url;
1036
+ }
1037
+
1038
+ var prefix = url.match(/\?/) ? "&" : "?";
1039
+ return "" + url + prefix + this.serialize(params);
1040
+ }
1041
+ }]);
1042
+
1043
+ return Ajax;
1044
+ })();
1045
+
1046
+ Ajax.states = { complete: 4 };
1047
+
1048
+ var Presence = exports.Presence = {
1049
+ syncState: function syncState(state, newState, onJoin, onLeave) {
1050
+ var _this12 = this;
1051
+
1052
+ var joins = {};
1053
+ var leaves = {};
1054
+
1055
+ this.map(state, function (key, presence) {
1056
+ if (!newState[key]) {
1057
+ leaves[key] = _this12.clone(presence);
1058
+ }
1059
+ });
1060
+ this.map(newState, function (key, newPresence) {
1061
+ var currentPresence = state[key];
1062
+ 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
+ })();
1085
+ } else {
1086
+ joins[key] = newPresence;
1087
+ }
1088
+ });
1089
+ this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
1090
+ },
1091
+ syncDiff: function syncDiff(state, _ref2, onJoin, onLeave) {
1092
+ var joins = _ref2.joins;
1093
+ var leaves = _ref2.leaves;
1094
+
1095
+ if (!onJoin) {
1096
+ onJoin = function () {};
1097
+ }
1098
+ if (!onLeave) {
1099
+ onLeave = function () {};
1100
+ }
1101
+
1102
+ this.map(joins, function (key, newPresence) {
1103
+ var currentPresence = state[key];
1104
+ state[key] = newPresence;
1105
+ if (currentPresence) {
1106
+ var _state$key$metas;
1107
+
1108
+ (_state$key$metas = state[key].metas).unshift.apply(_state$key$metas, _toConsumableArray(currentPresence.metas));
1109
+ }
1110
+ onJoin(key, currentPresence, newPresence);
1111
+ });
1112
+ this.map(leaves, function (key, leftPresence) {
1113
+ var currentPresence = state[key];
1114
+ if (!currentPresence) {
1115
+ return;
1116
+ }
1117
+ var refsToRemove = leftPresence.metas.map(function (m) {
1118
+ return m.phx_ref;
1119
+ });
1120
+ currentPresence.metas = currentPresence.metas.filter(function (p) {
1121
+ return refsToRemove.indexOf(p.phx_ref) < 0;
1122
+ });
1123
+ onLeave(key, currentPresence, leftPresence);
1124
+ if (currentPresence.metas.length === 0) {
1125
+ delete state[key];
1126
+ }
1127
+ });
1128
+ },
1129
+ list: function list(presences, chooser) {
1130
+ if (!chooser) {
1131
+ chooser = function (key, pres) {
1132
+ return pres;
1133
+ };
1134
+ }
1135
+
1136
+ return this.map(presences, function (key, presence) {
1137
+ return chooser(key, presence);
1138
+ });
1139
+ },
1140
+
1141
+ // private
1142
+
1143
+ map: function map(obj, func) {
1144
+ return Object.getOwnPropertyNames(obj).map(function (key) {
1145
+ return func(key, obj[key]);
1146
+ });
1147
+ },
1148
+ clone: function clone(obj) {
1149
+ return JSON.parse(JSON.stringify(obj));
1150
+ }
1151
+ };
1152
+
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 () {
1168
+ function Timer(callback, timerCalc) {
1169
+ _classCallCheck(this, Timer);
1170
+
1171
+ this.callback = callback;
1172
+ this.timerCalc = timerCalc;
1173
+ this.timer = null;
1174
+ this.tries = 0;
1175
+ }
1176
+
1177
+ _createClass(Timer, [{
1178
+ key: "reset",
1179
+ value: function reset() {
1180
+ this.tries = 0;
1181
+ clearTimeout(this.timer);
1182
+ }
1183
+
1184
+ // Cancels any previous scheduleTimeout and schedules callback
1185
+
1186
+ }, {
1187
+ key: "scheduleTimeout",
1188
+ value: function scheduleTimeout() {
1189
+ var _this13 = this;
1190
+
1191
+ clearTimeout(this.timer);
1192
+
1193
+ this.timer = setTimeout(function () {
1194
+ _this13.tries = _this13.tries + 1;
1195
+ _this13.callback();
1196
+ }, this.timerCalc(this.tries + 1));
1197
+ }
1198
+ }]);
1199
+
1200
+ return Timer;
1201
+ })();
1202
+
1203
+
1204
+ })(typeof(exports) === "undefined" ? window.Phoenix = window.Phoenix || {} : exports);