"use strict"; var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; (function (root, factory) { if (typeof define === "function" && define.amd) { return define(["phoenix"], factory); } else if (typeof exports === "object") { return factory(exports); } else { root.Phoenix = {}; return factory.call(root, root.Phoenix); } })(Function("return this")(), function (exports) { var root = this; var SOCKET_STATES = { connecting: 0, open: 1, closing: 2, closed: 3 }; exports.Channel = (function () { function Channel(topic, message, callback, socket) { _classCallCheck(this, Channel); this.topic = topic; this.message = message; this.callback = callback; this.socket = socket; this.bindings = null; this.reset(); } _prototypeProperties(Channel, null, { reset: { value: function reset() { this.bindings = []; }, writable: true, configurable: true }, on: { value: function on(event, callback) { this.bindings.push({ event: event, callback: callback }); }, writable: true, configurable: true }, isMember: { value: function isMember(topic) { return this.topic === topic; }, writable: true, configurable: true }, off: { value: function off(event) { this.bindings = this.bindings.filter(function (bind) { return bind.event !== event; }); }, writable: true, configurable: true }, trigger: { value: function trigger(triggerEvent, msg) { this.bindings.filter(function (bind) { return bind.event === triggerEvent; }).map(function (bind) { return bind.callback(msg); }); }, writable: true, configurable: true }, send: { value: function send(event, payload) { this.socket.send({ topic: this.topic, event: event, payload: payload }); }, writable: true, configurable: true }, leave: { value: function leave() { var message = arguments[0] === undefined ? {} : arguments[0]; this.socket.leave(this.topic, message); this.reset(); }, writable: true, configurable: true } }); return Channel; })(); exports.Socket = (function () { // Initializes the Socket // // endPoint - The string WebSocket endpoint, ie, "ws://example.com/ws", // "wss://example.com" // "/ws" (inherited host & protocol) // opts - Optional configuration // transport - The Websocket Transport, ie WebSocket, Phoenix.LongPoller. // Defaults to WebSocket with automatic LongPoller fallback. // heartbeatIntervalMs - The millisecond interval to send a heartbeat message // logger - The optional function for specialized logging, ie: // `logger: (msg) -> console.log(msg)` // function Socket(endPoint) { var opts = arguments[1] === undefined ? {} : arguments[1]; _classCallCheck(this, Socket); this.states = SOCKET_STATES; this.stateChangeCallbacks = { open: [], close: [], error: [], message: [] }; this.flushEveryMs = 50; this.reconnectTimer = null; this.reconnectAfterMs = 5000; this.heartbeatIntervalMs = 30000; this.channels = []; this.sendBuffer = []; this.transport = opts.transport || root.WebSocket || exports.LongPoller; this.heartbeatIntervalMs = opts.heartbeatIntervalMs || this.heartbeatIntervalMs; this.logger = opts.logger || function () {}; // noop this.endPoint = this.expandEndpoint(endPoint); this.resetBufferTimer(); this.reconnect(); } _prototypeProperties(Socket, null, { protocol: { value: function protocol() { return location.protocol.match(/^https/) ? "wss" : "ws"; }, writable: true, configurable: true }, expandEndpoint: { value: function expandEndpoint(endPoint) { if (endPoint.charAt(0) !== "/") { return endPoint; } if (endPoint.charAt(1) === "/") { return "" + this.protocol() + ":" + endPoint; } return "" + this.protocol() + "://" + location.host + "" + endPoint; }, writable: true, configurable: true }, close: { value: function close(callback, code, reason) { if (this.conn) { this.conn.onclose = function () {}; // noop if (code) { this.conn.close(code, reason || ""); } else { this.conn.close(); } this.conn = null; } callback && callback(); }, writable: true, configurable: true }, reconnect: { value: function reconnect() { var _this = this; this.close(function () { _this.conn = new _this.transport(_this.endPoint); _this.conn.onopen = function () { return _this.onConnOpen(); }; _this.conn.onerror = function (error) { return _this.onConnError(error); }; _this.conn.onmessage = function (event) { return _this.onConnMessage(event); }; _this.conn.onclose = function (event) { return _this.onConnClose(event); }; }); }, writable: true, configurable: true }, resetBufferTimer: { value: function resetBufferTimer() { var _this = this; clearTimeout(this.sendBufferTimer); this.sendBufferTimer = setTimeout(function () { return _this.flushSendBuffer(); }, this.flushEveryMs); }, writable: true, configurable: true }, log: { // Logs the message. Override `this.logger` for specialized logging. noops by default value: function log(msg) { this.logger(msg); }, writable: true, configurable: true }, onOpen: { // Registers callbacks for connection state change events // // Examples // // socket.onError (error) -> alert("An error occurred") // value: function onOpen(callback) { this.stateChangeCallbacks.open.push(callback); }, writable: true, configurable: true }, onClose: { value: function onClose(callback) { this.stateChangeCallbacks.close.push(callback); }, writable: true, configurable: true }, onError: { value: function onError(callback) { this.stateChangeCallbacks.error.push(callback); }, writable: true, configurable: true }, onMessage: { value: function onMessage(callback) { this.stateChangeCallbacks.message.push(callback); }, writable: true, configurable: true }, onConnOpen: { value: function onConnOpen() { var _this = this; clearInterval(this.reconnectTimer); if (!this.transport.skipHeartbeat) { this.heartbeatTimer = setInterval(function () { return _this.sendHeartbeat(); }, this.heartbeatIntervalMs); } this.rejoinAll(); this.stateChangeCallbacks.open.forEach(function (callback) { return callback(); }); }, writable: true, configurable: true }, onConnClose: { value: function onConnClose(event) { var _this = this; this.log("WS close:"); this.log(event); clearInterval(this.reconnectTimer); clearInterval(this.heartbeatTimer); this.reconnectTimer = setInterval(function () { return _this.reconnect(); }, this.reconnectAfterMs); this.stateChangeCallbacks.close.forEach(function (callback) { return callback(event); }); }, writable: true, configurable: true }, onConnError: { value: function onConnError(error) { this.log("WS error:"); this.log(error); this.stateChangeCallbacks.error.forEach(function (callback) { return callback(error); }); }, writable: true, configurable: true }, connectionState: { value: function connectionState() { switch (this.conn && this.conn.readyState) { case this.states.connecting: return "connecting"; case this.states.open: return "open"; case this.states.closing: return "closing"; default: return "closed"; } }, writable: true, configurable: true }, isConnected: { value: function isConnected() { return this.connectionState() === "open"; }, writable: true, configurable: true }, rejoinAll: { value: function rejoinAll() { var _this = this; this.channels.forEach(function (chan) { return _this.rejoin(chan); }); }, writable: true, configurable: true }, rejoin: { value: function rejoin(chan) { chan.reset(); this.send({ topic: chan.topic, event: "join", payload: chan.message }); chan.callback(chan); }, writable: true, configurable: true }, join: { value: function join(topic, message, callback) { var chan = new exports.Channel(topic, message, callback, this); this.channels.push(chan); if (this.isConnected()) { this.rejoin(chan); } }, writable: true, configurable: true }, leave: { value: function leave(topic) { var message = arguments[1] === undefined ? {} : arguments[1]; this.send({ topic: topic, event: "leave", payload: message }); this.channels = this.channels.filter(function (c) { return !c.isMember(topic); }); }, writable: true, configurable: true }, send: { value: function send(data) { var _this = this; var callback = function () { return _this.conn.send(root.JSON.stringify(data)); }; if (this.isConnected()) { callback(); } else { this.sendBuffer.push(callback); } }, writable: true, configurable: true }, sendHeartbeat: { value: function sendHeartbeat() { this.send({ topic: "phoenix", event: "heartbeat", payload: {} }); }, writable: true, configurable: true }, flushSendBuffer: { value: function flushSendBuffer() { if (this.isConnected() && this.sendBuffer.length > 0) { this.sendBuffer.forEach(function (callback) { return callback(); }); this.sendBuffer = []; } this.resetBufferTimer(); }, writable: true, configurable: true }, onConnMessage: { value: function onConnMessage(rawMessage) { this.log("message received:"); this.log(rawMessage); var _root$JSON$parse = root.JSON.parse(rawMessage.data); var topic = _root$JSON$parse.topic; var event = _root$JSON$parse.event; var payload = _root$JSON$parse.payload; this.channels.filter(function (chan) { return chan.isMember(topic); }).forEach(function (chan) { return chan.trigger(event, payload); }); this.stateChangeCallbacks.message.forEach(function (callback) { callback(topic, event, payload); }); }, writable: true, configurable: true } }); return Socket; })(); exports.LongPoller = (function () { function LongPoller(endPoint) { _classCallCheck(this, LongPoller); this.retryInMs = 5000; this.endPoint = null; this.token = null; this.sig = null; this.skipHeartbeat = true; this.onopen = function () {}; // noop this.onerror = function () {}; // noop this.onmessage = function () {}; // noop this.onclose = function () {}; // noop this.states = SOCKET_STATES; this.upgradeEndpoint = this.normalizeEndpoint(endPoint); this.pollEndpoint = this.upgradeEndpoint + (/\/$/.test(endPoint) ? "poll" : "/poll"); this.readyState = this.states.connecting; this.poll(); } _prototypeProperties(LongPoller, null, { normalizeEndpoint: { value: function normalizeEndpoint(endPoint) { return endPoint.replace("ws://", "http://").replace("wss://", "https://"); }, writable: true, configurable: true }, endpointURL: { value: function endpointURL() { return this.pollEndpoint + ("?token=" + encodeURIComponent(this.token) + "&sig=" + encodeURIComponent(this.sig)); }, writable: true, configurable: true }, closeAndRetry: { value: function closeAndRetry() { this.close(); this.readyState = this.states.connecting; }, writable: true, configurable: true }, ontimeout: { value: function ontimeout() { this.onerror("timeout"); this.closeAndRetry(); }, writable: true, configurable: true }, poll: { value: function poll() { var _this = this; if (!(this.readyState === this.states.open || this.readyState === this.states.connecting)) { return; } exports.Ajax.request("GET", this.endpointURL(), "application/json", null, this.ontimeout.bind(this), function (status, resp) { if (resp && resp !== "") { var _root$JSON$parse = root.JSON.parse(resp); var token = _root$JSON$parse.token; var sig = _root$JSON$parse.sig; var messages = _root$JSON$parse.messages; _this.token = token; _this.sig = sig; } switch (status) { case 200: messages.forEach(function (msg) { return _this.onmessage({ data: root.JSON.stringify(msg) }); }); _this.poll(); break; case 204: _this.poll(); break; case 410: _this.readyState = _this.states.open; _this.onopen(); _this.poll(); break; case 0: case 500: _this.onerror(); _this.closeAndRetry(); break; default: throw "unhandled poll status " + status; } }); }, writable: true, configurable: true }, send: { value: function send(body) { var _this = this; exports.Ajax.request("POST", this.endpointURL(), "application/json", body, this.onerror.bind(this, "timeout"), function (status, resp) { if (status !== 200) { _this.onerror(status); } }); }, writable: true, configurable: true }, close: { value: function close(code, reason) { this.readyState = this.states.closed; this.onclose(); }, writable: true, configurable: true } }); return LongPoller; })(); exports.Ajax = { states: { complete: 4 }, request: function (method, endPoint, accept, body, ontimeout, callback) { var _this = this; var req = root.XMLHttpRequest ? new root.XMLHttpRequest() : // IE7+, Firefox, Chrome, Opera, Safari new root.ActiveXObject("Microsoft.XMLHTTP"); // IE6, IE5 req.open(method, endPoint, true); req.setRequestHeader("Content-type", accept); req.onerror = function () { callback && callback(500, null); }; req.onreadystatechange = function () { if (req.readyState === _this.states.complete && callback) { callback(req.status, req.responseText); } }; if (ontimeout) { req.ontimeout = ontimeout; } req.send(body); } }; });