From 379a059166f02846a4b91d4a927935b405ded894 Mon Sep 17 00:00:00 2001 From: Chris McCord Date: Wed, 30 May 2018 01:21:15 -0400 Subject: [PATCH] Accept function for socket and channel params. Closes #2825 --- assets/js/phoenix.js | 53 +++++++++++++------------------- assets/package-lock.json | 60 +++++++++++++++++++++++++++++++++++++ assets/test/channel_test.js | 41 ++++++++++++++++--------- assets/test/socket_test.js | 12 ++++++-- priv/static/phoenix.js | 2 +- 5 files changed, 119 insertions(+), 49 deletions(-) diff --git a/assets/js/phoenix.js b/assets/js/phoenix.js index 0eab863140..7e54782f3d 100644 --- a/assets/js/phoenix.js +++ b/assets/js/phoenix.js @@ -216,6 +216,16 @@ const TRANSPORTS = { websocket: "websocket" } +// wraps value in closure or returns closure +let closure = (value) => { + if(typeof(value) === "function"){ + return value + } else { + let closure = function(){ return value } + return closure + } +} + /** * Initializes the Push * @param {Channel} channel - The Channel @@ -227,7 +237,7 @@ class Push { constructor(channel, event, payload, timeout){ this.channel = channel this.event = event - this.payload = payload || {} + this.payload = payload || function(){ return {} } this.receivedResp = null this.timeout = timeout this.timeoutTimer = null @@ -254,7 +264,7 @@ class Push { this.channel.socket.push({ topic: this.channel.topic, event: this.event, - payload: this.payload, + payload: this.payload(), ref: this.ref, join_ref: this.channel.joinRef() }) @@ -274,16 +284,6 @@ class Push { return this } - /** - * Updates the Push payload for subsequent resends - * - * @param {Object} payload - * @returns {Push} - */ - updatePayload(payload) { - this.payload = payload; - } - /** * @private */ @@ -355,14 +355,14 @@ class Push { /** * * @param {string} topic - * @param {Object} params + * @param {(Object|function)} params * @param {Socket} socket */ export class Channel { constructor(topic, params, socket) { this.state = CHANNEL_STATES.closed this.topic = topic - this.params = params || {} + this.params = closure(params || {}) this.socket = socket this.bindings = [] this.bindingRef = 0 @@ -393,7 +393,7 @@ export class Channel { }) this.joinPush.receive("timeout", () => { if(!this.isJoining()){ return } this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout) - let leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, this.timeout) + let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout) leavePush.send() this.state = CHANNEL_STATES.errored this.joinPush.reset() @@ -429,17 +429,6 @@ export class Channel { } } - /** - * Updates the params passed as the second argument to `new Channel("topic", params, socket)` - * Any subsequent reconnects on the channel will send the updated params to the `join` callback on the sever - * - * @param {Object} params - */ - updateJoinParams(params = {}) { - this.params = params; - this.joinPush.updatePayload(params); - } - /** * Hook into channel close * @param {Function} callback @@ -504,7 +493,7 @@ export class Channel { if(!this.joinedOnce){ throw(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`) } - let pushEvent = new Push(this, event, payload, timeout) + let pushEvent = new Push(this, event, function(){ return payload }, timeout) if(this.canPush()){ pushEvent.send() } else { @@ -537,7 +526,7 @@ export class Channel { this.socket.log("channel", `leave ${this.topic}`) this.trigger(CHANNEL_EVENTS.close, "leave") } - let leavePush = new Push(this, CHANNEL_EVENTS.leave, {}, timeout) + let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout) leavePush.receive("ok", () => onClose() ) .receive("timeout", () => onClose() ) leavePush.send() @@ -705,7 +694,7 @@ const Serializer = { * * Defaults to 20s (double the server long poll timer). * - * @param {Object} [opts.params] - The optional params to pass when connecting + * @param {{Object|function)} [opts.params] - The optional params to pass when connecting * * */ @@ -732,7 +721,7 @@ export class Socket { } this.logger = opts.logger || function(){} // noop this.longpollerTimeout = opts.longpollerTimeout || 20000 - this.params = opts.params || {} + this.params = closure(opts.params || {}) this.endPoint = `${endPoint}/${TRANSPORTS.websocket}` this.heartbeatTimer = null this.pendingHeartbeatRef = null @@ -755,7 +744,7 @@ export class Socket { */ endPointURL(){ let uri = Ajax.appendParams( - Ajax.appendParams(this.endPoint, this.params), {vsn: VSN}) + Ajax.appendParams(this.endPoint, this.params()), {vsn: VSN}) if(uri.charAt(0) !== "/"){ return uri } if(uri.charAt(1) === "/"){ return `${this.protocol()}:${uri}` } @@ -783,7 +772,7 @@ export class Socket { connect(params){ if(params){ console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor") - this.params = params + this.params = closure(params) } if(this.conn){ return } diff --git a/assets/package-lock.json b/assets/package-lock.json index c83529211d..6b88f2532d 100644 --- a/assets/package-lock.json +++ b/assets/package-lock.json @@ -1076,6 +1076,44 @@ "babel-types": "6.26.0" } }, + "babel-preset-env": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", + "integrity": "sha512-9OR2afuKDneX2/q2EurSftUYM0xGu4O2D9adAhVfADDhrYDaxXV0rBbevVYoY9n6nyX1PmQW/0jtpJvUNr9CHg==", + "dev": true, + "requires": { + "babel-plugin-check-es2015-constants": "6.22.0", + "babel-plugin-syntax-trailing-function-commas": "6.22.0", + "babel-plugin-transform-async-to-generator": "6.24.1", + "babel-plugin-transform-es2015-arrow-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "6.22.0", + "babel-plugin-transform-es2015-block-scoping": "6.26.0", + "babel-plugin-transform-es2015-classes": "6.24.1", + "babel-plugin-transform-es2015-computed-properties": "6.24.1", + "babel-plugin-transform-es2015-destructuring": "6.23.0", + "babel-plugin-transform-es2015-duplicate-keys": "6.24.1", + "babel-plugin-transform-es2015-for-of": "6.23.0", + "babel-plugin-transform-es2015-function-name": "6.24.1", + "babel-plugin-transform-es2015-literals": "6.22.0", + "babel-plugin-transform-es2015-modules-amd": "6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "6.26.0", + "babel-plugin-transform-es2015-modules-systemjs": "6.24.1", + "babel-plugin-transform-es2015-modules-umd": "6.24.1", + "babel-plugin-transform-es2015-object-super": "6.24.1", + "babel-plugin-transform-es2015-parameters": "6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "6.24.1", + "babel-plugin-transform-es2015-spread": "6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "6.24.1", + "babel-plugin-transform-es2015-template-literals": "6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "6.23.0", + "babel-plugin-transform-es2015-unicode-regex": "6.24.1", + "babel-plugin-transform-exponentiation-operator": "6.24.1", + "babel-plugin-transform-regenerator": "6.26.0", + "browserslist": "3.2.8", + "invariant": "2.2.3", + "semver": "5.5.0" + } + }, "babel-preset-es2015": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz", @@ -1486,6 +1524,16 @@ "pako": "1.0.6" } }, + "browserslist": { + "version": "3.2.8", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.8.tgz", + "integrity": "sha512-WHVocJYavUwVgVViC0ORikPHQquXwVh939TaelZ4WDqpWgTX/FsGhl/+P4qBUAGcRvtOgDgC+xftNWWp2RUTAQ==", + "dev": true, + "requires": { + "caniuse-lite": "1.0.30000846", + "electron-to-chromium": "1.3.48" + } + }, "buffer": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", @@ -1612,6 +1660,12 @@ "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", "dev": true }, + "caniuse-lite": { + "version": "1.0.30000846", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000846.tgz", + "integrity": "sha512-qxUOHr5mTaadWH1ap0ueivHd8x42Bnemcn+JutVr7GWmm2bU4zoBhjuv5QdXgALQnnT626lOQros7cCDf8PwCg==", + "dev": true + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -2684,6 +2738,12 @@ "integrity": "sha1-zIcsFoiArjxxiXYv1f/ACJbJUYo=", "dev": true }, + "electron-to-chromium": { + "version": "1.3.48", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz", + "integrity": "sha1-07DYWTgUBE4JLs4hCPw6ya6kuQA=", + "dev": true + }, "elegant-spinner": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", diff --git a/assets/test/channel_test.js b/assets/test/channel_test.js index ffcd35efca..c56b4a25a2 100644 --- a/assets/test/channel_test.js +++ b/assets/test/channel_test.js @@ -21,7 +21,7 @@ describe("constructor", () => { assert.equal(channel.state, "closed") assert.equal(channel.topic, "topic") - assert.deepEqual(channel.params, { one: "two" }) + assert.deepEqual(channel.params(), { one: "two" }) assert.deepEqual(channel.socket, socket) assert.equal(channel.timeout, 1234) assert.equal(channel.joinedOnce, false) @@ -29,39 +29,52 @@ describe("constructor", () => { assert.deepEqual(channel.pushBuffer, []) }) - it("sets up joinPush object", () => { + it("sets up joinPush objec with literal params", () => { channel = new Channel("topic", { one: "two" }, socket) const joinPush = channel.joinPush assert.deepEqual(joinPush.channel, channel) - assert.deepEqual(joinPush.payload, { one: "two" }) + assert.deepEqual(joinPush.payload(), { one: "two" }) assert.equal(joinPush.event, "phx_join") assert.equal(joinPush.timeout, 1234) }) + + it("sets up joinPush objec with closure params", () => { + channel = new Channel("topic", function(){ return({one: "two"}) }, socket) + const joinPush = channel.joinPush + + assert.deepEqual(joinPush.channel, channel) + assert.deepEqual(joinPush.payload(), { one: "two" }) + assert.equal(joinPush.event, "phx_join") + assert.equal(joinPush.timeout, 1234) + }) + }) -describe("updateJoinParams", () => { +describe("updating join params", () => { beforeEach(() => { socket = { timeout: 1234 } }) it("can update the join params", () => { - channel = new Channel("topic", { one: "two" }, socket) + let counter = 0 + let params = function(){ return({value: counter}) } + + channel = new Channel("topic", params, socket) const joinPush = channel.joinPush assert.deepEqual(joinPush.channel, channel) - assert.deepEqual(joinPush.payload, { one: "two" }) + assert.deepEqual(joinPush.payload(), { value: 0 }) assert.equal(joinPush.event, "phx_join") assert.equal(joinPush.timeout, 1234) - channel.updateJoinParams({ three: "four" }) + counter++ assert.deepEqual(joinPush.channel, channel) - assert.deepEqual(joinPush.payload, { three: "four" }) - assert.deepEqual(channel.params, { three: "four" }) + assert.deepEqual(joinPush.payload(), { value: 1 }) + assert.deepEqual(channel.params(), { value: 1 }) assert.equal(joinPush.event, "phx_join") assert.equal(joinPush.timeout, 1234) - }); }); @@ -759,7 +772,7 @@ describe("on", () => { assert.ok(!ignoredSpy.called) }) - + it("generates unique refs for callbacks", () => { const ref1 = channel.on("event1", () => 0) const ref2 = channel.on("event2", () => 0) @@ -793,11 +806,11 @@ describe("off", () => { assert.ok(!spy2.called) assert.ok(spy3.called) }) - + it("removes callback by its ref", () => { const spy1 = sinon.spy() const spy2 = sinon.spy() - + const ref1 = channel.on("event", spy1) const ref2 = channel.on("event", spy2) @@ -805,7 +818,7 @@ describe("off", () => { channel.trigger("event", {}, defaultRef) assert.ok(!spy1.called) - assert.ok(spy2.called) + assert.ok(spy2.called) }) }) diff --git a/assets/test/socket_test.js b/assets/test/socket_test.js index d0ce4773c2..352c934a83 100644 --- a/assets/test/socket_test.js +++ b/assets/test/socket_test.js @@ -33,6 +33,14 @@ describe("constructor", () => { assert.equal(typeof socket.reconnectAfterMs, "function") }) + it("supports closure or literal params", () => { + socket = new Socket("/socket", {params: {one: "two"}}) + assert.deepEqual(socket.params(), {one: "two"}) + + socket = new Socket("/socket", {params: function(){ return({three: "four"}) }}) + assert.deepEqual(socket.params(), {three: "four"}) + }) + it("overrides some defaults with options", () => { const customTransport = function transport() {} const customLogger = function logger() {} @@ -54,7 +62,7 @@ describe("constructor", () => { assert.equal(socket.transport, customTransport) assert.equal(socket.logger, customLogger) assert.equal(socket.reconnectAfterMs, customReconnect) - assert.deepEqual(socket.params, {one: "two"}) + assert.deepEqual(socket.params(), {one: "two"}) }) describe("with Websocket", () => { @@ -370,7 +378,7 @@ describe("channel", () => { assert.deepStrictEqual(channel.socket, socket) assert.equal(channel.topic, "topic") - assert.deepEqual(channel.params, {one: "two"}) + assert.deepEqual(channel.params(), {one: "two"}) }) it("adds channel to sockets channels list", () => { diff --git a/priv/static/phoenix.js b/priv/static/phoenix.js index bb646116be..dd40f3937b 100644 --- a/priv/static/phoenix.js +++ b/priv/static/phoenix.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Phoenix=t():e.Phoenix=t()}(window,function(){return function(e){var t={};function s(i){if(t[i])return t[i].exports;var n=t[i]={i:i,l:!1,exports:{}};return e[i].call(n.exports,n,n.exports,s),n.l=!0,n.exports}return s.m=e,s.c=t,s.d=function(e,t,i){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:i})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=2)}([function(e,t,s){"use strict";s.r(t),s.d(t,"Channel",function(){return f}),s.d(t,"Socket",function(){return m}),s.d(t,"LongPoll",function(){return g}),s.d(t,"Ajax",function(){return v}),s.d(t,"Presence",function(){return j});const i="undefined"!=typeof self?self:window,n="2.0.0",o={connecting:0,open:1,closing:2,closed:3},r=1e4,h=1e3,a={closed:"closed",errored:"errored",joined:"joined",joining:"joining",leaving:"leaving"},c={close:"phx_close",error:"phx_error",join:"phx_join",reply:"phx_reply",leave:"phx_leave"},l=[c.close,c.error,c.join,c.reply,c.leave],u={longpoll:"longpoll",websocket:"websocket"};class p{constructor(e,t,s,i){this.channel=e,this.event=t,this.payload=s||{},this.receivedResp=null,this.timeout=i,this.timeoutTimer=null,this.recHooks=[],this.sent=!1}resend(e){this.timeout=e,this.reset(),this.send()}send(){this.hasReceived("timeout")||(this.startTimeout(),this.sent=!0,this.channel.socket.push({topic:this.channel.topic,event:this.event,payload:this.payload,ref:this.ref,join_ref:this.channel.joinRef()}))}receive(e,t){return this.hasReceived(e)&&t(this.receivedResp.response),this.recHooks.push({status:e,callback:t}),this}updatePayload(e){this.payload=e}reset(){this.cancelRefEvent(),this.ref=null,this.refEvent=null,this.receivedResp=null,this.sent=!1}matchReceive({status:e,response:t,ref:s}){this.recHooks.filter(t=>t.status===e).forEach(e=>e.callback(t))}cancelRefEvent(){this.refEvent&&this.channel.off(this.refEvent)}cancelTimeout(){clearTimeout(this.timeoutTimer),this.timeoutTimer=null}startTimeout(){this.timeoutTimer&&this.cancelTimeout(),this.ref=this.channel.socket.makeRef(),this.refEvent=this.channel.replyEventName(this.ref),this.channel.on(this.refEvent,e=>{this.cancelRefEvent(),this.cancelTimeout(),this.receivedResp=e,this.matchReceive(e)}),this.timeoutTimer=setTimeout(()=>{this.trigger("timeout",{})},this.timeout)}hasReceived(e){return this.receivedResp&&this.receivedResp.status===e}trigger(e,t){this.channel.trigger(this.refEvent,{status:e,response:t})}}class f{constructor(e,t,s){this.state=a.closed,this.topic=e,this.params=t||{},this.socket=s,this.bindings=[],this.bindingRef=0,this.timeout=this.socket.timeout,this.joinedOnce=!1,this.joinPush=new p(this,c.join,this.params,this.timeout),this.pushBuffer=[],this.rejoinTimer=new b(()=>this.rejoinUntilConnected(),this.socket.reconnectAfterMs),this.joinPush.receive("ok",()=>{this.state=a.joined,this.rejoinTimer.reset(),this.pushBuffer.forEach(e=>e.send()),this.pushBuffer=[]}),this.onClose(()=>{this.rejoinTimer.reset(),this.socket.log("channel",`close ${this.topic} ${this.joinRef()}`),this.state=a.closed,this.socket.remove(this)}),this.onError(e=>{this.isLeaving()||this.isClosed()||(this.socket.log("channel",`error ${this.topic}`,e),this.state=a.errored,this.rejoinTimer.scheduleTimeout())}),this.joinPush.receive("timeout",()=>{if(!this.isJoining())return;this.socket.log("channel",`timeout ${this.topic} (${this.joinRef()})`,this.joinPush.timeout),new p(this,c.leave,{},this.timeout).send(),this.state=a.errored,this.joinPush.reset(),this.rejoinTimer.scheduleTimeout()}),this.on(c.reply,(e,t)=>{this.trigger(this.replyEventName(t),e)})}rejoinUntilConnected(){this.rejoinTimer.scheduleTimeout(),this.socket.isConnected()&&this.rejoin()}join(e=this.timeout){if(this.joinedOnce)throw"tried to join multiple times. 'join' can only be called a single time per channel instance";return this.joinedOnce=!0,this.rejoin(e),this.joinPush}updateJoinParams(e={}){this.params=e,this.joinPush.updatePayload(e)}onClose(e){this.on(c.close,e)}onError(e){return this.on(c.error,t=>e(t))}on(e,t){let s=this.bindingRef++;return this.bindings.push({event:e,ref:s,callback:t}),s}off(e,t){this.bindings=this.bindings.filter(s=>!(s.event===e&&(void 0===t||t===s.ref)))}canPush(){return this.socket.isConnected()&&this.isJoined()}push(e,t,s=this.timeout){if(!this.joinedOnce)throw`tried to push '${e}' to '${this.topic}' before joining. Use channel.join() before pushing events`;let i=new p(this,e,t,s);return this.canPush()?i.send():(i.startTimeout(),this.pushBuffer.push(i)),i}leave(e=this.timeout){this.state=a.leaving;let t=()=>{this.socket.log("channel",`leave ${this.topic}`),this.trigger(c.close,"leave")},s=new p(this,c.leave,{},e);return s.receive("ok",()=>t()).receive("timeout",()=>t()),s.send(),this.canPush()||s.trigger("ok",{}),s}onMessage(e,t,s){return t}isMember(e,t,s,i){if(this.topic!==e)return!1;let n=l.indexOf(t)>=0;return!i||!n||i===this.joinRef()||(this.socket.log("channel","dropping outdated message",{topic:e,event:t,payload:s,joinRef:i}),!1)}joinRef(){return this.joinPush.ref}sendJoin(e){this.state=a.joining,this.joinPush.resend(e)}rejoin(e=this.timeout){this.isLeaving()||this.sendJoin(e)}trigger(e,t,s,i){let n=this.onMessage(e,t,s,i);if(t&&!n)throw"channel onMessage callbacks must return the payload, modified or unmodified";this.bindings.filter(t=>t.event===e).map(e=>e.callback(n,s,i||this.joinRef()))}replyEventName(e){return`chan_reply_${e}`}isClosed(){return this.state===a.closed}isErrored(){return this.state===a.errored}isJoined(){return this.state===a.joined}isJoining(){return this.state===a.joining}isLeaving(){return this.state===a.leaving}}const d={encode(e,t){let s=[e.join_ref,e.ref,e.topic,e.event,e.payload];return t(JSON.stringify(s))},decode(e,t){let[s,i,n,o,r]=JSON.parse(e);return t({join_ref:s,ref:i,topic:n,event:o,payload:r})}};class m{constructor(e,t={}){this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.channels=[],this.sendBuffer=[],this.ref=0,this.timeout=t.timeout||r,this.transport=t.transport||i.WebSocket||g,this.defaultEncoder=d.encode,this.defaultDecoder=d.decode,this.transport!==g?(this.encode=t.encode||this.defaultEncoder,this.decode=t.decode||this.defaultDecoder):(this.encode=this.defaultEncoder,this.decode=this.defaultDecoder),this.heartbeatIntervalMs=t.heartbeatIntervalMs||3e4,this.reconnectAfterMs=t.reconnectAfterMs||function(e){return[1e3,2e3,5e3,1e4][e-1]||1e4},this.logger=t.logger||function(){},this.longpollerTimeout=t.longpollerTimeout||2e4,this.params=t.params||{},this.endPoint=`${e}/${u.websocket}`,this.heartbeatTimer=null,this.pendingHeartbeatRef=null,this.reconnectTimer=new b(()=>{this.disconnect(()=>this.connect())},this.reconnectAfterMs)}protocol(){return location.protocol.match(/^https/)?"wss":"ws"}endPointURL(){let e=v.appendParams(v.appendParams(this.endPoint,this.params),{vsn:n});return"/"!==e.charAt(0)?e:"/"===e.charAt(1)?`${this.protocol()}:${e}`:`${this.protocol()}://${location.host}${e}`}disconnect(e,t,s){this.conn&&(this.conn.onclose=function(){},t?this.conn.close(t,s||""):this.conn.close(),this.conn=null),e&&e()}connect(e){e&&(console&&console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor"),this.params=e),this.conn||(this.conn=new this.transport(this.endPointURL()),this.conn.timeout=this.longpollerTimeout,this.conn.onopen=(()=>this.onConnOpen()),this.conn.onerror=(e=>this.onConnError(e)),this.conn.onmessage=(e=>this.onConnMessage(e)),this.conn.onclose=(e=>this.onConnClose(e)))}log(e,t,s){this.logger(e,t,s)}onOpen(e){this.stateChangeCallbacks.open.push(e)}onClose(e){this.stateChangeCallbacks.close.push(e)}onError(e){this.stateChangeCallbacks.error.push(e)}onMessage(e){this.stateChangeCallbacks.message.push(e)}onConnOpen(){this.log("transport",`connected to ${this.endPointURL()}`),this.flushSendBuffer(),this.reconnectTimer.reset(),this.conn.skipHeartbeat||(clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),this.heartbeatIntervalMs)),this.stateChangeCallbacks.open.forEach(e=>e())}onConnClose(e){this.log("transport","close",e),this.triggerChanError(),clearInterval(this.heartbeatTimer),this.reconnectTimer.scheduleTimeout(),this.stateChangeCallbacks.close.forEach(t=>t(e))}onConnError(e){this.log("transport",e),this.triggerChanError(),this.stateChangeCallbacks.error.forEach(t=>t(e))}triggerChanError(){this.channels.forEach(e=>e.trigger(c.error))}connectionState(){switch(this.conn&&this.conn.readyState){case o.connecting:return"connecting";case o.open:return"open";case o.closing:return"closing";default:return"closed"}}isConnected(){return"open"===this.connectionState()}remove(e){this.channels=this.channels.filter(t=>t.joinRef()!==e.joinRef())}channel(e,t={}){let s=new f(e,t,this);return this.channels.push(s),s}push(e){let{topic:t,event:s,payload:i,ref:n,join_ref:o}=e,r=()=>{this.encode(e,e=>{this.conn.send(e)})};this.log("push",`${t} ${s} (${o}, ${n})`,i),this.isConnected()?r():this.sendBuffer.push(r)}makeRef(){let e=this.ref+1;return e===this.ref?this.ref=0:this.ref=e,this.ref.toString()}sendHeartbeat(){if(this.isConnected()){if(this.pendingHeartbeatRef)return this.pendingHeartbeatRef=null,this.log("transport","heartbeat timeout. Attempting to re-establish connection"),void this.conn.close(h,"hearbeat timeout");this.pendingHeartbeatRef=this.makeRef(),this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:this.pendingHeartbeatRef})}}flushSendBuffer(){this.isConnected()&&this.sendBuffer.length>0&&(this.sendBuffer.forEach(e=>e()),this.sendBuffer=[])}onConnMessage(e){this.decode(e.data,e=>{let{topic:t,event:s,payload:i,ref:n,join_ref:o}=e;n&&n===this.pendingHeartbeatRef&&(this.pendingHeartbeatRef=null),this.log("receive",`${i.status||""} ${t} ${s} ${n&&"("+n+")"||""}`,i),this.channels.filter(e=>e.isMember(t,s,i,o)).forEach(e=>e.trigger(s,i,n,o)),this.stateChangeCallbacks.message.forEach(t=>t(e))})}}class g{constructor(e){this.endPoint=null,this.token=null,this.skipHeartbeat=!0,this.onopen=function(){},this.onerror=function(){},this.onmessage=function(){},this.onclose=function(){},this.pollEndpoint=this.normalizeEndpoint(e),this.readyState=o.connecting,this.poll()}normalizeEndpoint(e){return e.replace("ws://","http://").replace("wss://","https://").replace(new RegExp("(.*)/"+u.websocket),"$1/"+u.longpoll)}endpointURL(){return v.appendParams(this.pollEndpoint,{token:this.token})}closeAndRetry(){this.close(),this.readyState=o.connecting}ontimeout(){this.onerror("timeout"),this.closeAndRetry()}poll(){this.readyState!==o.open&&this.readyState!==o.connecting||v.request("GET",this.endpointURL(),"application/json",null,this.timeout,this.ontimeout.bind(this),e=>{if(e){var{status:t,token:s,messages:i}=e;this.token=s}else var t=0;switch(t){case 200:i.forEach(e=>this.onmessage({data:e})),this.poll();break;case 204:this.poll();break;case 410:this.readyState=o.open,this.onopen(),this.poll();break;case 0:case 500:this.onerror(),this.closeAndRetry();break;default:throw`unhandled poll status ${t}`}})}send(e){v.request("POST",this.endpointURL(),"application/json",e,this.timeout,this.onerror.bind(this,"timeout"),e=>{e&&200===e.status||(this.onerror(e&&e.status),this.closeAndRetry())})}close(e,t){this.readyState=o.closed,this.onclose()}}class v{static request(e,t,s,n,o,r,h){if(i.XDomainRequest){let s=new XDomainRequest;this.xdomainRequest(s,e,t,n,o,r,h)}else{let a=i.XMLHttpRequest?new i.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");this.xhrRequest(a,e,t,s,n,o,r,h)}}static xdomainRequest(e,t,s,i,n,o,r){e.timeout=n,e.open(t,s),e.onload=(()=>{let t=this.parseJSON(e.responseText);r&&r(t)}),o&&(e.ontimeout=o),e.onprogress=(()=>{}),e.send(i)}static xhrRequest(e,t,s,i,n,o,r,h){e.open(t,s,!0),e.timeout=o,e.setRequestHeader("Content-Type",i),e.onerror=(()=>{h&&h(null)}),e.onreadystatechange=(()=>{if(e.readyState===this.states.complete&&h){let t=this.parseJSON(e.responseText);h(t)}}),r&&(e.ontimeout=r),e.send(n)}static parseJSON(e){if(!e||""===e)return null;try{return JSON.parse(e)}catch(t){return console&&console.log("failed to parse JSON response",e),null}}static serialize(e,t){let s=[];for(var i in e){if(!e.hasOwnProperty(i))continue;let n=t?`${t}[${i}]`:i,o=e[i];"object"==typeof o?s.push(this.serialize(o,n)):s.push(encodeURIComponent(n)+"="+encodeURIComponent(o))}return s.join("&")}static appendParams(e,t){if(0===Object.keys(t).length)return e;return`${e}${e.match(/\?/)?"&":"?"}${this.serialize(t)}`}}v.states={complete:4};class j{constructor(e,t={}){let s=t.events||{state:"presence_state",diff:"presence_diff"};this.state={},this.pendingDiffs=[],this.channel=e,this.joinRef=null,this.caller={onJoin:function(){},onLeave:function(){},onSync:function(){}},this.channel.on(s.state,e=>{let{onJoin:t,onLeave:s,onSync:i}=this.caller;this.joinRef=this.channel.joinRef(),this.state=j.syncState(this.state,e,t,s),this.pendingDiffs.forEach(e=>{this.state=j.syncDiff(this.state,e,t,s)}),this.pendingDiffs=[],i()}),this.channel.on(s.diff,e=>{let{onJoin:t,onLeave:s,onSync:i}=this.caller;this.inPendingSyncState()?this.pendingDiffs.push(e):(this.state=j.syncDiff(this.state,e,t,s),i())})}onJoin(e){this.caller.onJoin=e}onLeave(e){this.caller.onLeave=e}onSync(e){this.caller.onSync=e}list(e){return j.list(this.state,e)}inPendingSyncState(){return!this.joinRef||this.joinRef!==this.channel.joinRef()}static syncState(e,t,s,i){let n=this.clone(e),o={},r={};return this.map(n,(e,s)=>{t[e]||(r[e]=s)}),this.map(t,(e,t)=>{let s=n[e];if(s){let i=t.metas.map(e=>e.phx_ref),n=s.metas.map(e=>e.phx_ref),h=t.metas.filter(e=>n.indexOf(e.phx_ref)<0),a=s.metas.filter(e=>i.indexOf(e.phx_ref)<0);h.length>0&&(o[e]=t,o[e].metas=h),a.length>0&&(r[e]=this.clone(s),r[e].metas=a)}else o[e]=t}),this.syncDiff(n,{joins:o,leaves:r},s,i)}static syncDiff(e,{joins:t,leaves:s},i,n){let o=this.clone(e);return i||(i=function(){}),n||(n=function(){}),this.map(t,(e,t)=>{let s=o[e];if(o[e]=t,s){let t=o[e].metas.map(e=>e.phx_ref),i=s.metas.filter(e=>t.indexOf(e.phx_ref)<0);o[e].metas.unshift(...i)}i(e,s,t)}),this.map(s,(e,t)=>{let s=o[e];if(!s)return;let i=t.metas.map(e=>e.phx_ref);s.metas=s.metas.filter(e=>i.indexOf(e.phx_ref)<0),n(e,s,t),0===s.metas.length&&delete o[e]}),o}static list(e,t){return t||(t=function(e,t){return t}),this.map(e,(e,s)=>t(e,s))}static map(e,t){return Object.getOwnPropertyNames(e).map(s=>t(s,e[s]))}static clone(e){return JSON.parse(JSON.stringify(e))}}class b{constructor(e,t){this.callback=e,this.timerCalc=t,this.timer=null,this.tries=0}reset(){this.tries=0,clearTimeout(this.timer)}scheduleTimeout(){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.tries=this.tries+1,this.callback()},this.timerCalc(this.tries+1))}}},function(e,t){var s;s=function(){return this}();try{s=s||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(s=window)}e.exports=s},function(e,t,s){"use strict";(function(t){e.exports=t.Phoenix=s(0)}).call(this,s(1))}])}); \ No newline at end of file +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Phoenix=t():e.Phoenix=t()}(window,function(){return function(e){var t={};function s(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,s),i.l=!0,i.exports}return s.m=e,s.c=t,s.d=function(e,t,n){s.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},s.r=function(e){Object.defineProperty(e,"__esModule",{value:!0})},s.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return s.d(t,"a",t),t},s.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},s.p="",s(s.s=2)}([function(e,t,s){"use strict";s.r(t),s.d(t,"Channel",function(){return d}),s.d(t,"Socket",function(){return g}),s.d(t,"LongPoll",function(){return v}),s.d(t,"Ajax",function(){return j}),s.d(t,"Presence",function(){return b});const n="undefined"!=typeof self?self:window,i="2.0.0",o={connecting:0,open:1,closing:2,closed:3},r=1e4,h=1e3,a={closed:"closed",errored:"errored",joined:"joined",joining:"joining",leaving:"leaving"},c={close:"phx_close",error:"phx_error",join:"phx_join",reply:"phx_reply",leave:"phx_leave"},l=[c.close,c.error,c.join,c.reply,c.leave],u={longpoll:"longpoll",websocket:"websocket"};let f=e=>{if("function"==typeof e)return e;return function(){return e}};class p{constructor(e,t,s,n){this.channel=e,this.event=t,this.payload=s||function(){return{}},this.receivedResp=null,this.timeout=n,this.timeoutTimer=null,this.recHooks=[],this.sent=!1}resend(e){this.timeout=e,this.reset(),this.send()}send(){this.hasReceived("timeout")||(this.startTimeout(),this.sent=!0,this.channel.socket.push({topic:this.channel.topic,event:this.event,payload:this.payload(),ref:this.ref,join_ref:this.channel.joinRef()}))}receive(e,t){return this.hasReceived(e)&&t(this.receivedResp.response),this.recHooks.push({status:e,callback:t}),this}reset(){this.cancelRefEvent(),this.ref=null,this.refEvent=null,this.receivedResp=null,this.sent=!1}matchReceive({status:e,response:t,ref:s}){this.recHooks.filter(t=>t.status===e).forEach(e=>e.callback(t))}cancelRefEvent(){this.refEvent&&this.channel.off(this.refEvent)}cancelTimeout(){clearTimeout(this.timeoutTimer),this.timeoutTimer=null}startTimeout(){this.timeoutTimer&&this.cancelTimeout(),this.ref=this.channel.socket.makeRef(),this.refEvent=this.channel.replyEventName(this.ref),this.channel.on(this.refEvent,e=>{this.cancelRefEvent(),this.cancelTimeout(),this.receivedResp=e,this.matchReceive(e)}),this.timeoutTimer=setTimeout(()=>{this.trigger("timeout",{})},this.timeout)}hasReceived(e){return this.receivedResp&&this.receivedResp.status===e}trigger(e,t){this.channel.trigger(this.refEvent,{status:e,response:t})}}class d{constructor(e,t,s){this.state=a.closed,this.topic=e,this.params=f(t||{}),this.socket=s,this.bindings=[],this.bindingRef=0,this.timeout=this.socket.timeout,this.joinedOnce=!1,this.joinPush=new p(this,c.join,this.params,this.timeout),this.pushBuffer=[],this.rejoinTimer=new y(()=>this.rejoinUntilConnected(),this.socket.reconnectAfterMs),this.joinPush.receive("ok",()=>{this.state=a.joined,this.rejoinTimer.reset(),this.pushBuffer.forEach(e=>e.send()),this.pushBuffer=[]}),this.onClose(()=>{this.rejoinTimer.reset(),this.socket.log("channel",`close ${this.topic} ${this.joinRef()}`),this.state=a.closed,this.socket.remove(this)}),this.onError(e=>{this.isLeaving()||this.isClosed()||(this.socket.log("channel",`error ${this.topic}`,e),this.state=a.errored,this.rejoinTimer.scheduleTimeout())}),this.joinPush.receive("timeout",()=>{if(!this.isJoining())return;this.socket.log("channel",`timeout ${this.topic} (${this.joinRef()})`,this.joinPush.timeout),new p(this,c.leave,f({}),this.timeout).send(),this.state=a.errored,this.joinPush.reset(),this.rejoinTimer.scheduleTimeout()}),this.on(c.reply,(e,t)=>{this.trigger(this.replyEventName(t),e)})}rejoinUntilConnected(){this.rejoinTimer.scheduleTimeout(),this.socket.isConnected()&&this.rejoin()}join(e=this.timeout){if(this.joinedOnce)throw"tried to join multiple times. 'join' can only be called a single time per channel instance";return this.joinedOnce=!0,this.rejoin(e),this.joinPush}onClose(e){this.on(c.close,e)}onError(e){return this.on(c.error,t=>e(t))}on(e,t){let s=this.bindingRef++;return this.bindings.push({event:e,ref:s,callback:t}),s}off(e,t){this.bindings=this.bindings.filter(s=>!(s.event===e&&(void 0===t||t===s.ref)))}canPush(){return this.socket.isConnected()&&this.isJoined()}push(e,t,s=this.timeout){if(!this.joinedOnce)throw`tried to push '${e}' to '${this.topic}' before joining. Use channel.join() before pushing events`;let n=new p(this,e,function(){return t},s);return this.canPush()?n.send():(n.startTimeout(),this.pushBuffer.push(n)),n}leave(e=this.timeout){this.state=a.leaving;let t=()=>{this.socket.log("channel",`leave ${this.topic}`),this.trigger(c.close,"leave")},s=new p(this,c.leave,f({}),e);return s.receive("ok",()=>t()).receive("timeout",()=>t()),s.send(),this.canPush()||s.trigger("ok",{}),s}onMessage(e,t,s){return t}isMember(e,t,s,n){if(this.topic!==e)return!1;let i=l.indexOf(t)>=0;return!n||!i||n===this.joinRef()||(this.socket.log("channel","dropping outdated message",{topic:e,event:t,payload:s,joinRef:n}),!1)}joinRef(){return this.joinPush.ref}sendJoin(e){this.state=a.joining,this.joinPush.resend(e)}rejoin(e=this.timeout){this.isLeaving()||this.sendJoin(e)}trigger(e,t,s,n){let i=this.onMessage(e,t,s,n);if(t&&!i)throw"channel onMessage callbacks must return the payload, modified or unmodified";this.bindings.filter(t=>t.event===e).map(e=>e.callback(i,s,n||this.joinRef()))}replyEventName(e){return`chan_reply_${e}`}isClosed(){return this.state===a.closed}isErrored(){return this.state===a.errored}isJoined(){return this.state===a.joined}isJoining(){return this.state===a.joining}isLeaving(){return this.state===a.leaving}}const m={encode(e,t){let s=[e.join_ref,e.ref,e.topic,e.event,e.payload];return t(JSON.stringify(s))},decode(e,t){let[s,n,i,o,r]=JSON.parse(e);return t({join_ref:s,ref:n,topic:i,event:o,payload:r})}};class g{constructor(e,t={}){this.stateChangeCallbacks={open:[],close:[],error:[],message:[]},this.channels=[],this.sendBuffer=[],this.ref=0,this.timeout=t.timeout||r,this.transport=t.transport||n.WebSocket||v,this.defaultEncoder=m.encode,this.defaultDecoder=m.decode,this.transport!==v?(this.encode=t.encode||this.defaultEncoder,this.decode=t.decode||this.defaultDecoder):(this.encode=this.defaultEncoder,this.decode=this.defaultDecoder),this.heartbeatIntervalMs=t.heartbeatIntervalMs||3e4,this.reconnectAfterMs=t.reconnectAfterMs||function(e){return[1e3,2e3,5e3,1e4][e-1]||1e4},this.logger=t.logger||function(){},this.longpollerTimeout=t.longpollerTimeout||2e4,this.params=f(t.params||{}),this.endPoint=`${e}/${u.websocket}`,this.heartbeatTimer=null,this.pendingHeartbeatRef=null,this.reconnectTimer=new y(()=>{this.disconnect(()=>this.connect())},this.reconnectAfterMs)}protocol(){return location.protocol.match(/^https/)?"wss":"ws"}endPointURL(){let e=j.appendParams(j.appendParams(this.endPoint,this.params()),{vsn:i});return"/"!==e.charAt(0)?e:"/"===e.charAt(1)?`${this.protocol()}:${e}`:`${this.protocol()}://${location.host}${e}`}disconnect(e,t,s){this.conn&&(this.conn.onclose=function(){},t?this.conn.close(t,s||""):this.conn.close(),this.conn=null),e&&e()}connect(e){e&&(console&&console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor"),this.params=f(e)),this.conn||(this.conn=new this.transport(this.endPointURL()),this.conn.timeout=this.longpollerTimeout,this.conn.onopen=(()=>this.onConnOpen()),this.conn.onerror=(e=>this.onConnError(e)),this.conn.onmessage=(e=>this.onConnMessage(e)),this.conn.onclose=(e=>this.onConnClose(e)))}log(e,t,s){this.logger(e,t,s)}onOpen(e){this.stateChangeCallbacks.open.push(e)}onClose(e){this.stateChangeCallbacks.close.push(e)}onError(e){this.stateChangeCallbacks.error.push(e)}onMessage(e){this.stateChangeCallbacks.message.push(e)}onConnOpen(){this.log("transport",`connected to ${this.endPointURL()}`),this.flushSendBuffer(),this.reconnectTimer.reset(),this.conn.skipHeartbeat||(clearInterval(this.heartbeatTimer),this.heartbeatTimer=setInterval(()=>this.sendHeartbeat(),this.heartbeatIntervalMs)),this.stateChangeCallbacks.open.forEach(e=>e())}onConnClose(e){this.log("transport","close",e),this.triggerChanError(),clearInterval(this.heartbeatTimer),this.reconnectTimer.scheduleTimeout(),this.stateChangeCallbacks.close.forEach(t=>t(e))}onConnError(e){this.log("transport",e),this.triggerChanError(),this.stateChangeCallbacks.error.forEach(t=>t(e))}triggerChanError(){this.channels.forEach(e=>e.trigger(c.error))}connectionState(){switch(this.conn&&this.conn.readyState){case o.connecting:return"connecting";case o.open:return"open";case o.closing:return"closing";default:return"closed"}}isConnected(){return"open"===this.connectionState()}remove(e){this.channels=this.channels.filter(t=>t.joinRef()!==e.joinRef())}channel(e,t={}){let s=new d(e,t,this);return this.channels.push(s),s}push(e){let{topic:t,event:s,payload:n,ref:i,join_ref:o}=e,r=()=>{this.encode(e,e=>{this.conn.send(e)})};this.log("push",`${t} ${s} (${o}, ${i})`,n),this.isConnected()?r():this.sendBuffer.push(r)}makeRef(){let e=this.ref+1;return e===this.ref?this.ref=0:this.ref=e,this.ref.toString()}sendHeartbeat(){if(this.isConnected()){if(this.pendingHeartbeatRef)return this.pendingHeartbeatRef=null,this.log("transport","heartbeat timeout. Attempting to re-establish connection"),void this.conn.close(h,"hearbeat timeout");this.pendingHeartbeatRef=this.makeRef(),this.push({topic:"phoenix",event:"heartbeat",payload:{},ref:this.pendingHeartbeatRef})}}flushSendBuffer(){this.isConnected()&&this.sendBuffer.length>0&&(this.sendBuffer.forEach(e=>e()),this.sendBuffer=[])}onConnMessage(e){this.decode(e.data,e=>{let{topic:t,event:s,payload:n,ref:i,join_ref:o}=e;i&&i===this.pendingHeartbeatRef&&(this.pendingHeartbeatRef=null),this.log("receive",`${n.status||""} ${t} ${s} ${i&&"("+i+")"||""}`,n),this.channels.filter(e=>e.isMember(t,s,n,o)).forEach(e=>e.trigger(s,n,i,o)),this.stateChangeCallbacks.message.forEach(t=>t(e))})}}class v{constructor(e){this.endPoint=null,this.token=null,this.skipHeartbeat=!0,this.onopen=function(){},this.onerror=function(){},this.onmessage=function(){},this.onclose=function(){},this.pollEndpoint=this.normalizeEndpoint(e),this.readyState=o.connecting,this.poll()}normalizeEndpoint(e){return e.replace("ws://","http://").replace("wss://","https://").replace(new RegExp("(.*)/"+u.websocket),"$1/"+u.longpoll)}endpointURL(){return j.appendParams(this.pollEndpoint,{token:this.token})}closeAndRetry(){this.close(),this.readyState=o.connecting}ontimeout(){this.onerror("timeout"),this.closeAndRetry()}poll(){this.readyState!==o.open&&this.readyState!==o.connecting||j.request("GET",this.endpointURL(),"application/json",null,this.timeout,this.ontimeout.bind(this),e=>{if(e){var{status:t,token:s,messages:n}=e;this.token=s}else var t=0;switch(t){case 200:n.forEach(e=>this.onmessage({data:e})),this.poll();break;case 204:this.poll();break;case 410:this.readyState=o.open,this.onopen(),this.poll();break;case 0:case 500:this.onerror(),this.closeAndRetry();break;default:throw`unhandled poll status ${t}`}})}send(e){j.request("POST",this.endpointURL(),"application/json",e,this.timeout,this.onerror.bind(this,"timeout"),e=>{e&&200===e.status||(this.onerror(e&&e.status),this.closeAndRetry())})}close(e,t){this.readyState=o.closed,this.onclose()}}class j{static request(e,t,s,i,o,r,h){if(n.XDomainRequest){let s=new XDomainRequest;this.xdomainRequest(s,e,t,i,o,r,h)}else{let a=n.XMLHttpRequest?new n.XMLHttpRequest:new ActiveXObject("Microsoft.XMLHTTP");this.xhrRequest(a,e,t,s,i,o,r,h)}}static xdomainRequest(e,t,s,n,i,o,r){e.timeout=i,e.open(t,s),e.onload=(()=>{let t=this.parseJSON(e.responseText);r&&r(t)}),o&&(e.ontimeout=o),e.onprogress=(()=>{}),e.send(n)}static xhrRequest(e,t,s,n,i,o,r,h){e.open(t,s,!0),e.timeout=o,e.setRequestHeader("Content-Type",n),e.onerror=(()=>{h&&h(null)}),e.onreadystatechange=(()=>{if(e.readyState===this.states.complete&&h){let t=this.parseJSON(e.responseText);h(t)}}),r&&(e.ontimeout=r),e.send(i)}static parseJSON(e){if(!e||""===e)return null;try{return JSON.parse(e)}catch(t){return console&&console.log("failed to parse JSON response",e),null}}static serialize(e,t){let s=[];for(var n in e){if(!e.hasOwnProperty(n))continue;let i=t?`${t}[${n}]`:n,o=e[n];"object"==typeof o?s.push(this.serialize(o,i)):s.push(encodeURIComponent(i)+"="+encodeURIComponent(o))}return s.join("&")}static appendParams(e,t){if(0===Object.keys(t).length)return e;return`${e}${e.match(/\?/)?"&":"?"}${this.serialize(t)}`}}j.states={complete:4};class b{constructor(e,t={}){let s=t.events||{state:"presence_state",diff:"presence_diff"};this.state={},this.pendingDiffs=[],this.channel=e,this.joinRef=null,this.caller={onJoin:function(){},onLeave:function(){},onSync:function(){}},this.channel.on(s.state,e=>{let{onJoin:t,onLeave:s,onSync:n}=this.caller;this.joinRef=this.channel.joinRef(),this.state=b.syncState(this.state,e,t,s),this.pendingDiffs.forEach(e=>{this.state=b.syncDiff(this.state,e,t,s)}),this.pendingDiffs=[],n()}),this.channel.on(s.diff,e=>{let{onJoin:t,onLeave:s,onSync:n}=this.caller;this.inPendingSyncState()?this.pendingDiffs.push(e):(this.state=b.syncDiff(this.state,e,t,s),n())})}onJoin(e){this.caller.onJoin=e}onLeave(e){this.caller.onLeave=e}onSync(e){this.caller.onSync=e}list(e){return b.list(this.state,e)}inPendingSyncState(){return!this.joinRef||this.joinRef!==this.channel.joinRef()}static syncState(e,t,s,n){let i=this.clone(e),o={},r={};return this.map(i,(e,s)=>{t[e]||(r[e]=s)}),this.map(t,(e,t)=>{let s=i[e];if(s){let n=t.metas.map(e=>e.phx_ref),i=s.metas.map(e=>e.phx_ref),h=t.metas.filter(e=>i.indexOf(e.phx_ref)<0),a=s.metas.filter(e=>n.indexOf(e.phx_ref)<0);h.length>0&&(o[e]=t,o[e].metas=h),a.length>0&&(r[e]=this.clone(s),r[e].metas=a)}else o[e]=t}),this.syncDiff(i,{joins:o,leaves:r},s,n)}static syncDiff(e,{joins:t,leaves:s},n,i){let o=this.clone(e);return n||(n=function(){}),i||(i=function(){}),this.map(t,(e,t)=>{let s=o[e];if(o[e]=t,s){let t=o[e].metas.map(e=>e.phx_ref),n=s.metas.filter(e=>t.indexOf(e.phx_ref)<0);o[e].metas.unshift(...n)}n(e,s,t)}),this.map(s,(e,t)=>{let s=o[e];if(!s)return;let n=t.metas.map(e=>e.phx_ref);s.metas=s.metas.filter(e=>n.indexOf(e.phx_ref)<0),i(e,s,t),0===s.metas.length&&delete o[e]}),o}static list(e,t){return t||(t=function(e,t){return t}),this.map(e,(e,s)=>t(e,s))}static map(e,t){return Object.getOwnPropertyNames(e).map(s=>t(s,e[s]))}static clone(e){return JSON.parse(JSON.stringify(e))}}class y{constructor(e,t){this.callback=e,this.timerCalc=t,this.timer=null,this.tries=0}reset(){this.tries=0,clearTimeout(this.timer)}scheduleTimeout(){clearTimeout(this.timer),this.timer=setTimeout(()=>{this.tries=this.tries+1,this.callback()},this.timerCalc(this.tries+1))}}},function(e,t){var s;s=function(){return this}();try{s=s||Function("return this")()||(0,eval)("this")}catch(e){"object"==typeof window&&(s=window)}e.exports=s},function(e,t,s){"use strict";(function(t){e.exports=t.Phoenix=s(0)}).call(this,s(1))}])}); \ No newline at end of file