opal-phoenix 0.0.5 → 0.0.6

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
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);