phoenix.js 43 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432
  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) {
  6. "use strict";
  7. Object.defineProperty(exports, "__esModule", {
  8. value: true
  9. });
  10. 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. 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"); } }; }();
  12. 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; }; }();
  13. 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); } }
  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. * ```javascript
  25. * let socket = new Socket("/socket", {params: {userToken: "123"}})
  26. * socket.connect()
  27. * ```
  28. *
  29. * The `Socket` constructor takes the mount point of the socket,
  30. * the authentication params, as well as options that can be found in
  31. * the Socket docs, such as configuring the `LongPoll` transport, and
  32. * heartbeat.
  33. *
  34. * ## Channels
  35. *
  36. * Channels are isolated, concurrent processes on the server that
  37. * subscribe to topics and broker events between the client and server.
  38. * To join a channel, you must provide the topic, and channel params for
  39. * authorization. Here's an example chat room example where `"new_msg"`
  40. * events are listened for, messages are pushed to the server, and
  41. * the channel is joined with ok/error/timeout matches:
  42. *
  43. * ```javascript
  44. * let channel = socket.channel("room:123", {token: roomToken})
  45. * channel.on("new_msg", msg => console.log("Got message", msg) )
  46. * $input.onEnter( e => {
  47. * channel.push("new_msg", {body: e.target.val}, 10000)
  48. * .receive("ok", (msg) => console.log("created message", msg) )
  49. * .receive("error", (reasons) => console.log("create failed", reasons) )
  50. * .receive("timeout", () => console.log("Networking issue...") )
  51. * })
  52. * channel.join()
  53. * .receive("ok", ({messages}) => console.log("catching up", messages) )
  54. * .receive("error", ({reason}) => console.log("failed join", reason) )
  55. * .receive("timeout", () => console.log("Networking issue. Still waiting...") )
  56. *```
  57. *
  58. * ## Joining
  59. *
  60. * Creating a channel with `socket.channel(topic, params)`, binds the params to
  61. * `channel.params`, which are sent up on `channel.join()`.
  62. * Subsequent rejoins will send up the modified params for
  63. * updating authorization params, or passing up last_message_id information.
  64. * Successful joins receive an "ok" status, while unsuccessful joins
  65. * receive "error".
  66. *
  67. * ## Duplicate Join Subscriptions
  68. *
  69. * While the client may join any number of topics on any number of channels,
  70. * the client may only hold a single subscription for each unique topic at any
  71. * given time. When attempting to create a duplicate subscription,
  72. * the server will close the existing channel, log a warning, and
  73. * spawn a new channel for the topic. The client will have their
  74. * `channel.onClose` callbacks fired for the existing channel, and the new
  75. * channel join will have its receive hooks processed as normal.
  76. *
  77. * ## Pushing Messages
  78. *
  79. * From the previous example, we can see that pushing messages to the server
  80. * can be done with `channel.push(eventName, payload)` and we can optionally
  81. * receive responses from the push. Additionally, we can use
  82. * `receive("timeout", callback)` to abort waiting for our other `receive` hooks
  83. * and take action after some period of waiting. The default timeout is 5000ms.
  84. *
  85. *
  86. * ## Socket Hooks
  87. *
  88. * Lifecycle events of the multiplexed connection can be hooked into via
  89. * `socket.onError()` and `socket.onClose()` events, ie:
  90. *
  91. * ```javascript
  92. * socket.onError( () => console.log("there was an error with the connection!") )
  93. * socket.onClose( () => console.log("the connection dropped") )
  94. * ```
  95. *
  96. *
  97. * ## Channel Hooks
  98. *
  99. * For each joined channel, you can bind to `onError` and `onClose` events
  100. * to monitor the channel lifecycle, ie:
  101. *
  102. * ```javascript
  103. * channel.onError( () => console.log("there was an error!") )
  104. * channel.onClose( () => console.log("the channel has gone away gracefully") )
  105. * ```
  106. *
  107. * ### onError hooks
  108. *
  109. * `onError` hooks are invoked if the socket connection drops, or the channel
  110. * crashes on the server. In either case, a channel rejoin is attempted
  111. * automatically in an exponential backoff manner.
  112. *
  113. * ### onClose hooks
  114. *
  115. * `onClose` hooks are invoked only in two cases. 1) the channel explicitly
  116. * closed on the server, or 2). The client explicitly closed, by calling
  117. * `channel.leave()`
  118. *
  119. *
  120. * ## Presence
  121. *
  122. * The `Presence` object provides features for syncing presence information
  123. * from the server with the client and handling presences joining and leaving.
  124. *
  125. * ### Syncing initial state from the server
  126. *
  127. * `Presence.syncState` is used to sync the list of presences on the server
  128. * with the client's state. An optional `onJoin` and `onLeave` callback can
  129. * be provided to react to changes in the client's local presences across
  130. * disconnects and reconnects with the server.
  131. *
  132. * `Presence.syncDiff` is used to sync a diff of presence join and leave
  133. * events from the server, as they happen. Like `syncState`, `syncDiff`
  134. * accepts optional `onJoin` and `onLeave` callbacks to react to a user
  135. * joining or leaving from a device.
  136. *
  137. * ### Listing Presences
  138. *
  139. * `Presence.list` is used to return a list of presence information
  140. * based on the local state of metadata. By default, all presence
  141. * metadata is returned, but a `listBy` function can be supplied to
  142. * allow the client to select which metadata to use for a given presence.
  143. * For example, you may have a user online from different devices with
  144. * a metadata status of "online", but they have set themselves to "away"
  145. * on another device. In this case, the app may choose to use the "away"
  146. * status for what appears on the UI. The example below defines a `listBy`
  147. * function which prioritizes the first metadata which was registered for
  148. * each user. This could be the first tab they opened, or the first device
  149. * they came online from:
  150. *
  151. * ```javascript
  152. * let state = {}
  153. * state = Presence.syncState(state, stateFromServer)
  154. * let listBy = (id, {metas: [first, ...rest]}) => {
  155. * first.count = rest.length + 1 // count of this user's presences
  156. * first.id = id
  157. * return first
  158. * }
  159. * let onlineUsers = Presence.list(state, listBy)
  160. * ```
  161. *
  162. *
  163. * ### Example Usage
  164. *```javascript
  165. * // detect if user has joined for the 1st time or from another tab/device
  166. * let onJoin = (id, current, newPres) => {
  167. * if(!current){
  168. * console.log("user has entered for the first time", newPres)
  169. * } else {
  170. * console.log("user additional presence", newPres)
  171. * }
  172. * }
  173. * // detect if user has left from all tabs/devices, or is still present
  174. * let onLeave = (id, current, leftPres) => {
  175. * if(current.metas.length === 0){
  176. * console.log("user has left from all devices", leftPres)
  177. * } else {
  178. * console.log("user left from a device", leftPres)
  179. * }
  180. * }
  181. * let presences = {} // client's initial empty presence state
  182. * // receive initial presence data from server, sent after join
  183. * myChannel.on("presence_state", state => {
  184. * presences = Presence.syncState(presences, state, onJoin, onLeave)
  185. * displayUsers(Presence.list(presences))
  186. * })
  187. * // receive "presence_diff" from server, containing join/leave events
  188. * myChannel.on("presence_diff", diff => {
  189. * presences = Presence.syncDiff(presences, diff, onJoin, onLeave)
  190. * this.setState({users: Presence.list(room.presences, listBy)})
  191. * })
  192. * ```
  193. * @module phoenix
  194. */
  195. var VSN = "2.0.0";
  196. var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 };
  197. var DEFAULT_TIMEOUT = 10000;
  198. var WS_CLOSE_NORMAL = 1000;
  199. var CHANNEL_STATES = {
  200. closed: "closed",
  201. errored: "errored",
  202. joined: "joined",
  203. joining: "joining",
  204. leaving: "leaving"
  205. };
  206. var CHANNEL_EVENTS = {
  207. close: "phx_close",
  208. error: "phx_error",
  209. join: "phx_join",
  210. reply: "phx_reply",
  211. leave: "phx_leave"
  212. };
  213. var CHANNEL_LIFECYCLE_EVENTS = [CHANNEL_EVENTS.close, CHANNEL_EVENTS.error, CHANNEL_EVENTS.join, CHANNEL_EVENTS.reply, CHANNEL_EVENTS.leave];
  214. var TRANSPORTS = {
  215. longpoll: "longpoll",
  216. websocket: "websocket"
  217. };
  218. /**
  219. * Initializes the Push
  220. * @param {Channel} channel - The Channel
  221. * @param {string} event - The event, for example `"phx_join"`
  222. * @param {Object} payload - The payload, for example `{user_id: 123}`
  223. * @param {number} timeout - The push timeout in milliseconds
  224. */
  225. var Push = function () {
  226. function Push(channel, event, payload, timeout) {
  227. _classCallCheck(this, Push);
  228. this.channel = channel;
  229. this.event = event;
  230. this.payload = payload || {};
  231. this.receivedResp = null;
  232. this.timeout = timeout;
  233. this.timeoutTimer = null;
  234. this.recHooks = [];
  235. this.sent = false;
  236. }
  237. /**
  238. *
  239. * @param {number} timeout
  240. */
  241. _createClass(Push, [{
  242. key: "resend",
  243. value: function resend(timeout) {
  244. this.timeout = timeout;
  245. this.reset();
  246. this.send();
  247. }
  248. /**
  249. *
  250. */
  251. }, {
  252. key: "send",
  253. value: function send() {
  254. if (this.hasReceived("timeout")) {
  255. return;
  256. }
  257. this.startTimeout();
  258. this.sent = true;
  259. this.channel.socket.push({
  260. topic: this.channel.topic,
  261. event: this.event,
  262. payload: this.payload,
  263. ref: this.ref,
  264. join_ref: this.channel.joinRef()
  265. });
  266. }
  267. /**
  268. *
  269. * @param {*} status
  270. * @param {*} callback
  271. */
  272. }, {
  273. key: "receive",
  274. value: function receive(status, callback) {
  275. if (this.hasReceived(status)) {
  276. callback(this.receivedResp.response);
  277. }
  278. this.recHooks.push({ status: status, callback: callback });
  279. return this;
  280. }
  281. // private
  282. }, {
  283. key: "reset",
  284. value: function reset() {
  285. this.cancelRefEvent();
  286. this.ref = null;
  287. this.refEvent = null;
  288. this.receivedResp = null;
  289. this.sent = false;
  290. }
  291. }, {
  292. key: "matchReceive",
  293. value: function matchReceive(_ref) {
  294. var status = _ref.status,
  295. response = _ref.response,
  296. ref = _ref.ref;
  297. this.recHooks.filter(function (h) {
  298. return h.status === status;
  299. }).forEach(function (h) {
  300. return h.callback(response);
  301. });
  302. }
  303. }, {
  304. key: "cancelRefEvent",
  305. value: function cancelRefEvent() {
  306. if (!this.refEvent) {
  307. return;
  308. }
  309. this.channel.off(this.refEvent);
  310. }
  311. }, {
  312. key: "cancelTimeout",
  313. value: function cancelTimeout() {
  314. clearTimeout(this.timeoutTimer);
  315. this.timeoutTimer = null;
  316. }
  317. }, {
  318. key: "startTimeout",
  319. value: function startTimeout() {
  320. var _this = this;
  321. if (this.timeoutTimer) {
  322. this.cancelTimeout();
  323. }
  324. this.ref = this.channel.socket.makeRef();
  325. this.refEvent = this.channel.replyEventName(this.ref);
  326. this.channel.on(this.refEvent, function (payload) {
  327. _this.cancelRefEvent();
  328. _this.cancelTimeout();
  329. _this.receivedResp = payload;
  330. _this.matchReceive(payload);
  331. });
  332. this.timeoutTimer = setTimeout(function () {
  333. _this.trigger("timeout", {});
  334. }, this.timeout);
  335. }
  336. }, {
  337. key: "hasReceived",
  338. value: function hasReceived(status) {
  339. return this.receivedResp && this.receivedResp.status === status;
  340. }
  341. }, {
  342. key: "trigger",
  343. value: function trigger(status, response) {
  344. this.channel.trigger(this.refEvent, { status: status, response: response });
  345. }
  346. }]);
  347. return Push;
  348. }();
  349. /**
  350. *
  351. * @param {string} topic
  352. * @param {Object} params
  353. * @param {Socket} socket
  354. */
  355. var Channel = exports.Channel = function () {
  356. function Channel(topic, params, socket) {
  357. var _this2 = this;
  358. _classCallCheck(this, Channel);
  359. this.state = CHANNEL_STATES.closed;
  360. this.topic = topic;
  361. this.params = params || {};
  362. this.socket = socket;
  363. this.bindings = [];
  364. this.timeout = this.socket.timeout;
  365. this.joinedOnce = false;
  366. this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout);
  367. this.pushBuffer = [];
  368. this.rejoinTimer = new Timer(function () {
  369. return _this2.rejoinUntilConnected();
  370. }, this.socket.reconnectAfterMs);
  371. this.joinPush.receive("ok", function () {
  372. _this2.state = CHANNEL_STATES.joined;
  373. _this2.rejoinTimer.reset();
  374. _this2.pushBuffer.forEach(function (pushEvent) {
  375. return pushEvent.send();
  376. });
  377. _this2.pushBuffer = [];
  378. });
  379. this.onClose(function () {
  380. _this2.rejoinTimer.reset();
  381. _this2.socket.log("channel", "close " + _this2.topic + " " + _this2.joinRef());
  382. _this2.state = CHANNEL_STATES.closed;
  383. _this2.socket.remove(_this2);
  384. });
  385. this.onError(function (reason) {
  386. if (_this2.isLeaving() || _this2.isClosed()) {
  387. return;
  388. }
  389. _this2.socket.log("channel", "error " + _this2.topic, reason);
  390. _this2.state = CHANNEL_STATES.errored;
  391. _this2.rejoinTimer.scheduleTimeout();
  392. });
  393. this.joinPush.receive("timeout", function () {
  394. if (!_this2.isJoining()) {
  395. return;
  396. }
  397. _this2.socket.log("channel", "timeout " + _this2.topic + " (" + _this2.joinRef() + ")", _this2.joinPush.timeout);
  398. var leavePush = new Push(_this2, CHANNEL_EVENTS.leave, {}, _this2.timeout);
  399. leavePush.send();
  400. _this2.state = CHANNEL_STATES.errored;
  401. _this2.joinPush.reset();
  402. _this2.rejoinTimer.scheduleTimeout();
  403. });
  404. this.on(CHANNEL_EVENTS.reply, function (payload, ref) {
  405. _this2.trigger(_this2.replyEventName(ref), payload);
  406. });
  407. }
  408. _createClass(Channel, [{
  409. key: "rejoinUntilConnected",
  410. value: function rejoinUntilConnected() {
  411. this.rejoinTimer.scheduleTimeout();
  412. if (this.socket.isConnected()) {
  413. this.rejoin();
  414. }
  415. }
  416. }, {
  417. key: "join",
  418. value: function join() {
  419. var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
  420. if (this.joinedOnce) {
  421. throw "tried to join multiple times. 'join' can only be called a single time per channel instance";
  422. } else {
  423. this.joinedOnce = true;
  424. this.rejoin(timeout);
  425. return this.joinPush;
  426. }
  427. }
  428. }, {
  429. key: "onClose",
  430. value: function onClose(callback) {
  431. this.on(CHANNEL_EVENTS.close, callback);
  432. }
  433. }, {
  434. key: "onError",
  435. value: function onError(callback) {
  436. this.on(CHANNEL_EVENTS.error, function (reason) {
  437. return callback(reason);
  438. });
  439. }
  440. }, {
  441. key: "on",
  442. value: function on(event, callback) {
  443. this.bindings.push({ event: event, callback: callback });
  444. }
  445. }, {
  446. key: "off",
  447. value: function off(event) {
  448. this.bindings = this.bindings.filter(function (bind) {
  449. return bind.event !== event;
  450. });
  451. }
  452. }, {
  453. key: "canPush",
  454. value: function canPush() {
  455. return this.socket.isConnected() && this.isJoined();
  456. }
  457. }, {
  458. key: "push",
  459. value: function push(event, payload) {
  460. var timeout = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : this.timeout;
  461. if (!this.joinedOnce) {
  462. throw "tried to push '" + event + "' to '" + this.topic + "' before joining. Use channel.join() before pushing events";
  463. }
  464. var pushEvent = new Push(this, event, payload, timeout);
  465. if (this.canPush()) {
  466. pushEvent.send();
  467. } else {
  468. pushEvent.startTimeout();
  469. this.pushBuffer.push(pushEvent);
  470. }
  471. return pushEvent;
  472. }
  473. /** Leaves the channel
  474. *
  475. * Unsubscribes from server events, and
  476. * instructs channel to terminate on server
  477. *
  478. * Triggers onClose() hooks
  479. *
  480. * To receive leave acknowledgements, use the a `receive`
  481. * hook to bind to the server ack, ie:
  482. *
  483. * ```javascript
  484. * channel.leave().receive("ok", () => alert("left!") )
  485. * ```
  486. */
  487. }, {
  488. key: "leave",
  489. value: function leave() {
  490. var _this3 = this;
  491. var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
  492. this.state = CHANNEL_STATES.leaving;
  493. var onClose = function onClose() {
  494. _this3.socket.log("channel", "leave " + _this3.topic);
  495. _this3.trigger(CHANNEL_EVENTS.close, "leave");
  496. };
  497. var leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout);
  498. leavePush.receive("ok", function () {
  499. return onClose();
  500. }).receive("timeout", function () {
  501. return onClose();
  502. });
  503. leavePush.send();
  504. if (!this.canPush()) {
  505. leavePush.trigger("ok", {});
  506. }
  507. return leavePush;
  508. }
  509. /**
  510. * Overridable message hook
  511. *
  512. * Receives all events for specialized message handling
  513. * before dispatching to the channel callbacks.
  514. *
  515. * Must return the payload, modified or unmodified
  516. */
  517. }, {
  518. key: "onMessage",
  519. value: function onMessage(event, payload, ref) {
  520. return payload;
  521. }
  522. // private
  523. }, {
  524. key: "isMember",
  525. value: function isMember(topic, event, payload, joinRef) {
  526. if (this.topic !== topic) {
  527. return false;
  528. }
  529. var isLifecycleEvent = CHANNEL_LIFECYCLE_EVENTS.indexOf(event) >= 0;
  530. if (joinRef && isLifecycleEvent && joinRef !== this.joinRef()) {
  531. this.socket.log("channel", "dropping outdated message", { topic: topic, event: event, payload: payload, joinRef: joinRef });
  532. return false;
  533. } else {
  534. return true;
  535. }
  536. }
  537. }, {
  538. key: "joinRef",
  539. value: function joinRef() {
  540. return this.joinPush.ref;
  541. }
  542. }, {
  543. key: "sendJoin",
  544. value: function sendJoin(timeout) {
  545. this.state = CHANNEL_STATES.joining;
  546. this.joinPush.resend(timeout);
  547. }
  548. }, {
  549. key: "rejoin",
  550. value: function rejoin() {
  551. var timeout = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.timeout;
  552. if (this.isLeaving()) {
  553. return;
  554. }
  555. this.sendJoin(timeout);
  556. }
  557. }, {
  558. key: "trigger",
  559. value: function trigger(event, payload, ref, joinRef) {
  560. var _this4 = this;
  561. var handledPayload = this.onMessage(event, payload, ref, joinRef);
  562. if (payload && !handledPayload) {
  563. throw "channel onMessage callbacks must return the payload, modified or unmodified";
  564. }
  565. this.bindings.filter(function (bind) {
  566. return bind.event === event;
  567. }).map(function (bind) {
  568. return bind.callback(handledPayload, ref, joinRef || _this4.joinRef());
  569. });
  570. }
  571. }, {
  572. key: "replyEventName",
  573. value: function replyEventName(ref) {
  574. return "chan_reply_" + ref;
  575. }
  576. }, {
  577. key: "isClosed",
  578. value: function isClosed() {
  579. return this.state === CHANNEL_STATES.closed;
  580. }
  581. }, {
  582. key: "isErrored",
  583. value: function isErrored() {
  584. return this.state === CHANNEL_STATES.errored;
  585. }
  586. }, {
  587. key: "isJoined",
  588. value: function isJoined() {
  589. return this.state === CHANNEL_STATES.joined;
  590. }
  591. }, {
  592. key: "isJoining",
  593. value: function isJoining() {
  594. return this.state === CHANNEL_STATES.joining;
  595. }
  596. }, {
  597. key: "isLeaving",
  598. value: function isLeaving() {
  599. return this.state === CHANNEL_STATES.leaving;
  600. }
  601. }]);
  602. return Channel;
  603. }();
  604. var Serializer = {
  605. encode: function encode(msg, callback) {
  606. var payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload];
  607. return callback(JSON.stringify(payload));
  608. },
  609. decode: function decode(rawPayload, callback) {
  610. var _JSON$parse = JSON.parse(rawPayload),
  611. _JSON$parse2 = _slicedToArray(_JSON$parse, 5),
  612. join_ref = _JSON$parse2[0],
  613. ref = _JSON$parse2[1],
  614. topic = _JSON$parse2[2],
  615. event = _JSON$parse2[3],
  616. payload = _JSON$parse2[4];
  617. return callback({ join_ref: join_ref, ref: ref, topic: topic, event: event, payload: payload });
  618. }
  619. };
  620. /** Initializes the Socket
  621. *
  622. *
  623. * For IE8 support use an ES5-shim (https://github.com/es-shims/es5-shim)
  624. *
  625. * @param {string} endPoint - The string WebSocket endpoint, ie, `"ws://example.com/socket"`,
  626. * `"wss://example.com"`
  627. * `"/socket"` (inherited host & protocol)
  628. * @param {Object} opts - Optional configuration
  629. * @param {string} opts.transport - The Websocket Transport, for example WebSocket or Phoenix.LongPoll.
  630. *
  631. * Defaults to WebSocket with automatic LongPoll fallback.
  632. * @param {Function} opts.encode - The function to encode outgoing messages.
  633. *
  634. * Defaults to JSON:
  635. *
  636. * ```javascript
  637. * (payload, callback) => callback(JSON.stringify(payload))
  638. * ```
  639. *
  640. * @param {Function} opts.decode - The function to decode incoming messages.
  641. *
  642. * Defaults to JSON:
  643. *
  644. * ```javascript
  645. * (payload, callback) => callback(JSON.parse(payload))
  646. * ```
  647. *
  648. * @param {number} opts.timeout - The default timeout in milliseconds to trigger push timeouts.
  649. *
  650. * Defaults `DEFAULT_TIMEOUT`
  651. * @param {number} opts.heartbeatIntervalMs - The millisec interval to send a heartbeat message
  652. * @param {number} opts.reconnectAfterMs - The optional function that returns the millsec reconnect interval.
  653. *
  654. * Defaults to stepped backoff of:
  655. *
  656. * ```javascript
  657. * function(tries){
  658. * return [1000, 5000, 10000][tries - 1] || 10000
  659. * }
  660. * ```
  661. * @param {Function} opts.logger - The optional function for specialized logging, ie:
  662. * ```javascript
  663. * logger: (kind, msg, data) => { console.log(`${kind}: ${msg}`, data) }
  664. * ```
  665. *
  666. * @param {number} opts.longpollerTimeout - The maximum timeout of a long poll AJAX request.
  667. *
  668. * Defaults to 20s (double the server long poll timer).
  669. *
  670. * @param {Object} opts.params - The optional params to pass when connecting
  671. *
  672. *
  673. */
  674. var Socket = exports.Socket = function () {
  675. function Socket(endPoint) {
  676. var _this5 = this;
  677. var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  678. _classCallCheck(this, Socket);
  679. this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] };
  680. this.channels = [];
  681. this.sendBuffer = [];
  682. this.ref = 0;
  683. this.timeout = opts.timeout || DEFAULT_TIMEOUT;
  684. this.transport = opts.transport || window.WebSocket || LongPoll;
  685. this.defaultEncoder = Serializer.encode;
  686. this.defaultDecoder = Serializer.decode;
  687. if (this.transport !== LongPoll) {
  688. this.encode = opts.encode || this.defaultEncoder;
  689. this.decode = opts.decode || this.defaultDecoder;
  690. } else {
  691. this.encode = this.defaultEncoder;
  692. this.decode = this.defaultDecoder;
  693. }
  694. this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 30000;
  695. this.reconnectAfterMs = opts.reconnectAfterMs || function (tries) {
  696. return [1000, 2000, 5000, 10000][tries - 1] || 10000;
  697. };
  698. this.logger = opts.logger || function () {}; // noop
  699. this.longpollerTimeout = opts.longpollerTimeout || 20000;
  700. this.params = opts.params || {};
  701. this.endPoint = endPoint + "/" + TRANSPORTS.websocket;
  702. this.heartbeatTimer = null;
  703. this.pendingHeartbeatRef = null;
  704. this.reconnectTimer = new Timer(function () {
  705. _this5.disconnect(function () {
  706. return _this5.connect();
  707. });
  708. }, this.reconnectAfterMs);
  709. }
  710. _createClass(Socket, [{
  711. key: "protocol",
  712. value: function protocol() {
  713. return location.protocol.match(/^https/) ? "wss" : "ws";
  714. }
  715. }, {
  716. key: "endPointURL",
  717. value: function endPointURL() {
  718. var uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params), { vsn: VSN });
  719. if (uri.charAt(0) !== "/") {
  720. return uri;
  721. }
  722. if (uri.charAt(1) === "/") {
  723. return this.protocol() + ":" + uri;
  724. }
  725. return this.protocol() + "://" + location.host + uri;
  726. }
  727. }, {
  728. key: "disconnect",
  729. value: function disconnect(callback, code, reason) {
  730. if (this.conn) {
  731. this.conn.onclose = function () {}; // noop
  732. if (code) {
  733. this.conn.close(code, reason || "");
  734. } else {
  735. this.conn.close();
  736. }
  737. this.conn = null;
  738. }
  739. callback && callback();
  740. }
  741. /**
  742. *
  743. * @param {Object} params - The params to send when connecting, for example `{user_id: userToken}`
  744. */
  745. }, {
  746. key: "connect",
  747. value: function connect(params) {
  748. var _this6 = this;
  749. if (params) {
  750. console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor");
  751. this.params = params;
  752. }
  753. if (this.conn) {
  754. return;
  755. }
  756. this.conn = new this.transport(this.endPointURL());
  757. this.conn.timeout = this.longpollerTimeout;
  758. this.conn.onopen = function () {
  759. return _this6.onConnOpen();
  760. };
  761. this.conn.onerror = function (error) {
  762. return _this6.onConnError(error);
  763. };
  764. this.conn.onmessage = function (event) {
  765. return _this6.onConnMessage(event);
  766. };
  767. this.conn.onclose = function (event) {
  768. return _this6.onConnClose(event);
  769. };
  770. }
  771. /**
  772. * Logs the message. Override `this.logger` for specialized logging. noops by default
  773. * @param {string} kind
  774. * @param {string} msg
  775. * @param {Object} data
  776. */
  777. }, {
  778. key: "log",
  779. value: function log(kind, msg, data) {
  780. this.logger(kind, msg, data);
  781. }
  782. // Registers callbacks for connection state change events
  783. //
  784. // Examples
  785. //
  786. // socket.onError(function(error){ alert("An error occurred") })
  787. //
  788. }, {
  789. key: "onOpen",
  790. value: function onOpen(callback) {
  791. this.stateChangeCallbacks.open.push(callback);
  792. }
  793. }, {
  794. key: "onClose",
  795. value: function onClose(callback) {
  796. this.stateChangeCallbacks.close.push(callback);
  797. }
  798. }, {
  799. key: "onError",
  800. value: function onError(callback) {
  801. this.stateChangeCallbacks.error.push(callback);
  802. }
  803. }, {
  804. key: "onMessage",
  805. value: function onMessage(callback) {
  806. this.stateChangeCallbacks.message.push(callback);
  807. }
  808. }, {
  809. key: "onConnOpen",
  810. value: function onConnOpen() {
  811. var _this7 = this;
  812. this.log("transport", "connected to " + this.endPointURL());
  813. this.flushSendBuffer();
  814. this.reconnectTimer.reset();
  815. if (!this.conn.skipHeartbeat) {
  816. clearInterval(this.heartbeatTimer);
  817. this.heartbeatTimer = setInterval(function () {
  818. return _this7.sendHeartbeat();
  819. }, this.heartbeatIntervalMs);
  820. }
  821. this.stateChangeCallbacks.open.forEach(function (callback) {
  822. return callback();
  823. });
  824. }
  825. }, {
  826. key: "onConnClose",
  827. value: function onConnClose(event) {
  828. this.log("transport", "close", event);
  829. this.triggerChanError();
  830. clearInterval(this.heartbeatTimer);
  831. this.reconnectTimer.scheduleTimeout();
  832. this.stateChangeCallbacks.close.forEach(function (callback) {
  833. return callback(event);
  834. });
  835. }
  836. }, {
  837. key: "onConnError",
  838. value: function onConnError(error) {
  839. this.log("transport", error);
  840. this.triggerChanError();
  841. this.stateChangeCallbacks.error.forEach(function (callback) {
  842. return callback(error);
  843. });
  844. }
  845. }, {
  846. key: "triggerChanError",
  847. value: function triggerChanError() {
  848. this.channels.forEach(function (channel) {
  849. return channel.trigger(CHANNEL_EVENTS.error);
  850. });
  851. }
  852. }, {
  853. key: "connectionState",
  854. value: function connectionState() {
  855. switch (this.conn && this.conn.readyState) {
  856. case SOCKET_STATES.connecting:
  857. return "connecting";
  858. case SOCKET_STATES.open:
  859. return "open";
  860. case SOCKET_STATES.closing:
  861. return "closing";
  862. default:
  863. return "closed";
  864. }
  865. }
  866. }, {
  867. key: "isConnected",
  868. value: function isConnected() {
  869. return this.connectionState() === "open";
  870. }
  871. }, {
  872. key: "remove",
  873. value: function remove(channel) {
  874. this.channels = this.channels.filter(function (c) {
  875. return c.joinRef() !== channel.joinRef();
  876. });
  877. }
  878. }, {
  879. key: "channel",
  880. value: function channel(topic) {
  881. var chanParams = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
  882. var chan = new Channel(topic, chanParams, this);
  883. this.channels.push(chan);
  884. return chan;
  885. }
  886. }, {
  887. key: "push",
  888. value: function push(data) {
  889. var _this8 = this;
  890. var topic = data.topic,
  891. event = data.event,
  892. payload = data.payload,
  893. ref = data.ref,
  894. join_ref = data.join_ref;
  895. var callback = function callback() {
  896. _this8.encode(data, function (result) {
  897. _this8.conn.send(result);
  898. });
  899. };
  900. this.log("push", topic + " " + event + " (" + join_ref + ", " + ref + ")", payload);
  901. if (this.isConnected()) {
  902. callback();
  903. } else {
  904. this.sendBuffer.push(callback);
  905. }
  906. }
  907. /**
  908. * Return the next message ref, accounting for overflows
  909. */
  910. }, {
  911. key: "makeRef",
  912. value: function makeRef() {
  913. var newRef = this.ref + 1;
  914. if (newRef === this.ref) {
  915. this.ref = 0;
  916. } else {
  917. this.ref = newRef;
  918. }
  919. return this.ref.toString();
  920. }
  921. }, {
  922. key: "sendHeartbeat",
  923. value: function sendHeartbeat() {
  924. if (!this.isConnected()) {
  925. return;
  926. }
  927. if (this.pendingHeartbeatRef) {
  928. this.pendingHeartbeatRef = null;
  929. this.log("transport", "heartbeat timeout. Attempting to re-establish connection");
  930. this.conn.close(WS_CLOSE_NORMAL, "hearbeat timeout");
  931. return;
  932. }
  933. this.pendingHeartbeatRef = this.makeRef();
  934. this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef });
  935. }
  936. }, {
  937. key: "flushSendBuffer",
  938. value: function flushSendBuffer() {
  939. if (this.isConnected() && this.sendBuffer.length > 0) {
  940. this.sendBuffer.forEach(function (callback) {
  941. return callback();
  942. });
  943. this.sendBuffer = [];
  944. }
  945. }
  946. }, {
  947. key: "onConnMessage",
  948. value: function onConnMessage(rawMessage) {
  949. var _this9 = this;
  950. this.decode(rawMessage.data, function (msg) {
  951. var topic = msg.topic,
  952. event = msg.event,
  953. payload = msg.payload,
  954. ref = msg.ref,
  955. join_ref = msg.join_ref;
  956. if (ref && ref === _this9.pendingHeartbeatRef) {
  957. _this9.pendingHeartbeatRef = null;
  958. }
  959. _this9.log("receive", (payload.status || "") + " " + topic + " " + event + " " + (ref && "(" + ref + ")" || ""), payload);
  960. _this9.channels.filter(function (channel) {
  961. return channel.isMember(topic, event, payload, join_ref);
  962. }).forEach(function (channel) {
  963. return channel.trigger(event, payload, ref, join_ref);
  964. });
  965. _this9.stateChangeCallbacks.message.forEach(function (callback) {
  966. return callback(msg);
  967. });
  968. });
  969. }
  970. }]);
  971. return Socket;
  972. }();
  973. var LongPoll = exports.LongPoll = function () {
  974. function LongPoll(endPoint) {
  975. _classCallCheck(this, LongPoll);
  976. this.endPoint = null;
  977. this.token = null;
  978. this.skipHeartbeat = true;
  979. this.onopen = function () {}; // noop
  980. this.onerror = function () {}; // noop
  981. this.onmessage = function () {}; // noop
  982. this.onclose = function () {}; // noop
  983. this.pollEndpoint = this.normalizeEndpoint(endPoint);
  984. this.readyState = SOCKET_STATES.connecting;
  985. this.poll();
  986. }
  987. _createClass(LongPoll, [{
  988. key: "normalizeEndpoint",
  989. value: function normalizeEndpoint(endPoint) {
  990. return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)\/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll);
  991. }
  992. }, {
  993. key: "endpointURL",
  994. value: function endpointURL() {
  995. return Ajax.appendParams(this.pollEndpoint, { token: this.token });
  996. }
  997. }, {
  998. key: "closeAndRetry",
  999. value: function closeAndRetry() {
  1000. this.close();
  1001. this.readyState = SOCKET_STATES.connecting;
  1002. }
  1003. }, {
  1004. key: "ontimeout",
  1005. value: function ontimeout() {
  1006. this.onerror("timeout");
  1007. this.closeAndRetry();
  1008. }
  1009. }, {
  1010. key: "poll",
  1011. value: function poll() {
  1012. var _this10 = this;
  1013. if (!(this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting)) {
  1014. return;
  1015. }
  1016. Ajax.request("GET", this.endpointURL(), "application/json", null, this.timeout, this.ontimeout.bind(this), function (resp) {
  1017. if (resp) {
  1018. var status = resp.status,
  1019. token = resp.token,
  1020. messages = resp.messages;
  1021. _this10.token = token;
  1022. } else {
  1023. var status = 0;
  1024. }
  1025. switch (status) {
  1026. case 200:
  1027. messages.forEach(function (msg) {
  1028. return _this10.onmessage({ data: msg });
  1029. });
  1030. _this10.poll();
  1031. break;
  1032. case 204:
  1033. _this10.poll();
  1034. break;
  1035. case 410:
  1036. _this10.readyState = SOCKET_STATES.open;
  1037. _this10.onopen();
  1038. _this10.poll();
  1039. break;
  1040. case 0:
  1041. case 500:
  1042. _this10.onerror();
  1043. _this10.closeAndRetry();
  1044. break;
  1045. default:
  1046. throw "unhandled poll status " + status;
  1047. }
  1048. });
  1049. }
  1050. }, {
  1051. key: "send",
  1052. value: function send(body) {
  1053. var _this11 = this;
  1054. Ajax.request("POST", this.endpointURL(), "application/json", body, this.timeout, this.onerror.bind(this, "timeout"), function (resp) {
  1055. if (!resp || resp.status !== 200) {
  1056. _this11.onerror(resp && resp.status);
  1057. _this11.closeAndRetry();
  1058. }
  1059. });
  1060. }
  1061. }, {
  1062. key: "close",
  1063. value: function close(code, reason) {
  1064. this.readyState = SOCKET_STATES.closed;
  1065. this.onclose();
  1066. }
  1067. }]);
  1068. return LongPoll;
  1069. }();
  1070. var Ajax = exports.Ajax = function () {
  1071. function Ajax() {
  1072. _classCallCheck(this, Ajax);
  1073. }
  1074. _createClass(Ajax, null, [{
  1075. key: "request",
  1076. value: function request(method, endPoint, accept, body, timeout, ontimeout, callback) {
  1077. if (window.XDomainRequest) {
  1078. var req = new XDomainRequest(); // IE8, IE9
  1079. this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback);
  1080. } else {
  1081. var _req = window.XMLHttpRequest ? new window.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari
  1082. new ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5
  1083. this.xhrRequest(_req, method, endPoint, accept, body, timeout, ontimeout, callback);
  1084. }
  1085. }
  1086. }, {
  1087. key: "xdomainRequest",
  1088. value: function xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) {
  1089. var _this12 = this;
  1090. req.timeout = timeout;
  1091. req.open(method, endPoint);
  1092. req.onload = function () {
  1093. var response = _this12.parseJSON(req.responseText);
  1094. callback && callback(response);
  1095. };
  1096. if (ontimeout) {
  1097. req.ontimeout = ontimeout;
  1098. }
  1099. // Work around bug in IE9 that requires an attached onprogress handler
  1100. req.onprogress = function () {};
  1101. req.send(body);
  1102. }
  1103. }, {
  1104. key: "xhrRequest",
  1105. value: function xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) {
  1106. var _this13 = this;
  1107. req.open(method, endPoint, true);
  1108. req.timeout = timeout;
  1109. req.setRequestHeader("Content-Type", accept);
  1110. req.onerror = function () {
  1111. callback && callback(null);
  1112. };
  1113. req.onreadystatechange = function () {
  1114. if (req.readyState === _this13.states.complete && callback) {
  1115. var response = _this13.parseJSON(req.responseText);
  1116. callback(response);
  1117. }
  1118. };
  1119. if (ontimeout) {
  1120. req.ontimeout = ontimeout;
  1121. }
  1122. req.send(body);
  1123. }
  1124. }, {
  1125. key: "parseJSON",
  1126. value: function parseJSON(resp) {
  1127. if (!resp || resp === "") {
  1128. return null;
  1129. }
  1130. try {
  1131. return JSON.parse(resp);
  1132. } catch (e) {
  1133. console && console.log("failed to parse JSON response", resp);
  1134. return null;
  1135. }
  1136. }
  1137. }, {
  1138. key: "serialize",
  1139. value: function serialize(obj, parentKey) {
  1140. var queryStr = [];
  1141. for (var key in obj) {
  1142. if (!obj.hasOwnProperty(key)) {
  1143. continue;
  1144. }
  1145. var paramKey = parentKey ? parentKey + "[" + key + "]" : key;
  1146. var paramVal = obj[key];
  1147. if ((typeof paramVal === "undefined" ? "undefined" : _typeof(paramVal)) === "object") {
  1148. queryStr.push(this.serialize(paramVal, paramKey));
  1149. } else {
  1150. queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal));
  1151. }
  1152. }
  1153. return queryStr.join("&");
  1154. }
  1155. }, {
  1156. key: "appendParams",
  1157. value: function appendParams(url, params) {
  1158. if (Object.keys(params).length === 0) {
  1159. return url;
  1160. }
  1161. var prefix = url.match(/\?/) ? "&" : "?";
  1162. return "" + url + prefix + this.serialize(params);
  1163. }
  1164. }]);
  1165. return Ajax;
  1166. }();
  1167. Ajax.states = { complete: 4 };
  1168. var Presence = exports.Presence = {
  1169. syncState: function syncState(currentState, newState, onJoin, onLeave) {
  1170. var _this14 = this;
  1171. var state = this.clone(currentState);
  1172. var joins = {};
  1173. var leaves = {};
  1174. this.map(state, function (key, presence) {
  1175. if (!newState[key]) {
  1176. leaves[key] = presence;
  1177. }
  1178. });
  1179. this.map(newState, function (key, newPresence) {
  1180. var currentPresence = state[key];
  1181. if (currentPresence) {
  1182. var newRefs = newPresence.metas.map(function (m) {
  1183. return m.phx_ref;
  1184. });
  1185. var curRefs = currentPresence.metas.map(function (m) {
  1186. return m.phx_ref;
  1187. });
  1188. var joinedMetas = newPresence.metas.filter(function (m) {
  1189. return curRefs.indexOf(m.phx_ref) < 0;
  1190. });
  1191. var leftMetas = currentPresence.metas.filter(function (m) {
  1192. return newRefs.indexOf(m.phx_ref) < 0;
  1193. });
  1194. if (joinedMetas.length > 0) {
  1195. joins[key] = newPresence;
  1196. joins[key].metas = joinedMetas;
  1197. }
  1198. if (leftMetas.length > 0) {
  1199. leaves[key] = _this14.clone(currentPresence);
  1200. leaves[key].metas = leftMetas;
  1201. }
  1202. } else {
  1203. joins[key] = newPresence;
  1204. }
  1205. });
  1206. return this.syncDiff(state, { joins: joins, leaves: leaves }, onJoin, onLeave);
  1207. },
  1208. syncDiff: function syncDiff(currentState, _ref2, onJoin, onLeave) {
  1209. var joins = _ref2.joins,
  1210. leaves = _ref2.leaves;
  1211. var state = this.clone(currentState);
  1212. if (!onJoin) {
  1213. onJoin = function onJoin() {};
  1214. }
  1215. if (!onLeave) {
  1216. onLeave = function onLeave() {};
  1217. }
  1218. this.map(joins, function (key, newPresence) {
  1219. var currentPresence = state[key];
  1220. state[key] = newPresence;
  1221. if (currentPresence) {
  1222. var _state$key$metas;
  1223. (_state$key$metas = state[key].metas).unshift.apply(_state$key$metas, _toConsumableArray(currentPresence.metas));
  1224. }
  1225. onJoin(key, currentPresence, newPresence);
  1226. });
  1227. this.map(leaves, function (key, leftPresence) {
  1228. var currentPresence = state[key];
  1229. if (!currentPresence) {
  1230. return;
  1231. }
  1232. var refsToRemove = leftPresence.metas.map(function (m) {
  1233. return m.phx_ref;
  1234. });
  1235. currentPresence.metas = currentPresence.metas.filter(function (p) {
  1236. return refsToRemove.indexOf(p.phx_ref) < 0;
  1237. });
  1238. onLeave(key, currentPresence, leftPresence);
  1239. if (currentPresence.metas.length === 0) {
  1240. delete state[key];
  1241. }
  1242. });
  1243. return state;
  1244. },
  1245. list: function list(presences, chooser) {
  1246. if (!chooser) {
  1247. chooser = function chooser(key, pres) {
  1248. return pres;
  1249. };
  1250. }
  1251. return this.map(presences, function (key, presence) {
  1252. return chooser(key, presence);
  1253. });
  1254. },
  1255. // private
  1256. map: function map(obj, func) {
  1257. return Object.getOwnPropertyNames(obj).map(function (key) {
  1258. return func(key, obj[key]);
  1259. });
  1260. },
  1261. clone: function clone(obj) {
  1262. return JSON.parse(JSON.stringify(obj));
  1263. }
  1264. };
  1265. /**
  1266. *
  1267. * Creates a timer that accepts a `timerCalc` function to perform
  1268. * calculated timeout retries, such as exponential backoff.
  1269. *
  1270. * ## Examples
  1271. *
  1272. * ```javascript
  1273. * let reconnectTimer = new Timer(() => this.connect(), function(tries){
  1274. * return [1000, 5000, 10000][tries - 1] || 10000
  1275. * })
  1276. * reconnectTimer.scheduleTimeout() // fires after 1000
  1277. * reconnectTimer.scheduleTimeout() // fires after 5000
  1278. * reconnectTimer.reset()
  1279. * reconnectTimer.scheduleTimeout() // fires after 1000
  1280. * ```
  1281. * @param {Function} callback
  1282. * @param {Function} timerCalc
  1283. */
  1284. var Timer = function () {
  1285. function Timer(callback, timerCalc) {
  1286. _classCallCheck(this, Timer);
  1287. this.callback = callback;
  1288. this.timerCalc = timerCalc;
  1289. this.timer = null;
  1290. this.tries = 0;
  1291. }
  1292. _createClass(Timer, [{
  1293. key: "reset",
  1294. value: function reset() {
  1295. this.tries = 0;
  1296. clearTimeout(this.timer);
  1297. }
  1298. /**
  1299. * Cancels any previous scheduleTimeout and schedules callback
  1300. */
  1301. }, {
  1302. key: "scheduleTimeout",
  1303. value: function scheduleTimeout() {
  1304. var _this15 = this;
  1305. clearTimeout(this.timer);
  1306. this.timer = setTimeout(function () {
  1307. _this15.tries = _this15.tries + 1;
  1308. _this15.callback();
  1309. }, this.timerCalc(this.tries + 1));
  1310. }
  1311. }]);
  1312. return Timer;
  1313. }();
  1314. })));