// js/phoenix/utils.js var closure = (value) => { if (typeof value === "function") { return value; } else { let closure2 = function() { return value; }; return closure2; } }; // js/phoenix/constants.js var globalSelf = typeof self !== "undefined" ? self : null; var phxWindow = typeof window !== "undefined" ? window : null; var global = globalSelf || phxWindow || global; var DEFAULT_VSN = "2.0.0"; var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; var DEFAULT_TIMEOUT = 1e4; var WS_CLOSE_NORMAL = 1e3; var CHANNEL_STATES = { closed: "closed", errored: "errored", joined: "joined", joining: "joining", leaving: "leaving" }; var CHANNEL_EVENTS = { close: "phx_close", error: "phx_error", join: "phx_join", reply: "phx_reply", leave: "phx_leave" }; var TRANSPORTS = { longpoll: "longpoll", websocket: "websocket" }; var XHR_STATES = { complete: 4 }; // js/phoenix/push.js var Push = class { constructor(channel, event, payload, timeout) { this.channel = channel; this.event = event; this.payload = payload || function() { return {}; }; this.receivedResp = null; this.timeout = timeout; this.timeoutTimer = null; this.recHooks = []; this.sent = false; } resend(timeout) { this.timeout = timeout; this.reset(); this.send(); } send() { if (this.hasReceived("timeout")) { return; } this.startTimeout(); this.sent = true; this.channel.socket.push({ topic: this.channel.topic, event: this.event, payload: this.payload(), ref: this.ref, join_ref: this.channel.joinRef() }); } receive(status, callback) { if (this.hasReceived(status)) { callback(this.receivedResp.response); } this.recHooks.push({ status, callback }); return this; } reset() { this.cancelRefEvent(); this.ref = null; this.refEvent = null; this.receivedResp = null; this.sent = false; } matchReceive({ status, response, _ref }) { this.recHooks.filter((h) => h.status === status).forEach((h) => h.callback(response)); } cancelRefEvent() { if (!this.refEvent) { return; } this.channel.off(this.refEvent); } cancelTimeout() { clearTimeout(this.timeoutTimer); this.timeoutTimer = null; } startTimeout() { if (this.timeoutTimer) { this.cancelTimeout(); } this.ref = this.channel.socket.makeRef(); this.refEvent = this.channel.replyEventName(this.ref); this.channel.on(this.refEvent, (payload) => { this.cancelRefEvent(); this.cancelTimeout(); this.receivedResp = payload; this.matchReceive(payload); }); this.timeoutTimer = setTimeout(() => { this.trigger("timeout", {}); }, this.timeout); } hasReceived(status) { return this.receivedResp && this.receivedResp.status === status; } trigger(status, response) { this.channel.trigger(this.refEvent, { status, response }); } }; // js/phoenix/timer.js var Timer = class { constructor(callback, timerCalc) { this.callback = callback; this.timerCalc = timerCalc; 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)); } }; // js/phoenix/channel.js var Channel = class { constructor(topic, params, socket) { this.state = CHANNEL_STATES.closed; this.topic = topic; this.params = closure(params || {}); this.socket = socket; this.bindings = []; this.bindingRef = 0; this.timeout = this.socket.timeout; this.joinedOnce = false; this.joinPush = new Push(this, CHANNEL_EVENTS.join, this.params, this.timeout); this.pushBuffer = []; this.stateChangeRefs = []; this.rejoinTimer = new Timer(() => { if (this.socket.isConnected()) { this.rejoin(); } }, this.socket.rejoinAfterMs); this.stateChangeRefs.push(this.socket.onError(() => this.rejoinTimer.reset())); this.stateChangeRefs.push(this.socket.onOpen(() => { this.rejoinTimer.reset(); if (this.isErrored()) { this.rejoin(); } })); this.joinPush.receive("ok", () => { this.state = CHANNEL_STATES.joined; this.rejoinTimer.reset(); this.pushBuffer.forEach((pushEvent) => pushEvent.send()); this.pushBuffer = []; }); this.joinPush.receive("error", () => { this.state = CHANNEL_STATES.errored; if (this.socket.isConnected()) { this.rejoinTimer.scheduleTimeout(); } }); this.onClose(() => { this.rejoinTimer.reset(); if (this.socket.hasLogger()) this.socket.log("channel", `close ${this.topic} ${this.joinRef()}`); this.state = CHANNEL_STATES.closed; this.socket.remove(this); }); this.onError((reason) => { if (this.socket.hasLogger()) this.socket.log("channel", `error ${this.topic}`, reason); if (this.isJoining()) { this.joinPush.reset(); } this.state = CHANNEL_STATES.errored; if (this.socket.isConnected()) { this.rejoinTimer.scheduleTimeout(); } }); this.joinPush.receive("timeout", () => { if (this.socket.hasLogger()) this.socket.log("channel", `timeout ${this.topic} (${this.joinRef()})`, this.joinPush.timeout); let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), this.timeout); leavePush.send(); this.state = CHANNEL_STATES.errored; this.joinPush.reset(); if (this.socket.isConnected()) { this.rejoinTimer.scheduleTimeout(); } }); this.on(CHANNEL_EVENTS.reply, (payload, ref) => { this.trigger(this.replyEventName(ref), payload); }); } join(timeout = this.timeout) { if (this.joinedOnce) { throw new Error("tried to join multiple times. 'join' can only be called a single time per channel instance"); } else { this.timeout = timeout; this.joinedOnce = true; this.rejoin(); return this.joinPush; } } onClose(callback) { this.on(CHANNEL_EVENTS.close, callback); } onError(callback) { return this.on(CHANNEL_EVENTS.error, (reason) => callback(reason)); } on(event, callback) { let ref = this.bindingRef++; this.bindings.push({ event, ref, callback }); return ref; } off(event, ref) { this.bindings = this.bindings.filter((bind) => { return !(bind.event === event && (typeof ref === "undefined" || ref === bind.ref)); }); } canPush() { return this.socket.isConnected() && this.isJoined(); } push(event, payload, timeout = this.timeout) { payload = payload || {}; if (!this.joinedOnce) { throw new Error(`tried to push '${event}' to '${this.topic}' before joining. Use channel.join() before pushing events`); } let pushEvent = new Push(this, event, function() { return payload; }, timeout); if (this.canPush()) { pushEvent.send(); } else { pushEvent.startTimeout(); this.pushBuffer.push(pushEvent); } return pushEvent; } leave(timeout = this.timeout) { this.rejoinTimer.reset(); this.joinPush.cancelTimeout(); this.state = CHANNEL_STATES.leaving; let onClose = () => { if (this.socket.hasLogger()) this.socket.log("channel", `leave ${this.topic}`); this.trigger(CHANNEL_EVENTS.close, "leave"); }; let leavePush = new Push(this, CHANNEL_EVENTS.leave, closure({}), timeout); leavePush.receive("ok", () => onClose()).receive("timeout", () => onClose()); leavePush.send(); if (!this.canPush()) { leavePush.trigger("ok", {}); } return leavePush; } onMessage(_event, payload, _ref) { return payload; } isMember(topic, event, payload, joinRef) { if (this.topic !== topic) { return false; } if (joinRef && joinRef !== this.joinRef()) { if (this.socket.hasLogger()) this.socket.log("channel", "dropping outdated message", { topic, event, payload, joinRef }); return false; } else { return true; } } joinRef() { return this.joinPush.ref; } rejoin(timeout = this.timeout) { if (this.isLeaving()) { return; } this.socket.leaveOpenTopic(this.topic); this.state = CHANNEL_STATES.joining; this.joinPush.resend(timeout); } trigger(event, payload, ref, joinRef) { let handledPayload = this.onMessage(event, payload, ref, joinRef); if (payload && !handledPayload) { throw new Error("channel onMessage callbacks must return the payload, modified or unmodified"); } let eventBindings = this.bindings.filter((bind) => bind.event === event); for (let i = 0; i < eventBindings.length; i++) { let bind = eventBindings[i]; bind.callback(handledPayload, ref, joinRef || this.joinRef()); } } replyEventName(ref) { return `chan_reply_${ref}`; } isClosed() { return this.state === CHANNEL_STATES.closed; } isErrored() { return this.state === CHANNEL_STATES.errored; } isJoined() { return this.state === CHANNEL_STATES.joined; } isJoining() { return this.state === CHANNEL_STATES.joining; } isLeaving() { return this.state === CHANNEL_STATES.leaving; } }; // js/phoenix/ajax.js var Ajax = class { static request(method, endPoint, accept, body, timeout, ontimeout, callback) { if (global.XDomainRequest) { let req = new global.XDomainRequest(); return this.xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback); } else { let req = new global.XMLHttpRequest(); return this.xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback); } } static xdomainRequest(req, method, endPoint, body, timeout, ontimeout, callback) { req.timeout = timeout; req.open(method, endPoint); req.onload = () => { let response = this.parseJSON(req.responseText); callback && callback(response); }; if (ontimeout) { req.ontimeout = ontimeout; } req.onprogress = () => { }; req.send(body); return req; } static xhrRequest(req, method, endPoint, accept, body, timeout, ontimeout, callback) { req.open(method, endPoint, true); req.timeout = timeout; req.setRequestHeader("Content-Type", accept); req.onerror = () => callback && callback(null); req.onreadystatechange = () => { if (req.readyState === XHR_STATES.complete && callback) { let response = this.parseJSON(req.responseText); callback(response); } }; if (ontimeout) { req.ontimeout = ontimeout; } req.send(body); return req; } static parseJSON(resp) { if (!resp || resp === "") { return null; } try { return JSON.parse(resp); } catch (e) { console && console.log("failed to parse JSON response", resp); return null; } } static serialize(obj, parentKey) { let queryStr = []; for (var key in obj) { if (!Object.prototype.hasOwnProperty.call(obj, key)) { continue; } let paramKey = parentKey ? `${parentKey}[${key}]` : key; let paramVal = obj[key]; if (typeof paramVal === "object") { queryStr.push(this.serialize(paramVal, paramKey)); } else { queryStr.push(encodeURIComponent(paramKey) + "=" + encodeURIComponent(paramVal)); } } return queryStr.join("&"); } static appendParams(url, params) { if (Object.keys(params).length === 0) { return url; } let prefix = url.match(/\?/) ? "&" : "?"; return `${url}${prefix}${this.serialize(params)}`; } }; // js/phoenix/longpoll.js var LongPoll = class { constructor(endPoint) { this.endPoint = null; this.token = null; this.skipHeartbeat = true; this.reqs = /* @__PURE__ */ new Set(); this.onopen = function() { }; this.onerror = function() { }; this.onmessage = function() { }; this.onclose = function() { }; this.pollEndpoint = this.normalizeEndpoint(endPoint); this.readyState = SOCKET_STATES.connecting; this.poll(); } normalizeEndpoint(endPoint) { return endPoint.replace("ws://", "http://").replace("wss://", "https://").replace(new RegExp("(.*)/" + TRANSPORTS.websocket), "$1/" + TRANSPORTS.longpoll); } endpointURL() { return Ajax.appendParams(this.pollEndpoint, { token: this.token }); } closeAndRetry(code, reason, wasClean) { this.close(code, reason, wasClean); this.readyState = SOCKET_STATES.connecting; } ontimeout() { this.onerror("timeout"); this.closeAndRetry(1005, "timeout", false); } isActive() { return this.readyState === SOCKET_STATES.open || this.readyState === SOCKET_STATES.connecting; } poll() { this.ajax("GET", null, () => this.ontimeout(), (resp) => { if (resp) { var { status, token, messages } = resp; this.token = token; } else { status = 0; } switch (status) { case 200: messages.forEach((msg) => { setTimeout(() => this.onmessage({ data: msg }), 0); }); this.poll(); break; case 204: this.poll(); break; case 410: this.readyState = SOCKET_STATES.open; this.onopen({}); this.poll(); break; case 403: this.onerror(403); this.close(1008, "forbidden", false); break; case 0: case 500: this.onerror(500); this.closeAndRetry(1011, "internal server error", 500); break; default: throw new Error(`unhandled poll status ${status}`); } }); } send(body) { this.ajax("POST", body, () => this.onerror("timeout"), (resp) => { if (!resp || resp.status !== 200) { this.onerror(resp && resp.status); this.closeAndRetry(1011, "internal server error", false); } }); } close(code, reason, wasClean) { for (let req of this.reqs) { req.abort(); } this.readyState = SOCKET_STATES.closed; let opts = Object.assign({ code: 1e3, reason: void 0, wasClean: true }, { code, reason, wasClean }); if (typeof CloseEvent !== "undefined") { this.onclose(new CloseEvent("close", opts)); } else { this.onclose(opts); } } ajax(method, body, onCallerTimeout, callback) { let req; let ontimeout = () => { this.reqs.delete(req); onCallerTimeout(); }; req = Ajax.request(method, this.endpointURL(), "application/json", body, this.timeout, ontimeout, (resp) => { this.reqs.delete(req); if (this.isActive()) { callback(resp); } }); this.reqs.add(req); } }; // js/phoenix/presence.js var Presence = class { constructor(channel, opts = {}) { let events = opts.events || { state: "presence_state", diff: "presence_diff" }; this.state = {}; this.pendingDiffs = []; this.channel = channel; this.joinRef = null; this.caller = { onJoin: function() { }, onLeave: function() { }, onSync: function() { } }; this.channel.on(events.state, (newState) => { let { onJoin, onLeave, onSync } = this.caller; this.joinRef = this.channel.joinRef(); this.state = Presence.syncState(this.state, newState, onJoin, onLeave); this.pendingDiffs.forEach((diff) => { this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); }); this.pendingDiffs = []; onSync(); }); this.channel.on(events.diff, (diff) => { let { onJoin, onLeave, onSync } = this.caller; if (this.inPendingSyncState()) { this.pendingDiffs.push(diff); } else { this.state = Presence.syncDiff(this.state, diff, onJoin, onLeave); onSync(); } }); } onJoin(callback) { this.caller.onJoin = callback; } onLeave(callback) { this.caller.onLeave = callback; } onSync(callback) { this.caller.onSync = callback; } list(by) { return Presence.list(this.state, by); } inPendingSyncState() { return !this.joinRef || this.joinRef !== this.channel.joinRef(); } static syncState(currentState, newState, onJoin, onLeave) { let state = this.clone(currentState); let joins = {}; let leaves = {}; this.map(state, (key, presence) => { if (!newState[key]) { leaves[key] = presence; } }); this.map(newState, (key, newPresence) => { let currentPresence = state[key]; if (currentPresence) { let newRefs = newPresence.metas.map((m) => m.phx_ref); let curRefs = currentPresence.metas.map((m) => m.phx_ref); let joinedMetas = newPresence.metas.filter((m) => curRefs.indexOf(m.phx_ref) < 0); let leftMetas = currentPresence.metas.filter((m) => newRefs.indexOf(m.phx_ref) < 0); if (joinedMetas.length > 0) { joins[key] = newPresence; joins[key].metas = joinedMetas; } if (leftMetas.length > 0) { leaves[key] = this.clone(currentPresence); leaves[key].metas = leftMetas; } } else { joins[key] = newPresence; } }); return this.syncDiff(state, { joins, leaves }, onJoin, onLeave); } static syncDiff(state, diff, onJoin, onLeave) { let { joins, leaves } = this.clone(diff); if (!onJoin) { onJoin = function() { }; } if (!onLeave) { onLeave = function() { }; } this.map(joins, (key, newPresence) => { let currentPresence = state[key]; state[key] = this.clone(newPresence); if (currentPresence) { let joinedRefs = state[key].metas.map((m) => m.phx_ref); let curMetas = currentPresence.metas.filter((m) => joinedRefs.indexOf(m.phx_ref) < 0); state[key].metas.unshift(...curMetas); } onJoin(key, currentPresence, newPresence); }); this.map(leaves, (key, leftPresence) => { let currentPresence = state[key]; if (!currentPresence) { return; } let refsToRemove = leftPresence.metas.map((m) => m.phx_ref); currentPresence.metas = currentPresence.metas.filter((p) => { return refsToRemove.indexOf(p.phx_ref) < 0; }); onLeave(key, currentPresence, leftPresence); if (currentPresence.metas.length === 0) { delete state[key]; } }); return state; } static list(presences, chooser) { if (!chooser) { chooser = function(key, pres) { return pres; }; } return this.map(presences, (key, presence) => { return chooser(key, presence); }); } static map(obj, func) { return Object.getOwnPropertyNames(obj).map((key) => func(key, obj[key])); } static clone(obj) { return JSON.parse(JSON.stringify(obj)); } }; // js/phoenix/serializer.js var serializer_default = { HEADER_LENGTH: 1, META_LENGTH: 4, KINDS: { push: 0, reply: 1, broadcast: 2 }, encode(msg, callback) { if (msg.payload.constructor === ArrayBuffer) { return callback(this.binaryEncode(msg)); } else { let payload = [msg.join_ref, msg.ref, msg.topic, msg.event, msg.payload]; return callback(JSON.stringify(payload)); } }, decode(rawPayload, callback) { if (rawPayload.constructor === ArrayBuffer) { return callback(this.binaryDecode(rawPayload)); } else { let [join_ref, ref, topic, event, payload] = JSON.parse(rawPayload); return callback({ join_ref, ref, topic, event, payload }); } }, binaryEncode(message) { let { join_ref, ref, event, topic, payload } = message; let metaLength = this.META_LENGTH + join_ref.length + ref.length + topic.length + event.length; let header = new ArrayBuffer(this.HEADER_LENGTH + metaLength); let view = new DataView(header); let offset = 0; view.setUint8(offset++, this.KINDS.push); view.setUint8(offset++, join_ref.length); view.setUint8(offset++, ref.length); view.setUint8(offset++, topic.length); view.setUint8(offset++, event.length); Array.from(join_ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(ref, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(topic, (char) => view.setUint8(offset++, char.charCodeAt(0))); Array.from(event, (char) => view.setUint8(offset++, char.charCodeAt(0))); var combined = new Uint8Array(header.byteLength + payload.byteLength); combined.set(new Uint8Array(header), 0); combined.set(new Uint8Array(payload), header.byteLength); return combined.buffer; }, binaryDecode(buffer) { let view = new DataView(buffer); let kind = view.getUint8(0); let decoder = new TextDecoder(); switch (kind) { case this.KINDS.push: return this.decodePush(buffer, view, decoder); case this.KINDS.reply: return this.decodeReply(buffer, view, decoder); case this.KINDS.broadcast: return this.decodeBroadcast(buffer, view, decoder); } }, decodePush(buffer, view, decoder) { let joinRefSize = view.getUint8(1); let topicSize = view.getUint8(2); let eventSize = view.getUint8(3); let offset = this.HEADER_LENGTH + this.META_LENGTH - 1; let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; let event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; let data = buffer.slice(offset, buffer.byteLength); return { join_ref: joinRef, ref: null, topic, event, payload: data }; }, decodeReply(buffer, view, decoder) { let joinRefSize = view.getUint8(1); let refSize = view.getUint8(2); let topicSize = view.getUint8(3); let eventSize = view.getUint8(4); let offset = this.HEADER_LENGTH + this.META_LENGTH; let joinRef = decoder.decode(buffer.slice(offset, offset + joinRefSize)); offset = offset + joinRefSize; let ref = decoder.decode(buffer.slice(offset, offset + refSize)); offset = offset + refSize; let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; let event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; let data = buffer.slice(offset, buffer.byteLength); let payload = { status: event, response: data }; return { join_ref: joinRef, ref, topic, event: CHANNEL_EVENTS.reply, payload }; }, decodeBroadcast(buffer, view, decoder) { let topicSize = view.getUint8(1); let eventSize = view.getUint8(2); let offset = this.HEADER_LENGTH + 2; let topic = decoder.decode(buffer.slice(offset, offset + topicSize)); offset = offset + topicSize; let event = decoder.decode(buffer.slice(offset, offset + eventSize)); offset = offset + eventSize; let data = buffer.slice(offset, buffer.byteLength); return { join_ref: null, ref: null, topic, event, payload: data }; } }; // js/phoenix/socket.js var Socket = class { constructor(endPoint, opts = {}) { this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] }; this.channels = []; this.sendBuffer = []; this.ref = 0; this.timeout = opts.timeout || DEFAULT_TIMEOUT; this.transport = opts.transport || global.WebSocket || LongPoll; this.establishedConnections = 0; this.defaultEncoder = serializer_default.encode.bind(serializer_default); this.defaultDecoder = serializer_default.decode.bind(serializer_default); this.closeWasClean = false; this.binaryType = opts.binaryType || "arraybuffer"; this.connectClock = 1; if (this.transport !== LongPoll) { this.encode = opts.encode || this.defaultEncoder; this.decode = opts.decode || this.defaultDecoder; } else { this.encode = this.defaultEncoder; this.decode = this.defaultDecoder; } let awaitingConnectionOnPageShow = null; if (phxWindow && phxWindow.addEventListener) { phxWindow.addEventListener("pagehide", (_e) => { if (this.conn) { this.disconnect(); awaitingConnectionOnPageShow = this.connectClock; } }); phxWindow.addEventListener("pageshow", (_e) => { if (awaitingConnectionOnPageShow === this.connectClock) { awaitingConnectionOnPageShow = null; this.connect(); } }); } this.heartbeatIntervalMs = opts.heartbeatIntervalMs || 3e4; this.rejoinAfterMs = (tries) => { if (opts.rejoinAfterMs) { return opts.rejoinAfterMs(tries); } else { return [1e3, 2e3, 5e3][tries - 1] || 1e4; } }; this.reconnectAfterMs = (tries) => { if (opts.reconnectAfterMs) { return opts.reconnectAfterMs(tries); } else { return [10, 50, 100, 150, 200, 250, 500, 1e3, 2e3][tries - 1] || 5e3; } }; this.logger = opts.logger || null; this.longpollerTimeout = opts.longpollerTimeout || 2e4; this.params = closure(opts.params || {}); this.endPoint = `${endPoint}/${TRANSPORTS.websocket}`; this.vsn = opts.vsn || DEFAULT_VSN; this.heartbeatTimeoutTimer = null; this.heartbeatTimer = null; this.pendingHeartbeatRef = null; this.reconnectTimer = new Timer(() => { this.teardown(() => this.connect()); }, this.reconnectAfterMs); } getLongPollTransport() { return LongPoll; } replaceTransport(newTransport) { this.connectClock++; this.closeWasClean = true; this.reconnectTimer.reset(); this.sendBuffer = []; if (this.conn) { this.conn.close(); this.conn = null; } this.transport = newTransport; } protocol() { return location.protocol.match(/^https/) ? "wss" : "ws"; } endPointURL() { let uri = Ajax.appendParams(Ajax.appendParams(this.endPoint, this.params()), { vsn: this.vsn }); if (uri.charAt(0) !== "/") { return uri; } if (uri.charAt(1) === "/") { return `${this.protocol()}:${uri}`; } return `${this.protocol()}://${location.host}${uri}`; } disconnect(callback, code, reason) { this.connectClock++; this.closeWasClean = true; this.reconnectTimer.reset(); this.teardown(callback, code, reason); } connect(params) { if (params) { console && console.log("passing params to connect is deprecated. Instead pass :params to the Socket constructor"); this.params = closure(params); } if (this.conn) { return; } this.connectClock++; this.closeWasClean = false; this.conn = new this.transport(this.endPointURL()); this.conn.binaryType = this.binaryType; this.conn.timeout = this.longpollerTimeout; this.conn.onopen = () => this.onConnOpen(); this.conn.onerror = (error) => this.onConnError(error); this.conn.onmessage = (event) => this.onConnMessage(event); this.conn.onclose = (event) => this.onConnClose(event); } log(kind, msg, data) { this.logger(kind, msg, data); } hasLogger() { return this.logger !== null; } onOpen(callback) { let ref = this.makeRef(); this.stateChangeCallbacks.open.push([ref, callback]); return ref; } onClose(callback) { let ref = this.makeRef(); this.stateChangeCallbacks.close.push([ref, callback]); return ref; } onError(callback) { let ref = this.makeRef(); this.stateChangeCallbacks.error.push([ref, callback]); return ref; } onMessage(callback) { let ref = this.makeRef(); this.stateChangeCallbacks.message.push([ref, callback]); return ref; } ping(callback) { if (!this.isConnected()) { return false; } let ref = this.makeRef(); let startTime = Date.now(); this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref }); let onMsgRef = this.onMessage((msg) => { if (msg.ref === ref) { this.off([onMsgRef]); callback(Date.now() - startTime); } }); return true; } clearHeartbeats() { clearTimeout(this.heartbeatTimer); clearTimeout(this.heartbeatTimeoutTimer); } onConnOpen() { if (this.hasLogger()) this.log("transport", `connected to ${this.endPointURL()}`); this.closeWasClean = false; this.establishedConnections++; this.flushSendBuffer(); this.reconnectTimer.reset(); this.resetHeartbeat(); this.stateChangeCallbacks.open.forEach(([, callback]) => callback()); } heartbeatTimeout() { if (this.pendingHeartbeatRef) { this.pendingHeartbeatRef = null; if (this.hasLogger()) { this.log("transport", "heartbeat timeout. Attempting to re-establish connection"); } this.triggerChanError(); this.closeWasClean = false; this.teardown(() => this.reconnectTimer.scheduleTimeout(), WS_CLOSE_NORMAL, "heartbeat timeout"); } } resetHeartbeat() { if (this.conn && this.conn.skipHeartbeat) { return; } this.pendingHeartbeatRef = null; this.clearHeartbeats(); this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs); } teardown(callback, code, reason) { if (!this.conn) { return callback && callback(); } this.waitForBufferDone(() => { if (this.conn) { if (code) { this.conn.close(code, reason || ""); } else { this.conn.close(); } } this.waitForSocketClosed(() => { if (this.conn) { this.conn.onopen = function() { }; this.conn.onerror = function() { }; this.conn.onmessage = function() { }; this.conn.onclose = function() { }; this.conn = null; } callback && callback(); }); }); } waitForBufferDone(callback, tries = 1) { if (tries === 5 || !this.conn || !this.conn.bufferedAmount) { callback(); return; } setTimeout(() => { this.waitForBufferDone(callback, tries + 1); }, 150 * tries); } waitForSocketClosed(callback, tries = 1) { if (tries === 5 || !this.conn || this.conn.readyState === SOCKET_STATES.closed) { callback(); return; } setTimeout(() => { this.waitForSocketClosed(callback, tries + 1); }, 150 * tries); } onConnClose(event) { let closeCode = event && event.code; if (this.hasLogger()) this.log("transport", "close", event); this.triggerChanError(); this.clearHeartbeats(); if (!this.closeWasClean && closeCode !== 1e3) { this.reconnectTimer.scheduleTimeout(); } this.stateChangeCallbacks.close.forEach(([, callback]) => callback(event)); } onConnError(error) { if (this.hasLogger()) this.log("transport", error); let transportBefore = this.transport; let establishedBefore = this.establishedConnections; this.stateChangeCallbacks.error.forEach(([, callback]) => { callback(error, transportBefore, establishedBefore); }); if (transportBefore === this.transport || establishedBefore > 0) { this.triggerChanError(); } } triggerChanError() { this.channels.forEach((channel) => { if (!(channel.isErrored() || channel.isLeaving() || channel.isClosed())) { channel.trigger(CHANNEL_EVENTS.error); } }); } connectionState() { switch (this.conn && this.conn.readyState) { case SOCKET_STATES.connecting: return "connecting"; case SOCKET_STATES.open: return "open"; case SOCKET_STATES.closing: return "closing"; default: return "closed"; } } isConnected() { return this.connectionState() === "open"; } remove(channel) { this.off(channel.stateChangeRefs); this.channels = this.channels.filter((c) => c.joinRef() !== channel.joinRef()); } off(refs) { for (let key in this.stateChangeCallbacks) { this.stateChangeCallbacks[key] = this.stateChangeCallbacks[key].filter(([ref]) => { return refs.indexOf(ref) === -1; }); } } channel(topic, chanParams = {}) { let chan = new Channel(topic, chanParams, this); this.channels.push(chan); return chan; } push(data) { if (this.hasLogger()) { let { topic, event, payload, ref, join_ref } = data; this.log("push", `${topic} ${event} (${join_ref}, ${ref})`, payload); } if (this.isConnected()) { this.encode(data, (result) => this.conn.send(result)); } else { this.sendBuffer.push(() => this.encode(data, (result) => this.conn.send(result))); } } makeRef() { let newRef = this.ref + 1; if (newRef === this.ref) { this.ref = 0; } else { this.ref = newRef; } return this.ref.toString(); } sendHeartbeat() { if (this.pendingHeartbeatRef && !this.isConnected()) { return; } this.pendingHeartbeatRef = this.makeRef(); this.push({ topic: "phoenix", event: "heartbeat", payload: {}, ref: this.pendingHeartbeatRef }); this.heartbeatTimeoutTimer = setTimeout(() => this.heartbeatTimeout(), this.heartbeatIntervalMs); } flushSendBuffer() { if (this.isConnected() && this.sendBuffer.length > 0) { this.sendBuffer.forEach((callback) => callback()); this.sendBuffer = []; } } onConnMessage(rawMessage) { this.decode(rawMessage.data, (msg) => { let { topic, event, payload, ref, join_ref } = msg; if (ref && ref === this.pendingHeartbeatRef) { this.clearHeartbeats(); this.pendingHeartbeatRef = null; this.heartbeatTimer = setTimeout(() => this.sendHeartbeat(), this.heartbeatIntervalMs); } if (this.hasLogger()) this.log("receive", `${payload.status || ""} ${topic} ${event} ${ref && "(" + ref + ")" || ""}`, payload); for (let i = 0; i < this.channels.length; i++) { const channel = this.channels[i]; if (!channel.isMember(topic, event, payload, join_ref)) { continue; } channel.trigger(event, payload, ref, join_ref); } for (let i = 0; i < this.stateChangeCallbacks.message.length; i++) { let [, callback] = this.stateChangeCallbacks.message[i]; callback(msg); } }); } leaveOpenTopic(topic) { let dupChannel = this.channels.find((c) => c.topic === topic && (c.isJoined() || c.isJoining())); if (dupChannel) { if (this.hasLogger()) this.log("transport", `leaving duplicate topic "${topic}"`); dupChannel.leave(); } } }; export { Channel, LongPoll, Presence, serializer_default as Serializer, Socket }; //# sourceMappingURL=phoenix.mjs.map